diff --git a/.circleci/config.yml b/.circleci/config.yml
index 663d57643ee..cc15e71e0a8 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,51 +1,115 @@
+parameters:
+
+# v2: 11m.
defaults: &defaults
+ resource_class: large
docker:
- - image: bepsays/ci-goreleaser:1.17.5
- environment:
- CGO_ENABLED: "0"
-
+ - image: bepsays/ci-hugoreleaser:1.22600.20100
+environment: &buildenv
+ GOMODCACHE: /root/project/gomodcache
version: 2
jobs:
- build:
- <<: *defaults
+ prepare_release:
+ <<: *defaults
+ environment: &buildenv
+ GOMODCACHE: /root/project/gomodcache
steps:
- - checkout:
+ - setup_remote_docker
+ - checkout:
path: hugo
+ - &git-config
+ run:
+ command: |
+ git config --global user.email "bjorn.erik.pedersen+hugoreleaser@gmail.com"
+ git config --global user.name "hugoreleaser"
- run:
- command: |
- git clone git@github.com:gohugoio/hugoDocs.git
- cd hugo
- go mod download
- sleep 5
- go mod verify
- - persist_to_workspace:
- root: .
- paths: .
- release:
- <<: *defaults
+ command: |
+ cd hugo
+ go mod download
+ go run -tags release main.go release --step 1
+ - save_cache:
+ key: git-sha-{{ .Revision }}
+ paths:
+ - hugo
+ - gomodcache
+ build_container1:
+ <<: [*defaults]
+ environment:
+ <<: [*buildenv]
steps:
- - attach_workspace:
- at: /root/project
+ - &restore-cache
+ restore_cache:
+ key: git-sha-{{ .Revision }}
- run:
- command: |
- cd hugo
- git config --global user.email "bjorn.erik.pedersen+hugoreleaser@gmail.com"
- git config --global user.name "hugoreleaser"
- go run -tags release main.go release -r ${CIRCLE_BRANCH}
-
+ no_output_timeout: 20m
+ command: |
+ mkdir -p /tmp/files/dist1
+ cd hugo
+ hugoreleaser build -paths "builds/container1/**" -workers 3 -dist /tmp/files/dist1 -chunks $CIRCLE_NODE_TOTAL -chunk-index $CIRCLE_NODE_INDEX
+ - &persist-workspace
+ persist_to_workspace:
+ root: /tmp/files
+ paths:
+ - dist1
+ - dist2
+ parallelism: 7
+ build_container2:
+ <<: [*defaults]
+ environment:
+ <<: [*buildenv]
+ docker:
+ - image: bepsays/ci-hugoreleaser-linux-arm64:1.22600.20100
+ steps:
+ - *restore-cache
+ - &attach-workspace
+ attach_workspace:
+ at: /tmp/workspace
+ - run:
+ command: |
+ mkdir -p /tmp/files/dist2
+ cd hugo
+ hugoreleaser build -paths "builds/container2/**" -workers 1 -dist /tmp/files/dist2
+ - *persist-workspace
+ archive_and_release:
+ <<: [*defaults]
+ environment:
+ <<: [*buildenv]
+ steps:
+ - *restore-cache
+ - *attach-workspace
+ - *git-config
+ - run:
+ name: Add github.com to known hosts
+ command: ssh-keyscan github.com >> ~/.ssh/known_hosts
+ - run:
+ command: |
+ cp -a /tmp/workspace/dist1/. ./hugo/dist
+ cp -a /tmp/workspace/dist2/. ./hugo/dist
+ - run:
+ command: |
+ cd hugo
+ hugoreleaser archive
+ hugoreleaser release
+ go run -tags release main.go release --step 2
workflows:
version: 2
release:
jobs:
- - build:
+ - prepare_release:
filters:
branches:
only: /release-.*/
- - hold:
- type: approval
+ - build_container1:
+ requires:
+ - prepare_release
+ - build_container2:
requires:
- - build
- - release:
+ - prepare_release
+ - archive_and_release:
context: org-global
requires:
- - hold
+ - build_container1
+ - build_container2
+
+
+
diff --git a/.gemini/config.yaml b/.gemini/config.yaml
new file mode 100644
index 00000000000..236e043ad24
--- /dev/null
+++ b/.gemini/config.yaml
@@ -0,0 +1,13 @@
+have_fun: false
+memory_config:
+ disabled: false
+code_review:
+ disable: false
+ comment_severity_threshold: HIGH
+ max_review_comments: -1
+ pull_request_opened:
+ help: true
+ summary: false
+ code_review: false
+ include_drafts: false
+ignore_patterns: []
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 0117060d461..fa27914928d 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -1,11 +1,16 @@
---
name: 'Bug report'
-labels: 'bug'
+labels: 'Bug, NeedsTriage'
assignees: ''
about: Create a report to help us improve
---
-
+
+
### What version of Hugo are you using (`hugo version`)?
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index da14802fbf7..c114b3d7f7c 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -1,8 +1,11 @@
---
name: Proposal
-about: Suggest an idea for Hugo
+about: Propose a new feature for Hugo
title: ''
-labels: 'Proposal'
+labels: 'Proposal, NeedsTriage'
assignees: ''
---
+
+
+
\ No newline at end of file
diff --git a/.github/stale.yml b/.github/stale.yml
deleted file mode 100644
index 692c59659cd..00000000000
--- a/.github/stale.yml
+++ /dev/null
@@ -1,23 +0,0 @@
-# Number of days of inactivity before an issue becomes stale
-daysUntilStale: 120
-# Number of days of inactivity before a stale issue is closed
-daysUntilClose: 30
-# Issues with these labels will never be considered stale
-exemptLabels:
- - Keep
- - Security
-# Label to use when marking an issue as stale
-staleLabel: Stale
-# Comment to post when marking an issue as stale. Set to `false` to disable
-markComment: >
- This issue has been automatically marked as stale because it has not had
- recent activity. The resources of the Hugo team are limited, and so we are asking for your help.
-
- If this is a **bug** and you can still reproduce this error on the master branch, please reply with all of the information you have about it in order to keep the issue open.
-
- If this is a **feature request**, and you feel that it is still relevant and valuable, please tell us why.
-
- This issue will automatically be closed in the near future if no further activity occurs. Thank you for all your contributions.
-
-# Comment to post when closing a stale issue. Set to `false` to disable
-closeComment: false
diff --git a/.github/workflows/image.yml b/.github/workflows/image.yml
new file mode 100644
index 00000000000..8eab2f3ee8a
--- /dev/null
+++ b/.github/workflows/image.yml
@@ -0,0 +1,49 @@
+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@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
+
+ - name: Docker meta
+ id: meta
+ uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 # v5.9.0
+ with:
+ images: ${{ env.REGISTRY_IMAGE }}
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
+
+ - name: Login to GHCR
+ # Login is only needed when the image is pushed
+ uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
+ with:
+ registry: ghcr.io
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Build and push
+ id: build
+ uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
+ 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 }}
+ build-args: HUGO_BUILD_TAGS=extended,withdeploy
\ No newline at end of file
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
new file mode 100644
index 00000000000..7af0f7ea72f
--- /dev/null
+++ b/.github/workflows/stale.yml
@@ -0,0 +1,52 @@
+name: 'Close stale and lock closed issues and PRs'
+on:
+ workflow_dispatch:
+ schedule:
+ - cron: '30 1 * * *'
+permissions:
+ contents: read
+jobs:
+ stale:
+ permissions:
+ issues: write
+ pull-requests: write
+ runs-on: ubuntu-latest
+ steps:
+ - uses: dessant/lock-threads@7de207be1d3ce97a9abe6ff1306222982d1ca9f9 # v5.0.1
+ with:
+ issue-inactive-days: 21
+ add-issue-labels: 'Outdated'
+ issue-comment: >
+ This issue has been automatically locked since there
+ has not been any recent activity after it was closed.
+ Please open a new issue for related bugs.
+ pr-comment: >
+ 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@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
+ with:
+ operations-per-run: 999
+ days-before-issue-stale: 365
+ days-before-pr-stale: 365
+ days-before-issue-close: 56
+ days-before-pr-close: 56
+ stale-issue-message: >
+ This issue has been automatically marked as stale because it has not had
+ recent activity. The resources of the Hugo team are limited, and so we are asking for your help.
+
+ If this is a **bug** and you can still reproduce this error on the master branch, please reply with all of the information you have about it in order to keep the issue open.
+
+ If this is a **feature request**, and you feel that it is still relevant and valuable, please tell us why.
+
+ This issue will automatically be closed in the near future if no further activity occurs. Thank you for all your contributions.
+ stale-pr-message: This PR has been automatically marked as stale because it has not had
+ recent activity. The resources of the Hugo team are limited, and so we are asking for your help.
+
+ Please check https://github.com/gohugoio/hugo/blob/master/CONTRIBUTING.md#code-contribution and verify that this code contribution fits with the description. If yes, tell us in a comment.
+
+ This PR will automatically be closed in the near future if no further activity occurs. Thank you for all your contributions.
+ stale-issue-label: 'Stale'
+ exempt-issue-labels: 'Keep,Security'
+ stale-pr-label: 'Stale'
+ exempt-pr-labels: 'Keep,Security'
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 32ff044973a..ec1e73ae8b8 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -1,82 +1,134 @@
-on: [push, pull_request]
+on:
+ push:
+ branches: [master]
+ pull_request:
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
+permissions:
+ contents: read
jobs:
test:
- env:
- GOPROXY: https://proxy.golang.org
- GO111MODULE: on
strategy:
matrix:
- go-version: [1.16.x, 1.17.x]
- os: [ubuntu-latest, macos-latest, windows-latest]
+ go-version: [1.25.x, 1.26.x]
+ os: [ubuntu-latest, windows-latest] # macos disabled for now because of disk space issues.
runs-on: ${{ matrix.os }}
steps:
- - name: Install Go
- uses: actions/setup-go@37335c7bb261b353407cff977110895fa0b4f7d8
- with:
- go-version: ${{ matrix.go-version }}
- - name: Install Ruby
- uses: actions/setup-ruby@5f29a1cd8dfebf420691c4c9a0e832e2fae5a526
- with:
- ruby-version: '2.7'
- - name: Install Python
- uses: actions/setup-python@3105fb18c05ddd93efea5f9e0bef7a03a6e9e7df
- with:
- python-version: '3.x'
- - name: Install Mage
- run: go get github.com/magefile/mage@07afc7d24f4d6d6442305d49552f04fbda5ccb3e
- - name: Install asciidoctor
- uses: reitzig/actions-asciidoctor@7570212ae20b63653481675fb1ff62d1073632b0
- - name: Checkout code
- uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- - name: Install docutils
- run: |
- pip install docutils
- rst2html.py --version
- - if: matrix.os == 'ubuntu-latest'
- name: Install pandoc on Linux
- run: |
+ - if: matrix.os == 'ubuntu-latest'
+ name: Free Disk Space (Ubuntu)
+ uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1
+ with:
+ tool-cache: true
+ android: true
+ dotnet: true
+ haskell: true
+ large-packages: true
+ docker-images: true
+ swap-storage: true
+ - name: Checkout code
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
+ - name: Install Go
+ uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
+ with:
+ go-version: ${{ matrix.go-version }}
+ check-latest: true
+ cache: true
+ cache-dependency-path: |
+ **/go.sum
+ **/go.mod
+ - name: Install Ruby
+ uses: ruby/setup-ruby@8aeb6ff8030dd539317f8e1769a044873b56ea71 # v1.268.0
+ with:
+ ruby-version: "3.4.5"
+ - name: Install Ruby gems
+ run: |
+ gem install asciidoctor -v "2.0.26"
+ gem install asciidoctor-diagram -v "3.1.0"
+ - name: Install GoAT
+ run: go install github.com/blampe/goat/cmd/goat@177de93b192b8ffae608e5d9ec421cc99bf68402
+ - name: Install Python
+ uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
+ with:
+ python-version: "3.x"
+ - name: Install Mage
+ run: go install github.com/magefile/mage@v1.15.0
+ - name: Install docutils
+ run: |
+ pip install docutils
+ rst2html --version
+ - if: matrix.os == 'ubuntu-latest'
+ name: Install pandoc on Linux
+ run: |
sudo apt-get update -y
sudo apt-get install -y pandoc
- - if: matrix.os == 'macos-latest'
- run: |
- brew install pandoc
- - if: matrix.os == 'windows-latest'
- run: |
- choco install pandoc
- - run: pandoc -v
- - if: matrix.os == 'ubuntu-latest'
- name: Install dart-sass-embedded Linux
- run: |
- curl -LJO https://github.com/sass/dart-sass-embedded/releases/download/1.0.0-beta.6/sass_embedded-1.0.0-beta.6-linux-x64.tar.gz;
- echo "04fc1e5e28d29a4585a701941b6dace56771d94bfbe7f9e4db28d24417ceeec3 sass_embedded-1.0.0-beta.6-linux-x64.tar.gz" | sha256sum -c;
- tar -xvf sass_embedded-1.0.0-beta.6-linux-x64.tar.gz;
- echo "$GITHUB_WORKSPACE/sass_embedded/" >> $GITHUB_PATH
- - if: matrix.os == 'macos-latest'
- name: Install dart-sass-embedded MacOS
- run: |
- curl -LJO https://github.com/sass/dart-sass-embedded/releases/download/1.0.0-beta.6/sass_embedded-1.0.0-beta.6-macos-x64.tar.gz;
- echo "b3b984675a9b04aa22f6f2302dda4191b507ac2ca124467db2dfe7e58e72fbad sass_embedded-1.0.0-beta.6-macos-x64.tar.gz" | shasum -a 256 -c;
- tar -xvf sass_embedded-1.0.0-beta.6-macos-x64.tar.gz;
- echo "$GITHUB_WORKSPACE/sass_embedded/" >> $GITHUB_PATH
- - if: matrix.os == 'windows-latest'
- name: Install dart-sass-embedded Windows
- run: |
- curl -LJO https://github.com/sass/dart-sass-embedded/releases/download/1.0.0-beta.6/sass_embedded-1.0.0-beta.6-windows-x64.zip;
- echo "6ae442129dbb3334bc21ef851261da6c0c1b560da790ca2e1350871d00ab816d sass_embedded-1.0.0-beta.6-windows-x64.zip" | sha256sum -c;
- unzip sass_embedded-1.0.0-beta.6-windows-x64.zip;
- echo "$env:GITHUB_WORKSPACE/sass_embedded/" | Out-File -FilePath $Env:GITHUB_PATH -Encoding utf-8 -Append
- - name: Test
- run: |
- mage -v test
- mage -v check;
- - name: Build Docs
- env:
- HUGO_BUILD_TAGS: extended
- HUGO_TIMEOUT: 31000
- HUGO_IGNOREERRORS: error-remote-getjson
- HUGO_SERVICES_INSTAGRAM_ACCESSTOKEN: dummytoken
- run: |
- mage -v hugo
- ./hugo -s docs/
- ./hugo --renderToMemory -s docs/
+ - if: matrix.os == 'macos-latest'
+ run: |
+ brew install pandoc
+ - if: matrix.os == 'windows-latest'
+ run: |
+ choco install pandoc
+ - run: pandoc -v
+ - if: matrix.os == 'windows-latest'
+ run: |
+ choco install mingw
+ - if: matrix.os == 'ubuntu-latest'
+ name: Install dart-sass Linux
+ run: |
+ echo "Install Dart Sass version ${SASS_VERSION} ..."
+ curl -LJO "https://github.com/sass/dart-sass/releases/download/${SASS_VERSION}/dart-sass-${SASS_VERSION}-linux-x64.tar.gz";
+ echo "${DART_SASS_SHA_LINUX} dart-sass-${SASS_VERSION}-linux-x64.tar.gz" | sha256sum -c;
+ tar -xvf "dart-sass-${SASS_VERSION}-linux-x64.tar.gz";
+ echo "$GOBIN"
+ echo "$GITHUB_WORKSPACE/dart-sass/" >> $GITHUB_PATH
+ - if: matrix.os == 'macos-latest'
+ name: Install dart-sass MacOS
+ run: |
+ echo "Install Dart Sass version ${SASS_VERSION} ..."
+ curl -LJO "https://github.com/sass/dart-sass/releases/download/${SASS_VERSION}/dart-sass-${SASS_VERSION}-macos-x64.tar.gz";
+ echo "${DART_SASS_SHA_MACOS} dart-sass-${SASS_VERSION}-macos-x64.tar.gz" | shasum -a 256 -c;
+ tar -xvf "dart-sass-${SASS_VERSION}-macos-x64.tar.gz";
+ echo "$GITHUB_WORKSPACE/dart-sass/" >> $GITHUB_PATH
+ - if: matrix.os == 'windows-latest'
+ name: Install dart-sass Windows
+ run: |
+ echo "Install Dart Sass version ${env:SASS_VERSION} ..."
+ curl -LJO "https://github.com/sass/dart-sass/releases/download/${env:SASS_VERSION}/dart-sass-${env:SASS_VERSION}-windows-x64.zip";
+ Expand-Archive -Path "dart-sass-${env:SASS_VERSION}-windows-x64.zip" -DestinationPath .;
+ echo "$env:GITHUB_WORKSPACE/dart-sass/" | Out-File -FilePath $Env:GITHUB_PATH -Encoding utf-8 -Append
+ - if: matrix.os == 'ubuntu-latest'
+ name: Install staticcheck
+ run: go install honnef.co/go/tools/cmd/staticcheck@latest
+ - if: matrix.os == 'ubuntu-latest'
+ name: Run staticcheck
+ run: |
+ export STATICCHECK_CACHE="${{ runner.temp }}/staticcheck"
+ staticcheck ./...
+ rm -rf ${{ runner.temp }}/staticcheck
+ - if: matrix.os != 'windows-latest'
+ name: Check
+ run: |
+ sass --version;
+ mage -v check;
+ env:
+ HUGO_BUILD_TAGS: extended,withdeploy
+ - if: matrix.os == 'windows-latest'
+ # See issue #11052. We limit the build to regular test (no -race flag) on Windows for now.
+ name: Test
+ run: |
+ mage -v test
+ env:
+ HUGO_BUILD_TAGS: extended,withdeploy
+ - if: matrix.os == 'ubuntu-latest'
+ name: Build for dragonfly
+ run: |
+ go install
+ go clean -i -cache
+ env:
+ GOARCH: amd64
+ GOOS: dragonfly
diff --git a/.gitignore b/.gitignore
index 1b78398ccbd..ed5bfdcb314 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,28 +1,7 @@
-/hugo
-docs/public*
-/.idea
-.vscode/*
-hugo.exe
-*.test
-*.prof
-nohup.out
-cover.out
-*.swp
-*.swo
-.DS_Store
-*~
-vendor/*/
-*.bench
-*.debug
-coverage*.out
-
-dock.sh
-
-GoBuilds
-dist
-
-hugolib/hugo_stats.json
-resources/sunset.jpg
-
-vendor
+*.test
+imports.*
+dist/
+public/
+.DS_Store
+cache/filecache/_gen/
\ No newline at end of file
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 00000000000..838f027a087
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1 @@
+tpl/tplimpl/embedded/templates/**
\ No newline at end of file
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 00000000000..42c0706b637
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,17 @@
+* Brevity is good.
+* Assume that the maintainers and readers of the code you write are Go experts:
+ * Don't use comments to explain the obvious.
+ * Use self-explanatory variable and function names.
+ * Use short variable names when the context is clear.
+* If you need to add temporary debug printing, use `hdebug.Printf`.[^1]
+* Never export symbols that's not needed outside of the package.
+* Avoid global state at (almost) all cost.
+* This is a project with a long history; assume that a similiar problem has been solved before, look hard for helper functions before creating new ones.
+* In tests, use `qt` matchers (e.g. `b.Assert(err, qt.ErrorMatches, ...)`) instead of raw `if`/`t.Fatal` checks.
+* In tests, always use the latest Hugo specification, e.g. for layouts, it's `layouts/page.html` and not `layouts/_default/single.html`, `layouts/list.html` and not `layouts/_default/list.html`
+* Brevity is good. This applies to code, comments and commit messages. Don't write a novel.
+* Use `./check.sh ./somepackage/...` when iterating.
+* Use `./check.sh` when you're done.
+
+
+[^1]: CI build fail if you forget to remove the debug printing.
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 00000000000..43c994c2d36
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1 @@
+@AGENTS.md
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 0b27703f781..26ed8c77ebd 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,3 +1,5 @@
+>**Note:** We would appreciate if you hold on with any big refactoring (like renaming deprecated Go packages), mainly because of potential for extra merge work for future coming in in the near future.
+
# Contributing to Hugo
We welcome contributions to Hugo of any kind including documentation, themes,
@@ -17,6 +19,7 @@ The Hugo community and maintainers are [very active](https://github.com/gohugoio
* [Reporting Issues](#reporting-issues)
* [Submitting Patches](#submitting-patches)
* [Code Contribution Guidelines](#code-contribution-guidelines)
+ * [AI Assistance Notice](#ai-assistance-notice)
* [Git Commit Message Guidelines](#git-commit-message-guidelines)
* [Fetching the Sources From GitHub](#fetching-the-sources-from-github)
* [Building Hugo with Your Changes](#building-hugo-with-your-changes)
@@ -50,11 +53,9 @@ Hugo has become a fully featured static site generator, so any new functionality
If it is of some complexity, the contributor is expected to maintain and support the new feature in the future (answer questions on the forum, fix any bugs etc.).
-It is recommended to open up a discussion on the [Hugo Forum](https://discourse.gohugo.io/) to get feedback on your idea before you begin.
-
Any non-trivial code change needs to update an open [issue](https://github.com/gohugoio/hugo/issues). A non-trivial code change without an issue reference with one of the labels `bug` or `enhancement` will not be merged.
-Note that we do not accept new features that require [CGO](https://github.com/golang/go/wiki/cgo).
+Note that we do not accept new features that require [CGO](https://go.dev/wiki/cgo).
We have one exception to this rule which is LibSASS.
**Bug fixes are, of course, always welcome.**
@@ -76,23 +77,37 @@ To make the contribution process as seamless as possible, we ask for the followi
* Run `go fmt`.
* Add documentation if you are adding new features or changing functionality. The docs site lives in `/docs`.
* Squash your commits into a single commit. `git rebase -i`. It’s okay to force update your pull request with `git push -f`.
- * Ensure that `mage check` succeeds. [Travis CI](https://travis-ci.org/gohugoio/hugo) (Windows, Linux and macOS) will fail the build if `mage check` fails.
+ * Ensure that `./check.sh` succeeds. Note that some tests are skipped when running locally, some because they are slow. To run these locally, do `CI_LOCAL=true ./check.sh ./somepackage/...`.
* Follow the **Git Commit Message Guidelines** below.
+## AI Assistance Notice
+
+If a substantial part of your contribution is autogenerated with AI, **this must be disclosed in the pull request**, along with the extent to which AI assistance was used.
+
+An example disclosure:
+
+> This PR was written primarily by Claude Code.
+
+When using AI assistance, we expect contributors to understand the code that is produced and be able to answer critical questions about it. Also, AI contributions from non-maintainers needs to have a fairly narrow scope (e.g. a bug fix), as we have limited review capacity.
+
### Git Commit Message Guidelines
-This [blog article](http://chris.beams.io/posts/git-commit/) is a good resource for learning how to write good commit messages,
+This [blog article](https://cbea.ms/git-commit/) is a good resource for learning how to write good commit messages,
the most important part being that each commit message should have a title/subject in imperative mood starting with a capital letter and no trailing period:
-*"Return error on wrong use of the Paginator"*, **NOT** *"returning some error."*
+*"js: Return error when option x is not set"*, **NOT** *"returning some error."*
-Also, if your commit references one or more GitHub issues, always end your commit message body with *See #1234* or *Fixes #1234*.
-Replace *1234* with the GitHub issue ID. The last example will close the issue when the commit is merged into *master*.
+Most title/subjects should have a lower-cased prefix with a colon and one whitespace. The prefix can be:
-Sometimes it makes sense to prefix the commit message with the package name (or docs folder) all lowercased ending with a colon.
-That is fine, but the rest of the rules above apply.
-So it is "tpl: Add emojify template func", not "tpl: add emojify template func.", and "docs: Document emoji", not "doc: document emoji."
+* The name of the package where (most of) the changes are made (e.g. `media: Add text/calendar`)
+* If the package name is deeply nested/long, try to shorten it from the left side, e.g. `markup/goldmark` is OK, `resources/resource_transformers/js` can be shortened to `js`.
+* If this commit touches several packages with a common functional topic, use that as a prefix, e.g. `errors: Resolve correct line numbers`)
+* If this commit touches many packages without a common functional topic, prefix with `all:` (e.g. `all: Reformat Go code`)
+* If this is a documentation update, prefix with `docs:`.
+* If nothing of the above applies, just leave the prefix out.
+* Note that the above excludes nouns seen in other repositories, e.g. "chore:".
-Please use a short and descriptive branch name, e.g. **NOT** "patch-1". It's very common but creates a naming conflict each time when a submission is pulled for a review.
+Also, if your commit references one or more GitHub issues, always end your commit message body with *See #1234* or *Fixes #1234*.
+Replace *1234* with the GitHub issue ID. The last example will close the issue when the commit is merged into *master*.
An example:
@@ -119,12 +134,10 @@ cd hugo
go install
```
->Note: Some Go tools may not be fully updated to support Go Modules yet. One example would be LiteIDE. Follow [this workaround](https://github.com/visualfc/liteide/issues/986#issuecomment-428117702) for how to continue to work with Hugo below `GOPATH`.
-
For some convenient build and test targets, you also will want to install Mage:
```bash
-go get github.com/magefile/mage
+go install github.com/magefile/mage
```
Now, to make a change to Hugo's source:
diff --git a/Dockerfile b/Dockerfile
index fcae03f5484..ee9f9cc55fc 100755
--- a/Dockerfile
+++ b/Dockerfile
@@ -2,44 +2,100 @@
# Twitter: https://twitter.com/gohugoio
# Website: https://gohugo.io/
-FROM golang:1.16-alpine AS build
+ARG GO_VERSION="1.26"
+ARG ALPINE_VERSION="3.22"
+ARG DART_SASS_VERSION="1.79.3"
-# 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 --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
-ARG CGO=1
-ENV CGO_ENABLED=${CGO}
-ENV GOOS=linux
-ENV GO111MODULE=on
+
+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
WORKDIR /go/src/github.com/gohugoio/hugo
-COPY . /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 <
-A Fast and Flexible Static Site Generator built with love by [bep](https://github.com/bep), [spf13](http://spf13.com/) and [friends](https://github.com/gohugoio/hugo/graphs/contributors) in [Go][].
+A fast and flexible static site generator built with love by [bep][], [spf13][], and [friends][] in Go.
-[Website](https://gohugo.io) |
-[Forum](https://discourse.gohugo.io) |
-[Documentation](https://gohugo.io/getting-started/) |
-[Installation Guide](https://gohugo.io/getting-started/installing/) |
-[Contribution Guide](CONTRIBUTING.md) |
-[Twitter](https://twitter.com/gohugoio)
+---
[](https://godoc.org/github.com/gohugoio/hugo)
[](https://github.com/gohugoio/hugo/actions?query=workflow%3ATest)
[](https://goreportcard.com/report/github.com/gohugoio/hugo)
+[Website][] | [Installation][] | [Documentation][] | [Support][] | [Contributing][] | Mastodon
+
## Overview
-Hugo is a static HTML and CSS website generator written in [Go][].
-It is optimized for speed, ease of use, and configurability.
-Hugo takes a directory with content and templates and renders them into a full HTML website.
+Hugo is a [static site generator][] written in Go, optimized for speed and designed for flexibility. With its advanced templating system and fast asset pipelines, Hugo renders a complete site in seconds, often less.
+
+Due to its flexible framework, multilingual support, and powerful taxonomy system, Hugo is widely used to create:
+
+- Corporate, government, nonprofit, education, news, event, and project sites
+- Documentation sites
+- Image portfolios
+- Landing pages
+- Business, professional, and personal blogs
+- Resumes and CVs
+
+Use Hugo's embedded web server during development to instantly see changes to content, structure, behavior, and presentation. Then deploy the site to your host, or push changes to your Git provider for automated builds and deployment.
+
+Hugo's fast asset pipelines include:
-Hugo relies on Markdown files with front matter for metadata, and you can run Hugo from any directory.
-This works well for shared hosts and other systems where you don’t have a privileged account.
+- CSS Processing – Bundle, transform, minify, create source maps, perform SRI hashing, and integrate with PostCSS.
+- Image processing – Convert, resize, crop, rotate, adjust colors, apply filters, overlay text and images, and extract metadata
+- JavaScript bundling – Transpile TypeScript and JSX to JavaScript, bundle, tree shake, minify, create source maps, and perform SRI hashing.
+- Sass processing – Transpile Sass to CSS, bundle, tree shake, minify, create source maps, perform SRI hashing, and integrate with PostCSS
+- Tailwind CSS processing – Compile Tailwind CSS utility classes into standard CSS, bundle, tree shake, optimize, minify, perform SRI hashing, and integrate with PostCSS
-Hugo renders a typical website of moderate size in a fraction of a second.
-A good rule of thumb is that each piece of content renders in around 1 millisecond.
+And with [Hugo Modules][], you can share content, assets, data, translations, themes, templates, and configuration with other projects via public or private Git repositories.
-Hugo is designed to work well for any kind of website including blogs, tumbles, and docs.
+See the [features][] section of the documentation for a comprehensive summary of Hugo's capabilities.
-#### Supported Architectures
+## Sponsors
-Currently, we provide pre-built Hugo binaries for Windows, Linux, FreeBSD, NetBSD, DragonFly BSD, OpenBSD, macOS (Darwin), and [Android](https://gist.github.com/bep/a0d8a26cf6b4f8bc992729b8e50b480b) for x64, i386 and ARM architectures.
+
+
+
+
+
+
-Hugo may also be compiled from source wherever the Go compiler tool chain can run, e.g. for other operating systems including Plan 9 and Solaris.
+## Editions
-**Complete documentation is available at [Hugo Documentation](https://gohugo.io/getting-started/).**
+Hugo is available in several editions. Use the standard edition unless you need additional features.
-## Choose How to Install
+Feature|standard|deploy|extended|extended/deploy
+:--|:-:|:-:|:-:|:-:
+Core features|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:
+Direct cloud deployment (1)|:x:|:heavy_check_mark:|:x:|:heavy_check_mark:
+LibSass support (2)|:x:|:x:|:heavy_check_mark:|:heavy_check_mark:
-If you want to use Hugo as your site generator, simply install the Hugo binaries.
-The Hugo binaries have no external dependencies.
+(1) Deploy your site directly to a Google Cloud Storage bucket, an AWS S3 bucket, or an Azure Storage container. See [details][].
-To contribute to the Hugo source code or documentation, you should [fork the Hugo GitHub project](https://github.com/gohugoio/hugo#fork-destination-box) and clone it to your local machine.
+(2) [Transpile Sass to CSS][] via embedded LibSass. Note that embedded LibSass was deprecated in v0.153.0 and will be removed in a future release. Use the [Dart Sass][] transpiler instead, which is compatible with any edition.
-Finally, you can install the Hugo source code with `go`, build the binaries yourself, and run Hugo that way.
-Building the binaries is an easy task for an experienced `go` getter.
+## Installation
-### Install Hugo as Your Site Generator (Binary Install)
+Install Hugo from a [prebuilt binary][], package manager, or package repository. Please see the installation instructions for your operating system:
-Use the [installation instructions in the Hugo documentation](https://gohugo.io/getting-started/installing/).
+- [macOS][]
+- [Linux][]
+- [Windows][]
+- [DragonFly BSD, FreeBSD, NetBSD, and OpenBSD][]
-### Build and Install the Binaries from Source (Advanced Install)
+## Build from source
-#### Prerequisite Tools
+To build Hugo from source you must install:
-* [Git](https://git-scm.com/)
-* [Go (we test it with the last 2 major versions; but note that Hugo 0.81.0 only builds with >= Go 1.16.)](https://golang.org/dl/)
+1. [Git][]
+1. [Go][] version 1.25.0 or later
-#### Fetch from GitHub
+### Standard edition
-Since Hugo 0.48, Hugo uses the Go Modules support built into Go 1.11 to build. The easiest is to clone Hugo in a directory outside of `GOPATH`, as in the following example:
+To build and install the standard edition:
-```bash
-mkdir $HOME/src
-cd $HOME/src
-git clone https://github.com/gohugoio/hugo.git
-cd hugo
-go install
+```sh
+CGO_ENABLED=0 go install github.com/gohugoio/hugo@latest
```
-**If you are a Windows user, substitute the `$HOME` environment variable above with `%USERPROFILE%`.**
+### Deploy edition
-If you want to compile with Sass/SCSS support use `--tags extended` and make sure `CGO_ENABLED=1` is set in your go environment. If you don't want to have CGO enabled, you may use the following command to temporarily enable CGO only for hugo compilation:
+To build and install the deploy edition:
-```bash
-CGO_ENABLED=1 go install --tags extended
+```sh
+CGO_ENABLED=0 go install -tags withdeploy github.com/gohugoio/hugo@latest
```
-## The Hugo Documentation
+### Extended edition
-The Hugo documentation now lives in its own repository, see https://github.com/gohugoio/hugoDocs. But we do keep a version of that documentation as a `git subtree` in this repository. To build the sub folder `/docs` as a Hugo site, you need to clone this repo:
+To build and install the extended edition, first install a C compiler such as [GCC][] or [Clang][] and then run the following command.
-```bash
-git clone git@github.com:gohugoio/hugo.git
+```sh
+CGO_ENABLED=1 go install -tags extended github.com/gohugoio/hugo@latest
```
-## Contributing to Hugo
-For a complete guide to contributing to Hugo, see the [Contribution Guide](CONTRIBUTING.md).
+### Extended/deploy edition
-We welcome contributions to Hugo of any kind including documentation, themes,
-organization, tutorials, blog posts, bug reports, issues, feature requests,
-feature implementations, pull requests, answering questions on the forum,
-helping to manage issues, etc.
+To build and install the extended/deploy edition, first install a C compiler such as [GCC][] or [Clang][] and then run the following command.
-The Hugo community and maintainers are [very active](https://github.com/gohugoio/hugo/pulse/monthly) and helpful, and the project benefits greatly from this activity.
+```sh
+CGO_ENABLED=1 go install -tags extended,withdeploy github.com/gohugoio/hugo@latest
+```
-### Asking Support Questions
+## Star History
-We have an active [discussion forum](https://discourse.gohugo.io) where users and developers can ask questions.
-Please don't use the GitHub issue tracker to ask questions.
+[](https://star-history.com/#gohugoio/hugo&Timeline)
-### Reporting Issues
+## Documentation
-If you believe you have found a defect in Hugo or its documentation, use
-the GitHub issue tracker to report the problem to the Hugo maintainers.
-If you're not sure if it's a bug or not, start by asking in the [discussion forum](https://discourse.gohugo.io).
-When reporting the issue, please provide the version of Hugo in use (`hugo version`).
+Hugo's [documentation][] includes installation instructions, a quick start guide, conceptual explanations, reference information, and examples.
-### Submitting Patches
+Please submit documentation issues and pull requests to the [documentation repository][].
-The Hugo project welcomes all contributors and contributions regardless of skill or experience level.
-If you are interested in helping with the project, we will help you with your contribution.
-Hugo is a very active project with many contributions happening daily.
+## Support
-We want to create the best possible product for our users and the best contribution experience for our developers,
-we have a set of guidelines which ensure that all contributions are acceptable.
-The guidelines are not intended as a filter or barrier to participation.
-If you are unfamiliar with the contribution process, the Hugo team will help you and teach you how to bring your contribution in accordance with the guidelines.
+Please **do not use the issue queue** for questions or troubleshooting. Unless you are certain that your issue is a software defect, use the [forum][].
-For a complete guide to contributing code to Hugo, see the [Contribution Guide](CONTRIBUTING.md).
+Hugo's [forum][] is an active community of users and developers who answer questions, share knowledge, and provide examples. A quick search of over 20,000 topics will often answer your question. Please be sure to read about [requesting help][] before asking your first question.
-[Go]: https://golang.org/
-[Hugo Documentation]: https://gohugo.io/overview/introduction/
+## Contributing
-## Dependencies
+You can contribute to the Hugo project by:
-Hugo stands on the shoulder of many great open source libraries.
+- Answering questions on the [forum][]
+- Improving the [documentation][]
+- Monitoring the [issue queue][]
+- Creating or improving [themes][]
+- Squashing [bugs][]
-If you run `hugo env -v` you will get a complete and up to date list.
+Please submit documentation issues and pull requests to the [documentation repository][].
-In Hugo 0.89.0 that list is, in lexical order:
+If you have an idea for an enhancement or new feature, create a new topic on the [forum][] in the "Feature" category. This will help you to:
-```
-cloud.google.com/go/storage="v1.10.0"
-cloud.google.com/go="v0.87.0"
-github.com/Azure/azure-pipeline-go="v0.2.2"
-github.com/Azure/azure-storage-blob-go="v0.9.0"
+- Determine if the capability already exists
+- Measure interest
+- Refine the concept
+
+If there is sufficient interest, [create a proposal][]. Do not submit a pull request until the project lead accepts the proposal.
+
+For a complete guide to contributing to Hugo, see the [Contribution Guide](CONTRIBUTING.md).
+
+## License
+
+For the Hugo source code, see [LICENSE](/LICENSE).
+
+We also bundle some libraries in binary/WASM form:
+
+- [libwebp](https://github.com/webmproject/libwebp), [BSD-3-Clause license](https://github.com/webmproject/libwebp?tab=BSD-3-Clause-1-ov-file#readme)
+- [Katex](https://github.com/KaTeX/KaTeX), [MIT license](https://github.com/KaTeX/KaTeX?tab=MIT-1-ov-file#readme)
+- [QuickJS](https://github.com/bellard/quickjs?tab=License-1-ov-file#readme), [License](https://github.com/bellard/quickjs?tab=License-1-ov-file#readme)
+
+## Dependencies
+
+Hugo stands on the shoulders of great open source libraries. Run `hugo env --logLevel info` to display a list of dependencies.
+
+
+See current dependencies
+
+```text
github.com/BurntSushi/locker="v0.0.0-20171006230638-a6e239ea1c69"
-github.com/BurntSushi/toml="v0.3.1"
-github.com/PuerkitoBio/purell="v1.1.1"
-github.com/PuerkitoBio/urlesc="v0.0.0-20170810143723-de5bf2ad4578"
-github.com/alecthomas/chroma="v0.9.4"
-github.com/armon/go-radix="v1.0.0"
-github.com/aws/aws-sdk-go="v1.41.14"
+github.com/JohannesKaufmann/dom="v0.2.0"
+github.com/JohannesKaufmann/html-to-markdown/v2="v2.5.0"
+github.com/alecthomas/chroma/v2="v2.21.1"
+github.com/aymerick/douceur="v0.2.0"
+github.com/bep/clocks="v0.5.0"
github.com/bep/debounce="v1.2.0"
-github.com/bep/gitmap="v1.1.2"
-github.com/bep/godartsass="v0.12.0"
-github.com/bep/golibsass="v1.0.0"
-github.com/bep/gowebp="v0.1.0"
+github.com/bep/gitmap="v1.9.0"
+github.com/bep/goat="v0.5.0"
+github.com/bep/godartsass/v2="v2.5.0"
+github.com/bep/golibsass="v1.2.0"
+github.com/bep/goportabletext="v0.1.0"
+github.com/bep/helpers="v0.6.0"
+github.com/bep/imagemeta="v0.12.0"
+github.com/bep/lazycache="v0.8.0"
+github.com/bep/logg="v0.4.0"
+github.com/bep/mclib="v1.20400.20402"
+github.com/bep/overlayfs="v0.10.0"
+github.com/bep/simplecobra="v0.6.1"
+github.com/bep/textandbinarywriter="v0.0.0-20251212174530-cd9f0732f60f"
github.com/bep/tmc="v0.5.1"
-github.com/cli/safeexec="v1.0.0"
-github.com/cpuguy83/go-md2man/v2="v2.0.0"
+github.com/bits-and-blooms/bitset="v1.24.4"
+github.com/cespare/xxhash/v2="v2.3.0"
+github.com/clbanning/mxj/v2="v2.7.0"
+github.com/clipperhouse/displaywidth="v0.6.0"
+github.com/clipperhouse/stringish="v0.1.1"
+github.com/clipperhouse/uax29/v2="v2.3.0"
+github.com/cpuguy83/go-md2man/v2="v2.0.6"
github.com/disintegration/gift="v1.2.1"
-github.com/dlclark/regexp2="v1.4.0"
-github.com/dustin/go-humanize="v1.0.0"
-github.com/evanw/esbuild="v0.13.12"
-github.com/fsnotify/fsnotify="v1.5.1"
-github.com/getkin/kin-openapi="v0.80.0"
-github.com/ghodss/yaml="v1.0.0"
-github.com/go-openapi/jsonpointer="v0.19.5"
-github.com/go-openapi/swag="v0.19.5"
-github.com/gobuffalo/flect="v0.2.3"
+github.com/dlclark/regexp2="v1.11.5"
+github.com/evanw/esbuild="v0.27.2"
+github.com/fatih/color="v1.18.0"
+github.com/frankban/quicktest="v1.14.6"
+github.com/fsnotify/fsnotify="v1.9.0"
+github.com/getkin/kin-openapi="v0.133.0"
+github.com/go-openapi/jsonpointer="v0.21.0"
+github.com/go-openapi/swag="v0.23.0"
+github.com/gobuffalo/flect="v1.0.3"
github.com/gobwas/glob="v0.2.3"
-github.com/gohugoio/go-i18n/v2="v2.1.3-0.20210430103248-4c28c89f8013"
+github.com/goccy/go-yaml="v1.19.1"
+github.com/gohugoio/go-i18n/v2="v2.1.3-0.20251018145728-cfcc22d823c6"
+github.com/gohugoio/go-radix="v1.2.0"
+github.com/gohugoio/hashstructure="v0.6.0"
+github.com/gohugoio/httpcache="v0.8.0"
+github.com/gohugoio/hugo-goldmark-extensions/extras="v0.5.0"
+github.com/gohugoio/hugo-goldmark-extensions/passthrough="v0.3.1"
github.com/gohugoio/locales="v0.14.0"
-github.com/gohugoio/localescompressed="v0.14.0"
-github.com/golang/groupcache="v0.0.0-20200121045136-8c9f03a8e57e"
-github.com/golang/protobuf="v1.5.2"
-github.com/google/go-cmp="v0.5.6"
-github.com/google/uuid="v1.1.2"
-github.com/google/wire="v0.4.0"
-github.com/googleapis/gax-go/v2="v2.0.5"
-github.com/googleapis/gax-go="v2.0.2+incompatible"
-github.com/gorilla/websocket="v1.4.2"
-github.com/inconshreveable/mousetrap="v1.0.0"
+github.com/gohugoio/localescompressed="v1.0.1"
+github.com/google/go-cmp="v0.7.0"
+github.com/gorilla/css="v1.0.1"
+github.com/gorilla/websocket="v1.5.3"
+github.com/hairyhenderson/go-codeowners="v0.7.0"
+github.com/hashicorp/golang-lru/v2="v2.0.7"
github.com/jdkato/prose="v1.2.1"
-github.com/jmespath/go-jmespath="v0.4.0"
-github.com/kyokomi/emoji/v2="v2.2.8"
-github.com/mailru/easyjson="v0.0.0-20190626092158-b2ccc519800e"
-github.com/mattn/go-ieproxy="v0.0.1"
-github.com/mattn/go-isatty="v0.0.14"
-github.com/mattn/go-runewidth="v0.0.9"
-github.com/miekg/mmark="v1.3.6"
-github.com/mitchellh/hashstructure="v1.1.0"
-github.com/mitchellh/mapstructure="v1.4.2"
+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/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.19"
+github.com/microcosm-cc/bluemonday="v1.0.27"
+github.com/mitchellh/mapstructure="v1.5.1-0.20231216201459-8508981c8b6c"
+github.com/mohae/deepcopy="v0.0.0-20170929034955-c48cc78d4826"
github.com/muesli/smartcrop="v0.3.0"
-github.com/niklasfasching/go-org="v1.5.0"
-github.com/olekukonko/tablewriter="v0.0.5"
-github.com/pelletier/go-toml/v2="v2.0.0-beta.3.0.20210727221244-fa0796069526"
+github.com/niklasfasching/go-org="v1.9.1"
+github.com/oasdiff/yaml3="v0.0.0-20250309153720-d2182401db90"
+github.com/oasdiff/yaml="v0.0.0-20250309154309-f31be36b4037"
+github.com/olekukonko/cat="v0.0.0-20250911104152-50322a0618f6"
+github.com/olekukonko/errors="v1.1.0"
+github.com/olekukonko/ll="v0.1.3"
+github.com/olekukonko/tablewriter="v1.1.2"
+github.com/pbnjay/memory="v0.0.0-20210728143218-7b4eea64cf58"
+github.com/pelletier/go-toml/v2="v2.2.4"
+github.com/perimeterx/marshmallow="v1.1.5"
+github.com/pkg/browser="v0.0.0-20240102092130-5ac0b6a4141c"
github.com/pkg/errors="v0.9.1"
-github.com/rogpeppe/go-internal="v1.8.0"
-github.com/russross/blackfriday/v2="v2.0.1"
-github.com/russross/blackfriday="v1.5.3-0.20200218234912-41c5fccfd6f6"
-github.com/rwcarlsen/goexif="v0.0.0-20190401172101-9e8deecbddbd"
-github.com/sanity-io/litter="v1.5.1"
-github.com/sass/libsass="3.6.5"
-github.com/shurcooL/sanitized_anchor_name="v1.0.0"
-github.com/spf13/afero="v1.6.0"
-github.com/spf13/cast="v1.4.1"
-github.com/spf13/cobra="v1.2.1"
-github.com/spf13/fsync="v0.9.0"
-github.com/spf13/jwalterweatherman="v1.1.0"
-github.com/spf13/pflag="v1.0.5"
-github.com/tdewolff/minify/v2="v2.9.22"
-github.com/tdewolff/parse/v2="v2.5.21"
-github.com/webmproject/libwebp="v1.2.0"
-github.com/yuin/goldmark-highlighting="v0.0.0-20200307114337-60d527fdb691"
-github.com/yuin/goldmark="v1.4.2"
-go.opencensus.io="v0.23.0"
-gocloud.dev="v0.20.0"
-golang.org/x/image="v0.0.0-20210220032944-ac19c3e999fb"
-golang.org/x/net="v0.0.0-20210614182718-04defd469f4e"
-golang.org/x/oauth2="v0.0.0-20210628180205-a41e5a781914"
-golang.org/x/sync="v0.0.0-20210220032951-036812b2e83c"
-golang.org/x/sys="v0.0.0-20210908233432-aa78b53d3365"
-golang.org/x/text="v0.3.7"
-golang.org/x/xerrors="v0.0.0-20200804184101-5ec99f83aff1"
-google.golang.org/api="v0.51.0"
-google.golang.org/genproto="v0.0.0-20210716133855-ce7ef5c701ea"
-google.golang.org/grpc="v1.39.0"
-google.golang.org/protobuf="v1.27.1"
-gopkg.in/yaml.v2="v2.4.0"
+github.com/rogpeppe/go-internal="v1.14.1"
+github.com/russross/blackfriday/v2="v2.1.0"
+github.com/sass/dart-sass/compiler="1.97.1"
+github.com/sass/dart-sass/implementation="1.97.1"
+github.com/sass/dart-sass/protocol="3.2.0"
+github.com/spf13/afero="v1.15.0"
+github.com/spf13/cast="v1.10.0"
+github.com/spf13/cobra="v1.10.2"
+github.com/spf13/fsync="v0.10.1"
+github.com/spf13/pflag="v1.0.9"
+github.com/tdewolff/minify/v2="v2.24.8"
+github.com/tdewolff/parse/v2="v2.8.5"
+github.com/tetratelabs/wazero="v1.10.1"
+github.com/webmproject/libwebp="v1.6.0"
+github.com/woodsbury/decimal128="v1.3.0"
+github.com/yuin/goldmark-emoji="v1.0.6"
+github.com/yuin/goldmark="v1.7.13"
+go.uber.org/automaxprocs="v1.5.3"
+go.yaml.in/yaml/v3="v3.0.4"
+golang.org/x/crypto="v0.46.0"
+golang.org/x/image="v0.34.0"
+golang.org/x/mod="v0.31.0"
+golang.org/x/net="v0.48.0"
+golang.org/x/sync="v0.19.0"
+golang.org/x/sys="v0.39.0"
+golang.org/x/text="v0.32.0"
+golang.org/x/tools="v0.40.0"
+google.golang.org/protobuf="v1.36.10"
+gopkg.in/yaml.v3="v3.0.1"
+rsc.io/qr="v0.2.0"
+software.sslmate.com/src/go-pkcs12="v0.2.0"
```
-
+
diff --git a/SECURITY.md b/SECURITY.md
index 320b2ff54fa..6ac90f07293 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -4,4 +4,4 @@
Please report (suspected) security vulnerabilities to **[bjorn.erik.pedersen@gmail.com](mailto:bjorn.erik.pedersen@gmail.com)**. You will receive a response from us within 48 hours. If we can confirm the issue, we will release a patch as soon as possible depending on the complexity of the issue but historically within days.
-Also see [Hugo's Security Model](https://gohugo.io/about/security-model/).
+Also see [Hugo's Security Model](https://gohugo.io/about/security/).
diff --git a/bench.sh b/bench.sh
deleted file mode 100755
index c6a20a7e315..00000000000
--- a/bench.sh
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/usr/bin/env bash
-
-# allow user to override go executable by running as GOEXE=xxx make ...
-GOEXE="${GOEXE-go}"
-
-# Convenience script to
-# - For a given branch
-# - Run benchmark tests for a given package
-# - Do the same for master
-# - then compare the two runs with benchcmp
-
-benchFilter=".*"
-
-if (( $# < 2 ));
- then
- echo "USAGE: ./bench.sh (and (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
diff --git a/benchSite.sh b/benchSite.sh
deleted file mode 100755
index aae21231c7f..00000000000
--- a/benchSite.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/bash
-
-# allow user to override go executable by running as GOEXE=xxx make ...
-GOEXE="${GOEXE-go}"
-
-# Send in a regexp matching the benchmarks you want to run, i.e. './benchSite.sh "YAML"'.
-# Note the quotes, which will be needed for more complex expressions.
-# The above will run all variations, but only for front matter YAML.
-
-echo "Running with BenchmarkSiteBuilding/${1}"
-
-"${GOEXE}" test -run="NONE" -bench="BenchmarkSiteBuilding/${1}" -test.benchmem=true ./hugolib -memprofile mem.prof -count 3 -cpuprofile cpu.prof
diff --git a/benchbep.sh b/benchbep.sh
deleted file mode 100755
index efd616c8859..00000000000
--- a/benchbep.sh
+++ /dev/null
@@ -1 +0,0 @@
-gobench -package=./hugolib -bench="BenchmarkSiteNew/Deep_content_tree"
\ No newline at end of file
diff --git a/bepdock.sh b/bepdock.sh
deleted file mode 100755
index a7ac0c63969..00000000000
--- a/bepdock.sh
+++ /dev/null
@@ -1 +0,0 @@
-docker run --rm --mount type=bind,source="$(pwd)",target=/hugo -w /hugo -i -t bepsays/ci-goreleaser:1.11-2 /bin/bash
\ No newline at end of file
diff --git a/bufferpool/bufpool.go b/bufferpool/bufpool.go
index c1e4105d0eb..f05675e3e01 100644
--- a/bufferpool/bufpool.go
+++ b/bufferpool/bufpool.go
@@ -20,7 +20,7 @@ import (
)
var bufferPool = &sync.Pool{
- New: func() interface{} {
+ New: func() any {
return &bytes.Buffer{}
},
}
diff --git a/cache/docs.go b/cache/docs.go
new file mode 100644
index 00000000000..b9c49840fad
--- /dev/null
+++ b/cache/docs.go
@@ -0,0 +1,2 @@
+// Package cache contains the different cache implementations.
+package cache
diff --git a/cache/dynacache/dynacache.go b/cache/dynacache/dynacache.go
new file mode 100644
index 00000000000..54bbf3a9b9c
--- /dev/null
+++ b/cache/dynacache/dynacache.go
@@ -0,0 +1,647 @@
+// 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 dynacache
+
+import (
+ "context"
+ "fmt"
+ "math"
+ "path"
+ "regexp"
+ "runtime"
+ "sync"
+ "time"
+
+ "github.com/bep/lazycache"
+ "github.com/bep/logg"
+ "github.com/gohugoio/hugo/common/collections"
+ "github.com/gohugoio/hugo/common/herrors"
+ "github.com/gohugoio/hugo/common/loggers"
+ "github.com/gohugoio/hugo/common/paths"
+ "github.com/gohugoio/hugo/common/rungroup"
+ "github.com/gohugoio/hugo/config"
+ "github.com/gohugoio/hugo/helpers"
+ "github.com/gohugoio/hugo/identity"
+ "github.com/gohugoio/hugo/resources/resource"
+)
+
+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 {
+ opts.CheckInterval = time.Second * 2
+ }
+
+ if opts.MaxSize == 0 {
+ opts.MaxSize = 100000
+ }
+ if opts.Log == nil {
+ panic("nil Log")
+ }
+
+ if opts.MinMaxSize == 0 {
+ opts.MinMaxSize = 30
+ }
+
+ stats := &stats{
+ opts: opts,
+ adjustmentFactor: 1.0,
+ currentMaxSize: opts.MaxSize,
+ availableMemory: config.GetMemoryLimit(),
+ }
+
+ infol := opts.Log.InfoCommand("dynacache")
+
+ evictedIdentities := collections.NewStackThreadSafe[KeyIdentity]()
+
+ 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})
+ return false
+ })
+ resource.MarkStale(v)
+ }
+
+ c := &Cache{
+ partitions: make(map[string]PartitionManager),
+ onEvict: onEvict,
+ evictedIdentities: evictedIdentities,
+ opts: opts,
+ stats: stats,
+ infol: infol,
+ }
+
+ c.stop = c.start()
+
+ return c
+}
+
+// Options for the cache.
+type Options struct {
+ Log loggers.Logger
+ CheckInterval time.Duration
+ MaxSize int
+ MinMaxSize int
+ Watching bool
+}
+
+// Options for a partition.
+type OptionsPartition struct {
+ // When to clear the this partition.
+ ClearWhen ClearWhen
+
+ // Weight is a number between 1 and 100 that indicates how, in general, how big this partition may get.
+ Weight int
+}
+
+func (o OptionsPartition) WeightFraction() float64 {
+ return float64(o.Weight) / 100
+}
+
+func (o OptionsPartition) CalculateMaxSize(maxSizePerPartition int) int {
+ return int(math.Floor(float64(maxSizePerPartition) * o.WeightFraction()))
+}
+
+// A dynamic partitioned cache.
+type Cache struct {
+ mu sync.RWMutex
+
+ partitions map[string]PartitionManager
+
+ onEvict func(k, v any)
+ evictedIdentities *collections.StackThreadSafe[KeyIdentity]
+
+ opts Options
+ infol logg.LevelLogger
+
+ stats *stats
+ stopOnce sync.Once
+ stop func()
+}
+
+// DrainEvictedIdentities drains the evicted identities from the cache.
+func (c *Cache) DrainEvictedIdentities() []KeyIdentity {
+ return c.evictedIdentities.Drain()
+}
+
+// DrainEvictedIdentitiesMatching drains the evicted identities from the cache that match the given predicate.
+func (c *Cache) DrainEvictedIdentitiesMatching(predicate func(KeyIdentity) bool) []KeyIdentity {
+ return c.evictedIdentities.DrainMatching(predicate)
+}
+
+// ClearMatching clears all partition for which the predicate returns true.
+func (c *Cache) ClearMatching(predicatePartition func(k string, p PartitionManager) bool, predicateValue func(k, v any) bool) {
+ if predicatePartition == nil {
+ predicatePartition = func(k string, p PartitionManager) bool { return true }
+ }
+ if predicateValue == nil {
+ panic("nil predicateValue")
+ }
+ g := rungroup.Run[PartitionManager](context.Background(), rungroup.Config[PartitionManager]{
+ NumWorkers: len(c.partitions),
+ Handle: func(ctx context.Context, partition PartitionManager) error {
+ partition.clearMatching(predicateValue)
+ return nil
+ },
+ })
+
+ for k, p := range c.partitions {
+ if !predicatePartition(k, p) {
+ continue
+ }
+ g.Enqueue(p)
+ }
+
+ g.Wait()
+}
+
+// ClearOnRebuild prepares the cache for a new rebuild taking the given changeset into account.
+// predicate is optional and will clear any entry for which it returns true.
+func (c *Cache) ClearOnRebuild(predicate func(k, v any) bool, changeset ...identity.Identity) {
+ g := rungroup.Run[PartitionManager](context.Background(), rungroup.Config[PartitionManager]{
+ NumWorkers: len(c.partitions),
+ Handle: func(ctx context.Context, partition PartitionManager) error {
+ partition.clearOnRebuild(predicate, changeset...)
+ return nil
+ },
+ })
+
+ for _, p := range c.partitions {
+ g.Enqueue(p)
+ }
+
+ g.Wait()
+
+ // Clear any entries marked as stale above.
+ g = rungroup.Run[PartitionManager](context.Background(), rungroup.Config[PartitionManager]{
+ NumWorkers: len(c.partitions),
+ Handle: func(ctx context.Context, partition PartitionManager) error {
+ partition.clearStale()
+ return nil
+ },
+ })
+
+ for _, p := range c.partitions {
+ g.Enqueue(p)
+ }
+
+ g.Wait()
+}
+
+type keysProvider interface {
+ Keys() []string
+}
+
+// Keys returns a list of keys in all partitions.
+func (c *Cache) Keys(predicate func(s string) bool) []string {
+ if predicate == nil {
+ predicate = func(s string) bool { return true }
+ }
+ var keys []string
+ for pn, g := range c.partitions {
+ pkeys := g.(keysProvider).Keys()
+ for _, k := range pkeys {
+ p := path.Join(pn, k)
+ if predicate(p) {
+ keys = append(keys, p)
+ }
+ }
+
+ }
+ return keys
+}
+
+func calculateMaxSizePerPartition(maxItemsTotal, totalWeightQuantity, numPartitions int) int {
+ if numPartitions == 0 {
+ panic("numPartitions must be > 0")
+ }
+ if totalWeightQuantity == 0 {
+ panic("totalWeightQuantity must be > 0")
+ }
+
+ avgWeight := float64(totalWeightQuantity) / float64(numPartitions)
+ return int(math.Floor(float64(maxItemsTotal) / float64(numPartitions) * (100.0 / avgWeight)))
+}
+
+// Stop stops the cache.
+func (c *Cache) Stop() {
+ c.stopOnce.Do(func() {
+ c.stop()
+ })
+}
+
+func (c *Cache) adjustCurrentMaxSize() {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
+ if len(c.partitions) == 0 {
+ return
+ }
+ var m runtime.MemStats
+ runtime.ReadMemStats(&m)
+ s := c.stats
+ s.memstatsCurrent = m
+ // fmt.Printf("\n\nAvailable = %v\nAlloc = %v\nTotalAlloc = %v\nSys = %v\nNumGC = %v\nMaxSize = %d\nAdjustmentFactor=%f\n\n", helpers.FormatByteCount(s.availableMemory), helpers.FormatByteCount(m.Alloc), helpers.FormatByteCount(m.TotalAlloc), helpers.FormatByteCount(m.Sys), m.NumGC, c.stats.currentMaxSize, s.adjustmentFactor)
+
+ if s.availableMemory >= s.memstatsCurrent.Alloc {
+ if s.adjustmentFactor <= 1.0 {
+ s.adjustmentFactor += 0.2
+ }
+ } else {
+ // We're low on memory.
+ s.adjustmentFactor -= 0.4
+ }
+
+ if s.adjustmentFactor <= 0 {
+ s.adjustmentFactor = 0.05
+ }
+
+ if !s.adjustCurrentMaxSize() {
+ return
+ }
+
+ totalWeight := 0
+ for _, pm := range c.partitions {
+ totalWeight += pm.getOptions().Weight
+ }
+
+ maxSizePerPartition := calculateMaxSizePerPartition(c.stats.currentMaxSize, totalWeight, len(c.partitions))
+
+ evicted := 0
+ for _, p := range c.partitions {
+ evicted += p.adjustMaxSize(p.getOptions().CalculateMaxSize(maxSizePerPartition))
+ }
+
+ if evicted > 0 {
+ c.infol.
+ WithFields(
+ logg.Fields{
+ {Name: "evicted", Value: evicted},
+ {Name: "numGC", Value: m.NumGC},
+ {Name: "limit", Value: helpers.FormatByteCount(c.stats.availableMemory)},
+ {Name: "alloc", Value: helpers.FormatByteCount(m.Alloc)},
+ {Name: "totalAlloc", Value: helpers.FormatByteCount(m.TotalAlloc)},
+ },
+ ).Logf("adjusted partitions' max size")
+ }
+}
+
+func (c *Cache) start() func() {
+ ticker := time.NewTicker(c.opts.CheckInterval)
+ quit := make(chan struct{})
+
+ go func() {
+ for {
+ select {
+ case <-ticker.C:
+ c.adjustCurrentMaxSize()
+ // Reset the ticker to avoid drift.
+ ticker.Reset(c.opts.CheckInterval)
+ case <-quit:
+ ticker.Stop()
+ return
+ }
+ }
+ }()
+
+ return func() {
+ close(quit)
+ }
+}
+
+var partitionNameRe = regexp.MustCompile(`^\/[a-zA-Z0-9]{4}(\/[a-zA-Z0-9]+)?(\/[a-zA-Z0-9]+)?`)
+
+// GetOrCreatePartition gets or creates a partition with the given name.
+func GetOrCreatePartition[K comparable, V any](c *Cache, name string, opts OptionsPartition) *Partition[K, V] {
+ if c == nil {
+ panic("nil Cache")
+ }
+ if opts.Weight < 1 || opts.Weight > 100 {
+ panic("invalid Weight, must be between 1 and 100")
+ }
+
+ if !partitionNameRe.MatchString(name) {
+ panic(fmt.Sprintf("invalid partition name %q", name))
+ }
+
+ c.mu.RLock()
+ p, found := c.partitions[name]
+ c.mu.RUnlock()
+ if found {
+ return p.(*Partition[K, V])
+ }
+
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ // Double check.
+ p, found = c.partitions[name]
+ if found {
+ return p.(*Partition[K, V])
+ }
+
+ // At this point, we don't know the number of partitions or their configuration, but
+ // this will be re-adjusted later.
+ const numberOfPartitionsEstimate = 10
+ maxSize := opts.CalculateMaxSize(c.opts.MaxSize / numberOfPartitionsEstimate)
+
+ onEvict := func(k K, v V) {
+ c.onEvict(k, v)
+ }
+
+ // Create a new partition and cache it.
+ partition := &Partition[K, V]{
+ c: lazycache.New(lazycache.Options[K, V]{MaxEntries: maxSize, OnEvict: onEvict}),
+ maxSize: maxSize,
+ trace: c.opts.Log.Logger().WithLevel(logg.LevelTrace).WithField("partition", name),
+ opts: opts,
+ }
+
+ c.partitions[name] = partition
+
+ return partition
+}
+
+// Partition is a partition in the cache.
+type Partition[K comparable, V any] struct {
+ c *lazycache.Cache[K, V]
+
+ zero V
+
+ trace logg.LevelLogger
+ opts OptionsPartition
+
+ maxSize int
+}
+
+// GetOrCreate gets or creates a value for the given key.
+func (p *Partition[K, V]) GetOrCreate(key K, create func(key K) (V, error)) (V, error) {
+ v, err := p.doGetOrCreate(key, create)
+ if err != nil {
+ return p.zero, err
+ }
+ if resource.StaleVersion(v) > 0 {
+ p.c.Delete(key)
+ return p.doGetOrCreate(key, create)
+ }
+ return v, err
+}
+
+func (p *Partition[K, V]) doGetOrCreate(key K, create func(key K) (V, error)) (V, error) {
+ v, _, err := p.c.GetOrCreate(key, create)
+ return v, err
+}
+
+func (p *Partition[K, V]) GetOrCreateWitTimeout(key K, duration time.Duration, create func(key K) (V, error)) (V, error) {
+ v, err := p.doGetOrCreateWitTimeout(key, duration, create)
+ if err != nil {
+ return p.zero, err
+ }
+ if resource.StaleVersion(v) > 0 {
+ p.c.Delete(key)
+ return p.doGetOrCreateWitTimeout(key, duration, create)
+ }
+ return v, err
+}
+
+// GetOrCreateWitTimeout gets or creates a value for the given key and times out if the create function
+// takes too long.
+func (p *Partition[K, V]) doGetOrCreateWitTimeout(key K, duration time.Duration, create func(key K) (V, error)) (V, error) {
+ resultch := make(chan V, 1)
+ 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)
+ }()
+
+ select {
+ case v := <-resultch:
+ return v, nil
+ case err := <-errch:
+ return p.zero, err
+ case <-time.After(duration):
+ return p.zero, &herrors.TimeoutError{
+ Duration: duration,
+ }
+ }
+}
+
+func (p *Partition[K, V]) clearMatching(predicate func(k, v any) bool) {
+ p.c.DeleteFunc(func(key K, v V) bool {
+ if predicate(key, v) {
+ p.trace.Log(
+ logg.StringFunc(
+ func() string {
+ return fmt.Sprintf("clearing cache key %v", key)
+ },
+ ),
+ )
+ return true
+ }
+ return false
+ })
+}
+
+func (p *Partition[K, V]) clearOnRebuild(predicate func(k, v any) bool, changeset ...identity.Identity) {
+ if predicate == nil {
+ predicate = func(k, v any) bool {
+ return false
+ }
+ }
+ opts := p.getOptions()
+ if opts.ClearWhen == ClearNever {
+ return
+ }
+
+ if opts.ClearWhen == ClearOnRebuild {
+ // Clear all.
+ p.Clear()
+ return
+ }
+
+ depsFinder := identity.NewFinder(identity.FinderConfig{})
+
+ shouldDelete := func(key K, v V) bool {
+ // We always clear elements marked as stale.
+ if resource.StaleVersion(v) > 0 {
+ return true
+ }
+
+ // Now check if this entry has changed based on the changeset
+ // based on filesystem events.
+ if len(changeset) == 0 {
+ // Nothing changed.
+ return false
+ }
+
+ var probablyDependent bool
+ identity.WalkIdentitiesShallow(v, func(level int, id2 identity.Identity) bool {
+ for _, id := range changeset {
+ if r := depsFinder.Contains(id, id2, -1); r > 0 {
+ // It's probably dependent, evict from cache.
+ probablyDependent = true
+ return true
+ }
+ }
+ return false
+ })
+
+ return probablyDependent
+ }
+
+ // First pass.
+ // Second pass needs to be done in a separate loop to catch any
+ // elements marked as stale in the other partitions.
+ p.c.DeleteFunc(func(key K, v V) bool {
+ if predicate(key, v) || shouldDelete(key, v) {
+ p.trace.Log(
+ logg.StringFunc(
+ func() string {
+ return fmt.Sprintf("first pass: clearing cache key %v", key)
+ },
+ ),
+ )
+ return true
+ }
+ return false
+ })
+}
+
+func (p *Partition[K, V]) Keys() []K {
+ var keys []K
+ p.c.DeleteFunc(func(key K, v V) bool {
+ keys = append(keys, key)
+ return false
+ })
+ return keys
+}
+
+func (p *Partition[K, V]) clearStale() {
+ p.c.DeleteFunc(func(key K, v V) bool {
+ staleVersion := resource.StaleVersion(v)
+ if staleVersion > 0 {
+ p.trace.Log(
+ logg.StringFunc(
+ func() string {
+ return fmt.Sprintf("second pass: clearing cache key %v", key)
+ },
+ ),
+ )
+ }
+
+ return staleVersion > 0
+ })
+}
+
+// adjustMaxSize adjusts the max size of the and returns the number of items evicted.
+func (p *Partition[K, V]) adjustMaxSize(newMaxSize int) int {
+ if newMaxSize < minMaxSize {
+ newMaxSize = minMaxSize
+ }
+ oldMaxSize := p.maxSize
+ if newMaxSize == oldMaxSize {
+ return 0
+ }
+ p.maxSize = newMaxSize
+ // fmt.Println("Adjusting max size of partition from", oldMaxSize, "to", newMaxSize)
+ return p.c.Resize(newMaxSize)
+}
+
+func (p *Partition[K, V]) getMaxSize() int {
+ return p.maxSize
+}
+
+func (p *Partition[K, V]) getOptions() OptionsPartition {
+ return p.opts
+}
+
+func (p *Partition[K, V]) Clear() {
+ p.c.DeleteFunc(func(key K, v V) bool {
+ return true
+ })
+}
+
+func (p *Partition[K, V]) Get(ctx context.Context, key K) (V, bool) {
+ return p.c.Get(key)
+}
+
+type PartitionManager interface {
+ adjustMaxSize(addend int) int
+ getMaxSize() int
+ getOptions() OptionsPartition
+ clearOnRebuild(predicate func(k, v any) bool, changeset ...identity.Identity)
+ clearMatching(predicate func(k, v any) bool)
+ clearStale()
+}
+
+const (
+ ClearOnRebuild ClearWhen = iota + 1
+ ClearOnChange
+ ClearNever
+)
+
+type ClearWhen int
+
+type stats struct {
+ opts Options
+ memstatsCurrent runtime.MemStats
+ currentMaxSize int
+ availableMemory uint64
+
+ adjustmentFactor float64
+}
+
+func (s *stats) adjustCurrentMaxSize() bool {
+ newCurrentMaxSize := int(math.Floor(float64(s.opts.MaxSize) * s.adjustmentFactor))
+
+ if newCurrentMaxSize < s.opts.MinMaxSize {
+ newCurrentMaxSize = int(s.opts.MinMaxSize)
+ }
+ changed := newCurrentMaxSize != s.currentMaxSize
+ s.currentMaxSize = newCurrentMaxSize
+ return changed
+}
+
+// CleanKey turns s into a format suitable for a cache key for this package.
+// The key will be a Unix-styled path with a leading slash but no trailing slash.
+func CleanKey(s string) string {
+ return path.Clean(paths.ToSlashPreserveLeading(s))
+}
diff --git a/cache/dynacache/dynacache_test.go b/cache/dynacache/dynacache_test.go
new file mode 100644
index 00000000000..78b2fc82e5c
--- /dev/null
+++ b/cache/dynacache/dynacache_test.go
@@ -0,0 +1,230 @@
+// 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 dynacache
+
+import (
+ "errors"
+ "fmt"
+ "path/filepath"
+ "testing"
+ "time"
+
+ qt "github.com/frankban/quicktest"
+ "github.com/gohugoio/hugo/common/loggers"
+ "github.com/gohugoio/hugo/identity"
+ "github.com/gohugoio/hugo/resources/resource"
+)
+
+var (
+ _ resource.StaleInfo = (*testItem)(nil)
+ _ identity.Identity = (*testItem)(nil)
+)
+
+type testItem struct {
+ name string
+ staleVersion uint32
+}
+
+func (t testItem) StaleVersion() uint32 {
+ return t.staleVersion
+}
+
+func (t testItem) IdentifierBase() string {
+ return t.name
+}
+
+func TestCache(t *testing.T) {
+ t.Parallel()
+ c := qt.New(t)
+
+ cache := New(Options{
+ Log: loggers.NewDefault(),
+ })
+
+ c.Cleanup(func() {
+ cache.Stop()
+ })
+
+ opts := OptionsPartition{Weight: 30}
+
+ c.Assert(cache, qt.Not(qt.IsNil))
+
+ p1 := GetOrCreatePartition[string, testItem](cache, "/aaaa/bbbb", opts)
+ c.Assert(p1, qt.Not(qt.IsNil))
+
+ p2 := GetOrCreatePartition[string, testItem](cache, "/aaaa/bbbb", opts)
+
+ c.Assert(func() { GetOrCreatePartition[string, testItem](cache, "foo bar", opts) }, qt.PanicMatches, ".*invalid partition name.*")
+ c.Assert(func() { GetOrCreatePartition[string, testItem](cache, "/aaaa/cccc", OptionsPartition{Weight: 1234}) }, qt.PanicMatches, ".*invalid Weight.*")
+
+ c.Assert(p2, qt.Equals, p1)
+
+ p3 := GetOrCreatePartition[string, testItem](cache, "/aaaa/cccc", opts)
+ c.Assert(p3, qt.Not(qt.IsNil))
+ c.Assert(p3, qt.Not(qt.Equals), p1)
+
+ c.Assert(func() { New(Options{}) }, qt.PanicMatches, ".*nil Log.*")
+}
+
+func TestCalculateMaxSizePerPartition(t *testing.T) {
+ t.Parallel()
+ c := qt.New(t)
+
+ c.Assert(calculateMaxSizePerPartition(1000, 500, 5), qt.Equals, 200)
+ c.Assert(calculateMaxSizePerPartition(1000, 250, 5), qt.Equals, 400)
+ c.Assert(func() { calculateMaxSizePerPartition(1000, 250, 0) }, qt.PanicMatches, ".*must be > 0.*")
+ c.Assert(func() { calculateMaxSizePerPartition(1000, 0, 1) }, qt.PanicMatches, ".*must be > 0.*")
+}
+
+func TestCleanKey(t *testing.T) {
+ c := qt.New(t)
+
+ c.Assert(CleanKey("a/b/c"), qt.Equals, "/a/b/c")
+ c.Assert(CleanKey("/a/b/c"), qt.Equals, "/a/b/c")
+ c.Assert(CleanKey("a/b/c/"), qt.Equals, "/a/b/c")
+ c.Assert(CleanKey(filepath.FromSlash("/a/b/c/")), qt.Equals, "/a/b/c")
+}
+
+func newTestCache(t *testing.T) *Cache {
+ cache := New(
+ Options{
+ Log: loggers.NewDefault(),
+ },
+ )
+
+ p1 := GetOrCreatePartition[string, testItem](cache, "/aaaa/bbbb", OptionsPartition{Weight: 30, ClearWhen: ClearOnRebuild})
+ p2 := GetOrCreatePartition[string, testItem](cache, "/aaaa/cccc", OptionsPartition{Weight: 30, ClearWhen: ClearOnChange})
+
+ p1.GetOrCreate("clearOnRebuild", func(string) (testItem, error) {
+ return testItem{}, nil
+ })
+
+ p2.GetOrCreate("clearBecauseStale", func(string) (testItem, error) {
+ return testItem{
+ staleVersion: 32,
+ }, nil
+ })
+
+ p2.GetOrCreate("clearBecauseIdentityChanged", func(string) (testItem, error) {
+ return testItem{
+ name: "changed",
+ }, nil
+ })
+
+ p2.GetOrCreate("clearNever", func(string) (testItem, error) {
+ return testItem{
+ staleVersion: 0,
+ }, nil
+ })
+
+ t.Cleanup(func() {
+ cache.Stop()
+ })
+
+ return cache
+}
+
+func TestClear(t *testing.T) {
+ t.Parallel()
+ c := qt.New(t)
+
+ predicateAll := func(string) bool {
+ return true
+ }
+
+ cache := newTestCache(t)
+
+ c.Assert(cache.Keys(predicateAll), qt.HasLen, 4)
+
+ cache.ClearOnRebuild(nil)
+
+ // Stale items are always cleared.
+ c.Assert(cache.Keys(predicateAll), qt.HasLen, 2)
+
+ cache = newTestCache(t)
+ cache.ClearOnRebuild(nil, identity.StringIdentity("changed"))
+
+ c.Assert(cache.Keys(nil), qt.HasLen, 1)
+
+ cache = newTestCache(t)
+
+ cache.ClearMatching(nil, func(k, v any) bool {
+ return k.(string) == "clearOnRebuild"
+ })
+
+ c.Assert(cache.Keys(predicateAll), qt.HasLen, 3)
+
+ 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 := range 3 {
+ for range 3 {
+ 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 := range 3 {
+ for range 3 {
+ 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)
+ cache := newTestCache(t)
+ alloc := cache.stats.memstatsCurrent.Alloc
+ cache.adjustCurrentMaxSize()
+ c.Assert(cache.stats.memstatsCurrent.Alloc, qt.Not(qt.Equals), alloc)
+}
diff --git a/cache/filecache/filecache.go b/cache/filecache/filecache.go
index 5a87382aeaf..1d5ba765082 100644
--- a/cache/filecache/filecache.go
+++ b/cache/filecache/filecache.go
@@ -1,4 +1,4 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
+// Copyright 2026 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -16,15 +16,17 @@ package filecache
import (
"bytes"
"errors"
+ "fmt"
"io"
- "io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
"time"
+ "github.com/gohugoio/httpcache"
"github.com/gohugoio/hugo/common/hugio"
+ "github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/helpers"
@@ -36,7 +38,7 @@ import (
var ErrFatal = errors.New("fatal filecache error")
const (
- filecacheRootDirname = "filecache"
+ FilecacheRootDirname = "filecache"
)
// Cache caches a set of files in a directory. This is usually a file on
@@ -44,14 +46,13 @@ const (
type Cache struct {
Fs afero.Fs
- // Max age for items in this cache. Negative duration means forever,
- // 0 is effectively turning this cache off.
- maxAge time.Duration
+ cfg FileCacheConfig
- // When set, we just remove this entire root directory on expiration.
- pruneAllRootDir string
+ entryLocker *lockTracker
- nlocker *lockTracker
+ initOnce sync.Once
+ isInited bool
+ initErr error
}
type lockTracker struct {
@@ -84,12 +85,15 @@ type ItemInfo struct {
}
// NewCache creates a new file cache with the given filesystem and max age.
-func NewCache(fs afero.Fs, maxAge time.Duration, pruneAllRootDir string) *Cache {
+func NewCache(fs afero.Fs, cfg FileCacheConfig) *Cache {
+ if err := cfg.init(); err != nil {
+ panic(fmt.Sprintf("invalid cache config: %s", err))
+ }
+
return &Cache{
- Fs: fs,
- nlocker: &lockTracker{Locker: locker.NewLocker(), seen: make(map[string]struct{})},
- maxAge: maxAge,
- pruneAllRootDir: pruneAllRootDir,
+ Fs: fs,
+ entryLocker: &lockTracker{Locker: locker.NewLocker(), seen: make(map[string]struct{})},
+ cfg: cfg,
}
}
@@ -104,23 +108,43 @@ func (l *lockedFile) Close() error {
return l.File.Close()
}
+func (c *Cache) init() error {
+ if c == nil {
+ panic("cache is nil")
+ }
+
+ c.initOnce.Do(func() {
+ c.isInited = true
+ // Create the base dir if it does not exist.
+ if err := c.Fs.MkdirAll("", 0o777); err != nil && !os.IsExist(err) {
+ err = fmt.Errorf("failled to create base cache directory: %s", err)
+ c.initErr = err
+ }
+ })
+ return c.initErr
+}
+
// WriteCloser returns a transactional writer into the cache.
// It's important that it's closed when done.
func (c *Cache) WriteCloser(id string) (ItemInfo, io.WriteCloser, error) {
+ if err := c.init(); err != nil {
+ return ItemInfo{}, nil, err
+ }
+
id = cleanID(id)
- c.nlocker.Lock(id)
+ c.entryLocker.Lock(id)
info := ItemInfo{Name: id}
f, err := helpers.OpenFileForWriting(c.Fs, id)
if err != nil {
- c.nlocker.Unlock(id)
+ c.entryLocker.Unlock(id)
return info, nil, err
}
return info, &lockedFile{
File: f,
- unlock: func() { c.nlocker.Unlock(id) },
+ unlock: func() { c.entryLocker.Unlock(id) },
}, nil
}
@@ -130,11 +154,16 @@ func (c *Cache) WriteCloser(id string) (ItemInfo, io.WriteCloser, error) {
// it when done.
func (c *Cache) ReadOrCreate(id string,
read func(info ItemInfo, r io.ReadSeeker) error,
- create func(info ItemInfo, w io.WriteCloser) error) (info ItemInfo, err error) {
+ create func(info ItemInfo, w io.WriteCloser) error,
+) (info ItemInfo, err error) {
+ if err := c.init(); err != nil {
+ return ItemInfo{}, err
+ }
+
id = cleanID(id)
- c.nlocker.Lock(id)
- defer c.nlocker.Unlock(id)
+ c.entryLocker.Lock(id)
+ defer c.entryLocker.Unlock(id)
info = ItemInfo{Name: id}
@@ -160,14 +189,26 @@ func (c *Cache) ReadOrCreate(id string,
return
}
+// NamedLock locks the given id. The lock is released when the returned function is called.
+func (c *Cache) NamedLock(id string) func() {
+ id = cleanID(id)
+ c.entryLocker.Lock(id)
+ return func() {
+ c.entryLocker.Unlock(id)
+ }
+}
+
// GetOrCreate tries to get the file with the given id from cache. If not found or expired, create will
// be invoked and the result cached.
// This method is protected by a named lock using the given id as identifier.
func (c *Cache) GetOrCreate(id string, create func() (io.ReadCloser, error)) (ItemInfo, io.ReadCloser, error) {
+ if err := c.init(); err != nil {
+ return ItemInfo{}, nil, err
+ }
id = cleanID(id)
- c.nlocker.Lock(id)
- defer c.nlocker.Unlock(id)
+ c.entryLocker.Lock(id)
+ defer c.entryLocker.Unlock(id)
info := ItemInfo{Name: id}
@@ -185,7 +226,7 @@ func (c *Cache) GetOrCreate(id string, create func() (io.ReadCloser, error)) (It
return info, nil, err
}
- if c.maxAge == 0 {
+ if c.cfg.MaxAge == 0 {
// No caching.
return info, hugio.ToReadCloser(r), nil
}
@@ -193,21 +234,74 @@ func (c *Cache) GetOrCreate(id string, create func() (io.ReadCloser, error)) (It
var buff bytes.Buffer
return info,
hugio.ToReadCloser(&buff),
- afero.WriteReader(c.Fs, id, io.TeeReader(r, &buff))
+ c.writeReader(id, io.TeeReader(r, &buff))
+}
+
+// AbsFilenameFromID returns the filename for the given id in the cache.
+// This will be an absolute path.
+func (c *Cache) AbsFilenameFromID(id string) string {
+ return filepath.Join(c.cfg.DirCompiled, cleanID(id))
+}
+
+// GetOrCreateInfo tries to get the item info with the given id from cache. If not found or expired, create will
+// be invoked with the id. The create function is expected to create the cache item with the given id. The returned ItemInfo will have the id as Name.
+// This method is protected by a named lock using the given id as identifier.
+func (c *Cache) GetOrCreateInfo(id string, create func(id string) error) (ItemInfo, error) {
+ if err := c.init(); err != nil {
+ return ItemInfo{}, err
+ }
+
+ id = cleanID(id)
+
+ c.entryLocker.Lock(id)
+ defer c.entryLocker.Unlock(id)
+
+ info := ItemInfo{Name: id}
+
+ if !c.removeIfNeeded(id) {
+ // The file exists and is not expired, so we consider it a cache hit.
+ return info, nil
+ }
+
+ if err := create(id); err != nil {
+ c.remove(id)
+ return info, err
+ }
+
+ return info, nil
+}
+
+func (c *Cache) writeReader(id string, r io.Reader) error {
+ dir := filepath.Dir(id)
+ if dir != "" {
+ _ = c.Fs.MkdirAll(dir, 0o777)
+ }
+ f, err := c.Fs.Create(id)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ _, _ = io.Copy(f, r)
+
+ return nil
}
// GetOrCreateBytes is the same as GetOrCreate, but produces a byte slice.
func (c *Cache) GetOrCreateBytes(id string, create func() ([]byte, error)) (ItemInfo, []byte, error) {
+ if err := c.init(); err != nil {
+ return ItemInfo{}, nil, err
+ }
id = cleanID(id)
- c.nlocker.Lock(id)
- defer c.nlocker.Unlock(id)
+ c.entryLocker.Lock(id)
+ defer c.entryLocker.Unlock(id)
info := ItemInfo{Name: id}
if r := c.getOrRemove(id); r != nil {
defer r.Close()
- b, err := ioutil.ReadAll(r)
+ b, err := io.ReadAll(r)
return info, b, err
}
@@ -221,40 +315,71 @@ func (c *Cache) GetOrCreateBytes(id string, create func() ([]byte, error)) (Item
return info, nil, err
}
- if c.maxAge == 0 {
+ if c.cfg.MaxAge == 0 {
return info, b, nil
}
- if err := afero.WriteReader(c.Fs, id, bytes.NewReader(b)); err != nil {
+ if err := c.writeReader(id, bytes.NewReader(b)); err != nil {
return info, nil, err
}
+
return info, b, nil
}
+// SetBytes sets the file content with the given id in the cache.
+func (c *Cache) SetBytes(id string, data []byte) error {
+ if err := c.init(); err != nil {
+ return err
+ }
+ id = cleanID(id)
+
+ c.entryLocker.Lock(id)
+ defer c.entryLocker.Unlock(id)
+
+ if c.cfg.MaxAge == 0 {
+ // No caching.
+ return nil
+ }
+
+ return c.writeReader(id, bytes.NewReader(data))
+}
+
// GetBytes gets the file content with the given id from the cache, nil if none found.
-func (c *Cache) GetBytes(id string) (ItemInfo, []byte, error) {
+func (c *Cache) GetBytes(id string) ([]byte, error) {
+ _, b, err := c.GetItemBytes(id)
+ return b, err
+}
+
+// GetItemBytes gets the ItemInfo and file content with the given id from the cache, nil if none found.
+func (c *Cache) GetItemBytes(id string) (ItemInfo, []byte, error) {
+ if err := c.init(); err != nil {
+ return ItemInfo{}, nil, err
+ }
id = cleanID(id)
- c.nlocker.Lock(id)
- defer c.nlocker.Unlock(id)
+ c.entryLocker.Lock(id)
+ defer c.entryLocker.Unlock(id)
info := ItemInfo{Name: id}
if r := c.getOrRemove(id); r != nil {
defer r.Close()
- b, err := ioutil.ReadAll(r)
+ b, err := io.ReadAll(r)
return info, b, err
}
return info, nil, nil
}
-// Get gets the file with the given id from the cahce, nil if none found.
+// Get gets the file with the given id from the cache, nil if none found.
func (c *Cache) Get(id string) (ItemInfo, io.ReadCloser, error) {
+ if err := c.init(); err != nil {
+ return ItemInfo{}, nil, err
+ }
id = cleanID(id)
- c.nlocker.Lock(id)
- defer c.nlocker.Unlock(id)
+ c.entryLocker.Lock(id)
+ defer c.entryLocker.Unlock(id)
info := ItemInfo{Name: id}
@@ -263,24 +388,29 @@ func (c *Cache) Get(id string) (ItemInfo, io.ReadCloser, error) {
return info, r, nil
}
+// removeIfNeeded checks if the file with the given id should be re-created.
+func (c *Cache) removeIfNeeded(id string) bool {
+ if c.cfg.MaxAge == 0 {
+ // No caching, remove.
+ c.remove(id)
+ return true
+ }
+ if removed, err := c.removeIfExpired(id); err != nil || removed {
+ return true
+ }
+ return false
+}
+
// getOrRemove gets the file with the given id. If it's expired, it will
// be removed.
func (c *Cache) getOrRemove(id string) hugio.ReadSeekCloser {
- if c.maxAge == 0 {
+ if c.cfg.MaxAge == 0 {
// No caching.
return nil
}
- if c.maxAge > 0 {
- fi, err := c.Fs.Stat(id)
- if err != nil {
- return nil
- }
-
- if c.isExpired(fi.ModTime()) {
- c.Fs.Remove(id)
- return nil
- }
+ if removed, err := c.removeIfExpired(id); err != nil || removed {
+ return nil
}
f, err := c.Fs.Open(id)
@@ -291,19 +421,73 @@ func (c *Cache) getOrRemove(id string) hugio.ReadSeekCloser {
return f
}
+func (c *Cache) getBytesAndRemoveIfExpired(id string) ([]byte, bool) {
+ if c.cfg.MaxAge == 0 {
+ // No caching.
+ return nil, false
+ }
+
+ f, err := c.Fs.Open(id)
+ if err != nil {
+ return nil, false
+ }
+ defer f.Close()
+
+ b, err := io.ReadAll(f)
+ if err != nil {
+ return nil, false
+ }
+
+ removed, err := c.removeIfExpired(id)
+ if err != nil {
+ return nil, false
+ }
+
+ return b, removed
+}
+
+func (c *Cache) removeIfExpired(id string) (bool, error) {
+ if c.cfg.MaxAge <= 0 {
+ return false, nil
+ }
+
+ fi, err := c.Fs.Stat(id)
+ if err != nil {
+ return false, err
+ }
+
+ if c.isExpired(fi.ModTime()) {
+ c.remove(id)
+ return true, nil
+ }
+
+ return false, nil
+}
+
+func (c *Cache) remove(id string) {
+ if c.cfg.entryIsDir {
+ c.Fs.RemoveAll(id)
+ } else {
+ c.Fs.Remove(id)
+ }
+}
+
func (c *Cache) isExpired(modTime time.Time) bool {
- if c.maxAge < 0 {
+ if c.cfg.MaxAge < 0 {
return false
}
- return c.maxAge == 0 || time.Since(modTime) > c.maxAge
+
+ // Note the use of time.Since here.
+ // We cannot use Hugo's global Clock for this.
+ return c.cfg.MaxAge == 0 || time.Since(modTime) > c.cfg.MaxAge
}
// For testing
-func (c *Cache) getString(id string) string {
+func (c *Cache) GetString(id string) string {
id = cleanID(id)
- c.nlocker.Lock(id)
- defer c.nlocker.Unlock(id)
+ c.entryLocker.Lock(id)
+ defer c.entryLocker.Unlock(id)
f, err := c.Fs.Open(id)
if err != nil {
@@ -311,13 +495,24 @@ func (c *Cache) getString(id string) string {
}
defer f.Close()
- b, _ := ioutil.ReadAll(f)
+ b, _ := io.ReadAll(f)
return string(b)
}
// Caches is a named set of caches.
type Caches map[string]*Cache
+func (f Caches) SetResourceFs(fs afero.Fs) {
+ for _, c := range f {
+ if c.cfg.IsResourceDir {
+ if c.isInited {
+ panic("cannot set resource fs after init")
+ }
+ c.Fs = hugofs.NewBasePathFs(fs, c.cfg.DirCompiled)
+ }
+ }
+}
+
// Get gets a named cache, nil if none found.
func (f Caches) Get(name string) *Cache {
return f[strings.ToLower(name)]
@@ -325,57 +520,60 @@ func (f Caches) Get(name string) *Cache {
// NewCaches creates a new set of file caches from the given
// configuration.
-func NewCaches(p *helpers.PathSpec) (Caches, error) {
- var dcfg Configs
- if c, ok := p.Cfg.Get("filecacheConfigs").(Configs); ok {
- dcfg = c
- } else {
- var err error
- dcfg, err = DecodeConfig(p.Fs.Source, p.Cfg)
- if err != nil {
- return nil, err
- }
- }
-
- fs := p.Fs.Source
+func NewCaches(dcfg Configs, sourceFs afero.Fs) (Caches, error) {
+ fs := sourceFs
m := make(Caches)
for k, v := range dcfg {
var cfs afero.Fs
-
- if v.isResourceDir {
- cfs = p.BaseFs.ResourcesCache
+ if v.IsResourceDir {
+ cfs = nil // Set later. TODO(bep) this needs to be cleanded up.
} else {
- cfs = fs
+ cfs = hugofs.NewBasePathFs(fs, v.DirCompiled)
}
- if cfs == nil {
- // TODO(bep) we still have some places that do not initialize the
- // full dependencies of a site, e.g. the import Jekyll command.
- // That command does not need these caches, so let us just continue
- // for now.
- continue
- }
+ c := NewCache(cfs, v)
- baseDir := v.Dir
+ m[k] = c
+ }
- if err := cfs.MkdirAll(baseDir, 0777); err != nil && !os.IsExist(err) {
- return nil, err
- }
+ return m, nil
+}
- bfs := afero.NewBasePathFs(cfs, baseDir)
+func cleanID(name string) string {
+ return strings.TrimPrefix(filepath.Clean(name), helpers.FilePathSeparator)
+}
- var pruneAllRootDir string
- if k == cacheKeyModules {
- pruneAllRootDir = "pkg"
- }
+// AsHTTPCache returns an httpcache.Cache implementation for this file cache.
+// Note that none of the methods are protected by named locks, so you need to make sure
+// to do that in your own code.
+func (c *Cache) AsHTTPCache() httpcache.Cache {
+ return &httpCache{c: c}
+}
+
+type httpCache struct {
+ c *Cache
+}
+
+func (h *httpCache) Get(id string) (resp []byte, ok bool) {
+ id = cleanID(id)
+ b, removed := h.c.getBytesAndRemoveIfExpired(id)
+
+ return b, !removed
+}
- m[k] = NewCache(bfs, v.MaxAge, pruneAllRootDir)
+func (h *httpCache) Set(id string, resp []byte) {
+ if h.c.cfg.MaxAge == 0 {
+ return
}
- return m, nil
+ id = cleanID(id)
+
+ if err := h.c.writeReader(id, bytes.NewReader(resp)); err != nil {
+ panic(err)
+ }
}
-func cleanID(name string) string {
- return strings.TrimPrefix(filepath.Clean(name), helpers.FilePathSeparator)
+func (h *httpCache) Delete(key string) {
+ h.c.Fs.Remove(key)
}
diff --git a/cache/filecache/filecache_config.go b/cache/filecache/filecache_config.go
index 816c6b8a6d9..feaa07d494a 100644
--- a/cache/filecache/filecache_config.go
+++ b/cache/filecache/filecache_config.go
@@ -11,118 +11,201 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+// Package filecache provides a file based cache for Hugo.
package filecache
import (
+ "encoding/json"
+ "errors"
+ "fmt"
"path"
"path/filepath"
"strings"
"time"
- "github.com/gohugoio/hugo/common/maps"
-
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/config"
- "github.com/gohugoio/hugo/helpers"
-
"github.com/mitchellh/mapstructure"
- "github.com/pkg/errors"
"github.com/spf13/afero"
)
const (
- cachesConfigKey = "caches"
-
resourcesGenDir = ":resourceDir/_gen"
cacheDirProject = ":cacheDir/:project"
)
-var defaultCacheConfig = Config{
- MaxAge: -1, // Never expire
- Dir: cacheDirProject,
-}
-
const (
- cacheKeyGetJSON = "getjson"
- cacheKeyGetCSV = "getcsv"
- cacheKeyImages = "images"
- cacheKeyAssets = "assets"
- cacheKeyModules = "modules"
- cacheKeyGetResource = "getresource"
+ CacheKeyImages = "images"
+ CacheKeyAssets = "assets"
+ CacheKeyModules = "modules"
+ CacheKeyModuleQueries = "modulequeries"
+ CacheKeyModuleGitInfo = "modulegitinfo"
+ CacheKeyGetResource = "getresource"
+ CacheKeyMisc = "misc"
)
-type Configs map[string]Config
+type Configs map[string]FileCacheConfig
+// CacheDirModules returns the compiled path to the modules cache.
+// For internal use.
func (c Configs) CacheDirModules() string {
- return c[cacheKeyModules].Dir
+ return c[CacheKeyModules].DirCompiled
+}
+
+// CacheDirMisc returns the compiled path to the misc cache.
+// For internal use.
+func (c Configs) CacheDirMisc() string {
+ return c[CacheKeyMisc].DirCompiled
}
var defaultCacheConfigs = Configs{
- cacheKeyModules: {
+ CacheKeyModules: {
MaxAge: -1,
Dir: ":cacheDir/modules",
+ fileCacheConfigInternal: fileCacheConfigInternal{
+ entryIsDir: true,
+ isReadOnly: true, // we need to make it writable when pruning.
+ },
+ },
+ CacheKeyModuleQueries: {
+ MaxAge: 24 * time.Hour,
+ Dir: ":cacheDir/modules",
+ },
+ CacheKeyModuleGitInfo: {
+ MaxAge: 24 * time.Hour,
+ Dir: ":cacheDir/modules",
+ fileCacheConfigInternal: fileCacheConfigInternal{
+ entryIsDir: true,
+ },
},
- cacheKeyGetJSON: defaultCacheConfig,
- cacheKeyGetCSV: defaultCacheConfig,
- cacheKeyImages: {
+ CacheKeyImages: {
MaxAge: -1,
Dir: resourcesGenDir,
},
- cacheKeyAssets: {
+ CacheKeyAssets: {
MaxAge: -1,
Dir: resourcesGenDir,
},
- cacheKeyGetResource: Config{
+ CacheKeyGetResource: {
MaxAge: -1, // Never expire
Dir: cacheDirProject,
},
+ CacheKeyMisc: {
+ MaxAge: -1,
+ Dir: cacheDirProject,
+ },
+}
+
+func init() {
+ for k, v := range defaultCacheConfigs {
+ v.name = k
+ defaultCacheConfigs[k] = v
+ }
}
-type Config struct {
+type FileCacheConfig struct {
// Max age of cache entries in this cache. Any items older than this will
// be removed and not returned from the cache.
- // a negative value means forever, 0 means cache is disabled.
+ // A negative value means forever, 0 means cache is disabled.
+ // Hugo is lenient with what types it accepts here, but we recommend using
+ // a duration string, a sequence of decimal numbers, each with optional fraction and a unit suffix,
+ // such as "300ms", "1.5h" or "2h45m".
+ // Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
MaxAge time.Duration
// The directory where files are stored.
Dir string
- // Will resources/_gen will get its own composite filesystem that
- // also checks any theme.
- isResourceDir bool
+ fileCacheConfigInternal `json:"-"`
+}
+
+func (cfg *FileCacheConfig) init() error {
+ if cfg.DirCompiled == "" {
+ // From unit tests. Just check that it does not contain any placeholders.
+ if strings.Contains(cfg.Dir, ":") {
+ return fmt.Errorf("cache dir %q contains unresolved placeholders", cfg.Dir)
+ }
+ cfg.DirCompiled = cfg.Dir
+ }
+ // Sanity check the config.
+ if len(cfg.DirCompiled) < 5 {
+ panic(fmt.Sprintf("invalid cache dir: %q", cfg.DirCompiled))
+ }
+ return nil
}
-// GetJSONCache gets the file cache for getJSON.
-func (f Caches) GetJSONCache() *Cache {
- return f[cacheKeyGetJSON]
+type fileCacheConfigInternal struct {
+ DirCompiled string
+
+ name string // The name of this cache, e.g. "images", "modules" etc.
+ entryIsDir bool // when set, the cache entries represents directories directly below the base dir.
+ isReadOnly bool // when set, the cache is read only and needs to be pruned differently. This is used for the Go modules cache.
+ IsResourceDir bool // resources/_gen will get its own composite filesystem that also checks any theme. TODO(bep) unexport this.
}
-// GetCSVCache gets the file cache for getCSV.
-func (f Caches) GetCSVCache() *Cache {
- return f[cacheKeyGetCSV]
+// MarshalJSON marshals FileCacheConfig to JSON with MaxAge as a human-readable string.
+func (c FileCacheConfig) MarshalJSON() ([]byte, error) {
+ var maxAge any
+ if c.MaxAge == -1 {
+ maxAge = -1
+ } else {
+ maxAge = strings.TrimSuffix(c.MaxAge.String(), "0m0s")
+ }
+ return json.Marshal(&struct {
+ MaxAge any `json:"maxAge"`
+ Dir string `json:"dir"`
+ }{
+ MaxAge: maxAge,
+ Dir: c.Dir,
+ })
}
// ImageCache gets the file cache for processed images.
func (f Caches) ImageCache() *Cache {
- return f[cacheKeyImages]
+ return f[CacheKeyImages]
}
// ModulesCache gets the file cache for Hugo Modules.
func (f Caches) ModulesCache() *Cache {
- return f[cacheKeyModules]
+ return f[CacheKeyModules]
+}
+
+// ModuleQueriesCache gets the file cache for Hugo Module version queries.
+// Returns nil if not found.
+func (f Caches) ModuleQueriesCache() *Cache {
+ c, ok := f[CacheKeyModuleQueries]
+ if !ok {
+ panic("module queries cache not set")
+ }
+ return c
+}
+
+// ModuleGitInfoCache gets the file cache for Hugo Module git info.
+func (f Caches) ModuleGitInfoCache() *Cache {
+ c, ok := f[CacheKeyModuleGitInfo]
+ if !ok {
+ panic("module git info cache not set")
+ }
+ return c
}
// AssetsCache gets the file cache for assets (processed resources, SCSS etc.).
func (f Caches) AssetsCache() *Cache {
- return f[cacheKeyAssets]
+ 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]
+ return f[CacheKeyGetResource]
}
-func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) {
+func DecodeConfig(fs afero.Fs, bcfg config.BaseConfig, m map[string]any) (Configs, error) {
c := make(Configs)
valid := make(map[string]bool)
// Add defaults
@@ -131,15 +214,17 @@ func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) {
valid[k] = true
}
- m := cfg.GetStringMap(cachesConfigKey)
-
_, isOsFs := fs.(*afero.OsFs)
for k, v := range m {
- if _, ok := v.(maps.Params); !ok {
+ if _, ok := v.(hmaps.Params); !ok {
continue
}
- cc := defaultCacheConfig
+ var ok bool
+ cc, ok := c[k]
+ if !ok {
+ return nil, fmt.Errorf("%q is not a valid cache name", k)
+ }
dc := &mapstructure.DecoderConfig{
Result: &cc,
@@ -153,24 +238,17 @@ func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) {
}
if err := decoder.Decode(v); err != nil {
- return nil, errors.Wrap(err, "failed to decode filecache config")
+ return nil, fmt.Errorf("failed to decode filecache config: %w", err)
}
if cc.Dir == "" {
return c, errors.New("must provide cache Dir")
}
- name := strings.ToLower(k)
- if !valid[name] {
- return nil, errors.Errorf("%q is not a valid cache name", name)
- }
+ c[k] = cc
- c[name] = cc
}
- // This is a very old flag in Hugo, but we need to respect it.
- disabled := cfg.GetBool("ignoreCache")
-
for k, v := range c {
dir := filepath.ToSlash(filepath.Clean(v.Dir))
hadSlash := strings.HasPrefix(dir, "/")
@@ -178,12 +256,12 @@ func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) {
for i, part := range parts {
if strings.HasPrefix(part, ":") {
- resolved, isResource, err := resolveDirPlaceholder(fs, cfg, part)
+ resolved, isResource, err := resolveDirPlaceholder(fs, bcfg, part)
if err != nil {
return c, err
}
if isResource {
- v.isResourceDir = true
+ v.IsResourceDir = true
}
parts[i] = resolved
}
@@ -193,33 +271,29 @@ func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) {
if hadSlash {
dir = "/" + dir
}
- v.Dir = filepath.Clean(filepath.FromSlash(dir))
+ v.DirCompiled = filepath.Clean(filepath.FromSlash(dir))
- if !v.isResourceDir {
- if isOsFs && !filepath.IsAbs(v.Dir) {
- return c, errors.Errorf("%q must resolve to an absolute directory", v.Dir)
+ if !v.IsResourceDir {
+ if isOsFs && !filepath.IsAbs(v.DirCompiled) {
+ return c, fmt.Errorf("%q must resolve to an absolute directory", v.DirCompiled)
}
// Avoid cache in root, e.g. / (Unix) or c:\ (Windows)
- if len(strings.TrimPrefix(v.Dir, filepath.VolumeName(v.Dir))) == 1 {
- return c, errors.Errorf("%q is a root folder and not allowed as cache dir", v.Dir)
+ if len(strings.TrimPrefix(v.DirCompiled, filepath.VolumeName(v.DirCompiled))) == 1 {
+ return c, fmt.Errorf("%q is a root folder and not allowed as cache dir", v.DirCompiled)
}
}
- if !strings.HasPrefix(v.Dir, "_gen") {
+ if !strings.HasPrefix(v.DirCompiled, "_gen") {
// We do cache eviction (file removes) and since the user can set
// his/hers own cache directory, we really want to make sure
// we do not delete any files that do not belong to this cache.
// We do add the cache name as the root, but this is an extra safe
// guard. We skip the files inside /resources/_gen/ because
// that would be breaking.
- v.Dir = filepath.Join(v.Dir, filecacheRootDirname, k)
+ v.DirCompiled = filepath.Join(v.DirCompiled, FilecacheRootDirname, k)
} else {
- v.Dir = filepath.Join(v.Dir, k)
- }
-
- if disabled {
- v.MaxAge = 0
+ v.DirCompiled = filepath.Join(v.DirCompiled, k)
}
c[k] = v
@@ -229,18 +303,15 @@ func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) {
}
// Resolves :resourceDir => /myproject/resources etc., :cacheDir => ...
-func resolveDirPlaceholder(fs afero.Fs, cfg config.Provider, placeholder string) (cacheDir string, isResource bool, err error) {
- workingDir := cfg.GetString("workingDir")
-
+func resolveDirPlaceholder(fs afero.Fs, bcfg config.BaseConfig, placeholder string) (cacheDir string, isResource bool, err error) {
switch strings.ToLower(placeholder) {
case ":resourcedir":
return "", true, nil
case ":cachedir":
- d, err := helpers.GetCacheDir(fs, cfg)
- return d, false, err
+ return bcfg.CacheDir, false, nil
case ":project":
- return filepath.Base(workingDir), false, nil
+ return filepath.Base(bcfg.WorkingDir), false, nil
}
- return "", false, errors.Errorf("%q is not a valid placeholder (valid values are :cacheDir or :resourceDir)", placeholder)
+ return "", false, fmt.Errorf("%q is not a valid placeholder (valid values are :cacheDir or :resourceDir)", placeholder)
}
diff --git a/cache/filecache/filecache_config_test.go b/cache/filecache/filecache_config_test.go
index 1ff3b8112cf..2215f13cdbb 100644
--- a/cache/filecache/filecache_config_test.go
+++ b/cache/filecache/filecache_config_test.go
@@ -11,18 +11,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package filecache
+package filecache_test
import (
+ "encoding/json"
"path/filepath"
"runtime"
- "strings"
"testing"
"time"
"github.com/spf13/afero"
+ "github.com/gohugoio/hugo/cache/filecache"
"github.com/gohugoio/hugo/config"
+ "github.com/gohugoio/hugo/config/testconfig"
qt "github.com/frankban/quicktest"
)
@@ -42,12 +44,9 @@ assetDir = "assets"
archetypeDir = "archetypes"
[caches]
-[caches.getJSON]
+[caches.misc]
maxAge = "10m"
dir = "/path/to/c1"
-[caches.getCSV]
-maxAge = "11h"
-dir = "/path/to/c2"
[caches.images]
dir = "/path/to/c3"
[caches.getResource]
@@ -57,22 +56,20 @@ dir = "/path/to/c4"
cfg, err := config.FromConfigString(configStr, "toml")
c.Assert(err, qt.IsNil)
fs := afero.NewMemMapFs()
- decoded, err := DecodeConfig(fs, cfg)
- c.Assert(err, qt.IsNil)
-
- c.Assert(len(decoded), qt.Equals, 6)
+ decoded := testconfig.GetTestConfigs(fs, cfg).Base.Caches
+ c.Assert(len(decoded), qt.Equals, 7)
- c2 := decoded["getcsv"]
- c.Assert(c2.MaxAge.String(), qt.Equals, "11h0m0s")
- c.Assert(c2.Dir, qt.Equals, filepath.FromSlash("/path/to/c2/filecache/getcsv"))
+ c2 := decoded["misc"]
+ c.Assert(c2.MaxAge.String(), qt.Equals, "10m0s")
+ c.Assert(c2.DirCompiled, qt.Equals, filepath.FromSlash("/path/to/c1/filecache/misc"))
c3 := decoded["images"]
c.Assert(c3.MaxAge, qt.Equals, time.Duration(-1))
- c.Assert(c3.Dir, qt.Equals, filepath.FromSlash("/path/to/c3/filecache/images"))
+ c.Assert(c3.DirCompiled, qt.Equals, filepath.FromSlash("/path/to/c3/filecache/images"))
c4 := decoded["getresource"]
c.Assert(c4.MaxAge, qt.Equals, time.Duration(-1))
- c.Assert(c4.Dir, qt.Equals, filepath.FromSlash("/path/to/c4/filecache/getresource"))
+ c.Assert(c4.DirCompiled, qt.Equals, filepath.FromSlash("/path/to/c4/filecache/getresource"))
}
func TestDecodeConfigIgnoreCache(t *testing.T) {
@@ -91,12 +88,9 @@ archeTypedir = "archetypes"
ignoreCache = true
[caches]
-[caches.getJSON]
+[caches.misc]
maxAge = 1234
dir = "/path/to/c1"
-[caches.getCSV]
-maxAge = 3456
-dir = "/path/to/c2"
[caches.images]
dir = "/path/to/c3"
[caches.getResource]
@@ -106,10 +100,8 @@ dir = "/path/to/c4"
cfg, err := config.FromConfigString(configStr, "toml")
c.Assert(err, qt.IsNil)
fs := afero.NewMemMapFs()
- decoded, err := DecodeConfig(fs, cfg)
- c.Assert(err, qt.IsNil)
-
- c.Assert(len(decoded), qt.Equals, 6)
+ decoded := testconfig.GetTestConfigs(fs, cfg).Base.Caches
+ c.Assert(len(decoded), qt.Equals, 7)
for _, v := range decoded {
c.Assert(v.MaxAge, qt.Equals, time.Duration(0))
@@ -118,7 +110,7 @@ dir = "/path/to/c4"
func TestDecodeConfigDefault(t *testing.T) {
c := qt.New(t)
- cfg := newTestConfig()
+ cfg := config.New()
if runtime.GOOS == "windows" {
cfg.Set("resourceDir", "c:\\cache\\resources")
@@ -128,71 +120,52 @@ func TestDecodeConfigDefault(t *testing.T) {
cfg.Set("resourceDir", "/cache/resources")
cfg.Set("cacheDir", "/cache/thecache")
}
+ cfg.Set("workingDir", filepath.FromSlash("/my/cool/hugoproject"))
fs := afero.NewMemMapFs()
+ decoded := testconfig.GetTestConfigs(fs, cfg).Base.Caches
+ c.Assert(len(decoded), qt.Equals, 7)
- decoded, err := DecodeConfig(fs, cfg)
-
- c.Assert(err, qt.IsNil)
-
- c.Assert(len(decoded), qt.Equals, 6)
-
- imgConfig := decoded[cacheKeyImages]
- jsonConfig := decoded[cacheKeyGetJSON]
+ imgConfig := decoded[filecache.CacheKeyImages]
+ miscConfig := decoded[filecache.CacheKeyMisc]
if runtime.GOOS == "windows" {
- c.Assert(imgConfig.Dir, qt.Equals, filepath.FromSlash("_gen/images"))
+ c.Assert(imgConfig.DirCompiled, qt.Equals, filepath.FromSlash("_gen/images"))
} else {
- c.Assert(imgConfig.Dir, qt.Equals, "_gen/images")
- c.Assert(jsonConfig.Dir, qt.Equals, "/cache/thecache/hugoproject/filecache/getjson")
+ c.Assert(imgConfig.DirCompiled, qt.Equals, "_gen/images")
+ c.Assert(miscConfig.DirCompiled, qt.Equals, "/cache/thecache/hugoproject/filecache/misc")
}
- c.Assert(imgConfig.isResourceDir, qt.Equals, true)
- c.Assert(jsonConfig.isResourceDir, qt.Equals, false)
+ c.Assert(imgConfig.IsResourceDir, qt.Equals, true)
+ c.Assert(miscConfig.IsResourceDir, qt.Equals, false)
}
-func TestDecodeConfigInvalidDir(t *testing.T) {
- t.Parallel()
-
+func TestFileCacheConfigMarshalJSON(t *testing.T) {
c := qt.New(t)
- configStr := `
-resourceDir = "myresources"
-contentDir = "content"
-dataDir = "data"
-i18nDir = "i18n"
-layoutDir = "layouts"
-assetDir = "assets"
-archeTypedir = "archetypes"
+ cfg := config.New()
+ cfg.Set("cacheDir", "/cache")
+ cfg.Set("workingDir", "/my/project")
-[caches]
-[caches.getJSON]
-maxAge = "10m"
-dir = "/"
+ fs := afero.NewMemMapFs()
+ decoded := testconfig.GetTestConfigs(fs, cfg).Base.Caches
-`
- if runtime.GOOS == "windows" {
- configStr = strings.Replace(configStr, "/", "c:\\\\", 1)
- }
+ moduleQueriesConfig := decoded[filecache.CacheKeyModuleQueries]
+ c.Assert(moduleQueriesConfig.MaxAge, qt.Equals, 24*time.Hour)
- cfg, err := config.FromConfigString(configStr, "toml")
+ // Also verify the new moduleGitInfo cache.
+ moduleGitInfoConfig := decoded[filecache.CacheKeyModuleGitInfo]
+ c.Assert(moduleGitInfoConfig.MaxAge, qt.Equals, 24*time.Hour)
+
+ b, err := json.Marshal(moduleQueriesConfig)
c.Assert(err, qt.IsNil)
- fs := afero.NewMemMapFs()
- _, err = DecodeConfig(fs, cfg)
- c.Assert(err, qt.Not(qt.IsNil))
-}
+ c.Assert(string(b), qt.Contains, `"maxAge":"24h"`)
+ c.Assert(string(b), qt.Not(qt.Contains), "86400000000000")
+ c.Assert(string(b), qt.Not(qt.Contains), "8.64e")
-func newTestConfig() config.Provider {
- cfg := config.New()
- cfg.Set("workingDir", filepath.FromSlash("/my/cool/hugoproject"))
- cfg.Set("contentDir", "content")
- cfg.Set("dataDir", "data")
- cfg.Set("resourceDir", "resources")
- cfg.Set("i18nDir", "i18n")
- cfg.Set("layoutDir", "layouts")
- cfg.Set("archetypeDir", "archetypes")
- cfg.Set("assetDir", "assets")
-
- return cfg
+ moduleQueriesConfig.MaxAge = -1
+ b, err = json.Marshal(moduleQueriesConfig)
+ c.Assert(err, qt.IsNil)
+ c.Assert(string(b), qt.Contains, `"maxAge":-1`)
}
diff --git a/cache/filecache/filecache_integration_test.go b/cache/filecache/filecache_integration_test.go
new file mode 100644
index 00000000000..88873c740a5
--- /dev/null
+++ b/cache/filecache/filecache_integration_test.go
@@ -0,0 +1,105 @@
+// 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 filecache_test
+
+import (
+ "path/filepath"
+ "testing"
+ "time"
+
+ qt "github.com/frankban/quicktest"
+ "github.com/gohugoio/hugo/htesting"
+ "github.com/gohugoio/hugo/hugolib"
+)
+
+// See issue #10781. That issue wouldn't have been triggered if we kept
+// the empty root directories (e.g. _resources/gen/images).
+// It's still an upstream Go issue that we also need to handle, but
+// this is a test for the first part.
+func TestPruneShouldPreserveEmptyCacheRoots(t *testing.T) {
+ files := `
+-- hugo.toml --
+baseURL = "https://example.com"
+-- content/_index.md --
+---
+title: "Home"
+---
+
+`
+
+ b := hugolib.Test(t, files, hugolib.TestOptOsFs(), hugolib.TestOptWithConfig(func(c *hugolib.IntegrationTestConfig) {
+ c.RunGC = true
+ }))
+
+ _, err := b.H.BaseFs.ResourcesCache.Stat(filepath.Join("_gen", "images"))
+
+ b.Assert(err, qt.IsNil)
+}
+
+func TestPruneImages(t *testing.T) {
+ if htesting.IsCI() {
+ // TODO(bep)
+ t.Skip("skip flaky test on CI server")
+ }
+ t.Skip("skip flaky test")
+ files := `
+-- hugo.toml --
+baseURL = "https://example.com"
+[caches]
+[caches.images]
+maxAge = "200ms"
+dir = ":resourceDir/_gen"
+-- content/_index.md --
+---
+title: "Home"
+---
+-- assets/a/pixel.png --
+iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
+-- layouts/home.html --
+{{ warnf "HOME!" }}
+{{ $img := resources.GetMatch "**.png" }}
+{{ $img = $img.Resize "3x3" }}
+{{ $img.RelPermalink }}
+
+
+
+`
+
+ b := hugolib.TestRunning(t, files, hugolib.TestOptOsFs(), hugolib.TestOptInfo(), hugolib.TestOptWithConfig(func(c *hugolib.IntegrationTestConfig) {
+ c.RunGC = true
+ }))
+
+ b.Assert(b.GCCount, qt.Equals, 0)
+ b.Assert(b.H, qt.IsNotNil)
+
+ imagesCacheDir := filepath.Join("_gen", "images")
+ _, err := b.H.BaseFs.ResourcesCache.Stat(imagesCacheDir)
+
+ b.Assert(err, qt.IsNil)
+
+ // TODO(bep) we need a way to test full rebuilds.
+ // For now, just sleep a little so the cache elements expires.
+ time.Sleep(500 * time.Millisecond)
+
+ b.RenameFile("assets/a/pixel.png", "assets/b/pixel2.png").Build()
+
+ b.Assert(b.GCCount, qt.Equals, 1)
+ // Build it again to GC the empty a dir.
+ b.Build()
+
+ _, err = b.H.BaseFs.ResourcesCache.Stat(filepath.Join(imagesCacheDir, "a"))
+ b.Assert(err, qt.Not(qt.IsNil))
+ _, err = b.H.BaseFs.ResourcesCache.Stat(imagesCacheDir)
+ b.Assert(err, qt.IsNil)
+}
diff --git a/cache/filecache/filecache_pruner.go b/cache/filecache/filecache_pruner.go
index db187570157..890aee96598 100644
--- a/cache/filecache/filecache_pruner.go
+++ b/cache/filecache/filecache_pruner.go
@@ -14,12 +14,13 @@
package filecache
import (
+ "fmt"
"io"
"os"
+ "github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/hugofs"
- "github.com/pkg/errors"
"github.com/spf13/afero"
)
@@ -30,16 +31,15 @@ import (
func (c Caches) Prune() (int, error) {
counter := 0
for k, cache := range c {
-
count, err := cache.Prune(false)
counter += count
if err != nil {
- if os.IsNotExist(err) {
+ if herrors.IsNotExist(err) {
continue
}
- return counter, errors.Wrapf(err, "failed to prune cache %q", k)
+ return counter, fmt.Errorf("failed to prune cache %q: %w", k, err)
}
}
@@ -50,8 +50,11 @@ func (c Caches) Prune() (int, error) {
// Prune removes expired and unused items from this cache.
// If force is set, everything will be removed not considering expiry time.
func (c *Cache) Prune(force bool) (int, error) {
- if c.pruneAllRootDir != "" {
- return c.pruneRootDir(force)
+ if c.cfg.entryIsDir {
+ return c.pruneRootDirs(force)
+ }
+ if err := c.init(); err != nil {
+ return 0, err
}
counter := 0
@@ -69,14 +72,19 @@ func (c *Cache) Prune(force bool) (int, error) {
// This cache dir may not exist.
return nil
}
- defer f.Close()
_, err = f.Readdirnames(1)
+ f.Close()
if err == io.EOF {
// Empty dir.
- err = c.Fs.Remove(name)
+ if name == "." {
+ // e.g. /_gen/images -- keep it even if empty.
+ err = nil
+ } else {
+ err = c.Fs.Remove(name)
+ }
}
- if err != nil && !os.IsNotExist(err) {
+ if err != nil && !herrors.IsNotExist(err) {
return err
}
@@ -85,9 +93,9 @@ func (c *Cache) Prune(force bool) (int, error) {
shouldRemove := force || c.isExpired(info.ModTime())
- if !shouldRemove && len(c.nlocker.seen) > 0 {
+ if !shouldRemove && len(c.entryLocker.seen) > 0 {
// Remove it if it's not been touched/used in the last build.
- _, seen := c.nlocker.seen[name]
+ _, seen := c.entryLocker.seen[name]
shouldRemove = !seen
}
@@ -97,7 +105,7 @@ func (c *Cache) Prune(force bool) (int, error) {
counter++
}
- if err != nil && !os.IsNotExist(err) {
+ if err != nil && !herrors.IsNotExist(err) {
return err
}
@@ -109,10 +117,45 @@ func (c *Cache) Prune(force bool) (int, error) {
return counter, err
}
-func (c *Cache) pruneRootDir(force bool) (int, error) {
- info, err := c.Fs.Stat(c.pruneAllRootDir)
+func (c *Cache) pruneRootDirs(force bool) (int, error) {
+ dirs, err := afero.ReadDir(c.Fs, "")
+ if err != nil {
+ if herrors.IsNotExist(err) {
+ return 0, nil
+ }
+ return 0, err
+ }
+
+ counter := 0
+
+ for _, dir := range dirs {
+ if !dir.IsDir() {
+ continue
+ }
+
+ count, err := c.pruneRootDir(dir.Name(), force)
+ if err != nil {
+ return counter, err
+ }
+ counter += count
+ }
+
+ return counter, nil
+}
+
+func (c *Cache) pruneRootDir(dirname string, force bool) (int, error) {
+ if err := c.init(); err != nil {
+ return 0, err
+ }
+
+ // Sanity check.
+ if dirname != "pkg" && len(dirname) < 5 {
+ panic(fmt.Sprintf("invalid cache dir name: %q", dirname))
+ }
+
+ info, err := c.Fs.Stat(dirname)
if err != nil {
- if os.IsNotExist(err) {
+ if herrors.IsNotExist(err) {
return 0, nil
}
return 0, err
@@ -122,5 +165,9 @@ func (c *Cache) pruneRootDir(force bool) (int, error) {
return 0, nil
}
- return hugofs.MakeReadableAndRemoveAllModulePkgDir(c.Fs, c.pruneAllRootDir)
+ if c.cfg.isReadOnly {
+ return hugofs.MakeReadableAndRemoveAllModulePkgDir(c.Fs, dirname)
+ }
+
+ return 1, c.Fs.RemoveAll(dirname)
}
diff --git a/cache/filecache/filecache_pruner_test.go b/cache/filecache/filecache_pruner_test.go
index 46e1317ce85..9cb58e05381 100644
--- a/cache/filecache/filecache_pruner_test.go
+++ b/cache/filecache/filecache_pruner_test.go
@@ -11,13 +11,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package filecache
+package filecache_test
import (
"fmt"
"testing"
"time"
+ "github.com/gohugoio/hugo/cache/filecache"
"github.com/spf13/afero"
qt "github.com/frankban/quicktest"
@@ -38,12 +39,9 @@ assetDir = "assets"
archeTypedir = "archetypes"
[caches]
-[caches.getjson]
+[caches.misc]
maxAge = "200ms"
dir = "/cache/c"
-[caches.getcsv]
-maxAge = "200ms"
-dir = "/cache/d"
[caches.assets]
maxAge = "200ms"
dir = ":resourceDir/_gen"
@@ -52,13 +50,16 @@ maxAge = "200ms"
dir = ":resourceDir/_gen"
`
- for _, name := range []string{cacheKeyGetCSV, cacheKeyGetJSON, cacheKeyAssets, cacheKeyImages} {
+ for _, name := range []string{filecache.CacheKeyAssets, filecache.CacheKeyImages} {
msg := qt.Commentf("cache: %s", name)
- p := newPathsSpec(t, afero.NewMemMapFs(), configStr)
- caches, err := NewCaches(p)
+ fs := afero.NewMemMapFs()
+ p := newPathsSpec(t, fs, configStr)
+ fileCachConfig := p.Cfg.GetConfigSection("caches").(filecache.Configs)
+ caches, err := filecache.NewCaches(fileCachConfig, fs)
c.Assert(err, qt.IsNil)
+ caches.SetResourceFs(fs)
cache := caches[name]
- for i := 0; i < 10; i++ {
+ for i := range 10 {
id := fmt.Sprintf("i%d", i)
cache.GetOrCreateBytes(id, func() ([]byte, error) {
return []byte("abc"), nil
@@ -73,9 +74,9 @@ dir = ":resourceDir/_gen"
c.Assert(err, qt.IsNil)
c.Assert(count, qt.Equals, 5, msg)
- for i := 0; i < 10; i++ {
+ for i := range 10 {
id := fmt.Sprintf("i%d", i)
- v := cache.getString(id)
+ v := cache.GetString(id)
if i < 5 {
c.Assert(v, qt.Equals, "")
} else {
@@ -83,8 +84,9 @@ dir = ":resourceDir/_gen"
}
}
- caches, err = NewCaches(p)
+ caches, err = filecache.NewCaches(fileCachConfig, fs)
c.Assert(err, qt.IsNil)
+ caches.SetResourceFs(fs)
cache = caches[name]
// Touch one and then prune.
cache.GetOrCreateBytes("i5", func() ([]byte, error) {
@@ -96,9 +98,9 @@ dir = ":resourceDir/_gen"
c.Assert(count, qt.Equals, 4)
// Now only the i5 should be left.
- for i := 0; i < 10; i++ {
+ for i := range 10 {
id := fmt.Sprintf("i%d", i)
- v := cache.getString(id)
+ v := cache.GetString(id)
if i != 5 {
c.Assert(v, qt.Equals, "")
} else {
diff --git a/cache/filecache/filecache_test.go b/cache/filecache/filecache_test.go
index 6a051a26495..5beb4054b7d 100644
--- a/cache/filecache/filecache_test.go
+++ b/cache/filecache/filecache_test.go
@@ -1,4 +1,4 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
+// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -11,27 +11,23 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package filecache
+package filecache_test
import (
"errors"
"fmt"
"io"
- "io/ioutil"
- "os"
- "path/filepath"
"strings"
"sync"
"testing"
"time"
- "github.com/gobwas/glob"
-
- "github.com/gohugoio/hugo/langs"
- "github.com/gohugoio/hugo/modules"
+ "github.com/gohugoio/hugo/htesting"
+ "github.com/gohugoio/hugo/cache/filecache"
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/config"
+ "github.com/gohugoio/hugo/config/testconfig"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
@@ -44,13 +40,8 @@ func TestFileCache(t *testing.T) {
t.Parallel()
c := qt.New(t)
- tempWorkingDir, err := ioutil.TempDir("", "hugo_filecache_test_work")
- c.Assert(err, qt.IsNil)
- defer os.Remove(tempWorkingDir)
-
- tempCacheDir, err := ioutil.TempDir("", "hugo_filecache_test_cache")
- c.Assert(err, qt.IsNil)
- defer os.Remove(tempCacheDir)
+ tempWorkingDir := t.TempDir()
+ tempCacheDir := t.TempDir()
osfs := afero.NewOsFs()
@@ -75,7 +66,7 @@ assetDir = "assets"
archeTypedir = "archetypes"
[caches]
-[caches.getJSON]
+[caches.misc]
maxAge = "10h"
dir = ":cacheDir/c"
@@ -89,32 +80,17 @@ dir = ":cacheDir/c"
configStr = strings.Replace(configStr, "\\", winPathSep, -1)
p := newPathsSpec(t, osfs, configStr)
+ fileCachConfig := p.Cfg.GetConfigSection("caches").(filecache.Configs)
- caches, err := NewCaches(p)
+ caches, err := filecache.NewCaches(fileCachConfig, p.Fs.Source)
c.Assert(err, qt.IsNil)
+ caches.SetResourceFs(p.SourceFs)
- cache := caches.Get("GetJSON")
+ cache := caches.Get("Misc")
c.Assert(cache, qt.Not(qt.IsNil))
- c.Assert(cache.maxAge.String(), qt.Equals, "10h0m0s")
-
- bfs, ok := cache.Fs.(*afero.BasePathFs)
- c.Assert(ok, qt.Equals, true)
- filename, err := bfs.RealPath("key")
- c.Assert(err, qt.IsNil)
- if test.cacheDir != "" {
- c.Assert(filename, qt.Equals, filepath.Join(test.cacheDir, "c/"+filecacheRootDirname+"/getjson/key"))
- } else {
- // Temp dir.
- c.Assert(filename, qt.Matches, ".*hugo_cache.*"+filecacheRootDirname+".*key")
- }
cache = caches.Get("Images")
c.Assert(cache, qt.Not(qt.IsNil))
- c.Assert(cache.maxAge, qt.Equals, time.Duration(-1))
- bfs, ok = cache.Fs.(*afero.BasePathFs)
- c.Assert(ok, qt.Equals, true)
- filename, _ = bfs.RealPath("key")
- c.Assert(filename, qt.Equals, filepath.FromSlash("_gen/images/key"))
rf := func(s string) func() (io.ReadCloser, error) {
return func() (io.ReadCloser, error) {
@@ -123,7 +99,7 @@ dir = ":cacheDir/c"
io.Closer
}{
strings.NewReader(s),
- ioutil.NopCloser(nil),
+ io.NopCloser(nil),
}, nil
}
}
@@ -132,13 +108,13 @@ dir = ":cacheDir/c"
return []byte("bcd"), nil
}
- for _, ca := range []*Cache{caches.ImageCache(), caches.AssetsCache(), caches.GetJSONCache(), caches.GetCSVCache()} {
- for i := 0; i < 2; i++ {
+ for _, ca := range []*filecache.Cache{caches.ImageCache(), caches.AssetsCache()} {
+ for range 2 {
info, r, err := ca.GetOrCreate("a", rf("abc"))
c.Assert(err, qt.IsNil)
c.Assert(r, qt.Not(qt.IsNil))
c.Assert(info.Name, qt.Equals, "a")
- b, _ := ioutil.ReadAll(r)
+ b, _ := io.ReadAll(r)
r.Close()
c.Assert(string(b), qt.Equals, "abc")
@@ -154,30 +130,28 @@ dir = ":cacheDir/c"
_, r, err = ca.GetOrCreate("a", rf("bcd"))
c.Assert(err, qt.IsNil)
- b, _ = ioutil.ReadAll(r)
+ b, _ = io.ReadAll(r)
r.Close()
c.Assert(string(b), qt.Equals, "abc")
}
}
- c.Assert(caches.Get("getJSON"), qt.Not(qt.IsNil))
-
info, w, err := caches.ImageCache().WriteCloser("mykey")
c.Assert(err, qt.IsNil)
c.Assert(info.Name, qt.Equals, "mykey")
io.WriteString(w, "Hugo is great!")
w.Close()
- c.Assert(caches.ImageCache().getString("mykey"), qt.Equals, "Hugo is great!")
+ c.Assert(caches.ImageCache().GetString("mykey"), qt.Equals, "Hugo is great!")
info, r, err := caches.ImageCache().Get("mykey")
c.Assert(err, qt.IsNil)
c.Assert(r, qt.Not(qt.IsNil))
c.Assert(info.Name, qt.Equals, "mykey")
- b, _ := ioutil.ReadAll(r)
+ b, _ := io.ReadAll(r)
r.Close()
c.Assert(string(b), qt.Equals, "Hugo is great!")
- info, b, err = caches.ImageCache().GetBytes("mykey")
+ info, b, err = caches.ImageCache().GetItemBytes("mykey")
c.Assert(err, qt.IsNil)
c.Assert(info.Name, qt.Equals, "mykey")
c.Assert(string(b), qt.Equals, "Hugo is great!")
@@ -186,6 +160,7 @@ dir = ":cacheDir/c"
}
func TestFileCacheConcurrent(t *testing.T) {
+ htesting.SkipSlowTestUnlessCI(t)
t.Parallel()
c := qt.New(t)
@@ -200,18 +175,19 @@ assetDir = "assets"
archeTypedir = "archetypes"
[caches]
-[caches.getjson]
+[caches.misc]
maxAge = "1s"
dir = "/cache/c"
`
p := newPathsSpec(t, afero.NewMemMapFs(), configStr)
-
- caches, err := NewCaches(p)
+ fileCachConfig := p.Cfg.GetConfigSection("caches").(filecache.Configs)
+ caches, err := filecache.NewCaches(fileCachConfig, p.Fs.Source)
c.Assert(err, qt.IsNil)
+ caches.SetResourceFs(p.Fs.Source)
- const cacheName = "getjson"
+ const cacheName = "misc"
filenameData := func(i int) (string, string) {
data := fmt.Sprintf("data: %d", i)
@@ -221,11 +197,11 @@ dir = "/cache/c"
var wg sync.WaitGroup
- for i := 0; i < 50; i++ {
+ for i := range 50 {
wg.Add(1)
go func(i int) {
defer wg.Done()
- for j := 0; j < 20; j++ {
+ for range 20 {
ca := caches.Get(cacheName)
c.Assert(ca, qt.Not(qt.IsNil))
filename, data := filenameData(i)
@@ -233,7 +209,7 @@ dir = "/cache/c"
return hugio.ToReadCloser(strings.NewReader(data)), nil
})
c.Assert(err, qt.IsNil)
- b, _ := ioutil.ReadAll(r)
+ b, _ := io.ReadAll(r)
r.Close()
c.Assert(string(b), qt.Equals, data)
// Trigger some expiration.
@@ -251,24 +227,24 @@ func TestFileCacheReadOrCreateErrorInRead(t *testing.T) {
var result string
- rf := func(failLevel int) func(info ItemInfo, r io.ReadSeeker) error {
- return func(info ItemInfo, r io.ReadSeeker) error {
+ rf := func(failLevel int) func(info filecache.ItemInfo, r io.ReadSeeker) error {
+ return func(info filecache.ItemInfo, r io.ReadSeeker) error {
if failLevel > 0 {
if failLevel > 1 {
- return ErrFatal
+ return filecache.ErrFatal
}
return errors.New("fail")
}
- b, _ := ioutil.ReadAll(r)
+ b, _ := io.ReadAll(r)
result = string(b)
return nil
}
}
- bf := func(s string) func(info ItemInfo, w io.WriteCloser) error {
- return func(info ItemInfo, w io.WriteCloser) error {
+ bf := func(s string) func(info filecache.ItemInfo, w io.WriteCloser) error {
+ return func(info filecache.ItemInfo, w io.WriteCloser) error {
defer w.Close()
result = s
_, err := w.Write([]byte(s))
@@ -276,7 +252,12 @@ func TestFileCacheReadOrCreateErrorInRead(t *testing.T) {
}
}
- cache := NewCache(afero.NewMemMapFs(), 100*time.Hour, "")
+ cfg := filecache.FileCacheConfig{
+ MaxAge: 100 * time.Hour,
+ Dir: "cache/c",
+ }
+
+ cache := filecache.NewCache(afero.NewMemMapFs(), cfg)
const id = "a32"
@@ -290,59 +271,15 @@ func TestFileCacheReadOrCreateErrorInRead(t *testing.T) {
c.Assert(err, qt.IsNil)
c.Assert(result, qt.Equals, "v3")
_, err = cache.ReadOrCreate(id, rf(2), bf("v3"))
- c.Assert(err, qt.Equals, ErrFatal)
-}
-
-func TestCleanID(t *testing.T) {
- c := qt.New(t)
- c.Assert(cleanID(filepath.FromSlash("/a/b//c.txt")), qt.Equals, filepath.FromSlash("a/b/c.txt"))
- c.Assert(cleanID(filepath.FromSlash("a/b//c.txt")), qt.Equals, filepath.FromSlash("a/b/c.txt"))
-}
-
-func initConfig(fs afero.Fs, cfg config.Provider) error {
- if _, err := langs.LoadLanguageSettings(cfg, nil); err != nil {
- return err
- }
-
- modConfig, err := modules.DecodeConfig(cfg)
- if err != nil {
- return err
- }
-
- workingDir := cfg.GetString("workingDir")
- themesDir := cfg.GetString("themesDir")
- if !filepath.IsAbs(themesDir) {
- themesDir = filepath.Join(workingDir, themesDir)
- }
- globAll := glob.MustCompile("**", '/')
- modulesClient := modules.NewClient(modules.ClientConfig{
- Fs: fs,
- WorkingDir: workingDir,
- ThemesDir: themesDir,
- ModuleConfig: modConfig,
- IgnoreVendor: globAll,
- })
-
- moduleConfig, err := modulesClient.Collect()
- if err != nil {
- return err
- }
-
- if err := modules.ApplyProjectConfigDefaults(cfg, moduleConfig.ActiveModules[len(moduleConfig.ActiveModules)-1]); err != nil {
- return err
- }
-
- cfg.Set("allModules", moduleConfig.ActiveModules)
-
- return nil
+ c.Assert(err, qt.Equals, filecache.ErrFatal)
}
func newPathsSpec(t *testing.T, fs afero.Fs, configStr string) *helpers.PathSpec {
c := qt.New(t)
cfg, err := config.FromConfigString(configStr, "toml")
c.Assert(err, qt.IsNil)
- initConfig(fs, cfg)
- p, err := helpers.NewPathSpec(hugofs.NewFrom(fs, cfg), cfg, nil)
+ acfg := testconfig.GetTestConfig(fs, cfg)
+ p, err := helpers.NewPathSpec(hugofs.NewFrom(fs, acfg.BaseConfig()), acfg, nil, nil)
c.Assert(err, qt.IsNil)
return p
}
diff --git a/cache/httpcache/httpcache.go b/cache/httpcache/httpcache.go
new file mode 100644
index 00000000000..44f243bd3d9
--- /dev/null
+++ b/cache/httpcache/httpcache.go
@@ -0,0 +1,240 @@
+// Copyright 2024 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package httpcache
+
+import (
+ "encoding/json"
+ "time"
+
+ "github.com/gobwas/glob"
+ "github.com/gohugoio/hugo/common/predicate"
+ "github.com/gohugoio/hugo/config"
+ "github.com/mitchellh/mapstructure"
+)
+
+// DefaultConfig holds the default configuration for the HTTP cache.
+var DefaultConfig = Config{
+ RespectCacheControlNoStoreInRequest: true,
+ RespectCacheControlNoStoreInResponse: false,
+ Cache: Cache{
+ For: GlobMatcher{
+ Excludes: []string{"**"},
+ },
+ },
+ Polls: []PollConfig{
+ {
+ For: GlobMatcher{
+ Includes: []string{"**"},
+ },
+ Disable: true,
+ },
+ },
+}
+
+// Config holds the configuration for the HTTP cache.
+type Config struct {
+ // When enabled and there's a Cache-Control: no-store directive in the request, response will never be stored in disk cache.
+ RespectCacheControlNoStoreInRequest bool
+
+ // When enabled and there's a Cache-Control: no-store directive in the response, response will never be stored in disk cache.
+ RespectCacheControlNoStoreInResponse bool
+
+ // Enables HTTP cache behavior (RFC 9111) for these resources.
+ // When this is not enabled for a resource, Hugo will go straight to the file cache.
+ Cache Cache
+
+ // Polls holds a list of configurations for polling remote resources to detect changes in watch mode.
+ // This can be disabled for some resources, typically if they are known to not change.
+ Polls []PollConfig
+}
+
+type Cache struct {
+ // Enable HTTP cache behavior (RFC 9111) for these resources.
+ For GlobMatcher
+}
+
+func (c *Config) Compile() (ConfigCompiled, error) {
+ cc := ConfigCompiled{
+ Base: *c,
+ }
+
+ p, err := c.Cache.For.CompilePredicate()
+ if err != nil {
+ return cc, err
+ }
+
+ cc.For = p
+
+ for _, pc := range c.Polls {
+
+ p, err := pc.For.CompilePredicate()
+ if err != nil {
+ return cc, err
+ }
+
+ cc.PollConfigs = append(cc.PollConfigs, PollConfigCompiled{
+ For: p,
+ Config: pc,
+ })
+ }
+
+ return cc, nil
+}
+
+// PollConfig holds the configuration for polling remote resources to detect changes in watch mode.
+type PollConfig struct {
+ // What remote resources to apply this configuration to.
+ For GlobMatcher
+
+ // Disable polling for this configuration.
+ Disable bool
+
+ // Low is the lower bound for the polling interval.
+ // This is the starting point when the resource has recently changed,
+ // if that resource stops changing, the polling interval will gradually increase towards High.
+ Low time.Duration
+
+ // High is the upper bound for the polling interval.
+ // This is the interval used when the resource is stable.
+ High time.Duration
+}
+
+func (c PollConfig) MarshalJSON() (b []byte, err error) {
+ // Marshal the durations as strings.
+ type Alias PollConfig
+ return json.Marshal(&struct {
+ Low string
+ High string
+ Alias
+ }{
+ Low: c.Low.String(),
+ High: c.High.String(),
+ Alias: (Alias)(c),
+ })
+}
+
+type GlobMatcher struct {
+ // Excludes holds a list of glob patterns that will be excluded.
+ Excludes []string
+
+ // Includes holds a list of glob patterns that will be included.
+ Includes []string
+}
+
+func (gm GlobMatcher) IsZero() bool {
+ return len(gm.Includes) == 0 && len(gm.Excludes) == 0
+}
+
+type ConfigCompiled struct {
+ Base Config
+ For predicate.P[string]
+ PollConfigs []PollConfigCompiled
+}
+
+func (c *ConfigCompiled) PollConfigFor(s string) PollConfigCompiled {
+ for _, pc := range c.PollConfigs {
+ if pc.For(s) {
+ return pc
+ }
+ }
+ return PollConfigCompiled{}
+}
+
+func (c *ConfigCompiled) IsPollingDisabled() bool {
+ for _, pc := range c.PollConfigs {
+ if !pc.Config.Disable {
+ return false
+ }
+ }
+ return true
+}
+
+type PollConfigCompiled struct {
+ For predicate.P[string]
+ Config PollConfig
+}
+
+func (p PollConfigCompiled) IsZero() bool {
+ return p.For == nil
+}
+
+func (gm *GlobMatcher) CompilePredicate() (func(string) bool, error) {
+ if gm.IsZero() {
+ panic("no includes or excludes")
+ }
+ var b predicate.PR[string]
+ for _, include := range gm.Includes {
+ g, err := glob.Compile(include, '/')
+ if err != nil {
+ return nil, err
+ }
+ fn := func(s string) predicate.Match {
+ return predicate.BoolMatch(g.Match(s))
+ }
+ b = b.Or(fn)
+ }
+
+ for _, exclude := range gm.Excludes {
+ g, err := glob.Compile(exclude, '/')
+ if err != nil {
+ return nil, err
+ }
+ fn := func(s string) predicate.Match {
+ return predicate.BoolMatch(!g.Match(s))
+ }
+ b = b.And(fn)
+ }
+
+ return b.BoolFunc(), nil
+}
+
+func DecodeConfig(_ config.BaseConfig, m map[string]any) (Config, error) {
+ if len(m) == 0 {
+ return DefaultConfig, nil
+ }
+
+ var c Config
+
+ dc := &mapstructure.DecoderConfig{
+ Result: &c,
+ DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
+ WeaklyTypedInput: true,
+ }
+
+ decoder, err := mapstructure.NewDecoder(dc)
+ if err != nil {
+ return c, err
+ }
+
+ if err := decoder.Decode(m); err != nil {
+ return c, err
+ }
+
+ if c.Cache.For.IsZero() {
+ c.Cache.For = DefaultConfig.Cache.For
+ }
+
+ for pci := range c.Polls {
+ if c.Polls[pci].For.IsZero() {
+ c.Polls[pci].For = DefaultConfig.Cache.For
+ c.Polls[pci].Disable = true
+ }
+ }
+
+ if len(c.Polls) == 0 {
+ c.Polls = DefaultConfig.Polls
+ }
+
+ return c, nil
+}
diff --git a/cache/httpcache/httpcache_integration_test.go b/cache/httpcache/httpcache_integration_test.go
new file mode 100644
index 00000000000..4d6a5f71848
--- /dev/null
+++ b/cache/httpcache/httpcache_integration_test.go
@@ -0,0 +1,95 @@
+// Copyright 2024 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package httpcache_test
+
+import (
+ "testing"
+ "time"
+
+ qt "github.com/frankban/quicktest"
+ "github.com/gohugoio/hugo/hugolib"
+)
+
+func TestConfigCustom(t *testing.T) {
+ t.Parallel()
+
+ files := `
+-- hugo.toml --
+[httpcache]
+[httpcache.cache.for]
+includes = ["**gohugo.io**"]
+[[httpcache.polls]]
+low = "5s"
+high = "32s"
+[httpcache.polls.for]
+includes = ["**gohugo.io**"]
+
+
+`
+
+ b := hugolib.Test(t, files)
+
+ httpcacheConf := b.H.Configs.Base.HTTPCache
+ compiled := b.H.Configs.Base.C.HTTPCache
+
+ b.Assert(httpcacheConf.Cache.For.Includes, qt.DeepEquals, []string{"**gohugo.io**"})
+ b.Assert(httpcacheConf.Cache.For.Excludes, qt.IsNil)
+
+ pc := compiled.PollConfigFor("https://gohugo.io/foo.jpg")
+ b.Assert(pc.Config.Low, qt.Equals, 5*time.Second)
+ b.Assert(pc.Config.High, qt.Equals, 32*time.Second)
+ b.Assert(compiled.PollConfigFor("https://example.com/foo.jpg").IsZero(), qt.IsTrue)
+}
+
+func TestConfigDefault(t *testing.T) {
+ t.Parallel()
+
+ files := `
+-- hugo.toml --
+`
+ b := hugolib.Test(t, files)
+
+ compiled := b.H.Configs.Base.C.HTTPCache
+
+ b.Assert(compiled.For("https://gohugo.io/posts.json"), qt.IsFalse)
+ b.Assert(compiled.For("https://gohugo.io/foo.jpg"), qt.IsFalse)
+ b.Assert(compiled.PollConfigFor("https://gohugo.io/foo.jpg").Config.Disable, qt.IsTrue)
+}
+
+func TestConfigPollsOnly(t *testing.T) {
+ t.Parallel()
+ files := `
+-- hugo.toml --
+[httpcache]
+[[httpcache.polls]]
+low = "5s"
+high = "32s"
+[httpcache.polls.for]
+includes = ["**gohugo.io**"]
+
+
+`
+
+ b := hugolib.Test(t, files)
+
+ compiled := b.H.Configs.Base.C.HTTPCache
+
+ b.Assert(compiled.For("https://gohugo.io/posts.json"), qt.IsFalse)
+ b.Assert(compiled.For("https://gohugo.io/foo.jpg"), qt.IsFalse)
+
+ pc := compiled.PollConfigFor("https://gohugo.io/foo.jpg")
+ b.Assert(pc.Config.Low, qt.Equals, 5*time.Second)
+ b.Assert(pc.Config.High, qt.Equals, 32*time.Second)
+ b.Assert(compiled.PollConfigFor("https://example.com/foo.jpg").IsZero(), qt.IsTrue)
+}
diff --git a/cache/httpcache/httpcache_test.go b/cache/httpcache/httpcache_test.go
new file mode 100644
index 00000000000..737dff23958
--- /dev/null
+++ b/cache/httpcache/httpcache_test.go
@@ -0,0 +1,73 @@
+// Copyright 2024 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package httpcache
+
+import (
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+ "github.com/gohugoio/hugo/config"
+)
+
+func TestGlobMatcher(t *testing.T) {
+ c := qt.New(t)
+
+ g := GlobMatcher{
+ Includes: []string{"**/*.jpg", "**.png", "**/bar/**"},
+ Excludes: []string{"**/foo.jpg", "**.css"},
+ }
+
+ p, err := g.CompilePredicate()
+ c.Assert(err, qt.IsNil)
+
+ c.Assert(p("foo.jpg"), qt.IsFalse)
+ c.Assert(p("foo.png"), qt.IsTrue)
+ c.Assert(p("foo/bar.jpg"), qt.IsTrue)
+ c.Assert(p("foo/bar.png"), qt.IsTrue)
+ c.Assert(p("foo/bar/foo.jpg"), qt.IsFalse)
+ c.Assert(p("foo/bar/foo.css"), qt.IsFalse)
+ c.Assert(p("foo.css"), qt.IsFalse)
+ c.Assert(p("foo/bar/foo.css"), qt.IsFalse)
+ c.Assert(p("foo/bar/foo.xml"), qt.IsTrue)
+}
+
+func TestDefaultConfig(t *testing.T) {
+ c := qt.New(t)
+
+ _, err := DefaultConfig.Compile()
+ c.Assert(err, qt.IsNil)
+}
+
+func TestDecodeConfigInjectsDefaultAndCompiles(t *testing.T) {
+ c := qt.New(t)
+
+ cfg, err := DecodeConfig(config.BaseConfig{}, map[string]any{})
+ c.Assert(err, qt.IsNil)
+ c.Assert(cfg, qt.DeepEquals, DefaultConfig)
+
+ _, err = cfg.Compile()
+ c.Assert(err, qt.IsNil)
+
+ cfg, err = DecodeConfig(config.BaseConfig{}, map[string]any{
+ "cache": map[string]any{
+ "polls": []map[string]any{
+ {"disable": true},
+ },
+ },
+ })
+ c.Assert(err, qt.IsNil)
+
+ _, err = cfg.Compile()
+ c.Assert(err, qt.IsNil)
+}
diff --git a/cache/namedmemcache/named_cache.go b/cache/namedmemcache/named_cache.go
deleted file mode 100644
index 4e912bf5871..00000000000
--- a/cache/namedmemcache/named_cache.go
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// 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 namedmemcache provides a memory cache with a named lock. This is suitable
-// for situations where creating the cached resource can be time consuming or otherwise
-// resource hungry, or in situations where a "once only per key" is a requirement.
-package namedmemcache
-
-import (
- "sync"
-
- "github.com/BurntSushi/locker"
-)
-
-// Cache holds the cached values.
-type Cache struct {
- nlocker *locker.Locker
- cache map[string]cacheEntry
- mu sync.RWMutex
-}
-
-type cacheEntry struct {
- value interface{}
- err error
-}
-
-// New creates a new cache.
-func New() *Cache {
- return &Cache{
- nlocker: locker.NewLocker(),
- cache: make(map[string]cacheEntry),
- }
-}
-
-// Clear clears the cache state.
-func (c *Cache) Clear() {
- c.mu.Lock()
- defer c.mu.Unlock()
-
- c.cache = make(map[string]cacheEntry)
- c.nlocker = locker.NewLocker()
-}
-
-// GetOrCreate tries to get the value with the given cache key, if not found
-// create will be called and cached.
-// This method is thread safe. It also guarantees that the create func for a given
-// key is invoked only once for this cache.
-func (c *Cache) GetOrCreate(key string, create func() (interface{}, error)) (interface{}, error) {
- c.mu.RLock()
- entry, found := c.cache[key]
- c.mu.RUnlock()
-
- if found {
- return entry.value, entry.err
- }
-
- c.nlocker.Lock(key)
- defer c.nlocker.Unlock(key)
-
- // Create it.
- value, err := create()
-
- c.mu.Lock()
- c.cache[key] = cacheEntry{value: value, err: err}
- c.mu.Unlock()
-
- return value, err
-}
diff --git a/cache/namedmemcache/named_cache_test.go b/cache/namedmemcache/named_cache_test.go
deleted file mode 100644
index 9feddb11f2a..00000000000
--- a/cache/namedmemcache/named_cache_test.go
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// 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 namedmemcache
-
-import (
- "fmt"
- "sync"
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestNamedCache(t *testing.T) {
- t.Parallel()
- c := qt.New(t)
-
- cache := New()
-
- counter := 0
- create := func() (interface{}, error) {
- counter++
- return counter, nil
- }
-
- for i := 0; i < 5; i++ {
- v1, err := cache.GetOrCreate("a1", create)
- c.Assert(err, qt.IsNil)
- c.Assert(v1, qt.Equals, 1)
- v2, err := cache.GetOrCreate("a2", create)
- c.Assert(err, qt.IsNil)
- c.Assert(v2, qt.Equals, 2)
- }
-
- cache.Clear()
-
- v3, err := cache.GetOrCreate("a2", create)
- c.Assert(err, qt.IsNil)
- c.Assert(v3, qt.Equals, 3)
-}
-
-func TestNamedCacheConcurrent(t *testing.T) {
- t.Parallel()
-
- c := qt.New(t)
-
- var wg sync.WaitGroup
-
- cache := New()
-
- create := func(i int) func() (interface{}, error) {
- return func() (interface{}, error) {
- return i, nil
- }
- }
-
- for i := 0; i < 10; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- for j := 0; j < 100; j++ {
- id := fmt.Sprintf("id%d", j)
- v, err := cache.GetOrCreate(id, create(j))
- c.Assert(err, qt.IsNil)
- c.Assert(v, qt.Equals, j)
- }
- }()
- }
- wg.Wait()
-}
diff --git a/check.sh b/check.sh
new file mode 100755
index 00000000000..20aacb1eff1
--- /dev/null
+++ b/check.sh
@@ -0,0 +1,82 @@
+#!/bin/bash
+
+set -e
+
+# Default to all packages if none specified
+PACKAGES="${1:-./...}"
+
+echo "==> Checking packages: $PACKAGES"
+
+# Timing arrays
+declare -a STEP_NAMES
+declare -a STEP_TIMES
+
+time_step() {
+ local name="$1"
+ shift
+ local start=$(date +%s.%N)
+ "$@"
+ local end=$(date +%s.%N)
+ local elapsed=$(echo "$end - $start" | bc)
+ STEP_NAMES+=("$name")
+ STEP_TIMES+=("$elapsed")
+}
+
+# Check gofmt
+run_gofmt() {
+ echo "==> Running gofmt..."
+ # Convert package pattern to path (e.g., ./hugolib/... -> ./hugolib)
+ local path="${PACKAGES%/...}"
+ GOFMT_OUTPUT=$(gofmt -l "$path" 2>&1) || true
+ if [ -n "$GOFMT_OUTPUT" ]; then
+ echo "gofmt found issues in:"
+ echo "$GOFMT_OUTPUT"
+ exit 1
+ fi
+ echo " OK"
+}
+
+# Run staticcheck
+run_staticcheck() {
+ # Check if staticcheck is installed, install if not
+ if ! command -v staticcheck &> /dev/null; then
+ echo "==> Installing staticcheck..."
+ go install honnef.co/go/tools/cmd/staticcheck@latest
+ fi
+ echo "==> Running staticcheck..."
+ staticcheck $PACKAGES
+ echo " OK"
+}
+
+# Run tests
+run_tests() {
+ echo "==> Running tests..."
+ local output
+ if ! output=$(go test -failfast $PACKAGES 2>&1); then
+ echo "$output"
+ exit 1
+ fi
+ echo " OK"
+}
+
+# Run all steps with timing
+TOTAL_START=$(date +%s.%N)
+
+time_step "gofmt" run_gofmt
+time_step "staticcheck" run_staticcheck
+time_step "tests" run_tests
+
+TOTAL_END=$(date +%s.%N)
+TOTAL_ELAPSED=$(echo "$TOTAL_END - $TOTAL_START" | bc)
+
+# Print timing summary
+echo ""
+echo "==> All checks passed!"
+echo ""
+echo "Timing summary:"
+echo "---------------"
+for i in "${!STEP_NAMES[@]}"; do
+ printf " %-15s %6.2fs\n" "${STEP_NAMES[$i]}" "${STEP_TIMES[$i]}"
+done
+echo "---------------"
+printf " %-15s %6.2fs\n" "Total" "$TOTAL_ELAPSED"
diff --git a/check_gofmt.sh b/check_gofmt.sh
new file mode 100755
index 00000000000..c77517d3ff3
--- /dev/null
+++ b/check_gofmt.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+diff <(gofmt -d .) <(printf '')
\ No newline at end of file
diff --git a/codegen/methods.go b/codegen/methods.go
index 23994c5f875..d705914bcff 100644
--- a/codegen/methods.go
+++ b/codegen/methods.go
@@ -26,6 +26,7 @@ import (
"path/filepath"
"reflect"
"regexp"
+ "slices"
"sort"
"strings"
"sync"
@@ -72,7 +73,7 @@ func (c *Inspector) MethodsFromTypes(include []reflect.Type, exclude []reflect.T
nameAndPackage := func(t reflect.Type) (string, string) {
var name, pkg string
- isPointer := t.Kind() == reflect.Ptr
+ isPointer := t.Kind() == reflect.Pointer
if isPointer {
t = t.Elem()
@@ -102,7 +103,7 @@ func (c *Inspector) MethodsFromTypes(include []reflect.Type, exclude []reflect.T
}
for _, t := range include {
- for i := 0; i < t.NumMethod(); i++ {
+ for i := range t.NumMethod() {
m := t.Method(i)
if excludes[m.Name] || seen[m.Name] {
@@ -122,7 +123,7 @@ func (c *Inspector) MethodsFromTypes(include []reflect.Type, exclude []reflect.T
method := Method{Owner: t, OwnerName: ownerName, Name: m.Name}
- for i := 0; i < numIn; i++ {
+ for i := range numIn {
in := m.Type.In(i)
name, pkg := nameAndPackage(in)
@@ -137,7 +138,7 @@ func (c *Inspector) MethodsFromTypes(include []reflect.Type, exclude []reflect.T
numOut := m.Type.NumOut()
if numOut > 0 {
- for i := 0; i < numOut; i++ {
+ for i := range numOut {
out := m.Type.Out(i)
name, pkg := nameAndPackage(out)
@@ -304,7 +305,7 @@ func (m Method) inOutStr() string {
}
args := make([]string, len(m.In))
- for i := 0; i < len(args); i++ {
+ for i := range args {
args[i] = fmt.Sprintf("arg%d", i)
}
return "(" + strings.Join(args, ", ") + ")"
@@ -316,7 +317,7 @@ func (m Method) inStr() string {
}
args := make([]string, len(m.In))
- for i := 0; i < len(args); i++ {
+ for i := range args {
args[i] = fmt.Sprintf("arg%d %s", i, m.In[i])
}
return "(" + strings.Join(args, ", ") + ")"
@@ -339,7 +340,7 @@ func (m Method) outStrNamed() string {
}
outs := make([]string, len(m.Out))
- for i := 0; i < len(outs); i++ {
+ for i := range outs {
outs[i] = fmt.Sprintf("o%d %s", i, m.Out[i])
}
@@ -435,7 +436,7 @@ func (m Methods) ToMarshalJSON(receiver, pkgPath string, excludes ...string) (st
// Exclude self
for i, pkgImp := range pkgImports {
if pkgImp == pkgPath {
- pkgImports = append(pkgImports[:i], pkgImports[i+1:]...)
+ pkgImports = slices.Delete(pkgImports, i, i+1)
}
}
}
@@ -452,12 +453,15 @@ func collectMethodsRecursive(pkg string, f []*ast.Field) []string {
}
if ident, ok := m.Type.(*ast.Ident); ok && ident.Obj != nil {
- // Embedded interface
- methodNames = append(
- methodNames,
- collectMethodsRecursive(
- pkg,
- ident.Obj.Decl.(*ast.TypeSpec).Type.(*ast.InterfaceType).Methods.List)...)
+ switch tt := ident.Obj.Decl.(*ast.TypeSpec).Type.(type) {
+ case *ast.InterfaceType:
+ // Embedded interface
+ methodNames = append(
+ methodNames,
+ collectMethodsRecursive(
+ pkg,
+ tt.Methods.List)...)
+ }
} else {
// Embedded, but in a different file/package. Return the
// package.Name and deal with that later.
@@ -505,7 +509,7 @@ func typeName(name, pkg string) string {
func uniqueNonEmptyStrings(s []string) []string {
var unique []string
- set := map[string]interface{}{}
+ set := map[string]any{}
for _, val := range s {
if val == "" {
continue
diff --git a/codegen/methods_test.go b/codegen/methods_test.go
index 66000f4e953..bce80ec03f2 100644
--- a/codegen/methods_test.go
+++ b/codegen/methods_test.go
@@ -26,9 +26,9 @@ import (
func TestMethods(t *testing.T) {
var (
- zeroIE = reflect.TypeOf((*IEmbed)(nil)).Elem()
- zeroIEOnly = reflect.TypeOf((*IEOnly)(nil)).Elem()
- zeroI = reflect.TypeOf((*I)(nil)).Elem()
+ zeroIE = reflect.TypeFor[IEmbed]()
+ zeroIEOnly = reflect.TypeFor[IEOnly]()
+ zeroI = reflect.TypeFor[I]()
)
dir, _ := os.Getwd()
@@ -85,7 +85,7 @@ type I interface {
Method3(myint int, mystring string)
Method5() (string, error)
Method6() *net.IP
- Method7() interface{}
+ Method7() any
Method8() herrors.ErrorContext
method2()
method9() os.FileInfo
diff --git a/commands/check.go b/commands/check.go
deleted file mode 100644
index f9be84852af..00000000000
--- a/commands/check.go
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// 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.
-
-// +build !darwin
-
-package commands
-
-import (
- "github.com/spf13/cobra"
-)
-
-var _ cmder = (*checkCmd)(nil)
-
-type checkCmd struct {
- *baseCmd
-}
-
-func newCheckCmd() *checkCmd {
- return &checkCmd{baseCmd: &baseCmd{
- cmd: &cobra.Command{
- Use: "check",
- Short: "Contains some verification checks",
- },
- }}
-}
diff --git a/commands/check_darwin.go b/commands/check_darwin.go
deleted file mode 100644
index ac542916d00..00000000000
--- a/commands/check_darwin.go
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// 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/spf13/cobra"
-)
-
-var _ cmder = (*checkCmd)(nil)
-
-type checkCmd struct {
- *baseCmd
-}
-
-func newCheckCmd() *checkCmd {
- cc := &checkCmd{baseCmd: &baseCmd{
- cmd: &cobra.Command{
- Use: "check",
- Short: "Contains some verification checks",
- },
- }}
-
- cc.cmd.AddCommand(newLimitCmd().getCommand())
-
- return cc
-}
diff --git a/commands/commandeer.go b/commands/commandeer.go
index 7706360abb0..21a8f24fc1c 100644
--- a/commands/commandeer.go
+++ b/commands/commandeer.go
@@ -1,4 +1,4 @@
-// Copyright 2019 The Hugo Authors. All rights reserved.
+// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,423 +14,679 @@
package commands
import (
- "bytes"
+ "context"
"errors"
- "io/ioutil"
+ "fmt"
+ "io"
+ "log"
"os"
+ "os/signal"
"path/filepath"
- "regexp"
+ "runtime"
+ "strings"
"sync"
+ "sync/atomic"
+ "syscall"
"time"
- hconfig "github.com/gohugoio/hugo/config"
+ "go.uber.org/automaxprocs/maxprocs"
- "golang.org/x/sync/semaphore"
-
- "github.com/gohugoio/hugo/common/herrors"
- "github.com/gohugoio/hugo/common/hugo"
-
- jww "github.com/spf13/jwalterweatherman"
+ "github.com/bep/clocks"
+ "github.com/bep/lazycache"
+ "github.com/bep/logg"
+ "github.com/bep/overlayfs"
+ "github.com/bep/simplecobra"
+ "github.com/gohugoio/hugo/common/hstrings"
+ "github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/loggers"
- "github.com/gohugoio/hugo/config"
-
- "github.com/spf13/cobra"
-
- "github.com/gohugoio/hugo/hugolib"
- "github.com/spf13/afero"
-
- "github.com/bep/debounce"
+ "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"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
- "github.com/gohugoio/hugo/langs"
+ "github.com/gohugoio/hugo/hugolib"
+ "github.com/gohugoio/hugo/identity"
+ "github.com/gohugoio/hugo/resources/kinds"
+ "github.com/spf13/afero"
+ "github.com/spf13/cobra"
)
-type commandeerHugoState struct {
- *deps.DepsCfg
- hugoSites *hugolib.HugoSites
- fsCreate sync.Once
- created chan struct{}
+var errHelp = errors.New("help requested")
+
+// Execute executes a command.
+func Execute(args []string) error {
+ // Default GOMAXPROCS to be CPU limit aware, still respecting GOMAXPROCS env.
+ maxprocs.Set()
+ x, err := newExec()
+ if err != nil {
+ return err
+ }
+ 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()
+ fmt.Println()
+ return nil
+ }
+ if simplecobra.IsCommandError(err) {
+ // Print the help, but also return the error to fail the command.
+ cd.CobraCommand.Help()
+ fmt.Println()
+ }
+ }
+ return err
+}
+
+type commonConfig struct {
+ mu *sync.Mutex
+ configs *allconfig.Configs
+ cfg config.Provider
+ fs *hugofs.Fs
}
-type commandeer struct {
- *commandeerHugoState
+type configKey struct {
+ counter int32
+ ignoreModulesDoesNotExists bool
+ skipNpmCheck bool
+}
- logger loggers.Logger
- serverConfig *config.Server
+// This is the root command.
+type rootCommand struct {
+ Printf func(format string, v ...any)
+ Println func(a ...any)
+ StdOut io.Writer
+ StdErr io.Writer
- // Loading state
- mustHaveConfigFile bool
- failOnInitErr bool
- running bool
+ logger loggers.Logger
- // Currently only set when in "fast render mode". But it seems to
- // be fast enough that we could maybe just add it for all server modes.
- changeDetector *fileChangeDetector
+ // The main cache busting key for the caches below.
+ configVersionID atomic.Int32
- // We need to reuse this on server rebuilds.
- destinationFs afero.Fs
+ // 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]
- h *hugoBuilderCommon
- ftch flagsToConfigHandler
+ // changesFromBuild received from Hugo in watch mode.
+ changesFromBuild chan []identity.Identity
- visitedURLs *types.EvictingStringQueue
+ commands []simplecobra.Commander
- cfgInit func(c *commandeer) error
+ // Flags
+ source string
+ buildWatch bool
+ environment string
- // We watch these for changes.
- configFiles []string
+ // Common build flags.
+ baseURL string
+ gc bool
+ poll string
+ forceSyncStatic bool
+ panicOnWarning bool
- // Used in cases where we get flooded with events in server mode.
- debounce func(f func())
+ // Profile flags (for debugging of performance problems)
+ cpuprofile string
+ memprofile string
+ mutexprofile string
+ traceprofile string
+ printm bool
- serverPorts []int
- languagesConfigured bool
- languages langs.Languages
- doLiveReload bool
- fastRenderMode bool
- showErrorInBrowser bool
- wasError bool
+ logLevel string
- configured bool
- paused bool
+ quiet bool
+ devMode bool // Hidden flag.
- fullRebuildSem *semaphore.Weighted
+ renderToMemory bool
- // Any error from the last build.
- buildErr error
+ cfgFile string
+ cfgDir string
}
-func newCommandeerHugoState() *commandeerHugoState {
- return &commandeerHugoState{
- created: make(chan struct{}),
+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 (c *commandeerHugoState) hugo() *hugolib.HugoSites {
- <-c.created
- return c.hugoSites
+func (r *rootCommand) Build(cd *simplecobra.Commandeer, bcfg hugolib.BuildCfg, cfg config.Provider) (*hugolib.HugoSites, error) {
+ h, err := r.Hugo(cfg)
+ if err != nil {
+ return nil, err
+ }
+ if err := h.Build(bcfg); err != nil {
+ return nil, err
+ }
+
+ return h, nil
}
-func (c *commandeer) errCount() int {
- return int(c.logger.LogCounters().ErrorCounter.Count())
+func (r *rootCommand) Commands() []simplecobra.Commander {
+ return r.commands
}
-func (c *commandeer) getErrorWithContext() interface{} {
- errCount := c.errCount()
+func (r *rootCommand) ConfigFromConfig(key configKey, oldConf *commonConfig) (*commonConfig, error) {
+ cc, _, err := r.commonConfigs.GetOrCreate(key, func(key configKey) (*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,
+ SkipNpmCheck: key.skipNpmCheck,
+ },
+ )
+ if err != nil {
+ return nil, err
+ }
- if errCount == 0 {
- return nil
- }
+ if !configs.Base.C.Clock.IsZero() {
+ // TODO(bep) find a better place for this.
+ htime.Clock = clocks.Start(configs.Base.C.Clock)
+ }
- m := make(map[string]interface{})
+ return &commonConfig{
+ mu: oldConf.mu,
+ configs: configs,
+ cfg: oldConf.cfg,
+ fs: fs,
+ }, nil
+ })
- m["Error"] = errors.New(removeErrorPrefixFromLog(c.logger.Errors()))
- m["Version"] = hugo.BuildVersionString()
+ return cc, err
+}
- fe := herrors.UnwrapErrorWithFileContext(c.buildErr)
- if fe != nil {
- m["File"] = fe
+func (r *rootCommand) ConfigFromProvider(key configKey, cfg config.Provider) (*commonConfig, error) {
+ if cfg == nil {
+ panic("cfg must be set")
}
+ cc, _, err := r.commonConfigs.GetOrCreate(key, func(key configKey) (*commonConfig, error) {
+ var dir string
+ if r.source != "" {
+ dir, _ = filepath.Abs(r.source)
+ } else {
+ dir, _ = os.Getwd()
+ }
- if c.h.verbose {
- var b bytes.Buffer
- herrors.FprintStackTraceFromErr(&b, c.buildErr)
- m["StackTrace"] = b.String()
- }
+ if cfg == nil {
+ cfg = config.New()
+ }
- return m
-}
+ if !cfg.IsSet("workingDir") {
+ cfg.Set("workingDir", dir)
+ } else {
+ if err := os.MkdirAll(cfg.GetString("workingDir"), 0o777); err != nil {
+ return nil, fmt.Errorf("failed to create workingDir: %w", err)
+ }
+ }
-func (c *commandeer) Set(key string, value interface{}) {
- if c.configured {
- panic("commandeer cannot be changed")
- }
- c.Cfg.Set(key, value)
-}
+ // 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,
+ SkipNpmCheck: key.skipNpmCheck,
+ },
+ )
+ if err != nil {
+ return nil, err
+ }
-func (c *commandeer) initFs(fs *hugofs.Fs) error {
- c.destinationFs = fs.Destination
- c.DepsCfg.Fs = fs
+ base := configs.Base
+
+ cfg.Set("publishDir", base.PublishDir)
+ cfg.Set("publishDirStatic", base.PublishDir)
+ cfg.Set("publishDirDynamic", base.PublishDir)
+
+ renderStaticToDisk := cfg.GetBool("renderStaticToDisk")
+
+ sourceFs := hugofs.Os
+ var destinationFs afero.Fs
+ if cfg.GetBool("renderToMemory") {
+ destinationFs = afero.NewMemMapFs()
+ if renderStaticToDisk {
+ // Hybrid, render dynamic content to Root.
+ cfg.Set("publishDirDynamic", "/")
+ } else {
+ // Rendering to memoryFS, publish to Root regardless of publishDir.
+ cfg.Set("publishDirDynamic", "/")
+ cfg.Set("publishDirStatic", "/")
+ }
+ } else {
+ destinationFs = hugofs.Os
+ }
- return nil
-}
+ fs := hugofs.NewFromSourceAndDestination(sourceFs, destinationFs, cfg)
+
+ if renderStaticToDisk {
+ dynamicFs := fs.PublishDir
+ publishDirStatic := cfg.GetString("publishDirStatic")
+ workingDir := cfg.GetString("workingDir")
+ absPublishDirStatic := paths.AbsPathify(workingDir, publishDirStatic)
+ staticFs := hugofs.NewBasePathFs(afero.NewOsFs(), absPublishDirStatic)
+
+ // Serve from both the static and dynamic fs,
+ // the first will take priority.
+ // THis is a read-only filesystem,
+ // we do all the writes to
+ // fs.Destination and fs.DestinationStatic.
+ fs.PublishDirServer = overlayfs.New(
+ overlayfs.Options{
+ Fss: []afero.Fs{
+ dynamicFs,
+ staticFs,
+ },
+ },
+ )
+ fs.PublishDirStatic = staticFs
-func newCommandeer(mustHaveConfigFile, failOnInitErr, running bool, h *hugoBuilderCommon, f flagsToConfigHandler, cfgInit func(c *commandeer) error, subCmdVs ...*cobra.Command) (*commandeer, error) {
- var rebuildDebouncer func(f func())
- if running {
- // The time value used is tested with mass content replacements in a fairly big Hugo site.
- // It is better to wait for some seconds in those cases rather than get flooded
- // with rebuilds.
- rebuildDebouncer = debounce.New(4 * time.Second)
- }
+ }
- out := ioutil.Discard
- if !h.quiet {
- out = os.Stdout
- }
+ if !base.C.Clock.IsZero() {
+ // TODO(bep) find a better place for this.
+ htime.Clock = clocks.Start(configs.Base.C.Clock)
+ }
- c := &commandeer{
- h: h,
- ftch: f,
- commandeerHugoState: newCommandeerHugoState(),
- cfgInit: cfgInit,
- visitedURLs: types.NewEvictingStringQueue(10),
- debounce: rebuildDebouncer,
- fullRebuildSem: semaphore.NewWeighted(1),
-
- // Init state
- mustHaveConfigFile: mustHaveConfigFile,
- failOnInitErr: failOnInitErr,
- running: running,
-
- // This will be replaced later, but we need something to log to before the configuration is read.
- logger: loggers.NewLogger(jww.LevelWarn, jww.LevelError, out, ioutil.Discard, running),
- }
+ if base.PrintPathWarnings {
+ // Note that we only care about the "dynamic creates" here,
+ // so skip the static fs.
+ fs.PublishDir = hugofs.NewCreateCountingFs(fs.PublishDir)
+ }
- return c, c.loadConfig()
-}
+ commonConfig := &commonConfig{
+ mu: &sync.Mutex{},
+ configs: configs,
+ cfg: cfg,
+ fs: fs,
+ }
-type fileChangeDetector struct {
- sync.Mutex
- current map[string]string
- prev map[string]string
+ return commonConfig, nil
+ })
- irrelevantRe *regexp.Regexp
+ return cc, err
}
-func (f *fileChangeDetector) OnFileClose(name, md5sum string) {
- f.Lock()
- defer f.Unlock()
- f.current[name] = md5sum
+func (r *rootCommand) HugFromConfig(conf *commonConfig) (*hugolib.HugoSites, error) {
+ if conf == nil {
+ return nil, fmt.Errorf("conf must be set")
+ }
+ k := configKey{counter: r.configVersionID.Load()}
+ h, _, err := r.hugoSites.GetOrCreate(k, func(key configKey) (*hugolib.HugoSites, error) {
+ depsCfg := r.newDepsConfig(conf)
+ return hugolib.NewHugoSites(depsCfg)
+ })
+ return h, err
}
-func (f *fileChangeDetector) changed() []string {
- if f == nil {
- return nil
- }
- f.Lock()
- defer f.Unlock()
- var c []string
- for k, v := range f.current {
- vv, found := f.prev[k]
- if !found || v != vv {
- c = append(c, k)
+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) {
+ conf, err := r.ConfigFromProvider(key, cfg)
+ if err != nil {
+ return nil, err
}
- }
+ depsCfg := r.newDepsConfig(conf)
+ return hugolib.NewHugoSites(depsCfg)
+ })
+ return h, err
+}
- return f.filterIrrelevant(c)
+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}
}
-func (f *fileChangeDetector) filterIrrelevant(in []string) []string {
- var filtered []string
- for _, v := range in {
- if !f.irrelevantRe.MatchString(v) {
- filtered = append(filtered, v)
- }
- }
- return filtered
+func (r *rootCommand) Name() string {
+ return "hugo"
}
-func (f *fileChangeDetector) PrepareNew() {
- if f == nil {
- return
- }
+func (r *rootCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
+ b := newHugoBuilder(r, nil)
- f.Lock()
- defer f.Unlock()
+ if !r.buildWatch {
+ defer b.postBuild("Total", time.Now())
+ }
- if f.current == nil {
- f.current = make(map[string]string)
- f.prev = make(map[string]string)
- return
+ if err := b.loadConfig(cd, false); err != nil {
+ return err
}
- f.prev = make(map[string]string)
- for k, v := range f.current {
- f.prev[k] = v
+ err := func() error {
+ if r.buildWatch {
+ defer r.timeTrack(time.Now(), "Built")
+ }
+ err := b.build()
+ if err != nil {
+ return err
+ }
+ return nil
+ }()
+ if err != nil {
+ return err
}
- f.current = make(map[string]string)
-}
-func (c *commandeer) loadConfig() error {
- if c.DepsCfg == nil {
- c.DepsCfg = &deps.DepsCfg{}
+ if !r.buildWatch {
+ // Done.
+ return nil
}
- if c.logger != nil {
- // Truncate the error log if this is a reload.
- c.logger.Reset()
+ watchDirs, err := b.getDirList()
+ if err != nil {
+ return err
}
- cfg := c.DepsCfg
- c.configured = false
- cfg.Running = c.running
+ watchGroups := helpers.ExtractAndGroupRootPaths(watchDirs)
- var dir string
- if c.h.source != "" {
- dir, _ = filepath.Abs(c.h.source)
- } else {
- dir, _ = os.Getwd()
+ r.Printf("Watching for changes in %s\n", strings.Join(watchGroups, ", "))
+ watcher, err := b.newWatcher(r.poll, watchDirs...)
+ if err != nil {
+ return err
}
- var sourceFs afero.Fs = hugofs.Os
- if c.DepsCfg.Fs != nil {
- sourceFs = c.DepsCfg.Fs.Source
- }
+ defer watcher.Close()
- environment := c.h.getEnvironment(c.running)
+ r.Println("Press Ctrl+C to stop")
- doWithConfig := func(cfg config.Provider) error {
- if c.ftch != nil {
- c.ftch.flagsToConfig(cfg)
- }
+ sigs := make(chan os.Signal, 1)
+ signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
- cfg.Set("workingDir", dir)
- cfg.Set("environment", environment)
- return nil
+ <-sigs
+
+ return nil
+}
+
+func (r *rootCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
+ r.StdOut = os.Stdout
+ r.StdErr = os.Stderr
+ if r.quiet {
+ r.StdOut = io.Discard
+ r.StdErr = io.Discard
}
+ // Used by mkcert (server).
+ log.SetOutput(r.StdOut)
- cfgSetAndInit := func(cfg config.Provider) error {
- c.Cfg = cfg
- if c.cfgInit == nil {
- return nil
+ r.Printf = func(format string, v ...any) {
+ if !r.quiet {
+ fmt.Fprintf(r.StdOut, format, v...)
}
- err := c.cfgInit(c)
- return err
}
-
- configPath := c.h.source
- if configPath == "" {
- configPath = dir
+ r.Println = func(a ...any) {
+ if !r.quiet {
+ fmt.Fprintln(r.StdOut, a...)
+ }
}
- config, configFiles, err := hugolib.LoadConfig(
- hugolib.ConfigSourceDescriptor{
- Fs: sourceFs,
- Logger: c.logger,
- Path: configPath,
- WorkingDir: dir,
- Filename: c.h.cfgFile,
- AbsConfigDir: c.h.getConfigDir(dir),
- Environment: environment,
- },
- cfgSetAndInit,
- doWithConfig)
-
+ _, running := runner.Command.(*serverCommand)
+ var err error
+ r.logger, err = r.createLogger(running)
if err != nil {
- // We should improve the error handling here,
- // but with hugo mod init and similar there is a chicken and egg situation
- // with modules already configured in config.toml, so ignore those errors.
- if c.mustHaveConfigFile || (c.failOnInitErr && !moduleNotFoundRe.MatchString(err.Error())) {
- return err
- } else {
- // Just make it a warning.
- c.logger.Warnln(err)
- }
- } else if c.mustHaveConfigFile && len(configFiles) == 0 {
- return hugolib.ErrNoConfigFile
+ return err
}
+ // Set up the global logger early to allow info deprecations during config load.
+ loggers.SetGlobalLogger(r.logger)
+
+ r.changesFromBuild = make(chan []identity.Identity, 10)
+
+ r.commonConfigs = lazycache.New(lazycache.Options[configKey, *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]{
+ MaxEntries: 1,
+ OnEvict: func(key configKey, value *hugolib.HugoSites) {
+ value.Close()
+ runtime.GC()
+ },
+ })
- c.configFiles = configFiles
-
- if l, ok := c.Cfg.Get("languagesSorted").(langs.Languages); ok {
- c.languagesConfigured = true
- c.languages = l
- }
+ return nil
+}
- // Set some commonly used flags
- c.doLiveReload = c.running && !c.Cfg.GetBool("disableLiveReload")
- c.fastRenderMode = c.doLiveReload && !c.Cfg.GetBool("disableFastRender")
- c.showErrorInBrowser = c.doLiveReload && !c.Cfg.GetBool("disableBrowserError")
+func (r *rootCommand) createLogger(running bool) (loggers.Logger, error) {
+ level := logg.LevelWarn
- // This is potentially double work, but we need to do this one more time now
- // that all the languages have been configured.
- if c.cfgInit != nil {
- if err := c.cfgInit(c); err != nil {
- return err
+ if r.devMode {
+ level = logg.LevelTrace
+ } else {
+ if r.logLevel != "" {
+ switch strings.ToLower(r.logLevel) {
+ case "debug":
+ level = logg.LevelDebug
+ case "info":
+ level = logg.LevelInfo
+ case "warn", "warning":
+ level = logg.LevelWarn
+ case "error":
+ level = logg.LevelError
+ default:
+ return nil, fmt.Errorf("invalid log level: %q, must be one of debug, warn, info or error", r.logLevel)
+ }
}
}
- logger, err := c.createLogger(config)
- if err != nil {
- return err
+ var logHookLast func(e *logg.Entry) error
+ if r.panicOnWarning {
+ logHookLast = loggers.PanicOnWarningHook
}
- cfg.Logger = logger
- c.logger = logger
- c.serverConfig, err = hconfig.DecodeServer(cfg.Cfg)
- if err != nil {
- return err
+ optsLogger := loggers.Options{
+ DistinctLevel: logg.LevelWarn,
+ Level: level,
+ StdOut: r.StdOut,
+ StdErr: r.StdErr,
+ StoreErrors: running,
+ HandlerPost: logHookLast,
}
- createMemFs := config.GetBool("renderToMemory")
+ return loggers.New(optsLogger), nil
+}
- if createMemFs {
- // Rendering to memoryFS, publish to Root regardless of publishDir.
- config.Set("publishDir", "/")
+func (r *rootCommand) resetLogs() {
+ r.logger.Reset()
+ loggers.Log().Reset()
+}
+
+// IsTestRun reports whether the command is running as a test.
+func (r *rootCommand) IsTestRun() bool {
+ return os.Getenv("HUGO_TESTRUN") != ""
+}
+
+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 project"
+ cmd.Long = `COMMAND_NAME is the main command, used to build your Hugo project.
+
+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")
+ cmd.PersistentFlags().StringP("destination", "d", "", "filesystem path to write files to")
+ _ = cmd.MarkFlagDirname("destination")
+
+ cmd.PersistentFlags().StringVarP(&r.environment, "environment", "e", "", "build environment")
+ _ = cmd.RegisterFlagCompletionFunc("environment", cobra.NoFileCompletions)
+ cmd.PersistentFlags().StringP("themesDir", "", "", "filesystem path to themes directory")
+ _ = cmd.MarkFlagDirname("themesDir")
+ cmd.PersistentFlags().StringP("ignoreVendorPaths", "", "", "ignores any _vendor for module paths matching the given Glob pattern")
+ cmd.PersistentFlags().BoolP("noBuildLock", "", false, "don't create .hugo_build.lock file")
+ _ = cmd.RegisterFlagCompletionFunc("ignoreVendorPaths", cobra.NoFileCompletions)
+ cmd.PersistentFlags().String("clock", "", "set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00")
+ _ = cmd.RegisterFlagCompletionFunc("clock", cobra.NoFileCompletions)
+
+ cmd.PersistentFlags().StringVar(&r.cfgFile, "config", "", "config file (default is hugo.yaml|json|toml)")
+ _ = cmd.MarkFlagFilename("config", config.ValidConfigFileExtensions...)
+ cmd.PersistentFlags().StringVar(&r.cfgDir, "configDir", "config", "config dir")
+ _ = cmd.MarkFlagDirname("configDir")
+ cmd.PersistentFlags().BoolVar(&r.quiet, "quiet", false, "build in quiet mode")
+ cmd.PersistentFlags().BoolVarP(&r.renderToMemory, "renderToMemory", "M", false, "render to memory (mostly useful when running the server)")
+
+ cmd.PersistentFlags().BoolVarP(&r.devMode, "devMode", "", false, "only used for internal testing, flag hidden.")
+ cmd.PersistentFlags().StringVar(&r.logLevel, "logLevel", "", "log level (debug|info|warn|error)")
+ _ = cmd.RegisterFlagCompletionFunc("logLevel", cobra.FixedCompletions([]string{"debug", "info", "warn", "error"}, cobra.ShellCompDirectiveNoFileComp))
+ cmd.Flags().BoolVarP(&r.buildWatch, "watch", "w", false, "watch filesystem for changes and recreate as needed")
+
+ cmd.PersistentFlags().MarkHidden("devMode")
+
+ // Configure local flags
+ applyLocalFlagsBuild(cmd, r)
- c.fsCreate.Do(func() {
- fs := hugofs.NewFrom(sourceFs, config)
+ return nil
+}
- if c.destinationFs != nil {
- // Need to reuse the destination on server rebuilds.
- fs.Destination = c.destinationFs
- } else if createMemFs {
- // Hugo writes the output to memory instead of the disk.
- fs.Destination = new(afero.MemMapFs)
- }
+// A sub set of the complete build flags. These flags are used by new and mod.
+func applyLocalFlagsBuildConfig(cmd *cobra.Command, r *rootCommand) {
+ cmd.Flags().StringSliceP("theme", "t", []string{}, "themes to use (located in /themes/THEMENAME/)")
+ _ = cmd.MarkFlagDirname("theme")
+ cmd.Flags().StringVarP(&r.baseURL, "baseURL", "b", "", "hostname (and path) to the root, e.g. https://spf13.com/")
+ cmd.Flags().StringP("cacheDir", "", "", "filesystem path to cache directory")
+ _ = cmd.MarkFlagDirname("cacheDir")
+ cmd.Flags().StringP("contentDir", "c", "", "filesystem path to content directory")
+ cmd.Flags().StringSliceP("renderSegments", "", []string{}, "named segments to render (configured in the segments config)")
+}
- if c.fastRenderMode {
- // For now, fast render mode only. It should, however, be fast enough
- // for the full variant, too.
- changeDetector := &fileChangeDetector{
- // We use this detector to decide to do a Hot reload of a single path or not.
- // We need to filter out source maps and possibly some other to be able
- // to make that decision.
- irrelevantRe: regexp.MustCompile(`\.map$`),
- }
+// Flags needed to do a build (used by hugo and hugo server commands)
+func applyLocalFlagsBuild(cmd *cobra.Command, r *rootCommand) {
+ applyLocalFlagsBuildConfig(cmd, r)
+ cmd.Flags().Bool("cleanDestinationDir", false, "remove files from destination not found in static directories")
+ cmd.Flags().BoolP("buildDrafts", "D", false, "include content marked as draft")
+ cmd.Flags().BoolP("buildFuture", "F", false, "include content with publishdate in the future")
+ cmd.Flags().BoolP("buildExpired", "E", false, "include expired content")
+ cmd.Flags().BoolP("ignoreCache", "", false, "ignores the cache directory")
+ cmd.Flags().Bool("enableGitInfo", false, "add Git revision, date, author, and CODEOWNERS info to the pages")
+ cmd.Flags().StringP("layoutDir", "l", "", "filesystem path to layout directory")
+ _ = cmd.MarkFlagDirname("layoutDir")
+ cmd.Flags().BoolVar(&r.gc, "gc", false, "enable to run some cleanup tasks (remove unused cache files) after the build")
+ cmd.Flags().StringVar(&r.poll, "poll", "", "set this to a poll interval, e.g --poll 700ms, to use a poll based approach to watch for file system changes")
+ _ = cmd.RegisterFlagCompletionFunc("poll", cobra.NoFileCompletions)
+ cmd.Flags().BoolVar(&r.panicOnWarning, "panicOnWarning", false, "panic on first WARNING log")
+ cmd.Flags().Bool("templateMetrics", false, "display metrics about template executions")
+ cmd.Flags().Bool("templateMetricsHints", false, "calculate some improvement hints when combined with --templateMetrics")
+ cmd.Flags().BoolVar(&r.forceSyncStatic, "forceSyncStatic", false, "copy all files when static is changed.")
+ cmd.Flags().BoolP("noTimes", "", false, "don't sync modification time of files")
+ cmd.Flags().BoolP("noChmod", "", false, "don't sync permission mode of files")
+ cmd.Flags().BoolP("printI18nWarnings", "", false, "print missing translations")
+ cmd.Flags().BoolP("printPathWarnings", "", false, "print warnings on duplicate target paths etc.")
+ cmd.Flags().BoolP("printUnusedTemplates", "", false, "print warnings on unused templates.")
+ cmd.Flags().StringVarP(&r.cpuprofile, "profile-cpu", "", "", "write cpu profile to `file`")
+ cmd.Flags().StringVarP(&r.memprofile, "profile-mem", "", "", "write memory profile to `file`")
+ cmd.Flags().BoolVarP(&r.printm, "printMemoryUsage", "", false, "print memory usage to screen at intervals")
+ cmd.Flags().StringVarP(&r.mutexprofile, "profile-mutex", "", "", "write Mutex profile to `file`")
+ cmd.Flags().StringVarP(&r.traceprofile, "trace", "", "", "write trace to `file` (not useful in general)")
+
+ // Hide these for now.
+ cmd.Flags().MarkHidden("profile-cpu")
+ cmd.Flags().MarkHidden("profile-mem")
+ cmd.Flags().MarkHidden("profile-mutex")
+
+ cmd.Flags().StringSlice("disableKinds", []string{}, "disable different kind of pages (home, RSS etc.)")
+ _ = cmd.RegisterFlagCompletionFunc("disableKinds", cobra.FixedCompletions(kinds.AllKinds, cobra.ShellCompDirectiveNoFileComp))
+ cmd.Flags().Bool("minify", false, "minify any supported output format (HTML, XML etc.)")
+}
- changeDetector.PrepareNew()
- fs.Destination = hugofs.NewHashingFs(fs.Destination, changeDetector)
- c.changeDetector = changeDetector
- }
+func (r *rootCommand) timeTrack(start time.Time, name string) {
+ elapsed := time.Since(start)
+ r.Printf("%s in %v ms\n", name, int(1000*elapsed.Seconds()))
+}
- if c.Cfg.GetBool("logPathWarnings") {
- fs.Destination = hugofs.NewCreateCountingFs(fs.Destination)
- }
+type simpleCommand struct {
+ use string
+ name string
+ short string
+ long string
+ aliases []string
+ run func(ctx context.Context, cd *simplecobra.Commandeer, rootCmd *rootCommand, args []string) error
+ withc func(cmd *cobra.Command, r *rootCommand)
+ initc func(cd *simplecobra.Commandeer) error
- // To debug hard-to-find path issues.
- // fs.Destination = hugofs.NewStacktracerFs(fs.Destination, `fr/fr`)
+ commands []simplecobra.Commander
- err = c.initFs(fs)
- if err != nil {
- close(c.created)
- return
- }
+ rootCmd *rootCommand
+}
- var h *hugolib.HugoSites
+func (c *simpleCommand) Commands() []simplecobra.Commander {
+ return c.commands
+}
- var createErr error
- h, createErr = hugolib.NewHugoSites(*c.DepsCfg)
- if h == nil || c.failOnInitErr {
- err = createErr
- }
- c.hugoSites = h
- close(c.created)
- })
+func (c *simpleCommand) Name() string {
+ return c.name
+}
- if err != nil {
- return err
+func (c *simpleCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
+ if c.run == nil {
+ return nil
}
+ return c.run(ctx, cd, c.rootCmd, args)
+}
- cacheDir, err := helpers.GetCacheDir(sourceFs, config)
- if err != nil {
- return err
+func (c *simpleCommand) Init(cd *simplecobra.Commandeer) error {
+ c.rootCmd = cd.Root.Command.(*rootCommand)
+ cmd := cd.CobraCommand
+ cmd.Short = c.short
+ cmd.Long = c.long
+ cmd.Aliases = c.aliases
+ if c.use != "" {
+ cmd.Use = c.use
}
- config.Set("cacheDir", cacheDir)
+ if c.withc != nil {
+ c.withc(cmd, c.rootCmd)
+ }
+ return nil
+}
+func (c *simpleCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
+ if c.initc != nil {
+ return c.initc(cd)
+ }
return nil
}
+
+func mapLegacyArgs(args []string) []string {
+ if len(args) > 1 && args[0] == "new" && !hstrings.EqualAny(args[1], "project", "site", "theme", "content") {
+ // Insert "content" as the second argument
+ args = append(args[:1], append([]string{"content"}, args[1:]...)...)
+ }
+ return args
+}
diff --git a/commands/commands.go b/commands/commands.go
index 4153c3cdf2c..10ab106e277 100644
--- a/commands/commands.go
+++ b/commands/commands.go
@@ -1,4 +1,4 @@
-// Copyright 2019 The Hugo Authors. All rights reserved.
+// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,330 +14,60 @@
package commands
import (
- "fmt"
- "os"
- "time"
+ "context"
- "github.com/gohugoio/hugo/hugolib/paths"
-
- "github.com/gohugoio/hugo/common/hugo"
- "github.com/gohugoio/hugo/common/loggers"
- "github.com/gohugoio/hugo/config"
- "github.com/gohugoio/hugo/helpers"
- "github.com/spf13/cobra"
+ "github.com/bep/simplecobra"
)
-type commandsBuilder struct {
- hugoBuilderCommon
-
- commands []cmder
-}
-
-func newCommandsBuilder() *commandsBuilder {
- return &commandsBuilder{}
-}
-
-func (b *commandsBuilder) addCommands(commands ...cmder) *commandsBuilder {
- b.commands = append(b.commands, commands...)
- return b
-}
-
-func (b *commandsBuilder) addAll() *commandsBuilder {
- b.addCommands(
- b.newServerCmd(),
- newVersionCmd(),
- newEnvCmd(),
- b.newConfigCmd(),
- newCheckCmd(),
- b.newDeployCmd(),
- b.newConvertCmd(),
- b.newNewCmd(),
- b.newListCmd(),
- newImportCmd(),
- newGenCmd(),
- createReleaser(),
- b.newModCmd(),
- )
-
- return b
-}
-
-func (b *commandsBuilder) build() *hugoCmd {
- h := b.newHugoCmd()
- addCommands(h.getCommand(), b.commands...)
- return h
-}
-
-func addCommands(root *cobra.Command, commands ...cmder) {
- for _, command := range commands {
- cmd := command.getCommand()
- if cmd == nil {
- continue
- }
- root.AddCommand(cmd)
+// newExec wires up all of Hugo's CLI.
+func newExec() (*simplecobra.Exec, error) {
+ rootCmd := &rootCommand{
+ commands: []simplecobra.Commander{
+ newHugoBuildCmd(),
+ newVersionCmd(),
+ newEnvCommand(),
+ newServerCommand(),
+ newDeployCommand(),
+ newConfigCommand(),
+ newNewCommand(),
+ newConvertCommand(),
+ newImportCommand(),
+ newListCommand(),
+ newModCommands(),
+ newGenCommand(),
+ newReleaseCommand(),
+ },
}
-}
-
-type baseCmd struct {
- cmd *cobra.Command
-}
-
-var _ commandsBuilderGetter = (*baseBuilderCmd)(nil)
-
-// Used in tests.
-type commandsBuilderGetter interface {
- getCommandsBuilder() *commandsBuilder
-}
-
-type baseBuilderCmd struct {
- *baseCmd
- *commandsBuilder
-}
-
-func (b *baseBuilderCmd) getCommandsBuilder() *commandsBuilder {
- return b.commandsBuilder
-}
-func (c *baseCmd) getCommand() *cobra.Command {
- return c.cmd
+ return simplecobra.New(rootCmd)
}
-func newBaseCmd(cmd *cobra.Command) *baseCmd {
- return &baseCmd{cmd: cmd}
+func newHugoBuildCmd() simplecobra.Commander {
+ return &hugoBuildCommand{}
}
-func (b *commandsBuilder) newBuilderCmd(cmd *cobra.Command) *baseBuilderCmd {
- bcmd := &baseBuilderCmd{commandsBuilder: b, baseCmd: &baseCmd{cmd: cmd}}
- bcmd.hugoBuilderCommon.handleFlags(cmd)
- return bcmd
+// hugoBuildCommand just delegates to the rootCommand.
+type hugoBuildCommand struct {
+ rootCmd *rootCommand
}
-func (b *commandsBuilder) newBuilderBasicCmd(cmd *cobra.Command) *baseBuilderCmd {
- bcmd := &baseBuilderCmd{commandsBuilder: b, baseCmd: &baseCmd{cmd: cmd}}
- bcmd.hugoBuilderCommon.handleCommonBuilderFlags(cmd)
- return bcmd
-}
-
-func (c *baseCmd) flagsToConfig(cfg config.Provider) {
- initializeFlags(c.cmd, cfg)
-}
-
-type hugoCmd struct {
- *baseBuilderCmd
-
- // Need to get the sites once built.
- c *commandeer
-}
-
-var _ cmder = (*nilCommand)(nil)
-
-type nilCommand struct {
-}
-
-func (c *nilCommand) getCommand() *cobra.Command {
+func (c *hugoBuildCommand) Commands() []simplecobra.Commander {
return nil
}
-func (c *nilCommand) flagsToConfig(cfg config.Provider) {
-}
-
-func (b *commandsBuilder) newHugoCmd() *hugoCmd {
- cc := &hugoCmd{}
-
- cc.baseBuilderCmd = b.newBuilderCmd(&cobra.Command{
- Use: "hugo",
- Short: "hugo builds your site",
- 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 http://gohugo.io/.`,
- RunE: func(cmd *cobra.Command, args []string) error {
- defer cc.timeTrack(time.Now(), "Total")
- cfgInit := func(c *commandeer) error {
- if cc.buildWatch {
- c.Set("disableLiveReload", true)
- }
- return nil
- }
-
- // prevent cobra printing error so it can be handled here (before the timeTrack prints)
- cmd.SilenceErrors = true
-
- c, err := initializeConfig(true, true, cc.buildWatch, &cc.hugoBuilderCommon, cc, cfgInit)
- if err != nil {
- cmd.PrintErrln("Error:", err.Error())
- return err
- }
- cc.c = c
-
- err = c.build()
- if err != nil {
- cmd.PrintErrln("Error:", err.Error())
- }
- return err
- },
- })
-
- cc.cmd.PersistentFlags().StringVar(&cc.cfgFile, "config", "", "config file (default is path/config.yaml|json|toml)")
- cc.cmd.PersistentFlags().StringVar(&cc.cfgDir, "configDir", "config", "config dir")
- cc.cmd.PersistentFlags().BoolVar(&cc.quiet, "quiet", false, "build in quiet mode")
-
- // Set bash-completion
- _ = cc.cmd.PersistentFlags().SetAnnotation("config", cobra.BashCompFilenameExt, config.ValidConfigFileExtensions)
-
- cc.cmd.PersistentFlags().BoolVarP(&cc.verbose, "verbose", "v", false, "verbose output")
- cc.cmd.PersistentFlags().BoolVarP(&cc.debug, "debug", "", false, "debug output")
- cc.cmd.PersistentFlags().BoolVar(&cc.logging, "log", false, "enable Logging")
- cc.cmd.PersistentFlags().StringVar(&cc.logFile, "logFile", "", "log File path (if set, logging enabled automatically)")
- cc.cmd.PersistentFlags().BoolVar(&cc.verboseLog, "verboseLog", false, "verbose logging")
-
- cc.cmd.Flags().BoolVarP(&cc.buildWatch, "watch", "w", false, "watch filesystem for changes and recreate as needed")
-
- cc.cmd.Flags().Bool("renderToMemory", false, "render to memory (only useful for benchmark testing)")
-
- // Set bash-completion
- _ = cc.cmd.PersistentFlags().SetAnnotation("logFile", cobra.BashCompFilenameExt, []string{})
-
- cc.cmd.SetGlobalNormalizationFunc(helpers.NormalizeHugoFlags)
- cc.cmd.SilenceUsage = true
-
- return cc
-}
-
-type hugoBuilderCommon struct {
- source string
- baseURL string
- environment string
-
- buildWatch bool
- poll string
-
- gc bool
-
- // Profile flags (for debugging of performance problems)
- cpuprofile string
- memprofile string
- mutexprofile string
- traceprofile string
- printm bool
-
- // TODO(bep) var vs string
- logging bool
- verbose bool
- verboseLog bool
- debug bool
- quiet bool
-
- cfgFile string
- cfgDir string
- logFile string
-}
-
-func (cc *hugoBuilderCommon) timeTrack(start time.Time, name string) {
- if cc.quiet {
- return
- }
- elapsed := time.Since(start)
- fmt.Printf("%s in %v ms\n", name, int(1000*elapsed.Seconds()))
-}
-
-func (cc *hugoBuilderCommon) getConfigDir(baseDir string) string {
- if cc.cfgDir != "" {
- return paths.AbsPathify(baseDir, cc.cfgDir)
- }
-
- if v, found := os.LookupEnv("HUGO_CONFIGDIR"); found {
- return paths.AbsPathify(baseDir, v)
- }
-
- return paths.AbsPathify(baseDir, "config")
+func (c *hugoBuildCommand) Name() string {
+ return "build"
}
-func (cc *hugoBuilderCommon) getEnvironment(isServer bool) string {
- if cc.environment != "" {
- return cc.environment
- }
-
- if v, found := os.LookupEnv("HUGO_ENVIRONMENT"); found {
- return v
- }
-
- // Used by Netlify and Forestry
- if v, found := os.LookupEnv("HUGO_ENV"); found {
- return v
- }
-
- if isServer {
- return hugo.EnvironmentDevelopment
- }
-
- return hugo.EnvironmentProduction
-}
-
-func (cc *hugoBuilderCommon) handleCommonBuilderFlags(cmd *cobra.Command) {
- cmd.PersistentFlags().StringVarP(&cc.source, "source", "s", "", "filesystem path to read files relative from")
- cmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{})
- cmd.PersistentFlags().StringVarP(&cc.environment, "environment", "e", "", "build environment")
- cmd.PersistentFlags().StringP("themesDir", "", "", "filesystem path to themes directory")
- cmd.PersistentFlags().BoolP("ignoreVendor", "", false, "ignores any _vendor directory")
- cmd.PersistentFlags().StringP("ignoreVendorPaths", "", "", "ignores any _vendor for module paths matching the given Glob pattern")
+func (c *hugoBuildCommand) Init(cd *simplecobra.Commandeer) error {
+ c.rootCmd = cd.Root.Command.(*rootCommand)
+ return c.rootCmd.initRootCommand("build", cd)
}
-func (cc *hugoBuilderCommon) handleFlags(cmd *cobra.Command) {
- cc.handleCommonBuilderFlags(cmd)
- cmd.Flags().Bool("cleanDestinationDir", false, "remove files from destination not found in static directories")
- cmd.Flags().BoolP("buildDrafts", "D", false, "include content marked as draft")
- cmd.Flags().BoolP("buildFuture", "F", false, "include content with publishdate in the future")
- cmd.Flags().BoolP("buildExpired", "E", false, "include expired content")
- cmd.Flags().StringP("contentDir", "c", "", "filesystem path to content directory")
- cmd.Flags().StringP("layoutDir", "l", "", "filesystem path to layout directory")
- cmd.Flags().StringP("cacheDir", "", "", "filesystem path to cache directory. Defaults: $TMPDIR/hugo_cache/")
- cmd.Flags().BoolP("ignoreCache", "", false, "ignores the cache directory")
- cmd.Flags().StringP("destination", "d", "", "filesystem path to write files to")
- cmd.Flags().StringSliceP("theme", "t", []string{}, "themes to use (located in /themes/THEMENAME/)")
- cmd.Flags().StringVarP(&cc.baseURL, "baseURL", "b", "", "hostname (and path) to the root, e.g. http://spf13.com/")
- cmd.Flags().Bool("enableGitInfo", false, "add Git revision, date and author info to the pages")
- cmd.Flags().BoolVar(&cc.gc, "gc", false, "enable to run some cleanup tasks (remove unused cache files) after the build")
- cmd.Flags().StringVar(&cc.poll, "poll", "", "set this to a poll interval, e.g --poll 700ms, to use a poll based approach to watch for file system changes")
-
- cmd.Flags().Bool("templateMetrics", false, "display metrics about template executions")
- cmd.Flags().Bool("templateMetricsHints", false, "calculate some improvement hints when combined with --templateMetrics")
- cmd.Flags().BoolP("forceSyncStatic", "", false, "copy all files when static is changed.")
- cmd.Flags().BoolP("noTimes", "", false, "don't sync modification time of files")
- cmd.Flags().BoolP("noChmod", "", false, "don't sync permission mode of files")
- cmd.Flags().BoolP("i18n-warnings", "", false, "print missing translations")
- cmd.Flags().BoolP("path-warnings", "", false, "print warnings on duplicate target paths etc.")
- cmd.Flags().StringVarP(&cc.cpuprofile, "profile-cpu", "", "", "write cpu profile to `file`")
- cmd.Flags().StringVarP(&cc.memprofile, "profile-mem", "", "", "write memory profile to `file`")
- cmd.Flags().BoolVarP(&cc.printm, "print-mem", "", false, "print memory usage to screen at intervals")
- cmd.Flags().StringVarP(&cc.mutexprofile, "profile-mutex", "", "", "write Mutex profile to `file`")
- cmd.Flags().StringVarP(&cc.traceprofile, "trace", "", "", "write trace to `file` (not useful in general)")
-
- // Hide these for now.
- cmd.Flags().MarkHidden("profile-cpu")
- cmd.Flags().MarkHidden("profile-mem")
- cmd.Flags().MarkHidden("profile-mutex")
-
- cmd.Flags().StringSlice("disableKinds", []string{}, "disable different kind of pages (home, RSS etc.)")
-
- cmd.Flags().Bool("minify", false, "minify any supported output format (HTML, XML etc.)")
-
- // Set bash-completion.
- // Each flag must first be defined before using the SetAnnotation() call.
- _ = cmd.Flags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{})
- _ = cmd.Flags().SetAnnotation("cacheDir", cobra.BashCompSubdirsInDir, []string{})
- _ = cmd.Flags().SetAnnotation("destination", cobra.BashCompSubdirsInDir, []string{})
- _ = cmd.Flags().SetAnnotation("theme", cobra.BashCompSubdirsInDir, []string{"themes"})
+func (c *hugoBuildCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
+ return c.rootCmd.PreRun(cd, runner)
}
-func checkErr(logger loggers.Logger, err error, s ...string) {
- if err == nil {
- return
- }
- for _, message := range s {
- logger.Errorln(message)
- }
- logger.Errorln(err)
+func (c *hugoBuildCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
+ return c.rootCmd.Run(ctx, cd, args)
}
diff --git a/commands/commands_test.go b/commands/commands_test.go
deleted file mode 100644
index ef61dc7a5e7..00000000000
--- a/commands/commands_test.go
+++ /dev/null
@@ -1,410 +0,0 @@
-// Copyright 2019 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 (
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
- "testing"
-
- "github.com/gohugoio/hugo/config"
-
- "github.com/gohugoio/hugo/htesting"
-
- "github.com/spf13/afero"
-
- "github.com/gohugoio/hugo/hugofs"
-
- "github.com/gohugoio/hugo/common/types"
-
- "github.com/spf13/cobra"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestExecute(t *testing.T) {
- c := qt.New(t)
-
- createSite := func(c *qt.C) (string, func()) {
- dir, clean, err := createSimpleTestSite(t, testSiteConfig{})
- c.Assert(err, qt.IsNil)
- return dir, clean
- }
-
- c.Run("hugo", func(c *qt.C) {
- dir, clean := createSite(c)
- defer clean()
- resp := Execute([]string{"-s=" + dir})
- c.Assert(resp.Err, qt.IsNil)
- result := resp.Result
- c.Assert(len(result.Sites) == 1, qt.Equals, true)
- c.Assert(len(result.Sites[0].RegularPages()) == 1, qt.Equals, true)
- c.Assert(result.Sites[0].Info.Params()["myparam"], qt.Equals, "paramproduction")
- })
-
- c.Run("hugo, set environment", func(c *qt.C) {
- dir, clean := createSite(c)
- defer clean()
- resp := Execute([]string{"-s=" + dir, "-e=staging"})
- c.Assert(resp.Err, qt.IsNil)
- result := resp.Result
- c.Assert(result.Sites[0].Info.Params()["myparam"], qt.Equals, "paramstaging")
- })
-
- c.Run("convert toJSON", func(c *qt.C) {
- dir, clean := createSite(c)
- output := filepath.Join(dir, "myjson")
- defer clean()
- resp := Execute([]string{"convert", "toJSON", "-s=" + dir, "-e=staging", "-o=" + output})
- c.Assert(resp.Err, qt.IsNil)
- converted := readFileFrom(c, filepath.Join(output, "content", "p1.md"))
- c.Assert(converted, qt.Equals, "{\n \"title\": \"P1\",\n \"weight\": 1\n}\n\nContent\n\n", qt.Commentf(converted))
- })
-
- c.Run("config, set environment", func(c *qt.C) {
- dir, clean := createSite(c)
- defer clean()
- out, err := captureStdout(func() error {
- resp := Execute([]string{"config", "-s=" + dir, "-e=staging"})
- return resp.Err
- })
- c.Assert(err, qt.IsNil)
- c.Assert(out, qt.Contains, "params = map[myparam:paramstaging]", qt.Commentf(out))
- })
-
- c.Run("deploy, environment set", func(c *qt.C) {
- dir, clean := createSite(c)
- defer clean()
- resp := Execute([]string{"deploy", "-s=" + dir, "-e=staging", "--target=mydeployment", "--dryRun"})
- c.Assert(resp.Err, qt.Not(qt.IsNil))
- c.Assert(resp.Err.Error(), qt.Contains, `no driver registered for "hugocloud"`)
- })
-
- c.Run("list", func(c *qt.C) {
- dir, clean := createSite(c)
- defer clean()
- out, err := captureStdout(func() error {
- resp := Execute([]string{"list", "all", "-s=" + dir, "-e=staging"})
- return resp.Err
- })
- c.Assert(err, qt.IsNil)
- c.Assert(out, qt.Contains, "p1.md")
- })
-
- c.Run("new theme", func(c *qt.C) {
- dir, clean := createSite(c)
- defer clean()
- themesDir := filepath.Join(dir, "mythemes")
- resp := Execute([]string{"new", "theme", "mytheme", "-s=" + dir, "-e=staging", "--themesDir=" + themesDir})
- c.Assert(resp.Err, qt.IsNil)
- themeTOML := readFileFrom(c, filepath.Join(themesDir, "mytheme", "theme.toml"))
- c.Assert(themeTOML, qt.Contains, "name = \"Mytheme\"")
- })
-
- c.Run("new site", func(c *qt.C) {
- dir, clean := createSite(c)
- defer clean()
- siteDir := filepath.Join(dir, "mysite")
- resp := Execute([]string{"new", "site", siteDir, "-e=staging"})
- c.Assert(resp.Err, qt.IsNil)
- config := readFileFrom(c, filepath.Join(siteDir, "config.toml"))
- c.Assert(config, qt.Contains, "baseURL = 'http://example.org/'")
- checkNewSiteInited(c, siteDir)
- })
-}
-
-func checkNewSiteInited(c *qt.C, basepath string) {
- paths := []string{
- filepath.Join(basepath, "layouts"),
- filepath.Join(basepath, "content"),
- filepath.Join(basepath, "archetypes"),
- filepath.Join(basepath, "static"),
- filepath.Join(basepath, "data"),
- filepath.Join(basepath, "config.toml"),
- }
-
- for _, path := range paths {
- _, err := os.Stat(path)
- c.Assert(err, qt.IsNil)
- }
-}
-
-func readFileFrom(c *qt.C, filename string) string {
- c.Helper()
- filename = filepath.Clean(filename)
- b, err := afero.ReadFile(hugofs.Os, filename)
- c.Assert(err, qt.IsNil)
- return string(b)
-}
-
-func TestFlags(t *testing.T) {
- c := qt.New(t)
-
- noOpRunE := func(cmd *cobra.Command, args []string) error {
- return nil
- }
-
- tests := []struct {
- name string
- args []string
- check func(c *qt.C, cmd *serverCmd)
- }{
- {
- // https://github.com/gohugoio/hugo/issues/7642
- name: "ignoreVendor as bool",
- args: []string{"server", "--ignoreVendor"},
- check: func(c *qt.C, cmd *serverCmd) {
- cfg := config.New()
- cmd.flagsToConfig(cfg)
- c.Assert(cfg.Get("ignoreVendor"), qt.Equals, true)
- },
- },
- {
- // https://github.com/gohugoio/hugo/issues/7642
- name: "ignoreVendorPaths",
- args: []string{"server", "--ignoreVendorPaths=github.com/**"},
- check: func(c *qt.C, cmd *serverCmd) {
- cfg := config.New()
- cmd.flagsToConfig(cfg)
- c.Assert(cfg.Get("ignoreVendorPaths"), qt.Equals, "github.com/**")
- },
- },
- {
- name: "Persistent flags",
- args: []string{
- "server",
- "--config=myconfig.toml",
- "--configDir=myconfigdir",
- "--contentDir=mycontent",
- "--disableKinds=page,home",
- "--environment=testing",
- "--configDir=myconfigdir",
- "--layoutDir=mylayouts",
- "--theme=mytheme",
- "--gc",
- "--themesDir=mythemes",
- "--cleanDestinationDir",
- "--navigateToChanged",
- "--disableLiveReload",
- "--noHTTPCache",
- "--i18n-warnings",
- "--destination=/tmp/mydestination",
- "-b=https://example.com/b/",
- "--port=1366",
- "--renderToDisk",
- "--source=mysource",
- "--path-warnings",
- },
- check: func(c *qt.C, sc *serverCmd) {
- c.Assert(sc, qt.Not(qt.IsNil))
- c.Assert(sc.navigateToChanged, qt.Equals, true)
- c.Assert(sc.disableLiveReload, qt.Equals, true)
- c.Assert(sc.noHTTPCache, qt.Equals, true)
- c.Assert(sc.renderToDisk, qt.Equals, true)
- c.Assert(sc.serverPort, qt.Equals, 1366)
- c.Assert(sc.environment, qt.Equals, "testing")
-
- cfg := config.New()
- sc.flagsToConfig(cfg)
- c.Assert(cfg.GetString("publishDir"), qt.Equals, "/tmp/mydestination")
- c.Assert(cfg.GetString("contentDir"), qt.Equals, "mycontent")
- c.Assert(cfg.GetString("layoutDir"), qt.Equals, "mylayouts")
- c.Assert(cfg.GetStringSlice("theme"), qt.DeepEquals, []string{"mytheme"})
- c.Assert(cfg.GetString("themesDir"), qt.Equals, "mythemes")
- c.Assert(cfg.GetString("baseURL"), qt.Equals, "https://example.com/b/")
-
- c.Assert(cfg.Get("disableKinds"), qt.DeepEquals, []string{"page", "home"})
-
- c.Assert(cfg.GetBool("gc"), qt.Equals, true)
-
- // The flag is named path-warnings
- c.Assert(cfg.GetBool("logPathWarnings"), qt.Equals, true)
-
- // The flag is named i18n-warnings
- c.Assert(cfg.GetBool("logI18nWarnings"), qt.Equals, true)
- },
- },
- }
-
- for _, test := range tests {
- c.Run(test.name, func(c *qt.C) {
- b := newCommandsBuilder()
- root := b.addAll().build()
-
- for _, cmd := range b.commands {
- if cmd.getCommand() == nil {
- continue
- }
- // We are only intereseted in the flag handling here.
- cmd.getCommand().RunE = noOpRunE
- }
- rootCmd := root.getCommand()
- rootCmd.SetArgs(test.args)
- c.Assert(rootCmd.Execute(), qt.IsNil)
- test.check(c, b.commands[0].(*serverCmd))
- })
- }
-}
-
-func TestCommandsExecute(t *testing.T) {
- c := qt.New(t)
-
- dir, clean, err := createSimpleTestSite(t, testSiteConfig{})
- c.Assert(err, qt.IsNil)
-
- dirOut, clean2, err := htesting.CreateTempDir(hugofs.Os, "hugo-cli-out")
- c.Assert(err, qt.IsNil)
-
- defer clean()
- defer clean2()
-
- sourceFlag := fmt.Sprintf("-s=%s", dir)
-
- tests := []struct {
- commands []string
- flags []string
- expectErrToContain string
- }{
- // TODO(bep) permission issue on my OSX? "operation not permitted" {[]string{"check", "ulimit"}, nil, false},
- {[]string{"env"}, nil, ""},
- {[]string{"version"}, nil, ""},
- // no args = hugo build
- {nil, []string{sourceFlag}, ""},
- {nil, []string{sourceFlag, "--renderToMemory"}, ""},
- {[]string{"config"}, []string{sourceFlag}, ""},
- {[]string{"convert", "toTOML"}, []string{sourceFlag, "-o=" + filepath.Join(dirOut, "toml")}, ""},
- {[]string{"convert", "toYAML"}, []string{sourceFlag, "-o=" + filepath.Join(dirOut, "yaml")}, ""},
- {[]string{"convert", "toJSON"}, []string{sourceFlag, "-o=" + filepath.Join(dirOut, "json")}, ""},
- {[]string{"gen", "autocomplete"}, []string{"--completionfile=" + filepath.Join(dirOut, "autocomplete.txt")}, ""},
- {[]string{"gen", "chromastyles"}, []string{"--style=manni"}, ""},
- {[]string{"gen", "doc"}, []string{"--dir=" + filepath.Join(dirOut, "doc")}, ""},
- {[]string{"gen", "man"}, []string{"--dir=" + filepath.Join(dirOut, "man")}, ""},
- {[]string{"list", "drafts"}, []string{sourceFlag}, ""},
- {[]string{"list", "expired"}, []string{sourceFlag}, ""},
- {[]string{"list", "future"}, []string{sourceFlag}, ""},
- {[]string{"new", "new-page.md"}, []string{sourceFlag}, ""},
- {[]string{"new", "site", filepath.Join(dirOut, "new-site")}, nil, ""},
- {[]string{"unknowncommand"}, nil, "unknown command"},
- // TODO(bep) cli refactor fix https://github.com/gohugoio/hugo/issues/4450
- //{[]string{"new", "theme", filepath.Join(dirOut, "new-theme")}, nil,false},
- }
-
- for _, test := range tests {
- b := newCommandsBuilder().addAll().build()
- hugoCmd := b.getCommand()
- test.flags = append(test.flags, "--quiet")
- hugoCmd.SetArgs(append(test.commands, test.flags...))
-
- // TODO(bep) capture output and add some simple asserts
- // TODO(bep) misspelled subcommands does not return an error. We should investigate this
- // but before that, check for "Error: unknown command".
-
- _, err := hugoCmd.ExecuteC()
- if test.expectErrToContain != "" {
- c.Assert(err, qt.Not(qt.IsNil))
- c.Assert(err.Error(), qt.Contains, test.expectErrToContain)
- } else {
- c.Assert(err, qt.IsNil)
- }
-
- // Assert that we have not left any development debug artifacts in
- // the code.
- if b.c != nil {
- _, ok := b.c.destinationFs.(types.DevMarker)
- c.Assert(ok, qt.Equals, false)
- }
-
- }
-}
-
-type testSiteConfig struct {
- configTOML string
- contentDir string
-}
-
-func createSimpleTestSite(t *testing.T, cfg testSiteConfig) (string, func(), error) {
- d, clean, e := htesting.CreateTempDir(hugofs.Os, "hugo-cli")
- if e != nil {
- return "", nil, e
- }
-
- cfgStr := `
-
-baseURL = "https://example.org"
-title = "Hugo Commands"
-
-
-`
-
- contentDir := "content"
-
- if cfg.configTOML != "" {
- cfgStr = cfg.configTOML
- }
- if cfg.contentDir != "" {
- contentDir = cfg.contentDir
- }
-
- os.MkdirAll(filepath.Join(d, "public"), 0777)
-
- // Just the basic. These are for CLI tests, not site testing.
- writeFile(t, filepath.Join(d, "config.toml"), cfgStr)
- writeFile(t, filepath.Join(d, "config", "staging", "params.toml"), `myparam="paramstaging"`)
- writeFile(t, filepath.Join(d, "config", "staging", "deployment.toml"), `
-[[targets]]
-name = "mydeployment"
-URL = "hugocloud://hugotestbucket"
-`)
-
- writeFile(t, filepath.Join(d, "config", "testing", "params.toml"), `myparam="paramtesting"`)
- writeFile(t, filepath.Join(d, "config", "production", "params.toml"), `myparam="paramproduction"`)
-
- writeFile(t, filepath.Join(d, contentDir, "p1.md"), `
----
-title: "P1"
-weight: 1
----
-
-Content
-
-`)
-
- writeFile(t, filepath.Join(d, "layouts", "_default", "single.html"), `
-
-Single: {{ .Title }}
-
-`)
-
- writeFile(t, filepath.Join(d, "layouts", "_default", "list.html"), `
-
-List: {{ .Title }}
-Environment: {{ hugo.Environment }}
-
-`)
-
- return d, clean, nil
-}
-
-func writeFile(t *testing.T, filename, content string) {
- must(t, os.MkdirAll(filepath.Dir(filename), os.FileMode(0755)))
- must(t, ioutil.WriteFile(filename, []byte(content), os.FileMode(0755)))
-}
-
-func must(t *testing.T, err error) {
- if err != nil {
- t.Fatal(err)
- }
-}
diff --git a/commands/config.go b/commands/config.go
index 6453319537c..59433b359e2 100644
--- a/commands/config.go
+++ b/commands/config.go
@@ -1,4 +1,4 @@
-// Copyright 2015 The Hugo Authors. All rights reserved.
+// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -9,131 +9,145 @@
// 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.Print the version number of Hug
+// limitations under the License.
package commands
import (
+ "bytes"
+ "context"
"encoding/json"
"fmt"
"os"
- "reflect"
- "regexp"
- "sort"
"strings"
"time"
- "github.com/gohugoio/hugo/common/maps"
-
+ "github.com/bep/simplecobra"
+ "github.com/gohugoio/hugo/common/hmaps"
+ "github.com/gohugoio/hugo/config/allconfig"
+ "github.com/gohugoio/hugo/hugolib/sitesmatrix"
+ "github.com/gohugoio/hugo/modules"
"github.com/gohugoio/hugo/parser"
"github.com/gohugoio/hugo/parser/metadecoders"
-
- "github.com/gohugoio/hugo/modules"
-
"github.com/spf13/cobra"
)
-var _ cmder = (*configCmd)(nil)
-
-type configCmd struct {
- *baseBuilderCmd
+// newConfigCommand creates a new config command and its subcommands.
+func newConfigCommand() *configCommand {
+ return &configCommand{
+ commands: []simplecobra.Commander{
+ &configMountsCommand{},
+ },
+ }
}
-func (b *commandsBuilder) newConfigCmd() *configCmd {
- cc := &configCmd{}
- cmd := &cobra.Command{
- Use: "config",
- Short: "Print the site configuration",
- Long: `Print the site configuration, both default and custom settings.`,
- RunE: cc.printConfig,
- }
+type configCommand struct {
+ r *rootCommand
- printMountsCmd := &cobra.Command{
- Use: "mounts",
- Short: "Print the configured file mounts",
- RunE: cc.printMounts,
- }
+ format string
+ lang string
+ printZero bool
- cmd.AddCommand(printMountsCmd)
+ commands []simplecobra.Commander
+}
- cc.baseBuilderCmd = b.newBuilderBasicCmd(cmd)
+func (c *configCommand) Commands() []simplecobra.Commander {
+ return c.commands
+}
- return cc
+func (c *configCommand) Name() string {
+ return "config"
}
-func (c *configCmd) printMounts(cmd *cobra.Command, args []string) error {
- cfg, err := initializeConfig(true, false, false, &c.hugoBuilderCommon, c, nil)
+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))
if err != nil {
return err
}
-
- allModules := cfg.Cfg.Get("allmodules").(modules.Modules)
-
- for _, m := range allModules {
- if err := parser.InterfaceToConfig(&modMounts{m: m, verbose: c.verbose}, metadecoders.JSON, os.Stdout); err != nil {
- return err
+ var config *allconfig.Config
+ if c.lang != "" {
+ var found bool
+ config, found = conf.configs.LanguageConfigMap[c.lang]
+ if !found {
+ return fmt.Errorf("language %q not found", c.lang)
}
+ } else {
+ config = conf.configs.LanguageConfigMap[conf.configs.Base.DefaultContentLanguage]
}
- return nil
-}
-func (c *configCmd) printConfig(cmd *cobra.Command, args []string) error {
- cfg, err := initializeConfig(true, false, false, &c.hugoBuilderCommon, c, nil)
- if err != nil {
+ var buf bytes.Buffer
+ dec := json.NewEncoder(&buf)
+ dec.SetIndent("", " ")
+ dec.SetEscapeHTML(false)
+
+ if err := dec.Encode(parser.ReplacingJSONMarshaller{Value: config, KeysToLower: true, OmitEmpty: !c.printZero}); err != nil {
return err
}
- allSettings := cfg.Cfg.Get("").(maps.Params)
-
- // We need to clean up this, but we store objects in the config that
- // isn't really interesting to the end user, so filter these.
- ignoreKeysRe := regexp.MustCompile("client|sorted|filecacheconfigs|allmodules|multilingual")
-
- separator := ": "
-
- if len(cfg.configFiles) > 0 && strings.HasSuffix(cfg.configFiles[0], ".toml") {
- separator = " = "
- }
+ format := strings.ToLower(c.format)
- var keys []string
- for k := range allSettings {
- if ignoreKeysRe.MatchString(k) {
- continue
+ switch format {
+ case "json":
+ os.Stdout.Write(buf.Bytes())
+ default:
+ // Decode the JSON to a map[string]interface{} and then unmarshal it again to the correct format.
+ var m map[string]any
+ if err := json.Unmarshal(buf.Bytes(), &m); err != nil {
+ return err
}
- keys = append(keys, k)
- }
- sort.Strings(keys)
- for _, k := range keys {
- kv := reflect.ValueOf(allSettings[k])
- if kv.Kind() == reflect.String {
- fmt.Printf("%s%s\"%+v\"\n", k, separator, allSettings[k])
- } else {
- fmt.Printf("%s%s%+v\n", k, separator, allSettings[k])
+ hmaps.ConvertFloat64WithNoDecimalsToInt(m)
+ switch format {
+ case "yaml":
+ return parser.InterfaceToConfig(m, metadecoders.YAML, os.Stdout)
+ case "toml":
+ return parser.InterfaceToConfig(m, metadecoders.TOML, os.Stdout)
+ default:
+ return fmt.Errorf("unsupported format: %q", format)
}
}
return nil
}
-type modMounts struct {
- verbose bool
- m modules.Module
+func (c *configCommand) Init(cd *simplecobra.Commandeer) error {
+ c.r = cd.Root.Command.(*rootCommand)
+ cmd := cd.CobraCommand
+ cmd.Short = "Display project configuration"
+ cmd.Long = `Display project 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.")
+ cmd.Flags().BoolVar(&c.printZero, "printZero", false, `include config options with zero values (e.g. false, 0, "") in the output`)
+ _ = cmd.RegisterFlagCompletionFunc("lang", cobra.NoFileCompletions)
+ applyLocalFlagsBuildConfig(cmd, c.r)
+
+ return nil
}
-type modMount struct {
- Source string `json:"source"`
- Target string `json:"target"`
- Lang string `json:"lang,omitempty"`
+func (c *configCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
+ return nil
}
-func (m *modMounts) MarshalJSON() ([]byte, error) {
- var mounts []modMount
+type configModMount struct {
+ Source string `json:"source"`
+ Target string `json:"target"`
+ Sites sitesmatrix.Sites `json:"sites,omitzero"`
+}
+
+type configModMounts struct {
+ verbose bool
+ m modules.Module
+}
+
+// MarshalJSON is for internal use only.
+func (m *configModMounts) MarshalJSON() ([]byte, error) {
+ var mounts []configModMount
for _, mount := range m.m.Mounts() {
- mounts = append(mounts, modMount{
+ mounts = append(mounts, configModMount{
Source: mount.Source,
Target: mount.Target,
- Lang: mount.Lang,
+ Sites: mount.Sites,
})
}
@@ -145,15 +159,15 @@ func (m *modMounts) MarshalJSON() ([]byte, error) {
if m.verbose {
config := m.m.Config()
return json.Marshal(&struct {
- Path string `json:"path"`
- Version string `json:"version"`
- Time time.Time `json:"time"`
- Owner string `json:"owner"`
- Dir string `json:"dir"`
- Meta map[string]interface{} `json:"meta"`
- HugoVersion modules.HugoVersion `json:"hugoVersion"`
-
- Mounts []modMount `json:"mounts"`
+ Path string `json:"path"`
+ Version string `json:"version"`
+ Time time.Time `json:"time"`
+ Owner string `json:"owner"`
+ Dir string `json:"dir"`
+ Meta map[string]any `json:"meta"`
+ HugoVersion modules.HugoVersion `json:"hugoVersion"`
+
+ Mounts []configModMount `json:"mounts"`
}{
Path: m.m.Path(),
Version: m.m.Version(),
@@ -167,12 +181,12 @@ func (m *modMounts) MarshalJSON() ([]byte, error) {
}
return json.Marshal(&struct {
- Path string `json:"path"`
- Version string `json:"version"`
- Time time.Time `json:"time"`
- Owner string `json:"owner"`
- Dir string `json:"dir"`
- Mounts []modMount `json:"mounts"`
+ Path string `json:"path"`
+ Version string `json:"version"`
+ Time time.Time `json:"time"`
+ Owner string `json:"owner"`
+ Dir string `json:"dir"`
+ Mounts []configModMount `json:"mounts"`
}{
Path: m.m.Path(),
Version: m.m.Version(),
@@ -181,5 +195,46 @@ func (m *modMounts) MarshalJSON() ([]byte, error) {
Dir: m.m.Dir(),
Mounts: mounts,
})
+}
+
+type configMountsCommand struct {
+ r *rootCommand
+ configCmd *configCommand
+}
+
+func (c *configMountsCommand) Commands() []simplecobra.Commander {
+ return nil
+}
+
+func (c *configMountsCommand) Name() string {
+ return "mounts"
+}
+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))
+ if err != nil {
+ return err
+ }
+
+ for _, m := range conf.configs.Modules {
+ if err := parser.InterfaceToConfig(&configModMounts{m: m, verbose: r.isVerbose()}, metadecoders.JSON, os.Stdout); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (c *configMountsCommand) Init(cd *simplecobra.Commandeer) error {
+ c.r = cd.Root.Command.(*rootCommand)
+ cmd := cd.CobraCommand
+ cmd.Short = "Print the configured file mounts"
+ cmd.ValidArgsFunction = cobra.NoFileCompletions
+ applyLocalFlagsBuildConfig(cmd, c.r)
+ return nil
+}
+
+func (c *configMountsCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
+ c.configCmd = cd.Parent.Command.(*configCommand)
+ return nil
}
diff --git a/commands/convert.go b/commands/convert.go
index e2bd4307fad..06982984935 100644
--- a/commands/convert.go
+++ b/commands/convert.go
@@ -1,4 +1,4 @@
-// Copyright 2019 The Hugo Authors. All rights reserved.
+// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,136 +15,139 @@ package commands
import (
"bytes"
+ "context"
"fmt"
"path/filepath"
+ "slices"
"strings"
"time"
- "github.com/gohugoio/hugo/parser/pageparser"
-
- "github.com/gohugoio/hugo/resources/page"
-
- "github.com/gohugoio/hugo/hugofs"
-
+ "github.com/bep/simplecobra"
+ "github.com/gohugoio/hugo/common/hugio"
+ "github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
-
+ "github.com/gohugoio/hugo/hugofs"
+ "github.com/gohugoio/hugo/hugolib"
"github.com/gohugoio/hugo/parser"
"github.com/gohugoio/hugo/parser/metadecoders"
-
- "github.com/pkg/errors"
-
- "github.com/gohugoio/hugo/hugolib"
-
+ "github.com/gohugoio/hugo/parser/pageparser"
+ "github.com/gohugoio/hugo/resources/page"
"github.com/spf13/cobra"
)
-var _ cmder = (*convertCmd)(nil)
-
-type convertCmd struct {
- outputDir string
- unsafe bool
-
- *baseBuilderCmd
-}
-
-func (b *commandsBuilder) newConvertCmd() *convertCmd {
- cc := &convertCmd{}
-
- cmd := &cobra.Command{
- Use: "convert",
- Short: "Convert your content to different formats",
- Long: `Convert your content (e.g. front matter) to different formats.
-
-See convert's subcommands toJSON, toTOML and toYAML for more information.`,
- RunE: nil,
- }
-
- cmd.AddCommand(
- &cobra.Command{
- Use: "toJSON",
- Short: "Convert front matter to JSON",
- Long: `toJSON converts all front matter in the content directory
+func newConvertCommand() *convertCommand {
+ var c *convertCommand
+ c = &convertCommand{
+ commands: []simplecobra.Commander{
+ &simpleCommand{
+ name: "toJSON",
+ short: "Convert front matter to JSON",
+ long: `toJSON converts all front matter in the content directory
to use JSON for the front matter.`,
- RunE: func(cmd *cobra.Command, args []string) error {
- return cc.convertContents(metadecoders.JSON)
+ run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
+ return c.convertContents(metadecoders.JSON)
+ },
+ withc: func(cmd *cobra.Command, r *rootCommand) {
+ cmd.ValidArgsFunction = cobra.NoFileCompletions
+ },
},
- },
- &cobra.Command{
- Use: "toTOML",
- Short: "Convert front matter to TOML",
- Long: `toTOML converts all front matter in the content directory
+ &simpleCommand{
+ name: "toTOML",
+ short: "Convert front matter to TOML",
+ long: `toTOML converts all front matter in the content directory
to use TOML for the front matter.`,
- RunE: func(cmd *cobra.Command, args []string) error {
- return cc.convertContents(metadecoders.TOML)
+ run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
+ return c.convertContents(metadecoders.TOML)
+ },
+ withc: func(cmd *cobra.Command, r *rootCommand) {
+ cmd.ValidArgsFunction = cobra.NoFileCompletions
+ },
},
- },
- &cobra.Command{
- Use: "toYAML",
- Short: "Convert front matter to YAML",
- Long: `toYAML converts all front matter in the content directory
+ &simpleCommand{
+ name: "toYAML",
+ short: "Convert front matter to YAML",
+ long: `toYAML converts all front matter in the content directory
to use YAML for the front matter.`,
- RunE: func(cmd *cobra.Command, args []string) error {
- return cc.convertContents(metadecoders.YAML)
+ run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
+ return c.convertContents(metadecoders.YAML)
+ },
+ withc: func(cmd *cobra.Command, r *rootCommand) {
+ cmd.ValidArgsFunction = cobra.NoFileCompletions
+ },
},
},
- )
+ }
+ return c
+}
- cmd.PersistentFlags().StringVarP(&cc.outputDir, "output", "o", "", "filesystem path to write files to")
- cmd.PersistentFlags().BoolVar(&cc.unsafe, "unsafe", false, "enable less safe operations, please backup first")
+type convertCommand struct {
+ // Flags.
+ outputDir string
+ unsafe bool
- cc.baseBuilderCmd = b.newBuilderBasicCmd(cmd)
+ // Deps.
+ r *rootCommand
+ h *hugolib.HugoSites
- return cc
+ // Commands.
+ commands []simplecobra.Commander
}
-func (cc *convertCmd) convertContents(format metadecoders.Format) error {
- if cc.outputDir == "" && !cc.unsafe {
- return newUserError("Unsafe operation not allowed, use --unsafe or set a different output path")
- }
+func (c *convertCommand) Commands() []simplecobra.Commander {
+ return c.commands
+}
- c, err := initializeConfig(true, false, false, &cc.hugoBuilderCommon, cc, nil)
- if err != nil {
- return err
- }
+func (c *convertCommand) Name() string {
+ return "convert"
+}
- c.Cfg.Set("buildDrafts", true)
+func (c *convertCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
+ return nil
+}
- h, err := hugolib.NewHugoSites(*c.DepsCfg)
- if err != nil {
- return err
- }
+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.
- if err := h.Build(hugolib.BuildCfg{SkipRender: true}); err != nil {
- return err
- }
+See convert's subcommands toJSON, toTOML and toYAML for more information.`
- site := h.Sites[0]
+ cmd.PersistentFlags().StringVarP(&c.outputDir, "output", "o", "", "filesystem path to write files to")
+ _ = cmd.MarkFlagDirname("output")
+ cmd.PersistentFlags().BoolVar(&c.unsafe, "unsafe", false, "enable less safe operations, please backup first")
- site.Log.Println("processing", len(site.AllPages()), "content files")
- for _, p := range site.AllPages() {
- if err := cc.convertAndSavePage(p, site, format); err != nil {
- return err
- }
+ cmd.RunE = nil
+ return nil
+}
+
+func (c *convertCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
+ c.r = cd.Root.Command.(*rootCommand)
+ cfg := config.New()
+ cfg.Set("buildDrafts", true)
+ h, err := c.r.Hugo(flagsToCfg(cd, cfg))
+ if err != nil {
+ return err
}
+ c.h = h
return nil
}
-func (cc *convertCmd) convertAndSavePage(p page.Page, site *hugolib.Site, targetFormat metadecoders.Format) error {
+func (c *convertCommand) convertAndSavePage(p page.Page, site *hugolib.Site, targetFormat metadecoders.Format) error {
// The resources are not in .Site.AllPages.
for _, r := range p.Resources().ByType("page") {
- if err := cc.convertAndSavePage(r.(page.Page), site, targetFormat); err != nil {
+ if err := c.convertAndSavePage(r.(page.Page), site, targetFormat); err != nil {
return err
}
}
- if p.File().IsZero() {
+ if p.File() == nil {
// No content file.
return nil
}
- errMsg := fmt.Errorf("Error processing file %q", p.Path())
+ errMsg := fmt.Errorf("error processing file %q", p.File().Path())
- site.Log.Infoln("Attempting to convert", p.File().Filename())
+ site.Log.Infoln("attempting to convert", p.File().Filename())
f := p.File()
file, err := f.FileInfo().Meta().Open()
@@ -184,26 +187,101 @@ func (cc *convertCmd) convertAndSavePage(p page.Page, site *hugolib.Site, target
newFilename := p.File().Filename()
- if cc.outputDir != "" {
- contentDir := strings.TrimSuffix(newFilename, p.Path())
+ if c.outputDir != "" {
+ contentDir := strings.TrimSuffix(newFilename, p.File().Path())
contentDir = filepath.Base(contentDir)
- newFilename = filepath.Join(cc.outputDir, contentDir, p.Path())
+ newFilename = filepath.Join(c.outputDir, contentDir, p.File().Path())
}
fs := hugofs.Os
if err := helpers.WriteToDisk(newFilename, &newContent, fs); err != nil {
- return errors.Wrapf(err, "Failed to save file %q:", newFilename)
+ return fmt.Errorf("failed to save file %q:: %w", newFilename, err)
+ }
+
+ return nil
+}
+
+func (c *convertCommand) copyContentDirsForOutput(pagesBackedByFile page.Pages) error {
+ contentDirs := make(map[string]bool)
+ for _, p := range pagesBackedByFile {
+ filename := p.File().Filename()
+ contentDir := strings.TrimSuffix(filename, p.File().Path())
+ if contentDir == filename {
+ continue
+ }
+ contentDirs[filepath.Clean(contentDir)] = true
+ }
+
+ var contentDirList []string
+ for contentDir := range contentDirs {
+ contentDirList = append(contentDirList, contentDir)
+ }
+ slices.Sort(contentDirList)
+
+ outputDirAbs, err := filepath.Abs(c.outputDir)
+ if err != nil {
+ return fmt.Errorf("failed to resolve output path %q: %w", c.outputDir, err)
+ }
+
+ for _, contentDir := range contentDirList {
+ outputContentDirAbs := filepath.Join(outputDirAbs, filepath.Base(contentDir))
+
+ skipDirs := make(map[string]bool)
+ relToOutputDir, err := filepath.Rel(contentDir, outputDirAbs)
+ if err == nil && relToOutputDir != ".." && !strings.HasPrefix(relToOutputDir, ".."+string(filepath.Separator)) {
+ skipDirs[filepath.Clean(outputDirAbs)] = true
+ }
+ relToOutputContentDir, err := filepath.Rel(contentDir, outputContentDirAbs)
+ if err == nil && relToOutputContentDir != ".." && !strings.HasPrefix(relToOutputContentDir, ".."+string(filepath.Separator)) {
+ skipDirs[filepath.Clean(outputContentDirAbs)] = true
+ }
+
+ var shouldCopy func(filename string) bool
+ if len(skipDirs) > 0 {
+ shouldCopy = func(filename string) bool {
+ return !skipDirs[filepath.Clean(filename)]
+ }
+ }
+
+ if err := hugio.CopyDir(hugofs.Os, contentDir, outputContentDirAbs, shouldCopy); err != nil {
+ return fmt.Errorf("failed to copy %q to %q: %w", contentDir, outputContentDirAbs, err)
+ }
}
return nil
}
-type parsedFile struct {
- frontMatterFormat metadecoders.Format
- frontMatterSource []byte
- frontMatter map[string]interface{}
+func (c *convertCommand) convertContents(format metadecoders.Format) error {
+ if c.outputDir == "" && !c.unsafe {
+ return newUserError("Unsafe operation not allowed, use --unsafe or set a different output path")
+ }
+
+ if err := c.h.Build(hugolib.BuildCfg{SkipRender: true}); err != nil {
+ return err
+ }
+
+ site := c.h.Sites[0]
- // Everything after Front Matter
- content []byte
+ var pagesBackedByFile page.Pages
+ for _, p := range site.AllPages() {
+ if p.File() == nil {
+ continue
+ }
+ pagesBackedByFile = append(pagesBackedByFile, p)
+ }
+
+ if c.outputDir != "" {
+ if err := c.copyContentDirsForOutput(pagesBackedByFile); err != nil {
+ return err
+ }
+ }
+
+ site.Log.Println("processing", len(pagesBackedByFile), "content files")
+ for _, p := range site.AllPages() {
+ if err := c.convertAndSavePage(p, site, format); err != nil {
+ return err
+ }
+ }
+ return nil
}
diff --git a/commands/deploy.go b/commands/deploy.go
index 2b6d91246ff..2f32bd8f62c 100644
--- a/commands/deploy.go
+++ b/commands/deploy.go
@@ -1,4 +1,4 @@
-// Copyright 2019 The Hugo Authors. All rights reserved.
+// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// +build !nodeploy
+//go:build withdeploy
package commands
@@ -19,67 +19,33 @@ import (
"context"
"github.com/gohugoio/hugo/deploy"
+
+ "github.com/bep/simplecobra"
"github.com/spf13/cobra"
)
-var _ cmder = (*deployCmd)(nil)
-
-// deployCmd supports deploying sites to Cloud providers.
-type deployCmd struct {
- *baseBuilderCmd
-
- invalidateCDN bool
- maxDeletes int
-}
-
-// TODO: In addition to the "deploy" command, consider adding a "--deploy"
-// flag for the default command; this would build the site and then deploy it.
-// It's not obvious how to do this; would all of the deploy-specific flags
-// have to exist at the top level as well?
-
-// TODO: The output files change every time "hugo" is executed, it looks
-// like because of map order randomization. This means that you can
-// run "hugo && hugo deploy" again and again and upload new stuff every time. Is
-// this intended?
-
-func (b *commandsBuilder) newDeployCmd() *deployCmd {
- cc := &deployCmd{}
-
- cmd := &cobra.Command{
- Use: "deploy",
- Short: "Deploy your site to a Cloud provider.",
- Long: `Deploy your site to a Cloud provider.
+func newDeployCommand() simplecobra.Commander {
+ return &simpleCommand{
+ name: "deploy",
+ short: "Deploy your project to a cloud provider",
+ long: `Deploy your project to a cloud provider
See https://gohugo.io/hosting-and-deployment/hugo-deploy/ for detailed
documentation.
`,
-
- RunE: func(cmd *cobra.Command, args []string) error {
- cfgInit := func(c *commandeer) error {
- c.Set("invalidateCDN", cc.invalidateCDN)
- c.Set("maxDeletes", cc.maxDeletes)
- return nil
- }
- comm, err := initializeConfig(true, true, false, &cc.hugoBuilderCommon, cc, cfgInit)
+ run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
+ h, err := r.Hugo(flagsToCfgWithAdditionalConfigBase(cd, nil, "deployment"))
if err != nil {
return err
}
- deployer, err := deploy.New(comm.Cfg, comm.hugo().PathSpec.PublishFs)
+ deployer, err := deploy.New(h.Configs.GetFirstLanguageConfig(), h.Log, h.PathSpec.PublishFs)
if err != nil {
return err
}
- return deployer.Deploy(context.Background())
+ return deployer.Deploy(ctx)
+ },
+ withc: func(cmd *cobra.Command, r *rootCommand) {
+ applyDeployFlags(cmd, r)
},
}
-
- cmd.Flags().String("target", "", "target deployment from deployments section in config file; defaults to the first one")
- 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().BoolVar(&cc.invalidateCDN, "invalidateCDN", true, "invalidate the CDN cache listed in the deployment target")
- cmd.Flags().IntVar(&cc.maxDeletes, "maxDeletes", 256, "maximum # of files to delete, or -1 to disable")
-
- cc.baseBuilderCmd = b.newBuilderBasicCmd(cmd)
-
- return cc
}
diff --git a/commands/deploy_flags.go b/commands/deploy_flags.go
new file mode 100644
index 00000000000..d4326547ab3
--- /dev/null
+++ b/commands/deploy_flags.go
@@ -0,0 +1,33 @@
+// Copyright 2024 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package commands
+
+import (
+ "github.com/gohugoio/hugo/deploy/deployconfig"
+ "github.com/spf13/cobra"
+)
+
+func applyDeployFlags(cmd *cobra.Command, r *rootCommand) {
+ cmd.ValidArgsFunction = cobra.NoFileCompletions
+ cmd.Flags().String("target", "", "target deployment from deployments section in config file; defaults to the first one")
+ _ = cmd.RegisterFlagCompletionFunc("target", cobra.NoFileCompletions)
+ cmd.Flags().Bool("confirm", false, "ask for confirmation before making changes to the target")
+ cmd.Flags().Bool("dryRun", false, "dry run")
+ cmd.Flags().Bool("force", false, "force upload of all files")
+ cmd.Flags().Bool("invalidateCDN", deployconfig.DefaultConfig.InvalidateCDN, "invalidate the CDN cache listed in the deployment target")
+ cmd.Flags().Int("maxDeletes", deployconfig.DefaultConfig.MaxDeletes, "maximum # of files to delete, or -1 to disable")
+ _ = cmd.RegisterFlagCompletionFunc("maxDeletes", cobra.NoFileCompletions)
+ cmd.Flags().Int("workers", deployconfig.DefaultConfig.Workers, "number of workers to transfer files. defaults to 10")
+ _ = cmd.RegisterFlagCompletionFunc("workers", cobra.NoFileCompletions)
+}
diff --git a/commands/deploy_off.go b/commands/deploy_off.go
new file mode 100644
index 00000000000..8f5eaa2deaf
--- /dev/null
+++ b/commands/deploy_off.go
@@ -0,0 +1,50 @@
+// 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
+
+// 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 (
+ "context"
+ "errors"
+
+ "github.com/bep/simplecobra"
+ "github.com/spf13/cobra"
+)
+
+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")
+ },
+ withc: func(cmd *cobra.Command, r *rootCommand) {
+ applyDeployFlags(cmd, r)
+ cmd.Hidden = true
+ },
+ }
+}
diff --git a/commands/env.go b/commands/env.go
index 65808b1be67..753522560f2 100644
--- a/commands/env.go
+++ b/commands/env.go
@@ -1,4 +1,4 @@
-// Copyright 2016 The Hugo Authors. All rights reserved.
+// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,47 +14,57 @@
package commands
import (
+ "context"
"runtime"
+ "github.com/bep/simplecobra"
"github.com/gohugoio/hugo/common/hugo"
-
"github.com/spf13/cobra"
- jww "github.com/spf13/jwalterweatherman"
)
-var _ cmder = (*envCmd)(nil)
-
-type envCmd struct {
- *baseCmd
-}
-
-func newEnvCmd() *envCmd {
- return &envCmd{
- baseCmd: newBaseCmd(&cobra.Command{
- Use: "env",
- Short: "Print Hugo version and environment info",
- Long: `Print Hugo version and environment info. This is useful in Hugo bug reports.
-
-If you add the -v flag, you will get a full dependency list.
-`,
- RunE: func(cmd *cobra.Command, args []string) error {
- printHugoVersion()
- jww.FEEDBACK.Printf("GOOS=%q\n", runtime.GOOS)
- jww.FEEDBACK.Printf("GOARCH=%q\n", runtime.GOARCH)
- jww.FEEDBACK.Printf("GOVERSION=%q\n", runtime.Version())
+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",
+ 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)
+ r.Printf("GOARCH=%q\n", runtime.GOARCH)
+ r.Printf("GOVERSION=%q\n", runtime.Version())
- isVerbose, _ := cmd.Flags().GetBool("verbose")
-
- if isVerbose {
- deps := hugo.GetDependencyList()
- for _, dep := range deps {
- jww.FEEDBACK.Printf("%s\n", dep)
- }
+ if r.isVerbose() {
+ deps := hugo.GetDependencyList()
+ for _, dep := range deps {
+ r.Printf("%s\n", dep)
}
-
- return nil
- },
- }),
+ } else {
+ // These are also included in the GetDependencyList above;
+ // always print these as these are most likely the most useful to know about.
+ deps := hugo.GetDependencyListNonGo()
+ for _, dep := range deps {
+ r.Printf("%s\n", dep)
+ }
+ }
+ return nil
+ },
+ withc: func(cmd *cobra.Command, r *rootCommand) {
+ cmd.ValidArgsFunction = cobra.NoFileCompletions
+ },
}
+}
+func newVersionCmd() simplecobra.Commander {
+ return &simpleCommand{
+ name: "version",
+ run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
+ r.Println(hugo.BuildVersionString())
+ return nil
+ },
+ short: "Display version",
+ long: "Display version and environment info. This is useful in Hugo bug reports.",
+ withc: func(cmd *cobra.Command, r *rootCommand) {
+ cmd.ValidArgsFunction = cobra.NoFileCompletions
+ },
+ }
}
diff --git a/commands/gen.go b/commands/gen.go
index 6878cfe70e2..6ec6a9916a4 100644
--- a/commands/gen.go
+++ b/commands/gen.go
@@ -1,4 +1,4 @@
-// Copyright 2015 The Hugo Authors. All rights reserved.
+// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,28 +14,295 @@
package commands
import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "os"
+ "path"
+ "path/filepath"
+ "slices"
+ "strings"
+
+ "github.com/alecthomas/chroma/v2"
+ "github.com/alecthomas/chroma/v2/formatters/html"
+ "github.com/alecthomas/chroma/v2/styles"
+ "github.com/bep/simplecobra"
+ "github.com/goccy/go-yaml"
+ "github.com/gohugoio/hugo/common/hugo"
+ "github.com/gohugoio/hugo/docshelper"
+ "github.com/gohugoio/hugo/helpers"
+ "github.com/gohugoio/hugo/hugofs"
+ "github.com/gohugoio/hugo/hugolib"
+ "github.com/gohugoio/hugo/parser"
"github.com/spf13/cobra"
+ "github.com/spf13/cobra/doc"
)
-var _ cmder = (*genCmd)(nil)
+func newGenCommand() *genCommand {
+ var (
+ // Flags.
+ gendocdir string
+ genmandir string
+
+ // Chroma flags.
+ style string
+ highlightStyle string
+ lineNumbersInlineStyle string
+ lineNumbersTableStyle string
+ omitEmpty bool
+ omitClassComments bool
+ )
+
+ newChromaStyles := func() simplecobra.Commander {
+ return &simpleCommand{
+ name: "chromastyles",
+ short: "Generate CSS stylesheet for the Chroma code highlighter",
+ long: `Generate CSS stylesheet for the Chroma code highlighter for a given style. This stylesheet is needed if markup.highlight.noClasses is disabled in config.
+
+See https://gohugo.io/quick-reference/syntax-highlighting-styles/ for a preview of the available styles.`,
+
+ run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
+ style = strings.ToLower(style)
+ if !slices.Contains(styles.Names(), style) {
+ return fmt.Errorf("invalid style: %s", style)
+ }
+ builder := styles.Get(style).Builder()
+ if highlightStyle != "" {
+ builder.Add(chroma.LineHighlight, highlightStyle)
+ }
+ if lineNumbersInlineStyle != "" {
+ builder.Add(chroma.LineNumbers, lineNumbersInlineStyle)
+ }
+ if lineNumbersTableStyle != "" {
+ builder.Add(chroma.LineNumbersTable, lineNumbersTableStyle)
+ }
+ style, err := builder.Build()
+ if err != nil {
+ return err
+ }
+
+ if omitEmpty {
+ // See https://github.com/alecthomas/chroma/commit/5b2a4c5a26c503c79bc86ba3c4ae5b330028bd3d
+ hugo.Deprecate("--omitEmpty", "Flag is no longer needed, empty classes are now always omitted.", "v0.149.0")
+ }
+ options := []html.Option{
+ html.WithCSSComments(!omitClassComments),
+ }
+ formatter := html.New(options...)
+
+ w := os.Stdout
+ fmt.Fprintf(w, "/* Generated using: hugo %s */\n\n", strings.Join(os.Args[1:], " "))
+ formatter.WriteCSS(w, style)
+ return nil
+ },
+ withc: func(cmd *cobra.Command, r *rootCommand) {
+ cmd.ValidArgsFunction = cobra.NoFileCompletions
+ cmd.PersistentFlags().StringVar(&style, "style", "friendly", "highlighter style")
+ _ = cmd.RegisterFlagCompletionFunc("style", cobra.NoFileCompletions)
+ cmd.PersistentFlags().StringVar(&highlightStyle, "highlightStyle", "", `foreground and background colors for highlighted lines, e.g. --highlightStyle "#fff000 bg:#000fff"`)
+ _ = cmd.RegisterFlagCompletionFunc("highlightStyle", cobra.NoFileCompletions)
+ cmd.PersistentFlags().StringVar(&lineNumbersInlineStyle, "lineNumbersInlineStyle", "", `foreground and background colors for inline line numbers, e.g. --lineNumbersInlineStyle "#fff000 bg:#000fff"`)
+ _ = cmd.RegisterFlagCompletionFunc("lineNumbersInlineStyle", cobra.NoFileCompletions)
+ cmd.PersistentFlags().StringVar(&lineNumbersTableStyle, "lineNumbersTableStyle", "", `foreground and background colors for table line numbers, e.g. --lineNumbersTableStyle "#fff000 bg:#000fff"`)
+ _ = cmd.RegisterFlagCompletionFunc("lineNumbersTableStyle", cobra.NoFileCompletions)
+ cmd.PersistentFlags().BoolVar(&omitEmpty, "omitEmpty", false, `omit empty CSS rules (deprecated, no longer needed)`)
+ _ = cmd.RegisterFlagCompletionFunc("omitEmpty", cobra.NoFileCompletions)
+ cmd.PersistentFlags().BoolVar(&omitClassComments, "omitClassComments", false, `omit CSS class comment prefixes in the generated CSS`)
+ _ = cmd.RegisterFlagCompletionFunc("omitClassComments", cobra.NoFileCompletions)
+ },
+ }
+ }
+
+ newMan := func() simplecobra.Commander {
+ return &simpleCommand{
+ name: "man",
+ short: "Generate man pages for the Hugo CLI",
+ long: `This command automatically generates up-to-date man pages of Hugo's
+ command-line interface. By default, it creates the man page files
+ in the "man" directory under the current directory.`,
+
+ run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
+ header := &doc.GenManHeader{
+ Section: "1",
+ Manual: "Hugo Manual",
+ Source: fmt.Sprintf("Hugo %s", hugo.CurrentVersion),
+ }
+ if !strings.HasSuffix(genmandir, helpers.FilePathSeparator) {
+ genmandir += helpers.FilePathSeparator
+ }
+ if found, _ := helpers.Exists(genmandir, hugofs.Os); !found {
+ r.Println("Directory", genmandir, "does not exist, creating...")
+ if err := hugofs.Os.MkdirAll(genmandir, 0o777); err != nil {
+ return err
+ }
+ }
+ cd.CobraCommand.Root().DisableAutoGenTag = true
+
+ r.Println("Generating Hugo man pages in", genmandir, "...")
+ doc.GenManTree(cd.CobraCommand.Root(), header, genmandir)
+
+ r.Println("Done.")
+
+ return nil
+ },
+ withc: func(cmd *cobra.Command, r *rootCommand) {
+ cmd.ValidArgsFunction = cobra.NoFileCompletions
+ cmd.PersistentFlags().StringVar(&genmandir, "dir", "man/", "the directory to write the man pages.")
+ _ = cmd.MarkFlagDirname("dir")
+ },
+ }
+ }
+
+ newGen := func() simplecobra.Commander {
+ const gendocFrontmatterTemplate = `---
+title: "%s"
+slug: %s
+url: %s
+---
+`
+
+ return &simpleCommand{
+ name: "doc",
+ short: "Generate Markdown documentation for the Hugo CLI",
+ long: `Generate Markdown documentation for the Hugo CLI.
+ This command is, mostly, used to create up-to-date documentation
+ of Hugo's command-line interface for https://gohugo.io/.
+
+ It creates one Markdown file per command with front matter suitable
+ for rendering in Hugo.`,
+ run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
+ cd.CobraCommand.VisitParents(func(c *cobra.Command) {
+ // Disable the "Auto generated by spf13/cobra on DATE"
+ // as it creates a lot of diffs.
+ c.DisableAutoGenTag = true
+ })
+ if !strings.HasSuffix(gendocdir, helpers.FilePathSeparator) {
+ gendocdir += helpers.FilePathSeparator
+ }
+ if found, _ := helpers.Exists(gendocdir, hugofs.Os); !found {
+ r.Println("Directory", gendocdir, "does not exist, creating...")
+ if err := hugofs.Os.MkdirAll(gendocdir, 0o777); err != nil {
+ return err
+ }
+ }
+ prepender := func(filename string) string {
+ name := filepath.Base(filename)
+ base := strings.TrimSuffix(name, path.Ext(name))
+ url := "/docs/reference/commands/" + strings.ToLower(base) + "/"
+ return fmt.Sprintf(gendocFrontmatterTemplate, strings.Replace(base, "_", " ", -1), base, url)
+ }
+
+ linkHandler := func(name string) string {
+ base := strings.TrimSuffix(name, path.Ext(name))
+ return "/docs/reference/commands/" + strings.ToLower(base) + "/"
+ }
+ r.Println("Generating Hugo command-line documentation in", gendocdir, "...")
+ doc.GenMarkdownTreeCustom(cd.CobraCommand.Root(), gendocdir, prepender, linkHandler)
+ r.Println("Done.")
+
+ return nil
+ },
+ withc: func(cmd *cobra.Command, r *rootCommand) {
+ cmd.ValidArgsFunction = cobra.NoFileCompletions
+ cmd.PersistentFlags().StringVar(&gendocdir, "dir", "/tmp/hugodoc/", "the directory to write the doc.")
+ _ = cmd.MarkFlagDirname("dir")
+ },
+ }
+ }
+
+ var docsHelperTarget string
+
+ newDocsHelper := func() simplecobra.Commander {
+ return &simpleCommand{
+ name: "docshelper",
+ short: "Generate some data files for the Hugo docs",
+
+ run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
+ r.Println("Generate docs data to", docsHelperTarget)
+
+ var buf bytes.Buffer
+ jsonEnc := json.NewEncoder(&buf)
+
+ configProvider := func() docshelper.DocProvider {
+ conf := hugolib.DefaultConfig()
+ conf.CacheDir = "" // The default value does not make sense in the docs.
+ defaultConfig := parser.NullBoolJSONMarshaller{Wrapped: parser.LowerCaseCamelJSONMarshaller{Value: conf}}
+ return docshelper.DocProvider{"config": defaultConfig}
+ }
+
+ docshelper.AddDocProviderFunc(configProvider)
+ if err := jsonEnc.Encode(docshelper.GetDocProvider()); err != nil {
+ return err
+ }
+
+ // Decode the JSON to a map[string]interface{} and then unmarshal it again to the correct format.
+ var m map[string]any
+ if err := json.Unmarshal(buf.Bytes(), &m); err != nil {
+ return err
+ }
+
+ targetFile := filepath.Join(docsHelperTarget, "docs.yaml")
+
+ f, err := os.Create(targetFile)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ yamlEnc := yaml.NewEncoder(f, yaml.UseSingleQuote(true), yaml.AutoInt())
+ if err := yamlEnc.Encode(m); err != nil {
+ return err
+ }
+
+ r.Println("Done!")
+ return nil
+ },
+ withc: func(cmd *cobra.Command, r *rootCommand) {
+ cmd.Hidden = true
+ cmd.ValidArgsFunction = cobra.NoFileCompletions
+ cmd.PersistentFlags().StringVarP(&docsHelperTarget, "dir", "", "docs/data", "data dir")
+ },
+ }
+ }
+
+ return &genCommand{
+ commands: []simplecobra.Commander{
+ newChromaStyles(),
+ newGen(),
+ newMan(),
+ newDocsHelper(),
+ },
+ }
+}
+
+type genCommand struct {
+ rootCmd *rootCommand
+
+ commands []simplecobra.Commander
+}
+
+func (c *genCommand) Commands() []simplecobra.Commander {
+ return c.commands
+}
+
+func (c *genCommand) Name() string {
+ return "gen"
+}
+
+func (c *genCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
+ return nil
+}
+
+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."
-type genCmd struct {
- *baseCmd
+ cmd.RunE = nil
+ return nil
}
-func newGenCmd() *genCmd {
- cc := &genCmd{}
- cc.baseCmd = newBaseCmd(&cobra.Command{
- Use: "gen",
- Short: "A collection of several useful generators.",
- })
-
- cc.cmd.AddCommand(
- newGenautocompleteCmd().getCommand(),
- newGenDocCmd().getCommand(),
- newGenManCmd().getCommand(),
- createGenDocsHelper().getCommand(),
- createGenChromaStyles().getCommand())
-
- return cc
+func (c *genCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
+ c.rootCmd = cd.Root.Command.(*rootCommand)
+ return nil
}
diff --git a/commands/genautocomplete.go b/commands/genautocomplete.go
deleted file mode 100644
index 5cdb7b688ae..00000000000
--- a/commands/genautocomplete.go
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright 2015 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 (
- "io"
- "os"
-
- "github.com/spf13/cobra"
- jww "github.com/spf13/jwalterweatherman"
-)
-
-var _ cmder = (*genautocompleteCmd)(nil)
-
-type genautocompleteCmd struct {
- autocompleteTarget string
-
- // bash, zsh, fish or powershell
- autocompleteType string
-
- *baseCmd
-}
-
-func newGenautocompleteCmd() *genautocompleteCmd {
- cc := &genautocompleteCmd{}
-
- cc.baseCmd = newBaseCmd(&cobra.Command{
- Use: "autocomplete",
- Short: "Generate shell autocompletion script for Hugo",
- Long: `Generates a shell autocompletion script for Hugo.
-
-The script is written to the console (stdout).
-
-To write to file, add the ` + "`--completionfile=/path/to/file`" + ` flag.
-
-Add ` + "`--type={bash, zsh, fish or powershell}`" + ` flag to set alternative
-shell type.
-
-Logout and in again to reload the completion scripts,
-or just source them in directly:
-
- $ . /etc/bash_completion or /path/to/file`,
-
- RunE: func(cmd *cobra.Command, args []string) error {
- var err error
- var target io.Writer
-
- if cc.autocompleteTarget == "" {
- target = os.Stdout
- } else {
- target, _ = os.OpenFile(cc.autocompleteTarget, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
- }
-
- switch cc.autocompleteType {
- case "bash":
- err = cmd.Root().GenBashCompletion(target)
- case "zsh":
- err = cmd.Root().GenZshCompletion(target)
- case "fish":
- err = cmd.Root().GenFishCompletion(target, true)
- case "powershell":
- err = cmd.Root().GenPowerShellCompletion(target)
- default:
- return newUserError("Unsupported completion type")
- }
-
- if err != nil {
- return err
- }
-
- if cc.autocompleteTarget != "" {
- jww.FEEDBACK.Println(cc.autocompleteType+" completion file for Hugo saved to", cc.autocompleteTarget)
- }
- return nil
- },
- })
-
- cc.cmd.PersistentFlags().StringVarP(&cc.autocompleteTarget, "completionfile", "f", "", "autocompletion file, defaults to stdout")
- cc.cmd.PersistentFlags().StringVarP(&cc.autocompleteType, "type", "t", "bash", "autocompletion type (bash, zsh, fish, or powershell)")
-
- return cc
-}
diff --git a/commands/genchromastyles.go b/commands/genchromastyles.go
deleted file mode 100644
index a2476ffdff0..00000000000
--- a/commands/genchromastyles.go
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2017-present 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 (
- "os"
-
- "github.com/alecthomas/chroma"
- "github.com/alecthomas/chroma/formatters/html"
- "github.com/alecthomas/chroma/styles"
- "github.com/spf13/cobra"
-)
-
-var _ cmder = (*genChromaStyles)(nil)
-
-type genChromaStyles struct {
- style string
- highlightStyle string
- linesStyle string
- *baseCmd
-}
-
-// TODO(bep) highlight
-func createGenChromaStyles() *genChromaStyles {
- g := &genChromaStyles{
- baseCmd: newBaseCmd(&cobra.Command{
- Use: "chromastyles",
- Short: "Generate CSS stylesheet for the Chroma code highlighter",
- Long: `Generate CSS stylesheet for the Chroma code highlighter for a given style. This stylesheet is needed if markup.highlight.noClasses is disabled in config.
-
-See https://xyproto.github.io/splash/docs/all.html for a preview of the available styles`,
- }),
- }
-
- g.cmd.RunE = func(cmd *cobra.Command, args []string) error {
- return g.generate()
- }
-
- g.cmd.PersistentFlags().StringVar(&g.style, "style", "friendly", "highlighter style (see https://xyproto.github.io/splash/docs/)")
- g.cmd.PersistentFlags().StringVar(&g.highlightStyle, "highlightStyle", "bg:#ffffcc", "style used for highlighting lines (see https://github.com/alecthomas/chroma)")
- g.cmd.PersistentFlags().StringVar(&g.linesStyle, "linesStyle", "", "style used for line numbers (see https://github.com/alecthomas/chroma)")
-
- return g
-}
-
-func (g *genChromaStyles) generate() error {
- builder := styles.Get(g.style).Builder()
- if g.highlightStyle != "" {
- builder.Add(chroma.LineHighlight, g.highlightStyle)
- }
- if g.linesStyle != "" {
- builder.Add(chroma.LineNumbers, g.linesStyle)
- }
- style, err := builder.Build()
- if err != nil {
- return err
- }
- formatter := html.New(html.WithAllClasses(true))
- formatter.WriteCSS(os.Stdout, style)
- return nil
-}
diff --git a/commands/gendoc.go b/commands/gendoc.go
deleted file mode 100644
index 6d03aabcf09..00000000000
--- a/commands/gendoc.go
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright 2016 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 (
- "fmt"
- "path"
- "path/filepath"
- "strings"
-
- "github.com/gohugoio/hugo/helpers"
- "github.com/gohugoio/hugo/hugofs"
- "github.com/spf13/cobra"
- "github.com/spf13/cobra/doc"
- jww "github.com/spf13/jwalterweatherman"
-)
-
-var _ cmder = (*genDocCmd)(nil)
-
-type genDocCmd struct {
- gendocdir string
- *baseCmd
-}
-
-func newGenDocCmd() *genDocCmd {
- const gendocFrontmatterTemplate = `---
-title: "%s"
-slug: %s
-url: %s
----
-`
-
- cc := &genDocCmd{}
-
- cc.baseCmd = newBaseCmd(&cobra.Command{
- Use: "doc",
- Short: "Generate Markdown documentation for the Hugo CLI.",
- Long: `Generate Markdown documentation for the Hugo CLI.
-
-This command is, mostly, used to create up-to-date documentation
-of Hugo's command-line interface for http://gohugo.io/.
-
-It creates one Markdown file per command with front matter suitable
-for rendering in Hugo.`,
-
- RunE: func(cmd *cobra.Command, args []string) error {
- cmd.VisitParents(func(c *cobra.Command) {
- // Disable the "Auto generated by spf13/cobra on DATE"
- // as it creates a lot of diffs.
- c.DisableAutoGenTag = true
- })
-
- if !strings.HasSuffix(cc.gendocdir, helpers.FilePathSeparator) {
- cc.gendocdir += helpers.FilePathSeparator
- }
- if found, _ := helpers.Exists(cc.gendocdir, hugofs.Os); !found {
- jww.FEEDBACK.Println("Directory", cc.gendocdir, "does not exist, creating...")
- if err := hugofs.Os.MkdirAll(cc.gendocdir, 0777); err != nil {
- return err
- }
- }
- prepender := func(filename string) string {
- name := filepath.Base(filename)
- base := strings.TrimSuffix(name, path.Ext(name))
- url := "/commands/" + strings.ToLower(base) + "/"
- return fmt.Sprintf(gendocFrontmatterTemplate, strings.Replace(base, "_", " ", -1), base, url)
- }
-
- linkHandler := func(name string) string {
- base := strings.TrimSuffix(name, path.Ext(name))
- return "/commands/" + strings.ToLower(base) + "/"
- }
- jww.FEEDBACK.Println("Generating Hugo command-line documentation in", cc.gendocdir, "...")
- doc.GenMarkdownTreeCustom(cmd.Root(), cc.gendocdir, prepender, linkHandler)
- jww.FEEDBACK.Println("Done.")
-
- return nil
- },
- })
-
- cc.cmd.PersistentFlags().StringVar(&cc.gendocdir, "dir", "/tmp/hugodoc/", "the directory to write the doc.")
-
- // For bash-completion
- cc.cmd.PersistentFlags().SetAnnotation("dir", cobra.BashCompSubdirsInDir, []string{})
-
- return cc
-}
diff --git a/commands/gendocshelper.go b/commands/gendocshelper.go
deleted file mode 100644
index 34d45154fab..00000000000
--- a/commands/gendocshelper.go
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2017-present 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 (
- "encoding/json"
- "fmt"
- "os"
- "path/filepath"
-
- "github.com/gohugoio/hugo/docshelper"
- "github.com/spf13/cobra"
-)
-
-var _ cmder = (*genDocsHelper)(nil)
-
-type genDocsHelper struct {
- target string
- *baseCmd
-}
-
-func createGenDocsHelper() *genDocsHelper {
- g := &genDocsHelper{
- baseCmd: newBaseCmd(&cobra.Command{
- Use: "docshelper",
- Short: "Generate some data files for the Hugo docs.",
- Hidden: true,
- }),
- }
-
- g.cmd.RunE = func(cmd *cobra.Command, args []string) error {
- return g.generate()
- }
-
- g.cmd.PersistentFlags().StringVarP(&g.target, "dir", "", "docs/data", "data dir")
-
- return g
-}
-
-func (g *genDocsHelper) generate() error {
- fmt.Println("Generate docs data to", g.target)
-
- targetFile := filepath.Join(g.target, "docs.json")
-
- f, err := os.Create(targetFile)
- if err != nil {
- return err
- }
- defer f.Close()
-
- enc := json.NewEncoder(f)
- enc.SetIndent("", " ")
-
- if err := enc.Encode(docshelper.GetDocProvider()); err != nil {
- return err
- }
-
- fmt.Println("Done!")
- return nil
-}
diff --git a/commands/genman.go b/commands/genman.go
deleted file mode 100644
index 7200462891c..00000000000
--- a/commands/genman.go
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2016 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 (
- "fmt"
- "strings"
-
- "github.com/gohugoio/hugo/common/hugo"
- "github.com/gohugoio/hugo/helpers"
- "github.com/gohugoio/hugo/hugofs"
- "github.com/spf13/cobra"
- "github.com/spf13/cobra/doc"
- jww "github.com/spf13/jwalterweatherman"
-)
-
-var _ cmder = (*genManCmd)(nil)
-
-type genManCmd struct {
- genmandir string
- *baseCmd
-}
-
-func newGenManCmd() *genManCmd {
- cc := &genManCmd{}
-
- cc.baseCmd = newBaseCmd(&cobra.Command{
- Use: "man",
- Short: "Generate man pages for the Hugo CLI",
- Long: `This command automatically generates up-to-date man pages of Hugo's
-command-line interface. By default, it creates the man page files
-in the "man" directory under the current directory.`,
-
- RunE: func(cmd *cobra.Command, args []string) error {
- header := &doc.GenManHeader{
- Section: "1",
- Manual: "Hugo Manual",
- Source: fmt.Sprintf("Hugo %s", hugo.CurrentVersion),
- }
- if !strings.HasSuffix(cc.genmandir, helpers.FilePathSeparator) {
- cc.genmandir += helpers.FilePathSeparator
- }
- if found, _ := helpers.Exists(cc.genmandir, hugofs.Os); !found {
- jww.FEEDBACK.Println("Directory", cc.genmandir, "does not exist, creating...")
- if err := hugofs.Os.MkdirAll(cc.genmandir, 0777); err != nil {
- return err
- }
- }
- cmd.Root().DisableAutoGenTag = true
-
- jww.FEEDBACK.Println("Generating Hugo man pages in", cc.genmandir, "...")
- doc.GenManTree(cmd.Root(), header, cc.genmandir)
-
- jww.FEEDBACK.Println("Done.")
-
- return nil
- },
- })
-
- cc.cmd.PersistentFlags().StringVar(&cc.genmandir, "dir", "man/", "the directory to write the man pages.")
-
- // For bash-completion
- cc.cmd.PersistentFlags().SetAnnotation("dir", cobra.BashCompSubdirsInDir, []string{})
-
- return cc
-}
diff --git a/commands/helpers.go b/commands/helpers.go
index 1386e425fe6..fbe1d362a7d 100644
--- a/commands/helpers.go
+++ b/commands/helpers.go
@@ -1,4 +1,4 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
+// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -11,16 +11,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// Package commands defines and implements command-line commands and flags
-// used by Hugo. Commands and flags are implemented using Cobra.
package commands
import (
+ "errors"
"fmt"
- "regexp"
+ "log"
+ "os"
+ "path/filepath"
+ "strings"
+ "github.com/bep/simplecobra"
"github.com/gohugoio/hugo/config"
- "github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
const (
@@ -30,50 +33,89 @@ const (
showCursor = ansiEsc + "[?25h"
)
-type flagsToConfigHandler interface {
- flagsToConfig(cfg config.Provider)
+func newUserError(a ...any) *simplecobra.CommandError {
+ return &simplecobra.CommandError{Err: errors.New(fmt.Sprint(a...))}
}
-type cmder interface {
- flagsToConfigHandler
- getCommand() *cobra.Command
-}
+func setValueFromFlag(flags *pflag.FlagSet, key string, cfg config.Provider, targetKey string, force bool) {
+ key = strings.TrimSpace(key)
+ if (force && flags.Lookup(key) != nil) || flags.Changed(key) {
+ f := flags.Lookup(key)
+ configKey := key
+ if targetKey != "" {
+ configKey = targetKey
+ }
+ // Gotta love this API.
+ switch f.Value.Type() {
+ case "bool":
+ bv, _ := flags.GetBool(key)
+ cfg.Set(configKey, bv)
+ case "string":
+ cfg.Set(configKey, f.Value.String())
+ case "stringSlice":
+ bv, _ := flags.GetStringSlice(key)
+ cfg.Set(configKey, bv)
+ case "int":
+ iv, _ := flags.GetInt(key)
+ cfg.Set(configKey, iv)
+ default:
+ panic(fmt.Sprintf("update switch with %s", f.Value.Type()))
+ }
-// commandError is an error used to signal different error situations in command handling.
-type commandError struct {
- s string
- userError bool
+ }
}
-func (c commandError) Error() string {
- return c.s
+func flagsToCfg(cd *simplecobra.Commandeer, cfg config.Provider) config.Provider {
+ return flagsToCfgWithAdditionalConfigBase(cd, cfg, "")
}
-func (c commandError) isUserError() bool {
- return c.userError
-}
+func flagsToCfgWithAdditionalConfigBase(cd *simplecobra.Commandeer, cfg config.Provider, additionalConfigBase string) config.Provider {
+ if cfg == nil {
+ cfg = config.New()
+ }
-func newUserError(a ...interface{}) commandError {
- return commandError{s: fmt.Sprintln(a...), userError: true}
-}
+ // Flags with a different name in the config.
+ keyMap := map[string]string{
+ "minify": "minify.minifyOutput",
+ "destination": "publishDir",
+ "editor": "newContentEditor",
+ }
-func newSystemError(a ...interface{}) commandError {
- return commandError{s: fmt.Sprintln(a...), userError: false}
-}
+ // Flags that we for some reason don't want to expose in the project config.
+ internalKeySet := map[string]bool{
+ "quiet": true,
+ "verbose": true,
+ "watch": true,
+ "liveReloadPort": true,
+ "renderToMemory": true,
+ "clock": true,
+ }
-func newSystemErrorF(format string, a ...interface{}) commandError {
- return commandError{s: fmt.Sprintf(format, a...), userError: false}
-}
+ cmd := cd.CobraCommand
+ flags := cmd.Flags()
-// Catch some of the obvious user errors from Cobra.
-// We don't want to show the usage message for every error.
-// The below may be to generic. Time will show.
-var userErrorRegexp = regexp.MustCompile("argument|flag|shorthand")
+ flags.VisitAll(func(f *pflag.Flag) {
+ if f.Changed {
+ targetKey := f.Name
+ if internalKeySet[targetKey] {
+ targetKey = "internal." + targetKey
+ } else if mapped, ok := keyMap[targetKey]; ok {
+ targetKey = mapped
+ }
+ setValueFromFlag(flags, f.Name, cfg, targetKey, false)
+ if additionalConfigBase != "" {
+ setValueFromFlag(flags, f.Name, cfg, additionalConfigBase+"."+targetKey, true)
+ }
+ }
+ })
-func isUserError(err error) bool {
- if cErr, ok := err.(commandError); ok && cErr.isUserError() {
- return true
- }
+ return cfg
+}
- return userErrorRegexp.MatchString(err.Error())
+func mkdir(x ...string) {
+ p := filepath.Join(x...)
+ err := os.MkdirAll(p, 0o777) // before umask
+ if err != nil {
+ log.Fatal(err)
+ }
}
diff --git a/commands/hugo.go b/commands/hugo.go
deleted file mode 100644
index d442a62b504..00000000000
--- a/commands/hugo.go
+++ /dev/null
@@ -1,1239 +0,0 @@
-// Copyright 2019 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 defines and implements command-line commands and flags
-// used by Hugo. Commands and flags are implemented using Cobra.
-package commands
-
-import (
- "context"
- "fmt"
- "io/ioutil"
- "os"
- "os/signal"
- "path/filepath"
- "runtime"
- "runtime/pprof"
- "runtime/trace"
- "strings"
- "sync/atomic"
- "syscall"
- "time"
-
- "github.com/gohugoio/hugo/hugofs/files"
-
- "github.com/gohugoio/hugo/common/types"
-
- "github.com/gohugoio/hugo/hugofs"
-
- "github.com/gohugoio/hugo/resources/page"
-
- "github.com/pkg/errors"
-
- "github.com/gohugoio/hugo/common/herrors"
- "github.com/gohugoio/hugo/common/hugo"
- "github.com/gohugoio/hugo/common/loggers"
- "github.com/gohugoio/hugo/common/terminal"
-
- "github.com/gohugoio/hugo/hugolib/filesystems"
-
- "golang.org/x/sync/errgroup"
-
- "github.com/gohugoio/hugo/config"
-
- flag "github.com/spf13/pflag"
-
- "github.com/fsnotify/fsnotify"
- "github.com/gohugoio/hugo/helpers"
- "github.com/gohugoio/hugo/hugolib"
- "github.com/gohugoio/hugo/livereload"
- "github.com/gohugoio/hugo/watcher"
- "github.com/spf13/afero"
- "github.com/spf13/cobra"
- "github.com/spf13/fsync"
- jww "github.com/spf13/jwalterweatherman"
-)
-
-// The Response value from Execute.
-type Response struct {
- // The build Result will only be set in the hugo build command.
- Result *hugolib.HugoSites
-
- // Err is set when the command failed to execute.
- Err error
-
- // The command that was executed.
- Cmd *cobra.Command
-}
-
-// IsUserError returns true is the Response error is a user error rather than a
-// system error.
-func (r Response) IsUserError() bool {
- return r.Err != nil && isUserError(r.Err)
-}
-
-// Execute adds all child commands to the root command HugoCmd and sets flags appropriately.
-// The args are usually filled with os.Args[1:].
-func Execute(args []string) Response {
- hugoCmd := newCommandsBuilder().addAll().build()
- cmd := hugoCmd.getCommand()
- cmd.SetArgs(args)
-
- c, err := cmd.ExecuteC()
-
- var resp Response
-
- if c == cmd && hugoCmd.c != nil {
- // Root command executed
- resp.Result = hugoCmd.c.hugo()
- }
-
- if err == nil {
- errCount := int(loggers.GlobalErrorCounter.Count())
- if errCount > 0 {
- err = fmt.Errorf("logged %d errors", errCount)
- } else if resp.Result != nil {
- errCount = resp.Result.NumLogErrors()
- if errCount > 0 {
- err = fmt.Errorf("logged %d errors", errCount)
- }
- }
-
- }
-
- resp.Err = err
- resp.Cmd = c
-
- return resp
-}
-
-// InitializeConfig initializes a config file with sensible default configuration flags.
-func initializeConfig(mustHaveConfigFile, failOnInitErr, running bool,
- h *hugoBuilderCommon,
- f flagsToConfigHandler,
- cfgInit func(c *commandeer) error) (*commandeer, error) {
- c, err := newCommandeer(mustHaveConfigFile, failOnInitErr, running, h, f, cfgInit)
- if err != nil {
- return nil, err
- }
-
- return c, nil
-}
-
-func (c *commandeer) createLogger(cfg config.Provider) (loggers.Logger, error) {
- var (
- logHandle = ioutil.Discard
- logThreshold = jww.LevelWarn
- logFile = cfg.GetString("logFile")
- outHandle = ioutil.Discard
- stdoutThreshold = jww.LevelWarn
- )
-
- if !c.h.quiet {
- outHandle = os.Stdout
- }
-
- if c.h.verboseLog || c.h.logging || (c.h.logFile != "") {
- var err error
- if logFile != "" {
- logHandle, err = os.OpenFile(logFile, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
- if err != nil {
- return nil, newSystemError("Failed to open log file:", logFile, err)
- }
- } else {
- logHandle, err = ioutil.TempFile("", "hugo")
- if err != nil {
- return nil, newSystemError(err)
- }
- }
- } else if !c.h.quiet && cfg.GetBool("verbose") {
- stdoutThreshold = jww.LevelInfo
- }
-
- if cfg.GetBool("debug") {
- stdoutThreshold = jww.LevelDebug
- }
-
- if c.h.verboseLog {
- logThreshold = jww.LevelInfo
- if cfg.GetBool("debug") {
- logThreshold = jww.LevelDebug
- }
- }
-
- loggers.InitGlobalLogger(stdoutThreshold, logThreshold, outHandle, logHandle)
- helpers.InitLoggers()
-
- return loggers.NewLogger(stdoutThreshold, logThreshold, outHandle, logHandle, c.running), nil
-}
-
-func initializeFlags(cmd *cobra.Command, cfg config.Provider) {
- persFlagKeys := []string{
- "debug",
- "verbose",
- "logFile",
- // Moved from vars
- }
- flagKeys := []string{
- "cleanDestinationDir",
- "buildDrafts",
- "buildFuture",
- "buildExpired",
- "uglyURLs",
- "canonifyURLs",
- "enableRobotsTXT",
- "enableGitInfo",
- "pluralizeListTitles",
- "preserveTaxonomyNames",
- "ignoreCache",
- "forceSyncStatic",
- "noTimes",
- "noChmod",
- "ignoreVendor",
- "ignoreVendorPaths",
- "templateMetrics",
- "templateMetricsHints",
-
- // Moved from vars.
- "baseURL",
- "buildWatch",
- "cacheDir",
- "cfgFile",
- "confirm",
- "contentDir",
- "debug",
- "destination",
- "disableKinds",
- "dryRun",
- "force",
- "gc",
- "i18n-warnings",
- "invalidateCDN",
- "layoutDir",
- "logFile",
- "maxDeletes",
- "quiet",
- "renderToMemory",
- "source",
- "target",
- "theme",
- "themesDir",
- "verbose",
- "verboseLog",
- "duplicateTargetPaths",
- }
-
- for _, key := range persFlagKeys {
- setValueFromFlag(cmd.PersistentFlags(), key, cfg, "", false)
- }
- for _, key := range flagKeys {
- setValueFromFlag(cmd.Flags(), key, cfg, "", false)
- }
-
- setValueFromFlag(cmd.Flags(), "minify", cfg, "minifyOutput", true)
-
- // Set some "config aliases"
- setValueFromFlag(cmd.Flags(), "destination", cfg, "publishDir", false)
- setValueFromFlag(cmd.Flags(), "i18n-warnings", cfg, "logI18nWarnings", false)
- setValueFromFlag(cmd.Flags(), "path-warnings", cfg, "logPathWarnings", false)
-}
-
-func setValueFromFlag(flags *flag.FlagSet, key string, cfg config.Provider, targetKey string, force bool) {
- key = strings.TrimSpace(key)
- if (force && flags.Lookup(key) != nil) || flags.Changed(key) {
- f := flags.Lookup(key)
- configKey := key
- if targetKey != "" {
- configKey = targetKey
- }
- // Gotta love this API.
- switch f.Value.Type() {
- case "bool":
- bv, _ := flags.GetBool(key)
- cfg.Set(configKey, bv)
- case "string":
- cfg.Set(configKey, f.Value.String())
- case "stringSlice":
- bv, _ := flags.GetStringSlice(key)
- cfg.Set(configKey, bv)
- case "int":
- iv, _ := flags.GetInt(key)
- cfg.Set(configKey, iv)
- default:
- panic(fmt.Sprintf("update switch with %s", f.Value.Type()))
- }
-
- }
-}
-
-func isTerminal() bool {
- return terminal.IsTerminal(os.Stdout)
-}
-
-func (c *commandeer) fullBuild(noBuildLock bool) error {
-
- var (
- g errgroup.Group
- langCount map[string]uint64
- )
-
- if !c.h.quiet {
- fmt.Println("Start building sites … ")
- fmt.Println(hugo.BuildVersionString())
- if isTerminal() {
- defer func() {
- fmt.Print(showCursor + clearLine)
- }()
- }
- }
-
- copyStaticFunc := func() error {
- cnt, err := c.copyStatic()
- if err != nil {
- return errors.Wrap(err, "Error copying static files")
- }
- langCount = cnt
- return nil
- }
- buildSitesFunc := func() error {
- if err := c.buildSites(noBuildLock); err != nil {
- return errors.Wrap(err, "Error building site")
- }
- return nil
- }
- // Do not copy static files and build sites in parallel if cleanDestinationDir is enabled.
- // This flag deletes all static resources in /public folder that are missing in /static,
- // and it does so at the end of copyStatic() call.
- if c.Cfg.GetBool("cleanDestinationDir") {
- if err := copyStaticFunc(); err != nil {
- return err
- }
- if err := buildSitesFunc(); err != nil {
- return err
- }
- } else {
- g.Go(copyStaticFunc)
- g.Go(buildSitesFunc)
- if err := g.Wait(); err != nil {
- return err
- }
- }
-
- for _, s := range c.hugo().Sites {
- s.ProcessingStats.Static = langCount[s.Language().Lang]
- }
-
- if c.h.gc {
- count, err := c.hugo().GC()
- if err != nil {
- return err
- }
- for _, s := range c.hugo().Sites {
- // We have no way of knowing what site the garbage belonged to.
- s.ProcessingStats.Cleaned = uint64(count)
- }
- }
-
- return nil
-}
-
-func (c *commandeer) initCPUProfile() (func(), error) {
- if c.h.cpuprofile == "" {
- return nil, nil
- }
-
- f, err := os.Create(c.h.cpuprofile)
- if err != nil {
- return nil, errors.Wrap(err, "failed to create CPU profile")
- }
- if err := pprof.StartCPUProfile(f); err != nil {
- return nil, errors.Wrap(err, "failed to start CPU profile")
- }
- return func() {
- pprof.StopCPUProfile()
- f.Close()
- }, nil
-}
-
-func (c *commandeer) initMemProfile() {
- if c.h.memprofile == "" {
- return
- }
-
- f, err := os.Create(c.h.memprofile)
- if err != nil {
- c.logger.Errorf("could not create memory profile: ", err)
- }
- defer f.Close()
- runtime.GC() // get up-to-date statistics
- if err := pprof.WriteHeapProfile(f); err != nil {
- c.logger.Errorf("could not write memory profile: ", err)
- }
-}
-
-func (c *commandeer) initTraceProfile() (func(), error) {
- if c.h.traceprofile == "" {
- return nil, nil
- }
-
- f, err := os.Create(c.h.traceprofile)
- if err != nil {
- return nil, errors.Wrap(err, "failed to create trace file")
- }
-
- if err := trace.Start(f); err != nil {
- return nil, errors.Wrap(err, "failed to start trace")
- }
-
- return func() {
- trace.Stop()
- f.Close()
- }, nil
-}
-
-func (c *commandeer) initMutexProfile() (func(), error) {
- if c.h.mutexprofile == "" {
- return nil, nil
- }
-
- f, err := os.Create(c.h.mutexprofile)
- if err != nil {
- return nil, err
- }
-
- runtime.SetMutexProfileFraction(1)
-
- return func() {
- pprof.Lookup("mutex").WriteTo(f, 0)
- f.Close()
- }, nil
-}
-
-func (c *commandeer) initMemTicker() func() {
- memticker := time.NewTicker(5 * time.Second)
- quit := make(chan struct{})
- printMem := func() {
- var m runtime.MemStats
- runtime.ReadMemStats(&m)
- fmt.Printf("\n\nAlloc = %v\nTotalAlloc = %v\nSys = %v\nNumGC = %v\n\n", formatByteCount(m.Alloc), formatByteCount(m.TotalAlloc), formatByteCount(m.Sys), m.NumGC)
- }
-
- go func() {
- for {
- select {
- case <-memticker.C:
- printMem()
- case <-quit:
- memticker.Stop()
- printMem()
- return
- }
- }
- }()
-
- return func() {
- close(quit)
- }
-}
-
-func (c *commandeer) initProfiling() (func(), error) {
- stopCPUProf, err := c.initCPUProfile()
- if err != nil {
- return nil, err
- }
-
- stopMutexProf, err := c.initMutexProfile()
- if err != nil {
- return nil, err
- }
-
- stopTraceProf, err := c.initTraceProfile()
- if err != nil {
- return nil, err
- }
-
- var stopMemTicker func()
- if c.h.printm {
- stopMemTicker = c.initMemTicker()
- }
-
- return func() {
- c.initMemProfile()
-
- if stopCPUProf != nil {
- stopCPUProf()
- }
- if stopMutexProf != nil {
- stopMutexProf()
- }
-
- if stopTraceProf != nil {
- stopTraceProf()
- }
-
- if stopMemTicker != nil {
- stopMemTicker()
- }
- }, nil
-}
-
-func (c *commandeer) build() error {
- stopProfiling, err := c.initProfiling()
- if err != nil {
- return err
- }
-
- defer func() {
- if stopProfiling != nil {
- stopProfiling()
- }
- }()
-
- if err := c.fullBuild(false); err != nil {
- return err
- }
-
- // TODO(bep) Feedback?
- if !c.h.quiet {
- fmt.Println()
- c.hugo().PrintProcessingStats(os.Stdout)
- fmt.Println()
-
- if createCounter, ok := c.destinationFs.(hugofs.DuplicatesReporter); ok {
- dupes := createCounter.ReportDuplicates()
- if dupes != "" {
- c.logger.Warnln("Duplicate target paths:", dupes)
- }
- }
- }
-
- if c.h.buildWatch {
- watchDirs, err := c.getDirList()
- if err != nil {
- return err
- }
-
- baseWatchDir := c.Cfg.GetString("workingDir")
- rootWatchDirs := getRootWatchDirsStr(baseWatchDir, watchDirs)
-
- c.logger.Printf("Watching for changes in %s%s{%s}\n", baseWatchDir, helpers.FilePathSeparator, rootWatchDirs)
- c.logger.Println("Press Ctrl+C to stop")
- watcher, err := c.newWatcher(c.h.poll, watchDirs...)
- checkErr(c.Logger, err)
- defer watcher.Close()
-
- sigs := make(chan os.Signal, 1)
- signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
-
- <-sigs
- }
-
- return nil
-}
-
-func (c *commandeer) serverBuild() error {
-
- stopProfiling, err := c.initProfiling()
- if err != nil {
- return err
- }
-
- defer func() {
- if stopProfiling != nil {
- stopProfiling()
- }
- }()
-
- if err := c.fullBuild(false); err != nil {
- return err
- }
-
- // TODO(bep) Feedback?
- if !c.h.quiet {
- fmt.Println()
- c.hugo().PrintProcessingStats(os.Stdout)
- fmt.Println()
- }
-
- return nil
-}
-
-func (c *commandeer) copyStatic() (map[string]uint64, error) {
- m, err := c.doWithPublishDirs(c.copyStaticTo)
- if err == nil || os.IsNotExist(err) {
- return m, nil
- }
- return m, err
-}
-
-func (c *commandeer) doWithPublishDirs(f func(sourceFs *filesystems.SourceFilesystem) (uint64, error)) (map[string]uint64, error) {
- langCount := make(map[string]uint64)
-
- staticFilesystems := c.hugo().BaseFs.SourceFilesystems.Static
-
- if len(staticFilesystems) == 0 {
- c.logger.Infoln("No static directories found to sync")
- return langCount, nil
- }
-
- for lang, fs := range staticFilesystems {
- cnt, err := f(fs)
- if err != nil {
- return langCount, err
- }
-
- if lang == "" {
- // Not multihost
- for _, l := range c.languages {
- langCount[l.Lang] = cnt
- }
- } else {
- langCount[lang] = cnt
- }
- }
-
- return langCount, nil
-}
-
-type countingStatFs struct {
- afero.Fs
- statCounter uint64
-}
-
-func (fs *countingStatFs) Stat(name string) (os.FileInfo, error) {
- f, err := fs.Fs.Stat(name)
- if err == nil {
- if !f.IsDir() {
- atomic.AddUint64(&fs.statCounter, 1)
- }
- }
- return f, err
-}
-
-func chmodFilter(dst, src os.FileInfo) bool {
- // Hugo publishes data from multiple sources, potentially
- // with overlapping directory structures. We cannot sync permissions
- // for directories as that would mean that we might end up with write-protected
- // directories inside /public.
- // One example of this would be syncing from the Go Module cache,
- // which have 0555 directories.
- return src.IsDir()
-}
-
-func (c *commandeer) copyStaticTo(sourceFs *filesystems.SourceFilesystem) (uint64, error) {
- publishDir := c.hugo().PathSpec.PublishDir
- // If root, remove the second '/'
- if publishDir == "//" {
- publishDir = helpers.FilePathSeparator
- }
-
- if sourceFs.PublishFolder != "" {
- publishDir = filepath.Join(publishDir, sourceFs.PublishFolder)
- }
-
- fs := &countingStatFs{Fs: sourceFs.Fs}
-
- syncer := fsync.NewSyncer()
- syncer.NoTimes = c.Cfg.GetBool("noTimes")
- syncer.NoChmod = c.Cfg.GetBool("noChmod")
- syncer.ChmodFilter = chmodFilter
- syncer.SrcFs = fs
- syncer.DestFs = c.Fs.Destination
- // Now that we are using a unionFs for the static directories
- // We can effectively clean the publishDir on initial sync
- syncer.Delete = c.Cfg.GetBool("cleanDestinationDir")
-
- if syncer.Delete {
- c.logger.Infoln("removing all files from destination that don't exist in static dirs")
-
- syncer.DeleteFilter = func(f os.FileInfo) bool {
- return f.IsDir() && strings.HasPrefix(f.Name(), ".")
- }
- }
- c.logger.Infoln("syncing static files to", publishDir)
-
- // because we are using a baseFs (to get the union right).
- // set sync src to root
- err := syncer.Sync(publishDir, helpers.FilePathSeparator)
- if err != nil {
- return 0, err
- }
-
- // Sync runs Stat 3 times for every source file (which sounds much)
- numFiles := fs.statCounter / 3
-
- return numFiles, err
-}
-
-func (c *commandeer) firstPathSpec() *helpers.PathSpec {
- return c.hugo().Sites[0].PathSpec
-}
-
-func (c *commandeer) timeTrack(start time.Time, name string) {
- elapsed := time.Since(start)
- c.logger.Printf("%s in %v ms", name, int(1000*elapsed.Seconds()))
-}
-
-// getDirList provides NewWatcher() with a list of directories to watch for changes.
-func (c *commandeer) getDirList() ([]string, error) {
- var filenames []string
-
- walkFn := func(path string, fi hugofs.FileMetaInfo, err error) error {
- if err != nil {
- c.logger.Errorln("walker: ", err)
- return nil
- }
-
- if fi.IsDir() {
- if fi.Name() == ".git" ||
- fi.Name() == "node_modules" || fi.Name() == "bower_components" {
- return filepath.SkipDir
- }
-
- filenames = append(filenames, fi.Meta().Filename)
- }
-
- return nil
- }
-
- watchFiles := c.hugo().PathSpec.BaseFs.WatchDirs()
- for _, fi := range watchFiles {
- if !fi.IsDir() {
- filenames = append(filenames, fi.Meta().Filename)
- continue
- }
-
- w := hugofs.NewWalkway(hugofs.WalkwayConfig{Logger: c.logger, Info: fi, WalkFn: walkFn})
- if err := w.Walk(); err != nil {
- c.logger.Errorln("walker: ", err)
- }
- }
-
- filenames = helpers.UniqueStringsSorted(filenames)
-
- return filenames, nil
-}
-
-func (c *commandeer) buildSites(noBuildLock bool) (err error) {
- return c.hugo().Build(hugolib.BuildCfg{NoBuildLock: noBuildLock})
-}
-
-func (c *commandeer) handleBuildErr(err error, msg string) {
- c.buildErr = err
-
- c.logger.Errorln(msg + ":\n")
- c.logger.Errorln(helpers.FirstUpper(err.Error()))
- if !c.h.quiet && c.h.verbose {
- herrors.PrintStackTraceFromErr(err)
- }
-}
-
-func (c *commandeer) rebuildSites(events []fsnotify.Event) error {
-
- c.buildErr = nil
- visited := c.visitedURLs.PeekAllSet()
- if c.fastRenderMode {
- // Make sure we always render the home pages
- for _, l := range c.languages {
- langPath := c.hugo().PathSpec.GetLangSubDir(l.Lang)
- if langPath != "" {
- langPath = langPath + "/"
- }
- home := c.hugo().PathSpec.PrependBasePath("/"+langPath, false)
- visited[home] = true
- }
- }
- return c.hugo().Build(hugolib.BuildCfg{NoBuildLock: true, RecentlyVisited: visited, ErrRecovery: c.wasError}, events...)
-}
-
-func (c *commandeer) partialReRender(urls ...string) error {
- defer func() {
- c.wasError = false
- }()
- c.buildErr = nil
- visited := make(map[string]bool)
- for _, url := range urls {
- visited[url] = true
- }
-
- // Note: We do not set NoBuildLock as the file lock is not acquired at this stage.
- return c.hugo().Build(hugolib.BuildCfg{NoBuildLock: false, RecentlyVisited: visited, PartialReRender: true, ErrRecovery: c.wasError})
-}
-
-func (c *commandeer) fullRebuild(changeType string) {
- if changeType == configChangeGoMod {
- // go.mod may be changed during the build itself, and
- // we really want to prevent superfluous builds.
- if !c.fullRebuildSem.TryAcquire(1) {
- return
- }
- c.fullRebuildSem.Release(1)
- }
-
- c.fullRebuildSem.Acquire(context.Background(), 1)
-
- go func() {
- defer c.fullRebuildSem.Release(1)
-
- c.printChangeDetected(changeType)
-
- defer func() {
- // Allow any file system events to arrive back.
- // This will block any rebuild on config changes for the
- // duration of the sleep.
- time.Sleep(2 * time.Second)
- }()
-
- defer c.timeTrack(time.Now(), "Rebuilt")
-
- c.commandeerHugoState = newCommandeerHugoState()
- err := c.loadConfig()
- if err != nil {
- // Set the processing on pause until the state is recovered.
- c.paused = true
- c.handleBuildErr(err, "Failed to reload config")
-
- } else {
- c.paused = false
- }
-
- if !c.paused {
- _, err := c.copyStatic()
- if err != nil {
- c.logger.Errorln(err)
- return
- }
-
- err = c.buildSites(true)
- if err != nil {
- c.logger.Errorln(err)
- } else if !c.h.buildWatch && !c.Cfg.GetBool("disableLiveReload") {
- livereload.ForceRefresh()
- }
- }
- }()
-}
-
-// newWatcher creates a new watcher to watch filesystem events.
-func (c *commandeer) newWatcher(pollIntervalStr string, dirList ...string) (*watcher.Batcher, error) {
- if runtime.GOOS == "darwin" {
- tweakLimit()
- }
-
- staticSyncer, err := newStaticSyncer(c)
- if err != nil {
- return nil, err
- }
-
- var pollInterval time.Duration
- poll := pollIntervalStr != ""
- if poll {
- pollInterval, err = types.ToDurationE(pollIntervalStr)
- if err != nil {
- return nil, fmt.Errorf("invalid value for flag poll: %s", err)
- }
- c.logger.Printf("Use watcher with poll interval %v", pollInterval)
- }
-
- if pollInterval == 0 {
- pollInterval = 500 * time.Millisecond
- }
-
- watcher, err := watcher.New(500*time.Millisecond, pollInterval, poll)
- if err != nil {
- return nil, err
- }
-
- for _, d := range dirList {
- if d != "" {
- _ = watcher.Add(d)
- }
- }
-
- // Identifies changes to config (config.toml) files.
- configSet := make(map[string]bool)
-
- c.logger.Println("Watching for config changes in", strings.Join(c.configFiles, ", "))
- for _, configFile := range c.configFiles {
- watcher.Add(configFile)
- configSet[configFile] = true
- }
-
- go func() {
- for {
- select {
- case evs := <-watcher.Events:
- unlock, err := c.hugo().BaseFs.LockBuild()
- if err != nil {
- c.logger.Errorln("Failed to acquire a build lock: %s", err)
- return
- }
- c.handleEvents(watcher, staticSyncer, evs, configSet)
- if c.showErrorInBrowser && c.errCount() > 0 {
- // Need to reload browser to show the error
- livereload.ForceRefresh()
- }
- unlock()
- case err := <-watcher.Errors():
- if err != nil && !os.IsNotExist(err) {
- c.logger.Errorln("Error while watching:", err)
- }
- }
- }
- }()
-
- return watcher, nil
-}
-
-func (c *commandeer) printChangeDetected(typ string) {
- msg := "\nChange"
- if typ != "" {
- msg += " of " + typ
- }
- msg += " detected, rebuilding site."
-
- c.logger.Println(msg)
- const layout = "2006-01-02 15:04:05.000 -0700"
- c.logger.Println(time.Now().Format(layout))
-}
-
-const (
- configChangeConfig = "config file"
- configChangeGoMod = "go.mod file"
-)
-
-func (c *commandeer) handleEvents(watcher *watcher.Batcher,
- staticSyncer *staticSyncer,
- evs []fsnotify.Event,
- configSet map[string]bool) {
- defer func() {
- c.wasError = false
- }()
-
- var isHandled bool
-
- for _, ev := range evs {
- isConfig := configSet[ev.Name]
- configChangeType := configChangeConfig
- if isConfig {
- if strings.Contains(ev.Name, "go.mod") {
- configChangeType = configChangeGoMod
- }
- }
- if !isConfig {
- // It may be one of the /config folders
- dirname := filepath.Dir(ev.Name)
- if dirname != "." && configSet[dirname] {
- isConfig = true
- }
- }
-
- if isConfig {
- isHandled = true
-
- if ev.Op&fsnotify.Chmod == fsnotify.Chmod {
- continue
- }
-
- if ev.Op&fsnotify.Remove == fsnotify.Remove || ev.Op&fsnotify.Rename == fsnotify.Rename {
- for _, configFile := range c.configFiles {
- counter := 0
- for watcher.Add(configFile) != nil {
- counter++
- if counter >= 100 {
- break
- }
- time.Sleep(100 * time.Millisecond)
- }
- }
- }
-
- // Config file(s) changed. Need full rebuild.
- c.fullRebuild(configChangeType)
-
- return
- }
- }
-
- if isHandled {
- return
- }
-
- if c.paused {
- // Wait for the server to get into a consistent state before
- // we continue with processing.
- return
- }
-
- if len(evs) > 50 {
- // This is probably a mass edit of the content dir.
- // Schedule a full rebuild for when it slows down.
- c.debounce(func() {
- c.fullRebuild("")
- })
- return
- }
-
- c.logger.Infoln("Received System Events:", evs)
-
- staticEvents := []fsnotify.Event{}
- dynamicEvents := []fsnotify.Event{}
-
- filtered := []fsnotify.Event{}
- for _, ev := range evs {
- if c.hugo().ShouldSkipFileChangeEvent(ev) {
- continue
- }
- // Check the most specific first, i.e. files.
- contentMapped := c.hugo().ContentChanges.GetSymbolicLinkMappings(ev.Name)
- if len(contentMapped) > 0 {
- for _, mapped := range contentMapped {
- filtered = append(filtered, fsnotify.Event{Name: mapped, Op: ev.Op})
- }
- continue
- }
-
- // Check for any symbolic directory mapping.
-
- dir, name := filepath.Split(ev.Name)
-
- contentMapped = c.hugo().ContentChanges.GetSymbolicLinkMappings(dir)
-
- if len(contentMapped) == 0 {
- filtered = append(filtered, ev)
- continue
- }
-
- for _, mapped := range contentMapped {
- mappedFilename := filepath.Join(mapped, name)
- filtered = append(filtered, fsnotify.Event{Name: mappedFilename, Op: ev.Op})
- }
- }
-
- evs = filtered
-
- for _, ev := range evs {
- ext := filepath.Ext(ev.Name)
- baseName := filepath.Base(ev.Name)
- istemp := strings.HasSuffix(ext, "~") ||
- (ext == ".swp") || // vim
- (ext == ".swx") || // vim
- (ext == ".tmp") || // generic temp file
- (ext == ".DS_Store") || // OSX Thumbnail
- baseName == "4913" || // vim
- strings.HasPrefix(ext, ".goutputstream") || // gnome
- strings.HasSuffix(ext, "jb_old___") || // intelliJ
- strings.HasSuffix(ext, "jb_tmp___") || // intelliJ
- strings.HasSuffix(ext, "jb_bak___") || // intelliJ
- strings.HasPrefix(ext, ".sb-") || // byword
- strings.HasPrefix(baseName, ".#") || // emacs
- strings.HasPrefix(baseName, "#") // emacs
- if istemp {
- continue
- }
- if c.hugo().Deps.SourceSpec.IgnoreFile(ev.Name) {
- continue
- }
- // Sometimes during rm -rf operations a '"": REMOVE' is triggered. Just ignore these
- if ev.Name == "" {
- continue
- }
-
- // Write and rename operations are often followed by CHMOD.
- // There may be valid use cases for rebuilding the site on CHMOD,
- // but that will require more complex logic than this simple conditional.
- // On OS X this seems to be related to Spotlight, see:
- // https://github.com/go-fsnotify/fsnotify/issues/15
- // A workaround is to put your site(s) on the Spotlight exception list,
- // but that may be a little mysterious for most end users.
- // So, for now, we skip reload on CHMOD.
- // We do have to check for WRITE though. On slower laptops a Chmod
- // could be aggregated with other important events, and we still want
- // to rebuild on those
- if ev.Op&(fsnotify.Chmod|fsnotify.Write|fsnotify.Create) == fsnotify.Chmod {
- continue
- }
-
- walkAdder := func(path string, f hugofs.FileMetaInfo, err error) error {
- if f.IsDir() {
- c.logger.Println("adding created directory to watchlist", path)
- if err := watcher.Add(path); err != nil {
- return err
- }
- } else if !staticSyncer.isStatic(path) {
- // Hugo's rebuilding logic is entirely file based. When you drop a new folder into
- // /content on OSX, the above logic will handle future watching of those files,
- // but the initial CREATE is lost.
- dynamicEvents = append(dynamicEvents, fsnotify.Event{Name: path, Op: fsnotify.Create})
- }
- return nil
- }
-
- // recursively add new directories to watch list
- // When mkdir -p is used, only the top directory triggers an event (at least on OSX)
- if ev.Op&fsnotify.Create == fsnotify.Create {
- if s, err := c.Fs.Source.Stat(ev.Name); err == nil && s.Mode().IsDir() {
- _ = helpers.SymbolicWalk(c.Fs.Source, ev.Name, walkAdder)
- }
- }
-
- if staticSyncer.isStatic(ev.Name) {
- staticEvents = append(staticEvents, ev)
- } else {
- dynamicEvents = append(dynamicEvents, ev)
- }
- }
-
- if len(staticEvents) > 0 {
- c.printChangeDetected("Static files")
-
- if c.Cfg.GetBool("forceSyncStatic") {
- c.logger.Printf("Syncing all static files\n")
- _, err := c.copyStatic()
- if err != nil {
- c.logger.Errorln("Error copying static files to publish dir:", err)
- return
- }
- } else {
- if err := staticSyncer.syncsStaticEvents(staticEvents); err != nil {
- c.logger.Errorln("Error syncing static files to publish dir:", err)
- return
- }
- }
-
- if !c.h.buildWatch && !c.Cfg.GetBool("disableLiveReload") {
- // 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.wasError && len(staticEvents) == 1 {
- ev := staticEvents[0]
- path := c.hugo().BaseFs.SourceFilesystems.MakeStaticPathRelative(ev.Name)
- path = c.firstPathSpec().RelURL(helpers.ToSlashTrimLeading(path), false)
-
- livereload.RefreshPath(path)
- } else {
- livereload.ForceRefresh()
- }
- }
- }
-
- if len(dynamicEvents) > 0 {
- partitionedEvents := partitionDynamicEvents(
- c.firstPathSpec().BaseFs.SourceFilesystems,
- dynamicEvents)
-
- doLiveReload := !c.h.buildWatch && !c.Cfg.GetBool("disableLiveReload")
- onePageName := pickOneWriteOrCreatePath(partitionedEvents.ContentEvents)
-
- c.printChangeDetected("")
- c.changeDetector.PrepareNew()
-
- func() {
- defer c.timeTrack(time.Now(), "Total")
- if err := c.rebuildSites(dynamicEvents); err != nil {
- c.handleBuildErr(err, "Rebuild failed")
- }
- }()
-
- if doLiveReload {
- if len(partitionedEvents.ContentEvents) == 0 && len(partitionedEvents.AssetEvents) > 0 {
- if c.wasError {
- livereload.ForceRefresh()
- return
- }
- changed := c.changeDetector.changed()
- if c.changeDetector != nil && len(changed) == 0 {
- // Nothing has changed.
- return
- } else if len(changed) == 1 {
- pathToRefresh := c.firstPathSpec().RelURL(helpers.ToSlashTrimLeading(changed[0]), false)
- livereload.RefreshPath(pathToRefresh)
- } else {
- livereload.ForceRefresh()
- }
- }
-
- if len(partitionedEvents.ContentEvents) > 0 {
-
- navigate := c.Cfg.GetBool("navigateToChanged")
- // We have fetched the same page above, but it may have
- // changed.
- var p page.Page
-
- if navigate {
- if onePageName != "" {
- p = c.hugo().GetContentPage(onePageName)
- }
- }
-
- if p != nil {
- livereload.NavigateToPathForPort(p.RelPermalink(), p.Site().ServerPort())
- } else {
- livereload.ForceRefresh()
- }
- }
- }
- }
-}
-
-// dynamicEvents contains events that is considered dynamic, as in "not static".
-// Both of these categories will trigger a new build, but the asset events
-// does not fit into the "navigate to changed" logic.
-type dynamicEvents struct {
- ContentEvents []fsnotify.Event
- AssetEvents []fsnotify.Event
-}
-
-func partitionDynamicEvents(sourceFs *filesystems.SourceFilesystems, events []fsnotify.Event) (de dynamicEvents) {
- for _, e := range events {
- if sourceFs.IsAsset(e.Name) {
- de.AssetEvents = append(de.AssetEvents, e)
- } else {
- de.ContentEvents = append(de.ContentEvents, e)
- }
- }
- return
-}
-
-func pickOneWriteOrCreatePath(events []fsnotify.Event) string {
- name := ""
-
- for _, ev := range events {
- if ev.Op&fsnotify.Write == fsnotify.Write || ev.Op&fsnotify.Create == fsnotify.Create {
- if files.IsIndexContentFile(ev.Name) {
- return ev.Name
- }
-
- if files.IsContentFile(ev.Name) {
- name = ev.Name
- }
-
- }
- }
-
- return name
-}
-
-func formatByteCount(b uint64) string {
- const unit = 1000
- if b < unit {
- return fmt.Sprintf("%d B", b)
- }
- div, exp := int64(unit), 0
- for n := b / unit; n >= unit; n /= unit {
- div *= unit
- exp++
- }
- return fmt.Sprintf("%.1f %cB",
- float64(b)/float64(div), "kMGTPE"[exp])
-}
diff --git a/commands/hugo_test.go b/commands/hugo_test.go
deleted file mode 100644
index 4bead09f0f6..00000000000
--- a/commands/hugo_test.go
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2019 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 (
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-// Issue #5662
-func TestHugoWithContentDirOverride(t *testing.T) {
- c := qt.New(t)
-
- hugoCmd := newCommandsBuilder().addAll().build()
- cmd := hugoCmd.getCommand()
-
- contentDir := "contentOverride"
-
- cfgStr := `
-
-baseURL = "https://example.org"
-title = "Hugo Commands"
-
-contentDir = "thisdoesnotexist"
-
-`
- dir, clean, err := createSimpleTestSite(t, testSiteConfig{configTOML: cfgStr, contentDir: contentDir})
- c.Assert(err, qt.IsNil)
- defer clean()
-
- cmd.SetArgs([]string{"-s=" + dir, "-c=" + contentDir})
-
- _, err = cmd.ExecuteC()
- c.Assert(err, qt.IsNil)
-}
diff --git a/commands/hugo_windows.go b/commands/hugo_windows.go
index 1724f12cd98..c354e889d42 100644
--- a/commands/hugo_windows.go
+++ b/commands/hugo_windows.go
@@ -1,4 +1,4 @@
-// Copyright 2015 The Hugo Authors. All rights reserved.
+// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -25,9 +25,9 @@ func init() {
// This message to show to Windows users if Hugo is opened from explorer.exe
cobra.MousetrapHelpText = `
- Hugo is a command-line tool for generating static website.
+ Hugo is a command-line tool for generating static websites.
+
+ You need to open PowerShell and run Hugo from there.
- You need to open cmd.exe and run Hugo from there.
-
Visit https://gohugo.io/ for more information.`
}
diff --git a/commands/hugobuilder.go b/commands/hugobuilder.go
new file mode 100644
index 00000000000..4037f66117d
--- /dev/null
+++ b/commands/hugobuilder.go
@@ -0,0 +1,1211 @@
+// 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 (
+ "context"
+ "errors"
+ "fmt"
+ "os"
+ "path/filepath"
+ "runtime"
+ "runtime/pprof"
+ "runtime/trace"
+ "strings"
+ "sync"
+ "sync/atomic"
+ "time"
+
+ "github.com/bep/debounce"
+ "github.com/bep/simplecobra"
+ "github.com/fsnotify/fsnotify"
+ "github.com/gohugoio/hugo/common/herrors"
+ "github.com/gohugoio/hugo/common/hmaps"
+ "github.com/gohugoio/hugo/common/hstrings"
+ "github.com/gohugoio/hugo/common/htime"
+ "github.com/gohugoio/hugo/common/hugo"
+ "github.com/gohugoio/hugo/common/loggers"
+ "github.com/gohugoio/hugo/common/paths"
+ "github.com/gohugoio/hugo/common/terminal"
+ "github.com/gohugoio/hugo/common/types"
+ "github.com/gohugoio/hugo/config"
+ "github.com/gohugoio/hugo/helpers"
+ "github.com/gohugoio/hugo/hugofs"
+ "github.com/gohugoio/hugo/hugolib"
+ "github.com/gohugoio/hugo/hugolib/filesystems"
+ "github.com/gohugoio/hugo/identity"
+ "github.com/gohugoio/hugo/livereload"
+ "github.com/gohugoio/hugo/resources/page"
+ "github.com/gohugoio/hugo/watcher"
+ "github.com/spf13/fsync"
+ "golang.org/x/sync/errgroup"
+ "golang.org/x/sync/semaphore"
+)
+
+type hugoBuilder struct {
+ r *rootCommand
+
+ confmu sync.Mutex
+ confOld *commonConfig
+ conf *commonConfig
+
+ // May be nil.
+ s *serverCommand
+
+ // Currently only set when in "fast render mode".
+ changeDetector *fileChangeDetector
+ visitedURLs *types.EvictingQueue[string]
+
+ fullRebuildSem *semaphore.Weighted
+ debounce func(f func())
+
+ onConfigLoaded func(reloaded bool) error
+
+ fastRenderMode bool
+ showErrorInBrowser bool
+
+ errState hugoBuilderErrState
+}
+
+var errConfigNotSet = errors.New("config not set")
+
+func (c *hugoBuilder) withConfE(fn func(conf *commonConfig) error) error {
+ c.confmu.Lock()
+ defer c.confmu.Unlock()
+ if c.conf == nil {
+ return errConfigNotSet
+ }
+ return fn(c.conf)
+}
+
+func (c *hugoBuilder) withConf(fn func(conf *commonConfig)) {
+ c.confmu.Lock()
+ defer c.confmu.Unlock()
+ fn(c.conf)
+}
+
+func (c *hugoBuilder) withConfOrOldConf(fn func(conf *commonConfig)) {
+ c.confmu.Lock()
+ defer c.confmu.Unlock()
+ if c.conf != nil {
+ fn(c.conf)
+ } else if c.confOld != nil {
+ fn(c.confOld)
+ }
+}
+
+func (c *hugoBuilder) withConfOrOldConfE(fn func(conf *commonConfig) error) error {
+ c.confmu.Lock()
+ defer c.confmu.Unlock()
+ if c.conf != nil {
+ return fn(c.conf)
+ } else if c.confOld != nil {
+ return fn(c.confOld)
+ }
+ return errConfigNotSet
+}
+
+type hugoBuilderErrState struct {
+ mu sync.Mutex
+ paused bool
+ builderr error
+ waserr bool
+}
+
+func (e *hugoBuilderErrState) setPaused(p bool) {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+ e.paused = p
+}
+
+func (e *hugoBuilderErrState) isPaused() bool {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+ return e.paused
+}
+
+func (e *hugoBuilderErrState) setBuildErr(err error) {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+ e.builderr = err
+}
+
+func (e *hugoBuilderErrState) buildErr() error {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+ return e.builderr
+}
+
+func (e *hugoBuilderErrState) setWasErr(w bool) {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+ e.waserr = w
+}
+
+func (e *hugoBuilderErrState) wasErr() bool {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+ return e.waserr
+}
+
+// getDirList provides NewWatcher() with a list of directories to watch for changes.
+func (c *hugoBuilder) getDirList() ([]string, error) {
+ h, err := c.hugo()
+ if err != nil {
+ return nil, err
+ }
+
+ return hstrings.UniqueStringsSorted(h.PathSpec.BaseFs.WatchFilenames()), nil
+}
+
+func (c *hugoBuilder) initCPUProfile() (func(), error) {
+ if c.r.cpuprofile == "" {
+ return nil, nil
+ }
+
+ f, err := os.Create(c.r.cpuprofile)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create CPU profile: %w", err)
+ }
+ if err := pprof.StartCPUProfile(f); err != nil {
+ f.Close()
+ return nil, fmt.Errorf("failed to start CPU profile: %w", err)
+ }
+ return func() {
+ pprof.StopCPUProfile()
+ f.Close()
+ }, nil
+}
+
+func (c *hugoBuilder) initMemProfile() {
+ if c.r.memprofile == "" {
+ return
+ }
+
+ f, err := os.Create(c.r.memprofile)
+ if err != nil {
+ c.r.logger.Errorf("could not create memory profile: ", err)
+ }
+ defer f.Close()
+ runtime.GC() // get up-to-date statistics
+ if err := pprof.WriteHeapProfile(f); err != nil {
+ c.r.logger.Errorf("could not write memory profile: ", err)
+ }
+}
+
+func (c *hugoBuilder) initMemTicker() func() {
+ memticker := time.NewTicker(5 * time.Second)
+ quit := make(chan struct{})
+ printMem := func() {
+ var m runtime.MemStats
+ runtime.ReadMemStats(&m)
+ fmt.Printf("\n\nAlloc = %v\nTotalAlloc = %v\nSys = %v\nNumGC = %v\n\n", formatByteCount(m.Alloc), formatByteCount(m.TotalAlloc), formatByteCount(m.Sys), m.NumGC)
+ }
+
+ go func() {
+ for {
+ select {
+ case <-memticker.C:
+ printMem()
+ case <-quit:
+ memticker.Stop()
+ printMem()
+ return
+ }
+ }
+ }()
+
+ return func() {
+ close(quit)
+ }
+}
+
+func (c *hugoBuilder) initMutexProfile() (func(), error) {
+ if c.r.mutexprofile == "" {
+ return nil, nil
+ }
+
+ f, err := os.Create(c.r.mutexprofile)
+ if err != nil {
+ return nil, err
+ }
+
+ runtime.SetMutexProfileFraction(1)
+
+ return func() {
+ pprof.Lookup("mutex").WriteTo(f, 0)
+ f.Close()
+ }, nil
+}
+
+func (c *hugoBuilder) initProfiling() (func(), error) {
+ stopCPUProf, err := c.initCPUProfile()
+ if err != nil {
+ return nil, err
+ }
+
+ stopMutexProf, err := c.initMutexProfile()
+ if err != nil {
+ return nil, err
+ }
+
+ stopTraceProf, err := c.initTraceProfile()
+ if err != nil {
+ return nil, err
+ }
+
+ var stopMemTicker func()
+ if c.r.printm {
+ stopMemTicker = c.initMemTicker()
+ }
+
+ return func() {
+ c.initMemProfile()
+
+ if stopCPUProf != nil {
+ stopCPUProf()
+ }
+ if stopMutexProf != nil {
+ stopMutexProf()
+ }
+
+ if stopTraceProf != nil {
+ stopTraceProf()
+ }
+
+ if stopMemTicker != nil {
+ stopMemTicker()
+ }
+ }, nil
+}
+
+func (c *hugoBuilder) initTraceProfile() (func(), error) {
+ if c.r.traceprofile == "" {
+ return nil, nil
+ }
+
+ f, err := os.Create(c.r.traceprofile)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create trace file: %w", err)
+ }
+
+ if err := trace.Start(f); err != nil {
+ return nil, fmt.Errorf("failed to start trace: %w", err)
+ }
+
+ return func() {
+ trace.Stop()
+ f.Close()
+ }, nil
+}
+
+// newWatcher creates a new watcher to watch filesystem events.
+func (c *hugoBuilder) newWatcher(pollIntervalStr string, dirList ...string) (*watcher.Batcher, error) {
+ staticSyncer := &staticSyncer{c: c}
+
+ var pollInterval time.Duration
+ poll := pollIntervalStr != ""
+ if poll {
+ pollInterval, err := types.ToDurationE(pollIntervalStr)
+ if err != nil {
+ return nil, fmt.Errorf("invalid value for flag poll: %s", err)
+ }
+ c.r.logger.Printf("Use watcher with poll interval %v", pollInterval)
+ }
+
+ if pollInterval == 0 {
+ pollInterval = 500 * time.Millisecond
+ }
+
+ watcher, err := watcher.New(500*time.Millisecond, pollInterval, poll)
+ if err != nil {
+ return nil, err
+ }
+
+ h, err := c.hugo()
+ if err != nil {
+ return nil, err
+ }
+ spec := h.Deps.SourceSpec
+
+ for _, d := range dirList {
+ if d != "" {
+ if spec.IgnoreFile(d) {
+ continue
+ }
+ _ = watcher.Add(d)
+ }
+ }
+
+ // Identifies changes to config (config.toml) files.
+ configSet := make(map[string]bool)
+ var configFiles []string
+ c.withConf(func(conf *commonConfig) {
+ configFiles = conf.configs.LoadingInfo.ConfigFiles
+ })
+
+ c.r.Println("Watching for config changes in", strings.Join(configFiles, ", "))
+ for _, configFile := range configFiles {
+ watcher.Add(configFile)
+ configSet[configFile] = true
+ }
+
+ go func() {
+ for {
+ select {
+ case changes := <-c.r.changesFromBuild:
+ unlock, err := h.LockBuild()
+ if err != nil {
+ c.r.logger.Errorln("Failed to acquire a build lock: %s", err)
+ return
+ }
+ c.changeDetector.PrepareNew()
+ err = c.rebuildSitesForChanges(changes)
+ if err != nil {
+ c.r.logger.Errorln("Error while watching:", err)
+ }
+ if c.s != nil && c.s.doLiveReload {
+ doReload := c.changeDetector == nil || len(c.changeDetector.changed()) > 0
+ doReload = doReload || c.showErrorInBrowser && c.errState.buildErr() != nil
+ if doReload {
+ livereload.ForceRefresh()
+ }
+ }
+ unlock()
+
+ case evs := <-watcher.Events:
+ unlock, err := h.LockBuild()
+ if err != nil {
+ c.r.logger.Errorln("Failed to acquire a build lock: %s", err)
+ return
+ }
+ c.handleEvents(watcher, staticSyncer, evs, configSet)
+ if c.showErrorInBrowser && c.errState.buildErr() != nil {
+ // Need to reload browser to show the error
+ livereload.ForceRefresh()
+ }
+ unlock()
+ case err := <-watcher.Errors():
+ if err != nil && !herrors.IsNotExist(err) {
+ c.r.logger.Errorln("Error while watching:", err)
+ }
+ }
+ }
+ }()
+
+ return watcher, nil
+}
+
+func (c *hugoBuilder) build() error {
+ stopProfiling, err := c.initProfiling()
+ if err != nil {
+ return err
+ }
+
+ defer func() {
+ if stopProfiling != nil {
+ stopProfiling()
+ }
+ }()
+
+ if err := c.fullBuild(false); err != nil {
+ return err
+ }
+
+ if !c.r.quiet {
+ c.r.Println()
+ h, err := c.hugo()
+ if err != nil {
+ return err
+ }
+
+ h.PrintProcessingStats(os.Stdout)
+ c.r.Println()
+ }
+
+ return nil
+}
+
+func (c *hugoBuilder) buildSites(noBuildLock bool) (err error) {
+ defer func() {
+ c.errState.setBuildErr(err)
+ }()
+
+ var h *hugolib.HugoSites
+ h, err = c.hugo()
+ if err != nil {
+ return
+ }
+ err = h.Build(hugolib.BuildCfg{NoBuildLock: noBuildLock})
+ return
+}
+
+func (c *hugoBuilder) copyStatic() (map[string]uint64, error) {
+ m, err := c.doWithPublishDirs(c.copyStaticTo)
+ if err == nil || herrors.IsNotExist(err) {
+ return m, nil
+ }
+ return m, err
+}
+
+func (c *hugoBuilder) copyStaticTo(sourceFs *filesystems.SourceFilesystem) (uint64, error) {
+ infol := c.r.logger.InfoCommand("static")
+ publishDir := helpers.FilePathSeparator
+
+ if sourceFs.PublishFolder != "" {
+ publishDir = filepath.Join(publishDir, sourceFs.PublishFolder)
+ }
+
+ fs := &countingStatFs{Fs: sourceFs.Fs}
+
+ syncer := fsync.NewSyncer()
+ c.withConf(func(conf *commonConfig) {
+ syncer.NoTimes = conf.configs.Base.NoTimes
+ syncer.NoChmod = conf.configs.Base.NoChmod
+ syncer.ChmodFilter = chmodFilter
+
+ syncer.DestFs = conf.fs.PublishDirStatic
+ // Now that we are using a unionFs for the static directories
+ // We can effectively clean the publishDir on initial sync
+ syncer.Delete = conf.configs.Base.CleanDestinationDir
+ })
+
+ syncer.SrcFs = fs
+
+ if syncer.Delete {
+ infol.Logf("removing all files from destination that don't exist in static dirs")
+
+ syncer.DeleteFilter = func(f fsync.FileInfo) bool {
+ name := f.Name()
+
+ // Keep .gitignore and .gitattributes anywhere
+ if name == ".gitignore" || name == ".gitattributes" {
+ return true
+ }
+
+ // Keep Hugo's original dot-directory behavior
+ return f.IsDir() && strings.HasPrefix(name, ".")
+ }
+ }
+ start := time.Now()
+
+ // because we are using a baseFs (to get the union right).
+ // set sync src to root
+ err := syncer.Sync(publishDir, helpers.FilePathSeparator)
+ if err != nil {
+ return 0, err
+ }
+ loggers.TimeTrackf(infol, start, nil, "syncing static files to %s", publishDir)
+
+ // Sync runs Stat 2 times for every source file.
+ numFiles := fs.statCounter / 2
+
+ return numFiles, err
+}
+
+func (c *hugoBuilder) doWithPublishDirs(f func(sourceFs *filesystems.SourceFilesystem) (uint64, error)) (map[string]uint64, error) {
+ langCount := make(map[string]uint64)
+
+ h, err := c.hugo()
+ if err != nil {
+ return nil, err
+ }
+ staticFilesystems := h.BaseFs.SourceFilesystems.Static
+
+ if len(staticFilesystems) == 0 {
+ c.r.logger.Infoln("No static directories found to sync")
+ return langCount, nil
+ }
+
+ for lang, fs := range staticFilesystems {
+ cnt, err := f(fs)
+ if err != nil {
+ return langCount, err
+ }
+ if lang == "" {
+ // Not multihost
+ c.withConf(func(conf *commonConfig) {
+ for _, l := range conf.configs.Languages {
+ langCount[l.Lang] = cnt
+ }
+ })
+ } else {
+ langCount[lang] = cnt
+ }
+ }
+
+ return langCount, nil
+}
+
+func (c *hugoBuilder) progressIntermediate() {
+ terminal.ReportProgress(c.r.StdOut, terminal.ProgressIntermediate, 0)
+}
+
+func (c *hugoBuilder) progressHidden() {
+ terminal.ReportProgress(c.r.StdOut, terminal.ProgressHidden, 0)
+}
+
+func (c *hugoBuilder) fullBuild(noBuildLock bool) error {
+ var (
+ g errgroup.Group
+ langCount map[string]uint64
+ )
+
+ c.r.logger.Println("Start building sites … ")
+ c.r.logger.Println(hugo.BuildVersionString())
+ c.r.logger.Println()
+ if terminal.IsTerminal(os.Stdout) {
+ defer func() {
+ fmt.Print(showCursor + clearLine)
+ }()
+ }
+
+ copyStaticFunc := func() error {
+ cnt, err := c.copyStatic()
+ if err != nil {
+ return fmt.Errorf("error copying static files: %w", err)
+ }
+ langCount = cnt
+ return nil
+ }
+ buildSitesFunc := func() error {
+ if err := c.buildSites(noBuildLock); err != nil {
+ return fmt.Errorf("error building site: %w", err)
+ }
+ return nil
+ }
+ // Do not copy static files and build sites in parallel if cleanDestinationDir is enabled.
+ // This flag deletes all static resources in /public folder that are missing in /static,
+ // and it does so at the end of copyStatic() call.
+ var cleanDestinationDir bool
+ c.withConf(func(conf *commonConfig) {
+ cleanDestinationDir = conf.configs.Base.CleanDestinationDir
+ })
+ if cleanDestinationDir {
+ if err := copyStaticFunc(); err != nil {
+ return err
+ }
+ if err := buildSitesFunc(); err != nil {
+ return err
+ }
+ } else {
+ g.Go(copyStaticFunc)
+ g.Go(buildSitesFunc)
+ if err := g.Wait(); err != nil {
+ return err
+ }
+ }
+
+ h, err := c.hugo()
+ if err != nil {
+ return err
+ }
+ for _, s := range h.Sites {
+ s.ProcessingStats.Static = langCount[s.Language().Lang]
+ }
+
+ if c.r.gc {
+ count, err := h.GC()
+ if err != nil {
+ return err
+ }
+ for _, s := range h.Sites {
+ // We have no way of knowing what site the garbage belonged to.
+ s.ProcessingStats.Cleaned = uint64(count)
+ }
+ }
+
+ return nil
+}
+
+func (c *hugoBuilder) fullRebuild(changeType string) {
+ if changeType == configChangeGoMod {
+ // go.mod may be changed during the build itself, and
+ // we really want to prevent superfluous builds.
+ if !c.fullRebuildSem.TryAcquire(1) {
+ return
+ }
+ c.fullRebuildSem.Release(1)
+ }
+
+ c.fullRebuildSem.Acquire(context.Background(), 1)
+
+ go func() {
+ defer c.fullRebuildSem.Release(1)
+
+ c.printChangeDetected(changeType)
+
+ defer func() {
+ // Allow any file system events to arrive basimplecobra.
+ // This will block any rebuild on config changes for the
+ // duration of the sleep.
+ time.Sleep(2 * time.Second)
+ }()
+
+ defer c.postBuild("Rebuilt", time.Now())
+
+ err := c.reloadConfig()
+ if err != nil {
+ // 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)
+ }
+
+ if !c.errState.isPaused() {
+ _, err := c.copyStatic()
+ if err != nil {
+ c.r.logger.Errorln(err)
+ return
+ }
+ err = c.buildSites(false)
+ if err != nil {
+ c.r.logger.Errorln(err)
+ } else if c.s != nil && c.s.doLiveReload {
+ livereload.ForceRefresh()
+ }
+ }
+ }()
+}
+
+func (c *hugoBuilder) handleBuildErr(err error, msg string) {
+ c.errState.setBuildErr(err)
+ c.r.logger.Errorln(msg + ": " + cleanErrorLog(err.Error()))
+}
+
+func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
+ staticSyncer *staticSyncer,
+ evs []fsnotify.Event,
+ configSet map[string]bool,
+) {
+ defer func() {
+ c.errState.setWasErr(false)
+ }()
+
+ var isHandled bool
+
+ // Filter out ghost events (from deleted, renamed directories).
+ // This seems to be a bug in fsnotify, or possibly MacOS.
+ var n int
+ for _, ev := range evs {
+ keep := true
+ // Write and rename operations are often followed by CHMOD.
+ // There may be valid use cases for rebuilding the site on CHMOD,
+ // but that will require more complex logic than this simple conditional.
+ // On OS X this seems to be related to Spotlight, see:
+ // https://github.com/go-fsnotify/fsnotify/issues/15
+ // A workaround is to put your site(s) on the Spotlight exception list,
+ // but that may be a little mysterious for most end users.
+ // So, for now, we skip reload on CHMOD.
+ // We do have to check for WRITE though. On slower laptops a Chmod
+ // could be aggregated with other important events, and we still want
+ // to rebuild on those
+ if ev.Op == fsnotify.Chmod {
+ keep = false
+ } else if ev.Has(fsnotify.Create) || ev.Has(fsnotify.Write) {
+ if _, err := os.Stat(ev.Name); err != nil {
+ keep = false
+ }
+ }
+ if keep {
+ evs[n] = ev
+ n++
+ }
+ }
+ evs = evs[:n]
+
+ for _, ev := range evs {
+ isConfig := configSet[ev.Name]
+ configChangeType := configChangeConfig
+ if isConfig {
+ if strings.Contains(ev.Name, "go.mod") {
+ configChangeType = configChangeGoMod
+ }
+ if strings.Contains(ev.Name, ".work") {
+ configChangeType = configChangeGoWork
+ }
+ }
+ if !isConfig {
+ // It may be one of the /config folders
+ dirname := filepath.Dir(ev.Name)
+ if dirname != "." && configSet[dirname] {
+ isConfig = true
+ }
+ }
+
+ if isConfig {
+ isHandled = true
+
+ if ev.Op&fsnotify.Chmod == fsnotify.Chmod {
+ continue
+ }
+
+ if ev.Op&fsnotify.Remove == fsnotify.Remove || ev.Op&fsnotify.Rename == fsnotify.Rename {
+ c.withConf(func(conf *commonConfig) {
+ for _, configFile := range conf.configs.LoadingInfo.ConfigFiles {
+ counter := 0
+ for watcher.Add(configFile) != nil {
+ counter++
+ if counter >= 100 {
+ break
+ }
+ time.Sleep(100 * time.Millisecond)
+ }
+ }
+ })
+ }
+
+ // Config file(s) changed. Need full rebuild.
+ c.fullRebuild(configChangeType)
+
+ return
+ }
+ }
+
+ if isHandled {
+ return
+ }
+
+ if c.errState.isPaused() {
+ // Wait for the server to get into a consistent state before
+ // we continue with processing.
+ return
+ }
+
+ if len(evs) > 50 {
+ // This is probably a mass edit of the content dir.
+ // Schedule a full rebuild for when it slows down.
+ c.debounce(func() {
+ c.fullRebuild("")
+ })
+ return
+ }
+
+ c.r.logger.Debugln("Received System Events:", evs)
+
+ 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)
+ return
+ }
+ n = 0
+ for _, ev := range evs {
+ if h.ShouldSkipFileChangeEvent(ev) {
+ continue
+ }
+ evs[n] = ev
+ n++
+ }
+ evs = evs[:n]
+
+ for _, ev := range evs {
+ ext := filepath.Ext(ev.Name)
+ baseName := filepath.Base(ev.Name)
+ 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
+ strings.HasPrefix(ext, ".goutputstream") || // gnome
+ strings.HasSuffix(ext, "jb_old___") || // intelliJ
+ strings.HasSuffix(ext, "jb_tmp___") || // intelliJ
+ strings.HasSuffix(ext, "jb_bak___") || // intelliJ
+ strings.HasPrefix(ext, ".sb-") || // byword
+ strings.HasPrefix(baseName, ".#") || // emacs
+ strings.HasPrefix(baseName, "#") // emacs
+ if istemp {
+ continue
+ }
+
+ if h.Deps.SourceSpec.IgnoreFile(ev.Name) {
+ continue
+ }
+ // Sometimes during rm -rf operations a '"": REMOVE' is triggered. Just ignore these
+ if ev.Name == "" {
+ continue
+ }
+
+ walkAdder := func(ctx context.Context, path string, f hugofs.FileMetaInfo) error {
+ if f.IsDir() {
+ c.r.logger.Println("adding created directory to watchlist", path)
+ if err := watcher.Add(path); err != nil {
+ return err
+ }
+ } else if !staticSyncer.isStatic(h, path) {
+ // Hugo's rebuilding logic is entirely file based. When you drop a new folder into
+ // /content on OSX, the above logic will handle future watching of those files,
+ // but the initial CREATE is lost.
+ dynamicEvents = append(dynamicEvents, fsnotify.Event{Name: path, Op: fsnotify.Create})
+ }
+ return nil
+ }
+
+ // recursively add new directories to watch list
+ if ev.Has(fsnotify.Create) || ev.Has(fsnotify.Rename) {
+ c.withConf(func(conf *commonConfig) {
+ if s, err := conf.fs.Source.Stat(ev.Name); err == nil && s.Mode().IsDir() {
+ _ = helpers.Walk(conf.fs.Source, ev.Name, walkAdder)
+ }
+ })
+ }
+
+ if staticSyncer.isStatic(h, ev.Name) {
+ staticEvents = append(staticEvents, ev)
+ } else {
+ dynamicEvents = append(dynamicEvents, ev)
+ }
+ }
+
+ lrl := c.r.logger.InfoCommand("livereload")
+
+ staticEvents = filterDuplicateEvents(staticEvents)
+ dynamicEvents = filterDuplicateEvents(dynamicEvents)
+
+ if len(staticEvents) > 0 {
+ c.printChangeDetected("Static files")
+
+ if c.r.forceSyncStatic {
+ c.r.logger.Printf("Syncing all static files\n")
+ _, err := c.copyStatic()
+ if err != nil {
+ c.r.logger.Errorln("Error copying static files to publish dir:", err)
+ return
+ }
+ } else {
+ if err := staticSyncer.syncsStaticEvents(staticEvents); err != nil {
+ c.r.logger.Errorln("Error syncing static files to publish dir:", err)
+ return
+ }
+ }
+
+ 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
+
+ if !c.errState.wasErr() && len(staticEvents) == 1 {
+ 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.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()
+ }
+ }
+ }
+
+ if len(dynamicEvents) > 0 {
+ partitionedEvents := partitionDynamicEvents(
+ h.BaseFs.SourceFilesystems,
+ dynamicEvents)
+
+ onePageName := pickOneWriteOrCreatePath(h.Conf.ContentTypes(), partitionedEvents.ContentEvents)
+
+ c.printChangeDetected("")
+ c.changeDetector.PrepareNew()
+
+ func() {
+ defer c.postBuild("Total", time.Now())
+ if err := c.rebuildSites(dynamicEvents); err != nil {
+ c.handleBuildErr(err, "Rebuild failed")
+ }
+ }()
+
+ if c.s != nil && c.s.doLiveReload {
+ if c.errState.wasErr() {
+ livereload.ForceRefresh()
+ return
+ }
+
+ changed := c.changeDetector.changed()
+ if c.changeDetector != nil {
+ if len(changed) >= 10 {
+ lrl.Logf("build changed %d files", len(changed))
+ } else {
+ lrl.Logf("build changed %d files: %q", len(changed), changed)
+ }
+ if len(changed) == 0 {
+ // Nothing has changed.
+ return
+ }
+ }
+
+ // 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)
+ } else {
+ otherChanges = append(otherChanges, ev)
+ }
+ }
+
+ if len(partitionedEvents.ContentEvents) > 0 {
+ navigate := c.s != nil && c.s.navigateToChanged
+ // We have fetched the same page above, but it may have
+ // changed.
+ var p page.Page
+
+ if navigate {
+ if onePageName != "" {
+ p = h.GetContentPage(onePageName)
+ }
+ }
+
+ 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)
+ } else {
+ lrl.Logf("no page to navigate to, force refresh")
+ livereload.ForceRefresh()
+ }
+ } else if len(otherChanges) > 0 || len(cssChanges) > 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()
+ }
+ } else {
+ lrl.Logf("force refresh")
+ livereload.ForceRefresh()
+ }
+
+ if len(cssChanges) > 0 {
+ // Allow some time for the live reload script to get reconnected.
+ if len(otherChanges) > 0 {
+ time.Sleep(200 * time.Millisecond)
+ }
+ for _, ev := range cssChanges {
+ pathToRefresh := h.PathSpec.RelURL(paths.ToSlashTrimLeading(ev), false)
+ lrl.Logf("refreshing CSS %q", pathToRefresh)
+ livereload.RefreshPath(pathToRefresh)
+ }
+ }
+ }
+ }
+}
+
+func (c *hugoBuilder) postBuild(what string, start time.Time) {
+ if h, err := c.hugo(); err == nil && h.Conf.Running() {
+ h.LogServerAddresses()
+ }
+ c.r.timeTrack(start, what)
+}
+
+func (c *hugoBuilder) hugo() (*hugolib.HugoSites, error) {
+ var h *hugolib.HugoSites
+ if err := c.withConfE(func(conf *commonConfig) error {
+ var err error
+ h, err = c.r.HugFromConfig(conf)
+ return err
+ }); err != nil {
+ return nil, err
+ }
+
+ if c.s != nil {
+ // A running server, register the media types.
+ for _, s := range h.Sites {
+ s.RegisterMediaTypes()
+ }
+ }
+ return h, nil
+}
+
+func (c *hugoBuilder) hugoTry() *hugolib.HugoSites {
+ var h *hugolib.HugoSites
+ c.withConf(func(conf *commonConfig) {
+ h, _ = c.r.HugFromConfig(conf)
+ })
+ return h
+}
+
+func (c *hugoBuilder) loadConfig(cd *simplecobra.Commandeer, running bool) error {
+ if terminal.PrintANSIColors(os.Stdout) {
+ defer c.progressHidden()
+ // If the configuration takes a while to load, we want to show some progress.
+ // This is typically loading of external modules.
+ d := debounce.New(500 * time.Millisecond)
+ d(func() {
+ c.progressIntermediate()
+ })
+ defer d(func() {})
+ }
+
+ cfg := config.New()
+ cfg.Set("renderToMemory", c.r.renderToMemory)
+ watch := c.r.buildWatch || (c.s != nil && c.s.serverWatch)
+ if c.r.environment == "" {
+ // We need to set the environment as early as possible because we need it to load the correct config.
+ // Check if the user has set it in env.
+ if env := os.Getenv("HUGO_ENVIRONMENT"); env != "" {
+ c.r.environment = env
+ } else if env := os.Getenv("HUGO_ENV"); env != "" {
+ c.r.environment = env
+ } else {
+ if c.s != nil {
+ // The server defaults to development.
+ c.r.environment = hugo.EnvironmentDevelopment
+ } else {
+ c.r.environment = hugo.EnvironmentProduction
+ }
+ }
+ }
+ cfg.Set("environment", c.r.environment)
+
+ cfg.Set("internal", hmaps.Params{
+ "running": running,
+ "watch": watch,
+ "verbose": c.r.isVerbose(),
+ "fastRenderMode": c.fastRenderMode,
+ })
+
+ conf, err := c.r.ConfigFromProvider(configKey{counter: c.r.configVersionID.Load()}, flagsToCfg(cd, cfg))
+ if err != nil {
+ return err
+ }
+
+ if len(conf.configs.LoadingInfo.ConfigFiles) == 0 {
+ //lint:ignore ST1005 end user message.
+ return errors.New("Unable to locate config file or config directory. Perhaps you need to create a new project.\nRun `hugo help new` for details.")
+ }
+
+ c.conf = conf
+ c.confOld = conf
+ if c.onConfigLoaded != nil {
+ if err := c.onConfigLoaded(false); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+var rebuildCounter atomic.Uint64
+
+func (c *hugoBuilder) printChangeDetected(typ string) {
+ msg := "\nChange"
+ if typ != "" {
+ msg += " of " + typ
+ }
+ msg += fmt.Sprintf(" detected, rebuilding site (#%d).", rebuildCounter.Add(1))
+
+ c.r.logger.Println(msg)
+ const layout = "2006-01-02 15:04:05.000 -0700"
+ c.r.logger.Println(htime.Now().Format(layout))
+}
+
+func (c *hugoBuilder) rebuildSites(events []fsnotify.Event) (err error) {
+ defer func() {
+ c.errState.setBuildErr(err)
+ }()
+ 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()
+ if err != nil {
+ return
+ }
+ err = h.Build(hugolib.BuildCfg{NoBuildLock: true, RecentlyTouched: c.visitedURLs, ErrRecovery: c.errState.wasErr()}, events...)
+ return
+}
+
+func (c *hugoBuilder) rebuildSitesForChanges(ids []identity.Identity) (err error) {
+ defer func() {
+ c.errState.setBuildErr(err)
+ }()
+
+ var h *hugolib.HugoSites
+ h, err = c.hugo()
+ if err != nil {
+ return
+ }
+ whatChanged := &hugolib.WhatChanged{}
+ whatChanged.Add(ids...)
+ err = h.Build(hugolib.BuildCfg{NoBuildLock: true, WhatChanged: whatChanged, RecentlyTouched: c.visitedURLs, ErrRecovery: c.errState.wasErr()})
+
+ return
+}
+
+func (c *hugoBuilder) reloadConfig() error {
+ c.r.resetLogs()
+ c.r.configVersionID.Add(1)
+
+ if err := c.withConfOrOldConfE(func(conf *commonConfig) error {
+ oldConf := conf
+ c.conf = nil
+ newConf, err := c.r.ConfigFromConfig(configKey{counter: c.r.configVersionID.Load()}, conf)
+ if err != nil {
+ return err
+ }
+ sameLen := len(oldConf.configs.Languages) == len(newConf.configs.Languages)
+ if !sameLen {
+ if oldConf.configs.IsMultihost || newConf.configs.IsMultihost {
+ return errors.New("multihost change detected, please restart server")
+ }
+ }
+ c.conf = newConf
+ return nil
+ }); err != nil {
+ return err
+ }
+
+ if c.onConfigLoaded != nil {
+ if err := c.onConfigLoaded(true); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/commands/import.go b/commands/import.go
new file mode 100644
index 00000000000..8d36fb92304
--- /dev/null
+++ b/commands/import.go
@@ -0,0 +1,618 @@
+// 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 (
+ "bytes"
+ "context"
+ "errors"
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "path/filepath"
+ "regexp"
+ "strconv"
+ "strings"
+ "time"
+ "unicode"
+
+ "github.com/bep/simplecobra"
+ "github.com/gohugoio/hugo/common/hmaps"
+ "github.com/gohugoio/hugo/common/htime"
+ "github.com/gohugoio/hugo/common/hugio"
+ "github.com/gohugoio/hugo/helpers"
+ "github.com/gohugoio/hugo/hugofs"
+ "github.com/gohugoio/hugo/parser"
+ "github.com/gohugoio/hugo/parser/metadecoders"
+ "github.com/gohugoio/hugo/parser/pageparser"
+ "github.com/spf13/afero"
+ "github.com/spf13/cobra"
+)
+
+func newImportCommand() *importCommand {
+ var c *importCommand
+ c = &importCommand{
+ commands: []simplecobra.Commander{
+ &simpleCommand{
+ name: "jekyll",
+ short: "hugo import from Jekyll",
+ long: `hugo import from Jekyll.
+
+Import from Jekyll requires two paths, e.g. ` + "`hugo import jekyll jekyll_root_path target_path`.",
+ run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
+ if len(args) < 2 {
+ return newUserError(`import from jekyll requires two paths, e.g. ` + "`hugo import jekyll jekyll_root_path target_path`.")
+ }
+ return c.importFromJekyll(args)
+ },
+ withc: func(cmd *cobra.Command, r *rootCommand) {
+ cmd.ValidArgsFunction = cobra.NoFileCompletions
+ cmd.Flags().BoolVar(&c.force, "force", false, "allow import into non-empty target directory")
+ },
+ },
+ },
+ }
+
+ return c
+}
+
+type importCommand struct {
+ r *rootCommand
+
+ force bool
+
+ commands []simplecobra.Commander
+}
+
+func (c *importCommand) Commands() []simplecobra.Commander {
+ return c.commands
+}
+
+func (c *importCommand) Name() string {
+ return "import"
+}
+
+func (c *importCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
+ return nil
+}
+
+func (c *importCommand) Init(cd *simplecobra.Commandeer) error {
+ cmd := cd.CobraCommand
+ cmd.Short = "Import a project from another system"
+ cmd.Long = `Import a project from another system.
+
+Import requires a subcommand, e.g. ` + "`hugo import jekyll jekyll_root_path target_path`."
+
+ cmd.RunE = nil
+ return nil
+}
+
+func (c *importCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
+ c.r = cd.Root.Command.(*rootCommand)
+ return nil
+}
+
+func (i *importCommand) createConfigFromJekyll(fs afero.Fs, inpath string, kind metadecoders.Format, jekyllConfig map[string]any) (err error) {
+ title := "My New Hugo Project"
+ baseURL := "http://example.org/"
+
+ for key, value := range jekyllConfig {
+ lowerKey := strings.ToLower(key)
+
+ switch lowerKey {
+ case "title":
+ if str, ok := value.(string); ok {
+ title = str
+ }
+
+ case "url":
+ if str, ok := value.(string); ok {
+ baseURL = str
+ }
+ }
+ }
+
+ in := map[string]any{
+ "baseURL": baseURL,
+ "title": title,
+ "locale": "en-us",
+ "disablePathToLower": true,
+ }
+
+ var buf bytes.Buffer
+ err = parser.InterfaceToConfig(in, kind, &buf)
+ if err != nil {
+ return err
+ }
+
+ return helpers.WriteToDisk(filepath.Join(inpath, "hugo."+string(kind)), &buf, fs)
+}
+
+func (c *importCommand) getJekyllDirInfo(fs afero.Fs, jekyllRoot string) (map[string]bool, bool) {
+ postDirs := make(map[string]bool)
+ hasAnyPost := false
+ if entries, err := os.ReadDir(jekyllRoot); err == nil {
+ for _, entry := range entries {
+ if entry.IsDir() {
+ subDir := filepath.Join(jekyllRoot, entry.Name())
+ if isPostDir, hasAnyPostInDir := c.retrieveJekyllPostDir(fs, subDir); isPostDir {
+ postDirs[entry.Name()] = hasAnyPostInDir
+ if hasAnyPostInDir {
+ hasAnyPost = true
+ }
+ }
+ }
+ }
+ }
+ return postDirs, hasAnyPost
+}
+
+func (c *importCommand) createProjectFromJekyll(jekyllRoot, targetDir string, jekyllPostDirs map[string]bool) error {
+ fs := &afero.OsFs{}
+ if exists, _ := helpers.Exists(targetDir, fs); exists {
+ if isDir, _ := helpers.IsDir(targetDir, fs); !isDir {
+ return errors.New("target path \"" + targetDir + "\" exists but is not a directory")
+ }
+
+ isEmpty, _ := helpers.IsEmpty(targetDir, fs)
+
+ if !isEmpty && !c.force {
+ return errors.New("target path \"" + targetDir + "\" exists and is not empty")
+ }
+ }
+
+ jekyllConfig := c.loadJekyllConfig(fs, jekyllRoot)
+
+ mkdir(targetDir, "layouts")
+ mkdir(targetDir, "content")
+ mkdir(targetDir, "archetypes")
+ mkdir(targetDir, "static")
+ mkdir(targetDir, "data")
+ mkdir(targetDir, "themes")
+
+ c.createConfigFromJekyll(fs, targetDir, "yaml", jekyllConfig)
+
+ c.copyJekyllFilesAndFolders(jekyllRoot, filepath.Join(targetDir, "static"), jekyllPostDirs)
+
+ return nil
+}
+
+func (c *importCommand) convertJekyllContent(m any, content string) (string, error) {
+ metadata, _ := hmaps.ToStringMapE(m)
+
+ lines := strings.Split(content, "\n")
+ var resultLines []string
+ for _, line := range lines {
+ resultLines = append(resultLines, strings.Trim(line, "\r\n"))
+ }
+
+ content = strings.Join(resultLines, "\n")
+
+ excerptSep := ""
+ if value, ok := metadata["excerpt_separator"]; ok {
+ if str, strOk := value.(string); strOk {
+ content = strings.Replace(content, strings.TrimSpace(str), excerptSep, -1)
+ }
+ }
+
+ replaceList := []struct {
+ re *regexp.Regexp
+ replace string
+ }{
+ {regexp.MustCompile("(?i)"), ""},
+ {regexp.MustCompile(`\{%\s*raw\s*%\}\s*(.*?)\s*\{%\s*endraw\s*%\}`), "$1"},
+ {regexp.MustCompile(`{%\s*endhighlight\s*%}`), "{{< / highlight >}}"},
+ }
+
+ for _, replace := range replaceList {
+ content = replace.re.ReplaceAllString(content, replace.replace)
+ }
+
+ replaceListFunc := []struct {
+ re *regexp.Regexp
+ replace func(string) string
+ }{
+ // Octopress image tag: http://octopress.org/docs/plugins/image-tag/
+ {regexp.MustCompile(`{%\s+img\s*(.*?)\s*%}`), c.replaceImageTag},
+ {regexp.MustCompile(`{%\s*highlight\s*(.*?)\s*%}`), c.replaceHighlightTag},
+ }
+
+ for _, replace := range replaceListFunc {
+ content = replace.re.ReplaceAllStringFunc(content, replace.replace)
+ }
+
+ var buf bytes.Buffer
+ if len(metadata) != 0 {
+ err := parser.InterfaceToFrontMatter(m, metadecoders.YAML, &buf)
+ if err != nil {
+ return "", err
+ }
+ }
+ buf.WriteString(content)
+
+ return buf.String(), nil
+}
+
+func (c *importCommand) convertJekyllMetaData(m any, postName string, postDate time.Time, draft bool) (any, error) {
+ metadata, err := hmaps.ToStringMapE(m)
+ if err != nil {
+ return nil, err
+ }
+
+ if draft {
+ metadata["draft"] = true
+ }
+
+ for key, value := range metadata {
+ lowerKey := strings.ToLower(key)
+
+ switch lowerKey {
+ case "layout":
+ delete(metadata, key)
+ case "permalink":
+ if str, ok := value.(string); ok {
+ metadata["url"] = str
+ }
+ delete(metadata, key)
+ case "category":
+ if str, ok := value.(string); ok {
+ metadata["categories"] = []string{str}
+ }
+ delete(metadata, key)
+ case "excerpt_separator":
+ if key != lowerKey {
+ delete(metadata, key)
+ metadata[lowerKey] = value
+ }
+ case "date":
+ if str, ok := value.(string); ok {
+ re := regexp.MustCompile(`(\d+):(\d+):(\d+)`)
+ r := re.FindAllStringSubmatch(str, -1)
+ if len(r) > 0 {
+ hour, _ := strconv.Atoi(r[0][1])
+ minute, _ := strconv.Atoi(r[0][2])
+ second, _ := strconv.Atoi(r[0][3])
+ postDate = time.Date(postDate.Year(), postDate.Month(), postDate.Day(), hour, minute, second, 0, time.UTC)
+ }
+ }
+ delete(metadata, key)
+ }
+
+ }
+
+ metadata["date"] = postDate.Format(time.RFC3339)
+
+ return metadata, nil
+}
+
+func (c *importCommand) convertJekyllPost(path, relPath, targetDir string, draft bool) error {
+ log.Println("Converting", path)
+
+ filename := filepath.Base(path)
+ postDate, postName, err := c.parseJekyllFilename(filename)
+ if err != nil {
+ c.r.Printf("Failed to parse filename '%s': %s. Skipping.", filename, err)
+ return nil
+ }
+
+ log.Println(filename, postDate, postName)
+
+ targetFile := filepath.Join(targetDir, relPath)
+ targetParentDir := filepath.Dir(targetFile)
+ os.MkdirAll(targetParentDir, 0o777)
+
+ contentBytes, err := os.ReadFile(path)
+ if err != nil {
+ c.r.logger.Errorln("Read file error:", path)
+ return err
+ }
+ pf, err := pageparser.ParseFrontMatterAndContent(bytes.NewReader(contentBytes))
+ if err != nil {
+ return fmt.Errorf("failed to parse file %q: %s", filename, err)
+ }
+ newmetadata, err := c.convertJekyllMetaData(pf.FrontMatter, postName, postDate, draft)
+ if err != nil {
+ return fmt.Errorf("failed to convert metadata for file %q: %s", filename, err)
+ }
+
+ content, err := c.convertJekyllContent(newmetadata, string(pf.Content))
+ if err != nil {
+ return fmt.Errorf("failed to convert content for file %q: %s", filename, err)
+ }
+
+ fs := hugofs.Os
+ if err := helpers.WriteToDisk(targetFile, strings.NewReader(content), fs); err != nil {
+ return fmt.Errorf("failed to save file %q: %s", filename, err)
+ }
+ return nil
+}
+
+func (c *importCommand) copyJekyllFilesAndFolders(jekyllRoot, dest string, jekyllPostDirs map[string]bool) (err error) {
+ fs := hugofs.Os
+
+ fi, err := fs.Stat(jekyllRoot)
+ if err != nil {
+ return err
+ }
+ if !fi.IsDir() {
+ return errors.New(jekyllRoot + " is not a directory")
+ }
+ err = os.MkdirAll(dest, fi.Mode())
+ if err != nil {
+ return err
+ }
+ entries, err := os.ReadDir(jekyllRoot)
+ if err != nil {
+ return err
+ }
+
+ for _, entry := range entries {
+ sfp := filepath.Join(jekyllRoot, entry.Name())
+ dfp := filepath.Join(dest, entry.Name())
+ if entry.IsDir() {
+ if entry.Name()[0] != '_' && entry.Name()[0] != '.' {
+ if _, ok := jekyllPostDirs[entry.Name()]; !ok {
+ err = hugio.CopyDir(fs, sfp, dfp, nil)
+ if err != nil {
+ c.r.logger.Errorln(err)
+ }
+ }
+ }
+ } else {
+ lowerEntryName := strings.ToLower(entry.Name())
+ exceptSuffix := []string{
+ ".md", ".markdown", ".html", ".htm",
+ ".xml", ".textile", "rakefile", "gemfile", ".lock",
+ }
+ isExcept := false
+ for _, suffix := range exceptSuffix {
+ if strings.HasSuffix(lowerEntryName, suffix) {
+ isExcept = true
+ break
+ }
+ }
+
+ if !isExcept && entry.Name()[0] != '.' && entry.Name()[0] != '_' {
+ err = hugio.CopyFile(fs, sfp, dfp)
+ if err != nil {
+ c.r.logger.Errorln(err)
+ }
+ }
+ }
+
+ }
+ return nil
+}
+
+func (c *importCommand) importFromJekyll(args []string) error {
+ jekyllRoot, err := filepath.Abs(filepath.Clean(args[0]))
+ if err != nil {
+ return newUserError("path error:", args[0])
+ }
+
+ targetDir, err := filepath.Abs(filepath.Clean(args[1]))
+ if err != nil {
+ return newUserError("path error:", args[1])
+ }
+
+ c.r.Println("Import Jekyll from:", jekyllRoot, "to:", targetDir)
+
+ if strings.HasPrefix(filepath.Dir(targetDir), jekyllRoot) {
+ return newUserError("abort: target path should not be inside the Jekyll root")
+ }
+
+ fs := afero.NewOsFs()
+ jekyllPostDirs, hasAnyPost := c.getJekyllDirInfo(fs, jekyllRoot)
+ if !hasAnyPost {
+ return errors.New("abort: jekyll root contains neither posts nor drafts")
+ }
+
+ err = c.createProjectFromJekyll(jekyllRoot, targetDir, jekyllPostDirs)
+ if err != nil {
+ return newUserError(err)
+ }
+
+ c.r.Println("Importing...")
+
+ fileCount := 0
+ callback := func(ctx context.Context, path string, fi hugofs.FileMetaInfo) error {
+ if fi.IsDir() {
+ return nil
+ }
+
+ relPath, err := filepath.Rel(jekyllRoot, path)
+ if err != nil {
+ return newUserError("get rel path error:", path)
+ }
+
+ relPath = filepath.ToSlash(relPath)
+ draft := false
+
+ switch {
+ case strings.Contains(relPath, "_posts/"):
+ relPath = filepath.Join("content/post", strings.Replace(relPath, "_posts/", "", -1))
+ case strings.Contains(relPath, "_drafts/"):
+ relPath = filepath.Join("content/draft", strings.Replace(relPath, "_drafts/", "", -1))
+ draft = true
+ default:
+ return nil
+ }
+
+ fileCount++
+ return c.convertJekyllPost(path, relPath, targetDir, draft)
+ }
+
+ for jekyllPostDir, hasAnyPostInDir := range jekyllPostDirs {
+ if hasAnyPostInDir {
+ if err = helpers.Walk(hugofs.Os, filepath.Join(jekyllRoot, jekyllPostDir), callback); err != nil {
+ return err
+ }
+ }
+ }
+
+ c.r.Println("Congratulations!", fileCount, "post(s) imported!")
+ c.r.Println("Now, start Hugo by yourself:")
+ c.r.Println("cd " + args[1])
+ c.r.Println("git init")
+ c.r.Println("git submodule add https://github.com/theNewDynamic/gohugo-theme-ananke themes/ananke")
+ c.r.Println("echo \"theme = 'ananke'\" > hugo.toml")
+ c.r.Println("hugo server")
+
+ return nil
+}
+
+func (c *importCommand) loadJekyllConfig(fs afero.Fs, jekyllRoot string) map[string]any {
+ path := filepath.Join(jekyllRoot, "_config.yml")
+
+ exists, err := helpers.Exists(path, fs)
+
+ if err != nil || !exists {
+ c.r.Println("_config.yaml not found: Is the specified Jekyll root correct?")
+ return nil
+ }
+
+ f, err := fs.Open(path)
+ if err != nil {
+ return nil
+ }
+
+ defer f.Close()
+
+ b, err := io.ReadAll(f)
+ if err != nil {
+ return nil
+ }
+
+ m, err := metadecoders.Default.UnmarshalToMap(b, metadecoders.YAML)
+ if err != nil {
+ return nil
+ }
+
+ return m
+}
+
+func (c *importCommand) parseJekyllFilename(filename string) (time.Time, string, error) {
+ re := regexp.MustCompile(`(\d+-\d+-\d+)-(.+)\..*`)
+ r := re.FindAllStringSubmatch(filename, -1)
+ if len(r) == 0 {
+ return htime.Now(), "", errors.New("filename not match")
+ }
+
+ postDate, err := time.Parse("2006-1-2", r[0][1])
+ if err != nil {
+ return htime.Now(), "", err
+ }
+
+ postName := r[0][2]
+
+ return postDate, postName, nil
+}
+
+func (c *importCommand) replaceHighlightTag(match string) string {
+ r := regexp.MustCompile(`{%\s*highlight\s*(.*?)\s*%}`)
+ parts := r.FindStringSubmatch(match)
+ lastQuote := rune(0)
+ f := func(c rune) bool {
+ switch {
+ case c == lastQuote:
+ lastQuote = rune(0)
+ return false
+ case lastQuote != rune(0):
+ return false
+ case unicode.In(c, unicode.Quotation_Mark):
+ lastQuote = c
+ return false
+ default:
+ return unicode.IsSpace(c)
+ }
+ }
+ // splitting string by space but considering quoted section
+ items := strings.FieldsFunc(parts[1], f)
+
+ result := bytes.NewBufferString("{{< highlight ")
+ result.WriteString(items[0]) // language
+ options := items[1:]
+ for i, opt := range options {
+ opt = strings.Replace(opt, "\"", "", -1)
+ if opt == "linenos" {
+ opt = "linenos=table"
+ }
+ if i == 0 {
+ opt = " \"" + opt
+ }
+ if i < len(options)-1 {
+ opt += ","
+ } else if i == len(options)-1 {
+ opt += "\""
+ }
+ result.WriteString(opt)
+ }
+
+ result.WriteString(" >}}")
+ return result.String()
+}
+
+func (c *importCommand) replaceImageTag(match string) string {
+ r := regexp.MustCompile(`{%\s+img\s*(\p{L}*)\s+([\S]*/[\S]+)\s+(\d*)\s*(\d*)\s*(.*?)\s*%}`)
+ result := bytes.NewBufferString("{{< figure ")
+ parts := r.FindStringSubmatch(match)
+ // Index 0 is the entire string, ignore
+ c.replaceOptionalPart(result, "class", parts[1])
+ c.replaceOptionalPart(result, "src", parts[2])
+ c.replaceOptionalPart(result, "width", parts[3])
+ c.replaceOptionalPart(result, "height", parts[4])
+ // title + alt
+ part := parts[5]
+ if len(part) > 0 {
+ splits := strings.Split(part, "'")
+ lenSplits := len(splits)
+ if lenSplits == 1 {
+ c.replaceOptionalPart(result, "title", splits[0])
+ } else if lenSplits == 3 {
+ c.replaceOptionalPart(result, "title", splits[1])
+ } else if lenSplits == 5 {
+ c.replaceOptionalPart(result, "title", splits[1])
+ c.replaceOptionalPart(result, "alt", splits[3])
+ }
+ }
+ result.WriteString(">}}")
+ return result.String()
+}
+
+func (c *importCommand) replaceOptionalPart(buffer *bytes.Buffer, partName string, part string) {
+ if len(part) > 0 {
+ buffer.WriteString(partName + "=\"" + part + "\" ")
+ }
+}
+
+func (c *importCommand) retrieveJekyllPostDir(fs afero.Fs, dir string) (bool, bool) {
+ if strings.HasSuffix(dir, "_posts") || strings.HasSuffix(dir, "_drafts") {
+ isEmpty, _ := helpers.IsEmpty(dir, fs)
+ return true, !isEmpty
+ }
+
+ if entries, err := os.ReadDir(dir); err == nil {
+ for _, entry := range entries {
+ if entry.IsDir() {
+ subDir := filepath.Join(dir, entry.Name())
+ if isPostDir, hasAnyPost := c.retrieveJekyllPostDir(fs, subDir); isPostDir {
+ return isPostDir, hasAnyPost
+ }
+ }
+ }
+ }
+
+ return false, true
+}
diff --git a/commands/import_jekyll.go b/commands/import_jekyll.go
deleted file mode 100644
index b9ea56d4b49..00000000000
--- a/commands/import_jekyll.go
+++ /dev/null
@@ -1,609 +0,0 @@
-// Copyright 2019 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 (
- "bytes"
- "errors"
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
- "regexp"
- "strconv"
- "strings"
- "time"
- "unicode"
-
- "github.com/gohugoio/hugo/parser/pageparser"
-
- "github.com/gohugoio/hugo/common/hugio"
-
- "github.com/gohugoio/hugo/parser/metadecoders"
-
- "github.com/gohugoio/hugo/common/maps"
- "github.com/gohugoio/hugo/helpers"
- "github.com/gohugoio/hugo/hugofs"
- "github.com/gohugoio/hugo/hugolib"
- "github.com/gohugoio/hugo/parser"
- "github.com/spf13/afero"
- "github.com/spf13/cobra"
- jww "github.com/spf13/jwalterweatherman"
-)
-
-var _ cmder = (*importCmd)(nil)
-
-type importCmd struct {
- *baseCmd
-}
-
-func newImportCmd() *importCmd {
- cc := &importCmd{}
-
- cc.baseCmd = newBaseCmd(&cobra.Command{
- Use: "import",
- Short: "Import your site from others.",
- 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`.",
- RunE: nil,
- })
-
- importJekyllCmd := &cobra.Command{
- Use: "jekyll",
- Short: "hugo import from Jekyll",
- Long: `hugo import from Jekyll.
-
-Import from Jekyll requires two paths, e.g. ` + "`hugo import jekyll jekyll_root_path target_path`.",
- RunE: cc.importFromJekyll,
- }
-
- importJekyllCmd.Flags().Bool("force", false, "allow import into non-empty target directory")
-
- cc.cmd.AddCommand(importJekyllCmd)
-
- return cc
-}
-
-func (i *importCmd) importFromJekyll(cmd *cobra.Command, args []string) error {
- if len(args) < 2 {
- return newUserError(`import from jekyll requires two paths, e.g. ` + "`hugo import jekyll jekyll_root_path target_path`.")
- }
-
- jekyllRoot, err := filepath.Abs(filepath.Clean(args[0]))
- if err != nil {
- return newUserError("path error:", args[0])
- }
-
- targetDir, err := filepath.Abs(filepath.Clean(args[1]))
- if err != nil {
- return newUserError("path error:", args[1])
- }
-
- jww.INFO.Println("Import Jekyll from:", jekyllRoot, "to:", targetDir)
-
- if strings.HasPrefix(filepath.Dir(targetDir), jekyllRoot) {
- return newUserError("abort: target path should not be inside the Jekyll root")
- }
-
- forceImport, _ := cmd.Flags().GetBool("force")
-
- fs := afero.NewOsFs()
- jekyllPostDirs, hasAnyPost := i.getJekyllDirInfo(fs, jekyllRoot)
- if !hasAnyPost {
- return errors.New("abort: jekyll root contains neither posts nor drafts")
- }
-
- err = i.createSiteFromJekyll(jekyllRoot, targetDir, jekyllPostDirs, forceImport)
-
- if err != nil {
- return newUserError(err)
- }
-
- jww.FEEDBACK.Println("Importing...")
-
- fileCount := 0
- callback := func(path string, fi hugofs.FileMetaInfo, err error) error {
- if err != nil {
- return err
- }
-
- if fi.IsDir() {
- return nil
- }
-
- relPath, err := filepath.Rel(jekyllRoot, path)
- if err != nil {
- return newUserError("get rel path error:", path)
- }
-
- relPath = filepath.ToSlash(relPath)
- draft := false
-
- switch {
- case strings.Contains(relPath, "_posts/"):
- relPath = filepath.Join("content/post", strings.Replace(relPath, "_posts/", "", -1))
- case strings.Contains(relPath, "_drafts/"):
- relPath = filepath.Join("content/draft", strings.Replace(relPath, "_drafts/", "", -1))
- draft = true
- default:
- return nil
- }
-
- fileCount++
- return convertJekyllPost(path, relPath, targetDir, draft)
- }
-
- for jekyllPostDir, hasAnyPostInDir := range jekyllPostDirs {
- if hasAnyPostInDir {
- if err = helpers.SymbolicWalk(hugofs.Os, filepath.Join(jekyllRoot, jekyllPostDir), callback); err != nil {
- return err
- }
- }
- }
-
- jww.FEEDBACK.Println("Congratulations!", fileCount, "post(s) imported!")
- jww.FEEDBACK.Println("Now, start Hugo by yourself:\n" +
- "$ git clone https://github.com/spf13/herring-cove.git " + args[1] + "/themes/herring-cove")
- jww.FEEDBACK.Println("$ cd " + args[1] + "\n$ hugo server --theme=herring-cove")
-
- return nil
-}
-
-func (i *importCmd) getJekyllDirInfo(fs afero.Fs, jekyllRoot string) (map[string]bool, bool) {
- postDirs := make(map[string]bool)
- hasAnyPost := false
- if entries, err := ioutil.ReadDir(jekyllRoot); err == nil {
- for _, entry := range entries {
- if entry.IsDir() {
- subDir := filepath.Join(jekyllRoot, entry.Name())
- if isPostDir, hasAnyPostInDir := i.retrieveJekyllPostDir(fs, subDir); isPostDir {
- postDirs[entry.Name()] = hasAnyPostInDir
- if hasAnyPostInDir {
- hasAnyPost = true
- }
- }
- }
- }
- }
- return postDirs, hasAnyPost
-}
-
-func (i *importCmd) retrieveJekyllPostDir(fs afero.Fs, dir string) (bool, bool) {
- if strings.HasSuffix(dir, "_posts") || strings.HasSuffix(dir, "_drafts") {
- isEmpty, _ := helpers.IsEmpty(dir, fs)
- return true, !isEmpty
- }
-
- if entries, err := ioutil.ReadDir(dir); err == nil {
- for _, entry := range entries {
- if entry.IsDir() {
- subDir := filepath.Join(dir, entry.Name())
- if isPostDir, hasAnyPost := i.retrieveJekyllPostDir(fs, subDir); isPostDir {
- return isPostDir, hasAnyPost
- }
- }
- }
- }
-
- return false, true
-}
-
-func (i *importCmd) createSiteFromJekyll(jekyllRoot, targetDir string, jekyllPostDirs map[string]bool, force bool) error {
- s, err := hugolib.NewSiteDefaultLang()
- if err != nil {
- return err
- }
-
- fs := s.Fs.Source
- if exists, _ := helpers.Exists(targetDir, fs); exists {
- if isDir, _ := helpers.IsDir(targetDir, fs); !isDir {
- return errors.New("target path \"" + targetDir + "\" exists but is not a directory")
- }
-
- isEmpty, _ := helpers.IsEmpty(targetDir, fs)
-
- if !isEmpty && !force {
- return errors.New("target path \"" + targetDir + "\" exists and is not empty")
- }
- }
-
- jekyllConfig := i.loadJekyllConfig(fs, jekyllRoot)
-
- mkdir(targetDir, "layouts")
- mkdir(targetDir, "content")
- mkdir(targetDir, "archetypes")
- mkdir(targetDir, "static")
- mkdir(targetDir, "data")
- mkdir(targetDir, "themes")
-
- i.createConfigFromJekyll(fs, targetDir, "yaml", jekyllConfig)
-
- i.copyJekyllFilesAndFolders(jekyllRoot, filepath.Join(targetDir, "static"), jekyllPostDirs)
-
- return nil
-}
-
-func (i *importCmd) loadJekyllConfig(fs afero.Fs, jekyllRoot string) map[string]interface{} {
- path := filepath.Join(jekyllRoot, "_config.yml")
-
- exists, err := helpers.Exists(path, fs)
-
- if err != nil || !exists {
- jww.WARN.Println("_config.yaml not found: Is the specified Jekyll root correct?")
- return nil
- }
-
- f, err := fs.Open(path)
- if err != nil {
- return nil
- }
-
- defer f.Close()
-
- b, err := ioutil.ReadAll(f)
- if err != nil {
- return nil
- }
-
- c, err := metadecoders.Default.UnmarshalToMap(b, metadecoders.YAML)
- if err != nil {
- return nil
- }
-
- return c
-}
-
-func (i *importCmd) createConfigFromJekyll(fs afero.Fs, inpath string, kind metadecoders.Format, jekyllConfig map[string]interface{}) (err error) {
- title := "My New Hugo Site"
- baseURL := "http://example.org/"
-
- for key, value := range jekyllConfig {
- lowerKey := strings.ToLower(key)
-
- switch lowerKey {
- case "title":
- if str, ok := value.(string); ok {
- title = str
- }
-
- case "url":
- if str, ok := value.(string); ok {
- baseURL = str
- }
- }
- }
-
- in := map[string]interface{}{
- "baseURL": baseURL,
- "title": title,
- "languageCode": "en-us",
- "disablePathToLower": true,
- }
-
- var buf bytes.Buffer
- err = parser.InterfaceToConfig(in, kind, &buf)
- if err != nil {
- return err
- }
-
- return helpers.WriteToDisk(filepath.Join(inpath, "config."+string(kind)), &buf, fs)
-}
-
-func (i *importCmd) copyJekyllFilesAndFolders(jekyllRoot, dest string, jekyllPostDirs map[string]bool) (err error) {
- fs := hugofs.Os
-
- fi, err := fs.Stat(jekyllRoot)
- if err != nil {
- return err
- }
- if !fi.IsDir() {
- return errors.New(jekyllRoot + " is not a directory")
- }
- err = os.MkdirAll(dest, fi.Mode())
- if err != nil {
- return err
- }
- entries, err := ioutil.ReadDir(jekyllRoot)
- if err != nil {
- return err
- }
-
- for _, entry := range entries {
- sfp := filepath.Join(jekyllRoot, entry.Name())
- dfp := filepath.Join(dest, entry.Name())
- if entry.IsDir() {
- if entry.Name()[0] != '_' && entry.Name()[0] != '.' {
- if _, ok := jekyllPostDirs[entry.Name()]; !ok {
- err = hugio.CopyDir(fs, sfp, dfp, nil)
- if err != nil {
- jww.ERROR.Println(err)
- }
- }
- }
- } else {
- lowerEntryName := strings.ToLower(entry.Name())
- exceptSuffix := []string{
- ".md", ".markdown", ".html", ".htm",
- ".xml", ".textile", "rakefile", "gemfile", ".lock",
- }
- isExcept := false
- for _, suffix := range exceptSuffix {
- if strings.HasSuffix(lowerEntryName, suffix) {
- isExcept = true
- break
- }
- }
-
- if !isExcept && entry.Name()[0] != '.' && entry.Name()[0] != '_' {
- err = hugio.CopyFile(fs, sfp, dfp)
- if err != nil {
- jww.ERROR.Println(err)
- }
- }
- }
-
- }
- return nil
-}
-
-func parseJekyllFilename(filename string) (time.Time, string, error) {
- re := regexp.MustCompile(`(\d+-\d+-\d+)-(.+)\..*`)
- r := re.FindAllStringSubmatch(filename, -1)
- if len(r) == 0 {
- return time.Now(), "", errors.New("filename not match")
- }
-
- postDate, err := time.Parse("2006-1-2", r[0][1])
- if err != nil {
- return time.Now(), "", err
- }
-
- postName := r[0][2]
-
- return postDate, postName, nil
-}
-
-func convertJekyllPost(path, relPath, targetDir string, draft bool) error {
- jww.TRACE.Println("Converting", path)
-
- filename := filepath.Base(path)
- postDate, postName, err := parseJekyllFilename(filename)
- if err != nil {
- jww.WARN.Printf("Failed to parse filename '%s': %s. Skipping.", filename, err)
- return nil
- }
-
- jww.TRACE.Println(filename, postDate, postName)
-
- targetFile := filepath.Join(targetDir, relPath)
- targetParentDir := filepath.Dir(targetFile)
- os.MkdirAll(targetParentDir, 0777)
-
- contentBytes, err := ioutil.ReadFile(path)
- if err != nil {
- jww.ERROR.Println("Read file error:", path)
- return err
- }
-
- pf, err := pageparser.ParseFrontMatterAndContent(bytes.NewReader(contentBytes))
- if err != nil {
- jww.ERROR.Println("Parse file error:", path)
- return err
- }
-
- newmetadata, err := convertJekyllMetaData(pf.FrontMatter, postName, postDate, draft)
- if err != nil {
- jww.ERROR.Println("Convert metadata error:", path)
- return err
- }
-
- content, err := convertJekyllContent(newmetadata, string(pf.Content))
- if err != nil {
- jww.ERROR.Println("Converting Jekyll error:", path)
- return err
- }
-
- fs := hugofs.Os
- if err := helpers.WriteToDisk(targetFile, strings.NewReader(content), fs); err != nil {
- return fmt.Errorf("failed to save file %q: %s", filename, err)
- }
-
- return nil
-}
-
-func convertJekyllMetaData(m interface{}, postName string, postDate time.Time, draft bool) (interface{}, error) {
- metadata, err := maps.ToStringMapE(m)
- if err != nil {
- return nil, err
- }
-
- if draft {
- metadata["draft"] = true
- }
-
- for key, value := range metadata {
- lowerKey := strings.ToLower(key)
-
- switch lowerKey {
- case "layout":
- delete(metadata, key)
- case "permalink":
- if str, ok := value.(string); ok {
- metadata["url"] = str
- }
- delete(metadata, key)
- case "category":
- if str, ok := value.(string); ok {
- metadata["categories"] = []string{str}
- }
- delete(metadata, key)
- case "excerpt_separator":
- if key != lowerKey {
- delete(metadata, key)
- metadata[lowerKey] = value
- }
- case "date":
- if str, ok := value.(string); ok {
- re := regexp.MustCompile(`(\d+):(\d+):(\d+)`)
- r := re.FindAllStringSubmatch(str, -1)
- if len(r) > 0 {
- hour, _ := strconv.Atoi(r[0][1])
- minute, _ := strconv.Atoi(r[0][2])
- second, _ := strconv.Atoi(r[0][3])
- postDate = time.Date(postDate.Year(), postDate.Month(), postDate.Day(), hour, minute, second, 0, time.UTC)
- }
- }
- delete(metadata, key)
- }
-
- }
-
- metadata["date"] = postDate.Format(time.RFC3339)
-
- return metadata, nil
-}
-
-func convertJekyllContent(m interface{}, content string) (string, error) {
- metadata, _ := maps.ToStringMapE(m)
-
- lines := strings.Split(content, "\n")
- var resultLines []string
- for _, line := range lines {
- resultLines = append(resultLines, strings.Trim(line, "\r\n"))
- }
-
- content = strings.Join(resultLines, "\n")
-
- excerptSep := ""
- if value, ok := metadata["excerpt_separator"]; ok {
- if str, strOk := value.(string); strOk {
- content = strings.Replace(content, strings.TrimSpace(str), excerptSep, -1)
- }
- }
-
- replaceList := []struct {
- re *regexp.Regexp
- replace string
- }{
- {regexp.MustCompile("(?i)"), ""},
- {regexp.MustCompile(`\{%\s*raw\s*%\}\s*(.*?)\s*\{%\s*endraw\s*%\}`), "$1"},
- {regexp.MustCompile(`{%\s*endhighlight\s*%}`), "{{< / highlight >}}"},
- }
-
- for _, replace := range replaceList {
- content = replace.re.ReplaceAllString(content, replace.replace)
- }
-
- replaceListFunc := []struct {
- re *regexp.Regexp
- replace func(string) string
- }{
- // Octopress image tag: http://octopress.org/docs/plugins/image-tag/
- {regexp.MustCompile(`{%\s+img\s*(.*?)\s*%}`), replaceImageTag},
- {regexp.MustCompile(`{%\s*highlight\s*(.*?)\s*%}`), replaceHighlightTag},
- }
-
- for _, replace := range replaceListFunc {
- content = replace.re.ReplaceAllStringFunc(content, replace.replace)
- }
-
- var buf bytes.Buffer
- if len(metadata) != 0 {
- err := parser.InterfaceToFrontMatter(m, metadecoders.YAML, &buf)
- if err != nil {
- return "", err
- }
- }
- buf.WriteString(content)
-
- return buf.String(), nil
-}
-
-func replaceHighlightTag(match string) string {
- r := regexp.MustCompile(`{%\s*highlight\s*(.*?)\s*%}`)
- parts := r.FindStringSubmatch(match)
- lastQuote := rune(0)
- f := func(c rune) bool {
- switch {
- case c == lastQuote:
- lastQuote = rune(0)
- return false
- case lastQuote != rune(0):
- return false
- case unicode.In(c, unicode.Quotation_Mark):
- lastQuote = c
- return false
- default:
- return unicode.IsSpace(c)
- }
- }
- // splitting string by space but considering quoted section
- items := strings.FieldsFunc(parts[1], f)
-
- result := bytes.NewBufferString("{{< highlight ")
- result.WriteString(items[0]) // language
- options := items[1:]
- for i, opt := range options {
- opt = strings.Replace(opt, "\"", "", -1)
- if opt == "linenos" {
- opt = "linenos=table"
- }
- if i == 0 {
- opt = " \"" + opt
- }
- if i < len(options)-1 {
- opt += ","
- } else if i == len(options)-1 {
- opt += "\""
- }
- result.WriteString(opt)
- }
-
- result.WriteString(" >}}")
- return result.String()
-}
-
-func replaceImageTag(match string) string {
- r := regexp.MustCompile(`{%\s+img\s*(\p{L}*)\s+([\S]*/[\S]+)\s+(\d*)\s*(\d*)\s*(.*?)\s*%}`)
- result := bytes.NewBufferString("{{< figure ")
- parts := r.FindStringSubmatch(match)
- // Index 0 is the entire string, ignore
- replaceOptionalPart(result, "class", parts[1])
- replaceOptionalPart(result, "src", parts[2])
- replaceOptionalPart(result, "width", parts[3])
- replaceOptionalPart(result, "height", parts[4])
- // title + alt
- part := parts[5]
- if len(part) > 0 {
- splits := strings.Split(part, "'")
- lenSplits := len(splits)
- if lenSplits == 1 {
- replaceOptionalPart(result, "title", splits[0])
- } else if lenSplits == 3 {
- replaceOptionalPart(result, "title", splits[1])
- } else if lenSplits == 5 {
- replaceOptionalPart(result, "title", splits[1])
- replaceOptionalPart(result, "alt", splits[3])
- }
- }
- result.WriteString(">}}")
- return result.String()
-}
-
-func replaceOptionalPart(buffer *bytes.Buffer, partName string, part string) {
- if len(part) > 0 {
- buffer.WriteString(partName + "=\"" + part + "\" ")
- }
-}
diff --git a/commands/import_jekyll_test.go b/commands/import_jekyll_test.go
deleted file mode 100644
index 3819d03a5fd..00000000000
--- a/commands/import_jekyll_test.go
+++ /dev/null
@@ -1,177 +0,0 @@
-// Copyright 2015 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 (
- "encoding/json"
- "testing"
- "time"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestParseJekyllFilename(t *testing.T) {
- c := qt.New(t)
- filenameArray := []string{
- "2015-01-02-test.md",
- "2012-03-15-中文.markup",
- }
-
- expectResult := []struct {
- postDate time.Time
- postName string
- }{
- {time.Date(2015, time.January, 2, 0, 0, 0, 0, time.UTC), "test"},
- {time.Date(2012, time.March, 15, 0, 0, 0, 0, time.UTC), "中文"},
- }
-
- for i, filename := range filenameArray {
- postDate, postName, err := parseJekyllFilename(filename)
- c.Assert(err, qt.IsNil)
- c.Assert(expectResult[i].postDate.Format("2006-01-02"), qt.Equals, postDate.Format("2006-01-02"))
- c.Assert(expectResult[i].postName, qt.Equals, postName)
- }
-}
-
-func TestConvertJekyllMetadata(t *testing.T) {
- c := qt.New(t)
- testDataList := []struct {
- metadata interface{}
- postName string
- postDate time.Time
- draft bool
- expect string
- }{
- {
- map[interface{}]interface{}{},
- "testPost", time.Date(2015, 10, 1, 0, 0, 0, 0, time.UTC), false,
- `{"date":"2015-10-01T00:00:00Z"}`,
- },
- {
- map[interface{}]interface{}{},
- "testPost", time.Date(2015, 10, 1, 0, 0, 0, 0, time.UTC), true,
- `{"date":"2015-10-01T00:00:00Z","draft":true}`,
- },
- {
- map[interface{}]interface{}{"Permalink": "/permalink.html", "layout": "post"},
- "testPost", time.Date(2015, 10, 1, 0, 0, 0, 0, time.UTC), false,
- `{"date":"2015-10-01T00:00:00Z","url":"/permalink.html"}`,
- },
- {
- map[interface{}]interface{}{"permalink": "/permalink.html"},
- "testPost", time.Date(2015, 10, 1, 0, 0, 0, 0, time.UTC), false,
- `{"date":"2015-10-01T00:00:00Z","url":"/permalink.html"}`,
- },
- {
- map[interface{}]interface{}{"category": nil, "permalink": 123},
- "testPost", time.Date(2015, 10, 1, 0, 0, 0, 0, time.UTC), false,
- `{"date":"2015-10-01T00:00:00Z"}`,
- },
- {
- map[interface{}]interface{}{"Excerpt_Separator": "sep"},
- "testPost", time.Date(2015, 10, 1, 0, 0, 0, 0, time.UTC), false,
- `{"date":"2015-10-01T00:00:00Z","excerpt_separator":"sep"}`,
- },
- {
- map[interface{}]interface{}{"category": "book", "layout": "post", "Others": "Goods", "Date": "2015-10-01 12:13:11"},
- "testPost", time.Date(2015, 10, 1, 0, 0, 0, 0, time.UTC), false,
- `{"Others":"Goods","categories":["book"],"date":"2015-10-01T12:13:11Z"}`,
- },
- }
-
- for _, data := range testDataList {
- result, err := convertJekyllMetaData(data.metadata, data.postName, data.postDate, data.draft)
- c.Assert(err, qt.IsNil)
- jsonResult, err := json.Marshal(result)
- c.Assert(err, qt.IsNil)
- c.Assert(string(jsonResult), qt.Equals, data.expect)
- }
-}
-
-func TestConvertJekyllContent(t *testing.T) {
- c := qt.New(t)
- testDataList := []struct {
- metadata interface{}
- content string
- expect string
- }{
- {
- map[interface{}]interface{}{},
- "Test content\r\n\npart2 content", "Test content\n\npart2 content",
- },
- {
- map[interface{}]interface{}{},
- "Test content\n\npart2 content", "Test content\n\npart2 content",
- },
- {
- map[interface{}]interface{}{"excerpt_separator": ""},
- "Test content\n\npart2 content",
- "---\nexcerpt_separator: \n---\nTest content\n\npart2 content",
- },
- {map[interface{}]interface{}{}, "{% raw %}text{% endraw %}", "text"},
- {map[interface{}]interface{}{}, "{%raw%} text2 {%endraw %}", "text2"},
- {
- map[interface{}]interface{}{},
- "{% highlight go %}\nvar s int\n{% endhighlight %}",
- "{{< highlight go >}}\nvar s int\n{{< / highlight >}}",
- },
- {
- map[interface{}]interface{}{},
- "{% highlight go linenos hl_lines=\"1 2\" %}\nvar s string\nvar i int\n{% endhighlight %}",
- "{{< highlight go \"linenos=table,hl_lines=1 2\" >}}\nvar s string\nvar i int\n{{< / highlight >}}",
- },
-
- // Octopress image tag
- {
- map[interface{}]interface{}{},
- "{% img http://placekitten.com/890/280 %}",
- "{{< figure src=\"http://placekitten.com/890/280\" >}}",
- },
- {
- map[interface{}]interface{}{},
- "{% img left http://placekitten.com/320/250 Place Kitten #2 %}",
- "{{< figure class=\"left\" src=\"http://placekitten.com/320/250\" title=\"Place Kitten #2\" >}}",
- },
- {
- map[interface{}]interface{}{},
- "{% img right http://placekitten.com/300/500 150 250 'Place Kitten #3' %}",
- "{{< figure class=\"right\" src=\"http://placekitten.com/300/500\" width=\"150\" height=\"250\" title=\"Place Kitten #3\" >}}",
- },
- {
- map[interface{}]interface{}{},
- "{% img right http://placekitten.com/300/500 150 250 'Place Kitten #4' 'An image of a very cute kitten' %}",
- "{{< figure class=\"right\" src=\"http://placekitten.com/300/500\" width=\"150\" height=\"250\" title=\"Place Kitten #4\" alt=\"An image of a very cute kitten\" >}}",
- },
- {
- map[interface{}]interface{}{},
- "{% img http://placekitten.com/300/500 150 250 'Place Kitten #4' 'An image of a very cute kitten' %}",
- "{{< figure src=\"http://placekitten.com/300/500\" width=\"150\" height=\"250\" title=\"Place Kitten #4\" alt=\"An image of a very cute kitten\" >}}",
- },
- {
- map[interface{}]interface{}{},
- "{% img right /placekitten/300/500 'Place Kitten #4' 'An image of a very cute kitten' %}",
- "{{< figure class=\"right\" src=\"/placekitten/300/500\" title=\"Place Kitten #4\" alt=\"An image of a very cute kitten\" >}}",
- },
- {
- map[interface{}]interface{}{"category": "book", "layout": "post", "Date": "2015-10-01 12:13:11"},
- "somecontent",
- "---\nDate: \"2015-10-01 12:13:11\"\ncategory: book\nlayout: post\n---\nsomecontent",
- },
- }
- for _, data := range testDataList {
- result, err := convertJekyllContent(data.metadata, data.content)
- c.Assert(result, qt.Equals, data.expect)
- c.Assert(err, qt.IsNil)
- }
-}
diff --git a/commands/limit_darwin.go b/commands/limit_darwin.go
deleted file mode 100644
index 6799f37b131..00000000000
--- a/commands/limit_darwin.go
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// 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 (
- "syscall"
-
- "github.com/spf13/cobra"
- jww "github.com/spf13/jwalterweatherman"
-)
-
-var _ cmder = (*limitCmd)(nil)
-
-type limitCmd struct {
- *baseCmd
-}
-
-func newLimitCmd() *limitCmd {
- ccmd := &cobra.Command{
- Use: "ulimit",
- Short: "Check system ulimit settings",
- Long: `Hugo will inspect the current ulimit settings on the system.
-This is primarily to ensure that Hugo can watch enough files on some OSs`,
- RunE: func(cmd *cobra.Command, args []string) error {
- var rLimit syscall.Rlimit
- err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
- if err != nil {
- return newSystemError("Error Getting rlimit ", err)
- }
-
- jww.FEEDBACK.Println("Current rLimit:", rLimit)
-
- if rLimit.Cur >= newRlimit {
- return nil
- }
-
- jww.FEEDBACK.Println("Attempting to increase limit")
- rLimit.Cur = newRlimit
- err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
- if err != nil {
- return newSystemError("Error Setting rLimit ", err)
- }
- err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
- if err != nil {
- return newSystemError("Error Getting rLimit ", err)
- }
- jww.FEEDBACK.Println("rLimit after change:", rLimit)
-
- return nil
- },
- }
-
- return &limitCmd{baseCmd: newBaseCmd(ccmd)}
-}
-
-const newRlimit = 10240
-
-func tweakLimit() {
- var rLimit syscall.Rlimit
- err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
- if err != nil {
- jww.WARN.Println("Unable to get rlimit:", err)
- return
- }
- if rLimit.Cur < newRlimit {
- rLimit.Cur = newRlimit
- err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
- if err != nil {
- // This may not succeed, see https://github.com/golang/go/issues/30401
- jww.INFO.Println("Unable to increase number of open files limit:", err)
- }
- }
-}
diff --git a/commands/limit_others.go b/commands/limit_others.go
deleted file mode 100644
index 8d3e6ad7060..00000000000
--- a/commands/limit_others.go
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// 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.
-
-// +build !darwin
-
-package commands
-
-func tweakLimit() {
- // nothing to do
-}
diff --git a/commands/list.go b/commands/list.go
index d62672f61db..42f3408ba09 100644
--- a/commands/list.go
+++ b/commands/list.go
@@ -1,4 +1,4 @@
-// Copyright 2019 The Hugo Authors. All rights reserved.
+// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,189 +14,200 @@
package commands
import (
+ "context"
"encoding/csv"
"os"
+ "path/filepath"
"strconv"
"strings"
"time"
+ "github.com/bep/simplecobra"
"github.com/gohugoio/hugo/hugolib"
+ "github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/resources/resource"
"github.com/spf13/cobra"
- jww "github.com/spf13/jwalterweatherman"
)
-var _ cmder = (*listCmd)(nil)
-
-type listCmd struct {
- *baseBuilderCmd
-}
-
-func (lc *listCmd) buildSites(config map[string]interface{}) (*hugolib.HugoSites, error) {
- cfgInit := func(c *commandeer) error {
- for key, value := range config {
- c.Set(key, value)
+// newListCommand creates a new list command and its subcommands.
+func newListCommand() *listCommand {
+ createRecord := func(workingDir string, p page.Page) []string {
+ return []string{
+ filepath.ToSlash(strings.TrimPrefix(p.File().Filename(), workingDir+string(os.PathSeparator))),
+ p.Slug(),
+ p.Title(),
+ p.Date().Format(time.RFC3339),
+ p.ExpiryDate().Format(time.RFC3339),
+ p.PublishDate().Format(time.RFC3339),
+ strconv.FormatBool(p.Draft()),
+ p.Permalink(),
+ p.Kind(),
+ p.Section(),
}
- return nil
}
- c, err := initializeConfig(true, true, false, &lc.hugoBuilderCommon, lc, cfgInit)
- if err != nil {
- return nil, err
- }
-
- sites, err := hugolib.NewHugoSites(*c.DepsCfg)
- if err != nil {
- return nil, newSystemError("Error creating sites", err)
- }
-
- if err := sites.Build(hugolib.BuildCfg{SkipRender: true}); err != nil {
- return nil, newSystemError("Error Processing Source Content", err)
- }
-
- return sites, nil
-}
-
-func (b *commandsBuilder) newListCmd() *listCmd {
- cc := &listCmd{}
+ list := func(cd *simplecobra.Commandeer, r *rootCommand, shouldInclude func(page.Page) bool, opts ...any) error {
+ bcfg := hugolib.BuildCfg{SkipRender: true}
+ cfg := flagsToCfg(cd, nil)
+ for i := 0; i < len(opts); i += 2 {
+ cfg.Set(opts[i].(string), opts[i+1])
+ }
+ h, err := r.Build(cd, bcfg, cfg)
+ if err != nil {
+ return err
+ }
- cmd := &cobra.Command{
- Use: "list",
- Short: "Listing out various types of content",
- Long: `Listing out various types of content.
+ writer := csv.NewWriter(r.StdOut)
+ defer writer.Flush()
+
+ writer.Write([]string{
+ "path",
+ "slug",
+ "title",
+ "date",
+ "expiryDate",
+ "publishDate",
+ "draft",
+ "permalink",
+ "kind",
+ "section",
+ })
+
+ for _, p := range h.Pages() {
+ if shouldInclude(p) {
+ record := createRecord(h.Conf.BaseConfig().WorkingDir, p)
+ if err := writer.Write(record); err != nil {
+ return err
+ }
+ }
+ }
-List requires a subcommand, e.g. ` + "`hugo list drafts`.",
- RunE: nil,
+ return nil
}
- cmd.AddCommand(
- &cobra.Command{
- Use: "drafts",
- Short: "List all drafts",
- Long: `List all of the drafts in your content directory.`,
- RunE: func(cmd *cobra.Command, args []string) error {
- sites, err := cc.buildSites(map[string]interface{}{"buildDrafts": true})
- if err != nil {
- return newSystemError("Error building sites", err)
- }
-
- for _, p := range sites.Pages() {
- if p.Draft() {
- jww.FEEDBACK.Println(strings.TrimPrefix(p.File().Filename(), sites.WorkingDir+string(os.PathSeparator)))
+ return &listCommand{
+ commands: []simplecobra.Commander{
+ &simpleCommand{
+ name: "drafts",
+ short: "List draft content",
+ long: `List draft content.`,
+ run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
+ shouldInclude := func(p page.Page) bool {
+ if !p.Draft() || p.File() == nil {
+ return false
+ }
+ return true
}
- }
-
- return nil
+ return list(cd, r, shouldInclude,
+ "buildDrafts", true,
+ "buildFuture", true,
+ "buildExpired", true,
+ )
+ },
+ withc: func(cmd *cobra.Command, r *rootCommand) {
+ cmd.ValidArgsFunction = cobra.NoFileCompletions
+ },
},
- },
- &cobra.Command{
- Use: "future",
- Short: "List all posts dated in the future",
- Long: `List all of the posts in your content directory which will be posted in the future.`,
- RunE: func(cmd *cobra.Command, args []string) error {
- sites, err := cc.buildSites(map[string]interface{}{"buildFuture": true})
- if err != nil {
- return newSystemError("Error building sites", err)
- }
-
- writer := csv.NewWriter(os.Stdout)
- defer writer.Flush()
-
- for _, p := range sites.Pages() {
- if resource.IsFuture(p) {
- err := writer.Write([]string{
- strings.TrimPrefix(p.File().Filename(), sites.WorkingDir+string(os.PathSeparator)),
- p.PublishDate().Format(time.RFC3339),
- })
- if err != nil {
- return newSystemError("Error writing future posts to stdout", err)
+ &simpleCommand{
+ name: "future",
+ short: "List future content",
+ long: `List content with a future publication date.`,
+ run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
+ shouldInclude := func(p page.Page) bool {
+ if !resource.IsFuture(p) || p.File() == nil {
+ return false
}
+ return true
}
- }
-
- return nil
+ return list(cd, r, shouldInclude,
+ "buildFuture", true,
+ "buildDrafts", true,
+ )
+ },
+ withc: func(cmd *cobra.Command, r *rootCommand) {
+ cmd.ValidArgsFunction = cobra.NoFileCompletions
+ },
},
- },
- &cobra.Command{
- Use: "expired",
- Short: "List all posts already expired",
- Long: `List all of the posts in your content directory which has already expired.`,
- RunE: func(cmd *cobra.Command, args []string) error {
- sites, err := cc.buildSites(map[string]interface{}{"buildExpired": true})
- if err != nil {
- return newSystemError("Error building sites", err)
- }
-
- writer := csv.NewWriter(os.Stdout)
- defer writer.Flush()
-
- for _, p := range sites.Pages() {
- if resource.IsExpired(p) {
- err := writer.Write([]string{
- strings.TrimPrefix(p.File().Filename(), sites.WorkingDir+string(os.PathSeparator)),
- p.ExpiryDate().Format(time.RFC3339),
- })
- if err != nil {
- return newSystemError("Error writing expired posts to stdout", err)
+ &simpleCommand{
+ name: "expired",
+ short: "List expired content",
+ long: `List content with a past expiration date.`,
+ run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
+ shouldInclude := func(p page.Page) bool {
+ if !resource.IsExpired(p) || p.File() == nil {
+ return false
}
+ return true
}
- }
-
- return nil
+ return list(cd, r, shouldInclude,
+ "buildExpired", true,
+ "buildDrafts", true,
+ )
+ },
+ withc: func(cmd *cobra.Command, r *rootCommand) {
+ cmd.ValidArgsFunction = cobra.NoFileCompletions
+ },
},
- },
- &cobra.Command{
- Use: "all",
- Short: "List all posts",
- Long: `List all of the posts in your content directory, include drafts, future and expired pages.`,
- RunE: func(cmd *cobra.Command, args []string) error {
- sites, err := cc.buildSites(map[string]interface{}{
- "buildExpired": true,
- "buildDrafts": true,
- "buildFuture": true,
- })
- if err != nil {
- return newSystemError("Error building sites", err)
- }
-
- writer := csv.NewWriter(os.Stdout)
- defer writer.Flush()
-
- writer.Write([]string{
- "path",
- "slug",
- "title",
- "date",
- "expiryDate",
- "publishDate",
- "draft",
- "permalink",
- })
- for _, p := range sites.Pages() {
- if !p.IsPage() {
- continue
+ &simpleCommand{
+ name: "all",
+ short: "List all content",
+ long: `List all content including draft, future, and expired.`,
+ run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
+ shouldInclude := func(p page.Page) bool {
+ return p.File() != nil
}
- err := writer.Write([]string{
- strings.TrimPrefix(p.File().Filename(), sites.WorkingDir+string(os.PathSeparator)),
- p.Slug(),
- p.Title(),
- p.Date().Format(time.RFC3339),
- p.ExpiryDate().Format(time.RFC3339),
- p.PublishDate().Format(time.RFC3339),
- strconv.FormatBool(p.Draft()),
- p.Permalink(),
- })
- if err != nil {
- return newSystemError("Error writing posts to stdout", err)
+ return list(cd, r, shouldInclude, "buildDrafts", true, "buildFuture", true, "buildExpired", true)
+ },
+ withc: func(cmd *cobra.Command, r *rootCommand) {
+ cmd.ValidArgsFunction = cobra.NoFileCompletions
+ },
+ },
+ &simpleCommand{
+ name: "published",
+ short: "List published content",
+ long: `List content that is not draft, future, or expired.`,
+ run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
+ shouldInclude := func(p page.Page) bool {
+ return !p.Draft() && !resource.IsFuture(p) && !resource.IsExpired(p) && p.File() != nil
}
- }
-
- return nil
+ return list(cd, r, shouldInclude)
+ },
+ withc: func(cmd *cobra.Command, r *rootCommand) {
+ cmd.ValidArgsFunction = cobra.NoFileCompletions
+ },
},
},
- )
+ }
+}
+
+type listCommand struct {
+ commands []simplecobra.Commander
+}
+
+func (c *listCommand) Commands() []simplecobra.Commander {
+ return c.commands
+}
+
+func (c *listCommand) Name() string {
+ return "list"
+}
+
+func (c *listCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
+ // Do nothing.
+ return nil
+}
- cc.baseBuilderCmd = b.newBuilderBasicCmd(cmd)
+func (c *listCommand) Init(cd *simplecobra.Commandeer) error {
+ cmd := cd.CobraCommand
+ cmd.Short = "List content"
+ cmd.Long = `List content.
+
+List requires a subcommand, e.g. hugo list drafts`
+
+ cmd.RunE = nil
+ return nil
+}
- return cc
+func (c *listCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
+ return nil
}
diff --git a/commands/list_test.go b/commands/list_test.go
deleted file mode 100644
index 6f3d6c74df3..00000000000
--- a/commands/list_test.go
+++ /dev/null
@@ -1,71 +0,0 @@
-package commands
-
-import (
- "bytes"
- "encoding/csv"
- "io"
- "os"
- "path/filepath"
- "strings"
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func captureStdout(f func() error) (string, error) {
- old := os.Stdout
- r, w, _ := os.Pipe()
- os.Stdout = w
-
- err := f()
-
- w.Close()
- os.Stdout = old
-
- var buf bytes.Buffer
- io.Copy(&buf, r)
- return buf.String(), err
-}
-
-func TestListAll(t *testing.T) {
- c := qt.New(t)
- dir, clean, err := createSimpleTestSite(t, testSiteConfig{})
- defer clean()
-
- c.Assert(err, qt.IsNil)
-
- hugoCmd := newCommandsBuilder().addAll().build()
- cmd := hugoCmd.getCommand()
-
- defer func() {
- os.RemoveAll(dir)
- }()
-
- cmd.SetArgs([]string{"-s=" + dir, "list", "all"})
-
- out, err := captureStdout(func() error {
- _, err := cmd.ExecuteC()
- return err
- })
- c.Assert(err, qt.IsNil)
-
- r := csv.NewReader(strings.NewReader(out))
-
- header, err := r.Read()
-
- c.Assert(err, qt.IsNil)
- c.Assert(header, qt.DeepEquals, []string{
- "path", "slug", "title",
- "date", "expiryDate", "publishDate",
- "draft", "permalink",
- })
-
- record, err := r.Read()
-
- c.Assert(err, qt.IsNil)
- c.Assert(record, qt.DeepEquals, []string{
- filepath.Join("content", "p1.md"), "", "P1",
- "0001-01-01T00:00:00Z", "0001-01-01T00:00:00Z", "0001-01-01T00:00:00Z",
- "false", "https://example.org/p1/",
- })
-}
diff --git a/commands/mod.go b/commands/mod.go
index eb6b1a44042..eed9cfb0f37 100644
--- a/commands/mod.go
+++ b/commands/mod.go
@@ -1,4 +1,4 @@
-// Copyright 2020 The Hugo Authors. All rights reserved.
+// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,280 +14,341 @@
package commands
import (
+ "context"
"errors"
- "fmt"
"os"
"path/filepath"
- "regexp"
+ "github.com/bep/simplecobra"
+ "github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/hugolib"
-
- "github.com/gohugoio/hugo/modules"
+ "github.com/gohugoio/hugo/modules/npm"
"github.com/spf13/cobra"
)
-var _ cmder = (*modCmd)(nil)
-
-type modCmd struct {
- *baseBuilderCmd
-}
-
-func (c *modCmd) newVerifyCmd() *cobra.Command {
- var clean bool
+const commonUsageMod = `
+Note that Hugo will always start out by resolving the components defined in the project
+configuration, provided by a _vendor directory (if no --ignoreVendorPaths flag provided),
+Go Modules, or a folder inside the themes directory, in that order.
- verifyCmd := &cobra.Command{
- Use: "verify",
- Short: "Verify dependencies.",
- Long: `Verify checks that the dependencies of the current module, which are stored in a local downloaded source cache, have not been modified since being downloaded.
-`,
- RunE: func(cmd *cobra.Command, args []string) error {
- return c.withModsClient(true, func(c *modules.Client) error {
- return c.Verify(clean)
- })
- },
- }
+See https://gohugo.io/hugo-modules/ for more information.
- verifyCmd.Flags().BoolVarP(&clean, "clean", "", false, "delete module cache for dependencies that fail verification")
+`
- return verifyCmd
-}
+// buildConfigCommands creates a new config command and its subcommands.
+func newModCommands() *modCommands {
+ var (
+ clean bool
+ pattern string
+ all bool
+ )
-var moduleNotFoundRe = regexp.MustCompile("module.*not found")
+ npmCommand := &simpleCommand{
+ name: "npm",
+ short: "Various npm helpers",
+ long: `Various npm (Node package manager) helpers.`,
+ commands: []simplecobra.Commander{
+ &simpleCommand{
+ name: "pack",
+ short: "Merges module Node.js dependencies into an npm workspace",
+ long: `Merges Node.js dependencies from all Hugo modules into a "packages/hugoautogen" npm workspace.
-func (c *modCmd) newCleanCmd() *cobra.Command {
- var pattern string
- var all bool
- cmd := &cobra.Command{
- Use: "clean",
- Short: "Delete the Hugo Module cache for the current project.",
- Long: `Delete the Hugo Module cache for the current project.
+The merged dependencies are written to packages/hugoautogen/package.json, and the root package.json
+is updated with a "workspaces" entry pointing to "packages/hugoautogen".
-Note that after you run this command, all of your dependencies will be re-downloaded next time you run "hugo".
+The source entries are read from either package.hugo.json or package.json in the module root, with package.hugo.json taking precedence if both exist.
-Also note that if you configure a positive maxAge for the "modules" file cache, it will also be cleaned as part of "hugo --gc".
-
+See [Node.js dependencies](/hugo-modules/nodejs-dependencies/) for more information.
`,
- RunE: func(cmd *cobra.Command, args []string) error {
- if all {
- com, err := c.initConfig(false)
-
- if err != nil && com == nil {
- return err
- }
-
- count, err := com.hugo().FileCaches.ModulesCache().Prune(true)
- com.logger.Printf("Deleted %d files from module cache.", count)
- return err
- }
- return c.withModsClient(true, func(c *modules.Client) error {
- return c.Clean(pattern)
- })
+ withc: func(cmd *cobra.Command, r *rootCommand) {
+ cmd.ValidArgsFunction = cobra.NoFileCompletions
+ applyLocalFlagsBuildConfig(cmd, r)
+ },
+ run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
+ cfg := flagsToCfg(cd, nil)
+ k := configKey{counter: r.configVersionID.Load(), skipNpmCheck: true}
+ h, _, err := r.hugoSites.GetOrCreate(k, func(key configKey) (*hugolib.HugoSites, error) {
+ conf, err := r.ConfigFromProvider(key, cfg)
+ if err != nil {
+ return nil, err
+ }
+ return hugolib.NewHugoSites(r.newDepsConfig(conf))
+ })
+ if err != nil {
+ return err
+ }
+ return npm.Pack(h.BaseFs.ProjectSourceFs, h.BaseFs.AssetsWithDuplicatesPreserved.Fs, h.Configs.Modules)
+ },
+ },
},
}
- cmd.Flags().StringVarP(&pattern, "pattern", "", "", `pattern matching module paths to clean (all if not set), e.g. "**hugo*"`)
- cmd.Flags().BoolVarP(&all, "all", "", false, "clean entire module cache")
-
- return cmd
-}
-
-func (b *commandsBuilder) newModCmd() *modCmd {
- c := &modCmd{}
-
- const commonUsage = `
-Note that Hugo will always start out by resolving the components defined in the site
-configuration, provided by a _vendor directory (if no --ignoreVendor flag provided),
-Go Modules, or a folder inside the themes directory, in that order.
-
-See https://gohugo.io/hugo-modules/ for more information.
-
-`
-
- cmd := &cobra.Command{
- Use: "mod",
- Short: "Various Hugo Modules helpers.",
- 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".
-
-` + commonUsage,
-
- RunE: nil,
- }
+ return &modCommands{
+ commands: []simplecobra.Commander{
+ &simpleCommand{
+ name: "init",
+ short: "Initialize this project as a Hugo Module",
+ long: `Initialize this project as a Hugo Module.
+ It will try to guess the module path, but you may help by passing it as an argument, e.g:
+
+ hugo mod init github.com/gohugoio/testshortcodes
+
+ Note that Hugo Modules supports multi-module projects, so you can initialize a Hugo Module
+ inside a subfolder on GitHub, as one example.
+ `,
+ withc: func(cmd *cobra.Command, r *rootCommand) {
+ cmd.ValidArgsFunction = cobra.NoFileCompletions
+ applyLocalFlagsBuildConfig(cmd, r)
+ },
+ run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
+ h, err := r.getOrCreateHugo(flagsToCfg(cd, nil), true)
+ if err != nil {
+ return err
+ }
+ var initPath string
+ if len(args) >= 1 {
+ initPath = args[0]
+ }
+ c := h.Configs.ModulesClient
+ if err := c.Init(initPath); err != nil {
+ return err
+ }
+ return nil
+ },
+ },
+ &simpleCommand{
+ name: "verify",
+ short: "Verify dependencies",
+ long: `Verify checks that the dependencies of the current module, which are stored in a local downloaded source cache, have not been modified since being downloaded.`,
+ withc: func(cmd *cobra.Command, r *rootCommand) {
+ cmd.ValidArgsFunction = cobra.NoFileCompletions
+ applyLocalFlagsBuildConfig(cmd, r)
+ 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))
+ if err != nil {
+ return err
+ }
+ client := conf.configs.ModulesClient
+ return client.Verify(clean)
+ },
+ },
+ &simpleCommand{
+ name: "graph",
+ short: "Print a module dependency graph",
+ long: `Print a module dependency graph with information about module status (disabled, vendored).
+Note that for vendored modules, that is the version listed and not the one from go.mod.
+`,
+ withc: func(cmd *cobra.Command, r *rootCommand) {
+ cmd.ValidArgsFunction = cobra.NoFileCompletions
+ applyLocalFlagsBuildConfig(cmd, r)
+ 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))
+ if err != nil {
+ return err
+ }
+ client := conf.configs.ModulesClient
+ return client.Graph(os.Stdout)
+ },
+ },
+ &simpleCommand{
+ name: "clean",
+ short: "Delete the Hugo Module cache for the current project",
+ long: `Delete the Hugo Module cache for the current project.`,
+ withc: func(cmd *cobra.Command, r *rootCommand) {
+ cmd.ValidArgsFunction = cobra.NoFileCompletions
+ applyLocalFlagsBuildConfig(cmd, r)
+ cmd.Flags().StringVarP(&pattern, "pattern", "", "", `pattern matching module paths to clean (all if not set), e.g. "**hugo*"`)
+ _ = cmd.RegisterFlagCompletionFunc("pattern", cobra.NoFileCompletions)
+ cmd.Flags().BoolVarP(&all, "all", "", false, "clean entire module cache")
+ },
+ run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
+ h, err := r.Hugo(flagsToCfg(cd, nil))
+ if err != nil {
+ return err
+ }
+ if all {
+ modCache := h.ResourceSpec.FileCaches.ModulesCache()
+ count, err := modCache.Prune(true)
+ r.Printf("Deleted %d directories from module cache.", count)
+ return err
+ }
- cmd.AddCommand(newModNPMCmd(c))
+ return h.Configs.ModulesClient.Clean(pattern)
+ },
+ },
+ &simpleCommand{
+ name: "tidy",
+ short: "Remove unused entries in go.mod and go.sum",
+ withc: func(cmd *cobra.Command, r *rootCommand) {
+ cmd.ValidArgsFunction = cobra.NoFileCompletions
+ applyLocalFlagsBuildConfig(cmd, r)
+ },
+ run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
+ h, err := r.Hugo(flagsToCfg(cd, nil))
+ if err != nil {
+ return err
+ }
+ return h.Configs.ModulesClient.Tidy()
+ },
+ },
+ &simpleCommand{
+ name: "vendor",
+ short: "Vendor all module dependencies into the _vendor directory",
+ long: `Vendor all module dependencies into the _vendor directory.
+ If a module is vendored, that is where Hugo will look for it's dependencies.
+ `,
+ withc: func(cmd *cobra.Command, r *rootCommand) {
+ cmd.ValidArgsFunction = cobra.NoFileCompletions
+ applyLocalFlagsBuildConfig(cmd, r)
+ },
+ run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
+ h, err := r.Hugo(flagsToCfg(cd, nil))
+ if err != nil {
+ return err
+ }
+ return h.Configs.ModulesClient.Vendor()
+ },
+ },
- cmd.AddCommand(
- &cobra.Command{
- Use: "get",
- DisableFlagParsing: true,
- Short: "Resolves dependencies in your current Hugo Project.",
- Long: `
-Resolves dependencies in your current Hugo Project.
+ &simpleCommand{
+ name: "get",
+ short: "Resolves dependencies in your current Hugo project",
+ long: `
+Resolves dependencies in your current Hugo project.
Some examples:
Install the latest version possible for a given module:
hugo mod get github.com/gohugoio/testshortcodes
-
+
Install a specific version:
hugo mod get github.com/gohugoio/testshortcodes@v0.3.0
-Install the latest versions of all module dependencies:
+Install the latest versions of all direct module dependencies:
+
+ hugo mod get
+ hugo mod get ./... (recursive)
+
+Install the latest versions of all module dependencies (direct and indirect):
hugo mod get -u
hugo mod get -u ./... (recursive)
Run "go help get" for more information. All flags available for "go get" is also relevant here.
-` + commonUsage,
- RunE: func(cmd *cobra.Command, args []string) error {
- // We currently just pass on the flags we get to Go and
- // need to do the flag handling manually.
- if len(args) == 1 && args[0] == "-h" {
- return cmd.Help()
- }
-
- var lastArg string
- if len(args) != 0 {
- lastArg = args[len(args)-1]
- }
-
- if lastArg == "./..." {
- args = args[:len(args)-1]
- // Do a recursive update.
- dirname, err := os.Getwd()
- if err != nil {
- return err
+` + commonUsageMod,
+ withc: func(cmd *cobra.Command, r *rootCommand) {
+ cmd.DisableFlagParsing = true
+ cmd.ValidArgsFunction = cobra.NoFileCompletions
+ },
+ run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
+ // We currently just pass on the flags we get to Go and
+ // need to do the flag handling manually.
+ if len(args) == 1 && (args[0] == "-h" || args[0] == "--help") {
+ return errHelp
}
- // Sanity check. We do recursive walking and want to avoid
- // accidents.
- if len(dirname) < 5 {
- return errors.New("must not be run from the file system root")
+ var lastArg string
+ if len(args) != 0 {
+ lastArg = args[len(args)-1]
}
- filepath.Walk(dirname, func(path string, info os.FileInfo, err error) error {
- if info.IsDir() {
- return nil
+ if lastArg == "./..." {
+ args = args[:len(args)-1]
+ // Do a recursive update.
+ dirname, err := os.Getwd()
+ if err != nil {
+ return err
}
- if info.Name() == "go.mod" {
- // Found a module.
- dir := filepath.Dir(path)
- fmt.Println("Update module in", dir)
- c.source = dir
- err := c.withModsClient(false, func(c *modules.Client) error {
- if len(args) == 1 && args[0] == "-h" {
- return cmd.Help()
- }
- return c.Get(args...)
- })
- if err != nil {
- return err
+ // Sanity chesimplecobra. We do recursive walking and want to avoid
+ // accidents.
+ if len(dirname) < 5 {
+ return errors.New("must not be run from the file system root")
+ }
+
+ filepath.Walk(dirname, func(path string, info os.FileInfo, err error) error {
+ if info.IsDir() {
+ return nil
}
+ if info.Name() == "go.mod" {
+ // Found a module.
+ dir := filepath.Dir(path)
+
+ cfg := config.New()
+ cfg.Set("workingDir", dir)
+ conf, err := r.ConfigFromProvider(configKey{counter: r.configVersionID.Add(1)}, 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...)
+ }
+ return nil
+ })
+ return nil
+ } else {
+ conf, err := r.ConfigFromProvider(configKey{counter: r.configVersionID.Load()}, flagsToCfg(cd, nil))
+ if err != nil {
+ return err
+ }
+ client := conf.configs.ModulesClient
+ if err := client.Get(args...); err != nil {
+ return err
}
-
return nil
- })
-
- return nil
- }
-
- return c.withModsClient(false, func(c *modules.Client) error {
- return c.Get(args...)
- })
- },
- },
- &cobra.Command{
- Use: "graph",
- Short: "Print a module dependency graph.",
- Long: `Print a module dependency graph with information about module status (disabled, vendored).
-Note that for vendored modules, that is the version listed and not the one from go.mod.
-`,
- RunE: func(cmd *cobra.Command, args []string) error {
- return c.withModsClient(true, func(c *modules.Client) error {
- return c.Graph(os.Stdout)
- })
+ }
+ },
},
+ npmCommand,
},
- &cobra.Command{
- Use: "init",
- Short: "Initialize this project as a Hugo Module.",
- Long: `Initialize this project as a Hugo Module.
-It will try to guess the module path, but you may help by passing it as an argument, e.g:
-
- hugo mod init github.com/gohugoio/testshortcodes
+ }
+}
-Note that Hugo Modules supports multi-module projects, so you can initialize a Hugo Module
-inside a subfolder on GitHub, as one example.
-`,
- RunE: func(cmd *cobra.Command, args []string) error {
- var path string
- if len(args) >= 1 {
- path = args[0]
- }
- return c.withModsClient(false, func(c *modules.Client) error {
- return c.Init(path)
- })
- },
- },
- &cobra.Command{
- Use: "vendor",
- Short: "Vendor all module dependencies into the _vendor directory.",
- Long: `Vendor all module dependencies into the _vendor directory.
+type modCommands struct {
+ r *rootCommand
-If a module is vendored, that is where Hugo will look for it's dependencies.
-`,
- RunE: func(cmd *cobra.Command, args []string) error {
- return c.withModsClient(true, func(c *modules.Client) error {
- return c.Vendor()
- })
- },
- },
- c.newVerifyCmd(),
- &cobra.Command{
- Use: "tidy",
- Short: "Remove unused entries in go.mod and go.sum.",
- RunE: func(cmd *cobra.Command, args []string) error {
- return c.withModsClient(true, func(c *modules.Client) error {
- return c.Tidy()
- })
- },
- },
- c.newCleanCmd(),
- )
+ commands []simplecobra.Commander
+}
- c.baseBuilderCmd = b.newBuilderCmd(cmd)
+func (c *modCommands) Commands() []simplecobra.Commander {
+ return c.commands
+}
- return c
+func (c *modCommands) Name() string {
+ return "mod"
}
-func (c *modCmd) withModsClient(failOnMissingConfig bool, f func(*modules.Client) error) error {
- com, err := c.initConfig(failOnMissingConfig)
+func (c *modCommands) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
+ _, err := c.r.ConfigFromProvider(configKey{counter: c.r.configVersionID.Load()}, nil)
if err != nil {
return err
}
+ // config := conf.configs.Base
- return f(com.hugo().ModulesClient)
+ return nil
}
-func (c *modCmd) withHugo(f func(*hugolib.HugoSites) error) error {
- com, err := c.initConfig(true)
- if err != nil {
- return err
- }
+func (c *modCommands) Init(cd *simplecobra.Commandeer) error {
+ cmd := cd.CobraCommand
+ cmd.Short = "Manage modules"
+ 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".
- return f(com.hugo())
+` + commonUsageMod
+ cmd.RunE = nil
+ return nil
}
-func (c *modCmd) initConfig(failOnNoConfig bool) (*commandeer, error) {
- com, err := initializeConfig(failOnNoConfig, false, false, &c.hugoBuilderCommon, c, nil)
- if err != nil {
- return nil, err
- }
- return com, nil
+func (c *modCommands) PreRun(cd, runner *simplecobra.Commandeer) error {
+ c.r = cd.Root.Command.(*rootCommand)
+ return nil
}
diff --git a/commands/mod_npm.go b/commands/mod_npm.go
deleted file mode 100644
index 852d98571b4..00000000000
--- a/commands/mod_npm.go
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2020 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/hugolib"
- "github.com/gohugoio/hugo/modules/npm"
- "github.com/spf13/cobra"
-)
-
-func newModNPMCmd(c *modCmd) *cobra.Command {
- cmd := &cobra.Command{
- Use: "npm",
- Short: "Various npm helpers.",
- Long: `Various npm (Node package manager) helpers.`,
- RunE: func(cmd *cobra.Command, args []string) error {
- return c.withHugo(func(h *hugolib.HugoSites) error {
- return nil
- })
- },
- }
-
- cmd.AddCommand(&cobra.Command{
- Use: "pack",
- Short: "Experimental: Prepares and writes a composite package.json file for your project.",
- Long: `Prepares and writes a composite package.json file for your project.
-
-On first run it creates a "package.hugo.json" in the project root if not already there. This file will be used as a template file
-with the base dependency set.
-
-This set will be merged with all "package.hugo.json" files found in the dependency tree, picking the version closest to the project.
-
-This command is marked as 'Experimental'. We think it's a great idea, so it's not likely to be
-removed from Hugo, but we need to test this out in "real life" to get a feel of it,
-so this may/will change in future versions of Hugo.
-`,
- RunE: func(cmd *cobra.Command, args []string) error {
- return c.withHugo(func(h *hugolib.HugoSites) error {
- return npm.Pack(h.BaseFs.SourceFs, h.BaseFs.Assets.Dirs)
- })
- },
- })
-
- return cmd
-}
diff --git a/commands/new.go b/commands/new.go
index c5b5cd18271..e82258a797c 100644
--- a/commands/new.go
+++ b/commands/new.go
@@ -1,4 +1,4 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
+// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,112 +15,216 @@ package commands
import (
"bytes"
- "os"
+ "context"
"path/filepath"
"strings"
+ "github.com/bep/simplecobra"
+ "github.com/gohugoio/hugo/common/paths"
+ "github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/create"
- "github.com/gohugoio/hugo/helpers"
- "github.com/gohugoio/hugo/hugolib"
- "github.com/spf13/afero"
+ "github.com/gohugoio/hugo/create/skeletons"
"github.com/spf13/cobra"
- jww "github.com/spf13/jwalterweatherman"
)
-var _ cmder = (*newCmd)(nil)
-
-type newCmd struct {
- contentEditor string
- contentType string
-
- *baseBuilderCmd
-}
-
-func (b *commandsBuilder) newNewCmd() *newCmd {
- cmd := &cobra.Command{
- Use: "new [path]",
- Short: "Create new content for your site",
- Long: `Create a new content file and automatically set the date and title.
+func newNewCommand() *newCommand {
+ var (
+ force bool
+ contentType string
+ format string
+ )
+
+ var c *newCommand
+ c = &newCommand{
+ commands: []simplecobra.Commander{
+ &simpleCommand{
+ name: "content",
+ use: "content [path]",
+ short: "Create new content",
+ 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.
You can also specify the kind with ` + "`-k KIND`" + `.
-If archetypes are provided in your theme or site, they will be used.
-
-Ensure you run this within the root directory of your site.`,
+If archetypes are provided in your theme or project, they will be used.
+
+Ensure you run this within the root directory of your project.`,
+ run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
+ if len(args) < 1 {
+ return newUserError("path needs to be provided")
+ }
+ cfg := flagsToCfg(cd, nil)
+ cfg.Set("BuildFuture", true)
+ h, err := r.Hugo(cfg)
+ if err != nil {
+ return err
+ }
+ return create.NewContent(h, contentType, args[0], force)
+ },
+ withc: func(cmd *cobra.Command, r *rootCommand) {
+ cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+ if len(args) != 0 {
+ return []string{}, cobra.ShellCompDirectiveNoFileComp
+ }
+ return []string{}, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveFilterDirs
+ }
+ cmd.Flags().StringVarP(&contentType, "kind", "k", "", "content type to create")
+ cmd.Flags().String("editor", "", "edit new content with this editor, if provided")
+ _ = cmd.RegisterFlagCompletionFunc("editor", cobra.NoFileCompletions)
+ cmd.Flags().BoolVarP(&force, "force", "f", false, "overwrite file if it already exists")
+ applyLocalFlagsBuildConfig(cmd, r)
+ },
+ },
+ &simpleCommand{
+ name: "project",
+ use: "project [path]",
+ short: "Create a new project",
+ long: `Create a new project at the specified path.`,
+ aliases: []string{"site"},
+ run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
+ if len(args) < 1 {
+ return newUserError("path needs to be provided")
+ }
+ createpath, err := filepath.Abs(filepath.Clean(args[0]))
+ if err != nil {
+ return err
+ }
+
+ cfg := config.New()
+ cfg.Set("workingDir", createpath)
+ cfg.Set("publishDir", "public")
+
+ conf, err := r.ConfigFromProvider(configKey{counter: r.configVersionID.Load()}, flagsToCfg(cd, cfg))
+ if err != nil {
+ return err
+ }
+ sourceFs := conf.fs.Source
+
+ err = skeletons.CreateProject(createpath, sourceFs, force, format)
+ if err != nil {
+ return err
+ }
+
+ r.Printf("Congratulations! Your new Hugo project was created in %s.\n\n", createpath)
+ r.Println(c.newProjectNextStepsText(createpath, format))
+
+ return nil
+ },
+ withc: func(cmd *cobra.Command, r *rootCommand) {
+ cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+ if len(args) != 0 {
+ return []string{}, cobra.ShellCompDirectiveNoFileComp
+ }
+ return []string{}, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveFilterDirs
+ }
+ cmd.Flags().BoolVarP(&force, "force", "f", false, "init inside non-empty directory")
+ cmd.Flags().StringVar(&format, "format", "toml", "preferred file format (toml, yaml or json)")
+ _ = cmd.RegisterFlagCompletionFunc("format", cobra.FixedCompletions([]string{"toml", "yaml", "json"}, cobra.ShellCompDirectiveNoFileComp))
+ },
+ },
+ &simpleCommand{
+ name: "theme",
+ use: "theme [name]",
+ short: "Create a new theme",
+ long: `Create a new theme with the specified name in the ./themes directory.
+This generates a functional theme including template examples and sample content.`,
+ run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
+ if len(args) < 1 {
+ return newUserError("theme name needs to be provided")
+ }
+ cfg := config.New()
+ cfg.Set("publishDir", "public")
+
+ conf, err := r.ConfigFromProvider(configKey{counter: r.configVersionID.Load()}, flagsToCfg(cd, cfg))
+ if err != nil {
+ return err
+ }
+ sourceFs := conf.fs.Source
+ createpath := paths.AbsPathify(conf.configs.Base.WorkingDir, filepath.Join(conf.configs.Base.ThemesDir, args[0]))
+ r.Println("Creating new theme in", createpath)
+
+ err = skeletons.CreateTheme(createpath, sourceFs, format)
+ if err != nil {
+ return err
+ }
+
+ return nil
+ },
+ withc: func(cmd *cobra.Command, r *rootCommand) {
+ cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+ if len(args) != 0 {
+ return []string{}, cobra.ShellCompDirectiveNoFileComp
+ }
+ return []string{}, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveFilterDirs
+ }
+ cmd.Flags().StringVar(&format, "format", "toml", "preferred file format (toml, yaml or json)")
+ _ = cmd.RegisterFlagCompletionFunc("format", cobra.FixedCompletions([]string{"toml", "yaml", "json"}, cobra.ShellCompDirectiveNoFileComp))
+ },
+ },
+ },
}
- cc := &newCmd{baseBuilderCmd: b.newBuilderCmd(cmd)}
+ return c
+}
- cmd.Flags().StringVarP(&cc.contentType, "kind", "k", "", "content type to create")
- cmd.Flags().StringVar(&cc.contentEditor, "editor", "", "edit new content with this editor, if provided")
+type newCommand struct {
+ rootCmd *rootCommand
- cmd.AddCommand(b.newNewSiteCmd().getCommand())
- cmd.AddCommand(b.newNewThemeCmd().getCommand())
+ commands []simplecobra.Commander
+}
- cmd.RunE = cc.newContent
+func (c *newCommand) Commands() []simplecobra.Commander {
+ return c.commands
+}
- return cc
+func (c *newCommand) Name() string {
+ return "new"
}
-func (n *newCmd) newContent(cmd *cobra.Command, args []string) error {
- cfgInit := func(c *commandeer) error {
- if cmd.Flags().Changed("editor") {
- c.Set("newContentEditor", n.contentEditor)
- }
- return nil
- }
+func (c *newCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
+ return nil
+}
- c, err := initializeConfig(true, true, false, &n.hugoBuilderCommon, n, cfgInit)
- if err != nil {
- return err
- }
+func (c *newCommand) Init(cd *simplecobra.Commandeer) error {
+ cmd := cd.CobraCommand
+ cmd.Short = "Create new content"
+ 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.
- if len(args) < 1 {
- return newUserError("path needs to be provided")
- }
+You can also specify the kind with ` + "`-k KIND`" + `.
- return create.NewContent(c.hugo(), n.contentType, args[0])
-}
+If archetypes are provided in your theme or project, they will be used.
-func mkdir(x ...string) {
- p := filepath.Join(x...)
+Ensure you run this within the root directory of your project.`
- err := os.MkdirAll(p, 0777) // before umask
- if err != nil {
- jww.FATAL.Fatalln(err)
- }
+ cmd.RunE = nil
+ return nil
}
-func touchFile(fs afero.Fs, x ...string) {
- inpath := filepath.Join(x...)
- mkdir(filepath.Dir(inpath))
- err := helpers.WriteToDisk(inpath, bytes.NewReader([]byte{}), fs)
- if err != nil {
- jww.FATAL.Fatalln(err)
- }
+func (c *newCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
+ c.rootCmd = cd.Root.Command.(*rootCommand)
+ return nil
}
-func newContentPathSection(h *hugolib.HugoSites, path string) (string, string) {
- // Forward slashes is used in all examples. Convert if needed.
- // Issue #1133
- createpath := filepath.FromSlash(path)
+func (c *newCommand) newProjectNextStepsText(path string, format string) string {
+ format = strings.ToLower(format)
+ var nextStepsText bytes.Buffer
- if h != nil {
- for _, dir := range h.BaseFs.Content.Dirs {
- createpath = strings.TrimPrefix(createpath, dir.Meta().Filename)
- }
- }
+ nextStepsText.WriteString(`Just a few more steps...
- var section string
- // assume the first directory is the section (kind)
- if strings.Contains(createpath[1:], helpers.FilePathSeparator) {
- parts := strings.Split(strings.TrimPrefix(createpath, helpers.FilePathSeparator), helpers.FilePathSeparator)
- if len(parts) > 0 {
- section = parts[0]
- }
+1. Change the current directory to ` + path + `.
+2. Create or install a theme:
+ - Create a new theme with the command "hugo new theme "
+ - Or, install a theme from https://themes.gohugo.io/
+3. Edit hugo.` + format + `, setting the "theme" property to the theme name.
+4. Create new content with the command "hugo new content `)
- }
+ nextStepsText.WriteString(filepath.Join("", "."))
+
+ nextStepsText.WriteString(`".
+5. Start the embedded web server with the command "hugo server --buildDrafts".
+
+See documentation at https://gohugo.io/.`)
- return createpath, section
+ return nextStepsText.String()
}
diff --git a/commands/new_content_test.go b/commands/new_content_test.go
deleted file mode 100644
index 42a7c968c0e..00000000000
--- a/commands/new_content_test.go
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2019 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 (
- "path/filepath"
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-// Issue #1133
-func TestNewContentPathSectionWithForwardSlashes(t *testing.T) {
- c := qt.New(t)
- p, s := newContentPathSection(nil, "/post/new.md")
- c.Assert(p, qt.Equals, filepath.FromSlash("/post/new.md"))
- c.Assert(s, qt.Equals, "post")
-}
diff --git a/commands/new_site.go b/commands/new_site.go
deleted file mode 100644
index 11e9ce40a15..00000000000
--- a/commands/new_site.go
+++ /dev/null
@@ -1,164 +0,0 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// 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 (
- "bytes"
- "errors"
- "path/filepath"
- "strings"
-
- "github.com/gohugoio/hugo/config"
- "github.com/gohugoio/hugo/parser/metadecoders"
-
- _errors "github.com/pkg/errors"
-
- "github.com/gohugoio/hugo/create"
- "github.com/gohugoio/hugo/helpers"
- "github.com/gohugoio/hugo/hugofs"
- "github.com/gohugoio/hugo/parser"
- "github.com/spf13/cobra"
- jww "github.com/spf13/jwalterweatherman"
-)
-
-var _ cmder = (*newSiteCmd)(nil)
-
-type newSiteCmd struct {
- configFormat string
-
- *baseBuilderCmd
-}
-
-func (b *commandsBuilder) newNewSiteCmd() *newSiteCmd {
- cc := &newSiteCmd{}
-
- cmd := &cobra.Command{
- Use: "site [path]",
- Short: "Create a new site (skeleton)",
- Long: `Create a new site in the provided directory.
-The new site will have the correct structure, but no content or theme yet.
-Use ` + "`hugo new [contentPath]`" + ` to create new content.`,
- RunE: cc.newSite,
- }
-
- cmd.Flags().StringVarP(&cc.configFormat, "format", "f", "toml", "config & frontmatter format")
- cmd.Flags().Bool("force", false, "init inside non-empty directory")
-
- cc.baseBuilderCmd = b.newBuilderBasicCmd(cmd)
-
- return cc
-}
-
-func (n *newSiteCmd) doNewSite(fs *hugofs.Fs, basepath string, force bool) error {
- archeTypePath := filepath.Join(basepath, "archetypes")
- dirs := []string{
- filepath.Join(basepath, "layouts"),
- filepath.Join(basepath, "content"),
- archeTypePath,
- filepath.Join(basepath, "static"),
- filepath.Join(basepath, "data"),
- filepath.Join(basepath, "themes"),
- }
-
- if exists, _ := helpers.Exists(basepath, fs.Source); exists {
- if isDir, _ := helpers.IsDir(basepath, fs.Source); !isDir {
- return errors.New(basepath + " already exists but not a directory")
- }
-
- isEmpty, _ := helpers.IsEmpty(basepath, fs.Source)
-
- switch {
- case !isEmpty && !force:
- return errors.New(basepath + " already exists and is not empty. See --force.")
-
- case !isEmpty && force:
- all := append(dirs, filepath.Join(basepath, "config."+n.configFormat))
- for _, path := range all {
- if exists, _ := helpers.Exists(path, fs.Source); exists {
- return errors.New(path + " already exists")
- }
- }
- }
- }
-
- for _, dir := range dirs {
- if err := fs.Source.MkdirAll(dir, 0777); err != nil {
- return _errors.Wrap(err, "Failed to create dir")
- }
- }
-
- createConfig(fs, basepath, n.configFormat)
-
- // Create a default archetype file.
- helpers.SafeWriteToDisk(filepath.Join(archeTypePath, "default.md"),
- strings.NewReader(create.DefaultArchetypeTemplateTemplate), fs.Source)
-
- jww.FEEDBACK.Printf("Congratulations! Your new Hugo site is created in %s.\n\n", basepath)
- jww.FEEDBACK.Println(nextStepsText())
-
- return nil
-}
-
-// newSite creates a new Hugo site and initializes a structured Hugo directory.
-func (n *newSiteCmd) newSite(cmd *cobra.Command, args []string) error {
- if len(args) < 1 {
- return newUserError("path needs to be provided")
- }
-
- createpath, err := filepath.Abs(filepath.Clean(args[0]))
- if err != nil {
- return newUserError(err)
- }
-
- forceNew, _ := cmd.Flags().GetBool("force")
-
- return n.doNewSite(hugofs.NewDefault(config.New()), createpath, forceNew)
-}
-
-func createConfig(fs *hugofs.Fs, inpath string, kind string) (err error) {
- in := map[string]string{
- "baseURL": "http://example.org/",
- "title": "My New Hugo Site",
- "languageCode": "en-us",
- }
-
- var buf bytes.Buffer
- err = parser.InterfaceToConfig(in, metadecoders.FormatFromString(kind), &buf)
- if err != nil {
- return err
- }
-
- return helpers.WriteToDisk(filepath.Join(inpath, "config."+kind), &buf, fs.Source)
-}
-
-func nextStepsText() string {
- var nextStepsText bytes.Buffer
-
- nextStepsText.WriteString(`Just a few more steps and you're ready to go:
-
-1. Download a theme into the same-named folder.
- Choose a theme from https://themes.gohugo.io/ or
- create your own with the "hugo new theme " command.
-2. Perhaps you want to add some content. You can add single files
- with "hugo new `)
-
- nextStepsText.WriteString(filepath.Join("", "."))
-
- nextStepsText.WriteString(`".
-3. Start the built-in live server via "hugo server".
-
-Visit https://gohugo.io/ for quickstart guide and full documentation.`)
-
- return nextStepsText.String()
-}
diff --git a/commands/new_theme.go b/commands/new_theme.go
deleted file mode 100644
index 9b1a4786c41..00000000000
--- a/commands/new_theme.go
+++ /dev/null
@@ -1,176 +0,0 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// 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 (
- "bytes"
- "errors"
- "path/filepath"
- "strings"
- "time"
-
- "github.com/gohugoio/hugo/helpers"
- "github.com/gohugoio/hugo/hugofs"
- "github.com/spf13/cobra"
- jww "github.com/spf13/jwalterweatherman"
-)
-
-var _ cmder = (*newThemeCmd)(nil)
-
-type newThemeCmd struct {
- *baseBuilderCmd
-}
-
-func (b *commandsBuilder) newNewThemeCmd() *newThemeCmd {
- cc := &newThemeCmd{}
-
- cmd := &cobra.Command{
- Use: "theme [name]",
- Short: "Create a new theme",
- Long: `Create a new theme (skeleton) called [name] in ./themes.
-New theme is a skeleton. Please add content to the touched files. Add your
-name to the copyright line in the license and adjust the theme.toml file
-as you see fit.`,
- RunE: cc.newTheme,
- }
-
- cc.baseBuilderCmd = b.newBuilderBasicCmd(cmd)
-
- return cc
-}
-
-// newTheme creates a new Hugo theme template
-func (n *newThemeCmd) newTheme(cmd *cobra.Command, args []string) error {
- c, err := initializeConfig(false, false, false, &n.hugoBuilderCommon, n, nil)
- if err != nil {
- return err
- }
-
- if len(args) < 1 {
- return newUserError("theme name needs to be provided")
- }
-
- createpath := c.hugo().PathSpec.AbsPathify(filepath.Join(c.Cfg.GetString("themesDir"), args[0]))
- jww.FEEDBACK.Println("Creating theme at", createpath)
-
- cfg := c.DepsCfg
-
- if x, _ := helpers.Exists(createpath, cfg.Fs.Source); x {
- return errors.New(createpath + " already exists")
- }
-
- mkdir(createpath, "layouts", "_default")
- mkdir(createpath, "layouts", "partials")
-
- touchFile(cfg.Fs.Source, createpath, "layouts", "index.html")
- touchFile(cfg.Fs.Source, createpath, "layouts", "404.html")
- touchFile(cfg.Fs.Source, createpath, "layouts", "_default", "list.html")
- touchFile(cfg.Fs.Source, createpath, "layouts", "_default", "single.html")
-
- baseofDefault := []byte(`
-
- {{- partial "head.html" . -}}
-
- {{- partial "header.html" . -}}
-
- {{- block "main" . }}{{- end }}
-
- {{- partial "footer.html" . -}}
-
-
-`)
- err = helpers.WriteToDisk(filepath.Join(createpath, "layouts", "_default", "baseof.html"), bytes.NewReader(baseofDefault), cfg.Fs.Source)
- if err != nil {
- return err
- }
-
- touchFile(cfg.Fs.Source, createpath, "layouts", "partials", "head.html")
- touchFile(cfg.Fs.Source, createpath, "layouts", "partials", "header.html")
- touchFile(cfg.Fs.Source, createpath, "layouts", "partials", "footer.html")
-
- mkdir(createpath, "archetypes")
-
- archDefault := []byte("+++\n+++\n")
-
- err = helpers.WriteToDisk(filepath.Join(createpath, "archetypes", "default.md"), bytes.NewReader(archDefault), cfg.Fs.Source)
- if err != nil {
- return err
- }
-
- mkdir(createpath, "static", "js")
- mkdir(createpath, "static", "css")
-
- by := []byte(`The MIT License (MIT)
-
-Copyright (c) ` + time.Now().Format("2006") + ` YOUR_NAME_HERE
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
-the Software, and to permit persons to whom the Software is furnished to do so,
-subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
-FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
-COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
-IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-`)
-
- err = helpers.WriteToDisk(filepath.Join(createpath, "LICENSE"), bytes.NewReader(by), cfg.Fs.Source)
- if err != nil {
- return err
- }
-
- n.createThemeMD(cfg.Fs, createpath)
-
- return nil
-}
-
-func (n *newThemeCmd) createThemeMD(fs *hugofs.Fs, inpath string) (err error) {
- by := []byte(`# theme.toml template for a Hugo theme
-# See https://github.com/gohugoio/hugoThemes#themetoml for an example
-
-name = "` + strings.Title(helpers.MakeTitle(filepath.Base(inpath))) + `"
-license = "MIT"
-licenselink = "https://github.com/yourname/yourtheme/blob/master/LICENSE"
-description = ""
-homepage = "http://example.com/"
-tags = []
-features = []
-min_version = "0.41.0"
-
-[author]
- name = ""
- homepage = ""
-
-# If porting an existing theme
-[original]
- name = ""
- homepage = ""
- repo = ""
-`)
-
- err = helpers.WriteToDisk(filepath.Join(inpath, "theme.toml"), bytes.NewReader(by), fs.Source)
- if err != nil {
- return
- }
-
- return nil
-}
diff --git a/commands/nodeploy.go b/commands/nodeploy.go
deleted file mode 100644
index 363602c516e..00000000000
--- a/commands/nodeploy.go
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2019 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.
-
-// +build nodeploy
-
-package commands
-
-import (
- "errors"
-
- "github.com/spf13/cobra"
-)
-
-var _ cmder = (*deployCmd)(nil)
-
-// deployCmd supports deploying sites to Cloud providers.
-type deployCmd struct {
- *baseBuilderCmd
-}
-
-func (b *commandsBuilder) newDeployCmd() *deployCmd {
- cc := &deployCmd{}
-
- cmd := &cobra.Command{
- Use: "deploy",
- 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.
-`,
- RunE: func(cmd *cobra.Command, args []string) error {
- return errors.New("build without HUGO_BUILD_TAGS=nodeploy to use this command")
- },
- }
-
- cc.baseBuilderCmd = b.newBuilderBasicCmd(cmd)
-
- return cc
-}
diff --git a/commands/release.go b/commands/release.go
index 3b78590e6b9..ae97cadc777 100644
--- a/commands/release.go
+++ b/commands/release.go
@@ -1,6 +1,4 @@
-// +build release
-
-// Copyright 2017-present The Hugo Authors. All rights reserved.
+// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -16,56 +14,42 @@
package commands
import (
- "errors"
+ "context"
- "github.com/gohugoio/hugo/config"
+ "github.com/bep/simplecobra"
"github.com/gohugoio/hugo/releaser"
"github.com/spf13/cobra"
)
-var _ cmder = (*releaseCommandeer)(nil)
-
-type releaseCommandeer struct {
- cmd *cobra.Command
-
- version string
-
- skipPublish bool
- try bool
-}
-
-func createReleaser() cmder {
- // Note: This is a command only meant for internal use and must be run
- // via "go run -tags release main.go release" on the actual code base that is in the release.
- r := &releaseCommandeer{
- cmd: &cobra.Command{
- Use: "release",
- Short: "Release a new version of Hugo.",
- Hidden: true,
+// Note: This is a command only meant for internal use and must be run
+// via "go run -tags release main.go release" on the actual code base that is in the release.
+func newReleaseCommand() simplecobra.Commander {
+ var (
+ step int
+ skipPush bool
+ try bool
+ version string
+ )
+
+ return &simpleCommand{
+ name: "release",
+ short: "Release a new version of Hugo",
+ run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
+ rel, err := releaser.New(skipPush, try, step, version)
+ if err != nil {
+ return err
+ }
+
+ return rel.Run()
+ },
+ withc: func(cmd *cobra.Command, r *rootCommand) {
+ cmd.Hidden = true
+ cmd.ValidArgsFunction = cobra.NoFileCompletions
+ cmd.PersistentFlags().BoolVarP(&skipPush, "skip-push", "", false, "skip pushing to remote")
+ cmd.PersistentFlags().BoolVarP(&try, "try", "", false, "no changes")
+ cmd.PersistentFlags().IntVarP(&step, "step", "", 0, "step to run (1: set new version 2: prepare next dev version)")
+ cmd.PersistentFlags().StringVarP(&version, "version", "", "", "version to release (derived from branch name if not set)")
+ _ = cmd.RegisterFlagCompletionFunc("step", cobra.FixedCompletions([]string{"1", "2"}, cobra.ShellCompDirectiveNoFileComp))
},
}
-
- r.cmd.RunE = func(cmd *cobra.Command, args []string) error {
- return r.release()
- }
-
- r.cmd.PersistentFlags().StringVarP(&r.version, "rel", "r", "", "new release version, i.e. 0.25.1")
- r.cmd.PersistentFlags().BoolVarP(&r.skipPublish, "skip-publish", "", false, "skip all publishing pipes of the release")
- r.cmd.PersistentFlags().BoolVarP(&r.try, "try", "", false, "simulate a release, i.e. no changes")
-
- return r
-}
-
-func (c *releaseCommandeer) getCommand() *cobra.Command {
- return c.cmd
-}
-
-func (c *releaseCommandeer) flagsToConfig(cfg config.Provider) {
-}
-
-func (r *releaseCommandeer) release() error {
- if r.version == "" {
- return errors.New("must set the --rel flag to the relevant version number")
- }
- return releaser.New(r.version, r.skipPublish, r.try).Run()
}
diff --git a/commands/release_noop.go b/commands/release_noop.go
deleted file mode 100644
index ccf34b68e3f..00000000000
--- a/commands/release_noop.go
+++ /dev/null
@@ -1,20 +0,0 @@
-// +build !release
-
-// Copyright 2018 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// 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
-
-func createReleaser() cmder {
- return &nilCommand{}
-}
diff --git a/commands/server.go b/commands/server.go
index 7d9462b3655..eb023d69866 100644
--- a/commands/server.go
+++ b/commands/server.go
@@ -1,4 +1,4 @@
-// Copyright 2019 The Hugo Authors. All rights reserved.
+// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,339 +15,257 @@ package commands
import (
"bytes"
+ "context"
+ "crypto/tls"
+ "crypto/x509"
+ "encoding/json"
+ "encoding/pem"
+ "errors"
"fmt"
"io"
+ "maps"
"net"
"net/http"
+ _ "net/http/pprof"
"net/url"
"os"
"os/signal"
+ "path"
"path/filepath"
"regexp"
- "runtime"
+ "sort"
"strconv"
"strings"
"sync"
+ "sync/atomic"
"syscall"
"time"
- "github.com/gohugoio/hugo/common/paths"
-
- "github.com/pkg/errors"
+ "github.com/bep/mclib"
+ "github.com/pkg/browser"
- "github.com/gohugoio/hugo/livereload"
+ "github.com/bep/debounce"
+ "github.com/bep/simplecobra"
+ "github.com/fsnotify/fsnotify"
+ "github.com/gohugoio/hugo/common/herrors"
+ "github.com/gohugoio/hugo/common/hugo"
+ "github.com/gohugoio/hugo/common/paths"
+ "github.com/gohugoio/hugo/langs"
+ "github.com/gohugoio/hugo/tpl/tplimpl"
+ "github.com/gohugoio/hugo/common/types"
+ "github.com/gohugoio/hugo/common/urls"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
+ "github.com/gohugoio/hugo/hugofs"
+ "github.com/gohugoio/hugo/hugolib"
+ "github.com/gohugoio/hugo/hugolib/filesystems"
+ "github.com/gohugoio/hugo/livereload"
+ "github.com/gohugoio/hugo/transform"
+ "github.com/gohugoio/hugo/transform/livereloadinject"
"github.com/spf13/afero"
"github.com/spf13/cobra"
- jww "github.com/spf13/jwalterweatherman"
+ "github.com/spf13/fsync"
+ "golang.org/x/sync/errgroup"
+ "golang.org/x/sync/semaphore"
)
-type serverCmd struct {
- // Can be used to stop the server. Useful in tests
- stop <-chan bool
+var (
+ logDuplicateTemplateExecuteRe = regexp.MustCompile(`: template: .*?:\d+:\d+: executing ".*?"`)
+ logDuplicateTemplateParseRe = regexp.MustCompile(`: template: .*?:\d+:\d*`)
+)
- disableLiveReload bool
- navigateToChanged bool
- renderToDisk bool
- serverAppend bool
- serverInterface string
- serverPort int
- liveReloadPort int
- serverWatch bool
- noHTTPCache bool
+var logReplacer = strings.NewReplacer(
+ "can't", "can’t", // Chroma lexer doesn't do well with "can't"
+ "*hugolib.pageState", "page.Page", // Page is the public interface.
+ "Rebuild failed:", "",
+)
- disableFastRender bool
- disableBrowserError bool
+const (
+ configChangeConfig = "config file"
+ configChangeGoMod = "go.mod file"
+ configChangeGoWork = "go work file"
+)
- *baseBuilderCmd
-}
+const (
+ hugoHeaderRedirect = "X-Hugo-Redirect"
+)
-func (b *commandsBuilder) newServerCmd() *serverCmd {
- return b.newServerCmdSignaled(nil)
+func newHugoBuilder(r *rootCommand, s *serverCommand, onConfigLoaded ...func(reloaded bool) error) *hugoBuilder {
+ var visitedURLs *types.EvictingQueue[string]
+ if s != nil && !s.disableFastRender {
+ visitedURLs = types.NewEvictingQueue[string](20)
+ }
+ return &hugoBuilder{
+ r: r,
+ s: s,
+ visitedURLs: visitedURLs,
+ fullRebuildSem: semaphore.NewWeighted(1),
+ debounce: debounce.New(4 * time.Second),
+ onConfigLoaded: func(reloaded bool) error {
+ for _, wc := range onConfigLoaded {
+ if err := wc(reloaded); err != nil {
+ return err
+ }
+ }
+ return nil
+ },
+ }
}
-func (b *commandsBuilder) newServerCmdSignaled(stop <-chan bool) *serverCmd {
- cc := &serverCmd{stop: stop}
-
- cc.baseBuilderCmd = b.newBuilderCmd(&cobra.Command{
- Use: "server",
- Aliases: []string{"serve"},
- Short: "A high performance webserver",
- 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.
-Many run it in production, but the standard behavior is for people to use it
-in development and use a more full featured server such as Nginx or Caddy.
-
-'hugo server' will avoid writing the rendered and served content to disk,
-preferring to store it in memory.
-
-By default hugo will also watch your files for any changes you make and
-automatically rebuild the site. It will then live reload any open browser pages
-and push the latest content to them. As most Hugo sites are built in a fraction
-of a second, you will be able to save and see your changes nearly instantly.`,
- RunE: cc.server,
- })
-
- cc.cmd.Flags().IntVarP(&cc.serverPort, "port", "p", 1313, "port on which the server will listen")
- cc.cmd.Flags().IntVar(&cc.liveReloadPort, "liveReloadPort", -1, "port for live reloading (i.e. 443 in HTTPS proxy situations)")
- cc.cmd.Flags().StringVarP(&cc.serverInterface, "bind", "", "127.0.0.1", "interface to which the server will bind")
- cc.cmd.Flags().BoolVarP(&cc.serverWatch, "watch", "w", true, "watch filesystem for changes and recreate as needed")
- cc.cmd.Flags().BoolVar(&cc.noHTTPCache, "noHTTPCache", false, "prevent HTTP caching")
- cc.cmd.Flags().BoolVarP(&cc.serverAppend, "appendPort", "", true, "append port to baseURL")
- cc.cmd.Flags().BoolVar(&cc.disableLiveReload, "disableLiveReload", false, "watch without enabling live browser reload on rebuild")
- cc.cmd.Flags().BoolVar(&cc.navigateToChanged, "navigateToChanged", false, "navigate to changed content file on live browser reload")
- cc.cmd.Flags().BoolVar(&cc.renderToDisk, "renderToDisk", false, "render to Destination path (default is render to memory & serve from there)")
- cc.cmd.Flags().BoolVar(&cc.disableFastRender, "disableFastRender", false, "enables full re-renders on changes")
- cc.cmd.Flags().BoolVar(&cc.disableBrowserError, "disableBrowserError", false, "do not show build errors in the browser")
-
- cc.cmd.Flags().String("memstats", "", "log memory usage to this file")
- cc.cmd.Flags().String("meminterval", "100ms", "interval to poll memory usage (requires --memstats), valid time units are \"ns\", \"us\" (or \"µs\"), \"ms\", \"s\", \"m\", \"h\".")
+func newServerCommand() *serverCommand {
+ // Flags.
+ var uninstall bool
+
+ c := &serverCommand{
+ quit: make(chan bool),
+ commands: []simplecobra.Commander{
+ &simpleCommand{
+ name: "trust",
+ short: "Install the local CA in the system trust store",
+ run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
+ action := "-install"
+ if uninstall {
+ action = "-uninstall"
+ }
+ os.Args = []string{action}
+ return mclib.RunMain()
+ },
+ withc: func(cmd *cobra.Command, r *rootCommand) {
+ cmd.ValidArgsFunction = cobra.NoFileCompletions
+ cmd.Flags().BoolVar(&uninstall, "uninstall", false, "Uninstall the local CA (but do not delete it).")
+ },
+ },
+ },
+ }
- return cc
+ return c
}
-type filesOnlyFs struct {
- fs http.FileSystem
+func (c *serverCommand) Commands() []simplecobra.Commander {
+ return c.commands
}
-type noDirFile struct {
- http.File
+type countingStatFs struct {
+ afero.Fs
+ statCounter uint64
}
-func (fs filesOnlyFs) Open(name string) (http.File, error) {
- f, err := fs.fs.Open(name)
- if err != nil {
- return nil, err
+func (fs *countingStatFs) Stat(name string) (os.FileInfo, error) {
+ f, err := fs.Fs.Stat(name)
+ if err == nil {
+ if !f.IsDir() {
+ atomic.AddUint64(&fs.statCounter, 1)
+ }
}
- return noDirFile{f}, nil
+ return f, err
}
-func (f noDirFile) Readdir(count int) ([]os.FileInfo, error) {
- return nil, nil
+// dynamicEvents contains events that is considered dynamic, as in "not static".
+// Both of these categories will trigger a new build, but the asset events
+// does not fit into the "navigate to changed" logic.
+type dynamicEvents struct {
+ ContentEvents []fsnotify.Event
+ AssetEvents []fsnotify.Event
}
-var serverPorts []int
-
-func (sc *serverCmd) server(cmd *cobra.Command, args []string) error {
- // If a Destination is provided via flag write to disk
- destination, _ := cmd.Flags().GetString("destination")
- if destination != "" {
- sc.renderToDisk = true
- }
-
- var serverCfgInit sync.Once
-
- cfgInit := func(c *commandeer) error {
- c.Set("renderToMemory", !sc.renderToDisk)
- if cmd.Flags().Changed("navigateToChanged") {
- c.Set("navigateToChanged", sc.navigateToChanged)
- }
- if cmd.Flags().Changed("disableLiveReload") {
- c.Set("disableLiveReload", sc.disableLiveReload)
- }
- if cmd.Flags().Changed("disableFastRender") {
- c.Set("disableFastRender", sc.disableFastRender)
- }
- if cmd.Flags().Changed("disableBrowserError") {
- c.Set("disableBrowserError", sc.disableBrowserError)
- }
- if sc.serverWatch {
- c.Set("watch", true)
- }
-
- // TODO(bep) yes, we should fix.
- if !c.languagesConfigured {
- return nil
- }
-
- var err error
-
- // We can only do this once.
- serverCfgInit.Do(func() {
- serverPorts = make([]int, 1)
-
- if c.languages.IsMultihost() {
- if !sc.serverAppend {
- err = newSystemError("--appendPort=false not supported when in multihost mode")
- }
- serverPorts = make([]int, len(c.languages))
- }
-
- currentServerPort := sc.serverPort
-
- for i := 0; i < len(serverPorts); i++ {
- l, err := net.Listen("tcp", net.JoinHostPort(sc.serverInterface, strconv.Itoa(currentServerPort)))
- if err == nil {
- l.Close()
- serverPorts[i] = currentServerPort
- } else {
- if i == 0 && sc.cmd.Flags().Changed("port") {
- // port set explicitly by user -- he/she probably meant it!
- err = newSystemErrorF("Server startup failed: %s", err)
- }
- c.logger.Println("port", sc.serverPort, "already in use, attempting to use an available port")
- sp, err := helpers.FindAvailablePort()
- if err != nil {
- err = newSystemError("Unable to find alternative port to use:", err)
- }
- serverPorts[i] = sp.Port
- }
-
- currentServerPort = serverPorts[i] + 1
- }
- })
-
- c.serverPorts = serverPorts
-
- c.Set("port", sc.serverPort)
- if sc.liveReloadPort != -1 {
- c.Set("liveReloadPort", sc.liveReloadPort)
- } else {
- c.Set("liveReloadPort", serverPorts[0])
- }
+type fileChangeDetector struct {
+ sync.Mutex
+ current map[string]uint64
+ prev map[string]uint64
- isMultiHost := c.languages.IsMultihost()
- for i, language := range c.languages {
- var serverPort int
- if isMultiHost {
- serverPort = serverPorts[i]
- } else {
- serverPort = serverPorts[0]
- }
-
- baseURL, err := sc.fixURL(language, sc.baseURL, serverPort)
- if err != nil {
- return nil
- }
- if isMultiHost {
- language.Set("baseURL", baseURL)
- }
- if i == 0 {
- c.Set("baseURL", baseURL)
- }
- }
+ irrelevantRe *regexp.Regexp
+}
- return err
- }
+func (f *fileChangeDetector) OnFileClose(name string, checksum uint64) {
+ f.Lock()
+ defer f.Unlock()
+ f.current[name] = checksum
+}
- if err := memStats(); err != nil {
- jww.WARN.Println("memstats error:", err)
+func (f *fileChangeDetector) PrepareNew() {
+ if f == nil {
+ return
}
- // silence errors in cobra so we can handle them here
- cmd.SilenceErrors = true
+ f.Lock()
+ defer f.Unlock()
- c, err := initializeConfig(true, true, true, &sc.hugoBuilderCommon, sc, cfgInit)
- if err != nil {
- cmd.PrintErrln("Error:", err.Error())
- return err
+ if f.current == nil {
+ f.current = make(map[string]uint64)
+ f.prev = make(map[string]uint64)
+ return
}
- err = func() error {
- defer c.timeTrack(time.Now(), "Built")
- err := c.serverBuild()
- if err != nil {
- cmd.PrintErrln("Error:", err.Error())
- }
- return err
- }()
- if err != nil {
- return err
- }
+ f.prev = make(map[string]uint64)
+ maps.Copy(f.prev, f.current)
+ f.current = make(map[string]uint64)
+}
- for _, s := range c.hugo().Sites {
- s.RegisterMediaTypes()
+func (f *fileChangeDetector) changed() []string {
+ if f == nil {
+ return nil
}
-
- // Watch runs its own server as part of the routine
- if sc.serverWatch {
-
- watchDirs, err := c.getDirList()
- if err != nil {
- return err
- }
-
- watchGroups := helpers.ExtractAndGroupRootPaths(watchDirs)
-
- for _, group := range watchGroups {
- jww.FEEDBACK.Printf("Watching for changes in %s\n", group)
- }
- watcher, err := c.newWatcher(sc.poll, watchDirs...)
- if err != nil {
- return err
+ f.Lock()
+ defer f.Unlock()
+ var c []string
+ for k, v := range f.current {
+ vv, found := f.prev[k]
+ if !found || v != vv {
+ c = append(c, k)
}
-
- defer watcher.Close()
-
}
- return c.serve(sc)
+ return f.filterIrrelevantAndSort(c)
}
-func getRootWatchDirsStr(baseDir string, watchDirs []string) string {
- relWatchDirs := make([]string, len(watchDirs))
- for i, dir := range watchDirs {
- relWatchDirs[i], _ = paths.GetRelativePath(dir, baseDir)
+func (f *fileChangeDetector) filterIrrelevantAndSort(in []string) []string {
+ var filtered []string
+ for _, v := range in {
+ if !f.irrelevantRe.MatchString(v) {
+ filtered = append(filtered, v)
+ }
}
-
- return strings.Join(helpers.UniqueStringsSorted(helpers.ExtractRootPaths(relWatchDirs)), ",")
+ sort.Strings(filtered)
+ return filtered
}
type fileServer struct {
- baseURLs []string
+ baseURLs []urls.BaseURL
roots []string
- errorTemplate func(err interface{}) (io.Reader, error)
- c *commandeer
- s *serverCmd
+ errorTemplate func(err any) (io.Reader, error)
+ c *serverCommand
}
-func (f *fileServer) rewriteRequest(r *http.Request, toPath string) *http.Request {
- r2 := new(http.Request)
- *r2 = *r
- r2.URL = new(url.URL)
- *r2.URL = *r.URL
- r2.URL.Path = toPath
- r2.Header.Set("X-Rewrite-Original-URI", r.URL.RequestURI())
-
- return r2
-}
-
-func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, error) {
+func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string, string, error) {
+ r := f.c.r
baseURL := f.baseURLs[i]
root := f.roots[i]
- port := f.c.serverPorts[i]
-
- publishDir := f.c.Cfg.GetString("publishDir")
-
- if root != "" {
- publishDir = filepath.Join(publishDir, root)
- }
-
- absPublishDir := f.c.hugo().PathSpec.AbsPathify(publishDir)
-
- jww.FEEDBACK.Printf("Environment: %q", f.c.hugo().Deps.Site.Hugo().Environment)
+ port := f.c.serverPorts[i].p
+ listener := f.c.serverPorts[i].ln
+ logger := f.c.r.logger
if i == 0 {
- if f.s.renderToDisk {
- jww.FEEDBACK.Println("Serving pages from " + absPublishDir)
+ r.Printf("Environment: %q\n", f.c.hugoTry().Deps.Site.Hugo().Environment())
+ mainTarget := "disk"
+ if f.c.r.renderToMemory {
+ mainTarget = "memory"
+ }
+ if f.c.renderStaticToDisk {
+ r.Printf("Serving pages from %s and static files from disk\n", mainTarget)
} else {
- jww.FEEDBACK.Println("Serving pages from memory")
+ r.Printf("Serving pages from %s\n", mainTarget)
}
}
- httpFs := afero.NewHttpFs(f.c.destinationFs)
- fs := filesOnlyFs{httpFs.Dir(absPublishDir)}
+ var httpFs *afero.HttpFs
+ f.c.withConf(func(conf *commonConfig) {
+ httpFs = afero.NewHttpFs(conf.fs.PublishDirServer)
+ })
+ fs := filesOnlyFs{httpFs.Dir(path.Join("/", root))}
if i == 0 && f.c.fastRenderMode {
- jww.FEEDBACK.Println("Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender")
- }
-
- // We're only interested in the path
- u, err := url.Parse(baseURL)
- if err != nil {
- return nil, "", "", errors.Wrap(err, "Invalid baseURL")
+ r.Println("Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender")
}
decorate := func(h http.Handler) http.Handler {
@@ -356,18 +274,20 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, erro
// First check the error state
err := f.c.getErrorWithContext()
if err != nil {
- f.c.wasError = true
+ f.c.errState.setWasErr(true)
w.WriteHeader(500)
r, err := f.errorTemplate(err)
if err != nil {
- f.c.logger.Errorln(err)
+ logger.Errorln(err)
}
port = 1313
- if !f.c.paused {
- port = f.c.Cfg.GetInt("liveReloadPort")
- }
- lr := *u
+ f.c.withConfOrOldConf(func(conf *commonConfig) {
+ if lrport := conf.configs.GetFirstLanguageConfig().BaseURLLiveReload().Port(); lrport != 0 {
+ port = lrport
+ }
+ })
+ lr := baseURL.URL()
lr.Host = fmt.Sprintf("%s:%d", lr.Hostname(), port)
fmt.Fprint(w, injectLiveReloadScript(r, lr))
@@ -375,54 +295,88 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, erro
}
}
- if f.s.noHTTPCache {
+ if f.c.noHTTPCache {
w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0")
w.Header().Set("Pragma", "no-cache")
}
+ var serverConfig config.Server
+ f.c.withConf(func(conf *commonConfig) {
+ serverConfig = conf.configs.Base.Server
+ })
+
// Ignore any query params for the operations below.
- requestURI := strings.TrimSuffix(r.RequestURI, "?"+r.URL.RawQuery)
+ requestURI, _ := url.PathUnescape(strings.TrimSuffix(r.RequestURI, "?"+r.URL.RawQuery))
- for _, header := range f.c.serverConfig.MatchHeaders(requestURI) {
+ for _, header := range serverConfig.MatchHeaders(requestURI) {
w.Header().Set(header.Key, header.Value)
}
- if redirect := f.c.serverConfig.MatchRedirect(requestURI); !redirect.IsZero() {
- doRedirect := true
- // This matches Netlify's behaviour and is needed for SPA behaviour.
- // See https://docs.netlify.com/routing/redirects/rewrites-proxies/
- if !redirect.Force {
- path := filepath.Clean(strings.TrimPrefix(requestURI, u.Path))
- fi, err := f.c.hugo().BaseFs.PublishFs.Stat(path)
- if err == nil {
- if fi.IsDir() {
- // There will be overlapping directories, so we
- // need to check for a file.
- _, err = f.c.hugo().BaseFs.PublishFs.Stat(filepath.Join(path, "index.html"))
- doRedirect = err != nil
- } else {
- doRedirect = false
+ if canRedirect(requestURI, r) {
+ if redirect := serverConfig.MatchRedirect(requestURI, r.Header); !redirect.IsZero() {
+ doRedirect := true
+ // This matches Netlify's behavior and is needed for SPA behavior.
+ // See https://docs.netlify.com/routing/redirects/rewrites-proxies/
+ if !redirect.Force {
+ path := filepath.Clean(strings.TrimPrefix(requestURI, baseURL.Path()))
+ if root != "" {
+ path = filepath.Join(root, path)
+ }
+ var fs afero.Fs
+ f.c.withConf(func(conf *commonConfig) {
+ fs = conf.fs.PublishDirServer
+ })
+
+ fi, err := fs.Stat(path)
+
+ if err == nil {
+ if fi.IsDir() {
+ // There will be overlapping directories, so we
+ // need to check for a file.
+ _, err = fs.Stat(filepath.Join(path, "index.html"))
+ doRedirect = err != nil
+ } else {
+ doRedirect = false
+ }
}
}
- }
- if doRedirect {
- if redirect.Status == 200 {
- if r2 := f.rewriteRequest(r, strings.TrimPrefix(redirect.To, u.Path)); r2 != nil {
- requestURI = redirect.To
- r = r2
+ if doRedirect {
+ w.Header().Set(hugoHeaderRedirect, "true")
+ switch redirect.Status {
+ case 404:
+ w.WriteHeader(404)
+ file, err := fs.Open(strings.TrimPrefix(redirect.To, baseURL.Path()))
+ if err == nil {
+ defer file.Close()
+ io.Copy(w, file)
+ } else {
+ fmt.Fprintln(w, "
Page Not Found
")
+ }
+ return
+ case 200:
+ if r2 := f.rewriteRequest(r, strings.TrimPrefix(redirect.To, baseURL.Path())); r2 != nil {
+ requestURI = redirect.To
+ r = r2
+ }
+ default:
+ w.Header().Set("Content-Type", "")
+ http.Redirect(w, r, redirect.To, redirect.Status)
+ return
+
}
- } else {
- w.Header().Set("Content-Type", "")
- http.Redirect(w, r, redirect.To, redirect.Status)
- return
}
}
-
}
- if f.c.fastRenderMode && f.c.buildErr == nil {
- if strings.HasSuffix(requestURI, "/") || strings.HasSuffix(requestURI, "html") || strings.HasSuffix(requestURI, "htm") {
+ if f.c.fastRenderMode && f.c.errState.buildErr() == nil {
+ if isNavigation(requestURI, r) {
+ // See issue 14240.
+ // Hugo escapes the URL paths when generating them,
+ // that may not be the case when we receive it back from the browser.
+ // PathEscape will escape if it is not already escaped.
+ requestURI = paths.PathEscape(requestURI)
+
if !f.c.visitedURLs.Contains(requestURI) {
// If not already on stack, re-render that single page.
if err := f.c.partialReRender(requestURI); err != nil {
@@ -445,148 +399,443 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, erro
fileserver := decorate(http.FileServer(fs))
mu := http.NewServeMux()
- if u.Path == "" || u.Path == "/" {
+ if baseURL.Path() == "" || baseURL.Path() == "/" {
mu.Handle("/", fileserver)
} else {
- mu.Handle(u.Path, http.StripPrefix(u.Path, fileserver))
+ mu.Handle(baseURL.Path(), http.StripPrefix(baseURL.Path(), fileserver))
+ }
+ if r.IsTestRun() {
+ var shutDownOnce sync.Once
+ mu.HandleFunc("/__stop", func(w http.ResponseWriter, r *http.Request) {
+ shutDownOnce.Do(func() {
+ close(f.c.quit)
+ })
+ })
}
- endpoint := net.JoinHostPort(f.s.serverInterface, strconv.Itoa(port))
+ endpoint := net.JoinHostPort(f.c.serverInterface, strconv.Itoa(port))
- return mu, u.String(), endpoint, nil
+ return mu, listener, baseURL.String(), endpoint, nil
}
-var logErrorRe = regexp.MustCompile(`(?s)ERROR \d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} `)
+func (f *fileServer) rewriteRequest(r *http.Request, toPath string) *http.Request {
+ r2 := new(http.Request)
+ *r2 = *r
+ r2.URL = new(url.URL)
+ *r2.URL = *r.URL
+ r2.URL.Path = toPath
+ r2.Header.Set("X-Rewrite-Original-URI", r.URL.RequestURI())
-func removeErrorPrefixFromLog(content string) string {
- return logErrorRe.ReplaceAllLiteralString(content, "")
+ return r2
}
-func (c *commandeer) serve(s *serverCmd) error {
- isMultiHost := c.hugo().IsMultihost()
+type filesOnlyFs struct {
+ fs http.FileSystem
+}
- var (
- baseURLs []string
- roots []string
- )
+func (fs filesOnlyFs) Open(name string) (http.File, error) {
+ f, err := fs.fs.Open(name)
+ if err != nil {
+ return nil, err
+ }
+ return noDirFile{f}, nil
+}
+
+type noDirFile struct {
+ http.File
+}
+
+func (f noDirFile) Readdir(count int) ([]os.FileInfo, error) {
+ return nil, nil
+}
- if isMultiHost {
- for _, s := range c.hugo().Sites {
- baseURLs = append(baseURLs, s.BaseURL.String())
- roots = append(roots, s.Language().Lang)
+type serverCommand struct {
+ r *rootCommand
+
+ commands []simplecobra.Commander
+
+ *hugoBuilder
+
+ quit chan bool // Closed when the server should shut down. Used in tests only.
+ serverPorts []serverPortListener
+ doLiveReload bool
+
+ // Flags.
+ renderStaticToDisk bool
+ navigateToChanged bool
+ openBrowser bool
+ serverAppend bool
+ serverInterface string
+ tlsCertFile string
+ tlsKeyFile string
+ tlsAuto bool
+ pprof bool
+ serverPort int
+ liveReloadPort int
+ serverWatch bool
+ noHTTPCache bool
+ disableLiveReload bool
+ disableFastRender bool
+ disableBrowserError bool
+}
+
+func (c *serverCommand) Name() string {
+ return "server"
+}
+
+func (c *serverCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
+ if c.pprof {
+ go func() {
+ http.ListenAndServe("localhost:8080", nil)
+ }()
+ }
+ // Watch runs its own server as part of the routine
+ if c.serverWatch {
+
+ watchDirs, err := c.getDirList()
+ if err != nil {
+ return err
}
- } else {
- s := c.hugo().Sites[0]
- baseURLs = []string{s.BaseURL.String()}
- roots = []string{""}
+
+ watchGroups := helpers.ExtractAndGroupRootPaths(watchDirs)
+
+ c.r.Printf("Watching for changes in %s\n", strings.Join(watchGroups, ", "))
+ watcher, err := c.newWatcher(c.r.poll, watchDirs...)
+ if err != nil {
+ return err
+ }
+
+ defer watcher.Close()
+
}
- templ, err := c.hugo().TextTmpl().Parse("__default_server_error", buildErrorTemplate)
+ err := func() error {
+ defer c.r.timeTrack(time.Now(), "Built")
+ return c.build()
+ }()
if err != nil {
return err
}
- srv := &fileServer{
- baseURLs: baseURLs,
- roots: roots,
- c: c,
- s: s,
- errorTemplate: func(ctx interface{}) (io.Reader, error) {
- b := &bytes.Buffer{}
- err := c.hugo().Tmpl().Execute(templ, b, ctx)
- return b, err
+ return c.serve()
+}
+
+func (c *serverCommand) Init(cd *simplecobra.Commandeer) error {
+ cmd := cd.CobraCommand
+ cmd.Short = "Start the embedded web server"
+ cmd.Long = `Hugo provides its own webserver which builds and serves the project.
+While hugo server is high performance, it is a webserver with limited options.
+
+The ` + "`" + `hugo server` + "`" + ` command will by default write and serve files from disk, but
+you can render to memory by using the ` + "`" + `--renderToMemory` + "`" + ` flag. This can be
+faster in some cases, but it will consume more memory.
+
+By default hugo will also watch your files for any changes you make and
+automatically rebuild the project. It will then live reload any open browser pages
+and push the latest content to them. As most Hugo projects are built in a fraction
+of a second, you will be able to save and see your changes nearly instantly.`
+ cmd.Aliases = []string{"serve"}
+
+ cmd.Flags().IntVarP(&c.serverPort, "port", "p", 1313, "port on which the server will listen")
+ _ = cmd.RegisterFlagCompletionFunc("port", cobra.NoFileCompletions)
+ cmd.Flags().IntVar(&c.liveReloadPort, "liveReloadPort", -1, "port for live reloading (i.e. 443 in HTTPS proxy situations)")
+ _ = cmd.RegisterFlagCompletionFunc("liveReloadPort", cobra.NoFileCompletions)
+ cmd.Flags().StringVarP(&c.serverInterface, "bind", "", "127.0.0.1", "interface to which the server will bind")
+ _ = cmd.RegisterFlagCompletionFunc("bind", cobra.NoFileCompletions)
+ cmd.Flags().StringVarP(&c.tlsCertFile, "tlsCertFile", "", "", "path to TLS certificate file")
+ _ = cmd.MarkFlagFilename("tlsCertFile", "pem")
+ cmd.Flags().StringVarP(&c.tlsKeyFile, "tlsKeyFile", "", "", "path to TLS key file")
+ _ = cmd.MarkFlagFilename("tlsKeyFile", "pem")
+ cmd.Flags().BoolVar(&c.tlsAuto, "tlsAuto", false, "generate and use locally-trusted certificates.")
+ cmd.Flags().BoolVar(&c.pprof, "pprof", false, "enable the pprof server (port 8080)")
+ cmd.Flags().BoolVarP(&c.serverWatch, "watch", "w", true, "watch filesystem for changes and recreate as needed")
+ cmd.Flags().BoolVar(&c.noHTTPCache, "noHTTPCache", false, "prevent HTTP caching")
+ cmd.Flags().BoolVarP(&c.serverAppend, "appendPort", "", true, "append port to baseURL")
+ cmd.Flags().BoolVar(&c.disableLiveReload, "disableLiveReload", false, "watch without enabling live browser reload on rebuild")
+ cmd.Flags().BoolVarP(&c.navigateToChanged, "navigateToChanged", "N", false, "navigate to changed content file on live browser reload")
+ cmd.Flags().BoolVarP(&c.openBrowser, "openBrowser", "O", false, "open the project 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")
+
+ r := cd.Root.Command.(*rootCommand)
+ applyLocalFlagsBuild(cmd, r)
+
+ return nil
+}
+
+func (c *serverCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
+ c.r = cd.Root.Command.(*rootCommand)
+
+ c.hugoBuilder = newHugoBuilder(
+ c.r,
+ c,
+ func(reloaded bool) error {
+ if !reloaded {
+ if err := c.createServerPorts(cd); err != nil {
+ return err
+ }
+
+ if (c.tlsCertFile == "" || c.tlsKeyFile == "") && c.tlsAuto {
+ c.withConfE(func(conf *commonConfig) error {
+ return c.createCertificates(conf)
+ })
+ }
+ }
+
+ if err := c.setServerInfoInConfig(); err != nil {
+ return err
+ }
+
+ if !reloaded && c.fastRenderMode {
+ c.withConf(func(conf *commonConfig) {
+ conf.fs.PublishDir = hugofs.NewHashingFs(conf.fs.PublishDir, c.changeDetector)
+ conf.fs.PublishDirStatic = hugofs.NewHashingFs(conf.fs.PublishDirStatic, c.changeDetector)
+ })
+ }
+
+ return nil
},
+ )
+
+ destinationFlag := cd.CobraCommand.Flags().Lookup("destination")
+ if c.r.renderToMemory && (destinationFlag != nil && destinationFlag.Changed) {
+ return fmt.Errorf("cannot use --renderToMemory with --destination")
}
+ c.doLiveReload = !c.disableLiveReload
+ c.fastRenderMode = !c.disableFastRender
+ c.showErrorInBrowser = c.doLiveReload && !c.disableBrowserError
+
+ if c.fastRenderMode {
+ // For now, fast render mode only. It should, however, be fast enough
+ // for the full variant, too.
+ c.changeDetector = &fileChangeDetector{
+ // We use this detector to decide to do a Hot reload of a single path or not.
+ // We need to filter out source maps and possibly some other to be able
+ // to make that decision.
+ irrelevantRe: regexp.MustCompile(`\.map$`),
+ }
- doLiveReload := !c.Cfg.GetBool("disableLiveReload")
+ c.changeDetector.PrepareNew()
- if doLiveReload {
- livereload.Initialize()
}
- sigs := make(chan os.Signal, 1)
- signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
+ err := c.loadConfig(cd, true)
+ if err != nil {
+ return err
+ }
- for i := range baseURLs {
- mu, serverURL, endpoint, err := srv.createEndpoint(i)
+ return nil
+}
- if doLiveReload {
- u, err := url.Parse(helpers.SanitizeURL(baseURLs[i]))
+func (c *serverCommand) setServerInfoInConfig() error {
+ if len(c.serverPorts) == 0 {
+ panic("no server ports set")
+ }
+ return c.withConfE(func(conf *commonConfig) error {
+ for i, language := range conf.configs.Languages {
+ isMultihost := conf.configs.IsMultihost
+ var serverPort int
+ if isMultihost {
+ serverPort = c.serverPorts[i].p
+ } else {
+ serverPort = c.serverPorts[0].p
+ }
+ langConfig := conf.configs.LanguageConfigMap[language.Lang]
+ baseURLStr, err := c.fixURL(langConfig.BaseURL, c.r.baseURL, serverPort)
if err != nil {
return err
}
-
- mu.HandleFunc(u.Path+"/livereload.js", livereload.ServeJS)
- mu.HandleFunc(u.Path+"/livereload", livereload.Handler)
- }
- jww.FEEDBACK.Printf("Web Server is available at %s (bind address %s)\n", serverURL, s.serverInterface)
- go func() {
- err = http.ListenAndServe(endpoint, mu)
+ baseURL, err := urls.NewBaseURLFromString(baseURLStr)
if err != nil {
- c.logger.Errorf("Error: %s\n", err.Error())
- os.Exit(1)
+ return fmt.Errorf("failed to create baseURL from %q: %s", baseURLStr, err)
}
- }()
+
+ baseURLLiveReload := baseURL
+ if c.liveReloadPort != -1 {
+ baseURLLiveReload, _ = baseURLLiveReload.WithPort(c.liveReloadPort)
+ }
+ langConfig.C.SetServerInfo(baseURL, baseURLLiveReload, c.serverInterface)
+
+ }
+ return nil
+ })
+}
+
+func (c *serverCommand) getErrorWithContext() any {
+ buildErr := c.errState.buildErr()
+ if buildErr == nil {
+ return nil
}
- jww.FEEDBACK.Println("Press Ctrl+C to stop")
+ m := make(map[string]any)
+
+ m["Error"] = cleanErrorLog(c.r.logger.Errors())
+
+ m["Version"] = hugo.BuildVersionString()
+ ferrors := herrors.UnwrapFileErrorsWithErrorContext(buildErr)
+ m["Files"] = ferrors
+
+ return m
+}
- if s.stop != nil {
- select {
- case <-sigs:
- case <-s.stop:
+func (c *serverCommand) createCertificates(conf *commonConfig) error {
+ hostname := "localhost"
+ if c.r.baseURL != "" {
+ u, err := url.Parse(c.r.baseURL)
+ if err != nil {
+ return err
}
- } else {
- <-sigs
+ hostname = u.Hostname()
+ }
+
+ // For now, store these in the Hugo cache dir.
+ // Hugo should probably introduce some concept of a less temporary application directory.
+ keyDir := filepath.Join(conf.configs.LoadingInfo.BaseConfig.CacheDir, "_mkcerts")
+
+ // Create the directory if it doesn't exist.
+ if _, err := os.Stat(keyDir); os.IsNotExist(err) {
+ if err := os.MkdirAll(keyDir, 0o777); err != nil {
+ return err
+ }
+ }
+
+ c.tlsCertFile = filepath.Join(keyDir, fmt.Sprintf("%s.pem", hostname))
+ c.tlsKeyFile = filepath.Join(keyDir, fmt.Sprintf("%s-key.pem", hostname))
+
+ // Check if the certificate already exists and is valid.
+ certPEM, err := os.ReadFile(c.tlsCertFile)
+ if err == nil {
+ rootPem, err := os.ReadFile(filepath.Join(mclib.GetCAROOT(), "rootCA.pem"))
+ if err == nil {
+ if err := c.verifyCert(rootPem, certPEM, hostname); err == nil {
+ c.r.Println("Using existing", c.tlsCertFile, "and", c.tlsKeyFile)
+ return nil
+ }
+ }
+ }
+
+ c.r.Println("Creating TLS certificates in", keyDir)
+
+ // Yes, this is unfortunate, but it's currently the only way to use Mkcert as a library.
+ os.Args = []string{"-cert-file", c.tlsCertFile, "-key-file", c.tlsKeyFile, hostname}
+ return mclib.RunMain()
+}
+
+func (c *serverCommand) verifyCert(rootPEM, certPEM []byte, name string) error {
+ roots := x509.NewCertPool()
+ ok := roots.AppendCertsFromPEM(rootPEM)
+ if !ok {
+ return fmt.Errorf("failed to parse root certificate")
+ }
+
+ block, _ := pem.Decode(certPEM)
+ if block == nil {
+ return fmt.Errorf("failed to parse certificate PEM")
+ }
+ cert, err := x509.ParseCertificate(block.Bytes)
+ if err != nil {
+ return fmt.Errorf("failed to parse certificate: %v", err.Error())
+ }
+
+ opts := x509.VerifyOptions{
+ DNSName: name,
+ Roots: roots,
}
- c.hugo().Close()
+ if _, err := cert.Verify(opts); err != nil {
+ return fmt.Errorf("failed to verify certificate: %v", err.Error())
+ }
return nil
}
+func (c *serverCommand) createServerPorts(cd *simplecobra.Commandeer) error {
+ flags := cd.CobraCommand.Flags()
+ var cerr error
+ c.withConf(func(conf *commonConfig) {
+ isMultihost := conf.configs.IsMultihost
+ c.serverPorts = make([]serverPortListener, 1)
+ if isMultihost {
+ if !c.serverAppend {
+ cerr = errors.New("--appendPort=false not supported when in multihost mode")
+ return
+ }
+ c.serverPorts = make([]serverPortListener, len(conf.configs.Languages))
+ }
+ currentServerPort := c.serverPort
+ for i := range c.serverPorts {
+ l, err := net.Listen("tcp", net.JoinHostPort(c.serverInterface, strconv.Itoa(currentServerPort)))
+ if err == nil {
+ c.serverPorts[i] = serverPortListener{ln: l, p: currentServerPort}
+ } else {
+ if i == 0 && flags.Changed("port") {
+ // port set explicitly by user -- he/she probably meant it!
+ cerr = fmt.Errorf("server startup failed: %s", err)
+ return
+ }
+ c.r.Println("port", currentServerPort, "already in use, attempting to use an available port")
+ l, sp, err := helpers.TCPListen()
+ if err != nil {
+ cerr = fmt.Errorf("unable to find alternative port to use: %s", err)
+ return
+ }
+ c.serverPorts[i] = serverPortListener{ln: l, p: sp.Port}
+ }
+
+ currentServerPort = c.serverPorts[i].p + 1
+ }
+ })
+
+ return cerr
+}
+
// fixURL massages the baseURL into a form needed for serving
// all pages correctly.
-func (sc *serverCmd) fixURL(cfg config.Provider, s string, port int) (string, error) {
+func (c *serverCommand) fixURL(baseURLFromConfig, baseURLFromFlag string, port int) (string, error) {
+ certsSet := (c.tlsCertFile != "" && c.tlsKeyFile != "") || c.tlsAuto
useLocalhost := false
- if s == "" {
- s = cfg.GetString("baseURL")
+ baseURL := baseURLFromFlag
+ if baseURL == "" {
+ baseURL = baseURLFromConfig
useLocalhost = true
}
- if !strings.HasSuffix(s, "/") {
- s = s + "/"
+ if !strings.HasSuffix(baseURL, "/") {
+ baseURL = baseURL + "/"
}
// do an initial parse of the input string
- u, err := url.Parse(s)
+ u, err := url.Parse(baseURL)
if err != nil {
return "", err
}
// if no Host is defined, then assume that no schema or double-slash were
// present in the url. Add a double-slash and make a best effort attempt.
- if u.Host == "" && s != "/" {
- s = "//" + s
+ if u.Host == "" && baseURL != "/" {
+ baseURL = "//" + baseURL
- u, err = url.Parse(s)
+ u, err = url.Parse(baseURL)
if err != nil {
return "", err
}
}
if useLocalhost {
- if u.Scheme == "https" {
+ if certsSet {
+ u.Scheme = "https"
+ } else if u.Scheme == "https" {
u.Scheme = "http"
}
u.Host = "localhost"
}
- if sc.serverAppend {
+ if c.serverAppend {
if strings.Contains(u.Host, ":") {
u.Host, _, err = net.SplitHostPort(u.Host)
if err != nil {
- return "", errors.Wrap(err, "Failed to split baseURL hostpost")
+ return "", fmt.Errorf("failed to split baseURL hostport: %w", err)
}
}
u.Host += fmt.Sprintf(":%d", port)
@@ -595,39 +844,418 @@ func (sc *serverCmd) fixURL(cfg config.Provider, s string, port int) (string, er
return u.String(), nil
}
-func memStats() error {
- b := newCommandsBuilder()
- sc := b.newServerCmd().getCommand()
- memstats := sc.Flags().Lookup("memstats").Value.String()
- if memstats != "" {
- interval, err := time.ParseDuration(sc.Flags().Lookup("meminterval").Value.String())
- if err != nil {
- interval, _ = time.ParseDuration("100ms")
- }
+func (c *serverCommand) partialReRender(urls ...string) (err error) {
+ defer func() {
+ c.errState.setWasErr(false)
+ }()
+ visited := types.NewEvictingQueue[string](len(urls))
+ for _, url := range urls {
+ visited.Add(url)
+ }
- fileMemStats, err := os.Create(memstats)
+ var h *hugolib.HugoSites
+ h, err = c.hugo()
+ if err != nil {
+ return
+ }
+
+ // Note: We do not set NoBuildLock as the file lock is not acquired at this stage.
+ err = h.Build(hugolib.BuildCfg{NoBuildLock: false, RecentlyTouched: visited, PartialReRender: true, ErrRecovery: c.errState.wasErr()})
+
+ return
+}
+
+func (c *serverCommand) serve() error {
+ var (
+ baseURLs []urls.BaseURL
+ roots []string
+ h *hugolib.HugoSites
+ )
+ err := c.withConfE(func(conf *commonConfig) error {
+ isMultihost := conf.configs.IsMultihost
+ var err error
+ h, err = c.r.HugFromConfig(conf)
if err != nil {
return err
}
- fileMemStats.WriteString("# Time\tHeapSys\tHeapAlloc\tHeapIdle\tHeapReleased\n")
+ // We need the server to share the same logger as the Hugo build (for error counts etc.)
+ c.r.logger = h.Log
- go func() {
- var stats runtime.MemStats
+ if isMultihost {
+ for _, l := range conf.configs.ConfigLangs() {
+ baseURLs = append(baseURLs, l.BaseURL())
+ roots = append(roots, l.Language().(*langs.Language).Lang)
+ }
+ } else {
+ l := conf.configs.GetFirstLanguageConfig()
+ baseURLs = []urls.BaseURL{l.BaseURL()}
+ roots = []string{""}
+ }
+
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+
+ // Cache it here. The HugoSites object may be unavailable later on due to intermittent configuration errors.
+ // To allow the en user to change the error template while the server is running, we use
+ // the freshest template we can provide.
+ var (
+ errTempl *tplimpl.TemplInfo
+ templHandler *tplimpl.TemplateStore
+ )
+ getErrorTemplateAndHandler := func(h *hugolib.HugoSites) (*tplimpl.TemplInfo, *tplimpl.TemplateStore) {
+ if h == nil {
+ return errTempl, templHandler
+ }
+ templHandler := h.GetTemplateStore()
+ errTempl := templHandler.LookupByPath("/_server/error.html")
+ if errTempl == nil {
+ panic("template server/error.html not found")
+ }
+ return errTempl, templHandler
+ }
+ errTempl, templHandler = getErrorTemplateAndHandler(h)
+
+ srv := &fileServer{
+ baseURLs: baseURLs,
+ roots: roots,
+ c: c,
+ errorTemplate: func(ctx any) (io.Reader, error) {
+ // hugoTry does not block, getErrorTemplateAndHandler will fall back
+ // to cached values if nil.
+ templ, handler := getErrorTemplateAndHandler(c.hugoTry())
+ b := &bytes.Buffer{}
+ err := handler.ExecuteWithContext(context.Background(), templ, b, ctx)
+ return b, err
+ },
+ }
+
+ doLiveReload := !c.disableLiveReload
+
+ if doLiveReload {
+ livereload.Initialize()
+ }
+
+ sigs := make(chan os.Signal, 1)
+ signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
+ var servers []*http.Server
+
+ wg1, ctx := errgroup.WithContext(context.Background())
+
+ for i := range baseURLs {
+ mu, listener, serverURL, endpoint, err := srv.createEndpoint(i)
+ var srv *http.Server
+ if c.tlsCertFile != "" && c.tlsKeyFile != "" {
+ srv = &http.Server{
+ Addr: endpoint,
+ Handler: mu,
+ TLSConfig: &tls.Config{
+ MinVersion: tls.VersionTLS12,
+ },
+ }
+ } else {
+ srv = &http.Server{
+ Addr: endpoint,
+ Handler: mu,
+ }
+ }
+
+ servers = append(servers, srv)
+
+ if doLiveReload {
+ baseURL := baseURLs[i]
+ mu.HandleFunc(baseURL.Path()+"livereload.js", livereload.ServeJS)
+ mu.HandleFunc(baseURL.Path()+"livereload", livereload.Handler)
+ }
+ c.r.Printf("Web Server is available at %s (bind address %s) %s\n", serverURL, c.serverInterface, roots[i])
+ wg1.Go(func() error {
+ if c.tlsCertFile != "" && c.tlsKeyFile != "" {
+ err = srv.ServeTLS(listener, c.tlsCertFile, c.tlsKeyFile)
+ } else {
+ err = srv.Serve(listener)
+ }
+ if err != nil && err != http.ErrServerClosed {
+ return err
+ }
+ return nil
+ })
+ }
+
+ if c.r.IsTestRun() {
+ // Write a .ready file to disk to signal ready status.
+ // This is where the test is run from.
+ var baseURLs []string
+ for _, baseURL := range srv.baseURLs {
+ baseURLs = append(baseURLs, baseURL.String())
+ }
+ testInfo := map[string]any{
+ "baseURLs": baseURLs,
+ }
+
+ dir := os.Getenv("WORK")
+ if dir != "" {
+ readyFile := filepath.Join(dir, ".ready")
+ // encode the test info as JSON into the .ready file.
+ b, err := json.Marshal(testInfo)
+ if err != nil {
+ return err
+ }
+ err = os.WriteFile(readyFile, b, 0o777)
+ if err != nil {
+ return err
+ }
+ }
+
+ }
+
+ 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 {
+ case <-c.quit:
+ return nil
+ case <-sigs:
+ return nil
+ case <-ctx.Done():
+ return ctx.Err()
+ }
+ }
+ }()
+ if err != nil {
+ c.r.Println("Error:", err)
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+ wg2, ctx := errgroup.WithContext(ctx)
+ for _, srv := range servers {
+ wg2.Go(func() error {
+ return srv.Shutdown(ctx)
+ })
+ }
+
+ err1, err2 := wg1.Wait(), wg2.Wait()
+ if err1 != nil {
+ return err1
+ }
+ return err2
+}
+
+type serverPortListener struct {
+ p int
+ ln net.Listener
+}
- start := time.Now().UnixNano()
+type staticSyncer struct {
+ c *hugoBuilder
+}
+
+func (s *staticSyncer) isStatic(h *hugolib.HugoSites, filename string) bool {
+ return h.BaseFs.SourceFilesystems.IsStatic(filename)
+}
+
+func (s *staticSyncer) syncsStaticEvents(staticEvents []fsnotify.Event) error {
+ c := s.c
- for {
- runtime.ReadMemStats(&stats)
- if fileMemStats != nil {
- fileMemStats.WriteString(fmt.Sprintf("%d\t%d\t%d\t%d\t%d\n",
- (time.Now().UnixNano()-start)/1000000, stats.HeapSys, stats.HeapAlloc, stats.HeapIdle, stats.HeapReleased))
- time.Sleep(interval)
+ syncFn := func(sourceFs *filesystems.SourceFilesystem) (uint64, error) {
+ publishDir := helpers.FilePathSeparator
+
+ if sourceFs.PublishFolder != "" {
+ publishDir = filepath.Join(publishDir, sourceFs.PublishFolder)
+ }
+
+ syncer := fsync.NewSyncer()
+ c.withConf(func(conf *commonConfig) {
+ syncer.NoTimes = conf.configs.Base.NoTimes
+ syncer.NoChmod = conf.configs.Base.NoChmod
+ syncer.ChmodFilter = chmodFilter
+ syncer.SrcFs = sourceFs.Fs
+ syncer.DestFs = conf.fs.PublishDir
+ if c.s != nil && c.s.renderStaticToDisk {
+ syncer.DestFs = conf.fs.PublishDirStatic
+ }
+ })
+
+ logger := s.c.r.logger
+
+ for _, ev := range staticEvents {
+ // Due to our approach of layering both directories and the content's rendered output
+ // into one we can't accurately remove a file not in one of the source directories.
+ // If a file is in the local static dir and also in the theme static dir and we remove
+ // it from one of those locations we expect it to still exist in the destination
+ //
+ // If Hugo generates a file (from the content dir) over a static file
+ // the content generated file should take precedence.
+ //
+ // Because we are now watching and handling individual events it is possible that a static
+ // event that occupies the same path as a content generated file will take precedence
+ // until a regeneration of the content takes places.
+ //
+ // Hugo assumes that these cases are very rare and will permit this bad behavior
+ // The alternative is to track every single file and which pipeline rendered it
+ // and then to handle conflict resolution on every event.
+
+ fromPath := ev.Name
+
+ relPath, found := sourceFs.MakePathRelative(fromPath, true)
+
+ if !found {
+ // Not member of this virtual host.
+ continue
+ }
+
+ // Remove || rename is harder and will require an assumption.
+ // Hugo takes the following approach:
+ // If the static file exists in any of the static source directories after this event
+ // Hugo will re-sync it.
+ // If it does not exist in all of the static directories Hugo will remove it.
+ //
+ // This assumes that Hugo has not generated content on top of a static file and then removed
+ // the source of that static file. In this case Hugo will incorrectly remove that file
+ // from the published directory.
+ if ev.Op&fsnotify.Rename == fsnotify.Rename || ev.Op&fsnotify.Remove == fsnotify.Remove {
+ if _, err := sourceFs.Fs.Stat(relPath); herrors.IsNotExist(err) {
+ // If file doesn't exist in any static dir, remove it
+ logger.Println("File no longer exists in static dir, removing", relPath)
+ c.withConf(func(conf *commonConfig) {
+ _ = conf.fs.PublishDirStatic.RemoveAll(relPath)
+ })
+
+ } else if err == nil {
+ // If file still exists, sync it
+ logger.Println("Syncing", relPath, "to", publishDir)
+
+ if err := syncer.Sync(relPath, relPath); err != nil {
+ c.r.logger.Errorln(err)
+ }
} else {
- break
+ c.r.logger.Errorln(err)
}
+
+ continue
}
- }()
+
+ // For all other event operations Hugo will sync static.
+ logger.Println("Syncing", relPath, "to", publishDir)
+ if err := syncer.Sync(filepath.Join(publishDir, relPath), relPath); err != nil {
+ c.r.logger.Errorln(err)
+ }
+ }
+
+ return 0, nil
}
- return nil
+
+ _, err := c.doWithPublishDirs(syncFn)
+ return err
+}
+
+// chmodFilter is a ChmodFilter for static syncing.
+// Returns true to skip syncing permissions for directories and files without
+// owner-write permission. The primary use case is files from the module cache (0444).
+func chmodFilter(dst, src os.FileInfo) bool {
+ if src.IsDir() {
+ return true
+ }
+ return src.Mode().Perm()&0o200 == 0
+}
+
+func cleanErrorLog(content string) string {
+ content = logReplacer.Replace(content)
+ content = logDuplicateTemplateExecuteRe.ReplaceAllString(content, "")
+ content = logDuplicateTemplateParseRe.ReplaceAllString(content, "")
+ seen := make(map[string]bool)
+ parts := strings.Split(content, ": ")
+ keep := make([]string, 0, len(parts))
+ for _, part := range parts {
+ if seen[part] {
+ continue
+ }
+ seen[part] = true
+ keep = append(keep, part)
+ }
+ return strings.Join(keep, ": ")
+}
+
+func injectLiveReloadScript(src io.Reader, baseURL *url.URL) string {
+ var b bytes.Buffer
+ chain := transform.Chain{livereloadinject.New(baseURL)}
+ chain.Apply(&b, src)
+
+ return b.String()
+}
+
+func partitionDynamicEvents(sourceFs *filesystems.SourceFilesystems, events []fsnotify.Event) (de dynamicEvents) {
+ for _, e := range events {
+ if !sourceFs.IsContent(e.Name) {
+ de.AssetEvents = append(de.AssetEvents, e)
+ } else {
+ de.ContentEvents = append(de.ContentEvents, e)
+ }
+ }
+ return
+}
+
+func pickOneWriteOrCreatePath(contentTypes config.ContentTypesProvider, events []fsnotify.Event) string {
+ name := ""
+
+ for _, ev := range events {
+ if ev.Op&fsnotify.Write == fsnotify.Write || ev.Op&fsnotify.Create == fsnotify.Create {
+ if contentTypes.IsIndexContentFile(ev.Name) {
+ return ev.Name
+ }
+
+ if contentTypes.IsContentFile(ev.Name) {
+ name = ev.Name
+ }
+
+ }
+ }
+
+ return name
+}
+
+func formatByteCount(b uint64) string {
+ const unit = 1000
+ if b < unit {
+ return fmt.Sprintf("%d B", b)
+ }
+ div, exp := int64(unit), 0
+ for n := b / unit; n >= unit; n /= unit {
+ div *= unit
+ exp++
+ }
+ return fmt.Sprintf("%.1f %cB",
+ float64(b)/float64(div), "kMGTPE"[exp])
+}
+
+func canRedirect(requestURIWithoutQuery string, r *http.Request) bool {
+ if r.Header.Get(hugoHeaderRedirect) != "" {
+ return false
+ }
+ return isNavigation(requestURIWithoutQuery, r)
+}
+
+// Sec-Fetch-Mode should be sent by all recent browser versions, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Fetch-Mode#navigate
+// Fall back to the file extension if not set.
+// The main take here is that we don't want to have CSS/JS files etc. partake in this logic.
+func isNavigation(requestURIWithoutQuery string, r *http.Request) bool {
+ return r.Header.Get("Sec-Fetch-Mode") == "navigate" || isPropablyHTMLRequest(requestURIWithoutQuery)
+}
+
+func isPropablyHTMLRequest(requestURIWithoutQuery string) bool {
+ if strings.HasSuffix(requestURIWithoutQuery, "/") || strings.HasSuffix(requestURIWithoutQuery, "html") || strings.HasSuffix(requestURIWithoutQuery, "htm") {
+ return true
+ }
+ return !strings.Contains(requestURIWithoutQuery, ".")
}
diff --git a/commands/server_errors.go b/commands/server_errors.go
deleted file mode 100644
index 0e3bc50deae..00000000000
--- a/commands/server_errors.go
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// 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 (
- "bytes"
- "io"
- "net/url"
-
- "github.com/gohugoio/hugo/transform"
- "github.com/gohugoio/hugo/transform/livereloadinject"
-)
-
-var buildErrorTemplate = `
-
-
-
- Hugo Server: Error
-
-
-
-
- {{ highlight .Error "apl" "linenos=false,noclasses=true,style=paraiso-dark" }}
- {{ with .File }}
- {{ $params := printf "noclasses=true,style=paraiso-dark,linenos=table,hl_lines=%d,linenostart=%d" (add .LinesPos 1) (sub .Position.LineNumber .LinesPos) }}
- {{ $lexer := .ChromaLexer | default "go-html-template" }}
- {{ highlight (delimit .Lines "\n") $lexer $params }}
- {{ end }}
- {{ with .StackTrace }}
- {{ highlight . "apl" "noclasses=true,style=paraiso-dark" }}
- {{ end }}
-
{{ .Version }}
- Reload Page
-
-
-
-`
-
-func injectLiveReloadScript(src io.Reader, baseURL url.URL) string {
- var b bytes.Buffer
- chain := transform.Chain{livereloadinject.New(baseURL)}
- chain.Apply(&b, src)
-
- return b.String()
-}
diff --git a/commands/server_test.go b/commands/server_test.go
deleted file mode 100644
index 05d21a5165b..00000000000
--- a/commands/server_test.go
+++ /dev/null
@@ -1,133 +0,0 @@
-// Copyright 2015 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 (
- "fmt"
- "net/http"
- "os"
- "runtime"
- "strings"
- "testing"
- "time"
-
- "github.com/gohugoio/hugo/config"
- "github.com/gohugoio/hugo/helpers"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestServer(t *testing.T) {
- if isWindowsCI() {
- // TODO(bep) not sure why server tests have started to fail on the Windows CI server.
- t.Skip("Skip server test on appveyor")
- }
- c := qt.New(t)
- dir, clean, err := createSimpleTestSite(t, testSiteConfig{})
- defer clean()
- c.Assert(err, qt.IsNil)
-
- // Let us hope that this port is available on all systems ...
- port := 1331
-
- defer func() {
- os.RemoveAll(dir)
- }()
-
- stop := make(chan bool)
-
- b := newCommandsBuilder()
- scmd := b.newServerCmdSignaled(stop)
-
- cmd := scmd.getCommand()
- cmd.SetArgs([]string{"-s=" + dir, fmt.Sprintf("-p=%d", port)})
-
- go func() {
- _, err = cmd.ExecuteC()
- c.Assert(err, qt.IsNil)
- }()
-
- // There is no way to know exactly when the server is ready for connections.
- // We could improve by something like https://golang.org/pkg/net/http/httptest/#Server
- // But for now, let us sleep and pray!
- time.Sleep(2 * time.Second)
-
- resp, err := http.Get("http://localhost:1331/")
- c.Assert(err, qt.IsNil)
- defer resp.Body.Close()
- homeContent := helpers.ReaderToString(resp.Body)
-
- c.Assert(homeContent, qt.Contains, "List: Hugo Commands")
- c.Assert(homeContent, qt.Contains, "Environment: development")
-
- // Stop the server.
- stop <- true
-}
-
-func TestFixURL(t *testing.T) {
- type data struct {
- TestName string
- CLIBaseURL string
- CfgBaseURL string
- AppendPort bool
- Port int
- Result string
- }
- tests := []data{
- {"Basic http localhost", "", "http://foo.com", true, 1313, "http://localhost:1313/"},
- {"Basic https production, http localhost", "", "https://foo.com", true, 1313, "http://localhost:1313/"},
- {"Basic subdir", "", "http://foo.com/bar", true, 1313, "http://localhost:1313/bar/"},
- {"Basic production", "http://foo.com", "http://foo.com", false, 80, "http://foo.com/"},
- {"Production subdir", "http://foo.com/bar", "http://foo.com/bar", false, 80, "http://foo.com/bar/"},
- {"No http", "", "foo.com", true, 1313, "//localhost:1313/"},
- {"Override configured port", "", "foo.com:2020", true, 1313, "//localhost:1313/"},
- {"No http production", "foo.com", "foo.com", false, 80, "//foo.com/"},
- {"No http production with port", "foo.com", "foo.com", true, 2020, "//foo.com:2020/"},
- {"No config", "", "", true, 1313, "//localhost:1313/"},
- }
-
- for _, test := range tests {
- t.Run(test.TestName, func(t *testing.T) {
- b := newCommandsBuilder()
- s := b.newServerCmd()
- v := config.New()
- baseURL := test.CLIBaseURL
- v.Set("baseURL", test.CfgBaseURL)
- s.serverAppend = test.AppendPort
- s.serverPort = test.Port
- result, err := s.fixURL(v, baseURL, s.serverPort)
- if err != nil {
- t.Errorf("Unexpected error %s", err)
- }
- if result != test.Result {
- t.Errorf("Expected %q, got %q", test.Result, result)
- }
- })
- }
-}
-
-func TestRemoveErrorPrefixFromLog(t *testing.T) {
- c := qt.New(t)
- content := `ERROR 2018/10/07 13:11:12 Error while rendering "home": template: _default/baseof.html:4:3: executing "main" at : error calling partial: template: partials/logo.html:5:84: executing "partials/logo.html" at <$resized.AHeight>: can't evaluate field AHeight in type *resource.Image
-ERROR 2018/10/07 13:11:12 Rebuild failed: logged 1 error(s)
-`
-
- withoutError := removeErrorPrefixFromLog(content)
-
- c.Assert(strings.Contains(withoutError, "ERROR"), qt.Equals, false)
-}
-
-func isWindowsCI() bool {
- return runtime.GOOS == "windows" && os.Getenv("CI") != ""
-}
diff --git a/commands/static_syncer.go b/commands/static_syncer.go
deleted file mode 100644
index 5569d4de688..00000000000
--- a/commands/static_syncer.go
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright 2017 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 (
- "os"
- "path/filepath"
-
- "github.com/gohugoio/hugo/hugolib/filesystems"
-
- "github.com/fsnotify/fsnotify"
- "github.com/gohugoio/hugo/helpers"
- "github.com/spf13/fsync"
-)
-
-type staticSyncer struct {
- c *commandeer
-}
-
-func newStaticSyncer(c *commandeer) (*staticSyncer, error) {
- return &staticSyncer{c: c}, nil
-}
-
-func (s *staticSyncer) isStatic(filename string) bool {
- return s.c.hugo().BaseFs.SourceFilesystems.IsStatic(filename)
-}
-
-func (s *staticSyncer) syncsStaticEvents(staticEvents []fsnotify.Event) error {
- c := s.c
-
- syncFn := func(sourceFs *filesystems.SourceFilesystem) (uint64, error) {
- publishDir := c.hugo().PathSpec.PublishDir
- // If root, remove the second '/'
- if publishDir == "//" {
- publishDir = helpers.FilePathSeparator
- }
-
- if sourceFs.PublishFolder != "" {
- publishDir = filepath.Join(publishDir, sourceFs.PublishFolder)
- }
-
- syncer := fsync.NewSyncer()
- syncer.NoTimes = c.Cfg.GetBool("noTimes")
- syncer.NoChmod = c.Cfg.GetBool("noChmod")
- syncer.ChmodFilter = chmodFilter
- syncer.SrcFs = sourceFs.Fs
- syncer.DestFs = c.Fs.Destination
-
- // prevent spamming the log on changes
- logger := helpers.NewDistinctErrorLogger()
-
- for _, ev := range staticEvents {
- // Due to our approach of layering both directories and the content's rendered output
- // into one we can't accurately remove a file not in one of the source directories.
- // If a file is in the local static dir and also in the theme static dir and we remove
- // it from one of those locations we expect it to still exist in the destination
- //
- // If Hugo generates a file (from the content dir) over a static file
- // the content generated file should take precedence.
- //
- // Because we are now watching and handling individual events it is possible that a static
- // event that occupies the same path as a content generated file will take precedence
- // until a regeneration of the content takes places.
- //
- // Hugo assumes that these cases are very rare and will permit this bad behavior
- // The alternative is to track every single file and which pipeline rendered it
- // and then to handle conflict resolution on every event.
-
- fromPath := ev.Name
-
- relPath, found := sourceFs.MakePathRelative(fromPath)
-
- if !found {
- // Not member of this virtual host.
- continue
- }
-
- // Remove || rename is harder and will require an assumption.
- // Hugo takes the following approach:
- // If the static file exists in any of the static source directories after this event
- // Hugo will re-sync it.
- // If it does not exist in all of the static directories Hugo will remove it.
- //
- // This assumes that Hugo has not generated content on top of a static file and then removed
- // the source of that static file. In this case Hugo will incorrectly remove that file
- // from the published directory.
- if ev.Op&fsnotify.Rename == fsnotify.Rename || ev.Op&fsnotify.Remove == fsnotify.Remove {
- if _, err := sourceFs.Fs.Stat(relPath); os.IsNotExist(err) {
- // If file doesn't exist in any static dir, remove it
- toRemove := filepath.Join(publishDir, relPath)
-
- logger.Println("File no longer exists in static dir, removing", toRemove)
- _ = c.Fs.Destination.RemoveAll(toRemove)
- } else if err == nil {
- // If file still exists, sync it
- logger.Println("Syncing", relPath, "to", publishDir)
-
- if err := syncer.Sync(filepath.Join(publishDir, relPath), relPath); err != nil {
- c.logger.Errorln(err)
- }
- } else {
- c.logger.Errorln(err)
- }
-
- continue
- }
-
- // For all other event operations Hugo will sync static.
- logger.Println("Syncing", relPath, "to", publishDir)
- if err := syncer.Sync(filepath.Join(publishDir, relPath), relPath); err != nil {
- c.logger.Errorln(err)
- }
- }
-
- return 0, nil
- }
-
- _, err := c.doWithPublishDirs(syncFn)
- return err
-}
diff --git a/commands/version.go b/commands/version.go
deleted file mode 100644
index 287950a2dd7..00000000000
--- a/commands/version.go
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2015 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/common/hugo"
- "github.com/spf13/cobra"
- jww "github.com/spf13/jwalterweatherman"
-)
-
-var _ cmder = (*versionCmd)(nil)
-
-type versionCmd struct {
- *baseCmd
-}
-
-func newVersionCmd() *versionCmd {
- return &versionCmd{
- newBaseCmd(&cobra.Command{
- Use: "version",
- Short: "Print the version number of Hugo",
- Long: `All software has versions. This is Hugo's.`,
- RunE: func(cmd *cobra.Command, args []string) error {
- printHugoVersion()
- return nil
- },
- }),
- }
-}
-
-func printHugoVersion() {
- jww.FEEDBACK.Println(hugo.BuildVersionString())
-}
diff --git a/common/collections/append.go b/common/collections/append.go
index d6fb89241c8..8a5aee40c00 100644
--- a/common/collections/append.go
+++ b/common/collections/append.go
@@ -16,35 +16,72 @@ package collections
import (
"fmt"
"reflect"
+
+ "github.com/gohugoio/hugo/common/hreflect"
)
// Append appends from to a slice to and returns the resulting slice.
// If length of from is one and the only element is a slice of same type as to,
// it will be appended.
-func Append(to interface{}, from ...interface{}) (interface{}, error) {
- tov, toIsNil := indirect(reflect.ValueOf(to))
+func Append(to any, from ...any) (any, error) {
+ if len(from) == 0 {
+ return to, nil
+ }
+ tov, toIsNil := hreflect.Indirect(reflect.ValueOf(to))
toIsNil = toIsNil || to == nil
var tot reflect.Type
if !toIsNil {
+ if tov.Kind() == reflect.Slice {
+ // Create a copy of tov, so we don't modify the original.
+ c := reflect.MakeSlice(tov.Type(), tov.Len(), tov.Len()+len(from))
+ reflect.Copy(c, tov)
+ tov = c
+ }
+
if tov.Kind() != reflect.Slice {
return nil, fmt.Errorf("expected a slice, got %T", to)
}
tot = tov.Type().Elem()
+ if tot.Kind() == reflect.Slice {
+ totvt := tot.Elem()
+ fromvs := make([]reflect.Value, len(from))
+ for i, f := range from {
+ fromv := reflect.ValueOf(f)
+ fromt := fromv.Type()
+ if fromt.Kind() == reflect.Slice {
+ fromt = fromt.Elem()
+ }
+ if totvt != fromt {
+ return nil, fmt.Errorf("cannot append slice of %s to slice of %s", fromt, totvt)
+ } else {
+ fromvs[i] = fromv
+ }
+ }
+ return reflect.Append(tov, fromvs...).Interface(), nil
+
+ }
+
toIsNil = tov.Len() == 0
if len(from) == 1 {
fromv := reflect.ValueOf(from[0])
+ if !fromv.IsValid() {
+ // from[0] is nil
+ return appendToInterfaceSliceFromValues(tov, fromv)
+ }
+ fromt := fromv.Type()
+ if fromt.Kind() == reflect.Slice {
+ fromt = fromt.Elem()
+ }
if fromv.Kind() == reflect.Slice {
if toIsNil {
// If we get nil []string, we just return the []string
return from[0], nil
}
- fromt := reflect.TypeOf(from[0]).Elem()
-
// If we get []string []string, we append the from slice to to
if tot == fromt {
return reflect.AppendSlice(tov, fromv).Interface(), nil
@@ -52,6 +89,7 @@ func Append(to interface{}, from ...interface{}) (interface{}, error) {
// Fall back to a []interface{} slice.
return appendToInterfaceSliceFromValues(tov, fromv)
}
+
}
}
}
@@ -62,9 +100,9 @@ func Append(to interface{}, from ...interface{}) (interface{}, error) {
for _, f := range from {
fv := reflect.ValueOf(f)
- if !fv.Type().AssignableTo(tot) {
+ if !fv.IsValid() || !fv.Type().AssignableTo(tot) {
// Fall back to a []interface{} slice.
- tov, _ := indirect(reflect.ValueOf(to))
+ tov, _ := hreflect.Indirect(reflect.ValueOf(to))
return appendToInterfaceSlice(tov, from...)
}
tov = reflect.Append(tov, fv)
@@ -73,11 +111,15 @@ func Append(to interface{}, from ...interface{}) (interface{}, error) {
return tov.Interface(), nil
}
-func appendToInterfaceSliceFromValues(slice1, slice2 reflect.Value) ([]interface{}, error) {
- var tos []interface{}
+func appendToInterfaceSliceFromValues(slice1, slice2 reflect.Value) ([]any, error) {
+ var tos []any
for _, slice := range []reflect.Value{slice1, slice2} {
- for i := 0; i < slice.Len(); i++ {
+ if !slice.IsValid() {
+ tos = append(tos, nil)
+ continue
+ }
+ for i := range slice.Len() {
tos = append(tos, slice.Index(i).Interface())
}
}
@@ -85,10 +127,10 @@ func appendToInterfaceSliceFromValues(slice1, slice2 reflect.Value) ([]interface
return tos, nil
}
-func appendToInterfaceSlice(tov reflect.Value, from ...interface{}) ([]interface{}, error) {
- var tos []interface{}
+func appendToInterfaceSlice(tov reflect.Value, from ...any) ([]any, error) {
+ var tos []any
- for i := 0; i < tov.Len(); i++ {
+ for i := range tov.Len() {
tos = append(tos, tov.Index(i).Interface())
}
@@ -96,17 +138,3 @@ func appendToInterfaceSlice(tov reflect.Value, from ...interface{}) ([]interface
return tos, nil
}
-
-// indirect is borrowed from the Go stdlib: 'text/template/exec.go'
-// TODO(bep) consolidate
-func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
- for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() {
- if v.IsNil() {
- return v, true
- }
- if v.Kind() == reflect.Interface && v.NumMethod() > 0 {
- break
- }
- }
- return v, false
-}
diff --git a/common/collections/append_test.go b/common/collections/append_test.go
index 7d6117de858..c198ca09c3d 100644
--- a/common/collections/append_test.go
+++ b/common/collections/append_test.go
@@ -24,26 +24,26 @@ func TestAppend(t *testing.T) {
t.Parallel()
c := qt.New(t)
- for _, test := range []struct {
- start interface{}
- addend []interface{}
- expected interface{}
+ for i, test := range []struct {
+ start any
+ addend []any
+ expected any
}{
- {[]string{"a", "b"}, []interface{}{"c"}, []string{"a", "b", "c"}},
- {[]string{"a", "b"}, []interface{}{"c", "d", "e"}, []string{"a", "b", "c", "d", "e"}},
- {[]string{"a", "b"}, []interface{}{[]string{"c", "d", "e"}}, []string{"a", "b", "c", "d", "e"}},
- {[]string{"a"}, []interface{}{"b", template.HTML("c")}, []interface{}{"a", "b", template.HTML("c")}},
- {nil, []interface{}{"a", "b"}, []string{"a", "b"}},
- {nil, []interface{}{nil}, []interface{}{nil}},
- {[]interface{}{}, []interface{}{[]string{"c", "d", "e"}}, []string{"c", "d", "e"}},
+ {[]string{"a", "b"}, []any{"c"}, []string{"a", "b", "c"}},
+ {[]string{"a", "b"}, []any{"c", "d", "e"}, []string{"a", "b", "c", "d", "e"}},
+ {[]string{"a", "b"}, []any{[]string{"c", "d", "e"}}, []string{"a", "b", "c", "d", "e"}},
+ {[]string{"a"}, []any{"b", template.HTML("c")}, []any{"a", "b", template.HTML("c")}},
+ {nil, []any{"a", "b"}, []string{"a", "b"}},
+ {nil, []any{nil}, []any{nil}},
+ {[]any{}, []any{[]string{"c", "d", "e"}}, []string{"c", "d", "e"}},
{
tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}},
- []interface{}{&tstSlicer{"c"}},
+ []any{&tstSlicer{"c"}},
tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}, &tstSlicer{"c"}},
},
{
&tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}},
- []interface{}{&tstSlicer{"c"}},
+ []any{&tstSlicer{"c"}},
tstSlicers{
&tstSlicer{"a"},
&tstSlicer{"b"},
@@ -52,28 +52,32 @@ func TestAppend(t *testing.T) {
},
{
testSlicerInterfaces{&tstSlicerIn1{"a"}, &tstSlicerIn1{"b"}},
- []interface{}{&tstSlicerIn1{"c"}},
+ []any{&tstSlicerIn1{"c"}},
testSlicerInterfaces{&tstSlicerIn1{"a"}, &tstSlicerIn1{"b"}, &tstSlicerIn1{"c"}},
},
- //https://github.com/gohugoio/hugo/issues/5361
+ // https://github.com/gohugoio/hugo/issues/5361
{
[]string{"a", "b"},
- []interface{}{tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}}},
- []interface{}{"a", "b", &tstSlicer{"a"}, &tstSlicer{"b"}},
+ []any{tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}}},
+ []any{"a", "b", &tstSlicer{"a"}, &tstSlicer{"b"}},
},
{
[]string{"a", "b"},
- []interface{}{&tstSlicer{"a"}},
- []interface{}{"a", "b", &tstSlicer{"a"}},
+ []any{&tstSlicer{"a"}},
+ []any{"a", "b", &tstSlicer{"a"}},
},
// Errors
- {"", []interface{}{[]string{"a", "b"}}, false},
+ {"", []any{[]string{"a", "b"}}, false},
// No string concatenation.
{
"ab",
- []interface{}{"c"},
+ []any{"c"},
false,
},
+ {[]string{"a", "b"}, []any{nil}, []any{"a", "b", nil}},
+ {[]string{"a", "b"}, []any{nil, "d", nil}, []any{"a", "b", nil, "d", nil}},
+ {[]any{"a", nil, "c"}, []any{"d", nil, "f"}, []any{"a", nil, "c", "d", nil, "f"}},
+ {[]string{"a", "b"}, []any{}, []string{"a", "b"}},
} {
result, err := Append(test.start, test.addend...)
@@ -85,6 +89,61 @@ func TestAppend(t *testing.T) {
}
c.Assert(err, qt.IsNil)
- c.Assert(result, qt.DeepEquals, test.expected)
+ c.Assert(result, qt.DeepEquals, test.expected, qt.Commentf("test: [%d] %v", i, test))
+ }
+}
+
+// #11093
+func TestAppendToMultiDimensionalSlice(t *testing.T) {
+ t.Parallel()
+ c := qt.New(t)
+
+ for _, test := range []struct {
+ to any
+ from []any
+ expected any
+ }{
+ {
+ [][]string{{"a", "b"}},
+ []any{[]string{"c", "d"}},
+ [][]string{
+ {"a", "b"},
+ {"c", "d"},
+ },
+ },
+ {
+ [][]string{{"a", "b"}},
+ []any{[]string{"c", "d"}, []string{"e", "f"}},
+ [][]string{
+ {"a", "b"},
+ {"c", "d"},
+ {"e", "f"},
+ },
+ },
+ {
+ [][]string{{"a", "b"}},
+ []any{[]int{1, 2}},
+ false,
+ },
+ } {
+ result, err := Append(test.to, test.from...)
+ if b, ok := test.expected.(bool); ok && !b {
+ c.Assert(err, qt.Not(qt.IsNil))
+ } else {
+ c.Assert(err, qt.IsNil)
+ c.Assert(result, qt.DeepEquals, test.expected)
+ }
}
}
+
+func TestAppendShouldMakeACopyOfTheInputSlice(t *testing.T) {
+ t.Parallel()
+ c := qt.New(t)
+ slice := make([]string, 0, 100)
+ slice = append(slice, "a", "b")
+ result, err := Append(slice, "c")
+ c.Assert(err, qt.IsNil)
+ slice[0] = "d"
+ c.Assert(result, qt.DeepEquals, []string{"a", "b", "c"})
+ c.Assert(slice, qt.DeepEquals, []string{"d", "b"})
+}
diff --git a/common/collections/collections.go b/common/collections/collections.go
index bb47c8acca6..0b46abee922 100644
--- a/common/collections/collections.go
+++ b/common/collections/collections.go
@@ -17,5 +17,5 @@ package collections
// Grouper defines a very generic way to group items by a given key.
type Grouper interface {
- Group(key interface{}, items interface{}) (interface{}, error)
+ Group(key any, items any) (any, error)
}
diff --git a/common/collections/slice.go b/common/collections/slice.go
index 07ad48eb3c7..731f489f9bd 100644
--- a/common/collections/slice.go
+++ b/common/collections/slice.go
@@ -15,17 +15,18 @@ package collections
import (
"reflect"
+ "sort"
)
// Slicer defines a very generic way to create a typed slice. This is used
// in collections.Slice template func to get types such as Pages, PageGroups etc.
// instead of the less useful []interface{}.
type Slicer interface {
- Slice(items interface{}) (interface{}, error)
+ Slice(items any) (any, error)
}
// Slice returns a slice of all passed arguments.
-func Slice(args ...interface{}) interface{} {
+func Slice(args ...any) any {
if len(args) == 0 {
return args
}
@@ -66,11 +67,29 @@ func Slice(args ...interface{}) interface{} {
}
// StringSliceToInterfaceSlice converts ss to []interface{}.
-func StringSliceToInterfaceSlice(ss []string) []interface{} {
- result := make([]interface{}, len(ss))
+func StringSliceToInterfaceSlice(ss []string) []any {
+ result := make([]any, len(ss))
for i, s := range ss {
result[i] = s
}
return result
+}
+
+type SortedStringSlice []string
+// Contains returns true if s is in ss.
+func (ss SortedStringSlice) Contains(s string) bool {
+ i := sort.SearchStrings(ss, s)
+ return i < len(ss) && ss[i] == s
+}
+
+// Count returns the number of times s is in ss.
+func (ss SortedStringSlice) Count(s string) int {
+ var count int
+ i := sort.SearchStrings(ss, s)
+ for i < len(ss) && ss[i] == s {
+ count++
+ i++
+ }
+ return count
}
diff --git a/common/collections/slice_test.go b/common/collections/slice_test.go
index 93bad84da61..4008a5e6cb6 100644
--- a/common/collections/slice_test.go
+++ b/common/collections/slice_test.go
@@ -46,8 +46,8 @@ type tstSlicer struct {
TheName string
}
-func (p *tstSlicerIn1) Slice(in interface{}) (interface{}, error) {
- items := in.([]interface{})
+func (p *tstSlicerIn1) Slice(in any) (any, error) {
+ items := in.([]any)
result := make(testSlicerInterfaces, len(items))
for i, v := range items {
switch vv := v.(type) {
@@ -60,8 +60,8 @@ func (p *tstSlicerIn1) Slice(in interface{}) (interface{}, error) {
return result, nil
}
-func (p *tstSlicerIn2) Slice(in interface{}) (interface{}, error) {
- items := in.([]interface{})
+func (p *tstSlicerIn2) Slice(in any) (any, error) {
+ items := in.([]any)
result := make(testSlicerInterfaces, len(items))
for i, v := range items {
switch vv := v.(type) {
@@ -82,8 +82,8 @@ func (p *tstSlicerIn2) Name() string {
return p.TheName
}
-func (p *tstSlicer) Slice(in interface{}) (interface{}, error) {
- items := in.([]interface{})
+func (p *tstSlicer) Slice(in any) (any, error) {
+ items := in.([]any)
result := make(tstSlicers, len(items))
for i, v := range items {
switch vv := v.(type) {
@@ -103,17 +103,17 @@ func TestSlice(t *testing.T) {
c := qt.New(t)
for i, test := range []struct {
- args []interface{}
- expected interface{}
+ args []any
+ expected any
}{
- {[]interface{}{"a", "b"}, []string{"a", "b"}},
- {[]interface{}{&tstSlicer{"a"}, &tstSlicer{"b"}}, tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}}},
- {[]interface{}{&tstSlicer{"a"}, "b"}, []interface{}{&tstSlicer{"a"}, "b"}},
- {[]interface{}{}, []interface{}{}},
- {[]interface{}{nil}, []interface{}{nil}},
- {[]interface{}{5, "b"}, []interface{}{5, "b"}},
- {[]interface{}{&tstSlicerIn1{"a"}, &tstSlicerIn2{"b"}}, testSlicerInterfaces{&tstSlicerIn1{"a"}, &tstSlicerIn2{"b"}}},
- {[]interface{}{&tstSlicerIn1{"a"}, &tstSlicer{"b"}}, []interface{}{&tstSlicerIn1{"a"}, &tstSlicer{"b"}}},
+ {[]any{"a", "b"}, []string{"a", "b"}},
+ {[]any{&tstSlicer{"a"}, &tstSlicer{"b"}}, tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}}},
+ {[]any{&tstSlicer{"a"}, "b"}, []any{&tstSlicer{"a"}, "b"}},
+ {[]any{}, []any{}},
+ {[]any{nil}, []any{nil}},
+ {[]any{5, "b"}, []any{5, "b"}},
+ {[]any{&tstSlicerIn1{"a"}, &tstSlicerIn2{"b"}}, testSlicerInterfaces{&tstSlicerIn1{"a"}, &tstSlicerIn2{"b"}}},
+ {[]any{&tstSlicerIn1{"a"}, &tstSlicer{"b"}}, []any{&tstSlicerIn1{"a"}, &tstSlicer{"b"}}},
} {
errMsg := qt.Commentf("[%d] %v", i, test.args)
@@ -122,3 +122,51 @@ func TestSlice(t *testing.T) {
c.Assert(test.expected, qt.DeepEquals, result, errMsg)
}
}
+
+func TestSortedStringSlice(t *testing.T) {
+ t.Parallel()
+ c := qt.New(t)
+
+ var s SortedStringSlice = []string{"a", "b", "b", "b", "c", "d"}
+
+ c.Assert(s.Contains("a"), qt.IsTrue)
+ c.Assert(s.Contains("b"), qt.IsTrue)
+ c.Assert(s.Contains("z"), qt.IsFalse)
+ c.Assert(s.Count("b"), qt.Equals, 3)
+ c.Assert(s.Count("z"), qt.Equals, 0)
+ c.Assert(s.Count("a"), qt.Equals, 1)
+}
+
+func TestStringSliceToInterfaceSlice(t *testing.T) {
+ t.Parallel()
+ c := qt.New(t)
+
+ tests := []struct {
+ name string
+ in []string
+ want []any
+ }{
+ {
+ name: "empty slice",
+ in: []string{},
+ want: []any{},
+ },
+ {
+ name: "single element",
+ in: []string{"hello"},
+ want: []any{"hello"},
+ },
+ {
+ name: "multiple elements",
+ in: []string{"a", "b", "c"},
+ want: []any{"a", "b", "c"},
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := StringSliceToInterfaceSlice(tt.in)
+ c.Assert(got, qt.DeepEquals, tt.want)
+ })
+ }
+}
diff --git a/common/collections/stack.go b/common/collections/stack.go
new file mode 100644
index 00000000000..3eb6ab51503
--- /dev/null
+++ b/common/collections/stack.go
@@ -0,0 +1,135 @@
+// 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 collections
+
+import (
+ "iter"
+ "slices"
+ "sync"
+
+ "github.com/gohugoio/hugo/common/hiter"
+)
+
+// StackThreadSafe is a simple LIFO stack that is safe for concurrent use.
+type StackThreadSafe[T any] struct {
+ items []T
+ zero T
+ mu sync.RWMutex
+}
+
+func NewStackThreadSafe[T any]() *StackThreadSafe[T] {
+ return &StackThreadSafe[T]{}
+}
+
+func (s *StackThreadSafe[T]) Push(item T) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ s.items = append(s.items, item)
+}
+
+func (s *StackThreadSafe[T]) Pop() (T, bool) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ if len(s.items) == 0 {
+ return s.zero, false
+ }
+ item := s.items[len(s.items)-1]
+ s.items = s.items[:len(s.items)-1]
+ return item, true
+}
+
+func (s *StackThreadSafe[T]) Peek() (T, bool) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+ if len(s.items) == 0 {
+ return s.zero, false
+ }
+ return s.items[len(s.items)-1], true
+}
+
+func (s *StackThreadSafe[T]) Len() int {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+ return len(s.items)
+}
+
+// All returns all items in the stack, from bottom to top.
+func (s *StackThreadSafe[T]) All() iter.Seq2[int, T] {
+ return hiter.Lock2(slices.All(s.items), s.mu.RLock, s.mu.RUnlock)
+}
+
+func (s *StackThreadSafe[T]) Drain() []T {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ items := s.items
+ s.items = nil
+ return items
+}
+
+func (s *StackThreadSafe[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 = slices.Delete(s.items, i, i+1)
+ }
+ }
+ return items
+}
+
+// Stack is a simple LIFO stack that is not safe for concurrent use.
+type Stack[T any] struct {
+ items []T
+ zero T
+}
+
+func NewStack[T any]() *Stack[T] {
+ return &Stack[T]{}
+}
+
+func (s *Stack[T]) Push(item T) {
+ s.items = append(s.items, item)
+}
+
+func (s *Stack[T]) Pop() (T, bool) {
+ if len(s.items) == 0 {
+ return s.zero, false
+ }
+ item := s.items[len(s.items)-1]
+ s.items = s.items[:len(s.items)-1]
+ return item, true
+}
+
+func (s *Stack[T]) Peek() (T, bool) {
+ if len(s.items) == 0 {
+ return s.zero, false
+ }
+ return s.items[len(s.items)-1], true
+}
+
+func (s *Stack[T]) Len() int {
+ return len(s.items)
+}
+
+func (s *Stack[T]) All() iter.Seq2[int, T] {
+ return slices.All(s.items)
+}
+
+func (s *Stack[T]) Drain() []T {
+ items := s.items
+ s.items = nil
+ return items
+}
diff --git a/common/collections/stack_test.go b/common/collections/stack_test.go
new file mode 100644
index 00000000000..4b2b1a59a71
--- /dev/null
+++ b/common/collections/stack_test.go
@@ -0,0 +1,77 @@
+package collections
+
+import (
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestNewStack(t *testing.T) {
+ t.Parallel()
+ c := qt.New(t)
+
+ s := NewStackThreadSafe[int]()
+
+ c.Assert(s, qt.IsNotNil)
+}
+
+func TestStackBasic(t *testing.T) {
+ t.Parallel()
+ c := qt.New(t)
+
+ s := NewStackThreadSafe[int]()
+
+ c.Assert(s.Len(), qt.Equals, 0)
+
+ s.Push(1)
+ s.Push(2)
+ s.Push(3)
+
+ c.Assert(s.Len(), qt.Equals, 3)
+
+ top, ok := s.Peek()
+ c.Assert(ok, qt.Equals, true)
+ c.Assert(top, qt.Equals, 3)
+
+ popped, ok := s.Pop()
+ c.Assert(ok, qt.Equals, true)
+ c.Assert(popped, qt.Equals, 3)
+
+ c.Assert(s.Len(), qt.Equals, 2)
+
+ _, _ = s.Pop()
+ _, _ = s.Pop()
+ _, ok = s.Pop()
+
+ c.Assert(ok, qt.Equals, false)
+}
+
+func TestStackDrain(t *testing.T) {
+ t.Parallel()
+ c := qt.New(t)
+
+ s := NewStackThreadSafe[string]()
+ s.Push("a")
+ s.Push("b")
+
+ got := s.Drain()
+
+ c.Assert(got, qt.DeepEquals, []string{"a", "b"})
+ c.Assert(s.Len(), qt.Equals, 0)
+}
+
+func TestStackDrainMatching(t *testing.T) {
+ t.Parallel()
+ c := qt.New(t)
+
+ s := NewStackThreadSafe[int]()
+ s.Push(1)
+ s.Push(2)
+ s.Push(3)
+ s.Push(4)
+
+ got := s.DrainMatching(func(v int) bool { return v%2 == 0 })
+
+ c.Assert(got, qt.DeepEquals, []int{4, 2})
+ c.Assert(s.Drain(), qt.DeepEquals, []int{1, 3})
+}
diff --git a/common/constants/constants.go b/common/constants/constants.go
index e416d4ad35f..c7bbaa54127 100644
--- a/common/constants/constants.go
+++ b/common/constants/constants.go
@@ -13,13 +13,37 @@
package constants
-// Error IDs.
+// Error/Warning IDs.
// Do not change these values.
const (
- ErrIDAmbigousDisableKindTaxonomy = "error-disable-taxonomy"
- ErrIDAmbigousOutputKindTaxonomy = "error-output-taxonomy"
-
// IDs for remote errors in tpl/data.
ErrRemoteGetJSON = "error-remote-getjson"
ErrRemoteGetCSV = "error-remote-getcsv"
+
+ WarnFrontMatterParamsOverrides = "warning-frontmatter-params-overrides"
+ WarnRenderShortcodesInHTML = "warning-rendershortcodes-in-html"
+ WarnGoldmarkRawHTML = "warning-goldmark-raw-html"
+ WarnPartialSuperfluousPrefix = "warning-partial-superfluous-prefix"
+ WarnHomePageIsLeafBundle = "warning-home-page-is-leaf-bundle"
+)
+
+// Field/method names with special meaning.
+const (
+ FieldRelPermalink = "RelPermalink"
+ FieldPermalink = "Permalink"
)
+
+// IsFieldRelOrPermalink returns whether the given name is a RelPermalink or Permalink.
+func IsFieldRelOrPermalink(name string) bool {
+ return name == FieldRelPermalink || name == FieldPermalink
+}
+
+// Resource transformations.
+const (
+ ResourceTransformationFingerprint = "fingerprint"
+)
+
+// IsResourceTransformationPermalinkHash returns whether the given name is a resource transformation that changes the permalink based on the content.
+func IsResourceTransformationPermalinkHash(name string) bool {
+ return name == ResourceTransformationFingerprint
+}
diff --git a/common/docs.go b/common/docs.go
new file mode 100644
index 00000000000..041a62a0127
--- /dev/null
+++ b/common/docs.go
@@ -0,0 +1,2 @@
+// Package common provides common helper functionality for Hugo.
+package common
diff --git a/common/hashing/hashing.go b/common/hashing/hashing.go
new file mode 100644
index 00000000000..ad28bbce226
--- /dev/null
+++ b/common/hashing/hashing.go
@@ -0,0 +1,225 @@
+// 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/common/hugio"
+ "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
+}
+
+type Hasher interface {
+ io.StringWriter
+ io.Writer
+ io.ReaderFrom
+ Sum64() uint64
+}
+
+type HashCloser interface {
+ Hasher
+ io.Closer
+}
+
+// XxHasher returns a Hasher that uses xxHash.
+// Remember to call Close when done.
+func XxHasher() HashCloser {
+ h := getXxHashReadFrom()
+ return struct {
+ Hasher
+ io.Closer
+ }{
+ Hasher: h,
+ Closer: hugio.CloserFunc(func() error {
+ putXxHashReadFrom(h)
+ return nil
+ }),
+ }
+}
+
+// XxHashFromReaderHexEncoded calculates the xxHash for the given reader
+// and returns the hash as a hex encoded string.
+func XxHashFromReaderHexEncoded(r io.Reader) (string, error) {
+ h := getXxHashReadFrom()
+ defer putXxHashReadFrom(h)
+ _, err := io.Copy(h, r)
+ if err != nil {
+ return "", err
+ }
+ hash := h.Sum(nil)
+ return hex.EncodeToString(hash), nil
+}
+
+// XXHashFromString calculates the xxHash for the given string.
+func XXHashFromString(s string) (uint64, error) {
+ h := xxhash.New()
+ h.WriteString(s)
+ return h.Sum64(), nil
+}
+
+// XxHashFromStringHexEncoded calculates the xxHash for the given strings
+// and returns the hash as a hex encoded string.
+func XxHashFromStringHexEncoded(s ...string) string {
+ h := xxhash.New()
+ for _, f := range s {
+ 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)
+}
+
+// HashStringHex returns a hash from the given elements as a hex encoded string.
+// See HashString for more information.
+func HashStringHex(vs ...any) string {
+ hash := HashUint64(vs...)
+ return strconv.FormatUint(hash, 16)
+}
+
+var hashOptsPool = sync.Pool{
+ New: func() any {
+ return &hashstructure.HashOptions{
+ 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
+ }
+
+ hash, err := Hash(o)
+ if err != nil {
+ panic(err)
+ }
+ return hash
+}
+
+// Hash returns a hash from vs.
+func Hash(vs ...any) (uint64, error) {
+ hashOpts := getHashOpts()
+ defer putHashOpts(hashOpts)
+ var v any = vs
+ if len(vs) == 1 {
+ v = vs[0]
+ }
+ return hashstructure.Hash(v, hashOpts)
+}
+
+type keyer interface {
+ Key() string
+}
+
+// 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)
+}
diff --git a/common/hashing/hashing_test.go b/common/hashing/hashing_test.go
new file mode 100644
index 00000000000..de2fed8f0e9
--- /dev/null
+++ b/common/hashing/hashing_test.go
@@ -0,0 +1,152 @@
+// 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 := range 10 {
+ wg.Go(func() {
+ for j := range 100 {
+ 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")
+
+ for b.Loop() {
+ XXHashFromReader(r)
+ r.Seek(0, 0)
+ }
+}
+
+func BenchmarkXXHashFromString(b *testing.B) {
+ s := "Hello World"
+
+ for b.Loop() {
+ XXHashFromString(s)
+ }
+}
+
+func BenchmarkXXHashFromStringHexEncoded(b *testing.B) {
+ s := "The quick brown fox jumps over the lazy dog"
+
+ for b.Loop() {
+ 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 b.Loop() {
+ HashString(test)
+ }
+ })
+ }
+}
+
+func BenchmarkHashMap(b *testing.B) {
+ m := map[string]any{}
+ for i := range 1000 {
+ m[fmt.Sprintf("key%d", i)] = i
+ }
+
+ for b.Loop() {
+ HashString(m)
+ }
+}
diff --git a/common/hdebug/debug.go b/common/hdebug/debug.go
new file mode 100644
index 00000000000..a9773676df3
--- /dev/null
+++ b/common/hdebug/debug.go
@@ -0,0 +1,61 @@
+// Copyright 2025 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hdebug
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/gohugoio/hugo/common/types"
+ "github.com/gohugoio/hugo/htesting"
+)
+
+// Printf is a debug print function that should be removed before committing code to the repository.
+func Printf(format string, args ...any) {
+ panicIfRealCI()
+ if len(args) == 1 && !strings.Contains(format, "%") {
+ format = format + ": %v"
+ }
+ if !strings.HasSuffix(format, "\n") {
+ format = format + "\n"
+ }
+ fmt.Printf(format, args...)
+}
+
+func AssertNotNil(a ...any) {
+ panicIfRealCI()
+ for _, v := range a {
+ if types.IsNil(v) {
+ panic("hdebug.AssertNotNil: value is nil")
+ }
+ }
+}
+
+func Panicf(format string, args ...any) {
+ panicIfRealCI()
+ // fmt.Println(stack())
+ if len(args) == 1 && !strings.Contains(format, "%") {
+ format = format + ": %v"
+ }
+ if !strings.HasSuffix(format, "\n") {
+ format = format + "\n"
+ }
+ panic(fmt.Sprintf(format, args...))
+}
+
+func panicIfRealCI() {
+ if htesting.IsRealCI() {
+ panic("This debug statement should be removed before committing code!")
+ }
+}
diff --git a/common/herrors/error_locator.go b/common/herrors/error_locator.go
index 2c0d215b163..acaebb4bc3b 100644
--- a/common/herrors/error_locator.go
+++ b/common/herrors/error_locator.go
@@ -1,4 +1,4 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
+// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -16,13 +16,10 @@ package herrors
import (
"io"
- "io/ioutil"
"path/filepath"
"strings"
"github.com/gohugoio/hugo/common/text"
-
- "github.com/spf13/afero"
)
// LineMatcher contains the elements used to match an error to a line
@@ -36,19 +33,47 @@ type LineMatcher struct {
}
// LineMatcherFn is used to match a line with an error.
-type LineMatcherFn func(m LineMatcher) bool
+// It returns the column number or 0 if the line was found, but column could not be determined. Returns -1 if no line match.
+type LineMatcherFn func(m LineMatcher) int
// SimpleLineMatcher simply matches by line number.
-var SimpleLineMatcher = func(m LineMatcher) bool {
- return m.Position.LineNumber == m.LineNumber
+var SimpleLineMatcher = func(m LineMatcher) int {
+ if m.Position.LineNumber == m.LineNumber {
+ // We found the line, but don't know the column.
+ return 0
+ }
+ return -1
+}
+
+// NopLineMatcher is a matcher that always returns 1.
+// This will effectively give line 1, column 1.
+var NopLineMatcher = func(m LineMatcher) int {
+ return 1
+}
+
+// OffsetMatcher is a line matcher that matches by offset.
+var OffsetMatcher = func(m LineMatcher) int {
+ if m.Offset+len(m.Line) >= m.Position.Offset {
+ // We found the line, but return 0 to signal that we want to determine
+ // the column from the error.
+ return 0
+ }
+ return -1
}
-var _ text.Positioner = ErrorContext{}
+// ContainsMatcher is a line matcher that matches by line content.
+func ContainsMatcher(text string) func(m LineMatcher) int {
+ return func(m LineMatcher) int {
+ if idx := strings.Index(m.Line, text); idx != -1 {
+ return idx + 1
+ }
+ return -1
+ }
+}
// ErrorContext contains contextual information about an error. This will
// typically be the lines surrounding some problem in a file.
type ErrorContext struct {
-
// If a match will contain the matched line and up to 2 lines before and after.
// Will be empty if no match.
Lines []string
@@ -56,125 +81,15 @@ type ErrorContext struct {
// The position of the error in the Lines above. 0 based.
LinesPos int
- position text.Position
+ // The position of the content in the file. Note that this may be different from the error's position set
+ // in FileError.
+ Position text.Position
// The lexer to use for syntax highlighting.
// https://gohugo.io/content-management/syntax-highlighting/#list-of-chroma-highlighting-languages
ChromaLexer string
}
-// Position returns the text position of this error.
-func (e ErrorContext) Position() text.Position {
- return e.position
-}
-
-var _ causer = (*ErrorWithFileContext)(nil)
-
-// ErrorWithFileContext is an error with some additional file context related
-// to that error.
-type ErrorWithFileContext struct {
- cause error
- ErrorContext
-}
-
-func (e *ErrorWithFileContext) Error() string {
- pos := e.Position()
- if pos.IsValid() {
- return pos.String() + ": " + e.cause.Error()
- }
- return e.cause.Error()
-}
-
-func (e *ErrorWithFileContext) Cause() error {
- return e.cause
-}
-
-// WithFileContextForFile will try to add a file context with lines matching the given matcher.
-// If no match could be found, the original error is returned with false as the second return value.
-func WithFileContextForFile(e error, realFilename, filename string, fs afero.Fs, matcher LineMatcherFn) (error, bool) {
- f, err := fs.Open(filename)
- if err != nil {
- return e, false
- }
- defer f.Close()
- return WithFileContext(e, realFilename, f, matcher)
-}
-
-// WithFileContextForFileDefault tries to add file context using the default line matcher.
-func WithFileContextForFileDefault(err error, filename string, fs afero.Fs) error {
- err, _ = WithFileContextForFile(
- err,
- filename,
- filename,
- fs,
- SimpleLineMatcher)
- return err
-}
-
-// WithFileContextForFile will try to add a file context with lines matching the given matcher.
-// If no match could be found, the original error is returned with false as the second return value.
-func WithFileContext(e error, realFilename string, r io.Reader, matcher LineMatcherFn) (error, bool) {
- if e == nil {
- panic("error missing")
- }
- le := UnwrapFileError(e)
-
- if le == nil {
- var ok bool
- if le, ok = ToFileError("", e).(FileError); !ok {
- return e, false
- }
- }
-
- var errCtx ErrorContext
-
- posle := le.Position()
-
- if posle.Offset != -1 {
- errCtx = locateError(r, le, func(m LineMatcher) bool {
- if posle.Offset >= m.Offset && posle.Offset < m.Offset+len(m.Line) {
- lno := posle.LineNumber - m.Position.LineNumber + m.LineNumber
- m.Position = text.Position{LineNumber: lno}
- }
- return matcher(m)
- })
- } else {
- errCtx = locateError(r, le, matcher)
- }
-
- pos := &errCtx.position
-
- if pos.LineNumber == -1 {
- return e, false
- }
-
- pos.Filename = realFilename
-
- if le.Type() != "" {
- errCtx.ChromaLexer = chromaLexerFromType(le.Type())
- } else {
- errCtx.ChromaLexer = chromaLexerFromFilename(realFilename)
- }
-
- return &ErrorWithFileContext{cause: e, ErrorContext: errCtx}, true
-}
-
-// UnwrapErrorWithFileContext tries to unwrap an ErrorWithFileContext from err.
-// It returns nil if this is not possible.
-func UnwrapErrorWithFileContext(err error) *ErrorWithFileContext {
- for err != nil {
- switch v := err.(type) {
- case *ErrorWithFileContext:
- return v
- case causer:
- err = v.Cause()
- default:
- return nil
- }
- }
- return nil
-}
-
func chromaLexerFromType(fileType string) string {
switch fileType {
case "html", "htm":
@@ -196,31 +111,24 @@ func chromaLexerFromFilename(filename string) string {
return chromaLexerFromType(ext)
}
-func locateErrorInString(src string, matcher LineMatcherFn) ErrorContext {
+func locateErrorInString(src string, matcher LineMatcherFn) *ErrorContext {
return locateError(strings.NewReader(src), &fileError{}, matcher)
}
-func locateError(r io.Reader, le FileError, matches LineMatcherFn) ErrorContext {
+func locateError(r io.Reader, le FileError, matches LineMatcherFn) *ErrorContext {
if le == nil {
panic("must provide an error")
}
- errCtx := ErrorContext{position: text.Position{LineNumber: -1, ColumnNumber: 1, Offset: -1}, LinesPos: -1}
+ ectx := &ErrorContext{LinesPos: -1, Position: text.Position{Offset: -1}}
- b, err := ioutil.ReadAll(r)
+ b, err := io.ReadAll(r)
if err != nil {
- return errCtx
+ return ectx
}
- pos := &errCtx.position
- lepos := le.Position()
-
lines := strings.Split(string(b), "\n")
- if lepos.ColumnNumber >= 0 {
- pos.ColumnNumber = lepos.ColumnNumber
- }
-
lineNo := 0
posBytes := 0
@@ -233,34 +141,30 @@ func locateError(r io.Reader, le FileError, matches LineMatcherFn) ErrorContext
Offset: posBytes,
Line: line,
}
- if errCtx.LinesPos == -1 && matches(m) {
- pos.LineNumber = lineNo
+ v := matches(m)
+ if ectx.LinesPos == -1 && v != -1 {
+ ectx.Position.LineNumber = lineNo
+ ectx.Position.ColumnNumber = v
break
}
posBytes += len(line)
}
- if pos.LineNumber != -1 {
- low := pos.LineNumber - 3
- if low < 0 {
- low = 0
- }
+ if ectx.Position.LineNumber > 0 {
+ low := max(ectx.Position.LineNumber-3, 0)
- if pos.LineNumber > 2 {
- errCtx.LinesPos = 2
+ if ectx.Position.LineNumber > 2 {
+ ectx.LinesPos = 2
} else {
- errCtx.LinesPos = pos.LineNumber - 1
+ ectx.LinesPos = ectx.Position.LineNumber - 1
}
- high := pos.LineNumber + 2
- if high > len(lines) {
- high = len(lines)
- }
+ high := min(ectx.Position.LineNumber+2, len(lines))
- errCtx.Lines = lines[low:high]
+ ectx.Lines = lines[low:high]
}
- return errCtx
+ return ectx
}
diff --git a/common/herrors/error_locator_test.go b/common/herrors/error_locator_test.go
index 0cd5fb2d74a..62f15213d30 100644
--- a/common/herrors/error_locator_test.go
+++ b/common/herrors/error_locator_test.go
@@ -1,4 +1,4 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
+// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -24,8 +24,11 @@ import (
func TestErrorLocator(t *testing.T) {
c := qt.New(t)
- lineMatcher := func(m LineMatcher) bool {
- return strings.Contains(m.Line, "THEONE")
+ lineMatcher := func(m LineMatcher) int {
+ if strings.Contains(m.Line, "THEONE") {
+ return 1
+ }
+ return -1
}
lines := `LINE 1
@@ -39,35 +42,41 @@ LINE 8
`
location := locateErrorInString(lines, lineMatcher)
+ pos := location.Position
c.Assert(location.Lines, qt.DeepEquals, []string{"LINE 3", "LINE 4", "This is THEONE", "LINE 6", "LINE 7"})
- pos := location.Position()
c.Assert(pos.LineNumber, qt.Equals, 5)
c.Assert(location.LinesPos, qt.Equals, 2)
- c.Assert(locateErrorInString(`This is THEONE`, lineMatcher).Lines, qt.DeepEquals, []string{"This is THEONE"})
+ locate := func(s string, m LineMatcherFn) *ErrorContext {
+ ctx := locateErrorInString(s, m)
+ return ctx
+ }
+
+ c.Assert(locate(`This is THEONE`, lineMatcher).Lines, qt.DeepEquals, []string{"This is THEONE"})
location = locateErrorInString(`L1
This is THEONE
L2
`, lineMatcher)
- c.Assert(location.Position().LineNumber, qt.Equals, 2)
+ pos = location.Position
+ c.Assert(pos.LineNumber, qt.Equals, 2)
c.Assert(location.LinesPos, qt.Equals, 1)
c.Assert(location.Lines, qt.DeepEquals, []string{"L1", "This is THEONE", "L2", ""})
- location = locateErrorInString(`This is THEONE
+ location = locate(`This is THEONE
L2
`, lineMatcher)
c.Assert(location.LinesPos, qt.Equals, 0)
c.Assert(location.Lines, qt.DeepEquals, []string{"This is THEONE", "L2", ""})
- location = locateErrorInString(`L1
+ location = locate(`L1
This THEONE
`, lineMatcher)
c.Assert(location.Lines, qt.DeepEquals, []string{"L1", "This THEONE", ""})
c.Assert(location.LinesPos, qt.Equals, 1)
- location = locateErrorInString(`L1
+ location = locate(`L1
L2
This THEONE
`, lineMatcher)
@@ -75,12 +84,16 @@ This THEONE
c.Assert(location.LinesPos, qt.Equals, 2)
location = locateErrorInString("NO MATCH", lineMatcher)
- c.Assert(location.Position().LineNumber, qt.Equals, -1)
+ pos = location.Position
+ c.Assert(pos.LineNumber, qt.Equals, 0)
c.Assert(location.LinesPos, qt.Equals, -1)
c.Assert(len(location.Lines), qt.Equals, 0)
- lineMatcher = func(m LineMatcher) bool {
- return m.LineNumber == 6
+ lineMatcher = func(m LineMatcher) int {
+ if m.LineNumber == 6 {
+ return 1
+ }
+ return -1
}
location = locateErrorInString(`A
@@ -93,14 +106,18 @@ G
H
I
J`, lineMatcher)
+ pos = location.Position
c.Assert(location.Lines, qt.DeepEquals, []string{"D", "E", "F", "G", "H"})
- c.Assert(location.Position().LineNumber, qt.Equals, 6)
+ c.Assert(pos.LineNumber, qt.Equals, 6)
c.Assert(location.LinesPos, qt.Equals, 2)
// Test match EOF
- lineMatcher = func(m LineMatcher) bool {
- return m.LineNumber == 4
+ lineMatcher = func(m LineMatcher) int {
+ if m.LineNumber == 4 {
+ return 1
+ }
+ return -1
}
location = locateErrorInString(`A
@@ -108,12 +125,17 @@ B
C
`, lineMatcher)
+ pos = location.Position
+
c.Assert(location.Lines, qt.DeepEquals, []string{"B", "C", ""})
- c.Assert(location.Position().LineNumber, qt.Equals, 4)
+ c.Assert(pos.LineNumber, qt.Equals, 4)
c.Assert(location.LinesPos, qt.Equals, 2)
- offsetMatcher := func(m LineMatcher) bool {
- return m.Offset == 1
+ offsetMatcher := func(m LineMatcher) int {
+ if m.Offset == 1 {
+ return 1
+ }
+ return -1
}
location = locateErrorInString(`A
@@ -122,7 +144,9 @@ C
D
E`, offsetMatcher)
+ pos = location.Position
+
c.Assert(location.Lines, qt.DeepEquals, []string{"A", "B", "C", "D"})
- c.Assert(location.Position().LineNumber, qt.Equals, 2)
+ c.Assert(pos.LineNumber, qt.Equals, 2)
c.Assert(location.LinesPos, qt.Equals, 1)
}
diff --git a/common/herrors/errors.go b/common/herrors/errors.go
index 00aed1eb68c..5e223f42f53 100644
--- a/common/herrors/errors.go
+++ b/common/herrors/errors.go
@@ -1,4 +1,4 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
+// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,79 +15,94 @@
package herrors
import (
- "bytes"
"errors"
"fmt"
- "io"
"os"
+ "regexp"
"runtime"
- "runtime/debug"
- "strconv"
-
- _errors "github.com/pkg/errors"
+ "strings"
+ "time"
)
-// As defined in https://godoc.org/github.com/pkg/errors
-type causer interface {
- Cause() error
+// ErrorSender is a, typically, non-blocking error handler.
+type ErrorSender interface {
+ SendError(err error)
}
-type stackTracer interface {
- StackTrace() _errors.StackTrace
+// Recover is a helper function that can be used to capture panics.
+// Put this at the top of a method/function that crashes in a template:
+//
+// defer herrors.Recover()
+func Recover(args ...any) {
+ if r := recover(); r != nil {
+ fmt.Println("ERR:", r)
+ buf := make([]byte, 64<<10)
+ buf = buf[:runtime.Stack(buf, false)]
+ args = append(args, "stacktrace from panic: \n"+string(buf), "\n")
+ fmt.Println(args...)
+ }
}
-// PrintStackTraceFromErr prints the error's stack trace to stdoud.
-func PrintStackTraceFromErr(err error) {
- FprintStackTraceFromErr(os.Stdout, err)
+// IsTimeoutError returns true if the given error is or contains a TimeoutError.
+func IsTimeoutError(err error) bool {
+ return errors.Is(err, &TimeoutError{})
}
-// FprintStackTraceFromErr prints the error's stack trace to w.
-func FprintStackTraceFromErr(w io.Writer, err error) {
- if err, ok := err.(stackTracer); ok {
- for _, f := range err.StackTrace() {
- fmt.Fprintf(w, "%+s:%d\n", f, f)
- }
- }
+type TimeoutError struct {
+ Duration time.Duration
}
-// PrintStackTrace prints the current stacktrace to w.
-func PrintStackTrace(w io.Writer) {
- buf := make([]byte, 1<<16)
- runtime.Stack(buf, true)
- fmt.Fprintf(w, "%s", buf)
+func (e *TimeoutError) Error() string {
+ return fmt.Sprintf("timeout after %s", e.Duration)
}
-// ErrorSender is a, typically, non-blocking error handler.
-type ErrorSender interface {
- SendError(err error)
+func (e *TimeoutError) Is(target error) bool {
+ _, ok := target.(*TimeoutError)
+ return ok
}
-// Recover is a helper function that can be used to capture panics.
-// Put this at the top of a method/function that crashes in a template:
-// defer herrors.Recover()
-func Recover(args ...interface{}) {
- if r := recover(); r != nil {
- fmt.Println("ERR:", r)
- args = append(args, "stacktrace from panic: \n"+string(debug.Stack()), "\n")
- fmt.Println(args...)
- }
+// 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
}
-// Get the current goroutine id. Used only for debugging.
-func GetGID() uint64 {
- b := make([]byte, 64)
- b = b[:runtime.Stack(b, false)]
- b = bytes.TrimPrefix(b, []byte("goroutine "))
- b = b[:bytes.IndexByte(b, ' ')]
- n, _ := strconv.ParseUint(string(b), 10, 64)
- return n
+// IsFeatureNotAvailableError returns true if the given error is or contains a FeatureNotAvailableError.
+func IsFeatureNotAvailableError(err error) bool {
+ return errors.Is(err, &FeatureNotAvailableError{})
}
// ErrFeatureNotAvailable denotes that a feature is unavailable.
//
// We will, at least to begin with, make some Hugo features (SCSS with libsass) optional,
// and this error is used to signal those situations.
-var ErrFeatureNotAvailable = errors.New("this feature is not available in your current Hugo version, see https://goo.gl/YMrWcn for more information")
+var ErrFeatureNotAvailable = &FeatureNotAvailableError{Cause: errors.New("this feature is not available in your current Hugo version, see https://goo.gl/YMrWcn for more information")}
+
+// FeatureNotAvailableError is an error type used to signal that a feature is not available.
+type FeatureNotAvailableError struct {
+ Cause error
+}
+
+func (e *FeatureNotAvailableError) Unwrap() error {
+ return e.Cause
+}
+
+func (e *FeatureNotAvailableError) Error() string {
+ return e.Cause.Error()
+}
+
+func (e *FeatureNotAvailableError) Is(target error) bool {
+ _, ok := target.(*FeatureNotAvailableError)
+ return ok
+}
// Must panics if err != nil.
func Must(err error) {
@@ -95,3 +110,71 @@ func Must(err error) {
panic(err)
}
}
+
+// IsNotExist returns true if the error is a file not found error.
+// Unlike os.IsNotExist, this also considers wrapped errors.
+func IsNotExist(err error) bool {
+ if os.IsNotExist(err) {
+ return true
+ }
+
+ // os.IsNotExist does not consider wrapped errors.
+ if os.IsNotExist(errors.Unwrap(err)) {
+ return true
+ }
+
+ 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) {
+ 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 ""
+ }
+ 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)
+}
diff --git a/common/herrors/errors_test.go b/common/herrors/errors_test.go
new file mode 100644
index 00000000000..2f53a1e89d6
--- /dev/null
+++ b/common/herrors/errors_test.go
@@ -0,0 +1,45 @@
+// 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 herrors
+
+import (
+ "errors"
+ "fmt"
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+ "github.com/spf13/afero"
+)
+
+func TestIsNotExist(t *testing.T) {
+ c := qt.New(t)
+
+ c.Assert(IsNotExist(afero.ErrFileNotFound), qt.Equals, true)
+ c.Assert(IsNotExist(afero.ErrFileExists), qt.Equals, false)
+ c.Assert(IsNotExist(afero.ErrDestinationExists), qt.Equals, false)
+ c.Assert(IsNotExist(nil), qt.Equals, false)
+
+ c.Assert(IsNotExist(fmt.Errorf("foo")), qt.Equals, false)
+
+ // os.IsNotExist returns false for wrapped errors.
+ c.Assert(IsNotExist(fmt.Errorf("foo: %w", afero.ErrFileNotFound)), qt.Equals, true)
+}
+
+func TestIsFeatureNotAvailableError(t *testing.T) {
+ c := qt.New(t)
+
+ c.Assert(IsFeatureNotAvailableError(ErrFeatureNotAvailable), qt.Equals, true)
+ c.Assert(IsFeatureNotAvailableError(&FeatureNotAvailableError{}), qt.Equals, true)
+ c.Assert(IsFeatureNotAvailableError(errors.New("asdf")), qt.Equals, false)
+}
diff --git a/common/herrors/file_error.go b/common/herrors/file_error.go
index 1cb31ff9f25..62690dec99f 100644
--- a/common/herrors/file_error.go
+++ b/common/herrors/file_error.go
@@ -1,11 +1,11 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
+// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// 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
+// Unless required by applicable lfmtaw 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
@@ -15,111 +15,386 @@ package herrors
import (
"encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "path/filepath"
+ "github.com/bep/godartsass/v2"
+ "github.com/bep/golibsass/libsass/libsasserrors"
+ "github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/text"
-
- "github.com/pkg/errors"
+ "github.com/pelletier/go-toml/v2"
+ "github.com/spf13/afero"
+ "github.com/tdewolff/parse/v2"
)
-var _ causer = (*fileError)(nil)
-
// FileError represents an error when handling a file: Parsing a config file,
// execute a template etc.
type FileError interface {
error
+ // ErrorContext holds some context information about the error.
+ ErrorContext() *ErrorContext
+
text.Positioner
- // A string identifying the type of file, e.g. JSON, TOML, markdown etc.
- Type() string
+ // UpdatePosition updates the position of the error.
+ UpdatePosition(pos text.Position) FileError
+
+ // UpdateContent updates the error with a new ErrorContext from the content of the file.
+ UpdateContent(r io.Reader, linematcher LineMatcherFn) FileError
+
+ // SetFilename sets the filename of the error.
+ SetFilename(filename string) FileError
+}
+
+// Unwrapper can unwrap errors created with fmt.Errorf.
+type Unwrapper interface {
+ Unwrap() error
+}
+
+var (
+ _ FileError = (*fileError)(nil)
+ _ Unwrapper = (*fileError)(nil)
+)
+
+func (fe *fileError) SetFilename(filename string) FileError {
+ fe.position.Filename = filename
+ return fe
}
-var _ FileError = (*fileError)(nil)
+func (fe *fileError) UpdatePosition(pos text.Position) FileError {
+ oldFilename := fe.Position().Filename
+ if pos.Filename != "" && fe.fileType == "" {
+ _, fe.fileType = paths.FileAndExtNoDelimiter(filepath.Clean(pos.Filename))
+ }
+ if pos.Filename == "" {
+ pos.Filename = oldFilename
+ }
+ fe.position = pos
+ return fe
+}
+
+func (fe *fileError) UpdateContent(r io.Reader, linematcher LineMatcherFn) FileError {
+ if linematcher == nil {
+ linematcher = SimpleLineMatcher
+ }
+
+ var (
+ posle = fe.position
+ ectx *ErrorContext
+ )
+
+ if posle.LineNumber <= 1 && posle.Offset > 0 {
+ // Try to locate the line number from the content if offset is set.
+ ectx = locateError(r, fe, func(m LineMatcher) int {
+ if posle.Offset >= m.Offset && posle.Offset < m.Offset+len(m.Line) {
+ lno := posle.LineNumber - m.Position.LineNumber + m.LineNumber
+ m.Position = text.Position{LineNumber: lno}
+ return linematcher(m)
+ }
+ return -1
+ })
+ } else {
+ ectx = locateError(r, fe, linematcher)
+ }
+
+ if ectx.ChromaLexer == "" {
+ if fe.fileType != "" {
+ ectx.ChromaLexer = chromaLexerFromType(fe.fileType)
+ } else {
+ ectx.ChromaLexer = chromaLexerFromFilename(fe.Position().Filename)
+ }
+ }
+
+ fe.errorContext = ectx
+
+ if ectx.Position.LineNumber > 0 && ectx.Position.LineNumber > fe.position.LineNumber {
+ fe.position.LineNumber = ectx.Position.LineNumber
+ }
+
+ if ectx.Position.ColumnNumber > 0 && ectx.Position.ColumnNumber > fe.position.ColumnNumber {
+ fe.position.ColumnNumber = ectx.Position.ColumnNumber
+ }
+
+ return fe
+}
type fileError struct {
- position text.Position
+ position text.Position
+ errorContext *ErrorContext
fileType string
cause error
}
+func (e *fileError) ErrorContext() *ErrorContext {
+ return e.errorContext
+}
+
// Position returns the text position of this error.
func (e fileError) Position() text.Position {
return e.position
}
-func (e *fileError) Type() string {
- return e.fileType
+func (e *fileError) Error() string {
+ return fmt.Sprintf("%s: %s", e.position, e.causeString())
}
-func (e *fileError) Error() string {
+func (e *fileError) causeString() string {
if e.cause == nil {
return ""
}
- return e.cause.Error()
+ switch v := e.cause.(type) {
+ // Avoid repeating the file info in the error message.
+ case godartsass.SassError:
+ return v.Message
+ case libsasserrors.Error:
+ return v.Message
+ default:
+ return v.Error()
+ }
}
-func (f *fileError) Cause() error {
- return f.cause
+func (e *fileError) Unwrap() error {
+ return e.cause
}
-// NewFileError creates a new FileError.
-func NewFileError(fileType string, offset, lineNumber, columnNumber int, err error) FileError {
- pos := text.Position{Offset: offset, LineNumber: lineNumber, ColumnNumber: columnNumber}
+// NewFileError creates a new FileError that wraps err.
+// It will try to extract the filename and line number from err.
+func NewFileError(err error) FileError {
+ // Filetype is used to determine the Chroma lexer to use.
+ fileType, pos := extractFileTypePos(err)
return &fileError{cause: err, fileType: fileType, position: pos}
}
-// UnwrapFileError tries to unwrap a FileError from err.
-// It returns nil if this is not possible.
-func UnwrapFileError(err error) FileError {
+// NewFileErrorFromName creates a new FileError that wraps err.
+// The value for name should identify the file, the best
+// being the full filename to the file on disk.
+func NewFileErrorFromName(err error, name string) FileError {
+ // Filetype is used to determine the Chroma lexer to use.
+ fileType, pos := extractFileTypePos(err)
+ pos.Filename = name
+
+ if fileType == "" {
+ _, fileType = paths.FileAndExtNoDelimiter(filepath.Clean(name))
+ }
+
+ return &fileError{cause: err, fileType: fileType, position: pos}
+}
+
+// NewFileErrorFromPos will use the filename and line number from pos to create a new FileError, wrapping err.
+func NewFileErrorFromPos(err error, pos text.Position) FileError {
+ // Filetype is used to determine the Chroma lexer to use.
+ fileType, _ := extractFileTypePos(err)
+ if fileType == "" {
+ _, fileType = paths.FileAndExtNoDelimiter(filepath.Clean(pos.Filename))
+ }
+ return &fileError{cause: err, fileType: fileType, position: pos}
+}
+
+func NewFileErrorFromFileInErr(err error, fs afero.Fs, linematcher LineMatcherFn) FileError {
+ fe := NewFileError(err)
+ pos := fe.Position()
+ if pos.Filename == "" {
+ return fe
+ }
+
+ f, realFilename, err2 := openFile(pos.Filename, fs)
+ if err2 != nil {
+ return fe
+ }
+
+ pos.Filename = realFilename
+ defer f.Close()
+ return fe.UpdateContent(f, linematcher)
+}
+
+func NewFileErrorFromFileInPos(err error, pos text.Position, fs afero.Fs, linematcher LineMatcherFn) FileError {
+ if err == nil {
+ panic("err is nil")
+ }
+ f, realFilename, err2 := openFile(pos.Filename, fs)
+ if err2 != nil {
+ return NewFileErrorFromPos(err, pos)
+ }
+ pos.Filename = realFilename
+ defer f.Close()
+ return NewFileErrorFromPos(err, pos).UpdateContent(f, linematcher)
+}
+
+// NewFileErrorFromFile is a convenience method to create a new FileError from a file.
+func NewFileErrorFromFile(err error, filename string, fs afero.Fs, linematcher LineMatcherFn) FileError {
+ if err == nil {
+ panic("err is nil")
+ }
+ f, realFilename, err2 := openFile(filename, fs)
+ if err2 != nil {
+ return NewFileErrorFromName(err, realFilename)
+ }
+ defer f.Close()
+ fe := NewFileErrorFromName(err, realFilename)
+ fe = fe.UpdateContent(f, linematcher)
+ return fe
+}
+
+func openFile(filename string, fs afero.Fs) (afero.File, string, error) {
+ realFilename := filename
+
+ // We want the most specific filename possible in the error message.
+ fi, err2 := fs.Stat(filename)
+ if err2 == nil {
+ if s, ok := fi.(interface {
+ Filename() string
+ }); ok {
+ realFilename = s.Filename()
+ }
+ }
+
+ f, err2 := fs.Open(filename)
+ if err2 != nil {
+ return nil, realFilename, err2
+ }
+
+ return f, realFilename, nil
+}
+
+// Cause returns the underlying error, that is,
+// it unwraps errors until it finds one that does not implement
+// the Unwrap method.
+// For a shallow variant, see Unwrap.
+func Cause(err error) error {
+ type unwrapper interface {
+ Unwrap() error
+ }
+
for err != nil {
- switch v := err.(type) {
- case FileError:
- return v
- case causer:
- err = v.Cause()
- default:
- return nil
+ cause, ok := err.(unwrapper)
+ if !ok {
+ break
}
+ err = cause.Unwrap()
}
- return nil
+ return err
}
-// ToFileErrorWithOffset will return a new FileError with a line number
-// with the given offset from the original.
-func ToFileErrorWithOffset(fe FileError, offset int) FileError {
- pos := fe.Position()
- return ToFileErrorWithLineNumber(fe, pos.LineNumber+offset)
+// Unwrap returns the underlying error or itself if it does not implement Unwrap.
+func Unwrap(err error) error {
+ if u := errors.Unwrap(err); u != nil {
+ return u
+ }
+ return err
}
-// ToFileErrorWithOffset will return a new FileError with the given line number.
-func ToFileErrorWithLineNumber(fe FileError, lineNumber int) FileError {
- pos := fe.Position()
- pos.LineNumber = lineNumber
- return &fileError{cause: fe, fileType: fe.Type(), position: pos}
-}
-
-// ToFileError will convert the given error to an error supporting
-// the FileError interface.
-func ToFileError(fileType string, err error) FileError {
- for _, handle := range lineNumberExtractors {
- lno, col := handle(err)
- offset, typ := extractOffsetAndType(err)
- if fileType == "" {
- fileType = typ
+// UnwrapFileErrors returns all FileError contained in err.
+func UnwrapFileErrors(err error) []FileError {
+ if err == nil {
+ return nil
+ }
+ errs := Errors(err)
+ var fileErrors []FileError
+ for _, e := range errs {
+ if v, ok := e.(FileError); ok {
+ fileErrors = append(fileErrors, v)
+ }
+ fileErrors = append(fileErrors, UnwrapFileErrors(errors.Unwrap(e))...)
+ }
+ return fileErrors
+}
+
+// UnwrapFileErrorsWithErrorContext tries to unwrap all FileError in err that has an ErrorContext.
+func UnwrapFileErrorsWithErrorContext(err error) []FileError {
+ errs := UnwrapFileErrors(err)
+ var n int
+ for _, e := range errs {
+ if e.ErrorContext() != nil {
+ errs[n] = e
+ n++
}
+ }
+ return errs[:n]
+}
+
+// Errors returns the list of errors contained in err.
+func Errors(err error) []error {
+ if err == nil {
+ return nil
+ }
+
+ type unwrapper interface {
+ Unwrap() []error
+ }
+ if u, ok := err.(unwrapper); ok {
+ return u.Unwrap()
+ }
+ return []error{err}
+}
+
+func extractFileTypePos(err error) (string, text.Position) {
+ err = Unwrap(err)
+
+ var fileType string
+
+ // LibSass, DartSass
+ if pos := extractPosition(err); pos.LineNumber > 0 || pos.Offset > 0 {
+ _, fileType = paths.FileAndExtNoDelimiter(pos.Filename)
+ return fileType, pos
+ }
+
+ // Default to line 1 col 1 if we don't find any better.
+ pos := text.Position{
+ Offset: -1,
+ LineNumber: 1,
+ ColumnNumber: 1,
+ }
+
+ // JSON errors.
+ offset, typ := extractOffsetAndType(err)
+ if fileType == "" {
+ fileType = typ
+ }
+
+ if offset >= 0 {
+ pos.Offset = offset
+ }
+
+ // The error type from the minifier contains line number and column number.
+ if line, col := extractLineNumberAndColumnNumber(err); line >= 0 {
+ pos.LineNumber = line
+ pos.ColumnNumber = col
+ return fileType, pos
+ }
+
+ // Look in the error message for the line number.
+ if lno, col := commonLineNumberExtractor(err); lno > 0 {
+ pos.ColumnNumber = col
+ pos.LineNumber = lno
+ }
+
+ if fileType == "" && pos.Filename != "" {
+ _, fileType = paths.FileAndExtNoDelimiter(pos.Filename)
+ }
+
+ return fileType, pos
+}
- if lno > 0 || offset != -1 {
- return NewFileError(fileType, offset, lno, col, err)
+// UnwrapFileError tries to unwrap a FileError from err.
+// It returns nil if this is not possible.
+func UnwrapFileError(err error) FileError {
+ for err != nil {
+ switch v := err.(type) {
+ case FileError:
+ return v
+ default:
+ err = errors.Unwrap(err)
}
}
- // Fall back to the pointing to line number 1.
- return NewFileError(fileType, -1, 1, 1, err)
+ return nil
}
func extractOffsetAndType(e error) (int, string) {
- e = errors.Cause(e)
switch v := e.(type) {
case *json.UnmarshalTypeError:
return int(v.Offset), "json"
@@ -129,3 +404,46 @@ func extractOffsetAndType(e error) (int, string) {
return -1, ""
}
}
+
+func extractLineNumberAndColumnNumber(e error) (int, int) {
+ switch v := e.(type) {
+ case *parse.Error:
+ return v.Line, v.Column
+ case *toml.DecodeError:
+ return v.Position()
+
+ }
+
+ return -1, -1
+}
+
+func extractPosition(e error) (pos text.Position) {
+ switch v := e.(type) {
+ case godartsass.SassError:
+ span := v.Span
+ start := span.Start
+ filename, _ := paths.UrlStringToFilename(span.Url)
+ pos.Filename = filename
+ pos.Offset = start.Offset
+ pos.ColumnNumber = start.Column
+ case libsasserrors.Error:
+ pos.Filename = v.File
+ pos.LineNumber = v.Line
+ pos.ColumnNumber = v.Column
+ }
+ return
+}
+
+// TextSegmentError is an error with a text segment attached.
+type TextSegmentError struct {
+ Segment string
+ Err error
+}
+
+func (e TextSegmentError) Unwrap() error {
+ return e.Err
+}
+
+func (e TextSegmentError) Error() string {
+ return e.Err.Error()
+}
diff --git a/common/herrors/file_error_test.go b/common/herrors/file_error_test.go
index 675be94e877..e7e66ca8f9b 100644
--- a/common/herrors/file_error_test.go
+++ b/common/herrors/file_error_test.go
@@ -1,4 +1,4 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
+// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,14 +14,42 @@
package herrors
import (
+ "errors"
+ "fmt"
+ "strings"
"testing"
- "github.com/pkg/errors"
+ "github.com/gohugoio/hugo/common/text"
qt "github.com/frankban/quicktest"
)
-func TestToLineNumberError(t *testing.T) {
+func TestNewFileError(t *testing.T) {
+ t.Parallel()
+
+ c := qt.New(t)
+
+ fe := NewFileErrorFromName(errors.New("bar"), "foo.html")
+ c.Assert(fe.Error(), qt.Equals, `"foo.html:1:1": bar`)
+
+ var lines strings.Builder
+ for i := 1; i <= 100; i++ {
+ lines.WriteString(fmt.Sprintf("line %d\n", i))
+ }
+
+ fe.UpdatePosition(text.Position{LineNumber: 32, ColumnNumber: 2})
+ c.Assert(fe.Error(), qt.Equals, `"foo.html:32:2": bar`)
+ fe.UpdatePosition(text.Position{LineNumber: 0, ColumnNumber: 0, Offset: 212})
+ fe.UpdateContent(strings.NewReader(lines.String()), nil)
+ c.Assert(fe.Error(), qt.Equals, `"foo.html:32:0": bar`)
+ errorContext := fe.ErrorContext()
+ c.Assert(errorContext, qt.IsNotNil)
+ c.Assert(errorContext.Lines, qt.DeepEquals, []string{"line 30", "line 31", "line 32", "line 33", "line 34"})
+ c.Assert(errorContext.LinesPos, qt.Equals, 2)
+ c.Assert(errorContext.ChromaLexer, qt.Equals, "go-html-template")
+}
+
+func TestNewFileErrorExtractFromMessage(t *testing.T) {
t.Parallel()
c := qt.New(t)
@@ -37,18 +65,16 @@ func TestToLineNumberError(t *testing.T) {
{errors.New("parse failed: template: _default/bundle-resource-meta.html:11: unexpected in operand"), 0, 11, 1},
{errors.New(`failed:: template: _default/bundle-resource-meta.html:2:7: executing "main" at <.Titles>`), 0, 2, 7},
{errors.New(`failed to load translations: (6, 7): was expecting token =, but got "g" instead`), 0, 6, 7},
+ {errors.New(`execute of template failed: template: index.html:2:5: executing "index.html" at : error calling partial: "/layouts/partials/foo.html:3:6": execute of template failed: template: partials/foo.html:3:6: executing "partials/foo.html" at <.ThisDoesNotExist>: can't evaluate field ThisDoesNotExist in type *hugolib.pageStat`), 0, 2, 5},
} {
- got := ToFileError("template", test.in)
+ got := NewFileErrorFromName(test.in, "test.txt")
errMsg := qt.Commentf("[%d][%T]", i, got)
- le, ok := got.(FileError)
- c.Assert(ok, qt.Equals, true)
- c.Assert(ok, qt.Equals, true, errMsg)
- pos := le.Position()
+ pos := got.Position()
c.Assert(pos.LineNumber, qt.Equals, test.lineNumber, errMsg)
c.Assert(pos.ColumnNumber, qt.Equals, test.columnNumber, errMsg)
- c.Assert(errors.Cause(got), qt.Not(qt.IsNil))
+ c.Assert(errors.Unwrap(got), qt.Not(qt.IsNil))
}
}
diff --git a/common/herrors/line_number_extractors.go b/common/herrors/line_number_extractors.go
index 3df62bdfe3a..121506bb07a 100644
--- a/common/herrors/line_number_extractors.go
+++ b/common/herrors/line_number_extractors.go
@@ -16,36 +16,32 @@ package herrors
import (
"regexp"
"strconv"
-
- "github.com/pkg/errors"
-
- "github.com/pelletier/go-toml/v2"
)
var lineNumberExtractors = []lineNumberExtractor{
- // Template/shortcode parse errors
- newLineNumberErrHandlerFromRegexp(".*:(\\d+):(\\d*):"),
- newLineNumberErrHandlerFromRegexp(".*:(\\d+):"),
+ // YAML parse errors.
+ newLineNumberErrHandlerFromRegexp(`\[(\d+):(\d+)\]`),
- // TOML parse errors
- tomlLineNumberExtractor,
- // YAML parse errors
- newLineNumberErrHandlerFromRegexp("line (\\d+):"),
+ // Template/shortcode parse errors
+ newLineNumberErrHandlerFromRegexp(`:(\d+):(\d*):`),
+ newLineNumberErrHandlerFromRegexp(`:(\d+):`),
// i18n bundle errors
- newLineNumberErrHandlerFromRegexp("\\((\\d+),\\s(\\d*)"),
+ newLineNumberErrHandlerFromRegexp(`\((\d+),\s(\d*)`),
}
-type lineNumberExtractor func(e error) (int, int)
-
-var tomlLineNumberExtractor = func(e error) (int, int) {
- e = errors.Cause(e)
- if terr, ok := e.(*toml.DecodeError); ok {
- return terr.Position()
+func commonLineNumberExtractor(e error) (int, int) {
+ for _, handler := range lineNumberExtractors {
+ lno, col := handler(e)
+ if lno > 0 {
+ return lno, col
+ }
}
- return -1, -1
+ return 0, 0
}
+type lineNumberExtractor func(e error) (int, int)
+
func newLineNumberErrHandlerFromRegexp(expression string) lineNumberExtractor {
re := regexp.MustCompile(expression)
return extractLineNo(re)
@@ -72,6 +68,6 @@ func extractLineNo(re *regexp.Regexp) lineNumberExtractor {
return lno, col
}
- return -1, col
+ return 0, col
}
}
diff --git a/common/herrors/line_number_extractors_test.go b/common/herrors/line_number_extractors_test.go
new file mode 100644
index 00000000000..482c0dd6828
--- /dev/null
+++ b/common/herrors/line_number_extractors_test.go
@@ -0,0 +1,31 @@
+// Copyright 2025 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package herrors
+
+import (
+ "errors"
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestCommonLineNumberExtractor(t *testing.T) {
+ t.Parallel()
+
+ c := qt.New(t)
+
+ lno, col := commonLineNumberExtractor(errors.New("[4:9] value is not allowed in this context"))
+ c.Assert(lno, qt.Equals, 4)
+ c.Assert(col, qt.Equals, 9)
+}
diff --git a/common/hexec/exec.go b/common/hexec/exec.go
index a8bdd1bb7af..79d48a48392 100644
--- a/common/hexec/exec.go
+++ b/common/hexec/exec.go
@@ -19,13 +19,16 @@ import (
"errors"
"fmt"
"io"
- "regexp"
- "strings"
-
"os"
"os/exec"
+ "path/filepath"
+ "regexp"
+ "strings"
+ "sync"
- "github.com/cli/safeexec"
+ "github.com/bep/logg"
+ "github.com/gohugoio/hugo/common/hmaps"
+ "github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/security"
)
@@ -85,7 +88,7 @@ var WithEnviron = func(env []string) func(c *commandeer) {
}
// New creates a new Exec using the provided security config.
-func New(cfg security.Config) *Exec {
+func New(cfg security.Config, workingDir string, log loggers.Logger) *Exec {
var baseEnviron []string
for _, v := range os.Environ() {
k, _ := config.SplitEnvVar(v)
@@ -95,8 +98,11 @@ func New(cfg security.Config) *Exec {
}
return &Exec{
- sc: cfg,
- baseEnviron: baseEnviron,
+ sc: cfg,
+ workingDir: workingDir,
+ infol: log.InfoCommand("exec"),
+ baseEnviron: baseEnviron,
+ newNPXRunnerCache: hmaps.NewCache[string, func(arg ...any) (Runner, error)](),
}
}
@@ -106,29 +112,27 @@ func IsNotFound(err error) bool {
return errors.As(err, ¬FoundErr)
}
-// SafeCommand is a wrapper around os/exec Command which uses a LookPath
-// implementation that does not search in current directory before looking in PATH.
-// See https://github.com/cli/safeexec and the linked issues.
-func SafeCommand(name string, arg ...string) (*exec.Cmd, error) {
- bin, err := safeexec.LookPath(name)
- if err != nil {
- return nil, err
- }
-
- return exec.Command(bin, arg...), nil
-}
-
-// Exec encorces a security policy for commands run via os/exec.
+// Exec enforces a security policy for commands run via os/exec.
type Exec struct {
- sc security.Config
+ sc security.Config
+ workingDir string
+ infol logg.LevelLogger
// os.Environ filtered by the Exec.OsEnviron whitelist filter.
baseEnviron []string
+
+ newNPXRunnerCache *hmaps.Cache[string, func(arg ...any) (Runner, error)]
+ 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, arg ...interface{}) (Runner, error) {
+func (e *Exec) new(name string, fullyQualifiedName string, arg ...any) (Runner, error) {
if err := e.sc.CheckAllowedExec(name); err != nil {
return nil, err
}
@@ -137,18 +141,112 @@ func (e *Exec) New(name string, arg ...interface{}) (Runner, error) {
copy(env, e.baseEnviron)
cm := &commandeer{
- name: name,
- env: env,
+ name: name,
+ fullyQualifiedName: fullyQualifiedName,
+ env: env,
}
return cm.command(arg...)
+}
+
+type binaryLocation int
+
+func (b binaryLocation) String() string {
+ switch b {
+ case binaryLocationNodeModules:
+ return "node_modules/.bin"
+ case binaryLocationNpx:
+ return "npx"
+ case binaryLocationPath:
+ return "PATH"
+ }
+ return "unknown"
+}
+
+const (
+ binaryLocationNodeModules binaryLocation = iota + 1
+ binaryLocationNpx
+ binaryLocationPath
+)
+
+// Npx will in order:
+// 1. Try fo find the binary in the WORKINGDIR/node_modules/.bin directory.
+// 2. If not found, and npx is available, run npx --no-install .
+// 3. Fall back to the PATH.
+// If name is "tailwindcss", we will try the PATH as the second option.
+func (e *Exec) Npx(name string, arg ...any) (Runner, error) {
+ if err := e.sc.CheckAllowedExec(name); err != nil {
+ return nil, err
+ }
+ newRunner, err := e.newNPXRunnerCache.GetOrCreate(name, func() (func(...any) (Runner, error), error) {
+ type tryFunc func() func(...any) (Runner, error)
+ tryFuncs := map[binaryLocation]tryFunc{
+ binaryLocationNodeModules: func() func(...any) (Runner, error) {
+ nodeBinFilename := filepath.Join(e.workingDir, nodeModulesBinPath, name)
+ _, err := exec.LookPath(nodeBinFilename)
+ if err != nil {
+ return nil
+ }
+ return func(arg2 ...any) (Runner, error) {
+ return e.new(name, nodeBinFilename, arg2...)
+ }
+ },
+ binaryLocationNpx: func() func(...any) (Runner, error) {
+ e.checkNpx()
+ if !e.npxAvailable {
+ return nil
+ }
+ return func(arg2 ...any) (Runner, error) {
+ return e.npx(name, arg2...)
+ }
+ },
+ binaryLocationPath: func() func(...any) (Runner, error) {
+ if _, err := exec.LookPath(name); err != nil {
+ return nil
+ }
+ return func(arg2 ...any) (Runner, error) {
+ return e.New(name, arg2...)
+ }
+ },
+ }
+
+ locations := []binaryLocation{binaryLocationNodeModules, binaryLocationNpx, binaryLocationPath}
+ if name == "tailwindcss" {
+ // See https://github.com/gohugoio/hugo/issues/13221#issuecomment-2574801253
+ locations = []binaryLocation{binaryLocationNodeModules, binaryLocationPath, binaryLocationNpx}
+ }
+ for _, loc := range locations {
+ if f := tryFuncs[loc](); f != nil {
+ e.infol.Logf("resolve %q using %s", name, loc)
+ return f, nil
+ }
+ }
+ return nil, &NotFoundError{name: name, method: fmt.Sprintf("in %s", locations[len(locations)-1])}
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ return newRunner(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 0 {
+ delays[i] = 1
+ }
+ }
+ return delays
+}
+
+// GifDelaysToFrameDurations converts GIF delays in 100ths of a second to
+// frame durations in milliseconds.
+func GifDelaysToFrameDurations(delays []int) []int {
+ frameDurations := make([]int, len(delays))
+ for i, d := range delays {
+ frameDurations[i] = d * 10
+ }
+ return frameDurations
+}
diff --git a/common/hiter/iter.go b/common/hiter/iter.go
new file mode 100644
index 00000000000..dd2a418357c
--- /dev/null
+++ b/common/hiter/iter.go
@@ -0,0 +1,66 @@
+package hiter
+
+// Common iterator functions.
+// Some of these are are based on this discsussion: https://github.com/golang/go/issues/61898
+
+import "iter"
+
+// Concat returns an iterator over the concatenation of the sequences.
+// Any nil sequences are ignored.
+func Concat[V any](seqs ...iter.Seq[V]) iter.Seq[V] {
+ return func(yield func(V) bool) {
+ for _, seq := range seqs {
+ if seq == nil {
+ continue
+ }
+ for e := range seq {
+ if !yield(e) {
+ return
+ }
+ }
+ }
+ }
+}
+
+// Concat2 returns an iterator over the concatenation of the sequences.
+// Any nil sequences are ignored.
+func Concat2[K, V any](seqs ...iter.Seq2[K, V]) iter.Seq2[K, V] {
+ return func(yield func(K, V) bool) {
+ for _, seq := range seqs {
+ if seq == nil {
+ continue
+ }
+ for k, v := range seq {
+ if !yield(k, v) {
+ return
+ }
+ }
+ }
+ }
+}
+
+// Lock returns an iterator that locks before iterating and unlocks after.
+func Lock[V any](seq iter.Seq[V], lock, unlock func()) iter.Seq[V] {
+ return func(yield func(V) bool) {
+ lock()
+ defer unlock()
+ for e := range seq {
+ if !yield(e) {
+ return
+ }
+ }
+ }
+}
+
+// Lock2 returns an iterator that locks before iterating and unlocks after.
+func Lock2[K, V any](seq iter.Seq2[K, V], lock, unlock func()) iter.Seq2[K, V] {
+ return func(yield func(K, V) bool) {
+ lock()
+ defer unlock()
+ for k, v := range seq {
+ if !yield(k, v) {
+ return
+ }
+ }
+ }
+}
diff --git a/common/hmaps/cache.go b/common/hmaps/cache.go
new file mode 100644
index 00000000000..b67f1bcecba
--- /dev/null
+++ b/common/hmaps/cache.go
@@ -0,0 +1,218 @@
+// Copyright 2026 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hmaps
+
+import (
+ "sync"
+)
+
+// Cache is a simple thread safe cache backed by a map.
+type Cache[K comparable, T any] struct {
+ m map[K]T
+ opts CacheOptions
+ hasBeenInitialized bool
+ sync.RWMutex
+}
+
+// CacheOptions are the options for the Cache.
+type CacheOptions struct {
+ // If set, the cache will not grow beyond this size.
+ Size uint64
+}
+
+var defaultCacheOptions = CacheOptions{}
+
+// NewCache creates a new Cache with default options.
+func NewCache[K comparable, T any]() *Cache[K, T] {
+ return &Cache[K, T]{m: make(map[K]T), opts: defaultCacheOptions}
+}
+
+// NewCacheWithOptions creates a new Cache with the given options.
+func NewCacheWithOptions[K comparable, T any](opts CacheOptions) *Cache[K, T] {
+ return &Cache[K, T]{m: make(map[K]T), opts: opts}
+}
+
+// Delete deletes the given key from the cache.
+// If c is nil, this method is a no-op.
+func (c *Cache[K, T]) Get(key K) (T, bool) {
+ if c == nil {
+ var zero T
+ return zero, false
+ }
+ c.RLock()
+ v, found := c.get(key)
+ c.RUnlock()
+ return v, found
+}
+
+func (c *Cache[K, T]) get(key K) (T, bool) {
+ v, found := c.m[key]
+ return v, found
+}
+
+// GetOrCreate gets the value for the given key if it exists, or creates it if not.
+func (c *Cache[K, T]) GetOrCreate(key K, create func() (T, error)) (T, error) {
+ c.RLock()
+ v, found := c.m[key]
+ c.RUnlock()
+ if found {
+ return v, nil
+ }
+ c.Lock()
+ defer c.Unlock()
+ v, found = c.m[key]
+ if found {
+ return v, nil
+ }
+ v, err := create()
+ if err != nil {
+ return v, err
+ }
+ c.clearIfNeeded()
+ c.m[key] = v
+ return v, nil
+}
+
+// Contains returns whether the given key exists in the cache.
+func (c *Cache[K, T]) Contains(key K) bool {
+ c.RLock()
+ _, found := c.m[key]
+ c.RUnlock()
+ return found
+}
+
+// InitAndGet initializes the cache if not already done and returns the value for the given key.
+// The init state will be reset on Reset or Drain.
+func (c *Cache[K, T]) InitAndGet(key K, init func(get func(key K) (T, bool), set func(key K, value T)) error) (T, error) {
+ var v T
+ c.RLock()
+ if !c.hasBeenInitialized {
+ c.RUnlock()
+ if err := func() error {
+ c.Lock()
+ defer c.Unlock()
+ // Double check in case another goroutine has initialized it in the meantime.
+ if !c.hasBeenInitialized {
+ err := init(c.get, c.set)
+ if err != nil {
+ return err
+ }
+ c.hasBeenInitialized = true
+ }
+ return nil
+ }(); err != nil {
+ return v, err
+ }
+ // Reacquire the read lock.
+ c.RLock()
+ }
+
+ v = c.m[key]
+ c.RUnlock()
+
+ return v, nil
+}
+
+// Set sets the given key to the given value.
+func (c *Cache[K, T]) Set(key K, value T) {
+ c.Lock()
+ c.set(key, value)
+ c.Unlock()
+}
+
+// SetIfAbsent sets the given key to the given value if the key does not already exist in the cache.
+func (c *Cache[K, T]) SetIfAbsent(key K, value T) {
+ c.RLock()
+ if _, found := c.get(key); !found {
+ c.RUnlock()
+ c.Set(key, value)
+ } else {
+ c.RUnlock()
+ }
+}
+
+func (c *Cache[K, T]) clearIfNeeded() {
+ if c.opts.Size > 0 && uint64(len(c.m)) >= c.opts.Size {
+ // clear the map
+ clear(c.m)
+ }
+}
+
+func (c *Cache[K, T]) set(key K, value T) {
+ c.clearIfNeeded()
+ c.m[key] = value
+}
+
+// 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) {
+ c.RLock()
+ defer c.RUnlock()
+ for k, v := range c.m {
+ if !f(k, v) {
+ return
+ }
+ }
+}
+
+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()
+ clear(c.m)
+ 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
+ sync.RWMutex
+}
+
+func NewSliceCache[T any]() *SliceCache[T] {
+ return &SliceCache[T]{m: make(map[string][]T)}
+}
+
+func (c *SliceCache[T]) Get(key string) ([]T, bool) {
+ c.RLock()
+ v, found := c.m[key]
+ c.RUnlock()
+ return v, found
+}
+
+func (c *SliceCache[T]) Append(key string, values ...T) {
+ c.Lock()
+ c.m[key] = append(c.m[key], values...)
+ c.Unlock()
+}
+
+func (c *SliceCache[T]) Reset() {
+ c.Lock()
+ c.m = make(map[string][]T)
+ c.Unlock()
+}
diff --git a/common/hmaps/cache_test.go b/common/hmaps/cache_test.go
new file mode 100644
index 00000000000..c6555d2594a
--- /dev/null
+++ b/common/hmaps/cache_test.go
@@ -0,0 +1,57 @@
+// Copyright 2026 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hmaps
+
+import (
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestCacheSize(t *testing.T) {
+ c := qt.New(t)
+
+ cache := NewCacheWithOptions[string, string](CacheOptions{Size: 10})
+
+ for i := range 30 {
+ cache.Set(string(rune('a'+i)), "value")
+ }
+
+ c.Assert(len(cache.m), qt.Equals, 10)
+
+ for i := 20; i < 50; i++ {
+ cache.GetOrCreate(string(rune('a'+i)), func() (string, error) {
+ return "value", nil
+ })
+ }
+
+ c.Assert(len(cache.m), qt.Equals, 10)
+
+ for i := 100; i < 200; i++ {
+ cache.SetIfAbsent(string(rune('a'+i)), "value")
+ }
+
+ c.Assert(len(cache.m), qt.Equals, 10)
+
+ cache.InitAndGet("foo", func(
+ get func(key string) (string, bool), set func(key string, value string),
+ ) error {
+ for i := 50; i < 100; i++ {
+ set(string(rune('a'+i)), "value")
+ }
+ return nil
+ })
+
+ c.Assert(len(cache.m), qt.Equals, 10)
+}
diff --git a/common/hmaps/map.go b/common/hmaps/map.go
new file mode 100644
index 00000000000..8bb1cfa2b4e
--- /dev/null
+++ b/common/hmaps/map.go
@@ -0,0 +1,129 @@
+// Copyright 2026 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hmaps
+
+import (
+ "iter"
+ "sync"
+)
+
+func NewMap[K comparable, T any]() *Map[K, T] {
+ return &Map[K, T]{
+ m: make(map[K]T),
+ }
+}
+
+// Map is a thread safe map backed by a Go map.
+type Map[K comparable, T any] struct {
+ m map[K]T
+ mu sync.RWMutex
+}
+
+// Get gets the value for the given key.
+// It returns the zero value of T if the key is not found.
+func (m *Map[K, T]) Get(key K) T {
+ v, _ := m.Lookup(key)
+ return v
+}
+
+// Lookup looks up the given key in the map.
+// It returns the value and a boolean indicating whether the key was found.
+func (m *Map[K, T]) Lookup(key K) (T, bool) {
+ m.mu.RLock()
+ v, found := m.m[key]
+ m.mu.RUnlock()
+ return v, found
+}
+
+// GetOrCreate gets the value for the given key if it exists, or creates it if not.
+func (m *Map[K, T]) GetOrCreate(key K, create func() (T, error)) (T, error) {
+ v, found := m.Lookup(key)
+ if found {
+ return v, nil
+ }
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ v, found = m.m[key]
+ if found {
+ return v, nil
+ }
+ v, err := create()
+ if err != nil {
+ return v, err
+ }
+ m.m[key] = v
+ return v, nil
+}
+
+// Set sets the given key to the given value.
+func (m *Map[K, T]) Set(key K, value T) {
+ m.mu.Lock()
+ m.m[key] = value
+ m.mu.Unlock()
+}
+
+// Delete deletes the given key from the map.
+// It returns true if the key was found and deleted, false otherwise.
+func (m *Map[K, T]) Delete(key K) bool {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ if _, found := m.m[key]; found {
+ delete(m.m, key)
+ return true
+ }
+ return false
+}
+
+// WithWriteLock executes the given function with a write lock on the map.
+func (m *Map[K, T]) WithWriteLock(f func(m map[K]T) error) error {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ return f(m.m)
+}
+
+// SetIfAbsent sets the given key to the given value if the key does not already exist in the map.
+// It returns true if the value was set, false otherwise.
+func (m *Map[K, T]) SetIfAbsent(key K, value T) bool {
+ m.mu.RLock()
+ if _, found := m.m[key]; !found {
+ m.mu.RUnlock()
+ return m.doSetIfAbsent(key, value)
+ }
+ m.mu.RUnlock()
+ return false
+}
+
+func (m *Map[K, T]) doSetIfAbsent(key K, value T) bool {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ if _, found := m.m[key]; !found {
+ m.m[key] = value
+ return true
+ }
+ return false
+}
+
+// All returns an iterator over all key/value pairs in the map.
+// A read lock is held during the iteration.
+func (m *Map[K, T]) All() iter.Seq2[K, T] {
+ return func(yield func(K, T) bool) {
+ m.mu.RLock()
+ defer m.mu.RUnlock()
+ for k, v := range m.m {
+ if !yield(k, v) {
+ return
+ }
+ }
+ }
+}
diff --git a/common/hmaps/map_test.go b/common/hmaps/map_test.go
new file mode 100644
index 00000000000..7791bc6dac8
--- /dev/null
+++ b/common/hmaps/map_test.go
@@ -0,0 +1,72 @@
+// Copyright 2026 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hmaps
+
+import (
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestMap(t *testing.T) {
+ c := qt.New(t)
+
+ m := NewMap[string, int]()
+
+ m.Set("b", 42)
+ v, found := m.Lookup("b")
+ c.Assert(found, qt.Equals, true)
+ c.Assert(v, qt.Equals, 42)
+ v = m.Get("b")
+ c.Assert(v, qt.Equals, 42)
+ v, found = m.Lookup("c")
+ c.Assert(found, qt.Equals, false)
+ c.Assert(v, qt.Equals, 0)
+ v = m.Get("c")
+ c.Assert(v, qt.Equals, 0)
+ v, err := m.GetOrCreate("d", func() (int, error) {
+ return 100, nil
+ })
+ c.Assert(err, qt.IsNil)
+ c.Assert(v, qt.Equals, 100)
+ v, found = m.Lookup("d")
+ c.Assert(found, qt.Equals, true)
+ c.Assert(v, qt.Equals, 100)
+
+ v, err = m.GetOrCreate("d", func() (int, error) {
+ return 200, nil
+ })
+ c.Assert(err, qt.IsNil)
+ c.Assert(v, qt.Equals, 100)
+
+ wasSet := m.SetIfAbsent("e", 300)
+ c.Assert(wasSet, qt.Equals, true)
+ v, found = m.Lookup("e")
+ c.Assert(found, qt.Equals, true)
+ c.Assert(v, qt.Equals, 300)
+
+ wasSet = m.SetIfAbsent("e", 400)
+ c.Assert(wasSet, qt.Equals, false)
+ v, found = m.Lookup("e")
+ c.Assert(found, qt.Equals, true)
+ c.Assert(v, qt.Equals, 300)
+
+ m.WithWriteLock(func(m map[string]int) error {
+ m["f"] = 500
+ return nil
+ })
+ v, found = m.Lookup("f")
+ c.Assert(found, qt.Equals, true)
+ c.Assert(v, qt.Equals, 500)
+}
diff --git a/common/hmaps/maps.go b/common/hmaps/maps.go
new file mode 100644
index 00000000000..36d01035be7
--- /dev/null
+++ b/common/hmaps/maps.go
@@ -0,0 +1,238 @@
+// Copyright 2026 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hmaps
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/gohugoio/hugo/common/types"
+
+ "github.com/gobwas/glob"
+ "github.com/spf13/cast"
+)
+
+// ToStringMapE converts in to map[string]interface{}.
+func ToStringMapE(in any) (map[string]any, error) {
+ switch vv := in.(type) {
+ case Params:
+ return vv, nil
+ case map[string]string:
+ m := map[string]any{}
+ for k, v := range vv {
+ m[k] = v
+ }
+ return m, nil
+
+ default:
+ return cast.ToStringMapE(in)
+ }
+}
+
+// ToParamsAndPrepare converts in to Params and prepares it for use.
+// If in is nil, an empty map is returned.
+// See PrepareParams.
+func ToParamsAndPrepare(in any) (Params, error) {
+ if types.IsNil(in) {
+ return Params{}, nil
+ }
+ m, err := ToStringMapE(in)
+ if err != nil {
+ return nil, err
+ }
+ PrepareParams(m)
+ return m, nil
+}
+
+// MustToParamsAndPrepare calls ToParamsAndPrepare and panics if it fails.
+func MustToParamsAndPrepare(in any) Params {
+ p, err := ToParamsAndPrepare(in)
+ if err != nil {
+ panic(fmt.Sprintf("cannot convert %T to maps.Params: %s", in, err))
+ }
+ return p
+}
+
+// ToStringMap converts in to map[string]interface{}.
+func ToStringMap(in any) map[string]any {
+ m, _ := ToStringMapE(in)
+ return m
+}
+
+// ToStringMapStringE converts in to map[string]string.
+func ToStringMapStringE(in any) (map[string]string, error) {
+ m, err := ToStringMapE(in)
+ if err != nil {
+ return nil, err
+ }
+ return cast.ToStringMapStringE(m)
+}
+
+// ToStringMapString converts in to map[string]string.
+func ToStringMapString(in any) map[string]string {
+ m, _ := ToStringMapStringE(in)
+ return m
+}
+
+// ToStringMapBool converts in to bool.
+func ToStringMapBool(in any) map[string]bool {
+ m, _ := ToStringMapE(in)
+ return cast.ToStringMapBool(m)
+}
+
+// ToSliceStringMap converts in to []map[string]interface{}.
+func ToSliceStringMap(in any) ([]map[string]any, error) {
+ switch v := in.(type) {
+ case []map[string]any:
+ return v, nil
+ case Params:
+ return []map[string]any{v}, nil
+ case map[string]any:
+ return []map[string]any{v}, nil
+ case []any:
+ var s []map[string]any
+ for _, entry := range v {
+ if vv, ok := entry.(map[string]any); ok {
+ s = append(s, vv)
+ }
+ }
+ return s, nil
+ default:
+ return nil, fmt.Errorf("unable to cast %#v of type %T to []map[string]interface{}", in, in)
+ }
+}
+
+// LookupEqualFold finds key in m with case insensitive equality checks.
+func LookupEqualFold[T any | string](m map[string]T, key string) (T, string, bool) {
+ if v, found := m[key]; found {
+ return v, key, true
+ }
+ for k, v := range m {
+ if strings.EqualFold(k, key) {
+ return v, k, true
+ }
+ }
+ var s T
+ return s, "", false
+}
+
+// MergeShallow merges src into dst, but only if the key does not already exist in dst.
+// The keys are compared case insensitively.
+func MergeShallow(dst, src map[string]any) {
+ for k, v := range src {
+ found := false
+ for dk := range dst {
+ if strings.EqualFold(dk, k) {
+ found = true
+ break
+ }
+ }
+ if !found {
+ dst[k] = v
+ }
+ }
+}
+
+type keyRename struct {
+ pattern glob.Glob
+ newKey string
+}
+
+// KeyRenamer supports renaming of keys in a map.
+type KeyRenamer struct {
+ renames []keyRename
+}
+
+// NewKeyRenamer creates a new KeyRenamer given a list of pattern and new key
+// value pairs.
+func NewKeyRenamer(patternKeys ...string) (KeyRenamer, error) {
+ var renames []keyRename
+ for i := 0; i < len(patternKeys); i += 2 {
+ g, err := glob.Compile(strings.ToLower(patternKeys[i]), '/')
+ if err != nil {
+ return KeyRenamer{}, err
+ }
+ renames = append(renames, keyRename{pattern: g, newKey: patternKeys[i+1]})
+ }
+
+ return KeyRenamer{renames: renames}, nil
+}
+
+func (r KeyRenamer) getNewKey(keyPath string) string {
+ for _, matcher := range r.renames {
+ if matcher.pattern.Match(keyPath) {
+ return matcher.newKey
+ }
+ }
+
+ return ""
+}
+
+// Rename renames the keys in the given map according
+// to the patterns in the current KeyRenamer.
+func (r KeyRenamer) Rename(m map[string]any) {
+ r.renamePath("", m)
+}
+
+func (KeyRenamer) keyPath(k1, k2 string) string {
+ k1, k2 = strings.ToLower(k1), strings.ToLower(k2)
+ if k1 == "" {
+ return k2
+ }
+ return k1 + "/" + k2
+}
+
+func (r KeyRenamer) renamePath(parentKeyPath string, m map[string]any) {
+ for k, v := range m {
+ keyPath := r.keyPath(parentKeyPath, k)
+ switch vv := v.(type) {
+ case map[any]any:
+ r.renamePath(keyPath, cast.ToStringMap(vv))
+ case map[string]any:
+ r.renamePath(keyPath, vv)
+ }
+
+ newKey := r.getNewKey(keyPath)
+
+ if newKey != "" {
+ delete(m, k)
+ m[newKey] = v
+ }
+ }
+}
+
+// ConvertFloat64WithNoDecimalsToInt converts float64 values with no decimals to int recursively.
+func ConvertFloat64WithNoDecimalsToInt(m map[string]any) {
+ for k, v := range m {
+ switch vv := v.(type) {
+ case float64:
+ if v == float64(int64(vv)) {
+ m[k] = int64(vv)
+ }
+ case map[string]any:
+ ConvertFloat64WithNoDecimalsToInt(vv)
+ case []any:
+ for i, vvv := range vv {
+ switch vvvv := vvv.(type) {
+ case float64:
+ if vvv == float64(int64(vvvv)) {
+ vv[i] = int64(vvvv)
+ }
+ case map[string]any:
+ ConvertFloat64WithNoDecimalsToInt(vvvv)
+ }
+ }
+ }
+ }
+}
diff --git a/common/hmaps/maps_test.go b/common/hmaps/maps_test.go
new file mode 100644
index 00000000000..a017e6f70e3
--- /dev/null
+++ b/common/hmaps/maps_test.go
@@ -0,0 +1,201 @@
+// Copyright 2026 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hmaps
+
+import (
+ "fmt"
+ "reflect"
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestPrepareParams(t *testing.T) {
+ tests := []struct {
+ input Params
+ expected Params
+ }{
+ {
+ map[string]any{
+ "abC": 32,
+ },
+ Params{
+ "abc": 32,
+ },
+ },
+ {
+ map[string]any{
+ "abC": 32,
+ "deF": map[any]any{
+ 23: "A value",
+ 24: map[string]any{
+ "AbCDe": "A value",
+ "eFgHi": "Another value",
+ },
+ },
+ "gHi": map[string]any{
+ "J": 25,
+ },
+ "jKl": map[string]string{
+ "M": "26",
+ },
+ },
+ Params{
+ "abc": 32,
+ "def": Params{
+ "23": "A value",
+ "24": Params{
+ "abcde": "A value",
+ "efghi": "Another value",
+ },
+ },
+ "ghi": Params{
+ "j": 25,
+ },
+ "jkl": Params{
+ "m": "26",
+ },
+ },
+ },
+ }
+
+ for i, test := range tests {
+ t.Run(fmt.Sprint(i), func(t *testing.T) {
+ // PrepareParams modifies input.
+ prepareClone := PrepareParamsClone(test.input)
+ PrepareParams(test.input)
+ if !reflect.DeepEqual(test.expected, test.input) {
+ t.Errorf("[%d] Expected\n%#v, got\n%#v\n", i, test.expected, test.input)
+ }
+ if !reflect.DeepEqual(test.expected, prepareClone) {
+ t.Errorf("[%d] Expected\n%#v, got\n%#v\n", i, test.expected, prepareClone)
+ }
+ })
+ }
+}
+
+func TestToSliceStringMap(t *testing.T) {
+ c := qt.New(t)
+
+ tests := []struct {
+ input any
+ expected []map[string]any
+ }{
+ {
+ input: []map[string]any{
+ {"abc": 123},
+ },
+ expected: []map[string]any{
+ {"abc": 123},
+ },
+ }, {
+ input: []any{
+ map[string]any{
+ "def": 456,
+ },
+ },
+ expected: []map[string]any{
+ {"def": 456},
+ },
+ },
+ }
+
+ for _, test := range tests {
+ v, err := ToSliceStringMap(test.input)
+ c.Assert(err, qt.IsNil)
+ c.Assert(v, qt.DeepEquals, test.expected)
+ }
+}
+
+func TestToParamsAndPrepare(t *testing.T) {
+ c := qt.New(t)
+ _, err := ToParamsAndPrepare(map[string]any{"A": "av"})
+ c.Assert(err, qt.IsNil)
+
+ params, err := ToParamsAndPrepare(nil)
+ c.Assert(err, qt.IsNil)
+ c.Assert(params, qt.DeepEquals, Params{})
+}
+
+func TestRenameKeys(t *testing.T) {
+ c := qt.New(t)
+
+ m := map[string]any{
+ "a": 32,
+ "ren1": "m1",
+ "ren2": "m1_2",
+ "sub": map[string]any{
+ "subsub": map[string]any{
+ "REN1": "m2",
+ "ren2": "m2_2",
+ },
+ },
+ "no": map[string]any{
+ "ren1": "m2",
+ "ren2": "m2_2",
+ },
+ }
+
+ expected := map[string]any{
+ "a": 32,
+ "new1": "m1",
+ "new2": "m1_2",
+ "sub": map[string]any{
+ "subsub": map[string]any{
+ "new1": "m2",
+ "ren2": "m2_2",
+ },
+ },
+ "no": map[string]any{
+ "ren1": "m2",
+ "ren2": "m2_2",
+ },
+ }
+
+ renamer, err := NewKeyRenamer(
+ "{ren1,sub/*/ren1}", "new1",
+ "{Ren2,sub/ren2}", "new2",
+ )
+ c.Assert(err, qt.IsNil)
+
+ renamer.Rename(m)
+
+ if !reflect.DeepEqual(expected, m) {
+ t.Errorf("Expected\n%#v, got\n%#v\n", expected, m)
+ }
+}
+
+func TestLookupEqualFold(t *testing.T) {
+ c := qt.New(t)
+
+ m1 := map[string]any{
+ "a": "av",
+ "B": "bv",
+ }
+
+ v, k, found := LookupEqualFold(m1, "b")
+ c.Assert(found, qt.IsTrue)
+ c.Assert(v, qt.Equals, "bv")
+ c.Assert(k, qt.Equals, "B")
+
+ m2 := map[string]string{
+ "a": "av",
+ "B": "bv",
+ }
+
+ v, k, found = LookupEqualFold(m2, "b")
+ c.Assert(found, qt.IsTrue)
+ c.Assert(k, qt.Equals, "B")
+ c.Assert(v, qt.Equals, "bv")
+}
diff --git a/common/hmaps/ordered.go b/common/hmaps/ordered.go
new file mode 100644
index 00000000000..b1a567b8f50
--- /dev/null
+++ b/common/hmaps/ordered.go
@@ -0,0 +1,144 @@
+// Copyright 2026 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hmaps
+
+import (
+ "slices"
+
+ "github.com/gohugoio/hugo/common/hashing"
+)
+
+// Ordered is a map that can be iterated in the order of insertion.
+// Note that insertion order is not affected if a key is re-inserted into the map.
+// In a nil map, all operations are no-ops.
+// This is not thread safe.
+type Ordered[K comparable, T any] struct {
+ // The keys in the order they were added.
+ keys []K
+ // The values.
+ values map[K]T
+}
+
+// NewOrdered creates a new Ordered map.
+func NewOrdered[K comparable, T any]() *Ordered[K, T] {
+ return &Ordered[K, T]{values: make(map[K]T)}
+}
+
+// Set sets the value for the given key.
+// Note that insertion order is not affected if a key is re-inserted into the map.
+func (m *Ordered[K, T]) Set(key K, value T) {
+ if m == nil {
+ return
+ }
+ // Check if key already exists.
+ if _, found := m.values[key]; !found {
+ m.keys = append(m.keys, key)
+ }
+ m.values[key] = value
+}
+
+// Get gets the value for the given key.
+func (m *Ordered[K, T]) Get(key K) (T, bool) {
+ if m == nil {
+ var v T
+ return v, false
+ }
+ value, found := m.values[key]
+ return value, found
+}
+
+// Has returns whether the given key exists in the map.
+func (m *Ordered[K, T]) Has(key K) bool {
+ if m == nil {
+ return false
+ }
+ _, found := m.values[key]
+ return found
+}
+
+// Delete deletes the value for the given key.
+func (m *Ordered[K, T]) Delete(key K) {
+ if m == nil {
+ return
+ }
+ delete(m.values, key)
+ for i, k := range m.keys {
+ if k == key {
+ m.keys = slices.Delete(m.keys, i, i+1)
+ break
+ }
+ }
+}
+
+// Clone creates a shallow copy of the map.
+func (m *Ordered[K, T]) Clone() *Ordered[K, T] {
+ if m == nil {
+ return nil
+ }
+ clone := NewOrdered[K, T]()
+ for _, k := range m.keys {
+ clone.Set(k, m.values[k])
+ }
+ return clone
+}
+
+// Keys returns the keys in the order they were added.
+func (m *Ordered[K, T]) Keys() []K {
+ if m == nil {
+ return nil
+ }
+ return m.keys
+}
+
+// Values returns the values in the order they were added.
+func (m *Ordered[K, T]) Values() []T {
+ if m == nil {
+ return nil
+ }
+ var values []T
+ for _, k := range m.keys {
+ values = append(values, m.values[k])
+ }
+ return values
+}
+
+// Len returns the number of items in the map.
+func (m *Ordered[K, T]) Len() int {
+ if m == nil {
+ return 0
+ }
+ return len(m.keys)
+}
+
+// Range calls f sequentially for each key and value present in the map.
+// If f returns false, range stops the iteration.
+// TODO(bep) replace with iter.Seq2 when we bump go Go 1.24.
+func (m *Ordered[K, T]) Range(f func(key K, value T) bool) {
+ if m == nil {
+ return
+ }
+ for _, k := range m.keys {
+ if !f(k, m.values[k]) {
+ return
+ }
+ }
+}
+
+// Hash calculates a hash from the values.
+func (m *Ordered[K, T]) Hash() (uint64, error) {
+ if m == nil {
+ return 0, nil
+ }
+ return hashing.Hash(m.values)
+}
diff --git a/common/hmaps/ordered_test.go b/common/hmaps/ordered_test.go
new file mode 100644
index 00000000000..c04fe0aa4ae
--- /dev/null
+++ b/common/hmaps/ordered_test.go
@@ -0,0 +1,99 @@
+// Copyright 2026 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hmaps
+
+import (
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestOrdered(t *testing.T) {
+ c := qt.New(t)
+
+ m := NewOrdered[string, int]()
+ m.Set("a", 1)
+ m.Set("b", 2)
+ m.Set("c", 3)
+
+ c.Assert(m.Keys(), qt.DeepEquals, []string{"a", "b", "c"})
+ c.Assert(m.Values(), qt.DeepEquals, []int{1, 2, 3})
+
+ v, found := m.Get("b")
+ c.Assert(found, qt.Equals, true)
+ c.Assert(v, qt.Equals, 2)
+
+ m.Set("b", 22)
+ c.Assert(m.Keys(), qt.DeepEquals, []string{"a", "b", "c"})
+ c.Assert(m.Values(), qt.DeepEquals, []int{1, 22, 3})
+
+ m.Delete("b")
+
+ c.Assert(m.Keys(), qt.DeepEquals, []string{"a", "c"})
+ c.Assert(m.Values(), qt.DeepEquals, []int{1, 3})
+}
+
+func TestOrderedHash(t *testing.T) {
+ c := qt.New(t)
+
+ m := NewOrdered[string, int]()
+ m.Set("a", 1)
+ m.Set("b", 2)
+ m.Set("c", 3)
+
+ h1, err := m.Hash()
+ c.Assert(err, qt.IsNil)
+
+ m.Set("d", 4)
+
+ h2, err := m.Hash()
+ c.Assert(err, qt.IsNil)
+
+ c.Assert(h1, qt.Not(qt.Equals), h2)
+
+ m = NewOrdered[string, int]()
+ m.Set("b", 2)
+ m.Set("a", 1)
+ m.Set("c", 3)
+
+ h3, err := m.Hash()
+ c.Assert(err, qt.IsNil)
+ // Order does not matter.
+ c.Assert(h1, qt.Equals, h3)
+}
+
+func TestOrderedNil(t *testing.T) {
+ c := qt.New(t)
+
+ var m *Ordered[string, int]
+
+ m.Set("a", 1)
+ c.Assert(m.Keys(), qt.IsNil)
+ c.Assert(m.Values(), qt.IsNil)
+ v, found := m.Get("a")
+ c.Assert(found, qt.Equals, false)
+ c.Assert(v, qt.Equals, 0)
+ m.Delete("a")
+ var b bool
+ m.Range(func(k string, v int) bool {
+ b = true
+ return true
+ })
+ c.Assert(b, qt.Equals, false)
+ c.Assert(m.Len(), qt.Equals, 0)
+ c.Assert(m.Clone(), qt.IsNil)
+ h, err := m.Hash()
+ c.Assert(err, qt.IsNil)
+ c.Assert(h, qt.Equals, uint64(0))
+}
diff --git a/common/hmaps/orderedintset.go b/common/hmaps/orderedintset.go
new file mode 100644
index 00000000000..659d30ff894
--- /dev/null
+++ b/common/hmaps/orderedintset.go
@@ -0,0 +1,154 @@
+// Copyright 2026 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hmaps
+
+import (
+ "fmt"
+ "slices"
+
+ "github.com/bits-and-blooms/bitset"
+)
+
+type OrderedIntSet struct {
+ keys []int
+ values *bitset.BitSet
+}
+
+// NewOrderedIntSet creates a new OrderedIntSet.
+// Note that this is backed by https://github.com/bits-and-blooms/bitset
+func NewOrderedIntSet(vals ...int) *OrderedIntSet {
+ m := &OrderedIntSet{
+ keys: make([]int, 0, len(vals)),
+ values: bitset.New(uint(len(vals))),
+ }
+ for _, v := range vals {
+ m.Set(v)
+ }
+ return m
+}
+
+// Set sets the value for the given key.
+// Note that insertion order is not affected if a key is re-inserted into the set.
+func (m *OrderedIntSet) Set(key int) {
+ if m == nil {
+ panic("nil OrderedIntSet")
+ }
+ keyu := uint(key)
+ if m.values.Test(keyu) {
+ return
+ }
+ m.values.Set(keyu)
+ m.keys = append(m.keys, key)
+}
+
+// SetFrom sets the values from another OrderedIntSet.
+func (m *OrderedIntSet) SetFrom(other *OrderedIntSet) {
+ if m == nil || other == nil {
+ return
+ }
+ for _, key := range other.keys {
+ m.Set(key)
+ }
+}
+
+func (m *OrderedIntSet) Clone() *OrderedIntSet {
+ if m == nil {
+ return nil
+ }
+ newSet := &OrderedIntSet{
+ keys: slices.Clone(m.keys),
+ values: m.values.Clone(),
+ }
+ return newSet
+}
+
+// Next returns the next key in the set possibly including the given key.
+// It returns -1 if the key is not found or if there are no keys greater than the given key.
+func (m *OrderedIntSet) Next(i int) int {
+ n, ok := m.values.NextSet(uint(i))
+ if !ok {
+ return -1
+ }
+ return int(n)
+}
+
+// The reason we don't use iter.Seq is https://github.com/golang/go/issues/69015
+// This is 70% faster than using iter.Seq2[int, int] for the keys.
+// It returns false if the iteration was stopped early.
+func (m *OrderedIntSet) ForEachKey(yield func(int) bool) bool {
+ if m == nil {
+ return true
+ }
+ for _, key := range m.keys {
+ if !yield(key) {
+ return false
+ }
+ }
+ return true
+}
+
+func (m *OrderedIntSet) Has(key int) bool {
+ if m == nil {
+ return false
+ }
+ return m.values.Test(uint(key))
+}
+
+func (m *OrderedIntSet) Len() int {
+ if m == nil {
+ return 0
+ }
+ return len(m.keys)
+}
+
+// KeysSorted returns the keys in sorted order.
+func (m *OrderedIntSet) KeysSorted() []int {
+ if m == nil {
+ return nil
+ }
+ keys := slices.Clone(m.keys)
+ slices.Sort(keys)
+ return m.keys
+}
+
+func (m *OrderedIntSet) String() string {
+ if m == nil {
+ return "[]"
+ }
+ return fmt.Sprintf("%v", m.keys)
+}
+
+func (m *OrderedIntSet) Values() *bitset.BitSet {
+ if m == nil {
+ return nil
+ }
+ return m.values
+}
+
+func (m *OrderedIntSet) IsSuperSet(other *OrderedIntSet) bool {
+ if m == nil || other == nil {
+ return false
+ }
+ return m.values.IsSuperSet(other.values)
+}
+
+// Words returns the bitset as array of 64-bit words, giving direct access to the internal representation.
+// It is not a copy, so changes to the returned slice will affect the bitset.
+// It is meant for advanced users.
+func (m *OrderedIntSet) Words() []uint64 {
+ if m == nil {
+ return nil
+ }
+ return m.values.Words()
+}
diff --git a/common/hmaps/orderedintset_test.go b/common/hmaps/orderedintset_test.go
new file mode 100644
index 00000000000..8bce69b156e
--- /dev/null
+++ b/common/hmaps/orderedintset_test.go
@@ -0,0 +1,117 @@
+// Copyright 2026 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hmaps
+
+import (
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestOrderedIntSet(t *testing.T) {
+ c := qt.New(t)
+
+ m := NewOrderedIntSet(2, 1, 3, 7)
+
+ c.Assert(m.Len(), qt.Equals, 4)
+ c.Assert(m.Has(1), qt.Equals, true)
+ c.Assert(m.Has(4), qt.Equals, false)
+ c.Assert(m.String(), qt.Equals, "[2 1 3 7]")
+ m.Set(4)
+ c.Assert(m.Len(), qt.Equals, 5)
+ c.Assert(m.Has(4), qt.Equals, true)
+ c.Assert(m.Next(0), qt.Equals, 1)
+ c.Assert(m.Next(1), qt.Equals, 1)
+ c.Assert(m.Next(2), qt.Equals, 2)
+ c.Assert(m.Next(3), qt.Equals, 3)
+ c.Assert(m.Next(4), qt.Equals, 4)
+ c.Assert(m.Next(7), qt.Equals, 7)
+ c.Assert(m.Next(8), qt.Equals, -1)
+ c.Assert(m.String(), qt.Equals, "[2 1 3 7 4]")
+
+ var nilset *OrderedIntSet
+ c.Assert(nilset.Len(), qt.Equals, 0)
+ c.Assert(nilset.Has(1), qt.Equals, false)
+ c.Assert(nilset.String(), qt.Equals, "[]")
+
+ var collected []int
+ m.ForEachKey(func(key int) bool {
+ collected = append(collected, key)
+ return true
+ })
+ c.Assert(collected, qt.DeepEquals, []int{2, 1, 3, 7, 4})
+}
+
+func BenchmarkOrderedIntSet(b *testing.B) {
+ smallSet := NewOrderedIntSet()
+ for i := range 8 {
+ smallSet.Set(i)
+ }
+ mediumSet := NewOrderedIntSet()
+ for i := range 64 {
+ mediumSet.Set(i)
+ }
+ largeSet := NewOrderedIntSet()
+ for i := range 1024 {
+ largeSet.Set(i)
+ }
+
+ b.Run("New", func(b *testing.B) {
+ for b.Loop() {
+ NewOrderedIntSet(1, 2, 3, 4, 5, 6, 7, 8)
+ }
+ })
+
+ b.Run("Has small", func(b *testing.B) {
+ for i := 0; b.Loop(); i++ {
+ smallSet.Has(i % 32)
+ }
+ })
+
+ b.Run("Has medium", func(b *testing.B) {
+ for i := 0; b.Loop(); i++ {
+ mediumSet.Has(i % 32)
+ }
+ })
+
+ b.Run("Next", func(b *testing.B) {
+ for i := 0; b.Loop(); i++ {
+ mediumSet.Next(i % 32)
+ }
+ })
+
+ b.Run("ForEachKey small", func(b *testing.B) {
+ for b.Loop() {
+ smallSet.ForEachKey(func(key int) bool {
+ return true
+ })
+ }
+ })
+
+ b.Run("ForEachKey medium", func(b *testing.B) {
+ for b.Loop() {
+ mediumSet.ForEachKey(func(key int) bool {
+ return true
+ })
+ }
+ })
+
+ b.Run("ForEachKey large", func(b *testing.B) {
+ for b.Loop() {
+ largeSet.ForEachKey(func(key int) bool {
+ return true
+ })
+ }
+ })
+}
diff --git a/common/hmaps/params.go b/common/hmaps/params.go
new file mode 100644
index 00000000000..a67a45ca876
--- /dev/null
+++ b/common/hmaps/params.go
@@ -0,0 +1,473 @@
+// Copyright 2026 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hmaps
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+
+ "github.com/spf13/cast"
+)
+
+// Params is a map where all keys are lower case.
+type Params map[string]any
+
+// KeyParams is an utility struct for the WalkParams method.
+type KeyParams struct {
+ Key string
+ Params Params
+}
+
+// GetNested does a lower case and nested search in this map.
+// It will return nil if none found.
+// Make all of these methods internal somehow.
+func (p Params) GetNested(indices ...string) any {
+ v, _, _ := getNested(p, indices)
+ return v
+}
+
+// SetParams overwrites values in dst with values in src for common or new keys.
+// This is done recursively.
+func SetParams(dst, src Params) {
+ setParams(dst, src, 0)
+}
+
+func setParams(dst, src Params, depth int) {
+ const maxDepth = 1000
+ if depth > maxDepth {
+ panic(errors.New("max depth exceeded"))
+ }
+ for k, v := range src {
+ vv, found := dst[k]
+ if !found {
+ dst[k] = v
+ } else {
+ switch vvv := vv.(type) {
+ case Params:
+ if pv, ok := v.(Params); ok {
+ setParams(vvv, pv, depth+1)
+ } else {
+ dst[k] = v
+ }
+ default:
+ dst[k] = v
+ }
+ }
+ }
+}
+
+// IsZero returns true if p is considered empty.
+func (p Params) IsZero() bool {
+ if len(p) == 0 {
+ return true
+ }
+
+ if len(p) > 1 {
+ return false
+ }
+
+ for k := range p {
+ return k == MergeStrategyKey
+ }
+
+ return false
+}
+
+// MergeParamsWithStrategy transfers values from src to dst for new keys using the merge strategy given.
+// This is done recursively.
+func MergeParamsWithStrategy(strategy string, dst, src Params) {
+ dst.merge(ParamsMergeStrategy(strategy), src)
+}
+
+// MergeParams transfers values from src to dst for new keys using the merge encoded in dst.
+// This is done recursively.
+func MergeParams(dst, src Params) {
+ ms, _ := dst.GetMergeStrategy()
+ dst.merge(ms, src)
+}
+
+func (p Params) merge(ps ParamsMergeStrategy, pp Params) {
+ ns, found := p.GetMergeStrategy()
+
+ ms := ns
+ if !found && ps != "" {
+ ms = ps
+ }
+
+ noUpdate := ms == ParamsMergeStrategyNone
+ noUpdate = noUpdate || (ps != "" && ps == ParamsMergeStrategyShallow)
+
+ for k, v := range pp {
+ if k == MergeStrategyKey {
+ continue
+ }
+ vv, found := p[k]
+
+ if found {
+ // Key matches, if both sides are Params, we try to merge.
+ if vvv, ok := vv.(Params); ok {
+ if pv, ok := v.(Params); ok {
+ vvv.merge(ms, pv)
+ }
+ }
+ } else if !noUpdate {
+ p[k] = v
+ }
+
+ }
+}
+
+// For internal use.
+func (p Params) GetMergeStrategy() (ParamsMergeStrategy, bool) {
+ if v, found := p[MergeStrategyKey]; found {
+ if s, ok := v.(ParamsMergeStrategy); ok {
+ return s, true
+ }
+ }
+ return ParamsMergeStrategyShallow, false
+}
+
+// For internal use.
+func (p Params) DeleteMergeStrategy() bool {
+ if _, found := p[MergeStrategyKey]; found {
+ delete(p, MergeStrategyKey)
+ return true
+ }
+ return false
+}
+
+// For internal use.
+func (p Params) SetMergeStrategy(s ParamsMergeStrategy) {
+ switch s {
+ case ParamsMergeStrategyDeep, ParamsMergeStrategyNone, ParamsMergeStrategyShallow:
+ default:
+ panic(fmt.Sprintf("invalid merge strategy %q", s))
+ }
+ p[MergeStrategyKey] = s
+}
+
+func getNested(m map[string]any, indices []string) (any, string, map[string]any) {
+ if len(indices) == 0 {
+ return nil, "", nil
+ }
+
+ first := indices[0]
+ v, found := m[strings.ToLower(cast.ToString(first))]
+ if !found {
+ if len(indices) == 1 {
+ return nil, first, m
+ }
+ return nil, "", nil
+
+ }
+
+ if len(indices) == 1 {
+ return v, first, m
+ }
+
+ switch m2 := v.(type) {
+ case Params:
+ return getNested(m2, indices[1:])
+ case map[string]any:
+ return getNested(m2, indices[1:])
+ default:
+ return nil, "", nil
+ }
+}
+
+// CreateNestedParamsFromSegements creates empty nested maps for the given keySegments in the target map.
+func CreateNestedParamsFromSegements(target Params, keySegments ...string) Params {
+ if len(keySegments) == 0 {
+ return target
+ }
+
+ m := target
+ for i, key := range keySegments {
+ v, found := m[key]
+ if !found {
+ nm := Params{}
+ m[key] = nm
+ m = nm
+ if i == len(keySegments)-1 {
+ return nm
+ }
+ continue
+ }
+ m = v.(Params)
+ }
+
+ return m
+}
+
+// CreateNestedParamsSepString creates empty nested maps for the given keyStr in the target map
+// It returns the last map created.
+func CreateNestedParamsSepString(keyStr, separator string, target Params) Params {
+ keySegments := strings.Split(keyStr, separator)
+ return CreateNestedParamsFromSegements(target, keySegments...)
+}
+
+// SetNestedParamIfNotSet sets the value for the given keyStr in the target map if it does not exist.
+// It assumes that all but the last key in keyStr is a Params map or should be one.
+func SetNestedParamIfNotSet(keyStr, separator string, value any, target Params) Params {
+ keySegments := strings.Split(keyStr, separator)
+ if len(keySegments) == 0 {
+ return target
+ }
+ base := keySegments[:len(keySegments)-1]
+ last := keySegments[len(keySegments)-1]
+
+ m := CreateNestedParamsFromSegements(target, base...)
+
+ if _, ok := m[last]; !ok {
+ m[last] = value
+ }
+
+ return target
+}
+
+// GetNestedParam gets the first match of the keyStr in the candidates given.
+// It will first try the exact match and then try to find it as a nested map value,
+// using the given separator, e.g. "mymap.name".
+// It assumes that all the maps given have lower cased keys.
+func GetNestedParam(keyStr, separator string, candidates ...Params) (any, error) {
+ keyStr = strings.ToLower(keyStr)
+
+ // Try exact match first
+ for _, m := range candidates {
+ if v, ok := m[keyStr]; ok {
+ return v, nil
+ }
+ }
+
+ keySegments := strings.Split(keyStr, separator)
+ for _, m := range candidates {
+ if v := m.GetNested(keySegments...); v != nil {
+ return v, nil
+ }
+ }
+
+ return nil, nil
+}
+
+func GetNestedParamFn(keyStr, separator string, lookupFn func(key string) any) (any, string, map[string]any, error) {
+ keySegments := strings.Split(keyStr, separator)
+ if len(keySegments) == 0 {
+ return nil, "", nil, nil
+ }
+
+ first := lookupFn(keySegments[0])
+ if first == nil {
+ return nil, "", nil, nil
+ }
+
+ if len(keySegments) == 1 {
+ return first, keySegments[0], nil, nil
+ }
+
+ switch m := first.(type) {
+ case map[string]any:
+ v, key, owner := getNested(m, keySegments[1:])
+ return v, key, owner, nil
+ case Params:
+ v, key, owner := getNested(m, keySegments[1:])
+ return v, key, owner, nil
+ }
+
+ return nil, "", nil, nil
+}
+
+// ParamsMergeStrategy tells what strategy to use in Params.Merge.
+type ParamsMergeStrategy string
+
+const (
+ // Do not merge.
+ ParamsMergeStrategyNone ParamsMergeStrategy = "none"
+ // Only add new keys.
+ ParamsMergeStrategyShallow ParamsMergeStrategy = "shallow"
+ // Add new keys, merge existing.
+ ParamsMergeStrategyDeep ParamsMergeStrategy = "deep"
+
+ MergeStrategyKey = "_merge"
+)
+
+// CleanConfigStringMapString removes any processing instructions from m,
+// m will never be modified.
+func CleanConfigStringMapString(m map[string]string) map[string]string {
+ if len(m) == 0 {
+ return m
+ }
+ if _, found := m[MergeStrategyKey]; !found {
+ return m
+ }
+ // Create a new map and copy all the keys except the merge strategy key.
+ m2 := make(map[string]string, len(m)-1)
+ for k, v := range m {
+ if k != MergeStrategyKey {
+ m2[k] = v
+ }
+ }
+ return m2
+}
+
+// CleanConfigStringMap is the same as CleanConfigStringMapString but for
+// map[string]any.
+func CleanConfigStringMap(m map[string]any) map[string]any {
+ return doCleanConfigStringMap(m, 0)
+}
+
+func doCleanConfigStringMap(m map[string]any, depth int) map[string]any {
+ if len(m) == 0 {
+ return m
+ }
+ const maxDepth = 1000
+ if depth > maxDepth {
+ panic(errors.New("max depth exceeded"))
+ }
+ if _, found := m[MergeStrategyKey]; !found {
+ return m
+ }
+ // Create a new map and copy all the keys except the merge strategy key.
+ m2 := make(map[string]any, len(m)-1)
+ for k, v := range m {
+ if k != MergeStrategyKey {
+ m2[k] = v
+ }
+ switch v2 := v.(type) {
+ case map[string]any:
+ m2[k] = doCleanConfigStringMap(v2, depth+1)
+ case Params:
+ var p Params = doCleanConfigStringMap(v2, depth+1)
+ m2[k] = p
+ case map[string]string:
+ m2[k] = CleanConfigStringMapString(v2)
+ }
+
+ }
+ return m2
+}
+
+func toMergeStrategy(v any) ParamsMergeStrategy {
+ s := ParamsMergeStrategy(cast.ToString(v))
+ switch s {
+ case ParamsMergeStrategyDeep, ParamsMergeStrategyNone, ParamsMergeStrategyShallow:
+ return s
+ default:
+ return ParamsMergeStrategyDeep
+ }
+}
+
+// PrepareParams
+// * makes all the keys in the given map lower cased and will do so recursively.
+// * This will modify the map given.
+// * Any nested map[interface{}]interface{}, map[string]interface{},map[string]string will be converted to Params.
+// * Any _merge value will be converted to proper type and value.
+func PrepareParams(m Params) {
+ for k, v := range m {
+ var retyped bool
+ lKey := strings.ToLower(k)
+ if lKey == MergeStrategyKey {
+ v = toMergeStrategy(v)
+ retyped = true
+ } else {
+ switch vv := v.(type) {
+ case map[any]any:
+ var p Params = cast.ToStringMap(v)
+ v = p
+ PrepareParams(p)
+ retyped = true
+ case map[string]any:
+ var p Params = v.(map[string]any)
+ v = p
+ PrepareParams(p)
+ retyped = true
+ case map[string]string:
+ p := make(Params)
+ for k, v := range vv {
+ p[k] = v
+ }
+ v = p
+ PrepareParams(p)
+ retyped = true
+ }
+ }
+
+ if retyped || k != lKey {
+ delete(m, k)
+ m[lKey] = v
+ }
+ }
+}
+
+// CloneParamsDeep does a deep clone of the given Params,
+// meaning that any nested Params will be cloned as well.
+func CloneParamsDeep(m Params) Params {
+ return cloneParamsDeep(m, 0)
+}
+
+func cloneParamsDeep(m Params, depth int) Params {
+ const maxDepth = 1000
+ if depth > maxDepth {
+ panic(errors.New("max depth exceeded"))
+ }
+ m2 := make(Params)
+ for k, v := range m {
+ switch vv := v.(type) {
+ case Params:
+ m2[k] = cloneParamsDeep(vv, depth+1)
+ default:
+ m2[k] = v
+ }
+ }
+ return m2
+}
+
+// PrepareParamsClone is like PrepareParams, but it does not modify the input.
+func PrepareParamsClone(m Params) Params {
+ m2 := make(Params)
+ for k, v := range m {
+ var retyped bool
+ lKey := strings.ToLower(k)
+ if lKey == MergeStrategyKey {
+ v = toMergeStrategy(v)
+ retyped = true
+ } else {
+ switch vv := v.(type) {
+ case map[any]any:
+ var p Params = cast.ToStringMap(v)
+ v = PrepareParamsClone(p)
+ retyped = true
+ case map[string]any:
+ var p Params = v.(map[string]any)
+ v = PrepareParamsClone(p)
+ retyped = true
+ case map[string]string:
+ p := make(Params)
+ for k, v := range vv {
+ p[k] = v
+ }
+ v = p
+ PrepareParams(p)
+ retyped = true
+ }
+ }
+
+ if retyped || k != lKey {
+ m2[lKey] = v
+ } else {
+ m2[k] = v
+ }
+ }
+ return m2
+}
diff --git a/common/hmaps/params_test.go b/common/hmaps/params_test.go
new file mode 100644
index 00000000000..9202a84205b
--- /dev/null
+++ b/common/hmaps/params_test.go
@@ -0,0 +1,201 @@
+// Copyright 2026 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hmaps
+
+import (
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestGetNestedParam(t *testing.T) {
+ m := map[string]any{
+ "string": "value",
+ "first": 1,
+ "with_underscore": 2,
+ "nested": map[string]any{
+ "color": "blue",
+ "nestednested": map[string]any{
+ "color": "green",
+ },
+ },
+ }
+
+ c := qt.New(t)
+
+ must := func(keyStr, separator string, candidates ...Params) any {
+ v, err := GetNestedParam(keyStr, separator, candidates...)
+ c.Assert(err, qt.IsNil)
+ return v
+ }
+
+ c.Assert(must("first", "_", m), qt.Equals, 1)
+ c.Assert(must("First", "_", m), qt.Equals, 1)
+ c.Assert(must("with_underscore", "_", m), qt.Equals, 2)
+ c.Assert(must("nested_color", "_", m), qt.Equals, "blue")
+ c.Assert(must("nested.nestednested.color", ".", m), qt.Equals, "green")
+ c.Assert(must("string.name", ".", m), qt.IsNil)
+ c.Assert(must("nested.foo", ".", m), qt.IsNil)
+}
+
+// https://github.com/gohugoio/hugo/issues/7903
+func TestGetNestedParamFnNestedNewKey(t *testing.T) {
+ c := qt.New(t)
+
+ nested := map[string]any{
+ "color": "blue",
+ }
+ m := map[string]any{
+ "nested": nested,
+ }
+
+ existing, nestedKey, owner, err := GetNestedParamFn("nested.new", ".", func(key string) any {
+ return m[key]
+ })
+
+ c.Assert(err, qt.IsNil)
+ c.Assert(existing, qt.IsNil)
+ c.Assert(nestedKey, qt.Equals, "new")
+ c.Assert(owner, qt.DeepEquals, nested)
+}
+
+func TestParamsSetAndMerge(t *testing.T) {
+ c := qt.New(t)
+
+ createParamsPair := func() (Params, Params) {
+ p1 := Params{"a": "av", "c": "cv", "nested": Params{"al2": "al2v", "cl2": "cl2v"}}
+ p2 := Params{"b": "bv", "a": "abv", "nested": Params{"bl2": "bl2v", "al2": "al2bv"}, MergeStrategyKey: ParamsMergeStrategyDeep}
+ return p1, p2
+ }
+
+ p1, p2 := createParamsPair()
+
+ SetParams(p1, p2)
+
+ c.Assert(p1, qt.DeepEquals, Params{
+ "a": "abv",
+ "c": "cv",
+ "nested": Params{
+ "al2": "al2bv",
+ "cl2": "cl2v",
+ "bl2": "bl2v",
+ },
+ "b": "bv",
+ MergeStrategyKey: ParamsMergeStrategyDeep,
+ })
+
+ p1, p2 = createParamsPair()
+
+ MergeParamsWithStrategy("", p1, p2)
+
+ // Default is to do a shallow merge.
+ c.Assert(p1, qt.DeepEquals, Params{
+ "c": "cv",
+ "nested": Params{
+ "al2": "al2v",
+ "cl2": "cl2v",
+ },
+ "b": "bv",
+ "a": "av",
+ })
+
+ p1, p2 = createParamsPair()
+ p1.SetMergeStrategy(ParamsMergeStrategyNone)
+ MergeParamsWithStrategy("", p1, p2)
+ p1.DeleteMergeStrategy()
+
+ c.Assert(p1, qt.DeepEquals, Params{
+ "a": "av",
+ "c": "cv",
+ "nested": Params{
+ "al2": "al2v",
+ "cl2": "cl2v",
+ },
+ })
+
+ p1, p2 = createParamsPair()
+ p1.SetMergeStrategy(ParamsMergeStrategyShallow)
+ MergeParamsWithStrategy("", p1, p2)
+ p1.DeleteMergeStrategy()
+
+ c.Assert(p1, qt.DeepEquals, Params{
+ "a": "av",
+ "c": "cv",
+ "nested": Params{
+ "al2": "al2v",
+ "cl2": "cl2v",
+ },
+ "b": "bv",
+ })
+
+ p1, p2 = createParamsPair()
+ p1.SetMergeStrategy(ParamsMergeStrategyDeep)
+ MergeParamsWithStrategy("", p1, p2)
+ p1.DeleteMergeStrategy()
+
+ c.Assert(p1, qt.DeepEquals, Params{
+ "nested": Params{
+ "al2": "al2v",
+ "cl2": "cl2v",
+ "bl2": "bl2v",
+ },
+ "b": "bv",
+ "a": "av",
+ "c": "cv",
+ })
+}
+
+func TestParamsIsZero(t *testing.T) {
+ c := qt.New(t)
+
+ var nilParams Params
+
+ c.Assert(Params{}.IsZero(), qt.IsTrue)
+ c.Assert(nilParams.IsZero(), qt.IsTrue)
+ c.Assert(Params{"foo": "bar"}.IsZero(), qt.IsFalse)
+ c.Assert(Params{"_merge": "foo", "foo": "bar"}.IsZero(), qt.IsFalse)
+ c.Assert(Params{"_merge": "foo"}.IsZero(), qt.IsTrue)
+}
+
+func TestSetNestedParamIfNotSet(t *testing.T) {
+ c := qt.New(t)
+
+ m := Params{}
+
+ SetNestedParamIfNotSet("a.b.c", ".", "value", m)
+ c.Assert(m, qt.DeepEquals, Params{
+ "a": Params{
+ "b": Params{
+ "c": "value",
+ },
+ },
+ })
+
+ m = Params{
+ "a": Params{
+ "b": Params{
+ "c": "existingValue",
+ },
+ },
+ }
+
+ SetNestedParamIfNotSet("a.b.c", ".", "value", m)
+ c.Assert(m, qt.DeepEquals, Params{
+ "a": Params{
+ "b": Params{
+ "c": "existingValue",
+ },
+ },
+ })
+}
diff --git a/common/hreflect/convert.go b/common/hreflect/convert.go
new file mode 100644
index 00000000000..e317d4aedb6
--- /dev/null
+++ b/common/hreflect/convert.go
@@ -0,0 +1,222 @@
+// Copyright 2025 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hreflect
+
+import (
+ "fmt"
+ "math"
+ "reflect"
+)
+
+var (
+ typeInt64 = reflect.TypeFor[int64]()
+ typeFloat64 = reflect.TypeFor[float64]()
+ typeString = reflect.TypeFor[string]()
+)
+
+// ToInt64 converts v to int64 if possible, returning an error if not.
+func ToInt64E(v reflect.Value) (int64, error) {
+ if v, ok := ConvertIfPossible(v, typeInt64); ok {
+ return v.Int(), nil
+ }
+ return 0, errConvert(v, "int64")
+}
+
+// ToInt64 converts v to int64 if possible. It panics if the conversion is not possible.
+func ToInt64(v reflect.Value) int64 {
+ vv, err := ToInt64E(v)
+ if err != nil {
+ panic(err)
+ }
+ return vv
+}
+
+// ToFloat64E converts v to float64 if possible, returning an error if not.
+func ToFloat64E(v reflect.Value) (float64, error) {
+ if v, ok := ConvertIfPossible(v, typeFloat64); ok {
+ return v.Float(), nil
+ }
+ return 0, errConvert(v, "float64")
+}
+
+// ToFloat64 converts v to float64 if possible, panicking if not.
+func ToFloat64(v reflect.Value) float64 {
+ vv, err := ToFloat64E(v)
+ if err != nil {
+ panic(err)
+ }
+ return vv
+}
+
+// ToStringE converts v to string if possible, returning an error if not.
+func ToStringE(v reflect.Value) (string, error) {
+ vv, err := ToStringValueE(v)
+ if err != nil {
+ return "", err
+ }
+ return vv.String(), nil
+}
+
+func ToStringValueE(v reflect.Value) (reflect.Value, error) {
+ if v, ok := ConvertIfPossible(v, typeString); ok {
+ return v, nil
+ }
+ return reflect.Value{}, errConvert(v, "string")
+}
+
+// ToString converts v to string if possible, panicking if not.
+func ToString(v reflect.Value) string {
+ vv, err := ToStringE(v)
+ if err != nil {
+ panic(err)
+ }
+ return vv
+}
+
+func errConvert(v reflect.Value, s string) error {
+ return fmt.Errorf("unable to convert value of type %q to %q", v.Type().String(), s)
+}
+
+// ConvertIfPossible tries to convert val to typ if possible.
+// This is currently only implemented for int kinds,
+// added to handle the move to a new YAML library which produces uint64 for unsigned integers.
+// We can expand on this later if needed.
+// This conversion is lossless.
+// See Issue 14079.
+func ConvertIfPossible(val reflect.Value, typ reflect.Type) (reflect.Value, bool) {
+ switch val.Kind() {
+ case reflect.Pointer, reflect.Interface:
+ if val.IsNil() {
+ // Return typ's zero value.
+ return reflect.Zero(typ), true
+ }
+ val = val.Elem()
+ }
+
+ if val.Type().AssignableTo(typ) {
+ // No conversion needed.
+ return val, true
+ }
+
+ if IsInt(typ.Kind()) {
+ return convertToIntIfPossible(val, typ)
+ }
+ if IsFloat(typ.Kind()) {
+ return convertToFloatIfPossible(val, typ)
+ }
+ if IsUint(typ.Kind()) {
+ return convertToUintIfPossible(val, typ)
+ }
+ if IsString(typ.Kind()) && IsString(val.Kind()) {
+ return val.Convert(typ), true
+ }
+
+ return reflect.Value{}, false
+}
+
+func convertToUintIfPossible(val reflect.Value, typ reflect.Type) (reflect.Value, bool) {
+ if IsInt(val.Kind()) {
+ i := val.Int()
+ if i < 0 {
+ return reflect.Value{}, false
+ }
+ u := uint64(i)
+ if typ.OverflowUint(u) {
+ return reflect.Value{}, false
+ }
+ return reflect.ValueOf(u).Convert(typ), true
+ }
+ if IsUint(val.Kind()) {
+ if typ.OverflowUint(val.Uint()) {
+ return reflect.Value{}, false
+ }
+ return val.Convert(typ), true
+ }
+ if IsFloat(val.Kind()) {
+ f := val.Float()
+ if f < 0 || f > float64(math.MaxUint64) {
+ return reflect.Value{}, false
+ }
+ if f != math.Trunc(f) {
+ return reflect.Value{}, false
+ }
+ u := uint64(f)
+ if typ.OverflowUint(u) {
+ return reflect.Value{}, false
+ }
+ return reflect.ValueOf(u).Convert(typ), true
+ }
+ return reflect.Value{}, false
+}
+
+func convertToFloatIfPossible(val reflect.Value, typ reflect.Type) (reflect.Value, bool) {
+ if IsInt(val.Kind()) {
+ i := val.Int()
+ f := float64(i)
+ if typ.OverflowFloat(f) {
+ return reflect.Value{}, false
+ }
+ return reflect.ValueOf(f).Convert(typ), true
+ }
+ if IsUint(val.Kind()) {
+ u := val.Uint()
+ f := float64(u)
+ if typ.OverflowFloat(f) {
+ return reflect.Value{}, false
+ }
+ return reflect.ValueOf(f).Convert(typ), true
+ }
+ if IsFloat(val.Kind()) {
+ if typ.OverflowFloat(val.Float()) {
+ return reflect.Value{}, false
+ }
+ return val.Convert(typ), true
+ }
+
+ return reflect.Value{}, false
+}
+
+func convertToIntIfPossible(val reflect.Value, typ reflect.Type) (reflect.Value, bool) {
+ if IsInt(val.Kind()) {
+ if typ.OverflowInt(val.Int()) {
+ return reflect.Value{}, false
+ }
+ return val.Convert(typ), true
+ }
+ if IsUint(val.Kind()) {
+ if val.Uint() > uint64(math.MaxInt64) {
+ return reflect.Value{}, false
+ }
+ if typ.OverflowInt(int64(val.Uint())) {
+ return reflect.Value{}, false
+ }
+ return val.Convert(typ), true
+ }
+ if IsFloat(val.Kind()) {
+ f := val.Float()
+ if f < float64(math.MinInt64) || f > float64(math.MaxInt64) {
+ return reflect.Value{}, false
+ }
+ if f != math.Trunc(f) {
+ return reflect.Value{}, false
+ }
+ if typ.OverflowInt(int64(f)) {
+ return reflect.Value{}, false
+ }
+ return reflect.ValueOf(int64(f)).Convert(typ), true
+
+ }
+
+ return reflect.Value{}, false
+}
diff --git a/common/hreflect/convert_test.go b/common/hreflect/convert_test.go
new file mode 100644
index 00000000000..c64526ef61e
--- /dev/null
+++ b/common/hreflect/convert_test.go
@@ -0,0 +1,284 @@
+// Copyright 2025 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hreflect
+
+import (
+ "math"
+ "reflect"
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+ "github.com/gohugoio/hugo/htesting/hqt"
+)
+
+func TestToFuncs(t *testing.T) {
+ c := qt.New(t)
+
+ c.Assert(ToInt64(reflect.ValueOf(int(42))), qt.Equals, int64(42))
+ c.Assert(ToFloat64(reflect.ValueOf(float32(3.14))), hqt.IsSameFloat64, float64(3.14))
+ c.Assert(ToString(reflect.ValueOf("hello")), qt.Equals, "hello")
+}
+
+func TestConvertIfPossible(t *testing.T) {
+ c := qt.New(t)
+
+ for _, test := range []struct {
+ name string
+ value any
+ typ any
+ expected any
+ ok bool
+ }{
+ // From uint to int.
+ {
+ name: "uint64(math.MaxUint64) to int16",
+ value: uint64(math.MaxUint64),
+ typ: int16(0),
+ ok: false, // overflow
+ },
+
+ {
+ name: "uint64(math.MaxUint64) to int64",
+ value: uint64(math.MaxUint64),
+ typ: int64(0),
+ ok: false, // overflow
+ },
+ {
+ name: "uint64(math.MaxInt16) to int16",
+ value: uint64(math.MaxInt16),
+ typ: int64(0),
+ ok: true,
+ expected: int64(math.MaxInt16),
+ },
+ // From int to int.
+ {
+ name: "int64(math.MaxInt64) to int16",
+ value: int64(math.MaxInt64),
+ typ: int16(0),
+ ok: false, // overflow
+ },
+ {
+ name: "int64(math.MaxInt16) to int",
+ value: int64(math.MaxInt16),
+ typ: int(0),
+ ok: true,
+ expected: int(math.MaxInt16),
+ },
+
+ {
+ name: "int64(math.MaxInt16) to int",
+ value: int64(math.MaxInt16),
+ typ: int(0),
+ ok: true,
+ expected: int(math.MaxInt16),
+ },
+ // From float64 to int.
+ {
+ name: "float64(1.5) to int",
+ value: float64(1.5),
+ typ: int(0),
+ ok: false, // loss of precision
+ },
+ {
+ name: "float64(1.0) to int",
+ value: float64(1.0),
+ typ: int(0),
+ ok: true,
+ expected: int(1),
+ },
+ {
+ name: "float64(math.MaxInt16+1) to int16",
+ value: float64(math.MaxInt16 + 1),
+ typ: int16(0),
+ ok: false, // overflow
+ },
+ {
+ name: "float64(math.MaxFloat64) to int64",
+ value: float64(math.MaxFloat64),
+ typ: int64(0),
+ ok: false, // overflow
+ },
+ {
+ name: "float64(32767) to int16",
+ value: float64(32767),
+ typ: int16(0),
+ ok: true,
+ expected: int16(32767),
+ },
+ // From float32 to int.
+ {
+ name: "float32(1.5) to int",
+ value: float32(1.5),
+ typ: int(0),
+ ok: false, // loss of precision
+ },
+ {
+ name: "float32(1.0) to int",
+ value: float32(1.0),
+ typ: int(0),
+ ok: true,
+ expected: int(1),
+ },
+ {
+ name: "float32(math.MaxFloat32) to int16",
+ value: float32(math.MaxFloat32),
+ typ: int16(0),
+ ok: false, // overflow
+ },
+ {
+ name: "float32(math.MaxFloat32) to int64",
+ value: float32(math.MaxFloat32),
+ typ: int64(0),
+ ok: false, // overflow
+ },
+ {
+ name: "float32(math.MaxInt16) to int16",
+ value: float32(math.MaxInt16),
+ typ: int16(0),
+ ok: true,
+ expected: int16(32767),
+ },
+ {
+ name: "float32(math.MaxInt16+1) to int16",
+ value: float32(math.MaxInt16 + 1),
+ typ: int16(0),
+ ok: false, // overflow
+ },
+ // Int to float.
+ {
+ name: "int16(32767) to float32",
+ value: int16(32767),
+ typ: float32(0),
+ ok: true,
+ expected: float32(32767),
+ },
+ {
+ name: "int64(32767) to float32",
+ value: int64(32767),
+ typ: float32(0),
+ ok: true,
+ expected: float32(32767),
+ },
+ {
+ name: "int64(math.MaxInt64) to float32",
+ value: int64(math.MaxInt64),
+ typ: float32(0),
+ ok: true,
+ expected: float32(math.MaxInt64),
+ },
+ {
+ name: "int64(math.MaxInt64) to float64",
+ value: int64(math.MaxInt64),
+ typ: float64(0),
+ ok: true,
+ expected: float64(math.MaxInt64),
+ },
+ // Int to uint.
+ {
+ name: "int16(32767) to uint16",
+ value: int16(32767),
+ typ: uint16(0),
+ ok: true,
+ expected: uint16(32767),
+ },
+ {
+ name: "int16(32767) to uint8",
+ value: int16(32767),
+ typ: uint8(0),
+ ok: false,
+ },
+ {
+ name: "float64(3.14) to uint64",
+ value: float64(3.14),
+ typ: uint64(0),
+ ok: false,
+ },
+ {
+ name: "float64(3.0) to uint64",
+ value: float64(3.0),
+ typ: uint64(0),
+ ok: true,
+ expected: uint64(3),
+ },
+ // From uint to float.
+ {
+ name: "uint64(math.MaxInt16) to float64",
+ value: uint64(math.MaxInt16),
+ typ: float64(0),
+ ok: true,
+ expected: float64(math.MaxInt16),
+ },
+ // Float to float.
+ {
+ name: "float64(3.14) to float32",
+ value: float64(3.14),
+ typ: float32(0),
+ ok: true,
+ expected: float32(3.14),
+ },
+ {
+ name: "float32(3.14) to float64",
+ value: float32(3.14),
+ typ: float64(0),
+ ok: true,
+ expected: float64(3.14),
+ },
+ {
+ name: "float64(3.14) to float64",
+ value: float64(3.14),
+ typ: float64(0),
+ ok: true,
+ expected: float64(3.14),
+ },
+ } {
+
+ v, ok := ConvertIfPossible(reflect.ValueOf(test.value), reflect.TypeOf(test.typ))
+ c.Assert(ok, qt.Equals, test.ok, qt.Commentf("test case: %s", test.name))
+ if test.ok {
+ c.Assert(v.Interface(), hqt.IsSameNumber, test.expected, qt.Commentf("test case: %s", test.name))
+ }
+ }
+}
+
+func TestConvertIfPossibleMisc(t *testing.T) {
+ c := qt.New(t)
+ type s string
+
+ var (
+ i = int32(42)
+ i64 = int64(i)
+ iv any = i
+ ip = &i
+ inil any = (*int32)(nil)
+ shello = s("hello")
+ )
+
+ convertOK := func(v any, typ any) any {
+ rv, ok := ConvertIfPossible(reflect.ValueOf(v), reflect.TypeOf(typ))
+ c.Assert(ok, qt.IsTrue)
+ return rv.Interface()
+ }
+
+ c.Assert(convertOK(shello, ""), qt.Equals, "hello")
+ c.Assert(convertOK(ip, int64(0)), qt.Equals, i64)
+ c.Assert(convertOK(iv, int64(0)), qt.Equals, i64)
+ c.Assert(convertOK(inil, int64(0)), qt.Equals, int64(0))
+}
+
+func BenchmarkToInt64(b *testing.B) {
+ v := reflect.ValueOf(int(42))
+ for b.Loop() {
+ ToInt64(v)
+ }
+}
diff --git a/common/hreflect/helpers.go b/common/hreflect/helpers.go
index 0cc42ecc5b2..802f0e59ca9 100644
--- a/common/hreflect/helpers.go
+++ b/common/hreflect/helpers.go
@@ -1,6 +1,4 @@
-// Copyright 2019 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.
+// Copyright 2025 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -17,11 +15,21 @@
package hreflect
import (
+ "context"
"reflect"
+ "sync"
+ "time"
+ "github.com/gohugoio/hugo/common/hmaps"
+ "github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/types"
)
+// IsInterfaceOrPointer returns whether the given kind is an interface or a pointer.
+func IsInterfaceOrPointer(kind reflect.Kind) bool {
+ return kind == reflect.Interface || kind == reflect.Pointer
+}
+
// TODO(bep) replace the private versions in /tpl with these.
// IsNumber returns whether the given kind is a number.
func IsNumber(kind reflect.Kind) bool {
@@ -58,9 +66,14 @@ func IsFloat(kind reflect.Kind) bool {
}
}
+// IsString returns whether the given kind is a string.
+func IsString(kind reflect.Kind) bool {
+ return kind == reflect.String
+}
+
// IsTruthful returns whether in represents a truthful value.
// See IsTruthfulValue
-func IsTruthful(in interface{}) bool {
+func IsTruthful(in any) bool {
switch v := in.(type) {
case reflect.Value:
return IsTruthfulValue(v)
@@ -69,7 +82,29 @@ func IsTruthful(in interface{}) bool {
}
}
-var zeroType = reflect.TypeOf((*types.Zeroer)(nil)).Elem()
+// IsMap reports whether v is a map.
+func IsMap(v any) bool {
+ return reflect.ValueOf(v).Kind() == reflect.Map
+}
+
+// IsSlice reports whether v is a slice.
+func IsSlice(v any) bool {
+ return reflect.ValueOf(v).Kind() == reflect.Slice
+}
+
+var zeroType = reflect.TypeFor[types.Zeroer]()
+
+var isZeroCache sync.Map
+
+func implementsIsZero(tp reflect.Type) bool {
+ v, ok := isZeroCache.Load(tp)
+ if ok {
+ return v.(bool)
+ }
+ implements := tp.Implements(zeroType)
+ isZeroCache.Store(tp, implements)
+ return implements
+}
// IsTruthfulValue returns whether the given value has a meaningful truth value.
// This is based on template.IsTrue in Go's stdlib, but also considers
@@ -79,14 +114,18 @@ var zeroType = reflect.TypeOf((*types.Zeroer)(nil)).Elem()
// Based on:
// https://github.com/golang/go/blob/178a2c42254166cffed1b25fb1d3c7a5727cada6/src/text/template/exec.go#L306
func IsTruthfulValue(val reflect.Value) (truth bool) {
- val = indirectInterface(val)
+ val, isNil := Indirect(val)
if !val.IsValid() {
- // Something like var x interface{}, never set. It's a form of nil.
+ // Something like: var x any, never set. It's a form of nil.
+ return
+ }
+
+ if val.Kind() == reflect.Pointer && isNil {
return
}
- if val.Type().Implements(zeroType) {
+ if implementsIsZero(val.Type()) {
return !val.Interface().(types.Zeroer).IsZero()
}
@@ -97,7 +136,7 @@ func IsTruthfulValue(val reflect.Value) (truth bool) {
truth = val.Bool()
case reflect.Complex64, reflect.Complex128:
truth = val.Complex() != 0
- case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Interface:
+ case reflect.Chan, reflect.Func, reflect.Pointer, reflect.UnsafePointer, reflect.Interface:
truth = !val.IsNil()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
truth = val.Int() != 0
@@ -114,13 +153,225 @@ func IsTruthfulValue(val reflect.Value) (truth bool) {
return
}
-// Based on: https://github.com/golang/go/blob/178a2c42254166cffed1b25fb1d3c7a5727cada6/src/text/template/exec.go#L931
-func indirectInterface(v reflect.Value) reflect.Value {
- if v.Kind() != reflect.Interface {
- return v
+type methodKey struct {
+ typ reflect.Type
+ name string
+}
+
+var (
+ methodIndexCache sync.Map
+ methodCache sync.Map
+)
+
+// GetMethodByNameForType returns the method with the given name for the given type,
+// or a zero Method if no such method exists.
+// It panics if tp is an interface type.
+// It caches the lookup.
+func GetMethodByNameForType(tp reflect.Type, name string) reflect.Method {
+ if tp.Kind() == reflect.Interface {
+ // Func field is nil for interface types.
+ panic("not supported for interface types")
+ }
+ k := methodKey{tp, name}
+ v, found := methodCache.Load(k)
+ if found {
+ return v.(reflect.Method)
}
- if v.IsNil() {
+ m, _ := tp.MethodByName(name)
+ methodCache.Store(k, m)
+ return m
+}
+
+// GetMethodByName is the same as reflect.Value.MethodByName, but it caches the lookup.
+func GetMethodByName(v reflect.Value, name string) reflect.Value {
+ index := GetMethodIndexByName(v.Type(), name)
+
+ if index == -1 {
return reflect.Value{}
}
- return v.Elem()
+
+ return v.Method(index)
+}
+
+// GetMethodIndexByName returns the index of the method with the given name, or
+// -1 if no such method exists.
+func GetMethodIndexByName(tp reflect.Type, name string) int {
+ k := methodKey{tp, name}
+ v, found := methodIndexCache.Load(k)
+ if found {
+ return v.(int)
+ }
+ m, ok := tp.MethodByName(name)
+ index := m.Index
+ if !ok {
+ index = -1
+ }
+ methodIndexCache.Store(k, index)
+
+ if !ok {
+ return -1
+ }
+
+ return m.Index
+}
+
+var (
+ timeType = reflect.TypeFor[time.Time]()
+ asTimeProviderType = reflect.TypeFor[htime.AsTimeProvider]()
+)
+
+// IsTime returns whether tp is a time.Time type or if it can be converted into one
+// in ToTime.
+func IsTime(tp reflect.Type) bool {
+ if tp == timeType {
+ return true
+ }
+
+ if tp.Implements(asTimeProviderType) {
+ return true
+ }
+ return false
+}
+
+// IsValid returns whether v is not nil and a valid value.
+func IsValid(v reflect.Value) bool {
+ if !v.IsValid() {
+ return false
+ }
+
+ switch v.Kind() {
+ case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice:
+ return !v.IsNil()
+ }
+
+ return true
+}
+
+// AsTime returns v as a time.Time if possible.
+// The given location is only used if the value implements AsTimeProvider (e.g. go-toml local).
+// A zero Time and false is returned if this isn't possible.
+// Note that this function does not accept string dates.
+func AsTime(v reflect.Value, loc *time.Location) (time.Time, bool) {
+ if v.Kind() == reflect.Interface {
+ return AsTime(v.Elem(), loc)
+ }
+
+ if v.Type() == timeType {
+ return v.Interface().(time.Time), true
+ }
+
+ if v.Type().Implements(asTimeProviderType) {
+ return v.Interface().(htime.AsTimeProvider).AsTime(loc), true
+ }
+
+ 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 := range vvv.Len() {
+ out[i] = vvv.Index(i).Interface()
+ }
+ return out, true
+ }
+ }
+ return nil, false
+}
+
+// CallMethodByName calls the method with the given name on v.
+func CallMethodByName(cxt context.Context, name string, v reflect.Value) []reflect.Value {
+ fn := v.MethodByName(name)
+ var args []reflect.Value
+ tp := fn.Type()
+ if tp.NumIn() > 0 {
+ if tp.NumIn() > 1 {
+ panic("not supported")
+ }
+ first := tp.In(0)
+ if IsContextType(first) {
+ args = append(args, reflect.ValueOf(cxt))
+ }
+ }
+
+ return fn.Call(args)
+}
+
+// Indirect unwraps interfaces and pointers until it finds a non-interface/pointer value.
+// If a nil is encountered, the second return value is true.
+// If a pointer to a struct is encountered, it is not unwrapped.
+func Indirect(v reflect.Value) (vv reflect.Value, isNil bool) {
+ for ; IsInterfaceOrPointer(v.Kind()); v = v.Elem() {
+ if IsNil(v) {
+ return v, true
+ }
+ if v.Kind() != reflect.Interface {
+ // A pointer.
+ if v.NumMethod() > 0 {
+ break
+ }
+ if v.Elem().Kind() == reflect.Struct {
+ // Avoid unwrapping pointers to structs.
+ break
+ }
+ }
+ }
+ return v, false
+}
+
+// IndirectElem is like Indirect, but if the final value is a pointer, it unwraps it.
+func IndirectElem(v reflect.Value) (vv reflect.Value, isNil bool) {
+ vv, isNil = Indirect(v)
+ if isNil {
+ return vv, isNil
+ }
+ if vv.Kind() == reflect.Pointer {
+ vv = vv.Elem()
+ }
+ return vv, isNil
+}
+
+// IsNil reports whether v is nil.
+// Based on reflect.Value.IsNil, but also considers invalid values as nil.
+func IsNil(v reflect.Value) bool {
+ if !v.IsValid() {
+ return true
+ }
+ switch v.Kind() {
+ case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice:
+ return v.IsNil()
+ }
+ return false
+}
+
+var contextInterface = reflect.TypeFor[context.Context]()
+
+var isContextCache = hmaps.NewCache[reflect.Type, bool]()
+
+type k string
+
+var contextTypeValue = reflect.TypeOf(context.WithValue(context.Background(), k("key"), 32))
+
+// IsContextType returns whether tp is a context.Context type.
+func IsContextType(tp reflect.Type) bool {
+ if tp == contextTypeValue {
+ return true
+ }
+ if tp == contextInterface {
+ return true
+ }
+
+ isContext, _ := isContextCache.GetOrCreate(tp, func() (bool, error) {
+ return tp.Implements(contextInterface), nil
+ })
+ return isContext
}
diff --git a/common/hreflect/helpers_test.go b/common/hreflect/helpers_test.go
index 480ccb27ac7..fb5a06d27f5 100644
--- a/common/hreflect/helpers_test.go
+++ b/common/hreflect/helpers_test.go
@@ -1,4 +1,4 @@
-// Copyright 2019 The Hugo Authors. All rights reserved.
+// Copyright 2025 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,29 +14,247 @@
package hreflect
import (
+ "context"
"reflect"
"testing"
"time"
qt "github.com/frankban/quicktest"
+ "github.com/gohugoio/hugo/htesting/hqt"
)
+type zeroStruct struct {
+ zero bool
+}
+
+func (z zeroStruct) IsZero() bool {
+ return z.zero
+}
+
func TestIsTruthful(t *testing.T) {
c := qt.New(t)
+ var nilpointerZero *zeroStruct
+
c.Assert(IsTruthful(true), qt.Equals, true)
c.Assert(IsTruthful(false), qt.Equals, false)
c.Assert(IsTruthful(time.Now()), qt.Equals, true)
c.Assert(IsTruthful(time.Time{}), qt.Equals, false)
+ c.Assert(IsTruthful(&zeroStruct{zero: false}), qt.Equals, true)
+ c.Assert(IsTruthful(&zeroStruct{zero: true}), qt.Equals, false)
+ c.Assert(IsTruthful(zeroStruct{zero: false}), qt.Equals, true)
+ c.Assert(IsTruthful(zeroStruct{zero: true}), qt.Equals, false)
+ c.Assert(IsTruthful(nil), qt.Equals, false)
+ c.Assert(IsTruthful(nilpointerZero), qt.Equals, false)
}
-func BenchmarkIsTruthFul(b *testing.B) {
- v := reflect.ValueOf("Hugo")
+func TestGetMethodByName(t *testing.T) {
+ c := qt.New(t)
+ v := reflect.ValueOf(&testStruct{})
+ tp := v.Type()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- if !IsTruthfulValue(v) {
- b.Fatal("not truthful")
+ c.Assert(GetMethodIndexByName(tp, "Method1"), qt.Equals, 0)
+ c.Assert(GetMethodIndexByName(tp, "Method3"), qt.Equals, 2)
+ c.Assert(GetMethodIndexByName(tp, "Foo"), qt.Equals, -1)
+}
+
+func TestIsContextType(t *testing.T) {
+ c := qt.New(t)
+ type k string
+ ctx := context.Background()
+ valueCtx := context.WithValue(ctx, k("key"), 32)
+ c.Assert(IsContextType(reflect.TypeOf(ctx)), qt.IsTrue)
+ 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})
+}
+
+type testIndirectStruct struct {
+ S string
+}
+
+func (t *testIndirectStruct) GetS() string {
+ return t.S
+}
+
+func (t testIndirectStruct) Foo() string {
+ return "bar"
+}
+
+type testIndirectStructNoMethods struct {
+ S string
+}
+
+func TestIsNil(t *testing.T) {
+ c := qt.New(t)
+
+ var (
+ nilPtr *testIndirectStruct
+ nilIface any = nil
+ nonNilIface any = &testIndirectStruct{S: "hello"}
+ )
+
+ c.Assert(IsNil(reflect.ValueOf(nilPtr)), qt.Equals, true)
+ c.Assert(IsNil(reflect.ValueOf(nilIface)), qt.Equals, true)
+ c.Assert(IsNil(reflect.ValueOf(nonNilIface)), qt.Equals, false)
+}
+
+func TestIndirectInterface(t *testing.T) {
+ c := qt.New(t)
+
+ var (
+ structWithMethods = testIndirectStruct{S: "hello"}
+ structWithMethodsPointer = &testIndirectStruct{S: "hello"}
+ structWithMethodsPointerAny any = structWithMethodsPointer
+ structPointerToPointer = &structWithMethodsPointer
+ structNoMethodsPtr = &testIndirectStructNoMethods{S: "no methods"}
+ structNoMethods = testIndirectStructNoMethods{S: "no methods"}
+ intValue = 32
+ intPtr = &intValue
+ nilPtr *testIndirectStruct
+ nilIface any = nil
+ )
+
+ ind := func(v any) any {
+ c.Helper()
+ vv, isNil := Indirect(reflect.ValueOf(v))
+ c.Assert(isNil, qt.IsFalse)
+ return vv.Interface()
+ }
+
+ c.Assert(ind(intValue), hqt.IsSameType, 32)
+ c.Assert(ind(intPtr), hqt.IsSameType, 32)
+ c.Assert(ind(structNoMethodsPtr), hqt.IsSameType, structNoMethodsPtr)
+ c.Assert(ind(structWithMethods), hqt.IsSameType, structWithMethods)
+ c.Assert(ind(structNoMethods), hqt.IsSameType, structNoMethods)
+ c.Assert(ind(structPointerToPointer), hqt.IsSameType, &testIndirectStruct{})
+ c.Assert(ind(structWithMethodsPointer), hqt.IsSameType, &testIndirectStruct{})
+ c.Assert(ind(structWithMethodsPointerAny), hqt.IsSameType, structWithMethodsPointer)
+
+ vv, isNil := Indirect(reflect.ValueOf(nilPtr))
+ c.Assert(isNil, qt.IsTrue)
+ c.Assert(vv, qt.Equals, reflect.ValueOf(nilPtr))
+
+ vv, isNil = Indirect(reflect.ValueOf(nilIface))
+ c.Assert(isNil, qt.IsFalse)
+ c.Assert(vv, qt.Equals, reflect.ValueOf(nilIface))
+}
+
+func BenchmarkIsContextType(b *testing.B) {
+ const size = 1000
+ type k string
+ b.Run("value", func(b *testing.B) {
+ ctx := context.Background()
+ ctxs := make([]reflect.Type, size)
+ for i := range size {
+ ctxs[i] = reflect.TypeOf(context.WithValue(ctx, k("key"), i))
+ }
+
+ for i := 0; b.Loop(); i++ {
+ idx := i % size
+ if !IsContextType(ctxs[idx]) {
+ b.Fatal("not context")
+ }
+ }
+ })
+
+ b.Run("background", func(b *testing.B) {
+ var ctxt reflect.Type = reflect.TypeOf(context.Background())
+ for b.Loop() {
+ if !IsContextType(ctxt) {
+ b.Fatal("not context")
+ }
+ }
+ })
+}
+
+func BenchmarkIsTruthFulValue(b *testing.B) {
+ var (
+ stringHugo = reflect.ValueOf("Hugo")
+ stringEmpty = reflect.ValueOf("")
+ zero = reflect.ValueOf(time.Time{})
+ timeNow = reflect.ValueOf(time.Now())
+ boolTrue = reflect.ValueOf(true)
+ boolFalse = reflect.ValueOf(false)
+ nilPointer = reflect.ValueOf((*zeroStruct)(nil))
+ )
+
+ for b.Loop() {
+ IsTruthfulValue(stringHugo)
+ IsTruthfulValue(stringEmpty)
+ IsTruthfulValue(zero)
+ IsTruthfulValue(timeNow)
+ IsTruthfulValue(boolTrue)
+ IsTruthfulValue(boolFalse)
+ IsTruthfulValue(nilPointer)
+ }
+}
+
+type testStruct struct{}
+
+func (t *testStruct) Method1() string {
+ return "Hugo"
+}
+
+func (t *testStruct) Method2() string {
+ return "Hugo"
+}
+
+func (t *testStruct) Method3() string {
+ return "Hugo"
+}
+
+func (t *testStruct) Method4() string {
+ return "Hugo"
+}
+
+func (t *testStruct) Method5() string {
+ return "Hugo"
+}
+
+func BenchmarkGetMethodByNameForType(b *testing.B) {
+ tp := reflect.TypeFor[*testStruct]()
+ methods := []string{"Method1", "Method2", "Method3", "Method4", "Method5"}
+
+ for b.Loop() {
+ for _, method := range methods {
+ _ = GetMethodByNameForType(tp, method)
+ }
+ }
+}
+
+func BenchmarkGetMethodByName(b *testing.B) {
+ v := reflect.ValueOf(&testStruct{})
+ methods := []string{"Method1", "Method2", "Method3", "Method4", "Method5"}
+
+ for b.Loop() {
+ for _, method := range methods {
+ _ = GetMethodByName(v, method)
}
}
}
+
+func BenchmarkGetMethodByNamePara(b *testing.B) {
+ v := reflect.ValueOf(&testStruct{})
+ methods := []string{"Method1", "Method2", "Method3", "Method4", "Method5"}
+
+ b.ResetTimer()
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ for _, method := range methods {
+ _ = GetMethodByName(v, method)
+ }
+ }
+ })
+}
diff --git a/common/hstore/scratch.go b/common/hstore/scratch.go
new file mode 100644
index 00000000000..4b7a14bd166
--- /dev/null
+++ b/common/hstore/scratch.go
@@ -0,0 +1,160 @@
+// Copyright 2025 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hstore
+
+import (
+ "reflect"
+ "sort"
+ "sync"
+
+ "github.com/gohugoio/hugo/common/collections"
+ "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
+type Scratch struct {
+ values map[string]any
+ mu sync.RWMutex
+}
+
+// Add will, for single values, add (using the + operator) the addend to the existing addend (if found).
+// Supports numeric values and strings.
+//
+// If the first add for a key is an array or slice, then the next value(s) will be appended.
+func (c *Scratch) Add(key string, newAddend any) (string, error) {
+ var newVal any
+ c.mu.RLock()
+ existingAddend, found := c.values[key]
+ c.mu.RUnlock()
+ if found {
+ var err error
+
+ addendV := reflect.TypeOf(existingAddend)
+
+ if addendV.Kind() == reflect.Slice || addendV.Kind() == reflect.Array {
+ newVal, err = collections.Append(existingAddend, newAddend)
+ if err != nil {
+ return "", err
+ }
+ } else {
+ newVal, err = math.DoArithmetic(existingAddend, newAddend, '+')
+ if err != nil {
+ return "", err
+ }
+ }
+ } else {
+ newVal = newAddend
+ }
+ c.mu.Lock()
+ c.values[key] = newVal
+ c.mu.Unlock()
+ return "", nil // have to return something to make it work with the Go templates
+}
+
+// Set stores a value with the given key in the Node context.
+// This value can later be retrieved with Get.
+func (c *Scratch) Set(key string, value any) string {
+ c.mu.Lock()
+ c.values[key] = value
+ c.mu.Unlock()
+ return ""
+}
+
+// Delete deletes the given key.
+func (c *Scratch) Delete(key string) string {
+ c.mu.Lock()
+ delete(c.values, key)
+ c.mu.Unlock()
+ return ""
+}
+
+// Get returns a value previously set by Add or Set.
+func (c *Scratch) Get(key string) any {
+ c.mu.RLock()
+ val := c.values[key]
+ c.mu.RUnlock()
+
+ return val
+}
+
+// Values returns the raw backing map. Note that you should just use
+// this method on the locally scoped Scratch instances you obtain via newScratch, not
+// .Page.Scratch etc., as that will lead to concurrency issues.
+func (c *Scratch) Values() map[string]any {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+ return c.values
+}
+
+// SetInMap stores a value to a map with the given key in the Node context.
+// This map can later be retrieved with GetSortedMapValues.
+func (c *Scratch) SetInMap(key string, mapKey string, value any) string {
+ c.mu.Lock()
+ _, found := c.values[key]
+ if !found {
+ c.values[key] = make(map[string]any)
+ }
+
+ c.values[key].(map[string]any)[mapKey] = value
+ c.mu.Unlock()
+ return ""
+}
+
+// DeleteInMap deletes a value to a map with the given key in the Node context.
+func (c *Scratch) DeleteInMap(key string, mapKey string) string {
+ c.mu.Lock()
+ _, found := c.values[key]
+ if found {
+ delete(c.values[key].(map[string]any), mapKey)
+ }
+ c.mu.Unlock()
+ return ""
+}
+
+// GetSortedMapValues returns a sorted map previously filled with SetInMap.
+func (c *Scratch) GetSortedMapValues(key string) any {
+ c.mu.RLock()
+
+ if c.values[key] == nil {
+ c.mu.RUnlock()
+ return nil
+ }
+
+ unsortedMap := c.values[key].(map[string]any)
+ c.mu.RUnlock()
+ var keys []string
+ for mapKey := range unsortedMap {
+ keys = append(keys, mapKey)
+ }
+
+ sort.Strings(keys)
+
+ sortedArray := make([]any, len(unsortedMap))
+ for i, mapKey := range keys {
+ sortedArray[i] = unsortedMap[mapKey]
+ }
+
+ return sortedArray
+}
+
+// NewScratch returns a new instance of Scratch.
+func NewScratch() *Scratch {
+ return &Scratch{values: make(map[string]any)}
+}
diff --git a/common/maps/scratch_test.go b/common/hstore/scratch_test.go
similarity index 90%
rename from common/maps/scratch_test.go
rename to common/hstore/scratch_test.go
index 96b352572b4..1f08d46a92e 100644
--- a/common/maps/scratch_test.go
+++ b/common/hstore/scratch_test.go
@@ -1,4 +1,4 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
+// Copyright 2025 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package maps
+package hstore
import (
"reflect"
@@ -90,7 +90,7 @@ func TestScratchAddTypedSliceToInterfaceSlice(t *testing.T) {
c := qt.New(t)
scratch := NewScratch()
- scratch.Set("slice", []interface{}{})
+ scratch.Set("slice", []any{})
_, err := scratch.Add("slice", []int{1, 2})
c.Assert(err, qt.IsNil)
@@ -107,7 +107,7 @@ func TestScratchAddDifferentTypedSliceToInterfaceSlice(t *testing.T) {
_, err := scratch.Add("slice", []int{1, 2})
c.Assert(err, qt.IsNil)
- c.Assert(scratch.Get("slice"), qt.DeepEquals, []interface{}{"foo", 1, 2})
+ c.Assert(scratch.Get("slice"), qt.DeepEquals, []any{"foo", 1, 2})
}
func TestScratchSet(t *testing.T) {
@@ -140,7 +140,7 @@ func TestScratchInParallel(t *testing.T) {
for i := 1; i <= 10; i++ {
wg.Add(1)
go func(j int) {
- for k := 0; k < 10; k++ {
+ for k := range 10 {
newVal := int64(k + j)
_, err := scratch.Add(key, newVal)
@@ -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, []interface{}{0: "Abc (updated)", 1: "Def", 2: "Lux", 3: "Zyx"})
+ c.Assert(scratch.GetSortedMapValues("key"), qt.DeepEquals, any([]any{"Abc (updated)", "Def", "Lux", "Zyx"}))
}
func TestScratchDeleteInMap(t *testing.T) {
@@ -199,7 +199,7 @@ func TestScratchDeleteInMap(t *testing.T) {
scratch.DeleteInMap("key", "abc")
scratch.SetInMap("key", "def", "Def")
scratch.DeleteInMap("key", "lmn") // Do nothing
- c.Assert(scratch.GetSortedMapValues("key"), qt.DeepEquals, []interface{}{0: "Def", 1: "Lux", 2: "Zyx"})
+ c.Assert(scratch.GetSortedMapValues("key"), qt.DeepEquals, any([]any{"Def", "Lux", "Zyx"}))
}
func TestScratchGetSortedMapValues(t *testing.T) {
@@ -214,8 +214,8 @@ func TestScratchGetSortedMapValues(t *testing.T) {
func BenchmarkScratchGet(b *testing.B) {
scratch := NewScratch()
scratch.Add("A", 1)
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
scratch.Get("A")
}
}
diff --git a/common/hstrings/strings.go b/common/hstrings/strings.go
new file mode 100644
index 00000000000..29580e146f7
--- /dev/null
+++ b/common/hstrings/strings.go
@@ -0,0 +1,199 @@
+// 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 hstrings
+
+import (
+ "fmt"
+ "regexp"
+ "slices"
+ "sort"
+ "strings"
+ "sync"
+
+ "github.com/gohugoio/hugo/compare"
+)
+
+var _ compare.Eqer = StringEqualFold("")
+
+// StringEqualFold is a string that implements the compare.Eqer interface and considers
+// two strings equal if they are equal when folded to lower case.
+// The compare.Eqer interface is used in Hugo to compare values in templates (e.g. using the eq template function).
+type StringEqualFold string
+
+func (s StringEqualFold) EqualFold(s2 string) bool {
+ return strings.EqualFold(string(s), s2)
+}
+
+func (s StringEqualFold) String() string {
+ return string(s)
+}
+
+func (s StringEqualFold) Eq(s2 any) bool {
+ switch ss := s2.(type) {
+ case string:
+ return s.EqualFold(ss)
+ case fmt.Stringer:
+ return s.EqualFold(ss.String())
+ }
+
+ return false
+}
+
+// EqualAny returns whether a string is equal to any of the given strings.
+func EqualAny(a string, b ...string) bool {
+ return slices.Contains(b, a)
+}
+
+// regexpCache represents a cache of regexp objects protected by a mutex.
+type regexpCache struct {
+ mu sync.RWMutex
+ re map[string]*regexp.Regexp
+}
+
+func (rc *regexpCache) getOrCompileRegexp(pattern string) (re *regexp.Regexp, err error) {
+ var ok bool
+
+ if re, ok = rc.get(pattern); !ok {
+ re, err = regexp.Compile(pattern)
+ if err != nil {
+ return nil, err
+ }
+ rc.set(pattern, re)
+ }
+
+ return re, nil
+}
+
+func (rc *regexpCache) get(key string) (re *regexp.Regexp, ok bool) {
+ rc.mu.RLock()
+ re, ok = rc.re[key]
+ rc.mu.RUnlock()
+ return
+}
+
+func (rc *regexpCache) set(key string, re *regexp.Regexp) {
+ rc.mu.Lock()
+ rc.re[key] = re
+ rc.mu.Unlock()
+}
+
+var reCache = regexpCache{re: make(map[string]*regexp.Regexp)}
+
+// GetOrCompileRegexp retrieves a regexp object from the cache based upon the pattern.
+// If the pattern is not found in the cache, the pattern is compiled and added to
+// the cache.
+func GetOrCompileRegexp(pattern string) (re *regexp.Regexp, err error) {
+ return reCache.getOrCompileRegexp(pattern)
+}
+
+// HasAnyPrefix checks if the string s has any of the prefixes given.
+func HasAnyPrefix(s string, prefixes ...string) bool {
+ for _, p := range prefixes {
+ if strings.HasPrefix(s, p) {
+ return true
+ }
+ }
+ return false
+}
+
+// InSlice checks if a string is an element of a slice of strings
+// and returns a boolean value.
+func InSlice(arr []string, el string) bool {
+ return slices.Contains(arr, el)
+}
+
+// InSlicEqualFold checks if a string is an element of a slice of strings
+// and returns a boolean value.
+// It uses strings.EqualFold to compare.
+func InSlicEqualFold(arr []string, el string) bool {
+ for _, v := range arr {
+ if strings.EqualFold(v, el) {
+ return true
+ }
+ }
+ return false
+}
+
+// ToString converts the given value to a string.
+// Note that this is a more strict version compared to cast.ToString,
+// as it will not try to convert numeric values to strings,
+// but only accept strings or fmt.Stringer.
+func ToString(v any) (string, bool) {
+ switch vv := v.(type) {
+ case string:
+ return vv, true
+ case fmt.Stringer:
+ return vv.String(), true
+ }
+ return "", false
+}
+
+// UniqueStrings returns a new slice with any duplicates removed.
+func UniqueStrings(s []string) []string {
+ unique := make([]string, 0, len(s))
+ for i, val := range s {
+ var seen bool
+ for j := range i {
+ if s[j] == val {
+ seen = true
+ break
+ }
+ }
+ if !seen {
+ unique = append(unique, val)
+ }
+ }
+ return unique
+}
+
+// UniqueStringsReuse returns a slice with any duplicates removed.
+// It will modify the input slice.
+func UniqueStringsReuse(s []string) []string {
+ result := s[:0]
+ for i, val := range s {
+ var seen bool
+
+ for j := range i {
+ if s[j] == val {
+ seen = true
+ break
+ }
+ }
+
+ if !seen {
+ result = append(result, val)
+ }
+ }
+ return result
+}
+
+// UniqueStringsSorted returns a sorted slice with any duplicates removed.
+// It will modify the input slice.
+func UniqueStringsSorted(s []string) []string {
+ if len(s) == 0 {
+ return nil
+ }
+ ss := sort.StringSlice(s)
+ ss.Sort()
+ i := 0
+ for j := 1; j < len(s); j++ {
+ if !ss.Less(i, j) {
+ continue
+ }
+ i++
+ s[i] = s[j]
+ }
+
+ return s[:i+1]
+}
diff --git a/common/hstrings/strings_test.go b/common/hstrings/strings_test.go
new file mode 100644
index 00000000000..3150ada0755
--- /dev/null
+++ b/common/hstrings/strings_test.go
@@ -0,0 +1,134 @@
+// Copyright 2025 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hstrings
+
+import (
+ "reflect"
+ "regexp"
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestStringEqualFold(t *testing.T) {
+ c := qt.New(t)
+
+ s1 := "A"
+ s2 := "a"
+
+ c.Assert(StringEqualFold(s1).EqualFold(s2), qt.Equals, true)
+ c.Assert(StringEqualFold(s1).EqualFold(s1), qt.Equals, true)
+ c.Assert(StringEqualFold(s2).EqualFold(s1), qt.Equals, true)
+ c.Assert(StringEqualFold(s2).EqualFold(s2), qt.Equals, true)
+ c.Assert(StringEqualFold(s1).EqualFold("b"), qt.Equals, false)
+ c.Assert(StringEqualFold(s1).Eq(s2), qt.Equals, true)
+ c.Assert(StringEqualFold(s1).Eq("b"), qt.Equals, false)
+}
+
+func TestGetOrCompileRegexp(t *testing.T) {
+ c := qt.New(t)
+
+ re, err := GetOrCompileRegexp(`\d+`)
+ c.Assert(err, qt.IsNil)
+ c.Assert(re.MatchString("123"), qt.Equals, true)
+}
+
+func TestUniqueStrings(t *testing.T) {
+ in := []string{"a", "b", "a", "b", "c", "", "a", "", "d"}
+ output := UniqueStrings(in)
+ expected := []string{"a", "b", "c", "", "d"}
+ if !reflect.DeepEqual(output, expected) {
+ t.Errorf("Expected %#v, got %#v\n", expected, output)
+ }
+}
+
+func TestUniqueStringsReuse(t *testing.T) {
+ in := []string{"a", "b", "a", "b", "c", "", "a", "", "d"}
+ output := UniqueStringsReuse(in)
+ expected := []string{"a", "b", "c", "", "d"}
+ if !reflect.DeepEqual(output, expected) {
+ t.Errorf("Expected %#v, got %#v\n", expected, output)
+ }
+}
+
+func TestUniqueStringsSorted(t *testing.T) {
+ c := qt.New(t)
+ in := []string{"a", "a", "b", "c", "b", "", "a", "", "d"}
+ output := UniqueStringsSorted(in)
+ expected := []string{"", "a", "b", "c", "d"}
+ c.Assert(output, qt.DeepEquals, expected)
+ c.Assert(UniqueStringsSorted(nil), qt.IsNil)
+}
+
+// Note that these cannot use b.Loop() because of golang/go#27217.
+func BenchmarkUniqueStrings(b *testing.B) {
+ input := []string{"a", "b", "d", "e", "d", "h", "a", "i"}
+
+ b.Run("Safe", func(b *testing.B) {
+ for b.Loop() {
+ result := UniqueStrings(input)
+ if len(result) != 6 {
+ b.Fatalf("invalid count: %d", len(result))
+ }
+ }
+ })
+
+ b.Run("Reuse slice", func(b *testing.B) {
+ inputs := make([][]string, b.N)
+ for i := 0; i < b.N; i++ {
+ inputc := make([]string, len(input))
+ copy(inputc, input)
+ inputs[i] = inputc
+ }
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ inputc := inputs[i]
+
+ result := UniqueStringsReuse(inputc)
+ if len(result) != 6 {
+ b.Fatalf("invalid count: %d", len(result))
+ }
+ }
+ })
+
+ b.Run("Reuse slice sorted", func(b *testing.B) {
+ inputs := make([][]string, b.N)
+ for i := 0; i < b.N; i++ {
+ inputc := make([]string, len(input))
+ copy(inputc, input)
+ inputs[i] = inputc
+ }
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ inputc := inputs[i]
+
+ result := UniqueStringsSorted(inputc)
+ if len(result) != 6 {
+ b.Fatalf("invalid count: %d", len(result))
+ }
+ }
+ })
+}
+
+func BenchmarkGetOrCompileRegexp(b *testing.B) {
+ for b.Loop() {
+ GetOrCompileRegexp(`\d+`)
+ }
+}
+
+func BenchmarkCompileRegexp(b *testing.B) {
+ for b.Loop() {
+ regexp.MustCompile(`\d+`)
+ }
+}
diff --git a/common/hsync/oncemore.go b/common/hsync/oncemore.go
new file mode 100644
index 00000000000..cc951a21525
--- /dev/null
+++ b/common/hsync/oncemore.go
@@ -0,0 +1,162 @@
+// Copyright 2025 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hsync
+
+import (
+ "context"
+ "sync"
+ "sync/atomic"
+)
+
+// OnceMore is similar to sync.Once.
+//
+// Additional features are:
+// * it can be reset, so the action can be repeated if needed
+// * it has methods to check if it's done or in progress
+type OnceMore struct {
+ _ doNotCopy
+ done atomic.Bool
+ mu sync.Mutex
+}
+
+func (t *OnceMore) Do(f func()) {
+ if t.Done() {
+ return
+ }
+
+ t.mu.Lock()
+ defer t.mu.Unlock()
+
+ // Double check
+ if t.Done() {
+ return
+ }
+
+ defer t.done.Store(true)
+ f()
+}
+
+func (t *OnceMore) Done() bool {
+ return t.done.Load()
+}
+
+func (t *OnceMore) Reset() {
+ t.mu.Lock()
+ t.done.Store(false)
+ t.mu.Unlock()
+}
+
+type ValueResetter[T any] struct {
+ reset func()
+ f func(context.Context) T
+}
+
+func (v *ValueResetter[T]) Value(ctx context.Context) T {
+ return v.f(ctx)
+}
+
+func (v *ValueResetter[T]) Reset() {
+ v.reset()
+}
+
+// OnceMoreValue returns a function that invokes f only once and returns the value
+// returned by f. The returned function may be called concurrently.
+//
+// If f panics, the returned function will panic with the same value on every call.
+func OnceMoreValue[T any](f func(context.Context) T) ValueResetter[T] {
+ v := struct {
+ f func(context.Context) T
+ once OnceMore
+ ok bool
+ p any
+ result T
+ }{
+ f: f,
+ }
+ ff := func(ctx context.Context) T {
+ v.once.Do(func() {
+ v.ok = false
+ defer func() {
+ v.p = recover()
+ if !v.ok {
+ panic(v.p)
+ }
+ }()
+ v.result = v.f(ctx)
+ v.ok = true
+ })
+ if !v.ok {
+ panic(v.p)
+ }
+ return v.result
+ }
+
+ return ValueResetter[T]{
+ reset: v.once.Reset,
+ f: ff,
+ }
+}
+
+type FuncResetter struct {
+ f func(context.Context) error
+ reset func()
+}
+
+func (v *FuncResetter) Do(ctx context.Context) error {
+ return v.f(ctx)
+}
+
+func (v *FuncResetter) Reset() {
+ v.reset()
+}
+
+func OnceMoreFunc(f func(context.Context) error) FuncResetter {
+ v := struct {
+ f func(context.Context) error
+ once OnceMore
+ ok bool
+ err error
+ p any
+ }{
+ f: f,
+ }
+ ff := func(ctx context.Context) error {
+ v.once.Do(func() {
+ v.ok = false
+ defer func() {
+ v.p = recover()
+ if !v.ok {
+ panic(v.p)
+ }
+ }()
+ v.err = v.f(ctx)
+ v.ok = true
+ })
+ if !v.ok {
+ panic(v.p)
+ }
+ return v.err
+ }
+
+ return FuncResetter{
+ f: ff,
+ reset: v.once.Reset,
+ }
+}
+
+type doNotCopy struct{}
+
+// Lock is a no-op used by -copylocks checker from `go vet`.
+func (*doNotCopy) Lock() {}
+func (*doNotCopy) Unlock() {}
diff --git a/common/hsync/oncemore_test.go b/common/hsync/oncemore_test.go
new file mode 100644
index 00000000000..67a6e04d1df
--- /dev/null
+++ b/common/hsync/oncemore_test.go
@@ -0,0 +1,80 @@
+// Copyright 2025 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hsync
+
+import (
+ "context"
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestOnceMoreValue(t *testing.T) {
+ c := qt.New(t)
+
+ var counter int
+ f := func(context.Context) int {
+ counter++
+ return counter
+ }
+
+ omf := OnceMoreValue(f)
+ for range 10 {
+ c.Assert(omf.Value(context.Background()), qt.Equals, 1)
+ }
+ omf.Reset()
+ for range 10 {
+ c.Assert(omf.Value(context.Background()), qt.Equals, 2)
+ }
+}
+
+func TestOnceMoreFunc(t *testing.T) {
+ c := qt.New(t)
+
+ var counter int
+ f := func(context.Context) error {
+ counter++
+ return nil
+ }
+
+ omf := OnceMoreFunc(f)
+ for range 10 {
+ c.Assert(omf.Do(context.Background()), qt.IsNil)
+ c.Assert(counter, qt.Equals, 1)
+ }
+ omf.Reset()
+ for range 10 {
+ c.Assert(omf.Do(context.Background()), qt.IsNil)
+ c.Assert(counter, qt.Equals, 2)
+ }
+}
+
+func BenchmarkOnceMoreValue(b *testing.B) {
+ var counter int
+ f := func(context.Context) int {
+ counter++
+ return counter
+ }
+
+ for b.Loop() {
+ omf := OnceMoreValue(f)
+ for range 10 {
+ omf.Value(context.Background())
+ }
+ omf.Reset()
+ for range 10 {
+ omf.Value(context.Background())
+ }
+ }
+}
diff --git a/common/htime/htime_integration_test.go b/common/htime/htime_integration_test.go
new file mode 100644
index 00000000000..4acdd4beb97
--- /dev/null
+++ b/common/htime/htime_integration_test.go
@@ -0,0 +1,78 @@
+// 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 htime_test
+
+import (
+ "testing"
+
+ "github.com/gohugoio/hugo/hugolib"
+)
+
+// Issue #11267
+func TestApplyWithContext(t *testing.T) {
+ t.Parallel()
+
+ files := `
+-- hugo.toml --
+defaultContentLanguage = 'it'
+-- layouts/home.html --
+{{ $dates := slice
+ "2022-01-03"
+ "2022-02-01"
+ "2022-03-02"
+ "2022-04-07"
+ "2022-05-06"
+ "2022-06-04"
+ "2022-07-03"
+ "2022-08-01"
+ "2022-09-06"
+ "2022-10-05"
+ "2022-11-03"
+ "2022-12-02"
+}}
+{{ range $dates }}
+ {{ . | time.Format "month: _January_ weekday: _Monday_" }}
+ {{ . | time.Format "month: _Jan_ weekday: _Mon_" }}
+{{ end }}
+ `
+
+ b := hugolib.Test(t, files)
+
+ b.AssertFileContent("public/index.html", `
+month: _gennaio_ weekday: _lunedì_
+month: _gen_ weekday: _lun_
+month: _febbraio_ weekday: _martedì_
+month: _feb_ weekday: _mar_
+month: _marzo_ weekday: _mercoledì_
+month: _mar_ weekday: _mer_
+month: _aprile_ weekday: _giovedì_
+month: _apr_ weekday: _gio_
+month: _maggio_ weekday: _venerdì_
+month: _mag_ weekday: _ven_
+month: _giugno_ weekday: _sabato_
+month: _giu_ weekday: _sab_
+month: _luglio_ weekday: _domenica_
+month: _lug_ weekday: _dom_
+month: _agosto_ weekday: _lunedì_
+month: _ago_ weekday: _lun_
+month: _settembre_ weekday: _martedì_
+month: _set_ weekday: _mar_
+month: _ottobre_ weekday: _mercoledì_
+month: _ott_ weekday: _mer_
+month: _novembre_ weekday: _giovedì_
+month: _nov_ weekday: _gio_
+month: _dicembre_ weekday: _venerdì_
+month: _dic_ weekday: _ven_
+`)
+}
diff --git a/common/htime/time.go b/common/htime/time.go
index 9ab249d3835..3745e6abaf1 100644
--- a/common/htime/time.go
+++ b/common/htime/time.go
@@ -14,14 +14,14 @@
package htime
import (
+ "log"
"strings"
"time"
- "github.com/spf13/cast"
-
- toml "github.com/pelletier/go-toml/v2"
+ "github.com/bep/golocales"
- "github.com/gohugoio/locales"
+ "github.com/bep/clocks"
+ "github.com/spf13/cast"
)
var (
@@ -74,11 +74,13 @@ var (
"November",
"December",
}
+
+ Clock = clocks.System()
)
-func NewTimeFormatter(ltr locales.Translator) TimeFormatter {
+func NewTimeFormatter(ltr golocales.Translator) TimeFormatter {
if ltr == nil {
- panic("must provide a locales.Translator")
+ panic("must provide a golocales.Translator")
}
return TimeFormatter{
ltr: ltr,
@@ -87,7 +89,7 @@ func NewTimeFormatter(ltr locales.Translator) TimeFormatter {
// TimeFormatter is locale aware.
type TimeFormatter struct {
- ltr locales.Translator
+ ltr golocales.Translator
}
func (f TimeFormatter) Format(t time.Time, layout string) string {
@@ -99,52 +101,77 @@ func (f TimeFormatter) Format(t time.Time, layout string) string {
// It may be one of Hugo's custom layouts.
switch strings.ToLower(layout[1:]) {
case "date_full":
- return f.ltr.FmtDateFull(t)
+ return f.ltr.FormatDateFull(t)
case "date_long":
- return f.ltr.FmtDateLong(t)
+ return f.ltr.FormatDateLong(t)
case "date_medium":
- return f.ltr.FmtDateMedium(t)
+ return f.ltr.FormatDateMedium(t)
case "date_short":
- return f.ltr.FmtDateShort(t)
+ return f.ltr.FormatDateShort(t)
case "time_full":
- return f.ltr.FmtTimeFull(t)
+ return f.ltr.FormatTimeFull(t)
case "time_long":
- return f.ltr.FmtTimeLong(t)
+ return f.ltr.FormatTimeLong(t)
case "time_medium":
- return f.ltr.FmtTimeMedium(t)
+ return f.ltr.FormatTimeMedium(t)
case "time_short":
- return f.ltr.FmtTimeShort(t)
+ return f.ltr.FormatTimeShort(t)
}
}
s := t.Format(layout)
- monthIdx := t.Month() - 1 // Month() starts at 1.
+ monthIdx := t.Month() - 1 // time.Month is 1-based, but our month name slices are 0-based.
dayIdx := t.Weekday()
- s = strings.ReplaceAll(s, longMonthNames[monthIdx], f.ltr.MonthWide(t.Month()))
- if !strings.Contains(s, f.ltr.MonthWide(t.Month())) {
- s = strings.ReplaceAll(s, shortMonthNames[monthIdx], f.ltr.MonthAbbreviated(t.Month()))
+ if strings.Contains(layout, "January") {
+ s = strings.ReplaceAll(s, longMonthNames[monthIdx], f.ltr.MonthsWide()[monthIdx])
+ } else if strings.Contains(layout, "Jan") {
+ s = strings.ReplaceAll(s, shortMonthNames[monthIdx], f.ltr.MonthsAbbreviated()[monthIdx])
}
- s = strings.ReplaceAll(s, longDayNames[dayIdx], f.ltr.WeekdayWide(t.Weekday()))
- if !strings.Contains(s, f.ltr.WeekdayWide(t.Weekday())) {
- s = strings.ReplaceAll(s, shortDayNames[dayIdx], f.ltr.WeekdayAbbreviated(t.Weekday()))
+
+ if strings.Contains(layout, "Monday") {
+ s = strings.ReplaceAll(s, longDayNames[dayIdx], f.ltr.WeekdaysWide()[dayIdx])
+ } else if strings.Contains(layout, "Mon") {
+ s = strings.ReplaceAll(s, shortDayNames[dayIdx], f.ltr.WeekdaysAbbreviated()[dayIdx])
}
return s
}
-func ToTimeInDefaultLocationE(i interface{}, location *time.Location) (tim time.Time, err error) {
+func ToTimeInDefaultLocationE(i any, location *time.Location) (tim time.Time, err error) {
switch vv := i.(type) {
- case toml.LocalDate:
- return vv.AsTime(location), nil
- case toml.LocalDateTime:
+ case AsTimeProvider:
return vv.AsTime(location), nil
// issue #8895
// datetimes parsed by `go-toml` have empty zone name
// convert back them into string and use `cast`
+ // TODO(bep) add tests, make sure we really need this.
case time.Time:
i = vv.Format(time.RFC3339)
}
return cast.ToTimeInDefaultLocationE(i, location)
}
+
+// Now returns time.Now() or time value based on the `clock` flag.
+// Use this function to fake time inside hugo.
+func Now() time.Time {
+ return Clock.Now()
+}
+
+func Since(t time.Time) time.Duration {
+ return Clock.Since(t)
+}
+
+// AsTimeProvider is implemented by go-toml's LocalDate and LocalDateTime.
+type AsTimeProvider interface {
+ AsTime(zone *time.Location) time.Time
+}
+
+// StopWatch is a simple helper to measure time during development.
+func StopWatch(name string) func() {
+ start := time.Now()
+ return func() {
+ log.Printf("StopWatch %q took %s", name, time.Since(start))
+ }
+}
diff --git a/common/htime/time_test.go b/common/htime/time_test.go
index 6cb32340966..3f875e69314 100644
--- a/common/htime/time_test.go
+++ b/common/htime/time_test.go
@@ -17,8 +17,9 @@ import (
"testing"
"time"
+ "github.com/bep/golocales"
+
qt "github.com/frankban/quicktest"
- translators "github.com/gohugoio/localescompressed"
)
func TestTimeFormatter(t *testing.T) {
@@ -34,7 +35,7 @@ func TestTimeFormatter(t *testing.T) {
mondayNovemberFirst = mondayNovemberFirst.Add(33 * time.Second)
c.Run("Norsk nynorsk", func(c *qt.C) {
- f := NewTimeFormatter(translators.GetTranslator("nn"))
+ f := NewTimeFormatter(golocales.New("nn"))
c.Assert(f.Format(june06, "Monday Jan 2 2006"), qt.Equals, "onsdag juni 6 2018")
c.Assert(f.Format(june06, "Mon January 2 2006"), qt.Equals, "on. juni 6 2018")
@@ -42,37 +43,35 @@ func TestTimeFormatter(t *testing.T) {
})
c.Run("Custom layouts Norsk nynorsk", func(c *qt.C) {
- f := NewTimeFormatter(translators.GetTranslator("nn"))
+ f := NewTimeFormatter(golocales.New("nn"))
c.Assert(f.Format(june06, ":date_full"), qt.Equals, "onsdag 6. juni 2018")
c.Assert(f.Format(june06, ":date_long"), qt.Equals, "6. juni 2018")
c.Assert(f.Format(june06, ":date_medium"), qt.Equals, "6. juni 2018")
- c.Assert(f.Format(june06, ":date_short"), qt.Equals, "06.06.2018")
+ c.Assert(f.Format(june06, ":date_short"), qt.Equals, "06.06.18")
- c.Assert(f.Format(june06, ":time_full"), qt.Equals, "kl. 02:09:37 UTC")
+ c.Assert(f.Format(june06, ":time_full"), qt.Equals, "02:09:37 UTC")
c.Assert(f.Format(june06, ":time_long"), qt.Equals, "02:09:37 UTC")
c.Assert(f.Format(june06, ":time_medium"), qt.Equals, "02:09:37")
c.Assert(f.Format(june06, ":time_short"), qt.Equals, "02:09")
-
})
c.Run("Custom layouts English", func(c *qt.C) {
- f := NewTimeFormatter(translators.GetTranslator("en"))
+ f := NewTimeFormatter(golocales.New("en"))
c.Assert(f.Format(june06, ":date_full"), qt.Equals, "Wednesday, June 6, 2018")
c.Assert(f.Format(june06, ":date_long"), qt.Equals, "June 6, 2018")
c.Assert(f.Format(june06, ":date_medium"), qt.Equals, "Jun 6, 2018")
c.Assert(f.Format(june06, ":date_short"), qt.Equals, "6/6/18")
- c.Assert(f.Format(june06, ":time_full"), qt.Equals, "2:09:37 am UTC")
- c.Assert(f.Format(june06, ":time_long"), qt.Equals, "2:09:37 am UTC")
- c.Assert(f.Format(june06, ":time_medium"), qt.Equals, "2:09:37 am")
- c.Assert(f.Format(june06, ":time_short"), qt.Equals, "2:09 am")
-
+ c.Assert(f.Format(june06, ":time_full"), qt.Equals, "2:09:37\u202fam UTC")
+ c.Assert(f.Format(june06, ":time_long"), qt.Equals, "2:09:37\u202fam UTC")
+ c.Assert(f.Format(june06, ":time_medium"), qt.Equals, "2:09:37\u202fam")
+ c.Assert(f.Format(june06, ":time_short"), qt.Equals, "2:09\u202fam")
})
c.Run("English", func(c *qt.C) {
- f := NewTimeFormatter(translators.GetTranslator("en"))
+ f := NewTimeFormatter(golocales.New("en"))
c.Assert(f.Format(june06, "Monday Jan 2 2006"), qt.Equals, "Wednesday Jun 6 2018")
c.Assert(f.Format(june06, "Mon January 2 2006"), qt.Equals, "Wed June 6 2018")
@@ -80,43 +79,41 @@ func TestTimeFormatter(t *testing.T) {
})
c.Run("Weekdays German", func(c *qt.C) {
- tr := translators.GetTranslator("de")
+ tr := golocales.New("de")
f := NewTimeFormatter(tr)
// Issue #9107
for i, weekDayWideGerman := range []string{"Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag"} {
date := mondayNovemberFirst.Add(time.Duration(i*24) * time.Hour)
- c.Assert(tr.WeekdayWide(date.Weekday()), qt.Equals, weekDayWideGerman)
+ c.Assert(tr.WeekdaysWide()[date.Weekday()], qt.Equals, weekDayWideGerman)
c.Assert(f.Format(date, "Monday"), qt.Equals, weekDayWideGerman)
}
for i, weekDayAbbreviatedGerman := range []string{"Mo.", "Di.", "Mi.", "Do.", "Fr.", "Sa.", "So."} {
date := mondayNovemberFirst.Add(time.Duration(i*24) * time.Hour)
- c.Assert(tr.WeekdayAbbreviated(date.Weekday()), qt.Equals, weekDayAbbreviatedGerman)
+ c.Assert(tr.WeekdaysAbbreviated()[date.Weekday()], qt.Equals, weekDayAbbreviatedGerman)
c.Assert(f.Format(date, "Mon"), qt.Equals, weekDayAbbreviatedGerman)
}
})
c.Run("Months German", func(c *qt.C) {
- tr := translators.GetTranslator("de")
+ tr := golocales.New("de")
f := NewTimeFormatter(tr)
// Issue #9107
for i, monthWideNorway := range []string{"Januar", "Februar", "März", "April", "Mai", "Juni", "Juli"} {
date := jan06.Add(time.Duration(i*24*31) * time.Hour)
- c.Assert(tr.MonthWide(date.Month()), qt.Equals, monthWideNorway)
+ c.Assert(tr.MonthsWide()[date.Month()-1], qt.Equals, monthWideNorway)
c.Assert(f.Format(date, "January"), qt.Equals, monthWideNorway)
}
-
})
-
}
func BenchmarkTimeFormatter(b *testing.B) {
june06, _ := time.Parse("2006-Jan-02", "2018-Jun-06")
b.Run("Native", func(b *testing.B) {
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
got := june06.Format("Monday Jan 2 2006")
if got != "Wednesday Jun 6 2018" {
b.Fatalf("invalid format, got %q", got)
@@ -125,9 +122,9 @@ func BenchmarkTimeFormatter(b *testing.B) {
})
b.Run("Localized", func(b *testing.B) {
- f := NewTimeFormatter(translators.GetTranslator("nn"))
+ f := NewTimeFormatter(golocales.New("nn"))
b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
got := f.Format(june06, "Monday Jan 2 2006")
if got != "onsdag juni 6 2018" {
b.Fatalf("invalid format, got %q", got)
@@ -136,9 +133,9 @@ func BenchmarkTimeFormatter(b *testing.B) {
})
b.Run("Localized Custom", func(b *testing.B) {
- f := NewTimeFormatter(translators.GetTranslator("nn"))
+ f := NewTimeFormatter(golocales.New("nn"))
b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
got := f.Format(june06, ":date_medium")
if got != "6. juni 2018" {
b.Fatalf("invalid format, got %q", got)
diff --git a/common/hugio/copy.go b/common/hugio/copy.go
index be4506f4cd1..31d679dfce0 100644
--- a/common/hugio/copy.go
+++ b/common/hugio/copy.go
@@ -14,13 +14,11 @@
package hugio
import (
+ "fmt"
"io"
- "io/ioutil"
- "os"
+ iofs "io/fs"
"path/filepath"
- "github.com/pkg/errors"
-
"github.com/spf13/afero"
)
@@ -54,21 +52,25 @@ func CopyFile(fs afero.Fs, from, to string) error {
// CopyDir copies a directory.
func CopyDir(fs afero.Fs, from, to string, shouldCopy func(filename string) bool) error {
- fi, err := os.Stat(from)
+ fi, err := fs.Stat(from)
if err != nil {
return err
}
if !fi.IsDir() {
- return errors.Errorf("%q is not a directory", from)
+ return fmt.Errorf("%q is not a directory", from)
}
- err = fs.MkdirAll(to, 0777) // before umask
+ err = fs.MkdirAll(to, 0o777) // before umask
if err != nil {
return err
}
- entries, _ := ioutil.ReadDir(from)
+ d, err := fs.Open(from)
+ if err != nil {
+ return err
+ }
+ entries, _ := d.(iofs.ReadDirFile).ReadDir(-1)
for _, entry := range entries {
fromFilename := filepath.Join(from, entry.Name())
toFilename := filepath.Join(to, entry.Name())
diff --git a/common/hugio/hasBytesWriter.go b/common/hugio/hasBytesWriter.go
new file mode 100644
index 00000000000..d2bcd1bb408
--- /dev/null
+++ b/common/hugio/hasBytesWriter.go
@@ -0,0 +1,80 @@
+// 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 hugio
+
+import (
+ "bytes"
+)
+
+// HasBytesWriter is a writer will match against a slice of patterns.
+type HasBytesWriter struct {
+ Patterns []*HasBytesPattern
+
+ 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)
+ }
+
+ for i := range p {
+ h.buff[h.i] = p[i]
+ h.i++
+ if h.i == len(h.buff) {
+ // Shift left.
+ copy(h.buff, h.buff[len(h.buff)/2:])
+ h.i = len(h.buff) / 2
+ }
+
+ 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
+ }
+ }
+
+ }
+
+ return len(p), nil
+}
diff --git a/common/hugio/hasBytesWriter_test.go b/common/hugio/hasBytesWriter_test.go
new file mode 100644
index 00000000000..9e689a1121a
--- /dev/null
+++ b/common/hugio/hasBytesWriter_test.go
@@ -0,0 +1,67 @@
+// 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 hugio
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "math/rand"
+ "strings"
+ "testing"
+ "time"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestHasBytesWriter(t *testing.T) {
+ r := rand.New(rand.NewSource(time.Now().UnixNano()))
+
+ c := qt.New((t))
+
+ neww := func() (*HasBytesWriter, io.Writer) {
+ var b bytes.Buffer
+
+ h := &HasBytesWriter{
+ Patterns: []*HasBytesPattern{
+ {Pattern: []byte("__foo")},
+ },
+ }
+
+ return h, io.MultiWriter(&b, h)
+ }
+
+ rndStr := func() string {
+ return strings.Repeat("ab cfo", r.Intn(33))
+ }
+
+ for range 22 {
+ h, w := neww()
+ fmt.Fprint(w, rndStr()+"abc __foobar"+rndStr())
+ c.Assert(h.Patterns[0].Match, qt.Equals, true)
+
+ h, w = neww()
+ fmt.Fprint(w, rndStr()+"abc __f")
+ fmt.Fprint(w, "oo bar"+rndStr())
+ c.Assert(h.Patterns[0].Match, qt.Equals, true)
+
+ h, w = neww()
+ fmt.Fprint(w, rndStr()+"abc __moo bar")
+ c.Assert(h.Patterns[0].Match, qt.Equals, false)
+ }
+
+ h, w := neww()
+ fmt.Fprintf(w, "__foo")
+ c.Assert(h.Patterns[0].Match, qt.Equals, true)
+}
diff --git a/common/hugio/readers.go b/common/hugio/readers.go
index 60bd97992fc..7e50e6a5e19 100644
--- a/common/hugio/readers.go
+++ b/common/hugio/readers.go
@@ -14,46 +14,134 @@
package hugio
import (
+ "bytes"
"io"
"strings"
)
-// ReadSeeker wraps io.Reader and io.Seeker.
-type ReadSeeker interface {
- io.Reader
- io.Seeker
-}
-
// ReadSeekCloser is implemented by afero.File. We use this as the common type for
// content in Resource objects, even for strings.
type ReadSeekCloser interface {
- ReadSeeker
+ io.ReadSeeker
io.Closer
}
+// Sizer provides the size of, typically, a io.Reader.
+// As implemented by e.g. os.File and io.SectionReader.
+type Sizer interface {
+ Size() int64
+}
+
+type SizeReader interface {
+ io.Reader
+ Sizer
+}
+
+// ToSizeReader converts the given io.Reader to a SizeReader.
+// Note that if r is not a SizeReader, the entire content will be read into memory
+func ToSizeReader(r io.Reader) (SizeReader, error) {
+ if sr, ok := r.(SizeReader); ok {
+ return sr, nil
+ }
+ b, err := io.ReadAll(r)
+ if err != nil {
+ return nil, err
+ }
+ return bytes.NewReader(b), nil
+}
+
+// CloserFunc is an adapter to allow the use of ordinary functions as io.Closers.
+type CloserFunc func() error
+
+func (f CloserFunc) Close() error {
+ return f()
+}
+
// ReadSeekCloserProvider provides a ReadSeekCloser.
type ReadSeekCloserProvider interface {
ReadSeekCloser() (ReadSeekCloser, error)
}
-// ReadSeekerNoOpCloser implements ReadSeekCloser by doing nothing in Close.
-// TODO(bep) rename this and similar to ReadSeekerNopCloser, naming used in stdlib, which kind of makes sense.
-type ReadSeekerNoOpCloser struct {
- ReadSeeker
+// readSeekerNopCloser implements ReadSeekCloser by doing nothing in Close.
+type readSeekerNopCloser struct {
+ io.ReadSeeker
}
// Close does nothing.
-func (r ReadSeekerNoOpCloser) Close() error {
+func (r readSeekerNopCloser) Close() error {
return nil
}
// NewReadSeekerNoOpCloser creates a new ReadSeekerNoOpCloser with the given ReadSeeker.
-func NewReadSeekerNoOpCloser(r ReadSeeker) ReadSeekerNoOpCloser {
- return ReadSeekerNoOpCloser{r}
+func NewReadSeekerNoOpCloser(r io.ReadSeeker) ReadSeekCloser {
+ return readSeekerNopCloser{r}
}
// NewReadSeekerNoOpCloserFromString uses strings.NewReader to create a new ReadSeekerNoOpCloser
// from the given string.
-func NewReadSeekerNoOpCloserFromString(content string) ReadSeekerNoOpCloser {
- return ReadSeekerNoOpCloser{strings.NewReader(content)}
+func NewReadSeekerNoOpCloserFromString(content string) ReadSeekCloser {
+ return stringReadSeeker{s: content, readSeekerNopCloser: readSeekerNopCloser{strings.NewReader(content)}}
+}
+
+var _ StringReader = (*stringReadSeeker)(nil)
+
+type stringReadSeeker struct {
+ s string
+ readSeekerNopCloser
+}
+
+func (s *stringReadSeeker) ReadString() string {
+ return s.s
+}
+
+// StringReader provides a way to read a string.
+type StringReader interface {
+ ReadString() string
+}
+
+// NewReadSeekerNoOpCloserFromBytes uses bytes.NewReader to create a new ReadSeekerNoOpCloser
+// from the given bytes slice.
+func NewReadSeekerNoOpCloserFromBytes(content []byte) readSeekerNopCloser {
+ return readSeekerNopCloser{bytes.NewReader(content)}
+}
+
+// NewReadSeekerNoOpCloserFromReader creates a new ReadSeekerNoOpCloser from the given io.Reader.
+// If the given io.Reader is not an io.ReadSeeker, the entire content will be read into memory.
+func NewReadSeekerNoOpCloserFromReader(r io.Reader) (readSeekerNopCloser, error) {
+ var rs io.ReadSeeker
+ if s, ok := r.(io.ReadSeeker); ok {
+ rs = s
+ } else {
+ b, err := io.ReadAll(r)
+ if err != nil {
+ return readSeekerNopCloser{rs}, err
+ }
+ rs = bytes.NewReader(b)
+ }
+ return readSeekerNopCloser{rs}, nil
+}
+
+// NewOpenReadSeekCloser creates a new ReadSeekCloser from the given ReadSeeker.
+// The ReadSeeker will be seeked to the beginning before returned.
+func NewOpenReadSeekCloser(r ReadSeekCloser) OpenReadSeekCloser {
+ return func() (ReadSeekCloser, error) {
+ r.Seek(0, io.SeekStart)
+ return r, nil
+ }
+}
+
+// OpenReadSeekCloser allows setting some other way (than reading from a filesystem)
+// to open or create a ReadSeekCloser.
+type OpenReadSeekCloser func() (ReadSeekCloser, error)
+
+// ReadString reads from the given reader and returns the content as a string.
+func ReadString(r io.Reader) (string, error) {
+ if sr, ok := r.(StringReader); ok {
+ return sr.ReadString(), nil
+ }
+ b, err := io.ReadAll(r)
+ if err != nil {
+ return "", err
+ }
+ return string(b), nil
}
diff --git a/common/hugio/writers.go b/common/hugio/writers.go
index 82c4dca52e4..6f439cc8b00 100644
--- a/common/hugio/writers.go
+++ b/common/hugio/writers.go
@@ -15,9 +15,16 @@ package hugio
import (
"io"
- "io/ioutil"
)
+// As implemented by strings.Builder.
+type FlexiWriter interface {
+ io.Writer
+ io.ByteWriter
+ WriteString(s string) (int, error)
+ WriteRune(r rune) (int, error)
+}
+
type multiWriteCloser struct {
io.Writer
closers []io.WriteCloser
@@ -26,7 +33,7 @@ type multiWriteCloser struct {
func (m multiWriteCloser) Close() error {
var err error
for _, c := range m.closers {
- if closeErr := c.Close(); err != nil {
+ if closeErr := c.Close(); closeErr != nil {
err = closeErr
}
}
@@ -55,7 +62,7 @@ func ToWriteCloser(w io.Writer) io.WriteCloser {
io.Closer
}{
w,
- ioutil.NopCloser(nil),
+ io.NopCloser(nil),
}
}
@@ -71,6 +78,36 @@ func ToReadCloser(r io.Reader) io.ReadCloser {
io.Closer
}{
r,
- ioutil.NopCloser(nil),
+ 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))
+}
diff --git a/common/hugo/hugo.go b/common/hugo/hugo.go
index d8f92e2983f..fbb1e53a447 100644
--- a/common/hugo/hugo.go
+++ b/common/hugo/hugo.go
@@ -14,18 +14,30 @@
package hugo
import (
+ "context"
"fmt"
- "html/template"
"os"
"path/filepath"
"runtime/debug"
"sort"
"strings"
+ "sync"
+ "time"
+ "github.com/bep/helpers/contexthelpers"
+ "github.com/bep/logg"
+
+ "github.com/bep/godartsass/v2"
+
+ "github.com/gohugoio/hugo/common/hexec"
+ "github.com/gohugoio/hugo/common/loggers"
+ "github.com/gohugoio/hugo/common/version"
"github.com/gohugoio/hugo/hugofs/files"
"github.com/spf13/afero"
+ iofs "io/fs"
+
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/hugofs"
)
@@ -36,62 +48,60 @@ const (
)
var (
- // commitHash contains the current Git revision.
- // Use mage to build to make sure this gets set.
- commitHash string
-
- // buildDate contains the date of the current build.
+ // buildDate allows vendor-specified build date when .git/ is unavailable.
buildDate string
-
// vendorInfo contains vendor notes about the current build.
vendorInfo string
)
-// Info contains information about the current Hugo environment
-type Info struct {
- CommitHash string
- BuildDate string
-
- // The build environment.
- // Defaults are "production" (hugo) and "development" (hugo server).
- // This can also be set by the user.
- // It can be any string, but it will be all lower case.
- Environment string
+// BuildInfo holds build information extracted from runtime/debug.
+type BuildInfo struct {
+ Revision string
+ RevisionTime string
+ GoVersion string
}
-// Version returns the current version as a comparable version string.
-func (i Info) Version() VersionString {
- return CurrentVersion.Version()
+// GetBuildInfo returns the build info for the current binary.
+func GetBuildInfo() *BuildInfo {
+ bi := getBuildInfo()
+ if bi == nil {
+ return nil
+ }
+ return &BuildInfo{
+ Revision: bi.Revision,
+ RevisionTime: bi.RevisionTime,
+ GoVersion: bi.GoVersion,
+ }
}
-// Generator a Hugo meta generator HTML tag.
-func (i Info) Generator() template.HTML {
- return template.HTML(fmt.Sprintf(``, CurrentVersion.String()))
-}
+type contextKey uint8
+
+const (
+ contextKeyMarkupScope contextKey = iota
+)
-func (i Info) IsProduction() bool {
- return i.Environment == EnvironmentProduction
+var markupScope = contexthelpers.NewContextDispatcher[string](contextKeyMarkupScope)
+
+// Context gives access to some of the context scoped variables.
+type Context struct{}
+
+func (c Context) MarkupScope(ctx context.Context) string {
+ return GetMarkupScope(ctx)
}
-func (i Info) IsExtended() bool {
- return IsExtended
+// SetMarkupScope sets the markup scope in the context.
+func SetMarkupScope(ctx context.Context, s string) context.Context {
+ return markupScope.Set(ctx, s)
}
-// NewInfo creates a new Hugo Info object.
-func NewInfo(environment string) Info {
- if environment == "" {
- environment = EnvironmentProduction
- }
- return Info{
- CommitHash: commitHash,
- BuildDate: buildDate,
- Environment: environment,
- }
+// GetMarkupScope gets the markup scope from the context.
+func GetMarkupScope(ctx context.Context) string {
+ return markupScope.Get(ctx)
}
// GetExecEnviron creates and gets the common os/exec environment used in the
// external programs we interact with via os/exec, e.g. postcss.
-func GetExecEnviron(workDir string, cfg config.Provider, fs afero.Fs) []string {
+func GetExecEnviron(workDir string, cfg config.AllProvider, fs afero.Fs) []string {
var env []string
nodepath := filepath.Join(workDir, "node_modules")
if np := os.Getenv("NODE_PATH"); np != "" {
@@ -99,10 +109,17 @@ func GetExecEnviron(workDir string, cfg config.Provider, fs afero.Fs) []string {
}
config.SetEnvVars(&env, "NODE_PATH", nodepath)
config.SetEnvVars(&env, "PWD", workDir)
- config.SetEnvVars(&env, "HUGO_ENVIRONMENT", cfg.GetString("environment"))
+ config.SetEnvVars(&env, "HUGO_ENVIRONMENT", cfg.Environment())
+ config.SetEnvVars(&env, "HUGO_ENV", cfg.Environment())
+ config.SetEnvVars(&env, "HUGO_PUBLISHDIR", filepath.Join(workDir, cfg.BaseConfig().PublishDir))
if fs != nil {
- fis, err := afero.ReadDir(fs, files.FolderJSConfig)
+ var fis []iofs.DirEntry
+ d, err := fs.Open(files.FolderJSConfig)
+ if err == nil {
+ fis, err = d.(iofs.ReadDirFile).ReadDir(-1)
+ }
+
if err == nil {
for _, fi := range fis {
key := fmt.Sprintf("HUGO_FILE_%s", strings.ReplaceAll(strings.ToUpper(fi.Name()), ".", "_"))
@@ -115,26 +132,64 @@ func GetExecEnviron(workDir string, cfg config.Provider, fs afero.Fs) []string {
return env
}
+type buildInfo struct {
+ VersionControlSystem string
+ Revision string
+ RevisionTime string
+ Modified bool
+
+ GoOS string
+ GoArch string
+
+ *debug.BuildInfo
+}
+
+var (
+ bInfo *buildInfo
+ bInfoInit sync.Once
+)
+
+func getBuildInfo() *buildInfo {
+ bInfoInit.Do(func() {
+ bi, ok := debug.ReadBuildInfo()
+ if !ok {
+ return
+ }
+
+ bInfo = &buildInfo{BuildInfo: bi}
+
+ for _, s := range bInfo.Settings {
+ switch s.Key {
+ case "vcs":
+ bInfo.VersionControlSystem = s.Value
+ case "vcs.revision":
+ bInfo.Revision = s.Value
+ case "vcs.time":
+ bInfo.RevisionTime = s.Value
+ case "vcs.modified":
+ bInfo.Modified = s.Value == "true"
+ case "GOOS":
+ bInfo.GoOS = s.Value
+ case "GOARCH":
+ bInfo.GoArch = s.Value
+ }
+ }
+ })
+
+ return bInfo
+}
+
+func formatDep(path, version string) string {
+ return fmt.Sprintf("%s=%q", path, version)
+}
+
// GetDependencyList returns a sorted dependency list on the format package="version".
// It includes both Go dependencies and (a manually maintained) list of C(++) dependencies.
func GetDependencyList() []string {
var deps []string
- formatDep := func(path, version string) string {
- return fmt.Sprintf("%s=%q", path, version)
- }
-
- if IsExtended {
- deps = append(
- deps,
- // TODO(bep) consider adding a DepsNonGo() method to these upstream projects.
- formatDep("github.com/sass/libsass", "3.6.5"),
- formatDep("github.com/webmproject/libwebp", "v1.2.0"),
- )
- }
-
- bi, ok := debug.ReadBuildInfo()
- if !ok {
+ bi := getBuildInfo()
+ if bi == nil {
return deps
}
@@ -142,11 +197,38 @@ func GetDependencyList() []string {
deps = append(deps, formatDep(dep.Path, dep.Version))
}
+ deps = append(deps, GetDependencyListNonGo()...)
+
sort.Strings(deps)
return deps
}
+// GetDependencyListNonGo returns a list of non-Go dependencies.
+func GetDependencyListNonGo() []string {
+ deps := []string{formatDep("github.com/webmproject/libwebp", "v1.6.0")} // via WASM. TODO(bep) get versions from the plugin setup.
+
+ if IsExtended {
+ deps = append(
+ deps,
+ formatDep("github.com/sass/libsass", "3.6.6"),
+ )
+ }
+
+ if dartSass := dartSassVersion(); dartSass.ProtocolVersion != "" {
+ dartSassPath := "github.com/sass/dart-sass-embedded"
+ if IsDartSassGeV2() {
+ dartSassPath = "github.com/sass/dart-sass"
+ }
+ deps = append(deps,
+ formatDep(dartSassPath+"/protocol", dartSass.ProtocolVersion),
+ formatDep(dartSassPath+"/compiler", dartSass.CompilerVersion),
+ formatDep(dartSassPath+"/implementation", dartSass.ImplementationVersion),
+ )
+ }
+ return deps
+}
+
// IsRunningAsTest reports whether we are running as a test.
func IsRunningAsTest() bool {
for _, arg := range os.Args {
@@ -156,3 +238,175 @@ func IsRunningAsTest() bool {
}
return false
}
+
+// Dependency is a single dependency, which can be either a Hugo Module or a local theme.
+type Dependency struct {
+ // Returns the path to this module.
+ // This will either be the module path, e.g. "github.com/gohugoio/myshortcodes",
+ // or the path below your /theme folder, e.g. "mytheme".
+ Path string
+
+ // The module version.
+ Version string
+
+ // Whether this dependency is vendored.
+ Vendor bool
+
+ // Time version was created.
+ Time time.Time
+
+ // In the dependency tree, this is the first module that defines this module
+ // as a dependency.
+ Owner *Dependency
+
+ // Replaced by this dependency.
+ Replace *Dependency
+}
+
+func dartSassVersion() godartsass.DartSassVersion {
+ if DartSassBinaryName == "" || !IsDartSassGeV2() {
+ return godartsass.DartSassVersion{}
+ }
+ v, _ := godartsass.Version(DartSassBinaryName)
+ return v
+}
+
+// DartSassBinaryName is the name of the Dart Sass binary to use.
+// TODO(bep) find a better place for this.
+var DartSassBinaryName string
+
+func init() {
+ DartSassBinaryName = os.Getenv("DART_SASS_BINARY")
+ if DartSassBinaryName == "" {
+ for _, name := range dartSassBinaryNamesV2 {
+ if hexec.InPath(name) {
+ DartSassBinaryName = name
+ break
+ }
+ }
+ if DartSassBinaryName == "" {
+ if hexec.InPath(dartSassBinaryNameV1) {
+ DartSassBinaryName = dartSassBinaryNameV1
+ }
+ }
+ }
+}
+
+var (
+ dartSassBinaryNameV1 = "dart-sass-embedded"
+ 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.
+ return !strings.Contains(DartSassBinaryName, "embedded")
+}
+
+// Deprecate informs about a deprecation starting at the given version.
+//
+// A deprecation typically needs a simple change in the template, but doing so will make the template incompatible with older versions.
+// Theme maintainers generally want
+// 1. No warnings or errors in the console when building a Hugo site.
+// 2. Their theme to work for at least the last few Hugo versions.
+func Deprecate(item, alternative string, version string) {
+ level := deprecationLogLevelFromVersion(version)
+ deprecateLevel(item, alternative, version, level)
+}
+
+// See Deprecate for details.
+func DeprecateWithLogger(item, alternative string, version string, log logg.Logger) {
+ level := deprecationLogLevelFromVersion(version)
+ deprecateLevelWithLogger(item, alternative, version, level, log)
+}
+
+// DeprecateLevelMin informs about a deprecation starting at the given version, but with a minimum log level.
+func DeprecateLevelMin(item, alternative string, version string, minLevel logg.Level) {
+ level := max(deprecationLogLevelFromVersion(version), minLevel)
+ deprecateLevel(item, alternative, version, level)
+}
+
+// deprecateLevel informs about a deprecation logging at the given level.
+func deprecateLevel(item, alternative, version string, level logg.Level) {
+ deprecateLevelWithLogger(item, alternative, version, level, loggers.Log().Logger())
+}
+
+// DeprecateLevel informs about a deprecation logging at the given level.
+func deprecateLevelWithLogger(item, alternative, version string, level logg.Level, log logg.Logger) {
+ //if strings.Contains(item, "module.mounts.lang") || strings.Contains(item, "includeFiles") {
+ // hdebug.Panicf("Deprecated")
+ //}
+ var msg string
+ if level == logg.LevelError {
+ // Useful to debug deprecation errors that needs to be removedor fixed. Comment out when done debugging.
+ // hdebug.Panicf("deprecation error: %s was removed in Hugo %s. %s", item, version, alternative)
+ msg = fmt.Sprintf("%s was deprecated in Hugo %s and subsequently removed. %s", item, version, alternative)
+ } else {
+ msg = fmt.Sprintf("%s was deprecated in Hugo %s and will be removed in a future release. %s", item, version, alternative)
+ }
+
+ log.WithLevel(level).WithField(loggers.FieldNameCmd, "deprecated").Logf("%s", msg)
+}
+
+// We usually do about one minor version a month.
+// We want people to run at least the current and previous version without any warnings.
+// We want people who don't update Hugo that often to see the warnings and errors before we remove the feature.
+func deprecationLogLevelFromVersion(ver string) logg.Level {
+ from := version.MustParseVersion(ver)
+ to := CurrentVersion
+ minorDiff := to.Minor - from.Minor
+ switch {
+ case minorDiff >= 15:
+ // Start failing the build after about 15 months.
+ return logg.LevelError
+ case minorDiff >= 3:
+ // Start printing warnings after about 3 months.
+ return logg.LevelWarn
+ default:
+ return logg.LevelInfo
+ }
+}
+
+// BuildVersionString creates a version string. This is what you see when
+// running "hugo version".
+func BuildVersionString() string {
+ // program := "Hugo Static Site Generator"
+ program := "hugo"
+
+ version := "v" + CurrentVersion.String()
+
+ bi := getBuildInfo()
+ if bi == nil {
+ return version
+ }
+ if bi.Revision != "" {
+ version += "-" + bi.Revision
+ }
+ if IsExtended {
+ version += "+extended"
+ }
+ if IsWithdeploy {
+ version += "+withdeploy"
+ }
+
+ osArch := bi.GoOS + "/" + bi.GoArch
+
+ date := bi.RevisionTime
+ if date == "" {
+ // Accept vendor-specified build date if .git/ is unavailable.
+ date = buildDate
+ }
+ if date == "" {
+ date = "unknown"
+ }
+
+ versionString := fmt.Sprintf("%s %s %s BuildDate=%s",
+ program, version, osArch, date)
+
+ if vendorInfo != "" {
+ versionString += " VendorInfo=" + vendorInfo
+ }
+
+ return versionString
+}
diff --git a/common/hugo/hugo_integration_test.go b/common/hugo/hugo_integration_test.go
new file mode 100644
index 00000000000..160bea47238
--- /dev/null
+++ b/common/hugo/hugo_integration_test.go
@@ -0,0 +1,113 @@
+// 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 hugo_test
+
+import (
+ "strings"
+ "testing"
+
+ "github.com/gohugoio/hugo/hugolib"
+)
+
+func TestIsMultilingualAndIsMultihost(t *testing.T) {
+ t.Parallel()
+
+ files := `
+-- hugo.toml --
+disableKinds = ['page','rss','section','sitemap','taxonomy','term']
+defaultContentLanguageInSubdir = true
+[languages.de]
+baseURL = 'https://de.example.org/'
+[languages.en]
+baseURL = 'https://en.example.org/'
+-- content/_index.md --
+---
+title: home
+---
+-- layouts/home.html --
+multilingual={{ hugo.IsMultilingual }}
+multihost={{ hugo.IsMultihost }}
+ `
+
+ b := hugolib.Test(t, files)
+
+ b.AssertFileContent("public/de/index.html",
+ "multilingual=true",
+ "multihost=true",
+ )
+ b.AssertFileContent("public/en/index.html",
+ "multilingual=true",
+ "multihost=true",
+ )
+
+ files = strings.ReplaceAll(files, "baseURL = 'https://de.example.org/'", "")
+ files = strings.ReplaceAll(files, "baseURL = 'https://en.example.org/'", "")
+
+ b = hugolib.Test(t, files)
+
+ b.AssertFileContent("public/de/index.html",
+ "multilingual=true",
+ "multihost=false",
+ )
+ b.AssertFileContent("public/en/index.html",
+ "multilingual=true",
+ "multihost=false",
+ )
+
+ files = strings.ReplaceAll(files, "[languages.de]", "")
+ files = strings.ReplaceAll(files, "[languages.en]", "")
+
+ b = hugolib.Test(t, files)
+
+ b.AssertFileContent("public/en/index.html",
+ "multilingual=false",
+ "multihost=false",
+ )
+}
+
+func TestHugoSites(t *testing.T) {
+ t.Parallel()
+
+ files := `
+-- hugo.toml --
+disableKinds = ['page','rss','section','sitemap','taxonomy','term']
+defaultContentLanguage = 'fr'
+defaultContentLanguageInSubdir = true
+defaultContentVersionInSubdir = true
+defaultContentRoleInSubdir = true
+[languages]
+[languages.en]
+weight = 1
+[languages.fr]
+weight = 2
+[languages.de]
+weight = 3
+[roles]
+[roles.guest]
+weight = 1
+[roles.member]
+weight = 2
+[versions]
+[versions.'v1.0.0']
+weight = 1
+[versions.'v2.0.0']
+weight = 2
+-- layouts/home.html --
+{{ range hugo.Sites }}{{ .Language.Name }}-{{ .Role.Name }}-{{ .Version.Name }}|{{ end }}
+ `
+
+ b := hugolib.Test(t, files)
+
+ b.AssertFileContent("public/guest/v1.0.0/en/index.html", "en-guest-v1.0.0|en-member-v1.0.0|en-guest-v2.0.0|en-member-v2.0.0|fr-guest-v1.0.0|fr-member-v1.0.0|fr-guest-v2.0.0|fr-member-v2.0.0|de-guest-v1.0.0|de-member-v1.0.0|de-guest-v2.0.0|de-member-v2.0.0|")
+}
diff --git a/common/hugo/hugo_test.go b/common/hugo/hugo_test.go
index 0631be62c06..a572f74d458 100644
--- a/common/hugo/hugo_test.go
+++ b/common/hugo/hugo_test.go
@@ -14,26 +14,48 @@
package hugo
import (
- "fmt"
+ "context"
"testing"
+ "github.com/bep/logg"
qt "github.com/frankban/quicktest"
)
-func TestHugoInfo(t *testing.T) {
+func TestDeprecationLogLevelFromVersion(t *testing.T) {
c := qt.New(t)
- hugoInfo := NewInfo("")
+ c.Assert(deprecationLogLevelFromVersion("0.55.0"), qt.Equals, logg.LevelError)
+ ver := CurrentVersion
+ c.Assert(deprecationLogLevelFromVersion(ver.String()), qt.Equals, logg.LevelInfo)
+ ver.Minor -= 3
+ c.Assert(deprecationLogLevelFromVersion(ver.String()), qt.Equals, logg.LevelWarn)
+ ver.Minor -= 4
+ c.Assert(deprecationLogLevelFromVersion(ver.String()), qt.Equals, logg.LevelWarn)
+ ver.Minor -= 13
+ c.Assert(deprecationLogLevelFromVersion(ver.String()), qt.Equals, logg.LevelError)
- c.Assert(hugoInfo.Version(), qt.Equals, CurrentVersion.Version())
- c.Assert(fmt.Sprintf("%T", VersionString("")), qt.Equals, fmt.Sprintf("%T", hugoInfo.Version()))
- c.Assert(hugoInfo.CommitHash, qt.Equals, commitHash)
- c.Assert(hugoInfo.BuildDate, qt.Equals, buildDate)
- c.Assert(hugoInfo.Environment, qt.Equals, "production")
- c.Assert(string(hugoInfo.Generator()), qt.Contains, fmt.Sprintf("Hugo %s", hugoInfo.Version()))
- c.Assert(hugoInfo.IsProduction(), qt.Equals, true)
- c.Assert(hugoInfo.IsExtended(), qt.Equals, IsExtended)
+ // Added just to find the threshold for where we can remove deprecated items.
+ // Subtract 5 from the minor version of the first ERRORed version => 0.136.0.
+ c.Assert(deprecationLogLevelFromVersion("0.141.0"), qt.Equals, logg.LevelError)
+}
+
+func TestMarkupScope(t *testing.T) {
+ c := qt.New(t)
+
+ ctx := context.Background()
+ ctx = SetMarkupScope(ctx, "foo")
+
+ var hugoCtx Context
+ c.Assert(hugoCtx.MarkupScope(ctx), qt.Equals, "foo")
+ c.Assert(GetMarkupScope(ctx), qt.Equals, "foo")
+}
+
+func TestGetBuildInfo(t *testing.T) {
+ c := qt.New(t)
- devHugoInfo := NewInfo("development")
- c.Assert(devHugoInfo.IsProduction(), qt.Equals, false)
+ bi := GetBuildInfo()
+ // In test mode, build info may or may not be available.
+ if bi != nil {
+ c.Assert(bi.GoVersion, qt.Not(qt.Equals), "")
+ }
}
diff --git a/common/hugo/vars_extended.go b/common/hugo/vars_extended.go
index bb96bade6c5..ab01e264716 100644
--- a/common/hugo/vars_extended.go
+++ b/common/hugo/vars_extended.go
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// +build extended
+//go:build extended
package hugo
diff --git a/common/hugo/vars_regular.go b/common/hugo/vars_regular.go
index fae18df14d3..a78aeb0b661 100644
--- a/common/hugo/vars_regular.go
+++ b/common/hugo/vars_regular.go
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// +build !extended
+//go:build !extended
package hugo
diff --git a/common/hugo/vars_withdeploy.go b/common/hugo/vars_withdeploy.go
new file mode 100644
index 00000000000..4e0c3efbba0
--- /dev/null
+++ b/common/hugo/vars_withdeploy.go
@@ -0,0 +1,18 @@
+// 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
+
+package hugo
+
+var IsWithdeploy = true
diff --git a/common/hugo/vars_withdeploy_off.go b/common/hugo/vars_withdeploy_off.go
new file mode 100644
index 00000000000..36e9bd874bf
--- /dev/null
+++ b/common/hugo/vars_withdeploy_off.go
@@ -0,0 +1,18 @@
+// 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
+
+package hugo
+
+var IsWithdeploy = false
diff --git a/common/hugo/version.go b/common/hugo/version.go
deleted file mode 100644
index 531f4483dfa..00000000000
--- a/common/hugo/version.go
+++ /dev/null
@@ -1,265 +0,0 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// 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 hugo
-
-import (
- "fmt"
- "io"
- "runtime"
- "strings"
-
- "github.com/gohugoio/hugo/compare"
- "github.com/spf13/cast"
-)
-
-// Version represents the Hugo build version.
-type Version struct {
- // Major and minor version.
- Number float32
-
- // Increment this for bug releases
- PatchLevel int
-
- // HugoVersionSuffix is the suffix used in the Hugo version string.
- // It will be blank for release versions.
- Suffix string
-}
-
-var (
- _ compare.Eqer = (*VersionString)(nil)
- _ compare.Comparer = (*VersionString)(nil)
-)
-
-func (v Version) String() string {
- return version(v.Number, v.PatchLevel, v.Suffix)
-}
-
-// Version returns the Hugo version.
-func (v Version) Version() VersionString {
- return VersionString(v.String())
-}
-
-// VersionString represents a Hugo version string.
-type VersionString string
-
-func (h VersionString) String() string {
- return string(h)
-}
-
-// Compare implements the compare.Comparer interface.
-func (h VersionString) Compare(other interface{}) int {
- v := MustParseVersion(h.String())
- return compareVersionsWithSuffix(v.Number, v.PatchLevel, v.Suffix, other)
-}
-
-// Eq implements the compare.Eqer interface.
-func (h VersionString) Eq(other interface{}) bool {
- s, err := cast.ToStringE(other)
- if err != nil {
- return false
- }
- return s == h.String()
-}
-
-var versionSuffixes = []string{"-test", "-DEV"}
-
-// ParseVersion parses a version string.
-func ParseVersion(s string) (Version, error) {
- var vv Version
- for _, suffix := range versionSuffixes {
- if strings.HasSuffix(s, suffix) {
- vv.Suffix = suffix
- s = strings.TrimSuffix(s, suffix)
- }
- }
-
- v, p := parseVersion(s)
-
- vv.Number = v
- vv.PatchLevel = p
-
- return vv, nil
-}
-
-// MustParseVersion parses a version string
-// and panics if any error occurs.
-func MustParseVersion(s string) Version {
- vv, err := ParseVersion(s)
- if err != nil {
- panic(err)
- }
- return vv
-}
-
-// ReleaseVersion represents the release version.
-func (v Version) ReleaseVersion() Version {
- v.Suffix = ""
- return v
-}
-
-// Next returns the next Hugo release version.
-func (v Version) Next() Version {
- return Version{Number: v.Number + 0.01}
-}
-
-// Prev returns the previous Hugo release version.
-func (v Version) Prev() Version {
- return Version{Number: v.Number - 0.01}
-}
-
-// NextPatchLevel returns the next patch/bugfix Hugo version.
-// This will be a patch increment on the previous Hugo version.
-func (v Version) NextPatchLevel(level int) Version {
- return Version{Number: v.Number - 0.01, PatchLevel: level}
-}
-
-// BuildVersionString creates a version string. This is what you see when
-// running "hugo version".
-func BuildVersionString() string {
- // program := "Hugo Static Site Generator"
- program := "hugo"
-
- version := "v" + CurrentVersion.String()
- if commitHash != "" {
- version += "-" + strings.ToUpper(commitHash)
- }
- if IsExtended {
- version += "+extended"
- }
-
- osArch := runtime.GOOS + "/" + runtime.GOARCH
-
- date := buildDate
- if date == "" {
- date = "unknown"
- }
-
- versionString := fmt.Sprintf("%s %s %s BuildDate=%s",
- program, version, osArch, date)
-
- if vendorInfo != "" {
- versionString += " VendorInfo=" + vendorInfo
- }
-
- return versionString
-}
-
-func version(version float32, patchVersion int, suffix string) string {
- if patchVersion > 0 || version > 0.53 {
- return fmt.Sprintf("%.2f.%d%s", version, patchVersion, suffix)
- }
- return fmt.Sprintf("%.2f%s", version, suffix)
-}
-
-// CompareVersion compares the given version string or number against the
-// running Hugo version.
-// It returns -1 if the given version is less than, 0 if equal and 1 if greater than
-// the running version.
-func CompareVersion(version interface{}) int {
- return compareVersionsWithSuffix(CurrentVersion.Number, CurrentVersion.PatchLevel, CurrentVersion.Suffix, version)
-}
-
-func compareVersions(inVersion float32, inPatchVersion int, in interface{}) int {
- return compareVersionsWithSuffix(inVersion, inPatchVersion, "", in)
-}
-
-func compareVersionsWithSuffix(inVersion float32, inPatchVersion int, suffix string, in interface{}) int {
- var c int
- switch d := in.(type) {
- case float64:
- c = compareFloatVersions(inVersion, float32(d))
- case float32:
- c = compareFloatVersions(inVersion, d)
- case int:
- c = compareFloatVersions(inVersion, float32(d))
- case int32:
- c = compareFloatVersions(inVersion, float32(d))
- case int64:
- c = compareFloatVersions(inVersion, float32(d))
- default:
- s, err := cast.ToStringE(in)
- if err != nil {
- return -1
- }
-
- v, err := ParseVersion(s)
- if err != nil {
- return -1
- }
-
- if v.Number == inVersion && v.PatchLevel == inPatchVersion {
- return strings.Compare(suffix, v.Suffix)
- }
-
- if v.Number < inVersion || (v.Number == inVersion && v.PatchLevel < inPatchVersion) {
- return -1
- }
-
- return 1
- }
-
- if c == 0 && suffix != "" {
- return 1
- }
-
- return c
-}
-
-func parseVersion(s string) (float32, int) {
- var (
- v float32
- p int
- )
-
- if strings.Count(s, ".") == 2 {
- li := strings.LastIndex(s, ".")
- p = cast.ToInt(s[li+1:])
- s = s[:li]
- }
-
- v = float32(cast.ToFloat64(s))
-
- return v, p
-}
-
-func compareFloatVersions(version float32, v float32) int {
- if v == version {
- return 0
- }
- if v < version {
- return -1
- }
- return 1
-}
-
-func GoMinorVersion() int {
- return goMinorVersion(runtime.Version())
-}
-
-func goMinorVersion(version string) int {
- if strings.HasPrefix(version, "devel") {
- return 9999 // magic
- }
- var major, minor int
- var trailing string
- n, err := fmt.Sscanf(version, "go%d.%d%s", &major, &minor, &trailing)
- if n == 2 && err == io.EOF {
- // Means there were no trailing characters (i.e., not an alpha/beta)
- err = nil
- }
- if err != nil {
- return 0
- }
- return minor
-}
diff --git a/common/hugo/version_current.go b/common/hugo/version_current.go
index cd52d447114..35ebbda8556 100644
--- a/common/hugo/version_current.go
+++ b/common/hugo/version_current.go
@@ -1,4 +1,4 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
+// Copyright 2025 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -13,10 +13,13 @@
package hugo
+import "github.com/gohugoio/hugo/common/version"
+
// CurrentVersion represents the current build version.
// This should be the only one.
-var CurrentVersion = Version{
- Number: 0.92,
- PatchLevel: 0,
- Suffix: "-DEV",
+var CurrentVersion = version.Version{
+ Major: 0,
+ Minor: 160,
+ PatchLevel: 1,
+ Suffix: "",
}
diff --git a/common/hugo/version_test.go b/common/hugo/version_test.go
deleted file mode 100644
index 9e0ebb2952f..00000000000
--- a/common/hugo/version_test.go
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright 2015 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 hugo
-
-import (
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestHugoVersion(t *testing.T) {
- c := qt.New(t)
-
- c.Assert(version(0.15, 0, "-DEV"), qt.Equals, "0.15-DEV")
- c.Assert(version(0.15, 2, "-DEV"), qt.Equals, "0.15.2-DEV")
-
- v := Version{Number: 0.21, PatchLevel: 0, Suffix: "-DEV"}
-
- c.Assert(v.ReleaseVersion().String(), qt.Equals, "0.21")
- c.Assert(v.String(), qt.Equals, "0.21-DEV")
- c.Assert(v.Next().String(), qt.Equals, "0.22")
- nextVersionString := v.Next().Version()
- c.Assert(nextVersionString.String(), qt.Equals, "0.22")
- c.Assert(nextVersionString.Eq("0.22"), qt.Equals, true)
- c.Assert(nextVersionString.Eq("0.21"), qt.Equals, false)
- c.Assert(nextVersionString.Eq(nextVersionString), qt.Equals, true)
- c.Assert(v.NextPatchLevel(3).String(), qt.Equals, "0.20.3")
-
- // We started to use full semver versions even for main
- // releases in v0.54.0
- v = Version{Number: 0.53, PatchLevel: 0}
- c.Assert(v.String(), qt.Equals, "0.53")
- c.Assert(v.Next().String(), qt.Equals, "0.54.0")
- c.Assert(v.Next().Next().String(), qt.Equals, "0.55.0")
- v = Version{Number: 0.54, PatchLevel: 0, Suffix: "-DEV"}
- c.Assert(v.String(), qt.Equals, "0.54.0-DEV")
-}
-
-func TestCompareVersions(t *testing.T) {
- c := qt.New(t)
-
- c.Assert(compareVersions(0.20, 0, 0.20), qt.Equals, 0)
- c.Assert(compareVersions(0.20, 0, float32(0.20)), qt.Equals, 0)
- c.Assert(compareVersions(0.20, 0, float64(0.20)), qt.Equals, 0)
- c.Assert(compareVersions(0.19, 1, 0.20), qt.Equals, 1)
- c.Assert(compareVersions(0.19, 3, "0.20.2"), qt.Equals, 1)
- c.Assert(compareVersions(0.19, 1, 0.01), qt.Equals, -1)
- c.Assert(compareVersions(0, 1, 3), qt.Equals, 1)
- c.Assert(compareVersions(0, 1, int32(3)), qt.Equals, 1)
- c.Assert(compareVersions(0, 1, int64(3)), qt.Equals, 1)
- c.Assert(compareVersions(0.20, 0, "0.20"), qt.Equals, 0)
- c.Assert(compareVersions(0.20, 1, "0.20.1"), qt.Equals, 0)
- c.Assert(compareVersions(0.20, 1, "0.20"), qt.Equals, -1)
- c.Assert(compareVersions(0.20, 0, "0.20.1"), qt.Equals, 1)
- c.Assert(compareVersions(0.20, 1, "0.20.2"), qt.Equals, 1)
- c.Assert(compareVersions(0.21, 1, "0.22.1"), qt.Equals, 1)
- c.Assert(compareVersions(0.22, 0, "0.22-DEV"), qt.Equals, -1)
- c.Assert(compareVersions(0.22, 0, "0.22.1-DEV"), qt.Equals, 1)
- c.Assert(compareVersionsWithSuffix(0.22, 0, "-DEV", "0.22"), qt.Equals, 1)
- c.Assert(compareVersionsWithSuffix(0.22, 1, "-DEV", "0.22"), qt.Equals, -1)
- c.Assert(compareVersionsWithSuffix(0.22, 1, "-DEV", "0.22.1-DEV"), qt.Equals, 0)
-}
-
-func TestParseHugoVersion(t *testing.T) {
- c := qt.New(t)
-
- c.Assert(MustParseVersion("0.25").String(), qt.Equals, "0.25")
- c.Assert(MustParseVersion("0.25.2").String(), qt.Equals, "0.25.2")
- c.Assert(MustParseVersion("0.25-test").String(), qt.Equals, "0.25-test")
- c.Assert(MustParseVersion("0.25-DEV").String(), qt.Equals, "0.25-DEV")
-}
-
-func TestGoMinorVersion(t *testing.T) {
- c := qt.New(t)
- c.Assert(goMinorVersion("go1.12.5"), qt.Equals, 12)
- c.Assert(goMinorVersion("go1.14rc1"), qt.Equals, 14)
- c.Assert(GoMinorVersion() >= 11, qt.Equals, true)
-}
diff --git a/common/loggers/handlerdefault.go b/common/loggers/handlerdefault.go
new file mode 100644
index 00000000000..bc3c7eec25b
--- /dev/null
+++ b/common/loggers/handlerdefault.go
@@ -0,0 +1,106 @@
+// 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 contains some basic logging setup.
+package loggers
+
+import (
+ "fmt"
+ "io"
+ "strings"
+ "sync"
+
+ "github.com/bep/logg"
+
+ "github.com/fatih/color"
+)
+
+// levelColor mapping.
+var levelColor = [...]*color.Color{
+ logg.LevelTrace: color.New(color.FgWhite),
+ logg.LevelDebug: color.New(color.FgWhite),
+ logg.LevelInfo: color.New(color.FgBlue),
+ logg.LevelWarn: color.New(color.FgYellow),
+ logg.LevelError: color.New(color.FgRed),
+}
+
+// levelString mapping.
+var levelString = [...]string{
+ logg.LevelTrace: "TRACE",
+ logg.LevelDebug: "DEBUG",
+ logg.LevelInfo: "INFO ",
+ logg.LevelWarn: "WARN ",
+ logg.LevelError: "ERROR",
+}
+
+// newDefaultHandler handler.
+func newDefaultHandler(outWriter, errWriter io.Writer) logg.Handler {
+ return &defaultHandler{
+ outWriter: outWriter,
+ errWriter: errWriter,
+ Padding: 0,
+ }
+}
+
+// Default Handler implementation.
+// Based on https://github.com/apex/log/blob/master/handlers/cli/cli.go
+type defaultHandler struct {
+ mu sync.Mutex
+ outWriter io.Writer // Defaults to os.Stdout.
+ errWriter io.Writer // Defaults to os.Stderr.
+
+ Padding int
+}
+
+// HandleLog implements logg.Handler.
+func (h *defaultHandler) HandleLog(e *logg.Entry) error {
+ color := levelColor[e.Level]
+ level := levelString[e.Level]
+
+ h.mu.Lock()
+ defer h.mu.Unlock()
+
+ var w io.Writer
+ if e.Level > logg.LevelInfo {
+ w = h.errWriter
+ } else {
+ w = h.outWriter
+ }
+
+ var prefix string
+ for _, field := range e.Fields {
+ if field.Name == FieldNameCmd {
+ prefix = fmt.Sprint(field.Value)
+ break
+ }
+ }
+
+ if prefix != "" {
+ prefix = prefix + ": "
+ }
+
+ color.Fprintf(w, "%s %s%s", fmt.Sprintf("%*s", h.Padding+1, level), color.Sprint(prefix), e.Message)
+
+ for _, field := range e.Fields {
+ if strings.HasPrefix(field.Name, reservedFieldNamePrefix) {
+ continue
+ }
+ fmt.Fprintf(w, " %s %v", color.Sprint(field.Name), field.Value)
+ }
+
+ fmt.Fprintln(w)
+
+ return nil
+}
diff --git a/common/loggers/handlersmisc.go b/common/loggers/handlersmisc.go
new file mode 100644
index 00000000000..2ae6300f74b
--- /dev/null
+++ b/common/loggers/handlersmisc.go
@@ -0,0 +1,145 @@
+// 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 (
+ "fmt"
+ "strings"
+ "sync"
+
+ "github.com/bep/logg"
+ "github.com/gohugoio/hugo/common/hashing"
+)
+
+// PanicOnWarningHook panics on warnings.
+var PanicOnWarningHook = func(e *logg.Entry) error {
+ if e.Level != logg.LevelWarn {
+ return nil
+ }
+ panic(e.Message)
+}
+
+func newLogLevelCounter() *logLevelCounter {
+ return &logLevelCounter{
+ counters: make(map[logg.Level]int),
+ }
+}
+
+func newLogOnceHandler(threshold logg.Level) *logOnceHandler {
+ return &logOnceHandler{
+ threshold: threshold,
+ seen: make(map[uint64]bool),
+ }
+}
+
+func newStopHandler(h ...logg.Handler) *stopHandler {
+ return &stopHandler{
+ handlers: h,
+ }
+}
+
+func newSuppressStatementsHandler(statements map[string]bool) *suppressStatementsHandler {
+ return &suppressStatementsHandler{
+ statements: statements,
+ }
+}
+
+type logLevelCounter struct {
+ mu sync.RWMutex
+ counters map[logg.Level]int
+}
+
+func (h *logLevelCounter) HandleLog(e *logg.Entry) error {
+ h.mu.Lock()
+ defer h.mu.Unlock()
+ h.counters[e.Level]++
+ return nil
+}
+
+var errStop = fmt.Errorf("stop")
+
+type logOnceHandler struct {
+ threshold logg.Level
+ mu sync.Mutex
+ seen map[uint64]bool
+}
+
+func (h *logOnceHandler) HandleLog(e *logg.Entry) error {
+ if e.Level < h.threshold {
+ // We typically only want to enable this for warnings and above.
+ // The common use case is that many go routines may log the same error.
+ return nil
+ }
+ h.mu.Lock()
+ defer h.mu.Unlock()
+ hash := hashing.HashUint64(e.Level, e.Message, e.Fields)
+ if h.seen[hash] {
+ return errStop
+ }
+ h.seen[hash] = true
+ return nil
+}
+
+func (h *logOnceHandler) reset() {
+ h.mu.Lock()
+ defer h.mu.Unlock()
+ h.seen = make(map[uint64]bool)
+}
+
+type stopHandler struct {
+ handlers []logg.Handler
+}
+
+// HandleLog implements logg.Handler.
+func (h *stopHandler) HandleLog(e *logg.Entry) error {
+ for _, handler := range h.handlers {
+ if err := handler.HandleLog(e); err != nil {
+ if err == errStop {
+ return nil
+ }
+ return err
+ }
+ }
+ return nil
+}
+
+type suppressStatementsHandler struct {
+ statements map[string]bool
+}
+
+func (h *suppressStatementsHandler) HandleLog(e *logg.Entry) error {
+ for _, field := range e.Fields {
+ if field.Name == FieldNameStatementID {
+ if h.statements[field.Value.(string)] {
+ return errStop
+ }
+ }
+ }
+ return nil
+}
+
+// whiteSpaceTrimmer creates a new log handler that trims whitespace from log messages and string fields.
+func whiteSpaceTrimmer() logg.Handler {
+ return logg.HandlerFunc(func(e *logg.Entry) error {
+ e.Message = strings.TrimSpace(e.Message)
+ for i, field := range e.Fields {
+ if s, ok := field.Value.(string); ok {
+ e.Fields[i].Value = strings.TrimSpace(s)
+ }
+ }
+ return nil
+ })
+}
diff --git a/common/loggers/handlerterminal.go b/common/loggers/handlerterminal.go
new file mode 100644
index 00000000000..c6a86d3a2f1
--- /dev/null
+++ b/common/loggers/handlerterminal.go
@@ -0,0 +1,100 @@
+// 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 (
+ "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 {
+ if predicate == nil {
+ predicate = func(e *logg.Entry) bool { return true }
+ }
+ return &noAnsiEscapeHandler{
+ noLevelPrefix: noLevelPrefix,
+ outWriter: outWriter,
+ errWriter: errWriter,
+ predicate: predicate,
+ }
+}
+
+type noAnsiEscapeHandler struct {
+ mu sync.Mutex
+ outWriter io.Writer
+ errWriter io.Writer
+ predicate func(*logg.Entry) bool
+ noLevelPrefix bool
+}
+
+func (h *noAnsiEscapeHandler) HandleLog(e *logg.Entry) error {
+ if !h.predicate(e) {
+ return nil
+ }
+ h.mu.Lock()
+ defer h.mu.Unlock()
+
+ var w io.Writer
+ if e.Level > logg.LevelInfo {
+ w = h.errWriter
+ } else {
+ w = h.outWriter
+ }
+
+ var prefix string
+ for _, field := range e.Fields {
+ if field.Name == FieldNameCmd {
+ prefix = fmt.Sprint(field.Value)
+ break
+ }
+ }
+
+ if prefix != "" {
+ prefix = prefix + ": "
+ }
+
+ msg := stripANSI(e.Message)
+
+ if h.noLevelPrefix {
+ fmt.Fprintf(w, "%s%s", prefix, msg)
+ } else {
+ fmt.Fprintf(w, "%s %s%s", levelString[e.Level], prefix, msg)
+ }
+
+ for _, field := range e.Fields {
+ if strings.HasPrefix(field.Name, reservedFieldNamePrefix) {
+ continue
+ }
+ fmt.Fprintf(w, " %s %v", field.Name, field.Value)
+
+ }
+ fmt.Fprintln(w)
+
+ 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, "")
+}
diff --git a/common/loggers/handlerterminal_test.go b/common/loggers/handlerterminal_test.go
new file mode 100644
index 00000000000..f45ce80df31
--- /dev/null
+++ b/common/loggers/handlerterminal_test.go
@@ -0,0 +1,40 @@
+// Copyright 2024 The Hugo Authors. All rights reserved.
+// Some functions in this file (see comments) is based on the Go source code,
+// copyright The Go Authors and governed by a BSD-style license.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package loggers
+
+import (
+ "bytes"
+ "testing"
+
+ "github.com/bep/logg"
+ qt "github.com/frankban/quicktest"
+ "github.com/gohugoio/hugo/common/terminal"
+)
+
+func TestNoAnsiEscapeHandler(t *testing.T) {
+ c := qt.New(t)
+
+ test := func(s string) {
+ c.Assert(stripANSI(terminal.Notice(s)), qt.Equals, s)
+ }
+ test(`error in "file.md:1:2"`)
+
+ var buf bytes.Buffer
+ h := newNoAnsiEscapeHandler(&buf, &buf, false, nil)
+ h.HandleLog(&logg.Entry{Message: terminal.Notice(`error in "file.md:1:2"`), Level: logg.LevelInfo})
+
+ c.Assert(buf.String(), qt.Equals, "INFO error in \"file.md:1:2\"\n")
+}
diff --git a/common/loggers/ignorableLogger.go b/common/loggers/ignorableLogger.go
deleted file mode 100644
index 0a130900db0..00000000000
--- a/common/loggers/ignorableLogger.go
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2020 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 loggers
-
-import (
- "fmt"
- "strings"
-)
-
-// IgnorableLogger is a logger that ignores certain log statements.
-type IgnorableLogger interface {
- Logger
- Errorsf(statementID, format string, v ...interface{})
- Apply(logger Logger) IgnorableLogger
-}
-
-type ignorableLogger struct {
- Logger
- statements map[string]bool
-}
-
-// NewIgnorableLogger wraps the given logger and ignores the log statement IDs given.
-func NewIgnorableLogger(logger Logger, statements ...string) IgnorableLogger {
- statementsSet := make(map[string]bool)
- for _, s := range statements {
- statementsSet[strings.ToLower(s)] = true
- }
- return ignorableLogger{
- Logger: logger,
- statements: statementsSet,
- }
-}
-
-// Errorsf logs statementID as an ERROR if not configured as ignoreable.
-func (l ignorableLogger) Errorsf(statementID, format string, v ...interface{}) {
- if l.statements[statementID] {
- // Ignore.
- return
- }
- ignoreMsg := fmt.Sprintf(`
-If you feel that this should not be logged as an ERROR, you can ignore it by adding this to your site config:
-ignoreErrors = [%q]`, statementID)
-
- format += ignoreMsg
-
- l.Errorf(format, v...)
-}
-
-func (l ignorableLogger) Apply(logger Logger) IgnorableLogger {
- return ignorableLogger{
- Logger: logger,
- statements: l.statements,
- }
-}
diff --git a/common/loggers/logger.go b/common/loggers/logger.go
new file mode 100644
index 00000000000..ba26137cd0a
--- /dev/null
+++ b/common/loggers/logger.go
@@ -0,0 +1,398 @@
+// 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 (
+ "fmt"
+ "io"
+ "os"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/bep/logg"
+ "github.com/bep/logg/handlers/multi"
+ "github.com/gohugoio/hugo/common/terminal"
+)
+
+var (
+ reservedFieldNamePrefix = "__h_field_"
+ // FieldNameCmd is the name of the field that holds the command name.
+ FieldNameCmd = reservedFieldNamePrefix + "_cmd"
+ // Used to suppress statements.
+ FieldNameStatementID = reservedFieldNamePrefix + "__h_field_statement_id"
+)
+
+// Options defines options for the logger.
+type Options struct {
+ Level logg.Level
+ StdOut io.Writer
+ StdErr io.Writer
+ DistinctLevel logg.Level
+ StoreErrors bool
+ HandlerPost func(e *logg.Entry) error
+ SuppressStatements map[string]bool
+}
+
+// New creates a new logger with the given options.
+func New(opts Options) Logger {
+ if opts.StdOut == nil {
+ opts.StdOut = os.Stdout
+ }
+ if opts.StdErr == nil {
+ opts.StdErr = os.Stderr
+ }
+
+ if opts.Level == 0 {
+ opts.Level = logg.LevelWarn
+ }
+
+ var logHandler logg.Handler
+ if terminal.PrintANSIColors(os.Stderr) {
+ logHandler = newDefaultHandler(opts.StdErr, opts.StdErr)
+ } else {
+ logHandler = newNoAnsiEscapeHandler(opts.StdErr, opts.StdErr, false, nil)
+ }
+
+ errorsw := &strings.Builder{}
+ logCounters := newLogLevelCounter()
+ handlers := []logg.Handler{
+ logCounters,
+ }
+
+ if opts.Level == logg.LevelTrace {
+ // Trace is used during development only, and it's useful to
+ // only see the trace messages.
+ handlers = append(handlers,
+ logg.HandlerFunc(func(e *logg.Entry) error {
+ if e.Level != logg.LevelTrace {
+ return logg.ErrStopLogEntry
+ }
+ return nil
+ }),
+ )
+ }
+
+ handlers = append(handlers, whiteSpaceTrimmer(), logHandler)
+
+ if opts.HandlerPost != nil {
+ var hookHandler logg.HandlerFunc = func(e *logg.Entry) error {
+ opts.HandlerPost(e)
+ return nil
+ }
+ handlers = append(handlers, hookHandler)
+ }
+
+ if opts.StoreErrors {
+ h := newNoAnsiEscapeHandler(io.Discard, errorsw, true, func(e *logg.Entry) bool {
+ return e.Level >= logg.LevelError
+ })
+
+ handlers = append(handlers, h)
+ }
+
+ logHandler = multi.New(handlers...)
+
+ var logOnce *logOnceHandler
+ if opts.DistinctLevel != 0 {
+ logOnce = newLogOnceHandler(opts.DistinctLevel)
+ logHandler = newStopHandler(logOnce, logHandler)
+ }
+
+ if len(opts.SuppressStatements) > 0 {
+ logHandler = newStopHandler(newSuppressStatementsHandler(opts.SuppressStatements), logHandler)
+ }
+
+ logger := logg.New(
+ logg.Options{
+ Level: opts.Level,
+ Handler: logHandler,
+ },
+ )
+
+ l := logger.WithLevel(opts.Level)
+ logMu := &sync.Mutex{}
+
+ reset := func() {
+ logMu.Lock()
+ defer logMu.Unlock()
+ logCounters.mu.Lock()
+ defer logCounters.mu.Unlock()
+ logCounters.counters = make(map[logg.Level]int)
+ errorsw.Reset()
+ if logOnce != nil {
+ logOnce.reset()
+ }
+ }
+
+ return &logAdapter{
+ mu: logMu,
+ logCounters: logCounters,
+ errors: errorsw,
+ reset: reset,
+ stdOut: opts.StdOut,
+ stdErr: opts.StdErr,
+ level: opts.Level,
+ logger: logger,
+ tracel: l.WithLevel(logg.LevelTrace),
+ debugl: l.WithLevel(logg.LevelDebug),
+ infol: l.WithLevel(logg.LevelInfo),
+ warnl: l.WithLevel(logg.LevelWarn),
+ errorl: l.WithLevel(logg.LevelError),
+ }
+}
+
+// NewDefault creates a new logger with the default options.
+func NewDefault() Logger {
+ opts := Options{
+ DistinctLevel: logg.LevelWarn,
+ Level: logg.LevelWarn,
+ }
+ return New(opts)
+}
+
+func NewTrace() Logger {
+ opts := Options{
+ DistinctLevel: logg.LevelWarn,
+ Level: logg.LevelTrace,
+ }
+ return New(opts)
+}
+
+func LevelLoggerToWriter(l logg.LevelLogger) io.Writer {
+ return logWriter{l: l}
+}
+
+type Logger interface {
+ Debug() logg.LevelLogger
+ DebugCommand(command string) logg.LevelLogger
+ Debugf(format string, v ...any)
+ Debugln(v ...any)
+ Error() logg.LevelLogger
+ Errorf(format string, v ...any)
+ Erroridf(id, format string, v ...any)
+ Errorln(v ...any)
+ Errors() string
+ Info() logg.LevelLogger
+ InfoCommand(command string) logg.LevelLogger
+ Infof(format string, v ...any)
+ Infoln(v ...any)
+ Level() logg.Level
+ LoggCount(logg.Level) int
+ Logger() logg.Logger
+ StdOut() io.Writer
+ StdErr() io.Writer
+ Printf(format string, v ...any)
+ Println(v ...any)
+ PrintTimerIfDelayed(start time.Time, name string)
+ Reset()
+ Warn() logg.LevelLogger
+ WarnCommand(command string) logg.LevelLogger
+ Warnf(format string, v ...any)
+ Warnidf(id, format string, v ...any)
+ Warnln(v ...any)
+ Deprecatef(fail bool, format string, v ...any)
+ Trace(s logg.StringFunc)
+}
+
+type logAdapter struct {
+ mu *sync.Mutex
+ logCounters *logLevelCounter
+ errors *strings.Builder
+ reset func()
+ stdOut io.Writer
+ stdErr io.Writer
+ level logg.Level
+ logger logg.Logger
+ tracel logg.LevelLogger
+ debugl logg.LevelLogger
+ infol logg.LevelLogger
+ warnl logg.LevelLogger
+ errorl logg.LevelLogger
+}
+
+func (l *logAdapter) Debug() logg.LevelLogger {
+ return l.debugl
+}
+
+func (l *logAdapter) DebugCommand(command string) logg.LevelLogger {
+ return l.debugl.WithField(FieldNameCmd, command)
+}
+
+func (l *logAdapter) Debugf(format string, v ...any) {
+ l.debugl.Logf(format, v...)
+}
+
+func (l *logAdapter) Debugln(v ...any) {
+ l.debugl.Logf(l.sprint(v...))
+}
+
+func (l *logAdapter) Info() logg.LevelLogger {
+ return l.infol
+}
+
+func (l *logAdapter) InfoCommand(command string) logg.LevelLogger {
+ return l.infol.WithField(FieldNameCmd, command)
+}
+
+func (l *logAdapter) Infof(format string, v ...any) {
+ l.infol.Logf(format, v...)
+}
+
+func (l *logAdapter) Infoln(v ...any) {
+ l.infol.Logf(l.sprint(v...))
+}
+
+func (l *logAdapter) Level() logg.Level {
+ return l.level
+}
+
+func (l *logAdapter) LoggCount(level logg.Level) int {
+ l.logCounters.mu.RLock()
+ defer l.logCounters.mu.RUnlock()
+ return l.logCounters.counters[level]
+}
+
+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
+}
+
+// PrintTimerIfDelayed prints a time statement to the FEEDBACK logger
+// if considerable time is spent.
+func (l *logAdapter) PrintTimerIfDelayed(start time.Time, name string) {
+ elapsed := time.Since(start)
+ milli := int(1000 * elapsed.Seconds())
+ if milli < 500 {
+ return
+ }
+ fmt.Fprintf(l.stdErr, "%s in %v ms\n", name, milli)
+}
+
+func (l *logAdapter) Printf(format string, v ...any) {
+ // Add trailing newline if not present.
+ if !strings.HasSuffix(format, "\n") {
+ format += "\n"
+ }
+ fmt.Fprintf(l.stdOut, format, v...)
+}
+
+func (l *logAdapter) Println(v ...any) {
+ fmt.Fprintln(l.stdOut, v...)
+}
+
+func (l *logAdapter) Reset() {
+ l.reset()
+}
+
+func (l *logAdapter) Warn() logg.LevelLogger {
+ return l.warnl
+}
+
+func (l *logAdapter) Warnf(format string, v ...any) {
+ l.warnl.Logf(format, v...)
+}
+
+func (l *logAdapter) WarnCommand(command string) logg.LevelLogger {
+ return l.warnl.WithField(FieldNameCmd, command)
+}
+
+func (l *logAdapter) Warnln(v ...any) {
+ l.warnl.Logf(l.sprint(v...))
+}
+
+func (l *logAdapter) Error() logg.LevelLogger {
+ return l.errorl
+}
+
+func (l *logAdapter) Errorf(format string, v ...any) {
+ l.errorl.Logf(format, v...)
+}
+
+func (l *logAdapter) Errorln(v ...any) {
+ l.errorl.Logf(l.sprint(v...))
+}
+
+func (l *logAdapter) Errors() string {
+ l.mu.Lock()
+ defer l.mu.Unlock()
+ return l.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...)
+}
+
+func (l *logAdapter) idfInfoStatement(what, id, format string) string {
+ return fmt.Sprintf("\nYou can suppress this %s by adding the following to your project configuration:\nignoreLogs = ['%s']", what, id)
+}
+
+func (l *logAdapter) Trace(s logg.StringFunc) {
+ l.tracel.Log(s)
+}
+
+func (l *logAdapter) sprint(v ...any) string {
+ return strings.TrimRight(fmt.Sprintln(v...), "\n")
+}
+
+func (l *logAdapter) Deprecatef(fail bool, format string, v ...any) {
+ format = "DEPRECATED: " + format
+ if fail {
+ l.errorl.Logf(format, v...)
+ } else {
+ l.warnl.Logf(format, v...)
+ }
+}
+
+type logWriter struct {
+ l logg.LevelLogger
+}
+
+func (w logWriter) Write(p []byte) (n int, err error) {
+ w.l.Log(logg.String(string(p)))
+ return len(p), nil
+}
+
+func TimeTrackf(l logg.LevelLogger, start time.Time, fields logg.Fields, format string, a ...any) {
+ elapsed := time.Since(start)
+ if fields != nil {
+ l = l.WithFields(fields)
+ }
+ l.WithField("duration", elapsed).Logf(format, a...)
+}
+
+func TimeTrackfn(fn func() (logg.LevelLogger, error)) error {
+ start := time.Now()
+ l, err := fn()
+ elapsed := time.Since(start)
+ l.WithField("duration", elapsed).Logf("")
+ return err
+}
diff --git a/common/loggers/logger_test.go b/common/loggers/logger_test.go
new file mode 100644
index 00000000000..bc8975b06f0
--- /dev/null
+++ b/common/loggers/logger_test.go
@@ -0,0 +1,154 @@
+// 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_test
+
+import (
+ "io"
+ "strings"
+ "testing"
+
+ "github.com/bep/logg"
+ qt "github.com/frankban/quicktest"
+ "github.com/gohugoio/hugo/common/loggers"
+)
+
+func TestLogDistinct(t *testing.T) {
+ c := qt.New(t)
+
+ opts := loggers.Options{
+ DistinctLevel: logg.LevelWarn,
+ StoreErrors: true,
+ StdOut: io.Discard,
+ StdErr: io.Discard,
+ }
+
+ l := loggers.New(opts)
+
+ for range 10 {
+ l.Errorln("error 1")
+ l.Errorln("error 2")
+ l.Warnln("warn 1")
+ }
+ c.Assert(strings.Count(l.Errors(), "error 1"), qt.Equals, 1)
+ c.Assert(l.LoggCount(logg.LevelError), qt.Equals, 2)
+ c.Assert(l.LoggCount(logg.LevelWarn), qt.Equals, 1)
+}
+
+func TestHookLast(t *testing.T) {
+ c := qt.New(t)
+
+ opts := loggers.Options{
+ HandlerPost: func(e *logg.Entry) error {
+ panic(e.Message)
+ },
+ StdOut: io.Discard,
+ StdErr: io.Discard,
+ }
+
+ l := loggers.New(opts)
+
+ c.Assert(func() { l.Warnln("warn 1") }, qt.PanicMatches, "warn 1")
+}
+
+func TestOptionStoreErrors(t *testing.T) {
+ c := qt.New(t)
+
+ var sb strings.Builder
+
+ opts := loggers.Options{
+ StoreErrors: true,
+ StdErr: &sb,
+ StdOut: &sb,
+ }
+
+ l := loggers.New(opts)
+ l.Errorln("error 1")
+ l.Errorln("error 2")
+
+ errorsStr := l.Errors()
+
+ c.Assert(errorsStr, qt.Contains, "error 1")
+ c.Assert(errorsStr, qt.Not(qt.Contains), "ERROR")
+
+ c.Assert(sb.String(), qt.Contains, "error 1")
+ c.Assert(sb.String(), qt.Contains, "ERROR")
+}
+
+func TestLogCount(t *testing.T) {
+ c := qt.New(t)
+
+ opts := loggers.Options{
+ StoreErrors: true,
+ }
+
+ l := loggers.New(opts)
+ l.Errorln("error 1")
+ l.Errorln("error 2")
+ l.Warnln("warn 1")
+
+ c.Assert(l.LoggCount(logg.LevelError), qt.Equals, 2)
+ c.Assert(l.LoggCount(logg.LevelWarn), qt.Equals, 1)
+ c.Assert(l.LoggCount(logg.LevelInfo), qt.Equals, 0)
+}
+
+func TestSuppressStatements(t *testing.T) {
+ c := qt.New(t)
+
+ opts := loggers.Options{
+ StoreErrors: true,
+ SuppressStatements: map[string]bool{
+ "error-1": true,
+ },
+ }
+
+ l := loggers.New(opts)
+ l.Error().WithField(loggers.FieldNameStatementID, "error-1").Logf("error 1")
+ l.Errorln("error 2")
+
+ errorsStr := l.Errors()
+
+ c.Assert(errorsStr, qt.Not(qt.Contains), "error 1")
+ c.Assert(errorsStr, qt.Contains, "error 2")
+ c.Assert(l.LoggCount(logg.LevelError), qt.Equals, 1)
+}
+
+func TestReset(t *testing.T) {
+ c := qt.New(t)
+
+ opts := loggers.Options{
+ StoreErrors: true,
+ DistinctLevel: logg.LevelWarn,
+ StdOut: io.Discard,
+ StdErr: io.Discard,
+ }
+
+ l := loggers.New(opts)
+
+ for range 3 {
+ l.Errorln("error 1")
+ l.Errorln("error 2")
+ l.Errorln("error 1")
+ c.Assert(l.LoggCount(logg.LevelError), qt.Equals, 2)
+
+ l.Reset()
+
+ errorsStr := l.Errors()
+
+ c.Assert(errorsStr, qt.Equals, "")
+ c.Assert(l.LoggCount(logg.LevelError), qt.Equals, 0)
+
+ }
+}
diff --git a/common/loggers/loggerglobal.go b/common/loggers/loggerglobal.go
new file mode 100644
index 00000000000..b8c9a6931d6
--- /dev/null
+++ b/common/loggers/loggerglobal.go
@@ -0,0 +1,62 @@
+// 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 (
+ "sync"
+
+ "github.com/bep/logg"
+)
+
+// SetGlobalLogger sets the global logger.
+// This is used in a few places in Hugo, e.g. deprecated functions.
+func SetGlobalLogger(logger Logger) {
+ logMu.Lock()
+ defer logMu.Unlock()
+ log = logger
+}
+
+func initGlobalLogger(level logg.Level, panicOnWarnings bool) {
+ logMu.Lock()
+ defer logMu.Unlock()
+ var logHookLast func(e *logg.Entry) error
+ if panicOnWarnings {
+ logHookLast = PanicOnWarningHook
+ }
+
+ log = New(
+ Options{
+ Level: level,
+ DistinctLevel: logg.LevelInfo,
+ HandlerPost: logHookLast,
+ },
+ )
+}
+
+var logMu sync.Mutex
+
+func Log() Logger {
+ logMu.Lock()
+ defer logMu.Unlock()
+ return log
+}
+
+// The global logger.
+var log Logger
+
+func init() {
+ initGlobalLogger(logg.LevelWarn, false)
+}
diff --git a/common/loggers/loggers.go b/common/loggers/loggers.go
deleted file mode 100644
index 4ed1880164e..00000000000
--- a/common/loggers/loggers.go
+++ /dev/null
@@ -1,339 +0,0 @@
-// Copyright 2020 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 loggers
-
-import (
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "log"
- "os"
- "regexp"
- "runtime"
- "time"
-
- "github.com/gohugoio/hugo/common/terminal"
-
- jww "github.com/spf13/jwalterweatherman"
-)
-
-// Counts ERROR logs to the global jww logger.
-var GlobalErrorCounter *jww.Counter
-
-func init() {
- GlobalErrorCounter = &jww.Counter{}
- jww.SetLogListeners(jww.LogCounter(GlobalErrorCounter, jww.LevelError))
-}
-
-func LoggerToWriterWithPrefix(logger *log.Logger, prefix string) io.Writer {
- return prefixWriter{
- logger: logger,
- prefix: prefix,
- }
-}
-
-type prefixWriter struct {
- logger *log.Logger
- prefix string
-}
-
-func (w prefixWriter) Write(p []byte) (n int, err error) {
- w.logger.Printf("%s: %s", w.prefix, p)
- return len(p), nil
-}
-
-type Logger interface {
- Printf(format string, v ...interface{})
- Println(v ...interface{})
- PrintTimerIfDelayed(start time.Time, name string)
- Debug() *log.Logger
- Debugf(format string, v ...interface{})
- Debugln(v ...interface{})
- Info() *log.Logger
- Infof(format string, v ...interface{})
- Infoln(v ...interface{})
- Warn() *log.Logger
- Warnf(format string, v ...interface{})
- Warnln(v ...interface{})
- Error() *log.Logger
- Errorf(format string, v ...interface{})
- Errorln(v ...interface{})
- Errors() string
-
- Out() io.Writer
-
- Reset()
-
- // Used in tests.
- LogCounters() *LogCounters
-}
-
-type LogCounters struct {
- ErrorCounter *jww.Counter
- WarnCounter *jww.Counter
-}
-
-type logger struct {
- *jww.Notepad
-
- // The writer that represents stdout.
- // Will be ioutil.Discard when in quiet mode.
- out io.Writer
-
- logCounters *LogCounters
-
- // This is only set in server mode.
- errors *bytes.Buffer
-}
-
-func (l *logger) Printf(format string, v ...interface{}) {
- l.FEEDBACK.Printf(format, v...)
-}
-
-func (l *logger) Println(v ...interface{}) {
- l.FEEDBACK.Println(v...)
-}
-
-func (l *logger) Debug() *log.Logger {
- return l.DEBUG
-}
-
-func (l *logger) Debugf(format string, v ...interface{}) {
- l.DEBUG.Printf(format, v...)
-}
-
-func (l *logger) Debugln(v ...interface{}) {
- l.DEBUG.Println(v...)
-}
-
-func (l *logger) Infof(format string, v ...interface{}) {
- l.INFO.Printf(format, v...)
-}
-
-func (l *logger) Infoln(v ...interface{}) {
- l.INFO.Println(v...)
-}
-
-func (l *logger) Info() *log.Logger {
- return l.INFO
-}
-
-func (l *logger) Warnf(format string, v ...interface{}) {
- l.WARN.Printf(format, v...)
-}
-
-func (l *logger) Warnln(v ...interface{}) {
- l.WARN.Println(v...)
-}
-
-func (l *logger) Warn() *log.Logger {
- return l.WARN
-}
-
-func (l *logger) Errorf(format string, v ...interface{}) {
- l.ERROR.Printf(format, v...)
-}
-
-func (l *logger) Errorln(v ...interface{}) {
- l.ERROR.Println(v...)
-}
-
-func (l *logger) Error() *log.Logger {
- return l.ERROR
-}
-
-func (l *logger) LogCounters() *LogCounters {
- return l.logCounters
-}
-
-func (l *logger) Out() io.Writer {
- return l.out
-}
-
-// PrintTimerIfDelayed prints a time statement to the FEEDBACK logger
-// if considerable time is spent.
-func (l *logger) PrintTimerIfDelayed(start time.Time, name string) {
- elapsed := time.Since(start)
- milli := int(1000 * elapsed.Seconds())
- if milli < 500 {
- return
- }
- l.Printf("%s in %v ms", name, milli)
-}
-
-func (l *logger) PrintTimer(start time.Time, name string) {
- elapsed := time.Since(start)
- milli := int(1000 * elapsed.Seconds())
- l.Printf("%s in %v ms", name, milli)
-}
-
-func (l *logger) Errors() string {
- if l.errors == nil {
- return ""
- }
- return ansiColorRe.ReplaceAllString(l.errors.String(), "")
-}
-
-// Reset resets the logger's internal state.
-func (l *logger) Reset() {
- l.logCounters.ErrorCounter.Reset()
- if l.errors != nil {
- l.errors.Reset()
- }
-}
-
-// NewLogger creates a new Logger for the given thresholds
-func NewLogger(stdoutThreshold, logThreshold jww.Threshold, outHandle, logHandle io.Writer, saveErrors bool) Logger {
- return newLogger(stdoutThreshold, logThreshold, outHandle, logHandle, saveErrors)
-}
-
-// NewDebugLogger is a convenience function to create a debug logger.
-func NewDebugLogger() Logger {
- return NewBasicLogger(jww.LevelDebug)
-}
-
-// NewWarningLogger is a convenience function to create a warning logger.
-func NewWarningLogger() Logger {
- return NewBasicLogger(jww.LevelWarn)
-}
-
-// NewInfoLogger is a convenience function to create a info logger.
-func NewInfoLogger() Logger {
- return NewBasicLogger(jww.LevelInfo)
-}
-
-// NewErrorLogger is a convenience function to create an error logger.
-func NewErrorLogger() Logger {
- return NewBasicLogger(jww.LevelError)
-}
-
-// NewBasicLogger creates a new basic logger writing to Stdout.
-func NewBasicLogger(t jww.Threshold) Logger {
- return newLogger(t, jww.LevelError, os.Stdout, ioutil.Discard, false)
-}
-
-// NewBasicLoggerForWriter creates a new basic logger writing to w.
-func NewBasicLoggerForWriter(t jww.Threshold, w io.Writer) Logger {
- return newLogger(t, jww.LevelError, w, ioutil.Discard, false)
-}
-
-var (
- ansiColorRe = regexp.MustCompile("(?s)\\033\\[\\d*(;\\d*)*m")
- errorRe = regexp.MustCompile("^(ERROR|FATAL|WARN)")
-)
-
-type ansiCleaner struct {
- w io.Writer
-}
-
-func (a ansiCleaner) Write(p []byte) (n int, err error) {
- return a.w.Write(ansiColorRe.ReplaceAll(p, []byte("")))
-}
-
-type labelColorizer struct {
- w io.Writer
-}
-
-func (a labelColorizer) Write(p []byte) (n int, err error) {
- replaced := errorRe.ReplaceAllStringFunc(string(p), func(m string) string {
- switch m {
- case "ERROR", "FATAL":
- return terminal.Error(m)
- case "WARN":
- return terminal.Warning(m)
- default:
- return m
- }
- })
- // io.MultiWriter will abort if we return a bigger write count than input
- // bytes, so we lie a little.
- _, err = a.w.Write([]byte(replaced))
- return len(p), err
-}
-
-// InitGlobalLogger initializes the global logger, used in some rare cases.
-func InitGlobalLogger(stdoutThreshold, logThreshold jww.Threshold, outHandle, logHandle io.Writer) {
- outHandle, logHandle = getLogWriters(outHandle, logHandle)
-
- jww.SetStdoutOutput(outHandle)
- jww.SetLogOutput(logHandle)
- jww.SetLogThreshold(logThreshold)
- jww.SetStdoutThreshold(stdoutThreshold)
-}
-
-func getLogWriters(outHandle, logHandle io.Writer) (io.Writer, io.Writer) {
- isTerm := terminal.IsTerminal(os.Stdout)
- if logHandle != ioutil.Discard && isTerm {
- // Remove any Ansi coloring from log output
- logHandle = ansiCleaner{w: logHandle}
- }
-
- if isTerm {
- outHandle = labelColorizer{w: outHandle}
- }
-
- return outHandle, logHandle
-}
-
-type fatalLogWriter int
-
-func (s fatalLogWriter) Write(p []byte) (n int, err error) {
- trace := make([]byte, 1500)
- runtime.Stack(trace, true)
- fmt.Printf("\n===========\n\n%s\n", trace)
- os.Exit(-1)
-
- return 0, nil
-}
-
-var fatalLogListener = func(t jww.Threshold) io.Writer {
- if t != jww.LevelError {
- // Only interested in ERROR
- return nil
- }
-
- return new(fatalLogWriter)
-}
-
-func newLogger(stdoutThreshold, logThreshold jww.Threshold, outHandle, logHandle io.Writer, saveErrors bool) *logger {
- errorCounter := &jww.Counter{}
- warnCounter := &jww.Counter{}
- outHandle, logHandle = getLogWriters(outHandle, logHandle)
-
- listeners := []jww.LogListener{jww.LogCounter(errorCounter, jww.LevelError), jww.LogCounter(warnCounter, jww.LevelWarn)}
- var errorBuff *bytes.Buffer
- if saveErrors {
- errorBuff = new(bytes.Buffer)
- errorCapture := func(t jww.Threshold) io.Writer {
- if t != jww.LevelError {
- // Only interested in ERROR
- return nil
- }
- return errorBuff
- }
-
- listeners = append(listeners, errorCapture)
- }
-
- return &logger{
- Notepad: jww.NewNotepad(stdoutThreshold, logThreshold, outHandle, logHandle, "", log.Ldate|log.Ltime, listeners...),
- out: outHandle,
- logCounters: &LogCounters{
- ErrorCounter: errorCounter,
- WarnCounter: warnCounter,
- },
- errors: errorBuff,
- }
-}
diff --git a/common/loggers/loggers_test.go b/common/loggers/loggers_test.go
deleted file mode 100644
index 0c0cc859b7d..00000000000
--- a/common/loggers/loggers_test.go
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// 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"
- "fmt"
- "log"
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestLogger(t *testing.T) {
- c := qt.New(t)
- l := NewWarningLogger()
-
- l.Errorln("One error")
- l.Errorln("Two error")
- l.Warnln("A warning")
-
- c.Assert(l.LogCounters().ErrorCounter.Count(), qt.Equals, uint64(2))
-}
-
-func TestLoggerToWriterWithPrefix(t *testing.T) {
- c := qt.New(t)
-
- var b bytes.Buffer
-
- logger := log.New(&b, "", 0)
-
- w := LoggerToWriterWithPrefix(logger, "myprefix")
-
- fmt.Fprint(w, "Hello Hugo!")
-
- c.Assert(b.String(), qt.Equals, "myprefix: Hello Hugo!\n")
-}
diff --git a/common/maps/maps.go b/common/maps/maps.go
deleted file mode 100644
index b5e9ba2f54f..00000000000
--- a/common/maps/maps.go
+++ /dev/null
@@ -1,179 +0,0 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package maps
-
-import (
- "fmt"
- "strings"
-
- "github.com/gohugoio/hugo/common/types"
-
- "github.com/gobwas/glob"
- "github.com/spf13/cast"
-)
-
-// ToStringMapE converts in to map[string]interface{}.
-func ToStringMapE(in interface{}) (map[string]interface{}, error) {
- switch vv := in.(type) {
- case Params:
- return vv, nil
- case map[string]string:
- var m = map[string]interface{}{}
- for k, v := range vv {
- m[k] = v
- }
- return m, nil
-
- default:
- return cast.ToStringMapE(in)
- }
-}
-
-// ToParamsAndPrepare converts in to Params and prepares it for use.
-// If in is nil, an empty map is returned.
-// See PrepareParams.
-func ToParamsAndPrepare(in interface{}) (Params, bool) {
- if types.IsNil(in) {
- return Params{}, true
- }
- m, err := ToStringMapE(in)
- if err != nil {
- return nil, false
- }
- PrepareParams(m)
- return m, true
-}
-
-// MustToParamsAndPrepare calls ToParamsAndPrepare and panics if it fails.
-func MustToParamsAndPrepare(in interface{}) Params {
- if p, ok := ToParamsAndPrepare(in); ok {
- return p
- } else {
- panic(fmt.Sprintf("cannot convert %T to maps.Params", in))
- }
-}
-
-// ToStringMap converts in to map[string]interface{}.
-func ToStringMap(in interface{}) map[string]interface{} {
- m, _ := ToStringMapE(in)
- return m
-}
-
-// ToStringMapStringE converts in to map[string]string.
-func ToStringMapStringE(in interface{}) (map[string]string, error) {
- m, err := ToStringMapE(in)
- if err != nil {
- return nil, err
- }
- return cast.ToStringMapStringE(m)
-}
-
-// ToStringMapString converts in to map[string]string.
-func ToStringMapString(in interface{}) map[string]string {
- m, _ := ToStringMapStringE(in)
- return m
-}
-
-// ToStringMapBool converts in to bool.
-func ToStringMapBool(in interface{}) map[string]bool {
- m, _ := ToStringMapE(in)
- return cast.ToStringMapBool(m)
-}
-
-// ToSliceStringMap converts in to []map[string]interface{}.
-func ToSliceStringMap(in interface{}) ([]map[string]interface{}, error) {
- switch v := in.(type) {
- case []map[string]interface{}:
- return v, nil
- case []interface{}:
- var s []map[string]interface{}
- for _, entry := range v {
- if vv, ok := entry.(map[string]interface{}); ok {
- s = append(s, vv)
- }
- }
- return s, nil
- default:
- return nil, fmt.Errorf("unable to cast %#v of type %T to []map[string]interface{}", in, in)
- }
-}
-
-type keyRename struct {
- pattern glob.Glob
- newKey string
-}
-
-// KeyRenamer supports renaming of keys in a map.
-type KeyRenamer struct {
- renames []keyRename
-}
-
-// NewKeyRenamer creates a new KeyRenamer given a list of pattern and new key
-// value pairs.
-func NewKeyRenamer(patternKeys ...string) (KeyRenamer, error) {
- var renames []keyRename
- for i := 0; i < len(patternKeys); i += 2 {
- g, err := glob.Compile(strings.ToLower(patternKeys[i]), '/')
- if err != nil {
- return KeyRenamer{}, err
- }
- renames = append(renames, keyRename{pattern: g, newKey: patternKeys[i+1]})
- }
-
- return KeyRenamer{renames: renames}, nil
-}
-
-func (r KeyRenamer) getNewKey(keyPath string) string {
- for _, matcher := range r.renames {
- if matcher.pattern.Match(keyPath) {
- return matcher.newKey
- }
- }
-
- return ""
-}
-
-// Rename renames the keys in the given map according
-// to the patterns in the current KeyRenamer.
-func (r KeyRenamer) Rename(m map[string]interface{}) {
- r.renamePath("", m)
-}
-
-func (KeyRenamer) keyPath(k1, k2 string) string {
- k1, k2 = strings.ToLower(k1), strings.ToLower(k2)
- if k1 == "" {
- return k2
- }
- return k1 + "/" + k2
-}
-
-func (r KeyRenamer) renamePath(parentKeyPath string, m map[string]interface{}) {
- for key, val := range m {
- keyPath := r.keyPath(parentKeyPath, key)
- switch val.(type) {
- case map[interface{}]interface{}:
- val = cast.ToStringMap(val)
- r.renamePath(keyPath, val.(map[string]interface{}))
- case map[string]interface{}:
- r.renamePath(keyPath, val.(map[string]interface{}))
- }
-
- newKey := r.getNewKey(keyPath)
-
- if newKey != "" {
- delete(m, key)
- m[newKey] = val
- }
- }
-}
diff --git a/common/maps/maps_get.go b/common/maps/maps_get.go
deleted file mode 100644
index 9289991aefc..00000000000
--- a/common/maps/maps_get.go
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2019 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package maps
-
-import (
- "github.com/spf13/cast"
-)
-
-// GetString tries to get a value with key from map m and convert it to a string.
-// It will return an empty string if not found or if it cannot be convertd to a string.
-func GetString(m map[string]interface{}, key string) string {
- if m == nil {
- return ""
- }
- v, found := m[key]
- if !found {
- return ""
- }
- return cast.ToString(v)
-}
diff --git a/common/maps/maps_test.go b/common/maps/maps_test.go
deleted file mode 100644
index f0c32b9febc..00000000000
--- a/common/maps/maps_test.go
+++ /dev/null
@@ -1,173 +0,0 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package maps
-
-import (
- "fmt"
- "reflect"
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestPrepareParams(t *testing.T) {
- tests := []struct {
- input Params
- expected Params
- }{
- {
- map[string]interface{}{
- "abC": 32,
- },
- Params{
- "abc": 32,
- },
- },
- {
- map[string]interface{}{
- "abC": 32,
- "deF": map[interface{}]interface{}{
- 23: "A value",
- 24: map[string]interface{}{
- "AbCDe": "A value",
- "eFgHi": "Another value",
- },
- },
- "gHi": map[string]interface{}{
- "J": 25,
- },
- "jKl": map[string]string{
- "M": "26",
- },
- },
- Params{
- "abc": 32,
- "def": Params{
- "23": "A value",
- "24": Params{
- "abcde": "A value",
- "efghi": "Another value",
- },
- },
- "ghi": Params{
- "j": 25,
- },
- "jkl": Params{
- "m": "26",
- },
- },
- },
- }
-
- for i, test := range tests {
- t.Run(fmt.Sprint(i), func(t *testing.T) {
- // PrepareParams modifies input.
- PrepareParams(test.input)
- if !reflect.DeepEqual(test.expected, test.input) {
- t.Errorf("[%d] Expected\n%#v, got\n%#v\n", i, test.expected, test.input)
- }
- })
- }
-}
-
-func TestToSliceStringMap(t *testing.T) {
- c := qt.New(t)
-
- tests := []struct {
- input interface{}
- expected []map[string]interface{}
- }{
- {
- input: []map[string]interface{}{
- {"abc": 123},
- },
- expected: []map[string]interface{}{
- {"abc": 123},
- },
- }, {
- input: []interface{}{
- map[string]interface{}{
- "def": 456,
- },
- },
- expected: []map[string]interface{}{
- {"def": 456},
- },
- },
- }
-
- for _, test := range tests {
- v, err := ToSliceStringMap(test.input)
- c.Assert(err, qt.IsNil)
- c.Assert(v, qt.DeepEquals, test.expected)
- }
-}
-
-func TestToParamsAndPrepare(t *testing.T) {
- c := qt.New(t)
- _, ok := ToParamsAndPrepare(map[string]interface{}{"A": "av"})
- c.Assert(ok, qt.IsTrue)
-
- params, ok := ToParamsAndPrepare(nil)
- c.Assert(ok, qt.IsTrue)
- c.Assert(params, qt.DeepEquals, Params{})
-}
-
-func TestRenameKeys(t *testing.T) {
- c := qt.New(t)
-
- m := map[string]interface{}{
- "a": 32,
- "ren1": "m1",
- "ren2": "m1_2",
- "sub": map[string]interface{}{
- "subsub": map[string]interface{}{
- "REN1": "m2",
- "ren2": "m2_2",
- },
- },
- "no": map[string]interface{}{
- "ren1": "m2",
- "ren2": "m2_2",
- },
- }
-
- expected := map[string]interface{}{
- "a": 32,
- "new1": "m1",
- "new2": "m1_2",
- "sub": map[string]interface{}{
- "subsub": map[string]interface{}{
- "new1": "m2",
- "ren2": "m2_2",
- },
- },
- "no": map[string]interface{}{
- "ren1": "m2",
- "ren2": "m2_2",
- },
- }
-
- renamer, err := NewKeyRenamer(
- "{ren1,sub/*/ren1}", "new1",
- "{Ren2,sub/ren2}", "new2",
- )
- c.Assert(err, qt.IsNil)
-
- renamer.Rename(m)
-
- if !reflect.DeepEqual(expected, m) {
- t.Errorf("Expected\n%#v, got\n%#v\n", expected, m)
- }
-}
diff --git a/common/maps/params.go b/common/maps/params.go
deleted file mode 100644
index 15511f56f68..00000000000
--- a/common/maps/params.go
+++ /dev/null
@@ -1,289 +0,0 @@
-// Copyright 2019 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package maps
-
-import (
- "fmt"
- "strings"
-
- "github.com/spf13/cast"
-)
-
-// Params is a map where all keys are lower case.
-type Params map[string]interface{}
-
-// Get does a lower case and nested search in this map.
-// It will return nil if none found.
-func (p Params) Get(indices ...string) interface{} {
- v, _, _ := getNested(p, indices)
- return v
-}
-
-// Set overwrites values in p with values in pp for common or new keys.
-// This is done recursively.
-func (p Params) Set(pp Params) {
- for k, v := range pp {
- vv, found := p[k]
- if !found {
- p[k] = v
- } else {
- switch vvv := vv.(type) {
- case Params:
- if pv, ok := v.(Params); ok {
- vvv.Set(pv)
- } else {
- p[k] = v
- }
- default:
- p[k] = v
- }
- }
- }
-}
-
-// IsZero returns true if p is considered empty.
-func (p Params) IsZero() bool {
- if p == nil || len(p) == 0 {
- return true
- }
-
- if len(p) > 1 {
- return false
- }
-
- for k, _ := range p {
- return k == mergeStrategyKey
- }
-
- return false
-
-}
-
-// Merge transfers values from pp to p for new keys.
-// This is done recursively.
-func (p Params) Merge(pp Params) {
- p.merge("", pp)
-}
-
-// MergeRoot transfers values from pp to p for new keys where p is the
-// root of the tree.
-// This is done recursively.
-func (p Params) MergeRoot(pp Params) {
- ms, _ := p.GetMergeStrategy()
- p.merge(ms, pp)
-}
-
-func (p Params) merge(ps ParamsMergeStrategy, pp Params) {
- ns, found := p.GetMergeStrategy()
-
- var ms = ns
- if !found && ps != "" {
- ms = ps
- }
-
- noUpdate := ms == ParamsMergeStrategyNone
- noUpdate = noUpdate || (ps != "" && ps == ParamsMergeStrategyShallow)
-
- for k, v := range pp {
-
- if k == mergeStrategyKey {
- continue
- }
- vv, found := p[k]
-
- if found {
- // Key matches, if both sides are Params, we try to merge.
- if vvv, ok := vv.(Params); ok {
- if pv, ok := v.(Params); ok {
- vvv.merge(ms, pv)
- }
- }
- } else if !noUpdate {
- p[k] = v
- }
-
- }
-}
-
-func (p Params) GetMergeStrategy() (ParamsMergeStrategy, bool) {
- if v, found := p[mergeStrategyKey]; found {
- if s, ok := v.(ParamsMergeStrategy); ok {
- return s, true
- }
- }
- return ParamsMergeStrategyShallow, false
-}
-
-func (p Params) DeleteMergeStrategy() bool {
- if _, found := p[mergeStrategyKey]; found {
- delete(p, mergeStrategyKey)
- return true
- }
- return false
-}
-
-func (p Params) SetDefaultMergeStrategy(s ParamsMergeStrategy) {
- switch s {
- case ParamsMergeStrategyDeep, ParamsMergeStrategyNone, ParamsMergeStrategyShallow:
- default:
- panic(fmt.Sprintf("invalid merge strategy %q", s))
- }
- p[mergeStrategyKey] = s
-}
-
-func getNested(m map[string]interface{}, indices []string) (interface{}, string, map[string]interface{}) {
- if len(indices) == 0 {
- return nil, "", nil
- }
-
- first := indices[0]
- v, found := m[strings.ToLower(cast.ToString(first))]
- if !found {
- if len(indices) == 1 {
- return nil, first, m
- }
- return nil, "", nil
-
- }
-
- if len(indices) == 1 {
- return v, first, m
- }
-
- switch m2 := v.(type) {
- case Params:
- return getNested(m2, indices[1:])
- case map[string]interface{}:
- return getNested(m2, indices[1:])
- default:
- return nil, "", nil
- }
-}
-
-// GetNestedParam gets the first match of the keyStr in the candidates given.
-// It will first try the exact match and then try to find it as a nested map value,
-// using the given separator, e.g. "mymap.name".
-// It assumes that all the maps given have lower cased keys.
-func GetNestedParam(keyStr, separator string, candidates ...Params) (interface{}, error) {
- keyStr = strings.ToLower(keyStr)
-
- // Try exact match first
- for _, m := range candidates {
- if v, ok := m[keyStr]; ok {
- return v, nil
- }
- }
-
- keySegments := strings.Split(keyStr, separator)
- for _, m := range candidates {
- if v := m.Get(keySegments...); v != nil {
- return v, nil
- }
- }
-
- return nil, nil
-}
-
-func GetNestedParamFn(keyStr, separator string, lookupFn func(key string) interface{}) (interface{}, string, map[string]interface{}, error) {
- keySegments := strings.Split(keyStr, separator)
- if len(keySegments) == 0 {
- return nil, "", nil, nil
- }
-
- first := lookupFn(keySegments[0])
- if first == nil {
- return nil, "", nil, nil
- }
-
- if len(keySegments) == 1 {
- return first, keySegments[0], nil, nil
- }
-
- switch m := first.(type) {
- case map[string]interface{}:
- v, key, owner := getNested(m, keySegments[1:])
- return v, key, owner, nil
- case Params:
- v, key, owner := getNested(m, keySegments[1:])
- return v, key, owner, nil
- }
-
- return nil, "", nil, nil
-}
-
-// ParamsMergeStrategy tells what strategy to use in Params.Merge.
-type ParamsMergeStrategy string
-
-const (
- // Do not merge.
- ParamsMergeStrategyNone ParamsMergeStrategy = "none"
- // Only add new keys.
- ParamsMergeStrategyShallow ParamsMergeStrategy = "shallow"
- // Add new keys, merge existing.
- ParamsMergeStrategyDeep ParamsMergeStrategy = "deep"
-
- mergeStrategyKey = "_merge"
-)
-
-func toMergeStrategy(v interface{}) ParamsMergeStrategy {
- s := ParamsMergeStrategy(cast.ToString(v))
- switch s {
- case ParamsMergeStrategyDeep, ParamsMergeStrategyNone, ParamsMergeStrategyShallow:
- return s
- default:
- return ParamsMergeStrategyDeep
- }
-}
-
-// PrepareParams
-// * makes all the keys in the given map lower cased and will do so
-// * This will modify the map given.
-// * Any nested map[interface{}]interface{}, map[string]interface{},map[string]string will be converted to Params.
-// * Any _merge value will be converted to proper type and value.
-func PrepareParams(m Params) {
- for k, v := range m {
- var retyped bool
- lKey := strings.ToLower(k)
- if lKey == mergeStrategyKey {
- v = toMergeStrategy(v)
- retyped = true
- } else {
- switch vv := v.(type) {
- case map[interface{}]interface{}:
- var p Params = cast.ToStringMap(v)
- v = p
- PrepareParams(p)
- retyped = true
- case map[string]interface{}:
- var p Params = v.(map[string]interface{})
- v = p
- PrepareParams(p)
- retyped = true
- case map[string]string:
- p := make(Params)
- for k, v := range vv {
- p[k] = v
- }
- v = p
- PrepareParams(p)
- retyped = true
- }
- }
-
- if retyped || k != lKey {
- delete(m, k)
- m[lKey] = v
- }
- }
-}
diff --git a/common/maps/params_test.go b/common/maps/params_test.go
deleted file mode 100644
index 5c799aae188..00000000000
--- a/common/maps/params_test.go
+++ /dev/null
@@ -1,170 +0,0 @@
-// Copyright 2019 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package maps
-
-import (
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestGetNestedParam(t *testing.T) {
- m := map[string]interface{}{
- "string": "value",
- "first": 1,
- "with_underscore": 2,
- "nested": map[string]interface{}{
- "color": "blue",
- "nestednested": map[string]interface{}{
- "color": "green",
- },
- },
- }
-
- c := qt.New(t)
-
- must := func(keyStr, separator string, candidates ...Params) interface{} {
- v, err := GetNestedParam(keyStr, separator, candidates...)
- c.Assert(err, qt.IsNil)
- return v
- }
-
- c.Assert(must("first", "_", m), qt.Equals, 1)
- c.Assert(must("First", "_", m), qt.Equals, 1)
- c.Assert(must("with_underscore", "_", m), qt.Equals, 2)
- c.Assert(must("nested_color", "_", m), qt.Equals, "blue")
- c.Assert(must("nested.nestednested.color", ".", m), qt.Equals, "green")
- c.Assert(must("string.name", ".", m), qt.IsNil)
- c.Assert(must("nested.foo", ".", m), qt.IsNil)
-}
-
-// https://github.com/gohugoio/hugo/issues/7903
-func TestGetNestedParamFnNestedNewKey(t *testing.T) {
- c := qt.New(t)
-
- nested := map[string]interface{}{
- "color": "blue",
- }
- m := map[string]interface{}{
- "nested": nested,
- }
-
- existing, nestedKey, owner, err := GetNestedParamFn("nested.new", ".", func(key string) interface{} {
- return m[key]
- })
-
- c.Assert(err, qt.IsNil)
- c.Assert(existing, qt.IsNil)
- c.Assert(nestedKey, qt.Equals, "new")
- c.Assert(owner, qt.DeepEquals, nested)
-}
-
-func TestParamsSetAndMerge(t *testing.T) {
- c := qt.New(t)
-
- createParamsPair := func() (Params, Params) {
- p1 := Params{"a": "av", "c": "cv", "nested": Params{"al2": "al2v", "cl2": "cl2v"}}
- p2 := Params{"b": "bv", "a": "abv", "nested": Params{"bl2": "bl2v", "al2": "al2bv"}, mergeStrategyKey: ParamsMergeStrategyDeep}
- return p1, p2
- }
-
- p1, p2 := createParamsPair()
-
- p1.Set(p2)
-
- c.Assert(p1, qt.DeepEquals, Params{
- "a": "abv",
- "c": "cv",
- "nested": Params{
- "al2": "al2bv",
- "cl2": "cl2v",
- "bl2": "bl2v",
- },
- "b": "bv",
- mergeStrategyKey: ParamsMergeStrategyDeep,
- })
-
- p1, p2 = createParamsPair()
-
- p1.Merge(p2)
-
- // Default is to do a shallow merge.
- c.Assert(p1, qt.DeepEquals, Params{
- "c": "cv",
- "nested": Params{
- "al2": "al2v",
- "cl2": "cl2v",
- },
- "b": "bv",
- "a": "av",
- })
-
- p1, p2 = createParamsPair()
- p1.SetDefaultMergeStrategy(ParamsMergeStrategyNone)
- p1.Merge(p2)
- p1.DeleteMergeStrategy()
-
- c.Assert(p1, qt.DeepEquals, Params{
- "a": "av",
- "c": "cv",
- "nested": Params{
- "al2": "al2v",
- "cl2": "cl2v",
- },
- })
-
- p1, p2 = createParamsPair()
- p1.SetDefaultMergeStrategy(ParamsMergeStrategyShallow)
- p1.Merge(p2)
- p1.DeleteMergeStrategy()
-
- c.Assert(p1, qt.DeepEquals, Params{
- "a": "av",
- "c": "cv",
- "nested": Params{
- "al2": "al2v",
- "cl2": "cl2v",
- },
- "b": "bv",
- })
-
- p1, p2 = createParamsPair()
- p1.SetDefaultMergeStrategy(ParamsMergeStrategyDeep)
- p1.Merge(p2)
- p1.DeleteMergeStrategy()
-
- c.Assert(p1, qt.DeepEquals, Params{
- "nested": Params{
- "al2": "al2v",
- "cl2": "cl2v",
- "bl2": "bl2v",
- },
- "b": "bv",
- "a": "av",
- "c": "cv",
- })
-
-}
-
-func TestParamsIsZero(t *testing.T) {
- c := qt.New(t)
-
- var nilParams Params
-
- c.Assert(Params{}.IsZero(), qt.IsTrue)
- c.Assert(nilParams.IsZero(), qt.IsTrue)
- c.Assert(Params{"foo": "bar"}.IsZero(), qt.IsFalse)
- c.Assert(Params{"_merge": "foo", "foo": "bar"}.IsZero(), qt.IsFalse)
- c.Assert(Params{"_merge": "foo"}.IsZero(), qt.IsTrue)
-}
diff --git a/common/maps/scratch.go b/common/maps/scratch.go
deleted file mode 100644
index 26ffef7d250..00000000000
--- a/common/maps/scratch.go
+++ /dev/null
@@ -1,172 +0,0 @@
-// Copyright 2019 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package maps
-
-import (
- "reflect"
- "sort"
- "sync"
-
- "github.com/gohugoio/hugo/common/collections"
- "github.com/gohugoio/hugo/common/math"
-)
-
-// Scratch is a writable context used for stateful operations in Page/Node rendering.
-type Scratch struct {
- values map[string]interface{}
- mu sync.RWMutex
-}
-
-// Scratcher provides a scratching service.
-type Scratcher interface {
- 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.
-//
-// If the first add for a key is an array or slice, then the next value(s) will be appended.
-func (c *Scratch) Add(key string, newAddend interface{}) (string, error) {
- var newVal interface{}
- c.mu.RLock()
- existingAddend, found := c.values[key]
- c.mu.RUnlock()
- if found {
- var err error
-
- addendV := reflect.TypeOf(existingAddend)
-
- if addendV.Kind() == reflect.Slice || addendV.Kind() == reflect.Array {
- newVal, err = collections.Append(existingAddend, newAddend)
- if err != nil {
- return "", err
- }
- } else {
- newVal, err = math.DoArithmetic(existingAddend, newAddend, '+')
- if err != nil {
- return "", err
- }
- }
- } else {
- newVal = newAddend
- }
- c.mu.Lock()
- c.values[key] = newVal
- c.mu.Unlock()
- return "", nil // have to return something to make it work with the Go templates
-}
-
-// Set stores a value with the given key in the Node context.
-// This value can later be retrieved with Get.
-func (c *Scratch) Set(key string, value interface{}) string {
- c.mu.Lock()
- c.values[key] = value
- c.mu.Unlock()
- return ""
-}
-
-// Delete deletes the given key.
-func (c *Scratch) Delete(key string) string {
- c.mu.Lock()
- delete(c.values, key)
- c.mu.Unlock()
- return ""
-}
-
-// Get returns a value previously set by Add or Set.
-func (c *Scratch) Get(key string) interface{} {
- c.mu.RLock()
- val := c.values[key]
- c.mu.RUnlock()
-
- return val
-}
-
-// Values returns the raw backing map. Note that you should just use
-// this method on the locally scoped Scratch instances you obtain via newScratch, not
-// .Page.Scratch etc., as that will lead to concurrency issues.
-func (c *Scratch) Values() map[string]interface{} {
- c.mu.RLock()
- defer c.mu.RUnlock()
- return c.values
-}
-
-// SetInMap stores a value to a map with the given key in the Node context.
-// This map can later be retrieved with GetSortedMapValues.
-func (c *Scratch) SetInMap(key string, mapKey string, value interface{}) string {
- c.mu.Lock()
- _, found := c.values[key]
- if !found {
- c.values[key] = make(map[string]interface{})
- }
-
- c.values[key].(map[string]interface{})[mapKey] = value
- c.mu.Unlock()
- return ""
-}
-
-// DeleteInMap deletes a value to a map with the given key in the Node context.
-func (c *Scratch) DeleteInMap(key string, mapKey string) string {
- c.mu.Lock()
- _, found := c.values[key]
- if found {
- delete(c.values[key].(map[string]interface{}), mapKey)
- }
- c.mu.Unlock()
- return ""
-}
-
-// GetSortedMapValues returns a sorted map previously filled with SetInMap.
-func (c *Scratch) GetSortedMapValues(key string) interface{} {
- c.mu.RLock()
-
- if c.values[key] == nil {
- c.mu.RUnlock()
- return nil
- }
-
- unsortedMap := c.values[key].(map[string]interface{})
- c.mu.RUnlock()
- var keys []string
- for mapKey := range unsortedMap {
- keys = append(keys, mapKey)
- }
-
- sort.Strings(keys)
-
- sortedArray := make([]interface{}, len(unsortedMap))
- for i, mapKey := range keys {
- sortedArray[i] = unsortedMap[mapKey]
- }
-
- return sortedArray
-}
-
-// NewScratch returns a new instance of Scratch.
-func NewScratch() *Scratch {
- return &Scratch{values: make(map[string]interface{})}
-}
diff --git a/common/math/math.go b/common/math/math.go
index cd06379aa50..f88fbcd9cb6 100644
--- a/common/math/math.go
+++ b/common/math/math.go
@@ -20,35 +20,38 @@ import (
// DoArithmetic performs arithmetic operations (+,-,*,/) using reflection to
// determine the type of the two terms.
-func DoArithmetic(a, b interface{}, op rune) (interface{}, error) {
+func DoArithmetic(a, b any, op rune) (any, error) {
av := reflect.ValueOf(a)
bv := reflect.ValueOf(b)
var ai, bi int64
var af, bf float64
var au, bu uint64
+ var isInt, isFloat, isUint bool
switch av.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
ai = av.Int()
switch bv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ isInt = true
bi = bv.Int()
case reflect.Float32, reflect.Float64:
+ isFloat = true
af = float64(ai) // may overflow
- ai = 0
bf = bv.Float()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
bu = bv.Uint()
if ai >= 0 {
+ isUint = true
au = uint64(ai)
- ai = 0
} else {
+ isInt = true
bi = int64(bu) // may overflow
- bu = 0
}
default:
return nil, errors.New("can't apply the operator to the values")
}
case reflect.Float32, reflect.Float64:
+ isFloat = true
af = av.Float()
switch bv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
@@ -66,17 +69,18 @@ func DoArithmetic(a, b interface{}, op rune) (interface{}, error) {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
bi = bv.Int()
if bi >= 0 {
+ isUint = true
bu = uint64(bi)
- bi = 0
} else {
+ isInt = true
ai = int64(au) // may overflow
- au = 0
}
case reflect.Float32, reflect.Float64:
+ isFloat = true
af = float64(au) // may overflow
- au = 0
bf = bv.Float()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ isUint = true
bu = bv.Uint()
default:
return nil, errors.New("can't apply the operator to the values")
@@ -94,38 +98,32 @@ func DoArithmetic(a, b interface{}, op rune) (interface{}, error) {
switch op {
case '+':
- if ai != 0 || bi != 0 {
+ if isInt {
return ai + bi, nil
- } else if af != 0 || bf != 0 {
+ } else if isFloat {
return af + bf, nil
- } else if au != 0 || bu != 0 {
- return au + bu, nil
}
- return 0, nil
+ return au + bu, nil
case '-':
- if ai != 0 || bi != 0 {
+ if isInt {
return ai - bi, nil
- } else if af != 0 || bf != 0 {
+ } else if isFloat {
return af - bf, nil
- } else if au != 0 || bu != 0 {
- return au - bu, nil
}
- return 0, nil
+ return au - bu, nil
case '*':
- if ai != 0 || bi != 0 {
+ if isInt {
return ai * bi, nil
- } else if af != 0 || bf != 0 {
+ } else if isFloat {
return af * bf, nil
- } else if au != 0 || bu != 0 {
- return au * bu, nil
}
- return 0, nil
+ return au * bu, nil
case '/':
- if bi != 0 {
+ if isInt && bi != 0 {
return ai / bi, nil
- } else if bf != 0 {
+ } else if isFloat && bf != 0 {
return af / bf, nil
- } else if bu != 0 {
+ } else if isUint && bu != 0 {
return au / bu, nil
}
return nil, errors.New("can't divide the value by 0")
diff --git a/common/math/math_test.go b/common/math/math_test.go
index a1170186289..d75d30a6999 100644
--- a/common/math/math_test.go
+++ b/common/math/math_test.go
@@ -24,16 +24,18 @@ func TestDoArithmetic(t *testing.T) {
c := qt.New(t)
for _, test := range []struct {
- a interface{}
- b interface{}
+ a any
+ b any
op rune
- expect interface{}
+ expect any
}{
{3, 2, '+', int64(5)},
+ {0, 0, '+', int64(0)},
{3, 2, '-', int64(1)},
{3, 2, '*', int64(6)},
{3, 2, '/', int64(1)},
{3.0, 2, '+', float64(5)},
+ {0.0, 0, '+', float64(0.0)},
{3.0, 2, '-', float64(1)},
{3.0, 2, '*', float64(6)},
{3.0, 2, '/', float64(1.5)},
@@ -42,18 +44,22 @@ func TestDoArithmetic(t *testing.T) {
{3, 2.0, '*', float64(6)},
{3, 2.0, '/', float64(1.5)},
{3.0, 2.0, '+', float64(5)},
+ {0.0, 0.0, '+', float64(0.0)},
{3.0, 2.0, '-', float64(1)},
{3.0, 2.0, '*', float64(6)},
{3.0, 2.0, '/', float64(1.5)},
{uint(3), uint(2), '+', uint64(5)},
+ {uint(0), uint(0), '+', uint64(0)},
{uint(3), uint(2), '-', uint64(1)},
{uint(3), uint(2), '*', uint64(6)},
{uint(3), uint(2), '/', uint64(1)},
{uint(3), 2, '+', uint64(5)},
+ {uint(0), 0, '+', uint64(0)},
{uint(3), 2, '-', uint64(1)},
{uint(3), 2, '*', uint64(6)},
{uint(3), 2, '/', uint64(1)},
{3, uint(2), '+', uint64(5)},
+ {0, uint(0), '+', uint64(0)},
{3, uint(2), '-', uint64(1)},
{3, uint(2), '*', uint64(6)},
{3, uint(2), '/', uint64(1)},
@@ -66,16 +72,15 @@ func TestDoArithmetic(t *testing.T) {
{-3, uint(2), '*', int64(-6)},
{-3, uint(2), '/', int64(-1)},
{uint(3), 2.0, '+', float64(5)},
+ {uint(0), 0.0, '+', float64(0)},
{uint(3), 2.0, '-', float64(1)},
{uint(3), 2.0, '*', float64(6)},
{uint(3), 2.0, '/', float64(1.5)},
{3.0, uint(2), '+', float64(5)},
+ {0.0, uint(0), '+', float64(0)},
{3.0, uint(2), '-', float64(1)},
{3.0, uint(2), '*', float64(6)},
{3.0, uint(2), '/', float64(1.5)},
- {0, 0, '+', 0},
- {0, 0, '-', 0},
- {0, 0, '*', 0},
{"foo", "bar", '+', "foobar"},
{3, 0, '/', false},
{3.0, 0, '/', false},
diff --git a/common/para/para.go b/common/para/para.go
index 69bfc205b8b..c323a3073a4 100644
--- a/common/para/para.go
+++ b/common/para/para.go
@@ -27,7 +27,7 @@ type Workers struct {
// Runner wraps the lifecycle methods of a new task set.
//
-// Run wil block until a worker is available or the context is cancelled,
+// Run will block until a worker is available or the context is cancelled,
// and then run the given func in a new goroutine.
// Wait will wait for all the running goroutines to finish.
type Runner interface {
diff --git a/common/para/para_test.go b/common/para/para_test.go
index 646b7b36b77..3d28a017d7b 100644
--- a/common/para/para_test.go
+++ b/common/para/para_test.go
@@ -32,8 +32,9 @@ func TestPara(t *testing.T) {
t.Skipf("skip para test, CPU count is %d", runtime.NumCPU())
}
- if !htesting.IsCI() {
- t.Skip("skip para test when not running on CI")
+ // TODO(bep)
+ if htesting.IsCI() {
+ t.Skip("skip para test when running on CI")
}
c := qt.New(t)
@@ -41,7 +42,7 @@ func TestPara(t *testing.T) {
c.Run("Order", func(c *qt.C) {
n := 500
ints := make([]int, n)
- for i := 0; i < n; i++ {
+ for i := range n {
ints[i] = i
}
@@ -50,8 +51,7 @@ func TestPara(t *testing.T) {
var result []int
var mu sync.Mutex
- for i := 0; i < n; i++ {
- i := i
+ for i := range n {
r.Run(func() error {
mu.Lock()
defer mu.Unlock()
@@ -77,7 +77,7 @@ func TestPara(t *testing.T) {
var counter int64
- for i := 0; i < n; i++ {
+ for range n {
r.Run(func() error {
atomic.AddInt64(&counter, 1)
time.Sleep(1 * time.Millisecond)
diff --git a/common/paths/path.go b/common/paths/path.go
index 0237dd9f22a..e9b9108f514 100644
--- a/common/paths/path.go
+++ b/common/paths/path.go
@@ -16,15 +16,18 @@ package paths
import (
"errors"
"fmt"
- "os"
+ "net/url"
"path"
"path/filepath"
- "regexp"
"strings"
+ "unicode"
)
// FilePathSeparator as defined by os.Separator.
-const FilePathSeparator = string(filepath.Separator)
+const (
+ FilePathSeparator = string(filepath.Separator)
+ slash = "/"
+)
// filepathPathBridge is a bridge for common functionality in filepath vs path
type filepathPathBridge interface {
@@ -36,8 +39,7 @@ type filepathPathBridge interface {
Separator() string
}
-type filepathBridge struct {
-}
+type filepathBridge struct{}
func (filepathBridge) Base(in string) string {
return filepath.Base(in)
@@ -65,9 +67,37 @@ func (filepathBridge) Separator() string {
var fpb filepathBridge
-// ToSlashTrimLeading is just a filepath.ToSlash with an added / prefix trimmer.
-func ToSlashTrimLeading(s string) string {
- return strings.TrimPrefix(filepath.ToSlash(s), "/")
+// AbsPathify creates an absolute path if given a working dir and a relative path.
+// If already absolute, the path is just cleaned.
+func AbsPathify(workingDir, inPath string) string {
+ if filepath.IsAbs(inPath) {
+ return filepath.Clean(inPath)
+ }
+ return filepath.Join(workingDir, inPath)
+}
+
+// AddTrailingSlash adds a trailing Unix styled slash (/) if not already
+// there.
+func AddTrailingSlash(path string) string {
+ if !strings.HasSuffix(path, "/") {
+ path += "/"
+ }
+ return path
+}
+
+// AddLeadingSlash adds a leading Unix styled slash (/) if not already
+// there.
+func AddLeadingSlash(path string) string {
+ if !strings.HasPrefix(path, "/") {
+ path = "/" + path
+ }
+ return path
+}
+
+// AddTrailingAndLeadingSlash adds a leading and trailing Unix styled slash (/) if not already
+// there.
+func AddLeadingAndTrailingSlash(path string) string {
+ return AddTrailingSlash(AddLeadingSlash(path))
}
// MakeTitle converts the path given to a suitable title, trimming whitespace
@@ -85,50 +115,13 @@ func ReplaceExtension(path string, newExt string) string {
func makePathRelative(inPath string, possibleDirectories ...string) (string, error) {
for _, currentPath := range possibleDirectories {
- if strings.HasPrefix(inPath, currentPath) {
- return strings.TrimPrefix(inPath, currentPath), nil
+ if after, ok := strings.CutPrefix(inPath, currentPath); ok {
+ return after, nil
}
}
return inPath, errors.New("can't extract relative path, unknown prefix")
}
-// Should be good enough for Hugo.
-var isFileRe = regexp.MustCompile(`.*\..{1,6}$`)
-
-// GetDottedRelativePath expects a relative path starting after the content directory.
-// It returns a relative path with dots ("..") navigating up the path structure.
-func GetDottedRelativePath(inPath string) string {
- inPath = filepath.Clean(filepath.FromSlash(inPath))
-
- if inPath == "." {
- return "./"
- }
-
- if !isFileRe.MatchString(inPath) && !strings.HasSuffix(inPath, FilePathSeparator) {
- inPath += FilePathSeparator
- }
-
- if !strings.HasPrefix(inPath, FilePathSeparator) {
- inPath = FilePathSeparator + inPath
- }
-
- dir, _ := filepath.Split(inPath)
-
- sectionCount := strings.Count(dir, FilePathSeparator)
-
- if sectionCount == 0 || dir == FilePathSeparator {
- return "./"
- }
-
- var dottedPath string
-
- for i := 1; i < sectionCount; i++ {
- dottedPath += "../"
- }
-
- return dottedPath
-}
-
// ExtNoDelimiter takes a path and returns the extension, excluding the delimiter, i.e. "md".
func ExtNoDelimiter(in string) string {
return strings.TrimPrefix(Ext(in), ".")
@@ -165,12 +158,6 @@ func Filename(in string) (name string) {
return
}
-// PathNoExt takes a path, strips out the extension,
-// and returns the name of the file.
-func PathNoExt(in string) string {
- return strings.TrimSuffix(in, path.Ext(in))
-}
-
// FileAndExt returns the filename and any extension of a file path as
// two separate strings.
//
@@ -207,7 +194,7 @@ func extractFilename(in, ext, base, pathSeparator string) (name string) {
// return the filename minus the extension (and the ".")
name = base[:strings.LastIndex(base, ".")]
} else {
- // no extension case so just return base, which willi
+ // no extension case so just return base, which will
// be the filename
name = base
}
@@ -233,23 +220,6 @@ func GetRelativePath(path, base string) (final string, err error) {
return name, nil
}
-// PathPrep prepares the path using the uglify setting to create paths on
-// either the form /section/name/index.html or /section/name.html.
-func PathPrep(ugly bool, in string) string {
- if ugly {
- return Uglify(in)
- }
- return PrettifyPath(in)
-}
-
-// PrettifyPath is the same as PrettifyURLPath but for file paths.
-// /section/name.html becomes /section/name/index.html
-// /section/name/ becomes /section/name/index.html
-// /section/name/index.html becomes /section/name/index.html
-func PrettifyPath(in string) string {
- return prettifyPath(in, fpb)
-}
-
func prettifyPath(in string, b filepathPathBridge) string {
if filepath.Ext(in) == "" {
// /section/name/ -> /section/name/index.html
@@ -267,46 +237,194 @@ func prettifyPath(in string, b filepathPathBridge) string {
return b.Join(b.Dir(in), name, "index"+ext)
}
-type NamedSlice struct {
- Name string
- Slice []string
-}
+// CommonDirPath returns the common directory of the given paths.
+func CommonDirPath(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, "/")
+
+ var common []string
-func (n NamedSlice) String() string {
- if len(n.Slice) == 0 {
- return n.Name
+ for i := 0; i < len(p1) && i < len(p2); i++ {
+ if p1[i] == p2[i] {
+ common = append(common, p1[i])
+ } else {
+ break
+ }
+ }
+
+ s := strings.Join(common, "/")
+
+ if hadLeadingSlash && s != "" {
+ s = "/" + s
}
- return fmt.Sprintf("%s%s{%s}", n.Name, FilePathSeparator, strings.Join(n.Slice, ","))
+
+ return s
}
-// FindCWD returns the current working directory from where the Hugo
-// executable is run.
-func FindCWD() (string, error) {
- serverFile, err := filepath.Abs(os.Args[0])
- if err != nil {
- return "", fmt.Errorf("can't get absolute path for executable: %v", err)
+// Sanitize sanitizes string to be used in Hugo's file paths and URLs, allowing only
+// a predefined set of special Unicode characters.
+//
+// Spaces will be replaced with a single hyphen.
+//
+// This function is the core function used to normalize paths in Hugo.
+//
+// Note that this is the first common step for URL/path sanitation,
+// the final URL/path may end up looking differently if the user has stricter rules defined (e.g. removePathAccents=true).
+func Sanitize(s string) string {
+ var willChange bool
+ for i, r := range s {
+ willChange = !isAllowedPathCharacter(s, i, r)
+ if willChange {
+ break
+ }
}
- path := filepath.Dir(serverFile)
- realFile, err := filepath.EvalSymlinks(serverFile)
- if err != nil {
- if _, err = os.Stat(serverFile + ".exe"); err == nil {
- realFile = filepath.Clean(serverFile + ".exe")
+ if !willChange {
+ // Prevent allocation when nothing changes.
+ return s
+ }
+
+ target := make([]rune, 0, len(s))
+ var (
+ prependHyphen bool
+ wasHyphen bool
+ )
+
+ for i, r := range s {
+ isAllowed := isAllowedPathCharacter(s, i, r)
+
+ if isAllowed {
+ // track explicit hyphen in input; no need to add a new hyphen if
+ // we just saw one.
+ wasHyphen = r == '-'
+
+ if prependHyphen {
+ // if currently have a hyphen, don't prepend an extra one
+ if !wasHyphen {
+ target = append(target, '-')
+ }
+ prependHyphen = false
+ }
+ target = append(target, r)
+ } else if len(target) > 0 && !wasHyphen && unicode.IsSpace(r) {
+ prependHyphen = true
}
}
- if err == nil && realFile != serverFile {
- path = filepath.Dir(realFile)
+ return string(target)
+}
+
+func isAllowedPathCharacter(s string, i int, r rune) bool {
+ if r == ' ' {
+ return false
}
+ // Check for the most likely first (faster).
+ isAllowed := unicode.IsLetter(r) || unicode.IsDigit(r)
+ isAllowed = isAllowed || r == '.' || r == '/' || r == '\\' || r == '_' || r == '#' || r == '+' || r == '~' || r == '-' || r == '@'
+ isAllowed = isAllowed || unicode.IsMark(r)
+ isAllowed = isAllowed || (r == '%' && i+2 < len(s) && ishex(s[i+1]) && ishex(s[i+2]))
+ return isAllowed
+}
- return path, nil
+// From https://golang.org/src/net/url/url.go
+func ishex(c byte) bool {
+ switch {
+ case '0' <= c && c <= '9':
+ return true
+ case 'a' <= c && c <= 'f':
+ return true
+ case 'A' <= c && c <= 'F':
+ return true
+ }
+ return false
}
-// AddTrailingSlash adds a trailing Unix styled slash (/) if not already
-// there.
-func AddTrailingSlash(path string) string {
- if !strings.HasSuffix(path, "/") {
- path += "/"
+var slashFunc = func(r rune) bool {
+ return r == '/'
+}
+
+// Dir behaves like path.Dir without the path.Clean step.
+//
+// The returned path ends in a slash only if it is the root "/".
+func Dir(s string) string {
+ dir, _ := path.Split(s)
+ if len(dir) > 1 && dir[len(dir)-1] == '/' {
+ return dir[:len(dir)-1]
}
- return path
+ return dir
+}
+
+// FieldsSlash cuts s into fields separated with '/'.
+func FieldsSlash(s string) []string {
+ f := strings.FieldsFunc(s, slashFunc)
+ return f
+}
+
+// DirFile holds the result from path.Split.
+type DirFile struct {
+ Dir string
+ File string
+}
+
+// Used in test.
+func (df DirFile) String() string {
+ return fmt.Sprintf("%s|%s", df.Dir, df.File)
+}
+
+// PathEscape escapes unicode letters in pth.
+// Use URLEscape to escape full URLs including scheme, query etc.
+// This is slightly faster for the common case.
+// Note, there is a url.PathEscape function, but that also
+// escapes /.
+func PathEscape(pth string) string {
+ u, err := url.Parse(pth)
+ if err != nil {
+ panic(err)
+ }
+ return u.EscapedPath()
+}
+
+// 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, "/")
+}
+
+// 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), "/")
+}
+
+// ToSlashPreserveLeading converts the path given to a forward slash separated path
+// and preserves the leading slash if present trimming any trailing slash.
+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))
}
diff --git a/common/paths/path_test.go b/common/paths/path_test.go
index e55493c7d7c..3fa4c03b280 100644
--- a/common/paths/path_test.go
+++ b/common/paths/path_test.go
@@ -1,4 +1,4 @@
-// Copyright 2021 The Hugo Authors. All rights reserved.
+// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -24,7 +24,7 @@ func TestGetRelativePath(t *testing.T) {
tests := []struct {
path string
base string
- expect interface{}
+ expect any
}{
{filepath.FromSlash("/a/b"), filepath.FromSlash("/a"), filepath.FromSlash("b")},
{filepath.FromSlash("/a/b/c/"), filepath.FromSlash("/a"), filepath.FromSlash("b/c/")},
@@ -75,44 +75,6 @@ func TestMakePathRelative(t *testing.T) {
}
}
-func TestGetDottedRelativePath(t *testing.T) {
- // on Windows this will receive both kinds, both country and western ...
- for _, f := range []func(string) string{filepath.FromSlash, func(s string) string { return s }} {
- doTestGetDottedRelativePath(f, t)
- }
-}
-
-func doTestGetDottedRelativePath(urlFixer func(string) string, t *testing.T) {
- type test struct {
- input, expected string
- }
- data := []test{
- {"", "./"},
- {urlFixer("/"), "./"},
- {urlFixer("post"), "../"},
- {urlFixer("/post"), "../"},
- {urlFixer("post/"), "../"},
- {urlFixer("tags/foo.html"), "../"},
- {urlFixer("/tags/foo.html"), "../"},
- {urlFixer("/post/"), "../"},
- {urlFixer("////post/////"), "../"},
- {urlFixer("/foo/bar/index.html"), "../../"},
- {urlFixer("/foo/bar/foo/"), "../../../"},
- {urlFixer("/foo/bar/foo"), "../../../"},
- {urlFixer("foo/bar/foo/"), "../../../"},
- {urlFixer("foo/bar/foo/bar"), "../../../../"},
- {"404.html", "./"},
- {"404.xml", "./"},
- {"/404.html", "./"},
- }
- for i, d := range data {
- output := GetDottedRelativePath(d.input)
- if d.expected != output {
- t.Errorf("Test %d failed. Expected %q got %q", i, d.expected, output)
- }
- }
-}
-
func TestMakeTitle(t *testing.T) {
type test struct {
input, expected string
@@ -227,26 +189,183 @@ func TestFileAndExt(t *testing.T) {
}
}
-func TestFindCWD(t *testing.T) {
- type test struct {
- expectedDir string
- expectedErr error
+func TestSanitize(t *testing.T) {
+ c := qt.New(t)
+ tests := []struct {
+ input string
+ expected string
+ }{
+ {" Foo bar ", "Foo-bar"},
+ {"Foo.Bar/foo_Bar-Foo", "Foo.Bar/foo_Bar-Foo"},
+ {"fOO,bar:foobAR", "fOObarfoobAR"},
+ {"FOo/BaR.html", "FOo/BaR.html"},
+ {"FOo/Ba---R.html", "FOo/Ba---R.html"}, /// See #10104
+ {"FOo/Ba R.html", "FOo/Ba-R.html"},
+ {"трям/трям", "трям/трям"},
+ {"은행", "은행"},
+ {"Банковский кассир", "Банковский-кассир"},
+ // Issue #1488
+ {"संस्कृत", "संस्कृत"},
+ {"a%C3%B1ame", "a%C3%B1ame"}, // Issue #1292
+ {"this+is+a+test", "this+is+a+test"}, // Issue #1290
+ {"~foo", "~foo"}, // Issue #2177
+
}
- // cwd, _ := os.Getwd()
- data := []test{
- //{cwd, nil},
- // Commenting this out. It doesn't work properly.
- // There's a good reason why we don't use os.Getwd(), it doesn't actually work the way we want it to.
- // I really don't know a better way to test this function. - SPF 2014.11.04
+ for _, test := range tests {
+ c.Assert(Sanitize(test.input), qt.Equals, test.expected)
}
- for i, d := range data {
- dir, err := FindCWD()
- if d.expectedDir != dir {
- t.Errorf("Test %d failed. Expected %q but got %q", i, d.expectedDir, dir)
+}
+
+func BenchmarkSanitize(b *testing.B) {
+ const (
+ allAlowedPath = "foo/bar"
+ spacePath = "foo bar"
+ )
+
+ // This should not allocate any memory.
+ b.Run("All allowed", func(b *testing.B) {
+ for b.Loop() {
+ got := Sanitize(allAlowedPath)
+ if got != allAlowedPath {
+ b.Fatal(got)
+ }
+ }
+ })
+
+ // This will allocate some memory.
+ b.Run("Spaces", func(b *testing.B) {
+ for b.Loop() {
+ got := Sanitize(spacePath)
+ if got != "foo-bar" {
+ b.Fatal(got)
+ }
+ }
+ })
+}
+
+func TestDir(t *testing.T) {
+ c := qt.New(t)
+ c.Assert(Dir("/a/b/c/d"), qt.Equals, "/a/b/c")
+ c.Assert(Dir("/a"), qt.Equals, "/")
+ c.Assert(Dir("/"), qt.Equals, "/")
+ c.Assert(Dir(""), qt.Equals, "")
+}
+
+func TestFieldsSlash(t *testing.T) {
+ c := qt.New(t)
+
+ c.Assert(FieldsSlash("a/b/c"), qt.DeepEquals, []string{"a", "b", "c"})
+ c.Assert(FieldsSlash("/a/b/c"), qt.DeepEquals, []string{"a", "b", "c"})
+ c.Assert(FieldsSlash("/a/b/c/"), qt.DeepEquals, []string{"a", "b", "c"})
+ c.Assert(FieldsSlash("a/b/c/"), qt.DeepEquals, []string{"a", "b", "c"})
+ 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))
+ }
+}
+
+func BenchmarkAddLeadingSlash(b *testing.B) {
+ const (
+ noLeadingSlash = "a/b/c"
+ withLeadingSlash = "/a/b/c"
+ )
+
+ // This should not allocate any memory.
+ b.Run("With leading slash", func(b *testing.B) {
+ for b.Loop() {
+ got := AddLeadingSlash(withLeadingSlash)
+ if got != withLeadingSlash {
+ b.Fatal(got)
+ }
+ }
+ })
+
+ // This will allocate some memory.
+ b.Run("Without leading slash", func(b *testing.B) {
+ for b.Loop() {
+ got := AddLeadingSlash(noLeadingSlash)
+ if got != "/a/b/c" {
+ b.Fatal(got)
+ }
}
- if d.expectedErr != err {
- t.Errorf("Test %d failed. Expected %q but got %q", i, d.expectedErr, err)
+ })
+
+ b.Run("Blank string", func(b *testing.B) {
+ for b.Loop() {
+ got := AddLeadingSlash("")
+ if got != "/" {
+ b.Fatal(got)
+ }
+ }
+ })
+}
+
+func TestPathEscape(t *testing.T) {
+ c := qt.New(t)
+
+ for _, this := range []struct {
+ input string
+ expected string
+ }{
+ {"/tags/欢迎", "/tags/%E6%AC%A2%E8%BF%8E"},
+ {"/path with spaces", "/path%20with%20spaces"},
+ {"/simple-path", "/simple-path"},
+ {"/path/with/slash", "/path/with/slash"},
+ {"/path/with special&chars", "/path/with%20special&chars"},
+ } {
+ in := this.input
+ for range 2 {
+ result := PathEscape(in)
+ c.Assert(result, qt.Equals, this.expected, qt.Commentf("input: %q", this.input))
+ in = result // test idempotency
}
}
}
diff --git a/common/paths/pathparser.go b/common/paths/pathparser.go
new file mode 100644
index 00000000000..5d828df1425
--- /dev/null
+++ b/common/paths/pathparser.go
@@ -0,0 +1,875 @@
+// 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 paths
+
+import (
+ "fmt"
+ "path"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "sync"
+
+ "github.com/gohugoio/hugo/common/hmaps"
+ "github.com/gohugoio/hugo/common/types"
+ "github.com/gohugoio/hugo/hugofs/files"
+ "github.com/gohugoio/hugo/hugolib/sitesmatrix"
+ "github.com/gohugoio/hugo/identity"
+ "github.com/gohugoio/hugo/resources/kinds"
+)
+
+const (
+ identifierBaseof = "baseof"
+ identifierCustomWrapper = "_"
+)
+
+// isCustomWrapperIdentifier tells whether a supplied path is of the form _xyz_.
+// must have non-empty content between the identifierCustomWrapper's to pass.
+func isCustomWrapperIdentifier(s string) bool {
+ return len(s) > 2*len(identifierCustomWrapper) &&
+ strings.HasPrefix(s, identifierCustomWrapper) &&
+ strings.HasSuffix(s, identifierCustomWrapper)
+}
+
+// PathParser parses and manages paths.
+type PathParser struct {
+ // Maps the language code to its index in the languages/sites slice.
+ LanguageIndex map[string]int
+
+ // Reports whether the given language is disabled.
+ IsLangDisabled func(string) bool
+
+ // IsOutputFormat reports whether the given name is a valid output format.
+ // The second argument is optional.
+ IsOutputFormat func(name, ext string) bool
+
+ // Reports whether the given ext is a content file.
+ IsContentExt func(string) bool
+
+ // The configured sites matrix.
+ ConfiguredDimensions *sitesmatrix.ConfiguredDimensions
+
+ // Below gets created on demand.
+ initOnce sync.Once
+ sitesMatrixCache *hmaps.Cache[string, sitesmatrix.VectorStore] // Maps language index to sites matrix vector store.
+}
+
+func (pp *PathParser) init() {
+ pp.initOnce.Do(func() {
+ pp.sitesMatrixCache = hmaps.NewCache[string, sitesmatrix.VectorStore]()
+ })
+}
+
+// NormalizePathString returns a normalized path string using the very basic Hugo rules.
+func NormalizePathStringBasic(s string) string {
+ // All lower case.
+ s = strings.ToLower(s)
+
+ // Replace spaces with hyphens.
+ s = strings.ReplaceAll(s, " ", "-")
+
+ return s
+}
+
+func (pp *PathParser) SitesMatrixFromPath(p *Path) sitesmatrix.VectorStore {
+ pp.init()
+ lang := p.Lang()
+ v, _ := pp.sitesMatrixCache.GetOrCreate(lang, func() (sitesmatrix.VectorStore, error) {
+ builder := sitesmatrix.NewIntSetsBuilder(pp.ConfiguredDimensions)
+ if lang != "" {
+ if idx, ok := pp.LanguageIndex[lang]; ok {
+ builder.WithLanguageIndices(idx)
+ }
+ }
+
+ switch p.Component() {
+ case files.ComponentFolderContent:
+ builder.WithDefaultsIfNotSet()
+ case files.ComponentFolderLayouts:
+ builder.WithAllIfNotSet()
+ case files.ComponentFolderStatic:
+ builder.WithDefaultsAndAllLanguagesIfNotSet()
+ }
+
+ return builder.Build(), nil
+ })
+
+ return v
+}
+
+// ParseIdentity parses component c with path s into a StringIdentity.
+func (pp *PathParser) ParseIdentity(c, s string) identity.StringIdentity {
+ p := pp.parsePooled(c, s)
+ defer putPath(p)
+ return identity.StringIdentity(p.IdentifierBase())
+}
+
+// ParseBaseAndBaseNameNoIdentifier parses component c with path s into a base and a base name without any identifier.
+func (pp *PathParser) ParseBaseAndBaseNameNoIdentifier(c, s string) (string, string) {
+ p := pp.parsePooled(c, s)
+ defer putPath(p)
+ return p.Base(), p.BaseNameNoIdentifier()
+}
+
+func (pp *PathParser) parsePooled(c, s string) *Path {
+ s = NormalizePathStringBasic(s)
+ p := getPath()
+ p.component = c
+ p, err := pp.doParse(c, s, p)
+ if err != nil {
+ panic(err)
+ }
+ return p
+}
+
+// Parse parses component c with path s into Path using Hugo's content path rules.
+func (pp *PathParser) Parse(c, s string) *Path {
+ p, err := pp.parse(c, s)
+ if err != nil {
+ panic(err)
+ }
+ return p
+}
+
+func (pp *PathParser) newPath(component string) *Path {
+ p := &Path{}
+ p.reset()
+ p.component = component
+ return p
+}
+
+func (pp *PathParser) parse(component, s string) (*Path, error) {
+ ss := NormalizePathStringBasic(s)
+
+ p, err := pp.doParse(component, ss, pp.newPath(component))
+ if err != nil {
+ return nil, err
+ }
+
+ if s != ss {
+ var err error
+ // Preserve the original case for titles etc.
+ p.unnormalized, err = pp.doParse(component, s, pp.newPath(component))
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ p.unnormalized = p
+ }
+
+ return p, nil
+}
+
+func (pp *PathParser) parseIdentifier(component, s string, p *Path, i, lastDot, numDots int, isLast bool) {
+ if p.posContainerHigh != -1 {
+ return
+ }
+ mayHaveLang := numDots > 1 && p.posIdentifierLanguage == -1 && pp.LanguageIndex != nil
+ mayHaveLang = mayHaveLang && (component == files.ComponentFolderContent || component == files.ComponentFolderLayouts)
+ mayHaveOutputFormat := component == files.ComponentFolderLayouts
+ mayHaveKind := p.posIdentifierKind == -1 && mayHaveOutputFormat
+ var mayHaveLayout bool
+ if p.pathType == TypeShortcode {
+ mayHaveLayout = !isLast && component == files.ComponentFolderLayouts
+ } else {
+ mayHaveLayout = component == files.ComponentFolderLayouts
+ }
+
+ var found bool
+ var high int
+ if len(p.identifiersKnown) > 0 {
+ high = lastDot
+ } else {
+ high = len(p.s)
+ }
+ id := types.LowHigh[string]{Low: i + 1, High: high}
+ sid := p.s[id.Low:id.High]
+
+ if isCustomWrapperIdentifier(sid) {
+ p.identifiersKnown = append(p.identifiersKnown, id)
+ p.posIdentifierCustom = len(p.identifiersKnown) - 1
+ found = true
+ }
+
+ if len(p.identifiersKnown) == 0 {
+ // The first is always the extension.
+ p.identifiersKnown = append(p.identifiersKnown, id)
+ found = true
+
+ // May also be the output format.
+ if mayHaveOutputFormat && pp.IsOutputFormat(sid, "") {
+ p.posIdentifierOutputFormat = 0
+ }
+ } else {
+ var langFound bool
+
+ if mayHaveLang {
+ var disabled bool
+ _, langFound = pp.LanguageIndex[sid]
+
+ if !langFound {
+ disabled = pp.IsLangDisabled != nil && pp.IsLangDisabled(sid)
+ if disabled {
+ p.disabled = true
+ langFound = true
+ }
+ }
+ found = langFound
+ if langFound {
+ p.identifiersKnown = append(p.identifiersKnown, id)
+ p.posIdentifierLanguage = len(p.identifiersKnown) - 1
+ }
+
+ }
+
+ if !found && mayHaveOutputFormat {
+ // At this point we may already have resolved an output format,
+ // but we need to keep looking for a more specific one, e.g. amp before html.
+ // Use both name and extension to prevent
+ // false positives on the form css.html.
+ if pp.IsOutputFormat(sid, p.Ext()) {
+ found = true
+ p.identifiersKnown = append(p.identifiersKnown, id)
+ p.posIdentifierOutputFormat = len(p.identifiersKnown) - 1
+ }
+ }
+
+ if !found && mayHaveKind {
+ if kinds.GetKindMain(sid) != "" {
+ found = true
+ p.identifiersKnown = append(p.identifiersKnown, id)
+ p.posIdentifierKind = len(p.identifiersKnown) - 1
+ }
+ }
+
+ if !found && sid == identifierBaseof {
+ found = true
+ p.identifiersKnown = append(p.identifiersKnown, id)
+ p.posIdentifierBaseof = len(p.identifiersKnown) - 1
+ }
+
+ if !found && mayHaveLayout {
+ if p.posIdentifierLayout != -1 {
+ // Move it to identifiersUnknown.
+ p.identifiersUnknown = append(p.identifiersUnknown, p.identifiersKnown[p.posIdentifierLayout])
+ p.identifiersKnown[p.posIdentifierLayout] = id
+ } else {
+ p.identifiersKnown = append(p.identifiersKnown, id)
+ p.posIdentifierLayout = len(p.identifiersKnown) - 1
+ }
+ found = true
+ }
+
+ if !found {
+ p.identifiersUnknown = append(p.identifiersUnknown, id)
+ }
+
+ }
+}
+
+func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
+ if runtime.GOOS == "windows" {
+ s = path.Clean(filepath.ToSlash(s))
+ if s == "." {
+ s = ""
+ }
+ }
+
+ if s == "" {
+ s = "/"
+ }
+
+ // Leading slash, no trailing slash.
+ if !strings.HasPrefix(s, "/") {
+ s = "/" + s
+ }
+
+ if s != "/" && s[len(s)-1] == '/' {
+ s = s[:len(s)-1]
+ }
+
+ p.s = s
+ slashCount := 0
+ lastDot := 0
+ lastSlashIdx := strings.LastIndex(s, "/")
+ numDots := strings.Count(s[lastSlashIdx+1:], ".")
+ if strings.Contains(s, "/_shortcodes/") {
+ p.pathType = TypeShortcode
+ }
+
+ for i := len(s) - 1; i >= 0; i-- {
+ c := s[i]
+
+ switch c {
+ case '.':
+ pp.parseIdentifier(component, s, p, i, lastDot, numDots, false)
+ lastDot = i
+ case '/':
+ slashCount++
+ if p.posContainerHigh == -1 {
+ if lastDot > 0 {
+ pp.parseIdentifier(component, s, p, i, lastDot, numDots, true)
+ }
+ p.posContainerHigh = i + 1
+ } else if p.posContainerLow == -1 {
+ p.posContainerLow = i + 1
+ }
+ if i > 0 {
+ p.posSectionHigh = i
+ }
+ }
+ }
+
+ if len(p.identifiersKnown) > 0 {
+ isContentComponent := p.component == files.ComponentFolderContent || p.component == files.ComponentFolderArchetypes
+ isContent := isContentComponent && pp.IsContentExt(p.Ext())
+ id := p.identifiersKnown[len(p.identifiersKnown)-1]
+
+ if id.Low > p.posContainerHigh {
+ b := p.s[p.posContainerHigh : id.Low-1]
+ if isContent {
+ switch b {
+ case "index":
+ p.pathType = TypeLeaf
+ case "_index":
+ p.pathType = TypeBranch
+ default:
+ p.pathType = TypeContentSingle
+ }
+
+ if slashCount == 2 && p.IsLeafBundle() {
+ p.posSectionHigh = 0
+ }
+ } else if b == files.NameContentData && files.IsContentDataExt(p.Ext()) {
+ p.pathType = TypeContentData
+ }
+ }
+ }
+
+ if p.pathType < TypeMarkup && component == files.ComponentFolderLayouts {
+ if p.posIdentifierBaseof != -1 {
+ p.pathType = TypeBaseof
+ } else {
+ pth := p.Path()
+ if strings.Contains(pth, "/_shortcodes/") {
+ p.pathType = TypeShortcode
+ } else if strings.Contains(pth, "/_markup/") {
+ p.pathType = TypeMarkup
+ } else if strings.HasPrefix(pth, "/_partials/") {
+ p.pathType = TypePartial
+ }
+ }
+ }
+
+ if p.pathType == TypeShortcode && p.posIdentifierLayout != -1 {
+ id := p.identifiersKnown[p.posIdentifierLayout]
+ if id.Low == p.posContainerHigh {
+ // First identifier is shortcode name.
+ p.posIdentifierLayout = -1
+ }
+ }
+
+ return p, nil
+}
+
+func ModifyPathBundleTypeResource(p *Path) {
+ if p.IsContent() {
+ p.pathType = TypeContentResource
+ } else {
+ p.pathType = TypeFile
+ }
+}
+
+//go:generate stringer -type Type
+
+type Type int
+
+const (
+
+ // A generic file, e.g. a JSON file.
+ TypeFile Type = iota
+
+ // All below are content files.
+ // A resource of a content type with front matter.
+ TypeContentResource
+
+ // E.g. /blog/my-post.md
+ TypeContentSingle
+
+ // All below are bundled content files.
+
+ // Leaf bundles, e.g. /blog/my-post/index.md
+ TypeLeaf
+
+ // Branch bundles, e.g. /blog/_index.md
+ TypeBranch
+
+ // Content data file, _content.gotmpl.
+ TypeContentData
+
+ // Layout types.
+ TypeMarkup
+ TypeShortcode
+ TypePartial
+ TypeBaseof
+)
+
+type Path struct {
+ // Note: Any additions to this struct should also be added to the pathPool.
+ s string
+
+ posContainerLow int
+ posContainerHigh int
+ posSectionHigh int
+
+ component string
+ pathType Type
+
+ identifiersKnown []types.LowHigh[string]
+ identifiersUnknown []types.LowHigh[string]
+
+ posIdentifierLanguage int
+ posIdentifierOutputFormat int
+ posIdentifierKind int
+ posIdentifierLayout int
+ posIdentifierBaseof int
+ posIdentifierCustom int
+ disabled bool
+
+ trimLeadingSlash bool
+
+ unnormalized *Path
+}
+
+var pathPool = &sync.Pool{
+ New: func() any {
+ p := &Path{}
+ p.reset()
+ return p
+ },
+}
+
+func getPath() *Path {
+ return pathPool.Get().(*Path)
+}
+
+func putPath(p *Path) {
+ p.reset()
+ pathPool.Put(p)
+}
+
+func (p *Path) reset() {
+ p.s = ""
+ p.posContainerLow = -1
+ p.posContainerHigh = -1
+ p.posSectionHigh = -1
+ p.component = ""
+ p.pathType = 0
+ p.identifiersKnown = p.identifiersKnown[:0]
+ p.posIdentifierLanguage = -1
+ p.posIdentifierOutputFormat = -1
+ p.posIdentifierKind = -1
+ p.posIdentifierLayout = -1
+ p.posIdentifierBaseof = -1
+ p.posIdentifierCustom = -1
+ p.disabled = false
+ p.trimLeadingSlash = false
+ p.unnormalized = nil
+}
+
+// TrimLeadingSlash returns a copy of the Path with the leading slash removed.
+func (p Path) TrimLeadingSlash() *Path {
+ p.trimLeadingSlash = true
+ return &p
+}
+
+func (p *Path) norm(s string) string {
+ if p.trimLeadingSlash {
+ s = strings.TrimPrefix(s, "/")
+ }
+ return s
+}
+
+// IdentifierBase satisfies identity.Identity.
+func (p *Path) IdentifierBase() string {
+ if p.Component() == files.ComponentFolderLayouts {
+ return p.Path()
+ }
+ return p.Base()
+}
+
+// Component returns the component for this path (e.g. "content").
+func (p *Path) Component() string {
+ return p.component
+}
+
+// Container returns the base name of the container directory for this path.
+func (p *Path) Container() string {
+ if p.posContainerLow == -1 {
+ return ""
+ }
+ return p.norm(p.s[p.posContainerLow : p.posContainerHigh-1])
+}
+
+func (p *Path) String() string {
+ if p == nil {
+ return ""
+ }
+ return p.Path()
+}
+
+// ContainerDir returns the container directory for this path.
+// For content bundles this will be the parent directory.
+func (p *Path) ContainerDir() string {
+ if p.posContainerLow == -1 || !p.IsBundle() {
+ return p.Dir()
+ }
+ return p.norm(p.s[:p.posContainerLow-1])
+}
+
+// Section returns the first path element (section).
+func (p *Path) Section() string {
+ if p.posSectionHigh <= 0 {
+ return ""
+ }
+ return p.norm(p.s[1:p.posSectionHigh])
+}
+
+// IsContent returns true if the path is a content file (e.g. mypost.md).
+// Note that this will also return true for content files in a bundle.
+func (p *Path) IsContent() bool {
+ return p.Type() >= TypeContentResource && p.Type() <= TypeContentData
+}
+
+// isContentPage returns true if the path is a content file (e.g. mypost.md),
+// but nof if inside a leaf bundle.
+func (p *Path) isContentPage() bool {
+ return p.Type() >= TypeContentSingle && p.Type() <= TypeContentData
+}
+
+// Name returns the last element of path.
+func (p *Path) Name() string {
+ if p.posContainerHigh > 0 {
+ return p.s[p.posContainerHigh:]
+ }
+ return p.s
+}
+
+// Name returns the last element of path without any extension.
+func (p *Path) NameNoExt() string {
+ if i := p.identifierIndex(0); i != -1 {
+ return p.s[p.posContainerHigh : p.identifiersKnown[i].Low-1]
+ }
+ return p.s[p.posContainerHigh:]
+}
+
+// Name returns the last element of path without any language identifier.
+func (p *Path) NameNoLang() string {
+ i := p.identifierIndex(p.posIdentifierLanguage)
+ if i == -1 {
+ return p.Name()
+ }
+
+ return p.s[p.posContainerHigh:p.identifiersKnown[i].Low-1] + p.s[p.identifiersKnown[i].High:]
+}
+
+// BaseNameNoIdentifier returns the logical base name for a resource without any identifier (e.g. no extension).
+// For bundles this will be the containing directory's name, e.g. "blog".
+func (p *Path) BaseNameNoIdentifier() string {
+ if p.IsBundle() {
+ return p.Container()
+ }
+ return p.NameNoIdentifier()
+}
+
+// NameNoIdentifier returns the last element of path without any identifier (e.g. no extension).
+func (p *Path) NameNoIdentifier() string {
+ lowHigh := p.nameLowHigh()
+ return p.s[lowHigh.Low:lowHigh.High]
+}
+
+func (p *Path) nameLowHigh() types.LowHigh[string] {
+ if len(p.identifiersKnown) > 0 {
+ lastID := p.identifiersKnown[len(p.identifiersKnown)-1]
+ if p.posContainerHigh == lastID.Low {
+ // The last identifier is the name.
+ return lastID
+ }
+ return types.LowHigh[string]{
+ Low: p.posContainerHigh,
+ High: p.identifiersKnown[len(p.identifiersKnown)-1].Low - 1,
+ }
+ }
+ return types.LowHigh[string]{
+ Low: p.posContainerHigh,
+ High: len(p.s),
+ }
+}
+
+// Dir returns all but the last element of path, typically the path's directory.
+func (p *Path) Dir() (d string) {
+ if p.posContainerHigh > 0 {
+ d = p.s[:p.posContainerHigh-1]
+ }
+ if d == "" {
+ d = "/"
+ }
+ d = p.norm(d)
+ return
+}
+
+// Path returns the full path.
+func (p *Path) Path() (d string) {
+ return p.norm(p.s)
+}
+
+// PathNoLeadingSlash returns the full path without the leading slash.
+func (p *Path) PathNoLeadingSlash() string {
+ return p.Path()[1:]
+}
+
+// Unnormalized returns the Path with the original case preserved.
+func (p *Path) Unnormalized() *Path {
+ return p.unnormalized
+}
+
+// PathNoLang returns the Path but with any language identifier removed.
+func (p *Path) PathNoLang() string {
+ if p.identifierIndex(p.posIdentifierLanguage) == -1 {
+ return p.Path()
+ }
+ return p.base(true, false)
+}
+
+// PathNoIdentifier returns the Path but with any identifier (ext, lang) removed.
+func (p *Path) PathNoIdentifier() string {
+ return p.base(false, false)
+}
+
+// PathBeforeLangAndOutputFormatAndExt returns the path up to the first identifier that is not a language or output format.
+func (p *Path) PathBeforeLangAndOutputFormatAndExt() string {
+ if len(p.identifiersKnown) == 0 {
+ return p.norm(p.s)
+ }
+ i := p.identifierIndex(0)
+
+ if j := p.posIdentifierOutputFormat; i == -1 || (j != -1 && j < i) {
+ i = j
+ }
+ if j := p.posIdentifierLanguage; i == -1 || (j != -1 && j < i) {
+ i = j
+ }
+
+ if i == -1 {
+ return p.norm(p.s)
+ }
+
+ id := p.identifiersKnown[i]
+ return p.norm(p.s[:id.Low-1])
+}
+
+// PathRel returns the path relative to the given owner.
+func (p *Path) PathRel(owner *Path) string {
+ ob := owner.Base()
+ if !strings.HasSuffix(ob, "/") {
+ ob += "/"
+ }
+ return strings.TrimPrefix(p.Path(), ob)
+}
+
+// BaseRel returns the base path relative to the given owner.
+func (p *Path) BaseRel(owner *Path) string {
+ ob := owner.Base()
+ if ob == "/" {
+ ob = ""
+ }
+ return p.Base()[len(ob)+1:]
+}
+
+// For content files, Base returns the path without any identifiers (extension, language code etc.).
+// Any 'index' as the last path element is ignored.
+//
+// For other files (Resources), any extension is kept.
+func (p *Path) Base() string {
+ s := p.base(!p.isContentPage(), p.IsBundle())
+ if s == "/" && p.isContentPage() {
+ // The content home page is represented as "".
+ s = ""
+ }
+ return s
+}
+
+// Used in template lookups.
+// For pages with Type set, we treat that as the section.
+func (p *Path) BaseReTyped(typ string) (d string) {
+ base := p.Base()
+ if typ == "" || p.Section() == typ {
+ return base
+ }
+ d = "/" + typ
+ if p.posSectionHigh != -1 {
+ d += base[p.posSectionHigh:]
+ }
+ d = p.norm(d)
+ return
+}
+
+// BaseNoLeadingSlash returns the base path without the leading slash.
+func (p *Path) BaseNoLeadingSlash() string {
+ return p.Base()[1:]
+}
+
+func (p *Path) base(preserveExt, isBundle bool) string {
+ if len(p.identifiersKnown) == 0 {
+ return p.norm(p.s)
+ }
+
+ if preserveExt && len(p.identifiersKnown) == 1 {
+ // Preserve extension.
+ return p.norm(p.s)
+ }
+
+ var high int
+
+ if isBundle {
+ high = p.posContainerHigh - 1
+ } else {
+ high = p.nameLowHigh().High
+ }
+
+ if high == 0 {
+ high++
+ }
+
+ if !preserveExt {
+ return p.norm(p.s[:high])
+ }
+
+ // For txt files etc. we want to preserve the extension.
+ id := p.identifiersKnown[0]
+
+ return p.norm(p.s[:high] + p.s[id.Low-1:id.High])
+}
+
+func (p *Path) Ext() string {
+ return p.identifierAsString(0)
+}
+
+func (p *Path) OutputFormat() string {
+ return p.identifierAsString(p.posIdentifierOutputFormat)
+}
+
+func (p *Path) Kind() string {
+ return p.identifierAsString(p.posIdentifierKind)
+}
+
+func (p *Path) Layout() string {
+ return p.identifierAsString(p.posIdentifierLayout)
+}
+
+func (p *Path) Lang() string {
+ return p.identifierAsString(p.posIdentifierLanguage)
+}
+
+func (p *Path) Custom() string {
+ return strings.TrimSuffix(strings.TrimPrefix(p.identifierAsString(p.posIdentifierCustom), identifierCustomWrapper), identifierCustomWrapper)
+}
+
+func (p *Path) Identifier(i int) string {
+ return p.identifierAsString(i)
+}
+
+func (p *Path) Disabled() bool {
+ return p.disabled
+}
+
+func (p *Path) Identifiers() []string {
+ ids := make([]string, len(p.identifiersKnown))
+ for i, id := range p.identifiersKnown {
+ ids[i] = p.s[id.Low:id.High]
+ }
+ return ids
+}
+
+func (p *Path) IdentifiersUnknown() []string {
+ ids := make([]string, len(p.identifiersUnknown))
+ for i, id := range p.identifiersUnknown {
+ ids[i] = p.s[id.Low:id.High]
+ }
+ return ids
+}
+
+func (p *Path) Type() Type {
+ return p.pathType
+}
+
+func (p *Path) IsBundle() bool {
+ return p.pathType >= TypeLeaf && p.pathType <= TypeContentData
+}
+
+func (p *Path) IsBranchBundle() bool {
+ return p.pathType == TypeBranch
+}
+
+func (p *Path) IsLeafBundle() bool {
+ return p.pathType == TypeLeaf
+}
+
+func (p *Path) IsContentData() bool {
+ return p.pathType == TypeContentData
+}
+
+func (p Path) ForType(t Type) *Path {
+ p.pathType = t
+ return &p
+}
+
+func (p *Path) identifierAsString(i int) string {
+ i = p.identifierIndex(i)
+ if i == -1 {
+ return ""
+ }
+
+ id := p.identifiersKnown[i]
+ return p.s[id.Low:id.High]
+}
+
+func (p *Path) identifierIndex(i int) int {
+ if i < 0 || i >= len(p.identifiersKnown) {
+ return -1
+ }
+ return i
+}
+
+// HasExt returns true if the Unix styled path has an extension.
+func HasExt(p string) bool {
+ for i := len(p) - 1; i >= 0; i-- {
+ if p[i] == '.' {
+ return true
+ }
+ if p[i] == '/' {
+ return false
+ }
+ }
+ return false
+}
+
+// ValidateIdentifier returns true if the given string is a valid identifier according
+// to Hugo's basic path normalization rules.
+func ValidateIdentifier(s string) error {
+ if s == NormalizePathStringBasic(s) {
+ return nil
+ }
+ return fmt.Errorf("must be all lower case and no spaces")
+}
diff --git a/common/paths/pathparser_test.go b/common/paths/pathparser_test.go
new file mode 100644
index 00000000000..843b286c224
--- /dev/null
+++ b/common/paths/pathparser_test.go
@@ -0,0 +1,662 @@
+// 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 paths
+
+import (
+ "path/filepath"
+ "testing"
+
+ "github.com/gohugoio/hugo/hugofs/files"
+ "github.com/gohugoio/hugo/hugolib/sitesmatrix"
+ "github.com/gohugoio/hugo/resources/kinds"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func newTestParser() *PathParser {
+ dims := sitesmatrix.NewTestingDimensions([]string{"en", "no", "fr"}, []string{"v1", "v2", "v3"}, []string{"admin", "editor", "viewer", "guest"})
+
+ return &PathParser{
+ LanguageIndex: map[string]int{
+ "no": 0,
+ "en": 1,
+ "fr": 2,
+ },
+ IsContentExt: func(ext string) bool {
+ return ext == "md"
+ },
+ IsOutputFormat: func(name, ext string) bool {
+ switch name {
+ case "html", "amp", "csv", "rss":
+ return true
+ }
+ return false
+ },
+ ConfiguredDimensions: dims,
+ }
+}
+
+func TestParse(t *testing.T) {
+ c := qt.New(t)
+
+ tests := []struct {
+ name string
+ path string
+ assert func(c *qt.C, p *Path)
+ }{
+ {
+ "Basic text file",
+ "/a/b.txt",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Name(), qt.Equals, "b.txt")
+ c.Assert(p.Base(), qt.Equals, "/a/b.txt")
+ c.Assert(p.Container(), qt.Equals, "a")
+ c.Assert(p.Dir(), qt.Equals, "/a")
+ c.Assert(p.Ext(), qt.Equals, "txt")
+ c.Assert(p.IsContent(), qt.IsFalse)
+ },
+ },
+ {
+ "Basic text file, upper case",
+ "/A/B.txt",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Name(), qt.Equals, "b.txt")
+ c.Assert(p.NameNoExt(), qt.Equals, "b")
+ c.Assert(p.NameNoIdentifier(), qt.Equals, "b")
+ c.Assert(p.BaseNameNoIdentifier(), qt.Equals, "b")
+ c.Assert(p.Base(), qt.Equals, "/a/b.txt")
+ c.Assert(p.Ext(), qt.Equals, "txt")
+ },
+ },
+ {
+ "Basic text file, 1 space in dir",
+ "/a b/c.txt",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Base(), qt.Equals, "/a-b/c.txt")
+ },
+ },
+ {
+ "Basic text file, 2 spaces in dir",
+ "/a b/c.txt",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Base(), qt.Equals, "/a--b/c.txt")
+ },
+ },
+ {
+ "Basic text file, 1 space in filename",
+ "/a/b c.txt",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Base(), qt.Equals, "/a/b-c.txt")
+ },
+ },
+ {
+ "Basic text file, 2 spaces in filename",
+ "/a/b c.txt",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Base(), qt.Equals, "/a/b--c.txt")
+ },
+ },
+ {
+ "Basic text file, mixed case and spaces, unnormalized",
+ "/a/Foo BAR.txt",
+ func(c *qt.C, p *Path) {
+ pp := p.Unnormalized()
+ c.Assert(pp, qt.IsNotNil)
+ c.Assert(pp.BaseNameNoIdentifier(), qt.Equals, "Foo BAR")
+ },
+ },
+ {
+ "Basic Markdown file",
+ "/a/b/c.md",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Ext(), qt.Equals, "md")
+ c.Assert(p.Type(), qt.Equals, TypeContentSingle)
+ c.Assert(p.IsContent(), qt.IsTrue)
+ c.Assert(p.IsLeafBundle(), qt.IsFalse)
+ c.Assert(p.Name(), qt.Equals, "c.md")
+ c.Assert(p.Base(), qt.Equals, "/a/b/c")
+ c.Assert(p.BaseReTyped("foo"), qt.Equals, "/foo/b/c")
+ c.Assert(p.Section(), qt.Equals, "a")
+ c.Assert(p.BaseNameNoIdentifier(), qt.Equals, "c")
+ c.Assert(p.Path(), qt.Equals, "/a/b/c.md")
+ c.Assert(p.Dir(), qt.Equals, "/a/b")
+ c.Assert(p.Container(), qt.Equals, "b")
+ c.Assert(p.ContainerDir(), qt.Equals, "/a/b")
+ },
+ },
+ {
+ "Content resource",
+ "/a/b.md",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Name(), qt.Equals, "b.md")
+ c.Assert(p.Base(), qt.Equals, "/a/b")
+ c.Assert(p.BaseNoLeadingSlash(), qt.Equals, "a/b")
+ c.Assert(p.Section(), qt.Equals, "a")
+ c.Assert(p.BaseNameNoIdentifier(), qt.Equals, "b")
+
+ // Reclassify it as a content resource.
+ ModifyPathBundleTypeResource(p)
+ c.Assert(p.Type(), qt.Equals, TypeContentResource)
+ c.Assert(p.IsContent(), qt.IsTrue)
+ c.Assert(p.Name(), qt.Equals, "b.md")
+ c.Assert(p.Base(), qt.Equals, "/a/b.md")
+ },
+ },
+ {
+ "No ext",
+ "/a/b",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Name(), qt.Equals, "b")
+ c.Assert(p.NameNoExt(), qt.Equals, "b")
+ c.Assert(p.Base(), qt.Equals, "/a/b")
+ c.Assert(p.Ext(), qt.Equals, "")
+ },
+ },
+ {
+ "No ext, trailing slash",
+ "/a/b/",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Name(), qt.Equals, "b")
+ c.Assert(p.Base(), qt.Equals, "/a/b")
+ c.Assert(p.Ext(), qt.Equals, "")
+ },
+ },
+ {
+ "Identifiers",
+ "/a/b.a.b.no.txt",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Name(), qt.Equals, "b.a.b.no.txt")
+ c.Assert(p.NameNoIdentifier(), qt.Equals, "b.a.b")
+ c.Assert(p.NameNoLang(), qt.Equals, "b.a.b.txt")
+ c.Assert(p.Identifiers(), qt.DeepEquals, []string{"txt", "no"})
+ c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{"b", "a", "b"})
+ c.Assert(p.Base(), qt.Equals, "/a/b.a.b.txt")
+ c.Assert(p.BaseNoLeadingSlash(), qt.Equals, "a/b.a.b.txt")
+ c.Assert(p.Path(), qt.Equals, "/a/b.a.b.no.txt")
+ c.Assert(p.PathNoLang(), qt.Equals, "/a/b.a.b.txt")
+ c.Assert(p.Ext(), qt.Equals, "txt")
+ c.Assert(p.PathNoIdentifier(), qt.Equals, "/a/b.a.b")
+ },
+ },
+ {
+ "Home branch cundle",
+ "/_index.md",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md"})
+ c.Assert(p.IsBranchBundle(), qt.IsTrue)
+ c.Assert(p.IsBundle(), qt.IsTrue)
+ c.Assert(p.Base(), qt.Equals, "")
+ c.Assert(p.BaseReTyped("foo"), qt.Equals, "/foo")
+ c.Assert(p.Path(), qt.Equals, "/_index.md")
+ c.Assert(p.Container(), qt.Equals, "")
+ c.Assert(p.ContainerDir(), qt.Equals, "/")
+ },
+ },
+ {
+ "Index content file in root",
+ "/a/index.md",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Base(), qt.Equals, "/a")
+ c.Assert(p.BaseReTyped("foo"), qt.Equals, "/foo/a")
+ c.Assert(p.BaseNameNoIdentifier(), qt.Equals, "a")
+ c.Assert(p.Container(), qt.Equals, "a")
+ c.Assert(p.Container(), qt.Equals, "a")
+ c.Assert(p.ContainerDir(), qt.Equals, "")
+ c.Assert(p.Dir(), qt.Equals, "/a")
+ c.Assert(p.Ext(), qt.Equals, "md")
+ c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{"index"})
+ c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md"})
+ c.Assert(p.IsBranchBundle(), qt.IsFalse)
+ c.Assert(p.IsBundle(), qt.IsTrue)
+ c.Assert(p.IsLeafBundle(), qt.IsTrue)
+ c.Assert(p.Lang(), qt.Equals, "")
+ c.Assert(p.NameNoExt(), qt.Equals, "index")
+ c.Assert(p.NameNoIdentifier(), qt.Equals, "index")
+ c.Assert(p.NameNoLang(), qt.Equals, "index.md")
+ c.Assert(p.Section(), qt.Equals, "")
+ },
+ },
+ {
+ "Index content file with lang",
+ "/a/b/index.no.md",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Base(), qt.Equals, "/a/b")
+ c.Assert(p.BaseNameNoIdentifier(), qt.Equals, "b")
+ c.Assert(p.BaseReTyped("foo"), qt.Equals, "/foo/b")
+ c.Assert(p.Container(), qt.Equals, "b")
+ c.Assert(p.ContainerDir(), qt.Equals, "/a")
+ c.Assert(p.Dir(), qt.Equals, "/a/b")
+ c.Assert(p.Ext(), qt.Equals, "md")
+ c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md", "no"})
+ c.Assert(p.IsBranchBundle(), qt.IsFalse)
+ c.Assert(p.IsBundle(), qt.IsTrue)
+ c.Assert(p.IsLeafBundle(), qt.IsTrue)
+ c.Assert(p.Lang(), qt.Equals, "no")
+ c.Assert(p.NameNoExt(), qt.Equals, "index.no")
+ c.Assert(p.NameNoIdentifier(), qt.Equals, "index")
+ c.Assert(p.NameNoLang(), qt.Equals, "index.md")
+ c.Assert(p.Path(), qt.Equals, "/a/b/index.no.md")
+ c.Assert(p.PathNoLang(), qt.Equals, "/a/b/index.md")
+ c.Assert(p.Section(), qt.Equals, "a")
+ },
+ },
+ {
+ "Index branch content file",
+ "/a/b/_index.no.md",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Base(), qt.Equals, "/a/b")
+ c.Assert(p.BaseNameNoIdentifier(), qt.Equals, "b")
+ c.Assert(p.Container(), qt.Equals, "b")
+ c.Assert(p.ContainerDir(), qt.Equals, "/a")
+ c.Assert(p.Ext(), qt.Equals, "md")
+ c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md", "no"})
+ c.Assert(p.IsBranchBundle(), qt.IsTrue)
+ c.Assert(p.IsBundle(), qt.IsTrue)
+ c.Assert(p.IsLeafBundle(), qt.IsFalse)
+ c.Assert(p.NameNoExt(), qt.Equals, "_index.no")
+ c.Assert(p.NameNoLang(), qt.Equals, "_index.md")
+ },
+ },
+ {
+ "Index root no slash",
+ "_index.md",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Base(), qt.Equals, "")
+ c.Assert(p.Ext(), qt.Equals, "md")
+ c.Assert(p.Name(), qt.Equals, "_index.md")
+ },
+ },
+ {
+ "Index root",
+ "/_index.md",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Base(), qt.Equals, "")
+ c.Assert(p.Ext(), qt.Equals, "md")
+ c.Assert(p.Name(), qt.Equals, "_index.md")
+ },
+ },
+ {
+ "Index first",
+ "/a/_index.md",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Section(), qt.Equals, "a")
+ },
+ },
+ {
+ "Index text file",
+ "/a/b/index.no.txt",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Base(), qt.Equals, "/a/b/index.txt")
+ c.Assert(p.Ext(), qt.Equals, "txt")
+ c.Assert(p.Identifiers(), qt.DeepEquals, []string{"txt", "no"})
+ c.Assert(p.IsLeafBundle(), qt.IsFalse)
+ c.Assert(p.PathNoIdentifier(), qt.Equals, "/a/b/index")
+ },
+ },
+ {
+ "Empty",
+ "",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Base(), qt.Equals, "/")
+ c.Assert(p.Ext(), qt.Equals, "")
+ c.Assert(p.Name(), qt.Equals, "")
+ c.Assert(p.Path(), qt.Equals, "/")
+ },
+ },
+ {
+ "Slash",
+ "/",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Base(), qt.Equals, "/")
+ c.Assert(p.Ext(), qt.Equals, "")
+ c.Assert(p.Name(), qt.Equals, "")
+ },
+ },
+ {
+ "Trim Leading Slash bundle",
+ "foo/bar/index.no.md",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Path(), qt.Equals, "/foo/bar/index.no.md")
+ pp := p.TrimLeadingSlash()
+ c.Assert(pp.Path(), qt.Equals, "foo/bar/index.no.md")
+ c.Assert(pp.PathNoLang(), qt.Equals, "foo/bar/index.md")
+ c.Assert(pp.Base(), qt.Equals, "foo/bar")
+ c.Assert(pp.Dir(), qt.Equals, "foo/bar")
+ c.Assert(pp.ContainerDir(), qt.Equals, "foo")
+ c.Assert(pp.Container(), qt.Equals, "bar")
+ c.Assert(pp.BaseNameNoIdentifier(), qt.Equals, "bar")
+ },
+ },
+ {
+ "Trim Leading Slash file",
+ "foo/bar.txt",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Path(), qt.Equals, "/foo/bar.txt")
+ pp := p.TrimLeadingSlash()
+ c.Assert(pp.Path(), qt.Equals, "foo/bar.txt")
+ c.Assert(pp.PathNoLang(), qt.Equals, "foo/bar.txt")
+ c.Assert(pp.Base(), qt.Equals, "foo/bar.txt")
+ c.Assert(pp.Dir(), qt.Equals, "foo")
+ c.Assert(pp.ContainerDir(), qt.Equals, "foo")
+ c.Assert(pp.Container(), qt.Equals, "foo")
+ c.Assert(pp.BaseNameNoIdentifier(), qt.Equals, "bar")
+ },
+ },
+ {
+ "File separator",
+ filepath.FromSlash("/a/b/c.txt"),
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Base(), qt.Equals, "/a/b/c.txt")
+ c.Assert(p.Ext(), qt.Equals, "txt")
+ c.Assert(p.Name(), qt.Equals, "c.txt")
+ c.Assert(p.Path(), qt.Equals, "/a/b/c.txt")
+ },
+ },
+ {
+ "Content data file gotmpl",
+ "/a/b/_content.gotmpl",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Path(), qt.Equals, "/a/b/_content.gotmpl")
+ c.Assert(p.Ext(), qt.Equals, "gotmpl")
+ c.Assert(p.IsContentData(), qt.IsTrue)
+ },
+ },
+ {
+ "Content data file yaml",
+ "/a/b/_content.yaml",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.IsContentData(), qt.IsFalse)
+ },
+ },
+ {
+ "Custom identifier",
+ "/a/b/p1._myid_.no.md",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Base(), qt.Equals, "/a/b/p1")
+ c.Assert(p.Lang(), qt.Equals, "no")
+ c.Assert(p.Ext(), qt.Equals, "md")
+ c.Assert(p.Custom(), qt.Equals, "myid")
+ },
+ },
+ }
+ parser := newTestParser()
+ for _, test := range tests {
+ c.Run(test.name, func(c *qt.C) {
+ if test.name != "Caret up identifier" {
+ // return
+ }
+ test.assert(c, parser.Parse(files.ComponentFolderContent, test.path))
+ })
+ }
+}
+
+func TestParseLayouts(t *testing.T) {
+ c := qt.New(t)
+
+ tests := []struct {
+ name string
+ path string
+ assert func(c *qt.C, p *Path)
+ }{
+ {
+ "Basic",
+ "/list.html",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Base(), qt.Equals, "/list.html")
+ c.Assert(p.OutputFormat(), qt.Equals, "html")
+ },
+ },
+ {
+ "Lang",
+ "/list.no.html",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "no", "list"})
+ c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{})
+ c.Assert(p.Base(), qt.Equals, "/list.html")
+ c.Assert(p.Lang(), qt.Equals, "no")
+ },
+ },
+ {
+ "Kind",
+ "/section.no.html",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Kind(), qt.Equals, kinds.KindSection)
+ c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "no", "section"})
+ c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{})
+ c.Assert(p.Base(), qt.Equals, "/section.html")
+ c.Assert(p.Lang(), qt.Equals, "no")
+ },
+ },
+ {
+ "Layout",
+ "/list.section.no.html",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Layout(), qt.Equals, "list")
+ c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "no", "section", "list"})
+ c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{})
+ c.Assert(p.Base(), qt.Equals, "/list.html")
+ c.Assert(p.Lang(), qt.Equals, "no")
+ },
+ },
+ {
+ "Layout multiple",
+ "/mylayout.list.section.no.html",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Layout(), qt.Equals, "mylayout")
+ c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "no", "section", "mylayout"})
+ c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{"list"})
+ c.Assert(p.Base(), qt.Equals, "/mylayout.html")
+ c.Assert(p.Lang(), qt.Equals, "no")
+ },
+ },
+ {
+ "Layout shortcode",
+ "/_shortcodes/myshort.list.no.html",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Layout(), qt.Equals, "list")
+ },
+ },
+ {
+ "Layout baseof",
+ "/baseof.list.no.html",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Layout(), qt.Equals, "list")
+ },
+ },
+ {
+ "Lang and output format",
+ "/list.no.amp.not.html",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "list", "amp", "no"})
+ c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{"not"})
+ c.Assert(p.OutputFormat(), qt.Equals, "amp")
+ c.Assert(p.Ext(), qt.Equals, "html")
+ c.Assert(p.Lang(), qt.Equals, "no")
+ c.Assert(p.Base(), qt.Equals, "/list.html")
+ },
+ },
+ {
+ "Term",
+ "/term.html",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Base(), qt.Equals, "/term.html")
+ c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "term"})
+ c.Assert(p.PathNoIdentifier(), qt.Equals, "/term")
+ c.Assert(p.PathBeforeLangAndOutputFormatAndExt(), qt.Equals, "/term")
+ c.Assert(p.Lang(), qt.Equals, "")
+ c.Assert(p.Kind(), qt.Equals, "term")
+ c.Assert(p.OutputFormat(), qt.Equals, "html")
+ },
+ },
+ {
+ "Shortcode with layout",
+ "/_shortcodes/myshortcode.list.html",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Base(), qt.Equals, "/_shortcodes/myshortcode.html")
+ c.Assert(p.Type(), qt.Equals, TypeShortcode)
+ c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "list"})
+ c.Assert(p.Layout(), qt.Equals, "list")
+ c.Assert(p.PathNoIdentifier(), qt.Equals, "/_shortcodes/myshortcode")
+ c.Assert(p.PathBeforeLangAndOutputFormatAndExt(), qt.Equals, "/_shortcodes/myshortcode.list")
+ c.Assert(p.Lang(), qt.Equals, "")
+ c.Assert(p.Kind(), qt.Equals, "")
+ c.Assert(p.OutputFormat(), qt.Equals, "html")
+ },
+ },
+ {
+ "Sub dir",
+ "/pages/home.html",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "home"})
+ c.Assert(p.Lang(), qt.Equals, "")
+ c.Assert(p.Kind(), qt.Equals, "home")
+ c.Assert(p.OutputFormat(), qt.Equals, "html")
+ c.Assert(p.Dir(), qt.Equals, "/pages")
+ },
+ },
+ {
+ "Baseof",
+ "/pages/baseof.list.section.fr.amp.html",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "amp", "fr", "section", "list", "baseof"})
+ c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{})
+ c.Assert(p.Kind(), qt.Equals, kinds.KindSection)
+ c.Assert(p.Lang(), qt.Equals, "fr")
+ c.Assert(p.OutputFormat(), qt.Equals, "amp")
+ c.Assert(p.Dir(), qt.Equals, "/pages")
+ c.Assert(p.NameNoIdentifier(), qt.Equals, "baseof")
+ c.Assert(p.Type(), qt.Equals, TypeBaseof)
+ c.Assert(p.IdentifierBase(), qt.Equals, "/pages/baseof.list.section.fr.amp.html")
+ },
+ },
+ {
+ "Markup",
+ "/_markup/render-link.html",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Type(), qt.Equals, TypeMarkup)
+ },
+ },
+ {
+ "Markup nested",
+ "/foo/_markup/render-link.html",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Type(), qt.Equals, TypeMarkup)
+ },
+ },
+ {
+ "Shortcode",
+ "/_shortcodes/myshortcode.html",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Type(), qt.Equals, TypeShortcode)
+ },
+ },
+ {
+ "Shortcode nested",
+ "/foo/_shortcodes/myshortcode.html",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Type(), qt.Equals, TypeShortcode)
+ },
+ },
+ {
+ "Shortcode nested sub",
+ "/foo/_shortcodes/foo/myshortcode.html",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Type(), qt.Equals, TypeShortcode)
+ },
+ },
+ {
+ "Partials",
+ "/_partials/foo.bar",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Type(), qt.Equals, TypePartial)
+ },
+ },
+ {
+ "Shortcode lang in root",
+ "/_shortcodes/no.html",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Type(), qt.Equals, TypeShortcode)
+ c.Assert(p.Lang(), qt.Equals, "")
+ c.Assert(p.NameNoIdentifier(), qt.Equals, "no")
+ },
+ },
+ {
+ "Shortcode lang layout",
+ "/_shortcodes/myshortcode.no.html",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Type(), qt.Equals, TypeShortcode)
+ c.Assert(p.Lang(), qt.Equals, "no")
+ c.Assert(p.Layout(), qt.Equals, "")
+ c.Assert(p.NameNoIdentifier(), qt.Equals, "myshortcode")
+ },
+ },
+ {
+ "Not lang",
+ "/foo/index.xy.html",
+ func(c *qt.C, p *Path) {
+ c.Assert(p.Lang(), qt.Equals, "")
+ c.Assert(p.Layout(), qt.Equals, "index")
+ c.Assert(p.NameNoLang(), qt.Equals, "index.xy.html")
+ c.Assert(p.PathNoLang(), qt.Equals, "/foo/index.xy.html")
+ c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "index"})
+ c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{"xy"})
+ },
+ },
+ }
+
+ parser := newTestParser()
+ for _, test := range tests {
+ c.Run(test.name, func(c *qt.C) {
+ if test.name != "Not lang" {
+ return
+ }
+ test.assert(c, parser.Parse(files.ComponentFolderLayouts, test.path))
+ })
+ }
+}
+
+func TestHasExt(t *testing.T) {
+ c := qt.New(t)
+
+ c.Assert(HasExt("/a/b/c.txt"), qt.IsTrue)
+ c.Assert(HasExt("/a/b.c/d.txt"), qt.IsTrue)
+ c.Assert(HasExt("/a/b/c"), qt.IsFalse)
+ c.Assert(HasExt("/a/b.c/d"), qt.IsFalse)
+}
+
+func BenchmarkParseIdentity(b *testing.B) {
+ parser := newTestParser()
+ for b.Loop() {
+ parser.ParseIdentity(files.ComponentFolderAssets, "/a/b.css")
+ }
+}
+
+func TestSitesMatrixFromPath(t *testing.T) {
+ c := qt.New(t)
+
+ parser := newTestParser()
+ p := parser.Parse(files.ComponentFolderContent, "/a/b/c.fr.md")
+ v := parser.SitesMatrixFromPath(p)
+ c.Assert(v.HasLanguage(2), qt.IsTrue)
+ c.Assert(v.LenVectors(), qt.Equals, 1)
+ c.Assert(v.VectorSample(), qt.Equals, sitesmatrix.Vector{2, 0, 0})
+}
+
+func BenchmarkSitesMatrixFromPath(b *testing.B) {
+ parser := newTestParser()
+ p := parser.Parse(files.ComponentFolderContent, "/a/b/c.fr.md")
+ for b.Loop() {
+ parser.SitesMatrixFromPath(p)
+ }
+}
diff --git a/common/paths/paths_integration_test.go b/common/paths/paths_integration_test.go
new file mode 100644
index 00000000000..3f3f786f9f5
--- /dev/null
+++ b/common/paths/paths_integration_test.go
@@ -0,0 +1,103 @@
+// 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 paths_test
+
+import (
+ "testing"
+
+ "github.com/gohugoio/hugo/hugolib"
+)
+
+func TestRemovePathAccents(t *testing.T) {
+ t.Parallel()
+
+ files := `
+-- hugo.toml --
+disableKinds = ["taxonomy", "term"]
+defaultContentLanguage = "en"
+defaultContentLanguageInSubdir = true
+[languages]
+[languages.en]
+weight = 1
+[languages.fr]
+weight = 2
+removePathAccents = true
+-- content/διακριτικός.md --
+-- content/διακριτικός.fr.md --
+-- layouts/single.html --
+{{ .Language.Lang }}|Single.
+-- layouts/list.html --
+List
+`
+ b := hugolib.Test(t, files)
+
+ b.AssertFileContent("public/en/διακριτικός/index.html", "en|Single")
+ b.AssertFileContent("public/fr/διακριτικος/index.html", "fr|Single")
+}
+
+func TestDisablePathToLower(t *testing.T) {
+ t.Parallel()
+
+ files := `
+-- hugo.toml --
+disableKinds = ["taxonomy", "term"]
+defaultContentLanguage = "en"
+defaultContentLanguageInSubdir = true
+[languages]
+[languages.en]
+weight = 1
+[languages.fr]
+weight = 2
+disablePathToLower = true
+-- content/MySection/MyPage.md --
+-- content/MySection/MyPage.fr.md --
+-- content/MySection/MyBundle/index.md --
+-- content/MySection/MyBundle/index.fr.md --
+-- layouts/single.html --
+{{ .Language.Lang }}|Single.
+-- layouts/list.html --
+{{ .Language.Lang }}|List.
+`
+ b := hugolib.Test(t, files)
+
+ b.AssertFileContent("public/en/mysection/index.html", "en|List")
+ b.AssertFileContent("public/en/mysection/mypage/index.html", "en|Single")
+ b.AssertFileContent("public/fr/MySection/index.html", "fr|List")
+ b.AssertFileContent("public/fr/MySection/MyPage/index.html", "fr|Single")
+ b.AssertFileContent("public/en/mysection/mybundle/index.html", "en|Single")
+ b.AssertFileContent("public/fr/MySection/MyBundle/index.html", "fr|Single")
+}
+
+func TestIssue13596(t *testing.T) {
+ t.Parallel()
+
+ files := `
+-- hugo.toml --
+disableKinds = ['home','rss','section','sitemap','taxonomy','term']
+-- content/p1/index.md --
+---
+title: p1
+---
+-- content/p1/a.1.txt --
+-- content/p1/a.2.txt --
+-- layouts/all.html --
+{{ range .Resources.Match "*" }}{{ .Name }}|{{ end }}
+`
+
+ b := hugolib.Test(t, files)
+
+ b.AssertFileContent("public/p1/index.html", "a.1.txt|a.2.txt|")
+ b.AssertFileExists("public/p1/a.1.txt", true)
+ b.AssertFileExists("public/p1/a.2.txt", true) // fails
+}
diff --git a/common/paths/type_string.go b/common/paths/type_string.go
new file mode 100644
index 00000000000..08fbcc835e1
--- /dev/null
+++ b/common/paths/type_string.go
@@ -0,0 +1,32 @@
+// Code generated by "stringer -type Type"; DO NOT EDIT.
+
+package paths
+
+import "strconv"
+
+func _() {
+ // An "invalid array index" compiler error signifies that the constant values have changed.
+ // Re-run the stringer command to generate them again.
+ var x [1]struct{}
+ _ = x[TypeFile-0]
+ _ = x[TypeContentResource-1]
+ _ = x[TypeContentSingle-2]
+ _ = x[TypeLeaf-3]
+ _ = x[TypeBranch-4]
+ _ = x[TypeContentData-5]
+ _ = x[TypeMarkup-6]
+ _ = x[TypeShortcode-7]
+ _ = x[TypePartial-8]
+ _ = x[TypeBaseof-9]
+}
+
+const _Type_name = "TypeFileTypeContentResourceTypeContentSingleTypeLeafTypeBranchTypeContentDataTypeMarkupTypeShortcodeTypePartialTypeBaseof"
+
+var _Type_index = [...]uint8{0, 8, 27, 44, 52, 62, 77, 87, 100, 111, 121}
+
+func (i Type) String() string {
+ if i < 0 || i >= Type(len(_Type_index)-1) {
+ return "Type(" + strconv.FormatInt(int64(i), 10) + ")"
+ }
+ return _Type_name[_Type_index[i]:_Type_index[i+1]]
+}
diff --git a/common/paths/url.go b/common/paths/url.go
index 600e8d22db2..1d1408b51e6 100644
--- a/common/paths/url.go
+++ b/common/paths/url.go
@@ -1,4 +1,4 @@
-// Copyright 2021 The Hugo Authors. All rights reserved.
+// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -17,13 +17,12 @@ import (
"fmt"
"net/url"
"path"
+ "path/filepath"
+ "runtime"
"strings"
-
- "github.com/PuerkitoBio/purell"
)
-type pathBridge struct {
-}
+type pathBridge struct{}
func (pathBridge) Base(in string) string {
return path.Base(in)
@@ -51,56 +50,12 @@ func (pathBridge) Separator() string {
var pb pathBridge
-func sanitizeURLWithFlags(in string, f purell.NormalizationFlags) string {
- s, err := purell.NormalizeURLString(in, f)
- if err != nil {
- return in
- }
-
- // Temporary workaround for the bug fix and resulting
- // behavioral change in purell.NormalizeURLString():
- // a leading '/' was inadvertently added to relative links,
- // but no longer, see #878.
- //
- // I think the real solution is to allow Hugo to
- // make relative URL with relative path,
- // e.g. "../../post/hello-again/", as wished by users
- // in issues #157, #622, etc., without forcing
- // relative URLs to begin with '/'.
- // Once the fixes are in, let's remove this kludge
- // and restore SanitizeURL() to the way it was.
- // -- @anthonyfok, 2015-02-16
- //
- // Begin temporary kludge
- u, err := url.Parse(s)
- if err != nil {
- panic(err)
- }
- if len(u.Path) > 0 && !strings.HasPrefix(u.Path, "/") {
- u.Path = "/" + u.Path
- }
- return u.String()
- // End temporary kludge
-
- // return s
-
-}
-
-// SanitizeURL sanitizes the input URL string.
-func SanitizeURL(in string) string {
- return sanitizeURLWithFlags(in, purell.FlagsSafe|purell.FlagRemoveTrailingSlash|purell.FlagRemoveDotSegments|purell.FlagRemoveDuplicateSlashes|purell.FlagRemoveUnnecessaryHostDots|purell.FlagRemoveEmptyPortSeparator)
-}
-
-// SanitizeURLKeepTrailingSlash is the same as SanitizeURL, but will keep any trailing slash.
-func SanitizeURLKeepTrailingSlash(in string) string {
- return sanitizeURLWithFlags(in, purell.FlagsSafe|purell.FlagRemoveDotSegments|purell.FlagRemoveDuplicateSlashes|purell.FlagRemoveUnnecessaryHostDots|purell.FlagRemoveEmptyPortSeparator)
-}
-
// MakePermalink combines base URL with content path to create full URL paths.
// Example
-// base: http://spf13.com/
-// path: post/how-i-blog
-// result: http://spf13.com/post/how-i-blog
+//
+// base: http://spf13.com/
+// path: post/how-i-blog
+// result: http://spf13.com/post/how-i-blog
func MakePermalink(host, plink string) *url.URL {
base, err := url.Parse(host)
if err != nil {
@@ -117,6 +72,8 @@ func MakePermalink(host, plink string) *url.URL {
}
base.Path = path.Join(base.Path, p.Path)
+ base.Fragment = p.Fragment
+ base.RawQuery = p.RawQuery
// path.Join will strip off the last /, so put it back if it was there.
hadTrailingSlash := (plink == "" && strings.HasSuffix(host, "/")) || strings.HasSuffix(p.Path, "/")
@@ -127,16 +84,6 @@ func MakePermalink(host, plink string) *url.URL {
return base
}
-// IsAbsURL determines whether the given path points to an absolute URL.
-func IsAbsURL(path string) bool {
- url, err := url.Parse(path)
- if err != nil {
- return false
- }
-
- return url.IsAbs() || strings.HasPrefix(path, "//")
-}
-
// AddContextRoot adds the context root to an URL if it's not already set.
// For relative URL entries on sites with a base url with a context root set (i.e. http://example.com/mysite),
// relative URLs must not include the context root if canonifyURLs is enabled. But if it's disabled, it must be set.
@@ -174,17 +121,19 @@ func PrettifyURL(in string) string {
// PrettifyURLPath takes a URL path to a content and converts it
// to enable pretty URLs.
-// /section/name.html becomes /section/name/index.html
-// /section/name/ becomes /section/name/index.html
-// /section/name/index.html becomes /section/name/index.html
+//
+// /section/name.html becomes /section/name/index.html
+// /section/name/ becomes /section/name/index.html
+// /section/name/index.html becomes /section/name/index.html
func PrettifyURLPath(in string) string {
return prettifyPath(in, pb)
}
// Uglify does the opposite of PrettifyURLPath().
-// /section/name/index.html becomes /section/name.html
-// /section/name/ becomes /section/name.html
-// /section/name.html becomes /section/name.html
+//
+// /section/name/index.html becomes /section/name.html
+// /section/name/ becomes /section/name.html
+// /section/name.html becomes /section/name.html
func Uglify(in string) string {
if path.Ext(in) == "" {
if len(in) < 2 {
@@ -210,3 +159,115 @@ func Uglify(in string) string {
// /section/name.html -> /section/name.html
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
+}
+
+// UrlStringToFilename converts the URL s to a filename.
+// If ParseRequestURI fails, the input is just converted to OS specific slashes and returned.
+func UrlStringToFilename(s string) (string, bool) {
+ u, err := url.ParseRequestURI(s)
+ if err != nil {
+ return filepath.FromSlash(s), false
+ }
+
+ p := u.Path
+
+ if p == "" {
+ p, _ = url.QueryUnescape(u.Opaque)
+ return filepath.FromSlash(p), false
+ }
+
+ if runtime.GOOS != "windows" {
+ return p, true
+ }
+
+ if len(p) == 0 || p[0] != '/' {
+ return filepath.FromSlash(p), false
+ }
+
+ p = filepath.FromSlash(p)
+
+ if len(u.Host) == 1 {
+ // file://c/Users/...
+ return strings.ToUpper(u.Host) + ":" + p, true
+ }
+
+ if u.Host != "" && u.Host != "localhost" {
+ if filepath.VolumeName(u.Host) != "" {
+ return "", false
+ }
+ return `\\` + u.Host + p, true
+ }
+
+ if vol := filepath.VolumeName(p[1:]); vol == "" || strings.HasPrefix(vol, `\\`) {
+ return "", false
+ }
+
+ return p[1:], true
+}
diff --git a/common/paths/url_test.go b/common/paths/url_test.go
index 3e8391ef504..5a9233c26fa 100644
--- a/common/paths/url_test.go
+++ b/common/paths/url_test.go
@@ -14,41 +14,11 @@
package paths
import (
- "strings"
"testing"
qt "github.com/frankban/quicktest"
)
-func TestSanitizeURL(t *testing.T) {
- tests := []struct {
- input string
- expected string
- }{
- {"http://foo.bar/", "http://foo.bar"},
- {"http://foo.bar", "http://foo.bar"}, // issue #1105
- {"http://foo.bar/zoo/", "http://foo.bar/zoo"}, // issue #931
- }
-
- for i, test := range tests {
- o1 := SanitizeURL(test.input)
- o2 := SanitizeURLKeepTrailingSlash(test.input)
-
- expected2 := test.expected
-
- if strings.HasSuffix(test.input, "/") && !strings.HasSuffix(expected2, "/") {
- expected2 += "/"
- }
-
- if o1 != test.expected {
- t.Errorf("[%d] 1: Expected %#v, got %#v\n", i, test.expected, o1)
- }
- if o2 != expected2 {
- t.Errorf("[%d] 2: Expected %#v, got %#v\n", i, expected2, o2)
- }
- }
-}
-
func TestMakePermalink(t *testing.T) {
type test struct {
host, link, output string
@@ -61,6 +31,7 @@ func TestMakePermalink(t *testing.T) {
{"http://abc.com", "bar", "http://abc.com/bar"},
{"http://abc.com/foo/bar", "post/bar", "http://abc.com/foo/bar/post/bar"},
{"http://abc.com/foo/bar", "post/bar/", "http://abc.com/foo/bar/post/bar/"},
+ {"http://abc.com/foo", "post/bar?a=b#c", "http://abc.com/foo/post/bar?a=b#c"},
}
for i, d := range data {
diff --git a/common/predicate/predicate.go b/common/predicate/predicate.go
new file mode 100644
index 00000000000..dde356895e9
--- /dev/null
+++ b/common/predicate/predicate.go
@@ -0,0 +1,301 @@
+// Copyright 2025 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package predicate
+
+import (
+ "iter"
+ "strings"
+
+ "github.com/gobwas/glob"
+ "github.com/gohugoio/hugo/hugofs/hglob"
+)
+
+// Match represents the result of a predicate evaluation.
+type Match interface {
+ OK() bool
+}
+
+var (
+ // Predefined Match values for common cases.
+ True = BoolMatch(true)
+ False = BoolMatch(false)
+)
+
+// BoolMatch is a simple Match implementation based on a boolean value.
+type BoolMatch bool
+
+func (b BoolMatch) OK() bool {
+ return bool(b)
+}
+
+// breakMatch is a Match implementation that always returns false for OK() and signals to break evaluation.
+type breakMatch struct{}
+
+func (b breakMatch) OK() bool {
+ return false
+}
+
+var matchBreak = breakMatch{}
+
+// P is a predicate function that tests whether a value of type T satisfies some condition.
+type P[T any] func(T) bool
+
+// Or returns a predicate that is a short-circuiting logical OR of this and the given predicates.
+// Note that P[T] only supports Or. For chained AND/OR logic, use PR[T].
+func (p P[T]) Or(ps ...P[T]) P[T] {
+ return func(v T) bool {
+ if p != nil && p(v) {
+ return true
+ }
+ for _, pp := range ps {
+ if pp(v) {
+ return true
+ }
+ }
+ return false
+ }
+}
+
+// PR is a predicate function that tests whether a value of type T satisfies some condition and returns a Match result.
+type PR[T any] func(T) Match
+
+// BoolFunc returns a P[T] version of this predicate.
+func (p PR[T]) BoolFunc() P[T] {
+ return func(v T) bool {
+ if p == nil {
+ return false
+ }
+ return p(v).OK()
+ }
+}
+
+// And returns a predicate that is a short-circuiting logical AND of this and the given predicates.
+func (p PR[T]) And(ps ...PR[T]) PR[T] {
+ return func(v T) Match {
+ if p != nil {
+ m := p(v)
+ if !m.OK() || shouldBreak(m) {
+ return matchBreak
+ }
+ }
+ for _, pp := range ps {
+ m := pp(v)
+ if !m.OK() || shouldBreak(m) {
+ return matchBreak
+ }
+ }
+ return BoolMatch(true)
+ }
+}
+
+// Or returns a predicate that is a short-circuiting logical OR of this and the given predicates.
+func (p PR[T]) Or(ps ...PR[T]) PR[T] {
+ return func(v T) Match {
+ if p != nil {
+ m := p(v)
+ if m.OK() {
+ return m
+ }
+ if shouldBreak(m) {
+ return matchBreak
+ }
+ }
+ for _, pp := range ps {
+ m := pp(v)
+ if m.OK() {
+ return m
+ }
+ if shouldBreak(m) {
+ return matchBreak
+ }
+ }
+ return BoolMatch(false)
+ }
+}
+
+func shouldBreak(m Match) bool {
+ _, ok := m.(breakMatch)
+ return ok
+}
+
+// Filter returns a new slice holding only the elements of s that satisfy p.
+// Filter modifies the contents of the slice s and returns the modified slice, which may have a smaller length.
+func (p PR[T]) Filter(s []T) []T {
+ var n int
+ for _, v := range s {
+ if p(v).OK() {
+ s[n] = v
+ n++
+ }
+ }
+ return s[:n]
+}
+
+// FilterCopy returns a new slice holding only the elements of s that satisfy p.
+func (p PR[T]) FilterCopy(s []T) []T {
+ var result []T
+ for _, v := range s {
+ if p(v).OK() {
+ result = append(result, v)
+ }
+ }
+ return result
+}
+
+const (
+ rangeOpNone = iota
+ rangeOpLT
+ rangeOpLTE
+ rangeOpGT
+ rangeOpGTE
+ rangeOpEQ
+ rangeOpNE
+)
+
+func cutRangeOp(s string) (op int, rest string) {
+ switch {
+ case strings.HasPrefix(s, ">= "):
+ return rangeOpGTE, s[3:]
+ case strings.HasPrefix(s, "<= "):
+ return rangeOpLTE, s[3:]
+ case strings.HasPrefix(s, "== "):
+ return rangeOpEQ, s[3:]
+ case strings.HasPrefix(s, "!= "):
+ return rangeOpNE, s[3:]
+ case strings.HasPrefix(s, "> "):
+ return rangeOpGT, s[2:]
+ case strings.HasPrefix(s, "< "):
+ return rangeOpLT, s[2:]
+ default:
+ return rangeOpNone, s
+ }
+}
+
+// NewStringPredicateFromGlobs creates a string predicate from the given glob patterns.
+// A glob pattern starting with "!" is a negation pattern which will be ANDed with the rest.
+func NewStringPredicateFromGlobs(patterns []string, getGlob func(pattern string) (glob.Glob, error)) (P[string], error) {
+ var p PR[string]
+ for _, pattern := range patterns {
+ pattern = strings.TrimSpace(pattern)
+ if pattern == "" {
+ continue
+ }
+ negate := strings.HasPrefix(pattern, hglob.NegationPrefix)
+ if negate {
+ pattern = pattern[2:]
+ g, err := getGlob(pattern)
+ if err != nil {
+ return nil, err
+ }
+ p = p.And(func(s string) Match {
+ return BoolMatch(!g.Match(s))
+ })
+ } else {
+ g, err := getGlob(pattern)
+ if err != nil {
+ return nil, err
+ }
+ p = p.Or(func(s string) Match {
+ return BoolMatch(g.Match(s))
+ })
+
+ }
+ }
+
+ return p.BoolFunc(), nil
+}
+
+// NewIndexStringPredicateFromGlobsAndRanges creates an IndexString predicate from the given glob patterns and range patterns.
+// A glob pattern starting with "!" is a negation pattern which will be ANDed with the rest.
+// A range pattern is one of "> value", ">= value", "< value" or "<= value".
+func NewIndexStringPredicateFromGlobsAndRanges(patterns []string, getIndex func(s string) int, getGlob func(pattern string) (glob.Glob, error)) (P[IndexString], error) {
+ var p PR[IndexString]
+ for _, pattern := range patterns {
+ pattern = strings.TrimSpace(pattern)
+ if pattern == "" {
+ continue
+ }
+ negate := strings.HasPrefix(pattern, hglob.NegationPrefix)
+ if negate {
+ pattern = pattern[2:]
+ g, err := getGlob(pattern)
+ if err != nil {
+ return nil, err
+ }
+ p = p.And(func(s IndexString) Match {
+ return BoolMatch(!g.Match(s.String))
+ })
+ } else {
+ // This can be either a glob or a value prefixed with one of >, >=, < or <=.
+ o, v := cutRangeOp(pattern)
+ if o != rangeOpNone {
+ i := getIndex(v)
+ if i == -1 {
+ // No match possible.
+ p = p.And(func(s IndexString) Match {
+ return BoolMatch(false)
+ })
+ continue
+ }
+ switch o {
+ // The greater values starts at the top with index 0.
+ case rangeOpGT:
+ p = p.And(func(s IndexString) Match {
+ return BoolMatch(s.Index < i)
+ })
+ case rangeOpGTE:
+ p = p.And(func(s IndexString) Match {
+ return BoolMatch(s.Index <= i)
+ })
+ case rangeOpLT:
+ p = p.And(func(s IndexString) Match {
+ return BoolMatch(s.Index > i)
+ })
+ case rangeOpLTE:
+ p = p.And(func(s IndexString) Match {
+ return BoolMatch(s.Index >= i)
+ })
+ case rangeOpEQ:
+ p = p.And(func(s IndexString) Match {
+ return BoolMatch(s.Index == i)
+ })
+ case rangeOpNE:
+ p = p.And(func(s IndexString) Match {
+ return BoolMatch(s.Index != i)
+ })
+ }
+ } else {
+ g, err := getGlob(pattern)
+ if err != nil {
+ return nil, err
+ }
+ p = p.Or(func(s IndexString) Match {
+ return BoolMatch(g.Match(s.String))
+ })
+ }
+
+ }
+ }
+
+ return p.BoolFunc(), nil
+}
+
+type IndexString struct {
+ Index int
+ String string
+}
+
+type IndexMatcher interface {
+ IndexMatch(match P[IndexString]) (iter.Seq[int], error)
+}
diff --git a/common/predicate/predicate_test.go b/common/predicate/predicate_test.go
new file mode 100644
index 00000000000..6802938cb92
--- /dev/null
+++ b/common/predicate/predicate_test.go
@@ -0,0 +1,282 @@
+// 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 predicate_test
+
+import (
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+ "github.com/gobwas/glob"
+ "github.com/gohugoio/hugo/common/predicate"
+)
+
+func TestPredicate(t *testing.T) {
+ c := qt.New(t)
+
+ n := func() predicate.PR[int] {
+ var pr predicate.PR[int]
+ return pr
+ }
+
+ var pr predicate.PR[int]
+ p := pr.BoolFunc()
+ c.Assert(p(1), qt.IsFalse)
+
+ pr = n().Or(intP1).Or(intP2)
+ p = pr.BoolFunc()
+ c.Assert(p(1), qt.IsTrue) // true || false
+ c.Assert(p(2), qt.IsTrue) // false || true
+ c.Assert(p(3), qt.IsFalse) // false || false
+
+ pr = pr.And(intP3)
+ p = pr.BoolFunc()
+ c.Assert(p(2), qt.IsFalse) // true || true && false
+ c.Assert(pr(10), qt.IsTrue) // true || true && true
+
+ pr = pr.And(intP4)
+ p = pr.BoolFunc()
+ c.Assert(p(10), qt.IsTrue) // true || true && true && true
+ c.Assert(p(2), qt.IsFalse) // true || true && false && false
+ c.Assert(p(1), qt.IsFalse) // true || false && false && false
+ c.Assert(p(3), qt.IsFalse) // false || false && false && false
+ c.Assert(p(4), qt.IsFalse) // false || false && false && false
+ c.Assert(p(42), qt.IsFalse) // false || false && false && false
+
+ pr = n().And(intP1).And(intP2).And(intP3).And(intP4)
+ p = pr.BoolFunc()
+ c.Assert(p(1), qt.IsFalse)
+ c.Assert(p(2), qt.IsFalse)
+ c.Assert(p(10), qt.IsTrue)
+
+ pr = n().And(intP1).And(intP2).And(intP3).And(intP4)
+ p = pr.BoolFunc()
+ c.Assert(p(1), qt.IsFalse)
+ c.Assert(p(2), qt.IsFalse)
+ c.Assert(p(10), qt.IsTrue)
+
+ pr = n().Or(intP1).Or(intP2).Or(intP3)
+ p = pr.BoolFunc()
+ c.Assert(p(1), qt.IsTrue)
+ c.Assert(p(10), qt.IsTrue)
+ c.Assert(p(4), qt.IsFalse)
+}
+
+func TestFilter(t *testing.T) {
+ c := qt.New(t)
+
+ var p predicate.PR[int]
+ p = p.Or(intP1).Or(intP2)
+
+ ints := []int{1, 2, 3, 4, 1, 6, 7, 8, 2}
+
+ c.Assert(p.Filter(ints), qt.DeepEquals, []int{1, 2, 1, 2})
+ c.Assert(ints, qt.DeepEquals, []int{1, 2, 1, 2, 1, 6, 7, 8, 2})
+}
+
+func TestFilterCopy(t *testing.T) {
+ c := qt.New(t)
+
+ var p predicate.PR[int]
+ p = p.Or(intP1).Or(intP2)
+
+ ints := []int{1, 2, 3, 4, 1, 6, 7, 8, 2}
+
+ c.Assert(p.FilterCopy(ints), qt.DeepEquals, []int{1, 2, 1, 2})
+ c.Assert(ints, qt.DeepEquals, []int{1, 2, 3, 4, 1, 6, 7, 8, 2})
+}
+
+var intP1 = func(i int) predicate.Match {
+ if i == 10 {
+ return predicate.True
+ }
+ return predicate.BoolMatch(i == 1)
+}
+
+var intP2 = func(i int) predicate.Match {
+ if i == 10 {
+ return predicate.True
+ }
+ return predicate.BoolMatch(i == 2)
+}
+
+var intP3 = func(i int) predicate.Match {
+ if i == 10 {
+ return predicate.True
+ }
+ return predicate.BoolMatch(i == 3)
+}
+
+var intP4 = func(i int) predicate.Match {
+ if i == 10 {
+ return predicate.True
+ }
+ return predicate.BoolMatch(i == 4)
+}
+
+func TestNewStringPredicateFromGlobs(t *testing.T) {
+ c := qt.New(t)
+
+ getGlob := func(pattern string) (glob.Glob, error) {
+ return glob.Compile(pattern)
+ }
+
+ n := func(patterns ...string) predicate.P[string] {
+ p, err := predicate.NewStringPredicateFromGlobs(patterns, getGlob)
+ c.Assert(err, qt.IsNil)
+ return p
+ }
+
+ m := n("a", "! ab*", "abc")
+ c.Assert(m("a"), qt.IsTrue)
+ c.Assert(m("ab"), qt.IsFalse)
+ c.Assert(m("abc"), qt.IsFalse)
+
+ m = n()
+ c.Assert(m("anything"), qt.IsFalse)
+}
+
+func TestNewIndexStringPredicateFromGlobsAndRanges(t *testing.T) {
+ c := qt.New(t)
+
+ // Simulate versions: v4.0.0=0, v3.0.0=1, v2.0.0=2, v1.0.0=3
+ // Lower index = greater value.
+ versions := []string{"v4.0.0", "v3.0.0", "v2.0.0", "v1.0.0"}
+ getIndex := func(s string) int {
+ for i, v := range versions {
+ if v == s {
+ return i
+ }
+ }
+ return -1
+ }
+ getGlob := func(pattern string) (glob.Glob, error) {
+ return glob.Compile(pattern)
+ }
+
+ n := func(patterns ...string) predicate.P[predicate.IndexString] {
+ p, err := predicate.NewIndexStringPredicateFromGlobsAndRanges(patterns, getIndex, getGlob)
+ c.Assert(err, qt.IsNil)
+ return p
+ }
+
+ is := func(i int) predicate.IndexString {
+ return predicate.IndexString{Index: i, String: versions[i]}
+ }
+
+ // Test >= v2.0.0 (index 2): should match indices <= 2
+ m := n(">= v2.0.0")
+ c.Assert(m(is(0)), qt.IsTrue) // v4.0.0
+ c.Assert(m(is(1)), qt.IsTrue) // v3.0.0
+ c.Assert(m(is(2)), qt.IsTrue) // v2.0.0
+ c.Assert(m(is(3)), qt.IsFalse) // v1.0.0
+
+ // Test > v2.0.0 (index 2): should match indices < 2
+ m = n("> v2.0.0")
+ c.Assert(m(is(0)), qt.IsTrue) // v4.0.0
+ c.Assert(m(is(1)), qt.IsTrue) // v3.0.0
+ c.Assert(m(is(2)), qt.IsFalse) // v2.0.0
+ c.Assert(m(is(3)), qt.IsFalse) // v1.0.0
+
+ // Test < v3.0.0 (index 1): should match indices > 1
+ m = n("< v3.0.0")
+ c.Assert(m(is(0)), qt.IsFalse) // v4.0.0
+ c.Assert(m(is(1)), qt.IsFalse) // v3.0.0
+ c.Assert(m(is(2)), qt.IsTrue) // v2.0.0
+ c.Assert(m(is(3)), qt.IsTrue) // v1.0.0
+
+ // Test range: >= v2.0.0 AND <= v3.0.0
+ m = n(">= v2.0.0", "<= v3.0.0")
+ c.Assert(m(is(0)), qt.IsFalse) // v4.0.0 - too high
+ c.Assert(m(is(1)), qt.IsTrue) // v3.0.0
+ c.Assert(m(is(2)), qt.IsTrue) // v2.0.0
+ c.Assert(m(is(3)), qt.IsFalse) // v1.0.0 - too low
+
+ // Test glob pattern
+ m = n("v2.*.*")
+ c.Assert(m(is(0)), qt.IsFalse) // v4.0.0
+ c.Assert(m(is(2)), qt.IsTrue) // v2.0.0
+
+ // Test glob with negation
+ m = n("v*.*.*", "! v3.*.*")
+ c.Assert(m(is(0)), qt.IsTrue) // v4.0.0
+ c.Assert(m(is(1)), qt.IsFalse) // v3.0.0 - negated
+ c.Assert(m(is(2)), qt.IsTrue) // v2.0.0
+
+ // Test range with negation: >= v2.0.0 but not v3.0.0
+ m = n(">= v2.0.0", "! v3.0.0")
+ c.Assert(m(is(0)), qt.IsTrue) // v4.0.0
+ c.Assert(m(is(1)), qt.IsFalse) // v3.0.0 - negated
+ c.Assert(m(is(2)), qt.IsTrue) // v2.0.0
+ c.Assert(m(is(3)), qt.IsFalse) // v1.0.0 - out of range
+
+ // Test unknown value in range returns no match
+ m = n(">= v99.0.0")
+ c.Assert(m(is(0)), qt.IsFalse)
+ c.Assert(m(is(3)), qt.IsFalse)
+
+ // Test == v2.0.0: should only match v2.0.0
+ m = n("== v2.0.0")
+ c.Assert(m(is(0)), qt.IsFalse) // v4.0.0
+ c.Assert(m(is(1)), qt.IsFalse) // v3.0.0
+ c.Assert(m(is(2)), qt.IsTrue) // v2.0.0
+ c.Assert(m(is(3)), qt.IsFalse) // v1.0.0
+
+ // Test != v2.0.0: should match everything except v2.0.0
+ m = n("!= v2.0.0")
+ c.Assert(m(is(0)), qt.IsTrue) // v4.0.0
+ c.Assert(m(is(1)), qt.IsTrue) // v3.0.0
+ c.Assert(m(is(2)), qt.IsFalse) // v2.0.0
+ c.Assert(m(is(3)), qt.IsTrue) // v1.0.0
+
+ // Test != with range: >= v2.0.0 AND != v3.0.0
+ m = n(">= v2.0.0", "!= v3.0.0")
+ c.Assert(m(is(0)), qt.IsTrue) // v4.0.0
+ c.Assert(m(is(1)), qt.IsFalse) // v3.0.0 - excluded by !=
+ c.Assert(m(is(2)), qt.IsTrue) // v2.0.0
+ c.Assert(m(is(3)), qt.IsFalse) // v1.0.0 - out of range
+}
+
+func BenchmarkPredicate(b *testing.B) {
+ b.Run("and or no match", func(b *testing.B) {
+ var p predicate.PR[int] = intP1
+ p = p.And(intP2).Or(intP3)
+ for b.Loop() {
+ _ = p(3).OK()
+ }
+ })
+
+ b.Run("and and no match", func(b *testing.B) {
+ var p predicate.PR[int] = intP1
+ p = p.And(intP2)
+ for b.Loop() {
+ _ = p(3).OK()
+ }
+ })
+
+ b.Run("and and match", func(b *testing.B) {
+ var p predicate.PR[int] = intP1
+ p = p.And(intP2)
+ for b.Loop() {
+ _ = p(10).OK()
+ }
+ })
+
+ b.Run("or or match", func(b *testing.B) {
+ var p predicate.PR[int] = intP1
+ p = p.Or(intP2).Or(intP3)
+ for b.Loop() {
+ _ = p(2).OK()
+ }
+ })
+}
diff --git a/common/predicate/rangeop_test.go b/common/predicate/rangeop_test.go
new file mode 100644
index 00000000000..1650c276459
--- /dev/null
+++ b/common/predicate/rangeop_test.go
@@ -0,0 +1,52 @@
+// Copyright 2026 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package predicate
+
+import (
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestCutRangeOp(t *testing.T) {
+ c := qt.New(t)
+
+ op, rest := cutRangeOp(">= value")
+ c.Assert(op, qt.Equals, rangeOpGTE)
+ c.Assert(rest, qt.Equals, "value")
+
+ op, rest = cutRangeOp("<= value")
+ c.Assert(op, qt.Equals, rangeOpLTE)
+ c.Assert(rest, qt.Equals, "value")
+
+ op, rest = cutRangeOp("> value")
+ c.Assert(op, qt.Equals, rangeOpGT)
+ c.Assert(rest, qt.Equals, "value")
+
+ op, rest = cutRangeOp("< value")
+ c.Assert(op, qt.Equals, rangeOpLT)
+ c.Assert(rest, qt.Equals, "value")
+
+ op, rest = cutRangeOp("== value")
+ c.Assert(op, qt.Equals, rangeOpEQ)
+ c.Assert(rest, qt.Equals, "value")
+
+ op, rest = cutRangeOp("!= value")
+ c.Assert(op, qt.Equals, rangeOpNE)
+ c.Assert(rest, qt.Equals, "value")
+
+ op, rest = cutRangeOp("value")
+ c.Assert(op, qt.Equals, rangeOpNone)
+ c.Assert(rest, qt.Equals, "value")
+}
diff --git a/common/rungroup/rungroup.go b/common/rungroup/rungroup.go
new file mode 100644
index 00000000000..80a730ca938
--- /dev/null
+++ b/common/rungroup/rungroup.go
@@ -0,0 +1,93 @@
+// 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 rungroup
+
+import (
+ "context"
+
+ "golang.org/x/sync/errgroup"
+)
+
+// Group is a group of workers that can be used to enqueue work and wait for
+// them to finish.
+type Group[T any] interface {
+ Enqueue(T) error
+ Wait() error
+}
+
+type runGroup[T any] struct {
+ ctx context.Context
+ g *errgroup.Group
+ ch chan T
+}
+
+// Config is the configuration for a new Group.
+type Config[T any] struct {
+ NumWorkers int
+ Handle func(context.Context, T) error
+}
+
+// Run creates a new Group with the given configuration.
+func Run[T any](ctx context.Context, cfg Config[T]) Group[T] {
+ if cfg.NumWorkers <= 0 {
+ cfg.NumWorkers = 1
+ }
+ if cfg.Handle == nil {
+ panic("Handle must be set")
+ }
+
+ g, ctx := errgroup.WithContext(ctx)
+ // Buffered for performance.
+ ch := make(chan T, cfg.NumWorkers)
+
+ for range cfg.NumWorkers {
+ g.Go(func() error {
+ for {
+ select {
+ case <-ctx.Done():
+ return nil
+ case v, ok := <-ch:
+ if !ok {
+ return nil
+ }
+ if err := cfg.Handle(ctx, v); err != nil {
+ return err
+ }
+ }
+ }
+ })
+ }
+
+ return &runGroup[T]{
+ ctx: ctx,
+ g: g,
+ ch: ch,
+ }
+}
+
+// Enqueue enqueues a new item to be handled by the workers.
+func (r *runGroup[T]) Enqueue(t T) error {
+ select {
+ case <-r.ctx.Done():
+ return nil
+ case r.ch <- t:
+ }
+ return nil
+}
+
+// Wait waits for all workers to finish and returns the first error.
+func (r *runGroup[T]) Wait() error {
+ close(r.ch)
+ return r.g.Wait()
+}
diff --git a/common/rungroup/rungroup_test.go b/common/rungroup/rungroup_test.go
new file mode 100644
index 00000000000..ac902079e9c
--- /dev/null
+++ b/common/rungroup/rungroup_test.go
@@ -0,0 +1,44 @@
+// 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 rungroup
+
+import (
+ "context"
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestNew(t *testing.T) {
+ c := qt.New(t)
+
+ var result int
+ adder := func(ctx context.Context, i int) error {
+ result += i
+ return nil
+ }
+
+ g := Run[int](
+ context.Background(),
+ Config[int]{
+ Handle: adder,
+ },
+ )
+
+ c.Assert(g, qt.IsNotNil)
+ g.Enqueue(32)
+ g.Enqueue(33)
+ c.Assert(g.Wait(), qt.IsNil)
+ c.Assert(result, qt.Equals, 65)
+}
diff --git a/common/tasks/tasks.go b/common/tasks/tasks.go
new file mode 100644
index 00000000000..3f8a754e9d7
--- /dev/null
+++ b/common/tasks/tasks.go
@@ -0,0 +1,150 @@
+// Copyright 2024 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package tasks
+
+import (
+ "sync"
+ "time"
+)
+
+// RunEvery runs a function at intervals defined by the function itself.
+// Functions can be added and removed while running.
+type RunEvery struct {
+ // Any error returned from the function will be passed to this function.
+ HandleError func(string, error)
+
+ // If set, the function will be run immediately.
+ RunImmediately bool
+
+ // The named functions to run.
+ funcs map[string]*Func
+
+ mu sync.Mutex
+ started bool
+ closed bool
+ quit chan struct{}
+}
+
+type Func struct {
+ // The shortest interval between each run.
+ IntervalLow time.Duration
+
+ // The longest interval between each run.
+ IntervalHigh time.Duration
+
+ // The function to run.
+ F func(interval time.Duration) (time.Duration, error)
+
+ interval time.Duration
+ last time.Time
+}
+
+func (r *RunEvery) Start() error {
+ if r.started {
+ return nil
+ }
+
+ r.started = true
+ r.quit = make(chan struct{})
+
+ go func() {
+ if r.RunImmediately {
+ r.run()
+ }
+ ticker := time.NewTicker(500 * time.Millisecond)
+ defer ticker.Stop()
+ for {
+ select {
+ case <-r.quit:
+ return
+ case <-ticker.C:
+ r.run()
+ }
+ }
+ }()
+
+ return nil
+}
+
+// Close stops the RunEvery from running.
+func (r *RunEvery) Close() error {
+ if r.closed {
+ return nil
+ }
+ r.closed = true
+ if r.quit != nil {
+ close(r.quit)
+ }
+ return nil
+}
+
+// Add adds a function to the RunEvery.
+func (r *RunEvery) Add(name string, f Func) {
+ r.mu.Lock()
+ defer r.mu.Unlock()
+ if r.funcs == nil {
+ r.funcs = make(map[string]*Func)
+ }
+ if f.IntervalLow == 0 {
+ f.IntervalLow = 500 * time.Millisecond
+ }
+ if f.IntervalHigh <= f.IntervalLow {
+ f.IntervalHigh = 20 * time.Second
+ }
+
+ start := max(f.IntervalHigh/3, f.IntervalLow)
+ f.interval = start
+ f.last = time.Now()
+
+ r.funcs[name] = &f
+}
+
+// Remove removes a function from the RunEvery.
+func (r *RunEvery) Remove(name string) {
+ r.mu.Lock()
+ defer r.mu.Unlock()
+ delete(r.funcs, name)
+}
+
+// Has returns whether the RunEvery has a function with the given name.
+func (r *RunEvery) Has(name string) bool {
+ r.mu.Lock()
+ defer r.mu.Unlock()
+ _, found := r.funcs[name]
+ return found
+}
+
+func (r *RunEvery) run() {
+ r.mu.Lock()
+ defer r.mu.Unlock()
+ for name, f := range r.funcs {
+ if time.Now().Before(f.last.Add(f.interval)) {
+ continue
+ }
+ f.last = time.Now()
+ interval, err := f.F(f.interval)
+ if err != nil && r.HandleError != nil {
+ r.HandleError(name, err)
+ }
+
+ if interval < f.IntervalLow {
+ interval = f.IntervalLow
+ }
+
+ if interval > f.IntervalHigh {
+ interval = f.IntervalHigh
+ }
+ f.interval = interval
+ }
+}
diff --git a/common/terminal/colors.go b/common/terminal/colors.go
index 334b82fae5b..853c76ed4cc 100644
--- a/common/terminal/colors.go
+++ b/common/terminal/colors.go
@@ -1,4 +1,4 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
+// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -16,8 +16,8 @@ package terminal
import (
"fmt"
+ "io"
"os"
- "runtime"
"strings"
isatty "github.com/mattn/go-isatty"
@@ -29,13 +29,18 @@ const (
noticeColor = "\033[1;36m%s\033[0m"
)
-// IsTerminal return true if the file descriptor is terminal and the TERM
-// environment variable isn't a dumb one.
-func IsTerminal(f *os.File) bool {
- if runtime.GOOS == "windows" {
+// PrintANSIColors returns false if NO_COLOR env variable is set,
+// else IsTerminal(f).
+func PrintANSIColors(f *os.File) bool {
+ if os.Getenv("NO_COLOR") != "" {
return false
}
+ return IsTerminal(f)
+}
+// IsTerminal return true if the file descriptor is terminal and the TERM
+// environment variable isn't a dumb one.
+func IsTerminal(f *os.File) bool {
fd := f.Fd()
return os.Getenv("TERM") != "dumb" && (isatty.IsTerminal(fd) || isatty.IsCygwinTerminal(fd))
}
@@ -68,3 +73,27 @@ func doublePercent(str string) string {
func singlePercent(str string) string {
return strings.Replace(str, "%%", "%", -1)
}
+
+type ProgressState int
+
+const (
+ ProgressHidden ProgressState = iota
+ ProgressNormal
+ ProgressError
+ ProgressIntermediate
+ ProgressWarning
+)
+
+// ReportProgress writes OSC 9;4 sequence to w.
+func ReportProgress(w io.Writer, state ProgressState, progress float64) {
+ if progress < 0 {
+ progress = 0.0
+ }
+ if progress > 1 {
+ progress = 1.0
+ }
+
+ pi := int(progress * 100)
+
+ fmt.Fprintf(w, "\033]9;4;%d;%d\007", state, pi)
+}
diff --git a/common/text/position.go b/common/text/position.go
index 5dd64526a71..eb9de5624ef 100644
--- a/common/text/position.go
+++ b/common/text/position.go
@@ -24,6 +24,8 @@ import (
// Positioner represents a thing that knows its position in a text file or stream,
// typically an error.
type Positioner interface {
+ // Position returns the current position.
+ // Useful in error logging, e.g. {{ errorf "error in code block: %s" .Position }}.
Position() Position
}
@@ -69,7 +71,7 @@ func createPositionStringFormatter(formatStr string) func(p Position) string {
format := replacer.Replace(formatStr)
f := func(pos Position) string {
- args := make([]interface{}, len(identifiersFound))
+ args := make([]any, len(identifiersFound))
for i, id := range identifiersFound {
switch id {
case ":file":
@@ -83,7 +85,7 @@ func createPositionStringFormatter(formatStr string) func(p Position) string {
msg := fmt.Sprintf(format, args...)
- if terminal.IsTerminal(os.Stdout) {
+ if terminal.PrintANSIColors(os.Stdout) {
return terminal.Notice(msg)
}
diff --git a/common/text/transform.go b/common/text/transform.go
index f5957780363..de093af0d4c 100644
--- a/common/text/transform.go
+++ b/common/text/transform.go
@@ -14,6 +14,7 @@
package text
import (
+ "strings"
"sync"
"unicode"
@@ -23,7 +24,7 @@ import (
)
var accentTransformerPool = &sync.Pool{
- New: func() interface{} {
+ New: func() any {
return transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC)
},
}
@@ -45,3 +46,33 @@ func RemoveAccentsString(s string) string {
accentTransformerPool.Put(t)
return s
}
+
+// Chomp removes trailing newline characters from s.
+func Chomp(s string) string {
+ return strings.TrimRightFunc(s, func(r rune) bool {
+ return r == '\n' || r == '\r'
+ })
+}
+
+// Puts adds a trailing \n none found.
+func Puts(s string) string {
+ if s == "" || s[len(s)-1] == '\n' {
+ return s
+ }
+ return s + "\n"
+}
+
+// VisitLinesAfter calls the given function for each line, including newlines, in the given string.
+func VisitLinesAfter(s string, fn func(line string)) {
+ high := strings.IndexRune(s, '\n')
+ for high != -1 {
+ fn(s[:high+1])
+ s = s[high+1:]
+
+ high = strings.IndexRune(s, '\n')
+ }
+
+ if s != "" {
+ fn(s)
+ }
+}
diff --git a/common/text/transform_test.go b/common/text/transform_test.go
index 08265f976b5..e5ed3567f66 100644
--- a/common/text/transform_test.go
+++ b/common/text/transform_test.go
@@ -26,3 +26,47 @@ func TestRemoveAccents(t *testing.T) {
c.Assert(string(RemoveAccents([]byte("Hugo Rocks!"))), qt.Equals, "Hugo Rocks!")
c.Assert(string(RemoveAccentsString("Resumé")), qt.Equals, "Resume")
}
+
+func TestChomp(t *testing.T) {
+ c := qt.New(t)
+
+ c.Assert(Chomp("\nA\n"), qt.Equals, "\nA")
+ c.Assert(Chomp("A\r\n"), qt.Equals, "A")
+}
+
+func TestPuts(t *testing.T) {
+ c := qt.New(t)
+
+ c.Assert(Puts("A"), qt.Equals, "A\n")
+ c.Assert(Puts("\nA\n"), qt.Equals, "\nA\n")
+ c.Assert(Puts(""), qt.Equals, "")
+}
+
+func TestVisitLinesAfter(t *testing.T) {
+ const lines = `line 1
+line 2
+
+line 3`
+
+ var collected []string
+
+ VisitLinesAfter(lines, func(s string) {
+ collected = append(collected, s)
+ })
+
+ c := qt.New(t)
+
+ c.Assert(collected, qt.DeepEquals, []string{"line 1\n", "line 2\n", "\n", "line 3"})
+}
+
+func BenchmarkVisitLinesAfter(b *testing.B) {
+ const lines = `line 1
+ line 2
+
+ line 3`
+
+ for b.Loop() {
+ VisitLinesAfter(lines, func(s string) {
+ })
+ }
+}
diff --git a/common/types/closer.go b/common/types/closer.go
new file mode 100644
index 00000000000..9f8875a8a2f
--- /dev/null
+++ b/common/types/closer.go
@@ -0,0 +1,54 @@
+// Copyright 2024 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package types
+
+import "sync"
+
+type Closer interface {
+ Close() error
+}
+
+// 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)
+}
+
+type Closers struct {
+ mu sync.Mutex
+ cs []Closer
+}
+
+func (cs *Closers) Add(c Closer) {
+ cs.mu.Lock()
+ defer cs.mu.Unlock()
+ cs.cs = append(cs.cs, c)
+}
+
+func (cs *Closers) Close() error {
+ cs.mu.Lock()
+ defer cs.mu.Unlock()
+ for _, c := range cs.cs {
+ c.Close()
+ }
+
+ cs.cs = cs.cs[:0]
+
+ return nil
+}
diff --git a/common/types/convert.go b/common/types/convert.go
index 0560eda0de0..6b175037695 100644
--- a/common/types/convert.go
+++ b/common/types/convert.go
@@ -25,13 +25,13 @@ import (
// ToDuration converts v to time.Duration.
// See ToDurationE if you need to handle errors.
-func ToDuration(v interface{}) time.Duration {
+func ToDuration(v any) time.Duration {
d, _ := ToDurationE(v)
return d
}
// ToDurationE converts v to time.Duration.
-func ToDurationE(v interface{}) (time.Duration, error) {
+func ToDurationE(v any) (time.Duration, error) {
if n := cast.ToInt(v); n > 0 {
return time.Duration(n) * time.Millisecond, nil
}
@@ -44,14 +44,14 @@ func ToDurationE(v interface{}) (time.Duration, error) {
// ToStringSlicePreserveString is the same as ToStringSlicePreserveStringE,
// but it never fails.
-func ToStringSlicePreserveString(v interface{}) []string {
+func ToStringSlicePreserveString(v any) []string {
vv, _ := ToStringSlicePreserveStringE(v)
return vv
}
// ToStringSlicePreserveStringE converts v to a string slice.
// If v is a string, it will be wrapped in a string slice.
-func ToStringSlicePreserveStringE(v interface{}) ([]string, error) {
+func ToStringSlicePreserveStringE(v any) ([]string, error) {
if v == nil {
return nil, nil
}
@@ -69,7 +69,7 @@ func ToStringSlicePreserveStringE(v interface{}) ([]string, error) {
switch vv.Kind() {
case reflect.Slice, reflect.Array:
result = make([]string, vv.Len())
- for i := 0; i < vv.Len(); i++ {
+ for i := range vv.Len() {
s, err := cast.ToStringE(vv.Index(i).Interface())
if err != nil {
return nil, err
@@ -80,13 +80,12 @@ func ToStringSlicePreserveStringE(v interface{}) ([]string, error) {
default:
return nil, fmt.Errorf("failed to convert %T to a string slice", v)
}
-
}
// TypeToString converts v to a string if it's a valid string type.
// Note that this will not try to convert numeric values etc.,
// use ToString for that.
-func TypeToString(v interface{}) (string, bool) {
+func TypeToString(v any) (string, bool) {
switch s := v.(type) {
case string:
return s, true
@@ -110,13 +109,13 @@ func TypeToString(v interface{}) (string, bool) {
}
// ToString converts v to a string.
-func ToString(v interface{}) string {
+func ToString(v any) string {
s, _ := ToStringE(v)
return s
}
// ToStringE converts v to a string.
-func ToStringE(v interface{}) (string, error) {
+func ToStringE(v any) (string, error) {
if s, ok := TypeToString(v); ok {
return s, nil
}
diff --git a/common/types/convert_test.go b/common/types/convert_test.go
index 88b30360463..13059285d5f 100644
--- a/common/types/convert_test.go
+++ b/common/types/convert_test.go
@@ -26,7 +26,7 @@ func TestToStringSlicePreserveString(t *testing.T) {
c.Assert(ToStringSlicePreserveString("Hugo"), qt.DeepEquals, []string{"Hugo"})
c.Assert(ToStringSlicePreserveString(qt.Commentf("Hugo")), qt.DeepEquals, []string{"Hugo"})
- c.Assert(ToStringSlicePreserveString([]interface{}{"A", "B"}), qt.DeepEquals, []string{"A", "B"})
+ c.Assert(ToStringSlicePreserveString([]any{"A", "B"}), qt.DeepEquals, []string{"A", "B"})
c.Assert(ToStringSlicePreserveString([]int{1, 3}), qt.DeepEquals, []string{"1", "3"})
c.Assert(ToStringSlicePreserveString(nil), qt.IsNil)
}
@@ -45,5 +45,4 @@ func TestToDuration(t *testing.T) {
c.Assert(ToDuration("200"), qt.Equals, 200*time.Millisecond)
c.Assert(ToDuration("4m"), qt.Equals, 4*time.Minute)
c.Assert(ToDuration("asdfadf"), qt.Equals, time.Duration(0))
-
}
diff --git a/common/types/css/csstypes.go b/common/types/css/csstypes.go
new file mode 100644
index 00000000000..061acfe647e
--- /dev/null
+++ b/common/types/css/csstypes.go
@@ -0,0 +1,20 @@
+// 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 css
+
+// QuotedString is a string that needs to be quoted in CSS.
+type QuotedString string
+
+// UnquotedString is a string that does not need to be quoted in CSS.
+type UnquotedString string
diff --git a/common/types/evictingqueue.go b/common/types/evictingqueue.go
index 8847624265d..a335be3b272 100644
--- a/common/types/evictingqueue.go
+++ b/common/types/evictingqueue.go
@@ -15,57 +15,72 @@
package types
import (
+ "slices"
"sync"
)
-// EvictingStringQueue is a queue which automatically evicts elements from the head of
+// EvictingQueue is a queue which automatically evicts elements from the head of
// the queue when attempting to add new elements onto the queue and it is full.
// This queue orders elements LIFO (last-in-first-out). It throws away duplicates.
-// Note: This queue currently does not contain any remove (poll etc.) methods.
-type EvictingStringQueue struct {
+type EvictingQueue[T comparable] struct {
size int
- vals []string
- set map[string]bool
+ vals []T
+ set map[T]bool
mu sync.Mutex
+ zero T
}
-// NewEvictingStringQueue creates a new queue with the given size.
-func NewEvictingStringQueue(size int) *EvictingStringQueue {
- return &EvictingStringQueue{size: size, set: make(map[string]bool)}
+// NewEvictingQueue creates a new queue with the given size.
+func NewEvictingQueue[T comparable](size int) *EvictingQueue[T] {
+ return &EvictingQueue[T]{size: size, set: make(map[T]bool)}
}
// Add adds a new string to the tail of the queue if it's not already there.
-func (q *EvictingStringQueue) Add(v string) {
+func (q *EvictingQueue[T]) Add(v T) *EvictingQueue[T] {
q.mu.Lock()
if q.set[v] {
q.mu.Unlock()
- return
+ return q
}
if len(q.set) == q.size {
// Full
delete(q.set, q.vals[0])
- q.vals = append(q.vals[:0], q.vals[1:]...)
+ q.vals = slices.Delete(q.vals, 0, 1)
}
q.set[v] = true
q.vals = append(q.vals, v)
q.mu.Unlock()
+
+ return q
+}
+
+func (q *EvictingQueue[T]) Len() int {
+ if q == nil {
+ return 0
+ }
+ q.mu.Lock()
+ defer q.mu.Unlock()
+ return len(q.vals)
}
// Contains returns whether the queue contains v.
-func (q *EvictingStringQueue) Contains(v string) bool {
+func (q *EvictingQueue[T]) Contains(v T) bool {
+ if q == nil {
+ return false
+ }
q.mu.Lock()
defer q.mu.Unlock()
return q.set[v]
}
// Peek looks at the last element added to the queue.
-func (q *EvictingStringQueue) Peek() string {
+func (q *EvictingQueue[T]) Peek() T {
q.mu.Lock()
l := len(q.vals)
if l == 0 {
q.mu.Unlock()
- return ""
+ return q.zero
}
elem := q.vals[l-1]
q.mu.Unlock()
@@ -73,9 +88,12 @@ func (q *EvictingStringQueue) Peek() string {
}
// PeekAll looks at all the elements in the queue, with the newest first.
-func (q *EvictingStringQueue) PeekAll() []string {
+func (q *EvictingQueue[T]) PeekAll() []T {
+ if q == nil {
+ return nil
+ }
q.mu.Lock()
- vals := make([]string, len(q.vals))
+ vals := make([]T, len(q.vals))
copy(vals, q.vals)
q.mu.Unlock()
for i, j := 0, len(vals)-1; i < j; i, j = i+1, j-1 {
@@ -85,9 +103,9 @@ func (q *EvictingStringQueue) PeekAll() []string {
}
// PeekAllSet returns PeekAll as a set.
-func (q *EvictingStringQueue) PeekAllSet() map[string]bool {
+func (q *EvictingQueue[T]) PeekAllSet() map[T]bool {
all := q.PeekAll()
- set := make(map[string]bool)
+ set := make(map[T]bool)
for _, v := range all {
set[v] = true
}
diff --git a/common/types/evictingqueue_test.go b/common/types/evictingqueue_test.go
index 7489ba88d9b..a2fcc8e6f4b 100644
--- a/common/types/evictingqueue_test.go
+++ b/common/types/evictingqueue_test.go
@@ -23,7 +23,7 @@ import (
func TestEvictingStringQueue(t *testing.T) {
c := qt.New(t)
- queue := NewEvictingStringQueue(3)
+ queue := NewEvictingQueue[string](3)
c.Assert(queue.Peek(), qt.Equals, "")
queue.Add("a")
@@ -53,12 +53,10 @@ func TestEvictingStringQueueConcurrent(t *testing.T) {
var wg sync.WaitGroup
val := "someval"
- queue := NewEvictingStringQueue(3)
+ queue := NewEvictingQueue[string](3)
- for j := 0; j < 100; j++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
+ for range 100 {
+ wg.Go(func() {
queue.Add(val)
v := queue.Peek()
if v != val {
@@ -68,7 +66,7 @@ func TestEvictingStringQueueConcurrent(t *testing.T) {
if len(vals) != 1 || vals[0] != val {
t.Error("wrong val")
}
- }()
+ })
}
wg.Wait()
}
diff --git a/common/types/hstring/stringtypes.go b/common/types/hstring/stringtypes.go
new file mode 100644
index 00000000000..53ce2068f01
--- /dev/null
+++ b/common/types/hstring/stringtypes.go
@@ -0,0 +1,36 @@
+// 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 hstring
+
+import (
+ "html/template"
+
+ "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 {
+ return string(s)
+}
+
+func (s HTML) PrintableValue() any {
+ return template.HTML(s)
+}
diff --git a/common/types/hstring/stringtypes_test.go b/common/types/hstring/stringtypes_test.go
new file mode 100644
index 00000000000..05e2c22b9b6
--- /dev/null
+++ b/common/types/hstring/stringtypes_test.go
@@ -0,0 +1,30 @@
+// 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 hstring
+
+import (
+ "html/template"
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+ "github.com/spf13/cast"
+)
+
+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"))
+}
diff --git a/common/types/types.go b/common/types/types.go
index 4f9f02c8d7d..733a10946a5 100644
--- a/common/types/types.go
+++ b/common/types/types.go
@@ -17,6 +17,7 @@ package types
import (
"fmt"
"reflect"
+ "sync/atomic"
"github.com/spf13/cast"
)
@@ -27,10 +28,20 @@ type RLocker interface {
RUnlock()
}
+type Locker interface {
+ Lock()
+ Unlock()
+}
+
+type RWLocker interface {
+ RLocker
+ Locker
+}
+
// KeyValue is a interface{} tuple.
type KeyValue struct {
- Key interface{}
- Value interface{}
+ Key any
+ Value any
}
// KeyValueStr is a string tuple.
@@ -41,8 +52,8 @@ type KeyValueStr struct {
// KeyValues holds an key and a slice of values.
type KeyValues struct {
- Key interface{}
- Values []interface{}
+ Key any
+ Values []any
}
// KeyString returns the key as a string, an empty string if conversion fails.
@@ -57,8 +68,8 @@ func (k KeyValues) String() string {
// NewKeyValuesStrings takes a given key and slice of values and returns a new
// KeyValues struct.
func NewKeyValuesStrings(key string, values ...string) KeyValues {
- iv := make([]interface{}, len(values))
- for i := 0; i < len(values); i++ {
+ iv := make([]any, len(values))
+ for i := range values {
iv[i] = values[i]
}
return KeyValues{Key: key, Values: iv}
@@ -71,14 +82,14 @@ type Zeroer interface {
}
// IsNil reports whether v is nil.
-func IsNil(v interface{}) bool {
+func IsNil(v any) bool {
if v == nil {
return true
}
value := reflect.ValueOf(v)
switch value.Kind() {
- case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
+ case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice:
return value.IsNil()
}
@@ -90,3 +101,63 @@ func IsNil(v interface{}) bool {
type DevMarker interface {
DevOnly()
}
+
+// Unwrapper is implemented by types that can unwrap themselves.
+type Unwrapper interface {
+ // Unwrapv is for internal use only.
+ // It got its slightly odd name to prevent collisions with user types.
+ Unwrapv() any
+}
+
+// Unwrap returns the underlying value of v if it implements Unwrapper, otherwise v is returned.
+func Unwrapv(v any) any {
+ if u, ok := v.(Unwrapper); ok {
+ return u.Unwrapv()
+ }
+ return v
+}
+
+// LowHigh represents a byte or slice boundary.
+type LowHigh[S ~[]byte | string] 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
+
+// NewTrue returns a pointer to b.
+func NewBool(b bool) *bool {
+ return &b
+}
+
+// WeightProvider provides a weight.
+type WeightProvider interface {
+ Weight() int
+}
+
+// Weight0Provider provides a weight that's considered before the WeightProvider in sorting.
+// This allows the weight set on a given term to win.
+type Weight0Provider interface {
+ Weight0() int
+}
+
+// PrintableValueProvider is implemented by types that can provide a printable value.
+type PrintableValueProvider interface {
+ PrintableValue() any
+}
+
+type (
+ Strings2 [2]string
+ Strings3 [3]string
+ Ints2 [2]int
+ Ints3 [3]int
+)
diff --git a/common/types/types_test.go b/common/types/types_test.go
index 7c5cba659cd..7957330477a 100644
--- a/common/types/types_test.go
+++ b/common/types/types_test.go
@@ -25,5 +25,27 @@ func TestKeyValues(t *testing.T) {
kv := NewKeyValuesStrings("key", "a1", "a2")
c.Assert(kv.KeyString(), qt.Equals, "key")
- c.Assert(kv.Values, qt.DeepEquals, []interface{}{"a1", "a2"})
+ 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"))
}
diff --git a/common/urls/baseURL.go b/common/urls/baseURL.go
new file mode 100644
index 00000000000..2958a2a0464
--- /dev/null
+++ b/common/urls/baseURL.go
@@ -0,0 +1,112 @@
+// 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 urls
+
+import (
+ "fmt"
+ "net/url"
+ "strconv"
+ "strings"
+)
+
+// A BaseURL in Hugo is normally on the form scheme://path, but the
+// form scheme: is also valid (mailto:hugo@rules.com).
+type BaseURL struct {
+ url *url.URL
+ WithPath string
+ WithPathNoTrailingSlash string
+ WithoutPath string
+ BasePath string
+ BasePathNoTrailingSlash string
+}
+
+func (b BaseURL) String() string {
+ return b.WithPath
+}
+
+func (b BaseURL) Path() string {
+ return b.url.Path
+}
+
+func (b BaseURL) Port() int {
+ p, _ := strconv.Atoi(b.url.Port())
+ return p
+}
+
+// HostURL returns the URL to the host root without any path elements.
+func (b BaseURL) HostURL() string {
+ return strings.TrimSuffix(b.String(), b.Path())
+}
+
+// WithProtocol returns the BaseURL prefixed with the given protocol.
+// The Protocol is normally of the form "scheme://", i.e. "webcal://".
+func (b BaseURL) WithProtocol(protocol string) (BaseURL, error) {
+ u := b.URL()
+
+ scheme := protocol
+ isFullProtocol := strings.HasSuffix(scheme, "://")
+ isOpaqueProtocol := strings.HasSuffix(scheme, ":")
+
+ if isFullProtocol {
+ scheme = strings.TrimSuffix(scheme, "://")
+ } else if isOpaqueProtocol {
+ scheme = strings.TrimSuffix(scheme, ":")
+ }
+
+ u.Scheme = scheme
+
+ if isFullProtocol && u.Opaque != "" {
+ u.Opaque = "//" + u.Opaque
+ } else if isOpaqueProtocol && u.Opaque == "" {
+ return BaseURL{}, fmt.Errorf("cannot determine BaseURL for protocol %q", protocol)
+ }
+
+ return newBaseURLFromURL(u)
+}
+
+func (b BaseURL) WithPort(port int) (BaseURL, error) {
+ u := b.URL()
+ u.Host = u.Hostname() + ":" + strconv.Itoa(port)
+ return newBaseURLFromURL(u)
+}
+
+// URL returns a copy of the internal URL.
+// The copy can be safely used and modified.
+func (b BaseURL) URL() *url.URL {
+ c := *b.url
+ return &c
+}
+
+func NewBaseURLFromString(b string) (BaseURL, error) {
+ u, err := url.Parse(b)
+ if err != nil {
+ return BaseURL{}, err
+ }
+ return newBaseURLFromURL(u)
+}
+
+func newBaseURLFromURL(u *url.URL) (BaseURL, error) {
+ // A baseURL should always have a trailing slash, see #11669.
+ if !strings.HasSuffix(u.Path, "/") {
+ u.Path += "/"
+ }
+ baseURL := BaseURL{url: u, WithPath: u.String(), WithPathNoTrailingSlash: strings.TrimSuffix(u.String(), "/")}
+ baseURLNoPath := baseURL.URL()
+ baseURLNoPath.Path = ""
+ baseURL.WithoutPath = baseURLNoPath.String()
+ baseURL.BasePath = u.Path
+ baseURL.BasePathNoTrailingSlash = strings.TrimSuffix(u.Path, "/")
+
+ return baseURL, nil
+}
diff --git a/common/urls/baseURL_test.go b/common/urls/baseURL_test.go
new file mode 100644
index 00000000000..ba337aac84c
--- /dev/null
+++ b/common/urls/baseURL_test.go
@@ -0,0 +1,81 @@
+// 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 urls
+
+import (
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestBaseURL(t *testing.T) {
+ c := qt.New(t)
+
+ b, err := NewBaseURLFromString("http://example.com/")
+ c.Assert(err, qt.IsNil)
+ c.Assert(b.String(), qt.Equals, "http://example.com/")
+
+ b, err = NewBaseURLFromString("http://example.com")
+ c.Assert(err, qt.IsNil)
+ c.Assert(b.String(), qt.Equals, "http://example.com/")
+ c.Assert(b.WithPathNoTrailingSlash, qt.Equals, "http://example.com")
+ c.Assert(b.BasePath, qt.Equals, "/")
+
+ p, err := b.WithProtocol("webcal://")
+ c.Assert(err, qt.IsNil)
+ c.Assert(p.String(), qt.Equals, "webcal://example.com/")
+
+ p, err = b.WithProtocol("webcal")
+ c.Assert(err, qt.IsNil)
+ c.Assert(p.String(), qt.Equals, "webcal://example.com/")
+
+ _, err = b.WithProtocol("mailto:")
+ c.Assert(err, qt.Not(qt.IsNil))
+
+ b, err = NewBaseURLFromString("mailto:hugo@rules.com")
+ c.Assert(err, qt.IsNil)
+ c.Assert(b.String(), qt.Equals, "mailto:hugo@rules.com")
+
+ // These are pretty constructed
+ p, err = b.WithProtocol("webcal")
+ c.Assert(err, qt.IsNil)
+ c.Assert(p.String(), qt.Equals, "webcal:hugo@rules.com")
+
+ p, err = b.WithProtocol("webcal://")
+ c.Assert(err, qt.IsNil)
+ c.Assert(p.String(), qt.Equals, "webcal://hugo@rules.com")
+
+ // Test with "non-URLs". Some people will try to use these as a way to get
+ // relative URLs working etc.
+ b, err = NewBaseURLFromString("/")
+ c.Assert(err, qt.IsNil)
+ c.Assert(b.String(), qt.Equals, "/")
+
+ b, err = NewBaseURLFromString("")
+ c.Assert(err, qt.IsNil)
+ c.Assert(b.String(), qt.Equals, "/")
+
+ // BaseURL with sub path
+ b, err = NewBaseURLFromString("http://example.com/sub")
+ c.Assert(err, qt.IsNil)
+ c.Assert(b.String(), qt.Equals, "http://example.com/sub/")
+ c.Assert(b.WithPathNoTrailingSlash, qt.Equals, "http://example.com/sub")
+ c.Assert(b.BasePath, qt.Equals, "/sub/")
+ c.Assert(b.BasePathNoTrailingSlash, qt.Equals, "/sub")
+
+ b, err = NewBaseURLFromString("http://example.com/sub/")
+ c.Assert(err, qt.IsNil)
+ c.Assert(b.String(), qt.Equals, "http://example.com/sub/")
+ c.Assert(b.HostURL(), qt.Equals, "http://example.com")
+}
diff --git a/common/urls/ref.go b/common/urls/ref.go
index 71b00b71de8..e5804a27989 100644
--- a/common/urls/ref.go
+++ b/common/urls/ref.go
@@ -17,6 +17,6 @@ package urls
// args must contain a path, but can also point to the target
// language or output format.
type RefLinker interface {
- Ref(args map[string]interface{}) (string, error)
- RelRef(args map[string]interface{}) (string, error)
+ Ref(args map[string]any) (string, error)
+ RelRef(args map[string]any) (string, error)
}
diff --git a/common/version/version.go b/common/version/version.go
new file mode 100644
index 00000000000..34ed9bfd1a4
--- /dev/null
+++ b/common/version/version.go
@@ -0,0 +1,289 @@
+// Copyright 2025 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package version
+
+import (
+ "fmt"
+ "io"
+ "math"
+ "runtime"
+ "strconv"
+ "strings"
+
+ "github.com/gohugoio/hugo/compare"
+ "github.com/spf13/cast"
+)
+
+// Version represents the Hugo build version.
+type Version struct {
+ Major int
+
+ Minor int
+
+ // Increment this for bug releases
+ PatchLevel int
+
+ // HugoVersionSuffix is the suffix used in the Hugo version string.
+ // It will be blank for release versions.
+ Suffix string
+
+ // Set when parsed from a string,
+ source string
+}
+
+var (
+ _ compare.Eqer = (*VersionString)(nil)
+ _ compare.Comparer = (*VersionString)(nil)
+)
+
+// IsAlphaBetaOrRC returns whether this version is an alpha, beta, or release candidate.
+func (v Version) IsAlphaBetaOrRC() bool {
+ s := strings.ToLower(v.Suffix)
+ // e.g. "alpha.1", "beta.2", "rc.3"
+ return strings.Contains(s, "alpha.") || strings.Contains(s, "beta.") || strings.Contains(s, "rc.")
+}
+
+func (v Version) IsZero() bool {
+ return v.Major == 0 && v.Minor == 0 && v.PatchLevel == 0 && v.Suffix == ""
+}
+
+func (v Version) String() string {
+ return version(v.Major, v.Minor, v.PatchLevel, v.Suffix)
+}
+
+// Version returns the Hugo version.
+func (v Version) Version() VersionString {
+ return VersionString(v.String())
+}
+
+// Compare implements the compare.Comparer interface.
+func (h Version) Compare(other any) int {
+ return CompareVersions(h, other)
+}
+
+// VersionString represents a Hugo version string.
+type VersionString string
+
+func (h VersionString) String() string {
+ return string(h)
+}
+
+// Compare implements the compare.Comparer interface.
+func (h VersionString) Compare(other any) int {
+ return CompareVersions(h.Version(), other)
+}
+
+func (h VersionString) Version() Version {
+ return MustParseVersion(h.String())
+}
+
+// Eq implements the compare.Eqer interface.
+func (h VersionString) Eq(other any) bool {
+ s, err := cast.ToStringE(other)
+ if err != nil {
+ return false
+ }
+ return s == h.String()
+}
+
+// ParseVersion parses a version string.
+func ParseVersion(s string) (Version, error) {
+ s = strings.TrimPrefix(strings.TrimSpace(s), "v")
+ var vv Version
+ hyphen := strings.Index(s, "-")
+ if hyphen > 0 {
+ suffix := s[hyphen:]
+ if len(suffix) > 1 {
+ if suffix[0] == '-' {
+ suffix = suffix[1:]
+ }
+ if len(suffix) > 0 {
+ vv.Suffix = suffix
+ s = s[:hyphen]
+ }
+ }
+ vv.Suffix = suffix
+ }
+ vv.Major, vv.Minor, vv.PatchLevel = parseVersion(s)
+ vv.source = s
+
+ return vv, nil
+}
+
+// MustParseVersion parses a version string
+// and panics if any error occurs.
+func MustParseVersion(s string) Version {
+ vv, err := ParseVersion(s)
+ if err != nil {
+ panic(err)
+ }
+ return vv
+}
+
+// ReleaseVersion represents the release version.
+func (v Version) ReleaseVersion() Version {
+ v.Suffix = ""
+ return v
+}
+
+// Next returns the next Hugo release version.
+func (v Version) Next() Version {
+ return Version{Major: v.Major, Minor: v.Minor + 1}
+}
+
+// Prev returns the previous Hugo release version.
+func (v Version) Prev() Version {
+ return Version{Major: v.Major, Minor: v.Minor - 1}
+}
+
+// NextPatchLevel returns the next patch/bugfix Hugo version.
+// This will be a patch increment on the previous Hugo version.
+func (v Version) NextPatchLevel(level int) Version {
+ prev := v.Prev()
+ prev.PatchLevel = level
+ return prev
+}
+
+func version(major, minor, patch int, suffix string) string {
+ if suffix != "" {
+ if suffix[0] != '-' {
+ suffix = "-" + suffix
+ }
+ }
+ if patch > 0 || minor > 53 {
+ return fmt.Sprintf("%d.%d.%d%s", major, minor, patch, suffix)
+ }
+ return fmt.Sprintf("%d.%d%s", major, minor, suffix)
+}
+
+// CompareVersion compares v1 with v2.
+// It returns -1 if the v2 is less than, 0 if equal and 1 if greater than
+// v1.
+func CompareVersions(v1 Version, v2 any) int {
+ var c int
+ switch d := v2.(type) {
+ case float64:
+ c = compareFloatWithVersion(d, v1)
+ case float32:
+ c = compareFloatWithVersion(float64(d), v1)
+ case int:
+ c = compareFloatWithVersion(float64(d), v1)
+ case int32:
+ c = compareFloatWithVersion(float64(d), v1)
+ case int64:
+ c = compareFloatWithVersion(float64(d), v1)
+ case Version:
+ if v1.IsZero() && d.IsZero() {
+ // Fall back to source comparison.
+ return strings.Compare(v1.source, d.source)
+ }
+ if d.Major == v1.Major && d.Minor == v1.Minor && d.PatchLevel == v1.PatchLevel {
+ return strings.Compare(v1.Suffix, d.Suffix)
+ }
+ if d.Major > v1.Major {
+ return 1
+ } else if d.Major < v1.Major {
+ return -1
+ }
+ if d.Minor > v1.Minor {
+ return 1
+ } else if d.Minor < v1.Minor {
+ return -1
+ }
+ if d.PatchLevel > v1.PatchLevel {
+ return 1
+ } else if d.PatchLevel < v1.PatchLevel {
+ return -1
+ }
+ default:
+ s, err := cast.ToStringE(v2)
+ if err != nil {
+ return -1
+ }
+
+ v2v, _ := ParseVersion(s)
+ if v1.IsZero() && v2v.IsZero() {
+ // Fall back to source comparison.
+ return strings.Compare(v1.source, v2v.source)
+ }
+
+ return v1.Compare(v2v)
+
+ }
+
+ return c
+}
+
+func parseVersion(s string) (int, int, int) {
+ var major, minor, patch int
+ parts := strings.Split(s, ".")
+ if len(parts) > 0 {
+ major, _ = strconv.Atoi(parts[0])
+ }
+ if len(parts) > 1 {
+ minor, _ = strconv.Atoi(parts[1])
+ }
+ if len(parts) > 2 {
+ patch, _ = strconv.Atoi(parts[2])
+ }
+
+ return major, minor, patch
+}
+
+// compareFloatWithVersion compares v1 with v2.
+// It returns -1 if v1 is less than v2, 0 if v1 is equal to v2 and 1 if v1 is greater than v2.
+func compareFloatWithVersion(v1 float64, v2 Version) int {
+ mf, minf := math.Modf(v1)
+ v1maj := int(mf)
+ v1min := int(minf * 100)
+
+ if v2.Major == v1maj && v2.Minor == v1min {
+ return 0
+ }
+
+ if v1maj > v2.Major {
+ return 1
+ }
+
+ if v1maj < v2.Major {
+ return -1
+ }
+
+ if v1min > v2.Minor {
+ return 1
+ }
+
+ return -1
+}
+
+func GoMinorVersion() int {
+ return goMinorVersion(runtime.Version())
+}
+
+func goMinorVersion(version string) int {
+ if strings.HasPrefix(version, "devel") {
+ return 9999 // magic
+ }
+ var major, minor int
+ var trailing string
+ n, err := fmt.Sscanf(version, "go%d.%d%s", &major, &minor, &trailing)
+ if n == 2 && err == io.EOF {
+ // Means there were no trailing characters (i.e., not an alpha/beta)
+ err = nil
+ }
+ if err != nil {
+ return 0
+ }
+ return minor
+}
diff --git a/common/version/version_test.go b/common/version/version_test.go
new file mode 100644
index 00000000000..ed8d2a7cf30
--- /dev/null
+++ b/common/version/version_test.go
@@ -0,0 +1,103 @@
+// Copyright 2025 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package version
+
+import (
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestHugoVersion(t *testing.T) {
+ c := qt.New(t)
+
+ c.Assert(version(0, 15, 0, "-DEV"), qt.Equals, "0.15-DEV")
+ c.Assert(version(0, 15, 2, "-DEV"), qt.Equals, "0.15.2-DEV")
+
+ v := Version{Minor: 21, Suffix: "-DEV"}
+
+ c.Assert(v.ReleaseVersion().String(), qt.Equals, "0.21")
+ c.Assert(v.String(), qt.Equals, "0.21-DEV")
+ c.Assert(v.Next().String(), qt.Equals, "0.22")
+ nextVersionString := v.Next().Version()
+ c.Assert(nextVersionString.String(), qt.Equals, "0.22")
+ c.Assert(nextVersionString.Eq("0.22"), qt.Equals, true)
+ c.Assert(nextVersionString.Eq("0.21"), qt.Equals, false)
+ c.Assert(nextVersionString.Eq(nextVersionString), qt.Equals, true)
+ c.Assert(v.NextPatchLevel(3).String(), qt.Equals, "0.20.3")
+
+ // We started to use full semver versions even for main
+ // releases in v0.54.0
+ v = Version{Minor: 53, PatchLevel: 0}
+ c.Assert(v.String(), qt.Equals, "0.53")
+ c.Assert(v.Next().String(), qt.Equals, "0.54.0")
+ c.Assert(v.Next().Next().String(), qt.Equals, "0.55.0")
+ v = Version{Minor: 54, PatchLevel: 0, Suffix: "-DEV"}
+ c.Assert(v.String(), qt.Equals, "0.54.0-DEV")
+}
+
+func TestCompareVersions(t *testing.T) {
+ c := qt.New(t)
+
+ parseIgnoreErr := func(s string) Version {
+ v, _ := ParseVersion(s)
+ return v
+ }
+
+ c.Assert(CompareVersions(MustParseVersion("0.20.0"), 0.20), qt.Equals, 0)
+ c.Assert(CompareVersions(MustParseVersion("0.20.0"), float32(0.20)), qt.Equals, 0)
+ c.Assert(CompareVersions(MustParseVersion("0.20.0"), float64(0.20)), qt.Equals, 0)
+ c.Assert(CompareVersions(MustParseVersion("0.19.1"), 0.20), qt.Equals, 1)
+ c.Assert(CompareVersions(MustParseVersion("0.19.3"), "0.20.2"), qt.Equals, 1)
+ c.Assert(CompareVersions(MustParseVersion("0.1"), 3), qt.Equals, 1)
+ c.Assert(CompareVersions(MustParseVersion("0.1"), int32(3)), qt.Equals, 1)
+ c.Assert(CompareVersions(MustParseVersion("0.1"), int64(3)), qt.Equals, 1)
+ c.Assert(CompareVersions(MustParseVersion("0.20"), "0.20"), qt.Equals, 0)
+ c.Assert(CompareVersions(MustParseVersion("0.20.1"), "0.20.1"), qt.Equals, 0)
+ c.Assert(CompareVersions(MustParseVersion("0.20.1"), "0.20"), qt.Equals, -1)
+ c.Assert(CompareVersions(MustParseVersion("0.20.0"), "0.20.1"), qt.Equals, 1)
+ c.Assert(CompareVersions(MustParseVersion("0.20.1"), "0.20.2"), qt.Equals, 1)
+ c.Assert(CompareVersions(MustParseVersion("0.21.1"), "0.22.1"), qt.Equals, 1)
+ c.Assert(CompareVersions(MustParseVersion("0.22.0"), "0.22-DEV"), qt.Equals, -1)
+ c.Assert(CompareVersions(MustParseVersion("0.22.0"), "0.22.1-DEV"), qt.Equals, 1)
+ c.Assert(CompareVersions(MustParseVersion("0.22.0-DEV"), "0.22"), qt.Equals, 1)
+ c.Assert(CompareVersions(MustParseVersion("0.22.1-DEV"), "0.22"), qt.Equals, -1)
+ c.Assert(CompareVersions(MustParseVersion("0.22.1-DEV"), "0.22.1-DEV"), qt.Equals, 0)
+
+ c.Assert(CompareVersions(parseIgnoreErr("foobar"), "v1.0.0"), qt.Equals, 1)
+ c.Assert(CompareVersions(parseIgnoreErr("v1.0.0"), "foobar"), qt.Equals, -1)
+ c.Assert(CompareVersions(parseIgnoreErr("foobar"), "foobar"), qt.Equals, 0)
+ c.Assert(CompareVersions(parseIgnoreErr("foobar"), parseIgnoreErr("foobar")), qt.Equals, 0)
+ c.Assert(CompareVersions(parseIgnoreErr("a"), "b"), qt.Equals, -1)
+ c.Assert(CompareVersions(parseIgnoreErr("a"), parseIgnoreErr("b")), qt.Equals, -1)
+ c.Assert(CompareVersions(parseIgnoreErr("b"), "a"), qt.Equals, 1)
+ c.Assert(CompareVersions(parseIgnoreErr("b"), parseIgnoreErr("a")), qt.Equals, 1)
+}
+
+func TestParseHugoVersion(t *testing.T) {
+ c := qt.New(t)
+
+ c.Assert(MustParseVersion("v2.3.2").String(), qt.Equals, "2.3.2")
+ c.Assert(MustParseVersion("0.25").String(), qt.Equals, "0.25")
+ c.Assert(MustParseVersion("0.25.2").String(), qt.Equals, "0.25.2")
+ c.Assert(MustParseVersion("0.25-test").String(), qt.Equals, "0.25-test")
+ c.Assert(MustParseVersion("0.25-DEV").String(), qt.Equals, "0.25-DEV")
+}
+
+func TestGoMinorVersion(t *testing.T) {
+ c := qt.New(t)
+ c.Assert(goMinorVersion("go1.12.5"), qt.Equals, 12)
+ c.Assert(goMinorVersion("go1.14rc1"), qt.Equals, 14)
+ c.Assert(GoMinorVersion() >= 11, qt.Equals, true)
+}
diff --git a/compare/compare.go b/compare/compare.go
index 537a6667638..5c6a4dae973 100644
--- a/compare/compare.go
+++ b/compare/compare.go
@@ -1,4 +1,4 @@
-// Copyright 2019 The Hugo Authors. All rights reserved.
+// Copyright 2025 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -17,13 +17,16 @@ package compare
// The semantics of equals is that the two value are interchangeable
// in the Hugo templates.
type Eqer interface {
- Eq(other interface{}) bool
+ // Eq returns whether this value is equal to the other.
+ // This is for internal use.
+ Eq(other any) bool
}
// ProbablyEqer is an equal check that may return false positives, but never
// a false negative.
type ProbablyEqer interface {
- ProbablyEq(other interface{}) bool
+ // For internal use.
+ ProbablyEq(other any) bool
}
// Comparer can be used to compare two values.
@@ -31,5 +34,34 @@ type ProbablyEqer interface {
// Compare returns -1 if the given version is less than, 0 if equal and 1 if greater than
// the running version.
type Comparer interface {
- Compare(other interface{}) int
+ Compare(other any) int
+}
+
+// Eq returns whether v1 is equal to v2.
+// It will use the Eqer interface if implemented, which
+// defines equals when two value are interchangeable
+// in the Hugo templates.
+func Eq(v1, v2 any) bool {
+ if v1 == nil || v2 == nil {
+ return v1 == v2
+ }
+
+ if eqer, ok := v1.(Eqer); ok {
+ return eqer.Eq(v2)
+ }
+
+ return v1 == v2
+}
+
+// ProbablyEq returns whether v1 is probably equal to v2.
+func ProbablyEq(v1, v2 any) bool {
+ if Eq(v1, v2) {
+ return true
+ }
+
+ if peqer, ok := v1.(ProbablyEqer); ok {
+ return peqer.ProbablyEq(v2)
+ }
+
+ return false
}
diff --git a/compare/compare_strings_test.go b/compare/compare_strings_test.go
index 9c4aa58686e..0ccfab24368 100644
--- a/compare/compare_strings_test.go
+++ b/compare/compare_strings_test.go
@@ -62,3 +62,22 @@ func TestLexicographicSort(t *testing.T) {
c.Assert(s, qt.DeepEquals, []string{"A", "b", "Ba", "ba", "ba", "Bz"})
}
+
+// // Note that this cannot use b.Loop() because of golang/go#27217.
+func BenchmarkStringSort(b *testing.B) {
+ prototype := []string{"b", "Bz", "zz", "ba", "αβδ αβδ αβδ", "A", "Ba", "ba", "nnnnasdfnnn", "AAgæåz", "αβδC"}
+ b.Run("LessStrings", func(b *testing.B) {
+ ss := make([][]string, b.N)
+ for i := 0; i < b.N; i++ {
+ ss[i] = make([]string, len(prototype))
+ copy(ss[i], prototype)
+ }
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ sss := ss[i]
+ sort.Slice(sss, func(i, j int) bool {
+ return LessStrings(sss[i], sss[j])
+ })
+ }
+ })
+}
diff --git a/compare/compare_test.go b/compare/compare_test.go
new file mode 100644
index 00000000000..674dc1660aa
--- /dev/null
+++ b/compare/compare_test.go
@@ -0,0 +1,14 @@
+// Copyright 2025 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compare
diff --git a/config/allconfig/allconfig.go b/config/allconfig/allconfig.go
new file mode 100644
index 00000000000..e7598236ab9
--- /dev/null
+++ b/config/allconfig/allconfig.go
@@ -0,0 +1,1280 @@
+// 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 allconfig contains the full configuration for Hugo.
+// { "name": "Configuration", "description": "This section holds all configuration options in Hugo." }
+package allconfig
+
+import (
+ "errors"
+ "fmt"
+ "reflect"
+ "regexp"
+ "slices"
+ "sort"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/gohugoio/hugo/cache/filecache"
+ "github.com/gohugoio/hugo/cache/httpcache"
+ "github.com/gohugoio/hugo/common/hmaps"
+ "github.com/gohugoio/hugo/common/hstrings"
+ "github.com/gohugoio/hugo/common/hugo"
+ "github.com/gohugoio/hugo/common/loggers"
+ "github.com/gohugoio/hugo/common/paths"
+ "github.com/gohugoio/hugo/common/urls"
+ "github.com/gohugoio/hugo/config"
+ "github.com/gohugoio/hugo/config/privacy"
+ "github.com/gohugoio/hugo/config/security"
+ "github.com/gohugoio/hugo/config/services"
+ "github.com/gohugoio/hugo/deploy/deployconfig"
+ "github.com/gohugoio/hugo/helpers"
+ "github.com/gohugoio/hugo/hugolib/roles"
+ "github.com/gohugoio/hugo/hugolib/segments"
+ "github.com/gohugoio/hugo/hugolib/sitesmatrix"
+ "github.com/gohugoio/hugo/hugolib/versions"
+ "github.com/gohugoio/hugo/langs"
+ gc "github.com/gohugoio/hugo/markup/goldmark/goldmark_config"
+ "github.com/gohugoio/hugo/markup/markup_config"
+ "github.com/gohugoio/hugo/media"
+ "github.com/gohugoio/hugo/minifiers"
+ "github.com/gohugoio/hugo/modules"
+ "github.com/gohugoio/hugo/navigation"
+ "github.com/gohugoio/hugo/output"
+ "github.com/gohugoio/hugo/related"
+ "github.com/gohugoio/hugo/resources/images"
+ "github.com/gohugoio/hugo/resources/kinds"
+ "github.com/gohugoio/hugo/resources/page"
+ "github.com/gohugoio/hugo/resources/page/pagemeta"
+ "github.com/spf13/afero"
+)
+
+// InternalConfig is the internal configuration for Hugo, not read from any user provided config file.
+type InternalConfig struct {
+ // Server mode?
+ Running bool
+
+ Quiet bool
+ Verbose bool
+ Clock string
+ Watch bool
+ FastRenderMode bool
+ LiveReloadPort int
+}
+
+// All non-params config keys for language.
+var configLanguageKeys map[string]bool
+
+func init() {
+ skip := map[string]bool{
+ "internal": true,
+ "c": true,
+ "rootconfig": true,
+ }
+ configLanguageKeys = make(map[string]bool)
+ addKeys := func(v reflect.Value) {
+ for i := range v.NumField() {
+ name := strings.ToLower(v.Type().Field(i).Name)
+ if skip[name] {
+ continue
+ }
+ configLanguageKeys[name] = true
+ }
+ }
+ addKeys(reflect.ValueOf(Config{}))
+ addKeys(reflect.ValueOf(RootConfig{}))
+ addKeys(reflect.ValueOf(config.CommonDirs{}))
+ addKeys(reflect.ValueOf(langs.LanguageConfig{}))
+}
+
+type Config struct {
+ // For internal use only.
+ Internal InternalConfig `mapstructure:"-" json:"-"`
+ // For internal use only.
+ C *ConfigCompiled `mapstructure:"-" json:"-"`
+ isLanguageClone bool
+
+ RootConfig
+
+ // Author information.
+ // Deprecated: Use taxonomies instead.
+ Author map[string]any
+
+ // Social links.
+ // Deprecated: Use .Site.Params instead.
+ Social map[string]string
+
+ // The build configuration section contains build-related configuration options.
+ // {"identifiers": ["build"] }
+ Build config.BuildConfig `mapstructure:"-"`
+
+ // The caches configuration section contains cache-related configuration options.
+ // {"identifiers": ["caches"] }
+ Caches filecache.Configs `mapstructure:"-"`
+
+ // The httpcache configuration section contains HTTP-cache-related configuration options.
+ // {"identifiers": ["httpcache"] }
+ HTTPCache httpcache.Config `mapstructure:"-"`
+
+ // The markup configuration section contains markup-related configuration options.
+ // {"identifiers": ["markup"] }
+ Markup markup_config.Config `mapstructure:"-"`
+
+ // ContentTypes are the media types that's considered content in Hugo.
+ ContentTypes *config.ConfigNamespace[map[string]media.ContentTypeConfig, media.ContentTypes] `mapstructure:"-"`
+
+ // The mediatypes configuration section maps the MIME type (a string) to a configuration object for that type.
+ // {"identifiers": ["mediatypes"], "refs": ["types:media:type"] }
+ MediaTypes *config.ConfigNamespace[map[string]media.MediaTypeConfig, media.Types] `mapstructure:"-"`
+
+ Imaging *config.ConfigNamespace[images.ImagingConfig, images.ImagingConfigInternal] `mapstructure:"-"`
+
+ // The outputformats configuration sections maps a format name (a string) to a configuration object for that format.
+ OutputFormats *config.ConfigNamespace[map[string]output.OutputFormatConfig, output.Formats] `mapstructure:"-"`
+
+ // The languages configuration sections maps a language code (a string) to a configuration object for that language.
+ Languages *config.ConfigNamespace[map[string]langs.LanguageConfig, langs.LanguagesInternal] `mapstructure:"-"`
+
+ // The versions configuration section contains the top level versions configuration options.
+ Versions *config.ConfigNamespace[map[string]versions.VersionConfig, versions.VersionsInternal] `mapstructure:"-"`
+
+ // The roles configuration section contains the top level roles configuration options.
+ Roles *config.ConfigNamespace[map[string]roles.RoleConfig, roles.RolesInternal] `mapstructure:"-"`
+
+ // The outputs configuration section maps a Page Kind (a string) to a slice of output formats.
+ // This can be overridden in the front matter.
+ Outputs map[string][]string `mapstructure:"-"`
+
+ // The cascade configuration section contains the top level front matter cascade configuration options,
+ // a slice of page matcher and params to apply to those pages.
+ Cascade *page.PageMatcherParamsConfigs `mapstructure:"-"`
+
+ // The segments defines segments for the site. Used for partial/segmented builds.
+ Segments *config.ConfigNamespace[map[string]segments.SegmentConfig, *segments.Segments] `mapstructure:"-"`
+
+ // Menu configuration.
+ // {"refs": ["config:languages:menus"] }
+ Menus *config.ConfigNamespace[map[string]navigation.MenuConfig, navigation.Menus] `mapstructure:"-"`
+
+ // The deployment configuration section contains for hugo deployconfig.
+ Deployment deployconfig.DeployConfig `mapstructure:"-"`
+
+ // Module configuration.
+ Module modules.Config `mapstructure:"-"`
+
+ // Front matter configuration.
+ Frontmatter pagemeta.FrontmatterConfig `mapstructure:"-"`
+
+ // Minification configuration.
+ Minify minifiers.MinifyConfig `mapstructure:"-"`
+
+ // Permalink configuration.
+ Permalinks map[string]map[string]string `mapstructure:"-"`
+
+ // Taxonomy configuration.
+ Taxonomies map[string]string `mapstructure:"-"`
+
+ // Sitemap configuration.
+ Sitemap config.SitemapConfig `mapstructure:"-"`
+
+ // Related content configuration.
+ Related related.Config `mapstructure:"-"`
+
+ // Server configuration.
+ Server config.Server `mapstructure:"-"`
+
+ // Pagination configuration.
+ Pagination config.Pagination `mapstructure:"-"`
+
+ // Page configuration.
+ Page config.PageConfig `mapstructure:"-"`
+
+ // Privacy configuration.
+ Privacy privacy.Config `mapstructure:"-"`
+
+ // Security configuration.
+ Security security.Config `mapstructure:"-"`
+
+ // Services configuration.
+ Services services.Config `mapstructure:"-"`
+
+ // User provided parameters.
+ // {"refs": ["config:languages:params"] }
+ Params hmaps.Params `mapstructure:"-"`
+
+ // UglyURLs configuration. Either a boolean or a sections map.
+ UglyURLs any `mapstructure:"-"`
+}
+
+// Early initialization of config.
+type configCompiler interface {
+ CompileConfig(logger loggers.Logger) error
+}
+
+// Late initialization of config.
+type configInitializer interface {
+ InitConfig(logger loggers.Logger, defaultSitesMatrix sitesmatrix.VectorStore, configuredDimensions *sitesmatrix.ConfiguredDimensions) error
+}
+
+func (c Config) cloneForLang() *Config {
+ x := c
+ x.isLanguageClone = true
+ x.C = nil
+ copyStringSlice := func(in []string) []string {
+ if in == nil {
+ return nil
+ }
+ out := make([]string, len(in))
+ copy(out, in)
+ return out
+ }
+
+ // Copy all the slices to avoid sharing.
+ x.DisableKinds = copyStringSlice(x.DisableKinds)
+ x.DisableLanguages = copyStringSlice(x.DisableLanguages)
+ x.MainSections = copyStringSlice(x.MainSections)
+ x.IgnoreLogs = copyStringSlice(x.IgnoreLogs)
+ x.IgnoreFiles = copyStringSlice(x.IgnoreFiles)
+ x.Theme = copyStringSlice(x.Theme)
+
+ // Collapse all static dirs to one.
+ x.StaticDir = x.staticDirs()
+ // These will go away soon ...
+ x.StaticDir0 = nil
+ x.StaticDir1 = nil
+ x.StaticDir2 = nil
+ x.StaticDir3 = nil
+ x.StaticDir4 = nil
+ x.StaticDir5 = nil
+ x.StaticDir6 = nil
+ x.StaticDir7 = nil
+ x.StaticDir8 = nil
+ x.StaticDir9 = nil
+ x.StaticDir10 = nil
+
+ return &x
+}
+
+func (c *Config) CompileConfig(logger loggers.Logger) error {
+ var transientErr error
+ s := c.Timeout
+ if _, err := strconv.Atoi(s); err == nil {
+ // A number, assume seconds.
+ s = s + "s"
+ }
+ timeout, err := time.ParseDuration(s)
+ if err != nil {
+ return fmt.Errorf("failed to parse timeout: %s", err)
+ }
+ disabledKinds := make(map[string]bool)
+ for _, kind := range c.DisableKinds {
+ kind = strings.ToLower(kind)
+ if newKind := kinds.IsDeprecatedAndReplacedWith(kind); newKind != "" {
+ logger.Deprecatef(false, "Kind %q used in disableKinds is deprecated, use %q instead.", kind, newKind)
+ // Legacy config.
+ kind = newKind
+ }
+ if kinds.GetKindAny(kind) == "" {
+ logger.Warnf("Unknown kind %q in disableKinds configuration.", kind)
+ continue
+ }
+ disabledKinds[kind] = true
+ }
+ kindOutputFormats := make(map[string]output.Formats)
+ isRssDisabled := disabledKinds["rss"]
+ outputFormats := c.OutputFormats.Config
+ for kind, formats := range c.Outputs {
+ if newKind := kinds.IsDeprecatedAndReplacedWith(kind); newKind != "" {
+ logger.Deprecatef(false, "Kind %q used in outputs configuration is deprecated, use %q instead.", kind, newKind)
+ kind = newKind
+ }
+ if disabledKinds[kind] {
+ continue
+ }
+ if kinds.GetKindAny(kind) == "" {
+ logger.Warnf("Unknown kind %q in outputs configuration.", kind)
+ continue
+ }
+ for _, format := range formats {
+ if isRssDisabled && format == "rss" {
+ // Legacy config.
+ continue
+ }
+ f, found := outputFormats.GetByName(format)
+ if !found {
+ transientErr = fmt.Errorf("unknown output format %q for kind %q", format, kind)
+ continue
+ }
+ kindOutputFormats[kind] = append(kindOutputFormats[kind], f)
+ }
+ }
+
+ defaultOutputFormat := outputFormats[0]
+ c.DefaultOutputFormat = strings.ToLower(c.DefaultOutputFormat)
+ if c.DefaultOutputFormat != "" {
+ f, found := outputFormats.GetByName(c.DefaultOutputFormat)
+ if !found {
+ return fmt.Errorf("unknown default output format %q", c.DefaultOutputFormat)
+ }
+ defaultOutputFormat = f
+ } else {
+ c.DefaultOutputFormat = defaultOutputFormat.Name
+ }
+
+ disabledLangs := make(map[string]bool)
+ for _, lang := range c.DisableLanguages {
+ disabledLangs[lang] = true
+ }
+
+ 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
+ }
+
+ baseURL, err := urls.NewBaseURLFromString(c.BaseURL)
+ if err != nil {
+ return err
+ }
+
+ isUglyURL := func(section string) bool {
+ switch v := c.UglyURLs.(type) {
+ case bool:
+ return v
+ case map[string]bool:
+ return v[section]
+ default:
+ return false
+ }
+ }
+
+ ignoreFile := func(s string) bool {
+ return false
+ }
+ if len(c.IgnoreFiles) > 0 {
+ regexps := make([]*regexp.Regexp, len(c.IgnoreFiles))
+ for i, pattern := range c.IgnoreFiles {
+ var err error
+ regexps[i], err = regexp.Compile(pattern)
+ if err != nil {
+ return fmt.Errorf("failed to compile ignoreFiles pattern %q: %s", pattern, err)
+ }
+ }
+ ignoreFile = func(s string) bool {
+ for _, r := range regexps {
+ if r.MatchString(s) {
+ return true
+ }
+ }
+ return false
+ }
+ }
+
+ var clock time.Time
+ if c.Internal.Clock != "" {
+ var err error
+ clock, err = time.Parse(time.RFC3339, c.Internal.Clock)
+ if err != nil {
+ return fmt.Errorf("failed to parse clock: %s", err)
+ }
+ }
+
+ httpCache, err := c.HTTPCache.Compile()
+ if err != nil {
+ return err
+ }
+
+ // Legacy language values.
+ if c.LanguageCode != "" {
+ if !c.isLanguageClone {
+ hugo.DeprecateWithLogger("project config key languageCode", "Use locale instead.", "v0.158.0", logger.Logger())
+ }
+ if c.Locale == "" {
+ c.Locale = c.LanguageCode
+ }
+ c.LanguageCode = ""
+ }
+ for k, v := range c.Languages.Config.LanguageConfigs {
+ //lint:ignore SA1019 Keep as adapter for now.
+ if v.LanguageCode != "" {
+ if !c.isLanguageClone {
+ hugo.DeprecateWithLogger(fmt.Sprintf("project config key languages.%s.languageCode", k), fmt.Sprintf("Use languages.%s.locale instead.", k), "v0.158.0", logger.Logger())
+ }
+ if v.Locale == "" {
+ //lint:ignore SA1019 Keep as adapter for now.
+ v.Locale = v.LanguageCode
+ }
+ //lint:ignore SA1019 Keep as adapter for now.
+ v.LanguageCode = ""
+ }
+ //lint:ignore SA1019 Keep as adapter for now.
+ if v.LanguageName != "" {
+ if !c.isLanguageClone {
+ hugo.DeprecateWithLogger(fmt.Sprintf("project config key languages.%s.languageName", k), fmt.Sprintf("Use languages.%s.label instead.", k), "v0.158.0", logger.Logger())
+ }
+ if v.Label == "" {
+ //lint:ignore SA1019 Keep as adapter for now.
+ v.Label = v.LanguageName
+ }
+ //lint:ignore SA1019 Keep as adapter for now.
+ v.LanguageName = ""
+ }
+ //lint:ignore SA1019 Keep as adapter for now.
+ if v.LanguageDirection != "" {
+ if !c.isLanguageClone {
+ hugo.DeprecateWithLogger(fmt.Sprintf("project config key languages.%s.languageDirection", k), fmt.Sprintf("Use languages.%s.direction instead.", k), "v0.158.0", logger.Logger())
+ }
+ if v.Direction == "" {
+ //lint:ignore SA1019 Keep as adapter for now.
+ v.Direction = v.LanguageDirection
+ }
+ //lint:ignore SA1019 Keep as adapter for now.
+ v.LanguageDirection = ""
+ }
+
+ c.Languages.Config.LanguageConfigs[k] = v
+ // Sorted is a snapshot of LanguageConfigs taken at decode time; keep it
+ // in sync so Configs.Init, which reads from Sorted, sees the migrated values.
+ for i, s := range c.Languages.Config.Sorted {
+ if s.Name == k {
+ c.Languages.Config.Sorted[i].LanguageConfig = v
+ break
+ }
+ }
+ }
+
+ // Legacy privacy values.
+ if c.Privacy.Twitter.Disable {
+ hugo.DeprecateWithLogger("project config key privacy.twitter.disable", "Use privacy.x.disable instead.", "v0.141.0", logger.Logger())
+ c.Privacy.X.Disable = c.Privacy.Twitter.Disable
+ }
+ if c.Privacy.Twitter.EnableDNT {
+ hugo.DeprecateWithLogger("project config key privacy.twitter.enableDNT", "Use privacy.x.enableDNT instead.", "v0.141.0", logger.Logger())
+ c.Privacy.X.EnableDNT = c.Privacy.Twitter.EnableDNT
+ }
+ if c.Privacy.Twitter.Simple {
+ hugo.DeprecateWithLogger("project config key privacy.twitter.simple", "Use privacy.x.simple instead.", "v0.141.0", logger.Logger())
+ c.Privacy.X.Simple = c.Privacy.Twitter.Simple
+ }
+
+ // Legacy services values.
+ if c.Services.Twitter.DisableInlineCSS {
+ hugo.DeprecateWithLogger("project config key services.twitter.disableInlineCSS", "Use services.x.disableInlineCSS instead.", "v0.141.0", logger.Logger())
+ c.Services.X.DisableInlineCSS = c.Services.Twitter.DisableInlineCSS
+ }
+
+ // Legacy permalink tokens
+ vs := fmt.Sprintf("%v", c.Permalinks)
+ if strings.Contains(vs, ":filename") {
+ hugo.DeprecateWithLogger("the \":filename\" permalink token", "Use \":contentbasename\" instead.", "0.144.0", logger.Logger())
+ }
+ if strings.Contains(vs, ":slugorfilename") {
+ hugo.DeprecateWithLogger("the \":slugorfilename\" permalink token", "Use \":slugorcontentbasename\" instead.", "0.144.0", logger.Logger())
+ }
+
+ // Legacy render hook values.
+ alternativeDetails := fmt.Sprintf(
+ "Set to %q if previous value was false, or set to %q if previous value was true.",
+ gc.RenderHookUseEmbeddedNever,
+ gc.RenderHookUseEmbeddedFallback,
+ )
+ if c.Markup.Goldmark.RenderHooks.Image.EnableDefault != nil {
+ alternative := "Use markup.goldmark.renderHooks.image.useEmbedded instead." + " " + alternativeDetails
+ hugo.DeprecateWithLogger("project config key markup.goldmark.renderHooks.image.enableDefault", alternative, "0.148.0", logger.Logger())
+ if *c.Markup.Goldmark.RenderHooks.Image.EnableDefault {
+ c.Markup.Goldmark.RenderHooks.Image.UseEmbedded = gc.RenderHookUseEmbeddedFallback
+ } else {
+ c.Markup.Goldmark.RenderHooks.Image.UseEmbedded = gc.RenderHookUseEmbeddedNever
+ }
+ }
+ if c.Markup.Goldmark.RenderHooks.Link.EnableDefault != nil {
+ alternative := "Use markup.goldmark.renderHooks.link.useEmbedded instead." + " " + alternativeDetails
+ hugo.DeprecateWithLogger("project config key markup.goldmark.renderHooks.link.enableDefault", alternative, "0.148.0", logger.Logger())
+ if *c.Markup.Goldmark.RenderHooks.Link.EnableDefault {
+ c.Markup.Goldmark.RenderHooks.Link.UseEmbedded = gc.RenderHookUseEmbeddedFallback
+ } else {
+ c.Markup.Goldmark.RenderHooks.Link.UseEmbedded = gc.RenderHookUseEmbeddedNever
+ }
+ }
+
+ // Validate render hook configuration.
+ renderHookUseEmbeddedModes := []string{
+ gc.RenderHookUseEmbeddedAlways,
+ gc.RenderHookUseEmbeddedAuto,
+ gc.RenderHookUseEmbeddedFallback,
+ gc.RenderHookUseEmbeddedNever,
+ }
+ if !slices.Contains(renderHookUseEmbeddedModes, c.Markup.Goldmark.RenderHooks.Image.UseEmbedded) {
+ return fmt.Errorf("project config markup.goldmark.renderHooks.image.useEmbedded must be one of %s", helpers.StringSliceToList(renderHookUseEmbeddedModes, "or"))
+ }
+ if !slices.Contains(renderHookUseEmbeddedModes, c.Markup.Goldmark.RenderHooks.Link.UseEmbedded) {
+ return fmt.Errorf("project config markup.goldmark.renderHooks.link.useEmbedded must be one of %s", helpers.StringSliceToList(renderHookUseEmbeddedModes, "or"))
+ }
+
+ c.C = &ConfigCompiled{
+ Timeout: timeout,
+ BaseURL: baseURL,
+ BaseURLLiveReload: baseURL,
+ DisabledKinds: disabledKinds,
+ DisabledLanguages: disabledLangs,
+ IgnoredLogs: ignoredLogIDs,
+ KindOutputFormats: kindOutputFormats,
+ DefaultOutputFormat: defaultOutputFormat,
+ CreateTitle: helpers.GetTitleFunc(c.TitleCaseStyle),
+ IsUglyURLSection: isUglyURL,
+ IgnoreFile: ignoreFile,
+ MainSections: c.MainSections,
+ Clock: clock,
+ HTTPCache: httpCache,
+ transientErr: transientErr,
+ }
+
+ for _, s := range allDecoderSetups {
+ if getCompiler := s.getCompiler; getCompiler != nil {
+ if err := getCompiler(c).CompileConfig(logger); err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
+}
+
+func (c *Config) IsKindEnabled(kind string) bool {
+ return !c.C.DisabledKinds[kind]
+}
+
+func (c *Config) IsLangDisabled(lang string) bool {
+ return c.C.DisabledLanguages[lang]
+}
+
+// ConfigCompiled holds values and functions that are derived from the config.
+type ConfigCompiled struct {
+ Timeout time.Duration
+ BaseURL urls.BaseURL
+ BaseURLLiveReload urls.BaseURL
+ ServerInterface string
+ KindOutputFormats map[string]output.Formats
+ DefaultOutputFormat output.Format
+ DisabledKinds map[string]bool
+ DisabledLanguages map[string]bool
+ IgnoredLogs map[string]bool
+ CreateTitle func(s string) string
+ IsUglyURLSection func(section string) bool
+ IgnoreFile func(filename string) bool
+ MainSections []string
+ Clock time.Time
+ HTTPCache httpcache.ConfigCompiled
+
+ // This is set to the last transient error found during config compilation.
+ // With themes/modules we compute the configuration in multiple passes, and
+ // errors with missing output format definitions may resolve itself.
+ transientErr error
+
+ mu sync.Mutex
+}
+
+// This may be set after the config is compiled.
+func (c *ConfigCompiled) SetMainSections(sections []string) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ c.MainSections = sections
+}
+
+// IsMainSectionsSet returns whether the main sections have been set.
+func (c *ConfigCompiled) IsMainSectionsSet() bool {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ return c.MainSections != nil
+}
+
+// This is set after the config is compiled by the server command.
+func (c *ConfigCompiled) SetServerInfo(baseURL, baseURLLiveReload urls.BaseURL, serverInterface string) {
+ c.BaseURL = baseURL
+ c.BaseURLLiveReload = baseURLLiveReload
+ c.ServerInterface = serverInterface
+}
+
+// RootConfig holds all the top-level configuration options in Hugo
+type RootConfig struct {
+ // The base URL of the site.
+ // Note that the default value is empty, but Hugo requires a valid URL (e.g. "https://example.com/") to work properly.
+ // {"identifiers": ["URL"] }
+ BaseURL string
+
+ // Whether to build content marked as draft.X
+ // {"identifiers": ["draft"] }
+ BuildDrafts bool
+
+ // Whether to build content with expiryDate in the past.
+ // {"identifiers": ["expiryDate"] }
+ BuildExpired bool
+
+ // Whether to build content with publishDate in the future.
+ // {"identifiers": ["publishDate"] }
+ BuildFuture bool
+
+ // Copyright information.
+ Copyright string
+
+ // The language to apply to content without any language indicator.
+ DefaultContentLanguage string
+
+ // By default, we put the default content language in the root and the others below their language ID, e.g. /no/.
+ // Set this to true to put all languages below their language ID.
+ DefaultContentLanguageInSubdir bool
+
+ // The default content role to use for the site.
+ DefaultContentRole string
+
+ // Set this to true to put the default role in a subdirectory.
+ DefaultContentRoleInSubdir bool
+
+ // The default content version to use for the site.
+ DefaultContentVersion string
+
+ // Set to true to render the default version in a subdirectory.
+ DefaultContentVersionInSubdir bool
+
+ // The default output format to use for the site.
+ // If not set, we will use the first output format.
+ DefaultOutputFormat string
+
+ // Disable generation of redirect to the default language when DefaultContentLanguageInSubdir is enabled.
+ // Note that this currently is an alias for DisableDefaultSiteRedirect introduced in v0.154.5.
+ // It's not obvious how a more fine grained setup would work.
+ DisableDefaultLanguageRedirect bool
+
+ // Disable generation of redirects to the default site when DefaultContentRoleInSubdir or DefaultContentVersionInSubdir or DefaultContentLanguageInSubdir is enabled.
+ // The default site is the site is the combination of defaultContentLanguage, defaultContentVersion and defaultContentRole.
+ DisableDefaultSiteRedirect bool
+
+ // Disable creation of alias redirect pages.
+ DisableAliases bool
+
+ // Disable lower casing of path segments.
+ DisablePathToLower bool
+
+ // Disable page kinds from build.
+ DisableKinds []string
+
+ // A list of languages to disable.
+ DisableLanguages []string
+
+ // The named segments to render.
+ // This needs to match the name of the segment in the segments configuration.
+ RenderSegments []string
+
+ // Disable the injection of the Hugo generator tag on the home page.
+ DisableHugoGeneratorInject bool
+
+ // Disable live reloading in server mode.
+ DisableLiveReload bool
+
+ // Enable replacement in Pages' Content of Emoji shortcodes with their equivalent Unicode characters.
+ // {"identifiers": ["Content", "Unicode"] }
+ EnableEmoji bool
+
+ // THe main section(s) of the site.
+ // If not set, Hugo will try to guess this from the content.
+ MainSections []string
+
+ // Enable robots.txt generation.
+ EnableRobotsTXT bool
+
+ // When enabled, Hugo will apply Git version information to each Page if possible, which
+ // can be used to keep lastUpdated in synch and to print version information.
+ // {"identifiers": ["Page"] }
+ EnableGitInfo bool
+
+ // Enable to track, calculate and print metrics.
+ TemplateMetrics bool
+
+ // Enable to track, print and calculate metric hints.
+ TemplateMetricsHints bool
+
+ // Enable to disable the build lock file.
+ NoBuildLock bool
+
+ // A list of log IDs to ignore.
+ IgnoreLogs []string
+
+ // A list of regexps that match paths to ignore.
+ // Deprecated: Use the settings on module imports.
+ IgnoreFiles []string
+
+ // Ignore cache.
+ IgnoreCache bool
+
+ // Enable to print greppable placeholders (on the form "[i18n] TRANSLATIONID") for missing translation strings.
+ EnableMissingTranslationPlaceholders bool
+
+ // Enable to panic on warning log entries. This may make it easier to detect the source.
+ PanicOnWarning bool
+
+ // The configured environment. Default is "development" for server and "production" for build.
+ Environment string
+
+ // Deprecated: Use Locale instead.
+ LanguageCode string `json:"-"`
+
+ // The default locale.
+ Locale string
+
+ // Enable if the site content has CJK language (Chinese, Japanese, or Korean). This affects how Hugo counts words.
+ HasCJKLanguage bool
+
+ // Whether to pluralize default list titles.
+ // Note that this currently only works for English, but you can provide your own title in the content file's front matter.
+ PluralizeListTitles bool
+
+ // Whether to capitalize automatic page titles, applicable to section, taxonomy, and term pages.
+ CapitalizeListTitles bool
+
+ // Make all relative URLs absolute using the baseURL.
+ // {"identifiers": ["baseURL"] }
+ CanonifyURLs bool
+
+ // Enable this to make all relative URLs relative to content root. Note that this does not affect absolute URLs.
+ RelativeURLs bool
+
+ // Removes non-spacing marks from composite characters in content paths.
+ RemovePathAccents bool
+
+ // Whether to track and print unused templates during the build.
+ PrintUnusedTemplates bool
+
+ // Enable to print warnings for missing translation strings.
+ PrintI18nWarnings bool
+
+ // ENable to print warnings for multiple files published to the same destination.
+ PrintPathWarnings bool
+
+ // URL to be used as a placeholder when a page reference cannot be found in ref or relref. Is used as-is.
+ RefLinksNotFoundURL string
+
+ // When using ref or relref to resolve page links and a link cannot be resolved, it will be logged with this log level.
+ // Valid values are ERROR (default) or WARNING. Any ERROR will fail the build (exit -1).
+ RefLinksErrorLevel string
+
+ // This will create a menu with all the sections as menu items and all the sections’ pages as “shadow-members”.
+ SectionPagesMenu string
+
+ // The length of text in words to show in a .Summary.
+ SummaryLength int
+
+ // The site title.
+ Title string
+
+ // The theme(s) to use.
+ // See Modules for more a more flexible way to load themes.
+ Theme []string
+
+ // Timeout for generating page contents, specified as a duration or in seconds.
+ Timeout string
+
+ // The time zone (or location), e.g. Europe/Oslo, used to parse front matter dates without such information and in the time function.
+ TimeZone string
+
+ // Set titleCaseStyle to specify the title style used by the title template function and the automatic section titles in Hugo.
+ // It defaults to AP Stylebook for title casing, but you can also set it to Chicago or Go (every word starts with a capital letter).
+ TitleCaseStyle string
+
+ // The editor used for opening up new content.
+ NewContentEditor string
+
+ // Don't sync modification time of files for the static mounts.
+ NoTimes bool
+
+ // Don't sync modification time of files for the static mounts.
+ NoChmod bool
+
+ // Clean the destination folder before a new build.
+ // This currently only handles static files.
+ CleanDestinationDir bool
+
+ // A Glob pattern of module paths to ignore in the _vendor folder.
+ IgnoreVendorPaths string
+
+ config.CommonDirs `mapstructure:",squash"`
+
+ // The odd constructs below are kept for backwards compatibility.
+ // Deprecated: Use module mount config instead.
+ StaticDir []string
+ // Deprecated: Use module mount config instead.
+ StaticDir0 []string
+ // Deprecated: Use module mount config instead.
+ StaticDir1 []string
+ // Deprecated: Use module mount config instead.
+ StaticDir2 []string
+ // Deprecated: Use module mount config instead.
+ StaticDir3 []string
+ // Deprecated: Use module mount config instead.
+ StaticDir4 []string
+ // Deprecated: Use module mount config instead.
+ StaticDir5 []string
+ // Deprecated: Use module mount config instead.
+ StaticDir6 []string
+ // Deprecated: Use module mount config instead.
+ StaticDir7 []string
+ // Deprecated: Use module mount config instead.
+ StaticDir8 []string
+ // Deprecated: Use module mount config instead.
+ StaticDir9 []string
+ // Deprecated: Use module mount config instead.
+ StaticDir10 []string
+}
+
+func (c RootConfig) staticDirs() []string {
+ var dirs []string
+ dirs = append(dirs, c.StaticDir...)
+ dirs = append(dirs, c.StaticDir0...)
+ dirs = append(dirs, c.StaticDir1...)
+ dirs = append(dirs, c.StaticDir2...)
+ dirs = append(dirs, c.StaticDir3...)
+ dirs = append(dirs, c.StaticDir4...)
+ dirs = append(dirs, c.StaticDir5...)
+ dirs = append(dirs, c.StaticDir6...)
+ dirs = append(dirs, c.StaticDir7...)
+ dirs = append(dirs, c.StaticDir8...)
+ dirs = append(dirs, c.StaticDir9...)
+ dirs = append(dirs, c.StaticDir10...)
+ return hstrings.UniqueStringsReuse(dirs)
+}
+
+type Configs struct {
+ Base *Config
+ LoadingInfo config.LoadConfigResult
+ LanguageConfigMap map[string]*Config
+
+ IsMultihost bool
+
+ Modules modules.Modules
+ ModulesClient *modules.Client
+ FileCaches filecache.Caches
+
+ // All below is set in Init.
+ Languages langs.Languages
+ ContentPathParser *paths.PathParser
+ ConfiguredDimensions *sitesmatrix.ConfiguredDimensions
+ DefaultContentSitesMatrix *sitesmatrix.IntSets
+ AllSitesMatrix *sitesmatrix.IntSets
+
+ configLangs []config.AllProvider
+}
+
+func (c *Configs) Validate(logger loggers.Logger) error {
+ return nil
+}
+
+// transientErr returns the last transient error found during config compilation.
+func (c *Configs) transientErr() error {
+ for _, l := range c.LanguageConfigMap {
+ if l.C.transientErr != nil {
+ return l.C.transientErr
+ }
+ }
+ return nil
+}
+
+func (c *Configs) IsZero() bool {
+ // A config always has at least one language.
+ return c == nil || len(c.Languages) == 0
+}
+
+func (c *Configs) Init(sourceFs afero.Fs, logger loggers.Logger) error {
+ var languages langs.Languages
+
+ for _, f := range c.Base.Languages.Config.Sorted {
+ v, found := c.LanguageConfigMap[f.Name]
+ if !found {
+ return fmt.Errorf("invalid language configuration for %q", f.Name)
+ }
+ language, err := langs.NewLanguage(f.Name, c.Base.DefaultContentLanguage, v.TimeZone, f.LanguageConfig, logger)
+ if err != nil {
+ return err
+ }
+ languages = append(languages, language)
+ }
+
+ // Filter out disabled languages.
+ var n int
+ for _, l := range languages {
+ if !l.Disabled {
+ languages[n] = l
+ n++
+ }
+ }
+ languages = languages[:n]
+
+ c.Languages = languages
+ c.ConfiguredDimensions = &sitesmatrix.ConfiguredDimensions{
+ ConfiguredLanguages: c.Base.Languages.Config,
+ ConfiguredVersions: c.Base.Versions.Config,
+ ConfiguredRoles: c.Base.Roles.Config,
+ }
+
+ if err := c.ConfiguredDimensions.Init(); err != nil {
+ return err
+ }
+
+ intSetsCfg := sitesmatrix.IntSetsConfig{
+ ApplyDefaults: sitesmatrix.IntSetsConfigApplyDefaultsIfNotSet,
+ }
+ matrix := sitesmatrix.NewIntSetsBuilder(c.ConfiguredDimensions).WithConfig(intSetsCfg)
+ c.DefaultContentSitesMatrix = matrix.Build()
+ c.AllSitesMatrix = sitesmatrix.NewIntSetsBuilder(c.ConfiguredDimensions).WithAllIfNotSet().Build()
+
+ c.ContentPathParser = &paths.PathParser{
+ ConfiguredDimensions: c.ConfiguredDimensions,
+ LanguageIndex: languages.AsIndexSet(),
+ IsLangDisabled: c.Base.IsLangDisabled,
+ IsContentExt: c.Base.ContentTypes.Config.IsContentSuffix,
+ IsOutputFormat: func(name, ext string) bool {
+ if name == "" {
+ return false
+ }
+
+ if of, ok := c.Base.OutputFormats.Config.GetByName(name); ok {
+ if ext != "" && !of.MediaType.HasSuffix(ext) {
+ return false
+ }
+ return true
+ }
+ return false
+ },
+ }
+
+ c.configLangs = make([]config.AllProvider, len(c.Languages))
+
+ // Config can be shared between languages,
+ // avoid initializing the same config more than once.
+ for i, l := range c.Languages {
+ langConfig := c.LanguageConfigMap[l.Lang]
+ for _, s := range allDecoderSetups {
+ if getInitializer := s.getInitializer; getInitializer != nil {
+ if err := getInitializer(langConfig).InitConfig(logger, nil, c.ConfiguredDimensions); err != nil {
+ return err
+ }
+ }
+ }
+
+ c.configLangs[i] = ConfigLanguage{
+ m: c,
+ config: langConfig,
+ baseConfig: c.LoadingInfo.BaseConfig,
+ language: l,
+ languageIndex: i,
+ }
+ }
+
+ if len(c.Modules) == 0 {
+ return errors.New("no modules loaded (need at least the main module)")
+ }
+
+ // Apply default project mounts.
+ if err := modules.ApplyProjectConfigDefaults(logger.Logger(), c.Modules[0], c.configLangs...); err != nil {
+ return err
+ }
+
+ // We should consolidate this, but to get a full view of the mounts in e.g. "hugo config" we need to
+ // transfer any default mounts added above to the config used to print the config.
+ for _, m := range c.Modules[0].Mounts() {
+ var found bool
+ for _, cm := range c.Base.Module.Mounts {
+ if cm.Equal(m) {
+ found = true
+ break
+ }
+ }
+ if !found {
+ c.Base.Module.Mounts = append(c.Base.Module.Mounts, m)
+ }
+ }
+
+ // Transfer the changed mounts to the language versions (all share the same mount set, but can be displayed in different languages).
+ for _, l := range c.LanguageConfigMap {
+ l.Module.Mounts = c.Base.Module.Mounts
+ }
+
+ return nil
+}
+
+func (c Configs) ConfigLangs() []config.AllProvider {
+ return c.configLangs
+}
+
+func (c Configs) GetFirstLanguageConfig() config.AllProvider {
+ return c.configLangs[0]
+}
+
+func (c Configs) GetByLang(lang string) config.AllProvider {
+ for _, l := range c.configLangs {
+ if l.Language().(*langs.Language).Lang == lang {
+ return l
+ }
+ }
+ return nil
+}
+
+func newDefaultConfig() *Config {
+ return &Config{
+ Taxonomies: map[string]string{"tag": "tags", "category": "categories"},
+ Sitemap: config.SitemapConfig{Priority: -1, Filename: "sitemap.xml"},
+ RootConfig: RootConfig{
+ Environment: hugo.EnvironmentProduction,
+ TitleCaseStyle: "AP",
+ PluralizeListTitles: true,
+ CapitalizeListTitles: true,
+ StaticDir: []string{"static"},
+ SummaryLength: 70,
+ Timeout: "60s",
+
+ CommonDirs: config.CommonDirs{
+ ArcheTypeDir: "archetypes",
+ ContentDir: "content",
+ ResourceDir: "resources",
+ PublishDir: "public",
+ ThemesDir: "themes",
+ AssetDir: "assets",
+ LayoutDir: "layouts",
+ I18nDir: "i18n",
+ DataDir: "data",
+ },
+ },
+ }
+}
+
+// fromLoadConfigResult creates a new Config from res.
+func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadConfigResult) (*Configs, error) {
+ if !res.Cfg.IsSet("languages") {
+ // We need at least one
+ lang := res.Cfg.GetString("defaultContentLanguage")
+ if lang == "" {
+ lang = "en"
+ }
+ res.Cfg.Set("languages", hmaps.Params{lang: hmaps.Params{}})
+ }
+ bcfg := res.BaseConfig
+ cfg := res.Cfg
+
+ all := newDefaultConfig()
+
+ err := decodeConfigFromParams(fs, logger, bcfg, cfg, all, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ langConfigMap := make(map[string]*Config)
+
+ languagesConfig := cfg.GetStringMap("languages")
+
+ var isMultihost bool
+
+ if err := all.CompileConfig(logger); err != nil {
+ return nil, err
+ }
+
+ for k, v := range languagesConfig {
+ mergedConfig := config.New()
+ var differentRootKeys []string
+ switch x := v.(type) {
+ case nil:
+ case hmaps.Params:
+ _, found := x["params"]
+ if !found {
+ x["params"] = hmaps.Params{
+ hmaps.MergeStrategyKey: hmaps.ParamsMergeStrategyDeep,
+ }
+ }
+ for kk, vv := range x {
+ if kk == "_merge" {
+ continue
+ }
+ if kk == "baseurl" {
+ // baseURL configure don the language level is a multihost setup.
+ isMultihost = true
+ }
+
+ if p, ok := vv.(hmaps.Params); ok {
+ // With the introduction of YAML anchor and alias support, language config entries
+ // may be contain shared references.
+ // This also break potential cycles.
+ vv = hmaps.CloneParamsDeep(p)
+ }
+
+ if kk == "cascade" {
+ // If not set, add the current language to make sure it does not get applied to other languages.
+ page.AddLangToCascadeTargetMap(k, vv.(hmaps.Params))
+ // Always clone cascade config to get the sites matrix right.
+ differentRootKeys = append(differentRootKeys, kk)
+ }
+
+ mergedConfig.Set(kk, vv)
+ rootv := cfg.Get(kk)
+ if rootv != nil && cfg.IsSet(kk) {
+ // This overrides a root key and potentially needs a merge.
+ if !reflect.DeepEqual(rootv, vv) {
+ switch vvv := vv.(type) {
+ case hmaps.Params:
+ differentRootKeys = append(differentRootKeys, kk)
+
+ // Use the language value as base.
+ // Note that this is already cloned above.
+ mergedConfigEntry := vvv
+ // Merge in the root value.
+ hmaps.MergeParams(mergedConfigEntry, rootv.(hmaps.Params))
+
+ mergedConfig.Set(kk, mergedConfigEntry)
+ default:
+ // Apply new values to the root.
+ differentRootKeys = append(differentRootKeys, "")
+ }
+ }
+ } else {
+ switch vv.(type) {
+ case hmaps.Params:
+ differentRootKeys = append(differentRootKeys, kk)
+ default:
+ // Apply new values to the root.
+ differentRootKeys = append(differentRootKeys, "")
+ }
+ }
+ }
+ differentRootKeys = hstrings.UniqueStringsSorted(differentRootKeys)
+
+ if len(differentRootKeys) == 0 {
+ langConfigMap[k] = all
+ continue
+ }
+
+ // Create a copy of the complete config and replace the root keys with the language specific ones.
+ clone := all.cloneForLang()
+
+ if err := decodeConfigFromParams(fs, logger, bcfg, mergedConfig, clone, differentRootKeys); err != nil {
+ return nil, fmt.Errorf("failed to decode config for language %q: %w", k, err)
+ }
+ if err := clone.CompileConfig(logger); err != nil {
+ return nil, err
+ }
+
+ // Adjust Goldmark config defaults for multilingual, single-host sites.
+ if len(languagesConfig) > 1 && !isMultihost && !clone.Markup.Goldmark.DuplicateResourceFiles {
+ if clone.Markup.Goldmark.RenderHooks.Image.UseEmbedded == gc.RenderHookUseEmbeddedAuto {
+ clone.Markup.Goldmark.RenderHooks.Image.UseEmbedded = gc.RenderHookUseEmbeddedFallback
+ }
+ if clone.Markup.Goldmark.RenderHooks.Link.UseEmbedded == gc.RenderHookUseEmbeddedAuto {
+ clone.Markup.Goldmark.RenderHooks.Link.UseEmbedded = gc.RenderHookUseEmbeddedFallback
+ }
+ }
+
+ langConfigMap[k] = clone
+ case hmaps.ParamsMergeStrategy:
+
+ default:
+ panic(fmt.Sprintf("unknown type in languages config: %T", v))
+
+ }
+ }
+
+ bcfg.PublishDir = all.PublishDir
+ res.BaseConfig = bcfg
+ all.CommonDirs.CacheDir = bcfg.CacheDir
+ for _, l := range langConfigMap {
+ l.CommonDirs.CacheDir = bcfg.CacheDir
+ }
+
+ caches, err := filecache.NewCaches(all.Caches, fs)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create file caches from configuration: %w", err)
+ }
+
+ cm := &Configs{
+ Base: all,
+ LanguageConfigMap: langConfigMap,
+ LoadingInfo: res,
+ FileCaches: caches,
+ IsMultihost: isMultihost,
+ }
+
+ return cm, nil
+}
+
+func decodeConfigFromParams(fs afero.Fs, logger loggers.Logger, bcfg config.BaseConfig, p config.Provider, target *Config, keys []string) error {
+ var decoderSetups []decodeWeight
+
+ if len(keys) == 0 {
+ for _, v := range allDecoderSetups {
+ decoderSetups = append(decoderSetups, v)
+ }
+ } else {
+ for _, key := range keys {
+ if v, found := allDecoderSetups[key]; found {
+ decoderSetups = append(decoderSetups, v)
+ } else {
+ logger.Warnf("Skip unknown config key %q", key)
+ }
+ }
+ }
+
+ // Sort them to get the dependency order right.
+ sort.Slice(decoderSetups, func(i, j int) bool {
+ ki, kj := decoderSetups[i], decoderSetups[j]
+ if ki.weight == kj.weight {
+ return ki.key < kj.key
+ }
+ return ki.weight < kj.weight
+ })
+
+ for _, v := range decoderSetups {
+ p := decodeConfig{p: p, c: target, fs: fs, bcfg: bcfg, logger: logger}
+ if err := v.decode(v, p); err != nil {
+ return fmt.Errorf("failed to decode %q: %w", v.key, err)
+ }
+ }
+
+ return nil
+}
+
+func createDefaultOutputFormats(allFormats output.Formats) map[string][]string {
+ if len(allFormats) == 0 {
+ panic("no output formats")
+ }
+ rssOut, rssFound := allFormats.GetByName(output.RSSFormat.Name)
+ htmlOut, _ := allFormats.GetByName(output.HTMLFormat.Name)
+
+ defaultListTypes := []string{htmlOut.Name}
+ if rssFound {
+ defaultListTypes = append(defaultListTypes, rssOut.Name)
+ }
+
+ m := map[string][]string{
+ kinds.KindPage: {htmlOut.Name},
+ kinds.KindHome: defaultListTypes,
+ kinds.KindSection: defaultListTypes,
+ kinds.KindTerm: defaultListTypes,
+ kinds.KindTaxonomy: defaultListTypes,
+ }
+
+ // May be disabled
+ if rssFound {
+ m["rss"] = []string{rssOut.Name}
+ }
+
+ return m
+}
diff --git a/config/allconfig/allconfig_integration_test.go b/config/allconfig/allconfig_integration_test.go
new file mode 100644
index 00000000000..8bdfe76ac80
--- /dev/null
+++ b/config/allconfig/allconfig_integration_test.go
@@ -0,0 +1,384 @@
+package allconfig_test
+
+import (
+ "path/filepath"
+ "strings"
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+ "github.com/gohugoio/hugo/common/hugo"
+ "github.com/gohugoio/hugo/config/allconfig"
+ "github.com/gohugoio/hugo/hugolib"
+ gc "github.com/gohugoio/hugo/markup/goldmark/goldmark_config"
+ "github.com/gohugoio/hugo/media"
+)
+
+func TestDirsMount(t *testing.T) {
+ files := `
+-- hugo.toml --
+baseURL = "https://example.com"
+disableKinds = ["taxonomy", "term"]
+[languages]
+[languages.en]
+weight = 1
+[languages.sv]
+weight = 2
+[[module.mounts]]
+source = 'content/en'
+target = 'content'
+lang = 'en'
+[[module.mounts]]
+source = 'content/sv'
+target = 'content'
+lang = 'sv'
+-- content/en/p1.md --
+---
+title: "p1"
+---
+-- content/sv/p1.md --
+---
+title: "p1"
+---
+-- layouts/single.html --
+Title: {{ .Title }}
+ `
+
+ b := hugolib.Test(t, files)
+
+ // b.AssertFileContent("public/p1/index.html", "Title: p1")
+
+ sites := b.H.Sites
+ b.Assert(len(sites), qt.Equals, 2)
+
+ configs := b.H.Configs
+ mods := configs.Modules
+ b.Assert(len(mods), qt.Equals, 1)
+ mod := mods[0]
+ b.Assert(mod.Mounts(), qt.HasLen, 8)
+
+ enConcp := sites[0].Conf
+ enConf := enConcp.GetConfig().(*allconfig.Config)
+
+ b.Assert(enConcp.BaseURL().String(), qt.Equals, "https://example.com/")
+ modConf := enConf.Module
+ b.Assert(modConf.Mounts, qt.HasLen, 8)
+ b.Assert(modConf.Mounts[0].Source, qt.Equals, filepath.FromSlash("content/en"))
+ b.Assert(modConf.Mounts[0].Target, qt.Equals, "content")
+ b.Assert(modConf.Mounts[0].Lang, qt.Equals, "")
+ b.Assert(modConf.Mounts[0].Sites.Matrix.Languages, qt.DeepEquals, []string{"en"})
+ b.Assert(modConf.Mounts[1].Source, qt.Equals, filepath.FromSlash("content/sv"))
+ b.Assert(modConf.Mounts[1].Target, qt.Equals, "content")
+ b.Assert(modConf.Mounts[1].Lang, qt.Equals, "")
+ b.Assert(modConf.Mounts[1].Sites.Matrix.Languages, qt.DeepEquals, []string{"sv"})
+}
+
+func TestConfigAliases(t *testing.T) {
+ files := `
+-- hugo.toml --
+baseURL = "https://example.com"
+logI18nWarnings = true
+logPathWarnings = true
+`
+ b := hugolib.Test(t, files)
+
+ conf := b.H.Configs.Base
+
+ b.Assert(conf.PrintI18nWarnings, qt.Equals, true)
+ b.Assert(conf.PrintPathWarnings, qt.Equals, true)
+}
+
+func TestRedefineContentTypes(t *testing.T) {
+ files := `
+-- hugo.toml --
+baseURL = "https://example.com"
+[mediaTypes]
+[mediaTypes."text/html"]
+suffixes = ["html", "xhtml"]
+`
+
+ b := hugolib.Test(t, files)
+
+ conf := b.H.Configs.Base
+ contentTypes := conf.ContentTypes.Config
+
+ 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/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/home.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"`)
+}
+
+func TestContentTypesDefault(t *testing.T) {
+ files := `
+-- hugo.toml --
+baseURL = "https://example.com"
+
+
+`
+
+ b := hugolib.Test(t, files)
+
+ ct := b.H.Configs.Base.ContentTypes
+ c := ct.Config
+ s := ct.SourceStructure.(map[string]media.ContentTypeConfig)
+
+ b.Assert(c.IsContentFile("foo.md"), qt.Equals, true)
+ b.Assert(len(s), qt.Equals, 6)
+}
+
+func TestMergeDeep(t *testing.T) {
+ t.Parallel()
+
+ files := `
+-- hugo.toml --
+baseURL = "https://example.com"
+theme = ["theme1", "theme2"]
+_merge = "deep"
+-- themes/theme1/hugo.toml --
+[sitemap]
+filename = 'mysitemap.xml'
+[services]
+[services.googleAnalytics]
+id = 'foo bar'
+[taxonomies]
+ foo = 'bars'
+-- themes/theme2/config/_default/hugo.toml --
+[taxonomies]
+ bar = 'baz'
+-- layouts/home.html --
+GA ID: {{ site.Config.Services.GoogleAnalytics.ID }}.
+
+`
+
+ b := hugolib.Test(t, files)
+
+ conf := b.H.Configs
+ base := conf.Base
+
+ b.Assert(base.Environment, qt.Equals, hugo.EnvironmentProduction)
+ b.Assert(base.BaseURL, qt.Equals, "https://example.com")
+ b.Assert(base.Sitemap.Filename, qt.Equals, "mysitemap.xml")
+ b.Assert(base.Taxonomies, qt.DeepEquals, map[string]string{"bar": "baz", "foo": "bars"})
+
+ b.AssertFileContent("public/index.html", "GA ID: foo bar.")
+}
+
+func TestMergeDeepBuildStats(t *testing.T) {
+ t.Parallel()
+
+ files := `
+-- hugo.toml --
+baseURL = "https://example.com"
+title = "Theme 1"
+_merge = "deep"
+[module]
+[module.hugoVersion]
+[[module.imports]]
+path = "theme1"
+-- themes/theme1/hugo.toml --
+[build]
+[build.buildStats]
+disableIDs = true
+enable = true
+-- layouts/home.html --
+Home.
+
+`
+
+ b := hugolib.Test(t, files, hugolib.TestOptOsFs())
+
+ conf := b.H.Configs
+ base := conf.Base
+
+ b.Assert(base.Title, qt.Equals, "Theme 1")
+ b.Assert(len(base.Module.Imports), qt.Equals, 1)
+ b.Assert(base.Build.BuildStats.Enable, qt.Equals, true)
+ b.AssertFileExists("/hugo_stats.json", true)
+}
+
+func TestMergeDeepBuildStatsTheme(t *testing.T) {
+ t.Parallel()
+
+ files := `
+-- hugo.toml --
+baseURL = "https://example.com"
+_merge = "deep"
+theme = ["theme1"]
+-- themes/theme1/hugo.toml --
+title = "Theme 1"
+[build]
+[build.buildStats]
+disableIDs = true
+enable = true
+-- layouts/home.html --
+Home.
+
+`
+
+ b := hugolib.Test(t, files, hugolib.TestOptOsFs())
+
+ conf := b.H.Configs
+ base := conf.Base
+
+ b.Assert(base.Title, qt.Equals, "Theme 1")
+ b.Assert(len(base.Module.Imports), qt.Equals, 1)
+ b.Assert(base.Build.BuildStats.Enable, qt.Equals, true)
+ b.AssertFileExists("/hugo_stats.json", true)
+}
+
+func TestDefaultConfigLanguageBlankWhenNoEnglishExists(t *testing.T) {
+ t.Parallel()
+
+ files := `
+-- hugo.toml --
+baseURL = "https://example.com"
+[languages]
+[languages.nn]
+weight = 20
+[languages.sv]
+weight = 10
+[languages.sv.taxonomies]
+ tag = "taggar"
+-- layouts/all.html --
+All.
+`
+
+ b := hugolib.Test(t, files)
+
+ b.Assert(b.H.Conf.DefaultContentLanguage(), qt.Equals, "sv")
+}
+
+func TestDefaultConfigEnvDisableLanguagesIssue13707(t *testing.T) {
+ t.Parallel()
+
+ files := `
+-- hugo.toml --
+disableLanguages = []
+[languages]
+[languages.en]
+weight = 1
+[languages.nn]
+weight = 2
+[languages.sv]
+weight = 3
+`
+
+ b := hugolib.Test(t, files, hugolib.TestOptWithConfig(func(conf *hugolib.IntegrationTestConfig) {
+ conf.Environ = []string{`HUGO_DISABLELANGUAGES=sv nn`}
+ }))
+
+ b.Assert(len(b.H.Sites), qt.Equals, 1)
+}
+
+// Issue 13535
+// We changed enablement of the embedded link and image render hooks from
+// booleans to enums in v0.148.0.
+func TestLegacyEmbeddedRenderHookEnablement(t *testing.T) {
+ files := `
+-- hugo.toml --
+[markup.goldmark.renderHooks.image]
+#KEY_VALUE
+
+[markup.goldmark.renderHooks.link]
+#KEY_VALUE
+`
+ f := strings.ReplaceAll(files, "#KEY_VALUE", "enableDefault = false")
+ b := hugolib.Test(t, f)
+ c := b.H.Configs.Base.Markup.Goldmark.RenderHooks
+ b.Assert(c.Link.UseEmbedded, qt.Equals, gc.RenderHookUseEmbeddedNever)
+ b.Assert(c.Image.UseEmbedded, qt.Equals, gc.RenderHookUseEmbeddedNever)
+
+ f = strings.ReplaceAll(files, "#KEY_VALUE", "enableDefault = true")
+ b = hugolib.Test(t, f)
+ c = b.H.Configs.Base.Markup.Goldmark.RenderHooks
+ b.Assert(c.Link.UseEmbedded, qt.Equals, gc.RenderHookUseEmbeddedFallback)
+ b.Assert(c.Image.UseEmbedded, qt.Equals, gc.RenderHookUseEmbeddedFallback)
+}
diff --git a/config/allconfig/alldecoders.go b/config/allconfig/alldecoders.go
new file mode 100644
index 00000000000..835ec19d195
--- /dev/null
+++ b/config/allconfig/alldecoders.go
@@ -0,0 +1,501 @@
+// 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 allconfig
+
+import (
+ "fmt"
+ "sort"
+ "strings"
+
+ "github.com/gohugoio/hugo/cache/filecache"
+ "github.com/gohugoio/hugo/langs"
+
+ "github.com/gohugoio/hugo/cache/httpcache"
+ "github.com/gohugoio/hugo/common/hmaps"
+ "github.com/gohugoio/hugo/common/hstrings"
+ "github.com/gohugoio/hugo/common/loggers"
+ "github.com/gohugoio/hugo/common/types"
+ "github.com/gohugoio/hugo/config"
+ "github.com/gohugoio/hugo/config/privacy"
+ "github.com/gohugoio/hugo/config/security"
+ "github.com/gohugoio/hugo/config/services"
+ "github.com/gohugoio/hugo/deploy/deployconfig"
+ "github.com/gohugoio/hugo/hugolib/roles"
+ "github.com/gohugoio/hugo/hugolib/segments"
+ "github.com/gohugoio/hugo/hugolib/versions"
+ "github.com/gohugoio/hugo/markup/markup_config"
+ "github.com/gohugoio/hugo/media"
+ "github.com/gohugoio/hugo/minifiers"
+ "github.com/gohugoio/hugo/modules"
+
+ "github.com/gohugoio/hugo/navigation"
+ "github.com/gohugoio/hugo/output"
+ "github.com/gohugoio/hugo/related"
+ "github.com/gohugoio/hugo/resources/images"
+ "github.com/gohugoio/hugo/resources/page"
+ "github.com/gohugoio/hugo/resources/page/pagemeta"
+ "github.com/mitchellh/mapstructure"
+ "github.com/spf13/afero"
+ "github.com/spf13/cast"
+)
+
+type decodeConfig struct {
+ p config.Provider
+ c *Config
+ fs afero.Fs
+ bcfg config.BaseConfig
+ logger loggers.Logger
+}
+
+type decodeWeight struct {
+ key string
+ decode func(decodeWeight, decodeConfig) error
+ getCompiler func(c *Config) configCompiler
+ getInitializer func(c *Config) configInitializer
+ weight int
+ internalOrDeprecated bool // Hide it from the docs.
+}
+
+var allDecoderSetups = map[string]decodeWeight{
+ "": {
+ key: "",
+ weight: -100, // Always first.
+ decode: func(d decodeWeight, p decodeConfig) error {
+ if err := mapstructure.WeakDecode(p.p.Get(""), &p.c.RootConfig); err != nil {
+ return err
+ }
+
+ // This need to match with the map keys, always lower case.
+ p.c.RootConfig.DefaultContentLanguage = strings.ToLower(p.c.RootConfig.DefaultContentLanguage)
+ p.c.RootConfig.DefaultContentRole = strings.ToLower(p.c.RootConfig.DefaultContentRole)
+ p.c.RootConfig.DefaultContentVersion = strings.ToLower(p.c.RootConfig.DefaultContentVersion)
+
+ return nil
+ },
+ },
+ "imaging": {
+ key: "imaging",
+ decode: func(d decodeWeight, p decodeConfig) error {
+ var err error
+ p.c.Imaging, err = images.DecodeConfig(p.p.GetStringMap(d.key))
+ return err
+ },
+ },
+ "caches": {
+ key: "caches",
+ decode: func(d decodeWeight, p decodeConfig) error {
+ var err error
+ p.c.Caches, err = filecache.DecodeConfig(p.fs, p.bcfg, p.p.GetStringMap(d.key))
+ if p.c.IgnoreCache {
+ // Set MaxAge in all caches to 0.
+ for k, cache := range p.c.Caches {
+ cache.MaxAge = 0
+ p.c.Caches[k] = cache
+ }
+ }
+ return err
+ },
+ },
+ "httpcache": {
+ key: "httpcache",
+ decode: func(d decodeWeight, p decodeConfig) error {
+ var err error
+ p.c.HTTPCache, err = httpcache.DecodeConfig(p.bcfg, p.p.GetStringMap(d.key))
+ if p.c.IgnoreCache {
+ p.c.HTTPCache.Cache.For.Excludes = []string{"**"}
+ p.c.HTTPCache.Cache.For.Includes = []string{}
+ }
+ return err
+ },
+ },
+ "build": {
+ key: "build",
+ decode: func(d decodeWeight, p decodeConfig) error {
+ p.c.Build = config.DecodeBuildConfig(p.p)
+ return nil
+ },
+ getCompiler: func(c *Config) configCompiler {
+ return &c.Build
+ },
+ },
+ "frontmatter": {
+ key: "frontmatter",
+ decode: func(d decodeWeight, p decodeConfig) error {
+ var err error
+ p.c.Frontmatter, err = pagemeta.DecodeFrontMatterConfig(p.p)
+ return err
+ },
+ },
+ "markup": {
+ key: "markup",
+ decode: func(d decodeWeight, p decodeConfig) error {
+ var err error
+ p.c.Markup, err = markup_config.Decode(p.p)
+ return err
+ },
+ },
+ "segments": {
+ key: "segments",
+ decode: func(d decodeWeight, p decodeConfig) error {
+ var err error
+ p.c.Segments, err = segments.DecodeSegments(p.p.GetStringMap(d.key), p.c.RenderSegments, p.logger)
+ return err
+ },
+ getInitializer: func(c *Config) configInitializer {
+ return c.Segments.Config
+ },
+ },
+ "server": {
+ key: "server",
+ decode: func(d decodeWeight, p decodeConfig) error {
+ var err error
+ p.c.Server, err = config.DecodeServer(p.p)
+ return err
+ },
+ getCompiler: func(c *Config) configCompiler {
+ return &c.Server
+ },
+ },
+ "minify": {
+ key: "minify",
+ decode: func(d decodeWeight, p decodeConfig) error {
+ var err error
+ p.c.Minify, err = minifiers.DecodeConfig(p.p.Get(d.key))
+ return err
+ },
+ },
+ "contenttypes": {
+ key: "contenttypes",
+ weight: 100, // This needs to be decoded after media types.
+ decode: func(d decodeWeight, p decodeConfig) error {
+ var err error
+ p.c.ContentTypes, err = media.DecodeContentTypes(p.p.GetStringMap(d.key), p.c.MediaTypes.Config)
+ return err
+ },
+ },
+ "mediatypes": {
+ key: "mediatypes",
+ decode: func(d decodeWeight, p decodeConfig) error {
+ var err error
+ p.c.MediaTypes, err = media.DecodeTypes(p.p.GetStringMap(d.key))
+ return err
+ },
+ },
+ "outputs": {
+ key: "outputs",
+ decode: func(d decodeWeight, p decodeConfig) error {
+ defaults := createDefaultOutputFormats(p.c.OutputFormats.Config)
+ m := hmaps.CleanConfigStringMap(p.p.GetStringMap("outputs"))
+ p.c.Outputs = make(map[string][]string)
+ for k, v := range m {
+ s := types.ToStringSlicePreserveString(v)
+ for i, v := range s {
+ s[i] = strings.ToLower(v)
+ }
+ p.c.Outputs[k] = s
+ }
+ // Apply defaults.
+ for k, v := range defaults {
+ if _, found := p.c.Outputs[k]; !found {
+ p.c.Outputs[k] = v
+ }
+ }
+ return nil
+ },
+ },
+ "outputformats": {
+ key: "outputformats",
+ decode: func(d decodeWeight, p decodeConfig) error {
+ var err error
+ p.c.OutputFormats, err = output.DecodeConfig(p.c.MediaTypes.Config, p.p.Get(d.key))
+ return err
+ },
+ },
+ "languages": {
+ key: "languages",
+ decode: func(d decodeWeight, p decodeConfig) error {
+ m := hmaps.CleanConfigStringMap(p.p.GetStringMap(d.key))
+ // Root-level locale/languageCode is passed to DecodeConfig so it can
+ // be applied to the default content language inside langs.DecodeConfig.
+ // They are passed separately so that an explicit per-lang languageCode
+ // can override a root-level languageCode (but not a root-level locale).
+ rootLocale := p.p.GetString("locale")
+ rootLanguageCode := p.p.GetString("languagecode")
+ var (
+ err error
+ defaultContentLanguage string
+ )
+ p.c.Languages, defaultContentLanguage, err = langs.DecodeConfig(p.c.RootConfig.DefaultContentLanguage, rootLocale, rootLanguageCode, p.c.RootConfig.DisableLanguages, m)
+ if err != nil {
+ return fmt.Errorf("failed to decode languages config: %w", err)
+ }
+ for k, v := range p.c.Languages.Config.LanguageConfigs {
+ if v.Disabled {
+ p.c.RootConfig.DisableLanguages = append(p.c.RootConfig.DisableLanguages, k)
+ }
+ }
+
+ p.c.RootConfig.DisableLanguages = hstrings.UniqueStringsReuse(p.c.RootConfig.DisableLanguages)
+ sort.Strings(p.c.RootConfig.DisableLanguages)
+ p.c.RootConfig.DefaultContentLanguage = defaultContentLanguage
+ return nil
+ },
+ },
+ "versions": {
+ key: "versions",
+ decode: func(d decodeWeight, p decodeConfig) error {
+ var err error
+ m := hmaps.CleanConfigStringMap(p.p.GetStringMap(d.key))
+ p.c.Versions, err = versions.DecodeConfig(p.c.RootConfig.DefaultContentVersion, m)
+ return err
+ },
+ },
+ "roles": {
+ key: "roles",
+ decode: func(d decodeWeight, p decodeConfig) error {
+ var (
+ err error
+ defaultContentRole string
+ )
+ m := hmaps.CleanConfigStringMap(p.p.GetStringMap(d.key))
+ p.c.Roles, defaultContentRole, err = roles.DecodeConfig(p.c.RootConfig.DefaultContentRole, m)
+ p.c.RootConfig.DefaultContentRole = defaultContentRole
+
+ return err
+ },
+ },
+
+ "params": {
+ key: "params",
+ decode: func(d decodeWeight, p decodeConfig) error {
+ p.c.Params = hmaps.CleanConfigStringMap(p.p.GetStringMap("params"))
+ if p.c.Params == nil {
+ p.c.Params = make(map[string]any)
+ }
+
+ // Before Hugo 0.112.0 this was configured via site Params.
+ if mainSections, found := p.c.Params["mainsections"]; found {
+ p.c.MainSections = types.ToStringSlicePreserveString(mainSections)
+ if p.c.MainSections == nil {
+ p.c.MainSections = []string{}
+ }
+ }
+
+ return nil
+ },
+ },
+ "module": {
+ key: "module",
+ decode: func(d decodeWeight, p decodeConfig) error {
+ var err error
+ p.c.Module, err = modules.DecodeConfig(p.logger.Logger(), p.p)
+ return err
+ },
+ },
+ "permalinks": {
+ key: "permalinks",
+ decode: func(d decodeWeight, p decodeConfig) error {
+ var err error
+ p.c.Permalinks, err = page.DecodePermalinksConfig(p.p.GetStringMap(d.key))
+ return err
+ },
+ },
+ "sitemap": {
+ key: "sitemap",
+ decode: func(d decodeWeight, p decodeConfig) error {
+ var err error
+ if p.p.IsSet(d.key) {
+ p.c.Sitemap, err = config.DecodeSitemap(p.c.Sitemap, p.p.GetStringMap(d.key))
+ }
+ return err
+ },
+ },
+ "taxonomies": {
+ key: "taxonomies",
+ decode: func(d decodeWeight, p decodeConfig) error {
+ if p.p.IsSet(d.key) {
+ m := hmaps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
+ // Remove invalid entries (e.g. non-taxonomy keys placed inside [taxonomies] in TOML).
+ for k, v := range m {
+ if k == "" || v == "" {
+ delete(m, k)
+ }
+ }
+ p.c.Taxonomies = m
+ }
+ return nil
+ },
+ },
+ "related": {
+ key: "related",
+ weight: 100, // This needs to be decoded after taxonomies.
+ decode: func(d decodeWeight, p decodeConfig) error {
+ if p.p.IsSet(d.key) {
+ var err error
+ p.c.Related, err = related.DecodeConfig(p.p.GetParams(d.key))
+ if err != nil {
+ return fmt.Errorf("failed to decode related config: %w", err)
+ }
+ } else {
+ p.c.Related = related.DefaultConfig
+ if _, found := p.c.Taxonomies["tag"]; found {
+ p.c.Related.Add(related.IndexConfig{Name: "tags", Weight: 80, Type: related.TypeBasic})
+ }
+ }
+ return nil
+ },
+ },
+
+ "cascade": {
+ key: "cascade",
+ decode: func(d decodeWeight, p decodeConfig) error {
+ var err error
+ p.c.Cascade, err = page.DecodeCascadeConfig(p.p.Get(d.key))
+ return err
+ },
+ getInitializer: func(c *Config) configInitializer { return c.Cascade },
+ },
+ "menus": {
+ key: "menus",
+ decode: func(d decodeWeight, p decodeConfig) error {
+ var err error
+ p.c.Menus, err = navigation.DecodeConfig(p.p.Get(d.key))
+ 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 {
+ var err error
+ p.c.Privacy, err = privacy.DecodeConfig(p.p)
+ return err
+ },
+ },
+ "security": {
+ key: "security",
+ decode: func(d decodeWeight, p decodeConfig) error {
+ var err error
+ p.c.Security, err = security.DecodeConfig(p.p)
+ return err
+ },
+ },
+ "services": {
+ key: "services",
+ decode: func(d decodeWeight, p decodeConfig) error {
+ var err error
+ p.c.Services, err = services.DecodeConfig(p.p)
+ return err
+ },
+ },
+ "deployment": {
+ key: "deployment",
+ decode: func(d decodeWeight, p decodeConfig) error {
+ var err error
+ p.c.Deployment, err = deployconfig.DecodeConfig(p.p)
+ return err
+ },
+ },
+ "author": {
+ key: "author",
+ decode: func(d decodeWeight, p decodeConfig) error {
+ p.c.Author = hmaps.CleanConfigStringMap(p.p.GetStringMap(d.key))
+ return nil
+ },
+ internalOrDeprecated: true,
+ },
+ "social": {
+ key: "social",
+ decode: func(d decodeWeight, p decodeConfig) error {
+ p.c.Social = hmaps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
+ return nil
+ },
+ internalOrDeprecated: true,
+ },
+ "uglyurls": {
+ key: "uglyurls",
+ decode: func(d decodeWeight, p decodeConfig) error {
+ v := p.p.Get(d.key)
+ switch vv := v.(type) {
+ case bool:
+ p.c.UglyURLs = vv
+ case string:
+ p.c.UglyURLs = vv == "true"
+ case hmaps.Params:
+ p.c.UglyURLs = cast.ToStringMapBool(hmaps.CleanConfigStringMap(vv))
+ default:
+ p.c.UglyURLs = cast.ToStringMapBool(v)
+ }
+ return nil
+ },
+ internalOrDeprecated: true,
+ },
+ "internal": {
+ key: "internal",
+ decode: func(d decodeWeight, p decodeConfig) error {
+ return mapstructure.WeakDecode(p.p.GetStringMap(d.key), &p.c.Internal)
+ },
+ internalOrDeprecated: true,
+ },
+}
+
+func init() {
+ for k, v := range allDecoderSetups {
+ // Verify that k and v.key is all lower case.
+ if k != strings.ToLower(k) {
+ panic(fmt.Sprintf("key %q is not lower case", k))
+ }
+ if v.key != strings.ToLower(v.key) {
+ panic(fmt.Sprintf("key %q is not lower case", v.key))
+ }
+
+ if k != v.key {
+ panic(fmt.Sprintf("key %q is not the same as the map key %q", k, v.key))
+ }
+ }
+}
diff --git a/config/allconfig/configlanguage.go b/config/allconfig/configlanguage.go
new file mode 100644
index 00000000000..c52d5da3d65
--- /dev/null
+++ b/config/allconfig/configlanguage.go
@@ -0,0 +1,302 @@
+// 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 allconfig
+
+import (
+ "time"
+
+ "github.com/gohugoio/hugo/common/paths"
+ "github.com/gohugoio/hugo/common/urls"
+ "github.com/gohugoio/hugo/config"
+ "github.com/gohugoio/hugo/hugolib/sitesmatrix"
+ "github.com/gohugoio/hugo/identity"
+ "github.com/gohugoio/hugo/langs"
+)
+
+type ConfigLanguage struct {
+ config *Config
+ baseConfig config.BaseConfig
+ m *Configs
+ language *langs.Language
+ languageIndex int
+}
+
+func (c ConfigLanguage) Language() any {
+ return c.language
+}
+
+func (c ConfigLanguage) LanguageIndex() int {
+ return c.languageIndex
+}
+
+func (c ConfigLanguage) Languages() any {
+ return c.m.Languages
+}
+
+func (c ConfigLanguage) PathParser() *paths.PathParser {
+ return c.m.ContentPathParser
+}
+
+func (c ConfigLanguage) LanguagePrefix() string {
+ if c.DefaultContentLanguageInSubdir() && c.DefaultContentLanguage() == c.language.Lang {
+ return c.language.Lang
+ }
+
+ if !c.IsMultilingual() || c.DefaultContentLanguage() == c.language.Lang {
+ return ""
+ }
+ return c.language.Lang
+}
+
+func (c ConfigLanguage) BaseURL() urls.BaseURL {
+ return c.config.C.BaseURL
+}
+
+func (c ConfigLanguage) BaseURLLiveReload() urls.BaseURL {
+ return c.config.C.BaseURLLiveReload
+}
+
+func (c ConfigLanguage) Environment() string {
+ return c.config.Environment
+}
+
+func (c ConfigLanguage) IsMultihost() bool {
+ if len(c.m.Languages)-len(c.config.C.DisabledLanguages) <= 1 {
+ return false
+ }
+ return c.m.IsMultihost
+}
+
+func (c ConfigLanguage) FastRenderMode() bool {
+ return c.config.Internal.FastRenderMode
+}
+
+func (c ConfigLanguage) IsMultilingual() bool {
+ return len(c.m.Languages) > 1
+}
+
+func (c ConfigLanguage) TemplateMetrics() bool {
+ return c.config.TemplateMetrics
+}
+
+func (c ConfigLanguage) TemplateMetricsHints() bool {
+ return c.config.TemplateMetricsHints
+}
+
+func (c ConfigLanguage) IsLangDisabled(lang string) bool {
+ return c.config.C.DisabledLanguages[lang]
+}
+
+func (c ConfigLanguage) IsKindEnabled(kind string) bool {
+ return !c.config.C.DisabledKinds[kind]
+}
+
+func (c ConfigLanguage) IgnoredLogs() map[string]bool {
+ return c.config.C.IgnoredLogs
+}
+
+func (c ConfigLanguage) NoBuildLock() bool {
+ return c.config.NoBuildLock
+}
+
+func (c ConfigLanguage) NewContentEditor() string {
+ return c.config.NewContentEditor
+}
+
+func (c ConfigLanguage) Timeout() time.Duration {
+ return c.config.C.Timeout
+}
+
+func (c ConfigLanguage) BaseConfig() config.BaseConfig {
+ return c.baseConfig
+}
+
+func (c ConfigLanguage) FileCaches() any {
+ return c.m.FileCaches
+}
+
+func (c ConfigLanguage) Dirs() config.CommonDirs {
+ return c.config.CommonDirs
+}
+
+func (c ConfigLanguage) DirsBase() config.CommonDirs {
+ return c.m.Base.CommonDirs
+}
+
+func (c ConfigLanguage) WorkingDir() string {
+ return c.m.Base.WorkingDir
+}
+
+func (c ConfigLanguage) CacheDirMisc() string {
+ return c.config.Caches.CacheDirMisc()
+}
+
+func (c ConfigLanguage) Quiet() bool {
+ return c.m.Base.Internal.Quiet
+}
+
+func (c ConfigLanguage) Watching() bool {
+ return c.m.Base.Internal.Watch
+}
+
+func (c ConfigLanguage) NewIdentityManager(opts ...identity.ManagerOption) identity.Manager {
+ if !c.Watching() {
+ return identity.NopManager
+ }
+ return identity.NewManager(opts...)
+}
+
+func (c ConfigLanguage) ContentTypes() config.ContentTypesProvider {
+ return c.config.ContentTypes.Config
+}
+
+// GetConfigSection is mostly used in tests. The switch statement isn't complete, but what's in use.
+func (c ConfigLanguage) GetConfigSection(s string) any {
+ switch s {
+ case "security":
+ return c.config.Security
+ case "build":
+ return c.config.Build
+ case "cascade":
+ return c.config.Cascade
+ case "frontmatter":
+ return c.config.Frontmatter
+ case "caches":
+ return c.config.Caches
+ case "markup":
+ return c.config.Markup
+ case "module":
+ return c.config.Module
+ case "mediaTypes":
+ return c.config.MediaTypes.Config
+ case "outputFormats":
+ return c.config.OutputFormats.Config
+ case "roles":
+ return c.config.Roles.Config
+ case "versions":
+ return c.config.Versions.Config
+ case "permalinks":
+ return c.config.Permalinks
+ case "minify":
+ return c.config.Minify
+ case "allModules":
+ return c.m.Modules
+ case "deployment":
+ return c.config.Deployment
+ case "httpCacheCompiled":
+ return c.config.C.HTTPCache
+ default:
+ panic("not implemented: " + s)
+ }
+}
+
+func (c ConfigLanguage) GetConfig() any {
+ return c.config
+}
+
+func (c ConfigLanguage) CanonifyURLs() bool {
+ return c.config.CanonifyURLs
+}
+
+func (c ConfigLanguage) IsUglyURLs(section string) bool {
+ return c.config.C.IsUglyURLSection(section)
+}
+
+func (c ConfigLanguage) IgnoreFile(s string) bool {
+ return c.config.C.IgnoreFile(s)
+}
+
+func (c ConfigLanguage) DisablePathToLower() bool {
+ return c.config.DisablePathToLower
+}
+
+func (c ConfigLanguage) RemovePathAccents() bool {
+ return c.config.RemovePathAccents
+}
+
+func (c ConfigLanguage) DefaultContentLanguage() string {
+ return c.config.DefaultContentLanguage
+}
+
+func (c ConfigLanguage) DefaultContentLanguageInSubdir() bool {
+ return c.config.DefaultContentLanguageInSubdir
+}
+
+func (c ConfigLanguage) DefaultContentRoleInSubdir() bool {
+ return c.config.DefaultContentRoleInSubdir
+}
+
+func (c ConfigLanguage) DefaultContentVersionInSubdir() bool {
+ return c.config.DefaultContentVersionInSubdir
+}
+
+func (c ConfigLanguage) SummaryLength() int {
+ return c.config.SummaryLength
+}
+
+func (c ConfigLanguage) BuildExpired() bool {
+ return c.config.BuildExpired
+}
+
+func (c ConfigLanguage) BuildFuture() bool {
+ return c.config.BuildFuture
+}
+
+func (c ConfigLanguage) BuildDrafts() bool {
+ return c.config.BuildDrafts
+}
+
+func (c ConfigLanguage) Running() bool {
+ return c.config.Internal.Running
+}
+
+func (c ConfigLanguage) PrintUnusedTemplates() bool {
+ return c.config.PrintUnusedTemplates
+}
+
+func (c ConfigLanguage) EnableMissingTranslationPlaceholders() bool {
+ return c.config.EnableMissingTranslationPlaceholders
+}
+
+func (c ConfigLanguage) PrintI18nWarnings() bool {
+ return c.config.PrintI18nWarnings
+}
+
+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) StaticDirs() []string {
+ return c.config.staticDirs()
+}
+
+func (c ConfigLanguage) EnableEmoji() bool {
+ return c.config.EnableEmoji
+}
+
+func (c ConfigLanguage) ConfiguredDimensions() *sitesmatrix.ConfiguredDimensions {
+ return c.m.ConfiguredDimensions
+}
+
+func (c ConfigLanguage) DefaultContentsitesMatrix() *sitesmatrix.IntSets {
+ return c.m.DefaultContentSitesMatrix
+}
+
+func (c ConfigLanguage) AllSitesMatrix() *sitesmatrix.IntSets {
+ return c.m.AllSitesMatrix
+}
diff --git a/config/allconfig/docshelper.go b/config/allconfig/docshelper.go
new file mode 100644
index 00000000000..deaf4b9e028
--- /dev/null
+++ b/config/allconfig/docshelper.go
@@ -0,0 +1,48 @@
+// 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 allconfig
+
+import (
+ "github.com/gohugoio/hugo/common/hmaps"
+ "github.com/gohugoio/hugo/config"
+ "github.com/gohugoio/hugo/docshelper"
+)
+
+// This is is just some helpers used to create some JSON used in the Hugo docs.
+func init() {
+ docsProvider := func() docshelper.DocProvider {
+ cfg := config.New()
+ for configRoot, v := range allDecoderSetups {
+ if v.internalOrDeprecated {
+ continue
+ }
+ cfg.Set(configRoot, make(hmaps.Params))
+ }
+ lang := hmaps.Params{
+ "en": hmaps.Params{
+ "menus": hmaps.Params{},
+ "params": hmaps.Params{},
+ },
+ }
+ cfg.Set("languages", lang)
+ cfg.SetDefaultMergeStrategy()
+
+ configHelpers := map[string]any{
+ "mergeStrategy": cfg.Get(""),
+ }
+ return docshelper.DocProvider{"config_helpers": configHelpers}
+ }
+
+ docshelper.AddDocProviderFunc(docsProvider)
+}
diff --git a/config/allconfig/load.go b/config/allconfig/load.go
new file mode 100644
index 00000000000..fdb349c293b
--- /dev/null
+++ b/config/allconfig/load.go
@@ -0,0 +1,575 @@
+// 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 allconfig contains the full configuration for Hugo.
+package allconfig
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "path/filepath"
+ "runtime/debug"
+ "strings"
+
+ "github.com/gobwas/glob"
+ "github.com/gohugoio/hugo/common/herrors"
+ "github.com/gohugoio/hugo/common/hexec"
+ "github.com/gohugoio/hugo/common/hmaps"
+ "github.com/gohugoio/hugo/common/hugo"
+ "github.com/gohugoio/hugo/common/loggers"
+ "github.com/gohugoio/hugo/common/paths"
+ "github.com/gohugoio/hugo/common/types"
+ "github.com/gohugoio/hugo/config"
+ "github.com/gohugoio/hugo/helpers"
+ hglob "github.com/gohugoio/hugo/hugofs/hglob"
+ "github.com/gohugoio/hugo/modules"
+ "github.com/gohugoio/hugo/modules/npm"
+ "github.com/gohugoio/hugo/parser/metadecoders"
+ "github.com/spf13/afero"
+)
+
+//lint:ignore ST1005 end user message.
+var ErrNoConfigFile = errors.New("Unable to locate config file or config directory. Perhaps you need to create a new project.\n Run `hugo help new` for details.\n")
+
+func LoadConfig(d ConfigSourceDescriptor) (configs *Configs, err error) {
+ defer func() {
+ if r := recover(); r != nil {
+ err = fmt.Errorf("failed to load config: %v", r)
+ debug.PrintStack()
+ }
+ }()
+
+ if len(d.Environ) == 0 && !hugo.IsRunningAsTest() {
+ d.Environ = os.Environ()
+ }
+
+ if d.Logger == nil {
+ d.Logger = loggers.NewDefault()
+ }
+
+ l := &configLoader{ConfigSourceDescriptor: d, cfg: config.New()}
+ // Make sure we always do this, even in error situations,
+ // as we have commands (e.g. "hugo mod init") that will
+ // use a partial configuration to do its job.
+ defer l.deleteMergeStrategies()
+ res, _, err := l.loadConfigMain(d)
+ if err != nil {
+ return nil, fmt.Errorf("failed to load config: %w", err)
+ }
+
+ configs, err = fromLoadConfigResult(d.Fs, d.Logger, res)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create config from result: %w", err)
+ }
+
+ moduleConfig, modulesClient, err := l.loadModules(configs, d.IgnoreModuleDoesNotExist)
+ if err != nil {
+ return nil, fmt.Errorf("failed to load modules: %w", err)
+ }
+
+ if len(l.ModulesConfigFiles) > 0 {
+ // Config merged in from modules.
+ // Re-read the config.
+ configs, err = fromLoadConfigResult(d.Fs, d.Logger, res)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create config from modules config: %w", err)
+ }
+ if err := configs.transientErr(); err != nil {
+ return nil, fmt.Errorf("failed to create config from modules config: %w", err)
+ }
+ configs.LoadingInfo.ConfigFiles = append(configs.LoadingInfo.ConfigFiles, l.ModulesConfigFiles...)
+ } else if err := configs.transientErr(); err != nil {
+ return nil, fmt.Errorf("failed to create config: %w", err)
+ }
+
+ configs.Modules = moduleConfig.AllModules
+ configs.ModulesClient = modulesClient
+
+ if !d.SkipNpmCheck && npm.NpmPackNeedsUpdate(d.Fs, configs.Modules) {
+ d.Logger.Warnln(`npm dependencies are out of sync, please run "hugo mod npm pack" (you may also want to run "npm install" after that)`)
+ }
+
+ if err := configs.Init(d.Fs, d.Logger); err != nil {
+ return nil, fmt.Errorf("failed to init config: %w", err)
+ }
+
+ loggers.SetGlobalLogger(d.Logger)
+
+ return
+}
+
+// ConfigSourceDescriptor describes where to find the config (e.g. config.toml etc.).
+type ConfigSourceDescriptor struct {
+ Fs afero.Fs
+ Logger loggers.Logger
+
+ // Config received from the command line.
+ // These will override any config file settings.
+ Flags config.Provider
+
+ // Path to the config file to use, e.g. /my/project/config.toml
+ Filename string
+
+ // The (optional) directory for additional configuration files.
+ ConfigDir string
+
+ // production, development
+ Environment string
+
+ // 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
+
+ // If set, skip the npm pack staleness check (used by hugo mod npm pack).
+ SkipNpmCheck bool
+}
+
+func (d ConfigSourceDescriptor) configFilenames() []string {
+ if d.Filename == "" {
+ return nil
+ }
+ return strings.Split(d.Filename, ",")
+}
+
+type configLoader struct {
+ cfg config.Provider
+ BaseConfig config.BaseConfig
+ ConfigSourceDescriptor
+
+ // collected
+ ModulesConfig modules.ModulesConfig
+ ModulesConfigFiles []string
+}
+
+// Handle some legacy values.
+func (l configLoader) applyConfigAliases() error {
+ aliases := []types.KeyValueStr{
+ {Key: "indexes", Value: "taxonomies"},
+ {Key: "logI18nWarnings", Value: "printI18nWarnings"},
+ {Key: "logPathWarnings", Value: "printPathWarnings"},
+ {Key: "ignoreErrors", Value: "ignoreLogs"},
+ }
+
+ for _, alias := range aliases {
+ if l.cfg.IsSet(alias.Key) {
+ vv := l.cfg.Get(alias.Key)
+ l.cfg.Set(alias.Value, vv)
+ }
+ }
+
+ return nil
+}
+
+func (l configLoader) applyDefaultConfig() error {
+ defaultSettings := hmaps.Params{
+ // These dirs are used early/before we build the config struct.
+ "themesDir": "themes",
+ "configDir": "config",
+ }
+
+ l.cfg.SetDefaults(defaultSettings)
+
+ return nil
+}
+
+func (l configLoader) normalizeCfg(cfg config.Provider) error {
+ if b, ok := cfg.Get("minifyOutput").(bool); ok {
+ hugo.Deprecate("project config minifyOutput", "Use minify.minifyOutput instead.", "v0.150.0")
+ if b {
+ cfg.Set("minify.minifyOutput", true)
+ }
+ } else if b, ok := cfg.Get("minify").(bool); ok {
+ hugo.Deprecate("project config minify", "Use minify.minifyOutput instead.", "v0.150.0")
+ if b {
+ cfg.Set("minify", hmaps.Params{"minifyOutput": true})
+ }
+ }
+
+ return nil
+}
+
+func (l configLoader) cleanExternalConfig(cfg config.Provider) error {
+ if cfg.IsSet("internal") {
+ cfg.Set("internal", nil)
+ }
+ return nil
+}
+
+func (l configLoader) applyFlagsOverrides(cfg config.Provider) error {
+ for _, k := range cfg.Keys() {
+ l.cfg.Set(k, cfg.Get(k))
+ }
+ return nil
+}
+
+func (l configLoader) applyOsEnvOverrides(environ []string) error {
+ if len(environ) == 0 {
+ return nil
+ }
+
+ const delim = "__env__delim"
+
+ // Extract all that start with the HUGO prefix.
+ // The delimiter is the following rune, usually "_".
+ const hugoEnvPrefix = "HUGO"
+ var hugoEnv []types.KeyValueStr
+ for _, v := range environ {
+ key, val := config.SplitEnvVar(v)
+ if after, ok := strings.CutPrefix(key, hugoEnvPrefix); ok {
+ delimiterAndKey := after
+ if len(delimiterAndKey) < 2 {
+ continue
+ }
+ // Allow delimiters to be case sensitive.
+ // It turns out there isn't that many allowed special
+ // chars in environment variables when used in Bash and similar,
+ // so variables on the form HUGOxPARAMSxFOO=bar is one option.
+ key := strings.ReplaceAll(delimiterAndKey[1:], delimiterAndKey[:1], delim)
+ key = strings.ToLower(key)
+ hugoEnv = append(hugoEnv, types.KeyValueStr{
+ Key: key,
+ Value: val,
+ })
+
+ }
+ }
+
+ for _, env := range hugoEnv {
+ existing, nestedKey, owner, err := hmaps.GetNestedParamFn(env.Key, delim, l.cfg.Get)
+ if err != nil {
+ return err
+ }
+
+ if existing != nil {
+ val, err := metadecoders.Default.UnmarshalStringTo(env.Value, existing)
+ if err == nil {
+ val = l.envValToVal(env.Key, val)
+ if owner != nil {
+ owner[nestedKey] = val
+ } else {
+ l.cfg.Set(env.Key, val)
+ }
+ continue
+ }
+ }
+
+ if owner != nil && nestedKey != "" {
+ owner[nestedKey] = env.Value
+ } else {
+ var val any
+ key := strings.ReplaceAll(env.Key, delim, ".")
+ _, ok := allDecoderSetups[key]
+ if ok {
+ // A map.
+ if v, err := metadecoders.Default.UnmarshalStringTo(env.Value, map[string]any{}); err == nil {
+ val = v
+ }
+ }
+
+ if val == nil {
+ // A string.
+ val = l.envStringToVal(key, env.Value)
+ }
+ l.cfg.Set(key, val)
+ }
+
+ }
+
+ return nil
+}
+
+func (l *configLoader) envValToVal(k string, v any) any {
+ switch v := v.(type) {
+ case string:
+ return l.envStringToVal(k, v)
+ default:
+ return v
+ }
+}
+
+func (l *configLoader) envStringToVal(k, v string) any {
+ switch k {
+ case "disablekinds", "disablelanguages", "ignorefiles", "ignorelogs":
+ v = strings.TrimSpace(v)
+ if strings.HasPrefix(v, "[") && strings.HasSuffix(v, "]") {
+ if parsed, err := metadecoders.Default.UnmarshalStringTo(v, []any{}); err == nil {
+ return parsed
+ }
+ }
+
+ if strings.Contains(v, ",") {
+ return strings.Split(v, ",")
+ } else {
+ return strings.Fields(v)
+ }
+ default:
+ return v
+ }
+}
+
+func (l *configLoader) loadConfigMain(d ConfigSourceDescriptor) (config.LoadConfigResult, modules.ModulesConfig, error) {
+ var res config.LoadConfigResult
+
+ if d.Flags != nil {
+ if err := l.normalizeCfg(d.Flags); err != nil {
+ return res, l.ModulesConfig, err
+ }
+ }
+
+ if d.Fs == nil {
+ return res, l.ModulesConfig, errors.New("no filesystem provided")
+ }
+
+ if d.Flags != nil {
+ if err := l.applyFlagsOverrides(d.Flags); err != nil {
+ return res, l.ModulesConfig, err
+ }
+ workingDir := filepath.Clean(l.cfg.GetString("workingDir"))
+
+ l.BaseConfig = config.BaseConfig{
+ WorkingDir: workingDir,
+ ThemesDir: paths.AbsPathify(workingDir, l.cfg.GetString("themesDir")),
+ }
+
+ }
+
+ names := d.configFilenames()
+
+ if names != nil {
+ for _, name := range names {
+ var filename string
+ filename, err := l.loadConfig(name)
+ if err == nil {
+ res.ConfigFiles = append(res.ConfigFiles, filename)
+ } else if err != ErrNoConfigFile {
+ return res, l.ModulesConfig, l.wrapFileError(err, filename)
+ }
+ }
+ } else {
+ for _, name := range config.DefaultConfigNames {
+ var filename string
+ filename, err := l.loadConfig(name)
+ if err == nil {
+ res.ConfigFiles = append(res.ConfigFiles, filename)
+ break
+ } else if err != ErrNoConfigFile {
+ return res, l.ModulesConfig, l.wrapFileError(err, filename)
+ }
+ }
+ }
+
+ if d.ConfigDir != "" {
+ absConfigDir := paths.AbsPathify(l.BaseConfig.WorkingDir, d.ConfigDir)
+ dcfg, dirnames, err := config.LoadConfigFromDir(l.Fs, absConfigDir, l.Environment)
+ if err == nil {
+ if len(dirnames) > 0 {
+ if err := l.normalizeCfg(dcfg); err != nil {
+ return res, l.ModulesConfig, err
+ }
+ if err := l.cleanExternalConfig(dcfg); err != nil {
+ return res, l.ModulesConfig, err
+ }
+ l.cfg.Set("", dcfg.Get(""))
+ res.ConfigFiles = append(res.ConfigFiles, dirnames...)
+ }
+ } else if err != ErrNoConfigFile {
+ if len(dirnames) > 0 {
+ return res, l.ModulesConfig, l.wrapFileError(err, dirnames[0])
+ }
+ return res, l.ModulesConfig, err
+ }
+ }
+
+ res.Cfg = l.cfg
+
+ if err := l.applyDefaultConfig(); err != nil {
+ return res, l.ModulesConfig, err
+ }
+
+ // Some settings are used before we're done collecting all settings,
+ // so apply OS environment both before and after.
+ if err := l.applyOsEnvOverrides(d.Environ); err != nil {
+ return res, l.ModulesConfig, err
+ }
+
+ workingDir := filepath.Clean(l.cfg.GetString("workingDir"))
+
+ l.BaseConfig = config.BaseConfig{
+ WorkingDir: workingDir,
+ CacheDir: l.cfg.GetString("cacheDir"),
+ ThemesDir: paths.AbsPathify(workingDir, l.cfg.GetString("themesDir")),
+ }
+
+ var err error
+ l.BaseConfig.CacheDir, err = helpers.GetCacheDir(l.Fs, l.BaseConfig.CacheDir)
+ if err != nil {
+ return res, l.ModulesConfig, err
+ }
+
+ res.BaseConfig = l.BaseConfig
+
+ l.cfg.SetDefaultMergeStrategy()
+
+ res.ConfigFiles = append(res.ConfigFiles, l.ModulesConfigFiles...)
+
+ if d.Flags != nil {
+ if err := l.applyFlagsOverrides(d.Flags); err != nil {
+ return res, l.ModulesConfig, err
+ }
+ }
+
+ if err := l.applyOsEnvOverrides(d.Environ); err != nil {
+ return res, l.ModulesConfig, err
+ }
+
+ if err = l.applyConfigAliases(); err != nil {
+ return res, l.ModulesConfig, err
+ }
+
+ return res, l.ModulesConfig, err
+}
+
+func (l *configLoader) loadModules(configs *Configs, ignoreModuleDoesNotExist bool) (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
+
+ var ignoreVendor glob.Glob
+ if s := conf.IgnoreVendorPaths; s != "" {
+ ignoreVendor, _ = hglob.GetGlob(hglob.NormalizePath(s))
+ }
+
+ ex := hexec.New(conf.Security, workingDir, l.Logger)
+
+ hook := func(m *modules.ModulesConfig) error {
+ for _, tc := range m.AllModules {
+ if len(tc.ConfigFilenames()) > 0 {
+ if tc.Watch() {
+ l.ModulesConfigFiles = append(l.ModulesConfigFiles, tc.ConfigFilenames()...)
+ }
+
+ // Merge in the theme config using the configured
+ // merge strategy.
+ cfg.Merge("", tc.Cfg().Get(""))
+
+ }
+ }
+
+ return nil
+ }
+
+ 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(),
+ ModuleQueriesCache: configs.FileCaches.ModuleQueriesCache(),
+ ModuleConfig: conf.Module,
+ IgnoreVendor: ignoreVendor,
+ IgnoreModuleDoesNotExist: ignoreModuleDoesNotExist,
+ })
+
+ moduleConfig, err := modulesClient.Collect()
+
+ // We want to watch these for changes and trigger rebuild on version
+ // changes etc.
+ if moduleConfig.GoModulesFilename != "" {
+ l.ModulesConfigFiles = append(l.ModulesConfigFiles, moduleConfig.GoModulesFilename)
+ }
+
+ if moduleConfig.GoWorkspaceFilename != "" {
+ l.ModulesConfigFiles = append(l.ModulesConfigFiles, moduleConfig.GoWorkspaceFilename)
+ }
+
+ return moduleConfig, modulesClient, err
+}
+
+func (l configLoader) loadConfig(configName string) (string, error) {
+ baseDir := l.BaseConfig.WorkingDir
+ var baseFilename string
+ if filepath.IsAbs(configName) {
+ baseFilename = configName
+ } else {
+ baseFilename = filepath.Join(baseDir, configName)
+ }
+
+ var filename string
+ if paths.ExtNoDelimiter(configName) != "" {
+ exists, _ := helpers.Exists(baseFilename, l.Fs)
+ if exists {
+ filename = baseFilename
+ }
+ } else {
+ for _, ext := range config.ValidConfigFileExtensions {
+ filenameToCheck := baseFilename + "." + ext
+ exists, _ := helpers.Exists(filenameToCheck, l.Fs)
+ if exists {
+ filename = filenameToCheck
+ break
+ }
+ }
+ }
+
+ if filename == "" {
+ return "", ErrNoConfigFile
+ }
+
+ m, err := config.FromFileToMap(l.Fs, filename)
+ if err != nil {
+ return filename, err
+ }
+
+ // Set overwrites keys of the same name, recursively.
+ l.cfg.Set("", m)
+
+ if err := l.normalizeCfg(l.cfg); err != nil {
+ return filename, err
+ }
+
+ if err := l.cleanExternalConfig(l.cfg); err != nil {
+ return filename, err
+ }
+
+ return filename, nil
+}
+
+func (l configLoader) deleteMergeStrategies() (err error) {
+ l.cfg.WalkParams(func(params ...hmaps.KeyParams) bool {
+ params[len(params)-1].Params.DeleteMergeStrategy()
+ return false
+ })
+ return
+}
+
+func (l configLoader) wrapFileError(err error, filename string) error {
+ fe := herrors.UnwrapFileError(err)
+ if fe != nil {
+ pos := fe.Position()
+ pos.Filename = filename
+ fe.UpdatePosition(pos)
+ return err
+ }
+ return herrors.NewFileErrorFromFile(err, filename, l.Fs, nil)
+}
diff --git a/config/allconfig/load_test.go b/config/allconfig/load_test.go
new file mode 100644
index 00000000000..10a88d7b7bf
--- /dev/null
+++ b/config/allconfig/load_test.go
@@ -0,0 +1,67 @@
+package allconfig
+
+import (
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/spf13/afero"
+)
+
+func BenchmarkLoad(b *testing.B) {
+ tempDir := b.TempDir()
+ configFilename := filepath.Join(tempDir, "hugo.toml")
+ config := `
+baseURL = "https://example.com"
+defaultContentLanguage = 'en'
+
+[module]
+[[module.mounts]]
+source = 'content/en'
+target = 'content/en'
+lang = 'en'
+[[module.mounts]]
+source = 'content/nn'
+target = 'content/nn'
+lang = 'nn'
+[[module.mounts]]
+source = 'content/no'
+target = 'content/no'
+lang = 'no'
+[[module.mounts]]
+source = 'content/sv'
+target = 'content/sv'
+lang = 'sv'
+[[module.mounts]]
+source = 'layouts'
+target = 'layouts'
+
+[languages]
+[languages.en]
+title = "English"
+weight = 1
+[languages.nn]
+title = "Nynorsk"
+weight = 2
+[languages.no]
+title = "Norsk"
+weight = 3
+[languages.sv]
+title = "Svenska"
+weight = 4
+`
+ if err := os.WriteFile(configFilename, []byte(config), 0o666); err != nil {
+ b.Fatal(err)
+ }
+ d := ConfigSourceDescriptor{
+ Fs: afero.NewOsFs(),
+ Filename: configFilename,
+ }
+
+ for b.Loop() {
+ _, err := LoadConfig(d)
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+}
diff --git a/config/commonConfig.go b/config/commonConfig.go
index d0d24f8ff3a..2c10ba9cfc6 100644
--- a/config/commonConfig.go
+++ b/config/commonConfig.go
@@ -14,61 +14,186 @@
package config
import (
+ "fmt"
+ "net/http"
+ "regexp"
+ "slices"
"sort"
"strings"
- "sync"
-
- "github.com/pkg/errors"
+ "github.com/bep/logg"
+ "github.com/gobwas/glob"
+ "github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/types"
- "github.com/gobwas/glob"
"github.com/gohugoio/hugo/common/herrors"
"github.com/mitchellh/mapstructure"
"github.com/spf13/cast"
- jww "github.com/spf13/jwalterweatherman"
)
-var DefaultBuild = Build{
+type BaseConfig struct {
+ WorkingDir string
+ CacheDir string
+ ThemesDir string
+ PublishDir string
+}
+
+type CommonDirs struct {
+ // The directory where Hugo will look for themes.
+ ThemesDir string
+
+ // Where to put the generated files.
+ PublishDir string
+
+ // The directory to put the generated resources files. This directory should in most situations be considered temporary
+ // and not be committed to version control. But there may be cached content in here that you want to keep,
+ // e.g. resources/_gen/images for performance reasons or CSS built from SASS when your CI server doesn't have the full setup.
+ ResourceDir string
+
+ // The project root directory.
+ WorkingDir string
+
+ // The root directory for all cache files.
+ CacheDir string
+
+ // The content source directory.
+ // Deprecated: Use module mounts.
+ ContentDir string
+ // Deprecated: Use module mounts.
+ // The data source directory.
+ DataDir string
+ // Deprecated: Use module mounts.
+ // The layout source directory.
+ LayoutDir string
+ // Deprecated: Use module mounts.
+ // The i18n source directory.
+ I18nDir string
+ // Deprecated: Use module mounts.
+ // The archetypes source directory.
+ ArcheTypeDir string
+ // Deprecated: Use module mounts.
+ // The assets source directory.
+ AssetDir string
+}
+
+type LoadConfigResult struct {
+ Cfg Provider
+ ConfigFiles []string
+ BaseConfig BaseConfig
+}
+
+var defaultBuild = BuildConfig{
UseResourceCacheWhen: "fallback",
- WriteStats: false,
+ BuildStats: BuildStats{},
+
+ CacheBusters: []CacheBuster{
+ {
+ Source: `(postcss|tailwind)\.config\.js`,
+ Target: cssTargetCachebusterRe,
+ },
+ },
}
-// Build holds some build related configuration.
-type Build struct {
- UseResourceCacheWhen string // never, fallback, always. Default is fallback
+// BuildConfig holds some build related configuration.
+type BuildConfig struct {
+ // When to use the resource file cache.
+ // One of never, fallback, always. Default is fallback
+ UseResourceCacheWhen string
// When enabled, will collect and write a hugo_stats.json with some build
// related aggregated data (e.g. CSS class names).
- WriteStats bool
+ // Note that this was a bool <= v0.115.0.
+ BuildStats BuildStats
- // Can be used to toggle off writing of the intellinsense /assets/jsconfig.js
+ // Can be used to toggle off writing of the IntelliSense /assets/jsconfig.js
// file.
NoJSConfigInAssets bool
+
+ // Can used to control how the resource cache gets evicted on rebuilds.
+ CacheBusters []CacheBuster
}
-func (b Build) UseResourceCache(err error) bool {
+// BuildStats configures if and what to write to the hugo_stats.json file.
+type BuildStats struct {
+ Enable bool
+ DisableTags bool
+ DisableClasses bool
+ DisableIDs bool
+}
+
+func (w BuildStats) Enabled() bool {
+ if !w.Enable {
+ return false
+ }
+ return !w.DisableTags || !w.DisableClasses || !w.DisableIDs
+}
+
+func (b BuildConfig) clone() BuildConfig {
+ b.CacheBusters = slices.Clone(b.CacheBusters)
+ return b
+}
+
+func (b BuildConfig) UseResourceCache(err error) bool {
if b.UseResourceCacheWhen == "never" {
return false
}
if b.UseResourceCacheWhen == "fallback" {
- return err == herrors.ErrFeatureNotAvailable
+ return herrors.IsFeatureNotAvailableError(err)
}
return true
}
-func DecodeBuild(cfg Provider) Build {
+// MatchCacheBuster returns the cache buster for the given path p, nil if none.
+func (s BuildConfig) MatchCacheBuster(logger loggers.Logger, p string) (func(string) bool, error) {
+ var matchers []func(string) bool
+ for _, cb := range s.CacheBusters {
+ if matcher := cb.compiledSource(p); matcher != nil {
+ matchers = append(matchers, matcher)
+ }
+ }
+ if len(matchers) > 0 {
+ return (func(cacheKey string) bool {
+ for _, m := range matchers {
+ if m(cacheKey) {
+ return true
+ }
+ }
+ return false
+ }), nil
+ }
+ return nil, nil
+}
+
+func (b *BuildConfig) CompileConfig(logger loggers.Logger) error {
+ for i, cb := range b.CacheBusters {
+ if err := cb.CompileConfig(logger); err != nil {
+ return fmt.Errorf("failed to compile cache buster %q: %w", cb.Source, err)
+ }
+ b.CacheBusters[i] = cb
+ }
+ return nil
+}
+
+func DecodeBuildConfig(cfg Provider) BuildConfig {
m := cfg.GetStringMap("build")
- b := DefaultBuild
+
+ b := defaultBuild.clone()
if m == nil {
return b
}
+ // writeStats was a bool <= v0.115.0.
+ if writeStats, ok := m["writestats"]; ok {
+ if bb, ok := writeStats.(bool); ok {
+ m["buildstats"] = BuildStats{Enable: bb}
+ }
+ }
+
err := mapstructure.WeakDecode(m, &b)
if err != nil {
- return DefaultBuild
+ return b
}
b.UseResourceCacheWhen = strings.ToLower(b.UseResourceCacheWhen)
@@ -80,28 +205,21 @@ func DecodeBuild(cfg Provider) Build {
return b
}
-// Sitemap configures the sitemap to be generated.
-type Sitemap struct {
+// SitemapConfig configures the sitemap to be generated.
+type SitemapConfig struct {
+ // The page change frequency.
ChangeFreq string
- Priority float64
- Filename string
+ // The priority of the page.
+ Priority float64
+ // The sitemap filename.
+ Filename string
+ // Whether to disable page inclusion.
+ Disable bool
}
-func DecodeSitemap(prototype Sitemap, input map[string]interface{}) Sitemap {
- for key, value := range input {
- switch key {
- case "changefreq":
- prototype.ChangeFreq = cast.ToString(value)
- case "priority":
- prototype.Priority = cast.ToFloat64(value)
- case "filename":
- prototype.Filename = cast.ToString(value)
- default:
- jww.WARN.Printf("Unknown Sitemap field: %s\n", key)
- }
- }
-
- return prototype
+func DecodeSitemap(prototype SitemapConfig, input map[string]any) (SitemapConfig, error) {
+ err := mapstructure.WeakDecode(input, &prototype)
+ return prototype, err
}
// Config for the dev server.
@@ -109,25 +227,71 @@ type Server struct {
Headers []Headers
Redirects []Redirect
- compiledInit sync.Once
compiledHeaders []glob.Glob
- compiledRedirects []glob.Glob
+ compiledRedirects []redirect
+}
+
+type redirect struct {
+ from glob.Glob
+ fromRe *regexp.Regexp
+ headers map[string]glob.Glob
+}
+
+func (r redirect) matchHeader(header http.Header) bool {
+ for k, v := range r.headers {
+ if !v.Match(header.Get(k)) {
+ return false
+ }
+ }
+ return true
}
-func (s *Server) init() {
- s.compiledInit.Do(func() {
- for _, h := range s.Headers {
- s.compiledHeaders = append(s.compiledHeaders, glob.MustCompile(h.For))
+func (s *Server) CompileConfig(logger loggers.Logger) error {
+ if s.compiledHeaders != nil {
+ return nil
+ }
+ for _, h := range s.Headers {
+ g, err := glob.Compile(h.For)
+ if err != nil {
+ return fmt.Errorf("failed to compile Headers glob %q: %w", h.For, err)
}
- for _, r := range s.Redirects {
- s.compiledRedirects = append(s.compiledRedirects, glob.MustCompile(r.From))
+ s.compiledHeaders = append(s.compiledHeaders, g)
+ }
+ for _, r := range s.Redirects {
+ if r.From == "" && r.FromRe == "" {
+ return fmt.Errorf("redirects must have either From or FromRe set")
}
- })
+ rd := redirect{
+ headers: make(map[string]glob.Glob),
+ }
+ if r.From != "" {
+ g, err := glob.Compile(r.From)
+ if err != nil {
+ return fmt.Errorf("failed to compile Redirect glob %q: %w", r.From, err)
+ }
+ rd.from = g
+ }
+ if r.FromRe != "" {
+ re, err := regexp.Compile(r.FromRe)
+ if err != nil {
+ return fmt.Errorf("failed to compile Redirect regexp %q: %w", r.FromRe, err)
+ }
+ rd.fromRe = re
+ }
+ for k, v := range r.FromHeaders {
+ g, err := glob.Compile(v)
+ if err != nil {
+ return fmt.Errorf("failed to compile Redirect header glob %q: %w", v, err)
+ }
+ rd.headers[k] = g
+ }
+ s.compiledRedirects = append(s.compiledRedirects, rd)
+ }
+
+ return nil
}
func (s *Server) MatchHeaders(pattern string) []types.KeyValueStr {
- s.init()
-
if s.compiledHeaders == nil {
return nil
}
@@ -150,24 +314,42 @@ func (s *Server) MatchHeaders(pattern string) []types.KeyValueStr {
return matches
}
-func (s *Server) MatchRedirect(pattern string) Redirect {
- s.init()
-
+func (s *Server) MatchRedirect(pattern string, header http.Header) Redirect {
if s.compiledRedirects == nil {
return Redirect{}
}
pattern = strings.TrimSuffix(pattern, "index.html")
- for i, g := range s.compiledRedirects {
+ for i, r := range s.compiledRedirects {
redir := s.Redirects[i]
- // No redirect to self.
- if redir.To == pattern {
- return Redirect{}
+ var found bool
+
+ if r.from != nil {
+ if r.from.Match(pattern) {
+ found = header == nil || r.matchHeader(header)
+ // We need to do regexp group replacements if needed.
+ }
}
- if g.Match(pattern) {
+ if r.fromRe != nil {
+ m := r.fromRe.FindStringSubmatch(pattern)
+ if m != nil {
+ if !found {
+ found = header == nil || r.matchHeader(header)
+ }
+
+ if found {
+ // Replace $1, $2 etc. in To.
+ for i, g := range m[1:] {
+ redir.To = strings.ReplaceAll(redir.To, fmt.Sprintf("$%d", i+1), g)
+ }
+ }
+ }
+ }
+
+ if found {
return redir
}
}
@@ -177,40 +359,153 @@ func (s *Server) MatchRedirect(pattern string) Redirect {
type Headers struct {
For string
- Values map[string]interface{}
+ Values map[string]any
}
type Redirect struct {
- From string
- To string
+ // From is the Glob pattern to match.
+ // One of From or FromRe must be set.
+ From string
+
+ // FromRe is the regexp to match.
+ // This regexp can contain group matches (e.g. $1) that can be used in the To field.
+ // One of From or FromRe must be set.
+ FromRe string
+
+ // To is the target URL.
+ To string
+
+ // Headers to match for the redirect.
+ // This maps the HTTP header name to a Glob pattern with values to match.
+ // If the map is empty, the redirect will always be triggered.
+ FromHeaders map[string]string
+
+ // HTTP status code to use for the redirect.
+ // A status code of 200 will trigger a URL rewrite.
Status int
- Force bool
+
+ // Forcode redirect, even if original request path exists.
+ Force bool
+}
+
+// CacheBuster configures cache busting for assets.
+type CacheBuster struct {
+ // Trigger for files matching this regexp.
+ Source string
+
+ // Cache bust targets matching this regexp.
+ // This regexp can contain group matches (e.g. $1) from the source regexp.
+ Target string
+
+ compiledSource func(string) func(string) bool
+}
+
+func (c *CacheBuster) CompileConfig(logger loggers.Logger) error {
+ if c.compiledSource != nil {
+ return nil
+ }
+
+ source := c.Source
+ sourceRe, err := regexp.Compile(source)
+ if err != nil {
+ return fmt.Errorf("failed to compile cache buster source %q: %w", c.Source, err)
+ }
+ target := c.Target
+ var compileErr error
+ debugl := logger.Logger().WithLevel(logg.LevelDebug).WithField(loggers.FieldNameCmd, "cachebuster")
+
+ c.compiledSource = func(s string) func(string) bool {
+ m := sourceRe.FindStringSubmatch(s)
+ matchString := "no match"
+ match := m != nil
+ if match {
+ matchString = "match!"
+ }
+ debugl.Logf("Matching %q with source %q: %s", s, source, matchString)
+ if !match {
+ return nil
+ }
+ groups := m[1:]
+ currentTarget := target
+ // Replace $1, $2 etc. in target.
+ for i, g := range groups {
+ currentTarget = strings.ReplaceAll(target, fmt.Sprintf("$%d", i+1), g)
+ }
+ targetRe, err := regexp.Compile(currentTarget)
+ if err != nil {
+ compileErr = fmt.Errorf("failed to compile cache buster target %q: %w", currentTarget, err)
+ return nil
+ }
+ return func(ss string) bool {
+ match := targetRe.MatchString(ss)
+ matchString := "no match"
+ if match {
+ matchString = "match!"
+ }
+ logger.Debugf("Matching %q with target %q: %s", ss, currentTarget, matchString)
+
+ return match
+ }
+ }
+ return compileErr
}
func (r Redirect) IsZero() bool {
- return r.From == ""
+ return r.From == "" && r.FromRe == ""
}
-func DecodeServer(cfg Provider) (*Server, error) {
- m := cfg.GetStringMap("server")
+const (
+ // Keep this a little coarse grained, some false positives are OK.
+ cssTargetCachebusterRe = `(css|styles|scss|sass)`
+)
+
+func DecodeServer(cfg Provider) (Server, error) {
s := &Server{}
- if m == nil {
- return s, nil
- }
- _ = mapstructure.WeakDecode(m, s)
+ _ = mapstructure.WeakDecode(cfg.GetStringMap("server"), s)
for i, redir := range s.Redirects {
- // Get it in line with the Hugo server.
redir.To = strings.TrimSuffix(redir.To, "index.html")
- if !strings.HasPrefix(redir.To, "https") && !strings.HasSuffix(redir.To, "/") {
- // There are some tricky infinite loop situations when dealing
- // when the target does not have a trailing slash.
- // This can certainly be handled better, but not time for that now.
- return nil, errors.Errorf("unsupported redirect to value %q in server config; currently this must be either a remote destination or a local folder, e.g. \"/blog/\" or \"/blog/index.html\"", redir.To)
- }
s.Redirects[i] = redir
}
- return s, nil
+ if len(s.Redirects) == 0 {
+ // Set up a default redirect for 404s.
+ s.Redirects = []Redirect{
+ {
+ From: "/**",
+ To: "/404.html",
+ Status: 404,
+ },
+ }
+ }
+
+ 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
}
diff --git a/config/commonConfig_test.go b/config/commonConfig_test.go
index 55767913f35..05ba185e350 100644
--- a/config/commonConfig_test.go
+++ b/config/commonConfig_test.go
@@ -18,6 +18,7 @@ import (
"testing"
"github.com/gohugoio/hugo/common/herrors"
+ "github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/types"
qt "github.com/frankban/quicktest"
@@ -27,19 +28,19 @@ func TestBuild(t *testing.T) {
c := qt.New(t)
v := New()
- v.Set("build", map[string]interface{}{
+ v.Set("build", map[string]any{
"useResourceCacheWhen": "always",
})
- b := DecodeBuild(v)
+ b := DecodeBuildConfig(v)
c.Assert(b.UseResourceCacheWhen, qt.Equals, "always")
- v.Set("build", map[string]interface{}{
+ v.Set("build", map[string]any{
"useResourceCacheWhen": "foo",
})
- b = DecodeBuild(v)
+ b = DecodeBuildConfig(v)
c.Assert(b.UseResourceCacheWhen, qt.Equals, "fallback")
@@ -70,17 +71,33 @@ X-Content-Type-Options = "nosniff"
[[server.redirects]]
from = "/foo/**"
-to = "/foo/index.html"
+to = "/baz/index.html"
status = 200
[[server.redirects]]
-from = "/google/**"
-to = "https://google.com/"
-status = 301
+from = "/loop/**"
+to = "/loop/foo/"
+status = 200
+
+[[server.redirects]]
+from = "/b/**"
+fromRe = "/b/(.*)/"
+to = "/baz/$1/"
+status = 200
+
+[[server.redirects]]
+fromRe = "/c/(.*)/"
+to = "/boo/$1/"
+status = 200
+
+[[server.redirects]]
+fromRe = "/d/(.*)/"
+to = "/boo/$1/"
+status = 200
[[server.redirects]]
-from = "/**"
-to = "/default/index.html"
+from = "/google/**"
+to = "https://google.com/"
status = 301
@@ -91,6 +108,7 @@ status = 301
s, err := DecodeServer(cfg)
c.Assert(err, qt.IsNil)
+ c.Assert(s.CompileConfig(loggers.NewDefault()), qt.IsNil)
c.Assert(s.MatchHeaders("/foo.jpg"), qt.DeepEquals, []types.KeyValueStr{
{Key: "X-Content-Type-Options", Value: "nosniff"},
@@ -98,43 +116,82 @@ status = 301
{Key: "X-XSS-Protection", Value: "1; mode=block"},
})
- c.Assert(s.MatchRedirect("/foo/bar/baz"), qt.DeepEquals, Redirect{
+ c.Assert(s.MatchRedirect("/foo/bar/baz", nil), qt.DeepEquals, Redirect{
From: "/foo/**",
- To: "/foo/",
+ To: "/baz/",
Status: 200,
})
- c.Assert(s.MatchRedirect("/someother"), qt.DeepEquals, Redirect{
- From: "/**",
- To: "/default/",
- Status: 301,
+ c.Assert(s.MatchRedirect("/foo/bar/", nil), qt.DeepEquals, Redirect{
+ From: "/foo/**",
+ To: "/baz/",
+ Status: 200,
+ })
+
+ c.Assert(s.MatchRedirect("/b/c/", nil), qt.DeepEquals, Redirect{
+ From: "/b/**",
+ FromRe: "/b/(.*)/",
+ To: "/baz/c/",
+ Status: 200,
})
- c.Assert(s.MatchRedirect("/google/foo"), qt.DeepEquals, Redirect{
+ c.Assert(s.MatchRedirect("/c/d/", nil).To, qt.Equals, "/boo/d/")
+ c.Assert(s.MatchRedirect("/c/d/e/", nil).To, qt.Equals, "/boo/d/e/")
+
+ c.Assert(s.MatchRedirect("/someother", nil), qt.DeepEquals, Redirect{})
+
+ c.Assert(s.MatchRedirect("/google/foo", nil), qt.DeepEquals, Redirect{
From: "/google/**",
To: "https://google.com/",
Status: 301,
})
+}
+
+func TestBuildConfigCacheBusters(t *testing.T) {
+ c := qt.New(t)
+ cfg := New()
+ conf := DecodeBuildConfig(cfg)
+ l := loggers.NewDefault()
+ c.Assert(conf.CompileConfig(l), qt.IsNil)
+
+ m, _ := conf.MatchCacheBuster(l, "tailwind.config.js")
+ c.Assert(m, qt.IsNotNil)
+ c.Assert(m("css"), qt.IsTrue)
+ c.Assert(m("js"), qt.IsFalse)
+
+ m, _ = conf.MatchCacheBuster(l, "foo.bar")
+ c.Assert(m, qt.IsNil)
+}
+
+func TestBuildConfigCacheBusterstTailwindSetup(t *testing.T) {
+ c := qt.New(t)
+ cfg := New()
+ cfg.Set("build", map[string]any{
+ "cacheBusters": []map[string]string{
+ {
+ "source": "assets/watching/hugo_stats\\.json",
+ "target": "css",
+ },
+ {
+ "source": "(postcss|tailwind)\\.config\\.js",
+ "target": "css",
+ },
+ {
+ "source": "assets/.*\\.(js|ts|jsx|tsx)",
+ "target": "js",
+ },
+ {
+ "source": "assets/.*\\.(.*)$",
+ "target": "$1",
+ },
+ },
+ })
- // No redirect loop, please.
- c.Assert(s.MatchRedirect("/default/index.html"), qt.DeepEquals, Redirect{})
- c.Assert(s.MatchRedirect("/default/"), qt.DeepEquals, Redirect{})
-
- for _, errorCase := range []string{
- `[[server.redirects]]
-from = "/**"
-to = "/file"
-status = 301`,
- `[[server.redirects]]
-from = "/**"
-to = "/foo/file.html"
-status = 301`,
- } {
-
- cfg, err := FromConfigString(errorCase, "toml")
- c.Assert(err, qt.IsNil)
- _, err = DecodeServer(cfg)
- c.Assert(err, qt.Not(qt.IsNil))
-
- }
+ conf := DecodeBuildConfig(cfg)
+ l := loggers.NewDefault()
+ c.Assert(conf.CompileConfig(l), qt.IsNil)
+
+ m, err := conf.MatchCacheBuster(l, "assets/watching/hugo_stats.json")
+ c.Assert(err, qt.IsNil)
+ c.Assert(m("css"), qt.IsTrue)
}
diff --git a/config/compositeConfig.go b/config/compositeConfig.go
deleted file mode 100644
index 92bb165b7c3..00000000000
--- a/config/compositeConfig.go
+++ /dev/null
@@ -1,117 +0,0 @@
-// 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.
-// 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 config
-
-import (
- "github.com/gohugoio/hugo/common/maps"
-)
-
-// NewCompositeConfig creates a new composite Provider with a read-only base
-// and a writeable layer.
-func NewCompositeConfig(base, layer Provider) Provider {
- return &compositeConfig{
- base: base,
- layer: layer,
- }
-}
-
-// compositeConfig contains a read only config base with
-// a possibly writeable config layer on top.
-type compositeConfig struct {
- base Provider
- layer Provider
-}
-
-func (c *compositeConfig) GetBool(key string) bool {
- if c.layer.IsSet(key) {
- return c.layer.GetBool(key)
- }
- return c.base.GetBool(key)
-}
-
-func (c *compositeConfig) GetInt(key string) int {
- if c.layer.IsSet(key) {
- return c.layer.GetInt(key)
- }
- return c.base.GetInt(key)
-}
-
-func (c *compositeConfig) Merge(key string, value interface{}) {
- c.layer.Merge(key, value)
-}
-
-func (c *compositeConfig) GetParams(key string) maps.Params {
- if c.layer.IsSet(key) {
- return c.layer.GetParams(key)
- }
- return c.base.GetParams(key)
-}
-
-func (c *compositeConfig) GetStringMap(key string) map[string]interface{} {
- if c.layer.IsSet(key) {
- return c.layer.GetStringMap(key)
- }
- return c.base.GetStringMap(key)
-}
-
-func (c *compositeConfig) GetStringMapString(key string) map[string]string {
- if c.layer.IsSet(key) {
- return c.layer.GetStringMapString(key)
- }
- return c.base.GetStringMapString(key)
-}
-
-func (c *compositeConfig) GetStringSlice(key string) []string {
- if c.layer.IsSet(key) {
- return c.layer.GetStringSlice(key)
- }
- return c.base.GetStringSlice(key)
-}
-
-func (c *compositeConfig) Get(key string) interface{} {
- if c.layer.IsSet(key) {
- return c.layer.Get(key)
- }
- return c.base.Get(key)
-}
-
-func (c *compositeConfig) IsSet(key string) bool {
- if c.layer.IsSet(key) {
- return true
- }
- return c.base.IsSet(key)
-}
-
-func (c *compositeConfig) GetString(key string) string {
- if c.layer.IsSet(key) {
- return c.layer.GetString(key)
- }
- return c.base.GetString(key)
-}
-
-func (c *compositeConfig) Set(key string, value interface{}) {
- c.layer.Set(key, value)
-}
-
-func (c *compositeConfig) SetDefaults(params maps.Params) {
- c.layer.SetDefaults(params)
-}
-
-func (c *compositeConfig) WalkParams(walkFn func(params ...KeyParams) bool) {
- panic("not supported")
-}
-
-func (c *compositeConfig) SetDefaultMergeStrategy() {
- panic("not supported")
-}
diff --git a/config/compositeConfig_test.go b/config/compositeConfig_test.go
deleted file mode 100644
index 60644102fd2..00000000000
--- a/config/compositeConfig_test.go
+++ /dev/null
@@ -1,40 +0,0 @@
-// 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.
-// 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 config
-
-import (
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestCompositeConfig(t *testing.T) {
- c := qt.New(t)
-
- c.Run("Set and get", func(c *qt.C) {
- base, layer := New(), New()
- cfg := NewCompositeConfig(base, layer)
-
- layer.Set("a1", "av")
- base.Set("b1", "bv")
- cfg.Set("c1", "cv")
-
- c.Assert(cfg.Get("a1"), qt.Equals, "av")
- c.Assert(cfg.Get("b1"), qt.Equals, "bv")
- c.Assert(cfg.Get("c1"), qt.Equals, "cv")
- c.Assert(cfg.IsSet("c1"), qt.IsTrue)
- c.Assert(layer.IsSet("c1"), qt.IsTrue)
- c.Assert(base.IsSet("c1"), qt.IsFalse)
- })
-}
diff --git a/config/configLoader.go b/config/configLoader.go
index 145b95d7da1..49fc27fc6c9 100644
--- a/config/configLoader.go
+++ b/config/configLoader.go
@@ -1,4 +1,4 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
+// Copyright 2025 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,27 +14,38 @@
package config
import (
+ "fmt"
"os"
"path/filepath"
"strings"
"github.com/gohugoio/hugo/common/herrors"
-
- "github.com/pkg/errors"
+ "github.com/gohugoio/hugo/common/hmaps"
+ "github.com/gohugoio/hugo/parser"
"github.com/gohugoio/hugo/common/paths"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/parser/metadecoders"
"github.com/spf13/afero"
)
var (
+ // See issue #8979 for context.
+ // Hugo has always used config.toml etc. as the default config file name.
+ // But hugo.toml is a more descriptive name, but we need to check for both.
+ DefaultConfigNames = []string{"hugo", "config"}
+
+ DefaultConfigNamesSet = make(map[string]bool)
+
ValidConfigFileExtensions = []string{"toml", "yaml", "yml", "json"}
validConfigFileExtensionsMap map[string]bool = make(map[string]bool)
)
func init() {
+ for _, name := range DefaultConfigNames {
+ DefaultConfigNamesSet[name] = true
+ }
+
for _, ext := range ValidConfigFileExtensions {
validConfigFileExtensionsMap[ext] = true
}
@@ -47,6 +58,25 @@ func IsValidConfigFilename(filename string) bool {
return validConfigFileExtensionsMap[ext]
}
+// FromTOMLConfigString creates a config from the given TOML config. This is useful in tests.
+func FromTOMLConfigString(config string) Provider {
+ cfg, err := FromConfigString(config, "toml")
+ if err != nil {
+ panic(err)
+ }
+ return cfg
+}
+
+// FromMapToTOMLString converts the given map to a TOML string. This is useful in tests.
+func FromMapToTOMLString(v map[string]any) string {
+ var sb strings.Builder
+ err := parser.InterfaceToConfig(v, metadecoders.TOML, &sb)
+ if err != nil {
+ panic(err)
+ }
+ return sb.String()
+}
+
// FromConfigString creates a config from the given YAML, JSON or TOML config. This is useful in tests.
func FromConfigString(config, configType string) (Provider, error) {
m, err := readConfig(metadecoders.FormatFromString(configType), []byte(config))
@@ -60,18 +90,25 @@ func FromConfigString(config, configType string) (Provider, error) {
func FromFile(fs afero.Fs, filename string) (Provider, error) {
m, err := loadConfigFromFile(fs, filename)
if err != nil {
- return nil, herrors.WithFileContextForFileDefault(err, filename, fs)
+ fe := herrors.UnwrapFileError(err)
+ if fe != nil {
+ pos := fe.Position()
+ pos.Filename = filename
+ fe.UpdatePosition(pos)
+ return nil, err
+ }
+ return nil, herrors.NewFileErrorFromFile(err, filename, fs, nil)
}
return NewFrom(m), nil
}
// FromFileToMap is the same as FromFile, but it returns the config values
// as a simple map.
-func FromFileToMap(fs afero.Fs, filename string) (map[string]interface{}, error) {
+func FromFileToMap(fs afero.Fs, filename string) (map[string]any, error) {
return loadConfigFromFile(fs, filename)
}
-func readConfig(format metadecoders.Format, data []byte) (map[string]interface{}, error) {
+func readConfig(format metadecoders.Format, data []byte) (map[string]any, error) {
m, err := metadecoders.Default.UnmarshalToMap(data, format)
if err != nil {
return nil, err
@@ -82,7 +119,7 @@ func readConfig(format metadecoders.Format, data []byte) (map[string]interface{}
return m, nil
}
-func loadConfigFromFile(fs afero.Fs, filename string) (map[string]interface{}, error) {
+func loadConfigFromFile(fs afero.Fs, filename string) (map[string]any, error) {
m, err := metadecoders.Default.UnmarshalFileToMap(fs, filename)
if err != nil {
return nil, err
@@ -132,12 +169,11 @@ func LoadConfigFromDir(sourceFs afero.Fs, configDir, environment string) (Provid
if err != nil {
// This will be used in error reporting, use the most specific value.
dirnames = []string{path}
- return errors.Wrapf(err, "failed to unmarshl config for path %q", path)
+ return fmt.Errorf("failed to unmarshal config for path %q: %w", path, err)
}
var keyPath []string
-
- if name != "config" {
+ if !DefaultConfigNamesSet[name] {
// Can be params.jp, menus.en etc.
name, lang := paths.FileAndExtNoDelimiter(name)
@@ -156,13 +192,13 @@ func LoadConfigFromDir(sourceFs afero.Fs, configDir, environment string) (Provid
root := item
if len(keyPath) > 0 {
- root = make(map[string]interface{})
+ root = make(map[string]any)
m := root
for i, key := range keyPath {
if i >= len(keyPath)-1 {
m[key] = item
} else {
- nm := make(map[string]interface{})
+ nm := make(map[string]any)
m[key] = nm
m = nm
}
@@ -184,18 +220,16 @@ func LoadConfigFromDir(sourceFs afero.Fs, configDir, environment string) (Provid
}
return cfg, dirnames, nil
-
}
-var keyAliases maps.KeyRenamer
+var keyAliases hmaps.KeyRenamer
func init() {
var err error
- keyAliases, err = maps.NewKeyRenamer(
+ keyAliases, err = hmaps.NewKeyRenamer(
// Before 0.53 we used singular for "menu".
"{menu,languages/*/menu}", "menus",
)
-
if err != nil {
panic(err)
}
@@ -203,6 +237,6 @@ func init() {
// RenameKeys renames config keys in m recursively according to a global Hugo
// alias definition.
-func RenameKeys(m map[string]interface{}) {
+func RenameKeys(m map[string]any) {
keyAliases.Rename(m)
}
diff --git a/config/configLoader_test.go b/config/configLoader_test.go
index 5460313344f..a2d16fba75b 100644
--- a/config/configLoader_test.go
+++ b/config/configLoader_test.go
@@ -14,6 +14,7 @@
package config
import (
+ "regexp"
"strings"
"testing"
@@ -32,3 +33,13 @@ func TestIsValidConfigFileName(t *testing.T) {
c.Assert(IsValidConfigFilename(""), qt.Equals, false)
c.Assert(IsValidConfigFilename("config.toml.swp"), qt.Equals, false)
}
+
+func TestFromTOMLConfigString(t *testing.T) {
+ c := qt.New(t)
+
+ c.Assert(
+ func() { FromTOMLConfigString("cfg") },
+ qt.PanicMatches,
+ regexp.MustCompile("_stream.toml:.*"),
+ )
+}
diff --git a/config/configProvider.go b/config/configProvider.go
index 481524c3b2b..c1a7afeb69e 100644
--- a/config/configProvider.go
+++ b/config/configProvider.go
@@ -14,25 +14,100 @@
package config
import (
- "github.com/gohugoio/hugo/common/maps"
+ "time"
+
+ "github.com/gohugoio/hugo/common/hmaps"
+ "github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/types"
+ "github.com/gohugoio/hugo/common/urls"
+ "github.com/gohugoio/hugo/hugolib/sitesmatrix"
+ "github.com/gohugoio/hugo/identity"
)
+// AllProvider is a sub set of all config settings.
+type AllProvider interface {
+ Language() any
+ LanguageIndex() int
+ Languages() any
+ LanguagePrefix() string
+ BaseURL() urls.BaseURL
+ BaseURLLiveReload() urls.BaseURL
+ PathParser() *paths.PathParser
+ Environment() string
+ IsMultihost() bool
+ IsMultilingual() bool
+ NoBuildLock() bool
+ BaseConfig() BaseConfig
+ Dirs() CommonDirs
+ Quiet() bool
+ DirsBase() CommonDirs
+ FileCaches() any
+ ContentTypes() ContentTypesProvider
+ GetConfigSection(string) any
+ GetConfig() any
+ CanonifyURLs() bool
+ DisablePathToLower() bool
+ RemovePathAccents() bool
+ IsUglyURLs(section string) bool
+ DefaultContentLanguage() string
+ DefaultContentLanguageInSubdir() bool
+ DefaultContentRoleInSubdir() bool
+ DefaultContentVersionInSubdir() bool
+ DefaultContentsitesMatrix() *sitesmatrix.IntSets
+ AllSitesMatrix() *sitesmatrix.IntSets
+ IsKindEnabled(string) bool
+ IsLangDisabled(string) bool
+ SummaryLength() int
+ Pagination() Pagination
+ BuildExpired() bool
+ BuildFuture() bool
+ BuildDrafts() bool
+ Running() bool
+ Watching() bool
+ NewIdentityManager(opts ...identity.ManagerOption) identity.Manager
+ FastRenderMode() bool
+ PrintUnusedTemplates() bool
+ EnableMissingTranslationPlaceholders() bool
+ TemplateMetrics() bool
+ TemplateMetricsHints() bool
+ PrintI18nWarnings() bool
+ CreateTitle(s string) string
+ IgnoreFile(s string) bool
+ NewContentEditor() string
+ Timeout() time.Duration
+ StaticDirs() []string
+ IgnoredLogs() map[string]bool
+ WorkingDir() string
+ EnableEmoji() bool
+ ConfiguredDimensions() *sitesmatrix.ConfiguredDimensions
+ CacheDirMisc() string
+}
+
+// We cannot import the media package as that would create a circular dependency.
+// This interface defines a subset of what media.ContentTypes provides.
+type ContentTypesProvider interface {
+ IsContentSuffix(suffix string) bool
+ IsContentFile(filename string) bool
+ IsIndexContentFile(filename string) bool
+ IsHTMLSuffix(suffix string) bool
+}
+
// Provider provides the configuration settings for Hugo.
type Provider interface {
GetString(key string) string
GetInt(key string) int
GetBool(key string) bool
- GetParams(key string) maps.Params
- GetStringMap(key string) map[string]interface{}
+ GetParams(key string) hmaps.Params
+ GetStringMap(key string) map[string]any
GetStringMapString(key string) map[string]string
GetStringSlice(key string) []string
- Get(key string) interface{}
- Set(key string, value interface{})
- Merge(key string, value interface{})
- SetDefaults(params maps.Params)
+ Get(key string) any
+ Set(key string, value any)
+ Keys() []string
+ Merge(key string, value any)
+ SetDefaults(params hmaps.Params)
SetDefaultMergeStrategy()
- WalkParams(walkFn func(params ...KeyParams) bool)
+ WalkParams(walkFn func(params ...hmaps.KeyParams) bool)
IsSet(key string) bool
}
@@ -44,14 +119,5 @@ func GetStringSlicePreserveString(cfg Provider, key string) []string {
return types.ToStringSlicePreserveString(sd)
}
-// SetBaseTestDefaults provides some common config defaults used in tests.
-func SetBaseTestDefaults(cfg Provider) {
- cfg.Set("resourceDir", "resources")
- cfg.Set("contentDir", "content")
- cfg.Set("dataDir", "data")
- cfg.Set("i18nDir", "i18n")
- cfg.Set("layoutDir", "layouts")
- cfg.Set("assetDir", "assets")
- cfg.Set("archetypeDir", "archetypes")
- cfg.Set("publishDir", "public")
-}
+/*func (cd ConfiguredDimensions) Language(v sitesmatrix.Vector) ConfiguredDimension {
+}*/
diff --git a/config/defaultConfigProvider.go b/config/defaultConfigProvider.go
index 7701e765a2d..7e1e0adb0c5 100644
--- a/config/defaultConfigProvider.go
+++ b/config/defaultConfigProvider.go
@@ -14,62 +14,26 @@
package config
import (
+ "errors"
"fmt"
"sort"
"strings"
"sync"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/spf13/cast"
-
- "github.com/gohugoio/hugo/common/maps"
-)
-
-var (
-
- // ConfigRootKeysSet contains all of the config map root keys.
- ConfigRootKeysSet = map[string]bool{
- "build": true,
- "caches": true,
- "cascade": true,
- "frontmatter": true,
- "languages": true,
- "imaging": true,
- "markup": true,
- "mediatypes": true,
- "menus": true,
- "minify": true,
- "module": true,
- "outputformats": true,
- "params": true,
- "permalinks": true,
- "related": true,
- "sitemap": true,
- "privacy": true,
- "security": true,
- "taxonomies": true,
- }
-
- // ConfigRootKeys is a sorted version of ConfigRootKeysSet.
- ConfigRootKeys []string
)
-func init() {
- for k := range ConfigRootKeysSet {
- ConfigRootKeys = append(ConfigRootKeys, k)
- }
- sort.Strings(ConfigRootKeys)
-}
-
-// New creates a Provider backed by an empty maps.Params.
+// New creates a Provider backed by an empty hmaps.Params.
func New() Provider {
return &defaultConfigProvider{
- root: make(maps.Params),
+ root: make(hmaps.Params),
}
}
// NewFrom creates a Provider backed by params.
-func NewFrom(params maps.Params) Provider {
- maps.PrepareParams(params)
+func NewFrom(params hmaps.Params) Provider {
+ hmaps.PrepareParams(params)
return &defaultConfigProvider{
root: params,
}
@@ -79,12 +43,12 @@ func NewFrom(params maps.Params) Provider {
// All methods are thread safe.
type defaultConfigProvider struct {
mu sync.RWMutex
- root maps.Params
+ root hmaps.Params
keyCache sync.Map
}
-func (c *defaultConfigProvider) Get(k string) interface{} {
+func (c *defaultConfigProvider) Get(k string) any {
if k == "" {
return c.root
}
@@ -125,22 +89,22 @@ func (c *defaultConfigProvider) GetString(k string) string {
return cast.ToString(v)
}
-func (c *defaultConfigProvider) GetParams(k string) maps.Params {
+func (c *defaultConfigProvider) GetParams(k string) hmaps.Params {
v := c.Get(k)
if v == nil {
return nil
}
- return v.(maps.Params)
+ return v.(hmaps.Params)
}
-func (c *defaultConfigProvider) GetStringMap(k string) map[string]interface{} {
+func (c *defaultConfigProvider) GetStringMap(k string) map[string]any {
v := c.Get(k)
- return maps.ToStringMap(v)
+ return hmaps.ToStringMap(v)
}
func (c *defaultConfigProvider) GetStringMapString(k string) map[string]string {
v := c.Get(k)
- return maps.ToStringMapString(v)
+ return hmaps.ToStringMapString(v)
}
func (c *defaultConfigProvider) GetStringSlice(k string) []string {
@@ -148,16 +112,16 @@ func (c *defaultConfigProvider) GetStringSlice(k string) []string {
return cast.ToStringSlice(v)
}
-func (c *defaultConfigProvider) Set(k string, v interface{}) {
+func (c *defaultConfigProvider) Set(k string, v any) {
c.mu.Lock()
defer c.mu.Unlock()
k = strings.ToLower(k)
if k == "" {
- if p, ok := maps.ToParamsAndPrepare(v); ok {
+ if p, err := hmaps.ToParamsAndPrepare(v); err == nil {
// Set the values directly in root.
- c.root.Set(p)
+ hmaps.SetParams(c.root, p)
} else {
c.root[k] = v
}
@@ -166,8 +130,8 @@ func (c *defaultConfigProvider) Set(k string, v interface{}) {
}
switch vv := v.(type) {
- case map[string]interface{}, map[interface{}]interface{}, map[string]string:
- p := maps.MustToParamsAndPrepare(vv)
+ case map[string]any, map[any]any, map[string]string:
+ p := hmaps.MustToParamsAndPrepare(vv)
v = p
}
@@ -177,9 +141,9 @@ func (c *defaultConfigProvider) Set(k string, v interface{}) {
}
if existing, found := m[key]; found {
- if p1, ok := existing.(maps.Params); ok {
- if p2, ok := v.(maps.Params); ok {
- p1.Set(p2)
+ if p1, ok := existing.(hmaps.Params); ok {
+ if p2, ok := v.(hmaps.Params); ok {
+ hmaps.SetParams(p1, p2)
return
}
}
@@ -189,8 +153,8 @@ func (c *defaultConfigProvider) Set(k string, v interface{}) {
}
// SetDefaults will set values from params if not already set.
-func (c *defaultConfigProvider) SetDefaults(params maps.Params) {
- maps.PrepareParams(params)
+func (c *defaultConfigProvider) SetDefaults(params hmaps.Params) {
+ hmaps.PrepareParams(params)
for k, v := range params {
if _, found := c.root[k]; !found {
c.root[k] = v
@@ -198,76 +162,35 @@ func (c *defaultConfigProvider) SetDefaults(params maps.Params) {
}
}
-func (c *defaultConfigProvider) Merge(k string, v interface{}) {
+func (c *defaultConfigProvider) Merge(k string, v any) {
c.mu.Lock()
defer c.mu.Unlock()
k = strings.ToLower(k)
- const (
- languagesKey = "languages"
- paramsKey = "params"
- menusKey = "menus"
- )
-
if k == "" {
rs, f := c.root.GetMergeStrategy()
- if f && rs == maps.ParamsMergeStrategyNone {
+ if f && rs == hmaps.ParamsMergeStrategyNone {
// The user has set a "no merge" strategy on this,
// nothing more to do.
return
}
- if p, ok := maps.ToParamsAndPrepare(v); ok {
+ if p, err := hmaps.ToParamsAndPrepare(v); err == nil {
// As there may be keys in p not in root, we need to handle
// those as a special case.
var keysToDelete []string
for kk, vv := range p {
- if pp, ok := vv.(maps.Params); ok {
+ if pp, ok := vv.(hmaps.Params); ok {
if pppi, ok := c.root[kk]; ok {
- ppp := pppi.(maps.Params)
- if kk == languagesKey {
- // Languages is currently a special case.
- // We may have languages with menus or params in the
- // right map that is not present in the left map.
- // With the default merge strategy those items will not
- // be passed over.
- var hasParams, hasMenus bool
- for _, rv := range pp {
- if lkp, ok := rv.(maps.Params); ok {
- _, hasMenus = lkp[menusKey]
- _, hasParams = lkp[paramsKey]
- }
- }
-
- if hasMenus || hasParams {
- for _, lv := range ppp {
- if lkp, ok := lv.(maps.Params); ok {
- if hasMenus {
- if _, ok := lkp[menusKey]; !ok {
- p := maps.Params{}
- p.SetDefaultMergeStrategy(maps.ParamsMergeStrategyShallow)
- lkp[menusKey] = p
- }
- }
- if hasParams {
- if _, ok := lkp[paramsKey]; !ok {
- p := maps.Params{}
- p.SetDefaultMergeStrategy(maps.ParamsMergeStrategyShallow)
- lkp[paramsKey] = p
- }
- }
- }
- }
- }
- }
- ppp.Merge(pp)
+ ppp := pppi.(hmaps.Params)
+ hmaps.MergeParamsWithStrategy("", ppp, pp)
} else {
// We need to use the default merge strategy for
// this key.
- np := make(maps.Params)
- strategy := c.determineMergeStrategy(KeyParams{Key: "", Params: c.root}, KeyParams{Key: kk, Params: np})
- np.SetDefaultMergeStrategy(strategy)
- np.Merge(pp)
+ np := make(hmaps.Params)
+ strategy := c.determineMergeStrategy(hmaps.KeyParams{Key: "", Params: c.root}, hmaps.KeyParams{Key: kk, Params: np})
+ np.SetMergeStrategy(strategy)
+ hmaps.MergeParamsWithStrategy("", np, pp)
c.root[kk] = np
if np.IsZero() {
// Just keep it until merge is done.
@@ -277,7 +200,7 @@ func (c *defaultConfigProvider) Merge(k string, v interface{}) {
}
}
// Merge the rest.
- c.root.MergeRoot(p)
+ hmaps.MergeParams(c.root, p)
for _, k := range keysToDelete {
delete(c.root, k)
}
@@ -289,8 +212,8 @@ func (c *defaultConfigProvider) Merge(k string, v interface{}) {
}
switch vv := v.(type) {
- case map[string]interface{}, map[interface{}]interface{}, map[string]string:
- p := maps.MustToParamsAndPrepare(vv)
+ case map[string]any, map[any]any, map[string]string:
+ p := hmaps.MustToParamsAndPrepare(vv)
v = p
}
@@ -300,9 +223,9 @@ func (c *defaultConfigProvider) Merge(k string, v interface{}) {
}
if existing, found := m[key]; found {
- if p1, ok := existing.(maps.Params); ok {
- if p2, ok := v.(maps.Params); ok {
- p1.Merge(p2)
+ if p1, ok := existing.(hmaps.Params); ok {
+ if p2, ok := v.(hmaps.Params); ok {
+ hmaps.MergeParamsWithStrategy("", p1, p2)
}
}
} else {
@@ -310,33 +233,48 @@ func (c *defaultConfigProvider) Merge(k string, v interface{}) {
}
}
-func (c *defaultConfigProvider) WalkParams(walkFn func(params ...KeyParams) bool) {
- var walk func(params ...KeyParams)
- walk = func(params ...KeyParams) {
+func (c *defaultConfigProvider) Keys() []string {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+ var keys []string
+ for k := range c.root {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ return keys
+}
+
+func (c *defaultConfigProvider) WalkParams(walkFn func(params ...hmaps.KeyParams) bool) {
+ maxDepth := 1000
+ var walk func(depth int, params ...hmaps.KeyParams)
+ walk = func(depth int, params ...hmaps.KeyParams) {
+ if depth > maxDepth {
+ panic(errors.New("max depth exceeded"))
+ }
if walkFn(params...) {
return
}
p1 := params[len(params)-1]
i := len(params)
for k, v := range p1.Params {
- if p2, ok := v.(maps.Params); ok {
- paramsplus1 := make([]KeyParams, i+1)
+ if p2, ok := v.(hmaps.Params); ok {
+ paramsplus1 := make([]hmaps.KeyParams, i+1)
copy(paramsplus1, params)
- paramsplus1[i] = KeyParams{Key: k, Params: p2}
- walk(paramsplus1...)
+ paramsplus1[i] = hmaps.KeyParams{Key: k, Params: p2}
+ walk(depth+1, paramsplus1...)
}
}
}
- walk(KeyParams{Key: "", Params: c.root})
+ walk(0, hmaps.KeyParams{Key: "", Params: c.root})
}
-func (c *defaultConfigProvider) determineMergeStrategy(params ...KeyParams) maps.ParamsMergeStrategy {
+func (c *defaultConfigProvider) determineMergeStrategy(params ...hmaps.KeyParams) hmaps.ParamsMergeStrategy {
if len(params) == 0 {
- return maps.ParamsMergeStrategyNone
+ return hmaps.ParamsMergeStrategyNone
}
var (
- strategy maps.ParamsMergeStrategy
+ strategy hmaps.ParamsMergeStrategy
prevIsRoot bool
curr = params[len(params)-1]
)
@@ -360,10 +298,10 @@ func (c *defaultConfigProvider) determineMergeStrategy(params ...KeyParams) maps
// Don't set a merge strategy on the root unless set by user.
// This will be handled as a special case.
case "params":
- strategy = maps.ParamsMergeStrategyDeep
+ strategy = hmaps.ParamsMergeStrategyDeep
case "outputformats", "mediatypes":
if prevIsRoot {
- strategy = maps.ParamsMergeStrategyShallow
+ strategy = hmaps.ParamsMergeStrategyShallow
}
case "menus":
isMenuKey := prevIsRoot
@@ -375,24 +313,19 @@ func (c *defaultConfigProvider) determineMergeStrategy(params ...KeyParams) maps
}
}
if isMenuKey {
- strategy = maps.ParamsMergeStrategyShallow
+ strategy = hmaps.ParamsMergeStrategyShallow
}
default:
if strategy == "" {
- strategy = maps.ParamsMergeStrategyNone
+ strategy = hmaps.ParamsMergeStrategyNone
}
}
return strategy
}
-type KeyParams struct {
- Key string
- Params maps.Params
-}
-
func (c *defaultConfigProvider) SetDefaultMergeStrategy() {
- c.WalkParams(func(params ...KeyParams) bool {
+ c.WalkParams(func(params ...hmaps.KeyParams) bool {
if len(params) == 0 {
return false
}
@@ -404,14 +337,13 @@ func (c *defaultConfigProvider) SetDefaultMergeStrategy() {
}
strategy := c.determineMergeStrategy(params...)
if strategy != "" {
- p.SetDefaultMergeStrategy(strategy)
+ p.SetMergeStrategy(strategy)
}
return false
})
-
}
-func (c *defaultConfigProvider) getNestedKeyAndMap(key string, create bool) (string, maps.Params) {
+func (c *defaultConfigProvider) getNestedKeyAndMap(key string, create bool) (string, hmaps.Params) {
var parts []string
v, ok := c.keyCache.Load(key)
if ok {
@@ -421,18 +353,18 @@ func (c *defaultConfigProvider) getNestedKeyAndMap(key string, create bool) (str
c.keyCache.Store(key, parts)
}
current := c.root
- for i := 0; i < len(parts)-1; i++ {
+ for i := range len(parts) - 1 {
next, found := current[parts[i]]
if !found {
if create {
- next = make(maps.Params)
+ next = make(hmaps.Params)
current[parts[i]] = next
} else {
return "", nil
}
}
var ok bool
- current, ok = next.(maps.Params)
+ current, ok = next.(hmaps.Params)
if !ok {
// E.g. a string, not a map that we can store values in.
return "", nil
diff --git a/config/defaultConfigProvider_test.go b/config/defaultConfigProvider_test.go
index 585ce4c6727..7ab4185f8a9 100644
--- a/config/defaultConfigProvider_test.go
+++ b/config/defaultConfigProvider_test.go
@@ -17,16 +17,14 @@ import (
"context"
"errors"
"fmt"
+ "slices"
"strconv"
"strings"
"testing"
- "github.com/spf13/viper"
-
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/para"
- "github.com/gohugoio/hugo/common/maps"
-
qt "github.com/frankban/quicktest"
)
@@ -36,7 +34,7 @@ func TestDefaultConfigProvider(t *testing.T) {
c.Run("Set and get", func(c *qt.C) {
cfg := New()
var k string
- var v interface{}
+ var v any
k, v = "foo", "bar"
cfg.Set(k, v)
@@ -49,7 +47,7 @@ func TestDefaultConfigProvider(t *testing.T) {
c.Assert(cfg.Get(k), qt.Equals, v)
c.Assert(cfg.GetInt(k), qt.Equals, v)
- c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
+ c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{
"foo": 42,
})
})
@@ -57,42 +55,42 @@ func TestDefaultConfigProvider(t *testing.T) {
c.Run("Set and get map", func(c *qt.C) {
cfg := New()
- cfg.Set("foo", map[string]interface{}{
+ cfg.Set("foo", map[string]any{
"bar": "baz",
})
- c.Assert(cfg.Get("foo"), qt.DeepEquals, maps.Params{
+ c.Assert(cfg.Get("foo"), qt.DeepEquals, hmaps.Params{
"bar": "baz",
})
- c.Assert(cfg.GetStringMap("foo"), qt.DeepEquals, map[string]interface{}{"bar": string("baz")})
+ c.Assert(cfg.GetStringMap("foo"), qt.DeepEquals, map[string]any{"bar": string("baz")})
c.Assert(cfg.GetStringMapString("foo"), qt.DeepEquals, map[string]string{"bar": string("baz")})
})
c.Run("Set and get nested", func(c *qt.C) {
cfg := New()
- cfg.Set("a", map[string]interface{}{
+ cfg.Set("a", map[string]any{
"B": "bv",
})
cfg.Set("a.c", "cv")
- c.Assert(cfg.Get("a"), qt.DeepEquals, maps.Params{
+ c.Assert(cfg.Get("a"), qt.DeepEquals, hmaps.Params{
"b": "bv",
"c": "cv",
})
c.Assert(cfg.Get("a.c"), qt.Equals, "cv")
cfg.Set("b.a", "av")
- c.Assert(cfg.Get("b"), qt.DeepEquals, maps.Params{
+ c.Assert(cfg.Get("b"), qt.DeepEquals, hmaps.Params{
"a": "av",
})
- cfg.Set("b", map[string]interface{}{
+ cfg.Set("b", map[string]any{
"b": "bv",
})
- c.Assert(cfg.Get("b"), qt.DeepEquals, maps.Params{
+ c.Assert(cfg.Get("b"), qt.DeepEquals, hmaps.Params{
"a": "av",
"b": "bv",
})
@@ -101,12 +99,12 @@ func TestDefaultConfigProvider(t *testing.T) {
cfg.Set("a", "av")
- cfg.Set("", map[string]interface{}{
+ cfg.Set("", map[string]any{
"a": "av2",
"b": "bv2",
})
- c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
+ c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{
"a": "av2",
"b": "bv2",
})
@@ -115,30 +113,30 @@ func TestDefaultConfigProvider(t *testing.T) {
cfg.Set("a", "av")
- cfg.Set("", map[string]interface{}{
+ cfg.Set("", map[string]any{
"b": "bv2",
})
- c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
+ c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{
"a": "av",
"b": "bv2",
})
cfg = New()
- cfg.Set("", map[string]interface{}{
- "foo": map[string]interface{}{
+ cfg.Set("", map[string]any{
+ "foo": map[string]any{
"a": "av",
},
})
- cfg.Set("", map[string]interface{}{
- "foo": map[string]interface{}{
+ cfg.Set("", map[string]any{
+ "foo": map[string]any{
"b": "bv2",
},
})
- c.Assert(cfg.Get("foo"), qt.DeepEquals, maps.Params{
+ c.Assert(cfg.Get("foo"), qt.DeepEquals, hmaps.Params{
"a": "av",
"b": "bv2",
})
@@ -147,16 +145,16 @@ func TestDefaultConfigProvider(t *testing.T) {
c.Run("Merge default strategy", func(c *qt.C) {
cfg := New()
- cfg.Set("a", map[string]interface{}{
+ cfg.Set("a", map[string]any{
"B": "bv",
})
- cfg.Merge("a", map[string]interface{}{
+ cfg.Merge("a", map[string]any{
"B": "bv2",
"c": "cv2",
})
- c.Assert(cfg.Get("a"), qt.DeepEquals, maps.Params{
+ c.Assert(cfg.Get("a"), qt.DeepEquals, hmaps.Params{
"b": "bv",
"c": "cv2",
})
@@ -165,12 +163,12 @@ func TestDefaultConfigProvider(t *testing.T) {
cfg.Set("a", "av")
- cfg.Merge("", map[string]interface{}{
+ cfg.Merge("", map[string]any{
"a": "av2",
"b": "bv2",
})
- c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
+ c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{
"a": "av",
})
})
@@ -178,26 +176,26 @@ func TestDefaultConfigProvider(t *testing.T) {
c.Run("Merge shallow", func(c *qt.C) {
cfg := New()
- cfg.Set("a", map[string]interface{}{
+ cfg.Set("a", map[string]any{
"_merge": "shallow",
"B": "bv",
- "c": map[string]interface{}{
+ "c": map[string]any{
"b": "bv",
},
})
- cfg.Merge("a", map[string]interface{}{
- "c": map[string]interface{}{
+ cfg.Merge("a", map[string]any{
+ "c": map[string]any{
"d": "dv2",
},
"e": "ev2",
})
- c.Assert(cfg.Get("a"), qt.DeepEquals, maps.Params{
+ c.Assert(cfg.Get("a"), qt.DeepEquals, hmaps.Params{
"e": "ev2",
- "_merge": maps.ParamsMergeStrategyShallow,
+ "_merge": hmaps.ParamsMergeStrategyShallow,
"b": "bv",
- "c": maps.Params{
+ "c": hmaps.Params{
"b": "bv",
},
})
@@ -205,61 +203,59 @@ func TestDefaultConfigProvider(t *testing.T) {
// Issue #8679
c.Run("Merge typed maps", func(c *qt.C) {
-
- for _, left := range []interface{}{
+ for _, left := range []any{
map[string]string{
"c": "cv1",
},
- map[string]interface{}{
+ map[string]any{
"c": "cv1",
},
- map[interface{}]interface{}{
+ map[any]any{
"c": "cv1",
},
} {
cfg := New()
- cfg.Set("", map[string]interface{}{
+ cfg.Set("", map[string]any{
"b": left,
})
- cfg.Merge("", maps.Params{
- "b": maps.Params{
+ cfg.Merge("", hmaps.Params{
+ "b": hmaps.Params{
"c": "cv2",
"d": "dv2",
},
})
- c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
- "b": maps.Params{
+ c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{
+ "b": hmaps.Params{
"c": "cv1",
"d": "dv2",
},
})
}
- for _, left := range []interface{}{
+ for _, left := range []any{
map[string]string{
"b": "bv1",
},
- map[string]interface{}{
+ map[string]any{
"b": "bv1",
},
- map[interface{}]interface{}{
+ map[any]any{
"b": "bv1",
},
} {
-
- for _, right := range []interface{}{
+ for _, right := range []any{
map[string]string{
"b": "bv2",
"c": "cv2",
},
- map[string]interface{}{
+ map[string]any{
"b": "bv2",
"c": "cv2",
},
- map[interface{}]interface{}{
+ map[any]any{
"b": "bv2",
"c": "cv2",
},
@@ -270,34 +266,32 @@ func TestDefaultConfigProvider(t *testing.T) {
cfg.Merge("a", right)
- c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
- "a": maps.Params{
+ c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{
+ "a": hmaps.Params{
"b": "bv1",
"c": "cv2",
},
})
}
-
}
-
})
// Issue #8701
c.Run("Prevent _merge only maps", func(c *qt.C) {
cfg := New()
- cfg.Set("", map[string]interface{}{
+ cfg.Set("", map[string]any{
"B": "bv",
})
- cfg.Merge("", map[string]interface{}{
- "c": map[string]interface{}{
+ cfg.Merge("", map[string]any{
+ "c": map[string]any{
"_merge": "shallow",
"d": "dv2",
},
})
- c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
+ c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{
"b": "bv",
})
})
@@ -305,7 +299,7 @@ func TestDefaultConfigProvider(t *testing.T) {
c.Run("IsSet", func(c *qt.C) {
cfg := New()
- cfg.Set("a", map[string]interface{}{
+ cfg.Set("a", map[string]any{
"B": "bv",
})
@@ -338,8 +332,7 @@ func TestDefaultConfigProvider(t *testing.T) {
return nil
}
- for i := 0; i < 20; i++ {
- i := i
+ for i := range 20 {
r.Run(func() error {
const v = 42
k := fmt.Sprintf("k%d", i)
@@ -347,7 +340,7 @@ func TestDefaultConfigProvider(t *testing.T) {
return err
}
- m := maps.Params{
+ m := hmaps.Params{
"new": 42,
}
@@ -359,19 +352,103 @@ func TestDefaultConfigProvider(t *testing.T) {
c.Assert(r.Wait(), qt.IsNil)
})
+
+ c.Run("GetBool", func(c *qt.C) {
+ cfg := New()
+
+ var k string
+ var v bool
+
+ k, v = "foo", true
+
+ cfg.Set(k, v)
+ c.Assert(cfg.Get(k), qt.Equals, v)
+ c.Assert(cfg.GetBool(k), qt.Equals, v)
+ })
+
+ c.Run("GetParams", func(c *qt.C) {
+ cfg := New()
+ k := "foo"
+
+ cfg.Set(k, hmaps.Params{k: true})
+ c.Assert(cfg.GetParams(k), qt.DeepEquals, hmaps.Params{
+ k: true,
+ })
+
+ c.Assert(cfg.GetParams("bar"), qt.IsNil)
+ })
+
+ c.Run("Keys", func(c *qt.C) {
+ cfg := New()
+ k := "foo"
+ k2 := "bar"
+
+ cfg.Set(k, hmaps.Params{k: struct{}{}})
+ cfg.Set(k2, hmaps.Params{k2: struct{}{}})
+
+ c.Assert(len(cfg.Keys()), qt.Equals, 2)
+
+ got := cfg.Keys()
+ slices.Sort(got)
+
+ want := []string{k, k2}
+ slices.Sort(want)
+
+ c.Assert(got, qt.DeepEquals, want)
+ })
+
+ c.Run("WalkParams", func(c *qt.C) {
+ cfg := New()
+
+ cfg.Set("x", hmaps.Params{})
+ cfg.Set("y", hmaps.Params{})
+
+ var got []string
+ cfg.WalkParams(func(params ...hmaps.KeyParams) bool {
+ got = append(got, params[len(params)-1].Key)
+ return false
+ })
+
+ want := []string{"", "x", "y"}
+ slices.Sort(got)
+ slices.Sort(want)
+
+ c.Assert(got, qt.DeepEquals, want)
+
+ cfg = New()
+ cfg.WalkParams(func(params ...hmaps.KeyParams) bool {
+ return true
+ })
+
+ got = []string{""}
+ want = []string{""}
+ c.Assert(got, qt.DeepEquals, want)
+ })
+
+ c.Run("SetDefaults", func(c *qt.C) {
+ cfg := New()
+
+ cfg.SetDefaults(hmaps.Params{
+ "foo": "bar",
+ "bar": "baz",
+ })
+
+ c.Assert(cfg.Get("foo"), qt.Equals, "bar")
+ c.Assert(cfg.Get("bar"), qt.Equals, "baz")
+ })
}
func BenchmarkDefaultConfigProvider(b *testing.B) {
type cfger interface {
- Get(key string) interface{}
- Set(key string, value interface{})
+ Get(key string) any
+ Set(key string, value any)
IsSet(key string) bool
}
- newMap := func() map[string]interface{} {
- return map[string]interface{}{
- "a": map[string]interface{}{
- "b": map[string]interface{}{
+ newMap := func() map[string]any {
+ return map[string]any{
+ "a": map[string]any{
+ "b": map[string]any{
"c": 32,
"d": 43,
},
@@ -397,16 +474,9 @@ func BenchmarkDefaultConfigProvider(b *testing.B) {
}
}
- b.Run("Viper", func(b *testing.B) {
- v := viper.New()
- for i := 0; i < b.N; i++ {
- runMethods(b, v)
- }
- })
-
b.Run("Custom", func(b *testing.B) {
cfg := New()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
runMethods(b, cfg)
}
})
diff --git a/config/docshelper.go b/config/docshelper.go
deleted file mode 100644
index 336a0dc16b0..00000000000
--- a/config/docshelper.go
+++ /dev/null
@@ -1,45 +0,0 @@
-// 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.
-// 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 config
-
-import (
- "github.com/gohugoio/hugo/common/maps"
- "github.com/gohugoio/hugo/docshelper"
-)
-
-// This is is just some helpers used to create some JSON used in the Hugo docs.
-func init() {
- docsProvider := func() docshelper.DocProvider {
-
- cfg := New()
- for _, configRoot := range ConfigRootKeys {
- cfg.Set(configRoot, make(maps.Params))
- }
- lang := maps.Params{
- "en": maps.Params{
- "menus": maps.Params{},
- "params": maps.Params{},
- },
- }
- cfg.Set("languages", lang)
- cfg.SetDefaultMergeStrategy()
-
- configHelpers := map[string]interface{}{
- "mergeStrategy": cfg.Get(""),
- }
- return docshelper.DocProvider{"config": configHelpers}
- }
-
- docshelper.AddDocProviderFunc(docsProvider)
-}
diff --git a/config/env.go b/config/env.go
index 1e7d4721655..4dcd6365392 100644
--- a/config/env.go
+++ b/config/env.go
@@ -18,6 +18,12 @@ import (
"runtime"
"strconv"
"strings"
+
+ "github.com/pbnjay/memory"
+)
+
+const (
+ gigabyte = 1 << 30
)
// GetNumWorkerMultiplier returns the base value used to calculate the number
@@ -33,6 +39,36 @@ func GetNumWorkerMultiplier() int {
return runtime.NumCPU()
}
+// GetMemoryLimit returns the upper memory limit in bytes for Hugo's in-memory caches.
+// Note that this does not represent "all of the memory" that Hugo will use,
+// so it needs to be set to a lower number than the available system memory.
+// It will read from the HUGO_MEMORYLIMIT (in Gigabytes) environment variable.
+// If that is not set, it will set aside a quarter of the total system memory.
+func GetMemoryLimit() uint64 {
+ if mem := os.Getenv("HUGO_MEMORYLIMIT"); mem != "" {
+ if v := stringToGibabyte(mem); v > 0 {
+ return v
+ }
+ }
+
+ // There is a FreeMemory function, but as the kernel in most situations
+ // will take whatever memory that is left and use for caching etc.,
+ // that value is not something that we can use.
+ m := memory.TotalMemory()
+ if m != 0 {
+ return uint64(m / 4)
+ }
+
+ return 2 * gigabyte
+}
+
+func stringToGibabyte(f string) uint64 {
+ if v, err := strconv.ParseFloat(f, 32); err == nil && v > 0 {
+ return uint64(v * gigabyte)
+ }
+ return 0
+}
+
// SetEnvVars sets vars on the form key=value in the oldVars slice.
func SetEnvVars(oldVars *[]string, keyValues ...string) {
for i := 0; i < len(keyValues); i += 2 {
@@ -41,8 +77,8 @@ func SetEnvVars(oldVars *[]string, keyValues ...string) {
}
func SplitEnvVar(v string) (string, string) {
- parts := strings.SplitN(v, "=", 2)
- return parts[0], parts[1]
+ name, value, _ := strings.Cut(v, "=")
+ return name, value
}
func setEnvVar(vars *[]string, key, value string) {
diff --git a/config/namespace.go b/config/namespace.go
new file mode 100644
index 00000000000..e41b56e2d9e
--- /dev/null
+++ b/config/namespace.go
@@ -0,0 +1,75 @@
+// 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 config
+
+import (
+ "encoding/json"
+
+ "github.com/gohugoio/hugo/common/hashing"
+)
+
+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.HashStringHex(configSource)
+
+ // Build the config
+ c, ext, err := buildConfig(configSource)
+ if err != nil {
+ return nil, err
+ }
+
+ if ext == nil {
+ ext = configSource
+ }
+
+ if ext == nil {
+ panic("ext is nil")
+ }
+
+ ns := &ConfigNamespace[S, C]{
+ SourceStructure: ext,
+ SourceHash: h,
+ Config: c,
+ }
+
+ return ns, nil
+}
+
+// ConfigNamespace holds a Hugo configuration namespace.
+// The construct looks a little odd, but it's built to make the configuration elements
+// both self-documenting and contained in a common structure.
+type ConfigNamespace[S, C any] struct {
+ // SourceStructure represents the source configuration with any defaults applied.
+ // This is used for documentation and printing of the configuration setup to the user.
+ SourceStructure any
+
+ // SourceHash is a hash of the source configuration before any defaults gets applied.
+ SourceHash string
+
+ // Config is the final configuration as used by Hugo.
+ Config C
+}
+
+// MarshalJSON marshals the source structure.
+func (ns *ConfigNamespace[S, C]) MarshalJSON() ([]byte, error) {
+ return json.Marshal(ns.SourceStructure)
+}
+
+// Signature returns the signature of the source structure.
+// Note that this is for documentation purposes only and SourceStructure may not always be cast to S (it's usually just a map).
+func (ns *ConfigNamespace[S, C]) Signature() S {
+ var s S
+ return s
+}
diff --git a/config/namespace_test.go b/config/namespace_test.go
new file mode 100644
index 00000000000..481302d26bc
--- /dev/null
+++ b/config/namespace_test.go
@@ -0,0 +1,60 @@
+// 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 config
+
+import (
+ "strings"
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+ "github.com/gohugoio/hugo/common/hmaps"
+ "github.com/mitchellh/mapstructure"
+)
+
+func TestNamespace(t *testing.T) {
+ c := qt.New(t)
+ c.Assert(true, qt.Equals, true)
+
+ // ns, err := config.DecodeNamespace[map[string]DocsMediaTypeConfig](in, defaultMediaTypesConfig, buildConfig)
+
+ ns, err := DecodeNamespace[[]*tstNsExt](
+ map[string]any{"foo": "bar"},
+ func(v any) (*tstNsExt, any, error) {
+ t := &tstNsExt{}
+ m, err := hmaps.ToStringMapE(v)
+ if err != nil {
+ return nil, nil, err
+ }
+ return t, nil, mapstructure.WeakDecode(m, t)
+ },
+ )
+
+ c.Assert(err, qt.IsNil)
+ c.Assert(ns, qt.Not(qt.IsNil))
+ c.Assert(ns.SourceStructure, qt.DeepEquals, map[string]any{"foo": "bar"})
+ c.Assert(ns.SourceHash, qt.Equals, "1420f6c7782f7459")
+ c.Assert(ns.Config, qt.DeepEquals, &tstNsExt{Foo: "bar"})
+ c.Assert(ns.Signature(), qt.DeepEquals, []*tstNsExt(nil))
+}
+
+type (
+ tstNsExt struct {
+ Foo string
+ }
+)
+
+func (t *tstNsExt) Init() error {
+ t.Foo = strings.ToUpper(t.Foo)
+ return nil
+}
diff --git a/config/privacy/privacyConfig.go b/config/privacy/privacyConfig.go
index a360463643f..b707206623d 100644
--- a/config/privacy/privacyConfig.go
+++ b/config/privacy/privacyConfig.go
@@ -30,9 +30,10 @@ type Config struct {
Disqus Disqus
GoogleAnalytics GoogleAnalytics
Instagram Instagram
- Twitter Twitter
+ Twitter Twitter `json:"-"` // deprecated in favor of X in v0.141.0
Vimeo Vimeo
YouTube YouTube
+ X X
}
// Disqus holds the privacy configuration settings related to the Disqus template.
@@ -44,15 +45,9 @@ type Disqus struct {
type GoogleAnalytics struct {
Service `mapstructure:",squash"`
- // Enabling this will disable the use of Cookies and use Session Storage to Store the GA Client ID.
- UseSessionStorage bool
-
// Enabling this will make the GA templates respect the
// "Do Not Track" HTTP header. See https://www.paulfurley.com/google-analytics-dnt/.
RespectDoNotTrack bool
-
- // Enabling this will make it so the users' IP addresses are anonymized within Google Analytics.
- AnonymizeIP bool
}
// Instagram holds the privacy configuration settings related to the Instagram shortcode.
@@ -64,7 +59,8 @@ type Instagram struct {
Simple bool
}
-// Twitter holds the privacy configuration settingsrelated to the Twitter shortcode.
+// Twitter holds the privacy configuration settings related to the Twitter shortcode.
+// Deprecated in favor of X in v0.141.0.
type Twitter struct {
Service `mapstructure:",squash"`
@@ -76,7 +72,7 @@ type Twitter struct {
Simple bool
}
-// Vimeo holds the privacy configuration settingsrelated to the Vimeo shortcode.
+// Vimeo holds the privacy configuration settings related to the Vimeo shortcode.
type Vimeo struct {
Service `mapstructure:",squash"`
@@ -90,7 +86,7 @@ type Vimeo struct {
Simple bool
}
-// YouTube holds the privacy configuration settingsrelated to the YouTube shortcode.
+// YouTube holds the privacy configuration settings related to the YouTube shortcode.
type YouTube struct {
Service `mapstructure:",squash"`
@@ -100,14 +96,24 @@ type YouTube struct {
PrivacyEnhanced bool
}
+// X holds the privacy configuration settings related to the X shortcode.
+type X struct {
+ Service `mapstructure:",squash"`
+
+ // When set to true, the X post and its embedded page on your site are not
+ // used for purposes that include personalized suggestions and personalized
+ // ads.
+ EnableDNT bool
+
+ // If simple mode is enabled, a static and no-JS version of the X post will
+ // be built.
+ Simple bool
+}
+
// DecodeConfig creates a privacy Config from a given Hugo configuration.
func DecodeConfig(cfg config.Provider) (pc Config, err error) {
- if !cfg.IsSet(privacyConfigKey) {
- return
- }
-
+ pc.GoogleAnalytics.RespectDoNotTrack = true
m := cfg.GetStringMap(privacyConfigKey)
-
err = mapstructure.WeakDecode(m, &pc)
return
diff --git a/config/privacy/privacyConfig_test.go b/config/privacy/privacyConfig_test.go
index c17ce713dea..1dd20215bc4 100644
--- a/config/privacy/privacyConfig_test.go
+++ b/config/privacy/privacyConfig_test.go
@@ -33,12 +33,10 @@ disable = true
[privacy.googleAnalytics]
disable = true
respectDoNotTrack = true
-anonymizeIP = true
-useSessionStorage = true
[privacy.instagram]
disable = true
simple = true
-[privacy.twitter]
+[privacy.x]
disable = true
enableDNT = true
simple = true
@@ -60,11 +58,11 @@ simple = true
got := []bool{
pc.Disqus.Disable, pc.GoogleAnalytics.Disable,
- pc.GoogleAnalytics.RespectDoNotTrack, pc.GoogleAnalytics.AnonymizeIP,
- pc.GoogleAnalytics.UseSessionStorage, pc.Instagram.Disable,
- pc.Instagram.Simple, pc.Twitter.Disable, pc.Twitter.EnableDNT,
- pc.Twitter.Simple, pc.Vimeo.Disable, pc.Vimeo.EnableDNT, pc.Vimeo.Simple,
- pc.YouTube.PrivacyEnhanced, pc.YouTube.Disable,
+ pc.GoogleAnalytics.RespectDoNotTrack, pc.Instagram.Disable,
+ pc.Instagram.Simple,
+ pc.Vimeo.Disable, pc.Vimeo.EnableDNT, pc.Vimeo.Simple,
+ pc.YouTube.PrivacyEnhanced, pc.YouTube.Disable, pc.X.Disable, pc.X.EnableDNT,
+ pc.X.Simple,
}
c.Assert(got, qt.All(qt.Equals), true)
diff --git a/config/security/docshelper.go b/config/security/docshelper.go
deleted file mode 100644
index ade03560ebc..00000000000
--- a/config/security/docshelper.go
+++ /dev/null
@@ -1,26 +0,0 @@
-// 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.
-// 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 security
-
-import (
- "github.com/gohugoio/hugo/docshelper"
-)
-
-func init() {
- docsProvider := func() docshelper.DocProvider {
-
- return docshelper.DocProvider{"config": DefaultConfig.ToSecurityMap()}
- }
- docshelper.AddDocProviderFunc(docsProvider)
-}
diff --git a/config/security/securityConfig.go b/config/security/securityConfig.go
index 09c5cb62573..d47492c0e60 100644
--- a/config/security/securityConfig.go
+++ b/config/security/securityConfig.go
@@ -34,34 +34,38 @@ const securityConfigKey = "security"
// DefaultConfig holds the default security policy.
var DefaultConfig = Config{
Exec: Exec{
- Allow: NewWhitelist(
- "^dart-sass-embedded$",
- "^go$", // for Go Modules
- "^npx$", // used by all Node tools (Babel, PostCSS).
+ 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.
- OsEnv: NewWhitelist("(?i)^(PATH|PATHEXT|APPDATA|TMP|TEMP|TERM)$"),
+ OsEnv: MustNewWhitelist(`(?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\w+|(XDG_CONFIG_)?HOME|USERPROFILE|SSH_AUTH_SOCK|DISPLAY|LANG|SYSTEMDRIVE|PROGRAMDATA)$`),
},
Funcs: Funcs{
- Getenv: NewWhitelist("^HUGO_"),
+ Getenv: MustNewWhitelist("^HUGO_", "^CI$"),
},
HTTP: HTTP{
- URLs: NewWhitelist(".*"),
- Methods: NewWhitelist("(?i)GET|POST"),
+ URLs: MustNewWhitelist(".*"),
+ Methods: MustNewWhitelist("(?i)GET|POST"),
},
}
// Config is the top level security config.
+// {"name": "security", "description": "This section holds the top level security config.", "newIn": "0.91.0" }
type Config struct {
- // Restricts access to os.Exec.
+ // Restricts access to os.Exec....
+ // { "newIn": "0.91.0" }
Exec Exec `json:"exec"`
// Restricts access to certain template funcs.
Funcs Funcs `json:"funcs"`
- // Restricts access to resources.Get, getJSON, getCSV.
+ // Restricts access to resources.GetRemote, getJSON, getCSV.
HTTP HTTP `json:"http"`
// Allow inline shortcodes
@@ -86,6 +90,9 @@ type HTTP struct {
// HTTP methods to allow.
Methods Whitelist `json:"methods"`
+
+ // Media types where the Content-Type in the response is used instead of resolving from the file content.
+ MediaTypes Whitelist `json:"mediaTypes"`
}
// ToTOML converts c to TOML with [security] as the root.
@@ -110,7 +117,6 @@ func (c Config) CheckAllowedExec(name string) error {
}
}
return nil
-
}
func (c Config) CheckAllowedGetEnv(name string) error {
@@ -147,19 +153,18 @@ func (c Config) CheckAllowedHTTPMethod(method string) error {
}
// ToSecurityMap converts c to a map with 'security' as the root key.
-func (c Config) ToSecurityMap() map[string]interface{} {
+func (c Config) ToSecurityMap() map[string]any {
// Take it to JSON and back to get proper casing etc.
asJson, err := json.Marshal(c)
herrors.Must(err)
- m := make(map[string]interface{})
+ m := make(map[string]any)
herrors.Must(json.Unmarshal(asJson, &m))
// Add the root
- sec := map[string]interface{}{
+ sec := map[string]any{
"security": m,
}
return sec
-
}
// DecodeConfig creates a privacy Config from a given Hugo configuration.
@@ -189,23 +194,21 @@ func DecodeConfig(cfg config.Provider) (Config, error) {
}
return sc, nil
-
}
func stringSliceToWhitelistHook() mapstructure.DecodeHookFuncType {
return func(
f reflect.Type,
t reflect.Type,
- data interface{}) (interface{}, error) {
-
- if t != reflect.TypeOf(Whitelist{}) {
+ data any,
+ ) (any, error) {
+ if t != reflect.TypeFor[Whitelist]() {
return data, nil
}
wl := types.ToStringSlicePreserveString(data)
- return NewWhitelist(wl...), nil
-
+ return NewWhitelist(wl...)
}
}
diff --git a/config/security/securityConfig_test.go b/config/security/securityConfig_test.go
new file mode 100644
index 00000000000..4ea77a50154
--- /dev/null
+++ b/config/security/securityConfig_test.go
@@ -0,0 +1,167 @@
+// Copyright 2018 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// 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 security
+
+import (
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+ "github.com/gohugoio/hugo/config"
+)
+
+func TestDecodeConfigFromTOML(t *testing.T) {
+ c := qt.New(t)
+
+ c.Run("Slice whitelist", func(c *qt.C) {
+ c.Parallel()
+ tomlConfig := `
+
+
+someOtherValue = "bar"
+
+[security]
+enableInlineShortcodes=true
+[security.exec]
+allow=["a", "b"]
+osEnv=["a", "b", "c"]
+[security.funcs]
+getEnv=["a", "b"]
+
+`
+
+ cfg, err := config.FromConfigString(tomlConfig, "toml")
+ c.Assert(err, qt.IsNil)
+
+ pc, err := DecodeConfig(cfg)
+ c.Assert(err, qt.IsNil)
+ c.Assert(pc, qt.Not(qt.IsNil))
+ c.Assert(pc.EnableInlineShortcodes, qt.IsTrue)
+ c.Assert(pc.Exec.Allow.Accept("a"), qt.IsTrue)
+ c.Assert(pc.Exec.Allow.Accept("d"), qt.IsFalse)
+ c.Assert(pc.Exec.OsEnv.Accept("a"), qt.IsTrue)
+ c.Assert(pc.Exec.OsEnv.Accept("e"), qt.IsFalse)
+ c.Assert(pc.Funcs.Getenv.Accept("a"), qt.IsTrue)
+ c.Assert(pc.Funcs.Getenv.Accept("c"), qt.IsFalse)
+ })
+
+ c.Run("String whitelist", func(c *qt.C) {
+ c.Parallel()
+ tomlConfig := `
+
+
+someOtherValue = "bar"
+
+[security]
+[security.exec]
+allow="a"
+osEnv="b"
+
+`
+
+ cfg, err := config.FromConfigString(tomlConfig, "toml")
+ c.Assert(err, qt.IsNil)
+
+ pc, err := DecodeConfig(cfg)
+ c.Assert(err, qt.IsNil)
+ c.Assert(pc, qt.Not(qt.IsNil))
+ c.Assert(pc.Exec.Allow.Accept("a"), qt.IsTrue)
+ c.Assert(pc.Exec.Allow.Accept("d"), qt.IsFalse)
+ c.Assert(pc.Exec.OsEnv.Accept("b"), qt.IsTrue)
+ c.Assert(pc.Exec.OsEnv.Accept("e"), qt.IsFalse)
+ })
+
+ c.Run("Default exec.osEnv", func(c *qt.C) {
+ c.Parallel()
+ tomlConfig := `
+
+
+someOtherValue = "bar"
+
+[security]
+[security.exec]
+allow="a"
+
+`
+
+ cfg, err := config.FromConfigString(tomlConfig, "toml")
+ c.Assert(err, qt.IsNil)
+
+ pc, err := DecodeConfig(cfg)
+ c.Assert(err, qt.IsNil)
+ c.Assert(pc, qt.Not(qt.IsNil))
+ c.Assert(pc.Exec.Allow.Accept("a"), qt.IsTrue)
+ c.Assert(pc.Exec.OsEnv.Accept("PATH"), qt.IsTrue)
+ c.Assert(pc.Exec.OsEnv.Accept("e"), qt.IsFalse)
+ })
+
+ c.Run("Enable inline shortcodes, legacy", func(c *qt.C) {
+ c.Parallel()
+ tomlConfig := `
+
+
+someOtherValue = "bar"
+enableInlineShortcodes=true
+
+[security]
+[security.exec]
+allow="a"
+osEnv="b"
+
+`
+
+ cfg, err := config.FromConfigString(tomlConfig, "toml")
+ c.Assert(err, qt.IsNil)
+
+ pc, err := DecodeConfig(cfg)
+ c.Assert(err, qt.IsNil)
+ c.Assert(pc.EnableInlineShortcodes, qt.IsTrue)
+ })
+}
+
+func TestToTOML(t *testing.T) {
+ c := qt.New(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|PROGRAMDATA)$']\n\n [security.funcs]\n getenv = ['^HUGO_', '^CI$']\n\n [security.http]\n methods = ['(?i)GET|POST']\n urls = ['.*']",
+ )
+}
+
+func TestDecodeConfigDefault(t *testing.T) {
+ t.Parallel()
+ c := qt.New(t)
+
+ pc, err := DecodeConfig(config.New())
+ c.Assert(err, qt.IsNil)
+ c.Assert(pc, qt.Not(qt.IsNil))
+ c.Assert(pc.Exec.Allow.Accept("a"), qt.IsFalse)
+ c.Assert(pc.Exec.Allow.Accept("npx"), qt.IsTrue)
+ c.Assert(pc.Exec.Allow.Accept("Npx"), qt.IsFalse)
+
+ c.Assert(pc.HTTP.URLs.Accept("https://example.org"), qt.IsTrue)
+ c.Assert(pc.HTTP.Methods.Accept("POST"), qt.IsTrue)
+ c.Assert(pc.HTTP.Methods.Accept("GET"), qt.IsTrue)
+ c.Assert(pc.HTTP.Methods.Accept("get"), qt.IsTrue)
+ c.Assert(pc.HTTP.Methods.Accept("DELETE"), qt.IsFalse)
+ c.Assert(pc.HTTP.MediaTypes.Accept("application/msword"), qt.IsFalse)
+
+ c.Assert(pc.Exec.OsEnv.Accept("PATH"), qt.IsTrue)
+ c.Assert(pc.Exec.OsEnv.Accept("GOROOT"), qt.IsTrue)
+ c.Assert(pc.Exec.OsEnv.Accept("HOME"), qt.IsTrue)
+ c.Assert(pc.Exec.OsEnv.Accept("SSH_AUTH_SOCK"), qt.IsTrue)
+ c.Assert(pc.Exec.OsEnv.Accept("a"), qt.IsFalse)
+ c.Assert(pc.Exec.OsEnv.Accept("e"), qt.IsFalse)
+ c.Assert(pc.Exec.OsEnv.Accept("MYSECRET"), qt.IsFalse)
+}
diff --git a/config/security/securityonfig_test.go b/config/security/securityonfig_test.go
deleted file mode 100644
index d0416a20df3..00000000000
--- a/config/security/securityonfig_test.go
+++ /dev/null
@@ -1,166 +0,0 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// 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 security
-
-import (
- "testing"
-
- qt "github.com/frankban/quicktest"
- "github.com/gohugoio/hugo/config"
-)
-
-func TestDecodeConfigFromTOML(t *testing.T) {
- c := qt.New(t)
-
- c.Run("Slice whitelist", func(c *qt.C) {
- c.Parallel()
- tomlConfig := `
-
-
-someOtherValue = "bar"
-
-[security]
-enableInlineShortcodes=true
-[security.exec]
-allow=["a", "b"]
-osEnv=["a", "b", "c"]
-[security.funcs]
-getEnv=["a", "b"]
-
-`
-
- cfg, err := config.FromConfigString(tomlConfig, "toml")
- c.Assert(err, qt.IsNil)
-
- pc, err := DecodeConfig(cfg)
- c.Assert(err, qt.IsNil)
- c.Assert(pc, qt.Not(qt.IsNil))
- c.Assert(pc.EnableInlineShortcodes, qt.IsTrue)
- c.Assert(pc.Exec.Allow.Accept("a"), qt.IsTrue)
- c.Assert(pc.Exec.Allow.Accept("d"), qt.IsFalse)
- c.Assert(pc.Exec.OsEnv.Accept("a"), qt.IsTrue)
- c.Assert(pc.Exec.OsEnv.Accept("e"), qt.IsFalse)
- c.Assert(pc.Funcs.Getenv.Accept("a"), qt.IsTrue)
- c.Assert(pc.Funcs.Getenv.Accept("c"), qt.IsFalse)
-
- })
-
- c.Run("String whitelist", func(c *qt.C) {
- c.Parallel()
- tomlConfig := `
-
-
-someOtherValue = "bar"
-
-[security]
-[security.exec]
-allow="a"
-osEnv="b"
-
-`
-
- cfg, err := config.FromConfigString(tomlConfig, "toml")
- c.Assert(err, qt.IsNil)
-
- pc, err := DecodeConfig(cfg)
- c.Assert(err, qt.IsNil)
- c.Assert(pc, qt.Not(qt.IsNil))
- c.Assert(pc.Exec.Allow.Accept("a"), qt.IsTrue)
- c.Assert(pc.Exec.Allow.Accept("d"), qt.IsFalse)
- c.Assert(pc.Exec.OsEnv.Accept("b"), qt.IsTrue)
- c.Assert(pc.Exec.OsEnv.Accept("e"), qt.IsFalse)
-
- })
-
- c.Run("Default exec.osEnv", func(c *qt.C) {
- c.Parallel()
- tomlConfig := `
-
-
-someOtherValue = "bar"
-
-[security]
-[security.exec]
-allow="a"
-
-`
-
- cfg, err := config.FromConfigString(tomlConfig, "toml")
- c.Assert(err, qt.IsNil)
-
- pc, err := DecodeConfig(cfg)
- c.Assert(err, qt.IsNil)
- c.Assert(pc, qt.Not(qt.IsNil))
- c.Assert(pc.Exec.Allow.Accept("a"), qt.IsTrue)
- c.Assert(pc.Exec.OsEnv.Accept("PATH"), qt.IsTrue)
- c.Assert(pc.Exec.OsEnv.Accept("e"), qt.IsFalse)
-
- })
-
- c.Run("Enable inline shortcodes, legacy", func(c *qt.C) {
- c.Parallel()
- tomlConfig := `
-
-
-someOtherValue = "bar"
-enableInlineShortcodes=true
-
-[security]
-[security.exec]
-allow="a"
-osEnv="b"
-
-`
-
- cfg, err := config.FromConfigString(tomlConfig, "toml")
- c.Assert(err, qt.IsNil)
-
- pc, err := DecodeConfig(cfg)
- c.Assert(err, qt.IsNil)
- c.Assert(pc.EnableInlineShortcodes, qt.IsTrue)
-
- })
-
-}
-
-func TestToTOML(t *testing.T) {
- c := qt.New(t)
-
- got := DefaultConfig.ToTOML()
-
- c.Assert(got, qt.Equals,
- "[security]\n enableInlineShortcodes = false\n [security.exec]\n allow = ['^dart-sass-embedded$', '^go$', '^npx$', '^postcss$']\n osEnv = ['(?i)^(PATH|PATHEXT|APPDATA|TMP|TEMP|TERM)$']\n\n [security.funcs]\n getenv = ['^HUGO_']\n\n [security.http]\n methods = ['(?i)GET|POST']\n urls = ['.*']",
- )
-}
-
-func TestDecodeConfigDefault(t *testing.T) {
- t.Parallel()
- c := qt.New(t)
-
- pc, err := DecodeConfig(config.New())
- c.Assert(err, qt.IsNil)
- c.Assert(pc, qt.Not(qt.IsNil))
- c.Assert(pc.Exec.Allow.Accept("a"), qt.IsFalse)
- c.Assert(pc.Exec.Allow.Accept("npx"), qt.IsTrue)
- c.Assert(pc.Exec.Allow.Accept("Npx"), qt.IsFalse)
- c.Assert(pc.Exec.OsEnv.Accept("a"), qt.IsFalse)
- c.Assert(pc.Exec.OsEnv.Accept("PATH"), qt.IsTrue)
- c.Assert(pc.Exec.OsEnv.Accept("e"), qt.IsFalse)
-
- c.Assert(pc.HTTP.URLs.Accept("https://example.org"), qt.IsTrue)
- c.Assert(pc.HTTP.Methods.Accept("POST"), qt.IsTrue)
- c.Assert(pc.HTTP.Methods.Accept("GET"), qt.IsTrue)
- c.Assert(pc.HTTP.Methods.Accept("get"), qt.IsTrue)
- c.Assert(pc.HTTP.Methods.Accept("DELETE"), qt.IsFalse)
-}
diff --git a/config/security/whitelist.go b/config/security/whitelist.go
index 0d2c187c6e5..5ce369a1f38 100644
--- a/config/security/whitelist.go
+++ b/config/security/whitelist.go
@@ -33,6 +33,7 @@ type Whitelist struct {
patternsStrings []string
}
+// MarshalJSON is for internal use only.
func (w Whitelist) MarshalJSON() ([]byte, error) {
if w.acceptNone {
return json.Marshal(acceptNoneKeyword)
@@ -43,10 +44,10 @@ func (w Whitelist) MarshalJSON() ([]byte, error) {
// NewWhitelist creates a new Whitelist from zero or more patterns.
// An empty patterns list or a pattern with the value 'none' will create
-// a whitelist that will Accept noone.
-func NewWhitelist(patterns ...string) Whitelist {
+// a whitelist that will Accept none.
+func NewWhitelist(patterns ...string) (Whitelist, error) {
if len(patterns) == 0 {
- return Whitelist{acceptNone: true}
+ return Whitelist{acceptNone: true}, nil
}
var acceptSome bool
@@ -67,23 +68,36 @@ func NewWhitelist(patterns ...string) Whitelist {
if !acceptSome {
return Whitelist{
acceptNone: true,
- }
+ }, nil
}
var patternsr []*regexp.Regexp
- for i := 0; i < len(patterns); i++ {
+ for i := range patterns {
p := strings.TrimSpace(patterns[i])
if p == "" {
continue
}
- patternsr = append(patternsr, regexp.MustCompile(p))
+ re, err := regexp.Compile(p)
+ if err != nil {
+ return Whitelist{}, fmt.Errorf("failed to compile whitelist pattern %q: %w", p, err)
+ }
+ patternsr = append(patternsr, re)
}
- return Whitelist{patterns: patternsr, patternsStrings: patternsStrings}
+ return Whitelist{patterns: patternsr, patternsStrings: patternsStrings}, nil
+}
+
+// MustNewWhitelist creates a new Whitelist from zero or more patterns and panics on error.
+func MustNewWhitelist(patterns ...string) Whitelist {
+ w, err := NewWhitelist(patterns...)
+ if err != nil {
+ panic(err)
+ }
+ return w
}
-// Accepted reports whether name is whitelisted.
+// Accept reports whether name is whitelisted.
func (w Whitelist) Accept(name string) bool {
if w.acceptNone {
return false
diff --git a/config/security/whitelist_test.go b/config/security/whitelist_test.go
index 5c4196dff21..add3345a8da 100644
--- a/config/security/whitelist_test.go
+++ b/config/security/whitelist_test.go
@@ -24,24 +24,23 @@ func TestWhitelist(t *testing.T) {
c := qt.New(t)
c.Run("none", func(c *qt.C) {
- c.Assert(NewWhitelist("none", "foo").Accept("foo"), qt.IsFalse)
- c.Assert(NewWhitelist().Accept("foo"), qt.IsFalse)
- c.Assert(NewWhitelist("").Accept("foo"), qt.IsFalse)
- c.Assert(NewWhitelist(" ", " ").Accept("foo"), qt.IsFalse)
+ c.Assert(MustNewWhitelist("none", "foo").Accept("foo"), qt.IsFalse)
+ c.Assert(MustNewWhitelist().Accept("foo"), qt.IsFalse)
+ c.Assert(MustNewWhitelist("").Accept("foo"), qt.IsFalse)
+ c.Assert(MustNewWhitelist(" ", " ").Accept("foo"), qt.IsFalse)
c.Assert(Whitelist{}.Accept("foo"), qt.IsFalse)
})
c.Run("One", func(c *qt.C) {
- w := NewWhitelist("^foo.*")
+ w := MustNewWhitelist("^foo.*")
c.Assert(w.Accept("foo"), qt.IsTrue)
c.Assert(w.Accept("mfoo"), qt.IsFalse)
})
c.Run("Multiple", func(c *qt.C) {
- w := NewWhitelist("^foo.*", "^bar.*")
+ w := MustNewWhitelist("^foo.*", "^bar.*")
c.Assert(w.Accept("foo"), qt.IsTrue)
c.Assert(w.Accept("bar"), qt.IsTrue)
c.Assert(w.Accept("mbar"), qt.IsFalse)
})
-
}
diff --git a/config/services/servicesConfig.go b/config/services/servicesConfig.go
index 1b4317e92f5..21dd7a6d962 100644
--- a/config/services/servicesConfig.go
+++ b/config/services/servicesConfig.go
@@ -26,12 +26,13 @@ const (
rssLimitKey = "rssLimit"
)
-// Config is a privacy configuration for all the relevant services in Hugo.
+// Config is a services configuration for all the relevant services in Hugo.
type Config struct {
Disqus Disqus
GoogleAnalytics GoogleAnalytics
- Instagram Instagram
- Twitter Twitter
+ Instagram Instagram `json:"-"` // the embedded instagram shortcode no longer uses this
+ Twitter Twitter `json:"-"` // deprecated in favor of X in v0.141.0
+ X X
RSS RSS
}
@@ -52,15 +53,16 @@ type Instagram struct {
// The Simple variant of the Instagram is decorated with Bootstrap 4 card classes.
// This means that if you use Bootstrap 4 or want to provide your own CSS, you want
// to disable the inline CSS provided by Hugo.
- DisableInlineCSS bool
+ DisableInlineCSS bool // this is no longer used by the embedded instagram shortcode
// App or Client Access Token.
// If you are using a Client Access Token, remember that you must combine it with your App ID
// using a pipe symbol (|) otherwise the request will fail.
- AccessToken string
+ AccessToken string // this is no longer used by the embedded instagram shortcode
}
// Twitter holds the functional configuration settings related to the Twitter shortcodes.
+// Deprecated in favor of X in v0.141.0.
type Twitter struct {
// The Simple variant of Twitter is decorated with a basic set of inline styles.
// This means that if you want to provide your own CSS, you want
@@ -68,6 +70,14 @@ type Twitter struct {
DisableInlineCSS bool
}
+// X holds the functional configuration settings related to the X shortcodes.
+type X struct {
+ // The Simple variant of X is decorated with a basic set of inline styles.
+ // This means that if you want to provide your own CSS, you want
+ // to disable the inline CSS provided by Hugo.
+ DisableInlineCSS bool
+}
+
// RSS holds the functional configuration settings related to the RSS feeds.
type RSS struct {
// Limit the number of pages.
@@ -91,6 +101,9 @@ func DecodeConfig(cfg config.Provider) (c Config, err error) {
if c.RSS.Limit == 0 {
c.RSS.Limit = cfg.GetInt(rssLimitKey)
+ if c.RSS.Limit == 0 {
+ c.RSS.Limit = -1
+ }
}
return
diff --git a/config/services/servicesConfig_test.go b/config/services/servicesConfig_test.go
index d7a52ba4f90..952a7fe1c7b 100644
--- a/config/services/servicesConfig_test.go
+++ b/config/services/servicesConfig_test.go
@@ -18,7 +18,6 @@ import (
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/config"
-
)
func TestDecodeConfigFromTOML(t *testing.T) {
@@ -37,6 +36,8 @@ id = "ga_id"
disableInlineCSS = true
[services.twitter]
disableInlineCSS = true
+[services.x]
+disableInlineCSS = true
`
cfg, err := config.FromConfigString(tomlConfig, "toml")
c.Assert(err, qt.IsNil)
diff --git a/config/testconfig/testconfig.go b/config/testconfig/testconfig.go
new file mode 100644
index 00000000000..20497b2b552
--- /dev/null
+++ b/config/testconfig/testconfig.go
@@ -0,0 +1,92 @@
+// 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.
+
+// This package should only be used for testing.
+package testconfig
+
+import (
+ _ "unsafe"
+
+ "github.com/gohugoio/hugo/common/hmaps"
+ "github.com/gohugoio/hugo/config"
+ "github.com/gohugoio/hugo/config/allconfig"
+ "github.com/gohugoio/hugo/deps"
+ "github.com/gohugoio/hugo/hugofs"
+ "github.com/gohugoio/hugo/internal/warpc"
+ toml "github.com/pelletier/go-toml/v2"
+ "github.com/spf13/afero"
+)
+
+func GetTestConfigs(fs afero.Fs, cfg config.Provider) *allconfig.Configs {
+ if fs == nil {
+ fs = afero.NewMemMapFs()
+ }
+ if cfg == nil {
+ cfg = config.New()
+ }
+ // Make sure that the workingDir exists.
+ workingDir := cfg.GetString("workingDir")
+ if workingDir != "" {
+ if err := fs.MkdirAll(workingDir, 0o777); err != nil {
+ panic(err)
+ }
+ }
+
+ configs, err := allconfig.LoadConfig(allconfig.ConfigSourceDescriptor{Fs: fs, Flags: cfg, Environ: []string{"EMPTY_TEST_ENVIRONMENT"}})
+ if err != nil {
+ panic(err)
+ }
+ return configs
+}
+
+func GetTestConfig(fs afero.Fs, cfg config.Provider) config.AllProvider {
+ return GetTestConfigs(fs, cfg).GetFirstLanguageConfig()
+}
+
+func GetTestDeps(fs afero.Fs, cfg config.Provider, beforeInit ...func(*deps.Deps)) *deps.Deps {
+ if fs == nil {
+ fs = afero.NewMemMapFs()
+ }
+ conf := GetTestConfig(fs, cfg)
+ d := &deps.Deps{
+ Conf: conf,
+ Fs: hugofs.NewFrom(fs, conf.BaseConfig()),
+ WasmDispatchers: warpc.AllDispatchers(
+ warpc.Options{
+ PoolSize: 1,
+ },
+ warpc.Options{
+ PoolSize: 1,
+ },
+ ),
+ }
+ for _, f := range beforeInit {
+ f(d)
+ }
+ if err := d.Init(); err != nil {
+ panic(err)
+ }
+ return d
+}
+
+func GetTestConfigSectionFromStruct(section string, v any) config.AllProvider {
+ data, err := toml.Marshal(v)
+ if err != nil {
+ panic(err)
+ }
+ p := hmaps.Params{
+ section: config.FromTOMLConfigString(string(data)).Get(""),
+ }
+ cfg := config.NewFrom(p)
+ return GetTestConfig(nil, cfg)
+}
diff --git a/create/content.go b/create/content.go
index 6ae91288264..519346960a5 100644
--- a/create/content.go
+++ b/create/content.go
@@ -16,20 +16,17 @@ package create
import (
"bytes"
+ "context"
+ "errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
- "github.com/gohugoio/hugo/hugofs/glob"
-
"github.com/gohugoio/hugo/common/hexec"
"github.com/gohugoio/hugo/common/paths"
-
- "github.com/pkg/errors"
-
- "github.com/gohugoio/hugo/hugofs/files"
+ "github.com/gohugoio/hugo/hugofs/hglob"
"github.com/gohugoio/hugo/hugofs"
@@ -39,10 +36,10 @@ import (
)
const (
- // DefaultArchetypeTemplateTemplate is the template used in 'hugo new site'
+ // DefaultArchetypeTemplateTemplate is the template used in 'hugo new project'
// and the template we use as a fall back.
DefaultArchetypeTemplateTemplate = `---
-title: "{{ replace .Name "-" " " | title }}"
+title: "{{ replace .File.ContentBaseName "-" " " | title }}"
date: {{ .Date }}
draft: true
---
@@ -52,8 +49,8 @@ draft: true
// NewContent creates a new content file in h (or a full bundle if the archetype is a directory)
// in targetPath.
-func NewContent(h *hugolib.HugoSites, kind, targetPath string) error {
- if h.BaseFs.Content.Dirs == nil {
+func NewContent(h *hugolib.HugoSites, kind, targetPath string, force bool) error {
+ if _, err := h.BaseFs.Content.Fs.Stat(""); err != nil {
return errors.New("no existing content directory configured for this project")
}
@@ -76,6 +73,7 @@ func NewContent(h *hugolib.HugoSites, kind, targetPath string) error {
kind: kind,
targetPath: targetPath,
+ force: force,
}
ext := paths.Ext(targetPath)
@@ -83,26 +81,27 @@ func NewContent(h *hugolib.HugoSites, kind, targetPath string) error {
b.setArcheTypeFilenameToUse(ext)
withBuildLock := func() (string, error) {
- unlock, err := h.BaseFs.LockBuild()
- if err != nil {
- return "", fmt.Errorf("failed to acquire a build lock: %s", err)
+ if !h.Configs.Base.NoBuildLock {
+ unlock, err := h.BaseFs.LockBuild()
+ if err != nil {
+ return "", fmt.Errorf("failed to acquire a build lock: %s", err)
+ }
+ defer unlock()
}
- defer unlock()
if b.isDir {
return "", b.buildDir()
}
if ext == "" {
- return "", errors.Errorf("failed to resolve %q to a archetype template", targetPath)
+ return "", fmt.Errorf("failed to resolve %q to an archetype template", targetPath)
}
- if !files.IsContentFile(b.targetPath) {
- return "", errors.Errorf("target path %q is not a known content format", b.targetPath)
+ if !h.Conf.ContentTypes().IsContentFile(b.targetPath) {
+ return "", fmt.Errorf("target path %q is not a known content format", b.targetPath)
}
return b.buildFile()
-
}
filename, err := withBuildLock()
@@ -115,7 +114,6 @@ func NewContent(h *hugolib.HugoSites, kind, targetPath string) error {
}
return nil
-
}
type contentBuilder struct {
@@ -127,11 +125,12 @@ type contentBuilder struct {
cf hugolib.ContentFactory
// Builder state
- archetypeFilename string
- targetPath string
- kind string
- isDir bool
- dirMap archetypeMap
+ archetypeFi hugofs.FileMetaInfo
+ targetPath string
+ kind string
+ isDir bool
+ dirMap archetypeMap
+ force bool
}
func (b *contentBuilder) buildDir() error {
@@ -144,8 +143,11 @@ func (b *contentBuilder) buildDir() error {
var baseDir string
for _, fi := range b.dirMap.contentFiles {
- targetFilename := filepath.Join(b.targetPath, strings.TrimPrefix(fi.Meta().Path, b.archetypeFilename))
- abs, err := b.cf.CreateContentPlaceHolder(targetFilename)
+
+ targetFilename := filepath.Join(b.targetPath, strings.TrimPrefix(fi.Meta().PathInfo.Path(), b.archetypeFi.Meta().PathInfo.Path()))
+
+ // ===> post/my-post/pages/bio.md
+ abs, err := b.cf.CreateContentPlaceHolder(targetFilename, b.force)
if err != nil {
return err
}
@@ -156,10 +158,10 @@ func (b *contentBuilder) buildDir() error {
contentTargetFilenames = append(contentTargetFilenames, abs)
}
- var contentInclusionFilter *glob.FilenameFilter
+ var contentInclusionFilter *hglob.FilenameFilter
if !b.dirMap.siteUsed {
// We don't need to build everything.
- contentInclusionFilter = glob.NewFilenameFilterForInclusionFunc(func(filename string) bool {
+ contentInclusionFilter = hglob.NewFilenameFilterForInclusionFunc(func(filename string) bool {
filename = strings.TrimPrefix(filename, string(os.PathSeparator))
for _, cn := range contentTargetFilenames {
if strings.Contains(cn, filename) {
@@ -168,7 +170,6 @@ func (b *contentBuilder) buildDir() error {
}
return false
})
-
}
if err := b.h.Build(hugolib.BuildCfg{NoBuildLock: true, SkipRender: true, ContentInclusionFilter: contentInclusionFilter}); err != nil {
@@ -176,26 +177,24 @@ func (b *contentBuilder) buildDir() error {
}
for i, filename := range contentTargetFilenames {
- if err := b.applyArcheType(filename, b.dirMap.contentFiles[i].Meta().Path); err != nil {
+ if err := b.applyArcheType(filename, b.dirMap.contentFiles[i]); err != nil {
return err
}
}
// Copy the rest as is.
- for _, f := range b.dirMap.otherFiles {
- meta := f.Meta()
- filename := meta.Path
+ for _, fi := range b.dirMap.otherFiles {
+ meta := fi.Meta()
in, err := meta.Open()
if err != nil {
- return errors.Wrap(err, "failed to open non-content file")
+ return fmt.Errorf("failed to open non-content file: %w", err)
}
-
- targetFilename := filepath.Join(baseDir, b.targetPath, strings.TrimPrefix(filename, b.archetypeFilename))
+ targetFilename := filepath.Join(baseDir, b.targetPath, strings.TrimPrefix(fi.Meta().Filename, b.archetypeFi.Meta().Filename))
targetDir := filepath.Dir(targetFilename)
if err := b.sourceFs.MkdirAll(targetDir, 0o777); err != nil && !os.IsExist(err) {
- return errors.Wrapf(err, "failed to create target directory for %q", targetDir)
+ return fmt.Errorf("failed to create target directory for %q: %w", targetDir, err)
}
out, err := b.sourceFs.Create(targetFilename)
@@ -218,20 +217,20 @@ func (b *contentBuilder) buildDir() error {
}
func (b *contentBuilder) buildFile() (string, error) {
- contentPlaceholderAbsFilename, err := b.cf.CreateContentPlaceHolder(b.targetPath)
+ contentPlaceholderAbsFilename, err := b.cf.CreateContentPlaceHolder(b.targetPath, b.force)
if err != nil {
return "", err
}
- usesSite, err := b.usesSiteVar(b.archetypeFilename)
+ usesSite, err := b.usesSiteVar(b.archetypeFi)
if err != nil {
return "", err
}
- var contentInclusionFilter *glob.FilenameFilter
+ var contentInclusionFilter *hglob.FilenameFilter
if !usesSite {
// We don't need to build everything.
- contentInclusionFilter = glob.NewFilenameFilterForInclusionFunc(func(filename string) bool {
+ contentInclusionFilter = hglob.NewFilenameFilterForInclusionFunc(func(filename string) bool {
filename = strings.TrimPrefix(filename, string(os.PathSeparator))
return strings.Contains(contentPlaceholderAbsFilename, filename)
})
@@ -241,7 +240,7 @@ func (b *contentBuilder) buildFile() (string, error) {
return "", err
}
- if err := b.applyArcheType(contentPlaceholderAbsFilename, b.archetypeFilename); err != nil {
+ if err := b.applyArcheType(contentPlaceholderAbsFilename, b.archetypeFi); err != nil {
return "", err
}
@@ -262,18 +261,17 @@ func (b *contentBuilder) setArcheTypeFilenameToUse(ext string) {
for _, p := range pathsToCheck {
fi, err := b.archeTypeFs.Stat(p)
if err == nil {
- b.archetypeFilename = p
+ b.archetypeFi = fi.(hugofs.FileMetaInfo)
b.isDir = fi.IsDir()
return
}
}
-
}
-func (b *contentBuilder) applyArcheType(contentFilename, archetypeFilename string) error {
+func (b *contentBuilder) applyArcheType(contentFilename string, archetypeFi hugofs.FileMetaInfo) error {
p := b.h.GetContentPage(contentFilename)
if p == nil {
- panic(fmt.Sprintf("[BUG] no Page found for %q", contentFilename))
+ return fmt.Errorf("no page found for %q; if a file with the same name but different case already exists, please use a different filename or remove the existing file", contentFilename)
}
f, err := b.sourceFs.Create(contentFilename)
@@ -282,32 +280,28 @@ func (b *contentBuilder) applyArcheType(contentFilename, archetypeFilename strin
}
defer f.Close()
- if archetypeFilename == "" {
- return b.cf.AppplyArchetypeTemplate(f, p, b.kind, DefaultArchetypeTemplateTemplate)
+ if archetypeFi == nil {
+ return b.cf.ApplyArchetypeTemplate(f, p, b.kind, DefaultArchetypeTemplateTemplate)
}
- return b.cf.AppplyArchetypeFilename(f, p, b.kind, archetypeFilename)
-
+ return b.cf.ApplyArchetypeFi(f, p, b.kind, archetypeFi)
}
func (b *contentBuilder) mapArcheTypeDir() error {
var m archetypeMap
- walkFn := func(path string, fi hugofs.FileMetaInfo, err error) error {
- if err != nil {
- return err
- }
-
- if fi.IsDir() {
+ walkFn := func(ctx context.Context, path string, fim hugofs.FileMetaInfo) error {
+ if fim.IsDir() {
return nil
}
- fil := fi.(hugofs.FileMetaInfo)
+ pi := fim.Meta().PathInfo
- if files.IsContentFile(path) {
- m.contentFiles = append(m.contentFiles, fil)
+ if pi.IsContent() {
+ m.contentFiles = append(m.contentFiles, fim)
if !m.siteUsed {
- m.siteUsed, err = b.usesSiteVar(path)
+ var err error
+ m.siteUsed, err = b.usesSiteVar(fim)
if err != nil {
return err
}
@@ -315,7 +309,7 @@ func (b *contentBuilder) mapArcheTypeDir() error {
return nil
}
- m.otherFiles = append(m.otherFiles, fil)
+ m.otherFiles = append(m.otherFiles, fim)
return nil
}
@@ -323,13 +317,13 @@ func (b *contentBuilder) mapArcheTypeDir() error {
walkCfg := hugofs.WalkwayConfig{
WalkFn: walkFn,
Fs: b.archeTypeFs,
- Root: b.archetypeFilename,
+ Root: filepath.FromSlash(b.archetypeFi.Meta().PathInfo.Path()),
}
w := hugofs.NewWalkway(walkCfg)
if err := w.Walk(); err != nil {
- return errors.Wrapf(err, "failed to walk archetype dir %q", b.archetypeFilename)
+ return fmt.Errorf("failed to walk archetype dir %q: %w", b.archetypeFi.Meta().Filename, err)
}
b.dirMap = m
@@ -338,20 +332,29 @@ func (b *contentBuilder) mapArcheTypeDir() error {
}
func (b *contentBuilder) openInEditorIfConfigured(filename string) error {
- editor := b.h.Cfg.GetString("newContentEditor")
+ editor := b.h.Conf.NewContentEditor()
if editor == "" {
return nil
}
- b.h.Log.Printf("Editing %q with %q ...\n", filename, editor)
- cmd, err := b.h.Deps.ExecHelper.New(
- editor,
+ editorExec := strings.Fields(editor)[0]
+ editorFlags := strings.Fields(editor)[1:]
+
+ var args []any
+ for _, editorFlag := range editorFlags {
+ args = append(args, editorFlag)
+ }
+ args = append(
+ args,
filename,
hexec.WithStdin(os.Stdin),
hexec.WithStderr(os.Stderr),
hexec.WithStdout(os.Stdout),
)
+ b.h.Log.Printf("Editing %q with %q ...\n", filename, editorExec)
+
+ cmd, err := b.h.Deps.ExecHelper.New(editorExec, args...)
if err != nil {
return err
}
@@ -359,17 +362,21 @@ func (b *contentBuilder) openInEditorIfConfigured(filename string) error {
return cmd.Run()
}
-func (b *contentBuilder) usesSiteVar(filename string) (bool, error) {
- if filename == "" {
+func (b *contentBuilder) usesSiteVar(fi hugofs.FileMetaInfo) (bool, error) {
+ if fi == nil {
return false, nil
}
- bb, err := afero.ReadFile(b.archeTypeFs, filename)
+ f, err := fi.Meta().Open()
+ if err != nil {
+ return false, err
+ }
+ defer f.Close()
+ bb, err := io.ReadAll(f)
if err != nil {
- return false, errors.Wrap(err, "failed to open archetype file")
+ return false, fmt.Errorf("failed to read archetype file: %w", err)
}
return bytes.Contains(bb, []byte(".Site")) || bytes.Contains(bb, []byte("site.")), nil
-
}
type archetypeMap struct {
diff --git a/create/content_test.go b/create/content_test.go
index a3692251fdf..deafaed0943 100644
--- a/create/content_test.go
+++ b/create/content_test.go
@@ -21,6 +21,8 @@ import (
"testing"
"github.com/gohugoio/hugo/config"
+ "github.com/gohugoio/hugo/config/allconfig"
+ "github.com/gohugoio/hugo/config/testconfig"
"github.com/gohugoio/hugo/deps"
@@ -40,7 +42,7 @@ func TestNewContentFromFile(t *testing.T) {
name string
kind string
path string
- expected interface{}
+ expected any
}{
{"Post", "post", "post/sample-1.md", []string{`title = "Post Arch title"`, `test = "test1"`, "date = \"2015-01-12T19:20:04-07:00\""}},
{"Post org-mode", "post", "post/org-1.org", []string{`#+title: ORG-1`}},
@@ -72,7 +74,6 @@ func TestNewContentFromFile(t *testing.T) {
c := qt.New(t)
for i, cas := range cases {
- cas := cas
c.Run(cas.name, func(c *qt.C) {
c.Parallel()
@@ -80,10 +81,10 @@ func TestNewContentFromFile(t *testing.T) {
mm := afero.NewMemMapFs()
c.Assert(initFs(mm), qt.IsNil)
cfg, fs := newTestCfg(c, mm)
- h, err := hugolib.NewHugoSites(deps.DepsCfg{Cfg: cfg, Fs: fs})
+ conf := testconfig.GetTestConfigs(fs.Source, cfg)
+ h, err := hugolib.NewHugoSites(deps.DepsCfg{Configs: conf, Fs: fs})
c.Assert(err, qt.IsNil)
-
- err = create.NewContent(h, cas.kind, cas.path)
+ err = create.NewContent(h, cas.kind, cas.path, false)
if b, ok := cas.expected.(bool); ok && !b {
if !b {
@@ -98,6 +99,7 @@ func TestNewContentFromFile(t *testing.T) {
if !strings.HasPrefix(fname, "content") {
fname = filepath.Join("content", fname)
}
+
content := readFileFromFs(c, fs.Source, fname)
for _, v := range cas.expected.([]string) {
@@ -111,57 +113,6 @@ func TestNewContentFromFile(t *testing.T) {
}
}
-func TestNewContentFromDir(t *testing.T) {
- mm := afero.NewMemMapFs()
- c := qt.New(t)
-
- archetypeDir := filepath.Join("archetypes", "my-bundle")
- c.Assert(mm.MkdirAll(archetypeDir, 0o755), qt.IsNil)
-
- archetypeThemeDir := filepath.Join("themes", "mytheme", "archetypes", "my-theme-bundle")
- c.Assert(mm.MkdirAll(archetypeThemeDir, 0o755), qt.IsNil)
-
- contentFile := `
-File: %s
-Site Lang: {{ .Site.Language.Lang }}
-Name: {{ replace .Name "-" " " | title }}
-i18n: {{ T "hugo" }}
-`
-
- c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "index.md"), []byte(fmt.Sprintf(contentFile, "index.md")), 0o755), qt.IsNil)
- c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "index.nn.md"), []byte(fmt.Sprintf(contentFile, "index.nn.md")), 0o755), qt.IsNil)
-
- c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "pages", "bio.md"), []byte(fmt.Sprintf(contentFile, "bio.md")), 0o755), qt.IsNil)
- c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "resources", "hugo1.json"), []byte(`hugo1: {{ printf "no template handling in here" }}`), 0o755), qt.IsNil)
- c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "resources", "hugo2.xml"), []byte(`hugo2: {{ printf "no template handling in here" }}`), 0o755), qt.IsNil)
-
- c.Assert(afero.WriteFile(mm, filepath.Join(archetypeThemeDir, "index.md"), []byte(fmt.Sprintf(contentFile, "index.md")), 0o755), qt.IsNil)
- c.Assert(afero.WriteFile(mm, filepath.Join(archetypeThemeDir, "resources", "hugo1.json"), []byte(`hugo1: {{ printf "no template handling in here" }}`), 0o755), qt.IsNil)
-
- c.Assert(initFs(mm), qt.IsNil)
- cfg, fs := newTestCfg(c, mm)
-
- h, err := hugolib.NewHugoSites(deps.DepsCfg{Cfg: cfg, Fs: fs})
- c.Assert(err, qt.IsNil)
- c.Assert(len(h.Sites), qt.Equals, 2)
-
- c.Assert(create.NewContent(h, "my-bundle", "post/my-post"), qt.IsNil)
-
- cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/resources/hugo1.json")), `hugo1: {{ printf "no template handling in here" }}`)
- cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/resources/hugo2.xml")), `hugo2: {{ printf "no template handling in here" }}`)
-
- // Content files should get the correct site context.
- // TODO(bep) archetype check i18n
- cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/index.md")), `File: index.md`, `Site Lang: en`, `Name: My Post`, `i18n: Hugo Rocks!`)
- cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/index.nn.md")), `File: index.nn.md`, `Site Lang: nn`, `Name: My Post`, `i18n: Hugo Rokkar!`)
-
- cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/pages/bio.md")), `File: bio.md`, `Site Lang: en`, `Name: Bio`)
-
- c.Assert(create.NewContent(h, "my-theme-bundle", "post/my-theme-post"), qt.IsNil)
- cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-theme-post/index.md")), `File: index.md`, `Site Lang: en`, `Name: My Theme Post`, `i18n: Hugo Rocks!`)
- cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-theme-post/resources/hugo1.json")), `hugo1: {{ printf "no template handling in here" }}`)
-}
-
func TestNewContentFromDirSiteFunction(t *testing.T) {
mm := afero.NewMemMapFs()
c := qt.New(t)
@@ -177,79 +128,35 @@ site RegularPages: {{ len site.RegularPages }}
`
- c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "index.md"), []byte(fmt.Sprintf(contentFile, "index.md")), 0o755), qt.IsNil)
+ c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "index.md"), fmt.Appendf(nil, contentFile, "index.md"), 0o755), qt.IsNil)
c.Assert(afero.WriteFile(mm, filepath.Join(defaultArchetypeDir, "index.md"), []byte("default archetype index.md"), 0o755), qt.IsNil)
c.Assert(initFs(mm), qt.IsNil)
cfg, fs := newTestCfg(c, mm)
- h, err := hugolib.NewHugoSites(deps.DepsCfg{Cfg: cfg, Fs: fs})
- c.Assert(err, qt.IsNil)
- c.Assert(len(h.Sites), qt.Equals, 2)
+ conf := testconfig.GetTestConfigs(fs.Source, cfg)
- c.Assert(create.NewContent(h, "my-bundle", "post/my-post"), qt.IsNil)
+ h := func() *hugolib.HugoSites {
+ h, err := hugolib.NewHugoSites(deps.DepsCfg{Configs: conf, Fs: fs})
+ c.Assert(err, qt.IsNil)
+ c.Assert(len(h.Sites), qt.Equals, 2)
+ return h
+ }
+
+ c.Assert(create.NewContent(h(), "my-bundle", "post/my-post", false), qt.IsNil)
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/index.md")), `site RegularPages: 10`)
// Default bundle archetype
- c.Assert(create.NewContent(h, "", "post/my-post2"), qt.IsNil)
+ c.Assert(create.NewContent(h(), "", "post/my-post2", false), qt.IsNil)
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post2/index.md")), `default archetype index.md`)
// Regular file with bundle kind.
- c.Assert(create.NewContent(h, "my-bundle", "post/foo.md"), qt.IsNil)
+ c.Assert(create.NewContent(h(), "my-bundle", "post/foo.md", false), qt.IsNil)
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/foo.md")), `draft: true`)
// Regular files should fall back to the default archetype (we have no regular file archetype).
- c.Assert(create.NewContent(h, "my-bundle", "mypage.md"), qt.IsNil)
+ c.Assert(create.NewContent(h(), "my-bundle", "mypage.md", false), qt.IsNil)
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "mypage.md")), `draft: true`)
-
-}
-
-func TestNewContentFromDirNoSite(t *testing.T) {
- mm := afero.NewMemMapFs()
- c := qt.New(t)
-
- archetypeDir := filepath.Join("archetypes", "my-bundle")
- c.Assert(mm.MkdirAll(archetypeDir, 0o755), qt.IsNil)
-
- archetypeThemeDir := filepath.Join("themes", "mytheme", "archetypes", "my-theme-bundle")
- c.Assert(mm.MkdirAll(archetypeThemeDir, 0o755), qt.IsNil)
-
- contentFile := `
-File: %s
-Name: {{ replace .Name "-" " " | title }}
-i18n: {{ T "hugo" }}
-`
-
- c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "index.md"), []byte(fmt.Sprintf(contentFile, "index.md")), 0o755), qt.IsNil)
- c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "index.nn.md"), []byte(fmt.Sprintf(contentFile, "index.nn.md")), 0o755), qt.IsNil)
-
- c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "pages", "bio.md"), []byte(fmt.Sprintf(contentFile, "bio.md")), 0o755), qt.IsNil)
- c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "resources", "hugo1.json"), []byte(`hugo1: {{ printf "no template handling in here" }}`), 0o755), qt.IsNil)
- c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "resources", "hugo2.xml"), []byte(`hugo2: {{ printf "no template handling in here" }}`), 0o755), qt.IsNil)
-
- c.Assert(afero.WriteFile(mm, filepath.Join(archetypeThemeDir, "index.md"), []byte(fmt.Sprintf(contentFile, "index.md")), 0o755), qt.IsNil)
- c.Assert(afero.WriteFile(mm, filepath.Join(archetypeThemeDir, "resources", "hugo1.json"), []byte(`hugo1: {{ printf "no template handling in here" }}`), 0o755), qt.IsNil)
-
- c.Assert(initFs(mm), qt.IsNil)
- cfg, fs := newTestCfg(c, mm)
-
- h, err := hugolib.NewHugoSites(deps.DepsCfg{Cfg: cfg, Fs: fs})
- c.Assert(err, qt.IsNil)
- c.Assert(len(h.Sites), qt.Equals, 2)
-
- c.Assert(create.NewContent(h, "my-bundle", "post/my-post"), qt.IsNil)
-
- cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/resources/hugo1.json")), `hugo1: {{ printf "no template handling in here" }}`)
- cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/resources/hugo2.xml")), `hugo2: {{ printf "no template handling in here" }}`)
-
- cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/index.md")), `File: index.md`, `Name: My Post`, `i18n: Hugo Rocks!`)
- cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/index.nn.md")), `File: index.nn.md`, `Name: My Post`, `i18n: Hugo Rokkar!`)
-
- cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/pages/bio.md")), `File: bio.md`, `Name: Bio`)
-
- c.Assert(create.NewContent(h, "my-theme-bundle", "post/my-theme-post"), qt.IsNil)
- cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-theme-post/index.md")), `File: index.md`, `Name: My Theme Post`, `i18n: Hugo Rocks!`)
- cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-theme-post/resources/hugo1.json")), `hugo1: {{ printf "no template handling in here" }}`)
}
func initFs(fs afero.Fs) error {
@@ -275,7 +182,7 @@ func initFs(fs afero.Fs) error {
afero.WriteFile(fs, filename, []byte(`---
title: Test
---
-`), 0666)
+`), 0o666)
}
// create archetype files
@@ -368,7 +275,7 @@ Some text.
return nil
}
-func cContains(c *qt.C, v interface{}, matches ...string) {
+func cContains(c *qt.C, v any, matches ...string) {
for _, m := range matches {
c.Assert(v, qt.Contains, m)
}
@@ -434,8 +341,8 @@ other = "Hugo Rokkar!"`), 0o755), qt.IsNil)
c.Assert(afero.WriteFile(mm, "config.toml", []byte(cfg), 0o755), qt.IsNil)
- v, _, err := hugolib.LoadConfig(hugolib.ConfigSourceDescriptor{Fs: mm, Filename: "config.toml"})
+ res, err := allconfig.LoadConfig(allconfig.ConfigSourceDescriptor{Fs: mm, Filename: "config.toml"})
c.Assert(err, qt.IsNil)
- return v, hugofs.NewFrom(mm, v)
+ return res.LoadingInfo.Cfg, hugofs.NewFrom(mm, res.LoadingInfo.BaseConfig)
}
diff --git a/docs/_vendor/github.com/gohugoio/gohugoioTheme/assets/js/filesaver.js b/create/skeletons/project/assets/.gitkeep
similarity index 100%
rename from docs/_vendor/github.com/gohugoio/gohugoioTheme/assets/js/filesaver.js
rename to create/skeletons/project/assets/.gitkeep
diff --git a/docs/_vendor/github.com/gohugoio/gohugoioTheme/layouts/partials/svg/exclamation.svg b/create/skeletons/project/content/.gitkeep
similarity index 100%
rename from docs/_vendor/github.com/gohugoio/gohugoioTheme/layouts/partials/svg/exclamation.svg
rename to create/skeletons/project/content/.gitkeep
diff --git a/create/skeletons/project/data/.gitkeep b/create/skeletons/project/data/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/create/skeletons/project/i18n/.gitkeep b/create/skeletons/project/i18n/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/create/skeletons/project/layouts/.gitkeep b/create/skeletons/project/layouts/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/create/skeletons/project/static/.gitkeep b/create/skeletons/project/static/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/create/skeletons/project/themes/.gitkeep b/create/skeletons/project/themes/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/create/skeletons/skeletons.go b/create/skeletons/skeletons.go
new file mode 100644
index 00000000000..835f305825d
--- /dev/null
+++ b/create/skeletons/skeletons.go
@@ -0,0 +1,182 @@
+// 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 skeletons
+
+import (
+ "bytes"
+ "embed"
+ "errors"
+ "io/fs"
+ "path/filepath"
+ "strings"
+
+ "github.com/gohugoio/hugo/helpers"
+ "github.com/gohugoio/hugo/parser"
+ "github.com/gohugoio/hugo/parser/metadecoders"
+ "github.com/spf13/afero"
+)
+
+//go:embed all:project/*
+var projectFs embed.FS
+
+//go:embed all:theme/*
+var themeFs embed.FS
+
+// CreateTheme creates a theme skeleton.
+func CreateTheme(createpath string, sourceFs afero.Fs, format string) error {
+ if exists, _ := helpers.Exists(createpath, sourceFs); exists {
+ return errors.New(createpath + " already exists")
+ }
+
+ format = strings.ToLower(format)
+
+ projectConfig := map[string]any{
+ "baseURL": "https://example.org/",
+ "locale": "en-US",
+ "title": "My New Hugo Project",
+ "menus": map[string]any{
+ "main": []any{
+ map[string]any{
+ "name": "Home",
+ "pageRef": "/",
+ "weight": 10,
+ },
+ map[string]any{
+ "name": "Posts",
+ "pageRef": "/posts",
+ "weight": 20,
+ },
+ map[string]any{
+ "name": "Tags",
+ "pageRef": "/tags",
+ "weight": 30,
+ },
+ },
+ },
+ "module": map[string]any{
+ "hugoVersion": map[string]any{
+ "extended": false,
+ "min": "0.146.0",
+ },
+ },
+ }
+
+ err := createProjectConfig(sourceFs, createpath, projectConfig, format)
+ if err != nil {
+ return err
+ }
+
+ defaultArchetype := map[string]any{
+ "title": "{{ replace .File.ContentBaseName \"-\" \" \" | title }}",
+ "date": "{{ .Date }}",
+ "draft": true,
+ }
+
+ err = createDefaultArchetype(sourceFs, createpath, defaultArchetype, format)
+ if err != nil {
+ return err
+ }
+
+ return copyFiles(createpath, sourceFs, themeFs)
+}
+
+// CreateProject creates a project skeleton.
+func CreateProject(createpath string, sourceFs afero.Fs, force bool, format string) error {
+ format = strings.ToLower(format)
+ if exists, _ := helpers.Exists(createpath, sourceFs); exists {
+ if isDir, _ := helpers.IsDir(createpath, sourceFs); !isDir {
+ return errors.New(createpath + " already exists but not a directory")
+ }
+
+ isEmpty, _ := helpers.IsEmpty(createpath, sourceFs)
+
+ switch {
+ case !isEmpty && !force:
+ return errors.New(createpath + " already exists and is not empty. See --force.")
+ case !isEmpty && force:
+ var all []string
+ fs.WalkDir(projectFs, ".", func(path string, d fs.DirEntry, err error) error {
+ if d.IsDir() && path != "." {
+ all = append(all, path)
+ }
+ return nil
+ })
+ all = append(all, filepath.Join(createpath, "hugo."+format))
+ for _, path := range all {
+ if exists, _ := helpers.Exists(path, sourceFs); exists {
+ return errors.New(path + " already exists")
+ }
+ }
+ }
+ }
+
+ projectConfig := map[string]any{
+ "baseURL": "https://example.org/",
+ "locale": "en-us",
+ "title": "My New Hugo Project",
+ }
+
+ err := createProjectConfig(sourceFs, createpath, projectConfig, format)
+ if err != nil {
+ return err
+ }
+
+ defaultArchetype := map[string]any{
+ "title": "{{ replace .File.ContentBaseName \"-\" \" \" | title }}",
+ "date": "{{ .Date }}",
+ "draft": true,
+ }
+
+ err = createDefaultArchetype(sourceFs, createpath, defaultArchetype, format)
+ if err != nil {
+ return err
+ }
+
+ return copyFiles(createpath, sourceFs, projectFs)
+}
+
+func copyFiles(createpath string, sourceFs afero.Fs, skeleton embed.FS) error {
+ return fs.WalkDir(skeleton, ".", func(path string, d fs.DirEntry, err error) error {
+ _, slug, _ := strings.Cut(path, "/")
+ if d.IsDir() {
+ return sourceFs.MkdirAll(filepath.Join(createpath, slug), 0o777)
+ } else {
+ if filepath.Base(path) != ".gitkeep" {
+ data, _ := fs.ReadFile(skeleton, path)
+ return helpers.WriteToDisk(filepath.Join(createpath, slug), bytes.NewReader(data), sourceFs)
+ }
+ return nil
+ }
+ })
+}
+
+func createProjectConfig(fs afero.Fs, createpath string, in map[string]any, format string) (err error) {
+ var buf bytes.Buffer
+ err = parser.InterfaceToConfig(in, metadecoders.FormatFromString(format), &buf)
+ if err != nil {
+ return err
+ }
+
+ return helpers.WriteToDisk(filepath.Join(createpath, "hugo."+format), &buf, fs)
+}
+
+func createDefaultArchetype(fs afero.Fs, createpath string, in map[string]any, format string) (err error) {
+ 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)
+}
diff --git a/create/skeletons/theme/assets/css/components/footer.css b/create/skeletons/theme/assets/css/components/footer.css
new file mode 100644
index 00000000000..abe2b5a6e7d
--- /dev/null
+++ b/create/skeletons/theme/assets/css/components/footer.css
@@ -0,0 +1,4 @@
+footer {
+ border-top: 1px solid #222;
+ margin-top: 1rem;
+}
diff --git a/create/skeletons/theme/assets/css/components/header.css b/create/skeletons/theme/assets/css/components/header.css
new file mode 100644
index 00000000000..8efea1e8c92
--- /dev/null
+++ b/create/skeletons/theme/assets/css/components/header.css
@@ -0,0 +1,4 @@
+header {
+ border-bottom: 1px solid #222;
+ margin-bottom: 1rem;
+}
diff --git a/create/skeletons/theme/assets/css/main.css b/create/skeletons/theme/assets/css/main.css
new file mode 100644
index 00000000000..6c0b6609068
--- /dev/null
+++ b/create/skeletons/theme/assets/css/main.css
@@ -0,0 +1,15 @@
+@import "components/header.css";
+@import "components/footer.css";
+
+body {
+ color: #222;
+ font-family: sans-serif;
+ line-height: 1.5;
+ margin: 1rem;
+ max-width: 768px;
+}
+
+a {
+ color: #00e;
+ text-decoration: none;
+}
diff --git a/create/skeletons/theme/assets/js/main.js b/create/skeletons/theme/assets/js/main.js
new file mode 100644
index 00000000000..e2aac527510
--- /dev/null
+++ b/create/skeletons/theme/assets/js/main.js
@@ -0,0 +1 @@
+console.log('This site was generated by Hugo.');
diff --git a/create/skeletons/theme/content/_index.md b/create/skeletons/theme/content/_index.md
new file mode 100644
index 00000000000..652623b5730
--- /dev/null
+++ b/create/skeletons/theme/content/_index.md
@@ -0,0 +1,9 @@
++++
+title = 'Home'
+date = 2023-01-01T08:00:00-07:00
+draft = false
++++
+
+Laborum voluptate pariatur ex culpa magna nostrud est incididunt fugiat
+pariatur do dolor ipsum enim. Consequat tempor do dolor eu. Non id id anim anim
+excepteur excepteur pariatur nostrud qui irure ullamco.
diff --git a/create/skeletons/theme/content/posts/_index.md b/create/skeletons/theme/content/posts/_index.md
new file mode 100644
index 00000000000..e7066c092ba
--- /dev/null
+++ b/create/skeletons/theme/content/posts/_index.md
@@ -0,0 +1,7 @@
++++
+title = 'Posts'
+date = 2023-01-01T08:30:00-07:00
+draft = false
++++
+
+Tempor est exercitation ad qui pariatur quis adipisicing aliquip nisi ea consequat ipsum occaecat. Nostrud consequat ullamco laboris fugiat esse esse adipisicing velit laborum ipsum incididunt ut enim. Dolor pariatur nulla quis fugiat dolore excepteur. Aliquip ad quis aliqua enim do consequat.
diff --git a/create/skeletons/theme/content/posts/post-1.md b/create/skeletons/theme/content/posts/post-1.md
new file mode 100644
index 00000000000..3e3fc6b2592
--- /dev/null
+++ b/create/skeletons/theme/content/posts/post-1.md
@@ -0,0 +1,10 @@
++++
+title = 'Post 1'
+date = 2023-01-15T09:00:00-07:00
+draft = false
+tags = ['red']
++++
+
+Tempor proident minim aliquip reprehenderit dolor et ad anim Lorem duis sint eiusmod. Labore ut ea duis dolor. Incididunt consectetur proident qui occaecat incididunt do nisi Lorem. Tempor do laborum elit laboris excepteur eiusmod do. Eiusmod nisi excepteur ut amet pariatur adipisicing Lorem.
+
+Occaecat nulla excepteur dolore excepteur duis eiusmod ullamco officia anim in voluptate ea occaecat officia. Cillum sint esse velit ea officia minim fugiat. Elit ea esse id aliquip pariatur cupidatat id duis minim incididunt ea ea. Anim ut duis sunt nisi. Culpa cillum sit voluptate voluptate eiusmod dolor. Enim nisi Lorem ipsum irure est excepteur voluptate eu in enim nisi. Nostrud ipsum Lorem anim sint labore consequat do.
diff --git a/create/skeletons/theme/content/posts/post-2.md b/create/skeletons/theme/content/posts/post-2.md
new file mode 100644
index 00000000000..22b8287699e
--- /dev/null
+++ b/create/skeletons/theme/content/posts/post-2.md
@@ -0,0 +1,10 @@
++++
+title = 'Post 2'
+date = 2023-02-15T10:00:00-07:00
+draft = false
+tags = ['red','green']
++++
+
+Anim eiusmod irure incididunt sint cupidatat. Incididunt irure irure irure nisi ipsum do ut quis fugiat consectetur proident cupidatat incididunt cillum. Dolore voluptate occaecat qui mollit laborum ullamco et. Ipsum laboris officia anim laboris culpa eiusmod ex magna ex cupidatat anim ipsum aute. Mollit aliquip occaecat qui sunt velit ut cupidatat reprehenderit enim sunt laborum. Velit veniam in officia nulla adipisicing ut duis officia.
+
+Exercitation voluptate irure in irure tempor mollit Lorem nostrud ad officia. Velit id fugiat occaecat do tempor. Sit officia Lorem aliquip eu deserunt consectetur. Aute proident deserunt in nulla aliquip dolore ipsum Lorem ut cupidatat consectetur sit sint laborum. Esse cupidatat sit sint sunt tempor exercitation deserunt. Labore dolor duis laborum est do nisi ut veniam dolor et nostrud nostrud.
diff --git a/create/skeletons/theme/content/posts/post-3/bryce-canyon.jpg b/create/skeletons/theme/content/posts/post-3/bryce-canyon.jpg
new file mode 100644
index 00000000000..9a923bea0d1
Binary files /dev/null and b/create/skeletons/theme/content/posts/post-3/bryce-canyon.jpg differ
diff --git a/create/skeletons/theme/content/posts/post-3/index.md b/create/skeletons/theme/content/posts/post-3/index.md
new file mode 100644
index 00000000000..ca42a664b0a
--- /dev/null
+++ b/create/skeletons/theme/content/posts/post-3/index.md
@@ -0,0 +1,12 @@
++++
+title = 'Post 3'
+date = 2023-03-15T11:00:00-07:00
+draft = false
+tags = ['red','green','blue']
++++
+
+Occaecat aliqua consequat laborum ut ex aute aliqua culpa quis irure esse magna dolore quis. Proident fugiat labore eu laboris officia Lorem enim. Ipsum occaecat cillum ut tempor id sint aliqua incididunt nisi incididunt reprehenderit. Voluptate ad minim sint est aute aliquip esse occaecat tempor officia qui sunt. Aute ex ipsum id ut in est velit est laborum incididunt. Aliqua qui id do esse sunt eiusmod id deserunt eu nostrud aute sit ipsum. Deserunt esse cillum Lorem non magna adipisicing mollit amet consequat.
+
+
+
+Sit excepteur do velit veniam mollit in nostrud laboris incididunt ea. Amet eu cillum ut reprehenderit culpa aliquip labore laborum amet sit sit duis. Laborum id proident nostrud dolore laborum reprehenderit quis mollit nulla amet veniam officia id id. Aliquip in deserunt qui magna duis qui pariatur officia sunt deserunt.
diff --git a/create/skeletons/theme/data/.gitkeep b/create/skeletons/theme/data/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/create/skeletons/theme/i18n/.gitkeep b/create/skeletons/theme/i18n/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/create/skeletons/theme/layouts/_partials/footer.html b/create/skeletons/theme/layouts/_partials/footer.html
new file mode 100644
index 00000000000..a7cd916d054
--- /dev/null
+++ b/create/skeletons/theme/layouts/_partials/footer.html
@@ -0,0 +1 @@
+
Copyright {{ now.Year }}. All rights reserved.
diff --git a/create/skeletons/theme/layouts/_partials/head.html b/create/skeletons/theme/layouts/_partials/head.html
new file mode 100644
index 00000000000..02c22401895
--- /dev/null
+++ b/create/skeletons/theme/layouts/_partials/head.html
@@ -0,0 +1,5 @@
+
+
+{{ if .IsHome }}{{ site.Title }}{{ else }}{{ printf "%s | %s" .Title site.Title }}{{ end }}
+{{ partialCached "head/css.html" . }}
+{{ partialCached "head/js.html" . }}
diff --git a/create/skeletons/theme/layouts/_partials/head/css.html b/create/skeletons/theme/layouts/_partials/head/css.html
new file mode 100644
index 00000000000..889786684b1
--- /dev/null
+++ b/create/skeletons/theme/layouts/_partials/head/css.html
@@ -0,0 +1,15 @@
+{{- with resources.Get "css/main.css" }}
+ {{- $opts := dict
+ "minify" (cond hugo.IsDevelopment false true)
+ "sourceMap" (cond hugo.IsDevelopment "linked" "none")
+ }}
+ {{- with . | css.Build $opts }}
+ {{- if hugo.IsDevelopment }}
+
+ {{- else }}
+ {{- with . | fingerprint }}
+
+ {{- end }}
+ {{- end }}
+ {{- end }}
+{{- end }}
diff --git a/create/skeletons/theme/layouts/_partials/head/js.html b/create/skeletons/theme/layouts/_partials/head/js.html
new file mode 100644
index 00000000000..0210efa8b42
--- /dev/null
+++ b/create/skeletons/theme/layouts/_partials/head/js.html
@@ -0,0 +1,15 @@
+{{- with resources.Get "js/main.js" }}
+ {{- $opts := dict
+ "minify" (cond hugo.IsDevelopment false true)
+ "sourceMap" (cond hugo.IsDevelopment "linked" "none")
+ }}
+ {{- with . | js.Build $opts }}
+ {{- if hugo.IsDevelopment }}
+
+ {{- else }}
+ {{- with . | fingerprint }}
+
+ {{- end }}
+ {{- end }}
+ {{- end }}
+{{- end }}
diff --git a/create/skeletons/theme/layouts/_partials/header.html b/create/skeletons/theme/layouts/_partials/header.html
new file mode 100644
index 00000000000..7980a00e1e2
--- /dev/null
+++ b/create/skeletons/theme/layouts/_partials/header.html
@@ -0,0 +1,2 @@
+
{{ site.Title }}
+{{ partial "menu.html" (dict "menuID" "main" "page" .) }}
diff --git a/create/skeletons/theme/layouts/_partials/menu.html b/create/skeletons/theme/layouts/_partials/menu.html
new file mode 100644
index 00000000000..14245b55dba
--- /dev/null
+++ b/create/skeletons/theme/layouts/_partials/menu.html
@@ -0,0 +1,51 @@
+{{- /*
+Renders a menu for the given menu ID.
+
+@context {page} page The current page.
+@context {string} menuID The menu ID.
+
+@example: {{ partial "menu.html" (dict "menuID" "main" "page" .) }}
+*/}}
+
+{{- $page := .page }}
+{{- $menuID := .menuID }}
+
+{{- with index site.Menus $menuID }}
+
+{{- end }}
+
+{{- define "_partials/inline/menu/walk.html" }}
+ {{- $page := .page }}
+ {{- range .menuEntries }}
+ {{- $attrs := dict "href" .URL }}
+ {{- if $page.IsMenuCurrent .Menu . }}
+ {{- $attrs = merge $attrs (dict "class" "active" "aria-current" "page") }}
+ {{- else if $page.HasMenuCurrent .Menu .}}
+ {{- $attrs = merge $attrs (dict "class" "ancestor" "aria-current" "true") }}
+ {{- end }}
+ {{- $name := .Name }}
+ {{- with .Identifier }}
+ {{- with T . }}
+ {{- $name = . }}
+ {{- end }}
+ {{- end }}
+
diff --git a/docs/_vendor/github.com/gohugoio/gohugoioTheme/layouts/partials/previous-next-links-in-section-with-title.html b/docs/_vendor/github.com/gohugoio/gohugoioTheme/layouts/partials/previous-next-links-in-section-with-title.html
deleted file mode 100644
index 71a14c0efab..00000000000
--- a/docs/_vendor/github.com/gohugoio/gohugoioTheme/layouts/partials/previous-next-links-in-section-with-title.html
+++ /dev/null
@@ -1,14 +0,0 @@
-{{ if or .PrevInSection .NextInSection }}
-{{/* this div holds these a tags as a unit for flex-box display */}}
-
-{{ end }}
diff --git a/docs/_vendor/github.com/gohugoio/gohugoioTheme/layouts/partials/previous-next-links-in-section.html b/docs/_vendor/github.com/gohugoio/gohugoioTheme/layouts/partials/previous-next-links-in-section.html
deleted file mode 100644
index af9f4aac10d..00000000000
--- a/docs/_vendor/github.com/gohugoio/gohugoioTheme/layouts/partials/previous-next-links-in-section.html
+++ /dev/null
@@ -1,16 +0,0 @@
-{{ if or .PrevInSection .NextInSection }}
-{{/* this div holds these a tags as a unit for flex-box display */}}
-
- {{ if ne ($.Get "copy") "false" }}
-
- {{/* Functionality located within filesaver.js The copy here is located in the css with .copy class so it can be replaced with JS on success */}}
- {{end}}
- {{ end }}
-
- {{- end -}}
-
- {{ if ne (.Get "copy") "false" }}
-
- {{/* Functionality located within filesaver.js The copy here is located in the css with .copy class so it can be replaced with JS on success */}}
- {{end}}
-
+{{ end }}
+```
-lang
-: A Glob pattern matching the Page's language, e.g. "{en,sv}".
+[`GetTerms`]: /methods/page/getterms/
-Any of the above can be omitted.
+## Cascade
-### Example
+A [node](g) can cascade front matter values to its descendants. However, this cascading will be prevented if the descendant already defines the field, or if a closer ancestor node has already cascaded a value for that same field.
-In `content/blog/_index.md`
+For example, to cascade a "color" parameter from the home page to all its descendants:
-{{< code-toggle copy="false" >}}
-title: Blog
-cascade:
- banner: images/typewriter.jpg
-{{ code-toggle >}}
+{{< code-toggle file=content/_index.md fm=true >}}
+title = 'Home'
+[cascade.params]
+color = 'red'
+{{< /code-toggle >}}
-With the above example the Blog section page and its descendants will return `images/typewriter.jpg` when `.Params.banner` is invoked unless:
+{{< new-in 0.153.0 />}}
+From Hugo 0.153.0, you can also set the [sites](#sites) front matter as cascade front matter values, which means that you can e.g. apply one or more languages to the `target` pages.
-- Said descendant has its own `banner` value set
-- Or a closer ancestor node has its own `cascade.banner` value set.
+### Target
+
+The `target`[^1] keyword allows you to target specific pages or [environments](g). For example, to cascade a "color" parameter from the home page only to pages within the "articles" section, including the "articles" section page itself:
-## Order Content Through Front Matter
+[^1]: The `_target` alias for `target` is deprecated and will be removed in a future release.
-You can assign content-specific `weight` in the front matter of your content. These values are especially useful for [ordering][ordering] in list views. You can use `weight` for ordering of content and the convention of [`_weight`][taxweight] for ordering content within a taxonomy. See [Ordering and Grouping Hugo Lists][lists] to see how `weight` can be used to organize your content in list views.
+{{< code-toggle file=content/_index.md fm=true >}}
+title = 'Home'
+[cascade.params]
+color = 'red'
+[cascade.target]
+path = '{/articles,/articles/**}'
+[cascade.target.sites.matrix]
+languages = ['en','fr']
+{{< /code-toggle >}}
-## Override Global Markdown Configuration
+Use any combination of these keywords to target pages and/or environments:
-It's possible to set some options for Markdown rendering in a content's front matter as an override to the [BlackFriday rendering options set in your project configuration][config].
+environment
+: (`string`) A [glob pattern](g) matching the build [environment](g). For example: `{staging,production}`.
-## Front Matter Format Specs
+kind
+: (`string`) A [glob pattern](g) matching the [page kind](g). For example: `{taxonomy,term}`.
+
+path
+: (`string`) A [glob pattern](g) matching the page's [logical path](g). For example: `{/books,/books/**}`.
+
+sites
+: {{< new-in 0.153.0 />}}
+: (`map`) A map to define [sites matrix](g) for the target, as in: Which sites should receive the cascaded values.
+
+### Array
-* [TOML Spec][toml]
-* [YAML Spec][yaml]
-* [JSON Spec][json]
+Define an array of cascade parameters to apply different values to different targets. For example:
+
+{{< code-toggle file=content/_index.md fm=true >}}
+title = 'Home'
+[[cascade]]
+[cascade.params]
+color = 'red'
+[cascade.target]
+path = '{/books/**}'
+kind = 'page'
+[[cascade]]
+[cascade.params]
+color = 'blue'
+[cascade.target]
+path = '{/films/**}'
+kind = 'page'
+{{< /code-toggle >}}
-[variables]: /variables/
+> [!note]
+> For multilingual projects, defining cascade values in your project configuration is often more efficient. This avoids repeating the same cascade values on the home, section, taxonomy, or term page for each language. See [details](/configuration/cascade/).
+>
+> If you choose to define cascade values in front matter for a multilingual project, you must create a corresponding home, section, taxonomy, or term page for every language.
+
+## Emacs Org Mode
+
+If your [content format] is [Emacs Org Mode], you may provide front matter using Org Mode keywords. For example:
+
+```text {file="content/example.org"}
+#+TITLE: Example
+#+DATE: 2024-02-02T04:14:54-08:00
+#+DRAFT: false
+#+AUTHOR: John Smith
+#+GENRES: mystery
+#+GENRES: romance
+#+TAGS: red
+#+TAGS: blue
+#+WEIGHT: 10
+```
+
+Note that you can also specify array elements on a single line:
+
+```text {file="content/example.org"}
+#+TAGS[]: red blue
+```
+
+[content format]: /content-management/formats/
+[emacs org mode]: https://orgmode.org/
+
+## Dates
+
+When populating a date field, whether a [custom page parameter](#parameters) or one of the four predefined fields ([`date`](#date), [`expiryDate`](#expirydate), [`lastmod`](#lastmod), [`publishDate`](#publishdate)), use one of these parsable formats:
+
+{{% include "/_common/parsable-date-time-strings.md" %}}
+
+To override the default time zone, set the [`timeZone`](/configuration/all/#timezone) in your project configuration. The order of precedence for determining the time zone is:
+
+1. The time zone offset in the date/time string
+1. The time zone specified in your project configuration
+1. The `Etc/UTC` time zone
+
+[`aliases`]: /methods/page/aliases/
+[`date`]: /methods/page/date/
+[`description`]: /methods/page/description/
+[`draft`]: /methods/page/draft/
+[`expirydate`]: /methods/page/expirydate/
+[`fuzzywordcount`]: /methods/page/wordcount/
+[`keywords`]: /methods/page/keywords/
+[`lastmod`]: /methods/page/date/
+[`layout`]: /methods/page/layout/
+[`linktitle`]: /methods/page/linktitle/
+[`opengraph.html`]: <{{% eturl opengraph %}}>
+[`Param`]: /methods/page/param/
+[`Params`]: /methods/page/params/
+[`publishdate`]: /methods/page/publishdate/
+[`readingtime`]: /methods/page/readingtime/
+[`schema.html`]: <{{% eturl schema %}}>
+[`sitemap`]: /methods/page/sitemap/
+[`slug`]: /methods/page/slug/
+[`Summary`]: /methods/page/summary/
+[`title`]: /methods/page/title/
+[`translationkey`]: /methods/page/translationkey/
+[`twitter_cards.html`]: <{{% eturl twitter_cards %}}>
+[`type`]: /methods/page/type/
+[`weight`]: /methods/page/weight/
+[`wordcount`]: /methods/page/wordcount/
[aliases]: /content-management/urls/#aliases
-[archetype]: /content-management/archetypes/
-[bylinktitle]: /templates/lists/#by-link-title
-[config]: /getting-started/configuration/ "Hugo documentation for site configuration"
-[content type]: /content-management/types/
-[contentorg]: /content-management/organization/
-[definetype]: /content-management/types/#defining-a-content-type "Learn how to specify a type and a layout in a content's front matter"
-[headless-bundle]: /content-management/page-bundles/#headless-bundle
-[json]: https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf "Specification for JSON, JavaScript Object Notation"
-[lists]: /templates/lists/#ordering-content "See how to order content in list pages; for example, templates that look to specific _index.md for content and front matter."
-[lookup]: /templates/lookup-order/ "Hugo traverses your templates in a specific order when rendering content to allow for DRYer templating."
-[ordering]: /templates/lists/ "Hugo provides multiple ways to sort and order your content in list templates"
-[outputs]: /templates/output-formats/ "With the release of v22, you can output your content to any text format using Hugo's familiar templating"
-[page-resources]: /content-management/page-resources/
-[pagevars]: /variables/page/
-[section]: /content-management/sections/
-[taxweight]: /content-management/taxonomies/
-[toml]: https://github.com/toml-lang/toml "Specification for TOML, Tom's Obvious Minimal Language"
-[urls]: /content-management/urls/
-[variables]: /variables/
-[yaml]: https://yaml.org/spec/ "Specification for YAML, YAML Ain't Markup Language"
+[build options]: /content-management/build-options/
+[cascade]: #cascade-1
+[configure outputs]: /configuration/outputs/#outputs-per-page
+[content formats]: /content-management/formats/#classification
+[embedded templates]: /templates/embedded/
+[json]: https://www.json.org/
+[leaf bundles]: /content-management/page-bundles/#leaf-bundles
+[menus]: /content-management/menus/#define-in-front-matter
+[output formats]: /configuration/output-formats/
+[page parameters]: #parameters
+[page resources]: /content-management/page-resources/#metadata
+[sitemap templates]: /templates/sitemap/
+[target a specific template]: /templates/lookup-order/#target-a-template
+[template lookup order]: /templates/lookup-order/
+[toml]: https://toml.io/
+[URL management]: /content-management/urls/#slug
+[yaml]: https://yaml.org/
diff --git a/docs/content/en/content-management/image-processing/index.md b/docs/content/en/content-management/image-processing/index.md
index 8f84145ac88..173f3b3d300 100644
--- a/docs/content/en/content-management/image-processing/index.md
+++ b/docs/content/en/content-management/image-processing/index.md
@@ -1,346 +1,169 @@
---
-title: "Image Processing"
-description: "Image Page resources can be resized and cropped."
-date: 2018-01-24T13:10:00-05:00
-linktitle: "Image Processing"
-categories: ["content management"]
-keywords: [resources, images]
-weight: 4004
-draft: false
-toc: true
-menu:
- docs:
- parent: "content-management"
- weight: 32
+title: Image processing
+description: Transform images to change their size, shape, and appearance.
+categories: []
+keywords: []
---
-## The Image Page Resource
+Hugo provides methods to transform and analyze images during the build process. While Hugo can manage any image format as a resource, only [processable images](g) can be transformed using the methods below. The results are cached to ensure subsequent builds remain fast.
-The `image` is a [Page Resource]({{< relref "/content-management/page-resources" >}}), and the processing methods listed below do not work on images inside your `/static` folder.
+> [!note]
+> Use the [`reflect.IsImageResourceProcessable`][] function to verify that an image can be processed.
-To print all images paths in a [Page Bundle]({{< relref "/content-management/organization#page-bundles" >}}):
+## Resources
-```go-html-template
-{{ with .Resources.ByType "image" }}
-{{ range . }}
-{{ .RelPermalink }}
-{{ end }}
-{{ end }}
-
-```
-
-## The Image Resource
-
-The `image` resource can also be retrieved from a [global resource]({{< relref "/hugo-pipes/introduction#from-file-to-resource" >}})
-
-```go-html-template
-{{- $image := resources.Get "images/logo.jpg" -}}
-```
+To process an image you must capture the file as a page resource, a global resource, or a remote resource.
-## Image Processing Methods
+### Page
-The `image` resource implements the `Resize`, `Fit`, `Fill`, and `Filter` methods, each returning a transformed image using the specified dimensions and processing options.
+{{% glossary-term "page resource" %}}
-{{% note %}}
-Metadata (EXIF, IPTC, XMP, etc.) is not preserved during image transformation. Use the [`Exif`](#exif) method with the _original_ image to extract EXIF metadata from JPEG or TIFF images.
-{{% /note %}}
-
-### Resize
-
-Resizes the image to the specified width and height.
-
-```go
-// Resize to a width of 600px and preserve ratio
-{{ $image := $resource.Resize "600x" }}
-
-// Resize to a height of 400px and preserve ratio
-{{ $image := $resource.Resize "x400" }}
-
-// Resize to a width 600px and a height of 400px
-{{ $image := $resource.Resize "600x400" }}
+```text
+content/
+└── posts/
+ └── post-1/ <-- page bundle
+ ├── index.md
+ └── sunset.jpg <-- page resource
```
-### Fit
+To capture an image as a page resource:
-Scale down the image to fit the given dimensions while maintaining aspect ratio. Both height and width are required.
-
-```go
-{{ $image := $resource.Fit "600x400" }}
+```go-html-template
+{{ $image := .Resources.Get "sunset.jpg" }}
```
-### Fill
+### Global
-Resize and crop the image to match the given dimensions. Both height and width are required.
+{{% glossary-term "global resource" %}}
-```go
-{{ $image := $resource.Fill "600x400" }}
+```text
+assets/
+└── images/
+ └── sunset.jpg <-- global resource
```
-### Filter
-
-Apply one or more filters to your image. See [Image Filters](/functions/images/#image-filters) for a full list.
+To capture an image as a global resource:
```go-html-template
-{{ $img = $img.Filter (images.GaussianBlur 6) (images.Pixelate 8) }}
+{{ $image := resources.Get "images/sunset.jpg" }}
```
-The above can also be written in a more functional style using pipes:
-
-```go-html-template
-{{ $img = $img | images.Filter (images.GaussianBlur 6) (images.Pixelate 8) }}
-```
+### Remote
-The filters will be applied in the given order.
+{{% glossary-term "remote resource" %}}
-Sometimes it can be useful to create the filter chain once and then reuse it:
+To capture an image as a remote resource:
```go-html-template
-{{ $filters := slice (images.GaussianBlur 6) (images.Pixelate 8) }}
-{{ $img1 = $img1.Filter $filters }}
-{{ $img2 = $img2.Filter $filters }}
+{{ $image := resources.GetRemote "https://gohugo.io/img/hugo-logo.png" }}
```
-### Exif
+## Rendering
-Provides an [Exif](https://en.wikipedia.org/wiki/Exif) object with metadata about the image.
+Once you have captured an image as a resource, render it in your templates using the [`Permalink`][], [`RelPermalink`][], [`Width`][], and [`Height`][] methods.
-Note that this is only supported for JPEG and TIFF images, so it's recommended to wrap the access with a `with`, e.g.:
+Example 1: Throw an error if the resource is not found.
```go-html-template
-{{ with $img.Exif }}
-Date: {{ .Date }}
-Lat/Long: {{ .Lat}}/{{ .Long }}
-Tags:
-{{ range $k, $v := .Tags }}
-TAG: {{ $k }}: {{ $v }}
-{{ end }}
-{{ end }}
+{{ $image := .Resources.GetMatch "sunset.jpg" }}
+
```
-Or individually access EXIF data with dot access, e.g.:
+Example 2: Skip image rendering if the resource is not found.
```go-html-template
-{{ with $src.Exif }}
-
- {{ with .Date }}
Date: {{ .Format "January 02, 2006" }}
{{ end }}
- {{ with .Tags.ApertureValue }}
Aperture: {{ lang.NumFmt 2 . }}
{{ end }}
- {{ with .Tags.BrightnessValue }}
Brightness: {{ lang.NumFmt 2 . }}
{{ end }}
- {{ with .Tags.ExposureTime }}
Exposure Time: {{ . }}
{{ end }}
- {{ with .Tags.FNumber }}
F Number: {{ . }}
{{ end }}
- {{ with .Tags.FocalLength }}
Focal Length: {{ . }}
{{ end }}
- {{ with .Tags.ISOSpeedRatings }}
ISO Speed Ratings: {{ . }}
{{ end }}
- {{ with .Tags.LensModel }}
Lens Model: {{ . }}
{{ end }}
-
+{{ $image := .Resources.GetMatch "sunset.jpg" }}
+{{ with $image }}
+
{{ end }}
```
-Some fields may need to be formatted with [`lang.FormatNumberCustom`]({{< relref "functions/lang" >}}) function to prevent display like `Aperture: 2.278934289` instead of `Aperture: 2.28`.
-
-#### Exif fields
-
-Date
-: "photo taken" date/time
-
-Lat
-: "photo taken where", GPS latitude
-
-Long
-: "photo taken where", GPS longitude
-
-See [Image Processing Config](#image-processing-config) for how to configure what gets included in Exif.
-
-## Image Processing Options
-
-In addition to the dimensions (e.g. `600x400`), Hugo supports a set of additional image options.
-
-### Background Color
-
-The background color to fill into the transparency layer. This is mostly useful when converting to a format that does not support transparency, e.g. `JPEG`.
-
-You can set the background color to use with a 3 or 6 digit hex code starting with `#`.
-
-```go
-{{ $image.Resize "600x jpg #b31280" }}
-```
-
-For color codes, see https://www.google.com/search?q=color+picker
-
-**Note** that you also set a default background color to use, see [Image Processing Config](#image-processing-config).
-
-### JPEG and WebP Quality
-
-Only relevant for JPEG and WebP images, values 1 to 100 inclusive, higher is better. Default is 75.
-
-```go
-{{ $image.Resize "600x q50" }}
-```
-
-{{< new-in "0.83.0" >}} WebP support was added in Hugo 0.83.0.
-
-### Hint
-
- {{< new-in "0.83.0" >}}
-
- {{< new-in "0.83.0" >}}
-
-Hint about what type of image this is. Currently only used when encoding to WebP.
-
-Default value is `photo`.
-
-Valid values are `picture`, `photo`, `drawing`, `icon`, or `text`.
-
-```go
-{{ $image.Resize "600x webp drawing" }}
-```
-
-### Rotate
-
-Rotates an image by the given angle counter-clockwise. The rotation will be performed first to get the dimensions correct. The main use of this is to be able to manually correct for [EXIF orientation](https://github.com/golang/go/issues/4341) of JPEG images.
-
-```go
-{{ $image.Resize "600x r90" }}
-```
-
-### Anchor
+Example 3: A more concise way to skip image rendering if the resource is not found.
-Only relevant for the `Fill` method. This is useful for thumbnail generation where the main motive is located in, say, the left corner.
-
-Valid values are `Smart`, `Center`, `TopLeft`, `Top`, `TopRight`, `Left`, `Right`, `BottomLeft`, `Bottom`, `BottomRight`.
-
-Default value is `Smart`, which uses [Smartcrop](https://github.com/muesli/smartcrop) to determine the best crop.
-
-```go
-{{ $image.Fill "300x200 BottomLeft" }}
+```go-html-template
+{{ with .Resources.GetMatch "sunset.jpg" }}
+
+{{ end }}
```
-### Resample Filter
+Example 4: Skip rendering if there's problem accessing a remote resource.
-Filter used in resizing. Default is `Box`, a simple and fast resampling filter appropriate for downscaling.
-
-Examples are: `Box`, `NearestNeighbor`, `Linear`, `Gaussian`.
-
-See https://github.com/disintegration/imaging for more. If you want to trade quality for faster processing, this may be a option to test.
-
-```go
-{{ $image.Resize "600x400 Gaussian" }}
+```go-html-template
+{{ $url := "https://gohugo.io/img/hugo-logo.png" }}
+{{ with try (resources.GetRemote $url) }}
+ {{ with .Err }}
+ {{ errorf "%s" . }}
+ {{ else with .Value }}
+
+ {{ else }}
+ {{ errorf "Unable to get remote resource %q" $url }}
+ {{ end }}
+{{ end }}
```
-### Target Format
+{{% include "/_common/functions/reflect/image-reflection-functions.md" %}}
-By default the images is encoded in the source format, but you can set the target format as an option.
+## Processing
-Valid values are `bmp`, `gif`, `jpeg`, `jpg`, `png`, `tif`, `tiff`, and `webp`.
+To transform an image, apply a processing method to the image resource. Hugo generates the processed image on demand, caches the result, and returns a new resource object.
-```go
-{{ $image.Resize "600x jpg" }}
+```go-html-template
+{{ with .Resources.Get "sunset.jpg" }}
+ {{ with .Resize "400x" }}
+
+ {{ end }}
+{{ end }}
```
-{{< new-in "0.83.0" >}} WebP support was added in Hugo 0.83.0.
-
-## Image Processing Examples
-
-_The photo of the sunset used in the examples below is Copyright [Bjørn Erik Pedersen](https://commons.wikimedia.org/wiki/User:Bep) (Creative Commons Attribution-Share Alike 4.0 International license)_
-
-{{< imgproc sunset Resize "300x" />}}
+> [!note]
+> Metadata is not preserved during image transformation. Use the [`Meta`][] method with the original image resource to extract metadata from supported formats.
-{{< imgproc sunset Fill "90x120 left" />}}
+Select a method from the table below for syntax and usage examples, depending on your specific transformation or metadata requirements:
-{{< imgproc sunset Fill "90x120 right" />}}
+{{% render-table-of-pages-in-section
+ path=/methods/resource
+ filter=methods_resource_image_processing
+ filterType=include
+ headingColumn1=Method
+ headingColumn2=Description
+%}}{class="!mt-0"}
-{{< imgproc sunset Fit "90x90" />}}
+## Performance
-{{< imgproc sunset Resize "300x q10" />}}
+### Caching
-This is the shortcode used in the examples above:
+Hugo processes images on demand and returns a new resource object. To ensure subsequent builds remain fast, Hugo caches the results in the directory specified in the [file cache][] section of your project configuration.
-{{< code file="layouts/shortcodes/imgproc.html" >}}
-{{< readfile file="layouts/shortcodes/imgproc.html" >}}
-{{< /code >}}
-
-And it is used like this:
-
-```go-html-template
-{{* imgproc sunset Resize "300x" /*/>}}
-```
-
-{{% note %}}
-**Tip:** Note the self-closing shortcode syntax above. The `imgproc` shortcode can be called both with and without **inner content**.
-{{% /note %}}
-
-## Image Processing Config
-
-You can configure an `imaging` section in `config.toml` with default image processing options:
+If you host your site with Netlify, include the following in your project configuration to persist the image cache between builds:
```toml
-[imaging]
-# Default resample filter used for resizing. Default is Box,
-# a simple and fast averaging filter appropriate for downscaling.
-# See https://github.com/disintegration/imaging
-resampleFilter = "box"
-
-# Default JPEG or WebP quality setting. Default is 75.
-quality = 75
-
-# Default hint about what type of image. Currently only used for WebP encoding.
-# Default is "photo".
-# Valid values are "picture", "photo", "drawing", "icon", or "text".
-hint = "photo"
-
-# Anchor used when cropping pictures.
-# Default is "smart" which does Smart Cropping, using https://github.com/muesli/smartcrop
-# Smart Cropping is content aware and tries to find the best crop for each image.
-# Valid values are Smart, Center, TopLeft, Top, TopRight, Left, Right, BottomLeft, Bottom, BottomRight
-anchor = "smart"
-
-# Default background color.
-# Hugo will preserve transparency for target formats that supports it,
-# but will fall back to this color for JPEG.
-# Expects a standard HEX color string with 3 or 6 digits.
-# See https://www.google.com/search?q=color+picker
-bgColor = "#ffffff"
-
-[imaging.exif]
- # Regexp matching the fields you want to Exclude from the (massive) set of Exif info
-# available. As we cache this info to disk, this is for performance and
-# disk space reasons more than anything.
-# If you want it all, put ".*" in this config setting.
-# Note that if neither this or ExcludeFields is set, Hugo will return a small
-# default set.
-includeFields = ""
-
-# Regexp matching the Exif fields you want to exclude. This may be easier to use
-# than IncludeFields above, depending on what you want.
-excludeFields = ""
-
-# Hugo extracts the "photo taken" date/time into .Date by default.
-# Set this to true to turn it off.
-disableDate = false
-
-# Hugo extracts the "photo taken where" (GPS latitude and longitude) into
-# .Long and .Lat. Set this to true to turn it off.
-disableLatLong = false
+[caches]
+ [caches.images]
+ dir = ':cacheDir/images'
```
-## Smart Cropping of Images
-
-By default, Hugo will use [Smartcrop](https://github.com/muesli/smartcrop), a library created by [muesli](https://github.com/muesli), when cropping images with `.Fill`. You can set the anchor point manually, but in most cases the smart option will make a good choice. And we will work with the library author to improve this in the future.
+### Garbage collection
-An example using the sunset image from above:
+If you change image processing methods, or rename/remove images, the cache will eventually contain unused files. To remove them and reclaim disk space, run Hugo's garbage collection:
-{{< imgproc sunset Fill "200x200 smart" />}}
+```text
+hugo build --gc
+```
-## Image Processing Performance Consideration
+### Resource usage
-Processed images are stored below `/resources` (can be set with `resourceDir` config setting). This folder is deliberately placed in the project, as it is recommended to check these into source control as part of the project. These images are not "Hugo fast" to generate, but once generated they can be reused.
+The time and memory required to process an image increase with the image's dimensions. For example, a `4032x2268` image requires significantly more memory and processing time than a `1920x1080` image.
-If you change your image settings (e.g. size), remove or rename images etc., you will end up with unused images taking up space and cluttering your project.
+If your source images are much larger than the maximum size you intend to publish, consider scaling them down before the build to optimize performance.
-To clean up, run:
+## Configuration
-```bash
-hugo --gc
-```
+See [configure imaging](/configuration/imaging).
-{{% note %}}
-**GC** is short for **Garbage Collection**.
-{{% /note %}}
+[`Height`]: /methods/resource/height/
+[`Meta`]: /methods/resource/meta/
+[`Permalink`]: /methods/resource/permalink/
+[`RelPermalink`]: /methods/resource/relpermalink/
+[`Width`]: /methods/resource/width/
+[`reflect.IsImageResourceProcessable`]: /functions/reflect/isimageresourceprocessable/
+[file cache]: /configuration/caches/
diff --git a/docs/content/en/content-management/markdown-attributes.md b/docs/content/en/content-management/markdown-attributes.md
new file mode 100644
index 00000000000..42e7bcf944f
--- /dev/null
+++ b/docs/content/en/content-management/markdown-attributes.md
@@ -0,0 +1,117 @@
+---
+title: Markdown attributes
+description: Use Markdown attributes to add HTML attributes when rendering Markdown to HTML.
+categories: []
+keywords: []
+---
+
+## Overview
+
+Hugo supports Markdown attributes on images and block elements including blockquotes, fenced code blocks, headings, horizontal rules, lists, paragraphs, and tables.
+
+For example:
+
+```text
+This is a paragraph.
+{class="foo bar" id="baz"}
+```
+
+With `class` and `id` attributes you can also use short-form notation:
+
+```text
+This is a paragraph.
+{.foo .bar #baz}
+```
+
+Hugo renders both of the examples above to:
+
+```html
+
This is a paragraph.
+```
+
+With `class` and `id` attributes, whether you use long-form or short-form notation, the resulting values are available in [render hook templates][] via the `Attributes` method. For example:
+
+```go-html-template
+{{ .Attributes.class }} → foo bar
+{{ .Attributes.id }} → baz
+```
+
+## Block elements
+
+Update your project configuration to enable Markdown attributes for block-level elements.
+
+{{< code-toggle file=hugo >}}
+[markup.goldmark.parser.attribute]
+title = true # default is true
+block = true # default is false
+{{< /code-toggle >}}
+
+## Standalone images
+
+By default, when the [Goldmark] Markdown renderer encounters a standalone image element (no other elements or text on the same line), it wraps the image element within a paragraph element per the [CommonMark specification].
+
+[CommonMark specification]: https://spec.commonmark.org/current/
+[Goldmark]: https://github.com/yuin/goldmark
+
+If you were to place an attribute list beneath an image element, Hugo would apply the attributes to the surrounding paragraph, not the image.
+
+To apply attributes to a standalone image element, you must disable the default wrapping behavior:
+
+{{< code-toggle file=hugo >}}
+[markup.goldmark.parser]
+wrapStandAloneImageWithinParagraph = false # default is true
+{{< /code-toggle >}}
+
+## Usage
+
+You may add [global HTML attributes], or HTML attributes specific to the current element type. Consistent with its content security model, Hugo removes HTML event attributes such as `onclick` and `onmouseover`.
+
+[global HTML attributes]: https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes
+
+The attribute list consists of one or more key-value pairs, separated by spaces or commas, wrapped by braces. You must quote string values that contain spaces. Unlike HTML, boolean attributes must have both key and value.
+
+For example:
+
+```text
+> This is a blockquote.
+{class="foo bar" hidden=hidden}
+```
+
+Hugo renders this to:
+
+```html
+
+
This is a blockquote.
+
+```
+
+In most cases, place the attribute list beneath the markup element. For headings and fenced code blocks, place the attribute list on the right.
+
+Element|Position of attribute list
+:--|:--
+blockquote|bottom
+fenced code block|right
+heading|right
+horizontal rule|bottom
+image|bottom
+list|bottom
+paragraph|bottom
+table|bottom
+
+For example:
+
+````text
+## Section 1 {class=foo}
+
+```sh {class=foo linenos=inline}
+declare a=1
+echo "${a}"
+```
+
+This is a paragraph.
+{class=foo}
+````
+
+As shown above, the attribute list for fenced code blocks is not limited to HTML attributes. You can also configure syntax highlighting by passing one or more of [these options](/functions/transform/highlight/#options).
+
+[render hook templates]: /render-hooks/introduction/
diff --git a/docs/content/en/content-management/mathematics.md b/docs/content/en/content-management/mathematics.md
new file mode 100644
index 00000000000..6b86cac1c66
--- /dev/null
+++ b/docs/content/en/content-management/mathematics.md
@@ -0,0 +1,237 @@
+---
+title: Mathematics in Markdown
+linkTitle: Mathematics
+description: Include mathematical equations and expressions in Markdown using LaTeX markup.
+categories: []
+keywords: []
+---
+
+## Overview
+
+Mathematical equations and expressions written in [LaTeX][] are common in academic and scientific publications. Your browser typically renders this mathematical markup using an open-source JavaScript display engine such as [MathJax][] or [KaTeX][].
+
+For example, this LaTeX markup:
+
+```text
+\[
+\begin{aligned}
+KL(\hat{y} || y) &= \sum_{c=1}^{M}\hat{y}_c \log{\frac{\hat{y}_c}{y_c}} \\
+JS(\hat{y} || y) &= \frac{1}{2}(KL(y||\frac{y+\hat{y}}{2}) + KL(\hat{y}||\frac{y+\hat{y}}{2}))
+\end{aligned}
+\]
+```
+
+Is rendered to:
+
+\[
+\begin{aligned}
+KL(\hat{y} || y) &= \sum_{c=1}^{M}\hat{y}_c \log{\frac{\hat{y}_c}{y_c}} \\
+JS(\hat{y} || y) &= \frac{1}{2}(KL(y||\frac{y+\hat{y}}{2}) + KL(\hat{y}||\frac{y+\hat{y}}{2}))
+\end{aligned}
+\]
+
+Equations and expressions can be displayed inline with other text, or as standalone blocks. Block presentation is also known as "display" mode.
+
+Whether an equation or expression appears inline, or as a block, depends on the delimiters that surround the mathematical markup. Delimiters are defined in pairs, where each pair consists of an opening and closing delimiter. The opening and closing delimiters may be the same, or different.
+
+> [!note]
+> You can configure Hugo to render mathematical markup on the client side using the MathJax or KaTeX display engine, or you can render the markup with the [`transform.ToMath`][] function while building your project.
+>
+> The first approach is described below.
+
+## Setup
+
+Follow these instructions to include mathematical equations and expressions in your Markdown using LaTeX markup.
+
+Step 1
+: Enable and configure the Goldmark [passthrough extension][] in your project configuration. The passthrough extension preserves raw Markdown within delimited snippets of text, including the delimiters themselves.
+
+ {{< code-toggle file=hugo copy=true >}}
+ [markup.goldmark.extensions.passthrough]
+ enable = true
+
+ [markup.goldmark.extensions.passthrough.delimiters]
+ block = [['\[', '\]'], ['$$', '$$']]
+ inline = [['\(', '\)']]
+
+ [params]
+ math = true
+ {{< /code-toggle >}}
+
+ The configuration above enables mathematical rendering on every page unless you set the `math` parameter to `false` in front matter. To enable mathematical rendering as needed, set the `math` parameter to `false` in your project configuration, and set the `math` parameter to `true` in front matter. Use this parameter in your base template as shown in [Step 3][].
+
+ > [!note]
+ > The configuration above precludes the use of the `$...$` delimiter pair for inline equations. Although you can add this delimiter pair to the configuration and JavaScript, you must double-escape the `$` symbol when used outside of math contexts to avoid unintended formatting.
+ >
+ > See the [inline delimiters][] section for details.
+
+ To disable passthrough of inline snippets, omit the `inline` key from the configuration:
+
+ {{< code-toggle file=hugo >}}
+ [markup.goldmark.extensions.passthrough.delimiters]
+ block = [['\[', '\]'], ['$$', '$$']]
+ {{< /code-toggle >}}
+
+ You can define your own opening and closing delimiters, provided they match the delimiters that you set in [Step 2][].
+
+ {{< code-toggle file=hugo >}}
+ [markup.goldmark.extensions.passthrough.delimiters]
+ block = [['@@', '@@']]
+ inline = [['@', '@']]
+ {{< /code-toggle >}}
+
+Step 2
+: Create a _partial_ template to load MathJax or KaTeX. The example below loads MathJax, or you can use KaTeX as described in the [engines][] section.
+
+ ```go-html-template {file="layouts/_partials/math.html" copy=true}
+
+
+
+ ```
+
+ The delimiters above must match the delimiters in your project configuration.
+
+Step 3
+: Conditionally call the _partial_ template from the base template.
+
+ ```go-html-template {file="layouts/baseof.html"}
+
+ ...
+ {{ if .Param "math" }}
+ {{ partialCached "math.html" . }}
+ {{ end }}
+ ...
+
+ ```
+
+ The example above loads the _partial_ template if you have set the `math` parameter in front matter to `true`. If you have not set the `math` parameter in front matter, the conditional statement falls back to the `math` parameter in your project configuration.
+
+Step 4
+: If you set the `math` parameter to `false` in your project configuration, you must set the `math` parameter to `true` in front matter. For example:
+
+ {{< code-toggle file=content/math-examples.md fm=true >}}
+ title = 'Math examples'
+ date = 2024-01-24T18:09:49-08:00
+ [params]
+ math = true
+ {{< /code-toggle >}}
+
+Step 5
+: Include mathematical equations and expressions in Markdown using LaTeX markup.
+
+ ```text {file="content/math-examples.md" copy=true}
+ This is an inline \(a^*=x-b^*\) equation.
+
+ These are block equations:
+
+ \[a^*=x-b^*\]
+
+ \[ a^*=x-b^* \]
+
+ \[
+ a^*=x-b^*
+ \]
+
+ These are also block equations:
+
+ $$a^*=x-b^*$$
+
+ $$ a^*=x-b^* $$
+
+ $$
+ a^*=x-b^*
+ $$
+ ```
+
+## Inline delimiters
+
+The configuration, JavaScript, and examples above use the `\(...\)` delimiter pair for inline equations. The `$...$` delimiter pair is a common alternative, but using it may result in unintended formatting if you use the `$` symbol outside of math contexts.
+
+If you add the `$...$` delimiter pair to your configuration and JavaScript, you must double-escape the `$` symbol when used outside of math contexts to avoid unintended formatting. For example:
+
+```text
+I will give you \\$2 if you can solve $y = x^2$.
+```
+
+> [!note]
+> If you use the `$...$` delimiter pair for inline equations, and occasionally use the `$` symbol outside of math contexts, you must use MathJax instead of KaTeX to avoid unintended formatting caused by [this KaTeX limitation][].
+
+## Engines
+
+MathJax and KaTeX are open-source JavaScript display engines.
+
+> [!note]
+> If you use the `$...$` delimiter pair for inline equations, and occasionally use the `$` symbol outside of math contexts, you must use MathJax instead of KaTeX to avoid unintended formatting caused by [this KaTeX limitation][].
+>
+>See the [inline delimiters][] section for details.
+
+To use KaTeX instead of MathJax, replace the _partial_ template from [Step 2][] with this:
+
+```go-html-template {file="layouts/_partials/math.html" copy=true}
+
+
+
+
+```
+
+The delimiters above must match the delimiters in your project configuration.
+
+## Chemistry
+
+Both MathJax and KaTeX provide support for chemical equations. For example:
+
+```text
+$$C_p[\ce{H2O(l)}] = \pu{75.3 J // mol K}$$
+```
+
+$$C_p[\ce{H2O(l)}] = \pu{75.3 J // mol K}$$
+
+As shown in [Step 2][] above, MathJax supports chemical equations without additional configuration. To add chemistry support to KaTeX, enable the mhchem extension as described in the KaTeX [documentation](https://katex.org/docs/libs).
+
+[`transform.ToMath`]: /functions/transform/tomath/
+[engines]: #engines
+[inline delimiters]: #inline-delimiters
+[KaTeX]: https://katex.org/
+[LaTeX]: https://www.latex-project.org/
+[MathJax]: https://www.mathjax.org/
+[passthrough extension]: /configuration/markup/#passthrough
+[Step 2]: #step-2
+[Step 3]: #step-3
+[this KaTeX limitation]: https://github.com/KaTeX/KaTeX/issues/437
diff --git a/docs/content/en/content-management/menus.md b/docs/content/en/content-management/menus.md
index aefc99e00b7..766cad7ef8f 100644
--- a/docs/content/en/content-management/menus.md
+++ b/docs/content/en/content-management/menus.md
@@ -1,133 +1,99 @@
---
title: Menus
-linktitle: Menus
-description: Hugo has a simple yet powerful menu system.
-date: 2017-02-01
-publishdate: 2017-02-01
-lastmod: 2017-03-31
-categories: [content management]
-keywords: [menus]
-draft: false
-menu:
- docs:
- parent: "content-management"
- weight: 120
-weight: 120
+description: Create menus by defining entries, localizing each entry, and rendering the resulting data structure.
+categories: []
+keywords: []
aliases: [/extras/menus/]
-toc: true
---
-{{% note "Lazy Blogger"%}}
-If all you want is a simple menu for your sections, see the ["Section Menu for Lazy Bloggers" in Menu Templates](/templates/menu-templates/#section-menu-for-lazy-bloggers).
-{{% /note %}}
+## Overview
-You can do this:
+To create a menu for your site:
-* Place content in one or many menus
-* Handle nested menus with unlimited depth
-* Create menu entries without being attached to any content
-* Distinguish active element (and active branch)
+1. Define the menu entries
+1. [Localize](multilingual/#menus) each entry
+1. Render the menu with a [template]
-## What is a Menu in Hugo?
+Create multiple menus, either flat or nested. For example, create a main menu for the header, and a separate menu for the footer.
-A **menu** is a named array of menu entries accessible by name via the [`.Site.Menus` site variable][sitevars]. For example, you can access your site's `main` menu via `.Site.Menus.main`.
+There are three ways to define menu entries:
-{{% note "Menus on Multilingual Sites" %}}
-If you make use of the [multilingual feature](/content-management/multilingual/), you can define language-independent menus.
-{{% /note %}}
+1. Automatically
+1. In front matter
+1. In your project configuration
-See the [Menu Entry Properties][me-props] for all the variables and functions related to a menu entry.
+> [!note]
+> Although you can use these methods in combination when defining a menu, the menu will be easier to conceptualize and maintain if you use one method throughout the site.
-## Add content to menus
+## Define automatically
-Hugo allows you to add content to a menu via the content's [front matter](/content-management/front-matter/).
+To automatically define a menu entry for each top-level [section](g) of your site, enable the section pages menu in your project configuration.
-### Simple
-
-If all you need to do is add an entry to a menu, the simple form works well.
+{{< code-toggle file=hugo >}}
+sectionPagesMenu = 'main'
+{{< /code-toggle >}}
-#### A Single Menu
+This creates a menu structure that you can access with `site.Menus.main` in your templates. See [menu templates] for details.
-{{< code-toggle >}}
-menu: "main"
-{{< /code-toggle >}}
+## Define in front matter
-#### Multiple Menus
+To add a page to the "main" menu:
-{{< code-toggle >}}
-menu: ["main", "footer"]
+{{< code-toggle file=content/about.md fm=true >}}
+title = 'About'
+menus = 'main'
{{< /code-toggle >}}
-#### Advanced
+Access the entry with `site.Menus.main` in your templates. See [menu templates] for details.
-{{< code-toggle >}}
-menu:
- docs:
- parent: 'extras'
- weight: 20
-{{< /code-toggle >}}
+To add a page to the "main" and "footer" menus:
-## Add Non-content Entries to a Menu
-
-You can also add entries to menus that aren’t attached to a piece of content. This takes place in your Hugo project's [`config` file][config].
-
-Here’s an example snippet pulled from a configuration file:
-
-{{< code-toggle file="config" >}}
-[[menu.main]]
- name = "about hugo"
- pre = ""
- weight = -110
- identifier = "about"
- url = "/about/"
-[[menu.main]]
- name = "getting started"
- pre = ""
- post = "New!"
- weight = -100
- url = "/getting-started/"
+{{< code-toggle file=content/contact.md fm=true >}}
+title = 'Contact'
+menus = ['main','footer']
{{< /code-toggle >}}
-{{% note %}}
-The URLs must be relative to the context root. If the `baseURL` is `https://example.com/mysite/`, then the URLs in the menu must not include the context root `mysite`. Using an absolute URL will override the baseURL. If the value used for `URL` in the above example is `https://subdomain.example.com/`, the output will be `https://subdomain.example.com`.
-{{% /note %}}
+Access the entry with `site.Menus.main` and `site.Menus.footer` in your templates. See [menu templates] for details.
+
+> [!note]
+> The configuration key in the examples above is `menus`. The `menu` (singular) configuration key is an alias for `menus`.
-## Nesting
+### Properties
-All nesting of content is done via the `parent` field.
+Use these properties when defining menu entries in front matter:
-The parent of an entry should be the identifier of another entry. The identifier should be unique (within a menu).
+{{% include "/_common/menu-entry-properties.md" %}}
-The following order is used to determine an Identifier:
+### Example
-`.Name > .LinkTitle > .Title`
+This front matter menu entry demonstrates some of the available properties:
-This means that `.Title` will be used unless `.LinkTitle` is present, etc. In practice, `.Name` and `.Identifier` are only used to structure relationships and therefore never displayed.
+
+{{< code-toggle file=content/products/software.md fm=true >}}
+title = 'Software'
+[menus.main]
+parent = 'Products'
+weight = 20
+pre = ''
+[menus.main.params]
+class = 'center'
+{{< /code-toggle >}}
+
-In this example, the top level of the menu is defined in your [site `config` file][config]. All content entries are attached to one of these entries via the `.Parent` field.
+Access the entry with `site.Menus.main` in your templates. See [menu templates] for details.
-## Params
+## Define in project configuration
-You can also add user-defined content to menu items via the `params` field.
+See [configure menus](/configuration/menus/).
-A common use case is to define a custom param to add a css class to a specific menu item.
+## Localize
-{{< code-toggle file="config" >}}
-[[menu.main]]
- name = "about hugo"
- pre = ""
- weight = -110
- identifier = "about"
- url = "/about/"
- [menu.main.params]
- class = "highlight-menu-item"
-{{ code-toggle >}}
+Hugo provides two methods to localize your menu entries. See [multilingual].
-## Render Menus
+## Render
-See [Menu Templates](/templates/menu-templates/) for information on how to render your site menus within your templates.
+See [menu templates].
-[config]: /getting-started/configuration/
-[multilingual]: /content-management/multilingual/
-[sitevars]: /variables/
-[me-props]: /variables/menus/
+[menu templates]: /templates/menu/
+[multilingual]: /content-management/multilingual/#menus
+[template]: /templates/menu/
diff --git a/docs/content/en/content-management/multilingual.md b/docs/content/en/content-management/multilingual.md
index 620ec0b9309..d3904db71ca 100644
--- a/docs/content/en/content-management/multilingual.md
+++ b/docs/content/en/content-management/multilingual.md
@@ -1,207 +1,51 @@
---
-title: Multilingual Mode
-linktitle: Multilingual
-description: Hugo supports the creation of websites with multiple languages side by side.
-date: 2017-01-10
-publishdate: 2017-01-10
-lastmod: 2017-01-10
-categories: [content management]
-keywords: [multilingual,i18n, internationalization]
-menu:
- docs:
- parent: "content-management"
- weight: 150
-weight: 150 #rem
-draft: false
+title: Multilingual mode
+linkTitle: Multilingual
+description: Localize your project for each language and region, including translations, images, dates, currencies, numbers, percentages, and collation sequence. Hugo's multilingual framework supports single-host and multihost configurations.
+categories: []
+keywords: []
aliases: [/content/multilingual/,/tutorials/create-a-multilingual-site/]
-toc: true
---
-You should define the available languages in a `languages` section in your site configuration.
+## Configuration
-> Also See [Hugo Multilingual Part 1: Content translation]
+See [configure languages](/configuration/languages/).
-## Configure Languages
-
-The following is an example of a site configuration for a multilingual Hugo project:
-
-{{< code-toggle file="config" >}}
-defaultContentLanguage = "en"
-copyright = "Everything is mine"
-
-[params]
-[params.navigation]
-help = "Help"
-
-[languages]
-[languages.en]
-title = "My blog"
-weight = 1
-[languages.en.params]
-linkedin = "https://linkedin.com/whoever"
-
-[languages.fr]
-title = "Mon blogue"
-weight = 2
-[languages.fr.params]
-linkedin = "https://linkedin.com/fr/whoever"
-[languages.fr.params.navigation]
-help = "Aide"
-
-[languages.ar]
-title = "مدونتي"
-weight = 2
-languagedirection = "rtl"
-
-[languages.pt-pt]
-title = "O meu blog"
-weight = 3
-{{< /code-toggle >}}
-
-Anything not defined in a `languages` block will fall back to the global value for that key (e.g., `copyright` for the English `en` language). This also works for `params`, as demonstrated with `help` above: You will get the value `Aide` in French and `Help` in all the languages without this parameter set.
-
-With the configuration above, all content, sitemap, RSS feeds, paginations,
-and taxonomy pages will be rendered below `/` in English (your default content language) and then below `/fr` in French.
-
-When working with front matter `Params` in [single page templates], omit the `params` in the key for the translation.
-
-`defaultContentLanguage` sets the project's default language. If not set, the default language will be `en`.
-
-If the default language needs to be rendered below its own language code (`/en`) like the others, set `defaultContentLanguageInSubdir: true`.
-
-Only the obvious non-global options can be overridden per language. Examples of global options are `baseURL`, `buildDrafts`, etc.
-
-**Please note:** use lowercase language codes, even when using regional languages (ie. use pt-pt instead of pt-PT). Currently Hugo language internals lowercase language codes, which can cause conflicts with settings like `defaultContentLanguage` which are not lowercased. Please track the evolution of this issue in [Hugo repository issue tracker](https://github.com/gohugoio/hugo/issues/7344)
-
-### Disable a Language
-
-You can disable one or more languages. This can be useful when working on a new translation.
-
-{{< code-toggle file="config" >}}
-disableLanguages = ["fr", "ja"]
-{{< /code-toggle >}}
-
-Note that you cannot disable the default content language.
-
-We kept this as a standalone setting to make it easier to set via [OS environment]:
-
-```bash
-HUGO_DISABLELANGUAGES="fr ja" hugo
-```
-
-If you have already a list of disabled languages in `config.toml`, you can enable them in development like this:
-
-```bash
-HUGO_DISABLELANGUAGES=" " hugo server
-```
-
-### Configure Multilingual Multihost
-
-From **Hugo 0.31** we support multiple languages in a multihost configuration. See [this issue](https://github.com/gohugoio/hugo/issues/4027) for details.
-
-This means that you can now configure a `baseURL` per `language`:
-
-> If a `baseURL` is set on the `language` level, then all languages must have one and they must all be different.
-
-Example:
-
-{{< code-toggle file="config" >}}
-[languages]
-[languages.fr]
-baseURL = "https://example.fr"
-languageName = "Français"
-weight = 1
-title = "En Français"
-
-[languages.en]
-baseURL = "https://example.com"
-languageName = "English"
-weight = 2
-title = "In English"
-{{ code-toggle >}}
-
-With the above, the two sites will be generated into `public` with their own root:
-
-```text
-public
-├── en
-└── fr
-```
-
-**All URLs (i.e `.Permalink` etc.) will be generated from that root. So the English home page above will have its `.Permalink` set to `https://example.com/`.**
-
-When you run `hugo server` we will start multiple HTTP servers. You will typically see something like this in the console:
-
-```text
-Web Server is available at 127.0.0.1:1313 (bind address 127.0.0.1)
-Web Server is available at 127.0.0.1:1314 (bind address 127.0.0.1)
-Press Ctrl+C to stop
-```
-
-Live reload and `--navigateToChanged` between the servers work as expected.
-
-### Taxonomies and Blackfriday
-
-Taxonomies and [Blackfriday configuration][config] can also be set per language:
-
-{{< code-toggle file="config" >}}
-[Taxonomies]
-tag = "tags"
-
-[blackfriday]
-angledQuotes = true
-hrefTargetBlank = true
-
-[languages]
-[languages.en]
-weight = 1
-title = "English"
-[languages.en.blackfriday]
-angledQuotes = false
-
-[languages.fr]
-weight = 2
-title = "Français"
-[languages.fr.Taxonomies]
-plaque = "plaques"
-{{ code-toggle >}}
-
-## Translate Your Content
+## Translate your content
There are two ways to manage your content translations. Both ensure each page is assigned a language and is linked to its counterpart translations.
-### Translation by filename
+### Translation by file name
Considering the following example:
1. `/content/about.en.md`
-2. `/content/about.fr.md`
+1. `/content/about.fr.md`
The first file is assigned the English language and is linked to the second.
The second file is assigned the French language and is linked to the first.
-Their language is __assigned__ according to the language code added as a __suffix to the filename__.
+Their language is assigned according to the language code added as a suffix to the file name.
-By having the same **path and base filename**, the content pieces are __linked__ together as translated pages.
+By having the same path and base file name, the content pieces are linked together as translated pages.
-{{< note >}}
-If a file has no language code, it will be assigned the default language.
-{{ note >}}
+> [!note]
+> If a file has no language code, it will be assigned the default language.
### Translation by content directory
-This system uses different content directories for each of the languages. Each language's content directory is set using the `contentDir` param.
-
-{{< code-toggle file="config" >}}
-languages:
- en:
- weight: 10
- languageName: "English"
- contentDir: "content/english"
- fr:
- weight: 20
- languageName: "Français"
- contentDir: "content/french"
+This system uses different content directories for each of the languages. Each language's `content` directory is set using the `contentDir` parameter.
+
+{{< code-toggle file=hugo >}}
+[languages.en]
+contentDir = 'content/english'
+label = "English"
+weight = 10
+
+[languages.fr]
+contentDir = 'content/french'
+label = "Français"
+weight = 20
{{< /code-toggle >}}
The value of `contentDir` can be any valid path -- even absolute path references. The only restriction is that the content directories cannot overlap.
@@ -209,14 +53,14 @@ The value of `contentDir` can be any valid path -- even absolute path references
Considering the following example in conjunction with the configuration above:
1. `/content/english/about.md`
-2. `/content/french/about.md`
+1. `/content/french/about.md`
The first file is assigned the English language and is linked to the second.
The second file is assigned the French language and is linked to the first.
-Their language is __assigned__ according to the content directory they are __placed__ in.
+Their language is assigned according to the `content` directory they are placed in.
-By having the same **path and basename** (relative to their language content directory), the content pieces are __linked__ together as translated pages.
+By having the same path and basename (relative to their language `content` directory), the content pieces are linked together as translated pages.
### Bypassing default linking
@@ -225,192 +69,70 @@ Any pages sharing the same `translationKey` set in front matter will be linked a
Considering the following example:
1. `/content/about-us.en.md`
-2. `/content/om.nn.md`
-3. `/content/presentation/a-propos.fr.md`
+1. `/content/om.nn.md`
+1. `/content/presentation/a-propos.fr.md`
-{{< code-toggle >}}
+{{< code-toggle file=hugo >}}
translationKey: "about"
{{< /code-toggle >}}
-By setting the `translationKey` front matter param to `about` in all three pages, they will be __linked__ as translated pages.
+By setting the `translationKey` front matter parameter to `about` in all three pages, they will be linked as translated pages.
### Localizing permalinks
-Because paths and filenames are used to handle linking, all translated pages will share the same URL (apart from the language subdirectory).
+Because paths and file names are used to handle linking, all translated pages will share the same URL (apart from the language subdirectory).
+
+To localize URLs:
-To localize the URLs, the [`slug`]({{< ref "/content-management/organization/index.md#slug" >}}) or [`url`]({{< ref "/content-management/organization/index.md#url" >}}) front matter param can be set in any of the non-default language file.
+- For a regular page, set either [`slug`] or [`url`] in front matter
+- For a section page, set [`url`] in front matter
-For example, a French translation (`content/about.fr.md`) can have its own localized slug.
+For example, a French translation can have its own localized slug.
-{{< code-toggle >}}
-Title: A Propos
+{{< code-toggle file=content/about.fr.md fm=true >}}
+title: A Propos
slug: "a-propos"
{{< /code-toggle >}}
-At render, Hugo will build both `/about/` and `/fr/a-propos/` while maintaining their translation linking.
+At render, Hugo will build both `/about/` and `/fr/a-propos/` without affecting the translation link.
-{{% note %}}
-If using `url`, remember to include the language part as well: `/fr/compagnie/a-propos/`.
-{{%/ note %}}
+### Page bundles
-### Page Bundles
-
-To avoid the burden of having to duplicate files, each Page Bundle inherits the resources of its linked translated pages' bundles except for the content files (markdown files, html files etc...).
+To avoid the burden of having to duplicate files, each Page Bundle inherits the resources of its linked translated pages' bundles except for the content files (Markdown files, HTML files etc.).
Therefore, from within a template, the page will have access to the files from all linked pages' bundles.
If, across the linked bundles, two or more files share the same basename, only one will be included and chosen as follows:
-* File from current language bundle, if present.
-* First file found across bundles by order of language `Weight`.
-
-{{% note %}}
-Page Bundle resources follow the same language assignment logic as content files, both by filename (`image.jpg`, `image.fr.jpg`) and by directory (`english/about/header.jpg`, `french/about/header.jpg`).
-{{%/ note %}}
-
-## Reference the Translated Content
-
-To create a list of links to translated content, use a template similar to the following:
-
-{{< code file="layouts/partials/i18nlist.html" >}}
-{{ if .IsTranslated }}
-
-{{ end }}
-{{< /code >}}
-
-The above can be put in a `partial` (i.e., inside `layouts/partials/`) and included in any template, whether a [single content page][contenttemplate] or the [homepage]. It will not print anything if there are no translations for a given page.
-
-The above also uses the [`i18n` function][i18func] described in the next section.
-
-### List All Available Languages
-
-`.AllTranslations` on a `Page` can be used to list all translations, including the page itself. On the home page it can be used to build a language navigator:
-
-{{< code file="layouts/partials/allLanguages.html" >}}
-
-{{< /code >}}
-
-## Translation of Strings
-
-Hugo uses [go-i18n] to support string translations. [See the project's source repository][go-i18n-source] to find tools that will help you manage your translation workflows.
+- File from current language bundle, if present.
+- First file found across bundles by order of language `Weight`.
-Translations are collected from the `themes//i18n/` folder (built into the theme), as well as translations present in `i18n/` at the root of your project. In the `i18n`, the translations will be merged and take precedence over what is in the theme folder. Language files should be named according to [RFC 5646] with names such as `en-US.toml`, `fr.toml`, etc.
+> [!note]
+> Page Bundle resources follow the same language assignment logic as content files, both by file name (`image.jpg`, `image.fr.jpg`) and by directory (`english/about/header.jpg`, `french/about/header.jpg`).
-Artificial languages with private use subtags as defined in [RFC 5646 § 2.2.7](https://datatracker.ietf.org/doc/html/rfc5646#section-2.2.7) are also supported. You may omit the `art-x-` prefix for brevity. For example:
+## Translation of strings
-```text
-art-x-hugolang
-hugolang
-```
-
-Private use subtags must not exceed 8 alphanumeric characters.
-
-### Query basic translation
-
-From within your templates, use the `i18n` function like this:
-
-```go-html-template
-{{ i18n "home" }}
-```
-
-The function will search for the `"home"` id:
-
-{{< code-toggle file="i18n/en-US" >}}
-[home]
-other = "Home"
-{{< /code-toggle >}}
-
-The result will be
-
-```text
-Home
-```
-
-### Query a flexible translation with variables
-
-Often you will want to use the page variables in the translation strings. To do so, pass the `.` context when calling `i18n`:
-
-```go-html-template
-{{ i18n "wordCount" . }}
-```
-
-The function will pass the `.` context to the `"wordCount"` id:
-
-{{< code-toggle file="i18n/en-US" >}}
-[wordCount]
-other = "This article has {{ .WordCount }} words."
-{{< /code-toggle >}}
-
-Assume `.WordCount` in the context has value is 101. The result will be:
-
-```text
-This article has 101 words.
-```
-
-### Query a singular/plural translation
-
-In order to meet singular/plural requirement, you must pass a dictionary (map) with a numeric `.Count` property to the `i18n` function. The below example uses `.ReadingTime` variable which has a built-in `.Count` property.
-
-```go-html-template
-{{ i18n "readingTime" .ReadingTime }}
-```
-
-The function will read `.Count` from `.ReadingTime` and evaluate where the number is singular (`one`) or plural (`other`). After that, it will pass to `readingTime` id:
-
-{{< code-toggle file="i18n/en-US" >}}
-[readingTime]
-one = "One minute to read"
-other = "{{.Count}} minutes to read"
-{{< /code-toggle >}}
-
-Assume `.ReadingTime.Count` in the context has value of 525600. The result will be:
-
-```text
-525600 minutes to read
-```
-
-If `.ReadingTime.Count` in the context has value is 1. The result is:
-
-```text
-One minute to read
-```
-
-In case you need to pass custom data: (`(dict "Count" 25)` is minimum requirement)
-
-```go-html-template
-{{ i18n "readingTime" (dict "Count" 25 "FirstArgument" true "SecondArgument" false "Etc" "so on, so far") }}
-```
+See the [`lang.Translate`] template function.
## Localization
-The following localization examples assume your site's primary language is English, with translations to French and German.
+The following localization examples assume your project's primary language is English, with translations to French and German.
-{{< code-toggle file="config" >}}
-defaultContentLang = 'en'
+{{< code-toggle file=hugo >}}
+defaultContentLanguage = 'en'
[languages]
[languages.en]
contentDir = 'content/en'
-languageName = 'English'
+label = 'English'
weight = 1
[languages.fr]
contentDir = 'content/fr'
-languageName = 'Français'
+label = 'Français'
weight = 2
[languages.de]
contentDir = 'content/de'
-languageName = 'Deutsch'
+label = 'Deutsch'
weight = 3
{{< /code-toggle >}}
@@ -419,7 +141,7 @@ weight = 3
With this front matter:
-{{< code-toggle >}}
+{{< code-toggle file=hugo >}}
date = 2021-11-03T12:34:56+01:00
{{< /code-toggle >}}
@@ -437,7 +159,7 @@ English|Wednesday, November 3, 2021
Français|mercredi 3 novembre 2021
Deutsch|Mittwoch, 3. November 2021
-See [time.Format] for details.
+See [`time.Format`] for details.
### Currency
@@ -480,7 +202,7 @@ See [lang.FormatNumber] and [lang.FormatNumberCustom] for details.
With this template code:
```go-html-template
-{{ 512.5032 | lang.FormatPercent 2 }} ---> 512.50%
+{{ 512.5032 | lang.FormatPercent 2 }}
```
The rendered page displays:
@@ -495,88 +217,179 @@ See [lang.FormatPercent] for details.
## Menus
-You can define your menus for each language independently. Creating multilingual menus works just like [creating regular menus][menus], except they're defined in language-specific blocks in the configuration file:
+Localization of menu entries depends on how you define them:
-{{< code-toggle file="config" >}}
-defaultContentLanguage = "en"
+- When you define menu entries [automatically] using the section pages menu, you must use translation tables to localize each entry.
+- When you define menu entries in [front matter], they are already localized based on the front matter itself. If the front matter values are insufficient, use translation tables to localize each entry.
+- When you define menu entries in your [project configuration], you must create language-specific menu entries under each language key. If the names of the menu entries are insufficient, use translation tables to localize each entry.
-[languages.en]
-weight = 0
-languageName = "English"
+### Create language-specific menu entries
+
+#### Method 1 -- Use a single configuration file
-[[languages.en.menu.main]]
-url = "/"
-name = "Home"
-weight = 0
+For a simple menu with a small number of entries, use a single configuration file. For example:
+{{< code-toggle file=hugo >}}
[languages.de]
+label = 'Deutsch'
+locale = 'de-DE'
+weight = 1
+
+[[languages.de.menus.main]]
+name = 'Produkte'
+pageRef = '/products'
+weight = 10
+
+[[languages.de.menus.main]]
+name = 'Leistungen'
+pageRef = '/services'
+weight = 20
+
+[languages.en]
+label = 'English'
+locale = 'en-US'
+weight = 2
+
+[[languages.en.menus.main]]
+name = 'Products'
+pageRef = '/products'
+weight = 10
+
+[[languages.en.menus.main]]
+name = 'Services'
+pageRef = '/services'
+weight = 20
+{{< /code-toggle >}}
+
+#### Method 2 -- Use a configuration directory
+
+With a more complex menu structure, create a [configuration directory] and split the menu entries into multiple files, one file per language. For example:
+
+```text
+config/
+└── _default/
+ ├── menus.de.toml
+ ├── menus.en.toml
+ └── hugo.toml
+```
+
+{{< code-toggle file=config/_default/menus.de >}}
+[[main]]
+name = 'Produkte'
+pageRef = '/products'
weight = 10
-languageName = "Deutsch"
+[[main]]
+name = 'Leistungen'
+pageRef = '/services'
+weight = 20
+{{< /code-toggle >}}
-[[languages.de.menu.main]]
-url = "/"
-name = "Startseite"
-weight = 0
+{{< code-toggle file=config/_default/menus.en >}}
+[[main]]
+name = 'Products'
+pageRef = '/products'
+weight = 10
+[[main]]
+name = 'Services'
+pageRef = '/services'
+weight = 20
{{< /code-toggle >}}
-The rendering of the main navigation works as usual. `.Site.Menus` will just contain the menu in the current language. Note that `absLangURL` below will link to the correct locale of your website. Without it, menu entries in all languages would link to the English version, since it's the default content language that resides in the root directory.
+### Use translation tables
+
+When rendering the text that appears in menu each entry, the [example menu template] does this:
```go-html-template
-
+{{ or (T .Identifier) .Name | safeHTML }}
```
-## Missing Translations
+It queries the translation table for the current language using the menu entry's `identifier` and returns the translated string. If the translation table does not exist, or if the `identifier` key is not present in the translation table, it falls back to `name`.
+
+The `identifier` depends on how you define menu entries:
+
+- If you define the menu entry [automatically] using the section pages menu, the `identifier` is the page's `.Section`.
+- If you define the menu entry in your [project configuration] or in [front matter], set the `identifier` property to the desired value.
+
+For example, if you define menu entries in project configuration:
+
+{{< code-toggle file=hugo >}}
+[[menus.main]]
+ identifier = 'products'
+ name = 'Products'
+ pageRef = '/products'
+ weight = 10
+[[menus.main]]
+ identifier = 'services'
+ name = 'Services'
+ pageRef = '/services'
+ weight = 20
+{{< / code-toggle >}}
+
+Create corresponding entries in the translation tables:
+
+{{< code-toggle file=i18n/de >}}
+products = 'Produkte'
+services = 'Leistungen'
+{{< / code-toggle >}}
+
+## Missing translations
If a string does not have a translation for the current language, Hugo will use the value from the default language. If no default value is set, an empty string will be shown.
While translating a Hugo website, it can be handy to have a visual indicator of missing translations. The [`enableMissingTranslationPlaceholders` configuration option][config] will flag all untranslated strings with the placeholder `[i18n] identifier`, where `identifier` is the id of the missing translation.
-{{% note %}}
-Hugo will generate your website with these missing translation placeholders. It might not be suitable for production environments.
-{{% /note %}}
+> [!note]
+> Hugo will generate your website with these missing translation placeholders. It might not be suitable for production environments.
For merging of content from other languages (i.e. missing content translations), see [lang.Merge].
-To track down missing translation strings, run Hugo with the `--i18n-warnings` flag:
+To track down missing translation strings, run Hugo with the `--printI18nWarnings` flag:
-```bash
-hugo --i18n-warnings | grep i18n
+```sh
+hugo build --printI18nWarnings | grep i18n
i18n|MISSING_TRANSLATION|en|wordCount
```
-## Multilingual Themes support
+## Multilingual themes support
To support Multilingual mode in your themes, some considerations must be taken for the URLs in the templates. If there is more than one language, URLs must meet the following criteria:
-* Come from the built-in `.Permalink` or `.RelPermalink`
-* Be constructed with the [`relLangURL` template function][rellangurl] or the [`absLangURL` template function][abslangurl] **OR** be prefixed with `{{ .LanguagePrefix }}`
-
-If there is more than one language defined, the `LanguagePrefix` variable will equal `/en` (or whatever your `CurrentLanguage` is). If not enabled, it will be an empty string (and is therefore harmless for single-language Hugo websites).
-
-[abslangurl]: /functions/abslangurl
-[config]: /getting-started/configuration/
-[contenttemplate]: /templates/single-page-templates/
-[go-i18n-source]: https://github.com/nicksnyder/go-i18n
-[go-i18n]: https://github.com/nicksnyder/go-i18n
-[homepage]: /templates/homepage/
-[Hugo Multilingual Part 1: Content translation]: https://regisphilibert.com/blog/2018/08/hugo-multilingual-part-1-managing-content-translation/
-[i18func]: /functions/i18n/
-[lang.FormatAccounting]: /functions/lang/#langformataccounting
-[lang.FormatCurrency]: /functions/lang/#langformatcurrency
-[lang.FormatNumber]: /functions/lang/#langformatnumber
-[lang.FormatNumberCustom]: functions/lang/#langformatnumbercustom
-[lang.FormatPercent]: /functions/lang/#langformatpercent
-[lang.Merge]: /functions/lang.merge/
-[menus]: /content-management/menus/
-[OS environment]: /getting-started/configuration/#configure-with-environment-variables
-[rellangurl]: /functions/rellangurl
-[RFC 5646]: https://tools.ietf.org/html/rfc5646
-[single page templates]: /templates/single-page-templates/
-[time.Format]: /functions/dateformat
+- Come from the built-in `.Permalink` or `.RelPermalink`
+- Be constructed with the [`relLangURL`] or [`absLangURL`] template function, or be prefixed with `{{ .LanguagePrefix }}`
+
+If there is more than one language defined, the `LanguagePrefix` method will return `/en` (or whatever the current language is). If not enabled, it will be an empty string (and is therefore harmless for single-language Hugo websites).
+
+## Generate multilingual content with `hugo new content`
+
+If you organize content with translations in the same directory:
+
+```sh
+hugo new content post/test.en.md
+hugo new content post/test.de.md
+```
+
+If you organize content with translations in different directories:
+
+```sh
+hugo new content content/en/post/test.md
+hugo new content content/de/post/test.md
+```
+
+[`absLangURL`]: /functions/urls/abslangurl/
+[`lang.Translate`]: /functions/lang/translate
+[`relLangURL`]: /functions/urls/rellangurl/
+[`slug`]: /content-management/urls/#slug
+[`time.Format`]: /functions/time/format/
+[`url`]: /content-management/urls/#url
+[automatically]: /content-management/menus/#define-automatically
+[config]: /configuration/
+[configuration directory]: /configuration/introduction/#configuration-directory
+[example menu template]: /templates/menu/#example
+[front matter]: /content-management/menus/#define-in-front-matter
+[lang.FormatAccounting]: /functions/lang/formataccounting/
+[lang.FormatCurrency]: /functions/lang/formatcurrency/
+[lang.FormatNumberCustom]: /functions/lang/formatnumbercustom/
+[lang.FormatNumber]: /functions/lang/formatnumber/
+[lang.FormatPercent]: /functions/lang/formatpercent/
+[lang.Merge]: /functions/lang/merge/
+[project configuration]: /content-management/menus/#define-in-project-configuration
diff --git a/docs/content/en/content-management/organization/1-featured-content-bundles.png b/docs/content/en/content-management/organization/1-featured-content-bundles.png
deleted file mode 100644
index 501e671e2f4..00000000000
Binary files a/docs/content/en/content-management/organization/1-featured-content-bundles.png and /dev/null differ
diff --git a/docs/content/en/content-management/organization/index.md b/docs/content/en/content-management/organization/index.md
index 10e22c2d359..5762e0d4ec6 100644
--- a/docs/content/en/content-management/organization/index.md
+++ b/docs/content/en/content-management/organization/index.md
@@ -1,91 +1,90 @@
---
-title: Content Organization
-linktitle: Organization
+title: Content organization
+linkTitle: Organization
description: Hugo assumes that the same structure that works to organize your source content is used to organize the rendered site.
-date: 2017-02-01
-publishdate: 2017-02-01
-lastmod: 2017-02-01
-categories: [content management,fundamentals]
-keywords: [sections,content,organization,bundle,resources]
-menu:
- docs:
- parent: "content-management"
- weight: 10
-weight: 10 #rem
-draft: false
+categories: []
+keywords: []
aliases: [/content/sections/]
-toc: true
---
-## Page Bundles
-
-Hugo `0.32` announced page-relative images and other resources packaged into `Page Bundles`.
-
-These terms are connected, and you also need to read about [Page Resources]({{< relref "/content-management/page-resources" >}}) and [Image Processing]({{< relref "/content-management/image-processing" >}}) to get the full picture.
-
-{{< imgproc 1-featured Resize "300x" >}}
-The illustration shows three bundles. Note that the home page bundle cannot contain other content pages, although other files (images etc.) are allowed.
-{{< /imgproc >}}
-
-
-{{% note %}}
-The bundle documentation is a **work in progress**. We will publish more comprehensive docs about this soon.
-{{% /note %}}
-
+## Page bundles
+
+Hugo supports page-relative images and other resources packaged into `Page Bundles`.
+
+These terms are connected, and you also need to read about [Page Resources](/content-management/page-resources) and [Image Processing](/content-management/image-processing) to get the full picture.
+
+```text
+content/
+├── blog/
+│ ├── hugo-is-cool/
+│ │ ├── images/
+│ │ │ ├── funnier-cat.jpg
+│ │ │ └── funny-cat.jpg
+│ │ ├── cats-info.md
+│ │ └── index.md
+│ ├── posts/
+│ │ ├── post1.md
+│ │ └── post2.md
+│ ├── 1-landscape.jpg
+│ ├── 2-sunset.jpg
+│ ├── _index.md
+│ ├── content-1.md
+│ └── content-2.md
+├── 1-logo.png
+└── _index.md
+```
-## Organization of Content Source
+The file tree above shows three bundles. Note that the home page bundle cannot contain other content pages, although other files (images etc.) are allowed.
+## Organization of content source
In Hugo, your content should be organized in a manner that reflects the rendered website.
-While Hugo supports content nested at any level, the top levels (i.e. `content/`) are special in Hugo and are considered the content type used to determine layouts etc. To read more about sections, including how to nest them, see [sections][].
+While Hugo supports content nested at any level, the top levels (i.e. `content/`) are special in Hugo and are considered the content type used to determine layouts etc. To read more about sections, including how to nest them, see [sections].
Without any additional configuration, the following will automatically work:
-```
+```txt
.
└── content
└── about
- | └── index.md // <- https://example.com/about/
+ | └── index.md // <- https://example.org/about/
├── posts
- | ├── firstpost.md // <- https://example.com/posts/firstpost/
+ | ├── firstpost.md // <- https://example.org/posts/firstpost/
| ├── happy
- | | └── ness.md // <- https://example.com/posts/happy/ness/
- | └── secondpost.md // <- https://example.com/posts/secondpost/
+ | | └── ness.md // <- https://example.org/posts/happy/ness/
+ | └── secondpost.md // <- https://example.org/posts/secondpost/
└── quote
- ├── first.md // <- https://example.com/quote/first/
- └── second.md // <- https://example.com/quote/second/
+ ├── first.md // <- https://example.org/quote/first/
+ └── second.md // <- https://example.org/quote/second/
```
-## Path Breakdown in Hugo
-
+## Path breakdown in Hugo
-The following demonstrates the relationships between your content organization and the output URL structure for your Hugo website when it renders. These examples assume you are [using pretty URLs][pretty], which is the default behavior for Hugo. The examples also assume a key-value of `baseURL = "https://example.com"` in your [site's configuration file][config].
+The following demonstrates the relationships between your content organization and the output URL structure for your Hugo website when it renders. These examples assume you are [using pretty URLs][pretty], which is the default behavior for Hugo. The examples also assume a key-value of `baseURL = "https://example.org/"` in your [project configuration][config].
-### Index Pages: `_index.md`
+### Index pages: `_index.md`
-`_index.md` has a special role in Hugo. It allows you to add front matter and content to your [list templates][lists]. These templates include those for [section templates][], [taxonomy templates][], [taxonomy terms templates][], and your [homepage template][].
+`_index.md` has a special role in Hugo. It allows you to add front matter and content to `home`, `section`, `taxonomy`, and `term` pages.
-{{% note %}}
-**Tip:** You can get a reference to the content and metadata in `_index.md` using the [`.Site.GetPage` function](/functions/getpage/).
-{{% /note %}}
+> [!note]
+> Access the content and metadata within an `_index.md` file by invoking the `GetPage` method on a `Site` or `Page` object.
-You can create one `_index.md` for your homepage and one in each of your content sections, taxonomies, and taxonomy terms. The following shows typical placement of an `_index.md` that would contain content and front matter for a `posts` section list page on a Hugo website:
+You can create one `_index.md` for your home page and one in each of your content sections, taxonomies, and terms. The following shows typical placement of an `_index.md` that would contain content and front matter for a `posts` section list page on a Hugo website:
-
-```
+```txt
. url
. ⊢--^-⊣
. path slug
. ⊢--^-⊣⊢---^---⊣
-. filepath
+. file path
. ⊢------^------⊣
content/posts/_index.md
```
At build, this will output to the following destination with the associated values:
-```
+```txt
url ("/posts/")
⊢-^-⊣
@@ -93,18 +92,16 @@ At build, this will output to the following destination with the associated valu
⊢--------^---------⊣⊢-^-⊣
permalink
⊢----------^-------------⊣
-https://example.com/posts/index.html
+https://example.org/posts/index.html
```
The [sections] can be nested as deeply as you want. The important thing to understand is that to make the section tree fully navigational, at least the lower-most section must include a content file. (i.e. `_index.md`).
+### Single pages in sections
-### Single Pages in Sections
-
-Single content files in each of your sections will be rendered as [single page templates][singles]. Here is an example of a single `post` within `posts`:
-
+Single content files in each of your sections will be rendered by a [page template]. Here is an example of a single `post` within `posts`:
-```
+```txt
path ("posts/my-first-hugo-post.md")
. ⊢-----------^------------⊣
. section slug
@@ -114,7 +111,7 @@ content/posts/my-first-hugo-post.md
When Hugo builds your site, the content will be output to the following destination:
-```
+```txt
url ("/posts/my-first-hugo-post/")
⊢------------^----------⊣
@@ -122,119 +119,33 @@ When Hugo builds your site, the content will be output to the following destinat
⊢--------^--------⊣⊢-^--⊣⊢-------^---------⊣
permalink
⊢--------------------^---------------------⊣
-https://example.com/posts/my-first-hugo-post/index.html
+https://example.org/posts/my-first-hugo-post/index.html
```
-
-## Paths Explained
+## Paths explained
The following concepts provide more insight into the relationship between your project's organization and the default Hugo behavior when building output for the website.
### `section`
-A default content type is determined by the section in which a content item is stored. `section` is determined by the location within the project's `content` directory. `section` *cannot* be specified or overridden in front matter.
+A default content type is determined by the section in which a content item is stored. `section` is determined by the location within the project's `content` directory. `section` cannot be specified or overridden in front matter.
### `slug`
-A content's `slug` is either `name.extension` or `name/`. The value for `slug` is determined by
-
-* the name of the content file (e.g., `lollapalooza.md`) OR
-* front matter overrides
+The `slug` is the last segment of the URL path, defined by the file name and optionally overridden by a `slug` value in front matter. See [URL Management](/content-management/urls/#slug) for details.
### `path`
-A content's `path` is determined by the section's path to the file. The file `path`
-
-* is based on the path to the content's location AND
-* does not include the slug
-
-### `url`
-
-The `url` is the relative URL for the piece of content. The `url`
-
-* is based on the content item's location within the directory structure OR
-* is defined in front matter, in which case it *overrides all the above*
-
-## Override Destination Paths via Front Matter
-
-Hugo assumes that your content is organized with a purpose. The same structure that you use to organize your source content is used to organize the rendered site. As displayed above, the organization of the source content will be mirrored at the destination.
-
-There are times when you may need more fine-grained control over the content organization. In such cases, the front matter field can be used to determine the destination of a specific piece of content.
-
-The following items are defined in a specific order for a reason: items explained lower down in the list override higher items. Note that not all items can be defined in front matter.
+A content's `path` is determined by the section's path to the file. The file `path`:
-### `filename`
-
-`filename` is not a front matter field. It is the actual file name, minus the extension. This will be the name of the file in the destination (e.g., `content/posts/my-post.md` becomes `example.com/posts/my-post/`).
-
-### `slug`
-
-When defined in the front matter, the `slug` can take the place of the filename in the destination.
-
-{{< code file="content/posts/old-post.md" >}}
----
-title: A new post with the filename old-post.md
-slug: "new-post"
----
-{{< /code >}}
-
-This will render to the following destination according to Hugo's default behavior:
-
-```
-example.com/posts/new-post/
-```
-
-### `section`
-
-`section` is determined by a content item's location on disk and *cannot* be specified in the front matter. See [sections] for more information.
-
-### `type`
-
-A content item's `type` is also determined by its location on disk but, unlike `section`, it *can* be specified in the front matter. See [types]. This can come in especially handy when you want a piece of content to render using a different layout. In the following example, you can create a layout at `layouts/new/mylayout.html` that Hugo will use to render this piece of content, even in the midst of many other posts.
-
-{{< code file="content/posts/my-post.md" >}}
----
-title: My Post
-type: new
-layout: mylayout
----
-{{< /code >}}
-
-
-
-
+- Is based on the path to the content's location AND
+- Does not include the slug
### `url`
-A complete URL can be provided. This will override all the above as it pertains to the end destination. This must be the path from the baseURL (starting with a `/`). `url` will be used exactly as it is defined in the front matter, and will ignore the `--uglyURLs` setting in your site configuration:
-
-{{< code file="content/posts/old-url.md" >}}
----
-title: Old URL
-url: /blog/new-url/
----
-{{< /code >}}
-
-Assuming your `baseURL` is [configured][config] to `https://example.com`, the addition of `url` to the front matter will make `old-url.md` render to the following destination:
-
-```
-https://example.com/blog/new-url/
-```
+The `url` is the entire URL path, defined by the file path and optionally overridden by a `url` value in front matter. See [URL Management](/content-management/urls/#slug) for details.
-You can see more information on how to control output paths in [URL Management][urls].
-
-[config]: /getting-started/configuration/
-[formats]: /content-management/formats/
-[front matter]: /content-management/front-matter/
-[getpage]: /functions/getpage/
-[homepage template]: /templates/homepage/
-[homepage]: /templates/homepage/
-[lists]: /templates/lists/
-[pretty]: /content-management/urls/#pretty-urls
-[section templates]: /templates/section-templates/
+[config]: /configuration/
+[pretty]: /content-management/urls/#appearance
[sections]: /content-management/sections/
-[singles]: /templates/single-page-templates/
-[taxonomy templates]: /templates/taxonomy-templates/
-[taxonomy terms templates]: /templates/taxonomy-templates/
-[types]: /content-management/types/
-[urls]: /content-management/urls/
+[page template]: /templates/types/#page
diff --git a/docs/content/en/content-management/page-bundles.md b/docs/content/en/content-management/page-bundles.md
index 9561ea2e92e..a5bf9a3afb9 100644
--- a/docs/content/en/content-management/page-bundles.md
+++ b/docs/content/en/content-management/page-bundles.md
@@ -1,188 +1,145 @@
---
-title : "Page Bundles"
-description : "Content organization using Page Bundles"
-date : 2018-01-24T13:09:00-05:00
-linktitle : "Page Bundles"
-keywords : ["page", "bundle", "leaf", "branch"]
-categories : ["content management"]
-toc : true
-menu :
- docs:
- identifier : "page-bundles"
- parent : "content-management"
- weight : 11
+title: Page bundles
+description: Use page bundles to logically associate one or more resources with content.
+categories: []
+keywords: []
---
-Page Bundles are a way to group [Page Resources](/content-management/page-resources/).
+## Introduction
-A Page Bundle can be one of:
+A page bundle is a directory that encapsulates both content and associated resources.
-- Leaf Bundle (leaf means it has no children)
-- Branch Bundle (home page, section, taxonomy terms, taxonomy list)
-
-| | Leaf Bundle | Branch Bundle |
-|-------------------------------------|----------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| Usage | Collection of content and attachments for single pages | Collection of attachments for section pages (home page, section, taxonomy terms, taxonomy list) |
-| Index file name | `index.md` [^fn:1] | `_index.md` [^fn:1] |
-| Allowed Resources | Page and non-page (like images, pdf, etc.) types | Only non-page (like images, pdf, etc.) types |
-| Where can the Resources live? | At any directory level within the leaf bundle directory. | Only in the directory level **of** the branch bundle directory i.e. the directory containing the `_index.md` ([ref](https://discourse.gohugo.io/t/question-about-content-folder-structure/11822/4?u=kaushalmodi)). |
-| Layout type | `single` | `list` |
-| Nesting | Does not allow nesting of more bundles under it | Allows nesting of leaf or branch bundles under it |
-| Example | `content/posts/my-post/index.md` | `content/posts/_index.md` |
-| Content from non-index page files... | Accessed only as page resources | Accessed only as regular pages |
-
-
-## Leaf Bundles {#leaf-bundles}
-
-A _Leaf Bundle_ is a directory at any hierarchy within the `content/`
-directory, that contains an **`index.md`** file.
-
-### Examples of Leaf Bundle organization {#examples-of-leaf-bundle-organization}
+By way of example, this site has an "about" page and a "privacy" page:
```text
content/
-├── about
+├── about/
│ ├── index.md
-├── posts
-│ ├── my-post
-│ │ ├── content1.md
-│ │ ├── content2.md
-│ │ ├── image1.jpg
-│ │ ├── image2.png
-│ │ └── index.md
-│ └── my-other-post
-│ └── index.md
-│
-└── another-section
- ├── ..
- └── not-a-leaf-bundle
- ├── ..
- └── another-leaf-bundle
- └── index.md
+│ └── welcome.jpg
+└── privacy.md
```
-In the above example `content/` directory, there are four leaf
-bundles:
+The "about" page is a page bundle. It logically associates a resource with content by bundling them together. Resources within a page bundle are [page resources], accessible with the [`Resources`] method on the `Page` object.
-about
-: This leaf bundle is at the root level (directly under
- `content` directory) and has only the `index.md`.
+Page bundles are either _leaf bundles_ or _branch bundles_.
-my-post
-: This leaf bundle has the `index.md`, two other content
- Markdown files and two image files.
+leaf bundle
+: A _leaf bundle_ is a directory that contains an `index.md` file and zero or more resources. Analogous to a physical leaf, a leaf bundle is at the end of a branch. It has no descendants.
-image1
-: This image is a page resource of `my-post`
- and only available in `my-post/index.md` resources.
+branch bundle
+: A _branch bundle_ is a directory that contains an `_index.md` file and zero or more resources. Analogous to a physical branch, a branch bundle may have descendants including leaf bundles and other branch bundles. Top-level directories with or without `_index.md` files are also branch bundles. This includes the home page.
-image2
-: This image is a page resource of `my-post`
- and only available in `my-post/index.md` resources.
+> [!note]
+> In the definitions above and the examples below, the extension of the index file depends on the [content format](g). For example, use `index.md` for Markdown content, `index.html` for HTML content, `index.adoc` for AsciiDoc content, etc.
-my-other-post
-: This leaf bundle has only the `index.md`.
+## Comparison
-another-leaf-bundle
-: This leaf bundle is nested under couple of
- directories. This bundle also has only the `index.md`.
+Page bundle characteristics vary by bundle type.
-{{% note %}}
-The hierarchy depth at which a leaf bundle is created does not matter,
-as long as it is not inside another **leaf** bundle.
-{{% /note %}}
+| | Leaf bundle | Branch bundle |
+|---------------------|---------------------------------------------------------|---------------------------------------------------------|
+| Index file | `index.md` | `_index.md` |
+| Example | `content/about/index.md` | `content/posts/_index.md` |
+| [Page kinds](g) | `page` | `home`, `section`, `taxonomy`, or `term` |
+| Template types | [single] | [home], [section], [taxonomy], or [term] |
+| Descendant pages | None | Zero or more |
+| Resource location | Adjacent to the index file or in a nested subdirectory | Same as a leaf bundles, but excludes descendant bundles |
+| [Resource types](g) | `page`, `image`, `video`, etc. | all but `page` |
+Files with [resource type](g) `page` include content written in Markdown, HTML, AsciiDoc, Pandoc, reStructuredText, and Emacs Org Mode. In a leaf bundle, excluding the index file, these files are only accessible as page resources. In a branch bundle, these files are only accessible as content pages.
-### Headless Bundle {#headless-bundle}
+## Leaf bundles
-A headless bundle is a bundle that is configured to not get published
-anywhere:
+A _leaf bundle_ is a directory that contains an `index.md` file and zero or more resources. Analogous to a physical leaf, a leaf bundle is at the end of a branch. It has no descendants.
-- It will have no `Permalink` and no rendered HTML in `public/`.
-- It will not be part of `.Site.RegularPages`, etc.
-
-But you can get it by `.Site.GetPage`. Here is an example:
-
-```go-html-template
-{{ $headless := .Site.GetPage "/some-headless-bundle" }}
-{{ $reusablePages := $headless.Resources.Match "author*" }}
-
Authors
-{{ range $reusablePages }}
-
{{ .Title }}
- {{ .Content }}
-{{ end }}
+```text
+content/
+├── about
+│ └── index.md
+├── posts
+│ ├── my-post
+│ │ ├── content-1.md
+│ │ ├── content-2.md
+│ │ ├── image-1.jpg
+│ │ ├── image-2.png
+│ │ └── index.md
+│ └── my-other-post
+│ └── index.md
+└── another-section
+ ├── foo.md
+ └── not-a-leaf-bundle
+ ├── bar.md
+ └── another-leaf-bundle
+ └── index.md
```
-_In this example, we are assuming the `some-headless-bundle` to be a headless
- bundle containing one or more **page** resources whose `.Name` matches
- `"author*"`._
-
-Explanation of the above example:
-
-1. Get the `some-headless-bundle` Page "object".
-2. Collect a *slice* of resources in this *Page Bundle* that matches
- `"author*"` using `.Resources.Match`.
-3. Loop through that *slice* of nested pages, and output their `.Title` and
- `.Content`.
-
----
+There are four leaf bundles in the example above:
-A leaf bundle can be made headless by adding below in the Front Matter
-(in the `index.md`):
+about
+: This leaf bundle does not contain any page resources.
-```toml
-headless = true
-```
+my-post
+: This leaf bundle contains an index file, two resources of [resource type](g) `page`, and two resources of resource type `image`.
-There are many use cases of such headless page bundles:
+ - content-1, content-2
-- Shared media galleries
-- Reusable page content "snippets"
+ These are resources of resource type `page`, accessible via the [`Resources`] method on the `Page` object. Hugo will not render these as individual pages.
+ - image-1, image-2
-## Branch Bundles {#branch-bundles}
+ These are resources of resource type `image`, accessible via the `Resources` method on the `Page` object
-A _Branch Bundle_ is any directory at any hierarchy within the
-`content/` directory, that contains at least an **`_index.md`** file.
+my-other-post
+: This leaf bundle does not contain any page resources.
-This `_index.md` can also be directly under the `content/` directory.
+another-leaf-bundle
+: This leaf bundle does not contain any page resources.
-{{% note %}}
-Here `md` (markdown) is used just as an example. You can use any file
-type as a content resource as long as it is a content type recognized by Hugo.
-{{% /note %}}
+> [!note]
+> Create leaf bundles at any depth within the `content` directory, but a leaf bundle may not contain another bundle. Leaf bundles do not have descendants.
+## Branch bundles
-### Examples of Branch Bundle organization {#examples-of-branch-bundle-organization}
+A _branch bundle_ is a directory that contains an `_index.md` file and zero or more resources. Analogous to a physical branch, a branch bundle may have descendants including leaf bundles and other branch bundles. Top-level directories with or without `_index.md` files are also branch bundles. This includes the home page.
```text
content/
-├── branch-bundle-1
-│ ├── branch-content1.md
-│ ├── branch-content2.md
-│ ├── image1.jpg
-│ ├── image2.png
-│ └── _index.md
-└── branch-bundle-2
- ├── _index.md
- └── a-leaf-bundle
- └── index.md
+├── branch-bundle-1/
+│ ├── _index.md
+│ ├── content-1.md
+│ ├── content-2.md
+│ ├── image-1.jpg
+│ └── image-2.png
+├── branch-bundle-2/
+│ ├── a-leaf-bundle/
+│ │ └── index.md
+│ └── _index.md
+└── _index.md
```
-In the above example `content/` directory, there are two branch
-bundles (and a leaf bundle):
+There are three branch bundles in the example above:
+
+home page
+: This branch bundle contains an index file, two descendant branch bundles, and no resources.
+
+branch-bundle-1
+: This branch bundle contains an index file, two resources of [resource type](g) `page`, and two resources of resource type `image`.
+
+branch-bundle-2
+: This branch bundle contains an index file and a leaf bundle.
-`branch-bundle-1`
-: This branch bundle has the `_index.md`, two
- other content Markdown files and two image files.
+> [!note]
+> Create branch bundles at any depth within the `content` directory. Branch bundles may have descendants.
-`branch-bundle-2`
-: This branch bundle has the `_index.md` and a
- nested leaf bundle.
+## Headless bundles
-{{% note %}}
-The hierarchy depth at which a branch bundle is created does not
-matter.
-{{% /note %}}
+Use [build options] in front matter to create an unpublished leaf or branch bundle whose content and resources you can include in other pages.
-[^fn:1]: The `.md` extension is just an example. The extension can be `.html`, `.json` or any valid MIME type.
+[`Resources`]: /methods/page/resources/
+[build options]: /content-management/build-options/
+[home]: /templates/types/#home
+[page resources]: /content-management/page-resources/
+[section]: /templates/types/#section
+[single]: /templates/types/#single
+[taxonomy]: /templates/types/#taxonomy
+[term]: /templates/types/#term
diff --git a/docs/content/en/content-management/page-resources.md b/docs/content/en/content-management/page-resources.md
index 9f2c0cfab1b..db92734c181 100644
--- a/docs/content/en/content-management/page-resources.md
+++ b/docs/content/en/content-management/page-resources.md
@@ -1,21 +1,12 @@
---
-title : "Page Resources"
-description : "Page resources -- images, other pages, documents, etc. -- have page-relative URLs and their own metadata."
-date: 2018-01-24
-categories: ["content management"]
-keywords: [bundle,content,resources]
-weight: 4003
-draft: false
-toc: true
-linktitle: "Page Resources"
-menu:
- docs:
- parent: "content-management"
- weight: 31
+title: Page resources
+description: Use page resources to logically associate assets with a page.
+categories: []
+keywords: []
---
-Page resources are only accessible from [page bundles]({{< relref
-"/content-management/page-bundles" >}}), those directories with `index.md` or
-`_index.md` files at their root. Page resources are only available to the
+
+Page resources are only accessible from [page bundles](/content-management/page-bundles), those directories with `index.md` or
+`_index.md` files at their root. Page resources are only available to the
page with which they are bundled.
In this example, `first-post` is a page bundle with access to 10 page resources including audio, data, documents, images, and video. Although `second-post` is also a page bundle, it has no page resources and is unable to directly access the page resources associated with `first-post`.
@@ -40,133 +31,126 @@ content
└── index.md (root of page bundle)
```
-## Properties
-
-ResourceType
-: The main type of the resource's [Media Type](/templates/output-formats/#media-types). For example, a file of MIME type `image/jpeg` has the ResourceType `image`. A `Page` will have `ResourceType` with value `page`.
+## Examples
-{{< new-in "0.80.0" >}} Note that we in Hugo `v0.80.0` fixed a bug where non-image resources (e.g. video) would return the MIME sub type, e.g. `json`.
+Use any of these methods on a `Page` object to capture page resources:
-Name
-: Default value is the filename (relative to the owning page). Can be set in front matter.
+- [`Resources.ByType`]
+- [`Resources.Get`]
+- [`Resources.GetMatch`]
+- [`Resources.Match`]
-Title
-: Default value is the same as `.Name`. Can be set in front matter.
+ Once you have captured a resource, use any of the applicable [`Resource`] methods to return a value or perform an action.
-Permalink
-: The absolute URL to the resource. Resources of type `page` will have no value.
+The following examples assume this content structure:
-RelPermalink
-: The relative URL to the resource. Resources of type `page` will have no value.
+```text
+content/
+└── example/
+ ├── data/
+ │ └── books.json <-- page resource
+ ├── images/
+ │ ├── a.jpg <-- page resource
+ │ └── b.jpg <-- page resource
+ ├── snippets/
+ │ └── text.md <-- page resource
+ └── index.md
+```
-Content
-: The content of the resource itself. For most resources, this returns a string
-with the contents of the file. Use this to create inline resources.
+Render a single image, and throw an error if the file does not exist:
```go-html-template
-{{ with .Resources.GetMatch "script.js" }}
-
+{{ $path := "images/a.jpg" }}
+{{ with .Resources.Get $path }}
+
+{{ else }}
+ {{ errorf "Unable to get page resource %q" $path }}
{{ end }}
+```
-{{ with .Resources.GetMatch "style.css" }}
-
-{{ end }}
+Render all images, resized to 300 px wide:
-{{ with .Resources.GetMatch "img.png" }}
-
+```go-html-template
+{{ range .Resources.ByType "image" }}
+ {{ with .Resize "300x" }}
+
+ {{ end }}
{{ end }}
```
-MediaType
-: The MIME type of the resource, such as `image/jpeg`.
-
-MediaType.MainType
-: The main type of the resource's MIME type. For example, a file of MIME type `application/pdf` has for MainType `application`.
-
-MediaType.SubType
-: The subtype of the resource's MIME type. For example, a file of MIME type `application/pdf` has for SubType `pdf`. Note that this is not the same as the file extension - PowerPoint files have a subtype of `vnd.mspowerpoint`.
-
-MediaType.Suffixes
-: A slice of possible suffixes for the resource's MIME type.
+Render the markdown snippet:
-## Methods
-ByType
-: Returns the page resources of the given type.
-
-```go
-{{ .Resources.ByType "image" }}
-```
-Match
-: Returns all the page resources (as a slice) whose `Name` matches the given Glob pattern ([examples](https://github.com/gobwas/glob/blob/master/readme.md)). The matching is case-insensitive.
-
-```go
-{{ .Resources.Match "images/*" }}
+```go-html-template
+{{ with .Resources.Get "snippets/text.md" }}
+ {{ .Content }}
+{{ end }}
```
-GetMatch
-: Same as `Match` but will return the first match.
-
-### Pattern Matching
-```go
-// Using Match/GetMatch to find this images/sunset.jpg ?
-.Resources.Match "images/sun*" ✅
-.Resources.Match "**/sunset.jpg" ✅
-.Resources.Match "images/*.jpg" ✅
-.Resources.Match "**.jpg" ✅
-.Resources.Match "*" 🚫
-.Resources.Match "sunset.jpg" 🚫
-.Resources.Match "*sunset.jpg" 🚫
+List the titles in the data file, and throw an error if the file does not exist.
+```go-html-template
+{{ $path := "data/books.json" }}
+{{ with .Resources.Get $path }}
+ {{ with . | transform.Unmarshal }}
+
Books:
+
+ {{ range . }}
+
{{ .title }}
+ {{ end }}
+
+ {{ end }}
+{{ else }}
+ {{ errorf "Unable to get page resource %q" $path }}
+{{ end }}
```
-## Page Resources Metadata
+## Metadata
The page resources' metadata is managed from the corresponding page's front matter with an array/table parameter named `resources`. You can batch assign values using [wildcards](https://tldp.org/LDP/GNU-Linux-Tools-Summary/html/x11655.htm).
-{{% note %}}
-Resources of type `page` get `Title` etc. from their own front matter.
-{{% /note %}}
+> [!note]
+> Resources of type `page` get `Title` etc. from their own front matter.
name
-: Sets the value returned in `Name`.
+: (`string`) Sets the value returned in `Name`.
-{{% warning %}}
-The methods `Match` and `GetMatch` use `Name` to match the resources.
-{{%/ warning %}}
+> [!note]
+> The methods `Match`, `Get` and `GetMatch` use `Name` to match the resources.
title
-: Sets the value returned in `Title`
+: (`string`) Sets the value returned in `Title`
params
-: A map of custom key/values.
+: (`map`) A map of custom key-value pairs.
+### Resources metadata example
-### Resources metadata example
-
-{{< code-toggle copy="false">}}
+
+{{< code-toggle file=content/example.md fm=true >}}
title: Application
-date : 2018-01-25
-resources :
-- src : "images/sunset.jpg"
- name : "header"
-- src : "documents/photo_specs.pdf"
- title : "Photo Specifications"
- params:
- icon : "photo"
-- src : "documents/guide.pdf"
- title : "Instruction Guide"
-- src : "documents/checklist.pdf"
- title : "Document Checklist"
-- src : "documents/payment.docx"
- title : "Proof of Payment"
-- src : "**.pdf"
- name : "pdf-file-:counter"
- params :
- icon : "pdf"
-- src : "**.docx"
- params :
- icon : "word"
+date: 2018-01-25
+resources:
+ - src: images/sunset.jpg
+ name: header
+ - src: documents/photo_specs.pdf
+ title: Photo Specifications
+ params:
+ icon: photo
+ - src: documents/guide.pdf
+ title: Instruction Guide
+ - src: documents/checklist.pdf
+ title: Document Checklist
+ - src: documents/payment.docx
+ title: Proof of Payment
+ - src: "**.pdf"
+ name: pdf-file-:counter
+ params:
+ icon: pdf
+ - src: "**.docx"
+ params:
+ icon: word
{{ code-toggle >}}
+
From the example above:
@@ -177,9 +161,8 @@ From the example above:
- All `PDF` files will get a new `Name`. The `name` parameter contains a special placeholder [`:counter`](#the-counter-placeholder-in-name-and-title), so the `Name` will be `pdf-file-1`, `pdf-file-2`, `pdf-file-3`.
- Every docx in the bundle will receive the `word` icon.
-{{% warning %}}
-The __order matters__ --- Only the **first set** values of the `title`, `name` and `params`-**keys** will be used. Consecutive parameters will be set only for the ones not already set. In the above example, `.Params.icon` is first set to `"photo"` in `src = "documents/photo_specs.pdf"`. So that would not get overridden to `"pdf"` by the later set `src = "**.pdf"` rule.
-{{%/ warning %}}
+> [!note]
+> The order matters; only the first set values of the `title`, `name` and `params` keys will be used. Consecutive parameters will be set only for the ones not already set. In the above example, `.Params.icon` is first set to `"photo"` in `src = "documents/photo_specs.pdf"`. So that would not get overridden to `"pdf"` by the later set `src = "**.pdf"` rule.
### The `:counter` placeholder in `name` and `title`
@@ -189,13 +172,14 @@ The counter starts at 1 the first time they are used in either `name` or `title`
For example, if a bundle has the resources `photo_specs.pdf`, `other_specs.pdf`, `guide.pdf` and `checklist.pdf`, and the front matter has specified the `resources` as:
-{{< code-toggle copy="false">}}
+{{< code-toggle file=content/inspections/engine/index.md fm=true >}}
+title = 'Engine inspections'
[[resources]]
- src = "*specs.pdf"
- title = "Specification #:counter"
+ src = '*specs.pdf'
+ title = 'Specification #:counter'
[[resources]]
- src = "**.pdf"
- name = "pdf-file-:counter"
+ src = '**.pdf'
+ name = 'pdf-file-:counter'
{{ code-toggle >}}
the `Name` and `Title` will be assigned to the resource files as follows:
@@ -204,5 +188,110 @@ the `Name` and `Title` will be assigned to the resource files as follows:
|-------------------|-------------------|-----------------------|
| checklist.pdf | `"pdf-file-1.pdf` | `"checklist.pdf"` |
| guide.pdf | `"pdf-file-2.pdf` | `"guide.pdf"` |
-| other\_specs.pdf | `"pdf-file-3.pdf` | `"Specification #1"` |
-| photo\_specs.pdf | `"pdf-file-4.pdf` | `"Specification #2"` |
+| other\_specs.pdf | `"pdf-file-3.pdf` | `"Specification #1"` |
+| photo\_specs.pdf | `"pdf-file-4.pdf` | `"Specification #2"` |
+
+## Multilingual
+
+By default, with a multilingual single-host project, Hugo does not duplicate shared page during the build.
+
+> [!note]
+> This behavior is limited to Markdown content. Shared page resources for other [content formats] are copied into each language bundle.
+
+Consider this project configuration:
+
+{{< code-toggle file=hugo >}}
+defaultContentLanguage = 'de'
+defaultContentLanguageInSubdir = true
+
+[languages.de]
+label = 'Deutsch'
+locale = 'de-DE'
+weight = 1
+
+[languages.en]
+label = 'English'
+locale = 'en-US'
+weight = 2
+{{< /code-toggle >}}
+
+And this content:
+
+```text
+content/
+└── my-bundle/
+ ├── a.jpg <-- shared page resource
+ ├── b.jpg <-- shared page resource
+ ├── c.de.jpg
+ ├── c.en.jpg
+ ├── index.de.md
+ └── index.en.md
+```
+
+With v0.122.0 and earlier, Hugo duplicated the shared page resources, creating copies for each language:
+
+```text
+public/
+├── de/
+│ ├── my-bundle/
+│ │ ├── a.jpg <-- shared page resource
+│ │ ├── b.jpg <-- shared page resource
+│ │ ├── c.de.jpg
+│ │ └── index.html
+│ └── index.html
+├── en/
+│ ├── my-bundle/
+│ │ ├── a.jpg <-- shared page resource (duplicate)
+│ │ ├── b.jpg <-- shared page resource (duplicate)
+│ │ ├── c.en.jpg
+│ │ └── index.html
+│ └── index.html
+└── index.html
+
+```
+
+With v0.123.0 and later, Hugo places the shared resources in the page bundle for the default content language:
+
+```text
+public/
+├── de/
+│ ├── my-bundle/
+│ │ ├── a.jpg <-- shared page resource
+│ │ ├── b.jpg <-- shared page resource
+│ │ ├── c.de.jpg
+│ │ └── index.html
+│ └── index.html
+├── en/
+│ ├── my-bundle/
+│ │ ├── c.en.jpg
+│ │ └── index.html
+│ └── index.html
+└── index.html
+```
+
+This approach reduces build times, storage requirements, bandwidth consumption, and deployment times, ultimately reducing cost.
+
+> [!important]
+> To resolve Markdown link and image destinations to the correct location, you must use link and image render hooks that capture the page resource with the [`Resources.Get`] method, and then invoke its [`RelPermalink`] method.
+>
+> In its default configuration, Hugo automatically uses the [embedded link render hook] and the [embedded image render hook] for multilingual single-host projects, specifically when the [duplication of shared page resources] feature is disabled. This is the default behavior for such projects. If custom link or image render hooks are defined by your project, modules, or themes, these will be used instead.
+>
+> You can also configure Hugo to `always` use the embedded link or image render hook, use it only as a `fallback`, or `never` use it. See [details](/configuration/markup/#renderhookslinkuseembedded).
+
+Although duplicating shared page resources is inefficient, you can enable this feature in your project configuration if desired:
+
+{{< code-toggle file=hugo >}}
+[markup.goldmark]
+duplicateResourceFiles = true
+{{< /code-toggle >}}
+
+[`RelPermalink`]: /methods/resource/relpermalink/
+[`Resource`]: /methods/resource
+[`Resources.ByType`]: /methods/page/resources#bytype
+[`Resources.Get`]: /methods/page/resources/#get
+[`Resources.GetMatch`]: /methods/page/resources#getmatch
+[`Resources.Match`]: /methods/page/resources#match
+[content formats]: /content-management/formats/
+[duplication of shared page resources]: /configuration/markup/#duplicateresourcefiles
+[embedded image render hook]: /render-hooks/images/#embedded
+[embedded link render hook]: /render-hooks/links/#embedded
diff --git a/docs/content/en/content-management/related-content.md b/docs/content/en/content-management/related-content.md
new file mode 100644
index 00000000000..48009ec4612
--- /dev/null
+++ b/docs/content/en/content-management/related-content.md
@@ -0,0 +1,102 @@
+---
+title: Related content
+description: List related content in "See Also" sections.
+categories: []
+keywords: []
+aliases: [/content/related/,/related/,/content-management/related/]
+---
+
+Hugo uses a set of factors to identify a page's related content based on front matter parameters. This can be tuned to the desired set of indices and parameters or left to Hugo's default [related content configuration](/configuration/related-content/).
+
+## List related content
+
+To list up to 5 related pages (which share the same _date_ or _keyword_ parameters) is as simple as including something similar to this partial in your template:
+
+```go-html-template {file="layouts/_partials/related.html" copy=true}
+{{ with site.RegularPages.Related . | first 5 }}
+
+{{ end }}
+```
+
+The `Related` method takes one argument which may be a `Page` or an options map. The options map has these options:
+
+indices
+: (`slice`) The indices to search within.
+
+document
+: (`page`) The page for which to find related content. Required when specifying an options map.
+
+namedSlices
+: (`slice`) The keywords to search for, expressed as a slice of `KeyValues` using the [`keyVals`] function.
+
+fragments
+: (`slice`) A list of special keywords that is used for indices configured as type "fragments". This will match the [fragment](g) identifiers of the documents.
+
+A fictional example using all of the above options:
+
+```go-html-template
+{{ $page := . }}
+{{ $opts := dict
+ "indices" (slice "tags" "keywords")
+ "document" $page
+ "namedSlices" (slice (keyVals "tags" "hugo" "rocks") (keyVals "date" $page.Date))
+ "fragments" (slice "heading-1" "heading-2")
+}}
+```
+
+> [!note]
+> We improved and simplified this feature in Hugo 0.111.0. Before this we had 3 different methods: `Related`, `RelatedTo` and `RelatedIndices`. Now we have only one method: `Related`. The old methods are still available but deprecated. Also see [this blog article](https://regisphilibert.com/blog/2018/04/hugo-optmized-relashionships-with-related-content/) for a great explanation of more advanced usage of this feature.
+
+## Index content headings
+
+Hugo can index the headings in your content and use this to find related content. You can enable this by adding a index of type `fragments` to your `related` configuration:
+
+{{< code-toggle file=hugo >}}
+[related]
+threshold = 20
+includeNewer = true
+toLower = false
+[[related.indices]]
+name = 'fragmentrefs'
+type = 'fragments'
+applyFilter = true
+weight = 80
+{{< /code-toggle >}}
+
+- The `name` maps to a optional front matter slice attribute that can be used to link from the page level down to the fragment/heading level.
+- If `applyFilter` is enabled, the `.HeadingsFiltered` on each page in the result will reflect the filtered headings. This is useful if you want to show the headings in the related content listing:
+
+```go-html-template
+{{ $related := .Site.RegularPages.Related . | first 5 }}
+{{ with $related }}
+
+{{ end }}
+```
+
+## Configuration
+
+See [configure related content](/configuration/related-content/).
+
+[`keyVals`]: /functions/collections/keyvals/
diff --git a/docs/content/en/content-management/related.md b/docs/content/en/content-management/related.md
deleted file mode 100644
index a42cec32e92..00000000000
--- a/docs/content/en/content-management/related.md
+++ /dev/null
@@ -1,137 +0,0 @@
----
-title: Related Content
-description: List related content in "See Also" sections.
-date: 2017-09-05
-categories: [content management]
-keywords: [content]
-menu:
- docs:
- parent: "content-management"
- weight: 40
-weight: 30
-draft: false
-aliases: [/content/related/,/related/]
-toc: true
----
-
-
-Hugo uses a set of factors to identify a page's related content based on Front Matter parameters. This can be tuned to the desired set of indices and parameters or left to Hugo's default [Related Content configuration](#configure-related-content).
-
-## List Related Content
-
-
-To list up to 5 related pages (which share the same _date_ or _keyword_ parameters) is as simple as including something similar to this partial in your single page template:
-
-{{< code file="layouts/partials/related.html" >}}
-{{ $related := .Site.RegularPages.Related . | first 5 }}
-{{ with $related }}
-
-{{ end }}
-{{< /code >}}
-
-### Methods
-
-Here is the list of "Related" methods available on a page collection such `.RegularPages`.
-
-#### .Related PAGE
-Returns a collection of pages related the given one.
-
-```
-{{ $related := .Site.RegularPages.Related . }}
-```
-
-#### .RelatedIndices PAGE INDICE1 [INDICE2 ...]
-Returns a collection of pages related to a given one restricted to a list of indices.
-
-```
-{{ $related := .Site.RegularPages.RelatedIndices . "tags" "date" }}
-```
-
-#### .RelatedTo KEYVALS [KEYVALS2 ...]
-Returns a collection of pages related together by a set of indices and their match.
-
-In order to build those set and pass them as argument, one must use the `keyVals` function where the first argument would be the `indice` and the consecutive ones its potential `matches`.
-
-```
-{{ $related := .Site.RegularPages.RelatedTo ( keyVals "tags" "hugo" "rocks") ( keyVals "date" .Date ) }}
-```
-
-{{% note %}}
-Read [this blog article](https://regisphilibert.com/blog/2018/04/hugo-optmized-relashionships-with-related-content/) for a great explanation of more advanced usage of this feature.
-{{% /note %}}
-
-## Configure Related Content
-Hugo provides a sensible default configuration of Related Content, but you can fine-tune this in your configuration, on the global or language level if needed.
-
-### Default configuration
-
-Without any `related` configuration set on the project, Hugo's Related Content methods will use the following.
-
-{{< code-toggle file="config" >}}
-related:
- threshold: 80
- includeNewer: false
- toLower: false
- indices:
- - name: keywords
- weight: 100
- - name: date
- weight: 10
-{{< /code-toggle >}}
-
-Note that if you have configured `tags` as a taxonomy, `tags` will also be added to the default configuration above with the weight of `80`.
-
-Custom configuration should be set using the same syntax.
-
-{{% note %}}
-If you add a `related` config section, you need to add a complete configuration. It is not possible to just set, say, `includeNewer` and use the rest from the Hugo defaults.
-{{% /note %}}
-
-### Top Level Config Options
-
-threshold
-: A value between 0-100. Lower value will give more, but maybe not so relevant, matches.
-
-includeNewer
-: Set to true to include **pages newer than the current page** in the related content listing. This will mean that the output for older posts may change as new related content gets added.
-
-toLower
-: Set to true to lower case keywords in both the indexes and the queries. This may give more accurate results at a slight performance penalty. Note that this can also be set per index.
-
-### Config Options per Index
-
-name
-: The index name. This value maps directly to a page param. Hugo supports string values (`author` in the example) and lists (`tags`, `keywords` etc.) and time and date objects.
-
-weight
-: An integer weight that indicates _how important_ this parameter is relative to the other parameters. It can be 0, which has the effect of turning this index off, or even negative. Test with different values to see what fits your content best.
-
-pattern
-: This is currently only relevant for dates. When listing related content, we may want to list content that is also close in time. Setting "2006" (default value for date indexes) as the pattern for a date index will add weight to pages published in the same year. For busier blogs, "200601" (year and month) may be a better default.
-
-toLower
-: See above.
-
-## Performance Considerations
-
-**Fast is Hugo's middle name** and we would not have released this feature had it not been blistering fast.
-
-This feature has been in the back log and requested by many for a long time. The development got this recent kick start from this Twitter thread:
-
-{{< tweet user="scott_lowe" id="898398437527363585" >}}
-
-Scott S. Lowe removed the "Related Content" section built using the `intersect` template function on tags, and the build time dropped from 30 seconds to less than 2 seconds on his 1700 content page sized blog.
-
-He should now be able to add an improved version of that "Related Content" section without giving up the fast live-reloads. But it's worth noting that:
-
-* If you don't use any of the `Related` methods, you will not use the Relate Content feature, and performance will be the same as before.
-* Calling `.RegularPages.Related` etc. will create one inverted index, also sometimes named posting list, that will be reused for any lookups in that same page collection. Doing that in addition to, as an example, calling `.Pages.Related` will work as expected, but will create one additional inverted index. This should still be very fast, but worth having in mind, especially for bigger sites.
-
-{{% note %}}
-We currently do not index **Page content**. We thought we would release something that will make most people happy before we start solving [Sherlock's last case](https://github.com/joearms/sherlock).
-{{% /note %}}
diff --git a/docs/content/en/content-management/sections.md b/docs/content/en/content-management/sections.md
index 6806e342c10..3dfad5bd4f2 100644
--- a/docs/content/en/content-management/sections.md
+++ b/docs/content/en/content-management/sections.md
@@ -1,101 +1,139 @@
---
-title: Content Sections
-linktitle: Sections
-description: "Hugo generates a **section tree** that matches your content."
-date: 2017-02-01
-publishdate: 2017-02-01
-lastmod: 2017-02-01
-categories: [content management]
-keywords: [lists,sections,content types,organization]
-menu:
- docs:
- parent: "content-management"
- weight: 50
-weight: 50 #rem
-draft: false
+title: Sections
+description: Organize content into sections.
+
+categories: []
+keywords: []
aliases: [/content/sections/]
-toc: true
---
-A **Section** is a collection of pages that gets defined based on the
-organization structure under the `content/` directory.
+## Overview
+
+{{% glossary-term "section" %}}
+
+```text
+content/
+├── articles/ <-- section (top-level directory)
+│ ├── 2022/
+│ │ ├── article-1/
+│ │ │ ├── cover.jpg
+│ │ │ └── index.md
+│ │ └── article-2.md
+│ └── 2023/
+│ ├── article-3.md
+│ └── article-4.md
+├── products/ <-- section (top-level directory)
+│ ├── product-1/ <-- section (has _index.md file)
+│ │ ├── benefits/ <-- section (has _index.md file)
+│ │ │ ├── _index.md
+│ │ │ ├── benefit-1.md
+│ │ │ └── benefit-2.md
+│ │ ├── features/ <-- section (has _index.md file)
+│ │ │ ├── _index.md
+│ │ │ ├── feature-1.md
+│ │ │ └── feature-2.md
+│ │ └── _index.md
+│ └── product-2/ <-- section (has _index.md file)
+│ ├── benefits/ <-- section (has _index.md file)
+│ │ ├── _index.md
+│ │ ├── benefit-1.md
+│ │ └── benefit-2.md
+│ ├── features/ <-- section (has _index.md file)
+│ │ ├── _index.md
+│ │ ├── feature-1.md
+│ │ └── feature-2.md
+│ └── _index.md
+├── _index.md
+└── about.md
+```
-By default, all the **first-level** directories under `content/` form their own
-sections (**root sections**) provided they constitute [Branch Bundles][branch bundles].
-Directories which are just [Leaf Bundles][leaf bundles] do *not* form
-their own sections, despite being first-level directories.
+The example above has two top-level sections: articles and products. None of the directories under articles are sections, while all of the directories under products are sections. A section within a section is a known as a nested section or subsection.
-If a user needs to define a section `foo` at a deeper level, they need to create
-a directory named `foo` with an `_index.md` file (see [Branch Bundles][branch bundles]
-for more information).
+## Explanation
+Sections and non-sections behave differently.
-{{% note %}}
-A **section** cannot be defined or overridden by a front matter parameter -- it
-is strictly derived from the content organization structure.
-{{% /note %}}
+ |Sections|Non-sections
+:--|:-:|:-:
+Directory names become URL segments|:heavy_check_mark:|:heavy_check_mark:
+Have logical ancestors and descendants|:heavy_check_mark:|:x:
+Have list pages|:heavy_check_mark:|:x:
-## Nested Sections
+With the file structure from the [example above](#overview):
-The sections can be nested as deeply as you need.
+1. The list page for the articles section includes all articles, regardless of directory structure; none of the subdirectories are sections.
+1. The articles/2022 and articles/2023 directories do not have list pages; they are not sections.
+1. The list page for the products section, by default, includes product-1 and product-2, but not their descendant pages. To include descendant pages, use the `RegularPagesRecursive` method instead of the `Pages` method in the _section_ template.
+1. All directories in the products section have list pages; each directory is a section.
-```bash
-content
-└── blog <-- Section, because first-level dir under content/
- ├── funny-cats
- │ ├── mypost.md
- │ └── kittens <-- Section, because contains _index.md
- │ └── _index.md
- └── tech <-- Section, because contains _index.md
- └── _index.md
-```
+## Template selection
-**The important part to understand is, that to make the section tree fully navigational, at least the lower-most section needs a content file. (e.g. `_index.md`).**
+Hugo has a defined [lookup order] to determine which template to use when rendering a page. The [lookup rules] consider the top-level section name; subsection names are not considered when selecting a template.
-{{% note %}}
-When we talk about a **section** in correlation with template selection, it is
-currently always the *root section* only (`/blog/funny-cats/mypost/ => blog`).
+With the file structure from the [example above](#overview):
-If you need a specific template for a sub-section, you need to adjust either the `type` or `layout` in front matter.
-{{% /note %}}
+Content directory|Section template
+:--|:--
+`content/products`|`layouts/products/section.html`
+`content/products/product-1`|`layouts/products/section.html`
+`content/products/product-1/benefits`|`layouts/products/section.html`
-## Example: Breadcrumb Navigation
+Content directory|Page template
+:--|:--
+`content/products`|`layouts/products/page.html`
+`content/products/product-1`|`layouts/products/page.html`
+`content/products/product-1/benefits`|`layouts/products/page.html`
-With the available [section variables and methods](#section-page-variables-and-methods) you can build powerful navigation. One common example would be a partial to show Breadcrumb navigation:
+If you need to use a different template for a subsection, specify `type` and/or `layout` in front matter.
-{{< code file="layouts/partials/breadcrumb.html" download="breadcrumb.html" >}}
-
- {{ template "breadcrumbnav" (dict "p1" . "p2" .) }}
-
-{{ define "breadcrumbnav" }}
-{{ if .p1.Parent }}
-{{ template "breadcrumbnav" (dict "p1" .p1.Parent "p2" .p2 ) }}
-{{ else if not .p1.IsHome }}
-{{ template "breadcrumbnav" (dict "p1" .p1.Site.Home "p2" .p2 ) }}
-{{ end }}
-
-{{ end }}
-{{< /code >}}
+## Ancestors and descendants
-## Section Page Variables and Methods
+A section has one or more ancestors (including the home page), and zero or more descendants. With the file structure from the [example above](#overview):
-Also see [Page Variables](/variables/page/).
+```text
+content/products/product-1/benefits/benefit-1.md
+```
-{{< readfile file="/content/en/readfiles/sectionvars.md" markdown="true" >}}
+The content file (benefit-1.md) has four ancestors: benefits, product-1, products, and the home page. This logical relationship allows us to use the `.Parent` and `.Ancestors` methods to traverse the site structure.
+
+For example, use the `.Ancestors` method to render breadcrumb navigation.
+
+```go-html-template {file="layouts/_partials/breadcrumb.html"}
+
+```
-## Content Section Lists
+With this CSS:
-Hugo will automatically create pages for each *root section* that list all of the content in that section. See the documentation on [section templates][] for details on customizing the way these pages are rendered.
+```css
+.breadcrumb ol {
+ padding-left: 0;
+}
-## Content *Section* vs Content *Type*
+.breadcrumb li {
+ display: inline;
+}
-By default, everything created within a section will use the [content `type`][content type] that matches the *root section* name. For example, Hugo will assume that `posts/post-1.md` has a `posts` content `type`. If you are using an [archetype][] for your `posts` section, Hugo will generate front matter according to what it finds in `archetypes/posts.md`.
+.breadcrumb li:not(:last-child)::after {
+ content: "»";
+}
+```
+
+Hugo renders this, where each breadcrumb is a link to the corresponding page:
+
+```text
+Home » Products » Product 1 » Benefits » Benefit 1
+```
-[archetype]: /content-management/archetypes/
-[content type]: /content-management/types/
-[directory structure]: /getting-started/directory-structure/
-[section templates]: /templates/section-templates/
-[leaf bundles]: /content-management/page-bundles/#leaf-bundles
-[branch bundles]: /content-management/page-bundles/#branch-bundles
+[lookup order]: /templates/lookup-order/
+[lookup rules]: /templates/lookup-order/#lookup-rules
diff --git a/docs/content/en/content-management/shortcodes.md b/docs/content/en/content-management/shortcodes.md
index 013532ce49a..7a1f1b83262 100644
--- a/docs/content/en/content-management/shortcodes.md
+++ b/docs/content/en/content-management/shortcodes.md
@@ -1,444 +1,231 @@
---
title: Shortcodes
-linktitle:
-description: Shortcodes are simple snippets inside your content files calling built-in or custom templates.
-date: 2017-02-01
-publishdate: 2017-02-01
-lastmod: 2019-11-07
-menu:
- docs:
- parent: "content-management"
- weight: 35
-weight: 35 #rem
-categories: [content management]
-keywords: [markdown,content,shortcodes]
-draft: false
+description: Use embedded, custom, or inline shortcodes to insert elements such as videos, images, and social media embeds into your content.
+categories: []
+keywords: []
aliases: [/extras/shortcodes/]
-testparam: "Hugo Rocks!"
-toc: true
---
-## What a Shortcode is
+## Introduction
-Hugo loves Markdown because of its simple content format, but there are times when Markdown falls short. Often, content authors are forced to add raw HTML (e.g., video `
` Tag"%}}
-Hugo injects the LiveReload `
-```
+See the [`resources.Fingerprint`](/functions/resources/fingerprint/) function.
diff --git a/docs/content/en/hugo-pipes/introduction.md b/docs/content/en/hugo-pipes/introduction.md
index 7cace938a71..fa9493f202a 100755
--- a/docs/content/en/hugo-pipes/introduction.md
+++ b/docs/content/en/hugo-pipes/introduction.md
@@ -1,109 +1,63 @@
---
-title: Hugo Pipes Introduction
-linkTitle: Hugo Pipes
+title: Hugo Pipes
+linkTitle: Introduction
description: Hugo Pipes is Hugo's asset processing set of functions.
-date: 2018-07-14
-publishdate: 2018-07-14
-lastmod: 2018-07-14
-categories: [asset management]
+categories: []
keywords: []
-menu:
- docs:
- parent: "pipes"
- weight: 20
-weight: 01
-sections_weight: 01
-draft: false
-toc: true
+weight: 10
aliases: [/assets/]
---
-## Get Resource with resources.Get
+## Find resources in assets
-In order to process an asset with Hugo Pipes, it must be retrieved as a `Resource` using `resources.Get`. The first argument can be either a local the path to file relative to the `asset` directory/directories or a remote URL.
+This is about global and remote resources.
-```go-html-template
-{{ $local := resources.Get "sass/main.scss" }}
-{{ $remote := resources.Get "https://www.example.com/styles.scss" }}
-```
+global resource
+: A file within the `assets` directory, or within any directory [mounted] to the `assets` directory.
-`resources.Get` will always return `nil` if the resource could not be found.
+remote resource
+: A file on a remote server, accessible via HTTP or HTTPS.
-### Error Handling
+For `.Page` scoped resources, see the [page resources] section.
-{{< new-in "0.90.1" >}}
+[mounted]: /configuration/module/#mounts
+[page resources]: /content-management/page-resources/
-The return value from `resources.Get` includes an `.Err` method that will return an error if the call failed. If you want to just log any error as a `WARNING` you can use a construct similar to the one below.
+## Get a resource
-```go-html-template
-{{ with resources.Get "https://gohugo.io/images/gohugoio-card-1.png" }}
- {{ with .Err }}
- {{ warnf "%s" . }}
- {{ else }}
-
- {{ end }}
-{{ end }}
-```
+In order to process an asset with Hugo Pipes, it must be retrieved as a resource.
-Note that if you do not handle `.Err` yourself, Hugo will fail the build the first time you start using the `Resource` object.
+For global resources, use:
-### Remote Options
+- [`resources.ByType`](/functions/resources/bytype/)
+- [`resources.Get`](/functions/resources/get/)
+- [`resources.GetMatch`](/functions/resources/getmatch/)
+- [`resources.Match`](/functions/resources/match/)
-When fetching a remote `Resource`, `resources.Get` takes an optional options map as the last argument, e.g.:
+For remote resources, use:
-```go-html-template
-{{ $resource := resources.Get "https://example.org/api" (dict "headers" (dict "Authorization" "Bearer abcd")) }}
-```
+- [`resources.GetRemote`](/functions/resources/getremote/)
-If you need multiple values for the same header key, use a slice:
+See the [GoDoc Page](https://pkg.go.dev/github.com/gohugoio/hugo/tpl/resources) for the `resources` package for an up to date overview of all template functions in this namespace.
-```go-html-template
-{{ $resource := resources.Get "https://example.org/api" (dict "headers" (dict "X-List" (slice "a" "b" "c"))) }}
-```
-
-You can also change the request method and set the request body:
+## Copy a resource
-```go-html-template
-{{ $postResponse := resources.Get "https://example.org/api" (dict
- "method" "post"
- "body" `{"complete": true}`
- "headers" (dict
- "Content-Type" "application/json"
- )
-)}}
-```
-
-### Caching of Remote Resources
-
-Remote resources fetched with `resources.Get` will be cached on disk. See [Configure File Caches](/getting-started/configuration/#configure-file-caches) for details.
+See the [`resources.Copy`](/functions/resources/copy/) function.
## Asset directory
-Asset files must be stored in the asset directory. This is `/assets` by default, but can be configured via the configuration file's `assetDir` key.
-
+Asset files must be stored in the asset directory. This is `assets` by default, but can be configured via the configuration file's `assetDir` key.
-### Asset Publishing
+## Asset publishing
-Assets will only be published (to `/public`) if `.Permalink` or `.RelPermalink` is used. You can use `.Content` to inline the asset.
+Hugo publishes assets to the `publishDir` (typically `public`) when you invoke `.Permalink`, `.RelPermalink`, or `.Publish`. You can use `.Content` to inline the asset.
## Go Pipes
For improved readability, the Hugo Pipes examples of this documentation will be written using [Go Pipes](/templates/introduction/#pipes):
```go-html-template
-{{ $style := resources.Get "sass/main.scss" | resources.ToCSS | resources.Minify | resources.Fingerprint }}
-
-```
-
-## Method aliases
-
-Each Hugo Pipes `resources` transformation method uses a __camelCased__ alias (`toCSS` for `resources.ToCSS`).
-Non-transformation methods deprived of such aliases are `resources.Get`, `resources.FromString`, `resources.ExecuteAsTemplate` and `resources.Concat`.
-
-The example above can therefore also be written as follows:
-
-```go-html-template
-{{ $style := resources.Get "sass/main.scss" | toCSS | minify | fingerprint }}
+{{ $style := resources.Get "sass/main.scss" | css.Sass | resources.Minify | resources.Fingerprint }}
```
diff --git a/docs/content/en/hugo-pipes/js.md b/docs/content/en/hugo-pipes/js.md
index 63bd8bdd95a..18572d5386f 100644
--- a/docs/content/en/hugo-pipes/js.md
+++ b/docs/content/en/hugo-pipes/js.md
@@ -1,171 +1,9 @@
---
-title: JavaScript Building
-description: Hugo Pipes can process JavaScript files with [ESBuild](https://github.com/evanw/esbuild).
-date: 2020-07-20
-publishdate: 2020-07-20
-lastmod: 2020-07-20
-categories: [asset management]
+title: JavaScript
+linkTitle: JavaScript building
+description: Bundle, transpile, tree shake, code split, and minify JavaScript resources.
+categories: []
keywords: []
-menu:
- docs:
- parent: "pipes"
- weight: 45
-weight: 45
-sections_weight: 45
-draft: false
---
-Any JavaScript resource file can be transpiled and "tree shaken" using `js.Build` which takes for argument either a string for the filepath or a dict of options listed below.
-
-### Options
-
-targetPath [string]
-: If not set, the source path will be used as the base target path.
-Note that the target path's extension may change if the target MIME type is different, e.g. when the source is TypeScript.
-
-params [map or slice] {{< new-in "0.78.0" >}}
-: Params that can be imported as JSON in your JS files, e.g.:
-
-```go-html-template
-{{ $js := resources.Get "js/main.js" | js.Build (dict "params" (dict "api" "https://example.org/api")) }}
-```
-And then in your JS file:
-
-```js
-import * as params from '@params';
-```
-
-Note that this is meant for small data sets, e.g. config settings. For larger data, please put/mount the files into `/assets` and import them directly.
-
-minify [bool]
-: Let `js.Build` handle the minification.
-
-inject [slice] {{< new-in "0.81.0" >}}
-: This option allows you to automatically replace a global variable with an import from another file. The path names must be relative to `assets`. See https://esbuild.github.io/api/#inject
-
-shims {{< new-in "0.81.0" >}}
-: This option allows swapping out a component with another. A common use case is to load dependencies like React from a CDN (with _shims_) when in production, but running with the full bundled `node_modules` dependency during development:
-
-```
-{{ $shims := dict "react" "js/shims/react.js" "react-dom" "js/shims/react-dom.js" }}
-{{ $js = $js | js.Build dict "shims" $shims }}
-```
-
-The _shim_ files may look like these:
-
-```js
-// js/shims/react.js
-module.exports = window.React;
-```
-
-```js
-// js/shims/react-dom.js
-module.exports = window.ReactDOM;
-```
-
-
-With the above, these imports should work in both scenarios:
-
-```js
-import * as React from 'react'
-import * as ReactDOM from 'react-dom';
-```
-
-target [string]
-: The language target.
- One of: `es5`, `es2015`, `es2016`, `es2017`, `es2018`, `es2019`, `es2020` or `esnext`.
- Default is `esnext`.
-
-externals [slice]
-: External dependencies. Use this to trim dependencies you know will never be executed. See https://esbuild.github.io/api/#external
-
-
-defines [map]
-: Allow to define a set of string replacement to be performed when building. Should be a map where each key is to be replaced by its value.
-
-```go-html-template
-{{ $defines := dict "process.env.NODE_ENV" `"development"` }}
-```
-
-format [string] {{< new-in "0.74.3" >}}
-: The output format.
- One of: `iife`, `cjs`, `esm`.
- Default is `iife`, a self-executing function, suitable for inclusion as a
-```
-
-
+See [JS functions](/functions/js/).
diff --git a/docs/content/en/hugo-pipes/minification.md b/docs/content/en/hugo-pipes/minification.md
index d11ee58a90e..4ba1ea641f2 100755
--- a/docs/content/en/hugo-pipes/minification.md
+++ b/docs/content/en/hugo-pipes/minification.md
@@ -1,27 +1,9 @@
---
-title: Asset minification
-description: Hugo Pipes allows the minification of any CSS, JS, JSON, HTML, SVG or XML resource.
-date: 2018-07-14
-publishdate: 2018-07-14
-lastmod: 2018-07-14
-categories: [asset management]
+title: Minify
+linkTitle: Asset minification
+description: Minify a given resource.
+categories: []
keywords: []
-menu:
- docs:
- parent: "pipes"
- weight: 50
-weight: 50
-sections_weight: 50
-draft: false
---
-
-Any resource of the aforementioned types can be minified using `resources.Minify` which takes for argument the resource object.
-
-
-```go-html-template
-{{ $css := resources.Get "css/main.css" }}
-{{ $style := $css | resources.Minify }}
-```
-
-Note that you can also minify the final HTML output to `/public` by running `hugo --minify`.
+See the [`resources.Minify`](/functions/resources/minify/) function.
diff --git a/docs/content/en/hugo-pipes/postcss.md b/docs/content/en/hugo-pipes/postcss.md
index fddc7e9cf10..1b47e8aefcc 100755
--- a/docs/content/en/hugo-pipes/postcss.md
+++ b/docs/content/en/hugo-pipes/postcss.md
@@ -1,84 +1,8 @@
---
title: PostCSS
-description: Hugo Pipes can process CSS files with PostCSS.
-date: 2018-07-14
-publishdate: 2018-07-14
-lastmod: 2018-07-14
-categories: [asset management]
+description: Process the given resource with PostCSS using any PostCSS plugin.
+categories: []
keywords: []
-menu:
- docs:
- parent: "pipes"
- weight: 40
-weight: 40
-sections_weight: 40
-draft: false
---
-Any asset file can be processed using `resources.PostCSS` which takes for argument the resource object and a slice of options listed below.
-
-The resource will be processed using the project's or theme's own `postcss.config.js` or any file set with the `config` option.
-
-```go-html-template
-{{ $css := resources.Get "css/main.css" }}
-{{ $style := $css | resources.PostCSS }}
-```
-
-{{% note %}}
-Hugo Pipe's PostCSS requires the `postcss-cli` JavaScript package to be installed in the environment (`npm install -g postcss postcss-cli`) along with any PostCSS plugin(s) used (e.g., `npm install -g autoprefixer`).
-
-If you are using the Hugo Snap package, PostCSS and plugin(s) need to be installed locally within your Hugo site directory, e.g., `npm install postcss-cli` without the `-g` flag.
-{{% /note %}}
-
-### Options
-
-config [string]
-: Path to the PostCSS configuration file
-
-noMap [bool]
-: Default is `false`. Disable the default inline sourcemaps
-
-inlineImports [bool] {{< new-in "0.66.0" >}}
-: Default is `false`. Enable inlining of @import statements. It does so recursively, but will only import a file once.
-URL imports (e.g. `@import url('https://fonts.googleapis.com/css?family=Open+Sans&display=swap');`) and imports with media queries will be ignored.
-Note that this import routine does not care about the CSS spec, so you can have @import anywhere in the file.
-Hugo will look for imports relative to the module mount and will respect theme overrides.
-
-_If no configuration file is used:_
-
-use [string]
-: Space-delimited list of PostCSS plugins to use
-
-parser [string]
-: Custom PostCSS parser
-
-stringifier [string]
-: Custom PostCSS stringifier
-
-syntax [string]
-: Custom postcss syntax
-
-```go-html-template
-{{ $options := dict "config" "customPostCSS.js" "noMap" true }}
-{{ $style := resources.Get "css/main.css" | resources.PostCSS $options }}
-
-{{ $options := dict "use" "autoprefixer postcss-color-alpha" }}
-{{ $style := resources.Get "css/main.css" | resources.PostCSS $options }}
-```
-
-## Check Hugo Environment from postcss.config.js
-
-{{< new-in "0.66.0" >}}
-
-The current Hugo environment name (set by `--environment` or in config or OS environment) is available in the Node context, which allows constructs like this:
-
-```js
-module.exports = {
- plugins: [
- require('autoprefixer'),
- ...process.env.HUGO_ENVIRONMENT === 'production'
- ? [purgecss]
- : []
- ]
-}
-```
+See the [`css.PostCSS`](/functions/css/postcss/) function.
diff --git a/docs/content/en/hugo-pipes/postprocess.md b/docs/content/en/hugo-pipes/postprocess.md
index aafb786a473..72540fd5db4 100755
--- a/docs/content/en/hugo-pipes/postprocess.md
+++ b/docs/content/en/hugo-pipes/postprocess.md
@@ -1,70 +1,8 @@
---
title: PostProcess
-description: Allows delaying of resource transformations to after the build.
-date: 2020-04-09
-categories: [asset management]
+description: Process the given resource after the build.
+categories: []
keywords: []
-menu:
- docs:
- parent: "pipes"
- weight: 39
-weight: 39
-sections_weight: 39
---
-Marking a resource with `resources.PostProcess` delays any transformations to after the build, typically because one or more of the steps in the transformation chain depends on the result of the build (e.g. files in `public`).{{< new-in "0.69.0" >}}
-
-A prime use case for this is [CSS purging with PostCSS](#css-purging-with-postcss).
-
-There are currently two limitations to this:
-
-1. This only works in `*.html` templates (i.e. templates that produces HTML files).
-2. You cannot manipulate the values returned from the resource's methods. E.g. the `upper` in this example will not work as expected:
-
- ```go-html-template
- {{ $css := resources.Get "css/main.css" }}
- {{ $css = $css | resources.PostCSS | minify | fingerprint | resources.PostProcess }}
- {{ $css.RelPermalink | upper }}
- ```
-
-## CSS purging with PostCSS
-
-{{% note %}}
-There are several ways to set up CSS purging with PostCSS in Hugo. If you have a simple project, you should consider going the simpler route and drop the use of `resources.PostProcess` and just extract keywords from the templates. See the [Tailwind documentation](https://tailwindcss.com/docs/controlling-file-size/#app) for some examples.
-{{% /note %}}
-
-The below configuration will write a `hugo_stats.json` file to the project root as part of the build. If you're only using this for the production build, you should consider placing it below [config/production](/getting-started/configuration/#configuration-directory).
-
-{{< code-toggle file="config" >}}
-[build]
- writeStats = true
-{{< /code-toggle >}}
-
-`postcss.config.js`
-
-```js
-const purgecss = require('@fullhuman/postcss-purgecss')({
- content: [ './hugo_stats.json' ],
- defaultExtractor: (content) => {
- let els = JSON.parse(content).htmlElements;
- return els.tags.concat(els.classes, els.ids);
- }
-});
-
-module.exports = {
- plugins: [
- ...(process.env.HUGO_ENVIRONMENT === 'production' ? [ purgecss ] : [])
- ]
- };
-```
-
-Note that in the example above, the "CSS purge step" will only be applied to the production build. This means that you need to do something like this in your head template to build and include your CSS:
-
-```go-html-template
-{{ $css := resources.Get "css/main.css" }}
-{{ $css = $css | resources.PostCSS }}
-{{ if hugo.IsProduction }}
-{{ $css = $css | minify | fingerprint | resources.PostProcess }}
-{{ end }}
-
-```
+See the [`resources.PostProcess`](/functions/resources/postprocess/) function.
diff --git a/docs/content/en/hugo-pipes/resource-from-string.md b/docs/content/en/hugo-pipes/resource-from-string.md
index 8b942d2f32b..ed5eaff8029 100755
--- a/docs/content/en/hugo-pipes/resource-from-string.md
+++ b/docs/content/en/hugo-pipes/resource-from-string.md
@@ -1,31 +1,9 @@
---
-title: Creating a resource from a string
-linkTitle: Resource from String
-description: Hugo Pipes allows the creation of a resource from a string.
-date: 2018-07-14
-publishdate: 2018-07-14
-lastmod: 2018-07-14
-categories: [asset management]
+title: FromString
+linkTitle: Resource from string
+description: Create a resource from a string.
+categories: []
keywords: []
-menu:
- docs:
- parent: "pipes"
- weight: 90
-weight: 90
-sections_weight: 90
-draft: false
---
-It is possible to create a resource directly from the template using `resources.FromString` which takes two arguments, the given string and the resource target path.
-
-The following example creates a resource file containing localized variables for every project's languages.
-
-```go-html-template
-{{ $string := (printf "var rootURL = '%s'; var apiURL = '%s';" (absURL "/") (.Param "API_URL")) }}
-{{ $targetPath := "js/vars.js" }}
-{{ $vars := $string | resources.FromString $targetPath }}
-{{ $global := resources.Get "js/global.js" | resources.Minify }}
-
-
-
-```
+See the [`resources.FromString`](/functions/resources/fromstring/) function.
diff --git a/docs/content/en/hugo-pipes/resource-from-template.md b/docs/content/en/hugo-pipes/resource-from-template.md
index 9bc8c7e9d9b..bc50f630b48 100755
--- a/docs/content/en/hugo-pipes/resource-from-template.md
+++ b/docs/content/en/hugo-pipes/resource-from-template.md
@@ -1,38 +1,9 @@
---
-title: Creating a resource from template
-linkTitle: Resource from Template
-description: Hugo Pipes allows the creation of a resource from an asset file using Go Template.
-date: 2018-07-14
-publishdate: 2018-07-14
-lastmod: 2018-07-14
-categories: [asset management]
+title: ExecuteAsTemplate
+linkTitle: Resource from template
+description: Create a resource from a Go template, parsed and executed with the given context.
+categories: []
keywords: []
-menu:
- docs:
- parent: "pipes"
- weight: 80
-weight: 80
-sections_weight: 80
-draft: false
---
-In order to use Hugo Pipes function on an asset file containing Go Template magic the function `resources.ExecuteAsTemplate` must be used.
-
-The function takes three arguments: the resource target path, the template context, and the resource object.
-
-```go-html-template
-// assets/sass/template.scss
-$backgroundColor: {{ .Param "backgroundColor" }};
-$textColor: {{ .Param "textColor" }};
-body{
- background-color:$backgroundColor;
- color: $textColor;
-}
-// [...]
-```
-
-
-```go-html-template
-{{ $sassTemplate := resources.Get "sass/template.scss" }}
-{{ $style := $sassTemplate | resources.ExecuteAsTemplate "main.scss" . | resources.ToCSS }}
-```
+See the [`resources.ExecuteAsTemplate`](/functions/resources/executeastemplate/) function.
diff --git a/docs/content/en/hugo-pipes/scss-sass.md b/docs/content/en/hugo-pipes/scss-sass.md
deleted file mode 100755
index 12cebc0142c..00000000000
--- a/docs/content/en/hugo-pipes/scss-sass.md
+++ /dev/null
@@ -1,54 +0,0 @@
----
-title: SASS / SCSS
-description: Hugo Pipes allows the processing of SASS and SCSS files.
-date: 2018-07-14
-publishdate: 2018-07-14
-lastmod: 2018-07-14
-categories: [asset management]
-keywords: []
-menu:
- docs:
- parent: "pipes"
- weight: 30
-weight: 02
-sections_weight: 02
-draft: false
----
-
-
-Any SASS or SCSS file can be transformed into a CSS file using `resources.ToCSS` which takes two arguments, the resource object and a map of options listed below.
-
-```go-html-template
-{{ $sass := resources.Get "sass/main.scss" }}
-{{ $style := $sass | resources.ToCSS }}
-```
-
-### Options
-
-transpiler [string] {{< new-in "0.80.0" >}}
-
-: The `transpiler` to use, valid values are `libsass` (default) and `dartsass`. Note that the Embedded Dart Sass project is still in beta. We will try to improve the installation process when it has stable releases, but if you want to use Hugo with Dart Sass you need to download a release binary from [Embedded Dart Sass](https://github.com/sass/dart-sass-embedded/releases) (Hugo after 0.81.0 requires beta 6 or newer) and make sure it's in your PC's `$PATH` (or `%PATH%` on Windows).
-
-targetPath [string]
-: If not set, the resource's target path will be the asset file original path with its extension replaced by `.css`.
-
-outputStyle [string]
-: Default is `nested` (LibSass) and `expanded` (Dart Sass). Other available output styles for LibSass are `expanded`, `compact` and `compressed`. Dart Sass only supports `expanded` and `compressed`.
-
-precision [int]
-: Precision of floating point math. **Note:** This option is not supported by Dart Sass.
-
-enableSourceMap [bool]
-: When enabled, a source map will be generated.
-
-includePaths [string slice]
-: Additional SCSS/SASS include paths. Paths must be relative to the project directory.
-
-```go-html-template
-{{ $options := (dict "targetPath" "style.css" "outputStyle" "compressed" "enableSourceMap" true "includePaths" (slice "node_modules/myscss")) }}
-{{ $style := resources.Get "sass/main.scss" | resources.ToCSS $options }}
-```
-
-{{% note %}}
-Setting `outputStyle` to `compressed` will handle SASS/SCSS files minification better than the more generic [`resources.Minify`]({{< ref "minification">}}).
-{{% /note %}}
diff --git a/docs/content/en/hugo-pipes/transpile-sass-to-css.md b/docs/content/en/hugo-pipes/transpile-sass-to-css.md
new file mode 100644
index 00000000000..75691429748
--- /dev/null
+++ b/docs/content/en/hugo-pipes/transpile-sass-to-css.md
@@ -0,0 +1,10 @@
+---
+title: ToCSS
+linkTitle: Transpile Sass to CSS
+description: Transpile Sass to CSS.
+categories: []
+keywords: []
+aliases: [/hugo-pipes/transform-to-css/]
+---
+
+See the [`css.Sass`](/functions/css/sass) function.
diff --git a/docs/content/en/installation/_index.md b/docs/content/en/installation/_index.md
new file mode 100644
index 00000000000..fdcb8f9eb8f
--- /dev/null
+++ b/docs/content/en/installation/_index.md
@@ -0,0 +1,8 @@
+---
+title: Installation
+description: Install Hugo on macOS, Linux, Windows, BSD, and on any machine that can run the Go compiler tool chain.
+categories: []
+keywords: []
+weight: 10
+aliases: [/getting-started/installing/]
+---
diff --git a/docs/content/en/installation/bsd.md b/docs/content/en/installation/bsd.md
new file mode 100644
index 00000000000..7b8e8c82c97
--- /dev/null
+++ b/docs/content/en/installation/bsd.md
@@ -0,0 +1,71 @@
+---
+title: BSD
+description: Install Hugo on BSD derivatives.
+categories: []
+keywords: []
+weight: 40
+---
+
+{{% include "/_common/installation/01-editions.md" %}}
+
+{{% include "/_common/installation/02-prerequisites.md" %}}
+
+{{% include "/_common/installation/03-prebuilt-binaries.md" %}}
+
+## Repository packages
+
+Most BSD derivatives maintain a repository for commonly installed applications. Please note that these repositories may not contain the [latest release].
+
+[latest release]: https://github.com/gohugoio/hugo/releases/latest
+
+### DragonFly BSD
+
+[DragonFly BSD] includes Hugo in its package repository. To install the extended edition of Hugo:
+
+```sh
+sudo pkg install gohugo
+```
+
+[DragonFly BSD]: https://www.dragonflybsd.org/
+
+### FreeBSD
+
+[FreeBSD] includes Hugo in its package repository. To install the extended edition of Hugo:
+
+```sh
+sudo pkg install gohugo
+```
+
+[FreeBSD]: https://www.freebsd.org/
+
+### NetBSD
+
+[NetBSD] includes Hugo in its package repository. To install the extended edition of Hugo:
+
+```sh
+sudo pkgin install go-hugo
+```
+
+[NetBSD]: https://www.netbsd.org/
+
+### OpenBSD
+
+[OpenBSD] includes Hugo in its package repository. This will prompt you to select which edition of Hugo to install:
+
+```sh
+doas pkg_add hugo
+```
+
+[OpenBSD]: https://www.openbsd.org/
+
+{{% include "/_common/installation/04-build-from-source.md" %}}
+
+## Comparison
+
+ |Prebuilt binaries|Repository packages|Build from source
+:--|:--:|:--:|:--:
+Easy to install?|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:
+Easy to upgrade?|:heavy_check_mark:|varies|:heavy_check_mark:
+Easy to downgrade?|:heavy_check_mark:|varies|:heavy_check_mark:
+Automatic updates?|:x:|varies|:x:
+Latest version available?|:heavy_check_mark:|varies|:heavy_check_mark:
diff --git a/docs/content/en/installation/linux.md b/docs/content/en/installation/linux.md
new file mode 100644
index 00000000000..6e8c9cd49c6
--- /dev/null
+++ b/docs/content/en/installation/linux.md
@@ -0,0 +1,216 @@
+---
+title: Linux
+description: Install Hugo on Linux.
+categories: []
+keywords: []
+weight: 20
+---
+
+{{% include "/_common/installation/01-editions.md" %}}
+
+{{% include "/_common/installation/02-prerequisites.md" %}}
+
+{{% include "/_common/installation/03-prebuilt-binaries.md" %}}
+
+## Package managers
+
+### Snap
+
+[Snap] is a free and open-source package manager for Linux. Available for [most distributions], snap packages are simple to install and are automatically updated.
+
+The Hugo snap package is [strictly confined]. Strictly confined snaps run in complete isolation, up to a minimal access level that's deemed always safe. The sites you create and build must be located within your home directory, or on removable media.
+
+To install the extended edition of Hugo:
+
+```sh
+sudo snap install hugo
+```
+
+To control automatic updates:
+
+```sh
+# disable automatic updates
+sudo snap refresh --hold hugo
+
+# enable automatic updates
+sudo snap refresh --unhold hugo
+```
+
+To control access to removable media:
+
+```sh
+# allow access
+sudo snap connect hugo:removable-media
+
+# revoke access
+sudo snap disconnect hugo:removable-media
+```
+
+To control access to SSH keys:
+
+```sh
+# allow access
+sudo snap connect hugo:ssh-keys
+
+# revoke access
+sudo snap disconnect hugo:ssh-keys
+```
+
+[strictly confined]: https://snapcraft.io/docs/snap-confinement
+[most distributions]: https://snapcraft.io/docs/installing-snapd
+[Snap]: https://snapcraft.io/
+
+{{% include "/_common/installation/homebrew.md" %}}
+
+## Repository packages
+
+Most Linux distributions maintain a repository for commonly installed applications.
+
+> [!note]
+> The Hugo version available in package repositories varies based on Linux distribution and release, and in some cases will not be the [latest version].
+>
+> Use one of the other installation methods if your package repository does not provide the desired version.
+
+### Alpine Linux
+
+To install the extended edition of Hugo on [Alpine Linux]:
+
+```sh
+doas apk add --no-cache --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community hugo
+```
+
+### Arch Linux
+
+Derivatives of the [Arch Linux] distribution of Linux include [EndeavourOS], [Garuda Linux], [Manjaro], and others. To install the extended edition of Hugo:
+
+```sh
+sudo pacman -S hugo
+```
+
+### Debian
+
+Derivatives of the [Debian] distribution of Linux include [elementary OS], [KDE neon], [Linux Lite], [Linux Mint], [MX Linux], [Pop!_OS], [Ubuntu], [Zorin OS], and others. To install the extended edition of Hugo:
+
+```sh
+sudo apt install hugo
+```
+
+You can also download Debian packages from the [latest release][] page.
+
+### Exherbo
+
+To install the extended edition of Hugo on [Exherbo]:
+
+1. Add this line to /etc/paludis/options.conf:
+
+ ```text
+ www-apps/hugo extended
+ ```
+
+1. Install using the Paludis package manager:
+
+ ```sh
+ cave resolve -x repository/heirecka
+ cave resolve -x hugo
+ ```
+
+### Fedora
+
+Derivatives of the [Fedora] distribution of Linux include [CentOS], [Red Hat Enterprise Linux], and others. To install the extended edition of Hugo:
+
+```sh
+sudo dnf install hugo
+```
+
+### Gentoo
+
+Derivatives of the [Gentoo] distribution of Linux include [Calculate Linux], [Funtoo], and others. To install the extended edition of Hugo:
+
+1. Specify the `extended` [USE] flag in /etc/portage/package.use/hugo:
+
+ ```text
+ www-apps/hugo extended
+ ```
+
+1. Build using the Portage package manager:
+
+ ```sh
+ sudo emerge www-apps/hugo
+ ```
+
+### NixOS
+
+The NixOS distribution of Linux includes Hugo in its package repository. To install the extended edition of Hugo:
+
+```sh
+nix-env -iA nixos.hugo
+```
+
+### openSUSE
+
+Derivatives of the [openSUSE] distribution of Linux include [GeckoLinux], [Linux Karmada], and others. To install the extended edition of Hugo:
+
+```sh
+sudo zypper install hugo
+```
+
+### Solus
+
+The [Solus] distribution of Linux includes Hugo in its package repository. To install the extended edition of Hugo:
+
+```sh
+sudo eopkg install hugo
+```
+
+### Void Linux
+
+To install the extended edition of Hugo on [Void Linux]:
+
+```sh
+sudo xbps-install -S hugo
+```
+
+{{% include "/_common/installation/04-build-from-source.md" %}}
+
+## Comparison
+
+ |Prebuilt binaries|Package managers|Repository packages|Build from source
+:--|:--:|:--:|:--:|:--:
+Easy to install?|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:
+Easy to upgrade?|:heavy_check_mark:|:heavy_check_mark:|varies|:heavy_check_mark:
+Easy to downgrade?|:heavy_check_mark:|:heavy_check_mark: [^1]|varies|:heavy_check_mark:
+Automatic updates?|:x:|varies [^2]|:x:|:x:
+Latest version available?|:heavy_check_mark:|:heavy_check_mark:|varies|:heavy_check_mark:
+
+[^1]: Easy if a previous version is still installed.
+[^2]: Snap packages are automatically updated. Homebrew requires advanced configuration.
+
+[Alpine Linux]: https://alpinelinux.org/
+[Arch Linux]: https://archlinux.org/
+[Calculate Linux]: https://www.calculate-linux.org/
+[CentOS]: https://www.centos.org/
+[Debian]: https://www.debian.org/
+[elementary OS]: https://elementary.io/
+[EndeavourOS]: https://endeavouros.com/
+[Exherbo]: https://www.exherbolinux.org/
+[Fedora]: https://getfedora.org/
+[Funtoo]: https://www.funtoo.org/
+[Garuda Linux]: https://garudalinux.org/
+[GeckoLinux]: https://geckolinux.github.io/
+[Gentoo]: https://www.gentoo.org/
+[KDE neon]: https://neon.kde.org/
+[latest release]: https://github.com/gohugoio/hugo/releases/latest
+[latest version]: https://github.com/gohugoio/hugo/releases/latest
+[Linux Karmada]: https://linuxkamarada.com/
+[Linux Lite]: https://www.linuxliteos.com/
+[Linux Mint]: https://linuxmint.com/
+[Manjaro]: https://manjaro.org/
+[MX Linux]: https://mxlinux.org/
+[openSUSE]: https://www.opensuse.org/
+[Pop!_OS]: https://pop.system76.com/
+[Red Hat Enterprise Linux]: https://www.redhat.com/
+[Solus]: https://getsol.us/
+[Ubuntu]: https://ubuntu.com/
+[USE]: https://packages.gentoo.org/packages/www-apps/hugo
+[Void Linux]: https://voidlinux.org/
+[Zorin OS]: https://zorin.com/os/
diff --git a/docs/content/en/installation/macos.md b/docs/content/en/installation/macos.md
new file mode 100644
index 00000000000..1984d1a8eeb
--- /dev/null
+++ b/docs/content/en/installation/macos.md
@@ -0,0 +1,42 @@
+---
+title: macOS
+description: Install Hugo on macOS.
+categories: []
+keywords: []
+weight: 10
+---
+
+{{% include "/_common/installation/01-editions.md" %}}
+
+{{% include "/_common/installation/02-prerequisites.md" %}}
+
+{{% include "/_common/installation/03-prebuilt-binaries.md" %}}
+
+## Package managers
+
+{{% include "/_common/installation/homebrew.md" %}}
+
+### MacPorts
+
+[MacPorts] is a free and open-source package manager for macOS. To install the extended edition of Hugo:
+
+```sh
+sudo port install hugo
+```
+
+[MacPorts]: https://www.macports.org/
+
+{{% include "/_common/installation/04-build-from-source.md" %}}
+
+## Comparison
+
+ |Prebuilt binaries|Package managers|Build from source
+:--|:--:|:--:|:--:
+Easy to install?|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:
+Easy to upgrade?|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:
+Easy to downgrade?|:heavy_check_mark:|:heavy_check_mark: [^1]|:heavy_check_mark:
+Automatic updates?|:x:|:x: [^2]|:x:
+Latest version available?|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:
+
+[^1]: Easy if a previous version is still installed.
+[^2]: Possible but requires advanced configuration.
diff --git a/docs/content/en/installation/windows.md b/docs/content/en/installation/windows.md
new file mode 100644
index 00000000000..8563ab7eed0
--- /dev/null
+++ b/docs/content/en/installation/windows.md
@@ -0,0 +1,70 @@
+---
+title: Windows
+description: Install Hugo on Windows.
+categories: []
+keywords: []
+weight: 30
+---
+
+> [!note]
+> Hugo v0.121.1 and later require at least Windows 10 or Windows Server 2016.
+
+{{% include "/_common/installation/01-editions.md" %}}
+
+{{% include "/_common/installation/02-prerequisites.md" %}}
+
+{{% include "/_common/installation/03-prebuilt-binaries.md" %}}
+
+## Package managers
+
+### Chocolatey
+
+[Chocolatey] is a free and open-source package manager for Windows. To install the extended edition of Hugo:
+
+```sh
+choco install hugo-extended
+```
+
+### Scoop
+
+[Scoop] is a free and open-source package manager for Windows. To install the extended edition of Hugo:
+
+```sh
+scoop install hugo-extended
+```
+
+### Winget
+
+[Winget] is Microsoft's official free and open-source package manager for Windows. To install the extended edition of Hugo:
+
+```sh
+winget install Hugo.Hugo.Extended
+```
+
+To uninstall the extended edition of Hugo:
+
+```sh
+winget uninstall --name "Hugo (Extended)"
+```
+
+{{% include "/_common/installation/04-build-from-source.md" %}}
+
+> [!note]
+> See these [detailed instructions](https://discourse.gohugo.io/t/41370) to install GCC on Windows.
+
+## Comparison
+
+ |Prebuilt binaries|Package managers|Build from source
+:--|:--:|:--:|:--:
+Easy to install?|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:
+Easy to upgrade?|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:
+Easy to downgrade?|:heavy_check_mark:|:heavy_check_mark: [^2]|:heavy_check_mark:
+Automatic updates?|:x:|:x: [^1]|:x:
+Latest version available?|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:
+
+[^1]: Possible but requires advanced configuration.
+[^2]: Easy if a previous version is still installed.
+
+[Chocolatey]: https://chocolatey.org/
+[Scoop]: https://scoop.sh/
+[Winget]: https://learn.microsoft.com/en-us/windows/package-manager/
diff --git a/docs/content/en/maintenance/_index.md b/docs/content/en/maintenance/_index.md
deleted file mode 100644
index 691a5d47ce0..00000000000
--- a/docs/content/en/maintenance/_index.md
+++ /dev/null
@@ -1,14 +0,0 @@
----
-title: Maintenance
-description: Some lists useful for the maintenance of the Hugo docs site.
-date: 2018-02-09
-categories: [maintenance]
-keywords: [maintenance]
-menu:
- docs:
- weight: 200
-slug:
-aliases: []
-toc: true
----
-
diff --git a/docs/content/en/methods/_index.md b/docs/content/en/methods/_index.md
new file mode 100644
index 00000000000..39f2b614607
--- /dev/null
+++ b/docs/content/en/methods/_index.md
@@ -0,0 +1,8 @@
+---
+title: Methods
+description: Use these methods within your templates.
+categories: []
+keywords: []
+weight: 10
+aliases: ['/variables/']
+---
diff --git a/docs/content/en/methods/duration/Abs.md b/docs/content/en/methods/duration/Abs.md
new file mode 100644
index 00000000000..2e85797ea0f
--- /dev/null
+++ b/docs/content/en/methods/duration/Abs.md
@@ -0,0 +1,15 @@
+---
+title: Abs
+description: Returns the absolute value of the given time.Duration value.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: time.Duration
+ signatures: [DURATION.Abs]
+---
+
+```go-html-template
+{{ $d = time.ParseDuration "-3h" }}
+{{ $d.Abs }} → 3h0m0s
+```
diff --git a/docs/content/en/methods/duration/Hours.md b/docs/content/en/methods/duration/Hours.md
new file mode 100644
index 00000000000..23655510e1f
--- /dev/null
+++ b/docs/content/en/methods/duration/Hours.md
@@ -0,0 +1,15 @@
+---
+title: Hours
+description: Returns the time.Duration value as a floating point number of hours.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: float64
+ signatures: [DURATION.Hours]
+---
+
+```go-html-template
+{{ $d = time.ParseDuration "3.5h2.5m1.5s" }}
+{{ $d.Hours }} → 3.5420833333333333
+```
diff --git a/docs/content/en/methods/duration/Microseconds.md b/docs/content/en/methods/duration/Microseconds.md
new file mode 100644
index 00000000000..c090316d028
--- /dev/null
+++ b/docs/content/en/methods/duration/Microseconds.md
@@ -0,0 +1,15 @@
+---
+title: Microseconds
+description: Returns the time.Duration value as an integer microsecond count.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: int64
+ signatures: [DURATION.Microseconds]
+---
+
+```go-html-template
+{{ $d = time.ParseDuration "3.5h2.5m1.5s" }}
+{{ $d.Microseconds }} → 12751500000
+```
diff --git a/docs/content/en/methods/duration/Milliseconds.md b/docs/content/en/methods/duration/Milliseconds.md
new file mode 100644
index 00000000000..288f3695a6c
--- /dev/null
+++ b/docs/content/en/methods/duration/Milliseconds.md
@@ -0,0 +1,15 @@
+---
+title: Milliseconds
+description: Returns the time.Duration value as an integer millisecond count.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: int64
+ signatures: [DURATION.Milliseconds]
+---
+
+```go-html-template
+{{ $d = time.ParseDuration "3.5h2.5m1.5s" }}
+{{ $d.Milliseconds }} → 12751500
+```
diff --git a/docs/content/en/methods/duration/Minutes.md b/docs/content/en/methods/duration/Minutes.md
new file mode 100644
index 00000000000..aec904fa790
--- /dev/null
+++ b/docs/content/en/methods/duration/Minutes.md
@@ -0,0 +1,15 @@
+---
+title: Minutes
+description: Returns the time.Duration value as a floating point number of minutes.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: float64
+ signatures: [DURATION.Minutes]
+---
+
+```go-html-template
+{{ $d = time.ParseDuration "3.5h2.5m1.5s" }}
+{{ $d.Minutes }} → 212.525
+```
diff --git a/docs/content/en/methods/duration/Nanoseconds.md b/docs/content/en/methods/duration/Nanoseconds.md
new file mode 100644
index 00000000000..fd1b9e4969b
--- /dev/null
+++ b/docs/content/en/methods/duration/Nanoseconds.md
@@ -0,0 +1,15 @@
+---
+title: Nanoseconds
+description: Returns the time.Duration value as an integer nanosecond count.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: int64
+ signatures: [DURATION.Nanoseconds]
+---
+
+```go-html-template
+{{ $d = time.ParseDuration "3.5h2.5m1.5s" }}
+{{ $d.Nanoseconds }} → 12751500000000
+```
diff --git a/docs/content/en/methods/duration/Round.md b/docs/content/en/methods/duration/Round.md
new file mode 100644
index 00000000000..dfd06253f9f
--- /dev/null
+++ b/docs/content/en/methods/duration/Round.md
@@ -0,0 +1,18 @@
+---
+title: Round
+description: Returns the result of rounding DURATION1 to the nearest multiple of DURATION2.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType:
+ signatures: [DURATION1.Round DURATION2]
+---
+
+```go-html-template
+{{ $d = time.ParseDuration "3.5h2.5m1.5s" }}
+
+{{ $d.Round (time.ParseDuration "2h") }} → 4h0m0s
+{{ $d.Round (time.ParseDuration "3m") }} → 3h33m0s
+{{ $d.Round (time.ParseDuration "4s") }} → 3h32m32s
+```
diff --git a/docs/content/en/methods/duration/Seconds.md b/docs/content/en/methods/duration/Seconds.md
new file mode 100644
index 00000000000..8b6d060b9ca
--- /dev/null
+++ b/docs/content/en/methods/duration/Seconds.md
@@ -0,0 +1,15 @@
+---
+title: Seconds
+description: Returns the time.Duration value as a floating point number of seconds.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: float64
+ signatures: [DURATION.Seconds]
+---
+
+```go-html-template
+{{ $d = time.ParseDuration "3.5h2.5m1.5s" }}
+{{ $d.Seconds }} → 12751.5
+```
diff --git a/docs/content/en/methods/duration/Truncate.md b/docs/content/en/methods/duration/Truncate.md
new file mode 100644
index 00000000000..5a785a77a55
--- /dev/null
+++ b/docs/content/en/methods/duration/Truncate.md
@@ -0,0 +1,18 @@
+---
+title: Truncate
+description: Returns the result of rounding DURATION1 toward zero to a multiple of DURATION2.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: time.Duration
+ signatures: [DURATION1.Truncate DURATION2]
+---
+
+```go-html-template
+{{ $d = time.ParseDuration "3.5h2.5m1.5s" }}
+
+{{ $d.Truncate (time.ParseDuration "2h") }} → 2h0m0s
+{{ $d.Truncate (time.ParseDuration "3m") }} → 3h30m0s
+{{ $d.Truncate (time.ParseDuration "4s") }} → 3h32m28s
+```
diff --git a/docs/content/en/methods/duration/_index.md b/docs/content/en/methods/duration/_index.md
new file mode 100644
index 00000000000..4c690ecc39c
--- /dev/null
+++ b/docs/content/en/methods/duration/_index.md
@@ -0,0 +1,7 @@
+---
+title: Duration methods
+linkTitle: Duration
+description: Use these methods with a time.Duration value.
+categories: []
+keywords: []
+---
diff --git a/docs/content/en/methods/menu-entry/Children.md b/docs/content/en/methods/menu-entry/Children.md
new file mode 100644
index 00000000000..16cec71d96e
--- /dev/null
+++ b/docs/content/en/methods/menu-entry/Children.md
@@ -0,0 +1,66 @@
+---
+title: Children
+description: Returns a collection of child menu entries, if any, under the given menu entry.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: navigation.Menu
+ signatures: [MENUENTRY.Children]
+---
+
+Use the `Children` method when rendering a nested menu.
+
+With this project configuration:
+
+{{< code-toggle file=hugo >}}
+[[menus.main]]
+name = 'Products'
+pageRef = '/product'
+weight = 10
+
+[[menus.main]]
+name = 'Product 1'
+pageRef = '/products/product-1'
+parent = 'Products'
+weight = 1
+
+[[menus.main]]
+name = 'Product 2'
+pageRef = '/products/product-2'
+parent = 'Products'
+weight = 2
+{{< /code-toggle >}}
+
+And this template:
+
+```go-html-template
+
+```
diff --git a/docs/content/en/methods/menu-entry/Identifier.md b/docs/content/en/methods/menu-entry/Identifier.md
new file mode 100644
index 00000000000..7310ff3ba8d
--- /dev/null
+++ b/docs/content/en/methods/menu-entry/Identifier.md
@@ -0,0 +1,41 @@
+---
+title: Identifier
+description: Returns the `identifier` property of the given menu entry.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [MENUENTRY.Identifier]
+---
+
+The `Identifier` method returns the `identifier` property of the menu entry. If you define the menu entry [automatically], it returns the page's section.
+
+{{< code-toggle file=hugo >}}
+[[menus.main]]
+identifier = 'about'
+name = 'About'
+pageRef = '/about'
+weight = 10
+
+[[menus.main]]
+identifier = 'contact'
+name = 'Contact'
+pageRef = '/contact'
+weight = 20
+{{< /code-toggle >}}
+
+This example uses the `Identifier` method when querying the translation table on a multilingual project, falling back the `name` property if a matching key in the translation table does not exist:
+
+```go-html-template
+
+```
+
+> [!note]
+> In the menu definition above, note that the `identifier` property is only required when two or more menu entries have the same name, or when localizing the name using translation tables.
+
+[automatically]: /content-management/menus/#define-automatically
diff --git a/docs/content/en/methods/menu-entry/KeyName.md b/docs/content/en/methods/menu-entry/KeyName.md
new file mode 100644
index 00000000000..abf6396674a
--- /dev/null
+++ b/docs/content/en/methods/menu-entry/KeyName.md
@@ -0,0 +1,39 @@
+---
+title: KeyName
+description: Returns the `identifier` property of the given menu entry, falling back to its `name` property.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [MENUENTRY.KeyName]
+---
+
+In this menu definition, the second entry does not contain an `identifier`, so the `Identifier` method returns its `name` property instead:
+
+{{< code-toggle file=hugo >}}
+[[menus.main]]
+identifier = 'about'
+name = 'About'
+pageRef = '/about'
+weight = 10
+
+[[menus.main]]
+name = 'Contact'
+pageRef = '/contact'
+weight = 20
+{{< /code-toggle >}}
+
+This example uses the `KeyName` method when querying the translation table on a multilingual project, falling back the `name` property if a matching key in the translation table does not exist:
+
+```go-html-template
+
+```
+
+In the example above, we need to pass the value returned by `.KeyName` through the [`lower`] function because the keys in the translation table are lowercase.
+
+[`lower`]: /functions/strings/tolower/
diff --git a/docs/content/en/methods/menu-entry/Menu.md b/docs/content/en/methods/menu-entry/Menu.md
new file mode 100644
index 00000000000..074911eeb13
--- /dev/null
+++ b/docs/content/en/methods/menu-entry/Menu.md
@@ -0,0 +1,22 @@
+---
+title: Menu
+description: Returns the identifier of the menu that contains the given menu entry.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [MENUENTRY.Menu]
+---
+
+```go-html-template
+{{ range .Site.Menus.main }}
+ {{ .Menu }} → main
+{{ end }}
+```
+
+Use this method with the [`IsMenuCurrent`] and [`HasMenuCurrent`] methods on a `Page` object to set "active" and "ancestor" classes on a rendered entry. See [this example].
+
+[`HasMenuCurrent`]: /methods/page/hasmenucurrent/
+[`IsMenuCurrent`]: /methods/page/ismenucurrent/
+[this example]: /templates/menu/#example
diff --git a/docs/content/en/methods/menu-entry/Name.md b/docs/content/en/methods/menu-entry/Name.md
new file mode 100644
index 00000000000..a00601b2de5
--- /dev/null
+++ b/docs/content/en/methods/menu-entry/Name.md
@@ -0,0 +1,28 @@
+---
+title: Name
+description: Returns the `name` property of the given menu entry.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [MENUENTRY.Name]
+---
+
+If you define the menu entry [automatically], the `Name` method returns the page's [`LinkTitle`], falling back to its [`Title`].
+
+If you define the menu entry in [front matter] or in your [project configuration], the `Name` method returns the `name` property of the given menu entry. If the `name` is not defined, and the menu entry resolves to a page, the `Name` returns the page [`LinkTitle`], falling back to its [`Title`].
+
+```go-html-template
+
+```
+
+[`LinkTitle`]: /methods/page/linktitle/
+[`Title`]: /methods/page/title/
+[automatically]: /content-management/menus/#define-automatically
+[front matter]: /content-management/menus/#define-in-front-matter
+[project configuration]: /content-management/menus/#define-in-project-configuration
diff --git a/docs/content/en/methods/menu-entry/Page.md b/docs/content/en/methods/menu-entry/Page.md
new file mode 100644
index 00000000000..489ee7accba
--- /dev/null
+++ b/docs/content/en/methods/menu-entry/Page.md
@@ -0,0 +1,53 @@
+---
+title: Page
+description: Returns the Page object associated with the given menu entry.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Page
+ signatures: [MENUENTRY.Page]
+---
+
+Regardless of how you [define menu entries], an entry associated with a page has access to its [methods].
+
+In this menu definition, the first two entries are associated with a page, the last entry is not:
+
+{{< code-toggle file=hugo >}}
+[[menus.main]]
+pageRef = '/about'
+weight = 10
+
+[[menus.main]]
+pageRef = '/contact'
+weight = 20
+
+[[menus.main]]
+name = 'Hugo'
+url = 'https://gohugo.io'
+weight = 30
+{{< /code-toggle >}}
+
+In this example, if the menu entry is associated with a page, we use page's [`RelPermalink`] and [`LinkTitle`] when rendering the anchor element.
+
+If the entry is not associated with a page, we use its `url` and `name` properties.
+
+```go-html-template
+
+ {{ range .Site.Menus.main }}
+ {{ with .Page }}
+
+```
+
+See the [menu templates] section for more information.
+
+[`LinkTitle`]: /methods/page/linktitle/
+[`RelPermalink`]: /methods/page/relpermalink/
+[define menu entries]: /content-management/menus/
+[menu templates]: /templates/menu/#page-references
+[methods]: /methods/page/
diff --git a/docs/content/en/methods/menu-entry/PageRef.md b/docs/content/en/methods/menu-entry/PageRef.md
new file mode 100644
index 00000000000..8d4e536566c
--- /dev/null
+++ b/docs/content/en/methods/menu-entry/PageRef.md
@@ -0,0 +1,109 @@
+---
+title: PageRef
+description: Returns the `pageRef` property of the given menu entry.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [MENUENTRY.PageRef]
+---
+
+> [!note]
+> The use case for this method is rare.
+> In almost also scenarios you should use the [`URL`] method instead.
+
+## Explanation
+
+If you specify a `pageRef` property when [defining a menu entry] in your project configuration, Hugo looks for a matching page when rendering the entry.
+
+If a matching page is found:
+
+- The [`URL`] method returns the page's relative permalink
+- The [`Page`] method returns the corresponding `Page` object
+- The [`HasMenuCurrent`] and [`IsMenuCurrent`] methods on a `Page` object return the expected values
+
+If a matching page is not found:
+
+- The [`URL`] method returns the entry's `url` property if set, else an empty string
+- The [`Page`] method returns nil
+- The [`HasMenuCurrent`] and [`IsMenuCurrent`] methods on a `Page` object return `false`
+
+> [!note]
+> In almost also scenarios you should use the [`URL`] method instead.
+
+## Example
+
+This example is contrived.
+
+> [!note]
+> In almost also scenarios you should use the [`URL`] method instead.
+
+Consider this content structure:
+
+```text
+content/
+├── products.md
+└── _index.md
+```
+
+And this menu definition:
+
+{{< code-toggle file=hugo >}}
+[[menus.main]]
+name = 'Products'
+pageRef = '/products'
+weight = 10
+[[menus.main]]
+name = 'Services'
+pageRef = '/services'
+weight = 20
+{{< /code-toggle >}}
+
+With this template code:
+
+```go-html-template {file="layouts/_partials/menu.html"}
+
+```
+
+In the above note that the `href` attribute of the second `anchor` element is blank because Hugo was unable to find the "services" page.
+
+With this template code:
+
+```go-html-template {file="layouts/_partials/menu.html"}
+
+```
+
+In the above note that Hugo populates the `href` attribute of the second `anchor` element with the `pageRef` property as defined in your project configuration because the template code falls back to the `PageRef` method.
+
+[`HasMenuCurrent`]: /methods/page/hasmenucurrent/
+[`IsMenuCurrent`]: /methods/page/ismenucurrent/
+[`Page`]: /methods/menu-entry/page/
+[`URL`]: /methods/menu-entry/url/
+[defining a menu entry]: /content-management/menus/#define-in-project-configuration
diff --git a/docs/content/en/methods/menu-entry/Params.md b/docs/content/en/methods/menu-entry/Params.md
new file mode 100644
index 00000000000..1131781471d
--- /dev/null
+++ b/docs/content/en/methods/menu-entry/Params.md
@@ -0,0 +1,61 @@
+---
+title: Params
+description: Returns the `params` property of the given menu entry.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: maps.Params
+ signatures: [MENUENTRY.Params]
+---
+
+When you define menu entries in your [project configuration] or in [front matter], you can include a `params` key to attach additional information to the entry. For example:
+
+{{< code-toggle file=hugo >}}
+[[menus.main]]
+name = 'About'
+pageRef = '/about'
+weight = 10
+
+[[menus.main]]
+name = 'Contact'
+pageRef = '/contact'
+weight = 20
+
+[[menus.main]]
+name = 'Hugo'
+url = 'https://gohugo.io'
+weight = 30
+[menus.main.params]
+ rel = 'external'
+{{< /code-toggle >}}
+
+With this template:
+
+```go-html-template
+
+```
diff --git a/docs/content/en/methods/menu-entry/Post.md b/docs/content/en/methods/menu-entry/Post.md
new file mode 100644
index 00000000000..2da8c38d8a1
--- /dev/null
+++ b/docs/content/en/methods/menu-entry/Post.md
@@ -0,0 +1,12 @@
+---
+title: Post
+description: Returns the `post` property of the given menu entry.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: template.HTML
+ signatures: [MENUENTRY.Post]
+---
+
+{{% include "/_common/menu-entries/pre-and-post.md" %}}
diff --git a/docs/content/en/methods/menu-entry/Pre.md b/docs/content/en/methods/menu-entry/Pre.md
new file mode 100644
index 00000000000..19af243e7fc
--- /dev/null
+++ b/docs/content/en/methods/menu-entry/Pre.md
@@ -0,0 +1,12 @@
+---
+title: Pre
+description: Returns the `pre` property of the given menu entry.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: template.HTML
+ signatures: [MENUENTRY.Pre]
+---
+
+{{% include "/_common/menu-entries/pre-and-post.md" %}}
diff --git a/docs/content/en/methods/menu-entry/Title.md b/docs/content/en/methods/menu-entry/Title.md
new file mode 100644
index 00000000000..526132d7c14
--- /dev/null
+++ b/docs/content/en/methods/menu-entry/Title.md
@@ -0,0 +1,22 @@
+---
+title: Title
+description: Returns the `title` property of the given menu entry.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [MENUENTRY.Title]
+---
+
+The `Title` method returns the `title` property of the given menu entry. If the `title` is not defined, and the menu entry resolves to a page, the `Title` returns the page [`Title`].
+
+[`Title`]: /methods/page/title/
+
+```go-html-template
+
+```
+
+[`RelPermalink`]: /methods/page/relpermalink/
diff --git a/docs/content/en/methods/menu-entry/Weight.md b/docs/content/en/methods/menu-entry/Weight.md
new file mode 100644
index 00000000000..17fc3a43b46
--- /dev/null
+++ b/docs/content/en/methods/menu-entry/Weight.md
@@ -0,0 +1,31 @@
+---
+title: Weight
+description: Returns the `weight` property of the given menu entry.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: int
+ signatures: [MENUENTRY.Weight]
+---
+
+If you define the menu entry [automatically], the `Weight` method returns the page's [`Weight`].
+
+If you define the menu entry in [front matter] or in your [project configuration], the `Weight` method returns the `weight` property, falling back to the page's `Weight`.
+
+In this contrived example, we limit the number of menu entries based on weight:
+
+```go-html-template
+
+ {{ range .Site.Menus.main }}
+ {{ if le .Weight 42 }}
+
+```
+
+When using the sort function with menu entries, specify any of the following keys: `Identifier`, `Name`, `Parent`, `Post`, `Pre`, `Title`, `URL`, or `Weight`.
+
+[`sort`]: /functions/collections/sort/
diff --git a/docs/content/en/methods/menu/ByWeight.md b/docs/content/en/methods/menu/ByWeight.md
new file mode 100644
index 00000000000..013d37e1311
--- /dev/null
+++ b/docs/content/en/methods/menu/ByWeight.md
@@ -0,0 +1,71 @@
+---
+title: ByWeight
+description: Returns the given menu with its entries sorted by weight, then by name, then by identifier.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: navigation.Menu
+ signatures: [MENU.ByWeight]
+---
+
+The `ByWeight` method returns the given menu with its entries sorted by [`weight`](g), then by `name`, then by `identifier`. This is the default sort order.
+
+Consider this menu definition:
+
+{{< code-toggle file=hugo >}}
+[[menus.main]]
+identifier = 'about'
+name = 'About'
+pageRef = '/about'
+weight = 20
+
+[[menus.main]]
+identifier = 'services'
+name = 'Services'
+pageRef = '/services'
+weight = 10
+
+[[menus.main]]
+identifier = 'contact'
+name = 'Contact'
+pageRef = '/contact'
+weight = 30
+{{< /code-toggle >}}
+
+To sort the entries by `weight`, then by `name`, then by `identifier`:
+
+```go-html-template
+
+```
+
+> [!note]
+> In the menu definition above, note that the `identifier` property is only required when two or more menu entries have the same name, or when localizing the name using translation tables.
+
+You can also sort menu entries using the [`sort`] function. For example, to sort by `weight` in descending order:
+
+```go-html-template
+
+ {{ range sort .Site.Menus.main "Weight" "desc" }}
+
+```
+
+When using the sort function with menu entries, specify any of the following keys: `Identifier`, `Name`, `Parent`, `Post`, `Pre`, `Title`, `URL`, or `Weight`.
+
+[`sort`]: /functions/collections/sort/
diff --git a/docs/content/en/methods/menu/Limit.md b/docs/content/en/methods/menu/Limit.md
new file mode 100644
index 00000000000..005fef14440
--- /dev/null
+++ b/docs/content/en/methods/menu/Limit.md
@@ -0,0 +1,50 @@
+---
+title: Limit
+description: Returns the given menu, limited to the first N entries.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: navigation.Menu
+ signatures: [MENU.Limit N]
+---
+
+The `Limit` method returns the given menu, limited to the first N entries.
+
+Consider this menu definition:
+
+{{< code-toggle file=hugo >}}
+[[menus.main]]
+name = 'Services'
+pageRef = '/services'
+weight = 10
+
+[[menus.main]]
+name = 'About'
+pageRef = '/about'
+weight = 20
+
+[[menus.main]]
+name = 'Contact'
+pageRef = '/contact'
+weight = 30
+{{< /code-toggle >}}
+
+To sort the entries by name, and limit to the first 2 entries:
+
+```go-html-template
+
+```
diff --git a/docs/content/en/methods/menu/_index.md b/docs/content/en/methods/menu/_index.md
new file mode 100644
index 00000000000..41084fdba17
--- /dev/null
+++ b/docs/content/en/methods/menu/_index.md
@@ -0,0 +1,7 @@
+---
+title: Menu methods
+linkTitle: Menu
+description: Use these methods when ranging through menu entries.
+categories: []
+keywords: []
+---
diff --git a/docs/content/en/methods/output-format/MediaType.md b/docs/content/en/methods/output-format/MediaType.md
new file mode 100644
index 00000000000..45f6267a1f2
--- /dev/null
+++ b/docs/content/en/methods/output-format/MediaType.md
@@ -0,0 +1,36 @@
+---
+title: MediaType
+description: Returns the media type of the given output format.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: media.Type
+ signatures: [OUTPUTFORMAT.MediaType]
+---
+
+{{% include "/_common/methods/output-formats/to-use-this-method.md" %}}
+
+```go-html-template
+{{ with .Site.Home.OutputFormats.Get "rss" }}
+ {{ with .MediaType }}
+ {{ .Type }} → application/rss+xml
+ {{ .MainType }} → application
+ {{ .SubType }} → rss
+ {{ end }}
+{{ end }}
+```
+
+## Methods
+
+### MainType
+
+(`string`) Returns the main type of the output format's media type.
+
+### SubType
+
+(`string`) Returns the subtype of the current format's media type.
+
+### Type
+
+(`string`) Returns the the current format's media type.
diff --git a/docs/content/en/methods/output-format/Name.md b/docs/content/en/methods/output-format/Name.md
new file mode 100644
index 00000000000..307f940e7a5
--- /dev/null
+++ b/docs/content/en/methods/output-format/Name.md
@@ -0,0 +1,18 @@
+---
+title: Name
+description: Returns the identifier of the given output format.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [OUTPUTFORMAT.Name]
+---
+
+{{% include "/_common/methods/output-formats/to-use-this-method.md" %}}
+
+```go-html-template
+{{ with .Site.Home.OutputFormats.Get "rss" }}
+ {{ .Name }} → rss
+{{ end }}
+```
diff --git a/docs/content/en/methods/output-format/Permalink.md b/docs/content/en/methods/output-format/Permalink.md
new file mode 100644
index 00000000000..09b98dc7ccb
--- /dev/null
+++ b/docs/content/en/methods/output-format/Permalink.md
@@ -0,0 +1,18 @@
+---
+title: Permalink
+description: Returns the permalink of the page generated by the current output format.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [OUTPUTFORMAT.Permalink]
+---
+
+{{% include "/_common/methods/output-formats/to-use-this-method.md" %}}
+
+```go-html-template
+{{ with .Site.Home.OutputFormats.Get "rss" }}
+ {{ .Permalink }} → https://example.org/index.xml
+{{ end }}
+```
diff --git a/docs/content/en/methods/output-format/Rel.md b/docs/content/en/methods/output-format/Rel.md
new file mode 100644
index 00000000000..4c3db9791b0
--- /dev/null
+++ b/docs/content/en/methods/output-format/Rel.md
@@ -0,0 +1,18 @@
+---
+title: Rel
+description: Returns the rel value of the given output format, either the default or as defined in your project configuration.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [OUTPUTFORMAT.Rel]
+---
+
+{{% include "/_common/methods/output-formats/to-use-this-method.md" %}}
+
+```go-html-template
+{{ with .Site.Home.OutputFormats.Get "rss" }}
+ {{ .Rel }} → alternate
+{{ end }}
+```
diff --git a/docs/content/en/methods/output-format/RelPermalink.md b/docs/content/en/methods/output-format/RelPermalink.md
new file mode 100644
index 00000000000..434100c56ff
--- /dev/null
+++ b/docs/content/en/methods/output-format/RelPermalink.md
@@ -0,0 +1,18 @@
+---
+title: RelPermalink
+description: Returns the relative permalink of the page generated by the current output format.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [OUTPUTFORMAT.RelPermalink]
+---
+
+{{% include "/_common/methods/output-formats/to-use-this-method.md" %}}
+
+```go-html-template
+{{ with .Site.Home.OutputFormats.Get "rss" }}
+ {{ .RelPermalink }} → /index.xml
+{{ end }}
+```
diff --git a/docs/content/en/methods/output-format/_index.md b/docs/content/en/methods/output-format/_index.md
new file mode 100644
index 00000000000..dd374de144c
--- /dev/null
+++ b/docs/content/en/methods/output-format/_index.md
@@ -0,0 +1,7 @@
+---
+title: Output format methods
+linkTitle: Output format
+description: Use these methods with an OutputFormat object.
+categories: []
+keywords: []
+---
diff --git a/docs/content/en/methods/page/Aliases.md b/docs/content/en/methods/page/Aliases.md
new file mode 100644
index 00000000000..f159ba86864
--- /dev/null
+++ b/docs/content/en/methods/page/Aliases.md
@@ -0,0 +1,138 @@
+---
+title: Aliases
+description: Returns the aliases defined in front matter as server-relative URLs, resolved according to the current content dimension.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: '[]string'
+ signatures: [PAGE.Aliases]
+---
+
+The `Aliases` method on a `Page` object returns the values defined in the [`aliases`][] front matter field as server-relative URLs, resolved according to the current [content dimension](g).
+
+The `Aliases` method is useful for generating a `_redirects` file, which contains a source URL, a target URL, and an HTTP status code for each alias. You can use a `_redirects` file with hosting services such as Cloudflare, GitLab Pages, and Netlify.
+
+## Redirects
+
+By default, Hugo handles aliases by creating individual HTML files for each alias path. These files contain a `meta http-equiv="refresh"` tag to redirect the visitor via the browser.
+
+While functional, generating a single `_redirects` file allows your hosting provider to handle redirects at the server level. This is more efficient than client-side redirection and improves performance by eliminating the need to load a middle-man HTML page.
+
+> [!tip]
+> You can use the same general approach to generate an `.htaccess` file.
+
+## Example
+
+The following example demonstrates how to configure your site and create a template to automate the generation of a `_redirects` file.
+
+### Content structure
+
+The content structure for this multilingual example looks like this:
+
+```text
+content/
+├── examples/
+│ ├── a.de.md aliases = ['a-old']
+│ ├── a.en.md aliases = ['a-old', 'a-older']
+│ ├── b.de.md aliases = ['b-old']
+│ └── b.en.md aliases = ['b-old', 'b-older']
+└── _index.md
+```
+
+In the example above, the aliases are [page-relative](g). To specify a [site-relative](g) path, preface the entry with a slash (`/`). Both forms are resolved to [server-relative](g) paths.
+
+Page-relative paths can also include directory traversal:
+
+| Path type | File path | Alias | Server-relative path |
+| :--- | :--- | :--- | :--- |
+| page-relative | `content/examples/a.en.md` | `a-old` | `/en/examples/a-old/` |
+| page-relative | `content/examples/a.en.md` | `../a-old` | `/en/a-old/` |
+| site-relative | `content/examples/a.en.md` | `/a-old` | `/en/a-old/` |
+
+### Project configuration
+
+To implement this, you must update your project configuration to:
+
+1. Disable the generation of default HTML redirect files by setting `disableAliases` to `true`.
+1. Define a [media type][] named `text/redirects` to handle the file format.
+1. Define a custom [output format][] named `redirects` to set the filename to `_redirects` and place it at the root of the published site.
+1. Configure the home page [outputs][] to include the `redirects` format in addition to `html`.
+
+{{< code-toggle file=hugo >}}
+baseURL = 'https://example.org/'
+disableAliases = true
+
+defaultContentLanguage = 'en'
+defaultContentLanguageInSubdir = true
+
+[languages.en]
+ locale = 'en-US'
+ direction = 'ltr'
+ name = 'English'
+ weight = 1
+ title = 'My Site in English'
+
+[languages.de]
+ locale = 'de-DE'
+ direction = 'ltr'
+ name = 'Deutsch'
+ weight = 2
+ title = 'My Site in German'
+
+[mediaTypes]
+ [mediaTypes.'text/redirects']
+ delimiter = ''
+
+[outputFormats]
+ [outputFormats.redirects]
+ baseName = '_redirects'
+ isPlainText = true
+ mediaType = 'text/redirects'
+ root = true
+
+[outputs]
+ home = ['html', 'redirects']
+{{< /code-toggle >}}
+
+### Template implementation
+
+Next, create a home page template specifically for the `redirects` output format. The following template iterates through every page in every language and extracts its aliases.
+
+To ensure the resulting `_redirects` file is valid, the template uses the [`strings.FindRE`][] function to check for whitespace such as tabs or newlines within the alias string. If whitespace is detected, Hugo will throw an error and fail the build to prevent generating an invalid file.
+
+```go-html-template {file="layouts/home.redirects" copy=true}
+{{- if site.IsDefault -}}
+ {{- range hugo.Sites -}}
+ {{- range $p := .Pages -}}
+ {{- range .Aliases -}}
+ {{- if findRE `\s` . -}}
+ {{- errorf "One of the front matter aliases in %q contains whitespace" $p.String -}}
+ {{- end -}}
+ {{- printf "%s %s 301\n" . $p.RelPermalink -}}
+ {{- end -}}
+ {{- end -}}
+ {{- end -}}
+{{- end -}}
+```
+
+### Generated output
+
+Once Hugo processes the template, it produces a clean list of redirect rules. Each line follows the required format: the source URL, the destination URL, and the HTTP status code.
+
+The resulting `_redirects` file looks like this:
+
+```text
+/de/examples/a-old /de/examples/a/ 301
+/de/examples/b-old /de/examples/b/ 301
+/en/examples/b-old /en/examples/b/ 301
+/en/examples/b-older /en/examples/b/ 301
+/en/examples/a-old /en/examples/a/ 301
+/en/examples/a-older /en/examples/a/ 301
+```
+
+[`aliases`]: /content-management/front-matter/#aliases
+[`strings.FindRE`]: /functions/strings/findre/
+[media type]: /configuration/media-types/
+[output format]: /configuration/output-formats/
+[outputs]: /configuration/outputs/
diff --git a/docs/content/en/methods/page/AllTranslations.md b/docs/content/en/methods/page/AllTranslations.md
new file mode 100644
index 00000000000..e34c8b74581
--- /dev/null
+++ b/docs/content/en/methods/page/AllTranslations.md
@@ -0,0 +1,88 @@
+---
+title: AllTranslations
+description: Returns all translations of the given page, including the current language, sorted by language weight then language name.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Pages
+ signatures: [PAGE.AllTranslations]
+---
+
+With this project configuration:
+
+{{< code-toggle file=hugo >}}
+defaultContentLanguage = 'en'
+
+[languages.en]
+contentDir = 'content/en'
+label = 'English'
+locale = 'en-US'
+weight = 1
+
+[languages.de]
+contentDir = 'content/de'
+label = 'Deutsch'
+locale = 'de-DE'
+weight = 2
+
+[languages.fr]
+contentDir = 'content/fr'
+label = 'Français'
+locale = 'fr-FR'
+weight = 3
+{{< /code-toggle >}}
+
+And this content:
+
+```text
+content/
+├── de/
+│ ├── books/
+│ │ ├── book-1.md
+│ │ └── book-2.md
+│ └── _index.md
+├── en/
+│ ├── books/
+│ │ ├── book-1.md
+│ │ └── book-2.md
+│ └── _index.md
+├── fr/
+│ ├── books/
+│ │ └── book-1.md
+│ └── _index.md
+└── _index.md
+```
+
+And this template:
+
+```go-html-template
+{{ with .AllTranslations }}
+
+```
diff --git a/docs/content/en/methods/page/AlternativeOutputFormats.md b/docs/content/en/methods/page/AlternativeOutputFormats.md
new file mode 100644
index 00000000000..72ef9f5d05f
--- /dev/null
+++ b/docs/content/en/methods/page/AlternativeOutputFormats.md
@@ -0,0 +1,29 @@
+---
+title: AlternativeOutputFormats
+description: Returns a slice of OutputFormat objects, excluding the current output format, each representing one of the output formats enabled for the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.OutputFormats
+ signatures: [PAGE.AlternativeOutputFormats]
+---
+
+{{% glossary-term "output format" %}}
+
+The `AlternativeOutputFormats` method on a `Page` object returns a slice of `OutputFormat` objects, excluding the current output format, each representing one of the output formats enabled for the given page. See [details](/configuration/output-formats/).
+
+For example, to generate a `link` element for each of the alternative output formats:
+
+```go-html-template
+{{ range .AlternativeOutputFormats }}
+ {{ printf "" .Rel .MediaType.Type .Permalink | safeHTML }}
+{{ end }}
+```
+
+Hugo renders this to something like:
+
+```html
+
+
+```
diff --git a/docs/content/en/methods/page/Ancestors.md b/docs/content/en/methods/page/Ancestors.md
new file mode 100644
index 00000000000..d8275cf7636
--- /dev/null
+++ b/docs/content/en/methods/page/Ancestors.md
@@ -0,0 +1,76 @@
+---
+title: Ancestors
+description: Returns a collection of Page objects, one for each ancestor section of the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Pages
+ signatures: [PAGE.Ancestors]
+---
+
+With this content structure:
+
+```text
+content/
+├── auctions/
+│ ├── 2023-11/
+│ │ ├── _index.md <-- front matter: weight = 202311
+│ │ ├── auction-1.md
+│ │ └── auction-2.md
+│ ├── 2023-12/
+│ │ ├── _index.md <-- front matter: weight = 202312
+│ │ ├── auction-3.md
+│ │ └── auction-4.md
+│ ├── _index.md <-- front matter: weight = 30
+│ ├── bidding.md
+│ └── payment.md
+├── books/
+│ ├── _index.md <-- front matter: weight = 10
+│ ├── book-1.md
+│ └── book-2.md
+├── films/
+│ ├── _index.md <-- front matter: weight = 20
+│ ├── film-1.md
+│ └── film-2.md
+└── _index.md
+```
+
+And this template:
+
+```go-html-template
+{{ range .Ancestors }}
+ {{ .LinkTitle }}
+{{ end }}
+```
+
+On the November 2023 auctions page, Hugo renders:
+
+```html
+Auctions in November 2023
+Auctions
+Home
+```
+
+In the example above, notice that Hugo orders the ancestors from closest to furthest. This makes breadcrumb navigation simple:
+
+```go-html-template
+
+```
+
+With some CSS, the code above renders something like this, where each breadcrumb links to its page:
+
+```text
+Home > Auctions > Auctions in November 2023 > Auction 1
+```
diff --git a/docs/content/en/methods/page/BundleType.md b/docs/content/en/methods/page/BundleType.md
new file mode 100644
index 00000000000..e919511dab4
--- /dev/null
+++ b/docs/content/en/methods/page/BundleType.md
@@ -0,0 +1,33 @@
+---
+title: BundleType
+description: Returns the bundle type of the given page, or an empty string if the page is not a page bundle.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [PAGE.BundleType]
+---
+
+A page bundle is a directory that encapsulates both content and associated [resources](g). There are two types of page bundles: [leaf bundles](g) and [branch bundles](g). See [details](/content-management/page-bundles/).
+
+The `BundleType` method on a `Page` object returns `branch` for branch bundles, `leaf` for leaf bundles, and an empty string if the page is not a page bundle.
+
+```text
+content/
+├── films/
+│ ├── film-1/
+│ │ ├── a.jpg
+│ │ └── index.md <-- leaf bundle
+│ ├── _index.md <-- branch bundle
+│ ├── b.jpg
+│ ├── film-2.md
+│ └── film-3.md
+└── _index.md <-- branch bundle
+```
+
+To get the value within a template:
+
+```go-html-template
+{{ .BundleType }}
+```
diff --git a/docs/content/en/methods/page/CodeOwners.md b/docs/content/en/methods/page/CodeOwners.md
new file mode 100644
index 00000000000..00afa754938
--- /dev/null
+++ b/docs/content/en/methods/page/CodeOwners.md
@@ -0,0 +1,65 @@
+---
+title: CodeOwners
+description: Returns of slice of code owners for the given page, derived from the CODEOWNERS file in the root of the project directory.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: '[]string'
+ signatures: [PAGE.CodeOwners]
+---
+
+GitHub and GitLab support CODEOWNERS files. This file specifies the users responsible for developing and maintaining software and documentation. This definition can apply to the entire repository, specific directories, or to individual files. To learn more:
+
+- [GitHub CODEOWNERS documentation]
+- [GitLab CODEOWNERS documentation]
+
+Use the `CodeOwners` method on a `Page` object to determine the code owners for the given page.
+
+[GitHub CODEOWNERS documentation]: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
+[GitLab CODEOWNERS documentation]: https://docs.gitlab.com/ee/user/project/code_owners.html
+
+To use the `CodeOwners` method you must enable access to your local Git repository:
+
+{{< code-toggle file=hugo >}}
+enableGitInfo = true
+{{< /code-toggle >}}
+
+Consider this project structure:
+
+```text
+my-project/
+├── content/
+│ ├── books/
+│ │ └── les-miserables.md
+│ └── films/
+│ └── the-hunchback-of-notre-dame.md
+└── CODEOWNERS
+```
+
+And this CODEOWNERS file:
+
+```text
+* @jdoe
+/content/books/ @tjones
+/content/films/ @mrichards @rsmith
+```
+
+The table below shows the slice of code owners returned for each file:
+
+Path|Code owners
+:--|:--
+`books/les-miserables.md`|`[@tjones]`
+`films/the-hunchback-of-notre-dame.md`|`[@mrichards @rsmith]`
+
+Render the code owners for each content page:
+
+```go-html-template
+{{ range .CodeOwners }}
+ {{ . }}
+{{ end }}
+```
+
+Combine this method with [`resources.GetRemote`] to retrieve names and avatars from your Git provider by querying their API.
+
+[`resources.GetRemote`]: /functions/resources/getremote/
diff --git a/docs/content/en/methods/page/Content.md b/docs/content/en/methods/page/Content.md
new file mode 100644
index 00000000000..21348ebe67b
--- /dev/null
+++ b/docs/content/en/methods/page/Content.md
@@ -0,0 +1,16 @@
+---
+title: Content
+description: Returns the rendered content of the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: template.HTML
+ signatures: [PAGE.Content]
+---
+
+The `Content` method on a `Page` object renders Markdown and shortcodes to HTML.
+
+```go-html-template
+{{ .Content }}
+```
diff --git a/docs/content/en/methods/page/ContentWithoutSummary.md b/docs/content/en/methods/page/ContentWithoutSummary.md
new file mode 100644
index 00000000000..4923b1197e5
--- /dev/null
+++ b/docs/content/en/methods/page/ContentWithoutSummary.md
@@ -0,0 +1,22 @@
+---
+title: ContentWithoutSummary
+description: Returns the rendered content of the given page, excluding the content summary.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: template.HTML
+ signatures: [PAGE.ContentWithoutSummary]
+---
+
+{{< new-in 0.134.0 />}}
+
+Applicable when using manual or automatic [content summaries], the `ContentWithoutSummary` method on a `Page` object renders Markdown and shortcodes to HTML, excluding the content summary from the result.
+
+[content summaries]: /content-management/summaries/#manual-summary
+
+```go-html-template
+{{ .ContentWithoutSummary }}
+```
+
+The `ContentWithoutSummary` method returns the same as `Content` if you define the content summary in front matter.
diff --git a/docs/content/en/methods/page/CurrentSection.md b/docs/content/en/methods/page/CurrentSection.md
new file mode 100644
index 00000000000..93457f13f20
--- /dev/null
+++ b/docs/content/en/methods/page/CurrentSection.md
@@ -0,0 +1,48 @@
+---
+title: CurrentSection
+description: Returns the Page object of the section in which the given page resides.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Page
+ signatures: [PAGE.CurrentSection]
+---
+
+{{% glossary-term section %}}
+
+> [!note]
+> The current section of a [section page](g), [taxonomy page](g), [term page](g), or the home page, is itself.
+
+Consider this content structure:
+
+```text
+content/
+├── auctions/
+│ ├── 2023-11/
+│ │ ├── _index.md <-- current section: 2023-11
+│ │ ├── auction-1.md
+│ │ └── auction-2.md <-- current section: 2023-11
+│ ├── 2023-12/
+│ │ ├── _index.md
+│ │ ├── auction-3.md
+│ │ └── auction-4.md
+│ ├── _index.md <-- current section: auctions
+│ ├── bidding.md
+│ └── payment.md <-- current section: auctions
+├── books/
+│ ├── _index.md <-- current section: books
+│ ├── book-1.md
+│ └── book-2.md <-- current section: books
+├── films/
+│ ├── _index.md <-- current section: films
+│ ├── film-1.md
+│ └── film-2.md <-- current section: films
+└── _index.md <-- current section: home
+```
+
+To create a link to the current section page:
+
+```go-html-template
+{{ .CurrentSection.LinkTitle }}
+```
diff --git a/docs/content/en/methods/page/Data.md b/docs/content/en/methods/page/Data.md
new file mode 100644
index 00000000000..3d4433a8eb8
--- /dev/null
+++ b/docs/content/en/methods/page/Data.md
@@ -0,0 +1,101 @@
+---
+title: Data
+description: Returns a unique data object for each page kind.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Data
+ signatures: [PAGE.Data]
+---
+
+The `Data` method on a `Page` object returns a unique data object for each [page kind](g).
+
+> [!note]
+> The `Data` method is only useful within [taxonomy](g) and [term](g) templates.
+>
+> Themes that are not actively maintained may still use `.Data.Pages` in their templates. Although that syntax remains functional, use one of these methods instead: [`Pages`], [`RegularPages`], or [`RegularPagesRecursive`]
+
+The examples that follow are based on this project configuration:
+
+{{< code-toggle file=hugo >}}
+[taxonomies]
+genre = 'genres'
+author = 'authors'
+{{< /code-toggle >}}
+
+And this content structure:
+
+```text
+content/
+├── books/
+│ ├── and-then-there-were-none.md --> genres: suspense
+│ ├── death-on-the-nile.md --> genres: suspense
+│ └── jamaica-inn.md --> genres: suspense, romance
+│ └── pride-and-prejudice.md --> genres: romance
+└── _index.md
+```
+
+## In a taxonomy template
+
+Use these methods on the `Data` object within a _taxonomy_ template.
+
+Singular
+: (`string`) Returns the singular name of the taxonomy.
+
+```go-html-template
+{{ .Data.Singular }} → genre
+```
+
+Plural
+: (`string`) Returns the plural name of the taxonomy.
+
+```go-html-template
+{{ .Data.Plural }} → genres
+```
+
+Terms
+: (`page.Taxonomy`) Returns the `Taxonomy` object, consisting of a map of terms and the [weighted pages](g) associated with each term.
+
+```go-html-template
+{{ $taxonomyObject := .Data.Terms }}
+```
+
+> [!note]
+> Once you have captured the `Taxonomy` object, use any of the [taxonomy methods] to sort, count, or capture a subset of its weighted pages.
+
+Learn more about [taxonomy templates].
+
+## In a term template
+
+Use these methods on the `Data` object within a _term_ template.
+
+Singular
+: (`string`) Returns the singular name of the taxonomy.
+
+```go-html-template
+{{ .Data.Singular }} → genre
+```
+
+Plural
+: (`string`) Returns the plural name of the taxonomy.
+
+```go-html-template
+{{ .Data.Plural }} → genres
+```
+
+Term
+: (`string`) Returns the name of the term.
+
+```go-html-template
+{{ .Data.Term }} → suspense
+```
+
+Learn more about [term templates].
+
+[`Pages`]: /methods/page/pages/
+[`RegularPages`]: /methods/page/regularpages/
+[`RegularPagesRecursive`]: /methods/page/regularpagesrecursive/
+[taxonomy methods]: /methods/taxonomy/
+[taxonomy templates]: /templates/types/#taxonomy
+[term templates]: /templates/types/#term
diff --git a/docs/content/en/methods/page/Date.md b/docs/content/en/methods/page/Date.md
new file mode 100644
index 00000000000..ffddcf047fe
--- /dev/null
+++ b/docs/content/en/methods/page/Date.md
@@ -0,0 +1,33 @@
+---
+title: Date
+description: Returns the date of the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: time.Time
+ signatures: [PAGE.Date]
+---
+
+Set the date in front matter:
+
+{{< code-toggle file=content/news/article-1.md fm=true >}}
+title = 'Article 1'
+date = 2023-10-19T00:40:04-07:00
+{{< /code-toggle >}}
+
+> [!note]
+> The date field in front matter is often considered to be the creation date, You can change its meaning, and its effect on your project, in your project configuration. See [details].
+
+The date is a [time.Time] value. Format and localize the value with the [`time.Format`] function, or use it with any of the [time methods].
+
+```go-html-template
+{{ .Date | time.Format ":date_medium" }} → Oct 19, 2023
+```
+
+In the example above we explicitly set the date in front matter. With Hugo's default configuration, the `Date` method returns the front matter value. This behavior is configurable, allowing you to set fallback values if the date is not defined in front matter. See [details].
+
+[`time.Format`]: /functions/time/format/
+[details]: /configuration/front-matter/#dates
+[time methods]: /methods/time/
+[time.Time]: https://pkg.go.dev/time#Time
diff --git a/docs/content/en/methods/page/Description.md b/docs/content/en/methods/page/Description.md
new file mode 100644
index 00000000000..7a609bb071a
--- /dev/null
+++ b/docs/content/en/methods/page/Description.md
@@ -0,0 +1,27 @@
+---
+title: Description
+description: Returns the description of the given page as defined in front matter.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [PAGE.Description]
+---
+
+Conceptually different from a [content summary], a page description is typically used in metadata about the page.
+
+{{< code-toggle file=content/recipes/sushi.md fm=true >}}
+title = 'How to make spicy tuna hand rolls'
+description = 'Instructions for making spicy tuna hand rolls.'
+{{< /code-toggle >}}
+
+```go-html-template {file="layouts/baseof.html"}
+
+ ...
+
+ ...
+
+```
+
+[content summary]: /content-management/summaries/
diff --git a/docs/content/en/methods/page/Draft.md b/docs/content/en/methods/page/Draft.md
new file mode 100644
index 00000000000..d814d854190
--- /dev/null
+++ b/docs/content/en/methods/page/Draft.md
@@ -0,0 +1,21 @@
+---
+title: Draft
+description: Reports whether the given page is a draft as defined in front matter.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: bool
+ signatures: [PAGE.Draft]
+---
+
+By default, Hugo does not publish draft pages when you build your project. To include draft pages when you build your project, use the `--buildDrafts` command line flag.
+
+{{< code-toggle file=content/posts/post-1.md fm=true >}}
+title = 'Post 1'
+draft = true
+{{< /code-toggle >}}
+
+```go-html-template
+{{ .Draft }} → true
+```
diff --git a/docs/content/en/methods/page/Eq.md b/docs/content/en/methods/page/Eq.md
new file mode 100644
index 00000000000..0cfe1f1f741
--- /dev/null
+++ b/docs/content/en/methods/page/Eq.md
@@ -0,0 +1,21 @@
+---
+title: Eq
+description: Reports whether two Page objects are equal.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: bool
+ signatures: [PAGE1.Eq PAGE2]
+---
+
+In this contrived example we list all pages in the current section except for the current page.
+
+```go-html-template {file="layouts/page.html"}
+{{ $currentPage := . }}
+{{ range .CurrentSection.Pages }}
+ {{ if not (.Eq $currentPage) }}
+ {{ .LinkTitle }}
+ {{ end }}
+{{ end }}
+```
diff --git a/docs/content/en/methods/page/ExpiryDate.md b/docs/content/en/methods/page/ExpiryDate.md
new file mode 100644
index 00000000000..67e6b30d39c
--- /dev/null
+++ b/docs/content/en/methods/page/ExpiryDate.md
@@ -0,0 +1,32 @@
+---
+title: ExpiryDate
+description: Returns the expiry date of the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: time.Time
+ signatures: [PAGE.ExpiryDate]
+---
+
+By default, Hugo excludes expired pages when building your project. To include expired pages, use the `--buildExpired` command line flag.
+
+Set the expiry date in front matter:
+
+{{< code-toggle file=content/news/article-1.md fm=true >}}
+title = 'Article 1'
+expiryDate = 2024-10-19T00:32:13-07:00
+{{< /code-toggle >}}
+
+The expiry date is a [time.Time] value. Format and localize the value with the [`time.Format`] function, or use it with any of the [time methods].
+
+```go-html-template
+{{ .ExpiryDate | time.Format ":date_medium" }} → Oct 19, 2024
+```
+
+In the example above we explicitly set the expiry date in front matter. With Hugo's default configuration, the `ExpiryDate` method returns the front matter value. This behavior is configurable, allowing you to set fallback values if the expiry date is not defined in front matter. See [details].
+
+[`time.Format`]: /functions/time/format/
+[details]: /configuration/front-matter/#dates
+[time methods]: /methods/time/
+[time.Time]: https://pkg.go.dev/time#Time
diff --git a/docs/content/en/methods/page/File.md b/docs/content/en/methods/page/File.md
new file mode 100644
index 00000000000..0bf2fdecf42
--- /dev/null
+++ b/docs/content/en/methods/page/File.md
@@ -0,0 +1,192 @@
+---
+title: File
+description: For pages backed by a file, returns file information for the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: hugolib.fileInfo
+ signatures: [PAGE.File]
+---
+
+By default, not all pages are backed by a file, including top-level [section pages](g), [taxonomy pages](g), and [term pages](g). By definition, you cannot retrieve file information when the file does not exist.
+
+To back one of the pages above with a file, create an `_index.md` file in the corresponding directory. For example:
+
+```text
+content/
+└── books/
+ ├── _index.md <-- the top-slevel section page
+ ├── book-1.md
+ └── book-2.md
+```
+
+> [!note]
+> Code defensively by verifying file existence as shown in the examples below.
+
+## Methods
+
+> [!note]
+> The path separators (slash or backslash) in `Path`, `Dir`, and `Filename` depend on the operating system.
+
+### BaseFileName
+
+(`string`) The file name, excluding the extension.
+
+```go-html-template
+{{ with .File }}
+ {{ .BaseFileName }}
+{{ end }}
+```
+
+### ContentBaseName
+
+(`string`) If the page is a branch or leaf bundle, the name of the containing directory, else the `TranslationBaseName`.
+
+```go-html-template
+{{ with .File }}
+ {{ .ContentBaseName }}
+{{ end }}
+```
+
+### Dir
+
+(`string`) The file path, excluding the file name, relative to the `content` directory.
+
+```go-html-template
+{{ with .File }}
+ {{ .Dir }}
+{{ end }}
+```
+
+### Ext
+
+(`string`) The file extension.
+
+```go-html-template
+{{ with .File }}
+ {{ .Ext }}
+{{ end }}
+```
+
+### Filename
+
+(`string`) The absolute file path.
+
+```go-html-template
+{{ with .File }}
+ {{ .Filename }}
+{{ end }}
+```
+
+### IsContentAdapter
+
+(`bool`) Reports whether the file is a [content adapter].
+
+```go-html-template
+{{ with .File }}
+ {{ .IsContentAdapter }}
+{{ end }}
+```
+
+### LogicalName
+
+(`string`) The file name.
+
+```go-html-template
+{{ with .File }}
+ {{ .LogicalName }}
+{{ end }}
+```
+
+### Path
+
+(`string`) The file path, relative to the `content` directory.
+
+```go-html-template
+{{ with .File }}
+ {{ .Path }}
+{{ end }}
+```
+
+### Section
+
+(`string`) The name of the top-level section in which the file resides.
+
+```go-html-template
+{{ with .File }}
+ {{ .Section }}
+{{ end }}
+```
+
+### TranslationBaseName
+
+(`string`) The file name, excluding the extension and language identifier.
+
+```go-html-template
+{{ with .File }}
+ {{ .TranslationBaseName }}
+{{ end }}
+```
+
+### UniqueID
+
+(`string`) The MD5 hash of `.File.Path`.
+
+```go-html-template
+{{ with .File }}
+ {{ .UniqueID }}
+{{ end }}
+```
+
+## Examples
+
+Consider this content structure in a multilingual project:
+
+```text
+content/
+├── news/
+│ ├── b/
+│ │ ├── index.de.md <-- leaf bundle
+│ │ └── index.en.md <-- leaf bundle
+│ ├── a.de.md <-- regular content
+│ ├── a.en.md <-- regular content
+│ ├── _index.de.md <-- branch bundle
+│ └── _index.en.md <-- branch bundle
+├── _index.de.md
+└── _index.en.md
+```
+
+With the English language site:
+
+ |regular content|leaf bundle|branch bundle
+:--|:--|:--|:--
+BaseFileName|a.en|index.en|_index.en
+ContentBaseName|a|b|news
+Dir|news/|news/b/|news/
+Ext|md|md|md
+Filename|/home/user/...|/home/user/...|/home/user/...
+IsContentAdapter|false|false|false
+LogicalName|a.en.md|index.en.md|_index.en.md
+Path|news/a.en.md|news/b/index.en.md|news/_index.en.md
+Section|news|news|news
+TranslationBaseName|a|index|_index
+UniqueID|15be14b...|186868f...|7d9159d...
+
+## Defensive coding
+
+Some of the pages on a site may not be backed by a file. For example:
+
+- Top-level section pages
+- Taxonomy pages
+- Term pages
+
+Without a backing file, Hugo will throw an error if you attempt to access a `.File` property. To code defensively, first check for file existence:
+
+```go-html-template
+{{ with .File }}
+ {{ .ContentBaseName }}
+{{ end }}
+```
+
+[content adapter]: /content-management/content-adapters/
diff --git a/docs/content/en/methods/page/FirstSection.md b/docs/content/en/methods/page/FirstSection.md
new file mode 100644
index 00000000000..73ddd2d7ba2
--- /dev/null
+++ b/docs/content/en/methods/page/FirstSection.md
@@ -0,0 +1,48 @@
+---
+title: FirstSection
+description: Returns the Page object of the top-level section of which the given page is a descendant.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Page
+ signatures: [PAGE.FirstSection]
+---
+
+{{% glossary-term section %}}
+
+> [!note]
+> When called on the home page, the `FirstSection` method returns the `Page` object of the home page itself.
+
+Consider this content structure:
+
+```text
+content/
+├── auctions/
+│ ├── 2023-11/
+│ │ ├── _index.md <-- first section: auctions
+│ │ ├── auction-1.md
+│ │ └── auction-2.md <-- first section: auctions
+│ ├── 2023-12/
+│ │ ├── _index.md
+│ │ ├── auction-3.md
+│ │ └── auction-4.md
+│ ├── _index.md <-- first section: auctions
+│ ├── bidding.md
+│ └── payment.md <-- first section: auctions
+├── books/
+│ ├── _index.md <-- first section: books
+│ ├── book-1.md
+│ └── book-2.md <-- first section: books
+├── films/
+│ ├── _index.md <-- first section: films
+│ ├── film-1.md
+│ └── film-2.md <-- first section: films
+└── _index.md <-- first section: home
+```
+
+To link to the top-level section of which the current page is a descendant:
+
+```go-html-template
+{{ .FirstSection.LinkTitle }}
+```
diff --git a/docs/content/en/methods/page/Fragments.md b/docs/content/en/methods/page/Fragments.md
new file mode 100644
index 00000000000..9df47f31c2d
--- /dev/null
+++ b/docs/content/en/methods/page/Fragments.md
@@ -0,0 +1,109 @@
+---
+title: Fragments
+description: Returns a data structure of the fragments in the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: tableofcontents.Fragments
+ signatures: [PAGE.Fragments]
+---
+
+In a URL, whether absolute or relative, the [fragment](g) links to an `id` attribute of an HTML element on the page.
+
+```text
+/articles/article-1#section-2
+------------------- ---------
+ path fragment
+```
+
+Hugo assigns an `id` attribute to each Markdown [ATX] and [setext] heading within the page content. You can override the `id` with a [Markdown attribute](g) as needed. This creates the relationship between an entry in the [table of contents] (TOC) and a heading on the page.
+
+Use the `Fragments` method on a `Page` object to create a table of contents with the `Fragments.ToHTML` method, or by [walking](g) the `Fragments.Map` data structure.
+
+## Methods
+
+### Headings
+
+(`slice`) A slice of maps of all headings on the page, with first-level keys for each heading. Each map contains the following keys: `ID`, `Level`, `Title` and `Headings`. To inspect the data structure:
+
+```go-html-template
+
{{ debug.Dump .Fragments.Headings }}
+```
+
+### HeadingsMap
+
+(`map`) A nested map of all headings on the page. Each map contains the following keys: `ID`, `Level`, `Title` and `Headings`. To inspect the data structure:
+
+```go-html-template
+
{{ debug.Dump .Fragments.HeadingsMap }}
+```
+
+### Identifiers
+
+(`slice`) A slice containing the `id` attribute of each heading on the page. If so configured, will also contain the `id` attribute of each description term (i.e., `dt` element) on the page.
+
+See [configure Markup](/configuration/markup/#parserautodefinitiontermid).
+
+To inspect the data structure:
+
+```go-html-template
+
{{ debug.Dump .Fragments.Identifiers }}
+```
+
+### Identifiers.Contains ID
+
+(`bool`) Reports whether one or more headings on the page has the given `id` attribute, useful for validating fragments within a link [render hook](g).
+
+```go-html-template
+{{ .Fragments.Identifiers.Contains "section-2" }} → true
+```
+
+### Identifiers.Count ID
+
+(`int`) The number of headings on a page with the given `id` attribute, useful for detecting duplicates.
+
+```go-html-template
+{{ .Fragments.Identifiers.Count "section-2" }} → 1
+```
+
+### ToHTML
+
+(`template.HTML`) Returns a TOC as a nested list, either ordered or unordered, identical to the HTML returned by the [`TableOfContents`] method. This method take three arguments: the start level (`int`), the end level (`int`), and a boolean (`true` to return an ordered list, `false` to return an unordered list).
+
+Use this method when you want to control the start level, end level, or list type independently from the table of contents settings in your project configuration.
+
+```go-html-template
+{{ $startLevel := 2 }}
+{{ $endLevel := 3 }}
+{{ $ordered := true }}
+{{ .Fragments.ToHTML $startLevel $endLevel $ordered }}
+```
+
+Hugo renders this to:
+
+```html
+
+```
+
+> [!note]
+> It is safe to use the `Fragments` methods within a render hook, even for the current page.
+>
+> When using the `Fragments` methods within a shortcode, call the shortcode using [standard notation]. If you use [Markdown notation] the rendered shortcode is included in the creation of the fragments map, resulting in a circular loop.
+
+[`TableOfContents`]: /methods/page/tableofcontents/
+[ATX]: https://spec.commonmark.org/current/#atx-headings
+[Markdown notation]: /content-management/shortcodes/#notation
+[setext]: https://spec.commonmark.org/current/#setext-headings
+[standard notation]: /content-management/shortcodes/#notation
+[table of contents]: /methods/page/tableofcontents/
diff --git a/docs/content/en/methods/page/FuzzyWordCount.md b/docs/content/en/methods/page/FuzzyWordCount.md
new file mode 100644
index 00000000000..815a0740215
--- /dev/null
+++ b/docs/content/en/methods/page/FuzzyWordCount.md
@@ -0,0 +1,18 @@
+---
+title: FuzzyWordCount
+description: Returns the number of words in the content of the given page, rounded up to the nearest multiple of 100.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: int
+ signatures: [PAGE.FuzzyWordCount]
+---
+
+```go-html-template
+{{ .FuzzyWordCount }} → 200
+```
+
+To get the exact word count, use the [`WordCount`] method.
+
+[`WordCount`]: /methods/page/wordcount/
diff --git a/docs/content/en/methods/page/GetPage.md b/docs/content/en/methods/page/GetPage.md
new file mode 100644
index 00000000000..7c1e45dd935
--- /dev/null
+++ b/docs/content/en/methods/page/GetPage.md
@@ -0,0 +1,64 @@
+---
+title: GetPage
+description: Returns a Page object from the given path.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Page
+ signatures: [PAGE.GetPage PATH]
+aliases: [/functions/getpage]
+---
+
+The `GetPage` method is also available on a `Site` object. See [details].
+
+[details]: /methods/site/getpage/
+
+When using the `GetPage` method on the `Page` object, specify a path relative to the current directory or relative to the `content` directory.
+
+If Hugo cannot resolve the path to a page, the method returns nil. If the path is ambiguous, Hugo throws an error and fails the build.
+
+Consider this content structure:
+
+```text
+content/
+├── works/
+│ ├── paintings/
+│ │ ├── _index.md
+│ │ ├── starry-night.md
+│ │ └── the-mona-lisa.md
+│ ├── sculptures/
+│ │ ├── _index.md
+│ │ ├── david.md
+│ │ └── the-thinker.md
+│ └── _index.md
+└── _index.md
+```
+
+The examples below depict the result of rendering `works/paintings/the-mona-lisa.md`:
+
+```go-html-template {file="layouts/works/page.html"}
+{{ with .GetPage "starry-night" }}
+ {{ .Title }} → Starry Night
+{{ end }}
+
+{{ with .GetPage "./starry-night" }}
+ {{ .Title }} → Starry Night
+{{ end }}
+
+{{ with .GetPage "../paintings/starry-night" }}
+ {{ .Title }} → Starry Night
+{{ end }}
+
+{{ with .GetPage "/works/paintings/starry-night" }}
+ {{ .Title }} → Starry Night
+{{ end }}
+
+{{ with .GetPage "../sculptures/david" }}
+ {{ .Title }} → David
+{{ end }}
+
+{{ with .GetPage "/works/sculptures/david" }}
+ {{ .Title }} → David
+{{ end }}
+```
diff --git a/docs/content/en/methods/page/GetTerms.md b/docs/content/en/methods/page/GetTerms.md
new file mode 100644
index 00000000000..53b996fc519
--- /dev/null
+++ b/docs/content/en/methods/page/GetTerms.md
@@ -0,0 +1,41 @@
+---
+title: GetTerms
+description: Returns a collection of term pages for terms defined on the given page in the given taxonomy, ordered according to the sequence in which they appear in front matter.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Pages
+ signatures: [PAGE.GetTerms TAXONOMY]
+---
+
+Given this front matter:
+
+{{< code-toggle file=content/books/les-miserables.md fm=true >}}
+title = 'Les Misérables'
+tags = ['historical','classic','fiction']
+{{< /code-toggle >}}
+
+This template code:
+
+```go-html-template
+{{ with .GetTerms "tags" }}
+
+```
diff --git a/docs/content/en/methods/page/GitInfo.md b/docs/content/en/methods/page/GitInfo.md
new file mode 100644
index 00000000000..8a60f0934b6
--- /dev/null
+++ b/docs/content/en/methods/page/GitInfo.md
@@ -0,0 +1,194 @@
+---
+title: GitInfo
+description: Provides access to commit metadata for a given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: '*gitmap.GitInfo'
+ signatures: [PAGE.GitInfo]
+---
+
+The `GitInfo` method on a `Page` object provides access to commit metadata from your Git history, such as the author's name, the commit hash, and the commit message.
+
+> [!note]
+> Hugo's Git integration is performant, but may increase build times for large projects.
+
+## Prerequisites
+
+Install Git, create a repository, and commit your project files.
+
+You must also allow Hugo to access your repository by adding this to your project configuration:
+
+{{< code-toggle file=hugo >}}
+enableGitInfo = true
+{{< /code-toggle >}}
+
+> [!note]
+> When you set [`enableGitInfo`][] to `true`, the last modification date for each content page will automatically be the Author Date of the last commit for that file.
+>
+> This is configurable. See [details][].
+
+## Scope
+
+Commit metadata is available for content stored in your local repository and for content provided by [modules](g).
+
+### Local content
+
+Hugo retrieves commit metadata for files tracked within your project's local repository. This includes all content files managed by Git in your main project directory.
+
+### Module content
+
+{{< new-in 0.157.0 />}}
+
+Hugo also retrieves commit metadata for content provided by modules. This allows you to display commit data for remote repositories that are mounted as content directories, such as when aggregating documentation from multiple sources.
+
+## Methods
+
+### AbbreviatedHash
+
+(`string`) Returns the seven-character shortened version of the commit hash.
+
+```go-html-template
+{{ with .GitInfo }}
+ {{ .AbbreviatedHash }} → aab9ec0
+{{ end }}
+```
+
+### AuthorDate
+
+(`time.Time`) Returns the date the author originally created the commit.
+
+```go-html-template
+{{ with .GitInfo }}
+ {{ .AuthorDate.Format "2006-01-02" }} → 2023-10-09
+{{ end }}
+```
+
+### AuthorEmail
+
+(`string`) Returns the author's email address, respecting [gitmailmap][].
+
+```go-html-template
+{{ with .GitInfo }}
+ {{ .AuthorEmail }} → jsmith@example.org
+{{ end }}
+```
+
+### AuthorName
+
+(`string`) Returns the author's name, respecting [gitmailmap][].
+
+```go-html-template
+{{ with .GitInfo }}
+ {{ .AuthorName }} → John Smith
+{{ end }}
+```
+
+### CommitDate
+
+(`time.Time`) Returns the date the commit was applied to the branch.
+
+```go-html-template
+{{ with .GitInfo }}
+ {{ .CommitDate.Format "2006-01-02" }} → 2023-10-09
+{{ end }}
+```
+
+### Hash
+
+(`string`) Returns the full SHA-1 commit hash.
+
+```go-html-template
+{{ with .GitInfo }}
+ {{ .Hash }} → aab9ec0b31ebac916a1468c4c9c305f2bebf78d4
+{{ end }}
+```
+
+### Subject
+
+(`string`) Returns the first line of the commit message (the summary).
+
+```go-html-template
+{{ with .GitInfo }}
+ {{ .Subject }} → Add tutorials
+{{ end }}
+```
+
+### Body
+
+(`string`) Returns the full content of the commit message, excluding the subject line.
+
+```go-html-template
+{{ with .GitInfo }}
+ {{ .Body }} → Two new pages added.
+{{ end }}
+```
+
+### Ancestors
+
+(`gitmap.GitInfos`) Returns a list of previous commits for this specific file, ordered from most recent to oldest.
+
+For example, to list the last 5 commits:
+
+```go-html-template
+{{ with .GitInfo }}
+ {{ range .Ancestors | first 5 }}
+ {{ .CommitDate.Format "2006-01-02" }}: {{ .Subject }}
+ {{ end }}
+{{ end }}
+```
+
+To reverse the order:
+
+```go-html-template
+{{ with .GitInfo }}
+ {{ range .Ancestors.Reverse | first 5 }}
+ {{ .CommitDate.Format "2006-01-02" }}: {{ .Subject }}
+ {{ end }}
+{{ end }}
+```
+
+### Parent
+
+(`*gitmap.GitInfo`) Returns the most recent ancestor commit for the file, if any.
+
+## Last modified date
+
+By default, when `enableGitInfo` is `true`, the `Lastmod` method on a `Page` object returns the Git AuthorDate of the last commit that included the file.
+
+You can change this behavior in your [project configuration][].
+
+## Hosting considerations
+
+On a [CI/CD](g) platform, the step that clones your project repository must perform a deep clone. If the clone is shallow, the Git information for a given file may be inaccurate. It might incorrectly reflect the most recent repository commit, rather than the commit that actually modified the file.
+
+While some providers perform a deep clone by default, others require you to configure the depth yourself.
+
+Hosting service|Default clone depth|Configurable
+:--|:--|:--
+AWS Amplify|Deep|N/A
+Cloudflare|Shallow|Yes [^1]
+DigitalOcean App Platform|Deep|N/A
+GitHub Pages|Shallow|Yes [^2]
+GitLab Pages|Shallow|Yes [^3]
+Netlify|Deep|N/A
+Render|Shallow|Yes [^1]
+Vercel|Shallow|Yes [^1]
+
+[^1]: To perform a deep clone when hosting on Cloudflare, Render, or Vercel, include this code in the build script after the repository has been cloned:
+
+ ```text
+ if [ "$(git rev-parse --is-shallow-repository)" = "true" ]; then
+ git fetch --unshallow
+ fi
+ ```
+
+[^2]: To perform a deep clone when hosting on GitHub Pages, set `fetch-depth: 0` in the `checkout` step of the GitHub Action. See [example](/host-and-deploy/host-on-github-pages/#step-7).
+
+[^3]: To perform a deep clone when hosting on GitLab Pages, set the `GIT_DEPTH` environment variable to `0` in the workflow file. See [example](/host-and-deploy/host-on-gitlab-pages/#configure-gitlab-cicd).
+
+[`enableGitInfo`]: /configuration/all/#enablegitinfo
+[details]: /configuration/front-matter/#dates
+[gitmailmap]: https://git-scm.com/docs/gitmailmap
+[project configuration]: /configuration/front-matter/
diff --git a/docs/content/en/methods/page/HasMenuCurrent.md b/docs/content/en/methods/page/HasMenuCurrent.md
new file mode 100644
index 00000000000..22bcc6483c1
--- /dev/null
+++ b/docs/content/en/methods/page/HasMenuCurrent.md
@@ -0,0 +1,33 @@
+---
+title: HasMenuCurrent
+description: Reports whether the given Page object matches the Page object associated with one of the child menu entries under the given menu entry in the given menu.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: bool
+ signatures: [PAGE.HasMenuCurrent MENU MENUENTRY]
+aliases: [/functions/hasmenucurrent]
+---
+
+If the `Page` object associated with the menu entry is a section, this method also returns `true` for any descendant of that section.
+
+```go-html-template
+{{ $currentPage := . }}
+{{ range site.Menus.main }}
+ {{ if $currentPage.IsMenuCurrent .Menu . }}
+ {{ .Name }}
+ {{ else if $currentPage.HasMenuCurrent .Menu . }}
+ {{ .Name }}
+ {{ else }}
+ {{ .Name }}
+ {{ end }}
+{{ end }}
+```
+
+See [menu templates] for a complete example.
+
+> [!note]
+> When using this method you must either define the menu entry in front matter, or specify a `pageRef` property when defining the menu entry in your project configuration.
+
+[menu templates]: /templates/menu/#example
diff --git a/docs/content/en/methods/page/HasShortcode.md b/docs/content/en/methods/page/HasShortcode.md
new file mode 100644
index 00000000000..2e585da31e4
--- /dev/null
+++ b/docs/content/en/methods/page/HasShortcode.md
@@ -0,0 +1,50 @@
+---
+title: HasShortcode
+description: Reports whether the given shortcode is called by the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: bool
+ signatures: [PAGE.HasShortcode NAME]
+---
+
+By example, let's use [Plotly] to render a chart:
+
+[Plotly]: https://plotly.com/javascript/
+
+```text {file="content/example.md"}
+{{* plotly */>}}
+{
+ "data": [
+ {
+ "x": ["giraffes", "orangutans", "monkeys"],
+ "y": [20, 14, 23],
+ "type": "bar"
+ }
+ ],
+}
+{{* /plotly */>}}
+```
+
+The shortcode is simple:
+
+```go-html-template {file="layouts/_shortcodes/plotly.html"}
+{{ $id := printf "plotly-%02d" .Ordinal }}
+
+
+```
+
+Now we can selectively load the required JavaScript on pages that call the "plotly" shortcode:
+
+```go-html-template {file="layouts/baseof.html"}
+
+ ...
+ {{ if .HasShortcode "plotly" }}
+
+ {{ end }}
+ ...
+
+```
diff --git a/docs/content/en/methods/page/HeadingsFiltered.md b/docs/content/en/methods/page/HeadingsFiltered.md
new file mode 100644
index 00000000000..86c989d4333
--- /dev/null
+++ b/docs/content/en/methods/page/HeadingsFiltered.md
@@ -0,0 +1,16 @@
+---
+title: HeadingsFiltered
+description: Returns a slice of headings for each page related to the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: tableofcontents.Headings
+ signatures: [PAGE.HeadingsFiltered]
+---
+
+Use in conjunction with the [`Related`] method on a [`Pages`] object. See [details].
+
+[`Pages`]: /methods/pages/
+[`Related`]: /methods/pages/related/
+[details]: /content-management/related-content/#index-content-headings
diff --git a/docs/content/en/methods/page/InSection.md b/docs/content/en/methods/page/InSection.md
new file mode 100644
index 00000000000..adca82d8644
--- /dev/null
+++ b/docs/content/en/methods/page/InSection.md
@@ -0,0 +1,91 @@
+---
+title: InSection
+description: Reports whether the given page is in the given section.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: bool
+ signatures: [PAGE.InSection SECTION]
+---
+
+{{% glossary-term section %}}
+
+The `InSection` method on a `Page` object reports whether the given page is in the given section. Note that the method returns `true` when comparing a page to a sibling.
+
+With this content structure:
+
+```text
+content/
+├── auctions/
+│ ├── 2023-11/
+│ │ ├── _index.md
+│ │ ├── auction-1.md
+│ │ └── auction-2.md
+│ ├── 2023-12/
+│ │ ├── _index.md
+│ │ ├── auction-3.md
+│ │ └── auction-4.md
+│ ├── _index.md
+│ ├── bidding.md
+│ └── payment.md
+└── _index.md
+```
+
+When rendering the "auction-1" page:
+
+```go-html-template
+{{ with .Site.GetPage "/" }}
+ {{ $.InSection . }} → false
+{{ end }}
+
+{{ with .Site.GetPage "/auctions" }}
+ {{ $.InSection . }} → false
+{{ end }}
+
+{{ with .Site.GetPage "/auctions/2023-11" }}
+ {{ $.InSection . }} → true
+{{ end }}
+
+{{ with .Site.GetPage "/auctions/2023-11/auction-2" }}
+ {{ $.InSection . }} → true
+{{ end }}
+```
+
+In the examples above we are coding defensively using the [`with`] statement, returning nothing if the page does not exist. By adding an [`else`] clause we can do some error reporting:
+
+```go-html-template
+{{ $path := "/auctions/2023-11" }}
+{{ with .Site.GetPage $path }}
+ {{ $.InSection . }} → true
+{{ else }}
+ {{ errorf "Unable to find the section with path %s" $path }}
+{{ end }}
+ ```
+
+## Understanding context
+
+Inside of the `with` block, the [context](g) (the dot) is the section `Page` object, not the `Page` object passed into the template. If we were to use this syntax:
+
+```go-html-template
+{{ with .Site.GetPage "/auctions" }}
+ {{ .InSection . }} → true
+{{ end }}
+```
+
+The result would be wrong when rendering the "auction-1" page because we are comparing the section page to itself.
+
+> [!note]
+> Use the `$` to get the context passed into the template.
+
+```go-html-template
+{{ with .Site.GetPage "/auctions" }}
+ {{ $.InSection . }} → true
+{{ end }}
+```
+
+> [!note]
+> Gaining a thorough understanding of context is critical for anyone writing template code.
+
+[`else`]: /functions/go-template/else/
+[`with`]: /functions/go-template/with/
diff --git a/docs/content/en/methods/page/IsAncestor.md b/docs/content/en/methods/page/IsAncestor.md
new file mode 100644
index 00000000000..fe1b7845419
--- /dev/null
+++ b/docs/content/en/methods/page/IsAncestor.md
@@ -0,0 +1,87 @@
+---
+title: IsAncestor
+description: Reports whether PAGE1 is an ancestor of PAGE2.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: bool
+ signatures: [PAGE1.IsAncestor PAGE2]
+---
+
+With this content structure:
+
+```text
+content/
+├── auctions/
+│ ├── 2023-11/
+│ │ ├── _index.md
+│ │ ├── auction-1.md
+│ │ └── auction-2.md
+│ ├── 2023-12/
+│ │ ├── _index.md
+│ │ ├── auction-3.md
+│ │ └── auction-4.md
+│ ├── _index.md
+│ ├── bidding.md
+│ └── payment.md
+└── _index.md
+```
+
+When rendering the "auctions" page:
+
+```go-html-template
+{{ with .Site.GetPage "/" }}
+ {{ $.IsAncestor . }} → false
+{{ end }}
+
+{{ with .Site.GetPage "/auctions" }}
+ {{ $.IsAncestor . }} → false
+{{ end }}
+
+{{ with .Site.GetPage "/auctions/2023-11" }}
+ {{ $.IsAncestor . }} → true
+{{ end }}
+
+{{ with .Site.GetPage "/auctions/2023-11/auction-2" }}
+ {{ $.IsAncestor . }} → true
+{{ end }}
+```
+
+In the examples above we are coding defensively using the [`with`] statement, returning nothing if the page does not exist. By adding an [`else`] clause we can do some error reporting:
+
+```go-html-template
+{{ $path := "/auctions/2023-11" }}
+{{ with .Site.GetPage $path }}
+ {{ $.IsAncestor . }} → true
+{{ else }}
+ {{ errorf "Unable to find the section with path %s" $path }}
+{{ end }}
+ ```
+
+## Understanding context
+
+Inside of the `with` block, the [context](g) (the dot) is the section `Page` object, not the `Page` object passed into the template. If we were to use this syntax:
+
+```go-html-template
+{{ with .Site.GetPage "/auctions" }}
+ {{ .IsAncestor . }} → true
+{{ end }}
+```
+
+The result would be wrong when rendering the "auction-1" page because we are comparing the section page to itself.
+
+> [!note]
+> Use the `$` to get the context passed into the template.
+
+```go-html-template
+{{ with .Site.GetPage "/auctions" }}
+ {{ $.IsAncestor . }} → true
+{{ end }}
+```
+
+> [!note]
+> Gaining a thorough understanding of context is critical for anyone writing template code.
+
+[`else`]: /functions/go-template/else/
+[`with`]: /functions/go-template/with/
diff --git a/docs/content/en/methods/page/IsDescendant.md b/docs/content/en/methods/page/IsDescendant.md
new file mode 100644
index 00000000000..6ee8d3c4f76
--- /dev/null
+++ b/docs/content/en/methods/page/IsDescendant.md
@@ -0,0 +1,87 @@
+---
+title: IsDescendant
+description: Reports whether PAGE1 is a descendant of PAGE2.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: bool
+ signatures: [PAGE1.IsDescendant PAGE2]
+---
+
+With this content structure:
+
+```text
+content/
+├── auctions/
+│ ├── 2023-11/
+│ │ ├── _index.md
+│ │ ├── auction-1.md
+│ │ └── auction-2.md
+│ ├── 2023-12/
+│ │ ├── _index.md
+│ │ ├── auction-3.md
+│ │ └── auction-4.md
+│ ├── _index.md
+│ ├── bidding.md
+│ └── payment.md
+└── _index.md
+```
+
+When rendering the "auctions" page:
+
+```go-html-template
+{{ with .Site.GetPage "/" }}
+ {{ $.IsDescendant . }} → true
+{{ end }}
+
+{{ with .Site.GetPage "/auctions" }}
+ {{ $.IsDescendant . }} → false
+{{ end }}
+
+{{ with .Site.GetPage "/auctions/2023-11" }}
+ {{ $.IsDescendant . }} → false
+{{ end }}
+
+{{ with .Site.GetPage "/auctions/2023-11/auction-2" }}
+ {{ $.IsDescendant . }} → false
+{{ end }}
+```
+
+In the examples above we are coding defensively using the [`with`] statement, returning nothing if the page does not exist. By adding an [`else`] clause we can do some error reporting:
+
+```go-html-template
+{{ $path := "/auctions/2023-11" }}
+{{ with .Site.GetPage $path }}
+ {{ $.IsDescendant . }} → true
+{{ else }}
+ {{ errorf "Unable to find the section with path %s" $path }}
+{{ end }}
+ ```
+
+## Understanding context
+
+Inside of the `with` block, the [context](g) (the dot) is the section `Page` object, not the `Page` object passed into the template. If we were to use this syntax:
+
+```go-html-template
+{{ with .Site.GetPage "/auctions" }}
+ {{ .IsDescendant . }} → true
+{{ end }}
+```
+
+The result would be wrong when rendering the "auction-1" page because we are comparing the section page to itself.
+
+> [!note]
+> Use the `$` to get the context passed into the template.
+
+```go-html-template
+{{ with .Site.GetPage "/auctions" }}
+ {{ $.IsDescendant . }} → true
+{{ end }}
+```
+
+> [!note]
+> Gaining a thorough understanding of context is critical for anyone writing template code.
+
+[`else`]: /functions/go-template/else/
+[`with`]: /functions/go-template/with/
diff --git a/docs/content/en/methods/page/IsHome.md b/docs/content/en/methods/page/IsHome.md
new file mode 100644
index 00000000000..66d8180b067
--- /dev/null
+++ b/docs/content/en/methods/page/IsHome.md
@@ -0,0 +1,26 @@
+---
+title: IsHome
+description: Reports whether the given page is the home page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: bool
+ signatures: [PAGE.IsHome]
+---
+
+The `IsHome` method on a `Page` object returns `true` if the [page kind](g) is `home`.
+
+```text
+content/
+├── books/
+│ ├── book-1/
+│ │ └── index.md <-- kind = page
+│ ├── book-2.md <-- kind = page
+│ └── _index.md <-- kind = section
+└── _index.md <-- kind = home
+```
+
+```go-html-template
+{{ .IsHome }}
+```
diff --git a/docs/content/en/methods/page/IsMenuCurrent.md b/docs/content/en/methods/page/IsMenuCurrent.md
new file mode 100644
index 00000000000..60b68428a6a
--- /dev/null
+++ b/docs/content/en/methods/page/IsMenuCurrent.md
@@ -0,0 +1,31 @@
+---
+title: IsMenuCurrent
+description: Reports whether the given Page object matches the Page object associated with the given menu entry in the given menu.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: bool
+ signatures: [PAGE.IsMenuCurrent MENU MENUENTRY]
+aliases: [/functions/ismenucurrent]
+---
+
+```go-html-template
+{{ $currentPage := . }}
+{{ range site.Menus.main }}
+ {{ if $currentPage.IsMenuCurrent .Menu . }}
+ {{ .Name }}
+ {{ else if $currentPage.HasMenuCurrent .Menu . }}
+ {{ .Name }}
+ {{ else }}
+ {{ .Name }}
+ {{ end }}
+{{ end }}
+```
+
+See [menu templates] for a complete example.
+
+> [!note]
+> When using this method you must either define the menu entry in front matter, or specify a `pageRef` property when defining the menu entry in your project configuration.
+
+[menu templates]: /templates/menu/#example
diff --git a/docs/content/en/methods/page/IsNode.md b/docs/content/en/methods/page/IsNode.md
new file mode 100644
index 00000000000..24e7c033ea1
--- /dev/null
+++ b/docs/content/en/methods/page/IsNode.md
@@ -0,0 +1,30 @@
+---
+title: IsNode
+description: Reports whether the given page is a node.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: bool
+ signatures: [PAGE.IsNode]
+---
+
+The `IsNode` method on a `Page` object checks if the [page kind](g) is one of the following: `home`, `section`, `taxonomy`, or `term`. If it is, the method returns `true`, indicating the page is a [node](g). Otherwise, if the page kind is page, it returns `false`.
+
+```text
+content/
+├── books/
+│ ├── book-1/
+│ │ └── index.md <-- kind = page IsNode = false
+│ ├── book-2.md <-- kind = page IsNode = false
+│ └── _index.md <-- kind = section IsNode = true
+├── tags
+│ ├── fiction
+│ │ └── _index.md <-- kind = term IsNode = true
+│ └── _index.md <-- kind = taxonomy IsNode = true
+└── _index.md <-- kind = home IsNode = true
+```
+
+```go-html-template
+{{ .IsNode }}
+```
diff --git a/docs/content/en/methods/page/IsPage.md b/docs/content/en/methods/page/IsPage.md
new file mode 100644
index 00000000000..910a3a7e10b
--- /dev/null
+++ b/docs/content/en/methods/page/IsPage.md
@@ -0,0 +1,26 @@
+---
+title: IsPage
+description: Reports whether the given page is a regular page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: bool
+ signatures: [PAGE.IsPage]
+---
+
+The `IsPage` method on a `Page` object returns `true` if the [page kind](g) is `page`.
+
+```text
+content/
+├── books/
+│ ├── book-1/
+│ │ └── index.md <-- kind = page
+│ ├── book-2.md <-- kind = page
+│ └── _index.md <-- kind = section
+└── _index.md <-- kind = home
+```
+
+```go-html-template
+{{ .IsPage }}
+```
diff --git a/docs/content/en/methods/page/IsSection.md b/docs/content/en/methods/page/IsSection.md
new file mode 100644
index 00000000000..7a04fbd8fba
--- /dev/null
+++ b/docs/content/en/methods/page/IsSection.md
@@ -0,0 +1,26 @@
+---
+title: IsSection
+description: Reports whether the given page is a section page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: bool
+ signatures: [PAGE.IsSection]
+---
+
+The `IsSection` method on a `Page` object returns `true` if the [page kind](g) is `section`.
+
+```text
+content/
+├── books/
+│ ├── book-1/
+│ │ └── index.md <-- kind = page
+│ ├── book-2.md <-- kind = page
+│ └── _index.md <-- kind = section
+└── _index.md <-- kind = home
+```
+
+```go-html-template
+{{ .IsSection }}
+```
diff --git a/docs/content/en/methods/page/IsTranslated.md b/docs/content/en/methods/page/IsTranslated.md
new file mode 100644
index 00000000000..a9d12b9fb6e
--- /dev/null
+++ b/docs/content/en/methods/page/IsTranslated.md
@@ -0,0 +1,56 @@
+---
+title: IsTranslated
+description: Reports whether the given page has one or more translations.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: bool
+ signatures: [PAGE.IsTranslated]
+---
+
+With this project configuration:
+
+{{< code-toggle file=hugo >}}
+defaultContentLanguage = 'en'
+
+[languages.en]
+contentDir = 'content/en'
+label = 'English'
+locale = 'en-US'
+weight = 1
+
+[languages.de]
+contentDir = 'content/de'
+label = 'Deutsch'
+locale = 'de-DE'
+weight = 2
+{{< /code-toggle >}}
+
+And this content:
+
+```text
+content/
+├── de/
+│ ├── books/
+│ │ └── book-1.md
+│ └── _index.md
+├── en/
+│ ├── books/
+│ │ ├── book-1.md
+│ │ └── book-2.md
+│ └── _index.md
+└── _index.md
+```
+
+When rendering `content/en/books/book-1.md`:
+
+```go-html-template
+{{ .IsTranslated }} → true
+```
+
+When rendering `content/en/books/book-2.md`:
+
+```go-html-template
+{{ .IsTranslated }} → false
+```
diff --git a/docs/content/en/methods/page/Keywords.md b/docs/content/en/methods/page/Keywords.md
new file mode 100644
index 00000000000..7c940984eff
--- /dev/null
+++ b/docs/content/en/methods/page/Keywords.md
@@ -0,0 +1,46 @@
+---
+title: Keywords
+description: Returns a slice of keywords as defined in front matter.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: '[]string'
+ signatures: [PAGE.Keywords]
+---
+
+By default, Hugo evaluates the keywords when creating collections of [related content].
+
+[related content]: /content-management/related-content/
+
+{{< code-toggle file=content/recipes/sushi.md fm=true >}}
+title = 'How to make spicy tuna hand rolls'
+keywords = ['tuna','sriracha','nori','rice']
+{{< /code-toggle >}}
+
+To list the keywords within a template:
+
+```go-html-template
+{{ range .Keywords }}
+ {{ . }}
+{{ end }}
+```
+
+Or use the [delimit] function:
+
+```go-html-template
+{{ delimit .Keywords ", " ", and " }} → tuna, sriracha, nori, and rice
+```
+
+[delimit]: /functions/collections/delimit/
+
+Keywords are also a useful [taxonomy]:
+
+{{< code-toggle file=hugo >}}
+[taxonomies]
+tag = 'tags'
+keyword = 'keywords'
+category = 'categories'
+{{< /code-toggle >}}
+
+[taxonomy]: /content-management/taxonomies/
diff --git a/docs/content/en/methods/page/Kind.md b/docs/content/en/methods/page/Kind.md
new file mode 100644
index 00000000000..a01877e8c5e
--- /dev/null
+++ b/docs/content/en/methods/page/Kind.md
@@ -0,0 +1,32 @@
+---
+title: Kind
+description: Returns the kind of the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [PAGE.Kind]
+---
+
+The [page kind](g) is one of `home`, `page`, `section`, `taxonomy`, or `term`.
+
+```text
+content/
+├── books/
+│ ├── book-1/
+│ │ └── index.md <-- kind = page
+│ ├── book-2.md <-- kind = page
+│ └── _index.md <-- kind = section
+├── tags/
+│ ├── fiction/
+│ │ └── _index.md <-- kind = term
+│ └── _index.md <-- kind = taxonomy
+└── _index.md <-- kind = home
+```
+
+To get the value within a template:
+
+```go-html-template
+{{ .Kind }}
+```
diff --git a/docs/content/en/methods/page/Language.md b/docs/content/en/methods/page/Language.md
new file mode 100644
index 00000000000..26a40e5e05f
--- /dev/null
+++ b/docs/content/en/methods/page/Language.md
@@ -0,0 +1,135 @@
+---
+title: Language
+description: Returns the Language object for the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: langs.Language
+ signatures: [PAGE.Language]
+---
+
+The `Language` method on a `Page` object returns the `Language` object for the given page, derived from the language definition in your project configuration.
+
+You can also use the `Language` method on a `Site` object. See [details][].
+
+## Methods
+
+The examples below assume the following language definition.
+
+{{< code-toggle file=hugo >}}
+[languages.de]
+direction = 'ltr'
+label = 'Deutsch'
+locale = 'de-DE'
+weight = 2
+{{< /code-toggle >}}
+
+### Direction
+
+{{< new-in 0.158.0 />}}
+
+(`string`) Returns the [`direction`][] from the language definition.
+
+```go-html-template
+{{ .Language.Direction }} → ltr
+```
+
+### IsDefault
+
+{{< new-in 0.153.0 />}}
+
+(`bool`) Reports whether this is the [default language][].
+
+```go-html-template
+{{ .Language.IsDefault }} → true
+```
+
+### Label
+
+{{< new-in 0.158.0 />}}
+
+(`string`) Returns the [`label`][] from the language definition.
+
+```go-html-template
+{{ .Language.Label }} → Deutsch
+```
+
+### Lang
+
+{{}}
+
+Use [`Name`](#name) instead.
+
+### LanguageCode
+
+{{}}
+
+Use [`Locale`](#locale) instead.
+
+### LanguageDirection
+
+{{}}
+
+Use [`Direction`](#direction) instead.
+
+### LanguageName
+
+{{}}
+
+Use [`Label`](#label) instead.
+
+### Locale
+
+{{< new-in 0.158.0 />}}
+
+(`string`) Returns the [`locale`][] from the language definition, falling back to [`Name`](#name).
+
+```go-html-template
+{{ .Language.Locale }} → de-DE
+```
+
+### Name
+
+{{< new-in 0.153.0 />}}
+
+(`string`) Returns the language tag as defined by [RFC 5646][]. This is the lowercased key from the language definition.
+
+```go-html-template
+{{ .Language.Name }} → de
+```
+
+### Weight
+
+{{}}
+
+[RFC 5646]: https://datatracker.ietf.org/doc/html/rfc5646
+[`direction`]: /configuration/languages/#direction
+[`label`]: /configuration/languages/#label
+[`locale`]: /configuration/languages/#locale
+[default language]: /quick-reference/glossary/#default-language
+[details]: /methods/site/language/
+
+## Example
+
+Use the code below to create a language selector, allowing users to navigate between the different translated versions of the current page.
+
+```go-html-template {file="layouts/_partials/language-selector.html" copy=true}
+{{ with .Rotate "language" }}
+
+{{ end }}
+```
diff --git a/docs/content/en/methods/page/Lastmod.md b/docs/content/en/methods/page/Lastmod.md
new file mode 100644
index 00000000000..643eddc5e2d
--- /dev/null
+++ b/docs/content/en/methods/page/Lastmod.md
@@ -0,0 +1,36 @@
+---
+title: Lastmod
+description: Returns the last modification date of the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: time.Time
+ signatures: [PAGE.Lastmod]
+---
+
+Set the last modification date in front matter:
+
+{{< code-toggle file=content/news/article-1.md fm=true >}}
+title = 'Article 1'
+lastmod = 2023-10-19T00:40:04-07:00
+{{< /code-toggle >}}
+
+The last modification date is a [time.Time] value. Format and localize the value with the [`time.Format`] function, or use it with any of the [time methods].
+
+```go-html-template
+{{ .Lastmod | time.Format ":date_medium" }} → Oct 19, 2023
+```
+
+In the example above we explicitly set the last modification date in front matter. With Hugo's default configuration, the `Lastmod` method returns the front matter value. This behavior is configurable, allowing you to:
+
+- Set the last modification date to the Author Date of the last Git commit for that file. See [`GitInfo`] for details.
+- Set fallback values if the last modification date is not defined in front matter.
+
+Learn more about [date configuration].
+
+[`gitinfo`]: /methods/page/gitinfo/
+[`time.format`]: /functions/time/format/
+[date configuration]: /configuration/front-matter/#dates
+[time methods]: /methods/time/
+[time.time]: https://pkg.go.dev/time#Time
diff --git a/docs/content/en/methods/page/Layout.md b/docs/content/en/methods/page/Layout.md
new file mode 100644
index 00000000000..e038f383707
--- /dev/null
+++ b/docs/content/en/methods/page/Layout.md
@@ -0,0 +1,40 @@
+---
+title: Layout
+description: Returns the layout for the given page as defined in front matter.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [PAGE.Layout]
+---
+
+Specify the `layout` field in front matter to target a particular template. See [details].
+
+[details]: /templates/lookup-order/#target-a-template
+
+{{< code-toggle file=content/contact.md fm=true >}}
+title = 'Contact'
+layout = 'contact'
+{{< /code-toggle >}}
+
+Hugo will render the page using contact.html.
+
+```text
+layouts/
+├── baseof.html
+├── contact.html
+├── home.html
+├── page.html
+├── section.html
+├── taxonomy.html
+└── term.html
+```
+
+Although rarely used within a template, you can access the value with:
+
+```go-html-template
+{{ .Layout }}
+```
+
+The `Layout` method returns an empty string if the `layout` field in front matter is not defined.
diff --git a/docs/content/en/methods/page/Len.md b/docs/content/en/methods/page/Len.md
new file mode 100644
index 00000000000..010da88d16c
--- /dev/null
+++ b/docs/content/en/methods/page/Len.md
@@ -0,0 +1,14 @@
+---
+title: Len
+description: Returns the length, in bytes, of the rendered content of the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: int
+ signatures: [PAGE.Len]
+---
+
+```go-html-template
+{{ .Len }} → 42
+```
diff --git a/docs/content/en/methods/page/LinkTitle.md b/docs/content/en/methods/page/LinkTitle.md
new file mode 100644
index 00000000000..fcfd5318d5b
--- /dev/null
+++ b/docs/content/en/methods/page/LinkTitle.md
@@ -0,0 +1,29 @@
+---
+title: LinkTitle
+description: Returns the link title of the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [PAGE.LinkTitle]
+---
+
+The `LinkTitle` method returns the `linkTitle` field as defined in front matter, falling back to the value returned by the [`Title`] method.
+
+[`Title`]: /methods/page/title/
+
+{{< code-toggle file=content/articles/healthy-desserts.md fm=true >}}
+title = 'Seventeen delightful recipes for healthy desserts'
+linkTitle = 'Dessert recipes'
+{{< /code-toggle >}}
+
+```go-html-template
+{{ .LinkTitle }} → Dessert recipes
+```
+
+As demonstrated above, defining a link title in front matter is advantageous when the page title is long. Use it when generating anchor elements in your templates:
+
+```go-html-template
+{{ .LinkTitle }}
+```
diff --git a/docs/content/en/methods/page/Next.md b/docs/content/en/methods/page/Next.md
new file mode 100644
index 00000000000..9966030832e
--- /dev/null
+++ b/docs/content/en/methods/page/Next.md
@@ -0,0 +1,12 @@
+---
+title: Next
+description: Returns the next page in a site's collection of regular pages, relative to the current page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Page
+ signatures: [PAGE.Next]
+---
+
+{{% include "/_common/methods/page/next-and-prev.md" %}}
diff --git a/docs/content/en/methods/page/NextInSection.md b/docs/content/en/methods/page/NextInSection.md
new file mode 100644
index 00000000000..eb02c949290
--- /dev/null
+++ b/docs/content/en/methods/page/NextInSection.md
@@ -0,0 +1,12 @@
+---
+title: NextInSection
+description: Returns the next regular page in a section, relative to the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Page
+ signatures: [PAGE.NextInSection]
+---
+
+{{% include "/_common/methods/page/nextinsection-and-previnsection.md" %}}
diff --git a/docs/content/en/methods/page/OutputFormats.md b/docs/content/en/methods/page/OutputFormats.md
new file mode 100644
index 00000000000..19377feefbf
--- /dev/null
+++ b/docs/content/en/methods/page/OutputFormats.md
@@ -0,0 +1,73 @@
+---
+title: OutputFormats
+description: Returns a slice of OutputFormat objects, each representing one of the output formats enabled for the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: '[]OutputFormat'
+ signatures: [PAGE.OutputFormats]
+---
+
+{{% glossary-term "output format" %}}
+
+The `OutputFormats` method on a `Page` object returns a slice of `OutputFormat` objects, each representing one of the output formats enabled for the given page. See [details](/configuration/output-formats/).
+
+## Methods
+
+### Canonical
+
+{{< new-in "0.154.4" />}}
+
+(`page.OutputFormat`) Returns the [canonical output format](g) for the current page, if defined. Once you have captured the object, use any of its [associated methods][].
+
+```go-html-template
+{{ with .Site.Home.OutputFormats.Canonical }}
+ {{ .MediaType.Type }} → text/html
+ {{ .MediaType.MainType }} → text
+ {{ .MediaType.SubType }} → html
+ {{ .Name }} → html
+ {{ .Permalink }} → https://example.org/
+ {{ .Rel }} → canonical
+ {{ .RelPermalink }} → /
+{{ end }}
+```
+
+### Get
+
+(`page.OutputFormat`) Returns the `OutputFormat` object with the given identifier. Once you have captured the object, use any of its [associated methods][].
+
+```go-html-template
+{{ with .Site.Home.OutputFormats.Get "rss" }}
+ {{ .MediaType.Type }} → application/rss+xml
+ {{ .MediaType.MainType }} → application
+ {{ .MediaType.SubType }} → rss
+ {{ .Name }} → rss
+ {{ .Permalink }} → https://example.org/index.xml
+ {{ .Rel }} → alternate
+ {{ .RelPermalink }} → /index.xml
+{{ end }}
+```
+
+## Examples
+
+To render a `link` element pointing to the [canonical output format](g) for the current page:
+
+```go-html-template
+{{ with .OutputFormats.Canonical }}
+ {{ printf "" .Rel .MediaType.Type .Permalink | safeHTML }}
+{{ end }}
+```
+
+To render an anchor element pointing to the `rss` output format for the current page:
+
+```go-html-template
+{{ with .OutputFormats.Get "rss" }}
+ RSS Feed
+{{ end }}
+```
+
+Please see the [link to output formats] section to understand the importance of the construct above.
+
+[associated methods]: /methods/output-format/
+[link to output formats]: /configuration/output-formats/#link-to-output-formats
diff --git a/docs/content/en/methods/page/Page.md b/docs/content/en/methods/page/Page.md
new file mode 100644
index 00000000000..b7bdf355830
--- /dev/null
+++ b/docs/content/en/methods/page/Page.md
@@ -0,0 +1,35 @@
+---
+title: Page
+description: Returns the Page object of the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Page
+ signatures: [PAGE.Page]
+---
+
+This is a convenience method, useful within _partial_ templates that are called from both _shortcode_ and other template types.
+
+```go-html-template {file="layouts/_shortcodes/foo.html"}
+{{ partial "my-partial.html" . }}
+```
+
+When the _shortcode_ template calls the _partial_ template, it passes the current [context](g) (the dot). The context includes identifiers such as `Page`, `Params`, `Inner`, and `Name`.
+
+```go-html-template {file="layouts/page.html"}
+{{ partial "my-partial.html" . }}
+```
+
+When the _page_ template calls the _partial_ template, it also passes the current context (the dot). But in this case, the dot _is_ the `Page` object.
+
+```go-html-template {file="layouts/_partials/my-partial.html"}
+The page title is: {{ .Page.Title }}
+```
+
+To handle both scenarios, the _partial_ template must be able to access the `Page` object with `Page.Page`.
+
+> [!note]
+> And yes, that means you can do `.Page.Page.Page.Page.Title` too.
+>
+> But don't.
diff --git a/docs/content/en/methods/page/Pages.md b/docs/content/en/methods/page/Pages.md
new file mode 100644
index 00000000000..ba43c36a80a
--- /dev/null
+++ b/docs/content/en/methods/page/Pages.md
@@ -0,0 +1,82 @@
+---
+title: Pages
+description: Returns a collection of regular pages within the current section, and section pages of immediate descendant sections.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Pages
+ signatures: [PAGE.Pages]
+---
+
+The `Pages` method on a `Page` object is available to these [page kinds](g): `home`, `section`, `taxonomy`, and `term`. The templates for these page kinds receive a page [collection](g) in [context](g), in the [default sort order](g).
+
+Range through the page collection in your template:
+
+```go-html-template
+{{ range .Pages.ByTitle }}
+
+{{ end }}
+```
+
+[details]: /methods/site/pages/
diff --git a/docs/content/en/methods/page/Paginate.md b/docs/content/en/methods/page/Paginate.md
new file mode 100644
index 00000000000..452bf9daf8a
--- /dev/null
+++ b/docs/content/en/methods/page/Paginate.md
@@ -0,0 +1,47 @@
+---
+title: Paginate
+description: Paginates a collection of pages.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Pager
+ signatures: ['PAGE.Paginate COLLECTION [N]']
+---
+
+Pagination is the process of splitting a list page into two or more pagers, where each pager contains a subset of the page collection and navigation links to other pagers.
+
+By default, the number of elements on each pager is determined by your [project configuration]. The default is `10`. Override that value by providing a second argument, an integer, when calling the `Paginate` method.
+
+> [!note]
+> There is also a `Paginator` method on `Page` objects, but it can neither filter nor sort the page collection.
+>
+> The `Paginate` method is more flexible.
+
+You can invoke pagination in [home], [section], [taxonomy], and [term] templates.
+
+```go-html-template {file="layouts/section.html"}
+{{ $pages := where .Site.RegularPages "Section" "articles" }}
+{{ $pages = $pages.ByTitle }}
+{{ range (.Paginate $pages 7).Pages }}
+
+{{ end }}
+{{ partial "pagination.html" . }}
+```
+
+In the example above, we:
+
+1. Build a page collection
+1. Sort the collection by title
+1. Paginate the collection, with 7 elements per pager
+1. Range over the paginated page collection, rendering a link to each page
+1. Call the embedded pagination template to create navigation links between pagers
+
+> [!note]
+> Please note that the results of pagination are cached. Once you have invoked either the `Paginator` or `Paginate` method, the paginated collection is immutable. Additional invocations of these methods will have no effect.
+
+[home]: /templates/types/#home
+[section]: /templates/types/#section
+[project configuration]: /configuration/pagination/
+[taxonomy]: /templates/types/#taxonomy
+[term]: /templates/types/#term
diff --git a/docs/content/en/methods/page/Paginator.md b/docs/content/en/methods/page/Paginator.md
new file mode 100644
index 00000000000..059e55e2d6d
--- /dev/null
+++ b/docs/content/en/methods/page/Paginator.md
@@ -0,0 +1,40 @@
+---
+title: Paginator
+description: Paginates the collection of regular pages received in context.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Pager
+ signatures: [PAGE.Paginator]
+---
+
+Pagination is the process of splitting a list page into two or more pagers, where each pager contains a subset of the page collection and navigation links to other pagers.
+
+The number of elements on each pager is determined by your [project configuration]. The default is `10`.
+
+You can invoke pagination in [home], [section], [taxonomy], and [term] templates. Each of these receives a collection of regular pages in [context](g). When you invoke the `Paginator` method, it paginates the page collection received in context.
+
+```go-html-template {file="layouts/section.html"}
+{{ range .Paginator.Pages }}
+
+{{ end }}
+{{ partial "pagination.html" . }}
+```
+
+In the example above, the embedded pagination template creates navigation links between pagers.
+
+> [!note]
+> Although simple to invoke, with the `Paginator` method you can neither filter nor sort the page collection. It acts upon the page collection received in context.
+>
+> The [`Paginate`] method is more flexible, and strongly recommended.
+
+> [!note]
+> Please note that the results of pagination are cached. Once you have invoked either the `Paginator` or `Paginate` method, the paginated collection is immutable. Additional invocations of these methods will have no effect.
+
+[home]: /templates/types/#home
+[section]: /templates/types/#section
+[project configuration]: /configuration/pagination/
+[taxonomy]: /templates/types/#taxonomy
+[term]: /templates/types/#term
+[`Paginate`]: /methods/page/paginate/
diff --git a/docs/content/en/methods/page/Param.md b/docs/content/en/methods/page/Param.md
new file mode 100644
index 00000000000..b07c1cd920f
--- /dev/null
+++ b/docs/content/en/methods/page/Param.md
@@ -0,0 +1,48 @@
+---
+title: Param
+description: Returns a page parameter with the given key, falling back to a site parameter if present.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: any
+ signatures: [PAGE.Param KEY]
+aliases: [/functions/param]
+---
+
+The `Param` method on a `Page` object looks for the given `KEY` in page parameters, and returns the corresponding value. If it cannot find the `KEY` in page parameters, it looks for the `KEY` in site parameters. If it cannot find the `KEY` in either location, the `Param` method returns `nil`.
+
+Site and theme developers commonly set parameters at the site level, allowing content authors to override those parameters at the page level.
+
+For example, to show a table of contents on every page, but allow authors to hide the table of contents as needed:
+
+Configuration:
+
+{{< code-toggle file=hugo >}}
+[params]
+display_toc = true
+{{< /code-toggle >}}
+
+Content:
+
+{{< code-toggle file=content/example.md fm=true >}}
+title = 'Example'
+date = 2023-01-01
+draft = false
+[params]
+display_toc = false
+{{< /code-toggle >}}
+
+Template:
+
+```go-html-template
+{{ if .Param "display_toc" }}
+ {{ .TableOfContents }}
+{{ end }}
+```
+
+The `Param` method returns the value associated with the given `KEY`, regardless of whether the value is truthy or falsy. If you need to ignore falsy values, use this construct instead:
+
+```go-html-template
+{{ or .Params.foo site.Params.foo }}
+```
diff --git a/docs/content/en/methods/page/Params.md b/docs/content/en/methods/page/Params.md
new file mode 100644
index 00000000000..eeb2534378e
--- /dev/null
+++ b/docs/content/en/methods/page/Params.md
@@ -0,0 +1,42 @@
+---
+title: Params
+description: Returns a map of custom parameters as defined in the front matter of the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: maps.Params
+ signatures: [PAGE.Params]
+---
+
+By way of example, consider this front matter:
+
+{{< code-toggle file=content/annual-conference.md fm=true >}}
+title = 'Annual conference'
+date = 2023-10-17T15:11:37-07:00
+[params]
+display_related = true
+key-with-hyphens = 'must use index function'
+[params.author]
+ email = 'jsmith@example.org'
+ name = 'John Smith'
+{{< /code-toggle >}}
+
+The `title` and `date` fields are standard [front matter fields], while the other fields are user-defined.
+
+Access the custom fields by [chaining](g) the [identifiers](g) when needed:
+
+```go-html-template
+{{ .Params.display_related }} → true
+{{ .Params.author.email }} → jsmith@example.org
+{{ .Params.author.name }} → John Smith
+```
+
+In the template example above, each of the keys is a valid identifier. For example, none of the keys contains a hyphen. To access a key that is not a valid identifier, use the [`index`] function:
+
+```go-html-template
+{{ index .Params "key-with-hyphens" }} → must use index function
+```
+
+[`index`]: /functions/collections/indexfunction/
+[front matter fields]: /content-management/front-matter/#fields
diff --git a/docs/content/en/methods/page/Parent.md b/docs/content/en/methods/page/Parent.md
new file mode 100644
index 00000000000..0946a79937d
--- /dev/null
+++ b/docs/content/en/methods/page/Parent.md
@@ -0,0 +1,52 @@
+---
+title: Parent
+description: Returns the Page object of the parent section of the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Page
+ signatures: [PAGE.Parent]
+---
+
+{{% glossary-term section %}}
+
+> [!note]
+> The parent section of a regular page is the [current section].
+
+Consider this content structure:
+
+```text
+content/
+├── auctions/
+│ ├── 2023-11/
+│ │ ├── _index.md <-- parent: auctions
+│ │ ├── auction-1.md
+│ │ └── auction-2.md <-- parent: 2023-11
+│ ├── 2023-12/
+│ │ ├── _index.md
+│ │ ├── auction-3.md
+│ │ └── auction-4.md
+│ ├── _index.md <-- parent: home
+│ ├── bidding.md
+│ └── payment.md <-- parent: auctions
+├── books/
+│ ├── _index.md <-- parent: home
+│ ├── book-1.md
+│ └── book-2.md <-- parent: books
+├── films/
+│ ├── _index.md <-- parent: home
+│ ├── film-1.md
+│ └── film-2.md <-- parent: films
+└── _index.md <-- parent: nil
+```
+
+In the example above, note the parent section of the home page is nil. Code defensively by verifying existence of the parent section before calling methods on its `Page` object. To create a link to the parent section page of the current page:
+
+```go-html-template
+{{ with .Parent }}
+ {{ .LinkTitle }}
+{{ end }}
+```
+
+[current section]: /methods/page/currentsection/
diff --git a/docs/content/en/methods/page/Path.md b/docs/content/en/methods/page/Path.md
new file mode 100644
index 00000000000..b2ef7a031d5
--- /dev/null
+++ b/docs/content/en/methods/page/Path.md
@@ -0,0 +1,138 @@
+---
+title: Path
+description: Returns the logical path of the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [PAGE.Path]
+---
+
+The `Path` method on a `Page` object returns the logical path of the given page, regardless of whether the page is backed by a file.
+
+{{% glossary-term "logical path" %}}
+
+```go-html-template
+{{ .Path }} → /posts/post-1
+```
+
+> [!note]
+> Beginning with the release of [v0.92.0] in January 2022, Hugo emitted a warning whenever calling the `Path` method. The warning indicated that this method would change in a future release.
+>
+> The meaning of, and value returned by, the `Path` method on a `Page` object changed with the release of [v0.123.0] in February 2024.
+
+The value returned by the `Path` method on a `Page` object is independent of content format, language, and URL modifiers such as the `slug` and `url` front matter fields.
+
+## Examples
+
+### Monolingual project
+
+Note that the logical path is independent of content format and URL modifiers.
+
+File path|Front matter slug|Logical path
+:--|:--|:--
+`content/_index.md`||`/`
+`content/posts/_index.md`||`/posts`
+`content/posts/post-1.md`|`foo`|`/posts/post-1`
+`content/posts/post-2.html`|`bar`|`/posts/post-2`
+
+### Multilingual site
+
+Note that the logical path is independent of content format, language identifiers, and URL modifiers.
+
+File path|Front matter slug|Logical path
+:--|:--|:--
+`content/_index.en.md`||`/`
+`content/_index.de.md`||`/`
+`content/posts/_index.en.md`||`/posts`
+`content/posts/_index.de.md`||`/posts`
+`content/posts/posts-1.en.md`|`foo`|`/posts/post-1`
+`content/posts/posts-1.de.md`|`foo`|`/posts/post-1`
+`content/posts/posts-2.en.html`|`bar`|`/posts/post-2`
+`content/posts/posts-2.de.html`|`bar`|`/posts/post-2`
+
+### Pages not backed by a file
+
+The `Path` method on a `Page` object returns a value regardless of whether the page is backed by a file.
+
+```text
+content/
+└── posts/
+ └── post-1.md <-- front matter: tags = ['hugo']
+```
+
+When you build the site:
+
+```text
+public/
+├── posts/
+│ ├── post-1/
+│ │ └── index.html .Page.Path = /posts/post-1
+│ └── index.html .Page.Path = /posts
+├── tags/
+│ ├── hugo/
+│ │ └── index.html .Page.Path = /tags/hugo
+│ └── index.html .Page.Path = /tags
+└── index.html .Page.Path = /
+```
+
+## Finding pages
+
+These methods, functions, and shortcodes use the logical path to find the given page:
+
+Methods|Functions|Shortcodes
+:--|:--|:--
+[`Site.GetPage`]|[`urls.Ref`]|[`ref`]
+[`Page.GetPage`]|[`urls.RelRef`]|[`relref`]
+[`Page.Ref`]| |
+[`Page.RelRef`]| |
+[`Shortcode.Ref`]| |
+[`Shortcode.RelRef`]| |
+
+> [!note]
+> Specify the logical path when using any of these methods, functions, or shortcodes. If you include a file extension or language identifier, Hugo will strip these values before finding the page in the logical tree.
+
+## Logical tree
+
+Just as file paths form a file tree, logical paths form a logical tree.
+
+A file tree:
+
+```text
+content/
+└── s1/
+ ├── p1/
+ │ └── index.md
+ └── p2.md
+```
+
+The same content represented as a logical tree:
+
+```text
+content/
+└── s1/
+ ├── p1
+ └── p2
+```
+
+A key difference between these trees is the relative path from p1 to p2:
+
+- In the file tree, the relative path from p1 to p2 is `../p2.md`
+- In the logical tree, the relative path is `p2`
+
+> [!note]
+> Remember to use the logical path when using any of the methods, functions, or shortcodes listed in the previous section. If you include a file extension or language identifier, Hugo will strip these values before finding the page in the logical tree.
+
+[`Page.GetPage`]: /methods/page/getpage/
+[`Page.Ref`]: /methods/page/ref/
+[`Page.RelRef`]: /methods/page/relref/
+[`ref`]: /shortcodes/ref/
+[`relref`]: /shortcodes/relref/
+[`Shortcode.Ref`]: /methods/shortcode/ref
+[`Shortcode.RelRef`]: /methods/shortcode/relref
+[`Site.GetPage`]: /methods/site/getpage/
+[`urls.Ref`]: /functions/urls/ref/
+[`urls.RelRef`]: /functions/urls/relref/
+[v0.123.0]: https://github.com/gohugoio/hugo/releases/tag/v0.123.0
+[v0.92.0]: https://github.com/gohugoio/hugo/releases/tag/v0.92.0
diff --git a/docs/content/en/methods/page/Permalink.md b/docs/content/en/methods/page/Permalink.md
new file mode 100644
index 00000000000..ffd6ede0b69
--- /dev/null
+++ b/docs/content/en/methods/page/Permalink.md
@@ -0,0 +1,24 @@
+---
+title: Permalink
+description: Returns the permalink of the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [PAGE.Permalink]
+---
+
+Project configuration:
+
+{{< code-toggle file=hugo >}}
+title = 'Documentation'
+baseURL = 'https://example.org/docs/'
+{{< /code-toggle >}}
+
+Template:
+
+```go-html-template
+{{ $page := .Site.GetPage "/about" }}
+{{ $page.Permalink }} → https://example.org/docs/about/
+```
diff --git a/docs/content/en/methods/page/Plain.md b/docs/content/en/methods/page/Plain.md
new file mode 100644
index 00000000000..23bc214130a
--- /dev/null
+++ b/docs/content/en/methods/page/Plain.md
@@ -0,0 +1,23 @@
+---
+title: Plain
+description: Returns the rendered content of the given page, removing all HTML tags.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [PAGE.Plain]
+---
+
+The `Plain` method on a `Page` object renders Markdown and [shortcodes](g) to HTML, then strips the HTML [tags][]. It does not strip HTML [entities][].
+
+To prevent Go's [`html/template`][] package from escaping HTML entities, pass the result through the [`htmlUnescape`][] function.
+
+```go-html-template
+{{ .Plain | htmlUnescape }}
+```
+
+[`html/template`]: https://pkg.go.dev/html/template
+[entities]: https://developer.mozilla.org/en-US/docs/Glossary/Entity
+[tags]: https://developer.mozilla.org/en-US/docs/Glossary/Tag
+[`htmlUnescape`]: /functions/transform/htmlunescape/
diff --git a/docs/content/en/methods/page/PlainWords.md b/docs/content/en/methods/page/PlainWords.md
new file mode 100644
index 00000000000..5749a21f9d4
--- /dev/null
+++ b/docs/content/en/methods/page/PlainWords.md
@@ -0,0 +1,31 @@
+---
+title: PlainWords
+description: Calls the Plain method, splits the result into a slice of words, and returns the slice.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: '[]string'
+ signatures: [PAGE.PlainWords]
+---
+
+The `PlainWords` method on a `Page` object calls the [`Plain`] method, then uses Go's [`strings.Fields`] function to split the result into words.
+
+> [!note]
+> `Fields` splits the string `s` around each instance of one or more consecutive whitespace characters, as defined by [`unicode.IsSpace`], returning a slice of substrings of `s` or an empty slice if `s` contains only whitespace.
+
+As a result, elements within the slice may contain leading or trailing punctuation.
+
+```go-html-template
+{{ .PlainWords }}
+```
+
+To determine the approximate number of unique words on a page:
+
+```go-html-template
+{{ .PlainWords | uniq }} → 42
+```
+
+[`Plain`]: /methods/page/plain/
+[`strings.Fields`]: https://pkg.go.dev/strings#Fields
+[`unicode.IsSpace`]: https://pkg.go.dev/unicode#IsSpace
diff --git a/docs/content/en/methods/page/Prev.md b/docs/content/en/methods/page/Prev.md
new file mode 100644
index 00000000000..5a6e2162d8e
--- /dev/null
+++ b/docs/content/en/methods/page/Prev.md
@@ -0,0 +1,12 @@
+---
+title: Prev
+description: Returns the previous page in a site's collection of regular pages, relative to the current page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Page
+ signatures: [PAGE.Prev]
+---
+
+{{% include "/_common/methods/page/next-and-prev.md" %}}
diff --git a/docs/content/en/methods/page/PrevInSection.md b/docs/content/en/methods/page/PrevInSection.md
new file mode 100644
index 00000000000..14d3ca082c6
--- /dev/null
+++ b/docs/content/en/methods/page/PrevInSection.md
@@ -0,0 +1,12 @@
+---
+title: PrevInSection
+description: Returns the previous regular page in a section, relative to the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Page
+ signatures: [PAGE.PrevInSection]
+---
+
+{{% include "/_common/methods/page/nextinsection-and-previnsection.md" %}}
diff --git a/docs/content/en/methods/page/PublishDate.md b/docs/content/en/methods/page/PublishDate.md
new file mode 100644
index 00000000000..ec3c1337786
--- /dev/null
+++ b/docs/content/en/methods/page/PublishDate.md
@@ -0,0 +1,32 @@
+---
+title: PublishDate
+description: Returns the publish date of the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: time.Time
+ signatures: [PAGE.PublishDate]
+---
+
+By default, Hugo excludes pages with future publish dates when building your project. To include future pages, use the `--buildFuture` command line flag.
+
+Set the publish date in front matter:
+
+{{< code-toggle file=content/news/article-1.md fm=true >}}
+title = 'Article 1'
+publishDate = 2023-10-19T00:40:04-07:00
+{{< /code-toggle >}}
+
+The publish date is a [time.Time] value. Format and localize the value with the [`time.Format`] function, or use it with any of the [time methods].
+
+```go-html-template
+{{ .PublishDate | time.Format ":date_medium" }} → Oct 19, 2023
+```
+
+In the example above we explicitly set the publish date in front matter. With Hugo's default configuration, the `PublishDate` method returns the front matter value. This behavior is configurable, allowing you to set fallback values if the publish date is not defined in front matter. See [details].
+
+[`time.Format`]: /functions/time/format/
+[details]: /configuration/front-matter/#dates
+[time methods]: /methods/time/
+[time.Time]: https://pkg.go.dev/time#Time
diff --git a/docs/content/en/methods/page/RawContent.md b/docs/content/en/methods/page/RawContent.md
new file mode 100644
index 00000000000..41215ef53c6
--- /dev/null
+++ b/docs/content/en/methods/page/RawContent.md
@@ -0,0 +1,23 @@
+---
+title: RawContent
+description: Returns the raw content of the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [PAGE.RawContent]
+---
+
+The `RawContent` method on a `Page` object returns the raw content. The raw content does not include front matter.
+
+```go-html-template
+{{ .RawContent }}
+```
+
+This is useful when rendering a page in a plain text [output format](g).
+
+> [!note]
+> [Shortcodes](g) within the content are not rendered. To get the raw content with shortcodes rendered, use the [`RenderShortcodes`] method on a `Page` object.
+
+[`RenderShortcodes`]: /methods/page/rendershortcodes/
diff --git a/docs/content/en/methods/page/ReadingTime.md b/docs/content/en/methods/page/ReadingTime.md
new file mode 100644
index 00000000000..2fd2ea4d764
--- /dev/null
+++ b/docs/content/en/methods/page/ReadingTime.md
@@ -0,0 +1,47 @@
+---
+title: ReadingTime
+description: Returns the estimated reading time, in minutes, for the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: int
+ signatures: [PAGE.ReadingTime]
+---
+
+The estimated reading time is calculated by dividing the number of words in the content by the reading speed.
+
+By default, Hugo assumes a reading speed of 212 words per minute. For CJK languages, it assumes 500 words per minute.
+
+```go-html-template
+{{ printf "Estimated reading time: %d minutes" .ReadingTime }}
+```
+
+Reading speed varies by language. Create language-specific estimated reading times on your multilingual project using site parameters.
+
+{{< code-toggle file=hugo >}}
+[languages]
+ [languages.de]
+ contentDir = 'content/de'
+ label = 'Deutsch'
+ locale = 'de-DE'
+ weight = 2
+ [languages.de.params]
+ reading_speed = 179
+ [languages.en]
+ contentDir = 'content/en'
+ label = 'English'
+ locale = 'en-US'
+ weight = 1
+ [languages.en.params]
+ reading_speed = 228
+{{< /code-toggle >}}
+
+Then in your template:
+
+```go-html-template
+{{ $readingTime := div (float .WordCount) .Site.Params.reading_speed }}
+{{ $readingTime = math.Ceil $readingTime }}
+```
+
+We cast the `.WordCount` to a float to obtain a float when we divide by the reading speed. Then round up to the nearest integer.
diff --git a/docs/content/en/methods/page/Ref.md b/docs/content/en/methods/page/Ref.md
new file mode 100644
index 00000000000..35f9460bae8
--- /dev/null
+++ b/docs/content/en/methods/page/Ref.md
@@ -0,0 +1,37 @@
+---
+title: Ref
+description: Returns the absolute URL of the page with the given path, language, and output format.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [PAGE.Ref OPTIONS]
+---
+
+## Usage
+
+The `Ref` method accepts a single argument: an options map.
+
+## Options
+
+{{% include "_common/ref-and-relref-options.md" %}}
+
+## Examples
+
+The following examples show the rendered output for a page on the English version of the site:
+
+```go-html-template
+{{ $opts := dict "path" "/books/book-1" }}
+{{ .Ref $opts }} → https://example.org/en/books/book-1/
+
+{{ $opts := dict "path" "/books/book-1" "lang" "de" }}
+{{ .Ref $opts }} → https://example.org/de/books/book-1/
+
+{{ $opts := dict "path" "/books/book-1" "lang" "de" "outputFormat" "json" }}
+{{ .Ref $opts }} → https://example.org/de/books/book-1/index.json
+```
+
+## Error handling
+
+{{% include "_common/ref-and-relref-error-handling.md" %}}
diff --git a/docs/content/en/methods/page/RegularPages.md b/docs/content/en/methods/page/RegularPages.md
new file mode 100644
index 00000000000..761de3af527
--- /dev/null
+++ b/docs/content/en/methods/page/RegularPages.md
@@ -0,0 +1,79 @@
+---
+title: RegularPages
+description: Returns a collection of regular pages within the current section.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Pages
+ signatures: [PAGE.RegularPages]
+---
+
+The `RegularPages` method on a `Page` object is available to these [page kinds](g): `home`, `section`, `taxonomy`, and `term`. The templates for these page kinds receive a page [collection](g) in [context](g), in the [default sort order](g).
+
+Range through the page collection in your template:
+
+```go-html-template
+{{ range .RegularPages.ByTitle }}
+
+{{ end }}
+```
+
+[details]: /methods/site/regularpages/
diff --git a/docs/content/en/methods/page/RegularPagesRecursive.md b/docs/content/en/methods/page/RegularPagesRecursive.md
new file mode 100644
index 00000000000..d85cd0b48ee
--- /dev/null
+++ b/docs/content/en/methods/page/RegularPagesRecursive.md
@@ -0,0 +1,83 @@
+---
+title: RegularPagesRecursive
+description: Returns a collection of regular pages within the current section, and regular pages within all descendant sections.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Pages
+ signatures: [PAGE.RegularPagesRecursive]
+---
+
+The `RegularPagesRecursive` method on a `Page` object is available to these [page kinds](g): `home`, `section`, `taxonomy`, and `term`. The templates for these page kinds receive a page [collection](g) in [context](g), in the [default sort order](g).
+
+Range through the page collection in your template:
+
+```go-html-template
+{{ range .RegularPagesRecursive.ByTitle }}
+
+ {{ .Render "summary" }}
+{{ end }}
+```
+
+In the example above, note that the template ("summary") is identified by its file name without directory or extension.
+
+Although similar to the [`partial`] function, there are key differences.
+
+`Render` method|`partial` function
+:--|:--
+The `Page` object is automatically passed to the given template. You cannot pass additional context.|You must specify the context, allowing you to pass a combination of objects, slices, maps, and scalars.
+The path to the template is determined by the [content type](g).|You must specify the path to the template, relative to the `layouts/_partials` directory.
+
+Consider this layout structure:
+
+```text
+layouts/
+├── books/
+│ └── li.html <-- used when content type is "books"
+├── baseof.html
+├── home.html
+├── li.html <-- used for other content types
+├── page.html
+├── section.html
+├── taxonomy.html
+└── term.html
+```
+
+And this template:
+
+```go-html-template
+
+ {{ range site.RegularPages.ByDate }}
+ {{ .Render "li" }}
+ {{ end }}
+
+```
+
+When rendering content of type "books" the `Render` method calls:
+
+```text
+layouts/books/li.html
+```
+
+For all other content types the `Render` methods calls:
+
+```text
+layouts/li.html
+```
+
+See [content views] for more examples.
+
+[content views]: /templates/types/#content-view
+[`partial`]: /functions/partials/include/
diff --git a/docs/content/en/methods/page/RenderShortcodes.md b/docs/content/en/methods/page/RenderShortcodes.md
new file mode 100644
index 00000000000..e440302dc10
--- /dev/null
+++ b/docs/content/en/methods/page/RenderShortcodes.md
@@ -0,0 +1,87 @@
+---
+title: RenderShortcodes
+description: Renders all shortcodes in the content of the given page, preserving the surrounding markup.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: template.HTML
+ signatures: [PAGE.RenderShortcodes]
+---
+
+Use this method in _shortcode_ templates to compose a page from multiple content files, while preserving a global context for footnotes and the table of contents.
+
+For example:
+
+```go-html-template {file="layouts/_shortcodes/include.html" copy=true}
+{{ with .Get 0 }}
+ {{ with $.Page.GetPage . }}
+ {{- .RenderShortcodes }}
+ {{ else }}
+ {{ errorf "The %q shortcode was unable to find %q. See %s" $.Name . $.Position }}
+ {{ end }}
+{{ else }}
+ {{ errorf "The %q shortcode requires a positional parameter indicating the logical path of the file to include. See %s" .Name .Position }}
+{{ end }}
+```
+
+Then call the shortcode in your Markdown:
+
+```text {file="content/about.md"}
+{{%/* include "/snippets/services" */%}}
+{{%/* include "/snippets/values" */%}}
+{{%/* include "/snippets/leadership" */%}}
+```
+
+Each of the included Markdown files can contain calls to other shortcodes.
+
+## Shortcode notation
+
+In the example above it's important to understand the difference between the two delimiters used when calling a shortcode:
+
+- `{{* myshortcode */>}}` tells Hugo that the rendered shortcode does not need further processing. For example, the shortcode content is HTML.
+- `{{%/* myshortcode */%}}` tells Hugo that the rendered shortcode needs further processing. For example, the shortcode content is Markdown.
+
+Use the latter for the "include" shortcode described above.
+
+## Further explanation
+
+To understand what is returned by the `RenderShortcodes` method, consider this content file
+
+```text {file="content/about.md"}
++++
+title = 'About'
+date = 2023-10-07T12:28:33-07:00
++++
+
+{{* ref "privacy" */>}}
+
+An *emphasized* word.
+```
+
+With this template code:
+
+```go-html-template
+{{ $p := site.GetPage "/about" }}
+{{ $p.RenderShortcodes }}
+```
+
+Hugo renders this:;
+
+```html
+https://example.org/privacy/
+
+An *emphasized* word.
+```
+
+Note that the shortcode within the content file was rendered, but the surrounding Markdown was preserved.
+
+## Limitations
+
+The primary use case for `.RenderShortcodes` is inclusion of Markdown content. If you try to use `.RenderShortcodes` inside `HTML` blocks when inside Markdown, you will get a warning similar to this:
+
+```text
+WARN .RenderShortcodes detected inside HTML block in "/content/mypost.md"; this may not be what you intended ...
+```
+
+The above warning can be turned off is this is what you really want.
diff --git a/docs/content/en/methods/page/RenderString.md b/docs/content/en/methods/page/RenderString.md
new file mode 100644
index 00000000000..5f97e3576b8
--- /dev/null
+++ b/docs/content/en/methods/page/RenderString.md
@@ -0,0 +1,49 @@
+---
+title: RenderString
+description: Renders markup to HTML.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: template.HTML
+ signatures: ['PAGE.RenderString [OPTIONS] MARKUP']
+aliases: [/functions/renderstring]
+---
+
+```go-html-template
+{{ $s := "An *emphasized* word" }}
+{{ $s | .RenderString }} → An emphasized word
+```
+
+This method takes an optional map of options:
+
+display
+: (`string`) Specify either `inline` or `block`. If `inline`, removes surrounding `p` tags from short snippets. Default is `inline`.
+
+markup
+: (`string`) Specify a [markup identifier] for the provided markup. Default is the `markup` front matter value, falling back to the value derived from the page's file extension.
+
+Render with the default markup renderer:
+
+```go-html-template
+{{ $s := "An *emphasized* word" }}
+{{ $s | .RenderString }} → An emphasized word
+
+{{ $opts := dict "display" "block" }}
+{{ $s | .RenderString $opts }} →
+```
+
+[markup identifier]: /content-management/formats/#classification
+[pandoc]: https://pandoc.org/
diff --git a/docs/content/en/methods/page/Resources.md b/docs/content/en/methods/page/Resources.md
new file mode 100644
index 00000000000..a99ca2f21b6
--- /dev/null
+++ b/docs/content/en/methods/page/Resources.md
@@ -0,0 +1,90 @@
+---
+title: Resources
+description: Returns a collection of page resources.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: resource.Resources
+ signatures: [PAGE.Resources]
+---
+
+The `Resources` method on a `Page` object returns a collection of page resources. A page resource is a file within a [page bundle](g).
+
+To work with global or remote resources, see the [`resources`] functions.
+
+## Methods
+
+### ByType
+
+(`resource.Resources`) Returns a collection of page resources of the given [media type], or nil if none found. The media type is typically one of `image`, `text`, `audio`, `video`, or `application`.
+
+```go-html-template
+{{ range .Resources.ByType "image" }}
+
+{{ end }}
+```
+
+When working with global resources instead of page resources, use the [`resources.ByType`] function.
+
+### Get
+
+(`resource.Resource`) Returns a page resource from the given path, or nil if none found.
+
+```go-html-template
+{{ with .Resources.Get "images/a.jpg" }}
+
+{{ end }}
+```
+
+When working with global resources instead of page resources, use the [`resources.Get`] function.
+
+### GetMatch
+
+(`resource.Resource`) Returns the first page resource from paths matching the given [glob pattern](g), or nil if none found.
+
+```go-html-template
+{{ with .Resources.GetMatch "images/*.jpg" }}
+
+{{ end }}
+```
+
+When working with global resources instead of page resources, use the [`resources.GetMatch`] function.
+
+### Match
+
+(`resource.Resources`) Returns a collection of page resources from paths matching the given [glob pattern](g), or nil if none found.
+
+```go-html-template
+{{ range .Resources.Match "images/*.jpg" }}
+
+{{ end }}
+```
+
+When working with global resources instead of page resources, use the [`resources.Match`] function.
+
+### Mount
+
+{{< new-in 0.140.0 />}}
+
+(`ResourceGetter`) Mounts the given resources from the two arguments base (`string`) to the given target path (`string`) and returns an object that implements [Get](#get). Note that leading slashes in target marks an absolute path. Relative target paths allows you to mount resources relative to another set, e.g. a [Page bundle](/content-management/page-bundles/):
+
+```go-html-template
+{{ $common := resources.Match "/js/headlessui/*.*" }}
+{{ $importContext := (slice $.Page ($common.Mount "/js/headlessui" ".")) }}
+```
+
+This method is currently only useful in [js.Batch](/functions/js/batch/#import-context).
+
+## Pattern matching
+
+With the `GetMatch` and `Match` methods, Hugo determines a match using a case-insensitive [glob pattern](g).
+
+{{% include "/_common/glob-patterns.md" %}}
+
+[`resources.ByType`]: /functions/resources/ByType/
+[`resources.GetMatch`]: /functions/resources/ByType/
+[`resources.Get`]: /functions/resources/ByType/
+[`resources.Match`]: /functions/resources/ByType/
+[`resources`]: /functions/resources/
+[media type]: https://en.wikipedia.org/wiki/Media_type
diff --git a/docs/content/en/methods/page/Rotate.md b/docs/content/en/methods/page/Rotate.md
new file mode 100644
index 00000000000..643945d85bb
--- /dev/null
+++ b/docs/content/en/methods/page/Rotate.md
@@ -0,0 +1,55 @@
+---
+title: Rotate
+description: Returns a collection of pages that vary along the specified dimension while sharing the current page's values for the other dimensions, including the current page, sorted by the dimension's default sort order.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Pages
+ signatures: [PAGE.Rotate DIMENSION]
+---
+
+{{< new-in 0.153.0 />}}
+
+The rotate method on a page object returns a collection of pages that vary along the specified [dimension](g), while holding the other dimensions constant. The result includes the current page and is sorted according to the rules of the specified dimension. For example, rotating along [language](g) returns all language variants that share the current page's [version](g) and [role](g).
+
+The `DIMENSION` argument must be one of `language`, `version`, or `role`.
+
+## Sort order
+
+Use the following rules to understand how Hugo sorts the collection returned by the `Rotate` method.
+
+| Dimension | Primary Sort | Secondary Sort |
+| :--- | :--- | :--- |
+| Language | Weight ascending | Lexicographical ascending |
+| Version | Weight ascending | Semantic version descending |
+| Role | Weight ascending | Lexicographical ascending |
+
+## Examples
+
+To render a list of the current page's language variants, including the current page, while sharing its current version and role:
+
+```go-html-template
+{{/* Returns languages sorted by weight ascending, then lexicographically ascending */}}
+{{ range .Rotate "language" }}
+
+{{ end }}
+```
+
+To render a list of the current page's version variants, including the current page, while sharing its current language and role:
+
+```go-html-template
+{{/* Returns versions sorted by weight ascending, then semantic version descending */}}
+{{ range .Rotate "version" }}
+
+{{ end }}
+```
+
+To render a list of the current page's role variants, including the current page, while sharing its current language and version:
+
+```go-html-template
+{{/* Returns roles sorted by weight ascending, then lexicographically ascending */}}
+{{ range .Rotate "role" }}
+
+{{ end }}
+```
diff --git a/docs/content/en/methods/page/Scratch.md b/docs/content/en/methods/page/Scratch.md
new file mode 100644
index 00000000000..61c5dc19eb0
--- /dev/null
+++ b/docs/content/en/methods/page/Scratch.md
@@ -0,0 +1,21 @@
+---
+title: Scratch
+description: Returns a "scratch pad" to store and manipulate data, scoped to the current page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: maps.Scratch
+ signatures: [PAGE.Scratch]
+expiryDate: 2026-11-18 # deprecated 2024-11-18 (soft)
+---
+
+{{< deprecated-in 0.138.0 >}}
+Use the [`PAGE.Store`] method instead.
+
+This is a soft deprecation. This method will be removed in a future release, but the removal date has not been established. Although Hugo will not emit a warning if you continue to use this method, you should begin using `PAGE.Store` as soon as possible.
+
+Beginning with v0.138.0 the `PAGE.Scratch` method is aliased to `PAGE.Store`.
+
+[`PAGE.Store`]: /methods/page/store/
+{{< /deprecated-in >}}
diff --git a/docs/content/en/methods/page/Section.md b/docs/content/en/methods/page/Section.md
new file mode 100644
index 00000000000..04c6a8a241b
--- /dev/null
+++ b/docs/content/en/methods/page/Section.md
@@ -0,0 +1,54 @@
+---
+title: Section
+description: Returns the name of the top-level section in which the given page resides.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [PAGE.Section]
+---
+
+{{% glossary-term section %}}
+
+With this content structure:
+
+```text
+content/
+├── lessons/
+│ ├── math/
+│ │ ├── _index.md
+│ │ ├── lesson-1.md
+│ │ └── lesson-2.md
+│ └── _index.md
+└── _index.md
+```
+
+When rendering lesson-1.md:
+
+```go-html-template
+{{ .Section }} → lessons
+```
+
+In the example above "lessons" is the top-level section.
+
+The `Section` method is often used with the [`where`] function to build a page collection.
+
+```go-html-template
+{{ range where .Site.RegularPages "Section" "lessons" }}
+
+{{ end }}
+```
+
+This is similar to using the [`Type`] method with the `where` function
+
+```go-html-template
+{{ range where .Site.RegularPages "Type" "lessons" }}
+
+{{ end }}
+```
+
+However, if the `type` field in front matter has been defined on one or more pages, the page collection based on `Type` will be different than the page collection based on `Section`.
+
+[`where`]: /functions/collections/where/
+[`Type`]: /methods/page/type/
diff --git a/docs/content/en/methods/page/Sections.md b/docs/content/en/methods/page/Sections.md
new file mode 100644
index 00000000000..12f0a8c241b
--- /dev/null
+++ b/docs/content/en/methods/page/Sections.md
@@ -0,0 +1,62 @@
+---
+title: Sections
+description: Returns a collection of section pages, one for each immediate descendant section of the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Pages
+ signatures: [PAGE.Sections]
+---
+
+The `Sections` method on a `Page` object is available to these [page kinds](g): `home`, `section`, and `taxonomy`. The templates for these page kinds receive a page [collection](g) in [context](g), in the [default sort order](g).
+
+With this content structure:
+
+```text
+content/
+├── auctions/
+│ ├── 2023-11/
+│ │ ├── _index.md <-- front matter: weight = 202311
+│ │ ├── auction-1.md
+│ │ └── auction-2.md
+│ ├── 2023-12/
+│ │ ├── _index.md <-- front matter: weight = 202312
+│ │ ├── auction-3.md
+│ │ └── auction-4.md
+│ ├── _index.md <-- front matter: weight = 30
+│ ├── bidding.md
+│ └── payment.md
+├── books/
+│ ├── _index.md <-- front matter: weight = 20
+│ ├── book-1.md
+│ └── book-2.md
+├── films/
+│ ├── _index.md <-- front matter: weight = 10
+│ ├── film-1.md
+│ └── film-2.md
+└── _index.md
+```
+
+And this template:
+
+```go-html-template
+{{ range .Sections.ByWeight }}
+
+```
diff --git a/docs/content/en/methods/page/Site.md b/docs/content/en/methods/page/Site.md
new file mode 100644
index 00000000000..4649e5e0002
--- /dev/null
+++ b/docs/content/en/methods/page/Site.md
@@ -0,0 +1,18 @@
+---
+title: Site
+description: Returns the Site object.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.siteWrapper
+ signatures: [PAGE.Site]
+---
+
+See [Site methods].
+
+[Site methods]: /methods/site/
+
+```go-html-template
+{{ .Site.Title }}
+```
diff --git a/docs/content/en/methods/page/Sitemap.md b/docs/content/en/methods/page/Sitemap.md
new file mode 100644
index 00000000000..53f8cae8211
--- /dev/null
+++ b/docs/content/en/methods/page/Sitemap.md
@@ -0,0 +1,79 @@
+---
+title: Sitemap
+description: Returns the sitemap settings for the given page as defined in front matter, falling back to the sitemap settings as defined in your project configuration.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: config.SitemapConfig
+ signatures: [PAGE.Sitemap]
+---
+
+Access to the `Sitemap` method on a `Page` object is restricted to [sitemap templates].
+
+## Methods
+
+### ChangeFreq
+
+(`string`) How frequently a page is likely to change. Valid values are `always`, `hourly`, `daily`, `weekly`, `monthly`, `yearly`, and `never`. With the default value of `""` Hugo will omit this field from the sitemap. See [details](https://www.sitemaps.org/protocol.html#changefreqdef).
+
+```go-html-template
+{{ .Sitemap.ChangeFreq }}
+```
+
+### Disable
+
+(`bool`) Whether to disable page inclusion. Default is `false`. Set to `true` in front matter to exclude the page.
+
+```go-html-template
+{{ .Sitemap.Disable }}
+```
+
+### Priority
+
+(`float`) The priority of a page relative to any other page on the site. Valid values range from 0.0 to 1.0. With the default value of `-1` Hugo will omit this field from the sitemap. See [details](https://www.sitemaps.org/protocol.html#prioritydef).
+
+```go-html-template
+{{ .Sitemap.Priority }}
+```
+
+## Example
+
+With this project configuration:
+
+{{< code-toggle file=hugo >}}
+[sitemap]
+changeFreq = 'monthly'
+{{< /code-toggle >}}
+
+And this content:
+
+{{< code-toggle file=content/news.md fm=true >}}
+title = 'News'
+[sitemap]
+changeFreq = 'hourly'
+{{< /code-toggle >}}
+
+And this simplistic sitemap template:
+
+```xml {file="layouts/sitemap.xml"}
+{{ printf "" | safeHTML }}
+
+ {{ range .Pages }}
+
+ {{ .Permalink }}
+ {{ if not .Lastmod.IsZero }}
+ {{ .Lastmod.Format "2006-01-02T15:04:05-07:00" | safeHTML }}
+ {{ end }}
+ {{ with .Sitemap.ChangeFreq }}
+ {{ . }}
+ {{ end }}
+
+ {{ end }}
+
+```
+
+The change frequency will be `hourly` for the news page, and `monthly` for other pages.
+
+[sitemap templates]: /templates/sitemap/
diff --git a/docs/content/en/methods/page/Sites.md b/docs/content/en/methods/page/Sites.md
new file mode 100644
index 00000000000..293590bfa44
--- /dev/null
+++ b/docs/content/en/methods/page/Sites.md
@@ -0,0 +1,15 @@
+---
+title: Sites
+description: Returns a collection of all sites for all dimensions.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Sites
+ signatures: [PAGE.Sites]
+expiryDate: '2028-02-18' # deprecated 2026-02-18 in v0.156.0
+---
+
+{{< deprecated-in 0.156.0 >}}
+Use [`hugo.Sites`](/functions/hugo/sites/) instead.
+{{< /deprecated-in >}}
diff --git a/docs/content/en/methods/page/Slug.md b/docs/content/en/methods/page/Slug.md
new file mode 100644
index 00000000000..34000b6601a
--- /dev/null
+++ b/docs/content/en/methods/page/Slug.md
@@ -0,0 +1,25 @@
+---
+title: Slug
+description: Returns the URL slug of the given page as defined in front matter.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [PAGE.Slug]
+---
+
+{{< code-toggle file=content/recipes/spicy-tuna-hand-rolls.md fm=true >}}
+title = 'How to make spicy tuna hand rolls'
+slug = 'sushi'
+{{< /code-toggle >}}
+
+This page will be served from:
+
+ https://example.org/recipes/sushi
+
+To get the slug value within a template:
+
+```go-html-template
+{{ .Slug }} → sushi
+```
diff --git a/docs/content/en/methods/page/Store.md b/docs/content/en/methods/page/Store.md
new file mode 100644
index 00000000000..c5ad6b3161c
--- /dev/null
+++ b/docs/content/en/methods/page/Store.md
@@ -0,0 +1,35 @@
+---
+title: Store
+description: Returns a "scratch pad" to store and manipulate data, scoped to the current page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: maps.Scratch
+ signatures: [PAGE.Store]
+aliases: [/functions/store/,/extras/scratch/,/doc/scratch/,/functions/scratch]
+---
+
+Use the `Store` method on a `Page` object to create a [scratch pad](g) to store and manipulate data, scoped to the current page. To create a scratch pad with a different [scope](g), refer to the [scope](#scope) section below.
+
+{{% include "_common/store-methods.md" %}}
+
+{{% include "_common/scratch-pad-scope.md" %}}
+
+## Determinate values
+
+The `Store` method is often used to set scratch pad values within a _shortcode_ template, a _partial_ template called by a _shortcode_ template, or by a _render hook_ template. In all three cases, the scratch pad values are indeterminate until Hugo renders the page content.
+
+If you need to access a scratch pad value from a parent template, and the parent template has not yet rendered the page content, you can trigger content rendering by assigning the returned value to a [noop](g) variable:
+
+```go-html-template
+{{ $noop := .Content }}
+{{ .Store.Get "mykey" }}
+```
+
+You can also trigger content rendering with the `ContentWithoutSummary`, `FuzzyWordCount`, `Len`, `Plain`, `PlainWords`, `ReadingTime`, `Summary`, `Truncated`, and `WordCount` methods. For example:
+
+```go-html-template
+{{ $noop := .WordCount }}
+{{ .Store.Get "mykey" }}
+```
diff --git a/docs/content/en/methods/page/Summary.md b/docs/content/en/methods/page/Summary.md
new file mode 100644
index 00000000000..c72a2440d42
--- /dev/null
+++ b/docs/content/en/methods/page/Summary.md
@@ -0,0 +1,48 @@
+---
+title: Summary
+description: Returns the summary of the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: template.HTML
+ signatures: [PAGE.Summary]
+---
+
+
+
+
+
+
+You can define a [summary] manually, in front matter, or automatically. A manual summary takes precedence over a front matter summary, and a front matter summary takes precedence over an automatic summary.
+
+To list the pages in a section with a summary beneath each link:
+
+```go-html-template
+{{ range .Pages }}
+
+ {{ .Summary }}
+{{ end }}
+```
+
+> [!warning]
+> Automatic `.Summary` may cut block tags (e.g., `blockquote`) in the middle, causing the browser to recover the end tag. See [automatic summary] for details and for ways to avoid this.
+
+Depending on content length and how you define the summary, the summary may be equivalent to the content itself. To determine whether the content length exceeds the summary length, use the [`Truncated`] method on a `Page` object. This is useful for conditionally rendering a “read more” link:
+
+```go-html-template
+{{ range .Pages }}
+
+```
diff --git a/docs/content/en/methods/page/Truncated.md b/docs/content/en/methods/page/Truncated.md
new file mode 100644
index 00000000000..8c257306973
--- /dev/null
+++ b/docs/content/en/methods/page/Truncated.md
@@ -0,0 +1,29 @@
+---
+title: Truncated
+description: Reports whether the content length exceeds the summary length.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: bool
+ signatures: [PAGE.Truncated]
+---
+
+You can define a [summary] manually, in front matter, or automatically. A manual summary takes precedence over a front matter summary, and a front matter summary takes precedence over an automatic summary.
+
+[summary]: /content-management/summaries/
+
+The `Truncated` method returns `true` if the content length exceeds the summary length. This is useful for conditionally rendering a "read more" link:
+
+```go-html-template
+{{ range .Pages }}
+
+ {{ .Summary }}
+ {{ if .Truncated }}
+ Read more...
+ {{ end }}
+{{ end }}
+```
+
+> [!note]
+> The `Truncated` method returns `false` if you define the summary in front matter.
diff --git a/docs/content/en/methods/page/Type.md b/docs/content/en/methods/page/Type.md
new file mode 100644
index 00000000000..6f855fbe339
--- /dev/null
+++ b/docs/content/en/methods/page/Type.md
@@ -0,0 +1,51 @@
+---
+title: Type
+description: Returns the content type of the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [PAGE.Type]
+---
+
+The `Type` method on a `Page` object returns the [content type](g) of the given page. The content type is defined by the `type` field in front matter, or inferred from the top-level directory name if the `type` field in front matter is not defined.
+
+With this content structure:
+
+```text
+content/
+├── auction/
+│ ├── _index.md
+│ ├── item-1.md
+│ └── item-2.md <-- front matter: type = books
+├── books/
+│ ├── _index.md
+│ ├── book-1.md
+│ └── book-2.md
+├── films/
+│ ├── _index.md
+│ ├── film-1.md
+│ └── film-2.md
+└── _index.md
+```
+
+To list the books, regardless of [section](g):
+
+```go-html-template
+{{ range where .Site.RegularPages.ByTitle "Type" "books" }}
+
+```
+
+The `type` field in front matter is also useful for targeting a template. See [details].
+
+[details]: /templates/lookup-order/#target-a-template
diff --git a/docs/content/en/methods/page/Weight.md b/docs/content/en/methods/page/Weight.md
new file mode 100644
index 00000000000..c14af025715
--- /dev/null
+++ b/docs/content/en/methods/page/Weight.md
@@ -0,0 +1,25 @@
+---
+title: Weight
+description: Returns the weight of the given page as defined in front matter.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: int
+ signatures: [PAGE.Weight]
+---
+
+The `Weight` method on a `Page` object returns the [weight](g) of the given page as defined in front matter.
+
+{{< code-toggle file=content/recipes/sushi.md fm=true >}}
+title = 'How to make spicy tuna hand rolls'
+weight = 42
+{{< /code-toggle >}}
+
+Page weight controls the position of a page within a collection that is sorted by weight. Assign weights using non-zero integers. Lighter items float to the top, while heavier items sink to the bottom. Unweighted or zero-weighted elements are placed at the end of the collection.
+
+Although rarely used within a template, you can access the value with:
+
+```go-html-template
+{{ .Weight }} → 42
+```
diff --git a/docs/content/en/methods/page/WordCount.md b/docs/content/en/methods/page/WordCount.md
new file mode 100644
index 00000000000..3950244ca84
--- /dev/null
+++ b/docs/content/en/methods/page/WordCount.md
@@ -0,0 +1,18 @@
+---
+title: WordCount
+description: Returns the number of words in the content of the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: int
+ signatures: [PAGE.WordCount]
+---
+
+```go-html-template
+{{ .WordCount }} → 103
+```
+
+To round up to nearest multiple of 100, use the [`FuzzyWordCount`] method.
+
+[`FuzzyWordCount`]: /methods/page/fuzzywordcount/
diff --git a/docs/content/en/methods/page/_index.md b/docs/content/en/methods/page/_index.md
new file mode 100644
index 00000000000..c7ae7ad5d4c
--- /dev/null
+++ b/docs/content/en/methods/page/_index.md
@@ -0,0 +1,8 @@
+---
+title: Page methods
+linkTitle: Page
+description: Use these methods with a Page object.
+categories: []
+keywords: []
+aliases: [/variables/page/]
+---
diff --git a/docs/content/en/methods/pager/First.md b/docs/content/en/methods/pager/First.md
new file mode 100644
index 00000000000..9cd58989bdb
--- /dev/null
+++ b/docs/content/en/methods/pager/First.md
@@ -0,0 +1,38 @@
+---
+title: First
+description: Returns the first pager in the pager collection.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Pager
+ signatures: [PAGER.First]
+---
+
+Use the `First` method to build navigation between pagers.
+
+```go-html-template
+{{ $pages := where site.RegularPages "Type" "posts" }}
+{{ $paginator := .Paginate $pages }}
+
+{{ range $paginator.Pages }}
+
+{{ end }}
+```
+
+You can also write the above without using the `HasNext` method:
+
+```go-html-template
+{{ $pages := where site.RegularPages "Type" "posts" }}
+{{ $paginator := .Paginate $pages }}
+
+{{ range $paginator.Pages }}
+
+{{ end }}
+```
+
+You can also write the above without using the `HasPrev` method:
+
+```go-html-template
+{{ $pages := where site.RegularPages "Type" "posts" }}
+{{ $paginator := .Paginate $pages }}
+
+{{ range $paginator.Pages }}
+
+{{ end }}
+```
diff --git a/docs/content/en/methods/pager/PagerSize.md b/docs/content/en/methods/pager/PagerSize.md
new file mode 100644
index 00000000000..1bec9e4ea63
--- /dev/null
+++ b/docs/content/en/methods/pager/PagerSize.md
@@ -0,0 +1,29 @@
+---
+title: PagerSize
+description: Returns the number of pages per pager.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: int
+ signatures: [PAGER.PagerSize]
+aliases: [/methods/pager/pagesize/]
+---
+
+The number of pages per pager is determined by the optional second argument passed to the [`Paginate`] method, falling back to the `pagerSize` as defined in your [project configuration].
+
+[`Paginate`]: /methods/page/paginate/
+[project configuration]: /templates/pagination/#configuration
+
+```go-html-template
+{{ $pages := where site.RegularPages "Type" "posts" }}
+{{ $paginator := .Paginate $pages }}
+
+{{ range $paginator.Pages }}
+
+{{ end }}
+```
diff --git a/docs/content/en/methods/pager/_index.md b/docs/content/en/methods/pager/_index.md
new file mode 100644
index 00000000000..200640c3ac1
--- /dev/null
+++ b/docs/content/en/methods/pager/_index.md
@@ -0,0 +1,6 @@
+---
+title: Pager methods
+linkTitle: Pager
+description: Use these methods with a Pager object when building navigation for a paginated list page.
+keywords: []
+---
diff --git a/docs/content/en/methods/pages/ByDate.md b/docs/content/en/methods/pages/ByDate.md
new file mode 100644
index 00000000000..5d1e339a02c
--- /dev/null
+++ b/docs/content/en/methods/pages/ByDate.md
@@ -0,0 +1,28 @@
+---
+title: ByDate
+description: Returns the given page collection sorted by date in ascending order.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Pages
+ signatures: [PAGES.ByDate]
+---
+
+When sorting by date, the value is determined by your [project configuration], defaulting to the `date` field in front matter.
+
+[project configuration]: /configuration/front-matter/#dates
+
+```go-html-template
+{{ range .Pages.ByDate }}
+
+{{ end }}
+```
diff --git a/docs/content/en/methods/pages/ByExpiryDate.md b/docs/content/en/methods/pages/ByExpiryDate.md
new file mode 100644
index 00000000000..4f34c6e03bc
--- /dev/null
+++ b/docs/content/en/methods/pages/ByExpiryDate.md
@@ -0,0 +1,28 @@
+---
+title: ByExpiryDate
+description: Returns the given page collection sorted by expiration date in ascending order.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Pages
+ signatures: [PAGES.ByExpiryDate]
+---
+
+When sorting by expiration date, the value is determined by your [project configuration], defaulting to the `expiryDate` field in front matter.
+
+[project configuration]: /configuration/front-matter/#dates
+
+```go-html-template
+{{ range .Pages.ByExpiryDate }}
+
+{{ end }}
+```
diff --git a/docs/content/en/methods/pages/ByLanguage.md b/docs/content/en/methods/pages/ByLanguage.md
new file mode 100644
index 00000000000..751e1d8f582
--- /dev/null
+++ b/docs/content/en/methods/pages/ByLanguage.md
@@ -0,0 +1,45 @@
+---
+title: ByLanguage
+description: Returns the given page collection sorted by language.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Pages
+ signatures: [PAGES.ByLanguage]
+---
+
+When sorting by language, Hugo orders the page collection using the following priority:
+
+1. Language weight (ascending)
+1. Date (descending)
+1. LinkTitle (ascending)
+
+This method is rarely, if ever, needed. Page collections that already contain multiple languages, such as those returned by the [`Rotate`][], [`Translations`][], or [`AllTranslations`][] methods on a `Page` object, are already sorted by language weight.
+
+This contrived example aggregates pages from all sites and then sorts them by language:
+
+```go-html-template
+{{ $p := slice }}
+{{ range hugo.Sites }}
+ {{ range .Pages }}
+ {{ $p = $p | append . }}
+ {{ end }}
+{{ end }}
+
+{{ range $p.ByLanguage }}
+
+{{ end }}
+```
+
+[`AllTranslations`]: /methods/page/alltranslations/
+[`Rotate`]: /methods/page/rotate/
+[`Translations`]: /methods/page/translations/
diff --git a/docs/content/en/methods/pages/ByLastmod.md b/docs/content/en/methods/pages/ByLastmod.md
new file mode 100644
index 00000000000..7fc865e0a68
--- /dev/null
+++ b/docs/content/en/methods/pages/ByLastmod.md
@@ -0,0 +1,28 @@
+---
+title: ByLastmod
+description: Returns the given page collection sorted by last modification date in ascending order.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Pages
+ signatures: [PAGES.ByLastmod]
+---
+
+When sorting by last modification date, the value is determined by your [project configuration], defaulting to the `lastmod` field in front matter.
+
+[project configuration]: /configuration/front-matter/#dates
+
+```go-html-template
+{{ range .Pages.ByLastmod }}
+
+{{ end }}
+```
diff --git a/docs/content/en/methods/pages/ByLinkTitle.md b/docs/content/en/methods/pages/ByLinkTitle.md
new file mode 100644
index 00000000000..4a024d25a14
--- /dev/null
+++ b/docs/content/en/methods/pages/ByLinkTitle.md
@@ -0,0 +1,24 @@
+---
+title: ByLinkTitle
+description: Returns the given page collection sorted by link title in ascending order, falling back to title if link title is not defined.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Pages
+ signatures: [PAGES.ByLinkTitle]
+---
+
+```go-html-template
+{{ range .Pages.ByLinkTitle }}
+
+{{ end }}
+```
diff --git a/docs/content/en/methods/pages/ByParam.md b/docs/content/en/methods/pages/ByParam.md
new file mode 100644
index 00000000000..fcc9b287fa6
--- /dev/null
+++ b/docs/content/en/methods/pages/ByParam.md
@@ -0,0 +1,34 @@
+---
+title: ByParam
+description: Returns the given page collection sorted by the given parameter in ascending order.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Pages
+ signatures: [PAGES.ByParam PARAM]
+---
+
+If the given parameter is not present in front matter, Hugo will use the matching parameter in your project configuration if present.
+
+```go-html-template
+{{ range .Pages.ByParam "author" }}
+
+{{ end }}
+```
+
+If the targeted parameter is nested, access the field using dot notation:
+
+```go-html-template
+{{ range .Pages.ByParam "author.last_name" }}
+
+{{ end }}
+```
diff --git a/docs/content/en/methods/pages/ByPublishDate.md b/docs/content/en/methods/pages/ByPublishDate.md
new file mode 100644
index 00000000000..76d2b2b5867
--- /dev/null
+++ b/docs/content/en/methods/pages/ByPublishDate.md
@@ -0,0 +1,28 @@
+---
+title: ByPublishDate
+description: Returns the given page collection sorted by publish date in ascending order.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Pages
+ signatures: [PAGES.ByPublishDate]
+---
+
+When sorting by publish date, the value is determined by your [project configuration], defaulting to the `publishDate` field in front matter.
+
+[project configuration]: /configuration/front-matter/#dates
+
+```go-html-template
+{{ range .Pages.ByPublishDate }}
+
+{{ end }}
+```
diff --git a/docs/content/en/methods/pages/ByWeight.md b/docs/content/en/methods/pages/ByWeight.md
new file mode 100644
index 00000000000..ba255d3c332
--- /dev/null
+++ b/docs/content/en/methods/pages/ByWeight.md
@@ -0,0 +1,26 @@
+---
+title: ByWeight
+description: Returns the given page collection sorted by weight in ascending order.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Pages
+ signatures: [PAGES.ByWeight]
+---
+
+Assign a [weight](g) to a page using the `weight` field in front matter. The weight must be a non-zero integer. Lighter items float to the top, while heavier items sink to the bottom. Unweighted or zero-weighted pages are placed at the end of the collection.
+
+```go-html-template
+{{ range .Pages.ByWeight }}
+
+{{ end }}
+```
diff --git a/docs/content/en/methods/pages/GroupByDate.md b/docs/content/en/methods/pages/GroupByDate.md
new file mode 100644
index 00000000000..19f7918865e
--- /dev/null
+++ b/docs/content/en/methods/pages/GroupByDate.md
@@ -0,0 +1,63 @@
+---
+title: GroupByDate
+description: Returns the given page collection grouped by date in descending order.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.PagesGroup
+ signatures: ['PAGES.GroupByDate LAYOUT [SORT]']
+---
+
+When grouping by date, the value is determined by your [project configuration], defaulting to the `date` field in front matter.
+
+The [layout string] has the same format as the layout string for the [`time.Format`] function. The resulting group key is [localized](g) for language and region.
+
+[`time.Format`]: /functions/time/format/
+[layout string]: #layout-string
+[project configuration]: /configuration/front-matter/#dates
+
+{{% include "/_common/methods/pages/group-sort-order.md" %}}
+
+To group content by year and month:
+
+```go-html-template
+{{ range .Pages.GroupByDate "January 2006" }}
+
+{{ end }}
+```
+
+The pages within each group will also be sorted by date, either ascending or descending depending on the grouping option. To sort the pages within each group, use one of the sorting methods. For example, to sort the pages within each group by title:
+
+```go-html-template
+{{ range .Pages.GroupByDate "January 2006" }}
+
+{{ end }}
+```
+
+## Layout string
+
+{{% include "/_common/time-layout-string.md" %}}
diff --git a/docs/content/en/methods/pages/GroupByExpiryDate.md b/docs/content/en/methods/pages/GroupByExpiryDate.md
new file mode 100644
index 00000000000..7fe0282c2ca
--- /dev/null
+++ b/docs/content/en/methods/pages/GroupByExpiryDate.md
@@ -0,0 +1,63 @@
+---
+title: GroupByExpiryDate
+description: Returns the given page collection grouped by expiration date in descending order.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.PagesGroup
+ signatures: ['PAGES.GroupByExpiryDate LAYOUT [SORT]']
+---
+
+When grouping by expiration date, the value is determined by your [project configuration], defaulting to the `expiryDate` field in front matter.
+
+The [layout string] has the same format as the layout string for the [`time.Format`] function. The resulting group key is [localized](g) for language and region.
+
+[`time.Format`]: /functions/time/format/
+[layout string]: #layout-string
+[project configuration]: /configuration/front-matter/#dates
+
+{{% include "/_common/methods/pages/group-sort-order.md" %}}
+
+To group content by year and month:
+
+```go-html-template
+{{ range .Pages.GroupByExpiryDate "January 2006" }}
+
+{{ end }}
+```
+
+The pages within each group will also be sorted by expiration date, either ascending or descending depending on your grouping option. To sort the pages within each group, use one of the sorting methods. For example, to sort the pages within each group by title:
+
+```go-html-template
+{{ range .Pages.GroupByExpiryDate "January 2006" }}
+
+{{ end }}
+```
+
+## Layout string
+
+{{% include "/_common/time-layout-string.md" %}}
diff --git a/docs/content/en/methods/pages/GroupByLastmod.md b/docs/content/en/methods/pages/GroupByLastmod.md
new file mode 100644
index 00000000000..45da5af31ad
--- /dev/null
+++ b/docs/content/en/methods/pages/GroupByLastmod.md
@@ -0,0 +1,63 @@
+---
+title: GroupByLastmod
+description: Returns the given page collection grouped by last modification date in descending order.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.PagesGroup
+ signatures: ['PAGES.GroupByLastmod LAYOUT [SORT]']
+---
+
+When grouping by last modification date, the value is determined by your [project configuration], defaulting to the `lastmod` field in front matter.
+
+The [layout string] has the same format as the layout string for the [`time.Format`] function. The resulting group key is [localized](g) for language and region.
+
+[`time.Format`]: /functions/time/format/
+[layout string]: #layout-string
+[project configuration]: /configuration/front-matter/#dates
+
+{{% include "/_common/methods/pages/group-sort-order.md" %}}
+
+To group content by year and month:
+
+```go-html-template
+{{ range .Pages.GroupByLastmod "January 2006" }}
+
+{{ end }}
+```
+
+The pages within each group will also be sorted by last modification date, either ascending or descending depending on your grouping option. To sort the pages within each group, use one of the sorting methods. For example, to sort the pages within each group by title:
+
+```go-html-template
+{{ range .Pages.GroupByLastmod "January 2006" }}
+
+{{ end }}
+```
diff --git a/docs/content/en/methods/pages/GroupByParamDate.md b/docs/content/en/methods/pages/GroupByParamDate.md
new file mode 100644
index 00000000000..b05a096d285
--- /dev/null
+++ b/docs/content/en/methods/pages/GroupByParamDate.md
@@ -0,0 +1,60 @@
+---
+title: GroupByParamDate
+description: Returns the given page collection grouped by the given date parameter in descending order.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.PagesGroup
+ signatures: ['PAGES.GroupByParamDate PARAM LAYOUT [SORT]']
+---
+
+The [layout string] has the same format as the layout string for the [`time.Format`] function. The resulting group key is [localized](g) for language and region.
+
+[`time.Format`]: /functions/time/format/
+[layout string]: #layout-string
+
+{{% include "/_common/methods/pages/group-sort-order.md" %}}
+
+To group content by year and month:
+
+```go-html-template
+{{ range .Pages.GroupByParamDate "eventDate" "January 2006" }}
+
+{{ end }}
+```
+
+To sort the groups in ascending order:
+
+```go-html-template
+{{ range .Pages.GroupByParamDate "eventDate" "January 2006" "asc" }}
+
+{{ end }}
+```
+
+The pages within each group will also be sorted by the parameter date, either ascending or descending depending on your grouping option. To sort the pages within each group, use one of the sorting methods. For example, to sort the pages within each group by title:
+
+```go-html-template
+{{ range .Pages.GroupByParamDate "eventDate" "January 2006" }}
+
+{{ end }}
+```
+
+## Layout string
+
+{{% include "/_common/time-layout-string.md" %}}
diff --git a/docs/content/en/methods/pages/GroupByPublishDate.md b/docs/content/en/methods/pages/GroupByPublishDate.md
new file mode 100644
index 00000000000..629a8480a70
--- /dev/null
+++ b/docs/content/en/methods/pages/GroupByPublishDate.md
@@ -0,0 +1,63 @@
+---
+title: GroupByPublishDate
+description: Returns the given page collection grouped by publish date in descending order.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.PagesGroup
+ signatures: ['PAGES.GroupByPublishDate LAYOUT [SORT]']
+---
+
+When grouping by publish date, the value is determined by your [project configuration], defaulting to the `publishDate` field in front matter.
+
+The [layout string] has the same format as the layout string for the [`time.Format`] function. The resulting group key is [localized](g) for language and region.
+
+[`time.Format`]: /functions/time/format/
+[layout string]: #layout-string
+[project configuration]: /configuration/front-matter/#dates
+
+{{% include "/_common/methods/pages/group-sort-order.md" %}}
+
+To group content by year and month:
+
+```go-html-template
+{{ range .Pages.GroupByPublishDate "January 2006" }}
+
+{{ end }}
+```
+
+The pages within each group will also be sorted by publish date, either ascending or descending depending on your grouping option. To sort the pages within each group, use one of the sorting methods. For example, to sort the pages within each group by title:
+
+```go-html-template
+{{ range .Pages.GroupByPublishDate "January 2006" }}
+
+{{ end }}
+```
diff --git a/docs/content/en/methods/pages/Next.md b/docs/content/en/methods/pages/Next.md
new file mode 100644
index 00000000000..ce091c1abaa
--- /dev/null
+++ b/docs/content/en/methods/pages/Next.md
@@ -0,0 +1,12 @@
+---
+title: Next
+description: Returns the next page in a page collection, relative to the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Page
+ signatures: [PAGES.Next PAGE]
+---
+
+{{% include "/_common/methods/pages/next-and-prev.md" %}}
diff --git a/docs/content/en/methods/pages/Prev.md b/docs/content/en/methods/pages/Prev.md
new file mode 100644
index 00000000000..004b9496d30
--- /dev/null
+++ b/docs/content/en/methods/pages/Prev.md
@@ -0,0 +1,12 @@
+---
+title: Prev
+description: Returns the previous page in a page collection, relative to the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Pages
+ signatures: [PAGES.Prev PAGE]
+---
+
+{{% include "/_common/methods/pages/next-and-prev.md" %}}
diff --git a/docs/content/en/methods/pages/Related.md b/docs/content/en/methods/pages/Related.md
new file mode 100644
index 00000000000..6d385956017
--- /dev/null
+++ b/docs/content/en/methods/pages/Related.md
@@ -0,0 +1,75 @@
+---
+title: Related
+description: Returns a collection of pages related to the given page.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Pages
+ signatures:
+ - PAGES.Related PAGE
+ - PAGES.Related OPTIONS
+---
+
+Based on front matter, Hugo uses several factors to identify content related to the given page. Use the default [related content configuration], or tune the results to the desired indices and parameters. See [details].
+
+The argument passed to the `Related` method may be a `Page` or an options map. For example, to pass the current page:
+
+```go-html-template {file="layouts/page.html"}
+{{ with .Site.RegularPages.Related . | first 5 }}
+
+{{ end }}
+```
+
+## Options
+
+indices
+: (`slice`) The indices to search within.
+
+document
+: (`page`) The page for which to find related content. Required when specifying an options map.
+
+namedSlices
+: (`slice`) The keywords to search for, expressed as a slice of `KeyValues` using the [`keyVals`] function.
+
+[`keyVals`]: /functions/collections/keyvals/
+
+fragments
+: (`slice`) A list of special keywords that is used for indices configured as type "fragments". This will match the [fragment](g) identifiers of the documents.
+
+A contrived example using all of the above:
+
+```go-html-template
+{{ $page := . }}
+{{ $opts := dict
+ "indices" (slice "tags" "keywords")
+ "document" $page
+ "namedSlices" (slice (keyVals "tags" "hugo" "rocks") (keyVals "date" $page.Date))
+ "fragments" (slice "heading-1" "heading-2")
+}}
+```
+
+[details]: /content-management/related-content/
+[related content configuration]: /configuration/related-content/
diff --git a/docs/content/en/methods/pages/Reverse.md b/docs/content/en/methods/pages/Reverse.md
new file mode 100644
index 00000000000..23c4b03243f
--- /dev/null
+++ b/docs/content/en/methods/pages/Reverse.md
@@ -0,0 +1,16 @@
+---
+title: Reverse
+description: Returns the given page collection in reverse order.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Pages
+ signatures: [PAGES.Reverse]
+---
+
+```go-html-template
+{{ range .Pages.ByDate.Reverse }}
+
+{{ end }}
+```
diff --git a/docs/content/en/methods/pages/_index.md b/docs/content/en/methods/pages/_index.md
new file mode 100644
index 00000000000..f2495ae49e0
--- /dev/null
+++ b/docs/content/en/methods/pages/_index.md
@@ -0,0 +1,8 @@
+---
+title: Pages methods
+linkTitle: Pages
+description: Use these methods with a collection of Page objects.
+categories: []
+keywords: []
+aliases: [/variables/pages]
+---
diff --git a/docs/content/en/methods/resource/Colors.md b/docs/content/en/methods/resource/Colors.md
new file mode 100644
index 00000000000..b2490688e45
--- /dev/null
+++ b/docs/content/en/methods/resource/Colors.md
@@ -0,0 +1,176 @@
+---
+title: Colors
+description: Applicable to images, returns a slice of the most dominant colors using a simple histogram method.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: '[]images.Color'
+ signatures: [RESOURCE.Colors]
+---
+
+{{% include "/_common/methods/resource/global-page-remote-resources.md" %}}
+
+The `Colors` method returns a slice of the most dominant colors in a [processable image](g), ordered from most dominant to least dominant.
+
+> [!note]
+> Use the [`reflect.IsImageResourceProcessable`][] function to verify that an image can be processed.
+
+## Usage
+
+This method is fast, but if you downscale your image first, you can further improve performance by extracting colors from the smaller resource.
+
+## Methods
+
+Each color in the slice is an object with the following methods:
+
+### ColorHex
+
+(`string`) Returns the [hexadecimal color][] value, prefixed with a hash sign.
+
+### Luminance
+
+(`float64`) Returns the [relative luminance][] of the color in the sRGB colorspace in the range [0, 1]. A value of `0` represents the darkest black, while a value of `1` represents the lightest white.
+
+> [!note]
+> Image filters such as [`images.Dither`][], [`images.Padding`][], and [`images.Text`][] accept either hexadecimal color values or `images.Color` objects as arguments. Hugo renders an `images.Color` object as a hexadecimal color value.
+
+## Sorting
+
+As a contrived example, create a table of an image's dominant colors with the most dominant color first, and display the relative luminance of each dominant color:
+
+```go-html-template
+{{ with resources.Get "images/a.jpg" }}
+
+
+
+
Color
+
Relative luminance
+
+
+
+ {{ range .Colors }}
+
+
{{ .ColorHex }}
+
{{ .Luminance | lang.FormatNumber 4 }}
+
+ {{ end }}
+
+
+{{ end }}
+```
+
+Hugo renders this to:
+
+ColorHex|Relative luminance
+:--|:--
+`#bebebd`|`0.5145`
+`#514947`|`0.0697`
+`#768a9a`|`0.2436`
+`#647789`|`0.1771`
+`#90725e`|`0.1877`
+`#a48974`|`0.2704`
+
+To sort by dominance with the least dominant color first:
+
+```go-html-template
+{{ range .Colors | collections.Reverse }}
+```
+
+To sort by relative luminance with the darkest color first:
+
+```go-html-template
+{{ range sort .Colors "Luminance" }}
+```
+
+To sort by relative luminance with the lightest color first, use either of these constructs:
+
+```go-html-template
+{{ range sort .Colors "Luminance" | collections.Reverse }}
+{{ range sort .Colors "Luminance" "desc" }}
+```
+
+## Examples
+
+### Image borders
+
+To add a 5 pixel border to an image using the most dominant color:
+
+```go-html-template
+{{ with resources.Get "images/a.jpg" }}
+ {{ $mostDominant := index .Colors 0 }}
+ {{ $filter := images.Padding 5 $mostDominant }}
+ {{ with .Filter $filter }}
+
+ {{ end }}
+{{ end }}
+```
+
+To add a 5 pixel border to an image using the darkest dominant color:
+
+```go-html-template
+{{ with resources.Get "images/a.jpg" }}
+ {{ $darkest := index (sort .Colors "Luminance") 0 }}
+ {{ $filter := images.Padding 5 $darkest }}
+ {{ with .Filter $filter }}
+
+ {{ end }}
+{{ end }}
+```
+
+### Light text on dark background
+
+To create a text box where the foreground and background colors are derived from an image's lightest and darkest dominant colors:
+
+```go-html-template
+{{ with resources.Get "images/a.jpg" }}
+ {{ $darkest := index (sort .Colors "Luminance") 0 }}
+ {{ $lightest := index (sort .Colors "Luminance" "desc") 0 }}
+
+
+
This is light text on a dark background.
+
+
+{{ end }}
+```
+
+### WCAG contrast ratio
+
+In the previous example we placed light text on a dark background, but does this color combination conform to [WCAG][] guidelines for either the [minimum][] or the [enhanced][] contrast ratio?
+
+The WCAG defines the [contrast ratio][] as:
+
+$$contrast\ ratio = { L_1 + 0.05 \over L_2 + 0.05 }$$
+
+where \(L_1\) is the relative luminance of the lightest color and \(L_2\) is the relative luminance of the darkest color.
+
+Calculate the contrast ratio to determine WCAG conformance:
+
+```go-html-template
+{{ with resources.Get "images/a.jpg" }}
+ {{ $lightest := index (sort .Colors "Luminance" "desc") 0 }}
+ {{ $darkest := index (sort .Colors "Luminance") 0 }}
+ {{ $cr := div
+ (add $lightest.Luminance 0.05)
+ (add $darkest.Luminance 0.05)
+ }}
+ {{ if ge $cr 7.5 }}
+ {{ printf "The %.2f contrast ratio conforms to WCAG Level AAA." $cr }}
+ {{ else if ge $cr 4.5 }}
+ {{ printf "The %.2f contrast ratio conforms to WCAG Level AA." $cr }}
+ {{ else }}
+ {{ printf "The %.2f contrast ratio does not conform to WCAG guidelines." $cr }}
+ {{ end }}
+{{ end }}
+```
+
+[WCAG]: https://en.wikipedia.org/wiki/Web_Content_Accessibility_Guidelines
+[`images.Dither`]: /functions/images/dither/
+[`images.Padding`]: /functions/images/padding/
+[`images.Text`]: /functions/images/text/
+[`reflect.IsImageResourceProcessable`]: /functions/reflect/isimageresourceprocessable/
+[contrast ratio]: https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio
+[enhanced]: https://www.w3.org/WAI/WCAG22/quickref/?showtechniques=145#contrast-enhanced
+[hexadecimal color]: https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color
+[minimum]: https://www.w3.org/WAI/WCAG22/quickref/?showtechniques=145#contrast-minimum
+[relative luminance]: https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
diff --git a/docs/content/en/methods/resource/Content.md b/docs/content/en/methods/resource/Content.md
new file mode 100644
index 00000000000..ff2ad6de87f
--- /dev/null
+++ b/docs/content/en/methods/resource/Content.md
@@ -0,0 +1,60 @@
+---
+title: Content
+description: Returns the content of the given resource.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: any
+ signatures: [RESOURCE.Content]
+---
+
+{{% include "/_common/methods/resource/global-page-remote-resources.md" %}}
+
+The `Content` method on a `Resource` object returns `template.HTML` when the [resource type] is `page`, otherwise it returns a `string`.
+
+[resource type]: /methods/resource/resourcetype/
+
+```text {file="assets/quotations/kipling.txt"}
+He travels the fastest who travels alone.
+```
+
+To get the content:
+
+```go-html-template
+{{ with resources.Get "quotations/kipling.txt" }}
+ {{ .Content }} → He travels the fastest who travels alone.
+{{ end }}
+```
+
+To get the size in bytes:
+
+```go-html-template
+{{ with resources.Get "quotations/kipling.txt" }}
+ {{ .Content | len }} → 42
+{{ end }}
+```
+
+To create an inline image:
+
+```go-html-template
+{{ with resources.Get "images/a.jpg" }}
+
+{{ end }}
+```
+
+To create inline CSS:
+
+```go-html-template
+{{ with resources.Get "css/style.css" }}
+
+{{ end }}
+```
+
+To create inline JavaScript:
+
+```go-html-template
+{{ with resources.Get "js/script.js" }}
+
+{{ end }}
+```
diff --git a/docs/content/en/methods/resource/Crop.md b/docs/content/en/methods/resource/Crop.md
new file mode 100644
index 00000000000..e764be1676e
--- /dev/null
+++ b/docs/content/en/methods/resource/Crop.md
@@ -0,0 +1,54 @@
+---
+title: Crop
+description: Applicable to images, returns a new image resource cropped according to the given processing specification.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: images.ImageResource
+ signatures: [RESOURCE.Crop SPECIFICATION]
+---
+
+{{% include "/_common/methods/resource/global-page-remote-resources.md" %}}
+
+The `Crop` method returns a new resource from a [processable image](g) according to the given [processing specification][].
+
+> [!note]
+> Use the [`reflect.IsImageResourceProcessable`][] function to verify that an image can be processed.
+
+## Usage
+
+When cropping, you must provide both width and height (such as `200x200`) within the specification. This method does not perform any resizing; it simply extracts a region of the image based on the dimensions and the [anchor](#anchor) provided, if any.
+
+```go-html-template
+{{ with resources.Get "images/original.jpg" }}
+ {{ with .Crop "200x200 TopRight" }}
+
+ {{ end }}
+{{ end }}
+```
+
+In the example above, `"200x200 TopRight"` is the processing specification.
+
+{{% include "/_common/methods/resource/processing-spec.md" %}}
+
+## Example
+
+```go-html-template
+{{ with resources.Get "images/original.jpg" }}
+ {{ with .Crop "200x200 TopRight" }}
+
+ {{ end }}
+{{ end }}
+```
+
+{{< img
+ src="images/examples/zion-national-park.jpg"
+ alt="Zion National Park"
+ filter="Process"
+ filterArgs="crop 200x200 TopRight"
+ example=true
+>}}
+
+[`reflect.IsImageResourceProcessable`]: /functions/reflect/isimageresourceprocessable/
+[processing specification]: #processing-specification
diff --git a/docs/content/en/methods/resource/Data.md b/docs/content/en/methods/resource/Data.md
new file mode 100644
index 00000000000..01b22823d12
--- /dev/null
+++ b/docs/content/en/methods/resource/Data.md
@@ -0,0 +1,64 @@
+---
+title: Data
+description: Applicable to resources returned by the resources.GetRemote function, returns information from the HTTP response.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: map
+ signatures: [RESOURCE.Data]
+---
+
+The `Data` method on a resource returned by the [`resources.GetRemote`] function returns information from the HTTP response.
+
+## Example
+
+```go-html-template
+{{ $url := "https://example.org/images/a.jpg" }}
+{{ $opts := dict "responseHeaders" (slice "Server") }}
+{{ with try (resources.GetRemote $url) }}
+ {{ with .Err }}
+ {{ errorf "%s" . }}
+ {{ else with .Value }}
+ {{ with .Data }}
+ {{ .ContentLength }} → 42764
+ {{ .ContentType }} → image/jpeg
+ {{ .Headers }} → map[Server:[Netlify]]
+ {{ .Status }} → 200 OK
+ {{ .StatusCode }} → 200
+ {{ .TransferEncoding }} → []
+ {{ end }}
+ {{ else }}
+ {{ errorf "Unable to get remote resource %q" $url }}
+ {{ end }}
+{{ end }}
+```
+
+## Methods
+
+### ContentLength
+
+(`int`) The content length in bytes.
+
+### ContentType
+
+(`string`) The content type.
+
+### Headers
+
+(`map[string][]string`) A map of response headers matching those requested in the [`responseHeaders`] option passed to the `resources.GetRemote` function. The header name matching is case-insensitive. In most cases there will be one value per header key.
+
+### Status
+
+(`string`) The HTTP status text.
+
+### StatusCode
+
+(`int`) The HTTP status code.
+
+### TransferEncoding
+
+(`string`) The transfer encoding.
+
+[`resources.GetRemote`]: /functions/resources/getremote/
+[`responseHeaders`]: /functions/resources/getremote/#responseheaders
diff --git a/docs/content/en/methods/resource/Err.md b/docs/content/en/methods/resource/Err.md
new file mode 100644
index 00000000000..aa0d076b15b
--- /dev/null
+++ b/docs/content/en/methods/resource/Err.md
@@ -0,0 +1,17 @@
+---
+title: Err
+description: Applicable to resources returned by the resources.GetRemote function, returns an error message if the HTTP request fails, else nil.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: resource.resourceError
+ signatures: [RESOURCE.Err]
+expiryDate: 2027-01-16 # deprecated 2025-01-16 in v0.141.0
+---
+
+{{< deprecated-in 0.141.0 >}}
+Use the `try` statement instead. See [example].
+
+[example]: /functions/go-template/try/#example
+{{< /deprecated-in >}}
diff --git a/docs/content/en/methods/resource/Exif.md b/docs/content/en/methods/resource/Exif.md
new file mode 100644
index 00000000000..bc060f4c9c4
--- /dev/null
+++ b/docs/content/en/methods/resource/Exif.md
@@ -0,0 +1,15 @@
+---
+title: Exif
+description: Returns an object containing Exif metadata for supported image formats.
+categories: []
+keywords: ['metadata']
+params:
+ functions_and_methods:
+ returnType: meta.ExifInfo
+ signatures: [RESOURCE.Exif]
+expiryDate: 2028-01-28 # deprecated 2026-01-28 in v0.155.0
+---
+
+{{< deprecated-in 0.155.0 >}}
+Use [`Meta`](/methods/resource/meta/) instead.
+{{< /deprecated-in >}}
diff --git a/docs/content/en/methods/resource/Fill.md b/docs/content/en/methods/resource/Fill.md
new file mode 100644
index 00000000000..c510afe6d44
--- /dev/null
+++ b/docs/content/en/methods/resource/Fill.md
@@ -0,0 +1,54 @@
+---
+title: Fill
+description: Applicable to images, returns a new image resource cropped and resized according to the given processing specification.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: images.ImageResource
+ signatures: [RESOURCE.Fill SPECIFICATION]
+---
+
+{{% include "/_common/methods/resource/global-page-remote-resources.md" %}}
+
+The `Fill` method returns a new resource from a [processable image](g) according to the given [processing specification][].
+
+> [!note]
+> Use the [`reflect.IsImageResourceProcessable`][] function to verify that an image can be processed.
+
+## Usage
+
+When filling, you must provide both width and height (such as `500x200`) within the specification. `Fill` maintains the original aspect ratio by resizing the image to cover the target area and cropping any overflowing pixels based on the [anchor](#anchor) provided.
+
+```go-html-template
+{{ with resources.Get "images/original.jpg" }}
+ {{ with .Fill "500x200 TopRight" }}
+
+ {{ end }}
+{{ end }}
+```
+
+In the example above, `"500x200 TopRight"` is the _processing specification.
+
+{{% include "/_common/methods/resource/processing-spec.md" %}}
+
+## Example
+
+```go-html-template
+{{ with resources.Get "images/original.jpg" }}
+ {{ with .Fill "500x200 TopRight" }}
+
+ {{ end }}
+{{ end }}
+```
+
+{{< img
+ src="images/examples/zion-national-park.jpg"
+ alt="Zion National Park"
+ filter="Process"
+ filterArgs="fill 500x200 TopRight"
+ example=true
+>}}
+
+[`reflect.IsImageResourceProcessable`]: /functions/reflect/isimageresourceprocessable/
+[processing specification]: #processing-specification
diff --git a/docs/content/en/methods/resource/Filter.md b/docs/content/en/methods/resource/Filter.md
new file mode 100644
index 00000000000..812466a5532
--- /dev/null
+++ b/docs/content/en/methods/resource/Filter.md
@@ -0,0 +1,75 @@
+---
+title: Filter
+description: Applicable to images, applies one or more image filters to the given image resource.
+categories: []
+keywords: [filter]
+params:
+ alt_title: RESOURCE.Filter
+ functions_and_methods:
+ returnType: images.ImageResource
+ signatures: [RESOURCE.Filter FILTER...]
+---
+
+{{% include "/_common/methods/resource/global-page-remote-resources.md" %}}
+
+The `Filter` method returns a new resource from a [processable image](g) after applying one or more [image filters](#image-filters).
+
+> [!note]
+> Use the [`reflect.IsImageResourceProcessable`][] function to verify that an image can be processed.
+
+## Usage
+
+Use the `Filter` method to apply effects such as blurring, sharpening, or grayscale conversion. You can pass a single filter or a slice of filters. When providing a slice, Hugo applies the filters from left to right.
+
+To apply a single filter:
+
+```go-html-template
+{{ with resources.Get "images/original.jpg" }}
+ {{ with .Filter images.Grayscale }}
+
+ {{ end }}
+{{ end }}
+```
+
+To apply multiple filters:
+
+```go-html-template
+{{ $filters := slice
+ images.Grayscale
+ (images.GaussianBlur 8)
+}}
+{{ with resources.Get "images/original.jpg" }}
+ {{ with .Filter $filters }}
+
+ {{ end }}
+{{ end }}
+```
+
+You can also apply image filters using the [`images.Filter`][] function.
+
+## Example
+
+```go-html-template
+{{ with resources.Get "images/original.jpg" }}
+ {{ with .Filter images.Grayscale }}
+
+ {{ end }}
+{{ end }}
+```
+
+{{< img
+ src="images/examples/zion-national-park.jpg"
+ alt="Zion National Park"
+ filter="Grayscale"
+ filterArgs=""
+ example=true
+>}}
+
+## Image filters
+
+Use any of these filters with the `Filter` method.
+
+{{% render-list-of-pages-in-section path=/functions/images filter=functions_images_no_filters filterType=exclude %}}
+
+[`images.Filter`]: /functions/images/filter/
+[`reflect.IsImageResourceProcessable`]: /functions/reflect/isimageresourceprocessable/
diff --git a/docs/content/en/methods/resource/Fit.md b/docs/content/en/methods/resource/Fit.md
new file mode 100644
index 00000000000..2c0a7c91c53
--- /dev/null
+++ b/docs/content/en/methods/resource/Fit.md
@@ -0,0 +1,56 @@
+---
+title: Fit
+description: Applicable to images, returns a new image resource downscaled to fit according to the given processing specification.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: images.ImageResource
+ signatures: [RESOURCE.Fit SPECIFICATION]
+---
+
+{{% include "/_common/methods/resource/global-page-remote-resources.md" %}}
+
+The `Fit` method returns a new resource from a [processable image](g) according to the given [processing specification][].
+
+> [!note]
+> Use the [`reflect.IsImageResourceProcessable`][] function to verify that an image can be processed.
+
+## Usage
+
+When fitting, you must provide both width and height (such as `300x175`) within the specification. `Fit` maintains the original aspect ratio by downscaling the image until it fits within the specified dimensions. Unlike [`Fill`][] or [`Resize`][], this method will never upscale an image; if the source image is smaller than the target dimensions, the dimensions of the resulting image are the same as the original.
+
+```go-html-template
+{{ with resources.Get "images/original.jpg" }}
+ {{ with .Fit "300x175" }}
+
+ {{ end }}
+{{ end }}
+```
+
+In the example above, `"300x175"` is the processing specification.
+
+{{% include "/_common/methods/resource/processing-spec.md" %}}
+
+## Example
+
+```go-html-template
+{{ with resources.Get "images/original.jpg" }}
+ {{ with .Fit "300x175" }}
+
+ {{ end }}
+{{ end }}
+```
+
+{{< img
+ src="images/examples/zion-national-park.jpg"
+ alt="Zion National Park"
+ filter="Process"
+ filterArgs="fit 300x175"
+ example=true
+>}}
+
+[`Fill`]: /methods/resource/fill/
+[`Resize`]: /methods/resource/resize/
+[`reflect.IsImageResourceProcessable`]: /functions/reflect/isimageresourceprocessable/
+[processing specification]: #processing-specification
diff --git a/docs/content/en/methods/resource/Height.md b/docs/content/en/methods/resource/Height.md
new file mode 100644
index 00000000000..726802cb005
--- /dev/null
+++ b/docs/content/en/methods/resource/Height.md
@@ -0,0 +1,26 @@
+---
+title: Height
+description: Applicable to images, returns the height of the given resource.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: int
+ signatures: [RESOURCE.Height]
+---
+
+{{% include "/_common/methods/resource/global-page-remote-resources.md" %}}
+
+Use the [`reflect.IsImageResourceWithMeta`][] function to verify that Hugo can determine the dimensions before calling the `Height` method.
+
+```go-html-template
+{{ with resources.GetMatch "images/featured.*" }}
+ {{ if reflect.IsImageResourceWithMeta . }}
+
+ {{ else }}
+
+ {{ end }}
+{{ end }}
+```
+
+[`reflect.IsImageResourceWithMeta`]: /functions/reflect/isimageresourcewithmeta/
diff --git a/docs/content/en/methods/resource/MediaType.md b/docs/content/en/methods/resource/MediaType.md
new file mode 100644
index 00000000000..7721f69ba1b
--- /dev/null
+++ b/docs/content/en/methods/resource/MediaType.md
@@ -0,0 +1,66 @@
+---
+title: MediaType
+description: Returns a media type object for the given resource.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: media.Type
+ signatures: [RESOURCE.MediaType]
+---
+
+{{% include "/_common/methods/resource/global-page-remote-resources.md" %}}
+
+The `MediaType` method on a `Resource` object returns an object with additional methods.
+
+## Methods
+
+### Type
+
+(`string`) The resource's media type.
+
+```go-html-template
+{{ with resources.Get "images/a.jpg" }}
+ {{ .MediaType.Type }} → image/jpeg
+{{ end }}
+```
+
+### MainType
+
+(`string`) The main type of the resource's media type.
+
+```go-html-template
+{{ with resources.Get "images/a.jpg" }}
+ {{ .MediaType.MainType }} → image
+{{ end }}
+```
+
+### SubType
+
+(`string`) The subtype of the resource's media type. This may or may not correspond to the file suffix.
+
+```go-html-template
+{{ with resources.Get "images/a.jpg" }}
+ {{ .MediaType.SubType }} → jpeg
+{{ end }}
+```
+
+### Suffixes
+
+(`slice`) A slice of possible file suffixes for the resource's media type.
+
+```go-html-template
+{{ with resources.Get "images/a.jpg" }}
+ {{ .MediaType.Suffixes }} → [jpg jpeg jpe jif jfif]
+{{ end }}
+```
+
+### FirstSuffix.Suffix
+
+(`string`) The first of the possible file suffixes for the resource's media type.
+
+```go-html-template
+{{ with resources.Get "images/a.jpg" }}
+ {{ .MediaType.FirstSuffix.Suffix }} → jpg
+{{ end }}
+```
diff --git a/docs/content/en/methods/resource/Meta.md b/docs/content/en/methods/resource/Meta.md
new file mode 100644
index 00000000000..8a992ba72be
--- /dev/null
+++ b/docs/content/en/methods/resource/Meta.md
@@ -0,0 +1,110 @@
+---
+title: Meta
+description: Applicable to images, returns an object containing Exif, IPTC, and XMP metadata for supported image formats.
+categories: []
+keywords: ['metadata']
+params:
+ functions_and_methods:
+ returnType: meta.MetaInfo
+ signatures: [RESOURCE.Meta]
+---
+
+{{< new-in 0.155.3 />}}
+
+{{% include "/_common/methods/resource/global-page-remote-resources.md" %}}
+
+The `Meta` method on an image `Resource` object returns an object containing [Exif][Exif_Definition], [IPTC][IPTC_Definition], and [XMP][XMP_Definition] metadata.
+
+While Hugo classifies many file types as images, only certain formats support metadata extraction. Supported formats include AVIF, BMP, GIF, HEIC, HEIF, JPEG, PNG, TIFF, and WebP.
+
+> [!note]
+> Metadata is not preserved during image transformation. Use this method with the _original_ image resource to extract metadata from supported formats.
+
+## Usage
+
+Use the [`reflect.IsImageResourceWithMeta`][] function to verify that a resource supports metadata extraction before calling the `Meta` method.
+
+```go-html-template
+{{ with resources.GetMatch "images/featured.*" }}
+ {{ if reflect.IsImageResourceWithMeta . }}
+ {{ with .Meta }}
+ {{ .Date.Format "2006-01-02" }}
+ {{ end }}
+ {{ end }}
+{{ end }}
+```
+
+## Methods
+
+### Date
+
+(`time.Time`) Returns the image creation date/time. Format with the [`time.Format`][] function.
+
+### Lat
+
+(`float64`) Returns the GPS latitude in degrees from Exif metadata, with a fallback to XMP metadata.
+
+### Long
+
+(`float64`) Returns the GPS longitude in degrees from Exif metadata, with a fallback to XMP metadata.
+
+### Orientation
+
+(`int`) Returns the value of the Exif `Orientation` tag, one of eight possible values.
+
+Value|Description
+:--|:--
+`1`|Horizontal (normal)
+`2`|Mirrored horizontal
+`3`|Rotated 180 degrees
+`4`|Mirrored vertical
+`5`|Mirrored horizontal and rotated 270 degrees clockwise
+`6`|Rotated 90 degrees clockwise
+`7`|Mirrored horizontal and rotated 90 degrees clockwise
+`8`|Rotated 270 degrees clockwise
+{class="!mt-0"}
+
+> [!tip]
+> Use the [`images.AutoOrient`][] image filter to rotate and flip an image as needed per its Exif orientation tag
+
+### Exif
+
+(`meta.Tags`) Returns a collection of available Exif fields for this image. Availability is determined by the [`sources`][] setting and specific fields are managed via the [`fields`][] setting, both of which are managed in your project configuration.
+
+### IPTC
+
+(`meta.Tags`) Returns a collection of available IPTC fields for this image. Availability is determined by the [`sources`][] setting and specific fields are managed via the [`fields`][] setting, both of which are managed in your project configuration.
+
+### XMP
+
+(`meta.Tags`) Returns a collection of available XMP fields for this image. Availability is determined by the [`sources`][] setting and specific fields are managed via the [`fields`][] setting, both of which are managed in your project configuration.
+
+## Examples
+
+To list the creation date, latitude, longitude, and orientation:
+
+```go-html-template
+{{ with resources.GetMatch "images/featured.*" }}
+ {{ if reflect.IsImageResourceWithMeta . }}
+ {{ with .Meta }}
+
+ {{ end }}
+ {{ end }}
+{{ end }}
+```
+
+{{% include "/_common/functions/reflect/image-reflection-functions.md" %}}
+
+[`fields`]: /configuration/imaging/#fields
+[`images.AutoOrient`]: /functions/images/autoorient/
+[`reflect.IsImageResourceWithMeta`]: /functions/reflect/isimageresourcewithmeta/
+[`sources`]: /configuration/imaging/#sources
+[`time.Format`]: /functions/time/format/
+[Exif_Definition]: https://en.wikipedia.org/wiki/Exif
+[IPTC_Definition]: https://en.wikipedia.org/wiki/IPTC_Information_Interchange_Model
+[XMP_Definition]: https://en.wikipedia.org/wiki/Extensible_Metadata_Platform
diff --git a/docs/content/en/methods/resource/Name.md b/docs/content/en/methods/resource/Name.md
new file mode 100644
index 00000000000..3331bb50d5b
--- /dev/null
+++ b/docs/content/en/methods/resource/Name.md
@@ -0,0 +1,89 @@
+---
+title: Name
+description: Returns the name of the given resource as optionally defined in front matter, falling back to its file path.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [RESOURCE.Name]
+---
+
+The value returned by the `Name` method on a `Resource` object depends on the resource type.
+
+## Global resource
+
+With a [global resource](g), the `Name` method returns the path to the resource, relative to the `assets` directory.
+
+```text
+assets/
+└── images/
+ └── Sunrise in Bryce Canyon.jpg
+```
+
+```go-html-template
+{{ with resources.Get "images/Sunrise in Bryce Canyon.jpg" }}
+ {{ .Name }} → /images/Sunrise in Bryce Canyon.jpg
+{{ end }}
+```
+
+## Page resource
+
+With a [page resource](g), if you create an element in the `resources` array in front matter, the `Name` method returns the value of the `name` parameter.
+
+```text
+content/
+├── example/
+│ ├── images/
+│ │ └── a.jpg
+│ └── index.md
+└── _index.md
+```
+
+{{< code-toggle file=content/example/index.md fm=true >}}
+title = 'Example'
+[[resources]]
+src = 'images/a.jpg'
+name = 'Sunrise in Bryce Canyon'
+{{< /code-toggle >}}
+
+```go-html-template
+{{ with .Resources.Get "images/a.jpg" }}
+ {{ .Name }} → Sunrise in Bryce Canyon
+{{ end }}
+```
+
+You can also capture the image by specifying its `name` instead of its path:
+
+```go-html-template
+{{ with .Resources.Get "Sunrise in Bryce Canyon" }}
+ {{ .Name }} → Sunrise in Bryce Canyon
+{{ end }}
+```
+
+If you do not create an element in the `resources` array in front matter, the `Name` method returns the file path, relative to the page bundle.
+
+```text
+content/
+├── example/
+│ ├── images/
+│ │ └── Sunrise in Bryce Canyon.jpg
+│ └── index.md
+└── _index.md
+```
+
+```go-html-template
+{{ with .Resources.Get "images/Sunrise in Bryce Canyon.jpg" }}
+ {{ .Name }} → images/Sunrise in Bryce Canyon.jpg
+{{ end }}
+```
+
+## Remote resource
+
+With a [remote resource](g), the `Name` method returns a hashed file name.
+
+```go-html-template
+{{ with resources.GetRemote "https://example.org/images/a.jpg" }}
+ {{ .Name }} → /a_18432433023265451104.jpg
+{{ end }}
+```
diff --git a/docs/content/en/methods/resource/Params.md b/docs/content/en/methods/resource/Params.md
new file mode 100644
index 00000000000..38f2ef6c27e
--- /dev/null
+++ b/docs/content/en/methods/resource/Params.md
@@ -0,0 +1,61 @@
+---
+title: Params
+description: Returns a map of resource parameters as defined in front matter.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: map
+ signatures: [RESOURCE.Params]
+---
+
+Use the `Params` method with [page resources](g). It is not applicable to either [global resources](g) or [remote resources](g).
+
+With this content structure:
+
+```text
+content/
+├── posts/
+│ ├── cats/
+│ │ ├── images/
+│ │ │ └── a.jpg
+│ │ └── index.md
+│ └── _index.md
+└── _index.md
+```
+
+And this front matter:
+
+{{< code-toggle file=content/posts/cats.md fm=true >}}
+title = 'Cats'
+[[resources]]
+ src = 'images/a.jpg'
+ title = 'Felix the cat'
+ [resources.params]
+ alt = 'Photograph of black cat'
+ temperament = 'vicious'
+{{< /code-toggle >}}
+
+And this template:
+
+```go-html-template
+{{ with .Resources.Get "images/a.jpg" }}
+
+
+ {{ .Title }} is {{ .Params.temperament }}
+
+{{ end }}
+```
+
+Hugo renders:
+
+```html
+
+
+ Felix the cat is vicious
+
+```
+
+See the [page resources] section for more information.
+
+[page resources]: /content-management/page-resources/
diff --git a/docs/content/en/methods/resource/Permalink.md b/docs/content/en/methods/resource/Permalink.md
new file mode 100644
index 00000000000..a8ec2d323ce
--- /dev/null
+++ b/docs/content/en/methods/resource/Permalink.md
@@ -0,0 +1,20 @@
+---
+title: Permalink
+description: Publishes the given resource and returns its permalink.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [RESOURCE.Permalink]
+---
+
+{{% include "/_common/methods/resource/global-page-remote-resources.md" %}}
+
+The `Permalink` method on a `Resource` object writes the resource to the publish directory, typically `public`, and returns its [permalink](g).
+
+```go-html-template
+{{ with resources.Get "images/a.jpg" }}
+ {{ .Permalink }} → https://example.org/images/a.jpg
+{{ end }}
+```
diff --git a/docs/content/en/methods/resource/Process.md b/docs/content/en/methods/resource/Process.md
new file mode 100644
index 00000000000..9edb086e005
--- /dev/null
+++ b/docs/content/en/methods/resource/Process.md
@@ -0,0 +1,70 @@
+---
+title: Process
+description: Applicable to images, returns a new image resource processed according to the given processing specification.
+categories: []
+keywords: [process]
+params:
+ alt_title: RESOURCE.Process
+ functions_and_methods:
+ returnType: images.ImageResource
+ signatures: [RESOURCE.Process SPECIFICATION]
+---
+
+{{% include "/_common/methods/resource/global-page-remote-resources.md" %}}
+
+The `Process` method returns a new resource from a [processable image](g) according to the given [processing specification][].
+
+> [!note]
+> Use the [`reflect.IsImageResourceProcessable`][] function to verify that an image can be processed.
+
+## Usage
+
+This versatile method supports the full range of image transformations including resizing, cropping, rotation, and format conversion within a single specification string. Unlike specialized methods such as [`Resize`][] or [`Crop`][], you must explicitly include the [action](#action) in the specification if you are changing the image dimensions.
+
+```go-html-template
+{{ with resources.Get "images/original.jpg" }}
+ {{ with .Process "crop 200x200 TopRight webp q50" }}
+
+ {{ end }}
+{{ end }}
+```
+
+In the example above, `"crop 200x200 TopRight webp q50"` is the processing specification.
+
+You can also use this method to apply simple transformations such as rotation and conversion:
+
+```go-html-template
+{{/* Rotate 90 degrees counter-clockwise. */}}
+{{ $image := $image.Process "r90" }}
+
+{{/* Convert to WebP. */}}
+{{ $image := $image.Process "webp" }}
+```
+
+The `Process` method is also available as a filter. This is more effective if you need to apply multiple filters to an image. See [`images.Process`][].
+
+{{% include "/_common/methods/resource/processing-spec.md" %}}
+
+## Example
+
+```go-html-template
+{{ with resources.Get "images/original.jpg" }}
+ {{ with .Process "crop 200x200 TopRight webp q50" }}
+
+ {{ end }}
+{{ end }}
+```
+
+{{< img
+ src="images/examples/zion-national-park.jpg"
+ alt="Zion National Park"
+ filter="Process"
+ filterArgs="crop 200x200 TopRight webp q50"
+ example=true
+>}}
+
+[`Crop`]: /methods/resource/crop/
+[`Resize`]: /methods/resource/resize/
+[`images.Process`]: /functions/images/process/
+[`reflect.IsImageResourceProcessable`]: /functions/reflect/isimageresourceprocessable/
+[processing specification]: #processing-specification
diff --git a/docs/content/en/methods/resource/Publish.md b/docs/content/en/methods/resource/Publish.md
new file mode 100644
index 00000000000..0ecdf7e7449
--- /dev/null
+++ b/docs/content/en/methods/resource/Publish.md
@@ -0,0 +1,32 @@
+---
+title: Publish
+description: Publishes the given resource.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: nil
+ signatures: [RESOURCE.Publish]
+---
+
+{{% include "/_common/methods/resource/global-page-remote-resources.md" %}}
+
+The `Publish` method on a `Resource` object writes the resource to the publish directory, typically `public`.
+
+```go-html-template
+{{ with resources.Get "images/a.jpg" }}
+ {{ .Publish }}
+{{ end }}
+```
+
+The `Permalink` and `RelPermalink` methods also publish a resource. `Publish` is a convenience method for publishing without a return value. For example, this:
+
+```go-html-template
+{{ $resource.Publish }}
+```
+
+Instead of this:
+
+```go-html-template
+{{ $noop := $resource.Permalink }}
+```
diff --git a/docs/content/en/methods/resource/RelPermalink.md b/docs/content/en/methods/resource/RelPermalink.md
new file mode 100644
index 00000000000..d4c907bff0c
--- /dev/null
+++ b/docs/content/en/methods/resource/RelPermalink.md
@@ -0,0 +1,20 @@
+---
+title: RelPermalink
+description: Publishes the given resource and returns its relative permalink.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [RESOURCE.RelPermalink]
+---
+
+{{% include "/_common/methods/resource/global-page-remote-resources.md" %}}
+
+The `Permalink` method on a `Resource` object writes the resource to the publish directory, typically `public`, and returns its [relative permalink](g).
+
+```go-html-template
+{{ with resources.Get "images/a.jpg" }}
+ {{ .RelPermalink }} → /images/a.jpg
+{{ end }}
+```
diff --git a/docs/content/en/methods/resource/Resize.md b/docs/content/en/methods/resource/Resize.md
new file mode 100644
index 00000000000..f1e08dbaf28
--- /dev/null
+++ b/docs/content/en/methods/resource/Resize.md
@@ -0,0 +1,56 @@
+---
+title: Resize
+description: Applicable to images, returns a new image resource resized according to the given processing specification.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: images.ImageResource
+ signatures: [RESOURCE.Resize SPECIFICATION]
+---
+
+{{% include "/_common/methods/resource/global-page-remote-resources.md" %}}
+
+The `Resize` method returns a new resource from a [processable image](g) according to the given [processing specification][].
+
+> [!note]
+> Use the [`reflect.IsImageResourceProcessable`][] function to verify that an image can be processed.
+
+## Usage
+
+Resize an image according to the given processing specification. You may specify only the width (such as `300x`) or only the height (such as `x150`) for proportional scaling.
+
+If you specify both width and height (such as `300x150`), the resulting image will be scaled to those exact dimensions. If the target aspect ratio differs from the original, the image will be non-proportionally scaled (stretched or squashed).
+
+```go-html-template
+{{ with resources.Get "images/original.jpg" }}
+ {{ with .Resize "300x" }}
+
+ {{ end }}
+{{ end }}
+```
+
+In the example above, `"300x"` is the processing specification.
+
+{{% include "/_common/methods/resource/processing-spec.md" %}}
+
+## Example
+
+```go-html-template
+{{ with resources.Get "images/original.jpg" }}
+ {{ with .Resize "300x" }}
+
+ {{ end }}
+{{ end }}
+```
+
+{{< img
+ src="images/examples/zion-national-park.jpg"
+ alt="Zion National Park"
+ filter="Process"
+ filterArgs="resize 300x"
+ example=true
+>}}
+
+[`reflect.IsImageResourceProcessable`]: /functions/reflect/isimageresourceprocessable/
+[processing specification]: #processing-specification
diff --git a/docs/content/en/methods/resource/ResourceType.md b/docs/content/en/methods/resource/ResourceType.md
new file mode 100644
index 00000000000..70dc5910848
--- /dev/null
+++ b/docs/content/en/methods/resource/ResourceType.md
@@ -0,0 +1,43 @@
+---
+title: ResourceType
+description: Returns the main type of the given resource's media type.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [RESOURCE.ResourceType]
+---
+
+{{% include "/_common/methods/resource/global-page-remote-resources.md" %}}
+
+Common resource types include `audio`, `image`, `text`, and `video`.
+
+```go-html-template
+{{ with resources.Get "image/a.jpg" }}
+ {{ .ResourceType }} → image
+ {{ .MediaType.MainType }} → image
+{{ end }}
+```
+
+When working with content files, the resource type is `page`.
+
+```text
+content/
+├── lessons/
+│ ├── lesson-1/
+│ │ ├── _objectives.md <-- resource type = page
+│ │ ├── _topics.md <-- resource type = page
+│ │ ├── _example.jpg <-- resource type = image
+│ │ └── index.md
+│ └── _index.md
+└── _index.md
+```
+
+With the structure above, we can range through page resources of type `page` to build content:
+
+```go-html-template {file="layouts/lessons/page.html"}
+{{ range .Resources.ByType "page" }}
+ {{ .Content }}
+{{ end }}
+```
diff --git a/docs/content/en/methods/resource/Title.md b/docs/content/en/methods/resource/Title.md
new file mode 100644
index 00000000000..c02d29ff8fd
--- /dev/null
+++ b/docs/content/en/methods/resource/Title.md
@@ -0,0 +1,81 @@
+---
+title: Title
+description: Returns the title of the given resource as optionally defined in front matter, falling back to a relative path or hashed file name depending on resource type.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [RESOURCE.Title]
+---
+
+The value returned by the `Title` method on a `Resource` object depends on the resource type.
+
+## Global resource
+
+With a [global resource](g), the `Title` method returns the path to the resource, relative to the `assets` directory.
+
+```text
+assets/
+└── images/
+ └── Sunrise in Bryce Canyon.jpg
+```
+
+```go-html-template
+{{ with resources.Get "images/Sunrise in Bryce Canyon.jpg" }}
+ {{ .Title }} → /images/Sunrise in Bryce Canyon.jpg
+{{ end }}
+```
+
+## Page resource
+
+With a [page resource](g), if you create an element in the `resources` array in front matter, the `Title` method returns the value of the `title` parameter.
+
+```text
+content/
+├── example/
+│ ├── images/
+│ │ └── a.jpg
+│ └── index.md
+└── _index.md
+```
+
+{{< code-toggle file=content/example/index.md fm=true >}}
+title = 'Example'
+[[resources]]
+src = 'images/a.jpg'
+title = 'A beautiful sunrise in Bryce Canyon'
+{{< /code-toggle >}}
+
+```go-html-template
+{{ with .Resources.Get "images/a.jpg" }}
+ {{ .Title }} → A beautiful sunrise in Bryce Canyon
+{{ end }}
+```
+
+If you do not create an element in the `resources` array in front matter, the `Title` method returns the file path, relative to the page bundle.
+
+```text
+content/
+├── example/
+│ ├── images/
+│ │ └── Sunrise in Bryce Canyon.jpg
+│ └── index.md
+└── _index.md
+```
+
+```go-html-template
+{{ with .Resources.Get "Sunrise in Bryce Canyon.jpg" }}
+ {{ .Title }} → images/Sunrise in Bryce Canyon.jpg
+{{ end }}
+```
+
+## Remote resource
+
+With a [remote resource](g), the `Title` method returns a hashed file name.
+
+```go-html-template
+{{ with resources.GetRemote "https://example.org/images/a.jpg" }}
+ {{ .Title }} → /a_18432433023265451104.jpg
+{{ end }}
+```
diff --git a/docs/content/en/methods/resource/Width.md b/docs/content/en/methods/resource/Width.md
new file mode 100644
index 00000000000..74eb373c450
--- /dev/null
+++ b/docs/content/en/methods/resource/Width.md
@@ -0,0 +1,26 @@
+---
+title: Width
+description: Applicable to images, returns the width of the given resource.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: int
+ signatures: [RESOURCE.Width]
+---
+
+{{% include "/_common/methods/resource/global-page-remote-resources.md" %}}
+
+Use the [`reflect.IsImageResourceWithMeta`][] function to verify that Hugo can determine the dimensions before calling the `Width` method.
+
+```go-html-template
+{{ with resources.GetMatch "images/featured.*" }}
+ {{ if reflect.IsImageResourceWithMeta . }}
+
+ {{ else }}
+
+ {{ end }}
+{{ end }}
+```
+
+[`reflect.IsImageResourceWithMeta`]: /functions/reflect/isimageresourcewithmeta/
diff --git a/docs/content/en/methods/resource/_index.md b/docs/content/en/methods/resource/_index.md
new file mode 100644
index 00000000000..c6012e75beb
--- /dev/null
+++ b/docs/content/en/methods/resource/_index.md
@@ -0,0 +1,7 @@
+---
+title: Resource methods
+linkTitle: Resource
+description: Use these methods with a global, page, or remote Resource object.
+categories: []
+keywords: []
+---
diff --git a/docs/content/en/methods/shortcode/Get.md b/docs/content/en/methods/shortcode/Get.md
new file mode 100644
index 00000000000..aef9987f098
--- /dev/null
+++ b/docs/content/en/methods/shortcode/Get.md
@@ -0,0 +1,46 @@
+---
+title: Get
+description: Returns the value of the given argument.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: any
+ signatures: [SHORTCODE.Get ARG]
+---
+
+Specify the argument by position or by name. When calling a shortcode within Markdown, use either positional or named argument, but not both.
+
+> [!note]
+> Some shortcodes support positional arguments, some support named arguments, and others support both. Refer to the shortcode's documentation for usage details.
+
+## Positional arguments
+
+This shortcode call uses positional arguments:
+
+```text {file="content/about.md"}
+{{* myshortcode "Hello" "world" */>}}
+```
+
+To retrieve arguments by position:
+
+```go-html-template {file="layouts/_shortcodes/myshortcode.html"}
+{{ printf "%s %s." (.Get 0) (.Get 1) }} → Hello world.
+```
+
+## Named arguments
+
+This shortcode call uses named arguments:
+
+```text {file="content/about.md"}
+{{* myshortcode greeting="Hello" firstName="world" */>}}
+```
+
+To retrieve arguments by name:
+
+```go-html-template {file="layouts/_shortcodes/myshortcode.html"}
+{{ printf "%s %s." (.Get "greeting") (.Get "firstName") }} → Hello world.
+```
+
+> [!note]
+> Argument names are case-sensitive.
diff --git a/docs/content/en/methods/shortcode/Inner.md b/docs/content/en/methods/shortcode/Inner.md
new file mode 100644
index 00000000000..bb5a16a0189
--- /dev/null
+++ b/docs/content/en/methods/shortcode/Inner.md
@@ -0,0 +1,143 @@
+---
+title: Inner
+description: Returns the content between opening and closing shortcode tags, applicable when the shortcode call includes a closing tag.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: template.HTML
+ signatures: [SHORTCODE.Inner]
+---
+
+This content:
+
+```text {file="content/services.md"}
+{{* card title="Product Design" */>}}
+We design the **best** widgets in the world.
+{{* /card */>}}
+```
+
+With this shortcode:
+
+```go-html-template {file="layouts/_shortcodes/card.html"}
+
+ {{ with .Get "title" }}
+
{{ . }}
+ {{ end }}
+
+ {{ .Inner | strings.TrimSpace }}
+
+
+```
+
+Is rendered to:
+
+```html
+
+
Product Design
+
+ We design the **best** widgets in the world.
+
+
+```
+
+> [!note]
+> Content between opening and closing shortcode tags may include leading and/or trailing newlines, depending on placement within the Markdown. Use the [`strings.TrimSpace`] function as shown above to remove carriage returns and newlines.
+
+> [!note]
+> In the example above, the value returned by `Inner` is Markdown, but it was rendered as plain text. Use either of the following approaches to render Markdown to HTML.
+
+## Use RenderString
+
+Let's modify the example above to pass the value returned by `Inner` through the [`RenderString`] method on the `Page` object:
+
+```go-html-template {file="layouts/_shortcodes/card.html"}
+
+```
+
+You can use the [`markdownify`] function instead of the `RenderString` method, but the latter is more flexible. See [details].
+
+## Alternative notation
+
+Instead of calling the shortcode with the `{{* */>}}` notation, use the `{{%/* */%}}` notation:
+
+```text {file="content/services.md"}
+{{%/* card title="Product Design" */%}}
+We design the **best** widgets in the world.
+{{%/* /card */%}}
+```
+
+When you use the `{{%/* */%}}` notation, Hugo renders the entire shortcode as Markdown, requiring the following changes.
+
+First, configure the renderer to allow raw HTML within Markdown:
+
+{{< code-toggle file=hugo >}}
+[markup.goldmark.renderer]
+unsafe = true
+{{< /code-toggle >}}
+
+This configuration is not unsafe if _you_ control the content. Read more about Hugo's [security model].
+
+Second, because we are rendering the entire shortcode as Markdown, we must adhere to the rules governing [indentation] and inclusion of [raw HTML blocks] as provided in the [CommonMark] specification.
+
+```go-html-template {file="layouts/_shortcodes/card.html"}
+
+ {{ with .Get "title" }}
+
{{ . }}
+ {{ end }}
+
+
+ {{ .Inner | strings.TrimSpace }}
+
+
+```
+
+The difference between this and the previous example is subtle but required. Note the change in indentation, the addition of a blank line, and removal of the `RenderString` method.
+
+```diff
+--- layouts/_shortcodes/a.html
++++ layouts/_shortcodes/b.html
+@@ -1,8 +1,9 @@
+
+```
+
+> [!note]
+> Don't process the `Inner` value with `RenderString` or `markdownify` when using [Markdown notation] to call the shortcode.
+
+[`markdownify`]: /functions/transform/markdownify/
+[`RenderString`]: /methods/page/renderstring/
+[`strings.TrimSpace`]: /functions/strings/trimspace/
+[CommonMark]: https://spec.commonmark.org/current/
+[details]: /methods/page/renderstring/
+[indentation]: https://spec.commonmark.org/current/#indented-code-blocks
+[Markdown notation]: /content-management/shortcodes/#notation
+[raw HTML blocks]: https://spec.commonmark.org/current/#html-blocks
+[security model]: /about/security/
diff --git a/docs/content/en/methods/shortcode/InnerDeindent.md b/docs/content/en/methods/shortcode/InnerDeindent.md
new file mode 100644
index 00000000000..3d7536b6b11
--- /dev/null
+++ b/docs/content/en/methods/shortcode/InnerDeindent.md
@@ -0,0 +1,98 @@
+---
+title: InnerDeindent
+description: Returns the content between opening and closing shortcode tags, with indentation removed, applicable when the shortcode call includes a closing tag.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: template.HTML
+ signatures: [SHORTCODE.InnerDeindent]
+---
+
+Similar to the [`Inner`] method, `InnerDeindent` returns the content between opening and closing shortcode tags. However, with `InnerDeindent`, indentation before the content is removed.
+
+This allows us to effectively bypass the rules governing [indentation] as provided in the [CommonMark] specification.
+
+Consider this Markdown, an unordered list with a small gallery of thumbnail images within each list item:
+
+```text {file="content/about.md"}
+- Gallery one
+
+ {{* gallery */>}}
+ 
+ 
+ {{* /gallery */>}}
+
+- Gallery two
+
+ {{* gallery */>}}
+ 
+ 
+ {{* /gallery */>}}
+```
+
+In the example above, notice that the content between the opening and closing shortcode tags is indented by four spaces. Per the CommonMark specification, this is treated as an indented code block.
+
+With this shortcode, calling `Inner` instead of `InnerDeindent`:
+
+```go-html-template {file="layouts/_shortcodes/gallery.html"}
+
+```
+
+Although technically correct per the CommonMark specification, this is not what we want. If we remove the indentation using the `InnerDeindent` method:
+
+```go-html-template {file="layouts/_shortcodes/gallery.html"}
+
+```
+
+Hugo renders the Markdown to:
+
+```html
+
+
+
Gallery one
+
+
+
+
+
+
+
Gallery two
+
+
+
+
+
+
+```
+
+[commonmark]: https://commonmark.org/
+[indentation]: https://spec.commonmark.org/current/#indented-code-blocks
+[`Inner`]: /methods/shortcode/inner/
diff --git a/docs/content/en/methods/shortcode/IsNamedParams.md b/docs/content/en/methods/shortcode/IsNamedParams.md
new file mode 100644
index 00000000000..0574128ece5
--- /dev/null
+++ b/docs/content/en/methods/shortcode/IsNamedParams.md
@@ -0,0 +1,29 @@
+---
+title: IsNamedParams
+description: Reports whether the shortcode call uses named arguments.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: bool
+ signatures: [SHORTCODE.IsNamedParams]
+---
+
+To support both positional and named arguments when calling a shortcode, use the `IsNamedParams` method to determine how the shortcode was called.
+
+With this _shortcode_ template:
+
+```go-html-template {file="layouts/_shortcodes/myshortcode.html"}
+{{ if .IsNamedParams }}
+ {{ printf "%s %s." (.Get "greeting") (.Get "firstName") }}
+{{ else }}
+ {{ printf "%s %s." (.Get 0) (.Get 1) }}
+{{ end }}
+```
+
+Both of these calls return the same value:
+
+```text {file="content/about.md"}
+{{* myshortcode greeting="Hello" firstName="world" */>}}
+{{* myshortcode "Hello" "world" */>}}
+```
diff --git a/docs/content/en/methods/shortcode/Name.md b/docs/content/en/methods/shortcode/Name.md
new file mode 100644
index 00000000000..c42513cb290
--- /dev/null
+++ b/docs/content/en/methods/shortcode/Name.md
@@ -0,0 +1,27 @@
+---
+title: Name
+description: Returns the shortcode file name, excluding the file extension.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [SHORTCODE.Name]
+---
+
+The `Name` method is useful for error reporting. For example, if your shortcode requires a "greeting" argument:
+
+```go-html-template {file="layouts/_shortcodes/myshortcode.html"}
+{{ $greeting := "" }}
+{{ with .Get "greeting" }}
+ {{ $greeting = . }}
+{{ else }}
+ {{ errorf "The %q shortcode requires a 'greeting' argument. See %s" .Name .Position }}
+{{ end }}
+```
+
+In the absence of a "greeting" argument, Hugo will throw an error message and fail the build:
+
+```text
+ERROR The "myshortcode" shortcode requires a 'greeting' argument. See "/home/user/project/content/about.md:11:1"
+```
diff --git a/docs/content/en/methods/shortcode/Ordinal.md b/docs/content/en/methods/shortcode/Ordinal.md
new file mode 100644
index 00000000000..b1675895254
--- /dev/null
+++ b/docs/content/en/methods/shortcode/Ordinal.md
@@ -0,0 +1,52 @@
+---
+title: Ordinal
+description: Returns the zero-based ordinal of the shortcode in relation to its parent.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: int
+ signatures: [SHORTCODE.Ordinal]
+---
+
+The `Ordinal` method returns the zero-based ordinal of the shortcode in relation to its parent. If the parent is the page itself, the ordinal represents the position of this shortcode in the page content.
+
+> [!note]
+> Hugo increments the ordinal with each shortcode call, regardless of the specific shortcode type. This means that the ordinal value is tracked sequentially across all shortcodes within a given page.
+
+This method is useful for, among other things, assigning unique element IDs when a shortcode is called two or more times from the same page. For example:
+
+```text {file="content/about.md"}
+{{* img src="images/a.jpg" */>}}
+
+{{* img src="images/b.jpg" */>}}
+```
+
+This shortcode performs error checking, then renders an HTML `img` element with a unique `id` attribute:
+
+```go-html-template {file="layouts/_shortcodes/img.html"}
+{{ $src := "" }}
+{{ with .Get "src" }}
+ {{ $src = . }}
+ {{ with resources.Get $src }}
+ {{ $id := printf "img-%03d" $.Ordinal }}
+
+ {{ else }}
+ {{ errorf "The %q shortcode was unable to find %s. See %s" $.Name $src $.Position }}
+ {{ end }}
+{{ else }}
+ {{ errorf "The %q shortcode requires a 'src' argument. See %s" .Name .Position }}
+{{ end }}
+```
+
+Hugo renders the page to:
+
+```html
+
+
+```
+
+> [!note]
+> In the _shortcode_ template above, the [`with`] statement is used to create conditional blocks. Remember that the `with` statement binds context (the dot) to its expression. Inside of a `with` block, preface shortcode method calls with a `$` to access the top-level context passed into the template.
+
+[`with`]: /functions/go-template/with/
diff --git a/docs/content/en/methods/shortcode/Page.md b/docs/content/en/methods/shortcode/Page.md
new file mode 100644
index 00000000000..0fa1c9cc9b0
--- /dev/null
+++ b/docs/content/en/methods/shortcode/Page.md
@@ -0,0 +1,36 @@
+---
+title: Page
+description: Returns the Page object from which the shortcode was called.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: hugolib.pageForShortcode
+ signatures: [SHORTCODE.Page]
+---
+
+With this content:
+
+{{< code-toggle file=content/books/les-miserables.md fm=true >}}
+title = 'Les Misérables'
+author = 'Victor Hugo'
+publication_year = 1862
+isbn = '978-0451419439'
+{{< /code-toggle >}}
+
+Calling this shortcode:
+
+```text
+{{* book-details */>}}
+```
+
+We can access the front matter values using the `Page` method:
+
+```go-html-template {file="layouts/_shortcodes/book-details.html"}
+
+
Title: {{ .Page.Title }}
+
Author: {{ .Page.Params.author }}
+
Published: {{ .Page.Params.publication_year }}
+
ISBN: {{ .Page.Params.isbn }}
+
+```
diff --git a/docs/content/en/methods/shortcode/Params.md b/docs/content/en/methods/shortcode/Params.md
new file mode 100644
index 00000000000..c8252f5b151
--- /dev/null
+++ b/docs/content/en/methods/shortcode/Params.md
@@ -0,0 +1,32 @@
+---
+title: Params
+description: Returns a collection of the shortcode arguments.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: any
+ signatures: [SHORTCODE.Params]
+---
+
+When you call a shortcode using positional arguments, the `Params` method returns a slice.
+
+```text {file="content/about.md"}
+{{* myshortcode "Hello" "world" */>}}
+```
+
+```go-html-template {file="layouts/_shortcodes/myshortcode.html"}
+{{ index .Params 0 }} → Hello
+{{ index .Params 1 }} → world
+```
+
+When you call a shortcode using named arguments, the `Params` method returns a map.
+
+```text {file="content/about.md"}
+{{* myshortcode greeting="Hello" name="world" */>}}
+```
+
+```go-html-template {file="layouts/_shortcodes/myshortcode.html"}
+{{ .Params.greeting }} → Hello
+{{ .Params.name }} → world
+```
diff --git a/docs/content/en/methods/shortcode/Parent.md b/docs/content/en/methods/shortcode/Parent.md
new file mode 100644
index 00000000000..4597d1034f3
--- /dev/null
+++ b/docs/content/en/methods/shortcode/Parent.md
@@ -0,0 +1,50 @@
+---
+title: Parent
+description: Returns the parent shortcode context in nested shortcodes.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: hugolib.ShortcodeWithPage
+ signatures: [SHORTCODE.Parent]
+---
+
+This is useful for inheritance of common shortcode arguments from the root.
+
+In this contrived example, the "greeting" shortcode is the parent, and the "now" shortcode is child.
+
+```text {file="content/welcome.md"}
+{{* greeting dateFormat="Jan 2, 2006" */>}}
+Welcome. Today is {{* now */>}}.
+{{* /greeting */>}}
+```
+
+```go-html-template {file="layouts/_shortcodes/greeting.html"}
+
+```
+
+To get a regular page instead of a section page:
+
+```go-html-template {file="layouts/home.html"}
+{{ with .Site.GetPage "/works/paintings/starry-night" }}
+ {{ .Title }} → Starry Night
+ {{ .Params.artist }} → Vincent van Gogh
+{{ end }}
+```
+
+## Multilingual projects
+
+With multilingual projects, the `GetPage` method on a `Site` object resolves the given path to a page in the current language.
+
+To get a page from a different language, query the `Sites` object:
+
+```go-html-template
+{{ with where hugo.Sites "Language.Name" "eq" "de" }}
+ {{ with index . 0 }}
+ {{ with .GetPage "/works/paintings/starry-night" }}
+ {{ .Title }} → Sternenklare Nacht
+ {{ end }}
+ {{ end }}
+{{ end }}
+```
+
+## Page bundles
+
+Consider this content structure:
+
+```text
+content/
+├── headless/
+│ ├── a.jpg
+│ ├── b.jpg
+│ ├── c.jpg
+│ └── index.md <-- front matter: headless = true
+└── _index.md
+```
+
+In the _home_ template, use the `GetPage` method on a `Site` object to render all the images in the headless [page bundle](g):
+
+```go-html-template {file="layouts/home.html"}
+{{ with .Site.GetPage "/headless" }}
+ {{ range .Resources.ByType "image" }}
+
+ {{ end }}
+{{ end }}
+```
diff --git a/docs/content/en/methods/site/Home.md b/docs/content/en/methods/site/Home.md
new file mode 100644
index 00000000000..57b2ceaae6d
--- /dev/null
+++ b/docs/content/en/methods/site/Home.md
@@ -0,0 +1,39 @@
+---
+title: Home
+description: Returns the home Page object for the given site.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Page
+ signatures: [SITE.Home]
+---
+
+The `Home` method on a `Site` object is a convenient way to access the home page, and is functionally equivalent to:
+
+```go-html-template
+{{ .Site.GetPage "/" }}
+```
+
+Because it returns a `Page` object, you can use any of the available [page methods][] by chaining them. For example:
+
+```go-html-template
+{{ .Site.Home.Store.Set "greeting" "Hello" }}
+```
+
+This method is commonly used to generate a link to the home page. For example:
+
+Project configuration:
+
+{{< code-toggle file=hugo >}}
+baseURL = 'https://example.org/docs/'
+{{< /code-toggle >}}
+
+Template:
+
+```go-html-template
+{{ .Site.Home.Permalink }} → https://example.org/docs/
+{{ .Site.Home.RelPermalink }} → /docs/
+```
+
+[page methods]: /methods/page/
diff --git a/docs/content/en/methods/site/IsDefault.md b/docs/content/en/methods/site/IsDefault.md
new file mode 100644
index 00000000000..543bc4f5249
--- /dev/null
+++ b/docs/content/en/methods/site/IsDefault.md
@@ -0,0 +1,50 @@
+---
+title: IsDefault
+description: Reports whether the given site is the default site across all dimensions.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: bool
+ signatures: [SITE.IsDefault]
+---
+
+{{< new-in 0.156.0 />}}
+
+The `IsDefault` method on a `Site` object reports whether the given site is the [default site](g) across all dimensions: [language](g), [version](g), and [role](g). This is useful to ensure that a block of code executes only once per build, regardless of the number of [sites](g) generated by your [dimensions](g).
+
+For example, the following configuration defines a matrix of sites across language and version dimensions.
+
+{{< code-toggle file=hugo >}}
+[languages.de]
+contentDir = 'content/de'
+direction = 'ltr'
+label = 'Deutsch'
+locale = 'de-DE'
+title = 'Projekt Dokumentation'
+weight = 1
+
+[languages.en]
+contentDir = 'content/en'
+direction = 'ltr'
+label = 'English'
+locale = 'en-US'
+title = 'Project Documentation'
+weight = 2
+
+[versions.'v1.0.0']
+[versions.'v2.0.0']
+[versions.'v3.0.0']
+{{< /code-toggle >}}
+
+If you call an initialization partial to handle one-time build logic or global variable setup, wrap that call in an [`if`][] statement using this function. This prevents the logic from being executed for every dimensional variation.
+
+```go-html-template
+{{ if .Site.IsDefault }}
+ {{ partial "init.html" . }}
+{{ end }}
+```
+
+In this setup, the code block is only executed for the English version v3.0.0 site. English is selected because it has the lowest weight, and version v3.0.0 is selected because it is the first version when sorted semantically in descending order.
+
+[`if`]: /functions/go-template/if/
diff --git a/docs/content/en/methods/site/Language.md b/docs/content/en/methods/site/Language.md
new file mode 100644
index 00000000000..6f2f013e7f1
--- /dev/null
+++ b/docs/content/en/methods/site/Language.md
@@ -0,0 +1,122 @@
+---
+title: Language
+description: Returns the Language object for the given site.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: langs.Language
+ signatures: [SITE.Language]
+---
+
+The `Language` method on a `Site` object returns the `Language` object for the given site, derived from the language definition in your project configuration.
+
+You can also use the `Language` method on a `Page` object. See [details][].
+
+## Methods
+
+The examples below assume the following language definition.
+
+{{< code-toggle file=hugo >}}
+[languages.de]
+direction = 'ltr'
+label = 'Deutsch'
+locale = 'de-DE'
+weight = 2
+{{< /code-toggle >}}
+
+### Direction
+
+{{< new-in 0.158.0 />}}
+
+(`string`) Returns the [`direction`][] from the language definition.
+
+```go-html-template
+{{ .Site.Language.Direction }} → ltr
+```
+
+### IsDefault
+
+{{< new-in 0.153.0 />}}
+
+(`bool`) Reports whether this is the [default language][].
+
+```go-html-template
+{{ .Site.Language.IsDefault }} → true
+```
+
+### Label
+
+{{< new-in 0.158.0 />}}
+
+(`string`) Returns the [`label`][] from the language definition.
+
+```go-html-template
+{{ .Site.Language.Label }} → Deutsch
+```
+
+### Lang
+
+{{}}
+
+Use [`Name`](#name) instead.
+
+### LanguageCode
+
+{{}}
+
+Use [`Locale`](#locale) instead.
+
+### LanguageDirection
+
+{{}}
+
+Use [`Direction`](#direction) instead.
+
+### LanguageName
+
+{{}}
+
+Use [`Label`](#label) instead.
+
+### Locale
+
+{{< new-in 0.158.0 />}}
+
+(`string`) Returns the [`locale`][] from the language definition, falling back to [`Name`](#name).
+
+```go-html-template
+{{ .Site.Language.Locale }} → de-DE
+```
+
+### Name
+
+{{< new-in 0.153.0 />}}
+
+(`string`) Returns the language tag as defined by [RFC 5646][]. This is the lowercased key from the language definition.
+
+```go-html-template
+{{ .Site.Language.Name }} → de
+```
+
+### Weight
+
+{{}}
+
+## Example
+
+Some of the methods above are commonly used in a base template as attributes for the `html` element.
+
+```go-html-template
+
+```
+
+[RFC 5646]: https://datatracker.ietf.org/doc/html/rfc5646
+[`direction`]: /configuration/languages/#direction
+[`label`]: /configuration/languages/#label
+[`locale`]: /configuration/languages/#locale
+[default language]: /quick-reference/glossary/#default-language
+[details]: /methods/page/language/
diff --git a/docs/content/en/methods/site/LanguagePrefix.md b/docs/content/en/methods/site/LanguagePrefix.md
new file mode 100644
index 00000000000..59e7d3aeb84
--- /dev/null
+++ b/docs/content/en/methods/site/LanguagePrefix.md
@@ -0,0 +1,51 @@
+---
+title: LanguagePrefix
+description: Returns the URL language prefix, if any, for the given site.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [SITE.LanguagePrefix]
+---
+
+Consider this project configuration:
+
+{{< code-toggle file=hugo >}}
+defaultContentLanguage = 'de'
+defaultContentLanguageInSubdir = false
+
+[languages.de]
+direction = 'ltr'
+label = 'Deutsch'
+locale = 'de-DE'
+title = 'Projekt Dokumentation'
+weight = 1
+
+[languages.en]
+direction = 'ltr'
+label = 'English'
+locale = 'en-US'
+title = 'Project Documentation'
+weight = 2
+{{< /code-toggle >}}
+
+When visiting the German language site:
+
+```go-html-template
+{{ .Site.LanguagePrefix }} → ""
+```
+
+When visiting the English language site:
+
+```go-html-template
+{{ .Site.LanguagePrefix }} → /en
+```
+
+If you change `defaultContentLanguageInSubdir` to `true`, when visiting the German language site:
+
+```go-html-template
+{{ .Site.LanguagePrefix }} → /de
+```
+
+You may use the `LanguagePrefix` method with both monolingual and multilingual projects.
diff --git a/docs/content/en/methods/site/Languages.md b/docs/content/en/methods/site/Languages.md
new file mode 100644
index 00000000000..fa51f1e5147
--- /dev/null
+++ b/docs/content/en/methods/site/Languages.md
@@ -0,0 +1,15 @@
+---
+title: Languages
+description: Returns a collection of language objects for all sites, ordered by language weight.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: langs.Languages
+ signatures: [SITE.Languages]
+expiryDate: '2028-02-18' # deprecated 2026-02-18 in v0.156.0
+---
+
+{{< deprecated-in 0.156.0 >}}
+See [details](https://discourse.gohugo.io/t/56732).
+{{< /deprecated-in >}}
diff --git a/docs/content/en/methods/site/Lastmod.md b/docs/content/en/methods/site/Lastmod.md
new file mode 100644
index 00000000000..2dec75001ca
--- /dev/null
+++ b/docs/content/en/methods/site/Lastmod.md
@@ -0,0 +1,21 @@
+---
+title: Lastmod
+description: Returns the last modification date of site content.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: time.Time
+ signatures: [SITE.Lastmod]
+---
+
+The `Lastmod` method on a `Site` object returns a [`time.Time`] value. Use this with time [functions] and [methods]. For example:
+
+```go-html-template
+{{ .Site.Lastmod | time.Format ":date_long" }} → January 31, 2024
+
+```
+
+[`time.Time`]: https://pkg.go.dev/time#Time
+[functions]: /functions/time/
+[methods]: /methods/time/
diff --git a/docs/content/en/methods/site/MainSections.md b/docs/content/en/methods/site/MainSections.md
new file mode 100644
index 00000000000..0df8de649e3
--- /dev/null
+++ b/docs/content/en/methods/site/MainSections.md
@@ -0,0 +1,54 @@
+---
+title: MainSections
+description: Returns a slice of the main section names as defined in your project configuration, falling back to the top-level section with the most pages.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: '[]string'
+ signatures: [SITE.MainSections]
+---
+
+Project configuration:
+
+{{< code-toggle file=hugo >}}
+mainSections = ['books','films']
+{{< /code-toggle >}}
+
+Template:
+
+```go-html-template
+{{ .Site.MainSections }} → [books films]
+```
+
+If `mainSections` is not defined in your project configuration, this method returns a slice with one element---the top-level section with the most pages.
+
+With this content structure, the "films" section has the most pages:
+
+```text
+content/
+├── books/
+│ ├── book-1.md
+│ └── book-2.md
+├── films/
+│ ├── film-1.md
+│ ├── film-2.md
+│ └── film-3.md
+└── _index.md
+```
+
+Template:
+
+```go-html-template
+{{ .Site.MainSections }} → [films]
+```
+
+When creating a theme, instead of hardcoding section names when listing the most relevant pages on the front page, instruct users to set `mainSections` in their project configuration.
+
+Then your _home_ template can do something like this:
+
+```go-html-template {file="layouts/home.html"}
+{{ range where .Site.RegularPages "Section" "in" .Site.MainSections }}
+
+{{ end }}
+```
diff --git a/docs/content/en/methods/site/Menus.md b/docs/content/en/methods/site/Menus.md
new file mode 100644
index 00000000000..0c8509e92ae
--- /dev/null
+++ b/docs/content/en/methods/site/Menus.md
@@ -0,0 +1,89 @@
+---
+title: Menus
+description: Returns a collection of menu objects for the given site.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: navigation.Menus
+ signatures: [SITE.Menus]
+---
+
+The `Menus` method on a `Site` object returns a collection of menus, where each menu contains one or more entries, either flat or nested. Each entry points to a page within the site, or to an external resource.
+
+> [!note]
+> Menus can be defined and localized in several ways. Please see the [menus] section for a complete explanation and examples.
+
+A site can have multiple menus. For example, a main menu and a footer menu:
+
+{{< code-toggle file=hugo >}}
+[[menus.main]]
+name = 'Home'
+pageRef = '/'
+weight = 10
+
+[[menus.main]]
+name = 'Books'
+pageRef = '/books'
+weight = 20
+
+[[menus.main]]
+name = 'Films'
+pageRef = '/films'
+weight = 30
+
+[[menus.footer]]
+name = 'Legal'
+pageRef = '/legal'
+weight = 10
+
+[[menus.footer]]
+name = 'Privacy'
+pageRef = '/privacy'
+weight = 20
+{{< /code-toggle >}}
+
+This template renders the main menu:
+
+```go-html-template
+{{ with site.Menus.main }}
+
+{{ end }}
+```
+
+When viewing the home page, the result is:
+
+```html
+
+```
+
+When viewing the "books" page, the result is:
+
+```html
+
+```
+
+You will typically render a menu using a _partial_ template. As the active menu entry will be different on each page, use the [`partial`] function to call the template. Do not use the [`partialCached`] function.
+
+The example above is simplistic. Please see the [menu templates] section for more information.
+
+[`partial`]: /functions/partials/include/
+[`partialCached`]: /functions/partials/includecached/
+[menu templates]: /templates/menu/
+[menus]: /content-management/menus/
diff --git a/docs/content/en/methods/site/Pages.md b/docs/content/en/methods/site/Pages.md
new file mode 100644
index 00000000000..a6ba5e02923
--- /dev/null
+++ b/docs/content/en/methods/site/Pages.md
@@ -0,0 +1,22 @@
+---
+title: Pages
+description: Returns a collection of all pages.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Pages
+ signatures: [SITE.Pages]
+---
+
+This method returns all page [kinds](g) in the current language, in the [default sort order](g). That includes the home page, section pages, taxonomy pages, term pages, and regular pages.
+
+In most cases you should use the [`RegularPages`] method instead.
+
+[`RegularPages`]: /methods/site/regularpages/
+
+```go-html-template
+{{ range .Site.Pages }}
+
+{{ end }}
+```
diff --git a/docs/content/en/methods/site/Param.md b/docs/content/en/methods/site/Param.md
new file mode 100644
index 00000000000..b2ec5d9bbb0
--- /dev/null
+++ b/docs/content/en/methods/site/Param.md
@@ -0,0 +1,28 @@
+---
+title: Param
+description: Returns the site parameter with the given key.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: any
+ signatures: [SITE.Param KEY]
+---
+
+The `Param` method on a `Site` object is a convenience method to return the value of a user-defined parameter in your project configuration.
+
+{{< code-toggle file=hugo >}}
+[params]
+display_toc = true
+{{< /code-toggle >}}
+
+```go-html-template
+{{ .Site.Param "display_toc" }} → true
+```
+
+The above is equivalent to either of these:
+
+```go-html-template
+{{ .Site.Params.display_toc }}
+{{ index .Site.Params "display_toc" }}
+```
diff --git a/docs/content/en/methods/site/Params.md b/docs/content/en/methods/site/Params.md
new file mode 100644
index 00000000000..62bc8f1f5c3
--- /dev/null
+++ b/docs/content/en/methods/site/Params.md
@@ -0,0 +1,42 @@
+---
+title: Params
+description: Returns a map of custom parameters as defined in your project configuration.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: maps.Params
+ signatures: [SITE.Params]
+---
+
+With this project configuration:
+
+{{< code-toggle file=hugo >}}
+[params]
+ subtitle = 'The Best Widgets on Earth'
+ copyright-year = '2023'
+ [params.author]
+ email = 'jsmith@example.org'
+ name = 'John Smith'
+ [params.layouts]
+ rfc_1123 = 'Mon, 02 Jan 2006 15:04:05 MST'
+ rfc_3339 = '2006-01-02T15:04:05-07:00'
+{{< /code-toggle >}}
+
+Access the custom parameters by [chaining](g) the [identifiers](g):
+
+```go-html-template
+{{ .Site.Params.subtitle }} → The Best Widgets on Earth
+{{ .Site.Params.author.name }} → John Smith
+
+{{ $layout := .Site.Params.layouts.rfc_1123 }}
+{{ .Site.Lastmod.Format $layout }} → Tue, 17 Oct 2023 13:21:02 PDT
+```
+
+In the template example above, each of the keys is a valid identifier. For example, none of the keys contains a hyphen. To access a key that is not a valid identifier, use the [`index`] function:
+
+```go-html-template
+{{ index .Site.Params "copyright-year" }} → 2023
+```
+
+[`index`]: /functions/collections/indexfunction/
diff --git a/docs/content/en/methods/site/RegularPages.md b/docs/content/en/methods/site/RegularPages.md
new file mode 100644
index 00000000000..69a460529e6
--- /dev/null
+++ b/docs/content/en/methods/site/RegularPages.md
@@ -0,0 +1,32 @@
+---
+title: RegularPages
+description: Returns a collection of all regular pages.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Pages
+ signatures: [SITE.RegularPages]
+---
+
+The `RegularPages` method on a `Site` object returns a collection of all [regular pages](g), in the [default sort order](g).
+
+```go-html-template
+{{ range .Site.RegularPages }}
+
+{{ end }}
+```
+
+{{% glossary-term "default sort order" %}}
+
+[default sort order](g)
+
+To change the sort order, use any of the `Pages` [sorting methods]. For example:
+
+```go-html-template
+{{ range .Site.RegularPages.ByTitle }}
+
+```
diff --git a/docs/content/en/methods/site/Sites.md b/docs/content/en/methods/site/Sites.md
new file mode 100644
index 00000000000..ef7ff8bbdde
--- /dev/null
+++ b/docs/content/en/methods/site/Sites.md
@@ -0,0 +1,15 @@
+---
+title: Sites
+description: Returns a collection of all sites for all dimensions.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Sites
+ signatures: [SITE.Sites]
+expiryDate: '2028-02-18' # deprecated 2026-02-18 in v0.156.0
+---
+
+{{< deprecated-in 0.156.0 >}}
+Use [`hugo.Sites`](/functions/hugo/sites/) instead.
+{{< /deprecated-in >}}
diff --git a/docs/content/en/methods/site/Store.md b/docs/content/en/methods/site/Store.md
new file mode 100644
index 00000000000..6c6d57a900f
--- /dev/null
+++ b/docs/content/en/methods/site/Store.md
@@ -0,0 +1,117 @@
+---
+title: Store
+description: Returns a "scratch pad" to store and manipulate data, scoped to the current site.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: maps.Scratch
+ signatures: [site.Store]
+---
+
+{{< new-in 0.139.0 />}}
+
+Use the `Store` method on a `Site` object to create a [scratch pad](g) to store and manipulate data, scoped to the current site. To create a scratch pad with a different [scope](g), refer to the [scope](#scope) section below.
+
+## Methods
+
+### Set
+
+Sets the value of a given key.
+
+```go-html-template
+{{ site.Store.Set "greeting" "Hello" }}
+```
+
+### Get
+
+Gets the value of a given key.
+
+```go-html-template
+{{ site.Store.Set "greeting" "Hello" }}
+{{ site.Store.Get "greeting" }} → Hello
+```
+
+### Add
+
+Adds a given value to existing value(s) of the given key.
+
+For single values, `Add` accepts values that support Go's `+` operator. If the first `Add` for a key is an array or slice, the following adds will be appended to that list.
+
+```go-html-template
+{{ site.Store.Set "greeting" "Hello" }}
+{{ site.Store.Add "greeting" "Welcome" }}
+{{ site.Store.Get "greeting" }} → HelloWelcome
+```
+
+```go-html-template
+{{ site.Store.Set "total" 3 }}
+{{ site.Store.Add "total" 7 }}
+{{ site.Store.Get "total" }} → 10
+```
+
+```go-html-template
+{{ site.Store.Set "greetings" (slice "Hello") }}
+{{ site.Store.Add "greetings" (slice "Welcome" "Cheers") }}
+{{ site.Store.Get "greetings" }} → [Hello Welcome Cheers]
+ ```
+
+### SetInMap
+
+Takes a `key`, `mapKey` and `value` and adds a map of `mapKey` and `value` to the given `key`.
+
+```go-html-template
+{{ site.Store.SetInMap "greetings" "english" "Hello" }}
+{{ site.Store.SetInMap "greetings" "french" "Bonjour" }}
+{{ site.Store.Get "greetings" }} → map[english:Hello french:Bonjour]
+```
+
+### DeleteInMap
+
+Takes a `key` and `mapKey` and removes the map of `mapKey` from the given `key`.
+
+```go-html-template
+{{ site.Store.SetInMap "greetings" "english" "Hello" }}
+{{ site.Store.SetInMap "greetings" "french" "Bonjour" }}
+{{ site.Store.DeleteInMap "greetings" "english" }}
+{{ site.Store.Get "greetings" }} → map[french:Bonjour]
+```
+
+### GetSortedMapValues
+
+Returns an array of values from `key` sorted by `mapKey`.
+
+```go-html-template
+{{ site.Store.SetInMap "greetings" "english" "Hello" }}
+{{ site.Store.SetInMap "greetings" "french" "Bonjour" }}
+{{ site.Store.GetSortedMapValues "greetings" }} → [Hello Bonjour]
+```
+
+### Delete
+
+Removes the given key.
+
+```go-html-template
+{{ site.Store.Set "greeting" "Hello" }}
+{{ site.Store.Delete "greeting" }}
+```
+
+{{% include "_common/scratch-pad-scope.md" %}}
+
+## Determinate values
+
+The `Store` method is often used to set scratch pad values within a _shortcode_ template, a _partial_ template called by a _shortcode_ template, or by a _render hook_ template. In all three cases, the scratch pad values are indeterminate until Hugo renders the page content.
+
+If you need to access a scratch pad value from a parent template, and the parent template has not yet rendered the page content, you can trigger content rendering by assigning the returned value to a [noop](g) variable:
+
+```go-html-template
+{{ $noop := .Content }}
+{{ site.Store.Get "mykey" }}
+```
+
+You can also trigger content rendering with the `ContentWithoutSummary`, `FuzzyWordCount`, `Len`, `Plain`, `PlainWords`, `ReadingTime`, `Summary`, `Truncated`, and `WordCount` methods. For example:
+
+```go-html-template
+{{ $noop := .WordCount }}
+{{ site.Store.Get "mykey" }}
+```
diff --git a/docs/content/en/methods/site/Taxonomies.md b/docs/content/en/methods/site/Taxonomies.md
new file mode 100644
index 00000000000..4417c9ef735
--- /dev/null
+++ b/docs/content/en/methods/site/Taxonomies.md
@@ -0,0 +1,181 @@
+---
+title: Taxonomies
+description: Returns a data structure containing the site's Taxonomy objects, the terms within each Taxonomy object, and the pages to which the terms are assigned.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.TaxonomyList
+ signatures: [SITE.Taxonomies]
+---
+
+Conceptually, the `Taxonomies` method on a `Site` object returns a data structure such as:
+
+
+{{< code-toggle file=hugo >}}
+taxonomy a:
+ - term 1:
+ - page 1
+ - page 2
+ - term 2:
+ - page 1
+taxonomy b:
+ - term 1:
+ - page 2
+ - term 2:
+ - page 1
+ - page 2
+{{< /code-toggle >}}
+
+
+For example, on a book review site you might create two taxonomies; one for genres and another for authors.
+
+With this project configuration:
+
+{{< code-toggle file=hugo >}}
+[taxonomies]
+genre = 'genres'
+author = 'authors'
+{{< /code-toggle >}}
+
+And this content structure:
+
+```text
+content/
+├── books/
+│ ├── and-then-there-were-none.md --> genres: suspense
+│ ├── death-on-the-nile.md --> genres: suspense
+│ └── jamaica-inn.md --> genres: suspense, romance
+│ └── pride-and-prejudice.md --> genres: romance
+└── _index.md
+```
+
+Conceptually, the taxonomies data structure looks like:
+
+
+{{< code-toggle file=hugo >}}
+genres:
+ - suspense:
+ - And Then There Were None
+ - Death on the Nile
+ - Jamaica Inn
+ - romance:
+ - Jamaica Inn
+ - Pride and Prejudice
+authors:
+ - achristie:
+ - And Then There Were None
+ - Death on the Nile
+ - ddmaurier:
+ - Jamaica Inn
+ - jausten:
+ - Pride and Prejudice
+{{< /code-toggle >}}
+
+
+To list the "suspense" books:
+
+```go-html-template
+
+```
+
+> [!note]
+> Hugo's taxonomy system is powerful, allowing you to classify content and create relationships between pages.
+>
+> Please see the [taxonomies] section for a complete explanation and examples.
+
+## Examples
+
+### List content with the same taxonomy term
+
+If you are using a taxonomy for something like a series of posts, you can list individual pages associated with the same term. For example:
+
+```go-html-template
+
+```
+
+### List all content in a given taxonomy
+
+This would be very useful in a sidebar as “featured content”. You could even have different sections of “featured content” by assigning different terms to the content.
+
+```go-html-template
+
+
+ {{ range $term, $taxonomy := .Site.Taxonomies.featured }}
+
+```
+
+This example will list all taxonomies and their terms, as well as all the content assigned to each of the terms.
+
+```go-html-template {file="layouts/_partials/all-taxonomies.html"}
+{{ with .Site.Taxonomies }}
+ {{ $numberOfTerms := 0 }}
+ {{ range $taxonomy, $terms := . }}
+ {{ $numberOfTerms = len . | add $numberOfTerms }}
+ {{ end }}
+
+ {{ if gt $numberOfTerms 0 }}
+
+ {{ range $taxonomy, $terms := . }}
+ {{ with $terms }}
+
+ {{ end }}
+{{ end }}
+```
+
+[taxonomies]: /content-management/taxonomies/
diff --git a/docs/content/en/methods/site/Title.md b/docs/content/en/methods/site/Title.md
new file mode 100644
index 00000000000..ad8a48c3cbc
--- /dev/null
+++ b/docs/content/en/methods/site/Title.md
@@ -0,0 +1,22 @@
+---
+title: Title
+description: Returns the title as defined in your project configuration.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [SITE.Title]
+---
+
+Project configuration:
+
+{{< code-toggle file=hugo >}}
+title = 'My Documentation Site'
+{{< /code-toggle >}}
+
+Template:
+
+```go-html-template
+{{ .Site.Title }} → My Documentation Site
+```
diff --git a/docs/content/en/methods/site/Version.md b/docs/content/en/methods/site/Version.md
new file mode 100644
index 00000000000..d424d4666ed
--- /dev/null
+++ b/docs/content/en/methods/site/Version.md
@@ -0,0 +1,34 @@
+---
+title: Version
+description: Returns the Version object for the given site.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: versions.Version
+ signatures: [SITE.Version]
+---
+
+{{< new-in 0.153.0 />}}
+
+The `Version` method on a `Site` object returns the `Version` object for the given site, derived from the version definition in your project configuration.
+
+## Methods
+
+### IsDefault
+
+(`bool`) Reports whether this is the [default version][].
+
+```go-html-template
+{{ .Site.Version.IsDefault }} → true
+```
+
+### Name
+
+(`string`) Returns the version name. This is the lowercased key from your project configuration.
+
+```go-html-template
+{{ .Site.Version.Name }} → v1.0.0
+```
+
+[default version]: /quick-reference/glossary/#default-version
diff --git a/docs/content/en/methods/site/_index.md b/docs/content/en/methods/site/_index.md
new file mode 100644
index 00000000000..4f878a10029
--- /dev/null
+++ b/docs/content/en/methods/site/_index.md
@@ -0,0 +1,8 @@
+---
+title: Site methods
+linkTitle: Site
+description: Use these methods with a Site object.
+categories: []
+keywords: []
+aliases: [/variables/site/]
+---
diff --git a/docs/content/en/methods/taxonomy/Alphabetical.md b/docs/content/en/methods/taxonomy/Alphabetical.md
new file mode 100644
index 00000000000..af4af596c38
--- /dev/null
+++ b/docs/content/en/methods/taxonomy/Alphabetical.md
@@ -0,0 +1,69 @@
+---
+title: Alphabetical
+description: Returns an ordered taxonomy, sorted alphabetically by term.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.OrderedTaxonomy
+ signatures: [TAXONOMY.Alphabetical]
+---
+
+The `Alphabetical` method on a `Taxonomy` object returns an [ordered taxonomy](g), sorted alphabetically by [term](g).
+
+While a `Taxonomy` object is a [map](g), an ordered taxonomy is a [slice](g), where each element is an object that contains the term and a slice of its [weighted pages](g).
+
+{{% include "/_common/methods/taxonomy/get-a-taxonomy-object.md" %}}
+
+## Get the ordered taxonomy
+
+Now that we have captured the “genres” Taxonomy object, let's get the ordered taxonomy sorted alphabetically by term:
+
+```go-html-template
+{{ $taxonomyObject.Alphabetical }}
+```
+
+To reverse the sort order:
+
+```go-html-template
+{{ $taxonomyObject.Alphabetical.Reverse }}
+```
+
+To inspect the data structure:
+
+```go-html-template
+
{{ debug.Dump $taxonomyObject.Alphabetical }}
+```
+
+{{% include "/_common/methods/taxonomy/ordered-taxonomy-element-methods.md" %}}
+
+## Example
+
+With this template:
+
+```go-html-template
+{{ range $taxonomyObject.Alphabetical }}
+
+```
diff --git a/docs/content/en/methods/taxonomy/ByCount.md b/docs/content/en/methods/taxonomy/ByCount.md
new file mode 100644
index 00000000000..196b3176938
--- /dev/null
+++ b/docs/content/en/methods/taxonomy/ByCount.md
@@ -0,0 +1,69 @@
+---
+title: ByCount
+description: Returns an ordered taxonomy, sorted by the number of pages associated with each term.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.OrderedTaxonomy
+ signatures: [TAXONOMY.ByCount]
+---
+
+The `ByCount` method on a `Taxonomy` object returns an [ordered taxonomy](g), sorted by the number of pages associated with each [term](g), then sorted alphabetically by term in the event of a tie.
+
+While a `Taxonomy` object is a [map](g), an ordered taxonomy is a [slice](g), where each element is an object that contains the term and a slice of its [weighted pages](g).
+
+{{% include "/_common/methods/taxonomy/get-a-taxonomy-object.md" %}}
+
+## Get the ordered taxonomy
+
+Now that we have captured the “genres” Taxonomy object, let's get the ordered taxonomy sorted by the number of pages associated with each term:
+
+```go-html-template
+{{ $taxonomyObject.ByCount }}
+```
+
+To reverse the sort order:
+
+```go-html-template
+{{ $taxonomyObject.ByCount.Reverse }}
+```
+
+To inspect the data structure:
+
+```go-html-template
+
{{ debug.Dump $taxonomyObject.ByCount }}
+```
+
+{{% include "/_common/methods/taxonomy/ordered-taxonomy-element-methods.md" %}}
+
+## Example
+
+With this template:
+
+```go-html-template
+{{ range $taxonomyObject.ByCount }}
+
+```
diff --git a/docs/content/en/methods/taxonomy/Count.md b/docs/content/en/methods/taxonomy/Count.md
new file mode 100644
index 00000000000..76af8ee04cf
--- /dev/null
+++ b/docs/content/en/methods/taxonomy/Count.md
@@ -0,0 +1,22 @@
+---
+title: Count
+description: Returns the number of number of weighted pages to which the given term has been assigned.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: int
+ signatures: [TAXONOMY.Count TERM]
+---
+
+The `Count` method on a `Taxonomy` object returns the number of number of [weighted pages](g) to which the given [term](g) has been assigned.
+
+{{% include "/_common/methods/taxonomy/get-a-taxonomy-object.md" %}}
+
+## Count the weighted pages
+
+Now that we have captured the "genres" `Taxonomy` object, let's count the number of weighted pages to which the "suspense" term has been assigned:
+
+```go-html-template
+{{ $taxonomyObject.Count "suspense" }} → 3
+```
diff --git a/docs/content/en/methods/taxonomy/Get.md b/docs/content/en/methods/taxonomy/Get.md
new file mode 100644
index 00000000000..03c184868c1
--- /dev/null
+++ b/docs/content/en/methods/taxonomy/Get.md
@@ -0,0 +1,67 @@
+---
+title: Get
+description: Returns a slice of weighted pages to which the given term has been assigned.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.WeightedPages
+ signatures: [TAXONOMY.Get TERM]
+---
+
+The `Get` method on a `Taxonomy` object returns a slice of [weighted pages](g) to which the given [term](g) has been assigned.
+
+{{% include "/_common/methods/taxonomy/get-a-taxonomy-object.md" %}}
+
+## Get the weighted pages
+
+Now that we have captured the "genres" `Taxonomy` object, let's get the weighted pages to which the "suspense" term has been assigned:
+
+```go-html-template
+{{ $weightedPages := $taxonomyObject.Get "suspense" }}
+```
+
+The above is equivalent to:
+
+```go-html-template
+{{ $weightedPages := $taxonomyObject.suspense }}
+```
+
+But, if the term is not a valid [identifier](g), you cannot use the [chaining](g) syntax. For example, this will throw an error because the identifier contains a hyphen:
+
+```go-html-template
+{{ $weightedPages := $taxonomyObject.my-genre }}
+```
+
+You could also use the [`index`] function, but the syntax is more verbose:
+
+```go-html-template
+{{ $weightedPages := index $taxonomyObject "my-genre" }}
+```
+
+To inspect the data structure:
+
+```go-html-template
+
{{ debug.Dump $weightedPages }}
+```
+
+## Example
+
+With this template:
+
+```go-html-template
+{{ $weightedPages := $taxonomyObject.Get "suspense" }}
+{{ range $weightedPages }}
+
+```
+
+[`index`]: /functions/collections/indexfunction/
diff --git a/docs/content/en/methods/taxonomy/Page.md b/docs/content/en/methods/taxonomy/Page.md
new file mode 100644
index 00000000000..628148e4d16
--- /dev/null
+++ b/docs/content/en/methods/taxonomy/Page.md
@@ -0,0 +1,24 @@
+---
+title: Page
+description: Returns the taxonomy page or nil if the taxonomy has no terms.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: page.Page
+ signatures: [TAXONOMY.Page]
+---
+
+This `TAXONOMY` method returns nil if the taxonomy has no terms, so you must code defensively:
+
+```go-html-template
+{{ with .Site.Taxonomies.tags.Page }}
+ {{ .LinkTitle }}
+{{ end }}
+```
+
+This is rendered to:
+
+```html
+Tags
+```
diff --git a/docs/content/en/methods/taxonomy/_index.md b/docs/content/en/methods/taxonomy/_index.md
new file mode 100644
index 00000000000..9cf4c745699
--- /dev/null
+++ b/docs/content/en/methods/taxonomy/_index.md
@@ -0,0 +1,7 @@
+---
+title: Taxonomy methods
+linkTitle: Taxonomy
+description: Use these methods with a Taxonomy object.
+keywords: []
+aliases: [/variables/taxonomy/]
+---
diff --git a/docs/content/en/methods/time/Add.md b/docs/content/en/methods/time/Add.md
new file mode 100644
index 00000000000..e518a16332b
--- /dev/null
+++ b/docs/content/en/methods/time/Add.md
@@ -0,0 +1,20 @@
+---
+title: Add
+description: Returns the given time plus the given duration.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: time.Time
+ signatures: [TIME.Add DURATION]
+---
+
+```go-html-template
+{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }}
+
+{{ $d1 = time.ParseDuration "3h20m10s" }}
+{{ $d2 = time.ParseDuration "-3h20m10s" }}
+
+{{ $t.Add $d1 }} → 2023-01-28 03:05:08 -0800 PST
+{{ $t.Add $d2 }} → 2023-01-27 20:24:48 -0800 PST
+```
diff --git a/docs/content/en/methods/time/AddDate.md b/docs/content/en/methods/time/AddDate.md
new file mode 100644
index 00000000000..ffc93c712a7
--- /dev/null
+++ b/docs/content/en/methods/time/AddDate.md
@@ -0,0 +1,37 @@
+---
+title: AddDate
+description: Returns the time corresponding to adding the given number of years, months, and days to the given time.Time value.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: time.Time
+ signatures: [TIME.AddDate YEARS MONTHS DAYS]
+aliases: [/functions/adddate]
+---
+
+```go-html-template
+{{ $d := "2022-01-01" | time.AsTime }}
+
+{{ $d.AddDate 0 0 1 | time.Format "2006-01-02" }} → 2022-01-02
+{{ $d.AddDate 0 1 1 | time.Format "2006-01-02" }} → 2022-02-02
+{{ $d.AddDate 1 1 1 | time.Format "2006-01-02" }} → 2023-02-02
+
+{{ $d.AddDate -1 -1 -1 | time.Format "2006-01-02" }} → 2020-11-30
+```
+
+> [!note]
+> When adding months or years, Hugo normalizes the final `time.Time` value if the resulting day does not exist. For example, adding one month to 31 January produces 2 March or 3 March, depending on the year.
+>
+> See [this explanation](https://github.com/golang/go/issues/31145#issuecomment-479067967) from the Go team.
+
+```go-html-template
+{{ $d := "2023-01-31" | time.AsTime }}
+{{ $d.AddDate 0 1 0 | time.Format "2006-01-02" }} → 2023-03-03
+
+{{ $d := "2024-01-31" | time.AsTime }}
+{{ $d.AddDate 0 1 0 | time.Format "2006-01-02" }} → 2024-03-02
+
+{{ $d := "2024-02-29" | time.AsTime }}
+{{ $d.AddDate 1 0 0 | time.Format "2006-01-02" }} → 2025-03-01
+```
diff --git a/docs/content/en/methods/time/After.md b/docs/content/en/methods/time/After.md
new file mode 100644
index 00000000000..1c8d41f640c
--- /dev/null
+++ b/docs/content/en/methods/time/After.md
@@ -0,0 +1,17 @@
+---
+title: After
+description: Reports whether TIME1 is after TIME2.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: bool
+ signatures: [TIME1.After TIME2]
+---
+
+```go-html-template
+{{ $t1 := time.AsTime "2023-01-01T17:00:00-08:00" }}
+{{ $t2 := time.AsTime "2010-01-01T17:00:00-08:00" }}
+
+{{ $t1.After $t2 }} → true
+```
diff --git a/docs/content/en/methods/time/Before.md b/docs/content/en/methods/time/Before.md
new file mode 100644
index 00000000000..f6dc3a8e70d
--- /dev/null
+++ b/docs/content/en/methods/time/Before.md
@@ -0,0 +1,16 @@
+---
+title: Before
+description: Reports whether TIME1 is before TIME2.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: bool
+ signatures: [TIME1.Before TIME2]
+---
+
+```go-html-template
+{{ $t1 := time.AsTime "2023-01-01T17:00:00-08:00" }}
+{{ $t2 := time.AsTime "2030-01-01T17:00:00-08:00" }}
+
+{{ $t1.Before $t2 }} → true
diff --git a/docs/content/en/methods/time/Day.md b/docs/content/en/methods/time/Day.md
new file mode 100644
index 00000000000..e9e67873cab
--- /dev/null
+++ b/docs/content/en/methods/time/Day.md
@@ -0,0 +1,15 @@
+---
+title: Day
+description: Returns the day of the month of the given time.Time value.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: int
+ signatures: [TIME.Day]
+---
+
+```go-html-template
+{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }}
+{{ $t.Day }} → 27
+```
diff --git a/docs/content/en/methods/time/Equal.md b/docs/content/en/methods/time/Equal.md
new file mode 100644
index 00000000000..6db10423c31
--- /dev/null
+++ b/docs/content/en/methods/time/Equal.md
@@ -0,0 +1,17 @@
+---
+title: Equal
+description: Reports whether TIME1 is equal to TIME2.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: bool
+ signatures: [TIME1.Equal TIME2]
+---
+
+```go-html-template
+{{ $t1 := time.AsTime "2023-01-01T17:00:00-08:00" }}
+{{ $t2 := time.AsTime "2023-01-01T20:00:00-05:00" }}
+
+{{ $t1.Equal $t2 }} → true
+```
diff --git a/docs/content/en/methods/time/Format.md b/docs/content/en/methods/time/Format.md
new file mode 100644
index 00000000000..8a484b74e17
--- /dev/null
+++ b/docs/content/en/methods/time/Format.md
@@ -0,0 +1,88 @@
+---
+title: Format
+description: Returns a textual representation of the time.Time value formatted according to the layout string.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: string
+ signatures: [TIME.Format LAYOUT]
+aliases: [/methods/time/format]
+---
+
+```go-template
+{{ $t := "2023-01-27T23:44:58-08:00" }}
+{{ $t = time.AsTime $t }}
+{{ $format := "2 Jan 2006" }}
+
+{{ $t.Format $format }} → 27 Jan 2023
+```
+
+> [!note]
+> To [localize](g) the return value, use the [`time.Format`] function instead.
+
+Use the `Format` method with any `time.Time` value, including the four predefined front matter dates:
+
+```go-html-template
+{{ $format := "2 Jan 2006" }}
+
+{{ .Date.Format $format }}
+{{ .PublishDate.Format $format }}
+{{ .ExpiryDate.Format $format }}
+{{ .Lastmod.Format $format }}
+```
+
+> [!note]
+> Use the [`time.Format`] function to format string representations of dates, and to format raw TOML dates that exclude time and time zone offset.
+
+## Layout string
+
+{{% include "/_common/time-layout-string.md" %}}
+
+## Examples
+
+Given this front matter:
+
+{{< code-toggle fm=true >}}
+title = "About time"
+date = 2023-01-27T23:44:58-08:00
+{{< /code-toggle >}}
+
+The examples below were rendered in the `America/Los_Angeles` time zone:
+
+Format string|Result
+:--|:--
+`Monday, January 2, 2006`|`Friday, January 27, 2023`
+`Mon Jan 2 2006`|`Fri Jan 27 2023`
+`January 2006`|`January 2023`
+`2006-01-02`|`2023-01-27`
+`Monday`|`Friday`
+`02 Jan 06 15:04 MST`|`27 Jan 23 23:44 PST`
+`Mon, 02 Jan 2006 15:04:05 MST`|`Fri, 27 Jan 2023 23:44:58 PST`
+`Mon, 02 Jan 2006 15:04:05 -0700`|`Fri, 27 Jan 2023 23:44:58 -0800`
+
+## UTC and local time
+
+Convert and format any `time.Time` value to either Coordinated Universal Time (UTC) or local time.
+
+```go-html-template
+{{ $t := "2023-01-27T23:44:58-08:00" }}
+{{ $t = time.AsTime $t }}
+{{ $format := "2 Jan 2006 3:04:05 PM MST" }}
+
+{{ $t.UTC.Format $format }} → 28 Jan 2023 7:44:58 AM UTC
+{{ $t.Local.Format $format }} → 27 Jan 2023 11:44:58 PM PST
+```
+
+## Ordinal representation
+
+Use the [`humanize`](/functions/inflect/humanize) function to render the day of the month as an ordinal number:
+
+```go-html-template
+{{ $t := "2023-01-27T23:44:58-08:00" }}
+{{ $t = time.AsTime $t }}
+
+{{ humanize $t.Day }} of {{ $t.Format "January 2006" }} → 27th of January 2023
+```
+
+[`time.Format`]: /functions/time/format/
diff --git a/docs/content/en/methods/time/Hour.md b/docs/content/en/methods/time/Hour.md
new file mode 100644
index 00000000000..28ecf62ace6
--- /dev/null
+++ b/docs/content/en/methods/time/Hour.md
@@ -0,0 +1,15 @@
+---
+title: Hour
+description: Returns the hour within the day of the given time.Time value, in the range [0, 23].
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: int
+ signatures: [TIME.Hour]
+---
+
+```go-html-template
+{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }}
+{{ $t.Hour }} → 23
+```
diff --git a/docs/content/en/methods/time/IsDST.md b/docs/content/en/methods/time/IsDST.md
new file mode 100644
index 00000000000..28177b105f7
--- /dev/null
+++ b/docs/content/en/methods/time/IsDST.md
@@ -0,0 +1,18 @@
+---
+title: IsDST
+description: Reports whether the given time.Time value is in Daylight Savings Time.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: bool
+ signatures: [TIME.IsDST]
+---
+
+```go-html-template
+{{ $t1 := time.AsTime "2023-01-01T00:00:00-08:00" }}
+{{ $t2 := time.AsTime "2023-07-01T00:00:00-07:00" }}
+
+{{ $t1.IsDST }} → false
+{{ $t2.IsDST }} → true
+```
diff --git a/docs/content/en/methods/time/IsZero.md b/docs/content/en/methods/time/IsZero.md
new file mode 100644
index 00000000000..400172794ab
--- /dev/null
+++ b/docs/content/en/methods/time/IsZero.md
@@ -0,0 +1,18 @@
+---
+title: IsZero
+description: Reports whether the given time.Time value represents the zero time instant, January 1, year 1, 00:00:00 UTC.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: bool
+ signatures: [TIME.IsZero]
+---
+
+````go-html-template
+{{ $t1 := time.AsTime "2023-01-01T00:00:00-08:00" }}
+{{ $t2 := time.AsTime "0001-01-01T00:00:00-00:00" }}
+
+{{ $t1.IsZero }} → false
+{{ $t2.IsZero }} → true
+```
diff --git a/docs/content/en/methods/time/Local.md b/docs/content/en/methods/time/Local.md
new file mode 100644
index 00000000000..74fe889e0dd
--- /dev/null
+++ b/docs/content/en/methods/time/Local.md
@@ -0,0 +1,15 @@
+---
+title: Local
+description: Returns the given time.Time value with the location set to local time.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: time.Time
+ signatures: [TIME.Local]
+---
+
+```go-html-template
+{{ $t := time.AsTime "2023-01-28T07:44:58+00:00" }}
+{{ $t.Local }} → 2023-01-27 23:44:58 -0800 PST
+```
diff --git a/docs/content/en/methods/time/Minute.md b/docs/content/en/methods/time/Minute.md
new file mode 100644
index 00000000000..b53db6d8352
--- /dev/null
+++ b/docs/content/en/methods/time/Minute.md
@@ -0,0 +1,15 @@
+---
+title: Minute
+description: Returns the minute offset within the hour of the given time.Time value, in the range [0, 59].
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: int
+ signatures: [TIME.Minute]
+---
+
+```go-html-template
+{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }}
+{{ $t.Minute }} → 44
+```
diff --git a/docs/content/en/methods/time/Month.md b/docs/content/en/methods/time/Month.md
new file mode 100644
index 00000000000..b0ccea9c398
--- /dev/null
+++ b/docs/content/en/methods/time/Month.md
@@ -0,0 +1,24 @@
+---
+title: Month
+description: Returns the month of the year of the given time.Time value.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: time.Month
+ signatures: [TIME.Month]
+---
+
+To convert the `time.Month` value to a string:
+
+```go-html-template
+{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }}
+{{ $t.Month.String }} → January
+```
+
+To convert the `time.Month` value to an integer.
+
+```go-html-template
+{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }}
+{{ $t.Month | int }} → 1
+```
diff --git a/docs/content/en/methods/time/Nanosecond.md b/docs/content/en/methods/time/Nanosecond.md
new file mode 100644
index 00000000000..d895f962211
--- /dev/null
+++ b/docs/content/en/methods/time/Nanosecond.md
@@ -0,0 +1,15 @@
+---
+title: Nanosecond
+description: Returns the nanosecond offset within the second of the given time.Time value, in the range [0, 999999999].
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: int
+ signatures: [TIME.Nanosecond]
+---
+
+```go-html-template
+{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }}
+{{ $t.Nanosecond }} → 0
+```
diff --git a/docs/content/en/methods/time/Round.md b/docs/content/en/methods/time/Round.md
new file mode 100644
index 00000000000..afda0e294c1
--- /dev/null
+++ b/docs/content/en/methods/time/Round.md
@@ -0,0 +1,21 @@
+---
+title: Round
+description: Returns the result of rounding TIME to the nearest multiple of DURATION since January 1, 0001, 00:00:00 UTC.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: time.Time
+ signatures: [TIME.Round DURATION]
+---
+
+The rounding behavior for halfway values is to round up.
+
+The `Round` method operates on TIME as an absolute duration since the [zero time](g); it does not operate on the presentation form of the time. If DURATION is a multiple of one hour, `Round` may return a time with a non-zero minute, depending on the time zone.
+
+```go-html-template
+{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }}
+{{ $d := time.ParseDuration "1h" }}
+
+{{ ($t.Round $d).Format "2006-01-02T15:04:05-00:00" }} → 2023-01-28T00:00:00-00:00
+```
diff --git a/docs/content/en/methods/time/Second.md b/docs/content/en/methods/time/Second.md
new file mode 100644
index 00000000000..3af086fd3ca
--- /dev/null
+++ b/docs/content/en/methods/time/Second.md
@@ -0,0 +1,15 @@
+---
+title: Second
+description: Returns the second offset within the minute of the given time.Time value, in the range [0, 59].
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: int
+ signatures: [TIME.Second]
+---
+
+```go-html-template
+{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }}
+{{ $t.Second }} → 58
+```
diff --git a/docs/content/en/methods/time/Sub.md b/docs/content/en/methods/time/Sub.md
new file mode 100644
index 00000000000..d48bf34672d
--- /dev/null
+++ b/docs/content/en/methods/time/Sub.md
@@ -0,0 +1,17 @@
+---
+title: Sub
+description: Returns the duration computed by subtracting TIME2 from TIME1.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: time.Duration
+ signatures: [TIME1.Sub TIME2]
+---
+
+```go-html-template
+{{ $t1 := time.AsTime "2023-01-27T23:44:58-08:00" }}
+{{ $t2 := time.AsTime "2023-01-26T22:34:38-08:00" }}
+
+{{ $t1.Sub $t2 }} → 25h10m20s
+```
diff --git a/docs/content/en/methods/time/Truncate.md b/docs/content/en/methods/time/Truncate.md
new file mode 100644
index 00000000000..b90c3847f7a
--- /dev/null
+++ b/docs/content/en/methods/time/Truncate.md
@@ -0,0 +1,19 @@
+---
+title: Truncate
+description: Returns the result of rounding TIME down to a multiple of DURATION since January 1, 0001, 00:00:00 UTC.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: time.Time
+ signatures: [TIME.Truncate DURATION]
+---
+
+The `Truncate` method operates on TIME as an absolute duration since the [zero time](g); it does not operate on the presentation form of the time. If DURATION is a multiple of one hour, `Truncate` may return a time with a non-zero minute, depending on the time zone.
+
+```go-html-template
+{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }}
+{{ $d := time.ParseDuration "1h" }}
+
+{{ ($t.Truncate $d).Format "2006-01-02T15:04:05-00:00" }} → 2023-01-27T23:00:00-00:00
+```
diff --git a/docs/content/en/methods/time/UTC.md b/docs/content/en/methods/time/UTC.md
new file mode 100644
index 00000000000..e131a003eaf
--- /dev/null
+++ b/docs/content/en/methods/time/UTC.md
@@ -0,0 +1,14 @@
+---
+title: UTC
+description: Returns the given time.Time value with the location set to UTC.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: time.Time
+ signatures: [TIME.UTC]
+---
+
+```go-html-template
+{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }}
+{{ $t.UTC }} → 2023-01-28 07:44:58 +0000 UTC
diff --git a/docs/content/en/methods/time/Unix.md b/docs/content/en/methods/time/Unix.md
new file mode 100644
index 00000000000..73deb524ee7
--- /dev/null
+++ b/docs/content/en/methods/time/Unix.md
@@ -0,0 +1,17 @@
+---
+title: Unix
+description: Returns the given time.Time value expressed as the number of seconds elapsed since January 1, 1970 UTC.
+categories: []
+params:
+ functions_and_methods:
+ returnType: int64
+ signatures: [TIME.Unix]
+aliases: [/functions/unix]
+---
+
+See [Unix epoch](https://en.wikipedia.org/wiki/Unix_time).
+
+```go-html-template
+{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }}
+{{ $t.Unix }} → 1674891898
+```
diff --git a/docs/content/en/methods/time/UnixMicro.md b/docs/content/en/methods/time/UnixMicro.md
new file mode 100644
index 00000000000..fadb0916c90
--- /dev/null
+++ b/docs/content/en/methods/time/UnixMicro.md
@@ -0,0 +1,17 @@
+---
+title: UnixMicro
+description: Returns the given time.Time value expressed as the number of microseconds elapsed since January 1, 1970 UTC.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: int64
+ signatures: [TIME.UnixMicro]
+---
+
+See [Unix epoch](https://en.wikipedia.org/wiki/Unix_time).
+
+```go-html-template
+{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }}
+{{ $t.UnixMicro }} → 1674891898000000
+```
diff --git a/docs/content/en/methods/time/UnixMilli.md b/docs/content/en/methods/time/UnixMilli.md
new file mode 100644
index 00000000000..9d2261d9167
--- /dev/null
+++ b/docs/content/en/methods/time/UnixMilli.md
@@ -0,0 +1,17 @@
+---
+title: UnixMilli
+description: Returns the given time.Time value expressed as the number of milliseconds elapsed since January 1, 1970 UTC.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: int64
+ signatures: [TIME.UnixMilli]
+---
+
+See [Unix epoch](https://en.wikipedia.org/wiki/Unix_time).
+
+```go-html-template
+{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }}
+{{ $t.UnixMilli }} → 1674891898000
+```
diff --git a/docs/content/en/methods/time/UnixNano.md b/docs/content/en/methods/time/UnixNano.md
new file mode 100644
index 00000000000..4159ddee254
--- /dev/null
+++ b/docs/content/en/methods/time/UnixNano.md
@@ -0,0 +1,17 @@
+---
+title: UnixNano
+description: Returns the given time.Time value expressed as the number of nanoseconds elapsed since January 1, 1970 UTC.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: int64
+ signatures: [TIME.UnixNano]
+---
+
+See [Unix epoch](https://en.wikipedia.org/wiki/Unix_time).
+
+```go-html-template
+{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }}
+{{ $t.UnixNano }} → 1674891898000000000
+```
diff --git a/docs/content/en/methods/time/Weekday.md b/docs/content/en/methods/time/Weekday.md
new file mode 100644
index 00000000000..da939ff8750
--- /dev/null
+++ b/docs/content/en/methods/time/Weekday.md
@@ -0,0 +1,23 @@
+---
+title: Weekday
+description: Returns the day of the week of the given time.Time value.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: time.Weekday
+ signatures: [TIME.Weekday]
+---
+
+To convert the `time.Weekday` value to a string:
+
+```go-html-template
+{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }}
+{{ $t.Weekday.String }} → Friday
+```
+
+To convert the `time.Weekday` value to an integer.
+
+```go-html-template
+{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }}
+{{ $t.Weekday | int }} → 5
diff --git a/docs/content/en/methods/time/Year.md b/docs/content/en/methods/time/Year.md
new file mode 100644
index 00000000000..3f647ea343a
--- /dev/null
+++ b/docs/content/en/methods/time/Year.md
@@ -0,0 +1,15 @@
+---
+title: Year
+description: Returns the year of the given time.Time value.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: int
+ signatures: [TIME.Year]
+---
+
+```go-html-template
+{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }}
+{{ $t.Year }} → 2023
+```
diff --git a/docs/content/en/methods/time/YearDay.md b/docs/content/en/methods/time/YearDay.md
new file mode 100644
index 00000000000..a93158b4553
--- /dev/null
+++ b/docs/content/en/methods/time/YearDay.md
@@ -0,0 +1,15 @@
+---
+title: YearDay
+description: Returns the day of the year of the given time.Time value, in the range [1, 365] for non-leap years, and [1, 366] in leap years.
+categories: []
+keywords: []
+params:
+ functions_and_methods:
+ returnType: int
+ signatures: [TIME.YearDay]
+---
+
+```go-html-template
+{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }}
+{{ $t.YearDay }} → 27
+```
diff --git a/docs/content/en/methods/time/_index.md b/docs/content/en/methods/time/_index.md
new file mode 100644
index 00000000000..067d91a7e6f
--- /dev/null
+++ b/docs/content/en/methods/time/_index.md
@@ -0,0 +1,7 @@
+---
+title: Time methods
+linkTitle: Time
+description: Use these methods with a time.Time value.
+categories: []
+keywords: []
+---
diff --git a/docs/content/en/myshowcase/bio.md b/docs/content/en/myshowcase/bio.md
deleted file mode 100644
index 7d1b30895ba..00000000000
--- a/docs/content/en/myshowcase/bio.md
+++ /dev/null
@@ -1,8 +0,0 @@
-
-Add some **general info** about Myshowcase here.
-
-The site is built by:
-
-* [Person 1](https://example.com)
-* [Person 1](https://example.com)
-
diff --git a/docs/content/en/myshowcase/featured.png b/docs/content/en/myshowcase/featured.png
deleted file mode 100644
index 4f390132eb1..00000000000
Binary files a/docs/content/en/myshowcase/featured.png and /dev/null differ
diff --git a/docs/content/en/myshowcase/index.md b/docs/content/en/myshowcase/index.md
deleted file mode 100644
index 08ea1e55f94..00000000000
--- a/docs/content/en/myshowcase/index.md
+++ /dev/null
@@ -1,37 +0,0 @@
----
-
-title: Myshowcase
-date: 2021-01-14
-
-description: "A short description of this page."
-
-# The URL to the site on the internet.
-siteURL: https://gohugo.io/
-
-# Link to the site's Hugo source code if public and you can/want to share.
-# Remove or leave blank if not needed/wanted.
-siteSource: https://github.com/gohugoio/hugoDocs
-
-# Add credit to the article author. Leave blank or remove if not needed/wanted.
-byline: "[bep](https://github.com/bep), Hugo Lead"
-
----
-
-To complete this showcase:
-
-1. Write the story about your site in this file.
-2. Add a summary to the `bio.md` file in this folder.
-3. Replace the `featured-template.png` with a screenshot of your site. You can rename it, but it must contain the word `featured`.
-4. Create a new pull request in https://github.com/gohugoio/hugoDocs/pulls
-
-The content of this bundle explained:
-
-index.md
-: The main content file. Fill in required front matter metadata and write your story. I does not have to be a novel. It can even be self-promotional, but it should include Hugo in some form.
-
-bio.md
-: A short summary of the website. Site credits (who built it) fits nicely here.
-
-featured.png
-: A reasonably sized screenshot of your website. It can be named anything, but the name must start with "featured". The sample image is `1500x750` (2:1 aspect ratio).
-
diff --git a/docs/content/en/news/0.10-relnotes/index.md b/docs/content/en/news/0.10-relnotes/index.md
deleted file mode 100644
index 060998ba0be..00000000000
--- a/docs/content/en/news/0.10-relnotes/index.md
+++ /dev/null
@@ -1,21 +0,0 @@
----
-date: 2014-03-01T17:21:33Z
-description: "Hugo 0.10 brings syntax highlighting (powered by Pygments), automatic table of contents generation, support for Unicode, and a lot more!"
-title: "Hugo 0.10"
-categories: ["Releases"]
----
-
-- [Syntax highlighting](http://hugo.spf13.com/extras/highlighting) powered by pygments (**slow**)
-- Ability to [sort content](http://hugo.spf13.com/content/ordering) many more ways
-- Automatic [table of contents](http://hugo.spf13.com/extras/toc) generation
-- Support for unicode URLs, aliases and indexes
-- Configurable per-section [permalink](http://hugo.spf13.com/extras/permalinks) pattern support
-- Support for [paired shortcodes](http://hugo.spf13.com/extras/shortcodes)
-- Shipping with some [shortcodes](http://hugo.spf13.com/extras/shortcodes) (highlight & figure)
-- Adding [canonify](http://hugo.spf13.com/extras/urls) option to keep URLs relative
-- A bunch of [additional template functions](http://hugo.spf13.com/layouts/templatefunctions)
-- Watching very large sites now works on mac
-- RSS generation improved. Limited to 50 items by default, can limit further in [template](http://hugo.spf13.com/layout/rss)
-- Boolean params now supported in [frontmatter](http://hugo.spf13.com/content/front-matter)
-- Launched website [showcase](http://hugo.spf13.com/showcase). Show off your own hugo site!
-- A bunch of [bug fixes](https://github.com/spf13/hugo/commits/master)
diff --git a/docs/content/en/news/0.11-relnotes/index.md b/docs/content/en/news/0.11-relnotes/index.md
deleted file mode 100644
index dc4115fe0af..00000000000
--- a/docs/content/en/news/0.11-relnotes/index.md
+++ /dev/null
@@ -1,23 +0,0 @@
----
-date: 2014-05-29T22:53:22Z
-description: "Now 3–4 times faster, Hugo 0.11 brings live reload, theme engine, menu system, XML sitemap, and more!"
-title: "Hugo 0.11"
-categories: ["Releases"]
----
-
-- Considerably faster... about 3 - 4x faster on average
-- [Live Reload](/getting-started/usage/#livereload). Hugo will automatically reload the browser when the build is complete
-- Theme engine w/[Theme Repository](http://github.com/spf13/hugoThemes)
-- [Menu system](/content-management/menus/) with support for active page
-- [Builders](/extras/builders) to quickly create a new site, content or theme
-- [XML sitemap](/templates/sitemap) generation
-- [Integrated Disqus](/extras/comments) support
-- Streamlined [template organization](/templates/overview)
-- Brand new docs site
-- Support for publishDate which allows for posts to be dated in the future
-- More [sort](/functions/sort/#readout) options
-- Logging support
-- Much better error handling
-- More informative verbose output
-- Renamed Indexes > [Taxonomies](/taxonomies/overview)
-- Renamed Chrome > [Partials](/templates/partials)
diff --git a/docs/content/en/news/0.12-relnotes/index.md b/docs/content/en/news/0.12-relnotes/index.md
deleted file mode 100644
index 4401b5efc2b..00000000000
--- a/docs/content/en/news/0.12-relnotes/index.md
+++ /dev/null
@@ -1,29 +0,0 @@
----
-date: 2015-01-21T20:35:00Z
-description: "Hugo 0.12 focused on polishing the theme engine and adding critical functionality to the templates."
-title: "Hugo 0.12"
-categories: ["Releases"]
----
-
-A lot has happened since Hugo v0.11.0 was released. Most of the work has been
-focused on polishing the theme engine and adding critical functionality to the
-templates.
-
-This release represents over 90 code commits from 28 different contributors.
-
-- 10 [new themes](https://github.com/spf13/hugoThemes) created by the community
-- fully themable [partials](/templates/partials/)
-- [404 template](/templates/404/) support in themes
-- [shortcode](/extras/shortcodes/) support in themes
-- [views](/templates/views/) support in themes
-- inner [shortcode](/extras/shortcodes/) content now treated as markdown
-- support for header ids in markdown (# header {#myid})
-- [where](/templates/lists/#where) template function to filter lists of content, taxonomies, etc
-- [groupby](/templates/list) & [groupbydate](/templates/list) methods to group pages
-- taxonomy [pages list](/taxonomies/methods/) now sortable, filterable, limitable & groupable
-- general cleanup to taxonomies & documentation to make it more clear and consistent
-- [showcase](/showcase/) returned and has been expanded
-- pretty links now always have trailing slashes
-- [baseurl](/overview/configuration/) can now include a subdirectory
-- better feedback about draft & future post rendering
-- a variety of improvements to [the website](/)
diff --git a/docs/content/en/news/0.13-relnotes/index.md b/docs/content/en/news/0.13-relnotes/index.md
deleted file mode 100644
index 198f5fe7bd2..00000000000
--- a/docs/content/en/news/0.13-relnotes/index.md
+++ /dev/null
@@ -1,95 +0,0 @@
----
-date: 2015-02-22T04:10:06Z
-description: "Hugo 0.13 is the largest Hugo release to date. The release introduced some long sought after features (pagination, sequencing, data loading, tons of template improvements) as well as major internal improvements. In addition to the code changes, the Hugo community has grown significantly and now has over 3000 stars on GitHub, 134 contributors, 24 themes and 1000s of happy users."
-title: "Hugo 0.13"
-categories: ["Releases"]
----
-
-The v0.13.0 release is the largest Hugo release to date. The release introduced
-some long sought after features (pagination, sequencing, data loading, tons of
-template improvements) as well as major internal improvements. In addition to
-the code changes, the Hugo community has grown significantly and now has over
-3000 stars on GitHub, 134 contributors, 24 themes and 1000s of happy users.
-
-This release represents **448 contributions by 65 contributors**
-
-A special shout out to [@bep](https://github.com/bep) and
-[@anthonyfok](https://github.com/anthonyfok) for their new role as Hugo
-maintainers and their tremendous contributions this release.
-
-### New major features
-- Support for [data files](http://gohugo.io/extras/datafiles/) in [YAML](http://yaml.org/),
- [JSON](http://www.json.org/), or [TOML](https://github.com/toml-lang/toml)
- located in the `data` directory ([#885](https://github.com/spf13/hugo/issues/885))
-- Support for [dynamic content](http://gohugo.io/extras/dynamiccontent/) by loading JSON & CSV
- from remote sources via GetJson and GetCsv in short codes or other layout
- files ([#748](https://github.com/spf13/hugo/issues/748))
-- [Pagination support](http://gohugo.io/extras/pagination/) for home page, sections and
- taxonomies ([#750](https://github.com/spf13/hugo/issues/750))
-- Universal sequencing support
- - A new, generic Next/Prev functionality is added to all lists of pages
- (sections, taxonomies, etc.)
- - Add in-section [Next/Prev](http://gohugo.io/templates/variables/) content pointers
-- `Scratch` -- [a "scratchpad"](http://gohugo.io/extras/scratch) for your node- and page-scoped
- variables
-- [Cross Reference](http://gohugo.io/extras/crossreferences/) support to easily link documents
- together with the ref and relref shortcodes.
-- [Ace](http://ace.yoss.si/) template engine support ([#541](https://github.com/spf13/hugo/pull/541))
-- A new [shortcode](http://gohugo.io/extras/shortcodes/) token of `{{* */>}}` (raw HTML)
- alongside the existing `{{%/* */%}}` (Markdown)
-- A top level `Hugo` variable (on Page & Node) is added with various build
- information
-- Several new ways to order and group content:
- - `ByPublishDate`
- - `GroupByPublishDate(format, order)`
- - `GroupByParam(key, order)`
- - `GroupByParamDate(key, format, order)`
-- Hugo has undergone a major refactoring, with a new handler system and a
- generic file system. This sounds and is technical, but will pave the way for
- new features and make Hugo even speedier
-
-### Notable enhancements to existing features
-- The [shortcode](http://gohugo.io/extras/shortcodes/) handling is rewritten for speed and
- better error messages.
-- Several improvements to the [template functions](http://gohugo.io/templates/functions/):
- - `where` is now even more powerful and accepts SQL-like syntax with the
- operators `==`, `eq`; `!=`, `<>`, `ne`; `>=`, `ge`; `>`, `gt`; `<=`,
- `le`; `<`, `lt`; `in`, `not in`
- - `where` template function now also accepts dot chaining key argument
- (e.g. `"Params.foo.bar"`)
-- New template functions:
- - `apply`
- - `chomp`
- - `delimit`
- - `sort`
- - `markdownify`
- - `in` and `intersect`
- - `trim`
- - `replace`
- - `dateFormat`
-- Several [configurable improvements related to Markdown
- rendering](http://gohugo.io/overview/configuration/#configure-blackfriday-rendering:a66b35d20295cb764719ac8bd35837ec):
- - Configuration of footnote rendering
- - Optional support for smart angled quotes, e.g. `"Hugo"` → «Hugo»
- - Enable descriptive header IDs
-- URLs in XML output is now correctly canonified ([#725](https://github.com/spf13/hugo/issues/725), [#728](https://github.com/spf13/hugo/issues/728), and part
- of [#789](https://github.com/spf13/hugo/issues/789))
-
-### Other improvements
-- Internal change to use byte buffer pool significantly lowering memory usage
- and providing measurable performance improvements overall
-- Changes to docs:
- - A new [Troubleshooting](http://gohugo.io/troubleshooting/overview/) section is added
- - It's now searchable through Google Custom Search ([#753](https://github.com/spf13/hugo/issues/753))
- - Some new great tutorials:
- - [Automated deployments with
- Wercker](http://gohugo.io/tutorials/automated-deployments/)
- - [Creating a new theme](http://gohugo.io/tutorials/creating-a-new-theme/)
-- [`hugo new`](http://gohugo.io/content/archetypes/) now copies the content in addition to the front matter
-- Improved unit test coverage
-- Fixed a lot of Windows-related path issues
-- Improved error messages for template and rendering errors
-- Enabled soft LiveReload of CSS and images ([#490](https://github.com/spf13/hugo/pull/490))
-- Various fixes in RSS feed generation ([#789](https://github.com/spf13/hugo/issues/789))
-- `HasMenuCurrent` and `IsMenuCurrent` is now supported on Nodes
-- A bunch of [bug fixes](https://github.com/spf13/hugo/commits/master)
diff --git a/docs/content/en/news/0.14-relnotes/index.md b/docs/content/en/news/0.14-relnotes/index.md
deleted file mode 100644
index 9b7928b47e7..00000000000
--- a/docs/content/en/news/0.14-relnotes/index.md
+++ /dev/null
@@ -1,54 +0,0 @@
----
-date: 2015-05-26T01:32:45Z
-description: "The v0.14.0 Hugo release brings of the most demanded features to Hugo. The foundation of Hugo is stabilizing nicely and a lot of polish has been added. We’ve expanded support for additional content types with support for AsciiDoc, Restructured Text, HTML and Markdown."
-title: "Hugo 0.14"
-categories: ["Releases"]
----
-
-The v0.14.0 Hugo release brings of the most demanded features to Hugo. The foundation of Hugo is stabilizing nicely and a lot of polish has been added. We’ve expanded support for additional content types with support for AsciiDoc, Restructured Text, HTML and Markdown. Some of these types depend on external libraries as there does not currently exist native support in Go. We’ve tried to make the experience as seamless as possible. Look for more improvements here in upcoming releases.
-
-A lot of work has been done to improve the user experience, with extra polish to the Windows experience. Hugo errors are more helpful overall and Hugo now can detect if it’s being run in Windows Explorer and provide additional instructions to run it via the command prompt.
-
-The Hugo community continues to grow. Hugo has over 4000 stars on github, 165 contributors, 35 themes and 1000s of happy users. It is now the 5th most popular static site generator (by Stars) and has the 3rd largest contributor community.
-
-This release represents over **240 contributions by 36 contributors** to the main Hugo codebase.
-
-Big shout out to [@bep](https://github.com/bep) who led the development of Hugo this release, [@anthonyfok](https://github.com/anthonyfok), [@eparis](https://github.com/eparis), [@SchumacherFM](https://github.com/SchumacherFM), [@RickCogley](https://github.com/RickCogley) & [@mdhender](https://github.com/mdhender) for their significant contributions and [@tatsushid](https://github.com/tatsushid) for his continuous improvements to the templates. Also a big thanks to all the theme creators. 11 new themes have been added since last release and the [hugoThemes repo now has previews of all of them](https://github.com/spf13/hugoThemes/blob/master/README.md#theme-list).
-
-Hugo also depends on a lot of other great projects. A big thanks to all of our dependencies inclding:
-[cobra](https://github.com/spf13/cobra), [viper](https://github.com/spf13/viper), [blackfriday](https://github.com/russross/blackfriday), [pflag](https://github.com/spf13/pflag), [HugoThemes](https://github.com/spf13/hugothemes), [BurntSushi/Toml](github.com/BurntSushi/toml), [goYaml](https://github.com/go-yaml/yaml/tree/v2), and the Go standard library.
-
-## New features
-- Support for all file types in content directory.
- - If dedicated file type handler isn’t found it will be copied to the destination.
-- Add `AsciiDoc` support using external helpers.
-- Add experimental support for [`Mmark`](https://github.com/miekg/mmark) markdown processor
-- Bash autocomplete support via `genautocomplete` command
-- Add section menu support for a [Section Menu for Lazy Bloggers](https://gohugo.io/templates/menu-templates/#section-menu-for-lazy-bloggers)
-- Add support for `Ace` base templates
-- Adding `RelativeURLs = true` to site config will now make all the relative URLs relative to the content root.
-- New template functions:
- - `getenv`
- - The string functions `substr` and `slicestr`
- *`seq`, a sequence generator very similar to its Gnu counterpart
- - `absURL` and `relURL`, both of which takes the `BaseURL` setting into account
-
-## Improvements
-- Highlighting with `Pygments` is now cached to disk -- expect a major speed boost if you use it!
-- More Pygments highlighting options, including `line numbers`
-- Show help information to Windows users who try to double click on `hugo.exe`.
-- Add `bind` flag to `hugo server` to set the interface to which the server will bind
-- Add support for `canonifyurls` in `srcset`
-- Add shortcode support for HTML (content) files
-- Allow the same `shortcode` to be used with or without inline content
-- Configurable RSS output filename
-
-## Bugfixes
-- Fix panic with paginator and zero pages in result set.
-- Fix crossrefs on Windows.
-- Fix `eq` and `ne` template functions when used with a raw number combined with the result of `add`, `sub` etc.
-- Fix paginator with uglyurls
-- Fix [#998](https://github.com/spf13/hugo/issues/988), supporting UTF8 characters in Permalinks.
-
-## Notices
-- To get variable and function names in line with the rest of the Go community, a set of variable and function names has been deprecated: These will still work in 0.14, but will be removed in 0.15. What to do should be obvious by the build log; `getJson` to `getJSON`, `getCsv` to `getCSV`, `safeHtml` to `safeHTML`, `safeCss` to `safeCSS`, `safeUrl` to `safeURL`, `Url` to `URL`, `UrlPath` to `URLPath`, `BaseUrl` to `BaseURL`, `Recent` to `Pages`, `Indexes` to `Taxonomies`.
diff --git a/docs/content/en/news/0.15-relnotes/index.md b/docs/content/en/news/0.15-relnotes/index.md
deleted file mode 100644
index 5053e3fb200..00000000000
--- a/docs/content/en/news/0.15-relnotes/index.md
+++ /dev/null
@@ -1,97 +0,0 @@
----
-date: 2015-12-19T09:45:24Z
-description: "The v0.15.0 Hugo release brings a lot of polish to Hugo. Exactly 6 months after the 0.14 release, Hugo has seen massive growth and changes. Most notably, this is Hugo's first release under the Apache 2.0 license."
-title: "Hugo 0.15"
-categories: ["Releases"]
----
-
-The v0.15.0 Hugo release brings a lot of polish to Hugo. Exactly 6 months after the 0.14 release, Hugo has seen massive growth and changes. Most notably, this is Hugo's first release under the Apache 2.0 license. With this license change we hope to expand the great community around Hugo and make it easier for our many users to contribute. This release represents over **377 contributions by 87 contributors** to the main Hugo repo and hundreds of improvements to the libraries Hugo uses. Hugo also launched a [new theme showcase](http://themes.gohugo.io) and participated in [Hacktoberfest](https://hacktoberfest.digitalocean.com).
-
-Hugo now has:
-- 6700 (+2700) stars on GitHub
-- 235 (+75) contributors
-- 65 (+30) themes
-
-**Template Improvements:** This release takes Hugo to a new level of speed and usability. Considerable work has been done adding features and performance to the template system which now has full support of Ace, Amber and Go Templates.
-
-**Hugo Import:** Have a Jekyll site, but dreaming of porting it to Hugo? This release introduces a new `hugo import jekyll`command that makes this easier than ever.
-
-**Performance Improvements:** Just when you thought Hugo couldn't get any faster, Hugo continues to improve in speed while adding features. Notably Hugo 0.15 introduces the ability to render and serve directly from memory resulting in 30%+ lower render times.
-
-Huge thanks to all who participated in this release. A special thanks to [@bep](https://github.com/bep) who led the development of Hugo this release again, [@anthonyfok](https://github.com/anthonyfok), [@eparis](https://github.com/eparis), [@tatsushid](https://github.com/tatsushid) and [@DigitalCraftsman](https://github.com/digitalcraftsman/).
-
-## New features
-- new `hugo import jekyll` command. [#1469](https://github.com/spf13/hugo/pull/1469)
-- The new `Param` convenience method on `Page` and `Node` can be used to get the most specific parameter value for a given key. [#1462](https://github.com/spf13/hugo/issues/1462)
-- Several new information elements have been added to `Page` and `Node`:
- - `RuneCount`: The number of [runes](http://blog.golang.org/strings) in the content, excluding any whitespace. This may be a good alternative to `.WordCount` for Japanese and other CJK languages where a word-split by spaces makes no sense. [#1266](https://github.com/spf13/hugo/issues/1266)
- - `RawContent`: Raw Markdown as a string. One use case may be of embedding remarkjs.com slides.
- - `IsHome`: tells the truth about whether you're on the home page or not.
-
-## Improvements
-- `hugo server` now builds ~30%+ faster by rendering to memory instead of disk. To get the old behavior, start the server with `--renderToDisk=true`.
-- Hugo now supports dynamic reloading of the config file when watching.
-- We now use a custom-built `LazyFileReader` for reading file contents, which means we don't read media files in `/content` into memory anymore -- and file reading is now performed in parallel on multicore PCs. [#1181](https://github.com/spf13/hugo/issues/1181)
-- Hugo is now built with `Go 1.5` which, among many other improvements, have fixed the last known data race in Hugo. [#917](https://github.com/spf13/hugo/issues/917)
-- Paginator now also supports page groups. [#1274](https://github.com/spf13/hugo/issues/1274)
-- Markdown improvements:
- - Hugo now supports GitHub-flavoured markdown code fences for highlighting for `md`-files (Blackfriday rendered markdown) and `mmark` files (MMark rendered markdown). [#362](https://github.com/spf13/hugo/issues/362) [#1258](https://github.com/spf13/hugo/issues/1258)
- - Several new Blackfriday options are added:
- - Option to disable Blackfriday's `Smartypants`.
- - Option for Blackfriday to open links in a new window/tab. [#1220](https://github.com/spf13/hugo/issues/1220)
- - Option to disable Blackfriday's LaTeX style dashes [#1231](https://github.com/spf13/hugo/issues/1231)
- - Definition lists extension support.
-- `Scratch` now has built-in `map` support.
-- We now fall back to `link title` for the default page sort. [#1299](https://github.com/spf13/hugo/issues/1299)
-- Some notable new configuration options:
- - `IgnoreFiles` can be set with a list of Regular Expressions that matches files to be ignored during build. [#1189](https://github.com/spf13/hugo/issues/1189)
- - `PreserveTaxonomyNames`, when set to `true`, will preserve what you type as the taxonomy name both in the folders created and the taxonomy `key`, but it will be normalized for the URL. [#1180](https://github.com/spf13/hugo/issues/1180)
-- `hugo gen` can now generate man files, bash auto complete and markdown documentation
-- Hugo will now make suggestions when a command is mistyped
-- Shortcodes now have a boolean `.IsNamedParams` property. [#1597](https://github.com/spf13/hugo/pull/1597)
-
-## New Template Features
-- All template engines:
- - The new `dict` function that could be used to pass maps into a template. [#1463](https://github.com/spf13/hugo/pull/1463)
- - The new `pluralize` and `singularize` template funcs.
- - The new `base64Decode` and `base64Encode` template funcs.
- - The `sort` template func now accepts field/key chaining arguments and pointer values. [#1330](https://github.com/spf13/hugo/issues/1330)
- - Several fixes for `slicestr` and `substr`, most importantly, they now have full `utf-8`-support. [#1190](https://github.com/spf13/hugo/issues/1190) [#1333](https://github.com/spf13/hugo/issues/1333) [#1347](https://github.com/spf13/hugo/issues/1347)
- - The new `last` template function allows the user to select the last `N` items of a slice. [#1148](https://github.com/spf13/hugo/issues/1148)
- - The new `after` func allows the user to select the items after the `Nth` item. [#1200](https://github.com/spf13/hugo/pull/1200)
- - Add `time.Time` type support to the `where`, `ge`, `gt`, `le`, and `lt` template functions.
- - It is now possible to use constructs like `where Values ".Param.key" nil` to filter pages that doesn't have a particular parameter. [#1232](https://github.com/spf13/hugo/issues/1232)
- - `getJSON`/`getCSV`: Add retry on invalid content. [#1166](https://github.com/spf13/hugo/issues/1166)
- - The new `readDir` func lists local files. [#1204](https://github.com/spf13/hugo/pull/1204)
- - The new `safeJS` function allows the embedding of content into JavaScript contexts in Go templates.
- - Get the main site RSS link from any page by accessing the `.Site.RSSLink` property. [#1566](https://github.com/spf13/hugo/pull/1566)
-- Ace templates:
- - Base templates now also works in themes. [#1215](https://github.com/spf13/hugo/issues/1215).
- - And now also on Windows. [#1178](https://github.com/spf13/hugo/issues/1178)
-- Full support for Amber templates including all template functions.
-- A built-in template for Google Analytics. [#1505](https://github.com/spf13/hugo/pull/1505)
-- Hugo is now shipped with new built-in shortcodes: [#1576](https://github.com/spf13/hugo/issues/1576)
- - `youtube` for YouTube videos
- - `vimeo` for Vimeo videos
- - `gist` for GitHub gists
- - `tweet` for Twitter Tweets
- - `speakerdeck` for Speakerdeck slides
-
-## Bugfixes
-- Fix data races in page sorting and page reversal. These operations are now also cached. [#1293](https://github.com/spf13/hugo/issues/1293)
-- `page.HasMenuCurrent()` and `node.HasMenuCurrent()` now work correctly in multi-level nested menus.
-- Support `Fish and Chips` style section titles. Previously, this would end up as `Fish And Chips`. Now, the first character is made toupper, but the rest are preserved as-is. [#1176](https://github.com/spf13/hugo/issues/1176)
-- Hugo now removes superfluous p-tags around shortcodes. [#1148](https://github.com/spf13/hugo/issues/1148)
-
-## Notices
-- `hugo server` will watch by default now.
-- Some fields and methods were deprecated in `0.14`. These are now removed, so the error message isn't as friendly if you still use the old values. So please change:
- - `getJson` to `getJSON`, `getCsv` to `getCSV`, `safeHtml` to
- `safeHTML`, `safeCss` to `safeCSS`, `safeUrl` to `safeURL`, `Url` to `URL`,
- `UrlPath` to `URLPath`, `BaseUrl` to `BaseURL`, `Recent` to `Pages`.
-
-## Known Issues
-
-Using the Hugo v0.15 32-bit Windows or ARM binary, running `hugo server` would crash or hang due to a [memory alignment issue](https://golang.org/pkg/sync/atomic/#pkg-note-BUG) in [Afero](https://github.com/spf13/afero). The bug was discovered shortly after the v0.15.0 release and has since been [fixed](https://github.com/spf13/afero/pull/23) by @tpng. If you encounter this bug, you may either compile Hugo v0.16-DEV from source, or use the following solution/workaround:
-- **64-bit Windows users: Please use [hugo_0.15_windows_amd64.zip](https://github.com/spf13/hugo/releases/download/v0.15/hugo_0.15_windows_amd64.zip)** (amd64 == x86-64). It is only the 32-bit hugo_0.15_windows_386.zip that crashes/hangs (see #1621 and #1628).
-- **32-bit Windows and ARM users: Please run `hugo server --renderToDisk` as a workaround** until Hugo v0.16 is released (see [“hugo server” returns runtime error on armhf](https://discuss.gohugo.io/t/hugo-server-returns-runtime-error-on-armhf/2293) and #1716).
diff --git a/docs/content/en/news/0.16-relnotes/index.md b/docs/content/en/news/0.16-relnotes/index.md
deleted file mode 100644
index 92f6e54a022..00000000000
--- a/docs/content/en/news/0.16-relnotes/index.md
+++ /dev/null
@@ -1,248 +0,0 @@
----
-date: 2016-06-06T13:54:06-04:00
-description: "Hugo 0.16 is our best and biggest release ever. The Hugo community has outdone itself with continued performance improvements, beautiful themes and increased stability."
-title: "Hugo 0.16"
-categories: ["Releases"]
-aliases: [/0-16/]
----
-
-Hugo 0.16 is our best and biggest release ever. The Hugo community has
-outdone itself with continued performance improvements,
-[beautiful themes](http://themes.gohugo.io) for all types of sites from project
-sites to documentation to blogs to portfolios, and increased stability.
-
-This release represents **over 550 contributions by over 110 contributors** to
-the main Hugo code base. Since last release Hugo has **gained 3500 stars, 90
-contributors and 23 additional themes.**
-
-This release celebrates 3 years since {{< gh "@spf13" >}} wrote the first lines
-of Hugo. During those 3 years Hugo has accomplished some major milestones
-including...
-
-* 10,000+ stars on GitHub
-* 320+ contributors
-* 90+ themes
-* 1000s of happy websites
-* Many subprojects like {{< gh "@spf13/cobra">}}, {{< gh "@spf13/viper">}} and
- {{< gh "@spf13/afero">}} which have experienced broad usage across the Go
- ecosystem.
-
-{{< gh "@bep" >}} led the development of Hugo for the 3rd consecutive release
-with nearly half of the contributions to 0.16 in addition to his considerable
-contributions as lead maintainer. {{< gh "@anthonyfok" >}}, {{< gh
-"@DigitalCraftsman" >}}, {{< gh "@MooreReason" >}} all made significant
-contributions. A special thanks to {{< gh "@abourget" >}} for his considerable
-work on multilingual support. Due to its broad impact we wanted to spend more
-time testing it and it will be included in Hugo's next release.
-
-### Highlights
-
-**Partial Builds:** Prior to this release Hugo would always reread and rebuild
-the entire site. This release introduces support for reactive site building
-while watching (`hugo server`). Hugo will watch the filesystem for changes and
-only re-read the changed files. Depending on the files change Hugo will
-intelligently re-render only the needed portion of the site. Performance gains
-depend on the operation performed and size of the site. In our testing build
-times decreased anywhere from 10% to 99%.
-
-**Template Improvements:** Template improvements continue to be a mainstay of each Hugo release. Hugo 0.16 adds support for the new `block` keyword introduced in Go 1.6 -- think base templates with default sections -- as well as many new template functions.
-
-**Polish:** As Hugo matures releases will inevitably contain fewer huge new features. This release represents hundreds of small improvements across ever facet of Hugo which will make for a much better experience for all of our users. Worth mentioning here is the curious bug where live reloading didn't work in some editors on OS X, including the popular TextMate 2. This is now fixed. Oh, and now any error will exit with an error code, a big thing for automated deployments.
-
-### New Features
-* Support reading configuration variables from the OS environment {{}}
-* Add emoji support {{}}
-* Add `themesDir` option to configuration {{}}
-* Add support for Go 1.6 `block` keyword in templates {{}}
-* Partial static sync {{}}
-* Source file based relative linking (à la GitHub) {{}}
-* Add `ByLastmod` sort function to pages. {{}}
-* New templates functions:
- * `readFile` {{}}
- * `countwords` and `countrunes` {{}}
- * `default` {{}}
- * `hasPrefix` {{}}
- * `humanize` {{}}
- * `jsonify` {{}}
- * `md5` and `sha1` {{}}
- * `replaceRE` {{}}
- * `findRE` {{}}
- * `shuffle` {{}}
- * `slice` {{}}
- * `plainify` {{}}
-
-### Enhancements
-
-* Hugo now exits with error code on any error. This is a big thing for
- automated deployments. {{}}
-* Print error when `/index.html` is zero-length {{}}
-* Enable dirname and filename bash autocompletion for more flags {{}}
-* Improve error handling in commands {{}}
-* Add sanity checks for `hugo import jekyll` {{}}
-* Add description to `Page.Params` {{}}
-* Add async version of Google Analytics internal template {{}}
-* Add autostart option to YouTube shortcode {{}}
-* Set Date and Lastmod for main home page {{}}
-* Allow URL with extension in front matter {{}}
-* Add list support in Scratch {{}}
-* Add file option to gist shortcode {{}}
-* Add config layout and content directory CLI options {{}}
-* Add boolean value comparison to `where` template function {{}}
-* Do not write to cache when `ignoreCache` is set {{}}
-* Add option to disable rendering of 404 page {{}}
-* Mercurial is no longer needed to build Hugo {{}}
-* Do not create `robots.txt` by default {{}}
-* Disable syntax guessing for PygmentsCodeFences by default. To enable syntax
- guessing again, add the following to your config file:
- `PygmentsCodeFencesGuessSyntax = true` {{}}
-* Make `ByCount` sort consistently {{}}
-* Add `Scratch` to shortcode {{}}
-* Add support for symbolic links for content, layout, static, theme {{}}
-* Add '+' as one of the valid characters in URLs specified in the front matter {{}}
-* Make alias redirect output URLs relative when `RelativeURLs = true` {{}}
-* Hugo injects meta generator tag on homepage if missing {{}}
-
-### Fixes
-
-* Fix file change watcher for TextMate 2 and friends on OS X {{}}
-* Make dynamic reloading of config file reliable on all platform {{}}
-* Hugo now works on Linux/arm64 {{}}
-* `plainIDAnchors` now defaults to `true` {{}}
-* Win32 and ARM builds fixed {{}}
-* Copy static dir files without theme's static dir {{}}
-* Make `noTimes` command flag work {{}}
-* Change most global CLI flags into local ones {{}}
-* Remove transformation of menu URLs {{}}
-* Do not fail on unknown Jekyll file {{}}
-* Use absolute path when editing with editor {{}}
-* Fix hugo server "Watching for changes" path display {{}}
-* Do not strip special characters out of URLs {{}}
-* Fix `RSSLink` when uglyURLs are enabled {{}}
-* Get BaseURL from viper in server mode {{}}
-* Fix shortcode handling in RST {{}}
-* Use default sitemap configuration for homepage {{}}
-* Exit if specific port is unavailable in server mode {{}}
-* Fix regression in "section menus for lazy blogger" {{}}
-
-****
-
-## **0.15.0** November 25, 2015
-
-The v0.15.0 Hugo release brings a lot of polish to Hugo. Exactly 6 months after
-the 0.14 release, Hugo has seen massive growth and changes. Most notably, this
-is Hugo's first release under the Apache 2.0 license. With this license change
-we hope to expand the great community around Hugo and make it easier for our
-many users to contribute. This release represents over **377 contributions by
-87 contributors** to the main Hugo repo and hundreds of improvements to the
-libraries Hugo uses. Hugo also launched a [new theme
-showcase](http://themes.gohugo.io) and participated in
-[Hacktoberfest](https://hacktoberfest.digitalocean.com).
-
-Hugo now has:
-
-* 6700 (+2700) stars on GitHub
-* 235 (+75) contributors
-* 65 (+30) themes
-
-
-**Template Improvements:** This release takes Hugo to a new level of speed and
-usability. Considerable work has been done adding features and performance to
-the template system which now has full support of Ace, Amber and Go Templates.
-
-**Hugo Import:** Have a Jekyll site, but dreaming of porting it to Hugo? This
-release introduces a new `hugo import jekyll`command that makes this easier
-than ever.
-
-**Performance Improvements:** Just when you thought Hugo couldn't get any faster,
-Hugo continues to improve in speed while adding features. Notably Hugo 0.15
-introduces the ability to render and serve directly from memory resulting in
-30%+ lower render times.
-
-Huge thanks to all who participated in this release. A special thanks to
-{{< gh "@bep" >}} who led the development of Hugo this release again,
-{{< gh "@anthonyfok" >}},
-{{< gh "@eparis" >}},
-{{< gh "@tatsushid" >}} and
-{{< gh "@DigitalCraftsman" >}}.
-
-
-### New features
-* new `hugo import jekyll` command. {{< gh 1469 >}}
-* The new `Param` convenience method on `Page` and `Node` can be used to get the most specific parameter value for a given key. {{< gh 1462 >}}
-* Several new information elements have been added to `Page` and `Node`:
- * `RuneCount`: The number of [runes](http://blog.golang.org/strings) in the content, excluding any whitespace. This may be a good alternative to `.WordCount` for Japanese and other CJK languages where a word-split by spaces makes no sense. {{< gh 1266 >}}
- * `RawContent`: Raw Markdown as a string. One use case may be of embedding remarkjs.com slides.
- * `IsHome`: tells the truth about whether you're on the home page or not.
-
-### Improvements
-* `hugo server` now builds ~30%+ faster by rendering to memory instead of disk. To get the old behavior, start the server with `--renderToDisk=true`.
-* Hugo now supports dynamic reloading of the config file when watching.
-* We now use a custom-built `LazyFileReader` for reading file contents, which means we don't read media files in `/content` into memory anymore -- and file reading is now performed in parallel on multicore PCs. {{< gh 1181 >}}
-* Hugo is now built with `Go 1.5` which, among many other improvements, have fixed the last known data race in Hugo. {{< gh 917 >}}
-* Paginator now also supports page groups. {{< gh 1274 >}}
-* Markdown improvements:
- * Hugo now supports GitHub-flavoured markdown code fences for highlighting for `md`-files (Blackfriday rendered markdown) and `mmark` files (MMark rendered markdown). {{< gh 362 1258 >}}
- * Several new Blackfriday options are added:
- * Option to disable Blackfriday's `Smartypants`.
- * Option for Blackfriday to open links in a new window/tab. {{< gh 1220 >}}
- * Option to disable Blackfriday's LaTeX style dashes {{< gh 1231 >}}
- * Definition lists extension support.
-* `Scratch` now has built-in `map` support.
-* We now fall back to `link title` for the default page sort. {{< gh 1299 >}}
-* Some notable new configuration options:
- * `IgnoreFiles` can be set with a list of Regular Expressions that matches files to be ignored during build. {{< gh 1189 >}}
- * `PreserveTaxonomyNames`, when set to `true`, will preserve what you type as the taxonomy name both in the folders created and the taxonomy `key`, but it will be normalized for the URL. {{< gh 1180 >}}
-* `hugo gen` can now generate man files, bash auto complete and markdown documentation
-* Hugo will now make suggestions when a command is mistyped
-* Shortcodes now have a boolean `.IsNamedParams` property. {{< gh 1597 >}}
-
-### New Template Features
-* All template engines:
- * The new `dict` function that could be used to pass maps into a template. {{< gh 1463 >}}
- * The new `pluralize` and `singularize` template funcs.
- * The new `base64Decode` and `base64Encode` template funcs.
- * The `sort` template func now accepts field/key chaining arguments and pointer values. {{< gh 1330 >}}
- * Several fixes for `slicestr` and `substr`, most importantly, they now have full `utf-8`-support. {{< gh 1190 1333 1347 >}}
- * The new `last` template function allows the user to select the last `N` items of a slice. {{< gh 1148 >}}
- * The new `after` func allows the user to select the items after the `Nth` item. {{< gh 1200 >}}
- * Add `time.Time` type support to the `where`, `ge`, `gt`, `le`, and `lt` template functions.
- * It is now possible to use constructs like `where Values ".Param.key" nil` to filter pages that doesn't have a particular parameter. {{< gh 1232 >}}
- * `getJSON`/`getCSV`: Add retry on invalid content. {{< gh 1166 >}}
- * The new `readDir` func lists local files. {{< gh 1204 >}}
- * The new `safeJS` function allows the embedding of content into JavaScript contexts in Go templates.
- * Get the main site RSS link from any page by accessing the `.Site.RSSLink` property. {{< gh 1566 >}}
-* Ace templates:
- * Base templates now also works in themes. {{< gh 1215 >}}.
- * And now also on Windows. {{< gh 1178 >}}
-* Full support for Amber templates including all template functions.
-* A built-in template for Google Analytics. {{< gh 1505 >}}
-* Hugo is now shipped with new built-in shortcodes: {{< gh 1576 >}}
- * `youtube` for YouTube videos
- * `vimeo` for Vimeo videos
- * `gist` for GitHub gists
- * `tweet` for Twitter Tweets
- * `speakerdeck` for Speakerdeck slides
-
-
-### Bugfixes
-* Fix data races in page sorting and page reversal. These operations are now also cached. {{< gh 1293 >}}
-* `page.HasMenuCurrent()` and `node.HasMenuCurrent()` now work correctly in multi-level nested menus.
-* Support `Fish and Chips` style section titles. Previously, this would end up as `Fish And Chips`. Now, the first character is made toupper, but the rest are preserved as-is. {{< gh 1176 >}}
-* Hugo now removes superfluous p-tags around shortcodes. {{< gh 1148 >}}
-
-### Notices
-* `hugo server` will watch by default now.
-* Some fields and methods were deprecated in `0.14`. These are now removed, so the error message isn't as friendly if you still use the old values. So please change:
- * `getJson` to `getJSON`, `getCsv` to `getCSV`, `safeHtml` to
- `safeHTML`, `safeCss` to `safeCSS`, `safeUrl` to `safeURL`, `Url` to `URL`,
- `UrlPath` to `URLPath`, `BaseUrl` to `BaseURL`, `Recent` to `Pages`.
-
-### Known Issues
-
-Using the Hugo v0.15 32-bit Windows or ARM binary, running `hugo server` would crash or hang due to a [memory alignment issue](https://golang.org/pkg/sync/atomic/#pkg-note-BUG) in [Afero](https://github.com/spf13/afero). The bug was discovered shortly after the v0.15.0 release and has since been [fixed](https://github.com/spf13/afero/pull/23) by {{< gh "@tpng" >}}. If you encounter this bug, you may either compile Hugo v0.16-DEV from source, or use the following solution/workaround:
-
-* **64-bit Windows users: Please use [hugo_0.15_windows_amd64.zip](https://github.com/gohugoio/hugo/releases/download/v0.15/hugo_0.15_windows_amd64.zip)** (amd64 == x86-64). It is only the 32-bit hugo_0.15_windows_386.zip that crashes/hangs (see {{< gh 1621 >}} and {{< gh 1628 >}}).
-* **32-bit Windows and ARM users: Please run `hugo server --renderToDisk` as a workaround** until Hugo v0.16 is released (see [“hugo server” returns runtime error on armhf](https://discourse.gohugo.io/t/hugo-server-returns-runtime-error-on-armhf/2293) and {{< gh 1716 >}}).
diff --git a/docs/content/en/news/0.17-relnotes/index.md b/docs/content/en/news/0.17-relnotes/index.md
deleted file mode 100644
index 034e8e89155..00000000000
--- a/docs/content/en/news/0.17-relnotes/index.md
+++ /dev/null
@@ -1,93 +0,0 @@
----
-author: bep
-categories: ["Releases"]
-date: 2016-10-07T13:54:06-04:00
-description: "Hugo now supports multilingual sites with the most simple and elegant experience."
-link: ""
-title: "0.17: Hugo is going global"
-draft: false
-aliases: [/0-17/]
----
-Hugo is going global with our 0.17 release. We put a lot of thought into how we could extend Hugo
-to support multilingual websites with the most simple and elegant experience. Hugo's multilingual
-capabilities rival the best web and documentation software, but Hugo's experience is unmatched.
-If you have a single language website, the simple Hugo experience you already love is unchanged.
-Adding additional languages to your website is simple and straightforward. Hugo has been completely
-internally rewritten to be multilingual aware with translation and internationalization features
-embedded throughout Hugo.
-
-Hugo continues its trend of each release being faster than the last. It's quite a challenge to consistently add
-significant new functionality and simultaneously dramatically improve performance. {{}} has made it
-his personal mission to apply the Go mantra of "Enable more. Do less" to Hugo. Hugo's consistent improvement
-is a testament to his brilliance and his dedication to his craft. Hugo additionally benefits from the
-performance improvements from the Go team in the Go 1.7 release.
-
-This release represents **over 300 contributions by over 70 contributors** to
-the main Hugo code base. Since last release Hugo has **gained 2000 stars, 50 new
-contributors and 20 additional themes.**
-
-Hugo now has:
-
-* 12,000 stars on GitHub
-* 370+ contributors
-* 110+ themes
-
-{{}} continues to lead the project with the lionshare of contributions
-and reviews. A special thanks to {{}} and {{}} for their
-considerable work on multilingual support.
-
-A big welcome to newcomers {{}}, {{}} and
-{{}} for their critical contributions.
-
-### Highlights
-
-**Multilingual Support:**
-Hugo now supports multiple languages side-by-side. A single site can now have multiple languages rendered with
-full support for translation and i18n.
-
-**Performance:**
-Hugo is faster than ever! Hugo 0.17 is not only our fastest release, it's also the most efficient.
-Hugo 0.17 is **nearly twice as fast as Hugo 0.16** and uses about 10% less memory.
-This means that the same site will build in nearly half the time it took with Hugo 0.16.
-For the first time Hugo sites are averaging well under 1ms per rendered content.
-
-**Docs overhaul:**
-This release really focused on improving the documentation. [Gohugo.io](http://gohugo.io) is
-more accurate and complete than ever.
-
-**Support for macOS Sierra**
-
-### New Features
-* Multilingual support {{}}
-* Allow content expiration {{}}
-* New templates functions:
- * `querify` function to generate query strings inside templates {{}}
- * `htmlEscape` and `htmlUnescape` template functions {{}}
- * `time` converts a timestamp string into a time.Time structure {{}}
-
-### Enhancements
-
-* Render the shortcodes as late as possible {{}}
-* Remove unneeded casts in page.getParam {{}}
-* Automatic page date fallback {{}}
-* Enable safeHTMLAttr {{}}
-* Add TODO list support for markdown {{}}
-* Make absURL and relURL accept any type {{}}
-* Suppress 'missing static' error {{}}
-* Make summary, wordcount etc. more efficient {{}}
-* Better error reporting in `hugo convert` {{}}
-* Reproducible builds thanks to govendor {{}}
-
-### Fixes
-
-* Fix shortcode in markdown headers {{}}
-* Explicitly bind livereload to hugo server port {{}}
-* Fix Emojify for certain text patterns {{}}
-* Normalize file name to NFC {{}}
-* Ignore emacs temp files {{}}
-* Handle symlink change event {{}}
-* Fix panic when using URLize {{}}
-* `hugo import jekyll`: Fixed target path location check {{}}
-* Return all errors from casting in templates {{}}
-* Fix paginator counter on x86-32 {{}}
-* Fix half-broken self-closing shortcodes {{}}
diff --git a/docs/content/en/news/0.18-relnotes/index.md b/docs/content/en/news/0.18-relnotes/index.md
deleted file mode 100644
index 5aaab9ffe07..00000000000
--- a/docs/content/en/news/0.18-relnotes/index.md
+++ /dev/null
@@ -1,114 +0,0 @@
----
-date: 2016-12-30T13:54:02-04:00
-categories: ["Releases"]
-description: "The primary new feature in Hugo 0.18 is that every piece of content is now a Page."
-link: ""
-title: "Hugo 0.18"
-draft: false
-author: bep
-aliases: [/0-18/]
----
-
-Hugo 0.18.1 is a bug fix release fixing some issues introduced in Hugo 0.18:
-
-* Fix 32-bit binaries {{}}
-* Fix issues with `preserveTaxonomyNames` {{}}
-* Fix `.URL` for taxonomy pages when `uglyURLs=true` {{}}
-* Fix `IsTranslated` and `Translations` for node pages {{}}
-* Make template error messages more verbose {{}}
-
-## **0.18.0** December 19th 2016
-
-Today, we're excited to release the much-anticipated Hugo 0.18!
-
-We're heading towards the end of the year 2016, and we can look back on three releases and a steady growing community around the project.
-This release includes **over 220 contributions by nearly 50 contributors** to the main codebase.
-Since the last release, Hugo has **gained 1750 stars and 27 additional themes**.
-
-Hugo now has:
-
-- 13750+ stars
-- 408+ contributors
-- 137+ themes
-
-{{< gh "@bep" >}} once again took the lead of Hugo and contributed a significant amount of additions.
-Also a big shoutout to {{< gh "@digitalcraftsman" >}} for his relentless work on keeping the documentation and the themes site in pristine condition,
-and also a big thanks to {{< gh "@moorereason" >}} and {{< gh "@bogem" >}} for their contributions.
-
-We wish you all a Merry Christmas and a Happy New Year.
-*The Hugo team*
-
-### Highlights
-
-The primary new feature in Hugo 0.18 is that every piece of content is now a `Page` ({{}}). This means that every page, including the homepage, can have a content file with front matter.
-
-Not only is this a much simpler model to understand, it is also faster and paved the way for several important new features:
-
-* Enable proper titles for Nodes {{}}
-* Sitemap.xml should include nodes, as well as pages {{}}
-* Document homepage content workaround {{}}
-* Allow home page to be easily authored in markdown {{}}
-* Minimalist website with homepage as content {{}}
-
-Hugo again continues its trend of each release being faster than the last. It's quite a challenge to consistently add significant new functionality and simultaneously dramatically improve performance. Running [this benchmark]( https://github.com/bep/hugo-benchmark) with [these sites](https://github.com/bep/hugo-benchmark/tree/master/sites) (renders to memory) shows about 60% reduction in time spent and 30% reduction in memory usage compared to Hugo 0.17.
-
-### Other New Features
-
-* Every `Page` now has a `Kind` property. Since everything is a `Page` now, the `Kind` is used to differentiate different kinds of pages.
- Possible values are `page`, `home`, `section`, `taxonomy`, and `taxonomyTerm`.
- (Internally, we also define `RSS`, `sitemap`, `robotsTXT`, and `404`, but those have no practical use for end users at the moment since they are not included in any collections.)
-* Add a `GitInfo` object to `Page` if `enableGitInfo` is set. It then also sets `Lastmod` for the given `Page` to the author date provided by Git. {{}}
-* Implement support for alias templates {{}}
-* New template functions:
- * Add `imageConfig` function {{}}
- * Add `sha256` function {{}}
- * Add `partialCached` template function {{}}
-* Add shortcode to display Instagram images {{}}
-* Add `noChmod` option to disable perm sync {{}}
-* Add `quiet` build mode {{}}
-
-
-### Notices
-
-* `.Site.Pages` will now contain *several kinds of pages*, including regular pages, sections, taxonomies, and the home page.
- If you want a specific kind of page, you can filter it with `where` and `Kind`.
- `.Site.RegularPages` is a shortcut to the page collection you have been used to getting.
-* `RSSlink` is now deprecated. Use `RSSLink` instead.
- Note that in Hugo 0.17 both of them existed, so there is a fifty-fifty chance you will not have to do anything
- (if you use a theme, the chance is close to 0), and `RSSlink` will still work for two Hugo versions.
-
-### Fixes
-
-* Revise the `base` template lookup logic so it now better matches the behavior of regular templates, making it easier to override the master templates from the theme {{}}
-* Add workaround for `block` template crash.
- Block templates are very useful, but there is a bug in Go 1.6 and 1.7 which makes the template rendering crash if you use the block template in more complex scenarios.
- This is fixed in the upcoming Go 1.8, but Hugo adds a temporary workaround in Hugo 0.18. {{}}
-* All the `Params` configurations are now case insensitive {{}} {{}} {{}}
-* Make RawContent raw again {{}}
-* Fix archetype title and date handling {{}}
-* Fix TOML archetype parsing in `hugo new` {{}}
-* Fix page sorting when weight is zero {{}}
-* Fix page names that contain dot {{}}
-* Fix RSS Title regression {{}}
-* Handle ToC before handling shortcodes {{}}
-* Only watch relevant themes dir {{}}
-* Hugo new content creates TOML slices with closing bracket on new line {{}}
-
-### Improvements
-
-* Add page information to error logging in rendering {{}}
-* Deprecate `RSSlink` in favor of `RSSLink`
-* Make benchmark command more useful {{}}
-* Consolidate the `Param` methods {{}}
-* Allow to set cache dir in config file
-* Performance improvements:
- * Avoid repeated Viper loads of `sectionPagesMenu` {{}}
- * Avoid reading from Viper for path and URL funcs {{}}
- * Add `partialCached` template function. This can be a significant performance boost if you have complex partials that does not need to be rerendered for every page. {{}}
-
-### Documentation Updates
-
-* Update roadmap {{}}
-* Update multilingual example {{}}
-* Add a "Deployment with rsync" tutorial page {{}}
-* Refactor `/docs` to use the `block` keyword {{}}
diff --git a/docs/content/en/news/0.19-relnotes/index.md b/docs/content/en/news/0.19-relnotes/index.md
deleted file mode 100644
index 073b474957b..00000000000
--- a/docs/content/en/news/0.19-relnotes/index.md
+++ /dev/null
@@ -1,83 +0,0 @@
----
-date: 2017-02-27T13:53:58-04:00
-categories: ["Releases"]
-description: "Hugo 0.19 brings native Emacs Org-mode content support, and Hugo has its own Twitter account"
-link: ""
-title: "Hugo 0.19"
-draft: false
-author: budparr
-aliases: [/0-19/]
----
-
-We're happy to announce the first release of Hugo in 2017.
-
-This release represents **over 180 contributions by over 50 contributors** to the main Hugo code base. Since last release Hugo has **gained 1450 stars, 35 new contributors, and 15 additional themes.**
-
-Hugo now has:
-
-* 15200+ stars
-* 470+ contributors
-* 151+ themes
-
-Furthermore, Hugo has its own Twitter account ([@gohugoio](https://twitter.com/gohugoio)) where we share bite-sized news and themes from the Hugo community.
-
-{{< gh "@bep" >}} leads the Hugo development and once again contributed a significant amount of additions. Also a big shoutout to {{< gh "@chaseadamsio" >}} for the Emacs Org-mode support, {{< gh "@digitalcraftsman" >}} for his relentless work on keeping the documentation and the themes site in pristine condition, {{< gh "@fj" >}}for his work on revising the `params` handling in Hugo, and {{< gh "@moorereason" >}} and {{< gh "@bogem" >}} for their ongoing contributions.
-
-### Highlights
-
-Hugo `0.19` brings native Emacs Org-mode content support ({{}}), big thanks to {{< gh "@chaseadamsio" >}}.
-
-Also, a considerably amount of work have been put into cleaning up the Hugo source code, in an issue titled [Refactor the globals out of site build](https://github.com/gohugoio/hugo/issues/2701). This is not immediately visible to the Hugo end user, but will speed up future development.
-
-Hugo `0.18` was bringing full-parallel page rendering, so workarounds depending on rendering order did not work anymore, and pages with duplicate target paths (common examples would be `/index.md` or `/about/index.md`) would now conflict with the home page or the section listing.
-
-With Hugo `0.19`, you can control this behaviour by turning off page types you do not want ({{}}). In its most extreme case, if you put the below setting in your [`config.toml`](/getting-started/configuration/), you will get **nothing!**:
-
-{{< code-toggle file="config" >}}
-disableKinds = ["page", "home", "section", "taxonomy", "taxonomyTerm", "RSS", "sitemap", "robotsTXT", "404"]
-{{< /code-toggle >}}
-
-### Other New Features
-
-* Add ability to sort pages by front matter parameters, enabling easy custom "top 10" page lists. {{}}
-* Add `truncate` template function {{}}
-* Add `now` function, which replaces the now deprecated `.Now` {{}}
-* Make RSS item limit configurable {{}}
-
-### Enhancements
-
-* Enhance `.Param` to permit arbitrarily nested parameter references {{}}
-* Use `Page.Params` more consistently when adding metadata {{}}
-* The `sectionPagesMenu` feature ("Section menu for the lazy blogger") is now integrated with the section content pages. {{}}
-* Hugo `0.19` is compiled with Go 1.8!
-* Make template funcs like `findRE` and friends more liberal in what argument types they accept {{}} {{}}
-* Improve generation of OpenGraph date tags {{}}
-
-### Notes
-
-* `sourceRelativeLinks` is now deprecated and will be removed in Hugo `0.21` if no one is stepping up to the plate and fixes and maintains this feature. {{}}
-
-### Fixes
-
-* Fix `.Site.LastChange` on sites where the default sort order is not chronological. {{}}
-* Fix regression of `.Truncated` evaluation in manual summaries. {{}}
-* Fix `preserveTaxonomyNames` regression {{}}
-* Fix issue with taxonomies when only some have content page {{}}
-* Fix instagram shortcode panic on invalid ID {{}}
-* Fix subtle data race in `getJSON` {{}}
-* Fix deadlock in cached partials {{}}
-* Avoid double-encoding of paginator URLs {{}}
-* Allow tilde in URLs {{}}
-* Fix `.Site.Pages` handling on live reloads {{}}
-* `UniqueID` now correctly uses the fill file path from the content root to calculate the hash, and is finally ... unique!
-* Discard current language based on `.Lang()`, go get translations correct for paginated pages. {{}}
-* Fix infinite loop in template AST handling for recursive templates {{}}
-* Fix issue with watching when config loading fails {{}}
-* Correctly flush the imageConfig on live-reload {{}}
-* Fix parsing of TOML arrays in front matter {{}}
-
-### Docs
-
-* Add tutorial "How to use Google Firebase to host a Hugo site" {{}}
-* Improve documentation for menu rendering {{}}
-* Revise GitHub Pages deployment tutorial {{}}
diff --git a/docs/content/en/news/0.20-relnotes/index.md b/docs/content/en/news/0.20-relnotes/index.md
deleted file mode 100644
index 75e944a6c3e..00000000000
--- a/docs/content/en/news/0.20-relnotes/index.md
+++ /dev/null
@@ -1,72 +0,0 @@
----
-date: 2017-04-10T13:53:58-04:00
-categories: ["Releases"]
-description: "Hugo 0.20 introduces the powerful and long sought after feature Custom Output Formats"
-link: ""
-title: "Hugo 0.20"
-draft: false
-author: bep
-aliases: [/0-20/]
----
-
-Hugo `0.20` introduces the powerful and long sought after feature [Custom Output Formats](http://gohugo.io/extras/output-formats/); Hugo isn’t just that “static HTML with an added RSS feed” anymore. _Say hello_ to calendars, e-book formats, Google AMP, and JSON search indexes, to name a few ( [#2828](//github.com/gohugoio/hugo/issues/2828) ).
-
-This release represents **over 180 contributions by over 30 contributors** to the main Hugo code base. Since last release Hugo has **gained 1100 stars, 20 new contributors and 5 additional themes.**
-
-Hugo now has:
-
-* 16300+ stars
-* 495+ contributors
-* 156+ themes
-
-[@bep](//github.com/bep) still leads the Hugo development with his witty Norwegian humor, and once again contributed a significant amount of additions. Also a big shoutout to [@digitalcraftsman](//github.com/digitalcraftsman) for his relentless work on keeping the documentation and the themes site in pristine condition, and [@moorereason](//github.com/moorereason) and [@bogem](//github.com/bogem) for their ongoing contributions.
-
-## Other Highlights
-
-[@bogem](//github.com/bogem) has also contributed TOML as an alternative and much simpler format for language/i18n files ([#3200](//github.com/gohugoio/hugo/issues/3200)). A feature you will appreciate when you start to work on larger translations.
-
-Also, there have been some important updates in the Emacs Org-mode handling: [@chaseadamsio](//github.com/chaseadamsio) has fixed the newline-handling ( [#3126](//github.com/gohugoio/hugo/issues/3126) ) and [@clockoon](//github.com/clockoon) has added basic footnote support.
-
-Worth mentioning is also the ongoing work that [@rdwatters](//github.com/rdwatters) and [@budparr](//github.com/budparr) is doing to re-do the [gohugo.io](https://gohugo.io/) site, including a total restructuring and partial rewrite of the documentation. It is getting close to finished, and it looks fantastic!
-
-## Notes
-
-* `RSS` description in the built-in template is changed from full `.Content` to `.Summary`. This is a somewhat breaking change, but is what most people expect from their RSS feeds. If you want full content, please provide your own RSS template.
-* The deprecated `.RSSlink` is now removed. Use `.RSSLink`.
-* `RSSUri` is deprecated and will be removed in a future Hugo version, replace it with an output format definition.
-* The deprecated `.Site.GetParam` is now removed, use `.Site.Param`.
-* Hugo does no longer append missing trailing slash to `baseURL` set as a command line parameter, making it consistent with how it behaves from site config. [#3262](//github.com/gohugoio/hugo/issues/3262)
-
-## Enhancements
-
-* Hugo `0.20` is built with Go 1.8.1.
-* Add `.Site.Params.mainSections` that defaults to the section with the most pages. Plan is to get themes to use this instead of the hardcoded `blog` in `where` clauses. [#3206](//github.com/gohugoio/hugo/issues/3206)
-* File extension is now configurable. [#320](//github.com/gohugoio/hugo/issues/320)
-* Impove `markdownify` template function performance. [#3292](//github.com/gohugoio/hugo/issues/3292)
-* Add taxonomy terms’ pages to `.Data.Pages` [#2826](//github.com/gohugoio/hugo/issues/2826)
-* Change `RSS` description from full `.Content` to `.Summary`.
-* Ignore “.” dirs in `hugo --cleanDestinationDir` [#3202](//github.com/gohugoio/hugo/issues/3202)
-* Allow `jekyll import` to accept both `2006-01-02` and `2006-1-2` date format [#2738](//github.com/gohugoio/hugo/issues/2738)
-* Raise the default `rssLimit` [#3145](//github.com/gohugoio/hugo/issues/3145)
-* Unify section list vs single template lookup order [#3116](//github.com/gohugoio/hugo/issues/3116)
-* Allow `apply` to be used with the built-in Go template funcs `print`, `printf` and `println`. [#3139](//github.com/gohugoio/hugo/issues/3139)
-
-## Fixes
-
-* Fix deadlock in `getJSON` [#3211](//github.com/gohugoio/hugo/issues/3211)
-* Make sure empty terms pages are created. [#2977](//github.com/gohugoio/hugo/issues/2977)
-* Fix base template lookup order for sections [#2995](//github.com/gohugoio/hugo/issues/2995)
-* `URL` fixes:
- * Fix pagination URLs with `baseURL` with sub-root and `canonifyUrls=false` [#1252](//github.com/gohugoio/hugo/issues/1252)
- * Fix pagination URL for resources with “.” in name [#2110](//github.com/gohugoio/hugo/issues/2110) [#2374](//github.com/gohugoio/hugo/issues/2374) [#1885](//github.com/gohugoio/hugo/issues/1885)
- * Handle taxonomy names with period [#3169](//github.com/gohugoio/hugo/issues/3169)
- * Handle `uglyURLs` ambiguity in `Permalink` [#3102](//github.com/gohugoio/hugo/issues/3102)
- * Fix `Permalink` for language-roots wrong when `uglyURLs` is `true` [#3179](//github.com/gohugoio/hugo/issues/3179)
- * Fix misc case issues for `URLs` [#1641](//github.com/gohugoio/hugo/issues/1641)
- * Fix for taxonomies URLs when `uglyUrls=true` [#1989](//github.com/gohugoio/hugo/issues/1989)
- * Fix empty `RSSLink` for list pages with content page. [#3131](//github.com/gohugoio/hugo/issues/3131)
-* Correctly identify regular pages on the form “my_index_page.md” [#3234](//github.com/gohugoio/hugo/issues/3234)
-* `Exit -1` on `ERROR` in global logger [#3239](//github.com/gohugoio/hugo/issues/3239)
-* Document hugo `help command` [#2349](//github.com/gohugoio/hugo/issues/2349)
-* Fix internal `Hugo` version handling for bug fix releases. [#3025](//github.com/gohugoio/hugo/issues/3025)
-* Only return `RSSLink` for pages that actually have a RSS feed. [#1302](//github.com/gohugoio/hugo/issues/1302)
\ No newline at end of file
diff --git a/docs/content/en/news/0.20.1-relnotes/index.md b/docs/content/en/news/0.20.1-relnotes/index.md
deleted file mode 100644
index 109737bb355..00000000000
--- a/docs/content/en/news/0.20.1-relnotes/index.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-date: 2017-04-13T13:53:58-04:00
-categories: ["Releases"]
-description: "Hugo 0.20.1 is a bug fix release, fixing some important regressions introduced in 0.20"
-link: ""
-title: "Hugo 0.20.1"
-draft: false
-author: bep
-aliases: [/0-20-1/]
----
-
-* Fix logic for base template in work dir vs in the theme [#3323](//github.com/gohugoio/hugo/issues/3323)
-* camelCased templates (partials, shortcodes etc.) not found [#3333](//github.com/gohugoio/hugo/issues/3333)
-* Live-reload fails with `_index.md` with paginator [#3315](//github.com/gohugoio/hugo/issues/3315)
-* `rssURI` WARNING always shown [#3319](//github.com/gohugoio/hugo/issues/3319)
\ No newline at end of file
diff --git a/docs/content/en/news/0.20.2-relnotes/index.md b/docs/content/en/news/0.20.2-relnotes/index.md
deleted file mode 100644
index 2e67d20eaf9..00000000000
--- a/docs/content/en/news/0.20.2-relnotes/index.md
+++ /dev/null
@@ -1,47 +0,0 @@
----
-date: 2017-04-16T13:53:58-04:00
-categories: ["Releases"]
-description: "Hugo 0.20.2 adds support for plain text partials included into HTML templates"
-link: ""
-title: "Hugo 0.20.2"
-draft: false
-author: bep
-aliases: [/0-20-2/]
----
-
-Hugo `0.20.2` adds support for plain text partials included into `HTML` templates. This was a side-effect of the big new [Custom Output Format](https://gohugo.io/extras/output-formats/) feature in `0.20`, and while the change was intentional and there was an ongoing discussion about fixing it in [#3273](//github.com/gohugoio/hugo/issues/3273), it did break some themes. There were valid workarounds for these themes, but we might as well get it right.
-
-The most obvious use case for this is inline `CSS` styles, which you now can do without having to name your partials with a `html` suffix.
-
-A simple example:
-
-In `layouts/partials/mystyles.css`:
-
- body {
- background-color: {{ .Param "colors.main" }}
- }
-
-Then in `config.toml` (note that by using the `.Param` lookup func, we can override the color in a page’s front matter if we want):
-
-{{< code-toggle file="config" >}}
-[params]
-[params.colors]
-main = "green"
-text = "blue"
-{{< /code-toggle >}}
-
-And then in `layouts/partials/head.html` (or the partial used to include the head section into your layout):
-
-
-
-
-
-Of course, `0.20` also made it super-easy to create external `CSS` stylesheets based on your site and page configuration. A simple example:
-
-Add “CSS” to your home page’s `outputs` list, create the template `/layouts/index.css` using Go template syntax for the dynamic parts, and then include it into your `HTML` template with:
-
- {{ with .OutputFormats.Get "css" }}
-
- {{ end }}`
\ No newline at end of file
diff --git a/docs/content/en/news/0.20.3-relnotes/index.md b/docs/content/en/news/0.20.3-relnotes/index.md
deleted file mode 100644
index c79d9b202a0..00000000000
--- a/docs/content/en/news/0.20.3-relnotes/index.md
+++ /dev/null
@@ -1,26 +0,0 @@
----
-date: 2017-04-24T13:53:58-04:00
-categories: ["Releases"]
-description: "This is a bug-fix release with one important fix. But it also adds some harness around GoReleaser"
-link: ""
-title: "Hugo 0.20.3"
-draft: false
-author: bep
-aliases: [/0-20-3/]
----
-
-This is a bug-fix release with one important fix. But it also adds some harness around [GoReleaser](https://github.com/goreleaser/goreleaser) to automate the Hugo release process. Big thanks to [@caarlos0](https://github.com/caarlos0) for great and super-fast support fixing issues along the way.
-
-Hugo now has:
-
-* 16619+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 458+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 156+ [themes](http://themes.gohugo.io/)
-
-## Enhancement
-
-* Automate the Hugo release process [550eba64](https://github.com/gohugoio/hugo/commit/550eba64705725eb54fdb1042e0fb4dbf6f29fd0) [@bep](https://github.com/bep) [#3358](https://github.com/gohugoio/hugo/issues/3358)
-
-## Fix
-
-* Fix handling of zero-length files [9bf5c381](https://github.com/gohugoio/hugo/commit/9bf5c381b6b3e69d4d8dbfd7a40074ac44792bbf) [@bep](https://github.com/bep) [#3355](https://github.com/gohugoio/hugo/issues/3355)
\ No newline at end of file
diff --git a/docs/content/en/news/0.20.4-relnotes/index.md b/docs/content/en/news/0.20.4-relnotes/index.md
deleted file mode 100644
index 2fde30e14ab..00000000000
--- a/docs/content/en/news/0.20.4-relnotes/index.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-date: 2017-04-24T17:53:58-04:00
-categories: ["Releases"]
-description: "This is the second bug-fix release of the day, fixing a couple of issues related to the new release scripts"
-link: ""
-title: "Hugo 0.20.4"
-draft: false
-author: bep
-aliases: [/0-20-4/]
----
-
-This is the second bug-fix release of the day, fixing a couple of issues related to the new release scripts.
-
-Hugo now has:
-
-* 16626+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 457+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 156+ [themes](http://themes.gohugo.io/)
-
-## Fixes
-
-* Fix statically linked binaries [275bcf56](https://github.com/gohugoio/hugo/commit/275bcf566c7cb72367d4423cf4810319311ff680) [@munnerz](https://github.com/munnerz) [#3382](https://github.com/gohugoio/hugo/issues/3382)
-* Filename change in Hugo 0.20.3 binaries [#3385](https://github.com/gohugoio/hugo/issues/3385)
-* Fix version calculation [cb3c6b6f](https://github.com/gohugoio/hugo/commit/cb3c6b6f7670f85189a4a3637e7132901d1ed6e9) [@bep](https://github.com/bep)
\ No newline at end of file
diff --git a/docs/content/en/news/0.20.5-relnotes/index.md b/docs/content/en/news/0.20.5-relnotes/index.md
deleted file mode 100644
index eaed2783289..00000000000
--- a/docs/content/en/news/0.20.5-relnotes/index.md
+++ /dev/null
@@ -1,12 +0,0 @@
----
-date: 2017-04-25T17:53:58-04:00
-categories: ["Releases"]
-description: ""
-link: ""
-title: "Hugo 0.20.5"
-draft: false
-author: bep
-aliases: [/0-20-5/]
----
-
-This is a bug-fix release which fixes the version number of `0.20.4` (which wrongly shows up as `0.21-DEV`) ([#3388](https://github.com/gohugoio/hugo/issues/3388)).
\ No newline at end of file
diff --git a/docs/content/en/news/0.20.6-relnotes/index.md b/docs/content/en/news/0.20.6-relnotes/index.md
deleted file mode 100644
index 52189092a78..00000000000
--- a/docs/content/en/news/0.20.6-relnotes/index.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-date: 2017-04-27T17:53:58-04:00
-categories: ["Releases"]
-description: ""
-link: ""
-title: "Hugo 0.20.6"
-draft: false
-author: bep
-aliases: [/0-20-6/]
----
-
-There have been some [shouting on discuss.gohugo.io](https://discuss.gohugo.io/t/index-md-is-generated-in-subfolder-index-index-html-hugo-0-20/6338/15) about some broken sites after the release of Hugo `0.20`. This release reintroduces the old behaviour, making `/my-blog-post/index.md` work as expected.
-
-Hugo now has:
-
-* 16675+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 456+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 156+ [themes](http://themes.gohugo.io/)
-
-## Fixes
-
-* Avoid index.md in /index/index.html [#3396](https://github.com/gohugoio/hugo/issues/3396)
-* Make missing GitInfo a WARNING [b30ca4be](https://github.com/gohugoio/hugo/commit/b30ca4bec811dbc17e9fd05925544db2b75e0e49) [@bep](https://github.com/bep) [#3376](https://github.com/gohugoio/hugo/issues/3376)
-* Fix some of the fpm fields for deb [3bd1d057](https://github.com/gohugoio/hugo/commit/3bd1d0571d5f2f6bf0dc8f90a8adf2dbfcb2fdfd) [@anthonyfok](https://github.com/anthonyfok)
\ No newline at end of file
diff --git a/docs/content/en/news/0.20.7-relnotes/index.md b/docs/content/en/news/0.20.7-relnotes/index.md
deleted file mode 100644
index 50ac365d5b0..00000000000
--- a/docs/content/en/news/0.20.7-relnotes/index.md
+++ /dev/null
@@ -1,23 +0,0 @@
----
-date: 2017-05-03T17:53:58-04:00
-categories: ["Releases"]
-description: "This just fixes an issue with the release scripts, no change in the binaries"
-link: ""
-title: "Hugo 0.20.7"
-draft: false
-author: bep
-aliases: [/0-20-7/]
----
-
-This just fixes an issue with the release scripts, no change in the binaries.
-
-
-Hugo now has:
-
-* 16782+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 458+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 156+ [themes](http://themes.gohugo.io/)
-
-## Fix
-
-* Push the tag before goreleaser is run [3682bf52](https://github.com/gohugoio/hugo/commit/3682bf527989e86d9da32d76809306cb576383e8) [@bep](https://github.com/bep) [#3405](https://github.com/gohugoio/hugo/issues/3405)
\ No newline at end of file
diff --git a/docs/content/en/news/0.21-relnotes/index.md b/docs/content/en/news/0.21-relnotes/index.md
deleted file mode 100644
index aae1fd0b4b8..00000000000
--- a/docs/content/en/news/0.21-relnotes/index.md
+++ /dev/null
@@ -1,105 +0,0 @@
----
-date: 2017-05-22T17:53:58-04:00
-categories: ["Releases"]
-description: "Hugo 0.21 brings full support for shortcodes per Output Format, the last vital piece of that puzzle"
-link: ""
-title: "Hugo 0.21"
-draft: false
-author: bep
-aliases: [/0-21/]
----
-
-Hugo `0.21` brings full support for shortcodes per [Output Format](https://gohugo.io/extras/output-formats/) ([#3220](https://github.com/gohugoio/hugo/issues/3220)), the last vital piece of that puzzle. This is especially useful for `Google AMP` with its many custom media tags.
-
-This release represents **126 contributions by 29 contributors** to the main Hugo code base. Since last main release Hugo has **gained 850 stars and 7 additional themes**.
-
-Hugo now has:
-
-* 17156+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 457+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 163+ [themes](http://themes.gohugo.io/)
-
-[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@moorereason](https://github.com/moorereason), [@bogem](https://github.com/bogem), and [@munnerz](https://github.com/munnerz) for their ongoing contributions. And as always a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the documentation and the themes site in pristine condition.
-
-## Other Highlights
-
-On a more technical side, [@moorereason](https://github.com/moorereason) and [@bep](https://github.com/bep) have introduced namespaces for Hugo's many template funcs ([#3042](https://github.com/gohugoio/hugo/issues/3042) ). There are so many now, and adding more into that big pile would be a sure path toward losing control. Now they are nicely categorised into namespaces with its own tests and examples, with an API that the documentation site can use to make sure it is correct and up-to-date.
-
-## Notes
-
-* The deprecated `.Extension`, `.Now` and `.TargetPath` will now `ERROR` [544f0a63](https://github.com/gohugoio/hugo/commit/544f0a6394b0e085d355e8217fc5bb3d96c12a98) [@bep](https://github.com/bep)
-* The config settings and flags `disable404`, `disableRSS`, `disableSitemap`, `disableRobotsTXT` are now deprecated. Use `disableKinds`. [5794a265](https://github.com/gohugoio/hugo/commit/5794a265b41ffdeebfd8485eecf65cf4088d49d6) [@bep](https://github.com/bep) [#3345](https://github.com/gohugoio/hugo/issues/3345)
-
-## Enhancements
-
-### Templates
-
-* Log a WARNING on wrong usage of `IsSet` [38661c17](https://github.com/gohugoio/hugo/commit/38661c17bb8c31c9f31ee18f8eba5e3bfddd5574) [@moorereason](https://github.com/moorereason) [#3092](https://github.com/gohugoio/hugo/issues/3092)
-* Add support for ellipsed paginator navigator, making paginators with lots of pages more compact [b6ea492b](https://github.com/gohugoio/hugo/commit/b6ea492b7a6325d04d44eeb00a990a3a0e29e0c0) [@bep](https://github.com/bep) [#3466](https://github.com/gohugoio/hugo/issues/3466)
-* Add support for interfaces to `intersect` [f1c29b01](https://github.com/gohugoio/hugo/commit/f1c29b017bbd88e701cd5151dd186e868672ef89) [@moorereason](https://github.com/moorereason) [#1952](https://github.com/gohugoio/hugo/issues/1952)
-* Add `NumFmt` function [93b3b138](https://github.com/gohugoio/hugo/commit/93b3b1386714999d716e03b131f77234248f1724) [@moorereason](https://github.com/moorereason) [#1444](https://github.com/gohugoio/hugo/issues/1444)
-* Add template function namespaces [#3418](https://github.com/gohugoio/hugo/issues/3418) [#3042](https://github.com/gohugoio/hugo/issues/3042) [@moorereason](https://github.com/moorereason) [@bep](https://github.com/bep)
-* Add translation links to the default sitemap template [90d3fbf1](https://github.com/gohugoio/hugo/commit/90d3fbf1da93a279cfe994a226ae82cf5441deab) [@rayjolt](https://github.com/rayjolt) [#2569](https://github.com/gohugoio/hugo/issues/2569)
-* Allow text partials in HTML templates and the other way around [1cf29200](https://github.com/gohugoio/hugo/commit/1cf29200b4bb0a9c006155ec76759b7f4b1ad925) [@bep](https://github.com/bep) [#3273](https://github.com/gohugoio/hugo/issues/3273)
-
-### Output
-
-* Refactor site rendering with an "output format context". In this release, this is used for shortcode handling only, but this paves the way for future niceness [1e4d082c](https://github.com/gohugoio/hugo/commit/1e4d082cf5b92fedbc60b1b4f0e9d1ee6ec45e33) [@bep](https://github.com/bep) [#3397](https://github.com/gohugoio/hugo/issues/3397) [2bcbf104](https://github.com/gohugoio/hugo/commit/2bcbf104006e0ec03be4fd500f2519301d460f8c) [@bep](https://github.com/bep) [#3220](https://github.com/gohugoio/hugo/issues/3220)
-
-
-### Core
-
-* Handle `shortcode` per `Output Format` [af72db80](https://github.com/gohugoio/hugo/commit/af72db806f2c1c0bf1dfe5832275c41eeba89906) [@bep](https://github.com/bep) [#3220](https://github.com/gohugoio/hugo/issues/3220)
-* Improve shortcode error message [58d9cbd3](https://github.com/gohugoio/hugo/commit/58d9cbd31bcf7c296a39860fd7e566d10faaff28) [@bep](https://github.com/bep)
-* Avoid `index.md` in `/index/index.html` [fea4fd86](https://github.com/gohugoio/hugo/commit/fea4fd86a324bf9679df23f8289887d91b42e919) [@bep](https://github.com/bep) [#3396](https://github.com/gohugoio/hugo/issues/3396)
-* Make missing `GitInfo` a `WARNING` [5ad2f176](https://github.com/gohugoio/hugo/commit/5ad2f17693a9860be76ef8089c8728d2b59d6b04) [@bep](https://github.com/bep) [#3376](https://github.com/gohugoio/hugo/issues/3376)
-* Prevent decoding `pageParam` in common cases [e98f885b](https://github.com/gohugoio/hugo/commit/e98f885b8af27f5473a89d31d0b1f02e61e8a5ec) [@bogem](https://github.com/bogem)
-* Ignore non-source files on partial rebuild [b5b6e81c](https://github.com/gohugoio/hugo/commit/b5b6e81c0269abf9b0f4bc6a127744a25344e5c6) [@xofyarg](https://github.com/xofyarg) [#3325](https://github.com/gohugoio/hugo/issues/3325)
-* Log `WARNING` only on unknown `/data` files [ab692e73](https://github.com/gohugoio/hugo/commit/ab692e73dea3ddfe979c88ee236cc394e47e82f1) [@bep](https://github.com/bep) [#3361](https://github.com/gohugoio/hugo/issues/3361)
-* Avoid processing the same notify event twice [3b677594](https://github.com/gohugoio/hugo/commit/3b67759495c9268c30e6ba2d8c7e3b75d52d2960) [@bep](https://github.com/bep)
-* Only show `rssURI` deprecation `WARNING` if it is actually set [cfd3af8e](https://github.com/gohugoio/hugo/commit/cfd3af8e691119461effa4385251b9d3818e2291) [@bep](https://github.com/bep) [#3319](https://github.com/gohugoio/hugo/issues/3319)
-
-### Docs
-
-* Add documentation on slug translation [635b3bb4](https://github.com/gohugoio/hugo/commit/635b3bb4eb873978c7d52e6c0cb85da0c4d25299) [@xavib](https://github.com/xavib)
-* Replace `cdn.mathjax.org` with `cdnjs.cloudflare.com` [4b637ac0](https://github.com/gohugoio/hugo/commit/4b637ac041d17b22187f5ccd0f65461f0065aaa9) [@takuti](https://github.com/takuti)
-* Add notes about some output format behaviour [162d3a58](https://github.com/gohugoio/hugo/commit/162d3a586d36cabf6376a76b096fd8b6414487ae) [@jpatters](https://github.com/jpatters)
-* Add `txtpen` as alternative commenting service [7cdc244a](https://github.com/gohugoio/hugo/commit/7cdc244a72de4c08edc0008e37aec83d945dccdf) [@rickyhan](https://github.com/rickyhan)
-
-### Other
-
-* Embed `Page` in `WeightedPage` [ebf677a5](https://github.com/gohugoio/hugo/commit/ebf677a58360126d8b9a1e98d086aa4279f53181) [@bep](https://github.com/bep) [#3435](https://github.com/gohugoio/hugo/issues/3435)
-* Improve the detection of untranslated strings [a40d1f6e](https://github.com/gohugoio/hugo/commit/a40d1f6ed2aedddc99725658993258cd557640ed) [@bogem](https://github.com/bogem) [#2607](https://github.com/gohugoio/hugo/issues/2607)
-* Make first letter of the Hugo commands flags' usage lowercase [f0f69d03](https://github.com/gohugoio/hugo/commit/f0f69d03c551acb8ac2eeedaad579cf0b596f9ef) [@bogem](https://github.com/bogem)
-* Import `Octopress` image tag in `Jekyll importer` [5f3ad1c3](https://github.com/gohugoio/hugo/commit/5f3ad1c31985450fab8d6772e9cbfcb57cf5cc53) [@buynov](https://github.com/buynov)
-
-## Fixes
-
-### Templates
-
-* Do not lower case template names [6d2ea0f7](https://github.com/gohugoio/hugo/commit/6d2ea0f7d7e8a54b8edfc36e52ff74266c30dc27) [@bep](https://github.com/bep) [#3333](https://github.com/gohugoio/hugo/issues/3333)
-
-### Output
-
-* Fix output format mixup in example [10287263](https://github.com/gohugoio/hugo/commit/10287263f529181d3169668b044cb84e2e3b049a) [@bep](https://github.com/bep) [#3481](https://github.com/gohugoio/hugo/issues/3481)
-* Fix base theme vs project base template logic [077005e5](https://github.com/gohugoio/hugo/commit/077005e514b1ed50d84ceb90c7c72f184cb04521) [@bep](https://github.com/bep) [#3323](https://github.com/gohugoio/hugo/issues/3323)
-
-### Core
-* Render `404` in default language only [154e18dd](https://github.com/gohugoio/hugo/commit/154e18ddb9ad205055d5bd4827c87f3f0daf499f) [@mitchchn](https://github.com/mitchchn) [#3075](https://github.com/gohugoio/hugo/issues/3075)
-* Fix `RSSLink` vs `RSS` `Output Format` [e682fcc6](https://github.com/gohugoio/hugo/commit/e682fcc62233b47cf5bdcaf598ac0657ef089471) [@bep](https://github.com/bep) [#3450](https://github.com/gohugoio/hugo/issues/3450)
-* Add default config for `ignoreFiles`, making that option work when running in server mode [42f4ce15](https://github.com/gohugoio/hugo/commit/42f4ce15a9d68053da36f9efcf7a7d975cc59559) [@chaseadamsio](https://github.com/chaseadamsio)
-* Fix output formats override when no outputs definition given [6e2f2dd8](https://github.com/gohugoio/hugo/commit/6e2f2dd8d3ca61c92a2ee8824fbf05cadef08425) [@bep](https://github.com/bep) [#3447](https://github.com/gohugoio/hugo/issues/3447)
-* Fix handling of zero-length files [0e87b18b](https://github.com/gohugoio/hugo/commit/0e87b18b66d2c8ba9e2abc429630cb03f5b093d6) [@bep](https://github.com/bep) [#3355](https://github.com/gohugoio/hugo/issues/3355)
-* Must recreate `Paginator` on live-reload [45c74526](https://github.com/gohugoio/hugo/commit/45c74526686f6a2afa02bcee767d837d6b9dd028) [@bep](https://github.com/bep) [#3315](https://github.com/gohugoio/hugo/issues/3315)
-
-### Docs
-
-* Fix incorrect path in `templates/list` [27e88154](https://github.com/gohugoio/hugo/commit/27e88154af2dd9af6d0523d6e67b612e6336f91c) [@MunifTanjim](https://github.com/MunifTanjim)
-* Fixed incorrect specification of directory structure [a28fbca6](https://github.com/gohugoio/hugo/commit/a28fbca6dcfa80b6541f5ef6c8c12cd1804ae9ed) [@TejasQ](https://github.com/TejasQ)
-* Fix `bash` command in `tutorials/github-pages-blog` [c9976155](https://github.com/gohugoio/hugo/commit/c99761555c014e4d041438d5d7e53a6cbaee4492) [@hansott](https://github.com/hansott)
-* Fix `.Data.Pages` range in example [b5e32eb6](https://github.com/gohugoio/hugo/commit/b5e32eb60993b4656918af2c959ae217a68c461e) [@hxlnt](https://github.com/hxlnt)
-
-### Other
-
-* Fix data race in live-reload close, avoiding some rare panics [355736ec](https://github.com/gohugoio/hugo/commit/355736ec357c81dfb2eb6851ee019d407090c5ec) [@bep](https://github.com/bep) [#2625](https://github.com/gohugoio/hugo/issues/2625)
-* Skip `.git` directories in file scan [94b5be67](https://github.com/gohugoio/hugo/commit/94b5be67fc73b87d114d94a7bb1a33ab997f30f1) [@bogem](https://github.com/bogem) [#3468](https://github.com/gohugoio/hugo/issues/3468)
\ No newline at end of file
diff --git a/docs/content/en/news/0.22-relnotes/index.md b/docs/content/en/news/0.22-relnotes/index.md
deleted file mode 100644
index 8a9e8d5f52d..00000000000
--- a/docs/content/en/news/0.22-relnotes/index.md
+++ /dev/null
@@ -1,89 +0,0 @@
----
-date: 2017-06-12T17:53:58-04:00
-categories: ["Releases"]
-description: "Hugo 0.22 brings nested sections, by popular demand and a long sought after feature"
-link: ""
-title: "Hugo 0.22"
-draft: false
-author: bep
-aliases: [/0-22/]
----
-
-
-Hugo `0.22` brings **nested sections**, by popular demand and a long sought after feature ([#465](https://github.com/gohugoio/hugo/issues/465)). We are still low on documentation for this great feature, but [@bep](https://github.com/bep) has been kind enough to accompany his implementation with a [demo site](http://hugotest.bep.is/).
-
-This release represents **58 contributions by 10 contributors** to the main Hugo code base. Since last release Hugo has **gained 420 stars and 2 additional themes.**
-
-[@bep](https://github.com/bep) still leads the Hugo development with his witty Norwegian humor, and once again contributed a significant amount of additions. But also a big shoutout to [@bogem](https://github.com/bogem), [@moorereason](https://github.com/moorereason), and [@onedrawingperday](https://github.com/onedrawingperday) for their ongoing contributions. And as always big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the documentation and the themes site in pristine condition.
-
-Hugo now has:
-
-* 17576+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 455+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 165+ [themes](http://themes.gohugo.io/)
-
-## Other Highlights
-
-`.Site.GetPage` can now also be used to get regular pages ([#2844](https://github.com/gohugoio/hugo/issues/2844)):
-
-```
-{{ (.Site.GetPage "page" "blog" "mypost.md" ).Title }}
-```
-
-Also, considerable work has been put into writing automated benchmark tests for the site builds, and we're happy to report that although this release comes with fundamental structural changes, this version is -- in general -- even faster than the previous. It’s quite a challenge to consistently add significant new functionality and simultaneously maintain the stellar performance Hugo is famous for.
-
-
-
-## Notes
-
-`.Site.Sections` is replaced. We have reworked how sections work in Hugo, they can now be nested and are no longer taxonomies. If you use the old collection, you should get detailed upgrade instructions in the log when you run `hugo`. For more information, see this [demo site](http://hugotest.bep.is/).
-
-## Enhancements
-
-### Templates
-
-* Add `uint` support to `In` [b82cd82f](https://github.com/gohugoio/hugo/commit/b82cd82f1198a371ed94bda7faafe22813f4cb29) [@moorereason](https://github.com/moorereason)
-* Support interfaces in `union` [204c3a9e](https://github.com/gohugoio/hugo/commit/204c3a9e32fcf6617ede978e35d3e2e89a5b491c) [@moorereason](https://github.com/moorereason) [#3411](https://github.com/gohugoio/hugo/issues/3411)
-* Add `uniq` function [e28d9aa4](https://github.com/gohugoio/hugo/commit/e28d9aa42c3429d22fe254e69e4605aaf1e684f3) [@adiabatic](https://github.com/adiabatic)
-* Handle `template.HTML` and friends in `ToInt` [4113693a](https://github.com/gohugoio/hugo/commit/4113693ac1b275f3a40aa5c248269340ef9b57f6) [@moorereason](https://github.com/moorereason) [#3308](https://github.com/gohugoio/hugo/issues/3308)
-
-
-### Core
-
-* Make the `RSS feed` use the date for the node it represents [f1da5a15](https://github.com/gohugoio/hugo/commit/f1da5a15a37666ee59350d6600a8c14c1383f5bc) [@bep](https://github.com/bep) [#2708](https://github.com/gohugoio/hugo/issues/2708)
-* Enable `nested sections` [b3968939](https://github.com/gohugoio/hugo/commit/b39689393ccb8434d9a57658a64b77568c718e99) [@bep](https://github.com/bep) [#465](https://github.com/gohugoio/hugo/issues/465)
-* Add test for "no 404" in `sitemap` [8aaec644](https://github.com/gohugoio/hugo/commit/8aaec644a90d09bd7f079d35d382f76bb4ed35db) [@bep](https://github.com/bep) [#3563](https://github.com/gohugoio/hugo/issues/3563)
-* Support regular pages in `.Site.GetPage` [e0c2e798](https://github.com/gohugoio/hugo/commit/e0c2e798201f75ae6e9a81a7442355288c2d141b) [@bep](https://github.com/bep) [#2844](https://github.com/gohugoio/hugo/issues/2844)
-[#3082](https://github.com/gohugoio/hugo/issues/3082)
-
-### Performance
-* Add site building benchmarks [8930e259](https://github.com/gohugoio/hugo/commit/8930e259d78cba4041b550cc51a7f40bc91d7c20) [@bep](https://github.com/bep) [#3535](https://github.com/gohugoio/hugo/issues/3535)
-* Add a cache to `GetPage` which makes it much faster [50d11138](https://github.com/gohugoio/hugo/commit/50d11138f3e18b545c15fadf52f7b0b744bf3e7c) [@bep](https://github.com/bep)
-* Speed up `GetPage` [fbb78b89](https://github.com/gohugoio/hugo/commit/fbb78b89df8ccef8f0ab26af00aa45d35c1ee2cf) [@bep](https://github.com/bep) [#3503](https://github.com/gohugoio/hugo/issues/3503)
-* Add BenchmarkFrontmatterTags [3d9c4f51](https://github.com/gohugoio/hugo/commit/3d9c4f513b0443648d7e88995e351df1739646d2) [@bep](https://github.com/bep) [#3464](https://github.com/gohugoio/hugo/issues/3464)
-* Add `benchSite.sh` to make it easy to run Hugo performance benchmarks [d74452cf](https://github.com/gohugoio/hugo/commit/d74452cfe8f69a85ec83e05481e16bebf199a5cb) [@bep](https://github.com/bep)
-* Cache language config [4aff2b6e](https://github.com/gohugoio/hugo/commit/4aff2b6e7409a308f30cff1825fec02991e0d56a) [@bep](https://github.com/bep)
-* Temporarily revert to BurntSushi for `TOML` front matter handling; it is currently much faster [0907a5c1](https://github.com/gohugoio/hugo/commit/0907a5c1c293755e6bf297246f07888448d81f8b) [@bep](https://github.com/bep) [#3541](https://github.com/gohugoio/hugo/issues/3541) [#3464](https://github.com/gohugoio/hugo/issues/3464)
-* Add a simple partitioned lazy cache [87203139](https://github.com/gohugoio/hugo/commit/87203139c38e0b992c96d7b8a23c7730649c68e5) [@bep](https://github.com/bep)
-
-### Other
-
-* Add `noindex` tag to HTML generated by Hugo aliases [d5ab7f08](https://github.com/gohugoio/hugo/commit/d5ab7f087d967b30e7de7d789e6ad3091b42f1f7) [@onedrawingperday](https://github.com/onedrawingperday)
-* Update Go versions [bde807bd](https://github.com/gohugoio/hugo/commit/bde807bd1e560fb4cc765c0fc22132db7f8a0801) [@bep](https://github.com/bep)
-* Remove the `rlimit` tweaking on `macOS` [bcd32f10](https://github.com/gohugoio/hugo/commit/bcd32f1086c8c604fb22a7496924e41cc46b1605) [@bep](https://github.com/bep) [#3512](https://github.com/gohugoio/hugo/issues/3512)
-
-### Docs
-* Rewrite “Archetypes” article [@davidturnbull](https://github.com/davidturnbull) [#3543](https://github.com/gohugoio/hugo/pull/3543/)
-* Remove Unmaintaned Frontends from Tools. [f41f7282](https://github.com/gohugoio/hugo/commit/f41f72822251c9a31031fd5b3dda585c57c8b028) [@onedrawingperday](https://github.com/onedrawingperday)
-
-## Fixes
-
-### Core
-* Improve `live-reload` on directory structure changes making removal of directories or pasting new content directories into `/content` just work [fe901b81](https://github.com/gohugoio/hugo/commit/fe901b81191860b60e6fcb29f8ebf87baef2ee79) [@bep](https://github.com/bep) [#3570](https://github.com/gohugoio/hugo/issues/3570)
-* Respect `disableKinds=["sitemap"]` [69d92dc4](https://github.com/gohugoio/hugo/commit/69d92dc49cb8ab9276ab013d427ba2d9aaf9135d) [@bep](https://github.com/bep) [#3544](https://github.com/gohugoio/hugo/issues/3544)
-* Fix `disablePathToLower` regression [5be04486](https://github.com/gohugoio/hugo/commit/5be0448635fdf5fe6b1ee673e869f2b9baf1a5c6) [@bep](https://github.com/bep) [#3374](https://github.com/gohugoio/hugo/issues/3374)
-* Fix `ref`/`relref` issue with duplicate base filenames [612f6e3a](https://github.com/gohugoio/hugo/commit/612f6e3afe0510c31f70f3621f3dc8ba609dade4) [@bep](https://github.com/bep) [#2507](https://github.com/gohugoio/hugo/issues/2507)
-
-### Docs
-
-* Fix parameter name in `YouTube` shortcode section [37e37877](https://github.com/gohugoio/hugo/commit/37e378773fbc127863f2b7a389d5ce3a14674c73) [@zivbk1](https://github.com/zivbk1)
\ No newline at end of file
diff --git a/docs/content/en/news/0.22.1-relnotes/index.md b/docs/content/en/news/0.22.1-relnotes/index.md
deleted file mode 100644
index ceb207d70d1..00000000000
--- a/docs/content/en/news/0.22.1-relnotes/index.md
+++ /dev/null
@@ -1,37 +0,0 @@
----
-date: 2017-06-13T17:53:58-04:00
-categories: ["Releases"]
-description: "Hugo 0.22.1 fixes a couple of issues reported after the 0.22 release"
-link: ""
-title: "Hugo 0.22.1"
-draft: false
-author: bep
-aliases: [/0-22-1/]
----
-
-Hugo `0.22.1` fixes a couple of issues reported after the [0.22 release](https://github.com/gohugoio/hugo/releases/tag/v0.22) Monday. Most importantly a fix for detecting regular subfolders below the root-sections.
-
-Also, we forgot to adapt the `permalink settings` with support for nested sections, which made that feature less useful than it could be.
-
-With this release you can configure **permalinks with sections** like this:
-
-**First level only:**
-
-```
-[permalinks]
-blog = ":section/:title"
-```
-
-**Nested (all levels):**
-
-```
-[permalinks]
-blog = ":sections/:title"
-```
-## Fixes
-
-* Fix section logic for root folders with subfolders [a30023f5](https://github.com/gohugoio/hugo/commit/a30023f5cbafd06034807255181a5b7b17f3c25f) [@bep](https://github.com/bep) [#3586](https://github.com/gohugoio/hugo/issues/3586)
-* Support sub-sections in permalink settings [1f26420d](https://github.com/gohugoio/hugo/commit/1f26420d392a5ab4c7b7fe1911c0268b45d01ab8) [@bep](https://github.com/bep) [#3580](https://github.com/gohugoio/hugo/issues/3580)
-* Adjust rlimit to 64000 [ff54b6bd](https://github.com/gohugoio/hugo/commit/ff54b6bddcefab45339d8dc2b13776b92bdc04b9) [@bep](https://github.com/bep) [#3582](https://github.com/gohugoio/hugo/issues/3582)
-* Make error on setting rlimit a warning only [629e1439](https://github.com/gohugoio/hugo/commit/629e1439e819a7118ae483381d4634f16d3474dd) [@bep](https://github.com/bep) [#3582](https://github.com/gohugoio/hugo/issues/3582)
-* Revert: Remove the rlimit tweaking on macOS [26aa06a3](https://github.com/gohugoio/hugo/commit/26aa06a3db57ab7134a900d641fa2976f7971520) [@bep](https://github.com/bep) [#3582](https://github.com/gohugoio/hugo/issues/3582)
\ No newline at end of file
diff --git a/docs/content/en/news/0.23-relnotes/index.md b/docs/content/en/news/0.23-relnotes/index.md
deleted file mode 100644
index fdf6e9e73e4..00000000000
--- a/docs/content/en/news/0.23-relnotes/index.md
+++ /dev/null
@@ -1,46 +0,0 @@
----
-date: 2017-06-16T17:53:58-04:00
-categories: ["Releases"]
-description: "Hugo 0.23 is mainly a release that handles all the small changes needed to get Hugo moved to a GitHub organisation"
-link: ""
-title: "Hugo 0.23"
-draft: false
-author: bep
-aliases: [/0-23/]
----
-
-Hugo `0.23` is mainly a release that handles all the small changes needed to get Hugo moved to a GitHub organisation: [gohugoio](https://github.com/gohugoio), but it also contains a couple of important fixes that makes this an update worth-while for all.
-
-Hugo now has:
-
-* 17739+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 494+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 165+ [themes](http://themes.gohugo.io/)
-
-## Fixes
-
-* Fix handling of duplicate footnotes [a9e551a1](https://github.com/gohugoio/hugo/commit/a9e551a100e60a603210ee083103dd73369d6a98) [@bep](https://github.com/bep) [#1912](https://github.com/gohugoio/hugo/issues/1912)
-* Add support for spaces in project folder for `GitInfo` #3533 #3552
-
-## GitHub organisation related changes
-
-* Update layout references to gohugoio/hugo [66d4850b](https://github.com/gohugoio/hugo/commit/66d4850b89db293dc58e828de784037f06c6c8dc) [@bep](https://github.com/bep)
-* Update content references to gohugoio/hugo [715ff1f8](https://github.com/gohugoio/hugo/commit/715ff1f87406edf27738c8c0f52fe185fa974ee8) [@bep](https://github.com/bep)
-* Add note on updates for rpm-based distros [52a0cea6](https://github.com/gohugoio/hugo/commit/52a0cea65de7b75ae1662abe3dec36fca3604617) [@daftaupe](https://github.com/daftaupe)
-* Update logo link in README [ccb8300d](https://github.com/gohugoio/hugo/commit/ccb8300d380636d75a39f4133284eb0109e836c3) [@bep](https://github.com/bep)
-* Remove docs building from CI builds [214dbdfb](https://github.com/gohugoio/hugo/commit/214dbdfb6f016d21415bc1ed511a37a084238878) [@bep](https://github.com/bep)
-* Adjust docs path [729be807](https://github.com/gohugoio/hugo/commit/729be8074bddb58c9111f32c55cc769e49cd0d5a) [@bep](https://github.com/bep)
-* Add docs as submodule [6cee0dfe](https://github.com/gohugoio/hugo/commit/6cee0dfe53899d433afc3c173a87d56265904cb0) [@bep](https://github.com/bep)
-* Update Gitter link in README [fbb25014](https://github.com/gohugoio/hugo/commit/fbb25014e1306ce7127d53e5fc4fc49867790336) [@bep](https://github.com/bep)
-* Change Windows build badge link, take #3 [86543d6a](https://github.com/gohugoio/hugo/commit/86543d6a50251b40540ebd0b851d45eb99d017c7) [@bep](https://github.com/bep)
-* Update Windows build link [e6ae32a0](https://github.com/gohugoio/hugo/commit/e6ae32a0ba75b9894418227e87391defbb1b3b49) [@bep](https://github.com/bep)
-* Update links in CONTRIBUTING.md due to the org transition [95386544](https://github.com/gohugoio/hugo/commit/95386544e858949a2baa414f395f30aaf66a6257) [@digitalcraftsman](https://github.com/digitalcraftsman)
-* Update source path in Dockerfile due to the org transition [7b99fb9f](https://github.com/gohugoio/hugo/commit/7b99fb9f1ca8381457afe9d8e953a388b8ada182) [@digitalcraftsman](https://github.com/digitalcraftsman)
-* Update clone folder in appveyor.yml due to the org transition [d531d17b](https://github.com/gohugoio/hugo/commit/d531d17b3be0b14faf4934611e01ac3289e37835) [@digitalcraftsman](https://github.com/digitalcraftsman)
-* Update import path in snapcraft.yaml due to the org transition [9266bf9d](https://github.com/gohugoio/hugo/commit/9266bf9d4c24592b875a7f6b92f761b4cea40879) [@digitalcraftsman](https://github.com/digitalcraftsman)
-* Run gofmt to get imports in line vs gohugoio/hugo [873a6f18](https://github.com/gohugoio/hugo/commit/873a6f18851bcda79d562ff6c02e1109e8e31a88) [@bep](https://github.com/bep)
-* Update Makefile vs gohugoio/hugo [f503d76a](https://github.com/gohugoio/hugo/commit/f503d76a3b2719bbb65ab9df5595d0dbc871fae9) [@bep](https://github.com/bep)
-* Update README to point to gohugoio/hugo [93643860](https://github.com/gohugoio/hugo/commit/93643860c9db10c6c32176b17cc83f1c317279bd) [@bep](https://github.com/bep)
-* Update examples to point to gohugoio/hugo [db46bcf8](https://github.com/gohugoio/hugo/commit/db46bcf82d060656d4bc731550e63ec9cf8576f2) [@bep](https://github.com/bep)
-* Update textual references in Go source to point to gohugoio/hugo [c17ad675](https://github.com/gohugoio/hugo/commit/c17ad675e8fcdb2db40fc50816b8f016bc14294c) [@bep](https://github.com/bep)
-* Update import paths to gohugoio/hugo [d8717cd4](https://github.com/gohugoio/hugo/commit/d8717cd4c74e80ea8e20adead9321412a2d76022) [@bep](https://github.com/bep)
\ No newline at end of file
diff --git a/docs/content/en/news/0.24-relnotes/index.md b/docs/content/en/news/0.24-relnotes/index.md
deleted file mode 100644
index ec71e246fe8..00000000000
--- a/docs/content/en/news/0.24-relnotes/index.md
+++ /dev/null
@@ -1,74 +0,0 @@
----
-date: 2017-06-17T17:53:58-04:00
-categories: ["Releases"]
-description: "The Revival of the Archetypes!"
-link: ""
-title: "Hugo 0.24"
-draft: false
-author: bep
-aliases: [/0-24/]
----
-
-> "A feature that could be the name of the next Indiana Jones movie deserves its own release," says [@bep](https://github.com/bep).
-
-Hugo now handles the **archetype files as Go templates**. This means that the issues with sorting and lost comments are long gone. This also means that you will have to supply all values, including title and date. But this also opens up a lot of new windows.
-
-A fictional example for the section `newsletter` and the archetype file `archetypes/newsletter.md`:
-
-```
----
-title: "{{ replace .TranslationBaseName "-" " " | title }}"
-date: {{ .Date }}
-draft: true
----
-
-**Insert Lead paragraph here.**
-
-
-
-## New Cool Posts
-
-{{ range first 10 ( where .Site.RegularPages "Type" "cool" ) }}
-* {{ .Title }}
-{{ end }}
-```
-
-And then create a new post with:
-
-```
-hugo new newsletter/the-latest-cool.stuff.md
-```
-
-**Note:** the site will only be built if the `.Site` is in use in the archetype file, and this can be time consuming for big sites.
-
-**Hot Tip:** If you set the `newContentEditor` configuration variable to an editor on your `PATH`, the newly created article will be opened.
-
-The above _newsletter type archetype_ illustrates the possibilities: The full Hugo `.Site` and all of Hugo's template funcs can be used in the archetype file.
-
-**Also, Hugo now supports archetype files for all content formats, not just markdown.**
-
-Hugo now has:
-
-* 17839+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 493+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 166+ [themes](http://themes.gohugo.io/)
-
-## Notes
-
-* Archetype files now need to be complete, including `title` and `date`.
-* The `-f` (format) flag in `hugo new` is removed: Now use the archetype files as is.
-
-## Enhancements
-
-* Support extension-less media types. The motivation behind this change is to support Netlify's `_redirects` files, so we can generate server-side redirects for the Hugo docs site. See [this commit](https://github.com/gohugoio/hugoDocs/commit/c1ab9894e8292e0a74c43bbca2263b1fb3840f9e) to see how we configured that. [0f40e1fa](https://github.com/gohugoio/hugo/commit/0f40e1fadfca2276f65adefa6d7d5d63aef9160a) [@bep](https://github.com/bep) [#3614](https://github.com/gohugoio/hugo/issues/3614)
-* Add `disableAliases` [516e6c6d](https://github.com/gohugoio/hugo/commit/516e6c6dc5733cdaf985317d58eedbc6ec0ef2f7) [@bep](https://github.com/bep) [#3613](https://github.com/gohugoio/hugo/issues/3613)
-* Support non-md files as archetype files [19f2e729](https://github.com/gohugoio/hugo/commit/19f2e729135af700c5d4aa06e7b3540e6d4847fd) [@bep](https://github.com/bep) [#3597](https://github.com/gohugoio/hugo/issues/3597) [#3618](https://github.com/gohugoio/hugo/issues/3618)
-* Identify extension-less text types as text [c43b512b](https://github.com/gohugoio/hugo/commit/c43b512b4700f76ac77f12d632bb030c3a241393) [@bep](https://github.com/bep) [#3614](https://github.com/gohugoio/hugo/issues/3614)
-* Add `.Site` to the archetype templates [662e12f3](https://github.com/gohugoio/hugo/commit/662e12f348a638a6fcc92a416ee7f7c2a7ef8792) [@bep](https://github.com/bep) [#1629](https://github.com/gohugoio/hugo/issues/1629)
-* Use archetype template as-is as a Go template [422057f6](https://github.com/gohugoio/hugo/commit/422057f60709696bbbd1c38c9ead2bf114d47e31) [@bep](https://github.com/bep) [#452](https://github.com/gohugoio/hugo/issues/452) [#1629](https://github.com/gohugoio/hugo/issues/1629)
-* Update links to new discuss URL [4aa12390](https://github.com/gohugoio/hugo/commit/4aa1239070bb9d4324d3582f3e809b702a59d3ac) [@bep](https://github.com/bep)
-
-## Fixes
-
-* Fix error handling for `JSON` front matter [fb53987a](https://github.com/gohugoio/hugo/commit/fb53987a4ff2acb9da8dec6ec7b11924d37352ce) [@bep](https://github.com/bep) [#3610](https://github.com/gohugoio/hugo/issues/3610)
-* Fix handling of quoted brackets in `JSON` front matter [3183b9a2](https://github.com/gohugoio/hugo/commit/3183b9a29d8adac962fbc73f79b04542f4c4c55d) [@bep](https://github.com/bep) [#3511](https://github.com/gohugoio/hugo/issues/3511)
\ No newline at end of file
diff --git a/docs/content/en/news/0.24.1-relnotes/index.md b/docs/content/en/news/0.24.1-relnotes/index.md
deleted file mode 100644
index 2ec2cef55f3..00000000000
--- a/docs/content/en/news/0.24.1-relnotes/index.md
+++ /dev/null
@@ -1,22 +0,0 @@
----
-date: 2017-06-24T17:53:58-04:00
-categories: ["Releases"]
-description: "This release fixes some important archetype-related regressions from Hugo 0.24"
-link: ""
-title: "Hugo 0.24.1"
-draft: false
-author: bep
-aliases: [/0-24-1/]
----
-
-This release fixes some important **archetype-related regressions** from the recent Hugo 0.24-release.
-
-## Fixes
-
-* Fix archetype regression when no archetype file [4294dd8d](https://github.com/gohugoio/hugo/commit/4294dd8d9d22bd8107b7904d5389967da1f83f27) [@bep](https://github.com/bep) [#3626](https://github.com/gohugoio/hugo/issues/3626)
-* Preserve shortcodes in archetype templates [b63e4ee1](https://github.com/gohugoio/hugo/commit/b63e4ee198c875b73a6a9af6bb809589785ed589) [@bep](https://github.com/bep) [#3623](https://github.com/gohugoio/hugo/issues/3623)
-* Fix handling of timezones with positive UTC offset (e.g., +0800) in TOML [0744f81e](https://github.com/gohugoio/hugo/commit/0744f81ec00bb8888f59d6c8b5f57096e07e70b1) [@bep](https://github.com/bep) [#3628](https://github.com/gohugoio/hugo/issues/3628)
-
-## Enhancements
-
-* Create default archetype on new site [bfa336d9](https://github.com/gohugoio/hugo/commit/bfa336d96173377b9bbe2298dbd101f6a718c174) [@bep](https://github.com/bep) [#3626](https://github.com/gohugoio/hugo/issues/3626)
\ No newline at end of file
diff --git a/docs/content/en/news/0.25-relnotes/index.md b/docs/content/en/news/0.25-relnotes/index.md
deleted file mode 100644
index 9527c911a9e..00000000000
--- a/docs/content/en/news/0.25-relnotes/index.md
+++ /dev/null
@@ -1,78 +0,0 @@
----
-date: 2017-07-07T17:53:58-04:00
-categories: ["Releases"]
-description: "Hugo 0.25 automatically opens the page you're working on in the browser"
-link: ""
-title: "Hugo 0.25"
-draft: false
-author: bep
-aliases: [/0-25/]
----
-
-Hugo `0.25` is the **Kinder Surprise**: It automatically opens the page you're working on in the browser, it adds full `AND` and `OR` support in page queries, and you can now have templates per language.
-
-
-
-If you start with `hugo server --navigateToChanged`, Hugo will navigate to the relevant page on save (see animated GIF). This is extremely useful for site-wide edits. Another very useful feature in this version is the added support for `AND` (`intersect`) and `OR` (`union`) filters when combined with `where`.
-
-Example:
-
-```
-{{ $pages := where .Site.RegularPages "Type" "not in" (slice "page" "about") }}
-{{ $pages := $pages | union (where .Site.RegularPages "Params.pinned" true) }}
-{{ $pages := $pages | intersect (where .Site.RegularPages "Params.images" "!=" nil) }}
-```
-
-The above fetches regular pages not of `page` or `about` type unless they are pinned. And finally, we exclude all pages with no `images` set in Page params.
-
-This release represents **36 contributions by 12 contributors** to the main Hugo code base. [@bep](https://github.com/bep) still leads the Hugo development with his witty Norwegian humor, and once again contributed a significant amount of additions. But also a big shoutout to [@yihui](https://github.com/yihui), [@anthonyfok](https://github.com/anthonyfok), and [@kropp](https://github.com/kropp) for their ongoing contributions. And as always a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the documentation and the themes site in pristine condition.
-
-Hugo now has:
-
-* 18209+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 455+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 168+ [themes](http://themes.gohugo.io/)
-
-## Enhancements
-
-### Templates
-
-* Add `Pages` support to `intersect` (`AND`) and `union`(`OR`). This makes the `where` template func even more powerful. [ccdd08d5](https://github.com/gohugoio/hugo/commit/ccdd08d57ab64441e93d6861ae126b5faacdb92f) [@bep](https://github.com/bep) [#3174](https://github.com/gohugoio/hugo/issues/3174)
-* Add `math.Log` function. This is very handy for creating tag clouds. [34c56677](https://github.com/gohugoio/hugo/commit/34c566773a1364077e1397daece85b22948dc721) [@artem-sidorenko](https://github.com/artem-sidorenko)
-* Add `WebP` images support [8431c8d3](https://github.com/gohugoio/hugo/commit/8431c8d39d878c18c6b5463d9091a953608df10b) [@bep](https://github.com/bep) [#3529](https://github.com/gohugoio/hugo/issues/3529)
-* Only show post's own keywords in schema.org [da72805a](https://github.com/gohugoio/hugo/commit/da72805a4304a57362e8e79a01cc145767b027c5) [@brunoamaral](https://github.com/brunoamaral) [#2635](https://github.com/gohugoio/hugo/issues/2635)[#2646](https://github.com/gohugoio/hugo/issues/2646)
-* Simplify the `Disqus` template a little bit (#3655) [eccb0647](https://github.com/gohugoio/hugo/commit/eccb0647821e9db20ba9800da1b4861807cc5205) [@yihui](https://github.com/yihui)
-* Improve the built-in Disqus template (#3639) [2e1e4934](https://github.com/gohugoio/hugo/commit/2e1e4934b60ce8081a7f3a79191ed204f3098481) [@yihui](https://github.com/yihui)
-
-### Output
-
-* Support templates per site/language. This is for both regular templates and shortcode templates. [aa6b1b9b](https://github.com/gohugoio/hugo/commit/aa6b1b9be7c9d7322333893b642aaf8c7a5f2c2e) [@bep](https://github.com/bep) [#3360](https://github.com/gohugoio/hugo/issues/3360)
-
-### Core
-
-* Extend the sections API [a1d260b4](https://github.com/gohugoio/hugo/commit/a1d260b41a6673adef679ec4e262c5f390432cf5) [@bep](https://github.com/bep) [#3591](https://github.com/gohugoio/hugo/issues/3591)
-* Make `.Site.Sections` return the top level sections [dd9b1baa](https://github.com/gohugoio/hugo/commit/dd9b1baab0cb860a3eb32fd9043bac18cab3f9f0) [@bep](https://github.com/bep) [#3591](https://github.com/gohugoio/hugo/issues/3591)
-* Render `404.html` for all languages [41805dca](https://github.com/gohugoio/hugo/commit/41805dca9e40e9b0952e04d06074e6fc91140495) [@mitchchn](https://github.com/mitchchn) [#3598](https://github.com/gohugoio/hugo/issues/3598)
-
-### Other
-
-* Support human-readable `YAML` boolean values in `undraft` [1039356e](https://github.com/gohugoio/hugo/commit/1039356edf747f044c989a5bc0e85d792341ed5d) [@kropp](https://github.com/kropp)
-* `hugo import jekyll` support nested `_posts` directories [7ee1f25e](https://github.com/gohugoio/hugo/commit/7ee1f25e9ef3be8f99c171e8e7982f4f82c13e16) [@coderzh](https://github.com/coderzh) [#1890](https://github.com/gohugoio/hugo/issues/1890)[#1911](https://github.com/gohugoio/hugo/issues/1911)
-* Update `Dockerfile` and add Docker optimizations [118f8f7c](https://github.com/gohugoio/hugo/commit/118f8f7cf22d756d8a894ff93551974a806f2155) [@ellerbrock](https://github.com/ellerbrock)
-* Add Blackfriday `joinLines` extension support (#3574) [a5440496](https://github.com/gohugoio/hugo/commit/a54404968a4b36579797f2e7ff7f5eada94866d9) [@choueric](https://github.com/choueric)
-* add `--initial-header-level=2` to rst2html (#3528) [bfce30d8](https://github.com/gohugoio/hugo/commit/bfce30d85972c27c27e8a2caac9db6315f813298) [@frankbraun](https://github.com/frankbraun)
-* Support open "current content page" in browser [c825a731](https://github.com/gohugoio/hugo/commit/c825a7312131b4afa67ee90d593640dee3525d98) [@bep](https://github.com/bep) [#3643](https://github.com/gohugoio/hugo/issues/3643)
-* Make `--navigateToChanged` more robust on Windows [30e14cc3](https://github.com/gohugoio/hugo/commit/30e14cc31678ddc204b082ab362f86b6b8063881) [@anthonyfok](https://github.com/anthonyfok) [#3645](https://github.com/gohugoio/hugo/issues/3645)
-* Remove the docs submodule [31393f60](https://github.com/gohugoio/hugo/commit/31393f6024416ea1b2e61d1080dfd7104df36eda) [@bep](https://github.com/bep) [#3647](https://github.com/gohugoio/hugo/issues/3647)
-* Use `example.com` as homepage for new theme [aff1ac32](https://github.com/gohugoio/hugo/commit/aff1ac3235b6c075d01f7237addf44fecdd36d82) [@anthonyfok](https://github.com/anthonyfok)
-
-## Fixes
-
-### Templates
-
-* Fix `in` function for JSON arrays [d12cf5a2](https://github.com/gohugoio/hugo/commit/d12cf5a25df00fa16c59f0b2ae282187a398214c) [@bep](https://github.com/bep) [#1468](https://github.com/gohugoio/hugo/issues/1468)
-
-### Other
-
-* Fix handling of `JSON` front matter with escaped quotes [e10e51a0](https://github.com/gohugoio/hugo/commit/e10e51a00827b9fdc1bee51439fef05afc529831) [@bep](https://github.com/bep) [#3661](https://github.com/gohugoio/hugo/issues/3661)
-* Fix typo in code comment [56d82aa0](https://github.com/gohugoio/hugo/commit/56d82aa025f4d2edb1dc6315132cd7ab52df649a) [@dvic](https://github.com/dvic)
\ No newline at end of file
diff --git a/docs/content/en/news/0.25.1-relnotes/index.md b/docs/content/en/news/0.25.1-relnotes/index.md
deleted file mode 100644
index 7d70d87a5e2..00000000000
--- a/docs/content/en/news/0.25.1-relnotes/index.md
+++ /dev/null
@@ -1,25 +0,0 @@
----
-date: 2017-07-10T17:53:58-04:00
-categories: ["Releases"]
-description: "This is a bug-fix release with a couple of important fixes"
-link: ""
-title: "Hugo 0.25.1"
-draft: false
-author: bep
-aliases: [/0-25-1/]
----
-
-This is a bug-fix release with a couple of important fixes.
-
-Hugo now has:
-
-* 18277+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 456+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 170+ [themes](http://themes.gohugo.io/)
-
-## Fixes
-
-* Fix union when the first slice is empty [dbbc5c48](https://github.com/gohugoio/hugo/commit/dbbc5c4810a04ac06fad7500d88cf5c3bfe0c7fd) [@bep](https://github.com/bep) [#3686](https://github.com/gohugoio/hugo/issues/3686)
-* Navigate to changed on CREATE When working with content from IntelliJ IDE, like WebStorm, every file save is followed by two events: "RENAME" and then "CREATE". [7bcc1ce6](https://github.com/gohugoio/hugo/commit/7bcc1ce659710f2220b400ce3b76e50d2e48b241) [@miltador](https://github.com/miltador)
-* Final (!) fix for issue with escaped JSON front matter [7f82b41a](https://github.com/gohugoio/hugo/commit/7f82b41a24af0fd04d28fbfebf9254766a3c6e6f) [@bep](https://github.com/bep) [#3682](https://github.com/gohugoio/hugo/issues/3682)
-* Fix issue with escaped JSON front matter [84db6c74](https://github.com/gohugoio/hugo/commit/84db6c74a084d2b52117b999d4ec343cd3389a68) [@bep](https://github.com/bep) [#3682](https://github.com/gohugoio/hugo/issues/3682)
\ No newline at end of file
diff --git a/docs/content/en/news/0.26-relnotes/index.md b/docs/content/en/news/0.26-relnotes/index.md
deleted file mode 100644
index 7ebbf818535..00000000000
--- a/docs/content/en/news/0.26-relnotes/index.md
+++ /dev/null
@@ -1,81 +0,0 @@
-
----
-date: 2017-08-07
-title: "Hugo 0.26: Language Style Edition"
-description: "Hugo 0.26 brings proper AP Style or Chicago Style Title Case, « French Guillemets » and more."
-categories: ["Releases"]
-images:
-- images/blog/hugo-26-poster.png
----
-
-This release brings a choice of **AP Style or Chicago Style Title Case** ([8fb594bf](https://github.com/gohugoio/hugo/commit/8fb594bfb090c017d4e5cbb2905780221e202c41) [#989](https://github.com/gohugoio/hugo/issues/989)). You can also now configure Blackfriday to render **« French Guillemets »** ([cb9dfc26](https://github.com/gohugoio/hugo/commit/cb9dfc2613ae5125cafa450097fb0f62dd3770e7) [#3725](https://github.com/gohugoio/hugo/issues/3725)). To enable French Guillemets:
-
-
-{{< code-toggle file="config" >}}
-[blackfriday]
-angledQuotes = true
-smartypantsQuotesNBSP = true
-{{< /code-toggle >}}
-
-Oh, and this release also fixes it so you should see no ugly long crashes no more when you step wrong in your templates ([794ea21e](https://github.com/gohugoio/hugo/commit/794ea21e9449b876c5514f1ce8fe61449bbe4980)).
-
-Hugo `0.26` represents **46 contributions by 11 contributors** to the main Hugo code base.
-
-[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@anthonyfok](https://github.com/anthonyfok), [@jorinvo](https://github.com/jorinvo), and [@digitalcraftsman](https://github.com/digitalcraftsman) for their ongoing contributions. And as always a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the documentation and the themes site in pristine condition.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **838 contributions by 30 contributors**. A special thanks to [@rdwatters](https://github.com/rdwatters), [@bep](https://github.com/bep), [@digitalcraftsman](https://github.com/digitalcraftsman), and [@budparr](https://github.com/budparr) for their work on the documentation site.
-
-This may look like a **Waiting Sausage**, a barbecue term used in Norway for that sausage you eat while waiting for the steak to get ready. And it is: We're working on bigger and even more interesting changes behind the scenes. Stay tuned!
-
-Hugo now has:
-
-* 18802+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 457+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 175+ [themes](http://themes.gohugo.io/)
-
-## Notes
-
-* `sourceRelativeLinks` has been deprecated for a while and has now been removed. [9891c0fb](https://github.com/gohugoio/hugo/commit/9891c0fb0eb274b8a95b62c40070a87a6e04088c) [@bep](https://github.com/bep) [#3766](https://github.com/gohugoio/hugo/issues/3766)
-* The `title` template function and taxonomy page titles now default to following the [AP Stylebook](https://www.apstylebook.com/) for title casing. To override this default to use the old behavior, set `titleCaseStyle` to `Go` in your site configuration. [8fb594bf](https://github.com/gohugoio/hugo/commit/8fb594bfb090c017d4e5cbb2905780221e202c41) [@bep](https://github.com/bep) [#989](https://github.com/gohugoio/hugo/issues/989)
-
-## Enhancements
-
-### Templates
-
-* Use hash for cache key [6cd33f69](https://github.com/gohugoio/hugo/commit/6cd33f6953671edb13d42dcb15746bd10df3428b) [@RealOrangeOne](https://github.com/RealOrangeOne) [#3690](https://github.com/gohugoio/hugo/issues/3690)
-* Add some empty slice tests to intersect [e0cf2e05](https://github.com/gohugoio/hugo/commit/e0cf2e05bbdcb8b4a3f875df84a878f4ca80e904) [@bep](https://github.com/bep) [#3686](https://github.com/gohugoio/hugo/issues/3686)
-
-### Core
-
-* Support `reflinks` starting with a slash [dbe63970](https://github.com/gohugoio/hugo/commit/dbe63970e09313dec287816ab070b5c2f5a13b1b) [@bep](https://github.com/bep) [#3703](https://github.com/gohugoio/hugo/issues/3703)
-* Make template panics into nice error messages [794ea21e](https://github.com/gohugoio/hugo/commit/794ea21e9449b876c5514f1ce8fe61449bbe4980) [@bep](https://github.com/bep)
-
-### Other
-
-* Make the `title` case style guide configurable [8fb594bf](https://github.com/gohugoio/hugo/commit/8fb594bfb090c017d4e5cbb2905780221e202c41) [@bep](https://github.com/bep) [#989](https://github.com/gohugoio/hugo/issues/989)
-* Add support for French Guillemets [cb9dfc26](https://github.com/gohugoio/hugo/commit/cb9dfc2613ae5125cafa450097fb0f62dd3770e7) [@bep](https://github.com/bep) [#3725](https://github.com/gohugoio/hugo/issues/3725)
-* Add support for French Guillemets [c4a0b6e8](https://github.com/gohugoio/hugo/commit/c4a0b6e8abdf9f800fbd7a7f89e9f736edc60431) [@bep](https://github.com/bep) [#3725](https://github.com/gohugoio/hugo/issues/3725)
-* Switch from fork bep/inflect to markbates/inflect [09907d36](https://github.com/gohugoio/hugo/commit/09907d36af586c5b29389312f2ecc2962c06313c) [@jorinvo](https://github.com/jorinvo)
-* Remove unused dependencies from vendor.json [9b4170ce](https://github.com/gohugoio/hugo/commit/9b4170ce768717adfbe9d97c46e38ceaec2ce994) [@jorinvo](https://github.com/jorinvo)
-* Add `--debug` option to be improved on over time [aee2b067](https://github.com/gohugoio/hugo/commit/aee2b06780858c12d8cb04c7b1ba592543410aa9) [@maxandersen](https://github.com/maxandersen)
-* Reduce Docker image size from 277MB to 27MB [bfe0bfbb](https://github.com/gohugoio/hugo/commit/bfe0bfbbd1a59ddadb72a6b07fecce71716088ec) [@ellerbrock](https://github.com/ellerbrock) [#3730](https://github.com/gohugoio/hugo/issues/3730)[#3738](https://github.com/gohugoio/hugo/issues/3738)
-* Optimize Docker image size [606d6a8c](https://github.com/gohugoio/hugo/commit/606d6a8c9177dda4551ed198e0aabbe569f0725d) [@ellerbrock](https://github.com/ellerbrock) [#3674](https://github.com/gohugoio/hugo/issues/3674)
-* Add `--trace` to asciidoctor args [b60aa1a5](https://github.com/gohugoio/hugo/commit/b60aa1a504f3fbf9c19a6bf2030fdc7a04ab4a5a) [@miltador](https://github.com/miltador) [#3714](https://github.com/gohugoio/hugo/issues/3714)
-* Add script to pull in docs changes [ff433f98](https://github.com/gohugoio/hugo/commit/ff433f98133662063cbb16e220fd44c678c82823) [@bep](https://github.com/bep)
-* Add `HasShortcode` [deccc540](https://github.com/gohugoio/hugo/commit/deccc54004cbe88ddbf8f3f951d3178dc0693189) [@bep](https://github.com/bep) [#3707](https://github.com/gohugoio/hugo/issues/3707)
-* Improve the twitter card template [00b590d7](https://github.com/gohugoio/hugo/commit/00b590d7ab4f3021814acceaf74c4eaf64edb226) [@bep](https://github.com/bep) [#3711](https://github.com/gohugoio/hugo/issues/3711)
-* Add `GOEXE` to support building with different versions of `go` [ea5e9e34](https://github.com/gohugoio/hugo/commit/ea5e9e346c93320538c6517b619b5f57473291c8) [@mdhender](https://github.com/mdhender)
-
-## Fixes
-
-### Templates
-
-* Fix intersect on `[]interface{}` handling [55d0b894](https://github.com/gohugoio/hugo/commit/55d0b89417651eba3ae51c96bd9de9e0daa0399e) [@moorereason](https://github.com/moorereason) [#3718](https://github.com/gohugoio/hugo/issues/3718)
-
-### Other
-
-* Fix broken `TaskList` in Markdown [481924b3](https://github.com/gohugoio/hugo/commit/481924b34d23b0ce435778cce7bce77571b22f9d) [@mpcabd](https://github.com/mpcabd) [#3710](https://github.com/gohugoio/hugo/issues/3710)
-
-
-
diff --git a/docs/content/en/news/0.27-relnotes/index.md b/docs/content/en/news/0.27-relnotes/index.md
deleted file mode 100644
index 92fc3a7b0cd..00000000000
--- a/docs/content/en/news/0.27-relnotes/index.md
+++ /dev/null
@@ -1,95 +0,0 @@
-
----
-date: 2017-09-11
-title: "Hugo 0.27: Fast and Flexible Related Content!"
-description: "Makes it easy to add \"See Also\" sections etc. to your site."
-categories: ["Releases"]
-images:
-- images/blog/hugo-27-poster.png
----
-
-
-Hugo `0.27`comes with fast and flexible **Related Content** ([3b4f17bb](https://github.com/gohugoio/hugo/commit/3b4f17bbc9ff789faa581ac278ad109d1ac5b816) [@bep](https://github.com/bep) [#98](https://github.com/gohugoio/hugo/issues/98)). To add this to your site, put something like this in your single page template:
-
-```go-html-template
-{{ $related := .Site.RegularPages.Related . | first 5 }}
-{{ with $related }}
-
-{{ end }}
-```
-
-The above translates to _list the five regular pages mostly related to the current page_. See the [Related Content Documentation](https://gohugo.io/content-management/related/) for details and configuration options.
-
-This release represents **37 contributions by 9 contributors** to the main Hugo code base.
-
-[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@moorereason](https://github.com/moorereason), [@yihui](https://github.com/yihui), and [@oneleaftea](https://github.com/oneleaftea) for their ongoing contributions.
-
-And as always a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the documentation and the themes site in pristine condition.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **44 contributions by 30 contributors**. A special thanks to [@bep](https://github.com/bep), [@sdomino](https://github.com/sdomino), [@gotgenes](https://github.com/gotgenes), and [@digitalcraftsman](https://github.com/digitalcraftsman) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 19464+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 455+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 178+ [themes](http://themes.gohugo.io/)
-
-## Notes
-
-* We now only strip p tag in `markdownify` if there is only one paragraph. This allows blocks of paragraphs to be "markdownified" [33ae10b6](https://github.com/gohugoio/hugo/commit/33ae10b6ade67cd9618970121d7de5fd2ce7d781) [@bep](https://github.com/bep) [#3040](https://github.com/gohugoio/hugo/issues/3040)
-
-## Enhancements
-
-### Templates
-
-* Add `time.Duration` and `time.ParseDuration` template funcs [f4bf2141](https://github.com/gohugoio/hugo/commit/f4bf214137ebd24a0d12f16d3a98d9038e6eabd3) [@bep](https://github.com/bep) [#3828](https://github.com/gohugoio/hugo/issues/3828)
-* Add `cond` (ternary) template func [0462c96a](https://github.com/gohugoio/hugo/commit/0462c96a5a9da3e8adc78d96acd39575a8b46c40) [@bep](https://github.com/bep) [#3860](https://github.com/gohugoio/hugo/issues/3860)
-* Prepare for template metrics [d000cf60](https://github.com/gohugoio/hugo/commit/d000cf605091c6999b72d6c632752289bc680223) [@bep](https://github.com/bep)
-* Add `strings.TrimLeft` and `TrimRight` [7674ad73](https://github.com/gohugoio/hugo/commit/7674ad73825c61eecc4003475fe0577f225fe579) [@moorereason](https://github.com/moorereason)
-* compare, hugolib, tpl: Add `Eqer` interface [08f48b91](https://github.com/gohugoio/hugo/commit/08f48b91d68d3002b887ddf737456ff0cc4e786d) [@bep](https://github.com/bep) [#3807](https://github.com/gohugoio/hugo/issues/3807)
-* Only strip p tag in `markdownify` if only one paragraph [33ae10b6](https://github.com/gohugoio/hugo/commit/33ae10b6ade67cd9618970121d7de5fd2ce7d781) [@bep](https://github.com/bep) [#3040](https://github.com/gohugoio/hugo/issues/3040)
-* Cleanup `strings.TrimPrefix` and `TrimSuffix` [29a2da05](https://github.com/gohugoio/hugo/commit/29a2da0593b081cdd61b93c6328af2c9ea4eb20f) [@moorereason](https://github.com/moorereason)
-
-### Output
-
-* Improve the base template (aka `baseof.html`) identification [0019ce00](https://github.com/gohugoio/hugo/commit/0019ce002449d671a20a69406da37b10977f9493) [@bep](https://github.com/bep)
-
-### Core
-
-* Implement "related content" [3b4f17bb](https://github.com/gohugoio/hugo/commit/3b4f17bbc9ff789faa581ac278ad109d1ac5b816) [@bep](https://github.com/bep) [#98](https://github.com/gohugoio/hugo/issues/98)
-* Add `Page.Equals` [f0f49ed9](https://github.com/gohugoio/hugo/commit/f0f49ed9b0c9b4545a45c95d56340fcbf4aafbef) [@bep](https://github.com/bep)
-* Rewrite `replaceDivider` to reduce memory allocation [71ae9b45](https://github.com/gohugoio/hugo/commit/71ae9b4533083be185c5314c9c5b273cc3bd07bd) [@bep](https://github.com/bep)
-
-
-### Other
-
-* Set up Hugo release flow on `CircleCI` [d2249c50](https://github.com/gohugoio/hugo/commit/d2249c50991ba7b00b092aca6e315ca1a4de75a1) [@bep](https://github.com/bep) [#3779](https://github.com/gohugoio/hugo/issues/3779)
-* Maintain the scroll position if possible [7231d5a8](https://github.com/gohugoio/hugo/commit/7231d5a829f8d97336a2120afde1260db6ee6541) [@yihui](https://github.com/yihui) [#3824](https://github.com/gohugoio/hugo/issues/3824)
-* Add an `iFrame` title to the `YouTube` shortcode [919bc921](https://github.com/gohugoio/hugo/commit/919bc9210a69c801c7304c0b529df93d1dca27aa) [@nraboy](https://github.com/nraboy)
-* Remove the theme submodule from /docs [ea2cc26b](https://github.com/gohugoio/hugo/commit/ea2cc26b390476f1c605405604f8c92afd09b6ee) [@bep](https://github.com/bep) [#3791](https://github.com/gohugoio/hugo/issues/3791)
-* Add support for multiple config files via `--config a.toml,b.toml,c.toml` [0f9f73cc](https://github.com/gohugoio/hugo/commit/0f9f73cce5c3f1f05be20bcf1d23b2332623d7f9) [@jgielstra](https://github.com/jgielstra)
-* Render task list item inside `label` for correct accessibility [c8257f8b](https://github.com/gohugoio/hugo/commit/c8257f8b726478ca70dc8984cdcc17b31e4bdc0c) [@danieka](https://github.com/danieka) [#3303](https://github.com/gohugoio/hugo/issues/3303)
-* Normalize `UniqueID` between Windows & Linux [0abdeeef](https://github.com/gohugoio/hugo/commit/0abdeeef6740a3cbba0db95374853d040f2022b8) [@Shywim](https://github.com/Shywim)
-
-
-## Fixes
-
-### Output
-
-* Fix taxonomy term base template lookup [f88fe312](https://github.com/gohugoio/hugo/commit/f88fe312cb35f7de1615c095edd2f898303dd23b) [@bep](https://github.com/bep) [#3856](https://github.com/gohugoio/hugo/issues/3856)
-* Fix `published` front matter handling [202510fd](https://github.com/gohugoio/hugo/commit/202510fdc92d52a20baeaa7edb1091f6882bd95f) [@bep](https://github.com/bep) [#3867](https://github.com/gohugoio/hugo/issues/3867)
-
-
-
-
-
-
-
-
diff --git a/docs/content/en/news/0.27.1-relnotes/index.md b/docs/content/en/news/0.27.1-relnotes/index.md
deleted file mode 100644
index 1184cc17587..00000000000
--- a/docs/content/en/news/0.27.1-relnotes/index.md
+++ /dev/null
@@ -1,21 +0,0 @@
-
----
-date: 2017-09-13
-title: "Hugo 0.27.1: One bugfix"
-description: "Hugo 0.27.1 fixes an issue introduced in Go 1.9 with HTML escaping of shortcodes in multi output sites."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
----
-
-
-
-This fixes a regression introduced in [Go 1.9](https://github.com/golang/go/issues/21844) which lead to HTML in shortcodes in multi output Hugo sites being wrongly escaped in some cases.
-
-* Fix escaped HTML Go 1.9 multioutput issue (#3880) [2d613dd9](https://github.com/gohugoio/hugo/commit/2d613dd905bb8eeb8af57e30ddd749a0f04fbd3c) [@bep](https://github.com/bep) [#3876](https://github.com/gohugoio/hugo/issues/3876)
-
-* Bump to Go 1.9 in the Snap build [642ba6ca](https://github.com/gohugoio/hugo/commit/642ba6cab24c558b16378178fe829cbc45845424) [@bep](https://github.com/bep)
-
-
-
-
diff --git a/docs/content/en/news/0.28-relnotes/index.md b/docs/content/en/news/0.28-relnotes/index.md
deleted file mode 100644
index 91128e48ee7..00000000000
--- a/docs/content/en/news/0.28-relnotes/index.md
+++ /dev/null
@@ -1,64 +0,0 @@
-
----
-date: 2017-09-25
-title: "Hugo 0.28: High-speed Syntax Highlighting!"
-description: "Chroma is the new default syntax highlighter in Hugo."
-categories: ["Releases"]
-images:
-- images/blog/hugo-28-poster.png
----
-
- Hugo `0.28` brings **blistering fast and native syntax highlighting** from [Chroma](https://github.com/alecthomas/chroma) ([fb33d828](https://github.com/gohugoio/hugo/commit/fb33d8286d78a78a74deb44355b621852a1c4033) [@bep](https://github.com/bep) [#3888](https://github.com/gohugoio/hugo/issues/3888)). A big thank you to [Alec Thomas](https://github.com/alecthomas) for taking on this massive task of porting the popular python highlighter Pygments to Go.
-
-Hugo has been caching the highlighter output to disk, so for repeated builds it has been fine, but this little snippet, showing a build of the [gohugo.io](https://gohugo.io/) site without cache and with both Pygments and Chroma, should illustrate the improvement:
-
-{{< asciicast Lc5iwTVny2kuUC8lqvNnL6oDU >}}
-
-See the [Updated Documentation](https://gohugo.io/content-management/syntax-highlighting/) for more information about how this works.
-
-Worth mentioning is also the `liveReloadPort` flag on `hugo server`, which makes it possible to do "live reloads" behind a HTTPS proxy, which makes for very cool remote customer demos.
-
-One example would be a Hugo server running behind a [ngrok](https://ngrok.com) tunnel:
-
-```bash
-ngrok http 1313
-```
-Then start the Hugo server with:
-
-```bash
-hugo server -b https://youridhere.ngrok.io --appendPort=false --liveReloadPort=443 --navigateToChanged
-```
-
-The `navigateToChanged` flag is slightly unrelated, but it is super cool ...
-
-This release represents **15 contributions by 2 contributors** to the main Hugo code base.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **9 contributions by 7 contributors**. A special thanks to [@bep](https://github.com/bep), [@i-give-up](https://github.com/i-give-up), [@muhajirframe](https://github.com/muhajirframe), and [@icannotfly](https://github.com/icannotfly) for their work on the documentation site.
-
-Hugo now has:
-
-* 19771+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 454+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 180+ [themes](http://themes.gohugo.io/)
-
-## Notes
-* Hugo now uses Chroma as new default syntax highlighter. This should in most cases work out-of-the box or with very little adjustments. But if you want to continue to use Pygments, set `pygmentsUseClassic=true` in your site config.
-* We now add a set of "no cache" headers to the responses for `hugo server`, which makes the most sense in most development scenarios. Run with `hugo server --noHTTPCache=false` to get the old behaviour.
-
-## Enhancements
-
-### Templates
-
-* Add `urls.Parse` function [81ed5647](https://github.com/gohugoio/hugo/commit/81ed564793609a32be20a569cc15da2cc02dd734) [@moorereason](https://github.com/moorereason) [#3849](https://github.com/gohugoio/hugo/issues/3849)
-* Add `math.Ceil`, `Floor`, and `Round` [19c59104](https://github.com/gohugoio/hugo/commit/19c5910485242838d6678c2aacd8501f7e646a53) [@moorereason](https://github.com/moorereason) [#3883](https://github.com/gohugoio/hugo/issues/3883)
-
-### Other
-
-* Use Chroma as new default syntax highlighter [fb33d828](https://github.com/gohugoio/hugo/commit/fb33d8286d78a78a74deb44355b621852a1c4033) [@bep](https://github.com/bep) [#3888](https://github.com/gohugoio/hugo/issues/3888)
-* Trim newlines in the hightlight shortcode [0d29a0f7](https://github.com/gohugoio/hugo/commit/0d29a0f7819e8d73149701052c29f090cd6db42b) [@bep](https://github.com/bep) [#3898](https://github.com/gohugoio/hugo/issues/3898)
-* Update `goorgeous` [b8fabce2](https://github.com/gohugoio/hugo/commit/b8fabce217fcb52e3f273491bef95c7977058732) [@bep](https://github.com/bep) [#3899](https://github.com/gohugoio/hugo/issues/3899)
-* Add `liveReloadPort` flag to server [b1804776](https://github.com/gohugoio/hugo/commit/b180477631555824a06293053e2b6e63c5f07361) [@bep](https://github.com/bep) [#3882](https://github.com/gohugoio/hugo/issues/3882)
-* Add `noHTTPCache` flag to hugo server (default on) [0b34af21](https://github.com/gohugoio/hugo/commit/0b34af216154367af7f53ce93d44e6b3d58c3f34) [@bep](https://github.com/bep) [#3897](https://github.com/gohugoio/hugo/issues/3897)
-* Make `noHTTPCache` default on [80c7ea60](https://github.com/gohugoio/hugo/commit/80c7ea60a0e0f488563a6b7311f3d4c23457aac7) [@bep](https://github.com/bep) [#3897](https://github.com/gohugoio/hugo/issues/3897)
-
diff --git a/docs/content/en/news/0.29-relnotes/index.md b/docs/content/en/news/0.29-relnotes/index.md
deleted file mode 100644
index 810781dda48..00000000000
--- a/docs/content/en/news/0.29-relnotes/index.md
+++ /dev/null
@@ -1,46 +0,0 @@
-
----
-date: 2017-09-26
-title: "Hugo 0.29: Template Metrics"
-description: "Makes it easy to find template bottle necks."
-categories: ["Releases"]
-images:
-- images/blog/hugo-29-poster.png
----
-
-Hugo `0.29` brings Template Metrics by [@moorereason](https://github.com/moorereason). Hugo is very fast, but it is still possible to write ineffective templates. Now these should be easy to identify. Just run:
-
-```bash
-hugo --templateMetrics
-```
-Now, that was the tasty carrot. The real reason this release comes so fast after the last one is to change the default value for the new `noHTTPCache` flag, which gives away too much performance to make sense as a default value.
-
-Hugo now has:
-
-* 19817+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 454+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 180+ [themes](http://themes.gohugo.io/)
-
-## Notes
-* Make `noHTTPCache` default false [e94d4f01](https://github.com/gohugoio/hugo/commit/e94d4f0177852b357f40fb9686a0ff3667d86351) [@bep](https://github.com/bep)
-
-## Enhancements
-
-### Templates
-* Add simple template metrics feature [b4a14c25](https://github.com/gohugoio/hugo/commit/b4a14c25fe85c41b79497be27ead128502a4dd7b) [@moorereason](https://github.com/moorereason)
-* Set Metrics at creation time [b5e1dc58](https://github.com/gohugoio/hugo/commit/b5e1dc5892f81da798d0d4e964a1f3328532f45e) [@bep](https://github.com/bep)
-* Fix sort order [d3681f51](https://github.com/gohugoio/hugo/commit/d3681f51c08fb11e8addcf9f0b484848d20d46cc) [@bep](https://github.com/bep)
-* Add math.Ceil, Floor, and Round to method mappings [8a69d235](https://github.com/gohugoio/hugo/commit/8a69d2356703d9f2fcb75bce0ae514e70ebd8e01) [@moorereason](https://github.com/moorereason)
-
-### Other
-* Split go build in Dockerfile [d9697e27](https://github.com/gohugoio/hugo/commit/d9697e275ecb038958b3dcea2b43e11dcba28fc9) [@tjamet](https://github.com/tjamet)
-* Update Dockerfile to benefit build cache [09d960f1](https://github.com/gohugoio/hugo/commit/09d960f17396eb7fd2c8fe6527db9503d59f0b4f) [@tjamet](https://github.com/tjamet)
-* Add git to snap package for GitInfo [a3a3f5b8](https://github.com/gohugoio/hugo/commit/a3a3f5b86114213a23337499551f000662b26022) [@ghalse](https://github.com/ghalse)
-
-
-
-
-
-
-
-
diff --git a/docs/content/en/news/0.30-relnotes/index.md b/docs/content/en/news/0.30-relnotes/index.md
deleted file mode 100644
index 9281a5c203e..00000000000
--- a/docs/content/en/news/0.30-relnotes/index.md
+++ /dev/null
@@ -1,63 +0,0 @@
-
----
-date: 2017-10-16
-title: "Hugo 0.30: Race Car Edition!"
-description: "Fast Render Mode boosts live reloading!"
-categories: ["Releases"]
-images:
-- images/blog/hugo-30-poster.png
----
-
-
-Hugo `0.30` is the **Race Car Edition**. Hugo is already very very fast, but wants much more. So we added **Fast Render Mode**. It is hard to explain, so start the Hugo development server with `hugo server` and start editing. Live reloads just got so much faster! The "how and what" is discussed at length in [other places](https://github.com/gohugoio/hugo/pull/3959), but the short version is that we now re-render only the parts of the site that you are working on.
-
-The second performance-related feature is a follow-up to the Template Metrics added in Hugo `0.29`. Now, if you add the flag `--templateMetricsHints`, we will calculate a score for how your partials can be cached (with the `partialCached` template func).
-
-This release also more or less makes the really fast Chroma highlighter a complete alternative to Pygments. Most notable is the new table `linenos` support ([7c30e2cb](https://github.com/gohugoio/hugo/commit/7c30e2cbb08fdf0e61f80c7f1aa29909aeca4211) [@bep](https://github.com/bep) [#3915](https://github.com/gohugoio/hugo/issues/3915)), which makes copy-and-paste code blocks much easier.
-
-This release represents **31 contributions by 10 contributors** to the main Hugo code base.
-[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contribution, but also a big shoutout to [@moorereason](https://github.com/moorereason), [@digitalcraftsman](https://github.com/digitalcraftsman), and [@bmon](https://github.com/bmon) for their ongoing contributions.
-And as always a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the documentation and the themes site in pristine condition.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **26 contributions by 15 contributors**. A special thanks to [@bep](https://github.com/bep), [@digitalcraftsman](https://github.com/digitalcraftsman), [@moorereason](https://github.com/moorereason), and [@kaushalmodi](https://github.com/kaushalmodi) for their work on the documentation site.
-
-Hugo now has:
-
-* 20195+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 454+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 180+ [themes](http://themes.gohugo.io/)
-
-## Notes
-
-* Running `hugo server` will now run with the new "Fast Render Mode" default on. To turn it off, run `hugo server --disableFastRender` or set `disableFastRender=true` in your site config.
-* There have been several fixes and enhancements in the Chroma highlighter. One is that it now creates Pygments compatible CSS classes, which means that you may want to re-generate the stylesheet. See the [Syntax Highlighting Doc](https://gohugo.io/content-management/syntax-highlighting/).
-
-## Enhancements
-
-### Performance
-* Only re-render the view(s) you're working on [60bd332c](https://github.com/gohugoio/hugo/commit/60bd332c1f68e49e6ac439047e7c660865189380) [@bep](https://github.com/bep) [#3962](https://github.com/gohugoio/hugo/issues/3962)
-* Detect `partialCached` candidates [5800a20a](https://github.com/gohugoio/hugo/commit/5800a20a258378440e203a6c4a4343f5077755df) [@bep](https://github.com/bep)
-* Move metrics output to the end of the site build [b277cb33](https://github.com/gohugoio/hugo/commit/b277cb33e4dfa7440fca3b7888026944ce056154) [@moorereason](https://github.com/moorereason)
-
-### Templates
-
-* Output `xmlns:xhtml` only if there are translations available [0859d9df](https://github.com/gohugoio/hugo/commit/0859d9dfe647db3b8a192da38ad7efb5480a29a1) [@jamieconnolly](https://github.com/jamieconnolly)
-* Add `errorf` template function [4fc67fe4](https://github.com/gohugoio/hugo/commit/4fc67fe44a3c65fc7faaed21d5fa5bb5f87edf2c) [@bmon](https://github.com/bmon) [#3817](https://github.com/gohugoio/hugo/issues/3817)
-* Add `os.FileExists` template function [28188789](https://github.com/gohugoio/hugo/commit/2818878994e906c292cbe00cb2a83f1531a21f32) [@digitalcraftsman](https://github.com/digitalcraftsman) [#3839](https://github.com/gohugoio/hugo/issues/3839)
-* Add `float` template function [57adc539](https://github.com/gohugoio/hugo/commit/57adc539fc98dcb6fba8070b9611b8bd545f6f7f) [@x3ro](https://github.com/x3ro) [#3307](https://github.com/gohugoio/hugo/issues/3307)
-* Rework the partial test and benchmarks [e2e8bcbe](https://github.com/gohugoio/hugo/commit/e2e8bcbec34702a27047b91b6b007a15f1fc0797) [@bep](https://github.com/bep)
-
-### Other
-
-* Change `SummaryLength` to be configurable (#3924) [8717a60c](https://github.com/gohugoio/hugo/commit/8717a60cc030f4310c1779c0cdd51db37ad636cd) [@bmon](https://github.com/bmon) [#3734](https://github.com/gohugoio/hugo/issues/3734)
-* Replace `make` with `mage` in CircleCI build [fe71cb6f](https://github.com/gohugoio/hugo/commit/fe71cb6f5f83cdc8374cf1fc35a6d48102bd4b12) [@bep](https://github.com/bep) [#3969](https://github.com/gohugoio/hugo/issues/3969)
-* Add table `linenos` support for Chroma highlighter [7c30e2cb](https://github.com/gohugoio/hugo/commit/7c30e2cbb08fdf0e61f80c7f1aa29909aeca4211) [@bep](https://github.com/bep) [#3915](https://github.com/gohugoio/hugo/issues/3915)
-* Replace `make` with `mage` [8d2580f0](https://github.com/gohugoio/hugo/commit/8d2580f07c0253e12524a4b5c13165f876d00b21) [@bep](https://github.com/bep) [#3937](https://github.com/gohugoio/hugo/issues/3937)
-* Create `magefile` from `Makefile` [384a6ac4](https://github.com/gohugoio/hugo/commit/384a6ac4bd2de16fcd6a1c952e7ca41b66023a12) [@natefinch](https://github.com/natefinch)
-* Clean up lint in various packages [47fdfd51](https://github.com/gohugoio/hugo/commit/47fdfd5196cd24a23b30afe1d88969ffb413ab59) [@moorereason](https://github.com/moorereason)
-
-## Fixes
-
-* Make sure `Date` and `PublishDate` are always set to a value if one is available [6a30874f](https://github.com/gohugoio/hugo/commit/6a30874f19610a38e846e120aac03c68e12f9b7b) [@bep](https://github.com/bep) [#3854](https://github.com/gohugoio/hugo/issues/3854)
-* Add correct config file name to verbose server log [15ec031d](https://github.com/gohugoio/hugo/commit/15ec031d9818d239bfbff525c00cd99cc3118a96) [@mdhender](https://github.com/mdhender)
diff --git a/docs/content/en/news/0.30.1-relnotes/index.md b/docs/content/en/news/0.30.1-relnotes/index.md
deleted file mode 100644
index 68165e877a1..00000000000
--- a/docs/content/en/news/0.30.1-relnotes/index.md
+++ /dev/null
@@ -1,23 +0,0 @@
-
----
-date: 2017-10-19T00:00:00+02:00
-title: "Hugo 0.30.1: Two Important Bugfixes!"
-description: "Fixes `hugo -w` and validator-broken Sitemap."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
----
-
-
-
-This is a bug-fix release with two important fixes.
-
-* Fix hugo -w [fa53b13c](https://github.com/gohugoio/hugo/commit/fa53b13ca0ffb1db6ed20f5353661d3f8a5fd455) [@bep](https://github.com/bep) [#3980](https://github.com/gohugoio/hugo/issues/3980)
-* Revert sitemap template change to make the Google validator happy [5109ed520](https://github.com/gohugoio/hugo/commit/5109ed520f2ddde815d50e7b31acbbfc57ce7719) [@bep](https://github.com/bep) [#3978](https://github.com/gohugoio/hugo/issues/3978)
-
-
-Hugo now has:
-
-* 20285+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 454+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 180+ [themes](http://themes.gohugo.io/)
diff --git a/docs/content/en/news/0.30.2-relnotes/index.md b/docs/content/en/news/0.30.2-relnotes/index.md
deleted file mode 100644
index 1d4bcd94690..00000000000
--- a/docs/content/en/news/0.30.2-relnotes/index.md
+++ /dev/null
@@ -1,14 +0,0 @@
-
----
-date: 2017-10-19T12:00:00+02:00
-title: "Hugo 0.30.2: One More Bugfix"
-description: "Fixes Fast Render mode when having sub-path in `baseURL`."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
----
-This release fixes Fast Render mode with sub-path in baseURL [31641033](https://github.com/gohugoio/hugo/commit/3164103310fbca1211cfa9ce4a5eb7437854b6ad) [@bep](https://github.com/bep) [#3981](https://github.com/gohugoio/hugo/issues/3981).
-
-
-
-
diff --git a/docs/content/en/news/0.31-relnotes/index.md b/docs/content/en/news/0.31-relnotes/index.md
deleted file mode 100644
index ba16dfacb86..00000000000
--- a/docs/content/en/news/0.31-relnotes/index.md
+++ /dev/null
@@ -1,79 +0,0 @@
-
----
-date: 2017-11-20
-title: "Hugo 0.31: Language Multihost Edition!"
-description: "Hugo 0.31: Multihost, smart union static dirs, and more ..."
-categories: ["Releases"]
-images:
-- images/blog/hugo-31-poster.png
----
-
- Hugo `0.31` is the **Language Multihost Edition!**
-
-> The Multihost feature is sponsored by [eSolia](https://esolia.com/), [@rickcogley](https://github.com/rickcogley)'s company.
-
-[Multihost](https://gohugo.io/content-management/multilingual/#configure-multilingual-multihost) means that you can have a **`baseURL` per language**, for example, `https://no.example.com` and `https://en.example.com`. This is seamlessly integrated, and the built-in web server with live reload and `navigateToChanged` etc. just works. A related enhancement in this release is the support for **as many static dirs as you need**, with intelligent language overrides, forming a big union file system. Add to that several other language related fixes and enhancements, it is safe to say that this is the version you want for multilingual Hugo sites!
-
-This release represents **44 contributions by 7 contributors** to the main Hugo code base.
-[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@kaushalmodi](https://github.com/kaushalmodi), [@natefinch](https://github.com/natefinch), and [@betaveros](https://github.com/betaveros) for their ongoing contributions.
-And as always a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the documentation and the themes site in pristine condition.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **13 contributions by 9 contributors**. A special thanks to [@oncletom](https://github.com/oncletom), [@kaushalmodi](https://github.com/kaushalmodi), [@XhmikosR](https://github.com/XhmikosR), and [@digitalcraftsman](https://github.com/digitalcraftsman) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 21105+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 455+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 184+ [themes](http://themes.gohugo.io/)
-
-## Notes
-
-* For mapping of translated content, Hugo now considers the full path of the content file, which makes it possible with translation of duplicate content filenames such as `index.md`. A specific translation key can be specified with the new `translationKey` front matter variable. See [#2699](https://github.com/gohugoio/hugo/issues/2699).
-
-
-## Enhancements
-
-### Language related
-
-* Support unknown language codes [23ba779f](https://github.com/gohugoio/hugo/commit/23ba779fab90ce45cddd68b4f49a2515ce6d4878) [@bep](https://github.com/bep) [#3564](https://github.com/gohugoio/hugo/issues/3564)
-* Fix `.IsTranslated` with identical filenames [b3daa1f4](https://github.com/gohugoio/hugo/commit/b3daa1f4bf1b84bcc5da028257ba609be74e3ecc) [@bep](https://github.com/bep) [#2699](https://github.com/gohugoio/hugo/issues/2699)
-* Fall back to unstranslated base template [0a81a6b4](https://github.com/gohugoio/hugo/commit/0a81a6b4bae3de53aa9c179b855c671a2d30eec7) [@bep](https://github.com/bep) [#3893](https://github.com/gohugoio/hugo/issues/3893)
-* Add support for multiple static dirs [60dfb9a6](https://github.com/gohugoio/hugo/commit/60dfb9a6e076200ab3ca3fd30e34bb3c14e0a893) [@bep](https://github.com/bep) [#36](https://github.com/gohugoio/hugo/issues/36)[#4027](https://github.com/gohugoio/hugo/issues/4027)
-* Add multilingual multihost support [2e046576](https://github.com/gohugoio/hugo/commit/2e0465764b5dacc511b977b1c9aa07324ad0ee9c) [@bep](https://github.com/bep) [#4027](https://github.com/gohugoio/hugo/issues/4027)
-
-### Templates
-
-* Refactor `Mod` with `cast` [76dc811c](https://github.com/gohugoio/hugo/commit/76dc811c6539b2ed8b4d3b22693e5088b9f6ecfe) [@artem-sidorenko](https://github.com/artem-sidorenko)
-* Add support for height argument to figure shortcode [488631fe](https://github.com/gohugoio/hugo/commit/488631fe0abc3667355345c7eb98ba7a2204deb5) [@kaushalmodi](https://github.com/kaushalmodi) [#4014](https://github.com/gohugoio/hugo/issues/4014)
-
-### Core
-
-* Use ms precision for static change logging [bb048d81](https://github.com/gohugoio/hugo/commit/bb048d811d3977adb10656335cd339cd8c945a25) [@bep](https://github.com/bep)
-* Update Chroma to get the latest SASS lexer [b32ffed6](https://github.com/gohugoio/hugo/commit/b32ffed6abc67646cad89e163846f3ffef29cec8) [@bep](https://github.com/bep) [#4069](https://github.com/gohugoio/hugo/issues/4069)
-* Bump to Go 1.9.2 [9299a16c](https://github.com/gohugoio/hugo/commit/9299a16c9952a284d3ac3f31d2662f1812f77768) [@bep](https://github.com/bep) [#4064](https://github.com/gohugoio/hugo/issues/4064)
-* Update Travis and snapcraft to Go 1.9.2 [77cbd001](https://github.com/gohugoio/hugo/commit/77cbd001ff6b2e0aaa48566ef2af49ca68e19af9) [@bep](https://github.com/bep) [#4064](https://github.com/gohugoio/hugo/issues/4064)
-* Handle Taxonomy permalinks [d9a78b61](https://github.com/gohugoio/hugo/commit/d9a78b61adefe8e1803529f4774185874af85148) [@betaveros](https://github.com/betaveros) [#1208](https://github.com/gohugoio/hugo/issues/1208)
-
-
-### Other
-
-* Support Fast Render mode with sub-path in baseURL [31641033](https://github.com/gohugoio/hugo/commit/3164103310fbca1211cfa9ce4a5eb7437854b6ad) [@bep](https://github.com/bep) [#3981](https://github.com/gohugoio/hugo/issues/3981)
-* Simplify Site benchmarks [c3c10f2c](https://github.com/gohugoio/hugo/commit/c3c10f2c7ce4ee11186f51161943efc8b37a28c9) [@bep](https://github.com/bep)
-* Replace `make` with `mage` to build Hugo [#3969](https://github.com/gohugoio/hugo/issues/3969)
-* Convert to `dep` as dependency/vendor manager for Hugo [#3988](https://github.com/gohugoio/hugo/issues/3988)
-* Pre-allocate some slices [a9be687b](https://github.com/gohugoio/hugo/commit/a9be687b81df01c7343f78f0d3760042f467baa4) [@bep](https://github.com/bep)
-
-## Fixes
-
-### Templates
-
-* Make sure only one instance of a cached partial is rendered [#4086](https://github.com/gohugoio/hugo/issues/4086)
-
-### Other
-
-* Fix broken shortcodes for `Ace` and `Amber` [503ca6de](https://github.com/gohugoio/hugo/commit/503ca6de6ceb0b4af533f9efeff917d6f3871278) [@bep](https://github.com/bep) [#4051](https://github.com/gohugoio/hugo/issues/4051)
-* Fix error handling in `mage` build [c9c19d79](https://github.com/gohugoio/hugo/commit/c9c19d794537cf76ff281788c3d6cf5f2beac54d) [@natefinch](https://github.com/natefinch)
-* Fix `hugo -w` [fa53b13c](https://github.com/gohugoio/hugo/commit/fa53b13ca0ffb1db6ed20f5353661d3f8a5fd455) [@bep](https://github.com/bep) [#3980](https://github.com/gohugoio/hugo/issues/3980)
-
diff --git a/docs/content/en/news/0.31.1-relnotes/index.md b/docs/content/en/news/0.31.1-relnotes/index.md
deleted file mode 100644
index a74470d646b..00000000000
--- a/docs/content/en/news/0.31.1-relnotes/index.md
+++ /dev/null
@@ -1,19 +0,0 @@
-
----
-date: 2017-11-27
-title: "Hugo 0.31.1: One Bugfix!"
-description: "Fixes broken `--appendPort=false`."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-This is a bug-fix release with one important bug fix:
-
-* Fix broken `--appendPort=false` [8afd7d9c](https://github.com/gohugoio/hugo/commit/8afd7d9ceb0d168300e3399c6e87a355a88c9a28) [@bep](https://github.com/bep) [#4111](https://github.com/gohugoio/hugo/issues/4111)
-
-
-
-
-
diff --git a/docs/content/en/news/0.32-relnotes/index.md b/docs/content/en/news/0.32-relnotes/index.md
deleted file mode 100644
index c3f36fe644b..00000000000
--- a/docs/content/en/news/0.32-relnotes/index.md
+++ /dev/null
@@ -1,94 +0,0 @@
-
----
-date: 2017-12-31
-title: "Hugo 0.32: Page Bundles and Image Processing!"
-description: "Images and other resources with page-relative links, resize, scale and crop images, and much more."
-categories: ["Releases"]
-images:
-- images/blog/hugo-32-poster.png
----
-
- Hugo `0.32` features **Page Bundles and Image Processing** by [@bep](https://github.com/bep), which is very cool and useful on so many levels. Read about it in more detail in the [Hugo documentation](https://gohugo.io/about/new-in-032/), but some of the highlights include:
-
-* Automatic bundling of a content page with its resources. Resources can be anything: Images, `JSON` files ... and also other content pages.
-* A `Resource` will have its `RelPermalink` and `Permalink` relative to the "owning page". This makes the complete article with both text and images portable (just send a ZIP file with a folder to your editor), and it can be previewed directly on GitHub.
-* Powerful and simple to use image processing with the new `.Resize`, `.Fill`, and `.Fit` methods on the new `Image` resource.
-* Full support for symbolic links inside `/content`, both for regular files and directories.
-
-The built-in benchmarks in Hugo show that this is also the [fastest and most memory effective](https://gist.github.com/bep/2a9bbd221de2da5d39c8b32085c658f7) Hugo version to date. But note that the build time total reported in the console is now adjusted to be the *real total*, including the copy of static files. So, if it reports more milliseconds, it is still most likely faster ...
-
-This release represents **30 contributions by 7 contributors** to the main Hugo code base.
-
-[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@betaveros](https://github.com/betaveros), [@chaseadamsio](https://github.com/chaseadamsio), and [@kropp](https://github.com/kropp). And as always big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the documentation and the themes site in pristine condition.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **17 contributions by 7 contributors**. A special thanks to [@bep](https://github.com/bep), [@felicianotech](https://github.com/felicianotech), [@maiki](https://github.com/maiki), and [@carlchengli](https://github.com/carlchengli) for their work on the documentation site.
-
-Hugo now has:
-
-* 22061+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 454+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 193+ [themes](http://themes.gohugo.io/)
-
-Today is **New Year's Eve.** It is the last day of 2017, a year that have seen a **string of pearls of Hugo releases**, making Hugo _the_ top choice for website development:
-
-* 0.32, December 2017: **Page Bundles and Image Processing** edition.
-* 0.31, November 2017: The Language **Multihost Edition!** with one `baseURL` per language.
-* 0.30, October 2017: The Race Car Edition with the **Fast Render Mode**.
-* 0.29, September 2017: Added **Template Metrics**.
-* 0.28, September 2017: **Blistering fast and native syntax highlighting** from [Chroma](https://github.com/alecthomas/chroma).
-* 0.27, September 2017: Fast and flexible **Related Content.**
-* 0.26, August 2017: The **Language Style Edition** with AP Style or Chicago Style Title Case and « French Guillemets ».
-* 0.25, July 2017: The **Kinder Surprise** edition added, among other cool things, `hugo server --navigateToChanged` which navigates to the content page you start editing.
-* 0.24, June 2017: Was **The Revival of the Archetypes!** Now archetype files, i.e. the content file templates, can include template syntax with all of Hugo's functions and variables.
-* 0.23, June 2017: Hugo moved to it's own GitHub organization, **gohugoio**.
-* 0.22, June 2017: Added **nested sections**, a long sought after feature.
-* 0.21, May 2017: Full support for shortcodes per output format (think **AMP**).
-* 0.20, April 2017: Was all about **Custom Output Formats**.
-* 0.19, February 2017: Native Emacs Org-mode content support and lots of internal upgrades.
-
-## Notes
-
-* The build total in the console is now the ... total (i.e. it now includes both the copy of the static files and the Hugo build). So if your Hugo site seems to build slightly slower, it is in reality probably slightly faster than before this release.
-* Images and other static resources in folders with "_index.md" will have its `RelPermalink` relative to its page.
-* Images and other static resources in or below "index.md" folders will have its `RelPermalink` relative to its page (respecting permalink settings etc.)
-* Content pages in or below "index.md" will not get their own `URL`, but will be part of the `.Resources` collection of its page.
-* `.Site.Files` is deprecated.
-* Hugo no longer minfies CSS files inside `/content`. This was an undocumented "proof of concept feature". We may revisit the "assets handling" in a future release.
-* `Page.GetParam`does not lowercase your result anymore. If you really want to lowercase your params, do it with `.GetParam "myparam" | lower` or similar.
-
-Previously deprecated that will now `ERROR`:
-
-* `disable404`: Use `disableKinds=["404"]`
-* `disableRSS`: Use `disableKinds=["RSS"]`
-* `disableSitemap`: Use `disableKinds=["sitemap"]`
-* `disableRobotsTXT`: Use `disableKinds=["robotsTXT"]`
-
-## Enhancements
-
-* Add `.Title` and `.Page` to `MenuEntry` [9df3736f](https://github.com/gohugoio/hugo/commit/9df3736fec164c51d819797416dc263f2869be77) [@rmetzler](https://github.com/rmetzler) [#2784](https://github.com/gohugoio/hugo/issues/2784)
-* Add `Pandoc` support [e69da7a4](https://github.com/gohugoio/hugo/commit/e69da7a4cb725987f153707bf2fc59c135007e2a) [@betaveros](https://github.com/betaveros) [#234](https://github.com/gohugoio/hugo/issues/234)
-* Implement Page bundling and image handling [3cdf19e9](https://github.com/gohugoio/hugo/commit/3cdf19e9b7e46c57a9bb43ff02199177feb55768) [@bep](https://github.com/bep) [#3651](https://github.com/gohugoio/hugo/issues/3651)[#3158](https://github.com/gohugoio/hugo/issues/3158)[#1014](https://github.com/gohugoio/hugo/issues/1014)[#2021](https://github.com/gohugoio/hugo/issues/2021)[#1240](https://github.com/gohugoio/hugo/issues/1240)[#3757](https://github.com/gohugoio/hugo/issues/3757)
-* Make `chomp` return the type it receives [22cd89ad](https://github.com/gohugoio/hugo/commit/22cd89adc4792a3b55389d38acd4acfae3786775) [@kropp](https://github.com/kropp) [#2187](https://github.com/gohugoio/hugo/issues/2187)
-* Reuse the `BlackFriday` config instance when possible [db4b7a5c](https://github.com/gohugoio/hugo/commit/db4b7a5c6742c75f9cd9627d3b054d3a72802ec8) [@bep](https://github.com/bep)
-* Remove the goroutines from the shortcode lexer [24369410](https://github.com/gohugoio/hugo/commit/243694102a60da2fb1050020f68384539f9f9ef5) [@bep](https://github.com/bep)
-* Improve site benchmarks [051fa343](https://github.com/gohugoio/hugo/commit/051fa343d06d6c070df742f7cbd125432fcab665) [@bep](https://github.com/bep)
-* Update `Chroma` to `v0.2.0` [79892101](https://github.com/gohugoio/hugo/commit/7989210120dbde78da3741e2ef01b13f4aa78692) [@bep](https://github.com/bep) [#4087](https://github.com/gohugoio/hugo/issues/4087)
-* Update `goorgeous` to `v1.1.0` [7f2ae3ef](https://github.com/gohugoio/hugo/commit/7f2ae3ef39f27a9bd26ddb9258b073a840faf491) [@chaseadamsio](https://github.com/chaseadamsio)
-* Add test for homepage content for all rendering engines [407c2402](https://github.com/gohugoio/hugo/commit/407c24020ef2db90cf33fd07e7522b2257013722) [@bep](https://github.com/bep) [#4166](https://github.com/gohugoio/hugo/issues/4166)
-* Add output formats definition to benchmarks [a2d81ce9](https://github.com/gohugoio/hugo/commit/a2d81ce983d45b5742c93bd472503c88286f099a) [@bep](https://github.com/bep)
-
-## Fixes
-
-### Templates
-
-* Do not unescape input to `highlight` [c067f345](https://github.com/gohugoio/hugo/commit/c067f34558b82455b63b9ce8f5983b4b4849c7cf) [@bep](https://github.com/bep) [#4179](https://github.com/gohugoio/hugo/issues/4179)
-* Properly close image file in `imageConfig` [6d79beb5](https://github.com/gohugoio/hugo/commit/6d79beb5f67dbb54d7714c3195addf9d8e3924e8) [@bep](https://github.com/bep)
- * Fix `opengraph` video range template [23f69efb](https://github.com/gohugoio/hugo/commit/23f69efb3914946b39ce673fcc0f2e3a9ed9d878) [@drlogout](https://github.com/drlogout) [#4136](https://github.com/gohugoio/hugo/issues/4136)
-* Fix `humanize` for multi-byte runes [e7652180](https://github.com/gohugoio/hugo/commit/e7652180a13ce149041c48a1c2754c471df569c8) [@bep](https://github.com/bep) [#4133](https://github.com/gohugoio/hugo/issues/4133)
-
-### Other
-
-* Fix broken live reload without a server port. [25114986](https://github.com/gohugoio/hugo/commit/25114986086e5877a0b4108d8cf5e4e95f377241) [@sainaen](https://github.com/sainaen) [#4141](https://github.com/gohugoio/hugo/issues/4141)
-* Make sure all language homes are always re-rendered in fast render mode [72903be5](https://github.com/gohugoio/hugo/commit/72903be587e9c4e3644f60b11e26238ec03da2db) [@bep](https://github.com/bep) [#4125](https://github.com/gohugoio/hugo/issues/4125)
-* Do not `tolower` result from Page.GetParam [1c114d53](https://github.com/gohugoio/hugo/commit/1c114d539b0755724443fe28c90b12fe2a19085a) [@bep](https://github.com/bep) [#4187](https://github.com/gohugoio/hugo/issues/4187)
diff --git a/docs/content/en/news/0.32.1-relnotes/index.md b/docs/content/en/news/0.32.1-relnotes/index.md
deleted file mode 100644
index 867e3413e28..00000000000
--- a/docs/content/en/news/0.32.1-relnotes/index.md
+++ /dev/null
@@ -1,18 +0,0 @@
-
----
-date: 2018-01-02
-title: "Hugo 0.32.1: Two bugfixes"
-description: "Fixes image processing in shortcodes."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
----
-
-This fixes 2 bugs from the Hugo 0.32 release.
-
-* Fix image processing from shortcodes in non-server mode. [@bep](https://github.com/bep) [#4202](https://github.com/gohugoio/hugo/issues/4202)
-* Fix broken `hugo --renderToMemory`. Note that this is only useful for benchmark testing, as there is no easy way to actually view the result. [d36d71ed](https://github.com/gohugoio/hugo/commit/d36d71edd3b04df3b34edf4d108e3995a244c4f0) [@bep](https://github.com/bep) [#4212](https://github.com/gohugoio/hugo/issues/4212)
-
-
-
-
diff --git a/docs/content/en/news/0.32.2-relnotes/index.md b/docs/content/en/news/0.32.2-relnotes/index.md
deleted file mode 100644
index 7453a2678ab..00000000000
--- a/docs/content/en/news/0.32.2-relnotes/index.md
+++ /dev/null
@@ -1,23 +0,0 @@
-
----
-date: 2018-01-03
-title: "Hugo 0.32.2: One bugfix"
-description: "Fixes one issue with publishing of processed images when no cache."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with one important fix.
-
-
-* Handle publish of processed images to /public on fresh build with no image cache in /resources (as reported by one person in a Netlify build) [196da49c](https://github.com/gohugoio/hugo/commit/196da49c9d906fbae6d389fdd32b80c27cb38de4) [@bep](https://github.com/bep) [#4213](https://github.com/gohugoio/hugo/issues/4213)
-
-
-
-
-
-
diff --git a/docs/content/en/news/0.32.3-relnotes/index.md b/docs/content/en/news/0.32.3-relnotes/index.md
deleted file mode 100644
index ad795a1837c..00000000000
--- a/docs/content/en/news/0.32.3-relnotes/index.md
+++ /dev/null
@@ -1,30 +0,0 @@
-
----
-date: 2018-01-08
-title: "Hugo 0.32.3: Some important bug fixes"
-description: "Fixes multilingual resource (images etc.) handling etc."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-Hugo `0.32` was a big and [really cool](https://gohugo.io/news/0.32-relnotes/) release, and the [Hugo Forum](https://discourse.gohugo.io/) has been filled with questions from people wanting to upgrade their Hugo sites to be able to use the new image processing feature etc.
-
-And with that we have discovered some issues, which this release should fix, mostly releated to multilingual sites:
-
-* Fix multihost detection for sites without language definition [8969331f](https://github.com/gohugoio/hugo/commit/8969331f5be352939883074034adac6b7086ddc8) [@bep](https://github.com/bep) [#4221](https://github.com/gohugoio/hugo/issues/4221)
-* Fix hugo benchmark --renderToMemory [059e8458](https://github.com/gohugoio/hugo/commit/059e8458d690dbb9fcd3ebd58cfc61b062d3138e) [@bep](https://github.com/bep) [#4218](https://github.com/gohugoio/hugo/issues/4218)
-* Fix URLs for bundle resources in multihost mode [ab82a27d](https://github.com/gohugoio/hugo/commit/ab82a27d055c3aa177821d81a45a5c6e972aa29e) [@bep](https://github.com/bep) [#4217](https://github.com/gohugoio/hugo/issues/4217)
-* Fix sub-folder baseURL handling for Page resources [f25d8a9e](https://github.com/gohugoio/hugo/commit/f25d8a9e17fb65fa41dafdcbf0358853d68eaf45) [@bep](https://github.com/bep) [#4228](https://github.com/gohugoio/hugo/issues/4228)
-* Avoid processing and storing same image for each language [4b04db0f](https://github.com/gohugoio/hugo/commit/4b04db0f0855a1f54895d6c93c52dcea4b1ce3ca) [@bep](https://github.com/bep) [#4231](https://github.com/gohugoio/hugo/issues/4231)
-* Resources.ByType should return Resources [97c1866e](https://github.com/gohugoio/hugo/commit/97c1866e322284dec46db6f3d235807507f5b69f) [@bep](https://github.com/bep) [#4234](https://github.com/gohugoio/hugo/issues/4234)
-* Report build time on config.toml change [6feb1387](https://github.com/gohugoio/hugo/commit/6feb138785eeb9e813428d0df30010d9b5fb1059) [@bep](https://github.com/bep) [#4232](https://github.com/gohugoio/hugo/issues/4232)[#4224](https://github.com/gohugoio/hugo/issues/4224)
-* Fix handling of mixed-case taxonomy folders with content file [2d3189b2](https://github.com/gohugoio/hugo/commit/2d3189b22760e0a8995dae082a6bc5480f770bfe) [@bep](https://github.com/bep) [#4238](https://github.com/gohugoio/hugo/issues/4238)
-
-
-
-
-
diff --git a/docs/content/en/news/0.32.4-relnotes/index.md b/docs/content/en/news/0.32.4-relnotes/index.md
deleted file mode 100644
index bd8163e0ddf..00000000000
--- a/docs/content/en/news/0.32.4-relnotes/index.md
+++ /dev/null
@@ -1,20 +0,0 @@
-
----
-date: 2018-01-11
-title: "Hugo 0.32.4: Two bugfixes"
-description: "Fixes non-ASCII path handling for Page resources."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
----
-
-This is a bug-fix release with two of important fixes.
-
-* Fix non-ASCII path handling for Page resources [f0eecc6a](https://github.com/gohugoio/hugo/commit/f0eecc6a4f541838e9930c98bc982546f65c7a4f) [@bep](https://github.com/bep) [#4241](https://github.com/gohugoio/hugo/issues/4241)
-* Fix `--cleanDestinationDir` [5235a5bf](https://github.com/gohugoio/hugo/commit/5235a5bf5ef44b3789341e1d25b681a7bb14771a) [@biodranik](https://github.com/biodranik) [#4246](https://github.com/gohugoio/hugo/issues/4246)[#4248](https://github.com/gohugoio/hugo/issues/4248)
-
-
-
-
-
-
diff --git a/docs/content/en/news/0.33-relnotes/featured-hugo-33-poster.png b/docs/content/en/news/0.33-relnotes/featured-hugo-33-poster.png
deleted file mode 100644
index c30caafcc24..00000000000
Binary files a/docs/content/en/news/0.33-relnotes/featured-hugo-33-poster.png and /dev/null differ
diff --git a/docs/content/en/news/0.33-relnotes/index.md b/docs/content/en/news/0.33-relnotes/index.md
deleted file mode 100644
index 74cd50dc49a..00000000000
--- a/docs/content/en/news/0.33-relnotes/index.md
+++ /dev/null
@@ -1,76 +0,0 @@
-
----
-date: 2018-01-18
-title: "Hugo 0.33: The New Kinder Surprise!"
-description: "Hugo 0.33 comes with resource (images etc.) metadata, `type` and `layout` for all page types, `url` in front matter for list pages …"
-categories: ["Releases"]
----
-
- Hugo `0.33` is the first main Hugo release of the new year, and it is safe to say that [@bep](https://github.com/bep) has turned off his lazy Christmas mode :smiley:
-
-This is a full makeover of the layout selection logic with full custom `layout` and `type` support (many have asked for this). Also, Hugo now respects the `url` value in front matter for all page types, including sections. Also, you can now configure `uglyURLs` per section.
-
-But this release is also a follow-up to the `0.32` release which was all about bundles with resources and powerful image processing. With this release it is now simple to add metadata to your images and other bundle resources.
-
-[@bep](https://github.com/bep) has added a section with examples of both `resources` configuration in both `YAML` and `TOML` front matter in his [test site](http://hugotest.bep.is/resourcemeta/). The example below shows a sample of how it would look like in `YAML`:
-
-```yaml
-date: 2017-01-17
-title: My Bundle With YAML Resource Metadata
-resources:
-- src: "image-4.png"
- title: "The Fourth Image"
-- src: "*.png"
- name: "my-cool-image-:counter"
- title: "The Image #:counter"
- params:
- byline: "bep"
-```
-
-This release represents **41 contributions by 3 contributors** to the main Hugo code base.
-
-Hugo now has:
-
-* 22553+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 448+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 197+ [themes](http://themes.gohugo.io/)
-
-## Notes
-* We have re-implemented and unified the template layout lookup logic. This has made it more powerful and much simpler to understand. We don't expect any sites to break because of this. We have tested lots of Hugo sites, including the 200 [themes](http://themes.gohugo.io/).
-* The `indexes` type is removed from template lookup. It's not in the documentation, and is a legacy term inherited from very old Hugo versions.
-* If you have sub-dirs in your shiny new bundles (e.g. `my-bundle/images`) and use the `*Prefix*` methods to find them, we have made an unintended change that affects you. See [this issue](https://github.com/gohugoio/hugo/issues/4295).
-
-## Enhancements
-
-### Templates
-
-* Respect `Type` and `Layout` for list template selection [51dd462c](https://github.com/gohugoio/hugo/commit/51dd462c3958f7cf032b06503f1f200a6aceebb9) [@bep](https://github.com/bep) [#3005](https://github.com/gohugoio/hugo/issues/3005)[#3245](https://github.com/gohugoio/hugo/issues/3245)
-
-### Core
-
-* Allow `url` in front matter for list type pages [8a409894](https://github.com/gohugoio/hugo/commit/8a409894bdb0972e152a2eccc47a2738568e1cfc) [@bep](https://github.com/bep) [#4263](https://github.com/gohugoio/hugo/issues/4263)
-* Improve `.Site.GetPage` for regular translated pages. Before this change it was not possible to say "get me the current language edition of the given content page if possible." Now you can do that by doing a lookup without any extensions: `.Site.GetPage "page" "post/mypost"` [9409bc0f](https://github.com/gohugoio/hugo/commit/9409bc0f799a8057836a14ccdf2833a55902175e) [@bep](https://github.com/bep) [#4285](https://github.com/gohugoio/hugo/issues/4285)
-* Add front matter metadata to `Resource` [20c9b6ec](https://github.com/gohugoio/hugo/commit/20c9b6ec81171d1c586ea31d5d08b40b0edaffc6) [@bep](https://github.com/bep) [#4244](https://github.com/gohugoio/hugo/issues/4244)
-* Implement `Resources.ByPrefix` [46db900d](https://github.com/gohugoio/hugo/commit/46db900dab9c0e6fcd9d227f10a32fb24f5c8bd9) [@bep](https://github.com/bep) [#4266](https://github.com/gohugoio/hugo/issues/4266)
-* Make `GetByPrefix` work for Page resources [60c9f3b1](https://github.com/gohugoio/hugo/commit/60c9f3b1c34b69771e25a66906f150f460d73223) [@bep](https://github.com/bep) [#4264](https://github.com/gohugoio/hugo/issues/4264)
-* Make `Resources.GetByPrefix` case insensitive [db85e834](https://github.com/gohugoio/hugo/commit/db85e83403913cff4b8737b138932b28e5bf6160) [@bep](https://github.com/bep) [#4258](https://github.com/gohugoio/hugo/issues/4258)
-* Update `Chroma` and other third-party deps [64f0e9d1](https://github.com/gohugoio/hugo/commit/64f0e9d1c1d4ff2249fd9cf9749e70485002b36d) [@bep](https://github.com/bep) [#4267](https://github.com/gohugoio/hugo/issues/4267)
-* Remove superflous `BuildDate` logic [13d53b31](https://github.com/gohugoio/hugo/commit/13d53b31f19240879122d6b7e4aaeb60b5130a3c) [@bep](https://github.com/bep) [#4272](https://github.com/gohugoio/hugo/issues/4272)
-* Run benchmarks 3 times [b6ea6d07](https://github.com/gohugoio/hugo/commit/b6ea6d07d0b072d850fb066c78976acd6c2f5e81) [@bep](https://github.com/bep)
-* Support `uglyURLs` per section [57e10f17](https://github.com/gohugoio/hugo/commit/57e10f174e51cc5e1cf5f37eed30a0f3b153dd64) [@bep](https://github.com/bep) [#4256](https://github.com/gohugoio/hugo/issues/4256)
-* Update CONTRIBUTING.md [1046e936](https://github.com/gohugoio/hugo/commit/1046e9363f2e382fd0b4aac838735ae4cbbebe5a) [@vassudanagunta](https://github.com/vassudanagunta)
-* Support offline builds [d5803da1](https://github.com/gohugoio/hugo/commit/d5803da1befba5446d1b2c1ad16f6467dc7b3991) [@vassudanagunta](https://github.com/vassudanagunta)
-
-## Fixes
-
-* Fix handling of mixed-case taxonomy folders with content file [2d3189b2](https://github.com/gohugoio/hugo/commit/2d3189b22760e0a8995dae082a6bc5480f770bfe) [@bep](https://github.com/bep) [#4238](https://github.com/gohugoio/hugo/issues/4238)
-* Fix handling of very long image file names [ecaf1451](https://github.com/gohugoio/hugo/commit/ecaf14514e06321823bdd10235cf23e7d654ba77) [@bep](https://github.com/bep) [#4261](https://github.com/gohugoio/hugo/issues/4261)
-* Update `Afero` to avoid panic on "file name is too long" [f8a119b6](https://github.com/gohugoio/hugo/commit/f8a119b606d55aa4f31f16e5a3cadc929c99e4f8) [@bep](https://github.com/bep) [#4240](https://github.com/gohugoio/hugo/issues/4240)
-* And now really fix the server watch logic [d4f8f88e](https://github.com/gohugoio/hugo/commit/d4f8f88e67f958b8010f90cb9b9854114e52dac2) [@bep](https://github.com/bep) [#4275](https://github.com/gohugoio/hugo/issues/4275)
-* Fix server without watch [4e524ffc](https://github.com/gohugoio/hugo/commit/4e524ffcfff48c017717e261c6067416aa56410f) [@bep](https://github.com/bep) [#4275](https://github.com/gohugoio/hugo/issues/4275)
-
-
-
-
-
-
diff --git a/docs/content/en/news/0.34-relnotes/featured-34-poster.png b/docs/content/en/news/0.34-relnotes/featured-34-poster.png
deleted file mode 100644
index a5c81b8c808..00000000000
Binary files a/docs/content/en/news/0.34-relnotes/featured-34-poster.png and /dev/null differ
diff --git a/docs/content/en/news/0.34-relnotes/index.md b/docs/content/en/news/0.34-relnotes/index.md
deleted file mode 100644
index dd5418a776c..00000000000
--- a/docs/content/en/news/0.34-relnotes/index.md
+++ /dev/null
@@ -1,44 +0,0 @@
-
----
-date: 2018-01-22
-title: "Hugo 0.34: Pattern matching to filter images and other resources"
-description: "Hugo 0.34 adds full glob with super-asterisk support, for example `*.jpg`."
-categories: ["Releases"]
----
-
-Hugo `0.34` is a small release. It contains a few smaller bug-fixes, but more important is an overhaul of the API used to find images and other resources in your page bundles.
-
-We have added two simple methods on the `Resources` object:
-
-* `.Match` finds every resource matching a pattern. Examples: `.Match "images/*.jpg"` finds every JPEG image in `images` and `.Match "**.jpg"` finds every JPEG image in the bundle.
-* `.GetMatch` finds the first resource matching the pattern given.
-
-**Note: The path separators used are Unix-style forward slashes, even on Windows.**
-
-It uses [standard wildcard syntax](http://tldp.org/LDP/GNU-Linux-Tools-Summary/html/x11655.htm) with the addition of the `**`, aka super-asterisk, which matches across path boundaries.
-
-Thanks to [@gobwas](https://github.com/gobwas/glob) for a fast and easy-to-use Glob library.
-
-This release represents **5 contributions by 1 contributors** to the main Hugo code base.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **25 contributions by 16 contributors**. A special thanks to [@bep](https://github.com/bep), [@rmetzler](https://github.com/rmetzler), [@chris-rudmin](https://github.com/chris-rudmin), and [@stkevintan](https://github.com/stkevintan) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 22689+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 448+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 197+ [themes](http://themes.gohugo.io/)
-
-## Notes
-* `Resources.GetByPrefix` and `Resources.ByPrefix` are deprecated. They still work, but will eventually be removed. Use `Resources.Match` (many) and `Resources.GetMatch` (one).
-* When filtering bundles pages in sub-folders, you need to include the sub-folder when matching. This was a bug introduced in `0.33` and gets it in line with images and other resources.
-
-## Enhancements
-
-* Add `Resources.Match` and `Resources.GetMatch` [94213801](https://github.com/gohugoio/hugo/commit/9421380168f66620cb73203e1267814b3086d805) [@bep](https://github.com/bep) [#4301](https://github.com/gohugoio/hugo/issues/4301)
-
-## Fixes
-* Add validation for `defaultContentLanguage` [4d5e4f37](https://github.com/gohugoio/hugo/commit/4d5e4f379a890a3c6cbc11ddb40d77a90f14c015) [@bep](https://github.com/bep) [#4298](https://github.com/gohugoio/hugo/issues/4298)
-* Fix lookup of pages bundled in sub-folders in `ByPrefix` etc. [5d030869](https://github.com/gohugoio/hugo/commit/5d03086981b4a7d4bc450269a6a2e0fd22dbeed7) [@bep](https://github.com/bep) [#4295](https://github.com/gohugoio/hugo/issues/4295)
diff --git a/docs/content/en/news/0.35-relnotes/featured-hugo-35-poster.png b/docs/content/en/news/0.35-relnotes/featured-hugo-35-poster.png
deleted file mode 100644
index a97e3b90182..00000000000
Binary files a/docs/content/en/news/0.35-relnotes/featured-hugo-35-poster.png and /dev/null differ
diff --git a/docs/content/en/news/0.35-relnotes/index.md b/docs/content/en/news/0.35-relnotes/index.md
deleted file mode 100644
index 104cbd222f1..00000000000
--- a/docs/content/en/news/0.35-relnotes/index.md
+++ /dev/null
@@ -1,102 +0,0 @@
-
----
-date: 2018-01-31
-title: "Hugo 0.35: Headless Bundles!"
-description: "Headless Bundles, disable languages, improves fast render mode, and much more."
-categories: ["Releases"]
----
-
-The most notable new feature in Hugo `0.35` is perhaps **Headless Bundles**.
-
-This means that you in your `index.md` front matter can say:
-
-```yaml
-headless: true
-```
-And
-
-* it will have no `Permalink` and no rendered HTML in `/public`
-* it will not be part of `.Site.RegularPages` etc.
-
-But you can get it by:
-
-* `.Site.GetPage ...`
-
-The use cases are many:
-
-* Shared media libraries
-* Reusable page content "snippets"
-* ...
-
-But this release contains more goodies than possible to sum up in one paragraph, so study the release notes carefully. It represents **42 contributions by 8 contributors** to the main Hugo code base.
-
-[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@vassudanagunta](https://github.com/vassudanagunta), [@yanzay](https://github.com/yanzay), and [@robertbasic](https://github.com/robertbasic) for their ongoing contributions.
-
-And as always a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the documentation and the themes site in pristine condition.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **28 contributions by 5 contributors**. A special thanks to [@bep](https://github.com/bep), [@kaushalmodi](https://github.com/kaushalmodi), [@regisphilibert](https://github.com/regisphilibert), and [@salim-b](https://github.com/salim-b) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 22967+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 448+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 197+ [themes](http://themes.gohugo.io/)
-
-
-## Notes
-
-* Deprecate `useModTimeAsFallback` [adfd4370](https://github.com/gohugoio/hugo/commit/adfd4370b67fd7181178bd6b3b1d07356beaac71) [@bep](https://github.com/bep) [#4351](https://github.com/gohugoio/hugo/issues/4351)
-* Deprecate CLI flags `canonifyURLs`, `pluralizeListTitles`, `preserveTaxonomyNames`, `uglyURLs` [f08ea02d](https://github.com/gohugoio/hugo/commit/f08ea02d24d42929676756950f3affaca7fd8c01) [@bep](https://github.com/bep) [#4347](https://github.com/gohugoio/hugo/issues/4347)
-* Remove undraft command [2fa70c93](https://github.com/gohugoio/hugo/commit/2fa70c9344b231c9d999bbafdfa4acbf27ed9f6e) [@robertbasic](https://github.com/robertbasic) [#4353](https://github.com/gohugoio/hugo/issues/4353)
-
-## Enhancements
-
-### Templates
-
-* Update Twitter card to also consider images in `.Resources` [25d691da](https://github.com/gohugoio/hugo/commit/25d691daff57d7c6d7d0f63af3991d22e3f788fe) [@bep](https://github.com/bep) [#4349](https://github.com/gohugoio/hugo/issues/4349)
-* Seed random on init only [83c761b7](https://github.com/gohugoio/hugo/commit/83c761b71a980aee6331179b271c7e24e999e8eb) [@liguoqinjim](https://github.com/liguoqinjim)
-* Remove duplicate layout lookup layouts [b2fcbb1f](https://github.com/gohugoio/hugo/commit/b2fcbb1f9774aa1e929b8575c0e1ac366ab2fb73) [@bep](https://github.com/bep) [#4319](https://github.com/gohugoio/hugo/issues/4319)
-* Add "removable-media" interface to `snapcraft.yaml` [f0c0ece4](https://github.com/gohugoio/hugo/commit/f0c0ece44d55b6c2997cbd106d1bc099ea1a2fa7) [@anthonyfok](https://github.com/anthonyfok) [#3837](https://github.com/gohugoio/hugo/issues/3837)
-
-### Other
-
-* Add a way to disable one or more languages [6413559f](https://github.com/gohugoio/hugo/commit/6413559f7575e2653d76227a8037a7edbaae82aa) [@bep](https://github.com/bep) [#4297](https://github.com/gohugoio/hugo/issues/4297)[#4329](https://github.com/gohugoio/hugo/issues/4329)
-* Handle newly created files in Fast Render Mode [1707dae8](https://github.com/gohugoio/hugo/commit/1707dae8d3634006017eb6d040df4dbafc53d92f) [@yanzay](https://github.com/yanzay) [#4339](https://github.com/gohugoio/hugo/issues/4339)
-* Extract the Fast Render Mode logic into a method [94e736c5](https://github.com/gohugoio/hugo/commit/94e736c5e167a0ee70a528e1c19d64a47e7929c2) [@bep](https://github.com/bep) [#4339](https://github.com/gohugoio/hugo/issues/4339)
-* Remove unused code [ae5a45be](https://github.com/gohugoio/hugo/commit/ae5a45be6f0ee4d5c52b38fd28b22b55d9cd7b2d) [@bep](https://github.com/bep)
-* Add the last lookup variant for the `GetPage` index [3446fe9b](https://github.com/gohugoio/hugo/commit/3446fe9b8937610b8b628b2c212eb25888a7c1bb) [@bep](https://github.com/bep) [#4312](https://github.com/gohugoio/hugo/issues/4312)
-* Simplify bundle lookup via `.Site.GetPage`, `ref`, `relref` [517b6b62](https://github.com/gohugoio/hugo/commit/517b6b62389d23bfe41fe3ae551a691b11bdcaa7) [@bep](https://github.com/bep) [#4312](https://github.com/gohugoio/hugo/issues/4312)
-* Remove some now superflous Fast Render Mode code [feeed073](https://github.com/gohugoio/hugo/commit/feeed073c3320b09fb38168ce272ac88b987f1d2) [@bep](https://github.com/bep) [#4339](https://github.com/gohugoio/hugo/issues/4339)
-* Make resource counters for `name` and `title` independent [df20b054](https://github.com/gohugoio/hugo/commit/df20b05463fef42aba93d5208e410a7ecc56da5d) [@bep](https://github.com/bep) [#4335](https://github.com/gohugoio/hugo/issues/4335)
-* Provide .Name to the archetype templates [863a812e](https://github.com/gohugoio/hugo/commit/863a812e07193541b42732b0e227f3d320433f01) [@bep](https://github.com/bep) [#4348](https://github.com/gohugoio/hugo/issues/4348)
-* Only set `url` if permalink in metadata and remove duplicate confirm msg [3752348e](https://github.com/gohugoio/hugo/commit/3752348ef13ced8f6f528b42ee7d76a12a97ae5c) [@lildude](https://github.com/lildude) [#1887](https://github.com/gohugoio/hugo/issues/1887)
-* Start Resources :counter first time they're used [7b472e46](https://github.com/gohugoio/hugo/commit/7b472e46084b603045b87cea870ffc73ac1cf7e7) [@bep](https://github.com/bep) [#4335](https://github.com/gohugoio/hugo/issues/4335)
-* Update to Go 1.9.3 [a91aba1c](https://github.com/gohugoio/hugo/commit/a91aba1c1562259dffd321a608f38c38dd4d5aeb) [@bep](https://github.com/bep) [#4328](https://github.com/gohugoio/hugo/issues/4328)
-* Support pages without front matter [91bb774a](https://github.com/gohugoio/hugo/commit/91bb774ae4e129f7ed0624754b31479c960ef774) [@vassudanagunta](https://github.com/vassudanagunta) [#4320](https://github.com/gohugoio/hugo/issues/4320)
-* Add page metadata dates tests [3f0379ad](https://github.com/gohugoio/hugo/commit/3f0379adb72389954ca2be6a9f2ebfcd65c6c440) [@vassudanagunta](https://github.com/vassudanagunta)
-* Re-generate CLI docs [1e27d058](https://github.com/gohugoio/hugo/commit/1e27d0589118a114e49c032e4bd68b4798e44a5b) [@bep](https://github.com/bep)
-* Remove and update deprecation status [d418c2c2](https://github.com/gohugoio/hugo/commit/d418c2c2eacdc1dc6fffe839e0a90600867878ca) [@bep](https://github.com/bep)
-* Shorten the stale setup [4a7c2b36](https://github.com/gohugoio/hugo/commit/4a7c2b3695fe7b88861f2155ea7ef635fe425cd4) [@bep](https://github.com/bep)
-* Add a `GetPage` to the site benchmarks [a1956391](https://github.com/gohugoio/hugo/commit/a19563910eec5fed08f3b02563b9a7b38026183d) [@bep](https://github.com/bep)
-* Add headless bundle support [0432c64d](https://github.com/gohugoio/hugo/commit/0432c64dd22e4610302162678bb93661ba68d758) [@bep](https://github.com/bep) [#4311](https://github.com/gohugoio/hugo/issues/4311)
-* Merge matching resources params maps [5a0819b9](https://github.com/gohugoio/hugo/commit/5a0819b9b5eb9e79826cfa0a65f235d9821b1ac4) [@bep](https://github.com/bep) [#4315](https://github.com/gohugoio/hugo/issues/4315)
-* Add some general code contribution criteria [78c86330](https://github.com/gohugoio/hugo/commit/78c863305f337ed4faf3cf0a23675f28b0ae5641) [@bep](https://github.com/bep)
-* Tighten page kind logic, introduce tests [8125b4b0](https://github.com/gohugoio/hugo/commit/8125b4b03d10eb73f8aea3f9ea41172aba8df082) [@vassudanagunta](https://github.com/vassudanagunta)
-
-## Fixes
-* Fix `robots.txt` in multihost mode [4d912e2a](https://github.com/gohugoio/hugo/commit/4d912e2aad39bfe8d76672cf53b01317792e02c5) [@bep](https://github.com/bep) [#4193](https://github.com/gohugoio/hugo/issues/4193)
-* Fix `--uglyURLs` from comand line regression [016398ff](https://github.com/gohugoio/hugo/commit/016398ffe2e0a073453cf46a9d6bf72d693c11e5) [@bep](https://github.com/bep) [#4343](https://github.com/gohugoio/hugo/issues/4343)
-* Avoid unescape in `highlight` [ebdd8cba](https://github.com/gohugoio/hugo/commit/ebdd8cba3f5965a8ac897833f313d772271de649) [@bep](https://github.com/bep) [#4219](https://github.com/gohugoio/hugo/issues/4219)
-* Fix Docker build [a34213f0](https://github.com/gohugoio/hugo/commit/a34213f0b5624de101272aab469ca9b6fe0c273f) [@skoblenick](https://github.com/skoblenick) [#4076](https://github.com/gohugoio/hugo/issues/4076)[#4077](https://github.com/gohugoio/hugo/issues/4077)
-* Fix language params handling [ae742cb1](https://github.com/gohugoio/hugo/commit/ae742cb1bdf35b81aa0ede5453da6b0c4a4fccf2) [@bep](https://github.com/bep) [#4356](https://github.com/gohugoio/hugo/issues/4356)[#4352](https://github.com/gohugoio/hugo/issues/4352)
-* Fix handling of top-level page bundles [4eb2fec6](https://github.com/gohugoio/hugo/commit/4eb2fec67c3a72a3ac98aa834dc56fd4504626d8) [@bep](https://github.com/bep) [#4332](https://github.com/gohugoio/hugo/issues/4332)
-* Fix `baseURL` server regression for multilingual sites [ed4a00e4](https://github.com/gohugoio/hugo/commit/ed4a00e46f2344320a22f07febe5aec4075cb3fb) [@bep](https://github.com/bep) [#4333](https://github.com/gohugoio/hugo/issues/4333)
-* Fix "date" page param [322c5672](https://github.com/gohugoio/hugo/commit/322c567220aa4123a5d707629c1bebd375599912) [@vassudanagunta](https://github.com/vassudanagunta) [#4323](https://github.com/gohugoio/hugo/issues/4323)
-* Fix typo in comment [912147ab](https://github.com/gohugoio/hugo/commit/912147ab896e69a450b7100c3d6bf81a7bf78b5a) [@yanzay](https://github.com/yanzay)
-
-
-
-
-
diff --git a/docs/content/en/news/0.36-relnotes/featured-hugo-36-poster.png b/docs/content/en/news/0.36-relnotes/featured-hugo-36-poster.png
deleted file mode 100644
index 12dec42fcdb..00000000000
Binary files a/docs/content/en/news/0.36-relnotes/featured-hugo-36-poster.png and /dev/null differ
diff --git a/docs/content/en/news/0.36-relnotes/index.md b/docs/content/en/news/0.36-relnotes/index.md
deleted file mode 100644
index a818924581b..00000000000
--- a/docs/content/en/news/0.36-relnotes/index.md
+++ /dev/null
@@ -1,51 +0,0 @@
-
----
-date: 2018-02-05
-title: "Hugo 0.36: Smart Image Cropping!"
-description: "Hugo 0.36 announces smart image cropping and some important bug fixes."
-categories: ["Releases"]
----
-
-Hugo `0.36` announces **smart cropping** of images, using the [library](https://github.com/muesli/smartcrop) created by [muesli](https://github.com/muesli). We will work with him to improve this even more in the future, but this is now the default used when cropping images in Hugo.
-
-Go [here](http://hugotest.bep.is/resourcemeta/smartcrop/) for a list of examples.
-
-This release represents **7 contributions by 3 contributors** to the main Hugo code base.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **9 contributions by 4 contributors**. A special thanks to [@bep](https://github.com/bep), [@Jibec](https://github.com/Jibec), [@Nick-Rivera](https://github.com/Nick-Rivera), and [@kaushalmodi](https://github.com/kaushalmodi) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 23100+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 448+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 197+ [themes](http://themes.gohugo.io/)
-
-## Notes
-Hugo now defaults to **smart crop** when cropping images, if you don't specify it when calling `.Fill`.
-
-You can get the old default by adding this:
-
-{{< code-toggle file="config" >}}
-[imaging]
-anchor = "center"
-{{< /code-toggle >}}
-
-Also, we have removed the superflous anchor name from the processed filenames that does not use this anchor, so it can be wise to run `hugo --gc` once to remove unused images.
-
-## Enhancements
-* Add smart cropping [722086b4](https://github.com/gohugoio/hugo/commit/722086b4ed3e77d1aba6724474bec06d08e7de06) [@bep](https://github.com/bep) [#4375](https://github.com/gohugoio/hugo/issues/4375)
-
-## Fixes
-* Ensure site templates can override theme templates [084cf419](https://github.com/gohugoio/hugo/commit/084cf4191b3c1e7590a4223fd9251019ef5d4c21) [@moorereason](https://github.com/moorereason) [#3505](https://github.com/gohugoio/hugo/issues/3505)
-* Add additional test to `TestTemplateLookupOrder` [fc06d5c1](https://github.com/gohugoio/hugo/commit/fc06d5c18bb1e47f90f0297aa8121ee0775e047d) [@moorereason](https://github.com/moorereason) [#3505](https://github.com/gohugoio/hugo/issues/3505)
-* Fix broken `TestTemplateLookupOrder` [9a367d9d](https://github.com/gohugoio/hugo/commit/9a367d9d06db6f6cf22121d0397c464ae36e7089) [@moorereason](https://github.com/moorereason)
-* Fix JSON array-based data file handling regression [4402c077](https://github.com/gohugoio/hugo/commit/4402c077754991df19c3bbab0c4a671dcfdc192c) [@vassudanagunta](https://github.com/vassudanagunta) [#4361](https://github.com/gohugoio/hugo/issues/4361)
-* Increase data directory test coverage [4743de0d](https://github.com/gohugoio/hugo/commit/4743de0d3c7564fc06972074e903d5502d204353) [@vassudanagunta](https://github.com/vassudanagunta) [#4138](https://github.com/gohugoio/hugo/issues/4138)
-
-
-
-
-
-
diff --git a/docs/content/en/news/0.36.1-relnotes/index.md b/docs/content/en/news/0.36.1-relnotes/index.md
deleted file mode 100644
index 00a5b346c0b..00000000000
--- a/docs/content/en/news/0.36.1-relnotes/index.md
+++ /dev/null
@@ -1,43 +0,0 @@
-
----
-date: 2018-02-15
-title: "Hugo 0.36.1: One Bugfix"
-description: "Fixes a multi-thread image processing issue."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
- This release fixes a multi-thread issue when reprocessing and reusing images across pages. When doing something like this with the same image from a partial used in, say, both the home page and the single page:
-
-```bash
-{{ with $img }}
-{{ $big := .Fill "1024x512 top" }}
-{{ $small := $big.Resize "512x" }}
-{{ end }}
-```
-
-There would be timing issues making Hugo in some cases trying to process the same image twice at the same time.
-
-You would experience errors of type:
-
-```bash
-png: invalid format: not enough pixel data
-```
-
-This commit fixes that by adding a mutex per image. This should also improve the performance, slightly, as it avoids duplicate work.
-
-The current workaround before this fix is to always operate on the original:
-
-```bash
-{{ with $img }}
-{{ $big := .Fill "1024x512 top" }}
-{{ $small := .Fill "512x256 top" }}
-{{ end }}
-```
-This error was rare (no reports on GitHub or the discussion forum), but very hard to debug for the end user.
-
-* Fix multi-threaded image processing issue [d8fdffb5](https://github.com/gohugoio/hugo/commit/d8fdffb55268464d54558d6f9cd3874b612dc7c7) [@bep](https://github.com/bep) [#4404](https://github.com/gohugoio/hugo/issues/4404)
-* Improve error message in .Render [08521dac](https://github.com/gohugoio/hugo/commit/08521dac8323403933a8fd11acfd16930af5f17d) [@bep](https://github.com/bep)
-* Bump Travis/Snapcraft to Go 1.9.4 [fc23a80f](https://github.com/gohugoio/hugo/commit/fc23a80ffd3878b9ba9a160ce37e0e1d8703faf3) [@bep](https://github.com/bep)
-* Improve error processing error message [2851af02](https://github.com/gohugoio/hugo/commit/2851af0225cdf6c4e47058979cd22949ed6d1fc0) [@bep](https://github.com/bep)
diff --git a/docs/content/en/news/0.37-relnotes/featured-hugo-37-poster.png b/docs/content/en/news/0.37-relnotes/featured-hugo-37-poster.png
deleted file mode 100644
index 9f369ba2558..00000000000
Binary files a/docs/content/en/news/0.37-relnotes/featured-hugo-37-poster.png and /dev/null differ
diff --git a/docs/content/en/news/0.37-relnotes/index.md b/docs/content/en/news/0.37-relnotes/index.md
deleted file mode 100644
index a9b6b4cef74..00000000000
--- a/docs/content/en/news/0.37-relnotes/index.md
+++ /dev/null
@@ -1,76 +0,0 @@
-
----
-date: 2018-02-27
-title: "Hugo 0.37: Preserve PNG Colour Palette"
-description: "Reduces processed PNG file sizes. And 0.37 is built with Go 1.10!"
-categories: ["Releases"]
----
-
-The main item in Hugo `0.37` is that we now properly preserve the colour palette when processing `PNG` images. We got reports from users experiencing their `PNG` images getting bigger in file size when scaled down. Now, if you, as an example, start out with a carefully chosen 8 bit colour palette (i.e. `PNG-8`), this is now what you will end up with. A special thanks to [@aitva](https://github.com/aitva) for doing the investigative work finding a proper fix for this issue.
-
-This release represents **40 contributions by 5 contributors** to the main Hugo code base.
-
-[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@vassudanagunta](https://github.com/vassudanagunta), [@kaushalmodi](https://github.com/kaushalmodi), and [@curttimson](https://github.com/curttimson) for their ongoing contributions.
-
-And as always a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the documentation and the themes site in pristine condition.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **24 contributions by 8 contributors**. A special thanks to [@bep](https://github.com/bep), [@4RU](https://github.com/4RU), [@kaushalmodi](https://github.com/kaushalmodi), and [@mitchchn](https://github.com/mitchchn) for their work on the documentation site.
-
-Hugo now has:
-
-* 23649+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 447+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 197+ [themes](http://themes.gohugo.io/)
-
-## Notes
-
-* Hugo will now convert non-string `YAML` map keys to string. See [#4393](https://github.com/gohugoio/hugo/issues/4393) for more information. You will get a `WARNING` in the console if you are touched by this.
-* We have improved the `PNG` processing, and have incremented the version numbers on the URL for the processed `PNG` image. This will just work, but you may want to run `hugo --gc` to clean up some old stale images in the resource cache.
-
-## Enhancements
-
-### Templates
-
-* Add template func for TOML/JSON/YAML docs examples conversion. This is mainly motivated by the needs of the Hugo docs site. [d382502d](https://github.com/gohugoio/hugo/commit/d382502d6dfa1c066545e215ba83e2e0a9d2c8d7) [@bep](https://github.com/bep) [#4389](https://github.com/gohugoio/hugo/issues/4389)
-
-### Core
-
-* Refactor tests for JSON, YAML and TOML equivalency, add coverage [82eefded](https://github.com/gohugoio/hugo/commit/82eefded1353f0198fd8fe9f7df1aa620d3d50eb) [@vassudanagunta](https://github.com/vassudanagunta)
-* Re-enable YAML data tests disabled in f554503f [e1728349](https://github.com/gohugoio/hugo/commit/e1728349930e2cc1b6580766473de98adb0f3e50) [@vassudanagunta](https://github.com/vassudanagunta)
-
-### Other
-
-* Preserve color palette for PNG images [799c654b](https://github.com/gohugoio/hugo/commit/799c654b0d39ec869c2da24d41de3636eb7157f0) [@bep](https://github.com/bep) [#4416](https://github.com/gohugoio/hugo/issues/4416)
-* Use `Floyd-Steinberg` dithering for PNGs [13ea1e7c](https://github.com/gohugoio/hugo/commit/13ea1e7c352852966f88ef119d9434bbb1ee62fa) [@bep](https://github.com/bep) [#4453](https://github.com/gohugoio/hugo/issues/4453)
-* Make `ge`, `le` etc. work with the Hugo Version number [0602135f](https://github.com/gohugoio/hugo/commit/0602135fd44b0cfa0a51b0ec6e451ae58ac95666) [@bep](https://github.com/bep) [#4443](https://github.com/gohugoio/hugo/issues/4443)
-* Update dependencies [eaf573a2](https://github.com/gohugoio/hugo/commit/eaf573a2778e79287b871b69f4959fd3082d8887) [@bep](https://github.com/bep) [#4418](https://github.com/gohugoio/hugo/issues/4418)
-* Update to Go 1.10 (!) Take 2 [a3f26e56](https://github.com/gohugoio/hugo/commit/a3f26e56368c62b0900a10d83a11b7783630963b) [@bep](https://github.com/bep)
-* Update to Go 1.10 (!) [ff10c15a](https://github.com/gohugoio/hugo/commit/ff10c15a93632043f7a7f6551a30487c9ef58c50) [@bep](https://github.com/bep)
-* Add WARNING for integer YAML keys [0816a97a](https://github.com/gohugoio/hugo/commit/0816a97a469f11d8e9706143975eaa532e29639b) [@bep](https://github.com/bep) [#4393](https://github.com/gohugoio/hugo/issues/4393)
-* Tune stringifyMapKeys [10a917df](https://github.com/gohugoio/hugo/commit/10a917dfdce8851666c5b89ebc02af6f6c84ab59) [@bep](https://github.com/bep)
-* Rename stringifyYAMLMapKeys to stringifyMapKeys [d4beef0d](https://github.com/gohugoio/hugo/commit/d4beef0d2bb8f6481fa80e1d938454a7d4e38814) [@bep](https://github.com/bep)
-* Add benchmarks for stringifyYAMLMapKeys [51213e0b](https://github.com/gohugoio/hugo/commit/51213e0be19fc19dbca9815afa95c73bd6d159c2) [@bep](https://github.com/bep)
-* Add support for `YAML` array data files [1fa24177](https://github.com/gohugoio/hugo/commit/1fa2417777d82b81bf37919ad02de4f5dcbf0d50) [@vassudanagunta](https://github.com/vassudanagunta) [#3890](https://github.com/gohugoio/hugo/issues/3890)
-* Account for array type data in data dir merge/override logic [bb549a0d](https://github.com/gohugoio/hugo/commit/bb549a0d57505a6b8f28930bb91a9ab44cbb3288) [@vassudanagunta](https://github.com/vassudanagunta) [#4366](https://github.com/gohugoio/hugo/issues/4366)
-* Add "target" and "rel" parameters to figure shortcode [2e95ec68](https://github.com/gohugoio/hugo/commit/2e95ec6844bf65a25485bdc8e2638e45788f2dcf) [@kaushalmodi](https://github.com/kaushalmodi)
-* image" property, not "twitter:image:src" [76d38d5e](https://github.com/gohugoio/hugo/commit/76d38d5e5322fc6220fb9e74f9ca0668606ebb5d) [@kaushalmodi](https://github.com/kaushalmodi)
-
-## Fixes
-
-### Core
-
-* Continue `GitInfo` lookup on error [e9750d83](https://github.com/gohugoio/hugo/commit/e9750d831f749afa928d8a099af5889d18cb2484) [@bep](https://github.com/bep)
-* Fix paginator `URL` for sections with URL in front matter [9f740b37](https://github.com/gohugoio/hugo/commit/9f740b37cfb3278e34a5d085380ccd4d619dabff) [@bep](https://github.com/bep) [#4415](https://github.com/gohugoio/hugo/issues/4415)
-
-### Other
-
-* Fix bug in Site.assembleSections method [00868081](https://github.com/gohugoio/hugo/commit/00868081f624928d773a7b698654766f8cd70069) [@vassudanagunta](https://github.com/vassudanagunta) [#4447](https://github.com/gohugoio/hugo/issues/4447)
-* Update Blackfriday to fix footnote HTML5 validation error [492fea7c](https://github.com/gohugoio/hugo/commit/492fea7cd2bfcbdfe9f56aa0ae659cf62648833b) [@bep](https://github.com/bep) [#4433](https://github.com/gohugoio/hugo/issues/4433)
-* Fix `YAML` maps key type [16a5c745](https://github.com/gohugoio/hugo/commit/16a5c74519771138023f019fe535fa5b250dc50d) [@dmgawel](https://github.com/dmgawel) [#2441](https://github.com/gohugoio/hugo/issues/2441)
-* Remove `ERROR` on missing baseURL [55bd46a6](https://github.com/gohugoio/hugo/commit/55bd46a633d68f62e131457631ba839d6f876a55) [@bep](https://github.com/bep) [#4397](https://github.com/gohugoio/hugo/issues/4397)
-
-
-
-
-
diff --git a/docs/content/en/news/0.37.1-relnotes/index.md b/docs/content/en/news/0.37.1-relnotes/index.md
deleted file mode 100644
index 754ed4240c1..00000000000
--- a/docs/content/en/news/0.37.1-relnotes/index.md
+++ /dev/null
@@ -1,20 +0,0 @@
-
----
-date: 2018-03-07
-title: "Hugo 0.37.1: One Bugfix"
-description: "Fixes SVG (and similar) handling as page resources."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-This is a bug-fix release with a one important fix:
-
-Image content such as `SVG` cannot be scaled with the built-in image processing methods, but it should still be possible to use them as page resources. This was a regression in Hugo `0.37` and is now fixed. [ba94abbf](https://github.com/gohugoio/hugo/commit/ba94abbf5dd90f989242af8a7027d67a572a6128) [@bep](https://github.com/bep) [#4455](https://github.com/gohugoio/hugo/issues/4455)
-
-
-
-
-
-
diff --git a/docs/content/en/news/0.38-relnotes/featured-poster.png b/docs/content/en/news/0.38-relnotes/featured-poster.png
deleted file mode 100644
index 1e7988c8f6e..00000000000
Binary files a/docs/content/en/news/0.38-relnotes/featured-poster.png and /dev/null differ
diff --git a/docs/content/en/news/0.38-relnotes/index.md b/docs/content/en/news/0.38-relnotes/index.md
deleted file mode 100644
index 71d167cd5c4..00000000000
--- a/docs/content/en/news/0.38-relnotes/index.md
+++ /dev/null
@@ -1,66 +0,0 @@
-
----
-date: 2018-04-02
-title: "Hugo 0.38: The Easter Egg Edition"
-description: "Hugo 0.38: Date and slug from filenames, multiple content dirs, config from themes, language merge func …"
-categories: ["Releases"]
----
-
-Hugo `0.38` is an **Easter egg** filled with good stuff. We now support fetching **date and slug from the content filename**, making the move from Jekyll even easier. And you can now set `contentDir` per language with intelligent merging, and themes can now provide configuration ... Also worth mentioning is several improvements in the [Chroma](https://github.com/alecthomas/chroma) highlighter, most notable support for Go templates.
-
-We are working hard to get the documentation up-to-date with the new features, but you can also see them in action with the full source at [hugotest.bep.is](http://hugotest.bep.is/).
-
-This release represents **39 contributions by 4 contributors** to the main Hugo code base.
-[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@anthonyfok](https://github.com/anthonyfok), [@felicianotech](https://github.com/felicianotech), and [@paulcmal](https://github.com/paulcmal) for their ongoing contributions.
-
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the themes site in pristine condition and to [@kaushalmodi](https://github.com/kaushalmodi) for his great work on the documentation site.
-
-Also, a shoutout to [@regisphilibert](https://github.com/regisphilibert) for his work on the new [Code Toggle Shortcode](https://gohugo.io/getting-started/code-toggle/) on the Hugo docs site, which we will put to good use to improve all the configuration samples.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **55 contributions by 18 contributors**. A special thanks to [@kaushalmodi](https://github.com/kaushalmodi), [@bep](https://github.com/bep), [@xa0082249956](https://github.com/xa0082249956), and [@paulcmal](https://github.com/paulcmal) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 24547+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 447+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 213+ [themes](http://themes.gohugo.io/)
-
-## Notes
-
-* Hugo now allows partial redefinition `outputs` in your site configuration. This is what most people would expect, but it is still a change in behaviour. For details, see [#4487](https://github.com/gohugoio/hugo/issues/4487)
-* Before this release, Hugo flattened URLs of processed images in sub-folders. This worked fine but was not intentional. See [#4502](https://github.com/gohugoio/hugo/issues/4502).
-
-## Enhancements
-
-* Allow themes to define output formats, media types and params [e9c7b620](https://github.com/gohugoio/hugo/commit/e9c7b6205f94a7edac0e0df2cd18d1456cb26a06) [@bep](https://github.com/bep) [#4490](https://github.com/gohugoio/hugo/issues/4490)
-* Allow partial redefinition of the `ouputs` config [f8dc47ee](https://github.com/gohugoio/hugo/commit/f8dc47eeffa847fd0b51e376da355e3d957848a6) [@bep](https://github.com/bep) [#4487](https://github.com/gohugoio/hugo/issues/4487)
-* Add a way to merge pages by language [ffaec4ca](https://github.com/gohugoio/hugo/commit/ffaec4ca8c4c6fd05b195879ccd65acf2fd5a6ac) [@bep](https://github.com/bep) [#4463](https://github.com/gohugoio/hugo/issues/4463)
-* Extract `date` and `slug` from filename [68bf1511](https://github.com/gohugoio/hugo/commit/68bf1511f2be39b6576d882d071196e477c72c9f) [@bep](https://github.com/bep) [#285](https://github.com/gohugoio/hugo/issues/285)[#3310](https://github.com/gohugoio/hugo/issues/3310)[#3762](https://github.com/gohugoio/hugo/issues/3762)[#4340](https://github.com/gohugoio/hugo/issues/4340)
-* Add `Delete` method to delete key from `Scratch` [e46ab29b](https://github.com/gohugoio/hugo/commit/e46ab29bd24caa9e2cfa51f24ba15037750850d6) [@paulcmal](https://github.com/paulcmal)
-* Simplify Prev/Next [79dd7cb3](https://github.com/gohugoio/hugo/commit/79dd7cb31a941d7545df33b938ca3ed46593ddfd) [@bep](https://github.com/bep)
-* List Chroma lexers [2c54f1ad](https://github.com/gohugoio/hugo/commit/2c54f1ad48fe2a2f7504117d351d45abc89dcb1f) [@bep](https://github.com/bep) [#4554](https://github.com/gohugoio/hugo/issues/4554)
-* Add support for a `contentDir` set per language [eb42774e](https://github.com/gohugoio/hugo/commit/eb42774e587816b1fbcafbcea59ed65df703882a) [@bep](https://github.com/bep) [#4523](https://github.com/gohugoio/hugo/issues/4523)[#4552](https://github.com/gohugoio/hugo/issues/4552)[#4553](https://github.com/gohugoio/hugo/issues/4553)
-* Update Chroma [7a634898](https://github.com/gohugoio/hugo/commit/7a634898c359a6af0da52be17df07cae97c7937c) [@bep](https://github.com/bep) [#4549](https://github.com/gohugoio/hugo/issues/4549)
-* Add `.Site.IsServer` [1823c053](https://github.com/gohugoio/hugo/commit/1823c053c8900cb6ee53b8e5c02939c7398e34dd) [@felicianotech](https://github.com/felicianotech) [#4478](https://github.com/gohugoio/hugo/issues/4478)
-* Move to Ubuntu Trusty image [511d5d3b](https://github.com/gohugoio/hugo/commit/511d5d3b7681cb76822098f430ed6862232ca529) [@anthonyfok](https://github.com/anthonyfok)
-* Bump some deprecations [b6798ee8](https://github.com/gohugoio/hugo/commit/b6798ee8676c48f86b0bd8581ea244f4be4ef3fa) [@bep](https://github.com/bep)
-* Update Chroma to get `Go template support` [904a3d9d](https://github.com/gohugoio/hugo/commit/904a3d9ddf523d452d04d0b5814503e0ff17bd2e) [@bep](https://github.com/bep) [#4515](https://github.com/gohugoio/hugo/issues/4515)
-* Recover from error in server [f0052b6d](https://github.com/gohugoio/hugo/commit/f0052b6d0f8e113a50aeb6cd7bd34555dbf34a00) [@bep](https://github.com/bep) [#4516](https://github.com/gohugoio/hugo/issues/4516)
-* Spring test cleaning, take 2 [da880157](https://github.com/gohugoio/hugo/commit/da88015776645cc68b96e8b94030c95905df53ae) [@bep](https://github.com/bep)
-* Add docs for `lang.Merge` [70005364](https://github.com/gohugoio/hugo/commit/70005364a245ea3bc59c74192e1f4c56cb6879cf) [@bep](https://github.com/bep)
-* Remove archetype title/date warning [ac12d51e](https://github.com/gohugoio/hugo/commit/ac12d51e7ea3a0ffb7d8053a10b6bf6acf1235ae) [@bep](https://github.com/bep) [#4504](https://github.com/gohugoio/hugo/issues/4504)
-* Add docs on the new front matter configuration [0dbf79c2](https://github.com/gohugoio/hugo/commit/0dbf79c2f8cd5b1a5c91c04a8d677f956b0b8fe8) [@bep](https://github.com/bep) [#4495](https://github.com/gohugoio/hugo/issues/4495)
-* Refactor the GitInfo into the date handlers [ce6e4310](https://github.com/gohugoio/hugo/commit/ce6e4310febf5659392a41b543594382441f3681) [@bep](https://github.com/bep) [#4495](https://github.com/gohugoio/hugo/issues/4495)
-* Do not print build total when `--quiet` is set [50a03a5a](https://github.com/gohugoio/hugo/commit/50a03a5acc7c200c795590c3f4b964fdc56085f2) [@bep](https://github.com/bep) [#4456](https://github.com/gohugoio/hugo/issues/4456)
-
-## Fixes
-
-* Fix freeze in invalid front matter error case [93e24a03](https://github.com/gohugoio/hugo/commit/93e24a03ce98d3212a2d49ad04739141229d0809) [@bep](https://github.com/bep) [#4526](https://github.com/gohugoio/hugo/issues/4526)
-* Fix path duplication/flattening in processed images [3fbc7553](https://github.com/gohugoio/hugo/commit/3fbc75534d1acda2be1c597aa77c919d3a02659d) [@bep](https://github.com/bep) [#4502](https://github.com/gohugoio/hugo/issues/4502)[#4501](https://github.com/gohugoio/hugo/issues/4501)
-* Fix SVG and similar resource handling [ba94abbf](https://github.com/gohugoio/hugo/commit/ba94abbf5dd90f989242af8a7027d67a572a6128) [@bep](https://github.com/bep) [#4455](https://github.com/gohugoio/hugo/issues/4455)
-
-
-
-
diff --git a/docs/content/en/news/0.38.1-relnotes/index.md b/docs/content/en/news/0.38.1-relnotes/index.md
deleted file mode 100644
index a025b5415b7..00000000000
--- a/docs/content/en/news/0.38.1-relnotes/index.md
+++ /dev/null
@@ -1,32 +0,0 @@
-
----
-date: 2018-04-05
-title: "Hugo 0.38.1: Some Live Reload Fixes"
-description: "Hugo 0.38.1 fixes some live reload issues introduced in 0.38."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix that is mainly motivated by some issues with server live reloading introduced in Hugo 0.38.
-
-* Fix livereload for the home page bundle [f87239e4](https://github.com/gohugoio/hugo/commit/f87239e4cab958bf59ecfb1beb8cac439441a553) [@bep](https://github.com/bep) [#4576](https://github.com/gohugoio/hugo/issues/4576)
-* Fix empty BuildDate in "hugo version" [294c0f80](https://github.com/gohugoio/hugo/commit/294c0f8001fe598278c1eb8015deb6b98e8de686) [@anthonyfok](https://github.com/anthonyfok)
-* Fix some livereload content regressions [a4deaeff](https://github.com/gohugoio/hugo/commit/a4deaeff0cfd70abfbefa6d40c0b86839a216f6d) [@bep](https://github.com/bep) [#4566](https://github.com/gohugoio/hugo/issues/4566)
-* Update github.com/bep/gitmap to fix snap build [4d115c56](https://github.com/gohugoio/hugo/commit/4d115c56fac9060230fbac6181a05f7cc6d10b42) [@anthonyfok](https://github.com/anthonyfok) [#4538](https://github.com/gohugoio/hugo/issues/4538)
-* Fix two tests that are broken on Windows [26f34fd5](https://github.com/gohugoio/hugo/commit/26f34fd59da1ce1885d4f2909c5d9ef9c1726944) [@neurocline](https://github.com/neurocline)
-
-
-This release also contains some improvements:
-
-* Add bash completion [874159b5](https://github.com/gohugoio/hugo/commit/874159b5436bc9080aec71a9c26d35f8f62c9fd0) [@anthonyfok](https://github.com/anthonyfok)
-* Handle mass content etc. edits in server mode [730b66b6](https://github.com/gohugoio/hugo/commit/730b66b6520f263af16f555d1d7be51205a8e51d) [@bep](https://github.com/bep) [#4563](https://github.com/gohugoio/hugo/issues/4563)
-
-
-
-
-
-
diff --git a/docs/content/en/news/0.38.2-relnotes/index.md b/docs/content/en/news/0.38.2-relnotes/index.md
deleted file mode 100644
index 0a045eee882..00000000000
--- a/docs/content/en/news/0.38.2-relnotes/index.md
+++ /dev/null
@@ -1,26 +0,0 @@
-
----
-date: 2018-04-09
-title: "Hugo 0.38.2: Two Bugfixes"
-description: "0.38.2 fixes `--contentDir` flag handling and \".\" in content filenames."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes:
-
-
-* Fix handling of the `--contentDir` and possibly other related flags [080302eb](https://github.com/gohugoio/hugo/commit/080302eb8757fd94ccbd6bf99103432cd98e716c) [@bep](https://github.com/bep) [#4589](https://github.com/gohugoio/hugo/issues/4589)
-* Fix handling of content files with "." in them [2817e842](https://github.com/gohugoio/hugo/commit/2817e842407c8dcbfc738297ab634392fcb41ce1) [@bep](https://github.com/bep) [#4559](https://github.com/gohugoio/hugo/issues/4559)
-
-
-Also in this release:
-
-* Set .Parent in bundled pages to its owner [6792d86a](https://github.com/gohugoio/hugo/commit/6792d86ad028571c684a776c5f00e0107838c955) [@bep](https://github.com/bep) [#4582](https://github.com/gohugoio/hugo/issues/4582)
-
-
-
diff --git a/docs/content/en/news/0.39-relnotes/featured-hugo-39-poster.png b/docs/content/en/news/0.39-relnotes/featured-hugo-39-poster.png
deleted file mode 100644
index e3fa6400a8c..00000000000
Binary files a/docs/content/en/news/0.39-relnotes/featured-hugo-39-poster.png and /dev/null differ
diff --git a/docs/content/en/news/0.39-relnotes/index.md b/docs/content/en/news/0.39-relnotes/index.md
deleted file mode 100644
index d1c28252ae1..00000000000
--- a/docs/content/en/news/0.39-relnotes/index.md
+++ /dev/null
@@ -1,87 +0,0 @@
-
----
-date: 2018-04-16
-title: "Hugo 0.39: The Nat King Cole Stabilizer Edition"
-description: "Hugo 0.39: Rewrite of the /commands package, `Resource.Content`, several new template funcs, and more …"
-categories: ["Releases"]
----
-
-
-**Nat King Cole** was a fantastic American jazz pianist. When his bass player had visited the bar a little too often, he started with his percussive piano playing to keep the tempo flowing. Oscar Peterson called it _"Nat's stabilizers"_. This release is the software equivalent of that. We have been doing frequent main releases this year, but looking back, the patch releases that followed them seemed unneeded. And looking at the regressions, most of them stem from the `commands` package, a package that before this release was filled with globals and high coupling. This package is now rewritten and accompanied with decent test coverage.
-
-But this release isn't all boring and technical: It includes several important bug fixes, several useful new template functions, and `Resource.Content` allows you to get any resource's content without having to fiddle with file paths and `readFile`.
-
-This release represents **61 contributions by 4 contributors** to the main Hugo code base. A shout-out to [@bep](https://github.com/bep) for the implementation and [@it-gro](https://github.com/it-gro) and [@RickCogley](https://github.com/RickCogley) for the help testing it.
-
-[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions and his witty Norwegian humour, but also a big shoutout to [@anthonyfok](https://github.com/anthonyfok), [@thedodobird2](https://github.com/thedodobird2), and [@neurocline](https://github.com/neurocline) for their ongoing contributions.
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the themes site in pristine condition and to [@kaushalmodi](https://github.com/kaushalmodi) for his great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **6 contributions by 5 contributors**. A special thanks to [@kaushalmodi](https://github.com/kaushalmodi), [@regisphilibert](https://github.com/regisphilibert), [@bep](https://github.com/bep), and [@tomanistor](https://github.com/tomanistor) for their work on the documentation site.
-
-Hugo now has:
-
-* 24911+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 446+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 218+ [themes](http://themes.gohugo.io/)
-
-## Notes
-
-* The `main.Execute` function now returns a `Response` object and the global `Hugo` variable is removed. This is only relevant for people building some kind of API around Hugo.
-* Remove deprecated `File.Bytes` [94c8b29c](https://github.com/gohugoio/hugo/commit/94c8b29c39d0c485ee91d98c08fd615c28802496) [@bep](https://github.com/bep)
-
-## Enhancements
-
-### Templates
-
-* Add `anchorize` template func [4dba6ce1](https://github.com/gohugoio/hugo/commit/4dba6ce15ae9b5208b1e2d68c96d7b1dce0a07ab) [@bep](https://github.com/bep)
-* Add `path.Join` [880ca19f](https://github.com/gohugoio/hugo/commit/880ca19f209e68e6a8daa6686b361515ecacc91e) [@bep](https://github.com/bep)
-* Add `path.Split` template func [01b72eb5](https://github.com/gohugoio/hugo/commit/01b72eb592d0e0aefc5f7ae42f9f6ff112883bb6) [@bep](https://github.com/bep)
-
-### Other
-
-* Implement `Resource.Content` [0e7716a4](https://github.com/gohugoio/hugo/commit/0e7716a42450401c7998aa81ad2ed98c8ab109e8) [@bep](https://github.com/bep) [#4622](https://github.com/gohugoio/hugo/issues/4622)
-* Make `Page.Content` a method that returns interface{} [417c5e2b](https://github.com/gohugoio/hugo/commit/417c5e2b67b97fa80a0b6f77d259966f03b95344) [@bep](https://github.com/bep) [#4622](https://github.com/gohugoio/hugo/issues/4622)
-* Remove accidental and breaking space in baseURL flag [1b4e0c41](https://github.com/gohugoio/hugo/commit/1b4e0c4161fb631add62e77f494a7e62c3619020) [@bep](https://github.com/bep) [#4607](https://github.com/gohugoio/hugo/issues/4607)
-* Properly handle CLI slice arguments [27a524b0](https://github.com/gohugoio/hugo/commit/27a524b0905ec73c1eef233f94700feb9f465011) [@bep](https://github.com/bep) [#4607](https://github.com/gohugoio/hugo/issues/4607)
-* Correctly handle destination and i18n-warnings [bede93de](https://github.com/gohugoio/hugo/commit/bede93de005dcf934f3ec9be6388310ac6c57acd) [@bep](https://github.com/bep) [#4607](https://github.com/gohugoio/hugo/issues/4607)
-* Allow "*/" inside commented out shortcodes [14c35c8a](https://github.com/gohugoio/hugo/commit/14c35c8a56c4dc9a1ee0053e9ff976be7715ba99) [@bep](https://github.com/bep) [#4608](https://github.com/gohugoio/hugo/issues/4608)
-* Make commands.Execute return a Response object [96689a5c](https://github.com/gohugoio/hugo/commit/96689a5c319f720368491226f034d0ff9585217c) [@bep](https://github.com/bep) [#4598](https://github.com/gohugoio/hugo/issues/4598)
-* Remove some TODOs [e7010c1b](https://github.com/gohugoio/hugo/commit/e7010c1b621d68ee53411a5ba8143d07b976d9fe) [@bep](https://github.com/bep) [#4598](https://github.com/gohugoio/hugo/issues/4598)
-* Add basic server test [a7d00fc3](https://github.com/gohugoio/hugo/commit/a7d00fc39e87a5cac99b3a2380f5cc8c135d2b4b) [@bep](https://github.com/bep) [#4598](https://github.com/gohugoio/hugo/issues/4598)
-* Remove the Hugo global [b110d0ae](https://github.com/gohugoio/hugo/commit/b110d0ae04e13fb45c739bcebb580709745082e6) [@bep](https://github.com/bep) [#4598](https://github.com/gohugoio/hugo/issues/4598)
-* Make the limit command work again [73825cfc](https://github.com/gohugoio/hugo/commit/73825cfc1c0b007830b24bb1947a565175b52d36) [@bep](https://github.com/bep) [#4598](https://github.com/gohugoio/hugo/issues/4598)
-* Move the commands related logic to its own file [a8f7fbbb](https://github.com/gohugoio/hugo/commit/a8f7fbbb10aa78f3ebac008d29d9969bb197393c) [@bep](https://github.com/bep) [#4598](https://github.com/gohugoio/hugo/issues/4598)
-* Add CLI tests [e8d6ca95](https://github.com/gohugoio/hugo/commit/e8d6ca9531d19e4e898c57d77d2fd627ea38ade0) [@bep](https://github.com/bep) [#4598](https://github.com/gohugoio/hugo/issues/4598)
-* Make the hugo command non-global [4d32f2fa](https://github.com/gohugoio/hugo/commit/4d32f2fa8969f368b088dc9bcedb45f2c986cb27) [@bep](https://github.com/bep) [#4598](https://github.com/gohugoio/hugo/issues/4598)
-* Extract some common types into its own file [018602c4](https://github.com/gohugoio/hugo/commit/018602c46db8d729af2871bd5f4c1e7480420f09) [@bep](https://github.com/bep) [#4598](https://github.com/gohugoio/hugo/issues/4598)
-* Make the server command non-global [2f0d98a1](https://github.com/gohugoio/hugo/commit/2f0d98a19b021d03930003217b0519afaef3a391) [@bep](https://github.com/bep) [#4598](https://github.com/gohugoio/hugo/issues/4598)
-* Make the gen commands non-global [e0621d20](https://github.com/gohugoio/hugo/commit/e0621d207ce3278a82f8a60607e9cdd304149029) [@bep](https://github.com/bep) [#4598](https://github.com/gohugoio/hugo/issues/4598)
-* Make the list commands non-global [e26a8b24](https://github.com/gohugoio/hugo/commit/e26a8b242a6434117d089a0799238add7025dbf4) [@bep](https://github.com/bep) [#4598](https://github.com/gohugoio/hugo/issues/4598)
-* Make the import commands non-global [2a2c9838](https://github.com/gohugoio/hugo/commit/2a2c9838671b5401331d20f8c72e2b934fe34e8d) [@bep](https://github.com/bep) [#4598](https://github.com/gohugoio/hugo/issues/4598)
-* Make the config command non-global [15b1e269](https://github.com/gohugoio/hugo/commit/15b1e269ade91ddc6a74c552bc61b0c5e527d268) [@bep](https://github.com/bep) [#4598](https://github.com/gohugoio/hugo/issues/4598)
-* Make the new commands non-global [56a13080](https://github.com/gohugoio/hugo/commit/56a13080446283ed1cde6b69fc6f4fac85076c84) [@bep](https://github.com/bep) [#4598](https://github.com/gohugoio/hugo/issues/4598)
-* Make convert command non-global [4b780ca7](https://github.com/gohugoio/hugo/commit/4b780ca778ee7f25af808da38ede964a01698c70) [@bep](https://github.com/bep) [#4598](https://github.com/gohugoio/hugo/issues/4598)
-* Make more commands non-global [7bc5e89f](https://github.com/gohugoio/hugo/commit/7bc5e89fbaa5c613b8853ff7b69fae570bd0b56d) [@bep](https://github.com/bep) [#4598](https://github.com/gohugoio/hugo/issues/4598)
-* Make benchmark non-global [fdf1d94e](https://github.com/gohugoio/hugo/commit/fdf1d94ebc7d1aa4855c62237f2edbd4bdade1a7) [@bep](https://github.com/bep) [#4598](https://github.com/gohugoio/hugo/issues/4598)
-* Start of flag cleaning [1157fef8](https://github.com/gohugoio/hugo/commit/1157fef85908ea54883fe0dba6adc4861ba02162) [@bep](https://github.com/bep) [#4598](https://github.com/gohugoio/hugo/issues/4598)
-* Use short date format in CLI docs [e614d8a5](https://github.com/gohugoio/hugo/commit/e614d8a57c2ff5eef9270d51fcc6518398d7ff88) [@bep](https://github.com/bep) [#4598](https://github.com/gohugoio/hugo/issues/4598)
-* Update README.md [fca49d6c](https://github.com/gohugoio/hugo/commit/fca49d6c608d227049cb2f26895cfecc685f1c89) [@thedodobird2](https://github.com/thedodobird2)
-* Sync dependencies [0e8b3cbc](https://github.com/gohugoio/hugo/commit/0e8b3cbcd274e1f2e14be694c794a544f49efb56) [@bep](https://github.com/bep)
-* Bump Go versions [230f2b8c](https://github.com/gohugoio/hugo/commit/230f2b8c4fce03f14847de2b22402e64d4d69783) [@bep](https://github.com/bep) [#4545](https://github.com/gohugoio/hugo/issues/4545)
-* Add bash completion [874159b5](https://github.com/gohugoio/hugo/commit/874159b5436bc9080aec71a9c26d35f8f62c9fd0) [@anthonyfok](https://github.com/anthonyfok)
-* Handle mass content etc. edits in server mode [730b66b6](https://github.com/gohugoio/hugo/commit/730b66b6520f263af16f555d1d7be51205a8e51d) [@bep](https://github.com/bep) [#4563](https://github.com/gohugoio/hugo/issues/4563)
-
-## Fixes
-
-* Fix `livereload` of bundled pages [f3775877](https://github.com/gohugoio/hugo/commit/f3775877c61c11ab7c8fd1fc3e15470bf5da4820) [@bep](https://github.com/bep) [#4607](https://github.com/gohugoio/hugo/issues/4607)
-* Do not reset `.Page.Scratch` on rebuilds [61d52f14](https://github.com/gohugoio/hugo/commit/61d52f146297950e283ae086d8b1af61099d22a0) [@bep](https://github.com/bep) [#4627](https://github.com/gohugoio/hugo/issues/4627)
-* Fix failing Travis server test [9c782d51](https://github.com/gohugoio/hugo/commit/9c782d5147bfea0dd85cf3374f598f0176f204eb) [@bep](https://github.com/bep)
-* Fix the config command [f396cffa](https://github.com/gohugoio/hugo/commit/f396cffa239e948075af2224208671956d8b4a84) [@bep](https://github.com/bep) [#4598](https://github.com/gohugoio/hugo/issues/4598)
-* Fix some flag diff [24d5c219](https://github.com/gohugoio/hugo/commit/24d5c219424a9777bb1dd366b43e68e6f47e1adb) [@bep](https://github.com/bep) [#4598](https://github.com/gohugoio/hugo/issues/4598)
-* Fix TestFixURL [1e233b1c](https://github.com/gohugoio/hugo/commit/1e233b1c4598fd8cbce7da8a67bf2c4918c6047e) [@bep](https://github.com/bep) [#4598](https://github.com/gohugoio/hugo/issues/4598)
-* Disable shallow clone to fix TestPageWithLastmodFromGitInfo [094ec171](https://github.com/gohugoio/hugo/commit/094ec171420e659cdf962a19dd90105912ce9901) [@anthonyfok](https://github.com/anthonyfok) [#4584](https://github.com/gohugoio/hugo/issues/4584)
-* Fix livereload for the home page bundle [f87239e4](https://github.com/gohugoio/hugo/commit/f87239e4cab958bf59ecfb1beb8cac439441a553) [@bep](https://github.com/bep) [#4576](https://github.com/gohugoio/hugo/issues/4576)
-* Fix empty `BuildDate` in "hugo version" [294c0f80](https://github.com/gohugoio/hugo/commit/294c0f8001fe598278c1eb8015deb6b98e8de686) [@anthonyfok](https://github.com/anthonyfok)
-* Fix some livereload content regressions [a4deaeff](https://github.com/gohugoio/hugo/commit/a4deaeff0cfd70abfbefa6d40c0b86839a216f6d) [@bep](https://github.com/bep) [#4566](https://github.com/gohugoio/hugo/issues/4566)
-* Fix two tests that are broken on Windows [26f34fd5](https://github.com/gohugoio/hugo/commit/26f34fd59da1ce1885d4f2909c5d9ef9c1726944) [@neurocline](https://github.com/neurocline)
diff --git a/docs/content/en/news/0.40-relnotes/featured-hugo-40-poster.png b/docs/content/en/news/0.40-relnotes/featured-hugo-40-poster.png
deleted file mode 100644
index 9a7f36d1f0a..00000000000
Binary files a/docs/content/en/news/0.40-relnotes/featured-hugo-40-poster.png and /dev/null differ
diff --git a/docs/content/en/news/0.40-relnotes/index.md b/docs/content/en/news/0.40-relnotes/index.md
deleted file mode 100644
index 9a45c1c0941..00000000000
--- a/docs/content/en/news/0.40-relnotes/index.md
+++ /dev/null
@@ -1,76 +0,0 @@
-
----
-date: 2018-04-23
-title: "Hugo 0.40: The Revival of the Shortcodes"
-description: "Hugo 0.40: Shortcodes with `.Content` (almost) always available, processed in order of appearance, several new template funcs …"
-categories: ["Releases"]
----
-
-Hugo `0.40` is **The Revival of the Shortcodes**. Shortcodes is one of the prime features in Hugo. Really useful, but it has had some known shortcomings. [@bep](https://github.com/bep) has been really busy the last week to address those.
-
-In summary:
-
-* `.Content` for a page retrieved in a query in a shortcode is now _almost_ always available. Note that shortcodes can include content that can include shortcodes that can include content... It is possible to bite your tail. See more below.
-* Shortcodes are now processed and rendered in their order of appearance.
-* Related to the above, we have now added a zero-based `.Ordinal` to the shortcode.
-
-
-The first bullet above resolves some surprising behaviour when reading other pages' content from shortcodes. Before this release, that behaviour was undefined. Note that this has never been an issue from regular templates.
-
-It will still not be possible to get **the current shortcode's page's rendered content**. That would have impressed Einstein.
-
-The new and well defined rules are:
-
-* `.Page.Content` from a shortcode will be empty. The related `.Page.Truncated` `.Page.Summary`, `.Page.WordCount`, `.Page.ReadingTime`, `.Page.Plain` and `.Page.PlainWords` will also have empty values.
-* For _other pages_ (retrieved via `.Page.Site.GetPage`, `.Site.Pages` etc.) the `.Content` is there to use as you please as long as you don't have infinite content recursion in your shortcode/content setup. See below.
-* `.Page.TableOfContents` is good to go (but does not support shortcodes in headlines; this is unchanged)
-
-If you get into a situation of infinite recursion, the `.Content` will be empty. Run `hugo -v` for more information.
-
-This release represents **19 contributions by 3 contributors** to the main Hugo code base.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **13 contributions by 6 contributors**. A special thanks to [@kaushalmodi](https://github.com/kaushalmodi), [@anthonyfok](https://github.com/anthonyfok), [@bep](https://github.com/bep), and [@regisphilibert](https://github.com/regisphilibert) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 25071+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 446+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 222+ [themes](http://themes.gohugo.io/)
-
-## Notes
-
-* We have added a `timeout` configuration setting. This is currently only used to time out the `.Content` creation, to bail out of recursive recursions. Run `hugo -v` to see potential warnings about this. The `timeout` is set default to `10000` (10 seconds).
-
-## Enhancements
-
-### Templates
-
-* Add `path.Ext`, `path.Dir` and `path.Base` [47e7788b](https://github.com/gohugoio/hugo/commit/47e7788b3c30de6fb895522096baf2c13598c317) [@bep](https://github.com/bep)
-* Make `fileExist` use the same filesystem as `readFile` [51af1d2e](https://github.com/gohugoio/hugo/commit/51af1d2eadcad89e8c2906c05549352ef69ab016) [@bep](https://github.com/bep) [#4633](https://github.com/gohugoio/hugo/issues/4633)
-
-### Other
-
-* Add `.Page.BundleType` [402f6788](https://github.com/gohugoio/hugo/commit/402f6788ee955ad2aace84e8fba1625db7b356d9) [@bep](https://github.com/bep) [#4662](https://github.com/gohugoio/hugo/issues/4662)
-* Add zero-based `Ordinal` to shortcode [3decf4a3](https://github.com/gohugoio/hugo/commit/3decf4a327157e98d3da3502b6d777de63437c39) [@bep](https://github.com/bep) [#3359](https://github.com/gohugoio/hugo/issues/3359)
-* Process and render shortcodes in their order of appearance [85535084](https://github.com/gohugoio/hugo/commit/85535084dea4d3e3adf1ebd08ae57b39d76e1904) [@bep](https://github.com/bep) [#3359](https://github.com/gohugoio/hugo/issues/3359)
-* Init the content and shortcodes early [19084eaf](https://github.com/gohugoio/hugo/commit/19084eaf74246feac61d618c55031369520dfa8e) [@bep](https://github.com/bep) [#4632](https://github.com/gohugoio/hugo/issues/4632)
-* Prepare child page resources before the page itself [3238e14f](https://github.com/gohugoio/hugo/commit/3238e14fdfeedf189a5af122e20bff040ac059bd) [@bep](https://github.com/bep) [#4632](https://github.com/gohugoio/hugo/issues/4632)
-* Make .Content (almost) always available in shortcodes [4d26ab33](https://github.com/gohugoio/hugo/commit/4d26ab33dcef704086f43828d1dfb4b8beae2593) [@bep](https://github.com/bep) [#4632](https://github.com/gohugoio/hugo/issues/4632)[#4653](https://github.com/gohugoio/hugo/issues/4653)[#4655](https://github.com/gohugoio/hugo/issues/4655)
-* Add language merge support for Pages in resource.Resources [47c05c47](https://github.com/gohugoio/hugo/commit/47c05c47e0b663632a649ee5d256acc1a32fe9e4) [@bep](https://github.com/bep) [#4644](https://github.com/gohugoio/hugo/issues/4644)
-* Improve .Content vs shortcodes [e590cc26](https://github.com/gohugoio/hugo/commit/e590cc26eb1363a4b84603f051b20bd43fd1f7bd) [@bep](https://github.com/bep) [#4632](https://github.com/gohugoio/hugo/issues/4632)
-* Improve .Get docs [74520d2c](https://github.com/gohugoio/hugo/commit/74520d2cfd39bb4428182e26c57afa9df83ce7b5) [@paulcmal](https://github.com/paulcmal)
-* Update missing positional parameter test for .Get [e2b277bb](https://github.com/gohugoio/hugo/commit/e2b277bba5935c0686cb83f132eae021ef2dc5e1) [@paulcmal](https://github.com/paulcmal)
-* Improve error message in metadata parse [d681ea55](https://github.com/gohugoio/hugo/commit/d681ea55a0a59b7096dacd194ee0cb8fe15b0757) [@bep](https://github.com/bep) [#3696](https://github.com/gohugoio/hugo/issues/3696)
-* Add some context to front matter parse error [159bed34](https://github.com/gohugoio/hugo/commit/159bed34c3a850d58d08a36ddc40372ed96af2db) [@bep](https://github.com/bep) [#4638](https://github.com/gohugoio/hugo/issues/4638)
-* Updated GetCSV error message (#4636) [5cc944ff](https://github.com/gohugoio/hugo/commit/5cc944ffd77289ab0b8efd69d628fb11d1280993) [@CubeLuke](https://github.com/CubeLuke)
-* .Get doesn't crash on missing positional param fixes #4619 [236f0c84](https://github.com/gohugoio/hugo/commit/236f0c840b45e0c41fcbb2fb6ee556c0fb2d4859) [@paulcmal](https://github.com/paulcmal)
-* fix syntax signature [cd6a2612](https://github.com/gohugoio/hugo/commit/cd6a261242b63555ac2c3ca7a8462b874b490701) [@paulcmal](https://github.com/paulcmal)
-
-
-
-
-
-
-
diff --git a/docs/content/en/news/0.40.1-relnotes/index.md b/docs/content/en/news/0.40.1-relnotes/index.md
deleted file mode 100644
index 3352f164b61..00000000000
--- a/docs/content/en/news/0.40.1-relnotes/index.md
+++ /dev/null
@@ -1,19 +0,0 @@
-
----
-date: 2018-04-25
-title: "Hugo 0.40.1: One Bug Fix"
-description: "Fixes some shortcode vs .Content corner cases."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This release fixes some shortcode vs `.Content` corner cases introduced in Hugo `0.40` [288c3964](https://github.com/gohugoio/hugo/commit/288c39643906b4194a0a6acfbaf87cb0fbdeb361) [@bep](https://github.com/bep) [#4632](https://github.com/gohugoio/hugo/issues/4632)[#4664](https://github.com/gohugoio/hugo/issues/4664).
-
-
-
-
-
diff --git a/docs/content/en/news/0.40.2-relnotes/index.md b/docs/content/en/news/0.40.2-relnotes/index.md
deleted file mode 100644
index 50b9c38428b..00000000000
--- a/docs/content/en/news/0.40.2-relnotes/index.md
+++ /dev/null
@@ -1,20 +0,0 @@
-
----
-date: 2018-04-30
-title: "Hugo 0.40.2: Two Bug fixes"
-description: "Fixes some regressions introduced in 0.40."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-This release fixes some regressions introduced in Hugo `0.40`. See the [milestone](https://github.com/gohugoio/hugo/milestone/62?closed=1) for an issue list.
-
-* Reset .Page.Scratch on live-reload [655983a2](https://github.com/gohugoio/hugo/commit/655983a22dc35b2ee5119dcc4acb9918498d2d68) [@bep](https://github.com/bep) [#4683](https://github.com/gohugoio/hugo/issues/4683)
-* Fix .WordCount, .FuzzyWordCount, .ReadingTime when summary marker is set [001a28c2](https://github.com/gohugoio/hugo/commit/001a28c2f9ed121b33df18fe792859ab378784c6) [@bep](https://github.com/bep) [#4675](https://github.com/gohugoio/hugo/issues/4675)[#4682](https://github.com/gohugoio/hugo/issues/4682)
-
-
-
-
-
diff --git a/docs/content/en/news/0.40.3-relnotes/index.md b/docs/content/en/news/0.40.3-relnotes/index.md
deleted file mode 100644
index 6f822809d9e..00000000000
--- a/docs/content/en/news/0.40.3-relnotes/index.md
+++ /dev/null
@@ -1,12 +0,0 @@
-
----
-date: 2018-05-09
-title: "Hugo 0.40.3: One Bug Fix"
-description: "Fixes a rare, but possible Content truncation issue."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
- Hugo `0.40.3` fixes a possible `.Content` truncation issue introduced in `0.40.1` [90d0d830](https://github.com/gohugoio/hugo/commit/90d0d83097a20a3f521ffc1f5a54a2fbfaf14ce2) [@bep](https://github.com/bep) [#4706](https://github.com/gohugoio/hugo/issues/4706). This should be very rare. It has been reported by only one user on a synthetic site. We have tested a number of big sites that does not show this problem with `0.40.2`, but this is serious enough to warrant a patch release.
diff --git a/docs/content/en/news/0.41-relnotes/featured-hugo-41-poster.png b/docs/content/en/news/0.41-relnotes/featured-hugo-41-poster.png
deleted file mode 100644
index 8f752f6657c..00000000000
Binary files a/docs/content/en/news/0.41-relnotes/featured-hugo-41-poster.png and /dev/null differ
diff --git a/docs/content/en/news/0.41-relnotes/index.md b/docs/content/en/news/0.41-relnotes/index.md
deleted file mode 100644
index 411e373e58d..00000000000
--- a/docs/content/en/news/0.41-relnotes/index.md
+++ /dev/null
@@ -1,64 +0,0 @@
-
----
-date: 2018-05-25
-title: "Hugo 0.41: Privacy Configuration for GDPR"
-description: "Hugo 0.41 adds a new site configuration section to meet the new General Data Protection Regulation (GDPR)."
-categories: ["Releases"]
----
-
-
-In Hugo `0.41` we add a new **Privacy Configuration** section to meet the new regulations in the new **General Data Protection Regulation ([GDPR](https://en.wikipedia.org/wiki/General_Data_Protection_Regulation))**. Many have contributed work on this project, but a special thanks to [@onedrawingperday](https://github.com/onedrawingperday), [@jhabdas](https://github.com/jhabdas), and [@it-gro](https://github.com/it-gro).
-
-> You can read more about the new Privacy Config [here](https://gohugo.io/about/hugo-and-gdpr/).
-
-
-Hugo now has:
-
-* 25917+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 446+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 231+ [themes](http://themes.gohugo.io/)
-
-## Notes
-
-* We have fixed an issue where we sent the wrong `.Site` object into the archetype templates used in `hugo new`. This meant that if you wanted to access site params and some other fields you needed to use `.Site.Info.Params` etc. This release fixes that so now you can use the same construct in the archetype templates as in the regular templates.
-
-## Enhancements
-
-* Alias tweet shortode to twitter [3bfe8f4b](https://github.com/gohugoio/hugo/commit/3bfe8f4be653f44674293685cb5750d90668b2f6) [@bep](https://github.com/bep) [#4765](https://github.com/gohugoio/hugo/issues/4765)
-* Adjust GA templates [f45b522e](https://github.com/gohugoio/hugo/commit/f45b522ebffafc61a3cb9b694bc3542747c73e07) [@bep](https://github.com/bep) [#4616](https://github.com/gohugoio/hugo/issues/4616)
-* Wrap the relevant templates with the privacy policy disable check [67892073](https://github.com/gohugoio/hugo/commit/6789207347fc2df186741644a6fe968d41ea9077) [@bep](https://github.com/bep) [#4616](https://github.com/gohugoio/hugo/issues/4616)
-* Extract internal templates [34ad9a4f](https://github.com/gohugoio/hugo/commit/34ad9a4f178fcf50abe7246ad9d30b294327da16) [@bep](https://github.com/bep) [#4457](https://github.com/gohugoio/hugo/issues/4457)
-* Use double quotes instead of back quotes [b2b500f5](https://github.com/gohugoio/hugo/commit/b2b500f563c3bb36751a4c1610df113c4daad604) [@anthonyfok](https://github.com/anthonyfok)
-* Provide the correct .Site object to archetype templates [ab02594e](https://github.com/gohugoio/hugo/commit/ab02594e09c0414124186e42d67d52d474dd341a) [@bep](https://github.com/bep) [#4732](https://github.com/gohugoio/hugo/issues/4732)
-* Document the GDPR Privacy Config [c71f201f](https://github.com/gohugoio/hugo/commit/c71f201fd93287afa7cb7b875bd523c25e48400c) [@bep](https://github.com/bep) [#4751](https://github.com/gohugoio/hugo/issues/4751)
-* Add no-cookie variants of the Google Analytics templates [a51945ea] (https://github.com/gohugoio/hugo/commit/a51945ea4b99d17501d73cf3367926683e4a4dfd) [@bep](https://github.com/bep) [#4775](https://github.com/gohugoio/hugo/issues/4775)
-* Remove youtube_simple for now [448081b8](https://github.com/gohugoio/hugo/commit/448081b840db4a23c0c49c2d869ac207dcb6ac40) [@bep](https://github.com/bep) [#4751](https://github.com/gohugoio/hugo/issues/4751)
-* Add anonymizeIP to GA privacy config [1f1d955b](https://github.com/gohugoio/hugo/commit/1f1d955b56471e41d5288c57f1ef8333dc297120) [@bep](https://github.com/bep) [#4751](https://github.com/gohugoio/hugo/issues/4751)
-* Support DNT in Twitter shortcode for GDPR [9753cb59](https://github.com/gohugoio/hugo/commit/9753cb59f1f1d866943a485dd7c917d1b68f6eda) [@bep](https://github.com/bep) [#4765](https://github.com/gohugoio/hugo/issues/4765)
-* Regenerate embedded templates [6aa2c385](https://github.com/gohugoio/hugo/commit/6aa2c38507aa1c2246222684717b4d69d26b03d7) [@bep](https://github.com/bep) [#4761](https://github.com/gohugoio/hugo/issues/4761)
-* Add instagram_simple shortcode [9ad46a20](https://github.com/gohugoio/hugo/commit/9ad46a20357a7e28b405feef5c8f7d4501186da6) [@bep](https://github.com/bep) [#4748](https://github.com/gohugoio/hugo/issues/4748)
-* Go fmt [4256de33](https://github.com/gohugoio/hugo/commit/4256de3392d320a5a47fcab49882f2a3249c2163) [@bep](https://github.com/bep)
-* Remove the id from youtube_simple [bed7a0fa](https://github.com/gohugoio/hugo/commit/bed7a0faff90bbe389629347026853b7bc4c8c3f) [@bep](https://github.com/bep) [#4751](https://github.com/gohugoio/hugo/issues/4751)
-* Add an unified .Site.Config with a services section [4ddcf52c](https://github.com/gohugoio/hugo/commit/4ddcf52ccc7af3e23109ebaac1f0486087a212ba) [@bep](https://github.com/bep) [#4751](https://github.com/gohugoio/hugo/issues/4751)
-* Move the privacy config into a parent [353148c2](https://github.com/gohugoio/hugo/commit/353148c2bc2cdb9f2eb8ee967ba756ce09323801) [@bep](https://github.com/bep) [#4751](https://github.com/gohugoio/hugo/issues/4751)
-* Make the simple mode YouTube links schemaless [69ee6b41](https://github.com/gohugoio/hugo/commit/69ee6b41e36625595e2bcabcde0bc58663e5b93c) [@bep](https://github.com/bep) [#4616](https://github.com/gohugoio/hugo/issues/4616)
-* Add YouTube shortcode simple mode [88e35686](https://github.com/gohugoio/hugo/commit/88e356868062cc618385cd22b6730df2459518cd) [@bep](https://github.com/bep) [#4616](https://github.com/gohugoio/hugo/issues/4616)
-* Do not return error on .Get "class" and vice versa in shortcodes [2f17f937](https://github.com/gohugoio/hugo/commit/2f17f9378ad96c4a9f6d7d24b0776ed3a25a08a3) [@bep](https://github.com/bep) [#4745](https://github.com/gohugoio/hugo/issues/4745)
-* Create SUPPORT.md [0a7027e2](https://github.com/gohugoio/hugo/commit/0a7027e2a87283743d5310b74e18666e4a64d3e1) [@coliff](https://github.com/coliff)
-* Add PrivacyEnhanced mode for YouTube to the GDPR Policy [5f24a2c0](https://github.com/gohugoio/hugo/commit/5f24a2c047db0bff8c9e267bfa8ef8e43e6bd24e) [@bep](https://github.com/bep) [#4616](https://github.com/gohugoio/hugo/issues/4616)
-* Add RespectDoNotTrack to GDPR privacy policy for Google Analytics [71014201](https://github.com/gohugoio/hugo/commit/710142016b140538bfc11e48bb32d26fa685b2ad) [@bep](https://github.com/bep) [#4616](https://github.com/gohugoio/hugo/issues/4616)
-* Add the foundation for GDPR privacy configuration [0bbdef98](https://github.com/gohugoio/hugo/commit/0bbdef986d8eecf4fabe9a372e33626dbdfeb36b) [@bep](https://github.com/bep) [#4616](https://github.com/gohugoio/hugo/issues/4616)
-* Show site build warning in TestPageBundlerSiteRegular [9bd4236e](https://github.com/gohugoio/hugo/commit/9bd4236e1b3bee332439eef50e12d4481340c3eb) [@anthonyfok](https://github.com/anthonyfok) [#4672](https://github.com/gohugoio/hugo/issues/4672)
-* Do not show empty BuildDate in version [4eedb377](https://github.com/gohugoio/hugo/commit/4eedb377b60fb6742c97398942a0045ff2a824c4) [@anthonyfok](https://github.com/anthonyfok)
-* Improve markup determination logic [2fb9af59](https://github.com/gohugoio/hugo/commit/2fb9af59c14b1732ba1a2f21794e2cf8dfca0604) [@vassudanagunta](https://github.com/vassudanagunta)
-* Update CONTRIBUTING.md [b6ededf0](https://github.com/gohugoio/hugo/commit/b6ededf0591a81667754f1dccef2c6fe6d342811) [@domdomegg](https://github.com/domdomegg)
-
-## Fixes
-
-* Avoid ANSI character output on Windows [568b4335](https://github.com/gohugoio/hugo/commit/568b4335c20effb46168bd639317a3420f563463) [@LorenzCK](https://github.com/LorenzCK) [#4462](https://github.com/gohugoio/hugo/issues/4462)
-
-
-
-
-
-
diff --git a/docs/content/en/news/0.42-relnotes/featured-hugo-42-poster.png b/docs/content/en/news/0.42-relnotes/featured-hugo-42-poster.png
deleted file mode 100644
index 1f1cab1f150..00000000000
Binary files a/docs/content/en/news/0.42-relnotes/featured-hugo-42-poster.png and /dev/null differ
diff --git a/docs/content/en/news/0.42-relnotes/index.md b/docs/content/en/news/0.42-relnotes/index.md
deleted file mode 100644
index 9356eb675c4..00000000000
--- a/docs/content/en/news/0.42-relnotes/index.md
+++ /dev/null
@@ -1,66 +0,0 @@
-
----
-date: 2018-06-12
-title: "Hugo 0.42: Theme Composition and Inheritance!"
-description: "Hugo 0.42 adds Theme Components support, a new and powerful way of composing your Hugo sites."
-categories: ["Releases"]
----
-
- Hugo `0.42` adds **Theme Components**. This is a really powerful new way of building your Hugo sites with reusable components. This is both **Theme Composition** and **Theme Inheritance**.
-
->Read more about Theme Components in the [Hugo Documentation](https://gohugo.io/hugo-modules/theme-components/).
-
-The feature above was implemented by [@bep](https://github.com/bep), the funny Norwegian, with great design help from the Hugo community. But that implementation would not have been possible without [Afero](https://github.com/spf13/afero), [Steve Francia's](https://github.com/spf13) virtual file system. Hugo is built on top of Afero and many other fast and solid Go projects, and if you look at the combined contribution log of the Hugo project and its many open source dependencies, the total amount of contributions is staggering. A big thank you to them all!
-
-This release represents **27 contributions by 7 contributors** to the main Hugo code base. [@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@onedrawingperday](https://github.com/onedrawingperday), [@anthonyfok](https://github.com/anthonyfok), and [@stefanneuhaus](https://github.com/stefanneuhaus) for their ongoing contributions.
-
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the themes site in pristine condition and to [@kaushalmodi](https://github.com/kaushalmodi) for his great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **8 contributions by 5 contributors**. A special thanks to [@bep](https://github.com/bep), [@LorenzCK](https://github.com/LorenzCK), [@gavinwray](https://github.com/gavinwray), and [@deyton](https://github.com/deyton) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 26286+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 444+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 235+ [themes](http://themes.gohugo.io/)
-
-## Notes
-* The `speakerdeck` shortcode is removed. It didn't work properly, so is isn't expected to be in wide use. If you use it, you will get a build error. You will either have to remove its usage or add a `speakerdeck` shortcode to your own project or theme.
-* We have now virtualized the filesystems for project and theme files. This makes everything simpler, faster and more powerful. But it also means that template lookups on the form `{{ template “theme/partials/pagination.html” . }}` will not work anymore. That syntax has never been documented, so it's not expected to be in wide use. `{{ partial "pagination.html" . }}` will give you the most specific version of that partial.
-
-## Enhancements
-
-
-* Always load GA script over HTTPS [2e6712e2](https://github.com/gohugoio/hugo/commit/2e6712e2814f333caa807888c1d8a9a5a3c03709) [@coliff](https://github.com/coliff)
-* Remove speakerdeck shortcode [65deb72d](https://github.com/gohugoio/hugo/commit/65deb72dc4c9299416cf2d9defddb96dba4101fd) [@onedrawingperday](https://github.com/onedrawingperday) [#4830](https://github.com/gohugoio/hugo/issues/4830)
-* Add `strings.RuneCount` [019bd557](https://github.com/gohugoio/hugo/commit/019bd5576be87c9f06b6a928ede1a5e78677f7b3) [@theory](https://github.com/theory)
-* Add `strings.Repeat` [13435a6f](https://github.com/gohugoio/hugo/commit/13435a6f608306c5094fdcd72a1d9538727f91b2) [@theory](https://github.com/theory)
-* Reset Page's main output on server rebuilds [dc4226a8](https://github.com/gohugoio/hugo/commit/dc4226a8b27e03e31068fc945daab885d3819d04) [@bep](https://github.com/bep) [#4819](https://github.com/gohugoio/hugo/issues/4819)
-* Create LICENSE rather than LICENSE.md in "new theme" [ed4a345e](https://github.com/gohugoio/hugo/commit/ed4a345efeaa19eef2c1c6360d22f75c24abc31a) [@anthonyfok](https://github.com/anthonyfok) [#4623](https://github.com/gohugoio/hugo/issues/4623)
-* Create `_default/baseof.html` in "new theme" [9717ac7d](https://github.com/gohugoio/hugo/commit/9717ac7dce84d004afde4edb32ad81319c7dd8a7) [@anthonyfok](https://github.com/anthonyfok) [#3576](https://github.com/gohugoio/hugo/issues/3576)
-* Add `tweet_simple`shortcode [07b96d16](https://github.com/gohugoio/hugo/commit/07b96d16e8679c40e289c9076ef4414ed6eb7f81) [@onedrawingperday](https://github.com/onedrawingperday)
-* Make "new theme" feedback more intuitive [692ec008](https://github.com/gohugoio/hugo/commit/692ec008726b570c9b30ac3391774cbb622730cb) [@anthonyfok](https://github.com/anthonyfok)
-* Move nextStepsText() to new_site.go [d3dd74fd](https://github.com/gohugoio/hugo/commit/d3dd74fd655c22f21e91e38edb1d377a1357e3be) [@anthonyfok](https://github.com/anthonyfok)
-* Add support for theme composition and inheritance [80230f26](https://github.com/gohugoio/hugo/commit/80230f26a3020ff33bac2bef01b2c0e314b89f86) [@bep](https://github.com/bep) [#4460](https://github.com/gohugoio/hugo/issues/4460)[#4450](https://github.com/gohugoio/hugo/issues/4450)
-* Add vimeo_simple [8de53244](https://github.com/gohugoio/hugo/commit/8de53244799f0d2d0343056d348d810343cf7aa5) [@onedrawingperday](https://github.com/onedrawingperday) [#4749](https://github.com/gohugoio/hugo/issues/4749)
-* Add a BlackFriday option for rel="noreferrer" on external links [20cbc2c7](https://github.com/gohugoio/hugo/commit/20cbc2c7856a9b07d45648d940276374db35e425) [@stefanneuhaus](https://github.com/stefanneuhaus) [#4722](https://github.com/gohugoio/hugo/issues/4722)
-* Add a BlackFriday option for rel="nofollow" on external links [7a619264](https://github.com/gohugoio/hugo/commit/7a6192647a4b383cd539df2063388ea380371de6) [@stefanneuhaus](https://github.com/stefanneuhaus) [#4722](https://github.com/gohugoio/hugo/issues/4722)
-* Update Chroma [b5b36e32](https://github.com/gohugoio/hugo/commit/b5b36e32008bc8ea779ae06bf249b537f6d5c336) [@bep](https://github.com/bep) [#4779](https://github.com/gohugoio/hugo/issues/4779)
-* Enhance Page and Resource String() [4f0665f4](https://github.com/gohugoio/hugo/commit/4f0665f476e06e9707621c18f7422fdeb776e0d1) [@vassudanagunta](https://github.com/vassudanagunta)
-* Update theme documentation [c74b0f8f](https://github.com/gohugoio/hugo/commit/c74b0f8f9b30866e09efac8235cc5e0093ab3d50) [@bep](https://github.com/bep) [#4460](https://github.com/gohugoio/hugo/issues/4460)
-
-## Fixes
-
-* Make sure that `.Site.Taxonomies` is always set on rebuilds [6464981a](https://github.com/gohugoio/hugo/commit/6464981adb4d7d0f41e8e2c987342082982210a1) [@bep](https://github.com/bep) [#4838](https://github.com/gohugoio/hugo/issues/4838)
-* Reset the "distinct error logger" on rebuilds [bf5f10fa](https://github.com/gohugoio/hugo/commit/bf5f10faa9fd445c4dd21839aa7d73cd2acbfb85) [@bep](https://github.com/bep) [#4818](https://github.com/gohugoio/hugo/issues/4818)
-* Remove frameborder attr YT iframe + CSS fixes [ceaff7ca](https://github.com/gohugoio/hugo/commit/ceaff7cafc5357274e546984ae02a4cbdf305f81) [@onedrawingperday](https://github.com/onedrawingperday)
-* Fix vimeo_simple thumb scaling [b84389c5](https://github.com/gohugoio/hugo/commit/b84389c5e0e1ef15449b24d488bbbcbc41245c59) [@onedrawingperday](https://github.com/onedrawingperday)
-* Fix typo instagram_simple [d68367cb](https://github.com/gohugoio/hugo/commit/d68367cbe76cbc02adb5b778e8be98bed6319368) [@onedrawingperday](https://github.com/onedrawingperday)
-* Prevent isBaseTemplate() from matching "baseof" in dir [c3115292](https://github.com/gohugoio/hugo/commit/c3115292a7f2d2623cb45054a361e997ad9330c9) [@anthonyfok](https://github.com/anthonyfok) [#4809](https://github.com/gohugoio/hugo/issues/4809)
-
-
-
-
-
diff --git a/docs/content/en/news/0.42.1-relnotes/index.md b/docs/content/en/news/0.42.1-relnotes/index.md
deleted file mode 100644
index 6b5b3c775d4..00000000000
--- a/docs/content/en/news/0.42.1-relnotes/index.md
+++ /dev/null
@@ -1,18 +0,0 @@
-
----
-date: 2018-06-13
-title: "Hugo 0.42.1: Two Bug Fixes"
-description: "Hugo 0.42.1 fixes two issues."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with two fixes:
-
-* Reset the global pages cache on server rebuilds [128f14ef](https://github.com/gohugoio/hugo/commit/128f14efad90886ffef37c01ac1e20436a732f97) [@bep](https://github.com/bep) [#4845](https://github.com/gohugoio/hugo/issues/4845)
-* Do not fail server build when /static is missing [34ee27a7](https://github.com/gohugoio/hugo/commit/34ee27a78b9e2b5f475d44253ae234067b76cc6e) [@bep](https://github.com/bep) [#4846](https://github.com/gohugoio/hugo/issues/4846)
-
diff --git a/docs/content/en/news/0.42.2-relnotes/index.md b/docs/content/en/news/0.42.2-relnotes/index.md
deleted file mode 100644
index c9bf6c46963..00000000000
--- a/docs/content/en/news/0.42.2-relnotes/index.md
+++ /dev/null
@@ -1,18 +0,0 @@
-
----
-date: 2018-06-28
-title: "Hugo 0.42.2: One Bug Fix"
-description: "Hugo 0.42.2 fixes server reload on config changes."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
- This release fixes broken server-reload on config changes. This is a regression from Hugo `0.42`. [3a7706b0](https://github.com/gohugoio/hugo/commit/3a7706b069107e5fa6112b3f7ce006f16867cb38) [@bep](https://github.com/bep) [#4878](https://github.com/gohugoio/hugo/issues/4878)
-
-
-
-
-
diff --git a/docs/content/en/news/0.43-relnotes/featured-hugo-43-poster.png b/docs/content/en/news/0.43-relnotes/featured-hugo-43-poster.png
deleted file mode 100644
index b221ca7f102..00000000000
Binary files a/docs/content/en/news/0.43-relnotes/featured-hugo-43-poster.png and /dev/null differ
diff --git a/docs/content/en/news/0.43-relnotes/index.md b/docs/content/en/news/0.43-relnotes/index.md
deleted file mode 100644
index afa23329b02..00000000000
--- a/docs/content/en/news/0.43-relnotes/index.md
+++ /dev/null
@@ -1,96 +0,0 @@
-
----
-date: 2018-07-09
-title: "And Now: Hugo Pipes!"
-description: "Hugo 0.43 adds a powerful and simple to use assets pipeline with SASS/SCSS and much, much more …"
-categories: ["Releases"]
----
-
-Hugo `0.43` adds a powerful and very simple to use **Assets Pipeline** with **SASS and SCSS** with source map support, **PostCSS** and **minification** and **fingerprinting** and **Subresource Integrity** and ... much more. Oh, did we mention that you can now do **ad-hoc image processing** and execute text resources as Go templates?
-
-An example pipeline:
-
-```go-html-template
-{{ $styles := resources.Get "scss/main.scss" | toCSS | postCSS | minify | fingerprint }}
-
-```
-
-To me, the above is beautiful in its speed and simplicity. It could be printed on a t-shirt. I wrote in the [Hugo Birthday Post](https://gohugo.io/news/lets-celebrate-hugos-5th-birthday/) some days ago about the value of a single binary with native and fast implementations. I should have added _simplicity_ as a keyword. There seem to be a misconception that all of this needs to be hard and painful.
-
-New functions to create `Resource` objects:
-
-* `resources.Get`
-* `resources.FromString`: Create a Resource from a string.
-
-New `Resource` transformation funcs:
-
-* `resources.ToCSS`: Compile `SCSS` or `SASS` into `CSS`.
-* `resources.PostCSS`: Process your CSS with PostCSS. Config file support (project or theme or passed as an option).
-* `resources.Minify`: Currently supports `css`, `js`, `json`, `html`, `svg`, `xml`.
-* `resources.Fingerprint`: Creates a fingerprinted version of the given Resource with Subresource Integrity.
-* `resources.Concat`: Concatenates a list of Resource objects. Think of this as a poor man's bundler.
-* `resources.ExecuteAsTemplate`: Parses and executes the given Resource and data context (e.g. .Site) as a Go template.
-
-
-I, [@bep](https://github.com/bep), implemented this in [dea71670](https://github.com/gohugoio/hugo/commit/dea71670c059ab4d5a42bd22503f18c087dd22d4). We will work hard to get the documentation up to date, but follow the links above for details, and also see this [demo project](https://github.com/bep/hugo-sass-test).
-
-
-This release represents **35 contributions by 7 contributors** to the main Hugo code base.
-[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@anthonyfok](https://github.com/anthonyfok), [@openscript](https://github.com/openscript), and [@caarlos0](https://github.com/caarlos0) for their ongoing contributions.
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the themes site in pristine condition and to [@kaushalmodi](https://github.com/kaushalmodi) for his great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **11 contributions by 5 contributors**. A special thanks to [@bep](https://github.com/bep), [@danrl](https://github.com/danrl), [@regisphilibert](https://github.com/regisphilibert), and [@digitalcraftsman](https://github.com/digitalcraftsman) for their work on the documentation site.
-
-Hugo now has:
-
-* 26968+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 443+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 238+ [themes](http://themes.gohugo.io/)
-
-## Notes
-
-* Replace deprecated {Get,}ByPrefix with {Get,}Match [42ed6025](https://github.com/gohugoio/hugo/commit/42ed602580a672e420e1d860384e812f4871ff67) [@anthonyfok](https://github.com/anthonyfok)
-* Hugo is now released with two binary version: One with and one without SCSS/SASS support. At the time of writing, this is only available in the binaries on the GitHub release page. Brew, Snap builds etc. will come. But note that you **only need the extended version if you want to edit SCSS**. For your CI server, or if you don't use SCSS, you will most likely want the non-extended version.
-
-## Enhancements
-
-### Templates
-
-* Return en empty slice in `after` instead of error [f8212d20](https://github.com/gohugoio/hugo/commit/f8212d20009c4b5cc6e1ec733d09531eb6525d9f) [@bep](https://github.com/bep) [#4894](https://github.com/gohugoio/hugo/issues/4894)
-* Update internal pagination template to support Bootstrap 4 [ca1e46ef](https://github.com/gohugoio/hugo/commit/ca1e46efb94e3f3d2c8482cb9434d2f38ffd2683) [@bep](https://github.com/bep) [#4881](https://github.com/gohugoio/hugo/issues/4881)
-* Support text/template/parse API change in go1.11 [9f27091e](https://github.com/gohugoio/hugo/commit/9f27091e1067875e2577c331acc60adaef5bb234) [@anthonyfok](https://github.com/anthonyfok) [#4784](https://github.com/gohugoio/hugo/issues/4784)
-
-### Core
-
-* Allow forward slash in shortcode names [de37455e](https://github.com/gohugoio/hugo/commit/de37455ec73cffd039b44e8f6c62d2884b1d6bbd) [@bep](https://github.com/bep) [#4886](https://github.com/gohugoio/hugo/issues/4886)
-* Reset the global pages cache on server rebuilds [128f14ef](https://github.com/gohugoio/hugo/commit/128f14efad90886ffef37c01ac1e20436a732f97) [@bep](https://github.com/bep) [#4845](https://github.com/gohugoio/hugo/issues/4845)
-
-### Other
-
-* Bump CircleCI image [e3df6478](https://github.com/gohugoio/hugo/commit/e3df6478f09a7a5fed96aced791fa94fd2c35d1a) [@bep](https://github.com/bep)
-* Add Goreleaser extended config [626afc98](https://github.com/gohugoio/hugo/commit/626afc98254421f5a5edc97c541b10bd81d5bbbb) [@bep](https://github.com/bep) [#4908](https://github.com/gohugoio/hugo/issues/4908)
-* Build both hugo and hugo.extended for 0.43 [e1027c58](https://github.com/gohugoio/hugo/commit/e1027c5846b48c4ad450f6cc27e2654c9e0dae39) [@anthonyfok](https://github.com/anthonyfok) [#4908](https://github.com/gohugoio/hugo/issues/4908)
-* Add temporary build script [bfc3488b](https://github.com/gohugoio/hugo/commit/bfc3488b8e8b3dc1ffc6a339ee2dac8dcbdb55a9) [@bep](https://github.com/bep)
-* Add "extended" to "hugo version" [ce84b524](https://github.com/gohugoio/hugo/commit/ce84b524f4e94299b5b66afe7ce1a9bd4a9959fc) [@anthonyfok](https://github.com/anthonyfok) [#4913](https://github.com/gohugoio/hugo/issues/4913)
-* Add a newScratch template func [2b8d907a](https://github.com/gohugoio/hugo/commit/2b8d907ab731627f4e2a30442cd729064516c8bb) [@bep](https://github.com/bep) [#4685](https://github.com/gohugoio/hugo/issues/4685)
-* Add Hugo Piper with SCSS support and much more [dea71670](https://github.com/gohugoio/hugo/commit/dea71670c059ab4d5a42bd22503f18c087dd22d4) [@bep](https://github.com/bep) [#4381](https://github.com/gohugoio/hugo/issues/4381)[#4903](https://github.com/gohugoio/hugo/issues/4903)[#4858](https://github.com/gohugoio/hugo/issues/4858)
-* Consider root and current section's content type if set in front matter [c790029e](https://github.com/gohugoio/hugo/commit/c790029e1dbb0b66af18d05764bd6045deb2e180) [@bep](https://github.com/bep) [#4891](https://github.com/gohugoio/hugo/issues/4891)
-* Update docker image [554553c0](https://github.com/gohugoio/hugo/commit/554553c09c7657d28681e1fa0638806a452737a0) [@bep](https://github.com/bep)
-* Merge branch 'release-0.42.2' [282f6035](https://github.com/gohugoio/hugo/commit/282f6035e7c36f8550d91033e3a66718468c6c8b) [@bep](https://github.com/bep)
-* Release 0.42.2 [1637d12e](https://github.com/gohugoio/hugo/commit/1637d12e3762fc1ebab4cd675f75afaf25f59cdb) [@bep](https://github.com/bep)
-* Update GoReleaser config [1f0c4e1f](https://github.com/gohugoio/hugo/commit/1f0c4e1fb347bb233f3312c424fbf5a013c03604) [@caarlos0](https://github.com/caarlos0)
-* Create missing head.html partial on new theme generation [fd71fa89](https://github.com/gohugoio/hugo/commit/fd71fa89bd6c197402582c87b2b76d4b96d562bf) [@openscript](https://github.com/openscript)
-* Add html doctype to baseof.html template for new themes [b5a3aa70](https://github.com/gohugoio/hugo/commit/b5a3aa7082135d0a573f4fbb00f798e26b67b902) [@openscript](https://github.com/openscript)
-* Adds .gitattributes to force Go files to LF [6a2968fd](https://github.com/gohugoio/hugo/commit/6a2968fd5c0116d93de0f379ac615e9076821899) [@neurocline](https://github.com/neurocline)
-* Update to Go 1.9.7 and Go 1.10.3 [23d5fc82](https://github.com/gohugoio/hugo/commit/23d5fc82ee01d56440d0991c899acd31e9b63e27) [@anthonyfok](https://github.com/anthonyfok)
-* Update Dockerfile to a multi-stage build [8531ec7c](https://github.com/gohugoio/hugo/commit/8531ec7ca36fd35a57fba06bbb06a65c94dfd3ed) [@skoblenick](https://github.com/skoblenick) [#4154](https://github.com/gohugoio/hugo/issues/4154)
-* Release 0.42.1 [d67e843c](https://github.com/gohugoio/hugo/commit/d67e843c1212e1f53933556b5f946c8541188d9a) [@bep](https://github.com/bep)
-* Do not fail server build when /static is missing [34ee27a7](https://github.com/gohugoio/hugo/commit/34ee27a78b9e2b5f475d44253ae234067b76cc6e) [@bep](https://github.com/bep) [#4846](https://github.com/gohugoio/hugo/issues/4846)
-
-## Fixes
-
-* Do not create paginator pages for the other output formats [43338c3a](https://github.com/gohugoio/hugo/commit/43338c3a99769eb7d0df0c12559b8b3d42b67dba) [@bep](https://github.com/bep) [#4890](https://github.com/gohugoio/hugo/issues/4890)
-* Fix the shortcodes/partials vs base template detection [a5d0a57e](https://github.com/gohugoio/hugo/commit/a5d0a57e6bdab583134a68c035aac9b3007f006a) [@bep](https://github.com/bep) [#4897](https://github.com/gohugoio/hugo/issues/4897)
-* nfpm replacements [e1a052ec](https://github.com/gohugoio/hugo/commit/e1a052ecb823c688406a8af97dfaaf52a75231da) [@caarlos0](https://github.com/caarlos0)
-* Fix typos [3cea2932](https://github.com/gohugoio/hugo/commit/3cea2932e17a08ebc19cd05f3079d9379bc8fba5) [@idealhack](https://github.com/idealhack)
diff --git a/docs/content/en/news/0.44-relnotes/featured-hugo-44-poster.png b/docs/content/en/news/0.44-relnotes/featured-hugo-44-poster.png
deleted file mode 100644
index 330b235fbf2..00000000000
Binary files a/docs/content/en/news/0.44-relnotes/featured-hugo-44-poster.png and /dev/null differ
diff --git a/docs/content/en/news/0.44-relnotes/index.md b/docs/content/en/news/0.44-relnotes/index.md
deleted file mode 100644
index aa8396898d3..00000000000
--- a/docs/content/en/news/0.44-relnotes/index.md
+++ /dev/null
@@ -1,39 +0,0 @@
-
----
-date: 2018-07-13
-title: "Hugo 0.44: Friday the 13th Edition"
-description: "A sequel to the very popular Hugo Pipes Edition; bug-fixes and enhancements …"
-categories: ["Releases"]
----
-
-
-Hugo `0.44` is the follow-up release, or **The Sequel**, of the very well received `0.43` only days ago. That release added **Hugo Pipes**, with SCSS/SASS support, assets bundling and minification, ad-hoc image processing and much more.
-
-This is mostly a bug-fix release, but it also includes several important improvements.
-
-Many complained that their SVG images vanished when browsed from the `hugo server`. With **Hugo Pipes** MIME types suddenly got really important, but Hugo's use of `Suffix` was ambiguous. This became visible when we redefined the `image/svg+xml` to work with **Hugo Pipes**. We have now added a `Suffixes` field on the MIME type definition in Hugo, which is a list of one or more filename suffixes the MIME type is identified with. If you need to add a custom MIME type definition, this means that you also need to specify the full MIME type as the key, e.g. `image/svg+xml`.
-
-Hugo now has:
-
-* 27120+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 443+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 239+ [themes](http://themes.gohugo.io/)
-
-## Notes
-* `MediaType.Suffix` is deprecated and replaced with a plural version, `MediaType.Suffixes`, with a more specific definition. You will get a detailed WARNING in the console if you need to do anything.
-
-## Enhancements
-* Allow multiple file suffixes per media type [b874a1ba](https://github.com/gohugoio/hugo/commit/b874a1ba7ab8394dc741c8c70303a30a35b63e43) [@bep](https://github.com/bep) [#4920](https://github.com/gohugoio/hugo/issues/4920)
-* Clean up the in-memory Resource reader usage [47d38628](https://github.com/gohugoio/hugo/commit/47d38628ec0f4e72ff17661f13456b2a1511fe13) [@bep](https://github.com/bep) [#4936](https://github.com/gohugoio/hugo/issues/4936)
-* Move opening of the transformed resources after cache check [0024dcfe](https://github.com/gohugoio/hugo/commit/0024dcfe3e016c67046de06d1dac5e7f5235f9e1) [@bep](https://github.com/bep)
-* Improve type support in `resources.Concat` [306573de](https://github.com/gohugoio/hugo/commit/306573def0e20ec16ee5c447981cc09ed8bb7ec7) [@bep](https://github.com/bep) [#4934](https://github.com/gohugoio/hugo/issues/4934)
-* Flush `partialCached` cache on rebuilds [6b6dcb44](https://github.com/gohugoio/hugo/commit/6b6dcb44a014699c289bf32fe57d4c4216777be0) [@bep](https://github.com/bep) [#4931](https://github.com/gohugoio/hugo/issues/4931)
-* Include the transformation step in the error message [d96f2a46](https://github.com/gohugoio/hugo/commit/d96f2a460f58e91d8f6253a489d4879acfec6916) [@bep](https://github.com/bep) [#4924](https://github.com/gohugoio/hugo/issues/4924)
-* Exclude *.svg from CRLF/LF conversion [9c1e8208](https://github.com/gohugoio/hugo/commit/9c1e82085eb07d5b4dcdacbe82d5bafd26e08631) [@anthonyfok](https://github.com/anthonyfok)
-
-## Fixes
-
-* Fix `resources.Concat` for transformed resources [beec1fc9](https://github.com/gohugoio/hugo/commit/beec1fc98e5d37bba742d6bc2a0ff7c344b469f8) [@bep](https://github.com/bep) [#4936](https://github.com/gohugoio/hugo/issues/4936)
-* Fix static filesystem for themed multihost sites [80c8f3b8](https://github.com/gohugoio/hugo/commit/80c8f3b81a9849080e64bf877288ede28d960d3f) [@bep](https://github.com/bep) [#4929](https://github.com/gohugoio/hugo/issues/4929)
-* Set permission of embedded templates to 0644 [2b73e89d](https://github.com/gohugoio/hugo/commit/2b73e89d6d2822e86360a6c92c87f539677c119b) [@anthonyfok](https://github.com/anthonyfok)
-
diff --git a/docs/content/en/news/0.45-relnotes/featured-hugo-45-poster.png b/docs/content/en/news/0.45-relnotes/featured-hugo-45-poster.png
deleted file mode 100644
index 40f71daca9e..00000000000
Binary files a/docs/content/en/news/0.45-relnotes/featured-hugo-45-poster.png and /dev/null differ
diff --git a/docs/content/en/news/0.45-relnotes/index.md b/docs/content/en/news/0.45-relnotes/index.md
deleted file mode 100644
index 83051c0586c..00000000000
--- a/docs/content/en/news/0.45-relnotes/index.md
+++ /dev/null
@@ -1,107 +0,0 @@
-
----
-date: 2018-07-22
-title: "Hugo 0.45: Revival of ref, relref and GetPage"
-description: "Hugo 0.45 adds relative page lookups, language support in ref/relref and several Hugo Pipes improvements."
-categories: ["Releases"]
----
-
-
-Hugo `0.45` is the **revival of ref, relref and GetPage**. [@vassudanagunta](https://github.com/vassudanagunta) and [@bep](https://github.com/bep) have done some great work improving the API and implementation for the helper functions used to **get one page**. Before this release, the API was a little bit clumsy and the result potentially ambiguous in some situations.
-
-Now you can simply do:
-
-```go-html-template
-{{ with .Site.GetPage "/blog/my-post.md" }}{{ .Title }}{{ end }}
-```
-
-Or to get a section page:
-
-
-```go-html-template
-{{ with .Site.GetPage "/blog" }}{{ .Title }}{{ end }}
-```
-
-We have also added a `.GetPage` method on `Page` and added support for page-relative linking. This means that the leading slash (`/`) now has a meaning. For `.Site.GetPage`, all lookups will start at the content root. But for lookups with a `Page` context, paths without a leading slash will be treated as relative to the page.
-
-This means that the following example will find the page in the current section:
-
-```go-html-template
-{{* ref "my-post.md" */>}}
-```
-
-You can also use the `..` to refer to a page one level up etc.:
-
-```go-html-template
-{{* ref "../my-post.md" */>}}
-```
-
-We have now also added language support to `ref` and `relref`, so you can link to a page in another language:
-
-```go-html-template
-{{* relref path="document.md" lang="ja" */>}}
-```
-
-To link to a given Output Format of a document, you can use this syntax:
-
-```go-html-template
-{{* relref path="document.md" outputFormat="rss" */>}}
-```
-
-To make working with these reflinks on bigger sites easier to work with, we have also improved the error logging, and added two new configuration settings:
-
-* refLinksErrorLevel: ERROR (default, will fail the build when a reflink cannot be resolved) or WARNING.
-* refLinksNotFoundURL: Set this to an URL placeholder used when no reference could be resolved.
-
-Visit the [Hugo Docs](https://gohugo.io/content-management/cross-references) for more information.
-
-We have also done some important improvements and fixes in **Hugo Pipes** in this release: SCSS source maps on Windows now works, we now support project-local `PostCSS` installation, and we have added `IncludePaths` to `SCSS` options, making it possible to include, say, a path below `node_modules` in the SASS/SCSS build.
-
-This release represents **31 contributions by 4 contributors** to the main Hugo code base.
-[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@vassudanagunta](https://github.com/vassudanagunta), [@hairmare](https://github.com/hairmare), and [@garrmcnu](https://github.com/garrmcnu) for their ongoing contributions.
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the themes site in pristine condition and to [@kaushalmodi](https://github.com/kaushalmodi) for his great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **10 contributions by 8 contributors**. A special thanks to [@kaushalmodi](https://github.com/kaushalmodi), [@Hanzei](https://github.com/Hanzei), [@KurtTrowbridge](https://github.com/KurtTrowbridge), and [@regisphilibert](https://github.com/regisphilibert) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 27334+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 443+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 238+ [themes](http://themes.gohugo.io/)
-
-## Notes
-* `.Site.GetPage` with more than 2 arguments will not work anymore. This means that `{{ .Site.GetPage "page" "blog" "my-post.md" }}` will fail. `{{ .Site.GetPage "page" "blog/my-post.md" }}` will work, but we recommend you use the simpler `{{ .Site.GetPage "/blog/my-post.md" }}`
-* Relative paths in `relref` or `ref` that finds its match not relative to the page itself will work, but we now print a warning saying that you should correct it to an absolute path. E.g. `{{* ref "blog/my-post.md" */>}}` => `{{* ref "/blog/my-post.md" */>}}`.
-
-## Enhancements
-
-* Print a WARNING about relative non-relative ref/relref matches [a451c49f](https://github.com/gohugoio/hugo/commit/a451c49fde1da6e2cc436a2b7d383ee772b1f893) [@bep](https://github.com/bep) [#4973](https://github.com/gohugoio/hugo/issues/4973)
-* Allow untyped nil to be merged in lang.Merge [ff16c42e](https://github.com/gohugoio/hugo/commit/ff16c42ed0965e1c8acf6e6a6dcda3ea50c107f2) [@bep](https://github.com/bep) [#4977](https://github.com/gohugoio/hugo/issues/4977)
-* Get rid of the utils package [062510cf](https://github.com/gohugoio/hugo/commit/062510cf1f7b79aed2efe88c5b9340d009bdec0e) [@bep](https://github.com/bep)
-* Update hugo_windows.go [4e1d0cd9](https://github.com/gohugoio/hugo/commit/4e1d0cd9f1d43d133d669a019a84117cadd41955) [@bep](https://github.com/bep)
-* Add IncludePaths config option [166483fe](https://github.com/gohugoio/hugo/commit/166483fe1227b0c59c6b4d88cfdfaf7d7b0d79c5) [@bep](https://github.com/bep) [#4921](https://github.com/gohugoio/hugo/issues/4921)
-* Increase refLinker test coverage [8278384b](https://github.com/gohugoio/hugo/commit/8278384b9680cfdcecef9c668638ad483012857f) [@vassudanagunta](https://github.com/vassudanagunta)
-* Add test coverage for recent ref overhaul [2bac3715](https://github.com/gohugoio/hugo/commit/2bac3715448e90e197ada7cc73c87f696c19def6) [@vassudanagunta](https://github.com/vassudanagunta) [#4969](https://github.com/gohugoio/hugo/issues/4969)
-* Update ref, relref, GetPage docs [1eb8b36b](https://github.com/gohugoio/hugo/commit/1eb8b36b3802e72bc2c16965461ef1899bb073b3) [@bep](https://github.com/bep)
-* Document refLinksErrorLevel and refLinksNotFoundURL [00c74ee7](https://github.com/gohugoio/hugo/commit/00c74ee7ffae71fd5f47d555160354a775e26151) [@bep](https://github.com/bep) [#4964](https://github.com/gohugoio/hugo/issues/4964)
-* Add configurable ref/relref error handling and notFoundURL [e25aa655](https://github.com/gohugoio/hugo/commit/e25aa655f4227ac064be5fe770d517a80acd46b2) [@bep](https://github.com/bep) [#4964](https://github.com/gohugoio/hugo/issues/4964)
-* Try node_modules/postcss-cli/bin/postcss first [ebe4d39f](https://github.com/gohugoio/hugo/commit/ebe4d39f175f73e4f130972cb3d74ef0af5d5761) [@bep](https://github.com/bep) [#4952](https://github.com/gohugoio/hugo/issues/4952)
-* Add optional lang as argument to rel/relref [d741064b](https://github.com/gohugoio/hugo/commit/d741064bebe2f4663a7ba12556dccc3dffe08629) [@bep](https://github.com/bep) [#4956](https://github.com/gohugoio/hugo/issues/4956)
-* Simplify .Site.GetPage etc. [3eb313fe](https://github.com/gohugoio/hugo/commit/3eb313fef495a39731dafa6bddbf77760090230d) [@bep](https://github.com/bep) [#4147](https://github.com/gohugoio/hugo/issues/4147)[#4727](https://github.com/gohugoio/hugo/issues/4727)[#4728](https://github.com/gohugoio/hugo/issues/4728)[#4728](https://github.com/gohugoio/hugo/issues/4728)[#4726](https://github.com/gohugoio/hugo/issues/4726)[#4652](https://github.com/gohugoio/hugo/issues/4652)
-* Unify page lookups [b93417aa](https://github.com/gohugoio/hugo/commit/b93417aa1d3d38a9e56bad25937e0e638a113faf) [@vassudanagunta](https://github.com/vassudanagunta) [#4147](https://github.com/gohugoio/hugo/issues/4147)[#4727](https://github.com/gohugoio/hugo/issues/4727)[#4728](https://github.com/gohugoio/hugo/issues/4728)[#4728](https://github.com/gohugoio/hugo/issues/4728)[#4726](https://github.com/gohugoio/hugo/issues/4726)[#4652](https://github.com/gohugoio/hugo/issues/4652)
-* Improve error message [4c240800](https://github.com/gohugoio/hugo/commit/4c240800a4275244c9e0847cd6707383180f1ac3) [@bep](https://github.com/bep)
-* Remove unused code [2f2bc7ff](https://github.com/gohugoio/hugo/commit/2f2bc7ff70b90fb11580cc092ef3883bf68d8ad7) [@bep](https://github.com/bep)
-
-## Fixes
-
-* Avoid server panic on TOML mistake in i18n [75acff5f](https://github.com/gohugoio/hugo/commit/75acff5f20d0d41ffa1ae20402001c7a82f077cb) [@bep](https://github.com/bep) [#4942](https://github.com/gohugoio/hugo/issues/4942)
-* Only set 'allThemes' if there are themes in the config file [38204c4a](https://github.com/gohugoio/hugo/commit/38204c4ab6fa2aa2ab8bd06ddb3e07b66e5f9646) [@garrmcnu](https://github.com/garrmcnu) [#4851](https://github.com/gohugoio/hugo/issues/4851)
-* Fix potential server panic with drafts/future enabled [1ab4658c](https://github.com/gohugoio/hugo/commit/1ab4658c0d5ea2927f04bd748206e5b139a6326e) [@bep](https://github.com/bep) [#4965](https://github.com/gohugoio/hugo/issues/4965)
-* Mark shortcode changes as content changes in server mode [12679b40](https://github.com/gohugoio/hugo/commit/12679b408362a93a3c6159588d6291a3b7ed5548) [@bep](https://github.com/bep) [#4965](https://github.com/gohugoio/hugo/issues/4965)
-* Fix source maps on Windows [f01505c9](https://github.com/gohugoio/hugo/commit/f01505c910a325acc18742ac6b3637aa01975e37) [@bep](https://github.com/bep) [#4968](https://github.com/gohugoio/hugo/issues/4968)
-* Fix typo-logic bug in GetPage [b56d9a12](https://github.com/gohugoio/hugo/commit/b56d9a1294e692d096bff442e0b1fec61a8c2b0f) [@vassudanagunta](https://github.com/vassudanagunta)
-* Enable test case fixed by commit 501543d4 [d6fde8fa](https://github.com/gohugoio/hugo/commit/d6fde8fa131f3852fa98a8ec5c360e736486cf54) [@vassudanagunta](https://github.com/vassudanagunta)
-* Fix theme config for Work Fs [5c9d5413](https://github.com/gohugoio/hugo/commit/5c9d5413a4e2cc8d44a8b2d7dff04e6523ba2a29) [@bep](https://github.com/bep) [#4951](https://github.com/gohugoio/hugo/issues/4951)
-* Fix addkit link to account for i18n [fd1f4a78](https://github.com/gohugoio/hugo/commit/fd1f4a7860c4b989865b47c727239cf924a52fa4) [@hairmare](https://github.com/hairmare)
diff --git a/docs/content/en/news/0.45.1-relnotes/index.md b/docs/content/en/news/0.45.1-relnotes/index.md
deleted file mode 100644
index 84e0416c7f1..00000000000
--- a/docs/content/en/news/0.45.1-relnotes/index.md
+++ /dev/null
@@ -1,26 +0,0 @@
-
----
-date: 2018-07-25
-title: "Hugo 0.45.1: Two Bug Fixes"
-description: "Hugo 0.45.1 comes with two bug fixes ... and one enhancement."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
- This is a bug-fix release with a couple of important fixes (and one enhancement).
-
-## Enhancements
-
-* Add Page.FirstSection [016dd4a6](https://github.com/gohugoio/hugo/commit/016dd4a69a765061bb3da8490d3cac6ec47a91eb) [@bep](https://github.com/bep)
-
-## Fixes
-
-* Fix .Site.GetPage regression [04d4c08d](https://github.com/gohugoio/hugo/commit/04d4c08dbcac08ff7feeb88863e91799fed0937b) [@bep](https://github.com/bep) [#4989](https://github.com/gohugoio/hugo/issues/4989)
-* Fix "borrow content from another language" issue [c6b599a0](https://github.com/gohugoio/hugo/commit/c6b599a06d66b8e3c92343d25bb8043eb4f291f1) [@bep](https://github.com/bep) [#4986](https://github.com/gohugoio/hugo/issues/4986)
-
-
-
-
-
diff --git a/docs/content/en/news/0.46-relnotes/featured-hugo-46-poster.png b/docs/content/en/news/0.46-relnotes/featured-hugo-46-poster.png
deleted file mode 100644
index c00622e04f0..00000000000
Binary files a/docs/content/en/news/0.46-relnotes/featured-hugo-46-poster.png and /dev/null differ
diff --git a/docs/content/en/news/0.46-relnotes/index.md b/docs/content/en/news/0.46-relnotes/index.md
deleted file mode 100644
index ef0df242763..00000000000
--- a/docs/content/en/news/0.46-relnotes/index.md
+++ /dev/null
@@ -1,49 +0,0 @@
-
----
-date: 2018-08-01
-title: "The Summer of Hugo"
-description: "Hugo 0.46: Full SCSS/SASS import inheritance support, rework of Hugo Pipes vs. multilingual, and more …"
-categories: ["Releases"]
----
-
-**Hugo 0.46** is the closing credits to **The Summer of Hugo**. While most people have been relaxing on the beach, Hugo has been really busy:
-
-* **0.42, June 12, 2018**: [Theme Composition and Inheritance!](https://gohugo.io/news/0.42-relnotes/)
-* **0.43, July 9, 2018**: [Hugo Pipes!](https://gohugo.io/news/0.43-relnotes/)
-
-This was followed by some more technical follow-up releases. And today, when July has turned into August, we come with another one. It's not a big release. But with the big interest in **Hugo Pipes**, we felt that it was important to get this out there sooner rather than later.
-
-There are two main items in this release:
-
-1. We have added a custom SCSS/SASS import resolver that respects Hugo's project/themes filesystem hierarchy anywhere in `/assets`. Using the LibSass' resolver alone only made this work for the entry folder.
-2. Resources fetched via `resources.Get` and similar are now language agnostic. The thought behind the original implementation was maximum flexibility with support for `assetDir` per language. In practice, this was a bad idea, as it made some CSS imports hard to get working in multilingual setups, and you got duplication of identical content for no good reason, with added processing time.
-
-This release represents **12 contributions by 2 contributors** to the main Hugo code base.
-
-A special thanks in this release goes to [@onedrawingperday](https://github.com/onedrawingperday) for his excellent work maintaining the fast-growing [Themes Site](https://themes.gohugo.io/).
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **5 contributions by 3 contributors**.
-
-Hugo now has:
-
-* 27596+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 442+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 245+ [themes](http://themes.gohugo.io/)
-
-## Notes
-
-* Resources fetched via `resources.Get` and similar are now considered global across languages. If you need, as an example, to create a CSS per language, you need to set the path yourself.
-
-## Enhancements
-
-* Add `templates.Exists` template function. This can be used to check if a template, e.g. a partial, exists in the project or any of the themes in use. [0ba19c57](https://github.com/gohugoio/hugo/commit/0ba19c57f180c33b41c64335ea1d1c89335d34c0) [@bep](https://github.com/bep) [#5010](https://github.com/gohugoio/hugo/issues/5010)
-* Remove superflous loop [0afa2897](https://github.com/gohugoio/hugo/commit/0afa2897a0cf90f4348929ef432202efddc183a0) [@bep](https://github.com/bep)
-* Update Chroma [b5d13ca1](https://github.com/gohugoio/hugo/commit/b5d13ca16bf106c1bc29c2a5295cd231d1bf13fd) [@bep](https://github.com/bep) [#5019](https://github.com/gohugoio/hugo/issues/5019)
-* Make resources fetched via `resources.Get` and similar language agnostic [6b02f5c0](https://github.com/gohugoio/hugo/commit/6b02f5c0f4e0ba1730aebc5a590a111548233bd5) [@bep](https://github.com/bep) [#5017](https://github.com/gohugoio/hugo/issues/5017)
-* Improve SCSS project vs themes import resolution [f219ac09](https://github.com/gohugoio/hugo/commit/f219ac09f6b7e26d84599401512233d77c1bdb4c) [@bep](https://github.com/bep) [#5008](https://github.com/gohugoio/hugo/issues/5008)
-* Improve _ prefix handling in SCSS imports [88e447c4](https://github.com/gohugoio/hugo/commit/88e447c449608523d87c517396bde31a62f392b6) [@bep](https://github.com/bep) [#5008](https://github.com/gohugoio/hugo/issues/5008)
-
-## Fixes
-* Fix file paths for uncached transformed images [b718d743](https://github.com/gohugoio/hugo/commit/b718d743b7a2eff3bea74ced57147825294a629f) [@RJacksonm1](https://github.com/RJacksonm1) [#5012](https://github.com/gohugoio/hugo/issues/5012)
-* Fix image cache eviction for sites with subdir in baseURL [786f7230](https://github.com/gohugoio/hugo/commit/786f72302f65580ca8d1df2132a7756584539ea0) [@bep](https://github.com/bep) [#5006](https://github.com/gohugoio/hugo/issues/5006)
diff --git a/docs/content/en/news/0.47-relnotes/featured-hugo-47-poster.png b/docs/content/en/news/0.47-relnotes/featured-hugo-47-poster.png
deleted file mode 100644
index 601922961a8..00000000000
Binary files a/docs/content/en/news/0.47-relnotes/featured-hugo-47-poster.png and /dev/null differ
diff --git a/docs/content/en/news/0.47-relnotes/index.md b/docs/content/en/news/0.47-relnotes/index.md
deleted file mode 100644
index 79d15ec6299..00000000000
--- a/docs/content/en/news/0.47-relnotes/index.md
+++ /dev/null
@@ -1,79 +0,0 @@
-
----
-date: 2018-08-17
-title: "Output Minification, Live-Reload Fixes and More"
-description: "Hugo 0.47: Adds minification of rendered output, but is mostly a massive bug fix release."
-categories: ["Releases"]
----
-
-Hugo `0.47` is named **Hugo Reloaded**. It adds minification support for the final rendered output (run `hugo --minify`), but it is mostly a bug fix release. And most notably, it fixes a set of issues with live-reloading/partial rebuilds when running `hugo server`. Working with bundles should now be a more pleasant experience, to pick one example.
-
-This release represents **35 contributions by 6 contributors** to the main Hugo code base.
-[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@satotake](https://github.com/satotake), [@anthonyfok](https://github.com/anthonyfok), and [@coliff](https://github.com/coliff) for their ongoing contributions.
-
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) and [@onedrawingperday](https://github.com/onedrawingperday) for their relentless work on keeping the themes site in pristine condition and to [@kaushalmodi](https://github.com/kaushalmodi) for his great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **21 contributions by 10 contributors**. A special thanks to [@bep](https://github.com/bep), [@aapeliv](https://github.com/aapeliv), [@regisphilibert](https://github.com/regisphilibert), and [@brentybh](https://github.com/brentybh) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 27980+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 442+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 251+ [themes](http://themes.gohugo.io/)
-
-## Enhancements
-
-### Templates
-
-* Suppress blank lines from opengraph internal template [c09ee78f](https://github.com/gohugoio/hugo/commit/c09ee78fd235599d3fb794110cd75c024d80cfca) [@anthonyfok](https://github.com/anthonyfok)
-* Add MIME type to embedded JS [755d1ffe](https://github.com/gohugoio/hugo/commit/755d1ffe7a22d8ad83485240ff78cf25d501602f) [@bep](https://github.com/bep) [#5042](https://github.com/gohugoio/hugo/issues/5042)
-* Add `os.Stat` template function [d7112085](https://github.com/gohugoio/hugo/commit/d71120852a8e14d0ea4d24de269fce041ef7b666) [@satotake](https://github.com/satotake)
-
-### Output
-
-* Add support for minification of final output [789ef8c6](https://github.com/gohugoio/hugo/commit/789ef8c639e4621abd36da530bcb5942ac9297da) [@bep](https://github.com/bep) [#1251](https://github.com/gohugoio/hugo/issues/1251)
-
-### Other
-
-* Regenerate CLI docs [4a16b5f4](https://github.com/gohugoio/hugo/commit/4a16b5f4b0adbb31fee611c378de9d5526de2f86) [@bep](https://github.com/bep)
-* Include theme name in version mismatch error [e5052f4e](https://github.com/gohugoio/hugo/commit/e5052f4e09b6df590cddf2f8bc2c834fd3af3082) [@bep](https://github.com/bep) [#5044](https://github.com/gohugoio/hugo/issues/5044)
-* Make the JS minifier matcher less specific [c81fbf46](https://github.com/gohugoio/hugo/commit/c81fbf4625ae7cc7dd3a7a526331ddfdf5237cc6) [@bep](https://github.com/bep) [#5073](https://github.com/gohugoio/hugo/issues/5073)
-* Close file when done [f6ae436c](https://github.com/gohugoio/hugo/commit/f6ae436c5878bafeafa0bb2646a2c9b32c9b4380) [@bep](https://github.com/bep) [#5062](https://github.com/gohugoio/hugo/issues/5062)
-* https links to 3rd party sites [c2a67413](https://github.com/gohugoio/hugo/commit/c2a6741394bc609a663522b245d3d75f0ad17da4) [@coliff](https://github.com/coliff)
-* Update alias.go [06bd0136](https://github.com/gohugoio/hugo/commit/06bd0136419ebd6727635716c7023494cc5a8257) [@coliff](https://github.com/coliff)
-* Remove test debug [fb3cb05c](https://github.com/gohugoio/hugo/commit/fb3cb05cc3dfc50370449f622fb0130ba7e0ced2) [@bep](https://github.com/bep)
-* Update dependencies [d07882df](https://github.com/gohugoio/hugo/commit/d07882dfb76a65cce79aaa6f27df71279cd30600) [@bep](https://github.com/bep)
-* Update Chroma [7f535671](https://github.com/gohugoio/hugo/commit/7f5356717d14079432365974e1424fc4ff5987c9) [@bep](https://github.com/bep) [#5025](https://github.com/gohugoio/hugo/issues/5025)
-* Remove alias of os.Stat [71931b30](https://github.com/gohugoio/hugo/commit/71931b30b1813b146aaa60f5cdab16c0f9ebebdb) [@satotake](https://github.com/satotake)
-* Renmae FileStat Stat [d40116e5](https://github.com/gohugoio/hugo/commit/d40116e5f941e4734ed3bed69dce8ffe11fc76b2) [@satotake](https://github.com/satotake)
-* Reduce allocation in the benchmark itself [a6b1eb1e](https://github.com/gohugoio/hugo/commit/a6b1eb1e9150aa5c1c86fe7424cc4167d6f59a5a) [@bep](https://github.com/bep)
-* Simplify the 0 transformer case [27110133](https://github.com/gohugoio/hugo/commit/27110133ffca05feae2e11a9ff28a9a00f613350) [@bep](https://github.com/bep)
-
-## Fixes
-
-### Templates
-
-* Fix compiling Amber templates that import other templates [37438757](https://github.com/gohugoio/hugo/commit/37438757788d279c839506d54f460b2ab37db164) [@Stebalien](https://github.com/Stebalien)
-* Reimplement the ".Params tolower" template transformer [5c538491](https://github.com/gohugoio/hugo/commit/5c5384916e8f954f3ea66148ecceb3732584588e) [@bep](https://github.com/bep) [#5068](https://github.com/gohugoio/hugo/issues/5068)
-
-### Output
-
-* Fix Resource output in multihost setups [78f8475a](https://github.com/gohugoio/hugo/commit/78f8475a054a6277d37f13329afd240b00dc9408) [@bep](https://github.com/bep) [#5058](https://github.com/gohugoio/hugo/issues/5058)
-
-### Core
-
-* Force render of any changed page, even in Fast Render Mode [22475460](https://github.com/gohugoio/hugo/commit/2247546017c00201d2ce1232dd5303295451f1cc) [@bep](https://github.com/bep) [#5083](https://github.com/gohugoio/hugo/issues/5083)
-* Add configFile(s) back to the watch list after REMOVE event [abc54080](https://github.com/gohugoio/hugo/commit/abc54080ec8c43e8989c081d934b59f0c9570c0b) [@anthonyfok](https://github.com/anthonyfok) [#4701](https://github.com/gohugoio/hugo/issues/4701)
-* Gracefully handle typos in server config when running the server [a655e00d](https://github.com/gohugoio/hugo/commit/a655e00d702dbc20b3961b131b33ab21841b043d) [@bep](https://github.com/bep) [#5081](https://github.com/gohugoio/hugo/issues/5081)
-* Fix shortcode output wrapped in p [78c99463](https://github.com/gohugoio/hugo/commit/78c99463fdd45c91af9933528d12d36a86dc6482) [@gllera](https://github.com/gllera) [#1642](https://github.com/gohugoio/hugo/issues/1642)
-* Adjust tests for shortcode p-issue [baa62d0a](https://github.com/gohugoio/hugo/commit/baa62d0abbbf24a17d0aa800a4bb217f026c49ad) [@bep](https://github.com/bep) [#1642](https://github.com/gohugoio/hugo/issues/1642)
-* Fix image cache-clearing for sub-languages [9d973004](https://github.com/gohugoio/hugo/commit/9d973004f5379cff2adda489566fe40683553c4c) [@bep](https://github.com/bep) [#5084](https://github.com/gohugoio/hugo/issues/5084)
-* Fix error when deleting a bundle in server mode [0a88741f](https://github.com/gohugoio/hugo/commit/0a88741fe85f4f7aedc02ed748dfeb8ccc073dbf) [@bep](https://github.com/bep) [#5077](https://github.com/gohugoio/hugo/issues/5077)
-* Fix Related when called from shortcode [0dd06bda](https://github.com/gohugoio/hugo/commit/0dd06bdac008aa81ec2e8f29ad8110dac0227011) [@bep](https://github.com/bep) [#5071](https://github.com/gohugoio/hugo/issues/5071)
-* Use the interface value when doing Related search [a6f199f7](https://github.com/gohugoio/hugo/commit/a6f199f7a640161333608b4a843d701f7e182829) [@bep](https://github.com/bep) [#5071](https://github.com/gohugoio/hugo/issues/5071)
-* Fix GitInfo when multiple content dirs [2182ecfd](https://github.com/gohugoio/hugo/commit/2182ecfd34a24521bf0e3c939627a55327eb1e19) [@bep](https://github.com/bep) [#5054](https://github.com/gohugoio/hugo/issues/5054)
-* Add multiple content dirs to GitInfo test site [e85833d8](https://github.com/gohugoio/hugo/commit/e85833d868a902840c5ed1c90713256153b2548b) [@bep](https://github.com/bep) [#5054](https://github.com/gohugoio/hugo/issues/5054)
-* Fix "adding a bundle" in server mode [d139a037](https://github.com/gohugoio/hugo/commit/d139a037d98e4b388687eecb7831758412247c58) [@bep](https://github.com/bep) [#5075](https://github.com/gohugoio/hugo/issues/5075)
-* Fix typo [c362634b](https://github.com/gohugoio/hugo/commit/c362634b7d8802ea81b0b4341c800a9f78f7cd7c) [@satotake](https://github.com/satotake)
diff --git a/docs/content/en/news/0.47.1-relnotes/index.md b/docs/content/en/news/0.47.1-relnotes/index.md
deleted file mode 100644
index d35b0fad299..00000000000
--- a/docs/content/en/news/0.47.1-relnotes/index.md
+++ /dev/null
@@ -1,30 +0,0 @@
-
----
-date: 2018-08-20
-title: "Two Bug Fixes"
-description: "Hugo 0.47.1: Two Bug Fixes"
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
- This is a bug-fix release with two fixes.
-
-# Fixes
-
-* Fix .Site.Params case regression [fdff0d3a](https://github.com/gohugoio/hugo/commit/fdff0d3af4670f7079e539fef4b92af2a866d02d) [@bep](https://github.com/bep) [#5094](https://github.com/gohugoio/hugo/issues/5094)
-* Do not strip IE conditional statements [5a0ee2b9](https://github.com/gohugoio/hugo/commit/5a0ee2b934e38d596da0f9742361f81c1221e3d5) [@bep](https://github.com/bep) [#5089](https://github.com/gohugoio/hugo/issues/5089)
-
-## Updates to Linux Snap build
-
-* Remove unused files from Git, Perl, etc. [834617f9](https://github.com/gohugoio/hugo/commit/834617f9f8d870643b2631fe607471c8e2ef2f47) [@anthonyfok](https://github.com/anthonyfok)
-* Add nodejs to allow PostCSS to work [ef20ec1f](https://github.com/gohugoio/hugo/commit/ef20ec1fbaa8f5841b3fbe18978d4d8c19d8fc53) [@anthonyfok](https://github.com/anthonyfok)
-
-
-
-
-
-
-
-
diff --git a/docs/content/en/news/0.48-relnotes/featured-hugo-48-poster.png b/docs/content/en/news/0.48-relnotes/featured-hugo-48-poster.png
deleted file mode 100644
index 7adb0d22ee4..00000000000
Binary files a/docs/content/en/news/0.48-relnotes/featured-hugo-48-poster.png and /dev/null differ
diff --git a/docs/content/en/news/0.48-relnotes/index.md b/docs/content/en/news/0.48-relnotes/index.md
deleted file mode 100644
index 92c765f23c6..00000000000
--- a/docs/content/en/news/0.48-relnotes/index.md
+++ /dev/null
@@ -1,57 +0,0 @@
-
----
-date: 2018-08-29
-title: "This One Goes to 11!"
-description: "With Go 1.11, Hugo finally gets support for variable overwrites in templates!"
-categories: ["Releases"]
----
-
-Hugo `0.48` is built with the brand new Go 1.11. On the technical side this means that Hugo now uses [Go Modules](https://github.com/golang/go/wiki/Modules) for the build. The big new functional thing in Go 1.11 for Hugo is added support for [variable overwrites](https://github.com/golang/go/issues/10608). This means that you can now do this and get the expected result:
-
-```go-html-template
-{{ $var := "Hugo Page" }}
-{{ if .IsHome }}
- {{ $var = "Hugo Home" }}
-{{ end }}
-Var is {{ $var }}
-```
-
-The above may look obvious, but has not been possible until now. In Hugo we have had `.Scratch` as a workaround for this, but Go 1.11 will help clean up a lot of templates.
-
-This release represents **23 contributions by 5 contributors** to the main Hugo code base. [@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@anthonyfok](https://github.com/anthonyfok), [@vsopvsop](https://github.com/vsopvsop), and [@moorereason](https://github.com/moorereason) for their ongoing contributions. And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the themes site in pristine condition and to [@kaushalmodi](https://github.com/kaushalmodi) for his great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **15 contributions by 12 contributors**. A special thanks to [@bep](https://github.com/bep), [@kaushalmodi](https://github.com/kaushalmodi), [@regisphilibert](https://github.com/regisphilibert), and [@anthonyfok](https://github.com/anthonyfok) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 28275+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 441+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 252+ [themes](http://themes.gohugo.io/)
-
-## Enhancements
-
-* Add a test for template variable overwrite [0c8a4154](https://github.com/gohugoio/hugo/commit/0c8a4154838e32a33d34202fd4fa0187aa502190) [@bep](https://github.com/bep)
-* Include language code in REF_NOT_FOUND errors [94d0e79d](https://github.com/gohugoio/hugo/commit/94d0e79d33994b9a9d26a4d020500acdcc71e58c) [@bep](https://github.com/bep) [#5110](https://github.com/gohugoio/hugo/issues/5110)
-* Improve minifier MIME type resolution [ebb56e8b](https://github.com/gohugoio/hugo/commit/ebb56e8bdbfaf4f955326017e40b2805850871e9) [@bep](https://github.com/bep) [#5093](https://github.com/gohugoio/hugo/issues/5093)
-* Update to Go 1.11 [6b9934a2](https://github.com/gohugoio/hugo/commit/6b9934a26615ea614b1774770532cae9762a58d3) [@bep](https://github.com/bep) [#5115](https://github.com/gohugoio/hugo/issues/5115)
-* Set GO111MODULE=on for mage install [c7f05779](https://github.com/gohugoio/hugo/commit/c7f057797ca7bfc781d5a2bbf181bb52360f160f) [@bep](https://github.com/bep) [#5115](https://github.com/gohugoio/hugo/issues/5115)
-* Add instruction to install PostCSS when missing [08d14113](https://github.com/gohugoio/hugo/commit/08d14113b60ff70ffe922e8098e289b099a70e0f) [@anthonyfok](https://github.com/anthonyfok) [#5111](https://github.com/gohugoio/hugo/issues/5111)
-* Update snapcraft build config to Go 1.11 [94d6d678](https://github.com/gohugoio/hugo/commit/94d6d6780fac78e9ed5ed58ecdb9821ad8f0f27c) [@bep](https://github.com/bep) [#5115](https://github.com/gohugoio/hugo/issues/5115)
-* Use Go 1.11 modules with Mage [45c9c45d](https://github.com/gohugoio/hugo/commit/45c9c45d1d0d99443fa6bb524a1265fa9ba95e0e) [@bep](https://github.com/bep) [#5115](https://github.com/gohugoio/hugo/issues/5115)
-* Add go.mod [fce32c07](https://github.com/gohugoio/hugo/commit/fce32c07fb80e9929bc2660cf1e681e93009d24b) [@bep](https://github.com/bep) [#5115](https://github.com/gohugoio/hugo/issues/5115)
-* Update Travis to Go 1.11 and Go 1.10.4 [d32ff16f](https://github.com/gohugoio/hugo/commit/d32ff16fd61f55874e81d73759afa099b8bdcb57) [@bep](https://github.com/bep) [#5115](https://github.com/gohugoio/hugo/issues/5115)
-* Skip installing postcss due to failure on build server [66f688f7](https://github.com/gohugoio/hugo/commit/66f688f7120560ca787c1a23e3e7fbc3aa617956) [@anthonyfok](https://github.com/anthonyfok)
-
-## Fixes
-
-* Keep end tags [e6eda2a3](https://github.com/gohugoio/hugo/commit/e6eda2a370aa1184e0afaf12e95dbd6f8b63ace5) [@vsopvsop](https://github.com/vsopvsop)
-* Fix permissions when creating new folders [f4675fa0](https://github.com/gohugoio/hugo/commit/f4675fa0f0fae2358adfaea49e8da824ee094495) [@bep](https://github.com/bep) [#5128](https://github.com/gohugoio/hugo/issues/5128)
-* Fix handling of taxonomy terms containing slashes [fff13253](https://github.com/gohugoio/hugo/commit/fff132537b4094221f4f099e2251f3cda613060f) [@moorereason](https://github.com/moorereason) [#4090](https://github.com/gohugoio/hugo/issues/4090)
-* Fix build on armv7 [8999de19](https://github.com/gohugoio/hugo/commit/8999de193c18b7aa07b44e5b7d9f443a8572e117) [@caarlos0](https://github.com/caarlos0) [#5101](https://github.com/gohugoio/hugo/issues/5101)
-
-
-
-
-
diff --git a/docs/content/en/news/0.49-relnotes/featured-hugo-49-poster.png b/docs/content/en/news/0.49-relnotes/featured-hugo-49-poster.png
deleted file mode 100644
index 6f0f42ed48c..00000000000
Binary files a/docs/content/en/news/0.49-relnotes/featured-hugo-49-poster.png and /dev/null differ
diff --git a/docs/content/en/news/0.49-relnotes/index.md b/docs/content/en/news/0.49-relnotes/index.md
deleted file mode 100644
index 6bb272c33f4..00000000000
--- a/docs/content/en/news/0.49-relnotes/index.md
+++ /dev/null
@@ -1,120 +0,0 @@
-
----
-date: 2018-09-24
-title: "Hugo 0.49: Directory Based Archetypes"
-description: "Hugo 0.49 brings archetype bundle support and collection goodness."
-categories: ["Releases"]
----
-
-
-Hugo `0.49` brings [directory based archetypes](https://gohugo.io/content-management/archetypes/#directory-based-archetypes) and also improves the language handling in `hugo new`. This should simplify working with [page bundles](https://gohugo.io/content-management/page-bundles/). One example of this would be how you now can create a [new showcase](https://gohugo.io/showcase/template/) for the Hugo web site.
-
-But this release is also about _collections_. Go 1.11 gave us variable overwrite support in Go templates. That made it possible to simplify a lot of template constructs, but it also showed some limitations in Hugo's template functions. So with this release we have:
-
-* added [append](https://gohugo.io/functions/append/) function to append to collections.
-* added [group](https://gohugo.io/functions/group/) to create custom page groups.
-* improved the type support in [slice](https://gohugo.io/functions/slice/).
-
-This release represents **66 contributions by 9 contributors** to the main Hugo code base.
-[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@moorereason](https://github.com/moorereason), [@felicianotech](https://github.com/felicianotech), and [@vdanjean](https://github.com/vdanjean) for their ongoing contributions.
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) and [@onedrawingperday](https://github.com/onedrawingperday) for their relentless work on keeping the themes site in pristine condition and to [@kaushalmodi](https://github.com/kaushalmodi) for his great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **37 contributions by 20 contributors**. A special thanks to [@bep](https://github.com/bep), [@kaushalmodi](https://github.com/kaushalmodi), [@AlexChambers](https://github.com/AlexChambers), and [@shaform](https://github.com/shaform) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 28985+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 441+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 265+ [themes](http://themes.gohugo.io/)
-
-
-## Notes
-
-* Remove deprecated `rssURI` [f1a00b20](https://github.com/gohugoio/hugo/commit/f1a00b2069ede85feb487d29b9f690396e2402c6) [@bep](https://github.com/bep)
-* Remove deprecated flags [df4cbbd3](https://github.com/gohugoio/hugo/commit/df4cbbd3bdc05aa14a67b3a0a29a0db75b82e640) [@bep](https://github.com/bep)
-* Deprecate `Pages.Sort`. Use `.ByWeight` [2e2e34a9](https://github.com/gohugoio/hugo/commit/2e2e34a9350edec0220462aa3d47ecc9d428a0fb) [@bep](https://github.com/bep)
-* When setting `preserveTaxonomyNames` Hugo now _really_ preserves them. Before this release, we would make the first character upper case. If this is the behaviour you want you can use the new `strings.FirstUpper` function.
-
-## Enhancements
-
-### Templates
-
-* Allow `first` function to return an empty slice [cae07ce8](https://github.com/gohugoio/hugo/commit/cae07ce84b3bd4a33fd18b5109a1a3c3dce2191c) [@felicianotech](https://github.com/felicianotech) [#5235](https://github.com/gohugoio/hugo/issues/5235)
-* Use `safeHTMLAttr` instead of `safeHTML` for HTML attributes [4f9c109d](https://github.com/gohugoio/hugo/commit/4f9c109dc5431553e5dbf98e0ed37487c12e8d16) [@felicianotech](https://github.com/felicianotech) [#5236](https://github.com/gohugoio/hugo/issues/5236)
-* Add `collections.Append` (with alias `append`) [e27fd4c1](https://github.com/gohugoio/hugo/commit/e27fd4c1b80b7acb43290ac50e9f140d690cf042) [@bep](https://github.com/bep) [#5190](https://github.com/gohugoio/hugo/issues/5190)
-* Improve type handling in `collections.Slice` [fe6676c7](https://github.com/gohugoio/hugo/commit/fe6676c775b8d917a661238f24fd4a9088f25d50) [@bep](https://github.com/bep) [#5188](https://github.com/gohugoio/hugo/issues/5188)
-* Add `group` template func [6667c6d7](https://github.com/gohugoio/hugo/commit/6667c6d7430acc16b3683fbbacd263f1d00c8672) [@bep](https://github.com/bep) [#4865](https://github.com/gohugoio/hugo/issues/4865)
-* Add `strings.FirstUpper` [e5d66074](https://github.com/gohugoio/hugo/commit/e5d66074ce1ed4e0fe329e3fdef66f8b6fd5dc55) [@bep](https://github.com/bep) [#5174](https://github.com/gohugoio/hugo/issues/5174)
-
-### Core
-
-* Minor cleaning in the sorting code [2eed35c8](https://github.com/gohugoio/hugo/commit/2eed35c826e5de6aae432b36969a28c2ae3e0f02) [@bep](https://github.com/bep)
-* Make sure ambiguous lookups in GetPage gets an error [75e54345](https://github.com/gohugoio/hugo/commit/75e54345f9a7d786bb28af64ad80eb9502fee7c7) [@bep](https://github.com/bep) [#5138](https://github.com/gohugoio/hugo/issues/5138)
-* Allow creating page groups from any page collection [cfda13b3](https://github.com/gohugoio/hugo/commit/cfda13b36367465016f4458ab9924c948ed02b6f) [@vdanjean](https://github.com/vdanjean) [#4865](https://github.com/gohugoio/hugo/issues/4865)
-* Do not FirstUpper taxonomy titles [be3ae3ec](https://github.com/gohugoio/hugo/commit/be3ae3ec92da972a55112af39ce2e1c45121b9a5) [@Schnouki](https://github.com/Schnouki) [#5172](https://github.com/gohugoio/hugo/issues/5172)
-* Simplify some code [e38e8812](https://github.com/gohugoio/hugo/commit/e38e881248b7d20927eab0e56c85732e1acbc45e) [@moorereason](https://github.com/moorereason)
-* Add missing error checks [0665a395](https://github.com/gohugoio/hugo/commit/0665a3951be6ffc2551ef6664856da4cfccd45fa) [@moorereason](https://github.com/moorereason)
-* Remove extraneous createStaticFs call [1ed8c363](https://github.com/gohugoio/hugo/commit/1ed8c363367c2532014154e91eeade9b3a923f91) [@moorereason](https://github.com/moorereason)
-
-### Other
-
-* Add "go mod download" to CI scripts [cf47f43f](https://github.com/gohugoio/hugo/commit/cf47f43ff12ca5f5ea851a8b6761b19b5e4d4eba) [@bep](https://github.com/bep)
-* Regenerate CLI docs [3b6bd121](https://github.com/gohugoio/hugo/commit/3b6bd1210a2792c51c34b9c655cb8b7e9a0f15d7) [@bep](https://github.com/bep)
-* Document group [8388cd90](https://github.com/gohugoio/hugo/commit/8388cd90e89358f73ddcb7f496a1a8dc5c30c36c) [@bep](https://github.com/bep)
-* Make Data.Integrity be of type template.HTMLAttr [fe6a6f27](https://github.com/gohugoio/hugo/commit/fe6a6f2737769070fd64a5192ff685c9c89020bd) [@bep](https://github.com/bep)
-* Add directory based archetypes [2650fa77](https://github.com/gohugoio/hugo/commit/2650fa772b40846d9965f8c5f169286411f3beb2) [@bep](https://github.com/bep) [#4535](https://github.com/gohugoio/hugo/issues/4535)
-* Build on CircleCI outside of GOPATH [ef525b15](https://github.com/gohugoio/hugo/commit/ef525b15d4584886b52428bd7a35de835ab07a48) [@felicianotech](https://github.com/felicianotech) [#5135](https://github.com/gohugoio/hugo/issues/5135)
-* Prevent symbolic links from themes [f9168146](https://github.com/gohugoio/hugo/commit/f9168146978bd970d1f4fb061eff75264af88bb1) [@bep](https://github.com/bep)
-* Update releasenotes_writer.go [4b82f748](https://github.com/gohugoio/hugo/commit/4b82f74848836efbcf453c0122bd35555ee7517d) [@bep](https://github.com/bep)
-* Add docs for append [df50c108](https://github.com/gohugoio/hugo/commit/df50c108ba2f24936eff20b51d23f9328adb2d87) [@bep](https://github.com/bep) [#5190](https://github.com/gohugoio/hugo/issues/5190)
-* Set minifier to KeepEndTags [9b26b548](https://github.com/gohugoio/hugo/commit/9b26b5487b5c5142fe9fb58681fe7d1dac95a291) [@onedrawingperday](https://github.com/onedrawingperday)
-* Make JSON minification more generic [3dafe206](https://github.com/gohugoio/hugo/commit/3dafe206e31bb92f27802a04bf9159cbc20af234) [@zinefer](https://github.com/zinefer)
-* Update Mage [37d64634](https://github.com/gohugoio/hugo/commit/37d6463479952f7dfba59d899eed38b41e223283) [@bep](https://github.com/bep)
-* Update dependencies [bb2fe814](https://github.com/gohugoio/hugo/commit/bb2fe814c2db0c494b3b678a5da20a6cc0538857) [@bep](https://github.com/bep)
-* Improve some godoc comments [30bc4ed0](https://github.com/gohugoio/hugo/commit/30bc4ed0a01f965cc2f9187ccb6ab5d28a3149f6) [@moorereason](https://github.com/moorereason)
-* Update to latest Mage [3b103cb7](https://github.com/gohugoio/hugo/commit/3b103cb7b74228f26af5beb4cefc47edee794ce9) [@bep](https://github.com/bep)
-* Remove some duplicate code [c15c7da4](https://github.com/gohugoio/hugo/commit/c15c7da42a1c7bc535cc16cca2b341526f8cf169) [@bep](https://github.com/bep)
-* Update Dockerfile to Go 1.11 [bcbe57c6](https://github.com/gohugoio/hugo/commit/bcbe57c6e9243cbf3823f11b755f57c091cc1866) [@zyfdegh](https://github.com/zyfdegh) [#5145](https://github.com/gohugoio/hugo/issues/5145)
-* Init packages once [ea8ef573](https://github.com/gohugoio/hugo/commit/ea8ef573c6f869de95fdf4b19765d34026de6471) [@bep](https://github.com/bep)
-* Update script to Go 1.11 [293e1235](https://github.com/gohugoio/hugo/commit/293e12355dd9d9361774f5ab340cd8a03b4828a1) [@bep](https://github.com/bep) [#5127](https://github.com/gohugoio/hugo/issues/5127)
-* Remove the remains of Go Dep [fdf3c3b8](https://github.com/gohugoio/hugo/commit/fdf3c3b8234ed340f40a85fb76d96ae3a9ccf195) [@bep](https://github.com/bep) [#5115](https://github.com/gohugoio/hugo/issues/5115)
-* Update CONTRIBUTING.md [312d2252](https://github.com/gohugoio/hugo/commit/312d2252be6b7bf250fa4f8b1b541fdc13641940) [@bep](https://github.com/bep)
-* Update README.md [f627903e](https://github.com/gohugoio/hugo/commit/f627903efaa1a5f7e137c2d409efd1e1e2db47f6) [@bep](https://github.com/bep)
-* Fix golint issues [400fe96a](https://github.com/gohugoio/hugo/commit/400fe96aee8e38112e347e762661b8389701c938) [@moorereason](https://github.com/moorereason)
-* Fix golint godoc issues [3f45e729](https://github.com/gohugoio/hugo/commit/3f45e729f4e0296bb1a3558d60087bec8321444b) [@moorereason](https://github.com/moorereason)
-* Fix godoc comment [e03eb90a](https://github.com/gohugoio/hugo/commit/e03eb90a366159ed9ef9888246de87f283508866) [@moorereason](https://github.com/moorereason)
-* Fix typo in private func name [c915d0d3](https://github.com/gohugoio/hugo/commit/c915d0d3252007d61b680a388dcbe6b035d0adc8) [@moorereason](https://github.com/moorereason)
-* Fix golint godoc issues [f6f22ad9](https://github.com/gohugoio/hugo/commit/f6f22ad944a1c308fd823792b2fbff1504f42cef) [@moorereason](https://github.com/moorereason)
-* Fix filepath issue in test [d970327d](https://github.com/gohugoio/hugo/commit/d970327d7b994b495ef3bb468c3e0599b0deef5a) [@moorereason](https://github.com/moorereason)
-* Fix golint issues [0013bea9](https://github.com/gohugoio/hugo/commit/0013bea901ee2124f4c18f9728abf47c3880f97d) [@moorereason](https://github.com/moorereason)
-* Fix golint godoc issue [ffaa73dc](https://github.com/gohugoio/hugo/commit/ffaa73dc8aa860edb3476b2a460774071b8470a4) [@moorereason](https://github.com/moorereason)
-* Fix golint godoc issue [f8d8c854](https://github.com/gohugoio/hugo/commit/f8d8c85428f527139c20369910230741dcaf2969) [@moorereason](https://github.com/moorereason)
-* Fix golint issue [10dc87bf](https://github.com/gohugoio/hugo/commit/10dc87bf866f7a4f99c248436c38edf0ecdd157f) [@moorereason](https://github.com/moorereason)
-* Fix golint issues [7231869b](https://github.com/gohugoio/hugo/commit/7231869ba87f4e8d08e94dce18f20b7ed4fa2e15) [@moorereason](https://github.com/moorereason)
-* Fix golint godoc issues [600047ff](https://github.com/gohugoio/hugo/commit/600047ff1cb95d061af1983b9a755157eb4941f8) [@moorereason](https://github.com/moorereason)
-* Fix golint issues [5f2e1cb8](https://github.com/gohugoio/hugo/commit/5f2e1cb8969c2adac6c866b57cc331e1bc16d4e9) [@moorereason](https://github.com/moorereason)
-* Fix golint issues [c8ce6504](https://github.com/gohugoio/hugo/commit/c8ce65046dc7539f3bf5f6dd35fa7ece2bec866d) [@moorereason](https://github.com/moorereason)
-* Fix most golint issues [a53f9623](https://github.com/gohugoio/hugo/commit/a53f962312e273cea9fe460b40655350a82210f2) [@moorereason](https://github.com/moorereason)
-* Fix golint issues [daffeec3](https://github.com/gohugoio/hugo/commit/daffeec30d9d67017ec84064e15fd946b0b0cb0d) [@moorereason](https://github.com/moorereason)
-* Fix golint errors [b8b91f55](https://github.com/gohugoio/hugo/commit/b8b91f550646b2620649c3504e14a441975bea9f) [@moorereason](https://github.com/moorereason)
-* Fix golint issues [f0effac8](https://github.com/gohugoio/hugo/commit/f0effac80426325040c4bc703cd610f434d0b5a8) [@moorereason](https://github.com/moorereason)
-* Fix broken Travis config [2cf8fe2e](https://github.com/gohugoio/hugo/commit/2cf8fe2ea218d37776af72893691e772737750e3) [@bep](https://github.com/bep)
-* Fix error message for go vet [47d4edce](https://github.com/gohugoio/hugo/commit/47d4edce6083bab1c190dad99fefb7c73afc6af8) [@mdhender](https://github.com/mdhender)
-
-
-## Fixes
-
-* Compare every element in pages cache [ed4f1edb](https://github.com/gohugoio/hugo/commit/ed4f1edbd729bf75af89879b76fbad931693cd67) [@bep](https://github.com/bep) [#5239](https://github.com/gohugoio/hugo/issues/5239)
-* Revise error handling in `getJSON` and `getCSV` [43d44652](https://github.com/gohugoio/hugo/commit/43d446522a7c09af4bf6879f93341d8ff62654d1) [@bep](https://github.com/bep) [#5076](https://github.com/gohugoio/hugo/issues/5076)
-* Show error on `union` or `intersect` of uncomparable types [4f72e791](https://github.com/gohugoio/hugo/commit/4f72e79120a4f964330d10c8ebe9aceb2b5761a7) [@moorereason](https://github.com/moorereason) [#3820](https://github.com/gohugoio/hugo/issues/3820)
-* Do not set RSS as Kind in RSS output [555a5612](https://github.com/gohugoio/hugo/commit/555a5612b2641075b3e1b3b7af8ce9b5aba9f200) [@bep](https://github.com/bep) [#5138](https://github.com/gohugoio/hugo/issues/5138)
-
-
-
-
-
-
-
-
diff --git a/docs/content/en/news/0.49.1-relnotes/index.md b/docs/content/en/news/0.49.1-relnotes/index.md
deleted file mode 100644
index a3858a9e16c..00000000000
--- a/docs/content/en/news/0.49.1-relnotes/index.md
+++ /dev/null
@@ -1,22 +0,0 @@
-
----
-date: 2018-10-10
-title: "Hugo 0.49.1: Bug Fix"
-description: "This release fixes an issue where resources.Concat would sometimes fail."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-This is a bug-fix release with 2 related fixes. This was introduced in Hugo 0.49. The most notable error situation was that `resources.Concat` could fail in some situations.
-
-
-* Fix handling of different interface types in Slice [e2201ef1](https://github.com/gohugoio/hugo/commit/e2201ef15fdefe257ad284b2df4ccc8f8c38fac2) [@bep](https://github.com/bep) [#5269](https://github.com/gohugoio/hugo/issues/5269)
-
-* Improve append in Scratch [23f48c30](https://github.com/gohugoio/hugo/commit/23f48c300cb5ffe0fe43c88464f38c68831a17ad) [@bep](https://github.com/bep) [#5275](https://github.com/gohugoio/hugo/issues/5275)
-
-
-
-
-
diff --git a/docs/content/en/news/0.49.2-relnotes/index.md b/docs/content/en/news/0.49.2-relnotes/index.md
deleted file mode 100644
index 1d24cd624a5..00000000000
--- a/docs/content/en/news/0.49.2-relnotes/index.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-date: 2018-10-11
-title: "Hugo 0.49.2: One Bug Fix"
-description: "Fixes a type issue in append and Scratch.Add"
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
----
-
-This fixes one regression from Hugo `0.42.1`:
-
-* Fix type checking in Append [2159d77f](https://github.com/gohugoio/hugo/commit/2159d77f368eb1f78e51dd94133554f88052d85f) [@bep](https://github.com/bep) [#5303](https://github.com/gohugoio/hugo/issues/5303)
-
-
-
-
-
diff --git a/docs/content/en/news/0.50-relnotes/featured-hugo-50-poster.png b/docs/content/en/news/0.50-relnotes/featured-hugo-50-poster.png
deleted file mode 100644
index de5b76d7934..00000000000
Binary files a/docs/content/en/news/0.50-relnotes/featured-hugo-50-poster.png and /dev/null differ
diff --git a/docs/content/en/news/0.50-relnotes/index.md b/docs/content/en/news/0.50-relnotes/index.md
deleted file mode 100644
index 46ab61cd040..00000000000
--- a/docs/content/en/news/0.50-relnotes/index.md
+++ /dev/null
@@ -1,150 +0,0 @@
-
----
-date: 2018-10-29
-title: "Hugo 0.50: Errors so Good, You’ll Want to Fail!"
-description: "Errors with full filename and line and column number, shown in browser. And improved Fast Render Mode …"
-categories: ["Releases"]
----
-
-Hugo `0.50` brings **greatly improved error messages**, and we now also show them in the browser. Having error messages with filename, line- and column number greatly simplify troubleshooting. Many editors (like VS Code) even let you click and navigate directly to the problematic line. If your editor requires a different log format, you can set it via the `HUGO_FILE_LOG_FORMAT` OS environment variable:
-
-
-```bash
-env HUGO_FILE_LOG_FORMAT="\":file::line::col\"" hugo server
-```
-
-But this release isn't all about error handling. Getting line- and column number into "every" error also meant that we had to consolidate and simplify some code paths, which, as a nice side effect, made Hugo a little bit faster. Benchmarks show it running **about 5% faster and consume about 8% less memory**.
-
-Also, we have now implemented **"render on demand"** in Hugo's Fast Render Mode (default when running `hugo server`). This means that you should now always see updated content when navigating around the site after a change.
-
-This release represents **88 contributions by 14 contributors** to the main Hugo code base.
-[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@moorereason](https://github.com/moorereason), [@anthonyfok](https://github.com/anthonyfok), and [@GregorioMartinez](https://github.com/GregorioMartinez) for their ongoing contributions.
-
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) and [@onedrawingperday](https://github.com/onedrawingperday) for their relentless work on keeping the themes site in pristine condition and to [@kaushalmodi](https://github.com/kaushalmodi) for his great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **14 contributions by 9 contributors**. A special thanks to [@bep](https://github.com/bep), [@NotWoods](https://github.com/NotWoods), [@Nick-Rivera](https://github.com/Nick-Rivera), and [@tomanistor](https://github.com/tomanistor) for their work on the documentation site.
-
-Hugo now has:
-
-* 29842+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 441+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 275+ [themes](http://themes.gohugo.io/)
-
-## Notes
-
-* You should not get stale content in Fast Render Mode anymore.
-* Errors will now show up in the browser by default, turn it off by running `hugo server --disableBrowserError`
-* `jsonify` will now produce pretty/indented output
-
-## Enhancements
-
-### Templates
-
-* Handle truncated identifiers in Go template errors [2d7709d1](https://github.com/gohugoio/hugo/commit/2d7709d15584e4c11138cd7fe92717a2a58e4585) [@bep](https://github.com/bep) [#5346](https://github.com/gohugoio/hugo/issues/5346)
-* Update Jsonify to return pretty-print output [5a52cd5f](https://github.com/gohugoio/hugo/commit/5a52cd5f920bb3d067ab1682adece9f813c67ba1) [@SeanPrashad](https://github.com/SeanPrashad) [#5040](https://github.com/gohugoio/hugo/issues/5040)
-* Improve the Execute panic error message [0fe4ff18](https://github.com/gohugoio/hugo/commit/0fe4ff18751156fa072e1f83077e49a8597e7dcd) [@bep](https://github.com/bep) [#5327](https://github.com/gohugoio/hugo/issues/5327)
-* Use .Lastmod in embedded schema template [c21e5179](https://github.com/gohugoio/hugo/commit/c21e5179ce9a370c416c01fbe9472be1fb5c6650) [@akshaybabloo](https://github.com/akshaybabloo) [#5320](https://github.com/gohugoio/hugo/issues/5320)
-* Cast IsSet key to int for indexed types [0d5110d0](https://github.com/gohugoio/hugo/commit/0d5110d03324380cb4a288d3fa08c1b86ba227da) [@moorereason](https://github.com/moorereason) [#3681](https://github.com/gohugoio/hugo/issues/3681)
-* Add a delimiter parameter to lang.NumFmt [ce264b93](https://github.com/gohugoio/hugo/commit/ce264b936ce9f589bd889e18762881cff8bc9cd0) [@moorereason](https://github.com/moorereason) [#5260](https://github.com/gohugoio/hugo/issues/5260)
-
-### Core
-
-* Adjust error test to make it pass on Go tip [acc14b46](https://github.com/gohugoio/hugo/commit/acc14b4646d849e09e8da37552d4f4f777d0fce2) [@bep](https://github.com/bep)
-* Rename some page_* files [e3ed4a83](https://github.com/gohugoio/hugo/commit/e3ed4a83b8e92ce9bf070f7b41780798b006e848) [@bep](https://github.com/bep)
-* Get file context in "config parse failed" errors [ed7b3e26](https://github.com/gohugoio/hugo/commit/ed7b3e261909fe425ef64216f12806840c45b205) [@bep](https://github.com/bep) [#5325](https://github.com/gohugoio/hugo/issues/5325)
-* Improve errors in /i18n handlling [2bf686ee](https://github.com/gohugoio/hugo/commit/2bf686ee217808186385bfcf6156f15bbdb33651) [@bep](https://github.com/bep) [#5324](https://github.com/gohugoio/hugo/issues/5324)
-* Improve errors in /data handlling [9f74dc2a](https://github.com/gohugoio/hugo/commit/9f74dc2a52b6f568b5a060b7a4be47196804b01f) [@bep](https://github.com/bep) [#5324](https://github.com/gohugoio/hugo/issues/5324)
-* Continue the file context/line number errors work [d1661b82](https://github.com/gohugoio/hugo/commit/d1661b823af25c50d3bbe5366ea40a3cdd52e237) [@bep](https://github.com/bep) [#5324](https://github.com/gohugoio/hugo/issues/5324)
-* Remove the now superflous Source struct [7930d213](https://github.com/gohugoio/hugo/commit/7930d2132a3c36c1aaca20f16f56978c84656b0a) [@bep](https://github.com/bep) [#5324](https://github.com/gohugoio/hugo/issues/5324)
-* Redo the summary delimiter logic [44da60d8](https://github.com/gohugoio/hugo/commit/44da60d869578423dea529db62ed613588a2a560) [@bep](https://github.com/bep) [#5324](https://github.com/gohugoio/hugo/issues/5324)
-* Integrate new page parser [1e3e3400](https://github.com/gohugoio/hugo/commit/1e3e34002dae3d4a980141efcc86886e7de5bef8) [@bep](https://github.com/bep) [#5324](https://github.com/gohugoio/hugo/issues/5324)
-* Use []byte in shortcode parsing [1b7ecfc2](https://github.com/gohugoio/hugo/commit/1b7ecfc2e176315b69914756c70b46306561e4d1) [@bep](https://github.com/bep) [#5324](https://github.com/gohugoio/hugo/issues/5324)
-* Use stdlib context package [4b4af2c5](https://github.com/gohugoio/hugo/commit/4b4af2c52e658d516dd4bfaf59fef4f054dabec3) [@GregorioMartinez](https://github.com/GregorioMartinez)
-* Normalize permalink path segments [fae48d74](https://github.com/gohugoio/hugo/commit/fae48d7457de96969ec53349194dcbfa45adc269) [@moorereason](https://github.com/moorereason) [#5223](https://github.com/gohugoio/hugo/issues/5223)[#4926](https://github.com/gohugoio/hugo/issues/4926)
-* Improve error message for bad taxonomy weights [d3b81ee5](https://github.com/gohugoio/hugo/commit/d3b81ee58e8fd3a0ab8265a2898d66cbcdf6a7c1) [@moorereason](https://github.com/moorereason)
-* Cast taxonomy weight parameters to int [1fd30d46](https://github.com/gohugoio/hugo/commit/1fd30d462ee7f67fde6f29d170af1d225258322b) [@moorereason](https://github.com/moorereason) [#4628](https://github.com/gohugoio/hugo/issues/4628)
-* Allow nil to be unwrapped as *Page [498d6299](https://github.com/gohugoio/hugo/commit/498d6299581bead0f582431b8133d8b5f8760618) [@moorereason](https://github.com/moorereason) [#5043](https://github.com/gohugoio/hugo/issues/5043)
-* Be a litle more specific in NextPage TODO [fb732d53](https://github.com/gohugoio/hugo/commit/fb732d5322381ee7f3a849258419cef7bbf1487b) [@bep](https://github.com/bep)
-* Introduce Page.NextPage and Page.PrevPage [ad705aac](https://github.com/gohugoio/hugo/commit/ad705aac0649fa3102f7639bc4db65d45e108ee2) [@felicianotech](https://github.com/felicianotech) [#1061](https://github.com/gohugoio/hugo/issues/1061)
-
-### Other
-
-* Update go.sum [7082a5d1](https://github.com/gohugoio/hugo/commit/7082a5d14382acfc300ae4f66d07a14100e2358c) [@bep](https://github.com/bep)
-* Update minify [aa281b51](https://github.com/gohugoio/hugo/commit/aa281b5135db2e84b9e21b5f38a6cb63cf3ab158) [@bep](https://github.com/bep) [#5261](https://github.com/gohugoio/hugo/issues/5261)
-* Regenerate CLI docs [32501987](https://github.com/gohugoio/hugo/commit/325019872467ee152ea56329a796acf35dec6fb3) [@bep](https://github.com/bep)
-* Make sure the global logger also gets colored labels [9c88a8a5](https://github.com/gohugoio/hugo/commit/9c88a8a55adf7779039504fa77d74ec80d658c40) [@bep](https://github.com/bep) [#4414](https://github.com/gohugoio/hugo/issues/4414)
-* Avoid using the global logger [95e72f5e](https://github.com/gohugoio/hugo/commit/95e72f5e8e4634fbbb2ea7ece2156487230ad1d4) [@bep](https://github.com/bep) [#4414](https://github.com/gohugoio/hugo/issues/4414)
-* Add color to ERROR and WARN [1c7b7b4e](https://github.com/gohugoio/hugo/commit/1c7b7b4ef293aa133e5b55f3ebb2d37d8839532f) [@bep](https://github.com/bep) [#4414](https://github.com/gohugoio/hugo/issues/4414)
-* Make the file error log format configurable [1ad117cb](https://github.com/gohugoio/hugo/commit/1ad117cbe2903aa9d029f90750acf633eb2a51a2) [@bep](https://github.com/bep) [#5352](https://github.com/gohugoio/hugo/issues/5352)
-* Allow a mix of slice types in append/Scratch.Add [dac7092a](https://github.com/gohugoio/hugo/commit/dac7092a9cb22d59db28fb15af15f7b14ff47588) [@bep](https://github.com/bep) [#5361](https://github.com/gohugoio/hugo/issues/5361)
-* Allow .Data.Integrity to be accessed on its own [b27ccf34](https://github.com/gohugoio/hugo/commit/b27ccf34bf4e5ee618a66fa11c68a9690e395034) [@bep](https://github.com/bep) [#5296](https://github.com/gohugoio/hugo/issues/5296)
-* Update minify [83c873ff](https://github.com/gohugoio/hugo/commit/83c873ff37ddd379181540021232f026e7678486) [@bep](https://github.com/bep) [#5261](https://github.com/gohugoio/hugo/issues/5261)
-* Update cast [a2440dc0](https://github.com/gohugoio/hugo/commit/a2440dc0e2d46ef774305cd5e4fea5ff2bdd5f11) [@bep](https://github.com/bep) [#5340](https://github.com/gohugoio/hugo/issues/5340)
-* Truncate the error log on repeated config errors [1e9ac3dc](https://github.com/gohugoio/hugo/commit/1e9ac3dcc21e8f78d3f0a0ba4f35f6c142dfa6bc) [@bep](https://github.com/bep)
-* Regenerate CLI docs [40e99672](https://github.com/gohugoio/hugo/commit/40e99672b6f697a614485aace07ca84268f6c787) [@bep](https://github.com/bep) [#5354](https://github.com/gohugoio/hugo/issues/5354)
-* Serialize image processing [3a3badfd](https://github.com/gohugoio/hugo/commit/3a3badfd1d4b1d4c9863ecaf029512d36136fa0f) [@bep](https://github.com/bep) [#5220](https://github.com/gohugoio/hugo/issues/5220)
-* Only show Ansi escape codes if in a terminal [df021317](https://github.com/gohugoio/hugo/commit/df021317a964a482cd1cd579de5a12d50faf0d08) [@bep](https://github.com/bep)
-* Read disableFastRender from flag even if it's not changed [78a4c2e3](https://github.com/gohugoio/hugo/commit/78a4c2e32ef9ea8e92bb7bb3586e4c22b02eb494) [@bep](https://github.com/bep) [#5353](https://github.com/gohugoio/hugo/issues/5353)
-* Use overflow-x: auto; for browser errors [d4ebfea1](https://github.com/gohugoio/hugo/commit/d4ebfea1fffdc35059f42a46387e0aaf0ea877d2) [@bep](https://github.com/bep)
-* Remove the ANSI color for the browser error version [93aa6261](https://github.com/gohugoio/hugo/commit/93aa6261b4fc8caa74afef97b6304ea35dfd7d0e) [@bep](https://github.com/bep)
-* Add some color to the relevant filenames in terminal log [deff9e15](https://github.com/gohugoio/hugo/commit/deff9e154bc0371af56741ddb22cb1f9e392838a) [@bep](https://github.com/bep) [#5344](https://github.com/gohugoio/hugo/issues/5344)
-* Run gofmt -s [889aca05](https://github.com/gohugoio/hugo/commit/889aca054a267506a1c7cfaa3992d324764d6358) [@bep](https://github.com/bep)
-* Resolve error handling/parser related TODOs [6636cf1b](https://github.com/gohugoio/hugo/commit/6636cf1bea77d20ef2a72a45fae59ac402fb133b) [@bep](https://github.com/bep) [#5324](https://github.com/gohugoio/hugo/issues/5324)
-* Improve handling of JSON errors [f669ef6b](https://github.com/gohugoio/hugo/commit/f669ef6bec25155d015b6ab231c53caef4fa5cdc) [@bep](https://github.com/bep) [#5324](https://github.com/gohugoio/hugo/issues/5324)
-* Convert the rest to new page parser code paths [eb038cfa](https://github.com/gohugoio/hugo/commit/eb038cfa0a8ada29dfcba1204ec5c432da9ed7e0) [@bep](https://github.com/bep) [#5324](https://github.com/gohugoio/hugo/issues/5324)
-* Consolidate the metadata decoders [129c27ee](https://github.com/gohugoio/hugo/commit/129c27ee6e9fed98dbfebeaa272fd52757b475b2) [@bep](https://github.com/bep) [#5324](https://github.com/gohugoio/hugo/issues/5324)
-* Use []byte in page lexer [27f5a906](https://github.com/gohugoio/hugo/commit/27f5a906a2a34e3b8348c8baeea48355352b5bbb) [@bep](https://github.com/bep) [#5324](https://github.com/gohugoio/hugo/issues/5324)
-* Add front matter etc. support [2fdc4a24](https://github.com/gohugoio/hugo/commit/2fdc4a24d5450a98cf38a4456e8e0e8e97a3343d) [@bep](https://github.com/bep) [#5324](https://github.com/gohugoio/hugo/issues/5324)
-* File renames and splitting [f6863e1e](https://github.com/gohugoio/hugo/commit/f6863e1ef725f654a4c869ef4955f9add6908a46) [@bep](https://github.com/bep) [#5324](https://github.com/gohugoio/hugo/issues/5324)
-* Move the shortcode parser to the new pageparser package [d6c16afd](https://github.com/gohugoio/hugo/commit/d6c16afde0ce62cfea73447f30d6ed2b8ef4b411) [@bep](https://github.com/bep) [#5324](https://github.com/gohugoio/hugo/issues/5324)
-* Avoid panic in error handler on config errors [6f3716dc](https://github.com/gohugoio/hugo/commit/6f3716dc22e373097a38d053f5415feca602f330) [@bep](https://github.com/bep)
-* Prevent stale content in Fast Render Mode [4a366fcf](https://github.com/gohugoio/hugo/commit/4a366fcfee24b3a5a5045b16c3b87b76147adf5e) [@bep](https://github.com/bep) [#5281](https://github.com/gohugoio/hugo/issues/5281)
-* Allow date and slug from filename for leaf bundles [1f42e47e](https://github.com/gohugoio/hugo/commit/1f42e47e475c0cd684426dd230de411d4c385a3c) [@Japanuspus](https://github.com/Japanuspus) [#4558](https://github.com/gohugoio/hugo/issues/4558)
-* Show server error info in browser [35fbfb19](https://github.com/gohugoio/hugo/commit/35fbfb19a173b01bc881f2bbc5d104136633a7ec) [@bep](https://github.com/bep) [#5284](https://github.com/gohugoio/hugo/issues/5284)[#5290](https://github.com/gohugoio/hugo/issues/5290)[#5325](https://github.com/gohugoio/hugo/issues/5325)[#5324](https://github.com/gohugoio/hugo/issues/5324)
-* Add .gitignore "hugo new site" [92979d92](https://github.com/gohugoio/hugo/commit/92979d92889ff8991acfadd42982c2e55d01b163) [@napei](https://github.com/napei)
-* Optimize integrity string generation [0a3340e9](https://github.com/gohugoio/hugo/commit/0a3340e95254597bc8a9feb250f2733b7d51edf8) [@moorereason](https://github.com/moorereason)
-* Add help text to "hugo new" [6b21ac3e](https://github.com/gohugoio/hugo/commit/6b21ac3e67cb101255e8c3d9dbf076391a9eed8d) [@elliotforbes](https://github.com/elliotforbes)
-* Set "extended" tag based on build_url on Launchpad [d1442053](https://github.com/gohugoio/hugo/commit/d14420539ac04d31dde2252eee66d7e4c7749780) [@anthonyfok](https://github.com/anthonyfok)
-* Call rst2html directly on *nix [3d4a9882](https://github.com/gohugoio/hugo/commit/3d4a9882bfc81215fb4f9eba8859324958747d4a) [@shreyanshk](https://github.com/shreyanshk)
-* Update URLs to stop 301 redirects [bdca9727](https://github.com/gohugoio/hugo/commit/bdca9727944e4cbb5a9372a8404e948ffea7c31c) [@benabbottnz](https://github.com/benabbottnz)
-* Merge branch 'release-0.49.2' [604ddb90](https://github.com/gohugoio/hugo/commit/604ddb90c5d6f1ca5583be1ec0ea8e48f014741a) [@bep](https://github.com/bep)
-* Release 0.49.2 [c397f2c0](https://github.com/gohugoio/hugo/commit/c397f2c08087cf4cda3abe2d146e30f58d6d3216) [@bep](https://github.com/bep)
-* Merge branch 'release-0.49.1' [3583dd6d](https://github.com/gohugoio/hugo/commit/3583dd6d713c243808b5e8724b32565ceaf66104) [@bep](https://github.com/bep)
-* Relase 0.49.1 [235acf22](https://github.com/gohugoio/hugo/commit/235acf22321475895442ce49ca5d16be273c1e1f) [@bep](https://github.com/bep)
-* Improve append in Scratch [23f48c30](https://github.com/gohugoio/hugo/commit/23f48c300cb5ffe0fe43c88464f38c68831a17ad) [@bep](https://github.com/bep) [#5275](https://github.com/gohugoio/hugo/issues/5275)
-* Add GOPATH Hugo building tip [b5e17f7c](https://github.com/gohugoio/hugo/commit/b5e17f7c837ce796e1094c8033fa7084510402fb) [@bep](https://github.com/bep)
-* Consolidate MakeSegment vs MakePathSanitized [e421696d](https://github.com/gohugoio/hugo/commit/e421696d02bfb8764ae57238e211ce0e85e9782e) [@bep](https://github.com/bep) [#4926](https://github.com/gohugoio/hugo/issues/4926)
-* Render Markdown in figure shortcode "caption" and "attr" params [68181703](https://github.com/gohugoio/hugo/commit/6818170308994b5f01dec7a559f92d6c7c5ca100) [@kaushalmodi](https://github.com/kaushalmodi)
-* Re-organize the figure shortcode for better readability [c5279064](https://github.com/gohugoio/hugo/commit/c5279064df9664d6b2ad277e2fba1e4bb3b0f4be) [@kaushalmodi](https://github.com/kaushalmodi)
-* Update README & CONTRIBUTING [152cffb1](https://github.com/gohugoio/hugo/commit/152cffb13a237651c2277dc6c2c9e4172d58b3df) [@GregorioMartinez](https://github.com/GregorioMartinez)
-* Add custom x-nodejs plugin to support ppc64el and s390x [91f49c07](https://github.com/gohugoio/hugo/commit/91f49c0700dde13e16f42c745584a0bef60c6fe2) [@anthonyfok](https://github.com/anthonyfok)
-* Fetch mage with GO111MODULE=off [a475bf12](https://github.com/gohugoio/hugo/commit/a475bf125cd76dacc1bf7ccbcc263a7b59efc510) [@anthonyfok](https://github.com/anthonyfok)
-* Use build-snaps instead of building go from source [fa873a6c](https://github.com/gohugoio/hugo/commit/fa873a6cb3f0fa81002fcd725ecd52fc4b9df48f) [@anthonyfok](https://github.com/anthonyfok)
-* Skip "mage -v test" due to build failure on Launchpad [52ac85fb](https://github.com/gohugoio/hugo/commit/52ac85fbc4d4066b5e13df454593597df0166262) [@anthonyfok](https://github.com/anthonyfok)
-* Move snapcraft.yaml to snap/snapcraft.yaml [27d42111](https://github.com/gohugoio/hugo/commit/27d4211187d4617f4b3afa970f91349567886748) [@anthonyfok](https://github.com/anthonyfok)
-* Update the temp docker script [48413d76](https://github.com/gohugoio/hugo/commit/48413d76f44ecfc9b90f9df63974080f6b285667) [@bep](https://github.com/bep)
-
-## Fixes
-
-### Templates
-
-* Fix baseof.html in error message [646a52a5](https://github.com/gohugoio/hugo/commit/646a52a5c5f52890f2d0270b68ba0f863047484a) [@bep](https://github.com/bep) [#5288](https://github.com/gohugoio/hugo/issues/5288)
-* Fix handling of different interface types in Slice [10ac2ec4](https://github.com/gohugoio/hugo/commit/10ac2ec4466090957e1f6897906ddeb1e0b13673) [@bep](https://github.com/bep) [#5269](https://github.com/gohugoio/hugo/issues/5269)
-
-### Core
-
-* Fix test on Windows [083311d0](https://github.com/gohugoio/hugo/commit/083311d0336ced35909b3375950f7817ecf95ed0) [@bep](https://github.com/bep)
-* Fix FuzzyWordCount test error message [06d28a46](https://github.com/gohugoio/hugo/commit/06d28a464d086880f52dd850f91e668ab957b31f) [@GregorioMartinez](https://github.com/GregorioMartinez)
-
-### Other
-
-* Fix archetype handling of directories in theme [78578632](https://github.com/gohugoio/hugo/commit/78578632f545283741a01f024a6ccedc0b695a30) [@bep](https://github.com/bep) [#5318](https://github.com/gohugoio/hugo/issues/5318)
-* Simple doc fix in CONTRIBUTING.md [3a308912](https://github.com/gohugoio/hugo/commit/3a3089121b852332b5744d1f566959c8cf93cef4) [@krisbudhram](https://github.com/krisbudhram)
-* Fix type checking in Append [2159d77f](https://github.com/gohugoio/hugo/commit/2159d77f368eb1f78e51dd94133554f88052d85f) [@bep](https://github.com/bep) [#5303](https://github.com/gohugoio/hugo/issues/5303)
-* Fix go plugin build failure by renaming go.mod [3033a9a3](https://github.com/gohugoio/hugo/commit/3033a9a37eb66c08e60f9fe977f29d22bd646857) [@anthonyfok](https://github.com/anthonyfok)
-
-
-
-
-
diff --git a/docs/content/en/news/0.51-relnotes/featured-hugo-51-poster.png b/docs/content/en/news/0.51-relnotes/featured-hugo-51-poster.png
deleted file mode 100644
index 07755a1ab93..00000000000
Binary files a/docs/content/en/news/0.51-relnotes/featured-hugo-51-poster.png and /dev/null differ
diff --git a/docs/content/en/news/0.51-relnotes/index.md b/docs/content/en/news/0.51-relnotes/index.md
deleted file mode 100644
index 8590a422cab..00000000000
--- a/docs/content/en/news/0.51-relnotes/index.md
+++ /dev/null
@@ -1,102 +0,0 @@
-
----
-date: 2018-11-07
-title: "Hugo 0.51: The 30K Stars Edition!"
-description: "Bug fixes, new template functions and more error improvements."
-categories: ["Releases"]
----
-
-Hugo reached [30 000 stars on GitHub](https://github.com/gohugoio/hugo/stargazers) this week, which is a good occasion to do a follow-up release of the great Hugo `0.50`. This is mostly a bug fix release, but it also adds some useful new functionality, two examples are the new template funcs [complement](https://gohugo.io/functions/complement/) and [symdiff](https://gohugo.io/functions/symdiff/). This release also continues the work on improving Hugo's error messages. And with `.Position` now available on shortcodes, you can also improve your own error messages inside your custom shortcodes:
-
-
-```bash
-{{ with .Get "name" }}
-{{ else }}
-{{ errorf "missing value for param 'name': %s" .Position }}
-{{ end }}
-```
-
-When the above fails, you will see an `ERROR` log similar to the below:
-
-```bash
-ERROR 2018/11/07 10:05:55 missing value for param name: "/sites/hugoDocs/content/en/variables/shortcodes.md:32:1"
-```
-
-This release represents **31 contributions by 5 contributors** to the main Hugo code base.
-[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@krisbudhram](https://github.com/krisbudhram), [@LorenzCK](https://github.com/LorenzCK), and [@coliff](https://github.com/coliff) for their ongoing contributions.
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) and [@onedrawingperday](https://github.com/onedrawingperday) for their relentless work on keeping the themes site in pristine condition and to [@kaushalmodi](https://github.com/kaushalmodi) for his great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **6 contributions by 5 contributors**. A special thanks to [@ikemo3](https://github.com/ikemo3), [@maiki](https://github.com/maiki), [@morya](https://github.com/morya), and [@regisphilibert](https://github.com/regisphilibert) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 30095+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 441+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 276+ [themes](http://themes.gohugo.io/)
-
-
-## Notes
-
-* Remove deprecated useModTimeAsFallback [0bc4b024](https://github.com/gohugoio/hugo/commit/0bc4b0246dd6b7d71f8676a52644077a4f70ec8f) [@bep](https://github.com/bep)
-* Bump to ERROR for the deprecated Pages.Sort [faeb55c1](https://github.com/gohugoio/hugo/commit/faeb55c1d827f0ea994551a103ff4f7448786d39) [@bep](https://github.com/bep)
-* Deprecate .Site.Ref and .Site.RelRef [6c6a6c87](https://github.com/gohugoio/hugo/commit/6c6a6c87ec2b5ac7342e268ab47861429230f7f4) [@bep](https://github.com/bep) [#5386](https://github.com/gohugoio/hugo/issues/5386)
-
-## Enhancements
-
-### Templates
-
-* Properly handle pointer types in complement/symdiff [79a06aa4](https://github.com/gohugoio/hugo/commit/79a06aa4b64b526c242dfa41f2c7bc24e1352d5b) [@bep](https://github.com/bep)
-* Add collections.SymDiff [488776b6](https://github.com/gohugoio/hugo/commit/488776b6498d1377718133d42daa87ce1236215d) [@bep](https://github.com/bep) [#5410](https://github.com/gohugoio/hugo/issues/5410)
-* Add collections.Complement [42d8dfc8](https://github.com/gohugoio/hugo/commit/42d8dfc8c88af03ea926a59bc2332acc70cca5f6) [@bep](https://github.com/bep) [#5400](https://github.com/gohugoio/hugo/issues/5400)
-
-### Core
-
-* Improve error message on duplicate menu items [3a44920e](https://github.com/gohugoio/hugo/commit/3a44920e79ef86003555d8a4860c29257b2914f0) [@bep](https://github.com/bep)
-* Add .Position to shortcode [33a7b36f](https://github.com/gohugoio/hugo/commit/33a7b36fd42ee31dd79115ec6639bed24247332f) [@bep](https://github.com/bep) [#5371](https://github.com/gohugoio/hugo/issues/5371)
-
-### Other
-
-* Document shortcode error handling [e456e34b](https://github.com/gohugoio/hugo/commit/e456e34bdbde058243eb0a5d3c0017748639e08e) [@bep](https://github.com/bep)
-* Document symdiff [5d14d04a](https://github.com/gohugoio/hugo/commit/5d14d04ac678ad24e4946ed2a581ab71b3834def) [@bep](https://github.com/bep)
-* Document complement [ddcb4028](https://github.com/gohugoio/hugo/commit/ddcb402859b50193bfd6d8b752b568d26d14f603) [@bep](https://github.com/bep)
-* Update minify [d212f609](https://github.com/gohugoio/hugo/commit/d212f60949b6afefbe5aa79394f98dbddf7be068) [@bep](https://github.com/bep)
-* Re-generate CLI docs [2998fa0c](https://github.com/gohugoio/hugo/commit/2998fa0cd5bad161b9c802d2409d8c9c81155011) [@bep](https://github.com/bep)
-* Add --minify to hugo server [5b1edd28](https://github.com/gohugoio/hugo/commit/5b1edd281a493bdb27af4dc3c8fae7e10dd54830) [@bep](https://github.com/bep)
-* Make WARN the new default log log level [4b7d3e57](https://github.com/gohugoio/hugo/commit/4b7d3e57a40214a1269eda59731aa22a8f4463dd) [@bep](https://github.com/bep) [#5203](https://github.com/gohugoio/hugo/issues/5203)
-* Regenerate the docs helper [486bc46a](https://github.com/gohugoio/hugo/commit/486bc46a5217a9d70fe0d14ab9261d7b4eb026d6) [@bep](https://github.com/bep)
-* Skip watcher event files if matched in ignoreFiles [f8446188](https://github.com/gohugoio/hugo/commit/f8446188dbec8378f34f0fea39161a49fcc46083) [@krisbudhram](https://github.com/krisbudhram)
-* Update Chroma [d523aa4b](https://github.com/gohugoio/hugo/commit/d523aa4bb03e913f55c2f89544e6112e320c975a) [@bep](https://github.com/bep) [#5392](https://github.com/gohugoio/hugo/issues/5392)
-* Add file (line/col) info to ref/relref errors [1d18eb05](https://github.com/gohugoio/hugo/commit/1d18eb0574a57c3e9f468659d076a666a3dd76f2) [@bep](https://github.com/bep) [#5371](https://github.com/gohugoio/hugo/issues/5371)
-* Improve log color regexp [d3a98325](https://github.com/gohugoio/hugo/commit/d3a98325c31d7f02f0762e589a4986e55b2a0da2) [@bep](https://github.com/bep)
-* Correct minor typo (#5372) [e65268f2](https://github.com/gohugoio/hugo/commit/e65268f2c2dd5ac54681d3266564901d99ed3ea3) [@coliff](https://github.com/coliff)
-
-## Fixes
-
-### Templates
-
-* Fix the docshelper [61f210dd](https://github.com/gohugoio/hugo/commit/61f210dd7abe5de77c27dc6a6995a3ad5e77afa1) [@bep](https://github.com/bep)
-* Fix BOM issue in templates [3a786a24](https://github.com/gohugoio/hugo/commit/3a786a248d3eff6e732aa94e87d6e88196e5147a) [@bep](https://github.com/bep) [#4895](https://github.com/gohugoio/hugo/issues/4895)
-
-### Output
-
-* Fix ANSI character output regression on Windows [b8725f51](https://github.com/gohugoio/hugo/commit/b8725f5181f6a2709274a82c1c3fdfd8f2e3e28c) [@LorenzCK](https://github.com/LorenzCK) [#5377](https://github.com/gohugoio/hugo/issues/5377)
-
-### Core
-
-* Fix changing paginators in lazy render [b8b8436f](https://github.com/gohugoio/hugo/commit/b8b8436fcca17c152e94cae2a1acad32efc3946c) [@bep](https://github.com/bep) [#5406](https://github.com/gohugoio/hugo/issues/5406)
-* Fix REF_NOT_FOUND logging to include page path [6180c85f](https://github.com/gohugoio/hugo/commit/6180c85fb8f95e01446b74c50cab3f0480305fe4) [@bep](https://github.com/bep) [#5371](https://github.com/gohugoio/hugo/issues/5371)
-* Fix broken manual summary handling [b2a676f5](https://github.com/gohugoio/hugo/commit/b2a676f5f09a3eea360887b099b9d5fc25a88492) [@bep](https://github.com/bep) [#5381](https://github.com/gohugoio/hugo/issues/5381)
-* Fix deadlock when content building times out [729593c8](https://github.com/gohugoio/hugo/commit/729593c842794eaf7127050953a5c2256d332051) [@bep](https://github.com/bep) [#5375](https://github.com/gohugoio/hugo/issues/5375)
-
-### Other
-
-* Fix spelling [47506d16](https://github.com/gohugoio/hugo/commit/47506d164467eb7ddbcada81b767d8df5f9c8786) [@qeesung](https://github.com/qeesung)
-* Fix shortcode directly following a shortcode delimiter [d16a7a33](https://github.com/gohugoio/hugo/commit/d16a7a33ff1f22b9fa357189a901a4f1de4e65e7) [@bep](https://github.com/bep) [#5402](https://github.com/gohugoio/hugo/issues/5402)
-* Fix recently broken error template [2bd9d909](https://github.com/gohugoio/hugo/commit/2bd9d9099db267831731ed2d2200eb09305df9fc) [@bep](https://github.com/bep)
-
-
-
-
-
diff --git a/docs/content/en/news/0.52-relnotes/featured-hugo-52-poster.png b/docs/content/en/news/0.52-relnotes/featured-hugo-52-poster.png
deleted file mode 100644
index 190f5758a5d..00000000000
Binary files a/docs/content/en/news/0.52-relnotes/featured-hugo-52-poster.png and /dev/null differ
diff --git a/docs/content/en/news/0.52-relnotes/index.md b/docs/content/en/news/0.52-relnotes/index.md
deleted file mode 100644
index 849a0028cfb..00000000000
--- a/docs/content/en/news/0.52-relnotes/index.md
+++ /dev/null
@@ -1,74 +0,0 @@
-
----
-date: 2018-11-28
-title: "And Now: Hugo 0.52"
-description: "Configurable file caches, inline shortcodes, and more ..."
-categories: ["Releases"]
----
-
-The two big new items in this release is [Inline Shortcodes](https://gohugo.io//templates/shortcode-templates/#inline-shortcodes) and [Consolidated File Caches](https://gohugo.io/getting-started/configuration/#configure-file-caches). In Hugo we really care about build speed, and caching is important. With this release, you get much better control over your cache configuration, which is especially useful when building on a Continuous Integration server (Netlify, CircleCI or similar). Inline Shortcodes was implemented to help the Bootstrap project [move their documentation site](https://github.com/twbs/bootstrap/issues/24475#issuecomment-441238128) to Hugo. Note that this feature is disabled by default. To enable, set `enableInlineShortcodes = true` in your site config. Worth mentioning is also the new `param` shortcode, which looks up the param in page front matter with the site's parameter as a fall back.
-
-This release represents **33 contributions by 7 contributors** to the main Hugo code base.
-[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@moorereason](https://github.com/moorereason), [@emirb](https://github.com/emirb), and [@allizad](https://github.com/allizad) for their ongoing contributions.
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) and [@onedrawingperday](https://github.com/onedrawingperday) for their relentless work on keeping the themes site in pristine condition and to [@kaushalmodi](https://github.com/kaushalmodi) for his great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **10 contributions by 4 contributors**. A special thanks to [@budparr](https://github.com/budparr), [@bep](https://github.com/bep), [@allizad](https://github.com/allizad), and [@funkydan2](https://github.com/funkydan2) for their work on the documentation site.
-
-Hugo now has:
-
-* 30595+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 441+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 270+ [themes](http://themes.gohugo.io/)
-
-## Enhancements
-
-### Templates
-
-* Add tests [ed698e94](https://github.com/gohugoio/hugo/commit/ed698e94c12c05bfc392eaca4f0c8442eac64906) [@moorereason](https://github.com/moorereason)
-* Regenerate templates [89e2716d](https://github.com/gohugoio/hugo/commit/89e2716d290708ccde0a6f65504c1650c2f41b3d) [@bep](https://github.com/bep)
-* Add "param" shortcode [f37c5a25](https://github.com/gohugoio/hugo/commit/f37c5a25676db89c0e804ccaac69bb392758192b) [@bep](https://github.com/bep) [#4010](https://github.com/gohugoio/hugo/issues/4010)
-* Add float64 support to where [112461fd](https://github.com/gohugoio/hugo/commit/112461fded0d7970817ce7bf476c4763922ad314) [@moorereason](https://github.com/moorereason) [#5466](https://github.com/gohugoio/hugo/issues/5466)
-
-### Core
-
-* Fall back to title in ByLinkTitle sort [a9a93d08](https://github.com/gohugoio/hugo/commit/a9a93d082d8640684b7fd0076c64ea808ea7f762) [@bep](https://github.com/bep) [#4953](https://github.com/gohugoio/hugo/issues/4953)
-* Improve nil handling in IsDescendant and IsAncestor [b09a4033](https://github.com/gohugoio/hugo/commit/b09a40333f382cc1034d2eda856230258ab6b8cc) [@bep](https://github.com/bep) [#5461](https://github.com/gohugoio/hugo/issues/5461)
-
-### Other
-
-* Remove duplicate mapstructure depdendency [7e75aeca](https://github.com/gohugoio/hugo/commit/7e75aeca80aead50d64902d2ff47e4ad4d013352) [@bep](https://github.com/bep)
-* Add dependency list to README [e14e0b19](https://github.com/gohugoio/hugo/commit/e14e0b192f39812e3c3d5202d34ee907021412bb) [@bep](https://github.com/bep)
-* Document inline shortcodes [aded0f25](https://github.com/gohugoio/hugo/commit/aded0f25fd23a78804b10e127aebe0e4b6fed2ac) [@bep](https://github.com/bep) [#4011](https://github.com/gohugoio/hugo/issues/4011)
-* Add inline shortcode support [bc337e6a](https://github.com/gohugoio/hugo/commit/bc337e6ab5a75f1f1bfe3a83f3786d0afdb6346c) [@bep](https://github.com/bep) [#4011](https://github.com/gohugoio/hugo/issues/4011)
-* Include drafts in convert command [dcfeed35](https://github.com/gohugoio/hugo/commit/dcfeed35c6e14c1ce593d23be9d2b89c66ce9bee) [@bep](https://github.com/bep) [#5457](https://github.com/gohugoio/hugo/issues/5457)
-* Handle themes in the new file cache (for images, assets) [f9b4eb4f](https://github.com/gohugoio/hugo/commit/f9b4eb4f3968d32f45e0168c854e6b0c7f3a90b0) [@bep](https://github.com/bep) [#5460](https://github.com/gohugoio/hugo/issues/5460)
-* Add tests for permalink on Resource with baseURL with path [12742bac](https://github.com/gohugoio/hugo/commit/12742bac71c65d65dc56548b643debda94757aee) [@bep](https://github.com/bep) [#5226](https://github.com/gohugoio/hugo/issues/5226)
-* Add a comment about file mode for new files [fabf026f](https://github.com/gohugoio/hugo/commit/fabf026f4937bf6fbbb944aa7d6e721839ae4c92) [@bep](https://github.com/bep) [#5434](https://github.com/gohugoio/hugo/issues/5434)
-* Add a :project placeholder [94f0f7e5](https://github.com/gohugoio/hugo/commit/94f0f7e59788e802e706a55cac0d52a9e70ff745) [@bep](https://github.com/bep) [#5439](https://github.com/gohugoio/hugo/issues/5439)
-* Add a cache prune func [3c29c5af](https://github.com/gohugoio/hugo/commit/3c29c5af8ee865ef20741f576088e031e940c3d2) [@bep](https://github.com/bep) [#5439](https://github.com/gohugoio/hugo/issues/5439)
-* Add a filecache root dir [33502667](https://github.com/gohugoio/hugo/commit/33502667fbacf57167ede66df8f13e308a4a9aec) [@bep](https://github.com/bep)
-* Use time.Duration for maxAge [d3489eba](https://github.com/gohugoio/hugo/commit/d3489eba5dfc0ecdc032016d9db0746213dd5f0e) [@bep](https://github.com/bep) [#5438](https://github.com/gohugoio/hugo/issues/5438)
-* Split implementation and config into separate files [17d7ecde](https://github.com/gohugoio/hugo/commit/17d7ecde2b261d2ab29049d12361b66504e3f995) [@bep](https://github.com/bep)
-* Update to LibSASS 3.5.5 [e4b25728](https://github.com/gohugoio/hugo/commit/e4b2572880550a997d51dab3b198dac1fd642690) [@bep](https://github.com/bep) [#5432](https://github.com/gohugoio/hugo/issues/5432)[#5435](https://github.com/gohugoio/hugo/issues/5435)
-* More spelling corrections [782dd158](https://github.com/gohugoio/hugo/commit/782dd15858128d8dfe78970c86e543b6590a004c) [@bep](https://github.com/bep)
-* Spelling corrections [aff9c091](https://github.com/gohugoio/hugo/commit/aff9c091669a022b59f493c9dccf72be29511299) [@bep](https://github.com/bep)
-* Remove appveyor [fdd4a768](https://github.com/gohugoio/hugo/commit/fdd4a768f053b21271d4520bf0d43baf62d516da) [@bep](https://github.com/bep)
-* Document the new file cache [abeeff13](https://github.com/gohugoio/hugo/commit/abeeff1325267f8d8f1f66f0ec4ed175ffc140ad) [@bep](https://github.com/bep) [#5404](https://github.com/gohugoio/hugo/issues/5404)
-* Add a consolidated file cache [f7aeaa61](https://github.com/gohugoio/hugo/commit/f7aeaa61291dd75f92901bcbeecc7fce07a28dec) [@bep](https://github.com/bep) [#5404](https://github.com/gohugoio/hugo/issues/5404)
-* Add Windows build config to Travis [7d78a2af](https://github.com/gohugoio/hugo/commit/7d78a2afd3c4a6c4af77a4ddcbd2a82f15986048) [@emirb](https://github.com/emirb)
-* Add Elasticsearch/bonsai.io to services doc. [c0b3a1af](https://github.com/gohugoio/hugo/commit/c0b3a1af0354e3aa9979cc00ae8630d7f0be63dc) [@allizad](https://github.com/allizad)
-
-## Fixes
-
-### Templates
-
-* Fix whitespace issue [aba2647c](https://github.com/gohugoio/hugo/commit/aba2647c152ffff927f42523b77ee6651630cd67) [@max-arnold](https://github.com/max-arnold)
-* Fix test to pass with gccgo [a8cb1b07](https://github.com/gohugoio/hugo/commit/a8cb1b07b4cf7fcf0e949657cb03c1a4838f975e) [@ianlancetaylor](https://github.com/ianlancetaylor)
-
-### Other
-
-* Fix handling of commented out front matter [7540a628](https://github.com/gohugoio/hugo/commit/7540a62834d4465af8936967e430a9e05a1e1359) [@bep](https://github.com/bep) [#5478](https://github.com/gohugoio/hugo/issues/5478)
-* Fix when only shortcode and then summary [94ab125b](https://github.com/gohugoio/hugo/commit/94ab125b27a29a65e5ea45efd99dd247084b4c37) [@bep](https://github.com/bep) [#5464](https://github.com/gohugoio/hugo/issues/5464)
-* Fix ignored --config flag with 'new' command [e82b2dc8](https://github.com/gohugoio/hugo/commit/e82b2dc8c1628f2da33e5fb0bae1b03e0594ad2c) [@krisbudhram](https://github.com/krisbudhram)
-* Fix Permalink for resource, baseURL with path and canonifyURLs set [5df2b79d](https://github.com/gohugoio/hugo/commit/5df2b79dd2734e9a00ed1692328f58c385676468) [@bep](https://github.com/bep) [#5226](https://github.com/gohugoio/hugo/issues/5226)
diff --git a/docs/content/en/news/0.53-relnotes/featured-hugo-53-poster.png b/docs/content/en/news/0.53-relnotes/featured-hugo-53-poster.png
deleted file mode 100644
index c3cee3adc87..00000000000
Binary files a/docs/content/en/news/0.53-relnotes/featured-hugo-53-poster.png and /dev/null differ
diff --git a/docs/content/en/news/0.53-relnotes/index.md b/docs/content/en/news/0.53-relnotes/index.md
deleted file mode 100644
index b61ab9074b8..00000000000
--- a/docs/content/en/news/0.53-relnotes/index.md
+++ /dev/null
@@ -1,94 +0,0 @@
-
----
-date: 2018-12-24
-title: "Hugo Christmas Edition"
-description: "Hugo 0.53: Faster, config dir support, new unmarshal func, global site var, and more ..."
-categories: ["Releases"]
----
-
-From all of us to all of you, a very Merry Christmas -- and Hugo `0.53`!
-
-The main new features in this release are:
-
-**Config Dir:** You can now split your configuration sections into directories per environment. Hugo did support multiple configuration files before this release, but it was hard to manage for bigger sites, especially those with multiple languages. With this we have also formalized the concept of an `environment`; the defaults are `production` (when running `hugo`) or `development` (when running `hugo server`) but you can create any environment you like. We will update the documentation, but all the details are in [this issue](https://github.com/gohugoio/hugo/pull/5501#issue-236237630). Also, see [this PR](https://github.com/gohugoio/hugoDocs/pull/683) for how the refactored configuration for the Hugo website looks like.
-
-**Unmarshal JSON, TOML, YAML or CSV:** `transform.Unmarshal` (see the [documentation](https://gohugo.io//functions/transform.unmarshal/) is a new and powerful template function that can turn `Resource` objects or strings with JSON, TOML, YAML or CSV into maps/arrays.
-
-**Global site and hugo var:** Two new global variables in `site` and `hugo`. `hugo` gives you version info etc. (`{{ hugo.Version }}`, `{{ hugo.Environment }}`), but the `site` is probably more useful, as it allows you to access the current [site's variables](https://gohugo.io/variables/site/) (e.g. `{{ site.RegularPages }}`) without any context (or ".").
-
-This version is also the fastest to date. A site building benchmark shows around 10% faster, but that depends on the site. The important part here is that we're not getting slower. It’s quite a challenge to consistently add significant new functionality and simultaneously improve performance. It's like not gaining weight during Christmas. We also had a small performance boost in version `0.50`. A user then reported that his big and complicated site had a 30% reduction in build time. This is important to us, one of the core features. It's in the slogan: "The world’s fastest framework for building websites."
-
-This release represents **37 contributions by 5 contributors** to the main Hugo code base. [@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@moorereason](https://github.com/moorereason), [@coliff](https://github.com/coliff), and [@jfyuen](https://github.com/jfyuen) for their ongoing contributions. And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) and [@onedrawingperday](https://github.com/onedrawingperday) for their relentless work on keeping the themes site in pristine condition and to [@kaushalmodi](https://github.com/kaushalmodi) for his great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **19 contributions by 8 contributors**. A special thanks to [@bep](https://github.com/bep), [@kaushalmodi](https://github.com/kaushalmodi), [@peaceiris](https://github.com/peaceiris), and [@moorereason](https://github.com/moorereason) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 31174+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 441+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 279+ [themes](http://themes.gohugo.io/)
-
-
-## Notes
-
-* The `hugo benchmark` command is removed
-* We now do not publish transformed inline resources, e.g. minified CSS only accessed via `.Content.`, e.g. `{{ ($css | minify).Content }}`. Before this version, the minified CSS in that example would be copied to `/public`, which was never the intention. If you want that, you need to access either `.RelPermalink` or `.Permalink`.
-
-
-## Enhancements
-
-### Templates
-
-* Include options in cache key [be58c7b9](https://github.com/gohugoio/hugo/commit/be58c7b9c88116094ca2b424c77210ddcccfff8e) [@bep](https://github.com/bep) [#5555](https://github.com/gohugoio/hugo/issues/5555)
-* Simplify transform.Unmarshal func [094709e1](https://github.com/gohugoio/hugo/commit/094709e105d48547bf5297adc0ad0c777678b0a6) [@bep](https://github.com/bep) [#5428](https://github.com/gohugoio/hugo/issues/5428)
-* Add transform.Unmarshal func [822dc627](https://github.com/gohugoio/hugo/commit/822dc627a1cfdf1f97882f27761675ac6ace7669) [@bep](https://github.com/bep) [#5428](https://github.com/gohugoio/hugo/issues/5428)
-* Remove "double layout" lookup [d5a0b6bb](https://github.com/gohugoio/hugo/commit/d5a0b6bbbc83a3e274c62ed397a293f04ee8d241) [@bep](https://github.com/bep) [#5390](https://github.com/gohugoio/hugo/issues/5390)
-* Add reflect namespace [c84f506f](https://github.com/gohugoio/hugo/commit/c84f506f8ef1f2ca94ab96718a22ba6e290235ac) [@moorereason](https://github.com/moorereason) [#4081](https://github.com/gohugoio/hugo/issues/4081)
-* Use the correct Hugo var [931a1324](https://github.com/gohugoio/hugo/commit/931a1324503a4414e38d26efe82e1add811a8d29) [@bep](https://github.com/bep) [#5467](https://github.com/gohugoio/hugo/issues/5467)
-* Add tpl/site and tpl/hugo [831d23cb](https://github.com/gohugoio/hugo/commit/831d23cb4d1ca99cdc15ed31c8ee1f981497be8f) [@bep](https://github.com/bep) [#5470](https://github.com/gohugoio/hugo/issues/5470)[#5467](https://github.com/gohugoio/hugo/issues/5467)[#5503](https://github.com/gohugoio/hugo/issues/5503)
-* Add godoc packages comments [30a7c9ea](https://github.com/gohugoio/hugo/commit/30a7c9ea37a0f36451946f8688a3f807618a7eff) [@moorereason](https://github.com/moorereason)
-
-### Core
-
-* Adjust test [25ddbb09](https://github.com/gohugoio/hugo/commit/25ddbb09fea7794edbbafa2ffce4e361cdc9bacf) [@bep](https://github.com/bep) [#5544](https://github.com/gohugoio/hugo/issues/5544)
-* Add .Name as a shortcode variable [10217144](https://github.com/gohugoio/hugo/commit/1021714449a05ef85b2fdfaf65b354cbdee44f23) [@bep](https://github.com/bep) [#5546](https://github.com/gohugoio/hugo/issues/5546)
-* Improve logic of output path trimming [0483299b](https://github.com/gohugoio/hugo/commit/0483299bc06a742d40528e0d675e42e149910853) [@moorereason](https://github.com/moorereason) [#4666](https://github.com/gohugoio/hugo/issues/4666)
-* Enable Emoji in site benchmark [4d93aca2](https://github.com/gohugoio/hugo/commit/4d93aca27dfdebc9e06948ccf37a7922dac09d65) [@bep](https://github.com/bep)
-* Restore taxonomy term path separation [9ce0a1fb](https://github.com/gohugoio/hugo/commit/9ce0a1fb7011bd75eb0e2262e35354c49ce98ac5) [@bep](https://github.com/bep) [#5513](https://github.com/gohugoio/hugo/issues/5513)
-* Add .Site.Sites [83783588](https://github.com/gohugoio/hugo/commit/8378358857d852458d01c667d59d13baa59a719c) [@bep](https://github.com/bep) [#5504](https://github.com/gohugoio/hugo/issues/5504)
-
-### Other
-
-* Adjust CSV example [62d031ae](https://github.com/gohugoio/hugo/commit/62d031aedfc128729b460241bd99d97b5d902e62) [@bep](https://github.com/bep) [#5555](https://github.com/gohugoio/hugo/issues/5555)
-* Rename CSV option from comma to delimiter [ce06bdb1](https://github.com/gohugoio/hugo/commit/ce06bdb16a64dd39a8ebbb2e5a53b33520b00bb1) [@bep](https://github.com/bep) [#5555](https://github.com/gohugoio/hugo/issues/5555)
-* Document transform.Unmarshal [2efc1a64](https://github.com/gohugoio/hugo/commit/2efc1a64c391420b1007f6e94b6ff616fb136635) [@bep](https://github.com/bep) [#5556](https://github.com/gohugoio/hugo/issues/5556)
-* Regenerate CLI docs [e691c48a](https://github.com/gohugoio/hugo/commit/e691c48a5a9b4db5aa5383de6b83352fc18cc633) [@bep](https://github.com/bep) [#5544](https://github.com/gohugoio/hugo/issues/5544)
-* Add CSV support to transform.Unmarshal [a5744697](https://github.com/gohugoio/hugo/commit/a5744697971d296eb973e04e4259fe9e516b908f) [@bep](https://github.com/bep) [#5555](https://github.com/gohugoio/hugo/issues/5555)
-* Prevent resource publishing for transformed inline resources [43f9df01](https://github.com/gohugoio/hugo/commit/43f9df0194d229805d80b13c9e38a7a0fec12cf4) [@bep](https://github.com/bep) [#4944](https://github.com/gohugoio/hugo/issues/4944)
-* Remove the benchmark command [35bfca3b](https://github.com/gohugoio/hugo/commit/35bfca3b14977eaebab4003b43b5236c1888d93d) [@bep](https://github.com/bep) [#5543](https://github.com/gohugoio/hugo/issues/5543)
-* Move the emoji parsing to pageparser [9cd54cab](https://github.com/gohugoio/hugo/commit/9cd54cab20a03475e34ca462bd943069111481ae) [@bep](https://github.com/bep) [#5534](https://github.com/gohugoio/hugo/issues/5534)
-* Split the page lexer into some more files [a8853f1c](https://github.com/gohugoio/hugo/commit/a8853f1c5ace30ae8d256ad374bdb280c95d4228) [@bep](https://github.com/bep) [#5534](https://github.com/gohugoio/hugo/issues/5534)
-* parser/pageparser: Add a benchmark [f2167de8](https://github.com/gohugoio/hugo/commit/f2167de83493f13f02dd622425364668834f8208) [@bep](https://github.com/bep)
-* Update to Go 1.11.4 [bb9c2988](https://github.com/gohugoio/hugo/commit/bb9c2988f871ca5fe6af9c8e207ec852c631c3b3) [@bep](https://github.com/bep) [#5524](https://github.com/gohugoio/hugo/issues/5524)
-* Simplify implementation [f7691fe9](https://github.com/gohugoio/hugo/commit/f7691fe9652aa12b6c582dea0ae2555e772d1a5f) [@bep](https://github.com/bep)
-* Support unquoted URLs in canonifyURLs replacer [efe0b4e5](https://github.com/gohugoio/hugo/commit/efe0b4e5c0292c1e5e27b0c32fbc368062fde3e8) [@bep](https://github.com/bep) [#5529](https://github.com/gohugoio/hugo/issues/5529)
-* Regenerate CLI docs [50686817](https://github.com/gohugoio/hugo/commit/50686817072c8bef947959cb2bcc7f1914c7f839) [@bep](https://github.com/bep) [#5507](https://github.com/gohugoio/hugo/issues/5507)
-* Add /config dir support [78294740](https://github.com/gohugoio/hugo/commit/7829474088f835251f04caa1121d47e35fe89f7e) [@bep](https://github.com/bep) [#5422](https://github.com/gohugoio/hugo/issues/5422)
-* cache/filecache: Simplify test [514e18dc](https://github.com/gohugoio/hugo/commit/514e18dc27ce37a0e9a231741d616cf29d50d610) [@bep](https://github.com/bep) [#5497](https://github.com/gohugoio/hugo/issues/5497)
-* Use OS fs for test [b804a708](https://github.com/gohugoio/hugo/commit/b804a70881c7be26dc15274c4f98f1057469cbc1) [@bep](https://github.com/bep) [#5497](https://github.com/gohugoio/hugo/issues/5497)
-
-## Fixes
-
-### Templates
-
-* Fix case handling in cast params [64b6b290](https://github.com/gohugoio/hugo/commit/64b6b290751df01c47ff8d8fe21a3eca7a5db283) [@bep](https://github.com/bep) [#5538](https://github.com/gohugoio/hugo/issues/5538)
-
-### Other
-
-* Fix "failed to create file caches from configuration: file exists" on Windows [5178cd13](https://github.com/gohugoio/hugo/commit/5178cd13a7da3c5f5ec5d3217c9e40fc0be7152a) [@bep](https://github.com/bep) [#5497](https://github.com/gohugoio/hugo/issues/5497)
-* fix jekyll import highlight options [ab921476](https://github.com/gohugoio/hugo/commit/ab9214768de4ce10032d3fe7ec8c7b2932ead892) [@jfyuen](https://github.com/jfyuen)
-* Fix "always false" condition [25641891](https://github.com/gohugoio/hugo/commit/256418917c6642f7e5b3d3206ff4b6fa03b1cb28) [@Quasilyte](https://github.com/Quasilyte)
-* Fixx CSS2 color code handling [4b5f7439](https://github.com/gohugoio/hugo/commit/4b5f743959394d443c4dcaa0ccae21842b51adaf) [@bep](https://github.com/bep) [#5506](https://github.com/gohugoio/hugo/issues/5506)
-* common/collections: Fix defines typo [83468481](https://github.com/gohugoio/hugo/commit/8346848109ab57cb04de87c6d86859c6b3de8ffa) [@coliff](https://github.com/coliff)
diff --git a/docs/content/en/news/0.54.0-relnotes/featured-hugo-54.0-poster.png b/docs/content/en/news/0.54.0-relnotes/featured-hugo-54.0-poster.png
deleted file mode 100644
index 10fe563c36b..00000000000
Binary files a/docs/content/en/news/0.54.0-relnotes/featured-hugo-54.0-poster.png and /dev/null differ
diff --git a/docs/content/en/news/0.54.0-relnotes/index.md b/docs/content/en/news/0.54.0-relnotes/index.md
deleted file mode 100644
index 8fc56620b3c..00000000000
--- a/docs/content/en/news/0.54.0-relnotes/index.md
+++ /dev/null
@@ -1,69 +0,0 @@
-
----
-date: 2019-02-01
-title: "0.54.0: Mostly Bugfixes"
-description: "0.54.0 is mostly a bugfix-release, but also some nice improvements."
-categories: ["Releases"]
-
----
-
-This release represents **27 contributions by 7 contributors** to the main Hugo code base. [@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@tryzniak](https://github.com/tryzniak), [@anthonyfok](https://github.com/anthonyfok), and [@mywaiting](https://github.com/mywaiting) for their ongoing contributions. And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) and [@onedrawingperday](https://github.com/onedrawingperday) for their relentless work on keeping the themes site in pristine condition and to [@kaushalmodi](https://github.com/kaushalmodi) for his great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs), which has received **38 contributions by 17 contributors**. A special thanks to [@bep](https://github.com/bep), [@kaushalmodi](https://github.com/kaushalmodi), [@onedrawingperday](https://github.com/onedrawingperday), and [@peaceiris](https://github.com/peaceiris) for their work on the documentation site.
-
-Hugo now has:
-
-* 32265+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 441+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 289+ [themes](http://themes.gohugo.io/)
-
-## Enhancements
-
-### Templates
-
-* Adjust tests [ddc6d4e3](https://github.com/gohugoio/hugo/commit/ddc6d4e30f282f23b703a3b42da552886062c8c8) [@bep](https://github.com/bep) [#5643](https://github.com/gohugoio/hugo/issues/5643)
-* Prevent getJSON and getCSV fetch failure from aborting build [6a2bfcbe](https://github.com/gohugoio/hugo/commit/6a2bfcbec8df14b1741dbe9b5ead08158bf7adb9) [@anthonyfok](https://github.com/anthonyfok) [#5643](https://github.com/gohugoio/hugo/issues/5643)
-
-### Core
-
-* Expand TestPageWithEmoji to cover '+', '-' and '_' too [2a9060a8](https://github.com/gohugoio/hugo/commit/2a9060a85ce430b28f5ec47e1438c6ef1b8e13fa) [@anthonyfok](https://github.com/anthonyfok) [#5635](https://github.com/gohugoio/hugo/issues/5635)
-* Restore 0.48 slash handling in taxonomies [40ffb048](https://github.com/gohugoio/hugo/commit/40ffb0484b96b7b77fb66202b33073b241807199) [@bep](https://github.com/bep) [#5571](https://github.com/gohugoio/hugo/issues/5571)
-
-### Other
-
-* Use official semver even for main releases [fab41f42](https://github.com/gohugoio/hugo/commit/fab41f42d3e23c11651ab75413b01d97e5d37c30) [@bep](https://github.com/bep) [#5639](https://github.com/gohugoio/hugo/issues/5639)
-* Add test for --configDir [59d87044](https://github.com/gohugoio/hugo/commit/59d87044a4146f578b92b3d67b46660212940912) [@bep](https://github.com/bep) [#5662](https://github.com/gohugoio/hugo/issues/5662)
-* Ignore unknown config files in config dir [3244cb3b](https://github.com/gohugoio/hugo/commit/3244cb3b31f8f8c39d9dfa82bc01fb2d6db59257) [@bep](https://github.com/bep) [#5646](https://github.com/gohugoio/hugo/issues/5646)
-* Store supported config formats in a variable [d9282cf9](https://github.com/gohugoio/hugo/commit/d9282cf98a346fcf98f363d9c353e4920ca85fc7) [@tryzniak](https://github.com/tryzniak)
-* Bump to Go 1.11.5 [8ed2a1ca](https://github.com/gohugoio/hugo/commit/8ed2a1caa9e0892d5bf97ed1b7279befa159f764) [@bep](https://github.com/bep) [#5654](https://github.com/gohugoio/hugo/issues/5654)
-* Update Afero [e8596139](https://github.com/gohugoio/hugo/commit/e85961390a050cd4f2e6ce4f2666012bc83bb449) [@bep](https://github.com/bep) [#5650](https://github.com/gohugoio/hugo/issues/5650)
-* Accept hyphen and plus sign in emoji detection [3038464e](https://github.com/gohugoio/hugo/commit/3038464ea6f931c8a08ee49d47f1eaec99ba4817) [@anthonyfok](https://github.com/anthonyfok) [#5635](https://github.com/gohugoio/hugo/issues/5635)
-* Support numeric sort in ByParam [26f75edb](https://github.com/gohugoio/hugo/commit/26f75edb7a76c816349749a05edf98fb36dc338a) [@tryzniak](https://github.com/tryzniak) [#5305](https://github.com/gohugoio/hugo/issues/5305)
-* Make hugo server -t work again [db3c49d0](https://github.com/gohugoio/hugo/commit/db3c49d049193e0fc225fe4bdb95712c311d6615) [@tryzniak](https://github.com/tryzniak) [#5569](https://github.com/gohugoio/hugo/issues/5569)[#5061](https://github.com/gohugoio/hugo/issues/5061)[#4868](https://github.com/gohugoio/hugo/issues/4868)
-* Add configFile(s) back to the watch list after RENAME event too [e3cb8e6c](https://github.com/gohugoio/hugo/commit/e3cb8e6c7874d7dfe1d4d1c7f5c9765b681fb647) [@anthonyfok](https://github.com/anthonyfok) [#5205](https://github.com/gohugoio/hugo/issues/5205)
-* Remove historical rssURI config [55251aa8](https://github.com/gohugoio/hugo/commit/55251aa89099358c040d38f3af48e3699d67bab2) [@mywaiting](https://github.com/mywaiting)
-* Use subtests with server_test.go [843fcd19](https://github.com/gohugoio/hugo/commit/843fcd19d4d97bac979410a4e0abed72586a0aa0) [@tryzniak](https://github.com/tryzniak)
-* Move resource interfaces into its own package [ce8a09a4](https://github.com/gohugoio/hugo/commit/ce8a09a4c0661dece931ab1173e4f09e8e04aa38) [@bep](https://github.com/bep)
-* Move resource processors into sub-packages [669ada43](https://github.com/gohugoio/hugo/commit/669ada436787311cc5d02dae5b88e60a09adda58) [@bep](https://github.com/bep)
-* Update _index.md [50745122](https://github.com/gohugoio/hugo/commit/507451229c2255788d72b757a85ad5bb3ba00f4f) [@vrMarc](https://github.com/vrMarc)
-* Update go.sum [0584432b](https://github.com/gohugoio/hugo/commit/0584432b078f1e3a488ad4f27f39edac0557e042) [@bep](https://github.com/bep)
-* Update Chroma [cc351958](https://github.com/gohugoio/hugo/commit/cc351958e12d4dc83f664a1d51be76a447fea9b8) [@bep](https://github.com/bep) [#4993](https://github.com/gohugoio/hugo/issues/4993)
-* Make docshelper run again [c24f3ae2](https://github.com/gohugoio/hugo/commit/c24f3ae22b27dfe5339662277f8183596a6d148d) [@bep](https://github.com/bep) [#5568](https://github.com/gohugoio/hugo/issues/5568)
-
-## Fixes
-
-### Templates
-
-* Fix reflect [9e4f9e0b](https://github.com/gohugoio/hugo/commit/9e4f9e0bb69276e9bca0dfbdbc7aefbf5f6fc9e5) [@moorereason](https://github.com/moorereason) [#5564](https://github.com/gohugoio/hugo/issues/5564)
-
-### Other
-
-* Fix some inline shortcode issues [c52045bb](https://github.com/gohugoio/hugo/commit/c52045bbb38cbf64b9cb39352230060aa122cc9f) [@bep](https://github.com/bep) [#5645](https://github.com/gohugoio/hugo/issues/5645)[#5653](https://github.com/gohugoio/hugo/issues/5653)
-* Fix OpenGraph image fallback to site params [526b5b1c](https://github.com/gohugoio/hugo/commit/526b5b1c4986d43d6184671b02f45ca40f041b65) [@statik](https://github.com/statik)
-* Fix Params case handling in the new site global [e1a66c73](https://github.com/gohugoio/hugo/commit/e1a66c7343db9d232749255dd9e3a58d94b86997) [@bep](https://github.com/bep) [#5615](https://github.com/gohugoio/hugo/issues/5615)
-* cache/namedmemcache: Fix data race [3f3187de](https://github.com/gohugoio/hugo/commit/3f3187de0f62107da19d9341aebd1d8414bff0ea) [@bep](https://github.com/bep)
-
-
-
-
-
diff --git a/docs/content/en/news/0.55.0-relnotes/featured.png b/docs/content/en/news/0.55.0-relnotes/featured.png
deleted file mode 100644
index 0d3180579c4..00000000000
Binary files a/docs/content/en/news/0.55.0-relnotes/featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.55.0-relnotes/index.md b/docs/content/en/news/0.55.0-relnotes/index.md
deleted file mode 100644
index c22eaf36672..00000000000
--- a/docs/content/en/news/0.55.0-relnotes/index.md
+++ /dev/null
@@ -1,194 +0,0 @@
-
----
-date: 2019-04-08
-title: "Hugo 0.55.0: The early Easter Egg Edition!"
-description: "Faster, virtualized Output Formats, revised shortcodes, new return keyword, and much more …"
-categories: ["Releases"]
----
-
-Hugo `0.55` is **the early Easter Egg Edition** with lots of great improvements and fixes. The original motivation for this release was to prepare for [Issue #5074](https://github.com/gohugoio/hugo/issues/5074), but the structural changes needed for that paved the way for lots of others. Please study the list of changes below, and especially the **Notes** section, but some headlines include:
-
-## Virtualized Output Formats
-
-[Custom Output Formats](https://gohugo.io/templates/output-formats) has been a really useful feature, but it has had some annoying and not so obvious restrictions that are now lifted. Now all `Page` collections are aware of the output format being rendered. This means, to give some examples, that:
-
-* In a `RSS` template, listing pages with their content will use output format specific shortcode templates even if the pages themselves are not configured to output to that output format.
-* Using `.Render` when looping over a `Page` collection will now work as expected.
-* Every Output Format can be paginated.
-
-We have now also added a new `Permalinkable` configuration attribute, which is enabled by default for `HTML` and `AMP`.
-
-## Shortcodes Revised
-
-Shortcodes using the `{{%/* */%}}` as the outer-most delimiter will now be fully rendered when sent to the content renderer (e.g. Blackfriday for Markdown), meaning they can be part of the generated table of contents, footnotes, etc.
-
-If you want the old behavior, you can put the following line in the start of your shortcode template:
-
-```
-{{ $_hugo_config := `{ "version": 1 }` }}
-```
-
-But using the `{{* */>}}` delimiter will, in most cases, be a better alternative, possibly in combination with the `markdownify` template func.
-
-See [#5763](https://github.com/gohugoio/hugo/issues/5763).
-
-## New Return Keyword for Partials
-
-Hugo's `partial` and `partialCached` are great for template macros. With the new `return` keyword you can write partials as proper functions that can return any type:
-
-```go-html-template
-{{ $v := add . 42 }}
-{{ return $v }}
-```
-
-See [#5783](https://github.com/gohugoio/hugo/issues/5783).
-
-## .Page on Taxonomy nodes
-
-The taxonomy nodes now have a `.Page` accessor which makes it much simpler to get a proper `.Title` etc. This means that older and clumsy constructs can be simplified. Some examples:
-
-```go-html-template
-
-```
-
-See [#5719](https://github.com/gohugoio/hugo/issues/5719).
-
-## And it's Faster!
-
-This version is also the **fastest to date**. A site building benchmark shows more than 10% decrease in both build time and memory consumption, but that depends on the site. It’s quite a challenge to consistently add significant new functionality and simultaneously improve performance. Also, note that we are now more honest about build times reported (`Total in 1234 ms`). We now do all initialization in the `Build` step, so you may get a higher time reported if you, as an example, have `--enableGitInfo` enabled, which now is included in the reported time.
-
-
-
-## Thanks!
-
-This release represents **59 contributions by 10 contributors** to the main Hugo code base.
-[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@mcdee](https://github.com/mcdee), [@quasilyte](https://github.com/quasilyte), and [@danielcompton](https://github.com/danielcompton) for their ongoing contributions.
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) and [@onedrawingperday](https://github.com/onedrawingperday) for their relentless work on keeping the themes site in pristine condition and to [@kaushalmodi](https://github.com/kaushalmodi) for his great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **36 contributions by 21 contributors**. A special thanks to [@bep](https://github.com/bep), [@peaceiris](https://github.com/peaceiris), [@budparr](https://github.com/budparr), and [@tinymachine](https://github.com/tinymachine) for their work on the documentation site.
-
-As this release has required a significant effort with manual testing, a special thanks go to [@onedrawingperday](https://github.com/onedrawingperday) (the 300 theme demo sites have been invaluable to check for API-breakage!), [@adiabatic](https://github.com/adiabatic), and [@divinerites](https://github.com/divinerites).
-
-Hugo now has:
-
-* 34077+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 439+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 306+ [themes](http://themes.gohugo.io/)
-
-
-## Notes
-* `{{ %` as the outer-most shortcode delimiter means "pass the inner content to the content renderer" (e.g. Blackfriday); the old behavior can be had, see [#5763](https://github.com/gohugoio/hugo/issues/5763)
-* `preserveTaxonomyNames`configuration option is removed. Use `.Page.Title`.
-* We no longer limit the number of pages passed to the `RSS` Output Format. We have moved that limit to the internal `RSS` template, and you can do so yourself using the `Config.Services.RSS.Limit` in your custom template.
-* We no longer add XML headers to Output Formats that output XML (``). This header is moved to the templates. If you have custom RSS or sitemap templates you may want to add the XML declaration to these. Since they, by default, is handled by Go's HTML template package, you must do something like this to make sure it's preserved: `{{ printf "" | safeHTML }}`
-* More honest build times reported (`Total in 1234 ms`). We now do all initialization in the `Build` step, so you may get a higher time reported if you, as an example, have `--enableGitInfo` enabled, which now is included in the reported time.
-* The taxonomy nodes now have a `.Page` accessor which makes it much simpler to get a proper `.Title` etc. see [#5719](https://github.com/gohugoio/hugo/issues/5719).
-* The template keywords `with` and `if` now work properly for zero and interface types, see [#5739](https://github.com/gohugoio/hugo/issues/5739)
-* Taxonomy terms lists (`Page` of `Kind` `taxonomyTerm`) without any date(s) set (e.g. from front matter) will now pick the latest dates from the child pages. This is in line with how other branch nodes work in Hugo.
-* A new configuration option, `Permalinkable`, is added to Output Format and enabled that by default for `HTML` and `AMP` types. See [#5706](https://github.com/gohugoio/hugo/issues/5706)
-* `--stepAnalysis` is removed. If you want to really understand the latency in your project in Hugo, try the new `--trace` flag and pass that file to the many tools that read [Go Trace files](https://golang.org/pkg/runtime/trace/). There are also some newly hidden flags in `--profile-cpu`, `--profile-men`, `--profile-mutex`, hidden because they are considered to be only of interest to developers.
-* Chroma is updated with many fixes and new lexers, see [#5797](https://github.com/gohugoio/hugo/issues/5797)
-* We now support `Page`-relative aliases, e.g. aliases that do not start with a `/`, see [#5757](https://github.com/gohugoio/hugo/issues/5757)
-* We now support context-relative (language) URLs in front matter, meaning that in most cases `url: /en/mypage` can be replaced with the more portable `url: mypage`. See [#5704](https://github.com/gohugoio/hugo/issues/5704)
-
-## Enhancements
-
-### Templates
-
-* Allow the partial template func to return any type [a55640de](https://github.com/gohugoio/hugo/commit/a55640de8e3944d3b9f64b15155148a0e35cb31e) [@bep](https://github.com/bep) [#5783](https://github.com/gohugoio/hugo/issues/5783)
-
-### Output
-
-* Add missing JSON tag [b6a60f71](https://github.com/gohugoio/hugo/commit/b6a60f718e376066456da37e7bb997a7697edc31) [@bep](https://github.com/bep)
-
-### Core
-
-* Log warning on relative front matter url with lang [f34e6172](https://github.com/gohugoio/hugo/commit/f34e6172cf2a4d1d1aef22304ecbc7c8e2d142ff) [@bep](https://github.com/bep) [#5818](https://github.com/gohugoio/hugo/issues/5818)
-* Consider summary in front matter for .Summary [3a62d547](https://github.com/gohugoio/hugo/commit/3a62d54745e2cbfda6772390830042908d725c71) [@mcdee](https://github.com/mcdee) [#5800](https://github.com/gohugoio/hugo/issues/5800)
-* Buffer the render pages chan [95029551](https://github.com/gohugoio/hugo/commit/950295516da882dcc51d83f70835dde230a0b4d6) [@bep](https://github.com/bep)
-* Re-work "fast render" logic in the new flow [d0d661df](https://github.com/gohugoio/hugo/commit/d0d661dffd19d5ed6efbd4dd2c572bad008bd859) [@bep](https://github.com/bep) [#5811](https://github.com/gohugoio/hugo/issues/5811)[#5784](https://github.com/gohugoio/hugo/issues/5784)
-* Allow relative URLs in front matter [5185fb06](https://github.com/gohugoio/hugo/commit/5185fb065b0f8a4142c29ee3e3cd917e917280a4) [@bep](https://github.com/bep) [#5704](https://github.com/gohugoio/hugo/issues/5704)
-* Allow page-relative aliases [92baa14f](https://github.com/gohugoio/hugo/commit/92baa14fd3f45c0917c5988235cd1a0f8692f171) [@bep](https://github.com/bep) [#5757](https://github.com/gohugoio/hugo/issues/5757)
-* Add a simple test for jsonify of Site [8bfd3a54](https://github.com/gohugoio/hugo/commit/8bfd3a54a4142c397cab69bfa9699e5b5db9b40b) [@bep](https://github.com/bep) [#5780](https://github.com/gohugoio/hugo/issues/5780)
-* Do not fall back to site title if not set in content file [9bc6187b](https://github.com/gohugoio/hugo/commit/9bc6187b8337c4a370bd3f21130a764d9ef6f7b3) [@bep](https://github.com/bep) [#5784](https://github.com/gohugoio/hugo/issues/5784)
-* Add a test for home page with no title [bceda1b2](https://github.com/gohugoio/hugo/commit/bceda1b288f0ad6282916826b596cb1fe19983bb) [@bep](https://github.com/bep) [#5784](https://github.com/gohugoio/hugo/issues/5784)
-* Add String() to fileInfo [a7ee9b0b](https://github.com/gohugoio/hugo/commit/a7ee9b0bb98f519e485655af578fb35d755e5c44) [@bep](https://github.com/bep) [#5784](https://github.com/gohugoio/hugo/issues/5784)
-* Remove unused slice [3011f36c](https://github.com/gohugoio/hugo/commit/3011f36c27ecde309325e6c75ca377f4f87fa97a) [@bep](https://github.com/bep)
-* Adjust site benchmark [34c49d78](https://github.com/gohugoio/hugo/commit/34c49d788c102a370006e476d6f6143a51b2a03d) [@bep](https://github.com/bep)
-* Adjust test for Go 1.12 [b4148cd1](https://github.com/gohugoio/hugo/commit/b4148cd1d9ea889b81070d3e84a37bd5d23e5746) [@bep](https://github.com/bep)
-
-### Other
-
-* Misc paginator adjustments [612a06f0](https://github.com/gohugoio/hugo/commit/612a06f0671125be6b42ec2982a18080005994c8) [@bep](https://github.com/bep) [#5825](https://github.com/gohugoio/hugo/issues/5825)
-* Update to Go 1.12.2 and Go 1.11.7 [3db4a1cf](https://github.com/gohugoio/hugo/commit/3db4a1cf7ab12343ce5705ac56aa7ca6ea1677b6) [@bep](https://github.com/bep) [#5819](https://github.com/gohugoio/hugo/issues/5819)
-* Adjust rlimit logic [708d4cee](https://github.com/gohugoio/hugo/commit/708d4ceebd491c6a89f271311eb8d94d6b5d58bc) [@bep](https://github.com/bep) [#5821](https://github.com/gohugoio/hugo/issues/5821)
-* Add information about summary front matter variable [ed65bda3](https://github.com/gohugoio/hugo/commit/ed65bda3b43f6149e41ddb049cbb295a82473bc9) [@mcdee](https://github.com/mcdee)
-* Regenerate JSON wrapper [ebab291c](https://github.com/gohugoio/hugo/commit/ebab291c0e321d23b098684bacaf830a3979e310) [@bep](https://github.com/bep)
-* Add missing GitInfo to Page [75467cd7](https://github.com/gohugoio/hugo/commit/75467cd7852852305549a6c71ac503bb4a57e716) [@bep](https://github.com/bep)
-* Add support for sha384 [d1553b4b](https://github.com/gohugoio/hugo/commit/d1553b4b0f83e4a4305d2b4ab9ba6e305637f134) [@bep](https://github.com/bep) [#5815](https://github.com/gohugoio/hugo/issues/5815)
-* Add HUGO_NUMWORKERMULTIPLIER [87b16abd](https://github.com/gohugoio/hugo/commit/87b16abd93ff60acd245776d5b0d914fd580c259) [@bep](https://github.com/bep) [#5814](https://github.com/gohugoio/hugo/issues/5814)
-* Use YAML for the benchmark compare [8559f5c2](https://github.com/gohugoio/hugo/commit/8559f5c29f20b7b5188f93f8b1d9e510e3dee4f5) [@bep](https://github.com/bep)
-* Update to imaging v1.6.0 [032e6802](https://github.com/gohugoio/hugo/commit/032e6802d1f34cc41f6d1275fdd2deab8bbe5480) [@bep](https://github.com/bep) [#5812](https://github.com/gohugoio/hugo/issues/5812)
-* Adjust the howSimilar logic vs strings [4494a01b](https://github.com/gohugoio/hugo/commit/4494a01b794ab785c64c8e93c61ccbfa845bc478) [@bep](https://github.com/bep)
-* Implement compare.ProbablyEqer for the core slices [e91e222c](https://github.com/gohugoio/hugo/commit/e91e222cd21213961d1e6206e1523bee2c21fa0c) [@bep](https://github.com/bep) [#5808](https://github.com/gohugoio/hugo/issues/5808)
-* Regenerate docshelper data [bfdc4496](https://github.com/gohugoio/hugo/commit/bfdc44964af82807fa91407132d47b6bf52704c3) [@bep](https://github.com/bep) [#5799](https://github.com/gohugoio/hugo/issues/5799)
-* Update Chroma [cc8515f1](https://github.com/gohugoio/hugo/commit/cc8515f18767298da4c6d712d1fd747c7950150b) [@bep](https://github.com/bep) [#5780](https://github.com/gohugoio/hugo/issues/5780)
-* Regenerate CLI docs [bb533ca5](https://github.com/gohugoio/hugo/commit/bb533ca5e1c778c95ed7014eab99c8cc1bd4c85e) [@bep](https://github.com/bep) [#5779](https://github.com/gohugoio/hugo/issues/5779)
-* Update Afero [10bb614a](https://github.com/gohugoio/hugo/commit/10bb614a70db22c01c9a52054ede35bc0a01aa24) [@bep](https://github.com/bep) [#5673](https://github.com/gohugoio/hugo/issues/5673)
-* Avoid nilpointer on no File on Page [4dae52af](https://github.com/gohugoio/hugo/commit/4dae52af680e6ff2c8cdeb4ce1f219330b27001c) [@bep](https://github.com/bep) [#5781](https://github.com/gohugoio/hugo/issues/5781)
-* Improve the "feature not available" error [794d4052](https://github.com/gohugoio/hugo/commit/794d4052b87c98943588b35e1cfecc06e6a0c7f2) [@bep](https://github.com/bep)
-* Re-introduce .Page.Page [91ef9655](https://github.com/gohugoio/hugo/commit/91ef9655aaf2adea3a044bf9a464908084917a98) [@bep](https://github.com/bep) [#5784](https://github.com/gohugoio/hugo/issues/5784)
-* Apply staticcheck recommendations [b5f39d23](https://github.com/gohugoio/hugo/commit/b5f39d23b86f9cb83c51da9fe4abb4c19c01c3b7) [@bep](https://github.com/bep)
-* Run gofmt -s [d30e8454](https://github.com/gohugoio/hugo/commit/d30e845485b416e1c48fade14694b12a9fe59b6b) [@bep](https://github.com/bep)
-* Make Page an interface [597e418c](https://github.com/gohugoio/hugo/commit/597e418cb02883418f2cebb41400e8e61413f651) [@bep](https://github.com/bep) [#5074](https://github.com/gohugoio/hugo/issues/5074)[#5763](https://github.com/gohugoio/hugo/issues/5763)[#5758](https://github.com/gohugoio/hugo/issues/5758)[#5090](https://github.com/gohugoio/hugo/issues/5090)[#5204](https://github.com/gohugoio/hugo/issues/5204)[#4695](https://github.com/gohugoio/hugo/issues/4695)[#5607](https://github.com/gohugoio/hugo/issues/5607)[#5707](https://github.com/gohugoio/hugo/issues/5707)[#5719](https://github.com/gohugoio/hugo/issues/5719)[#3113](https://github.com/gohugoio/hugo/issues/3113)[#5706](https://github.com/gohugoio/hugo/issues/5706)[#5767](https://github.com/gohugoio/hugo/issues/5767)[#5723](https://github.com/gohugoio/hugo/issues/5723)[#5769](https://github.com/gohugoio/hugo/issues/5769)[#5770](https://github.com/gohugoio/hugo/issues/5770)[#5771](https://github.com/gohugoio/hugo/issues/5771)[#5759](https://github.com/gohugoio/hugo/issues/5759)[#5776](https://github.com/gohugoio/hugo/issues/5776)[#5777](https://github.com/gohugoio/hugo/issues/5777)[#5778](https://github.com/gohugoio/hugo/issues/5778)
-* List future and expired dates in CSV format [44f5c1c1](https://github.com/gohugoio/hugo/commit/44f5c1c14cb1f42cc5f01739c289e9cfc83602af) [@danielcompton](https://github.com/danielcompton) [#5610](https://github.com/gohugoio/hugo/issues/5610)
-* Update to Go 1.12.1 and Go 1.11.6 [984a73af](https://github.com/gohugoio/hugo/commit/984a73af9e5b5145297723f26faa38f29ca2918d) [@bep](https://github.com/bep) [#5755](https://github.com/gohugoio/hugo/issues/5755)
-* Update Viper [79d517d8](https://github.com/gohugoio/hugo/commit/79d517d86c02e879bc4a43ab86b817c589b61485) [@bep](https://github.com/bep)
-* Update to Go 1.12 [b9e75afd](https://github.com/gohugoio/hugo/commit/b9e75afd6c007a6af8b71caeebc4a5a24c270861) [@bep](https://github.com/bep) [#5716](https://github.com/gohugoio/hugo/issues/5716)
-* Remove Gitter dev chat link [dfc72d61](https://github.com/gohugoio/hugo/commit/dfc72d61a522f5cb926271d9391a8670f064d198) [@bep](https://github.com/bep)
-* Update Travis config to work for forked builds [bdf47e8d](https://github.com/gohugoio/hugo/commit/bdf47e8da80f87b7689badf48a6b8672c048d7e4) [@grahamjamesaddis](https://github.com/grahamjamesaddis)
-* Add skipHTML option to blackfriday config [75904332](https://github.com/gohugoio/hugo/commit/75904332f3bedcfe656856821d4c9560a177cc51) [@arrtchiu](https://github.com/arrtchiu)
-* Update stretchr/testify to 1.3.0. [60c0eb4e](https://github.com/gohugoio/hugo/commit/60c0eb4e892baedd533424b47baf7039c0005f87) [@QuLogic](https://github.com/QuLogic)
-* Rewrite relative action URLS [c154c2f7](https://github.com/gohugoio/hugo/commit/c154c2f7b2a6703dbde7f6bd2a1817a39c6fd2ea) [@larson004](https://github.com/larson004) [#5701](https://github.com/gohugoio/hugo/issues/5701)
-* Support Docker args TAGS, WORKDIR, CGO; speed up repetitive builds [075b17ee](https://github.com/gohugoio/hugo/commit/075b17ee1d621e0ebbcecf1063f8f68a00ac221a) [@tonymet](https://github.com/tonymet)
-* Support nested keys/fields with missing values with the `where` function [908692fa](https://github.com/gohugoio/hugo/commit/908692fae5c5840a0db8c7dd389b59dd3b8026b9) [@tryzniak](https://github.com/tryzniak) [#5637](https://github.com/gohugoio/hugo/issues/5637)[#5416](https://github.com/gohugoio/hugo/issues/5416)
-* Update debouncer version [7e4b18c5](https://github.com/gohugoio/hugo/commit/7e4b18c5ae409435760ebd86ff9ee3061db34a5d) [@bep](https://github.com/bep)
-
-## Fixes
-
-### Templates
-
-* Fix mutex unlock [e54213f5](https://github.com/gohugoio/hugo/commit/e54213f5257267ed232b2465337c39ddc8c73388) [@bep](https://github.com/bep)
-* Fix template truth logic [02eaddc2](https://github.com/gohugoio/hugo/commit/02eaddc2fbe92c26e67d9f82dd9aabecbbf2106c) [@bep](https://github.com/bep) [#5738](https://github.com/gohugoio/hugo/issues/5738)
-* Fix strings.HasPrefix args order [72010429](https://github.com/gohugoio/hugo/commit/7201042946dde78d5ea4fea9cb006fb4dded55c1) [@quasilyte](https://github.com/quasilyte)
-
-### Core
-
-* Fix default date assignment for sections [1d9dde82](https://github.com/gohugoio/hugo/commit/1d9dde82a0577d93eea8ed0a7ec0b4ae3068eb19) [@bep](https://github.com/bep) [#5784](https://github.com/gohugoio/hugo/issues/5784)
-* Fix the GOMAXPROCS env get [415ca967](https://github.com/gohugoio/hugo/commit/415ca9673d3bd3c06ab94f3d83897c892fce5f27) [@bep](https://github.com/bep) [#5813](https://github.com/gohugoio/hugo/issues/5813)
-* Fix benchmark for YAML front matter [e2dc432f](https://github.com/gohugoio/hugo/commit/e2dc432fe287a280aeba94bafdcce85b7a8646c6) [@bep](https://github.com/bep)
-* Fix alias path for AMP and similar [f9d6feca](https://github.com/gohugoio/hugo/commit/f9d6feca0802cd83c4d843244ce389cf7c792cec) [@bep](https://github.com/bep) [#5760](https://github.com/gohugoio/hugo/issues/5760)
-
-### Other
-
-* Fix image publish ordering issue [439ab033](https://github.com/gohugoio/hugo/commit/439ab0339d9ac6972caabaa55fa41887ace839cb) [@bep](https://github.com/bep) [#5730](https://github.com/gohugoio/hugo/issues/5730)
-* Fix doLiveReload logic [4a2a8aff](https://github.com/gohugoio/hugo/commit/4a2a8afff2021c8e967254c76c159147da7e78fa) [@bep](https://github.com/bep) [#5754](https://github.com/gohugoio/hugo/issues/5754)
-* Fix args order in strings.TrimPrefix [483cf19d](https://github.com/gohugoio/hugo/commit/483cf19d5de05e8a83fd1be6934baa169c7fd7c8) [@quasilyte](https://github.com/quasilyte)
-
-
-
-
-
diff --git a/docs/content/en/news/0.55.1-relnotes/index.md b/docs/content/en/news/0.55.1-relnotes/index.md
deleted file mode 100644
index 4e9880dc5c1..00000000000
--- a/docs/content/en/news/0.55.1-relnotes/index.md
+++ /dev/null
@@ -1,53 +0,0 @@
-
----
-date: 2019-04-12
-title: "0.55.1: 3 Bug Fixes"
-description: "Fixes 3 issues introduced in 0.55.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-
-Hugo now has:
-
-* 34225+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 439+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 307+ [themes](http://themes.gohugo.io/)
-
-
-## Notes
-
-* Replace deprecated .GetParam usage [27a8049d](https://github.com/gohugoio/hugo/commit/27a8049da7996b703d02083182b84a002eae2599) [@bep](https://github.com/bep) [#5834](https://github.com/gohugoio/hugo/issues/5834)
-
-## Enhancements
-
-### Core
-
-* Add a test for parent's resources in shortcode [8d7607ae](https://github.com/gohugoio/hugo/commit/8d7607aed10b3fe7373126ff5fa7dae36c559d7f) [@bep](https://github.com/bep) [#5833](https://github.com/gohugoio/hugo/issues/5833)
-
-### Other
-
-* Remove the space in `. RelPermalink` [7966c0b5](https://github.com/gohugoio/hugo/commit/7966c0b5b7b2297527f8be9040b793de5e4e3f48) [@yihui](https://github.com/yihui)
-
-## Fixes
-
-### Core
-
-* Fix simple menu config [9e9a1f92](https://github.com/gohugoio/hugo/commit/9e9a1f92baf151f8d840d6b5b963945d1410ce25) [@bep](https://github.com/bep)
-
-### Other
-
-* Fix [4d425a86](https://github.com/gohugoio/hugo/commit/4d425a86f5c03a5cca27d4e0f99d61acbb938d80) [@bep](https://github.com/bep)
-* Fix paginator refresh on server change [f7375c49](https://github.com/gohugoio/hugo/commit/f7375c497239115cd30ae42af6b4d298e4e7ad7d) [@bep](https://github.com/bep) [#5838](https://github.com/gohugoio/hugo/issues/5838)
-* Fix .RSSLinke deprecation message [3b86b4a9](https://github.com/gohugoio/hugo/commit/3b86b4a9f5ce010c9714d813d5b8ecddda22c69f) [@bep](https://github.com/bep) [#4427](https://github.com/gohugoio/hugo/issues/4427)
-
-
-
-
-
diff --git a/docs/content/en/news/0.55.2-relnotes/index.md b/docs/content/en/news/0.55.2-relnotes/index.md
deleted file mode 100644
index 0b6f49b1160..00000000000
--- a/docs/content/en/news/0.55.2-relnotes/index.md
+++ /dev/null
@@ -1,51 +0,0 @@
-
----
-date: 2019-04-17
-title: "Hugo 0.55.2: Some Important Bug Fixes"
-description: "Fixes some more issues introduced in Hugo 0.55.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-
-Hugo now has:
-
-* 34386+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 439+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 307+ [themes](http://themes.gohugo.io/)
-
-## Enhancements
-
-### Templates
-
-* Handle late transformation of templates [2957795f](https://github.com/gohugoio/hugo/commit/2957795f5276cc9bc8d438da2d7d9b61defea225) [@bep](https://github.com/bep) [#5865](https://github.com/gohugoio/hugo/issues/5865)
-
-### Core
-
-* Add more tests for Permalinkable [35f41834](https://github.com/gohugoio/hugo/commit/35f41834ea3a8799b9b7eda360cf8d30b1b727ba) [@bep](https://github.com/bep) [#5849](https://github.com/gohugoio/hugo/issues/5849)
-
-## Fixes
-
-### Core
-
-* Fix Pages reinitialization on rebuilds [9b17cbb6](https://github.com/gohugoio/hugo/commit/9b17cbb62a056ea7e26b1146cbf3ba42f5acf805) [@bep](https://github.com/bep) [#5833](https://github.com/gohugoio/hugo/issues/5833)
-* Fix shortcode namespace issue [56550d1e](https://github.com/gohugoio/hugo/commit/56550d1e449f45ebee398ac8a9e3b9818b3ee60e) [@bep](https://github.com/bep) [#5863](https://github.com/gohugoio/hugo/issues/5863)
-* Fix false WARNINGs in lang prefix check [7881b096](https://github.com/gohugoio/hugo/commit/7881b0965f8b83d03379e9ed102cd0c3bce297e2) [@bep](https://github.com/bep) [#5860](https://github.com/gohugoio/hugo/issues/5860)
-* Fix bundle resource publishing when multiple output formats [49d0a826](https://github.com/gohugoio/hugo/commit/49d0a82641581aa7dd66b9d5e8c7d75e23260083) [@bep](https://github.com/bep) [#5858](https://github.com/gohugoio/hugo/issues/5858)
-* Fix panic for unused taxonomy content files [b799b12f](https://github.com/gohugoio/hugo/commit/b799b12f4a693dfeae8a5a362f131081a727bb8f) [@bep](https://github.com/bep) [#5847](https://github.com/gohugoio/hugo/issues/5847)
-* Fix dates for sections with dates in front matter [70148672](https://github.com/gohugoio/hugo/commit/701486728e21bc0c6c78c2a8edb988abdf6116c7) [@bep](https://github.com/bep) [#5854](https://github.com/gohugoio/hugo/issues/5854)
-
-### Other
-
-* Fix WeightedPages in union etc. [f2795d4d](https://github.com/gohugoio/hugo/commit/f2795d4d2cef30170af43327f3ff7114923833b1) [@bep](https://github.com/bep) [#5850](https://github.com/gohugoio/hugo/issues/5850)
-
-
-
-
-
diff --git a/docs/content/en/news/0.55.3-relnotes/index.md b/docs/content/en/news/0.55.3-relnotes/index.md
deleted file mode 100644
index d00c47d54ae..00000000000
--- a/docs/content/en/news/0.55.3-relnotes/index.md
+++ /dev/null
@@ -1,49 +0,0 @@
-
----
-date: 2019-04-20
-title: "Hugo 0.55.3: A Few More Bug Fixes!"
-description: "To wrap up this Easter, here is one more patch release with some important fixes."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-This is a bug-fix release with a couple of important fixes.
-
-
-Hugo now has:
-
-* 34468+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 439+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 308+ [themes](http://themes.gohugo.io/)
-
-## Enhancements
-
-### Templates
-
-* Return error on invalid input in in [7fbfedf0](https://github.com/gohugoio/hugo/commit/7fbfedf01367ff076c3c875b183789b769b99241) [@bep](https://github.com/bep) [#5875](https://github.com/gohugoio/hugo/issues/5875)
-* Make Pages etc. work with the in func [06f56fc9](https://github.com/gohugoio/hugo/commit/06f56fc983d460506d39b3a6f638b1632af07073) [@bep](https://github.com/bep) [#5875](https://github.com/gohugoio/hugo/issues/5875)
-* Make Pages etc. work in uniq [d7a67dcb](https://github.com/gohugoio/hugo/commit/d7a67dcb51829b12d492d3f2ee4f6e2a3834da63) [@bep](https://github.com/bep) [#5852](https://github.com/gohugoio/hugo/issues/5852)
-
-### Core
-
-* Add some OutputFormats.Get tests [7aeeb60d](https://github.com/gohugoio/hugo/commit/7aeeb60d7ee71690461df92ff41cb8b2f7f5aa61) [@bep](https://github.com/bep) [#5877](https://github.com/gohugoio/hugo/issues/5877)
-* Add some integration tests for in/uniq using Pages [6c80acbd](https://github.com/gohugoio/hugo/commit/6c80acbd5e314dd92fc075551ffabafaae01dca7) [@bep](https://github.com/bep) [#5875](https://github.com/gohugoio/hugo/issues/5875)[#5852](https://github.com/gohugoio/hugo/issues/5852)
-
-### Other
-
-* Regenerate docs helper [75b16e30](https://github.com/gohugoio/hugo/commit/75b16e30ec55e82a8024cc4d27880d9b79e0fa41) [@bep](https://github.com/bep)
-* Replace IsDraft with Draft in list command [3e421bd4](https://github.com/gohugoio/hugo/commit/3e421bd47cd35061df89c1c127ec8fa4ae368449) [@bep](https://github.com/bep) [#5873](https://github.com/gohugoio/hugo/issues/5873)
-
-## Fixes
-
-### Output
-
-* Fix links for non-HTML output formats [c7dd66bf](https://github.com/gohugoio/hugo/commit/c7dd66bfe2e32430f9b1a3126c67014e40d8405e) [@bep](https://github.com/bep) [#5877](https://github.com/gohugoio/hugo/issues/5877)
-* Fix menu URL when multiple permalinkable output formats [ea529c84](https://github.com/gohugoio/hugo/commit/ea529c847ebc0267c6d0426cc8f77d5c76c73fe4) [@bep](https://github.com/bep) [#5849](https://github.com/gohugoio/hugo/issues/5849)
-
-
-
-
-
diff --git a/docs/content/en/news/0.55.4-relnotes/index.md b/docs/content/en/news/0.55.4-relnotes/index.md
deleted file mode 100644
index 292b3924426..00000000000
--- a/docs/content/en/news/0.55.4-relnotes/index.md
+++ /dev/null
@@ -1,44 +0,0 @@
-
----
-date: 2019-04-25
-title: "Hugo 0.55.4: Some Bug Fixes"
-description: "A couple of more bug fixes."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-
-Hugo now has:
-
-* 34558+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 439+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 310+ [themes](http://themes.gohugo.io/)
-
-## Enhancements
-
-### Core
-
-* Avoid recloning of shortcode templates [69a56420](https://github.com/gohugoio/hugo/commit/69a56420aec5bf5abb846701d4a5ec67fe060d96) [@bep](https://github.com/bep) [#5890](https://github.com/gohugoio/hugo/issues/5890)
-* No links for bundled pages [0775c98e](https://github.com/gohugoio/hugo/commit/0775c98e6c5b700e46adaaf190fc3f693a6ab002) [@bep](https://github.com/bep) [#5882](https://github.com/gohugoio/hugo/issues/5882)
-
-### Other
-
-* Avoid rebuilding the Translations map for every lookup [4756ec3c](https://github.com/gohugoio/hugo/commit/4756ec3cd8ef998f889619fe11be70cc900e2b75) [@bep](https://github.com/bep) [#5892](https://github.com/gohugoio/hugo/issues/5892)
-* Init mem profile at the end [4c3c5120](https://github.com/gohugoio/hugo/commit/4c3c5120389cc95edc63b8f18a0eee786aa0c5e2) [@bep](https://github.com/bep)
-
-## Fixes
-
-### Core
-
-* Fix shortcode version=1 logic [33c73811](https://github.com/gohugoio/hugo/commit/33c738116c26e2ac37f4bd48159e8e3197fd7b39) [@bep](https://github.com/bep) [#5831](https://github.com/gohugoio/hugo/issues/5831)
-
-
-
-
-
diff --git a/docs/content/en/news/0.55.5-relnotes/index.md b/docs/content/en/news/0.55.5-relnotes/index.md
deleted file mode 100644
index 45a3eda54e7..00000000000
--- a/docs/content/en/news/0.55.5-relnotes/index.md
+++ /dev/null
@@ -1,56 +0,0 @@
-
----
-date: 2019-05-02
-title: "Hugo 0.55.5: Take Five!"
-description: "We round up this 0.55 release with a final batch of bug fixes!"
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-
-Hugo now has:
-
-* 34743+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 439+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 314+ [themes](http://themes.gohugo.io/)
-
-## Enhancements
-
-### Templates
-
-* Provide more detailed errors in Where [f76e5011](https://github.com/gohugoio/hugo/commit/f76e50118b8b5dd8989d068db35222bfa0a242d8) [@moorereason](https://github.com/moorereason)
-
-### Other
-
-* Regenerate docs helper [cee181c3](https://github.com/gohugoio/hugo/commit/cee181c3a67fe04b8e0c9f2807c5aa2871df474e) [@bep](https://github.com/bep)
-* Update blackfriday to v1.5.2 [1cbb501b](https://github.com/gohugoio/hugo/commit/1cbb501be8aa83b08865fbb6ad5aee254946712f) [@dbirks](https://github.com/dbirks)
-
-## Fixes
-
-### Templates
-
-* Fix nil compare in eq/ne for interface values [66b143a0](https://github.com/gohugoio/hugo/commit/66b143a01d1c192619839b732ce188923ab15d60) [@bep](https://github.com/bep) [#5905](https://github.com/gohugoio/hugo/issues/5905)
-* Fix hugo package name and add godocs [4f93f8c6](https://github.com/gohugoio/hugo/commit/4f93f8c670b26258dc7e3a613c38dbc86d8eda76) [@moorereason](https://github.com/moorereason)
-
-### Output
-
-* Fix permalink in sitemap etc. when multiple permalinkable output formats [6b76841b](https://github.com/gohugoio/hugo/commit/6b76841b052b97625b8995f326d758b89f5c2349) [@bep](https://github.com/bep) [#5910](https://github.com/gohugoio/hugo/issues/5910)
-
-### Core
-
-* Fix PrevInSection/NextInSection for nested sections [bcbed4eb](https://github.com/gohugoio/hugo/commit/bcbed4ebdaf55b67abc521d69bba456c041a7e7d) [@bep](https://github.com/bep) [#5883](https://github.com/gohugoio/hugo/issues/5883)
-
-### Other
-
-* Fix concurrent initialization order [009076e5](https://github.com/gohugoio/hugo/commit/009076e5ee88fc46c95a9afd34f82f9386aa282a) [@bep](https://github.com/bep) [#5901](https://github.com/gohugoio/hugo/issues/5901)
-
-
-
-
-
diff --git a/docs/content/en/news/0.55.6-relnotes/index.md b/docs/content/en/news/0.55.6-relnotes/index.md
deleted file mode 100644
index c447aa061f2..00000000000
--- a/docs/content/en/news/0.55.6-relnotes/index.md
+++ /dev/null
@@ -1,13 +0,0 @@
-
----
-date: 2019-05-18
-title: "Hugo 0.55.6: One Bug Fix!"
-description: "Fixes some reported paginator crashes in server mode."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-This is a bug-fix release with one important fix. There have been reports about infrequent paginator crashes when running the Hugo server since 0.55.0. The reason have been narrowed down to that of parallel rebuilds. This isn't a new thing, but the changes in 0.55.0 made it extra important to serialize the page initialization. This release fixes that by protecting the `Build` method with a lock when running in server mode. [95ce2a40](https://github.com/gohugoio/hugo/commit/95ce2a40e734bb82b69f9a64270faf3ed69c92cc) [@bep](https://github.com/bep) [#5885](https://github.com/gohugoio/hugo/issues/5885)[#5968](https://github.com/gohugoio/hugo/issues/5968)
-
diff --git a/docs/content/en/news/0.56.0-relnotes/featured.png b/docs/content/en/news/0.56.0-relnotes/featured.png
deleted file mode 100644
index bd6410ead8f..00000000000
Binary files a/docs/content/en/news/0.56.0-relnotes/featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.56.0-relnotes/index.md b/docs/content/en/news/0.56.0-relnotes/index.md
deleted file mode 100644
index 631c1c6a5fe..00000000000
--- a/docs/content/en/news/0.56.0-relnotes/index.md
+++ /dev/null
@@ -1,189 +0,0 @@
-
----
-date: 2019-07-25
-title: "Hugo 0.56.0: Hugo Modules and Deployment"
-description: "This release adds powerful module support with dependency management for all component types, including content. And we now have built-in deployment support to GCS, S3, or Azure."
-categories: ["Releases"]
----
-
-
-**Hugo 0.56.0** is filled with improvements, but there are two main headliners: **Hugo Modules** and **Hugo Deploy**.
-
-**Hugo Deploy** is implemented by [@vangent](https://github.com/vangent) and brings built-in deployment support for GCS, S3, or Azure using the Hugo CLI. See the [Hugo Deploy Documentation](https://gohugo.io/hosting-and-deployment/hugo-deploy/) for more information.
-
-**Hugo Modules** is very much a community effort on the design and specification side, but [@bep](https://github.com/bep) has driven the implementation. Some notes about what all of this is about:
-
-* A new `module` configuration section where you can import almost anything. You can configure both your own file mounts and the file mounts of the modules you import. This is the new recommended way of configuring what you earlier put in `configDir`, `staticDir` etc. And it also allows you to mount folders in non-Hugo-projects, e.g. the `SCSS` folder in the Bootstrap GitHub project.
-* A module consists of a set of mounts to the standard 7 component types in Hugo: `static`, `content`, `layouts`, `data`, `assets`, `i18n`, and `archetypes`. Yes, Theme Components can now include content, which should be very useful, especially in bigger multilingual projects.
-* Modules not in your local file cache will be downloaded automatically and even "hot replaced" while the server is running.
-* Hugo Modules supports and encourages semver versioned modules, and uses the minimal version selection algorithm to resolve versions.
-* A new set of CLI commands are provided to manage all of this: `hugo mod init`, `hugo mod get`, `hugo mod graph`, `hugo mod tidy`, and `hugo mod vendor`.
-
-**Hugo Modules is powered by Go Modules.**
-
-This is all very much brand new and there are only a few example projects around:
-
-* https://github.com/bep/docuapi is a theme that has been ported to **Hugo Modules** while testing this feature. It is a good example of a non-Hugo-project mounted into Hugo's folder structure. It even shows a JS Bundler implementation in regular Go templates.
-* https://github.com/bep/my-modular-site is a very simple site used for testing.
-
-See the [Hugo Modules Documentation](https://gohugo.io/hugo-modules/) for more information.
-
-This release represents **104 contributions by 19 contributors** to the main Hugo code base.
-[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@vangent](https://github.com/vangent), [@niklasfasching](https://github.com/niklasfasching), and [@coliff](https://github.com/coliff) for their ongoing contributions.
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) and [@onedrawingperday](https://github.com/onedrawingperday) for their relentless work on keeping the themes site in pristine condition and to [@kaushalmodi](https://github.com/kaushalmodi) for his great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **48 contributions by 37 contributors**. A special thanks to [@davidsneighbour](https://github.com/davidsneighbour), [@bep](https://github.com/bep), [@BCNelson](https://github.com/BCNelson), and [@coliff](https://github.com/coliff) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 36902+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 440+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 328+ [themes](http://themes.gohugo.io/)
-
-
-## Notes
-
-* We have removed the "auto theme namespacing" of params from theme configuration. This was an undocumented and hidden feature that wasn't useful in practice.
-* We have revised and improved the symlinks support in Hugo: In earlier versions, symlinks were only fully supported for the content folders. With the introduction of the new very flexible file mounts, with content support even for what we have traditionally named "themes", we needed a more precise definition of symlink support in Hugo:
-
- * Symlinks are not supported outside of the main project ((the project you run `hugo` or `hugo server` from).
- * In the main project `static` mounts, only symlinks to files are supported.
- * In all other mounts in the main project, both file and directory symlinks are allowed.
-
-## Enhancements
-
-### Templates
-
-* Add Merge function [c624a779](https://github.com/gohugoio/hugo/commit/c624a77992c4f7c1bcb5f659e89332d8588986b7) [@bep](https://github.com/bep) [#5992](https://github.com/gohugoio/hugo/issues/5992)
-* Regenerate templates [b2a3d464](https://github.com/gohugoio/hugo/commit/b2a3d4644bb5a505db662b2927af6f80856a3076) [@bep](https://github.com/bep)
-* Unwrap any interface value in sort and where [8d898ad6](https://github.com/gohugoio/hugo/commit/8d898ad6672e0ccb62c5a29b6fccab24d980f104) [@bep](https://github.com/bep) [#5989](https://github.com/gohugoio/hugo/issues/5989)
-* Convert numeric values to float64 and compare them [fb007e9a](https://github.com/gohugoio/hugo/commit/fb007e9ae56f295abe9835485f98dcf3cc362420) [@tryzniak](https://github.com/tryzniak) [#5685](https://github.com/gohugoio/hugo/issues/5685)
-* Provide more detailed errors in Where [f76e5011](https://github.com/gohugoio/hugo/commit/f76e50118b8b5dd8989d068db35222bfa0a242d8) [@moorereason](https://github.com/moorereason)
-* Return error on invalid input in in [7fbfedf0](https://github.com/gohugoio/hugo/commit/7fbfedf01367ff076c3c875b183789b769b99241) [@bep](https://github.com/bep) [#5875](https://github.com/gohugoio/hugo/issues/5875)
-* Make Pages etc. work with the in func [06f56fc9](https://github.com/gohugoio/hugo/commit/06f56fc983d460506d39b3a6f638b1632af07073) [@bep](https://github.com/bep) [#5875](https://github.com/gohugoio/hugo/issues/5875)
-* Make Pages etc. work in uniq [d7a67dcb](https://github.com/gohugoio/hugo/commit/d7a67dcb51829b12d492d3f2ee4f6e2a3834da63) [@bep](https://github.com/bep) [#5852](https://github.com/gohugoio/hugo/issues/5852)
-* Handle late transformation of templates [2957795f](https://github.com/gohugoio/hugo/commit/2957795f5276cc9bc8d438da2d7d9b61defea225) [@bep](https://github.com/bep) [#5865](https://github.com/gohugoio/hugo/issues/5865)
-
-### Output
-
-* Remove comma after URL in new site output [de7b9475](https://github.com/gohugoio/hugo/commit/de7b9475c049e5df5b076d80799ebcbee3eb84c8) [@snorfalorpagus](https://github.com/snorfalorpagus)
-
-### Core
-
-* Add a symdiff test [072aa7f1](https://github.com/gohugoio/hugo/commit/072aa7f110ddc8a8b9dbc6d4cad3e5ba6c2ac4d0) [@bep](https://github.com/bep)
-* Add testfile to .gitignore [7611078d](https://github.com/gohugoio/hugo/commit/7611078daef32306ab31fe360db9895cdd3626d3) [@bep](https://github.com/bep)
-* Add another site benchmark [dc1d4a92](https://github.com/gohugoio/hugo/commit/dc1d4a9200c54e631775f34725140fd5009aa561) [@bep](https://github.com/bep)
-* Prevent parallel server rebuilds [95ce2a40](https://github.com/gohugoio/hugo/commit/95ce2a40e734bb82b69f9a64270faf3ed69c92cc) [@bep](https://github.com/bep) [#5885](https://github.com/gohugoio/hugo/issues/5885)[#5968](https://github.com/gohugoio/hugo/issues/5968)
-* Disable racy test [ad5703a9](https://github.com/gohugoio/hugo/commit/ad5703a91712cd245231ba8fdbc49632c794a165) [@bep](https://github.com/bep) [#5926](https://github.com/gohugoio/hugo/issues/5926)
-* Avoid recloning of shortcode templates [69a56420](https://github.com/gohugoio/hugo/commit/69a56420aec5bf5abb846701d4a5ec67fe060d96) [@bep](https://github.com/bep) [#5890](https://github.com/gohugoio/hugo/issues/5890)
-* No links for bundled pages [0775c98e](https://github.com/gohugoio/hugo/commit/0775c98e6c5b700e46adaaf190fc3f693a6ab002) [@bep](https://github.com/bep) [#5882](https://github.com/gohugoio/hugo/issues/5882)
-* Add some OutputFormats.Get tests [7aeeb60d](https://github.com/gohugoio/hugo/commit/7aeeb60d7ee71690461df92ff41cb8b2f7f5aa61) [@bep](https://github.com/bep) [#5877](https://github.com/gohugoio/hugo/issues/5877)
-* Add some integration tests for in/uniq using Pages [6c80acbd](https://github.com/gohugoio/hugo/commit/6c80acbd5e314dd92fc075551ffabafaae01dca7) [@bep](https://github.com/bep) [#5875](https://github.com/gohugoio/hugo/issues/5875)[#5852](https://github.com/gohugoio/hugo/issues/5852)
-* Add more tests for Permalinkable [35f41834](https://github.com/gohugoio/hugo/commit/35f41834ea3a8799b9b7eda360cf8d30b1b727ba) [@bep](https://github.com/bep) [#5849](https://github.com/gohugoio/hugo/issues/5849)
-* Add a test for parent's resources in shortcode [8d7607ae](https://github.com/gohugoio/hugo/commit/8d7607aed10b3fe7373126ff5fa7dae36c559d7f) [@bep](https://github.com/bep) [#5833](https://github.com/gohugoio/hugo/issues/5833)
-
-### Other
-
-* Add Hugo Modules docs [77bf2991](https://github.com/gohugoio/hugo/commit/77bf2991b1690bcde8c6570cd4c12f2041d93096) [@bep](https://github.com/bep)
-* Block symlink dir traversal for /static [e5f22997](https://github.com/gohugoio/hugo/commit/e5f229974166402f51e4ee0695ffb4d1e09fa174) [@bep](https://github.com/bep)
-* modules: Gofmt [87a07282](https://github.com/gohugoio/hugo/commit/87a07282a2f01779e098cde0aaee1bae34dc32e6) [@bep](https://github.com/bep)
-* Rename disabled => disable in config [882d678b](https://github.com/gohugoio/hugo/commit/882d678bbf2a149a90e2aed4341d7f6fc2cb394d) [@bep](https://github.com/bep)
-* Regenerate CLI docs [215d2ed8](https://github.com/gohugoio/hugo/commit/215d2ed883d5adbde1d119d33e2f2e88c8435f41) [@bep](https://github.com/bep)
-* Regenerate data helpers [23adc0c2](https://github.com/gohugoio/hugo/commit/23adc0c2d96d33b426836b09683d2440d6186728) [@bep](https://github.com/bep)
-* Add Hugo Modules [9f5a9207](https://github.com/gohugoio/hugo/commit/9f5a92078a3f388b52d597b5a59af5c933a112d2) [@bep](https://github.com/bep) [#5973](https://github.com/gohugoio/hugo/issues/5973)[#5996](https://github.com/gohugoio/hugo/issues/5996)[#6010](https://github.com/gohugoio/hugo/issues/6010)[#5911](https://github.com/gohugoio/hugo/issues/5911)[#5940](https://github.com/gohugoio/hugo/issues/5940)[#6074](https://github.com/gohugoio/hugo/issues/6074)[#6082](https://github.com/gohugoio/hugo/issues/6082)[#6092](https://github.com/gohugoio/hugo/issues/6092)
-* Tidy [47953148](https://github.com/gohugoio/hugo/commit/47953148b6121441d0147c960a99829c53b5a5ba) [@bep](https://github.com/bep)
-* Update xerrors [ac101aba](https://github.com/gohugoio/hugo/commit/ac101aba4fde02dc8273a3f06a3b4267ca501a3d) [@bep](https://github.com/bep)
-* Ignore errors in go mod download [58a47ccd](https://github.com/gohugoio/hugo/commit/58a47ccde00e2425364eaa5c2123e0718a2ee3f2) [@bep](https://github.com/bep)
-* Update Chroma [95b1d301](https://github.com/gohugoio/hugo/commit/95b1d3013b4717f8b02093a99d1d0c4a6a1ca929) [@bep](https://github.com/bep) [#6088](https://github.com/gohugoio/hugo/issues/6088)
-* Change postcss to check for local installation under node_modules/.bin [a5604e18](https://github.com/gohugoio/hugo/commit/a5604e18b0cb260b7748949b12c05814ced50815) [@ericselin](https://github.com/ericselin) [#5091](https://github.com/gohugoio/hugo/issues/5091)
-* Add org to front matter formats [020086cb](https://github.com/gohugoio/hugo/commit/020086cb2b0396909d831abf66b8f1455e6f7e6a) [@niklasfasching](https://github.com/niklasfasching)
-* Update go-org [8524baee](https://github.com/gohugoio/hugo/commit/8524baee167fa6a8684569e7acff225c16c301c7) [@niklasfasching](https://github.com/niklasfasching)
-* Pagination - do not render href if no next item [88c8a15b](https://github.com/gohugoio/hugo/commit/88c8a15be18a0bd1bd9b9cb175f7a68f2b9cd355) [@markmandel](https://github.com/markmandel)
-* Include path to source page in non-relative ref/relref warning [59c4bc52](https://github.com/gohugoio/hugo/commit/59c4bc52ed6b146baa6cca97d054004297ea098a) [@justinvp](https://github.com/justinvp)
-* Update link to prevent redirect [ff10aa52](https://github.com/gohugoio/hugo/commit/ff10aa522dd4d2741e8f4e50a4a40a0854232f23) [@coliff](https://github.com/coliff)
-* Update URLs [9f258d2b](https://github.com/gohugoio/hugo/commit/9f258d2b8f98a137c5c8c4586a3db3e3c53a0040) [@coliff](https://github.com/coliff)
-* Introduce '#+KEY[]:' array notation [fad183c4](https://github.com/gohugoio/hugo/commit/fad183c4ae55069be9246e64ab1c8b2f43d08d06) [@niklasfasching](https://github.com/niklasfasching)
-* Replace goorgeous with go-org [b6867bf8](https://github.com/gohugoio/hugo/commit/b6867bf8068fcaaddf1cb7478f4d52a9c1be1411) [@niklasfasching](https://github.com/niklasfasching)
-* Default --target to the first deployment target [9df57154](https://github.com/gohugoio/hugo/commit/9df57154ee3e3185d024bfe376101b404d8b7cc4) [@vangent](https://github.com/vangent)
-* Add safety barrier between concatenated javascript resources [35abce27](https://github.com/gohugoio/hugo/commit/35abce27cabee43cc751db55a75b927f26275833) [@vincent99](https://github.com/vincent99)
-* Update CLI doc for "long" form [8914fe7e](https://github.com/gohugoio/hugo/commit/8914fe7ed7e7e55e07be32564159310c90e2dbd4) [@vangent](https://github.com/vangent)
-* Drop dashes in http header matcher attributes; other changes from code review [b0f536fb](https://github.com/gohugoio/hugo/commit/b0f536fb276f63df0b0b8d92aeda80affb4b6a34) [@vangent](https://github.com/vangent)
-* Add documentation for "hugo deploy" [1384d77a](https://github.com/gohugoio/hugo/commit/1384d77a04d7027d719993c6f54f892b5e7974db) [@vangent](https://github.com/vangent)
-* remove TODO comment about subfolders; handled by GoCDK blob URLs [5e83f425](https://github.com/gohugoio/hugo/commit/5e83f4256279149879a8e88cb02679dd00e8da2b) [@vangent](https://github.com/vangent)
-* Update gocloud.dev to v0.15.0. [b376b268](https://github.com/gohugoio/hugo/commit/b376b2685a2e21961052a0946ab8a6acc076d4da) [@vangent](https://github.com/vangent)
-* Return nil when not found in resources.Get [4c560020](https://github.com/gohugoio/hugo/commit/4c560020bc0c50f8004873be8adf83698b7c095a) [@bep](https://github.com/bep) [#5999](https://github.com/gohugoio/hugo/issues/5999)
-* Update Viper [d44d3ea8](https://github.com/gohugoio/hugo/commit/d44d3ea89865baa33170244cac72a7ce26419b15) [@bep](https://github.com/bep) [#5954](https://github.com/gohugoio/hugo/issues/5954)
-* Remove references to Google+ [d1cf53f5](https://github.com/gohugoio/hugo/commit/d1cf53f5f4d71b9461e0fe58193b5a8418b572e7) [@brunoamaral](https://github.com/brunoamaral)
-* Update gitmap to get CommitDate field [811ee996](https://github.com/gohugoio/hugo/commit/811ee996a625b5eb3464a34a1623260b11c0bfd3) [@bep](https://github.com/bep)
-* Create new 'hugo list all' command [5b4b8bb3](https://github.com/gohugoio/hugo/commit/5b4b8bb3c1ecb30e7a38ed44eb795f1d972cd320) [@rusnasonov](https://github.com/rusnasonov) [#5904](https://github.com/gohugoio/hugo/issues/5904)
-* Medium -> Hugo exporting tool [2278b0eb](https://github.com/gohugoio/hugo/commit/2278b0eb02ccdd3c2d4358d39074767d33fecb71) [@Ahirice](https://github.com/Ahirice)
-* Switch base image for final build [41974303](https://github.com/gohugoio/hugo/commit/41974303f3e5e6d7deb87a791aba512dbf1b9c59) [@brianredbeard](https://github.com/brianredbeard) [#5970](https://github.com/gohugoio/hugo/issues/5970)[#5056](https://github.com/gohugoio/hugo/issues/5056)
-* Merge branch 'release-0.55.6' [6b3f1a10](https://github.com/gohugoio/hugo/commit/6b3f1a10028c81b776a5797bbd02c86957f8f042) [@bep](https://github.com/bep)
-* Release 0.55.6 [9b48c5d6](https://github.com/gohugoio/hugo/commit/9b48c5d6bd56741dac714324a6ae59f6374bccdc) [@bep](https://github.com/bep)
-* Update to Go 1.12.5 and Go 1.11.10 [71b8d8b6](https://github.com/gohugoio/hugo/commit/71b8d8b6a4127acacda8ada08cd61d7bfb18e962) [@bep](https://github.com/bep) [#5944](https://github.com/gohugoio/hugo/issues/5944)
-* Support configuration of upload order [527cf1ab](https://github.com/gohugoio/hugo/commit/527cf1ab03fe4746885e90a197bc25decad88f89) [@vangent](https://github.com/vangent)
-* Support invalidating a CloudFront CDN cache [f4956d9a](https://github.com/gohugoio/hugo/commit/f4956d9aae69b1cb5715114cf5242fd80a9cabc7) [@vangent](https://github.com/vangent)
-* Move the package below /langs [2838d58b](https://github.com/gohugoio/hugo/commit/2838d58b1daa0f6a337125c5a64d06215901c5d6) [@bep](https://github.com/bep)
-* compute MD5 by reading if List didn't provide one [f330e869](https://github.com/gohugoio/hugo/commit/f330e869e42dc782a48c045aea5d29a134e225cb) [@vangent](https://github.com/vangent)
-* Use proxy.golang.org [0091b1f8](https://github.com/gohugoio/hugo/commit/0091b1f893aba6a0060c392f58fcc0351ee0db66) [@bep](https://github.com/bep)
-* Add a "deploy" command [c7165589](https://github.com/gohugoio/hugo/commit/c7165589b3be5923f1275f0024818e6ae889d881) [@vangent](https://github.com/vangent)
-* Release 0.55.5 [e33ed29b](https://github.com/gohugoio/hugo/commit/e33ed29b754ac1982321e87e54a37c9bb59e53ca) [@bep](https://github.com/bep)
-* Regenerate docs helper [cee181c3](https://github.com/gohugoio/hugo/commit/cee181c3a67fe04b8e0c9f2807c5aa2871df474e) [@bep](https://github.com/bep)
-* Update blackfriday to v1.5.2 [1cbb501b](https://github.com/gohugoio/hugo/commit/1cbb501be8aa83b08865fbb6ad5aee254946712f) [@dbirks](https://github.com/dbirks)
-* Release 0.55.4 [1707f1a5](https://github.com/gohugoio/hugo/commit/1707f1a5f52b8036d675d6ce66fd844effcab9fe) [@bep](https://github.com/bep)
-* Avoid rebuilding the Translations map for every lookup [4756ec3c](https://github.com/gohugoio/hugo/commit/4756ec3cd8ef998f889619fe11be70cc900e2b75) [@bep](https://github.com/bep) [#5892](https://github.com/gohugoio/hugo/issues/5892)
-* Init mem profile at the end [4c3c5120](https://github.com/gohugoio/hugo/commit/4c3c5120389cc95edc63b8f18a0eee786aa0c5e2) [@bep](https://github.com/bep)
-* Release 0.55.3 [c85b726f](https://github.com/gohugoio/hugo/commit/c85b726f8a3cca2f06f766e5982dc0023d0dd12c) [@bep](https://github.com/bep)
-* Regenerate docs helper [75b16e30](https://github.com/gohugoio/hugo/commit/75b16e30ec55e82a8024cc4d27880d9b79e0fa41) [@bep](https://github.com/bep)
-* Replace IsDraft with Draft in list command [3e421bd4](https://github.com/gohugoio/hugo/commit/3e421bd47cd35061df89c1c127ec8fa4ae368449) [@bep](https://github.com/bep) [#5873](https://github.com/gohugoio/hugo/issues/5873)
-* Release 0.55.2 [fcd63a86](https://github.com/gohugoio/hugo/commit/fcd63a865e731077a0d508084460b6fe6ec82c33) [@bep](https://github.com/bep)
-* Release 0.55.1 [adb776b2](https://github.com/gohugoio/hugo/commit/adb776b22babc0d312ce7b83abbe6f5107c180d7) [@bep](https://github.com/bep)
-* Remove the space in `. RelPermalink` [7966c0b5](https://github.com/gohugoio/hugo/commit/7966c0b5b7b2297527f8be9040b793de5e4e3f48) [@yihui](https://github.com/yihui)
-
-## Fixes
-
-### Templates
-
-* Fix slice type handling in sort [e8a716b2](https://github.com/gohugoio/hugo/commit/e8a716b23a1ca78cf29460daacd4ba49bbc05ad1) [@bep](https://github.com/bep) [#6023](https://github.com/gohugoio/hugo/issues/6023)
-* Fix internal templates usage of safeHTMLAttr [e22b3f54](https://github.com/gohugoio/hugo/commit/e22b3f54c3d8ce6567c21c63beab0b03cf7983ea) [@rhcarvalho](https://github.com/rhcarvalho) [#5236](https://github.com/gohugoio/hugo/issues/5236)[#5246](https://github.com/gohugoio/hugo/issues/5246)
-* Fix nil compare in eq/ne for interface values [66b143a0](https://github.com/gohugoio/hugo/commit/66b143a01d1c192619839b732ce188923ab15d60) [@bep](https://github.com/bep) [#5905](https://github.com/gohugoio/hugo/issues/5905)
-* Fix hugo package name and add godocs [4f93f8c6](https://github.com/gohugoio/hugo/commit/4f93f8c670b26258dc7e3a613c38dbc86d8eda76) [@moorereason](https://github.com/moorereason)
-
-### Output
-
-* Fix permalink in sitemap etc. when multiple permalinkable output formats [6b76841b](https://github.com/gohugoio/hugo/commit/6b76841b052b97625b8995f326d758b89f5c2349) [@bep](https://github.com/bep) [#5910](https://github.com/gohugoio/hugo/issues/5910)
-* Fix links for non-HTML output formats [c7dd66bf](https://github.com/gohugoio/hugo/commit/c7dd66bfe2e32430f9b1a3126c67014e40d8405e) [@bep](https://github.com/bep) [#5877](https://github.com/gohugoio/hugo/issues/5877)
-* Fix menu URL when multiple permalinkable output formats [ea529c84](https://github.com/gohugoio/hugo/commit/ea529c847ebc0267c6d0426cc8f77d5c76c73fe4) [@bep](https://github.com/bep) [#5849](https://github.com/gohugoio/hugo/issues/5849)
-
-### Core
-
-* Fix broken test [fa28df10](https://github.com/gohugoio/hugo/commit/fa28df1058e0131364cea2e3ac7f80e934d024a1) [@bep](https://github.com/bep)
-* Fix bundle path when slug is set [3e6cb2cb](https://github.com/gohugoio/hugo/commit/3e6cb2cb77e16be5b6ddd4ae55d5fc6bfba2d226) [@bep](https://github.com/bep) [#4870](https://github.com/gohugoio/hugo/issues/4870)
-* Fix PrevInSection/NextInSection for nested sections [bcbed4eb](https://github.com/gohugoio/hugo/commit/bcbed4ebdaf55b67abc521d69bba456c041a7e7d) [@bep](https://github.com/bep) [#5883](https://github.com/gohugoio/hugo/issues/5883)
-* Fix shortcode version=1 logic [33c73811](https://github.com/gohugoio/hugo/commit/33c738116c26e2ac37f4bd48159e8e3197fd7b39) [@bep](https://github.com/bep) [#5831](https://github.com/gohugoio/hugo/issues/5831)
-* Fix Pages reinitialization on rebuilds [9b17cbb6](https://github.com/gohugoio/hugo/commit/9b17cbb62a056ea7e26b1146cbf3ba42f5acf805) [@bep](https://github.com/bep) [#5833](https://github.com/gohugoio/hugo/issues/5833)
-* Fix shortcode namespace issue [56550d1e](https://github.com/gohugoio/hugo/commit/56550d1e449f45ebee398ac8a9e3b9818b3ee60e) [@bep](https://github.com/bep) [#5863](https://github.com/gohugoio/hugo/issues/5863)
-* Fix false WARNINGs in lang prefix check [7881b096](https://github.com/gohugoio/hugo/commit/7881b0965f8b83d03379e9ed102cd0c3bce297e2) [@bep](https://github.com/bep) [#5860](https://github.com/gohugoio/hugo/issues/5860)
-* Fix bundle resource publishing when multiple output formats [49d0a826](https://github.com/gohugoio/hugo/commit/49d0a82641581aa7dd66b9d5e8c7d75e23260083) [@bep](https://github.com/bep) [#5858](https://github.com/gohugoio/hugo/issues/5858)
-* Fix panic for unused taxonomy content files [b799b12f](https://github.com/gohugoio/hugo/commit/b799b12f4a693dfeae8a5a362f131081a727bb8f) [@bep](https://github.com/bep) [#5847](https://github.com/gohugoio/hugo/issues/5847)
-* Fix dates for sections with dates in front matter [70148672](https://github.com/gohugoio/hugo/commit/701486728e21bc0c6c78c2a8edb988abdf6116c7) [@bep](https://github.com/bep) [#5854](https://github.com/gohugoio/hugo/issues/5854)
-* Fix simple menu config [9e9a1f92](https://github.com/gohugoio/hugo/commit/9e9a1f92baf151f8d840d6b5b963945d1410ce25) [@bep](https://github.com/bep)
-
-### Other
-
-* Fix test on Windows [e5b6e208](https://github.com/gohugoio/hugo/commit/e5b6e2085aba74767ace269cd5f8a746230b4fa4) [@bep](https://github.com/bep)
-* Fix livereload for @import case [2fc0abd2](https://github.com/gohugoio/hugo/commit/2fc0abd22a37d90b6f1032eef46191a7bddf41bd) [@bep](https://github.com/bep) [#6106](https://github.com/gohugoio/hugo/issues/6106)
-* Fix typo s/Meny/Menu/ [90b0127f](https://github.com/gohugoio/hugo/commit/90b0127f63e9cd5bf3a8bd4282237db224a3c263) [@kaushalmodi](https://github.com/kaushalmodi)
-* Add tests; fix Windows [5dc6d0df](https://github.com/gohugoio/hugo/commit/5dc6d0df94076e116934c83b837e2dd416efa784) [@vangent](https://github.com/vangent)
-* Fix concurrent initialization order [009076e5](https://github.com/gohugoio/hugo/commit/009076e5ee88fc46c95a9afd34f82f9386aa282a) [@bep](https://github.com/bep) [#5901](https://github.com/gohugoio/hugo/issues/5901)
-* Fix WeightedPages in union etc. [f2795d4d](https://github.com/gohugoio/hugo/commit/f2795d4d2cef30170af43327f3ff7114923833b1) [@bep](https://github.com/bep) [#5850](https://github.com/gohugoio/hugo/issues/5850)
-* Fix [4d425a86](https://github.com/gohugoio/hugo/commit/4d425a86f5c03a5cca27d4e0f99d61acbb938d80) [@bep](https://github.com/bep)
-* Fix paginator refresh on server change [f7375c49](https://github.com/gohugoio/hugo/commit/f7375c497239115cd30ae42af6b4d298e4e7ad7d) [@bep](https://github.com/bep) [#5838](https://github.com/gohugoio/hugo/issues/5838)
-* Fix .RSSLinke deprecation message [3b86b4a9](https://github.com/gohugoio/hugo/commit/3b86b4a9f5ce010c9714d813d5b8ecddda22c69f) [@bep](https://github.com/bep) [#4427](https://github.com/gohugoio/hugo/issues/4427)
-
-
-
-
-
diff --git a/docs/content/en/news/0.56.1-relnotes/index.md b/docs/content/en/news/0.56.1-relnotes/index.md
deleted file mode 100644
index c83250fe58e..00000000000
--- a/docs/content/en/news/0.56.1-relnotes/index.md
+++ /dev/null
@@ -1,21 +0,0 @@
-
----
-date: 2019-07-28
-title: "0.56.1: Two Bug Fixes"
-description: "This version fixes a couple of regressions introduced in 0.56.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-* common/maps: Do not return error on params dot access on incompatible types [e393c629](https://github.com/gohugoio/hugo/commit/e393c6290e827111a8a2e486791dc21f63a92b55) [@bep](https://github.com/bep) [#6121](https://github.com/gohugoio/hugo/issues/6121)
-* resources: Fix image Width/Height regression [93d02aab](https://github.com/gohugoio/hugo/commit/93d02aabe6e611d65c428a9c5669b422e1bcf5e8) [@bep](https://github.com/bep) [#6120](https://github.com/gohugoio/hugo/issues/6120)
-* modules: Set GO111MODULE=on [e5fe3789](https://github.com/gohugoio/hugo/commit/e5fe378925c16c75902bbb46499c376c530ebdb5) [@bep](https://github.com/bep) [#6114](https://github.com/gohugoio/hugo/issues/6114)
-
-
-
diff --git a/docs/content/en/news/0.56.2-relnotes/index.md b/docs/content/en/news/0.56.2-relnotes/index.md
deleted file mode 100644
index 67c5f8b6d91..00000000000
--- a/docs/content/en/news/0.56.2-relnotes/index.md
+++ /dev/null
@@ -1,26 +0,0 @@
-
----
-date: 2019-07-30
-title: "Hugo 0.56.2: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.56.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-* Add some more content language test assertions [84bc8d84](https://github.com/gohugoio/hugo/commit/84bc8d84e4d2ec1fc94aee3113ebc570a28d1d16) [@bep](https://github.com/bep) [#6136](https://github.com/gohugoio/hugo/issues/6136)
-* commands: Fix config reloading in Vim and similar [6eca0a3d](https://github.com/gohugoio/hugo/commit/6eca0a3dee77f0e764b1de2e10c10ec2b7cf8ef1) [@bep](https://github.com/bep) [#6139](https://github.com/gohugoio/hugo/issues/6139)
-* Fix Jekyll import [e28bd4c0](https://github.com/gohugoio/hugo/commit/e28bd4c0f843f39cfcb715b6c9c7d249bad5b500) [@bep](https://github.com/bep) [#6131](https://github.com/gohugoio/hugo/issues/6131)
-* Fix image format detection for upper case extensions, e.g. JPG [c62bbf7b](https://github.com/gohugoio/hugo/commit/c62bbf7b11d68d52ef11a4c6c70660914c473d08) [@bep](https://github.com/bep) [#6137](https://github.com/gohugoio/hugo/issues/6137)
-* Fix i18n project vs theme order [00a238e3](https://github.com/gohugoio/hugo/commit/00a238e32c82b0651e4145e306840cffa46e535d) [@bep](https://github.com/bep) [#6134](https://github.com/gohugoio/hugo/issues/6134)
-* Add proper error message when receiving nil in Resource transformation [e5f96024](https://github.com/gohugoio/hugo/commit/e5f960245938d8d8b4e99f312e9907f8d3aebf7a) [@bep](https://github.com/bep) [#6128](https://github.com/gohugoio/hugo/issues/6128)
-* Merge branch 'release-0.56.1' [9f497e7b](https://github.com/gohugoio/hugo/commit/9f497e7b5f77d0eb45d932a2301e648a3cd2d88f) [@bep](https://github.com/bep)
-* deps: Update go-org to v0.1.2 [56908509](https://github.com/gohugoio/hugo/commit/56908509eb3a5779743a2314c05693a732b7feb3) [@niklasfasching](https://github.com/niklasfasching)
-
-
-
diff --git a/docs/content/en/news/0.56.3-relnotes/index.md b/docs/content/en/news/0.56.3-relnotes/index.md
deleted file mode 100644
index c505e520df1..00000000000
--- a/docs/content/en/news/0.56.3-relnotes/index.md
+++ /dev/null
@@ -1,47 +0,0 @@
-
----
-date: 2019-07-31
-title: "Hugo 0.56.3: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.56.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-This is a bug-fix release with a couple of important fixes. After getting feedback about the new **Hugo Modules** feature, this release also adds some minor improvements:
-
-It adds support for overlapping file mounts, even for the filesystems where we walk down the directory structure. One relevant example that is fixed by this release:
-
-{{< code-toggle file="config" >}}
-[module]
-[[module.mounts]]
-source="content1"
-target="content"
-[[module.mounts]]
-source="content2"
-target="content/docs"
-{{< /code-toggle >}}
-
-The above is obviously both common and very useful. This was never an issue with the situations where you load a specific file/directory (e.g. `resources.Get "a/b/c/d/sunset.jpg"`).
-
-User feedback also told us that these file mounts were a little hard to debug, so we added a new command that prints the configured mounts as a JSON:
-
-```bash
-hugo config mounts
-```
-
-* hugolib: Fix bundle header clone logic [0e086785](https://github.com/gohugoio/hugo/commit/0e086785fa4be8086256e9d7de6cda78e18d00ee) [@bep](https://github.com/bep) [#6136](https://github.com/gohugoio/hugo/issues/6136)
-* docs: Regenerate CLI docs [02b947ea](https://github.com/gohugoio/hugo/commit/02b947eaa3cc68404180d796a2f7119dce074539) [@bep](https://github.com/bep)
-* commands: Add "hugo config mounts" command [d7c233af](https://github.com/gohugoio/hugo/commit/d7c233afee6a16b1947f60b7e5450e40612997bb) [@bep](https://github.com/bep) [#6144](https://github.com/gohugoio/hugo/issues/6144)
-* commands: Cleanup the hugo config command [45ee8a7a](https://github.com/gohugoio/hugo/commit/45ee8a7a52213bf394c7f41a72be78084ddc789a) [@bep](https://github.com/bep) [#6144](https://github.com/gohugoio/hugo/issues/6144)
-* Move the mount duplicate filter to the modules package [4b6c5eba](https://github.com/gohugoio/hugo/commit/4b6c5eba306e6e69f3dd07a6c102bfc8040b38c9) [@bep](https://github.com/bep)
-* Allow overlap in module mounts [edf9f0a3](https://github.com/gohugoio/hugo/commit/edf9f0a354e5eaa556f8faed70b5243b7273b35c) [@bep](https://github.com/bep) [#6146](https://github.com/gohugoio/hugo/issues/6146)
-* Fix self-mounts on the main project [36220851](https://github.com/gohugoio/hugo/commit/36220851e4ed7fc3fa78aa250d001d5f922210e7) [@bep](https://github.com/bep) [#6143](https://github.com/gohugoio/hugo/issues/6143)
-
-
-
-
-
-
-
diff --git a/docs/content/en/news/0.57.0-relnotes/hugo-57-poster-featured.png b/docs/content/en/news/0.57.0-relnotes/hugo-57-poster-featured.png
deleted file mode 100644
index aeb5561c86b..00000000000
Binary files a/docs/content/en/news/0.57.0-relnotes/hugo-57-poster-featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.57.0-relnotes/index.md b/docs/content/en/news/0.57.0-relnotes/index.md
deleted file mode 100644
index d99150e08aa..00000000000
--- a/docs/content/en/news/0.57.0-relnotes/index.md
+++ /dev/null
@@ -1,119 +0,0 @@
-
----
-date: 2019-08-14
-title: "Hugo 0.57: The Cascading Edition"
-description: "Hugo 0.57 brings cascading front matter, alphabetical sorting, resource loading from Assets with wildcards. And it's faster."
-categories: ["Releases"]
----
-
-Hugo 0.57 brings **Cascading Front Matter**, **Alphabetical Sorting**, **Resources Loading from Assets with Wildcards**. And more.
-
-**Cascading Front Matter**: We have added a new and powerful `cascade` keyword to Hugo's front matter. This can be added to any index node in `_index.md`. Any values in `cascade` will be merged into itself and all the descendants.
-
-```yaml
-title: "My Blog"
-icon: "world.png"
-cascade:
- icon: "flag.png"
- outputs: ["HTML"]
- type: "blog"
- ```
-
-It's worth noting that the `cascade` element itself will also be merged. Also, to grasp the full value of this feature, remember that front matter in Hugo is both **data** and **behaviour**: You can tell Hugo how to process a subset of the pages (some example keywords are `layout`, `type`, `outputs`, `weight`) using the `cascade` keyword, e.g. _"I want this subsection to be rendered in both the `HTML` and `Calendar` Output Formats"_.
-
-This feature is created by[@regisphilibert](https://github.com/regisphilibert) and [@bep](https://github.com/bep) See [#6041](https://github.com/gohugoio/hugo/issues/6041) for details.
-
-**Resources Loading from Assets with Wildcards**: We have added two new sought after template functions to the `resources` namespace: `resources.Match` and `resources.GetMatch`. These behaves like their namesake methods on `Page` (with [super-asterisk wildcard support](https://github.com/gobwas/glob)), but searches in all the resources in Assets. E.g. `{{ $prettyImages := resources.Match "images/**pretty.jpg" }}` will give a slice of all "pretty pictures". Another relevant example: `{{ $js := resources.Match "libs/*.js" | resources.Concat "js/bundle.js" }}`.
-
-**Performance:** In general, this version is slightly faster and more memory effective. In particular, we have fixed a performance issue with the replacer step that greatly improves the build speed of certain large and content-rich sites (thanks to [@vazrupe](https://github.com/vazrupe) for the fix).
-
-This release represents **46 contributions by 8 contributors** to the main Hugo code base.[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@muesli](https://github.com/muesli), [@XhmikosR](https://github.com/XhmikosR), and [@vazrupe](https://github.com/vazrupe) for their ongoing contributions.
-
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) and [@onedrawingperday](https://github.com/onedrawingperday) for their relentless work on keeping the themes site in pristine condition and to [@kaushalmodi](https://github.com/kaushalmodi) for his great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **13 contributions by 7 contributors**. A special thanks to [@regisphilibert](https://github.com/regisphilibert), [@bep](https://github.com/bep), [@kenberkeley](https://github.com/kenberkeley), and [@davidsneighbour](https://github.com/davidsneighbour) for their work on the documentation site.
-
-Hugo now has:
-
-* 37336+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 440+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 334+ [themes](http://themes.gohugo.io/)
-
-## Notes
-
-* All string sorting in Hugo is now alphabetical/lexicographical.
-* `home.Pages` now only returns pages in the top level section. Before this release, it included _all regular pages in the site_. This made it easy to list all the pages on home page, but it also meant that you needed to take special care if you wanted to navigate the page tree from top to bottom. If you need _all regular pages_, use `.Site.RegularPages`. Also see [#6153](https://github.com/gohugoio/hugo/issues/6153).
-* `.Pages` now include sections. We have added `.RegularPages` as a convenience method if you want the old behaviour. See [#6154](https://github.com/gohugoio/hugo/issues/6154) for details.
-* Hugo now only "auto create" sections for the home page and the top level folders. The other sections need a `_index.md` file. See [#6171](https://github.com/gohugoio/hugo/issues/6171) for details.
-
-
-## Enhancements
-
-### Templates
-
-* Regenerate templates [2d1d3367](https://github.com/gohugoio/hugo/commit/2d1d33673d82c5073335e18944744606a71a5029) [@bep](https://github.com/bep)
-* Always load GitHub Gists over HTTPS [be0d4efc](https://github.com/gohugoio/hugo/commit/be0d4efc3db18035a04b188e089c09cdd8e04365) [@coliff](https://github.com/coliff)
-
-### Core
-
-* Remove temporary warning [4644b95b](https://github.com/gohugoio/hugo/commit/4644b95bd568946429482aa36eeaff1eec6a7075) [@bep](https://github.com/bep)
-* Add some more site benchmarks [df374851](https://github.com/gohugoio/hugo/commit/df374851a0683f1446f33a4afef74c42f7d3eaaf) [@bep](https://github.com/bep)
-
-### Other
-
-* Add FileInfo to resources created with resources.Match etc. [1089cfe4](https://github.com/gohugoio/hugo/commit/1089cfe4e1c35bec1f269b8280da43b367b5d070) [@bep](https://github.com/bep) [#6190](https://github.com/gohugoio/hugo/issues/6190)
-* Improve the server assets cache invalidation logic [cd575023](https://github.com/gohugoio/hugo/commit/cd575023af846aa18ffa709f37bc70277e98cad3) [@bep](https://github.com/bep) [#6199](https://github.com/gohugoio/hugo/issues/6199)
-* Do not fail build on errors in theme.toml [63150981](https://github.com/gohugoio/hugo/commit/6315098104ff80f8be6d5ae812835b4b4079582e) [@bep](https://github.com/bep) [#6162](https://github.com/gohugoio/hugo/issues/6162)
-* Add resources.Match and resources.GetMatch [b64617fe](https://github.com/gohugoio/hugo/commit/b64617fe4f90da030bcf4a9c5a4913393ce96b14) [@bep](https://github.com/bep) [#6190](https://github.com/gohugoio/hugo/issues/6190)
-* Convert from testify to quicktest [9e571827](https://github.com/gohugoio/hugo/commit/9e571827055dedb46b78c5db3d17d6913f14870b) [@bep](https://github.com/bep)
-* Avoid unnecessary conversions [6027ee11](https://github.com/gohugoio/hugo/commit/6027ee11082d0b9d72de1d4d1980a702be294ad2) [@muesli](https://github.com/muesli)
-* Simplify code [a93cbb0d](https://github.com/gohugoio/hugo/commit/a93cbb0d6cc6e3a78ba34aa372abc5b41ca24b2c) [@muesli](https://github.com/muesli)
-* Implement cascading front matter [bd98182d](https://github.com/gohugoio/hugo/commit/bd98182dbde893a8a809661c70633741bbf63911) [@bep](https://github.com/bep) [#6041](https://github.com/gohugoio/hugo/issues/6041)
-* Use the SVG logo in README.md [c0eef3b4](https://github.com/gohugoio/hugo/commit/c0eef3b401615e85bb74baee6a515abcf531fc2c) [@XhmikosR](https://github.com/XhmikosR)
-* Add a branch bundle test case [82439520](https://github.com/gohugoio/hugo/commit/824395204680496d528684587a1f2977394aff3d) [@bep](https://github.com/bep) [#6173](https://github.com/gohugoio/hugo/issues/6173)
-* Simplify page tree logic [7ff0a8ee](https://github.com/gohugoio/hugo/commit/7ff0a8ee9fe8d710d407e57faf1fda43bd635f28) [@bep](https://github.com/bep) [#6154](https://github.com/gohugoio/hugo/issues/6154)[#6153](https://github.com/gohugoio/hugo/issues/6153)[#6152](https://github.com/gohugoio/hugo/issues/6152)
-* Merge pull request #6149 from bep/sort-caseinsensitive [53077b0d](https://github.com/gohugoio/hugo/commit/53077b0da54906feee64a03612e5186043e17341) [@bep](https://github.com/bep)
-* Regenerate CLI docs [02b947ea](https://github.com/gohugoio/hugo/commit/02b947eaa3cc68404180d796a2f7119dce074539) [@bep](https://github.com/bep)
-* Add "hugo config mounts" command [d7c233af](https://github.com/gohugoio/hugo/commit/d7c233afee6a16b1947f60b7e5450e40612997bb) [@bep](https://github.com/bep) [#6144](https://github.com/gohugoio/hugo/issues/6144)
-* Cleanup the hugo config command [45ee8a7a](https://github.com/gohugoio/hugo/commit/45ee8a7a52213bf394c7f41a72be78084ddc789a) [@bep](https://github.com/bep) [#6144](https://github.com/gohugoio/hugo/issues/6144)
-* Move the mount duplicate filter to the modules package [4b6c5eba](https://github.com/gohugoio/hugo/commit/4b6c5eba306e6e69f3dd07a6c102bfc8040b38c9) [@bep](https://github.com/bep)
-* Allow overlap in module mounts [edf9f0a3](https://github.com/gohugoio/hugo/commit/edf9f0a354e5eaa556f8faed70b5243b7273b35c) [@bep](https://github.com/bep) [#6146](https://github.com/gohugoio/hugo/issues/6146)
-* Add some more content language test assertions [84bc8d84](https://github.com/gohugoio/hugo/commit/84bc8d84e4d2ec1fc94aee3113ebc570a28d1d16) [@bep](https://github.com/bep) [#6136](https://github.com/gohugoio/hugo/issues/6136)
-* Add proper error message when receiving nil in Resource transformation [e5f96024](https://github.com/gohugoio/hugo/commit/e5f960245938d8d8b4e99f312e9907f8d3aebf7a) [@bep](https://github.com/bep) [#6128](https://github.com/gohugoio/hugo/issues/6128)
-* Merge branch 'release-0.56.1' [9f497e7b](https://github.com/gohugoio/hugo/commit/9f497e7b5f77d0eb45d932a2301e648a3cd2d88f) [@bep](https://github.com/bep)
-* Update go-org to v0.1.2 [56908509](https://github.com/gohugoio/hugo/commit/56908509eb3a5779743a2314c05693a732b7feb3) [@niklasfasching](https://github.com/niklasfasching)
-* Do not return error on params dot access on incompatible types [e393c629](https://github.com/gohugoio/hugo/commit/e393c6290e827111a8a2e486791dc21f63a92b55) [@bep](https://github.com/bep) [#6121](https://github.com/gohugoio/hugo/issues/6121)
-* Set GO111MODULE=on [e5fe3789](https://github.com/gohugoio/hugo/commit/e5fe378925c16c75902bbb46499c376c530ebdb5) [@bep](https://github.com/bep) [#6114](https://github.com/gohugoio/hugo/issues/6114)
-* Skip resource cache init if the fs is missing [da4c4a77](https://github.com/gohugoio/hugo/commit/da4c4a7789d403af3f4f4fdd5dfd3327535e4050) [@bep](https://github.com/bep) [#6113](https://github.com/gohugoio/hugo/issues/6113)
-
-## Fixes
-
-### Core
-
-* Fix output format handling of mix cased page kinds [de876242](https://github.com/gohugoio/hugo/commit/de87624241daa86660f205cc72a745409b9c9238) [@bep](https://github.com/bep) [#4528](https://github.com/gohugoio/hugo/issues/4528)
-* Fix broken test [9ef4dca3](https://github.com/gohugoio/hugo/commit/9ef4dca361727a78e0f66f8f4e54c64e4c4781cb) [@bep](https://github.com/bep)
-* Fix bundle header clone logic [0e086785](https://github.com/gohugoio/hugo/commit/0e086785fa4be8086256e9d7de6cda78e18d00ee) [@bep](https://github.com/bep) [#6136](https://github.com/gohugoio/hugo/issues/6136)
-
-### Other
-
-* Fix faulty -h logic in hugo mod get [17ca8f0c](https://github.com/gohugoio/hugo/commit/17ca8f0c4c636752fb9da2ad551679275dc03dd3) [@bep](https://github.com/bep) [#6197](https://github.com/gohugoio/hugo/issues/6197)
-* Fixed ineffectual assignments [c577a9ed](https://github.com/gohugoio/hugo/commit/c577a9ed2347559783c44232e1f08414008c5203) [@muesli](https://github.com/muesli)
-* Fixed tautological error conditions [e88d7989](https://github.com/gohugoio/hugo/commit/e88d7989907108b656eccd92bccc076be72a5c03) [@muesli](https://github.com/muesli)
-* Fix static sync issue with virtual mounts [166a394a](https://github.com/gohugoio/hugo/commit/166a394a2fef6f2990e264cc8dfb722af2cc6a67) [@bep](https://github.com/bep) [#6165](https://github.com/gohugoio/hugo/issues/6165)
-* Cache the next position of `urlreplacer.prefix` [a843ca53](https://github.com/gohugoio/hugo/commit/a843ca53b5e0f29df9535fa0e88408a63cdc2cd7) [@vazrupe](https://github.com/vazrupe) [#5942](https://github.com/gohugoio/hugo/issues/5942)
-* Fix no-map vs noMap discrepancy [02397e76](https://github.com/gohugoio/hugo/commit/02397e76cece28b467de30ff0cb0f471d9b212ee) [@bep](https://github.com/bep) [#6166](https://github.com/gohugoio/hugo/issues/6166)
-* Fix assorted typos [f7f549e3](https://github.com/gohugoio/hugo/commit/f7f549e3a7492c787c6abb4900cc0f57c8ab1826) [@XhmikosR](https://github.com/XhmikosR)
-* common/collections: Fix typo [6512d128](https://github.com/gohugoio/hugo/commit/6512d128c6d33b86f376764ab1d622a89ea18d20) [@shawnps](https://github.com/shawnps)
-* Fix multilingual example compatibility with latest version [b8758de1](https://github.com/gohugoio/hugo/commit/b8758de19ec75b4565075314f9578270a092bc6f) [@robinwassen](https://github.com/robinwassen)
-* Fix self-mounts on the main project [36220851](https://github.com/gohugoio/hugo/commit/36220851e4ed7fc3fa78aa250d001d5f922210e7) [@bep](https://github.com/bep) [#6143](https://github.com/gohugoio/hugo/issues/6143)
-* Fix config reloading in Vim and similar [6eca0a3d](https://github.com/gohugoio/hugo/commit/6eca0a3dee77f0e764b1de2e10c10ec2b7cf8ef1) [@bep](https://github.com/bep) [#6139](https://github.com/gohugoio/hugo/issues/6139)
-* Fix Jekyll import [e28bd4c0](https://github.com/gohugoio/hugo/commit/e28bd4c0f843f39cfcb715b6c9c7d249bad5b500) [@bep](https://github.com/bep) [#6131](https://github.com/gohugoio/hugo/issues/6131)
-* Fix image format detection for upper case extensions, e.g. JPG [c62bbf7b](https://github.com/gohugoio/hugo/commit/c62bbf7b11d68d52ef11a4c6c70660914c473d08) [@bep](https://github.com/bep) [#6137](https://github.com/gohugoio/hugo/issues/6137)
-* Fix i18n project vs theme order [00a238e3](https://github.com/gohugoio/hugo/commit/00a238e32c82b0651e4145e306840cffa46e535d) [@bep](https://github.com/bep) [#6134](https://github.com/gohugoio/hugo/issues/6134)
-* Fix image Width/Height regression [93d02aab](https://github.com/gohugoio/hugo/commit/93d02aabe6e611d65c428a9c5669b422e1bcf5e8) [@bep](https://github.com/bep) [#6120](https://github.com/gohugoio/hugo/issues/6120)
-
-
-
-
-
diff --git a/docs/content/en/news/0.57.1-relnotes/index.md b/docs/content/en/news/0.57.1-relnotes/index.md
deleted file mode 100644
index fea7833ff15..00000000000
--- a/docs/content/en/news/0.57.1-relnotes/index.md
+++ /dev/null
@@ -1,26 +0,0 @@
-
----
-date: 2019-08-15
-title: "Hugo 0.57.1: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.57.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-* hugolib: Fix draft etc. handling of _index.md pages [6ccf50ea](https://github.com/gohugoio/hugo/commit/6ccf50ea7bb291bcbe1d56a4d697a6fd57a9c629) [@bep](https://github.com/bep) [#6222](https://github.com/gohugoio/hugo/issues/6222)[#6210](https://github.com/gohugoio/hugo/issues/6210)
-* Fix mainSections logic [67524c99](https://github.com/gohugoio/hugo/commit/67524c993623871626f0f22e6a2ac705a816a959) [@bep](https://github.com/bep) [#6217](https://github.com/gohugoio/hugo/issues/6217)
-* Fix live reload mount logic with sub paths [952a3194](https://github.com/gohugoio/hugo/commit/952a3194962dd91f87e5bd227a1591b00c39ff05) [@bep](https://github.com/bep) [#6209](https://github.com/gohugoio/hugo/issues/6209)
-* modules: Disable "auto tidy" for now [321418f2](https://github.com/gohugoio/hugo/commit/321418f22a4a94b87f01e1403a2f4a71106461fb) [@bep](https://github.com/bep) [#6115](https://github.com/gohugoio/hugo/issues/6115)
-* hugolib: Recover and log panics in content init [7f3aab5a](https://github.com/gohugoio/hugo/commit/7f3aab5ac283ecfc7029b680d4c0a34920e728c8) [@bep](https://github.com/bep) [#6210](https://github.com/gohugoio/hugo/issues/6210)
-* hugolib: Add some outputs tests [028b9926](https://github.com/gohugoio/hugo/commit/028b992611209b241b1f55def8d47f9188038dc3) [@bep](https://github.com/bep) [#6210](https://github.com/gohugoio/hugo/issues/6210)
-* hugolib: Fix taxonomies vs expired [9475f61a](https://github.com/gohugoio/hugo/commit/9475f61a377fcf23f910cbfd4ddca59261326665) [@bep](https://github.com/bep) [#6213](https://github.com/gohugoio/hugo/issues/6213)
-* commands: Make sure the hugo field is always initialized before it's used [ea9261e8](https://github.com/gohugoio/hugo/commit/ea9261e856c13c1d4ae05fcca08766d410b4b65c) [@vazrupe](https://github.com/vazrupe) [#6193](https://github.com/gohugoio/hugo/issues/6193)
-
-
-
diff --git a/docs/content/en/news/0.57.2-relnotes/index.md b/docs/content/en/news/0.57.2-relnotes/index.md
deleted file mode 100644
index 83c34940129..00000000000
--- a/docs/content/en/news/0.57.2-relnotes/index.md
+++ /dev/null
@@ -1,35 +0,0 @@
-
----
-date: 2019-08-17
-title: "Hugo 0.57.2: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.57.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-Hugo 0.57.0 had some well-intended breaking changes. And while they made a lot of sense, one of them made a little too much noise.
-
-This release reverts the behavior for `.Pages` on the home page to how it behaved in 0.56, but adds a `WARNING` telling you what to do to prepare for Hugo 0.58.
-
-In short, `.Page` home will from 0.58 only return its immediate children (sections and regular pages).
-
-In this release it returns `.Site.RegularPages`. So to prepare for Hugo 0.58 you can either use `.Site.RegularPages` in your home template, or if you have a general `list.html` or RSS template, you can do something like this:
-
-```go-html-template
-{{- $pctx := . -}}
-{{- if .IsHome -}}{{ $pctx = .Site }}{{- end -}}
-{{- $pages := $pctx.RegularPages -}}
-```
-
-* tpl: Use RegularPages for RSS template [88d69936](https://github.com/gohugoio/hugo/commit/88d69936122f82fffc02850516bdb37be3d0892b) [@bep](https://github.com/bep) [#6238](https://github.com/gohugoio/hugo/issues/6238)
-* hugolib: Don't use the global warning logger [ea681603](https://github.com/gohugoio/hugo/commit/ea6816030081b2cffa6c0ae9ca5429a2c6fe2fa5) [@bep](https://github.com/bep) [#6238](https://github.com/gohugoio/hugo/issues/6238)
-* tpl: Avoid "home page warning" in RSS template [564cf1bb](https://github.com/gohugoio/hugo/commit/564cf1bb11e100891992e9131b271a79ea7fc528) [@bep](https://github.com/bep) [#6238](https://github.com/gohugoio/hugo/issues/6238)
-* hugolib: Allow index.md inside bundles [4b4bdcfe](https://github.com/gohugoio/hugo/commit/4b4bdcfe740d988e4cfb4fee53eced6985576abd) [@bep](https://github.com/bep) [#6208](https://github.com/gohugoio/hugo/issues/6208)
-* Adjust the default paginator for sections [18836a71](https://github.com/gohugoio/hugo/commit/18836a71ce7b671fa71dd1318b99fc661755e94d) [@bep](https://github.com/bep) [#6231](https://github.com/gohugoio/hugo/issues/6231)
-* hugolib: Add a site benchmark [416493b5](https://github.com/gohugoio/hugo/commit/416493b548a9bbaa27758fba9bab50a22b680e9d) [@bep](https://github.com/bep)
-* Update to Go 1.11.13 and 1.12.9 [f28efd35](https://github.com/gohugoio/hugo/commit/f28efd35820dc4909832c14dfd8ea6812ecead31) [@bep](https://github.com/bep) [#6228](https://github.com/gohugoio/hugo/issues/6228)
-
-
-
diff --git a/docs/content/en/news/0.58.0-relnotes/hugo58-featured.png b/docs/content/en/news/0.58.0-relnotes/hugo58-featured.png
deleted file mode 100644
index 52962050d58..00000000000
Binary files a/docs/content/en/news/0.58.0-relnotes/hugo58-featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.58.0-relnotes/index.md b/docs/content/en/news/0.58.0-relnotes/index.md
deleted file mode 100644
index 38b2143e072..00000000000
--- a/docs/content/en/news/0.58.0-relnotes/index.md
+++ /dev/null
@@ -1,109 +0,0 @@
-
----
-date: 2019-09-04
-title: "Image Processing Galore!"
-description: "Hugo 0.58 adds the long sought after Exif method plus many useful image filters. And it's faster ..."
-categories: ["Releases"]
----
-
-**Hugo 0.58** adds the long sought after [Exif (docs)](https://gohugo.io/content-management/image-processing/#exif) method on image and a bunch of useful [image filters (docs)](https://gohugo.io/functions/images/#image-filters), courtesy of [@disintegration](https://github.com/disintegration)'s great [Gift](https://github.com/disintegration/gift) image library.
-
-This means that you now can do variations of this:
-
-```go-html-template
-{{ $blurryGrayscale := $myimage.Resize "300x200" | images.Filter images.Grayscale (images.GaussianBlur 8) }}
-{{ $exif := $myimg.Exif }}
-```
-
-It's worth noting that the issue that enabled/triggered the implementation of the above was the simplifications needed to fix [#5903](https://github.com/gohugoio/hugo/issues/5903), which makes sure that type information is preserved when processed via **Hugo Pipes**. E.g. you can now do:
-
-```go-html-template
-{{ ($myimg | fingerprint ).Width }}
-```
-
-And it works as expected.
-
-This release is also built with the brand new **Go 1.13** which means that it's also the [fastest Hugo version](https://discourse.gohugo.io/t/hugo-benchmarks-go-1-12-vs-go-1-13/20572/5) to date.
-
-This release represents **39 contributions by 5 contributors** to the main Hugo code base.[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@niklasfasching](https://github.com/niklasfasching), [@vazrupe](https://github.com/vazrupe), and [@jakejarvis](https://github.com/jakejarvis) for their ongoing contributions.
-
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) and [@onedrawingperday](https://github.com/onedrawingperday) for their relentless work on keeping the themes site in pristine condition and to [@kaushalmodi](https://github.com/kaushalmodi) for his great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **8 contributions by 8 contributors**. A special thanks to [@jacebenson](https://github.com/jacebenson), [@digitalcraftsman](https://github.com/digitalcraftsman), [@jernst](https://github.com/jernst), and [@rgwood](https://github.com/rgwood) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 37859+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 440+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 317+ [themes](http://themes.gohugo.io/)
-
-## Notes
-
-* `home.Pages` now behaves like all the other sections, see [#6240](https://github.com/gohugoio/hugo/issues/6240). If you want to list all the regular pages, use `.Site.RegularPages`.
-* We have added some new image filters to Hugo's image processing. This also means that we have consolidated the resize operations to use the one `gift` library (from the same developer as the one we used before). The operations work as before, but one difference is that we no longer embed color profile information in PNG images, but this should also be a more portable solution. Software that supports color profiles will assume that images without an embedded profile are in the sRGB profile. Software that doesn't support color profiles will use the monitor's profile, which is most likely to be sRGB as well.
-* We have improved the file cache logic for processed images and only stores them once when the same image is bundled in multiple languages. This means that you may want to run `hugo --gc` to clean your image cache.
-
-## Enhancements
-
-### Templates
-
-* Migrate last shortcodes (YouTube and Vimeo) to HTTPS embeds [00297085](https://github.com/gohugoio/hugo/commit/00297085db48cbb7949c9867012f6df38817fc29) [@jakejarvis](https://github.com/jakejarvis)
-* Use RegularPages for RSS template [88d69936](https://github.com/gohugoio/hugo/commit/88d69936122f82fffc02850516bdb37be3d0892b) [@bep](https://github.com/bep) [#6238](https://github.com/gohugoio/hugo/issues/6238)
-* Avoid "home page warning" in RSS template [564cf1bb](https://github.com/gohugoio/hugo/commit/564cf1bb11e100891992e9131b271a79ea7fc528) [@bep](https://github.com/bep) [#6238](https://github.com/gohugoio/hugo/issues/6238)
-
-### Core
-
-* Adjust Go version specific test [dc3f3df2](https://github.com/gohugoio/hugo/commit/dc3f3df29d2b65532cedc9d321db7c4a38a28d7d) [@bep](https://github.com/bep) [#6304](https://github.com/gohugoio/hugo/issues/6304)
-* Remove the old and slow site benchmarks [28501ceb](https://github.com/gohugoio/hugo/commit/28501ceb93613729c5971105010dd3c22cfa0f7f) [@bep](https://github.com/bep)
-* Add a Sass includePaths test [1b5c7e32](https://github.com/gohugoio/hugo/commit/1b5c7e327c7f98cf8e9fff920f3328198f67a598) [@bep](https://github.com/bep) [#6274](https://github.com/gohugoio/hugo/issues/6274)
-* Change to output non-panic error message if missing shortcode template [fd3d90ce](https://github.com/gohugoio/hugo/commit/fd3d90ced85baaf6941be45b2fe29c25ff755c18) [@vazrupe](https://github.com/vazrupe) [#6075](https://github.com/gohugoio/hugo/issues/6075)
-* Don't use the global warning logger [ea681603](https://github.com/gohugoio/hugo/commit/ea6816030081b2cffa6c0ae9ca5429a2c6fe2fa5) [@bep](https://github.com/bep) [#6238](https://github.com/gohugoio/hugo/issues/6238)
-* Allow index.md inside bundles [4b4bdcfe](https://github.com/gohugoio/hugo/commit/4b4bdcfe740d988e4cfb4fee53eced6985576abd) [@bep](https://github.com/bep) [#6208](https://github.com/gohugoio/hugo/issues/6208)
-* Add a site benchmark [416493b5](https://github.com/gohugoio/hugo/commit/416493b548a9bbaa27758fba9bab50a22b680e9d) [@bep](https://github.com/bep)
-* Recover and log panics in content init [7f3aab5a](https://github.com/gohugoio/hugo/commit/7f3aab5ac283ecfc7029b680d4c0a34920e728c8) [@bep](https://github.com/bep) [#6210](https://github.com/gohugoio/hugo/issues/6210)
-* Add some outputs tests [028b9926](https://github.com/gohugoio/hugo/commit/028b992611209b241b1f55def8d47f9188038dc3) [@bep](https://github.com/bep) [#6210](https://github.com/gohugoio/hugo/issues/6210)
-
-### Other
-
-* Update to Go 1.13 [b4313011](https://github.com/gohugoio/hugo/commit/b43130115d9e3888d94df9e6f5fc72eba662632f) [@bep](https://github.com/bep) [#6304](https://github.com/gohugoio/hugo/issues/6304)
-* Cache processed images by their source path [8624b9fe](https://github.com/gohugoio/hugo/commit/8624b9fe9eb81aeb884d36311fb6f85fed98aa43) [@bep](https://github.com/bep) [#6269](https://github.com/gohugoio/hugo/issues/6269)
-* Remove test artifact [018494f3](https://github.com/gohugoio/hugo/commit/018494f363a32b9e4d3622da6842bc3e59b420b2) [@bep](https://github.com/bep)
-* Make the "is this a Hugo Module" logic more lenient [43298f02](https://github.com/gohugoio/hugo/commit/43298f028ccdf38e949b573d03d328bf96b998a3) [@bep](https://github.com/bep) [#6299](https://github.com/gohugoio/hugo/issues/6299)
-* Update to Go 1.11.13 and 1.12.9 [05d83b6c](https://github.com/gohugoio/hugo/commit/05d83b6c08089c20ca1d99bcd224188ed5d127d4) [@bep](https://github.com/bep) [#6228](https://github.com/gohugoio/hugo/issues/6228)
-* Make home.Pages work like any other section [4898fb3d](https://github.com/gohugoio/hugo/commit/4898fb3d64c856c5e0f324e0dfbf3b60da1d1d3a) [@bep](https://github.com/bep) [#6240](https://github.com/gohugoio/hugo/issues/6240)
-* Add some fingerprint tests [45d7988f](https://github.com/gohugoio/hugo/commit/45d7988f2d0aa95d1a56f4c66342574075cf2963) [@bep](https://github.com/bep) [#6284](https://github.com/gohugoio/hugo/issues/6284)[#6280](https://github.com/gohugoio/hugo/issues/6280)
-* Cache Exif data to disk [ce47c21a](https://github.com/gohugoio/hugo/commit/ce47c21a2998630f8edcbd056983d9c59a80b676) [@bep](https://github.com/bep) [#6291](https://github.com/gohugoio/hugo/issues/6291)
-* Remove metaDataFormat setting [de9cbf61](https://github.com/gohugoio/hugo/commit/de9cbf61954201943a7b170a7d0a8b34afb5942c) [@bep](https://github.com/bep)
-* Make the Exif benchmark filenames distinct [4f501169](https://github.com/gohugoio/hugo/commit/4f5011692a22762e213e872fd9e39d015141083f) [@bep](https://github.com/bep)
-* Add Exif benchmark [3becba7a](https://github.com/gohugoio/hugo/commit/3becba7a982f39f67c7ee7cff411eae50931c8cd) [@bep](https://github.com/bep) [#6291](https://github.com/gohugoio/hugo/issues/6291)
-* Remove unused map type [20bdc69a](https://github.com/gohugoio/hugo/commit/20bdc69a47b851871bdc4d9be6366fa7f51f25db) [@bep](https://github.com/bep)
-* Add image.Exif [28143397](https://github.com/gohugoio/hugo/commit/28143397d625cce1f89f4161cba97c0dddd9004c) [@bep](https://github.com/bep) [#4600](https://github.com/gohugoio/hugo/issues/4600)
-* Add a set of image filters [823f53c8](https://github.com/gohugoio/hugo/commit/823f53c861bb49aecc6104e0add39fc3b0729025) [@bep](https://github.com/bep) [#6255](https://github.com/gohugoio/hugo/issues/6255)
-* Image resource refactor [f9978ed1](https://github.com/gohugoio/hugo/commit/f9978ed16476ca6d233a89669c62c798cdf9db9d) [@bep](https://github.com/bep) [#5903](https://github.com/gohugoio/hugo/issues/5903)[#6234](https://github.com/gohugoio/hugo/issues/6234)[#6266](https://github.com/gohugoio/hugo/issues/6266)
-* Remove debug check left during development [ad1d6d64](https://github.com/gohugoio/hugo/commit/ad1d6d6406c9b208d4fd4e09d6ad9ef19aa65dbb) [@bep](https://github.com/bep) [#6249](https://github.com/gohugoio/hugo/issues/6249)
-* Adjust the default paginator for sections [18836a71](https://github.com/gohugoio/hugo/commit/18836a71ce7b671fa71dd1318b99fc661755e94d) [@bep](https://github.com/bep) [#6231](https://github.com/gohugoio/hugo/issues/6231)
-* Update to Go 1.11.13 and 1.12.9 [f28efd35](https://github.com/gohugoio/hugo/commit/f28efd35820dc4909832c14dfd8ea6812ecead31) [@bep](https://github.com/bep) [#6228](https://github.com/gohugoio/hugo/issues/6228)
-* Disable "auto tidy" for now [321418f2](https://github.com/gohugoio/hugo/commit/321418f22a4a94b87f01e1403a2f4a71106461fb) [@bep](https://github.com/bep) [#6115](https://github.com/gohugoio/hugo/issues/6115)
-* Make sure the hugo field is always initialized before it's used [ea9261e8](https://github.com/gohugoio/hugo/commit/ea9261e856c13c1d4ae05fcca08766d410b4b65c) [@vazrupe](https://github.com/vazrupe) [#6193](https://github.com/gohugoio/hugo/issues/6193)
-
-## Fixes
-
-### Core
-
-* Fix draft etc. handling of _index.md pages [6ccf50ea](https://github.com/gohugoio/hugo/commit/6ccf50ea7bb291bcbe1d56a4d697a6fd57a9c629) [@bep](https://github.com/bep) [#6222](https://github.com/gohugoio/hugo/issues/6222)[#6210](https://github.com/gohugoio/hugo/issues/6210)
-* Fix taxonomies vs expired [9475f61a](https://github.com/gohugoio/hugo/commit/9475f61a377fcf23f910cbfd4ddca59261326665) [@bep](https://github.com/bep) [#6213](https://github.com/gohugoio/hugo/issues/6213)
-
-### Other
-
-* Update go-org (fix descriptive lists) [8a8d4a6d](https://github.com/gohugoio/hugo/commit/8a8d4a6d97d181f1aaee639d35b198a27bb788e2) [@niklasfasching](https://github.com/niklasfasching)
-* Update go-org (fix footnotes in headlines) [58d4c0a8](https://github.com/gohugoio/hugo/commit/58d4c0a8be8beefbd7437b17bf7a9a381164d09b) [@niklasfasching](https://github.com/niklasfasching)
-* Discrepancy typo fix [c5319db9](https://github.com/gohugoio/hugo/commit/c5319db9f13f1dee97db5fbbeae38429a074c7d0) [@coliff](https://github.com/coliff)
-* Fix mainSections logic [67524c99](https://github.com/gohugoio/hugo/commit/67524c993623871626f0f22e6a2ac705a816a959) [@bep](https://github.com/bep) [#6217](https://github.com/gohugoio/hugo/issues/6217)
-* Fix live reload mount logic with sub paths [952a3194](https://github.com/gohugoio/hugo/commit/952a3194962dd91f87e5bd227a1591b00c39ff05) [@bep](https://github.com/bep) [#6209](https://github.com/gohugoio/hugo/issues/6209)
-
-
-
-
-
diff --git a/docs/content/en/news/0.58.1-relnotes/index.md b/docs/content/en/news/0.58.1-relnotes/index.md
deleted file mode 100644
index 1350c0a7305..00000000000
--- a/docs/content/en/news/0.58.1-relnotes/index.md
+++ /dev/null
@@ -1,23 +0,0 @@
-
----
-date: 2019-09-06
-title: "Hugo 0.58.1: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.58.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-* Avoid writing the same processed image to /public twice [9442937d](https://github.com/gohugoio/hugo/commit/9442937d82005b369780edcc557e0d15d6bf0bad) [@bep](https://github.com/bep) [#6307](https://github.com/gohugoio/hugo/issues/6307)
-* hugolib: Fix broken bundle live reload logic [901077c0](https://github.com/gohugoio/hugo/commit/901077c0364eaf3fe4f997c3026aa18cfc7781ed) [@bep](https://github.com/bep) [#6315](https://github.com/gohugoio/hugo/issues/6315)[#6308](https://github.com/gohugoio/hugo/issues/6308)
-* deps: Update github.com/bep/gitmap [24ad4295](https://github.com/gohugoio/hugo/commit/24ad4295718341dcae12b72bf52fef312d1036ed) [@bep](https://github.com/bep) [#6313](https://github.com/gohugoio/hugo/issues/6313)
-* Fix concat with fingerprint regression [3be2c253](https://github.com/gohugoio/hugo/commit/3be2c25351b421a26ee1ff2a38cbab00280c0583) [@bep](https://github.com/bep) [#6309](https://github.com/gohugoio/hugo/issues/6309)
-* tpl: Remove eq argument limitation [5e660947](https://github.com/gohugoio/hugo/commit/5e660947757023434dd7a1ec8b8239c0577fd501) [@vazrupe](https://github.com/vazrupe) [#6237](https://github.com/gohugoio/hugo/issues/6237)
-
-
-
diff --git a/docs/content/en/news/0.58.2-relnotes/index.md b/docs/content/en/news/0.58.2-relnotes/index.md
deleted file mode 100644
index e498aea59cb..00000000000
--- a/docs/content/en/news/0.58.2-relnotes/index.md
+++ /dev/null
@@ -1,20 +0,0 @@
-
----
-date: 2019-09-13
-title: "Hugo 0.58.2: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.58.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-* Fix cache keys for bundled resoures in transform.Unmarshal [c0d75736](https://github.com/gohugoio/hugo/commit/c0d7573677e9726c14749ccd432dccb75e0d194d) [@bep](https://github.com/bep) [#6327](https://github.com/gohugoio/hugo/issues/6327)
-* filecache: Ignore "does not exist" errors in prune [fcfa6f33](https://github.com/gohugoio/hugo/commit/fcfa6f33bbebc128a3f9bc3162173bc3780c5f50) [@bep](https://github.com/bep) [#6326](https://github.com/gohugoio/hugo/issues/6326)[#5745](https://github.com/gohugoio/hugo/issues/5745)
-
-
-
diff --git a/docs/content/en/news/0.58.3-relnotes/index.md b/docs/content/en/news/0.58.3-relnotes/index.md
deleted file mode 100644
index 86bc4b88fd7..00000000000
--- a/docs/content/en/news/0.58.3-relnotes/index.md
+++ /dev/null
@@ -1,21 +0,0 @@
-
----
-date: 2019-09-19
-title: "Hugo 0.58.3: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.58.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-* Add some more resource transform tests [c262a95a](https://github.com/gohugoio/hugo/commit/c262a95a5c5a9304c82b9d9e39701bc471916851) [@bep](https://github.com/bep) [#6348](https://github.com/gohugoio/hugo/issues/6348)
-* Fix cache key transformed resources [6dec671f](https://github.com/gohugoio/hugo/commit/6dec671fb930029e18ba9aa5135b3a27adcddb21) [@bep](https://github.com/bep) [#6348](https://github.com/gohugoio/hugo/issues/6348)
-* deploy: Do not compile in Azure on Solaris [c0d7188e](https://github.com/gohugoio/hugo/commit/c0d7188ec85e7a4b61489e38896108d877f6d902) [@fazalmajid](https://github.com/fazalmajid) [#6324](https://github.com/gohugoio/hugo/issues/6324)
-
-
-
diff --git a/docs/content/en/news/0.59.0-relnotes/hugo-59-poster-featured.png b/docs/content/en/news/0.59.0-relnotes/hugo-59-poster-featured.png
deleted file mode 100644
index 67dc6587262..00000000000
Binary files a/docs/content/en/news/0.59.0-relnotes/hugo-59-poster-featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.59.0-relnotes/index.md b/docs/content/en/news/0.59.0-relnotes/index.md
deleted file mode 100644
index 1a7552d096d..00000000000
--- a/docs/content/en/news/0.59.0-relnotes/index.md
+++ /dev/null
@@ -1,120 +0,0 @@
-
----
-date: 2019-10-21
-title: "Hugo 0.59.0"
-description: "Set image target format and background color, and more ..."
-categories: ["Releases"]
----
-
-The timing of this release is motivated by getting the copies of the docs repositories in synch, now fully "Hugo Modularized". But it also comes with some very nice additions:
-
-It is now possible to set the target format and the background fill color when processing images, e.g.:
-
-```
-{{ $image.Resize "600x jpg #b31280" }}
-```
-
-See [Image Processing Options](https://gohugo.io/content-management/image-processing/#image-processing-options).
-
-Another useful addon is the `$pages.Next` and `$pages.Prev` methods on the core page collections in Hugo. These works the same way as the built-in static variants one `Page`, e.g. `.Next` and `.NextInSection`:
-
-```
-{{with .Site.RegularPages.Next . }}{{.RelPermalink}}{{end}}
-```
-
-The above is a functionally equivalent (but slightly slower) variant of:
-
-```
-{{with .Next }}{{.RelPermalink}}{{end}}
-```
-
-See [Pages Methods](https://gohugo.io/variables/pages/) for more information.
-
-
-This release represents **45 contributions by 13 contributors** to the main Hugo code base. [@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@anthonyfok](https://github.com/anthonyfok), [@BaibhaVatsa](https://github.com/BaibhaVatsa), and [@XhmikosR](https://github.com/XhmikosR) for their ongoing contributions.
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) and [@onedrawingperday](https://github.com/onedrawingperday) for their relentless work on keeping the themes site in pristine condition and to [@kaushalmodi](https://github.com/kaushalmodi) for his great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **34 contributions by 20 contributors**. A special thanks to [@bep](https://github.com/bep), [@celtic-coder](https://github.com/celtic-coder), [@napcs](https://github.com/napcs), and [@bmackinney](https://github.com/bmackinney) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 38843+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 441+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 255+ [themes](http://themes.gohugo.io/)
-
-## Notes
-
-
-* Shortcode params can now be typed (supported types are `string`, `bool` `int` and `float64`, see [#6376](https://github.com/gohugoio/hugo/pull/6376).
-* Pages.Next/.Prev as described above has existed for a long time, but they have been undocumented. They have been reimplemented for this release and now works like their namesakes on `Page`. This may be considered a breaking change, but it should be a welcome one, as the old behaviour wasn't very useful. See [#4500](https://github.com/gohugoio/hugo/issues/4500)
-
-## Enhancements
-
-### Templates
-
-* Add optional "title" attribute to iframe in Vimeo shortcode [7b3edc29](https://github.com/gohugoio/hugo/commit/7b3edc293144dd450e87ca32f238221c21eb1b47) [@zbayoff](https://github.com/zbayoff)
-* Modify error messages of after, first, and last [65b7d422](https://github.com/gohugoio/hugo/commit/65b7d4221b90445bfc089873092411cf7e322933) [@BaibhaVatsa](https://github.com/BaibhaVatsa) [#6415](https://github.com/gohugoio/hugo/issues/6415)
-* Last now accepts 0 as limit [0e75af74](https://github.com/gohugoio/hugo/commit/0e75af74db30259ec355a7b79a1e257d5fe00eef) [@BaibhaVatsa](https://github.com/BaibhaVatsa) [#6419](https://github.com/gohugoio/hugo/issues/6419)
-* After now accepts 0 as index [096a4b67](https://github.com/gohugoio/hugo/commit/096a4b67b98259dabff5ebfbfd879a41999a1ed2) [@BaibhaVatsa](https://github.com/BaibhaVatsa) [#6388](https://github.com/gohugoio/hugo/issues/6388)
-* Make getJSON/getCVS accept non-string args [0d7b05be](https://github.com/gohugoio/hugo/commit/0d7b05be4cb2391cbd280f6109c01ec2d3d7e0c6) [@bep](https://github.com/bep) [#6382](https://github.com/gohugoio/hugo/issues/6382)
-* Add `rel="noopener"` for external links [34dc06b0](https://github.com/gohugoio/hugo/commit/34dc06b032741abac342d7a2a77510ded9b72ae8) [@XhmikosR](https://github.com/XhmikosR)
-* Remove unneeded space [2b1814ee](https://github.com/gohugoio/hugo/commit/2b1814ee580f3149f9fe0a4cf30b754bac9f0c90) [@XhmikosR](https://github.com/XhmikosR)
-* Remove eq argument limitation [5e660947](https://github.com/gohugoio/hugo/commit/5e660947757023434dd7a1ec8b8239c0577fd501) [@vazrupe](https://github.com/vazrupe) [#6237](https://github.com/gohugoio/hugo/issues/6237)
-
-### Output
-
-* Add common video media types [689f647b](https://github.com/gohugoio/hugo/commit/689f647baf96af078186f0cdc45199f7d0995d22) [@martignoni](https://github.com/martignoni)
-* Simplify test output to simplify diffing [339ee371](https://github.com/gohugoio/hugo/commit/339ee37143ca5a6bb22bbc1b0468d785f450cfb7) [@bep](https://github.com/bep)
-* Use + to create the Type string [64ec8c89](https://github.com/gohugoio/hugo/commit/64ec8c89049461c4731b23c491fb41e00a09a8b2) [@bep](https://github.com/bep)
-* Support output image format in image operations [e5856e61](https://github.com/gohugoio/hugo/commit/e5856e61d88ef5149582851b00e06b5b93dce9f8) [@jansorg](https://github.com/jansorg) [#6298](https://github.com/gohugoio/hugo/issues/6298)
-
-### Other
-
-* Replace /docs [39121de4](https://github.com/gohugoio/hugo/commit/39121de4d991bdcf5f202da4d8d81a8ac6c149fc) [@bep](https://github.com/bep)
-* Recover from file corruption [180195aa](https://github.com/gohugoio/hugo/commit/180195aa342777fece1b29a08ec89456d7996c61) [@bep](https://github.com/bep) [#6401](https://github.com/gohugoio/hugo/issues/6401)
-* Allow to set background fill colour [4b286b9d](https://github.com/gohugoio/hugo/commit/4b286b9d2722909d0682e50eeecdfe16c1f47fd8) [@bep](https://github.com/bep) [#6298](https://github.com/gohugoio/hugo/issues/6298)
-* Replace .RSSLink [46cafdba](https://github.com/gohugoio/hugo/commit/46cafdbaca13866f32db04c0cc28374e30ec5914) [@bep](https://github.com/bep) [#6037](https://github.com/gohugoio/hugo/issues/6037)
-* Use binary search in Pages.Prev/Next if possible [653e6856](https://github.com/gohugoio/hugo/commit/653e6856ea1cfc60cc16733807d23b302dbe4bd5) [@bep](https://github.com/bep) [#4500](https://github.com/gohugoio/hugo/issues/4500)
-* Make Pages.Prev/Next work like the other Prev/Next methods [f4f566ed](https://github.com/gohugoio/hugo/commit/f4f566edf4bd6a590cf9cdbd5cfc0026ecd93b14) [@bep](https://github.com/bep) [#4500](https://github.com/gohugoio/hugo/issues/4500)
-* Update feature_request.md [5f1aafaf](https://github.com/gohugoio/hugo/commit/5f1aafafb40299bb4c8aebf71e05843431eb84c5) [@bep](https://github.com/bep)
-* Update to Go 1.12.10 and 1.13.1 [71b18a07](https://github.com/gohugoio/hugo/commit/71b18a0786894893eafa01263a0915149ed303ec) [@bep](https://github.com/bep) [#6406](https://github.com/gohugoio/hugo/issues/6406)
-* Add FileMeta.String [f10db101](https://github.com/gohugoio/hugo/commit/f10db101a18f5cad332c9398136f77e35a169d52) [@bep](https://github.com/bep)
-* Update minify to v2.5.2 [b401858e](https://github.com/gohugoio/hugo/commit/b401858ebd346c433dd69a260eba7098bded5a30) [@anthonyfok](https://github.com/anthonyfok)
-* Add BaseFs to RenderingContext [020a6fbd](https://github.com/gohugoio/hugo/commit/020a6fbd7f6996ed84d80ba6c37fe0d8c2536806) [@niklasfasching](https://github.com/niklasfasching)
-* Update go-org [b152216d](https://github.com/gohugoio/hugo/commit/b152216d5c8adbf1bfa4c6fb7b2a50b6866c685e) [@niklasfasching](https://github.com/niklasfasching)
-* Upgrade to latest version of emoji dependency [c466b88c](https://github.com/gohugoio/hugo/commit/c466b88c998bc99e5d26e41cb67d87e1d4b976f5) [@jamietanna](https://github.com/jamietanna) [#6391](https://github.com/gohugoio/hugo/issues/6391)
-* Upgrade to latest version of emoji dependency [170f18d9](https://github.com/gohugoio/hugo/commit/170f18d9352d39213170dd9d5e947eb45854c84b) [@jamietanna](https://github.com/jamietanna)
-* Update Architectures [15a0364d](https://github.com/gohugoio/hugo/commit/15a0364d39741da34b8661f9a8386b54016049d6) [@bep](https://github.com/bep)
-* Add ability to invalidate Google Cloud CDN [674e81ae](https://github.com/gohugoio/hugo/commit/674e81ae8700bdd00d3e5e47ff930d42d25bc68b) [@gkelly](https://github.com/gkelly)
-* Ensure same dirinfos sort order in TestImageOperationsGolden [298092d5](https://github.com/gohugoio/hugo/commit/298092d516f623cc20051f506d460fb7625cdc84) [@anthonyfok](https://github.com/anthonyfok)
-* Update bug_report.md [019ae384](https://github.com/gohugoio/hugo/commit/019ae384835446266b951875aa0870d245382cf2) [@bep](https://github.com/bep)
-* Support typed bool, int and float in shortcode params [329e88db](https://github.com/gohugoio/hugo/commit/329e88db1f6d043d32c7083570773dccfd4f11fc) [@bep](https://github.com/bep) [#6371](https://github.com/gohugoio/hugo/issues/6371)
-* Update Chroma [e073f4ef](https://github.com/gohugoio/hugo/commit/e073f4efb1345f6408000ef3f389873f8cf7179e) [@bep](https://github.com/bep) [#6279](https://github.com/gohugoio/hugo/issues/6279)
-* Add issue templates and action [454a033d](https://github.com/gohugoio/hugo/commit/454a033dc5bc9b3db626fe1533d7e8494d79f472) [@bmackinney](https://github.com/bmackinney)
-* Add some more resource transform tests [c262a95a](https://github.com/gohugoio/hugo/commit/c262a95a5c5a9304c82b9d9e39701bc471916851) [@bep](https://github.com/bep) [#6348](https://github.com/gohugoio/hugo/issues/6348)
-* Do not compile in Azure on Solaris [c0d7188e](https://github.com/gohugoio/hugo/commit/c0d7188ec85e7a4b61489e38896108d877f6d902) [@fazalmajid](https://github.com/fazalmajid) [#6324](https://github.com/gohugoio/hugo/issues/6324)
-* Ignore "does not exist" errors in prune [fcfa6f33](https://github.com/gohugoio/hugo/commit/fcfa6f33bbebc128a3f9bc3162173bc3780c5f50) [@bep](https://github.com/bep) [#6326](https://github.com/gohugoio/hugo/issues/6326)[#5745](https://github.com/gohugoio/hugo/issues/5745)
-* Avoid writing the same processed image to /public twice [9442937d](https://github.com/gohugoio/hugo/commit/9442937d82005b369780edcc557e0d15d6bf0bad) [@bep](https://github.com/bep) [#6307](https://github.com/gohugoio/hugo/issues/6307)
-* Update github.com/bep/gitmap [24ad4295](https://github.com/gohugoio/hugo/commit/24ad4295718341dcae12b72bf52fef312d1036ed) [@bep](https://github.com/bep) [#6313](https://github.com/gohugoio/hugo/issues/6313)
-
-## Fixes
-
-### Core
-
-* Fix broken bundle live reload logic [901077c0](https://github.com/gohugoio/hugo/commit/901077c0364eaf3fe4f997c3026aa18cfc7781ed) [@bep](https://github.com/bep) [#6315](https://github.com/gohugoio/hugo/issues/6315)[#6308](https://github.com/gohugoio/hugo/issues/6308)
-
-### Other
-
-* Fix elements are doubling when append a not assignable type [a9762b5c](https://github.com/gohugoio/hugo/commit/a9762b5c48054e036332eff541a8fd32e54ada13) [@vazrupe](https://github.com/vazrupe) [#6188](https://github.com/gohugoio/hugo/issues/6188)
-* Fix data race in global logger init [bc70f2bf](https://github.com/gohugoio/hugo/commit/bc70f2bf123d94fc3226754ec9f1f44748e98162) [@bep](https://github.com/bep) [#6409](https://github.com/gohugoio/hugo/issues/6409)
-* Fix image test error on s390x, ppc64* and arm64 [39ed33fc](https://github.com/gohugoio/hugo/commit/39ed33fcebcde91605e645fd28fd94020b442d97) [@anthonyfok](https://github.com/anthonyfok) [#6387](https://github.com/gohugoio/hugo/issues/6387)
-* Fix cache key transformed resources [6dec671f](https://github.com/gohugoio/hugo/commit/6dec671fb930029e18ba9aa5135b3a27adcddb21) [@bep](https://github.com/bep) [#6348](https://github.com/gohugoio/hugo/issues/6348)
-* Fix cache keys for bundled resoures in transform.Unmarshal [c0d75736](https://github.com/gohugoio/hugo/commit/c0d7573677e9726c14749ccd432dccb75e0d194d) [@bep](https://github.com/bep) [#6327](https://github.com/gohugoio/hugo/issues/6327)
-* Fix concat with fingerprint regression [3be2c253](https://github.com/gohugoio/hugo/commit/3be2c25351b421a26ee1ff2a38cbab00280c0583) [@bep](https://github.com/bep) [#6309](https://github.com/gohugoio/hugo/issues/6309)
-
-
-
-
-
diff --git a/docs/content/en/news/0.59.1-relnotes/index.md b/docs/content/en/news/0.59.1-relnotes/index.md
deleted file mode 100644
index 830ccaca156..00000000000
--- a/docs/content/en/news/0.59.1-relnotes/index.md
+++ /dev/null
@@ -1,25 +0,0 @@
-
----
-date: 2019-10-31
-title: "Hugo 0.59.1: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.59.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-* hugofs: Fix crash in multilingual content fs [33c474b9](https://github.com/gohugoio/hugo/commit/33c474b9b3bd470670740f30c5131071ce906b22) [@bep](https://github.com/bep) [#6463](https://github.com/gohugoio/hugo/issues/6463)
-* Dockerfile: Switch to mage builds, various optimizations [ed268232](https://github.com/gohugoio/hugo/commit/ed2682325aeb8fd1c8139077d14a5f6906757a4e) [@jakejarvis](https://github.com/jakejarvis)
-* resources/images: Add exception for new test image [66fe68ff](https://github.com/gohugoio/hugo/commit/66fe68ffc98974936e157b18cf6bd9266ee081a4) [@anthonyfok](https://github.com/anthonyfok) [#6439](https://github.com/gohugoio/hugo/issues/6439)
-* Adjust benchmark templates [c5e1e824](https://github.com/gohugoio/hugo/commit/c5e1e8241a3b9f922f4a5134064ab2847174a959) [@bep](https://github.com/bep)
-* deps: Update to Chroma v0.6.8 to fix a crash [baa97508](https://github.com/gohugoio/hugo/commit/baa975082c6809c8a02a8109ec3062a2b7d48344) [@bep](https://github.com/bep) [#6450](https://github.com/gohugoio/hugo/issues/6450)
-* deps: Update quicktest [3e8b5a5c](https://github.com/gohugoio/hugo/commit/3e8b5a5c0157fdcf93588a42fbc90b3cd898f6b1) [@bep](https://github.com/bep)
-* Do not attempt to build if there is no config file [e6aa6edb](https://github.com/gohugoio/hugo/commit/e6aa6edb4c5f37feb1f2bb8c0f3f80933c7adf5f) [@ollien](https://github.com/ollien) [#5896](https://github.com/gohugoio/hugo/issues/5896)
-
-
-
diff --git a/docs/content/en/news/0.60.0-relnotes/index.md b/docs/content/en/news/0.60.0-relnotes/index.md
deleted file mode 100644
index 8cdebb35ef9..00000000000
--- a/docs/content/en/news/0.60.0-relnotes/index.md
+++ /dev/null
@@ -1,136 +0,0 @@
-
----
-date: 2019-11-27
-title: "Now CommonMark Compliant!"
-description: "Goldmark -- CommonMark compliant, GitHub flavored, fast and flexible -- is the new default library for Markdown in Hugo."
-categories: ["Releases"]
----
-
-[Goldmark](https://github.com/yuin/goldmark/) by [@yuin](https://github.com/yuin) is now the new default library used for Markdown in Hugo. It's CommonMark compliant and GitHub flavored, and both fast and flexible. Blackfriday, the old default, has served us well, but there have been formatting and portability issues that were hard to work around. The "CommonMark compliant" part is the main selling feature of Goldmark, but with that you also get attribute syntax on headers and code blocks (for code blocks you can turn on/off line numbers and highlight line ranges), strikethrough support and an improved and configurable implementation of `TableOfContents`. See [Markup Configuration](https://gohugo.io/getting-started/configuration-markup/) for an overview of extensions.
-
-Please read the [Notes Section](#notes) and the updated documentation. We suggest you start with [List of content formats in Hugo](https://gohugo.io/content-management/formats/#list-of-content-formats). Goldmark is better, but the feature set is not fully comparable and it may be more stricter in some areas (there are 17 rules for how a [headline](https://spec.commonmark.org/0.29/#emphasis-and-strong-emphasis) should look like); if you have any problems you cannot work around, see [Configure Markup](https://gohugo.io/getting-started/configuration-markup/#configure-markup) for a way to change the default Markdown handler.
-
-Also, if you have lots of inline HTML in your Markdown files, you may have to enable the `unsafe` mode:
-
-{{< code-toggle file="config" >}}
-markup:
- goldmark:
- renderer:
- unsafe: true
-{{< /code-toggle >}}
-
-This release represents **62 contributions by 10 contributors** to the main Hugo code base. [@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@anthonyfok](https://github.com/anthonyfok), [@max-arnold](https://github.com/max-arnold), and [@trimbo](https://github.com/trimbo) for their ongoing contributions.
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) and [@onedrawingperday](https://github.com/onedrawingperday) for their relentless work on keeping the themes site in pristine condition and to [@kaushalmodi](https://github.com/kaushalmodi) and [@davidsneighbour](https://github.com/davidsneighbour) for great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **8 contributions by 4 contributors**. A special thanks to [@bep](https://github.com/bep), [@jasdeepgill](https://github.com/jasdeepgill), [@luucamay](https://github.com/luucamay), and [@jkreft-usgs](https://github.com/jkreft-usgs) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 39668+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 440+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 274+ [themes](http://themes.gohugo.io/)
-
-
-## Notes
-
-* Permalink config now supports Go date format strings. [#6489](https://github.com/gohugoio/hugo/pull/6489)
-* We have removed the option to use Pygments as a highlighter. [#4491](https://github.com/gohugoio/hugo/pull/4491)
-* Config option for code highlighting of code fences in Markdown is now default on. This is what most people wants.
-* There are some differences in the feature set of Goldmark and Blackfriday. See the documentation for details.
-* The highlight shortcode/template func and the code fence attributes now share the same API regarding line numbers and highlight ranges.
-* The `Total in ...` for the `hugo` command now includes the configuration and modules loading, which should make it more honest/accurate.
-* The image logic in the 3 SEO internal templates twitter_cards.html, opengraph.html, and schema.html is consolidated: `images` page param first, then bundled image matching `*feature*`, `*cover*` or `*thumbnail*`, then finally `images` site param.
-* Deprecate mmark [33d73330](https://github.com/gohugoio/hugo/commit/33d733300a4f0b765234706e51bb7e077fdc2471) [@bep](https://github.com/bep) [#6486](https://github.com/gohugoio/hugo/issues/6486)
-
-## Enhancements
-
-### Templates
-
-* Featured and Site.Params image support for Schema [c91970c0](https://github.com/gohugoio/hugo/commit/c91970c08ddf8c22ca4f967c2cc864c483987ac7) [@max-arnold](https://github.com/max-arnold)
-* Add support for featured and global image to OpenGraph template [25a6b336](https://github.com/gohugoio/hugo/commit/25a6b33693992e8c6d9c35bc1e781ce3e2bca4be) [@max-arnold](https://github.com/max-arnold)
-* Allow dict to create nested structures [a2670bf4](https://github.com/gohugoio/hugo/commit/a2670bf460e10ed5de69f90abbe7c4e2b32068cf) [@bep](https://github.com/bep) [#6497](https://github.com/gohugoio/hugo/issues/6497)
-* Add collections.Reverse [90d0cdf2](https://github.com/gohugoio/hugo/commit/90d0cdf236b54000bfe444ba3a00236faaa28790) [@bep](https://github.com/bep) [#6499](https://github.com/gohugoio/hugo/issues/6499)
-* Make index work with slice as the last arg [95ef93be](https://github.com/gohugoio/hugo/commit/95ef93be667afb480184175a319584fd651abf03) [@bep](https://github.com/bep) [#6496](https://github.com/gohugoio/hugo/issues/6496)
-* Add some index map test cases [9f46a72c](https://github.com/gohugoio/hugo/commit/9f46a72c7eec25a4b9dea387d5717173b8d9ec72) [@bep](https://github.com/bep) [#3974](https://github.com/gohugoio/hugo/issues/3974)
-
-### Output
-
-* Add some more output if modules download takes time [14a1de14](https://github.com/gohugoio/hugo/commit/14a1de14fb1ec93444ba5dd028fdad8959924545) [@bep](https://github.com/bep) [#6519](https://github.com/gohugoio/hugo/issues/6519)
-* Add some more output if loading modules takes time [2dcc1318](https://github.com/gohugoio/hugo/commit/2dcc1318d1d9ed849d040115aa5ba6191a1c102a) [@bep](https://github.com/bep) [#6519](https://github.com/gohugoio/hugo/issues/6519)
-
-### Core
-
-* Disable test assertion on Windows [dd1e5fc0](https://github.com/gohugoio/hugo/commit/dd1e5fc0b43739941372c0c27b75977380acd582) [@bep](https://github.com/bep)
-* Adjust .Site.Permalinks deprecation level [03b369e6](https://github.com/gohugoio/hugo/commit/03b369e6726ed8a732c07db48f7209425c434bbe) [@bep](https://github.com/bep)
-* Remove .Site.Ref/RelRef [69fd1c60](https://github.com/gohugoio/hugo/commit/69fd1c60d8bcf6d1cea4bfea852f62df8891ee81) [@bep](https://github.com/bep)
-* Increase default timeout value to 30s [a8e9f838](https://github.com/gohugoio/hugo/commit/a8e9f8389a61471fa372c815b216511201b56823) [@bep](https://github.com/bep) [#6502](https://github.com/gohugoio/hugo/issues/6502)
-* Add a benchmark [0cf85c07](https://github.com/gohugoio/hugo/commit/0cf85c071aba57de8c6567fba166ed8332d01bac) [@bep](https://github.com/bep)
-
-### Other
-
-* Add some internal template image tests [dcde8af8](https://github.com/gohugoio/hugo/commit/dcde8af8c6ab39eb34b5e1d6030d1aa2fe6923ca) [@bep](https://github.com/bep) [#6542](https://github.com/gohugoio/hugo/issues/6542)
-* Update Goldmark [b0c7749f](https://github.com/gohugoio/hugo/commit/b0c7749fa1efca04839b767e1d48d617f3556867) [@bep](https://github.com/bep)
-* Use HUGO_ENV if set [5c5231e0](https://github.com/gohugoio/hugo/commit/5c5231e09e20953dc262df7d3b351a35f1c4b058) [@bep](https://github.com/bep) [#6456](https://github.com/gohugoio/hugo/issues/6456)
-* Make the image cache more robust [d6f7a9e2](https://github.com/gohugoio/hugo/commit/d6f7a9e28dfd5abff08b6aaf6fb3493c46bd1e39) [@bep](https://github.com/bep) [#6501](https://github.com/gohugoio/hugo/issues/6501)
-* Update to Go 1.13.4 and Go 1.12.13 [031f948f](https://github.com/gohugoio/hugo/commit/031f948f87ac97ca49d0a487a392a8a0c6afb699) [@bep](https://github.com/bep)
-* Restore -v behaviour [71597bd1](https://github.com/gohugoio/hugo/commit/71597bd1adfb016a3ea1977068c37dce92d49458) [@bep](https://github.com/bep)
-* Update Goldmark [82219128](https://github.com/gohugoio/hugo/commit/8221912869cf863d64ae7b50e0085589dc18e4d2) [@bep](https://github.com/bep)
-* Improve grammar in README.md [e1175ae8](https://github.com/gohugoio/hugo/commit/e1175ae83a365e0b17ec5904194e68ff3833e15a) [@jasdeepgill](https://github.com/jasdeepgill)
-* Replace the temp for with a dependency [a2d77f4a](https://github.com/gohugoio/hugo/commit/a2d77f4a803ce27802ea653a4aab53b89c37b488) [@bep](https://github.com/bep)
-* Update Chroma [b546417a](https://github.com/gohugoio/hugo/commit/b546417a27f8c59c8c7ccaebfef6bca03f5c4ac4) [@bep](https://github.com/bep)
-* Update Goldmark [4175b046](https://github.com/gohugoio/hugo/commit/4175b0468680b076a5e5f90450157a98f841789b) [@bep](https://github.com/bep)
-* markup/tableofcontents: GoDoc etc. [55f951cb](https://github.com/gohugoio/hugo/commit/55f951cbba69c29daabca57eeff5661d132fa162) [@bep](https://github.com/bep)
-* Minor cleanups [20f351ee](https://github.com/gohugoio/hugo/commit/20f351ee4cd40b3b53e33805fc6226c837290ed7) [@moorereason](https://github.com/moorereason)
-* Add Goldmark as the new default markdown handler [bfb9613a](https://github.com/gohugoio/hugo/commit/bfb9613a14ab2d93a4474e5486d22e52a9d5e2b3) [@bep](https://github.com/bep) [#5963](https://github.com/gohugoio/hugo/issues/5963)[#1778](https://github.com/gohugoio/hugo/issues/1778)[#6355](https://github.com/gohugoio/hugo/issues/6355)
-* Add parallel task executor helper [628efd6e](https://github.com/gohugoio/hugo/commit/628efd6e293d27984a3f5ba33522f8edd19d69d6) [@bep](https://github.com/bep)
-* Update homepage.md [14a985f8](https://github.com/gohugoio/hugo/commit/14a985f8abc527d4e8487fcd5fa742e1ab2a00ed) [@bep](https://github.com/bep)
-* Do not check for remote modules if main project is vendored [20ec9fa2](https://github.com/gohugoio/hugo/commit/20ec9fa2bbd69dc47dfc9f1db40c954e08520071) [@bep](https://github.com/bep) [#6506](https://github.com/gohugoio/hugo/issues/6506)
-* Add hint when dir not empty [1a36ce9b](https://github.com/gohugoio/hugo/commit/1a36ce9b0903e02a5068aed5f807ed9d21f48ece) [@YaguraStation](https://github.com/YaguraStation) [#4825](https://github.com/gohugoio/hugo/issues/4825)
-* Headless bundles should not be listed in .Pages [d1d1f240](https://github.com/gohugoio/hugo/commit/d1d1f240a25945b37eebe8a9a3f439f290832b33) [@bep](https://github.com/bep) [#6492](https://github.com/gohugoio/hugo/issues/6492)
-* Support Go time format strings in permalinks [70a1aa34](https://github.com/gohugoio/hugo/commit/70a1aa345b95bcf325f19c6e7184bcd6f885e454) [@look](https://github.com/look)
-* Increase timeout to 30000 for mage -v check [cafecca4](https://github.com/gohugoio/hugo/commit/cafecca440e495ec915cc6290fe09d2a343e9c95) [@anthonyfok](https://github.com/anthonyfok)
-* Prepare for Goldmark [5f6b6ec6](https://github.com/gohugoio/hugo/commit/5f6b6ec68936ebbbf590894c02a1a3ecad30735f) [@bep](https://github.com/bep) [#5963](https://github.com/gohugoio/hugo/issues/5963)
-* Update quicktest [366ee4d8](https://github.com/gohugoio/hugo/commit/366ee4d8da1c2b0c1751e9bf6d54638439735296) [@bep](https://github.com/bep)
-* Use pointer receiver for ContentSpec [9abd3967](https://github.com/gohugoio/hugo/commit/9abd396789d007193145db9246d5daf1640bbb8a) [@bep](https://github.com/bep)
-* Allow arm64 to fail [ad4c56b5](https://github.com/gohugoio/hugo/commit/ad4c56b5512226e74fb4ed6f10630d26d93e9eb6) [@bep](https://github.com/bep)
-* Add a JSON roundtrip test [3717db1f](https://github.com/gohugoio/hugo/commit/3717db1f90797f4e2a5d546472fb6b6df072d435) [@bep](https://github.com/bep) [#6472](https://github.com/gohugoio/hugo/issues/6472)
-* Update .travis.yml for arm64 support, etc. [ae4fde08](https://github.com/gohugoio/hugo/commit/ae4fde0866b2a10f0a414e0d76c4ff09bed3776e) [@anthonyfok](https://github.com/anthonyfok)
-* Skip Test386 on non-AMD64 architectures [c6d69d0c](https://github.com/gohugoio/hugo/commit/c6d69d0c95c42915956c210dbac8b884682d4a3e) [@anthonyfok](https://github.com/anthonyfok)
-* Switch to mage builds, various optimizations [ed268232](https://github.com/gohugoio/hugo/commit/ed2682325aeb8fd1c8139077d14a5f6906757a4e) [@jakejarvis](https://github.com/jakejarvis)
-* Add exception for new test image [66fe68ff](https://github.com/gohugoio/hugo/commit/66fe68ffc98974936e157b18cf6bd9266ee081a4) [@anthonyfok](https://github.com/anthonyfok) [#6439](https://github.com/gohugoio/hugo/issues/6439)
-* Adjust benchmark templates [c5e1e824](https://github.com/gohugoio/hugo/commit/c5e1e8241a3b9f922f4a5134064ab2847174a959) [@bep](https://github.com/bep)
-* Update quicktest [3e8b5a5c](https://github.com/gohugoio/hugo/commit/3e8b5a5c0157fdcf93588a42fbc90b3cd898f6b1) [@bep](https://github.com/bep)
-* Do not attempt to build if there is no config file [e6aa6edb](https://github.com/gohugoio/hugo/commit/e6aa6edb4c5f37feb1f2bb8c0f3f80933c7adf5f) [@ollien](https://github.com/ollien) [#5896](https://github.com/gohugoio/hugo/issues/5896)
-
-## Fixes
-
-### Output
-
-* Fix mage check on darwin and add debugging output [8beaa4c2](https://github.com/gohugoio/hugo/commit/8beaa4c25efb593d0363271000a3667b96567976) [@trimbo](https://github.com/trimbo)
-
-### Core
-
-* Fix cascade in server mode [01766439](https://github.com/gohugoio/hugo/commit/01766439246add22a6e6d0c12f932610be55cd8a) [@bep](https://github.com/bep) [#6538](https://github.com/gohugoio/hugo/issues/6538)
-* Fix .Sections vs siblings [da535235](https://github.com/gohugoio/hugo/commit/da53523599b43261520a22d77019b390aaa072e7) [@bep](https://github.com/bep) [#6365](https://github.com/gohugoio/hugo/issues/6365)
-* Fix recently broken timeout config [e3451371](https://github.com/gohugoio/hugo/commit/e3451371bdb68015f89c8c0f7d8ea0a19fff8df5) [@bep](https://github.com/bep)
-* Fix emoji handling inside shortcodes [812688fc](https://github.com/gohugoio/hugo/commit/812688fc2f3e220ac35cad9f0445a2548f0cc603) [@bep](https://github.com/bep) [#6504](https://github.com/gohugoio/hugo/issues/6504)
-* Fix ref/relref anhcor handling [c26d00db](https://github.com/gohugoio/hugo/commit/c26d00db648a4b475d94c9ed8e21dafb6efa1776) [@bep](https://github.com/bep) [#6481](https://github.com/gohugoio/hugo/issues/6481)
-
-### Other
-
-* Fix language handling in ExecuteAsTemplate [96f09659](https://github.com/gohugoio/hugo/commit/96f09659ce8752c32a2a6429c9faf23be4faa091) [@bep](https://github.com/bep) [#6331](https://github.com/gohugoio/hugo/issues/6331)
-* Fix potential data race [03e2d746](https://github.com/gohugoio/hugo/commit/03e2d7462dec17c2f623a13db709f9efc88182af) [@bep](https://github.com/bep) [#6478](https://github.com/gohugoio/hugo/issues/6478)
-* Fix jekyll metadata import on individual posts [8a89b858](https://github.com/gohugoio/hugo/commit/8a89b8582f0f681dc28961adb05ab0bf66da9543) [@trimbo](https://github.com/trimbo) [#5576](https://github.com/gohugoio/hugo/issues/5576)
-* Fix Params case handling in the index, sort and where func [a3fe5e5e](https://github.com/gohugoio/hugo/commit/a3fe5e5e35f311f22b6b4fc38abfcf64cd2c7d6f) [@bep](https://github.com/bep)
-* Fix GetPage Params case issue [cd07e6d5](https://github.com/gohugoio/hugo/commit/cd07e6d57b158a76f812e8c4c9567dbc84f57939) [@bep](https://github.com/bep) [#5946](https://github.com/gohugoio/hugo/issues/5946)
-* Update to Chroma v0.6.9 for Java lexer fix [8483b53a](https://github.com/gohugoio/hugo/commit/8483b53aefc3c6b52f9917e6e5af9c4d2e98df66) [@anthonyfok](https://github.com/anthonyfok) [#6476](https://github.com/gohugoio/hugo/issues/6476)
-* Update past go-cmp's checkptr fix [c3d433af](https://github.com/gohugoio/hugo/commit/c3d433af56071d42aeb3f85854bd30db64ed70b8) [@anthonyfok](https://github.com/anthonyfok)
-* Fix crash in multilingual content fs [33c474b9](https://github.com/gohugoio/hugo/commit/33c474b9b3bd470670740f30c5131071ce906b22) [@bep](https://github.com/bep) [#6463](https://github.com/gohugoio/hugo/issues/6463)
-* Update to Chroma v0.6.8 to fix a crash [baa97508](https://github.com/gohugoio/hugo/commit/baa975082c6809c8a02a8109ec3062a2b7d48344) [@bep](https://github.com/bep) [#6450](https://github.com/gohugoio/hugo/issues/6450)
-
-
-
-
-
diff --git a/docs/content/en/news/0.60.0-relnotes/poster-featured.png b/docs/content/en/news/0.60.0-relnotes/poster-featured.png
deleted file mode 100644
index 9bd99be59a7..00000000000
Binary files a/docs/content/en/news/0.60.0-relnotes/poster-featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.60.1-relnotes/featured-061.png b/docs/content/en/news/0.60.1-relnotes/featured-061.png
deleted file mode 100644
index 8ff4d4af9ed..00000000000
Binary files a/docs/content/en/news/0.60.1-relnotes/featured-061.png and /dev/null differ
diff --git a/docs/content/en/news/0.60.1-relnotes/index.md b/docs/content/en/news/0.60.1-relnotes/index.md
deleted file mode 100644
index 2709c7b6f0b..00000000000
--- a/docs/content/en/news/0.60.1-relnotes/index.md
+++ /dev/null
@@ -1,19 +0,0 @@
-
----
-date: 2019-11-29
-title: "Hugo 0.60.1: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.60.0."
-categories: ["Releases"]
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-* deps: Update minify [86a5b59f](https://github.com/gohugoio/hugo/commit/86a5b59f64dd6c4d338a9e091e98cd0ad6d4824f) [@MeiK2333](https://github.com/MeiK2333) [#6475](https://github.com/gohugoio/hugo/issues/6475)
-* Fix headless regression [bb80fff6](https://github.com/gohugoio/hugo/commit/bb80fff69ad3f2ddff23819bf6eb6f4b8512dc2a) [@bep](https://github.com/bep) [#6552](https://github.com/gohugoio/hugo/issues/6552)
-* deps: Update Goldmark [347cfb0c](https://github.com/gohugoio/hugo/commit/347cfb0c17b08626250180e8a84b53fc4800473f) [@bep](https://github.com/bep) [#6549](https://github.com/gohugoio/hugo/issues/6549)[#6551](https://github.com/gohugoio/hugo/issues/6551)
-* hugolib: Fix timeout number parsing for YAML/JSON config [b60ae35b](https://github.com/gohugoio/hugo/commit/b60ae35b97c4f44b9b09fcf06c863c695bc3c73a) [@bep](https://github.com/bep) [#6555](https://github.com/gohugoio/hugo/issues/6555)
-
-
-
diff --git a/docs/content/en/news/0.61.0-relnotes/hugo-61-featured.png b/docs/content/en/news/0.61.0-relnotes/hugo-61-featured.png
deleted file mode 100644
index 8691f30e24e..00000000000
Binary files a/docs/content/en/news/0.61.0-relnotes/hugo-61-featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.61.0-relnotes/index.md b/docs/content/en/news/0.61.0-relnotes/index.md
deleted file mode 100644
index 2922506dfa1..00000000000
--- a/docs/content/en/news/0.61.0-relnotes/index.md
+++ /dev/null
@@ -1,56 +0,0 @@
-
----
-date: 2019-12-11
-title: "40K GitHub Stars Edition"
-description: "40K stars on GitHub is a good enough reason to release a new version of Hugo!"
-categories: ["Releases"]
----
-
-This is the [40K GitHub Stars Edition](https://github.com/gohugoio/hugo/stargazers). It's mostly a bug fix release, and an important note is the deprecation of Amber and Ace as template engines. See [#6609](https://github.com/gohugoio/hugo/issues/6609) for more information.
-
-This release represents **10 contributions by 3 contributors** to the main Hugo code base.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **5 contributions by 4 contributors**. A special thanks to [@YuriyOborozhnyi](https://github.com/YuriyOborozhnyi), [@bep](https://github.com/bep), [@Flogex](https://github.com/Flogex), and [@atishay](https://github.com/atishay) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 40029+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 440+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 279+ [themes](http://themes.gohugo.io/)
-
-
-## Notes
-
-* Deprecate Ace and Amber [5f8c2818](https://github.com/gohugoio/hugo/commit/5f8c2818f120b881f58f4cec67aed876edb8bcdf) [@bep](https://github.com/bep) [#6609](https://github.com/gohugoio/hugo/issues/6609)
-
-## Enhancements
-
-### Templates
-
-* Allow any key type in partialCached [0efb00c2](https://github.com/gohugoio/hugo/commit/0efb00c2a86ec3f52000a643f26f54bb2a9dfbd6) [@bep](https://github.com/bep) [#6572](https://github.com/gohugoio/hugo/issues/6572)
-
-### Other
-
-* Update Goldmark [3cc217a6](https://github.com/gohugoio/hugo/commit/3cc217a650546b8bc29deabb95e648aacef96fbf) [@bep](https://github.com/bep)
-* Add typographic chars from goldmark to toc [c5f2f583](https://github.com/gohugoio/hugo/commit/c5f2f5837fdf6a30c7b28e8368033623b74a30a0) [@tangiel](https://github.com/tangiel) [#6592](https://github.com/gohugoio/hugo/issues/6592)
-* Reimplement pygmentsCodefencesGuessSyntax [40a092b0](https://github.com/gohugoio/hugo/commit/40a092b0687d44ecb53ef1fd53001a6299345780) [@bep](https://github.com/bep) [#6565](https://github.com/gohugoio/hugo/issues/6565)
-* Update Goldmark [d534ce94](https://github.com/gohugoio/hugo/commit/d534ce9424c952800dfb26c2faff2d47e9597cad) [@bep](https://github.com/bep) [#6557](https://github.com/gohugoio/hugo/issues/6557)
-* Update minify [86a5b59f](https://github.com/gohugoio/hugo/commit/86a5b59f64dd6c4d338a9e091e98cd0ad6d4824f) [@MeiK2333](https://github.com/MeiK2333) [#6475](https://github.com/gohugoio/hugo/issues/6475)
-* Update Goldmark [347cfb0c](https://github.com/gohugoio/hugo/commit/347cfb0c17b08626250180e8a84b53fc4800473f) [@bep](https://github.com/bep) [#6549](https://github.com/gohugoio/hugo/issues/6549)[#6551](https://github.com/gohugoio/hugo/issues/6551)
-
-## Fixes
-
-### Core
-
-* Fix timeout number parsing for YAML/JSON config [b60ae35b](https://github.com/gohugoio/hugo/commit/b60ae35b97c4f44b9b09fcf06c863c695bc3c73a) [@bep](https://github.com/bep) [#6555](https://github.com/gohugoio/hugo/issues/6555)
-
-### Other
-
-* Fix headless regression [bb80fff6](https://github.com/gohugoio/hugo/commit/bb80fff69ad3f2ddff23819bf6eb6f4b8512dc2a) [@bep](https://github.com/bep) [#6552](https://github.com/gohugoio/hugo/issues/6552)
-
-
-
-
-
diff --git a/docs/content/en/news/0.62.0-relnotes/hugo-62-poster-featured.png b/docs/content/en/news/0.62.0-relnotes/hugo-62-poster-featured.png
deleted file mode 100644
index 9a024c023b6..00000000000
Binary files a/docs/content/en/news/0.62.0-relnotes/hugo-62-poster-featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.62.0-relnotes/index.md b/docs/content/en/news/0.62.0-relnotes/index.md
deleted file mode 100644
index 71f01145de8..00000000000
--- a/docs/content/en/news/0.62.0-relnotes/index.md
+++ /dev/null
@@ -1,83 +0,0 @@
-
----
-date: 2019-12-23
-title: "Hugo Christmas Edition!"
-description: "Hugo 0.62 brings Markdown Render Hooks. And it's faster!"
-categories: ["Releases"]
----
-
-From all of us to all of you, a **very Merry Christmas** -- and Hugo `0.62.0`! This version brings [Markdown Render Hooks](https://gohugo.io/getting-started/configuration-markup/#markdown-render-hooks). This gives you full control over how links and images in Markdown are rendered without using any shortcodes. With this, you can get Markdown links that work on both GitHub and Hugo, resize images etc. It is a very long sought after feature, that has been hard to tackle until we got [Goldmark](https://github.com/yuin/goldmark/), the new Markdown engine, by [@yuin](https://github.com/yuin). When you read up on this new feature in the documentation, also note the new [.RenderString](https://gohugo.io/functions/renderstring/) method on `Page`.
-
-Adding these render hooks also had the nice side effect of making Hugo **faster and more memory effective**. We could have just added this feature on top of what we got, getting it to work. But you like Hugo's fast builds, you love instant browser-refreshes on change. So we had to take a step back and redesign how we detect "what changed?" for templates referenced from content files, either directly or indirectly. And by doing that we greatly simplified how we handle all the templates. Which accidentally makes this version **the fastest to date**. It's not an "every site will be much faster" statement. This depends. Sites with many languages and/or many templates will benefit more from this. We have benchmarks with site-building showing about 15% improvement in build speed and memory efficiency.
-
-This release represents **25 contributions by 5 contributors** to the main Hugo code base. [@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@gavinhoward](https://github.com/gavinhoward), [@niklasfasching](https://github.com/niklasfasching), and [@zaitseff](https://github.com/zaitseff) for their ongoing contributions. And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) and [@onedrawingperday](https://github.com/onedrawingperday) for their relentless work on keeping the themes site in pristine condition and to [@kaushalmodi](https://github.com/kaushalmodi) for his great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs), which has received **8 contributions by 5 contributors**. A special thanks to [@bep](https://github.com/bep), [@DirtyF](https://github.com/DirtyF), [@pfhawkins](https://github.com/pfhawkins), and [@bubelov](https://github.com/bubelov) for their work on the documentation site.
-
-Also a big shoutout and thanks to the very active and helpful moderators on the [Hugo Discourse](https://discourse.gohugo.io/), making it a first class forum for Hugo questions and discussions.
-
-Hugo now has:
-
-* 40362+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 440+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 284+ [themes](http://themes.gohugo.io/)
-
-## Notes
-
-* Ace and Amber support is now removed from Hugo. See [#6609](https://github.com/gohugoio/hugo/issues/6609) for more information.
-* The `markdownify` template function does not, yet, support render hooks. We recommend you look at the new and more powerful [.RenderString](https://gohugo.io/functions/renderstring/) method on `Page`.
-* If you have output format specific behaviour in a template used from a content file, you must create a output format specific template, e.g. `myshortcode.amp.html`. This also applies to the new rendering hooks introduced in this release. This has been the intended behaviour all the time, but a failing test (now fixed) shows that the implementation of this has not been as strict as specified, hence this note.
-* The `errorf` does not return any value anymore. This means that the ERROR will just be printed to the console. We have also added a `warnf` template func.
-
-
-## Enhancements
-
-### Templates
-
-* Do not return any value in errorf [50cc7fe5](https://github.com/gohugoio/hugo/commit/50cc7fe54580018239ea95aafe67f6a158cdcc9f) [@bep](https://github.com/bep) [#6653](https://github.com/gohugoio/hugo/issues/6653)
-* Add a warnf template func [1773d71d](https://github.com/gohugoio/hugo/commit/1773d71d5b40f5a6a14edca417d2818607a499f1) [@bep](https://github.com/bep) [#6628](https://github.com/gohugoio/hugo/issues/6628)
-* Some more params merge adjustments [ccb1bf1a](https://github.com/gohugoio/hugo/commit/ccb1bf1abb7341fa1be23a90b66c14ae89790f49) [@bep](https://github.com/bep) [#6633](https://github.com/gohugoio/hugo/issues/6633)
-* Get rid of the custom template truth logic [d20ca370](https://github.com/gohugoio/hugo/commit/d20ca3700512d661247b44d953515b9455e57ed6) [@bep](https://github.com/bep) [#6615](https://github.com/gohugoio/hugo/issues/6615)
-* Add some comments [92c7f7ab](https://github.com/gohugoio/hugo/commit/92c7f7ab85a40cae8f36f2348d86f3e47d811eb5) [@bep](https://github.com/bep)
-
-### Core
-
-* Improve error and reload handling of hook templates in server mode [8a58ebb3](https://github.com/gohugoio/hugo/commit/8a58ebb311fd079f65068e7e37725e4d43f17ab5) [@bep](https://github.com/bep) [#6635](https://github.com/gohugoio/hugo/issues/6635)
-
-### Other
-
-* Update Goldmark to v1.1.18 [1fb17be9](https://github.com/gohugoio/hugo/commit/1fb17be9a008b549d11b622849adbaad01d4023d) [@bep](https://github.com/bep) [#6649](https://github.com/gohugoio/hugo/issues/6649)
-* Update go-org [51d89dab](https://github.com/gohugoio/hugo/commit/51d89dab1827ae80f9d865f5c38cb5f6a3a11f68) [@niklasfasching](https://github.com/niklasfasching)
-* More on hooks [c8bfe47c](https://github.com/gohugoio/hugo/commit/c8bfe47c6a740c5fedfdb5b7465d7ae1db44cb65) [@bep](https://github.com/bep)
-* Update to Goldmark v1.1.17 [04536838](https://github.com/gohugoio/hugo/commit/0453683816cfbc94e1e19c644f5f84213bb8cf35) [@bep](https://github.com/bep) [#6641](https://github.com/gohugoio/hugo/issues/6641)
-* Regen docshelper [55c29d4d](https://github.com/gohugoio/hugo/commit/55c29d4de38df67dd116f1845f7cc69ca7e35843) [@bep](https://github.com/bep)
-* Preserve HTML Text for image render hooks [a67d95fe](https://github.com/gohugoio/hugo/commit/a67d95fe1a033ca4934957b5a98b12ecc8a9edbd) [@bep](https://github.com/bep) [#6639](https://github.com/gohugoio/hugo/issues/6639)
-* Update Goldmark [eef934ae](https://github.com/gohugoio/hugo/commit/eef934ae7eabc38eeba386831de6013eec0285f2) [@bep](https://github.com/bep) [#6626](https://github.com/gohugoio/hugo/issues/6626)
-* Preserve HTML Text for link render hooks [00954c5d](https://github.com/gohugoio/hugo/commit/00954c5d1fda0b18cd1b847ee580d5f4caa76c70) [@bep](https://github.com/bep) [#6629](https://github.com/gohugoio/hugo/issues/6629)
-* Footnote [3e316155](https://github.com/gohugoio/hugo/commit/3e316155c5d4fbf166d38e997a41101b6aa501d5) [@bep](https://github.com/bep)
-* Add render template hooks for links and images [e625088e](https://github.com/gohugoio/hugo/commit/e625088ef5a970388ad50e464e87db56b358dac4) [@bep](https://github.com/bep) [#6545](https://github.com/gohugoio/hugo/issues/6545)[#4663](https://github.com/gohugoio/hugo/issues/4663)[#6043](https://github.com/gohugoio/hugo/issues/6043)
-* Enhance accessibility to issues [0947cf95](https://github.com/gohugoio/hugo/commit/0947cf958358e5a45b4f605e2a5b2504896fa360) [@peaceiris](https://github.com/peaceiris) [#6233](https://github.com/gohugoio/hugo/issues/6233)
-* Re-introduce the correct version of Goldmark [03d6960a](https://github.com/gohugoio/hugo/commit/03d6960a15dcc8efc164e5ed310b12bd1ffdd930) [@bep](https://github.com/bep)
-* Rework template handling for function and map lookups [a03c631c](https://github.com/gohugoio/hugo/commit/a03c631c420a03f9d90699abdf9be7e4fca0ff61) [@bep](https://github.com/bep) [#6594](https://github.com/gohugoio/hugo/issues/6594)
-* Create lightweight forks of text/template and html/template [167c0153](https://github.com/gohugoio/hugo/commit/167c01530bb295c8b8d35921eb27ffa5bee76dfe) [@bep](https://github.com/bep) [#6594](https://github.com/gohugoio/hugo/issues/6594)
-* Add config option for ordered list [4c804319](https://github.com/gohugoio/hugo/commit/4c804319f6db0b8459cc9b5df4a904fd2c55dedd) [@gavinhoward](https://github.com/gavinhoward)
-
-## Fixes
-
-### Templates
-
-* Fix merge vs Params [1b785a7a](https://github.com/gohugoio/hugo/commit/1b785a7a6d3c264e39e4976c59b618c0ac1ba5f9) [@bep](https://github.com/bep) [#6633](https://github.com/gohugoio/hugo/issues/6633)
-
-### Core
-
-* Fix test [3c24ae03](https://github.com/gohugoio/hugo/commit/3c24ae030fe08ba259dd3de7ffea6c927c01e070) [@bep](https://github.com/bep)
-
-### Other
-
-* Fix abs path handling in module mounts [ad6504e6](https://github.com/gohugoio/hugo/commit/ad6504e6b504277bbc7b60d093cdccd4f3baaa4f) [@bep](https://github.com/bep) [#6622](https://github.com/gohugoio/hugo/issues/6622)
-* Fix incorrect MIME type from image/jpg to image/jpeg [158e7ec2](https://github.com/gohugoio/hugo/commit/158e7ec204e5149d77893d353cac9f55946d3e9a) [@zaitseff](https://github.com/zaitseff)
-
-
-
-
-
diff --git a/docs/content/en/news/0.62.1-relnotes/index.md b/docs/content/en/news/0.62.1-relnotes/index.md
deleted file mode 100644
index 98fe5eb5b12..00000000000
--- a/docs/content/en/news/0.62.1-relnotes/index.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-date: 2020-01-01
-title: "Hugo 0.62.1: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.62.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
----
-
-This release is mainly motivated by getting [this demo site](https://github.com/bep/portable-hugo-links) up and running. It demonstrates truly portable Markdown links and images, whether browsed on GitHub or deployed as a Hugo site.
-
-* Support files in content mounts [ff6253bc](https://github.com/gohugoio/hugo/commit/ff6253bc7cf745e9c0127ddc9006da3c2c00c738) [@bep](https://github.com/bep) [#6684](https://github.com/gohugoio/hugo/issues/6684)[#6696](https://github.com/gohugoio/hugo/issues/6696)
-* Update alpine base image in Dockerfile to 3.11 [aa4ccb8a](https://github.com/gohugoio/hugo/commit/aa4ccb8a1e9b8aa17397acf34049a2aa16b0b6cb) [@RemcodM](https://github.com/RemcodM)
-* hugolib: Fix inline shortcode regression [5509954c](https://github.com/gohugoio/hugo/commit/5509954c7e8b0ce8d5ea903b0ab639ea14b69acb) [@bep](https://github.com/bep) [#6677](https://github.com/gohugoio/hugo/issues/6677)
-
-
-
diff --git a/docs/content/en/news/0.62.2-relnotes/index.md b/docs/content/en/news/0.62.2-relnotes/index.md
deleted file mode 100644
index 0d116e5a295..00000000000
--- a/docs/content/en/news/0.62.2-relnotes/index.md
+++ /dev/null
@@ -1,26 +0,0 @@
-
----
-date: 2020-01-05
-title: "Hugo 0.62.2: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.62.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-The main driving force behind these patch releases in the new year has been getting a good story with [portable links](https://github.com/bep/portable-hugo-links/) between GitHub and Hugo, using the new render hooks introduced in [Hugo 0.62.0](https://gohugo.io/news/0.62.0-relnotes/). And all was mostly well until a Hugo user asked about anchor links. Which is, when you look into it, a slightly sad Markdown story. They have been [talking about anchors in Markdown](https://talk.commonmark.org/t/anchors-in-markdown/247) over at the CommonMark forum for the last six years, but it has come to nothing. The current situation is that some Markdown engines support the `{#anchorName}` attribute syntax (Hugo's Goldmark does, GitHub does not) and for those that support auto-generation of IDs, the implementation often varies. And this makes for poor portability.
-
-To improve this, Hugo has now reverse-engineered GitHub's implementation and made that the default strategy for generation or header IDs. We understand that this isn't everyone's cup of tea, so you can [configure the behaviour](https://gohugo.io/getting-started/configuration-markup#goldmark) to be one of `github`, `github-ascii` (some client-libraries have Unicode issues) or `blackfriday` (which will match how it behaved before Hugo 0.60).
-
-* hugolib: Fix relative .Page.GetPage from bundle [196a9df5](https://github.com/gohugoio/hugo/commit/196a9df585c4744e3280f37c1c24e469fce14b8c) [@bep](https://github.com/bep) [#6705](https://github.com/gohugoio/hugo/issues/6705)
-* markup/goldmark: Adjust auto ID space handling [9b6e6146](https://github.com/gohugoio/hugo/commit/9b6e61464b09ffe3423fb8d7c72bddb7a9ed5b98) [@bep](https://github.com/bep) [#6710](https://github.com/gohugoio/hugo/issues/6710)
-* docs: Document the new autoHeadingIDType setting [d62ede8e](https://github.com/gohugoio/hugo/commit/d62ede8e9e5883e7ebb023e49b82f07b45edc1c7) [@bep](https://github.com/bep) [#6707](https://github.com/gohugoio/hugo/issues/6707)[#6616](https://github.com/gohugoio/hugo/issues/6616)
-* docs: Regenerate docshelper [81b7e48a](https://github.com/gohugoio/hugo/commit/81b7e48a55092203aeee8785799e6fed3928760e) [@bep](https://github.com/bep) [#6707](https://github.com/gohugoio/hugo/issues/6707)[#6616](https://github.com/gohugoio/hugo/issues/6616)
-* markup/goldmark: Add an optional Blackfriday auto ID strategy [16e7c112](https://github.com/gohugoio/hugo/commit/16e7c1120346bd853cf6510ffac8e94824bf2c7f) [@bep](https://github.com/bep) [#6707](https://github.com/gohugoio/hugo/issues/6707)
-* markup/goldmark: Make the autoID type config a string [8f071fc1](https://github.com/gohugoio/hugo/commit/8f071fc159ce9a0fc0ea14a73bde8f299bedd109) [@bep](https://github.com/bep) [#6707](https://github.com/gohugoio/hugo/issues/6707)
-* markup/goldmark: Simplify code [5ee1f087](https://github.com/gohugoio/hugo/commit/5ee1f0876f3ec8b79d6305298185dc821ead2d28) [@bep](https://github.com/bep)
-* markup/goldmark: Make auto IDs GitHub compatible [a82d2700](https://github.com/gohugoio/hugo/commit/a82d2700fcc772aada15d65b8f76913ca23f7404) [@bep](https://github.com/bep) [#6616](https://github.com/gohugoio/hugo/issues/6616)
-
-
-
diff --git a/docs/content/en/news/0.63.0-relnotes/featured-063.png b/docs/content/en/news/0.63.0-relnotes/featured-063.png
deleted file mode 100644
index 3944d52ccaf..00000000000
Binary files a/docs/content/en/news/0.63.0-relnotes/featured-063.png and /dev/null differ
diff --git a/docs/content/en/news/0.63.0-relnotes/index.md b/docs/content/en/news/0.63.0-relnotes/index.md
deleted file mode 100644
index 899dfdb39d0..00000000000
--- a/docs/content/en/news/0.63.0-relnotes/index.md
+++ /dev/null
@@ -1,126 +0,0 @@
-
----
-date: 2020-01-23
-title: "Improved base templates, and faster!"
-description: "In Hugo 0.63 we have improved the base template lookup logic, and this simplification also made Hugo faster …"
-categories: ["Releases"]
----
-
-**Note:** There is already a [patch release](/news/0.63.1-relnotes/) with some fixes.
-
-Hugo `0.63` is, in general, **considerably faster and more memory effective** (see the site-building benchmarks below comparing it to `v0.62`). Not that we are particularly concerned about Hugo's build speed. We leave that to others. But we would hate if it got slower, so we have a comprehensive benchmark suite. And when we needed to simplify the template handling code to solve a concurrency issue, it also became more effective. And as a bonus, we also finally got the [base template lookup order](https://gohugo.io/templates/base/#base-template-lookup-order) that you really, really wanted!
-
-```bash
-name old time/op new time/op delta
-SiteNew/Bundle_with_image-16 13.2ms ± 2% 10.7ms ± 0% -19.13% (p=0.029 n=4+4)
-SiteNew/Bundle_with_JSON_file-16 13.1ms ± 0% 10.8ms ± 0% -17.50% (p=0.029 n=4+4)
-SiteNew/Tags_and_categories-16 47.7ms ± 1% 43.7ms ± 2% -8.43% (p=0.029 n=4+4)
-SiteNew/Canonify_URLs-16 52.3ms ± 6% 49.5ms ± 7% ~ (p=0.200 n=4+4)
-SiteNew/Deep_content_tree-16 77.7ms ± 0% 71.6ms ± 1% -7.84% (p=0.029 n=4+4)
-SiteNew/Many_HTML_templates-16 44.0ms ± 2% 37.5ms ± 1% -14.79% (p=0.029 n=4+4)
-SiteNew/Page_collections-16 58.4ms ± 1% 52.5ms ± 1% -10.09% (p=0.029 n=4+4)
-
-name old alloc/op new alloc/op delta
-SiteNew/Bundle_with_image-16 3.81MB ± 0% 2.22MB ± 0% -41.57% (p=0.029 n=4+4)
-SiteNew/Bundle_with_JSON_file-16 3.60MB ± 0% 2.01MB ± 0% -44.09% (p=0.029 n=4+4)
-SiteNew/Tags_and_categories-16 19.3MB ± 1% 14.2MB ± 0% -26.52% (p=0.029 n=4+4)
-SiteNew/Canonify_URLs-16 70.7MB ± 0% 69.0MB ± 0% -2.30% (p=0.029 n=4+4)
-SiteNew/Deep_content_tree-16 37.0MB ± 0% 31.2MB ± 0% -15.78% (p=0.029 n=4+4)
-SiteNew/Many_HTML_templates-16 17.5MB ± 0% 10.6MB ± 0% -39.68% (p=0.029 n=4+4)
-SiteNew/Page_collections-16 25.8MB ± 0% 21.2MB ± 0% -17.80% (p=0.029 n=4+4)
-
-name old allocs/op new allocs/op delta
-SiteNew/Bundle_with_image-16 52.3k ± 0% 26.1k ± 0% -50.08% (p=0.029 n=4+4)
-SiteNew/Bundle_with_JSON_file-16 52.3k ± 0% 26.1k ± 0% -50.06% (p=0.029 n=4+4)
-SiteNew/Tags_and_categories-16 337k ± 1% 272k ± 0% -19.20% (p=0.029 n=4+4)
-SiteNew/Canonify_URLs-16 422k ± 0% 395k ± 0% -6.33% (p=0.029 n=4+4)
-SiteNew/Deep_content_tree-16 400k ± 0% 314k ± 0% -21.41% (p=0.029 n=4+4)
-SiteNew/Many_HTML_templates-16 247k ± 0% 143k ± 0% -41.84% (p=0.029 n=4+4)
-SiteNew/Page_collections-16 282k ± 0% 207k ± 0% -26.31% (p=0.029 n=4+4)
-```
-
-This release represents **35 contributions by 9 contributors** to the main Hugo code base. [@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@hcwong](https://github.com/hcwong), [@flother](https://github.com/flother), and [@RemcodM](https://github.com/RemcodM) for their ongoing contributions.
-
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) and [@onedrawingperday](https://github.com/onedrawingperday) for their relentless work on keeping the themes site in pristine condition and to [@davidsneighbour](https://github.com/davidsneighbour) and [@kaushalmodi](https://github.com/kaushalmodi) for all the great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **13 contributions by 10 contributors**. A special thanks to [@bep](https://github.com/bep), [@jeffscottlevine](https://github.com/jeffscottlevine), [@davidsneighbour](https://github.com/davidsneighbour), and [@nicfits](https://github.com/nicfits) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 41091+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 440+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 286+ [themes](http://themes.gohugo.io/)
-
-## Notes
-
-* `.Type` on-page now returns an empty string if not set in the front matter or if `.Section` returns empty. See [#6760](https://github.com/gohugoio/hugo/issues/6760).
-* Baseof template (e.g. `baseof.html`) lookup order is completely revised/expanded. See [base template lookup order](https://gohugo.io/templates/base/#base-template-lookup-order). We now do template resolution at execution time following the same rules as the template it applies to (e.g. `_default/single.html`). This is an obvious improvement and your site should work as before.
-* Shortcode params now supports params with raw string literals (strings surrounded with \`\`) that supports newlines.
-* Note: In a base template (e.g. `baseof.html`), the first template block must be a `define`.
-
-## Enhancements
-
-### Templates
-
-* Rework template management to get rid of concurrency issues [c6d650c8](https://github.com/gohugoio/hugo/commit/c6d650c8c8b22fdc7ddedc1e42a3ca698e1390d6) [@bep](https://github.com/bep) [#6716](https://github.com/gohugoio/hugo/issues/6716)[#6760](https://github.com/gohugoio/hugo/issues/6760)[#6768](https://github.com/gohugoio/hugo/issues/6768)[#6778](https://github.com/gohugoio/hugo/issues/6778)
-* Put Go's internal template funcs in Hugo's map [1cf23541](https://github.com/gohugoio/hugo/commit/1cf235412f98b42aefe368e99a0e9e95bae6eef7) [@bep](https://github.com/bep) [#6717](https://github.com/gohugoio/hugo/issues/6717)
-
-### Output
-
-* Add base template lookup variant to docs.json [cafb1d53](https://github.com/gohugoio/hugo/commit/cafb1d53c0927e2aef8abff1bf9095c90c6f3067) [@bep](https://github.com/bep)
-
-### Core
-
-* Disable a test assertion on ARM [836c2426](https://github.com/gohugoio/hugo/commit/836c24261f9f175254256fb326d92a3db47e1c75) [@bep](https://github.com/bep) [#6655](https://github.com/gohugoio/hugo/issues/6655)
-* Some more benchmark adjustments [ddd75f21](https://github.com/gohugoio/hugo/commit/ddd75f212110a3d6643a07301e377415f3d163bd) [@bep](https://github.com/bep)
-* Adjust site benchmarks [4ed6ebef](https://github.com/gohugoio/hugo/commit/4ed6ebef4ca71572a19bb890cb4c026a688b2b5b) [@bep](https://github.com/bep)
-* Add a benchmark with lots of templates [ea05c0e8](https://github.com/gohugoio/hugo/commit/ea05c0e8456e8dec71ffd796148355b0d8b36eb0) [@bep](https://github.com/bep)
-
-### Other
-
-* Regen docs helper [4f466db6](https://github.com/gohugoio/hugo/commit/4f466db666dded1b6c6d1e6926e170f22164433a) [@bep](https://github.com/bep)
-* Allow multiple arguments in ne/ge/gt/le/lt functions Treat op arg1 arg2 arg3 ... as (arg1 op arg2) && (arg1 op arg3) and so on for ne/ge/gt/le/lt. [0c251be6](https://github.com/gohugoio/hugo/commit/0c251be66bf3ad4abafbc47583e394ca4e6ffcf1) [@le0tan](https://github.com/le0tan) [#6619](https://github.com/gohugoio/hugo/issues/6619)
-* Update go-org [8585b388](https://github.com/gohugoio/hugo/commit/8585b388d27abde1ab6b6c63ad6addf4066ec8dd) [@niklasfasching](https://github.com/niklasfasching)
-* Add support for newline characters in raw string shortcode [21ca2e9c](https://github.com/gohugoio/hugo/commit/21ca2e9ce4255bfad2bb0576aff087a240acf70a) [@hcwong](https://github.com/hcwong)
-* Update github.com/alecthomas/chroma [3efa1d81](https://github.com/gohugoio/hugo/commit/3efa1d81219a6e7b41c9676e9cab446741f69055) [@ghislainbourgeois](https://github.com/ghislainbourgeois)
-* Update minify to v2.7.2 [65ec8fe8](https://github.com/gohugoio/hugo/commit/65ec8fe827efef5a14c4e1bc440a6df97d2f20a2) [@bep](https://github.com/bep) [#6756](https://github.com/gohugoio/hugo/issues/6756)
-* Update Goldmark to v1.1.21 [d3e8ab2e](https://github.com/gohugoio/hugo/commit/d3e8ab2e39dcc27853b163079f4a82364286fe82) [@flother](https://github.com/flother) [#6571](https://github.com/gohugoio/hugo/issues/6571)
-* Allow raw string literals in shortcode params [da814556](https://github.com/gohugoio/hugo/commit/da814556567eab9ba0ac5fef5314c3ad5ee50ccd) [@hcwong](https://github.com/hcwong)
-* Update github.com/gohugoio/testmodBuilder [0c0bb372](https://github.com/gohugoio/hugo/commit/0c0bb372858b5de58c15ccd300144e0bc205ffad) [@bep](https://github.com/bep)
-* Update direct dependencies [94cfdf6b](https://github.com/gohugoio/hugo/commit/94cfdf6befd657e46c9458b23f17d851cd2f7037) [@bep](https://github.com/bep) [#6719](https://github.com/gohugoio/hugo/issues/6719)
-* Update to new CSS config [45138017](https://github.com/gohugoio/hugo/commit/451380177868e48127a33362aa8d553b90516fb5) [@bep](https://github.com/bep) [#6719](https://github.com/gohugoio/hugo/issues/6719)
-* Update to Minify v2.7.0 [56354a63](https://github.com/gohugoio/hugo/commit/56354a63bb73271224a9300a4742dc1a2f551202) [@bep](https://github.com/bep)
-* Add support for freebsd/arm64 [aead8108](https://github.com/gohugoio/hugo/commit/aead8108b80d77e23c68a47fd8d86464310130be) [@dmgk](https://github.com/dmgk) [#6719](https://github.com/gohugoio/hugo/issues/6719)
-* Update releasenotes_writer.go [df6e9efd](https://github.com/gohugoio/hugo/commit/df6e9efd8f345707932231ea23dc8713afb5b026) [@bep](https://github.com/bep)
-* Adjust auto ID space handling [9b6e6146](https://github.com/gohugoio/hugo/commit/9b6e61464b09ffe3423fb8d7c72bddb7a9ed5b98) [@bep](https://github.com/bep) [#6710](https://github.com/gohugoio/hugo/issues/6710)
-* Document the new autoHeadingIDType setting [d62ede8e](https://github.com/gohugoio/hugo/commit/d62ede8e9e5883e7ebb023e49b82f07b45edc1c7) [@bep](https://github.com/bep) [#6707](https://github.com/gohugoio/hugo/issues/6707)[#6616](https://github.com/gohugoio/hugo/issues/6616)
-* Regenerate docshelper [81b7e48a](https://github.com/gohugoio/hugo/commit/81b7e48a55092203aeee8785799e6fed3928760e) [@bep](https://github.com/bep) [#6707](https://github.com/gohugoio/hugo/issues/6707)[#6616](https://github.com/gohugoio/hugo/issues/6616)
-* Add an optional Blackfriday auto ID strategy [16e7c112](https://github.com/gohugoio/hugo/commit/16e7c1120346bd853cf6510ffac8e94824bf2c7f) [@bep](https://github.com/bep) [#6707](https://github.com/gohugoio/hugo/issues/6707)
-* Make the autoID type config a string [8f071fc1](https://github.com/gohugoio/hugo/commit/8f071fc159ce9a0fc0ea14a73bde8f299bedd109) [@bep](https://github.com/bep) [#6707](https://github.com/gohugoio/hugo/issues/6707)
-* markup/goldmark: Simplify code [5ee1f087](https://github.com/gohugoio/hugo/commit/5ee1f0876f3ec8b79d6305298185dc821ead2d28) [@bep](https://github.com/bep)
-* Make auto IDs GitHub compatible [a82d2700](https://github.com/gohugoio/hugo/commit/a82d2700fcc772aada15d65b8f76913ca23f7404) [@bep](https://github.com/bep) [#6616](https://github.com/gohugoio/hugo/issues/6616)
-* Support files in content mounts [ff6253bc](https://github.com/gohugoio/hugo/commit/ff6253bc7cf745e9c0127ddc9006da3c2c00c738) [@bep](https://github.com/bep) [#6684](https://github.com/gohugoio/hugo/issues/6684)[#6696](https://github.com/gohugoio/hugo/issues/6696)
-* Update alpine base image in Dockerfile to 3.11 [aa4ccb8a](https://github.com/gohugoio/hugo/commit/aa4ccb8a1e9b8aa17397acf34049a2aa16b0b6cb) [@RemcodM](https://github.com/RemcodM)
-
-## Fixes
-
-### Templates
-
-* Fix eq when > 2 args [2fefc016](https://github.com/gohugoio/hugo/commit/2fefc01606fddb119f368c89fb2dedd452ad6547) [@bep](https://github.com/bep) [#6786](https://github.com/gohugoio/hugo/issues/6786)
-
-### Core
-
-* Fix relative .Page.GetPage from bundle [196a9df5](https://github.com/gohugoio/hugo/commit/196a9df585c4744e3280f37c1c24e469fce14b8c) [@bep](https://github.com/bep) [#6705](https://github.com/gohugoio/hugo/issues/6705)
-* Fix inline shortcode regression [5509954c](https://github.com/gohugoio/hugo/commit/5509954c7e8b0ce8d5ea903b0ab639ea14b69acb) [@bep](https://github.com/bep) [#6677](https://github.com/gohugoio/hugo/issues/6677)
-
-### Other
-
-* Fix 0.62.1 server rebuild slowdown regression [17af79a0](https://github.com/gohugoio/hugo/commit/17af79a03e249a731cf5634ffea23ca00774333d) [@bep](https://github.com/bep) [#6784](https://github.com/gohugoio/hugo/issues/6784)
-* Fix blog not building [d61bee5e](https://github.com/gohugoio/hugo/commit/d61bee5e0916b5d2b388e66ef85c336312a21a06) [@colonelpopcorn](https://github.com/colonelpopcorn) [#6752](https://github.com/gohugoio/hugo/issues/6752)
-
-
-
-
-
diff --git a/docs/content/en/news/0.63.1-relnotes/index.md b/docs/content/en/news/0.63.1-relnotes/index.md
deleted file mode 100644
index e6ae8b90615..00000000000
--- a/docs/content/en/news/0.63.1-relnotes/index.md
+++ /dev/null
@@ -1,17 +0,0 @@
-
----
-date: 2020-01-23
-title: "Hugo 0.63.1: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.63.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-This is a bug-fix release with a couple of important fixes.
-
-* deps: Make the build flags shared between sites [0df7bd62](https://github.com/gohugoio/hugo/commit/0df7bd62df460a49544845d5332f33b2020b48a1) [@bep](https://github.com/bep) [#6789](https://github.com/gohugoio/hugo/issues/6789)
-* Fix baseof with regular define regression [f441f675](https://github.com/gohugoio/hugo/commit/f441f675126ef1123d9f94429872dd683b40e011) [@bep](https://github.com/bep) [#6790](https://github.com/gohugoio/hugo/issues/6790)
-* Revert to minify [v2.61](https://github.com/gohugoio/hugo/commit/7ed22e9fb6a5b74c52ae6054b843b8c64e83f4b6)
-
diff --git a/docs/content/en/news/0.63.2-relnotes/index.md b/docs/content/en/news/0.63.2-relnotes/index.md
deleted file mode 100644
index 8477ef02c0c..00000000000
--- a/docs/content/en/news/0.63.2-relnotes/index.md
+++ /dev/null
@@ -1,20 +0,0 @@
-
----
-date: 2020-01-27
-title: "Hugo 0.63.2: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.63.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-* And now finally fix the 404 templates [74b6c4e5](https://github.com/gohugoio/hugo/commit/74b6c4e5ff5ee16f0e6b352a26c1e58b90a25dc6) [@bep](https://github.com/bep) [#6795](https://github.com/gohugoio/hugo/issues/6795)
-* Fix 404 with base template regression [8df5d76e](https://github.com/gohugoio/hugo/commit/8df5d76e708238563185bac84809b34a4d395734) [@bep](https://github.com/bep) [#6795](https://github.com/gohugoio/hugo/issues/6795)
-* Revert to .Type = "page" when empty [#6805](https://github.com/gohugoio/hugo/issues/6805)
-
-
diff --git a/docs/content/en/news/0.64.0-relnotes/hugo-64-poster-featured.png b/docs/content/en/news/0.64.0-relnotes/hugo-64-poster-featured.png
deleted file mode 100644
index 71861bad8fc..00000000000
Binary files a/docs/content/en/news/0.64.0-relnotes/hugo-64-poster-featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.64.0-relnotes/index.md b/docs/content/en/news/0.64.0-relnotes/index.md
deleted file mode 100644
index e03dc8f548b..00000000000
--- a/docs/content/en/news/0.64.0-relnotes/index.md
+++ /dev/null
@@ -1,54 +0,0 @@
-
----
-date: 2020-02-04
-title: "Mostly bugfixes …"
-description: "Hugo 0.64.0 is the version you really really want …"
-categories: ["Releases"]
----
-
-Hugo **0.64.0** is mostly a bugfix-release, but well worth the download. The main reason this release comes so soon after the previous is my (me being [@bep](https://github.com/bep)) ongoing work on getting solid support for third-party libraries in [Hugo Modules](https://gohugo.io/hugo-modules/). In particular, this release makes the Hugo server's live-reload work with [Turbolinks](https://github.com/bep/hugo-alpine-test/blob/27927832630be588eab0be2197cc8c0cb5725540/config.toml#L11) and similar. Also worth mentioning is that `hugo mod get -u` (without any path) now correctly updates every module imported in `config.toml` even with Go 1.13.
-
-This release represents **16 contributions by 2 contributors** to the main Hugo code base.
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **6 contributions by 4 contributors**. A special thanks to [@bep](https://github.com/bep), [@peterkappus](https://github.com/peterkappus), [@kc0bfv](https://github.com/kc0bfv), and [@inwardmovement](https://github.com/inwardmovement) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 41348+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 439+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 289+ [themes](http://themes.gohugo.io/)
-
-## Enhancements
-
-### Output
-
-* Do not render alias paginator pages for non-HTML outputs [2d159e9c](https://github.com/gohugoio/hugo/commit/2d159e9cc7a25832e4b0cad226b149f7c4624708) [@bep](https://github.com/bep) [#6797](https://github.com/gohugoio/hugo/issues/6797)
-
-### Other
-
-* Mention a "no CGO rule" [29973101](https://github.com/gohugoio/hugo/commit/299731012441378bb9c057ceb0a3c277108aaf01) [@bep](https://github.com/bep) [#6842](https://github.com/gohugoio/hugo/issues/6842)
-* Update to Go 1.13.7 and Go 1.12.16 [0792cfa9](https://github.com/gohugoio/hugo/commit/0792cfa9fae94a06a31e393a46fed3b1dd73b66a) [@bep](https://github.com/bep) [#6830](https://github.com/gohugoio/hugo/issues/6830)
-* Add defer to livereload script tag [b3f0674b](https://github.com/gohugoio/hugo/commit/b3f0674b80a32425aeb4412f318c720391bbf773) [@bep](https://github.com/bep)
-* Don't use document.write to inject livereload [ef78a0d1](https://github.com/gohugoio/hugo/commit/ef78a0d18a13098bcea1ff2b2d45d7388b8d41a0) [@bep](https://github.com/bep) [#6507](https://github.com/gohugoio/hugo/issues/6507)
-* Add a render hook whitespace test [58595864](https://github.com/gohugoio/hugo/commit/585958645372e6219239247dbac02e447d2b355b) [@bep](https://github.com/bep) [#6832](https://github.com/gohugoio/hugo/issues/6832)
-* Inject livereload script right after head if possible [8f08cdd0](https://github.com/gohugoio/hugo/commit/8f08cdd0ac6a2decd5aa5c9c12c0b2c264f9a989) [@bep](https://github.com/bep) [#6821](https://github.com/gohugoio/hugo/issues/6821)
-* Update goldmark to v1.1.22 [281abb18](https://github.com/gohugoio/hugo/commit/281abb18ee39fa2b5d4782b64f27cffcbf4e0240) [@bhavin192](https://github.com/bhavin192)
-* Make the build flags shared between sites [0df7bd62](https://github.com/gohugoio/hugo/commit/0df7bd62df460a49544845d5332f33b2020b48a1) [@bep](https://github.com/bep) [#6789](https://github.com/gohugoio/hugo/issues/6789)
-
-## Fixes
-
-### Other
-
-* Fix module mount in sub folder [80dd6ddd](https://github.com/gohugoio/hugo/commit/80dd6ddde27ce36f5432fb780e94d4974b5277c7) [@bep](https://github.com/bep) [#6730](https://github.com/gohugoio/hugo/issues/6730)
-* Fix config environment handling [2bbc865f](https://github.com/gohugoio/hugo/commit/2bbc865f7bb713b2d0d2dbb02b90ae2621ad5367) [@bep](https://github.com/bep) [#6503](https://github.com/gohugoio/hugo/issues/6503)[#6824](https://github.com/gohugoio/hugo/issues/6824)
-* Fix base template handling with preceding comments [f45cb317](https://github.com/gohugoio/hugo/commit/f45cb3172862140883cfa08bd401c17e1ada5b39) [@bep](https://github.com/bep) [#6816](https://github.com/gohugoio/hugo/issues/6816)
-* Fix "hugo mod get -u" with no arguments [49ef6472](https://github.com/gohugoio/hugo/commit/49ef6472039ede7d485242eba511207a8274495a) [@bep](https://github.com/bep) [#6826](https://github.com/gohugoio/hugo/issues/6826)[#6825](https://github.com/gohugoio/hugo/issues/6825)
-* And now finally fix the 404 templates [74b6c4e5](https://github.com/gohugoio/hugo/commit/74b6c4e5ff5ee16f0e6b352a26c1e58b90a25dc6) [@bep](https://github.com/bep) [#6795](https://github.com/gohugoio/hugo/issues/6795)
-* Fix 404 with base template regression [8df5d76e](https://github.com/gohugoio/hugo/commit/8df5d76e708238563185bac84809b34a4d395734) [@bep](https://github.com/bep) [#6795](https://github.com/gohugoio/hugo/issues/6795)
-* Fix baseof with regular define regression [f441f675](https://github.com/gohugoio/hugo/commit/f441f675126ef1123d9f94429872dd683b40e011) [@bep](https://github.com/bep) [#6790](https://github.com/gohugoio/hugo/issues/6790)
-
-
-
-
-
diff --git a/docs/content/en/news/0.64.1-relnotes/index.md b/docs/content/en/news/0.64.1-relnotes/index.md
deleted file mode 100644
index 4dbcab6701e..00000000000
--- a/docs/content/en/news/0.64.1-relnotes/index.md
+++ /dev/null
@@ -1,22 +0,0 @@
-
----
-date: 2020-02-09
-title: "Hugo 0.64.1: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.64.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-* hugofs: Fix mount with hole regression [b78576fd](https://github.com/gohugoio/hugo/commit/b78576fd38a76bbdaab5ad21228c8e5a559090b1) [@bep](https://github.com/bep) [#6854](https://github.com/gohugoio/hugo/issues/6854)
-* Fix bundle resource ordering regression [18888e09](https://github.com/gohugoio/hugo/commit/18888e09bbb5325bdd63f2cd93116ff490dd37ab) [@bep](https://github.com/bep) [#6851](https://github.com/gohugoio/hugo/issues/6851)
-* CONTRIBUTING: Fix note about CGO [7f0ebd4a](https://github.com/gohugoio/hugo/commit/7f0ebd4a3c9e016afddc2cf5e7dfe6a820aa099a) [@moorereason](https://github.com/moorereason)
-* Update Go version requirement [23ea4318](https://github.com/gohugoio/hugo/commit/23ea43180b84e35d99e88083a83e7ca1916b3b36) [@bep](https://github.com/bep) [#6853](https://github.com/gohugoio/hugo/issues/6853)
-
-
-
diff --git a/docs/content/en/news/0.65.0-relnotes/hugo-65-poster-featured.png b/docs/content/en/news/0.65.0-relnotes/hugo-65-poster-featured.png
deleted file mode 100644
index a311df0cb1b..00000000000
Binary files a/docs/content/en/news/0.65.0-relnotes/hugo-65-poster-featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.65.0-relnotes/index.md b/docs/content/en/news/0.65.0-relnotes/index.md
deleted file mode 100644
index 1a2edb90723..00000000000
--- a/docs/content/en/news/0.65.0-relnotes/index.md
+++ /dev/null
@@ -1,141 +0,0 @@
-
----
-date: 2020-02-20
-title: "0.65.0: Hugo Reloaded!"
-description: "Draft, expire, resource bundling, and fine grained publishing control for any page. And it's faster."
-categories: ["Releases"]
----
-
-**Hugo 0.65** generalizes how a page is packaged and published to be applicable to **any page**. This should solve some of the most common issues we see people ask and talk about on the [issue tracker](https://github.com/gohugoio/hugo/issues) and on the [forum](https://discourse.gohugo.io/).
-
-## Release Highlights
-
-### New in Hugo Core
-
-Any [branch node](https://gohugo.io/content-management/page-bundles/#branch-bundles) can now bundle resources (images, data files etc.), even the taxonomy nodes (e.g. /categories).
-
-List pages (sections and the home page) can now be added to taxonomies.
-
-The front matter fields that control when and if to publish a piece of content (`draft`, `publishDate`, `expiryDate`) now also works for list pages, and is recursive.
-
-We have added a new `_build` front matter keyword to provide fine-grained control over page publishing. The default values:
-
-```yaml
-_build:
- # Whether to add it to any of the page collections.
- # Note that the page can still be found with .Site.GetPage.
- list: true
-
- # Whether to render it.
- render: true
-
- # Whether to publish its resources. These will still be published on demand,
- # but enabling this can be useful if the originals (e.g. images) are
- # never used.
- publishResources: true
-```
-
-Note that all front matter keywords can be set in the [cascade](https://gohugo.io/content-management/front-matter#front-matter-cascade) on a branch node, which would be especially useful for `_build`.
-
-We have also upgraded to the latest LibSass (v3.6.3). Nothing remarkable functional new here, but it makes Hugo ready for the upcoming [Dart Backport](https://github.com/sass/libsass/pull/2918).
-
-And finally, we have added a `GetTerms` method on `Page`, making listing the terms defined on this page in the given taxonomy much simpler:
-
-```go-html-template
-
-```
-
-### New in Hugo Modules
-
-There are several improvements to the tooling used in [Hugo Modules](https://gohugo.io/hugo-modules/). One bug fix, but also some improvements to make it easier to manage:
-
-* You can now recursively update your modules with `hugo mod get -u ./...`
-* `hugo mod clean` will now only clean the cache for the current project and now also takes an optional module path pattern, e.g. `hugo mod clean --pattern "github.com/**"`
-* A new command `hugo mod verify` is added to verify that the module cache matches the hashes in `go.sum`. Run with `hugo mod verify --clean` to delete any modules that fail this check.
-
-See [hugo mod](https://gohugo.io/commands/hugo_mod/#see-also).
-
-### Performance
-
-The new features listed above required a structural simplification, and we do watch our weight when doing this. And the benchmarks show that Hugo should, in general, be slightly faster. This is especially true if you're using taxonomies, and the partial rebuilding on content changes should be considerably faster.
-
-## Numbers
-
-This release represents **34 contributions by 6 contributors** to the main Hugo code base.[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@satotake](https://github.com/satotake), [@QuLogic](https://github.com/QuLogic), and [@JaymoKang](https://github.com/JaymoKang) for their ongoing contributions.
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) and [@onedrawingperday](https://github.com/onedrawingperday) for their relentless work on keeping the themes site in pristine condition and to [@davidsneighbour](https://github.com/davidsneighbour) and [@kaushalmodi](https://github.com/kaushalmodi) for all the great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **7 contributions by 4 contributors**. A special thanks to [@coliff](https://github.com/coliff), [@bep](https://github.com/bep), [@tibnew](https://github.com/tibnew), and [@nerg4l](https://github.com/nerg4l) for their work on the documentation site.
-
-Hugo now has:
-
-* 41724+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 439+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 299+ [themes](http://themes.gohugo.io/)
-
-## Notes
-
-* `.GetPage "members.md"` (the Page method) will now only do relative lookups, which is what most people would expect.
-* There have been a slight change of how disableKinds for regular pages: They will not be rendered on its own, but will be added to the site collections.
-
-## Enhancements
-
-### Templates
-
-* Adjust the RSS taxonomy logic [d73e3738](https://github.com/gohugoio/hugo/commit/d73e37387ca0012bd58bd3f36a0477854b41ab6e) [@bep](https://github.com/bep) [#6909](https://github.com/gohugoio/hugo/issues/6909)
-
-### Output
-
-* Handle disabled RSS even if it's defined in outputs [da54787c](https://github.com/gohugoio/hugo/commit/da54787cfa97789624e467a4451dfeb50f563e41) [@bep](https://github.com/bep)
-
-### Other
-
-* Regenerate CLI docs [a5ebdf7d](https://github.com/gohugoio/hugo/commit/a5ebdf7d17e6c6a9dc686cf8f7cd8e0a1bab5f2d) [@bep](https://github.com/bep)
-* Improve "hugo mod clean" [dce210ab](https://github.com/gohugoio/hugo/commit/dce210ab56fc885818fc5d1a084a1c3ba84e7929) [@bep](https://github.com/bep) [#6907](https://github.com/gohugoio/hugo/issues/6907)
-* Add "hugo mod verify" [0b96aba0](https://github.com/gohugoio/hugo/commit/0b96aba022d51cf9939605c029bb8dba806653a1) [@bep](https://github.com/bep) [#6907](https://github.com/gohugoio/hugo/issues/6907)
-* Add Page.GetTerms [fa520a2d](https://github.com/gohugoio/hugo/commit/fa520a2d983b982394ad10088393fb303e48980a) [@bep](https://github.com/bep) [#6905](https://github.com/gohugoio/hugo/issues/6905)
-* Add a list terms benchmark [7489a864](https://github.com/gohugoio/hugo/commit/7489a864591b6df03f435f40696c6ceeb4776ec9) [@bep](https://github.com/bep) [#6905](https://github.com/gohugoio/hugo/issues/6905)
-* Use the tree for taxonomy.Pages() [b2dcd53e](https://github.com/gohugoio/hugo/commit/b2dcd53e3c0240c4afd21d1818fd180c2d1b9d34) [@bep](https://github.com/bep)
-* Add some cagegories to the site collections benchmarks [36983e61](https://github.com/gohugoio/hugo/commit/36983e6189a717f1d4d1da6652621d7f8fe186ad) [@bep](https://github.com/bep)
-* Do not try to get local themes in "hugo mod get" [20f2211f](https://github.com/gohugoio/hugo/commit/20f2211fce55e1811629245f9e5e4a2ac754d788) [@bep](https://github.com/bep) [#6893](https://github.com/gohugoio/hugo/issues/6893)
-* Update goldmark-highlighting [a21a9373](https://github.com/gohugoio/hugo/commit/a21a9373e06091ab70d8a5f4da8ff43f7c609b4b) [@satotake](https://github.com/satotake)
-* Support "hugo mod get -u ./..." [775c7c24](https://github.com/gohugoio/hugo/commit/775c7c2474d8797c96c9ac529a3cd93c0c2d3514) [@bep](https://github.com/bep) [#6828](https://github.com/gohugoio/hugo/issues/6828)
-* Introduce a tree map for all content [eada236f](https://github.com/gohugoio/hugo/commit/eada236f87d9669885da1ff647672bb3dc6b4954) [@bep](https://github.com/bep) [#6312](https://github.com/gohugoio/hugo/issues/6312)[#6087](https://github.com/gohugoio/hugo/issues/6087)[#6738](https://github.com/gohugoio/hugo/issues/6738)[#6412](https://github.com/gohugoio/hugo/issues/6412)[#6743](https://github.com/gohugoio/hugo/issues/6743)[#6875](https://github.com/gohugoio/hugo/issues/6875)[#6034](https://github.com/gohugoio/hugo/issues/6034)[#6902](https://github.com/gohugoio/hugo/issues/6902)[#6173](https://github.com/gohugoio/hugo/issues/6173)[#6590](https://github.com/gohugoio/hugo/issues/6590)
-* Another benchmark rename [e5329f13](https://github.com/gohugoio/hugo/commit/e5329f13c02b87f0c30f8837759c810cd90ff8da) [@bep](https://github.com/bep)
-* Rename the Edit benchmarks [5b145ddc](https://github.com/gohugoio/hugo/commit/5b145ddc4c951a827e1ac00444dc4719e53e0885) [@bep](https://github.com/bep)
-* Refactor a benchmark to make it runnable as test [54bdcaac](https://github.com/gohugoio/hugo/commit/54bdcaacaedec178554e696f34647801bbe61362) [@bep](https://github.com/bep)
-* Add benchmark for content edits [1622510a](https://github.com/gohugoio/hugo/commit/1622510a5c651b59a79f64e9dc3cacd24832ec0b) [@bep](https://github.com/bep)
-* Add "go mod verify" to build scripts [56d0b658](https://github.com/gohugoio/hugo/commit/56d0b658879bbf476810d013176d6568553aa71e) [@bep](https://github.com/bep)
-* Add git to Dockerfile [75c3787f](https://github.com/gohugoio/hugo/commit/75c3787fc254d933fa11e5c39d978bfa1a21a371) [@JaymoKang](https://github.com/JaymoKang)
-* Update go.sum [9babb1f0](https://github.com/gohugoio/hugo/commit/9babb1f0c4fca048b0339f6ce3618f88d34e0457) [@bep](https://github.com/bep)
-* Rename doWithCommandeer to cfgInit/cfgSetAndInit [8a5124d6](https://github.com/gohugoio/hugo/commit/8a5124d6b38156cb6f765ac7492513ac7c0d90b2) [@MarkRosemaker](https://github.com/MarkRosemaker)
-* Update golibsass [898a0a96](https://github.com/gohugoio/hugo/commit/898a0a96afd472fad8fe70be71f6cb00a4267c4a) [@bep](https://github.com/bep) [#6885](https://github.com/gohugoio/hugo/issues/6885)
-* Shuffle test files before insertion [3b721110](https://github.com/gohugoio/hugo/commit/3b721110d560c8831c282e6e7a5c510fe7a5129a) [@bep](https://github.com/bep)
-* Update to LibSass v3.6.3 [40ba7e6d](https://github.com/gohugoio/hugo/commit/40ba7e6d63c1a0734f257a642e46eb1572116a32) [@bep](https://github.com/bep) [#6862](https://github.com/gohugoio/hugo/issues/6862)
-* Update Go version requirement [23ea4318](https://github.com/gohugoio/hugo/commit/23ea43180b84e35d99e88083a83e7ca1916b3b36) [@bep](https://github.com/bep) [#6853](https://github.com/gohugoio/hugo/issues/6853)
-
-## Fixes
-
-### Templates
-
-* Fix RSS template for the terms listing [aa3e1830](https://github.com/gohugoio/hugo/commit/aa3e1830568cabaa8bf3277feeba6cb48746e40c) [@bep](https://github.com/bep) [#6909](https://github.com/gohugoio/hugo/issues/6909)
-
-### Other
-
-* Fix lazy publishing with publishResources=false [9bdedb25](https://github.com/gohugoio/hugo/commit/9bdedb251c7cd8f8af800c7d9914cf84292c5c50) [@bep](https://github.com/bep) [#6914](https://github.com/gohugoio/hugo/issues/6914)
-* Fix goMinorVersion on non-final Go releases [c7975b48](https://github.com/gohugoio/hugo/commit/c7975b48b6532823868a6aa8c93eb76caa46c570) [@QuLogic](https://github.com/QuLogic)
-* Fix taxonomy [1b7acfe7](https://github.com/gohugoio/hugo/commit/1b7acfe7634a5d7bbc597ef4dddf4babce5666c5) [@bep](https://github.com/bep)
-* Fix RenderString for pages without content [19e12caf](https://github.com/gohugoio/hugo/commit/19e12caf8c90516e3b803ae8a40b907bd89dc96c) [@bep](https://github.com/bep) [#6882](https://github.com/gohugoio/hugo/issues/6882)
-* Fix chroma highlight [3c568ad0](https://github.com/gohugoio/hugo/commit/3c568ad0139c79e5c0596ca40637512d71401afc) [@satotake](https://github.com/satotake) [#6877](https://github.com/gohugoio/hugo/issues/6877)[#6856](https://github.com/gohugoio/hugo/issues/6856)
-* Fix mount with hole regression [b78576fd](https://github.com/gohugoio/hugo/commit/b78576fd38a76bbdaab5ad21228c8e5a559090b1) [@bep](https://github.com/bep) [#6854](https://github.com/gohugoio/hugo/issues/6854)
-* Fix bundle resource ordering regression [18888e09](https://github.com/gohugoio/hugo/commit/18888e09bbb5325bdd63f2cd93116ff490dd37ab) [@bep](https://github.com/bep) [#6851](https://github.com/gohugoio/hugo/issues/6851)
-* Fix note about CGO [7f0ebd4a](https://github.com/gohugoio/hugo/commit/7f0ebd4a3c9e016afddc2cf5e7dfe6a820aa099a) [@moorereason](https://github.com/moorereason)
-
-
-
-
-
diff --git a/docs/content/en/news/0.65.0-relnotes/pg-admin-tos.png b/docs/content/en/news/0.65.0-relnotes/pg-admin-tos.png
deleted file mode 100644
index fc2f4e34d3b..00000000000
Binary files a/docs/content/en/news/0.65.0-relnotes/pg-admin-tos.png and /dev/null differ
diff --git a/docs/content/en/news/0.65.1-relnotes/index.md b/docs/content/en/news/0.65.1-relnotes/index.md
deleted file mode 100644
index 07ee66569f8..00000000000
--- a/docs/content/en/news/0.65.1-relnotes/index.md
+++ /dev/null
@@ -1,20 +0,0 @@
-
----
-date: 2020-02-20
-title: "Hugo 0.65.1: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.65.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-* hugolib: Fix 2 Paginator.Pages taxonomy regressions [7ef5a4c8](https://github.com/gohugoio/hugo/commit/7ef5a4c83e4560bced3eee0ccf0e0db176146f44) [@bep](https://github.com/bep) [#6921](https://github.com/gohugoio/hugo/issues/6921)[#6918](https://github.com/gohugoio/hugo/issues/6918)
-* hugolib: Fix deletion of orphaned sections [a70bbd06](https://github.com/gohugoio/hugo/commit/a70bbd0696df3b0a6889650e48a07f8223151da4) [@bep](https://github.com/bep) [#6920](https://github.com/gohugoio/hugo/issues/6920)
-
-
-
diff --git a/docs/content/en/news/0.65.2-relnotes/index.md b/docs/content/en/news/0.65.2-relnotes/index.md
deleted file mode 100644
index ee9280976d3..00000000000
--- a/docs/content/en/news/0.65.2-relnotes/index.md
+++ /dev/null
@@ -1,21 +0,0 @@
-
----
-date: 2020-02-21
-title: "Hugo 0.65.2: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.65.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-* Apply missing go fmt [76b2afe6](https://github.com/gohugoio/hugo/commit/76b2afe642c37aedc7269b41d6fca5b78f467ce4) [@bep](https://github.com/bep)
-* Fix panic on no output formats [f4605303](https://github.com/gohugoio/hugo/commit/f46053034759c4f9790a79e0a146dbc1b426b1ff) [@bep](https://github.com/bep) [#6924](https://github.com/gohugoio/hugo/issues/6924)
-* Fix panic in 404.Parent [4c2a0de4](https://github.com/gohugoio/hugo/commit/4c2a0de412a850745ad32e580fcd65575192ca53) [@bep](https://github.com/bep) [#6924](https://github.com/gohugoio/hugo/issues/6924)
-
-
-
diff --git a/docs/content/en/news/0.65.3-relnotes/index.md b/docs/content/en/news/0.65.3-relnotes/index.md
deleted file mode 100644
index 1d47362bba9..00000000000
--- a/docs/content/en/news/0.65.3-relnotes/index.md
+++ /dev/null
@@ -1,21 +0,0 @@
-
----
-date: 2020-02-23
-title: "Hugo 0.65.3: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.65.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-* Fix panic when home page is drafted [0bd6356c](https://github.com/gohugoio/hugo/commit/0bd6356c6d2a2bac06d0c3705bf13a90cb7a2688) [@bep](https://github.com/bep) [#6927](https://github.com/gohugoio/hugo/issues/6927)
-* Fix goldmark toc rendering [ca68abf0](https://github.com/gohugoio/hugo/commit/ca68abf0bc2fa003c2052143218f7b2ab195a46e) [@satotake](https://github.com/satotake) [#6736](https://github.com/gohugoio/hugo/issues/6736)[#6809](https://github.com/gohugoio/hugo/issues/6809)
-* Fix crashes for 404 in IsAncestor etc. [a524124b](https://github.com/gohugoio/hugo/commit/a524124beb0e7ca226c207ea48a90cea2cbef76e) [@bep](https://github.com/bep) [#6931](https://github.com/gohugoio/hugo/issues/6931)
-
-
-
diff --git a/docs/content/en/news/0.66.0-relnotes/hugo-66-poster-featured.png b/docs/content/en/news/0.66.0-relnotes/hugo-66-poster-featured.png
deleted file mode 100644
index fcdc707ce35..00000000000
Binary files a/docs/content/en/news/0.66.0-relnotes/hugo-66-poster-featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.66.0-relnotes/index.md b/docs/content/en/news/0.66.0-relnotes/index.md
deleted file mode 100644
index 850a8fa717a..00000000000
--- a/docs/content/en/news/0.66.0-relnotes/index.md
+++ /dev/null
@@ -1,81 +0,0 @@
-
----
-date: 2020-03-03
-title: "Hugo 0.66.0: PostCSS Edition"
-description: "Native inline, recursive import support in PostCSS/Tailwind, \"dependency-less\" builds, and more …"
-categories: ["Releases"]
----
-
-This release adds [inline `@import`](/hugo-pipes/postcss/#options) support to `resources.PostCSS`, with imports relative to Hugo's virtual, composable file system. Another useful addition is the new `build` [configuration section](/getting-started/configuration/#configure-build). As an example:
-
-{{< code-toggle file="config" >}}
-[build]
- useResourceCacheWhen = "always"
-{{< /code-toggle >}}
-
-The above will tell Hugo to _always_ use the cached build resources inside `resources/_gen` for the build steps requiring a non-standard dependency (PostCSS and SCSS/SASS). Valid values are `never`, `always` and `fallback` (default).
-
-
-This release represents **27 contributions by 8 contributors** to the main Hugo code base.[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@anthonyfok](https://github.com/anthonyfok), [@carlmjohnson](https://github.com/carlmjohnson), and [@sams96](https://github.com/sams96) for their ongoing contributions.
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) and [@onedrawingperday](https://github.com/onedrawingperday) for their relentless work on keeping the themes site in pristine condition and to [@davidsneighbour](https://github.com/davidsneighbour) and [@kaushalmodi](https://github.com/kaushalmodi) for all the great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **8 contributions by 5 contributors**. A special thanks to [@bep](https://github.com/bep), [@nantipov](https://github.com/nantipov), [@regisphilibert](https://github.com/regisphilibert), and [@inwardmovement](https://github.com/inwardmovement) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 41984+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 439+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 299+ [themes](http://themes.gohugo.io/)
-
-## Enhancements
-
-### Templates
-
-* Change error message on missing resource [d7798906](https://github.com/gohugoio/hugo/commit/d7798906d8e152a5d33f76ed0362628da8dd2c35) [@sams96](https://github.com/sams96) [#6942](https://github.com/gohugoio/hugo/issues/6942)
-* Add math.Sqrt [d184e505](https://github.com/gohugoio/hugo/commit/d184e5059c72c15df055192b01da0fd8c5b0fc5c) [@StarsoftAnalysis](https://github.com/StarsoftAnalysis) [#6941](https://github.com/gohugoio/hugo/issues/6941)
-
-### Other
-
-* Skip some tests on CircleCI [6a34f88d](https://github.com/gohugoio/hugo/commit/6a34f88dcc1ac229247decc008471d7449d6d316) [@bep](https://github.com/bep)
-* {{ in }} should work with html.Template type [ae383f04](https://github.com/gohugoio/hugo/commit/ae383f04c806687cdae184d6138bcf51edbffcb2) [@carlmjohnson](https://github.com/carlmjohnson) [#7002](https://github.com/gohugoio/hugo/issues/7002)
-* Regen CLI docs [ee31e61f](https://github.com/gohugoio/hugo/commit/ee31e61fb06bb6e26c9d66d78d8763aabd19e11d) [@bep](https://github.com/bep)
-* Add --all flag to hugo mod clean [760a87a4](https://github.com/gohugoio/hugo/commit/760a87a45a0a3e6a581851e5cf4fe440e9a8c655) [@bep](https://github.com/bep)
-* Add build.UseResourceCacheWhen [3d3fa5c3](https://github.com/gohugoio/hugo/commit/3d3fa5c3fe5ee0c9df59d682ee0acaba71a06ae1) [@bep](https://github.com/bep) [#6993](https://github.com/gohugoio/hugo/issues/6993)
-* Update dependency list in README.md [ee3d0213](https://github.com/gohugoio/hugo/commit/ee3d02134d9b46b10e5a0403c9986ee1833ae6c1) [@anthonyfok](https://github.com/anthonyfok)
-* Add full filename to image when processing fails [305ce1c9](https://github.com/gohugoio/hugo/commit/305ce1c9ec746d3b8f6c9306b7014bfd621478a5) [@bep](https://github.com/bep) [#7000](https://github.com/gohugoio/hugo/issues/7000)
-* Update dependency list in README [449deb7f](https://github.com/gohugoio/hugo/commit/449deb7f9ce089236f8328dd4fa585bea6e9bfde) [@anthonyfok](https://github.com/anthonyfok)
-* Add basic @import support to resources.PostCSS [b66d38c4](https://github.com/gohugoio/hugo/commit/b66d38c41939252649365822d9edb10cf5990617) [@bep](https://github.com/bep) [#6957](https://github.com/gohugoio/hugo/issues/6957)[#6961](https://github.com/gohugoio/hugo/issues/6961)
-* Implement include/exclude filters for deploy [05a74eae](https://github.com/gohugoio/hugo/commit/05a74eaec0d944a4b29445c878a431cd6ae12277) [@vangent](https://github.com/vangent) [#6922](https://github.com/gohugoio/hugo/issues/6922)
-* Update to Go 1.14 and 1.13.8 [33ae6210](https://github.com/gohugoio/hugo/commit/33ae62108325f703f1eaeabef1e8a80950229415) [@bep](https://github.com/bep) [#6958](https://github.com/gohugoio/hugo/issues/6958)
-* Add hugo.IsProduction function [1352bc88](https://github.com/gohugoio/hugo/commit/1352bc880df4cd25eff65843973fcc0dd21b6304) [@hcwong](https://github.com/hcwong) [#6873](https://github.com/gohugoio/hugo/issues/6873)
-* Apply missing go fmt [76b2afe6](https://github.com/gohugoio/hugo/commit/76b2afe642c37aedc7269b41d6fca5b78f467ce4) [@bep](https://github.com/bep)
-
-## Fixes
-
-### Output
-
-* Fix panic on no output formats [f4605303](https://github.com/gohugoio/hugo/commit/f46053034759c4f9790a79e0a146dbc1b426b1ff) [@bep](https://github.com/bep) [#6924](https://github.com/gohugoio/hugo/issues/6924)
-
-### Core
-
-* Fix error handling in page collector [3e9db2ad](https://github.com/gohugoio/hugo/commit/3e9db2ad951dbb1000cd0f8f25e4a95445046679) [@bep](https://github.com/bep) [#6988](https://github.com/gohugoio/hugo/issues/6988)
-* Fix 2 Paginator.Pages taxonomy regressions [7ef5a4c8](https://github.com/gohugoio/hugo/commit/7ef5a4c83e4560bced3eee0ccf0e0db176146f44) [@bep](https://github.com/bep) [#6921](https://github.com/gohugoio/hugo/issues/6921)[#6918](https://github.com/gohugoio/hugo/issues/6918)
-* Fix deletion of orphaned sections [a70bbd06](https://github.com/gohugoio/hugo/commit/a70bbd0696df3b0a6889650e48a07f8223151da4) [@bep](https://github.com/bep) [#6920](https://github.com/gohugoio/hugo/issues/6920)
-
-### Other
-
-* Fix ref/relref short lookup for pages in sub-folder [8947c3fa](https://github.com/gohugoio/hugo/commit/8947c3fa0beec021e14b3f8040857335e1ecd473) [@bep](https://github.com/bep) [#6952](https://github.com/gohugoio/hugo/issues/6952)
-* Fix ref/relRef regression for relative refs from bundles [1746e8a9](https://github.com/gohugoio/hugo/commit/1746e8a9b2be46dcd6cecbb4bc90983a9c69b333) [@bep](https://github.com/bep) [#6952](https://github.com/gohugoio/hugo/issues/6952)
-* Fix potential infinite recursion in server change detection [6f48146e](https://github.com/gohugoio/hugo/commit/6f48146e75e9877c4271ec239b763e6f3bc3babb) [@bep](https://github.com/bep) [#6986](https://github.com/gohugoio/hugo/issues/6986)
-* Fix rebuild logic when editing template using a base template [b0d85032](https://github.com/gohugoio/hugo/commit/b0d850321e58a052ead25f7014b7851f63497601) [@bep](https://github.com/bep) [#6968](https://github.com/gohugoio/hugo/issues/6968)
-* Fix panic when home page is drafted [0bd6356c](https://github.com/gohugoio/hugo/commit/0bd6356c6d2a2bac06d0c3705bf13a90cb7a2688) [@bep](https://github.com/bep) [#6927](https://github.com/gohugoio/hugo/issues/6927)
-* Fix goldmark toc rendering [ca68abf0](https://github.com/gohugoio/hugo/commit/ca68abf0bc2fa003c2052143218f7b2ab195a46e) [@satotake](https://github.com/satotake) [#6736](https://github.com/gohugoio/hugo/issues/6736)[#6809](https://github.com/gohugoio/hugo/issues/6809)
-* Fix crashes for 404 in IsAncestor etc. [a524124b](https://github.com/gohugoio/hugo/commit/a524124beb0e7ca226c207ea48a90cea2cbef76e) [@bep](https://github.com/bep) [#6931](https://github.com/gohugoio/hugo/issues/6931)
-* Fix panic in 404.Parent [4c2a0de4](https://github.com/gohugoio/hugo/commit/4c2a0de412a850745ad32e580fcd65575192ca53) [@bep](https://github.com/bep) [#6924](https://github.com/gohugoio/hugo/issues/6924)
-
-
-
-
-
diff --git a/docs/content/en/news/0.67.0-relnotes/hugo-67-poster-featured.png b/docs/content/en/news/0.67.0-relnotes/hugo-67-poster-featured.png
deleted file mode 100644
index 059d8c07a7b..00000000000
Binary files a/docs/content/en/news/0.67.0-relnotes/hugo-67-poster-featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.67.0-relnotes/index.md b/docs/content/en/news/0.67.0-relnotes/index.md
deleted file mode 100644
index 881fe036702..00000000000
--- a/docs/content/en/news/0.67.0-relnotes/index.md
+++ /dev/null
@@ -1,64 +0,0 @@
-
----
-date: 2020-03-09
-title: "Hugo 0.67.0: Custom HTTP headers"
-description: "This version brings Custom HTTP headers to the development server and exclude/include filters in Hugo Deploy."
-categories: ["Releases"]
----
-
-The two main items in Hugo 0.67.0 is custom HTTP header support in `hugo server` and include/exclude filters for [Hugo Deploy](https://gohugo.io/hosting-and-deployment/hugo-deploy/#readout).
-
-Being able to [configure HTTP headers](https://gohugo.io/getting-started/configuration/#configure-server) in your development server means that you can now verify how your site behaves with the intended Content Security Policy settings etc., e.g.:
-
-{{< code-toggle file="config" >}}
-[server]
-[[server.headers]]
-for = "/**.html"
-
-[server.headers.values]
-X-Frame-Options = "DENY"
-X-XSS-Protection = "1; mode=block"
-X-Content-Type-Options = "nosniff"
-Referrer-Policy = "strict-origin-when-cross-origin"
-Content-Security-Policy = "script-src localhost:1313"
-{{< /code-toggle >}}
-
-**Note:** This release also changes how raw HTML files inside /content is processed to be in line with the documentation. See [#7030](https://github.com/gohugoio/hugo/issues/7030).
-
-This release represents **7 contributions by 4 contributors** to the main Hugo code base.[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@satotake](https://github.com/satotake), [@sams96](https://github.com/sams96), and [@davidejones](https://github.com/davidejones) for their ongoing contributions.
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) and [@onedrawingperday](https://github.com/onedrawingperday) for their relentless work on keeping the themes site in pristine condition and to [@davidsneighbour](https://github.com/davidsneighbour) and [@kaushalmodi](https://github.com/kaushalmodi) for all the great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **5 contributions by 5 contributors**. A special thanks to [@bep](https://github.com/bep), [@psliwka](https://github.com/psliwka), [@digitalcraftsman](https://github.com/digitalcraftsman), and [@jasikpark](https://github.com/jasikpark) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 42176+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 439+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 301+ [themes](http://themes.gohugo.io/)
-
-## Enhancements
-
-### Other
-
-* Doument the server config [63393230](https://github.com/gohugoio/hugo/commit/63393230c9d3ba19ad182064787e3bfd7ecf82d8) [@bep](https://github.com/bep)
-* Support unComparable args of uniq/complement/in [8279d2e2](https://github.com/gohugoio/hugo/commit/8279d2e2271ee64725133d36a12d1d7e2158bffd) [@satotake](https://github.com/satotake) [#6105](https://github.com/gohugoio/hugo/issues/6105)
-* Add HTTP header support for the dev server [10831444](https://github.com/gohugoio/hugo/commit/108314444b510bfc330ccac745dce7beccd52c91) [@bep](https://github.com/bep) [#7031](https://github.com/gohugoio/hugo/issues/7031)
-* Add include and exclude support for remote [51e178a6](https://github.com/gohugoio/hugo/commit/51e178a6a28a3f305d89ebb489675743f80862ee) [@davidejones](https://github.com/davidejones)
-
-## Fixes
-
-### Templates
-
-* Fix error with unicode in file paths [c4fa2f07](https://github.com/gohugoio/hugo/commit/c4fa2f07996c7f1f4e257089a3c3c5b4c1339722) [@sams96](https://github.com/sams96) [#6996](https://github.com/gohugoio/hugo/issues/6996)
-
-### Other
-
-* Fix ambigous error on site.GetPage [6cceef65](https://github.com/gohugoio/hugo/commit/6cceef65c2f4b7c262bf67a249867658112b6de4) [@bep](https://github.com/bep) [#7016](https://github.com/gohugoio/hugo/issues/7016)
-* Fix handling of HTML files without front matter [ffcb4aeb](https://github.com/gohugoio/hugo/commit/ffcb4aeb8e392a80da7cad0f1e03a4102efb24ec) [@bep](https://github.com/bep) [#7030](https://github.com/gohugoio/hugo/issues/7030)[#7028](https://github.com/gohugoio/hugo/issues/7028)[#6789](https://github.com/gohugoio/hugo/issues/6789)
-
-
-
-
-
diff --git a/docs/content/en/news/0.67.1-relnotes/index.md b/docs/content/en/news/0.67.1-relnotes/index.md
deleted file mode 100644
index 7962ccca3d9..00000000000
--- a/docs/content/en/news/0.67.1-relnotes/index.md
+++ /dev/null
@@ -1,24 +0,0 @@
-
----
-date: 2020-03-15
-title: "Hugo 0.67.1: A couple of Bug Fixes"
-description: "This version comes with some useful bug fixes."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-Some minor fixes:
-
-* metrics: Fix --templateMetricsHints [5eadc4c0](https://github.com/gohugoio/hugo/commit/5eadc4c0a8e5c51e72670591c4b7877e79c15e3c) [@bep](https://github.com/bep) [#7048](https://github.com/gohugoio/hugo/issues/7048)
-* resources: Add data context to the key in ExecuteAsTemplate [18cb21ff](https://github.com/gohugoio/hugo/commit/18cb21ff2e4a60e7094908e4d6113a9d5a086316) [@bep](https://github.com/bep) [#7046](https://github.com/gohugoio/hugo/issues/7046)
-* Improve Tailwind/PostCSS error messages [df298558](https://github.com/gohugoio/hugo/commit/df298558a5a5b747288d9656402af85e0ac75a43) [@bep](https://github.com/bep) [#7041](https://github.com/gohugoio/hugo/issues/7041)[#7042](https://github.com/gohugoio/hugo/issues/7042)
-* deps: Update Blackfriday [b1106f87](https://github.com/gohugoio/hugo/commit/b1106f8715cac3544b8ea662b969336fe56fa047) [@bep](https://github.com/bep) [#7039](https://github.com/gohugoio/hugo/issues/7039)
-* resources: Try to fix a Go 1.15 go vet error [c0177fe2](https://github.com/gohugoio/hugo/commit/c0177fe2b28eb09d1534e62370849c3f1d70b40f) [@bep](https://github.com/bep)
-* Add languageDirection to language configuration [5914f91b](https://github.com/gohugoio/hugo/commit/5914f91b6c980e42693661d5fd5640e237691df6) [@le0tan](https://github.com/le0tan) [#6550](https://github.com/gohugoio/hugo/issues/6550)
-
-
-
diff --git a/docs/content/en/news/0.68.0-relnotes/hugo-68-featured.png b/docs/content/en/news/0.68.0-relnotes/hugo-68-featured.png
deleted file mode 100644
index 0696d990d15..00000000000
Binary files a/docs/content/en/news/0.68.0-relnotes/hugo-68-featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.68.0-relnotes/index.md b/docs/content/en/news/0.68.0-relnotes/index.md
deleted file mode 100644
index 507249a21bc..00000000000
--- a/docs/content/en/news/0.68.0-relnotes/index.md
+++ /dev/null
@@ -1,57 +0,0 @@
-
----
-date: 2020-03-21
-title: "Minify config and more!"
-description: "Hugo 0.68.0 brings minify configuration and fully navigable headless sections."
-categories: ["Releases"]
----
-
-
-This release (finally) brings minify configuration, a big thanks to [@satotake](https://github.com/satotake) for that contribution. See [Configure Minify](https://gohugo.io/getting-started/configuration/#configure-minify) for details.
-
-We have also extended the [Page Build Options](https://gohugo.io/content-management/build-options/) to allow fully navigable headless sections.
-
-This release represents **17 contributions by 6 contributors** to the main Hugo code base.[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@evankanderson](https://github.com/evankanderson), [@QuLogic](https://github.com/QuLogic), and [@le0tan](https://github.com/le0tan) for their ongoing contributions.
-
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) and [@onedrawingperday](https://github.com/onedrawingperday) for their relentless work on keeping the themes site in pristine condition and to [@davidsneighbour](https://github.com/davidsneighbour) and [@kaushalmodi](https://github.com/kaushalmodi) for all the great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **3 contributions by 3 contributors**.
-
-Hugo now has:
-
-* 42462+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 439+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 300+ [themes](http://themes.gohugo.io/)
-
-## Enhancements
-
-### Other
-
-* Some minify configuration adjustments [7204b354](https://github.com/gohugoio/hugo/commit/7204b354a9f46778f068a4712447d6d4fefbefd8) [@bep](https://github.com/bep)
-* Add minify config [574c2959](https://github.com/gohugoio/hugo/commit/574c2959b8d3338764fa1db102a5e0fd6ed322d9) [@satotake](https://github.com/satotake) [#6750](https://github.com/gohugoio/hugo/issues/6750)[#6892](https://github.com/gohugoio/hugo/issues/6892)
-* Allow headless bundles to list pages via $page.Pages and $page.RegularPages [99958f90](https://github.com/gohugoio/hugo/commit/99958f90fedec11d749a1397300860aa8e8459c2) [@bep](https://github.com/bep) [#7075](https://github.com/gohugoio/hugo/issues/7075)
-* Update to Go 1.14.1 and 1.13.9 [1d91d8e1](https://github.com/gohugoio/hugo/commit/1d91d8e14b13bd135dc4d4a901fc936c9649b219) [@bep](https://github.com/bep) [#7078](https://github.com/gohugoio/hugo/issues/7078)
-* Pass directory name to filters in LstatIfPossible in the same way as Readdir [cc2a5d52](https://github.com/gohugoio/hugo/commit/cc2a5d52a4ad188d93aeb2d51d5c19c7661e098d) [@evankanderson](https://github.com/evankanderson)
-* Update to goldmark 1.1.25. [52c159c4](https://github.com/gohugoio/hugo/commit/52c159c452ab7f48369b5cc9ecc57ecc8dc91654) [@QuLogic](https://github.com/QuLogic)
-* Add workaround for regular CSS imports in SCSS [1a8af7d4](https://github.com/gohugoio/hugo/commit/1a8af7d4f087256710ae0bdf504ed53c0c24a211) [@bep](https://github.com/bep) [#7059](https://github.com/gohugoio/hugo/issues/7059)
-* Add .RegularPagesRecursive [03b93bb9](https://github.com/gohugoio/hugo/commit/03b93bb9884ea479c855c2699e8c7b039dce6224) [@bep](https://github.com/bep) [#6411](https://github.com/gohugoio/hugo/issues/6411)
-* Add data context to the key in ExecuteAsTemplate [18cb21ff](https://github.com/gohugoio/hugo/commit/18cb21ff2e4a60e7094908e4d6113a9d5a086316) [@bep](https://github.com/bep) [#7046](https://github.com/gohugoio/hugo/issues/7046)
-* Improve Tailwind/PostCSS error messages [df298558](https://github.com/gohugoio/hugo/commit/df298558a5a5b747288d9656402af85e0ac75a43) [@bep](https://github.com/bep) [#7041](https://github.com/gohugoio/hugo/issues/7041)[#7042](https://github.com/gohugoio/hugo/issues/7042)
-* Update Blackfriday [b1106f87](https://github.com/gohugoio/hugo/commit/b1106f8715cac3544b8ea662b969336fe56fa047) [@bep](https://github.com/bep) [#7039](https://github.com/gohugoio/hugo/issues/7039)
-* Add languageDirection to language configuration [5914f91b](https://github.com/gohugoio/hugo/commit/5914f91b6c980e42693661d5fd5640e237691df6) [@le0tan](https://github.com/le0tan) [#6550](https://github.com/gohugoio/hugo/issues/6550)
-
-## Fixes
-
-### Other
-
-* Fix Go build version [2ebb9f54](https://github.com/gohugoio/hugo/commit/2ebb9f5484162062c74698237bcdaa31cb8666b9) [@bep](https://github.com/bep)
-* Fix GetTerms nil pointer [95f49211](https://github.com/gohugoio/hugo/commit/95f492114e33fc6e4d9dcfd2b7c1eca5c50d755f) [@carlmjohnson](https://github.com/carlmjohnson) [#7061](https://github.com/gohugoio/hugo/issues/7061)
-* Fix scss vs css import regexp [c7b6d74e](https://github.com/gohugoio/hugo/commit/c7b6d74e898c78da9f5e272e528ff9654206576e) [@bep](https://github.com/bep) [#7063](https://github.com/gohugoio/hugo/issues/7063)
-* Fix --templateMetricsHints [5eadc4c0](https://github.com/gohugoio/hugo/commit/5eadc4c0a8e5c51e72670591c4b7877e79c15e3c) [@bep](https://github.com/bep) [#7048](https://github.com/gohugoio/hugo/issues/7048)
-* Try to fix a Go 1.15 go vet error [c0177fe2](https://github.com/gohugoio/hugo/commit/c0177fe2b28eb09d1534e62370849c3f1d70b40f) [@bep](https://github.com/bep)
-
-
-
-
-
diff --git a/docs/content/en/news/0.68.1-relnotes/index.md b/docs/content/en/news/0.68.1-relnotes/index.md
deleted file mode 100644
index ab9946b8e1a..00000000000
--- a/docs/content/en/news/0.68.1-relnotes/index.md
+++ /dev/null
@@ -1,19 +0,0 @@
-
----
-date: 2020-03-22
-title: "Hugo 0.68.1: 1 bug fix"
-description: "This release reverts a change in resources.ExecuteAsTemplate that broke some theme demos."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with one important fix.
-
-* Revert "resources: Add data context to the key in ExecuteAsTemplate" [c9dc316a](https://github.com/gohugoio/hugo/commit/c9dc316ad160e78c9dff4e75313db4cac8ea6414) [@bep](https://github.com/bep) [#7064](https://github.com/gohugoio/hugo/issues/7064)
-
-
-
diff --git a/docs/content/en/news/0.68.2-relnotes/index.md b/docs/content/en/news/0.68.2-relnotes/index.md
deleted file mode 100644
index c61d1012a49..00000000000
--- a/docs/content/en/news/0.68.2-relnotes/index.md
+++ /dev/null
@@ -1,19 +0,0 @@
-
----
-date: 2020-03-24T12:00:00-05:00
-title: "Hugo 0.68.2: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.68.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with one important fix.
-
-* Fix cache reset for a page's collections on server live reload [cfa73050](https://github.com/gohugoio/hugo/commit/cfa73050a49b2646fe3557cefa0ed31989b0eeeb) [@bep](https://github.com/bep) [#7085](https://github.com/gohugoio/hugo/issues/7085)
-
-
-
diff --git a/docs/content/en/news/0.68.3-relnotes/index.md b/docs/content/en/news/0.68.3-relnotes/index.md
deleted file mode 100644
index 3855aadf301..00000000000
--- a/docs/content/en/news/0.68.3-relnotes/index.md
+++ /dev/null
@@ -1,19 +0,0 @@
-
----
-date: 2020-03-24T13:00:00-05:00
-title: "Hugo 0.68.3: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.68.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with one important fix.
-
-* Fix _build.list.local logic [523d5194](https://github.com/gohugoio/hugo/commit/523d51948fc20e2afb4721b43203c5ab696ae220) [@bep](https://github.com/bep) [#7089](https://github.com/gohugoio/hugo/issues/7089)
-
-
-
diff --git a/docs/content/en/news/0.69.0-relnotes/hugo-69-easter-featured.png b/docs/content/en/news/0.69.0-relnotes/hugo-69-easter-featured.png
deleted file mode 100644
index d1b413142a9..00000000000
Binary files a/docs/content/en/news/0.69.0-relnotes/hugo-69-easter-featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.69.0-relnotes/index.md b/docs/content/en/news/0.69.0-relnotes/index.md
deleted file mode 100644
index 13bb1b76b3d..00000000000
--- a/docs/content/en/news/0.69.0-relnotes/index.md
+++ /dev/null
@@ -1,73 +0,0 @@
-
----
-date: 2020-04-10
-title: "Post Build Resource Transformations"
-description: "Hugo 0.69.0 allows you to delay resource processing to after the build, the prime use case being removal of unused CSS."
-categories: ["Releases"]
----
-
-**It's Eeaster, a time for mysteries and puzzles.** And at first glance, this Hugo release looks a little mysterious. The core of if is a mind-twister:
-
-```go-html-template
-{{ $css := resources.Get "css/main.css" }}
-{{ $css = $css | resources.PostCSS }}
-{{ if hugo.IsProduction }}
-{{ $css = $css | minify | fingerprint | resources.PostProcess }}
-{{ end }}
-
-```
-
-The above uses the new [resources.PostProcess](https://gohugo.io/hugo-pipes/postprocess/) template function which tells Hugo to postpone the transformation of the Hugo Pipes chain to _after the build_, allowing the build steps to use the build output in `/public` as part of its processing.
-
-The prime current use case for the above is CSS pruning in PostCSS. In simple cases you can use the templates as a base for the content filters, but that has its limitations and can be very hard to setup, especially in themed configurations. So we have added a new [writeStats](https://gohugo.io/getting-started/configuration/#configure-build) configuration that, when enabled, will write a file named `hugo_stats.json` to your project root with some aggregated data about the build, e.g. list of HTML entities published, to be used to do [CSS pruning](https://gohugo.io/hugo-pipes/postprocess/#css-purging-with-postcss).
-
-This release represents **20 contributions by 10 contributors** to the main Hugo code base.[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@moorereason](https://github.com/moorereason), [@jaywilliams](https://github.com/jaywilliams), and [@satotake](https://github.com/satotake) for their ongoing contributions.
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) and [@onedrawingperday](https://github.com/onedrawingperday) for their relentless work on keeping the themes site in pristine condition and to [@davidsneighbour](https://github.com/davidsneighbour) and [@kaushalmodi](https://github.com/kaushalmodi) for all the great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **14 contributions by 7 contributors**. A special thanks to [@bep](https://github.com/bep), [@coliff](https://github.com/coliff), [@dmgawel](https://github.com/dmgawel), and [@jasikpark](https://github.com/jasikpark) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 43052+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 438+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 302+ [themes](http://themes.gohugo.io/)
-
-## Enhancements
-
-### Templates
-
-* Extend Jsonify to support options map [8568928a](https://github.com/gohugoio/hugo/commit/8568928aa8e82a6bd7de4555c3703d8835fbd25b) [@moorereason](https://github.com/moorereason)
-* Extend Jsonify to support optional indent parameter [1bc93021](https://github.com/gohugoio/hugo/commit/1bc93021e3dca6405628f6fdd2dc32cff9c9836c) [@moorereason](https://github.com/moorereason) [#5040](https://github.com/gohugoio/hugo/issues/5040)
-
-### Other
-
-* Regen docs helper [b7ff4dc2](https://github.com/gohugoio/hugo/commit/b7ff4dc23e6314fd09ee2c1e24cde96fc833164e) [@bep](https://github.com/bep)
-* Collect HTML elements during the build to use in PurgeCSS etc. [095bf64c](https://github.com/gohugoio/hugo/commit/095bf64c99f57efe083540a50e658808a0a1c32b) [@bep](https://github.com/bep) [#6999](https://github.com/gohugoio/hugo/issues/6999)
-* Update to latest emoji package [7791a804](https://github.com/gohugoio/hugo/commit/7791a804e2179667617b3b145b0fe7eba17627a1) [@QuLogic](https://github.com/QuLogic)
-* Update hosting-on-aws-amplify.md [c774b230](https://github.com/gohugoio/hugo/commit/c774b230e941902675af081f118ea206a4f2a04e) [@Helicer](https://github.com/Helicer)
-* Add basic "post resource publish support" [2f721f8e](https://github.com/gohugoio/hugo/commit/2f721f8ec69c52202815cd1b543ca4bf535c0901) [@bep](https://github.com/bep) [#7146](https://github.com/gohugoio/hugo/issues/7146)
-* Typo correction [7eba37ae](https://github.com/gohugoio/hugo/commit/7eba37ae9b8653be4fc21a0dbbc6f35ca5b9280e) [@fekete-robert](https://github.com/fekete-robert)
-* Use semver for min_version per recommendations [efc61d6f](https://github.com/gohugoio/hugo/commit/efc61d6f3b9f5fb294411ac1dc872b8fc5bdbacb) [@jaywilliams](https://github.com/jaywilliams)
-* Updateto gitmap v1.1.2 [4de3ecdc](https://github.com/gohugoio/hugo/commit/4de3ecdc2658ffd54d2b5073c5ff303b4bf29383) [@dragtor](https://github.com/dragtor) [#6985](https://github.com/gohugoio/hugo/issues/6985)
-* Add data context to the key in ExecuteAsTemplate" [c9dc316a](https://github.com/gohugoio/hugo/commit/c9dc316ad160e78c9dff4e75313db4cac8ea6414) [@bep](https://github.com/bep) [#7064](https://github.com/gohugoio/hugo/issues/7064)
-
-## Fixes
-
-### Other
-
-* Fix hugo mod vendor for regular file mounts [d8d6a25b](https://github.com/gohugoio/hugo/commit/d8d6a25b5755bedaf90261a1539dc37a2f05c3df) [@bep](https://github.com/bep) [#7140](https://github.com/gohugoio/hugo/issues/7140)
-* Revert "Revert "common/herrors: Fix typos in comments"" [9f12be54](https://github.com/gohugoio/hugo/commit/9f12be54ee84f24efdf7c58f05867e8d0dea2ccb) [@bep](https://github.com/bep)
-* Fix typos in comments" [4437e918](https://github.com/gohugoio/hugo/commit/4437e918cdab1d84f2f184fe71e5dac14aa48897) [@bep](https://github.com/bep)
-* Fix typos in comments [1123711b](https://github.com/gohugoio/hugo/commit/1123711b0979b1647d7c486f67af7503afb11abb) [@rnazmo](https://github.com/rnazmo)
-* Fix TrimShortHTML [9c998753](https://github.com/gohugoio/hugo/commit/9c9987535f98714c8a4ec98903f54233735ef0e4) [@satotake](https://github.com/satotake) [#7081](https://github.com/gohugoio/hugo/issues/7081)
-* Fix IsDescendant/IsAncestor for overlapping section names [4a39564e](https://github.com/gohugoio/hugo/commit/4a39564efe7b02a685598ae9dbae95e2326c0230) [@bep](https://github.com/bep) [#7096](https://github.com/gohugoio/hugo/issues/7096)
-* fix typo in getting started [b6e097cf](https://github.com/gohugoio/hugo/commit/b6e097cfe65ecd1d47c805969082e6805563612b) [@matrixise](https://github.com/matrixise)
-* Fix _build.list.local logic [523d5194](https://github.com/gohugoio/hugo/commit/523d51948fc20e2afb4721b43203c5ab696ae220) [@bep](https://github.com/bep) [#7089](https://github.com/gohugoio/hugo/issues/7089)
-* Fix cache reset for a page's collections on server live reload [cfa73050](https://github.com/gohugoio/hugo/commit/cfa73050a49b2646fe3557cefa0ed31989b0eeeb) [@bep](https://github.com/bep) [#7085](https://github.com/gohugoio/hugo/issues/7085)
-
-
-
-
-
diff --git a/docs/content/en/news/0.69.1-relnotes/index.md b/docs/content/en/news/0.69.1-relnotes/index.md
deleted file mode 100644
index d80e3f26d90..00000000000
--- a/docs/content/en/news/0.69.1-relnotes/index.md
+++ /dev/null
@@ -1,29 +0,0 @@
-
----
-date: 2020-04-22
-title: "Hugo 0.69.1: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.69.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-* hugolib/filesystems: Fix typo in test suite [49e6c8cb](https://github.com/gohugoio/hugo/commit/49e6c8cb4ed83e20f1e0ac164e91c38854177b99) [@panakour](https://github.com/panakour)
-* Fix class collector when running with --minify [f37e77f2](https://github.com/gohugoio/hugo/commit/f37e77f2d338cf876cfa637a662acd76f0f2009b) [@bep](https://github.com/bep) [#7161](https://github.com/gohugoio/hugo/issues/7161)
-* related: Fix toLower [27af5a33](https://github.com/gohugoio/hugo/commit/27af5a339a4d3c5712b5ed946a636a8c21916039) [@bep](https://github.com/bep) [#7198](https://github.com/gohugoio/hugo/issues/7198)
-* Fix broken test [b3c82575](https://github.com/gohugoio/hugo/commit/b3c825756f3251f8b26e53262f9d6f484aecf750) [@bep](https://github.com/bep)
-* tpl/tmplimpl/template: Change defer RLock to RUnlock [5146dc61](https://github.com/gohugoio/hugo/commit/5146dc614fc45df698ebf890af06421dea988c96) [@BurtonQin](https://github.com/BurtonQin)
-* hugolib: Add Unlock before panic [736f84b2](https://github.com/gohugoio/hugo/commit/736f84b2d539857f7fdd0e42353af80b4dccfe8d) [@BurtonQin](https://github.com/BurtonQin)
-* docs: Fix typo in Hugo's Security Model [cd4d8202](https://github.com/gohugoio/hugo/commit/cd4d8202016bd3eb5ed9144c8945edaba73c8cf4) [@sensimevanidus](https://github.com/sensimevanidus)
-* deps: Update go-org to v1.1.0 [2b28e5a9](https://github.com/gohugoio/hugo/commit/2b28e5a9cb79af2a8d70c80036f52bcf5399b9df) [@niklasfasching](https://github.com/niklasfasching)
-* commands: Modify gen chromastyles to output all CSS classes [102ec2da](https://github.com/gohugoio/hugo/commit/102ec2da7adcc4afb7050b17989f0486f8379679) [@acahir](https://github.com/acahir) [#7167](https://github.com/gohugoio/hugo/issues/7167)
-* deps: Update to goldmark v1.1.28 [feaa582c](https://github.com/gohugoio/hugo/commit/feaa582cbe950e82969da5e99e3fb9a3947025df) [@bep](https://github.com/bep) [#7113](https://github.com/gohugoio/hugo/issues/7113)
-* Fix query parameter handling in server fast render mode [ee67dbef](https://github.com/gohugoio/hugo/commit/ee67dbeff5bae6941facaaa39cb995a1ee6def03) [@bep](https://github.com/bep) [#7163](https://github.com/gohugoio/hugo/issues/7163)
-
-
-
diff --git a/docs/content/en/news/0.69.2-relnotes/index.md b/docs/content/en/news/0.69.2-relnotes/index.md
deleted file mode 100644
index 048a58817fa..00000000000
--- a/docs/content/en/news/0.69.2-relnotes/index.md
+++ /dev/null
@@ -1,21 +0,0 @@
-
----
-date: 2020-04-24
-title: "Hugo 0.69.2: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.69.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-* Fix IsAncestor and IsDescendant when the same page is passed [8d5766d4](https://github.com/gohugoio/hugo/commit/8d5766d417d6564a1aa1cbe8f9a29ab9bba22371) [@tekezo](https://github.com/tekezo)
-* deps: Update goldmark-highlighting [5c41f41a](https://github.com/gohugoio/hugo/commit/5c41f41ad4b14e48aea64687a7600f5ad231e879) [@satotake](https://github.com/satotake) [#7027](https://github.com/gohugoio/hugo/issues/7027)[#6596](https://github.com/gohugoio/hugo/issues/6596)
-* Fix IsAncestor and IsDescendant under subsection [27a4c441](https://github.com/gohugoio/hugo/commit/27a4c4410cd9592249925fb14b32605fb961c597) [@tekezo](https://github.com/tekezo)
-
-
-
diff --git a/docs/content/en/news/0.7-relnotes/index.md b/docs/content/en/news/0.7-relnotes/index.md
deleted file mode 100644
index e140304c0c6..00000000000
--- a/docs/content/en/news/0.7-relnotes/index.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-date: 2013-07-05T04:22:00Z
-description: "The first public release of Hugo."
-title: "Hugo 0.7"
-categories: ["Releases"]
----
-
-As the first public release here's a bit about what Hugo can do so far:
-
-- **0.7.0** July 4, 2013
- - Hugo now includes a simple server
- - First public release
-- **0.6.0** July 2, 2013
- - Hugo includes an [example documentation site](http://hugo.spf13.com) which it builds
-- **0.5.0** June 25, 2013
- - Hugo is quite usable and able to build [spf13.com](http://spf13.com)
diff --git a/docs/content/en/news/0.70.0-relnotes/hugo-70-featured.png b/docs/content/en/news/0.70.0-relnotes/hugo-70-featured.png
deleted file mode 100644
index 3b9c67d5faa..00000000000
Binary files a/docs/content/en/news/0.70.0-relnotes/hugo-70-featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.70.0-relnotes/index.md b/docs/content/en/news/0.70.0-relnotes/index.md
deleted file mode 100644
index 8a6c25b00df..00000000000
--- a/docs/content/en/news/0.70.0-relnotes/index.md
+++ /dev/null
@@ -1,68 +0,0 @@
-
----
-date: 2020-05-06
-title: "JavaScript Transpiler"
-description: "Hugo 0.70.0 adds a new pipe function that uses Babel to transpile JavaScript."
-categories: ["Releases"]
----
-
-This is a small release, and the main new feature is that you can now use [Babel](https://gohugo.io/hugo-pipes/babel/) to transpile JavaScript.
-
-This release represents **22 contributions by 12 contributors** to the main Hugo code base.[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@BurtonQin](https://github.com/BurtonQin), [@tekezo](https://github.com/tekezo), and [@sensimevanidus](https://github.com/sensimevanidus) for their ongoing contributions.
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) and [@onedrawingperday](https://github.com/onedrawingperday) for their relentless work on keeping the themes site in pristine condition and to [@davidsneighbour](https://github.com/davidsneighbour) and [@kaushalmodi](https://github.com/kaushalmodi) for all the great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **6 contributions by 4 contributors**. A special thanks to [@bep](https://github.com/bep), [@MJ2097](https://github.com/MJ2097), [@jeremyzilar](https://github.com/jeremyzilar), and [@larryclaman](https://github.com/larryclaman) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 43734+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 437+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 316+ [themes](http://themes.gohugo.io/)
-
-## Enhancements
-
-### Templates
-
-* Change defer RLock to RUnlock [5146dc61](https://github.com/gohugoio/hugo/commit/5146dc614fc45df698ebf890af06421dea988c96) [@BurtonQin](https://github.com/BurtonQin)
-
-### Output
-
-* Modify gen chromastyles to output all CSS classes [102ec2da](https://github.com/gohugoio/hugo/commit/102ec2da7adcc4afb7050b17989f0486f8379679) [@acahir](https://github.com/acahir) [#7167](https://github.com/gohugoio/hugo/issues/7167)
-
-### Core
-
-* Add Unlock before panic [736f84b2](https://github.com/gohugoio/hugo/commit/736f84b2d539857f7fdd0e42353af80b4dccfe8d) [@BurtonQin](https://github.com/BurtonQin)
-
-### Other
-
-* Update minify to v2.6.2 [01befcce](https://github.com/gohugoio/hugo/commit/01befcce35ec992d195ce1b9a6a1eeda693cb5a8) [@pperzyna](https://github.com/pperzyna) [#6699](https://github.com/gohugoio/hugo/issues/6699)
-* Add support for sort by boolean [04b1a6d9](https://github.com/gohugoio/hugo/commit/04b1a6d997e72d9abada28db22650d38ccbcbb39) [@Mipsters](https://github.com/Mipsters)
-* Update to Libsass 3.6.4 [dd31e800](https://github.com/gohugoio/hugo/commit/dd31e800075eebd78f921df8b4865c238006e7a7) [@bep](https://github.com/bep) [#7231](https://github.com/gohugoio/hugo/issues/7231)
-* Rename transpileJS to babel [6add6d77](https://github.com/gohugoio/hugo/commit/6add6d77b48cf0aab8b39d7a2bddedb1aa2a52b8) [@bep](https://github.com/bep) [#5764](https://github.com/gohugoio/hugo/issues/5764)
-* Add JavaScript transpiling solution [2a171ff1](https://github.com/gohugoio/hugo/commit/2a171ff1c5d9b1603fe78c67d2d894bb2efccc8b) [@hmmmmniek](https://github.com/hmmmmniek) [#5764](https://github.com/gohugoio/hugo/issues/5764)
-* Disable a test locally [67f92041](https://github.com/gohugoio/hugo/commit/67f920419a53c7ff11e01c4286dca23e92110a12) [@bep](https://github.com/bep)
-* Add diagnostic hints to init timeout message [fe60b7d9](https://github.com/gohugoio/hugo/commit/fe60b7d9e4c12dbc428f992c05969bc14c7fe7a2) [@mtlynch](https://github.com/mtlynch)
-* Update goldmark-highlighting [5c41f41a](https://github.com/gohugoio/hugo/commit/5c41f41ad4b14e48aea64687a7600f5ad231e879) [@satotake](https://github.com/satotake) [#7027](https://github.com/gohugoio/hugo/issues/7027)[#6596](https://github.com/gohugoio/hugo/issues/6596)
-* Update go-org to v1.1.0 [2b28e5a9](https://github.com/gohugoio/hugo/commit/2b28e5a9cb79af2a8d70c80036f52bcf5399b9df) [@niklasfasching](https://github.com/niklasfasching)
-* Update to goldmark v1.1.28 [feaa582c](https://github.com/gohugoio/hugo/commit/feaa582cbe950e82969da5e99e3fb9a3947025df) [@bep](https://github.com/bep) [#7113](https://github.com/gohugoio/hugo/issues/7113)
-
-## Fixes
-
-### Other
-
-* Fix some missing JS class collector cases [c03ea2b6](https://github.com/gohugoio/hugo/commit/c03ea2b66010d2996d652903cb8fa41e983e787f) [@bep](https://github.com/bep) [#7216](https://github.com/gohugoio/hugo/issues/7216)
-* Fix IsAncestor and IsDescendant when the same page is passed [8d5766d4](https://github.com/gohugoio/hugo/commit/8d5766d417d6564a1aa1cbe8f9a29ab9bba22371) [@tekezo](https://github.com/tekezo)
-* Fix IsAncestor and IsDescendant under subsection [27a4c441](https://github.com/gohugoio/hugo/commit/27a4c4410cd9592249925fb14b32605fb961c597) [@tekezo](https://github.com/tekezo)
-* Fix typo in test suite [49e6c8cb](https://github.com/gohugoio/hugo/commit/49e6c8cb4ed83e20f1e0ac164e91c38854177b99) [@panakour](https://github.com/panakour)
-* Fix class collector when running with --minify [f37e77f2](https://github.com/gohugoio/hugo/commit/f37e77f2d338cf876cfa637a662acd76f0f2009b) [@bep](https://github.com/bep) [#7161](https://github.com/gohugoio/hugo/issues/7161)
-* Fix toLower [27af5a33](https://github.com/gohugoio/hugo/commit/27af5a339a4d3c5712b5ed946a636a8c21916039) [@bep](https://github.com/bep) [#7198](https://github.com/gohugoio/hugo/issues/7198)
-* Fix broken test [b3c82575](https://github.com/gohugoio/hugo/commit/b3c825756f3251f8b26e53262f9d6f484aecf750) [@bep](https://github.com/bep)
-* Fix typo in Hugo's Security Model [cd4d8202](https://github.com/gohugoio/hugo/commit/cd4d8202016bd3eb5ed9144c8945edaba73c8cf4) [@sensimevanidus](https://github.com/sensimevanidus)
-* Fix query parameter handling in server fast render mode [ee67dbef](https://github.com/gohugoio/hugo/commit/ee67dbeff5bae6941facaaa39cb995a1ee6def03) [@bep](https://github.com/bep) [#7163](https://github.com/gohugoio/hugo/issues/7163)
-
-
-
-
-
diff --git a/docs/content/en/news/0.71.0-relnotes/hugo-71-featured.png b/docs/content/en/news/0.71.0-relnotes/hugo-71-featured.png
deleted file mode 100644
index 081581df88e..00000000000
Binary files a/docs/content/en/news/0.71.0-relnotes/hugo-71-featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.71.0-relnotes/index.md b/docs/content/en/news/0.71.0-relnotes/index.md
deleted file mode 100644
index 07d951bf3dd..00000000000
--- a/docs/content/en/news/0.71.0-relnotes/index.md
+++ /dev/null
@@ -1,50 +0,0 @@
-
----
-date: 2020-05-18
-title: "Markdown Render Hooks for Headings"
-description: "Render hooks for headings, update to Go 1.14.3, several bug fixes etc."
-categories: ["Releases"]
----
-
-Hugo 0.71 brings [Markdown render hooks for headings](https://gohugo.io/getting-started/configuration-markup#markdown-render-hooks), a set of bug fixes and more.
-
-This release represents **12 contributions by 7 contributors** to the main Hugo code base. [@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@anthonyfok](https://github.com/anthonyfok), [@apexskier](https://github.com/apexskier), and [@johnweldon](https://github.com/johnweldon) for their ongoing contributions.
-
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) and [@onedrawingperday](https://github.com/onedrawingperday) for their relentless work on keeping the themes site in pristine condition and to [@davidsneighbour](https://github.com/davidsneighbour) and [@kaushalmodi](https://github.com/kaushalmodi) for all the great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **8 contributions by 7 contributors**. A special thanks to [@bep](https://github.com/bep), [@mikeee](https://github.com/mikeee), [@h-enk](https://github.com/h-enk), and [@tjamet](https://github.com/tjamet) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 44043+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 437+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 322+ [themes](http://themes.gohugo.io/)
-
-## Enhancements
-
-### Other
-
-* Use WARN log level also for the early initialization [518d1496](https://github.com/gohugoio/hugo/commit/518d149646c13fb49c296a63e61a048f5e672179) [@bep](https://github.com/bep) [#7285](https://github.com/gohugoio/hugo/issues/7285)
-* Update to Go 1.14.3 and Go 1.13.11 [3cc41523](https://github.com/gohugoio/hugo/commit/3cc41523bef802d1942f3d31018547a18cc55923) [@bep](https://github.com/bep)
-* Improve error message when no Babel installed [2fd0a5a6](https://github.com/gohugoio/hugo/commit/2fd0a5a6781456e88745b370d12aaf5351a020ff) [@bep](https://github.com/bep)
-* Add test for headings render hook [6e051c05](https://github.com/gohugoio/hugo/commit/6e051c053e2b5b8419f357ed8acd177440266d07) [@apexskier](https://github.com/apexskier)
-* Add render template hooks for headings [423b8f2f](https://github.com/gohugoio/hugo/commit/423b8f2fb834139cf31514b14b1c1bf28e43b384) [@elihunter173](https://github.com/elihunter173) [#6713](https://github.com/gohugoio/hugo/issues/6713)
-* Add math.Pow [99193449](https://github.com/gohugoio/hugo/commit/991934497e88dcd4134a369a213bb5072c51c139) [@jmooring](https://github.com/jmooring) [#7266](https://github.com/gohugoio/hugo/issues/7266)
-* Do not suppress .well-known/ directory [558c0930](https://github.com/gohugoio/hugo/commit/558c09305e2be16953238c6c0e828f62b950e4f5) [@johnweldon](https://github.com/johnweldon) [#6691](https://github.com/gohugoio/hugo/issues/6691)
-* Quote "@babel/cli" to solve build error [b69a3614](https://github.com/gohugoio/hugo/commit/b69a36140f42ec99ffa2d1e029b8b86ecf8ff929) [@anthonyfok](https://github.com/anthonyfok)
-* Remove custom x-nodejs plugin [a0103864](https://github.com/gohugoio/hugo/commit/a0103864ab76c6a1462a6dee538801740acf4858) [@anthonyfok](https://github.com/anthonyfok)
-* Use .Lastmod for og:updated_time [6205d56b](https://github.com/gohugoio/hugo/commit/6205d56b85fea31e008cd0fef26805bab8084786) [@dtip](https://github.com/dtip)
-
-## Fixes
-
-### Other
-
-* Fix Babel on Windows [723ec555](https://github.com/gohugoio/hugo/commit/723ec555e75fbfa94d90d3ecbcd5775d6c7800e1) [@bep](https://github.com/bep) [#7251](https://github.com/gohugoio/hugo/issues/7251)
-* Upgrade chroma to 0.7.3 to fix invalid css [b342e8fb](https://github.com/gohugoio/hugo/commit/b342e8fbdb23157f3979af91cb5d8d3438003707) [@apexskier](https://github.com/apexskier) [#7207](https://github.com/gohugoio/hugo/issues/7207)
-
-
-
-
-
diff --git a/docs/content/en/news/0.71.1-relnotes/index.md b/docs/content/en/news/0.71.1-relnotes/index.md
deleted file mode 100644
index 7fbd01dd3d6..00000000000
--- a/docs/content/en/news/0.71.1-relnotes/index.md
+++ /dev/null
@@ -1,25 +0,0 @@
-
----
-date: 2020-05-25
-title: "Hugo 0.71.1: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-* Add some more date test cases [81f56332](https://github.com/gohugoio/hugo/commit/81f5633245bf123fbe7ad78eec51ae7b4e6c177a) [@bep](https://github.com/bep) [#7310](https://github.com/gohugoio/hugo/issues/7310)
-* Fix RenderString vs render hooks [9698b0da](https://github.com/gohugoio/hugo/commit/9698b0dab11f52d52145e85ff71311d2f103cb4e) [@bep](https://github.com/bep) [#7265](https://github.com/gohugoio/hugo/issues/7265)
-* Prevent WARNINGs in RenderString [32344fe3](https://github.com/gohugoio/hugo/commit/32344fe3db862584e3f926d63bdf33b7fa7d22f7) [@bep](https://github.com/bep)
-* Fix IsAncestor/IsDescendant for taxonomies [4d7fa9f1](https://github.com/gohugoio/hugo/commit/4d7fa9f114c62ae2ec12257203ed21b0e4d69a04) [@bep](https://github.com/bep) [#7305](https://github.com/gohugoio/hugo/issues/7305)
-* Fix GetPage on section/bundle name overlaps [a985efce](https://github.com/gohugoio/hugo/commit/a985efcecf44afe1d252690ec0a00cf077974f44) [@bep](https://github.com/bep) [#7301](https://github.com/gohugoio/hugo/issues/7301)
-* Fix Go template script escaping [6c3c6686](https://github.com/gohugoio/hugo/commit/6c3c6686f5d3c7155e2d455b07ac8ab70f42cb88) [@bep](https://github.com/bep) [#6695](https://github.com/gohugoio/hugo/issues/6695)
-* Add a test helper [c34bf485](https://github.com/gohugoio/hugo/commit/c34bf48560c91c8a2fa106867af7b08a569609b5) [@bep](https://github.com/bep)
-
-
-
diff --git a/docs/content/en/news/0.72.0-relnotes/hugo-72-featured.png b/docs/content/en/news/0.72.0-relnotes/hugo-72-featured.png
deleted file mode 100644
index 673ab28c336..00000000000
Binary files a/docs/content/en/news/0.72.0-relnotes/hugo-72-featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.72.0-relnotes/index.md b/docs/content/en/news/0.72.0-relnotes/index.md
deleted file mode 100644
index 8e413f02a8c..00000000000
--- a/docs/content/en/news/0.72.0-relnotes/index.md
+++ /dev/null
@@ -1,41 +0,0 @@
-
----
-date: 2020-05-31
-title: URL rewrites in dev server
-description: "Hugo 0.72.0 comes with dev server redirects and URL rewrites, Goldmark typography extension fixes, Scratch.Values."
-categories: ["Releases"]
----
-
-This is a rather small release, its probably main motivation being the fixes in Goldmark's [Typographer extension](https://github.com/gohugoio/hugo/commit/432885c499849efb29d3e50196f377fe0e908333).
-
-This release also adds [redirect and URL rewrite support](https://gohugo.io/getting-started/configuration/#configure-server) to the development server, with mostly Netlify-compatible configuration syntax. This is especially useful if you're building a [SPA](https://en.wikipedia.org/wiki/Single-page_application) with client-side routing.
-
-
-This release represents **13 contributions by 3 contributors** to the main Hugo code base.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **9 contributions by 6 contributors**. A special thanks to [@faraixyz](https://github.com/faraixyz), [@bep](https://github.com/bep), [@coliff](https://github.com/coliff), and [@Leon0824](https://github.com/Leon0824) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 44383+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 437+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 327+ [themes](http://themes.gohugo.io/)
-
-## Enhancements
-
-### Other
-
-* Add Scratch.Values [2919a6a5](https://github.com/gohugoio/hugo/commit/2919a6a503f7b369154d6eb787023a1fe58a9ad4) [@bep](https://github.com/bep) [#7335](https://github.com/gohugoio/hugo/issues/7335)
-* Update Goldmark to improve Typographer [432885c4](https://github.com/gohugoio/hugo/commit/432885c499849efb29d3e50196f377fe0e908333) [@bep](https://github.com/bep) [#7289](https://github.com/gohugoio/hugo/issues/7289)
-* Add redirect support to the server [6a3e8974](https://github.com/gohugoio/hugo/commit/6a3e89743ccad58097a6dd203a63448946a2304d) [@bep](https://github.com/bep) [#7323](https://github.com/gohugoio/hugo/issues/7323)
-
-## Fixes
-
-### Other
-
-* Fix tag collector for nested table elements [c950c86b](https://github.com/gohugoio/hugo/commit/c950c86b4e5fb93f787ec78ca823bded9ef9fa3a) [@bep](https://github.com/bep) [#7318](https://github.com/gohugoio/hugo/issues/7318)
-* Fix build error: my previous commits did not fix it [91520249](https://github.com/gohugoio/hugo/commit/915202494b140882d594e0542153531f6afada02) [@anthonyfok](https://github.com/anthonyfok)
-
-
diff --git a/docs/content/en/news/0.73.0-relnotes/hugo-73-featured.png b/docs/content/en/news/0.73.0-relnotes/hugo-73-featured.png
deleted file mode 100644
index 92685b15c45..00000000000
Binary files a/docs/content/en/news/0.73.0-relnotes/hugo-73-featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.73.0-relnotes/index.md b/docs/content/en/news/0.73.0-relnotes/index.md
deleted file mode 100644
index 398869e92c0..00000000000
--- a/docs/content/en/news/0.73.0-relnotes/index.md
+++ /dev/null
@@ -1,64 +0,0 @@
-
----
-date: 2020-06-23
-title: "Summer Breeze"
-description: "Taxonomy terms cleanup, render hooks per section/type, HMAC function, and more."
-categories: ["Releases"]
----
-
-Again, a release on the small side. Some new features -- one example is that we now support hook templates per section/type, see [#7349](https://github.com/gohugoio/hugo/issues/7349) -- and some important bug fixes.
-
-But the most important part of this release is that we have now finally cleaned up the terms used for the taxonomy page kinds. This has made the taxonomy feature in Hugo harder to understand than it needed to be. The old/new values for these are `taxonomy` => `term` and `taxonomyTerm` => `taxonomy`. We have taken great care to avoid site breakage. See [#6911](https://github.com/gohugoio/hugo/issues/6911) for more information.
-
-This release represents **21 contributions by 9 contributors** to the main Hugo code base.bjorn.erik.pedersen leads the Hugo development with a significant amount of contributions, but also a big shoutout to helfper, moorereason, and onedrawingperday for their ongoing contributions.
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the themes site in pristine condition and to [@davidsneighbour](https://github.com/davidsneighbour), [@coliff](https://github.com/coliff) and [@kaushalmodi](https://github.com/kaushalmodi) for all the great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **30 contributions by 14 contributors**. A special thanks to christianoliff, bjorn.erik.pedersen, patrick, and hello for their work on the documentation site.
-
-
-## Notes
-
-* Rename taxonomy kinds from taxonomy to term, taxonomyTerm to taxonomy [#6911](https://github.com/gohugoio/hugo/issues/6911)
-
-## Enhancements
-
-### Templates
-
-* tpl/crypto: Add hmac
-
-### Other
-
-* Remove some old release notes
-* Create robots.txt in the domain root directory [#5160](https://github.com/gohugoio/hugo/issues/5160)[#4193](https://github.com/gohugoio/hugo/issues/4193)
-* Make GroupByParamDate work with string params [#3983](https://github.com/gohugoio/hugo/issues/3983)
-* Add GroupByLastmod [#7408](https://github.com/gohugoio/hugo/issues/7408)
-* Rename taxonomy kinds from taxonomy to term, taxonomyTerm to taxonomy [#6911](https://github.com/gohugoio/hugo/issues/6911)[#7395](https://github.com/gohugoio/hugo/issues/7395)
-* Add genDocsHelper mage target
-* Regenerate templates
-* Beautify HTML generated by pagination template [#7199](https://github.com/gohugoio/hugo/issues/7199)
-* Add a nested data dir test
-* Use os.PathError in RootMappingFs.doLstat
-* Remove credit (#7347)
-* Allow hook template per section/type [#7349](https://github.com/gohugoio/hugo/issues/7349)
-
-## Fixes
-
-### Templates
-
-* Fix bad rounding in NumFmt [#7116](https://github.com/gohugoio/hugo/issues/7116)
-
-### Other
-
-* Fix aliases with path in baseURL
-* Fix server data race/nil pointer in withMaps [#7392](https://github.com/gohugoio/hugo/issues/7392)
-* Fix order of GetTerms [#7213](https://github.com/gohugoio/hugo/issues/7213)
-* Fix aliases with uglyURLs
-* Fix crash for closing shortcode with no .Inner set [#6857](https://github.com/gohugoio/hugo/issues/6857)[#7330](https://github.com/gohugoio/hugo/issues/7330)
-* Fix aliases with relativeURLs
-* Fix URL rewrites vs fast render server mode [#7357](https://github.com/gohugoio/hugo/issues/7357)
-
-
-
-
-
diff --git a/docs/content/en/news/0.74.0-relnotes/featured.png b/docs/content/en/news/0.74.0-relnotes/featured.png
deleted file mode 100644
index 34f288495fd..00000000000
Binary files a/docs/content/en/news/0.74.0-relnotes/featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.74.0-relnotes/index.md b/docs/content/en/news/0.74.0-relnotes/index.md
deleted file mode 100644
index d793f48885a..00000000000
--- a/docs/content/en/news/0.74.0-relnotes/index.md
+++ /dev/null
@@ -1,85 +0,0 @@
-
----
-date: 2020-07-13
-title: "Native JS Bundler, Open API Support, Inline Partials"
-description: "Hugo 0.74.0 brings blazingly fast native JavaScript bundling, with minification, tree shaking, scope hoisting for ES6 modules, and transpilation of JSX and newer JS syntax down to ES6. And more."
-categories: ["Releases"]
----
-
-**Note:** The documentation site isn't updated with all of the main new things below. We will get to it soon. See https://github.com/gohugoio/hugoDocs/issues/1171
-
-This release comes with native JavaScript bundling (and minifier), with [import](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) support (from `node_modules` etc.), tree shaking, scope hoisting for ES6 modules, transpilation of JSX and newer JS syntax down to ES6, JavaScript/JSX and TypeScript/TSX support. And it's _very fast_. [Benchmarks](https://github.com/evanw/esbuild#benchmarks) rates it **at least 100x faster** than the other JavaScript bundlers included. This new feature is backed by the very impressive [ESBuild](https://github.com/evanw/esbuild) project by [@evanw](https://github.com/evanw). Many thanks to [@remko](https://github.com/remko) for the integration work.
-
-A very simple example building a TypeScript file:
-
-```go-html-template
-{{ $js := resources.Get "js/main.ts" | js.Build }}
-```
-This release also comes with Open API 3-support. This makes it much easier to create "Swagger styled" API-documentation. The below will unmarshal your YAML file into [this object graph](https://godoc.org/github.com/getkin/kin-openapi/openapi3#Swagger):
-
-```go-html-template
-{{ $api := resources.Get "api/openapi.yaml" | openapi3.Unmarshal }}
-```
-
-Hugo's Asciidoc integration has also gotten a face lift. A big shoutout to [@muenchhausen](https://github.com/muenchhausen) and [@bwklein](https://github.com/bwklein) for their work on this.
-
-And finally, [partials](https://gohugo.io/templates/partials/#inline-partials) can now be defined inline -- and that is way more useful than it sounds.
-
-
-This release represents **23 contributions by 9 contributors** to the main Hugo code base. [@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@niklasfasching](https://github.com/niklasfasching), [@bwklein](https://github.com/bwklein), and [@muenchhausen](https://github.com/muenchhausen) for their ongoing contributions.
-
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the themes site in pristine condition and to [@davidsneighbour](https://github.com/davidsneighbour), [@coliff](https://github.com/coliff) and [@kaushalmodi](https://github.com/kaushalmodi) for all the great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **8 contributions by 7 contributors**. A special thanks to [@OmarEmaraDev](https://github.com/OmarEmaraDev), [@regisphilibert](https://github.com/regisphilibert), [@coliff](https://github.com/coliff), and [@jessicahuynh](https://github.com/jessicahuynh) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 45377+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 438+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 331+ [themes](http://themes.gohugo.io/)
-
-## Enhancements
-
-### Templates
-
-* Add strings.Count [028b3567](https://github.com/gohugoio/hugo/commit/028b356787426dbc190ce9868fbc9a6400c2996e) [@bep](https://github.com/bep) [#7453](https://github.com/gohugoio/hugo/issues/7453)
-* Add debug.Dump [defd7106](https://github.com/gohugoio/hugo/commit/defd7106bf79a502418ec373bdb82742b16f777f) [@bep](https://github.com/bep) [#3957](https://github.com/gohugoio/hugo/issues/3957)
-
-### Output
-
-* Add proper Media Type handling in js.Build [9df98ec4](https://github.com/gohugoio/hugo/commit/9df98ec49ca9fa326125ccfee626b6e46c6ab14b) [@bep](https://github.com/bep) [#732](https://github.com/gohugoio/hugo/issues/732)
-
-### Core
-
-* Add missing zero check on file [ccfaeb67](https://github.com/gohugoio/hugo/commit/ccfaeb678b312535928af3451324a54f2c7cb199) [@bep](https://github.com/bep)
-
-### Other
-
-* Regenerate docs helper [25e3da33](https://github.com/gohugoio/hugo/commit/25e3da3343b5cd4bbcd11fa76b382fb089971840) [@bep](https://github.com/bep)
-* Add js.Build asset bundling [2fc33807](https://github.com/gohugoio/hugo/commit/2fc33807077cd25bf91f2298bf1a8ace126881a7) [@remko](https://github.com/remko) [#7321](https://github.com/gohugoio/hugo/issues/7321)
-* Add openapi3.Unmarshal [12a65e76](https://github.com/gohugoio/hugo/commit/12a65e76df9470d9563b91a22969ddb41b7c19aa) [@bep](https://github.com/bep) [#7442](https://github.com/gohugoio/hugo/issues/7442)[#7443](https://github.com/gohugoio/hugo/issues/7443)
-* Remove trailing hyphen from auto heading ID [58c0f5e6](https://github.com/gohugoio/hugo/commit/58c0f5e6171cbf8e3ed8d73ac95a7b85168c5b2f) [@jmooring](https://github.com/jmooring) [#6798](https://github.com/gohugoio/hugo/issues/6798)
-* Ensure that non-trivial default flag values are passed through. [a1c3e3c1](https://github.com/gohugoio/hugo/commit/a1c3e3c1f32bcbc3b3aa6921bdee98a9f795a2da) [@vangent](https://github.com/vangent)
-* Update formats.md doc for new allowed extensions. [e9f87c4e](https://github.com/gohugoio/hugo/commit/e9f87c4e3feee937d05504763935805fec26213c) [@bwklein](https://github.com/bwklein)
-* Update config.go to add two Asciidoctor extensions [beb6c03b](https://github.com/gohugoio/hugo/commit/beb6c03bc8f476b753e5f3e3bc7a4a2e3f8ad355) [@bwklein](https://github.com/bwklein)
-* Add support for inline partials [4a3efea7](https://github.com/gohugoio/hugo/commit/4a3efea7efe59cd3de7d0eb352836ab395a2b6b3) [@bep](https://github.com/bep) [#7444](https://github.com/gohugoio/hugo/issues/7444)
-* Add support for native Org dates in frontmatter [c66dc6c7](https://github.com/gohugoio/hugo/commit/c66dc6c74fa3bbe308ccaade8c76071b49908129) [@sometimesfood](https://github.com/sometimesfood)
-* Update go-org to v1.3.0 [127d5feb](https://github.com/gohugoio/hugo/commit/127d5feb32b466c4a0035e81f86684920dd88cfe) [@niklasfasching](https://github.com/niklasfasching)
-* Update go-org to v1.2.0 [2d42ba91](https://github.com/gohugoio/hugo/commit/2d42ba912ba945230aa0be23c3c8256cba40ce99) [@niklasfasching](https://github.com/niklasfasching)
-* Update bug_report.md [5b7b5dea](https://github.com/gohugoio/hugo/commit/5b7b5dea1fe3494995c6a9c3368087abf47cdc12) [@bep](https://github.com/bep)
-* Remove some unused code [057b1377](https://github.com/gohugoio/hugo/commit/057b1377c5f4d0d80ee299293db06384a475ad19) [@bep](https://github.com/bep)
-* Add an option to print memory usage at intervals [48dbb593](https://github.com/gohugoio/hugo/commit/48dbb593f7cc0dceb55d232ac198e82f3df1c964) [@bep](https://github.com/bep)
-* Rework external asciidoctor integration [f0266e2e](https://github.com/gohugoio/hugo/commit/f0266e2ef3487bc57dd05402002fc816e3b40195) [@muenchhausen](https://github.com/muenchhausen)
-* Enable the embedded template test when race detector is off [77aa385b](https://github.com/gohugoio/hugo/commit/77aa385b84dbc1805ff7e34dafeadb181905c689) [@bep](https://github.com/bep) [#5926](https://github.com/gohugoio/hugo/issues/5926)
-* Merge branch 'release-0.73.0' [545a1c1c](https://github.com/gohugoio/hugo/commit/545a1c1cedc93d091050bae07c02fc2435ad2d20) [@bep](https://github.com/bep)
-* Updated installation instruction about Sass/SCSS support [0b579db8](https://github.com/gohugoio/hugo/commit/0b579db80fba1bde7dab07ea92d622dd6214dcfb) [@mateusz-szczyrzyca](https://github.com/mateusz-szczyrzyca)
-
-## Fixes
-
-### Other
-
-* Fix server reload when non-HTML shortcode changes [42e150fb](https://github.com/gohugoio/hugo/commit/42e150fbfac736bd49bc7e50cb8cdf9f81386f59) [@bep](https://github.com/bep) [#7448](https://github.com/gohugoio/hugo/issues/7448)
-
-
diff --git a/docs/content/en/news/0.74.1-relnotes/index.md b/docs/content/en/news/0.74.1-relnotes/index.md
deleted file mode 100644
index 7419296afe6..00000000000
--- a/docs/content/en/news/0.74.1-relnotes/index.md
+++ /dev/null
@@ -1,19 +0,0 @@
-
----
-date: 2020-07-13
-title: "Hugo 0.74.1: A couple of Bug Fixes"
-description: "This version fixes one issue introduced in 0.74.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with one important fix.
-
-* Fix baseof block regression [c91dbe4c](https://github.com/gohugoio/hugo/commit/c91dbe4ce9c30623ba6e686fd17efae935aa0cc5) [@bep](https://github.com/bep) [#7478](https://github.com/gohugoio/hugo/issues/7478)
-
-
-
diff --git a/docs/content/en/news/0.74.2-relnotes/index.md b/docs/content/en/news/0.74.2-relnotes/index.md
deleted file mode 100644
index e78d8564cff..00000000000
--- a/docs/content/en/news/0.74.2-relnotes/index.md
+++ /dev/null
@@ -1,24 +0,0 @@
-
----
-date: 2020-07-17
-title: "Hugo 0.74.2: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.74.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-Add .Defines to js.Build options [35011bcb](https://github.com/gohugoio/hugo/commit/35011bcb26b6fcfcbd77dc05aa8246ca45b2c2ba) [@bep](https://github.com/bep) [#7489](https://github.com/gohugoio/hugo/issues/7489)
-
-This is needed to import `react` as a library, e.g.:
-
-```
-{{ $jsx := resources.Get "index.jsx" }}
-{{ $options := dict "defines" (dict "process.env.NODE_ENV" "\"development\"") }}
-{{ $js := $jsx | js.Build $options }}
-```
-
-
diff --git a/docs/content/en/news/0.74.3-relnotes/index.md b/docs/content/en/news/0.74.3-relnotes/index.md
deleted file mode 100644
index b5503982cb9..00000000000
--- a/docs/content/en/news/0.74.3-relnotes/index.md
+++ /dev/null
@@ -1,26 +0,0 @@
-
----
-date: 2020-07-23
-title: "Hugo 0.74.3: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.74.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-* publisher: Collect transition attributes as classes [00e00da2](https://github.com/gohugoio/hugo/commit/00e00da233ab4d643de90bafca00f60ee0bbe785) [@bep](https://github.com/bep) [#7509](https://github.com/gohugoio/hugo/issues/7509)
-* Fix Asciidoctor args [45c665d3](https://github.com/gohugoio/hugo/commit/45c665d396ed368261f4a63ceee753c7f6dc5bf9) [@helfper](https://github.com/helfper) [#7493](https://github.com/gohugoio/hugo/issues/7493)
-* Fix date format in internal schema template [a06c06a5](https://github.com/gohugoio/hugo/commit/a06c06a5c202de85ff47792b7468bfaeec2fea12) [@jmooring](https://github.com/jmooring) [#7495](https://github.com/gohugoio/hugo/issues/7495)
-* resources/js: Add option for setting bundle format [0256959a](https://github.com/gohugoio/hugo/commit/0256959a358bb26b983c9d9496862b0fdf387621) [@bep](https://github.com/bep) [#7503](https://github.com/gohugoio/hugo/issues/7503)
-* resources/js: Simplify options handling [eded9ac2](https://github.com/gohugoio/hugo/commit/eded9ac2a05b9a7244c25c70ca8f761b69b33385) [@bep](https://github.com/bep) [#7499](https://github.com/gohugoio/hugo/issues/7499)
-* make sure documentation intro text only appears once [8d725128](https://github.com/gohugoio/hugo/commit/8d72512825b4cee12dc1952004f48fd076a3517b) [@TheHippo](https://github.com/TheHippo)
-* resources/js: Add es5 build target [e81aef0a](https://github.com/gohugoio/hugo/commit/e81aef0a954623e4a19062d1534bd8c2af97102a) [@bep](https://github.com/bep)
-* deps: esbuild v0.6.5 [9f919147](https://github.com/gohugoio/hugo/commit/9f9191471ec501f1f957020726f939c9ef48e193) [@bep](https://github.com/bep)
-
-
-
diff --git a/docs/content/en/news/0.75.0-relnotes/featured.png b/docs/content/en/news/0.75.0-relnotes/featured.png
deleted file mode 100644
index 0ee2a713f6c..00000000000
Binary files a/docs/content/en/news/0.75.0-relnotes/featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.75.0-relnotes/index.md b/docs/content/en/news/0.75.0-relnotes/index.md
deleted file mode 100644
index 3758be13b7f..00000000000
--- a/docs/content/en/news/0.75.0-relnotes/index.md
+++ /dev/null
@@ -1,166 +0,0 @@
-
----
-date: 2020-09-14
-title: "NPM Pack"
-description: "Hugo 0.75 comes with a new \"hugo mod npm pack\" command, several improvements re. Hugo Modules and the Node tools, and more."
-categories: ["Releases"]
----
-
-Hugo `0.75.0` brings several improvements to Hugo Modules, a new CLI command to bridge the JavaScript dependencies into Hugo, a refresh of the versions of the most important upstream dependencies, and more. There are also some good bug fixes in this release. One notable one is covered by [this commit](https://github.com/gohugoio/hugo/commit/4055c121847847d8bd6b95a928185daee065091b) -- which covers a "stale content scenario in server" when you include content or page data via `GetPage` from a shortcode.
-
-## NPM Pack
-
-The new CLI command is called `hugo mod npm pack`. We have marked it as experimental. It works great, go ahead and use it, but we need to test this out in real projects to get a feel of it; it is likely that it will change/improve in the upcoming versions of Hugo. The command creates a consolidated `package.json` from the project and all of its [theme components](https://gohugo.io/hugo-modules/theme-components/). On version conflicts, the version closest to the project is selected. We may revise that strategy in the future ([minimal version selection](https://about.sourcegraph.com/blog/the-pain-that-minimal-version-selection-solves/) maybe?), but this should give both control and the least amount of surprise for the site owner.
-
-So, why did we do this? JavaScript is often a background actor in a Hugo project, and it doesn't make sense to publish it to a NPM registry. The JS dependencies are mostly build tools (PostCSS, TailwindCSS, Babel), `devDependencies`. This has been working fine as long as you kept the JS config files (including `package.json`) in the project, adding duplication/work when using ready-to-use theme components. These tools work best when you have everything below a single file tree, which is very much different to how [Hugo Modules](https://gohugo.io/hugo-modules/) work. An example of a module with TailwindCSS:
-
-```bash
-tailwind-module
-├── assets
-│ └── css
-├── package.json
-├── postcss.config.js
-└── tailwind.config.js
-```
-
-If you included `tailwind-module` in a Hugo project and processed it with `PostCSS`, this is what happened in earlier versions:
-
-* It used the directory where the `postcss.config.js` lives as a starting point to look for any `require`'d dependency.
-* TailwindCSS would, on the other hand, load its configuration file relative to where `PostCSS` was invoked (the project directory).
-
-The above just doesn't work and here is the technical notes on how we have fixed this:
-
-* The new `hugo mod npm pack` creates a consolidated `package.json` based on files named `package.hugo.json` it finds in the dependency tree (one is created for you the first time you run this command). The end result will always be `package.json`, which works seamlessly with `npm install` invoked automatically by Netlify and other CI vendors.
-* The main project's `node_modules` folder is added to [NODE_PATH](https://medium.com/nafsadh/setting-up-node-path-for-using-global-packages-via-require-642eb711c725) when running `PostCSS`and `Babel`.
-* We have introduced a new mount point `assets/_jsconfig` where we, by default, mount the JS configuration files that we're interested in. This is where Hugo will start looking for these files, and the files' filenames will also be available in the Node environment, so you can do:
-
-```js
-let tailwindConfig = process.env.HUGO_FILE_TAILWIND_CONFIG_JS || './tailwind.config.js';
-const tailwind = require('tailwindcss')(tailwindConfig);
-```
-
-## Module Enhancements
-
-* We have added a `noVendor` Glob pattern config to the module config [d4611c43](https://github.com/gohugoio/hugo/commit/d4611c4322dabfd8d2520232be578388029867db) [@bep](https://github.com/bep) [#7647](https://github.com/gohugoio/hugo/issues/7647). This allows you to only vendor a subset of your dependencies.
-* We have added `ignoreImports` option to module imports config [20af9a07](https://github.com/gohugoio/hugo/commit/20af9a078189ce1e92a1d2047c90fba2a4e91827) [@bep](https://github.com/bep) [#7646](https://github.com/gohugoio/hugo/issues/7646), which allows you to import a module and load its config, but not follow its imports.
-* We have deprecated `--ignoreVendor` in favour of a `--ignoreVendorPaths`, a patch matching Glob pattern [9a1e6d15](https://github.com/gohugoio/hugo/commit/9a1e6d15a31ec667b2ff9cf20e43b1daca61e004) [@bep](https://github.com/bep). A typical use for this would be when you have vendored your dependencies, but want to edit one of them.
-
-
-## Statistics
-
-This release represents **79 contributions by 19 contributors** to the main Hugo code base. [@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@dependabot[bot]](https://github.com/apps/dependabot), [@moorereason](https://github.com/moorereason), and [@jmooring](https://github.com/jmooring) for their ongoing contributions.
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the themes site in pristine condition and to [@davidsneighbour](https://github.com/davidsneighbour), [@coliff](https://github.com/coliff) and [@kaushalmodi](https://github.com/kaushalmodi) for all the great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **24 contributions by 15 contributors**. A special thanks to [@jmooring](https://github.com/jmooring), [@bep](https://github.com/bep), [@jornane](https://github.com/jornane), and [@inwardmovement](https://github.com/inwardmovement) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 46596+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 438+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 352+ [themes](http://themes.gohugo.io/)
-
-## Notes
-* We now build with Go 1.15, which means that we no longer build release binaries for MacOS 32-bit.
-* You may now get an error message about "error calling partial: partials that returns a value needs a non-zero argument.". This error situation was not caught earlier, and comes from a limitation in Go's templates: If you use the `return` keyword in a partial, the argument you pass to that partial (e.g. the ".") cannot be zero (and 0 and "" is considered a zero argument).
-
-## Enhancements
-
-### Templates
-
-* Print layout name if it was specified when showing missing layout file error [9df60b62](https://github.com/gohugoio/hugo/commit/9df60b62f9c4e36a269f0c6e9a69bee9dc691031) [@richtera](https://github.com/richtera) [#7617](https://github.com/gohugoio/hugo/issues/7617)
-* Add limit support to replaceRE [cdfd1c99](https://github.com/gohugoio/hugo/commit/cdfd1c99baa22d69e865294dfcd783811f96c880) [@moorereason](https://github.com/moorereason) [#7586](https://github.com/gohugoio/hugo/issues/7586)
-* Extend merge to accept multiple parameters [047af7cf](https://github.com/gohugoio/hugo/commit/047af7cfe5e9aa740b85e0f9974a2d31a0ef4c08) [@moorereason](https://github.com/moorereason) [#7595](https://github.com/gohugoio/hugo/issues/7595)
-* Add limit option to replace template function [f9ebaaed](https://github.com/gohugoio/hugo/commit/f9ebaaed1be1e4a26eef2aebd2c7554c979f29fa) [@moorereason](https://github.com/moorereason) [#7586](https://github.com/gohugoio/hugo/issues/7586)
-
-### Output
-
-* Respect mediatypes for deploy [12f6a1cd](https://github.com/gohugoio/hugo/commit/12f6a1cdc0aedf4319367af57bda3c94150d6a84) [@satotake](https://github.com/satotake) [#6861](https://github.com/gohugoio/hugo/issues/6861)
-
-### Other
-
-* Set PWD in environment when running the Node apps [377ad87a](https://github.com/gohugoio/hugo/commit/377ad87a51e0ef3619af4fe1be6aeee14c215c0a) [@bep](https://github.com/bep)
-* already -> already [292b0e26](https://github.com/gohugoio/hugo/commit/292b0e26ec9253398f7289dcf096691f63de2d96) [@dholbach](https://github.com/dholbach)
-* Regen docs helper [be2404c8](https://github.com/gohugoio/hugo/commit/be2404c8b17d3275cc82d9e659b9e41dddea7ded) [@bep](https://github.com/bep)
-* Regenerate CLI docs [c8da8eb1](https://github.com/gohugoio/hugo/commit/c8da8eb1f5551e6d141843daab41cb0ddbb0de4b) [@bep](https://github.com/bep)
-* Add "hugo mod npm pack" [85ba9bff](https://github.com/gohugoio/hugo/commit/85ba9bfffba9bfd0b095cb766f72700d4c211e31) [@bep](https://github.com/bep) [#7644](https://github.com/gohugoio/hugo/issues/7644)[#7656](https://github.com/gohugoio/hugo/issues/7656)[#7675](https://github.com/gohugoio/hugo/issues/7675)
-* bump github.com/aws/aws-sdk-go from 1.34.21 to 1.34.22 [4fad43c8](https://github.com/gohugoio/hugo/commit/4fad43c8bd528f1805e78c50cd2e33822351c183) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Add support to linkable line anchors on Chroma [fb0f2cc7](https://github.com/gohugoio/hugo/commit/fb0f2cc718a54fd0774a0367e0a60718b5731de5) [@fjorgemota](https://github.com/fjorgemota) [#7622](https://github.com/gohugoio/hugo/issues/7622)
-* Bump bundled Node.js from v8.12.0 to v12.18.3 [748fd4cb](https://github.com/gohugoio/hugo/commit/748fd4cb0d083de7c173d4b04b874358750fc900) [@anthonyfok](https://github.com/anthonyfok) [#7278](https://github.com/gohugoio/hugo/issues/7278)
-* Change confinement from strict to classic" [b82f440c](https://github.com/gohugoio/hugo/commit/b82f440c59a5bf466c0f4c0431af6099216b0e37) [@anthonyfok](https://github.com/anthonyfok)
-* bump github.com/getkin/kin-openapi from 0.14.0 to 0.22.0 [c8143efa](https://github.com/gohugoio/hugo/commit/c8143efa5d21d20bcf3fa1d4f3fb292e460f90d8) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/aws/aws-sdk-go from 1.34.20 to 1.34.21 [c80132bb](https://github.com/gohugoio/hugo/commit/c80132bbe50f443a8be06dcbc51b855a5a5f8fa2) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/spf13/viper from 1.6.1 to 1.7.1 [75fa4c5c](https://github.com/gohugoio/hugo/commit/75fa4c5c950a43e33dfadfa138f61126b548ac40) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Run "go mod tidy" [fd7969e0](https://github.com/gohugoio/hugo/commit/fd7969e0b09e282d1cd83281bc0f5a62080afe5a) [@bep](https://github.com/bep)
-* Update to Goldmark v1.2.1 [b7fa3c4b](https://github.com/gohugoio/hugo/commit/b7fa3c4bba73f873bda71ba028ef46ce58aad908) [@bep](https://github.com/bep)
-* bump github.com/aws/aws-sdk-go from 1.27.1 to 1.34.20 [746ba803](https://github.com/gohugoio/hugo/commit/746ba803afee8f0f56ee0655cc55087f1822d39c) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/mitchellh/mapstructure from 1.1.2 to 1.3.3 [612b7d37](https://github.com/gohugoio/hugo/commit/612b7d376f1c50abe1fe6fe5188d576c1f5f1743) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Change confinement from strict to classic [6f4ff1a4](https://github.com/gohugoio/hugo/commit/6f4ff1a4617ec42861d255db718286ceaf4f6c8e) [@anthonyfok](https://github.com/anthonyfok)
-* bump github.com/spf13/cobra from 0.0.5 to 0.0.7 [ddeca459](https://github.com/gohugoio/hugo/commit/ddeca45933ab6e58c1b5187ad58dd261c9059009) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/sanity-io/litter from 1.2.0 to 1.3.0 [31f2091f](https://github.com/gohugoio/hugo/commit/31f2091f5803129b97c2a3f6245acc8b788235c7) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Add noVendor to module config [d4611c43](https://github.com/gohugoio/hugo/commit/d4611c4322dabfd8d2520232be578388029867db) [@bep](https://github.com/bep) [#7647](https://github.com/gohugoio/hugo/issues/7647)
-* Add ignoreImports to module imports config [20af9a07](https://github.com/gohugoio/hugo/commit/20af9a078189ce1e92a1d2047c90fba2a4e91827) [@bep](https://github.com/bep) [#7646](https://github.com/gohugoio/hugo/issues/7646)
-* Make ignoreVendor a glob pattern [9a1e6d15](https://github.com/gohugoio/hugo/commit/9a1e6d15a31ec667b2ff9cf20e43b1daca61e004) [@bep](https://github.com/bep) [#7642](https://github.com/gohugoio/hugo/issues/7642)
-* bump github.com/gorilla/websocket from 1.4.1 to 1.4.2 [84adecf9](https://github.com/gohugoio/hugo/commit/84adecf97baa91ab18cb26812fa864b4451d3c5f) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/fsnotify/fsnotify from 1.4.7 to 1.4.9 [573558a0](https://github.com/gohugoio/hugo/commit/573558a078c6aaa671de0224c2d62b6d451d667c) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/kyokomi/emoji [8b10c22f](https://github.com/gohugoio/hugo/commit/8b10c22f822f0874890d2d6df68439450b83ef89) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/markbates/inflect from 1.0.0 to 1.0.4 [195bd124](https://github.com/gohugoio/hugo/commit/195bd1243b350e7a7814e0c893d17c3c408039c7) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/frankban/quicktest from 1.7.2 to 1.10.2 [6a544ece](https://github.com/gohugoio/hugo/commit/6a544ece24c37c98e2e4770fab350d76a0553f6a) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Encode & in livereload injected code [4b430d45](https://github.com/gohugoio/hugo/commit/4b430d456afee9c6da5e5ab46084a05469be1430) [@axllent](https://github.com/axllent)
-* bump github.com/niklasfasching/go-org from 1.3.1 to 1.3.2 [b9f10c75](https://github.com/gohugoio/hugo/commit/b9f10c75cb74c1976fbbf3d9e8dcdd4f3d46e790) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/bep/golibsass from 0.6.0 to 0.7.0 [537c598e](https://github.com/gohugoio/hugo/commit/537c598e9a4d8b8b47f5bffbcf59f72e9a1902c1) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump golang.org/x/text from 0.3.2 to 0.3.3 [67348676](https://github.com/gohugoio/hugo/commit/67348676f703f3ad3f778da1cdfa0fe001e5f925) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/evanw/esbuild from 0.6.5 to 0.6.32 [f9cc0ec7](https://github.com/gohugoio/hugo/commit/f9cc0ec76ee84451583a16a0abb9b09d298c7e00) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/nicksnyder/go-i18n from 1.10.0 to 1.10.1 [b5483eed](https://github.com/gohugoio/hugo/commit/b5483eed6e8c07809fc818192e0ce00d9496565c) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Revert "Update dependabot.yml" [90285f47](https://github.com/gohugoio/hugo/commit/90285f47504f8f2e30254745dd795d4ef007e205) [@bep](https://github.com/bep)
-* Update replaceRE func [f7c1b5fe](https://github.com/gohugoio/hugo/commit/f7c1b5fe1c22ba5f16e3fa442df6a8a70711f23f) [@moorereason](https://github.com/moorereason)
-* Update replace func [183e8626](https://github.com/gohugoio/hugo/commit/183e8626070a5f55c11648082e3060e35231d934) [@moorereason](https://github.com/moorereason)
-* Update merge function [f50ee6bb](https://github.com/gohugoio/hugo/commit/f50ee6bbe5ec0c0a1f7c21da6629faaed23bbe71) [@moorereason](https://github.com/moorereason)
-* Update dependabot.yml [c0655ba6](https://github.com/gohugoio/hugo/commit/c0655ba6ce5db54e8fec2c0e2bef9965b9fb90fc) [@bep](https://github.com/bep)
-* Create dependabot.yml [a2dda22c](https://github.com/gohugoio/hugo/commit/a2dda22c368adbffbba74c8c388cc10299801692) [@bep](https://github.com/bep)
-* Remove Pygments from requirements.txt [910d81a6](https://github.com/gohugoio/hugo/commit/910d81a6927c30ad1126c1bfaab1155b970f6442) [@bep](https://github.com/bep)
-* Regen CLI docs [8c490a73](https://github.com/gohugoio/hugo/commit/8c490a73b3735a0db46abba9bbe15de5ed2167e1) [@bep](https://github.com/bep)
-* Regen docs helper [e6cd9da4](https://github.com/gohugoio/hugo/commit/e6cd9da42d415552ae69e6b0afae823fd5e0003c) [@bep](https://github.com/bep)
-* markup/asciidocext: Revert trace=true [dcf25c0b](https://github.com/gohugoio/hugo/commit/dcf25c0b49eefef0572ec66337a5721bfde22233) [@bep](https://github.com/bep)
-* Update to Go 1.15.1 and 1.14.8 [e820b366](https://github.com/gohugoio/hugo/commit/e820b366b91729313c68be04b413e8894efc4421) [@bep](https://github.com/bep) [#7627](https://github.com/gohugoio/hugo/issues/7627)
-* Add support for .TableOfContents [3ba7c925](https://github.com/gohugoio/hugo/commit/3ba7c92530a80f2f04fe57705ab05c247a6e8437) [@npiganeau](https://github.com/npiganeau) [#1687](https://github.com/gohugoio/hugo/issues/1687)
-* Add a test case [19ef27b9](https://github.com/gohugoio/hugo/commit/19ef27b98edca53c4138b01c0f7c7bfbeb5ffcf1) [@bep](https://github.com/bep) [#7619](https://github.com/gohugoio/hugo/issues/7619)
-* Add SourceMap flag with inline option [c6b661de](https://github.com/gohugoio/hugo/commit/c6b661de826f3ed8768a97a5178b4e020cb2ace1) [@richtera](https://github.com/richtera) [#7607](https://github.com/gohugoio/hugo/issues/7607)
-* Remove logic that hides 'Building Sites' message after build completes [d39636a5](https://github.com/gohugoio/hugo/commit/d39636a5fc6bb82b3e0bd013858c7d116faa0c6b) [@jwarner112](https://github.com/jwarner112) [#7579](https://github.com/gohugoio/hugo/issues/7579)
-* Improve stderr logging for PostCSS and simlilar [ec374204](https://github.com/gohugoio/hugo/commit/ec37420468157284651ef6b04b30420b249179e2) [@bep](https://github.com/bep) [#7584](https://github.com/gohugoio/hugo/issues/7584)
-* Fail on partials with return when given none or a zero argument [ae63c2b5](https://github.com/gohugoio/hugo/commit/ae63c2b5c94f68fbabd5dbd821630e747e8959a4) [@bep](https://github.com/bep) [#7572](https://github.com/gohugoio/hugo/issues/7572)[#7528](https://github.com/gohugoio/hugo/issues/7528)
-* Update to Go 1.15 [e627449c](https://github.com/gohugoio/hugo/commit/e627449c0a2f1d2ffac29357c4f1832fc5462870) [@bep](https://github.com/bep) [#7554](https://github.com/gohugoio/hugo/issues/7554)
-* Revert "Update stale.yml" [c2235c6a](https://github.com/gohugoio/hugo/commit/c2235c6a62d29e0a9e2e274eb340358a445b695d) [@bep](https://github.com/bep)
-* Update stale.yml [4f69ade7](https://github.com/gohugoio/hugo/commit/4f69ade7118302abff97169d17bfa9baac6a711c) [@bep](https://github.com/bep)
-* Remove trailing whitespace and tabs from RSS templates [5f425901](https://github.com/gohugoio/hugo/commit/5f42590144579c318a444ea2ce46d5c3fbbbfe6e) [@solarkennedy](https://github.com/solarkennedy)
-* Add uninstall target [21dbfa1f](https://github.com/gohugoio/hugo/commit/21dbfa1f111ca2f066e06af68f267932ce6cf04f) [@felicianotech](https://github.com/felicianotech)
-* Update Chroma to 0.8.0 [e5591e89](https://github.com/gohugoio/hugo/commit/e5591e89d3a71560b70c5f0ded33f2c9465ffe5a) [@jmooring](https://github.com/jmooring) [#7517](https://github.com/gohugoio/hugo/issues/7517)
-* Update go-org to v1.3.1 [88929bc2](https://github.com/gohugoio/hugo/commit/88929bc23f5a830645c4e2cdac60aa43f480a478) [@niklasfasching](https://github.com/niklasfasching)
-* Collect transition attributes as classes [00e00da2](https://github.com/gohugoio/hugo/commit/00e00da233ab4d643de90bafca00f60ee0bbe785) [@bep](https://github.com/bep) [#7509](https://github.com/gohugoio/hugo/issues/7509)
-* Add option for setting bundle format [0256959a](https://github.com/gohugoio/hugo/commit/0256959a358bb26b983c9d9496862b0fdf387621) [@bep](https://github.com/bep) [#7503](https://github.com/gohugoio/hugo/issues/7503)
-* Simplify options handling [eded9ac2](https://github.com/gohugoio/hugo/commit/eded9ac2a05b9a7244c25c70ca8f761b69b33385) [@bep](https://github.com/bep) [#7499](https://github.com/gohugoio/hugo/issues/7499)
-* make sure documentation intro text only appears once [8d725128](https://github.com/gohugoio/hugo/commit/8d72512825b4cee12dc1952004f48fd076a3517b) [@TheHippo](https://github.com/TheHippo)
-* Add es5 build target [e81aef0a](https://github.com/gohugoio/hugo/commit/e81aef0a954623e4a19062d1534bd8c2af97102a) [@bep](https://github.com/bep)
-* esbuild v0.6.5 [9f919147](https://github.com/gohugoio/hugo/commit/9f9191471ec501f1f957020726f939c9ef48e193) [@bep](https://github.com/bep)
-* Add .Defines to js.Build options [35011bcb](https://github.com/gohugoio/hugo/commit/35011bcb26b6fcfcbd77dc05aa8246ca45b2c2ba) [@bep](https://github.com/bep) [#7489](https://github.com/gohugoio/hugo/issues/7489)
-
-## Fixes
-
-### Other
-
-* Fix AsciiDoc TOC with code [6a848cbc](https://github.com/gohugoio/hugo/commit/6a848cbc3a2487c8b015e715c2de44aef6051080) [@helfper](https://github.com/helfper) [#7649](https://github.com/gohugoio/hugo/issues/7649)
-* markup/asciidocext: Fix broken test [4949bdc2](https://github.com/gohugoio/hugo/commit/4949bdc2ef98a1aebe5536c554d214f15c574a81) [@bep](https://github.com/bep)
-* Fix some change detection issues on server reloads [4055c121](https://github.com/gohugoio/hugo/commit/4055c121847847d8bd6b95a928185daee065091b) [@bep](https://github.com/bep) [#7623](https://github.com/gohugoio/hugo/issues/7623)[#7624](https://github.com/gohugoio/hugo/issues/7624)[#7625](https://github.com/gohugoio/hugo/issues/7625)
-* Fixed misspelled words [ad01aea3](https://github.com/gohugoio/hugo/commit/ad01aea3f426206c2b70bbd97c5d29562dfe954d) [@aurkenb](https://github.com/aurkenb)
-* Fix a typo in CONTRIBUTING.md [f3cb0be3](https://github.com/gohugoio/hugo/commit/f3cb0be35adddfe43423a19116994b53817d97f7) [@capnfabs](https://github.com/capnfabs)
-* Revert "Fix ellipsis display logic in pagination template" [bffc4e12](https://github.com/gohugoio/hugo/commit/bffc4e12fe6d255e1fb8d28943993afc7e99e010) [@jmooring](https://github.com/jmooring)
-* Fix ellipsis display logic in pagination template [2fa851e6](https://github.com/gohugoio/hugo/commit/2fa851e6500752c0cea1da5cfdfc6d99e0a81a71) [@jmooring](https://github.com/jmooring) [#7523](https://github.com/gohugoio/hugo/issues/7523)
-* Fix Asciidoctor args [45c665d3](https://github.com/gohugoio/hugo/commit/45c665d396ed368261f4a63ceee753c7f6dc5bf9) [@helfper](https://github.com/helfper) [#7493](https://github.com/gohugoio/hugo/issues/7493)
-* Fix date format in internal schema template [a06c06a5](https://github.com/gohugoio/hugo/commit/a06c06a5c202de85ff47792b7468bfaeec2fea12) [@jmooring](https://github.com/jmooring) [#7495](https://github.com/gohugoio/hugo/issues/7495)
-* Fix baseof block regression [c91dbe4c](https://github.com/gohugoio/hugo/commit/c91dbe4ce9c30623ba6e686fd17efae935aa0cc5) [@bep](https://github.com/bep) [#7478](https://github.com/gohugoio/hugo/issues/7478)
-
-
-
-
-
diff --git a/docs/content/en/news/0.75.1-relnotes/index.md b/docs/content/en/news/0.75.1-relnotes/index.md
deleted file mode 100644
index 6da3d6c6567..00000000000
--- a/docs/content/en/news/0.75.1-relnotes/index.md
+++ /dev/null
@@ -1,21 +0,0 @@
-
----
-date: 2020-09-15
-title: "Hugo 0.75.1: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.75.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-* resources/image: Fix nilpointer for images with no Exif [cd00f7f9](https://github.com/gohugoio/hugo/commit/cd00f7f9661d67951ef16c5198541f09f1c058b4) [@bep](https://github.com/bep) [#7688](https://github.com/gohugoio/hugo/issues/7688)
-* modules/npm: Preserve the original package.json if it exists [214afe4c](https://github.com/gohugoio/hugo/commit/214afe4c1bb9c37bc6159e659d66ba9a268a2849) [@bep](https://github.com/bep) [#7690](https://github.com/gohugoio/hugo/issues/7690)
-* tpl: Fix grammar in the new 'requires non-zero' error message [cd830bb0](https://github.com/gohugoio/hugo/commit/cd830bb0275fc39240861627ef26e146985b5c86) [@nekr0z](https://github.com/nekr0z)
-
-
-
diff --git a/docs/content/en/news/0.76.0-relnotes/featured.png b/docs/content/en/news/0.76.0-relnotes/featured.png
deleted file mode 100644
index a2130ce8194..00000000000
Binary files a/docs/content/en/news/0.76.0-relnotes/featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.76.0-relnotes/index.md b/docs/content/en/news/0.76.0-relnotes/index.md
deleted file mode 100644
index 65f3ebb9dc4..00000000000
--- a/docs/content/en/news/0.76.0-relnotes/index.md
+++ /dev/null
@@ -1,111 +0,0 @@
-
----
-date: 2020-10-06
-title: "Multiple Cascades With Page Filters"
-description: "Hugo 0.76.0 brings multiple cascade blocks per page with filters for path, kind and language."
-categories: ["Releases"]
----
-
-In **Hugo 0.76.0** you can now have a list of [cascade](https://gohugo.io/content-management/front-matter#front-matter-cascade) blocks per page and a new `_target` keyword where you can select which pages to _cascade_ upon using [Glob](https://github.com/gobwas/glob) patterns for a `Page`'s `Kind`, `Lang` and/or `Path`:
-
-```toml
-title ="Blog"
-[[cascade]]
-background = "yosemite.jpg"
-[cascade._target]
-path="/blog/**"
-lang="en"
-kind="page"
-[[cascade]]
-background = "goldenbridge.jpg"
-[cascade._target]
-kind="section"
-```
-
-Tasks that were earlier hard/borderline impossible to do are now simple. One common example would to apply a different template set to nested sections; you can now apply a custom `Type` to these sections using `path="/blog/*/**"` and similar.
-
-A related improvement is that the [build option](https://gohugo.io/content-management/build-options/#readout) `render` is now an enum. In addition to turning on/off rendering of a given page you can tell Hugo to not render, but you want to preserve the `.Permalink`, useful for SPA applications.
-
-This release represents **35 contributions by 8 contributors** to the main Hugo code base. A big shoutout to [@bep](https://github.com/bep), [@ai](https://github.com/ai), and [@jmooring](https://github.com/jmooring) for their ongoing contributions.
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the themes site in pristine condition and to [@davidsneighbour](https://github.com/davidsneighbour), [@coliff](https://github.com/coliff) and [@kaushalmodi](https://github.com/kaushalmodi) for all the great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **11 contributions by 6 contributors**. A special thanks to [@amdw](https://github.com/amdw), [@davidsneighbour](https://github.com/davidsneighbour), [@samrobbins85](https://github.com/samrobbins85), and [@yaythomas](https://github.com/yaythomas) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 47025+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 438+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 354+ [themes](http://themes.gohugo.io/)
-
-## Notes
-
-
-We have added a `force` flag to the [server redirects](https://gohugo.io/getting-started/configuration/#configure-server) configuration, configuring whether to override any existing content in the path or not. This is inline with how [Netlify](https://docs.netlify.com/routing/redirects/#syntax-for-the-netlify-configuration-file) does it.
-
-This is set to default `false`. If you want the old behaviour you need to add this flag to your configuration:
-
-{{< code-toggle file="config" >}}
-[[redirects]]
-from = "/myspa/**"
-to = "/myspa/"
-status = 200
-force = true
-{{< /code-toggle >}}
-
-## Enhancements
-
-### Templates
-
-* Add Do Not Track (dnt) option to Vimeo shortcode [edc5c474](https://github.com/gohugoio/hugo/commit/edc5c4741caaee36ba4d42b5947c195a3e02e6aa) [@joshgerdes](https://github.com/joshgerdes) [#7700](https://github.com/gohugoio/hugo/issues/7700)
-
-### Other
-
-* Regen docshelper [b9318e43](https://github.com/gohugoio/hugo/commit/b9318e4315d9112f727140c0950d8836bf26eb87) [@bep](https://github.com/bep)
-* Make BuildConfig.Render an enum [63493890](https://github.com/gohugoio/hugo/commit/634938908ec8f393b9a05d26b4cfe19ca7abb0d0) [@bep](https://github.com/bep) [#7783](https://github.com/gohugoio/hugo/issues/7783)
-* Allow cascade to be a slice with a _target discriminator [c63db7f1](https://github.com/gohugoio/hugo/commit/c63db7f1f6774a2d661af1d8197c6fe377e3ad25) [@bep](https://github.com/bep) [#7782](https://github.com/gohugoio/hugo/issues/7782)
-* Add force flag to server redirects config [5e2a547c](https://github.com/gohugoio/hugo/commit/5e2a547cb594b31ecb0f089b08db2e15c6dc381a) [@bep](https://github.com/bep) [#7778](https://github.com/gohugoio/hugo/issues/7778)
-* bump github.com/evanw/esbuild from 0.7.8 to 0.7.9 [ee090c09](https://github.com/gohugoio/hugo/commit/ee090c0940cdbf636e3a55a40b41612d92b9c62d) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/tdewolff/minify/v2 from 2.9.5 to 2.9.7 [05e358fd](https://github.com/gohugoio/hugo/commit/05e358fd335bcb5c7bdc2783ab0c17ec42667df6) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/aws/aws-sdk-go from 1.34.34 to 1.35.0 [a2e85d9a](https://github.com/gohugoio/hugo/commit/a2e85d9a75aca59fd720cce6561ff64997858cd2) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/getkin/kin-openapi from 0.22.0 to 0.22.1 [4fba78dd](https://github.com/gohugoio/hugo/commit/4fba78dd0e950742132954a5d24629e4adfa1bb1) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/aws/aws-sdk-go from 1.34.33 to 1.34.34 [c011b466](https://github.com/gohugoio/hugo/commit/c011b4667f3e1e3c6ecea2fe8f251578884c53b6) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/evanw/esbuild from 0.7.7 to 0.7.8 [35348b4b](https://github.com/gohugoio/hugo/commit/35348b4b343600ec24b1eb1a06f4d3c59199df25) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/aws/aws-sdk-go from 1.34.27 to 1.34.33 [34915777](https://github.com/gohugoio/hugo/commit/34915777c2e8bc1457ff90d09cf814d494d9eece) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/evanw/esbuild from 0.7.4 to 0.7.7 [0f4a837e](https://github.com/gohugoio/hugo/commit/0f4a837ed1fd903bb6740b512683528ddb917918) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/tdewolff/minify/v2 from 2.9.4 to 2.9.5 [b395d686](https://github.com/gohugoio/hugo/commit/b395d686e9a77bf4e0d587ee9a3af4ae6e1aee02) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Upgrade to go-i18n v2 [97987e5c](https://github.com/gohugoio/hugo/commit/97987e5c0254e35668dca7f89e67b79553e617c8) [@bep](https://github.com/bep) [#5242](https://github.com/gohugoio/hugo/issues/5242)
-* bump github.com/evanw/esbuild from 0.7.2 to 0.7.4 [4855c186](https://github.com/gohugoio/hugo/commit/4855c186d8f05e5e1b0f681b4aa6482a033df241) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/aws/aws-sdk-go from 1.34.26 to 1.34.27 [6f07ec7e](https://github.com/gohugoio/hugo/commit/6f07ec7e9ec5c43f78100aa36b82786ba0260d75) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/alecthomas/chroma from 0.8.0 to 0.8.1 [4318dc72](https://github.com/gohugoio/hugo/commit/4318dc72f8c562b3bc106cd953d9fce58a93455d) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/evanw/esbuild from 0.7.1 to 0.7.2 [acdc27a3](https://github.com/gohugoio/hugo/commit/acdc27a32de83f32557e7a108797ddbebe4eb464) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Make sure CSS is rebuilt when postcss.config.js or tailwind.config.js changes [3acde9ae](https://github.com/gohugoio/hugo/commit/3acde9ae04fbf4a8c635d404608cb87218a8b803) [@bep](https://github.com/bep) [#7715](https://github.com/gohugoio/hugo/issues/7715)
-* bump github.com/aws/aws-sdk-go from 1.34.22 to 1.34.26 [0bce9770](https://github.com/gohugoio/hugo/commit/0bce97703c17318b13b95d78ba41f40efb06aea7) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Update to github.com/tdewolff/minify v2.9.4 [b254532b](https://github.com/gohugoio/hugo/commit/b254532b52785954c98a473a635b9cea016d8565) [@bep](https://github.com/bep)
-* Bump bundled Node.js from v12.18.3 to v12.18.4 [05a22892](https://github.com/gohugoio/hugo/commit/05a22892921bd4618efe6135ce0d6fe2be545607) [@anthonyfok](https://github.com/anthonyfok)
-* Add preserveTOC option [8e553dcd](https://github.com/gohugoio/hugo/commit/8e553dcdefe50ab534f1199c006ae7754e14bee5) [@helfper](https://github.com/helfper)
-* bump github.com/frankban/quicktest from 1.10.2 to 1.11.0 [d4fc70a3](https://github.com/gohugoio/hugo/commit/d4fc70a3b320a55c4f571eed806d5ad5fdf1ef14) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/evanw/esbuild from 0.6.32 to 0.7.1 [d905abc0](https://github.com/gohugoio/hugo/commit/d905abc002aa6fd260e82063ef1edb8876aa76fd) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/rogpeppe/go-internal from 1.5.1 to 1.6.2 [8f394674](https://github.com/gohugoio/hugo/commit/8f3946746dda444f183ba235288c2b39d0d6a943) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/jdkato/prose from 1.1.1 to 1.2.0 [b01b2564](https://github.com/gohugoio/hugo/commit/b01b2564eefe342c9bf9767ffc256ebd04b94c71) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/spf13/afero from 1.2.2 to 1.4.0 [9fa5ebe2](https://github.com/gohugoio/hugo/commit/9fa5ebe2c42fbb37d066ffcd36bad4d08efe879a) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Preserve the original package.json if it exists [214afe4c](https://github.com/gohugoio/hugo/commit/214afe4c1bb9c37bc6159e659d66ba9a268a2849) [@bep](https://github.com/bep) [#7690](https://github.com/gohugoio/hugo/issues/7690)
-
-## Fixes
-
-### Templates
-
-* Fix grammar in the new 'requires non-zero' error message [cd830bb0](https://github.com/gohugoio/hugo/commit/cd830bb0275fc39240861627ef26e146985b5c86) [@nekr0z](https://github.com/nekr0z)
-
-### Other
-
-* Fix writeStats with quote inside quotes [11134411](https://github.com/gohugoio/hugo/commit/111344113bf8c16ae45528d67ff408da15961727) [@bep](https://github.com/bep) [#7746](https://github.com/gohugoio/hugo/issues/7746)
-* Fix CLI example for PostCSS 8 [0c3d2b67](https://github.com/gohugoio/hugo/commit/0c3d2b67e0af38a4c3935fb04f722a73ec1d3f8b) [@ai](https://github.com/ai)
-* Fix typo in redirect error message [473b6610](https://github.com/gohugoio/hugo/commit/473b6610d51d4a33ba35917f95b0d97ea78dad2b) [@jmooring](https://github.com/jmooring)
-* Fix nilpointer for images with no Exif [cd00f7f9](https://github.com/gohugoio/hugo/commit/cd00f7f9661d67951ef16c5198541f09f1c058b4) [@bep](https://github.com/bep) [#7688](https://github.com/gohugoio/hugo/issues/7688)
-
-
-
-
-
diff --git a/docs/content/en/news/0.76.1-relnotes/index.md b/docs/content/en/news/0.76.1-relnotes/index.md
deleted file mode 100644
index b28b17960f3..00000000000
--- a/docs/content/en/news/0.76.1-relnotes/index.md
+++ /dev/null
@@ -1,17 +0,0 @@
-
----
-date: 2020-10-07
-title: "Hugo 0.76.1: One Bug Fix"
-description: "This version fixes one bug introduced in 0.76.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-With this, we should finally have proper [plural numerals in Russian etc.](https://discourse.gohugo.io/t/0-76-0-i18n-gone-wrong/28689/7?u=bep):
-
-* langs/i18n: Fix i18n .Count regression [f9e798e8](https://github.com/gohugoio/hugo/commit/f9e798e8c4234bd60277e3cb10663ba254d4ecb7) [@bep](https://github.com/bep) [#7787](https://github.com/gohugoio/hugo/issues/7787)
-
-
-
diff --git a/docs/content/en/news/0.76.2-relnotes/index.md b/docs/content/en/news/0.76.2-relnotes/index.md
deleted file mode 100644
index c071fdcac10..00000000000
--- a/docs/content/en/news/0.76.2-relnotes/index.md
+++ /dev/null
@@ -1,17 +0,0 @@
-
----
-date: 2020-10-07
-title: "Hugo 0.76.2: One Bug Fix"
-description: "This release reverts to an older (and working) version of the JS minifier."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-This is a bug-fix release with one important fix.
-
-* Revert "deps: Update to github.com/tdewolff/minify v2.9.4" [6dd60fca](https://github.com/gohugoio/hugo/commit/6dd60fca73ff96b48064bb8c6586631a2370ffc6) [@bep](https://github.com/bep) [#7792](https://github.com/gohugoio/hugo/issues/7792)
-
-
-
diff --git a/docs/content/en/news/0.76.3-relnotes/index.md b/docs/content/en/news/0.76.3-relnotes/index.md
deleted file mode 100644
index 1dc2d8b098b..00000000000
--- a/docs/content/en/news/0.76.3-relnotes/index.md
+++ /dev/null
@@ -1,20 +0,0 @@
-
----
-date: 2020-10-08
-title: "Hugo 0.76.3: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.76.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-* langs/i18n: Add workaround for known language, but missing plural rule error [33e9d79b](https://github.com/gohugoio/hugo/commit/33e9d79b78b32d0cc19693ab3c29ba9941d80f8f) [@bep](https://github.com/bep) [#7798](https://github.com/gohugoio/hugo/issues/7798)
-* langs/i18n: Fix for bare TOML keys [fc6abc39](https://github.com/gohugoio/hugo/commit/fc6abc39c75c152780151c35bc95b12bee01b09c) [@bep](https://github.com/bep)
-
-
-
diff --git a/docs/content/en/news/0.76.4-relnotes/index.md b/docs/content/en/news/0.76.4-relnotes/index.md
deleted file mode 100644
index cb7e8670cdd..00000000000
--- a/docs/content/en/news/0.76.4-relnotes/index.md
+++ /dev/null
@@ -1,22 +0,0 @@
-
----
-date: 2020-10-12
-title: "Hugo 0.76.4: One Bug Fix"
-description: "This version fixes one more i18n regression from 0.76.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-* snap: Install postcss v8 explicitly as it is now a peer dependency [e9a7ebaf](https://github.com/gohugoio/hugo/commit/e9a7ebaf67a63ffe5e64c3b3aaefe66feb7f1868) [@anthonyfok](https://github.com/anthonyfok)
-* lang/i18n: Fix for language code case issue with pt-br etc. [50682043](https://github.com/gohugoio/hugo/commit/506820435cacb39ce7bb1835f46a15e913b95828) [@bep](https://github.com/bep) [#7804](https://github.com/gohugoio/hugo/issues/7804)
-* Merge branch 'release-0.76.3' [49972d07](https://github.com/gohugoio/hugo/commit/49972d07925604fea45afe1ace7b5dcc6efc30bf) [@bep](https://github.com/bep)
-* Add merge helper [c98132e3](https://github.com/gohugoio/hugo/commit/c98132e30e01a9638e61bd888c769d30e4e43ad5) [@bep](https://github.com/bep)
-
-
-
diff --git a/docs/content/en/news/0.76.5-relnotes/index.md b/docs/content/en/news/0.76.5-relnotes/index.md
deleted file mode 100644
index 063cbbaa9cd..00000000000
--- a/docs/content/en/news/0.76.5-relnotes/index.md
+++ /dev/null
@@ -1,19 +0,0 @@
-
----
-date: 2020-10-14
-title: "Hugo 0.76.5: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.76.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with one important fix.
-
-* Render aliases even if render=link [79a022a1](https://github.com/gohugoio/hugo/commit/79a022a15c5f39b8ae87a94665f14bf1797b605c) [@bep](https://github.com/bep) [#7832](https://github.com/gohugoio/hugo/issues/7832)
-
-
-
diff --git a/docs/content/en/news/0.77.0-relnotes/featured.png b/docs/content/en/news/0.77.0-relnotes/featured.png
deleted file mode 100644
index 4245479a524..00000000000
Binary files a/docs/content/en/news/0.77.0-relnotes/featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.77.0-relnotes/index.md b/docs/content/en/news/0.77.0-relnotes/index.md
deleted file mode 100644
index c9db7ef99e2..00000000000
--- a/docs/content/en/news/0.77.0-relnotes/index.md
+++ /dev/null
@@ -1,90 +0,0 @@
-
----
-date: 2020-10-30
-title: "Hugo 0.77.0: Hugo Modules Improvements and More "
-description: "New Replacements config option for simpler development workflows, ignore errors from getJSON, localized dates, and more."
-categories: ["Releases"]
----
-
-Hugo `0.77.0` is a small, but useful release. Some notable updates are:
-
-* **time.AsTime** accepts an optional location as second parameter, allowing timezone aware printing of dates.
-* You can now build with `go install -tags nodeploy` if you don't need the **`hugo deploy`** feature.
-* Remote **`getJSON`** errors can now be ignored by adding `ignoreErrors = ["error-remote-getjson"]` to your site config.
-
-There are also several useful **[Hugo Modules](https://gohugo.io/hugo-modules/)** enhancements:
-
-* We have added `Replacements` to the [Module Configuration](https://gohugo.io/hugo-modules/configuration/#module-config-top-level). This should enable a much simpler developer workflow, simpler to set up preview sites for your remote theme etc, as you now can do `env HUGO_MODULE_REPLACEMENTS="github.com/bep/myprettytheme -> ../.." hugo` and similar.
-* The module `Path` for local modules can now be absolute for imports defined in the project.
-
-This release represents **38 contributions by 11 contributors** to the main Hugo code base. [@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@moorereason](https://github.com/moorereason), and [@anthonyfok](https://github.com/anthonyfok) for their ongoing contributions.
-
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the themes site in pristine condition and to [@davidsneighbour](https://github.com/davidsneighbour), [@coliff](https://github.com/coliff) and [@kaushalmodi](https://github.com/kaushalmodi) for all the great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **3 contributions by 3 contributors**.
-
-Hugo now has:
-
-* 47530+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 438+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 361+ [themes](http://themes.gohugo.io/)
-
-## Enhancements
-
-### Templates
-
-* Refactor time.AsTime location implementation [807db97a](https://github.com/gohugoio/hugo/commit/807db97af83ff61b022cbc8af80b9dc9cdb8dd43) [@moorereason](https://github.com/moorereason)
-* Update Hugo time to support optional [LOCATION] parameter [26eeb291](https://github.com/gohugoio/hugo/commit/26eeb2914720929d2d778f14d6a4bf737014e9e3) [@virgofx](https://github.com/virgofx)
-* Improve layout path construction [acfa1538](https://github.com/gohugoio/hugo/commit/acfa153863d6ff2acf17ffb4395e05d102229905) [@moorereason](https://github.com/moorereason)
-* Test all lookup permutations in TestLayout [78b26d53](https://github.com/gohugoio/hugo/commit/78b26d538c716d463b30c23de7df5eaa4d5504fd) [@moorereason](https://github.com/moorereason)
-* Reformat TestLayout table [28179bd5](https://github.com/gohugoio/hugo/commit/28179bd55619847f46ca0ffd316ef52fc9c96f1e) [@moorereason](https://github.com/moorereason)
-
-### Other
-
-* Allow absolute paths for project imports [beabc8d9](https://github.com/gohugoio/hugo/commit/beabc8d998249ecc5dd522d696dc6233a29131c2) [@bep](https://github.com/bep) [#7910](https://github.com/gohugoio/hugo/issues/7910)
-* Regen docs helper [332b65e4](https://github.com/gohugoio/hugo/commit/332b65e4ccb6ac0d606de2a1b23f5189c72542be) [@bep](https://github.com/bep)
-* Add module.replacements [173187e2](https://github.com/gohugoio/hugo/commit/173187e2633f3fc037c83e1e3de2902ae3c93b92) [@bep](https://github.com/bep) [#7904](https://github.com/gohugoio/hugo/issues/7904)[#7908](https://github.com/gohugoio/hugo/issues/7908)
-* Do not call CDN service invalidation when executing a dry run deployment [56a34350](https://github.com/gohugoio/hugo/commit/56a343507ca28254edb891bc1c21b6c8ca017982) [@zemanel](https://github.com/zemanel) [#7884](https://github.com/gohugoio/hugo/issues/7884)
-* Pass editor arguments from newContentEditor correctly [d48a98c4](https://github.com/gohugoio/hugo/commit/d48a98c477a818d28008d9771050d2681e63e880) [@bhavin192](https://github.com/bhavin192)
-* Bump github.com/spf13/cobra from 0.0.7 to 1.1.1 [3261678f](https://github.com/gohugoio/hugo/commit/3261678f63fd66810db77ccaf9a0c0e426be5380) [@anthonyfok](https://github.com/anthonyfok)
-* Allow optional "nodeploy" tag to exclude deploy command from bin [f465c5c3](https://github.com/gohugoio/hugo/commit/f465c5c3079261eb7fa513e2d2793851b9c52b83) [@emhagman](https://github.com/emhagman) [#7826](https://github.com/gohugoio/hugo/issues/7826)
-* Allow cascade _target to work with non toml fm [3400aff2](https://github.com/gohugoio/hugo/commit/3400aff2588cbf9dd4629c05537d16b019d0fdf5) [@gwatts](https://github.com/gwatts) [#7874](https://github.com/gohugoio/hugo/issues/7874)
-* Allow getJSON errors to be ignored [fdfa4a5f](https://github.com/gohugoio/hugo/commit/fdfa4a5fe62232f65f1dd8d6fe0c500374228788) [@bep](https://github.com/bep) [#7866](https://github.com/gohugoio/hugo/issues/7866)
-* bump github.com/evanw/esbuild from 0.7.15 to 0.7.18 [8cbe2bbf](https://github.com/gohugoio/hugo/commit/8cbe2bbfad6aa4de267921e24e166d4addf47040) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Revert "Add benchmark for building docs site" [b886fa46](https://github.com/gohugoio/hugo/commit/b886fa46bb92916152476cfac45c7a5ee5e5820a) [@bep](https://github.com/bep)
-* Avoid making unnecessary allocation [14bce18a](https://github.com/gohugoio/hugo/commit/14bce18a6c5aca8cb3e70a74d5045ca8b2358fee) [@moorereason](https://github.com/moorereason)
-* Add benchmark for building docs site [837e084b](https://github.com/gohugoio/hugo/commit/837e084bbe53e9e2e6cd471d2a3daf273a874d92) [@moorereason](https://github.com/moorereason)
-* Always show page number when 5 pages or less [08e4f9ff](https://github.com/gohugoio/hugo/commit/08e4f9ff9cc448d5fea9b8a62a23aed8aad0d047) [@moorereason](https://github.com/moorereason) [#7523](https://github.com/gohugoio/hugo/issues/7523)
-* bump github.com/frankban/quicktest from 1.11.0 to 1.11.1 [f033d9f0](https://github.com/gohugoio/hugo/commit/f033d9f01d13d8cd08205ccfaa09919ed15dca77) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/evanw/esbuild from 0.7.14 to 0.7.15 [59fe2794](https://github.com/gohugoio/hugo/commit/59fe279424c66ac6a89cafee01a5b2e34dbcc1fb) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Merge branch 'release-0.76.5' [62119022](https://github.com/gohugoio/hugo/commit/62119022d1be41e423ef3bcf467a671ce6c4f7dd) [@bep](https://github.com/bep)
-* Render aliases even if render=link [79a022a1](https://github.com/gohugoio/hugo/commit/79a022a15c5f39b8ae87a94665f14bf1797b605c) [@bep](https://github.com/bep) [#7832](https://github.com/gohugoio/hugo/issues/7832)
-* Render aliases even if render=link [ead5799f](https://github.com/gohugoio/hugo/commit/ead5799f7ea837fb2ca1879a6d37ba364e53827f) [@bep](https://github.com/bep) [#7832](https://github.com/gohugoio/hugo/issues/7832)
-* bump github.com/spf13/afero from 1.4.0 to 1.4.1 [d57be113](https://github.com/gohugoio/hugo/commit/d57be113243be4b76310d4476fbb7525d1452658) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/evanw/esbuild from 0.7.9 to 0.7.14 [d0705966](https://github.com/gohugoio/hugo/commit/d070596694a3edbf42fc315bb326505aa39fce90) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Update to Go 1.15 and Alpine 3.12 [f5ea359d](https://github.com/gohugoio/hugo/commit/f5ea359dd34bf59a2944f1d9667838202af13c93) [@ducksecops](https://github.com/ducksecops)
-* Install postcss v8 explicitly as it is now a peer dependency [e9a7ebaf](https://github.com/gohugoio/hugo/commit/e9a7ebaf67a63ffe5e64c3b3aaefe66feb7f1868) [@anthonyfok](https://github.com/anthonyfok)
-* Merge branch 'release-0.76.3' [49972d07](https://github.com/gohugoio/hugo/commit/49972d07925604fea45afe1ace7b5dcc6efc30bf) [@bep](https://github.com/bep)
-* Add merge helper [c98132e3](https://github.com/gohugoio/hugo/commit/c98132e30e01a9638e61bd888c769d30e4e43ad5) [@bep](https://github.com/bep)
-* Add workaround for known language, but missing plural rule error [33e9d79b](https://github.com/gohugoio/hugo/commit/33e9d79b78b32d0cc19693ab3c29ba9941d80f8f) [@bep](https://github.com/bep) [#7798](https://github.com/gohugoio/hugo/issues/7798)
-* Update to github.com/tdewolff/minify v2.9.4" [6dd60fca](https://github.com/gohugoio/hugo/commit/6dd60fca73ff96b48064bb8c6586631a2370ffc6) [@bep](https://github.com/bep) [#7792](https://github.com/gohugoio/hugo/issues/7792)
-
-## Fixes
-
-### Templates
-
-* Fix reflection bug in merge [6d95dc9d](https://github.com/gohugoio/hugo/commit/6d95dc9d74681cba53b46e79c6e1d58d27fcdfb0) [@moorereason](https://github.com/moorereason) [#7899](https://github.com/gohugoio/hugo/issues/7899)
-
-### Other
-
-* Fix setting HUGO_MODULE_PROXY etc. via env vars [8a1c637c](https://github.com/gohugoio/hugo/commit/8a1c637c4494751046142e0ef345fce38fc1431b) [@bep](https://github.com/bep) [#7903](https://github.com/gohugoio/hugo/issues/7903)
-* Fix for language code case issue with pt-br etc. [50682043](https://github.com/gohugoio/hugo/commit/506820435cacb39ce7bb1835f46a15e913b95828) [@bep](https://github.com/bep) [#7804](https://github.com/gohugoio/hugo/issues/7804)
-* Fix for bare TOML keys [fc6abc39](https://github.com/gohugoio/hugo/commit/fc6abc39c75c152780151c35bc95b12bee01b09c) [@bep](https://github.com/bep)
-* Fix i18n .Count regression [f9e798e8](https://github.com/gohugoio/hugo/commit/f9e798e8c4234bd60277e3cb10663ba254d4ecb7) [@bep](https://github.com/bep) [#7787](https://github.com/gohugoio/hugo/issues/7787)
-* Fix typo in 0.76.0 release note [ee56efff](https://github.com/gohugoio/hugo/commit/ee56efffcb3f81120b0d3e0297b4fb5966124354) [@digitalcraftsman](https://github.com/digitalcraftsman)
-
-
-
-
-
diff --git a/docs/content/en/news/0.78.0-relnotes/featured.png b/docs/content/en/news/0.78.0-relnotes/featured.png
deleted file mode 100644
index 36ae0ac95bb..00000000000
Binary files a/docs/content/en/news/0.78.0-relnotes/featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.78.0-relnotes/index.md b/docs/content/en/news/0.78.0-relnotes/index.md
deleted file mode 100644
index 25b0fd4d832..00000000000
--- a/docs/content/en/news/0.78.0-relnotes/index.md
+++ /dev/null
@@ -1,50 +0,0 @@
-
----
-date: 2020-11-03
-title: "Hugo 0.78.0: Full Hugo Modules Support in js.Build"
-description: "Resolve JavaScript imports top-down in the layered filesystem, pass parameters from template to JS, new JS intellisense helper, improved JS build errors."
-categories: ["Releases"]
----
-
-This release finally brings full [Hugo Modules](https://gohugo.io/hugo-modules/) support to [js.Build](https://gohugo.io/hugo-pipes/js/), curtsy of he new plugin API in the really, really fast [ESBuild](https://github.com/evanw/esbuild) by [@evanw](https://github.com/evanw).
-
-Some notes on the improvements in this release:
-
-* Now `js.Build` fully supports the virtual union filesystem in [Hugo Modules](https://gohugo.io/hugo-modules/). Any import inside your JavaScript components will resolve starting from the top component mount inside `/assets` with a fallback to the traditional "JS way" (`node_modules` etc.)
-* You can now pass configuration data from the templates to your scripts via a new `params` option.
-* Hugo now writes a `jsconfig.json` file inside `/assets` (you can turn it off) with import mappings to help editors such as VS Code with intellisense/navigation, which is especially useful when there is no common root and the source lives inside some temporary directory.
-* We have also improved the build errors you get from `js.Build`. In server mode you will get a preview of the failing lines and in the console you will get a link to the location.
-
-Read more about this in [the documentation](https://gohugo.io/hugo-pipes/js/), but a short usage example would look like:
-
-In the template:
-
-```go-html-template
-{{ $js := resources.Get "js/main.js" | js.Build (dict "params" (dict "api" "https://example.org/api" ) }}
-```
-
-And then in a JavaScript component:
-
-```js
-import * as params from '@params';
-
-// Will resolve to one of `hello.{js,ts,tsx,jsx}` inside `assets/my/module`.
-import { hello } from 'my/module/hello';
-
-var api = params.api;
-
-hello();
-
-```
-
-## Changes
-
-* Add avoidTDZ option [3b2fe3cd](https://github.com/gohugoio/hugo/commit/3b2fe3cd33b74166c3debec9826826f2b5a54fd9) [@bep](https://github.com/bep) [#7865](https://github.com/gohugoio/hugo/issues/7865)
-* Make js.Build fully support modules [85e4dd73](https://github.com/gohugoio/hugo/commit/85e4dd7370eae97ae367e596aa6a10ba42fd4b7c) [@bep](https://github.com/bep) [#7816](https://github.com/gohugoio/hugo/issues/7816)[#7777](https://github.com/gohugoio/hugo/issues/7777)[#7916](https://github.com/gohugoio/hugo/issues/7916)
-* Generate tsconfig files [3089fc0b](https://github.com/gohugoio/hugo/commit/3089fc0ba171be14670b19439bc2eab6b077b6c3) [@richtera](https://github.com/richtera) [#7777](https://github.com/gohugoio/hugo/issues/7777)
-
-
-
-
-
-
diff --git a/docs/content/en/news/0.78.1-relnotes/index.md b/docs/content/en/news/0.78.1-relnotes/index.md
deleted file mode 100644
index 168c1bbcdd8..00000000000
--- a/docs/content/en/news/0.78.1-relnotes/index.md
+++ /dev/null
@@ -1,20 +0,0 @@
-
----
-date: 2020-11-05
-title: "Hugo 0.78.1: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.78.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-The main fix in this release is that of dependency resolution for package.json/node_modules in theme components. See [the documentation](https://gohugo.io/hugo-pipes/js/#include-dependencies-in-packagejson--node_modules) for more information.
-
-* Disable NPM test on Travis on Windows [3437174c](https://github.com/gohugoio/hugo/commit/3437174c3a7b96925b82b351ac87530b4fa796a5) [@bep](https://github.com/bep)
-* travis: Install nodejs on Windows [f66302ca](https://github.com/gohugoio/hugo/commit/f66302ca0579171ffd1730eb8f33dd05af3d9a00) [@bep](https://github.com/bep)
-* js: Remove external source map option [944150ba](https://github.com/gohugoio/hugo/commit/944150bafbbb5c3e807ba3688174e70764dbdc64) [@bep](https://github.com/bep) [#7932](https://github.com/gohugoio/hugo/issues/7932)
-* js: Misc fixes [bf2837a3](https://github.com/gohugoio/hugo/commit/bf2837a314eaf70135791984a423b0b09f58741d) [@bep](https://github.com/bep) [#7924](https://github.com/gohugoio/hugo/issues/7924)[#7923](https://github.com/gohugoio/hugo/issues/7923)
-
-
-
diff --git a/docs/content/en/news/0.78.2-relnotes/index.md b/docs/content/en/news/0.78.2-relnotes/index.md
deleted file mode 100644
index 4714373e989..00000000000
--- a/docs/content/en/news/0.78.2-relnotes/index.md
+++ /dev/null
@@ -1,28 +0,0 @@
-
----
-date: 2020-11-13
-title: "Hugo 0.78.2: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.78.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-* js: Let ESBuild handle all imports from node_modules [78f227b6](https://github.com/gohugoio/hugo/commit/78f227b664d86c30fbb25f7a953b7ef8f2dacf13) [@bep](https://github.com/bep) [#7948](https://github.com/gohugoio/hugo/issues/7948)
-* build(deps): bump github.com/evanw/esbuild from 0.8.5 to 0.8.6 [5e03f644](https://github.com/gohugoio/hugo/commit/5e03f644a4507f51bdbcdb42b65ce4e99095374f) [@dependabot[bot]](https://github.com/apps/dependabot)
-* build(deps): bump github.com/evanw/esbuild from 0.8.4 to 0.8.5 [a92ef20f](https://github.com/gohugoio/hugo/commit/a92ef20ff6e43ba05844539b60782e8190712cdc) [@dependabot[bot]](https://github.com/apps/dependabot)
-* build(deps): bump github.com/getkin/kin-openapi from 0.22.1 to 0.26.0 [0d54a844](https://github.com/gohugoio/hugo/commit/0d54a844061e808dd5b4ff4874b2e4bd9df4d556) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Update GH docs to say "main" as default branch [943f3c93](https://github.com/gohugoio/hugo/commit/943f3c932f5f67ab52bf8e0636e57751dc9b1891) [@maco](https://github.com/maco)
-* Updated year in header [4f20bf29](https://github.com/gohugoio/hugo/commit/4f20bf29eb246a2e65508175fdd5f25b44e98370) [@AdamKorcz](https://github.com/AdamKorcz)
-* Added first fuzzer [4c613d5d](https://github.com/gohugoio/hugo/commit/4c613d5d5d60b80a262e968ae8a4525eba8619a2) [@AdamKorcz](https://github.com/AdamKorcz)
-* build(deps): bump github.com/frankban/quicktest from 1.11.1 to 1.11.2 [82a182e5](https://github.com/gohugoio/hugo/commit/82a182e52c4165b4f51d0cc8ef0f21df5d628c69) [@dependabot[bot]](https://github.com/apps/dependabot)
-* build(deps): bump golang.org/x/text from 0.3.3 to 0.3.4 [dfc662b2](https://github.com/gohugoio/hugo/commit/dfc662b2086430dde96c18ccb6b92bba4f1be428) [@dependabot[bot]](https://github.com/apps/dependabot)
-* build(deps): bump github.com/evanw/esbuild from 0.8.3 to 0.8.4 [2f0917cc](https://github.com/gohugoio/hugo/commit/2f0917cc014557e201a9348664736d608a7fa131) [@dependabot[bot]](https://github.com/apps/dependabot)
-
-
-
diff --git a/docs/content/en/news/0.79.0-relnotes/featured.png b/docs/content/en/news/0.79.0-relnotes/featured.png
deleted file mode 100644
index f1b7686da76..00000000000
Binary files a/docs/content/en/news/0.79.0-relnotes/featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.79.0-relnotes/index.md b/docs/content/en/news/0.79.0-relnotes/index.md
deleted file mode 100644
index 23ed1ef2efb..00000000000
--- a/docs/content/en/news/0.79.0-relnotes/index.md
+++ /dev/null
@@ -1,71 +0,0 @@
-
----
-date: 2020-11-27
-title: "Hugo 0.79.0: Black Friday Edition"
-description: "Hugo 0.79.0 brings .Params to menus, snake_case support for OS environment config, and a refresh of upstream dependencies (Chroma, ESBuild etc.)."
-categories: ["Releases"]
----
-
-Hugo `0.79.0` is a small, but useful release. You can now set custom `.Params` in your [menu](https://gohugo.io/content-management/menus/) configuration, and you can now also override deeply nested snake_cased configuration variables with [OS environment variables](https://gohugo.io/getting-started/configuration/#configure-with-environment-variables). Other than that we have refreshed all the core upstream dependencies. A special thanks to [@alecthomas](https://github.com/alecthomas) (some new [Chroma lexers](https://github.com/alecthomas/chroma/releases/tag/v0.8.2) and fixes) and [@evanw](https://github.com/evanw) ([ESBuild](https://github.com/evanw/esbuild)).
-
-This release represents **33 contributions by 8 contributors** to the main Hugo code base. [@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout [@AdamKorcz](https://github.com/AdamKorcz), and [@davidejones](https://github.com/davidejones) for their ongoing contributions. And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the themes site in pristine condition and to [@davidsneighbour](https://github.com/davidsneighbour), [@coliff](https://github.com/coliff) and [@kaushalmodi](https://github.com/kaushalmodi) for all the great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **13 contributions by 11 contributors**. A special thanks to [@Valac01](https://github.com/Valac01), [@bep](https://github.com/bep), [@mhansen](https://github.com/mhansen), and [@chanjarster](https://github.com/chanjarster) for their work on the documentation site.
-
-Hugo now has:
-
-* 48392+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 437+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 361+ [themes](http://themes.gohugo.io/)
-
-## Enhancements
-
-### Templates
-
-* Add more layout lookup tests [34061706](https://github.com/gohugoio/hugo/commit/34061706e6a9631d92ae3d01e0458eee7bc251cc) [@moorereason](https://github.com/moorereason) [#7964](https://github.com/gohugoio/hugo/issues/7964)
-
-### Other
-
-* bump gopkg.in/yaml.v2 from 2.3.0 to 2.4.0 [17e0bbe8](https://github.com/gohugoio/hugo/commit/17e0bbe821b508cea936bcfd5c1c181bdb8ad70d) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Allow setting the delimiter used for setting config via OS env, e.g. HUGO_ [7e223b3b](https://github.com/gohugoio/hugo/commit/7e223b3baaef68d6e6f99e28f162362c81deffba) [@bep](https://github.com/bep) [#7829](https://github.com/gohugoio/hugo/issues/7829)
-* Update to github.com/evanw/esbuild 0.8.11 to 0.8.14 [8a6e7060](https://github.com/gohugoio/hugo/commit/8a6e70605350255920100c5c085bb9ea6576d972) [@bep](https://github.com/bep) [#7986](https://github.com/gohugoio/hugo/issues/7986)
-* bump github.com/google/go-cmp from 0.5.2 to 0.5.3 [6f7633df](https://github.com/gohugoio/hugo/commit/6f7633df7d2c06e32eac628f9c7809dfee75eeed) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Remove unneeded meta tag from blog example [a546059a](https://github.com/gohugoio/hugo/commit/a546059a9c0b4541f6c9e292f2bb065c1b6115d9) [@coliff](https://github.com/coliff)
-* bump github.com/getkin/kin-openapi from 0.30.0 to 0.31.0 [b5d906e3](https://github.com/gohugoio/hugo/commit/b5d906e31e716328e2c0fbbdbfe6fc5b2ff98886) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Regen docshelper [fd70bdaf](https://github.com/gohugoio/hugo/commit/fd70bdafe7dc5d18c9a2f22c49acc3a8de376e8e) [@bep](https://github.com/bep)
-* Add menu params [8f5c9a74](https://github.com/gohugoio/hugo/commit/8f5c9a747fcebb02bb99f5de272046411eb15370) [@davidejones](https://github.com/davidejones) [#7951](https://github.com/gohugoio/hugo/issues/7951)
-* Preserve url set in frontmatter without sanitizing [e4fcb672](https://github.com/gohugoio/hugo/commit/e4fcb672ed8bae21fd9780292b54fea3040dd877) [@satotake](https://github.com/satotake) [#6007](https://github.com/gohugoio/hugo/issues/6007)
-* Add file deleted by accident [18c13adc](https://github.com/gohugoio/hugo/commit/18c13adcd46bdff963311fdba9eaa9b5a299106e) [@bep](https://github.com/bep) [#7972](https://github.com/gohugoio/hugo/issues/7972)
-* Regenerate docshelper" [20a35374](https://github.com/gohugoio/hugo/commit/20a35374a3c90adb32a90a5f671afb15165210be) [@bep](https://github.com/bep) [#7972](https://github.com/gohugoio/hugo/issues/7972)
-* Regenerate docshelper [caf16c20](https://github.com/gohugoio/hugo/commit/caf16c20853947138883f6460682e19733895f52) [@bep](https://github.com/bep)
-* Update to Chroma v0.8.2 [b298c06e](https://github.com/gohugoio/hugo/commit/b298c06e0551e3eba10b39ae5668b7a6a36a08a7) [@bep](https://github.com/bep) [#7970](https://github.com/gohugoio/hugo/issues/7970)
-* bump github.com/evanw/esbuild from 0.8.8 to 0.8.11 [55e290af](https://github.com/gohugoio/hugo/commit/55e290af41ad1c92af13679d4a84d64985d41456) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/getkin/kin-openapi from 0.26.0 to 0.30.0 [506a190a](https://github.com/gohugoio/hugo/commit/506a190a82cc5564012a1228b4179637b64e58eb) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/evanw/esbuild from 0.8.6 to 0.8.8 [fc81de64](https://github.com/gohugoio/hugo/commit/fc81de643934e84bb1e1392f6200559ee0ada9b6) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Let ESBuild handle all imports from node_modules [78f227b6](https://github.com/gohugoio/hugo/commit/78f227b664d86c30fbb25f7a953b7ef8f2dacf13) [@bep](https://github.com/bep) [#7948](https://github.com/gohugoio/hugo/issues/7948)
-* bump github.com/evanw/esbuild from 0.8.5 to 0.8.6 [5e03f644](https://github.com/gohugoio/hugo/commit/5e03f644a4507f51bdbcdb42b65ce4e99095374f) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/evanw/esbuild from 0.8.4 to 0.8.5 [a92ef20f](https://github.com/gohugoio/hugo/commit/a92ef20ff6e43ba05844539b60782e8190712cdc) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/getkin/kin-openapi from 0.22.1 to 0.26.0 [0d54a844](https://github.com/gohugoio/hugo/commit/0d54a844061e808dd5b4ff4874b2e4bd9df4d556) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Update GH docs to say "main" as default branch [943f3c93](https://github.com/gohugoio/hugo/commit/943f3c932f5f67ab52bf8e0636e57751dc9b1891) [@maco](https://github.com/maco)
-* Updated year in header [4f20bf29](https://github.com/gohugoio/hugo/commit/4f20bf29eb246a2e65508175fdd5f25b44e98370) [@AdamKorcz](https://github.com/AdamKorcz)
-* Added first fuzzer [4c613d5d](https://github.com/gohugoio/hugo/commit/4c613d5d5d60b80a262e968ae8a4525eba8619a2) [@AdamKorcz](https://github.com/AdamKorcz)
-* bump github.com/frankban/quicktest from 1.11.1 to 1.11.2 [82a182e5](https://github.com/gohugoio/hugo/commit/82a182e52c4165b4f51d0cc8ef0f21df5d628c69) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump golang.org/x/text from 0.3.3 to 0.3.4 [dfc662b2](https://github.com/gohugoio/hugo/commit/dfc662b2086430dde96c18ccb6b92bba4f1be428) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/evanw/esbuild from 0.8.3 to 0.8.4 [2f0917cc](https://github.com/gohugoio/hugo/commit/2f0917cc014557e201a9348664736d608a7fa131) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Disable NPM test on Travis on Windows [3437174c](https://github.com/gohugoio/hugo/commit/3437174c3a7b96925b82b351ac87530b4fa796a5) [@bep](https://github.com/bep)
-* Install nodejs on Windows [f66302ca](https://github.com/gohugoio/hugo/commit/f66302ca0579171ffd1730eb8f33dd05af3d9a00) [@bep](https://github.com/bep)
-* Remove external source map option [944150ba](https://github.com/gohugoio/hugo/commit/944150bafbbb5c3e807ba3688174e70764dbdc64) [@bep](https://github.com/bep) [#7932](https://github.com/gohugoio/hugo/issues/7932)
-
-## Fixes
-
-### Other
-
-* Fix memory usage in writeStats [d162bbd7](https://github.com/gohugoio/hugo/commit/d162bbd7990b6a523bdadcd10bf60fcb43ecf270) [@bep](https://github.com/bep) [#7945](https://github.com/gohugoio/hugo/issues/7945)
-* Fix server rebuild issue with partials referenced from render hooks [e442cf30](https://github.com/gohugoio/hugo/commit/e442cf30a215e33b49ce588a9098147282bd883f) [@bep](https://github.com/bep) [#7990](https://github.com/gohugoio/hugo/issues/7990)
-* Misc fixes [bf2837a3](https://github.com/gohugoio/hugo/commit/bf2837a314eaf70135791984a423b0b09f58741d) [@bep](https://github.com/bep) [#7924](https://github.com/gohugoio/hugo/issues/7924)[#7923](https://github.com/gohugoio/hugo/issues/7923)
-
-
-
-
-
diff --git a/docs/content/en/news/0.79.1-relnotes/index.md b/docs/content/en/news/0.79.1-relnotes/index.md
deleted file mode 100644
index 2a3f32765d0..00000000000
--- a/docs/content/en/news/0.79.1-relnotes/index.md
+++ /dev/null
@@ -1,22 +0,0 @@
-
----
-date: 2020-12-19
-title: "Hugo 0.79.1: One Security Patch for Hugo on Windows"
-description: "Disallow running of e.g. Pandoc in the current directory."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-Hugo depends on Go's `os/exec` for certain features, e.g. for rendering of Pandoc documents if these binaries are found in the system `%PATH%` on Windows. However, if a malicious file with the same name (`exe` or `bat`) was found in the current working directory at the time of running `hugo`, the malicious command would be invoked instead of the system one.
-
-Windows users who ran `hugo` inside untrusted Hugo sites were affected.
-
-The origin of this issue comes from Go, see https://github.com/golang/go/issues/38736
-
-We have fixed this in Hugo by [using](https://github.com/gohugoio/hugo/commit/4a8267d64a40564aced0695bca05249da17b0eab) a patched version of `exec.LookPath` from https://github.com/cli/safeexec (thanks to [@mislav](https://github.com/mislav) for the implementation).
-
-Thanks to [@Ry0taK](https://github.com/Ry0taK) for the bug report.
-
-
diff --git a/docs/content/en/news/0.8-relnotes/index.md b/docs/content/en/news/0.8-relnotes/index.md
deleted file mode 100644
index 6da6b9671de..00000000000
--- a/docs/content/en/news/0.8-relnotes/index.md
+++ /dev/null
@@ -1,21 +0,0 @@
----
-date: 2013-08-03T15:26:04Z
-description: "Hugo 0.8 added support for pretty URLs, support for TOML and YAML, better Windows compatibility, and more!"
-title: "Hugo 0.8"
-categories: ["Releases"]
----
-
-**Please read the docs as a few options have changed in this release:**
-
-- Added support for pretty URLs (filename/index.html vs filename.html)
-- Hugo supports a destination directory
-- Will efficiently sync content in static to destination directory
-- Cleaned up options.. now with support for short and long options
-- Added support for TOML
-- Added support for YAML
-- Added support for Previous & Next
-- Added support for indexes for the indexes
-- Better Windows compatibility
-- Support for series
-- Adding verbose output
-- Loads of bugfixes
diff --git a/docs/content/en/news/0.80.0-relnotes/featured.png b/docs/content/en/news/0.80.0-relnotes/featured.png
deleted file mode 100644
index 09308b04c4a..00000000000
Binary files a/docs/content/en/news/0.80.0-relnotes/featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.80.0-relnotes/index.md b/docs/content/en/news/0.80.0-relnotes/index.md
deleted file mode 100644
index 1c390b6858d..00000000000
--- a/docs/content/en/news/0.80.0-relnotes/index.md
+++ /dev/null
@@ -1,79 +0,0 @@
-
----
-date: 2020-12-31
-title: "Hugo 0.80: Last Release of 2020!"
-description: "This release brings Dart Sass support, a new image overlay function, and more."
-categories: ["Releases"]
----
-
-The last Hugo release of the year brings a new [images.Overlay](https://gohugo.io/functions/images/#overlay) filter to overlay an image on top of another, e.g. for watermarking, and [Dart Sass](https://gohugo.io/hugo-pipes/scss-sass/#options) support.
-
-This release represents **29 contributions by 12 contributors** to the main Hugo code base. [@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@moorereason](https://github.com/moorereason), and [@davidsneighbour](https://github.com/davidsneighbour) for their ongoing contributions.
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the themes site in pristine condition and to [@davidsneighbour](https://github.com/davidsneighbour), [@coliff](https://github.com/coliff) and [@kaushalmodi](https://github.com/kaushalmodi) for all the great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **22 contributions by 6 contributors**. A special thanks to [@bep](https://github.com/bep), [@D4D3VD4V3](https://github.com/D4D3VD4V3), [@chrischute](https://github.com/chrischute), and [@azenk](https://github.com/azenk) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 49096+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 436+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 369+ [themes](http://themes.gohugo.io/)
-
-## Notes
-
-* Resource.ResourceType now always returns MIME's main type [81975f84](https://github.com/gohugoio/hugo/commit/81975f847dc19c21c2321207645807771db97fab) [@bep](https://github.com/bep) [#8052](https://github.com/gohugoio/hugo/issues/8052)
-
-## Enhancements
-
-### Templates
-
-* Regenerate templates [a2d146ec](https://github.com/gohugoio/hugo/commit/a2d146ec32a26ccca9ffa68d3c840ec5b08cca96) [@bep](https://github.com/bep)
-* tpl/internal/go_templates: Revert formatting [718e09ed](https://github.com/gohugoio/hugo/commit/718e09ed4bc538f4fccc4337f99e9eb86aea31f3) [@bep](https://github.com/bep)
-* Add title parameter to YouTube shortcode [4fc918e0](https://github.com/gohugoio/hugo/commit/4fc918e02cfc7f260d6312248ff9d33e95b27943) [@azenk](https://github.com/azenk)
-
-### Output
-
-* Add missing OutputStyle option [428b0b32](https://github.com/gohugoio/hugo/commit/428b0b32947ec16f8585b8c33548d72fd4fb025d) [@bep](https://github.com/bep)
-
-### Other
-
-* Allow Dart Sass transformations to be cached on disk [ffbf5e45](https://github.com/gohugoio/hugo/commit/ffbf5e45fa0617a37950b34deab63736b1c6b1d3) [@bep](https://github.com/bep)
-* Dart Sass only supports `expanded` and `compressed` [48994ea7](https://github.com/gohugoio/hugo/commit/48994ea766f08332f57c0f8e74843b6c8617c3d1) [@bep](https://github.com/bep)
-* Update emoji import paths and version [1f7e9f73](https://github.com/gohugoio/hugo/commit/1f7e9f733397b891cefc725ffc94ba901e70425a) [@moorereason](https://github.com/moorereason)
-* Add Dart Sass support [cea15740](https://github.com/gohugoio/hugo/commit/cea157402365f34a69882110a4208999728007a6) [@bep](https://github.com/bep) [#7380](https://github.com/gohugoio/hugo/issues/7380)[#8102](https://github.com/gohugoio/hugo/issues/8102)
-* GroupByParamDate now supports datetimes [f9f77978](https://github.com/gohugoio/hugo/commit/f9f779786edcefc4449a14cfc04dd93379f71373) [@zerok](https://github.com/zerok)
-* Skip para test when not on CI [a9718f44](https://github.com/gohugoio/hugo/commit/a9718f44cd6c938448fc697f0ec720ebed7d863a) [@bep](https://github.com/bep) [#6963](https://github.com/gohugoio/hugo/issues/6963)
-* Update SECURITY.md [f802bb23](https://github.com/gohugoio/hugo/commit/f802bb236a60dcc6c64d53edac634891272e0c07) [@bep](https://github.com/bep)
-* Improve LookPath [10ae7c32](https://github.com/gohugoio/hugo/commit/10ae7c3210cd1add14d3750aa9512a87df0e1146) [@bep](https://github.com/bep)
-* create a SECURITY.md [ae2d1bd5](https://github.com/gohugoio/hugo/commit/ae2d1bd52df0099190ef9195666d0788708b0385) [@davidsneighbour](https://github.com/davidsneighbour) [#8074](https://github.com/gohugoio/hugo/issues/8074)
-* Show more detail on failed time test [8103188b](https://github.com/gohugoio/hugo/commit/8103188b9b9e8eeb3bcb53c8b64e2b83397e82ae) [@moorereason](https://github.com/moorereason) [#6963](https://github.com/gohugoio/hugo/issues/6963)
-* Add images.Overlay filter [3ba147e7](https://github.com/gohugoio/hugo/commit/3ba147e702a5ae0af6e8b3b0296d256c3246a546) [@bep](https://github.com/bep) [#8057](https://github.com/gohugoio/hugo/issues/8057)[#4595](https://github.com/gohugoio/hugo/issues/4595)[#6731](https://github.com/gohugoio/hugo/issues/6731)
-* Bump github.com/spf13/cobra from 0.15.0 to 0.20.0 [c84ad8db](https://github.com/gohugoio/hugo/commit/c84ad8db821c10225c0e603c6ec920c67b6ce36f) [@anthonyfok](https://github.com/anthonyfok)
-* configure proper link to discourse.gohugo.io (#8020) [4e0acb89](https://github.com/gohugoio/hugo/commit/4e0acb89b793d8895dc53eb8887be27430c3ab31) [@davidsneighbour](https://github.com/davidsneighbour)
-* Format code with gofumpt [d90e37e0](https://github.com/gohugoio/hugo/commit/d90e37e0c6e812f9913bf256c9c81aa05b7a08aa) [@bep](https://github.com/bep)
-* bump github.com/evanw/esbuild from 0.8.15 to 0.8.17 [32471b57](https://github.com/gohugoio/hugo/commit/32471b57bde51c55a15dbf1db75d6e5f7232c347) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Use --baseURL path for live-reload URL [0ad378b0](https://github.com/gohugoio/hugo/commit/0ad378b09cea90a2a70d7ff06af668abe22475a1) [@sth](https://github.com/sth) [#6595](https://github.com/gohugoio/hugo/issues/6595)
-* bump github.com/getkin/kin-openapi from 0.31.0 to 0.32.0 [907d9e92](https://github.com/gohugoio/hugo/commit/907d9e92682ed56a57a2206ae9bd9a985b3e1870) [@dependabot[bot]](https://github.com/apps/dependabot)
-
-## Fixes
-
-### Templates
-
-* Fix series detection in opengraph [d2d493ab](https://github.com/gohugoio/hugo/commit/d2d493ab5d6a054001a8448ea0de2949dac4b30e) [@Humberd](https://github.com/Humberd)
-* Fix substr when length parameter is zero [5862fd2a](https://github.com/gohugoio/hugo/commit/5862fd2a60b5d16f2437bd8c8b7bac700de5f047) [@moorereason](https://github.com/moorereason) [#7993](https://github.com/gohugoio/hugo/issues/7993)
-* Refactor and fix substr logic [64789fb5](https://github.com/gohugoio/hugo/commit/64789fb5dcf8326f14f13d69a2576ae3aa2bbbaa) [@moorereason](https://github.com/moorereason) [#7993](https://github.com/gohugoio/hugo/issues/7993)
-
-### Other
-
-* Fix Resource.ResourceType so it always returns MIME's main type [81975f84](https://github.com/gohugoio/hugo/commit/81975f847dc19c21c2321207645807771db97fab) [@bep](https://github.com/bep) [#8052](https://github.com/gohugoio/hugo/issues/8052)
-* hugolib/paths: Fix typo [ce96895d](https://github.com/gohugoio/hugo/commit/ce96895debb67df20ae24fb5f0f04b98a30cc6cc) [@mayocream](https://github.com/mayocream)
-* Fix minor typos [04b89857](https://github.com/gohugoio/hugo/commit/04b89857e104ac7dcbf9fc65d8d4f1a1178123e6) [@phil-davis](https://github.com/phil-davis)
-* Fix BenchmarkMergeByLanguage [21fa1e86](https://github.com/gohugoio/hugo/commit/21fa1e86f2aa929fb0983a0cc3dc4e271ea1cc54) [@bep](https://github.com/bep) [#7914](https://github.com/gohugoio/hugo/issues/7914)
-* Fix RelURL and AbsURL when path starts with language [aebfe156](https://github.com/gohugoio/hugo/commit/aebfe156fb2f27057e61b2e50c7576e6b06dab58) [@ivan-meridianbanc-com](https://github.com/ivan-meridianbanc-com)
-
-
-
-
-
diff --git a/docs/content/en/news/0.81.0-relnotes/featured.jpg b/docs/content/en/news/0.81.0-relnotes/featured.jpg
deleted file mode 100644
index e1488c5c218..00000000000
Binary files a/docs/content/en/news/0.81.0-relnotes/featured.jpg and /dev/null differ
diff --git a/docs/content/en/news/0.81.0-relnotes/index.md b/docs/content/en/news/0.81.0-relnotes/index.md
deleted file mode 100644
index ebcbb5ac71b..00000000000
--- a/docs/content/en/news/0.81.0-relnotes/index.md
+++ /dev/null
@@ -1,223 +0,0 @@
-
----
-date: 2021-02-19
-title: "Hugo 0.81.0: The Smorgasbord Edition"
-description: "Attribute lists (e.g. CSS classes) for Markdown blocks, newlines in template actions/blocks, native Apple M1 ARM64 binary, it's faster, and more …"
-categories: ["Releases"]
-toc: true
----
-
-**Hugo 0.81.0** is the first release of this decade, it is the fastest to date, and it's packed with useful new features.
-
-## Newlines in Template Actions and Commands
-
-You can now have newlines within template actions and pipelines. This means that you can now do this and similar:
-
-```go-html-template
-{{ dict
- "country" "Norway"
- "population" "5 millions"
- "language" "Norwegian"
- "language_code" "nb"
- "weather" "freezing cold"
- "capitol" "Oslo"
- "largest_city" "Oslo"
- "currency" "Norwegian krone"
- "dialing_code" "+47"
-}}
-```
-
-Note that the above construction will fail in Hugo versions < `0.81.0`.
-
-## Attribute Lists after Markdown Blocks
-
-Hugo already supports adding attribute lists (e.g CSS classes) after titles. We now also allow adding attribute lists after Markdown blocks, e.g. tables, lists, paragraphs etc.:
-
-```
-> foo
-> bar
-{.myclass}
-```
-
-See [Configure Goldmark](https://gohugo.io/getting-started/configuration-markup#goldmark).
-
-## Performance
-
-This release is the fastest Hugo to date, see details in the benchmarks below. This is [especially true](https://gohugo.io/news/hugo-macos-intel-vs-arm/) if you use the new ARM64 MacOS binary (only works on [Apple M1](https://en.wikipedia.org/wiki/Apple_M1) devices).
-
-### Site Building and Rebuilding Benchmarks: v0.80.0 => v0.81.0
-
-```
-name old time/op new time/op delta
-SiteNew/Edit_Bundle_with_image-16 771µs ± 6% 817µs ± 7% ~ (p=0.200 n=4+4)
-SiteNew/Edit_Bundle_with_JSON_file-16 728µs ± 2% 737µs ± 1% ~ (p=0.343 n=4+4)
-SiteNew/Edit_Tags_and_categories-16 16.6ms ± 5% 16.3ms ± 3% ~ (p=0.686 n=4+4)
-SiteNew/Edit_Canonify_URLs-16 29.4ms ± 6% 26.9ms ± 4% -8.37% (p=0.029 n=4+4)
-SiteNew/Edit_Deep_content_tree-16 33.8ms ± 3% 31.2ms ± 3% -7.53% (p=0.029 n=4+4)
-SiteNew/Edit_Many_HTML_templates-16 12.1ms ± 2% 11.6ms ± 1% -3.94% (p=0.029 n=4+4)
-SiteNew/Edit_Page_collections-16 20.6ms ± 1% 19.8ms ± 0% -3.57% (p=0.029 n=4+4)
-SiteNew/Edit_List_terms-16 3.91ms ± 1% 3.81ms ± 2% -2.52% (p=0.029 n=4+4)
-SiteNew/Regular_Bundle_with_image-16 6.15ms ± 2% 5.53ms ± 2% -10.11% (p=0.029 n=4+4)
-SiteNew/Regular_Bundle_with_JSON_file-16 6.26ms ± 4% 5.76ms ± 4% -7.98% (p=0.029 n=4+4)
-SiteNew/Regular_Tags_and_categories-16 26.2ms ± 2% 25.5ms ± 1% -2.42% (p=0.029 n=4+4)
-SiteNew/Regular_Canonify_URLs-16 34.7ms ± 8% 33.8ms ± 9% ~ (p=0.486 n=4+4)
-SiteNew/Regular_Deep_content_tree-16 43.8ms ± 1% 43.4ms ± 6% ~ (p=0.343 n=4+4)
-SiteNew/Regular_Many_HTML_templates-16 21.5ms ± 1% 19.7ms ± 2% -8.54% (p=0.029 n=4+4)
-SiteNew/Regular_Page_collections-16 30.7ms ± 2% 28.2ms ± 1% -8.23% (p=0.029 n=4+4)
-SiteNew/Regular_List_terms-16 9.70ms ± 1% 8.95ms ± 0% -7.72% (p=0.029 n=4+4)
-
-name old alloc/op new alloc/op delta
-SiteNew/Edit_Bundle_with_image-16 437kB ± 0% 428kB ± 0% -2.02% (p=0.029 n=4+4)
-SiteNew/Edit_Bundle_with_JSON_file-16 216kB ± 0% 207kB ± 0% -4.20% (p=0.029 n=4+4)
-SiteNew/Edit_Tags_and_categories-16 10.4MB ± 0% 9.7MB ± 0% -6.08% (p=0.029 n=4+4)
-SiteNew/Edit_Canonify_URLs-16 84.4MB ± 0% 85.2MB ± 0% +0.87% (p=0.029 n=4+4)
-SiteNew/Edit_Deep_content_tree-16 26.6MB ± 0% 25.6MB ± 0% -3.57% (p=0.029 n=4+4)
-SiteNew/Edit_Many_HTML_templates-16 6.03MB ± 0% 5.75MB ± 0% -4.57% (p=0.029 n=4+4)
-SiteNew/Edit_Page_collections-16 14.8MB ± 0% 14.2MB ± 0% -4.10% (p=0.029 n=4+4)
-SiteNew/Edit_List_terms-16 1.83MB ± 0% 1.73MB ± 0% -5.51% (p=0.029 n=4+4)
-SiteNew/Regular_Bundle_with_image-16 1.93MB ± 0% 1.90MB ± 0% -1.43% (p=0.029 n=4+4)
-SiteNew/Regular_Bundle_with_JSON_file-16 1.72MB ± 0% 1.69MB ± 0% -1.72% (p=0.029 n=4+4)
-SiteNew/Regular_Tags_and_categories-16 14.3MB ± 0% 13.6MB ± 0% -4.80% (p=0.029 n=4+4)
-SiteNew/Regular_Canonify_URLs-16 89.7MB ± 0% 90.2MB ± 0% +0.61% (p=0.029 n=4+4)
-SiteNew/Regular_Deep_content_tree-16 30.4MB ± 0% 29.2MB ± 0% -3.95% (p=0.029 n=4+4)
-SiteNew/Regular_Many_HTML_templates-16 9.26MB ± 0% 8.94MB ± 0% -3.47% (p=0.029 n=4+4)
-SiteNew/Regular_Page_collections-16 18.5MB ± 0% 17.7MB ± 0% -4.25% (p=0.029 n=4+4)
-SiteNew/Regular_List_terms-16 4.00MB ± 0% 3.85MB ± 0% -3.55% (p=0.029 n=4+4)
-
-name old allocs/op new allocs/op delta
-SiteNew/Edit_Bundle_with_image-16 3.99k ± 0% 4.07k ± 0% +1.80% (p=0.029 n=4+4)
-SiteNew/Edit_Bundle_with_JSON_file-16 3.99k ± 0% 4.06k ± 0% +1.81% (p=0.029 n=4+4)
-SiteNew/Edit_Tags_and_categories-16 241k ± 0% 245k ± 0% +1.67% (p=0.029 n=4+4)
-SiteNew/Edit_Canonify_URLs-16 364k ± 0% 321k ± 0% -11.78% (p=0.029 n=4+4)
-SiteNew/Edit_Deep_content_tree-16 264k ± 0% 268k ± 0% +1.53% (p=0.029 n=4+4)
-SiteNew/Edit_Many_HTML_templates-16 90.3k ± 0% 91.0k ± 0% +0.83% (p=0.029 n=4+4)
-SiteNew/Edit_Page_collections-16 153k ± 0% 156k ± 0% +1.46% (p=0.029 n=4+4)
-SiteNew/Edit_List_terms-16 30.4k ± 0% 30.9k ± 0% +1.54% (p=0.029 n=4+4)
-SiteNew/Regular_Bundle_with_image-16 23.3k ± 0% 23.1k ± 0% -0.57% (p=0.029 n=4+4)
-SiteNew/Regular_Bundle_with_JSON_file-16 23.3k ± 0% 23.1k ± 0% -0.59% (p=0.029 n=4+4)
-SiteNew/Regular_Tags_and_categories-16 284k ± 0% 288k ± 0% +1.58% (p=0.029 n=4+4)
-SiteNew/Regular_Canonify_URLs-16 387k ± 0% 343k ± 0% -11.41% (p=0.029 n=4+4)
-SiteNew/Regular_Deep_content_tree-16 307k ± 0% 309k ± 0% +0.52% (p=0.029 n=4+4)
-SiteNew/Regular_Many_HTML_templates-16 129k ± 0% 129k ± 0% +0.35% (p=0.029 n=4+4)
-SiteNew/Regular_Page_collections-16 199k ± 0% 200k ± 0% +0.55% (p=0.029 n=4+4)
-SiteNew/Regular_List_terms-16 53.5k ± 0% 53.4k ± 0% -0.18% (p=0.029 n=4+4)
-```
-
-
-
-## Native Arm Binary for Apple M1
-
-We finally provide native Hugo binary for [Apple M1](https://en.wikipedia.org/wiki/Apple_M1) devices. Download the file named `hugo_0.81.0_macOS-ARM64.tar.gz`.
-
-## JavaScript Building
-
-* Add `inject` config option allowing you to automatically replace a global variable with an import from another file relative to `assets`. [32b86076](https://github.com/gohugoio/hugo/commit/32b86076ee1c0833b538b84e1cc9e6d79babecf2) [@bep](https://github.com/bep) [#8164](https://github.com/gohugoio/hugo/issues/8164)
-* Add `shims` config option to swap out a component with another. A common use case is to load dependencies like React from a CDN (with _shims_) when in production, but running with the full bundled `node_modules` dependency during development [e19a046c](https://github.com/gohugoio/hugo/commit/e19a046c4be9b0654884259b9df94f41561e4fc3) [@bep](https://github.com/bep) [#8165](https://github.com/gohugoio/hugo/issues/8165)
-* Add external source map support to js.Build and Babel [2c8b5d91](https://github.com/gohugoio/hugo/commit/2c8b5d9165011c4b24b494e661ae60dfc7bb7d1b) [@richtera](https://github.com/richtera) [#8132](https://github.com/gohugoio/hugo/issues/8132)
-* Fix nilpointer in js.Build error handling [a1fe552f](https://github.com/gohugoio/hugo/commit/a1fe552fc9e622a15010a94281f604eb85bebd84) [@bep](https://github.com/bep) [#8162](https://github.com/gohugoio/hugo/issues/8162)
-
-Also see [js.Build Options](https://gohugo.io/hugo-pipes/js#options).
-
-## Hugo Modules
-
-There are several [Hugo Modules](https://gohugo.io/hugo-modules/)-related improvements in this release:
-
-* Allow absolute paths for any modules resolved via project replacement [3a5ee0d2](https://github.com/gohugoio/hugo/commit/3a5ee0d2d6e344b12efc7a97354ec3480c4c578b) [@bep](https://github.com/bep) [#8240](https://github.com/gohugoio/hugo/issues/8240)
-* Add config option modules.vendorClosest [bdfbcf6f](https://github.com/gohugoio/hugo/commit/bdfbcf6f4b4ab53a617ab76f72e8aa28da6067de) [@bep](https://github.com/bep) [#8235](https://github.com/gohugoio/hugo/issues/8235)[#8242](https://github.com/gohugoio/hugo/issues/8242)
-* Throw an error running hugo mod vendor on mountless module [4ffaeaf1](https://github.com/gohugoio/hugo/commit/4ffaeaf15536596c94dc73b393ca7894e3bd5e2c) [@bep](https://github.com/bep)
-
-## Minify - Keep Comments
-
-Keep comments when running `hugo --minify` with a new setting:
-
-{{< code-toggle file="config" >}}
-[minify.tdewolff.html]
-keepComments = true
-{{< /code-toggle >}}
-
-The default value for this setting is `false`.
-
-## Statistics
-
-This release represents **59 contributions by 14 contributors** to the main Hugo code base.[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@moorereason](https://github.com/moorereason) and [@benmezger](https://github.com/benmezger) for their ongoing contributions. And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the themes site in pristine condition and to [@davidsneighbour](https://github.com/davidsneighbour), [@coliff](https://github.com/coliff) and [@kaushalmodi](https://github.com/kaushalmodi) for all the great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **20 contributions by 13 contributors**. A special thanks to [@bep](https://github.com/bep), [@gagarine](https://github.com/gagarine), [@fridde](https://github.com/fridde), and [@NicoHood](https://github.com/NicoHood) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 50152+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 435+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 370+ [themes](http://themes.gohugo.io/)
-
-
-## Notes
-
-* We have updated to Beta 6 of the Dart Sass Protocol which is not backwards compatible, so if you use Dart Sass you need to also update [that binary](https://gohugo.io/hugo-pipes/scss-sass/#options).
-* `hugo gen autocomplete` now default to `stdout`; you can change this by setting `--completionfile`. As an added bonus we now also support auto completion for zsh, fish and powershell.
-
-## Changelog
-
-* Make the build green again [fe77f743](https://github.com/gohugoio/hugo/commit/fe77f7434bc0d7a9b54af69014eb28dbea2b236b) [@bep](https://github.com/bep)
-* Regenerate internal templates [c6080655](https://github.com/gohugoio/hugo/commit/c60806550a21062936b0d02708c9a8c240cafa9d) [@bep](https://github.com/bep)
-* Update date logic of opengraph and schema internal templates [ffd9dac4](https://github.com/gohugoio/hugo/commit/ffd9dac4218b8f1709de04f7131ca661715fc481) [@djatwood](https://github.com/djatwood)
-* Synch Go templates fork with Go 1.16dev [cf3e077d](https://github.com/gohugoio/hugo/commit/cf3e077da304e6f4d7c22f8296e1382335d055c6) [@bep](https://github.com/bep)
-* Exclude pages without Permalink from sitemap [4867cd1d](https://github.com/gohugoio/hugo/commit/4867cd1dea34ee53fb73cede2bcff4792e470104) [@Jaza](https://github.com/Jaza)
-* Add default user-agent header for getJSON requests [35def0ae](https://github.com/gohugoio/hugo/commit/35def0ae4560bb86febd12663bf5602485ad4b20) [@peacecwz](https://github.com/peacecwz)
-* remove 1mb limit for readFile. [ee9c1367](https://github.com/gohugoio/hugo/commit/ee9c1367635eab446fcf9baa1ab8b4066882548e) [@avdva](https://github.com/avdva)
-* Do not return errors in substr for out-of-bounds cases [8a26ab0b](https://github.com/gohugoio/hugo/commit/8a26ab0bc5dd9fa34e1362681fc08b0e522cd4ea) [@moorereason](https://github.com/moorereason) [#8113](https://github.com/gohugoio/hugo/issues/8113)
-* Add missing test scenario for strings.Substr [788e50ad](https://github.com/gohugoio/hugo/commit/788e50ad3a55609ed49ce0b7ee98965c181fe9cf) [@moorereason](https://github.com/moorereason)
-* Regen CLI docs [9e99950c](https://github.com/gohugoio/hugo/commit/9e99950c6ebf82c85ee52a8de85e284a506a2f90) [@bep](https://github.com/bep)
-* Regen docs helper [1b364b00](https://github.com/gohugoio/hugo/commit/1b364b003f68df3adb1644769fe69810d85e3897) [@bep](https://github.com/bep)
-* Run go mod tidy [88b93a09](https://github.com/gohugoio/hugo/commit/88b93a09dc79518d7fbd14681eeeea3411dab1dd) [@bep](https://github.com/bep)
-* Add arm64 to Darwinextended build and add vendorInfo [29fb456c](https://github.com/gohugoio/hugo/commit/29fb456c9e63ee1a2314bf4a7227a5146e7f9b31) [@bep](https://github.com/bep) [#8003](https://github.com/gohugoio/hugo/issues/8003)
-* Update Travis, GitHub, CircleCI and Snap to Go 1.16 (only) [718fba7d](https://github.com/gohugoio/hugo/commit/718fba7d63424017cb3b9774c33e7acc69c68af6) [@bep](https://github.com/bep)
-* Pull in latest Go 1.16 template source [e77b2e3a](https://github.com/gohugoio/hugo/commit/e77b2e3aa0b24c5ca960905772335b27845705eb) [@bep](https://github.com/bep)
-* Add breaking tests for "map read and map write in templates" [b5485aea](https://github.com/gohugoio/hugo/commit/b5485aeae7e1f73f18835fbf0b8eedc305d450d0) [@bep](https://github.com/bep) [#7293](https://github.com/gohugoio/hugo/issues/7293)
-* Pull in latest Go template source [ccb822eb](https://github.com/gohugoio/hugo/commit/ccb822eb5afad210432eb46ec3727e3536a87f58) [@bep](https://github.com/bep)
-* Expand template newline testcase to commands [21e9eb18](https://github.com/gohugoio/hugo/commit/21e9eb18acc2a2f8d8b97f096615b836e65091a2) [@bep](https://github.com/bep)
-* Add a test case for Go 1.16 template action newlines [ae57ba6a](https://github.com/gohugoio/hugo/commit/ae57ba6a9dee87347fa2d5e8c6865f390989622e) [@bep](https://github.com/bep)
-* Update github.com/tdewolff/minify/v2 v2.6.2 => v2.9.13 [66beac99](https://github.com/gohugoio/hugo/commit/66beac99c64b5e5fe7bec0bda437ba5858d49a36) [@bep](https://github.com/bep) [#8258](https://github.com/gohugoio/hugo/issues/8258)
-* bump github.com/frankban/quicktest from 1.11.2 to 1.11.3 [968dd7a7](https://github.com/gohugoio/hugo/commit/968dd7a711063934af84bd1c017c58a1e66f51bb) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/getkin/kin-openapi from 0.32.0 to 0.39.0 [38f29e81](https://github.com/gohugoio/hugo/commit/38f29e817f2058ed56f96fb8e628315f3ab5d7f9) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/aws/aws-sdk-go from 1.36.33 to 1.37.11 [cd87813a](https://github.com/gohugoio/hugo/commit/cd87813aa0327ec7a7e6f023dadcea5a3e6a9fef) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/sanity-io/litter from 1.3.0 to 1.5.0 [4e815b06](https://github.com/gohugoio/hugo/commit/4e815b063e4af70f21b6796688025675253bec65) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/olekukonko/tablewriter from 0.0.4 to 0.0.5 [652a59d3](https://github.com/gohugoio/hugo/commit/652a59d38523e23e39376cba9c554abbe87b198d) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Update to esbuild v0.8.46 [84f0ec7f](https://github.com/gohugoio/hugo/commit/84f0ec7f80855dcc9b123418bcbf816b5efa2cdf) [@bep](https://github.com/bep)
-* Add config option modules.vendorClosest [bdfbcf6f](https://github.com/gohugoio/hugo/commit/bdfbcf6f4b4ab53a617ab76f72e8aa28da6067de) [@bep](https://github.com/bep) [#8235](https://github.com/gohugoio/hugo/issues/8235)[#8242](https://github.com/gohugoio/hugo/issues/8242)
-* bump google.golang.org/api from 0.26.0 to 0.40.0 [a9b0fea6](https://github.com/gohugoio/hugo/commit/a9b0fea6a3aec658912a8db134824dee4a9b6369) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Change version string format and add VendorInfo to help with issue triaging [e8df0977](https://github.com/gohugoio/hugo/commit/e8df09774534abe6131eb455b4f5c614fb438983) [@anthonyfok](https://github.com/anthonyfok)
-* Allow absolute paths for any modules resolved via project replacement [3a5ee0d2](https://github.com/gohugoio/hugo/commit/3a5ee0d2d6e344b12efc7a97354ec3480c4c578b) [@bep](https://github.com/bep) [#8240](https://github.com/gohugoio/hugo/issues/8240)
-* Throw an error running hugo mod vendor on mountless module [4ffaeaf1](https://github.com/gohugoio/hugo/commit/4ffaeaf15536596c94dc73b393ca7894e3bd5e2c) [@bep](https://github.com/bep)
-* Add PowerShell completion support [5f621df2](https://github.com/gohugoio/hugo/commit/5f621df2570236a08cd21e8dd1c60502ec3db328) [@anthonyfok](https://github.com/anthonyfok) [#8122](https://github.com/gohugoio/hugo/issues/8122)
-* Refer to mage instead of make in comment regarding commitHash [7118f89c](https://github.com/gohugoio/hugo/commit/7118f89cf35246767e26dcb5e747469ffa61f473) [@anthonyfok](https://github.com/anthonyfok)
-* Add attributes support for blocks (tables etc.) [2681633d](https://github.com/gohugoio/hugo/commit/2681633db8d340d2dc59cf801419874d572fc704) [@bep](https://github.com/bep) [#7548](https://github.com/gohugoio/hugo/issues/7548)
-* Update to Goldmark v1.3.2 [1b247282](https://github.com/gohugoio/hugo/commit/1b2472825664763c0b88807b0d193e73553423ec) [@bep](https://github.com/bep) [#8143](https://github.com/gohugoio/hugo/issues/8143)
-* Update to Dart Sass Protocol beta6 [441b11be](https://github.com/gohugoio/hugo/commit/441b11beec3cf0371ff9a2898f220a0bf00faf8c) [@bep](https://github.com/bep)
-* Write to stdout by default [d36fd5b3](https://github.com/gohugoio/hugo/commit/d36fd5b3ee6989203de2a29b1de67521fd1c8ea5) [@benmezger](https://github.com/benmezger)
-* Remove powershell support [a7c515e1](https://github.com/gohugoio/hugo/commit/a7c515e1b56e8cab34ca2647b4116904df9c8250) [@benmezger](https://github.com/benmezger)
-* Add zsh, fish and powershell completion support [216b00f3](https://github.com/gohugoio/hugo/commit/216b00f358dbfa36b34ff515d7f4f88387156db8) [@benmezger](https://github.com/benmezger) [#4296](https://github.com/gohugoio/hugo/issues/4296)
-* Enable NPM tests on Windows [14494379](https://github.com/gohugoio/hugo/commit/144943798c2a199ed256ae901a14d3c918055eba) [@bep](https://github.com/bep) [#8196](https://github.com/gohugoio/hugo/issues/8196)
-* Update to esbuild v0.8.39 [440fdb0e](https://github.com/gohugoio/hugo/commit/440fdb0eb96b3230ddefee732b0c1afe52a37228) [@bep](https://github.com/bep) [#8189](https://github.com/gohugoio/hugo/issues/8189)
-* Trim whitespace in elements written to hugo_stats.json [b2a48dce](https://github.com/gohugoio/hugo/commit/b2a48dce58abd3a661aa198af3277ef12f44cce0) [@pmatiash](https://github.com/pmatiash) [#7958](https://github.com/gohugoio/hugo/issues/7958)
-* bump github.com/aws/aws-sdk-go from 1.35.0 to 1.36.33 [2f9dadae](https://github.com/gohugoio/hugo/commit/2f9dadae4072960bbaec3656347e20eec238288c) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Remove mention of a file size limit for readFile [ed3071b7](https://github.com/gohugoio/hugo/commit/ed3071b753c8dec83a2c054624e49b204553ecd3) [@avdva](https://github.com/avdva)
-* Add Inject config option [32b86076](https://github.com/gohugoio/hugo/commit/32b86076ee1c0833b538b84e1cc9e6d79babecf2) [@bep](https://github.com/bep) [#8164](https://github.com/gohugoio/hugo/issues/8164)
-* Add Shims option [e19a046c](https://github.com/gohugoio/hugo/commit/e19a046c4be9b0654884259b9df94f41561e4fc3) [@bep](https://github.com/bep) [#8165](https://github.com/gohugoio/hugo/issues/8165)
-* bump github.com/spf13/afero from 1.4.1 to 1.5.1 [07ad283f](https://github.com/gohugoio/hugo/commit/07ad283f686904e5835f621d73ed342ba2a48eb3) [@eclipseo](https://github.com/eclipseo)
-* Add external source map support to js.Build and Babel [2c8b5d91](https://github.com/gohugoio/hugo/commit/2c8b5d9165011c4b24b494e661ae60dfc7bb7d1b) [@richtera](https://github.com/richtera) [#8132](https://github.com/gohugoio/hugo/issues/8132)
-* Run go mod tidy [4d2b6fc4](https://github.com/gohugoio/hugo/commit/4d2b6fc4c0e714f3f1ed345d6d75ed1662948791) [@bep](https://github.com/bep)
-* Update go-org to v1.4.0 [212e5e55](https://github.com/gohugoio/hugo/commit/212e5e554284bc9368e52a512ed09be5a0224d3e) [@niklasfasching](https://github.com/niklasfasching)
-* Adjust log level [4fdec67b](https://github.com/gohugoio/hugo/commit/4fdec67b1155ae1cdf051582d9ab387286b71a07) [@bep](https://github.com/bep)
-* Add temporary patch to fix template data race [9650e568](https://github.com/gohugoio/hugo/commit/9650e568418a316e71ad94d7e27caf544a4a2d0d) [@bep](https://github.com/bep) [#7293](https://github.com/gohugoio/hugo/issues/7293)
-* Fix race condition in text template baseof [241b7483](https://github.com/gohugoio/hugo/commit/241b7483ea954653512d4895ad6bacf79ee26ddc) [@moorereason](https://github.com/moorereason)
-* Fix metrics hint tracking [0004a733](https://github.com/gohugoio/hugo/commit/0004a733c85cee991a8a170e93cd69c326cc8f2f) [@moorereason](https://github.com/moorereason) [#8125](https://github.com/gohugoio/hugo/issues/8125)
-* Fix potential path issue on Windows [b60e9279](https://github.com/gohugoio/hugo/commit/b60e9279ab95030828eb4f822be96250284c4d8d) [@bep](https://github.com/bep)
-* Fix some humanize issues [bf55afd7](https://github.com/gohugoio/hugo/commit/bf55afd71f2fdb47272ebf1188c9cc87df47b233) [@susiwen8](https://github.com/susiwen8) [#7912](https://github.com/gohugoio/hugo/issues/7912)
-* Fix handling of legacy attribute config [e6dd3128](https://github.com/gohugoio/hugo/commit/e6dd312812c7c711986af2d60f2999d116b82ea0) [@bep](https://github.com/bep) [#7548](https://github.com/gohugoio/hugo/issues/7548)
-* Support translation files with suffix *.yml [92c6c404](https://github.com/gohugoio/hugo/commit/92c6c40419bdc13b8bb422a212d1d79240356651) [@bep](https://github.com/bep) [#8212](https://github.com/gohugoio/hugo/issues/8212)
-* Fix nilpointer in js.Build error handling [a1fe552f](https://github.com/gohugoio/hugo/commit/a1fe552fc9e622a15010a94281f604eb85bebd84) [@bep](https://github.com/bep) [#8162](https://github.com/gohugoio/hugo/issues/8162)
-
-
-
diff --git a/docs/content/en/news/0.82.0-relnotes/featured.png b/docs/content/en/news/0.82.0-relnotes/featured.png
deleted file mode 100644
index d09243f61c6..00000000000
Binary files a/docs/content/en/news/0.82.0-relnotes/featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.82.0-relnotes/index.md b/docs/content/en/news/0.82.0-relnotes/index.md
deleted file mode 100644
index 0297ea04159..00000000000
--- a/docs/content/en/news/0.82.0-relnotes/index.md
+++ /dev/null
@@ -1,78 +0,0 @@
-
----
-date: 2021-03-21
-title: "Hugo 0.82: Mostly bugfixes"
-description: "Mostly bug fixes, but some useful improvements with Markdown attributes."
-categories: ["Releases"]
----
-
-This is a small release, mostly a maintainance/bugfix release. But also notable is that you can now add custom Markdown attributes (e.g. CSS classes) to code fences ([aed7df62](https://github.com/gohugoio/hugo/commit/aed7df62a811b07b73ec5cbbf03e69e4bbf00919) [@bep](https://github.com/bep) [#8278](https://github.com/gohugoio/hugo/issues/8278)) and that you can use the attribute lists in title render hooks (`.Attributes`; see [cd0c5d7e](https://github.com/gohugoio/hugo/commit/cd0c5d7ef32cbd570af00c50ce760452381df64e) [@bep](https://github.com/bep) [#8270](https://github.com/gohugoio/hugo/issues/8270)).
-
-This release represents **28 contributions by 8 contributors** to the main Hugo code base.[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@moorereason](https://github.com/moorereason), and [@gzagatti](https://github.com/gzagatti) for their ongoing contributions.
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the themes site in pristine condition and to [@davidsneighbour](https://github.com/davidsneighbour), [@coliff](https://github.com/coliff) and [@kaushalmodi](https://github.com/kaushalmodi) for all the great work on the documentation site.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **20 contributions by 12 contributors**. A special thanks to [@bep](https://github.com/bep), [@jmooring](https://github.com/jmooring), [@rootkea](https://github.com/rootkea), and [@PaulPineda](https://github.com/PaulPineda) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 50763+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 435+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 370+ [themes](http://themes.gohugo.io/)
-
-## Notes
-
-* We have made `.MediaType` comparable [ba1d0051](https://github.com/gohugoio/hugo/commit/ba1d0051b44fdd242b20899e195e37ab26501516) [@bep](https://github.com/bep) [#8317](https://github.com/gohugoio/hugo/issues/8317)[#8324](https://github.com/gohugoio/hugo/issues/8324). This also means that the old `MediaType.Suffix` and `MediaType.FullSuffix` is moved to `MediaType.FirstSuffix.Suffix` and `MediaType.FirstSuffix.FullSuffix`, which also better describes what they represent.
-
-## Enhancements
-
-### Templates
-
-* Add method mappings for strings.Contains, strings.ContainsAny [7f853003](https://github.com/gohugoio/hugo/commit/7f8530039aa018f23bad9d58e97777705a6d19ac) [@bep](https://github.com/bep)
-
-### Output
-
-* Make Type comparable [ba1d0051](https://github.com/gohugoio/hugo/commit/ba1d0051b44fdd242b20899e195e37ab26501516) [@bep](https://github.com/bep) [#8317](https://github.com/gohugoio/hugo/issues/8317)[#8324](https://github.com/gohugoio/hugo/issues/8324)
-* Add a basic benchmark [4d24e2a3](https://github.com/gohugoio/hugo/commit/4d24e2a3261d8c7dc0395db3ac4de89ebb0974a5) [@bep](https://github.com/bep)
-
-### Other
-
-* Regenerate docs helper [86b4fd35](https://github.com/gohugoio/hugo/commit/86b4fd35e78f545510f19b49246a3ccf5487831b) [@bep](https://github.com/bep)
-* Regen CLI docs [195d108d](https://github.com/gohugoio/hugo/commit/195d108da75c9e5b9ef790bc4a5879c1e913964b) [@bep](https://github.com/bep)
-* Simplify some config loading code [df8bb881](https://github.com/gohugoio/hugo/commit/df8bb8812f466bce563cdba297db3cd3f954a799) [@bep](https://github.com/bep)
-* Update github.com/evanw/esbuild v0.9.0 => v0.9.6 [57d8d208](https://github.com/gohugoio/hugo/commit/57d8d208ed2245858c6439f19803bf2749f9377f) [@bep](https://github.com/bep)
-* Apply OS env overrides twice [fc06e850](https://github.com/gohugoio/hugo/commit/fc06e85082b63a54d9403e57c8d01a7d5a62fc04) [@bep](https://github.com/bep)
-* Attributes for code fences should be placed after the lang indicator only [b725253f](https://github.com/gohugoio/hugo/commit/b725253f9e3033e18bd45096c0622e6fb7b1ff79) [@bep](https://github.com/bep) [#8313](https://github.com/gohugoio/hugo/issues/8313)
-* Bump github.com/tdewolff/minify/v2 v2.9.15 [35dedf15](https://github.com/gohugoio/hugo/commit/35dedf15c04a605df4d4a09263b0b299e5161f86) [@bep](https://github.com/bep) [#8332](https://github.com/gohugoio/hugo/issues/8332)
-* More explicit support link to discourse [137d2dab](https://github.com/gohugoio/hugo/commit/137d2dab3285e9b0f8fe4dcc65ab6ecf8bb09002) [@davidsneighbour](https://github.com/davidsneighbour)
-* Update to esbuild v0.9.0 [1b1dcf58](https://github.com/gohugoio/hugo/commit/1b1dcf586e220c3a8ad5ecfa8e4c3dac97f0ab44) [@bep](https://github.com/bep)
-* Allow more spacing characters in strings [0a2ab3f8](https://github.com/gohugoio/hugo/commit/0a2ab3f8feb961f8394b1f9964fab36bfa468027) [@moorereason](https://github.com/moorereason) [#8079](https://github.com/gohugoio/hugo/issues/8079)[#8079](https://github.com/gohugoio/hugo/issues/8079)
-* Rename a test [35bfb662](https://github.com/gohugoio/hugo/commit/35bfb662229226d5f3cc3077ca74323f0aa88b7d) [@bep](https://github.com/bep)
-* Add a debug helper [6d21559f](https://github.com/gohugoio/hugo/commit/6d21559fb55cda39c7b92bb61fd8e65a84465fe5) [@bep](https://github.com/bep)
-* Add support for Google Analytics v4 [ba16a14c](https://github.com/gohugoio/hugo/commit/ba16a14c6e884e309380610331aff78213f84751) [@djatwood](https://github.com/djatwood)
-* Bump go.mod to Go 1.16 [782c79ae](https://github.com/gohugoio/hugo/commit/782c79ae61a5ec30746ce3729933d6b4d31e0540) [@bep](https://github.com/bep) [#8294](https://github.com/gohugoio/hugo/issues/8294)
-* #8210 Upgrade golang version for Dockerfile [5afcae7e](https://github.com/gohugoio/hugo/commit/5afcae7e0b4c08bc37db6e34ab4cf960558f4b6e) [@systemkern](https://github.com/systemkern)
-* Update CONTRIBUTING.md [60469f42](https://github.com/gohugoio/hugo/commit/60469f429e227631d76d951f2ed92986f0bd92e9) [@bep](https://github.com/bep)
-* Handle attribute lists in code fences [aed7df62](https://github.com/gohugoio/hugo/commit/aed7df62a811b07b73ec5cbbf03e69e4bbf00919) [@bep](https://github.com/bep) [#8278](https://github.com/gohugoio/hugo/issues/8278)
-* Allow markdown attribute lists to be used in title render hooks [cd0c5d7e](https://github.com/gohugoio/hugo/commit/cd0c5d7ef32cbd570af00c50ce760452381df64e) [@bep](https://github.com/bep) [#8270](https://github.com/gohugoio/hugo/issues/8270)
-* bump github.com/kyokomi/emoji/v2 from 2.2.7 to 2.2.8 [88a85dce](https://github.com/gohugoio/hugo/commit/88a85dcea951b0b5622cf02b167ec9299d93118b) [@dependabot[bot]](https://github.com/apps/dependabot)
-
-## Fixes
-
-### Output
-
-* Fix output format handling for render hooks [18074d0c](https://github.com/gohugoio/hugo/commit/18074d0c2375cc4bf4d7933dd4206cb878a23d1c) [@bep](https://github.com/bep) [#8176](https://github.com/gohugoio/hugo/issues/8176)
-
-### Other
-
-* Fix OS env override for nested config param only available in theme [7ed56c69](https://github.com/gohugoio/hugo/commit/7ed56c6941edfdfa42eef2b779020b5d46ca194a) [@bep](https://github.com/bep) [#8346](https://github.com/gohugoio/hugo/issues/8346)
-* Fix `new theme` command description [24c716ca](https://github.com/gohugoio/hugo/commit/24c716cac35b0c5476944108e545058749c43e61) [@rootkea](https://github.com/rootkea)
-* Fix handling of utf8 runes in nullString() [f6612d8b](https://github.com/gohugoio/hugo/commit/f6612d8bd8c4c3bb498178d14f45d3acdf86aa7c) [@moorereason](https://github.com/moorereason)
-* Fixes #7698. [01dd7c16](https://github.com/gohugoio/hugo/commit/01dd7c16af6204d18d530f9d3018689215482170) [@gzagatti](https://github.com/gzagatti)
-* Fix autocomplete docs [c8f45d1d](https://github.com/gohugoio/hugo/commit/c8f45d1d861f596821afc068bd12eb1213aba5ce) [@bep](https://github.com/bep)
-
-
-
-
-
diff --git a/docs/content/en/news/0.82.1-relnotes/index.md b/docs/content/en/news/0.82.1-relnotes/index.md
deleted file mode 100644
index 54462b8ad81..00000000000
--- a/docs/content/en/news/0.82.1-relnotes/index.md
+++ /dev/null
@@ -1,19 +0,0 @@
-
----
-date: 2021-04-20
-title: "Hugo 0.82.1: One Bug Fix"
-description: "This version fixes a bug introduced in 0.82.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with one important fix.
-
-* Regression in media type suffix lookup [6e9d2bf0](https://github.com/gohugoio/hugo/commit/6e9d2bf0c936900f8f676d485098755b3f463373) [@bep](https://github.com/bep) [#8406](https://github.com/gohugoio/hugo/issues/8406)
-
-
-
diff --git a/docs/content/en/news/0.83.0-relnotes/featured.png b/docs/content/en/news/0.83.0-relnotes/featured.png
deleted file mode 100644
index 473170efdd1..00000000000
Binary files a/docs/content/en/news/0.83.0-relnotes/featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.83.0-relnotes/index.md b/docs/content/en/news/0.83.0-relnotes/index.md
deleted file mode 100644
index 987110c3fdb..00000000000
--- a/docs/content/en/news/0.83.0-relnotes/index.md
+++ /dev/null
@@ -1,133 +0,0 @@
-
----
-date: 2021-05-01
-title: "Hugo 0.83: WebP Support!"
-description: "WebP image encoding support, some important i18n fixes, and more."
-categories: ["Releases"]
----
-
-**Note:** If you use i18n, there is an unfortunate regression bug in this release (see [issue](https://github.com/gohugoio/hugo/issues/8492)). A patch release coming Sunday.
-
-
-Hugo `0.83` finally brings [WebP](https://gohugo.io/content-management/image-processing/) image processing support. Note that you need the [extended version](https://gohugo.io/troubleshooting/faq/#i-get-tocss--this-feature-is-not-available-in-your-current-hugo-version) of Hugo to encode to WebP. If you want to target all Hugo versions, you may use a construct such as this:
-
-```go-html-template
-{{ $images := slice }}
-{{ $images = $images | append ($img.Resize "300x") }}
-{{ if hugo.IsExtended }}
- {{ $images = $images | append ($img.Resize "300x webp") }}
-{{ end }}
-```
-
-Also worth highlighting:
-
-* Some important language/i18n fixes (thanks to [@jmooring](https://github.com/jmooring) for helping out with these):
- * Fix multiple unknown language codes [7eb80a9e](https://github.com/gohugoio/hugo/commit/7eb80a9e6fcb6d31711effa20310cfefb7b23c1b) [@bep](https://github.com/bep) [#7838](https://github.com/gohugoio/hugo/issues/7838)
- * Improve plural handling of floats [eebde0c2](https://github.com/gohugoio/hugo/commit/eebde0c2ac4964e91d26d8b0cf0ac43afcfd207f) [@bep](https://github.com/bep) [#8464](https://github.com/gohugoio/hugo/issues/8464)
- * Revise the plural implementation [537c905e](https://github.com/gohugoio/hugo/commit/537c905ec103dc5adaf8a1b2ccdef5da7cc660fd) [@bep](https://github.com/bep) [#8454](https://github.com/gohugoio/hugo/issues/8454)[#7822](https://github.com/gohugoio/hugo/issues/7822)
-* You can now use slice syntax in the sections permalinks config[2dc222ce](https://github.com/gohugoio/hugo/commit/2dc222cec4460595af8569165d1c498bb45aac84) [@bep](https://github.com/bep) [#8363](https://github.com/gohugoio/hugo/issues/8363).
-
-This release represents **61 contributions by 9 contributors** to the main Hugo code base.[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@dependabot[bot]](https://github.com/apps/dependabot), [@jmooring](https://github.com/jmooring), and [@anthonyfok](https://github.com/anthonyfok) for their ongoing contributions.
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the themes site in pristine condition.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **10 contributions by 5 contributors**. A special thanks to [@lupsa](https://github.com/lupsa), [@jmooring](https://github.com/jmooring), [@bep](https://github.com/bep), and [@arhuman](https://github.com/arhuman) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 51594+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 432+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 370+ [themes](http://themes.gohugo.io/)
-
-## Notes
-
-* We have updated ESBUild to v0.11.16. There are no breaking changes on the API side, but you may want to read the release upstream release notes: https://github.com/evanw/esbuild/releases/tag/v0.10.0 https://github.com/evanw/esbuild/releases/tag/v0.11.0
-
-## Enhancements
-
-### Templates
-
-* Remove the FuzzMarkdownify func for now [5656a908](https://github.com/gohugoio/hugo/commit/5656a908d837f2aa21837d39712b8ab4aa6db842) [@bep](https://github.com/bep)
-
-### Output
-
-* Make the shortcode template lookup for output formats stable [0d86a32d](https://github.com/gohugoio/hugo/commit/0d86a32d8f3031e2124c8005b680b597f3c0e558) [@bep](https://github.com/bep) [#7774](https://github.com/gohugoio/hugo/issues/7774)
-* Only output mediaType once in docshelper JSON [7b4ade56](https://github.com/gohugoio/hugo/commit/7b4ade56dd50d89a91760fc5ef8e2f151874de96) [@bep](https://github.com/bep) [#8379](https://github.com/gohugoio/hugo/issues/8379)
-
-### Other
-
-* Regenerate docs helper [a9b52b41](https://github.com/gohugoio/hugo/commit/a9b52b41758d20ae4c10b71721b22175395c69e9) [@bep](https://github.com/bep)
-* Regenerate CLI docs [b073a1c9](https://github.com/gohugoio/hugo/commit/b073a1c9723980eeb58717884006148dfc0e0c8e) [@bep](https://github.com/bep)
-* Remove all dates from gendoc [4227cc1b](https://github.com/gohugoio/hugo/commit/4227cc1bd308d1ef1ea151c86f72f537b5e77b1d) [@bep](https://github.com/bep)
-* Update getkin/kin-openapi v0.60.0 => v0.61. [3cc4fdd6](https://github.com/gohugoio/hugo/commit/3cc4fdd6f358263ffde33ccbf61546f073979e32) [@bep](https://github.com/bep)
-* Update github.com/evanw/esbuild v0.11.14 => v0.11.16 [78c1a6a7](https://github.com/gohugoio/hugo/commit/78c1a6a7c6e14f006854ee97ec561abdcf6203fc) [@bep](https://github.com/bep)
-* Remove .Site.Authors from embedded templates [f6745ad3](https://github.com/gohugoio/hugo/commit/f6745ad3588a7b3aaae228fec18fe0027affd566) [@jmooring](https://github.com/jmooring) [#4458](https://github.com/gohugoio/hugo/issues/4458)
-* Don't treat a NotFound response for Delete as a fatal error. [f523e9f0](https://github.com/gohugoio/hugo/commit/f523e9f0fd0e0b0ce75879532caa834742297d16) [@vangent](https://github.com/vangent)
-* Switch to deb packages of nodejs and python3-pygments [63cd05ce](https://github.com/gohugoio/hugo/commit/63cd05ce5ae308c496b848f6b11bcb3fdbdf5cb2) [@anthonyfok](https://github.com/anthonyfok)
-* Install bin/node from node/14/stable [902535ef](https://github.com/gohugoio/hugo/commit/902535ef11fce449b377896ab7498c4799beb9ce) [@anthonyfok](https://github.com/anthonyfok)
-* bump github.com/getkin/kin-openapi from 0.55.0 to 0.60.0 [70aebba0](https://github.com/gohugoio/hugo/commit/70aebba04d801fe6a3784394d25c433ffeb6d123) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/evanw/esbuild from 0.11.13 to 0.11.14 [3e3b7d44](https://github.com/gohugoio/hugo/commit/3e3b7d4474ea97a1990f303482a12f0c3031bd07) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Update to Chroma v0.9.1 [048418ba](https://github.com/gohugoio/hugo/commit/048418ba749d02eb3dde9d6895cedef2adaefefd) [@caarlos0](https://github.com/caarlos0)
-* Improve plural handling of floats [eebde0c2](https://github.com/gohugoio/hugo/commit/eebde0c2ac4964e91d26d8b0cf0ac43afcfd207f) [@bep](https://github.com/bep) [#8464](https://github.com/gohugoio/hugo/issues/8464)
-* bump github.com/evanw/esbuild from 0.11.12 to 0.11.13 [65c502cc](https://github.com/gohugoio/hugo/commit/65c502cc8110e49540cbe2b49ecd5a8ede9e67a1) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Revise the plural implementation [537c905e](https://github.com/gohugoio/hugo/commit/537c905ec103dc5adaf8a1b2ccdef5da7cc660fd) [@bep](https://github.com/bep) [#8454](https://github.com/gohugoio/hugo/issues/8454)[#7822](https://github.com/gohugoio/hugo/issues/7822)
-* Update to "base: core20" [243951eb](https://github.com/gohugoio/hugo/commit/243951ebe9715d3da3968e96e6f60dcd53e25d92) [@anthonyfok](https://github.com/anthonyfok)
-* bump github.com/frankban/quicktest from 1.11.3 to 1.12.0 [fe2ee028](https://github.com/gohugoio/hugo/commit/fe2ee028024836695c99e28595393588e3930136) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump google.golang.org/api from 0.44.0 to 0.45.0 [316d65cd](https://github.com/gohugoio/hugo/commit/316d65cd7049d60b0d5ac0080a87236198e74fc9) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/aws/aws-sdk-go from 1.37.11 to 1.38.23 [b95229ab](https://github.com/gohugoio/hugo/commit/b95229ab49ac2126aefe7802392ef34fdd021c3b) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Correct function name in comment [0551df09](https://github.com/gohugoio/hugo/commit/0551df090e6b2a391941bf7383b79c2dbc11d416) [@xhit](https://github.com/xhit)
-* Upgraded github.com/evanw/esbuild v0.11.0 => v0.11.12 [057e5a22](https://github.com/gohugoio/hugo/commit/057e5a22af937459082c3096ba3095b343d1a8bf) [@bep](https://github.com/bep)
-* Regen docs helper [fd96f65a](https://github.com/gohugoio/hugo/commit/fd96f65a3d7755e49b4a70fb276dfffcba4e541a) [@bep](https://github.com/bep)
-* bump github.com/tdewolff/minify/v2 from 2.9.15 to 2.9.16 [d3a64708](https://github.com/gohugoio/hugo/commit/d3a64708f49139552ca79a199a4cbf6544375443) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump golang.org/x/text from 0.3.5 to 0.3.6 [3b56244f](https://github.com/gohugoio/hugo/commit/3b56244f425a72c783bb58c30542aeb4b045acca) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Remove some unreachable code [f5d3d635](https://github.com/gohugoio/hugo/commit/f5d3d635e6b88d7c5d304b80f04e7b4361349fd6) [@bep](https://github.com/bep)
-* bump github.com/getkin/kin-openapi from 0.39.0 to 0.55.0 [0d3c42da](https://github.com/gohugoio/hugo/commit/0d3c42da56151325f16802b3b1a4105a21ce250e) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Some performance tweaks for the HTML elements collector [ef34dd8f](https://github.com/gohugoio/hugo/commit/ef34dd8f0e94e52ba6f1d5d607e4ac3ae98a7abb) [@bep](https://github.com/bep)
-* Exclude comment and doctype elements from writeStats [bc80022e](https://github.com/gohugoio/hugo/commit/bc80022e033a5462d1a9ce541f40a050994011cc) [@dirkolbrich](https://github.com/dirkolbrich) [#8396](https://github.com/gohugoio/hugo/issues/8396)[#8417](https://github.com/gohugoio/hugo/issues/8417)
-* Merge branch 'release-0.82.1' [2bb9496c](https://github.com/gohugoio/hugo/commit/2bb9496ce29dfe90e8b3664ed8cf7f895011b2d4) [@bep](https://github.com/bep)
-* bump github.com/yuin/goldmark from 1.3.2 to 1.3.5 [3ddffd06](https://github.com/gohugoio/hugo/commit/3ddffd064dbacf62aa854b26ea8ddc5d15ba1ef8) [@jmooring](https://github.com/jmooring) [#8377](https://github.com/gohugoio/hugo/issues/8377)
-* Remove duplicate references from release notes [6fc52d18](https://github.com/gohugoio/hugo/commit/6fc52d185a98b86c70b6ba862549cc6aae782691) [@jmooring](https://github.com/jmooring) [#8360](https://github.com/gohugoio/hugo/issues/8360)
-* bump github.com/spf13/afero from 1.5.1 to 1.6.0 [73c3ae81](https://github.com/gohugoio/hugo/commit/73c3ae818a7fc78febff092ac74772a114a2cbd2) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/pelletier/go-toml from 1.8.1 to 1.9.0 [7ca118fd](https://github.com/gohugoio/hugo/commit/7ca118fdfd9f0d1c636ef5e266c9000a20099e03) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Add webp image encoding support [33d5f805](https://github.com/gohugoio/hugo/commit/33d5f805923eb50dfb309d024f6555c59a339846) [@bep](https://github.com/bep) [#5924](https://github.com/gohugoio/hugo/issues/5924)
-* bump google.golang.org/api from 0.40.0 to 0.44.0 [509d39fa](https://github.com/gohugoio/hugo/commit/509d39fa6ddbba106c127b7923a41b0dcaea9381) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/nicksnyder/go-i18n/v2 from 2.1.1 to 2.1.2 [7725c41d](https://github.com/gohugoio/hugo/commit/7725c41d40b7009c2701a5ad3fa6bc9de57b88ee) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/rogpeppe/go-internal from 1.6.2 to 1.8.0 [5d36d801](https://github.com/gohugoio/hugo/commit/5d36d801534c0823697610fdb32e1eeb61f70e33) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Remove extraneous space from figure shortcode [9b34d42b](https://github.com/gohugoio/hugo/commit/9b34d42bb2ff05deaeeef63ff4b5b993f35f0451) [@jmooring](https://github.com/jmooring) [#8401](https://github.com/gohugoio/hugo/issues/8401)
-* bump github.com/magefile/mage from 1.10.0 to 1.11.0 [c2d8f87c](https://github.com/gohugoio/hugo/commit/c2d8f87cfc1c4ae666fbb1fb5b8983d43492333f) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/google/go-cmp from 0.5.4 to 0.5.5 [cbc24661](https://github.com/gohugoio/hugo/commit/cbc246616e88729322dad70971eae18ef59dd5d4) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Disable broken pretty relative links feature [fa432b17](https://github.com/gohugoio/hugo/commit/fa432b17b349ed7e914af3625187e2c1dc2e243b) [@niklasfasching](https://github.com/niklasfasching)
-* Update go-org to v1.5.0 [0cd55c66](https://github.com/gohugoio/hugo/commit/0cd55c66d370559b66eea220626c4842efaf7039) [@niklasfasching](https://github.com/niklasfasching)
-* bump github.com/jdkato/prose from 1.2.0 to 1.2.1 [0d5cf256](https://github.com/gohugoio/hugo/commit/0d5cf256e4f2a5babcbcf7b49a6818869c3c0691) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/spf13/cobra from 1.1.1 to 1.1.3 [36527576](https://github.com/gohugoio/hugo/commit/36527576b30224dff2eae7f6c9f27eff807d5402) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Add complete dependency list in "hugo env -v" [9b83f45b](https://github.com/gohugoio/hugo/commit/9b83f45b6dcafa6e50df80a4786d6a36400a47fe) [@bep](https://github.com/bep) [#8400](https://github.com/gohugoio/hugo/issues/8400)
-* Add hugo.IsExtended [7fdd2b95](https://github.com/gohugoio/hugo/commit/7fdd2b95e20f322b0a47f63ff1010a04f47ce67b) [@bep](https://github.com/bep) [#8399](https://github.com/gohugoio/hugo/issues/8399)
-* Also test minified HTML in the element collector [3d5dbdcb](https://github.com/gohugoio/hugo/commit/3d5dbdcb1a11b059fc2f93ed6fadb9009bf72673) [@bep](https://github.com/bep) [#7567](https://github.com/gohugoio/hugo/issues/7567)
-* Skip script, pre and textarea content when looking for HTML elements [8a308944](https://github.com/gohugoio/hugo/commit/8a308944e46f8c2aa054005d5aed89f2711f9c1d) [@bep](https://github.com/bep) [#7567](https://github.com/gohugoio/hugo/issues/7567)
-* Add slice syntax to sections permalinks config [2dc222ce](https://github.com/gohugoio/hugo/commit/2dc222cec4460595af8569165d1c498bb45aac84) [@bep](https://github.com/bep) [#8363](https://github.com/gohugoio/hugo/issues/8363)
-* Upgrade github.com/evanw/esbuild v0.9.6 => v0.11.0 [4d22ad58](https://github.com/gohugoio/hugo/commit/4d22ad580ec8c8e5e27cf4f5cce69b6828aa8501) [@bep](https://github.com/bep)
-
-## Fixes
-
-### Templates
-
-* Fix where on type mismatches [e4dc9a82](https://github.com/gohugoio/hugo/commit/e4dc9a82b557a417b1552c533b0df605c6ff1cc0) [@bep](https://github.com/bep) [#8353](https://github.com/gohugoio/hugo/issues/8353)
-
-### Output
-
-* Regression in media type suffix lookup [6e9d2bf0](https://github.com/gohugoio/hugo/commit/6e9d2bf0c936900f8f676d485098755b3f463373) [@bep](https://github.com/bep) [#8406](https://github.com/gohugoio/hugo/issues/8406)
-* Regression in media type suffix lookup [e73f7a77](https://github.com/gohugoio/hugo/commit/e73f7a770dfb06f23d842d589bdd3d0fb53c7eed) [@bep](https://github.com/bep) [#8406](https://github.com/gohugoio/hugo/issues/8406)
-
-### Other
-
-* Fix multiple unknown language codes [7eb80a9e](https://github.com/gohugoio/hugo/commit/7eb80a9e6fcb6d31711effa20310cfefb7b23c1b) [@bep](https://github.com/bep) [#7838](https://github.com/gohugoio/hugo/issues/7838)
-* Fix permalinks pattern detection for some of the sections variants [c13d3687](https://github.com/gohugoio/hugo/commit/c13d368746992eb39a33f065ca808e129baec4ef) [@bep](https://github.com/bep) [#8363](https://github.com/gohugoio/hugo/issues/8363)
-* Fix Params case handling in where with slices of structs (e.g. Pages) [bca40cf0](https://github.com/gohugoio/hugo/commit/bca40cf0c9c7b75e6d5b4a9ac8b927eb17590c7e) [@bep](https://github.com/bep) [#7009](https://github.com/gohugoio/hugo/issues/7009)
-* Fix typo in docshelper.go [7c7974b7](https://github.com/gohugoio/hugo/commit/7c7974b711879938eafc08a2ce242d0f00c8e9e6) [@jmooring](https://github.com/jmooring) [#8380](https://github.com/gohugoio/hugo/issues/8380)
-* Try to fix the fuzz build [5e2f1289](https://github.com/gohugoio/hugo/commit/5e2f1289118dc1489fb782bf289298a05104eeaf) [@bep](https://github.com/bep)
-
-
-
-
-
diff --git a/docs/content/en/news/0.83.1-relnotes/index.md b/docs/content/en/news/0.83.1-relnotes/index.md
deleted file mode 100644
index e896b04e136..00000000000
--- a/docs/content/en/news/0.83.1-relnotes/index.md
+++ /dev/null
@@ -1,19 +0,0 @@
-
----
-date: 2021-05-02
-title: "Hugo 0.83.1: One Bug Fix"
-description: "This version fixes an issue introduced in 0.83.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with one important fix.
-
-* langs/i18n: Fix warning regression in i18n [ececd1b1](https://github.com/gohugoio/hugo/commit/ececd1b122c741567a80acd8d60ccd6356fa5323) [@bep](https://github.com/bep) [#8492](https://github.com/gohugoio/hugo/issues/8492)
-
-
-
diff --git a/docs/content/en/news/0.84.0-relnotes/featured.png b/docs/content/en/news/0.84.0-relnotes/featured.png
deleted file mode 100644
index e733669e33d..00000000000
Binary files a/docs/content/en/news/0.84.0-relnotes/featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.84.0-relnotes/index.md b/docs/content/en/news/0.84.0-relnotes/index.md
deleted file mode 100644
index 8d3dfac24cd..00000000000
--- a/docs/content/en/news/0.84.0-relnotes/index.md
+++ /dev/null
@@ -1,160 +0,0 @@
-
----
-date: 2021-06-18
-title: "Config Revamp"
-description: "Hugo 0.84.0: Deep merge of theme configuration, config dir support now also in themes/modules, HTTP header support in getJSON, and more."
-categories: ["Releases"]
----
-
-**This release brings several configuration fixes and improvements that will be especially useful for themes.**
-
-## Deep merge of theme Params
-
-One of the most common complaints from Hugo theme owners/users has been about the configuration handling. Hugo has up until now only performed a shallow merge of theme `params` into the configuration.
-
-With that, given this example from a theme configuration:
-
-```toml
-[params]
-[params.colours]
-blue="#337DFF"
-green="#68FF33"
-red="#FF3358"
-```
-
-If you would like to use the above theme, but want a different shade of red, you earlier had to copy the entire block, even the colours you're totally happy with. This was painful with even the simplest setup.
-
-Now you can just override the `params` keys you want to change, e.g.:
-
-```toml
-[params]
-[params.colours]
-red="#fc0f03"
-```
-
-For more information, and especially about the way you can opt out of the above behaviour, see [Merge Configuration from Themes](https://gohugo.io/getting-started/configuration/#merge-configuration-from-themes).
-
-## Themes now support the config directory
-
-Now both the project and themes/modules can store its configuration in both the top level config file (e.g. `config.toml`) or in the `config` directory. See [Configuration Directory](https://gohugo.io/getting-started/configuration/#configuration-directory).
-
-## HTTP headers in getJSON/getCSV
-
-`getJSON` now supports custom HTTP headers. This has been a big limitation in Hugo, especially considering the [Authorization](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization) header.
-
-We have updated the internal Instagram shortcode to pass the access token in a header:
-
-```
-{{ $hideCaption := cond (eq (.Get 1) "hidecaption") "1" "0" }}
-{{ $headers := dict "Authorization" (printf "Bearer %s" $accessToken) }}
-{{ with getJSON "https://graph.facebook.com/v8.0/instagram_oembed/?url=https://instagram.com/p/" $id "/&hidecaption=" $hideCaption $headers }}
- {{ .html | safeHTML }}
-{{ end }}
-```
-
- Also see the discussion [this issue](https://github.com/gohugoio/hugo/issues/7879) about the access token above.
-
-## New erroridf template func
-
-Sometimes, especially when creating themes, it is useful to be able to let the user decide if an error situation is critical enough to fail the build. The new `erroridf` produces `ERROR` log statements that can be toggled off:
-
-```html
-{{ erroridf "some-custom-id" "Some error message." }}
-```
-
-Will log:
-
-```
-ERROR: Some error message.
-If you feel that this should not be logged as an ERROR, you can ignore it by adding this to your site config:
-ignoreErrors = ["some-custom-id"]
-```
-## Stats
-
-This release represents **46 contributions by 11 contributors** to the main Hugo code base.[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@jmooring](https://github.com/jmooring), [@satotake](https://github.com/satotake), and [@Seirdy](https://github.com/Seirdy) for their ongoing contributions.
-And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the themes site in pristine condition.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **20 contributions by 10 contributors**. A special thanks to [@salim-b](https://github.com/salim-b), [@bep](https://github.com/bep), [@thomasjsn](https://github.com/thomasjsn), and [@lucasew](https://github.com/lucasew) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 52487+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 432+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 370+ [themes](http://themes.gohugo.io/)
-
-
-## Notes
-
-* We now do deep merging of `params` from theme config(s). That is you most likely what you want, but [Merge Configuration from Themes](https://gohugo.io/getting-started/configuration/#merge-configuration-from-themes) describes how you can get the old behaviour back.
-
-## Enhancements
-
-### Templates
-
-* Rename err-missing-instagram-accesstoken => error-missing-instagram-accesstoken [9096842b](https://github.com/gohugoio/hugo/commit/9096842b0494166e401cc08a70b93ae2ee19a198) [@bep](https://github.com/bep)
-* Add a terse pagination template variant to improve performance [73483d0f](https://github.com/gohugoio/hugo/commit/73483d0f9eb46838d41640f88cc05c1d16811dc5) [@jmooring](https://github.com/jmooring) [#8599](https://github.com/gohugoio/hugo/issues/8599)
-* Add erroridf template func [f55d2f43](https://github.com/gohugoio/hugo/commit/f55d2f43769053b80b419a690554e747dc5dcede) [@bep](https://github.com/bep) [#8613](https://github.com/gohugoio/hugo/issues/8613)
-* Print response body on HTTP errors [282f1aa3](https://github.com/gohugoio/hugo/commit/282f1aa3db9f6420fdd360e46db1ffadd5b083a1) [@bep](https://github.com/bep)
-* Misc header improvements, tests, allow multiple headers of same key [fcd63de3](https://github.com/gohugoio/hugo/commit/fcd63de3a54fadcd30972654d8eb86dc4d889784) [@bep](https://github.com/bep) [#5617](https://github.com/gohugoio/hugo/issues/5617)
-* Allows user-defined HTTP headers with getJSON and getCSV [150d7573](https://github.com/gohugoio/hugo/commit/150d75738b54acddc485d363436757189144da6a) [@chamberlainpj](https://github.com/chamberlainpj) [#5617](https://github.com/gohugoio/hugo/issues/5617)
-* Allow 'Querify' to take lone slice/interface argument [c46fc838](https://github.com/gohugoio/hugo/commit/c46fc838a9320adfc6532b1b543e903c48b3b4cb) [@importhuman](https://github.com/importhuman) [#6735](https://github.com/gohugoio/hugo/issues/6735)
-
-### Output
-
-* Make WebAppManifestFormat NotAlternative=true [643b6719](https://github.com/gohugoio/hugo/commit/643b671931ed5530855e7d4819896790bf3f6c28) [@bep](https://github.com/bep) [#8624](https://github.com/gohugoio/hugo/issues/8624)
-* Adjust test assertion [ab4e1dfa](https://github.com/gohugoio/hugo/commit/ab4e1dfa4eebe0ac18f1d1f60a9647cbb7b41d7f) [@bep](https://github.com/bep) [#8625](https://github.com/gohugoio/hugo/issues/8625)
-* support application/manifest+json [02f31897](https://github.com/gohugoio/hugo/commit/02f31897b4f7252154850a65c900e88e0b237fa3) [@Seirdy](https://github.com/Seirdy) [#8624](https://github.com/gohugoio/hugo/issues/8624)
-
-### Other
-
-* Regenerate docs helper [be6b901c](https://github.com/gohugoio/hugo/commit/be6b901cf7d07238337334e6b6d886a7b039f5e6) [@bep](https://github.com/bep)
-* Regenerate docshelper [402da3f8](https://github.com/gohugoio/hugo/commit/402da3f8f327f97302c4b5d69cd4832a94bd189b) [@bep](https://github.com/bep)
-* Implement configuration in a directory for modules [bb2aa087](https://github.com/gohugoio/hugo/commit/bb2aa08709c812a5be29922a1a7f4d814e200cab) [@bep](https://github.com/bep) [#8654](https://github.com/gohugoio/hugo/issues/8654)
-* Update github.com/alecthomas/chroma v0.9.1 => v0.9.2 [3aa7f0b2](https://github.com/gohugoio/hugo/commit/3aa7f0b27fc736b4c32adbb1fc1fc7fbefd6efd9) [@bep](https://github.com/bep) [#8658](https://github.com/gohugoio/hugo/issues/8658)
-* Run go mod tidy [9b870aa7](https://github.com/gohugoio/hugo/commit/9b870aa788ab1b5159bc836fbac6e60a29bee329) [@bep](https://github.com/bep)
-* Split out the puthe path/filepath functions into common/paths [93aad3c5](https://github.com/gohugoio/hugo/commit/93aad3c543828efca2adeb7f96cf50ae29878593) [@bep](https://github.com/bep) [#8654](https://github.com/gohugoio/hugo/issues/8654)
-* Update to Goldmark v1.3.8 [8eafe084](https://github.com/gohugoio/hugo/commit/8eafe0845d66efd3cf442a8ed89a6da5c1d3117b) [@jmooring](https://github.com/jmooring) [#8648](https://github.com/gohugoio/hugo/issues/8648)
-* Do not read config from os.Environ when running tests [31fb29fb](https://github.com/gohugoio/hugo/commit/31fb29fb3f306678f3697e05bbccefb2078d7f78) [@bep](https://github.com/bep) [#8655](https://github.com/gohugoio/hugo/issues/8655)
-* Set a dummy Instagram token [a886dd53](https://github.com/gohugoio/hugo/commit/a886dd53b80322e1edf924f2ede4d4ea037c5baf) [@bep](https://github.com/bep)
-* Regenerate docs helper [a91cd765](https://github.com/gohugoio/hugo/commit/a91cd7652f7559492b070dbe02fe558348f3d0b6) [@bep](https://github.com/bep)
-* Update to Go 1.16.5, Goreleaser 0.169.0 [552cef5c](https://github.com/gohugoio/hugo/commit/552cef5c576ae4dbf4626f77f3c8b15b42a9e7f3) [@bep](https://github.com/bep) [#8619](https://github.com/gohugoio/hugo/issues/8619)[#8263](https://github.com/gohugoio/hugo/issues/8263)
-* Upgrade Instagram shortcode [9b5debe4](https://github.com/gohugoio/hugo/commit/9b5debe4b820132759cfdf7bff7fe9c1ad0a6bb1) [@bep](https://github.com/bep) [#7879](https://github.com/gohugoio/hugo/issues/7879)
-* Set modTime at creation time [06d29542](https://github.com/gohugoio/hugo/commit/06d295427f798da85de469924fd10f58c0de9a58) [@bep](https://github.com/bep) [#6161](https://github.com/gohugoio/hugo/issues/6161)
-* Add math.Max and math.Min [01758f99](https://github.com/gohugoio/hugo/commit/01758f99b915f34fe7ca4621e4d1ee09efe385b1) [@jmooring](https://github.com/jmooring) [#8583](https://github.com/gohugoio/hugo/issues/8583)
-* Catch incomplete shortcode error [845a7ba4](https://github.com/gohugoio/hugo/commit/845a7ba4fc30c61842148d67d31d0fa3db8f40b9) [@satotake](https://github.com/satotake) [#6866](https://github.com/gohugoio/hugo/issues/6866)
-* Use SPDX license identifier [10f60de8](https://github.com/gohugoio/hugo/commit/10f60de89a5a53528f1e3a47a77224e5c7915e4e) [@jmooring](https://github.com/jmooring) [#8555](https://github.com/gohugoio/hugo/issues/8555)
-* Cache and copy Menu for sorting [785a31b5](https://github.com/gohugoio/hugo/commit/785a31b5b84643f4769f9bd363599cbcce86f098) [@satotake](https://github.com/satotake) [#7594](https://github.com/gohugoio/hugo/issues/7594)
-* Update to LibSASS 3.6.5 [bc1e0528](https://github.com/gohugoio/hugo/commit/bc1e05286a96d08ad02ad200d6a4076bb01c486e) [@bep](https://github.com/bep)
-* Make the HTML element collector more robust [f518b4f7](https://github.com/gohugoio/hugo/commit/f518b4f71e1a61b09d660b5c284121ebf3b3b86b) [@bep](https://github.com/bep) [#8530](https://github.com/gohugoio/hugo/issues/8530)
-* Make the HTML element collector more robust" [dc6b7a75](https://github.com/gohugoio/hugo/commit/dc6b7a75ff5b7fcb8a0b0e3f7ed406422d847624) [@bep](https://github.com/bep)
-* Get the collector in line with the io.Writer interface" [3f515f0e](https://github.com/gohugoio/hugo/commit/3f515f0e3395b24776ae24045b846ff2b33b8906) [@bep](https://github.com/bep)
-* Get the collector in line with the io.Writer interface [a9bcd381](https://github.com/gohugoio/hugo/commit/a9bcd38181ceb79afba82adcd4de1aebf571e74c) [@bep](https://github.com/bep)
-* Make the HTML element collector more robust [ef0f1a72](https://github.com/gohugoio/hugo/commit/ef0f1a726901d6c614040cfc2d7e8f9a2ca97816) [@bep](https://github.com/bep) [#8530](https://github.com/gohugoio/hugo/issues/8530)
-* Add Scratch.DeleteInMap [abbc99d4](https://github.com/gohugoio/hugo/commit/abbc99d4c60b102e2779e4362ceb433095719384) [@meehawk](https://github.com/meehawk) [#8504](https://github.com/gohugoio/hugo/issues/8504)
-* Display version when building site (#8533) [76c95f55](https://github.com/gohugoio/hugo/commit/76c95f55a5d18290baa7f23667161d4af9fb9b53) [@jmooring](https://github.com/jmooring) [#8531](https://github.com/gohugoio/hugo/issues/8531)
-* Update querify function description and examples [2c7f5b62](https://github.com/gohugoio/hugo/commit/2c7f5b62f6c1fa1c7b3cf2c1f3a1663b18e75004) [@jmooring](https://github.com/jmooring)
-* Change SetEscapeHTML to false [504c78da](https://github.com/gohugoio/hugo/commit/504c78da4b5020e1fd13a1195ad38a9e85f8289a) [@peaceiris](https://github.com/peaceiris) [#8512](https://github.com/gohugoio/hugo/issues/8512)
-* Add a benchmark [b660ea8d](https://github.com/gohugoio/hugo/commit/b660ea8d545d6ba5479dd28a670044d57e5d196f) [@bep](https://github.com/bep)
-* Update dependency list [64f88f30](https://github.com/gohugoio/hugo/commit/64f88f3011de5a510d8e6d6bad8ac4a091b11c0c) [@bep](https://github.com/bep)
-
-## Fixes
-
-### Templates
-
-* Fix countwords to handle special chars [7a2c10ae](https://github.com/gohugoio/hugo/commit/7a2c10ae60f096dacee4b44e0c8ae0a1b66ae033) [@ResamVi](https://github.com/ResamVi) [#8479](https://github.com/gohugoio/hugo/issues/8479)
-
-### Other
-
-* Fix fill with smartcrop sometimes returning 0 bytes images [5af045eb](https://github.com/gohugoio/hugo/commit/5af045ebab109d3e5501b8b6d9fd448840c96c9a) [@bep](https://github.com/bep) [#7955](https://github.com/gohugoio/hugo/issues/7955)
-* Misc config loading fixes [d392893c](https://github.com/gohugoio/hugo/commit/d392893cd73dc00c927f342778f6dca9628d328e) [@bep](https://github.com/bep) [#8633](https://github.com/gohugoio/hugo/issues/8633)[#8618](https://github.com/gohugoio/hugo/issues/8618)[#8630](https://github.com/gohugoio/hugo/issues/8630)[#8591](https://github.com/gohugoio/hugo/issues/8591)[#6680](https://github.com/gohugoio/hugo/issues/6680)[#5192](https://github.com/gohugoio/hugo/issues/5192)
-* Fix nested OS env config override when parent does not exist [12530519](https://github.com/gohugoio/hugo/commit/12530519d8fb4513c9c18a6494099b7dff8e4fd4) [@bep](https://github.com/bep) [#8618](https://github.com/gohugoio/hugo/issues/8618)
-* Fix invalid timestamp of the "public" folder [26ae12c0](https://github.com/gohugoio/hugo/commit/26ae12c0c64b847d24bde60d7d710ea2efcb40d4) [@anthonyfok](https://github.com/anthonyfok) [#6161](https://github.com/gohugoio/hugo/issues/6161)
-* Fix env split to allow = character in values [ee733085](https://github.com/gohugoio/hugo/commit/ee733085b7f5d3f2aef1667901ab6ecb8041d699) [@xqbumu](https://github.com/xqbumu) [#8589](https://github.com/gohugoio/hugo/issues/8589)
-* Fix warning regression in i18n [ececd1b1](https://github.com/gohugoio/hugo/commit/ececd1b122c741567a80acd8d60ccd6356fa5323) [@bep](https://github.com/bep) [#8492](https://github.com/gohugoio/hugo/issues/8492)
-
-
-
-
-
diff --git a/docs/content/en/news/0.84.1-relnotes/index.md b/docs/content/en/news/0.84.1-relnotes/index.md
deleted file mode 100644
index 3cee1e29010..00000000000
--- a/docs/content/en/news/0.84.1-relnotes/index.md
+++ /dev/null
@@ -1,21 +0,0 @@
-
----
-date: 2021-06-24
-title: "Hugo 0.84.1: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.84.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-* Fix language menu config regression [093dacab](https://github.com/gohugoio/hugo/commit/093dacab29a3c6fc363408453d0bc3b1fc159ad5) [@bep](https://github.com/bep) [#8672](https://github.com/gohugoio/hugo/issues/8672)
-* config: Fix merge of config with map[string]string values. [4a9d408f](https://github.com/gohugoio/hugo/commit/4a9d408fe0bbf4c563546e35d2be7ade4e920c4c) [@bep](https://github.com/bep) [#8679](https://github.com/gohugoio/hugo/issues/8679)
-* markup: Rename Header(s) to Heading(s) in ToC struct [a7e3da24](https://github.com/gohugoio/hugo/commit/a7e3da242f98d4799dad013d7ba2f285717640d6) [@bep](https://github.com/bep)
-
-
-
diff --git a/docs/content/en/news/0.84.2-relnotes/index.md b/docs/content/en/news/0.84.2-relnotes/index.md
deleted file mode 100644
index d2469ff0c00..00000000000
--- a/docs/content/en/news/0.84.2-relnotes/index.md
+++ /dev/null
@@ -1,26 +0,0 @@
-
----
-date: 2021-06-28
-title: "Hugo 0.84.2: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.84.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is mostly a bug fix release, but it also contains some minor modules related improvements. Most notable you now get some more information in ` hugo config mounts`, and even more so when typing ` hugo config mounts -v`.
-
-* modules: Add module.import.noMounts config [40dfdd09](https://github.com/gohugoio/hugo/commit/40dfdd09521bcb8f56150e6791d60445198f27ab) [@bep](https://github.com/bep) [#8708](https://github.com/gohugoio/hugo/issues/8708)
-* modules: Use value type for module.Time [3a6dc6d3](https://github.com/gohugoio/hugo/commit/3a6dc6d3f423c4acb79ef21b5a76e616fa2c9477) [@bep](https://github.com/bep)
-* commands: Add version time to "hugo config mounts" [6cd2110a](https://github.com/gohugoio/hugo/commit/6cd2110ab295f598907a18da91e34d31407c1d9d) [@bep](https://github.com/bep)
-* commands: Add some more info to "hugo config mounts" [6a365c27](https://github.com/gohugoio/hugo/commit/6a365c2712c7607e067e192d213b266f0c88d0f3) [@bep](https://github.com/bep)
-* Fix config handling with empty config entries after merge [19aa95fc](https://github.com/gohugoio/hugo/commit/19aa95fc7f4cd58dcc8a8ff075762cfc86d41dc3) [@bep](https://github.com/bep) [#8701](https://github.com/gohugoio/hugo/issues/8701)
-* Fix config loading for "hugo mod init" [923dd9d1](https://github.com/gohugoio/hugo/commit/923dd9d1c1f649142f3f377109318b07e0f44d5d) [@bep](https://github.com/bep) [#8697](https://github.com/gohugoio/hugo/issues/8697)
-* deps: Update to Minify v2.9.18 [d9bdd37d](https://github.com/gohugoio/hugo/commit/d9bdd37d35ccd436b4dd470ef99efa372a6a086b) [@bep](https://github.com/bep) [#8693](https://github.com/gohugoio/hugo/issues/8693)
-* Remove credit from release notes [b2eaf4c8](https://github.com/gohugoio/hugo/commit/b2eaf4c8c2e31aa1c1bc4a2c0061f661e01d2de1) [@digitalcraftsman](https://github.com/digitalcraftsman)
-
-
-
diff --git a/docs/content/en/news/0.84.3-relnotes/index.md b/docs/content/en/news/0.84.3-relnotes/index.md
deleted file mode 100644
index c805efb46e8..00000000000
--- a/docs/content/en/news/0.84.3-relnotes/index.md
+++ /dev/null
@@ -1,20 +0,0 @@
-
----
-date: 2021-06-29
-title: "Hugo 0.84.3: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.84.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-* config: Fix Netlify default cache dir logic [6c8c0c8b](https://github.com/gohugoio/hugo/commit/6c8c0c8b6a0b39b91de44d72a7bd1cd49534a0f1) [@bep](https://github.com/bep) [#8710](https://github.com/gohugoio/hugo/issues/8710)
-* config: Fix handling of invalid OS env config overrides [49fedbc5](https://github.com/gohugoio/hugo/commit/49fedbc51cafa64e4eb0eae9fb79ccbe2d4c6774) [@bep](https://github.com/bep) [#8709](https://github.com/gohugoio/hugo/issues/8709)
-
-
-
diff --git a/docs/content/en/news/0.84.4-relnotes/index.md b/docs/content/en/news/0.84.4-relnotes/index.md
deleted file mode 100644
index a04e4251da8..00000000000
--- a/docs/content/en/news/0.84.4-relnotes/index.md
+++ /dev/null
@@ -1,20 +0,0 @@
-
----
-date: 2021-07-01
-title: "Hugo 0.84.4: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.84.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-* Fix Cloudflare vs Netlify cache dir issue [4c8552b1](https://github.com/gohugoio/hugo/commit/4c8552b11477141777101e0e0609dd1f32d191e9) [@bep](https://github.com/bep) [#8714](https://github.com/gohugoio/hugo/issues/8714)
-* Fix date format in schema and opengraph templates [34e4742f](https://github.com/gohugoio/hugo/commit/34e4742f0caab0d3eb9efd00fce4157d112617b5) [@jmooring](https://github.com/jmooring) [#8671](https://github.com/gohugoio/hugo/issues/8671)
-
-
-
diff --git a/docs/content/en/news/0.85.0-relnotes/featured.png b/docs/content/en/news/0.85.0-relnotes/featured.png
deleted file mode 100644
index eb68d317180..00000000000
Binary files a/docs/content/en/news/0.85.0-relnotes/featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.85.0-relnotes/index.md b/docs/content/en/news/0.85.0-relnotes/index.md
deleted file mode 100644
index e0c25e53718..00000000000
--- a/docs/content/en/news/0.85.0-relnotes/index.md
+++ /dev/null
@@ -1,70 +0,0 @@
-
----
-date: 2021-07-05
-title: "Poll based watching"
-description: "Hugo 0.85.0: Polled based alternative when watching for changes and some other nice improvements."
-categories: ["Releases"]
----
-
-Hugo `0.85.0` is on the smaller side of releases, but the main new thing it brings should be important to those who need it: Poll based watching the filesystem for changes.
-
-Hugo uses [Fsnotify](https://github.com/fsnotify/fsnotify) to provide native file system notifications. This is still the default, but there may situations where this isn't working. The file may not support it (e.g. NFS), or you get the "too many open files" error and cannot or do not want to increase the `ulimit`. Enable polling by passing the `--poll` flag with an interval:
-
-```bash
-hugo server --poll 700ms
-```
-
-You can even do "long polling" by passing a long interval:
-
-```bash
-hugo server --poll 24h
-```
-
-This release represents **23 contributions by 6 contributors** to the main Hugo code base.[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@raoulb](https://github.com/raoulb), [@jmooring](https://github.com/jmooring), and [@digitalcraftsman](https://github.com/digitalcraftsman) for their ongoing contributions.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **1 contributions by 1 contributors**.
-
-Hugo now has:
-
-* 52755+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 431+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 370+ [themes](http://themes.gohugo.io/)
-
-## Enhancements
-
-### Other
-
-* Move time notification to after any build errors [04dc469f](https://github.com/gohugoio/hugo/commit/04dc469fbd78d9fe784829f2cba61c8cce982bdb) [@jhollowe](https://github.com/jhollowe) [#8403](https://github.com/gohugoio/hugo/issues/8403)
-* Log warning for metadata decode error [07919d1c](https://github.com/gohugoio/hugo/commit/07919d1ccb01733f4c6c5952e59228cecc9b26c8) [@IveGotNorto](https://github.com/IveGotNorto) [#8519](https://github.com/gohugoio/hugo/issues/8519)
-* Make the --poll flag a duration [e31b1d19](https://github.com/gohugoio/hugo/commit/e31b1d194655ac3a38fe903ff3995806b129b88a) [@bep](https://github.com/bep) [#8720](https://github.com/gohugoio/hugo/issues/8720)
-* Regen CLI docs [43a23239](https://github.com/gohugoio/hugo/commit/43a23239b2e3ad602c06d9af0b648e0304fc8744) [@bep](https://github.com/bep)
-* Add polling as a fallback to native filesystem events in server watch [24ce98b6](https://github.com/gohugoio/hugo/commit/24ce98b6d10b2088af61c15112f5c5ed915a0c35) [@bep](https://github.com/bep) [#8720](https://github.com/gohugoio/hugo/issues/8720)[#6849](https://github.com/gohugoio/hugo/issues/6849)[#7930](https://github.com/gohugoio/hugo/issues/7930)
-* Bump github.com/yuin/goldmark v1.3.9 [0019d60f](https://github.com/gohugoio/hugo/commit/0019d60f67b6c4dde085753641a917fcd0aa4c76) [@bep](https://github.com/bep) [#8727](https://github.com/gohugoio/hugo/issues/8727)
-* Add module.import.noMounts config [40dfdd09](https://github.com/gohugoio/hugo/commit/40dfdd09521bcb8f56150e6791d60445198f27ab) [@bep](https://github.com/bep) [#8708](https://github.com/gohugoio/hugo/issues/8708)
-* Use value type for module.Time [3a6dc6d3](https://github.com/gohugoio/hugo/commit/3a6dc6d3f423c4acb79ef21b5a76e616fa2c9477) [@bep](https://github.com/bep)
-* Add version time to "hugo config mounts" [6cd2110a](https://github.com/gohugoio/hugo/commit/6cd2110ab295f598907a18da91e34d31407c1d9d) [@bep](https://github.com/bep)
-* Add some more info to "hugo config mounts" [6a365c27](https://github.com/gohugoio/hugo/commit/6a365c2712c7607e067e192d213b266f0c88d0f3) [@bep](https://github.com/bep)
-* Update to Minify v2.9.18 [d9bdd37d](https://github.com/gohugoio/hugo/commit/d9bdd37d35ccd436b4dd470ef99efa372a6a086b) [@bep](https://github.com/bep) [#8693](https://github.com/gohugoio/hugo/issues/8693)
-* Remove credit from release notes [b2eaf4c8](https://github.com/gohugoio/hugo/commit/b2eaf4c8c2e31aa1c1bc4a2c0061f661e01d2de1) [@digitalcraftsman](https://github.com/digitalcraftsman)
-* Rename Header(s) to Heading(s) in ToC struct [a7e3da24](https://github.com/gohugoio/hugo/commit/a7e3da242f98d4799dad013d7ba2f285717640d6) [@bep](https://github.com/bep)
-
-## Fixes
-
-### Other
-
-* Fix tab selection of disabled items in internal pagination template [f75f9007](https://github.com/gohugoio/hugo/commit/f75f90079a6f2a239c8186faba5db5dbe6e36cb6) [@raoulb](https://github.com/raoulb)
-* Fix panic when theme has permalinks config [e451b984](https://github.com/gohugoio/hugo/commit/e451b984cfb45b54a3972cefa59a02d50b0b0fd2) [@bep](https://github.com/bep) [#8724](https://github.com/gohugoio/hugo/issues/8724)
-* Fix Cloudflare vs Netlify cache dir issue [4c8552b1](https://github.com/gohugoio/hugo/commit/4c8552b11477141777101e0e0609dd1f32d191e9) [@bep](https://github.com/bep) [#8714](https://github.com/gohugoio/hugo/issues/8714)
-* Fix date format in schema and opengraph templates [34e4742f](https://github.com/gohugoio/hugo/commit/34e4742f0caab0d3eb9efd00fce4157d112617b5) [@jmooring](https://github.com/jmooring) [#8671](https://github.com/gohugoio/hugo/issues/8671)
-* Fix Netlify default cache dir logic [6c8c0c8b](https://github.com/gohugoio/hugo/commit/6c8c0c8b6a0b39b91de44d72a7bd1cd49534a0f1) [@bep](https://github.com/bep) [#8710](https://github.com/gohugoio/hugo/issues/8710)
-* Fix handling of invalid OS env config overrides [49fedbc5](https://github.com/gohugoio/hugo/commit/49fedbc51cafa64e4eb0eae9fb79ccbe2d4c6774) [@bep](https://github.com/bep) [#8709](https://github.com/gohugoio/hugo/issues/8709)
-* Fix config handling with empty config entries after merge [19aa95fc](https://github.com/gohugoio/hugo/commit/19aa95fc7f4cd58dcc8a8ff075762cfc86d41dc3) [@bep](https://github.com/bep) [#8701](https://github.com/gohugoio/hugo/issues/8701)
-* Fix config loading for "hugo mod init" [923dd9d1](https://github.com/gohugoio/hugo/commit/923dd9d1c1f649142f3f377109318b07e0f44d5d) [@bep](https://github.com/bep) [#8697](https://github.com/gohugoio/hugo/issues/8697)
-* Fix language menu config regression [093dacab](https://github.com/gohugoio/hugo/commit/093dacab29a3c6fc363408453d0bc3b1fc159ad5) [@bep](https://github.com/bep) [#8672](https://github.com/gohugoio/hugo/issues/8672)
-* Fix merge of config with map[string]string values. [4a9d408f](https://github.com/gohugoio/hugo/commit/4a9d408fe0bbf4c563546e35d2be7ade4e920c4c) [@bep](https://github.com/bep) [#8679](https://github.com/gohugoio/hugo/issues/8679)
-
-
-
-
-
diff --git a/docs/content/en/news/0.86.0-relnotes/featured.png b/docs/content/en/news/0.86.0-relnotes/featured.png
deleted file mode 100644
index e9967e864b7..00000000000
Binary files a/docs/content/en/news/0.86.0-relnotes/featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.86.0-relnotes/index.md b/docs/content/en/news/0.86.0-relnotes/index.md
deleted file mode 100644
index 4b717ed7cab..00000000000
--- a/docs/content/en/news/0.86.0-relnotes/index.md
+++ /dev/null
@@ -1,56 +0,0 @@
-
----
-date: 2021-07-21
-title: "Hugo 0.86.0: Cascade in Config"
-description: "Hugo 0.86.0 adds cascade keyword to site config, much improved \"active menu item\" logic for section pages, and more."
-categories: ["Releases"]
----
-
-This release is a set of smaller fixes and improvements. Some of the more notable:
-
-You can now have a top level [cascade](https://gohugo.io/content-management/front-matter#front-matter-cascade) (or one per language, if needed) section in your site configuration (e.g. `config.toml`). This way you can control default front matter values from outside of the content files.
-
-Hugo's [Menu system](https://gohugo.io/content-management/menus/) works well, but hasn't been particularly easy to set the active menu state for section pages without a menu definition. We have had the option [Section Menu for Lazy Bloggers](https://gohugo.io/templates/menu-templates/#section-menu-for-lazy-bloggers). That helped for the common case, but we have now made it behave more sensible out of the box: `$page.HasMenuCurrent $sectionMenuEntry` will now always return true for any descendant of that section. To support this for menu definitions in the site config, we have added a new `pageRef` option on [MenuEntry](https://gohugo.io/variables/menus/#menu-entry-variables):
-
-```toml
-[[menus.main]]
-pageRef = "blog"
-# When pageRef is set, setting `url` is optional; it will be used as a fallback if the page is not found.
-url = "/blog"
-```
-
-Set `pageRef` to a value that [site.GetPage](https://gohugo.io/functions/getpage/) understands, and the menu entry will be correctly connected to the page.
-
-This release represents **14 contributions by 2 contributors** to the main Hugo code base.
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **1 contributions by 1 contributors**.
-
-Hugo now has:
-
-* 53005+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 431+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 370+ [themes](http://themes.gohugo.io/)
-
-## Enhancements
-
-### Other
-
-* Simplify "active menu" logic for section menus [d831d2fc](https://github.com/gohugoio/hugo/commit/d831d2fce8198fb814ea4d3d8c311db5c388d04c) [@bep](https://github.com/bep) [#8776](https://github.com/gohugoio/hugo/issues/8776)
-* Make keepWhitespace = true default for HTML [c19f65f9](https://github.com/gohugoio/hugo/commit/c19f65f956739ab76c38222d48a3e461525e31af) [@bep](https://github.com/bep) [#8771](https://github.com/gohugoio/hugo/issues/8771)
-* Make FileMeta a struct [022c4795](https://github.com/gohugoio/hugo/commit/022c4795510306e08a4aba31504ca382d41c7fac) [@bep](https://github.com/bep) [#8749](https://github.com/gohugoio/hugo/issues/8749)
-* Add tabindex="0" to default
wrapper [f27e5424](https://github.com/gohugoio/hugo/commit/f27e542442d19436f1428cc22bb03aca398d37a7) [@rhymes](https://github.com/rhymes) [#7194](https://github.com/gohugoio/hugo/issues/7194)
-* Rename/reorder the hook methods [80566481](https://github.com/gohugoio/hugo/commit/805664818d0e1f95a3474271c2db3e5f49db26ba) [@bep](https://github.com/bep) [#8755](https://github.com/gohugoio/hugo/issues/8755)
-* Support auto links in render hook [ee3d2bb1](https://github.com/gohugoio/hugo/commit/ee3d2bb1d3974584f47cde7c973fbd1ae1f512b6) [@bep](https://github.com/bep) [#8755](https://github.com/gohugoio/hugo/issues/8755)
-* Adjust a test helper [eb2a5003](https://github.com/gohugoio/hugo/commit/eb2a500367780b07d67c301ce7c866e6b67aa687) [@bep](https://github.com/bep)
-* Add config.cascade [5cb52c23](https://github.com/gohugoio/hugo/commit/5cb52c23150032b3fdb211a095745c512369b463) [@bep](https://github.com/bep) [#8741](https://github.com/gohugoio/hugo/issues/8741)
-* Regenerate image golden testdata [30eea391](https://github.com/gohugoio/hugo/commit/30eea3915b67f72611a3b2f4547146d4c6a96864) [@bep](https://github.com/bep) [#8729](https://github.com/gohugoio/hugo/issues/8729)
-
-## Fixes
-
-### Other
-
-* Fix panic on invalid config in "hugo mod get" and similar [351ed0f5](https://github.com/gohugoio/hugo/commit/351ed0f569f96aff29b03925bf5154d80a164e00) [@bep](https://github.com/bep) [#8773](https://github.com/gohugoio/hugo/issues/8773)
-* Fix Params case handling for menu items defined in site config [634481ba](https://github.com/gohugoio/hugo/commit/634481ba8cfcd865ba0d8811d8834f6af45663d7) [@bep](https://github.com/bep) [#8775](https://github.com/gohugoio/hugo/issues/8775)
-* Fix default values when loading from config dir [ae6cf93c](https://github.com/gohugoio/hugo/commit/ae6cf93c84c3584b111f4b9fa3fb4e3f63d37915) [@bep](https://github.com/bep) [#8763](https://github.com/gohugoio/hugo/issues/8763)
-* Fix the deprecation error/warn log levels [a70da2b7](https://github.com/gohugoio/hugo/commit/a70da2b74a6af0834cce9668cdb6acdb1c86a4c0) [@bep](https://github.com/bep)
-* Fix transparency problem when converting 32-bit images to WebP [8f40f34c](https://github.com/gohugoio/hugo/commit/8f40f34cd10a98598bb822ec633fd5d0ea64b612) [@bep](https://github.com/bep) [#8729](https://github.com/gohugoio/hugo/issues/8729)
diff --git a/docs/content/en/news/0.86.1-relnotes/index.md b/docs/content/en/news/0.86.1-relnotes/index.md
deleted file mode 100644
index 68a893bfbde..00000000000
--- a/docs/content/en/news/0.86.1-relnotes/index.md
+++ /dev/null
@@ -1,19 +0,0 @@
-
----
-date: 2021-07-30
-title: "Hugo 0.86.1: One Bug Fix"
-description: "This version fixes a bug introduced in 0.86.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with one important fix.
-
-* config: Fix a potential deadlock in config reading [94b616bd](https://github.com/gohugoio/hugo/commit/94b616bdfad177daa99f5e87535943f509198f6f) [@bep](https://github.com/bep) [#8791](https://github.com/gohugoio/hugo/issues/8791)
-
-
-
diff --git a/docs/content/en/news/0.87.0-relnotes/featured.png b/docs/content/en/news/0.87.0-relnotes/featured.png
deleted file mode 100644
index 21d20985745..00000000000
Binary files a/docs/content/en/news/0.87.0-relnotes/featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.87.0-relnotes/index.md b/docs/content/en/news/0.87.0-relnotes/index.md
deleted file mode 100644
index e033aaa82c6..00000000000
--- a/docs/content/en/news/0.87.0-relnotes/index.md
+++ /dev/null
@@ -1,90 +0,0 @@
-
----
-date: 2021-08-03
-title: "Localized Time and Dates and Numbers"
-description: "Hugo 0.87.0 brings time zone support, localized time and dates and numbers backed by CLDR, and more."
-categories: ["Releases"]
----
-
-Hugo `0.87` brings two long sought-after features: Default time zone support (per language if needed) for dates without zone offset or location info, and localized time and dates and numbers (backed by [CLDR](https://en.wikipedia.org/wiki/Common_Locale_Data_Repository)).
-
-For more information, see:
-
-* The [time zone config](https://gohugo.io/getting-started/configuration/#timezone) documentation.
-* The [time.Format](https://gohugo.io/functions/dateformat/) documentation. This function will now give you localized dates (with weekdays and months in the current language). It supports all of Go's layout syntax, but also some predefined constants, e.g. `{{ .Date | time.Format ":date_long" }}`.
-* A set of new [localized number formatting ](https://gohugo.io/functions/lang/) functions.
-
-Also in this release, we have switched to using [go-toml](https://github.com/pelletier/go-toml) for all things TOML in Hugo. A big thanks to [@pelletier](https://github.com/pelletier) for his work on the `v2` version. It's both faster than what we had and [TOML v1.0.0](https://toml.io/en/v1.0.0) compliant.
-
-This release represents **40 contributions by 4 contributors** to the main Hugo code base.[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@dependabot[bot]](https://github.com/apps/dependabot), [@digitalcraftsman](https://github.com/digitalcraftsman), and [@jmooring](https://github.com/jmooring) for their ongoing contributions.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **1 contributions by 1 contributors**.
-
-Hugo now has:
-
-* 53261+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 430+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 395+ [themes](http://themes.gohugo.io/)
-
-
-## Notes
-
-* Deprecate Blackfriday and fix a potential deadlock in config [c7252224](https://github.com/gohugoio/hugo/commit/c7252224c4fecfe8321f31b901e2510d98b657c4) [@bep](https://github.com/bep) [#8792](https://github.com/gohugoio/hugo/issues/8792)[#8791](https://github.com/gohugoio/hugo/issues/8791)
-
-## Enhancements
-
-### Templates
-
-* Adjust tests to handle matching local time zones [9ff17c33](https://github.com/gohugoio/hugo/commit/9ff17c332405da5830cef9b3711706b1fc9a7444) [@bep](https://github.com/bep) [#8843](https://github.com/gohugoio/hugo/issues/8843)
-* Add new localized versions of lang.FormatNumber etc. [7907d24b](https://github.com/gohugoio/hugo/commit/7907d24ba16fc5a80930c1aabf5144e684ff7f29) [@bep](https://github.com/bep) [#8820](https://github.com/gohugoio/hugo/issues/8820)
-
-### Other
-
-* Make sure module config loading errors have file positioning info [d70c4857](https://github.com/gohugoio/hugo/commit/d70c485707edfd445bcfc0e84181bc15eb146e76) [@bep](https://github.com/bep) [#8845](https://github.com/gohugoio/hugo/issues/8845)
-* Remove superflous replace statement [7aaaf7e3](https://github.com/gohugoio/hugo/commit/7aaaf7e33afd05d2c74d74fbbfbd34d55e8129eb) [@bep](https://github.com/bep)
-* Reduce binary size vs locale, update to CLDR v36.1 [3a966555](https://github.com/gohugoio/hugo/commit/3a96655592d0b0db4126f20ca717d553dda9c4ed) [@bep](https://github.com/bep) [#8839](https://github.com/gohugoio/hugo/issues/8839)[#8841](https://github.com/gohugoio/hugo/issues/8841)
-* Update github.com/tdewolff/minify/v2 v2.9.20 => v2.9.21 [9a7383ca](https://github.com/gohugoio/hugo/commit/9a7383caf3945b9b11db2b108003f87e2e8b6a3a) [@jmooring](https://github.com/jmooring) [#8831](https://github.com/gohugoio/hugo/issues/8831)
-* Fail on invalid time zone [4d221ce4](https://github.com/gohugoio/hugo/commit/4d221ce468a1209ee9dd6cbece9d1273dad6a29b) [@bep](https://github.com/bep) [#8832](https://github.com/gohugoio/hugo/issues/8832)
-* Improve handling of Params [e3dc5240](https://github.com/gohugoio/hugo/commit/e3dc5240f01fd5ec67643e40f27c026d707da110) [@bep](https://github.com/bep) [#8825](https://github.com/gohugoio/hugo/issues/8825)
-* Merge branch 'release-0.86.1' [268065cb](https://github.com/gohugoio/hugo/commit/268065cb2d8339392766a23703beaf7cc49d6b5c) [@bep](https://github.com/bep)
-* bump github.com/evanw/esbuild from 0.12.16 to 0.12.17 [e90b3591](https://github.com/gohugoio/hugo/commit/e90b3591a155d1266a86c9490886720740b9d62e) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/getkin/kin-openapi from 0.67.0 to 0.68.0 [4b7da6a9](https://github.com/gohugoio/hugo/commit/4b7da6a9d720ed5ab4b45d6aa3b0b7af4683d02f) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Go back to WARNING for Page deprecations [726fe9c3](https://github.com/gohugoio/hugo/commit/726fe9c3c97a9c979dc7862e7f226fc5ec1341de) [@bep](https://github.com/bep)
-* Handle toml.LocalDate and toml.LocalDateTime in front matter [b5de37ee](https://github.com/gohugoio/hugo/commit/b5de37ee793c01f2acccdea7119be05c4182723f) [@bep](https://github.com/bep) [#8801](https://github.com/gohugoio/hugo/issues/8801)
-* Upgrade github.com/pelletier/go-toml/v2 v2.0.0-beta.3 => v2.0.0-beta.3.0.20210727221244-fa0796069526 [bf301daf](https://github.com/gohugoio/hugo/commit/bf301daf158e5e9673ad5f457ea3a264315942b5) [@bep](https://github.com/bep)
-* Switch to go-toml v2 [a3701e09](https://github.com/gohugoio/hugo/commit/a3701e09313695d4a0f6fb0eb7844c1a4befc07a) [@bep](https://github.com/bep) [#8801](https://github.com/gohugoio/hugo/issues/8801)
-* bump github.com/tdewolff/minify/v2 from 2.9.19 to 2.9.20 [40b6016c](https://github.com/gohugoio/hugo/commit/40b6016cf3f7aac541b042d32e3a162411fd9cd0) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Add a TOML front matter benchmark [7e130571](https://github.com/gohugoio/hugo/commit/7e1305710f08d26d9214abb5410ccd675e59a6e9) [@bep](https://github.com/bep)
-* Add timezone support for front matter dates without one [efa5760d](https://github.com/gohugoio/hugo/commit/efa5760db5ef39ae084bfccb5b8f756c7b117a2a) [@bep](https://github.com/bep) [#8810](https://github.com/gohugoio/hugo/issues/8810)
-* Localize time.Format [a57dda85](https://github.com/gohugoio/hugo/commit/a57dda854b5efd3429af5f0b1564fc9d9d5439b9) [@bep](https://github.com/bep) [#8797](https://github.com/gohugoio/hugo/issues/8797)
-* bump github.com/getkin/kin-openapi from 0.61.0 to 0.67.0 [f9afba93](https://github.com/gohugoio/hugo/commit/f9afba933579de07d2d2e36a457895ec5f1b7f01) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/spf13/cast from 1.3.1 to 1.4.0 [a5d2ba42](https://github.com/gohugoio/hugo/commit/a5d2ba429d34004efd3c6b82c1bcb130c85aca9c) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump google.golang.org/api from 0.45.0 to 0.51.0 [31972f36](https://github.com/gohugoio/hugo/commit/31972f3647b284eea1a66a2e27ed42d04a391a7a) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/sanity-io/litter from 1.5.0 to 1.5.1 [2e58782f](https://github.com/gohugoio/hugo/commit/2e58782f96972487dc5e5ba91d0256ec6e86dad7) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/mattn/go-isatty from 0.0.12 to 0.0.13 [7b68f652](https://github.com/gohugoio/hugo/commit/7b68f6524d24d450330cbe4a2380301e66abee4a) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/spf13/cobra from 1.1.3 to 1.2.1 [81265af2](https://github.com/gohugoio/hugo/commit/81265af2cccd3247df87f05eebf8907a14e978a4) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/mitchellh/mapstructure from 1.3.3 to 1.4.1 [c102c971](https://github.com/gohugoio/hugo/commit/c102c9719b3a29406ef59dc18eca6bd280e4dc43) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/google/go-cmp from 0.5.5 to 0.5.6 [7c0f904f](https://github.com/gohugoio/hugo/commit/7c0f904f29c41e8782b44a37fd4e98e441cd2b2c) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/mitchellh/hashstructure from 1.0.0 to 1.1.0 [b2fbd4d1](https://github.com/gohugoio/hugo/commit/b2fbd4d13a47ce3f6a56f08d0bda77e16793de72) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/gobuffalo/flect from 0.2.2 to 0.2.3 [90041d1b](https://github.com/gohugoio/hugo/commit/90041d1b6d4eeb91ea085f5a97b02887159a655b) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/pelletier/go-toml from 1.9.0 to 1.9.3 [05047096](https://github.com/gohugoio/hugo/commit/05047096f52e43ff09acbc50616441bb42a1c6f7) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/aws/aws-sdk-go from 1.38.23 to 1.40.8 [a469156e](https://github.com/gohugoio/hugo/commit/a469156ea4ad023aa4fda0d3fb657ce003412abb) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/tdewolff/minify/v2 from 2.9.18 to 2.9.19 [18fdd85b](https://github.com/gohugoio/hugo/commit/18fdd85bcc4ac2d9a33546dca8a0a24f63987361) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Update github.com/evanw/esbuild v0.11.16 => v0.12.16 [aeb1935d](https://github.com/gohugoio/hugo/commit/aeb1935d44eb258a794c8f055eedaf3a7655a3ad) [@bep](https://github.com/bep)
-* Update github.com/yuin/goldmark v1.3.9 => v1.4.0 [e09d7882](https://github.com/gohugoio/hugo/commit/e09d7882c88812bedb2c2e66b68c7eed21213dbc) [@bep](https://github.com/bep)
-* bump github.com/frankban/quicktest from 1.12.0 to 1.13.0 [15c0eed0](https://github.com/gohugoio/hugo/commit/15c0eed0487598ac1e5a6fff167b19031b6595bc) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Bump all long-living deprecations to ERRORs [91cbb963](https://github.com/gohugoio/hugo/commit/91cbb963020ac2aead68ff2bbd7e9077d5558abd) [@bep](https://github.com/bep)
-
-## Fixes
-
-### Other
-
-* Fix error handling for the time func alias [6c70e1f2](https://github.com/gohugoio/hugo/commit/6c70e1f22f365322d5f754302e110c9ed716b215) [@bep](https://github.com/bep) [#8835](https://github.com/gohugoio/hugo/issues/8835)
-* Fix a potential deadlock in config reading [94b616bd](https://github.com/gohugoio/hugo/commit/94b616bdfad177daa99f5e87535943f509198f6f) [@bep](https://github.com/bep) [#8791](https://github.com/gohugoio/hugo/issues/8791)
-* Fix theme count in release notes [a352d19d](https://github.com/gohugoio/hugo/commit/a352d19d881474f53d01791be4febd305453a9d6) [@digitalcraftsman](https://github.com/digitalcraftsman)
-
-
-
-
-
diff --git a/docs/content/en/news/0.88.0-relnotes/featured.png b/docs/content/en/news/0.88.0-relnotes/featured.png
deleted file mode 100644
index 94af243dc4e..00000000000
Binary files a/docs/content/en/news/0.88.0-relnotes/featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.88.0-relnotes/index.md b/docs/content/en/news/0.88.0-relnotes/index.md
deleted file mode 100644
index 9dfe9e681ec..00000000000
--- a/docs/content/en/news/0.88.0-relnotes/index.md
+++ /dev/null
@@ -1,62 +0,0 @@
-
----
-date: 2021-09-02
-title: "Go 1.17 Update"
-description: "Hugo 0.88.0 brings Go 1.17 support, a dependency refresh, and more."
-categories: ["Releases"]
----
-
-The most important piece in this release is the Go 1.17 update. This release is built with that new Go version, but also and more importantly, the Hugo Modules logic has been updated to support Go's new way of (lazy) loading transitive dependencies. If you already have Go 1.17 installed, building existing Hugo Modules backed projects have not been an issue, but `hugo mod init` for a new project could give you _too new_ versions of transitive dependencies. Hugo 0.88 fixes this.
-
-This release represents **26 contributions by 6 contributors** to the main Hugo code base.[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@dependabot[bot]](https://github.com/apps/dependabot), [@helfper](https://github.com/helfper), and [@wzshiming](https://github.com/wzshiming) for their ongoing contributions.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **9 contributions by 6 contributors**. A special thanks to [@bep](https://github.com/bep), [@jmooring](https://github.com/jmooring), [@StevenMaude](https://github.com/StevenMaude), and [@coliff](https://github.com/coliff) for their work on the documentation site.
-
-Hugo now has:
-
-* 53915+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 430+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 404+ [themes](http://themes.gohugo.io/)
-
-## Notes
-* We have fixed a bug with the import order in `js.Build` for the case `./foo` when both `./foo.js` and `./foo/index.js` exists. This is in line with how both Node and ESBuild's native import resolver does it. We discovered this trying to build AlpineJS v3 from source mounted in `/assets`. See [cf73cc2e](https://github.com/gohugoio/hugo/commit/cf73cc2ececd4e794df09ea382a38ab18960d84e) [@bep](https://github.com/bep) [#8945](https://github.com/gohugoio/hugo/issues/8945).
-
-## Enhancements
-
-### Templates
-
-* Handle nil values in time.AsTime [abd969a6](https://github.com/gohugoio/hugo/commit/abd969a670852f9ed57c1a26434445aa985706fe) [@bep](https://github.com/bep)
-* Handle nil values in time.AsTime [3e110728](https://github.com/gohugoio/hugo/commit/3e11072892ca31bb76980ee38890a4bd92d83dfd) [@bep](https://github.com/bep) [#8865](https://github.com/gohugoio/hugo/issues/8865)
-
-### Other
-
-* Run go mod tidy [6631c9c7](https://github.com/gohugoio/hugo/commit/6631c9c7e00fb9dc237b4ec2fbb261d05df268d1) [@bep](https://github.com/bep)
-* Don't fail on template errors on go mod graph etc. [7d1f806e](https://github.com/gohugoio/hugo/commit/7d1f806ecb3621ae7b545a686d04de4568814055) [@bep](https://github.com/bep) [#8942](https://github.com/gohugoio/hugo/issues/8942)
-* bump github.com/getkin/kin-openapi from 0.74.0 to 0.75.0 [04b59599](https://github.com/gohugoio/hugo/commit/04b59599613a62d378bf3710ac0eb06c9543b96d) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/frankban/quicktest from 1.13.0 to 1.13.1 [c278b6e4](https://github.com/gohugoio/hugo/commit/c278b6e45d56b101db9691347f9e5a99a9319572) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/evanw/esbuild from 0.12.22 to 0.12.24 [107c86fe](https://github.com/gohugoio/hugo/commit/107c86febbb7057c4ae90c6a35b3e8eda24297c7) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Avoid failing with "module not found" for hugo mod init and similar [a0489c2d](https://github.com/gohugoio/hugo/commit/a0489c2dfd3ceb4d0702de0da7a4af3eabce05e5) [@bep](https://github.com/bep) [#8940](https://github.com/gohugoio/hugo/issues/8940)
-* Update to Go 1.17 [0fc2ce9e](https://github.com/gohugoio/hugo/commit/0fc2ce9e4bf0524994a861b7300e4332f6f8d390) [@bep](https://github.com/bep) [#8930](https://github.com/gohugoio/hugo/issues/8930)
-* Remove Pygments from snapcraft.yml [32569285](https://github.com/gohugoio/hugo/commit/32569285c181c8798ef594c12d3cfd7f9a252a04) [@anthonyfok](https://github.com/anthonyfok)
-* bump github.com/fsnotify/fsnotify from 1.4.9 to 1.5.0 [5a46eefb](https://github.com/gohugoio/hugo/commit/5a46eefbc6da3463b796ada8d15902be197455a3) [@bep](https://github.com/bep) [#8920](https://github.com/gohugoio/hugo/issues/8920)
-* Add tabindex when code is not highlighted [7a15edaf](https://github.com/gohugoio/hugo/commit/7a15edafe240471c072d3548b72ccda0271ffd8f) [@helfper](https://github.com/helfper)
-* bump github.com/evanw/esbuild from 0.12.17 to 0.12.22 [2f0945ba](https://github.com/gohugoio/hugo/commit/2f0945bafe501103abe97b2f2b5566b28ec48e52) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump golang.org/x/text from 0.3.6 to 0.3.7 [7ba3f3d2](https://github.com/gohugoio/hugo/commit/7ba3f3d201e386cb9c7c15df5a6cc1c4b46473bd) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/fsnotify/fsnotify from 1.4.9 to 1.5.0 [f7016524](https://github.com/gohugoio/hugo/commit/f70165242b98e3ee182fbac08bf2893a7f09e961) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Prevent minifier from removing quotes around post-processed attributes [bc0743ed](https://github.com/gohugoio/hugo/commit/bc0743ed8eafc3c2d9b21a1e8f1b05d64b85e8ba) [@bep](https://github.com/bep) [#8884](https://github.com/gohugoio/hugo/issues/8884)
-* Avoid too many watch file handles causing the server to fail to start [ffa2fe61](https://github.com/gohugoio/hugo/commit/ffa2fe61172aa0d892234b23d1497c77a6a7f5c4) [@bep](https://github.com/bep)
-* Remove some pygments references [d966f5d0](https://github.com/gohugoio/hugo/commit/d966f5d08d7f75f1ae9acd94e292bf61de2adf0d) [@helfper](https://github.com/helfper)
-* Avoid too many watch file handles causing the server to fail to start [3f38c785](https://github.com/gohugoio/hugo/commit/3f38c785b7208440e2a9dd9a80cb39d4ae23e676) [@wzshiming](https://github.com/wzshiming) [#8904](https://github.com/gohugoio/hugo/issues/8904)
-* bump github.com/getkin/kin-openapi from 0.68.0 to 0.74.0 [24589c08](https://github.com/gohugoio/hugo/commit/24589c0814bc5d21565470bec6215ee792f1655e) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Update github.com/spf13/cast v1.4.0 => v1.4.1 [efebd756](https://github.com/gohugoio/hugo/commit/efebd756eb1f35c515ac82ccc85ec520bac91240) [@bep](https://github.com/bep) [#8891](https://github.com/gohugoio/hugo/issues/8891)
-* Import time/tzdata on Windows [58b6742c](https://github.com/gohugoio/hugo/commit/58b6742cfeb6d4cd04450cbe9592209510c2b977) [@bep](https://github.com/bep) [#8892](https://github.com/gohugoio/hugo/issues/8892)
-* Indent TOML tables [9bba9a3a](https://github.com/gohugoio/hugo/commit/9bba9a3a98fa268391597d8d7a52112fb401d952) [@bep](https://github.com/bep) [#8850](https://github.com/gohugoio/hugo/issues/8850)
-
-## Fixes
-
-### Other
-
-* Fix import order for ./foo when both ./foo.js and ./foo/index.js exists [cf73cc2e](https://github.com/gohugoio/hugo/commit/cf73cc2ececd4e794df09ea382a38ab18960d84e) [@bep](https://github.com/bep) [#8945](https://github.com/gohugoio/hugo/issues/8945)
-* Fix it so disableKinds etc. does not get merged in from theme [f4ffeea7](https://github.com/gohugoio/hugo/commit/f4ffeea71dd3d044a2628bbb5d6634680667398f) [@bep](https://github.com/bep) [#8866](https://github.com/gohugoio/hugo/issues/8866)
-* Fix `lang.FormatPercent` description [d6c8cd77](https://github.com/gohugoio/hugo/commit/d6c8cd771834ae2913658c652e30a9feadc2a7b7) [@salim-b](https://github.com/salim-b)
diff --git a/docs/content/en/news/0.88.1-relnotes/index.md b/docs/content/en/news/0.88.1-relnotes/index.md
deleted file mode 100644
index d94e72fe9cc..00000000000
--- a/docs/content/en/news/0.88.1-relnotes/index.md
+++ /dev/null
@@ -1,19 +0,0 @@
-
----
-date: 2021-09-04
-title: "Hugo 0.88.1: One Fix"
-description: "This version fixes a release issue introduced in 0.88.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with one important fix.
-
-* Bump down again to Go 1.16.7 for the release builds [e1ead4db](https://github.com/gohugoio/hugo/commit/e1ead4dbca4cfb00ec0893c8c8563c91624ecca7) [@bep](https://github.com/bep) [#8952](https://github.com/gohugoio/hugo/issues/8952)[#8955](https://github.com/gohugoio/hugo/issues/8955)
-
-
-
diff --git a/docs/content/en/news/0.89.0-relnotes/featured.png b/docs/content/en/news/0.89.0-relnotes/featured.png
deleted file mode 100644
index 474e1f79292..00000000000
Binary files a/docs/content/en/news/0.89.0-relnotes/featured.png and /dev/null differ
diff --git a/docs/content/en/news/0.89.0-relnotes/index.md b/docs/content/en/news/0.89.0-relnotes/index.md
deleted file mode 100644
index 6e32087e6fd..00000000000
--- a/docs/content/en/news/0.89.0-relnotes/index.md
+++ /dev/null
@@ -1,106 +0,0 @@
-
----
-date: 2021-11-02
-title: "Fine Grained File Filters"
-description: "Hugo 0.89.0 brings fine grained file filters, archetype rewrite, dependency refresh, and more ..."
-categories: ["Releases"]
----
-
-This release is a dependency refresh (the new Goldmark version comes with a lot of bug fixes, as one example), many bug fixes, but also some nice new features:
-
-We have added the [configuration settings](https://gohugo.io/hugo-modules/configuration/#module-config-mounts) **includeFiles** and **excludeFiles** to the mount configuration. This allows fine grained control over what files to include, and it works for all of Hugo's file systems (including `/static`).
-
-We have also [reimplemented archetypes](https://github.com/gohugoio/hugo/pull/9045). The old implementation had some issues, mostly related to the context (e.g. name, file paths) passed to the template. This new implementation is using the exact same code path for evaluating the pages as in a regular build. This also makes it more robust and easier to reason about in a multilingual setup. Now, if you are explicit about the target path, Hugo will now always pick the correct mount and language:
-
-```
-hugo new content/en/posts/my-first-post.md
-```
-
-This release represents **50 contributions by 13 contributors** to the main Hugo code base. [@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@dependabot[bot]](https://github.com/apps/dependabot), [@jmooring](https://github.com/jmooring), and [@anthonyfok](https://github.com/anthonyfok) for their ongoing contributions.
-And thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his ongoing work on keeping the themes site in pristine condition.
-
-Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
-which has received **23 contributions by 9 contributors**. A special thanks to [@jmooring](https://github.com/jmooring), [@bep](https://github.com/bep), [@coliff](https://github.com/coliff), and [@vipkr](https://github.com/vipkr) for their work on the documentation site.
-
-
-Hugo now has:
-
-* 54999+ [stars](https://github.com/gohugoio/hugo/stargazers)
-* 430+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
-* 413+ [themes](http://themes.gohugo.io/)
-
-
-## Notes
-
-* Hugo now writes an empty file named `.hugo_build.lock` to the root of the project when building (also when doing `hugo new mypost.md` and other commands that requires a build). We recommend you just leave this file alone. Put it in `.gitignore` or similar if you don't want the file in your source repository.
-* We have updated to ESBuild `v0.13.12`. The release notes for [v0.13.0](https://github.com/evanw/esbuild/releases/tag/v0.13.0) mentions a potential breaking change.
-* We now only build AMD64 release binaries (see [this issue](https://github.com/gohugoio/hugo/issues/9102)) for the Unix OSes (e.g. NetBSD). If you need, say, a binary for ARM64, you need to build it yourself.
-* We now build only one release binary/archive for MacOS (see [this issue](https://github.com/gohugoio/hugo/issues/9035)) that works on both Intel and the new Arm M1 systems.
-* `.File.ContentBaseName` now returns the owning directory name for all bundles (branch an leaf). This is a bug fix, but worth mentioning. See [this issue](https://github.com/gohugoio/hugo/issues/9112).
-* We have updated the Twitter shortcode to use Twitter's new API. See [this issue](https://github.com/gohugoio/hugo/pull/9106) for details.
-
-## Enhancements
-
-### Templates
-
-* Use configured location when date passed to Format is string [e82cbd74](https://github.com/gohugoio/hugo/commit/e82cbd746fd4b07e40fedacc4247b9cd50ef70e7) [@bep](https://github.com/bep) [#9084](https://github.com/gohugoio/hugo/issues/9084)
-* Add path.Clean [e55466ce](https://github.com/gohugoio/hugo/commit/e55466ce70363418309d465a0f2aa6c7ada1e51d) [@bradcypert](https://github.com/bradcypert) [#8885](https://github.com/gohugoio/hugo/issues/8885)
-
-### Other
-
-* Regen CLI docs [f503b639](https://github.com/gohugoio/hugo/commit/f503b6395707f8e576af734efab83092d62fae37) [@bep](https://github.com/bep)
-* Make ContentBaseName() return the directory for branch bundles [30aba7fb](https://github.com/gohugoio/hugo/commit/30aba7fb099678363b0a4828936ed28e740e00e2) [@bep](https://github.com/bep) [#9112](https://github.com/gohugoio/hugo/issues/9112)
-* Update Twitter shortcode oEmbed endpoint [0cc39af6](https://github.com/gohugoio/hugo/commit/0cc39af68232f1a4981aae2e72cf65da762b5768) [@jmooring](https://github.com/jmooring) [#8130](https://github.com/gohugoio/hugo/issues/8130)
-* bump github.com/evanw/esbuild from 0.13.10 to 0.13.12 [7fa66425](https://github.com/gohugoio/hugo/commit/7fa66425aa0a918b4bf5eb9a21f6e567e0a7e876) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/yuin/goldmark from 1.4.1 to 1.4.2 [69210cfd](https://github.com/gohugoio/hugo/commit/69210cfdf341d1faef23f4e9290d51448dd5e0c6) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/aws/aws-sdk-go from 1.40.8 to 1.41.14 [3339c2bb](https://github.com/gohugoio/hugo/commit/3339c2bb618c29bb3ad442c71fe1542ad7195971) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/getkin/kin-openapi from 0.79.0 to 0.80.0 [03bbdba8](https://github.com/gohugoio/hugo/commit/03bbdba8be19929cb6a14243b690372fbfbc6aa6) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/evanw/esbuild from 0.13.8 to 0.13.10 [a772b8fc](https://github.com/gohugoio/hugo/commit/a772b8fc3833e010553c412dd5daa0175e6ccead) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Rename excepted filenames for image golden testdata [dce49d13](https://github.com/gohugoio/hugo/commit/dce49d13336f3dbadaa1359322a277ad4cb55679) [@anthonyfok](https://github.com/anthonyfok) [#6387](https://github.com/gohugoio/hugo/issues/6387)
-* bump github.com/frankban/quicktest from 1.13.1 to 1.14.0 [61c5b7a2](https://github.com/gohugoio/hugo/commit/61c5b7a2e623255be99da7adf200f0591c9a1195) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Validate the target path in hugo new [75c9b893](https://github.com/gohugoio/hugo/commit/75c9b893d98961a504cff9ed3c89055d16e315d6) [@bep](https://github.com/bep) [#9072](https://github.com/gohugoio/hugo/issues/9072)
-* Set zone of datetime from from `go-toml` [b959ecbc](https://github.com/gohugoio/hugo/commit/b959ecbc8175e2bf260f10b08965531bce9bcb7e) [@satotake](https://github.com/satotake) [#8895](https://github.com/gohugoio/hugo/issues/8895)
-* Added nodesource apt repository to snap package [70e45481](https://github.com/gohugoio/hugo/commit/70e454812ef684d02ffa881becf0f8ce6a1b5f8c) [@sergiogarciadev](https://github.com/sergiogarciadev)
-* Set HUGO_ENABLEGITINFO=false override in Set_in_string [355ff83e](https://github.com/gohugoio/hugo/commit/355ff83e74f6e27c79033b8dfb899e3a3b529049) [@anthonyfok](https://github.com/anthonyfok)
-* Add includeFiles and excludeFiles to mount configuration [471ed91c](https://github.com/gohugoio/hugo/commit/471ed91c60cd36645794925cb4892cc820eae626) [@bep](https://github.com/bep) [#9042](https://github.com/gohugoio/hugo/issues/9042)
-* bump github.com/mitchellh/mapstructure from 1.4.1 to 1.4.2 [94a5bac5](https://github.com/gohugoio/hugo/commit/94a5bac5b29bbba1ca4809752fe3fd04a58547b6) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Always preserve the original transform error [9830ca9e](https://github.com/gohugoio/hugo/commit/9830ca9e319f6ce313f4e542a202bd0d0469a9ed) [@bep](https://github.com/bep)
-* Add hyperlink to the banner [b64fd057](https://github.com/gohugoio/hugo/commit/b64fd0577b0fb222bea22ae347acb5dd17b2aa04) [@itsAftabAlam](https://github.com/itsAftabAlam)
-* bump github.com/getkin/kin-openapi from 0.78.0 to 0.79.0 [2706437a](https://github.com/gohugoio/hugo/commit/2706437a7d593b66b0fbad0235dbaf917593971b) [@dependabot[bot]](https://github.com/apps/dependabot)
-* github.com/evanw/esbuild v0.13.5 => v0.13.8 [ec7c993c](https://github.com/gohugoio/hugo/commit/ec7c993cfe216b8a3c6fbac85669cefef59778dd) [@bep](https://github.com/bep)
-* Return error on no content dirs [32c6f656](https://github.com/gohugoio/hugo/commit/32c6f656d93ecf4308f7c30848b13b4c6f157436) [@bep](https://github.com/bep) [#9056](https://github.com/gohugoio/hugo/issues/9056)
-* Add a cross process build lock and use it in the archetype content builder [ba35e698](https://github.com/gohugoio/hugo/commit/ba35e69856900b6fc92681aa841cdcaefbb4b121) [@bep](https://github.com/bep) [#9048](https://github.com/gohugoio/hugo/issues/9048)
-* github.com/alecthomas/chroma v0.9.2 => v0.9.4 [bb053770](https://github.com/gohugoio/hugo/commit/bb053770337e214f41bc1c524d458ba7fbe1fc08) [@bep](https://github.com/bep) [#8532](https://github.com/gohugoio/hugo/issues/8532)
-* Reimplement archetypes [9185e11e](https://github.com/gohugoio/hugo/commit/9185e11effa682ea1ef7dc98f2943743671023a6) [@bep](https://github.com/bep) [#9032](https://github.com/gohugoio/hugo/issues/9032)[#7589](https://github.com/gohugoio/hugo/issues/7589)[#9043](https://github.com/gohugoio/hugo/issues/9043)[#9046](https://github.com/gohugoio/hugo/issues/9046)[#9047](https://github.com/gohugoio/hugo/issues/9047)
-* bump github.com/tdewolff/minify/v2 from 2.9.21 to 2.9.22 [168a3aab](https://github.com/gohugoio/hugo/commit/168a3aab4622786ccd0943137fce3912707f2a46) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Update github.com/evanw/esbuild v0.13.5 [8bcfa3bd](https://github.com/gohugoio/hugo/commit/8bcfa3bdf65492329da8093d841dd04c7a5a10c8) [@bep](https://github.com/bep)
-* bump github.com/mattn/go-isatty from 0.0.13 to 0.0.14 [cd4e67af](https://github.com/gohugoio/hugo/commit/cd4e67af182a1b3aa19db7609c7581c424e9310f) [@dependabot[bot]](https://github.com/apps/dependabot)
-* bump github.com/getkin/kin-openapi from 0.75.0 to 0.78.0 [e6ad1f0e](https://github.com/gohugoio/hugo/commit/e6ad1f0e763ee891bf4d71df0168b6949369c793) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Allow multiple plugins in the PostCSS options map [64abc83f](https://github.com/gohugoio/hugo/commit/64abc83fc4b70c70458c582ae2cf67fc9c67bb3f) [@jmooring](https://github.com/jmooring) [#9015](https://github.com/gohugoio/hugo/issues/9015)
-* Create path.Clean documentation [f8d132d7](https://github.com/gohugoio/hugo/commit/f8d132d731cf8e27c8c17931597fd975e8a7c3cc) [@jmooring](https://github.com/jmooring)
-* Skip a test assertion on CI [26f1919a](https://github.com/gohugoio/hugo/commit/26f1919ae0cf57d754bb029270c20e76cc32cf4d) [@bep](https://github.com/bep)
-* Remove tracking image [ecf025f0](https://github.com/gohugoio/hugo/commit/ecf025f006f22061728e78f2cf50257dde2225ee) [@kambojshalabh35](https://github.com/kambojshalabh35)
-* Revert "Remove credit from release notes" [fab1e43d](https://github.com/gohugoio/hugo/commit/fab1e43de59f3a7596ab23347387d846139bc3a3) [@digitalcraftsman](https://github.com/digitalcraftsman)
-* Pass minification errors to the user [e03f82ee](https://github.com/gohugoio/hugo/commit/e03f82eef2679ec8963894d0b911363eef40941a) [@ptgott](https://github.com/ptgott) [#8954](https://github.com/gohugoio/hugo/issues/8954)
-* Clarify "precision" in currency format functions [a864ffe9](https://github.com/gohugoio/hugo/commit/a864ffe9acf295034bb38e789a0efa62906b2ae4) [@ptgott](https://github.com/ptgott)
-* bump github.com/evanw/esbuild from 0.12.24 to 0.12.29 [b49da332](https://github.com/gohugoio/hugo/commit/b49da33280cb01795ce833e70c2b7b78cca1867e) [@dependabot[bot]](https://github.com/apps/dependabot)
-* Use default math/rand.Source for concurrency safety [7c21eca7](https://github.com/gohugoio/hugo/commit/7c21eca74f95b61d6813d0c0b155bf07c9aa8575) [@odeke-em](https://github.com/odeke-em) [#8981](https://github.com/gohugoio/hugo/issues/8981)
-* Make the error handling for the mod commands more lenient [13ad8408](https://github.com/gohugoio/hugo/commit/13ad8408fc6b645b12898fb8053388fc4848dfbd) [@bep](https://github.com/bep)
-* Add some help text to the 'unknown revision' error [1cabf61d](https://github.com/gohugoio/hugo/commit/1cabf61ddf96b89c95c3ba77a985168184920feb) [@bep](https://github.com/bep) [#6825](https://github.com/gohugoio/hugo/issues/6825)
-* Update github.com/yuin/goldmark v1.4.0 => v1.4.1 [268e3069](https://github.com/gohugoio/hugo/commit/268e3069f37df01a5a58b615844652fb75b8503a) [@jmooring](https://github.com/jmooring) [#8855](https://github.com/gohugoio/hugo/issues/8855)
-
-## Fixes
-
-### Templates
-
-* Fix time.Format with Go layouts [ed6fd26c](https://github.com/gohugoio/hugo/commit/ed6fd26ce884c49b02497728a99e90b92dd65f1f) [@bep](https://github.com/bep) [#9107](https://github.com/gohugoio/hugo/issues/9107)
-
-### Other
-
-* Fix description of lang.FormatNumberCustom [04a3b45d](https://github.com/gohugoio/hugo/commit/04a3b45db4cd28b4821b5c98cd67dfbf1d098957) [@jmooring](https://github.com/jmooring)
-* Fix typo in error message [1d60bd1e](https://github.com/gohugoio/hugo/commit/1d60bd1efa943349636edad3dd8c5427312ab0f1) [@jmooring](https://github.com/jmooring)
-* Fix panic when specifying multiple excludeFiles directives [64e1613f](https://github.com/gohugoio/hugo/commit/64e1613fb390bd893900dc0596e5c3f3c8e1cd8c) [@bep](https://github.com/bep) [#9076](https://github.com/gohugoio/hugo/issues/9076)
-* Fix file permissions in new archetype implementation [e02e0727](https://github.com/gohugoio/hugo/commit/e02e0727e57f123f9a8de506e9c098bb374f7a23) [@bep](https://github.com/bep) [#9057](https://github.com/gohugoio/hugo/issues/9057)
-* Fix the "page picker" logic in --navigateToChanged [096f5e19](https://github.com/gohugoio/hugo/commit/096f5e19217e985bccbf6c539e1b220541ffa6f6) [@bep](https://github.com/bep) [#9051](https://github.com/gohugoio/hugo/issues/9051)
-* Fix a typo on OpenBSD [c7957c90](https://github.com/gohugoio/hugo/commit/c7957c90e83ff2b2cc958bd61486a244f0fd8891) [@nabbisen](https://github.com/nabbisen)
-* Fix value of useResourceCacheWhen in TestResourceChainPostCSS [e6e44b7c](https://github.com/gohugoio/hugo/commit/e6e44b7c41a9b517ffc3775ea0a6aec2b1d4591b) [@jmooring](https://github.com/jmooring)
diff --git a/docs/content/en/news/0.89.1-relnotes/index.md b/docs/content/en/news/0.89.1-relnotes/index.md
deleted file mode 100644
index 383cd2ee824..00000000000
--- a/docs/content/en/news/0.89.1-relnotes/index.md
+++ /dev/null
@@ -1,20 +0,0 @@
-
----
-date: 2021-11-05
-title: "Hugo 0.89.1: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.89.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-* create: Make sure the build lock is released before we open editor [166862a0](https://github.com/gohugoio/hugo/commit/166862a096e1d920f46b18c600e8dadcb478b839) [@bep](https://github.com/bep) [#9121](https://github.com/gohugoio/hugo/issues/9121)
-* readme: Update dependency list [82c33c71](https://github.com/gohugoio/hugo/commit/82c33c7105a4d0c0e097f0d074c02995cb2b4d20) [@deining](https://github.com/deining)
-
-
-
diff --git a/docs/content/en/news/0.89.2-relnotes/index.md b/docs/content/en/news/0.89.2-relnotes/index.md
deleted file mode 100644
index 0b886effa09..00000000000
--- a/docs/content/en/news/0.89.2-relnotes/index.md
+++ /dev/null
@@ -1,20 +0,0 @@
-
----
-date: 2021-11-08
-title: "Hugo 0.89.2: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.89.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-* Fix path resolution in hugo new [2b01c85d](https://github.com/gohugoio/hugo/commit/2b01c85d14102353015cf6860d30be3d92964495) [@bep](https://github.com/bep) [#9129](https://github.com/gohugoio/hugo/issues/9129)
-* deps: Upgrade github.com/yuin/goldmark v1.4.2 => v1.4.3 [c09f5c5f](https://github.com/gohugoio/hugo/commit/c09f5c5fd35c03de0444928ada3ce1c5a214b321) [@bep](https://github.com/bep) [#9137](https://github.com/gohugoio/hugo/issues/9137)
-
-
-
diff --git a/docs/content/en/news/0.89.3-relnotes/index.md b/docs/content/en/news/0.89.3-relnotes/index.md
deleted file mode 100644
index ef6887d516f..00000000000
--- a/docs/content/en/news/0.89.3-relnotes/index.md
+++ /dev/null
@@ -1,26 +0,0 @@
-
----
-date: 2021-11-15
-title: "Hugo 0.89.3: A couple of Bug Fixes"
-description: "This version fixes a couple of bugs introduced in 0.89.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with a couple of important fixes.
-
-* Improve error when we cannot determine content directory in "hugo new" [b8155452](https://github.com/gohugoio/hugo/commit/b8155452ac699473b6b2a34f53988dee01b4da34) [@bep](https://github.com/bep) [#9166](https://github.com/gohugoio/hugo/issues/9166)
-* deps: Upgrade github.com/yuin/goldmark v1.4.3 => v1.4.4 [08552a7a](https://github.com/gohugoio/hugo/commit/08552a7a4cd1fe64efdd2f1b95142fa4295cb298) [@jmooring](https://github.com/jmooring) [#9159](https://github.com/gohugoio/hugo/issues/9159)
-* commands: Make sure pollInterval is always set [fdad91fd](https://github.com/gohugoio/hugo/commit/fdad91fd96bc4636bf3a957cdddce18b66473124) [@bep](https://github.com/bep) [#9165](https://github.com/gohugoio/hugo/issues/9165)
-* create: Improve archetype directory discovery and tests [5f3f6089](https://github.com/gohugoio/hugo/commit/5f3f60898cfe1c087841ec1fbd5ddc2916d0a2c6) [@bep](https://github.com/bep) [#9146](https://github.com/gohugoio/hugo/issues/9146)
-* create: Add a log statement when archetype is a directory [057d02de](https://github.com/gohugoio/hugo/commit/057d02de256a3866b7044abaa4d03c69d9fedef0) [@bep](https://github.com/bep) [#9157](https://github.com/gohugoio/hugo/issues/9157)
-* create: Always print "Content ... created" [43ac59da](https://github.com/gohugoio/hugo/commit/43ac59da850901cc848b35129ca7223f9f9a9b19) [@bep](https://github.com/bep) [#9157](https://github.com/gohugoio/hugo/issues/9157)
-* commands: Fix missing file locking in server partial render [ab5c6990](https://github.com/gohugoio/hugo/commit/ab5c6990a55cbb11d97f857b4619b83fddda3d18) [@bep](https://github.com/bep) [#9162](https://github.com/gohugoio/hugo/issues/9162)
-* modules: Improve error message [9369d13e](https://github.com/gohugoio/hugo/commit/9369d13e59ffac262944477fad3dcd2742d66288) [@davidsneighbour](https://github.com/davidsneighbour)
-
-
-
diff --git a/docs/content/en/news/0.89.4-relnotes/index.md b/docs/content/en/news/0.89.4-relnotes/index.md
deleted file mode 100644
index 1f5385f333b..00000000000
--- a/docs/content/en/news/0.89.4-relnotes/index.md
+++ /dev/null
@@ -1,19 +0,0 @@
-
----
-date: 2021-11-17
-title: "Hugo 0.89.4: One Bug Fix"
-description: "This version fixes one bug introduced in 0.89.0."
-categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
----
-
-
-
-This is a bug-fix release with one important fix for people using `hugo new` to create new content:
-
-* Fix content dir resolution when main project is a Hugo Module [2e70f61f](https://github.com/gohugoio/hugo/commit/2e70f61fb04cea08ef6598728a57637ae2cc199c) [@bep](https://github.com/bep) [#9177](https://github.com/gohugoio/hugo/issues/9177)
-
-
-
diff --git a/docs/content/en/news/0.9-relnotes/index.md b/docs/content/en/news/0.9-relnotes/index.md
deleted file mode 100644
index 5b9bf2c0d9e..00000000000
--- a/docs/content/en/news/0.9-relnotes/index.md
+++ /dev/null
@@ -1,43 +0,0 @@
----
-date: 2013-11-16T04:52:32Z
-description: "Hugo 0.9 is the most significant update to Hugo ever! It contains contributions from dozens of contributors and represents hundreds of features, fixes and improvements."
-title: "Hugo 0.9"
-categories: ["Releases"]
----
-
-This is the most significant update to Hugo ever!
-It contains contributions from dozens of contributors and represents hundreds of features, fixes and improvements.
-
-# Major New Features
-- New command based interface similar to git (`hugo server -s ./`)
-- Amber template support
-- Full Windows support
-- Better index support including ordering by content weight
-- Add params to site config, available in `.Site.Params` from templates
-- Support for html & xml content (with front matter support)
-- Support for top level pages (in addition to homepage)
-
-# Notable Fixes and Additions
-- Friendlier json support
-- Aliases (redirects)
-- Support for summary content divider (``)
-- HTML & shortcodes supported in summary (when using divider)
-- Complete overhaul of the documentation site
-- Added "Minutes to Read" functionality
-- Support for a custom 404 page
-- Cleanup of how content organization is handled
-- Loads of unit and performance tests
-- Integration with Travis CI
-- Static directory now watched and copied on any addition or modification
-- Support for relative permalinks
-- Fixed watching being triggered multiple times for the same event
-- Watch now ignores temp files (as created by Vim)
-- Configurable number of posts on homepage
-- Front matter supports multiple types (int, string, date, float)
-- Indexes can now use a default template
-- Addition of truncated bool to content to determine if should show 'more' link
-- Support for `linkTitles`
-- Better handling of most errors with directions on how to resolve
-- Support for more date / time formats
-- Support for Go 1.2
-- Loads more... see commit log for full list.
diff --git a/docs/content/en/news/_content.gotmpl b/docs/content/en/news/_content.gotmpl
new file mode 100644
index 00000000000..af3cf47ed74
--- /dev/null
+++ b/docs/content/en/news/_content.gotmpl
@@ -0,0 +1,31 @@
+{{/* Get releases from GitHub. */}}
+{{ $u := "https://api.github.com/repos/gohugoio/hugo/releases" }}
+{{ $releases := partial "helpers/funcs/get-remote-data.html" $u }}
+{{ $releases = where $releases "draft" false }}
+{{ $releases = where $releases "prerelease" false }}
+
+{{/* Add pages. */}}
+{{ range $releases | first 24 }}
+ {{ $publishDate := .published_at | time.AsTime }}
+
+ {{/* Correct the v0.138.0 release date. See https://github.com/gohugoio/hugo/issues/13066. */}}
+ {{ if eq .name "v0.138.0" }}
+ {{ $publishDate = "2024-11-06T11:22:34Z" }}
+ {{ end }}
+
+ {{ $content := dict "mediaType" "text/markdown" "value" "" }}
+ {{ $dates := dict "publishDate" (time.AsTime $publishDate) }}
+ {{ $params := dict "permalink" .html_url }}
+ {{ $build := dict "render" "never" "list" "local" }}
+ {{ $page := dict
+ "build" $build
+ "content" $content
+ "dates" $dates
+ "kind" "page"
+ "params" $params
+ "path" (strings.Replace .name "." "-")
+ "slug" .name
+ "title" (printf "Release %s" .name)
+ }}
+ {{ $.AddPage $page }}
+{{ end }}
diff --git a/docs/content/en/news/_index.md b/docs/content/en/news/_index.md
index 353accc3d0c..23c082cb738 100644
--- a/docs/content/en/news/_index.md
+++ b/docs/content/en/news/_index.md
@@ -1,4 +1,9 @@
---
-title: "Hugo News"
+title: News
+description: Stay up-to-date with the latest news and announcements.
+outputs:
+ - html
+ - rss
+weight: 10
aliases: [/release-notes/]
---
diff --git a/docs/content/en/news/hugo-macos-intel-vs-arm/featured.png b/docs/content/en/news/hugo-macos-intel-vs-arm/featured.png
deleted file mode 100644
index 30e73ad4e36..00000000000
Binary files a/docs/content/en/news/hugo-macos-intel-vs-arm/featured.png and /dev/null differ
diff --git a/docs/content/en/news/hugo-macos-intel-vs-arm/index.html b/docs/content/en/news/hugo-macos-intel-vs-arm/index.html
deleted file mode 100644
index 9bc83df5d9c..00000000000
--- a/docs/content/en/news/hugo-macos-intel-vs-arm/index.html
+++ /dev/null
@@ -1,9139 +0,0 @@
----
-title: "Hugo on Apple M1"
-date: 2020-12-10
-description: "The new Mac Mini M1 base model is blazing fast! We have run the Hugo benchmarks comparing it to a MacBook four times more expensive."
----
-
-
- The table below shows all of Hugo's benchmarks run on both a MacBook with Intel CPU and a Mac Mini M1 with an ARM CPU.
-
-
Some notes:
-
-
-
The Intel Mac is a 2019 16 inch MacBook with 2,3 GHz 8-Core Intel Core i9 with 32 GB ram.
-
The ARM Mac is a new Mac Mini M1 base model with 8 GB of ram
-
The benchmarks are run with go test -test.run=NONE -bench="Benchmark" -test.benchmem=true -cpu=8 -count=4 ./.... Since the M1 does not have a concept of Turbo Boost, I kept that on when running the Intel benchmarks.
-
The right column is the Mac Mini, negative (black) numbers are good, positive numbers (red) are not so good.
-
Go is compiled from the latest source: go version devel +5627a4dc30 Wed Dec 9 16:57:37 2020 +0000 darwin/arm64
-
-
-
-
- This test isn't exactly comparing apples with apples (pun intended); this is a 4K USD computer compared to a 1K computer, but that makes the performance of the Mac Mini even more impressive.
-
-
-
- There are some areas where the Intel still outshines the ARM, and that is most likely areas with highly optimized assembly code, and this will certainly improve. More benchmarks can be found here.
-
-
-
- You probably want to watch issue to track when we can get a Go release with MacOS M1 support. A couple of months?
-
-
-
- Also, this work document is a great resource for getting a native Go development environment up and running on the M1.
-
diff --git a/docs/content/en/news/lets-celebrate-hugos-5th-birthday/featured.png b/docs/content/en/news/lets-celebrate-hugos-5th-birthday/featured.png
deleted file mode 100644
index 4c31412fdfd..00000000000
Binary files a/docs/content/en/news/lets-celebrate-hugos-5th-birthday/featured.png and /dev/null differ
diff --git a/docs/content/en/news/lets-celebrate-hugos-5th-birthday/graph-stars.png b/docs/content/en/news/lets-celebrate-hugos-5th-birthday/graph-stars.png
deleted file mode 100644
index 00848fcf009..00000000000
Binary files a/docs/content/en/news/lets-celebrate-hugos-5th-birthday/graph-stars.png and /dev/null differ
diff --git a/docs/content/en/news/lets-celebrate-hugos-5th-birthday/graph-themes.png b/docs/content/en/news/lets-celebrate-hugos-5th-birthday/graph-themes.png
deleted file mode 100644
index 0d4dfd599d3..00000000000
Binary files a/docs/content/en/news/lets-celebrate-hugos-5th-birthday/graph-themes.png and /dev/null differ
diff --git a/docs/content/en/news/lets-celebrate-hugos-5th-birthday/index.md b/docs/content/en/news/lets-celebrate-hugos-5th-birthday/index.md
deleted file mode 100644
index 9912027b5d6..00000000000
--- a/docs/content/en/news/lets-celebrate-hugos-5th-birthday/index.md
+++ /dev/null
@@ -1,108 +0,0 @@
----
-date: 2018-07-04
-title: "Let’s celebrate Hugo’s 5th birthday"
-description: "How a side project became one of the most popular frameworks for building websites."
-categories: [blog]
-author: bep
----
-
-_By Bjørn Erik Pedersen ([@bepsays](https://twitter.com/bepsays) / [@bep](https://github.com/bep)), Hugo Lead_
-
-**Five years ago today, [Steve Francia](https://github.com/spf13/) made his [first commit](https://github.com/gohugoio/hugo/commit/50a1d6f3f155ab837310e00ffb309a9199773c73
-) on the Hugo project: "Hugo: A Fast and Flexible Static Site Generator built with love by spf13 in GoLang".**
-
-Steve was writing that on a train commute to New York. I'm writing this article running Hugo `v0.43-DEV`, the preview version of the next Hugo release. The release is scheduled for Monday and adds a powerful [assets pipeline](https://github.com/gohugoio/hugo/issues/4854#issue-333062459), with SCSS/SASS support, assets minification, fingerprinting/subresource integrity, ad-hoc image processing and much more.
-
-**I cannot remember the last time I was this excited about a Hugo release. "Game changer" may be too strong, but it makes for a really nice integrated website design-workflow that, with Hugo's build speed, is hard to beat.**
-
-{{< imgproc sunset Fill "600x300" >}}
-Fetch and scale an image in the upcoming Hugo 0.43.
-{{< /imgproc >}}
-
-But that is a release for Monday. Now is a time to look at the current status of Hugo after the first five years.
-
-## Hugo in Numbers
-
-According to [BuiltWith](https://trends.builtwith.com/cms/Hugo), more than 29 000 live websites are built with Hugo. Of those, 390 are in the top 1 million. Wappalyzer [reports](https://www.wappalyzer.com/categories/static-site-generator) that Hugo serves almost 50% of the static sites.
-
-Hugo is big in the [public sector](https://discourse.gohugo.io/t/hugo-in-public-administration/8792), with the US Government as a prominent user. Some examples are [vote.gov](https://vote.gov/) and [digital.gov](https://digital.gov/).
-
-[Smashing Magazine](https://www.smashingmagazine.com/) is a big and very popular Hugo site. It is [reported](https://discourse.gohugo.io/t/smashing-magazine-s-redesign-powered-by-hugo-jamstack/5826/7) that they build their complex site with 7500 content pages in 13 seconds.
-
-Some other example sites are [kubernetes.io](https://kubernetes.io/), [letsencrypt.org](https://gohugo.io/showcase/letsencrypt/), [support.1password.com](http://gohugo.io/showcase/1password-support/), [netlify.com](https://www.netlify.com), [litecoin.org](https://litecoin.org/), and [forestry.io](https://forestry.io/).
-
-
-{{< imgproc graph-stars Fit "600x400" >}}
-Number of GitHub stars in relation to the Hugo release dates.
-{{< /imgproc >}}
-
-More numbers:
-
-* 26800+ [stars](https://github.com/gohugoio/hugo/stargazers) on GitHub.
-* 444+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors) to the Hugo source repository, 654+ to [Hugo Docs](https://github.com/gohugoio/hugoDocs/graphs/contributors). [@bep](https://github.com/bep) is the most active with around 70% of the current code base (line count).
-* 235+ [themes](https://themes.gohugo.io/)
-* 50% increase in the number of user sessions on the [gohugo.io](https://gohugo.io/) web sites the last 12 months.[^2]
-* Hugo build release binaries for [a myriad](https://github.com/gohugoio/hugo/releases/tag/v0.42.2) of platforms. And since it can also be installed from Chocolatey on Windows, Brew on MacOs, Snap on Linux and `apt-get` on Debian/Ubuntu, it is impossible to give accurate download numbers. But the number is not small.
-
-## Hugo Next
-
-We're not finished with Hugo, but Hugo `0.43` very nicely wraps up the first five years. It started out as a small and fast static site generator. It is now [even faster](https://forestry.io/blog/hugo-vs-jekyll-benchmark/), and now so loaded with features that it has grown out of being just a "static site generator". It is a _framework for building websites_.
-
-My interest in Hugo started on the Sunday when I moved my blog, [bepsays.com](https://bepsays.com/en/), twice. The second static generator choice of that day, Hugo, was a good match. I remember Steve being very enthusiastic about getting patches with fixes and enhancements, and I was eventually taken over by the simplicity and power of Go, the programming language, and started to implement lots of new features.
-
-My goal with all of this, if there is one, is to get a single binary with native and really fast implementations of the complete stack I need for web development and content editing. The single binary takes most of the pain out of installation and upgrades (if you stick with the same binary, it will continue to just work for decades).
-
-**With 0.43, we are almost there.** With that release, it should be possible to set up a Hugo-only project without any additional tools (Gulp, WebPack) for all aspects of website building. There will still be situations where those tools will still be needed, of course, but we will continue to fill the gaps in the feature set.
-
-Hugo has stuck with the sub-zero versions to signal active development, with a new main release every 5-6 weeks. But we take stability very seriously (breaking things add lots of support work, we don't like that) and most site upgrades are [smooth](https://twitter.com/tmmx/status/1006288444459503616). But we are closing in on the first major stable version.
-
-
-### The Road to 1.0
-
-We have some more technical tasks that needs to be done (there is ongoing work to get the page queries into a more consistent state, also a simpler `.GetPage` method), but also some cool new functionality. The following roadmap is taken from memory, and may not be complete, but should be a good indication of what's ahead.
-
-Pages from "other data sources"
-: Currently, in addition to Hugo's list pages, every URL must be backed by a content file (Markdown, HTML etc.). This covers most use cases, but we need a flexible way to generate pages from other data sources. Think product catalogues and similar.
-
-Upgrade Blackfriday to v2
-: [Blackfriday](https://github.com/russross/blackfriday) is the main content renderer in Hugo. It has been rewritten to a more flexible architecture, which should allow us to fix some of the current shortcomings.
-
-We should be able to create a better and easier-to-use data structure from the rendered content: Summary, the content after the summary, being able to range over the footnotes and the ToC. Having ToC as a proper data structure also opens up a few other potential uses; using it as an index in [Related Content](https://gohugo.io/content-management/related/) would be one example.
-
-This should also enable us to _do more_ with [Custom Output Formats](/templates/output-formats). It is already very powerful. GettyPubs are using it in [Quire](https://github.com/gettypubs/quire) to build [beautiful multi-platform publications](http://www.getty.edu/publications/digital/digitalpubs.html). But it can be improved. For rendering of content files, you are currently restricted to HTML. It would be great if we could configure alternative renderers per output format, such as LaTeX and EPUB.
-
-Related to this is also to add a configurable "Markdown URL rewriter", which should make more portable URLs in Markdown, e.g. image links that work both when viewed on GitHub and your published site.
-
-### The Road to the Future
-
-These are the items that first come to mind if you ask me to think even further ahead:
-
-Dependency manager for Theme Components
-: In Hugo `0.42` we added [Theme Components](/themes/theme-components/) and Theme Inheritance. With SCSS support in Hugo `0.43`, which also follows the same project/themes precedence order (add `_variables.scss` to your project, configure SASS colour variables in `config.toml`), we have a solid foundation for creating easy to use and extensible themes. But we are missing some infrastructure around this. We have a site with 235+ [themes](https://themes.gohugo.io/)[^themes] listed, but you currently need to do some added work to get the theme up and running for your site. In the Go world, we don't have NPM to use, which is a curse and a blessing, but I have some ideas about building a simple dependency manager into Hugo, modelled after how Go is doing it (`hugo install`). You should be able to configure what theme and theme components you want to use, and Hugo should handle the installation of the correct versions. This should make it easier for the user, but it would also enable community driven and even commercial "theme stores".
-
-
-{{< imgproc graph-themes Fit "600x400" >}}
-Number of Hugo themes on themes.gohugo.io in relation to the Hugo release dates.
-{{< /imgproc >}}
-
-
-The "New York Times on Hugo" Use Case
-: There are recurring questions on the support forum from [really big sites](https://discourse.gohugo.io/t/transition-2m-posts-from-wordpress-to-hugo/12704) that want to move to Hugo. There are many [good reasons](https://www.netlify.com/blog/2016/05/18/9-reasons-your-site-should-be-static/) why they want this (security, cost-saving, EU regulations etc.). And while there have been reports about companies building 600 000 pages with Hugo on very powerful hardware, we will have to rethink the build model to make this usable. Keywords are: streaming builds, segmented builds, partial rebuilds. There are other site generators also talking about this. It should be possible, and my instinct tells me that it should be easier to do when your starting point is "really fast". But this is not a small weekend project for me, and I have already talked to several companies about sponsoring this.
-
-Plugins
-: A Theme Component could also be called a plugin. But there are several potential plugin hooks into Hugo's build pipeline: resource transformations, content rendering, etc. We will eventually get there, but we should do it without giving up too much of the Hugo speed and simplicity.
-
-
-## Thanks
-
-So, thanks to everyone who has contributed to getting Hugo where it is today. It is hard to single out individuals, but a big shout-out to all the Hugo experts and moderators helping out making [discourse.gohugo.io](https://discourse.gohugo.io/) a very active and possibly one of the best support forums out there.
-
-And the last shout-out goes to two maintainers who have been there more or less from the start. [@digitalcraftsman](https://github.com/digitalcraftsman/) has been doing a fantastic job keeping the fast growing theme site and [repository](https://github.com/gohugoio/hugoThemes) in pristine condition. I have it on my watch list, but that is just out of curiosity. There is lots of activity, but it runs as clock work. [Anthony Fok](https://github.com/anthonyfok) has contributed with a variety of things but is most notable as the Linux expert on the team. He manages the Debian build and is the one to thank for up-to-date binaries on Debian and Ubuntu.
-
-One final note: If you have not done so already, please visit [github.com/gohugoio/hugo](https://github.com/gohugoio/hugo) and push the "star button".
-
-Gopher artwork by [Ashley McNamara](https://github.com/ashleymcnamara/gophers/) (licensed under [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/)). Inspired by [Renee French](https://reneefrench.blogspot.com/).
-
-[^2]: Numbers from Google Analytics. The Hugo websites are https://discourse.gohugo.io, https://gohugo.io and https://themes.gohugo.io. It is rumoured that when [Matt Biilman](https://twitter.com/biilmann?lang=en), CEO and Co-founder of Netlify, opened the first power bill after sponsoring Hugo's hosting, said: "Du må lave fis med mig, those Hugo sites have lots of web traffic!"
-[^sgen]: That was at the time of writing this article. _Next_, a React based static site generator, has momentum and is closing in on Hugo's 2nd place.
-[^themes]: We pull all the themes from GitHub and build the theme site and 235 demo sites on Netlify in 4 minutes. That is impressive.
diff --git a/docs/content/en/news/lets-celebrate-hugos-5th-birthday/sunset-get.png b/docs/content/en/news/lets-celebrate-hugos-5th-birthday/sunset-get.png
deleted file mode 100644
index 5b368b97a4b..00000000000
Binary files a/docs/content/en/news/lets-celebrate-hugos-5th-birthday/sunset-get.png and /dev/null differ
diff --git a/docs/content/en/quick-reference/_index.md b/docs/content/en/quick-reference/_index.md
new file mode 100644
index 00000000000..98f978f4fcb
--- /dev/null
+++ b/docs/content/en/quick-reference/_index.md
@@ -0,0 +1,8 @@
+---
+title: Quick reference guides
+linkTitle: Quick reference
+description: Use these quick reference guides for quick access to key information.
+categories: []
+keywords: []
+weight: 10
+---
diff --git a/docs/content/en/quick-reference/emojis.md b/docs/content/en/quick-reference/emojis.md
new file mode 100644
index 00000000000..75f26973067
--- /dev/null
+++ b/docs/content/en/quick-reference/emojis.md
@@ -0,0 +1,1681 @@
+---
+title: Emojis
+description: Include emoji shortcodes in your Markdown or templates.
+categories: []
+keywords: []
+params:
+ searchable: false
+---
+
+## Attribution
+
+This quick reference guide was generated using the [ikatyang/emoji-cheat-sheet] project which reads from the [GitHub Emoji API] and the [Unicode Full Emoji List].
+
+Note that GitHub [custom emoji] are not supported.
+
+[custom emoji]: #github-custom-emoji
+[github emoji api]: https://api.github.com/emojis
+[ikatyang/emoji-cheat-sheet]: https://github.com/ikatyang/emoji-cheat-sheet/
+[unicode full emoji list]: https://unicode.org/emoji/charts/full-emoji-list.html
+
+## Usage
+
+Configure Hugo to enable emoji processing in Markdown:
+
+{{< code-toggle file=hugo >}}
+enableEmoji = true
+{{< /code-toggle >}}
+
+With emoji processing enabled, this Markdown:
+
+```md
+Hello! :wave:
+```
+
+Is rendered to:
+
+```html
+Hello! 👋
+```
+
+And in your browser... Hello! :wave:
+
+To process an emoji shortcode from within a template, use the [`emojify`] function or pass the string through the [`RenderString`] method on a `Page` object:
+
+```go-html-template
+{{ "Hello! :wave:" | .RenderString }}
+```
+
+[`emojify`]: /functions/transform/emojify/
+[`RenderString`]: /methods/page/renderstring/
+
+
+
+## Table of Contents
+
+- [Smileys & Emotion](#smileys--emotion)
+- [People & Body](#people--body)
+- [Animals & Nature](#animals--nature)
+- [Food & Drink](#food--drink)
+- [Travel & Places](#travel--places)
+- [Activities](#activities)
+- [Objects](#objects)
+- [Symbols](#symbols)
+- [Flags](#flags)
+- [GitHub Custom Emoji](#github-custom-emoji)
+
+## Smileys & Emotion
+
+- [Face Smiling](#face-smiling)
+- [Face Affection](#face-affection)
+- [Face Tongue](#face-tongue)
+- [Face Hand](#face-hand)
+- [Face Neutral Skeptical](#face-neutral-skeptical)
+- [Face Sleepy](#face-sleepy)
+- [Face Unwell](#face-unwell)
+- [Face Hat](#face-hat)
+- [Face Glasses](#face-glasses)
+- [Face Concerned](#face-concerned)
+- [Face Negative](#face-negative)
+- [Face Costume](#face-costume)
+- [Cat Face](#cat-face)
+- [Monkey Face](#monkey-face)
+- [Heart](#heart)
+- [Emotion](#emotion)
+
+### Face Smiling
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#smileys--emotion) | :grinning: | `:grinning:` | :smiley: | `:smiley:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :smile: | `:smile:` | :grin: | `:grin:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :laughing: | `:laughing:` `:satisfied:` | :sweat_smile: | `:sweat_smile:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :rofl: | `:rofl:` | :joy: | `:joy:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :slightly_smiling_face: | `:slightly_smiling_face:` | :upside_down_face: | `:upside_down_face:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :melting_face: | `:melting_face:` | :wink: | `:wink:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :blush: | `:blush:` | :innocent: | `:innocent:` | [top](#table-of-contents) |
+
+### Face Affection
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#smileys--emotion) | :smiling_face_with_three_hearts: | `:smiling_face_with_three_hearts:` | :heart_eyes: | `:heart_eyes:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :star_struck: | `:star_struck:` | :kissing_heart: | `:kissing_heart:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :kissing: | `:kissing:` | :relaxed: | `:relaxed:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :kissing_closed_eyes: | `:kissing_closed_eyes:` | :kissing_smiling_eyes: | `:kissing_smiling_eyes:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :smiling_face_with_tear: | `:smiling_face_with_tear:` | | | [top](#table-of-contents) |
+
+### Face Tongue
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#smileys--emotion) | :yum: | `:yum:` | :stuck_out_tongue: | `:stuck_out_tongue:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :stuck_out_tongue_winking_eye: | `:stuck_out_tongue_winking_eye:` | :zany_face: | `:zany_face:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :stuck_out_tongue_closed_eyes: | `:stuck_out_tongue_closed_eyes:` | :money_mouth_face: | `:money_mouth_face:` | [top](#table-of-contents) |
+
+### Face Hand
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#smileys--emotion) | :hugs: | `:hugs:` | :hand_over_mouth: | `:hand_over_mouth:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :face_with_open_eyes_and_hand_over_mouth: | `:face_with_open_eyes_and_hand_over_mouth:` | :face_with_peeking_eye: | `:face_with_peeking_eye:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :shushing_face: | `:shushing_face:` | :thinking: | `:thinking:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :saluting_face: | `:saluting_face:` | | | [top](#table-of-contents) |
+
+### Face Neutral Skeptical
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#smileys--emotion) | :zipper_mouth_face: | `:zipper_mouth_face:` | :raised_eyebrow: | `:raised_eyebrow:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :neutral_face: | `:neutral_face:` | :expressionless: | `:expressionless:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :no_mouth: | `:no_mouth:` | :dotted_line_face: | `:dotted_line_face:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :face_in_clouds: | `:face_in_clouds:` | :smirk: | `:smirk:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :unamused: | `:unamused:` | :roll_eyes: | `:roll_eyes:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :grimacing: | `:grimacing:` | :face_exhaling: | `:face_exhaling:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :lying_face: | `:lying_face:` | :shaking_face: | `:shaking_face:` | [top](#table-of-contents) |
+
+### Face Sleepy
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#smileys--emotion) | :relieved: | `:relieved:` | :pensive: | `:pensive:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :sleepy: | `:sleepy:` | :drooling_face: | `:drooling_face:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :sleeping: | `:sleeping:` | | | [top](#table-of-contents) |
+
+### Face Unwell
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#smileys--emotion) | :mask: | `:mask:` | :face_with_thermometer: | `:face_with_thermometer:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :face_with_head_bandage: | `:face_with_head_bandage:` | :nauseated_face: | `:nauseated_face:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :vomiting_face: | `:vomiting_face:` | :sneezing_face: | `:sneezing_face:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :hot_face: | `:hot_face:` | :cold_face: | `:cold_face:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :woozy_face: | `:woozy_face:` | :dizzy_face: | `:dizzy_face:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :face_with_spiral_eyes: | `:face_with_spiral_eyes:` | :exploding_head: | `:exploding_head:` | [top](#table-of-contents) |
+
+### Face Hat
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#smileys--emotion) | :cowboy_hat_face: | `:cowboy_hat_face:` | :partying_face: | `:partying_face:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :disguised_face: | `:disguised_face:` | | | [top](#table-of-contents) |
+
+### Face Glasses
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#smileys--emotion) | :sunglasses: | `:sunglasses:` | :nerd_face: | `:nerd_face:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :monocle_face: | `:monocle_face:` | | | [top](#table-of-contents) |
+
+### Face Concerned
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#smileys--emotion) | :confused: | `:confused:` | :face_with_diagonal_mouth: | `:face_with_diagonal_mouth:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :worried: | `:worried:` | :slightly_frowning_face: | `:slightly_frowning_face:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :frowning_face: | `:frowning_face:` | :open_mouth: | `:open_mouth:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :hushed: | `:hushed:` | :astonished: | `:astonished:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :flushed: | `:flushed:` | :pleading_face: | `:pleading_face:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :face_holding_back_tears: | `:face_holding_back_tears:` | :frowning: | `:frowning:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :anguished: | `:anguished:` | :fearful: | `:fearful:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :cold_sweat: | `:cold_sweat:` | :disappointed_relieved: | `:disappointed_relieved:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :cry: | `:cry:` | :sob: | `:sob:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :scream: | `:scream:` | :confounded: | `:confounded:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :persevere: | `:persevere:` | :disappointed: | `:disappointed:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :sweat: | `:sweat:` | :weary: | `:weary:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :tired_face: | `:tired_face:` | :yawning_face: | `:yawning_face:` | [top](#table-of-contents) |
+
+### Face Negative
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#smileys--emotion) | :triumph: | `:triumph:` | :pout: | `:pout:` `:rage:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :angry: | `:angry:` | :cursing_face: | `:cursing_face:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :smiling_imp: | `:smiling_imp:` | :imp: | `:imp:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :skull: | `:skull:` | :skull_and_crossbones: | `:skull_and_crossbones:` | [top](#table-of-contents) |
+
+### Face Costume
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#smileys--emotion) | :hankey: | `:hankey:` `:poop:` `:shit:` | :clown_face: | `:clown_face:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :japanese_ogre: | `:japanese_ogre:` | :japanese_goblin: | `:japanese_goblin:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :ghost: | `:ghost:` | :alien: | `:alien:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :space_invader: | `:space_invader:` | :robot: | `:robot:` | [top](#table-of-contents) |
+
+### Cat Face
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#smileys--emotion) | :smiley_cat: | `:smiley_cat:` | :smile_cat: | `:smile_cat:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :joy_cat: | `:joy_cat:` | :heart_eyes_cat: | `:heart_eyes_cat:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :smirk_cat: | `:smirk_cat:` | :kissing_cat: | `:kissing_cat:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :scream_cat: | `:scream_cat:` | :crying_cat_face: | `:crying_cat_face:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :pouting_cat: | `:pouting_cat:` | | | [top](#table-of-contents) |
+
+### Monkey Face
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#smileys--emotion) | :see_no_evil: | `:see_no_evil:` | :hear_no_evil: | `:hear_no_evil:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :speak_no_evil: | `:speak_no_evil:` | | | [top](#table-of-contents) |
+
+### Heart
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#smileys--emotion) | :love_letter: | `:love_letter:` | :cupid: | `:cupid:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :gift_heart: | `:gift_heart:` | :sparkling_heart: | `:sparkling_heart:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :heartpulse: | `:heartpulse:` | :heartbeat: | `:heartbeat:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :revolving_hearts: | `:revolving_hearts:` | :two_hearts: | `:two_hearts:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :heart_decoration: | `:heart_decoration:` | :heavy_heart_exclamation: | `:heavy_heart_exclamation:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :broken_heart: | `:broken_heart:` | :heart_on_fire: | `:heart_on_fire:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :mending_heart: | `:mending_heart:` | :heart: | `:heart:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :pink_heart: | `:pink_heart:` | :orange_heart: | `:orange_heart:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :yellow_heart: | `:yellow_heart:` | :green_heart: | `:green_heart:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :blue_heart: | `:blue_heart:` | :light_blue_heart: | `:light_blue_heart:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :purple_heart: | `:purple_heart:` | :brown_heart: | `:brown_heart:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :black_heart: | `:black_heart:` | :grey_heart: | `:grey_heart:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :white_heart: | `:white_heart:` | | | [top](#table-of-contents) |
+
+### Emotion
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#smileys--emotion) | :kiss: | `:kiss:` | :100: | `:100:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :anger: | `:anger:` | :boom: | `:boom:` `:collision:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :dizzy: | `:dizzy:` | :sweat_drops: | `:sweat_drops:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :dash: | `:dash:` | :hole: | `:hole:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :speech_balloon: | `:speech_balloon:` | :eye_speech_bubble: | `:eye_speech_bubble:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :left_speech_bubble: | `:left_speech_bubble:` | :right_anger_bubble: | `:right_anger_bubble:` | [top](#table-of-contents) |
+| [top](#smileys--emotion) | :thought_balloon: | `:thought_balloon:` | :zzz: | `:zzz:` | [top](#table-of-contents) |
+
+## People & Body
+
+- [Hand Fingers Open](#hand-fingers-open)
+- [Hand Fingers Partial](#hand-fingers-partial)
+- [Hand Single Finger](#hand-single-finger)
+- [Hand Fingers Closed](#hand-fingers-closed)
+- [Hands](#hands)
+- [Hand Prop](#hand-prop)
+- [Body Parts](#body-parts)
+- [Person](#person)
+- [Person Gesture](#person-gesture)
+- [Person Role](#person-role)
+- [Person Fantasy](#person-fantasy)
+- [Person Activity](#person-activity)
+- [Person Sport](#person-sport)
+- [Person Resting](#person-resting)
+- [Family](#family)
+- [Person Symbol](#person-symbol)
+
+### Hand Fingers Open
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#people--body) | :wave: | `:wave:` | :raised_back_of_hand: | `:raised_back_of_hand:` | [top](#table-of-contents) |
+| [top](#people--body) | :raised_hand_with_fingers_splayed: | `:raised_hand_with_fingers_splayed:` | :hand: | `:hand:` `:raised_hand:` | [top](#table-of-contents) |
+| [top](#people--body) | :vulcan_salute: | `:vulcan_salute:` | :rightwards_hand: | `:rightwards_hand:` | [top](#table-of-contents) |
+| [top](#people--body) | :leftwards_hand: | `:leftwards_hand:` | :palm_down_hand: | `:palm_down_hand:` | [top](#table-of-contents) |
+| [top](#people--body) | :palm_up_hand: | `:palm_up_hand:` | :leftwards_pushing_hand: | `:leftwards_pushing_hand:` | [top](#table-of-contents) |
+| [top](#people--body) | :rightwards_pushing_hand: | `:rightwards_pushing_hand:` | | | [top](#table-of-contents) |
+
+### Hand Fingers Partial
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#people--body) | :ok_hand: | `:ok_hand:` | :pinched_fingers: | `:pinched_fingers:` | [top](#table-of-contents) |
+| [top](#people--body) | :pinching_hand: | `:pinching_hand:` | :v: | `:v:` | [top](#table-of-contents) |
+| [top](#people--body) | :crossed_fingers: | `:crossed_fingers:` | :hand_with_index_finger_and_thumb_crossed: | `:hand_with_index_finger_and_thumb_crossed:` | [top](#table-of-contents) |
+| [top](#people--body) | :love_you_gesture: | `:love_you_gesture:` | :metal: | `:metal:` | [top](#table-of-contents) |
+| [top](#people--body) | :call_me_hand: | `:call_me_hand:` | | | [top](#table-of-contents) |
+
+### Hand Single Finger
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#people--body) | :point_left: | `:point_left:` | :point_right: | `:point_right:` | [top](#table-of-contents) |
+| [top](#people--body) | :point_up_2: | `:point_up_2:` | :fu: | `:fu:` `:middle_finger:` | [top](#table-of-contents) |
+| [top](#people--body) | :point_down: | `:point_down:` | :point_up: | `:point_up:` | [top](#table-of-contents) |
+| [top](#people--body) | :index_pointing_at_the_viewer: | `:index_pointing_at_the_viewer:` | | | [top](#table-of-contents) |
+
+### Hand Fingers Closed
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#people--body) | :+1: | `:+1:` `:thumbsup:` | :-1: | `:-1:` `:thumbsdown:` | [top](#table-of-contents) |
+| [top](#people--body) | :fist: | `:fist:` `:fist_raised:` | :facepunch: | `:facepunch:` `:fist_oncoming:` `:punch:` | [top](#table-of-contents) |
+| [top](#people--body) | :fist_left: | `:fist_left:` | :fist_right: | `:fist_right:` | [top](#table-of-contents) |
+
+### Hands
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#people--body) | :clap: | `:clap:` | :raised_hands: | `:raised_hands:` | [top](#table-of-contents) |
+| [top](#people--body) | :heart_hands: | `:heart_hands:` | :open_hands: | `:open_hands:` | [top](#table-of-contents) |
+| [top](#people--body) | :palms_up_together: | `:palms_up_together:` | :handshake: | `:handshake:` | [top](#table-of-contents) |
+| [top](#people--body) | :pray: | `:pray:` | | | [top](#table-of-contents) |
+
+### Hand Prop
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#people--body) | :writing_hand: | `:writing_hand:` | :nail_care: | `:nail_care:` | [top](#table-of-contents) |
+| [top](#people--body) | :selfie: | `:selfie:` | | | [top](#table-of-contents) |
+
+### Body Parts
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#people--body) | :muscle: | `:muscle:` | :mechanical_arm: | `:mechanical_arm:` | [top](#table-of-contents) |
+| [top](#people--body) | :mechanical_leg: | `:mechanical_leg:` | :leg: | `:leg:` | [top](#table-of-contents) |
+| [top](#people--body) | :foot: | `:foot:` | :ear: | `:ear:` | [top](#table-of-contents) |
+| [top](#people--body) | :ear_with_hearing_aid: | `:ear_with_hearing_aid:` | :nose: | `:nose:` | [top](#table-of-contents) |
+| [top](#people--body) | :brain: | `:brain:` | :anatomical_heart: | `:anatomical_heart:` | [top](#table-of-contents) |
+| [top](#people--body) | :lungs: | `:lungs:` | :tooth: | `:tooth:` | [top](#table-of-contents) |
+| [top](#people--body) | :bone: | `:bone:` | :eyes: | `:eyes:` | [top](#table-of-contents) |
+| [top](#people--body) | :eye: | `:eye:` | :tongue: | `:tongue:` | [top](#table-of-contents) |
+| [top](#people--body) | :lips: | `:lips:` | :biting_lip: | `:biting_lip:` | [top](#table-of-contents) |
+
+### Person
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#people--body) | :baby: | `:baby:` | :child: | `:child:` | [top](#table-of-contents) |
+| [top](#people--body) | :boy: | `:boy:` | :girl: | `:girl:` | [top](#table-of-contents) |
+| [top](#people--body) | :adult: | `:adult:` | :blond_haired_person: | `:blond_haired_person:` | [top](#table-of-contents) |
+| [top](#people--body) | :man: | `:man:` | :bearded_person: | `:bearded_person:` | [top](#table-of-contents) |
+| [top](#people--body) | :man_beard: | `:man_beard:` | :woman_beard: | `:woman_beard:` | [top](#table-of-contents) |
+| [top](#people--body) | :red_haired_man: | `:red_haired_man:` | :curly_haired_man: | `:curly_haired_man:` | [top](#table-of-contents) |
+| [top](#people--body) | :white_haired_man: | `:white_haired_man:` | :bald_man: | `:bald_man:` | [top](#table-of-contents) |
+| [top](#people--body) | :woman: | `:woman:` | :red_haired_woman: | `:red_haired_woman:` | [top](#table-of-contents) |
+| [top](#people--body) | :person_red_hair: | `:person_red_hair:` | :curly_haired_woman: | `:curly_haired_woman:` | [top](#table-of-contents) |
+| [top](#people--body) | :person_curly_hair: | `:person_curly_hair:` | :white_haired_woman: | `:white_haired_woman:` | [top](#table-of-contents) |
+| [top](#people--body) | :person_white_hair: | `:person_white_hair:` | :bald_woman: | `:bald_woman:` | [top](#table-of-contents) |
+| [top](#people--body) | :person_bald: | `:person_bald:` | :blond_haired_woman: | `:blond_haired_woman:` `:blonde_woman:` | [top](#table-of-contents) |
+| [top](#people--body) | :blond_haired_man: | `:blond_haired_man:` | :older_adult: | `:older_adult:` | [top](#table-of-contents) |
+| [top](#people--body) | :older_man: | `:older_man:` | :older_woman: | `:older_woman:` | [top](#table-of-contents) |
+
+### Person Gesture
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#people--body) | :frowning_person: | `:frowning_person:` | :frowning_man: | `:frowning_man:` | [top](#table-of-contents) |
+| [top](#people--body) | :frowning_woman: | `:frowning_woman:` | :pouting_face: | `:pouting_face:` | [top](#table-of-contents) |
+| [top](#people--body) | :pouting_man: | `:pouting_man:` | :pouting_woman: | `:pouting_woman:` | [top](#table-of-contents) |
+| [top](#people--body) | :no_good: | `:no_good:` | :ng_man: | `:ng_man:` `:no_good_man:` | [top](#table-of-contents) |
+| [top](#people--body) | :ng_woman: | `:ng_woman:` `:no_good_woman:` | :ok_person: | `:ok_person:` | [top](#table-of-contents) |
+| [top](#people--body) | :ok_man: | `:ok_man:` | :ok_woman: | `:ok_woman:` | [top](#table-of-contents) |
+| [top](#people--body) | :information_desk_person: | `:information_desk_person:` `:tipping_hand_person:` | :sassy_man: | `:sassy_man:` `:tipping_hand_man:` | [top](#table-of-contents) |
+| [top](#people--body) | :sassy_woman: | `:sassy_woman:` `:tipping_hand_woman:` | :raising_hand: | `:raising_hand:` | [top](#table-of-contents) |
+| [top](#people--body) | :raising_hand_man: | `:raising_hand_man:` | :raising_hand_woman: | `:raising_hand_woman:` | [top](#table-of-contents) |
+| [top](#people--body) | :deaf_person: | `:deaf_person:` | :deaf_man: | `:deaf_man:` | [top](#table-of-contents) |
+| [top](#people--body) | :deaf_woman: | `:deaf_woman:` | :bow: | `:bow:` | [top](#table-of-contents) |
+| [top](#people--body) | :bowing_man: | `:bowing_man:` | :bowing_woman: | `:bowing_woman:` | [top](#table-of-contents) |
+| [top](#people--body) | :facepalm: | `:facepalm:` | :man_facepalming: | `:man_facepalming:` | [top](#table-of-contents) |
+| [top](#people--body) | :woman_facepalming: | `:woman_facepalming:` | :shrug: | `:shrug:` | [top](#table-of-contents) |
+| [top](#people--body) | :man_shrugging: | `:man_shrugging:` | :woman_shrugging: | `:woman_shrugging:` | [top](#table-of-contents) |
+
+### Person Role
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#people--body) | :health_worker: | `:health_worker:` | :man_health_worker: | `:man_health_worker:` | [top](#table-of-contents) |
+| [top](#people--body) | :woman_health_worker: | `:woman_health_worker:` | :student: | `:student:` | [top](#table-of-contents) |
+| [top](#people--body) | :man_student: | `:man_student:` | :woman_student: | `:woman_student:` | [top](#table-of-contents) |
+| [top](#people--body) | :teacher: | `:teacher:` | :man_teacher: | `:man_teacher:` | [top](#table-of-contents) |
+| [top](#people--body) | :woman_teacher: | `:woman_teacher:` | :judge: | `:judge:` | [top](#table-of-contents) |
+| [top](#people--body) | :man_judge: | `:man_judge:` | :woman_judge: | `:woman_judge:` | [top](#table-of-contents) |
+| [top](#people--body) | :farmer: | `:farmer:` | :man_farmer: | `:man_farmer:` | [top](#table-of-contents) |
+| [top](#people--body) | :woman_farmer: | `:woman_farmer:` | :cook: | `:cook:` | [top](#table-of-contents) |
+| [top](#people--body) | :man_cook: | `:man_cook:` | :woman_cook: | `:woman_cook:` | [top](#table-of-contents) |
+| [top](#people--body) | :mechanic: | `:mechanic:` | :man_mechanic: | `:man_mechanic:` | [top](#table-of-contents) |
+| [top](#people--body) | :woman_mechanic: | `:woman_mechanic:` | :factory_worker: | `:factory_worker:` | [top](#table-of-contents) |
+| [top](#people--body) | :man_factory_worker: | `:man_factory_worker:` | :woman_factory_worker: | `:woman_factory_worker:` | [top](#table-of-contents) |
+| [top](#people--body) | :office_worker: | `:office_worker:` | :man_office_worker: | `:man_office_worker:` | [top](#table-of-contents) |
+| [top](#people--body) | :woman_office_worker: | `:woman_office_worker:` | :scientist: | `:scientist:` | [top](#table-of-contents) |
+| [top](#people--body) | :man_scientist: | `:man_scientist:` | :woman_scientist: | `:woman_scientist:` | [top](#table-of-contents) |
+| [top](#people--body) | :technologist: | `:technologist:` | :man_technologist: | `:man_technologist:` | [top](#table-of-contents) |
+| [top](#people--body) | :woman_technologist: | `:woman_technologist:` | :singer: | `:singer:` | [top](#table-of-contents) |
+| [top](#people--body) | :man_singer: | `:man_singer:` | :woman_singer: | `:woman_singer:` | [top](#table-of-contents) |
+| [top](#people--body) | :artist: | `:artist:` | :man_artist: | `:man_artist:` | [top](#table-of-contents) |
+| [top](#people--body) | :woman_artist: | `:woman_artist:` | :pilot: | `:pilot:` | [top](#table-of-contents) |
+| [top](#people--body) | :man_pilot: | `:man_pilot:` | :woman_pilot: | `:woman_pilot:` | [top](#table-of-contents) |
+| [top](#people--body) | :astronaut: | `:astronaut:` | :man_astronaut: | `:man_astronaut:` | [top](#table-of-contents) |
+| [top](#people--body) | :woman_astronaut: | `:woman_astronaut:` | :firefighter: | `:firefighter:` | [top](#table-of-contents) |
+| [top](#people--body) | :man_firefighter: | `:man_firefighter:` | :woman_firefighter: | `:woman_firefighter:` | [top](#table-of-contents) |
+| [top](#people--body) | :cop: | `:cop:` `:police_officer:` | :policeman: | `:policeman:` | [top](#table-of-contents) |
+| [top](#people--body) | :policewoman: | `:policewoman:` | :detective: | `:detective:` | [top](#table-of-contents) |
+| [top](#people--body) | :male_detective: | `:male_detective:` | :female_detective: | `:female_detective:` | [top](#table-of-contents) |
+| [top](#people--body) | :guard: | `:guard:` | :guardsman: | `:guardsman:` | [top](#table-of-contents) |
+| [top](#people--body) | :guardswoman: | `:guardswoman:` | :ninja: | `:ninja:` | [top](#table-of-contents) |
+| [top](#people--body) | :construction_worker: | `:construction_worker:` | :construction_worker_man: | `:construction_worker_man:` | [top](#table-of-contents) |
+| [top](#people--body) | :construction_worker_woman: | `:construction_worker_woman:` | :person_with_crown: | `:person_with_crown:` | [top](#table-of-contents) |
+| [top](#people--body) | :prince: | `:prince:` | :princess: | `:princess:` | [top](#table-of-contents) |
+| [top](#people--body) | :person_with_turban: | `:person_with_turban:` | :man_with_turban: | `:man_with_turban:` | [top](#table-of-contents) |
+| [top](#people--body) | :woman_with_turban: | `:woman_with_turban:` | :man_with_gua_pi_mao: | `:man_with_gua_pi_mao:` | [top](#table-of-contents) |
+| [top](#people--body) | :woman_with_headscarf: | `:woman_with_headscarf:` | :person_in_tuxedo: | `:person_in_tuxedo:` | [top](#table-of-contents) |
+| [top](#people--body) | :man_in_tuxedo: | `:man_in_tuxedo:` | :woman_in_tuxedo: | `:woman_in_tuxedo:` | [top](#table-of-contents) |
+| [top](#people--body) | :person_with_veil: | `:person_with_veil:` | :man_with_veil: | `:man_with_veil:` | [top](#table-of-contents) |
+| [top](#people--body) | :bride_with_veil: | `:bride_with_veil:` `:woman_with_veil:` | :pregnant_woman: | `:pregnant_woman:` | [top](#table-of-contents) |
+| [top](#people--body) | :pregnant_man: | `:pregnant_man:` | :pregnant_person: | `:pregnant_person:` | [top](#table-of-contents) |
+| [top](#people--body) | :breast_feeding: | `:breast_feeding:` | :woman_feeding_baby: | `:woman_feeding_baby:` | [top](#table-of-contents) |
+| [top](#people--body) | :man_feeding_baby: | `:man_feeding_baby:` | :person_feeding_baby: | `:person_feeding_baby:` | [top](#table-of-contents) |
+
+### Person Fantasy
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#people--body) | :angel: | `:angel:` | :santa: | `:santa:` | [top](#table-of-contents) |
+| [top](#people--body) | :mrs_claus: | `:mrs_claus:` | :mx_claus: | `:mx_claus:` | [top](#table-of-contents) |
+| [top](#people--body) | :superhero: | `:superhero:` | :superhero_man: | `:superhero_man:` | [top](#table-of-contents) |
+| [top](#people--body) | :superhero_woman: | `:superhero_woman:` | :supervillain: | `:supervillain:` | [top](#table-of-contents) |
+| [top](#people--body) | :supervillain_man: | `:supervillain_man:` | :supervillain_woman: | `:supervillain_woman:` | [top](#table-of-contents) |
+| [top](#people--body) | :mage: | `:mage:` | :mage_man: | `:mage_man:` | [top](#table-of-contents) |
+| [top](#people--body) | :mage_woman: | `:mage_woman:` | :fairy: | `:fairy:` | [top](#table-of-contents) |
+| [top](#people--body) | :fairy_man: | `:fairy_man:` | :fairy_woman: | `:fairy_woman:` | [top](#table-of-contents) |
+| [top](#people--body) | :vampire: | `:vampire:` | :vampire_man: | `:vampire_man:` | [top](#table-of-contents) |
+| [top](#people--body) | :vampire_woman: | `:vampire_woman:` | :merperson: | `:merperson:` | [top](#table-of-contents) |
+| [top](#people--body) | :merman: | `:merman:` | :mermaid: | `:mermaid:` | [top](#table-of-contents) |
+| [top](#people--body) | :elf: | `:elf:` | :elf_man: | `:elf_man:` | [top](#table-of-contents) |
+| [top](#people--body) | :elf_woman: | `:elf_woman:` | :genie: | `:genie:` | [top](#table-of-contents) |
+| [top](#people--body) | :genie_man: | `:genie_man:` | :genie_woman: | `:genie_woman:` | [top](#table-of-contents) |
+| [top](#people--body) | :zombie: | `:zombie:` | :zombie_man: | `:zombie_man:` | [top](#table-of-contents) |
+| [top](#people--body) | :zombie_woman: | `:zombie_woman:` | :troll: | `:troll:` | [top](#table-of-contents) |
+
+### Person Activity
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#people--body) | :massage: | `:massage:` | :massage_man: | `:massage_man:` | [top](#table-of-contents) |
+| [top](#people--body) | :massage_woman: | `:massage_woman:` | :haircut: | `:haircut:` | [top](#table-of-contents) |
+| [top](#people--body) | :haircut_man: | `:haircut_man:` | :haircut_woman: | `:haircut_woman:` | [top](#table-of-contents) |
+| [top](#people--body) | :walking: | `:walking:` | :walking_man: | `:walking_man:` | [top](#table-of-contents) |
+| [top](#people--body) | :walking_woman: | `:walking_woman:` | :standing_person: | `:standing_person:` | [top](#table-of-contents) |
+| [top](#people--body) | :standing_man: | `:standing_man:` | :standing_woman: | `:standing_woman:` | [top](#table-of-contents) |
+| [top](#people--body) | :kneeling_person: | `:kneeling_person:` | :kneeling_man: | `:kneeling_man:` | [top](#table-of-contents) |
+| [top](#people--body) | :kneeling_woman: | `:kneeling_woman:` | :person_with_probing_cane: | `:person_with_probing_cane:` | [top](#table-of-contents) |
+| [top](#people--body) | :man_with_probing_cane: | `:man_with_probing_cane:` | :woman_with_probing_cane: | `:woman_with_probing_cane:` | [top](#table-of-contents) |
+| [top](#people--body) | :person_in_motorized_wheelchair: | `:person_in_motorized_wheelchair:` | :man_in_motorized_wheelchair: | `:man_in_motorized_wheelchair:` | [top](#table-of-contents) |
+| [top](#people--body) | :woman_in_motorized_wheelchair: | `:woman_in_motorized_wheelchair:` | :person_in_manual_wheelchair: | `:person_in_manual_wheelchair:` | [top](#table-of-contents) |
+| [top](#people--body) | :man_in_manual_wheelchair: | `:man_in_manual_wheelchair:` | :woman_in_manual_wheelchair: | `:woman_in_manual_wheelchair:` | [top](#table-of-contents) |
+| [top](#people--body) | :runner: | `:runner:` `:running:` | :running_man: | `:running_man:` | [top](#table-of-contents) |
+| [top](#people--body) | :running_woman: | `:running_woman:` | :dancer: | `:dancer:` `:woman_dancing:` | [top](#table-of-contents) |
+| [top](#people--body) | :man_dancing: | `:man_dancing:` | :business_suit_levitating: | `:business_suit_levitating:` | [top](#table-of-contents) |
+| [top](#people--body) | :dancers: | `:dancers:` | :dancing_men: | `:dancing_men:` | [top](#table-of-contents) |
+| [top](#people--body) | :dancing_women: | `:dancing_women:` | :sauna_person: | `:sauna_person:` | [top](#table-of-contents) |
+| [top](#people--body) | :sauna_man: | `:sauna_man:` | :sauna_woman: | `:sauna_woman:` | [top](#table-of-contents) |
+| [top](#people--body) | :climbing: | `:climbing:` | :climbing_man: | `:climbing_man:` | [top](#table-of-contents) |
+| [top](#people--body) | :climbing_woman: | `:climbing_woman:` | | | [top](#table-of-contents) |
+
+### Person Sport
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#people--body) | :person_fencing: | `:person_fencing:` | :horse_racing: | `:horse_racing:` | [top](#table-of-contents) |
+| [top](#people--body) | :skier: | `:skier:` | :snowboarder: | `:snowboarder:` | [top](#table-of-contents) |
+| [top](#people--body) | :golfing: | `:golfing:` | :golfing_man: | `:golfing_man:` | [top](#table-of-contents) |
+| [top](#people--body) | :golfing_woman: | `:golfing_woman:` | :surfer: | `:surfer:` | [top](#table-of-contents) |
+| [top](#people--body) | :surfing_man: | `:surfing_man:` | :surfing_woman: | `:surfing_woman:` | [top](#table-of-contents) |
+| [top](#people--body) | :rowboat: | `:rowboat:` | :rowing_man: | `:rowing_man:` | [top](#table-of-contents) |
+| [top](#people--body) | :rowing_woman: | `:rowing_woman:` | :swimmer: | `:swimmer:` | [top](#table-of-contents) |
+| [top](#people--body) | :swimming_man: | `:swimming_man:` | :swimming_woman: | `:swimming_woman:` | [top](#table-of-contents) |
+| [top](#people--body) | :bouncing_ball_person: | `:bouncing_ball_person:` | :basketball_man: | `:basketball_man:` `:bouncing_ball_man:` | [top](#table-of-contents) |
+| [top](#people--body) | :basketball_woman: | `:basketball_woman:` `:bouncing_ball_woman:` | :weight_lifting: | `:weight_lifting:` | [top](#table-of-contents) |
+| [top](#people--body) | :weight_lifting_man: | `:weight_lifting_man:` | :weight_lifting_woman: | `:weight_lifting_woman:` | [top](#table-of-contents) |
+| [top](#people--body) | :bicyclist: | `:bicyclist:` | :biking_man: | `:biking_man:` | [top](#table-of-contents) |
+| [top](#people--body) | :biking_woman: | `:biking_woman:` | :mountain_bicyclist: | `:mountain_bicyclist:` | [top](#table-of-contents) |
+| [top](#people--body) | :mountain_biking_man: | `:mountain_biking_man:` | :mountain_biking_woman: | `:mountain_biking_woman:` | [top](#table-of-contents) |
+| [top](#people--body) | :cartwheeling: | `:cartwheeling:` | :man_cartwheeling: | `:man_cartwheeling:` | [top](#table-of-contents) |
+| [top](#people--body) | :woman_cartwheeling: | `:woman_cartwheeling:` | :wrestling: | `:wrestling:` | [top](#table-of-contents) |
+| [top](#people--body) | :men_wrestling: | `:men_wrestling:` | :women_wrestling: | `:women_wrestling:` | [top](#table-of-contents) |
+| [top](#people--body) | :water_polo: | `:water_polo:` | :man_playing_water_polo: | `:man_playing_water_polo:` | [top](#table-of-contents) |
+| [top](#people--body) | :woman_playing_water_polo: | `:woman_playing_water_polo:` | :handball_person: | `:handball_person:` | [top](#table-of-contents) |
+| [top](#people--body) | :man_playing_handball: | `:man_playing_handball:` | :woman_playing_handball: | `:woman_playing_handball:` | [top](#table-of-contents) |
+| [top](#people--body) | :juggling_person: | `:juggling_person:` | :man_juggling: | `:man_juggling:` | [top](#table-of-contents) |
+| [top](#people--body) | :woman_juggling: | `:woman_juggling:` | | | [top](#table-of-contents) |
+
+### Person Resting
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#people--body) | :lotus_position: | `:lotus_position:` | :lotus_position_man: | `:lotus_position_man:` | [top](#table-of-contents) |
+| [top](#people--body) | :lotus_position_woman: | `:lotus_position_woman:` | :bath: | `:bath:` | [top](#table-of-contents) |
+| [top](#people--body) | :sleeping_bed: | `:sleeping_bed:` | | | [top](#table-of-contents) |
+
+### Family
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#people--body) | :people_holding_hands: | `:people_holding_hands:` | :two_women_holding_hands: | `:two_women_holding_hands:` | [top](#table-of-contents) |
+| [top](#people--body) | :couple: | `:couple:` | :two_men_holding_hands: | `:two_men_holding_hands:` | [top](#table-of-contents) |
+| [top](#people--body) | :couplekiss: | `:couplekiss:` | :couplekiss_man_woman: | `:couplekiss_man_woman:` | [top](#table-of-contents) |
+| [top](#people--body) | :couplekiss_man_man: | `:couplekiss_man_man:` | :couplekiss_woman_woman: | `:couplekiss_woman_woman:` | [top](#table-of-contents) |
+| [top](#people--body) | :couple_with_heart: | `:couple_with_heart:` | :couple_with_heart_woman_man: | `:couple_with_heart_woman_man:` | [top](#table-of-contents) |
+| [top](#people--body) | :couple_with_heart_man_man: | `:couple_with_heart_man_man:` | :couple_with_heart_woman_woman: | `:couple_with_heart_woman_woman:` | [top](#table-of-contents) |
+| [top](#people--body) | :family_man_woman_boy: | `:family_man_woman_boy:` | :family_man_woman_girl: | `:family_man_woman_girl:` | [top](#table-of-contents) |
+| [top](#people--body) | :family_man_woman_girl_boy: | `:family_man_woman_girl_boy:` | :family_man_woman_boy_boy: | `:family_man_woman_boy_boy:` | [top](#table-of-contents) |
+| [top](#people--body) | :family_man_woman_girl_girl: | `:family_man_woman_girl_girl:` | :family_man_man_boy: | `:family_man_man_boy:` | [top](#table-of-contents) |
+| [top](#people--body) | :family_man_man_girl: | `:family_man_man_girl:` | :family_man_man_girl_boy: | `:family_man_man_girl_boy:` | [top](#table-of-contents) |
+| [top](#people--body) | :family_man_man_boy_boy: | `:family_man_man_boy_boy:` | :family_man_man_girl_girl: | `:family_man_man_girl_girl:` | [top](#table-of-contents) |
+| [top](#people--body) | :family_woman_woman_boy: | `:family_woman_woman_boy:` | :family_woman_woman_girl: | `:family_woman_woman_girl:` | [top](#table-of-contents) |
+| [top](#people--body) | :family_woman_woman_girl_boy: | `:family_woman_woman_girl_boy:` | :family_woman_woman_boy_boy: | `:family_woman_woman_boy_boy:` | [top](#table-of-contents) |
+| [top](#people--body) | :family_woman_woman_girl_girl: | `:family_woman_woman_girl_girl:` | :family_man_boy: | `:family_man_boy:` | [top](#table-of-contents) |
+| [top](#people--body) | :family_man_boy_boy: | `:family_man_boy_boy:` | :family_man_girl: | `:family_man_girl:` | [top](#table-of-contents) |
+| [top](#people--body) | :family_man_girl_boy: | `:family_man_girl_boy:` | :family_man_girl_girl: | `:family_man_girl_girl:` | [top](#table-of-contents) |
+| [top](#people--body) | :family_woman_boy: | `:family_woman_boy:` | :family_woman_boy_boy: | `:family_woman_boy_boy:` | [top](#table-of-contents) |
+| [top](#people--body) | :family_woman_girl: | `:family_woman_girl:` | :family_woman_girl_boy: | `:family_woman_girl_boy:` | [top](#table-of-contents) |
+| [top](#people--body) | :family_woman_girl_girl: | `:family_woman_girl_girl:` | | | [top](#table-of-contents) |
+
+### Person Symbol
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#people--body) | :speaking_head: | `:speaking_head:` | :bust_in_silhouette: | `:bust_in_silhouette:` | [top](#table-of-contents) |
+| [top](#people--body) | :busts_in_silhouette: | `:busts_in_silhouette:` | :people_hugging: | `:people_hugging:` | [top](#table-of-contents) |
+| [top](#people--body) | :family: | `:family:` | :footprints: | `:footprints:` | [top](#table-of-contents) |
+
+## Animals & Nature
+
+- [Animal Mammal](#animal-mammal)
+- [Animal Bird](#animal-bird)
+- [Animal Amphibian](#animal-amphibian)
+- [Animal Reptile](#animal-reptile)
+- [Animal Marine](#animal-marine)
+- [Animal Bug](#animal-bug)
+- [Plant Flower](#plant-flower)
+- [Plant Other](#plant-other)
+
+### Animal Mammal
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#animals--nature) | :monkey_face: | `:monkey_face:` | :monkey: | `:monkey:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :gorilla: | `:gorilla:` | :orangutan: | `:orangutan:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :dog: | `:dog:` | :dog2: | `:dog2:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :guide_dog: | `:guide_dog:` | :service_dog: | `:service_dog:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :poodle: | `:poodle:` | :wolf: | `:wolf:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :fox_face: | `:fox_face:` | :raccoon: | `:raccoon:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :cat: | `:cat:` | :cat2: | `:cat2:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :black_cat: | `:black_cat:` | :lion: | `:lion:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :tiger: | `:tiger:` | :tiger2: | `:tiger2:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :leopard: | `:leopard:` | :horse: | `:horse:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :moose: | `:moose:` | :donkey: | `:donkey:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :racehorse: | `:racehorse:` | :unicorn: | `:unicorn:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :zebra: | `:zebra:` | :deer: | `:deer:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :bison: | `:bison:` | :cow: | `:cow:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :ox: | `:ox:` | :water_buffalo: | `:water_buffalo:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :cow2: | `:cow2:` | :pig: | `:pig:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :pig2: | `:pig2:` | :boar: | `:boar:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :pig_nose: | `:pig_nose:` | :ram: | `:ram:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :sheep: | `:sheep:` | :goat: | `:goat:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :dromedary_camel: | `:dromedary_camel:` | :camel: | `:camel:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :llama: | `:llama:` | :giraffe: | `:giraffe:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :elephant: | `:elephant:` | :mammoth: | `:mammoth:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :rhinoceros: | `:rhinoceros:` | :hippopotamus: | `:hippopotamus:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :mouse: | `:mouse:` | :mouse2: | `:mouse2:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :rat: | `:rat:` | :hamster: | `:hamster:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :rabbit: | `:rabbit:` | :rabbit2: | `:rabbit2:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :chipmunk: | `:chipmunk:` | :beaver: | `:beaver:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :hedgehog: | `:hedgehog:` | :bat: | `:bat:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :bear: | `:bear:` | :polar_bear: | `:polar_bear:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :koala: | `:koala:` | :panda_face: | `:panda_face:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :sloth: | `:sloth:` | :otter: | `:otter:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :skunk: | `:skunk:` | :kangaroo: | `:kangaroo:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :badger: | `:badger:` | :feet: | `:feet:` `:paw_prints:` | [top](#table-of-contents) |
+
+### Animal Bird
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#animals--nature) | :turkey: | `:turkey:` | :chicken: | `:chicken:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :rooster: | `:rooster:` | :hatching_chick: | `:hatching_chick:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :baby_chick: | `:baby_chick:` | :hatched_chick: | `:hatched_chick:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :bird: | `:bird:` | :penguin: | `:penguin:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :dove: | `:dove:` | :eagle: | `:eagle:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :duck: | `:duck:` | :swan: | `:swan:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :owl: | `:owl:` | :dodo: | `:dodo:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :feather: | `:feather:` | :flamingo: | `:flamingo:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :peacock: | `:peacock:` | :parrot: | `:parrot:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :wing: | `:wing:` | :black_bird: | `:black_bird:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :goose: | `:goose:` | | | [top](#table-of-contents) |
+
+### Animal Amphibian
+
+| | ico | shortcode | |
+| - | :-: | - | - |
+| [top](#animals--nature) | :frog: | `:frog:` | [top](#table-of-contents) |
+
+### Animal Reptile
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#animals--nature) | :crocodile: | `:crocodile:` | :turtle: | `:turtle:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :lizard: | `:lizard:` | :snake: | `:snake:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :dragon_face: | `:dragon_face:` | :dragon: | `:dragon:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :sauropod: | `:sauropod:` | :t-rex: | `:t-rex:` | [top](#table-of-contents) |
+
+### Animal Marine
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#animals--nature) | :whale: | `:whale:` | :whale2: | `:whale2:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :dolphin: | `:dolphin:` `:flipper:` | :seal: | `:seal:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :fish: | `:fish:` | :tropical_fish: | `:tropical_fish:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :blowfish: | `:blowfish:` | :shark: | `:shark:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :octopus: | `:octopus:` | :shell: | `:shell:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :coral: | `:coral:` | :jellyfish: | `:jellyfish:` | [top](#table-of-contents) |
+
+### Animal Bug
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#animals--nature) | :snail: | `:snail:` | :butterfly: | `:butterfly:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :bug: | `:bug:` | :ant: | `:ant:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :bee: | `:bee:` `:honeybee:` | :beetle: | `:beetle:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :lady_beetle: | `:lady_beetle:` | :cricket: | `:cricket:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :cockroach: | `:cockroach:` | :spider: | `:spider:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :spider_web: | `:spider_web:` | :scorpion: | `:scorpion:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :mosquito: | `:mosquito:` | :fly: | `:fly:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :worm: | `:worm:` | :microbe: | `:microbe:` | [top](#table-of-contents) |
+
+### Plant Flower
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#animals--nature) | :bouquet: | `:bouquet:` | :cherry_blossom: | `:cherry_blossom:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :white_flower: | `:white_flower:` | :lotus: | `:lotus:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :rosette: | `:rosette:` | :rose: | `:rose:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :wilted_flower: | `:wilted_flower:` | :hibiscus: | `:hibiscus:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :sunflower: | `:sunflower:` | :blossom: | `:blossom:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :tulip: | `:tulip:` | :hyacinth: | `:hyacinth:` | [top](#table-of-contents) |
+
+### Plant Other
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#animals--nature) | :seedling: | `:seedling:` | :potted_plant: | `:potted_plant:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :evergreen_tree: | `:evergreen_tree:` | :deciduous_tree: | `:deciduous_tree:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :palm_tree: | `:palm_tree:` | :cactus: | `:cactus:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :ear_of_rice: | `:ear_of_rice:` | :herb: | `:herb:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :shamrock: | `:shamrock:` | :four_leaf_clover: | `:four_leaf_clover:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :maple_leaf: | `:maple_leaf:` | :fallen_leaf: | `:fallen_leaf:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :leaves: | `:leaves:` | :empty_nest: | `:empty_nest:` | [top](#table-of-contents) |
+| [top](#animals--nature) | :nest_with_eggs: | `:nest_with_eggs:` | :mushroom: | `:mushroom:` | [top](#table-of-contents) |
+
+## Food & Drink
+
+- [Food Fruit](#food-fruit)
+- [Food Vegetable](#food-vegetable)
+- [Food Prepared](#food-prepared)
+- [Food Asian](#food-asian)
+- [Food Marine](#food-marine)
+- [Food Sweet](#food-sweet)
+- [Drink](#drink)
+- [Dishware](#dishware)
+
+### Food Fruit
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#food--drink) | :grapes: | `:grapes:` | :melon: | `:melon:` | [top](#table-of-contents) |
+| [top](#food--drink) | :watermelon: | `:watermelon:` | :mandarin: | `:mandarin:` `:orange:` `:tangerine:` | [top](#table-of-contents) |
+| [top](#food--drink) | :lemon: | `:lemon:` | :banana: | `:banana:` | [top](#table-of-contents) |
+| [top](#food--drink) | :pineapple: | `:pineapple:` | :mango: | `:mango:` | [top](#table-of-contents) |
+| [top](#food--drink) | :apple: | `:apple:` | :green_apple: | `:green_apple:` | [top](#table-of-contents) |
+| [top](#food--drink) | :pear: | `:pear:` | :peach: | `:peach:` | [top](#table-of-contents) |
+| [top](#food--drink) | :cherries: | `:cherries:` | :strawberry: | `:strawberry:` | [top](#table-of-contents) |
+| [top](#food--drink) | :blueberries: | `:blueberries:` | :kiwi_fruit: | `:kiwi_fruit:` | [top](#table-of-contents) |
+| [top](#food--drink) | :tomato: | `:tomato:` | :olive: | `:olive:` | [top](#table-of-contents) |
+| [top](#food--drink) | :coconut: | `:coconut:` | | | [top](#table-of-contents) |
+
+### Food Vegetable
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#food--drink) | :avocado: | `:avocado:` | :eggplant: | `:eggplant:` | [top](#table-of-contents) |
+| [top](#food--drink) | :potato: | `:potato:` | :carrot: | `:carrot:` | [top](#table-of-contents) |
+| [top](#food--drink) | :corn: | `:corn:` | :hot_pepper: | `:hot_pepper:` | [top](#table-of-contents) |
+| [top](#food--drink) | :bell_pepper: | `:bell_pepper:` | :cucumber: | `:cucumber:` | [top](#table-of-contents) |
+| [top](#food--drink) | :leafy_green: | `:leafy_green:` | :broccoli: | `:broccoli:` | [top](#table-of-contents) |
+| [top](#food--drink) | :garlic: | `:garlic:` | :onion: | `:onion:` | [top](#table-of-contents) |
+| [top](#food--drink) | :peanuts: | `:peanuts:` | :beans: | `:beans:` | [top](#table-of-contents) |
+| [top](#food--drink) | :chestnut: | `:chestnut:` | :ginger_root: | `:ginger_root:` | [top](#table-of-contents) |
+| [top](#food--drink) | :pea_pod: | `:pea_pod:` | | | [top](#table-of-contents) |
+
+### Food Prepared
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#food--drink) | :bread: | `:bread:` | :croissant: | `:croissant:` | [top](#table-of-contents) |
+| [top](#food--drink) | :baguette_bread: | `:baguette_bread:` | :flatbread: | `:flatbread:` | [top](#table-of-contents) |
+| [top](#food--drink) | :pretzel: | `:pretzel:` | :bagel: | `:bagel:` | [top](#table-of-contents) |
+| [top](#food--drink) | :pancakes: | `:pancakes:` | :waffle: | `:waffle:` | [top](#table-of-contents) |
+| [top](#food--drink) | :cheese: | `:cheese:` | :meat_on_bone: | `:meat_on_bone:` | [top](#table-of-contents) |
+| [top](#food--drink) | :poultry_leg: | `:poultry_leg:` | :cut_of_meat: | `:cut_of_meat:` | [top](#table-of-contents) |
+| [top](#food--drink) | :bacon: | `:bacon:` | :hamburger: | `:hamburger:` | [top](#table-of-contents) |
+| [top](#food--drink) | :fries: | `:fries:` | :pizza: | `:pizza:` | [top](#table-of-contents) |
+| [top](#food--drink) | :hotdog: | `:hotdog:` | :sandwich: | `:sandwich:` | [top](#table-of-contents) |
+| [top](#food--drink) | :taco: | `:taco:` | :burrito: | `:burrito:` | [top](#table-of-contents) |
+| [top](#food--drink) | :tamale: | `:tamale:` | :stuffed_flatbread: | `:stuffed_flatbread:` | [top](#table-of-contents) |
+| [top](#food--drink) | :falafel: | `:falafel:` | :egg: | `:egg:` | [top](#table-of-contents) |
+| [top](#food--drink) | :fried_egg: | `:fried_egg:` | :shallow_pan_of_food: | `:shallow_pan_of_food:` | [top](#table-of-contents) |
+| [top](#food--drink) | :stew: | `:stew:` | :fondue: | `:fondue:` | [top](#table-of-contents) |
+| [top](#food--drink) | :bowl_with_spoon: | `:bowl_with_spoon:` | :green_salad: | `:green_salad:` | [top](#table-of-contents) |
+| [top](#food--drink) | :popcorn: | `:popcorn:` | :butter: | `:butter:` | [top](#table-of-contents) |
+| [top](#food--drink) | :salt: | `:salt:` | :canned_food: | `:canned_food:` | [top](#table-of-contents) |
+
+### Food Asian
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#food--drink) | :bento: | `:bento:` | :rice_cracker: | `:rice_cracker:` | [top](#table-of-contents) |
+| [top](#food--drink) | :rice_ball: | `:rice_ball:` | :rice: | `:rice:` | [top](#table-of-contents) |
+| [top](#food--drink) | :curry: | `:curry:` | :ramen: | `:ramen:` | [top](#table-of-contents) |
+| [top](#food--drink) | :spaghetti: | `:spaghetti:` | :sweet_potato: | `:sweet_potato:` | [top](#table-of-contents) |
+| [top](#food--drink) | :oden: | `:oden:` | :sushi: | `:sushi:` | [top](#table-of-contents) |
+| [top](#food--drink) | :fried_shrimp: | `:fried_shrimp:` | :fish_cake: | `:fish_cake:` | [top](#table-of-contents) |
+| [top](#food--drink) | :moon_cake: | `:moon_cake:` | :dango: | `:dango:` | [top](#table-of-contents) |
+| [top](#food--drink) | :dumpling: | `:dumpling:` | :fortune_cookie: | `:fortune_cookie:` | [top](#table-of-contents) |
+| [top](#food--drink) | :takeout_box: | `:takeout_box:` | | | [top](#table-of-contents) |
+
+### Food Marine
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#food--drink) | :crab: | `:crab:` | :lobster: | `:lobster:` | [top](#table-of-contents) |
+| [top](#food--drink) | :shrimp: | `:shrimp:` | :squid: | `:squid:` | [top](#table-of-contents) |
+| [top](#food--drink) | :oyster: | `:oyster:` | | | [top](#table-of-contents) |
+
+### Food Sweet
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#food--drink) | :icecream: | `:icecream:` | :shaved_ice: | `:shaved_ice:` | [top](#table-of-contents) |
+| [top](#food--drink) | :ice_cream: | `:ice_cream:` | :doughnut: | `:doughnut:` | [top](#table-of-contents) |
+| [top](#food--drink) | :cookie: | `:cookie:` | :birthday: | `:birthday:` | [top](#table-of-contents) |
+| [top](#food--drink) | :cake: | `:cake:` | :cupcake: | `:cupcake:` | [top](#table-of-contents) |
+| [top](#food--drink) | :pie: | `:pie:` | :chocolate_bar: | `:chocolate_bar:` | [top](#table-of-contents) |
+| [top](#food--drink) | :candy: | `:candy:` | :lollipop: | `:lollipop:` | [top](#table-of-contents) |
+| [top](#food--drink) | :custard: | `:custard:` | :honey_pot: | `:honey_pot:` | [top](#table-of-contents) |
+
+### Drink
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#food--drink) | :baby_bottle: | `:baby_bottle:` | :milk_glass: | `:milk_glass:` | [top](#table-of-contents) |
+| [top](#food--drink) | :coffee: | `:coffee:` | :teapot: | `:teapot:` | [top](#table-of-contents) |
+| [top](#food--drink) | :tea: | `:tea:` | :sake: | `:sake:` | [top](#table-of-contents) |
+| [top](#food--drink) | :champagne: | `:champagne:` | :wine_glass: | `:wine_glass:` | [top](#table-of-contents) |
+| [top](#food--drink) | :cocktail: | `:cocktail:` | :tropical_drink: | `:tropical_drink:` | [top](#table-of-contents) |
+| [top](#food--drink) | :beer: | `:beer:` | :beers: | `:beers:` | [top](#table-of-contents) |
+| [top](#food--drink) | :clinking_glasses: | `:clinking_glasses:` | :tumbler_glass: | `:tumbler_glass:` | [top](#table-of-contents) |
+| [top](#food--drink) | :pouring_liquid: | `:pouring_liquid:` | :cup_with_straw: | `:cup_with_straw:` | [top](#table-of-contents) |
+| [top](#food--drink) | :bubble_tea: | `:bubble_tea:` | :beverage_box: | `:beverage_box:` | [top](#table-of-contents) |
+| [top](#food--drink) | :mate: | `:mate:` | :ice_cube: | `:ice_cube:` | [top](#table-of-contents) |
+
+### Dishware
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#food--drink) | :chopsticks: | `:chopsticks:` | :plate_with_cutlery: | `:plate_with_cutlery:` | [top](#table-of-contents) |
+| [top](#food--drink) | :fork_and_knife: | `:fork_and_knife:` | :spoon: | `:spoon:` | [top](#table-of-contents) |
+| [top](#food--drink) | :hocho: | `:hocho:` `:knife:` | :jar: | `:jar:` | [top](#table-of-contents) |
+| [top](#food--drink) | :amphora: | `:amphora:` | | | [top](#table-of-contents) |
+
+## Travel & Places
+
+- [Place Map](#place-map)
+- [Place Geographic](#place-geographic)
+- [Place Building](#place-building)
+- [Place Religious](#place-religious)
+- [Place Other](#place-other)
+- [Transport Ground](#transport-ground)
+- [Transport Water](#transport-water)
+- [Transport Air](#transport-air)
+- [Hotel](#hotel)
+- [Time](#time)
+- [Sky & Weather](#sky--weather)
+
+### Place Map
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#travel--places) | :earth_africa: | `:earth_africa:` | :earth_americas: | `:earth_americas:` | [top](#table-of-contents) |
+| [top](#travel--places) | :earth_asia: | `:earth_asia:` | :globe_with_meridians: | `:globe_with_meridians:` | [top](#table-of-contents) |
+| [top](#travel--places) | :world_map: | `:world_map:` | :japan: | `:japan:` | [top](#table-of-contents) |
+| [top](#travel--places) | :compass: | `:compass:` | | | [top](#table-of-contents) |
+
+### Place Geographic
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#travel--places) | :mountain_snow: | `:mountain_snow:` | :mountain: | `:mountain:` | [top](#table-of-contents) |
+| [top](#travel--places) | :volcano: | `:volcano:` | :mount_fuji: | `:mount_fuji:` | [top](#table-of-contents) |
+| [top](#travel--places) | :camping: | `:camping:` | :beach_umbrella: | `:beach_umbrella:` | [top](#table-of-contents) |
+| [top](#travel--places) | :desert: | `:desert:` | :desert_island: | `:desert_island:` | [top](#table-of-contents) |
+| [top](#travel--places) | :national_park: | `:national_park:` | | | [top](#table-of-contents) |
+
+### Place Building
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#travel--places) | :stadium: | `:stadium:` | :classical_building: | `:classical_building:` | [top](#table-of-contents) |
+| [top](#travel--places) | :building_construction: | `:building_construction:` | :bricks: | `:bricks:` | [top](#table-of-contents) |
+| [top](#travel--places) | :rock: | `:rock:` | :wood: | `:wood:` | [top](#table-of-contents) |
+| [top](#travel--places) | :hut: | `:hut:` | :houses: | `:houses:` | [top](#table-of-contents) |
+| [top](#travel--places) | :derelict_house: | `:derelict_house:` | :house: | `:house:` | [top](#table-of-contents) |
+| [top](#travel--places) | :house_with_garden: | `:house_with_garden:` | :office: | `:office:` | [top](#table-of-contents) |
+| [top](#travel--places) | :post_office: | `:post_office:` | :european_post_office: | `:european_post_office:` | [top](#table-of-contents) |
+| [top](#travel--places) | :hospital: | `:hospital:` | :bank: | `:bank:` | [top](#table-of-contents) |
+| [top](#travel--places) | :hotel: | `:hotel:` | :love_hotel: | `:love_hotel:` | [top](#table-of-contents) |
+| [top](#travel--places) | :convenience_store: | `:convenience_store:` | :school: | `:school:` | [top](#table-of-contents) |
+| [top](#travel--places) | :department_store: | `:department_store:` | :factory: | `:factory:` | [top](#table-of-contents) |
+| [top](#travel--places) | :japanese_castle: | `:japanese_castle:` | :european_castle: | `:european_castle:` | [top](#table-of-contents) |
+| [top](#travel--places) | :wedding: | `:wedding:` | :tokyo_tower: | `:tokyo_tower:` | [top](#table-of-contents) |
+| [top](#travel--places) | :statue_of_liberty: | `:statue_of_liberty:` | | | [top](#table-of-contents) |
+
+### Place Religious
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#travel--places) | :church: | `:church:` | :mosque: | `:mosque:` | [top](#table-of-contents) |
+| [top](#travel--places) | :hindu_temple: | `:hindu_temple:` | :synagogue: | `:synagogue:` | [top](#table-of-contents) |
+| [top](#travel--places) | :shinto_shrine: | `:shinto_shrine:` | :kaaba: | `:kaaba:` | [top](#table-of-contents) |
+
+### Place Other
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#travel--places) | :fountain: | `:fountain:` | :tent: | `:tent:` | [top](#table-of-contents) |
+| [top](#travel--places) | :foggy: | `:foggy:` | :night_with_stars: | `:night_with_stars:` | [top](#table-of-contents) |
+| [top](#travel--places) | :cityscape: | `:cityscape:` | :sunrise_over_mountains: | `:sunrise_over_mountains:` | [top](#table-of-contents) |
+| [top](#travel--places) | :sunrise: | `:sunrise:` | :city_sunset: | `:city_sunset:` | [top](#table-of-contents) |
+| [top](#travel--places) | :city_sunrise: | `:city_sunrise:` | :bridge_at_night: | `:bridge_at_night:` | [top](#table-of-contents) |
+| [top](#travel--places) | :hotsprings: | `:hotsprings:` | :carousel_horse: | `:carousel_horse:` | [top](#table-of-contents) |
+| [top](#travel--places) | :playground_slide: | `:playground_slide:` | :ferris_wheel: | `:ferris_wheel:` | [top](#table-of-contents) |
+| [top](#travel--places) | :roller_coaster: | `:roller_coaster:` | :barber: | `:barber:` | [top](#table-of-contents) |
+| [top](#travel--places) | :circus_tent: | `:circus_tent:` | | | [top](#table-of-contents) |
+
+### Transport Ground
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#travel--places) | :steam_locomotive: | `:steam_locomotive:` | :railway_car: | `:railway_car:` | [top](#table-of-contents) |
+| [top](#travel--places) | :bullettrain_side: | `:bullettrain_side:` | :bullettrain_front: | `:bullettrain_front:` | [top](#table-of-contents) |
+| [top](#travel--places) | :train2: | `:train2:` | :metro: | `:metro:` | [top](#table-of-contents) |
+| [top](#travel--places) | :light_rail: | `:light_rail:` | :station: | `:station:` | [top](#table-of-contents) |
+| [top](#travel--places) | :tram: | `:tram:` | :monorail: | `:monorail:` | [top](#table-of-contents) |
+| [top](#travel--places) | :mountain_railway: | `:mountain_railway:` | :train: | `:train:` | [top](#table-of-contents) |
+| [top](#travel--places) | :bus: | `:bus:` | :oncoming_bus: | `:oncoming_bus:` | [top](#table-of-contents) |
+| [top](#travel--places) | :trolleybus: | `:trolleybus:` | :minibus: | `:minibus:` | [top](#table-of-contents) |
+| [top](#travel--places) | :ambulance: | `:ambulance:` | :fire_engine: | `:fire_engine:` | [top](#table-of-contents) |
+| [top](#travel--places) | :police_car: | `:police_car:` | :oncoming_police_car: | `:oncoming_police_car:` | [top](#table-of-contents) |
+| [top](#travel--places) | :taxi: | `:taxi:` | :oncoming_taxi: | `:oncoming_taxi:` | [top](#table-of-contents) |
+| [top](#travel--places) | :car: | `:car:` `:red_car:` | :oncoming_automobile: | `:oncoming_automobile:` | [top](#table-of-contents) |
+| [top](#travel--places) | :blue_car: | `:blue_car:` | :pickup_truck: | `:pickup_truck:` | [top](#table-of-contents) |
+| [top](#travel--places) | :truck: | `:truck:` | :articulated_lorry: | `:articulated_lorry:` | [top](#table-of-contents) |
+| [top](#travel--places) | :tractor: | `:tractor:` | :racing_car: | `:racing_car:` | [top](#table-of-contents) |
+| [top](#travel--places) | :motorcycle: | `:motorcycle:` | :motor_scooter: | `:motor_scooter:` | [top](#table-of-contents) |
+| [top](#travel--places) | :manual_wheelchair: | `:manual_wheelchair:` | :motorized_wheelchair: | `:motorized_wheelchair:` | [top](#table-of-contents) |
+| [top](#travel--places) | :auto_rickshaw: | `:auto_rickshaw:` | :bike: | `:bike:` | [top](#table-of-contents) |
+| [top](#travel--places) | :kick_scooter: | `:kick_scooter:` | :skateboard: | `:skateboard:` | [top](#table-of-contents) |
+| [top](#travel--places) | :roller_skate: | `:roller_skate:` | :busstop: | `:busstop:` | [top](#table-of-contents) |
+| [top](#travel--places) | :motorway: | `:motorway:` | :railway_track: | `:railway_track:` | [top](#table-of-contents) |
+| [top](#travel--places) | :oil_drum: | `:oil_drum:` | :fuelpump: | `:fuelpump:` | [top](#table-of-contents) |
+| [top](#travel--places) | :wheel: | `:wheel:` | :rotating_light: | `:rotating_light:` | [top](#table-of-contents) |
+| [top](#travel--places) | :traffic_light: | `:traffic_light:` | :vertical_traffic_light: | `:vertical_traffic_light:` | [top](#table-of-contents) |
+| [top](#travel--places) | :stop_sign: | `:stop_sign:` | :construction: | `:construction:` | [top](#table-of-contents) |
+
+### Transport Water
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#travel--places) | :anchor: | `:anchor:` | :ring_buoy: | `:ring_buoy:` | [top](#table-of-contents) |
+| [top](#travel--places) | :boat: | `:boat:` `:sailboat:` | :canoe: | `:canoe:` | [top](#table-of-contents) |
+| [top](#travel--places) | :speedboat: | `:speedboat:` | :passenger_ship: | `:passenger_ship:` | [top](#table-of-contents) |
+| [top](#travel--places) | :ferry: | `:ferry:` | :motor_boat: | `:motor_boat:` | [top](#table-of-contents) |
+| [top](#travel--places) | :ship: | `:ship:` | | | [top](#table-of-contents) |
+
+### Transport Air
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#travel--places) | :airplane: | `:airplane:` | :small_airplane: | `:small_airplane:` | [top](#table-of-contents) |
+| [top](#travel--places) | :flight_departure: | `:flight_departure:` | :flight_arrival: | `:flight_arrival:` | [top](#table-of-contents) |
+| [top](#travel--places) | :parachute: | `:parachute:` | :seat: | `:seat:` | [top](#table-of-contents) |
+| [top](#travel--places) | :helicopter: | `:helicopter:` | :suspension_railway: | `:suspension_railway:` | [top](#table-of-contents) |
+| [top](#travel--places) | :mountain_cableway: | `:mountain_cableway:` | :aerial_tramway: | `:aerial_tramway:` | [top](#table-of-contents) |
+| [top](#travel--places) | :artificial_satellite: | `:artificial_satellite:` | :rocket: | `:rocket:` | [top](#table-of-contents) |
+| [top](#travel--places) | :flying_saucer: | `:flying_saucer:` | | | [top](#table-of-contents) |
+
+### Hotel
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#travel--places) | :bellhop_bell: | `:bellhop_bell:` | :luggage: | `:luggage:` | [top](#table-of-contents) |
+
+### Time
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#travel--places) | :hourglass: | `:hourglass:` | :hourglass_flowing_sand: | `:hourglass_flowing_sand:` | [top](#table-of-contents) |
+| [top](#travel--places) | :watch: | `:watch:` | :alarm_clock: | `:alarm_clock:` | [top](#table-of-contents) |
+| [top](#travel--places) | :stopwatch: | `:stopwatch:` | :timer_clock: | `:timer_clock:` | [top](#table-of-contents) |
+| [top](#travel--places) | :mantelpiece_clock: | `:mantelpiece_clock:` | :clock12: | `:clock12:` | [top](#table-of-contents) |
+| [top](#travel--places) | :clock1230: | `:clock1230:` | :clock1: | `:clock1:` | [top](#table-of-contents) |
+| [top](#travel--places) | :clock130: | `:clock130:` | :clock2: | `:clock2:` | [top](#table-of-contents) |
+| [top](#travel--places) | :clock230: | `:clock230:` | :clock3: | `:clock3:` | [top](#table-of-contents) |
+| [top](#travel--places) | :clock330: | `:clock330:` | :clock4: | `:clock4:` | [top](#table-of-contents) |
+| [top](#travel--places) | :clock430: | `:clock430:` | :clock5: | `:clock5:` | [top](#table-of-contents) |
+| [top](#travel--places) | :clock530: | `:clock530:` | :clock6: | `:clock6:` | [top](#table-of-contents) |
+| [top](#travel--places) | :clock630: | `:clock630:` | :clock7: | `:clock7:` | [top](#table-of-contents) |
+| [top](#travel--places) | :clock730: | `:clock730:` | :clock8: | `:clock8:` | [top](#table-of-contents) |
+| [top](#travel--places) | :clock830: | `:clock830:` | :clock9: | `:clock9:` | [top](#table-of-contents) |
+| [top](#travel--places) | :clock930: | `:clock930:` | :clock10: | `:clock10:` | [top](#table-of-contents) |
+| [top](#travel--places) | :clock1030: | `:clock1030:` | :clock11: | `:clock11:` | [top](#table-of-contents) |
+| [top](#travel--places) | :clock1130: | `:clock1130:` | | | [top](#table-of-contents) |
+
+### Sky & Weather
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#travel--places) | :new_moon: | `:new_moon:` | :waxing_crescent_moon: | `:waxing_crescent_moon:` | [top](#table-of-contents) |
+| [top](#travel--places) | :first_quarter_moon: | `:first_quarter_moon:` | :moon: | `:moon:` `:waxing_gibbous_moon:` | [top](#table-of-contents) |
+| [top](#travel--places) | :full_moon: | `:full_moon:` | :waning_gibbous_moon: | `:waning_gibbous_moon:` | [top](#table-of-contents) |
+| [top](#travel--places) | :last_quarter_moon: | `:last_quarter_moon:` | :waning_crescent_moon: | `:waning_crescent_moon:` | [top](#table-of-contents) |
+| [top](#travel--places) | :crescent_moon: | `:crescent_moon:` | :new_moon_with_face: | `:new_moon_with_face:` | [top](#table-of-contents) |
+| [top](#travel--places) | :first_quarter_moon_with_face: | `:first_quarter_moon_with_face:` | :last_quarter_moon_with_face: | `:last_quarter_moon_with_face:` | [top](#table-of-contents) |
+| [top](#travel--places) | :thermometer: | `:thermometer:` | :sunny: | `:sunny:` | [top](#table-of-contents) |
+| [top](#travel--places) | :full_moon_with_face: | `:full_moon_with_face:` | :sun_with_face: | `:sun_with_face:` | [top](#table-of-contents) |
+| [top](#travel--places) | :ringed_planet: | `:ringed_planet:` | :star: | `:star:` | [top](#table-of-contents) |
+| [top](#travel--places) | :star2: | `:star2:` | :stars: | `:stars:` | [top](#table-of-contents) |
+| [top](#travel--places) | :milky_way: | `:milky_way:` | :cloud: | `:cloud:` | [top](#table-of-contents) |
+| [top](#travel--places) | :partly_sunny: | `:partly_sunny:` | :cloud_with_lightning_and_rain: | `:cloud_with_lightning_and_rain:` | [top](#table-of-contents) |
+| [top](#travel--places) | :sun_behind_small_cloud: | `:sun_behind_small_cloud:` | :sun_behind_large_cloud: | `:sun_behind_large_cloud:` | [top](#table-of-contents) |
+| [top](#travel--places) | :sun_behind_rain_cloud: | `:sun_behind_rain_cloud:` | :cloud_with_rain: | `:cloud_with_rain:` | [top](#table-of-contents) |
+| [top](#travel--places) | :cloud_with_snow: | `:cloud_with_snow:` | :cloud_with_lightning: | `:cloud_with_lightning:` | [top](#table-of-contents) |
+| [top](#travel--places) | :tornado: | `:tornado:` | :fog: | `:fog:` | [top](#table-of-contents) |
+| [top](#travel--places) | :wind_face: | `:wind_face:` | :cyclone: | `:cyclone:` | [top](#table-of-contents) |
+| [top](#travel--places) | :rainbow: | `:rainbow:` | :closed_umbrella: | `:closed_umbrella:` | [top](#table-of-contents) |
+| [top](#travel--places) | :open_umbrella: | `:open_umbrella:` | :umbrella: | `:umbrella:` | [top](#table-of-contents) |
+| [top](#travel--places) | :parasol_on_ground: | `:parasol_on_ground:` | :zap: | `:zap:` | [top](#table-of-contents) |
+| [top](#travel--places) | :snowflake: | `:snowflake:` | :snowman_with_snow: | `:snowman_with_snow:` | [top](#table-of-contents) |
+| [top](#travel--places) | :snowman: | `:snowman:` | :comet: | `:comet:` | [top](#table-of-contents) |
+| [top](#travel--places) | :fire: | `:fire:` | :droplet: | `:droplet:` | [top](#table-of-contents) |
+| [top](#travel--places) | :ocean: | `:ocean:` | | | [top](#table-of-contents) |
+
+## Activities
+
+- [Event](#event)
+- [Award Medal](#award-medal)
+- [Sport](#sport)
+- [Game](#game)
+- [Arts & Crafts](#arts--crafts)
+
+### Event
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#activities) | :jack_o_lantern: | `:jack_o_lantern:` | :christmas_tree: | `:christmas_tree:` | [top](#table-of-contents) |
+| [top](#activities) | :fireworks: | `:fireworks:` | :sparkler: | `:sparkler:` | [top](#table-of-contents) |
+| [top](#activities) | :firecracker: | `:firecracker:` | :sparkles: | `:sparkles:` | [top](#table-of-contents) |
+| [top](#activities) | :balloon: | `:balloon:` | :tada: | `:tada:` | [top](#table-of-contents) |
+| [top](#activities) | :confetti_ball: | `:confetti_ball:` | :tanabata_tree: | `:tanabata_tree:` | [top](#table-of-contents) |
+| [top](#activities) | :bamboo: | `:bamboo:` | :dolls: | `:dolls:` | [top](#table-of-contents) |
+| [top](#activities) | :flags: | `:flags:` | :wind_chime: | `:wind_chime:` | [top](#table-of-contents) |
+| [top](#activities) | :rice_scene: | `:rice_scene:` | :red_envelope: | `:red_envelope:` | [top](#table-of-contents) |
+| [top](#activities) | :ribbon: | `:ribbon:` | :gift: | `:gift:` | [top](#table-of-contents) |
+| [top](#activities) | :reminder_ribbon: | `:reminder_ribbon:` | :tickets: | `:tickets:` | [top](#table-of-contents) |
+| [top](#activities) | :ticket: | `:ticket:` | | | [top](#table-of-contents) |
+
+### Award Medal
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#activities) | :medal_military: | `:medal_military:` | :trophy: | `:trophy:` | [top](#table-of-contents) |
+| [top](#activities) | :medal_sports: | `:medal_sports:` | :1st_place_medal: | `:1st_place_medal:` | [top](#table-of-contents) |
+| [top](#activities) | :2nd_place_medal: | `:2nd_place_medal:` | :3rd_place_medal: | `:3rd_place_medal:` | [top](#table-of-contents) |
+
+### Sport
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#activities) | :soccer: | `:soccer:` | :baseball: | `:baseball:` | [top](#table-of-contents) |
+| [top](#activities) | :softball: | `:softball:` | :basketball: | `:basketball:` | [top](#table-of-contents) |
+| [top](#activities) | :volleyball: | `:volleyball:` | :football: | `:football:` | [top](#table-of-contents) |
+| [top](#activities) | :rugby_football: | `:rugby_football:` | :tennis: | `:tennis:` | [top](#table-of-contents) |
+| [top](#activities) | :flying_disc: | `:flying_disc:` | :bowling: | `:bowling:` | [top](#table-of-contents) |
+| [top](#activities) | :cricket_game: | `:cricket_game:` | :field_hockey: | `:field_hockey:` | [top](#table-of-contents) |
+| [top](#activities) | :ice_hockey: | `:ice_hockey:` | :lacrosse: | `:lacrosse:` | [top](#table-of-contents) |
+| [top](#activities) | :ping_pong: | `:ping_pong:` | :badminton: | `:badminton:` | [top](#table-of-contents) |
+| [top](#activities) | :boxing_glove: | `:boxing_glove:` | :martial_arts_uniform: | `:martial_arts_uniform:` | [top](#table-of-contents) |
+| [top](#activities) | :goal_net: | `:goal_net:` | :golf: | `:golf:` | [top](#table-of-contents) |
+| [top](#activities) | :ice_skate: | `:ice_skate:` | :fishing_pole_and_fish: | `:fishing_pole_and_fish:` | [top](#table-of-contents) |
+| [top](#activities) | :diving_mask: | `:diving_mask:` | :running_shirt_with_sash: | `:running_shirt_with_sash:` | [top](#table-of-contents) |
+| [top](#activities) | :ski: | `:ski:` | :sled: | `:sled:` | [top](#table-of-contents) |
+| [top](#activities) | :curling_stone: | `:curling_stone:` | | | [top](#table-of-contents) |
+
+### Game
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#activities) | :dart: | `:dart:` | :yo_yo: | `:yo_yo:` | [top](#table-of-contents) |
+| [top](#activities) | :kite: | `:kite:` | :gun: | `:gun:` | [top](#table-of-contents) |
+| [top](#activities) | :8ball: | `:8ball:` | :crystal_ball: | `:crystal_ball:` | [top](#table-of-contents) |
+| [top](#activities) | :magic_wand: | `:magic_wand:` | :video_game: | `:video_game:` | [top](#table-of-contents) |
+| [top](#activities) | :joystick: | `:joystick:` | :slot_machine: | `:slot_machine:` | [top](#table-of-contents) |
+| [top](#activities) | :game_die: | `:game_die:` | :jigsaw: | `:jigsaw:` | [top](#table-of-contents) |
+| [top](#activities) | :teddy_bear: | `:teddy_bear:` | :pinata: | `:pinata:` | [top](#table-of-contents) |
+| [top](#activities) | :mirror_ball: | `:mirror_ball:` | :nesting_dolls: | `:nesting_dolls:` | [top](#table-of-contents) |
+| [top](#activities) | :spades: | `:spades:` | :hearts: | `:hearts:` | [top](#table-of-contents) |
+| [top](#activities) | :diamonds: | `:diamonds:` | :clubs: | `:clubs:` | [top](#table-of-contents) |
+| [top](#activities) | :chess_pawn: | `:chess_pawn:` | :black_joker: | `:black_joker:` | [top](#table-of-contents) |
+| [top](#activities) | :mahjong: | `:mahjong:` | :flower_playing_cards: | `:flower_playing_cards:` | [top](#table-of-contents) |
+
+### Arts & Crafts
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#activities) | :performing_arts: | `:performing_arts:` | :framed_picture: | `:framed_picture:` | [top](#table-of-contents) |
+| [top](#activities) | :art: | `:art:` | :thread: | `:thread:` | [top](#table-of-contents) |
+| [top](#activities) | :sewing_needle: | `:sewing_needle:` | :yarn: | `:yarn:` | [top](#table-of-contents) |
+| [top](#activities) | :knot: | `:knot:` | | | [top](#table-of-contents) |
+
+## Objects
+
+- [Clothing](#clothing)
+- [Sound](#sound)
+- [Music](#music)
+- [Musical Instrument](#musical-instrument)
+- [Phone](#phone)
+- [Computer](#computer)
+- [Light & Video](#light--video)
+- [Book Paper](#book-paper)
+- [Money](#money)
+- [Mail](#mail)
+- [Writing](#writing)
+- [Office](#office)
+- [Lock](#lock)
+- [Tool](#tool)
+- [Science](#science)
+- [Medical](#medical)
+- [Household](#household)
+- [Other Object](#other-object)
+
+### Clothing
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#objects) | :eyeglasses: | `:eyeglasses:` | :dark_sunglasses: | `:dark_sunglasses:` | [top](#table-of-contents) |
+| [top](#objects) | :goggles: | `:goggles:` | :lab_coat: | `:lab_coat:` | [top](#table-of-contents) |
+| [top](#objects) | :safety_vest: | `:safety_vest:` | :necktie: | `:necktie:` | [top](#table-of-contents) |
+| [top](#objects) | :shirt: | `:shirt:` `:tshirt:` | :jeans: | `:jeans:` | [top](#table-of-contents) |
+| [top](#objects) | :scarf: | `:scarf:` | :gloves: | `:gloves:` | [top](#table-of-contents) |
+| [top](#objects) | :coat: | `:coat:` | :socks: | `:socks:` | [top](#table-of-contents) |
+| [top](#objects) | :dress: | `:dress:` | :kimono: | `:kimono:` | [top](#table-of-contents) |
+| [top](#objects) | :sari: | `:sari:` | :one_piece_swimsuit: | `:one_piece_swimsuit:` | [top](#table-of-contents) |
+| [top](#objects) | :swim_brief: | `:swim_brief:` | :shorts: | `:shorts:` | [top](#table-of-contents) |
+| [top](#objects) | :bikini: | `:bikini:` | :womans_clothes: | `:womans_clothes:` | [top](#table-of-contents) |
+| [top](#objects) | :folding_hand_fan: | `:folding_hand_fan:` | :purse: | `:purse:` | [top](#table-of-contents) |
+| [top](#objects) | :handbag: | `:handbag:` | :pouch: | `:pouch:` | [top](#table-of-contents) |
+| [top](#objects) | :shopping: | `:shopping:` | :school_satchel: | `:school_satchel:` | [top](#table-of-contents) |
+| [top](#objects) | :thong_sandal: | `:thong_sandal:` | :mans_shoe: | `:mans_shoe:` `:shoe:` | [top](#table-of-contents) |
+| [top](#objects) | :athletic_shoe: | `:athletic_shoe:` | :hiking_boot: | `:hiking_boot:` | [top](#table-of-contents) |
+| [top](#objects) | :flat_shoe: | `:flat_shoe:` | :high_heel: | `:high_heel:` | [top](#table-of-contents) |
+| [top](#objects) | :sandal: | `:sandal:` | :ballet_shoes: | `:ballet_shoes:` | [top](#table-of-contents) |
+| [top](#objects) | :boot: | `:boot:` | :hair_pick: | `:hair_pick:` | [top](#table-of-contents) |
+| [top](#objects) | :crown: | `:crown:` | :womans_hat: | `:womans_hat:` | [top](#table-of-contents) |
+| [top](#objects) | :tophat: | `:tophat:` | :mortar_board: | `:mortar_board:` | [top](#table-of-contents) |
+| [top](#objects) | :billed_cap: | `:billed_cap:` | :military_helmet: | `:military_helmet:` | [top](#table-of-contents) |
+| [top](#objects) | :rescue_worker_helmet: | `:rescue_worker_helmet:` | :prayer_beads: | `:prayer_beads:` | [top](#table-of-contents) |
+| [top](#objects) | :lipstick: | `:lipstick:` | :ring: | `:ring:` | [top](#table-of-contents) |
+| [top](#objects) | :gem: | `:gem:` | | | [top](#table-of-contents) |
+
+### Sound
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#objects) | :mute: | `:mute:` | :speaker: | `:speaker:` | [top](#table-of-contents) |
+| [top](#objects) | :sound: | `:sound:` | :loud_sound: | `:loud_sound:` | [top](#table-of-contents) |
+| [top](#objects) | :loudspeaker: | `:loudspeaker:` | :mega: | `:mega:` | [top](#table-of-contents) |
+| [top](#objects) | :postal_horn: | `:postal_horn:` | :bell: | `:bell:` | [top](#table-of-contents) |
+| [top](#objects) | :no_bell: | `:no_bell:` | | | [top](#table-of-contents) |
+
+### Music
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#objects) | :musical_score: | `:musical_score:` | :musical_note: | `:musical_note:` | [top](#table-of-contents) |
+| [top](#objects) | :notes: | `:notes:` | :studio_microphone: | `:studio_microphone:` | [top](#table-of-contents) |
+| [top](#objects) | :level_slider: | `:level_slider:` | :control_knobs: | `:control_knobs:` | [top](#table-of-contents) |
+| [top](#objects) | :microphone: | `:microphone:` | :headphones: | `:headphones:` | [top](#table-of-contents) |
+| [top](#objects) | :radio: | `:radio:` | | | [top](#table-of-contents) |
+
+### Musical Instrument
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#objects) | :saxophone: | `:saxophone:` | :accordion: | `:accordion:` | [top](#table-of-contents) |
+| [top](#objects) | :guitar: | `:guitar:` | :musical_keyboard: | `:musical_keyboard:` | [top](#table-of-contents) |
+| [top](#objects) | :trumpet: | `:trumpet:` | :violin: | `:violin:` | [top](#table-of-contents) |
+| [top](#objects) | :banjo: | `:banjo:` | :drum: | `:drum:` | [top](#table-of-contents) |
+| [top](#objects) | :long_drum: | `:long_drum:` | :maracas: | `:maracas:` | [top](#table-of-contents) |
+| [top](#objects) | :flute: | `:flute:` | | | [top](#table-of-contents) |
+
+### Phone
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#objects) | :iphone: | `:iphone:` | :calling: | `:calling:` | [top](#table-of-contents) |
+| [top](#objects) | :phone: | `:phone:` `:telephone:` | :telephone_receiver: | `:telephone_receiver:` | [top](#table-of-contents) |
+| [top](#objects) | :pager: | `:pager:` | :fax: | `:fax:` | [top](#table-of-contents) |
+
+### Computer
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#objects) | :battery: | `:battery:` | :low_battery: | `:low_battery:` | [top](#table-of-contents) |
+| [top](#objects) | :electric_plug: | `:electric_plug:` | :computer: | `:computer:` | [top](#table-of-contents) |
+| [top](#objects) | :desktop_computer: | `:desktop_computer:` | :printer: | `:printer:` | [top](#table-of-contents) |
+| [top](#objects) | :keyboard: | `:keyboard:` | :computer_mouse: | `:computer_mouse:` | [top](#table-of-contents) |
+| [top](#objects) | :trackball: | `:trackball:` | :minidisc: | `:minidisc:` | [top](#table-of-contents) |
+| [top](#objects) | :floppy_disk: | `:floppy_disk:` | :cd: | `:cd:` | [top](#table-of-contents) |
+| [top](#objects) | :dvd: | `:dvd:` | :abacus: | `:abacus:` | [top](#table-of-contents) |
+
+### Light & Video
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#objects) | :movie_camera: | `:movie_camera:` | :film_strip: | `:film_strip:` | [top](#table-of-contents) |
+| [top](#objects) | :film_projector: | `:film_projector:` | :clapper: | `:clapper:` | [top](#table-of-contents) |
+| [top](#objects) | :tv: | `:tv:` | :camera: | `:camera:` | [top](#table-of-contents) |
+| [top](#objects) | :camera_flash: | `:camera_flash:` | :video_camera: | `:video_camera:` | [top](#table-of-contents) |
+| [top](#objects) | :vhs: | `:vhs:` | :mag: | `:mag:` | [top](#table-of-contents) |
+| [top](#objects) | :mag_right: | `:mag_right:` | :candle: | `:candle:` | [top](#table-of-contents) |
+| [top](#objects) | :bulb: | `:bulb:` | :flashlight: | `:flashlight:` | [top](#table-of-contents) |
+| [top](#objects) | :izakaya_lantern: | `:izakaya_lantern:` `:lantern:` | :diya_lamp: | `:diya_lamp:` | [top](#table-of-contents) |
+
+### Book Paper
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#objects) | :notebook_with_decorative_cover: | `:notebook_with_decorative_cover:` | :closed_book: | `:closed_book:` | [top](#table-of-contents) |
+| [top](#objects) | :book: | `:book:` `:open_book:` | :green_book: | `:green_book:` | [top](#table-of-contents) |
+| [top](#objects) | :blue_book: | `:blue_book:` | :orange_book: | `:orange_book:` | [top](#table-of-contents) |
+| [top](#objects) | :books: | `:books:` | :notebook: | `:notebook:` | [top](#table-of-contents) |
+| [top](#objects) | :ledger: | `:ledger:` | :page_with_curl: | `:page_with_curl:` | [top](#table-of-contents) |
+| [top](#objects) | :scroll: | `:scroll:` | :page_facing_up: | `:page_facing_up:` | [top](#table-of-contents) |
+| [top](#objects) | :newspaper: | `:newspaper:` | :newspaper_roll: | `:newspaper_roll:` | [top](#table-of-contents) |
+| [top](#objects) | :bookmark_tabs: | `:bookmark_tabs:` | :bookmark: | `:bookmark:` | [top](#table-of-contents) |
+| [top](#objects) | :label: | `:label:` | | | [top](#table-of-contents) |
+
+### Money
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#objects) | :moneybag: | `:moneybag:` | :coin: | `:coin:` | [top](#table-of-contents) |
+| [top](#objects) | :yen: | `:yen:` | :dollar: | `:dollar:` | [top](#table-of-contents) |
+| [top](#objects) | :euro: | `:euro:` | :pound: | `:pound:` | [top](#table-of-contents) |
+| [top](#objects) | :money_with_wings: | `:money_with_wings:` | :credit_card: | `:credit_card:` | [top](#table-of-contents) |
+| [top](#objects) | :receipt: | `:receipt:` | :chart: | `:chart:` | [top](#table-of-contents) |
+
+### Mail
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#objects) | :envelope: | `:envelope:` | :e-mail: | `:e-mail:` `:email:` | [top](#table-of-contents) |
+| [top](#objects) | :incoming_envelope: | `:incoming_envelope:` | :envelope_with_arrow: | `:envelope_with_arrow:` | [top](#table-of-contents) |
+| [top](#objects) | :outbox_tray: | `:outbox_tray:` | :inbox_tray: | `:inbox_tray:` | [top](#table-of-contents) |
+| [top](#objects) | :package: | `:package:` | :mailbox: | `:mailbox:` | [top](#table-of-contents) |
+| [top](#objects) | :mailbox_closed: | `:mailbox_closed:` | :mailbox_with_mail: | `:mailbox_with_mail:` | [top](#table-of-contents) |
+| [top](#objects) | :mailbox_with_no_mail: | `:mailbox_with_no_mail:` | :postbox: | `:postbox:` | [top](#table-of-contents) |
+| [top](#objects) | :ballot_box: | `:ballot_box:` | | | [top](#table-of-contents) |
+
+### Writing
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#objects) | :pencil2: | `:pencil2:` | :black_nib: | `:black_nib:` | [top](#table-of-contents) |
+| [top](#objects) | :fountain_pen: | `:fountain_pen:` | :pen: | `:pen:` | [top](#table-of-contents) |
+| [top](#objects) | :paintbrush: | `:paintbrush:` | :crayon: | `:crayon:` | [top](#table-of-contents) |
+| [top](#objects) | :memo: | `:memo:` `:pencil:` | | | [top](#table-of-contents) |
+
+### Office
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#objects) | :briefcase: | `:briefcase:` | :file_folder: | `:file_folder:` | [top](#table-of-contents) |
+| [top](#objects) | :open_file_folder: | `:open_file_folder:` | :card_index_dividers: | `:card_index_dividers:` | [top](#table-of-contents) |
+| [top](#objects) | :date: | `:date:` | :calendar: | `:calendar:` | [top](#table-of-contents) |
+| [top](#objects) | :spiral_notepad: | `:spiral_notepad:` | :spiral_calendar: | `:spiral_calendar:` | [top](#table-of-contents) |
+| [top](#objects) | :card_index: | `:card_index:` | :chart_with_upwards_trend: | `:chart_with_upwards_trend:` | [top](#table-of-contents) |
+| [top](#objects) | :chart_with_downwards_trend: | `:chart_with_downwards_trend:` | :bar_chart: | `:bar_chart:` | [top](#table-of-contents) |
+| [top](#objects) | :clipboard: | `:clipboard:` | :pushpin: | `:pushpin:` | [top](#table-of-contents) |
+| [top](#objects) | :round_pushpin: | `:round_pushpin:` | :paperclip: | `:paperclip:` | [top](#table-of-contents) |
+| [top](#objects) | :paperclips: | `:paperclips:` | :straight_ruler: | `:straight_ruler:` | [top](#table-of-contents) |
+| [top](#objects) | :triangular_ruler: | `:triangular_ruler:` | :scissors: | `:scissors:` | [top](#table-of-contents) |
+| [top](#objects) | :card_file_box: | `:card_file_box:` | :file_cabinet: | `:file_cabinet:` | [top](#table-of-contents) |
+| [top](#objects) | :wastebasket: | `:wastebasket:` | | | [top](#table-of-contents) |
+
+### Lock
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#objects) | :lock: | `:lock:` | :unlock: | `:unlock:` | [top](#table-of-contents) |
+| [top](#objects) | :lock_with_ink_pen: | `:lock_with_ink_pen:` | :closed_lock_with_key: | `:closed_lock_with_key:` | [top](#table-of-contents) |
+| [top](#objects) | :key: | `:key:` | :old_key: | `:old_key:` | [top](#table-of-contents) |
+
+### Tool
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#objects) | :hammer: | `:hammer:` | :axe: | `:axe:` | [top](#table-of-contents) |
+| [top](#objects) | :pick: | `:pick:` | :hammer_and_pick: | `:hammer_and_pick:` | [top](#table-of-contents) |
+| [top](#objects) | :hammer_and_wrench: | `:hammer_and_wrench:` | :dagger: | `:dagger:` | [top](#table-of-contents) |
+| [top](#objects) | :crossed_swords: | `:crossed_swords:` | :bomb: | `:bomb:` | [top](#table-of-contents) |
+| [top](#objects) | :boomerang: | `:boomerang:` | :bow_and_arrow: | `:bow_and_arrow:` | [top](#table-of-contents) |
+| [top](#objects) | :shield: | `:shield:` | :carpentry_saw: | `:carpentry_saw:` | [top](#table-of-contents) |
+| [top](#objects) | :wrench: | `:wrench:` | :screwdriver: | `:screwdriver:` | [top](#table-of-contents) |
+| [top](#objects) | :nut_and_bolt: | `:nut_and_bolt:` | :gear: | `:gear:` | [top](#table-of-contents) |
+| [top](#objects) | :clamp: | `:clamp:` | :balance_scale: | `:balance_scale:` | [top](#table-of-contents) |
+| [top](#objects) | :probing_cane: | `:probing_cane:` | :link: | `:link:` | [top](#table-of-contents) |
+| [top](#objects) | :chains: | `:chains:` | :hook: | `:hook:` | [top](#table-of-contents) |
+| [top](#objects) | :toolbox: | `:toolbox:` | :magnet: | `:magnet:` | [top](#table-of-contents) |
+| [top](#objects) | :ladder: | `:ladder:` | | | [top](#table-of-contents) |
+
+### Science
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#objects) | :alembic: | `:alembic:` | :test_tube: | `:test_tube:` | [top](#table-of-contents) |
+| [top](#objects) | :petri_dish: | `:petri_dish:` | :dna: | `:dna:` | [top](#table-of-contents) |
+| [top](#objects) | :microscope: | `:microscope:` | :telescope: | `:telescope:` | [top](#table-of-contents) |
+| [top](#objects) | :satellite: | `:satellite:` | | | [top](#table-of-contents) |
+
+### Medical
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#objects) | :syringe: | `:syringe:` | :drop_of_blood: | `:drop_of_blood:` | [top](#table-of-contents) |
+| [top](#objects) | :pill: | `:pill:` | :adhesive_bandage: | `:adhesive_bandage:` | [top](#table-of-contents) |
+| [top](#objects) | :crutch: | `:crutch:` | :stethoscope: | `:stethoscope:` | [top](#table-of-contents) |
+| [top](#objects) | :x_ray: | `:x_ray:` | | | [top](#table-of-contents) |
+
+### Household
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#objects) | :door: | `:door:` | :elevator: | `:elevator:` | [top](#table-of-contents) |
+| [top](#objects) | :mirror: | `:mirror:` | :window: | `:window:` | [top](#table-of-contents) |
+| [top](#objects) | :bed: | `:bed:` | :couch_and_lamp: | `:couch_and_lamp:` | [top](#table-of-contents) |
+| [top](#objects) | :chair: | `:chair:` | :toilet: | `:toilet:` | [top](#table-of-contents) |
+| [top](#objects) | :plunger: | `:plunger:` | :shower: | `:shower:` | [top](#table-of-contents) |
+| [top](#objects) | :bathtub: | `:bathtub:` | :mouse_trap: | `:mouse_trap:` | [top](#table-of-contents) |
+| [top](#objects) | :razor: | `:razor:` | :lotion_bottle: | `:lotion_bottle:` | [top](#table-of-contents) |
+| [top](#objects) | :safety_pin: | `:safety_pin:` | :broom: | `:broom:` | [top](#table-of-contents) |
+| [top](#objects) | :basket: | `:basket:` | :roll_of_paper: | `:roll_of_paper:` | [top](#table-of-contents) |
+| [top](#objects) | :bucket: | `:bucket:` | :soap: | `:soap:` | [top](#table-of-contents) |
+| [top](#objects) | :bubbles: | `:bubbles:` | :toothbrush: | `:toothbrush:` | [top](#table-of-contents) |
+| [top](#objects) | :sponge: | `:sponge:` | :fire_extinguisher: | `:fire_extinguisher:` | [top](#table-of-contents) |
+| [top](#objects) | :shopping_cart: | `:shopping_cart:` | | | [top](#table-of-contents) |
+
+### Other Object
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#objects) | :smoking: | `:smoking:` | :coffin: | `:coffin:` | [top](#table-of-contents) |
+| [top](#objects) | :headstone: | `:headstone:` | :funeral_urn: | `:funeral_urn:` | [top](#table-of-contents) |
+| [top](#objects) | :nazar_amulet: | `:nazar_amulet:` | :hamsa: | `:hamsa:` | [top](#table-of-contents) |
+| [top](#objects) | :moyai: | `:moyai:` | :placard: | `:placard:` | [top](#table-of-contents) |
+| [top](#objects) | :identification_card: | `:identification_card:` | | | [top](#table-of-contents) |
+
+## Symbols
+
+- [Transport Sign](#transport-sign)
+- [Warning](#warning)
+- [Arrow](#arrow)
+- [Religion](#religion)
+- [Zodiac](#zodiac)
+- [Av Symbol](#av-symbol)
+- [Gender](#gender)
+- [Math](#math)
+- [Punctuation](#punctuation)
+- [Currency](#currency)
+- [Other Symbol](#other-symbol)
+- [Keycap](#keycap)
+- [Alphanum](#alphanum)
+- [Geometric](#geometric)
+
+### Transport Sign
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#symbols) | :atm: | `:atm:` | :put_litter_in_its_place: | `:put_litter_in_its_place:` | [top](#table-of-contents) |
+| [top](#symbols) | :potable_water: | `:potable_water:` | :wheelchair: | `:wheelchair:` | [top](#table-of-contents) |
+| [top](#symbols) | :mens: | `:mens:` | :womens: | `:womens:` | [top](#table-of-contents) |
+| [top](#symbols) | :restroom: | `:restroom:` | :baby_symbol: | `:baby_symbol:` | [top](#table-of-contents) |
+| [top](#symbols) | :wc: | `:wc:` | :passport_control: | `:passport_control:` | [top](#table-of-contents) |
+| [top](#symbols) | :customs: | `:customs:` | :baggage_claim: | `:baggage_claim:` | [top](#table-of-contents) |
+| [top](#symbols) | :left_luggage: | `:left_luggage:` | | | [top](#table-of-contents) |
+
+### Warning
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#symbols) | :warning: | `:warning:` | :children_crossing: | `:children_crossing:` | [top](#table-of-contents) |
+| [top](#symbols) | :no_entry: | `:no_entry:` | :no_entry_sign: | `:no_entry_sign:` | [top](#table-of-contents) |
+| [top](#symbols) | :no_bicycles: | `:no_bicycles:` | :no_smoking: | `:no_smoking:` | [top](#table-of-contents) |
+| [top](#symbols) | :do_not_litter: | `:do_not_litter:` | :non-potable_water: | `:non-potable_water:` | [top](#table-of-contents) |
+| [top](#symbols) | :no_pedestrians: | `:no_pedestrians:` | :no_mobile_phones: | `:no_mobile_phones:` | [top](#table-of-contents) |
+| [top](#symbols) | :underage: | `:underage:` | :radioactive: | `:radioactive:` | [top](#table-of-contents) |
+| [top](#symbols) | :biohazard: | `:biohazard:` | | | [top](#table-of-contents) |
+
+### Arrow
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#symbols) | :arrow_up: | `:arrow_up:` | :arrow_upper_right: | `:arrow_upper_right:` | [top](#table-of-contents) |
+| [top](#symbols) | :arrow_right: | `:arrow_right:` | :arrow_lower_right: | `:arrow_lower_right:` | [top](#table-of-contents) |
+| [top](#symbols) | :arrow_down: | `:arrow_down:` | :arrow_lower_left: | `:arrow_lower_left:` | [top](#table-of-contents) |
+| [top](#symbols) | :arrow_left: | `:arrow_left:` | :arrow_upper_left: | `:arrow_upper_left:` | [top](#table-of-contents) |
+| [top](#symbols) | :arrow_up_down: | `:arrow_up_down:` | :left_right_arrow: | `:left_right_arrow:` | [top](#table-of-contents) |
+| [top](#symbols) | :leftwards_arrow_with_hook: | `:leftwards_arrow_with_hook:` | :arrow_right_hook: | `:arrow_right_hook:` | [top](#table-of-contents) |
+| [top](#symbols) | :arrow_heading_up: | `:arrow_heading_up:` | :arrow_heading_down: | `:arrow_heading_down:` | [top](#table-of-contents) |
+| [top](#symbols) | :arrows_clockwise: | `:arrows_clockwise:` | :arrows_counterclockwise: | `:arrows_counterclockwise:` | [top](#table-of-contents) |
+| [top](#symbols) | :back: | `:back:` | :end: | `:end:` | [top](#table-of-contents) |
+| [top](#symbols) | :on: | `:on:` | :soon: | `:soon:` | [top](#table-of-contents) |
+| [top](#symbols) | :top: | `:top:` | | | [top](#table-of-contents) |
+
+### Religion
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#symbols) | :place_of_worship: | `:place_of_worship:` | :atom_symbol: | `:atom_symbol:` | [top](#table-of-contents) |
+| [top](#symbols) | :om: | `:om:` | :star_of_david: | `:star_of_david:` | [top](#table-of-contents) |
+| [top](#symbols) | :wheel_of_dharma: | `:wheel_of_dharma:` | :yin_yang: | `:yin_yang:` | [top](#table-of-contents) |
+| [top](#symbols) | :latin_cross: | `:latin_cross:` | :orthodox_cross: | `:orthodox_cross:` | [top](#table-of-contents) |
+| [top](#symbols) | :star_and_crescent: | `:star_and_crescent:` | :peace_symbol: | `:peace_symbol:` | [top](#table-of-contents) |
+| [top](#symbols) | :menorah: | `:menorah:` | :six_pointed_star: | `:six_pointed_star:` | [top](#table-of-contents) |
+| [top](#symbols) | :khanda: | `:khanda:` | | | [top](#table-of-contents) |
+
+### Zodiac
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#symbols) | :aries: | `:aries:` | :taurus: | `:taurus:` | [top](#table-of-contents) |
+| [top](#symbols) | :gemini: | `:gemini:` | :cancer: | `:cancer:` | [top](#table-of-contents) |
+| [top](#symbols) | :leo: | `:leo:` | :virgo: | `:virgo:` | [top](#table-of-contents) |
+| [top](#symbols) | :libra: | `:libra:` | :scorpius: | `:scorpius:` | [top](#table-of-contents) |
+| [top](#symbols) | :sagittarius: | `:sagittarius:` | :capricorn: | `:capricorn:` | [top](#table-of-contents) |
+| [top](#symbols) | :aquarius: | `:aquarius:` | :pisces: | `:pisces:` | [top](#table-of-contents) |
+| [top](#symbols) | :ophiuchus: | `:ophiuchus:` | | | [top](#table-of-contents) |
+
+### Av Symbol
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#symbols) | :twisted_rightwards_arrows: | `:twisted_rightwards_arrows:` | :repeat: | `:repeat:` | [top](#table-of-contents) |
+| [top](#symbols) | :repeat_one: | `:repeat_one:` | :arrow_forward: | `:arrow_forward:` | [top](#table-of-contents) |
+| [top](#symbols) | :fast_forward: | `:fast_forward:` | :next_track_button: | `:next_track_button:` | [top](#table-of-contents) |
+| [top](#symbols) | :play_or_pause_button: | `:play_or_pause_button:` | :arrow_backward: | `:arrow_backward:` | [top](#table-of-contents) |
+| [top](#symbols) | :rewind: | `:rewind:` | :previous_track_button: | `:previous_track_button:` | [top](#table-of-contents) |
+| [top](#symbols) | :arrow_up_small: | `:arrow_up_small:` | :arrow_double_up: | `:arrow_double_up:` | [top](#table-of-contents) |
+| [top](#symbols) | :arrow_down_small: | `:arrow_down_small:` | :arrow_double_down: | `:arrow_double_down:` | [top](#table-of-contents) |
+| [top](#symbols) | :pause_button: | `:pause_button:` | :stop_button: | `:stop_button:` | [top](#table-of-contents) |
+| [top](#symbols) | :record_button: | `:record_button:` | :eject_button: | `:eject_button:` | [top](#table-of-contents) |
+| [top](#symbols) | :cinema: | `:cinema:` | :low_brightness: | `:low_brightness:` | [top](#table-of-contents) |
+| [top](#symbols) | :high_brightness: | `:high_brightness:` | :signal_strength: | `:signal_strength:` | [top](#table-of-contents) |
+| [top](#symbols) | :wireless: | `:wireless:` | :vibration_mode: | `:vibration_mode:` | [top](#table-of-contents) |
+| [top](#symbols) | :mobile_phone_off: | `:mobile_phone_off:` | | | [top](#table-of-contents) |
+
+### Gender
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#symbols) | :female_sign: | `:female_sign:` | :male_sign: | `:male_sign:` | [top](#table-of-contents) |
+| [top](#symbols) | :transgender_symbol: | `:transgender_symbol:` | | | [top](#table-of-contents) |
+
+### Math
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#symbols) | :heavy_multiplication_x: | `:heavy_multiplication_x:` | :heavy_plus_sign: | `:heavy_plus_sign:` | [top](#table-of-contents) |
+| [top](#symbols) | :heavy_minus_sign: | `:heavy_minus_sign:` | :heavy_division_sign: | `:heavy_division_sign:` | [top](#table-of-contents) |
+| [top](#symbols) | :heavy_equals_sign: | `:heavy_equals_sign:` | :infinity: | `:infinity:` | [top](#table-of-contents) |
+
+### Punctuation
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#symbols) | :bangbang: | `:bangbang:` | :interrobang: | `:interrobang:` | [top](#table-of-contents) |
+| [top](#symbols) | :question: | `:question:` | :grey_question: | `:grey_question:` | [top](#table-of-contents) |
+| [top](#symbols) | :grey_exclamation: | `:grey_exclamation:` | :exclamation: | `:exclamation:` `:heavy_exclamation_mark:` | [top](#table-of-contents) |
+| [top](#symbols) | :wavy_dash: | `:wavy_dash:` | | | [top](#table-of-contents) |
+
+### Currency
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#symbols) | :currency_exchange: | `:currency_exchange:` | :heavy_dollar_sign: | `:heavy_dollar_sign:` | [top](#table-of-contents) |
+
+### Other Symbol
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#symbols) | :medical_symbol: | `:medical_symbol:` | :recycle: | `:recycle:` | [top](#table-of-contents) |
+| [top](#symbols) | :fleur_de_lis: | `:fleur_de_lis:` | :trident: | `:trident:` | [top](#table-of-contents) |
+| [top](#symbols) | :name_badge: | `:name_badge:` | :beginner: | `:beginner:` | [top](#table-of-contents) |
+| [top](#symbols) | :o: | `:o:` | :white_check_mark: | `:white_check_mark:` | [top](#table-of-contents) |
+| [top](#symbols) | :ballot_box_with_check: | `:ballot_box_with_check:` | :heavy_check_mark: | `:heavy_check_mark:` | [top](#table-of-contents) |
+| [top](#symbols) | :x: | `:x:` | :negative_squared_cross_mark: | `:negative_squared_cross_mark:` | [top](#table-of-contents) |
+| [top](#symbols) | :curly_loop: | `:curly_loop:` | :loop: | `:loop:` | [top](#table-of-contents) |
+| [top](#symbols) | :part_alternation_mark: | `:part_alternation_mark:` | :eight_spoked_asterisk: | `:eight_spoked_asterisk:` | [top](#table-of-contents) |
+| [top](#symbols) | :eight_pointed_black_star: | `:eight_pointed_black_star:` | :sparkle: | `:sparkle:` | [top](#table-of-contents) |
+| [top](#symbols) | :copyright: | `:copyright:` | :registered: | `:registered:` | [top](#table-of-contents) |
+| [top](#symbols) | :tm: | `:tm:` | | | [top](#table-of-contents) |
+
+### Keycap
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#symbols) | :hash: | `:hash:` | :asterisk: | `:asterisk:` | [top](#table-of-contents) |
+| [top](#symbols) | :zero: | `:zero:` | :one: | `:one:` | [top](#table-of-contents) |
+| [top](#symbols) | :two: | `:two:` | :three: | `:three:` | [top](#table-of-contents) |
+| [top](#symbols) | :four: | `:four:` | :five: | `:five:` | [top](#table-of-contents) |
+| [top](#symbols) | :six: | `:six:` | :seven: | `:seven:` | [top](#table-of-contents) |
+| [top](#symbols) | :eight: | `:eight:` | :nine: | `:nine:` | [top](#table-of-contents) |
+| [top](#symbols) | :keycap_ten: | `:keycap_ten:` | | | [top](#table-of-contents) |
+
+### Alphanum
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#symbols) | :capital_abcd: | `:capital_abcd:` | :abcd: | `:abcd:` | [top](#table-of-contents) |
+| [top](#symbols) | :1234: | `:1234:` | :symbols: | `:symbols:` | [top](#table-of-contents) |
+| [top](#symbols) | :abc: | `:abc:` | :a: | `:a:` | [top](#table-of-contents) |
+| [top](#symbols) | :ab: | `:ab:` | :b: | `:b:` | [top](#table-of-contents) |
+| [top](#symbols) | :cl: | `:cl:` | :cool: | `:cool:` | [top](#table-of-contents) |
+| [top](#symbols) | :free: | `:free:` | :information_source: | `:information_source:` | [top](#table-of-contents) |
+| [top](#symbols) | :id: | `:id:` | :m: | `:m:` | [top](#table-of-contents) |
+| [top](#symbols) | :new: | `:new:` | :ng: | `:ng:` | [top](#table-of-contents) |
+| [top](#symbols) | :o2: | `:o2:` | :ok: | `:ok:` | [top](#table-of-contents) |
+| [top](#symbols) | :parking: | `:parking:` | :sos: | `:sos:` | [top](#table-of-contents) |
+| [top](#symbols) | :up: | `:up:` | :vs: | `:vs:` | [top](#table-of-contents) |
+| [top](#symbols) | :koko: | `:koko:` | :sa: | `:sa:` | [top](#table-of-contents) |
+| [top](#symbols) | :u6708: | `:u6708:` | :u6709: | `:u6709:` | [top](#table-of-contents) |
+| [top](#symbols) | :u6307: | `:u6307:` | :ideograph_advantage: | `:ideograph_advantage:` | [top](#table-of-contents) |
+| [top](#symbols) | :u5272: | `:u5272:` | :u7121: | `:u7121:` | [top](#table-of-contents) |
+| [top](#symbols) | :u7981: | `:u7981:` | :accept: | `:accept:` | [top](#table-of-contents) |
+| [top](#symbols) | :u7533: | `:u7533:` | :u5408: | `:u5408:` | [top](#table-of-contents) |
+| [top](#symbols) | :u7a7a: | `:u7a7a:` | :congratulations: | `:congratulations:` | [top](#table-of-contents) |
+| [top](#symbols) | :secret: | `:secret:` | :u55b6: | `:u55b6:` | [top](#table-of-contents) |
+| [top](#symbols) | :u6e80: | `:u6e80:` | | | [top](#table-of-contents) |
+
+### Geometric
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#symbols) | :red_circle: | `:red_circle:` | :orange_circle: | `:orange_circle:` | [top](#table-of-contents) |
+| [top](#symbols) | :yellow_circle: | `:yellow_circle:` | :green_circle: | `:green_circle:` | [top](#table-of-contents) |
+| [top](#symbols) | :large_blue_circle: | `:large_blue_circle:` | :purple_circle: | `:purple_circle:` | [top](#table-of-contents) |
+| [top](#symbols) | :brown_circle: | `:brown_circle:` | :black_circle: | `:black_circle:` | [top](#table-of-contents) |
+| [top](#symbols) | :white_circle: | `:white_circle:` | :red_square: | `:red_square:` | [top](#table-of-contents) |
+| [top](#symbols) | :orange_square: | `:orange_square:` | :yellow_square: | `:yellow_square:` | [top](#table-of-contents) |
+| [top](#symbols) | :green_square: | `:green_square:` | :blue_square: | `:blue_square:` | [top](#table-of-contents) |
+| [top](#symbols) | :purple_square: | `:purple_square:` | :brown_square: | `:brown_square:` | [top](#table-of-contents) |
+| [top](#symbols) | :black_large_square: | `:black_large_square:` | :white_large_square: | `:white_large_square:` | [top](#table-of-contents) |
+| [top](#symbols) | :black_medium_square: | `:black_medium_square:` | :white_medium_square: | `:white_medium_square:` | [top](#table-of-contents) |
+| [top](#symbols) | :black_medium_small_square: | `:black_medium_small_square:` | :white_medium_small_square: | `:white_medium_small_square:` | [top](#table-of-contents) |
+| [top](#symbols) | :black_small_square: | `:black_small_square:` | :white_small_square: | `:white_small_square:` | [top](#table-of-contents) |
+| [top](#symbols) | :large_orange_diamond: | `:large_orange_diamond:` | :large_blue_diamond: | `:large_blue_diamond:` | [top](#table-of-contents) |
+| [top](#symbols) | :small_orange_diamond: | `:small_orange_diamond:` | :small_blue_diamond: | `:small_blue_diamond:` | [top](#table-of-contents) |
+| [top](#symbols) | :small_red_triangle: | `:small_red_triangle:` | :small_red_triangle_down: | `:small_red_triangle_down:` | [top](#table-of-contents) |
+| [top](#symbols) | :diamond_shape_with_a_dot_inside: | `:diamond_shape_with_a_dot_inside:` | :radio_button: | `:radio_button:` | [top](#table-of-contents) |
+| [top](#symbols) | :white_square_button: | `:white_square_button:` | :black_square_button: | `:black_square_button:` | [top](#table-of-contents) |
+
+## Flags
+
+- [Flag](#flag)
+- [Country Flag](#country-flag)
+- [Subdivision Flag](#subdivision-flag)
+
+### Flag
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#flags) | :checkered_flag: | `:checkered_flag:` | :triangular_flag_on_post: | `:triangular_flag_on_post:` | [top](#table-of-contents) |
+| [top](#flags) | :crossed_flags: | `:crossed_flags:` | :black_flag: | `:black_flag:` | [top](#table-of-contents) |
+| [top](#flags) | :white_flag: | `:white_flag:` | :rainbow_flag: | `:rainbow_flag:` | [top](#table-of-contents) |
+| [top](#flags) | :transgender_flag: | `:transgender_flag:` | :pirate_flag: | `:pirate_flag:` | [top](#table-of-contents) |
+
+### Country Flag
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#flags) | :ascension_island: | `:ascension_island:` | :andorra: | `:andorra:` | [top](#table-of-contents) |
+| [top](#flags) | :united_arab_emirates: | `:united_arab_emirates:` | :afghanistan: | `:afghanistan:` | [top](#table-of-contents) |
+| [top](#flags) | :antigua_barbuda: | `:antigua_barbuda:` | :anguilla: | `:anguilla:` | [top](#table-of-contents) |
+| [top](#flags) | :albania: | `:albania:` | :armenia: | `:armenia:` | [top](#table-of-contents) |
+| [top](#flags) | :angola: | `:angola:` | :antarctica: | `:antarctica:` | [top](#table-of-contents) |
+| [top](#flags) | :argentina: | `:argentina:` | :american_samoa: | `:american_samoa:` | [top](#table-of-contents) |
+| [top](#flags) | :austria: | `:austria:` | :australia: | `:australia:` | [top](#table-of-contents) |
+| [top](#flags) | :aruba: | `:aruba:` | :aland_islands: | `:aland_islands:` | [top](#table-of-contents) |
+| [top](#flags) | :azerbaijan: | `:azerbaijan:` | :bosnia_herzegovina: | `:bosnia_herzegovina:` | [top](#table-of-contents) |
+| [top](#flags) | :barbados: | `:barbados:` | :bangladesh: | `:bangladesh:` | [top](#table-of-contents) |
+| [top](#flags) | :belgium: | `:belgium:` | :burkina_faso: | `:burkina_faso:` | [top](#table-of-contents) |
+| [top](#flags) | :bulgaria: | `:bulgaria:` | :bahrain: | `:bahrain:` | [top](#table-of-contents) |
+| [top](#flags) | :burundi: | `:burundi:` | :benin: | `:benin:` | [top](#table-of-contents) |
+| [top](#flags) | :st_barthelemy: | `:st_barthelemy:` | :bermuda: | `:bermuda:` | [top](#table-of-contents) |
+| [top](#flags) | :brunei: | `:brunei:` | :bolivia: | `:bolivia:` | [top](#table-of-contents) |
+| [top](#flags) | :caribbean_netherlands: | `:caribbean_netherlands:` | :brazil: | `:brazil:` | [top](#table-of-contents) |
+| [top](#flags) | :bahamas: | `:bahamas:` | :bhutan: | `:bhutan:` | [top](#table-of-contents) |
+| [top](#flags) | :bouvet_island: | `:bouvet_island:` | :botswana: | `:botswana:` | [top](#table-of-contents) |
+| [top](#flags) | :belarus: | `:belarus:` | :belize: | `:belize:` | [top](#table-of-contents) |
+| [top](#flags) | :canada: | `:canada:` | :cocos_islands: | `:cocos_islands:` | [top](#table-of-contents) |
+| [top](#flags) | :congo_kinshasa: | `:congo_kinshasa:` | :central_african_republic: | `:central_african_republic:` | [top](#table-of-contents) |
+| [top](#flags) | :congo_brazzaville: | `:congo_brazzaville:` | :switzerland: | `:switzerland:` | [top](#table-of-contents) |
+| [top](#flags) | :cote_divoire: | `:cote_divoire:` | :cook_islands: | `:cook_islands:` | [top](#table-of-contents) |
+| [top](#flags) | :chile: | `:chile:` | :cameroon: | `:cameroon:` | [top](#table-of-contents) |
+| [top](#flags) | :cn: | `:cn:` | :colombia: | `:colombia:` | [top](#table-of-contents) |
+| [top](#flags) | :clipperton_island: | `:clipperton_island:` | :costa_rica: | `:costa_rica:` | [top](#table-of-contents) |
+| [top](#flags) | :cuba: | `:cuba:` | :cape_verde: | `:cape_verde:` | [top](#table-of-contents) |
+| [top](#flags) | :curacao: | `:curacao:` | :christmas_island: | `:christmas_island:` | [top](#table-of-contents) |
+| [top](#flags) | :cyprus: | `:cyprus:` | :czech_republic: | `:czech_republic:` | [top](#table-of-contents) |
+| [top](#flags) | :de: | `:de:` | :diego_garcia: | `:diego_garcia:` | [top](#table-of-contents) |
+| [top](#flags) | :djibouti: | `:djibouti:` | :denmark: | `:denmark:` | [top](#table-of-contents) |
+| [top](#flags) | :dominica: | `:dominica:` | :dominican_republic: | `:dominican_republic:` | [top](#table-of-contents) |
+| [top](#flags) | :algeria: | `:algeria:` | :ceuta_melilla: | `:ceuta_melilla:` | [top](#table-of-contents) |
+| [top](#flags) | :ecuador: | `:ecuador:` | :estonia: | `:estonia:` | [top](#table-of-contents) |
+| [top](#flags) | :egypt: | `:egypt:` | :western_sahara: | `:western_sahara:` | [top](#table-of-contents) |
+| [top](#flags) | :eritrea: | `:eritrea:` | :es: | `:es:` | [top](#table-of-contents) |
+| [top](#flags) | :ethiopia: | `:ethiopia:` | :eu: | `:eu:` `:european_union:` | [top](#table-of-contents) |
+| [top](#flags) | :finland: | `:finland:` | :fiji: | `:fiji:` | [top](#table-of-contents) |
+| [top](#flags) | :falkland_islands: | `:falkland_islands:` | :micronesia: | `:micronesia:` | [top](#table-of-contents) |
+| [top](#flags) | :faroe_islands: | `:faroe_islands:` | :fr: | `:fr:` | [top](#table-of-contents) |
+| [top](#flags) | :gabon: | `:gabon:` | :gb: | `:gb:` `:uk:` | [top](#table-of-contents) |
+| [top](#flags) | :grenada: | `:grenada:` | :georgia: | `:georgia:` | [top](#table-of-contents) |
+| [top](#flags) | :french_guiana: | `:french_guiana:` | :guernsey: | `:guernsey:` | [top](#table-of-contents) |
+| [top](#flags) | :ghana: | `:ghana:` | :gibraltar: | `:gibraltar:` | [top](#table-of-contents) |
+| [top](#flags) | :greenland: | `:greenland:` | :gambia: | `:gambia:` | [top](#table-of-contents) |
+| [top](#flags) | :guinea: | `:guinea:` | :guadeloupe: | `:guadeloupe:` | [top](#table-of-contents) |
+| [top](#flags) | :equatorial_guinea: | `:equatorial_guinea:` | :greece: | `:greece:` | [top](#table-of-contents) |
+| [top](#flags) | :south_georgia_south_sandwich_islands: | `:south_georgia_south_sandwich_islands:` | :guatemala: | `:guatemala:` | [top](#table-of-contents) |
+| [top](#flags) | :guam: | `:guam:` | :guinea_bissau: | `:guinea_bissau:` | [top](#table-of-contents) |
+| [top](#flags) | :guyana: | `:guyana:` | :hong_kong: | `:hong_kong:` | [top](#table-of-contents) |
+| [top](#flags) | :heard_mcdonald_islands: | `:heard_mcdonald_islands:` | :honduras: | `:honduras:` | [top](#table-of-contents) |
+| [top](#flags) | :croatia: | `:croatia:` | :haiti: | `:haiti:` | [top](#table-of-contents) |
+| [top](#flags) | :hungary: | `:hungary:` | :canary_islands: | `:canary_islands:` | [top](#table-of-contents) |
+| [top](#flags) | :indonesia: | `:indonesia:` | :ireland: | `:ireland:` | [top](#table-of-contents) |
+| [top](#flags) | :israel: | `:israel:` | :isle_of_man: | `:isle_of_man:` | [top](#table-of-contents) |
+| [top](#flags) | :india: | `:india:` | :british_indian_ocean_territory: | `:british_indian_ocean_territory:` | [top](#table-of-contents) |
+| [top](#flags) | :iraq: | `:iraq:` | :iran: | `:iran:` | [top](#table-of-contents) |
+| [top](#flags) | :iceland: | `:iceland:` | :it: | `:it:` | [top](#table-of-contents) |
+| [top](#flags) | :jersey: | `:jersey:` | :jamaica: | `:jamaica:` | [top](#table-of-contents) |
+| [top](#flags) | :jordan: | `:jordan:` | :jp: | `:jp:` | [top](#table-of-contents) |
+| [top](#flags) | :kenya: | `:kenya:` | :kyrgyzstan: | `:kyrgyzstan:` | [top](#table-of-contents) |
+| [top](#flags) | :cambodia: | `:cambodia:` | :kiribati: | `:kiribati:` | [top](#table-of-contents) |
+| [top](#flags) | :comoros: | `:comoros:` | :st_kitts_nevis: | `:st_kitts_nevis:` | [top](#table-of-contents) |
+| [top](#flags) | :north_korea: | `:north_korea:` | :kr: | `:kr:` | [top](#table-of-contents) |
+| [top](#flags) | :kuwait: | `:kuwait:` | :cayman_islands: | `:cayman_islands:` | [top](#table-of-contents) |
+| [top](#flags) | :kazakhstan: | `:kazakhstan:` | :laos: | `:laos:` | [top](#table-of-contents) |
+| [top](#flags) | :lebanon: | `:lebanon:` | :st_lucia: | `:st_lucia:` | [top](#table-of-contents) |
+| [top](#flags) | :liechtenstein: | `:liechtenstein:` | :sri_lanka: | `:sri_lanka:` | [top](#table-of-contents) |
+| [top](#flags) | :liberia: | `:liberia:` | :lesotho: | `:lesotho:` | [top](#table-of-contents) |
+| [top](#flags) | :lithuania: | `:lithuania:` | :luxembourg: | `:luxembourg:` | [top](#table-of-contents) |
+| [top](#flags) | :latvia: | `:latvia:` | :libya: | `:libya:` | [top](#table-of-contents) |
+| [top](#flags) | :morocco: | `:morocco:` | :monaco: | `:monaco:` | [top](#table-of-contents) |
+| [top](#flags) | :moldova: | `:moldova:` | :montenegro: | `:montenegro:` | [top](#table-of-contents) |
+| [top](#flags) | :st_martin: | `:st_martin:` | :madagascar: | `:madagascar:` | [top](#table-of-contents) |
+| [top](#flags) | :marshall_islands: | `:marshall_islands:` | :macedonia: | `:macedonia:` | [top](#table-of-contents) |
+| [top](#flags) | :mali: | `:mali:` | :myanmar: | `:myanmar:` | [top](#table-of-contents) |
+| [top](#flags) | :mongolia: | `:mongolia:` | :macau: | `:macau:` | [top](#table-of-contents) |
+| [top](#flags) | :northern_mariana_islands: | `:northern_mariana_islands:` | :martinique: | `:martinique:` | [top](#table-of-contents) |
+| [top](#flags) | :mauritania: | `:mauritania:` | :montserrat: | `:montserrat:` | [top](#table-of-contents) |
+| [top](#flags) | :malta: | `:malta:` | :mauritius: | `:mauritius:` | [top](#table-of-contents) |
+| [top](#flags) | :maldives: | `:maldives:` | :malawi: | `:malawi:` | [top](#table-of-contents) |
+| [top](#flags) | :mexico: | `:mexico:` | :malaysia: | `:malaysia:` | [top](#table-of-contents) |
+| [top](#flags) | :mozambique: | `:mozambique:` | :namibia: | `:namibia:` | [top](#table-of-contents) |
+| [top](#flags) | :new_caledonia: | `:new_caledonia:` | :niger: | `:niger:` | [top](#table-of-contents) |
+| [top](#flags) | :norfolk_island: | `:norfolk_island:` | :nigeria: | `:nigeria:` | [top](#table-of-contents) |
+| [top](#flags) | :nicaragua: | `:nicaragua:` | :netherlands: | `:netherlands:` | [top](#table-of-contents) |
+| [top](#flags) | :norway: | `:norway:` | :nepal: | `:nepal:` | [top](#table-of-contents) |
+| [top](#flags) | :nauru: | `:nauru:` | :niue: | `:niue:` | [top](#table-of-contents) |
+| [top](#flags) | :new_zealand: | `:new_zealand:` | :oman: | `:oman:` | [top](#table-of-contents) |
+| [top](#flags) | :panama: | `:panama:` | :peru: | `:peru:` | [top](#table-of-contents) |
+| [top](#flags) | :french_polynesia: | `:french_polynesia:` | :papua_new_guinea: | `:papua_new_guinea:` | [top](#table-of-contents) |
+| [top](#flags) | :philippines: | `:philippines:` | :pakistan: | `:pakistan:` | [top](#table-of-contents) |
+| [top](#flags) | :poland: | `:poland:` | :st_pierre_miquelon: | `:st_pierre_miquelon:` | [top](#table-of-contents) |
+| [top](#flags) | :pitcairn_islands: | `:pitcairn_islands:` | :puerto_rico: | `:puerto_rico:` | [top](#table-of-contents) |
+| [top](#flags) | :palestinian_territories: | `:palestinian_territories:` | :portugal: | `:portugal:` | [top](#table-of-contents) |
+| [top](#flags) | :palau: | `:palau:` | :paraguay: | `:paraguay:` | [top](#table-of-contents) |
+| [top](#flags) | :qatar: | `:qatar:` | :reunion: | `:reunion:` | [top](#table-of-contents) |
+| [top](#flags) | :romania: | `:romania:` | :serbia: | `:serbia:` | [top](#table-of-contents) |
+| [top](#flags) | :ru: | `:ru:` | :rwanda: | `:rwanda:` | [top](#table-of-contents) |
+| [top](#flags) | :saudi_arabia: | `:saudi_arabia:` | :solomon_islands: | `:solomon_islands:` | [top](#table-of-contents) |
+| [top](#flags) | :seychelles: | `:seychelles:` | :sudan: | `:sudan:` | [top](#table-of-contents) |
+| [top](#flags) | :sweden: | `:sweden:` | :singapore: | `:singapore:` | [top](#table-of-contents) |
+| [top](#flags) | :st_helena: | `:st_helena:` | :slovenia: | `:slovenia:` | [top](#table-of-contents) |
+| [top](#flags) | :svalbard_jan_mayen: | `:svalbard_jan_mayen:` | :slovakia: | `:slovakia:` | [top](#table-of-contents) |
+| [top](#flags) | :sierra_leone: | `:sierra_leone:` | :san_marino: | `:san_marino:` | [top](#table-of-contents) |
+| [top](#flags) | :senegal: | `:senegal:` | :somalia: | `:somalia:` | [top](#table-of-contents) |
+| [top](#flags) | :suriname: | `:suriname:` | :south_sudan: | `:south_sudan:` | [top](#table-of-contents) |
+| [top](#flags) | :sao_tome_principe: | `:sao_tome_principe:` | :el_salvador: | `:el_salvador:` | [top](#table-of-contents) |
+| [top](#flags) | :sint_maarten: | `:sint_maarten:` | :syria: | `:syria:` | [top](#table-of-contents) |
+| [top](#flags) | :swaziland: | `:swaziland:` | :tristan_da_cunha: | `:tristan_da_cunha:` | [top](#table-of-contents) |
+| [top](#flags) | :turks_caicos_islands: | `:turks_caicos_islands:` | :chad: | `:chad:` | [top](#table-of-contents) |
+| [top](#flags) | :french_southern_territories: | `:french_southern_territories:` | :togo: | `:togo:` | [top](#table-of-contents) |
+| [top](#flags) | :thailand: | `:thailand:` | :tajikistan: | `:tajikistan:` | [top](#table-of-contents) |
+| [top](#flags) | :tokelau: | `:tokelau:` | :timor_leste: | `:timor_leste:` | [top](#table-of-contents) |
+| [top](#flags) | :turkmenistan: | `:turkmenistan:` | :tunisia: | `:tunisia:` | [top](#table-of-contents) |
+| [top](#flags) | :tonga: | `:tonga:` | :tr: | `:tr:` | [top](#table-of-contents) |
+| [top](#flags) | :trinidad_tobago: | `:trinidad_tobago:` | :tuvalu: | `:tuvalu:` | [top](#table-of-contents) |
+| [top](#flags) | :taiwan: | `:taiwan:` | :tanzania: | `:tanzania:` | [top](#table-of-contents) |
+| [top](#flags) | :ukraine: | `:ukraine:` | :uganda: | `:uganda:` | [top](#table-of-contents) |
+| [top](#flags) | :us_outlying_islands: | `:us_outlying_islands:` | :united_nations: | `:united_nations:` | [top](#table-of-contents) |
+| [top](#flags) | :us: | `:us:` | :uruguay: | `:uruguay:` | [top](#table-of-contents) |
+| [top](#flags) | :uzbekistan: | `:uzbekistan:` | :vatican_city: | `:vatican_city:` | [top](#table-of-contents) |
+| [top](#flags) | :st_vincent_grenadines: | `:st_vincent_grenadines:` | :venezuela: | `:venezuela:` | [top](#table-of-contents) |
+| [top](#flags) | :british_virgin_islands: | `:british_virgin_islands:` | :us_virgin_islands: | `:us_virgin_islands:` | [top](#table-of-contents) |
+| [top](#flags) | :vietnam: | `:vietnam:` | :vanuatu: | `:vanuatu:` | [top](#table-of-contents) |
+| [top](#flags) | :wallis_futuna: | `:wallis_futuna:` | :samoa: | `:samoa:` | [top](#table-of-contents) |
+| [top](#flags) | :kosovo: | `:kosovo:` | :yemen: | `:yemen:` | [top](#table-of-contents) |
+| [top](#flags) | :mayotte: | `:mayotte:` | :south_africa: | `:south_africa:` | [top](#table-of-contents) |
+| [top](#flags) | :zambia: | `:zambia:` | :zimbabwe: | `:zimbabwe:` | [top](#table-of-contents) |
+
+### Subdivision Flag
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#flags) | :england: | `:england:` | :scotland: | `:scotland:` | [top](#table-of-contents) |
+| [top](#flags) | :wales: | `:wales:` | | | [top](#table-of-contents) |
+
+## GitHub Custom Emoji
+
+| | ico | shortcode | ico | shortcode | |
+| - | :-: | - | :-: | - | - |
+| [top](#github-custom-emoji) | :accessibility: | `:accessibility:` | :atom: | `:atom:` | [top](#table-of-contents) |
+| [top](#github-custom-emoji) | :basecamp: | `:basecamp:` | :basecampy: | `:basecampy:` | [top](#table-of-contents) |
+| [top](#github-custom-emoji) | :bowtie: | `:bowtie:` | :dependabot: | `:dependabot:` | [top](#table-of-contents) |
+| [top](#github-custom-emoji) | :electron: | `:electron:` | :feelsgood: | `:feelsgood:` | [top](#table-of-contents) |
+| [top](#github-custom-emoji) | :finnadie: | `:finnadie:` | :fishsticks: | `:fishsticks:` | [top](#table-of-contents) |
+| [top](#github-custom-emoji) | :goberserk: | `:goberserk:` | :godmode: | `:godmode:` | [top](#table-of-contents) |
+| [top](#github-custom-emoji) | :hurtrealbad: | `:hurtrealbad:` | :neckbeard: | `:neckbeard:` | [top](#table-of-contents) |
+| [top](#github-custom-emoji) | :octocat: | `:octocat:` | :rage1: | `:rage1:` | [top](#table-of-contents) |
+| [top](#github-custom-emoji) | :rage2: | `:rage2:` | :rage3: | `:rage3:` | [top](#table-of-contents) |
+| [top](#github-custom-emoji) | :rage4: | `:rage4:` | :shipit: | `:shipit:` | [top](#table-of-contents) |
+| [top](#github-custom-emoji) | :suspect: | `:suspect:` | :trollface: | `:trollface:` | [top](#table-of-contents) |
diff --git a/docs/content/en/quick-reference/functions.md b/docs/content/en/quick-reference/functions.md
new file mode 100644
index 00000000000..72235d0f348
--- /dev/null
+++ b/docs/content/en/quick-reference/functions.md
@@ -0,0 +1,8 @@
+---
+title: Functions
+description: A quick reference guide to Hugo's functions, grouped by namespace. Aliases, if any, appear in parentheses to the right of the function name.
+categories: []
+keywords: []
+---
+
+{{% quick-reference section="functions" %}}
diff --git a/docs/content/en/quick-reference/glob-patterns.md b/docs/content/en/quick-reference/glob-patterns.md
new file mode 100644
index 00000000000..13cd01f54be
--- /dev/null
+++ b/docs/content/en/quick-reference/glob-patterns.md
@@ -0,0 +1,38 @@
+---
+title: Glob patterns
+description: A quick reference guide to glob pattern syntax and matching rules for wildcards, character sets, and delimiters, featuring illustrative examples.
+categories: []
+keywords: []
+---
+
+{{% glossary-term "glob pattern" %}}
+
+The table below details the supported glob pattern syntax and its matching behavior. Each example illustrates a specific match type, the pattern used, and the expected boolean result when evaluated against a test string.
+
+| Match type | Glob pattern | Test string | Match? |
+| :--- | :--- | :--- | :--- |
+| Simple wildcard | `a/*.md` | `a/page.md` | true |
+| Literal match | `'a/*.md'` | `a/*.md` | true |
+| Single-level wildcard | `a/*/page.md` | `a/b/page.md` | true |
+| Single-level wildcard | `a/*/page.md` | `a/b/c/page.md` | false |
+| Multi-level wildcard | `a/**/page.md` | `a/b/c/page.md` | true |
+| Single character | `file.???` | `file.txt` | true |
+| Single character | `file.???` | `file.js` | false |
+| Delimiter exclusion | `?at` | `f/at` | false |
+| Character list | `f.[jt]xt` | `f.txt` | true |
+| Negated list | `f.[!j]xt` | `f.txt` | true |
+| Character range | `f.[a-c].txt` | `f.b.txt` | true |
+| Character range | `f.[a-c].txt` | `f.z.txt` | false |
+| Negated range | `f.[!a-c].txt` | `f.z.txt` | true |
+| Pattern alternates | `*.{jpg,png}` | `logo.png` | true |
+| No match | `*.{jpg,png}` | `logo.webp` | false |
+
+The matching logic follows these rules:
+
+- Standard wildcard (`*`) matches any character except for a delimiter.
+- Super wildcard (`**`) matches any character including delimiters.
+- Single character (`?`) matches exactly one character, excluding delimiters.
+- Negation (`!`) matches any character except those specified in a list or range when used inside brackets.
+- Character ranges (`[a-z]`) match any single character within the specified range.
+
+The delimiter is a slash (`/`), except when matching semantic version strings, where the delimiter is a dot (`.`).
diff --git a/docs/content/en/quick-reference/glossary/_index.md b/docs/content/en/quick-reference/glossary/_index.md
new file mode 100644
index 00000000000..8940fd73b3d
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/_index.md
@@ -0,0 +1,20 @@
+---
+title: Glossary
+description: Terms commonly used throughout the documentation.
+categories: []
+keywords: []
+build:
+ render: always
+ list: always
+cascade:
+ build:
+ render: never
+ list: local
+layout: single
+params:
+ hide_in_this_section: true
+ searchable: true
+aliases: [/getting-started/glossary/]
+---
+
+{{% glossary %}}
diff --git a/docs/content/en/quick-reference/glossary/action.md b/docs/content/en/quick-reference/glossary/action.md
new file mode 100644
index 00000000000..ced87732797
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/action.md
@@ -0,0 +1,5 @@
+---
+title: action
+---
+
+See [_template action_](g).
diff --git a/docs/content/en/quick-reference/glossary/archetype.md b/docs/content/en/quick-reference/glossary/archetype.md
new file mode 100644
index 00000000000..d22fae9549c
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/archetype.md
@@ -0,0 +1,7 @@
+---
+title: archetype
+params:
+ reference: /content-management/archetypes/
+---
+
+An _archetype_ is a template for new content.
diff --git a/docs/content/en/quick-reference/glossary/argument.md b/docs/content/en/quick-reference/glossary/argument.md
new file mode 100644
index 00000000000..912951d2b2d
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/argument.md
@@ -0,0 +1,5 @@
+---
+title: argument
+---
+
+An _argument_ is a [_scalar_](g), [_array_](g), [_slice_](g), [_map_](g), or [_object_](g) passed to a [_function_](g), [_method_](g), or [_shortcode_](g).
diff --git a/docs/content/en/quick-reference/glossary/array.md b/docs/content/en/quick-reference/glossary/array.md
new file mode 100644
index 00000000000..0df45f2121a
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/array.md
@@ -0,0 +1,6 @@
+---
+title: array
+reference: https://go.dev/ref/spec#Array_types
+---
+
+An _array_ is a numbered sequence of [_elements_](g). Unlike Go's [_slice_](g) data type, an array has a fixed length. Elements within an array can be [_scalars_](g), slices, [_maps_](g), pages, or other arrays.
diff --git a/docs/content/en/quick-reference/glossary/asset-pipeline.md b/docs/content/en/quick-reference/glossary/asset-pipeline.md
new file mode 100644
index 00000000000..5f3264a6ed5
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/asset-pipeline.md
@@ -0,0 +1,5 @@
+---
+title: asset pipeline
+---
+
+An _asset pipeline_ is a system that automates and optimizes the handling of static assets like images, stylesheets, and JavaScript files.
diff --git a/docs/content/en/quick-reference/glossary/bool.md b/docs/content/en/quick-reference/glossary/bool.md
new file mode 100644
index 00000000000..a4f33b5b99b
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/bool.md
@@ -0,0 +1,5 @@
+---
+title: bool
+---
+
+See [_boolean_](g).
diff --git a/docs/content/en/quick-reference/glossary/boolean.md b/docs/content/en/quick-reference/glossary/boolean.md
new file mode 100644
index 00000000000..e727c40b0a3
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/boolean.md
@@ -0,0 +1,5 @@
+---
+title: boolean
+---
+
+A _boolean_ is a data type with two possible values, either `true` or `false`.
diff --git a/docs/content/en/quick-reference/glossary/branch-bundle.md b/docs/content/en/quick-reference/glossary/branch-bundle.md
new file mode 100644
index 00000000000..d5688ba0b31
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/branch-bundle.md
@@ -0,0 +1,6 @@
+---
+title: branch bundle
+reference: /content-management/page-bundles
+---
+
+A _branch bundle_ is a top-level content directory or any content directory containing an `_index.md` file. Analogous to a physical branch, a branch bundle may have descendants including [_leaf bundles_](g) and other branch bundles. A branch bundle may also contain [_page resources_](g) such as images.
diff --git a/docs/content/en/quick-reference/glossary/build-artifacts.md b/docs/content/en/quick-reference/glossary/build-artifacts.md
new file mode 100644
index 00000000000..c3c2dbd6a9e
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/build-artifacts.md
@@ -0,0 +1,5 @@
+---
+title: build artifacts
+---
+
+The _build artifacts_ are the static files produced during the [_build_](g) process. These assets are stored in the `public` directory by default and represent the final, ready-to-deploy output of the project.
diff --git a/docs/content/en/quick-reference/glossary/build.md b/docs/content/en/quick-reference/glossary/build.md
new file mode 100644
index 00000000000..4d4f73cb4bc
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/build.md
@@ -0,0 +1,5 @@
+---
+title: build
+---
+
+To _build_ (verb) is to generate the static files for a [_project_](g), including HTML, images, CSS, and JavaScript. This process involves rendering templates, transforming resources, and resolving the matrix of [_language_](g), [_role_](g), and [_version_](g) defined in your project configuration.
diff --git a/docs/content/en/quick-reference/glossary/bundle.md b/docs/content/en/quick-reference/glossary/bundle.md
new file mode 100644
index 00000000000..fddc28ef85f
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/bundle.md
@@ -0,0 +1,5 @@
+---
+title: bundle
+---
+
+See [_page bundle_](g).
diff --git a/docs/content/en/quick-reference/glossary/cache.md b/docs/content/en/quick-reference/glossary/cache.md
new file mode 100644
index 00000000000..a86068e4ad5
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/cache.md
@@ -0,0 +1,5 @@
+---
+title: cache
+---
+
+A _cache_ is a software component that stores data so that future requests for the same data are faster.
diff --git a/docs/content/en/quick-reference/glossary/canonical-output-format.md b/docs/content/en/quick-reference/glossary/canonical-output-format.md
new file mode 100644
index 00000000000..d360083ba92
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/canonical-output-format.md
@@ -0,0 +1,14 @@
+---
+title: canonical output format
+---
+
+The _canonical output format_ is the [_output format_](g) for the current page where the format's [`rel`][] property is set to `canonical` in your project configuration, if such a format exists. If there is only one _output format_ for the current page, that is the _canonical output format_, regardless of whether the format's `rel` property is set to `canonical`.
+
+ By default, `html` is the only predefined _output format_ with this setting; the `rel` property for all others is set to `alternate`. If two or more _output formats_ for the current page have their `rel` property set to `canonical`, the _canonical output format_ is the first one specified in:
+
+ - The [`outputs`][outputs_front_matter] front matter field of the current page, or
+ - The [`outputs`][outputs_project_config] section of your project configuration for the current [_page kind_](g).
+
+ [`rel`]: /configuration/output-formats/#rel
+ [outputs_front_matter]: /configuration/outputs/#outputs-per-page
+ [outputs_project_config]: /configuration/outputs/#outputs-per-page-kind
diff --git a/docs/content/en/quick-reference/glossary/chain.md b/docs/content/en/quick-reference/glossary/chain.md
new file mode 100644
index 00000000000..c5d19b1b26b
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/chain.md
@@ -0,0 +1,5 @@
+---
+title: chain
+---
+
+To _chain_ (verb) is to connect one or more [_identifiers_](g) with a dot. An identifier can represent a [_method_](g), [_object_](g), or [_field_](g). For example, `.Site.Params.author.name` or `.Date.UTC.Hour`.
diff --git a/docs/content/en/quick-reference/glossary/cicd.md b/docs/content/en/quick-reference/glossary/cicd.md
new file mode 100644
index 00000000000..594a60e1994
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/cicd.md
@@ -0,0 +1,16 @@
+---
+title: CI/CD
+params:
+ reference: https://en.wikipedia.org/wiki/CI/CD
+---
+
+The term _CI/CD_ is an abbreviation for Continuous Integration and Continuous Delivery or Continuous Deployment depending on context.
+
+ Popular _CI/CD_ platforms for building and deploying Hugo sites include [Cloudflare][], [GitHub Pages][], [GitLab Pages][], [Netlify][], [Render][], and [Vercel][].
+
+ [Cloudflare]: /host-and-deploy/host-on-cloudflare/
+ [GitHub Pages]: /host-and-deploy/host-on-github-pages/
+ [GitLab Pages]: /host-and-deploy/host-on-gitlab-pages/
+ [Netlify]: /host-and-deploy/host-on-netlify/
+ [Render]: /host-and-deploy/host-on-render/
+ [Vercel]: /host-and-deploy/host-on-vercel/
diff --git a/docs/content/en/quick-reference/glossary/cjk.md b/docs/content/en/quick-reference/glossary/cjk.md
new file mode 100644
index 00000000000..05a294d4461
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/cjk.md
@@ -0,0 +1,5 @@
+---
+title: CJK
+---
+
+_CJK_ is a collective term for the Chinese, Japanese, and Korean languages.
diff --git a/docs/content/en/quick-reference/glossary/cli.md b/docs/content/en/quick-reference/glossary/cli.md
new file mode 100644
index 00000000000..8f898e364ed
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/cli.md
@@ -0,0 +1,5 @@
+---
+title: CLI
+---
+
+_CLI_ stands for command-line interface, a text-based method for interacting with computer programs or operating systems.
diff --git a/docs/content/en/quick-reference/glossary/collection.md b/docs/content/en/quick-reference/glossary/collection.md
new file mode 100644
index 00000000000..30e1ef80574
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/collection.md
@@ -0,0 +1,5 @@
+---
+title: collection
+---
+
+A _collection_ is an [_array_](g), [_slice_](g), or [_map_](g).
diff --git a/docs/content/en/quick-reference/glossary/component.md b/docs/content/en/quick-reference/glossary/component.md
new file mode 100644
index 00000000000..1843b1838f1
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/component.md
@@ -0,0 +1,16 @@
+---
+title: component
+---
+
+A _component_ is a collection of related files, housed within the [_unified file system_](g), that fulfills a specific function in building a Hugo [_project_](g). These components are categorized into seven types: [_archetypes_](g), assets, content, data, templates, [_translation tables_](g), and static files, and can be defined within the project or provided by [_modules_](g). Each component has a dedicated directory within the unified file system:
+
+ Component|Directory within the unified file system
+ :--|:--
+ archetypes|`archetypes`
+ assets|`assets`
+ content|`content`
+ data|`data`
+ templates|`layouts`
+ translation tables|`i18n`
+ static files|`static`
+ {class="!mt-0"}
diff --git a/docs/content/en/quick-reference/glossary/content-adapter.md b/docs/content/en/quick-reference/glossary/content-adapter.md
new file mode 100644
index 00000000000..974e61dca5e
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/content-adapter.md
@@ -0,0 +1,6 @@
+---
+title: content adapter
+reference: /content-management/content-adapters
+---
+
+A _content adapter_ is a template that dynamically creates pages when building a site. For example, use a content adapter to create pages from a remote data source such as JSON, TOML, YAML, or XML.
diff --git a/docs/content/en/quick-reference/glossary/content-dimension.md b/docs/content/en/quick-reference/glossary/content-dimension.md
new file mode 100644
index 00000000000..7317740e08d
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/content-dimension.md
@@ -0,0 +1,5 @@
+---
+title: content dimension
+---
+
+See [_dimension_](g).
diff --git a/docs/content/en/quick-reference/glossary/content-format.md b/docs/content/en/quick-reference/glossary/content-format.md
new file mode 100644
index 00000000000..ea459deb77d
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/content-format.md
@@ -0,0 +1,6 @@
+---
+title: content format
+reference: /content-management/formats
+---
+
+A _content format_ is a markup language for creating content. Typically Markdown, but may also be HTML, AsciiDoc, Org, Pandoc, or reStructuredText.
diff --git a/docs/content/en/quick-reference/glossary/content-type.md b/docs/content/en/quick-reference/glossary/content-type.md
new file mode 100644
index 00000000000..758800ccf8b
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/content-type.md
@@ -0,0 +1,5 @@
+---
+title: content type
+---
+
+A _content type_ is a classification of content inferred from the top-level directory name or the `type` set in [_front matter_](g). Pages in the root of the `content` directory, including the home page, are of type "page". The content type is a contributing factor in the template lookup order and determines which [_archetype_](g) template to use when creating new content.
diff --git a/docs/content/en/quick-reference/glossary/content-view.md b/docs/content/en/quick-reference/glossary/content-view.md
new file mode 100644
index 00000000000..443a07afd0a
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/content-view.md
@@ -0,0 +1,6 @@
+---
+title: content view
+reference: /templates/types/#content-view
+---
+
+A _content view_ is a template called with the [`Render`](/methods/page/render/) method on a `Page` object.
diff --git a/docs/content/en/quick-reference/glossary/context.md b/docs/content/en/quick-reference/glossary/context.md
new file mode 100644
index 00000000000..75afc709d88
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/context.md
@@ -0,0 +1,6 @@
+---
+title: context
+reference: /templates/introduction/#context
+---
+
+Represented by a dot (`.`) within a [_template action_](g), _context_ is the current location in a data structure. For example, while iterating over a [_collection_](g) of pages, the context within each iteration is the page's data structure. The context received by each template depends on template type and/or how it was called.
diff --git a/docs/content/en/quick-reference/glossary/default-language.md b/docs/content/en/quick-reference/glossary/default-language.md
new file mode 100644
index 00000000000..73f942c2f71
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/default-language.md
@@ -0,0 +1,9 @@
+---
+title: default language
+---
+
+The _default language_ is is the value defined by the [`defaultContentLanguage`][] setting, falling back to the first language in the project, and finally to `en`. The first language is identified by the lowest [_weight_](g), using lexicographical order as the final fallback if weights are tied or undefined.
+
+ See also: [_language_](g).
+
+ [`defaultContentLanguage`]: /configuration/all/#defaultcontentlanguage
diff --git a/docs/content/en/quick-reference/glossary/default-role.md b/docs/content/en/quick-reference/glossary/default-role.md
new file mode 100644
index 00000000000..f065189c1b6
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/default-role.md
@@ -0,0 +1,9 @@
+---
+title: default role
+---
+
+The _default role_ is the value defined by the [`defaultContentRole`][] setting, falling back to the first role in the project, and finally to `guest`. The first role is identified by the lowest [_weight_](g), using lexicographical order as the final fallback if weights are tied or undefined.
+
+ See also: [_role_](g).
+
+ [`defaultContentRole`]: /configuration/all/#defaultcontentrole
diff --git a/docs/content/en/quick-reference/glossary/default-site.md b/docs/content/en/quick-reference/glossary/default-site.md
new file mode 100644
index 00000000000..3acad31d078
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/default-site.md
@@ -0,0 +1,5 @@
+---
+title: default site
+---
+
+The _default site_ is the [_site_](g) with the [_default language_](g), [_default version_](g), and [_default role_](g).
diff --git a/docs/content/en/quick-reference/glossary/default-sort-order.md b/docs/content/en/quick-reference/glossary/default-sort-order.md
new file mode 100644
index 00000000000..9b981a7e9ff
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/default-sort-order.md
@@ -0,0 +1,10 @@
+---
+title: default sort order
+---
+
+The _default sort order_ for [_page collections_](g), used when no other criteria are set, follows this priority:
+
+ 1. [`weight`](/content-management/front-matter/#weight) (ascending)
+ 1. [`date`](/content-management/front-matter/#date) (descending)
+ 1. [`linkTitle`](/content-management/front-matter/#linktitle) falling back to [`title`](/content-management/front-matter/#title) (ascending)
+ 1. [logical path](g) (ascending)
diff --git a/docs/content/en/quick-reference/glossary/default-version.md b/docs/content/en/quick-reference/glossary/default-version.md
new file mode 100644
index 00000000000..dc290472555
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/default-version.md
@@ -0,0 +1,9 @@
+---
+title: default version
+---
+
+The _default version_ is the value defined by the [`defaultContentVersion`][] setting, falling back to the first version in the project, and finally to `v1.0.0`. The first version is identified by the lowest [_weight_](g), using a descending semantic sort as the final fallback if weights are tied or undefined.
+
+ See also: [_version_](g).
+
+ [`defaultContentVersion`]: /configuration/all/#defaultcontentversion
diff --git a/docs/content/en/quick-reference/glossary/dependency-graph.md b/docs/content/en/quick-reference/glossary/dependency-graph.md
new file mode 100644
index 00000000000..946e5b19705
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/dependency-graph.md
@@ -0,0 +1,5 @@
+---
+title: dependency graph
+---
+
+A _dependency graph_ visually represents the relationships between the [_modules_](g) used in a Hugo project. It shows how modules depend on each other, forming a network of dependencies.
diff --git a/docs/content/en/quick-reference/glossary/dimension.md b/docs/content/en/quick-reference/glossary/dimension.md
new file mode 100644
index 00000000000..9db6d64b804
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/dimension.md
@@ -0,0 +1,5 @@
+---
+title: dimension
+---
+
+A _dimension_ is a categorized axis of content variation that allows multiple variations of a logical page to exist simultaneously. The three dimensions are [_language_](g), [_role_](g), and [_version_](g). For example, a logical page may exist in 6 languages, 4 versions, and 2 roles.
diff --git a/docs/content/en/quick-reference/glossary/duration.md b/docs/content/en/quick-reference/glossary/duration.md
new file mode 100644
index 00000000000..21fd3c83222
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/duration.md
@@ -0,0 +1,5 @@
+---
+title: duration
+---
+
+A _duration_ is a data type that represent a length of time, expressed using units such as seconds (represented by `s`), minutes (represented by `m`), and hours (represented by `h`). For example, `42s` means 42 seconds, `6m7s` means 6 minutes and 7 seconds, and `6h7m42s` means 6 hours, 7 minutes, and 42 seconds.
diff --git a/docs/content/en/quick-reference/glossary/element.md b/docs/content/en/quick-reference/glossary/element.md
new file mode 100644
index 00000000000..39f5df65645
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/element.md
@@ -0,0 +1,5 @@
+---
+title: element
+---
+
+An _element_ is a member of a [_slice_](g) or [_array_](g).
diff --git a/docs/content/en/quick-reference/glossary/embedded-template.md b/docs/content/en/quick-reference/glossary/embedded-template.md
new file mode 100644
index 00000000000..3a087169022
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/embedded-template.md
@@ -0,0 +1,5 @@
+---
+title: embedded template
+---
+
+An _embedded template_ is a built-in component within the Hugo application. This includes features like [_partials_](g), [_shortcodes_](g), and [_render hooks_](g) that provide pre-defined structures or functionalities for creating website content.
diff --git a/docs/content/en/quick-reference/glossary/environment.md b/docs/content/en/quick-reference/glossary/environment.md
new file mode 100644
index 00000000000..0cfba95f73f
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/environment.md
@@ -0,0 +1,9 @@
+---
+title: environment
+---
+
+Typically one of `development`, `staging`, or `production`, each _environment_ may exhibit different behavior depending on configuration and template logic. For example, in a production environment you might minify and fingerprint CSS, but that probably doesn't make sense in a development environment.
+
+ When running the built-in development server with the `hugo server` command, the environment is set to `development`. When building your project with the `hugo build` command, the environment is set to `production`. To override the environment value, use the `--environment` command line flag or the `HUGO_ENVIRONMENT` environment variable.
+
+ To determine the current environment within a template, use the [`hugo.Environment`](/functions/hugo/environment/) function.
diff --git a/docs/content/en/quick-reference/glossary/field.md b/docs/content/en/quick-reference/glossary/field.md
new file mode 100644
index 00000000000..a32eb3a6bff
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/field.md
@@ -0,0 +1,5 @@
+---
+title: field
+---
+
+A _field_ is a predefined key-value pair in front matter such as `date` or `title`.
diff --git a/docs/content/en/quick-reference/glossary/flag.md b/docs/content/en/quick-reference/glossary/flag.md
new file mode 100644
index 00000000000..e7b6c5746f8
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/flag.md
@@ -0,0 +1,6 @@
+---
+title: flag
+reference: /commands/hugo
+---
+
+A _flag_ is an option passed to a command-line program, beginning with one or two hyphens.
diff --git a/docs/content/en/quick-reference/glossary/float.md b/docs/content/en/quick-reference/glossary/float.md
new file mode 100644
index 00000000000..86f2c8ffb03
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/float.md
@@ -0,0 +1,6 @@
+---
+title: float
+alias: true
+---
+
+See [floating point](g).
diff --git a/docs/content/en/quick-reference/glossary/floating-point.md b/docs/content/en/quick-reference/glossary/floating-point.md
new file mode 100644
index 00000000000..38ba9f012b8
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/floating-point.md
@@ -0,0 +1,5 @@
+---
+title: floating point
+---
+
+The term _floating point_ refers to a numeric data type with a fractional component. For example, `3.14159`.
diff --git a/docs/content/en/quick-reference/glossary/fragment.md b/docs/content/en/quick-reference/glossary/fragment.md
new file mode 100644
index 00000000000..57ef1b4ef4d
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/fragment.md
@@ -0,0 +1,5 @@
+---
+title: fragment
+---
+
+A _fragment_ is the final segment of a URL, beginning with a hash (`#`) mark, that references an `id` attribute of an HTML element on the page.
diff --git a/docs/content/en/quick-reference/glossary/front-matter.md b/docs/content/en/quick-reference/glossary/front-matter.md
new file mode 100644
index 00000000000..5a3cd30409d
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/front-matter.md
@@ -0,0 +1,6 @@
+---
+title: front matter
+reference: /content-management/front-matter
+---
+
+The term _front matter_ refers to the metadata at the beginning of each content page, separated from the content by format-specific delimiters.
diff --git a/docs/content/en/quick-reference/glossary/function.md b/docs/content/en/quick-reference/glossary/function.md
new file mode 100644
index 00000000000..a7da52cd289
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/function.md
@@ -0,0 +1,6 @@
+---
+title: function
+reference: /functions
+---
+
+Used within a [_template action_](g), a _function_ takes one or more [_arguments_](g) and returns a value. Unlike [_methods_](g), functions are not associated with an [_object_](g).
diff --git a/docs/content/en/quick-reference/glossary/glob-pattern.md b/docs/content/en/quick-reference/glossary/glob-pattern.md
new file mode 100644
index 00000000000..c57b452c147
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/glob-pattern.md
@@ -0,0 +1,6 @@
+---
+title: glob pattern
+reference: /quick-reference/glob-patterns/
+---
+
+A _glob pattern_ is a pattern used to match sets of values. It is a shorthand for specifying multiple targets at once, making it easier to work with groups of data or configurations.
diff --git a/docs/content/en/quick-reference/glossary/glob-slice.md b/docs/content/en/quick-reference/glossary/glob-slice.md
new file mode 100644
index 00000000000..df37718b1ee
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/glob-slice.md
@@ -0,0 +1,16 @@
+---
+title: glob slice
+---
+
+A _glob slice_ is a [_slice_](g) of [_glob patterns_](g). Within the _slice_, a _glob_ can be negated by prefixing it with an exclamation mark (`!`) and one space. Matches in negated patterns short-circuit the evaluation of the rest of the _slice_, and are useful for early coarse grained exclusions.
+
+ The following example illustrates how to use _glob slices_ to define a [_sites matrix_](g) in your project configuration:
+
+ ```toml
+ [sites.matrix]
+ languages = [ "! no", "**" ]
+ versions = [ "! v1.2.3", "v1.*.*", "v2.*.*" ]
+ roles = [ "{member, guest}" ]
+ ```
+
+ The `versions` example above evaluates as: `(not v1.2.3) AND (v1.*.* OR v2.*.*)`.
diff --git a/docs/content/en/quick-reference/glossary/global-resource.md b/docs/content/en/quick-reference/glossary/global-resource.md
new file mode 100644
index 00000000000..a4df65f6726
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/global-resource.md
@@ -0,0 +1,5 @@
+---
+title: global resource
+---
+
+A _global resource_ is file within the `assets` directory, or within any directory mounted to the `assets` directory.
diff --git a/docs/content/en/quick-reference/glossary/headless-bundle.md b/docs/content/en/quick-reference/glossary/headless-bundle.md
new file mode 100644
index 00000000000..ac7bf79c861
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/headless-bundle.md
@@ -0,0 +1,6 @@
+---
+title: headless bundle
+reference: /content-management/build-options/
+---
+
+A _headless bundle_ is an unpublished [_leaf bundle_](g) or an unpublished [_branch bundle_](g) whose content and resources you can include in other pages.
diff --git a/docs/content/en/quick-reference/glossary/i18n.md b/docs/content/en/quick-reference/glossary/i18n.md
new file mode 100644
index 00000000000..168828aa8ed
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/i18n.md
@@ -0,0 +1,5 @@
+---
+title: i18n
+---
+
+See [_internationalization_](g).
diff --git a/docs/content/en/quick-reference/glossary/iana.md b/docs/content/en/quick-reference/glossary/iana.md
new file mode 100644
index 00000000000..89497f76aae
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/iana.md
@@ -0,0 +1,6 @@
+---
+title: IANA
+reference: https://www.iana.org/about
+---
+
+_IANA_ is an abbreviation for the Internet Assigned Numbers Authority, a non-profit organization that manages the allocation of global IP addresses, autonomous system numbers, DNS root zone, media types, and other Internet Protocol-related resources.
diff --git a/docs/content/en/quick-reference/glossary/identifier.md b/docs/content/en/quick-reference/glossary/identifier.md
new file mode 100644
index 00000000000..f53472fa79a
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/identifier.md
@@ -0,0 +1,5 @@
+---
+title: identifier
+---
+
+An _identifier_ is a string that represents a variable, method, object, or field. It must conform to Go's [language specification](https://go.dev/ref/spec#Identifiers), beginning with a letter or underscore, followed by zero or more letters, digits, or underscores.
diff --git a/docs/content/en/quick-reference/glossary/int.md b/docs/content/en/quick-reference/glossary/int.md
new file mode 100644
index 00000000000..0896f1ce73f
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/int.md
@@ -0,0 +1,5 @@
+---
+title: int
+---
+
+See [_integer_](g).
diff --git a/docs/content/en/quick-reference/glossary/integer.md b/docs/content/en/quick-reference/glossary/integer.md
new file mode 100644
index 00000000000..0af821300da
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/integer.md
@@ -0,0 +1,5 @@
+---
+title: integer
+---
+
+An _integer_ is a numeric data type without a fractional component. For example, `42`.
diff --git a/docs/content/en/quick-reference/glossary/interleave.md b/docs/content/en/quick-reference/glossary/interleave.md
new file mode 100644
index 00000000000..077ff7e2c29
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/interleave.md
@@ -0,0 +1,5 @@
+---
+title: interleave
+---
+
+To _interleave_ (verb) is to insert a string at the beginning, the end, and between every character of another string.
diff --git a/docs/content/en/quick-reference/glossary/internationalization.md b/docs/content/en/quick-reference/glossary/internationalization.md
new file mode 100644
index 00000000000..4e8b0199cf2
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/internationalization.md
@@ -0,0 +1,5 @@
+---
+title: internationalization
+---
+
+The term _internationalization_ refers to software design and development efforts that enable [_localization_](g).
diff --git a/docs/content/en/quick-reference/glossary/interpreted-string-literal.md b/docs/content/en/quick-reference/glossary/interpreted-string-literal.md
new file mode 100644
index 00000000000..fa92c976fde
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/interpreted-string-literal.md
@@ -0,0 +1,6 @@
+---
+title: interpreted string literal
+reference: https://go.dev/ref/spec#String_literals
+---
+
+An _interpreted string literal_ is a character sequence between double quotes, as in `"foo"`. Within the quotes, any character may appear except a newline and an unescaped double quote. The text between the quotes forms the value of the literal, with backslash escapes interpreted.
diff --git a/docs/content/en/quick-reference/glossary/interval.md b/docs/content/en/quick-reference/glossary/interval.md
new file mode 100644
index 00000000000..a7ed8a6cdd9
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/interval.md
@@ -0,0 +1,11 @@
+---
+title: interval
+---
+
+An [_interval_](https://en.wikipedia.org/wiki/Interval_(mathematics)) is a range of numbers between two endpoints: closed, open, or half-open.
+
+ - A _closed interval_, denoted by brackets, includes its endpoints. For example, [0, 1] is the interval where `0 <= x <= 1`.
+
+ - An _open interval_, denoted by parentheses, excludes its endpoints. For example, (0, 1) is the interval where `0 < x < 1`.
+
+ - A _half-open interval_ includes only one of its endpoints. For example, (0, 1] is the _left-open_ interval where `0 < x <= 1`, while [0, 1) is the _right-open_ interval where `0 <= x < 1`.
diff --git a/docs/content/en/quick-reference/glossary/kind.md b/docs/content/en/quick-reference/glossary/kind.md
new file mode 100644
index 00000000000..a214dfbf13e
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/kind.md
@@ -0,0 +1,5 @@
+---
+title: kind
+---
+
+See [_page kind_](g).
diff --git a/docs/content/en/quick-reference/glossary/l10n.md b/docs/content/en/quick-reference/glossary/l10n.md
new file mode 100644
index 00000000000..013d0ab550f
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/l10n.md
@@ -0,0 +1,5 @@
+---
+title: l10n
+---
+
+See [_localization_](g).
diff --git a/docs/content/en/quick-reference/glossary/language.md b/docs/content/en/quick-reference/glossary/language.md
new file mode 100644
index 00000000000..551dc691ca2
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/language.md
@@ -0,0 +1,7 @@
+---
+title: language
+---
+
+A _language_ is a [_dimension_](g) that facilitates the localization and internationalization of content. While [_version_](g) focuses on lifecycle and [_role_](g) focuses on audience, the language dimension allows a logical page to be represented in different locales across the project.
+
+ See also: [_default language_](g).
diff --git a/docs/content/en/quick-reference/glossary/layout.md b/docs/content/en/quick-reference/glossary/layout.md
new file mode 100644
index 00000000000..a4e6b33f191
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/layout.md
@@ -0,0 +1,5 @@
+---
+title: layout
+---
+
+See [_template_](g).
diff --git a/docs/content/en/quick-reference/glossary/leaf-bundle.md b/docs/content/en/quick-reference/glossary/leaf-bundle.md
new file mode 100644
index 00000000000..aa41384ee32
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/leaf-bundle.md
@@ -0,0 +1,6 @@
+---
+title: leaf bundle
+reference: /content-management/page-bundles/
+---
+
+A _leaf bundle_ is a directory that contains an `index.md` file and zero or more [_resources_](g). Analogous to a physical leaf, a leaf bundle is at the end of a [_branch bundle_](g). It has no descendants.
diff --git a/docs/content/en/quick-reference/glossary/lexer.md b/docs/content/en/quick-reference/glossary/lexer.md
new file mode 100644
index 00000000000..a462231482b
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/lexer.md
@@ -0,0 +1,5 @@
+---
+title: lexer
+---
+
+A _lexer_ is a software component that identifies keywords, identifiers, operators, numbers, and other basic building blocks of a programming language within the input text.
diff --git a/docs/content/en/quick-reference/glossary/list-page.md b/docs/content/en/quick-reference/glossary/list-page.md
new file mode 100644
index 00000000000..d18efcfe725
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/list-page.md
@@ -0,0 +1,5 @@
+---
+title: list page
+---
+
+A list page is any [_page kind_](g) that receives a page [_collection_](g) in [_context_](g). This includes the home page, [_section pages_](g), [_taxonomy pages_](g), and [_term pages_](g).
diff --git a/docs/content/en/quick-reference/glossary/localization.md b/docs/content/en/quick-reference/glossary/localization.md
new file mode 100644
index 00000000000..01f45063589
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/localization.md
@@ -0,0 +1,6 @@
+---
+title: localization
+reference: /content-management/multilingual/
+---
+
+The term _localization_ refers to the process of adapting a site to meet language and regional requirements. This includes translations, date formats, number formats, currency formats, and collation order.
diff --git a/docs/content/en/quick-reference/glossary/logical-path.md b/docs/content/en/quick-reference/glossary/logical-path.md
new file mode 100644
index 00000000000..2eeee751f1a
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/logical-path.md
@@ -0,0 +1,6 @@
+---
+title: logical path
+reference: /methods/page/path/#examples
+---
+
+A _logical path_ is a page or page resource identifier derived from the file path, excluding its extension and language identifier. This value is neither a file path nor a URL. Starting with a file path relative to the `content` directory, Hugo determines the logical path by stripping the file extension and language identifier, converting to lower case, then replacing spaces with hyphens. Path segments are separated with a slash (`/`).
diff --git a/docs/content/en/quick-reference/glossary/map.md b/docs/content/en/quick-reference/glossary/map.md
new file mode 100644
index 00000000000..b4605d20be0
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/map.md
@@ -0,0 +1,6 @@
+---
+title: map
+reference: https://go.dev/ref/spec#Map_types
+---
+
+A _map_ is an unordered group of elements, each indexed by a unique key.
diff --git a/docs/content/en/quick-reference/glossary/markdown-attribute.md b/docs/content/en/quick-reference/glossary/markdown-attribute.md
new file mode 100644
index 00000000000..f5c57c728ec
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/markdown-attribute.md
@@ -0,0 +1,6 @@
+---
+title: Markdown attribute
+reference: /content-management/markdown-attributes/
+---
+
+A _Markdown attribute_ is a key-value pair attached to a Markdown element. These attributes are commonly used to add HTML attributes, like `class` and `id`, to the element when it's rendered into HTML. They provide a way to extend the basic Markdown syntax and add more semantic meaning or styling hooks to your content.
diff --git a/docs/content/en/quick-reference/glossary/marshal.md b/docs/content/en/quick-reference/glossary/marshal.md
new file mode 100644
index 00000000000..ce7d769b5ab
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/marshal.md
@@ -0,0 +1,6 @@
+---
+title: marshal
+reference: /functions/transform/remarshal/
+---
+
+To _marshal_ (verb) is to transform a data structure into a serialized object. For example, transforming a [_map_](g) into a JSON string.
diff --git a/docs/content/en/quick-reference/glossary/media-type.md b/docs/content/en/quick-reference/glossary/media-type.md
new file mode 100644
index 00000000000..2994360ef39
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/media-type.md
@@ -0,0 +1,6 @@
+---
+title: media type
+reference: /configuration/media-types/
+---
+
+A _media type_ (formerly known as a MIME type) is a two-part identifier for file formats and transmitted content. For example, the media type for JSON data is `application/json`.
diff --git a/docs/content/en/quick-reference/glossary/method.md b/docs/content/en/quick-reference/glossary/method.md
new file mode 100644
index 00000000000..634cd4b9791
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/method.md
@@ -0,0 +1,5 @@
+---
+title: method
+---
+
+Used within a [_template action_](g) and associated with an [_object_](g), a _method_ takes zero or more [_arguments_](g) and either returns a value or performs an action. For example, `IsHome` is a method on a `Page` object which returns `true` if the current page is the home page. See also [_function_](g).
diff --git a/docs/content/en/quick-reference/glossary/module.md b/docs/content/en/quick-reference/glossary/module.md
new file mode 100644
index 00000000000..30ec49ea4e3
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/module.md
@@ -0,0 +1,5 @@
+---
+title: module
+---
+
+A _module_ is a packaged combination of [_components_](g) which may include [_archetypes_](g), assets, content, data, templates, [_translation tables_](g), and static files. A module may be a [_theme_](g), a complete project, or a smaller collection of one or more components.
diff --git a/docs/content/en/quick-reference/glossary/mount.md b/docs/content/en/quick-reference/glossary/mount.md
new file mode 100644
index 00000000000..1608e750408
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/mount.md
@@ -0,0 +1,7 @@
+---
+title: mount
+params:
+ reference: /configuration/module
+---
+
+A _mount_ is a configuration object that maps a file path (source) to a [_component_](g) path (target) within Hugo's [_unified file system_](g).
diff --git a/docs/content/en/quick-reference/glossary/node.md b/docs/content/en/quick-reference/glossary/node.md
new file mode 100644
index 00000000000..b722e07c87e
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/node.md
@@ -0,0 +1,5 @@
+---
+title: node
+---
+
+A _node_ is a class of [_page kinds_](g) including `home`, `section`, `taxonomy`, and `term`.
diff --git a/docs/content/en/quick-reference/glossary/noop.md b/docs/content/en/quick-reference/glossary/noop.md
new file mode 100644
index 00000000000..bd159bb26b5
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/noop.md
@@ -0,0 +1,5 @@
+---
+title: noop
+---
+
+An abbreviated form of "no operation", a _noop_ is a statement that does nothing.
diff --git a/docs/content/en/quick-reference/glossary/object.md b/docs/content/en/quick-reference/glossary/object.md
new file mode 100644
index 00000000000..216609d0d74
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/object.md
@@ -0,0 +1,5 @@
+---
+title: object
+---
+
+An _object_ is a data structure with or without associated [_methods_](g).
diff --git a/docs/content/en/quick-reference/glossary/ordered-taxonomy.md b/docs/content/en/quick-reference/glossary/ordered-taxonomy.md
new file mode 100644
index 00000000000..9b6c1d92cd6
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/ordered-taxonomy.md
@@ -0,0 +1,5 @@
+---
+title: ordered taxonomy
+---
+
+Created by invoking the [`Alphabetical`](/methods/taxonomy/alphabetical/) or [`ByCount`](/methods/taxonomy/bycount/) method on a [`Taxonomy`](g) object, which is a [_map_](g), an _ordered taxonomy_ is a [_slice_](g), where each element is an object that contains the [_term_](g) and a slice of its [_weighted pages_](g).
diff --git a/docs/content/en/quick-reference/glossary/output-format.md b/docs/content/en/quick-reference/glossary/output-format.md
new file mode 100644
index 00000000000..aa5ddbdd770
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/output-format.md
@@ -0,0 +1,6 @@
+---
+title: output format
+reference: /configuration/output-formats/
+---
+
+An _output format_ is a collection of settings that defines how Hugo renders a file when building a site. For example, `html`, `json`, and `rss` are built-in output formats. You can create multiple output formats and control their generation based on [_page kind_](g), or by enabling one or more output formats for specific pages.
diff --git a/docs/content/en/quick-reference/glossary/page-bundle.md b/docs/content/en/quick-reference/glossary/page-bundle.md
new file mode 100644
index 00000000000..af76da2aa94
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/page-bundle.md
@@ -0,0 +1,6 @@
+---
+title: page bundle
+reference: /content-management/page-bundles/
+---
+
+A _page bundle_ is a directory that encapsulates both content and associated [_resources_](g). There are two types of page bundles: [_leaf bundles_](g) and [_branch bundles_](g).
diff --git a/docs/content/en/quick-reference/glossary/page-collection.md b/docs/content/en/quick-reference/glossary/page-collection.md
new file mode 100644
index 00000000000..f078ecf2770
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/page-collection.md
@@ -0,0 +1,5 @@
+---
+title: page collection
+---
+
+A _page collection_ is a slice of `Page` objects.
diff --git a/docs/content/en/quick-reference/glossary/page-kind.md b/docs/content/en/quick-reference/glossary/page-kind.md
new file mode 100644
index 00000000000..ee47fe0103c
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/page-kind.md
@@ -0,0 +1,6 @@
+---
+title: page kind
+reference: /methods/page/kind/
+---
+
+A _page kind_ is a classification of pages, one of `home`, `page`, `section`, `taxonomy`, or `term`.
diff --git a/docs/content/en/quick-reference/glossary/page-relative.md b/docs/content/en/quick-reference/glossary/page-relative.md
new file mode 100644
index 00000000000..d12327bb9cb
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/page-relative.md
@@ -0,0 +1,7 @@
+---
+title: page-relative
+---
+
+A _page-relative_ path is resolved relative to the current page's location in the content hierarchy. These paths do not begin with a leading slash. Examples include `old-name`, `./old-name`, and `../old-name`.
+
+ See also: [_site-relative_](g), [_server-relative_](g).
diff --git a/docs/content/en/quick-reference/glossary/page-resource.md b/docs/content/en/quick-reference/glossary/page-resource.md
new file mode 100644
index 00000000000..dab119eb409
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/page-resource.md
@@ -0,0 +1,5 @@
+---
+title: page resource
+---
+
+A _page resource_ is a file within a [_page bundle_](g).
diff --git a/docs/content/en/quick-reference/glossary/pager.md b/docs/content/en/quick-reference/glossary/pager.md
new file mode 100644
index 00000000000..f58874e4a87
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/pager.md
@@ -0,0 +1,5 @@
+---
+title: pager
+---
+
+Created during [_pagination_](g), a _pager_ contains a subset of a list page and navigation links to other pagers.
diff --git a/docs/content/en/quick-reference/glossary/paginate.md b/docs/content/en/quick-reference/glossary/paginate.md
new file mode 100644
index 00000000000..1caf9a07f6e
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/paginate.md
@@ -0,0 +1,5 @@
+---
+title: paginate
+---
+
+To _paginate_ (verb) is to split a list page into two or more subsets.
diff --git a/docs/content/en/quick-reference/glossary/pagination.md b/docs/content/en/quick-reference/glossary/pagination.md
new file mode 100644
index 00000000000..136341dbf6e
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/pagination.md
@@ -0,0 +1,6 @@
+---
+title: pagination
+reference: /templates/pagination
+---
+
+The term _pagination_ refers to the process of [_paginating_](g) a list page.
diff --git a/docs/content/en/quick-reference/glossary/paginator.md b/docs/content/en/quick-reference/glossary/paginator.md
new file mode 100644
index 00000000000..4d197348d62
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/paginator.md
@@ -0,0 +1,5 @@
+---
+title: paginator
+---
+
+A _paginator_ is a collection of [_pagers_](g).
diff --git a/docs/content/en/quick-reference/glossary/parameter.md b/docs/content/en/quick-reference/glossary/parameter.md
new file mode 100644
index 00000000000..f1a45ea3473
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/parameter.md
@@ -0,0 +1,5 @@
+---
+title: parameter
+---
+
+A _parameter_ is typically a user-defined key-value pair at the site or page level, but may also refer to a configuration setting or an [_argument_](g).
diff --git a/docs/content/en/quick-reference/glossary/partial-decorator.md b/docs/content/en/quick-reference/glossary/partial-decorator.md
new file mode 100644
index 00000000000..149e17ef242
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/partial-decorator.md
@@ -0,0 +1,8 @@
+---
+title: partial decorator
+reference: /templates/partial-decorators/
+---
+
+A _partial decorator_ is specific type of [_partial_](g) that functions as a [_wrapper component_](g). While a standard partial simply renders data within a fixed template, a decorator uses composition to enclose an entire block of content. It utilizes the [`templates.Inner`][] function as a placeholder to define exactly where that external content should be injected within the wrapper's layout.
+
+ [`templates.Inner`]: /functions/templates/inner/
diff --git a/docs/content/en/quick-reference/glossary/partial.md b/docs/content/en/quick-reference/glossary/partial.md
new file mode 100644
index 00000000000..a5dd5e51a1a
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/partial.md
@@ -0,0 +1,5 @@
+---
+title: partial
+---
+
+A _partial_ is a [_template_](g) called from any other template including [_shortcodes_](g), [render hooks](g), and other partials. A partial either renders something or returns something. A partial can also call itself, for example, to [_walk_](g) a data structure.
diff --git a/docs/content/en/quick-reference/glossary/permalink.md b/docs/content/en/quick-reference/glossary/permalink.md
new file mode 100644
index 00000000000..45651de83c1
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/permalink.md
@@ -0,0 +1,5 @@
+---
+title: permalink
+---
+
+A _permalink_ is the absolute URL of a published resource or a rendered page, including scheme and host.
diff --git a/docs/content/en/quick-reference/glossary/pipe.md b/docs/content/en/quick-reference/glossary/pipe.md
new file mode 100644
index 00000000000..f587eeccf9c
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/pipe.md
@@ -0,0 +1,5 @@
+---
+title: pipe
+---
+
+See [_pipeline_](g).
diff --git a/docs/content/en/quick-reference/glossary/pipeline.md b/docs/content/en/quick-reference/glossary/pipeline.md
new file mode 100644
index 00000000000..6dab5257886
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/pipeline.md
@@ -0,0 +1,7 @@
+---
+title: pipeline
+---
+
+Within a [_template action_](g), a _pipeline_ is a possibly chained sequence of values, [_function_](g) calls, or [_method_](g) calls. Functions and methods in the pipeline may take multiple [_arguments_](g).
+
+ A pipeline may be chained by separating a sequence of commands with pipeline characters (`|`). In a chained pipeline, the result of each command is passed as the last argument to the following command. The output of the final command in the pipeline is the value of the pipeline.
diff --git a/docs/content/en/quick-reference/glossary/pretty-url.md b/docs/content/en/quick-reference/glossary/pretty-url.md
new file mode 100644
index 00000000000..b15ef9f0f9f
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/pretty-url.md
@@ -0,0 +1,5 @@
+---
+title: pretty URL
+---
+
+A _pretty URL_ is a URL that does not include a file extension.
diff --git a/docs/content/en/quick-reference/glossary/primary-output-format.md b/docs/content/en/quick-reference/glossary/primary-output-format.md
new file mode 100644
index 00000000000..d89fe2563d5
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/primary-output-format.md
@@ -0,0 +1,10 @@
+---
+title: primary output format
+details: /configuration/outputs/
+---
+
+A _primary output format_ defines the default URL returned by the [`Permalink`] and [`RelPermalink`] methods for a given [_page kind_](g). It is specified as the first entry within the [outputs configuration] for that page kind.
+
+[`Permalink`]: /methods/page/permalink/
+[`RelPermalink`]: /methods/page/relpermalink/
+[outputs configuration]: /configuration/outputs/
diff --git a/docs/content/en/quick-reference/glossary/processable-image.md b/docs/content/en/quick-reference/glossary/processable-image.md
new file mode 100644
index 00000000000..eed69f105f2
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/processable-image.md
@@ -0,0 +1,19 @@
+---
+title: processable image
+---
+
+A _processable image_ is an image file characterized by one of the following [_media types_](g):
+
+ - `image/bmp`
+ - `image/gif`
+ - `image/jpeg`
+ - `image/png`
+ - `image/tiff`
+ - `image/webp`
+
+ Hugo can decode and encode these image formats, allowing you to use any of the [resource methods][] applicable to images such as `Width`, `Height`, `Crop`, `Fill`, `Fit`, `Filter`, `Process`, `Resize`, etc.
+
+ Use the [`reflect.IsImageResourceProcessable`][] function to determine if an image can be processed.
+
+ [`reflect.IsImageResourceProcessable`]: /functions/reflect/isimageresourceprocessable/
+ [resource methods]: /methods/resource
diff --git a/docs/content/en/quick-reference/glossary/project.md b/docs/content/en/quick-reference/glossary/project.md
new file mode 100644
index 00000000000..86a3f63d369
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/project.md
@@ -0,0 +1,5 @@
+---
+title: project
+---
+
+A _project_ is a collection of [_components_](g) used to generate one or more [sites](g). While a project may consist of only a single site, Hugo allows a single project to generate a matrix of sites based on [_language_](g), [role](g), and [_version_](g). The project serves as the parent container for the common assets and logic used across all sites within the build.
diff --git a/docs/content/en/quick-reference/glossary/publish.md b/docs/content/en/quick-reference/glossary/publish.md
new file mode 100644
index 00000000000..743e87a3e02
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/publish.md
@@ -0,0 +1,5 @@
+---
+title: publish
+---
+
+See [_build_](g).
diff --git a/docs/content/en/quick-reference/glossary/raw-string-literal.md b/docs/content/en/quick-reference/glossary/raw-string-literal.md
new file mode 100644
index 00000000000..056c0e6fc5a
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/raw-string-literal.md
@@ -0,0 +1,6 @@
+---
+title: raw string literal
+reference: https://go.dev/ref/spec#String_literals
+---
+
+A _raw string literal_ is a character sequence between backticks, as in `` `bar` ``. Within the backticks, any character may appear except a backtick. Backslashes have no special meaning and the string may contain newlines. Carriage return characters (`\r`) inside raw string literals are discarded from the raw string value.
diff --git a/docs/content/en/quick-reference/glossary/regular-expression.md b/docs/content/en/quick-reference/glossary/regular-expression.md
new file mode 100644
index 00000000000..82f8d98f549
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/regular-expression.md
@@ -0,0 +1,8 @@
+---
+title: regular expression
+reference:
+---
+
+A _regular expression_, also known as a _regex_, is a sequence of characters that defines a search pattern. Use the [RE2 syntax] when defining regular expressions in your templates or in your project configuration.
+
+ [RE2 syntax]: https://github.com/google/re2/wiki/syntax
diff --git a/docs/content/en/quick-reference/glossary/regular-page.md b/docs/content/en/quick-reference/glossary/regular-page.md
new file mode 100644
index 00000000000..084408e57f4
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/regular-page.md
@@ -0,0 +1,5 @@
+---
+title: regular page
+---
+
+A _regular page_ is a page with the "page" [_page kind_](g). See also [_section page_](g).
diff --git a/docs/content/en/quick-reference/glossary/relative-permalink.md b/docs/content/en/quick-reference/glossary/relative-permalink.md
new file mode 100644
index 00000000000..a272133a38f
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/relative-permalink.md
@@ -0,0 +1,5 @@
+---
+title: relative permalink
+---
+
+A _relative permalink_ is the host-relative URL of a published resource or a rendered page.
diff --git a/docs/content/en/quick-reference/glossary/remote-resource.md b/docs/content/en/quick-reference/glossary/remote-resource.md
new file mode 100644
index 00000000000..1637497633d
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/remote-resource.md
@@ -0,0 +1,5 @@
+---
+title: remote resource
+---
+
+A _remote resource_ is a file on a remote server, accessible via HTTP or HTTPS.
diff --git a/docs/content/en/quick-reference/glossary/render-hook.md b/docs/content/en/quick-reference/glossary/render-hook.md
new file mode 100644
index 00000000000..2cdb98e05ec
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/render-hook.md
@@ -0,0 +1,6 @@
+---
+title: render hook
+reference: /render-hooks
+---
+
+A _render hook_ is a [_template_](g) that overrides standard Markdown rendering.
diff --git a/docs/content/en/quick-reference/glossary/resource-type.md b/docs/content/en/quick-reference/glossary/resource-type.md
new file mode 100644
index 00000000000..b10cd5925fb
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/resource-type.md
@@ -0,0 +1,5 @@
+---
+title: resource type
+---
+
+A _resource type_ is the main type of a resource's [_media type_](g). Content files such as Markdown, HTML, AsciiDoc, Pandoc, reStructuredText, and Emacs Org Mode have resource type `page`. Other resource types include `image`, `text`, `video`, and others. Retrieve the resource type using the [`ResourceType`](/methods/resource/resourcetype/) method on a `Resource` object.
diff --git a/docs/content/en/quick-reference/glossary/resource.md b/docs/content/en/quick-reference/glossary/resource.md
new file mode 100644
index 00000000000..0864bc43d65
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/resource.md
@@ -0,0 +1,7 @@
+---
+title: resource
+---
+
+A _resource_ is any file consumed by the build process to augment or generate content, structure, behavior, or presentation. For example: images, videos, content snippets, CSS, Sass, JavaScript, and data.
+
+ Hugo supports three types of resources: [_global resources_](g), [_page resources_](g), and [_remote resources_](g).
diff --git a/docs/content/en/quick-reference/glossary/role.md b/docs/content/en/quick-reference/glossary/role.md
new file mode 100644
index 00000000000..f491d361091
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/role.md
@@ -0,0 +1,7 @@
+---
+title: role
+---
+
+A _role_ is a [_dimension_](g) that allows a logical page to be served in different forms depending on the target audience. While [_language_](g) focuses on localization and [_version_](g) focuses on lifecycle, the role dimension allows a project to generate variations of a page without duplicating content.
+
+ See also: [_default role_](g).
diff --git a/docs/content/en/quick-reference/glossary/rune-literal.md b/docs/content/en/quick-reference/glossary/rune-literal.md
new file mode 100644
index 00000000000..8a19b489ba4
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/rune-literal.md
@@ -0,0 +1,10 @@
+---
+title: rune literal
+reference: https://go.dev/ref/spec#Rune_literals
+---
+
+A _rune literal_ is the textual representation of a [_rune_](g) within a [_template_](g). It consists of a character sequence enclosed in single quotes, such as `'x'`, `'\n'`, or `'ü'`.
+
+ Unlike [_interpreted string literals_](g) or [_raw string literals_](g), which represent a sequence of characters, a _rune literal_ represents a single [_integer_](g) value identifying a Unicode [code point][]. Within the quotes, any character may appear except a newline or an unescaped single quote. Multi-character sequences starting with a backslash (`\`) can be used to encode specific values, such as `\n` for a newline or `\u00FC` for the letter `ü`.
+
+ [code point]: https://en.wikipedia.org/wiki/Code_point
diff --git a/docs/content/en/quick-reference/glossary/rune.md b/docs/content/en/quick-reference/glossary/rune.md
new file mode 100644
index 00000000000..74dfa4b5b8d
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/rune.md
@@ -0,0 +1,11 @@
+---
+title: rune
+---
+
+A _rune_ is a way to represent a single character as a number. In Hugo and Go, text is stored as a sequence of bytes. However, while a basic letter like `x` uses only one byte, a single character such as the German `ü` is made up of multiple bytes. A _rune_ represents the entire character as one single value, no matter how many bytes it takes to store it.
+
+ Technically, a _rune_ is just another name for a 32-bit [_integer_](g). It stores the Unicode [code point][], which is the official number assigned to that specific character.
+
+ When you want to manipulate text character-by-character rather than by raw data size, you are working with _runes_. You write a _rune_ in a [_template_](g) using a [_rune literal_](g), such as `'x'`, `'\n'`, or `'ü'`.
+
+ [code point]: https://en.wikipedia.org/wiki/Code_point
diff --git a/docs/content/en/quick-reference/glossary/scalar.md b/docs/content/en/quick-reference/glossary/scalar.md
new file mode 100644
index 00000000000..63ae27d3f12
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/scalar.md
@@ -0,0 +1,5 @@
+---
+title: scalar
+---
+
+A _scalar_ is a single value, one of [_string_](g), [_integer_](g), [floating point](g), or [_boolean_](g).
diff --git a/docs/content/en/quick-reference/glossary/scope.md b/docs/content/en/quick-reference/glossary/scope.md
new file mode 100644
index 00000000000..5230340bdea
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/scope.md
@@ -0,0 +1,5 @@
+---
+title: scope
+---
+
+The term _scope_ refers to the specific region of code where a [_variable_](g) or [_object_](g) is accessible. For example, a variable initialized in one [_template_](g) is not available within another.
diff --git a/docs/content/en/quick-reference/glossary/scratch-pad.md b/docs/content/en/quick-reference/glossary/scratch-pad.md
new file mode 100644
index 00000000000..4792ce12ea2
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/scratch-pad.md
@@ -0,0 +1,5 @@
+---
+title: scratch pad
+---
+
+Conceptually, a _scratch pad_ is a [_map_](g) with [_methods_](g) to set, get, update, and delete values. Attach the data structure to a `Page` or `Site` object using the [`Store`](/methods/page/store/) method, or create a locally scoped scratch pad using the [`newScratch`](/functions/collections/newscratch/) function.
diff --git a/docs/content/en/quick-reference/glossary/section-page.md b/docs/content/en/quick-reference/glossary/section-page.md
new file mode 100644
index 00000000000..fd84ed5f21b
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/section-page.md
@@ -0,0 +1,5 @@
+---
+title: section page
+---
+
+A _section page_ is a page with the "section" [_page kind_](g). Typically a listing of [_regular pages_](g) and/or other section pages within the current [_section_](g).
diff --git a/docs/content/en/quick-reference/glossary/section.md b/docs/content/en/quick-reference/glossary/section.md
new file mode 100644
index 00000000000..45d1203e0fd
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/section.md
@@ -0,0 +1,5 @@
+---
+title: section
+---
+
+A _section_ is a top-level content directory or any content directory containing an `_index.md` file.
diff --git a/docs/content/en/quick-reference/glossary/seed.md b/docs/content/en/quick-reference/glossary/seed.md
new file mode 100644
index 00000000000..bbb6e48091a
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/seed.md
@@ -0,0 +1,6 @@
+---
+title: seed
+reference: https://en.wikipedia.org/wiki/Random_seed
+---
+
+A _seed_ is the starting point for a computer algorithm that generates pseudo-random numbers. Using the same seed will always produce the identical sequence of numbers, which is essential for reproducibility in areas like simulations, cryptography, and video games.
diff --git a/docs/content/en/quick-reference/glossary/segment.md b/docs/content/en/quick-reference/glossary/segment.md
new file mode 100644
index 00000000000..21c62556b72
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/segment.md
@@ -0,0 +1,7 @@
+---
+title: segment
+params:
+ reference: /configuration/segments/
+---
+
+A _segment_ is a subset of a site, filtered by [_logical path_](g), [_sites matrix_](g), [_page kind_](g), or [_output format_](g).
diff --git a/docs/content/en/quick-reference/glossary/server-relative.md b/docs/content/en/quick-reference/glossary/server-relative.md
new file mode 100644
index 00000000000..8783a25aff3
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/server-relative.md
@@ -0,0 +1,9 @@
+---
+title: server-relative
+---
+
+A _server-relative_ path is the final path from the web server's root, used in the generated site. These paths always begin with a leading slash and account for the [`baseURL`] and [_content dimension_](g) prefixes such as language, [_role_](g), or version. For example, `/en/examples/old-name/` is a server-relative path.
+
+ See also: [_page-relative_](g), [_site-relative_](g).
+
+ [`baseURL`]: /configuration/all/#baseurl
diff --git a/docs/content/en/quick-reference/glossary/shortcode.md b/docs/content/en/quick-reference/glossary/shortcode.md
new file mode 100644
index 00000000000..a6503ea63ca
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/shortcode.md
@@ -0,0 +1,6 @@
+---
+title: shortcode
+reference: /content-management/shortcodes
+---
+
+A _shortcode_ is a [_template_](g) invoked within markup, accepting any number of [_arguments_](g). They can be used with any [_content format_](g) to insert elements such as videos, images, and social media embeds into your content.
diff --git a/docs/content/en/quick-reference/glossary/site-relative.md b/docs/content/en/quick-reference/glossary/site-relative.md
new file mode 100644
index 00000000000..b434fd1c2e3
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/site-relative.md
@@ -0,0 +1,7 @@
+---
+title: site-relative
+---
+
+A _site-relative_ path is resolved relative to the root of the content directory. These paths begin with a leading slash. For example, `/old-name` is a site-relative path.
+
+ See also: [_page-relative_](g), [_server-relative_](g).
diff --git a/docs/content/en/quick-reference/glossary/site-root.md b/docs/content/en/quick-reference/glossary/site-root.md
new file mode 100644
index 00000000000..1066acb4d14
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/site-root.md
@@ -0,0 +1,13 @@
+---
+title: site root
+---
+
+The _site root_ is the root directory of the current [_site_](g), relative to the [`publishDir`][]. The _site root_ may include one or more content [_dimension_](g) prefixes, such as [_language_](g), [_role_](g), or [_version_](g).
+
+ Project description|Site root examples
+ :--|:--|:--
+ Monolingual|`/`, `/guest`, `/guest/v1.2.3`
+ Multilingual single-host|`/en`, `/guest/en`, `/guest/v1.2.3/en`
+ Multilingual multihost|`/en`, `/en/guest`, `/en/guest/v1.2.3`
+
+ [`publishDir`]: /configuration/all/#publishdir
diff --git a/docs/content/en/quick-reference/glossary/site.md b/docs/content/en/quick-reference/glossary/site.md
new file mode 100644
index 00000000000..f3be3a1f3e2
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/site.md
@@ -0,0 +1,5 @@
+---
+title: site
+---
+
+A _site_ is a specific instance of your [_project_](g) representing a unique combination of [_language_](g), [_role_](g), and [_version_](g). While a simple project may consist of only a single site, Hugo's multidimensional content model allows a single codebase to generate a matrix of sites simultaneously.
diff --git a/docs/content/en/quick-reference/glossary/sites-complements.md b/docs/content/en/quick-reference/glossary/sites-complements.md
new file mode 100644
index 00000000000..80213e1f923
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/sites-complements.md
@@ -0,0 +1,5 @@
+---
+title: sites complements
+---
+
+A _sites complements_ is a configuration object defined in content front matter or a file mount. The links will point to the complementary [_sites_](g). The configuration is structured as a map of [_glob slices_](g).
diff --git a/docs/content/en/quick-reference/glossary/sites-matrix.md b/docs/content/en/quick-reference/glossary/sites-matrix.md
new file mode 100644
index 00000000000..8e6838d4e42
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/sites-matrix.md
@@ -0,0 +1,7 @@
+---
+title: sites matrix
+---
+
+A _sites matrix_ is a configuration object defined in content front matter or a file mount to precisely control which [_sites_](g) the content should be generated for. When defined in a file mount for templates, it controls which sites the template will be applied to. In Hugo multidimensional content model, the matrix defines the intersection of three dimensions: [_language_](g), [_role_](g), and [_version_](g). The configuration is structured as a map of [_glob slices_](g).
+
+ See also [_sites complements_](g), [front matter: sites](/content-management/front-matter/#sites), [module mounts: sites](/configuration/module/#sites), and [segments: sites](/configuration/segments/#sites).
diff --git a/docs/content/en/quick-reference/glossary/slice.md b/docs/content/en/quick-reference/glossary/slice.md
new file mode 100644
index 00000000000..5d83bed6b85
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/slice.md
@@ -0,0 +1,6 @@
+---
+title: slice
+reference: https://go.dev/ref/spec#Slice_types
+---
+
+A _slice_ is a numbered sequence of elements. Unlike Go's [_array_](g) data type, slices are dynamically sized. [_Elements_](g) within a slice can be [_scalars_](g), [_arrays_](g), [_maps_](g), pages, or other slices.
diff --git a/docs/content/en/quick-reference/glossary/string.md b/docs/content/en/quick-reference/glossary/string.md
new file mode 100644
index 00000000000..54ed4c1d145
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/string.md
@@ -0,0 +1,5 @@
+---
+title: string
+---
+
+A _string_ is a sequence of bytes. For example, `"What is 6 times 7?"`.
diff --git a/docs/content/en/quick-reference/glossary/taxonomic-weight.md b/docs/content/en/quick-reference/glossary/taxonomic-weight.md
new file mode 100644
index 00000000000..90bd804e08b
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/taxonomic-weight.md
@@ -0,0 +1,6 @@
+---
+title: taxonomic weight
+reference: /content-management/taxonomies/#taxonomic-weight
+---
+
+Defined in front matter and unique to each taxonomy, a _taxonomic weight_ is a [_weight_](g) that determines the sort order of page collections contained within a [`Taxonomy`](g) object.
diff --git a/docs/content/en/quick-reference/glossary/taxonomy-object.md b/docs/content/en/quick-reference/glossary/taxonomy-object.md
new file mode 100644
index 00000000000..525446a8e49
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/taxonomy-object.md
@@ -0,0 +1,5 @@
+---
+title: taxonomy object
+---
+
+A _taxonomy object_ is a [_map_](g) of [_terms_](g) and the [_weighted pages_](g) associated with each term.
diff --git a/docs/content/en/quick-reference/glossary/taxonomy-page.md b/docs/content/en/quick-reference/glossary/taxonomy-page.md
new file mode 100644
index 00000000000..6b62f0912e8
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/taxonomy-page.md
@@ -0,0 +1,5 @@
+---
+title: taxonomy page
+---
+
+A _taxonomy page_ is a page with the "taxonomy" [_page kind_](g). Typically a listing of [_terms_](g) within a given [_taxonomy_](g).
diff --git a/docs/content/en/quick-reference/glossary/taxonomy.md b/docs/content/en/quick-reference/glossary/taxonomy.md
new file mode 100644
index 00000000000..ce35a634cfd
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/taxonomy.md
@@ -0,0 +1,5 @@
+---
+title: taxonomy
+reference: /content-management/taxonomies
+---
+A _taxonomy_ is a group of related [_terms_](g) used to classify content. For example, a "colors" taxonomy might include the terms "red", "green", and "blue".
diff --git a/docs/content/en/quick-reference/glossary/template-action.md b/docs/content/en/quick-reference/glossary/template-action.md
new file mode 100644
index 00000000000..e40c228fe89
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/template-action.md
@@ -0,0 +1,6 @@
+---
+title: template action
+reference: https://pkg.go.dev/text/template#hdr-Actions
+---
+
+A data evaluation or control structure within a [_template_](g), delimited by `{{` and `}}`.
diff --git a/docs/content/en/quick-reference/glossary/template.md b/docs/content/en/quick-reference/glossary/template.md
new file mode 100644
index 00000000000..d442c0a224e
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/template.md
@@ -0,0 +1,6 @@
+---
+title: template
+reference: /templates
+---
+
+A _template_ is a file with [_template actions_](g), located within the `layouts` directory of a project, theme, or module.
diff --git a/docs/content/en/quick-reference/glossary/term-page.md b/docs/content/en/quick-reference/glossary/term-page.md
new file mode 100644
index 00000000000..78eabc11f66
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/term-page.md
@@ -0,0 +1,5 @@
+---
+title: term page
+---
+
+A _term page_ is a page with the "term" [_page kind_](g). Typically a listing of [_regular pages_](g) and [_section pages_](g) with a given [_term_](g).
diff --git a/docs/content/en/quick-reference/glossary/term.md b/docs/content/en/quick-reference/glossary/term.md
new file mode 100644
index 00000000000..cafd9258567
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/term.md
@@ -0,0 +1,6 @@
+---
+title: term
+reference: /content-management/taxonomies
+---
+
+A _term_ is a member of a [_taxonomy_](g), used to classify content.
diff --git a/docs/content/en/quick-reference/glossary/theme.md b/docs/content/en/quick-reference/glossary/theme.md
new file mode 100644
index 00000000000..4c6d15ecb18
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/theme.md
@@ -0,0 +1,5 @@
+---
+title: theme
+---
+
+A _theme_ is a [_module_](g) that delivers a complete set of [_components_](g) defining a site's layout, presentation, and behavior. While every theme is a module, not every module is a theme.
diff --git a/docs/content/en/quick-reference/glossary/token.md b/docs/content/en/quick-reference/glossary/token.md
new file mode 100644
index 00000000000..f8c12d57077
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/token.md
@@ -0,0 +1,5 @@
+---
+title: token
+---
+
+A _token_ is an identifier within a format string, beginning with a colon and replaced with a value when rendered. For example, use tokens in format strings for both [permalinks](/content-management/urls/#permalinks) and [dates](/functions/time/format/#localization).
diff --git a/docs/content/en/quick-reference/glossary/translation-table.md b/docs/content/en/quick-reference/glossary/translation-table.md
new file mode 100644
index 00000000000..a48febb67ce
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/translation-table.md
@@ -0,0 +1,5 @@
+---
+title: translation table
+---
+
+A _translation table_ is a data file within the `i18n` directory, holding translations for a single language.
diff --git a/docs/content/en/quick-reference/glossary/type.md b/docs/content/en/quick-reference/glossary/type.md
new file mode 100644
index 00000000000..18b9ab5597f
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/type.md
@@ -0,0 +1,5 @@
+---
+title: type
+---
+
+See [_content type_](g).
diff --git a/docs/content/en/quick-reference/glossary/ugly-url.md b/docs/content/en/quick-reference/glossary/ugly-url.md
new file mode 100644
index 00000000000..4083f937838
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/ugly-url.md
@@ -0,0 +1,5 @@
+---
+title: ugly URL
+---
+
+An _ugly URL_ is a URL that includes a file extension.
diff --git a/docs/content/en/quick-reference/glossary/unified-file-system.md b/docs/content/en/quick-reference/glossary/unified-file-system.md
new file mode 100644
index 00000000000..4e5f430d6f4
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/unified-file-system.md
@@ -0,0 +1,5 @@
+---
+title: unified file system
+---
+
+Hugo's _unified file system_ provides a layered view for each of its seven [_component_](g) types: [_archetypes_](g), assets, content, data, templates, [_translation tables_](g), and static files. Project component directories are layered over [_module_](g) component directories. When multiple layers contain the same file, Hugo uses the version from the highest layer.
diff --git a/docs/content/en/quick-reference/glossary/unmarshal.md b/docs/content/en/quick-reference/glossary/unmarshal.md
new file mode 100644
index 00000000000..d449b0b8c86
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/unmarshal.md
@@ -0,0 +1,6 @@
+---
+title: unmarshal
+reference: /functions/transform/unmarshal/
+---
+
+To _unmarshal_ (verb) is to transform a serialized object into a data structure. For example, transforming a JSON file into a [_map_](g) that you can access within a template.
diff --git a/docs/content/en/quick-reference/glossary/utc.md b/docs/content/en/quick-reference/glossary/utc.md
new file mode 100644
index 00000000000..a4627be5a09
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/utc.md
@@ -0,0 +1,6 @@
+---
+title: UTC
+reference: https://en.wikipedia.org/wiki/Coordinated_Universal_Time
+---
+
+_UTC_ is an abbreviation for Coordinated Universal Time, the primary time standard used worldwide to regulate clocks and time. It is the basis for civil time and time zones across the globe.
diff --git a/docs/content/en/quick-reference/glossary/variable.md b/docs/content/en/quick-reference/glossary/variable.md
new file mode 100644
index 00000000000..f8139a41f33
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/variable.md
@@ -0,0 +1,5 @@
+---
+title: variable
+---
+
+A _variable_ is a user-defined [_identifier_](g) prepended with a `$` symbol, representing a value of any data type, initialized or assigned within a [_template action_](g). For example, `$foo` and `$bar` are variables.
diff --git a/docs/content/en/quick-reference/glossary/vendor.md b/docs/content/en/quick-reference/glossary/vendor.md
new file mode 100644
index 00000000000..f745a4e1544
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/vendor.md
@@ -0,0 +1,7 @@
+---
+title: vendor
+---
+
+To _vendor_ (verb) in a software context is the process of including the source code of third-party dependencies directly within your own project's repository, rather than downloading them on the fly from an external package manager.
+
+ When you are asked to "vendor the dependencies into the project root," you are being told to move those external libraries from a temporary cache into a dedicated folder that gets committed to your version control system.
diff --git a/docs/content/en/quick-reference/glossary/version.md b/docs/content/en/quick-reference/glossary/version.md
new file mode 100644
index 00000000000..62eb8f35ae6
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/version.md
@@ -0,0 +1,9 @@
+---
+title: version
+---
+
+A _version_ is a [_dimension_](g) that represents a specific iteration, release, or lifecycle stage of content. While [_language_](g) focuses on localization and [_role_](g) focuses on audience, the version dimension allows you to maintain multiple states of the same content simultaneously using [semantic versioning].
+
+ See also: [_default version_](g).
+
+ [semantic versioning]: https://semver.org/
diff --git a/docs/content/en/quick-reference/glossary/walk.md b/docs/content/en/quick-reference/glossary/walk.md
new file mode 100644
index 00000000000..2b2a84462b1
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/walk.md
@@ -0,0 +1,5 @@
+---
+title: walk
+---
+
+To _walk_ (verb) is to recursively traverse a nested data structure. For example, rendering a multilevel menu.
diff --git a/docs/content/en/quick-reference/glossary/weight.md b/docs/content/en/quick-reference/glossary/weight.md
new file mode 100644
index 00000000000..01b91fe5258
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/weight.md
@@ -0,0 +1,5 @@
+---
+title: weight
+---
+
+A _weight_ is a numeric value used to position an element within a sorted [_collection_](g). Assign weights using non-zero integers. Lighter items float to the top, while heavier items sink to the bottom. Unweighted or zero-weighted elements are placed at the end of the collection. Weights are typically assigned to pages, menu entries, languages, [_roles_](g), versions, and output formats.
diff --git a/docs/content/en/quick-reference/glossary/weighted-page.md b/docs/content/en/quick-reference/glossary/weighted-page.md
new file mode 100644
index 00000000000..e6cf8d6dbde
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/weighted-page.md
@@ -0,0 +1,5 @@
+---
+title: weighted page
+---
+
+Contained within a [_taxonomy object_](g), a _weighted page_ is a [_map_](g) with two [_elements_](g): a `Page` object, and its [_taxonomic weight_](g) as defined in front matter. Access the elements using the `Page` and `Weight` keys.
diff --git a/docs/content/en/quick-reference/glossary/workspace.md b/docs/content/en/quick-reference/glossary/workspace.md
new file mode 100644
index 00000000000..6eda9bab3c7
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/workspace.md
@@ -0,0 +1,7 @@
+---
+title: workspace
+params:
+ reference: /hugo-modules/use-modules/#workspace
+---
+
+A _workspace_ is a collection of [_modules_](g) on disk.
diff --git a/docs/content/en/quick-reference/glossary/wrapper-component.md b/docs/content/en/quick-reference/glossary/wrapper-component.md
new file mode 100644
index 00000000000..ada5d466e53
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/wrapper-component.md
@@ -0,0 +1,7 @@
+---
+title: wrapper component
+---
+
+A _wrapper component_ is an interface pattern that encloses other content through composition rather than fixed parameters. It provides a reusable shell to handle layout, styling, or logic, allowing the calling template to inject arbitrary content into the component's interior.
+
+ See also: [_partial decorator_](g).
diff --git a/docs/content/en/quick-reference/glossary/zero-time.md b/docs/content/en/quick-reference/glossary/zero-time.md
new file mode 100644
index 00000000000..9701f391287
--- /dev/null
+++ b/docs/content/en/quick-reference/glossary/zero-time.md
@@ -0,0 +1,5 @@
+---
+title: zero time
+---
+
+The _zero time_ is January 1, 0001, 00:00:00 UTC. Formatted per [RFC3339](https://www.rfc-editor.org/rfc/rfc3339) the _zero time_ is 0001-01-01T00:00:00-00:00.
diff --git a/docs/content/en/quick-reference/methods.md b/docs/content/en/quick-reference/methods.md
new file mode 100644
index 00000000000..524713c1f47
--- /dev/null
+++ b/docs/content/en/quick-reference/methods.md
@@ -0,0 +1,8 @@
+---
+title: Methods
+description: A quick reference guide to Hugo's methods, grouped by object.
+categories: []
+keywords: []
+---
+
+{{% quick-reference section="methods" %}}
diff --git a/docs/content/en/quick-reference/page-collections.md b/docs/content/en/quick-reference/page-collections.md
new file mode 100644
index 00000000000..14bededbcac
--- /dev/null
+++ b/docs/content/en/quick-reference/page-collections.md
@@ -0,0 +1,38 @@
+---
+title: Page collections
+description: A quick reference guide to Hugo's page collections.
+categories: []
+keywords: []
+---
+
+## Page
+
+Use these `Page` methods when rendering lists on [section pages](g), [taxonomy pages](g), [term pages](g), and the home page.
+
+{{% render-list-of-pages-in-section path=/methods/page filter=methods_page_page_collections filterType=include titlePrefix=PAGE. %}}
+
+## Site
+
+Use these `Site` methods when rendering lists on any page.
+
+{{% render-list-of-pages-in-section path=/methods/site filter=methods_site_page_collections filterType=include titlePrefix=SITE. %}}
+
+## Filter
+
+Use the [`where`][] function to filter page collections.
+
+## Sort
+
+{{% glossary-term "default sort order" %}}
+
+Use these methods to sort page collections by different criteria.
+
+{{% render-list-of-pages-in-section path=/methods/pages filter=methods_pages_sort filterType=include titlePrefix=. titlePrefix=PAGES. %}}
+
+## Group
+
+Use these methods to group page collections.
+
+{{% render-list-of-pages-in-section path=/methods/pages filter=methods_pages_group filterType=include titlePrefix=. titlePrefix=PAGES. %}}
+
+[`where`]: /functions/collections/where/
diff --git a/docs/content/en/quick-reference/syntax-highlighting-styles.md b/docs/content/en/quick-reference/syntax-highlighting-styles.md
new file mode 100644
index 00000000000..3ba6fd833b9
--- /dev/null
+++ b/docs/content/en/quick-reference/syntax-highlighting-styles.md
@@ -0,0 +1,35 @@
+---
+title: Syntax highlighting styles
+description: Highlight code examples using one of these styles.
+categories: []
+keywords: [highlight]
+---
+
+## Overview
+
+Hugo provides several methods to add syntax highlighting to code examples:
+
+- Use the [`transform.Highlight`] function within your templates
+- Use the [`highlight`] shortcode with any [content format](g)
+- Use [fenced code blocks] with the Markdown content format
+
+Regardless of method, use any of the syntax highlighting styles below.
+
+Set the default syntax highlighting style in your project configuration:
+
+{{< code-toggle file=hugo >}}
+[markup.highlight]
+style = 'monokai'
+{{< /code-toggle >}}
+
+See [configure Markup](/configuration/markup/#highlight).
+
+[`transform.Highlight`]: /functions/transform/highlight/
+[`highlight`]: /shortcodes/highlight/
+[fenced code blocks]: /content-management/syntax-highlighting/#fenced-code-blocks
+
+## Styles
+
+This gallery demonstrates the application of each syntax highlighting style with code examples written in different programming languages.
+
+{{% syntax-highlighting-styles %}}
diff --git a/docs/content/en/readfiles/README.md b/docs/content/en/readfiles/README.md
deleted file mode 100644
index 4b10f0e4755..00000000000
--- a/docs/content/en/readfiles/README.md
+++ /dev/null
@@ -1,16 +0,0 @@
-# readdirs Directory for Reusable Content
-
-Files in this directory are:
-
-1. Used in *more than one place* within the Hugo docs
-2. Used in Examples of readdir (i.e. in local file templates)
-
-These files are called using the [`readfile` shortcode (source)](../layouts/readfile.html).
-
-You can call this shortcode in the docs as follows:
-
-
-{{% readfile file="/path/to/file.txt" markdown="true" %}}
-
-
-`markdown="true"` is optional (default = `"false"`) and parses the string through the Blackfriday renderer.
diff --git a/docs/content/en/readfiles/dateformatting.md b/docs/content/en/readfiles/dateformatting.md
deleted file mode 100644
index 42138dd8aca..00000000000
--- a/docs/content/en/readfiles/dateformatting.md
+++ /dev/null
@@ -1,87 +0,0 @@
-Go templates [format your dates][time] according to a single reference time:
-
-```
-Mon Jan 2 15:04:05 MST 2006
-```
-
-You can think of `MST` as `07`, thus making the reference format string a sequence of numbers. The following is [taken directly from the Go docs][gdex]:
-
-```
-Jan 2 15:04:05 2006 MST
- 1 2 3 4 5 6 -7
-```
-
-### Hugo Date Templating Reference
-
-Each of the following examples show the reference formatting string followed by the string Hugo will output in your HTML.
-
-Note that the examples were rendered and tested in [CST][] and pull from a single example date you might have in your content's front matter:
-
-```
-date: 2017-03-03T14:15:59-06:00
-```
-
-`.Date` (i.e. called via [page variable][pagevars])
-: **Returns**: `2017-03-03 14:15:59 -0600 CST`
-
-`"Monday, January 2, 2006"`
-: **Returns**: `Friday, March 3, 2017`
-
-`"Mon Jan 2 2006"`
-: **Returns**: `Fri Mar 3 2017`
-
-`"January 2nd"`
-: **Returns**: `March 3rd`
-
-`"January 2006"`
-: **Returns**: `March 2017`
-
-`"2006-01-02"`
-: **Returns**: `2017-03-03`
-
-`"Monday"`
-: **Returns**: `Friday`
-
-`"02 Jan 06 15:04 MST"` (RFC822)
-: **Returns**: `03 Mar 17 14:15 CST`
-
-`"02 Jan 06 15:04 -0700"` (RFC822Z)
-: **Returns**: `03 Mar 17 14:15 -0600`
-
-`"Mon, 02 Jan 2006 15:04:05 MST"` (RFC1123)
-: **Returns**: `Fri, 03 Mar 2017 14:15:59 CST`
-
-`"Mon, 02 Jan 2006 15:04:05 -0700"` (RFC339)
-: **Returns**: `Fri, 03 Mar 2017 14:15:59 -0600`
-
-### Cardinal Numbers and Ordinal Abbreviations
-
-Spelled-out cardinal numbers (e.g. "one", "two", and "three") and ordinal abbreviations (e.g. "1st", "2nd", and "3rd") are not currently supported.
-
-To continue with the example above:
-
-```
-{{.Date.Format "Jan 2nd 2006"}}
-```
-
-Hugo assumes you want to append `nd` as a string to the day of the month and outputs the following:
-
-```
-Mar 3nd 2017
-```
-
-### Use `.Local` and `.UTC`
-
-In conjunction with the [`dateFormat` function][dateFormat], you can also convert your dates to `UTC` or to local timezones:
-
-`{{ dateFormat "02 Jan 06 15:04 MST" .Date.UTC }}`
-: **Returns**: `03 Mar 17 20:15 UTC`
-
-`{{ dateFormat "02 Jan 06 15:04 MST" .Date.Local }}`
-: **Returns**: `03 Mar 17 14:15 CST`
-
-[CST]: https://en.wikipedia.org/wiki/Central_Time_Zone
-[dateFormat]: /functions/dateformat/
-[gdex]: https://golang.org/pkg/time/#example_Time_Format
-[pagevars]: /variables/page/
-[time]: https://golang.org/pkg/time/
\ No newline at end of file
diff --git a/docs/content/en/readfiles/index.md b/docs/content/en/readfiles/index.md
deleted file mode 100644
index 3d65eaa0ff9..00000000000
--- a/docs/content/en/readfiles/index.md
+++ /dev/null
@@ -1,3 +0,0 @@
----
-headless: true
----
\ No newline at end of file
diff --git a/docs/content/en/readfiles/pages-vs-site-pages.md b/docs/content/en/readfiles/pages-vs-site-pages.md
deleted file mode 100644
index df5c7d26e97..00000000000
--- a/docs/content/en/readfiles/pages-vs-site-pages.md
+++ /dev/null
@@ -1,34 +0,0 @@
-* A _regular_ page is a "post" page or a "content" page.
- * A _leaf bundle_ is a regular page.
-* A _list_ page can list _regular_ pages and other _list_ pages. Some
- examples are: homepage, section pages, _taxonomy_ (`/tags/`) and
- _term_ (`/tags/foo/`) pages.
- * A _branch bundle_ is a _list_ page.
-
-`.Site.Pages`
-: Collection of **all** pages of the site: _regular_ pages,
- sections, taxonomies, etc. -- Superset of everything!
-
-`.Site.RegularPages`
-: Collection of only _regular_ pages.
-
-The above `.Site. ..` page collections can be accessed from any scope in
-the templates.
-
-Below variables return a collection of pages only from the scope of
-the current _list_ page:
-
-`.Pages`
-: Collection of _regular_ pages and _only first-level_
- section pages under the current _list_ page.
-
-`.RegularPages`
-: Collection of only _regular_ pages under the
- current _list_ page. This **excludes** regular pages in nested sections/_list_ pages (those are subdirectories with an `_index.md` file.
-
-`.RegularPagesRecursive`
-: {{< new-in "0.68.0" >}} Collection of **all** _regular_ pages under a _list_ page. This **includes** regular pages in nested sections/_list_ pages.
-
-Note
-: From the scope of _regular_ pages, `.Pages` and
- `.RegularPages` return an empty slice.
diff --git a/docs/content/en/readfiles/sectionvars.md b/docs/content/en/readfiles/sectionvars.md
deleted file mode 100644
index 45aaff1f3f4..00000000000
--- a/docs/content/en/readfiles/sectionvars.md
+++ /dev/null
@@ -1,23 +0,0 @@
-.CurrentSection
-: The page's current section. The value can be the page itself if it is a section or the homepage.
-
-.FirstSection
-: The page's first section below root, e.g. `/docs`, `/blog` etc.
-
-.InSection $anotherPage
-: Whether the given page is in the current section.
-
-.IsAncestor $anotherPage
-: Whether the current page is an ancestor of the given page.
-
-.IsDescendant $anotherPage
-: Whether the current page is a descendant of the given page.
-
-.Parent
-: A section's parent section or a page's section.
-
-.Section
-: The [section](/content-management/sections/) this content belongs to. **Note:** For nested sections, this is the first path element in the directory, for example, `/blog/funny/mypost/ => blog`.
-
-.Sections
-: The [sections](/content-management/sections/) below this content.
diff --git a/docs/content/en/readfiles/testing.txt b/docs/content/en/readfiles/testing.txt
deleted file mode 100644
index 6428710e3cf..00000000000
--- a/docs/content/en/readfiles/testing.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-##### Hello World!
-
-Testing one, **two**, *three*. Don't delete this sample file used in the [templates](/templates/) section of the Hugo docs.
\ No newline at end of file
diff --git a/docs/content/en/render-hooks/_index.md b/docs/content/en/render-hooks/_index.md
new file mode 100644
index 00000000000..a957cb4cf6e
--- /dev/null
+++ b/docs/content/en/render-hooks/_index.md
@@ -0,0 +1,8 @@
+---
+title: Render hooks
+description: Create render hook templates to override the rendering of Markdown to HTML.
+categories: []
+keywords: []
+weight: 10
+aliases: [/templates/render-hooks/]
+---
diff --git a/docs/content/en/render-hooks/blockquotes.md b/docs/content/en/render-hooks/blockquotes.md
new file mode 100755
index 00000000000..77e7059c709
--- /dev/null
+++ b/docs/content/en/render-hooks/blockquotes.md
@@ -0,0 +1,183 @@
+---
+title: Blockquote render hooks
+linkTitle: Blockquotes
+description: Create blockquote render hook templates to override the rendering of Markdown blockquotes to HTML.
+categories: []
+keywords: []
+---
+
+{{< new-in 0.132.0 />}}
+
+## Context
+
+Blockquote _render hook_ templates receive the following [context](g):
+
+AlertType
+: (`string`) Applicable when [`Type`](#type) is `alert`, this is the alert type converted to lowercase. See the [alerts](#alerts) section below.
+
+AlertTitle
+: {{< new-in 0.134.0 />}}
+: (`template.HTML`) Applicable when [`Type`](#type) is `alert`, this is the alert title. See the [alerts](#alerts) section below.
+
+AlertSign
+: {{< new-in 0.134.0 />}}
+: (`string`) Applicable when [`Type`](#type) is `alert`, this is the alert sign. Typically used to indicate whether an alert is graphically foldable, this is one of `+`, `-`, or an empty string. See the [alerts](#alerts) section below.
+
+Attributes
+: (`map`) The [Markdown attributes], available if you configure your site as follows:
+
+ {{< code-toggle file=hugo >}}
+ [markup.goldmark.parser.attribute]
+ block = true
+ {{< /code-toggle >}}
+
+Ordinal
+: (`int`) The zero-based ordinal of the blockquote on the page.
+
+Page
+: (`page`) A reference to the current page.
+
+PageInner
+: (`page`) A reference to a page nested via the [`RenderShortcodes`] method. [See details](#pageinner-details).
+
+Position
+: (`string`) The position of the blockquote within the page content.
+
+Text
+: (`template.HTML`) The blockquote text, excluding the first line if [`Type`](#type) is `alert`. See the [alerts](#alerts) section below.
+
+Type
+: (`string`) The blockquote type. Returns `alert` if the blockquote has an alert designator, else `regular`. See the [alerts](#alerts) section below.
+
+## Examples
+
+In its default configuration, Hugo renders Markdown blockquotes according to the [CommonMark specification]. To create a render hook that does the same thing:
+
+```go-html-template {file="layouts/_markup/render-blockquote.html" copy=true}
+
+ {{ .Text }}
+
+```
+
+To render a blockquote as an HTML `figure` element with an optional citation and caption:
+
+```go-html-template {file="layouts/_markup/render-blockquote.html" copy=true}
+
+
+ {{ .Text }}
+
+ {{ with .Attributes.caption }}
+
+ {{ . | safeHTML }}
+
+ {{ end }}
+
+```
+
+Then in your markdown:
+
+```text
+> Some text
+{cite="https://gohugo.io" caption="Some caption"}
+```
+
+## Alerts
+
+Also known as _callouts_ or _admonitions_, alerts are blockquotes used to emphasize critical information.
+
+### Basic syntax
+
+With the basic Markdown syntax, the first line of each alert is an alert designator consisting of an exclamation point followed by the alert type, wrapped within brackets. For example:
+
+```text {file="content/example.md"}
+> [!NOTE]
+> Useful information that users should know, even when skimming content.
+
+> [!TIP]
+> Helpful advice for doing things better or more easily.
+
+> [!IMPORTANT]
+> Key information users need to know to achieve their goal.
+
+> [!WARNING]
+> Urgent info that needs immediate user attention to avoid problems.
+
+> [!CAUTION]
+> Advises about risks or negative outcomes of certain actions.
+```
+
+The basic syntax is compatible with [GitHub], [Obsidian], and [Typora].
+
+### Extended syntax
+
+With the extended Markdown syntax, you may optionally include an alert sign and/or an alert title. The alert sign is one of `+` or `-`, typically used to indicate whether an alert is graphically foldable. For example:
+
+```text {file="content/example.md"}
+> [!WARNING]+ Radiation hazard
+> Do not approach or handle without protective gear.
+```
+
+The extended syntax is compatible with [Obsidian].
+
+> [!note]
+> The extended syntax is not compatible with GitHub or Typora. If you include an alert sign or an alert title, these applications render the Markdown as a blockquote.
+
+### Example
+
+This blockquote render hook renders a multilingual alert if an alert designator is present, otherwise it renders a blockquote according to the CommonMark specification.
+
+```go-html-template {file="layouts/_markup/render-blockquote.html" copy=true}
+{{ $emojis := dict
+ "caution" ":exclamation:"
+ "important" ":information_source:"
+ "note" ":information_source:"
+ "tip" ":bulb:"
+ "warning" ":information_source:"
+}}
+
+{{ if eq .Type "alert" }}
+
+{{ end }}
+```
+
+To override the label, create these entries in your i18n files:
+
+{{< code-toggle file=i18n/en.toml >}}
+caution = 'Caution'
+important = 'Important'
+note = 'Note'
+tip = 'Tip'
+warning = 'Warning'
+{{< /code-toggle >}}
+
+Although you can use one template with conditional logic as shown above, you can also create separate templates for each [`Type`](#type) of blockquote:
+
+```text
+layouts/
+ └── _markup/
+ ├── render-blockquote-alert.html
+ └── render-blockquote-regular.html
+```
+
+{{% include "/_common/render-hooks/pageinner.md" %}}
+
+[`RenderShortcodes`]: /methods/page/rendershortcodes
+[CommonMark specification]: https://spec.commonmark.org/current/
+[GitHub]: https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts
+[Markdown attributes]: /content-management/markdown-attributes/
+[Obsidian]: https://help.obsidian.md/Editing+and+formatting/Callouts
+[Typora]: https://support.typora.io/Markdown-Reference/#callouts--github-style-alerts
diff --git a/docs/content/en/render-hooks/code-blocks.md b/docs/content/en/render-hooks/code-blocks.md
new file mode 100755
index 00000000000..5b226467be3
--- /dev/null
+++ b/docs/content/en/render-hooks/code-blocks.md
@@ -0,0 +1,125 @@
+---
+title: Code block render hooks
+linkTitle: Code blocks
+description: Create code block render hook templates to override the rendering of Markdown code blocks to HTML.
+categories: []
+keywords: []
+---
+
+## Markdown
+
+This Markdown example contains a fenced code block:
+
+````text {file="content/example.md"}
+```sh {class="my-class" id="my-codeblock" lineNos=inline tabWidth=2}
+declare a=1
+echo "$a"
+exit
+```
+````
+
+A fenced code block consists of:
+
+- A leading [code fence]
+- An optional [info string]
+- A code sample
+- A trailing code fence
+
+In the previous example, the info string contains:
+
+- The language of the code sample (the first word)
+- An optional space-delimited or comma-delimited list of attributes (everything within braces)
+
+The attributes in the info string can be generic attributes or highlighting options.
+
+In the example above, the _generic attributes_ are `class` and `id`. In the absence of special handling within a code block render hook, Hugo adds each generic attribute to the HTML element surrounding the rendered code block. Consistent with its content security model, Hugo removes HTML event attributes such as `onclick` and `onmouseover`. Generic attributes are typically global HTML attributes, but you may include custom attributes as well.
+
+In the example above, the _highlighting options_ are `lineNos` and `tabWidth`. Hugo uses the [Chroma] syntax highlighter to render the code sample. You can control the appearance of the rendered code by specifying one or more [highlighting options].
+
+> [!note]
+> Although `style` is a global HTML attribute, when used in an info string it is a highlighting option.
+
+## Context
+
+Code block _render hook_ templates receive the following [context](g):
+
+Attributes
+: (`map`) The generic attributes from the info string.
+
+Inner
+: (`string`) The content between the leading and trailing code fences, excluding the info string.
+
+Options
+: (`map`) The highlighting options from the info string. This map is empty if [`Type`](#type) is an empty string or a code language that is not supported by the Chroma syntax highlighter. However, in this case, the highlighting options are available in the [`Attributes`](#attributes) map.
+
+Ordinal
+: (`int`) The zero-based ordinal of the code block on the page.
+
+Page
+: (`page`) A reference to the current page.
+
+PageInner
+: (`page`) A reference to a page nested via the [`RenderShortcodes`] method. [See details](#pageinner-details).
+
+Position
+: (`text.Position`) The position of the code block within the page content.
+
+Type
+: (`string`) The first word of the info string, typically the code language.
+
+## Examples
+
+In its default configuration, Hugo renders fenced code blocks by passing the code sample through the Chroma syntax highlighter and wrapping the result. To create a render hook that does the same thing:
+
+```go-html-template {file="layouts/_markup/render-codeblock.html" copy=true}
+{{ $result := transform.HighlightCodeBlock . }}
+{{ $result.Wrapped }}
+```
+
+Although you can use one template with conditional logic to control the behavior on a per-language basis, you can also create language-specific templates.
+
+```text
+layouts/
+ └── _markup/
+ ├── render-codeblock-mermaid.html
+ ├── render-codeblock-python.html
+ └── render-codeblock.html
+```
+
+For example, to create a code block render hook to render [Mermaid] diagrams:
+
+```go-html-template {file="layouts/_markup/render-codeblock-mermaid.html" copy=true}
+
+ {{ .Inner | htmlEscape | safeHTML }}
+
+{{ .Page.Store.Set "hasMermaid" true }}
+```
+
+Then include this snippet at the _bottom_ of your base template, before the closing `body` tag:
+
+```go-html-template {file="layouts/baseof.html" copy=true}
+{{ if .Store.Get "hasMermaid" }}
+
+{{ end }}
+```
+
+See the [diagrams] page for details.
+
+## Embedded
+
+Hugo includes an [embedded code block render hook] to render [GoAT diagrams].
+
+{{% include "/_common/render-hooks/pageinner.md" %}}
+
+[`RenderShortcodes`]: /methods/page/rendershortcodes
+[Chroma]: https://github.com/alecthomas/chroma/
+[code fence]: https://spec.commonmark.org/current/#code-fence
+[diagrams]: /content-management/diagrams/#mermaid-diagrams
+[embedded code block render hook]: <{{% eturl render-codeblock-goat %}}>
+[GoAT diagrams]: /content-management/diagrams/#goat-diagrams-ascii
+[highlighting options]: /functions/transform/highlight/#options
+[info string]: https://spec.commonmark.org/current/#info-string
+[Mermaid]: https://mermaid.js.org/
diff --git a/docs/content/en/render-hooks/headings.md b/docs/content/en/render-hooks/headings.md
new file mode 100755
index 00000000000..75c57d3d67e
--- /dev/null
+++ b/docs/content/en/render-hooks/headings.md
@@ -0,0 +1,63 @@
+---
+title: Heading render hooks
+linkTitle: Headings
+description: Create heading render hook templates to override the rendering of Markdown headings to HTML.
+categories: []
+keywords: []
+---
+
+## Context
+
+Heading _render hook_ templates receive the following [context](g):
+
+Anchor
+: (`string`) The `id` attribute of the heading element.
+
+Attributes
+: (`map`) The [Markdown attributes], available if you configure your site as follows:
+
+ {{< code-toggle file=hugo >}}
+ [markup.goldmark.parser.attribute]
+ title = true
+ {{< /code-toggle >}}
+
+Level
+: (`int`) The heading level, 1 through 6.
+
+Page
+: (`page`) A reference to the current page.
+
+PageInner
+: (`page`) A reference to a page nested via the [`RenderShortcodes`] method. [See details](#pageinner-details).
+
+PlainText
+: (`string`) The heading text as plain text.
+
+Text
+: (`template.HTML`) The heading text.
+
+[Markdown attributes]: /content-management/markdown-attributes/
+[`RenderShortcodes`]: /methods/page/rendershortcodes
+
+## Examples
+
+In its default configuration, Hugo renders Markdown headings according to the [CommonMark specification] with the addition of automatic `id` attributes. To create a render hook that does the same thing:
+
+[CommonMark specification]: https://spec.commonmark.org/current/
+
+```go-html-template {file="layouts/_markup/render-heading.html" copy=true}
+
+ {{- .Text -}}
+
+```
+
+To add an anchor link to the right of each heading:
+
+```go-html-template {file="layouts/_markup/render-heading.html" copy=true}
+
+ {{ .Text }}
+ #
+
+```
+
+{{% include "/_common/render-hooks/pageinner.md" %}}
diff --git a/docs/content/en/render-hooks/images.md b/docs/content/en/render-hooks/images.md
new file mode 100755
index 00000000000..91fe40f305a
--- /dev/null
+++ b/docs/content/en/render-hooks/images.md
@@ -0,0 +1,134 @@
+---
+title: Image render hooks
+linkTitle: Images
+description: Create image render hook templates to override the rendering of Markdown images to HTML.
+categories: []
+keywords: []
+---
+
+## Markdown
+
+A Markdown image has three components: the image description, the image destination, and optionally the image title.
+
+```text
+
+ ------------ ------------------ ---------
+ description destination title
+```
+
+These components are passed into the render hook [context](g) as shown below.
+
+## Context
+
+Image _render hook_ templates receive the following context:
+
+Attributes
+: (`map`) The [Markdown attributes], available if you configure your site as follows:
+
+ {{< code-toggle file=hugo >}}
+ [markup.goldmark.parser]
+ wrapStandAloneImageWithinParagraph = false
+ [markup.goldmark.parser.attribute]
+ block = true
+ {{< /code-toggle >}}
+
+Destination
+: (`string`) The image destination.
+
+IsBlock
+: (`bool`) Reports whether a standalone image is not wrapped within a paragraph element.
+
+Ordinal
+: (`int`) The zero-based ordinal of the image on the page.
+
+Page
+: (`page`) A reference to the current page.
+
+PageInner
+: (`page`) A reference to a page nested via the [`RenderShortcodes`] method. [See details](#pageinner-details).
+
+PlainText
+: (`string`) The image description as plain text.
+
+Text
+: (`template.HTML`) The image description.
+
+Title
+: (`string`) The image title.
+
+## Examples
+
+> [!note]
+> With inline elements such as images and links, remove leading and trailing whitespace using the `{{‑ ‑}}` delimiter notation to prevent whitespace between adjacent inline elements and text.
+
+In its default configuration, Hugo renders Markdown images according to the [CommonMark specification]. To create a render hook that does the same thing:
+
+```go-html-template {file="layouts/_markup/render-image.html" copy=true}
+
+{{- /* chomp trailing newline */ -}}
+```
+
+To render standalone images within `figure` elements:
+
+```go-html-template {file="layouts/_markup/render-image.html" copy=true}
+{{- if .IsBlock -}}
+
+
+ {{- with .Title }}{{ . }}{{ end -}}
+
+{{- else -}}
+
+{{- end -}}
+```
+
+Note that the above requires the following project configuration:
+
+{{< code-toggle file=hugo >}}
+[markup.goldmark.parser]
+wrapStandAloneImageWithinParagraph = false
+{{< /code-toggle >}}
+
+## Embedded
+
+Hugo includes an [embedded image render hook] to resolve Markdown image destinations. You can adjust its behavior in your project configuration. This is the default setting:
+
+{{< code-toggle file=hugo >}}
+[markup.goldmark.renderHooks.image]
+useEmbedded = 'auto'
+{{< /code-toggle >}}
+
+When set to `auto` as shown above, Hugo automatically uses the embedded image render hook for multilingual single-host projects, specifically when the [duplication of shared page resources] feature is disabled. This is the default behavior for such projects. If custom image render hooks are defined by your project, modules, or themes, these will be used instead.
+
+You can also configure Hugo to `always` use the embedded image render hook, use it only as a `fallback`, or `never` use it. See [details](/configuration/markup/#renderhooksimageuseembedded).
+
+The embedded image render hook resolves internal Markdown destinations by looking for a matching [page resource](g), falling back to a matching [global resource](g). Remote destinations are passed through, and the render hook will not throw an error or warning if unable to resolve a destination.
+
+You must place global resources in the `assets` directory. If you have placed your resources in the `static` directory, and you are unable or unwilling to move them, you must mount the `static` directory to the `assets` directory by including both of these entries in your project configuration:
+
+{{< code-toggle file=hugo >}}
+[[module.mounts]]
+source = 'assets'
+target = 'assets'
+
+[[module.mounts]]
+source = 'static'
+target = 'assets'
+{{< /code-toggle >}}
+
+Note that the embedded image render hook does not perform image processing. Its sole purpose is to resolve Markdown image destinations.
+
+{{% include "/_common/render-hooks/pageinner.md" %}}
+
+[`RenderShortcodes`]: /methods/page/rendershortcodes
+[CommonMark specification]: https://spec.commonmark.org/current/
+[duplication of shared page resources]: /configuration/markup/#duplicateresourcefiles
+[embedded image render hook]: <{{% eturl render-image %}}>
+[Markdown attributes]: /content-management/markdown-attributes/
diff --git a/docs/content/en/render-hooks/introduction.md b/docs/content/en/render-hooks/introduction.md
new file mode 100755
index 00000000000..aafebf3e69a
--- /dev/null
+++ b/docs/content/en/render-hooks/introduction.md
@@ -0,0 +1,79 @@
+---
+title: Introduction
+description: An introduction to Hugo's render hooks.
+categories: []
+keywords: []
+weight: 10
+---
+
+When rendering Markdown to HTML, render hooks override the conversion. Each render hook is a template, with one template for each supported element type:
+
+- [Blockquotes](/render-hooks/blockquotes)
+- [Code blocks](/render-hooks/code-blocks)
+- [Headings](/render-hooks/headings)
+- [Images](/render-hooks/images)
+- [Links](/render-hooks/links)
+- [Passthrough elements](/render-hooks/passthrough)
+- [Tables](/render-hooks/tables)
+
+> [!note]
+> Hugo supports multiple [content formats] including Markdown, HTML, AsciiDoc, Emacs Org Mode, Pandoc, and reStructuredText.
+>
+> The render hook capability is limited to Markdown. You cannot create render hooks for the other content formats.
+
+For example, consider this Markdown:
+
+```text
+[Hugo](https://gohugo.io)
+
+
+```
+
+Without link or image render hooks, the example above is rendered to:
+
+```html
+
+```
+
+Each render hook is a template, with one template for each supported element type:
+
+```text
+layouts/
+ └── _markup/
+ ├── render-blockquote.html
+ ├── render-codeblock.html
+ ├── render-heading.html
+ ├── render-image.html
+ ├── render-link.html
+ ├── render-passthrough.html
+ └── render-table.html
+```
+
+The template lookup order allows you to create different render hooks for each page [type](g), [kind](g), language, and [output format](g). For example:
+
+```text
+layouts/
+├── _markup/
+│ ├── render-link.html
+│ └── render-link.rss.xml
+├── books/
+│ └── _markup/
+│ ├── render-link.html
+│ └── render-link.rss.xml
+└── films/
+ └── _markup/
+ ├── render-link.html
+ └── render-link.rss.xml
+```
+
+The remaining pages in this section describe each type of render hook, including examples and the context received by each template.
+
+[content formats]: /content-management/formats/
diff --git a/docs/content/en/render-hooks/links.md b/docs/content/en/render-hooks/links.md
new file mode 100755
index 00000000000..7cd65bdfa96
--- /dev/null
+++ b/docs/content/en/render-hooks/links.md
@@ -0,0 +1,104 @@
+---
+title: Link render hooks
+linkTitle: Links
+description: Create a link render hook to override the rendering of Markdown links to HTML.
+categories: []
+keywords: []
+---
+
+## Markdown
+
+A Markdown link has three components: the link text, the link destination, and optionally the link title.
+
+```text
+[Post 1](/posts/post-1 "My first post")
+ ------ ------------- -------------
+ text destination title
+```
+
+These components are passed into the render hook [context](g) as shown below.
+
+## Context
+
+Link _render hook_ templates receive the following context:
+
+Destination
+: (`string`) The link destination.
+
+Page
+: (`page`) A reference to the current page.
+
+PageInner
+: (`page`) A reference to a page nested via the [`RenderShortcodes`] method. [See details](#pageinner-details).
+
+PlainText
+: (`string`) The link description as plain text.
+
+Text
+: (`template.HTML`) The link description.
+
+Title
+: (`string`) The link title.
+
+## Examples
+
+> [!note]
+> With inline elements such as images and links, remove leading and trailing whitespace using the `{{‑ ‑}}` delimiter notation to prevent whitespace between adjacent inline elements and text.
+
+In its default configuration, Hugo renders Markdown links according to the [CommonMark specification]. To create a render hook that does the same thing:
+
+```go-html-template {file="layouts/_markup/render-link.html" copy=true}
+
+ {{- with .Text }}{{ . }}{{ end -}}
+
+{{- /* chomp trailing newline */ -}}
+```
+
+To include a `rel` attribute set to `external` for external links:
+
+```go-html-template {file="layouts/_markup/render-link.html" copy=true}
+{{- $u := urls.Parse .Destination -}}
+
+ {{- with .Text }}{{ . }}{{ end -}}
+
+{{- /* chomp trailing newline */ -}}
+```
+
+## Embedded
+
+Hugo includes an [embedded link render hook] to resolve Markdown link destinations. You can adjust its behavior in your project configuration. This is the default setting:
+
+{{< code-toggle file=hugo >}}
+[markup.goldmark.renderHooks.link]
+useEmbedded = 'auto'
+{{< /code-toggle >}}
+
+When set to `auto` as shown above, Hugo automatically uses the embedded link render hook for multilingual single-host projects, specifically when the [duplication of shared page resources] feature is disabled. This is the default behavior for such projects. If custom link render hooks are defined by your project, modules, or themes, these will be used instead.
+
+You can also configure Hugo to `always` use the embedded link render hook, use it only as a `fallback`, or `never` use it. See [details](/configuration/markup/#renderhookslinkuseembedded).
+
+The embedded link render hook resolves internal Markdown destinations by looking for a matching page, falling back to a matching [page resource](g), then falling back to a matching [global resource](g). Remote destinations are passed through, and the render hook will not throw an error or warning if unable to resolve a destination.
+
+You must place global resources in the `assets` directory. If you have placed your resources in the `static` directory, and you are unable or unwilling to move them, you must mount the `static` directory to the `assets` directory by including both of these entries in your project configuration:
+
+{{< code-toggle file=hugo >}}
+[[module.mounts]]
+source = 'assets'
+target = 'assets'
+
+[[module.mounts]]
+source = 'static'
+target = 'assets'
+{{< /code-toggle >}}
+
+{{% include "/_common/render-hooks/pageinner.md" %}}
+
+[`RenderShortcodes`]: /methods/page/rendershortcodes
+[CommonMark specification]: https://spec.commonmark.org/current/
+[duplication of shared page resources]: /configuration/markup/#duplicateresourcefiles
+[embedded link render hook]: <{{% eturl render-link %}}>
diff --git a/docs/content/en/render-hooks/passthrough.md b/docs/content/en/render-hooks/passthrough.md
new file mode 100755
index 00000000000..7aada5713ec
--- /dev/null
+++ b/docs/content/en/render-hooks/passthrough.md
@@ -0,0 +1,128 @@
+---
+title: Passthrough render hooks
+linkTitle: Passthrough
+description: Create passthrough render hook templates to override the rendering of text snippets captured by the Goldmark Passthrough extension.
+categories: []
+keywords: []
+---
+
+{{< new-in 0.132.0 />}}
+
+## Overview
+
+Hugo uses [Goldmark] to render Markdown to HTML. Goldmark supports custom extensions to extend its core functionality. The [Passthrough] extension captures and preserves raw Markdown within delimited snippets of text, including the delimiters themselves. These are known as _passthrough elements_.
+
+[Goldmark]: https://github.com/yuin/goldmark
+[Passthrough]: /configuration/markup/#passthrough
+
+Depending on your choice of delimiters, Hugo will classify a passthrough element as either _block_ or _inline_. Consider this contrived example:
+
+```text {file="content/example.md"}
+This is a
+
+\[block\]
+
+passthrough element with opening and closing block delimiters.
+
+This is an \(inline\) passthrough element with opening and closing inline delimiters.
+```
+
+Update your project configuration to enable the Passthrough extension and define opening and closing delimiters for each passthrough element type, either `block` or `inline`. For example:
+
+{{< code-toggle file=hugo >}}
+[markup.goldmark.extensions.passthrough]
+enable = true
+[markup.goldmark.extensions.passthrough.delimiters]
+block = [['\[', '\]'], ['$$', '$$']]
+inline = [['\(', '\)']]
+{{< /code-toggle >}}
+
+In the example above there are two sets of `block` delimiters. You may use either one in your Markdown.
+
+The Passthrough extension is often used in conjunction with the MathJax or KaTeX display engine to render [mathematical expressions] written in the LaTeX markup language.
+
+[mathematical expressions]: /content-management/mathematics/
+
+To enable custom rendering of passthrough elements, create a passthrough render hook.
+
+## Context
+
+Passthrough _render hook_ templates receive the following [context](g):
+
+Attributes
+: (`map`) The [Markdown attributes], available if you configure your site as follows:
+
+ {{< code-toggle file=hugo >}}
+ [markup.goldmark.parser.attribute]
+ block = true
+ {{< /code-toggle >}}
+
+ Hugo populates the `Attributes` map for _block_ passthrough elements. Markdown attributes are not applicable to _inline_ elements.
+
+Inner
+: (`string`) The inner content of the passthrough element, excluding the delimiters.
+
+Ordinal
+: (`int`) The zero-based ordinal of the passthrough element on the page.
+
+Page
+: (`page`) A reference to the current page.
+
+PageInner
+: (`page`) A reference to a page nested via the [`RenderShortcodes`] method. [See details](#pageinner-details).
+
+Position
+: (`string`) The position of the passthrough element within the page content.
+
+Type
+: (`string`) The passthrough element type, either `block` or `inline`.
+
+[Markdown attributes]: /content-management/markdown-attributes/
+[`RenderShortcodes`]: /methods/page/rendershortcodes
+
+## Example
+
+Instead of client-side JavaScript rendering of mathematical markup using MathJax or KaTeX, create a passthrough render hook which calls the [`transform.ToMath`] function.
+
+[`transform.ToMath`]: /functions/transform/tomath/
+
+```go-html-template {file="layouts/_markup/render-passthrough.html" copy=true}
+{{- $opts := dict "output" "htmlAndMathml" "displayMode" (eq .Type "block") }}
+{{- with try (transform.ToMath .Inner $opts) }}
+ {{- with .Err }}
+ {{- errorf "Unable to render mathematical markup to HTML using the transform.ToMath function. The KaTeX display engine threw the following error: %s: see %s." . $.Position }}
+ {{- else }}
+ {{- .Value }}
+ {{- $.Page.Store.Set "hasMath" true }}
+ {{- end }}
+{{- end -}}
+```
+
+Then, in your base template, conditionally include the KaTeX CSS within the head element:
+
+```go-html-template {file="layouts/baseof.html" copy=true}
+
+ {{ $noop := .WordCount }}
+ {{ if .Page.Store.Get "hasMath" }}
+
+ {{ end }}
+
+```
+
+In the above, note the use of a [noop](g) statement to force content rendering before we check the value of `hasMath` with the `Store.Get` method.
+
+Although you can use one template with conditional logic as shown above, you can also create separate templates for each [`Type`](#type) of passthrough element:
+
+```text
+layouts/
+ └── _markup/
+ ├── render-passthrough-block.html
+ └── render-passthrough-inline.html
+```
+
+{{% include "/_common/render-hooks/pageinner.md" %}}
diff --git a/docs/content/en/render-hooks/tables.md b/docs/content/en/render-hooks/tables.md
new file mode 100755
index 00000000000..852f67e1158
--- /dev/null
+++ b/docs/content/en/render-hooks/tables.md
@@ -0,0 +1,100 @@
+---
+title: Table render hooks
+linkTitle: Tables
+description: Create table render hook templates to override the rendering of Markdown tables to HTML.
+categories: []
+keywords: []
+---
+
+{{< new-in 0.134.0 />}}
+
+## Context
+
+Table _render hook_ templates receive the following [context](g):
+
+Attributes
+: (`map`) The [Markdown attributes], available if you configure your site as follows:
+
+ {{< code-toggle file=hugo >}}
+ [markup.goldmark.parser.attribute]
+ block = true
+ {{< /code-toggle >}}
+
+Ordinal
+: (`int`) The zero-based ordinal of the table on the page.
+
+Page
+: (`page`) A reference to the current page.
+
+PageInner
+: (`page`) A reference to a page nested via the [`RenderShortcodes`] method. [See details](#pageinner-details).
+
+Position
+: (`string`) The position of the table within the page content.
+
+THead
+: (`slice`) A slice of table header rows, where each element is a slice of table cells.
+
+TBody
+: (`slice`) A slice of table body rows, where each element is a slice of table cells.
+
+[Markdown attributes]: /content-management/markdown-attributes/
+[`RenderShortcodes`]: /methods/page/rendershortcodes
+
+## Table cells
+
+Each table cell within the slice of slices returned by the `THead` and `TBody` methods has the following fields:
+
+Alignment
+: (`string`) The alignment of the text within the table cell, one of `left`, `center`, or `right`.
+
+Text
+: (`template.HTML`) The text within the table cell.
+
+## Example
+
+In its default configuration, Hugo renders Markdown tables according to the [GitHub Flavored Markdown specification]. To create a render hook that does the same thing:
+
+[GitHub Flavored Markdown specification]: https://github.github.com/gfm/#tables-extension-
+
+```go-html-template {file="layouts/_markup/render-table.html" copy=true}
+
+
+ {{- range .THead }}
+
+ {{- range . }}
+
+ {{- .Text -}}
+
+ {{- end }}
+
+ {{- end }}
+
+
+ {{- range .TBody }}
+
+ {{- range . }}
+
+ {{- .Text -}}
+
+ {{- end }}
+
+ {{- end }}
+
+
+```
+
+{{% include "/_common/render-hooks/pageinner.md" %}}
diff --git a/docs/content/en/shortcodes/_index.md b/docs/content/en/shortcodes/_index.md
new file mode 100644
index 00000000000..826ee57960c
--- /dev/null
+++ b/docs/content/en/shortcodes/_index.md
@@ -0,0 +1,7 @@
+---
+title: Shortcodes
+description: Insert elements such as videos, images, and social media embeds into your content using Hugo's embedded shortcodes.
+categories: []
+keywords: []
+weight: 10
+---
diff --git a/docs/content/en/shortcodes/details.md b/docs/content/en/shortcodes/details.md
new file mode 100755
index 00000000000..5df6c118f00
--- /dev/null
+++ b/docs/content/en/shortcodes/details.md
@@ -0,0 +1,74 @@
+---
+title: Details shortcode
+linkTitle: Details
+description: Insert an HTML details element into your content using the details shortcode.
+categories: []
+keywords: []
+---
+
+{{< new-in 0.140.0 />}}
+
+> [!note]
+> To override Hugo's embedded `details` shortcode, copy the [source code] to a file with the same name in the `layouts/_shortcodes` directory.
+
+## Example
+
+With this markup:
+
+```text
+{{* details summary="See the details" */>}}
+This is a **bold** word.
+{{* /details */>}}
+```
+
+Hugo renders this HTML:
+
+```html
+
+ See the details
+
This is a bold word.
+
+```
+
+Which looks like this in your browser:
+
+{{< details summary="See the details" >}}
+This is a **bold** word.
+{{< /details >}}
+
+## Arguments
+
+summary
+: (`string`) The content of the child `summary` element rendered from Markdown to HTML. Default is `Details`.
+
+open
+: (`bool`) Whether to initially display the content of the `details` element. Default is `false`.
+
+class
+: (`string`) The `class` attribute of the `details` element.
+
+name
+: (`string`) The `name` attribute of the `details` element.
+
+title
+: (`string`) The `title` attribute of the `details` element.
+
+## Styling
+
+Use CSS to style the `details` element, the `summary` element, and the content itself.
+
+```css
+/* target the details element */
+details { }
+
+/* target the summary element */
+details > summary { }
+
+/* target the children of the summary element */
+details > summary > * { }
+
+/* target the content */
+details > :not(summary) { }
+```
+
+[source code]: <{{% eturl details %}}>
diff --git a/docs/content/en/shortcodes/figure.md b/docs/content/en/shortcodes/figure.md
new file mode 100755
index 00000000000..42438f7ad38
--- /dev/null
+++ b/docs/content/en/shortcodes/figure.md
@@ -0,0 +1,109 @@
+---
+title: Figure shortcode
+linkTitle: Figure
+description: Insert an HTML figure element into your content using the figure shortcode.
+categories: []
+keywords: []
+---
+
+> [!note]
+> To override Hugo's embedded `figure` shortcode, copy the [source code] to a file with the same name in the `layouts/_shortcodes` directory.
+
+## Example
+
+With this markup:
+
+```text
+{{* figure
+ src="/images/examples/zion-national-park.jpg"
+ alt="A photograph of Zion National Park"
+ link="https://www.nps.gov/zion/index.htm"
+ caption="Zion National Park"
+ class="ma0 w-75"
+*/>}}
+```
+
+Hugo renders this HTML:
+
+```html
+
+
+
+
+
+
Zion National Park
+
+
+```
+
+Which looks like this in your browser:
+
+{{< figure
+ src="/images/examples/zion-national-park.jpg"
+ alt="A photograph of Zion National Park"
+ link="https://www.nps.gov/zion/index.htm"
+ caption="Zion National Park"
+ class="ma0 w-75"
+>}}
+
+## Arguments
+
+src
+: (`string`) The `src` attribute of the `img` element. Typically this is a [page resource](g) or a [global resource](g).
+
+alt
+: (`string`) The `alt` attribute of the `img` element.
+
+width
+: (`int`) The `width` attribute of the `img` element.
+
+height
+: (`int`) The `height` attribute of the `img` element.
+
+loading
+: (`string`) The `loading` attribute of the `img` element.
+
+class
+: (`string`) The `class` attribute of the `figure` element.
+
+link
+: (`string`) The `href` attribute of the anchor element that wraps the `img` element.
+
+target
+: (`string`) The `target` attribute of the anchor element that wraps the `img` element.
+
+rel
+: (`rel`) The `rel` attribute of the anchor element that wraps the `img` element.
+
+title
+: (`string`) Within the `figurecaption` element, the title is at the top, wrapped within an `h4` element.
+
+caption
+: (`string`) Within the `figurecaption` element, the caption is at the bottom and may contain plain text or markdown.
+
+attr
+: (`string`) Within the `figurecaption` element, the attribution appears next to the caption and may contain plain text or markdown.
+
+attrlink
+: (`string`) The `href` attribute of the anchor element that wraps the attribution.
+
+## Image location
+
+The `figure` shortcode resolves internal Markdown destinations by looking for a matching [page resource](g), falling back to a matching [global resource](g). Remote destinations are passed through, and the render hook will not throw an error or warning if unable to resolve a destination.
+
+You must place global resources in the `assets` directory. If you have placed your resources in the `static` directory, and you are unable or unwilling to move them, you must mount the `static` directory to the `assets` directory by including both of these entries in your project configuration:
+
+{{< code-toggle file=hugo >}}
+[[module.mounts]]
+source = 'assets'
+target = 'assets'
+
+[[module.mounts]]
+source = 'static'
+target = 'assets'
+{{< /code-toggle >}}
+
+[source code]: <{{% eturl figure %}}>
diff --git a/docs/content/en/shortcodes/highlight.md b/docs/content/en/shortcodes/highlight.md
new file mode 100755
index 00000000000..d427da7cc4a
--- /dev/null
+++ b/docs/content/en/shortcodes/highlight.md
@@ -0,0 +1,107 @@
+---
+title: Highlight shortcode
+linkTitle: Highlight
+description: Insert syntax-highlighted code into your content using the highlight shortcode.
+categories: []
+keywords: [highlight]
+---
+
+> [!note]
+> To override Hugo's embedded `highlight` shortcode, copy the [source code] to a file with the same name in the `layouts/_shortcodes` directory.
+
+> [!note]
+> With the Markdown [content format], the `highlight` shortcode is rarely needed because, by default, Hugo automatically applies syntax highlighting to fenced code blocks.
+>
+> The primary use case for the `highlight` shortcode in Markdown is to apply syntax highlighting to inline code snippets.
+
+The `highlight` shortcode calls the [`transform.Highlight`] function which uses the [Chroma] syntax highlighter, supporting over 200 languages with more than 40 [highlighting styles].
+
+## Arguments
+
+The `highlight` shortcode takes three arguments.
+
+```text
+{{* highlight LANG OPTIONS */>}}
+CODE
+{{* /highlight */>}}
+```
+
+CODE
+: (`string`) The code to highlight.
+
+LANG
+: (`string`) The language of the code to highlight. Choose from one of the [supported languages]. This value is case-insensitive.
+
+OPTIONS
+: (`string`) Zero or more space-separated key-value pairs wrapped in quotation marks. Set default values for each option in your [project configuration]. The key names are case-insensitive.
+
+## Example
+
+```text
+{{* highlight go "linenos=inline, hl_lines=3 6-8, style=emacs" */>}}
+package main
+
+import "fmt"
+
+func main() {
+ for i := 0; i < 3; i++ {
+ fmt.Println("Value of i:", i)
+ }
+}
+{{* /highlight */>}}
+```
+
+Hugo renders this to:
+
+{{< highlight go "linenos=inline, hl_Lines=3 6-8, noClasses=true" >}}
+package main
+
+import "fmt"
+
+func main() {
+ for i := 0; i < 3; i++ {
+ fmt.Println("Value of i:", i)
+ }
+}
+{{< /highlight >}}
+
+You can also use the `highlight` shortcode for inline code snippets:
+
+```text
+This is some {{* highlight go "hl_inline=true" */>}}fmt.Println("inline"){{* /highlight */>}} code.
+```
+
+Hugo renders this to:
+
+This is some {{< highlight go "hl_inline=true, noClasses=true" >}}fmt.Println("inline"){{< /highlight >}} code.
+
+Given the verbosity of the example above, if you need to frequently highlight inline code snippets, create your own shortcode using a shorter name with preset options.
+
+```go-html-template {file="layouts/_shortcodes/hl.html"}
+{{ $code := .Inner | strings.TrimSpace }}
+{{ $lang := or (.Get 0) "go" }}
+{{ $opts := dict "hl_inline" true "noClasses" true }}
+{{ transform.Highlight $code $lang $opts }}
+```
+
+```text
+This is some {{* hl */>}}fmt.Println("inline"){{* /hl */>}} code.
+```
+
+Hugo renders this to:
+
+This is some {{< hl >}}fmt.Println("inline"){{< /hl >}} code.
+
+## Options
+
+Pass the options when calling the shortcode. You can set their default values in your [project configuration].
+
+{{% include "_common/syntax-highlighting-options.md" %}}
+
+[`transform.Highlight`]: /functions/transform/highlight/
+[Chroma]: https://github.com/alecthomas/chroma
+[content format]: /content-management/formats/
+[highlighting styles]: /quick-reference/syntax-highlighting-styles/
+[project configuration]: /configuration/markup/#highlight
+[source code]: <{{% eturl highlight %}}>
+[supported languages]: /content-management/syntax-highlighting/#languages
diff --git a/docs/content/en/shortcodes/instagram.md b/docs/content/en/shortcodes/instagram.md
new file mode 100755
index 00000000000..f3223e74289
--- /dev/null
+++ b/docs/content/en/shortcodes/instagram.md
@@ -0,0 +1,42 @@
+---
+title: Instagram shortcode
+linkTitle: Instagram
+description: Embed an Instagram post in your content using the instagram shortcode.
+categories: []
+keywords: []
+---
+
+> [!note]
+> To override Hugo's embedded `instagram` shortcode, copy the [source code] to a file with the same name in the `layouts/_shortcodes` directory.
+
+## Example
+
+To display an Instagram post with this URL:
+
+```text
+https://www.instagram.com/p/CxOWiQNP2MO/
+```
+
+Include this in your Markdown:
+
+```text
+{{* instagram CxOWiQNP2MO */>}}
+```
+
+Huge renders this to:
+
+{{< instagram CxOWiQNP2MO >}}
+
+## Privacy
+
+Adjust the relevant privacy settings in your project configuration.
+
+{{< code-toggle config=privacy.instagram />}}
+
+disable
+: (`bool`) Whether to disable the shortcode. Default is `false`.
+
+simple
+: (`bool`) Whether to enable simple mode for image card generation. If `true`, Hugo creates a static card without JavaScript. This mode only supports image cards, and the image is fetched directly from Instagram's servers. Default is `false`.
+
+[source code]: <{{% eturl instagram %}}>
diff --git a/docs/content/en/shortcodes/param.md b/docs/content/en/shortcodes/param.md
new file mode 100755
index 00000000000..51310a2a100
--- /dev/null
+++ b/docs/content/en/shortcodes/param.md
@@ -0,0 +1,38 @@
+---
+title: Param shortcode
+linkTitle: Param
+description: Insert a parameter from front matter or your project configuration into your content using the param shortcode.
+categories: []
+keywords: []
+---
+
+> [!note]
+> To override Hugo's embedded `param` shortcode, copy the [source code] to a file with the same name in the `layouts/_shortcodes` directory.
+
+The `param` shortcode renders a parameter from front matter, falling back to a site parameter of the same name. The shortcode throws an error if the parameter does not exist.
+
+```text {file="content/example.md"}
+---
+title: Example
+date: 2025-01-15T23:29:46-08:00
+params:
+ color: red
+ size: medium
+---
+
+We found a {{%/* param "color" */%}} shirt.
+```
+
+Hugo renders this to:
+
+```html
+
We found a red shirt.
+```
+
+Access nested values by [chaining](g) the [identifiers](g):
+
+```text
+{{%/* param my.nested.param */%}}
+```
+
+[source code]: <{{% eturl param %}}>
diff --git a/docs/content/en/shortcodes/qr.md b/docs/content/en/shortcodes/qr.md
new file mode 100755
index 00000000000..d83be2a6f8c
--- /dev/null
+++ b/docs/content/en/shortcodes/qr.md
@@ -0,0 +1,107 @@
+---
+title: QR shortcode
+linkTitle: QR
+description: Insert a QR code into your content using the qr shortcode.
+categories: []
+keywords: []
+---
+
+{{< new-in 0.141.0 />}}
+
+> [!note]
+> To override Hugo's embedded `qr` shortcode, copy the [source code] to a file with the same name in the `layouts/_shortcodes` directory.
+
+The `qr` shortcode encodes the given text into a [QR code] using the specified options and renders the resulting image.
+
+Internally this shortcode calls the `images.QR` function. Please read the [related documentation] for implementation details and guidance.
+
+## Examples
+
+Use the self-closing syntax to pass the text as an argument:
+
+```text
+{{* qr text="https://gohugo.io" /*/>}}
+```
+
+Or insert the text between the opening and closing tags:
+
+```text
+{{* qr */>}}
+https://gohugo.io
+{{* /qr */>}}
+```
+
+Both of the above produce this image:
+
+{{< qr text="https://gohugo.io" class="qrcode" targetDir="images/qr" />}}
+
+To create a QR code for a phone number:
+
+```text
+{{* qr text="tel:+12065550101" /*/>}}
+```
+
+{{< qr text="tel:+12065550101" class="qrcode" targetDir="images/qr" />}}
+
+To create a QR code containing contact information in the [vCard] format:
+
+```text
+{{* qr level="low" scale=2 alt="QR code of vCard for John Smith" */>}}
+BEGIN:VCARD
+VERSION:2.1
+N;CHARSET=UTF-8:Smith;John;R.;Dr.;PhD
+FN;CHARSET=UTF-8:Dr. John R. Smith, PhD.
+ORG;CHARSET=UTF-8:ABC Widgets
+TITLE;CHARSET=UTF-8:Vice President Engineering
+TEL;TYPE=WORK:+12065550101
+EMAIL;TYPE=WORK:jsmith@example.org
+END:VCARD
+{{* /qr */>}}
+```
+
+{{< qr level="low" scale=2 alt="QR code of vCard for John Smith" class="qrcode" targetDir="images/qr" >}}
+BEGIN:VCARD
+VERSION:2.1
+N;CHARSET=UTF-8:Smith;John;R.;Dr.;PhD
+FN;CHARSET=UTF-8:Dr. John R. Smith, PhD.
+ORG;CHARSET=UTF-8:ABC Widgets
+TITLE;CHARSET=UTF-8:Vice President Engineering
+TEL;TYPE=WORK:+12065550101
+EMAIL;TYPE=WORK:jsmith@example.org
+END:VCARD
+{{< /qr >}}
+
+## Arguments
+
+text
+: (`string`) The text to encode, falling back to the text between the opening and closing shortcode tags.
+
+level
+: (`string`) The error correction level to use when encoding the text, one of `low`, `medium`, `quartile`, or `high`. Default is `medium`.
+
+scale
+: (`int`) The number of image pixels per QR code module. Must be greater than or equal to 2. Default is `4`.
+
+targetDir
+: (`string`) The subdirectory within the [`publishDir`] where Hugo will place the generated image.
+
+alt
+: (`string`) The `alt` attribute of the `img` element.
+
+class
+: (`string`) The `class` attribute of the `img` element.
+
+id
+: (`string`) The `id` attribute of the `img` element.
+
+loading
+: (`string`) The `loading` attribute of the `img` element, either `eager` or `lazy`.
+
+title
+: (`string`) The `title` attribute of the `img` element.
+
+[`publishDir`]: /configuration/all/#publishdir
+[QR code]: https://en.wikipedia.org/wiki/QR_code
+[related documentation]: /functions/images/qr/
+[source code]: <{{% eturl qr %}}>
+[vCard]:
diff --git a/docs/content/en/shortcodes/ref.md b/docs/content/en/shortcodes/ref.md
new file mode 100755
index 00000000000..29c4b0bbee0
--- /dev/null
+++ b/docs/content/en/shortcodes/ref.md
@@ -0,0 +1,65 @@
+---
+title: Ref shortcode
+linkTitle: Ref
+description: Insert a permalink to the given page reference using the ref shortcode.
+categories: []
+keywords: []
+---
+
+> [!note]
+> To override Hugo's embedded `ref` shortcode, copy the [source code] to a file with the same name in the `layouts/_shortcodes` directory.
+
+> [!note]
+> When working with Markdown this shortcode is obsolete. Instead, to properly resolve Markdown link destinations, use the [embedded link render hook] or create your own.
+>
+> In its default configuration, Hugo automatically uses the embedded link render hook for multilingual single-host projects, specifically when the [duplication of shared page resources] feature is disabled. This is the default behavior for such projects. If custom link render hooks are defined by your project, modules, or themes, these will be used instead.
+>
+> You can also configure Hugo to `always` use the embedded link render hook, use it only as a `fallback`, or `never` use it. See [details](/configuration/markup/#renderhookslinkuseembedded).
+
+## Usage
+
+The `ref` shortcode accepts either a single positional argument (the path) or one or more named arguments, as listed below.
+
+## Arguments
+
+{{% include "_common/ref-and-relref-options.md" %}}
+
+## Examples
+
+The `ref` shortcode typically provides the destination for a Markdown link.
+
+> [!note]
+> Always use [Markdown notation] notation when calling this shortcode.
+
+The following examples show the rendered output for a page on the English version of the site:
+
+```md
+[Link A]({{%/* ref "/books/book-1" */%}})
+
+[Link B]({{%/* ref path="/books/book-1" */%}})
+
+[Link C]({{%/* ref path="/books/book-1" lang="de" */%}})
+
+[Link D]({{%/* ref path="/books/book-1" lang="de" outputFormat="json" */%}})
+```
+
+Rendered:
+
+```html
+Link A
+
+Link B
+
+Link C
+
+Link D
+```
+
+## Error handling
+
+{{% include "_common/ref-and-relref-error-handling.md" %}}
+
+[duplication of shared page resources]: /configuration/markup/#duplicateresourcefiles
+[embedded link render hook]: /render-hooks/links/#embedded
+[Markdown notation]: /content-management/shortcodes/#notation
+[source code]: <{{% eturl relref %}}>
diff --git a/docs/content/en/shortcodes/relref.md b/docs/content/en/shortcodes/relref.md
new file mode 100755
index 00000000000..1045ee91be1
--- /dev/null
+++ b/docs/content/en/shortcodes/relref.md
@@ -0,0 +1,65 @@
+---
+title: Relref shortcode
+linkTitle: Relref
+description: Insert a relative permalink to the given page reference using the relref shortcode.
+categories: []
+keywords: []
+---
+
+> [!note]
+> To override Hugo's embedded `relref` shortcode, copy the [source code] to a file with the same name in the `layouts/_shortcodes` directory.
+
+> [!note]
+> When working with Markdown this shortcode is obsolete. Instead, to properly resolve Markdown link destinations, use the [embedded link render hook] or create your own.
+>
+> In its default configuration, Hugo automatically uses the embedded link render hook for multilingual single-host projects, specifically when the [duplication of shared page resources] feature is disabled. This is the default behavior for such projects. If custom link render hooks are defined by your project, modules, or themes, these will be used instead.
+>
+> You can also configure Hugo to `always` use the embedded link render hook, use it only as a `fallback`, or `never` use it. See [details](/configuration/markup/#renderhookslinkuseembedded).
+
+## Usage
+
+The `relref` shortcode accepts either a single positional argument (the path) or one or more named arguments, as listed below.
+
+## Arguments
+
+{{% include "_common/ref-and-relref-options.md" %}}
+
+## Examples
+
+The `relref` shortcode typically provides the destination for a Markdown link.
+
+> [!note]
+> Always use [Markdown notation] notation when calling this shortcode.
+
+The following examples show the rendered output for a page on the English version of the site:
+
+```md
+[Link A]({{%/* relref "/books/book-1" */%}})
+
+[Link B]({{%/* relref path="/books/book-1" */%}})
+
+[Link C]({{%/* relref path="/books/book-1" lang="de" */%}})
+
+[Link D]({{%/* relref path="/books/book-1" lang="de" outputFormat="json" */%}})
+```
+
+Rendered:
+
+```html
+Link A
+
+Link B
+
+Link C
+
+Link D
+```
+
+## Error handling
+
+{{% include "_common/ref-and-relref-error-handling.md" %}}
+
+[duplication of shared page resources]: /configuration/markup/#duplicateresourcefiles
+[embedded link render hook]: /render-hooks/links/#embedded
+[Markdown notation]: /content-management/shortcodes/#notation
+[source code]: <{{% eturl relref %}}>
diff --git a/docs/content/en/shortcodes/vimeo.md b/docs/content/en/shortcodes/vimeo.md
new file mode 100755
index 00000000000..7bca4b86e78
--- /dev/null
+++ b/docs/content/en/shortcodes/vimeo.md
@@ -0,0 +1,73 @@
+---
+title: Vimeo shortcode
+linkTitle: Vimeo
+description: Embed a Vimeo video in your content using the vimeo shortcode.
+categories: []
+keywords: []
+---
+
+> [!note]
+> To override Hugo's embedded `vimeo` shortcode, copy the [source code] to a file with the same name in the `layouts/_shortcodes` directory.
+
+## Example
+
+To display a Vimeo video with this URL:
+
+```text
+https://vimeo.com/19899678
+```
+
+Include this in your Markdown:
+
+```text
+{{* vimeo 19899678 */>}}
+```
+
+Hugo renders this to:
+
+{{< vimeo 19899678 >}}
+
+## Arguments
+
+id
+: (string) The video `id`. Optional if the `id` is the first and only positional argument.
+
+allowFullScreen
+: {{< new-in 0.146.0 />}}
+: (`bool`) Whether the `iframe` element can activate full screen mode. Default is `true`.
+
+class
+: (`string`) The `class` attribute of the wrapping `div` element. Adding one or more CSS classes disables inline styling.
+
+loading
+: {{< new-in 0.146.0 />}}
+: (`string`) The loading attribute of the `iframe` element, either `eager` or `lazy`. Default is `eager`.
+
+title
+: (`string`) The `title` attribute of the `iframe` element.
+
+Here's an example using some of the available arguments:
+
+```text
+{{* vimeo id=19899678 allowFullScreen=false loading=lazy */>}}
+```
+
+## Privacy
+
+Adjust the relevant privacy settings in your project configuration.
+
+{{< code-toggle config=privacy.vimeo />}}
+
+disable
+: (`bool`) Whether to disable the shortcode. Default is `false`.
+
+enableDNT
+: (`bool`) Whether to block the Vimeo player from tracking session data and analytics. Default is `false`.
+
+simple
+: (`bool`) Whether to enable simple mode. If `true`, the video thumbnail is fetched from Vimeo and overlaid with a play button. Clicking the thumbnail opens the video in a new Vimeo tab. Default is `false`.
+
+The source code for the simple version of the shortcode is available [in this file].
+
+[in this file]: <{{% eturl vimeo_simple %}}>
+[source code]: <{{% eturl vimeo %}}>
diff --git a/docs/content/en/shortcodes/x.md b/docs/content/en/shortcodes/x.md
new file mode 100755
index 00000000000..1ca87949a6f
--- /dev/null
+++ b/docs/content/en/shortcodes/x.md
@@ -0,0 +1,54 @@
+---
+title: X shortcode
+linkTitle: X
+description: Embed an X post in your content using the x shortcode.
+categories: []
+keywords: []
+---
+
+{{< new-in 0.141.0 />}}
+
+> [!note]
+> To override Hugo's embedded `x` shortcode, copy the [source code] to a file with the same name in the `layouts/_shortcodes` directory.
+
+## Example
+
+To display an X post with this URL:
+
+```txt
+https://x.com/SanDiegoZoo/status/1453110110599868418
+```
+
+Include this in your Markdown:
+
+```text
+{{* x user="SanDiegoZoo" id="1453110110599868418" */>}}
+```
+
+Rendered:
+
+{{< x user="SanDiegoZoo" id="1453110110599868418" >}}
+
+## Privacy
+
+Adjust the relevant privacy settings in your project configuration.
+
+{{< code-toggle config=privacy.x />}}
+
+disable
+: (`bool`) Whether to disable the shortcode. Default is `false`.
+
+enableDNT
+: (`bool`) Whether to prevent X from using post and embedded page data for personalized suggestions and ads. Default is `false`.
+
+simple
+: (`bool`) Whether to enable simple mode. If `true`, Hugo builds a static version of the of the post without JavaScript. Default is `false`.
+
+The source code for the simple version of the shortcode is available [in this file].
+
+If you enable simple mode you may want to disable the hardcoded inline styles by setting `disableInlineCSS` to `true` in your project configuration. The default value for this setting is `false`.
+
+{{< code-toggle config=services.x />}}
+
+[in this file]: <{{% eturl x_simple %}}>
+[source code]: <{{% eturl x %}}>
diff --git a/docs/content/en/shortcodes/youtube.md b/docs/content/en/shortcodes/youtube.md
new file mode 100755
index 00000000000..c76c5f3083d
--- /dev/null
+++ b/docs/content/en/shortcodes/youtube.md
@@ -0,0 +1,83 @@
+---
+title: YouTube shortcode
+linkTitle: YouTube
+description: Embed a YouTube video in your content using the youtube shortcode.
+categories: []
+keywords: []
+---
+
+> [!note]
+> To override Hugo's embedded `youtube` shortcode, copy the [source code] to a file with the same name in the `layouts/_shortcodes` directory.
+
+## Example
+
+To display a YouTube video with this URL:
+
+```text
+https://www.youtube.com/watch?v=0RKpf3rK57I
+```
+
+Include this in your Markdown:
+
+```texts
+{{* youtube 0RKpf3rK57I */>}}
+```
+
+Hugo renders this to:
+
+{{< youtube 0RKpf3rK57I >}}
+
+## Arguments
+
+id
+: (`string`) The video `id`. Optional if the `id` is the first and only positional argument.
+
+allowFullScreen
+: (`bool`) Whether the `iframe` element can activate full screen mode. Default is `true`.
+
+autoplay
+: (`bool`) Whether to automatically play the video. Forces `mute` to `true`. Default is `false`.
+
+class
+: (`string`) The `class` attribute of the wrapping `div` element. When specified, removes the `style` attributes from the `iframe` element and its wrapping `div` element.
+
+controls
+: (`bool`) Whether to display the video controls. Default is `true`.
+
+end
+: (`int`) The time, measured in seconds from the start of the video, when the player should stop playing the video.
+
+loading
+: (`string`) The loading attribute of the `iframe` element, either `eager` or `lazy`. Default is `eager`.
+
+loop
+: (`bool`) Whether to indefinitely repeat the video. Ignores the `start` and `end` arguments after the first play. Default is `false`.
+
+mute
+: (`bool`) Whether to mute the video. Always `true` when `autoplay` is `true`. Default is `false`.
+
+start
+: (`int`) The time, measured in seconds from the start of the video, when the player should start playing the video.
+
+title
+: (`string`) The `title` attribute of the `iframe` element. Defaults to "YouTube video".
+
+Here's an example using some of the available arguments:
+
+```text
+{{* youtube id=0RKpf3rK57I start=30 end=60 loading=lazy */>}}
+```
+
+## Privacy
+
+Adjust the relevant privacy settings in your project configuration.
+
+{{< code-toggle config=privacy.youTube />}}
+
+disable
+: (`bool`) Whether to disable the shortcode. Default is `false`.
+
+privacyEnhanced
+: (`bool`) Whether to block YouTube from storing information about visitors on your website unless the user plays the embedded video. Default is `false`.
+
+[source code]: {{% eturl youtube %}}
diff --git a/docs/content/en/showcase/1password-support/bio.md b/docs/content/en/showcase/1password-support/bio.md
index 9187908d987..32d299bd4d8 100644
--- a/docs/content/en/showcase/1password-support/bio.md
+++ b/docs/content/en/showcase/1password-support/bio.md
@@ -1,5 +1,3 @@
-
**1Password** is a password manager that keeps you safe online. It protects your secure information behind the one password only you know.
-
The [1Password Support](https://support.1password.com/) website was built from scratch with **Hugo** and enhanced with **React** and **Elasticsearch** to give us the best of both worlds: The simplicity and performance of a static site, with the richness of a hosted web app.
diff --git a/docs/content/en/showcase/1password-support/index.md b/docs/content/en/showcase/1password-support/index.md
index 2bcbff3fd17..b75e54eccee 100644
--- a/docs/content/en/showcase/1password-support/index.md
+++ b/docs/content/en/showcase/1password-support/index.md
@@ -1,17 +1,15 @@
---
-
title: 1Password Support
date: 2018-02-22
-description: "Showcase: \"Compiles 400 pages in five languages in the blink of an eye.\""
+description: 'Showcase: "Compiles 400 pages in five languages in the blink of an eye."'
siteURL: https://support.1password.com/
byline: "[Mitch Cohen](https://github.com/mitchchn), Documentation Team Lead"
aliases: [/showcase/1password/]
-
---
At 1Password, we used to go through a different documentation platform every month: blog engines, ebooks, wikis, site generators written in Ruby and JavaScript. Each was inadequate in its own special way. Then we found **Hugo**. We made one last switch, and we're glad we did.
-### Not all static site generators are created equal
+## Not all static site generators are created equal
Finding a tool that will make your customers, writers, designers, _and_ DevOps team happy is no easy task, but we managed it with Hugo:
@@ -21,7 +19,7 @@ Finding a tool that will make your customers, writers, designers, _and_ DevOps t
**Hugo is FAST**. Our previous static site generator took nearly a minute to compile our (then much smaller) site. Developers might be used to this, but it wasn't cutting it for writers who wanted to see live previews of their work. Hugo did the same job in milliseconds, and to this day compiles 400 pages in five languages in the blink of an eye.
-**Hugo is flexible**. Thanks to Hugo's content and layout system, we were able to preserve our existing file and folder structure and port our entire production site in a few days. We could then create new content types that weren't possible before, like these snazzy [showcases](https://support.1password.com/explore/extension/).
+**Hugo is flexible**. Thanks to Hugo's content and layout system, we were able to preserve our existing file and directory structure and port our entire production site in a few days. We could then create new content types that weren't possible before, like these snazzy [showcases](https://support.1password.com/explore/extension/).
**Hugo is great for writers**. Our documentation team was already comfortable with Markdown and Git and could start creating content for Hugo with zero downtime. Once we added shortcodes, our writers were able to dress up articles with features like [platform boxes](https://support.1password.com/get-the-apps/) with just a bit of new syntax.
@@ -31,9 +29,9 @@ Finding a tool that will make your customers, writers, designers, _and_ DevOps t
### Tech specs
-* [1Password Support](https://support.1password.com) uses Hugo with a custom theme. It shares styles and some template code with [1Password.com](https://1password.com), which we also moved to Hugo in 2016.
-* Code and articles live in a private GitHub repository, which is deployed to a static content server using Git hooks.
-* Writers build and preview the site on their computers and contribute content using pull requests.
- * We use Hugo's [multilingual support](/content-management/multilingual/) to build the site in English, Spanish, French, Italian, German, and Russian. With the help of Hugo, 1Password Support became our very first site in multiple languages.
-* Our [contact form](https://support.1password.com/contact) is a single-page React app. We were able to integrate it with Hugo seamlessly thanks to its support for static files.
-* The one part of the support site which is not static is our search engine, which we developed with Elasticsearch and host on AWS.
+- [1Password Support](https://support.1password.com) uses Hugo with a custom theme. It shares styles and some template code with [1Password.com](https://1password.com), which we also moved to Hugo in 2016.
+- Code and articles live in a private GitHub repository, which is deployed to a static content server using Git hooks.
+- Writers build and preview the site on their computers and contribute content using pull requests.
+- We use Hugo's [multilingual support](/content-management/multilingual/) to build the site in English, Spanish, French, Italian, German, and Russian. With the help of Hugo, 1Password Support became our very first site in multiple languages.
+- Our [contact form](https://support.1password.com/contact) is a single-page React app. We were able to integrate it with Hugo seamlessly thanks to its support for static files.
+- The one part of the support site which is not static is our search engine, which we developed with Elasticsearch and host on AWS.
diff --git a/docs/content/en/showcase/_index.md b/docs/content/en/showcase/_index.md
new file mode 100644
index 00000000000..e618e81041b
--- /dev/null
+++ b/docs/content/en/showcase/_index.md
@@ -0,0 +1,7 @@
+---
+title: Showcases
+cascade:
+ build:
+ render: never
+ list: never
+---
diff --git a/docs/content/en/showcase/_template/bio.md b/docs/content/en/showcase/_template/bio.md
new file mode 100644
index 00000000000..5ea3896174b
--- /dev/null
+++ b/docs/content/en/showcase/_template/bio.md
@@ -0,0 +1,6 @@
+Add some **general info** about Myshowcase here.
+
+The site is built by:
+
+- [Person 1](https://example.org)
+- [Person 1](https://example.org)
diff --git a/docs/archetypes/showcase/featured.png b/docs/content/en/showcase/_template/featured.png
similarity index 100%
rename from docs/archetypes/showcase/featured.png
rename to docs/content/en/showcase/_template/featured.png
diff --git a/docs/content/en/showcase/_template/index.md b/docs/content/en/showcase/_template/index.md
new file mode 100644
index 00000000000..6a115961b1a
--- /dev/null
+++ b/docs/content/en/showcase/_template/index.md
@@ -0,0 +1,31 @@
+---
+title: Myshowcase
+date:
+draft: true
+description: A short description of this page.
+# The URL to the site on the internet.
+siteURL: https://gohugo.io/
+# Link to the site's Hugo source code if public and you can/want to share.
+# Remove or leave blank if not needed/wanted.
+siteSource: https://github.com/gohugoio/hugoDocs
+# Add credit to the article author. Leave blank or remove if not needed/wanted.
+byline: '[bep](https://github.com/bep), Hugo Lead'
+---
+
+To complete this showcase:
+
+1. Write the story about your site in this file.
+1. Add a summary to the `bio.md` file in this directory.
+1. Replace the `featured-template.png` with a screenshot of your site. You can rename it, but it must contain the word `featured`.
+1. Create a new pull request in
+
+The content of this bundle explained:
+
+index.md
+: The main content file. Fill in required front matter metadata and write your story. I does not have to be a novel. It can even be self-promotional, but it should include Hugo in some form.
+
+bio.md
+: A short summary of the website. Site credits (who built it) fits nicely here.
+
+featured.png
+: A reasonably sized screenshot of your website. It can be named anything, but the name must start with "featured". The sample image is `1500x750` (2:1 aspect ratio).
diff --git a/docs/content/en/showcase/alora-labs/bio.md b/docs/content/en/showcase/alora-labs/bio.md
new file mode 100644
index 00000000000..f14a90b753f
--- /dev/null
+++ b/docs/content/en/showcase/alora-labs/bio.md
@@ -0,0 +1,3 @@
+**Alora Labs** is a product development consultancy headquartered in Toronto, Canada.
+
+We help companies build software and IoT products and were recently recognized as one of the [**top IoT development firms**](https://aloralabs.com/insights/alora-labs-receives-clutch-2021-top-iot-agency-award) in Toronto.
diff --git a/docs/content/en/showcase/alora-labs/featured.png b/docs/content/en/showcase/alora-labs/featured.png
new file mode 100644
index 00000000000..b8e1f302b68
Binary files /dev/null and b/docs/content/en/showcase/alora-labs/featured.png differ
diff --git a/docs/content/en/showcase/alora-labs/index.md b/docs/content/en/showcase/alora-labs/index.md
new file mode 100644
index 00000000000..5e676bad3be
--- /dev/null
+++ b/docs/content/en/showcase/alora-labs/index.md
@@ -0,0 +1,18 @@
+---
+title: Alora Labs
+date: 2021-05-27
+description: 'Showcase: "Making performant websites accessible for everyone."'
+siteURL: https://aloralabs.com/
+siteSource: https://github.com/aloralabs/homepage
+aliases: [/showcase/aloralabs/]
+---
+
+At Alora Labs we always have an eye open for new tools and technology that we can utilize to the benefit of our customers or internal projects like our website.
+
+The previous iteration of our site was built with Jekyll, which served us well at first. However as time went on, we became frustrated with the number of dependencies we had to rely on, that would often break at the most inconvenient times.
+
+Hugo was a breath of fresh air in this regard, a single binary that works equally well on Windows as it did on macOS or Linux. We no longer need additional tools for image optimization, Sass compilation or JavaScript bundling. Everything just works, and with a substantial performance boost too.
+
+Hugo has become a favorite tool in the tool belt and the foundation for many client projects. We couldn't be happier with the switch and we are optimistic about recommending Hugo for many years to come.
+
+Thank you to the vibrant community and talented development team for all the hard work in making Hugo a success. As excellent as Hugo is now, we cannot wait to see what the release notes have in store for us next.
diff --git a/docs/content/en/showcase/ampio-help/bio.md b/docs/content/en/showcase/ampio-help/bio.md
new file mode 100644
index 00000000000..bc0a9a937bd
--- /dev/null
+++ b/docs/content/en/showcase/ampio-help/bio.md
@@ -0,0 +1,10 @@
+We are Ampio. We design and manufacture a building automation system that provides control, comfort, safety and reliability. Visit [our page](http://ampio.com/) to learn more about our solution!
+
+Ampio Knowledge Base is a service built and maintained with Hugo. It is a self-service support platform for our customers and certified installers. It also contains a complete portfolio of our modules---building blocks of the Ampio building automation system.
+
+The site is built by:
+
+- [@mgetka](https://github.com/mgetka), developer
+- [@SteynAnna](https://github.com/SteynAnna), maintainer
+
+and other members of the Ampio team responsible for content creation.
diff --git a/docs/content/en/showcase/ampio-help/featured.png b/docs/content/en/showcase/ampio-help/featured.png
new file mode 100644
index 00000000000..07974e7f11e
Binary files /dev/null and b/docs/content/en/showcase/ampio-help/featured.png differ
diff --git a/docs/content/en/showcase/ampio-help/index.md b/docs/content/en/showcase/ampio-help/index.md
new file mode 100644
index 00000000000..20e767289e1
--- /dev/null
+++ b/docs/content/en/showcase/ampio-help/index.md
@@ -0,0 +1,76 @@
+---
+title: Ampio Knowledge Base
+date: 2022-10-30
+description: "Knowledge base for the Ampio building automation system."
+siteURL: https://help.ampio.com/
+---
+
+As a company that specializes in highly customizable smart solutions for various industries, Ampio has accumulated a vast amount of knowledge throughout the years. We were on the lookout for a user-friendly platform to impart this knowledge to our clients and installers. Delivering a service that caters to both audiences, scattered around the globe with vastly divergent needs and expectations, was a challenge.
+
+On the one hand, we needed something that would let us educate a client with no technical knowledge about our system in a visually appealing way.
+
+On the other hand, our installers required technical drawings, offline manuals, and a deep dive into highly specialized subjects.
+
+Over and above that, we could not overlook the fact that our internal team of editors and maintainers of the Knowledge Base included non-programmers who had to be able to create content and navigate the architecture of the site just as well as those adept at coding.
+
+We started our journey with the following requirements:
+
+- Ease of contribution
+- Efficient search capabilities
+- The possibility of deployment to simple shared hosting
+- Proper support for multilingualism
+
+## Dark ages of WordPress
+
+With the above-mentioned in mind, we built our first revision of the service in WordPress with a commercial knowledge base plugin. The initial requirements seemed not to be exorbitant, and yet we were surprised to see that only a few of the available solutions covered them. Especially, the case of multilingualism appeared to be particularly neglected across the available products.
+
+The WordPress-based products made big promises: pay some bucks, bootstrap the service in minutes, and forget about all the development troubles. And although those promises could possibly be deliverable on WordPress' end, it was definitely not true for anything more than the most generic deployments. In our case, we were dealing with more and more trade-offs. Plus, the solution was just slow on the simple shared hosting environment that we dedicated to the job.
+
+## Turning point
+
+The turning point was the introduction of a new key requirement---each document was to be downloadable in the PDF format. Such functionality was not available in the plugins we owned, nor did it look like any of the other existing WordPress plugins could fulfill our needs to a satisfactory degree. Nobody in our team was brave enough to add such a functionality to the current stack, so we decided to start from scratch.
+
+On top of that new development, we had to remember another one of our key requirements, namely, that mostly non-programmers were to be responsible for the service maintenance and content creation. Initially, we were leaning towards headless CMS-based solutions, but finally we made a bold move and decided to create a Git-managed Jamstack service and see what happens.
+
+## Hugo to the rescue
+
+Hugo was our first choice of SSG. The multilingualism support was the primary feature that convinced us. Later on, going through the documentation, we continued to discover new exciting features that we didn't even know we needed when we started.
+
+The rich functionalities of WordPress WYSIWYG editors soon turned out to be a curse. It became burdensome to maintain formatting consistency across documents prepared by multiple contributors. When we considered Markdown, we knew that it would give us a lot less flexibility. In our case, it proved to be a blessing in disguise---the constraints imposed by the notation ensured that each document was prepared in the same way. And in the cases where Markdown was not enough, Hugo shortcodes gave us all that we needed to get the results we anticipated.
+
+In terms of PDF generation, we utilized [custom output formats](/configuration/output-formats/) to produce intermediary document representations, which are consumed by our custom tool transforming them to TeX documents, which are finally used to produce PDF files.
+
+Custom output formats were also used to create search indexes. The search functionality is built on the brilliant [TNTSearch](https://github.com/teamtnt/tntsearch) library. The search queries and results are handled by PHP snippets embedded into static documents handled by Hugo.
+
+We even implemented a simple REST API generated by Hugo! We have yet to find something that cannot be achieved with this stack, while in WordPress-based solutions we were struggling with things as simple as defining custom document ordering in one of the categories list views.
+
+When talking about Hugo, we cannot forget about the speed. At the beginning we were not considering it a killer feature, but as our document base grew bigger, we appreciated it more and more. Dry-runs are not so common---most of the time we are working on one of the documents with cache already built during one of the previous Hugo runs. In such a scenario, Hugo rebuilds the site in about a second and we consider it a very good result.
+
+```text
+ | EN | PL
+-------------------+-----+------
+ Pages | 483 | 486
+ Paginator pages | 56 | 55
+ Non-page files | 745 | 749
+ Static files | 917 | 917
+ Processed images | 487 | 490
+ Aliases | 80 | 79
+ Sitemaps | 2 | 1
+ Cleaned | 0 | 0
+
+Total in 1096 ms
+```
+
+## Adaptation among the contributors
+
+Very quickly it became apparent that our initial concerns about the adaptation of the workflow among contributors were grossly exaggerated. Markdown is fairly straightforward and did not cause any trouble for the contributors.
+
+We recommended that our colleagues use Visual Studio Code as a tool for content creation. The project's repository tracks project-scoped configuration of the editor, which includes a set of _tasks_ allowing to run a live server from the GUI level. This is very useful for those who are easily frightened when faced with the mighty terminal.
+
+The basic skills of the Git workflow were also easily acquired. At the end of the day, builds and deployments are fully managed by [CI/CD](g) processes, so the administration of the service drills down to reviewing and accepting merge requests in the Git frontend. As a side effect, we receive a full and clear history of contributions, which is well appreciated by our quality assurance auditors.
+
+We could even say that our experiment spread the love for Git among non-programmers in our organization!
+
+## Summary
+
+Hugo is the best! Definitely give it a try if you are ever faced with a challenge similar to ours. And do not give it a second thought if your service contributors are not too technically inclined---it might still turn out great!
diff --git a/docs/content/en/showcase/bypasscensorship/bio.md b/docs/content/en/showcase/bypasscensorship/bio.md
index 6563e13ca6a..a6c98f9bad6 100644
--- a/docs/content/en/showcase/bypasscensorship/bio.md
+++ b/docs/content/en/showcase/bypasscensorship/bio.md
@@ -2,5 +2,5 @@ Bypass Censorship find and promote tools that provide Internet access to everyon
The site is built by:
-* [Leyla Avsar](https://www.leylaavsar.com/) (designer)
-* [Fredrik Jonsson](https://xdeb.net/) (dev)
\ No newline at end of file
+- [Leyla Avsar](https://www.leylaavsar.com/) (designer)
+- [Fredrik Jonsson](https://xdeb.net/) (dev)
diff --git a/docs/content/en/showcase/bypasscensorship/index.md b/docs/content/en/showcase/bypasscensorship/index.md
index a266797ea04..bd1a072c0de 100644
--- a/docs/content/en/showcase/bypasscensorship/index.md
+++ b/docs/content/en/showcase/bypasscensorship/index.md
@@ -1,10 +1,9 @@
---
title: Bypass Censorship
date: 2019-06-16
-description: "Showcase: Bypass Censorship find and promote tools that provide Internet access to everyone."
+description: 'Showcase: "Bypass Censorship find and promote tools that provide Internet access to everyone."'
siteURL: https://www.bypasscensorship.org/
byline: "[Fredrik Jonsson](https://xdeb.net/), Web developer & Linux sysadmin"
-
---
The British Broadcasting Corporation (BBC) (UK), Deutsche Welle (DW) (Germany), France Médias Monde (FMM) (France), the U.S. Agency for Global Media (USAGM) (US) and the Open Technology Fund (OTF) (US) co-sponsor the Bypass Censorship website.
@@ -21,4 +20,4 @@ It's a simply site, basically one page in seven languages. I had no problems get
Thanks to the design by [Leyla Avsar](https://www.leylaavsar.com/) the site also looks good. I used the [Hugo Zen theme](https://github.com/frjo/hugo-theme-zen) with a few custom templates and the needed CSS.
-The editors can maintain content via [Forestry.io CMS](https://forestry.io/) or directly via Git. Forestry does unfortunately not have multilingual support. All the language versions are in one pile making it harder to find the right file to edit, but it works.
\ No newline at end of file
+The editors can maintain content via [Forestry.io CMS](https://forestry.io/) or directly via Git. Forestry does unfortunately not have multilingual support. All the language versions are in one pile making it harder to find the right file to edit, but it works.
diff --git a/docs/content/en/showcase/digitalgov/bio.md b/docs/content/en/showcase/digitalgov/bio.md
index db3ffafafca..70bb990b95c 100644
--- a/docs/content/en/showcase/digitalgov/bio.md
+++ b/docs/content/en/showcase/digitalgov/bio.md
@@ -1,2 +1 @@
-
**Digital.gov** helps people in the U.S. government deliver better, more accessible digital services through publishing essential guidance, resources, tools, and online events that make it easier for people to design, build, and deliver essential services for the public.
diff --git a/docs/content/en/showcase/digitalgov/featured.png b/docs/content/en/showcase/digitalgov/featured.png
index 5663180f91a..7d065dce912 100644
Binary files a/docs/content/en/showcase/digitalgov/featured.png and b/docs/content/en/showcase/digitalgov/featured.png differ
diff --git a/docs/content/en/showcase/digitalgov/index.md b/docs/content/en/showcase/digitalgov/index.md
index 63f44b64553..7f0584712fd 100644
--- a/docs/content/en/showcase/digitalgov/index.md
+++ b/docs/content/en/showcase/digitalgov/index.md
@@ -1,7 +1,7 @@
---
title: Digital.gov
date: 2020-05-01
-description: "Showcase: \"Guidance on building better digital services in government.\""
+description: 'Showcase: "Guidance on building better digital services in government."'
siteURL: https://digital.gov/
siteSource: https://github.com/gsa/digitalgov.gov
---
@@ -12,17 +12,17 @@ Through collaboration in our communities of practice, Digital.gov is a window in
Digital.gov is built using the [U.S. Web Design System](https://designsystem.digital.gov/) (USWDS) and have followed the [design principles](https://designsystem.digital.gov/maturity-model/) in building out our new site:
-- **Start with real user needs** — We used human-centered design methods to inform our product decisions (like qualitative user research), and gathered feedback from real users. We also continually test our assumptions with small experiments.
-- **Earn trust** —We recognize that trust has to be earned every time. We are including all [required links and content](https://digital.gov/resources/required-web-content-and-links/) on our site, clearly identifying as a government site, building with modern best practices, and using https.
-- **Embrace accessibility** — [Accessibility](https://digital.gov/resources/intro-accessibility/) affects everybody, and we built it into every decision. We’re continually working to conform to Section 508 requirements, use user experience best practices, and support a wide range of devices.
-- **Promote continuity** — We started from shared solutions like USWDS and [Federalist](https://federalist.18f.gov/). We designed our site to clearly identify as a government site by including USWDS’s .gov banner, common colors and patterns, and built with modern best practices.
-- **Listen** — We actively collect user feedback and web metrics. We use the [Digital Analytics Program](https://digital.gov/services/dap/) (DAP) and analyze the data to discover actionable insights. We make small, incremental changes to continuously improve our website by listening to readers and learning from what we hear.
+- **Start with real user needs** — We used human-centered design methods to inform our product decisions (like qualitative user research), and gathered feedback from real users. We also continually test our assumptions with small experiments.
+- **Earn trust** —We recognize that trust has to be earned every time. We are including all [required links and content](https://digital.gov/resources/required-web-content-and-links/) on our site, clearly identifying as a government site, building with modern best practices, and using HTTPS.
+- **Embrace accessibility** — [Accessibility](https://digital.gov/resources/intro-accessibility/) affects everybody, and we built it into every decision. We're continually working to conform to Section 508 requirements, use user experience best practices, and support a wide range of devices.
+- **Promote continuity** — We started from shared solutions like USWDS and [Federalist](https://federalist.18f.gov/). We designed our site to clearly identify as a government site by including USWDS's .gov banner, common colors and patterns, and built with modern best practices.
+- **Listen** — We actively collect user feedback and web metrics. We use the [Digital Analytics Program](https://digital.gov/services/dap/) (DAP) and analyze the data to discover actionable insights. We make small, incremental changes to continuously improve our website by listening to readers and learning from what we hear.
_More on the [USWDS maturity model »](https://designsystem.digital.gov/maturity-model/)_
-## Open Tools
+## Open tools
-We didn’t start from scratch. We built and designed the Digital.gov using many of the open-source tools and services that we develop for government here in the [Technology Transformation Services](https://www.gsa.gov/tts/) (TTS).
+We didn't start from scratch. We built and designed the Digital.gov using many of the open-source tools and services that we develop for government here in the [Technology Transformation Services](https://www.gsa.gov/tts/) (TTS).
Using services that make it possible to design, build, and iterate quickly are essential to modern web design and development, which is why [Federalist](https://federalist.18f.gov/) and the [U.S. Web Design System](https://designsystem.digital.gov/) are such a great combination.
@@ -34,7 +34,7 @@ At the moment, it takes around `32 seconds` to build close to `~10,000` pages!
Take a look:
-```bash
+```text
| EN
-------------------+-------
@@ -48,19 +48,17 @@ Take a look:
Cleaned | 0
Built in 32.427 seconds
-
```
In addition to Hugo, we are proudly using a number of other tools and services, all built by government are free to use:
-- [Federalist](https://federalist.18f.gov/)
-- [Search.gov](https://www.search.gov/) — A free, hosted search platform for federal websites.
-- [Cloud.gov](https://www.cloud.gov/) — helps teams build, run, and authorize cloud-ready or legacy government systems quickly and cheaply.
-- [Federal CrowdSource Mobile Testing Program](https://digital.gov/services/mobile-application-testing-program/) — Free mobile compatibility testing by feds, for feds.
-- [Digital Analytics Program](https://digital.gov/services/dap/) (DAP) — A free analytics tool for measuring digital services in the federal government
-- [Section508.gov](https://www.section508.gov/) and [PlainLanguage.gov](https://www.plainlanguage.gov/) resources
-- [API.data.gov](https://api.data.gov/) — a free API management service for federal agencies
-- [U.S. Digital Registry](https://digital.gov/services/u-s-digital-registry/) — A resource for confirming the official status of government social media accounts, mobile apps, and mobile websites.
-
+- [Federalist](https://federalist.18f.gov/)
+- [Search.gov](https://www.search.gov/) — A free, hosted search platform for federal websites.
+- [Cloud.gov](https://www.cloud.gov/) — helps teams build, run, and authorize cloud-ready or legacy government systems quickly and cheaply.
+- [Federal CrowdSource Mobile Testing Program](https://digital.gov/services/service_mobile-testing-program/) — Free mobile compatibility testing by feds, for feds.
+- [Digital Analytics Program](https://digital.gov/services/dap/) (DAP) — A free analytics tool for measuring digital services in the federal government
+- [Section508.gov](https://www.section508.gov/) and [PlainLanguage.gov](https://www.plainlanguage.gov/) resources
+- [API.data.gov](https://api.data.gov/) — a free API management service for federal agencies
+- [U.S. Digital Registry](https://digital.gov/services/u-s-digital-registry/) — A resource for confirming the official status of government social media accounts, mobile apps, and mobile websites.
**Questions or feedback?** [Submit an issue](https://github.com/GSA/digitalgov.gov/issues) or send us an email to [digitalgov@gsa.gov](mailto:digitalgov@gsa.gov) :heart:
diff --git a/docs/content/en/showcase/fireship/bio.md b/docs/content/en/showcase/fireship/bio.md
index faf739bfab5..2a5639aa7c0 100644
--- a/docs/content/en/showcase/fireship/bio.md
+++ b/docs/content/en/showcase/fireship/bio.md
@@ -1,6 +1,5 @@
-
**Fireship.io** is an ecosystem of detailed and practical resources for developers who want to build and ship high-quality apps.
The site is built by:
-* [Jeff Delaney](https://fireship.io/contributors/jeff-delaney/)
+- [Jeff Delaney](https://fireship.io/contributors/jeff-delaney/)
diff --git a/docs/content/en/showcase/fireship/index.md b/docs/content/en/showcase/fireship/index.md
index e9338a625f7..454ee87d7c5 100644
--- a/docs/content/en/showcase/fireship/index.md
+++ b/docs/content/en/showcase/fireship/index.md
@@ -1,7 +1,7 @@
---
title: fireship.io
date: 2019-02-02
-description: "Showcase: \"Hugo helps us create complex technical content that integrates engaging web components\""
+description: 'Showcase: "Hugo helps us create complex technical content that integrates engaging web components."'
siteURL: https://fireship.io
siteSource: https://github.com/fireship-io/fireship.io
byline: "[Jeff Delaney](https://github.com/codediodeio), Fireship.io Creator"
@@ -14,4 +14,4 @@ After careful consideration of JavaScript/JSX-based static site generators, it b
- **Composability.** Hugo's partial and shortcode systems empower us to write DRY and maintainable templates.
- **Simplicity.** Hugo is easy to learn (even without Go experience) and doesn't burden us with brittle dependencies.
-The site is able to achieve Lighthouse performance scores of 95+, despite the fact that it is a fully interactive PWA that ships Angular and Firebase in the JS bundle. This is made possible by (1) prerendering content with Hugo and (2) lazily embedding native web components directly in the HTML and markdown. We provide a [detailed explanation](https://youtu.be/gun8OiGtlNc) of the architecture on YouTube and can't imagine development without Hugo.
+The site is able to achieve Lighthouse performance scores of 95+, despite the fact that it is a fully interactive PWA that ships Angular and Firebase in the JS bundle. This is made possible by (1) prerendering content with Hugo and (2) lazily embedding native web components directly in the HTML and Markdown. We provide a [detailed explanation](https://youtu.be/gun8OiGtlNc) of the architecture on YouTube and can't imagine development without Hugo.
diff --git a/docs/content/en/showcase/flesland-flis/bio.md b/docs/content/en/showcase/flesland-flis/bio.md
deleted file mode 100644
index 2fa6a796471..00000000000
--- a/docs/content/en/showcase/flesland-flis/bio.md
+++ /dev/null
@@ -1,8 +0,0 @@
-
-A business page for Flesland Flis AS. A Norwegian Tiler located in Bergen.
-
-The page is designed and developed by Sindre Gusdal:
-
-* [Absoluttweb AS](https://www.absoluttweb.no)
-* [Sindre Gusdal](https://www.linkedin.com/in/sindregusdal/)
-
diff --git a/docs/content/en/showcase/flesland-flis/featured.png b/docs/content/en/showcase/flesland-flis/featured.png
deleted file mode 100644
index a6dae684ee7..00000000000
Binary files a/docs/content/en/showcase/flesland-flis/featured.png and /dev/null differ
diff --git a/docs/content/en/showcase/flesland-flis/index.md b/docs/content/en/showcase/flesland-flis/index.md
deleted file mode 100644
index 935bb46619b..00000000000
--- a/docs/content/en/showcase/flesland-flis/index.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-
-title: Flesland Flis AS
-date: 2018-04-24
-description: "showcase: Business Page for a tile shop in Bergen, Norway"
-siteURL: https://www.fleslandflis.no
-byline: "[Sindre Gusdal](https://www.absoluttweb.no), Absoluttweb AS"
-
----
-
-For **Flesland Flis** I use a combination of **Hugo, Forestry.io and Netlify**. Static Site Generators and Hugo has been on my radar for a long time, and with all the nice features released in Hugo the last years, it's now my preferred solution for new clients. Also a huge thanks to the guys at [Forestry.io](https://forestry.io), for making such a smooth CMS for Hugo.
-
-The #1 reason why I love Hugo is the logic between content and layout, and of course the speed. Compared to solutions like Jekyll, Hugo is just better at all the stuff I value the most - speed, flexibility, theming and more.
-
-### Thanks, Hugo!
-
-Today I use Hugo in a combination with GULP and Foundation 6 + my own Hugo starter theme. This works great for me, and gives me all the flexibility I need. Then I can include FancyBox, Responsive Text and other Node Modules when needed.
-
-In the past I had to do a lot of changes to layout, content and css, if the client f.ex needed an extra PDF or an image-gallery to a certain page. Just small details not fitting in the template, would be a hassle. So updating existing webpages was boring and time consuming.
-
-Today I just copy-paste a new layout file, adds some frontmatter, Pushes to GIT and that special page is done.
-
-**Gotta love it:)**
-
diff --git a/docs/content/en/showcase/forestry/bio.md b/docs/content/en/showcase/forestry/bio.md
index 767365cc023..23951a1c6ef 100644
--- a/docs/content/en/showcase/forestry/bio.md
+++ b/docs/content/en/showcase/forestry/bio.md
@@ -1,5 +1,3 @@
-
-Forestry.io is a Git-backed CMS (content management system) for websites and web products built using static site generators such as Hugo.
+Forestry.io is a Git-backed CMS (content management system) for websites and web products built using static site generators such as Hugo.
Forestry bridges the gap between developers and their teams, by making development fun and easy, while providing powerful content management for their teams.
-
diff --git a/docs/content/en/showcase/forestry/index.md b/docs/content/en/showcase/forestry/index.md
index 1a9c0faaacb..58ff8ee06d6 100644
--- a/docs/content/en/showcase/forestry/index.md
+++ b/docs/content/en/showcase/forestry/index.md
@@ -1,20 +1,20 @@
---
title: Forestry.io
date: 2018-03-16
-description: "Showcase: \"Seeing Hugo in action is a whole different world of awesome.\""
+description: 'Showcase: "Seeing Hugo in action is a whole different world of awesome."'
siteURL: https://forestry.io/
siteSource: https://github.com/forestryio/forestry.io
---
It was clear from the get-go that we had to go with a static site generator. Static sites are secure, performant, and give you 100% flexibility. At [Forestry.io](https://forestry.io/) we provide Content Management Solutions for websites built with static site generators, so we might be a little biased. The only question: Which static site generator was the right choice for us?
-### Why Hugo?
+## Why Hugo?
-In our early research we looked at Ionic’s [site](https://github.com/ionic-team/ionic) to get some inspiration. They used Jekyll to build their website. While Jekyll is a great generator, the build times for larger sites can be painfully slow. With more than 150 pages plus many custom configurations and add-ons, our website doesn’t fall into the low-volume category anymore. Our developers want a smooth experience when working on the website and our content editors need the ability to preview content quickly. In short, we need our builds to be lightning fast.
+In our early research we looked at Ionic's [site](https://github.com/ionic-team/ionic) to get some inspiration. They used Jekyll to build their website. While Jekyll is a great generator, the build times for larger sites can be painfully slow. With more than 150 pages plus many custom configurations and add-ons, our website doesn't fall into the low-volume category anymore. Our developers want a smooth experience when working on the website and our content editors need the ability to preview content quickly. In short, we need our builds to be lightning fast.
We knew Hugo was fast but we did [some additional benchmarking](https://forestry.io/blog/hugo-vs-jekyll-benchmark/) before making our decision. Seeing Hugo in action is a whole different world of awesome. Hugo takes less than one second to build our 150-page site! Take a look:
-```bash
+```text
| EN
+------------------+-----+
Pages | 141
@@ -35,14 +35,14 @@ Lastly, we want to take the opportunity to give some love to other amazing tools
### What tools did we use?
-* Our Norwegian designer Nichlas is in love with [**Sketch**](https://www.sketchapp.com/). From what we hear it’s a designer’s dream come true.
-* Some say our main graphic is [mesmerizing](https://twitter.com/hmncllctv/status/968907474664284160). Nichlas created it using [**3DS Max**](https://www.autodesk.com/products/3ds-max/overview).
-* [**Hugo**](https://gohugo.io/) -- of course.
-* Chris can’t think of modern web development without [**Gulp**](https://gulpjs.com/) & [**Webpack**](https://webpack.js.org/). We used them to add additional build steps such as Browsersync, CSS, JS and SVG optimization.
-* Speaking about adding steps to our build, our lives would be much harder without [**CircleCI**](https://circleci.com/) for continuous deployment and automated testing purposes.
-* We can’t stop raving about [**Algolia**](https://www.algolia.com/). Chris loves it and even wrote a tutorial on [how to implement Algolia](https://forestry.io/blog/search-with-algolia-in-hugo/) into static sites using Hugo’s [Custom Outputs](https://gohugo.io/templates/output-formats/).
-* [**Cloudinary**](https://cloudinary.com/) is probably one of the easiest ways to get responsive images into your website.
-* We might be a little biased on this one - We think [**Forestry.io**](https://forestry.io/) is a great way to add a content management system with a clean UI on top of your site without interrupting your experience as a developer.
-* For hosting purposes we use the almighty [**AWS**](https://aws.amazon.com/).
-* [**Formspree.io**](https://formspree.io/) is managing our support and enterprise requests.
-* We also use browser cookies and JS to customize our user’s experience and give it a more dynamic feel.
\ No newline at end of file
+- Our Norwegian designer Nichlas is in love with [**Sketch**](https://www.sketchapp.com/). From what we hear it's a designer's dream come true.
+- Some say our main graphic is [mesmerizing](https://x.com/hmncllctv/status/968907474664284160). Nichlas created it using [**3DS Max**](https://www.autodesk.com/products/3ds-max/overview).
+- [**Hugo**](https://gohugo.io/) -- of course.
+- Chris can't think of modern web development without [**Gulp**](https://gulpjs.com/) & [**Webpack**](https://webpack.js.org/). We used them to add additional build steps such as Browsersync, CSS, JS and SVG optimization.
+- Speaking about adding steps to our build, our lives would be much harder without [**CircleCI**](https://circleci.com/) for continuous deployment and automated testing purposes.
+- We can't stop raving about [**Algolia**](https://www.algolia.com/). Chris loves it and even wrote a tutorial on [how to implement Algolia](https://forestry.io/blog/search-with-algolia-in-hugo/) into static sites using Hugo's [custom output formats](/configuration/output-formats/).
+- [**Cloudinary**](https://cloudinary.com/) is probably one of the easiest ways to get responsive images into your website.
+- We might be a little biased on this one - We think [**Forestry.io**](https://forestry.io/) is a great way to add a content management system with a clean UI on top of your site without interrupting your experience as a developer.
+- For hosting purposes we use the almighty [**AWS**](https://aws.amazon.com/).
+- [**Formspree.io**](https://formspree.io/) is managing our support and enterprise requests.
+- We also use browser cookies and JS to customize our user's experience and give it a more dynamic feel.
diff --git a/docs/content/en/showcase/godot-tutorials/bio.md b/docs/content/en/showcase/godot-tutorials/bio.md
index 92fccadf66e..fd849f8444c 100644
--- a/docs/content/en/showcase/godot-tutorials/bio.md
+++ b/docs/content/en/showcase/godot-tutorials/bio.md
@@ -1,9 +1,7 @@
-
[Godot Tutorials](https://godottutorials.com) aims to teach beginners how to get up and running with basic game programming and game development skills.
The website is built with the **Hugo Framework** alongside aws+cloudfront+lambda.
The site is built by:
-* [Godot Tutorials](https://godottutorials.com)
-
+- [Godot Tutorials](https://godottutorials.com)
diff --git a/docs/content/en/showcase/godot-tutorials/index.md b/docs/content/en/showcase/godot-tutorials/index.md
index e33e413e1d8..fe4f9337e22 100644
--- a/docs/content/en/showcase/godot-tutorials/index.md
+++ b/docs/content/en/showcase/godot-tutorials/index.md
@@ -1,25 +1,17 @@
---
-
title: Godot Tutorials
date: 2021-01-07
-
description: "Teaching game development skills with love."
-
-# The URL to the site on the internet.
siteURL: https://godottutorials.com
-
-# Add credit to the article author. Leave blank or remove if not needed/wanted.
byline: "[Godot Tutorials](https://godottutorials.com), Web Developer & Game Programmer"
-
---
-
[Godot Tutorials](https://godottutorials.com) started as a way to teach beginners game programming and game development.
-As I created videos, I ran into a problem; if I made a mistake with a Youtube video, it was difficult to correct errors.
+As I created videos, I ran into a problem; if I made a mistake with a YouTube video, it was difficult to correct errors.
I discovered that blogging episodes and having articles that teach on top of my videos is a fantastic solution to my problem.
-As I researched blogging platforms, I came across two solutions; however, I chose [Hugo](https://gohugo.io) because it's built with markdown in mind and simplified my workflow.
+As I researched blogging platforms, I came across two solutions; however, I chose [Hugo](https://gohugo.io) because it's built with Markdown in mind and simplified my workflow.
In a sense, with [Hugo](https://gohugo.io) programmed the right way, I can focus **more time on planning, creating, and editing**
-my videos and **less time maintaining and fixing** my website.
\ No newline at end of file
+my videos and **less time maintaining and fixing** my website.
diff --git a/docs/content/en/showcase/hapticmedia/index.md b/docs/content/en/showcase/hapticmedia/index.md
index b32879b6954..52c3337bfc6 100644
--- a/docs/content/en/showcase/hapticmedia/index.md
+++ b/docs/content/en/showcase/hapticmedia/index.md
@@ -1,28 +1,27 @@
---
-
title: Hapticmedia Blog
date: 2019-10-01
-description: "Showcase: \"A simple, but powerful, multilingual blog.\""
+description: 'Showcase: "A simple, but powerful, multilingual blog."'
siteURL: https://hapticmedia.fr/blog/en/
byline: "[Cyril Bonnet](https://github.com/monsieurnebo), Web Developer"
-
---
Our goal was to create a simple, effective and multilingual blog on [3D technology](https://hapticmedia.fr/blog/en/3d-technology/) that could be managed by a non-technical profile.
## Why Hugo?
-Hugo addresses all these needs, coupled with [Forestry.io](https://forestry.io/) for its administration via a "turnkey" interface. We have attached particular importance to SEO, and therefore to the creation of an advanced taxonomy system. Thus, each author and tag has a dedicated page, listing the related posts.
+Hugo addresses all these needs, coupled with [Forestry.io](https://forestry.io/) for its administration via a "turnkey" interface. We have attached particular importance to SEO, and therefore to the creation of an advanced taxonomy system. Thus, each author and tag has a dedicated page, listing the related posts.
## What we liked
+
- The **multilingual** content support, especially simple to setup.
- The **multiple environments** support (develop, staging, test, production, ...).
- Although a hard start with the Go language, the power of the **Hugo's templating**.
-- The **partial layouts**, including the `internals` (e.g. social metas).
+- The **partial layouts**, including the `internals` (e.g. social meta tags).
- The **build time**, unbeatable ⚡️⚡️⚡️.
-
## Tools & workflow
+
- We used the same design as **[our website](https://hapticmedia.fr/en/)**, recreated as a Hugo HTML template.
- **[Hugo](https://gohugo.io)** for the static website generator.
- **[CircleCI](https://circleci.com)** for continuous integration & deployment.
@@ -30,4 +29,3 @@ Hugo addresses all these needs, coupled with [Forestry.io](https://forestry.io/)
- **[Forestry.io](https://forestry.io)** for the content management.
**All of these tools allow our editor to manage the blog's content without having to worry about its technical aspect, which is managed by the developers.**
-
diff --git a/docs/content/en/showcase/hartwell-insurance/bio.md b/docs/content/en/showcase/hartwell-insurance/bio.md
index 7fab742923e..4cded7beb6e 100644
--- a/docs/content/en/showcase/hartwell-insurance/bio.md
+++ b/docs/content/en/showcase/hartwell-insurance/bio.md
@@ -1,4 +1,3 @@
-
Hartwell Insurance is an insurance company set up solely to service the Broker community.
By combining **Hugo**, **Service Worker** and **Netlify**, we were able to achieve incredible global site performance.
diff --git a/docs/content/en/showcase/hartwell-insurance/index.md b/docs/content/en/showcase/hartwell-insurance/index.md
index 9254979499e..1e15510a829 100644
--- a/docs/content/en/showcase/hartwell-insurance/index.md
+++ b/docs/content/en/showcase/hartwell-insurance/index.md
@@ -1,26 +1,20 @@
---
-
title: Hartwell Insurance
-
date: 2018-02-09
-
-description: "Showcase: \"Hugo + Netlify + PWA makes for a rapid website.\""
-
+description: 'Showcase: "Hugo + Netlify + PWA makes for a rapid website."'
siteURL: https://www.hartwell-insurance.com/
-
byline: "[Trys Mudford](http://www.trysmudford.com), Lead Developer, Tomango"
-
---
-We’ve just launched a shiny new website for [Hartwell Insurance](https://www.hartwell-insurance.com/) – I’m really proud of it. It was tackled in a different way to most previous Tomango site builds, using some fancy new tools and some vintage web standards.
+We've just launched a shiny new website for [Hartwell Insurance](https://www.hartwell-insurance.com/). I'm really proud of it. It was tackled in a different way to most previous Tomango site builds, using some fancy new tools and some vintage web standards.
-It’s a multi-page, single-page (!) website written in Hugo, a static site generator built with performance as a first-class feature. _I’ve outlined a load of benefits to Hugo & static sites [here](https://why-static.netlify.com/), in case you’re interested._
+It's a multi-page, single-page (!) website written in Hugo, a static site generator built with performance as a first-class feature. I've outlined a load of benefits to Hugo and static sites [on a page explaining the benefits of static sites](https://why-static.netlify.com/), in case you're interested.
-> **In essence, a static site generator pre-renders the whole site into HTML files and serves them like it’s 1995.**
+> **In essence, a static site generator pre-renders the whole site into HTML files and serves them like it's 1995.**
-There’s no Apache or Node backend that does compilation at runtime, it’s all done at the build step. This means the server; Netlify in this case, only has to do one thing – serve files. Unsurprisingly, serving simple files is VERY quick.
+There's no Apache or Node backend that does compilation at runtime, it's all done at the build step. This means the server; Netlify in this case, only has to do one thing: serve files. Unsurprisingly, serving simple files is VERY quick.
-The starter point was the [Victor Hugo](https://github.com/netlify/victor-hugo) repository that Netlify have created. It let me dive in with Hugo, PostCSS, BrowserSync and ES6 without setting up any tooling myself – always a win!
+The starter point was the [Victor Hugo](https://github.com/netlify/victor-hugo) repository that Netlify have created. It let me dive in with Hugo, PostCSS, Browsersync and ES6 without setting up any tooling myself---always a win!
I then took all the content from the design file and moved it into Markdown, putting shortcodes in where necessary. This site did need a number of custom shortcodes for the presentational elements like the expanding circles and full width backgrounds. But mostly it was just clean, semantic HTML with some CSS and JS enhancement thrown in.
@@ -32,17 +26,17 @@ For the ripple effects on the section headings, I used JS to prepend a `