-
-### Does this issue reproduce with the latest release?
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
deleted file mode 100644
index c84d3276b..000000000
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-blank_issues_enabled: false
-contact_links:
- - name: SUPPORT, ISSUES and TROUBLESHOOTING
- url: https://discourse.gohugo.io/
- about: Please DO NOT use Github for support requests. Please visit https://discourse.gohugo.io for support! You will be helped much faster there. If you ignore this request your issue might be closed with a discourse label.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
deleted file mode 100644
index c114b3d7f..000000000
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-name: Proposal
-about: Propose a new feature for Hugo
-title: ''
-labels: 'Proposal, NeedsTriage'
-assignees: ''
-
----
-
-
-
\ No newline at end of file
diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md
deleted file mode 100644
index cc9de09ff..000000000
--- a/.github/SUPPORT.md
+++ /dev/null
@@ -1,3 +0,0 @@
-### Asking Support Questions
-
-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.
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
deleted file mode 100644
index 1801e72d9..000000000
--- a/.github/dependabot.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-# See https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates#package-ecosystem
-version: 2
-updates:
- - package-ecosystem: "gomod"
- directory: "/"
- schedule:
- interval: "daily"
diff --git a/.github/workflows/image.yml b/.github/workflows/image.yml
deleted file mode 100644
index c4f3c34c3..000000000
--- a/.github/workflows/image.yml
+++ /dev/null
@@ -1,49 +0,0 @@
-name: Build Docker image
-
-on:
- release:
- types: [published]
- pull_request:
-permissions:
- packages: write
-
-env:
- REGISTRY_IMAGE: ghcr.io/gohugoio/hugo
-
-jobs:
- build:
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
-
- - name: Docker meta
- id: meta
- uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1
- with:
- images: ${{ env.REGISTRY_IMAGE }}
-
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1
-
- - name: Login to GHCR
- # Login is only needed when the image is pushed
- uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
- with:
- registry: ghcr.io
- username: ${{ github.repository_owner }}
- password: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Build and push
- id: build
- uses: docker/build-push-action@16ebe778df0e7752d2cfcbd924afdbbd89c1a755 # v6.6.1
- with:
- context: .
- provenance: mode=max
- sbom: true
- push: ${{ github.event_name != 'pull_request' }}
- platforms: linux/amd64,linux/arm64
- tags: ${{ steps.meta.outputs.tags }}
- labels: ${{ steps.meta.outputs.labels }}
- 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
deleted file mode 100644
index 249c1ab54..000000000
--- a/.github/workflows/stale.yml
+++ /dev/null
@@ -1,52 +0,0 @@
-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@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.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 is 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
deleted file mode 100644
index c49c12371..000000000
--- a/.github/workflows/test.yml
+++ /dev/null
@@ -1,132 +0,0 @@
-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:
- strategy:
- matrix:
- go-version: [1.23.x, 1.24.x]
- os: [ubuntu-latest, windows-latest] # macos disabled for now because of disk space issues.
- runs-on: ${{ matrix.os }}
- steps:
- - if: matrix.os == 'ubuntu-latest'
- name: Free Disk Space (Ubuntu)
- uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1
- with:
- # this might remove tools that are actually needed,
- # if set to "true" but frees about 6 GB
- tool-cache: false
- android: true
- dotnet: true
- haskell: true
- large-packages: true
- docker-images: true
- swap-storage: true
- - name: Checkout code
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- - name: Install Go
- uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
- 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@a6e6f86333f0a2523ece813039b8b4be04560854 # v1.190.0
- with:
- ruby-version: "2.7"
- bundler-cache: true #
- - name: Install Python
- uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1
- with:
- python-version: "3.x"
- - name: Install Mage
- run: go install github.com/magefile/mage@v1.15.0
- - name: Install asciidoctor
- uses: reitzig/actions-asciidoctor@c642db5eedd1d729bb8c92034770d0b2f769eda6 # v2.0.2
- - 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 == '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: 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
- - name: Build tags
- run: |
- go install -tags extended
- - if: matrix.os == 'ubuntu-latest'
- name: Build for dragonfly
- run: |
- go install
- env:
- GOARCH: amd64
- GOOS: dragonfly
diff --git a/.gitignore b/.gitignore
index ddad69611..47721b7cb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,18 @@
+hugo
+docs/public*
+/.idea
+hugo.exe
+*.test
+*.prof
+nohup.out
+cover.out
+*.swp
+*.swo
+.DS_Store
+*~
+vendor/*/
+*.bench
+coverage*.out
-*.test
-imports.*
-dist/
-public/
-.DS_Store
\ No newline at end of file
+GoBuilds
+dist
diff --git a/.goxc.json b/.goxc.json
new file mode 100644
index 000000000..10f19d226
--- /dev/null
+++ b/.goxc.json
@@ -0,0 +1,6 @@
+{
+ "ArtifactsDest": "GoBuilds/",
+ "OutPath": "{{.Dest}}{{.PS}}{{.AppName}}{{.PS}}{{.Version}}{{.PS}}{{.AppName}}_{{.Version}}_{{.Os}}_{{.Arch}}{{.Ext}}",
+ "BuildConstraints": "linux windows darwin freebsd netbsd openbsd dragonfly",
+ "ConfigVersion": "0.9"
+}
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 000000000..514ab1ddf
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,23 @@
+language: go
+sudo: required
+go:
+ - 1.7.6
+ - 1.8.3
+ - tip
+os:
+ - linux
+ - osx
+matrix:
+ allow_failures:
+ - go: tip
+ fast_finish: true
+install:
+ - make vendor
+script:
+ - make hugo-race check
+ - ./hugo -s docs/
+ - ./hugo --renderToMemory -s docs/
+before_install:
+ # gem install must be run with sudo on OSX
+ - sudo gem install asciidoctor | gem install asciidoctor
+ - sudo pip install docutils
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ddd3efcf2..0368e6666 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,5 +1,3 @@
->**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,
@@ -9,10 +7,6 @@ helping to manage issues, etc.
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. We created a [step by step guide](https://gohugo.io/tutorials/how-to-contribute-to-hugo/) if you're unfamiliar with GitHub or contributing to open source projects in general.
-*Note that this repository only contains the actual source code of Hugo. For **only** documentation-related pull requests / issues please refer to the [hugoDocs](https://github.com/gohugoio/hugoDocs) repository.*
-
-*Changes to the codebase **and** related documentation, e.g. for a new feature, should still use a single pull request.*
-
## Table of Contents
* [Asking Support Questions](#asking-support-questions)
@@ -20,8 +14,11 @@ The Hugo community and maintainers are [very active](https://github.com/gohugoio
* [Submitting Patches](#submitting-patches)
* [Code Contribution Guidelines](#code-contribution-guidelines)
* [Git Commit Message Guidelines](#git-commit-message-guidelines)
+ * [Vendored Dependencies](#vendored-dependencies)
* [Fetching the Sources From GitHub](#fetching-the-sources-from-github)
- * [Building Hugo with Your Changes](#building-hugo-with-your-changes)
+ * [Using Git Remotes](#using-git-remotes)
+ * [Build Hugo with Your Changes](#build-hugo-with-your-changes)
+ * [Updating the Hugo Sources](#updating-the-hugo-sources)
## Asking Support Questions
@@ -31,42 +28,22 @@ Please don't use the GitHub issue tracker to ask questions.
## Reporting Issues
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`) and your operating system.
-
-- [Hugo Issues · gohugoio/hugo](https://github.com/gohugoio/hugo/issues)
-- [Hugo Documentation Issues · gohugoio/hugoDocs](https://github.com/gohugoio/hugoDocs/issues)
-- [Hugo Website Theme Issues · gohugoio/hugoThemesSite](https://github.com/gohugoio/hugoThemesSite/issues)
-
-## Code Contribution
-
-Hugo has become a fully featured static site generator, so any new functionality must:
-
-* be useful to many.
-* fit naturally into _what Hugo does best._
-* strive not to break existing sites.
-* close or update an open [Hugo issue](https://github.com/gohugoio/hugo/issues)
-
-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.).
-
-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).
-We have one exception to this rule which is LibSASS.
-
-**Bug fixes are, of course, always welcome.**
+the GitHub [issue tracker](https://github.com/gohugoio/hugo/issues) 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`) and your operating system.
## Submitting Patches
-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.
+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.
+Because 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.
### Code Contribution Guidelines
-Because 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.
-
To make the contribution process as seamless as possible, we ask for the following:
* Go ahead and fork the project and make your changes. We encourage pull requests to allow for review and discussion of code changes.
@@ -76,28 +53,24 @@ 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 `make check` succeeds. [Travis CI](https://travis-ci.org/gohugoio/hugo) (Linux and macOS) and [AppVeyor](https://ci.appveyor.com/project/gohugoio/hugo/branch/master) (Windows) will fail the build if `make check` fails.
* Follow the **Git Commit Message Guidelines** below.
### Git Commit Message Guidelines
-This [blog article](https://cbea.ms/git-commit/) is a good resource for learning how to write good commit messages,
+This [blog article](http://chris.beams.io/posts/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:
-*"js: Return error when option x is not set"*, **NOT** *"returning some error."*
-
-Most title/subjects should have a lower-cased prefix with a colon and one whitespace. The prefix can be:
-
-* 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:".
+*"Return error on wrong use of the Paginator"*, **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*.
+Sometimes it makes sense to prefix the commit message with the packagename (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."
+
+Please consider to 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.
+
An example:
```text
@@ -111,35 +84,42 @@ new default function more useful for Hugo users.
Fixes #1949
```
-### Fetching the Sources From GitHub
+### Vendored Dependencies
-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:
+Hugo uses [govendor](https://github.com/kardianos/govendor) to vendor dependencies, but we don't commit the vendored packages themselves to the Hugo git repository.
+Therefore, a simple `go get` is not supported since `go get` is not vendor-aware.
+You **must use govendor** to fetch and manage Hugo's dependencies.
-```bash
-mkdir $HOME/src
-cd $HOME/src
-git clone https://github.com/gohugoio/hugo.git
-cd hugo
-go install
+### Fetch the Sources From GitHub
+
+```
+go get github.com/kardianos/govendor
+govendor get github.com/gohugoio/hugo
```
-For some convenient build and test targets, you also will want to install Mage:
+### Using Git Remotes
-```bash
-go install github.com/magefile/mage
-```
+Due to the way Go handles package imports, the best approach for working on a
+Hugo fork is to use Git Remotes. Here's a simple walk-through for getting
+started:
-Now, to make a change to Hugo's source:
+1. Fetch the Hugo sources as described above.
+
+1. Change to the Hugo source directory:
+
+ ```
+ cd $HOME/go/src/github.com/gohugoio/hugo
+ ```
1. Create a new branch for your changes (the branch name is arbitrary):
- ```bash
+ ```
git checkout -b iss1234
```
1. After making your changes, commit them to your new branch:
- ```bash
+ ```
git commit -a -v
```
@@ -147,53 +127,35 @@ Now, to make a change to Hugo's source:
1. Add your fork as a new remote (the remote name, "fork" in this example, is arbitrary):
- ```bash
- git remote add fork git@github.com:USERNAME/hugo.git
+ ```
+ git remote add fork git://github.com/USERNAME/hugo.git
```
1. Push the changes to your new remote:
- ```bash
+ ```
git push --set-upstream fork iss1234
```
1. You're now ready to submit a PR based upon the new branch in your forked repository.
-### Building Hugo with Your Changes
-
-Hugo uses [mage](https://github.com/magefile/mage) to sync vendor dependencies, build Hugo, run the test suite and other things. You must run mage from the Hugo directory.
+### Build Hugo with Your Changes
```bash
cd $HOME/go/src/github.com/gohugoio/hugo
+make hugo
+# or to install in $HOME/go/bin:
+make install
```
-To build Hugo:
+### Updating the Hugo Sources
-```bash
-mage hugo
+If you want to stay in sync with the Hugo repository, you can easily pull down
+the source changes, but you'll need to keep the vendored packages up-to-date as
+well.
+
+```
+git pull
+make vendor
```
-To install hugo in `$HOME/go/bin`:
-
-```bash
-mage install
-```
-
-To run the tests:
-
-```bash
-mage hugoRace
-mage -v check
-```
-
-To list all available commands along with descriptions:
-
-```bash
-mage -l
-```
-
-**Note:** From Hugo 0.43 we have added a build tag, `extended` that adds **SCSS support**. This needs a C compiler installed to build. You can enable this when building by:
-
-```bash
-HUGO_BUILD_TAGS=extended mage install
-````
diff --git a/Dockerfile b/Dockerfile
old mode 100755
new mode 100644
index a0e34353f..43284e4d9
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,99 +1,27 @@
-# GitHub: https://github.com/gohugoio
-# Twitter: https://twitter.com/gohugoio
-# Website: https://gohugo.io/
+FROM golang:alpine3.6
-ARG GO_VERSION="1.24"
-ARG ALPINE_VERSION="3.22"
-ARG DART_SASS_VERSION="1.79.3"
+ENV GOPATH /go
-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
-
-
-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
-
-# 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 <>_
+
+### Terms and Conditions for use, reproduction, and distribution
+
+#### 1. Definitions
+
+“License” shall mean the terms and conditions for use, reproduction, and
+distribution as defined by Sections 1 through 9 of this document.
+
+“Licensor” shall mean the copyright owner or entity authorized by the copyright
+owner that is granting the License.
+
+“Legal Entity” shall mean the union of the acting entity and all other entities
+that control, are controlled by, or are under common control with that entity.
+For the purposes of this definition, “control” means **(i)** the power, direct or
+indirect, to cause the direction or management of such entity, whether by
+contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
+outstanding shares, or **(iii)** beneficial ownership of such entity.
+
+“You” (or “Your”) shall mean an individual or Legal Entity exercising
+permissions granted by this License.
+
+“Source” form shall mean the preferred form for making modifications, including
+but not limited to software source code, documentation source, and configuration
+files.
+
+“Object” form shall mean any form resulting from mechanical transformation or
+translation of a Source form, including but not limited to compiled object code,
+generated documentation, and conversions to other media types.
+
+“Work” shall mean the work of authorship, whether in Source or Object form, made
+available under the License, as indicated by a copyright notice that is included
+in or attached to the work (an example is provided in the Appendix below).
+
+“Derivative Works” shall mean any work, whether in Source or Object form, that
+is based on (or derived from) the Work and for which the editorial revisions,
+annotations, elaborations, or other modifications represent, as a whole, an
+original work of authorship. For the purposes of this License, Derivative Works
+shall not include works that remain separable from, or merely link (or bind by
+name) to the interfaces of, the Work and Derivative Works thereof.
+
+“Contribution” shall mean any work of authorship, including the original version
+of the Work and any modifications or additions to that Work or Derivative Works
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
+by the copyright owner or by an individual or Legal Entity authorized to submit
+on behalf of the copyright owner. For the purposes of this definition,
+“submitted” means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems, and
+issue tracking systems that are managed by, or on behalf of, the Licensor for
+the purpose of discussing and improving the Work, but excluding communication
+that is conspicuously marked or otherwise designated in writing by the copyright
+owner as “Not a Contribution.”
+
+“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
+of whom a Contribution has been received by Licensor and subsequently
+incorporated within the Work.
+
+#### 2. Grant of Copyright License
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the Work and such
+Derivative Works in Source or Object form.
+
+#### 3. Grant of Patent License
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable (except as stated in this section) patent license to make, have
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
+such license applies only to those patent claims licensable by such Contributor
+that are necessarily infringed by their Contribution(s) alone or by combination
+of their Contribution(s) with the Work to which such Contribution(s) was
+submitted. If You institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
+Contribution incorporated within the Work constitutes direct or contributory
+patent infringement, then any patent licenses granted to You under this License
+for that Work shall terminate as of the date such litigation is filed.
+
+#### 4. Redistribution
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof
+in any medium, with or without modifications, and in Source or Object form,
+provided that You meet the following conditions:
+
+* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+* **(b)** You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
+all copyright, patent, trademark, and attribution notices from the Source form
+of the Work, excluding those notices that do not pertain to any part of the
+Derivative Works; and
+* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a whole,
+provided Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+#### 5. Submission of Contributions
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+#### 6. Trademarks
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+#### 7. Disclaimer of Warranty
+
+Unless required by applicable law or agreed to in writing, Licensor provides the
+Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+#### 8. Limitation of Liability
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+#### 9. Accepting Warranty or Additional Liability
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License. However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+_END OF TERMS AND CONDITIONS_
+
+### APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets `[]` replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same “printed page” as the copyright notice for easier identification within
+third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/Makefile b/Makefile
new file mode 100644
index 000000000..4508ab7cf
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,83 @@
+# A Self-Documenting Makefile: http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
+
+PACKAGE = github.com/gohugoio/hugo
+COMMIT_HASH = `git rev-parse --short HEAD 2>/dev/null`
+BUILD_DATE = `date +%FT%T%z`
+LDFLAGS = -ldflags "-X ${PACKAGE}/hugolib.CommitHash=${COMMIT_HASH} -X ${PACKAGE}/hugolib.BuildDate=${BUILD_DATE}"
+NOGI_LDFLAGS = -ldflags "-X ${PACKAGE}/hugolib.BuildDate=${BUILD_DATE}"
+
+.PHONY: vendor docker check fmt lint test test-race vet test-cover-html help
+.DEFAULT_GOAL := help
+
+vendor: ## Install govendor and sync Hugo's vendored dependencies
+ go get github.com/kardianos/govendor
+ govendor sync ${PACKAGE}
+
+hugo: vendor ## Build hugo binary
+ go build ${LDFLAGS} ${PACKAGE}
+
+hugo-race: vendor ## Build hugo binary with race detector enabled
+ go build -race ${LDFLAGS} ${PACKAGE}
+
+install: vendor ## Install hugo binary
+ go install ${LDFLAGS} ${PACKAGE}
+
+hugo-no-gitinfo: LDFLAGS = ${NOGI_LDFLAGS}
+hugo-no-gitinfo: vendor hugo ## Build hugo without git info
+
+docker: ## Build hugo Docker container
+ docker build -t hugo .
+ docker rm -f hugo-build || true
+ docker run --name hugo-build hugo ls /go/bin
+ docker cp hugo-build:/go/bin/hugo .
+ docker rm hugo-build
+
+govendor: vendor # Deprecated: use "vendor" target
+get: vendor # Deprecated: use "vendor"
+gitinfo: hugo # Deprecated: use "hugo" target
+install-gitinfo: install # Deprecated: use "install" target
+no-git-info: hugo-no-gitinfo # Deprecated: use "hugo-no-gitinfo" target
+
+check: test-race test386 fmt vet ## Run tests and linters
+
+test386: ## Run tests in 32-bit mode
+ GOARCH=386 govendor test +local
+
+test: ## Run tests
+ govendor test +local
+
+test-race: ## Run tests with race detector
+ govendor test -race +local
+
+fmt: ## Run gofmt linter
+ @for d in `govendor list -no-status +local | sed 's/github.com.gohugoio.hugo/./'` ; do \
+ if [ "`gofmt -l $$d/*.go | tee /dev/stderr`" ]; then \
+ echo "^ improperly formatted go files" && echo && exit 1; \
+ fi \
+ done
+
+lint: ## Run golint linter
+ @for d in `govendor list -no-status +local | sed 's/github.com.gohugoio.hugo/./'` ; do \
+ if [ "`golint $$d | tee /dev/stderr`" ]; then \
+ echo "^ golint errors!" && echo && exit 1; \
+ fi \
+ done
+
+vet: ## Run go vet linter
+ @if [ "`govendor vet +local | tee /dev/stderr`" ]; then \
+ echo "^ go vet errors!" && echo && exit 1; \
+ fi
+
+test-cover-html: PACKAGES = $(shell govendor list -no-status +local | sed 's/github.com.gohugoio.hugo/./')
+test-cover-html: ## Generate test coverage report
+ echo "mode: count" > coverage-all.out
+ $(foreach pkg,$(PACKAGES),\
+ govendor test -coverprofile=coverage.out -covermode=count $(pkg);\
+ tail -n +2 coverage.out >> coverage-all.out;)
+ go tool cover -html=coverage-all.out
+
+check-vendor: ## Verify that vendored packages match git HEAD
+ @git diff-index --quiet HEAD vendor/ || (echo "check-vendor target failed: vendored packages out of sync" && echo && git diff vendor/ && exit 1)
+
+help:
+ @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
diff --git a/README.md b/README.md
index 9befa9c9d..e5591d5e0 100644
--- a/README.md
+++ b/README.md
@@ -1,282 +1,106 @@
-[bep]: https://github.com/bep
-[bugs]: https://github.com/gohugoio/hugo/issues?q=is%3Aopen+is%3Aissue+label%3ABug
-[contributing]: CONTRIBUTING.md
-[create a proposal]: https://github.com/gohugoio/hugo/issues/new?labels=Proposal%2C+NeedsTriage&template=feature_request.md
-[documentation repository]: https://github.com/gohugoio/hugoDocs
-[documentation]: https://gohugo.io/documentation
-[dragonfly bsd, freebsd, netbsd, and openbsd]: https://gohugo.io/installation/bsd
-[features]: https://gohugo.io/about/features/
-[forum]: https://discourse.gohugo.io
-[friends]: https://github.com/gohugoio/hugo/graphs/contributors
-[go]: https://go.dev/
-[hugo modules]: https://gohugo.io/hugo-modules/
-[installation]: https://gohugo.io/installation
-[issue queue]: https://github.com/gohugoio/hugo/issues
-[linux]: https://gohugo.io/installation/linux
-[macos]: https://gohugo.io/installation/macos
-[prebuilt binary]: https://github.com/gohugoio/hugo/releases/latest
-[requesting help]: https://discourse.gohugo.io/t/requesting-help/9132
-[spf13]: https://github.com/spf13
-[static site generator]: https://en.wikipedia.org/wiki/Static_site_generator
-[support]: https://discourse.gohugo.io
-[themes]: https://themes.gohugo.io/
-[website]: https://gohugo.io
-[windows]: https://gohugo.io/installation/windows
+
-
+A Fast and Flexible Static Site Generator built with love by [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) |
+[Developer Chat (no support)](https://gitter.im/gohugoio/hugo) |
+[Documentation](https://gohugo.io/overview/introduction/) |
+[Installation Guide](https://gohugo.io/overview/installing/) |
+[Contribution Guide](CONTRIBUTING.md) |
+[Twitter](http://twitter.com/gohugoio)
[](https://godoc.org/github.com/gohugoio/hugo)
-[](https://github.com/gohugoio/hugo/actions?query=workflow%3ATest)
+[](https://travis-ci.org/gohugoio/hugo)
+[](https://ci.appveyor.com/project/bep/hugo/branch/master)
+[](https://gitter.im/spf13/hugo?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[](https://goreportcard.com/report/github.com/gohugoio/hugo)
-[Website] | [Installation] | [Documentation] | [Support] | [Contributing] | Mastodon
-
## Overview
-Hugo is a [static site generator] written in [Go], optimized for speed and designed for flexibility. With its advanced templating system and fast asset pipelines, Hugo renders a complete site in seconds, often less.
+Hugo is a static HTML and CSS website generator written in [Go][].
+It is optimized for speed, easy use and configurability.
+Hugo takes a directory with content and templates and renders them into a full HTML website.
-Due to its flexible framework, multilingual support, and powerful taxonomy system, Hugo is widely used to create:
+Hugo relies on Markdown files with front matter for meta data.
+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.
-- Corporate, government, nonprofit, education, news, event, and project sites
-- Documentation sites
-- Image portfolios
-- Landing pages
-- Business, professional, and personal blogs
-- Resumes and CVs
+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.
-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 is designed to work well for any kind of website including blogs, tumbles and docs.
-Hugo's fast asset pipelines include:
+#### Supported Architectures
-- Image processing – Convert, resize, crop, rotate, adjust colors, apply filters, overlay text and images, and extract EXIF data
-- JavaScript bundling – Transpile TypeScript and JSX to JavaScript, bundle, tree shake, minify, create source maps, and perform SRI hashing.
-- Sass processing – Transpile Sass to CSS, bundle, tree shake, minify, create source maps, perform SRI hashing, and integrate with PostCSS
-- Tailwind CSS processing – Compile Tailwind CSS utility classes into standard CSS, bundle, tree shake, optimize, minify, perform SRI hashing, and integrate with PostCSS
+Currently, we provide pre-built Hugo binaries for Windows, Linux, FreeBSD, NetBSD and macOS (Darwin) and [Android](https://gist.github.com/bep/a0d8a26cf6b4f8bc992729b8e50b480b) for x64, i386 and ARM architectures.
-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 may also be compiled from source wherever the Go compiler tool chain can run, e.g. for other operating systems including DragonFly BSD, OpenBSD, Plan 9 and Solaris.
-See the [features] section of the documentation for a comprehensive summary of Hugo's capabilities.
+**Complete documentation is available at [Hugo Documentation][].**
-## Sponsors
+## Choose How to Install
-
-
-
-
-
-
-
-
+If you want to use Hugo as your site generator, simply install the Hugo binaries.
+The Hugo binaries have no external dependencies.
-## Editions
+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.
-Hugo is available in three editions: standard, extended, and extended/deploy. While the standard edition provides core functionality, the extended and extended/deploy editions offer advanced features.
+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.
-Feature|extended edition|extended/deploy edition
-:--|:-:|:-:
-Encode to the WebP format when [processing images]. You can decode WebP images with any edition.|:heavy_check_mark:|:heavy_check_mark:
-[Transpile Sass to CSS] using the embedded LibSass transpiler. You can use the [Dart Sass] transpiler with any edition.|:heavy_check_mark:|:heavy_check_mark:
-Deploy your site directly to a Google Cloud Storage bucket, an AWS S3 bucket, or an Azure Storage container. See [details].|:x:|:heavy_check_mark:
+### Install Hugo as Your Site Generator (Binary Install)
-[dart sass]: https://gohugo.io/functions/css/sass/#dart-sass
-[processing images]: https://gohugo.io/content-management/image-processing/
-[transpile sass to css]: https://gohugo.io/functions/css/sass/
-[details]: https://gohugo.io/hosting-and-deployment/hugo-deploy/
+Use the [installation instructions in the Hugo documentation](https://gohugo.io/overview/installing/).
-Unless your specific deployment needs require the extended/deploy edition, we recommend the extended edition.
+### Build and Install the Binaries from Source (Advanced Install)
-## Installation
+Add Hugo and its package dependencies to your go `src` directory.
-Install Hugo from a [prebuilt binary], package manager, or package repository. Please see the installation instructions for your operating system:
+ go get -v github.com/gohugoio/hugo
-- [macOS]
-- [Linux]
-- [Windows]
-- [DragonFly BSD, FreeBSD, NetBSD, and OpenBSD]
+Once the `get` completes, you should find your new `hugo` (or `hugo.exe`) executable sitting inside `$GOPATH/bin/`.
-## Build from source
+To update Hugo’s dependencies, use `go get` with the `-u` option.
-Prerequisites to build Hugo from source:
+ go get -u -v github.com/gohugoio/hugo
-- Standard edition: Go 1.23.0 or later
-- Extended edition: Go 1.23.0 or later, and GCC
-- Extended/deploy edition: Go 1.23.0 or later, and GCC
-
-Build the standard edition:
-
-```text
-go install github.com/gohugoio/hugo@latest
-```
-
-Build the extended edition:
-
-```text
-CGO_ENABLED=1 go install -tags extended github.com/gohugoio/hugo@latest
-```
-
-Build the extended/deploy edition:
-
-```text
-CGO_ENABLED=1 go install -tags extended,withdeploy github.com/gohugoio/hugo@latest
-```
-
-## Star History
-
-[](https://star-history.com/#gohugoio/hugo&Timeline)
-
-## Documentation
-
-Hugo's [documentation] includes installation instructions, a quick start guide, conceptual explanations, reference information, and examples.
-
-Please submit documentation issues and pull requests to the [documentation repository].
-
-## Support
-
-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].
-
-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.
-
-## Contributing
-
-You can contribute to the Hugo project by:
-
-- Answering questions on the [forum]
-- Improving the [documentation]
-- Monitoring the [issue queue]
-- Creating or improving [themes]
-- Squashing [bugs]
-
-Please submit documentation issues and pull requests to the [documentation repository].
-
-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:
-
-- 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.
+## Contributing to Hugo
For a complete guide to contributing to Hugo, see the [Contribution Guide](CONTRIBUTING.md).
-## Dependencies
+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.
-Hugo stands on the shoulders of great open source libraries. Run `hugo env --logLevel info` to display a list of dependencies.
+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.
-
-See current dependencies
+### Asking Support Questions
-```text
-github.com/BurntSushi/locker="v0.0.0-20171006230638-a6e239ea1c69"
-github.com/PuerkitoBio/goquery="v1.10.1"
-github.com/alecthomas/chroma/v2="v2.15.0"
-github.com/andybalholm/cascadia="v1.3.3"
-github.com/armon/go-radix="v1.0.1-0.20221118154546-54df44f2176c"
-github.com/bep/clocks="v0.5.0"
-github.com/bep/debounce="v1.2.0"
-github.com/bep/gitmap="v1.6.0"
-github.com/bep/goat="v0.5.0"
-github.com/bep/godartsass/v2="v2.3.2"
-github.com/bep/golibsass="v1.2.0"
-github.com/bep/gowebp="v0.3.0"
-github.com/bep/imagemeta="v0.8.4"
-github.com/bep/lazycache="v0.7.0"
-github.com/bep/logg="v0.4.0"
-github.com/bep/mclib="v1.20400.20402"
-github.com/bep/overlayfs="v0.9.2"
-github.com/bep/simplecobra="v0.5.0"
-github.com/bep/tmc="v0.5.1"
-github.com/cespare/xxhash/v2="v2.3.0"
-github.com/clbanning/mxj/v2="v2.7.0"
-github.com/cpuguy83/go-md2man/v2="v2.0.4"
-github.com/disintegration/gift="v1.2.1"
-github.com/dlclark/regexp2="v1.11.5"
-github.com/dop251/goja="v0.0.0-20250125213203-5ef83b82af17"
-github.com/evanw/esbuild="v0.24.2"
-github.com/fatih/color="v1.18.0"
-github.com/frankban/quicktest="v1.14.6"
-github.com/fsnotify/fsnotify="v1.8.0"
-github.com/getkin/kin-openapi="v0.129.0"
-github.com/ghodss/yaml="v1.0.0"
-github.com/go-openapi/jsonpointer="v0.21.0"
-github.com/go-openapi/swag="v0.23.0"
-github.com/go-sourcemap/sourcemap="v2.1.4+incompatible"
-github.com/gobuffalo/flect="v1.0.3"
-github.com/gobwas/glob="v0.2.3"
-github.com/gohugoio/go-i18n/v2="v2.1.3-0.20230805085216-e63c13218d0e"
-github.com/gohugoio/hashstructure="v0.5.0"
-github.com/gohugoio/httpcache="v0.7.0"
-github.com/gohugoio/hugo-goldmark-extensions/extras="v0.2.0"
-github.com/gohugoio/hugo-goldmark-extensions/passthrough="v0.3.0"
-github.com/gohugoio/locales="v0.14.0"
-github.com/gohugoio/localescompressed="v1.0.1"
-github.com/golang/freetype="v0.0.0-20170609003504-e2365dfdc4a0"
-github.com/google/go-cmp="v0.6.0"
-github.com/google/pprof="v0.0.0-20250208200701-d0013a598941"
-github.com/gorilla/websocket="v1.5.3"
-github.com/hairyhenderson/go-codeowners="v0.7.0"
-github.com/hashicorp/golang-lru/v2="v2.0.7"
-github.com/jdkato/prose="v1.2.1"
-github.com/josharian/intern="v1.0.0"
-github.com/kr/pretty="v0.3.1"
-github.com/kr/text="v0.2.0"
-github.com/kyokomi/emoji/v2="v2.2.13"
-github.com/lucasb-eyer/go-colorful="v1.2.0"
-github.com/mailru/easyjson="v0.7.7"
-github.com/makeworld-the-better-one/dither/v2="v2.4.0"
-github.com/marekm4/color-extractor="v1.2.1"
-github.com/mattn/go-colorable="v0.1.13"
-github.com/mattn/go-isatty="v0.0.20"
-github.com/mattn/go-runewidth="v0.0.9"
-github.com/mazznoer/csscolorparser="v0.1.5"
-github.com/mitchellh/mapstructure="v1.5.1-0.20231216201459-8508981c8b6c"
-github.com/mohae/deepcopy="v0.0.0-20170929034955-c48cc78d4826"
-github.com/muesli/smartcrop="v0.3.0"
-github.com/niklasfasching/go-org="v1.7.0"
-github.com/oasdiff/yaml3="v0.0.0-20241210130736-a94c01f36349"
-github.com/oasdiff/yaml="v0.0.0-20241210131133-6b86fb107d80"
-github.com/olekukonko/tablewriter="v0.0.5"
-github.com/pbnjay/memory="v0.0.0-20210728143218-7b4eea64cf58"
-github.com/pelletier/go-toml/v2="v2.2.3"
-github.com/perimeterx/marshmallow="v1.1.5"
-github.com/pkg/browser="v0.0.0-20240102092130-5ac0b6a4141c"
-github.com/pkg/errors="v0.9.1"
-github.com/rivo/uniseg="v0.4.7"
-github.com/rogpeppe/go-internal="v1.13.1"
-github.com/russross/blackfriday/v2="v2.1.0"
-github.com/sass/libsass="3.6.6"
-github.com/spf13/afero="v1.11.0"
-github.com/spf13/cast="v1.7.1"
-github.com/spf13/cobra="v1.8.1"
-github.com/spf13/fsync="v0.10.1"
-github.com/spf13/pflag="v1.0.6"
-github.com/tdewolff/minify/v2="v2.20.37"
-github.com/tdewolff/parse/v2="v2.7.15"
-github.com/tetratelabs/wazero="v1.8.2"
-github.com/webmproject/libwebp="v1.3.2"
-github.com/yuin/goldmark-emoji="v1.0.4"
-github.com/yuin/goldmark="v1.7.8"
-go.uber.org/automaxprocs="v1.5.3"
-golang.org/x/crypto="v0.33.0"
-golang.org/x/exp="v0.0.0-20250210185358-939b2ce775ac"
-golang.org/x/image="v0.24.0"
-golang.org/x/mod="v0.23.0"
-golang.org/x/net="v0.35.0"
-golang.org/x/sync="v0.11.0"
-golang.org/x/sys="v0.30.0"
-golang.org/x/text="v0.22.0"
-golang.org/x/tools="v0.30.0"
-golang.org/x/xerrors="v0.0.0-20240903120638-7835f813f4da"
-gonum.org/v1/plot="v0.15.0"
-google.golang.org/protobuf="v1.36.5"
-gopkg.in/yaml.v2="v2.4.0"
-gopkg.in/yaml.v3="v3.0.1"
-oss.terrastruct.com/d2="v0.6.9"
-oss.terrastruct.com/util-go="v0.0.0-20241005222610-44c011a04896"
-rsc.io/qr="v0.2.0"
-software.sslmate.com/src/go-pkcs12="v0.2.0"
-```
-
+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.
+
+### Reporting Issues
+
+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`).
+
+### Submitting Patches
+
+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.
+Because 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.
+
+For a complete guide to contributing code to Hugo, see the [Contribution Guide](CONTRIBUTING.md).
+
+[](https://github.com/igrigorik/ga-beacon)
+
+[Go]: https://golang.org/
+[Hugo Documentation]: https://gohugo.io/overview/introduction/
diff --git a/SECURITY.md b/SECURITY.md
deleted file mode 100644
index 6ac90f072..000000000
--- a/SECURITY.md
+++ /dev/null
@@ -1,7 +0,0 @@
-## Security Policy
-
-### Reporting a Vulnerability
-
-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/).
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 000000000..423a6e416
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,17 @@
+init:
+ - copy c:\MinGW\bin\mingw32-make.exe c:\MinGW\bin\make.exe
+ - set PATH=%PATH%;C:\MinGW\bin;%GOPATH%\bin
+ - go version
+ - go env
+
+# clones and cd's to path
+clone_folder: C:\GOPATH\src\github.com\gohugoio\hugo
+
+install:
+ - gem install asciidoctor
+ - pip install docutils
+
+build_script:
+ - make hugo-race check
+ - hugo -s docs/
+ - hugo --renderToMemory -s docs/
diff --git a/bench.sh b/bench.sh
new file mode 100755
index 000000000..367a74403
--- /dev/null
+++ b/bench.sh
@@ -0,0 +1,35 @@
+#!/usr/bin/env bash
+
+
+# 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
+go test -test.run=NONE -bench="$benchFilter" -test.benchmem=true ./$PACKAGE > /tmp/bench-$PACKAGE-$BRANCH.txt
+
+git checkout master
+go 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
\ No newline at end of file
diff --git a/benchSite.sh b/benchSite.sh
new file mode 100755
index 000000000..8130559f5
--- /dev/null
+++ b/benchSite.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+# Send in a regexp mathing 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}"
+
+go test -run="NONE" -bench="BenchmarkSiteBuilding/${1}$" -test.benchmem=true ./hugolib -memprofile mem.prof -cpuprofile cpu.prof
\ No newline at end of file
diff --git a/bufferpool/bufpool.go b/bufferpool/bufpool.go
index f05675e3e..c1e4105d0 100644
--- a/bufferpool/bufpool.go
+++ b/bufferpool/bufpool.go
@@ -20,7 +20,7 @@ import (
)
var bufferPool = &sync.Pool{
- New: func() any {
+ New: func() interface{} {
return &bytes.Buffer{}
},
}
diff --git a/bufferpool/bufpool_test.go b/bufferpool/bufpool_test.go
index 023724b97..cfa247f62 100644
--- a/bufferpool/bufpool_test.go
+++ b/bufferpool/bufpool_test.go
@@ -14,18 +14,14 @@
package bufferpool
import (
+ "github.com/stretchr/testify/assert"
"testing"
-
- qt "github.com/frankban/quicktest"
)
func TestBufferPool(t *testing.T) {
- c := qt.New(t)
-
buff := GetBuffer()
buff.WriteString("do be do be do")
- c.Assert(buff.String(), qt.Equals, "do be do be do")
+ assert.Equal(t, "do be do be do", buff.String())
PutBuffer(buff)
-
- c.Assert(buff.Len(), qt.Equals, 0)
+ assert.Equal(t, 0, buff.Len())
}
diff --git a/cache/docs.go b/cache/docs.go
deleted file mode 100644
index b9c49840f..000000000
--- a/cache/docs.go
+++ /dev/null
@@ -1,2 +0,0 @@
-// Package cache contains the different cache implementations.
-package cache
diff --git a/cache/dynacache/dynacache.go b/cache/dynacache/dynacache.go
deleted file mode 100644
index 25d0f9b29..000000000
--- a/cache/dynacache/dynacache.go
+++ /dev/null
@@ -1,647 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package 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.NewStack[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.Stack[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.FindString(name) != 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
deleted file mode 100644
index 78b2fc82e..000000000
--- a/cache/dynacache/dynacache_test.go
+++ /dev/null
@@ -1,230 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package 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
deleted file mode 100644
index 01c466ca6..000000000
--- a/cache/filecache/filecache.go
+++ /dev/null
@@ -1,496 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package filecache
-
-import (
- "bytes"
- "errors"
- "io"
- "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"
-
- "github.com/BurntSushi/locker"
- "github.com/spf13/afero"
-)
-
-// ErrFatal can be used to signal an unrecoverable error.
-var ErrFatal = errors.New("fatal filecache error")
-
-const (
- FilecacheRootDirname = "filecache"
-)
-
-// Cache caches a set of files in a directory. This is usually a file on
-// disk, but since this is backed by an Afero file system, it can be anything.
-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
-
- // When set, we just remove this entire root directory on expiration.
- pruneAllRootDir string
-
- nlocker *lockTracker
-
- initOnce sync.Once
- initErr error
-}
-
-type lockTracker struct {
- seenMu sync.RWMutex
- seen map[string]struct{}
-
- *locker.Locker
-}
-
-// Lock tracks the ids in use. We use this information to do garbage collection
-// after a Hugo build.
-func (l *lockTracker) Lock(id string) {
- l.seenMu.RLock()
- if _, seen := l.seen[id]; !seen {
- l.seenMu.RUnlock()
- l.seenMu.Lock()
- l.seen[id] = struct{}{}
- l.seenMu.Unlock()
- } else {
- l.seenMu.RUnlock()
- }
-
- l.Locker.Lock(id)
-}
-
-// ItemInfo contains info about a cached file.
-type ItemInfo struct {
- // This is the file's name relative to the cache's filesystem.
- Name string
-}
-
-// NewCache creates a new file cache with the given filesystem and max age.
-func NewCache(fs afero.Fs, maxAge time.Duration, pruneAllRootDir string) *Cache {
- return &Cache{
- Fs: fs,
- nlocker: &lockTracker{Locker: locker.NewLocker(), seen: make(map[string]struct{})},
- maxAge: maxAge,
- pruneAllRootDir: pruneAllRootDir,
- }
-}
-
-// lockedFile is a file with a lock that is released on Close.
-type lockedFile struct {
- afero.File
- unlock func()
-}
-
-func (l *lockedFile) Close() error {
- defer l.unlock()
- return l.File.Close()
-}
-
-func (c *Cache) init() error {
- c.initOnce.Do(func() {
- // Create the base dir if it does not exist.
- if err := c.Fs.MkdirAll("", 0o777); err != nil && !os.IsExist(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)
-
- info := ItemInfo{Name: id}
-
- f, err := helpers.OpenFileForWriting(c.Fs, id)
- if err != nil {
- c.nlocker.Unlock(id)
- return info, nil, err
- }
-
- return info, &lockedFile{
- File: f,
- unlock: func() { c.nlocker.Unlock(id) },
- }, nil
-}
-
-// ReadOrCreate tries to lookup the file in cache.
-// If found, it is passed to read and then closed.
-// If not found a new file is created and passed to create, which should close
-// 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) {
- if err := c.init(); err != nil {
- return ItemInfo{}, err
- }
-
- id = cleanID(id)
-
- c.nlocker.Lock(id)
- defer c.nlocker.Unlock(id)
-
- info = ItemInfo{Name: id}
-
- if r := c.getOrRemove(id); r != nil {
- err = read(info, r)
- defer r.Close()
- if err == nil || err == ErrFatal {
- // See https://github.com/gohugoio/hugo/issues/6401
- // To recover from file corruption we handle read errors
- // as the cache item was not found.
- // Any file permission issue will also fail in the next step.
- return
- }
- }
-
- f, err := helpers.OpenFileForWriting(c.Fs, id)
- if err != nil {
- return
- }
-
- err = create(info, f)
-
- return
-}
-
-// NamedLock locks the given id. The lock is released when the returned function is called.
-func (c *Cache) NamedLock(id string) func() {
- id = cleanID(id)
- c.nlocker.Lock(id)
- return func() {
- c.nlocker.Unlock(id)
- }
-}
-
-// GetOrCreate tries to get the file with the given id from cache. If not found or expired, create will
-// be invoked and the result cached.
-// This method is protected by a named lock using the given id as identifier.
-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)
-
- info := ItemInfo{Name: id}
-
- if r := c.getOrRemove(id); r != nil {
- return info, r, nil
- }
-
- var (
- r io.ReadCloser
- err error
- )
-
- r, err = create()
- if err != nil {
- return info, nil, err
- }
-
- if c.maxAge == 0 {
- // No caching.
- return info, hugio.ToReadCloser(r), nil
- }
-
- var buff bytes.Buffer
- return info,
- hugio.ToReadCloser(&buff),
- c.writeReader(id, io.TeeReader(r, &buff))
-}
-
-func (c *Cache) writeReader(id string, r io.Reader) error {
- dir := filepath.Dir(id)
- if dir != "" {
- _ = c.Fs.MkdirAll(dir, 0o777)
- }
- f, err := c.Fs.Create(id)
- if err != nil {
- return err
- }
- defer f.Close()
-
- _, _ = io.Copy(f, r)
-
- return nil
-}
-
-// 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)
-
- info := ItemInfo{Name: id}
-
- if r := c.getOrRemove(id); r != nil {
- defer r.Close()
- b, err := io.ReadAll(r)
- return info, b, err
- }
-
- var (
- b []byte
- err error
- )
-
- b, err = create()
- if err != nil {
- return info, nil, err
- }
-
- if c.maxAge == 0 {
- return info, b, nil
- }
-
- if err := c.writeReader(id, bytes.NewReader(b)); err != nil {
- return info, nil, err
- }
-
- return info, b, nil
-}
-
-// 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) {
- if err := c.init(); err != nil {
- return ItemInfo{}, nil, err
- }
- id = cleanID(id)
-
- c.nlocker.Lock(id)
- defer c.nlocker.Unlock(id)
-
- info := ItemInfo{Name: id}
-
- if r := c.getOrRemove(id); r != nil {
- defer r.Close()
- b, err := io.ReadAll(r)
- return info, b, err
- }
-
- return info, nil, nil
-}
-
-// 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)
-
- info := ItemInfo{Name: id}
-
- r := c.getOrRemove(id)
-
- return info, r, nil
-}
-
-// 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 {
- // No caching.
- return nil
- }
-
- if removed, err := c.removeIfExpired(id); err != nil || removed {
- return nil
- }
-
- f, err := c.Fs.Open(id)
- if err != nil {
- return nil
- }
-
- return f
-}
-
-func (c *Cache) getBytesAndRemoveIfExpired(id string) ([]byte, bool) {
- if c.maxAge == 0 {
- // No caching.
- return nil, false
- }
-
- f, err := c.Fs.Open(id)
- if err != nil {
- return nil, false
- }
- defer f.Close()
-
- b, err := io.ReadAll(f)
- if err != nil {
- return nil, false
- }
-
- removed, err := c.removeIfExpired(id)
- if err != nil {
- return nil, false
- }
-
- return b, removed
-}
-
-func (c *Cache) removeIfExpired(id string) (bool, error) {
- if c.maxAge <= 0 {
- return false, nil
- }
-
- fi, err := c.Fs.Stat(id)
- if err != nil {
- return false, err
- }
-
- if c.isExpired(fi.ModTime()) {
- c.Fs.Remove(id)
- return true, nil
- }
-
- return false, nil
-}
-
-func (c *Cache) isExpired(modTime time.Time) bool {
- if c.maxAge < 0 {
- return false
- }
-
- // Note the use of time.Since here.
- // We cannot use Hugo's global Clock for this.
- return c.maxAge == 0 || time.Since(modTime) > c.maxAge
-}
-
-// For testing
-func (c *Cache) GetString(id string) string {
- id = cleanID(id)
-
- c.nlocker.Lock(id)
- defer c.nlocker.Unlock(id)
-
- f, err := c.Fs.Open(id)
- if err != nil {
- return ""
- }
- defer f.Close()
-
- b, _ := io.ReadAll(f)
- return string(b)
-}
-
-// Caches is a named set of caches.
-type Caches map[string]*Cache
-
-// Get gets a named cache, nil if none found.
-func (f Caches) Get(name string) *Cache {
- return f[strings.ToLower(name)]
-}
-
-// NewCaches creates a new set of file caches from the given
-// configuration.
-func NewCaches(p *helpers.PathSpec) (Caches, error) {
- dcfg := p.Cfg.GetConfigSection("caches").(Configs)
- fs := p.Fs.Source
-
- m := make(Caches)
- for k, v := range dcfg {
- var cfs afero.Fs
-
- if v.IsResourceDir {
- cfs = p.BaseFs.ResourcesCache
- } else {
- cfs = fs
- }
-
- if cfs == nil {
- panic("nil fs")
- }
-
- baseDir := v.DirCompiled
-
- bfs := hugofs.NewBasePathFs(cfs, baseDir)
-
- var pruneAllRootDir string
- if k == CacheKeyModules {
- pruneAllRootDir = "pkg"
- }
-
- m[k] = NewCache(bfs, v.MaxAge, pruneAllRootDir)
- }
-
- return m, nil
-}
-
-func cleanID(name string) string {
- return strings.TrimPrefix(filepath.Clean(name), helpers.FilePathSeparator)
-}
-
-// AsHTTPCache returns an httpcache.Cache implementation for this file cache.
-// Note that none of the methods are protected by named locks, so you need to make sure
-// to do that in your own code.
-func (c *Cache) AsHTTPCache() httpcache.Cache {
- return &httpCache{c: c}
-}
-
-type httpCache struct {
- c *Cache
-}
-
-func (h *httpCache) Get(id string) (resp []byte, ok bool) {
- id = cleanID(id)
- b, removed := h.c.getBytesAndRemoveIfExpired(id)
-
- return b, !removed
-}
-
-func (h *httpCache) Set(id string, resp []byte) {
- if h.c.maxAge == 0 {
- return
- }
-
- id = cleanID(id)
-
- if err := h.c.writeReader(id, bytes.NewReader(resp)); err != nil {
- panic(err)
- }
-}
-
-func (h *httpCache) Delete(key string) {
- h.c.Fs.Remove(key)
-}
diff --git a/cache/filecache/filecache_config.go b/cache/filecache/filecache_config.go
deleted file mode 100644
index a71ddb474..000000000
--- a/cache/filecache/filecache_config.go
+++ /dev/null
@@ -1,247 +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 filecache provides a file based cache for Hugo.
-package filecache
-
-import (
- "errors"
- "fmt"
- "path"
- "path/filepath"
- "strings"
- "time"
-
- "github.com/gohugoio/hugo/common/maps"
- "github.com/gohugoio/hugo/config"
-
- "github.com/mitchellh/mapstructure"
- "github.com/spf13/afero"
-)
-
-const (
- resourcesGenDir = ":resourceDir/_gen"
- cacheDirProject = ":cacheDir/:project"
-)
-
-var defaultCacheConfig = FileCacheConfig{
- MaxAge: -1, // Never expire
- Dir: cacheDirProject,
-}
-
-const (
- CacheKeyGetJSON = "getjson"
- CacheKeyGetCSV = "getcsv"
- CacheKeyImages = "images"
- CacheKeyAssets = "assets"
- CacheKeyModules = "modules"
- CacheKeyGetResource = "getresource"
- CacheKeyMisc = "misc"
-)
-
-type Configs map[string]FileCacheConfig
-
-// For internal use.
-func (c Configs) CacheDirModules() string {
- return c[CacheKeyModules].DirCompiled
-}
-
-var defaultCacheConfigs = Configs{
- CacheKeyModules: {
- MaxAge: -1,
- Dir: ":cacheDir/modules",
- },
- CacheKeyGetJSON: defaultCacheConfig,
- CacheKeyGetCSV: defaultCacheConfig,
- CacheKeyImages: {
- MaxAge: -1,
- Dir: resourcesGenDir,
- },
- CacheKeyAssets: {
- MaxAge: -1,
- Dir: resourcesGenDir,
- },
- CacheKeyGetResource: {
- MaxAge: -1, // Never expire
- Dir: cacheDirProject,
- },
- CacheKeyMisc: {
- MaxAge: -1,
- Dir: cacheDirProject,
- },
-}
-
-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.
- // 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
- DirCompiled string `json:"-"`
-
- // Will resources/_gen will get its own composite filesystem that
- // also checks any theme.
- IsResourceDir bool `json:"-"`
-}
-
-// GetJSONCache gets the file cache for getJSON.
-func (f Caches) GetJSONCache() *Cache {
- return f[CacheKeyGetJSON]
-}
-
-// GetCSVCache gets the file cache for getCSV.
-func (f Caches) GetCSVCache() *Cache {
- return f[CacheKeyGetCSV]
-}
-
-// ImageCache gets the file cache for processed images.
-func (f Caches) ImageCache() *Cache {
- return f[CacheKeyImages]
-}
-
-// ModulesCache gets the file cache for Hugo Modules.
-func (f Caches) ModulesCache() *Cache {
- return f[CacheKeyModules]
-}
-
-// AssetsCache gets the file cache for assets (processed resources, SCSS etc.).
-func (f Caches) AssetsCache() *Cache {
- return f[CacheKeyAssets]
-}
-
-// MiscCache gets the file cache for miscellaneous stuff.
-func (f Caches) MiscCache() *Cache {
- return f[CacheKeyMisc]
-}
-
-// GetResourceCache gets the file cache for remote resources.
-func (f Caches) GetResourceCache() *Cache {
- return f[CacheKeyGetResource]
-}
-
-func DecodeConfig(fs afero.Fs, bcfg config.BaseConfig, m map[string]any) (Configs, error) {
- c := make(Configs)
- valid := make(map[string]bool)
- // Add defaults
- for k, v := range defaultCacheConfigs {
- c[k] = v
- valid[k] = true
- }
-
- _, isOsFs := fs.(*afero.OsFs)
-
- for k, v := range m {
- if _, ok := v.(maps.Params); !ok {
- continue
- }
- cc := defaultCacheConfig
-
- dc := &mapstructure.DecoderConfig{
- Result: &cc,
- DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
- WeaklyTypedInput: true,
- }
-
- decoder, err := mapstructure.NewDecoder(dc)
- if err != nil {
- return c, err
- }
-
- if err := decoder.Decode(v); err != nil {
- 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, fmt.Errorf("%q is not a valid cache name", name)
- }
-
- c[name] = cc
- }
-
- for k, v := range c {
- dir := filepath.ToSlash(filepath.Clean(v.Dir))
- hadSlash := strings.HasPrefix(dir, "/")
- parts := strings.Split(dir, "/")
-
- for i, part := range parts {
- if strings.HasPrefix(part, ":") {
- resolved, isResource, err := resolveDirPlaceholder(fs, bcfg, part)
- if err != nil {
- return c, err
- }
- if isResource {
- v.IsResourceDir = true
- }
- parts[i] = resolved
- }
- }
-
- dir = path.Join(parts...)
- if hadSlash {
- dir = "/" + dir
- }
- v.DirCompiled = filepath.Clean(filepath.FromSlash(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.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.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.DirCompiled = filepath.Join(v.DirCompiled, FilecacheRootDirname, k)
- } else {
- v.DirCompiled = filepath.Join(v.DirCompiled, k)
- }
-
- c[k] = v
- }
-
- return c, nil
-}
-
-// Resolves :resourceDir => /myproject/resources etc., :cacheDir => ...
-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":
- return bcfg.CacheDir, false, nil
- case ":project":
- return filepath.Base(bcfg.WorkingDir), false, nil
- }
-
- 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
deleted file mode 100644
index c6d346dfc..000000000
--- a/cache/filecache/filecache_config_test.go
+++ /dev/null
@@ -1,146 +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 filecache_test
-
-import (
- "path/filepath"
- "runtime"
- "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"
-)
-
-func TestDecodeConfig(t *testing.T) {
- t.Parallel()
-
- c := qt.New(t)
-
- configStr := `
-resourceDir = "myresources"
-contentDir = "content"
-dataDir = "data"
-i18nDir = "i18n"
-layoutDir = "layouts"
-assetDir = "assets"
-archetypeDir = "archetypes"
-
-[caches]
-[caches.getJSON]
-maxAge = "10m"
-dir = "/path/to/c1"
-[caches.getCSV]
-maxAge = "11h"
-dir = "/path/to/c2"
-[caches.images]
-dir = "/path/to/c3"
-[caches.getResource]
-dir = "/path/to/c4"
-`
-
- cfg, err := config.FromConfigString(configStr, "toml")
- c.Assert(err, qt.IsNil)
- fs := afero.NewMemMapFs()
- 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.DirCompiled, qt.Equals, filepath.FromSlash("/path/to/c2/filecache/getcsv"))
-
- c3 := decoded["images"]
- c.Assert(c3.MaxAge, qt.Equals, time.Duration(-1))
- 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.DirCompiled, qt.Equals, filepath.FromSlash("/path/to/c4/filecache/getresource"))
-}
-
-func TestDecodeConfigIgnoreCache(t *testing.T) {
- t.Parallel()
-
- c := qt.New(t)
-
- configStr := `
-resourceDir = "myresources"
-contentDir = "content"
-dataDir = "data"
-i18nDir = "i18n"
-layoutDir = "layouts"
-assetDir = "assets"
-archeTypedir = "archetypes"
-
-ignoreCache = true
-[caches]
-[caches.getJSON]
-maxAge = 1234
-dir = "/path/to/c1"
-[caches.getCSV]
-maxAge = 3456
-dir = "/path/to/c2"
-[caches.images]
-dir = "/path/to/c3"
-[caches.getResource]
-dir = "/path/to/c4"
-`
-
- cfg, err := config.FromConfigString(configStr, "toml")
- c.Assert(err, qt.IsNil)
- fs := afero.NewMemMapFs()
- 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))
- }
-}
-
-func TestDecodeConfigDefault(t *testing.T) {
- c := qt.New(t)
- cfg := config.New()
-
- if runtime.GOOS == "windows" {
- cfg.Set("resourceDir", "c:\\cache\\resources")
- cfg.Set("cacheDir", "c:\\cache\\thecache")
-
- } else {
- 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)
-
- imgConfig := decoded[filecache.CacheKeyImages]
- jsonConfig := decoded[filecache.CacheKeyGetJSON]
-
- if runtime.GOOS == "windows" {
- c.Assert(imgConfig.DirCompiled, qt.Equals, filepath.FromSlash("_gen/images"))
- } else {
- c.Assert(imgConfig.DirCompiled, qt.Equals, "_gen/images")
- c.Assert(jsonConfig.DirCompiled, qt.Equals, "/cache/thecache/hugoproject/filecache/getjson")
- }
-
- c.Assert(imgConfig.IsResourceDir, qt.Equals, true)
- c.Assert(jsonConfig.IsResourceDir, qt.Equals, false)
-}
diff --git a/cache/filecache/filecache_integration_test.go b/cache/filecache/filecache_integration_test.go
deleted file mode 100644
index 1e920c29f..000000000
--- a/cache/filecache/filecache_integration_test.go
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package filecache_test
-
-import (
- "path/filepath"
- "testing"
- "time"
-
- "github.com/bep/logg"
- 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.NewIntegrationTestBuilder(
- hugolib.IntegrationTestConfig{T: t, TxtarString: files, RunGC: true, NeedsOsFS: true},
- ).Build()
-
- _, 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/index.html --
-{{ warnf "HOME!" }}
-{{ $img := resources.GetMatch "**.png" }}
-{{ $img = $img.Resize "3x3" }}
-{{ $img.RelPermalink }}
-
-
-
-`
-
- b := hugolib.NewIntegrationTestBuilder(
- hugolib.IntegrationTestConfig{T: t, TxtarString: files, Running: true, RunGC: true, NeedsOsFS: true, LogLevel: logg.LevelInfo},
- ).Build()
-
- 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
deleted file mode 100644
index 6f224cef4..000000000
--- a/cache/filecache/filecache_pruner.go
+++ /dev/null
@@ -1,137 +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 filecache
-
-import (
- "fmt"
- "io"
- "os"
-
- "github.com/gohugoio/hugo/common/herrors"
- "github.com/gohugoio/hugo/hugofs"
-
- "github.com/spf13/afero"
-)
-
-// Prune removes expired and unused items from this cache.
-// The last one requires a full build so the cache usage can be tracked.
-// Note that we operate directly on the filesystem here, so this is not
-// thread safe.
-func (c Caches) Prune() (int, error) {
- counter := 0
- for k, cache := range c {
- count, err := cache.Prune(false)
-
- counter += count
-
- if err != nil {
- if herrors.IsNotExist(err) {
- continue
- }
- return counter, fmt.Errorf("failed to prune cache %q: %w", k, err)
- }
-
- }
-
- return counter, nil
-}
-
-// 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 err := c.init(); err != nil {
- return 0, err
- }
-
- counter := 0
-
- err := afero.Walk(c.Fs, "", func(name string, info os.FileInfo, err error) error {
- if info == nil {
- return nil
- }
-
- name = cleanID(name)
-
- if info.IsDir() {
- f, err := c.Fs.Open(name)
- if err != nil {
- // This cache dir may not exist.
- return nil
- }
- _, err = f.Readdirnames(1)
- f.Close()
- if err == io.EOF {
- // Empty dir.
- if name == "." {
- // e.g. /_gen/images -- keep it even if empty.
- err = nil
- } else {
- err = c.Fs.Remove(name)
- }
- }
-
- if err != nil && !herrors.IsNotExist(err) {
- return err
- }
-
- return nil
- }
-
- shouldRemove := force || c.isExpired(info.ModTime())
-
- if !shouldRemove && len(c.nlocker.seen) > 0 {
- // Remove it if it's not been touched/used in the last build.
- _, seen := c.nlocker.seen[name]
- shouldRemove = !seen
- }
-
- if shouldRemove {
- err := c.Fs.Remove(name)
- if err == nil {
- counter++
- }
-
- if err != nil && !herrors.IsNotExist(err) {
- return err
- }
-
- }
-
- return nil
- })
-
- return counter, err
-}
-
-func (c *Cache) pruneRootDir(force bool) (int, error) {
- if err := c.init(); err != nil {
- return 0, err
- }
- info, err := c.Fs.Stat(c.pruneAllRootDir)
- if err != nil {
- if herrors.IsNotExist(err) {
- return 0, nil
- }
- return 0, err
- }
-
- if !force && !c.isExpired(info.ModTime()) {
- return 0, nil
- }
-
- return hugofs.MakeReadableAndRemoveAllModulePkgDir(c.Fs, c.pruneAllRootDir)
-}
diff --git a/cache/filecache/filecache_pruner_test.go b/cache/filecache/filecache_pruner_test.go
deleted file mode 100644
index b49ba7645..000000000
--- a/cache/filecache/filecache_pruner_test.go
+++ /dev/null
@@ -1,111 +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 filecache_test
-
-import (
- "fmt"
- "testing"
- "time"
-
- "github.com/gohugoio/hugo/cache/filecache"
- "github.com/spf13/afero"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestPrune(t *testing.T) {
- t.Parallel()
-
- c := qt.New(t)
-
- configStr := `
-resourceDir = "myresources"
-contentDir = "content"
-dataDir = "data"
-i18nDir = "i18n"
-layoutDir = "layouts"
-assetDir = "assets"
-archeTypedir = "archetypes"
-
-[caches]
-[caches.getjson]
-maxAge = "200ms"
-dir = "/cache/c"
-[caches.getcsv]
-maxAge = "200ms"
-dir = "/cache/d"
-[caches.assets]
-maxAge = "200ms"
-dir = ":resourceDir/_gen"
-[caches.images]
-maxAge = "200ms"
-dir = ":resourceDir/_gen"
-`
-
- for _, name := range []string{filecache.CacheKeyGetCSV, filecache.CacheKeyGetJSON, filecache.CacheKeyAssets, filecache.CacheKeyImages} {
- msg := qt.Commentf("cache: %s", name)
- p := newPathsSpec(t, afero.NewMemMapFs(), configStr)
- caches, err := filecache.NewCaches(p)
- c.Assert(err, qt.IsNil)
- cache := caches[name]
- for i := range 10 {
- id := fmt.Sprintf("i%d", i)
- cache.GetOrCreateBytes(id, func() ([]byte, error) {
- return []byte("abc"), nil
- })
- if i == 4 {
- // This will expire the first 5
- time.Sleep(201 * time.Millisecond)
- }
- }
-
- count, err := caches.Prune()
- c.Assert(err, qt.IsNil)
- c.Assert(count, qt.Equals, 5, msg)
-
- for i := range 10 {
- id := fmt.Sprintf("i%d", i)
- v := cache.GetString(id)
- if i < 5 {
- c.Assert(v, qt.Equals, "")
- } else {
- c.Assert(v, qt.Equals, "abc")
- }
- }
-
- caches, err = filecache.NewCaches(p)
- c.Assert(err, qt.IsNil)
- cache = caches[name]
- // Touch one and then prune.
- cache.GetOrCreateBytes("i5", func() ([]byte, error) {
- return []byte("abc"), nil
- })
-
- count, err = caches.Prune()
- c.Assert(err, qt.IsNil)
- c.Assert(count, qt.Equals, 4)
-
- // Now only the i5 should be left.
- for i := range 10 {
- id := fmt.Sprintf("i%d", i)
- v := cache.GetString(id)
- if i != 5 {
- c.Assert(v, qt.Equals, "")
- } else {
- c.Assert(v, qt.Equals, "abc")
- }
- }
-
- }
-}
diff --git a/cache/filecache/filecache_test.go b/cache/filecache/filecache_test.go
deleted file mode 100644
index a30aaa50b..000000000
--- a/cache/filecache/filecache_test.go
+++ /dev/null
@@ -1,276 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package filecache_test
-
-import (
- "errors"
- "fmt"
- "io"
- "strings"
- "sync"
- "testing"
- "time"
-
- "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"
- "github.com/spf13/afero"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestFileCache(t *testing.T) {
- t.Parallel()
- c := qt.New(t)
-
- tempWorkingDir := t.TempDir()
- tempCacheDir := t.TempDir()
-
- osfs := afero.NewOsFs()
-
- for _, test := range []struct {
- cacheDir string
- workingDir string
- }{
- // Run with same dirs twice to make sure that works.
- {tempCacheDir, tempWorkingDir},
- {tempCacheDir, tempWorkingDir},
- } {
-
- configStr := `
-workingDir = "WORKING_DIR"
-resourceDir = "resources"
-cacheDir = "CACHEDIR"
-contentDir = "content"
-dataDir = "data"
-i18nDir = "i18n"
-layoutDir = "layouts"
-assetDir = "assets"
-archeTypedir = "archetypes"
-
-[caches]
-[caches.getJSON]
-maxAge = "10h"
-dir = ":cacheDir/c"
-
-`
-
- winPathSep := "\\\\"
-
- replacer := strings.NewReplacer("CACHEDIR", test.cacheDir, "WORKING_DIR", test.workingDir)
-
- configStr = replacer.Replace(configStr)
- configStr = strings.Replace(configStr, "\\", winPathSep, -1)
-
- p := newPathsSpec(t, osfs, configStr)
-
- caches, err := filecache.NewCaches(p)
- c.Assert(err, qt.IsNil)
-
- cache := caches.Get("GetJSON")
- c.Assert(cache, qt.Not(qt.IsNil))
-
- cache = caches.Get("Images")
- c.Assert(cache, qt.Not(qt.IsNil))
-
- rf := func(s string) func() (io.ReadCloser, error) {
- return func() (io.ReadCloser, error) {
- return struct {
- io.ReadSeeker
- io.Closer
- }{
- strings.NewReader(s),
- io.NopCloser(nil),
- }, nil
- }
- }
-
- bf := func() ([]byte, error) {
- return []byte("bcd"), nil
- }
-
- for _, ca := range []*filecache.Cache{caches.ImageCache(), caches.AssetsCache(), caches.GetJSONCache(), caches.GetCSVCache()} {
- 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, _ := io.ReadAll(r)
- r.Close()
- c.Assert(string(b), qt.Equals, "abc")
-
- info, b, err = ca.GetOrCreateBytes("b", bf)
- c.Assert(err, qt.IsNil)
- c.Assert(r, qt.Not(qt.IsNil))
- c.Assert(info.Name, qt.Equals, "b")
- c.Assert(string(b), qt.Equals, "bcd")
-
- _, b, err = ca.GetOrCreateBytes("a", bf)
- c.Assert(err, qt.IsNil)
- c.Assert(string(b), qt.Equals, "abc")
-
- _, r, err = ca.GetOrCreate("a", rf("bcd"))
- c.Assert(err, qt.IsNil)
- 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!")
-
- 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, _ := io.ReadAll(r)
- r.Close()
- c.Assert(string(b), qt.Equals, "Hugo is great!")
-
- info, b, err = caches.ImageCache().GetBytes("mykey")
- c.Assert(err, qt.IsNil)
- c.Assert(info.Name, qt.Equals, "mykey")
- c.Assert(string(b), qt.Equals, "Hugo is great!")
-
- }
-}
-
-func TestFileCacheConcurrent(t *testing.T) {
- t.Parallel()
-
- c := qt.New(t)
-
- configStr := `
-resourceDir = "myresources"
-contentDir = "content"
-dataDir = "data"
-i18nDir = "i18n"
-layoutDir = "layouts"
-assetDir = "assets"
-archeTypedir = "archetypes"
-
-[caches]
-[caches.getjson]
-maxAge = "1s"
-dir = "/cache/c"
-
-`
-
- p := newPathsSpec(t, afero.NewMemMapFs(), configStr)
-
- caches, err := filecache.NewCaches(p)
- c.Assert(err, qt.IsNil)
-
- const cacheName = "getjson"
-
- filenameData := func(i int) (string, string) {
- data := fmt.Sprintf("data: %d", i)
- filename := fmt.Sprintf("file%d", i)
- return filename, data
- }
-
- var wg sync.WaitGroup
-
- for i := range 50 {
- wg.Add(1)
- go func(i int) {
- defer wg.Done()
- for range 20 {
- ca := caches.Get(cacheName)
- c.Assert(ca, qt.Not(qt.IsNil))
- filename, data := filenameData(i)
- _, r, err := ca.GetOrCreate(filename, func() (io.ReadCloser, error) {
- return hugio.ToReadCloser(strings.NewReader(data)), nil
- })
- c.Assert(err, qt.IsNil)
- b, _ := io.ReadAll(r)
- r.Close()
- c.Assert(string(b), qt.Equals, data)
- // Trigger some expiration.
- time.Sleep(50 * time.Millisecond)
- }
- }(i)
-
- }
- wg.Wait()
-}
-
-func TestFileCacheReadOrCreateErrorInRead(t *testing.T) {
- t.Parallel()
- c := qt.New(t)
-
- var result string
-
- 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 filecache.ErrFatal
- }
- return errors.New("fail")
- }
-
- b, _ := io.ReadAll(r)
- result = string(b)
-
- return nil
- }
- }
-
- 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))
- return err
- }
- }
-
- cache := filecache.NewCache(afero.NewMemMapFs(), 100*time.Hour, "")
-
- const id = "a32"
-
- _, err := cache.ReadOrCreate(id, rf(0), bf("v1"))
- c.Assert(err, qt.IsNil)
- c.Assert(result, qt.Equals, "v1")
- _, err = cache.ReadOrCreate(id, rf(0), bf("v2"))
- c.Assert(err, qt.IsNil)
- c.Assert(result, qt.Equals, "v1")
- _, err = cache.ReadOrCreate(id, rf(1), bf("v3"))
- c.Assert(err, qt.IsNil)
- c.Assert(result, qt.Equals, "v3")
- _, err = cache.ReadOrCreate(id, rf(2), bf("v3"))
- 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)
- acfg := testconfig.GetTestConfig(fs, cfg)
- p, err := helpers.NewPathSpec(hugofs.NewFrom(fs, acfg.BaseConfig()), acfg, nil)
- c.Assert(err, qt.IsNil)
- return p
-}
diff --git a/cache/httpcache/httpcache.go b/cache/httpcache/httpcache.go
deleted file mode 100644
index bd6d4bf7d..000000000
--- a/cache/httpcache/httpcache.go
+++ /dev/null
@@ -1,229 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package httpcache
-
-import (
- "encoding/json"
- "time"
-
- "github.com/gobwas/glob"
- "github.com/gohugoio/hugo/common/predicate"
- "github.com/gohugoio/hugo/config"
- "github.com/mitchellh/mapstructure"
-)
-
-// DefaultConfig holds the default configuration for the HTTP cache.
-var DefaultConfig = Config{
- Cache: Cache{
- For: GlobMatcher{
- Excludes: []string{"**"},
- },
- },
- Polls: []PollConfig{
- {
- For: GlobMatcher{
- Includes: []string{"**"},
- },
- Disable: true,
- },
- },
-}
-
-// Config holds the configuration for the HTTP cache.
-type Config struct {
- // Configures the HTTP cache behavior (RFC 9111).
- // When this is not enabled for a resource, Hugo will go straight to the file cache.
- Cache Cache
-
- // Polls holds a list of configurations for polling remote resources to detect changes in watch mode.
- // This can be disabled for some resources, typically if they are known to not change.
- Polls []PollConfig
-}
-
-type Cache struct {
- // Enable HTTP cache behavior (RFC 9111) for these resources.
- For GlobMatcher
-}
-
-func (c *Config) Compile() (ConfigCompiled, error) {
- var cc ConfigCompiled
-
- p, err := c.Cache.For.CompilePredicate()
- if err != nil {
- return cc, err
- }
-
- cc.For = p
-
- for _, pc := range c.Polls {
-
- p, err := pc.For.CompilePredicate()
- if err != nil {
- return cc, err
- }
-
- cc.PollConfigs = append(cc.PollConfigs, PollConfigCompiled{
- For: p,
- Config: pc,
- })
- }
-
- return cc, nil
-}
-
-// PollConfig holds the configuration for polling remote resources to detect changes in watch mode.
-type PollConfig struct {
- // What remote resources to apply this configuration to.
- For GlobMatcher
-
- // Disable polling for this configuration.
- Disable bool
-
- // Low is the lower bound for the polling interval.
- // This is the starting point when the resource has recently changed,
- // if that resource stops changing, the polling interval will gradually increase towards High.
- Low time.Duration
-
- // High is the upper bound for the polling interval.
- // This is the interval used when the resource is stable.
- High time.Duration
-}
-
-func (c PollConfig) MarshalJSON() (b []byte, err error) {
- // Marshal the durations as strings.
- type Alias PollConfig
- return json.Marshal(&struct {
- Low string
- High string
- Alias
- }{
- Low: c.Low.String(),
- High: c.High.String(),
- Alias: (Alias)(c),
- })
-}
-
-type GlobMatcher struct {
- // Excludes holds a list of glob patterns that will be excluded.
- Excludes []string
-
- // Includes holds a list of glob patterns that will be included.
- Includes []string
-}
-
-func (gm GlobMatcher) IsZero() bool {
- return len(gm.Includes) == 0 && len(gm.Excludes) == 0
-}
-
-type ConfigCompiled struct {
- For predicate.P[string]
- PollConfigs []PollConfigCompiled
-}
-
-func (c *ConfigCompiled) PollConfigFor(s string) PollConfigCompiled {
- for _, pc := range c.PollConfigs {
- if pc.For(s) {
- return pc
- }
- }
- return PollConfigCompiled{}
-}
-
-func (c *ConfigCompiled) IsPollingDisabled() bool {
- for _, pc := range c.PollConfigs {
- if !pc.Config.Disable {
- return false
- }
- }
- return true
-}
-
-type PollConfigCompiled struct {
- For predicate.P[string]
- Config PollConfig
-}
-
-func (p PollConfigCompiled) IsZero() bool {
- return p.For == nil
-}
-
-func (gm *GlobMatcher) CompilePredicate() (func(string) bool, error) {
- if gm.IsZero() {
- panic("no includes or excludes")
- }
- var p predicate.P[string]
- for _, include := range gm.Includes {
- g, err := glob.Compile(include, '/')
- if err != nil {
- return nil, err
- }
- fn := func(s string) bool {
- return g.Match(s)
- }
- p = p.Or(fn)
- }
-
- for _, exclude := range gm.Excludes {
- g, err := glob.Compile(exclude, '/')
- if err != nil {
- return nil, err
- }
- fn := func(s string) bool {
- return !g.Match(s)
- }
- p = p.And(fn)
- }
-
- return p, nil
-}
-
-func DecodeConfig(_ 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
deleted file mode 100644
index 4d6a5f718..000000000
--- a/cache/httpcache/httpcache_integration_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package httpcache_test
-
-import (
- "testing"
- "time"
-
- qt "github.com/frankban/quicktest"
- "github.com/gohugoio/hugo/hugolib"
-)
-
-func TestConfigCustom(t *testing.T) {
- 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
deleted file mode 100644
index 60c07d056..000000000
--- a/cache/httpcache/httpcache_test.go
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package httpcache
-
-import (
- "testing"
-
- qt "github.com/frankban/quicktest"
- "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]interface{}{})
- 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/partitioned_lazy_cache.go b/cache/partitioned_lazy_cache.go
new file mode 100644
index 000000000..9baf0377d
--- /dev/null
+++ b/cache/partitioned_lazy_cache.go
@@ -0,0 +1,80 @@
+// 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 cache
+
+import (
+ "sync"
+)
+
+// Partition represents a cache partition where Load is the callback
+// for when the partition is needed.
+type Partition struct {
+ Key string
+ Load func() (map[string]interface{}, error)
+}
+
+type lazyPartition struct {
+ initSync sync.Once
+ cache map[string]interface{}
+ load func() (map[string]interface{}, error)
+}
+
+func (l *lazyPartition) init() error {
+ var err error
+ l.initSync.Do(func() {
+ var c map[string]interface{}
+ c, err = l.load()
+ l.cache = c
+ })
+
+ return err
+}
+
+// PartitionedLazyCache is a lazily loaded cache paritioned by a supplied string key.
+type PartitionedLazyCache struct {
+ partitions map[string]*lazyPartition
+}
+
+// NewPartitionedLazyCache creates a new NewPartitionedLazyCache with the supplied
+// partitions.
+func NewPartitionedLazyCache(partitions ...Partition) *PartitionedLazyCache {
+ lazyPartitions := make(map[string]*lazyPartition, len(partitions))
+ for _, partition := range partitions {
+ lazyPartitions[partition.Key] = &lazyPartition{load: partition.Load}
+ }
+ cache := &PartitionedLazyCache{partitions: lazyPartitions}
+
+ return cache
+}
+
+// Get initializes the partition if not already done so, then looks up the given
+// key in the given partition, returns nil if no value found.
+func (c *PartitionedLazyCache) Get(partition, key string) (interface{}, error) {
+ p, found := c.partitions[partition]
+
+ if !found {
+ return nil, nil
+ }
+
+ if err := p.init(); err != nil {
+ return nil, err
+ }
+
+ if v, found := p.cache[key]; found {
+ return v, nil
+ }
+
+ return nil, nil
+
+}
diff --git a/cache/partitioned_lazy_cache_test.go b/cache/partitioned_lazy_cache_test.go
new file mode 100644
index 000000000..ba8b6a454
--- /dev/null
+++ b/cache/partitioned_lazy_cache_test.go
@@ -0,0 +1,138 @@
+// 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 cache
+
+import (
+ "errors"
+ "sync"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestNewPartitionedLazyCache(t *testing.T) {
+ t.Parallel()
+
+ assert := require.New(t)
+
+ p1 := Partition{
+ Key: "p1",
+ Load: func() (map[string]interface{}, error) {
+ return map[string]interface{}{
+ "p1_1": "p1v1",
+ "p1_2": "p1v2",
+ "p1_nil": nil,
+ }, nil
+ },
+ }
+
+ p2 := Partition{
+ Key: "p2",
+ Load: func() (map[string]interface{}, error) {
+ return map[string]interface{}{
+ "p2_1": "p2v1",
+ "p2_2": "p2v2",
+ "p2_3": "p2v3",
+ }, nil
+ },
+ }
+
+ cache := NewPartitionedLazyCache(p1, p2)
+
+ v, err := cache.Get("p1", "p1_1")
+ assert.NoError(err)
+ assert.Equal("p1v1", v)
+
+ v, err = cache.Get("p1", "p2_1")
+ assert.NoError(err)
+ assert.Nil(v)
+
+ v, err = cache.Get("p1", "p1_nil")
+ assert.NoError(err)
+ assert.Nil(v)
+
+ v, err = cache.Get("p2", "p2_3")
+ assert.NoError(err)
+ assert.Equal("p2v3", v)
+
+ v, err = cache.Get("doesnotexist", "p1_1")
+ assert.NoError(err)
+ assert.Nil(v)
+
+ v, err = cache.Get("p1", "doesnotexist")
+ assert.NoError(err)
+ assert.Nil(v)
+
+ errorP := Partition{
+ Key: "p3",
+ Load: func() (map[string]interface{}, error) {
+ return nil, errors.New("Failed")
+ },
+ }
+
+ cache = NewPartitionedLazyCache(errorP)
+
+ v, err = cache.Get("p1", "doesnotexist")
+ assert.NoError(err)
+ assert.Nil(v)
+
+ _, err = cache.Get("p3", "doesnotexist")
+ assert.Error(err)
+
+}
+
+func TestConcurrentPartitionedLazyCache(t *testing.T) {
+ t.Parallel()
+
+ assert := require.New(t)
+
+ var wg sync.WaitGroup
+
+ p1 := Partition{
+ Key: "p1",
+ Load: func() (map[string]interface{}, error) {
+ return map[string]interface{}{
+ "p1_1": "p1v1",
+ "p1_2": "p1v2",
+ "p1_nil": nil,
+ }, nil
+ },
+ }
+
+ p2 := Partition{
+ Key: "p2",
+ Load: func() (map[string]interface{}, error) {
+ return map[string]interface{}{
+ "p2_1": "p2v1",
+ "p2_2": "p2v2",
+ "p2_3": "p2v3",
+ }, nil
+ },
+ }
+
+ cache := NewPartitionedLazyCache(p1, p2)
+
+ for i := 0; i < 100; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ for j := 0; j < 10; j++ {
+ v, err := cache.Get("p1", "p1_1")
+ assert.NoError(err)
+ assert.Equal("p1v1", v)
+ }
+ }()
+ }
+ wg.Wait()
+}
diff --git a/check_gofmt.sh b/check_gofmt.sh
deleted file mode 100755
index c77517d3f..000000000
--- a/check_gofmt.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/usr/bin/env bash
-diff <(gofmt -d .) <(printf '')
\ No newline at end of file
diff --git a/codegen/methods.go b/codegen/methods.go
deleted file mode 100644
index 08ac97b00..000000000
--- a/codegen/methods.go
+++ /dev/null
@@ -1,540 +0,0 @@
-// 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.
-//
-// 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 codegen contains helpers for code generation.
-package codegen
-
-import (
- "fmt"
- "go/ast"
- "go/parser"
- "go/token"
- "os"
- "path"
- "path/filepath"
- "reflect"
- "regexp"
- "slices"
- "sort"
- "strings"
- "sync"
-)
-
-// Make room for insertions
-const weightWidth = 1000
-
-// NewInspector creates a new Inspector given a source root.
-func NewInspector(root string) *Inspector {
- return &Inspector{ProjectRootDir: root}
-}
-
-// Inspector provides methods to help code generation. It uses a combination
-// of reflection and source code AST to do the heavy lifting.
-type Inspector struct {
- ProjectRootDir string
-
- init sync.Once
-
- // Determines method order. Go's reflect sorts lexicographically, so
- // we must parse the source to preserve this order.
- methodWeight map[string]map[string]int
-}
-
-// MethodsFromTypes create a method set from the include slice, excluding any
-// method in exclude.
-func (c *Inspector) MethodsFromTypes(include []reflect.Type, exclude []reflect.Type) Methods {
- c.parseSource()
-
- var methods Methods
-
- excludes := make(map[string]bool)
-
- if len(exclude) > 0 {
- for _, m := range c.MethodsFromTypes(exclude, nil) {
- excludes[m.Name] = true
- }
- }
-
- // There may be overlapping interfaces in types. Do a simple check for now.
- seen := make(map[string]bool)
-
- nameAndPackage := func(t reflect.Type) (string, string) {
- var name, pkg string
-
- isPointer := t.Kind() == reflect.Ptr
-
- if isPointer {
- t = t.Elem()
- }
-
- pkgPrefix := ""
- if pkgPath := t.PkgPath(); pkgPath != "" {
- pkgPath = strings.TrimSuffix(pkgPath, "/")
- _, shortPath := path.Split(pkgPath)
- pkgPrefix = shortPath + "."
- pkg = pkgPath
- }
-
- name = t.Name()
- if name == "" {
- // interface{}
- name = t.String()
- }
-
- if isPointer {
- pkgPrefix = "*" + pkgPrefix
- }
-
- name = pkgPrefix + name
-
- return name, pkg
- }
-
- for _, t := range include {
- for i := range t.NumMethod() {
-
- m := t.Method(i)
- if excludes[m.Name] || seen[m.Name] {
- continue
- }
-
- seen[m.Name] = true
-
- if m.PkgPath != "" {
- // Not exported
- continue
- }
-
- numIn := m.Type.NumIn()
-
- ownerName, _ := nameAndPackage(t)
-
- method := Method{Owner: t, OwnerName: ownerName, Name: m.Name}
-
- for i := range numIn {
- in := m.Type.In(i)
-
- name, pkg := nameAndPackage(in)
-
- if pkg != "" {
- method.Imports = append(method.Imports, pkg)
- }
-
- method.In = append(method.In, name)
- }
-
- numOut := m.Type.NumOut()
-
- if numOut > 0 {
- for i := range numOut {
- out := m.Type.Out(i)
- name, pkg := nameAndPackage(out)
-
- if pkg != "" {
- method.Imports = append(method.Imports, pkg)
- }
-
- method.Out = append(method.Out, name)
- }
- }
-
- methods = append(methods, method)
- }
- }
-
- sort.SliceStable(methods, func(i, j int) bool {
- mi, mj := methods[i], methods[j]
-
- wi := c.methodWeight[mi.OwnerName][mi.Name]
- wj := c.methodWeight[mj.OwnerName][mj.Name]
-
- if wi == wj {
- return mi.Name < mj.Name
- }
-
- return wi < wj
- })
-
- return methods
-}
-
-func (c *Inspector) parseSource() {
- c.init.Do(func() {
- if !strings.Contains(c.ProjectRootDir, "hugo") {
- panic("dir must be set to the Hugo root")
- }
-
- c.methodWeight = make(map[string]map[string]int)
- dirExcludes := regexp.MustCompile("docs|examples")
- fileExcludes := regexp.MustCompile("autogen")
- var filenames []string
-
- filepath.Walk(c.ProjectRootDir, func(path string, info os.FileInfo, err error) error {
- if info.IsDir() {
- if dirExcludes.MatchString(info.Name()) {
- return filepath.SkipDir
- }
- }
-
- if !strings.HasSuffix(path, ".go") || fileExcludes.MatchString(path) {
- return nil
- }
-
- filenames = append(filenames, path)
-
- return nil
- })
-
- for _, filename := range filenames {
-
- pkg := c.packageFromPath(filename)
-
- fset := token.NewFileSet()
- node, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)
- if err != nil {
- panic(err)
- }
-
- ast.Inspect(node, func(n ast.Node) bool {
- switch t := n.(type) {
- case *ast.TypeSpec:
- if t.Name.IsExported() {
- switch it := t.Type.(type) {
- case *ast.InterfaceType:
- iface := pkg + "." + t.Name.Name
- methodNames := collectMethodsRecursive(pkg, it.Methods.List)
- weights := make(map[string]int)
- weight := weightWidth
- for _, name := range methodNames {
- weights[name] = weight
- weight += weightWidth
- }
- c.methodWeight[iface] = weights
- }
- }
- }
- return true
- })
-
- }
-
- // Complement
- for _, v1 := range c.methodWeight {
- for k2, w := range v1 {
- if v, found := c.methodWeight[k2]; found {
- for k3, v3 := range v {
- v1[k3] = (v3 / weightWidth) + w
- }
- }
- }
- }
- })
-}
-
-func (c *Inspector) packageFromPath(p string) string {
- p = filepath.ToSlash(p)
- base := path.Base(p)
- if !strings.Contains(base, ".") {
- return base
- }
- return path.Base(strings.TrimSuffix(p, base))
-}
-
-// Method holds enough information about it to recreate it.
-type Method struct {
- // The interface we extracted this method from.
- Owner reflect.Type
-
- // String version of the above, on the form PACKAGE.NAME, e.g.
- // page.Page
- OwnerName string
-
- // Method name.
- Name string
-
- // Imports needed to satisfy the method signature.
- Imports []string
-
- // Argument types, including any package prefix, e.g. string, int, interface{},
- // net.Url
- In []string
-
- // Return types.
- Out []string
-}
-
-// Declaration creates a method declaration (without any body) for the given receiver.
-func (m Method) Declaration(receiver string) string {
- return fmt.Sprintf("func (%s %s) %s%s %s", receiverShort(receiver), receiver, m.Name, m.inStr(), m.outStr())
-}
-
-// DeclarationNamed creates a method declaration (without any body) for the given receiver
-// with named return values.
-func (m Method) DeclarationNamed(receiver string) string {
- return fmt.Sprintf("func (%s %s) %s%s %s", receiverShort(receiver), receiver, m.Name, m.inStr(), m.outStrNamed())
-}
-
-// Delegate creates a delegate call string.
-func (m Method) Delegate(receiver, delegate string) string {
- ret := ""
- if len(m.Out) > 0 {
- ret = "return "
- }
- return fmt.Sprintf("%s%s.%s.%s%s", ret, receiverShort(receiver), delegate, m.Name, m.inOutStr())
-}
-
-func (m Method) String() string {
- return m.Name + m.inStr() + " " + m.outStr() + "\n"
-}
-
-func (m Method) inOutStr() string {
- if len(m.In) == 0 {
- return "()"
- }
-
- args := make([]string, len(m.In))
- for i := range args {
- args[i] = fmt.Sprintf("arg%d", i)
- }
- return "(" + strings.Join(args, ", ") + ")"
-}
-
-func (m Method) inStr() string {
- if len(m.In) == 0 {
- return "()"
- }
-
- args := make([]string, len(m.In))
- for i := range args {
- args[i] = fmt.Sprintf("arg%d %s", i, m.In[i])
- }
- return "(" + strings.Join(args, ", ") + ")"
-}
-
-func (m Method) outStr() string {
- if len(m.Out) == 0 {
- return ""
- }
- if len(m.Out) == 1 {
- return m.Out[0]
- }
-
- return "(" + strings.Join(m.Out, ", ") + ")"
-}
-
-func (m Method) outStrNamed() string {
- if len(m.Out) == 0 {
- return ""
- }
-
- outs := make([]string, len(m.Out))
- for i := range outs {
- outs[i] = fmt.Sprintf("o%d %s", i, m.Out[i])
- }
-
- return "(" + strings.Join(outs, ", ") + ")"
-}
-
-// Methods represents a list of methods for one or more interfaces.
-// The order matches the defined order in their source file(s).
-type Methods []Method
-
-// Imports returns a sorted list of package imports needed to satisfy the
-// signatures of all methods.
-func (m Methods) Imports() []string {
- var pkgImports []string
- for _, method := range m {
- pkgImports = append(pkgImports, method.Imports...)
- }
- if len(pkgImports) > 0 {
- pkgImports = uniqueNonEmptyStrings(pkgImports)
- sort.Strings(pkgImports)
- }
- return pkgImports
-}
-
-// ToMarshalJSON creates a MarshalJSON method for these methods. Any method name
-// matching any of the regexps in excludes will be ignored.
-func (m Methods) ToMarshalJSON(receiver, pkgPath string, excludes ...string) (string, []string) {
- var sb strings.Builder
-
- r := receiverShort(receiver)
- what := firstToUpper(trimAsterisk(receiver))
- pgkName := path.Base(pkgPath)
-
- fmt.Fprintf(&sb, "func Marshal%sToJSON(%s %s) ([]byte, error) {\n", what, r, receiver)
-
- var methods Methods
- excludeRes := make([]*regexp.Regexp, len(excludes))
-
- for i, exclude := range excludes {
- excludeRes[i] = regexp.MustCompile(exclude)
- }
-
- for _, method := range m {
- // Exclude methods with arguments and incompatible return values
- if len(method.In) > 0 || len(method.Out) == 0 || len(method.Out) > 2 {
- continue
- }
-
- if len(method.Out) == 2 {
- if method.Out[1] != "error" {
- continue
- }
- }
-
- for _, re := range excludeRes {
- if re.MatchString(method.Name) {
- continue
- }
- }
-
- methods = append(methods, method)
- }
-
- for _, method := range methods {
- varn := varName(method.Name)
- if len(method.Out) == 1 {
- fmt.Fprintf(&sb, "\t%s := %s.%s()\n", varn, r, method.Name)
- } else {
- fmt.Fprintf(&sb, "\t%s, err := %s.%s()\n", varn, r, method.Name)
- fmt.Fprint(&sb, "\tif err != nil {\n\t\treturn nil, err\n\t}\n")
- }
- }
-
- fmt.Fprint(&sb, "\n\ts := struct {\n")
-
- for _, method := range methods {
- fmt.Fprintf(&sb, "\t\t%s %s\n", method.Name, typeName(method.Out[0], pgkName))
- }
-
- fmt.Fprint(&sb, "\n\t}{\n")
-
- for _, method := range methods {
- varn := varName(method.Name)
- fmt.Fprintf(&sb, "\t\t%s: %s,\n", method.Name, varn)
- }
-
- fmt.Fprint(&sb, "\n\t}\n\n")
- fmt.Fprint(&sb, "\treturn json.Marshal(&s)\n}")
-
- pkgImports := append(methods.Imports(), "encoding/json")
-
- if pkgPath != "" {
- // Exclude self
- for i, pkgImp := range pkgImports {
- if pkgImp == pkgPath {
- pkgImports = slices.Delete(pkgImports, i, i+1)
- }
- }
- }
-
- return sb.String(), pkgImports
-}
-
-func collectMethodsRecursive(pkg string, f []*ast.Field) []string {
- var methodNames []string
- for _, m := range f {
- if m.Names != nil {
- methodNames = append(methodNames, m.Names[0].Name)
- continue
- }
-
- if ident, ok := m.Type.(*ast.Ident); ok && ident.Obj != nil {
- 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.
- name := packageName(m.Type)
- if !strings.Contains(name, ".") {
- // Assume current package
- name = pkg + "." + name
- }
- methodNames = append(methodNames, name)
- }
- }
-
- return methodNames
-}
-
-func firstToLower(name string) string {
- return strings.ToLower(name[:1]) + name[1:]
-}
-
-func firstToUpper(name string) string {
- return strings.ToUpper(name[:1]) + name[1:]
-}
-
-func packageName(e ast.Expr) string {
- switch tp := e.(type) {
- case *ast.Ident:
- return tp.Name
- case *ast.SelectorExpr:
- return fmt.Sprintf("%s.%s", packageName(tp.X), packageName(tp.Sel))
- }
- return ""
-}
-
-func receiverShort(receiver string) string {
- return strings.ToLower(trimAsterisk(receiver))[:1]
-}
-
-func trimAsterisk(name string) string {
- return strings.TrimPrefix(name, "*")
-}
-
-func typeName(name, pkg string) string {
- return strings.TrimPrefix(name, pkg+".")
-}
-
-func uniqueNonEmptyStrings(s []string) []string {
- var unique []string
- set := map[string]any{}
- for _, val := range s {
- if val == "" {
- continue
- }
- if _, ok := set[val]; !ok {
- unique = append(unique, val)
- set[val] = val
- }
- }
- return unique
-}
-
-func varName(name string) string {
- name = firstToLower(name)
-
- // Adjust some reserved keywords, see https://golang.org/ref/spec#Keywords
- switch name {
- case "type":
- name = "typ"
- case "package":
- name = "pkg"
- // Not reserved, but syntax highlighters has it as a keyword.
- case "len":
- name = "length"
- }
-
- return name
-}
diff --git a/codegen/methods2_test.go b/codegen/methods2_test.go
deleted file mode 100644
index bd36b5e80..000000000
--- a/codegen/methods2_test.go
+++ /dev/null
@@ -1,20 +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 codegen
-
-type IEmbed interface {
- MethodEmbed3(s string) string
- MethodEmbed1() string
- MethodEmbed2()
-}
diff --git a/codegen/methods_test.go b/codegen/methods_test.go
deleted file mode 100644
index 0aff43d0e..000000000
--- a/codegen/methods_test.go
+++ /dev/null
@@ -1,96 +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 codegen
-
-import (
- "fmt"
- "net"
- "os"
- "reflect"
- "testing"
-
- qt "github.com/frankban/quicktest"
- "github.com/gohugoio/hugo/common/herrors"
-)
-
-func TestMethods(t *testing.T) {
- var (
- zeroIE = reflect.TypeOf((*IEmbed)(nil)).Elem()
- zeroIEOnly = reflect.TypeOf((*IEOnly)(nil)).Elem()
- zeroI = reflect.TypeOf((*I)(nil)).Elem()
- )
-
- dir, _ := os.Getwd()
- insp := NewInspector(dir)
-
- t.Run("MethodsFromTypes", func(t *testing.T) {
- c := qt.New(t)
-
- methods := insp.MethodsFromTypes([]reflect.Type{zeroI}, nil)
-
- methodsStr := fmt.Sprint(methods)
-
- c.Assert(methodsStr, qt.Contains, "Method1(arg0 herrors.ErrorContext)")
- c.Assert(methodsStr, qt.Contains, "Method7() interface {}")
- c.Assert(methodsStr, qt.Contains, "Method0() string\n Method4() string")
- c.Assert(methodsStr, qt.Contains, "MethodEmbed3(arg0 string) string\n MethodEmbed1() string")
-
- c.Assert(methods.Imports(), qt.Contains, "github.com/gohugoio/hugo/common/herrors")
- })
-
- t.Run("EmbedOnly", func(t *testing.T) {
- c := qt.New(t)
-
- methods := insp.MethodsFromTypes([]reflect.Type{zeroIEOnly}, nil)
-
- methodsStr := fmt.Sprint(methods)
-
- c.Assert(methodsStr, qt.Contains, "MethodEmbed3(arg0 string) string")
- })
-
- t.Run("ToMarshalJSON", func(t *testing.T) {
- c := qt.New(t)
-
- m, pkg := insp.MethodsFromTypes(
- []reflect.Type{zeroI},
- []reflect.Type{zeroIE}).ToMarshalJSON("*page", "page")
-
- c.Assert(m, qt.Contains, "method6 := p.Method6()")
- c.Assert(m, qt.Contains, "Method0: method0,")
- c.Assert(m, qt.Contains, "return json.Marshal(&s)")
-
- c.Assert(pkg, qt.Contains, "github.com/gohugoio/hugo/common/herrors")
- c.Assert(pkg, qt.Contains, "encoding/json")
-
- fmt.Println(pkg)
- })
-}
-
-type I interface {
- IEmbed
- Method0() string
- Method4() string
- Method1(myerr herrors.ErrorContext)
- Method3(myint int, mystring string)
- Method5() (string, error)
- Method6() *net.IP
- Method7() any
- Method8() herrors.ErrorContext
- method2()
- method9() os.FileInfo
-}
-
-type IEOnly interface {
- IEmbed
-}
diff --git a/commands/benchmark.go b/commands/benchmark.go
new file mode 100644
index 000000000..51f2be876
--- /dev/null
+++ b/commands/benchmark.go
@@ -0,0 +1,112 @@
+// 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 (
+ "os"
+ "runtime"
+ "runtime/pprof"
+ "time"
+
+ "github.com/spf13/cobra"
+ jww "github.com/spf13/jwalterweatherman"
+)
+
+var (
+ benchmarkTimes int
+ cpuProfileFile string
+ memProfileFile string
+)
+
+var benchmarkCmd = &cobra.Command{
+ Use: "benchmark",
+ Short: "Benchmark Hugo by building a site a number of times.",
+ Long: `Hugo can build a site many times over and analyze the running process
+creating a benchmark.`,
+}
+
+func init() {
+ initHugoBuilderFlags(benchmarkCmd)
+ initBenchmarkBuildingFlags(benchmarkCmd)
+
+ benchmarkCmd.Flags().StringVar(&cpuProfileFile, "cpuprofile", "", "path/filename for the CPU profile file")
+ benchmarkCmd.Flags().StringVar(&memProfileFile, "memprofile", "", "path/filename for the memory profile file")
+ benchmarkCmd.Flags().IntVarP(&benchmarkTimes, "count", "n", 13, "number of times to build the site")
+
+ benchmarkCmd.RunE = benchmark
+}
+
+func benchmark(cmd *cobra.Command, args []string) error {
+ cfg, err := InitializeConfig(benchmarkCmd)
+ if err != nil {
+ return err
+ }
+
+ c, err := newCommandeer(cfg)
+ if err != nil {
+ return err
+ }
+
+ var memProf *os.File
+ if memProfileFile != "" {
+ memProf, err = os.Create(memProfileFile)
+ if err != nil {
+ return err
+ }
+ }
+
+ var cpuProf *os.File
+ if cpuProfileFile != "" {
+ cpuProf, err = os.Create(cpuProfileFile)
+ if err != nil {
+ return err
+ }
+ }
+
+ var memStats runtime.MemStats
+ runtime.ReadMemStats(&memStats)
+ memAllocated := memStats.TotalAlloc
+ mallocs := memStats.Mallocs
+ if cpuProf != nil {
+ pprof.StartCPUProfile(cpuProf)
+ }
+
+ t := time.Now()
+ for i := 0; i < benchmarkTimes; i++ {
+ if err = c.resetAndBuildSites(false); err != nil {
+ return err
+ }
+ }
+ totalTime := time.Since(t)
+
+ if memProf != nil {
+ pprof.WriteHeapProfile(memProf)
+ memProf.Close()
+ }
+ if cpuProf != nil {
+ pprof.StopCPUProfile()
+ cpuProf.Close()
+ }
+
+ runtime.ReadMemStats(&memStats)
+ totalMemAllocated := memStats.TotalAlloc - memAllocated
+ totalMallocs := memStats.Mallocs - mallocs
+
+ jww.FEEDBACK.Println()
+ jww.FEEDBACK.Printf("Average time per operation: %vms\n", int(1000*totalTime.Seconds()/float64(benchmarkTimes)))
+ jww.FEEDBACK.Printf("Average memory allocated per operation: %vkB\n", totalMemAllocated/uint64(benchmarkTimes)/1024)
+ jww.FEEDBACK.Printf("Average allocations per operation: %v\n", totalMallocs/uint64(benchmarkTimes))
+
+ return nil
+}
diff --git a/commands/check.go b/commands/check.go
new file mode 100644
index 000000000..e5dbc1ffa
--- /dev/null
+++ b/commands/check.go
@@ -0,0 +1,23 @@
+// 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/spf13/cobra"
+)
+
+var checkCmd = &cobra.Command{
+ Use: "check",
+ Short: "Contains some verification checks",
+}
diff --git a/commands/commandeer.go b/commands/commandeer.go
index bf9655637..7de185d2f 100644
--- a/commands/commandeer.go
+++ b/commands/commandeer.go
@@ -1,4 +1,4 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
+// 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.
@@ -14,666 +14,49 @@
package commands
import (
- "context"
- "errors"
- "fmt"
- "io"
- "log"
- "os"
- "os/signal"
- "path/filepath"
- "runtime"
- "strings"
- "sync"
- "sync/atomic"
- "syscall"
- "time"
-
- "go.uber.org/automaxprocs/maxprocs"
-
- "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/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/hugolib"
- "github.com/gohugoio/hugo/identity"
- "github.com/gohugoio/hugo/resources/kinds"
- "github.com/spf13/afero"
- "github.com/spf13/cobra"
)
-var errHelp = errors.New("help requested")
+type commandeer struct {
+ *deps.DepsCfg
+ pathSpec *helpers.PathSpec
+ configured bool
+}
-// 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()
+func (c *commandeer) Set(key string, value interface{}) {
+ if c.configured {
+ panic("commandeer cannot be changed")
+ }
+ c.Cfg.Set(key, value)
+}
+
+// PathSpec lazily creates a new PathSpec, as all the paths must
+// be configured before it is created.
+func (c *commandeer) PathSpec() *helpers.PathSpec {
+ c.configured = true
+ return c.pathSpec
+}
+
+func (c *commandeer) initFs(fs *hugofs.Fs) error {
+ c.DepsCfg.Fs = fs
+ ps, err := helpers.NewPathSpec(fs, c.Cfg)
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 configKey struct {
- counter int32
- ignoreModulesDoesNotExists bool
-}
-
-// This is the root command.
-type rootCommand struct {
- Printf func(format string, v ...any)
- Println func(a ...any)
- StdOut io.Writer
- StdErr io.Writer
-
- logger loggers.Logger
-
- // The main cache busting key for the caches below.
- configVersionID atomic.Int32
-
- // 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]
-
- // changesFromBuild received from Hugo in watch mode.
- changesFromBuild chan []identity.Identity
-
- commands []simplecobra.Commander
-
- // Flags
- source string
- buildWatch bool
- environment string
-
- // Common build flags.
- baseURL string
- gc bool
- poll string
- forceSyncStatic bool
-
- // Profile flags (for debugging of performance problems)
- cpuprofile string
- memprofile string
- mutexprofile string
- traceprofile string
- printm bool
-
- logLevel string
-
- quiet bool
- devMode bool // Hidden flag.
-
- renderToMemory bool
-
- cfgFile string
- cfgDir string
-}
-
-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
- })
- }
+ c.pathSpec = ps
return nil
}
-func (r *rootCommand) Build(cd *simplecobra.Commandeer, bcfg hugolib.BuildCfg, cfg config.Provider) (*hugolib.HugoSites, error) {
- h, err := r.Hugo(cfg)
+func newCommandeer(cfg *deps.DepsCfg) (*commandeer, error) {
+ l := cfg.Language
+ if l == nil {
+ l = helpers.NewDefaultLanguage(cfg.Cfg)
+ }
+ ps, err := helpers.NewPathSpec(cfg.Fs, l)
if err != nil {
return nil, err
}
- if err := h.Build(bcfg); err != nil {
- return nil, err
- }
-
- return h, nil
-}
-
-func (r *rootCommand) Commands() []simplecobra.Commander {
- return r.commands
-}
-
-func (r *rootCommand) ConfigFromConfig(key configKey, oldConf *commonConfig) (*commonConfig, error) {
- cc, _, err := r.commonConfigs.GetOrCreate(key, func(key configKey) (*commonConfig, error) {
- 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,
- },
- )
- if err != nil {
- return nil, err
- }
-
- if !configs.Base.C.Clock.IsZero() {
- // TODO(bep) find a better place for this.
- htime.Clock = clocks.Start(configs.Base.C.Clock)
- }
-
- return &commonConfig{
- mu: oldConf.mu,
- configs: configs,
- cfg: oldConf.cfg,
- fs: fs,
- }, nil
- })
-
- return cc, err
-}
-
-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 cfg == nil {
- cfg = config.New()
- }
-
- 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)
- }
- }
-
- // 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,
- },
- )
- if err != nil {
- return nil, err
- }
-
- 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
- }
-
- 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
-
- }
-
- if !base.C.Clock.IsZero() {
- // TODO(bep) find a better place for this.
- htime.Clock = clocks.Start(configs.Base.C.Clock)
- }
-
- if base.PrintPathWarnings {
- // Note that we only care about the "dynamic creates" here,
- // so skip the static fs.
- fs.PublishDir = hugofs.NewCreateCountingFs(fs.PublishDir)
- }
-
- commonConfig := &commonConfig{
- mu: &sync.Mutex{},
- configs: configs,
- cfg: cfg,
- fs: fs,
- }
-
- return commonConfig, nil
- })
-
- return cc, err
-}
-
-func (r *rootCommand) HugFromConfig(conf *commonConfig) (*hugolib.HugoSites, error) {
- k := configKey{counter: r.configVersionID.Load()}
- h, _, err := r.hugoSites.GetOrCreate(k, func(key configKey) (*hugolib.HugoSites, error) {
- depsCfg := r.newDepsConfig(conf)
- return hugolib.NewHugoSites(depsCfg)
- })
- return h, err
-}
-
-func (r *rootCommand) Hugo(cfg config.Provider) (*hugolib.HugoSites, error) {
- return r.getOrCreateHugo(cfg, false)
-}
-
-func (r *rootCommand) getOrCreateHugo(cfg config.Provider, ignoreModuleDoesNotExist bool) (*hugolib.HugoSites, error) {
- k := configKey{counter: r.configVersionID.Load(), ignoreModulesDoesNotExists: ignoreModuleDoesNotExist}
- h, _, err := r.hugoSites.GetOrCreate(k, func(key configKey) (*hugolib.HugoSites, error) {
- conf, err := r.ConfigFromProvider(key, cfg)
- if err != nil {
- return nil, err
- }
- depsCfg := r.newDepsConfig(conf)
- return hugolib.NewHugoSites(depsCfg)
- })
- return h, err
-}
-
-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 (r *rootCommand) Name() string {
- return "hugo"
-}
-
-func (r *rootCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
- b := newHugoBuilder(r, nil)
-
- if !r.buildWatch {
- defer b.postBuild("Total", time.Now())
- }
-
- if err := b.loadConfig(cd, false); err != nil {
- return err
- }
-
- 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
- }
-
- if !r.buildWatch {
- // Done.
- return nil
- }
-
- watchDirs, err := b.getDirList()
- if err != nil {
- return err
- }
-
- watchGroups := helpers.ExtractAndGroupRootPaths(watchDirs)
-
- for _, group := range watchGroups {
- r.Printf("Watching for changes in %s\n", group)
- }
- watcher, err := b.newWatcher(r.poll, watchDirs...)
- if err != nil {
- return err
- }
-
- defer watcher.Close()
-
- r.Println("Press Ctrl+C to stop")
-
- sigs := make(chan os.Signal, 1)
- signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
-
- <-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)
-
- r.Printf = func(format string, v ...any) {
- if !r.quiet {
- fmt.Fprintf(r.StdOut, format, v...)
- }
- }
- r.Println = func(a ...any) {
- if !r.quiet {
- fmt.Fprintln(r.StdOut, a...)
- }
- }
- _, running := runner.Command.(*serverCommand)
- var err error
- r.logger, err = r.createLogger(running)
- if err != nil {
- 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()
- },
- })
-
- return nil
-}
-
-func (r *rootCommand) createLogger(running bool) (loggers.Logger, error) {
- level := logg.LevelWarn
-
- 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)
- }
- }
- }
-
- optsLogger := loggers.Options{
- DistinctLevel: logg.LevelWarn,
- Level: level,
- StdOut: r.StdOut,
- StdErr: r.StdErr,
- StoreErrors: running,
- }
-
- return loggers.New(optsLogger), nil
-}
-
-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 site"
- cmd.Long = `COMMAND_NAME is the main command, used to build your Hugo site.
-
-Hugo is a Fast and Flexible Static Site Generator
-built with love by spf13 and friends in Go.
-
-Complete documentation is available at https://gohugo.io/.`
-
- cmd.Long = strings.ReplaceAll(cmd.Long, "COMMAND_NAME", commandName)
-
- // Configure persistent flags
- cmd.PersistentFlags().StringVarP(&r.source, "source", "s", "", "filesystem path to read files relative from")
- _ = cmd.MarkFlagDirname("source")
- 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)
-
- return nil
-}
-
-// 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)")
-}
-
-// 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().Bool("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.)")
-}
-
-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()))
-}
-
-type simpleCommand struct {
- use string
- name string
- short string
- long 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
-
- commands []simplecobra.Commander
-
- rootCmd *rootCommand
-}
-
-func (c *simpleCommand) Commands() []simplecobra.Commander {
- return c.commands
-}
-
-func (c *simpleCommand) Name() string {
- return c.name
-}
-
-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)
-}
-
-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
- if c.use != "" {
- cmd.Use = c.use
- }
- 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], "site", "theme", "content") {
- // Insert "content" as the second argument
- args = append(args[:1], append([]string{"content"}, args[1:]...)...)
- }
- return args
+ return &commandeer{DepsCfg: cfg, pathSpec: ps}, nil
}
diff --git a/commands/commands.go b/commands/commands.go
deleted file mode 100644
index 10ab106e2..000000000
--- a/commands/commands.go
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package commands
-
-import (
- "context"
-
- "github.com/bep/simplecobra"
-)
-
-// 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(),
- },
- }
-
- return simplecobra.New(rootCmd)
-}
-
-func newHugoBuildCmd() simplecobra.Commander {
- return &hugoBuildCommand{}
-}
-
-// hugoBuildCommand just delegates to the rootCommand.
-type hugoBuildCommand struct {
- rootCmd *rootCommand
-}
-
-func (c *hugoBuildCommand) Commands() []simplecobra.Commander {
- return nil
-}
-
-func (c *hugoBuildCommand) Name() string {
- return "build"
-}
-
-func (c *hugoBuildCommand) Init(cd *simplecobra.Commandeer) error {
- c.rootCmd = cd.Root.Command.(*rootCommand)
- return c.rootCmd.initRootCommand("build", cd)
-}
-
-func (c *hugoBuildCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
- return c.rootCmd.PreRun(cd, runner)
-}
-
-func (c *hugoBuildCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
- return c.rootCmd.Run(ctx, cd, args)
-}
diff --git a/commands/config.go b/commands/config.go
deleted file mode 100644
index 7d166b9b8..000000000
--- a/commands/config.go
+++ /dev/null
@@ -1,239 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package commands
-
-import (
- "bytes"
- "context"
- "encoding/json"
- "fmt"
- "os"
- "strings"
- "time"
-
- "github.com/bep/simplecobra"
- "github.com/gohugoio/hugo/common/maps"
- "github.com/gohugoio/hugo/config/allconfig"
- "github.com/gohugoio/hugo/modules"
- "github.com/gohugoio/hugo/parser"
- "github.com/gohugoio/hugo/parser/metadecoders"
- "github.com/spf13/cobra"
-)
-
-// newConfigCommand creates a new config command and its subcommands.
-func newConfigCommand() *configCommand {
- return &configCommand{
- commands: []simplecobra.Commander{
- &configMountsCommand{},
- },
- }
-}
-
-type configCommand struct {
- r *rootCommand
-
- format string
- lang string
- printZero bool
-
- commands []simplecobra.Commander
-}
-
-func (c *configCommand) Commands() []simplecobra.Commander {
- return c.commands
-}
-
-func (c *configCommand) Name() string {
- return "config"
-}
-
-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
- }
- 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.LanguageConfigSlice[0]
- }
-
- 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
- }
-
- format := strings.ToLower(c.format)
-
- 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
- }
- maps.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
-}
-
-func (c *configCommand) Init(cd *simplecobra.Commandeer) error {
- c.r = cd.Root.Command.(*rootCommand)
- cmd := cd.CobraCommand
- cmd.Short = "Display site configuration"
- cmd.Long = `Display site configuration, both default and custom settings.`
- cmd.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
-}
-
-func (c *configCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
- return nil
-}
-
-type configModMount struct {
- Source string `json:"source"`
- Target string `json:"target"`
- Lang string `json:"lang,omitempty"`
-}
-
-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, configModMount{
- Source: mount.Source,
- Target: mount.Target,
- Lang: mount.Lang,
- })
- }
-
- var ownerPath string
- if m.m.Owner() != nil {
- ownerPath = m.m.Owner().Path()
- }
-
- 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]any `json:"meta"`
- HugoVersion modules.HugoVersion `json:"hugoVersion"`
-
- Mounts []configModMount `json:"mounts"`
- }{
- Path: m.m.Path(),
- Version: m.m.Version(),
- Time: m.m.Time(),
- Owner: ownerPath,
- Dir: m.m.Dir(),
- Meta: config.Params,
- HugoVersion: config.HugoVersion,
- Mounts: mounts,
- })
- }
-
- 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 []configModMount `json:"mounts"`
- }{
- Path: m.m.Path(),
- Version: m.m.Version(),
- Time: m.m.Time(),
- Owner: ownerPath,
- 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 ebf81cfb3..298ff6019 100644
--- a/commands/convert.go
+++ b/commands/convert.go
@@ -1,4 +1,4 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
+// 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.
@@ -14,216 +14,145 @@
package commands
import (
- "bytes"
- "context"
+ "errors"
"fmt"
"path/filepath"
- "strings"
"time"
- "github.com/bep/simplecobra"
- "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/gohugoio/hugo/parser/pageparser"
- "github.com/gohugoio/hugo/resources/page"
+ "github.com/spf13/cast"
"github.com/spf13/cobra"
)
-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
+var outputDir string
+var unsafe bool
+
+var convertCmd = &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,
+}
+
+var toJSONCmd = &cobra.Command{
+ Use: "toJSON",
+ Short: "Convert front matter to JSON",
+ Long: `toJSON converts all front matter in the content directory
to use JSON for the front matter.`,
- 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
- },
- },
- &simpleCommand{
- name: "toTOML",
- short: "Convert front matter to TOML",
- long: `toTOML converts all front matter in the content directory
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return convertContents(rune([]byte(parser.JSONLead)[0]))
+ },
+}
+
+var toTOMLCmd = &cobra.Command{
+ Use: "toTOML",
+ Short: "Convert front matter to TOML",
+ Long: `toTOML converts all front matter in the content directory
to use TOML for the front matter.`,
- 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
- },
- },
- &simpleCommand{
- name: "toYAML",
- short: "Convert front matter to YAML",
- long: `toYAML converts all front matter in the content directory
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return convertContents(rune([]byte(parser.TOMLLead)[0]))
+ },
+}
+
+var toYAMLCmd = &cobra.Command{
+ Use: "toYAML",
+ Short: "Convert front matter to YAML",
+ Long: `toYAML converts all front matter in the content directory
to use YAML for the front matter.`,
- 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
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return convertContents(rune([]byte(parser.YAMLLead)[0]))
+ },
}
-type convertCommand struct {
- // Flags.
- outputDir string
- unsafe bool
-
- // Deps.
- r *rootCommand
- h *hugolib.HugoSites
-
- // Commands.
- commands []simplecobra.Commander
+func init() {
+ convertCmd.AddCommand(toJSONCmd)
+ convertCmd.AddCommand(toTOMLCmd)
+ convertCmd.AddCommand(toYAMLCmd)
+ convertCmd.PersistentFlags().StringVarP(&outputDir, "output", "o", "", "filesystem path to write files to")
+ convertCmd.PersistentFlags().StringVarP(&source, "source", "s", "", "filesystem path to read files relative from")
+ convertCmd.PersistentFlags().BoolVar(&unsafe, "unsafe", false, "enable less safe operations, please backup first")
+ convertCmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{})
}
-func (c *convertCommand) Commands() []simplecobra.Commander {
- return c.commands
-}
-
-func (c *convertCommand) Name() string {
- return "convert"
-}
-
-func (c *convertCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
- return nil
-}
-
-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.
-
-See convert's subcommands toJSON, toTOML and toYAML for more information.`
-
- 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")
-
- 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))
+func convertContents(mark rune) error {
+ cfg, err := InitializeConfig()
if err != nil {
return err
}
- c.h = h
- return nil
-}
-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 := c.convertAndSavePage(r.(page.Page), site, targetFormat); err != nil {
+ h, err := hugolib.NewHugoSites(*cfg)
+ if err != nil {
+ return err
+ }
+
+ site := h.Sites[0]
+
+ if err = site.Initialise(); err != nil {
+ return err
+ }
+
+ if site.Source == nil {
+ panic("site.Source not set")
+ }
+ if len(site.Source.Files()) < 1 {
+ return errors.New("No source files found")
+ }
+
+ contentDir := site.PathSpec.AbsPathify(site.Cfg.GetString("contentDir"))
+ site.Log.FEEDBACK.Println("processing", len(site.Source.Files()), "content files")
+ for _, file := range site.Source.Files() {
+ site.Log.INFO.Println("Attempting to convert", file.LogicalName())
+ page, err := site.NewPage(file.LogicalName())
+ if err != nil {
return err
}
- }
- if p.File() == nil {
- // No content file.
- return nil
- }
+ psr, err := parser.ReadFrom(file.Contents)
+ if err != nil {
+ site.Log.ERROR.Println("Error processing file:", file.Path())
+ return err
+ }
+ metadata, err := psr.Metadata()
+ if err != nil {
+ site.Log.ERROR.Println("Error processing file:", file.Path())
+ return err
+ }
- errMsg := fmt.Errorf("error processing file %q", p.File().Path())
+ // better handling of dates in formats that don't have support for them
+ if mark == parser.FormatToLeadRune("json") || mark == parser.FormatToLeadRune("yaml") || mark == parser.FormatToLeadRune("toml") {
+ newMetadata := cast.ToStringMap(metadata)
+ for k, v := range newMetadata {
+ switch vv := v.(type) {
+ case time.Time:
+ newMetadata[k] = vv.Format(time.RFC3339)
+ }
+ }
+ metadata = newMetadata
+ }
- site.Log.Infoln("attempting to convert", p.File().Filename())
+ page.SetDir(filepath.Join(contentDir, file.Dir()))
+ page.SetSourceContent(psr.Content())
+ if err = page.SetSourceMetaData(metadata, mark); err != nil {
+ site.Log.ERROR.Printf("Failed to set source metadata for file %q: %s. For more info see For more info see https://github.com/gohugoio/hugo/issues/2458", page.FullFilePath(), err)
+ continue
+ }
- f := p.File()
- file, err := f.FileInfo().Meta().Open()
- if err != nil {
- site.Log.Errorln(errMsg)
- file.Close()
- return nil
- }
-
- pf, err := pageparser.ParseFrontMatterAndContent(file)
- if err != nil {
- site.Log.Errorln(errMsg)
- file.Close()
- return err
- }
-
- file.Close()
-
- // better handling of dates in formats that don't have support for them
- if pf.FrontMatterFormat == metadecoders.JSON || pf.FrontMatterFormat == metadecoders.YAML || pf.FrontMatterFormat == metadecoders.TOML {
- for k, v := range pf.FrontMatter {
- switch vv := v.(type) {
- case time.Time:
- pf.FrontMatter[k] = vv.Format(time.RFC3339)
+ if outputDir != "" {
+ if err = page.SaveSourceAs(filepath.Join(outputDir, page.FullFilePath())); err != nil {
+ return fmt.Errorf("Failed to save file %q: %s", page.FullFilePath(), err)
+ }
+ } else {
+ if unsafe {
+ if err = page.SaveSource(); err != nil {
+ return fmt.Errorf("Failed to save file %q: %s", page.FullFilePath(), err)
+ }
+ } else {
+ site.Log.FEEDBACK.Println("Unsafe operation not allowed, use --unsafe or set a different output path")
}
}
}
-
- var newContent bytes.Buffer
- err = parser.InterfaceToFrontMatter(pf.FrontMatter, targetFormat, &newContent)
- if err != nil {
- site.Log.Errorln(errMsg)
- return err
- }
-
- newContent.Write(pf.Content)
-
- newFilename := p.File().Filename()
-
- if c.outputDir != "" {
- contentDir := strings.TrimSuffix(newFilename, p.File().Path())
- contentDir = filepath.Base(contentDir)
-
- newFilename = filepath.Join(c.outputDir, contentDir, p.File().Path())
- }
-
- fs := hugofs.Os
- if err := helpers.WriteToDisk(newFilename, &newContent, fs); err != nil {
- return fmt.Errorf("failed to save file %q:: %w", newFilename, err)
- }
-
- return nil
-}
-
-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]
-
- var pagesBackedByFile page.Pages
- for _, p := range site.AllPages() {
- if p.File() == nil {
- continue
- }
- pagesBackedByFile = append(pagesBackedByFile, p)
- }
-
- 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
deleted file mode 100644
index 3e9d3df20..000000000
--- a/commands/deploy.go
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-//go:build withdeploy
-
-package commands
-
-import (
- "context"
-
- "github.com/gohugoio/hugo/deploy"
-
- "github.com/bep/simplecobra"
- "github.com/spf13/cobra"
-)
-
-func newDeployCommand() simplecobra.Commander {
- return &simpleCommand{
- name: "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.
-`,
- 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(h.Configs.GetFirstLanguageConfig(), h.Log, h.PathSpec.PublishFs)
- if err != nil {
- return err
- }
- return deployer.Deploy(ctx)
- },
- withc: func(cmd *cobra.Command, r *rootCommand) {
- applyDeployFlags(cmd, r)
- },
- }
-}
diff --git a/commands/deploy_flags.go b/commands/deploy_flags.go
deleted file mode 100644
index d4326547a..000000000
--- a/commands/deploy_flags.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package commands
-
-import (
- "github.com/gohugoio/hugo/deploy/deployconfig"
- "github.com/spf13/cobra"
-)
-
-func applyDeployFlags(cmd *cobra.Command, r *rootCommand) {
- cmd.ValidArgsFunction = cobra.NoFileCompletions
- cmd.Flags().String("target", "", "target deployment from deployments section in config file; defaults to the first one")
- _ = cmd.RegisterFlagCompletionFunc("target", cobra.NoFileCompletions)
- cmd.Flags().Bool("confirm", false, "ask for confirmation before making changes to the target")
- cmd.Flags().Bool("dryRun", false, "dry run")
- cmd.Flags().Bool("force", false, "force upload of all files")
- cmd.Flags().Bool("invalidateCDN", deployconfig.DefaultConfig.InvalidateCDN, "invalidate the CDN cache listed in the deployment target")
- cmd.Flags().Int("maxDeletes", deployconfig.DefaultConfig.MaxDeletes, "maximum # of files to delete, or -1 to disable")
- _ = cmd.RegisterFlagCompletionFunc("maxDeletes", cobra.NoFileCompletions)
- cmd.Flags().Int("workers", deployconfig.DefaultConfig.Workers, "number of workers to transfer files. defaults to 10")
- _ = cmd.RegisterFlagCompletionFunc("workers", cobra.NoFileCompletions)
-}
diff --git a/commands/deploy_off.go b/commands/deploy_off.go
deleted file mode 100644
index 8f5eaa2de..000000000
--- a/commands/deploy_off.go
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-//go:build !withdeploy
-
-// 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 753522560..54c98d527 100644
--- a/commands/env.go
+++ b/commands/env.go
@@ -1,4 +1,4 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
+// 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.
@@ -14,57 +14,22 @@
package commands
import (
- "context"
"runtime"
- "github.com/bep/simplecobra"
- "github.com/gohugoio/hugo/common/hugo"
"github.com/spf13/cobra"
+ jww "github.com/spf13/jwalterweatherman"
)
-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())
+var envCmd = &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.`,
+ 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())
- if r.isVerbose() {
- deps := hugo.GetDependencyList()
- for _, dep := range deps {
- r.Printf("%s\n", dep)
- }
- } 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
- },
- }
+ return nil
+ },
}
diff --git a/commands/gen.go b/commands/gen.go
index 1c5361840..62a84b0d0 100644
--- a/commands/gen.go
+++ b/commands/gen.go
@@ -1,4 +1,4 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
+// 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.
@@ -14,290 +14,10 @@
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/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"
- "gopkg.in/yaml.v2"
)
-func newGenCommand() *genCommand {
- var (
- // Flags.
- gendocdir string
- genmandir string
-
- // Chroma flags.
- style string
- highlightStyle string
- lineNumbersInlineStyle string
- lineNumbersTableStyle string
- omitEmpty 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://xyproto.github.io/splash/docs/all.html 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
- }
-
- var formatter *html.Formatter
- if omitEmpty {
- formatter = html.New(html.WithClasses(true))
- } else {
- formatter = html.New(html.WithAllClasses(true))
- }
-
- w := os.Stdout
- fmt.Fprintf(w, "/* Generated using: hugo %s */\n\n", strings.Join(os.Args[1:], " "))
- formatter.WriteCSS(w, style)
- return nil
- },
- withc: func(cmd *cobra.Command, r *rootCommand) {
- cmd.ValidArgsFunction = cobra.NoFileCompletions
- cmd.PersistentFlags().StringVar(&style, "style", "friendly", "highlighter style (see https://xyproto.github.io/splash/docs/)")
- _ = cmd.RegisterFlagCompletionFunc("style", cobra.NoFileCompletions)
- cmd.PersistentFlags().StringVar(&highlightStyle, "highlightStyle", "", `foreground and background colors for highlighted lines, e.g. --highlightStyle "#fff000 bg:#000fff"`)
- _ = cmd.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`)
- _ = cmd.RegisterFlagCompletionFunc("omitEmpty", 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)
- 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."
-
- cmd.RunE = nil
- return nil
-}
-
-func (c *genCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
- c.rootCmd = cd.Root.Command.(*rootCommand)
- return nil
+var genCmd = &cobra.Command{
+ Use: "gen",
+ Short: "A collection of several useful generators.",
}
diff --git a/commands/genautocomplete.go b/commands/genautocomplete.go
new file mode 100644
index 000000000..c2004ab22
--- /dev/null
+++ b/commands/genautocomplete.go
@@ -0,0 +1,70 @@
+// 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/spf13/cobra"
+ jww "github.com/spf13/jwalterweatherman"
+)
+
+var autocompleteTarget string
+
+// bash for now (zsh and others will come)
+var autocompleteType string
+
+var genautocompleteCmd = &cobra.Command{
+ Use: "autocomplete",
+ Short: "Generate shell autocompletion script for Hugo",
+ Long: `Generates a shell autocompletion script for Hugo.
+
+NOTE: The current version supports Bash only.
+ This should work for *nix systems with Bash installed.
+
+By default, the file is written directly to /etc/bash_completion.d
+for convenience, and the command may need superuser rights, e.g.:
+
+ $ sudo hugo gen autocomplete
+
+Add ` + "`--completionfile=/path/to/file`" + ` flag to set alternative
+file-path and name.
+
+Logout and in again to reload the completion scripts,
+or just source them in directly:
+
+ $ . /etc/bash_completion`,
+
+ RunE: func(cmd *cobra.Command, args []string) error {
+ if autocompleteType != "bash" {
+ return newUserError("Only Bash is supported for now")
+ }
+
+ err := cmd.Root().GenBashCompletionFile(autocompleteTarget)
+
+ if err != nil {
+ return err
+ }
+
+ jww.FEEDBACK.Println("Bash completion file for Hugo saved to", autocompleteTarget)
+
+ return nil
+ },
+}
+
+func init() {
+ genautocompleteCmd.PersistentFlags().StringVarP(&autocompleteTarget, "completionfile", "", "/etc/bash_completion.d/hugo.sh", "autocompletion file")
+ genautocompleteCmd.PersistentFlags().StringVarP(&autocompleteType, "type", "", "bash", "autocompletion type (currently only bash supported)")
+
+ // For bash-completion
+ genautocompleteCmd.PersistentFlags().SetAnnotation("completionfile", cobra.BashCompFilenameExt, []string{})
+}
diff --git a/commands/gendoc.go b/commands/gendoc.go
new file mode 100644
index 000000000..c4840050b
--- /dev/null
+++ b/commands/gendoc.go
@@ -0,0 +1,86 @@
+// 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"
+ "time"
+
+ "github.com/gohugoio/hugo/helpers"
+ "github.com/gohugoio/hugo/hugofs"
+ "github.com/spf13/cobra"
+ "github.com/spf13/cobra/doc"
+ jww "github.com/spf13/jwalterweatherman"
+)
+
+const gendocFrontmatterTemplate = `---
+date: %s
+title: "%s"
+slug: %s
+url: %s
+---
+`
+
+var gendocdir string
+var gendocCmd = &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 {
+ if !strings.HasSuffix(gendocdir, helpers.FilePathSeparator) {
+ gendocdir += helpers.FilePathSeparator
+ }
+ if found, _ := helpers.Exists(gendocdir, hugofs.Os); !found {
+ jww.FEEDBACK.Println("Directory", gendocdir, "does not exist, creating...")
+ if err := hugofs.Os.MkdirAll(gendocdir, 0777); err != nil {
+ return err
+ }
+ }
+ now := time.Now().Format(time.RFC3339)
+ 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, now, 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", gendocdir, "...")
+ doc.GenMarkdownTreeCustom(cmd.Root(), gendocdir, prepender, linkHandler)
+ jww.FEEDBACK.Println("Done.")
+
+ return nil
+ },
+}
+
+func init() {
+ gendocCmd.PersistentFlags().StringVar(&gendocdir, "dir", "/tmp/hugodoc/", "the directory to write the doc.")
+
+ // For bash-completion
+ gendocCmd.PersistentFlags().SetAnnotation("dir", cobra.BashCompSubdirsInDir, []string{})
+}
diff --git a/commands/gendocshelper.go b/commands/gendocshelper.go
new file mode 100644
index 000000000..ca781242e
--- /dev/null
+++ b/commands/gendocshelper.go
@@ -0,0 +1,70 @@
+// 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"
+)
+
+type genDocsHelper struct {
+ target string
+ cmd *cobra.Command
+}
+
+func createGenDocsHelper() *genDocsHelper {
+ g := &genDocsHelper{
+ cmd: &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.DocProviders); err != nil {
+ return err
+ }
+
+ fmt.Println("Done!")
+ return nil
+
+}
diff --git a/commands/genman.go b/commands/genman.go
new file mode 100644
index 000000000..004e669e7
--- /dev/null
+++ b/commands/genman.go
@@ -0,0 +1,66 @@
+// 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/helpers"
+ "github.com/gohugoio/hugo/hugofs"
+ "github.com/spf13/cobra"
+ "github.com/spf13/cobra/doc"
+ jww "github.com/spf13/jwalterweatherman"
+)
+
+var genmandir string
+var genmanCmd = &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", helpers.CurrentHugoVersion),
+ }
+ if !strings.HasSuffix(genmandir, helpers.FilePathSeparator) {
+ genmandir += helpers.FilePathSeparator
+ }
+ if found, _ := helpers.Exists(genmandir, hugofs.Os); !found {
+ jww.FEEDBACK.Println("Directory", genmandir, "does not exist, creating...")
+ if err := hugofs.Os.MkdirAll(genmandir, 0777); err != nil {
+ return err
+ }
+ }
+ cmd.Root().DisableAutoGenTag = true
+
+ jww.FEEDBACK.Println("Generating Hugo man pages in", genmandir, "...")
+ doc.GenManTree(cmd.Root(), header, genmandir)
+
+ jww.FEEDBACK.Println("Done.")
+
+ return nil
+ },
+}
+
+func init() {
+ genmanCmd.PersistentFlags().StringVar(&genmandir, "dir", "man/", "the directory to write the man pages.")
+
+ // For bash-completion
+ genmanCmd.PersistentFlags().SetAnnotation("dir", cobra.BashCompSubdirsInDir, []string{})
+}
diff --git a/commands/helpers.go b/commands/helpers.go
deleted file mode 100644
index a13bdebc2..000000000
--- a/commands/helpers.go
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package commands
-
-import (
- "errors"
- "fmt"
- "log"
- "os"
- "path/filepath"
- "strings"
-
- "github.com/bep/simplecobra"
- "github.com/gohugoio/hugo/config"
- "github.com/spf13/pflag"
-)
-
-const (
- ansiEsc = "\u001B"
- clearLine = "\r\033[K"
- hideCursor = ansiEsc + "[?25l"
- showCursor = ansiEsc + "[?25h"
-)
-
-func newUserError(a ...any) *simplecobra.CommandError {
- return &simplecobra.CommandError{Err: errors.New(fmt.Sprint(a...))}
-}
-
-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()))
- }
-
- }
-}
-
-func flagsToCfg(cd *simplecobra.Commandeer, cfg config.Provider) config.Provider {
- return flagsToCfgWithAdditionalConfigBase(cd, cfg, "")
-}
-
-func flagsToCfgWithAdditionalConfigBase(cd *simplecobra.Commandeer, cfg config.Provider, additionalConfigBase string) config.Provider {
- if cfg == nil {
- cfg = config.New()
- }
-
- // Flags with a different name in the config.
- keyMap := map[string]string{
- "minify": "minifyOutput",
- "destination": "publishDir",
- "editor": "newContentEditor",
- }
-
- // Flags that we for some reason don't want to expose in the site config.
- internalKeySet := map[string]bool{
- "quiet": true,
- "verbose": true,
- "watch": true,
- "liveReloadPort": true,
- "renderToMemory": true,
- "clock": true,
- }
-
- cmd := cd.CobraCommand
- flags := cmd.Flags()
-
- 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)
- }
- }
- })
-
- return cfg
-}
-
-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
new file mode 100644
index 000000000..b939ce6e9
--- /dev/null
+++ b/commands/hugo.go
@@ -0,0 +1,1078 @@
+// 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 defines and implements command-line commands and flags
+// used by Hugo. Commands and flags are implemented using Cobra.
+package commands
+
+import (
+ "fmt"
+ "io/ioutil"
+
+ "github.com/gohugoio/hugo/hugofs"
+
+ "log"
+ "net/http"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/gohugoio/hugo/config"
+
+ "github.com/gohugoio/hugo/parser"
+ flag "github.com/spf13/pflag"
+
+ "regexp"
+
+ "github.com/fsnotify/fsnotify"
+ "github.com/gohugoio/hugo/deps"
+ "github.com/gohugoio/hugo/helpers"
+ "github.com/gohugoio/hugo/hugolib"
+ "github.com/gohugoio/hugo/livereload"
+ "github.com/gohugoio/hugo/utils"
+ "github.com/gohugoio/hugo/watcher"
+ "github.com/spf13/afero"
+ "github.com/spf13/cobra"
+ "github.com/spf13/fsync"
+ jww "github.com/spf13/jwalterweatherman"
+ "github.com/spf13/nitro"
+ "github.com/spf13/viper"
+)
+
+// Hugo represents the Hugo sites to build. This variable is exported as it
+// is used by at least one external library (the Hugo caddy plugin). We should
+// provide a cleaner external API, but until then, this is it.
+var Hugo *hugolib.HugoSites
+
+// Reset resets Hugo ready for a new full build. This is mainly only useful
+// for benchmark testing etc. via the CLI commands.
+func Reset() error {
+ Hugo = nil
+ return nil
+}
+
+// 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 (c commandError) isUserError() bool {
+ return c.userError
+}
+
+func newUserError(a ...interface{}) commandError {
+ return commandError{s: fmt.Sprintln(a...), userError: true}
+}
+
+func newSystemError(a ...interface{}) commandError {
+ return commandError{s: fmt.Sprintln(a...), userError: false}
+}
+
+func newSystemErrorF(format string, a ...interface{}) commandError {
+ return commandError{s: fmt.Sprintf(format, a...), userError: false}
+}
+
+// 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")
+
+func isUserError(err error) bool {
+ if cErr, ok := err.(commandError); ok && cErr.isUserError() {
+ return true
+ }
+
+ return userErrorRegexp.MatchString(err.Error())
+}
+
+// HugoCmd is Hugo's root command.
+// Every other command attached to HugoCmd is a child command to it.
+var HugoCmd = &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 {
+ cfg, err := InitializeConfig()
+ if err != nil {
+ return err
+ }
+
+ c, err := newCommandeer(cfg)
+ if err != nil {
+ return err
+ }
+
+ if buildWatch {
+ cfg.Cfg.Set("disableLiveReload", true)
+ c.watchConfig()
+ }
+
+ return c.build()
+ },
+}
+
+var hugoCmdV *cobra.Command
+
+// Flags that are to be added to commands.
+var (
+ buildWatch bool
+ logging bool
+ renderToMemory bool // for benchmark testing
+ verbose bool
+ verboseLog bool
+ quiet bool
+)
+
+var (
+ baseURL string
+ cacheDir string
+ contentDir string
+ layoutDir string
+ cfgFile string
+ destination string
+ logFile string
+ theme string
+ themesDir string
+ source string
+ logI18nWarnings bool
+ disableKinds []string
+)
+
+// Execute adds all child commands to the root command HugoCmd and sets flags appropriately.
+func Execute() {
+ HugoCmd.SetGlobalNormalizationFunc(helpers.NormalizeHugoFlags)
+
+ HugoCmd.SilenceUsage = true
+
+ AddCommands()
+
+ if c, err := HugoCmd.ExecuteC(); err != nil {
+ if isUserError(err) {
+ c.Println("")
+ c.Println(c.UsageString())
+ }
+
+ os.Exit(-1)
+ }
+}
+
+// AddCommands adds child commands to the root command HugoCmd.
+func AddCommands() {
+ HugoCmd.AddCommand(serverCmd)
+ HugoCmd.AddCommand(versionCmd)
+ HugoCmd.AddCommand(envCmd)
+ HugoCmd.AddCommand(configCmd)
+ HugoCmd.AddCommand(checkCmd)
+ HugoCmd.AddCommand(benchmarkCmd)
+ HugoCmd.AddCommand(convertCmd)
+ HugoCmd.AddCommand(newCmd)
+ HugoCmd.AddCommand(listCmd)
+ HugoCmd.AddCommand(undraftCmd)
+ HugoCmd.AddCommand(importCmd)
+
+ HugoCmd.AddCommand(genCmd)
+ genCmd.AddCommand(genautocompleteCmd)
+ genCmd.AddCommand(gendocCmd)
+ genCmd.AddCommand(genmanCmd)
+ genCmd.AddCommand(createGenDocsHelper().cmd)
+}
+
+// initHugoBuilderFlags initializes all common flags, typically used by the
+// core build commands, namely hugo itself, server, check and benchmark.
+func initHugoBuilderFlags(cmd *cobra.Command) {
+ initHugoBuildCommonFlags(cmd)
+}
+
+func initRootPersistentFlags() {
+ HugoCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is path/config.yaml|json|toml)")
+ HugoCmd.PersistentFlags().BoolVar(&quiet, "quiet", false, "build in quiet mode")
+
+ // Set bash-completion
+ validConfigFilenames := []string{"json", "js", "yaml", "yml", "toml", "tml"}
+ _ = HugoCmd.PersistentFlags().SetAnnotation("config", cobra.BashCompFilenameExt, validConfigFilenames)
+}
+
+// initHugoBuildCommonFlags initialize common flags related to the Hugo build.
+// Called by initHugoBuilderFlags.
+func initHugoBuildCommonFlags(cmd *cobra.Command) {
+ 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().Bool("disable404", false, "do not render 404 page")
+ cmd.Flags().Bool("disableRSS", false, "do not build RSS files")
+ cmd.Flags().Bool("disableSitemap", false, "do not build Sitemap file")
+ cmd.Flags().StringVarP(&source, "source", "s", "", "filesystem path to read files relative from")
+ cmd.Flags().StringVarP(&contentDir, "contentDir", "c", "", "filesystem path to content directory")
+ cmd.Flags().StringVarP(&layoutDir, "layoutDir", "l", "", "filesystem path to layout directory")
+ cmd.Flags().StringVarP(&cacheDir, "cacheDir", "", "", "filesystem path to cache directory. Defaults: $TMPDIR/hugo_cache/")
+ cmd.Flags().BoolP("ignoreCache", "", false, "ignores the cache directory")
+ cmd.Flags().StringVarP(&destination, "destination", "d", "", "filesystem path to write files to")
+ cmd.Flags().StringVarP(&theme, "theme", "t", "", "theme to use (located in /themes/THEMENAME/)")
+ cmd.Flags().StringVarP(&themesDir, "themesDir", "", "", "filesystem path to themes directory")
+ cmd.Flags().Bool("uglyURLs", false, "if true, use /filename.html instead of /filename/")
+ cmd.Flags().Bool("canonifyURLs", false, "if true, all relative URLs will be canonicalized using baseURL")
+ cmd.Flags().StringVarP(&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(&nitro.AnalysisOn, "stepAnalysis", false, "display memory and timing of different steps of the program")
+ cmd.Flags().Bool("pluralizeListTitles", true, "pluralize titles in lists using inflect")
+ cmd.Flags().Bool("preserveTaxonomyNames", false, `preserve taxonomy names as written ("Gérard Depardieu" vs "gerard-depardieu")`)
+ 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().BoolVarP(&logI18nWarnings, "i18n-warnings", "", false, "print missing translations")
+
+ cmd.Flags().StringSliceVar(&disableKinds, "disableKinds", []string{}, "disable different kind of pages (home, RSS 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 initBenchmarkBuildingFlags(cmd *cobra.Command) {
+ cmd.Flags().BoolVar(&renderToMemory, "renderToMemory", false, "render to memory (only useful for benchmark testing)")
+}
+
+// init initializes flags.
+func init() {
+ HugoCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
+ HugoCmd.PersistentFlags().BoolVar(&logging, "log", false, "enable Logging")
+ HugoCmd.PersistentFlags().StringVar(&logFile, "logFile", "", "log File path (if set, logging enabled automatically)")
+ HugoCmd.PersistentFlags().BoolVar(&verboseLog, "verboseLog", false, "verbose logging")
+
+ initRootPersistentFlags()
+ initHugoBuilderFlags(HugoCmd)
+ initBenchmarkBuildingFlags(HugoCmd)
+
+ HugoCmd.Flags().BoolVarP(&buildWatch, "watch", "w", false, "watch filesystem for changes and recreate as needed")
+ hugoCmdV = HugoCmd
+
+ // Set bash-completion
+ _ = HugoCmd.PersistentFlags().SetAnnotation("logFile", cobra.BashCompFilenameExt, []string{})
+}
+
+// InitializeConfig initializes a config file with sensible default configuration flags.
+func InitializeConfig(subCmdVs ...*cobra.Command) (*deps.DepsCfg, error) {
+
+ var cfg *deps.DepsCfg = &deps.DepsCfg{}
+
+ // Init file systems. This may be changed at a later point.
+ osFs := hugofs.Os
+
+ config, err := hugolib.LoadConfig(osFs, source, cfgFile)
+ if err != nil {
+ return cfg, err
+ }
+
+ // Init file systems. This may be changed at a later point.
+ cfg.Cfg = config
+
+ c, err := newCommandeer(cfg)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, cmdV := range append([]*cobra.Command{hugoCmdV}, subCmdVs...) {
+ c.initializeFlags(cmdV)
+ }
+
+ if len(disableKinds) > 0 {
+ c.Set("disableKinds", disableKinds)
+ }
+
+ logger, err := createLogger(cfg.Cfg)
+ if err != nil {
+ return cfg, err
+ }
+
+ cfg.Logger = logger
+
+ config.Set("logI18nWarnings", logI18nWarnings)
+
+ if baseURL != "" {
+ config.Set("baseURL", baseURL)
+ }
+
+ if !config.GetBool("relativeURLs") && config.GetString("baseURL") == "" {
+ cfg.Logger.ERROR.Println("No 'baseURL' set in configuration or as a flag. Features like page menus will not work without one.")
+ }
+
+ if theme != "" {
+ config.Set("theme", theme)
+ }
+
+ if themesDir != "" {
+ config.Set("themesDir", themesDir)
+ }
+
+ if destination != "" {
+ config.Set("publishDir", destination)
+ }
+
+ var dir string
+ if source != "" {
+ dir, _ = filepath.Abs(source)
+ } else {
+ dir, _ = os.Getwd()
+ }
+ config.Set("workingDir", dir)
+
+ fs := hugofs.NewFrom(osFs, config)
+
+ // Hugo writes the output to memory instead of the disk.
+ // This is only used for benchmark testing. Cause the content is only visible
+ // in memory.
+ if renderToMemory {
+ fs.Destination = new(afero.MemMapFs)
+ // Rendering to memoryFS, publish to Root regardless of publishDir.
+ c.Set("publishDir", "/")
+ }
+
+ if contentDir != "" {
+ config.Set("contentDir", contentDir)
+ }
+
+ if layoutDir != "" {
+ config.Set("layoutDir", layoutDir)
+ }
+
+ if cacheDir != "" {
+ config.Set("cacheDir", cacheDir)
+ }
+
+ cacheDir = config.GetString("cacheDir")
+ if cacheDir != "" {
+ if helpers.FilePathSeparator != cacheDir[len(cacheDir)-1:] {
+ cacheDir = cacheDir + helpers.FilePathSeparator
+ }
+ isDir, err := helpers.DirExists(cacheDir, fs.Source)
+ utils.CheckErr(cfg.Logger, err)
+ if !isDir {
+ mkdir(cacheDir)
+ }
+ config.Set("cacheDir", cacheDir)
+ } else {
+ config.Set("cacheDir", helpers.GetTempDir("hugo_cache", fs.Source))
+ }
+
+ if err := c.initFs(fs); err != nil {
+ return nil, err
+ }
+
+ cfg.Logger.INFO.Println("Using config file:", viper.ConfigFileUsed())
+
+ themeDir := c.PathSpec().GetThemeDir()
+ if themeDir != "" {
+ if _, err := cfg.Fs.Source.Stat(themeDir); os.IsNotExist(err) {
+ return cfg, newSystemError("Unable to find theme Directory:", themeDir)
+ }
+ }
+
+ themeVersionMismatch, minVersion := c.isThemeVsHugoVersionMismatch()
+
+ if themeVersionMismatch {
+ cfg.Logger.ERROR.Printf("Current theme does not support Hugo version %s. Minimum version required is %s\n",
+ helpers.CurrentHugoVersion.ReleaseVersion(), minVersion)
+ }
+
+ return cfg, nil
+
+}
+
+func createLogger(cfg config.Provider) (*jww.Notepad, error) {
+ var (
+ logHandle = ioutil.Discard
+ logThreshold = jww.LevelWarn
+ logFile = cfg.GetString("logFile")
+ outHandle = os.Stdout
+ stdoutThreshold = jww.LevelError
+ )
+
+ if verboseLog || logging || (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 !quiet && cfg.GetBool("verbose") {
+ stdoutThreshold = jww.LevelInfo
+ }
+
+ if verboseLog {
+ logThreshold = jww.LevelInfo
+ }
+
+ // The global logger is used in some few cases.
+ jww.SetLogOutput(logHandle)
+ jww.SetLogThreshold(logThreshold)
+ jww.SetStdoutThreshold(stdoutThreshold)
+ helpers.InitLoggers()
+
+ return jww.NewNotepad(stdoutThreshold, logThreshold, outHandle, logHandle, "", log.Ldate|log.Ltime), nil
+}
+
+func (c *commandeer) initializeFlags(cmd *cobra.Command) {
+ persFlagKeys := []string{"verbose", "logFile"}
+ flagKeys := []string{
+ "cleanDestinationDir",
+ "buildDrafts",
+ "buildFuture",
+ "buildExpired",
+ "uglyURLs",
+ "canonifyURLs",
+ "disable404",
+ "disableRSS",
+ "disableSitemap",
+ "enableRobotsTXT",
+ "enableGitInfo",
+ "pluralizeListTitles",
+ "preserveTaxonomyNames",
+ "ignoreCache",
+ "forceSyncStatic",
+ "noTimes",
+ "noChmod",
+ }
+
+ // Remove these in Hugo 0.23.
+ if cmd.Flags().Changed("disable404") {
+ helpers.Deprecated("command line", "--disable404", "Use --disableKinds=404", false)
+ }
+
+ if cmd.Flags().Changed("disableRSS") {
+ helpers.Deprecated("command line", "--disableRSS", "Use --disableKinds=RSS", false)
+ }
+
+ if cmd.Flags().Changed("disableSitemap") {
+ helpers.Deprecated("command line", "--disableSitemap", "Use --disableKinds=sitemap", false)
+ }
+
+ for _, key := range persFlagKeys {
+ c.setValueFromFlag(cmd.PersistentFlags(), key)
+ }
+ for _, key := range flagKeys {
+ c.setValueFromFlag(cmd.Flags(), key)
+ }
+
+}
+
+func (c *commandeer) setValueFromFlag(flags *flag.FlagSet, key string) {
+ if flags.Changed(key) {
+ f := flags.Lookup(key)
+ c.Set(key, f.Value.String())
+ }
+}
+
+func (c *commandeer) watchConfig() {
+ v := c.Cfg.(*viper.Viper)
+ v.WatchConfig()
+ v.OnConfigChange(func(e fsnotify.Event) {
+ c.Logger.FEEDBACK.Println("Config file changed:", e.Name)
+ // Force a full rebuild
+ utils.CheckErr(c.Logger, c.recreateAndBuildSites(true))
+ if !c.Cfg.GetBool("disableLiveReload") {
+ // Will block forever trying to write to a channel that nobody is reading if livereload isn't initialized
+ livereload.ForceRefresh()
+ }
+ })
+}
+
+func (c *commandeer) build(watches ...bool) error {
+ if err := c.copyStatic(); err != nil {
+ return fmt.Errorf("Error copying static files to %s: %s", c.PathSpec().AbsPathify(c.Cfg.GetString("publishDir")), err)
+ }
+ watch := false
+ if len(watches) > 0 && watches[0] {
+ watch = true
+ }
+ if err := c.buildSites(buildWatch || watch); err != nil {
+ return fmt.Errorf("Error building site: %s", err)
+ }
+
+ if buildWatch {
+ c.Logger.FEEDBACK.Println("Watching for changes in", c.PathSpec().AbsPathify(c.Cfg.GetString("contentDir")))
+ c.Logger.FEEDBACK.Println("Press Ctrl+C to stop")
+ utils.CheckErr(c.Logger, c.newWatcher(0))
+ }
+
+ return nil
+}
+
+func (c *commandeer) getStaticSourceFs() afero.Fs {
+ source := c.Fs.Source
+ themeDir, err := c.PathSpec().GetThemeStaticDirPath()
+ staticDir := c.PathSpec().GetStaticDirPath() + helpers.FilePathSeparator
+ useTheme := true
+ useStatic := true
+
+ if err != nil {
+ if err != helpers.ErrThemeUndefined {
+ c.Logger.WARN.Println(err)
+ }
+ useTheme = false
+ } else {
+ if _, err := source.Stat(themeDir); os.IsNotExist(err) {
+ c.Logger.WARN.Println("Unable to find Theme Static Directory:", themeDir)
+ useTheme = false
+ }
+ }
+
+ if _, err := source.Stat(staticDir); os.IsNotExist(err) {
+ c.Logger.WARN.Println("Unable to find Static Directory:", staticDir)
+ useStatic = false
+ }
+
+ if !useStatic && !useTheme {
+ return nil
+ }
+
+ if !useStatic {
+ c.Logger.INFO.Println(themeDir, "is the only static directory available to sync from")
+ return afero.NewReadOnlyFs(afero.NewBasePathFs(source, themeDir))
+ }
+
+ if !useTheme {
+ c.Logger.INFO.Println(staticDir, "is the only static directory available to sync from")
+ return afero.NewReadOnlyFs(afero.NewBasePathFs(source, staticDir))
+ }
+
+ c.Logger.INFO.Println("using a UnionFS for static directory comprised of:")
+ c.Logger.INFO.Println("Base:", themeDir)
+ c.Logger.INFO.Println("Overlay:", staticDir)
+ base := afero.NewReadOnlyFs(afero.NewBasePathFs(source, themeDir))
+ overlay := afero.NewReadOnlyFs(afero.NewBasePathFs(source, staticDir))
+ return afero.NewCopyOnWriteFs(base, overlay)
+}
+
+func (c *commandeer) copyStatic() error {
+ publishDir := c.PathSpec().AbsPathify(c.Cfg.GetString("publishDir")) + helpers.FilePathSeparator
+
+ // If root, remove the second '/'
+ if publishDir == "//" {
+ publishDir = helpers.FilePathSeparator
+ }
+
+ // Includes both theme/static & /static
+ staticSourceFs := c.getStaticSourceFs()
+
+ if staticSourceFs == nil {
+ c.Logger.WARN.Println("No static directories found to sync")
+ return nil
+ }
+
+ syncer := fsync.NewSyncer()
+ syncer.NoTimes = c.Cfg.GetBool("noTimes")
+ syncer.NoChmod = c.Cfg.GetBool("noChmod")
+ syncer.SrcFs = staticSourceFs
+ 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.INFO.Println("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.INFO.Println("syncing static files to", publishDir)
+
+ // because we are using a baseFs (to get the union right).
+ // set sync src to root
+ return syncer.Sync(publishDir, helpers.FilePathSeparator)
+}
+
+// getDirList provides NewWatcher() with a list of directories to watch for changes.
+func (c *commandeer) getDirList() []string {
+ var a []string
+ dataDir := c.PathSpec().AbsPathify(c.Cfg.GetString("dataDir"))
+ i18nDir := c.PathSpec().AbsPathify(c.Cfg.GetString("i18nDir"))
+ layoutDir := c.PathSpec().GetLayoutDirPath()
+ staticDir := c.PathSpec().GetStaticDirPath()
+
+ walker := func(path string, fi os.FileInfo, err error) error {
+ if err != nil {
+ if path == dataDir && os.IsNotExist(err) {
+ c.Logger.WARN.Println("Skip dataDir:", err)
+ return nil
+ }
+
+ if path == i18nDir && os.IsNotExist(err) {
+ c.Logger.WARN.Println("Skip i18nDir:", err)
+ return nil
+ }
+
+ if path == layoutDir && os.IsNotExist(err) {
+ c.Logger.WARN.Println("Skip layoutDir:", err)
+ return nil
+ }
+
+ if path == staticDir && os.IsNotExist(err) {
+ c.Logger.WARN.Println("Skip staticDir:", err)
+ return nil
+ }
+
+ if os.IsNotExist(err) {
+ // Ignore.
+ return nil
+ }
+
+ c.Logger.ERROR.Println("Walker: ", err)
+ return nil
+ }
+
+ // Skip .git directories.
+ // Related to https://github.com/gohugoio/hugo/issues/3468.
+ if fi.Name() == ".git" {
+ return nil
+ }
+
+ if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
+ link, err := filepath.EvalSymlinks(path)
+ if err != nil {
+ c.Logger.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", path, err)
+ return nil
+ }
+ linkfi, err := c.Fs.Source.Stat(link)
+ if err != nil {
+ c.Logger.ERROR.Printf("Cannot stat '%s', error was: %s", link, err)
+ return nil
+ }
+ if !linkfi.Mode().IsRegular() {
+ c.Logger.ERROR.Printf("Symbolic links for directories not supported, skipping '%s'", path)
+ }
+ return nil
+ }
+
+ if fi.IsDir() {
+ if fi.Name() == ".git" ||
+ fi.Name() == "node_modules" || fi.Name() == "bower_components" {
+ return filepath.SkipDir
+ }
+ a = append(a, path)
+ }
+ return nil
+ }
+
+ // SymbolicWalk will log anny ERRORs
+ _ = helpers.SymbolicWalk(c.Fs.Source, dataDir, walker)
+ _ = helpers.SymbolicWalk(c.Fs.Source, c.PathSpec().AbsPathify(c.Cfg.GetString("contentDir")), walker)
+ _ = helpers.SymbolicWalk(c.Fs.Source, i18nDir, walker)
+ _ = helpers.SymbolicWalk(c.Fs.Source, layoutDir, walker)
+ _ = helpers.SymbolicWalk(c.Fs.Source, staticDir, walker)
+
+ if c.PathSpec().ThemeSet() {
+ themesDir := c.PathSpec().GetThemeDir()
+ _ = helpers.SymbolicWalk(c.Fs.Source, filepath.Join(themesDir, "layouts"), walker)
+ _ = helpers.SymbolicWalk(c.Fs.Source, filepath.Join(themesDir, "static"), walker)
+ _ = helpers.SymbolicWalk(c.Fs.Source, filepath.Join(themesDir, "i18n"), walker)
+ _ = helpers.SymbolicWalk(c.Fs.Source, filepath.Join(themesDir, "data"), walker)
+ }
+
+ return a
+}
+
+func (c *commandeer) recreateAndBuildSites(watching bool) (err error) {
+ if err := c.initSites(); err != nil {
+ return err
+ }
+ if !quiet {
+ c.Logger.FEEDBACK.Println("Started building sites ...")
+ }
+ return Hugo.Build(hugolib.BuildCfg{CreateSitesFromConfig: true, Watching: watching, PrintStats: !quiet})
+}
+
+func (c *commandeer) resetAndBuildSites(watching bool) (err error) {
+ if err = c.initSites(); err != nil {
+ return
+ }
+ if !quiet {
+ c.Logger.FEEDBACK.Println("Started building sites ...")
+ }
+ return Hugo.Build(hugolib.BuildCfg{ResetState: true, Watching: watching, PrintStats: !quiet})
+}
+
+func (c *commandeer) initSites() error {
+ if Hugo != nil {
+ return nil
+ }
+ h, err := hugolib.NewHugoSites(*c.DepsCfg)
+
+ if err != nil {
+ return err
+ }
+ Hugo = h
+
+ return nil
+}
+
+func (c *commandeer) buildSites(watching bool) (err error) {
+ if err := c.initSites(); err != nil {
+ return err
+ }
+ if !quiet {
+ c.Logger.FEEDBACK.Println("Started building sites ...")
+ }
+ return Hugo.Build(hugolib.BuildCfg{Watching: watching, PrintStats: !quiet})
+}
+
+func (c *commandeer) rebuildSites(events []fsnotify.Event) error {
+ if err := c.initSites(); err != nil {
+ return err
+ }
+ return Hugo.Build(hugolib.BuildCfg{PrintStats: !quiet, Watching: true}, events...)
+}
+
+// newWatcher creates a new watcher to watch filesystem events.
+func (c *commandeer) newWatcher(port int) error {
+ if runtime.GOOS == "darwin" {
+ tweakLimit()
+ }
+
+ watcher, err := watcher.New(1 * time.Second)
+ var wg sync.WaitGroup
+
+ if err != nil {
+ return err
+ }
+
+ defer watcher.Close()
+
+ wg.Add(1)
+
+ for _, d := range c.getDirList() {
+ if d != "" {
+ _ = watcher.Add(d)
+ }
+ }
+
+ go func() {
+ for {
+ select {
+ case evs := <-watcher.Events:
+ c.Logger.INFO.Println("Received System Events:", evs)
+
+ staticEvents := []fsnotify.Event{}
+ dynamicEvents := []fsnotify.Event{}
+
+ 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
+ }
+ // 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 os.FileInfo, err error) error {
+ if f.IsDir() {
+ c.Logger.FEEDBACK.Println("adding created directory to watchlist", path)
+ if err := watcher.Add(path); err != nil {
+ return err
+ }
+ } else if !c.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 c.isStatic(ev.Name) {
+ staticEvents = append(staticEvents, ev)
+ } else {
+ dynamicEvents = append(dynamicEvents, ev)
+ }
+ }
+
+ if len(staticEvents) > 0 {
+ publishDir := c.PathSpec().AbsPathify(c.Cfg.GetString("publishDir")) + helpers.FilePathSeparator
+
+ // If root, remove the second '/'
+ if publishDir == "//" {
+ publishDir = helpers.FilePathSeparator
+ }
+
+ c.Logger.FEEDBACK.Println("\nStatic file changes detected")
+ const layout = "2006-01-02 15:04 -0700"
+ c.Logger.FEEDBACK.Println(time.Now().Format(layout))
+
+ if c.Cfg.GetBool("forceSyncStatic") {
+ c.Logger.FEEDBACK.Printf("Syncing all static files\n")
+ err := c.copyStatic()
+ if err != nil {
+ utils.StopOnErr(c.Logger, err, fmt.Sprintf("Error copying static files to %s", publishDir))
+ }
+ } else {
+ staticSourceFs := c.getStaticSourceFs()
+
+ if staticSourceFs == nil {
+ c.Logger.WARN.Println("No static directories found to sync")
+ return
+ }
+
+ syncer := fsync.NewSyncer()
+ syncer.NoTimes = c.Cfg.GetBool("noTimes")
+ syncer.NoChmod = c.Cfg.GetBool("noChmod")
+ syncer.SrcFs = staticSourceFs
+ syncer.DestFs = c.Fs.Destination
+
+ // prevent spamming the log on changes
+ logger := helpers.NewDistinctFeedbackLogger()
+
+ 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
+
+ // If we are here we already know the event took place in a static dir
+ relPath, err := c.PathSpec().MakeStaticPathRelative(fromPath)
+ if err != nil {
+ c.Logger.ERROR.Println(err)
+ 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 := staticSourceFs.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.ERROR.Println(err)
+ }
+ } else {
+ c.Logger.ERROR.Println(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.ERROR.Println(err)
+ }
+ }
+ }
+
+ if !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 len(staticEvents) > 0 {
+ for _, ev := range staticEvents {
+ path, _ := c.PathSpec().MakeStaticPathRelative(ev.Name)
+ livereload.RefreshPath(path)
+ }
+
+ } else {
+ livereload.ForceRefresh()
+ }
+ }
+ }
+
+ if len(dynamicEvents) > 0 {
+ c.Logger.FEEDBACK.Println("\nChange detected, rebuilding site")
+ const layout = "2006-01-02 15:04 -0700"
+ c.Logger.FEEDBACK.Println(time.Now().Format(layout))
+
+ if err := c.rebuildSites(dynamicEvents); err != nil {
+ c.Logger.ERROR.Println("Failed to rebuild site:", err)
+ }
+
+ if !buildWatch && !c.Cfg.GetBool("disableLiveReload") {
+
+ navigate := c.Cfg.GetBool("navigateToChanged")
+
+ var p *hugolib.Page
+
+ if navigate {
+
+ // It is probably more confusing than useful
+ // to navigate to a new URL on RENAME etc.
+ // so for now we use the WRITE event only.
+ name := pickOneWritePath(dynamicEvents)
+
+ if name != "" {
+ p = Hugo.GetContentPage(name)
+ }
+ }
+
+ if p != nil {
+ livereload.NavigateToPath(p.RelPermalink())
+ } else {
+ livereload.ForceRefresh()
+ }
+ }
+ }
+ case err := <-watcher.Errors:
+ if err != nil {
+ c.Logger.ERROR.Println(err)
+ }
+ }
+ }
+ }()
+
+ if port > 0 {
+ if !c.Cfg.GetBool("disableLiveReload") {
+ livereload.Initialize()
+ http.HandleFunc("/livereload.js", livereload.ServeJS)
+ http.HandleFunc("/livereload", livereload.Handler)
+ }
+
+ go c.serve(port)
+ }
+
+ wg.Wait()
+ return nil
+}
+
+func pickOneWritePath(events []fsnotify.Event) string {
+ name := ""
+
+ for _, ev := range events {
+ if ev.Op&fsnotify.Write == fsnotify.Write && len(ev.Name) > len(name) {
+ name = ev.Name
+ }
+ }
+
+ return name
+}
+
+func (c *commandeer) isStatic(path string) bool {
+ return strings.HasPrefix(path, c.PathSpec().GetStaticDirPath()) || (len(c.PathSpec().GetThemesDirPath()) > 0 && strings.HasPrefix(path, c.PathSpec().GetThemesDirPath()))
+}
+
+// isThemeVsHugoVersionMismatch returns whether the current Hugo version is
+// less than the theme's min_version.
+func (c *commandeer) isThemeVsHugoVersionMismatch() (mismatch bool, requiredMinVersion string) {
+ if !c.PathSpec().ThemeSet() {
+ return
+ }
+
+ themeDir := c.PathSpec().GetThemeDir()
+
+ path := filepath.Join(themeDir, "theme.toml")
+
+ exists, err := helpers.Exists(path, c.Fs.Source)
+
+ if err != nil || !exists {
+ return
+ }
+
+ b, err := afero.ReadFile(c.Fs.Source, path)
+
+ tomlMeta, err := parser.HandleTOMLMetaData(b)
+
+ if err != nil {
+ return
+ }
+
+ config := tomlMeta.(map[string]interface{})
+
+ if minVersion, ok := config["min_version"]; ok {
+ return helpers.CompareVersion(minVersion) > 0, fmt.Sprint(minVersion)
+ }
+
+ return
+}
diff --git a/commands/hugo_windows.go b/commands/hugo_windows.go
index c354e889d..7342f21a0 100644
--- a/commands/hugo_windows.go
+++ b/commands/hugo_windows.go
@@ -1,4 +1,4 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
+// 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.
@@ -13,21 +13,15 @@
package commands
-import (
- // For time zone lookups on Windows without Go installed.
- // See #8892
- _ "time/tzdata"
-
- "github.com/spf13/cobra"
-)
+import "github.com/spf13/cobra"
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 websites.
+ Hugo is a command-line tool for generating static website.
- You need to open PowerShell and run Hugo from there.
-
- Visit https://gohugo.io/ for more information.`
+ You need to open cmd.exe and run Hugo from there.
+
+ Visit http://gohugo.io/ for more information.`
}
diff --git a/commands/hugobuilder.go b/commands/hugobuilder.go
deleted file mode 100644
index 3b57ac5e9..000000000
--- a/commands/hugobuilder.go
+++ /dev/null
@@ -1,1157 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package commands
-
-import (
- "context"
- "errors"
- "fmt"
- "os"
- "path/filepath"
- "runtime"
- "runtime/pprof"
- "runtime/trace"
- "strings"
- "sync"
- "sync/atomic"
- "time"
-
- "github.com/bep/simplecobra"
- "github.com/fsnotify/fsnotify"
- "github.com/gohugoio/hugo/common/herrors"
- "github.com/gohugoio/hugo/common/htime"
- "github.com/gohugoio/hugo/common/hugo"
- "github.com/gohugoio/hugo/common/loggers"
- "github.com/gohugoio/hugo/common/maps"
- "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
- 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)
-}
-
-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 helpers.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 {
- 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 {
- return f.IsDir() && strings.HasPrefix(f.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) 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(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 {
- 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 {
- 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", maps.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 site.\nRun `hugo help new` for details.")
- }
-
- c.conf = 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.withConfE(func(conf *commonConfig) error {
- oldConf := conf
- 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
deleted file mode 100644
index 37a6b0dbf..000000000
--- a/commands/import.go
+++ /dev/null
@@ -1,618 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package commands
-
-import (
- "bytes"
- "context"
- "errors"
- "fmt"
- "io"
- "log"
- "os"
- "path/filepath"
- "regexp"
- "strconv"
- "strings"
- "time"
- "unicode"
-
- "github.com/bep/simplecobra"
- "github.com/gohugoio/hugo/common/htime"
- "github.com/gohugoio/hugo/common/hugio"
- "github.com/gohugoio/hugo/common/maps"
- "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 site from another system"
- cmd.Long = `Import a site 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 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]any{
- "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, "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) createSiteFromJekyll(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, _ := 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*%}`), 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 := 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 (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.createSiteFromJekyll(jekyllRoot, targetDir, jekyllPostDirs)
- if err != nil {
- return newUserError(err)
- }
-
- c.r.Println("Importing...")
-
- fileCount := 0
- callback := func(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:\n")
- 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
new file mode 100644
index 000000000..3d89fee0d
--- /dev/null
+++ b/commands/import_jekyll.go
@@ -0,0 +1,614 @@
+// 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 (
+ "bytes"
+ "errors"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "regexp"
+ "strconv"
+ "strings"
+ "time"
+
+ "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/cast"
+ "github.com/spf13/cobra"
+ jww "github.com/spf13/jwalterweatherman"
+)
+
+func init() {
+ importCmd.AddCommand(importJekyllCmd)
+}
+
+var importCmd = &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,
+}
+
+var 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: importFromJekyll,
+}
+
+func init() {
+ importJekyllCmd.Flags().Bool("force", false, "allow import into non-empty target directory")
+}
+
+func 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("Target path should not be inside the Jekyll root, aborting.")
+ }
+
+ forceImport, _ := cmd.Flags().GetBool("force")
+
+ fs := afero.NewOsFs()
+ jekyllPostDirs, hasAnyPost := getJekyllDirInfo(fs, jekyllRoot)
+ if !hasAnyPost {
+ return errors.New("Your Jekyll root contains neither posts nor drafts, aborting.")
+ }
+
+ site, err := createSiteFromJekyll(jekyllRoot, targetDir, jekyllPostDirs, forceImport)
+
+ if err != nil {
+ return newUserError(err)
+ }
+
+ jww.FEEDBACK.Println("Importing...")
+
+ fileCount := 0
+ callback := func(path string, fi os.FileInfo, 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(site, 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")
+
+ 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 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 := retrieveJekyllPostDir(fs, subDir); isPostDir {
+ postDirs[entry.Name()] = hasAnyPostInDir
+ if hasAnyPostInDir {
+ hasAnyPost = true
+ }
+ }
+ }
+ }
+ }
+ return postDirs, hasAnyPost
+}
+
+func 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 := retrieveJekyllPostDir(fs, subDir); isPostDir {
+ return isPostDir, hasAnyPost
+ }
+ }
+ }
+ }
+
+ return false, true
+}
+
+func createSiteFromJekyll(jekyllRoot, targetDir string, jekyllPostDirs map[string]bool, force bool) (*hugolib.Site, error) {
+ s, err := hugolib.NewSiteDefaultLang()
+ if err != nil {
+ return nil, err
+ }
+
+ fs := s.Fs.Source
+ if exists, _ := helpers.Exists(targetDir, fs); exists {
+ if isDir, _ := helpers.IsDir(targetDir, fs); !isDir {
+ return nil, errors.New("Target path \"" + targetDir + "\" already exists but not a directory")
+ }
+
+ isEmpty, _ := helpers.IsEmpty(targetDir, fs)
+
+ if !isEmpty && !force {
+ return nil, errors.New("Target path \"" + targetDir + "\" already exists and is not empty")
+ }
+ }
+
+ jekyllConfig := loadJekyllConfig(fs, jekyllRoot)
+
+ mkdir(targetDir, "layouts")
+ mkdir(targetDir, "content")
+ mkdir(targetDir, "archetypes")
+ mkdir(targetDir, "static")
+ mkdir(targetDir, "data")
+ mkdir(targetDir, "themes")
+
+ createConfigFromJekyll(fs, targetDir, "yaml", jekyllConfig)
+
+ copyJekyllFilesAndFolders(jekyllRoot, filepath.Join(targetDir, "static"), jekyllPostDirs)
+
+ return s, nil
+}
+
+func 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 := parser.HandleYAMLMetaData(b)
+
+ if err != nil {
+ return nil
+ }
+
+ return c.(map[string]interface{})
+}
+
+func createConfigFromJekyll(fs afero.Fs, inpath string, kind string, 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,
+ }
+ kind = parser.FormatSanitize(kind)
+
+ var buf bytes.Buffer
+ err = parser.InterfaceToConfig(in, parser.FormatToLeadRune(kind), &buf)
+ if err != nil {
+ return err
+ }
+
+ return helpers.WriteToDisk(filepath.Join(inpath, "config."+kind), &buf, fs)
+}
+
+func copyFile(source string, dest string) error {
+ sf, err := os.Open(source)
+ if err != nil {
+ return err
+ }
+ defer sf.Close()
+ df, err := os.Create(dest)
+ if err != nil {
+ return err
+ }
+ defer df.Close()
+ _, err = io.Copy(df, sf)
+ if err == nil {
+ si, err := os.Stat(source)
+ if err != nil {
+ err = os.Chmod(dest, si.Mode())
+
+ if err != nil {
+ return err
+ }
+ }
+
+ }
+ return nil
+}
+
+func copyDir(source string, dest string) error {
+ fi, err := os.Stat(source)
+ if err != nil {
+ return err
+ }
+ if !fi.IsDir() {
+ return errors.New(source + " is not a directory")
+ }
+ err = os.MkdirAll(dest, fi.Mode())
+ if err != nil {
+ return err
+ }
+ entries, err := ioutil.ReadDir(source)
+ for _, entry := range entries {
+ sfp := filepath.Join(source, entry.Name())
+ dfp := filepath.Join(dest, entry.Name())
+ if entry.IsDir() {
+ err = copyDir(sfp, dfp)
+ if err != nil {
+ jww.ERROR.Println(err)
+ }
+ } else {
+ err = copyFile(sfp, dfp)
+ if err != nil {
+ jww.ERROR.Println(err)
+ }
+ }
+
+ }
+ return nil
+}
+
+func copyJekyllFilesAndFolders(jekyllRoot, dest string, jekyllPostDirs map[string]bool) (err error) {
+ fi, err := os.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)
+ 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 = copyDir(sfp, dfp)
+ 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 = copyFile(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(s *hugolib.Site, 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
+ }
+
+ psr, err := parser.ReadFrom(bytes.NewReader(contentBytes))
+ if err != nil {
+ jww.ERROR.Println("Parse file error:", path)
+ return err
+ }
+
+ metadata, err := psr.Metadata()
+ if err != nil {
+ jww.ERROR.Println("Processing file error:", path)
+ return err
+ }
+
+ newmetadata, err := convertJekyllMetaData(metadata, postName, postDate, draft)
+ if err != nil {
+ jww.ERROR.Println("Convert metadata error:", path)
+ return err
+ }
+
+ jww.TRACE.Println(newmetadata)
+ content := convertJekyllContent(newmetadata, string(psr.Content()))
+
+ page, err := s.NewPage(filename)
+ if err != nil {
+ jww.ERROR.Println("New page error", filename)
+ return err
+ }
+
+ page.SetDir(targetParentDir)
+ page.SetSourceContent([]byte(content))
+ page.SetSourceMetaData(newmetadata, parser.FormatToLeadRune("yaml"))
+ page.SaveSourceAs(targetFile)
+
+ jww.TRACE.Println("Target file:", targetFile)
+
+ return nil
+}
+
+func convertJekyllMetaData(m interface{}, postName string, postDate time.Time, draft bool) (interface{}, error) {
+ url := postDate.Format("/2006/01/02/") + postName + "/"
+
+ metadata, err := cast.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 {
+ 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["url"] = url
+ metadata["date"] = postDate.Format(time.RFC3339)
+
+ return metadata, nil
+}
+
+func convertJekyllContent(m interface{}, content string) string {
+ metadata, _ := cast.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*highlight\s*(.*?)\s*%}`), "{{< highlight $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},
+ }
+
+ for _, replace := range replaceListFunc {
+ content = replace.re.ReplaceAllStringFunc(content, replace.replace)
+ }
+
+ return content
+}
+
+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
new file mode 100644
index 000000000..90a05c01c
--- /dev/null
+++ b/commands/import_jekyll_test.go
@@ -0,0 +1,126 @@
+// 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"
+ "github.com/stretchr/testify/assert"
+ "testing"
+ "time"
+)
+
+func TestParseJekyllFilename(t *testing.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)
+ assert.Equal(t, err, nil)
+ assert.Equal(t, expectResult[i].postDate.Format("2006-01-02"), postDate.Format("2006-01-02"))
+ assert.Equal(t, expectResult[i].postName, postName)
+ }
+}
+
+func TestConvertJekyllMetadata(t *testing.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","url":"/2015/10/01/testPost/"}`},
+ {map[interface{}]interface{}{}, "testPost", time.Date(2015, 10, 1, 0, 0, 0, 0, time.UTC), true,
+ `{"date":"2015-10-01T00:00:00Z","draft":true,"url":"/2015/10/01/testPost/"}`},
+ {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","url":"/2015/10/01/testPost/"}`},
+ {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","url":"/2015/10/01/testPost/"}`},
+ {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","url":"/2015/10/01/testPost/"}`},
+ }
+
+ for _, data := range testDataList {
+ result, err := convertJekyllMetaData(data.metadata, data.postName, data.postDate, data.draft)
+ assert.Equal(t, nil, err)
+ jsonResult, err := json.Marshal(result)
+ assert.Equal(t, nil, err)
+ assert.Equal(t, data.expect, string(jsonResult))
+ }
+}
+
+func TestConvertJekyllContent(t *testing.T) {
+ testDataList := []struct {
+ metadata interface{}
+ content string
+ expect string
+ }{
+ {map[interface{}]interface{}{},
+ `Test content\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`, `Test 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 >}}"},
+
+ // 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\" >}}"},
+ }
+
+ for _, data := range testDataList {
+ result := convertJekyllContent(data.metadata, data.content)
+ assert.Equal(t, data.expect, result)
+ }
+}
diff --git a/commands/limit_darwin.go b/commands/limit_darwin.go
new file mode 100644
index 000000000..9246f4497
--- /dev/null
+++ b/commands/limit_darwin.go
@@ -0,0 +1,85 @@
+// 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.
+
+// 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 (
+ "syscall"
+
+ "github.com/spf13/cobra"
+ jww "github.com/spf13/jwalterweatherman"
+)
+
+func init() {
+ checkCmd.AddCommand(limit)
+}
+
+var limit = &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)
+
+ jww.FEEDBACK.Println("Attempting to increase limit")
+ rLimit.Max = 999999
+ rLimit.Cur = 999999
+ 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
+ },
+}
+
+func tweakLimit() {
+ var rLimit syscall.Rlimit
+ err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
+ if err != nil {
+ jww.ERROR.Println("Unable to obtain rLimit", err)
+ }
+ if rLimit.Cur < rLimit.Max {
+ rLimit.Max = 64000
+ rLimit.Cur = 64000
+ err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
+ if err != nil {
+ jww.WARN.Println("Unable to increase number of open files limit", err)
+ }
+ }
+}
diff --git a/commands/limit_others.go b/commands/limit_others.go
new file mode 100644
index 000000000..c757f174e
--- /dev/null
+++ b/commands/limit_others.go
@@ -0,0 +1,32 @@
+// 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.
+
+// +build !darwin
+// 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
+
+func tweakLimit() {
+ // nothing to do
+}
diff --git a/commands/list.go b/commands/list.go
index 42f3408ba..b2a6b5395 100644
--- a/commands/list.go
+++ b/commands/list.go
@@ -1,4 +1,4 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
+// 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.
@@ -14,200 +14,148 @@
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"
)
-// 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(),
- }
- }
+func init() {
+ listCmd.AddCommand(listDraftsCmd)
+ listCmd.AddCommand(listFutureCmd)
+ listCmd.AddCommand(listExpiredCmd)
+ listCmd.PersistentFlags().StringVarP(&source, "source", "s", "", "filesystem path to read files relative from")
+ listCmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{})
+}
- 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)
+var listCmd = &cobra.Command{
+ Use: "list",
+ Short: "Listing out various types of content",
+ Long: `Listing out various types of content.
+
+List requires a subcommand, e.g. ` + "`hugo list drafts`.",
+ RunE: nil,
+}
+
+var listDraftsCmd = &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 {
+
+ cfg, err := InitializeConfig()
if err != nil {
return err
}
- writer := csv.NewWriter(r.StdOut)
- defer writer.Flush()
+ c, err := newCommandeer(cfg)
+ if err != nil {
+ return err
+ }
- writer.Write([]string{
- "path",
- "slug",
- "title",
- "date",
- "expiryDate",
- "publishDate",
- "draft",
- "permalink",
- "kind",
- "section",
- })
+ c.Set("buildDrafts", true)
- for _, p := range h.Pages() {
- if shouldInclude(p) {
- record := createRecord(h.Conf.BaseConfig().WorkingDir, p)
- if err := writer.Write(record); err != nil {
- return err
- }
+ sites, err := hugolib.NewHugoSites(*cfg)
+
+ if err != nil {
+ return newSystemError("Error creating sites", err)
+ }
+
+ if err := sites.Build(hugolib.BuildCfg{SkipRender: true}); err != nil {
+ return newSystemError("Error Processing Source Content", err)
+ }
+
+ for _, p := range sites.Pages() {
+ if p.IsDraft() {
+ jww.FEEDBACK.Println(filepath.Join(p.File.Dir(), p.File.LogicalName()))
}
+
}
return nil
- }
- 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 list(cd, r, shouldInclude,
- "buildDrafts", true,
- "buildFuture", true,
- "buildExpired", true,
- )
- },
- withc: func(cmd *cobra.Command, r *rootCommand) {
- cmd.ValidArgsFunction = cobra.NoFileCompletions
- },
- },
- &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 list(cd, r, shouldInclude,
- "buildFuture", true,
- "buildDrafts", true,
- )
- },
- withc: func(cmd *cobra.Command, r *rootCommand) {
- cmd.ValidArgsFunction = cobra.NoFileCompletions
- },
- },
- &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 list(cd, r, shouldInclude,
- "buildExpired", true,
- "buildDrafts", true,
- )
- },
- withc: func(cmd *cobra.Command, r *rootCommand) {
- cmd.ValidArgsFunction = cobra.NoFileCompletions
- },
- },
- &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
- }
- 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 list(cd, r, shouldInclude)
- },
- withc: func(cmd *cobra.Command, r *rootCommand) {
- cmd.ValidArgsFunction = cobra.NoFileCompletions
- },
- },
- },
- }
+ },
}
-type listCommand struct {
- commands []simplecobra.Commander
+var listFutureCmd = &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 {
+
+ cfg, err := InitializeConfig()
+ if err != nil {
+ return err
+ }
+
+ c, err := newCommandeer(cfg)
+ if err != nil {
+ return err
+ }
+
+ c.Set("buildFuture", true)
+
+ sites, err := hugolib.NewHugoSites(*cfg)
+
+ if err != nil {
+ return newSystemError("Error creating sites", err)
+ }
+
+ if err := sites.Build(hugolib.BuildCfg{SkipRender: true}); err != nil {
+ return newSystemError("Error Processing Source Content", err)
+ }
+
+ for _, p := range sites.Pages() {
+ if p.IsFuture() {
+ jww.FEEDBACK.Println(filepath.Join(p.File.Dir(), p.File.LogicalName()))
+ }
+
+ }
+
+ return nil
+
+ },
}
-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
-}
-
-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
-}
-
-func (c *listCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
- return nil
+var listExpiredCmd = &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 {
+
+ cfg, err := InitializeConfig()
+ if err != nil {
+ return err
+ }
+
+ c, err := newCommandeer(cfg)
+ if err != nil {
+ return err
+ }
+
+ c.Set("buildExpired", true)
+
+ sites, err := hugolib.NewHugoSites(*cfg)
+
+ if err != nil {
+ return newSystemError("Error creating sites", err)
+ }
+
+ if err := sites.Build(hugolib.BuildCfg{SkipRender: true}); err != nil {
+ return newSystemError("Error Processing Source Content", err)
+ }
+
+ for _, p := range sites.Pages() {
+ if p.IsExpired() {
+ jww.FEEDBACK.Println(filepath.Join(p.File.Dir(), p.File.LogicalName()))
+ }
+
+ }
+
+ return nil
+
+ },
}
diff --git a/commands/list_config.go b/commands/list_config.go
new file mode 100644
index 000000000..f47f6e144
--- /dev/null
+++ b/commands/list_config.go
@@ -0,0 +1,66 @@
+// 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.Print the version number of Hug
+
+package commands
+
+import (
+ "reflect"
+ "sort"
+
+ "github.com/spf13/cobra"
+ jww "github.com/spf13/jwalterweatherman"
+ "github.com/spf13/viper"
+)
+
+var configCmd = &cobra.Command{
+ Use: "config",
+ Short: "Print the site configuration",
+ Long: `Print the site configuration, both default and custom settings.`,
+}
+
+func init() {
+ configCmd.RunE = printConfig
+}
+
+func printConfig(cmd *cobra.Command, args []string) error {
+ cfg, err := InitializeConfig(configCmd)
+
+ if err != nil {
+ return err
+ }
+
+ allSettings := cfg.Cfg.(*viper.Viper).AllSettings()
+
+ var separator string
+ if allSettings["metadataformat"] == "toml" {
+ separator = " = "
+ } else {
+ separator = ": "
+ }
+
+ var keys []string
+ for k := range allSettings {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ for _, k := range keys {
+ kv := reflect.ValueOf(allSettings[k])
+ if kv.Kind() == reflect.String {
+ jww.FEEDBACK.Printf("%s%s\"%+v\"\n", k, separator, allSettings[k])
+ } else {
+ jww.FEEDBACK.Printf("%s%s%+v\n", k, separator, allSettings[k])
+ }
+ }
+
+ return nil
+}
diff --git a/commands/mod.go b/commands/mod.go
deleted file mode 100644
index 58155f9be..000000000
--- a/commands/mod.go
+++ /dev/null
@@ -1,344 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package commands
-
-import (
- "context"
- "errors"
- "os"
- "path/filepath"
-
- "github.com/bep/simplecobra"
- "github.com/gohugoio/hugo/config"
- "github.com/gohugoio/hugo/modules/npm"
- "github.com/spf13/cobra"
-)
-
-const commonUsageMod = `
-Note that Hugo will always start out by resolving the components defined in the site
-configuration, provided by a _vendor directory (if no --ignoreVendorPaths flag provided),
-Go Modules, or a folder inside the themes directory, in that order.
-
-See https://gohugo.io/hugo-modules/ for more information.
-
-`
-
-// buildConfigCommands creates a new config command and its subcommands.
-func newModCommands() *modCommands {
- var (
- clean bool
- pattern string
- all bool
- )
-
- npmCommand := &simpleCommand{
- name: "npm",
- short: "Various npm helpers",
- long: `Various npm (Node package manager) helpers.`,
- commands: []simplecobra.Commander{
- &simpleCommand{
- name: "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.
-`,
- 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 npm.Pack(h.BaseFs.ProjectSourceFs, h.BaseFs.AssetsWithDuplicatesPreserved.Fs)
- },
- },
- },
- }
-
- 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 files from module cache.", count)
- return err
- }
-
- 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()
- },
- },
-
- &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 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.
-` + 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
- }
-
- 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
- }
-
- // 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
- return client.Get(args...)
- }
- },
- },
- npmCommand,
- },
- }
-}
-
-type modCommands struct {
- r *rootCommand
-
- commands []simplecobra.Commander
-}
-
-func (c *modCommands) Commands() []simplecobra.Commander {
- return c.commands
-}
-
-func (c *modCommands) Name() string {
- return "mod"
-}
-
-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 nil
-}
-
-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".
-
-` + commonUsageMod
- cmd.RunE = nil
- return nil
-}
-
-func (c *modCommands) PreRun(cd, runner *simplecobra.Commandeer) error {
- c.r = cd.Root.Command.(*rootCommand)
- return nil
-}
diff --git a/commands/new.go b/commands/new.go
index 81e1c65a4..5ed1fe8e0 100644
--- a/commands/new.go
+++ b/commands/new.go
@@ -1,4 +1,4 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
+// 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.
@@ -15,213 +15,385 @@ package commands
import (
"bytes"
- "context"
+ "errors"
+ "fmt"
+ "os"
"path/filepath"
"strings"
+ "time"
- "github.com/bep/simplecobra"
- "github.com/gohugoio/hugo/common/paths"
- "github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/create"
- "github.com/gohugoio/hugo/create/skeletons"
+ "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"
+ "github.com/spf13/viper"
)
-func newNewCommand() *newCommand {
- var (
- force bool
- contentType string
- format string
- )
+var (
+ configFormat string
+ contentEditor string
+ contentType 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.
+func init() {
+ newSiteCmd.Flags().StringVarP(&configFormat, "format", "f", "toml", "config & frontmatter format")
+ newSiteCmd.Flags().Bool("force", false, "init inside non-empty directory")
+ newCmd.Flags().StringVarP(&contentType, "kind", "k", "", "content type to create")
+ newCmd.PersistentFlags().StringVarP(&source, "source", "s", "", "filesystem path to read files relative from")
+ newCmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{})
+ newCmd.Flags().StringVar(&contentEditor, "editor", "", "edit new content with this editor, if provided")
+
+ newCmd.AddCommand(newSiteCmd)
+ newCmd.AddCommand(newThemeCmd)
+
+}
+
+var newCmd = &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.
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.
+If archetypes are provided in your theme or site, they will be used.`,
-Ensure you run this within the root directory of your 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")
- }
- h, err := r.Hugo(flagsToCfg(cd, nil))
- 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: "site",
- use: "site [path]",
- short: "Create a new site",
- long: `Create a new site at the specified path.`,
- 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
- }
+ RunE: NewContent,
+}
- cfg := config.New()
- cfg.Set("workingDir", createpath)
- cfg.Set("publishDir", "public")
+var newSiteCmd = &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: NewSite,
+}
- conf, err := r.ConfigFromProvider(configKey{counter: r.configVersionID.Load()}, flagsToCfg(cd, cfg))
- if err != nil {
- return err
- }
- sourceFs := conf.fs.Source
+var newThemeCmd = &cobra.Command{
+ Use: "theme [name]",
+ Short: "Create a new theme",
+ Long: `Create a new theme (skeleton) called [name] in the current directory.
+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: NewTheme,
+}
- err = skeletons.CreateSite(createpath, sourceFs, force, format)
- if err != nil {
- return err
- }
+// NewContent adds new content to a Hugo site.
+func NewContent(cmd *cobra.Command, args []string) error {
+ cfg, err := InitializeConfig()
- r.Printf("Congratulations! Your new Hugo site was created in %s.\n\n", createpath)
- r.Println(c.newSiteNextStepsText(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))
- },
- },
- },
+ if err != nil {
+ return err
}
- return c
+ c, err := newCommandeer(cfg)
+ if err != nil {
+ return err
+ }
+
+ if cmd.Flags().Changed("editor") {
+ c.Set("newContentEditor", contentEditor)
+ }
+
+ if len(args) < 1 {
+ return newUserError("path needs to be provided")
+ }
+
+ createPath := args[0]
+
+ var kind string
+
+ createPath, kind = newContentPathSection(createPath)
+
+ if contentType != "" {
+ kind = contentType
+ }
+
+ ps, err := helpers.NewPathSpec(cfg.Fs, cfg.Cfg)
+ if err != nil {
+ return err
+ }
+
+ // If a site isn't in use in the archetype template, we can skip the build.
+ siteFactory := func(filename string, siteUsed bool) (*hugolib.Site, error) {
+ if !siteUsed {
+ return hugolib.NewSite(*cfg)
+ }
+ var s *hugolib.Site
+ if err := c.initSites(); err != nil {
+ return nil, err
+ }
+
+ if err := Hugo.Build(hugolib.BuildCfg{SkipRender: true, PrintStats: false}); err != nil {
+ return nil, err
+ }
+
+ s = Hugo.Sites[0]
+
+ if len(Hugo.Sites) > 1 {
+ // Find the best match.
+ for _, ss := range Hugo.Sites {
+ if strings.Contains(createPath, "."+ss.Language.Lang) {
+ s = ss
+ break
+ }
+ }
+ }
+ return s, nil
+ }
+
+ return create.NewContent(ps, siteFactory, kind, createPath)
}
-type newCommand struct {
- rootCmd *rootCommand
+func 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"),
+ }
- commands []simplecobra.Commander
-}
+ 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")
+ }
-func (c *newCommand) Commands() []simplecobra.Commander {
- return c.commands
-}
+ isEmpty, _ := helpers.IsEmpty(basepath, fs.Source)
-func (c *newCommand) Name() string {
- return "new"
-}
+ switch {
+ case !isEmpty && !force:
+ return errors.New(basepath + " already exists and is not empty")
+
+ case !isEmpty && force:
+ all := append(dirs, filepath.Join(basepath, "config."+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 fmt.Errorf("Failed to create dir: %s", err)
+ }
+ }
+
+ createConfig(fs, basepath, configFormat)
+
+ // Create a defaul archetype file.
+ helpers.SafeWriteToDisk(filepath.Join(archeTypePath, "default.md"),
+ strings.NewReader(create.ArchetypeTemplateTemplate), fs.Source)
+
+ jww.FEEDBACK.Printf("Congratulations! Your new Hugo site is created in %s.\n\n", basepath)
+ jww.FEEDBACK.Println(nextStepsText())
-func (c *newCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
return nil
}
-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.
-
-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.`
-
- cmd.RunE = nil
- return nil
-}
-
-func (c *newCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
- c.rootCmd = cd.Root.Command.(*rootCommand)
- return nil
-}
-
-func (c *newCommand) newSiteNextStepsText(path string, format string) string {
- format = strings.ToLower(format)
+func nextStepsText() string {
var nextStepsText bytes.Buffer
- nextStepsText.WriteString(`Just a few more steps...
+ nextStepsText.WriteString(`Just a few more steps and you're ready to go:
-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 `)
+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(`".
-5. Start the embedded web server with the command "hugo server --buildDrafts".
+3. Start the built-in live server via "hugo server".
-See documentation at https://gohugo.io/.`)
+Visit https://gohugo.io/ for quickstart guide and full documentation.`)
return nextStepsText.String()
}
+
+// NewSite creates a new Hugo site and initializes a structured Hugo directory.
+func 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 doNewSite(hugofs.NewDefault(viper.New()), createpath, forceNew)
+}
+
+// NewTheme creates a new Hugo theme.
+func NewTheme(cmd *cobra.Command, args []string) error {
+ cfg, err := InitializeConfig()
+
+ if err != nil {
+ return err
+ }
+
+ if len(args) < 1 {
+ return newUserError("theme name needs to be provided")
+ }
+
+ c, err := newCommandeer(cfg)
+ if err != nil {
+ return err
+ }
+
+ createpath := c.PathSpec().AbsPathify(filepath.Join(c.Cfg.GetString("themesDir"), args[0]))
+ jww.INFO.Println("creating theme at", createpath)
+
+ if x, _ := helpers.Exists(createpath, cfg.Fs.Source); x {
+ return newUserError(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")
+
+ 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.md"), bytes.NewReader(by), cfg.Fs.Source)
+ if err != nil {
+ return err
+ }
+
+ createThemeMD(cfg.Fs, createpath)
+
+ return nil
+}
+
+func mkdir(x ...string) {
+ p := filepath.Join(x...)
+
+ err := os.MkdirAll(p, 0777) // before umask
+ if err != nil {
+ jww.FATAL.Fatalln(err)
+ }
+}
+
+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 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.md"
+description = ""
+homepage = "http://example.com/"
+tags = []
+features = []
+min_version = "0.25"
+
+[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
+}
+
+func newContentPathSection(path string) (string, string) {
+ // Forward slashes is used in all examples. Convert if needed.
+ // Issue #1133
+ createpath := filepath.FromSlash(path)
+ var section string
+ // assume the first directory is the section (kind)
+ if strings.Contains(createpath[1:], helpers.FilePathSeparator) {
+ section = helpers.GuessSection(createpath)
+ }
+
+ return createpath, section
+}
+
+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",
+ }
+ kind = parser.FormatSanitize(kind)
+
+ var buf bytes.Buffer
+ err = parser.InterfaceToConfig(in, parser.FormatToLeadRune(kind), &buf)
+ if err != nil {
+ return err
+ }
+
+ return helpers.WriteToDisk(filepath.Join(inpath, "config."+kind), &buf, fs.Source)
+}
diff --git a/commands/new_test.go b/commands/new_test.go
new file mode 100644
index 000000000..c9adb83d4
--- /dev/null
+++ b/commands/new_test.go
@@ -0,0 +1,122 @@
+// 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 (
+ "path/filepath"
+ "testing"
+
+ "github.com/gohugoio/hugo/hugofs"
+ "github.com/spf13/viper"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+// Issue #1133
+func TestNewContentPathSectionWithForwardSlashes(t *testing.T) {
+ p, s := newContentPathSection("/post/new.md")
+ assert.Equal(t, filepath.FromSlash("/post/new.md"), p)
+ assert.Equal(t, "post", s)
+}
+
+func checkNewSiteInited(fs *hugofs.Fs, basepath string, t *testing.T) {
+
+ 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 := fs.Source.Stat(path)
+ require.NoError(t, err)
+ }
+}
+
+func TestDoNewSite(t *testing.T) {
+ basepath := filepath.Join("base", "blog")
+ _, fs := newTestCfg()
+
+ require.NoError(t, doNewSite(fs, basepath, false))
+
+ checkNewSiteInited(fs, basepath, t)
+}
+
+func TestDoNewSite_noerror_base_exists_but_empty(t *testing.T) {
+ basepath := filepath.Join("base", "blog")
+ _, fs := newTestCfg()
+
+ require.NoError(t, fs.Source.MkdirAll(basepath, 777))
+
+ require.NoError(t, doNewSite(fs, basepath, false))
+}
+
+func TestDoNewSite_error_base_exists(t *testing.T) {
+ basepath := filepath.Join("base", "blog")
+ _, fs := newTestCfg()
+
+ require.NoError(t, fs.Source.MkdirAll(basepath, 777))
+ _, err := fs.Source.Create(filepath.Join(basepath, "foo"))
+ require.NoError(t, err)
+ // Since the directory already exists and isn't empty, expect an error
+ require.Error(t, doNewSite(fs, basepath, false))
+
+}
+
+func TestDoNewSite_force_empty_dir(t *testing.T) {
+ basepath := filepath.Join("base", "blog")
+ _, fs := newTestCfg()
+
+ require.NoError(t, fs.Source.MkdirAll(basepath, 777))
+
+ require.NoError(t, doNewSite(fs, basepath, true))
+
+ checkNewSiteInited(fs, basepath, t)
+}
+
+func TestDoNewSite_error_force_dir_inside_exists(t *testing.T) {
+ basepath := filepath.Join("base", "blog")
+ _, fs := newTestCfg()
+
+ contentPath := filepath.Join(basepath, "content")
+
+ require.NoError(t, fs.Source.MkdirAll(contentPath, 777))
+ require.Error(t, doNewSite(fs, basepath, true))
+}
+
+func TestDoNewSite_error_force_config_inside_exists(t *testing.T) {
+ basepath := filepath.Join("base", "blog")
+ _, fs := newTestCfg()
+
+ configPath := filepath.Join(basepath, "config.toml")
+ require.NoError(t, fs.Source.MkdirAll(basepath, 777))
+ _, err := fs.Source.Create(configPath)
+ require.NoError(t, err)
+
+ require.Error(t, doNewSite(fs, basepath, true))
+}
+
+func newTestCfg() (*viper.Viper, *hugofs.Fs) {
+
+ v := viper.New()
+ fs := hugofs.NewMem(v)
+
+ v.SetFs(fs.Source)
+
+ return v, fs
+
+}
diff --git a/commands/release.go b/commands/release.go
index 059f04eb8..0764685f0 100644
--- a/commands/release.go
+++ b/commands/release.go
@@ -1,4 +1,6 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
+// +build release
+
+// 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.
@@ -14,40 +16,53 @@
package commands
import (
- "context"
+ "errors"
- "github.com/bep/simplecobra"
"github.com/gohugoio/hugo/releaser"
"github.com/spf13/cobra"
)
-// 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
- )
+func init() {
+ HugoCmd.AddCommand(createReleaser().cmd)
+}
- 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)
- if err != nil {
- return err
- }
+type releaseCommandeer struct {
+ cmd *cobra.Command
- 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.RegisterFlagCompletionFunc("step", cobra.FixedCompletions([]string{"1", "2"}, cobra.ShellCompDirectiveNoFileComp))
+ version string
+
+ skipPublish bool
+ try bool
+
+ step int
+}
+
+func createReleaser() *releaseCommandeer {
+ // 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,
},
}
+
+ 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().IntVarP(&r.step, "step", "s", -1, "release step, defaults to -1 for all steps.")
+ 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 (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.step, r.skipPublish, r.try).Run()
}
diff --git a/commands/server.go b/commands/server.go
index c8895b9a1..89d5c239e 100644
--- a/commands/server.go
+++ b/commands/server.go
@@ -1,4 +1,4 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
+// 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.
@@ -14,417 +14,60 @@
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"
- "sort"
+ "runtime"
"strconv"
"strings"
- "sync"
- "sync/atomic"
- "syscall"
"time"
- "github.com/bep/mclib"
- "github.com/pkg/browser"
-
- "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/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"
- "github.com/spf13/fsync"
- "golang.org/x/sync/errgroup"
- "golang.org/x/sync/semaphore"
+ jww "github.com/spf13/jwalterweatherman"
)
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
+ serverWatch 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:", "",
-)
+var serverCmd = &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.
-const (
- configChangeConfig = "config file"
- configChangeGoMod = "go.mod file"
- configChangeGoWork = "go work file"
-)
+'hugo server' will avoid writing the rendered and served content to disk,
+preferring to store it in memory.
-const (
- hugoHeaderRedirect = "X-Hugo-Redirect"
-)
-
-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 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 c
-}
-
-func (c *serverCommand) Commands() []simplecobra.Commander {
- return c.commands
-}
-
-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
-}
-
-// 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
-}
-
-type fileChangeDetector struct {
- sync.Mutex
- current map[string]uint64
- prev map[string]uint64
-
- irrelevantRe *regexp.Regexp
-}
-
-func (f *fileChangeDetector) OnFileClose(name string, checksum uint64) {
- f.Lock()
- defer f.Unlock()
- f.current[name] = checksum
-}
-
-func (f *fileChangeDetector) PrepareNew() {
- if f == nil {
- return
- }
-
- f.Lock()
- defer f.Unlock()
-
- if f.current == nil {
- f.current = make(map[string]uint64)
- f.prev = make(map[string]uint64)
- return
- }
-
- f.prev = make(map[string]uint64)
- maps.Copy(f.prev, f.current)
- f.current = make(map[string]uint64)
-}
-
-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)
- }
- }
-
- return f.filterIrrelevantAndSort(c)
-}
-
-func (f *fileChangeDetector) filterIrrelevantAndSort(in []string) []string {
- var filtered []string
- for _, v := range in {
- if !f.irrelevantRe.MatchString(v) {
- filtered = append(filtered, v)
- }
- }
- sort.Strings(filtered)
- return filtered
-}
-
-type fileServer struct {
- baseURLs []urls.BaseURL
- roots []string
- errorTemplate func(err any) (io.Reader, error)
- c *serverCommand
-}
-
-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].p
- listener := f.c.serverPorts[i].ln
- logger := f.c.r.logger
-
- if i == 0 {
- 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 {
- r.Printf("Serving pages from %s\n", mainTarget)
- }
- }
-
- 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 {
- r.Println("Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender")
- }
-
- decorate := func(h http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if f.c.showErrorInBrowser {
- // First check the error state
- err := f.c.getErrorWithContext()
- if err != nil {
- f.c.errState.setWasErr(true)
- w.WriteHeader(500)
- r, err := f.errorTemplate(err)
- if err != nil {
- logger.Errorln(err)
- }
-
- port = 1313
- f.c.withConf(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))
-
- return
- }
- }
-
- 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, _ := url.PathUnescape(strings.TrimSuffix(r.RequestURI, "?"+r.URL.RawQuery))
-
- for _, header := range serverConfig.MatchHeaders(requestURI) {
- w.Header().Set(header.Key, header.Value)
- }
-
- 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 {
- 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
-
- }
- }
- }
- }
-
- if f.c.fastRenderMode && f.c.errState.buildErr() == nil {
- if isNavigation(requestURI, r) {
- if !f.c.visitedURLs.Contains(requestURI) {
- // If not already on stack, re-render that single page.
- if err := f.c.partialReRender(requestURI); err != nil {
- f.c.handleBuildErr(err, fmt.Sprintf("Failed to render %q", requestURI))
- if f.c.showErrorInBrowser {
- http.Redirect(w, r, requestURI, http.StatusMovedPermanently)
- return
- }
- }
- }
-
- f.c.visitedURLs.Add(requestURI)
-
- }
- }
-
- h.ServeHTTP(w, r)
- })
- }
-
- fileserver := decorate(http.FileServer(fs))
- mu := http.NewServeMux()
- if baseURL.Path() == "" || baseURL.Path() == "/" {
- mu.Handle("/", fileserver)
- } else {
- 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.c.serverInterface, strconv.Itoa(port))
-
- return mu, listener, baseURL.String(), endpoint, nil
-}
-
-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
+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: server,
}
type filesOnlyFs struct {
fs http.FileSystem
}
+type noDirFile struct {
+ http.File
+}
+
func (fs filesOnlyFs) Open(name string) (http.File, error) {
f, err := fs.fs.Open(name)
if err != nil {
@@ -433,825 +76,240 @@ func (fs filesOnlyFs) Open(name string) (http.File, error) {
return noDirFile{f}, nil
}
-type noDirFile struct {
- http.File
-}
-
func (f noDirFile) Readdir(count int) ([]os.FileInfo, error) {
return nil, nil
}
-type serverCommand struct {
- r *rootCommand
+func init() {
+ initHugoBuilderFlags(serverCmd)
- commands []simplecobra.Commander
+ serverCmd.Flags().IntVarP(&serverPort, "port", "p", 1313, "port on which the server will listen")
+ serverCmd.Flags().StringVarP(&serverInterface, "bind", "", "127.0.0.1", "interface to which the server will bind")
+ serverCmd.Flags().BoolVarP(&serverWatch, "watch", "w", true, "watch filesystem for changes and recreate as needed")
+ serverCmd.Flags().BoolVarP(&serverAppend, "appendPort", "", true, "append port to baseURL")
+ serverCmd.Flags().BoolVar(&disableLiveReload, "disableLiveReload", false, "watch without enabling live browser reload on rebuild")
+ serverCmd.Flags().BoolVar(&navigateToChanged, "navigateToChanged", false, "navigate to changed content file on live browser reload")
+ serverCmd.Flags().BoolVar(&renderToDisk, "renderToDisk", false, "render to Destination path (default is render to memory & serve from there)")
+ serverCmd.Flags().String("memstats", "", "log memory usage to this file")
+ serverCmd.Flags().String("meminterval", "100ms", "interval to poll memory usage (requires --memstats), valid time units are \"ns\", \"us\" (or \"µs\"), \"ms\", \"s\", \"m\", \"h\".")
- *hugoBuilder
+ serverCmd.RunE = server
- 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
- }
-
- watchGroups := helpers.ExtractAndGroupRootPaths(watchDirs)
-
- for _, group := range watchGroups {
- c.r.Printf("Watching for changes in %s\n", group)
- }
- watcher, err := c.newWatcher(c.r.poll, watchDirs...)
- if err != nil {
- return err
- }
-
- defer watcher.Close()
-
- }
-
- err := func() error {
- defer c.r.timeTrack(time.Now(), "Built")
- return c.build()
- }()
+func server(cmd *cobra.Command, args []string) error {
+ cfg, err := InitializeConfig(serverCmd)
if err != nil {
return 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 site.
-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 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.`
- 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 site in a browser after server startup")
- cmd.Flags().BoolVar(&c.renderStaticToDisk, "renderStaticToDisk", false, "serve static files from disk and dynamic files from memory")
- cmd.Flags().BoolVar(&c.disableFastRender, "disableFastRender", false, "enables full re-renders on changes")
- cmd.Flags().BoolVar(&c.disableBrowserError, "disableBrowserError", false, "do not show build errors in the browser")
-
- 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$`),
- }
-
- c.changeDetector.PrepareNew()
-
- }
-
- err := c.loadConfig(cd, true)
+ c, err := newCommandeer(cfg)
if err != nil {
return err
}
- return nil
-}
-
-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.LanguagesDefaultFirst {
- 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
- }
- baseURL, err := urls.NewBaseURLFromString(baseURLStr)
- if err != nil {
- 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
+ if cmd.Flags().Changed("disableLiveReload") {
+ c.Set("disableLiveReload", disableLiveReload)
}
- 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
-}
-
-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
- }
- hostname = u.Hostname()
+ if cmd.Flags().Changed("navigateToChanged") {
+ c.Set("navigateToChanged", navigateToChanged)
}
- // 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
- }
+ if serverWatch {
+ c.Set("watch", true)
}
- c.tlsCertFile = filepath.Join(keyDir, fmt.Sprintf("%s.pem", hostname))
- c.tlsKeyFile = filepath.Join(keyDir, fmt.Sprintf("%s-key.pem", hostname))
+ if c.Cfg.GetBool("watch") {
+ serverWatch = true
+ c.watchConfig()
+ }
- // Check if the certificate already exists and is valid.
- certPEM, err := os.ReadFile(c.tlsCertFile)
+ l, err := net.Listen("tcp", net.JoinHostPort(serverInterface, strconv.Itoa(serverPort)))
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
- }
+ l.Close()
+ } else {
+ if serverCmd.Flags().Changed("port") {
+ // port set explicitly by user -- he/she probably meant it!
+ return newSystemErrorF("Server startup failed: %s", err)
+ }
+ jww.ERROR.Println("port", serverPort, "already in use, attempting to use an available port")
+ sp, err := helpers.FindAvailablePort()
+ if err != nil {
+ return newSystemError("Unable to find alternative port to use:", err)
+ }
+ serverPort = sp.Port
+ }
+
+ c.Set("port", serverPort)
+
+ baseURL, err = fixURL(c.Cfg, baseURL)
+ if err != nil {
+ return err
+ }
+ c.Set("baseURL", baseURL)
+
+ if err := memStats(); err != nil {
+ jww.ERROR.Println("memstats error:", err)
+ }
+
+ // If a Destination is provided via flag write to disk
+ if destination != "" {
+ renderToDisk = true
+ }
+
+ // Hugo writes the output to memory instead of the disk
+ if !renderToDisk {
+ cfg.Fs.Destination = new(afero.MemMapFs)
+ // Rendering to memoryFS, publish to Root regardless of publishDir.
+ c.Set("publishDir", "/")
+ }
+
+ if err := c.build(serverWatch); err != nil {
+ return err
+ }
+
+ for _, s := range Hugo.Sites {
+ s.RegisterMediaTypes()
+ }
+
+ // Watch runs its own server as part of the routine
+ if serverWatch {
+ watchDirs := c.getDirList()
+ baseWatchDir := c.Cfg.GetString("workingDir")
+ for i, dir := range watchDirs {
+ watchDirs[i], _ = helpers.GetRelativePath(dir, baseWatchDir)
+ }
+
+ rootWatchDirs := strings.Join(helpers.UniqueStrings(helpers.ExtractRootPaths(watchDirs)), ",")
+
+ jww.FEEDBACK.Printf("Watching for changes in %s%s{%s}\n", baseWatchDir, helpers.FilePathSeparator, rootWatchDirs)
+ err := c.newWatcher(serverPort)
+
+ if err != nil {
+ return err
}
}
- 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,
- }
-
- if _, err := cert.Verify(opts); err != nil {
- return fmt.Errorf("failed to verify certificate: %v", err.Error())
- }
+ c.serve(serverPort)
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}
- }
+func (c *commandeer) serve(port int) {
+ if renderToDisk {
+ jww.FEEDBACK.Println("Serving pages from " + c.PathSpec().AbsPathify(c.Cfg.GetString("publishDir")))
+ } else {
+ jww.FEEDBACK.Println("Serving pages from memory")
+ }
- currentServerPort = c.serverPorts[i].p + 1
- }
- })
+ httpFs := afero.NewHttpFs(c.Fs.Destination)
+ fs := filesOnlyFs{httpFs.Dir(c.PathSpec().AbsPathify(c.Cfg.GetString("publishDir")))}
+ fileserver := http.FileServer(fs)
- return cerr
+ // We're only interested in the path
+ u, err := url.Parse(c.Cfg.GetString("baseURL"))
+ if err != nil {
+ jww.ERROR.Fatalf("Invalid baseURL: %s", err)
+ }
+ if u.Path == "" || u.Path == "/" {
+ http.Handle("/", fileserver)
+ } else {
+ http.Handle(u.Path, http.StripPrefix(u.Path, fileserver))
+ }
+
+ jww.FEEDBACK.Printf("Web Server is available at %s (bind address %s)\n", u.String(), serverInterface)
+ jww.FEEDBACK.Println("Press Ctrl+C to stop")
+
+ endpoint := net.JoinHostPort(serverInterface, strconv.Itoa(port))
+ err = http.ListenAndServe(endpoint, nil)
+ if err != nil {
+ jww.ERROR.Printf("Error: %s\n", err.Error())
+ os.Exit(1)
+ }
}
// fixURL massages the baseURL into a form needed for serving
// all pages correctly.
-func (c *serverCommand) fixURL(baseURLFromConfig, baseURLFromFlag string, port int) (string, error) {
- certsSet := (c.tlsCertFile != "" && c.tlsKeyFile != "") || c.tlsAuto
+func fixURL(cfg config.Provider, s string) (string, error) {
useLocalhost := false
- baseURL := baseURLFromFlag
- if baseURL == "" {
- baseURL = baseURLFromConfig
+ if s == "" {
+ s = cfg.GetString("baseURL")
useLocalhost = true
}
- if !strings.HasSuffix(baseURL, "/") {
- baseURL = baseURL + "/"
+ if !strings.HasSuffix(s, "/") {
+ s = s + "/"
}
// do an initial parse of the input string
- u, err := url.Parse(baseURL)
+ u, err := url.Parse(s)
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 == "" && baseURL != "/" {
- baseURL = "//" + baseURL
+ if u.Host == "" && s != "/" {
+ s = "//" + s
- u, err = url.Parse(baseURL)
+ u, err = url.Parse(s)
if err != nil {
return "", err
}
}
if useLocalhost {
- if certsSet {
- u.Scheme = "https"
- } else if u.Scheme == "https" {
+ if u.Scheme == "https" {
u.Scheme = "http"
}
u.Host = "localhost"
}
- if c.serverAppend {
+ if serverAppend {
if strings.Contains(u.Host, ":") {
u.Host, _, err = net.SplitHostPort(u.Host)
if err != nil {
- return "", fmt.Errorf("failed to split baseURL hostport: %w", err)
+ return "", fmt.Errorf("Failed to split baseURL hostpost: %s", err)
}
}
- u.Host += fmt.Sprintf(":%d", port)
+ u.Host += fmt.Sprintf(":%d", serverPort)
}
return u.String(), nil
}
-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)
- }
+func memStats() error {
+ memstats := serverCmd.Flags().Lookup("memstats").Value.String()
+ if memstats != "" {
+ interval, err := time.ParseDuration(serverCmd.Flags().Lookup("meminterval").Value.String())
+ if err != nil {
+ interval, _ = time.ParseDuration("100ms")
+ }
- 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)
+ fileMemStats, err := os.Create(memstats)
if err != nil {
return err
}
- // We need the server to share the same logger as the Hugo build (for error counts etc.)
- c.r.logger = h.Log
+ fileMemStats.WriteString("# Time\tHeapSys\tHeapAlloc\tHeapIdle\tHeapReleased\n")
- if isMultihost {
- for _, l := range conf.configs.ConfigLangs() {
- baseURLs = append(baseURLs, l.BaseURL())
- roots = append(roots, l.Language().Lang)
- }
- } else {
- l := conf.configs.GetFirstLanguageConfig()
- baseURLs = []urls.BaseURL{l.BaseURL()}
- roots = []string{""}
- }
+ go func() {
+ var stats runtime.MemStats
- return nil
- })
- if err != nil {
- return err
- }
+ start := time.Now().UnixNano()
- // 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 {
- srv := srv
- 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
-}
-
-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
-
- 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)
- }
+ 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)
} else {
- c.r.logger.Errorln(err)
+ break
}
-
- 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
+ }()
}
-
- _, err := c.doWithPublishDirs(syncFn)
- return 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 cleanErrorLog(content string) string {
- content = strings.ReplaceAll(content, "\n", " ")
- 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, ".")
+ return nil
}
diff --git a/commands/server_test.go b/commands/server_test.go
new file mode 100644
index 000000000..3f1518aaa
--- /dev/null
+++ b/commands/server_test.go
@@ -0,0 +1,58 @@
+// 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 (
+ "testing"
+
+ "github.com/spf13/viper"
+)
+
+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 i, test := range tests {
+ v := viper.New()
+ baseURL = test.CLIBaseURL
+ v.Set("baseURL", test.CfgBaseURL)
+ serverAppend = test.AppendPort
+ serverPort = test.Port
+ result, err := fixURL(v, baseURL)
+ if err != nil {
+ t.Errorf("Test #%d %s: unexpected error %s", i, test.TestName, err)
+ }
+ if result != test.Result {
+ t.Errorf("Test #%d %s: expected %q, got %q", i, test.TestName, test.Result, result)
+ }
+ }
+}
diff --git a/commands/undraft.go b/commands/undraft.go
new file mode 100644
index 000000000..8d4bffb93
--- /dev/null
+++ b/commands/undraft.go
@@ -0,0 +1,155 @@
+// 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 (
+ "bytes"
+ "errors"
+ "os"
+ "time"
+
+ "github.com/gohugoio/hugo/parser"
+ "github.com/spf13/cobra"
+)
+
+var undraftCmd = &cobra.Command{
+ Use: "undraft path/to/content",
+ Short: "Undraft resets the content's draft status",
+ Long: `Undraft resets the content's draft status
+and updates the date to the current date and time.
+If the content's draft status is 'False', nothing is done.`,
+ RunE: Undraft,
+}
+
+// Undraft publishes the specified content by setting its draft status
+// to false and setting its publish date to now. If the specified content is
+// not a draft, it will log an error.
+func Undraft(cmd *cobra.Command, args []string) error {
+ cfg, err := InitializeConfig()
+
+ if err != nil {
+ return err
+ }
+
+ if len(args) < 1 {
+ return newUserError("a piece of content needs to be specified")
+ }
+
+ location := args[0]
+ // open the file
+ f, err := cfg.Fs.Source.Open(location)
+ if err != nil {
+ return err
+ }
+
+ // get the page from file
+ p, err := parser.ReadFrom(f)
+ f.Close()
+ if err != nil {
+ return err
+ }
+
+ w, err := undraftContent(p)
+ if err != nil {
+ return newSystemErrorF("an error occurred while undrafting %q: %s", location, err)
+ }
+
+ f, err = cfg.Fs.Source.OpenFile(location, os.O_WRONLY|os.O_TRUNC, 0644)
+ if err != nil {
+ return newSystemErrorF("%q not be undrafted due to error opening file to save changes: %q\n", location, err)
+ }
+ defer f.Close()
+ _, err = w.WriteTo(f)
+ if err != nil {
+ return newSystemErrorF("%q not be undrafted due to save error: %q\n", location, err)
+ }
+ return nil
+}
+
+// undraftContent: if the content is a draft, change its draft status to
+// 'false' and set the date to time.Now(). If the draft status is already
+// 'false', don't do anything.
+func undraftContent(p parser.Page) (bytes.Buffer, error) {
+ var buff bytes.Buffer
+ // get the metadata; easiest way to see if it's a draft
+ meta, err := p.Metadata()
+ if err != nil {
+ return buff, err
+ }
+ // since the metadata was obtainable, we can also get the key/value separator for
+ // Front Matter
+ fm := p.FrontMatter()
+ if fm == nil {
+ return buff, errors.New("Front Matter was found, nothing was finalized")
+ }
+
+ var isDraft, gotDate bool
+ var date string
+L:
+ for k, v := range meta.(map[string]interface{}) {
+ switch k {
+ case "draft":
+ if !v.(bool) {
+ return buff, errors.New("not a Draft: nothing was done")
+ }
+ isDraft = true
+ if gotDate {
+ break L
+ }
+ case "date":
+ date = v.(string) // capture the value to make replacement easier
+ gotDate = true
+ if isDraft {
+ break L
+ }
+ }
+ }
+
+ // if draft wasn't found in FrontMatter, it isn't a draft.
+ if !isDraft {
+ return buff, errors.New("not a Draft: nothing was done")
+ }
+
+ // get the front matter as bytes and split it into lines
+ var lineEnding []byte
+ fmLines := bytes.Split(fm, []byte("\n"))
+ if len(fmLines) == 1 { // if the result is only 1 element, try to split on dos line endings
+ fmLines = bytes.Split(fm, []byte("\r\n"))
+ if len(fmLines) == 1 {
+ return buff, errors.New("unable to split FrontMatter into lines")
+ }
+ lineEnding = append(lineEnding, []byte("\r\n")...)
+ } else {
+ lineEnding = append(lineEnding, []byte("\n")...)
+ }
+
+ // Write the front matter lines to the buffer, replacing as necessary
+ for _, v := range fmLines {
+ pos := bytes.Index(v, []byte("draft"))
+ if pos != -1 {
+ continue
+ }
+ pos = bytes.Index(v, []byte("date"))
+ if pos != -1 { // if date field wasn't found, add it
+ v = bytes.Replace(v, []byte(date), []byte(time.Now().Format(time.RFC3339)), 1)
+ }
+ buff.Write(v)
+ buff.Write(lineEnding)
+ }
+
+ // append the actual content
+ buff.Write(p.Content())
+
+ return buff, nil
+}
diff --git a/commands/undraft_test.go b/commands/undraft_test.go
new file mode 100644
index 000000000..259e3479b
--- /dev/null
+++ b/commands/undraft_test.go
@@ -0,0 +1,87 @@
+// 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
+
+// TODO Support Mac Encoding (\r)
+
+import (
+ "bytes"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/gohugoio/hugo/parser"
+)
+
+var (
+ jsonFM = "{\n \"date\": \"12-04-06\",\n \"title\": \"test json\"\n}"
+ jsonDraftFM = "{\n \"draft\": true,\n \"date\": \"12-04-06\",\n \"title\":\"test json\"\n}"
+ tomlFM = "+++\n date= \"12-04-06\"\n title= \"test toml\"\n+++"
+ tomlDraftFM = "+++\n draft= true\n date= \"12-04-06\"\n title=\"test toml\"\n+++"
+ yamlFM = "---\n date: \"12-04-06\"\n title: \"test yaml\"\n---"
+ yamlDraftFM = "---\n draft: true\n date: \"12-04-06\"\n title: \"test yaml\"\n---"
+ yamlYesDraftFM = "---\n draft: yes\n date: \"12-04-06\"\n title: \"test yaml\"\n---"
+)
+
+func TestUndraftContent(t *testing.T) {
+ tests := []struct {
+ fm string
+ expectedErr string
+ }{
+ {jsonFM, "not a Draft: nothing was done"},
+ {jsonDraftFM, ""},
+ {tomlFM, "not a Draft: nothing was done"},
+ {tomlDraftFM, ""},
+ {yamlFM, "not a Draft: nothing was done"},
+ {yamlDraftFM, ""},
+ {yamlYesDraftFM, ""},
+ }
+
+ for i, test := range tests {
+ r := bytes.NewReader([]byte(test.fm))
+ p, _ := parser.ReadFrom(r)
+ res, err := undraftContent(p)
+ if test.expectedErr != "" {
+ if err == nil {
+ t.Errorf("[%d] Expected error, got none", i)
+ continue
+ }
+ if err.Error() != test.expectedErr {
+ t.Errorf("[%d] Expected %q, got %q", i, test.expectedErr, err)
+ continue
+ }
+ } else {
+ r = bytes.NewReader(res.Bytes())
+ p, _ = parser.ReadFrom(r)
+ meta, err := p.Metadata()
+ if err != nil {
+ t.Errorf("[%d] unexpected error %q", i, err)
+ continue
+ }
+ for k, v := range meta.(map[string]interface{}) {
+ if k == "draft" {
+ if v.(bool) {
+ t.Errorf("[%d] Expected %q to be \"false\", got \"true\"", i, k)
+ continue
+ }
+ }
+ if k == "date" {
+ if !strings.HasPrefix(v.(string), time.Now().Format("2006-01-02")) {
+ t.Errorf("[%d] Expected %v to start with %v", i, v.(string), time.Now().Format("2006-01-02"))
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/commands/version.go b/commands/version.go
new file mode 100644
index 000000000..5cd398b2b
--- /dev/null
+++ b/commands/version.go
@@ -0,0 +1,80 @@
+// 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 (
+ "os"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "time"
+
+ "github.com/gohugoio/hugo/helpers"
+ "github.com/gohugoio/hugo/hugolib"
+ "github.com/kardianos/osext"
+ "github.com/spf13/cobra"
+ jww "github.com/spf13/jwalterweatherman"
+)
+
+var versionCmd = &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() {
+ if hugolib.BuildDate == "" {
+ setBuildDate() // set the build date from executable's mdate
+ } else {
+ formatBuildDate() // format the compile time
+ }
+ if hugolib.CommitHash == "" {
+ jww.FEEDBACK.Printf("Hugo Static Site Generator v%s %s/%s BuildDate: %s\n", helpers.CurrentHugoVersion, runtime.GOOS, runtime.GOARCH, hugolib.BuildDate)
+ } else {
+ jww.FEEDBACK.Printf("Hugo Static Site Generator v%s-%s %s/%s BuildDate: %s\n", helpers.CurrentHugoVersion, strings.ToUpper(hugolib.CommitHash), runtime.GOOS, runtime.GOARCH, hugolib.BuildDate)
+ }
+}
+
+// setBuildDate checks the ModTime of the Hugo executable and returns it as a
+// formatted string. This assumes that the executable name is Hugo, if it does
+// not exist, an empty string will be returned. This is only called if the
+// hugolib.BuildDate wasn't set during compile time.
+//
+// osext is used for cross-platform.
+func setBuildDate() {
+ fname, _ := osext.Executable()
+ dir, err := filepath.Abs(filepath.Dir(fname))
+ if err != nil {
+ jww.ERROR.Println(err)
+ return
+ }
+ fi, err := os.Lstat(filepath.Join(dir, filepath.Base(fname)))
+ if err != nil {
+ jww.ERROR.Println(err)
+ return
+ }
+ t := fi.ModTime()
+ hugolib.BuildDate = t.Format(time.RFC3339)
+}
+
+// formatBuildDate formats the hugolib.BuildDate according to the value in
+// .Params.DateFormat, if it's set.
+func formatBuildDate() {
+ t, _ := time.Parse("2006-01-02T15:04:05-0700", hugolib.BuildDate)
+ hugolib.BuildDate = t.Format(time.RFC3339)
+}
diff --git a/common/collections/append.go b/common/collections/append.go
deleted file mode 100644
index db9db8bf3..000000000
--- a/common/collections/append.go
+++ /dev/null
@@ -1,152 +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 collections
-
-import (
- "fmt"
- "reflect"
-)
-
-// 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 any, from ...any) (any, error) {
- if len(from) == 0 {
- return to, nil
- }
- tov, toIsNil := 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
- }
-
- // If we get []string []string, we append the from slice to to
- if tot == fromt {
- return reflect.AppendSlice(tov, fromv).Interface(), nil
- } else if !fromt.AssignableTo(tot) {
- // Fall back to a []interface{} slice.
- return appendToInterfaceSliceFromValues(tov, fromv)
- }
-
- }
- }
- }
-
- if toIsNil {
- return Slice(from...), nil
- }
-
- for _, f := range from {
- fv := reflect.ValueOf(f)
- if !fv.IsValid() || !fv.Type().AssignableTo(tot) {
- // Fall back to a []interface{} slice.
- tov, _ := indirect(reflect.ValueOf(to))
- return appendToInterfaceSlice(tov, from...)
- }
- tov = reflect.Append(tov, fv)
- }
-
- return tov.Interface(), nil
-}
-
-func appendToInterfaceSliceFromValues(slice1, slice2 reflect.Value) ([]any, error) {
- var tos []any
-
- for _, slice := range []reflect.Value{slice1, slice2} {
- if !slice.IsValid() {
- tos = append(tos, nil)
- continue
- }
- for i := range slice.Len() {
- tos = append(tos, slice.Index(i).Interface())
- }
- }
-
- return tos, nil
-}
-
-func appendToInterfaceSlice(tov reflect.Value, from ...any) ([]any, error) {
- var tos []any
-
- for i := range tov.Len() {
- tos = append(tos, tov.Index(i).Interface())
- }
-
- tos = append(tos, from...)
-
- 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
deleted file mode 100644
index 62d9015ce..000000000
--- a/common/collections/append_test.go
+++ /dev/null
@@ -1,213 +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 collections
-
-import (
- "html/template"
- "reflect"
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestAppend(t *testing.T) {
- t.Parallel()
- c := qt.New(t)
-
- for i, test := range []struct {
- start any
- addend []any
- expected any
- }{
- {[]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"}},
- []any{&tstSlicer{"c"}},
- tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}, &tstSlicer{"c"}},
- },
- {
- &tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}},
- []any{&tstSlicer{"c"}},
- tstSlicers{
- &tstSlicer{"a"},
- &tstSlicer{"b"},
- &tstSlicer{"c"},
- },
- },
- {
- testSlicerInterfaces{&tstSlicerIn1{"a"}, &tstSlicerIn1{"b"}},
- []any{&tstSlicerIn1{"c"}},
- testSlicerInterfaces{&tstSlicerIn1{"a"}, &tstSlicerIn1{"b"}, &tstSlicerIn1{"c"}},
- },
- // https://github.com/gohugoio/hugo/issues/5361
- {
- []string{"a", "b"},
- []any{tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}}},
- []any{"a", "b", &tstSlicer{"a"}, &tstSlicer{"b"}},
- },
- {
- []string{"a", "b"},
- []any{&tstSlicer{"a"}},
- []any{"a", "b", &tstSlicer{"a"}},
- },
- // Errors
- {"", []any{[]string{"a", "b"}}, false},
- // No string concatenation.
- {
- "ab",
- []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...)
-
- if b, ok := test.expected.(bool); ok && !b {
-
- c.Assert(err, qt.Not(qt.IsNil))
- continue
- }
-
- c.Assert(err, qt.IsNil)
- 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"})
-}
-
-func TestIndirect(t *testing.T) {
- t.Parallel()
- c := qt.New(t)
-
- type testStruct struct {
- Field string
- }
-
- var (
- nilPtr *testStruct
- nilIface interface{} = nil
- nonNilIface interface{} = &testStruct{Field: "hello"}
- )
-
- tests := []struct {
- name string
- input any
- wantKind reflect.Kind
- wantNil bool
- }{
- {
- name: "nil pointer",
- input: nilPtr,
- wantKind: reflect.Ptr,
- wantNil: true,
- },
- {
- name: "nil interface",
- input: nilIface,
- wantKind: reflect.Invalid,
- wantNil: false,
- },
- {
- name: "non-nil pointer to struct",
- input: &testStruct{Field: "abc"},
- wantKind: reflect.Struct,
- wantNil: false,
- },
- {
- name: "non-nil interface holding pointer",
- input: nonNilIface,
- wantKind: reflect.Struct,
- wantNil: false,
- },
- {
- name: "plain value",
- input: testStruct{Field: "xyz"},
- wantKind: reflect.Struct,
- wantNil: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- v := reflect.ValueOf(tt.input)
- got, isNil := indirect(v)
-
- c.Assert(got.Kind(), qt.Equals, tt.wantKind)
- c.Assert(isNil, qt.Equals, tt.wantNil)
- })
- }
-}
diff --git a/common/collections/collections.go b/common/collections/collections.go
deleted file mode 100644
index 0b46abee9..000000000
--- a/common/collections/collections.go
+++ /dev/null
@@ -1,21 +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 collections contains common Hugo functionality related to collection
-// handling.
-package collections
-
-// Grouper defines a very generic way to group items by a given key.
-type Grouper interface {
- Group(key any, items any) (any, error)
-}
diff --git a/common/collections/order.go b/common/collections/order.go
deleted file mode 100644
index 4bdc3b4ac..000000000
--- a/common/collections/order.go
+++ /dev/null
@@ -1,20 +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 collections
-
-type Order interface {
- // Ordinal is a zero-based ordinal that represents the order of an object
- // in a collection.
- Ordinal() int
-}
diff --git a/common/collections/slice.go b/common/collections/slice.go
deleted file mode 100644
index 731f489f9..000000000
--- a/common/collections/slice.go
+++ /dev/null
@@ -1,95 +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 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 any) (any, error)
-}
-
-// Slice returns a slice of all passed arguments.
-func Slice(args ...any) any {
- if len(args) == 0 {
- return args
- }
-
- first := args[0]
- firstType := reflect.TypeOf(first)
-
- if firstType == nil {
- return args
- }
-
- if g, ok := first.(Slicer); ok {
- v, err := g.Slice(args)
- if err == nil {
- return v
- }
-
- // If Slice fails, the items are not of the same type and
- // []interface{} is the best we can do.
- return args
- }
-
- if len(args) > 1 {
- // This can be a mix of types.
- for i := 1; i < len(args); i++ {
- if firstType != reflect.TypeOf(args[i]) {
- // []interface{} is the best we can do
- return args
- }
- }
- }
-
- slice := reflect.MakeSlice(reflect.SliceOf(firstType), len(args), len(args))
- for i, arg := range args {
- slice.Index(i).Set(reflect.ValueOf(arg))
- }
- return slice.Interface()
-}
-
-// StringSliceToInterfaceSlice converts ss to []interface{}.
-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
deleted file mode 100644
index 4008a5e6c..000000000
--- a/common/collections/slice_test.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 collections
-
-import (
- "errors"
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-var (
- _ Slicer = (*tstSlicer)(nil)
- _ Slicer = (*tstSlicerIn1)(nil)
- _ Slicer = (*tstSlicerIn2)(nil)
- _ testSlicerInterface = (*tstSlicerIn1)(nil)
- _ testSlicerInterface = (*tstSlicerIn1)(nil)
-)
-
-type testSlicerInterface interface {
- Name() string
-}
-
-type testSlicerInterfaces []testSlicerInterface
-
-type tstSlicerIn1 struct {
- TheName string
-}
-
-type tstSlicerIn2 struct {
- TheName string
-}
-
-type tstSlicer struct {
- TheName string
-}
-
-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) {
- case testSlicerInterface:
- result[i] = vv
- default:
- return nil, errors.New("invalid type")
- }
- }
- return result, nil
-}
-
-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) {
- case testSlicerInterface:
- result[i] = vv
- default:
- return nil, errors.New("invalid type")
- }
- }
- return result, nil
-}
-
-func (p *tstSlicerIn1) Name() string {
- return p.TheName
-}
-
-func (p *tstSlicerIn2) Name() string {
- return p.TheName
-}
-
-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) {
- case *tstSlicer:
- result[i] = vv
- default:
- return nil, errors.New("invalid type")
- }
- }
- return result, nil
-}
-
-type tstSlicers []*tstSlicer
-
-func TestSlice(t *testing.T) {
- t.Parallel()
- c := qt.New(t)
-
- for i, test := range []struct {
- args []any
- expected any
- }{
- {[]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)
-
- result := Slice(test.args...)
-
- 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
deleted file mode 100644
index ff0db2f02..000000000
--- a/common/collections/stack.go
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package collections
-
-import "slices"
-
-import "sync"
-
-// Stack is a simple LIFO stack that is safe for concurrent use.
-type Stack[T any] struct {
- items []T
- zero T
- mu sync.RWMutex
-}
-
-func NewStack[T any]() *Stack[T] {
- return &Stack[T]{}
-}
-
-func (s *Stack[T]) Push(item T) {
- s.mu.Lock()
- defer s.mu.Unlock()
- s.items = append(s.items, item)
-}
-
-func (s *Stack[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 *Stack[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 *Stack[T]) Len() int {
- s.mu.RLock()
- defer s.mu.RUnlock()
- return len(s.items)
-}
-
-func (s *Stack[T]) Drain() []T {
- s.mu.Lock()
- defer s.mu.Unlock()
- items := s.items
- s.items = nil
- return items
-}
-
-func (s *Stack[T]) DrainMatching(predicate func(T) bool) []T {
- s.mu.Lock()
- defer s.mu.Unlock()
- var items []T
- for i := len(s.items) - 1; i >= 0; i-- {
- if predicate(s.items[i]) {
- items = append(items, s.items[i])
- s.items = slices.Delete(s.items, i, i+1)
- }
- }
- return items
-}
diff --git a/common/collections/stack_test.go b/common/collections/stack_test.go
deleted file mode 100644
index 965d4dbc8..000000000
--- a/common/collections/stack_test.go
+++ /dev/null
@@ -1,77 +0,0 @@
-package collections
-
-import (
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestNewStack(t *testing.T) {
- t.Parallel()
- c := qt.New(t)
-
- s := NewStack[int]()
-
- c.Assert(s, qt.IsNotNil)
-}
-
-func TestStackBasic(t *testing.T) {
- t.Parallel()
- c := qt.New(t)
-
- s := NewStack[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 := NewStack[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 := NewStack[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
deleted file mode 100644
index c7bbaa541..000000000
--- a/common/constants/constants.go
+++ /dev/null
@@ -1,49 +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 constants
-
-// Error/Warning IDs.
-// Do not change these values.
-const (
- // 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
deleted file mode 100644
index 041a62a01..000000000
--- a/common/docs.go
+++ /dev/null
@@ -1,2 +0,0 @@
-// Package common provides common helper functionality for Hugo.
-package common
diff --git a/common/hashing/hashing.go b/common/hashing/hashing.go
deleted file mode 100644
index e45356758..000000000
--- a/common/hashing/hashing.go
+++ /dev/null
@@ -1,194 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package hashing provides common hashing utilities.
-package hashing
-
-import (
- "crypto/md5"
- "encoding/hex"
- "io"
- "strconv"
- "sync"
-
- "github.com/cespare/xxhash/v2"
- "github.com/gohugoio/hashstructure"
- "github.com/gohugoio/hugo/identity"
-)
-
-// XXHashFromReader calculates the xxHash for the given reader.
-func XXHashFromReader(r io.Reader) (uint64, int64, error) {
- h := getXxHashReadFrom()
- defer putXxHashReadFrom(h)
-
- size, err := io.Copy(h, r)
- if err != nil {
- return 0, 0, err
- }
- return h.Sum64(), size, nil
-}
-
-// 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 string
-// and returns the hash as a hex encoded string.
-func XxHashFromStringHexEncoded(f string) string {
- h := xxhash.New()
- h.WriteString(f)
- hash := h.Sum(nil)
- return hex.EncodeToString(hash)
-}
-
-// MD5FromStringHexEncoded returns the MD5 hash of the given string.
-func MD5FromStringHexEncoded(f string) string {
- h := md5.New()
- h.Write([]byte(f))
- return hex.EncodeToString(h.Sum(nil))
-}
-
-// HashString returns a hash from the given elements.
-// It will panic if the hash cannot be calculated.
-// Note that this hash should be used primarily for identity, not for change detection as
-// it in the more complex values (e.g. Page) will not hash the full content.
-func HashString(vs ...any) string {
- hash := HashUint64(vs...)
- return strconv.FormatUint(hash, 10)
-}
-
-// 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
deleted file mode 100644
index 105b6d8b5..000000000
--- a/common/hashing/hashing_test.go
+++ /dev/null
@@ -1,157 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package hashing
-
-import (
- "fmt"
- "math"
- "strings"
- "sync"
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestXxHashFromReader(t *testing.T) {
- c := qt.New(t)
- s := "Hello World"
- r := strings.NewReader(s)
- got, size, err := XXHashFromReader(r)
- c.Assert(err, qt.IsNil)
- c.Assert(size, qt.Equals, int64(len(s)))
- c.Assert(got, qt.Equals, uint64(7148569436472236994))
-}
-
-func TestXxHashFromReaderPara(t *testing.T) {
- c := qt.New(t)
-
- var wg sync.WaitGroup
- for i := range 10 {
- i := i
- wg.Add(1)
- go func() {
- defer wg.Done()
- 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")
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- XXHashFromReader(r)
- r.Seek(0, 0)
- }
-}
-
-func BenchmarkXXHashFromString(b *testing.B) {
- s := "Hello World"
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- XXHashFromString(s)
- }
-}
-
-func BenchmarkXXHashFromStringHexEncoded(b *testing.B) {
- s := "The quick brown fox jumps over the lazy dog"
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- XxHashFromStringHexEncoded(s)
- }
-}
-
-func TestHashString(t *testing.T) {
- c := qt.New(t)
-
- c.Assert(HashString("a", "b"), qt.Equals, "3176555414984061461")
- c.Assert(HashString("ab"), qt.Equals, "7347350983217793633")
-
- var vals []any = []any{"a", "b", tstKeyer{"c"}}
-
- c.Assert(HashString(vals...), qt.Equals, "4438730547989914315")
- c.Assert(vals[2], qt.Equals, tstKeyer{"c"})
-}
-
-type tstKeyer struct {
- key string
-}
-
-func (t tstKeyer) Key() string {
- return t.key
-}
-
-func (t tstKeyer) String() string {
- return "key: " + t.key
-}
-
-func BenchmarkHashString(b *testing.B) {
- word := " hello "
-
- var tests []string
-
- for i := 1; i <= 5; i++ {
- sentence := strings.Repeat(word, int(math.Pow(4, float64(i))))
- tests = append(tests, sentence)
- }
-
- b.ResetTimer()
-
- for _, test := range tests {
- b.Run(fmt.Sprintf("n%d", len(test)), func(b *testing.B) {
- for i := 0; i < b.N; i++ {
- HashString(test)
- }
- })
- }
-}
-
-func BenchmarkHashMap(b *testing.B) {
- m := map[string]any{}
- for i := range 1000 {
- m[fmt.Sprintf("key%d", i)] = i
- }
-
- b.ResetTimer()
-
- for i := 0; i < b.N; i++ {
- HashString(m)
- }
-}
diff --git a/common/hcontext/context.go b/common/hcontext/context.go
deleted file mode 100644
index 9524ef284..000000000
--- a/common/hcontext/context.go
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package hcontext
-
-import "context"
-
-// ContextDispatcher is a generic interface for setting and getting values from a context.
-type ContextDispatcher[T any] interface {
- Set(ctx context.Context, value T) context.Context
- Get(ctx context.Context) T
-}
-
-// NewContextDispatcher creates a new ContextDispatcher with the given key.
-func NewContextDispatcher[T any, R comparable](key R) ContextDispatcher[T] {
- return keyInContext[T, R]{
- id: key,
- }
-}
-
-type keyInContext[T any, R comparable] struct {
- zero T
- id R
-}
-
-func (f keyInContext[T, R]) Get(ctx context.Context) T {
- v := ctx.Value(f.id)
- if v == nil {
- return f.zero
- }
- return v.(T)
-}
-
-func (f keyInContext[T, R]) Set(ctx context.Context, value T) context.Context {
- return context.WithValue(ctx, f.id, value)
-}
diff --git a/common/herrors/error_locator.go b/common/herrors/error_locator.go
deleted file mode 100644
index acaebb4bc..000000000
--- a/common/herrors/error_locator.go
+++ /dev/null
@@ -1,170 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package herrors contains common Hugo errors and error related utilities.
-package herrors
-
-import (
- "io"
- "path/filepath"
- "strings"
-
- "github.com/gohugoio/hugo/common/text"
-)
-
-// LineMatcher contains the elements used to match an error to a line
-type LineMatcher struct {
- Position text.Position
- Error error
-
- LineNumber int
- Offset int
- Line string
-}
-
-// LineMatcherFn is used to match a line with an error.
-// 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) 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
-}
-
-// 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
-
- // The position of the error in the Lines above. 0 based.
- LinesPos int
-
- // 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
-}
-
-func chromaLexerFromType(fileType string) string {
- switch fileType {
- case "html", "htm":
- return "go-html-template"
- }
- return fileType
-}
-
-func extNoDelimiter(filename string) string {
- return strings.TrimPrefix(filepath.Ext(filename), ".")
-}
-
-func chromaLexerFromFilename(filename string) string {
- if strings.Contains(filename, "layouts") {
- return "go-html-template"
- }
-
- ext := extNoDelimiter(filename)
- return chromaLexerFromType(ext)
-}
-
-func locateErrorInString(src string, matcher LineMatcherFn) *ErrorContext {
- return locateError(strings.NewReader(src), &fileError{}, matcher)
-}
-
-func locateError(r io.Reader, le FileError, matches LineMatcherFn) *ErrorContext {
- if le == nil {
- panic("must provide an error")
- }
-
- ectx := &ErrorContext{LinesPos: -1, Position: text.Position{Offset: -1}}
-
- b, err := io.ReadAll(r)
- if err != nil {
- return ectx
- }
-
- lines := strings.Split(string(b), "\n")
-
- lineNo := 0
- posBytes := 0
-
- for li, line := range lines {
- lineNo = li + 1
- m := LineMatcher{
- Position: le.Position(),
- Error: le,
- LineNumber: lineNo,
- Offset: posBytes,
- Line: line,
- }
- v := matches(m)
- if ectx.LinesPos == -1 && v != -1 {
- ectx.Position.LineNumber = lineNo
- ectx.Position.ColumnNumber = v
- break
- }
-
- posBytes += len(line)
- }
-
- if ectx.Position.LineNumber > 0 {
- low := max(ectx.Position.LineNumber-3, 0)
-
- if ectx.Position.LineNumber > 2 {
- ectx.LinesPos = 2
- } else {
- ectx.LinesPos = ectx.Position.LineNumber - 1
- }
-
- high := min(ectx.Position.LineNumber+2, len(lines))
-
- ectx.Lines = lines[low:high]
-
- }
-
- return ectx
-}
diff --git a/common/herrors/error_locator_test.go b/common/herrors/error_locator_test.go
deleted file mode 100644
index 62f15213d..000000000
--- a/common/herrors/error_locator_test.go
+++ /dev/null
@@ -1,152 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package herrors contains common Hugo errors and error related utilities.
-package herrors
-
-import (
- "strings"
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestErrorLocator(t *testing.T) {
- c := qt.New(t)
-
- lineMatcher := func(m LineMatcher) int {
- if strings.Contains(m.Line, "THEONE") {
- return 1
- }
- return -1
- }
-
- lines := `LINE 1
-LINE 2
-LINE 3
-LINE 4
-This is THEONE
-LINE 6
-LINE 7
-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"})
-
- c.Assert(pos.LineNumber, qt.Equals, 5)
- c.Assert(location.LinesPos, qt.Equals, 2)
-
- 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)
- 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 = 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 = locate(`L1
-This THEONE
-`, lineMatcher)
- c.Assert(location.Lines, qt.DeepEquals, []string{"L1", "This THEONE", ""})
- c.Assert(location.LinesPos, qt.Equals, 1)
-
- location = locate(`L1
-L2
-This THEONE
-`, lineMatcher)
- c.Assert(location.Lines, qt.DeepEquals, []string{"L1", "L2", "This THEONE", ""})
- c.Assert(location.LinesPos, qt.Equals, 2)
-
- location = locateErrorInString("NO MATCH", lineMatcher)
- 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) int {
- if m.LineNumber == 6 {
- return 1
- }
- return -1
- }
-
- location = locateErrorInString(`A
-B
-C
-D
-E
-F
-G
-H
-I
-J`, lineMatcher)
- pos = location.Position
-
- c.Assert(location.Lines, qt.DeepEquals, []string{"D", "E", "F", "G", "H"})
- c.Assert(pos.LineNumber, qt.Equals, 6)
- c.Assert(location.LinesPos, qt.Equals, 2)
-
- // Test match EOF
- lineMatcher = func(m LineMatcher) int {
- if m.LineNumber == 4 {
- return 1
- }
- return -1
- }
-
- location = locateErrorInString(`A
-B
-C
-`, lineMatcher)
-
- pos = location.Position
-
- c.Assert(location.Lines, qt.DeepEquals, []string{"B", "C", ""})
- c.Assert(pos.LineNumber, qt.Equals, 4)
- c.Assert(location.LinesPos, qt.Equals, 2)
-
- offsetMatcher := func(m LineMatcher) int {
- if m.Offset == 1 {
- return 1
- }
- return -1
- }
-
- location = locateErrorInString(`A
-B
-C
-D
-E`, offsetMatcher)
-
- pos = location.Position
-
- c.Assert(location.Lines, qt.DeepEquals, []string{"A", "B", "C", "D"})
- 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
deleted file mode 100644
index c7ee90dd0..000000000
--- a/common/herrors/errors.go
+++ /dev/null
@@ -1,187 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package herrors contains common Hugo errors and error related utilities.
-package herrors
-
-import (
- "errors"
- "fmt"
- "io"
- "os"
- "regexp"
- "runtime"
- "runtime/debug"
- "strings"
- "time"
-)
-
-// 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)
-}
-
-// ErrorSender is a, typically, non-blocking error handler.
-type ErrorSender interface {
- SendError(err error)
-}
-
-// 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)
- args = append(args, "stacktrace from panic: \n"+string(debug.Stack()), "\n")
- fmt.Println(args...)
- }
-}
-
-// IsTimeoutError returns true if the given error is or contains a TimeoutError.
-func IsTimeoutError(err error) bool {
- return errors.Is(err, &TimeoutError{})
-}
-
-type TimeoutError struct {
- Duration time.Duration
-}
-
-func (e *TimeoutError) Error() string {
- return fmt.Sprintf("timeout after %s", e.Duration)
-}
-
-func (e *TimeoutError) Is(target error) bool {
- _, ok := target.(*TimeoutError)
- return ok
-}
-
-// errMessage wraps an error with a message.
-type errMessage struct {
- msg string
- err error
-}
-
-func (e *errMessage) Error() string {
- return e.msg
-}
-
-func (e *errMessage) Unwrap() error {
- return e.err
-}
-
-// IsFeatureNotAvailableError returns true if the given error is or contains a FeatureNotAvailableError.
-func IsFeatureNotAvailableError(err error) bool {
- return errors.Is(err, &FeatureNotAvailableError{})
-}
-
-// 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 = &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) {
- if err != nil {
- 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
deleted file mode 100644
index 2f53a1e89..000000000
--- a/common/herrors/errors_test.go
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package 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
deleted file mode 100644
index 38b198656..000000000
--- a/common/herrors/file_error.go
+++ /dev/null
@@ -1,430 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable 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
-// limitations under the License.
-
-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/pelletier/go-toml/v2"
- "github.com/spf13/afero"
- "github.com/tdewolff/parse/v2"
-)
-
-// 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
-
- // 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
-}
-
-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 {
- fe.position.LineNumber = ectx.Position.LineNumber
- }
-
- if ectx.Position.ColumnNumber > 0 {
- fe.position.ColumnNumber = ectx.Position.ColumnNumber
- }
-
- return fe
-}
-
-type fileError struct {
- 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) Error() string {
- return fmt.Sprintf("%s: %s", e.position, e.causeString())
-}
-
-func (e *fileError) causeString() string {
- if e.cause == nil {
- return ""
- }
- 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 (e *fileError) Unwrap() error {
- return e.cause
-}
-
-// 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}
-}
-
-// 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()
- return NewFileErrorFromName(err, realFilename).UpdateContent(f, linematcher)
-}
-
-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 {
- cause, ok := err.(unwrapper)
- if !ok {
- break
- }
- err = cause.Unwrap()
- }
- return err
-}
-
-// Unwrap returns the underlying error or itself if it does not implement Unwrap.
-func Unwrap(err error) error {
- if u := errors.Unwrap(err); u != nil {
- return u
- }
- return 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.
- for _, handle := range lineNumberExtractors {
- lno, col := handle(err)
- if lno > 0 {
- pos.ColumnNumber = col
- pos.LineNumber = lno
- break
- }
- }
-
- if fileType == "" && pos.Filename != "" {
- _, fileType = paths.FileAndExtNoDelimiter(pos.Filename)
- }
-
- return fileType, pos
-}
-
-// 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)
- }
- }
- return nil
-}
-
-// UnwrapFileErrors tries to unwrap all FileError.
-func UnwrapFileErrors(err error) []FileError {
- var errs []FileError
- for err != nil {
- if v, ok := err.(FileError); ok {
- errs = append(errs, v)
- }
- err = errors.Unwrap(err)
- }
- return errs
-}
-
-// UnwrapFileErrorsWithErrorContext tries to unwrap all FileError in err that has an ErrorContext.
-func UnwrapFileErrorsWithErrorContext(err error) []FileError {
- var errs []FileError
- for err != nil {
- if v, ok := err.(FileError); ok && v.ErrorContext() != nil {
- errs = append(errs, v)
- }
- err = errors.Unwrap(err)
- }
- return errs
-}
-
-func extractOffsetAndType(e error) (int, string) {
- switch v := e.(type) {
- case *json.UnmarshalTypeError:
- return int(v.Offset), "json"
- case *json.SyntaxError:
- return int(v.Offset), "json"
- default:
- 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
deleted file mode 100644
index 7aca08405..000000000
--- a/common/herrors/file_error_test.go
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package herrors
-
-import (
- "errors"
- "fmt"
- "strings"
- "testing"
-
- "github.com/gohugoio/hugo/common/text"
-
- qt "github.com/frankban/quicktest"
-)
-
-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`)
-
- lines := ""
- for i := 1; i <= 100; i++ {
- lines += 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), 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)
-
- for i, test := range []struct {
- in error
- offset int
- lineNumber int
- columnNumber int
- }{
- {errors.New("no line number for you"), 0, 1, 1},
- {errors.New(`template: _default/single.html:4:15: executing "_default/single.html" at <.Titles>: can't evaluate field Titles in type *hugolib.PageOutput`), 0, 4, 15},
- {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 := NewFileErrorFromName(test.in, "test.txt")
-
- errMsg := qt.Commentf("[%d][%T]", i, got)
-
- pos := got.Position()
- c.Assert(pos.LineNumber, qt.Equals, test.lineNumber, errMsg)
- c.Assert(pos.ColumnNumber, qt.Equals, test.columnNumber, errMsg)
- 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
deleted file mode 100644
index f70a2691f..000000000
--- a/common/herrors/line_number_extractors.go
+++ /dev/null
@@ -1,63 +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 herrors
-
-import (
- "regexp"
- "strconv"
-)
-
-var lineNumberExtractors = []lineNumberExtractor{
- // Template/shortcode parse errors
- newLineNumberErrHandlerFromRegexp(`:(\d+):(\d*):`),
- newLineNumberErrHandlerFromRegexp(`:(\d+):`),
-
- // YAML parse errors
- newLineNumberErrHandlerFromRegexp(`line (\d+):`),
-
- // i18n bundle errors
- newLineNumberErrHandlerFromRegexp(`\((\d+),\s(\d*)`),
-}
-
-type lineNumberExtractor func(e error) (int, int)
-
-func newLineNumberErrHandlerFromRegexp(expression string) lineNumberExtractor {
- re := regexp.MustCompile(expression)
- return extractLineNo(re)
-}
-
-func extractLineNo(re *regexp.Regexp) lineNumberExtractor {
- return func(e error) (int, int) {
- if e == nil {
- panic("no error")
- }
- col := 1
- s := e.Error()
- m := re.FindStringSubmatch(s)
- if len(m) >= 2 {
- lno, _ := strconv.Atoi(m[1])
- if len(m) > 2 {
- col, _ = strconv.Atoi(m[2])
- }
-
- if col <= 0 {
- col = 1
- }
-
- return lno, col
- }
-
- return 0, col
- }
-}
diff --git a/common/hexec/exec.go b/common/hexec/exec.go
deleted file mode 100644
index c3a6ebf57..000000000
--- a/common/hexec/exec.go
+++ /dev/null
@@ -1,389 +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 hexec
-
-import (
- "bytes"
- "context"
- "errors"
- "fmt"
- "io"
- "os"
- "os/exec"
- "path/filepath"
- "regexp"
- "strings"
- "sync"
-
- "github.com/bep/logg"
- "github.com/gohugoio/hugo/common/loggers"
- "github.com/gohugoio/hugo/common/maps"
- "github.com/gohugoio/hugo/config"
- "github.com/gohugoio/hugo/config/security"
-)
-
-var WithDir = func(dir string) func(c *commandeer) {
- return func(c *commandeer) {
- c.dir = dir
- }
-}
-
-var WithContext = func(ctx context.Context) func(c *commandeer) {
- return func(c *commandeer) {
- c.ctx = ctx
- }
-}
-
-var WithStdout = func(w io.Writer) func(c *commandeer) {
- return func(c *commandeer) {
- c.stdout = w
- }
-}
-
-var WithStderr = func(w io.Writer) func(c *commandeer) {
- return func(c *commandeer) {
- c.stderr = w
- }
-}
-
-var WithStdin = func(r io.Reader) func(c *commandeer) {
- return func(c *commandeer) {
- c.stdin = r
- }
-}
-
-var WithEnviron = func(env []string) func(c *commandeer) {
- return func(c *commandeer) {
- setOrAppend := func(s string) {
- k1, _ := config.SplitEnvVar(s)
- var found bool
- for i, v := range c.env {
- k2, _ := config.SplitEnvVar(v)
- if k1 == k2 {
- found = true
- c.env[i] = s
- }
- }
-
- if !found {
- c.env = append(c.env, s)
- }
- }
-
- for _, s := range env {
- setOrAppend(s)
- }
- }
-}
-
-// New creates a new Exec using the provided security config.
-func New(cfg security.Config, workingDir string, log loggers.Logger) *Exec {
- var baseEnviron []string
- for _, v := range os.Environ() {
- k, _ := config.SplitEnvVar(v)
- if cfg.Exec.OsEnv.Accept(k) {
- baseEnviron = append(baseEnviron, v)
- }
- }
-
- return &Exec{
- sc: cfg,
- workingDir: workingDir,
- infol: log.InfoCommand("exec"),
- baseEnviron: baseEnviron,
- newNPXRunnerCache: maps.NewCache[string, func(arg ...any) (Runner, error)](),
- }
-}
-
-// IsNotFound reports whether this is an error about a binary not found.
-func IsNotFound(err error) bool {
- var notFoundErr *NotFoundError
- return errors.As(err, ¬FoundErr)
-}
-
-// Exec enforces a security policy for commands run via os/exec.
-type Exec struct {
- sc security.Config
- workingDir string
- infol logg.LevelLogger
-
- // os.Environ filtered by the Exec.OsEnviron whitelist filter.
- baseEnviron []string
-
- newNPXRunnerCache *maps.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, fullyQualifiedName string, arg ...any) (Runner, error) {
- if err := e.sc.CheckAllowedExec(name); err != nil {
- return nil, err
- }
-
- env := make([]string, len(e.baseEnviron))
- copy(env, e.baseEnviron)
-
- cm := &commandeer{
- 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
- case reflect.Bool:
- truth = val.Bool()
- case reflect.Complex64, reflect.Complex128:
- truth = val.Complex() != 0
- case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Interface:
- truth = !val.IsNil()
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- truth = val.Int() != 0
- case reflect.Float32, reflect.Float64:
- truth = val.Float() != 0
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
- truth = val.Uint() != 0
- case reflect.Struct:
- truth = true // Struct values are always true.
- default:
- return
- }
-
- return
-}
-
-type methodKey struct {
- typ reflect.Type
- name string
-}
-
-var methodCache sync.Map
-
-// GetMethodByName is the same as reflect.Value.MethodByName, but it caches the
-// type lookup.
-func GetMethodByName(v reflect.Value, name string) reflect.Value {
- index := GetMethodIndexByName(v.Type(), name)
-
- if index == -1 {
- return reflect.Value{}
- }
-
- 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 := methodCache.Load(k)
- if found {
- return v.(int)
- }
- m, ok := tp.MethodByName(name)
- index := m.Index
- if !ok {
- index = -1
- }
- methodCache.Store(k, index)
-
- if !ok {
- return -1
- }
-
- return m.Index
-}
-
-var (
- timeType = reflect.TypeOf((*time.Time)(nil)).Elem()
- asTimeProviderType = reflect.TypeOf((*htime.AsTimeProvider)(nil)).Elem()
-)
-
-// 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.Ptr, 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
-}
-
-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)
-}
-
-// 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
- }
- if v.IsNil() {
- return reflect.Value{}
- }
- return v.Elem()
-}
-
-var contextInterface = reflect.TypeOf((*context.Context)(nil)).Elem()
-
-var isContextCache = maps.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
deleted file mode 100644
index cbcad0f22..000000000
--- a/common/hreflect/helpers_test.go
+++ /dev/null
@@ -1,150 +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 hreflect
-
-import (
- "context"
- "reflect"
- "testing"
- "time"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestIsTruthful(t *testing.T) {
- c := qt.New(t)
-
- 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)
-}
-
-func TestGetMethodByName(t *testing.T) {
- c := qt.New(t)
- v := reflect.ValueOf(&testStruct{})
- tp := v.Type()
-
- 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})
-}
-
-func BenchmarkIsContextType(b *testing.B) {
- type k string
- b.Run("value", func(b *testing.B) {
- ctx := context.Background()
- ctxs := make([]reflect.Type, b.N)
- for i := 0; i < b.N; i++ {
- ctxs[i] = reflect.TypeOf(context.WithValue(ctx, k("key"), i))
- }
-
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- if !IsContextType(ctxs[i]) {
- b.Fatal("not context")
- }
- }
- })
-
- b.Run("background", func(b *testing.B) {
- var ctxt reflect.Type = reflect.TypeOf(context.Background())
- for i := 0; i < b.N; i++ {
- if !IsContextType(ctxt) {
- b.Fatal("not context")
- }
- }
- })
-}
-
-func BenchmarkIsTruthFul(b *testing.B) {
- v := reflect.ValueOf("Hugo")
-
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- if !IsTruthfulValue(v) {
- b.Fatal("not truthful")
- }
- }
-}
-
-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 BenchmarkGetMethodByName(b *testing.B) {
- v := reflect.ValueOf(&testStruct{})
- methods := []string{"Method1", "Method2", "Method3", "Method4", "Method5"}
-
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- 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/hstrings/strings.go b/common/hstrings/strings.go
deleted file mode 100644
index 1de38678f..000000000
--- a/common/hstrings/strings.go
+++ /dev/null
@@ -1,134 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package hstrings
-
-import (
- "fmt"
- "regexp"
- "slices"
- "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)
-}
-
-// 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
-}
-
-type (
- Strings2 [2]string
- Strings3 [3]string
-)
diff --git a/common/hstrings/strings_test.go b/common/hstrings/strings_test.go
deleted file mode 100644
index d8e9e204a..000000000
--- a/common/hstrings/strings_test.go
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package hstrings
-
-import (
- "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 BenchmarkGetOrCompileRegexp(b *testing.B) {
- for i := 0; i < b.N; i++ {
- GetOrCompileRegexp(`\d+`)
- }
-}
-
-func BenchmarkCompileRegexp(b *testing.B) {
- for i := 0; i < b.N; i++ {
- regexp.MustCompile(`\d+`)
- }
-}
diff --git a/common/htime/htime_integration_test.go b/common/htime/htime_integration_test.go
deleted file mode 100644
index 8090add12..000000000
--- a/common/htime/htime_integration_test.go
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package htime_test
-
-import (
- "testing"
-
- "github.com/gohugoio/hugo/hugolib"
-)
-
-// Issue #11267
-func TestApplyWithContext(t *testing.T) {
- t.Parallel()
-
- files := `
--- config.toml --
-defaultContentLanguage = 'it'
--- layouts/index.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
deleted file mode 100644
index c71e39ee4..000000000
--- a/common/htime/time.go
+++ /dev/null
@@ -1,177 +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 htime
-
-import (
- "log"
- "strings"
- "time"
-
- "github.com/bep/clocks"
- "github.com/spf13/cast"
-
- "github.com/gohugoio/locales"
-)
-
-var (
- longDayNames = []string{
- "Sunday",
- "Monday",
- "Tuesday",
- "Wednesday",
- "Thursday",
- "Friday",
- "Saturday",
- }
-
- shortDayNames = []string{
- "Sun",
- "Mon",
- "Tue",
- "Wed",
- "Thu",
- "Fri",
- "Sat",
- }
-
- shortMonthNames = []string{
- "Jan",
- "Feb",
- "Mar",
- "Apr",
- "May",
- "Jun",
- "Jul",
- "Aug",
- "Sep",
- "Oct",
- "Nov",
- "Dec",
- }
-
- longMonthNames = []string{
- "January",
- "February",
- "March",
- "April",
- "May",
- "June",
- "July",
- "August",
- "September",
- "October",
- "November",
- "December",
- }
-
- Clock = clocks.System()
-)
-
-func NewTimeFormatter(ltr locales.Translator) TimeFormatter {
- if ltr == nil {
- panic("must provide a locales.Translator")
- }
- return TimeFormatter{
- ltr: ltr,
- }
-}
-
-// TimeFormatter is locale aware.
-type TimeFormatter struct {
- ltr locales.Translator
-}
-
-func (f TimeFormatter) Format(t time.Time, layout string) string {
- if layout == "" {
- return ""
- }
-
- if layout[0] == ':' {
- // It may be one of Hugo's custom layouts.
- switch strings.ToLower(layout[1:]) {
- case "date_full":
- return f.ltr.FmtDateFull(t)
- case "date_long":
- return f.ltr.FmtDateLong(t)
- case "date_medium":
- return f.ltr.FmtDateMedium(t)
- case "date_short":
- return f.ltr.FmtDateShort(t)
- case "time_full":
- return f.ltr.FmtTimeFull(t)
- case "time_long":
- return f.ltr.FmtTimeLong(t)
- case "time_medium":
- return f.ltr.FmtTimeMedium(t)
- case "time_short":
- return f.ltr.FmtTimeShort(t)
- }
- }
-
- s := t.Format(layout)
-
- monthIdx := t.Month() - 1 // Month() starts at 1.
- dayIdx := t.Weekday()
-
- if strings.Contains(layout, "January") {
- s = strings.ReplaceAll(s, longMonthNames[monthIdx], f.ltr.MonthWide(t.Month()))
- } else if strings.Contains(layout, "Jan") {
- s = strings.ReplaceAll(s, shortMonthNames[monthIdx], f.ltr.MonthAbbreviated(t.Month()))
- }
-
- if strings.Contains(layout, "Monday") {
- s = strings.ReplaceAll(s, longDayNames[dayIdx], f.ltr.WeekdayWide(t.Weekday()))
- } else if strings.Contains(layout, "Mon") {
- s = strings.ReplaceAll(s, shortDayNames[dayIdx], f.ltr.WeekdayAbbreviated(t.Weekday()))
- }
-
- return s
-}
-
-func ToTimeInDefaultLocationE(i any, location *time.Location) (tim time.Time, err error) {
- switch vv := i.(type) {
- 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
deleted file mode 100644
index 78954887e..000000000
--- a/common/htime/time_test.go
+++ /dev/null
@@ -1,144 +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 htime
-
-import (
- "testing"
- "time"
-
- qt "github.com/frankban/quicktest"
- translators "github.com/gohugoio/localescompressed"
-)
-
-func TestTimeFormatter(t *testing.T) {
- c := qt.New(t)
-
- june06, _ := time.Parse("2006-Jan-02", "2018-Jun-06")
- june06 = june06.Add(7777 * time.Second)
-
- jan06, _ := time.Parse("2006-Jan-02", "2018-Jan-06")
- jan06 = jan06.Add(32 * time.Second)
-
- mondayNovemberFirst, _ := time.Parse("2006-Jan-02", "2021-11-01")
- mondayNovemberFirst = mondayNovemberFirst.Add(33 * time.Second)
-
- c.Run("Norsk nynorsk", func(c *qt.C) {
- f := NewTimeFormatter(translators.GetTranslator("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")
- c.Assert(f.Format(june06, "Mon Mon"), qt.Equals, "on. on.")
- })
-
- c.Run("Custom layouts Norsk nynorsk", func(c *qt.C) {
- f := NewTimeFormatter(translators.GetTranslator("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, ":time_full"), qt.Equals, "kl. 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"))
-
- 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.Run("English", func(c *qt.C) {
- f := NewTimeFormatter(translators.GetTranslator("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")
- c.Assert(f.Format(june06, "Mon Mon"), qt.Equals, "Wed Wed")
- })
-
- c.Run("Weekdays German", func(c *qt.C) {
- tr := translators.GetTranslator("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(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(f.Format(date, "Mon"), qt.Equals, weekDayAbbreviatedGerman)
- }
- })
-
- c.Run("Months German", func(c *qt.C) {
- tr := translators.GetTranslator("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(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++ {
- got := june06.Format("Monday Jan 2 2006")
- if got != "Wednesday Jun 6 2018" {
- b.Fatalf("invalid format, got %q", got)
- }
- }
- })
-
- b.Run("Localized", func(b *testing.B) {
- f := NewTimeFormatter(translators.GetTranslator("nn"))
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- got := f.Format(june06, "Monday Jan 2 2006")
- if got != "onsdag juni 6 2018" {
- b.Fatalf("invalid format, got %q", got)
- }
- }
- })
-
- b.Run("Localized Custom", func(b *testing.B) {
- f := NewTimeFormatter(translators.GetTranslator("nn"))
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- 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
deleted file mode 100644
index 31d679dfc..000000000
--- a/common/hugio/copy.go
+++ /dev/null
@@ -1,93 +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 hugio
-
-import (
- "fmt"
- "io"
- iofs "io/fs"
- "path/filepath"
-
- "github.com/spf13/afero"
-)
-
-// CopyFile copies a file.
-func CopyFile(fs afero.Fs, from, to string) error {
- sf, err := fs.Open(from)
- if err != nil {
- return err
- }
- defer sf.Close()
- df, err := fs.Create(to)
- if err != nil {
- return err
- }
- defer df.Close()
- _, err = io.Copy(df, sf)
- if err != nil {
- return err
- }
- si, err := fs.Stat(from)
- if err != nil {
- err = fs.Chmod(to, si.Mode())
-
- if err != nil {
- return err
- }
- }
-
- return nil
-}
-
-// CopyDir copies a directory.
-func CopyDir(fs afero.Fs, from, to string, shouldCopy func(filename string) bool) error {
- fi, err := fs.Stat(from)
- if err != nil {
- return err
- }
-
- if !fi.IsDir() {
- return fmt.Errorf("%q is not a directory", from)
- }
-
- err = fs.MkdirAll(to, 0o777) // before umask
- if err != nil {
- return err
- }
-
- 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())
- if entry.IsDir() {
- if shouldCopy != nil && !shouldCopy(fromFilename) {
- continue
- }
- if err := CopyDir(fs, fromFilename, toFilename, shouldCopy); err != nil {
- return err
- }
- } else {
- if err := CopyFile(fs, fromFilename, toFilename); err != nil {
- return err
- }
- }
-
- }
-
- return nil
-}
diff --git a/common/hugio/hasBytesWriter.go b/common/hugio/hasBytesWriter.go
deleted file mode 100644
index d2bcd1bb4..000000000
--- a/common/hugio/hasBytesWriter.go
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package 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
deleted file mode 100644
index 9e689a112..000000000
--- a/common/hugio/hasBytesWriter_test.go
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package 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
deleted file mode 100644
index c4304c84e..000000000
--- a/common/hugio/readers.go
+++ /dev/null
@@ -1,106 +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 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.Closer
-}
-
-// ReadSeekCloserProvider provides a ReadSeekCloser.
-type ReadSeekCloserProvider interface {
- ReadSeekCloser() (ReadSeekCloser, error)
-}
-
-// readSeekerNopCloser implements ReadSeekCloser by doing nothing in Close.
-type readSeekerNopCloser struct {
- ReadSeeker
-}
-
-// Close does nothing.
-func (r readSeekerNopCloser) Close() error {
- return nil
-}
-
-// NewReadSeekerNoOpCloser creates a new ReadSeekerNoOpCloser with the given ReadSeeker.
-func NewReadSeekerNoOpCloser(r ReadSeeker) ReadSeekCloser {
- return readSeekerNopCloser{r}
-}
-
-// NewReadSeekerNoOpCloserFromString uses strings.NewReader to create a new ReadSeekerNoOpCloser
-// from the given string.
-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)}
-}
-
-// 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
deleted file mode 100644
index 6f439cc8b..000000000
--- a/common/hugio/writers.go
+++ /dev/null
@@ -1,113 +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 hugio
-
-import (
- "io"
-)
-
-// 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
-}
-
-func (m multiWriteCloser) Close() error {
- var err error
- for _, c := range m.closers {
- if closeErr := c.Close(); closeErr != nil {
- err = closeErr
- }
- }
- return err
-}
-
-// NewMultiWriteCloser creates a new io.WriteCloser that duplicates its writes to all the
-// provided writers.
-func NewMultiWriteCloser(writeClosers ...io.WriteCloser) io.WriteCloser {
- writers := make([]io.Writer, len(writeClosers))
- for i, w := range writeClosers {
- writers[i] = w
- }
- return multiWriteCloser{Writer: io.MultiWriter(writers...), closers: writeClosers}
-}
-
-// ToWriteCloser creates an io.WriteCloser from the given io.Writer.
-// If it's not already, one will be created with a Close method that does nothing.
-func ToWriteCloser(w io.Writer) io.WriteCloser {
- if rw, ok := w.(io.WriteCloser); ok {
- return rw
- }
-
- return struct {
- io.Writer
- io.Closer
- }{
- w,
- io.NopCloser(nil),
- }
-}
-
-// ToReadCloser creates an io.ReadCloser from the given io.Reader.
-// If it's not already, one will be created with a Close method that does nothing.
-func ToReadCloser(r io.Reader) io.ReadCloser {
- if rc, ok := r.(io.ReadCloser); ok {
- return rc
- }
-
- return struct {
- io.Reader
- io.Closer
- }{
- r,
- 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
deleted file mode 100644
index 764a86a97..000000000
--- a/common/hugo/hugo.go
+++ /dev/null
@@ -1,467 +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 (
- "context"
- "fmt"
- "html/template"
- "os"
- "path/filepath"
- "runtime/debug"
- "sort"
- "strings"
- "sync"
- "time"
-
- "github.com/bep/logg"
-
- "github.com/bep/godartsass/v2"
- "github.com/gohugoio/hugo/common/hcontext"
- "github.com/gohugoio/hugo/common/hexec"
- "github.com/gohugoio/hugo/common/loggers"
- "github.com/gohugoio/hugo/common/maps"
- "github.com/gohugoio/hugo/hugofs/files"
-
- "github.com/spf13/afero"
-
- iofs "io/fs"
-
- "github.com/gohugoio/hugo/config"
- "github.com/gohugoio/hugo/hugofs"
-)
-
-const (
- EnvironmentDevelopment = "development"
- EnvironmentProduction = "production"
-)
-
-var (
- // buildDate allows vendor-specified build date when .git/ is unavailable.
- buildDate string
- // vendorInfo contains vendor notes about the current build.
- vendorInfo string
-)
-
-var _ maps.StoreProvider = (*HugoInfo)(nil)
-
-// HugoInfo contains information about the current Hugo environment
-type HugoInfo 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
-
- // version of go that the Hugo binary was built with
- GoVersion string
-
- conf ConfigProvider
- deps []*Dependency
-
- store *maps.Scratch
-
- // Context gives access to some of the context scoped variables.
- Context Context
-}
-
-// Version returns the current version as a comparable version string.
-func (i HugoInfo) Version() VersionString {
- return CurrentVersion.Version()
-}
-
-// Generator a Hugo meta generator HTML tag.
-func (i HugoInfo) Generator() template.HTML {
- return template.HTML(fmt.Sprintf(``, CurrentVersion.String()))
-}
-
-// IsDevelopment reports whether the current running environment is "development".
-func (i HugoInfo) IsDevelopment() bool {
- return i.Environment == EnvironmentDevelopment
-}
-
-// IsProduction reports whether the current running environment is "production".
-func (i HugoInfo) IsProduction() bool {
- return i.Environment == EnvironmentProduction
-}
-
-// IsServer reports whether the built-in server is running.
-func (i HugoInfo) IsServer() bool {
- return i.conf.Running()
-}
-
-// IsExtended reports whether the Hugo binary is the extended version.
-func (i HugoInfo) IsExtended() bool {
- return IsExtended
-}
-
-// WorkingDir returns the project working directory.
-func (i HugoInfo) WorkingDir() string {
- return i.conf.WorkingDir()
-}
-
-// Deps gets a list of dependencies for this Hugo build.
-func (i HugoInfo) Deps() []*Dependency {
- return i.deps
-}
-
-func (i HugoInfo) Store() *maps.Scratch {
- return i.store
-}
-
-// Deprecated: Use hugo.IsMultihost instead.
-func (i HugoInfo) IsMultiHost() bool {
- Deprecate("hugo.IsMultiHost", "Use hugo.IsMultihost instead.", "v0.124.0")
- return i.conf.IsMultihost()
-}
-
-// IsMultihost reports whether each configured language has a unique baseURL.
-func (i HugoInfo) IsMultihost() bool {
- return i.conf.IsMultihost()
-}
-
-// IsMultilingual reports whether there are two or more configured languages.
-func (i HugoInfo) IsMultilingual() bool {
- return i.conf.IsMultilingual()
-}
-
-type contextKey uint8
-
-const (
- contextKeyMarkupScope contextKey = iota
-)
-
-var markupScope = hcontext.NewContextDispatcher[string](contextKeyMarkupScope)
-
-type Context struct{}
-
-func (c Context) MarkupScope(ctx context.Context) string {
- return GetMarkupScope(ctx)
-}
-
-// SetMarkupScope sets the markup scope in the context.
-func SetMarkupScope(ctx context.Context, s string) context.Context {
- return markupScope.Set(ctx, s)
-}
-
-// GetMarkupScope gets the markup scope from the context.
-func GetMarkupScope(ctx context.Context) string {
- return markupScope.Get(ctx)
-}
-
-// ConfigProvider represents the config options that are relevant for HugoInfo.
-type ConfigProvider interface {
- Environment() string
- Running() bool
- WorkingDir() string
- IsMultihost() bool
- IsMultilingual() bool
-}
-
-// NewInfo creates a new Hugo Info object.
-func NewInfo(conf ConfigProvider, deps []*Dependency) HugoInfo {
- if conf.Environment() == "" {
- panic("environment not set")
- }
- var (
- commitHash string
- buildDate string
- goVersion string
- )
-
- bi := getBuildInfo()
- if bi != nil {
- commitHash = bi.Revision
- buildDate = bi.RevisionTime
- goVersion = bi.GoVersion
- }
-
- return HugoInfo{
- CommitHash: commitHash,
- BuildDate: buildDate,
- Environment: conf.Environment(),
- conf: conf,
- deps: deps,
- store: maps.NewScratch(),
- GoVersion: goVersion,
- }
-}
-
-// 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.AllProvider, fs afero.Fs) []string {
- var env []string
- nodepath := filepath.Join(workDir, "node_modules")
- if np := os.Getenv("NODE_PATH"); np != "" {
- nodepath = workDir + string(os.PathListSeparator) + np
- }
- config.SetEnvVars(&env, "NODE_PATH", nodepath)
- config.SetEnvVars(&env, "PWD", workDir)
- 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 {
- 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()), ".", "_"))
- value := fi.(hugofs.FileMetaInfo).Meta().Filename
- config.SetEnvVars(&env, key, value)
- }
- }
- }
-
- 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
-
- bi := getBuildInfo()
- if bi == nil {
- return deps
- }
-
- for _, dep := range bi.Deps {
- 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 {
- var deps []string
-
- if IsExtended {
- deps = append(
- deps,
- formatDep("github.com/sass/libsass", "3.6.6"),
- formatDep("github.com/webmproject/libwebp", "v1.3.2"),
- )
- }
-
- 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 {
- if strings.HasPrefix(arg, "-test") {
- return true
- }
- }
- 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) {
- var msg string
- if level == logg.LevelError {
- 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 := 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
- }
-}
diff --git a/common/hugo/hugo_integration_test.go b/common/hugo/hugo_integration_test.go
deleted file mode 100644
index 77dbb5c91..000000000
--- a/common/hugo/hugo_integration_test.go
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package 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/index.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",
- )
-}
diff --git a/common/hugo/hugo_test.go b/common/hugo/hugo_test.go
deleted file mode 100644
index f938073da..000000000
--- a/common/hugo/hugo_test.go
+++ /dev/null
@@ -1,111 +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 (
- "context"
- "fmt"
- "testing"
-
- "github.com/bep/logg"
- qt "github.com/frankban/quicktest"
-)
-
-func TestHugoInfo(t *testing.T) {
- c := qt.New(t)
-
- conf := testConfig{environment: "production", workingDir: "/mywork", running: false}
- hugoInfo := NewInfo(conf, nil)
-
- c.Assert(hugoInfo.Version(), qt.Equals, CurrentVersion.Version())
- c.Assert(fmt.Sprintf("%T", VersionString("")), qt.Equals, fmt.Sprintf("%T", hugoInfo.Version()))
- c.Assert(hugoInfo.WorkingDir(), qt.Equals, "/mywork")
-
- bi := getBuildInfo()
- if bi != nil {
- c.Assert(hugoInfo.CommitHash, qt.Equals, bi.Revision)
- c.Assert(hugoInfo.BuildDate, qt.Equals, bi.RevisionTime)
- c.Assert(hugoInfo.GoVersion, qt.Equals, bi.GoVersion)
- }
- c.Assert(hugoInfo.Environment, qt.Equals, "production")
- c.Assert(string(hugoInfo.Generator()), qt.Contains, fmt.Sprintf("Hugo %s", hugoInfo.Version()))
- c.Assert(hugoInfo.IsDevelopment(), qt.Equals, false)
- c.Assert(hugoInfo.IsProduction(), qt.Equals, true)
- c.Assert(hugoInfo.IsExtended(), qt.Equals, IsExtended)
- c.Assert(hugoInfo.IsServer(), qt.Equals, false)
-
- devHugoInfo := NewInfo(testConfig{environment: "development", running: true}, nil)
- c.Assert(devHugoInfo.IsDevelopment(), qt.Equals, true)
- c.Assert(devHugoInfo.IsProduction(), qt.Equals, false)
- c.Assert(devHugoInfo.IsServer(), qt.Equals, true)
-}
-
-func TestDeprecationLogLevelFromVersion(t *testing.T) {
- c := qt.New(t)
-
- 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)
-
- // Added just to find the threshold for where we can remove deprecated items.
- // Subtract 5 from the minor version of the first ERRORed version => 0.122.0.
- c.Assert(deprecationLogLevelFromVersion("0.127.0"), qt.Equals, logg.LevelError)
-}
-
-func TestMarkupScope(t *testing.T) {
- c := qt.New(t)
-
- conf := testConfig{environment: "production", workingDir: "/mywork", running: false}
- info := NewInfo(conf, nil)
-
- ctx := context.Background()
-
- ctx = SetMarkupScope(ctx, "foo")
-
- c.Assert(info.Context.MarkupScope(ctx), qt.Equals, "foo")
-}
-
-type testConfig struct {
- environment string
- running bool
- workingDir string
- multihost bool
- multilingual bool
-}
-
-func (c testConfig) Environment() string {
- return c.environment
-}
-
-func (c testConfig) Running() bool {
- return c.running
-}
-
-func (c testConfig) WorkingDir() string {
- return c.workingDir
-}
-
-func (c testConfig) IsMultihost() bool {
- return c.multihost
-}
-
-func (c testConfig) IsMultilingual() bool {
- return c.multilingual
-}
diff --git a/common/hugo/vars_extended.go b/common/hugo/vars_extended.go
deleted file mode 100644
index ab01e2647..000000000
--- a/common/hugo/vars_extended.go
+++ /dev/null
@@ -1,18 +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.
-
-//go:build extended
-
-package hugo
-
-var IsExtended = true
diff --git a/common/hugo/vars_regular.go b/common/hugo/vars_regular.go
deleted file mode 100644
index a78aeb0b6..000000000
--- a/common/hugo/vars_regular.go
+++ /dev/null
@@ -1,18 +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.
-
-//go:build !extended
-
-package hugo
-
-var IsExtended = false
diff --git a/common/hugo/vars_withdeploy.go b/common/hugo/vars_withdeploy.go
deleted file mode 100644
index 4e0c3efbb..000000000
--- a/common/hugo/vars_withdeploy.go
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-//go:build withdeploy
-
-package hugo
-
-var IsWithdeploy = true
diff --git a/common/hugo/vars_withdeploy_off.go b/common/hugo/vars_withdeploy_off.go
deleted file mode 100644
index 36e9bd874..000000000
--- a/common/hugo/vars_withdeploy_off.go
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-//go:build !withdeploy
-
-package hugo
-
-var IsWithdeploy = false
diff --git a/common/hugo/version.go b/common/hugo/version.go
deleted file mode 100644
index cf5988840..000000000
--- a/common/hugo/version.go
+++ /dev/null
@@ -1,305 +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"
- "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
-}
-
-var (
- _ compare.Eqer = (*VersionString)(nil)
- _ compare.Comparer = (*VersionString)(nil)
-)
-
-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()
-}
-
-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)
- }
- }
-
- vv.Major, vv.Minor, vv.PatchLevel = parseVersion(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
-}
-
-// 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
-}
-
-func version(major, minor, patch int, suffix string) string {
- 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 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 any) int {
- return compareVersions(CurrentVersion, version)
-}
-
-func compareVersions(inVersion Version, in any) int {
- var c int
- switch d := in.(type) {
- case float64:
- c = compareFloatWithVersion(d, inVersion)
- case float32:
- c = compareFloatWithVersion(float64(d), inVersion)
- case int:
- c = compareFloatWithVersion(float64(d), inVersion)
- case int32:
- c = compareFloatWithVersion(float64(d), inVersion)
- case int64:
- c = compareFloatWithVersion(float64(d), inVersion)
- case Version:
- if d.Major == inVersion.Major && d.Minor == inVersion.Minor && d.PatchLevel == inVersion.PatchLevel {
- return strings.Compare(inVersion.Suffix, d.Suffix)
- }
- if d.Major > inVersion.Major {
- return 1
- } else if d.Major < inVersion.Major {
- return -1
- }
- if d.Minor > inVersion.Minor {
- return 1
- } else if d.Minor < inVersion.Minor {
- return -1
- }
- if d.PatchLevel > inVersion.PatchLevel {
- return 1
- } else if d.PatchLevel < inVersion.PatchLevel {
- return -1
- }
- default:
- s, err := cast.ToStringE(in)
- if err != nil {
- return -1
- }
-
- v, err := ParseVersion(s)
- if err != nil {
- return -1
- }
- return inVersion.Compare(v)
-
- }
-
- 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/hugo/version_current.go b/common/hugo/version_current.go
deleted file mode 100644
index ba367ceb5..000000000
--- a/common/hugo/version_current.go
+++ /dev/null
@@ -1,23 +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
-
-// CurrentVersion represents the current build version.
-// This should be the only one.
-var CurrentVersion = Version{
- Major: 0,
- Minor: 148,
- PatchLevel: 0,
- Suffix: "-DEV",
-}
diff --git a/common/hugo/version_test.go b/common/hugo/version_test.go
deleted file mode 100644
index 33e50ebf5..000000000
--- a/common/hugo/version_test.go
+++ /dev/null
@@ -1,88 +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{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)
-
- 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)
-}
-
-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
deleted file mode 100644
index bc3c7eec2..000000000
--- a/common/loggers/handlerdefault.go
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-// Some functions in this file (see comments) is based on the Go source code,
-// copyright The Go Authors and governed by a BSD-style license.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// package loggers 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
deleted file mode 100644
index 2ae6300f7..000000000
--- a/common/loggers/handlersmisc.go
+++ /dev/null
@@ -1,145 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-// Some functions in this file (see comments) is based on the Go source code,
-// copyright The Go Authors and governed by a BSD-style license.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package loggers
-
-import (
- "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
deleted file mode 100644
index c6a86d3a2..000000000
--- a/common/loggers/handlerterminal.go
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-// Some functions in this file (see comments) is based on the Go source code,
-// copyright The Go Authors and governed by a BSD-style license.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package loggers
-
-import (
- "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
deleted file mode 100644
index f45ce80df..000000000
--- a/common/loggers/handlerterminal_test.go
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-// Some functions in this file (see comments) is based on the Go source code,
-// copyright The Go Authors and governed by a BSD-style license.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package loggers
-
-import (
- "bytes"
- "testing"
-
- "github.com/bep/logg"
- qt "github.com/frankban/quicktest"
- "github.com/gohugoio/hugo/common/terminal"
-)
-
-func TestNoAnsiEscapeHandler(t *testing.T) {
- c := qt.New(t)
-
- test := func(s string) {
- c.Assert(stripANSI(terminal.Notice(s)), qt.Equals, s)
- }
- test(`error in "file.md:1:2"`)
-
- var buf bytes.Buffer
- h := newNoAnsiEscapeHandler(&buf, &buf, false, nil)
- h.HandleLog(&logg.Entry{Message: terminal.Notice(`error in "file.md:1:2"`), Level: logg.LevelInfo})
-
- c.Assert(buf.String(), qt.Equals, "INFO error in \"file.md:1:2\"\n")
-}
diff --git a/common/loggers/logger.go b/common/loggers/logger.go
deleted file mode 100644
index a013049f7..000000000
--- a/common/loggers/logger.go
+++ /dev/null
@@ -1,385 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-// Some functions in this file (see comments) is based on the Go source code,
-// copyright The Go Authors and governed by a BSD-style license.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package loggers
-
-import (
- "fmt"
- "io"
- "os"
- "strings"
- "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)
-
- reset := func() {
- logCounters.mu.Lock()
- defer logCounters.mu.Unlock()
- logCounters.counters = make(map[logg.Level]int)
- errorsw.Reset()
- if logOnce != nil {
- logOnce.reset()
- }
- }
-
- return &logAdapter{
- 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
- 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 {
- 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) 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", 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 {
- 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 site 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
deleted file mode 100644
index bc8975b06..000000000
--- a/common/loggers/logger_test.go
+++ /dev/null
@@ -1,154 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-// Some functions in this file (see comments) is based on the Go source code,
-// copyright The Go Authors and governed by a BSD-style license.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package loggers_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
deleted file mode 100644
index b8c9a6931..000000000
--- a/common/loggers/loggerglobal.go
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-// Some functions in this file (see comments) is based on the Go source code,
-// copyright The Go Authors and governed by a BSD-style license.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package loggers
-
-import (
- "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/maps/cache.go b/common/maps/cache.go
deleted file mode 100644
index de1535994..000000000
--- a/common/maps/cache.go
+++ /dev/null
@@ -1,195 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package maps
-
-import (
- "sync"
-)
-
-// Cache is a simple thread safe cache backed by a map.
-type Cache[K comparable, T any] struct {
- m map[K]T
- hasBeenInitialized bool
- sync.RWMutex
-}
-
-// NewCache creates a new Cache.
-func NewCache[K comparable, T any]() *Cache[K, T] {
- return &Cache[K, T]{m: make(map[K]T)}
-}
-
-// Delete deletes the given key from the cache.
-// If c is nil, this method is a no-op.
-func (c *Cache[K, T]) Get(key K) (T, bool) {
- if c == nil {
- var zero T
- return zero, false
- }
- c.RLock()
- v, found := c.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.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]) set(key K, value T) {
- 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/maps/maps.go b/common/maps/maps.go
deleted file mode 100644
index f9171ebf2..000000000
--- a/common/maps/maps.go
+++ /dev/null
@@ -1,236 +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 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 []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/maps/maps_test.go b/common/maps/maps_test.go
deleted file mode 100644
index 40c8ac824..000000000
--- a/common/maps/maps_test.go
+++ /dev/null
@@ -1,201 +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]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/maps/ordered.go b/common/maps/ordered.go
deleted file mode 100644
index 0da9d239d..000000000
--- a/common/maps/ordered.go
+++ /dev/null
@@ -1,144 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package maps
-
-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/maps/ordered_test.go b/common/maps/ordered_test.go
deleted file mode 100644
index 65a827810..000000000
--- a/common/maps/ordered_test.go
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package maps
-
-import (
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestOrdered(t *testing.T) {
- c := qt.New(t)
-
- m := NewOrdered[string, int]()
- m.Set("a", 1)
- m.Set("b", 2)
- m.Set("c", 3)
-
- c.Assert(m.Keys(), qt.DeepEquals, []string{"a", "b", "c"})
- c.Assert(m.Values(), qt.DeepEquals, []int{1, 2, 3})
-
- v, found := m.Get("b")
- c.Assert(found, qt.Equals, true)
- c.Assert(v, qt.Equals, 2)
-
- m.Set("b", 22)
- c.Assert(m.Keys(), qt.DeepEquals, []string{"a", "b", "c"})
- c.Assert(m.Values(), qt.DeepEquals, []int{1, 22, 3})
-
- m.Delete("b")
-
- c.Assert(m.Keys(), qt.DeepEquals, []string{"a", "c"})
- c.Assert(m.Values(), qt.DeepEquals, []int{1, 3})
-}
-
-func TestOrderedHash(t *testing.T) {
- c := qt.New(t)
-
- m := NewOrdered[string, int]()
- m.Set("a", 1)
- m.Set("b", 2)
- m.Set("c", 3)
-
- h1, err := m.Hash()
- c.Assert(err, qt.IsNil)
-
- m.Set("d", 4)
-
- h2, err := m.Hash()
- c.Assert(err, qt.IsNil)
-
- c.Assert(h1, qt.Not(qt.Equals), h2)
-
- m = NewOrdered[string, int]()
- m.Set("b", 2)
- m.Set("a", 1)
- m.Set("c", 3)
-
- h3, err := m.Hash()
- c.Assert(err, qt.IsNil)
- // Order does not matter.
- c.Assert(h1, qt.Equals, h3)
-}
-
-func TestOrderedNil(t *testing.T) {
- c := qt.New(t)
-
- var m *Ordered[string, int]
-
- m.Set("a", 1)
- c.Assert(m.Keys(), qt.IsNil)
- c.Assert(m.Values(), qt.IsNil)
- v, found := m.Get("a")
- c.Assert(found, qt.Equals, false)
- c.Assert(v, qt.Equals, 0)
- m.Delete("a")
- var b bool
- m.Range(func(k string, v int) bool {
- b = true
- return true
- })
- c.Assert(b, qt.Equals, false)
- c.Assert(m.Len(), qt.Equals, 0)
- c.Assert(m.Clone(), qt.IsNil)
- h, err := m.Hash()
- c.Assert(err, qt.IsNil)
- c.Assert(h, qt.Equals, uint64(0))
-}
diff --git a/common/maps/params.go b/common/maps/params.go
deleted file mode 100644
index 819f796e4..000000000
--- a/common/maps/params.go
+++ /dev/null
@@ -1,384 +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]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) {
- 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)
- } 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
- }
-}
-
-// 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 {
- 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]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] = CleanConfigStringMap(v2)
- case Params:
- var p Params = CleanConfigStringMap(v2)
- 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
- }
- }
-}
-
-// 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/maps/params_test.go b/common/maps/params_test.go
deleted file mode 100644
index 892c77175..000000000
--- a/common/maps/params_test.go
+++ /dev/null
@@ -1,169 +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]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)
-}
diff --git a/common/maps/scratch.go b/common/maps/scratch.go
deleted file mode 100644
index cf5231783..000000000
--- a/common/maps/scratch.go
+++ /dev/null
@@ -1,160 +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"
-)
-
-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/maps/scratch_test.go
deleted file mode 100644
index f07169e61..000000000
--- a/common/maps/scratch_test.go
+++ /dev/null
@@ -1,221 +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 (
- "reflect"
- "sync"
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestScratchAdd(t *testing.T) {
- t.Parallel()
- c := qt.New(t)
-
- scratch := NewScratch()
- scratch.Add("int1", 10)
- scratch.Add("int1", 20)
- scratch.Add("int2", 20)
-
- c.Assert(scratch.Get("int1"), qt.Equals, int64(30))
- c.Assert(scratch.Get("int2"), qt.Equals, 20)
-
- scratch.Add("float1", float64(10.5))
- scratch.Add("float1", float64(20.1))
-
- c.Assert(scratch.Get("float1"), qt.Equals, float64(30.6))
-
- scratch.Add("string1", "Hello ")
- scratch.Add("string1", "big ")
- scratch.Add("string1", "World!")
-
- c.Assert(scratch.Get("string1"), qt.Equals, "Hello big World!")
-
- scratch.Add("scratch", scratch)
- _, err := scratch.Add("scratch", scratch)
-
- m := scratch.Values()
- c.Assert(m, qt.HasLen, 5)
-
- if err == nil {
- t.Errorf("Expected error from invalid arithmetic")
- }
-}
-
-func TestScratchAddSlice(t *testing.T) {
- t.Parallel()
- c := qt.New(t)
-
- scratch := NewScratch()
-
- _, err := scratch.Add("intSlice", []int{1, 2})
- c.Assert(err, qt.IsNil)
- _, err = scratch.Add("intSlice", 3)
- c.Assert(err, qt.IsNil)
-
- sl := scratch.Get("intSlice")
- expected := []int{1, 2, 3}
-
- if !reflect.DeepEqual(expected, sl) {
- t.Errorf("Slice difference, go %q expected %q", sl, expected)
- }
- _, err = scratch.Add("intSlice", []int{4, 5})
-
- c.Assert(err, qt.IsNil)
-
- sl = scratch.Get("intSlice")
- expected = []int{1, 2, 3, 4, 5}
-
- if !reflect.DeepEqual(expected, sl) {
- t.Errorf("Slice difference, go %q expected %q", sl, expected)
- }
-}
-
-// https://github.com/gohugoio/hugo/issues/5275
-func TestScratchAddTypedSliceToInterfaceSlice(t *testing.T) {
- t.Parallel()
- c := qt.New(t)
-
- scratch := NewScratch()
- scratch.Set("slice", []any{})
-
- _, err := scratch.Add("slice", []int{1, 2})
- c.Assert(err, qt.IsNil)
- c.Assert(scratch.Get("slice"), qt.DeepEquals, []int{1, 2})
-}
-
-// https://github.com/gohugoio/hugo/issues/5361
-func TestScratchAddDifferentTypedSliceToInterfaceSlice(t *testing.T) {
- t.Parallel()
- c := qt.New(t)
-
- scratch := NewScratch()
- scratch.Set("slice", []string{"foo"})
-
- _, err := scratch.Add("slice", []int{1, 2})
- c.Assert(err, qt.IsNil)
- c.Assert(scratch.Get("slice"), qt.DeepEquals, []any{"foo", 1, 2})
-}
-
-func TestScratchSet(t *testing.T) {
- t.Parallel()
- c := qt.New(t)
-
- scratch := NewScratch()
- scratch.Set("key", "val")
- c.Assert(scratch.Get("key"), qt.Equals, "val")
-}
-
-func TestScratchDelete(t *testing.T) {
- t.Parallel()
- c := qt.New(t)
-
- scratch := NewScratch()
- scratch.Set("key", "val")
- scratch.Delete("key")
- scratch.Add("key", "Lucy Parsons")
- c.Assert(scratch.Get("key"), qt.Equals, "Lucy Parsons")
-}
-
-// Issue #2005
-func TestScratchInParallel(t *testing.T) {
- var wg sync.WaitGroup
- scratch := NewScratch()
-
- key := "counter"
- scratch.Set(key, int64(1))
- for i := 1; i <= 10; i++ {
- wg.Add(1)
- go func(j int) {
- for k := range 10 {
- newVal := int64(k + j)
-
- _, err := scratch.Add(key, newVal)
- if err != nil {
- t.Errorf("Got err %s", err)
- }
-
- scratch.Set(key, newVal)
-
- val := scratch.Get(key)
-
- if counter, ok := val.(int64); ok {
- if counter < 1 {
- t.Errorf("Got %d", counter)
- }
- } else {
- t.Errorf("Got %T", val)
- }
- }
- wg.Done()
- }(i)
- }
- wg.Wait()
-}
-
-func TestScratchGet(t *testing.T) {
- t.Parallel()
- scratch := NewScratch()
- nothing := scratch.Get("nothing")
- if nothing != nil {
- t.Errorf("Should not return anything, but got %v", nothing)
- }
-}
-
-func TestScratchSetInMap(t *testing.T) {
- t.Parallel()
- c := qt.New(t)
-
- scratch := NewScratch()
- scratch.SetInMap("key", "lux", "Lux")
- scratch.SetInMap("key", "abc", "Abc")
- scratch.SetInMap("key", "zyx", "Zyx")
- scratch.SetInMap("key", "abc", "Abc (updated)")
- scratch.SetInMap("key", "def", "Def")
- c.Assert(scratch.GetSortedMapValues("key"), qt.DeepEquals, any([]any{"Abc (updated)", "Def", "Lux", "Zyx"}))
-}
-
-func TestScratchDeleteInMap(t *testing.T) {
- t.Parallel()
- c := qt.New(t)
-
- scratch := NewScratch()
- scratch.SetInMap("key", "lux", "Lux")
- scratch.SetInMap("key", "abc", "Abc")
- scratch.SetInMap("key", "zyx", "Zyx")
- scratch.DeleteInMap("key", "abc")
- scratch.SetInMap("key", "def", "Def")
- scratch.DeleteInMap("key", "lmn") // Do nothing
- c.Assert(scratch.GetSortedMapValues("key"), qt.DeepEquals, any([]any{"Def", "Lux", "Zyx"}))
-}
-
-func TestScratchGetSortedMapValues(t *testing.T) {
- t.Parallel()
- scratch := NewScratch()
- nothing := scratch.GetSortedMapValues("nothing")
- if nothing != nil {
- t.Errorf("Should not return anything, but got %v", nothing)
- }
-}
-
-func BenchmarkScratchGet(b *testing.B) {
- scratch := NewScratch()
- scratch.Add("A", 1)
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- scratch.Get("A")
- }
-}
diff --git a/common/math/math.go b/common/math/math.go
deleted file mode 100644
index f88fbcd9c..000000000
--- a/common/math/math.go
+++ /dev/null
@@ -1,133 +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 math
-
-import (
- "errors"
- "reflect"
-)
-
-// DoArithmetic performs arithmetic operations (+,-,*,/) using reflection to
-// determine the type of the two terms.
-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
- 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)
- } else {
- isInt = true
- bi = int64(bu) // may overflow
- }
- 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:
- bf = float64(bv.Int()) // may overflow
- case reflect.Float32, reflect.Float64:
- bf = bv.Float()
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- bf = float64(bv.Uint()) // may overflow
- default:
- return nil, errors.New("can't apply the operator to the values")
- }
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- au = av.Uint()
- switch bv.Kind() {
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- bi = bv.Int()
- if bi >= 0 {
- isUint = true
- bu = uint64(bi)
- } else {
- isInt = true
- ai = int64(au) // may overflow
- }
- case reflect.Float32, reflect.Float64:
- isFloat = true
- af = float64(au) // may overflow
- 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")
- }
- case reflect.String:
- as := av.String()
- if bv.Kind() == reflect.String && op == '+' {
- bs := bv.String()
- return as + bs, nil
- }
- return nil, errors.New("can't apply the operator to the values")
- default:
- return nil, errors.New("can't apply the operator to the values")
- }
-
- switch op {
- case '+':
- if isInt {
- return ai + bi, nil
- } else if isFloat {
- return af + bf, nil
- }
- return au + bu, nil
- case '-':
- if isInt {
- return ai - bi, nil
- } else if isFloat {
- return af - bf, nil
- }
- return au - bu, nil
- case '*':
- if isInt {
- return ai * bi, nil
- } else if isFloat {
- return af * bf, nil
- }
- return au * bu, nil
- case '/':
- if isInt && bi != 0 {
- return ai / bi, nil
- } else if isFloat && bf != 0 {
- return af / bf, nil
- } else if isUint && bu != 0 {
- return au / bu, nil
- }
- return nil, errors.New("can't divide the value by 0")
- default:
- return nil, errors.New("there is no such an operation")
- }
-}
diff --git a/common/math/math_test.go b/common/math/math_test.go
deleted file mode 100644
index d75d30a69..000000000
--- a/common/math/math_test.go
+++ /dev/null
@@ -1,111 +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 math
-
-import (
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestDoArithmetic(t *testing.T) {
- t.Parallel()
- c := qt.New(t)
-
- for _, test := range []struct {
- a any
- b any
- op rune
- 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)},
- {3, 2.0, '+', float64(5)},
- {3, 2.0, '-', float64(1)},
- {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)},
- {uint(3), -2, '+', int64(1)},
- {uint(3), -2, '-', int64(5)},
- {uint(3), -2, '*', int64(-6)},
- {uint(3), -2, '/', int64(-1)},
- {-3, uint(2), '+', int64(-1)},
- {-3, uint(2), '-', int64(-5)},
- {-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)},
- {"foo", "bar", '+', "foobar"},
- {3, 0, '/', false},
- {3.0, 0, '/', false},
- {3, 0.0, '/', false},
- {uint(3), uint(0), '/', false},
- {3, uint(0), '/', false},
- {-3, uint(0), '/', false},
- {uint(3), 0, '/', false},
- {3.0, uint(0), '/', false},
- {uint(3), 0.0, '/', false},
- {3, "foo", '+', false},
- {3.0, "foo", '+', false},
- {uint(3), "foo", '+', false},
- {"foo", 3, '+', false},
- {"foo", "bar", '-', false},
- {3, 2, '%', false},
- } {
- result, err := DoArithmetic(test.a, test.b, test.op)
-
- if b, ok := test.expect.(bool); ok && !b {
- c.Assert(err, qt.Not(qt.IsNil))
- continue
- }
-
- c.Assert(err, qt.IsNil)
- c.Assert(test.expect, qt.Equals, result)
- }
-}
diff --git a/common/para/para.go b/common/para/para.go
deleted file mode 100644
index c323a3073..000000000
--- a/common/para/para.go
+++ /dev/null
@@ -1,73 +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 para implements parallel execution helpers.
-package para
-
-import (
- "context"
-
- "golang.org/x/sync/errgroup"
-)
-
-// Workers configures a task executor with the most number of tasks to be executed in parallel.
-type Workers struct {
- sem chan struct{}
-}
-
-// Runner wraps the lifecycle methods of a new task set.
-//
-// 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 {
- Run(func() error)
- Wait() error
-}
-
-type errGroupRunner struct {
- *errgroup.Group
- w *Workers
- ctx context.Context
-}
-
-func (g *errGroupRunner) Run(fn func() error) {
- select {
- case g.w.sem <- struct{}{}:
- case <-g.ctx.Done():
- return
- }
-
- g.Go(func() error {
- err := fn()
- <-g.w.sem
- return err
- })
-}
-
-// New creates a new Workers with the given number of workers.
-func New(numWorkers int) *Workers {
- return &Workers{
- sem: make(chan struct{}, numWorkers),
- }
-}
-
-// Start starts a new Runner.
-func (w *Workers) Start(ctx context.Context) (Runner, context.Context) {
- g, ctx := errgroup.WithContext(ctx)
- return &errGroupRunner{
- Group: g,
- ctx: ctx,
- w: w,
- }, ctx
-}
diff --git a/common/para/para_test.go b/common/para/para_test.go
deleted file mode 100644
index cf24a4e37..000000000
--- a/common/para/para_test.go
+++ /dev/null
@@ -1,96 +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 para
-
-import (
- "context"
- "runtime"
- "sort"
- "sync"
- "sync/atomic"
- "testing"
- "time"
-
- "github.com/gohugoio/hugo/htesting"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestPara(t *testing.T) {
- if runtime.NumCPU() < 4 {
- t.Skipf("skip para test, CPU count is %d", runtime.NumCPU())
- }
-
- // TODO(bep)
- if htesting.IsCI() {
- t.Skip("skip para test when running on CI")
- }
-
- c := qt.New(t)
-
- c.Run("Order", func(c *qt.C) {
- n := 500
- ints := make([]int, n)
- for i := range n {
- ints[i] = i
- }
-
- p := New(4)
- r, _ := p.Start(context.Background())
-
- var result []int
- var mu sync.Mutex
- for i := range n {
- i := i
- r.Run(func() error {
- mu.Lock()
- defer mu.Unlock()
- result = append(result, i)
- return nil
- })
- }
-
- c.Assert(r.Wait(), qt.IsNil)
- c.Assert(result, qt.HasLen, len(ints))
- c.Assert(sort.IntsAreSorted(result), qt.Equals, false, qt.Commentf("Para does not seem to be parallel"))
- sort.Ints(result)
- c.Assert(result, qt.DeepEquals, ints)
- })
-
- c.Run("Time", func(c *qt.C) {
- const n = 100
-
- p := New(5)
- r, _ := p.Start(context.Background())
-
- start := time.Now()
-
- var counter int64
-
- for range n {
- r.Run(func() error {
- atomic.AddInt64(&counter, 1)
- time.Sleep(1 * time.Millisecond)
- return nil
- })
- }
-
- c.Assert(r.Wait(), qt.IsNil)
- c.Assert(counter, qt.Equals, int64(n))
-
- since := time.Since(start)
- limit := n / 2 * time.Millisecond
- c.Assert(since < limit, qt.Equals, true, qt.Commentf("%s >= %s", since, limit))
- })
-}
diff --git a/common/paths/path.go b/common/paths/path.go
deleted file mode 100644
index de91d6a2f..000000000
--- a/common/paths/path.go
+++ /dev/null
@@ -1,430 +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 paths
-
-import (
- "errors"
- "fmt"
- "net/url"
- "path"
- "path/filepath"
- "strings"
- "unicode"
-)
-
-// FilePathSeparator as defined by os.Separator.
-const (
- FilePathSeparator = string(filepath.Separator)
- slash = "/"
-)
-
-// filepathPathBridge is a bridge for common functionality in filepath vs path
-type filepathPathBridge interface {
- Base(in string) string
- Clean(in string) string
- Dir(in string) string
- Ext(in string) string
- Join(elem ...string) string
- Separator() string
-}
-
-type filepathBridge struct{}
-
-func (filepathBridge) Base(in string) string {
- return filepath.Base(in)
-}
-
-func (filepathBridge) Clean(in string) string {
- return filepath.Clean(in)
-}
-
-func (filepathBridge) Dir(in string) string {
- return filepath.Dir(in)
-}
-
-func (filepathBridge) Ext(in string) string {
- return filepath.Ext(in)
-}
-
-func (filepathBridge) Join(elem ...string) string {
- return filepath.Join(elem...)
-}
-
-func (filepathBridge) Separator() string {
- return FilePathSeparator
-}
-
-var fpb filepathBridge
-
-// 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
-// and replacing hyphens with whitespace.
-func MakeTitle(inpath string) string {
- return strings.Replace(strings.TrimSpace(inpath), "-", " ", -1)
-}
-
-// ReplaceExtension takes a path and an extension, strips the old extension
-// and returns the path with the new extension.
-func ReplaceExtension(path string, newExt string) string {
- f, _ := fileAndExt(path, fpb)
- return f + "." + newExt
-}
-
-func makePathRelative(inPath string, possibleDirectories ...string) (string, error) {
- for _, currentPath := range possibleDirectories {
- if strings.HasPrefix(inPath, currentPath) {
- return strings.TrimPrefix(inPath, currentPath), nil
- }
- }
- return inPath, errors.New("can't extract relative path, unknown prefix")
-}
-
-// ExtNoDelimiter takes a path and returns the extension, excluding the delimiter, i.e. "md".
-func ExtNoDelimiter(in string) string {
- return strings.TrimPrefix(Ext(in), ".")
-}
-
-// Ext takes a path and returns the extension, including the delimiter, i.e. ".md".
-func Ext(in string) string {
- _, ext := fileAndExt(in, fpb)
- return ext
-}
-
-// PathAndExt is the same as FileAndExt, but it uses the path package.
-func PathAndExt(in string) (string, string) {
- return fileAndExt(in, pb)
-}
-
-// FileAndExt takes a path and returns the file and extension separated,
-// the extension including the delimiter, i.e. ".md".
-func FileAndExt(in string) (string, string) {
- return fileAndExt(in, fpb)
-}
-
-// FileAndExtNoDelimiter takes a path and returns the file and extension separated,
-// the extension excluding the delimiter, e.g "md".
-func FileAndExtNoDelimiter(in string) (string, string) {
- file, ext := fileAndExt(in, fpb)
- return file, strings.TrimPrefix(ext, ".")
-}
-
-// Filename takes a file path, strips out the extension,
-// and returns the name of the file.
-func Filename(in string) (name string) {
- name, _ = fileAndExt(in, fpb)
- return
-}
-
-// FileAndExt returns the filename and any extension of a file path as
-// two separate strings.
-//
-// If the path, in, contains a directory name ending in a slash,
-// then both name and ext will be empty strings.
-//
-// If the path, in, is either the current directory, the parent
-// directory or the root directory, or an empty string,
-// then both name and ext will be empty strings.
-//
-// If the path, in, represents the path of a file without an extension,
-// then name will be the name of the file and ext will be an empty string.
-//
-// If the path, in, represents a filename with an extension,
-// then name will be the filename minus any extension - including the dot
-// and ext will contain the extension - minus the dot.
-func fileAndExt(in string, b filepathPathBridge) (name string, ext string) {
- ext = b.Ext(in)
- base := b.Base(in)
-
- return extractFilename(in, ext, base, b.Separator()), ext
-}
-
-func extractFilename(in, ext, base, pathSeparator string) (name string) {
- // No file name cases. These are defined as:
- // 1. any "in" path that ends in a pathSeparator
- // 2. any "base" consisting of just an pathSeparator
- // 3. any "base" consisting of just an empty string
- // 4. any "base" consisting of just the current directory i.e. "."
- // 5. any "base" consisting of just the parent directory i.e. ".."
- if (strings.LastIndex(in, pathSeparator) == len(in)-1) || base == "" || base == "." || base == ".." || base == pathSeparator {
- name = "" // there is NO filename
- } else if ext != "" { // there was an Extension
- // return the filename minus the extension (and the ".")
- name = base[:strings.LastIndex(base, ".")]
- } else {
- // no extension case so just return base, which will
- // be the filename
- name = base
- }
- return
-}
-
-// GetRelativePath returns the relative path of a given path.
-func GetRelativePath(path, base string) (final string, err error) {
- if filepath.IsAbs(path) && base == "" {
- return "", errors.New("source: missing base directory")
- }
- name := filepath.Clean(path)
- base = filepath.Clean(base)
-
- name, err = filepath.Rel(base, name)
- if err != nil {
- return "", err
- }
-
- if strings.HasSuffix(filepath.FromSlash(path), FilePathSeparator) && !strings.HasSuffix(name, FilePathSeparator) {
- name += FilePathSeparator
- }
- return name, nil
-}
-
-func prettifyPath(in string, b filepathPathBridge) string {
- if filepath.Ext(in) == "" {
- // /section/name/ -> /section/name/index.html
- if len(in) < 2 {
- return b.Separator()
- }
- return b.Join(in, "index.html")
- }
- name, ext := fileAndExt(in, b)
- if name == "index" {
- // /section/name/index.html -> /section/name/index.html
- return b.Clean(in)
- }
- // /section/name.html -> /section/name/index.html
- return b.Join(b.Dir(in), name, "index"+ext)
-}
-
-// 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
-
- 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 s
-}
-
-// 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
- }
- }
-
- 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
- }
- }
-
- 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
-}
-
-// 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
-}
-
-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 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
deleted file mode 100644
index bc27df6c6..000000000
--- a/common/paths/path_test.go
+++ /dev/null
@@ -1,313 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package paths
-
-import (
- "path/filepath"
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestGetRelativePath(t *testing.T) {
- tests := []struct {
- path string
- base string
- expect any
- }{
- {filepath.FromSlash("/a/b"), filepath.FromSlash("/a"), filepath.FromSlash("b")},
- {filepath.FromSlash("/a/b/c/"), filepath.FromSlash("/a"), filepath.FromSlash("b/c/")},
- {filepath.FromSlash("/c"), filepath.FromSlash("/a/b"), filepath.FromSlash("../../c")},
- {filepath.FromSlash("/c"), "", false},
- }
- for i, this := range tests {
- // ultimately a fancy wrapper around filepath.Rel
- result, err := GetRelativePath(this.path, this.base)
-
- if b, ok := this.expect.(bool); ok && !b {
- if err == nil {
- t.Errorf("[%d] GetRelativePath didn't return an expected error", i)
- }
- } else {
- if err != nil {
- t.Errorf("[%d] GetRelativePath failed: %s", i, err)
- continue
- }
- if result != this.expect {
- t.Errorf("[%d] GetRelativePath got %v but expected %v", i, result, this.expect)
- }
- }
-
- }
-}
-
-func TestMakePathRelative(t *testing.T) {
- type test struct {
- inPath, path1, path2, output string
- }
-
- data := []test{
- {"/abc/bcd/ab.css", "/abc/bcd", "/bbc/bcd", "/ab.css"},
- {"/abc/bcd/ab.css", "/abcd/bcd", "/abc/bcd", "/ab.css"},
- }
-
- for i, d := range data {
- output, _ := makePathRelative(d.inPath, d.path1, d.path2)
- if d.output != output {
- t.Errorf("Test #%d failed. Expected %q got %q", i, d.output, output)
- }
- }
- _, error := makePathRelative("a/b/c.ss", "/a/c", "/d/c", "/e/f")
-
- if error == nil {
- t.Errorf("Test failed, expected error")
- }
-}
-
-func TestMakeTitle(t *testing.T) {
- type test struct {
- input, expected string
- }
- data := []test{
- {"Make-Title", "Make Title"},
- {"MakeTitle", "MakeTitle"},
- {"make_title", "make_title"},
- }
- for i, d := range data {
- output := MakeTitle(d.input)
- if d.expected != output {
- t.Errorf("Test %d failed. Expected %q got %q", i, d.expected, output)
- }
- }
-}
-
-// Replace Extension is probably poorly named, but the intent of the
-// function is to accept a path and return only the file name with a
-// new extension. It's intentionally designed to strip out the path
-// and only provide the name. We should probably rename the function to
-// be more explicit at some point.
-func TestReplaceExtension(t *testing.T) {
- type test struct {
- input, newext, expected string
- }
- data := []test{
- // These work according to the above definition
- {"/some/random/path/file.xml", "html", "file.html"},
- {"/banana.html", "xml", "banana.xml"},
- {"./banana.html", "xml", "banana.xml"},
- {"banana/pie/index.html", "xml", "index.xml"},
- {"../pies/fish/index.html", "xml", "index.xml"},
- // but these all fail
- {"filename-without-an-ext", "ext", "filename-without-an-ext.ext"},
- {"/filename-without-an-ext", "ext", "filename-without-an-ext.ext"},
- {"/directory/mydir/", "ext", ".ext"},
- {"mydir/", "ext", ".ext"},
- }
-
- for i, d := range data {
- output := ReplaceExtension(filepath.FromSlash(d.input), d.newext)
- if d.expected != output {
- t.Errorf("Test %d failed. Expected %q got %q", i, d.expected, output)
- }
- }
-}
-
-func TestExtNoDelimiter(t *testing.T) {
- c := qt.New(t)
- c.Assert(ExtNoDelimiter(filepath.FromSlash("/my/data.json")), qt.Equals, "json")
-}
-
-func TestFilename(t *testing.T) {
- type test struct {
- input, expected string
- }
- data := []test{
- {"index.html", "index"},
- {"./index.html", "index"},
- {"/index.html", "index"},
- {"index", "index"},
- {"/tmp/index.html", "index"},
- {"./filename-no-ext", "filename-no-ext"},
- {"/filename-no-ext", "filename-no-ext"},
- {"filename-no-ext", "filename-no-ext"},
- {"directory/", ""}, // no filename case??
- {"directory/.hidden.ext", ".hidden"},
- {"./directory/../~/banana/gold.fish", "gold"},
- {"../directory/banana.man", "banana"},
- {"~/mydir/filename.ext", "filename"},
- {"./directory//tmp/filename.ext", "filename"},
- }
-
- for i, d := range data {
- output := Filename(filepath.FromSlash(d.input))
- if d.expected != output {
- t.Errorf("Test %d failed. Expected %q got %q", i, d.expected, output)
- }
- }
-}
-
-func TestFileAndExt(t *testing.T) {
- type test struct {
- input, expectedFile, expectedExt string
- }
- data := []test{
- {"index.html", "index", ".html"},
- {"./index.html", "index", ".html"},
- {"/index.html", "index", ".html"},
- {"index", "index", ""},
- {"/tmp/index.html", "index", ".html"},
- {"./filename-no-ext", "filename-no-ext", ""},
- {"/filename-no-ext", "filename-no-ext", ""},
- {"filename-no-ext", "filename-no-ext", ""},
- {"directory/", "", ""}, // no filename case??
- {"directory/.hidden.ext", ".hidden", ".ext"},
- {"./directory/../~/banana/gold.fish", "gold", ".fish"},
- {"../directory/banana.man", "banana", ".man"},
- {"~/mydir/filename.ext", "filename", ".ext"},
- {"./directory//tmp/filename.ext", "filename", ".ext"},
- }
-
- for i, d := range data {
- file, ext := fileAndExt(filepath.FromSlash(d.input), fpb)
- if d.expectedFile != file {
- t.Errorf("Test %d failed. Expected filename %q got %q.", i, d.expectedFile, file)
- }
- if d.expectedExt != ext {
- t.Errorf("Test %d failed. Expected extension %q got %q.", i, d.expectedExt, ext)
- }
- }
-}
-
-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
-
- }
-
- for _, test := range tests {
- c.Assert(Sanitize(test.input), qt.Equals, test.expected)
- }
-}
-
-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 i := 0; i < b.N; i++ {
- got := Sanitize(allAlowedPath)
- if got != allAlowedPath {
- b.Fatal(got)
- }
- }
- })
-
- // This will allocate some memory.
- b.Run("Spaces", func(b *testing.B) {
- for i := 0; i < b.N; i++ {
- 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))
- }
-}
diff --git a/common/paths/pathparser.go b/common/paths/pathparser.go
deleted file mode 100644
index 1cae710e8..000000000
--- a/common/paths/pathparser.go
+++ /dev/null
@@ -1,788 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package paths
-
-import (
- "path"
- "path/filepath"
- "runtime"
- "strings"
- "sync"
-
- "github.com/gohugoio/hugo/common/types"
- "github.com/gohugoio/hugo/hugofs/files"
- "github.com/gohugoio/hugo/identity"
- "github.com/gohugoio/hugo/resources/kinds"
-)
-
-const (
- identifierBaseof = "baseof"
-)
-
-// PathParser parses a path into a Path.
-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
-}
-
-// 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
-}
-
-// 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 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 {
- 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 resource, 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
- 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.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 {
- 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 {
- return p.base(!p.isContentPage(), p.IsBundle())
-}
-
-// 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) 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
-}
diff --git a/common/paths/pathparser_test.go b/common/paths/pathparser_test.go
deleted file mode 100644
index b1734aef2..000000000
--- a/common/paths/pathparser_test.go
+++ /dev/null
@@ -1,611 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package paths
-
-import (
- "path/filepath"
- "testing"
-
- "github.com/gohugoio/hugo/hugofs/files"
- "github.com/gohugoio/hugo/resources/kinds"
-
- qt "github.com/frankban/quicktest"
-)
-
-var testParser = &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
- },
-}
-
-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)
- },
- },
- }
- for _, test := range tests {
- c.Run(test.name, func(c *qt.C) {
- if test.name != "Home branch cundle" {
- // return
- }
- test.assert(c, testParser.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", "list", "mylayout"})
- c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{})
- 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", "not", "amp", "no", "list"})
- 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")
- },
- },
- }
-
- for _, test := range tests {
- c.Run(test.name, func(c *qt.C) {
- if test.name != "Shortcode lang layout" {
- // return
- }
- test.assert(c, testParser.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) {
- for i := 0; i < b.N; i++ {
- testParser.ParseIdentity(files.ComponentFolderAssets, "/a/b.css")
- }
-}
diff --git a/common/paths/paths_integration_test.go b/common/paths/paths_integration_test.go
deleted file mode 100644
index f5ea3066a..000000000
--- a/common/paths/paths_integration_test.go
+++ /dev/null
@@ -1,103 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package 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/_default/single.html --
-{{ .Language.Lang }}|Single.
--- layouts/_default/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/_default/single.html --
-{{ .Language.Lang }}|Single.
--- layouts/_default/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
deleted file mode 100644
index 08fbcc835..000000000
--- a/common/paths/type_string.go
+++ /dev/null
@@ -1,32 +0,0 @@
-// 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
deleted file mode 100644
index 1d1408b51..000000000
--- a/common/paths/url.go
+++ /dev/null
@@ -1,273 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package paths
-
-import (
- "fmt"
- "net/url"
- "path"
- "path/filepath"
- "runtime"
- "strings"
-)
-
-type pathBridge struct{}
-
-func (pathBridge) Base(in string) string {
- return path.Base(in)
-}
-
-func (pathBridge) Clean(in string) string {
- return path.Clean(in)
-}
-
-func (pathBridge) Dir(in string) string {
- return path.Dir(in)
-}
-
-func (pathBridge) Ext(in string) string {
- return path.Ext(in)
-}
-
-func (pathBridge) Join(elem ...string) string {
- return path.Join(elem...)
-}
-
-func (pathBridge) Separator() string {
- return "/"
-}
-
-var pb pathBridge
-
-// 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
-func MakePermalink(host, plink string) *url.URL {
- base, err := url.Parse(host)
- if err != nil {
- panic(err)
- }
-
- p, err := url.Parse(plink)
- if err != nil {
- panic(err)
- }
-
- if p.Host != "" {
- panic(fmt.Errorf("can't make permalink from absolute link %q", plink))
- }
-
- 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, "/")
- if hadTrailingSlash && !strings.HasSuffix(base.Path, "/") {
- base.Path = base.Path + "/"
- }
-
- return base
-}
-
-// 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.
-func AddContextRoot(baseURL, relativePath string) string {
- url, err := url.Parse(baseURL)
- if err != nil {
- panic(err)
- }
-
- newPath := path.Join(url.Path, relativePath)
-
- // path strips trailing slash, ignore root path.
- if newPath != "/" && strings.HasSuffix(relativePath, "/") {
- newPath += "/"
- }
- return newPath
-}
-
-// URLizeAn
-
-// PrettifyURL takes a URL string and returns a semantic, clean URL.
-func PrettifyURL(in string) string {
- x := PrettifyURLPath(in)
-
- if path.Base(x) == "index.html" {
- return path.Dir(x)
- }
-
- if in == "" {
- return "/"
- }
-
- return x
-}
-
-// 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
-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
-func Uglify(in string) string {
- if path.Ext(in) == "" {
- if len(in) < 2 {
- return "/"
- }
- // /section/name/ -> /section/name.html
- return path.Clean(in) + ".html"
- }
-
- name, ext := fileAndExt(in, pb)
- if name == "index" {
- // /section/name/index.html -> /section/name.html
- d := path.Dir(in)
- if len(d) > 1 {
- return d + ext
- }
- return in
- }
- // /.xml -> /index.xml
- if name == "" {
- return path.Dir(in) + "index" + ext
- }
- // /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
deleted file mode 100644
index 5a9233c26..000000000
--- a/common/paths/url_test.go
+++ /dev/null
@@ -1,100 +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 paths
-
-import (
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestMakePermalink(t *testing.T) {
- type test struct {
- host, link, output string
- }
-
- data := []test{
- {"http://abc.com/foo", "post/bar", "http://abc.com/foo/post/bar"},
- {"http://abc.com/foo/", "post/bar", "http://abc.com/foo/post/bar"},
- {"http://abc.com", "post/bar", "http://abc.com/post/bar"},
- {"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 {
- output := MakePermalink(d.host, d.link).String()
- if d.output != output {
- t.Errorf("Test #%d failed. Expected %q got %q", i, d.output, output)
- }
- }
-}
-
-func TestAddContextRoot(t *testing.T) {
- tests := []struct {
- baseURL string
- url string
- expected string
- }{
- {"http://example.com/sub/", "/foo", "/sub/foo"},
- {"http://example.com/sub/", "/foo/index.html", "/sub/foo/index.html"},
- {"http://example.com/sub1/sub2", "/foo", "/sub1/sub2/foo"},
- {"http://example.com", "/foo", "/foo"},
- // cannot guess that the context root is already added int the example below
- {"http://example.com/sub/", "/sub/foo", "/sub/sub/foo"},
- {"http://example.com/тря", "/трям/", "/тря/трям/"},
- {"http://example.com", "/", "/"},
- {"http://example.com/bar", "//", "/bar/"},
- }
-
- for _, test := range tests {
- output := AddContextRoot(test.baseURL, test.url)
- if output != test.expected {
- t.Errorf("Expected %#v, got %#v\n", test.expected, output)
- }
- }
-}
-
-func TestPretty(t *testing.T) {
- c := qt.New(t)
- c.Assert("/section/name/index.html", qt.Equals, PrettifyURLPath("/section/name.html"))
- c.Assert("/section/sub/name/index.html", qt.Equals, PrettifyURLPath("/section/sub/name.html"))
- c.Assert("/section/name/index.html", qt.Equals, PrettifyURLPath("/section/name/"))
- c.Assert("/section/name/index.html", qt.Equals, PrettifyURLPath("/section/name/index.html"))
- c.Assert("/index.html", qt.Equals, PrettifyURLPath("/index.html"))
- c.Assert("/name/index.xml", qt.Equals, PrettifyURLPath("/name.xml"))
- c.Assert("/", qt.Equals, PrettifyURLPath("/"))
- c.Assert("/", qt.Equals, PrettifyURLPath(""))
- c.Assert("/section/name", qt.Equals, PrettifyURL("/section/name.html"))
- c.Assert("/section/sub/name", qt.Equals, PrettifyURL("/section/sub/name.html"))
- c.Assert("/section/name", qt.Equals, PrettifyURL("/section/name/"))
- c.Assert("/section/name", qt.Equals, PrettifyURL("/section/name/index.html"))
- c.Assert("/", qt.Equals, PrettifyURL("/index.html"))
- c.Assert("/name/index.xml", qt.Equals, PrettifyURL("/name.xml"))
- c.Assert("/", qt.Equals, PrettifyURL("/"))
- c.Assert("/", qt.Equals, PrettifyURL(""))
-}
-
-func TestUgly(t *testing.T) {
- c := qt.New(t)
- c.Assert("/section/name.html", qt.Equals, Uglify("/section/name.html"))
- c.Assert("/section/sub/name.html", qt.Equals, Uglify("/section/sub/name.html"))
- c.Assert("/section/name.html", qt.Equals, Uglify("/section/name/"))
- c.Assert("/section/name.html", qt.Equals, Uglify("/section/name/index.html"))
- c.Assert("/index.html", qt.Equals, Uglify("/index.html"))
- c.Assert("/name.xml", qt.Equals, Uglify("/name.xml"))
- c.Assert("/", qt.Equals, Uglify("/"))
- c.Assert("/", qt.Equals, Uglify(""))
-}
diff --git a/common/predicate/predicate.go b/common/predicate/predicate.go
deleted file mode 100644
index f71536474..000000000
--- a/common/predicate/predicate.go
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package predicate
-
-// P is a predicate function that tests whether a value of type T satisfies some condition.
-type P[T any] func(T) bool
-
-// And returns a predicate that is a short-circuiting logical AND of this and the given predicates.
-func (p P[T]) And(ps ...P[T]) P[T] {
- return func(v T) bool {
- for _, pp := range ps {
- if !pp(v) {
- return false
- }
- }
- if p == nil {
- return true
- }
- return p(v)
- }
-}
-
-// Or returns a predicate that is a short-circuiting logical OR of this and the given predicates.
-func (p P[T]) Or(ps ...P[T]) P[T] {
- return func(v T) bool {
- for _, pp := range ps {
- if pp(v) {
- return true
- }
- }
- if p == nil {
- return false
- }
- return p(v)
- }
-}
-
-// Negate returns a predicate that is a logical negation of this predicate.
-func (p P[T]) Negate() P[T] {
- return func(v T) bool {
- return !p(v)
- }
-}
-
-// 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 P[T]) Filter(s []T) []T {
- var n int
- for _, v := range s {
- if p(v) {
- s[n] = v
- n++
- }
- }
- return s[:n]
-}
-
-// FilterCopy returns a new slice holding only the elements of s that satisfy p.
-func (p P[T]) FilterCopy(s []T) []T {
- var result []T
- for _, v := range s {
- if p(v) {
- result = append(result, v)
- }
- }
- return result
-}
diff --git a/common/predicate/predicate_test.go b/common/predicate/predicate_test.go
deleted file mode 100644
index 1e1ec004b..000000000
--- a/common/predicate/predicate_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package predicate_test
-
-import (
- "testing"
-
- qt "github.com/frankban/quicktest"
- "github.com/gohugoio/hugo/common/predicate"
-)
-
-func TestAdd(t *testing.T) {
- c := qt.New(t)
-
- var p predicate.P[int] = intP1
-
- c.Assert(p(1), qt.IsTrue)
- c.Assert(p(2), qt.IsFalse)
-
- neg := p.Negate()
- c.Assert(neg(1), qt.IsFalse)
- c.Assert(neg(2), qt.IsTrue)
-
- and := p.And(intP2)
- c.Assert(and(1), qt.IsFalse)
- c.Assert(and(2), qt.IsFalse)
- c.Assert(and(10), qt.IsTrue)
-
- or := p.Or(intP2)
- c.Assert(or(1), qt.IsTrue)
- c.Assert(or(2), qt.IsTrue)
- c.Assert(or(10), qt.IsTrue)
- c.Assert(or(11), qt.IsFalse)
-}
-
-func TestFilter(t *testing.T) {
- c := qt.New(t)
-
- var p predicate.P[int] = intP1
- p = p.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.P[int] = intP1
- p = p.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) bool {
- if i == 10 {
- return true
- }
- return i == 1
-}
-
-var intP2 = func(i int) bool {
- if i == 10 {
- return true
- }
- return i == 2
-}
diff --git a/common/rungroup/rungroup.go b/common/rungroup/rungroup.go
deleted file mode 100644
index 80a730ca9..000000000
--- a/common/rungroup/rungroup.go
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package 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
deleted file mode 100644
index ac902079e..000000000
--- a/common/rungroup/rungroup_test.go
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package 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
deleted file mode 100644
index 3f8a754e9..000000000
--- a/common/tasks/tasks.go
+++ /dev/null
@@ -1,150 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package tasks
-
-import (
- "sync"
- "time"
-)
-
-// RunEvery runs a function at intervals defined by the function itself.
-// Functions can be added and removed while running.
-type RunEvery struct {
- // Any error returned from the function will be passed to this function.
- HandleError func(string, error)
-
- // If set, the function will be run immediately.
- RunImmediately bool
-
- // The named functions to run.
- funcs map[string]*Func
-
- mu sync.Mutex
- started bool
- closed bool
- quit chan struct{}
-}
-
-type Func struct {
- // The shortest interval between each run.
- IntervalLow time.Duration
-
- // The longest interval between each run.
- IntervalHigh time.Duration
-
- // The function to run.
- F func(interval time.Duration) (time.Duration, error)
-
- interval time.Duration
- last time.Time
-}
-
-func (r *RunEvery) Start() error {
- if r.started {
- return nil
- }
-
- r.started = true
- r.quit = make(chan struct{})
-
- go func() {
- if r.RunImmediately {
- r.run()
- }
- ticker := time.NewTicker(500 * time.Millisecond)
- defer ticker.Stop()
- for {
- select {
- case <-r.quit:
- return
- case <-ticker.C:
- r.run()
- }
- }
- }()
-
- return nil
-}
-
-// Close stops the RunEvery from running.
-func (r *RunEvery) Close() error {
- if r.closed {
- return nil
- }
- r.closed = true
- if r.quit != nil {
- close(r.quit)
- }
- return nil
-}
-
-// Add adds a function to the RunEvery.
-func (r *RunEvery) Add(name string, f Func) {
- r.mu.Lock()
- defer r.mu.Unlock()
- if r.funcs == nil {
- r.funcs = make(map[string]*Func)
- }
- if f.IntervalLow == 0 {
- f.IntervalLow = 500 * time.Millisecond
- }
- if f.IntervalHigh <= f.IntervalLow {
- f.IntervalHigh = 20 * time.Second
- }
-
- start := 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
deleted file mode 100644
index fef6efce8..000000000
--- a/common/terminal/colors.go
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package terminal contains helper for the terminal, such as coloring output.
-package terminal
-
-import (
- "fmt"
- "os"
- "strings"
-
- isatty "github.com/mattn/go-isatty"
-)
-
-const (
- errorColor = "\033[1;31m%s\033[0m"
- warningColor = "\033[0;33m%s\033[0m"
- noticeColor = "\033[1;36m%s\033[0m"
-)
-
-// 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))
-}
-
-// Notice colorizes the string in a noticeable color.
-func Notice(s string) string {
- return colorize(s, noticeColor)
-}
-
-// Error colorizes the string in a colour that grabs attention.
-func Error(s string) string {
- return colorize(s, errorColor)
-}
-
-// Warning colorizes the string in a colour that warns.
-func Warning(s string) string {
- return colorize(s, warningColor)
-}
-
-// colorize s in color.
-func colorize(s, color string) string {
- s = fmt.Sprintf(color, doublePercent(s))
- return singlePercent(s)
-}
-
-func doublePercent(str string) string {
- return strings.Replace(str, "%", "%%", -1)
-}
-
-func singlePercent(str string) string {
- return strings.Replace(str, "%%", "%", -1)
-}
diff --git a/common/text/position.go b/common/text/position.go
deleted file mode 100644
index eb9de5624..000000000
--- a/common/text/position.go
+++ /dev/null
@@ -1,100 +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 text
-
-import (
- "fmt"
- "os"
- "strings"
-
- "github.com/gohugoio/hugo/common/terminal"
-)
-
-// 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
-}
-
-// Position holds a source position in a text file or stream.
-type Position struct {
- Filename string // filename, if any
- Offset int // byte offset, starting at 0. It's set to -1 if not provided.
- LineNumber int // line number, starting at 1
- ColumnNumber int // column number, starting at 1 (character count per line)
-}
-
-func (pos Position) String() string {
- if pos.Filename == "" {
- pos.Filename = ""
- }
- return positionStringFormatfunc(pos)
-}
-
-// IsValid returns true if line number is > 0.
-func (pos Position) IsValid() bool {
- return pos.LineNumber > 0
-}
-
-var positionStringFormatfunc func(p Position) string
-
-func createPositionStringFormatter(formatStr string) func(p Position) string {
- if formatStr == "" {
- formatStr = "\":file::line::col\""
- }
-
- identifiers := []string{":file", ":line", ":col"}
- var identifiersFound []string
-
- for i := range formatStr {
- for _, id := range identifiers {
- if strings.HasPrefix(formatStr[i:], id) {
- identifiersFound = append(identifiersFound, id)
- }
- }
- }
-
- replacer := strings.NewReplacer(":file", "%s", ":line", "%d", ":col", "%d")
- format := replacer.Replace(formatStr)
-
- f := func(pos Position) string {
- args := make([]any, len(identifiersFound))
- for i, id := range identifiersFound {
- switch id {
- case ":file":
- args[i] = pos.Filename
- case ":line":
- args[i] = pos.LineNumber
- case ":col":
- args[i] = pos.ColumnNumber
- }
- }
-
- msg := fmt.Sprintf(format, args...)
-
- if terminal.PrintANSIColors(os.Stdout) {
- return terminal.Notice(msg)
- }
-
- return msg
- }
-
- return f
-}
-
-func init() {
- positionStringFormatfunc = createPositionStringFormatter(os.Getenv("HUGO_FILE_LOG_FORMAT"))
-}
diff --git a/common/text/position_test.go b/common/text/position_test.go
deleted file mode 100644
index a1f43c5d4..000000000
--- a/common/text/position_test.go
+++ /dev/null
@@ -1,32 +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 text
-
-import (
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestPositionStringFormatter(t *testing.T) {
- c := qt.New(t)
-
- pos := Position{Filename: "/my/file.txt", LineNumber: 12, ColumnNumber: 13, Offset: 14}
-
- c.Assert(createPositionStringFormatter(":file|:col|:line")(pos), qt.Equals, "/my/file.txt|13|12")
- c.Assert(createPositionStringFormatter(":col|:file|:line")(pos), qt.Equals, "13|/my/file.txt|12")
- c.Assert(createPositionStringFormatter("好::col")(pos), qt.Equals, "好:13")
- c.Assert(createPositionStringFormatter("")(pos), qt.Equals, "\"/my/file.txt:12:13\"")
- c.Assert(pos.String(), qt.Equals, "\"/my/file.txt:12:13\"")
-}
diff --git a/common/text/transform.go b/common/text/transform.go
deleted file mode 100644
index de093af0d..000000000
--- a/common/text/transform.go
+++ /dev/null
@@ -1,78 +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 text
-
-import (
- "strings"
- "sync"
- "unicode"
-
- "golang.org/x/text/runes"
- "golang.org/x/text/transform"
- "golang.org/x/text/unicode/norm"
-)
-
-var accentTransformerPool = &sync.Pool{
- New: func() any {
- return transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC)
- },
-}
-
-// RemoveAccents removes all accents from b.
-func RemoveAccents(b []byte) []byte {
- t := accentTransformerPool.Get().(transform.Transformer)
- b, _, _ = transform.Bytes(t, b)
- t.Reset()
- accentTransformerPool.Put(t)
- return b
-}
-
-// RemoveAccentsString removes all accents from s.
-func RemoveAccentsString(s string) string {
- t := accentTransformerPool.Get().(transform.Transformer)
- s, _, _ = transform.String(t, s)
- t.Reset()
- 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
deleted file mode 100644
index 74bb37783..000000000
--- a/common/text/transform_test.go
+++ /dev/null
@@ -1,72 +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 text
-
-import (
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestRemoveAccents(t *testing.T) {
- c := qt.New(t)
-
- c.Assert(string(RemoveAccents([]byte("Resumé"))), qt.Equals, "Resume")
- 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 i := 0; i < b.N; i++ {
- VisitLinesAfter(lines, func(s string) {
- })
- }
-}
diff --git a/common/types/closer.go b/common/types/closer.go
deleted file mode 100644
index 9f8875a8a..000000000
--- a/common/types/closer.go
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package types
-
-import "sync"
-
-type Closer interface {
- Close() error
-}
-
-// 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
deleted file mode 100644
index 6b1750376..000000000
--- a/common/types/convert.go
+++ /dev/null
@@ -1,129 +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 types
-
-import (
- "encoding/json"
- "fmt"
- "html/template"
- "reflect"
- "time"
-
- "github.com/spf13/cast"
-)
-
-// ToDuration converts v to time.Duration.
-// See ToDurationE if you need to handle errors.
-func ToDuration(v any) time.Duration {
- d, _ := ToDurationE(v)
- return d
-}
-
-// ToDurationE converts v to time.Duration.
-func ToDurationE(v any) (time.Duration, error) {
- if n := cast.ToInt(v); n > 0 {
- return time.Duration(n) * time.Millisecond, nil
- }
- d, err := time.ParseDuration(cast.ToString(v))
- if err != nil {
- return 0, fmt.Errorf("cannot convert %v to time.Duration", v)
- }
- return d, nil
-}
-
-// ToStringSlicePreserveString is the same as ToStringSlicePreserveStringE,
-// but it never fails.
-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 any) ([]string, error) {
- if v == nil {
- return nil, nil
- }
- if sds, ok := v.(string); ok {
- return []string{sds}, nil
- }
- result, err := cast.ToStringSliceE(v)
- if err == nil {
- return result, nil
- }
-
- // Probably []int or similar. Fall back to reflect.
- vv := reflect.ValueOf(v)
-
- switch vv.Kind() {
- case reflect.Slice, reflect.Array:
- result = make([]string, vv.Len())
- for i := range vv.Len() {
- s, err := cast.ToStringE(vv.Index(i).Interface())
- if err != nil {
- return nil, err
- }
- result[i] = s
- }
- return result, nil
- 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 any) (string, bool) {
- switch s := v.(type) {
- case string:
- return s, true
- case template.HTML:
- return string(s), true
- case template.CSS:
- return string(s), true
- case template.HTMLAttr:
- return string(s), true
- case template.JS:
- return string(s), true
- case template.JSStr:
- return string(s), true
- case template.URL:
- return string(s), true
- case template.Srcset:
- return string(s), true
- }
-
- return "", false
-}
-
-// ToString converts v to a string.
-func ToString(v any) string {
- s, _ := ToStringE(v)
- return s
-}
-
-// ToStringE converts v to a string.
-func ToStringE(v any) (string, error) {
- if s, ok := TypeToString(v); ok {
- return s, nil
- }
-
- switch s := v.(type) {
- case json.RawMessage:
- return string(s), nil
- default:
- return cast.ToStringE(v)
- }
-}
diff --git a/common/types/convert_test.go b/common/types/convert_test.go
deleted file mode 100644
index 13059285d..000000000
--- a/common/types/convert_test.go
+++ /dev/null
@@ -1,48 +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 types
-
-import (
- "encoding/json"
- "testing"
- "time"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestToStringSlicePreserveString(t *testing.T) {
- c := qt.New(t)
-
- c.Assert(ToStringSlicePreserveString("Hugo"), qt.DeepEquals, []string{"Hugo"})
- c.Assert(ToStringSlicePreserveString(qt.Commentf("Hugo")), qt.DeepEquals, []string{"Hugo"})
- 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)
-}
-
-func TestToString(t *testing.T) {
- c := qt.New(t)
-
- c.Assert(ToString([]byte("Hugo")), qt.Equals, "Hugo")
- c.Assert(ToString(json.RawMessage("Hugo")), qt.Equals, "Hugo")
-}
-
-func TestToDuration(t *testing.T) {
- c := qt.New(t)
-
- c.Assert(ToDuration("200ms"), qt.Equals, 200*time.Millisecond)
- 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
deleted file mode 100644
index 061acfe64..000000000
--- a/common/types/css/csstypes.go
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package 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
deleted file mode 100644
index a335be3b2..000000000
--- a/common/types/evictingqueue.go
+++ /dev/null
@@ -1,114 +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 types contains types shared between packages in Hugo.
-package types
-
-import (
- "slices"
- "sync"
-)
-
-// 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.
-type EvictingQueue[T comparable] struct {
- size int
- vals []T
- set map[T]bool
- mu sync.Mutex
- zero T
-}
-
-// 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 *EvictingQueue[T]) Add(v T) *EvictingQueue[T] {
- q.mu.Lock()
- if q.set[v] {
- q.mu.Unlock()
- return q
- }
-
- if len(q.set) == q.size {
- // Full
- delete(q.set, q.vals[0])
- 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 *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 *EvictingQueue[T]) Peek() T {
- q.mu.Lock()
- l := len(q.vals)
- if l == 0 {
- q.mu.Unlock()
- return q.zero
- }
- elem := q.vals[l-1]
- q.mu.Unlock()
- return elem
-}
-
-// PeekAll looks at all the elements in the queue, with the newest first.
-func (q *EvictingQueue[T]) PeekAll() []T {
- if q == nil {
- return nil
- }
- q.mu.Lock()
- 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 {
- vals[i], vals[j] = vals[j], vals[i]
- }
- return vals
-}
-
-// PeekAllSet returns PeekAll as a set.
-func (q *EvictingQueue[T]) PeekAllSet() map[T]bool {
- all := q.PeekAll()
- set := make(map[T]bool)
- for _, v := range all {
- set[v] = true
- }
-
- return set
-}
diff --git a/common/types/evictingqueue_test.go b/common/types/evictingqueue_test.go
deleted file mode 100644
index b93243f3c..000000000
--- a/common/types/evictingqueue_test.go
+++ /dev/null
@@ -1,74 +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 types
-
-import (
- "sync"
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestEvictingStringQueue(t *testing.T) {
- c := qt.New(t)
-
- queue := NewEvictingQueue[string](3)
-
- c.Assert(queue.Peek(), qt.Equals, "")
- queue.Add("a")
- queue.Add("b")
- queue.Add("a")
- c.Assert(queue.Peek(), qt.Equals, "b")
- queue.Add("b")
- c.Assert(queue.Peek(), qt.Equals, "b")
-
- queue.Add("a")
- queue.Add("b")
-
- c.Assert(queue.Contains("a"), qt.Equals, true)
- c.Assert(queue.Contains("foo"), qt.Equals, false)
-
- c.Assert(queue.PeekAll(), qt.DeepEquals, []string{"b", "a"})
- c.Assert(queue.Peek(), qt.Equals, "b")
- queue.Add("c")
- queue.Add("d")
- // Overflowed, a should now be removed.
- c.Assert(queue.PeekAll(), qt.DeepEquals, []string{"d", "c", "b"})
- c.Assert(len(queue.PeekAllSet()), qt.Equals, 3)
- c.Assert(queue.PeekAllSet()["c"], qt.Equals, true)
-}
-
-func TestEvictingStringQueueConcurrent(t *testing.T) {
- var wg sync.WaitGroup
- val := "someval"
-
- queue := NewEvictingQueue[string](3)
-
- for range 100 {
- wg.Add(1)
- go func() {
- defer wg.Done()
- queue.Add(val)
- v := queue.Peek()
- if v != val {
- t.Error("wrong val")
- }
- vals := queue.PeekAll()
- 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
deleted file mode 100644
index 53ce2068f..000000000
--- a/common/types/hstring/stringtypes.go
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package 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
deleted file mode 100644
index 05e2c22b9..000000000
--- a/common/types/hstring/stringtypes_test.go
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package 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
deleted file mode 100644
index 7e94c1eea..000000000
--- a/common/types/types.go
+++ /dev/null
@@ -1,145 +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 types contains types shared between packages in Hugo.
-package types
-
-import (
- "fmt"
- "reflect"
- "sync/atomic"
-
- "github.com/spf13/cast"
-)
-
-// RLocker represents the read locks in sync.RWMutex.
-type RLocker interface {
- RLock()
- RUnlock()
-}
-
-type Locker interface {
- Lock()
- Unlock()
-}
-
-type RWLocker interface {
- RLocker
- Locker
-}
-
-// KeyValue is a interface{} tuple.
-type KeyValue struct {
- Key any
- Value any
-}
-
-// KeyValueStr is a string tuple.
-type KeyValueStr struct {
- Key string
- Value string
-}
-
-// KeyValues holds an key and a slice of values.
-type KeyValues struct {
- Key any
- Values []any
-}
-
-// KeyString returns the key as a string, an empty string if conversion fails.
-func (k KeyValues) KeyString() string {
- return cast.ToString(k.Key)
-}
-
-func (k KeyValues) String() string {
- return fmt.Sprintf("%v: %v", k.Key, k.Values)
-}
-
-// NewKeyValuesStrings takes a given key and slice of values and returns a new
-// KeyValues struct.
-func NewKeyValuesStrings(key string, values ...string) KeyValues {
- iv := make([]any, len(values))
- for i := range values {
- iv[i] = values[i]
- }
- return KeyValues{Key: key, Values: iv}
-}
-
-// Zeroer, as implemented by time.Time, will be used by the truth template
-// funcs in Hugo (if, with, not, and, or).
-type Zeroer interface {
- IsZero() bool
-}
-
-// IsNil reports whether v is nil.
-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:
- return value.IsNil()
- }
-
- return false
-}
-
-// DevMarker is a marker interface for types that should only be used during
-// development.
-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
-}
-
-// PrintableValueProvider is implemented by types that can provide a printable value.
-type PrintableValueProvider interface {
- PrintableValue() any
-}
diff --git a/common/types/types_test.go b/common/types/types_test.go
deleted file mode 100644
index 795733047..000000000
--- a/common/types/types_test.go
+++ /dev/null
@@ -1,51 +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 types
-
-import (
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestKeyValues(t *testing.T) {
- c := qt.New(t)
-
- kv := NewKeyValuesStrings("key", "a1", "a2")
-
- c.Assert(kv.KeyString(), qt.Equals, "key")
- c.Assert(kv.Values, qt.DeepEquals, []any{"a1", "a2"})
-}
-
-func TestLowHigh(t *testing.T) {
- c := qt.New(t)
-
- lh := LowHigh[string]{
- Low: 2,
- High: 10,
- }
-
- s := "abcdefghijklmnopqrstuvwxyz"
- c.Assert(lh.IsZero(), qt.IsFalse)
- c.Assert(lh.Value(s), qt.Equals, "cdefghij")
-
- lhb := LowHigh[[]byte]{
- Low: 2,
- High: 10,
- }
-
- sb := []byte(s)
- c.Assert(lhb.IsZero(), qt.IsFalse)
- c.Assert(lhb.Value(sb), qt.DeepEquals, []byte("cdefghij"))
-}
diff --git a/common/urls/baseURL.go b/common/urls/baseURL.go
deleted file mode 100644
index 2958a2a04..000000000
--- a/common/urls/baseURL.go
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package 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
deleted file mode 100644
index ba337aac8..000000000
--- a/common/urls/baseURL_test.go
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package 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
deleted file mode 100644
index e5804a279..000000000
--- a/common/urls/ref.go
+++ /dev/null
@@ -1,22 +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 urls
-
-// RefLinker is implemented by those who support reference linking.
-// args must contain a path, but can also point to the target
-// language or output format.
-type RefLinker interface {
- Ref(args map[string]any) (string, error)
- RelRef(args map[string]any) (string, error)
-}
diff --git a/compare/compare.go b/compare/compare.go
deleted file mode 100644
index fd15bd087..000000000
--- a/compare/compare.go
+++ /dev/null
@@ -1,67 +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 compare
-
-// Eqer can be used to determine if this value is equal to the other.
-// The semantics of equals is that the two value are interchangeable
-// in the Hugo templates.
-type Eqer interface {
- // 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 {
- // For internal use.
- ProbablyEq(other any) bool
-}
-
-// Comparer can be used to compare two values.
-// This will be used when using the le, ge etc. operators in the templates.
-// 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 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.go b/compare/compare_strings.go
deleted file mode 100644
index 1fd954081..000000000
--- a/compare/compare_strings.go
+++ /dev/null
@@ -1,113 +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 compare
-
-import (
- "strings"
- "unicode"
- "unicode/utf8"
-)
-
-// Strings returns an integer comparing two strings lexicographically.
-func Strings(s, t string) int {
- c := compareFold(s, t)
-
- if c == 0 {
- // "B" and "b" would be the same so we need a tiebreaker.
- return strings.Compare(s, t)
- }
-
- return c
-}
-
-// This function is derived from strings.EqualFold in Go's stdlib.
-// https://github.com/golang/go/blob/ad4a58e31501bce5de2aad90a620eaecdc1eecb8/src/strings/strings.go#L893
-func compareFold(s, t string) int {
- for s != "" && t != "" {
- var sr, tr rune
- if s[0] < utf8.RuneSelf {
- sr, s = rune(s[0]), s[1:]
- } else {
- r, size := utf8.DecodeRuneInString(s)
- sr, s = r, s[size:]
- }
- if t[0] < utf8.RuneSelf {
- tr, t = rune(t[0]), t[1:]
- } else {
- r, size := utf8.DecodeRuneInString(t)
- tr, t = r, t[size:]
- }
-
- if tr == sr {
- continue
- }
-
- c := 1
- if tr < sr {
- tr, sr = sr, tr
- c = -c
- }
-
- // ASCII only.
- if tr < utf8.RuneSelf {
- if sr >= 'A' && sr <= 'Z' {
- if tr <= 'Z' {
- // Same case.
- return -c
- }
-
- diff := tr - (sr + 'a' - 'A')
-
- if diff == 0 {
- continue
- }
-
- if diff < 0 {
- return c
- }
-
- if diff > 0 {
- return -c
- }
- }
- }
-
- // Unicode.
- r := unicode.SimpleFold(sr)
- for r != sr && r < tr {
- r = unicode.SimpleFold(r)
- }
-
- if r == tr {
- continue
- }
-
- return -c
- }
-
- if s == "" && t == "" {
- return 0
- }
-
- if s == "" {
- return -1
- }
-
- return 1
-}
-
-// LessStrings returns whether s is less than t lexicographically.
-func LessStrings(s, t string) bool {
- return Strings(s, t) < 0
-}
diff --git a/compare/compare_strings_test.go b/compare/compare_strings_test.go
deleted file mode 100644
index 1a5bb0b1a..000000000
--- a/compare/compare_strings_test.go
+++ /dev/null
@@ -1,82 +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 compare
-
-import (
- "sort"
- "strings"
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestCompare(t *testing.T) {
- c := qt.New(t)
- for _, test := range []struct {
- a string
- b string
- }{
- {"a", "a"},
- {"A", "a"},
- {"Ab", "Ac"},
- {"az", "Za"},
- {"C", "D"},
- {"B", "a"},
- {"C", ""},
- {"", ""},
- {"αβδC", "ΑΒΔD"},
- {"αβδC", "ΑΒΔ"},
- {"αβδ", "ΑΒΔD"},
- {"αβδ", "ΑΒΔ"},
- {"β", "δ"},
- {"好", strings.ToLower("好")},
- } {
-
- expect := strings.Compare(strings.ToLower(test.a), strings.ToLower(test.b))
- got := compareFold(test.a, test.b)
-
- c.Assert(got, qt.Equals, expect)
-
- }
-}
-
-func TestLexicographicSort(t *testing.T) {
- c := qt.New(t)
-
- s := []string{"b", "Bz", "ba", "A", "Ba", "ba"}
-
- sort.Slice(s, func(i, j int) bool {
- return LessStrings(s[i], s[j])
- })
-
- c.Assert(s, qt.DeepEquals, []string{"A", "b", "Ba", "ba", "ba", "Bz"})
-}
-
-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/config/allconfig/allconfig.go b/config/allconfig/allconfig.go
deleted file mode 100644
index 0db0be1d8..000000000
--- a/config/allconfig/allconfig.go
+++ /dev/null
@@ -1,1182 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package 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"
- "sort"
- "strconv"
- "strings"
- "sync"
- "time"
-
- "github.com/gohugoio/hugo/cache/filecache"
- "github.com/gohugoio/hugo/cache/httpcache"
- "github.com/gohugoio/hugo/common/hugo"
- "github.com/gohugoio/hugo/common/loggers"
- "github.com/gohugoio/hugo/common/maps"
- "github.com/gohugoio/hugo/common/paths"
- "github.com/gohugoio/hugo/common/types"
- "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/segments"
- "github.com/gohugoio/hugo/langs"
- "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"
-
- xmaps "golang.org/x/exp/maps"
-)
-
-// 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:"-"`
-
- 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 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 *config.ConfigNamespace[[]page.PageMatcherParamsConfig, *maps.Ordered[page.PageMatcher, page.PageMatcherParamsConfig]] `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 maps.Params `mapstructure:"-"`
-
- // The languages configuration sections maps a language code (a string) to a configuration object for that language.
- Languages map[string]langs.LanguageConfig `mapstructure:"-"`
-
- // UglyURLs configuration. Either a boolean or a sections map.
- UglyURLs any `mapstructure:"-"`
-}
-
-type configCompiler interface {
- CompileConfig(logger loggers.Logger) error
-}
-
-func (c Config) cloneForLang() *Config {
- x := c
- 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 lang, language := range c.Languages {
- if !language.Disabled && disabledLangs[lang] {
- language.Disabled = true
- c.Languages[lang] = language
- }
- if language.Disabled {
- disabledLangs[lang] = true
- if lang == c.DefaultContentLanguage {
- return fmt.Errorf("cannot disable default content language %q", lang)
- }
- }
- }
-
- 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 paginate values.
- if c.Paginate != 0 {
- hugo.DeprecateWithLogger("site config key paginate", "Use pagination.pagerSize instead.", "v0.128.0", logger.Logger())
- c.Pagination.PagerSize = c.Paginate
- }
-
- if c.PaginatePath != "" {
- hugo.DeprecateWithLogger("site config key paginatePath", "Use pagination.path instead.", "v0.128.0", logger.Logger())
- c.Pagination.Path = c.PaginatePath
- }
-
- // Legacy privacy values.
- if c.Privacy.Twitter.Disable {
- hugo.DeprecateWithLogger("site 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("site 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("site 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("site 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())
- }
-
- 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,
- SegmentFilter: c.Segments.Config.Get(func(s string) { logger.Warnf("Render segment %q not found in configuration", s) }, c.RootConfig.RenderSegments...),
- MainSections: c.MainSections,
- Clock: clock,
- HTTPCache: httpCache,
- transientErr: transientErr,
- }
-
- for _, s := range allDecoderSetups {
- 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
- SegmentFilter segments.SegmentFilter
- MainSections []string
- Clock time.Time
- HTTPCache httpcache.ConfigCompiled
-
- // This is set to the last transient error found during config compilation.
- // With themes/modules we compute the configuration in multiple passes, and
- // 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 output format to use for the site.
- // If not set, we will use the first output format.
- DefaultOutputFormat string
-
- // Disable generation of redirect to the default language when DefaultContentLanguageInSubdir is enabled.
- DisableDefaultLanguageRedirect bool
-
- // Disable creation of alias redirect pages.
- DisableAliases bool
-
- // 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
-
- // The default language code.
- LanguageCode string
-
- // Enable if the site content has CJK language (Chinese, Japanese, or Korean). This affects how Hugo counts words.
- HasCJKLanguage bool
-
- // The default number of pages per page when paginating.
- // Deprecated: Use the Pagination struct.
- Paginate int
-
- // The path to use when creating pagination URLs, e.g. "page" in /page/2/.
- // Deprecated: Use the Pagination struct.
- PaginatePath string
-
- // Whether to pluralize default list titles.
- // 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 helpers.UniqueStringsReuse(dirs)
-}
-
-type Configs struct {
- Base *Config
- LoadingInfo config.LoadConfigResult
- LanguageConfigMap map[string]*Config
- LanguageConfigSlice []*Config
-
- IsMultihost bool
-
- Modules modules.Modules
- ModulesClient *modules.Client
-
- // All below is set in Init.
- Languages langs.Languages
- LanguagesDefaultFirst langs.Languages
- ContentPathParser *paths.PathParser
-
- configLangs []config.AllProvider
-}
-
-func (c *Configs) Validate(logger loggers.Logger) error {
- c.Base.Cascade.Config.Range(func(p page.PageMatcher, cfg page.PageMatcherParamsConfig) bool {
- page.CheckCascadePattern(logger, p)
- return true
- })
- return nil
-}
-
-// transientErr returns the last transient error found during config compilation.
-func (c *Configs) transientErr() error {
- for _, l := range c.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() error {
- var languages langs.Languages
-
- var langKeys []string
- var hasEn bool
-
- const en = "en"
-
- for k := range c.LanguageConfigMap {
- langKeys = append(langKeys, k)
- if k == en {
- hasEn = true
- }
- }
-
- // Sort the LanguageConfigSlice by language weight (if set) or lang.
- sort.Slice(langKeys, func(i, j int) bool {
- ki := langKeys[i]
- kj := langKeys[j]
- lki := c.LanguageConfigMap[ki]
- lkj := c.LanguageConfigMap[kj]
- li := lki.Languages[ki]
- lj := lkj.Languages[kj]
- if li.Weight != lj.Weight {
- return li.Weight < lj.Weight
- }
- return ki < kj
- })
-
- // See issue #13646.
- defaultConfigLanguageFallback := en
- if !hasEn {
- // Pick the first one.
- defaultConfigLanguageFallback = langKeys[0]
- }
-
- if c.Base.DefaultContentLanguage == "" {
- c.Base.DefaultContentLanguage = defaultConfigLanguageFallback
- }
-
- for _, k := range langKeys {
- v := c.LanguageConfigMap[k]
- if v.DefaultContentLanguage == "" {
- v.DefaultContentLanguage = defaultConfigLanguageFallback
- }
- c.LanguageConfigSlice = append(c.LanguageConfigSlice, v)
- languageConf := v.Languages[k]
- language, err := langs.NewLanguage(k, c.Base.DefaultContentLanguage, v.TimeZone, languageConf)
- 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]
-
- var languagesDefaultFirst langs.Languages
- for _, l := range languages {
- if l.Lang == c.Base.DefaultContentLanguage {
- languagesDefaultFirst = append(languagesDefaultFirst, l)
- }
- }
- for _, l := range languages {
- if l.Lang != c.Base.DefaultContentLanguage {
- languagesDefaultFirst = append(languagesDefaultFirst, l)
- }
- }
-
- c.Languages = languages
- c.LanguagesDefaultFirst = languagesDefaultFirst
-
- c.ContentPathParser = &paths.PathParser{
- LanguageIndex: languagesDefaultFirst.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))
- for i, l := range c.LanguagesDefaultFirst {
- c.configLangs[i] = ConfigLanguage{
- m: c,
- config: c.LanguageConfigMap[l.Lang],
- baseConfig: c.LoadingInfo.BaseConfig,
- language: l,
- }
- }
-
- 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(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.Source == m.Source && cm.Target == m.Target && cm.Lang == m.Lang {
- 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.LanguageConfigSlice {
- 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().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", maps.Params{lang: maps.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 maps.Params:
- _, found := x["params"]
- if !found {
- x["params"] = maps.Params{
- maps.MergeStrategyKey: maps.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
- }
- 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 maps.Params:
- differentRootKeys = append(differentRootKeys, kk)
-
- // Use the language value as base.
- mergedConfigEntry := xmaps.Clone(vvv)
- // Merge in the root value.
- maps.MergeParams(mergedConfigEntry, rootv.(maps.Params))
-
- mergedConfig.Set(kk, mergedConfigEntry)
- default:
- // Apply new values to the root.
- differentRootKeys = append(differentRootKeys, "")
- }
- }
- } else {
- switch vv.(type) {
- case maps.Params:
- differentRootKeys = append(differentRootKeys, kk)
- default:
- // Apply new values to the root.
- differentRootKeys = append(differentRootKeys, "")
- }
- }
- }
- differentRootKeys = helpers.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.DuplicateResourceFiles {
- if clone.Markup.Goldmark.RenderHooks.Link.EnableDefault == nil {
- clone.Markup.Goldmark.RenderHooks.Link.EnableDefault = types.NewBool(true)
- }
- if clone.Markup.Goldmark.RenderHooks.Image.EnableDefault == nil {
- clone.Markup.Goldmark.RenderHooks.Image.EnableDefault = types.NewBool(true)
- }
- }
- }
-
- langConfigMap[k] = clone
- case maps.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
- }
-
- cm := &Configs{
- Base: all,
- LanguageConfigMap: langConfigMap,
- LoadingInfo: res,
- 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}
- 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
deleted file mode 100644
index 8f6cacf84..000000000
--- a/config/allconfig/allconfig_integration_test.go
+++ /dev/null
@@ -1,381 +0,0 @@
-package allconfig_test
-
-import (
- "path/filepath"
- "testing"
-
- qt "github.com/frankban/quicktest"
- "github.com/gohugoio/hugo/common/hugo"
- "github.com/gohugoio/hugo/config/allconfig"
- "github.com/gohugoio/hugo/hugolib"
- "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/_default/single.html --
-Title: {{ .Title }}
- `
-
- b := hugolib.NewIntegrationTestBuilder(
- hugolib.IntegrationTestConfig{T: t, TxtarString: files},
- ).Build()
-
- // 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, "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, "sv")
-}
-
-func TestConfigAliases(t *testing.T) {
- files := `
--- hugo.toml --
-baseURL = "https://example.com"
-logI18nWarnings = true
-logPathWarnings = true
-`
- b := hugolib.NewIntegrationTestBuilder(
- hugolib.IntegrationTestConfig{T: t, TxtarString: files},
- ).Build()
-
- 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/_default/list.html --
-{{ $paginator := .Paginate site.RegularPages }}
-{{ template "_internal/pagination.html" . }}
-{{ range $paginator.Pages }}
- {{ .Title }}
-{{ end }}
--- content/p1.md --
----
-title: "p1"
----
--- content/p2.md --
----
-title: "p2"
----
--- content/p3.md --
----
-title: "p3"
----
-`
-
- b := hugolib.Test(t, files)
-
- b.AssertFileExists("public/page/1/index.html", false)
- b.AssertFileContent("public/page/2/index.html", "pagination-default")
-}
-
-func TestMapUglyURLs(t *testing.T) {
- files := `
--- hugo.toml --
-[uglyurls]
- posts = true
-`
-
- b := hugolib.Test(t, files)
-
- c := b.H.Configs.Base
-
- b.Assert(c.C.IsUglyURLSection("posts"), qt.IsTrue)
- b.Assert(c.C.IsUglyURLSection("blog"), qt.IsFalse)
-}
-
-// Issue 13199
-func TestInvalidOutputFormat(t *testing.T) {
- t.Parallel()
-
- files := `
--- hugo.toml --
-disableKinds = ['page','rss','section','sitemap','taxonomy','term']
-[outputs]
-home = ['html','foo']
--- layouts/index.html --
-x
-`
-
- b, err := hugolib.TestE(t, files)
- b.Assert(err, qt.IsNotNil)
- b.Assert(err.Error(), qt.Contains, `failed to create config: unknown output format "foo" for kind "home"`)
-}
-
-// Issue 13201
-func TestLanguageConfigSlice(t *testing.T) {
- t.Parallel()
-
- files := `
--- hugo.toml --
-disableKinds = ['page','rss','section','sitemap','taxonomy','term']
-[languages.en]
-title = 'TITLE_EN'
-weight = 2
-[languages.de]
-title = 'TITLE_DE'
-weight = 1
-[languages.fr]
-title = 'TITLE_FR'
-weight = 3
-`
-
- b := hugolib.Test(t, files)
- b.Assert(b.H.Configs.LanguageConfigSlice[0].Title, qt.Equals, `TITLE_DE`)
-}
-
-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)
-}
diff --git a/config/allconfig/alldecoders.go b/config/allconfig/alldecoders.go
deleted file mode 100644
index 035349790..000000000
--- a/config/allconfig/alldecoders.go
+++ /dev/null
@@ -1,469 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package allconfig
-
-import (
- "fmt"
- "strings"
-
- "github.com/gohugoio/hugo/cache/filecache"
-
- "github.com/gohugoio/hugo/cache/httpcache"
- "github.com/gohugoio/hugo/common/maps"
- "github.com/gohugoio/hugo/common/types"
- "github.com/gohugoio/hugo/config"
- "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/segments"
- "github.com/gohugoio/hugo/langs"
- "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
-}
-
-type decodeWeight struct {
- key string
- decode func(decodeWeight, decodeConfig) error
- getCompiler func(c *Config) configCompiler
- 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 Lang which is always lower case.
- p.c.RootConfig.DefaultContentLanguage = strings.ToLower(p.c.RootConfig.DefaultContentLanguage)
-
- 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))
- return err
- },
- },
- "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 := maps.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
- },
- },
- "params": {
- key: "params",
- decode: func(d decodeWeight, p decodeConfig) error {
- p.c.Params = maps.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.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) {
- p.c.Taxonomies = maps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
- }
- 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
- },
- },
- "languages": {
- key: "languages",
- decode: func(d decodeWeight, p decodeConfig) error {
- var err error
- m := p.p.GetStringMap(d.key)
- if len(m) == 1 {
- // In v0.112.4 we moved this to the language config, but it's very commmon for mono language sites to have this at the top level.
- var first maps.Params
- var ok bool
- for _, v := range m {
- first, ok = v.(maps.Params)
- if ok {
- break
- }
- }
- if first != nil {
- if _, found := first["languagecode"]; !found {
- first["languagecode"] = p.p.GetString("languagecode")
- }
- }
- }
- p.c.Languages, err = langs.DecodeConfig(m)
- if err != nil {
- return err
- }
-
- // Validate defaultContentLanguage.
- if p.c.DefaultContentLanguage != "" {
- var found bool
- for lang := range p.c.Languages {
- if lang == p.c.DefaultContentLanguage {
- found = true
- break
- }
- }
- if !found {
- return fmt.Errorf("config value %q for defaultContentLanguage does not match any language definition", p.c.DefaultContentLanguage)
- }
- }
-
- return nil
- },
- },
- "cascade": {
- key: "cascade",
- decode: func(d decodeWeight, p decodeConfig) error {
- var err error
- p.c.Cascade, err = page.DecodeCascadeConfig(nil, true, p.p.Get(d.key))
- return err
- },
- },
- "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 = maps.CleanConfigStringMap(p.p.GetStringMap(d.key))
- return nil
- },
- internalOrDeprecated: true,
- },
- "social": {
- key: "social",
- decode: func(d decodeWeight, p decodeConfig) error {
- p.c.Social = maps.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 maps.Params:
- p.c.UglyURLs = cast.ToStringMapBool(maps.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
deleted file mode 100644
index 6990a3590..000000000
--- a/config/allconfig/configlanguage.go
+++ /dev/null
@@ -1,261 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package allconfig
-
-import (
- "time"
-
- "github.com/gohugoio/hugo/common/paths"
- "github.com/gohugoio/hugo/common/urls"
- "github.com/gohugoio/hugo/config"
- "github.com/gohugoio/hugo/identity"
- "github.com/gohugoio/hugo/langs"
-)
-
-type ConfigLanguage struct {
- config *Config
- baseConfig config.BaseConfig
-
- m *Configs
- language *langs.Language
-}
-
-func (c ConfigLanguage) Language() *langs.Language {
- return c.language
-}
-
-func (c ConfigLanguage) Languages() langs.Languages {
- return c.m.Languages
-}
-
-func (c ConfigLanguage) LanguagesDefaultFirst() langs.Languages {
- return c.m.LanguagesDefaultFirst
-}
-
-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) 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) 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) Quiet() bool {
- return c.m.Base.Internal.Quiet
-}
-
-func (c ConfigLanguage) Watching() bool {
- return c.m.Base.Internal.Watch
-}
-
-func (c ConfigLanguage) NewIdentityManager(name string, opts ...identity.ManagerOption) identity.Manager {
- if !c.Watching() {
- return identity.NopManager
- }
- return identity.NewManager(name, 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 "frontmatter":
- return c.config.Frontmatter
- case "caches":
- return c.config.Caches
- case "markup":
- return c.config.Markup
- case "mediaTypes":
- return c.config.MediaTypes.Config
- case "outputFormats":
- return c.config.OutputFormats.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) 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
-}
diff --git a/config/allconfig/docshelper.go b/config/allconfig/docshelper.go
deleted file mode 100644
index 1a5fb6153..000000000
--- a/config/allconfig/docshelper.go
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package allconfig
-
-import (
- "github.com/gohugoio/hugo/common/maps"
- "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(maps.Params))
- }
- lang := maps.Params{
- "en": maps.Params{
- "menus": maps.Params{},
- "params": maps.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
deleted file mode 100644
index 4fb8bbaef..000000000
--- a/config/allconfig/load.go
+++ /dev/null
@@ -1,544 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package allconfig contains the full configuration for Hugo.
-package allconfig
-
-import (
- "errors"
- "fmt"
- "os"
- "path/filepath"
- "strings"
-
- "github.com/gobwas/glob"
- "github.com/gohugoio/hugo/common/herrors"
- "github.com/gohugoio/hugo/common/hexec"
- "github.com/gohugoio/hugo/common/hugo"
- "github.com/gohugoio/hugo/common/loggers"
- "github.com/gohugoio/hugo/common/maps"
- "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/glob"
- "github.com/gohugoio/hugo/modules"
- "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 site.\n Run `hugo help new` for details.\n")
-
-func LoadConfig(d ConfigSourceDescriptor) (*Configs, error) {
- 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 err := configs.Init(); err != nil {
- return nil, fmt.Errorf("failed to init config: %w", err)
- }
-
- loggers.SetGlobalLogger(d.Logger)
-
- return configs, nil
-}
-
-// 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
-}
-
-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 := maps.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 && b {
- cfg.Set("minify.minifyOutput", true)
- } else if b, ok := cfg.Get("minify").(bool); ok && b {
- cfg.Set("minify", maps.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 strings.HasPrefix(key, hugoEnvPrefix) {
- delimiterAndKey := strings.TrimPrefix(key, hugoEnvPrefix)
- 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 := maps.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":
- 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(),
- 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() {
- l.cfg.WalkParams(func(params ...maps.KeyParams) bool {
- params[len(params)-1].Params.DeleteMergeStrategy()
- return false
- })
-}
-
-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
deleted file mode 100644
index 3c16e71e9..000000000
--- a/config/allconfig/load_test.go
+++ /dev/null
@@ -1,67 +0,0 @@
-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 i := 0; i < b.N; i++ {
- _, err := LoadConfig(d)
- if err != nil {
- b.Fatal(err)
- }
- }
-}
diff --git a/config/commonConfig.go b/config/commonConfig.go
deleted file mode 100644
index 947078672..000000000
--- a/config/commonConfig.go
+++ /dev/null
@@ -1,511 +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 config
-
-import (
- "fmt"
- "net/http"
- "regexp"
- "slices"
- "sort"
- "strings"
-
- "github.com/bep/logg"
- "github.com/gobwas/glob"
- "github.com/gohugoio/hugo/common/loggers"
- "github.com/gohugoio/hugo/common/types"
-
- "github.com/gohugoio/hugo/common/herrors"
- "github.com/mitchellh/mapstructure"
- "github.com/spf13/cast"
-)
-
-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",
- BuildStats: BuildStats{},
-
- CacheBusters: []CacheBuster{
- {
- Source: `(postcss|tailwind)\.config\.js`,
- Target: cssTargetCachebusterRe,
- },
- },
-}
-
-// 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).
- // Note that this was a bool <= v0.115.0.
- BuildStats BuildStats
-
- // 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
-}
-
-// 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 herrors.IsFeatureNotAvailableError(err)
- }
-
- return true
-}
-
-// 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.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 b
- }
-
- b.UseResourceCacheWhen = strings.ToLower(b.UseResourceCacheWhen)
- when := b.UseResourceCacheWhen
- if when != "never" && when != "always" && when != "fallback" {
- b.UseResourceCacheWhen = "fallback"
- }
-
- return b
-}
-
-// SitemapConfig configures the sitemap to be generated.
-type SitemapConfig struct {
- // The page change frequency.
- ChangeFreq string
- // The priority of the page.
- Priority float64
- // The sitemap filename.
- Filename string
- // Whether to disable page inclusion.
- Disable bool
-}
-
-func DecodeSitemap(prototype SitemapConfig, input map[string]any) (SitemapConfig, error) {
- err := mapstructure.WeakDecode(input, &prototype)
- return prototype, err
-}
-
-// Config for the dev server.
-type Server struct {
- Headers []Headers
- Redirects []Redirect
-
- compiledHeaders []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) 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)
- }
- 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 {
- if s.compiledHeaders == nil {
- return nil
- }
-
- var matches []types.KeyValueStr
-
- for i, g := range s.compiledHeaders {
- if g.Match(pattern) {
- h := s.Headers[i]
- for k, v := range h.Values {
- matches = append(matches, types.KeyValueStr{Key: k, Value: cast.ToString(v)})
- }
- }
- }
-
- sort.Slice(matches, func(i, j int) bool {
- return matches[i].Key < matches[j].Key
- })
-
- return matches
-}
-
-func (s *Server) MatchRedirect(pattern string, header http.Header) Redirect {
- if s.compiledRedirects == nil {
- return Redirect{}
- }
-
- pattern = strings.TrimSuffix(pattern, "index.html")
-
- for i, r := range s.compiledRedirects {
- redir := s.Redirects[i]
-
- 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 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
- }
- }
-
- return Redirect{}
-}
-
-type Headers struct {
- For string
- Values map[string]any
-}
-
-type Redirect struct {
- // 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
-
- // 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 == "" && r.FromRe == ""
-}
-
-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{}
-
- _ = mapstructure.WeakDecode(cfg.GetStringMap("server"), s)
-
- for i, redir := range s.Redirects {
- redir.To = strings.TrimSuffix(redir.To, "index.html")
- s.Redirects[i] = redir
- }
-
- 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
deleted file mode 100644
index 05ba185e3..000000000
--- a/config/commonConfig_test.go
+++ /dev/null
@@ -1,197 +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 config
-
-import (
- "errors"
- "testing"
-
- "github.com/gohugoio/hugo/common/herrors"
- "github.com/gohugoio/hugo/common/loggers"
- "github.com/gohugoio/hugo/common/types"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestBuild(t *testing.T) {
- c := qt.New(t)
-
- v := New()
- v.Set("build", map[string]any{
- "useResourceCacheWhen": "always",
- })
-
- b := DecodeBuildConfig(v)
-
- c.Assert(b.UseResourceCacheWhen, qt.Equals, "always")
-
- v.Set("build", map[string]any{
- "useResourceCacheWhen": "foo",
- })
-
- b = DecodeBuildConfig(v)
-
- c.Assert(b.UseResourceCacheWhen, qt.Equals, "fallback")
-
- c.Assert(b.UseResourceCache(herrors.ErrFeatureNotAvailable), qt.Equals, true)
- c.Assert(b.UseResourceCache(errors.New("err")), qt.Equals, false)
-
- b.UseResourceCacheWhen = "always"
- c.Assert(b.UseResourceCache(herrors.ErrFeatureNotAvailable), qt.Equals, true)
- c.Assert(b.UseResourceCache(errors.New("err")), qt.Equals, true)
- c.Assert(b.UseResourceCache(nil), qt.Equals, true)
-
- b.UseResourceCacheWhen = "never"
- c.Assert(b.UseResourceCache(herrors.ErrFeatureNotAvailable), qt.Equals, false)
- c.Assert(b.UseResourceCache(errors.New("err")), qt.Equals, false)
- c.Assert(b.UseResourceCache(nil), qt.Equals, false)
-}
-
-func TestServer(t *testing.T) {
- c := qt.New(t)
-
- cfg, err := FromConfigString(`[[server.headers]]
-for = "/*.jpg"
-
-[server.headers.values]
-X-Frame-Options = "DENY"
-X-XSS-Protection = "1; mode=block"
-X-Content-Type-Options = "nosniff"
-
-[[server.redirects]]
-from = "/foo/**"
-to = "/baz/index.html"
-status = 200
-
-[[server.redirects]]
-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 = "/google/**"
-to = "https://google.com/"
-status = 301
-
-
-
-`, "toml")
-
- c.Assert(err, qt.IsNil)
-
- 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"},
- {Key: "X-Frame-Options", Value: "DENY"},
- {Key: "X-XSS-Protection", Value: "1; mode=block"},
- })
-
- c.Assert(s.MatchRedirect("/foo/bar/baz", nil), qt.DeepEquals, Redirect{
- From: "/foo/**",
- To: "/baz/",
- Status: 200,
- })
-
- 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("/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",
- },
- },
- })
-
- 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/configLoader.go b/config/configLoader.go
deleted file mode 100644
index dd103f27b..000000000
--- a/config/configLoader.go
+++ /dev/null
@@ -1,231 +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 config
-
-import (
- "fmt"
- "os"
- "path/filepath"
- "strings"
-
- "github.com/gohugoio/hugo/common/herrors"
-
- "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
- }
-}
-
-// IsValidConfigFilename returns whether filename is one of the supported
-// config formats in Hugo.
-func IsValidConfigFilename(filename string) bool {
- ext := strings.ToLower(strings.TrimPrefix(filepath.Ext(filename), "."))
- return validConfigFileExtensionsMap[ext]
-}
-
-func FromTOMLConfigString(config string) Provider {
- cfg, err := FromConfigString(config, "toml")
- if err != nil {
- panic(err)
- }
- return cfg
-}
-
-// 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))
- if err != nil {
- return nil, err
- }
- return NewFrom(m), nil
-}
-
-// FromFile loads the configuration from the given filename.
-func FromFile(fs afero.Fs, filename string) (Provider, error) {
- m, err := loadConfigFromFile(fs, filename)
- if err != nil {
- 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]any, error) {
- return loadConfigFromFile(fs, filename)
-}
-
-func readConfig(format metadecoders.Format, data []byte) (map[string]any, error) {
- m, err := metadecoders.Default.UnmarshalToMap(data, format)
- if err != nil {
- return nil, err
- }
-
- RenameKeys(m)
-
- return m, nil
-}
-
-func loadConfigFromFile(fs afero.Fs, filename string) (map[string]any, error) {
- m, err := metadecoders.Default.UnmarshalFileToMap(fs, filename)
- if err != nil {
- return nil, err
- }
- RenameKeys(m)
- return m, nil
-}
-
-func LoadConfigFromDir(sourceFs afero.Fs, configDir, environment string) (Provider, []string, error) {
- defaultConfigDir := filepath.Join(configDir, "_default")
- environmentConfigDir := filepath.Join(configDir, environment)
- cfg := New()
-
- var configDirs []string
- // Merge from least to most specific.
- for _, dir := range []string{defaultConfigDir, environmentConfigDir} {
- if _, err := sourceFs.Stat(dir); err == nil {
- configDirs = append(configDirs, dir)
- }
- }
-
- if len(configDirs) == 0 {
- return nil, nil, nil
- }
-
- // Keep track of these so we can watch them for changes.
- var dirnames []string
-
- for _, configDir := range configDirs {
- err := afero.Walk(sourceFs, configDir, func(path string, fi os.FileInfo, err error) error {
- if fi == nil || err != nil {
- return nil
- }
-
- if fi.IsDir() {
- dirnames = append(dirnames, path)
- return nil
- }
-
- if !IsValidConfigFilename(path) {
- return nil
- }
-
- name := paths.Filename(filepath.Base(path))
-
- item, err := metadecoders.Default.UnmarshalFileToMap(sourceFs, path)
- if err != nil {
- // This will be used in error reporting, use the most specific value.
- dirnames = []string{path}
- return fmt.Errorf("failed to unmarshal config for path %q: %w", path, err)
- }
-
- var keyPath []string
- if !DefaultConfigNamesSet[name] {
- // Can be params.jp, menus.en etc.
- name, lang := paths.FileAndExtNoDelimiter(name)
-
- keyPath = []string{name}
-
- if lang != "" {
- keyPath = []string{"languages", lang}
- switch name {
- case "menu", "menus":
- keyPath = append(keyPath, "menus")
- case "params":
- keyPath = append(keyPath, "params")
- }
- }
- }
-
- root := item
- if len(keyPath) > 0 {
- 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]any)
- m[key] = nm
- m = nm
- }
- }
- }
-
- // Migrate menu => menus etc.
- RenameKeys(root)
-
- // Set will overwrite keys with the same name, recursively.
- cfg.Set("", root)
-
- return nil
- })
- if err != nil {
- return nil, dirnames, err
- }
-
- }
-
- return cfg, dirnames, nil
-}
-
-var keyAliases maps.KeyRenamer
-
-func init() {
- var err error
- keyAliases, err = maps.NewKeyRenamer(
- // Before 0.53 we used singular for "menu".
- "{menu,languages/*/menu}", "menus",
- )
-
- if err != nil {
- panic(err)
- }
-}
-
-// RenameKeys renames config keys in m recursively according to a global Hugo
-// alias definition.
-func RenameKeys(m map[string]any) {
- keyAliases.Rename(m)
-}
diff --git a/config/configLoader_test.go b/config/configLoader_test.go
deleted file mode 100644
index 546031334..000000000
--- a/config/configLoader_test.go
+++ /dev/null
@@ -1,34 +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 config
-
-import (
- "strings"
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestIsValidConfigFileName(t *testing.T) {
- c := qt.New(t)
-
- for _, ext := range ValidConfigFileExtensions {
- filename := "config." + ext
- c.Assert(IsValidConfigFilename(filename), qt.Equals, true)
- c.Assert(IsValidConfigFilename(strings.ToUpper(filename)), qt.Equals, true)
- }
-
- c.Assert(IsValidConfigFilename(""), qt.Equals, false)
- c.Assert(IsValidConfigFilename("config.toml.swp"), qt.Equals, false)
-}
diff --git a/config/configProvider.go b/config/configProvider.go
index c21342dce..870341f7f 100644
--- a/config/configProvider.go
+++ b/config/configProvider.go
@@ -1,4 +1,4 @@
-// Copyright 2019 The Hugo Authors. All rights reserved.
+// 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.
@@ -13,100 +13,14 @@
package config
-import (
- "time"
-
- "github.com/gohugoio/hugo/common/maps"
- "github.com/gohugoio/hugo/common/paths"
- "github.com/gohugoio/hugo/common/types"
- "github.com/gohugoio/hugo/common/urls"
- "github.com/gohugoio/hugo/identity"
- "github.com/gohugoio/hugo/langs"
-)
-
-// AllProvider is a sub set of all config settings.
-type AllProvider interface {
- Language() *langs.Language
- Languages() langs.Languages
- LanguagesDefaultFirst() langs.Languages
- 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
- ContentTypes() ContentTypesProvider
- GetConfigSection(string) any
- GetConfig() any
- CanonifyURLs() bool
- DisablePathToLower() bool
- RemovePathAccents() bool
- IsUglyURLs(section string) bool
- DefaultContentLanguage() string
- DefaultContentLanguageInSubdir() bool
- IsLangDisabled(string) bool
- SummaryLength() int
- Pagination() Pagination
- BuildExpired() bool
- BuildFuture() bool
- BuildDrafts() bool
- Running() bool
- Watching() bool
- NewIdentityManager(name string, 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
-}
-
-// 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]any
+ GetStringMap(key string) map[string]interface{}
GetStringMapString(key string) map[string]string
- GetStringSlice(key string) []string
- Get(key string) any
- Set(key string, value any)
- Keys() []string
- Merge(key string, value any)
- SetDefaults(params maps.Params)
- SetDefaultMergeStrategy()
- WalkParams(walkFn func(params ...maps.KeyParams) bool)
+ Get(key string) interface{}
+ Set(key string, value interface{})
IsSet(key string) bool
}
-
-// GetStringSlicePreserveString returns a string slice from the given config and key.
-// It differs from the GetStringSlice method in that if the config value is a string,
-// we do not attempt to split it into fields.
-func GetStringSlicePreserveString(cfg Provider, key string) []string {
- sd := cfg.Get(key)
- return types.ToStringSlicePreserveString(sd)
-}
diff --git a/config/configProvider_test.go b/config/configProvider_test.go
deleted file mode 100644
index 0afba1e58..000000000
--- a/config/configProvider_test.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.
-
-package config
-
-import (
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestGetStringSlicePreserveString(t *testing.T) {
- c := qt.New(t)
- cfg := New()
-
- s := "This is a string"
- sSlice := []string{"This", "is", "a", "slice"}
-
- cfg.Set("s1", s)
- cfg.Set("s2", sSlice)
-
- c.Assert(GetStringSlicePreserveString(cfg, "s1"), qt.DeepEquals, []string{s})
- c.Assert(GetStringSlicePreserveString(cfg, "s2"), qt.DeepEquals, sSlice)
- c.Assert(GetStringSlicePreserveString(cfg, "s3"), qt.IsNil)
-}
diff --git a/config/defaultConfigProvider.go b/config/defaultConfigProvider.go
deleted file mode 100644
index 8c1d63851..000000000
--- a/config/defaultConfigProvider.go
+++ /dev/null
@@ -1,366 +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 (
- "fmt"
- "strings"
- "sync"
-
- xmaps "golang.org/x/exp/maps"
-
- "github.com/spf13/cast"
-
- "github.com/gohugoio/hugo/common/maps"
-)
-
-// New creates a Provider backed by an empty maps.Params.
-func New() Provider {
- return &defaultConfigProvider{
- root: make(maps.Params),
- }
-}
-
-// NewFrom creates a Provider backed by params.
-func NewFrom(params maps.Params) Provider {
- maps.PrepareParams(params)
- return &defaultConfigProvider{
- root: params,
- }
-}
-
-// defaultConfigProvider is a Provider backed by a map where all keys are lower case.
-// All methods are thread safe.
-type defaultConfigProvider struct {
- mu sync.RWMutex
- root maps.Params
-
- keyCache sync.Map
-}
-
-func (c *defaultConfigProvider) Get(k string) any {
- if k == "" {
- return c.root
- }
- c.mu.RLock()
- key, m := c.getNestedKeyAndMap(strings.ToLower(k), false)
- if m == nil {
- c.mu.RUnlock()
- return nil
- }
- v := m[key]
- c.mu.RUnlock()
- return v
-}
-
-func (c *defaultConfigProvider) GetBool(k string) bool {
- v := c.Get(k)
- return cast.ToBool(v)
-}
-
-func (c *defaultConfigProvider) GetInt(k string) int {
- v := c.Get(k)
- return cast.ToInt(v)
-}
-
-func (c *defaultConfigProvider) IsSet(k string) bool {
- var found bool
- c.mu.RLock()
- key, m := c.getNestedKeyAndMap(strings.ToLower(k), false)
- if m != nil {
- _, found = m[key]
- }
- c.mu.RUnlock()
- return found
-}
-
-func (c *defaultConfigProvider) GetString(k string) string {
- v := c.Get(k)
- return cast.ToString(v)
-}
-
-func (c *defaultConfigProvider) GetParams(k string) maps.Params {
- v := c.Get(k)
- if v == nil {
- return nil
- }
- return v.(maps.Params)
-}
-
-func (c *defaultConfigProvider) GetStringMap(k string) map[string]any {
- v := c.Get(k)
- return maps.ToStringMap(v)
-}
-
-func (c *defaultConfigProvider) GetStringMapString(k string) map[string]string {
- v := c.Get(k)
- return maps.ToStringMapString(v)
-}
-
-func (c *defaultConfigProvider) GetStringSlice(k string) []string {
- v := c.Get(k)
- return cast.ToStringSlice(v)
-}
-
-func (c *defaultConfigProvider) Set(k string, v any) {
- c.mu.Lock()
- defer c.mu.Unlock()
-
- k = strings.ToLower(k)
-
- if k == "" {
- if p, err := maps.ToParamsAndPrepare(v); err == nil {
- // Set the values directly in root.
- maps.SetParams(c.root, p)
- } else {
- c.root[k] = v
- }
-
- return
- }
-
- switch vv := v.(type) {
- case map[string]any, map[any]any, map[string]string:
- p := maps.MustToParamsAndPrepare(vv)
- v = p
- }
-
- key, m := c.getNestedKeyAndMap(k, true)
- if m == nil {
- return
- }
-
- if existing, found := m[key]; found {
- if p1, ok := existing.(maps.Params); ok {
- if p2, ok := v.(maps.Params); ok {
- maps.SetParams(p1, p2)
- return
- }
- }
- }
-
- m[key] = v
-}
-
-// SetDefaults will set values from params if not already set.
-func (c *defaultConfigProvider) SetDefaults(params maps.Params) {
- maps.PrepareParams(params)
- for k, v := range params {
- if _, found := c.root[k]; !found {
- c.root[k] = v
- }
- }
-}
-
-func (c *defaultConfigProvider) Merge(k string, v any) {
- c.mu.Lock()
- defer c.mu.Unlock()
- k = strings.ToLower(k)
-
- if k == "" {
- rs, f := c.root.GetMergeStrategy()
- if f && rs == maps.ParamsMergeStrategyNone {
- // The user has set a "no merge" strategy on this,
- // nothing more to do.
- return
- }
-
- if p, err := maps.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 pppi, ok := c.root[kk]; ok {
- ppp := pppi.(maps.Params)
- maps.MergeParamsWithStrategy("", ppp, pp)
- } else {
- // We need to use the default merge strategy for
- // this key.
- np := make(maps.Params)
- strategy := c.determineMergeStrategy(maps.KeyParams{Key: "", Params: c.root}, maps.KeyParams{Key: kk, Params: np})
- np.SetMergeStrategy(strategy)
- maps.MergeParamsWithStrategy("", np, pp)
- c.root[kk] = np
- if np.IsZero() {
- // Just keep it until merge is done.
- keysToDelete = append(keysToDelete, kk)
- }
- }
- }
- }
- // Merge the rest.
- maps.MergeParams(c.root, p)
- for _, k := range keysToDelete {
- delete(c.root, k)
- }
- } else {
- panic(fmt.Sprintf("unsupported type %T received in Merge", v))
- }
-
- return
- }
-
- switch vv := v.(type) {
- case map[string]any, map[any]any, map[string]string:
- p := maps.MustToParamsAndPrepare(vv)
- v = p
- }
-
- key, m := c.getNestedKeyAndMap(k, true)
- if m == nil {
- return
- }
-
- if existing, found := m[key]; found {
- if p1, ok := existing.(maps.Params); ok {
- if p2, ok := v.(maps.Params); ok {
- maps.MergeParamsWithStrategy("", p1, p2)
- }
- }
- } else {
- m[key] = v
- }
-}
-
-func (c *defaultConfigProvider) Keys() []string {
- c.mu.RLock()
- defer c.mu.RUnlock()
- return xmaps.Keys(c.root)
-}
-
-func (c *defaultConfigProvider) WalkParams(walkFn func(params ...maps.KeyParams) bool) {
- var walk func(params ...maps.KeyParams)
- walk = func(params ...maps.KeyParams) {
- 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([]maps.KeyParams, i+1)
- copy(paramsplus1, params)
- paramsplus1[i] = maps.KeyParams{Key: k, Params: p2}
- walk(paramsplus1...)
- }
- }
- }
- walk(maps.KeyParams{Key: "", Params: c.root})
-}
-
-func (c *defaultConfigProvider) determineMergeStrategy(params ...maps.KeyParams) maps.ParamsMergeStrategy {
- if len(params) == 0 {
- return maps.ParamsMergeStrategyNone
- }
-
- var (
- strategy maps.ParamsMergeStrategy
- prevIsRoot bool
- curr = params[len(params)-1]
- )
-
- if len(params) > 1 {
- prev := params[len(params)-2]
- prevIsRoot = prev.Key == ""
-
- // Inherit from parent (but not from the root unless it's set by user).
- s, found := prev.Params.GetMergeStrategy()
- if !prevIsRoot && !found {
- panic("invalid state, merge strategy not set on parent")
- }
- if found || !prevIsRoot {
- strategy = s
- }
- }
-
- switch curr.Key {
- case "":
- // 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
- case "outputformats", "mediatypes":
- if prevIsRoot {
- strategy = maps.ParamsMergeStrategyShallow
- }
- case "menus":
- isMenuKey := prevIsRoot
- if !isMenuKey {
- // Can also be set below languages.
- // root > languages > en > menus
- if len(params) == 4 && params[1].Key == "languages" {
- isMenuKey = true
- }
- }
- if isMenuKey {
- strategy = maps.ParamsMergeStrategyShallow
- }
- default:
- if strategy == "" {
- strategy = maps.ParamsMergeStrategyNone
- }
- }
-
- return strategy
-}
-
-func (c *defaultConfigProvider) SetDefaultMergeStrategy() {
- c.WalkParams(func(params ...maps.KeyParams) bool {
- if len(params) == 0 {
- return false
- }
- p := params[len(params)-1].Params
- var found bool
- if _, found = p.GetMergeStrategy(); found {
- // Set by user.
- return false
- }
- strategy := c.determineMergeStrategy(params...)
- if strategy != "" {
- p.SetMergeStrategy(strategy)
- }
- return false
- })
-}
-
-func (c *defaultConfigProvider) getNestedKeyAndMap(key string, create bool) (string, maps.Params) {
- var parts []string
- v, ok := c.keyCache.Load(key)
- if ok {
- parts = v.([]string)
- } else {
- parts = strings.Split(key, ".")
- c.keyCache.Store(key, parts)
- }
- current := c.root
- for i := range len(parts) - 1 {
- next, found := current[parts[i]]
- if !found {
- if create {
- next = make(maps.Params)
- current[parts[i]] = next
- } else {
- return "", nil
- }
- }
- var ok bool
- current, ok = next.(maps.Params)
- if !ok {
- // E.g. a string, not a map that we can store values in.
- return "", nil
- }
- }
- return parts[len(parts)-1], current
-}
diff --git a/config/defaultConfigProvider_test.go b/config/defaultConfigProvider_test.go
deleted file mode 100644
index cd6247e60..000000000
--- a/config/defaultConfigProvider_test.go
+++ /dev/null
@@ -1,400 +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 (
- "context"
- "errors"
- "fmt"
- "strconv"
- "strings"
- "testing"
-
- "github.com/gohugoio/hugo/common/para"
-
- "github.com/gohugoio/hugo/common/maps"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestDefaultConfigProvider(t *testing.T) {
- c := qt.New(t)
-
- c.Run("Set and get", func(c *qt.C) {
- cfg := New()
- var k string
- var v any
-
- k, v = "foo", "bar"
- cfg.Set(k, v)
- c.Assert(cfg.Get(k), qt.Equals, v)
- c.Assert(cfg.Get(strings.ToUpper(k)), qt.Equals, v)
- c.Assert(cfg.GetString(k), qt.Equals, v)
-
- k, v = "foo", 42
- cfg.Set(k, v)
- c.Assert(cfg.Get(k), qt.Equals, v)
- c.Assert(cfg.GetInt(k), qt.Equals, v)
-
- c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
- "foo": 42,
- })
- })
-
- c.Run("Set and get map", func(c *qt.C) {
- cfg := New()
-
- cfg.Set("foo", map[string]any{
- "bar": "baz",
- })
-
- c.Assert(cfg.Get("foo"), qt.DeepEquals, maps.Params{
- "bar": "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]any{
- "B": "bv",
- })
- cfg.Set("a.c", "cv")
-
- c.Assert(cfg.Get("a"), qt.DeepEquals, maps.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{
- "a": "av",
- })
-
- cfg.Set("b", map[string]any{
- "b": "bv",
- })
-
- c.Assert(cfg.Get("b"), qt.DeepEquals, maps.Params{
- "a": "av",
- "b": "bv",
- })
-
- cfg = New()
-
- cfg.Set("a", "av")
-
- cfg.Set("", map[string]any{
- "a": "av2",
- "b": "bv2",
- })
-
- c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
- "a": "av2",
- "b": "bv2",
- })
-
- cfg = New()
-
- cfg.Set("a", "av")
-
- cfg.Set("", map[string]any{
- "b": "bv2",
- })
-
- c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
- "a": "av",
- "b": "bv2",
- })
-
- cfg = New()
-
- cfg.Set("", map[string]any{
- "foo": map[string]any{
- "a": "av",
- },
- })
-
- cfg.Set("", map[string]any{
- "foo": map[string]any{
- "b": "bv2",
- },
- })
-
- c.Assert(cfg.Get("foo"), qt.DeepEquals, maps.Params{
- "a": "av",
- "b": "bv2",
- })
- })
-
- c.Run("Merge default strategy", func(c *qt.C) {
- cfg := New()
-
- cfg.Set("a", map[string]any{
- "B": "bv",
- })
-
- cfg.Merge("a", map[string]any{
- "B": "bv2",
- "c": "cv2",
- })
-
- c.Assert(cfg.Get("a"), qt.DeepEquals, maps.Params{
- "b": "bv",
- "c": "cv2",
- })
-
- cfg = New()
-
- cfg.Set("a", "av")
-
- cfg.Merge("", map[string]any{
- "a": "av2",
- "b": "bv2",
- })
-
- c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
- "a": "av",
- })
- })
-
- c.Run("Merge shallow", func(c *qt.C) {
- cfg := New()
-
- cfg.Set("a", map[string]any{
- "_merge": "shallow",
- "B": "bv",
- "c": map[string]any{
- "b": "bv",
- },
- })
-
- cfg.Merge("a", map[string]any{
- "c": map[string]any{
- "d": "dv2",
- },
- "e": "ev2",
- })
-
- c.Assert(cfg.Get("a"), qt.DeepEquals, maps.Params{
- "e": "ev2",
- "_merge": maps.ParamsMergeStrategyShallow,
- "b": "bv",
- "c": maps.Params{
- "b": "bv",
- },
- })
- })
-
- // Issue #8679
- c.Run("Merge typed maps", func(c *qt.C) {
- for _, left := range []any{
- map[string]string{
- "c": "cv1",
- },
- map[string]any{
- "c": "cv1",
- },
- map[any]any{
- "c": "cv1",
- },
- } {
- cfg := New()
-
- cfg.Set("", map[string]any{
- "b": left,
- })
-
- cfg.Merge("", maps.Params{
- "b": maps.Params{
- "c": "cv2",
- "d": "dv2",
- },
- })
-
- c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
- "b": maps.Params{
- "c": "cv1",
- "d": "dv2",
- },
- })
- }
-
- for _, left := range []any{
- map[string]string{
- "b": "bv1",
- },
- map[string]any{
- "b": "bv1",
- },
- map[any]any{
- "b": "bv1",
- },
- } {
- for _, right := range []any{
- map[string]string{
- "b": "bv2",
- "c": "cv2",
- },
- map[string]any{
- "b": "bv2",
- "c": "cv2",
- },
- map[any]any{
- "b": "bv2",
- "c": "cv2",
- },
- } {
- cfg := New()
-
- cfg.Set("a", left)
-
- cfg.Merge("a", right)
-
- c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
- "a": maps.Params{
- "b": "bv1",
- "c": "cv2",
- },
- })
- }
- }
- })
-
- // Issue #8701
- c.Run("Prevent _merge only maps", func(c *qt.C) {
- cfg := New()
-
- cfg.Set("", map[string]any{
- "B": "bv",
- })
-
- cfg.Merge("", map[string]any{
- "c": map[string]any{
- "_merge": "shallow",
- "d": "dv2",
- },
- })
-
- c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
- "b": "bv",
- })
- })
-
- c.Run("IsSet", func(c *qt.C) {
- cfg := New()
-
- cfg.Set("a", map[string]any{
- "B": "bv",
- })
-
- c.Assert(cfg.IsSet("A"), qt.IsTrue)
- c.Assert(cfg.IsSet("a.b"), qt.IsTrue)
- c.Assert(cfg.IsSet("z"), qt.IsFalse)
- })
-
- c.Run("Para", func(c *qt.C) {
- cfg := New()
- p := para.New(4)
- r, _ := p.Start(context.Background())
-
- setAndGet := func(k string, v int) error {
- vs := strconv.Itoa(v)
- cfg.Set(k, v)
- err := errors.New("get failed")
- if cfg.Get(k) != v {
- return err
- }
- if cfg.GetInt(k) != v {
- return err
- }
- if cfg.GetString(k) != vs {
- return err
- }
- if !cfg.IsSet(k) {
- return err
- }
- return nil
- }
-
- for i := range 20 {
- i := i
- r.Run(func() error {
- const v = 42
- k := fmt.Sprintf("k%d", i)
- if err := setAndGet(k, v); err != nil {
- return err
- }
-
- m := maps.Params{
- "new": 42,
- }
-
- cfg.Merge("", m)
-
- return nil
- })
- }
-
- c.Assert(r.Wait(), qt.IsNil)
- })
-}
-
-func BenchmarkDefaultConfigProvider(b *testing.B) {
- type cfger interface {
- Get(key string) any
- Set(key string, value any)
- IsSet(key string) bool
- }
-
- newMap := func() map[string]any {
- return map[string]any{
- "a": map[string]any{
- "b": map[string]any{
- "c": 32,
- "d": 43,
- },
- },
- "b": 62,
- }
- }
-
- runMethods := func(b *testing.B, cfg cfger) {
- m := newMap()
- cfg.Set("mymap", m)
- cfg.Set("num", 32)
- if !(cfg.IsSet("mymap") && cfg.IsSet("mymap.a") && cfg.IsSet("mymap.a.b") && cfg.IsSet("mymap.a.b.c")) {
- b.Fatal("IsSet failed")
- }
-
- if cfg.Get("num") != 32 {
- b.Fatal("Get failed")
- }
-
- if cfg.Get("mymap.a.b.c") != 32 {
- b.Fatal("Get failed")
- }
- }
-
- b.Run("Custom", func(b *testing.B) {
- cfg := New()
- for i := 0; i < b.N; i++ {
- runMethods(b, cfg)
- }
- })
-}
diff --git a/config/env.go b/config/env.go
deleted file mode 100644
index 4dcd63653..000000000
--- a/config/env.go
+++ /dev/null
@@ -1,93 +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 config
-
-import (
- "os"
- "runtime"
- "strconv"
- "strings"
-
- "github.com/pbnjay/memory"
-)
-
-const (
- gigabyte = 1 << 30
-)
-
-// GetNumWorkerMultiplier returns the base value used to calculate the number
-// of workers to use for Hugo's parallel execution.
-// It returns the value in HUGO_NUMWORKERMULTIPLIER OS env variable if set to a
-// positive integer, else the number of logical CPUs.
-func GetNumWorkerMultiplier() int {
- if gmp := os.Getenv("HUGO_NUMWORKERMULTIPLIER"); gmp != "" {
- if p, err := strconv.Atoi(gmp); err == nil && p > 0 {
- return p
- }
- }
- 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 {
- setEnvVar(oldVars, keyValues[i], keyValues[i+1])
- }
-}
-
-func SplitEnvVar(v string) (string, string) {
- name, value, _ := strings.Cut(v, "=")
- return name, value
-}
-
-func setEnvVar(vars *[]string, key, value string) {
- for i := range *vars {
- if strings.HasPrefix((*vars)[i], key+"=") {
- (*vars)[i] = key + "=" + value
- return
- }
- }
- // New var.
- *vars = append(*vars, key+"="+value)
-}
diff --git a/config/env_test.go b/config/env_test.go
deleted file mode 100644
index 3c402b9ef..000000000
--- a/config/env_test.go
+++ /dev/null
@@ -1,32 +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 config
-
-import (
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestSetEnvVars(t *testing.T) {
- t.Parallel()
- c := qt.New(t)
- vars := []string{"FOO=bar", "HUGO=cool", "BAR=foo"}
- SetEnvVars(&vars, "HUGO", "rocking!", "NEW", "bar")
- c.Assert(vars, qt.DeepEquals, []string{"FOO=bar", "HUGO=rocking!", "BAR=foo", "NEW=bar"})
-
- key, val := SplitEnvVar("HUGO=rocks")
- c.Assert(key, qt.Equals, "HUGO")
- c.Assert(val, qt.Equals, "rocks")
-}
diff --git a/config/namespace.go b/config/namespace.go
deleted file mode 100644
index e41b56e2d..000000000
--- a/config/namespace.go
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package 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
deleted file mode 100644
index f443523a4..000000000
--- a/config/namespace_test.go
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package config
-
-import (
- "strings"
- "testing"
-
- qt "github.com/frankban/quicktest"
- "github.com/gohugoio/hugo/common/maps"
- "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 := maps.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
deleted file mode 100644
index 900f73540..000000000
--- a/config/privacy/privacyConfig.go
+++ /dev/null
@@ -1,124 +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 privacy
-
-import (
- "github.com/gohugoio/hugo/config"
- "github.com/mitchellh/mapstructure"
-)
-
-const privacyConfigKey = "privacy"
-
-// Service is the common values for a service in a policy definition.
-type Service struct {
- Disable bool
-}
-
-// Config is a privacy configuration for all the relevant services in Hugo.
-type Config struct {
- Disqus Disqus
- GoogleAnalytics GoogleAnalytics
- Instagram Instagram
- Twitter Twitter // deprecated in favor of X in v0.141.0
- Vimeo Vimeo
- YouTube YouTube
- X X
-}
-
-// Disqus holds the privacy configuration settings related to the Disqus template.
-type Disqus struct {
- Service `mapstructure:",squash"`
-}
-
-// GoogleAnalytics holds the privacy configuration settings related to the Google Analytics template.
-type GoogleAnalytics struct {
- Service `mapstructure:",squash"`
-
- // Enabling this will make the GA templates respect the
- // "Do Not Track" HTTP header. See https://www.paulfurley.com/google-analytics-dnt/.
- RespectDoNotTrack bool
-}
-
-// Instagram holds the privacy configuration settings related to the Instagram shortcode.
-type Instagram struct {
- Service `mapstructure:",squash"`
-
- // If simple mode is enabled, a static and no-JS version of the Instagram
- // image card will be built.
- Simple bool
-}
-
-// Twitter holds the privacy configuration settings related to the Twitter shortcode.
-// Deprecated in favor of X in v0.141.0.
-type Twitter struct {
- Service `mapstructure:",squash"`
-
- // When set to true, the Tweet and its embedded page on your site are not used
- // for purposes that include personalized suggestions and personalized ads.
- EnableDNT bool
-
- // If simple mode is enabled, a static and no-JS version of the Tweet will be built.
- Simple bool
-}
-
-// Vimeo holds the privacy configuration settings related to the Vimeo shortcode.
-type Vimeo struct {
- Service `mapstructure:",squash"`
-
- // When set to true, the Vimeo player will be blocked from tracking any session data,
- // including all cookies and stats.
- EnableDNT bool
-
- // If simple mode is enabled, only a thumbnail is fetched from i.vimeocdn.com and
- // shown with a play button overlaid. If a user clicks the button, he/she will
- // be taken to the video page on vimeo.com in a new browser tab.
- Simple bool
-}
-
-// YouTube holds the privacy configuration settings related to the YouTube shortcode.
-type YouTube struct {
- Service `mapstructure:",squash"`
-
- // When you turn on privacy-enhanced mode,
- // YouTube won’t store information about visitors on your website
- // unless the user plays the embedded video.
- 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
- }
-
- m := cfg.GetStringMap(privacyConfigKey)
-
- err = mapstructure.WeakDecode(m, &pc)
-
- return
-}
diff --git a/config/privacy/privacyConfig_test.go b/config/privacy/privacyConfig_test.go
deleted file mode 100644
index 1dd20215b..000000000
--- a/config/privacy/privacyConfig_test.go
+++ /dev/null
@@ -1,98 +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 privacy
-
-import (
- "testing"
-
- qt "github.com/frankban/quicktest"
- "github.com/gohugoio/hugo/config"
-)
-
-func TestDecodeConfigFromTOML(t *testing.T) {
- c := qt.New(t)
-
- tomlConfig := `
-
-someOtherValue = "foo"
-
-[privacy]
-[privacy.disqus]
-disable = true
-[privacy.googleAnalytics]
-disable = true
-respectDoNotTrack = true
-[privacy.instagram]
-disable = true
-simple = true
-[privacy.x]
-disable = true
-enableDNT = true
-simple = true
-[privacy.vimeo]
-disable = true
-enableDNT = true
-simple = true
-[privacy.youtube]
-disable = true
-privacyEnhanced = true
-simple = true
-`
- 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))
-
- got := []bool{
- pc.Disqus.Disable, pc.GoogleAnalytics.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)
-}
-
-func TestDecodeConfigFromTOMLCaseInsensitive(t *testing.T) {
- c := qt.New(t)
-
- tomlConfig := `
-
-someOtherValue = "foo"
-
-[Privacy]
-[Privacy.YouTube]
-PrivacyENhanced = true
-`
- 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.YouTube.PrivacyEnhanced, qt.Equals, true)
-}
-
-func TestDecodeConfigDefault(t *testing.T) {
- c := qt.New(t)
-
- pc, err := DecodeConfig(config.New())
- c.Assert(err, qt.IsNil)
- c.Assert(pc, qt.Not(qt.IsNil))
- c.Assert(pc.YouTube.PrivacyEnhanced, qt.Equals, false)
-}
diff --git a/config/security/securityConfig.go b/config/security/securityConfig.go
deleted file mode 100644
index a3ec5197d..000000000
--- a/config/security/securityConfig.go
+++ /dev/null
@@ -1,230 +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 (
- "bytes"
- "encoding/json"
- "errors"
- "fmt"
- "reflect"
- "strings"
-
- "github.com/gohugoio/hugo/common/herrors"
- "github.com/gohugoio/hugo/common/types"
- "github.com/gohugoio/hugo/config"
- "github.com/gohugoio/hugo/parser"
- "github.com/gohugoio/hugo/parser/metadecoders"
- "github.com/mitchellh/mapstructure"
-)
-
-const securityConfigKey = "security"
-
-// DefaultConfig holds the default security policy.
-var DefaultConfig = Config{
- Exec: Exec{
- 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: MustNewWhitelist(`(?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\w+|(XDG_CONFIG_)?HOME|USERPROFILE|SSH_AUTH_SOCK|DISPLAY|LANG|SYSTEMDRIVE)$`),
- },
- Funcs: Funcs{
- Getenv: MustNewWhitelist("^HUGO_", "^CI$"),
- },
- HTTP: HTTP{
- 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....
- // { "newIn": "0.91.0" }
- Exec Exec `json:"exec"`
-
- // Restricts access to certain template funcs.
- Funcs Funcs `json:"funcs"`
-
- // Restricts access to resources.GetRemote, getJSON, getCSV.
- HTTP HTTP `json:"http"`
-
- // Allow inline shortcodes
- EnableInlineShortcodes bool `json:"enableInlineShortcodes"`
-}
-
-// Exec holds os/exec policies.
-type Exec struct {
- Allow Whitelist `json:"allow"`
- OsEnv Whitelist `json:"osEnv"`
-}
-
-// Funcs holds template funcs policies.
-type Funcs struct {
- // OS env keys allowed to query in os.Getenv.
- Getenv Whitelist `json:"getenv"`
-}
-
-type HTTP struct {
- // URLs to allow in remote HTTP (resources.Get, getJSON, getCSV).
- URLs Whitelist `json:"urls"`
-
- // 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.
-func (c Config) ToTOML() string {
- sec := c.ToSecurityMap()
-
- var b bytes.Buffer
-
- if err := parser.InterfaceToConfig(sec, metadecoders.TOML, &b); err != nil {
- panic(err)
- }
-
- return strings.TrimSpace(b.String())
-}
-
-func (c Config) CheckAllowedExec(name string) error {
- if !c.Exec.Allow.Accept(name) {
- return &AccessDeniedError{
- name: name,
- path: "security.exec.allow",
- policies: c.ToTOML(),
- }
- }
- return nil
-}
-
-func (c Config) CheckAllowedGetEnv(name string) error {
- if !c.Funcs.Getenv.Accept(name) {
- return &AccessDeniedError{
- name: name,
- path: "security.funcs.getenv",
- policies: c.ToTOML(),
- }
- }
- return nil
-}
-
-func (c Config) CheckAllowedHTTPURL(url string) error {
- if !c.HTTP.URLs.Accept(url) {
- return &AccessDeniedError{
- name: url,
- path: "security.http.urls",
- policies: c.ToTOML(),
- }
- }
- return nil
-}
-
-func (c Config) CheckAllowedHTTPMethod(method string) error {
- if !c.HTTP.Methods.Accept(method) {
- return &AccessDeniedError{
- name: method,
- path: "security.http.method",
- policies: c.ToTOML(),
- }
- }
- return nil
-}
-
-// ToSecurityMap converts c to a map with 'security' as the root key.
-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]any)
- herrors.Must(json.Unmarshal(asJson, &m))
-
- // Add the root
- sec := map[string]any{
- "security": m,
- }
- return sec
-}
-
-// DecodeConfig creates a privacy Config from a given Hugo configuration.
-func DecodeConfig(cfg config.Provider) (Config, error) {
- sc := DefaultConfig
- if cfg.IsSet(securityConfigKey) {
- m := cfg.GetStringMap(securityConfigKey)
- dec, err := mapstructure.NewDecoder(
- &mapstructure.DecoderConfig{
- WeaklyTypedInput: true,
- Result: &sc,
- DecodeHook: stringSliceToWhitelistHook(),
- },
- )
- if err != nil {
- return sc, err
- }
-
- if err = dec.Decode(m); err != nil {
- return sc, err
- }
- }
-
- if !sc.EnableInlineShortcodes {
- // Legacy
- sc.EnableInlineShortcodes = cfg.GetBool("enableInlineShortcodes")
- }
-
- return sc, nil
-}
-
-func stringSliceToWhitelistHook() mapstructure.DecodeHookFuncType {
- return func(
- f reflect.Type,
- t reflect.Type,
- data any,
- ) (any, error) {
- if t != reflect.TypeOf(Whitelist{}) {
- return data, nil
- }
-
- wl := types.ToStringSlicePreserveString(data)
-
- return NewWhitelist(wl...)
- }
-}
-
-// AccessDeniedError represents a security policy conflict.
-type AccessDeniedError struct {
- path string
- name string
- policies string
-}
-
-func (e *AccessDeniedError) Error() string {
- return fmt.Sprintf("access denied: %q is not whitelisted in policy %q; the current security configuration is:\n\n%s\n\n", e.name, e.path, e.policies)
-}
-
-// IsAccessDenied reports whether err is an AccessDeniedError
-func IsAccessDenied(err error) bool {
- var notFoundErr *AccessDeniedError
- return errors.As(err, ¬FoundErr)
-}
diff --git a/config/security/securityConfig_test.go b/config/security/securityConfig_test.go
deleted file mode 100644
index faa05a97f..000000000
--- a/config/security/securityConfig_test.go
+++ /dev/null
@@ -1,167 +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\n [security.exec]\n allow = ['^(dart-)?sass(-embedded)?$', '^go$', '^git$', '^npx$', '^postcss$', '^tailwindcss$']\n osEnv = ['(?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\\w+|(XDG_CONFIG_)?HOME|USERPROFILE|SSH_AUTH_SOCK|DISPLAY|LANG|SYSTEMDRIVE)$']\n\n [security.funcs]\n getenv = ['^HUGO_', '^CI$']\n\n [security.http]\n methods = ['(?i)GET|POST']\n urls = ['.*']",
- )
-}
-
-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/whitelist.go b/config/security/whitelist.go
deleted file mode 100644
index 5ce369a1f..000000000
--- a/config/security/whitelist.go
+++ /dev/null
@@ -1,116 +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 (
- "encoding/json"
- "fmt"
- "regexp"
- "strings"
-)
-
-const (
- acceptNoneKeyword = "none"
-)
-
-// Whitelist holds a whitelist.
-type Whitelist struct {
- acceptNone bool
- patterns []*regexp.Regexp
-
- // Store this for debugging/error reporting
- patternsStrings []string
-}
-
-// MarshalJSON is for internal use only.
-func (w Whitelist) MarshalJSON() ([]byte, error) {
- if w.acceptNone {
- return json.Marshal(acceptNoneKeyword)
- }
-
- return json.Marshal(w.patternsStrings)
-}
-
-// 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 none.
-func NewWhitelist(patterns ...string) (Whitelist, error) {
- if len(patterns) == 0 {
- return Whitelist{acceptNone: true}, nil
- }
-
- var acceptSome bool
- var patternsStrings []string
-
- for _, p := range patterns {
- if p == acceptNoneKeyword {
- acceptSome = false
- break
- }
-
- if ps := strings.TrimSpace(p); ps != "" {
- acceptSome = true
- patternsStrings = append(patternsStrings, ps)
- }
- }
-
- if !acceptSome {
- return Whitelist{
- acceptNone: true,
- }, nil
- }
-
- var patternsr []*regexp.Regexp
-
- for i := range patterns {
- p := strings.TrimSpace(patterns[i])
- if p == "" {
- continue
- }
- 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}, 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
-}
-
-// Accept reports whether name is whitelisted.
-func (w Whitelist) Accept(name string) bool {
- if w.acceptNone {
- return false
- }
-
- for _, p := range w.patterns {
- if p.MatchString(name) {
- return true
- }
- }
- return false
-}
-
-func (w Whitelist) String() string {
- return fmt.Sprint(w.patternsStrings)
-}
diff --git a/config/security/whitelist_test.go b/config/security/whitelist_test.go
deleted file mode 100644
index add3345a8..000000000
--- a/config/security/whitelist_test.go
+++ /dev/null
@@ -1,46 +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 (
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestWhitelist(t *testing.T) {
- t.Parallel()
- c := qt.New(t)
-
- c.Run("none", func(c *qt.C) {
- 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 := MustNewWhitelist("^foo.*")
- c.Assert(w.Accept("foo"), qt.IsTrue)
- c.Assert(w.Accept("mfoo"), qt.IsFalse)
- })
-
- c.Run("Multiple", func(c *qt.C) {
- 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
deleted file mode 100644
index f9d5e1a6e..000000000
--- a/config/services/servicesConfig.go
+++ /dev/null
@@ -1,110 +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 services
-
-import (
- "github.com/gohugoio/hugo/config"
- "github.com/mitchellh/mapstructure"
-)
-
-const (
- servicesConfigKey = "services"
-
- disqusShortnameKey = "disqusshortname"
- googleAnalyticsKey = "googleanalytics"
- rssLimitKey = "rssLimit"
-)
-
-// Config is a privacy configuration for all the relevant services in Hugo.
-type Config struct {
- Disqus Disqus
- GoogleAnalytics GoogleAnalytics
- Instagram Instagram
- Twitter Twitter // deprecated in favor of X in v0.141.0
- X X
- RSS RSS
-}
-
-// Disqus holds the functional configuration settings related to the Disqus template.
-type Disqus struct {
- // A Shortname is the unique identifier assigned to a Disqus site.
- Shortname string
-}
-
-// GoogleAnalytics holds the functional configuration settings related to the Google Analytics template.
-type GoogleAnalytics struct {
- // The GA tracking ID.
- ID string
-}
-
-// Instagram holds the functional configuration settings related to the Instagram shortcodes.
-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
-
- // 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
-}
-
-// Twitter holds the functional configuration settings related to the Twitter shortcodes.
-// Deprecated in favor of X in v0.141.0.
-type Twitter struct {
- // The Simple variant of Twitter is decorated with a basic set of inline styles.
- // This means that if you want to provide your own CSS, you want
- // to disable the inline CSS provided by Hugo.
- DisableInlineCSS bool
-}
-
-// X holds the functional configuration settings related to the X shortcodes.
-type X struct {
- // The Simple variant of X is decorated with a basic set of inline styles.
- // 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.
- Limit int
-}
-
-// DecodeConfig creates a services Config from a given Hugo configuration.
-func DecodeConfig(cfg config.Provider) (c Config, err error) {
- m := cfg.GetStringMap(servicesConfigKey)
-
- err = mapstructure.WeakDecode(m, &c)
-
- // Keep backwards compatibility.
- if c.GoogleAnalytics.ID == "" {
- // Try the global config
- c.GoogleAnalytics.ID = cfg.GetString(googleAnalyticsKey)
- }
- if c.Disqus.Shortname == "" {
- c.Disqus.Shortname = cfg.GetString(disqusShortnameKey)
- }
-
- 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
deleted file mode 100644
index 952a7fe1c..000000000
--- a/config/services/servicesConfig_test.go
+++ /dev/null
@@ -1,69 +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 services
-
-import (
- "testing"
-
- qt "github.com/frankban/quicktest"
- "github.com/gohugoio/hugo/config"
-)
-
-func TestDecodeConfigFromTOML(t *testing.T) {
- c := qt.New(t)
-
- tomlConfig := `
-
-someOtherValue = "foo"
-
-[services]
-[services.disqus]
-shortname = "DS"
-[services.googleAnalytics]
-id = "ga_id"
-[services.instagram]
-disableInlineCSS = true
-[services.twitter]
-disableInlineCSS = true
-[services.x]
-disableInlineCSS = true
-`
- cfg, err := config.FromConfigString(tomlConfig, "toml")
- c.Assert(err, qt.IsNil)
-
- config, err := DecodeConfig(cfg)
- c.Assert(err, qt.IsNil)
- c.Assert(config, qt.Not(qt.IsNil))
-
- c.Assert(config.Disqus.Shortname, qt.Equals, "DS")
- c.Assert(config.GoogleAnalytics.ID, qt.Equals, "ga_id")
-
- c.Assert(config.Instagram.DisableInlineCSS, qt.Equals, true)
-}
-
-// Support old root-level GA settings etc.
-func TestUseSettingsFromRootIfSet(t *testing.T) {
- c := qt.New(t)
-
- cfg := config.New()
- cfg.Set("disqusShortname", "root_short")
- cfg.Set("googleAnalytics", "ga_root")
-
- config, err := DecodeConfig(cfg)
- c.Assert(err, qt.IsNil)
- c.Assert(config, qt.Not(qt.IsNil))
-
- c.Assert(config.Disqus.Shortname, qt.Equals, "root_short")
- c.Assert(config.GoogleAnalytics.ID, qt.Equals, "ga_root")
-}
diff --git a/config/testconfig/testconfig.go b/config/testconfig/testconfig.go
deleted file mode 100644
index 8f70e6cb7..000000000
--- a/config/testconfig/testconfig.go
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// This package should only be used for testing.
-package testconfig
-
-import (
- _ "unsafe"
-
- "github.com/gohugoio/hugo/common/maps"
- "github.com/gohugoio/hugo/config"
- "github.com/gohugoio/hugo/config/allconfig"
- "github.com/gohugoio/hugo/deps"
- "github.com/gohugoio/hugo/hugofs"
- 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()),
- }
- 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 := maps.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 a4661c1ba..8af417294 100644
--- a/create/content.go
+++ b/create/content.go
@@ -1,4 +1,4 @@
-// Copyright 2019 The Hugo Authors. All rights reserved.
+// 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.
@@ -16,387 +16,114 @@ package create
import (
"bytes"
- "errors"
- "fmt"
- "io"
"os"
+ "os/exec"
"path/filepath"
- "strings"
-
- "github.com/gohugoio/hugo/hugofs/glob"
-
- "github.com/gohugoio/hugo/common/hexec"
- "github.com/gohugoio/hugo/common/hstrings"
- "github.com/gohugoio/hugo/common/paths"
-
- "github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugolib"
- "github.com/spf13/afero"
+ jww "github.com/spf13/jwalterweatherman"
)
-const (
- // DefaultArchetypeTemplateTemplate is the template used in 'hugo new site'
- // and the template we use as a fall back.
- DefaultArchetypeTemplateTemplate = `---
-title: "{{ replace .File.ContentBaseName "-" " " | title }}"
-date: {{ .Date }}
-draft: true
----
+// NewContent creates a new content file in the content directory based upon the
+// given kind, which is used to lookup an archetype.
+func NewContent(
+ ps *helpers.PathSpec,
+ siteFactory func(filename string, siteUsed bool) (*hugolib.Site, error), kind, targetPath string) error {
+ ext := helpers.Ext(targetPath)
-`
-)
+ jww.INFO.Printf("attempting to create %q of %q of ext %q", targetPath, kind, ext)
-// 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, force bool) error {
- if _, err := h.BaseFs.Content.Fs.Stat(""); err != nil {
- return errors.New("no existing content directory configured for this project")
- }
+ archetypeFilename := findArchetype(ps, kind, ext)
- cf := hugolib.NewContentFactory(h)
+ // Building the sites can be expensive, so only do it if really needed.
+ siteUsed := false
- if kind == "" {
- var err error
- kind, err = cf.SectionFromFilename(targetPath)
+ if archetypeFilename != "" {
+ f, err := ps.Fs.Source.Open(archetypeFilename)
if err != nil {
return err
}
+ defer f.Close()
+
+ if helpers.ReaderContains(f, []byte(".Site")) {
+ siteUsed = true
+ }
}
- b := &contentBuilder{
- archeTypeFs: h.PathSpec.BaseFs.Archetypes.Fs,
- sourceFs: h.PathSpec.Fs.Source,
- ps: h.PathSpec,
- h: h,
- cf: cf,
-
- kind: kind,
- targetPath: targetPath,
- force: force,
- }
-
- ext := paths.Ext(targetPath)
-
- b.setArcheTypeFilenameToUse(ext)
-
- withBuildLock := func() (string, error) {
- 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()
- }
-
- if b.isDir {
- return "", b.buildDir()
- }
-
- if ext == "" {
- return "", fmt.Errorf("failed to resolve %q to an archetype template", 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()
+ s, err := siteFactory(targetPath, siteUsed)
if err != nil {
return err
}
- if filename != "" {
- return b.openInEditorIfConfigured(filename)
+ var content []byte
+
+ content, err = executeArcheTypeAsTemplate(s, kind, targetPath, archetypeFilename)
+ if err != nil {
+ return err
+ }
+
+ contentPath := s.PathSpec.AbsPathify(filepath.Join(s.Cfg.GetString("contentDir"), targetPath))
+
+ if err := helpers.SafeWriteToDisk(contentPath, bytes.NewReader(content), s.Fs.Source); err != nil {
+ return err
+ }
+
+ jww.FEEDBACK.Println(contentPath, "created")
+
+ editor := s.Cfg.GetString("newContentEditor")
+ if editor != "" {
+ jww.FEEDBACK.Printf("Editing %s with %q ...\n", targetPath, editor)
+
+ cmd := exec.Command(editor, contentPath)
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+
+ return cmd.Run()
}
return nil
}
-type contentBuilder struct {
- archeTypeFs afero.Fs
- sourceFs afero.Fs
+// FindArchetype takes a given kind/archetype of content and returns an output
+// path for that archetype. If no archetype is found, an empty string is
+// returned.
+func findArchetype(ps *helpers.PathSpec, kind, ext string) (outpath string) {
+ search := []string{ps.AbsPathify(ps.Cfg.GetString("archetypeDir"))}
- ps *helpers.PathSpec
- h *hugolib.HugoSites
- cf hugolib.ContentFactory
-
- // Builder state
- archetypeFi hugofs.FileMetaInfo
- targetPath string
- kind string
- isDir bool
- dirMap archetypeMap
- force bool
-}
-
-func (b *contentBuilder) buildDir() error {
- // Split the dir into content files and the rest.
- if err := b.mapArcheTypeDir(); err != nil {
- return err
+ if ps.Cfg.GetString("theme") != "" {
+ themeDir := filepath.Join(ps.AbsPathify(ps.Cfg.GetString("themesDir")+"/"+ps.Cfg.GetString("theme")), "/archetypes/")
+ if _, err := ps.Fs.Source.Stat(themeDir); os.IsNotExist(err) {
+ jww.ERROR.Printf("Unable to find archetypes directory for theme %q at %q", ps.Cfg.GetString("theme"), themeDir)
+ } else {
+ search = append(search, themeDir)
+ }
}
- var contentTargetFilenames []string
- var baseDir string
+ for _, x := range search {
+ // If the new content isn't in a subdirectory, kind == "".
+ // Therefore it should be excluded otherwise `is a directory`
+ // error will occur. github.com/gohugoio/hugo/issues/411
+ var pathsToCheck = []string{"default"}
- for _, fi := range b.dirMap.contentFiles {
-
- 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
- }
- if baseDir == "" {
- baseDir = strings.TrimSuffix(abs, targetFilename)
- }
-
- contentTargetFilenames = append(contentTargetFilenames, abs)
- }
-
- var contentInclusionFilter *glob.FilenameFilter
- if !b.dirMap.siteUsed {
- // We don't need to build everything.
- contentInclusionFilter = glob.NewFilenameFilterForInclusionFunc(func(filename string) bool {
- filename = strings.TrimPrefix(filename, string(os.PathSeparator))
- for _, cn := range contentTargetFilenames {
- if strings.Contains(cn, filename) {
- return true
- }
+ if ext != "" {
+ if kind != "" {
+ pathsToCheck = append([]string{kind + ext, "default" + ext}, pathsToCheck...)
+ } else {
+ pathsToCheck = append([]string{"default" + ext}, pathsToCheck...)
}
- return false
- })
- }
-
- if err := b.h.Build(hugolib.BuildCfg{NoBuildLock: true, SkipRender: true, ContentInclusionFilter: contentInclusionFilter}); err != nil {
- return err
- }
-
- for i, filename := range contentTargetFilenames {
- if err := b.applyArcheType(filename, b.dirMap.contentFiles[i]); err != nil {
- return err
- }
- }
-
- // Copy the rest as is.
- for _, fi := range b.dirMap.otherFiles {
- meta := fi.Meta()
-
- in, err := meta.Open()
- if err != nil {
- return fmt.Errorf("failed to open non-content file: %w", err)
- }
- 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 fmt.Errorf("failed to create target directory for %q: %w", targetDir, err)
}
- out, err := b.sourceFs.Create(targetFilename)
- if err != nil {
- return err
- }
-
- _, err = io.Copy(out, in)
- if err != nil {
- return err
- }
-
- in.Close()
- out.Close()
- }
-
- b.h.Log.Printf("Content dir %q created", filepath.Join(baseDir, b.targetPath))
-
- return nil
-}
-
-func (b *contentBuilder) buildFile() (string, error) {
- contentPlaceholderAbsFilename, err := b.cf.CreateContentPlaceHolder(b.targetPath, b.force)
- if err != nil {
- return "", err
- }
-
- usesSite, err := b.usesSiteVar(b.archetypeFi)
- if err != nil {
- return "", err
- }
-
- var contentInclusionFilter *glob.FilenameFilter
- if !usesSite {
- // We don't need to build everything.
- contentInclusionFilter = glob.NewFilenameFilterForInclusionFunc(func(filename string) bool {
- filename = strings.TrimPrefix(filename, string(os.PathSeparator))
- return strings.Contains(contentPlaceholderAbsFilename, filename)
- })
- }
-
- if err := b.h.Build(hugolib.BuildCfg{NoBuildLock: true, SkipRender: true, ContentInclusionFilter: contentInclusionFilter}); err != nil {
- return "", err
- }
-
- if err := b.applyArcheType(contentPlaceholderAbsFilename, b.archetypeFi); err != nil {
- return "", err
- }
-
- b.h.Log.Printf("Content %q created", contentPlaceholderAbsFilename)
-
- return contentPlaceholderAbsFilename, nil
-}
-
-func (b *contentBuilder) setArcheTypeFilenameToUse(ext string) {
- var pathsToCheck []string
-
- if b.kind != "" {
- pathsToCheck = append(pathsToCheck, b.kind+ext)
- }
-
- pathsToCheck = append(pathsToCheck, "default"+ext)
-
- for _, p := range pathsToCheck {
- fi, err := b.archeTypeFs.Stat(p)
- if err == nil {
- b.archetypeFi = fi.(hugofs.FileMetaInfo)
- b.isDir = fi.IsDir()
- return
- }
- }
-}
-
-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))
- }
-
- f, err := b.sourceFs.Create(contentFilename)
- if err != nil {
- return err
- }
- defer f.Close()
-
- if archetypeFi == nil {
- return b.cf.ApplyArchetypeTemplate(f, p, b.kind, DefaultArchetypeTemplateTemplate)
- }
-
- return b.cf.ApplyArchetypeFi(f, p, b.kind, archetypeFi)
-}
-
-func (b *contentBuilder) mapArcheTypeDir() error {
- var m archetypeMap
-
- seen := map[hstrings.Strings2]bool{}
-
- walkFn := func(path string, fim hugofs.FileMetaInfo) error {
- if fim.IsDir() {
- return nil
- }
-
- pi := fim.Meta().PathInfo
-
- if pi.IsContent() {
- pathLang := hstrings.Strings2{pi.PathBeforeLangAndOutputFormatAndExt(), fim.Meta().Lang}
- if seen[pathLang] {
- // Duplicate content file, e.g. page.md and page.html.
- // In the regular build, we will filter out the duplicates, but
- // for archetype folders these are ambiguous and we need to
- // fail.
- return fmt.Errorf("duplicate content file found in archetype folder: %q; having both e.g. %s.md and %s.html is ambigous", path, pi.BaseNameNoIdentifier(), pi.BaseNameNoIdentifier())
+ for _, p := range pathsToCheck {
+ curpath := filepath.Join(x, p)
+ jww.DEBUG.Println("checking", curpath, "for archetypes")
+ if exists, _ := helpers.Exists(curpath, ps.Fs.Source); exists {
+ jww.INFO.Println("curpath: " + curpath)
+ return curpath
}
- seen[pathLang] = true
- m.contentFiles = append(m.contentFiles, fim)
- if !m.siteUsed {
- var err error
- m.siteUsed, err = b.usesSiteVar(fim)
- if err != nil {
- return err
- }
- }
- return nil
}
-
- m.otherFiles = append(m.otherFiles, fim)
-
- return nil
}
- walkCfg := hugofs.WalkwayConfig{
- WalkFn: walkFn,
- Fs: b.archeTypeFs,
- Root: filepath.FromSlash(b.archetypeFi.Meta().PathInfo.Path()),
- }
-
- w := hugofs.NewWalkway(walkCfg)
-
- if err := w.Walk(); err != nil {
- return fmt.Errorf("failed to walk archetype dir %q: %w", b.archetypeFi.Meta().Filename, err)
- }
-
- b.dirMap = m
-
- return nil
-}
-
-func (b *contentBuilder) openInEditorIfConfigured(filename string) error {
- editor := b.h.Conf.NewContentEditor()
- if editor == "" {
- return nil
- }
-
- 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
- }
-
- return cmd.Run()
-}
-
-func (b *contentBuilder) usesSiteVar(fi hugofs.FileMetaInfo) (bool, error) {
- if fi == nil {
- return false, nil
- }
- f, err := fi.Meta().Open()
- if err != nil {
- return false, err
- }
- defer f.Close()
- bb, err := io.ReadAll(f)
- if err != nil {
- 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 {
- // These needs to be parsed and executed as Go templates.
- contentFiles []hugofs.FileMetaInfo
- // These are just copied to destination.
- otherFiles []hugofs.FileMetaInfo
- // If the templates needs a fully built site. This can potentially be
- // expensive, so only do when needed.
- siteUsed bool
+ return ""
}
diff --git a/create/content_template_handler.go b/create/content_template_handler.go
new file mode 100644
index 000000000..0be495d15
--- /dev/null
+++ b/create/content_template_handler.go
@@ -0,0 +1,135 @@
+// 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 create
+
+import (
+ "bytes"
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/gohugoio/hugo/helpers"
+ "github.com/gohugoio/hugo/source"
+
+ "github.com/gohugoio/hugo/hugolib"
+ "github.com/gohugoio/hugo/tpl"
+ "github.com/spf13/afero"
+)
+
+// ArchetypeFileData represents the data available to an archetype template.
+type ArchetypeFileData struct {
+ // The archetype content type, either given as --kind option or extracted
+ // from the target path's section, i.e. "blog/mypost.md" will resolve to
+ // "blog".
+ Type string
+
+ // The current date and time as a RFC3339 formatted string, suitable for use in front matter.
+ Date string
+
+ // The Site, fully equipped with all the pages etc. Note: This will only be set if it is actually
+ // used in the archetype template. Also, if this is a multilingual setup,
+ // this site is the site that best matches the target content file, based
+ // on the presence of language code in the filename.
+ Site *hugolib.Site
+
+ // The target content file. Note that the .Content will be empty, as that
+ // has not been created yet.
+ *source.File
+}
+
+const (
+ ArchetypeTemplateTemplate = `---
+title: "{{ replace .TranslationBaseName "-" " " | title }}"
+date: {{ .Date }}
+draft: true
+---
+
+`
+)
+
+var (
+ archetypeShortcodeReplacementsPre = strings.NewReplacer(
+ "{{<", "{x{<",
+ "{{%", "{x{%",
+ ">}}", ">}x}",
+ "%}}", "%}x}")
+
+ archetypeShortcodeReplacementsPost = strings.NewReplacer(
+ "{x{<", "{{<",
+ "{x{%", "{{%",
+ ">}x}", ">}}",
+ "%}x}", "%}}")
+)
+
+func executeArcheTypeAsTemplate(s *hugolib.Site, kind, targetPath, archetypeFilename string) ([]byte, error) {
+
+ var (
+ archetypeContent []byte
+ archetypeTemplate []byte
+ err error
+ )
+
+ sp := source.NewSourceSpec(s.Deps.Cfg, s.Deps.Fs)
+ f := sp.NewFile(targetPath)
+
+ data := ArchetypeFileData{
+ Type: kind,
+ Date: time.Now().Format(time.RFC3339),
+ File: f,
+ Site: s,
+ }
+
+ if archetypeFilename == "" {
+ // TODO(bep) archetype revive the issue about wrong tpl funcs arg order
+ archetypeTemplate = []byte(ArchetypeTemplateTemplate)
+ } else {
+ archetypeTemplate, err = afero.ReadFile(s.Fs.Source, archetypeFilename)
+ if err != nil {
+ return nil, fmt.Errorf("Failed to read archetype file %q: %s", archetypeFilename, err)
+ }
+
+ }
+
+ // The archetype template may contain shortcodes, and these does not play well
+ // with the Go templates. Need to set some temporary delimiters.
+ archetypeTemplate = []byte(archetypeShortcodeReplacementsPre.Replace(string(archetypeTemplate)))
+
+ // Reuse the Hugo template setup to get the template funcs properly set up.
+ templateHandler := s.Deps.Tmpl.(tpl.TemplateHandler)
+ templateName := "_text/" + helpers.Filename(archetypeFilename)
+ if err := templateHandler.AddTemplate(templateName, string(archetypeTemplate)); err != nil {
+ return nil, fmt.Errorf("Failed to parse archetype file %q: %s", archetypeFilename, err)
+ }
+
+ templ := templateHandler.Lookup(templateName)
+
+ var buff bytes.Buffer
+ if err := templ.Execute(&buff, data); err != nil {
+ return nil, fmt.Errorf("Failed to process archetype file %q: %s", archetypeFilename, err)
+ }
+
+ archetypeContent = []byte(archetypeShortcodeReplacementsPost.Replace(buff.String()))
+
+ if !bytes.Contains(archetypeContent, []byte("date")) || !bytes.Contains(archetypeContent, []byte("title")) {
+ // TODO(bep) remove some time in the future.
+ s.Log.FEEDBACK.Println(fmt.Sprintf(`WARNING: date and/or title missing from archetype file %q.
+From Hugo 0.24 this must be provided in the archetype file itself, if needed. Example:
+%s
+`, archetypeFilename, ArchetypeTemplateTemplate))
+
+ }
+
+ return archetypeContent, nil
+
+}
diff --git a/create/content_test.go b/create/content_test.go
index 429edfc26..914759164 100644
--- a/create/content_test.go
+++ b/create/content_test.go
@@ -14,150 +14,85 @@
package create_test
import (
- "fmt"
"os"
"path/filepath"
"strings"
"testing"
- "github.com/gohugoio/hugo/config"
- "github.com/gohugoio/hugo/config/allconfig"
- "github.com/gohugoio/hugo/config/testconfig"
-
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/hugolib"
+ "fmt"
+
"github.com/gohugoio/hugo/hugofs"
- qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/create"
"github.com/gohugoio/hugo/helpers"
"github.com/spf13/afero"
+ "github.com/spf13/viper"
+ "github.com/stretchr/testify/require"
)
-// TODO(bep) clean this up. Export the test site builder in Hugolib or something.
-func TestNewContentFromFile(t *testing.T) {
+func TestNewContent(t *testing.T) {
+ v := viper.New()
+ initViper(v)
+
cases := []struct {
- name string
kind string
path string
- expected any
+ expected []string
}{
- {"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`}},
- {"Post, unknown content filetype", "post", "post/sample-1.pdoc", false},
- {"Empty date", "emptydate", "post/sample-ed.md", []string{`title = "Empty Date Arch title"`, `test = "test1"`}},
- {"Archetype file not found", "stump", "stump/sample-2.md", []string{`title: "Sample 2"`}}, // no archetype file
- {"No archetype", "", "sample-3.md", []string{`title: "Sample 3"`}}, // no archetype
- {"Empty archetype", "product", "product/sample-4.md", []string{`title = "SAMPLE-4"`}}, // empty archetype front matter
- {"Filenames", "filenames", "content/mypage/index.md", []string{"title = \"INDEX\"\n+++\n\n\nContentBaseName: mypage"}},
- {"Branch Name", "name", "content/tags/tag-a/_index.md", []string{"+++\ntitle = 'Tag A'\n+++"}},
-
- {"Lang 1", "lang", "post/lang-1.md", []string{`Site Lang: en|Name: Lang 1|i18n: Hugo Rocks!`}},
- {"Lang 2", "lang", "post/lang-2.en.md", []string{`Site Lang: en|Name: Lang 2|i18n: Hugo Rocks!`}},
- {"Lang nn file", "lang", "content/post/lang-3.nn.md", []string{`Site Lang: nn|Name: Lang 3|i18n: Hugo Rokkar!`}},
- {"Lang nn dir", "lang", "content_nn/post/lang-4.md", []string{`Site Lang: nn|Name: Lang 4|i18n: Hugo Rokkar!`}},
- {"Lang en in nn dir", "lang", "content_nn/post/lang-5.en.md", []string{`Site Lang: en|Name: Lang 5|i18n: Hugo Rocks!`}},
- {"Lang en default", "lang", "post/my-bundle/index.md", []string{`Site Lang: en|Name: My Bundle|i18n: Hugo Rocks!`}},
- {"Lang en file", "lang", "post/my-bundle/index.en.md", []string{`Site Lang: en|Name: My Bundle|i18n: Hugo Rocks!`}},
- {"Lang nn bundle", "lang", "content/post/my-bundle/index.nn.md", []string{`Site Lang: nn|Name: My Bundle|i18n: Hugo Rokkar!`}},
- {"Site", "site", "content/mypage/index.md", []string{"RegularPages .Site: 10", "RegularPages site: 10"}},
- {"Shortcodes", "shortcodes", "shortcodes/go.md", []string{
+ {"post", "post/sample-1.md", []string{`title = "Post Arch title"`, `test = "test1"`, "date = \"2015-01-12T19:20:04-07:00\""}},
+ {"post", "post/org-1.org", []string{`#+title: ORG-1`}},
+ {"emptydate", "post/sample-ed.md", []string{`title = "Empty Date Arch title"`, `test = "test1"`}},
+ {"stump", "stump/sample-2.md", []string{`title: "Sample 2"`}}, // no archetype file
+ {"", "sample-3.md", []string{`title: "Sample 3"`}}, // no archetype
+ {"product", "product/sample-4.md", []string{`title = "SAMPLE-4"`}}, // empty archetype front matter
+ {"shortcodes", "shortcodes/go.md", []string{
`title = "GO"`,
"{{< myshortcode >}}",
"{{% myshortcode %}}",
- "{{* comment */>}}\n{{%/* comment */%}}",
- }}, // shortcodes
+ "{{* comment */>}}\n{{%/* comment */%}}"}}, // shortcodes
}
- c := qt.New(t)
+ for _, c := range cases {
+ cfg, fs := newTestCfg()
+ ps, err := helpers.NewPathSpec(fs, cfg)
+ require.NoError(t, err)
+ h, err := hugolib.NewHugoSites(deps.DepsCfg{Cfg: cfg, Fs: fs})
+ require.NoError(t, err)
+ require.NoError(t, initFs(fs))
- for i, cas := range cases {
- cas := cas
+ siteFactory := func(filename string, siteUsed bool) (*hugolib.Site, error) {
+ return h.Sites[0], nil
+ }
- c.Run(cas.name, func(c *qt.C) {
- c.Parallel()
+ require.NoError(t, create.NewContent(ps, siteFactory, c.kind, c.path))
- mm := afero.NewMemMapFs()
- c.Assert(initFs(mm), qt.IsNil)
- cfg, fs := newTestCfg(c, mm)
- conf := testconfig.GetTestConfigs(fs.Source, cfg)
- h, err := hugolib.NewHugoSites(deps.DepsCfg{Configs: conf, Fs: fs})
- c.Assert(err, qt.IsNil)
- err = create.NewContent(h, cas.kind, cas.path, false)
-
- if b, ok := cas.expected.(bool); ok && !b {
- if !b {
- c.Assert(err, qt.Not(qt.IsNil))
- }
- return
+ fname := filepath.Join("content", filepath.FromSlash(c.path))
+ content := readFileFromFs(t, fs.Source, fname)
+ for i, v := range c.expected {
+ found := strings.Contains(content, v)
+ if !found {
+ t.Errorf("[%d] %q missing from output:\n%q", i, v, content)
}
-
- c.Assert(err, qt.IsNil)
-
- fname := filepath.FromSlash(cas.path)
- if !strings.HasPrefix(fname, "content") {
- fname = filepath.Join("content", fname)
- }
-
- content := readFileFromFs(c, fs.Source, fname)
-
- for _, v := range cas.expected.([]string) {
- found := strings.Contains(content, v)
- if !found {
- c.Fatalf("[%d] %q missing from output:\n%q", i, v, content)
- }
- }
- })
-
+ }
}
}
-func TestNewContentFromDirSiteFunction(t *testing.T) {
- mm := afero.NewMemMapFs()
- c := qt.New(t)
-
- archetypeDir := filepath.Join("archetypes", "my-bundle")
- defaultArchetypeDir := filepath.Join("archetypes", "default")
- c.Assert(mm.MkdirAll(archetypeDir, 0o755), qt.IsNil)
- c.Assert(mm.MkdirAll(defaultArchetypeDir, 0o755), qt.IsNil)
-
- contentFile := `
-File: %s
-site RegularPages: {{ len site.RegularPages }}
-
-`
-
- 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)
-
- conf := testconfig.GetTestConfigs(fs.Source, cfg)
- h, err := hugolib.NewHugoSites(deps.DepsCfg{Configs: conf, 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", 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", 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", 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", false), qt.IsNil)
- cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "mypage.md")), `draft: true`)
+func initViper(v *viper.Viper) {
+ v.Set("metaDataFormat", "toml")
+ v.Set("archetypeDir", "archetypes")
+ v.Set("contentDir", "content")
+ v.Set("themesDir", "themes")
+ v.Set("layoutDir", "layouts")
+ v.Set("i18nDir", "i18n")
+ v.Set("theme", "sample")
}
-func initFs(fs afero.Fs) error {
- perm := os.FileMode(0o755)
+func initFs(fs *hugofs.Fs) error {
+ perm := os.FileMode(0755)
var err error
// create directories
@@ -167,22 +102,13 @@ func initFs(fs afero.Fs) error {
filepath.Join("themes", "sample", "archetypes"),
}
for _, dir := range dirs {
- err = fs.Mkdir(dir, perm)
- if err != nil && !os.IsExist(err) {
+ err = fs.Source.Mkdir(dir, perm)
+ if err != nil {
return err
}
}
- // create some dummy content
- for i := 1; i <= 10; i++ {
- filename := filepath.Join("content", fmt.Sprintf("page%d.md", i))
- afero.WriteFile(fs, filename, []byte(`---
-title: Test
----
-`), 0o666)
- }
-
- // create archetype files
+ // create files
for _, v := range []struct {
path string
content string
@@ -195,49 +121,16 @@ title: Test
path: filepath.Join("archetypes", "post.org"),
content: "#+title: {{ .BaseFileName | upper }}",
},
- {
- path: filepath.Join("archetypes", "name.md"),
- content: `+++
-title = '{{ replace .Name "-" " " | title }}'
-+++`,
- },
{
path: filepath.Join("archetypes", "product.md"),
content: `+++
title = "{{ .BaseFileName | upper }}"
+++`,
- },
- {
- path: filepath.Join("archetypes", "filenames.md"),
- content: `...
-title = "{{ .BaseFileName | upper }}"
-+++
-
-
-ContentBaseName: {{ .File.ContentBaseName }}
-
-`,
- },
- {
- path: filepath.Join("archetypes", "site.md"),
- content: `...
-title = "{{ .BaseFileName | upper }}"
-+++
-
-Len RegularPages .Site: {{ len .Site.RegularPages }}
-Len RegularPages site: {{ len site.RegularPages }}
-
-
-`,
},
{
path: filepath.Join("archetypes", "emptydate.md"),
content: "+++\ndate =\"\"\ntitle = \"Empty Date Arch title\"\ntest = \"test1\"\n+++\n",
},
- {
- path: filepath.Join("archetypes", "lang.md"),
- content: `Site Lang: {{ site.Language.Lang }}|Name: {{ replace .Name "-" " " | title }}|i18n: {{ T "hugo" }}`,
- },
// #3623x
{
path: filepath.Join("archetypes", "shortcodes.md"),
@@ -257,7 +150,7 @@ Some text.
`,
},
} {
- f, err := fs.Create(v.path)
+ f, err := fs.Source.Create(v.path)
if err != nil {
return err
}
@@ -272,15 +165,8 @@ Some text.
return nil
}
-func cContains(c *qt.C, v any, matches ...string) {
- for _, m := range matches {
- c.Assert(v, qt.Contains, m)
- }
-}
-
// TODO(bep) extract common testing package with this and some others
-func readFileFromFs(t testing.TB, fs afero.Fs, filename string) string {
- t.Helper()
+func readFileFromFs(t *testing.T, fs afero.Fs, filename string) string {
filename = filepath.FromSlash(filename)
b, err := afero.ReadFile(fs, filename)
if err != nil {
@@ -298,48 +184,15 @@ func readFileFromFs(t testing.TB, fs afero.Fs, filename string) string {
return string(b)
}
-func newTestCfg(c *qt.C, mm afero.Fs) (config.Provider, *hugofs.Fs) {
- cfg := `
+func newTestCfg() (*viper.Viper, *hugofs.Fs) {
-theme = "mytheme"
-[languages]
-[languages.en]
-weight = 1
-languageName = "English"
-[languages.nn]
-weight = 2
-languageName = "Nynorsk"
+ v := viper.New()
+ fs := hugofs.NewMem(v)
-[module]
-[[module.mounts]]
- source = 'archetypes'
- target = 'archetypes'
-[[module.mounts]]
- source = 'content'
- target = 'content'
- lang = 'en'
-[[module.mounts]]
- source = 'content_nn'
- target = 'content'
- lang = 'nn'
-`
- if mm == nil {
- mm = afero.NewMemMapFs()
- }
+ v.SetFs(fs.Source)
- mm.MkdirAll(filepath.FromSlash("content_nn"), 0o777)
+ initViper(v)
- mm.MkdirAll(filepath.FromSlash("themes/mytheme"), 0o777)
+ return v, fs
- c.Assert(afero.WriteFile(mm, filepath.Join("i18n", "en.toml"), []byte(`[hugo]
-other = "Hugo Rocks!"`), 0o755), qt.IsNil)
- c.Assert(afero.WriteFile(mm, filepath.Join("i18n", "nn.toml"), []byte(`[hugo]
-other = "Hugo Rokkar!"`), 0o755), qt.IsNil)
-
- c.Assert(afero.WriteFile(mm, "config.toml", []byte(cfg), 0o755), qt.IsNil)
-
- res, err := allconfig.LoadConfig(allconfig.ConfigSourceDescriptor{Fs: mm, Filename: "config.toml"})
- c.Assert(err, qt.IsNil)
-
- return res.LoadingInfo.Cfg, hugofs.NewFrom(mm, res.LoadingInfo.BaseConfig)
}
diff --git a/create/skeletons/site/assets/.gitkeep b/create/skeletons/site/assets/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/create/skeletons/site/content/.gitkeep b/create/skeletons/site/content/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/create/skeletons/site/data/.gitkeep b/create/skeletons/site/data/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/create/skeletons/site/i18n/.gitkeep b/create/skeletons/site/i18n/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/create/skeletons/site/layouts/.gitkeep b/create/skeletons/site/layouts/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/create/skeletons/site/static/.gitkeep b/create/skeletons/site/static/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/create/skeletons/site/themes/.gitkeep b/create/skeletons/site/themes/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/create/skeletons/skeletons.go b/create/skeletons/skeletons.go
deleted file mode 100644
index a6241ef92..000000000
--- a/create/skeletons/skeletons.go
+++ /dev/null
@@ -1,182 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package 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:site/*
-var siteFs 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)
-
- siteConfig := map[string]any{
- "baseURL": "https://example.org/",
- "languageCode": "en-US",
- "title": "My New Hugo Site",
- "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 := createSiteConfig(sourceFs, createpath, siteConfig, 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)
-}
-
-// CreateSite creates a site skeleton.
-func CreateSite(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(siteFs, ".", 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")
- }
- }
- }
- }
-
- siteConfig := map[string]any{
- "baseURL": "https://example.org/",
- "title": "My New Hugo Site",
- "languageCode": "en-us",
- }
-
- err := createSiteConfig(sourceFs, createpath, siteConfig, 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, siteFs)
-}
-
-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 createSiteConfig(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/main.css b/create/skeletons/theme/assets/css/main.css
deleted file mode 100644
index 166ade924..000000000
--- a/create/skeletons/theme/assets/css/main.css
+++ /dev/null
@@ -1,22 +0,0 @@
-body {
- color: #222;
- font-family: sans-serif;
- line-height: 1.5;
- margin: 1rem;
- max-width: 768px;
-}
-
-header {
- border-bottom: 1px solid #222;
- margin-bottom: 1rem;
-}
-
-footer {
- border-top: 1px solid #222;
- margin-top: 1rem;
-}
-
-a {
- color: #00e;
- text-decoration: none;
-}
diff --git a/create/skeletons/theme/assets/js/main.js b/create/skeletons/theme/assets/js/main.js
deleted file mode 100644
index e2aac5275..000000000
--- a/create/skeletons/theme/assets/js/main.js
+++ /dev/null
@@ -1 +0,0 @@
-console.log('This site was generated by Hugo.');
diff --git a/create/skeletons/theme/content/_index.md b/create/skeletons/theme/content/_index.md
deleted file mode 100644
index 652623b57..000000000
--- a/create/skeletons/theme/content/_index.md
+++ /dev/null
@@ -1,9 +0,0 @@
-+++
-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
deleted file mode 100644
index e7066c092..000000000
--- a/create/skeletons/theme/content/posts/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
-+++
-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
deleted file mode 100644
index 3e3fc6b25..000000000
--- a/create/skeletons/theme/content/posts/post-1.md
+++ /dev/null
@@ -1,10 +0,0 @@
-+++
-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
deleted file mode 100644
index 22b828769..000000000
--- a/create/skeletons/theme/content/posts/post-2.md
+++ /dev/null
@@ -1,10 +0,0 @@
-+++
-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
deleted file mode 100644
index 9a923bea0..000000000
Binary files a/create/skeletons/theme/content/posts/post-3/bryce-canyon.jpg and /dev/null differ
diff --git a/create/skeletons/theme/content/posts/post-3/index.md b/create/skeletons/theme/content/posts/post-3/index.md
deleted file mode 100644
index ca42a664b..000000000
--- a/create/skeletons/theme/content/posts/post-3/index.md
+++ /dev/null
@@ -1,12 +0,0 @@
-+++
-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
deleted file mode 100644
index e69de29bb..000000000
diff --git a/create/skeletons/theme/i18n/.gitkeep b/create/skeletons/theme/i18n/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/create/skeletons/theme/layouts/_partials/footer.html b/create/skeletons/theme/layouts/_partials/footer.html
deleted file mode 100644
index a7cd916d0..000000000
--- a/create/skeletons/theme/layouts/_partials/footer.html
+++ /dev/null
@@ -1 +0,0 @@
-
- {{ end }}
-{{ end }}
diff --git a/create/skeletons/theme/static/favicon.ico b/create/skeletons/theme/static/favicon.ico
deleted file mode 100644
index 67f8b7778..000000000
Binary files a/create/skeletons/theme/static/favicon.ico and /dev/null differ
diff --git a/deploy/cloudfront.go b/deploy/cloudfront.go
deleted file mode 100644
index 3202a73ea..000000000
--- a/deploy/cloudfront.go
+++ /dev/null
@@ -1,72 +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.
-
-//go:build withdeploy
-
-package deploy
-
-import (
- "context"
- "net/url"
- "time"
-
- "github.com/aws/aws-sdk-go-v2/aws"
- "github.com/aws/aws-sdk-go-v2/service/cloudfront"
- "github.com/aws/aws-sdk-go-v2/service/cloudfront/types"
- "github.com/gohugoio/hugo/deploy/deployconfig"
- gcaws "gocloud.dev/aws"
-)
-
-// V2ConfigFromURLParams will fail for any unknown params, so we need to remove them.
-// This is a mysterious API, but inspecting the code the known params are:
-var v2ConfigValidParams = map[string]bool{
- "endpoint": true,
- "region": true,
- "profile": true,
- "awssdk": true,
-}
-
-// InvalidateCloudFront invalidates the CloudFront cache for distributionID.
-// Uses AWS credentials config from the bucket URL.
-func InvalidateCloudFront(ctx context.Context, target *deployconfig.Target) error {
- u, err := url.Parse(target.URL)
- if err != nil {
- return err
- }
- vals := u.Query()
-
- // Remove any unknown params.
- for k := range vals {
- if !v2ConfigValidParams[k] {
- vals.Del(k)
- }
- }
-
- cfg, err := gcaws.V2ConfigFromURLParams(ctx, vals)
- if err != nil {
- return err
- }
- cf := cloudfront.NewFromConfig(cfg)
- req := &cloudfront.CreateInvalidationInput{
- DistributionId: aws.String(target.CloudFrontDistributionID),
- InvalidationBatch: &types.InvalidationBatch{
- CallerReference: aws.String(time.Now().Format("20060102150405")),
- Paths: &types.Paths{
- Items: []string{"/*"},
- Quantity: aws.Int32(1),
- },
- },
- }
- _, err = cf.CreateInvalidation(ctx, req)
- return err
-}
diff --git a/deploy/deploy.go b/deploy/deploy.go
deleted file mode 100644
index 57e1f41a2..000000000
--- a/deploy/deploy.go
+++ /dev/null
@@ -1,763 +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.
-
-//go:build withdeploy
-
-package deploy
-
-import (
- "bytes"
- "compress/gzip"
- "context"
- "crypto/md5"
- "encoding/hex"
- "errors"
- "fmt"
- "io"
- "mime"
- "os"
- "path/filepath"
- "regexp"
- "runtime"
- "sort"
- "strings"
- "sync"
-
- "github.com/dustin/go-humanize"
- "github.com/gobwas/glob"
- "github.com/gohugoio/hugo/common/loggers"
- "github.com/gohugoio/hugo/common/para"
- "github.com/gohugoio/hugo/config"
- "github.com/gohugoio/hugo/deploy/deployconfig"
- "github.com/gohugoio/hugo/media"
- "github.com/spf13/afero"
- "golang.org/x/text/unicode/norm"
-
- "gocloud.dev/blob"
- _ "gocloud.dev/blob/fileblob" // import
- _ "gocloud.dev/blob/gcsblob" // import
- _ "gocloud.dev/blob/s3blob" // import
- "gocloud.dev/gcerrors"
-)
-
-// Deployer supports deploying the site to target cloud providers.
-type Deployer struct {
- localFs afero.Fs
- bucket *blob.Bucket
-
- mediaTypes media.Types // Hugo's MediaType to guess ContentType
- quiet bool // true reduces STDOUT // TODO(bep) remove, this is a global feature.
-
- cfg deployconfig.DeployConfig
- logger loggers.Logger
-
- target *deployconfig.Target // the target to deploy to
-
- // For tests...
- summary deploySummary // summary of latest Deploy results
-}
-
-type deploySummary struct {
- NumLocal, NumRemote, NumUploads, NumDeletes int
-}
-
-const metaMD5Hash = "md5chksum" // the meta key to store md5hash in
-
-// New constructs a new *Deployer.
-func New(cfg config.AllProvider, logger loggers.Logger, localFs afero.Fs) (*Deployer, error) {
- dcfg := cfg.GetConfigSection(deployconfig.DeploymentConfigKey).(deployconfig.DeployConfig)
- targetName := dcfg.Target
-
- if len(dcfg.Targets) == 0 {
- return nil, errors.New("no deployment targets found")
- }
- mediaTypes := cfg.GetConfigSection("mediaTypes").(media.Types)
-
- // Find the target to deploy to.
- var tgt *deployconfig.Target
- if targetName == "" {
- // Default to the first target.
- tgt = dcfg.Targets[0]
- } else {
- for _, t := range dcfg.Targets {
- if t.Name == targetName {
- tgt = t
- }
- }
- if tgt == nil {
- return nil, fmt.Errorf("deployment target %q not found", targetName)
- }
- }
-
- return &Deployer{
- localFs: localFs,
- target: tgt,
- quiet: cfg.BuildExpired(),
- mediaTypes: mediaTypes,
- cfg: dcfg,
- }, nil
-}
-
-func (d *Deployer) openBucket(ctx context.Context) (*blob.Bucket, error) {
- if d.bucket != nil {
- return d.bucket, nil
- }
- d.logger.Printf("Deploying to target %q (%s)\n", d.target.Name, d.target.URL)
- return blob.OpenBucket(ctx, d.target.URL)
-}
-
-// Deploy deploys the site to a target.
-func (d *Deployer) Deploy(ctx context.Context) error {
- if d.logger == nil {
- d.logger = loggers.NewDefault()
- }
-
- bucket, err := d.openBucket(ctx)
- if err != nil {
- return err
- }
-
- if d.cfg.Workers <= 0 {
- d.cfg.Workers = 10
- }
-
- // Load local files from the source directory.
- var include, exclude glob.Glob
- var mappath func(string) string
- if d.target != nil {
- include, exclude = d.target.IncludeGlob, d.target.ExcludeGlob
- if d.target.StripIndexHTML {
- mappath = stripIndexHTML
- }
- }
- local, err := d.walkLocal(d.localFs, d.cfg.Matchers, include, exclude, d.mediaTypes, mappath)
- if err != nil {
- return err
- }
- d.logger.Infof("Found %d local files.\n", len(local))
- d.summary.NumLocal = len(local)
-
- // Load remote files from the target.
- remote, err := d.walkRemote(ctx, bucket, include, exclude)
- if err != nil {
- return err
- }
- d.logger.Infof("Found %d remote files.\n", len(remote))
- d.summary.NumRemote = len(remote)
-
- // Diff local vs remote to see what changes need to be applied.
- uploads, deletes := d.findDiffs(local, remote, d.cfg.Force)
- d.summary.NumUploads = len(uploads)
- d.summary.NumDeletes = len(deletes)
- if len(uploads)+len(deletes) == 0 {
- if !d.quiet {
- d.logger.Println("No changes required.")
- }
- return nil
- }
- if !d.quiet {
- d.logger.Println(summarizeChanges(uploads, deletes))
- }
-
- // Ask for confirmation before proceeding.
- if d.cfg.Confirm && !d.cfg.DryRun {
- fmt.Printf("Continue? (Y/n) ")
- var confirm string
- if _, err := fmt.Scanln(&confirm); err != nil {
- return err
- }
- if confirm != "" && confirm[0] != 'y' && confirm[0] != 'Y' {
- return errors.New("aborted")
- }
- }
-
- // Order the uploads. They are organized in groups; all uploads in a group
- // must be complete before moving on to the next group.
- uploadGroups := applyOrdering(d.cfg.Ordering, uploads)
-
- nParallel := d.cfg.Workers
- var errs []error
- var errMu sync.Mutex // protects errs
-
- for _, uploads := range uploadGroups {
- // Short-circuit for an empty group.
- if len(uploads) == 0 {
- continue
- }
-
- // Within the group, apply uploads in parallel.
- sem := make(chan struct{}, nParallel)
- for _, upload := range uploads {
- if d.cfg.DryRun {
- if !d.quiet {
- d.logger.Printf("[DRY RUN] Would upload: %v\n", upload)
- }
- continue
- }
-
- sem <- struct{}{}
- go func(upload *fileToUpload) {
- if err := d.doSingleUpload(ctx, bucket, upload); err != nil {
- errMu.Lock()
- defer errMu.Unlock()
- errs = append(errs, err)
- }
- <-sem
- }(upload)
- }
- // Wait for all uploads in the group to finish.
- for n := nParallel; n > 0; n-- {
- sem <- struct{}{}
- }
- }
-
- if d.cfg.MaxDeletes != -1 && len(deletes) > d.cfg.MaxDeletes {
- d.logger.Warnf("Skipping %d deletes because it is more than --maxDeletes (%d). If this is expected, set --maxDeletes to a larger number, or -1 to disable this check.\n", len(deletes), d.cfg.MaxDeletes)
- d.summary.NumDeletes = 0
- } else {
- // Apply deletes in parallel.
- sort.Slice(deletes, func(i, j int) bool { return deletes[i] < deletes[j] })
- sem := make(chan struct{}, nParallel)
- for _, del := range deletes {
- if d.cfg.DryRun {
- if !d.quiet {
- d.logger.Printf("[DRY RUN] Would delete %s\n", del)
- }
- continue
- }
- sem <- struct{}{}
- go func(del string) {
- d.logger.Infof("Deleting %s...\n", del)
- if err := bucket.Delete(ctx, del); err != nil {
- if gcerrors.Code(err) == gcerrors.NotFound {
- d.logger.Warnf("Failed to delete %q because it wasn't found: %v", del, err)
- } else {
- errMu.Lock()
- defer errMu.Unlock()
- errs = append(errs, err)
- }
- }
- <-sem
- }(del)
- }
- // Wait for all deletes to finish.
- for n := nParallel; n > 0; n-- {
- sem <- struct{}{}
- }
- }
-
- if len(errs) > 0 {
- if !d.quiet {
- d.logger.Printf("Encountered %d errors.\n", len(errs))
- }
- return errs[0]
- }
- if !d.quiet {
- d.logger.Println("Success!")
- }
-
- if d.cfg.InvalidateCDN {
- if d.target.CloudFrontDistributionID != "" {
- if d.cfg.DryRun {
- if !d.quiet {
- d.logger.Printf("[DRY RUN] Would invalidate CloudFront CDN with ID %s\n", d.target.CloudFrontDistributionID)
- }
- } else {
- d.logger.Println("Invalidating CloudFront CDN...")
- if err := InvalidateCloudFront(ctx, d.target); err != nil {
- d.logger.Printf("Failed to invalidate CloudFront CDN: %v\n", err)
- return err
- }
- }
- }
- if d.target.GoogleCloudCDNOrigin != "" {
- if d.cfg.DryRun {
- if !d.quiet {
- d.logger.Printf("[DRY RUN] Would invalidate Google Cloud CDN with origin %s\n", d.target.GoogleCloudCDNOrigin)
- }
- } else {
- d.logger.Println("Invalidating Google Cloud CDN...")
- if err := InvalidateGoogleCloudCDN(ctx, d.target.GoogleCloudCDNOrigin); err != nil {
- d.logger.Printf("Failed to invalidate Google Cloud CDN: %v\n", err)
- return err
- }
- }
- }
- d.logger.Println("Success!")
- }
- return nil
-}
-
-// summarizeChanges creates a text description of the proposed changes.
-func summarizeChanges(uploads []*fileToUpload, deletes []string) string {
- uploadSize := int64(0)
- for _, u := range uploads {
- uploadSize += u.Local.UploadSize
- }
- return fmt.Sprintf("Identified %d file(s) to upload, totaling %s, and %d file(s) to delete.", len(uploads), humanize.Bytes(uint64(uploadSize)), len(deletes))
-}
-
-// doSingleUpload executes a single file upload.
-func (d *Deployer) doSingleUpload(ctx context.Context, bucket *blob.Bucket, upload *fileToUpload) error {
- d.logger.Infof("Uploading %v...\n", upload)
- opts := &blob.WriterOptions{
- CacheControl: upload.Local.CacheControl(),
- ContentEncoding: upload.Local.ContentEncoding(),
- ContentType: upload.Local.ContentType(),
- Metadata: map[string]string{metaMD5Hash: hex.EncodeToString(upload.Local.MD5())},
- }
- w, err := bucket.NewWriter(ctx, upload.Local.SlashPath, opts)
- if err != nil {
- return err
- }
- r, err := upload.Local.Reader()
- if err != nil {
- return err
- }
- defer r.Close()
- _, err = io.Copy(w, r)
- if err != nil {
- return err
- }
- if err := w.Close(); err != nil {
- return err
- }
- return nil
-}
-
-// localFile represents a local file from the source. Use newLocalFile to
-// construct one.
-type localFile struct {
- // NativePath is the native path to the file (using file.Separator).
- NativePath string
- // SlashPath is NativePath converted to use /.
- SlashPath string
- // UploadSize is the size of the content to be uploaded. It may not
- // be the same as the local file size if the content will be
- // gzipped before upload.
- UploadSize int64
-
- fs afero.Fs
- matcher *deployconfig.Matcher
- md5 []byte // cache
- gzipped bytes.Buffer // cached of gzipped contents if gzipping
- mediaTypes media.Types
-}
-
-// newLocalFile initializes a *localFile.
-func newLocalFile(fs afero.Fs, nativePath, slashpath string, m *deployconfig.Matcher, mt media.Types) (*localFile, error) {
- f, err := fs.Open(nativePath)
- if err != nil {
- return nil, err
- }
- defer f.Close()
- lf := &localFile{
- NativePath: nativePath,
- SlashPath: slashpath,
- fs: fs,
- matcher: m,
- mediaTypes: mt,
- }
- if m != nil && m.Gzip {
- // We're going to gzip the content. Do it once now, and cache the result
- // in gzipped. The UploadSize is the size of the gzipped content.
- gz := gzip.NewWriter(&lf.gzipped)
- if _, err := io.Copy(gz, f); err != nil {
- return nil, err
- }
- if err := gz.Close(); err != nil {
- return nil, err
- }
- lf.UploadSize = int64(lf.gzipped.Len())
- } else {
- // Raw content. Just get the UploadSize.
- info, err := f.Stat()
- if err != nil {
- return nil, err
- }
- lf.UploadSize = info.Size()
- }
- return lf, nil
-}
-
-// Reader returns an io.ReadCloser for reading the content to be uploaded.
-// The caller must call Close on the returned ReaderCloser.
-// The reader content may not be the same as the local file content due to
-// gzipping.
-func (lf *localFile) Reader() (io.ReadCloser, error) {
- if lf.matcher != nil && lf.matcher.Gzip {
- // We've got the gzipped contents cached in gzipped.
- // Note: we can't use lf.gzipped directly as a Reader, since we it discards
- // data after it is read, and we may read it more than once.
- return io.NopCloser(bytes.NewReader(lf.gzipped.Bytes())), nil
- }
- // Not expected to fail since we did it successfully earlier in newLocalFile,
- // but could happen due to changes in the underlying filesystem.
- return lf.fs.Open(lf.NativePath)
-}
-
-// CacheControl returns the Cache-Control header to use for lf, based on the
-// first matching matcher (if any).
-func (lf *localFile) CacheControl() string {
- if lf.matcher == nil {
- return ""
- }
- return lf.matcher.CacheControl
-}
-
-// ContentEncoding returns the Content-Encoding header to use for lf, based
-// on the matcher's Content-Encoding and Gzip fields.
-func (lf *localFile) ContentEncoding() string {
- if lf.matcher == nil {
- return ""
- }
- if lf.matcher.Gzip {
- return "gzip"
- }
- return lf.matcher.ContentEncoding
-}
-
-// ContentType returns the Content-Type header to use for lf.
-// It first checks if there's a Content-Type header configured via a matching
-// matcher; if not, it tries to generate one based on the filename extension.
-// If this fails, the Content-Type will be the empty string. In this case, Go
-// Cloud will automatically try to infer a Content-Type based on the file
-// content.
-func (lf *localFile) ContentType() string {
- if lf.matcher != nil && lf.matcher.ContentType != "" {
- return lf.matcher.ContentType
- }
-
- ext := filepath.Ext(lf.NativePath)
- if mimeType, _, found := lf.mediaTypes.GetFirstBySuffix(strings.TrimPrefix(ext, ".")); found {
- return mimeType.Type
- }
-
- return mime.TypeByExtension(ext)
-}
-
-// Force returns true if the file should be forced to re-upload based on the
-// matching matcher.
-func (lf *localFile) Force() bool {
- return lf.matcher != nil && lf.matcher.Force
-}
-
-// MD5 returns an MD5 hash of the content to be uploaded.
-func (lf *localFile) MD5() []byte {
- if len(lf.md5) > 0 {
- return lf.md5
- }
- h := md5.New()
- r, err := lf.Reader()
- if err != nil {
- return nil
- }
- defer r.Close()
- if _, err := io.Copy(h, r); err != nil {
- return nil
- }
- lf.md5 = h.Sum(nil)
- return lf.md5
-}
-
-// knownHiddenDirectory checks if the specified name is a well known
-// hidden directory.
-func knownHiddenDirectory(name string) bool {
- knownDirectories := []string{
- ".well-known",
- }
-
- for _, dir := range knownDirectories {
- if name == dir {
- return true
- }
- }
- return false
-}
-
-// walkLocal walks the source directory and returns a flat list of files,
-// using localFile.SlashPath as the map keys.
-func (d *Deployer) walkLocal(fs afero.Fs, matchers []*deployconfig.Matcher, include, exclude glob.Glob, mediaTypes media.Types, mappath func(string) string) (map[string]*localFile, error) {
- retval := make(map[string]*localFile)
- var mu sync.Mutex
-
- workers := para.New(d.cfg.Workers)
- g, _ := workers.Start(context.Background())
-
- err := afero.Walk(fs, "", func(path string, info os.FileInfo, err error) error {
- if err != nil {
- return err
- }
- if info.IsDir() {
- // Skip hidden directories.
- if path != "" && strings.HasPrefix(info.Name(), ".") {
- // Except for specific hidden directories
- if !knownHiddenDirectory(info.Name()) {
- return filepath.SkipDir
- }
- }
- return nil
- }
-
- // .DS_Store is an internal MacOS attribute file; skip it.
- if info.Name() == ".DS_Store" {
- return nil
- }
-
- // Process each file in a worker
- g.Run(func() error {
- // When a file system is HFS+, its filepath is in NFD form.
- if runtime.GOOS == "darwin" {
- path = norm.NFC.String(path)
- }
-
- // Check include/exclude matchers.
- slashpath := filepath.ToSlash(path)
- if include != nil && !include.Match(slashpath) {
- d.logger.Infof(" dropping %q due to include\n", slashpath)
- return nil
- }
- if exclude != nil && exclude.Match(slashpath) {
- d.logger.Infof(" dropping %q due to exclude\n", slashpath)
- return nil
- }
-
- // Find the first matching matcher (if any).
- var m *deployconfig.Matcher
- for _, cur := range matchers {
- if cur.Matches(slashpath) {
- m = cur
- break
- }
- }
- // Apply any additional modifications to the local path, to map it to
- // the remote path.
- if mappath != nil {
- slashpath = mappath(slashpath)
- }
- lf, err := newLocalFile(fs, path, slashpath, m, mediaTypes)
- if err != nil {
- return err
- }
- mu.Lock()
- retval[lf.SlashPath] = lf
- mu.Unlock()
- return nil
- })
- return nil
- })
- if err != nil {
- return nil, err
- }
- if err := g.Wait(); err != nil {
- return nil, err
- }
- return retval, nil
-}
-
-// stripIndexHTML remaps keys matching "/index.html" to "/".
-func stripIndexHTML(slashpath string) string {
- const suffix = "/index.html"
- if strings.HasSuffix(slashpath, suffix) {
- return slashpath[:len(slashpath)-len(suffix)+1]
- }
- return slashpath
-}
-
-// walkRemote walks the target bucket and returns a flat list.
-func (d *Deployer) walkRemote(ctx context.Context, bucket *blob.Bucket, include, exclude glob.Glob) (map[string]*blob.ListObject, error) {
- retval := map[string]*blob.ListObject{}
- iter := bucket.List(nil)
- for {
- obj, err := iter.Next(ctx)
- if err == io.EOF {
- break
- }
- if err != nil {
- return nil, err
- }
- // Check include/exclude matchers.
- if include != nil && !include.Match(obj.Key) {
- d.logger.Infof(" remote dropping %q due to include\n", obj.Key)
- continue
- }
- if exclude != nil && exclude.Match(obj.Key) {
- d.logger.Infof(" remote dropping %q due to exclude\n", obj.Key)
- continue
- }
- // If the remote didn't give us an MD5, use remote attributes MD5, if that doesn't exist compute one.
- // This can happen for some providers (e.g., fileblob, which uses the
- // local filesystem), but not for the most common Cloud providers
- // (S3, GCS, Azure). Although, it can happen for S3 if the blob was uploaded
- // via a multi-part upload.
- // Although it's unfortunate to have to read the file, it's likely better
- // than assuming a delta and re-uploading it.
- if len(obj.MD5) == 0 {
- var attrMD5 []byte
- attrs, err := bucket.Attributes(ctx, obj.Key)
- if err == nil {
- md5String, exists := attrs.Metadata[metaMD5Hash]
- if exists {
- attrMD5, _ = hex.DecodeString(md5String)
- }
- }
- if len(attrMD5) == 0 {
- r, err := bucket.NewReader(ctx, obj.Key, nil)
- if err == nil {
- h := md5.New()
- if _, err := io.Copy(h, r); err == nil {
- obj.MD5 = h.Sum(nil)
- }
- r.Close()
- }
- } else {
- obj.MD5 = attrMD5
- }
- }
- retval[obj.Key] = obj
- }
- return retval, nil
-}
-
-// uploadReason is an enum of reasons why a file must be uploaded.
-type uploadReason string
-
-const (
- reasonUnknown uploadReason = "unknown"
- reasonNotFound uploadReason = "not found at target"
- reasonForce uploadReason = "--force"
- reasonSize uploadReason = "size differs"
- reasonMD5Differs uploadReason = "md5 differs"
- reasonMD5Missing uploadReason = "remote md5 missing"
-)
-
-// fileToUpload represents a single local file that should be uploaded to
-// the target.
-type fileToUpload struct {
- Local *localFile
- Reason uploadReason
-}
-
-func (u *fileToUpload) String() string {
- details := []string{humanize.Bytes(uint64(u.Local.UploadSize))}
- if s := u.Local.CacheControl(); s != "" {
- details = append(details, fmt.Sprintf("Cache-Control: %q", s))
- }
- if s := u.Local.ContentEncoding(); s != "" {
- details = append(details, fmt.Sprintf("Content-Encoding: %q", s))
- }
- if s := u.Local.ContentType(); s != "" {
- details = append(details, fmt.Sprintf("Content-Type: %q", s))
- }
- return fmt.Sprintf("%s (%s): %v", u.Local.SlashPath, strings.Join(details, ", "), u.Reason)
-}
-
-// findDiffs diffs localFiles vs remoteFiles to see what changes should be
-// applied to the remote target. It returns a slice of *fileToUpload and a
-// slice of paths for files to delete.
-func (d *Deployer) findDiffs(localFiles map[string]*localFile, remoteFiles map[string]*blob.ListObject, force bool) ([]*fileToUpload, []string) {
- var uploads []*fileToUpload
- var deletes []string
-
- found := map[string]bool{}
- for path, lf := range localFiles {
- upload := false
- reason := reasonUnknown
-
- if remoteFile, ok := remoteFiles[path]; ok {
- // The file exists in remote. Let's see if we need to upload it anyway.
-
- // TODO: We don't register a diff if the metadata (e.g., Content-Type
- // header) has changed. This would be difficult/expensive to detect; some
- // providers return metadata along with their "List" result, but others
- // (notably AWS S3) do not, so gocloud.dev's blob.Bucket doesn't expose
- // it in the list result. It would require a separate request per blob
- // to fetch. At least for now, we work around this by documenting it and
- // providing a "force" flag (to re-upload everything) and a "force" bool
- // per matcher (to re-upload all files in a matcher whose headers may have
- // changed).
- // Idea: extract a sample set of 1 file per extension + 1 file per matcher
- // and check those files?
- if force {
- upload = true
- reason = reasonForce
- } else if lf.Force() {
- upload = true
- reason = reasonForce
- } else if lf.UploadSize != remoteFile.Size {
- upload = true
- reason = reasonSize
- } else if len(remoteFile.MD5) == 0 {
- // This shouldn't happen unless the remote didn't give us an MD5 hash
- // from List, AND we failed to compute one by reading the remote file.
- // Default to considering the files different.
- upload = true
- reason = reasonMD5Missing
- } else if !bytes.Equal(lf.MD5(), remoteFile.MD5) {
- upload = true
- reason = reasonMD5Differs
- }
- found[path] = true
- } else {
- // The file doesn't exist in remote.
- upload = true
- reason = reasonNotFound
- }
- if upload {
- d.logger.Debugf("%s needs to be uploaded: %v\n", path, reason)
- uploads = append(uploads, &fileToUpload{lf, reason})
- } else {
- d.logger.Debugf("%s exists at target and does not need to be uploaded", path)
- }
- }
-
- // Remote files that weren't found locally should be deleted.
- for path := range remoteFiles {
- if !found[path] {
- deletes = append(deletes, path)
- }
- }
- return uploads, deletes
-}
-
-// applyOrdering returns an ordered slice of slices of uploads.
-//
-// The returned slice will have length len(ordering)+1.
-//
-// The subslice at index i, for i = 0 ... len(ordering)-1, will have all of the
-// uploads whose Local.SlashPath matched the regex at ordering[i] (but not any
-// previous ordering regex).
-// The subslice at index len(ordering) will have the remaining uploads that
-// didn't match any ordering regex.
-//
-// The subslices are sorted by Local.SlashPath.
-func applyOrdering(ordering []*regexp.Regexp, uploads []*fileToUpload) [][]*fileToUpload {
- // Sort the whole slice by Local.SlashPath first.
- sort.Slice(uploads, func(i, j int) bool { return uploads[i].Local.SlashPath < uploads[j].Local.SlashPath })
-
- retval := make([][]*fileToUpload, len(ordering)+1)
- for _, u := range uploads {
- matched := false
- for i, re := range ordering {
- if re.MatchString(u.Local.SlashPath) {
- retval[i] = append(retval[i], u)
- matched = true
- break
- }
- }
- if !matched {
- retval[len(ordering)] = append(retval[len(ordering)], u)
- }
- }
- return retval
-}
diff --git a/deploy/deploy_azure.go b/deploy/deploy_azure.go
deleted file mode 100644
index b1ce7358c..000000000
--- a/deploy/deploy_azure.go
+++ /dev/null
@@ -1,21 +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.
-
-//go:build !solaris && withdeploy
-
-package deploy
-
-import (
- _ "gocloud.dev/blob"
- _ "gocloud.dev/blob/azureblob" // import
-)
diff --git a/deploy/deploy_test.go b/deploy/deploy_test.go
deleted file mode 100644
index bdc8299a0..000000000
--- a/deploy/deploy_test.go
+++ /dev/null
@@ -1,1102 +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.
-
-//go:build withdeploy
-
-package deploy
-
-import (
- "bytes"
- "compress/gzip"
- "context"
- "crypto/md5"
- "fmt"
- "io"
- "os"
- "path"
- "path/filepath"
- "regexp"
- "sort"
- "testing"
-
- "github.com/gohugoio/hugo/common/loggers"
- "github.com/gohugoio/hugo/deploy/deployconfig"
- "github.com/gohugoio/hugo/hugofs"
- "github.com/gohugoio/hugo/media"
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "github.com/spf13/afero"
- "gocloud.dev/blob"
- "gocloud.dev/blob/fileblob"
- "gocloud.dev/blob/memblob"
-)
-
-func TestFindDiffs(t *testing.T) {
- hash1 := []byte("hash 1")
- hash2 := []byte("hash 2")
- makeLocal := func(path string, size int64, hash []byte) *localFile {
- return &localFile{NativePath: path, SlashPath: filepath.ToSlash(path), UploadSize: size, md5: hash}
- }
- makeRemote := func(path string, size int64, hash []byte) *blob.ListObject {
- return &blob.ListObject{Key: path, Size: size, MD5: hash}
- }
-
- tests := []struct {
- Description string
- Local []*localFile
- Remote []*blob.ListObject
- Force bool
- WantUpdates []*fileToUpload
- WantDeletes []string
- }{
- {
- Description: "empty -> no diffs",
- },
- {
- Description: "local == remote -> no diffs",
- Local: []*localFile{
- makeLocal("aaa", 1, hash1),
- makeLocal("bbb", 2, hash1),
- makeLocal("ccc", 3, hash2),
- },
- Remote: []*blob.ListObject{
- makeRemote("aaa", 1, hash1),
- makeRemote("bbb", 2, hash1),
- makeRemote("ccc", 3, hash2),
- },
- },
- {
- Description: "local w/ separators == remote -> no diffs",
- Local: []*localFile{
- makeLocal(filepath.Join("aaa", "aaa"), 1, hash1),
- makeLocal(filepath.Join("bbb", "bbb"), 2, hash1),
- makeLocal(filepath.Join("ccc", "ccc"), 3, hash2),
- },
- Remote: []*blob.ListObject{
- makeRemote("aaa/aaa", 1, hash1),
- makeRemote("bbb/bbb", 2, hash1),
- makeRemote("ccc/ccc", 3, hash2),
- },
- },
- {
- Description: "local == remote with force flag true -> diffs",
- Local: []*localFile{
- makeLocal("aaa", 1, hash1),
- makeLocal("bbb", 2, hash1),
- makeLocal("ccc", 3, hash2),
- },
- Remote: []*blob.ListObject{
- makeRemote("aaa", 1, hash1),
- makeRemote("bbb", 2, hash1),
- makeRemote("ccc", 3, hash2),
- },
- Force: true,
- WantUpdates: []*fileToUpload{
- {makeLocal("aaa", 1, nil), reasonForce},
- {makeLocal("bbb", 2, nil), reasonForce},
- {makeLocal("ccc", 3, nil), reasonForce},
- },
- },
- {
- Description: "local == remote with route.Force true -> diffs",
- Local: []*localFile{
- {NativePath: "aaa", SlashPath: "aaa", UploadSize: 1, matcher: &deployconfig.Matcher{Force: true}, md5: hash1},
- makeLocal("bbb", 2, hash1),
- },
- Remote: []*blob.ListObject{
- makeRemote("aaa", 1, hash1),
- makeRemote("bbb", 2, hash1),
- },
- WantUpdates: []*fileToUpload{
- {makeLocal("aaa", 1, nil), reasonForce},
- },
- },
- {
- Description: "extra local file -> upload",
- Local: []*localFile{
- makeLocal("aaa", 1, hash1),
- makeLocal("bbb", 2, hash2),
- },
- Remote: []*blob.ListObject{
- makeRemote("aaa", 1, hash1),
- },
- WantUpdates: []*fileToUpload{
- {makeLocal("bbb", 2, nil), reasonNotFound},
- },
- },
- {
- Description: "extra remote file -> delete",
- Local: []*localFile{
- makeLocal("aaa", 1, hash1),
- },
- Remote: []*blob.ListObject{
- makeRemote("aaa", 1, hash1),
- makeRemote("bbb", 2, hash2),
- },
- WantDeletes: []string{"bbb"},
- },
- {
- Description: "diffs in size or md5 -> upload",
- Local: []*localFile{
- makeLocal("aaa", 1, hash1),
- makeLocal("bbb", 2, hash1),
- makeLocal("ccc", 1, hash2),
- },
- Remote: []*blob.ListObject{
- makeRemote("aaa", 1, nil),
- makeRemote("bbb", 1, hash1),
- makeRemote("ccc", 1, hash1),
- },
- WantUpdates: []*fileToUpload{
- {makeLocal("aaa", 1, nil), reasonMD5Missing},
- {makeLocal("bbb", 2, nil), reasonSize},
- {makeLocal("ccc", 1, nil), reasonMD5Differs},
- },
- },
- {
- Description: "mix of updates and deletes",
- Local: []*localFile{
- makeLocal("same", 1, hash1),
- makeLocal("updated", 2, hash1),
- makeLocal("updated2", 1, hash2),
- makeLocal("new", 1, hash1),
- makeLocal("new2", 2, hash2),
- },
- Remote: []*blob.ListObject{
- makeRemote("same", 1, hash1),
- makeRemote("updated", 1, hash1),
- makeRemote("updated2", 1, hash1),
- makeRemote("stale", 1, hash1),
- makeRemote("stale2", 1, hash1),
- },
- WantUpdates: []*fileToUpload{
- {makeLocal("new", 1, nil), reasonNotFound},
- {makeLocal("new2", 2, nil), reasonNotFound},
- {makeLocal("updated", 2, nil), reasonSize},
- {makeLocal("updated2", 1, nil), reasonMD5Differs},
- },
- WantDeletes: []string{"stale", "stale2"},
- },
- }
-
- for _, tc := range tests {
- t.Run(tc.Description, func(t *testing.T) {
- local := map[string]*localFile{}
- for _, l := range tc.Local {
- local[l.SlashPath] = l
- }
- remote := map[string]*blob.ListObject{}
- for _, r := range tc.Remote {
- remote[r.Key] = r
- }
- d := newDeployer()
- gotUpdates, gotDeletes := d.findDiffs(local, remote, tc.Force)
- gotUpdates = applyOrdering(nil, gotUpdates)[0]
- sort.Slice(gotDeletes, func(i, j int) bool { return gotDeletes[i] < gotDeletes[j] })
- if diff := cmp.Diff(gotUpdates, tc.WantUpdates, cmpopts.IgnoreUnexported(localFile{})); diff != "" {
- t.Errorf("updates differ:\n%s", diff)
- }
- if diff := cmp.Diff(gotDeletes, tc.WantDeletes); diff != "" {
- t.Errorf("deletes differ:\n%s", diff)
- }
- })
- }
-}
-
-func TestWalkLocal(t *testing.T) {
- tests := map[string]struct {
- Given []string
- Expect []string
- MapPath func(string) string
- }{
- "Empty": {
- Given: []string{},
- Expect: []string{},
- },
- "Normal": {
- Given: []string{"file.txt", "normal_dir/file.txt"},
- Expect: []string{"file.txt", "normal_dir/file.txt"},
- },
- "Hidden": {
- Given: []string{"file.txt", ".hidden_dir/file.txt", "normal_dir/file.txt"},
- Expect: []string{"file.txt", "normal_dir/file.txt"},
- },
- "Well Known": {
- Given: []string{"file.txt", ".hidden_dir/file.txt", ".well-known/file.txt"},
- Expect: []string{"file.txt", ".well-known/file.txt"},
- },
- "StripIndexHTML": {
- Given: []string{"index.html", "file.txt", "dir/index.html", "dir/file.txt"},
- Expect: []string{"index.html", "file.txt", "dir/", "dir/file.txt"},
- MapPath: stripIndexHTML,
- },
- }
-
- for desc, tc := range tests {
- t.Run(desc, func(t *testing.T) {
- fs := afero.NewMemMapFs()
- for _, name := range tc.Given {
- dir, _ := path.Split(name)
- if dir != "" {
- if err := fs.MkdirAll(dir, 0o755); err != nil {
- t.Fatal(err)
- }
- }
- if fd, err := fs.Create(name); err != nil {
- t.Fatal(err)
- } else {
- fd.Close()
- }
- }
- d := newDeployer()
- if got, err := d.walkLocal(fs, nil, nil, nil, media.DefaultTypes, tc.MapPath); err != nil {
- t.Fatal(err)
- } else {
- expect := map[string]any{}
- for _, path := range tc.Expect {
- if _, ok := got[path]; !ok {
- t.Errorf("expected %q in results, but was not found", path)
- }
- expect[path] = nil
- }
- for path := range got {
- if _, ok := expect[path]; !ok {
- t.Errorf("got %q in results unexpectedly", path)
- }
- }
- }
- })
- }
-}
-
-func TestStripIndexHTML(t *testing.T) {
- tests := map[string]struct {
- Input string
- Output string
- }{
- "Unmapped": {Input: "normal_file.txt", Output: "normal_file.txt"},
- "Stripped": {Input: "directory/index.html", Output: "directory/"},
- "NoSlash": {Input: "prefix_index.html", Output: "prefix_index.html"},
- "Root": {Input: "index.html", Output: "index.html"},
- }
- for desc, tc := range tests {
- t.Run(desc, func(t *testing.T) {
- got := stripIndexHTML(tc.Input)
- if got != tc.Output {
- t.Errorf("got %q, expect %q", got, tc.Output)
- }
- })
- }
-}
-
-func TestStripIndexHTMLMatcher(t *testing.T) {
- // StripIndexHTML should not affect matchers.
- fs := afero.NewMemMapFs()
- if err := fs.Mkdir("dir", 0o755); err != nil {
- t.Fatal(err)
- }
- for _, name := range []string{"index.html", "dir/index.html", "file.txt"} {
- if fd, err := fs.Create(name); err != nil {
- t.Fatal(err)
- } else {
- fd.Close()
- }
- }
- d := newDeployer()
- const pattern = `\.html$`
- matcher := &deployconfig.Matcher{Pattern: pattern, Gzip: true, Re: regexp.MustCompile(pattern)}
- if got, err := d.walkLocal(fs, []*deployconfig.Matcher{matcher}, nil, nil, media.DefaultTypes, stripIndexHTML); err != nil {
- t.Fatal(err)
- } else {
- for _, name := range []string{"index.html", "dir/"} {
- lf := got[name]
- if lf == nil {
- t.Errorf("missing file %q", name)
- } else if lf.matcher == nil {
- t.Errorf("file %q has nil matcher, expect %q", name, pattern)
- }
- }
- const name = "file.txt"
- lf := got[name]
- if lf == nil {
- t.Errorf("missing file %q", name)
- } else if lf.matcher != nil {
- t.Errorf("file %q has matcher %q, expect nil", name, lf.matcher.Pattern)
- }
- }
-}
-
-func TestLocalFile(t *testing.T) {
- const (
- content = "hello world!"
- )
- contentBytes := []byte(content)
- contentLen := int64(len(contentBytes))
- contentMD5 := md5.Sum(contentBytes)
- var buf bytes.Buffer
- gz := gzip.NewWriter(&buf)
- if _, err := gz.Write(contentBytes); err != nil {
- t.Fatal(err)
- }
- gz.Close()
- gzBytes := buf.Bytes()
- gzLen := int64(len(gzBytes))
- gzMD5 := md5.Sum(gzBytes)
-
- tests := []struct {
- Description string
- Path string
- Matcher *deployconfig.Matcher
- MediaTypesConfig map[string]any
- WantContent []byte
- WantSize int64
- WantMD5 []byte
- WantContentType string // empty string is always OK, since content type detection is OS-specific
- WantCacheControl string
- WantContentEncoding string
- }{
- {
- Description: "file with no suffix",
- Path: "foo",
- WantContent: contentBytes,
- WantSize: contentLen,
- WantMD5: contentMD5[:],
- },
- {
- Description: "file with .txt suffix",
- Path: "foo.txt",
- WantContent: contentBytes,
- WantSize: contentLen,
- WantMD5: contentMD5[:],
- },
- {
- Description: "CacheControl from matcher",
- Path: "foo.txt",
- Matcher: &deployconfig.Matcher{CacheControl: "max-age=630720000"},
- WantContent: contentBytes,
- WantSize: contentLen,
- WantMD5: contentMD5[:],
- WantCacheControl: "max-age=630720000",
- },
- {
- Description: "ContentEncoding from matcher",
- Path: "foo.txt",
- Matcher: &deployconfig.Matcher{ContentEncoding: "foobar"},
- WantContent: contentBytes,
- WantSize: contentLen,
- WantMD5: contentMD5[:],
- WantContentEncoding: "foobar",
- },
- {
- Description: "ContentType from matcher",
- Path: "foo.txt",
- Matcher: &deployconfig.Matcher{ContentType: "foo/bar"},
- WantContent: contentBytes,
- WantSize: contentLen,
- WantMD5: contentMD5[:],
- WantContentType: "foo/bar",
- },
- {
- Description: "gzipped content",
- Path: "foo.txt",
- Matcher: &deployconfig.Matcher{Gzip: true},
- WantContent: gzBytes,
- WantSize: gzLen,
- WantMD5: gzMD5[:],
- WantContentEncoding: "gzip",
- },
- {
- Description: "Custom MediaType",
- Path: "foo.hugo",
- MediaTypesConfig: map[string]any{
- "hugo/custom": map[string]any{
- "suffixes": []string{"hugo"},
- },
- },
- WantContent: contentBytes,
- WantSize: contentLen,
- WantMD5: contentMD5[:],
- WantContentType: "hugo/custom",
- },
- }
-
- for _, tc := range tests {
- t.Run(tc.Description, func(t *testing.T) {
- fs := new(afero.MemMapFs)
- if err := afero.WriteFile(fs, tc.Path, []byte(content), os.ModePerm); err != nil {
- t.Fatal(err)
- }
- mediaTypes := media.DefaultTypes
- if len(tc.MediaTypesConfig) > 0 {
- mt, err := media.DecodeTypes(tc.MediaTypesConfig)
- if err != nil {
- t.Fatal(err)
- }
- mediaTypes = mt.Config
- }
- lf, err := newLocalFile(fs, tc.Path, filepath.ToSlash(tc.Path), tc.Matcher, mediaTypes)
- if err != nil {
- t.Fatal(err)
- }
- if got := lf.UploadSize; got != tc.WantSize {
- t.Errorf("got size %d want %d", got, tc.WantSize)
- }
- if got := lf.MD5(); !bytes.Equal(got, tc.WantMD5) {
- t.Errorf("got MD5 %x want %x", got, tc.WantMD5)
- }
- if got := lf.CacheControl(); got != tc.WantCacheControl {
- t.Errorf("got CacheControl %q want %q", got, tc.WantCacheControl)
- }
- if got := lf.ContentEncoding(); got != tc.WantContentEncoding {
- t.Errorf("got ContentEncoding %q want %q", got, tc.WantContentEncoding)
- }
- if tc.WantContentType != "" {
- if got := lf.ContentType(); got != tc.WantContentType {
- t.Errorf("got ContentType %q want %q", got, tc.WantContentType)
- }
- }
- // Verify the reader last to ensure the previous operations don't
- // interfere with it.
- r, err := lf.Reader()
- if err != nil {
- t.Fatal(err)
- }
- gotContent, err := io.ReadAll(r)
- if err != nil {
- t.Fatal(err)
- }
- if !bytes.Equal(gotContent, tc.WantContent) {
- t.Errorf("got content %q want %q", string(gotContent), string(tc.WantContent))
- }
- r.Close()
- // Verify we can read again.
- r, err = lf.Reader()
- if err != nil {
- t.Fatal(err)
- }
- gotContent, err = io.ReadAll(r)
- if err != nil {
- t.Fatal(err)
- }
- r.Close()
- if !bytes.Equal(gotContent, tc.WantContent) {
- t.Errorf("got content %q want %q", string(gotContent), string(tc.WantContent))
- }
- })
- }
-}
-
-func TestOrdering(t *testing.T) {
- tests := []struct {
- Description string
- Uploads []string
- Ordering []*regexp.Regexp
- Want [][]string
- }{
- {
- Description: "empty",
- Want: [][]string{nil},
- },
- {
- Description: "no ordering",
- Uploads: []string{"c", "b", "a", "d"},
- Want: [][]string{{"a", "b", "c", "d"}},
- },
- {
- Description: "one ordering",
- Uploads: []string{"db", "c", "b", "a", "da"},
- Ordering: []*regexp.Regexp{regexp.MustCompile("^d")},
- Want: [][]string{{"da", "db"}, {"a", "b", "c"}},
- },
- {
- Description: "two orderings",
- Uploads: []string{"db", "c", "b", "a", "da"},
- Ordering: []*regexp.Regexp{
- regexp.MustCompile("^d"),
- regexp.MustCompile("^b"),
- },
- Want: [][]string{{"da", "db"}, {"b"}, {"a", "c"}},
- },
- }
-
- for _, tc := range tests {
- t.Run(tc.Description, func(t *testing.T) {
- uploads := make([]*fileToUpload, len(tc.Uploads))
- for i, u := range tc.Uploads {
- uploads[i] = &fileToUpload{Local: &localFile{SlashPath: u}}
- }
- gotUploads := applyOrdering(tc.Ordering, uploads)
- var got [][]string
- for _, subslice := range gotUploads {
- var gotsubslice []string
- for _, u := range subslice {
- gotsubslice = append(gotsubslice, u.Local.SlashPath)
- }
- got = append(got, gotsubslice)
- }
- if diff := cmp.Diff(got, tc.Want); diff != "" {
- t.Error(diff)
- }
- })
- }
-}
-
-type fileData struct {
- Name string // name of the file
- Contents string // contents of the file
-}
-
-// initLocalFs initializes fs with some test files.
-func initLocalFs(ctx context.Context, fs afero.Fs) ([]*fileData, error) {
- // The initial local filesystem.
- local := []*fileData{
- {"aaa", "aaa"},
- {"bbb", "bbb"},
- {"subdir/aaa", "subdir-aaa"},
- {"subdir/nested/aaa", "subdir-nested-aaa"},
- {"subdir2/bbb", "subdir2-bbb"},
- }
- if err := writeFiles(fs, local); err != nil {
- return nil, err
- }
- return local, nil
-}
-
-// fsTest represents an (afero.FS, Go CDK blob.Bucket) against which end-to-end
-// tests can be run.
-type fsTest struct {
- name string
- fs afero.Fs
- bucket *blob.Bucket
-}
-
-// initFsTests initializes a pair of tests for end-to-end test:
-// 1. An in-memory afero.Fs paired with an in-memory Go CDK bucket.
-// 2. A filesystem-based afero.Fs paired with an filesystem-based Go CDK bucket.
-// It returns the pair of tests and a cleanup function.
-func initFsTests(t *testing.T) []*fsTest {
- t.Helper()
-
- tmpfsdir := t.TempDir()
- tmpbucketdir := t.TempDir()
-
- memfs := afero.NewMemMapFs()
- membucket := memblob.OpenBucket(nil)
- t.Cleanup(func() { membucket.Close() })
-
- filefs := hugofs.NewBasePathFs(afero.NewOsFs(), tmpfsdir)
- filebucket, err := fileblob.OpenBucket(tmpbucketdir, nil)
- if err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() { filebucket.Close() })
-
- tests := []*fsTest{
- {"mem", memfs, membucket},
- {"file", filefs, filebucket},
- }
- return tests
-}
-
-// TestEndToEndSync verifies that basic adds, updates, and deletes are working
-// correctly.
-func TestEndToEndSync(t *testing.T) {
- ctx := context.Background()
- tests := initFsTests(t)
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- local, err := initLocalFs(ctx, test.fs)
- if err != nil {
- t.Fatal(err)
- }
- deployer := &Deployer{
- localFs: test.fs,
- bucket: test.bucket,
- mediaTypes: media.DefaultTypes,
- cfg: deployconfig.DeployConfig{Workers: 2, MaxDeletes: -1},
- }
-
- // Initial deployment should sync remote with local.
- if err := deployer.Deploy(ctx); err != nil {
- t.Errorf("initial deploy: failed: %v", err)
- }
- wantSummary := deploySummary{NumLocal: 5, NumRemote: 0, NumUploads: 5, NumDeletes: 0}
- if !cmp.Equal(deployer.summary, wantSummary) {
- t.Errorf("initial deploy: got %v, want %v", deployer.summary, wantSummary)
- }
- if diff, err := verifyRemote(ctx, deployer.bucket, local); err != nil {
- t.Errorf("initial deploy: failed to verify remote: %v", err)
- } else if diff != "" {
- t.Errorf("initial deploy: remote snapshot doesn't match expected:\n%v", diff)
- }
-
- // A repeat deployment shouldn't change anything.
- if err := deployer.Deploy(ctx); err != nil {
- t.Errorf("no-op deploy: %v", err)
- }
- wantSummary = deploySummary{NumLocal: 5, NumRemote: 5, NumUploads: 0, NumDeletes: 0}
- if !cmp.Equal(deployer.summary, wantSummary) {
- t.Errorf("no-op deploy: got %v, want %v", deployer.summary, wantSummary)
- }
-
- // Make some changes to the local filesystem:
- // 1. Modify file [0].
- // 2. Delete file [1].
- // 3. Add a new file (sorted last).
- updatefd := local[0]
- updatefd.Contents = "new contents"
- deletefd := local[1]
- local = append(local[:1], local[2:]...) // removing deleted [1]
- newfd := &fileData{"zzz", "zzz"}
- local = append(local, newfd)
- if err := writeFiles(test.fs, []*fileData{updatefd, newfd}); err != nil {
- t.Fatal(err)
- }
- if err := test.fs.Remove(deletefd.Name); err != nil {
- t.Fatal(err)
- }
-
- // A deployment should apply those 3 changes.
- if err := deployer.Deploy(ctx); err != nil {
- t.Errorf("deploy after changes: failed: %v", err)
- }
- wantSummary = deploySummary{NumLocal: 5, NumRemote: 5, NumUploads: 2, NumDeletes: 1}
- if !cmp.Equal(deployer.summary, wantSummary) {
- t.Errorf("deploy after changes: got %v, want %v", deployer.summary, wantSummary)
- }
- if diff, err := verifyRemote(ctx, deployer.bucket, local); err != nil {
- t.Errorf("deploy after changes: failed to verify remote: %v", err)
- } else if diff != "" {
- t.Errorf("deploy after changes: remote snapshot doesn't match expected:\n%v", diff)
- }
-
- // Again, a repeat deployment shouldn't change anything.
- if err := deployer.Deploy(ctx); err != nil {
- t.Errorf("no-op deploy: %v", err)
- }
- wantSummary = deploySummary{NumLocal: 5, NumRemote: 5, NumUploads: 0, NumDeletes: 0}
- if !cmp.Equal(deployer.summary, wantSummary) {
- t.Errorf("no-op deploy: got %v, want %v", deployer.summary, wantSummary)
- }
- })
- }
-}
-
-// TestMaxDeletes verifies that the "maxDeletes" flag is working correctly.
-func TestMaxDeletes(t *testing.T) {
- ctx := context.Background()
- tests := initFsTests(t)
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- local, err := initLocalFs(ctx, test.fs)
- if err != nil {
- t.Fatal(err)
- }
- deployer := &Deployer{
- localFs: test.fs,
- bucket: test.bucket,
- mediaTypes: media.DefaultTypes,
- cfg: deployconfig.DeployConfig{Workers: 2, MaxDeletes: -1},
- }
-
- // Sync remote with local.
- if err := deployer.Deploy(ctx); err != nil {
- t.Errorf("initial deploy: failed: %v", err)
- }
- wantSummary := deploySummary{NumLocal: 5, NumRemote: 0, NumUploads: 5, NumDeletes: 0}
- if !cmp.Equal(deployer.summary, wantSummary) {
- t.Errorf("initial deploy: got %v, want %v", deployer.summary, wantSummary)
- }
-
- // Delete two files, [1] and [2].
- if err := test.fs.Remove(local[1].Name); err != nil {
- t.Fatal(err)
- }
- if err := test.fs.Remove(local[2].Name); err != nil {
- t.Fatal(err)
- }
-
- // A deployment with maxDeletes=0 shouldn't change anything.
- deployer.cfg.MaxDeletes = 0
- if err := deployer.Deploy(ctx); err != nil {
- t.Errorf("deploy failed: %v", err)
- }
- wantSummary = deploySummary{NumLocal: 3, NumRemote: 5, NumUploads: 0, NumDeletes: 0}
- if !cmp.Equal(deployer.summary, wantSummary) {
- t.Errorf("deploy: got %v, want %v", deployer.summary, wantSummary)
- }
-
- // A deployment with maxDeletes=1 shouldn't change anything either.
- deployer.cfg.MaxDeletes = 1
- if err := deployer.Deploy(ctx); err != nil {
- t.Errorf("deploy failed: %v", err)
- }
- wantSummary = deploySummary{NumLocal: 3, NumRemote: 5, NumUploads: 0, NumDeletes: 0}
- if !cmp.Equal(deployer.summary, wantSummary) {
- t.Errorf("deploy: got %v, want %v", deployer.summary, wantSummary)
- }
-
- // A deployment with maxDeletes=2 should make the changes.
- deployer.cfg.MaxDeletes = 2
- if err := deployer.Deploy(ctx); err != nil {
- t.Errorf("deploy failed: %v", err)
- }
- wantSummary = deploySummary{NumLocal: 3, NumRemote: 5, NumUploads: 0, NumDeletes: 2}
- if !cmp.Equal(deployer.summary, wantSummary) {
- t.Errorf("deploy: got %v, want %v", deployer.summary, wantSummary)
- }
-
- // Delete two more files, [0] and [3].
- if err := test.fs.Remove(local[0].Name); err != nil {
- t.Fatal(err)
- }
- if err := test.fs.Remove(local[3].Name); err != nil {
- t.Fatal(err)
- }
-
- // A deployment with maxDeletes=-1 should make the changes.
- deployer.cfg.MaxDeletes = -1
- if err := deployer.Deploy(ctx); err != nil {
- t.Errorf("deploy failed: %v", err)
- }
- wantSummary = deploySummary{NumLocal: 1, NumRemote: 3, NumUploads: 0, NumDeletes: 2}
- if !cmp.Equal(deployer.summary, wantSummary) {
- t.Errorf("deploy: got %v, want %v", deployer.summary, wantSummary)
- }
- })
- }
-}
-
-// TestIncludeExclude verifies that the include/exclude options for targets work.
-func TestIncludeExclude(t *testing.T) {
- ctx := context.Background()
- tests := []struct {
- Include string
- Exclude string
- Want deploySummary
- }{
- {
- Want: deploySummary{NumLocal: 5, NumUploads: 5},
- },
- {
- Include: "**aaa",
- Want: deploySummary{NumLocal: 3, NumUploads: 3},
- },
- {
- Include: "**bbb",
- Want: deploySummary{NumLocal: 2, NumUploads: 2},
- },
- {
- Include: "aaa",
- Want: deploySummary{NumLocal: 1, NumUploads: 1},
- },
- {
- Exclude: "**aaa",
- Want: deploySummary{NumLocal: 2, NumUploads: 2},
- },
- {
- Exclude: "**bbb",
- Want: deploySummary{NumLocal: 3, NumUploads: 3},
- },
- {
- Exclude: "aaa",
- Want: deploySummary{NumLocal: 4, NumUploads: 4},
- },
- {
- Include: "**aaa",
- Exclude: "**nested**",
- Want: deploySummary{NumLocal: 2, NumUploads: 2},
- },
- }
- for _, test := range tests {
- t.Run(fmt.Sprintf("include %q exclude %q", test.Include, test.Exclude), func(t *testing.T) {
- fsTests := initFsTests(t)
- fsTest := fsTests[1] // just do file-based test
-
- _, err := initLocalFs(ctx, fsTest.fs)
- if err != nil {
- t.Fatal(err)
- }
- tgt := &deployconfig.Target{
- Include: test.Include,
- Exclude: test.Exclude,
- }
- if err := tgt.ParseIncludeExclude(); err != nil {
- t.Error(err)
- }
- deployer := &Deployer{
- localFs: fsTest.fs,
- cfg: deployconfig.DeployConfig{Workers: 2, MaxDeletes: -1}, bucket: fsTest.bucket,
- target: tgt,
- mediaTypes: media.DefaultTypes,
- }
-
- // Sync remote with local.
- if err := deployer.Deploy(ctx); err != nil {
- t.Errorf("deploy: failed: %v", err)
- }
- if !cmp.Equal(deployer.summary, test.Want) {
- t.Errorf("deploy: got %v, want %v", deployer.summary, test.Want)
- }
- })
- }
-}
-
-// TestIncludeExcludeRemoteDelete verifies deleted local files that don't match include/exclude patterns
-// are not deleted on the remote.
-func TestIncludeExcludeRemoteDelete(t *testing.T) {
- ctx := context.Background()
-
- tests := []struct {
- Include string
- Exclude string
- Want deploySummary
- }{
- {
- Want: deploySummary{NumLocal: 3, NumRemote: 5, NumUploads: 0, NumDeletes: 2},
- },
- {
- Include: "**aaa",
- Want: deploySummary{NumLocal: 2, NumRemote: 3, NumUploads: 0, NumDeletes: 1},
- },
- {
- Include: "subdir/**",
- Want: deploySummary{NumLocal: 1, NumRemote: 2, NumUploads: 0, NumDeletes: 1},
- },
- {
- Exclude: "**bbb",
- Want: deploySummary{NumLocal: 2, NumRemote: 3, NumUploads: 0, NumDeletes: 1},
- },
- {
- Exclude: "bbb",
- Want: deploySummary{NumLocal: 3, NumRemote: 4, NumUploads: 0, NumDeletes: 1},
- },
- }
- for _, test := range tests {
- t.Run(fmt.Sprintf("include %q exclude %q", test.Include, test.Exclude), func(t *testing.T) {
- fsTests := initFsTests(t)
- fsTest := fsTests[1] // just do file-based test
-
- local, err := initLocalFs(ctx, fsTest.fs)
- if err != nil {
- t.Fatal(err)
- }
- deployer := &Deployer{
- localFs: fsTest.fs,
- cfg: deployconfig.DeployConfig{Workers: 2, MaxDeletes: -1}, bucket: fsTest.bucket,
- mediaTypes: media.DefaultTypes,
- }
-
- // Initial sync to get the files on the remote
- if err := deployer.Deploy(ctx); err != nil {
- t.Errorf("deploy: failed: %v", err)
- }
-
- // Delete two files, [1] and [2].
- if err := fsTest.fs.Remove(local[1].Name); err != nil {
- t.Fatal(err)
- }
- if err := fsTest.fs.Remove(local[2].Name); err != nil {
- t.Fatal(err)
- }
-
- // Second sync
- tgt := &deployconfig.Target{
- Include: test.Include,
- Exclude: test.Exclude,
- }
- if err := tgt.ParseIncludeExclude(); err != nil {
- t.Error(err)
- }
- deployer.target = tgt
- if err := deployer.Deploy(ctx); err != nil {
- t.Errorf("deploy: failed: %v", err)
- }
-
- if !cmp.Equal(deployer.summary, test.Want) {
- t.Errorf("deploy: got %v, want %v", deployer.summary, test.Want)
- }
- })
- }
-}
-
-// TestCompression verifies that gzip compression works correctly.
-// In particular, MD5 hashes must be of the compressed content.
-func TestCompression(t *testing.T) {
- ctx := context.Background()
-
- tests := initFsTests(t)
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- local, err := initLocalFs(ctx, test.fs)
- if err != nil {
- t.Fatal(err)
- }
- deployer := &Deployer{
- localFs: test.fs,
- bucket: test.bucket,
- cfg: deployconfig.DeployConfig{Workers: 2, MaxDeletes: -1, Matchers: []*deployconfig.Matcher{{Pattern: ".*", Gzip: true, Re: regexp.MustCompile(".*")}}},
- mediaTypes: media.DefaultTypes,
- }
-
- // Initial deployment should sync remote with local.
- if err := deployer.Deploy(ctx); err != nil {
- t.Errorf("initial deploy: failed: %v", err)
- }
- wantSummary := deploySummary{NumLocal: 5, NumRemote: 0, NumUploads: 5, NumDeletes: 0}
- if !cmp.Equal(deployer.summary, wantSummary) {
- t.Errorf("initial deploy: got %v, want %v", deployer.summary, wantSummary)
- }
-
- // A repeat deployment shouldn't change anything.
- if err := deployer.Deploy(ctx); err != nil {
- t.Errorf("no-op deploy: %v", err)
- }
- wantSummary = deploySummary{NumLocal: 5, NumRemote: 5, NumUploads: 0, NumDeletes: 0}
- if !cmp.Equal(deployer.summary, wantSummary) {
- t.Errorf("no-op deploy: got %v, want %v", deployer.summary, wantSummary)
- }
-
- // Make an update to the local filesystem, on [1].
- updatefd := local[1]
- updatefd.Contents = "new contents"
- if err := writeFiles(test.fs, []*fileData{updatefd}); err != nil {
- t.Fatal(err)
- }
-
- // A deployment should apply the changes.
- if err := deployer.Deploy(ctx); err != nil {
- t.Errorf("deploy after changes: failed: %v", err)
- }
- wantSummary = deploySummary{NumLocal: 5, NumRemote: 5, NumUploads: 1, NumDeletes: 0}
- if !cmp.Equal(deployer.summary, wantSummary) {
- t.Errorf("deploy after changes: got %v, want %v", deployer.summary, wantSummary)
- }
- })
- }
-}
-
-// TestMatching verifies that matchers match correctly, and that the Force
-// attribute for matcher works.
-func TestMatching(t *testing.T) {
- ctx := context.Background()
- tests := initFsTests(t)
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- _, err := initLocalFs(ctx, test.fs)
- if err != nil {
- t.Fatal(err)
- }
- deployer := &Deployer{
- localFs: test.fs,
- bucket: test.bucket,
- cfg: deployconfig.DeployConfig{Workers: 2, MaxDeletes: -1, Matchers: []*deployconfig.Matcher{{Pattern: "^subdir/aaa$", Force: true, Re: regexp.MustCompile("^subdir/aaa$")}}},
- mediaTypes: media.DefaultTypes,
- }
-
- // Initial deployment to sync remote with local.
- if err := deployer.Deploy(ctx); err != nil {
- t.Errorf("initial deploy: failed: %v", err)
- }
- wantSummary := deploySummary{NumLocal: 5, NumRemote: 0, NumUploads: 5, NumDeletes: 0}
- if !cmp.Equal(deployer.summary, wantSummary) {
- t.Errorf("initial deploy: got %v, want %v", deployer.summary, wantSummary)
- }
-
- // A repeat deployment should upload a single file, the one that matched the Force matcher.
- // Note that matching happens based on the ToSlash form, so this matches
- // even on Windows.
- if err := deployer.Deploy(ctx); err != nil {
- t.Errorf("no-op deploy with single force matcher: %v", err)
- }
- wantSummary = deploySummary{NumLocal: 5, NumRemote: 5, NumUploads: 1, NumDeletes: 0}
- if !cmp.Equal(deployer.summary, wantSummary) {
- t.Errorf("no-op deploy with single force matcher: got %v, want %v", deployer.summary, wantSummary)
- }
-
- // Repeat with a matcher that should now match 3 files.
- deployer.cfg.Matchers = []*deployconfig.Matcher{{Pattern: "aaa", Force: true, Re: regexp.MustCompile("aaa")}}
- if err := deployer.Deploy(ctx); err != nil {
- t.Errorf("no-op deploy with triple force matcher: %v", err)
- }
- wantSummary = deploySummary{NumLocal: 5, NumRemote: 5, NumUploads: 3, NumDeletes: 0}
- if !cmp.Equal(deployer.summary, wantSummary) {
- t.Errorf("no-op deploy with triple force matcher: got %v, want %v", deployer.summary, wantSummary)
- }
- })
- }
-}
-
-// writeFiles writes the files in fds to fd.
-func writeFiles(fs afero.Fs, fds []*fileData) error {
- for _, fd := range fds {
- dir := path.Dir(fd.Name)
- if dir != "." {
- err := fs.MkdirAll(dir, os.ModePerm)
- if err != nil {
- return err
- }
- }
- f, err := fs.Create(fd.Name)
- if err != nil {
- return err
- }
- defer f.Close()
- _, err = f.WriteString(fd.Contents)
- if err != nil {
- return err
- }
- }
- return nil
-}
-
-// verifyRemote that the current contents of bucket matches local.
-// It returns an empty string if the contents matched, and a non-empty string
-// capturing the diff if they didn't.
-func verifyRemote(ctx context.Context, bucket *blob.Bucket, local []*fileData) (string, error) {
- var cur []*fileData
- iter := bucket.List(nil)
- for {
- obj, err := iter.Next(ctx)
- if err == io.EOF {
- break
- }
- if err != nil {
- return "", err
- }
- contents, err := bucket.ReadAll(ctx, obj.Key)
- if err != nil {
- return "", err
- }
- cur = append(cur, &fileData{obj.Key, string(contents)})
- }
- if cmp.Equal(cur, local) {
- return "", nil
- }
- diff := "got: \n"
- for _, f := range cur {
- diff += fmt.Sprintf(" %s: %s\n", f.Name, f.Contents)
- }
- diff += "want: \n"
- for _, f := range local {
- diff += fmt.Sprintf(" %s: %s\n", f.Name, f.Contents)
- }
- return diff, nil
-}
-
-func newDeployer() *Deployer {
- return &Deployer{
- logger: loggers.NewDefault(),
- cfg: deployconfig.DeployConfig{Workers: 2},
- }
-}
diff --git a/deploy/deployconfig/deployConfig.go b/deploy/deployconfig/deployConfig.go
deleted file mode 100644
index b16b7c627..000000000
--- a/deploy/deployconfig/deployConfig.go
+++ /dev/null
@@ -1,179 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package deployconfig
-
-import (
- "errors"
- "fmt"
- "regexp"
-
- "github.com/gobwas/glob"
- "github.com/gohugoio/hugo/config"
- hglob "github.com/gohugoio/hugo/hugofs/glob"
- "github.com/mitchellh/mapstructure"
-)
-
-const DeploymentConfigKey = "deployment"
-
-// DeployConfig is the complete configuration for deployment.
-type DeployConfig struct {
- Targets []*Target
- Matchers []*Matcher
- Order []string
-
- // Usually set via flags.
- // Target deployment Name; defaults to the first one.
- Target string
- // Show a confirm prompt before deploying.
- Confirm bool
- // DryRun will try the deployment without any remote changes.
- DryRun bool
- // Force will re-upload all files.
- Force bool
- // Invalidate the CDN cache listed in the deployment target.
- InvalidateCDN bool
- // MaxDeletes is the maximum number of files to delete.
- MaxDeletes int
- // Number of concurrent workers to use when uploading files.
- Workers int
-
- Ordering []*regexp.Regexp `json:"-"` // compiled Order
-}
-
-type Target struct {
- Name string
- URL string
-
- CloudFrontDistributionID string
-
- // GoogleCloudCDNOrigin specifies the Google Cloud project and CDN origin to
- // invalidate when deploying this target. It is specified as /.
- GoogleCloudCDNOrigin string
-
- // Optional patterns of files to include/exclude for this target.
- // Parsed using github.com/gobwas/glob.
- Include string
- Exclude string
-
- // Parsed versions of Include/Exclude.
- IncludeGlob glob.Glob `json:"-"`
- ExcludeGlob glob.Glob `json:"-"`
-
- // If true, any local path matching /index.html will be mapped to the
- // remote path /. This does not affect the top-level index.html file,
- // since that would result in an empty path.
- StripIndexHTML bool
-}
-
-func (tgt *Target) ParseIncludeExclude() error {
- var err error
- if tgt.Include != "" {
- tgt.IncludeGlob, err = hglob.GetGlob(tgt.Include)
- if err != nil {
- return fmt.Errorf("invalid deployment.target.include %q: %v", tgt.Include, err)
- }
- }
- if tgt.Exclude != "" {
- tgt.ExcludeGlob, err = hglob.GetGlob(tgt.Exclude)
- if err != nil {
- return fmt.Errorf("invalid deployment.target.exclude %q: %v", tgt.Exclude, err)
- }
- }
- return nil
-}
-
-// Matcher represents configuration to be applied to files whose paths match
-// a specified pattern.
-type Matcher struct {
- // Pattern is the string pattern to match against paths.
- // Matching is done against paths converted to use / as the path separator.
- Pattern string
-
- // CacheControl specifies caching attributes to use when serving the blob.
- // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
- CacheControl string
-
- // ContentEncoding specifies the encoding used for the blob's content, if any.
- // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding
- ContentEncoding string
-
- // ContentType specifies the MIME type of the blob being written.
- // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type
- ContentType string
-
- // Gzip determines whether the file should be gzipped before upload.
- // If so, the ContentEncoding field will automatically be set to "gzip".
- Gzip bool
-
- // Force indicates that matching files should be re-uploaded. Useful when
- // other route-determined metadata (e.g., ContentType) has changed.
- Force bool
-
- // Re is Pattern compiled.
- Re *regexp.Regexp `json:"-"`
-}
-
-func (m *Matcher) Matches(path string) bool {
- return m.Re.MatchString(path)
-}
-
-var DefaultConfig = DeployConfig{
- Workers: 10,
- InvalidateCDN: true,
- MaxDeletes: 256,
-}
-
-// DecodeConfig creates a config from a given Hugo configuration.
-func DecodeConfig(cfg config.Provider) (DeployConfig, error) {
- dcfg := DefaultConfig
-
- if !cfg.IsSet(DeploymentConfigKey) {
- return dcfg, nil
- }
- if err := mapstructure.WeakDecode(cfg.GetStringMap(DeploymentConfigKey), &dcfg); err != nil {
- return dcfg, err
- }
-
- if dcfg.Workers <= 0 {
- dcfg.Workers = 10
- }
-
- for _, tgt := range dcfg.Targets {
- if *tgt == (Target{}) {
- return dcfg, errors.New("empty deployment target")
- }
- if err := tgt.ParseIncludeExclude(); err != nil {
- return dcfg, err
- }
- }
- var err error
- for _, m := range dcfg.Matchers {
- if *m == (Matcher{}) {
- return dcfg, errors.New("empty deployment matcher")
- }
- m.Re, err = regexp.Compile(m.Pattern)
- if err != nil {
- return dcfg, fmt.Errorf("invalid deployment.matchers.pattern: %v", err)
- }
- }
- for _, o := range dcfg.Order {
- re, err := regexp.Compile(o)
- if err != nil {
- return dcfg, fmt.Errorf("invalid deployment.orderings.pattern: %v", err)
- }
- dcfg.Ordering = append(dcfg.Ordering, re)
- }
-
- return dcfg, nil
-}
diff --git a/deploy/deployconfig/deployConfig_test.go b/deploy/deployconfig/deployConfig_test.go
deleted file mode 100644
index 38d0aadd6..000000000
--- a/deploy/deployconfig/deployConfig_test.go
+++ /dev/null
@@ -1,198 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-//go:build withdeploy
-
-package deployconfig
-
-import (
- "fmt"
- "testing"
-
- qt "github.com/frankban/quicktest"
- "github.com/gohugoio/hugo/config"
-)
-
-func TestDecodeConfigFromTOML(t *testing.T) {
- c := qt.New(t)
-
- tomlConfig := `
-
-someOtherValue = "foo"
-
-[deployment]
-
-order = ["o1", "o2"]
-
-# All lowercase.
-[[deployment.targets]]
-name = "name0"
-url = "url0"
-cloudfrontdistributionid = "cdn0"
-include = "*.html"
-
-# All uppercase.
-[[deployment.targets]]
-NAME = "name1"
-URL = "url1"
-CLOUDFRONTDISTRIBUTIONID = "cdn1"
-INCLUDE = "*.jpg"
-
-# Camelcase.
-[[deployment.targets]]
-name = "name2"
-url = "url2"
-cloudFrontDistributionID = "cdn2"
-exclude = "*.png"
-
-# All lowercase.
-[[deployment.matchers]]
-pattern = "^pattern0$"
-cachecontrol = "cachecontrol0"
-contentencoding = "contentencoding0"
-contenttype = "contenttype0"
-
-# All uppercase.
-[[deployment.matchers]]
-PATTERN = "^pattern1$"
-CACHECONTROL = "cachecontrol1"
-CONTENTENCODING = "contentencoding1"
-CONTENTTYPE = "contenttype1"
-GZIP = true
-FORCE = true
-
-# Camelcase.
-[[deployment.matchers]]
-pattern = "^pattern2$"
-cacheControl = "cachecontrol2"
-contentEncoding = "contentencoding2"
-contentType = "contenttype2"
-gzip = true
-force = true
-`
- cfg, err := config.FromConfigString(tomlConfig, "toml")
- c.Assert(err, qt.IsNil)
-
- dcfg, err := DecodeConfig(cfg)
- c.Assert(err, qt.IsNil)
-
- // Order.
- c.Assert(len(dcfg.Order), qt.Equals, 2)
- c.Assert(dcfg.Order[0], qt.Equals, "o1")
- c.Assert(dcfg.Order[1], qt.Equals, "o2")
- c.Assert(len(dcfg.Ordering), qt.Equals, 2)
-
- // Targets.
- c.Assert(len(dcfg.Targets), qt.Equals, 3)
- wantInclude := []string{"*.html", "*.jpg", ""}
- wantExclude := []string{"", "", "*.png"}
- for i := 0; i < 3; i++ {
- tgt := dcfg.Targets[i]
- c.Assert(tgt.Name, qt.Equals, fmt.Sprintf("name%d", i))
- c.Assert(tgt.URL, qt.Equals, fmt.Sprintf("url%d", i))
- c.Assert(tgt.CloudFrontDistributionID, qt.Equals, fmt.Sprintf("cdn%d", i))
- c.Assert(tgt.Include, qt.Equals, wantInclude[i])
- if wantInclude[i] != "" {
- c.Assert(tgt.IncludeGlob, qt.Not(qt.IsNil))
- }
- c.Assert(tgt.Exclude, qt.Equals, wantExclude[i])
- if wantExclude[i] != "" {
- c.Assert(tgt.ExcludeGlob, qt.Not(qt.IsNil))
- }
- }
-
- // Matchers.
- c.Assert(len(dcfg.Matchers), qt.Equals, 3)
- for i := 0; i < 3; i++ {
- m := dcfg.Matchers[i]
- c.Assert(m.Pattern, qt.Equals, fmt.Sprintf("^pattern%d$", i))
- c.Assert(m.Re, qt.Not(qt.IsNil))
- c.Assert(m.CacheControl, qt.Equals, fmt.Sprintf("cachecontrol%d", i))
- c.Assert(m.ContentEncoding, qt.Equals, fmt.Sprintf("contentencoding%d", i))
- c.Assert(m.ContentType, qt.Equals, fmt.Sprintf("contenttype%d", i))
- c.Assert(m.Gzip, qt.Equals, i != 0)
- c.Assert(m.Force, qt.Equals, i != 0)
- }
-}
-
-func TestInvalidOrderingPattern(t *testing.T) {
- c := qt.New(t)
-
- tomlConfig := `
-
-someOtherValue = "foo"
-
-[deployment]
-order = ["["] # invalid regular expression
-`
- cfg, err := config.FromConfigString(tomlConfig, "toml")
- c.Assert(err, qt.IsNil)
-
- _, err = DecodeConfig(cfg)
- c.Assert(err, qt.Not(qt.IsNil))
-}
-
-func TestInvalidMatcherPattern(t *testing.T) {
- c := qt.New(t)
-
- tomlConfig := `
-
-someOtherValue = "foo"
-
-[deployment]
-[[deployment.matchers]]
-Pattern = "[" # invalid regular expression
-`
- cfg, err := config.FromConfigString(tomlConfig, "toml")
- c.Assert(err, qt.IsNil)
-
- _, err = DecodeConfig(cfg)
- c.Assert(err, qt.Not(qt.IsNil))
-}
-
-func TestDecodeConfigDefault(t *testing.T) {
- c := qt.New(t)
-
- dcfg, err := DecodeConfig(config.New())
- c.Assert(err, qt.IsNil)
- c.Assert(len(dcfg.Targets), qt.Equals, 0)
- c.Assert(len(dcfg.Matchers), qt.Equals, 0)
-}
-
-func TestEmptyTarget(t *testing.T) {
- c := qt.New(t)
-
- tomlConfig := `
-[deployment]
-[[deployment.targets]]
-`
- cfg, err := config.FromConfigString(tomlConfig, "toml")
- c.Assert(err, qt.IsNil)
-
- _, err = DecodeConfig(cfg)
- c.Assert(err, qt.Not(qt.IsNil))
-}
-
-func TestEmptyMatcher(t *testing.T) {
- c := qt.New(t)
-
- tomlConfig := `
-[deployment]
-[[deployment.matchers]]
-`
- cfg, err := config.FromConfigString(tomlConfig, "toml")
- c.Assert(err, qt.IsNil)
-
- _, err = DecodeConfig(cfg)
- c.Assert(err, qt.Not(qt.IsNil))
-}
diff --git a/deploy/google.go b/deploy/google.go
deleted file mode 100644
index 5b302e95b..000000000
--- a/deploy/google.go
+++ /dev/null
@@ -1,39 +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.
-
-//go:build withdeploy
-
-package deploy
-
-import (
- "context"
- "fmt"
- "strings"
-
- "google.golang.org/api/compute/v1"
-)
-
-// Invalidate all of the content in a Google Cloud CDN distribution.
-func InvalidateGoogleCloudCDN(ctx context.Context, origin string) error {
- parts := strings.Split(origin, "/")
- if len(parts) != 2 {
- return fmt.Errorf("origin must be /")
- }
- service, err := compute.NewService(ctx)
- if err != nil {
- return err
- }
- rule := &compute.CacheInvalidationRule{Path: "/*"}
- _, err = service.UrlMaps.InvalidateCache(parts[0], parts[1], rule).Context(ctx).Do()
- return err
-}
diff --git a/deps/deps.go b/deps/deps.go
index d0d6d95fc..b5f935c09 100644
--- a/deps/deps.go
+++ b/deps/deps.go
@@ -1,51 +1,27 @@
package deps
import (
- "context"
- "fmt"
- "io"
+ "io/ioutil"
+ "log"
"os"
- "path/filepath"
- "sort"
- "strings"
- "sync"
- "sync/atomic"
- "github.com/bep/logg"
- "github.com/gohugoio/hugo/cache/dynacache"
- "github.com/gohugoio/hugo/cache/filecache"
- "github.com/gohugoio/hugo/common/hexec"
- "github.com/gohugoio/hugo/common/loggers"
- "github.com/gohugoio/hugo/common/maps"
- "github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/config"
- "github.com/gohugoio/hugo/config/allconfig"
- "github.com/gohugoio/hugo/config/security"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
- "github.com/gohugoio/hugo/identity"
- "github.com/gohugoio/hugo/internal/js"
- "github.com/gohugoio/hugo/internal/warpc"
- "github.com/gohugoio/hugo/media"
- "github.com/gohugoio/hugo/resources/page"
- "github.com/gohugoio/hugo/resources/postpub"
- "github.com/gohugoio/hugo/tpl/tplimpl"
-
- "github.com/gohugoio/hugo/metrics"
- "github.com/gohugoio/hugo/resources"
- "github.com/gohugoio/hugo/source"
+ "github.com/gohugoio/hugo/output"
"github.com/gohugoio/hugo/tpl"
- "github.com/spf13/afero"
+ jww "github.com/spf13/jwalterweatherman"
)
// Deps holds dependencies used by many.
-// There will be normally only one instance of deps in play
+// There will be normally be only one instance of deps in play
// at a given time, i.e. one per Site built.
type Deps struct {
// The logger to use.
- Log loggers.Logger `json:"-"`
+ Log *jww.Notepad `json:"-"`
- ExecHelper *hexec.Exec
+ // The templates to use. This will usually implement the full tpl.TemplateHandler.
+ Tmpl tpl.TemplateFinder `json:"-"`
// The file systems to use.
Fs *hugofs.Fs `json:"-"`
@@ -56,443 +32,145 @@ type Deps struct {
// The ContentSpec to use
*helpers.ContentSpec `json:"-"`
- // The SourceSpec to use
- SourceSpec *source.SourceSpec `json:"-"`
-
- // The Resource Spec to use
- ResourceSpec *resources.Spec
-
// The configuration to use
- Conf config.AllProvider `json:"-"`
-
- // The memory cache to use.
- MemCache *dynacache.Cache
+ Cfg config.Provider `json:"-"`
// The translation func to use
- Translate func(ctx context.Context, translationID string, templateData any) string `json:"-"`
+ Translate func(translationID string, args ...interface{}) string `json:"-"`
- // The site building.
- Site page.Site
+ Language *helpers.Language
- TemplateStore *tplimpl.TemplateStore
+ // All the output formats available for the current site.
+ OutputFormatsConfig output.Formats
- // Used in tests
- OverloadedTemplateFuncs map[string]any
+ templateProvider ResourceProvider
+ WithTemplate func(templ tpl.TemplateHandler) error `json:"-"`
- TranslationProvider ResourceProvider
-
- Metrics metrics.Provider
-
- // BuildStartListeners will be notified before a build starts.
- BuildStartListeners *Listeners[any]
-
- // BuildEndListeners will be notified after a build finishes.
- BuildEndListeners *Listeners[any]
-
- // OnChangeListeners will be notified when something changes.
- OnChangeListeners *Listeners[identity.Identity]
-
- // Resources that gets closed when the build is done or the server shuts down.
- BuildClosers *types.Closers
-
- // This is common/global for all sites.
- BuildState *BuildState
-
- // Misc counters.
- Counters *Counters
-
- // Holds RPC dispatchers for Katex etc.
- // TODO(bep) rethink this re. a plugin setup, but this will have to do for now.
- WasmDispatchers *warpc.Dispatchers
-
- // The JS batcher client.
- JSBatcherClient js.BatcherClient
-
- isClosed bool
-
- *globalErrHandler
-}
-
-func (d Deps) Clone(s page.Site, conf config.AllProvider) (*Deps, error) {
- d.Conf = conf
- d.Site = s
- d.ExecHelper = nil
- d.ContentSpec = nil
-
- if err := d.Init(); err != nil {
- return nil, err
- }
-
- return &d, nil
-}
-
-func (d *Deps) GetTemplateStore() *tplimpl.TemplateStore {
- return d.TemplateStore
-}
-
-func (d *Deps) Init() error {
- if d.Conf == nil {
- panic("conf is nil")
- }
-
- if d.Fs == nil {
- // For tests.
- d.Fs = hugofs.NewFrom(afero.NewMemMapFs(), d.Conf.BaseConfig())
- }
-
- if d.Log == nil {
- d.Log = loggers.NewDefault()
- }
-
- if d.globalErrHandler == nil {
- d.globalErrHandler = &globalErrHandler{
- logger: d.Log,
- }
- }
- if d.BuildState == nil {
- d.BuildState = &BuildState{}
- }
- if d.Counters == nil {
- d.Counters = &Counters{}
- }
- if d.BuildState.DeferredExecutions == nil {
- if d.BuildState.DeferredExecutionsGroupedByRenderingContext == nil {
- d.BuildState.DeferredExecutionsGroupedByRenderingContext = make(map[tpl.RenderingContext]*DeferredExecutions)
- }
- d.BuildState.DeferredExecutions = &DeferredExecutions{
- Executions: maps.NewCache[string, *tpl.DeferredExecution](),
- FilenamesWithPostPrefix: maps.NewCache[string, bool](),
- }
- }
-
- if d.BuildStartListeners == nil {
- d.BuildStartListeners = &Listeners[any]{}
- }
-
- if d.BuildEndListeners == nil {
- d.BuildEndListeners = &Listeners[any]{}
- }
-
- if d.BuildClosers == nil {
- d.BuildClosers = &types.Closers{}
- }
-
- if d.OnChangeListeners == nil {
- d.OnChangeListeners = &Listeners[identity.Identity]{}
- }
-
- if d.Metrics == nil && d.Conf.TemplateMetrics() {
- d.Metrics = metrics.NewProvider(d.Conf.TemplateMetricsHints())
- }
-
- if d.ExecHelper == nil {
- d.ExecHelper = hexec.New(d.Conf.GetConfigSection("security").(security.Config), d.Conf.WorkingDir(), d.Log)
- }
-
- if d.MemCache == nil {
- d.MemCache = dynacache.New(dynacache.Options{Watching: d.Conf.Watching(), Log: d.Log})
- }
-
- if d.PathSpec == nil {
- hashBytesReceiverFunc := func(name string, match []byte) {
- s := string(match)
- switch s {
- case postpub.PostProcessPrefix:
- d.BuildState.AddFilenameWithPostPrefix(name)
- case tpl.HugoDeferredTemplatePrefix:
- d.BuildState.DeferredExecutions.FilenamesWithPostPrefix.Set(name, true)
- }
- }
-
- // Skip binary files.
- mediaTypes := d.Conf.GetConfigSection("mediaTypes").(media.Types)
- hashBytesShouldCheck := func(name string) bool {
- ext := strings.TrimPrefix(filepath.Ext(name), ".")
- return mediaTypes.IsTextSuffix(ext)
- }
- d.Fs.PublishDir = hugofs.NewHasBytesReceiver(
- d.Fs.PublishDir,
- hashBytesShouldCheck,
- hashBytesReceiverFunc,
- []byte(tpl.HugoDeferredTemplatePrefix),
- []byte(postpub.PostProcessPrefix))
-
- pathSpec, err := helpers.NewPathSpec(d.Fs, d.Conf, d.Log)
- if err != nil {
- return err
- }
- d.PathSpec = pathSpec
- } else {
- var err error
- d.PathSpec, err = helpers.NewPathSpecWithBaseBaseFsProvided(d.Fs, d.Conf, d.Log, d.PathSpec.BaseFs)
- if err != nil {
- return err
- }
- }
-
- if d.ContentSpec == nil {
- contentSpec, err := helpers.NewContentSpec(d.Conf, d.Log, d.Content.Fs, d.ExecHelper)
- if err != nil {
- return err
- }
- d.ContentSpec = contentSpec
- }
-
- if d.SourceSpec == nil {
- d.SourceSpec = source.NewSourceSpec(d.PathSpec, nil, d.Fs.Source)
- }
-
- var common *resources.SpecCommon
- if d.ResourceSpec != nil {
- common = d.ResourceSpec.SpecCommon
- }
-
- fileCaches, err := filecache.NewCaches(d.PathSpec)
- if err != nil {
- return fmt.Errorf("failed to create file caches from configuration: %w", err)
- }
-
- resourceSpec, err := resources.NewSpec(d.PathSpec, common, fileCaches, d.MemCache, d.BuildState, d.Log, d, d.ExecHelper, d.BuildClosers, d.BuildState)
- if err != nil {
- return fmt.Errorf("failed to create resource spec: %w", err)
- }
- d.ResourceSpec = resourceSpec
-
- return nil
-}
-
-// TODO(bep) rework this to get it in line with how we manage templates.
-func (d *Deps) Compile(prototype *Deps) error {
- var err error
- if prototype == nil {
-
- if err = d.TranslationProvider.NewResource(d); err != nil {
- return err
- }
- return nil
- }
-
- if err = d.TranslationProvider.CloneResource(d, prototype); err != nil {
- return err
- }
-
- return nil
-}
-
-// MkdirTemp returns a temporary directory path that will be cleaned up on exit.
-func (d Deps) MkdirTemp(pattern string) (string, error) {
- filename, err := os.MkdirTemp("", pattern)
- if err != nil {
- return "", err
- }
- d.BuildClosers.Add(
- types.CloserFunc(
- func() error {
- return os.RemoveAll(filename)
- },
- ),
- )
-
- return filename, nil
-}
-
-type globalErrHandler struct {
- logger loggers.Logger
-
- // Channel for some "hard to get to" build errors
- buildErrors chan error
- // Used to signal that the build is done.
- quit chan struct{}
-}
-
-// SendError sends the error on a channel to be handled later.
-// This can be used in situations where returning and aborting the current
-// operation isn't practical.
-func (e *globalErrHandler) SendError(err error) {
- if e.buildErrors != nil {
- select {
- case <-e.quit:
- case e.buildErrors <- err:
- default:
- }
- return
- }
- e.logger.Errorln(err)
-}
-
-func (e *globalErrHandler) StartErrorCollector() chan error {
- e.quit = make(chan struct{})
- e.buildErrors = make(chan error, 10)
- return e.buildErrors
-}
-
-func (e *globalErrHandler) StopErrorCollector() {
- if e.buildErrors != nil {
- close(e.quit)
- close(e.buildErrors)
- }
-}
-
-// Listeners represents an event listener.
-type Listeners[T any] struct {
- sync.Mutex
-
- // A list of funcs to be notified about an event.
- // If the return value is true, the listener will be removed.
- listeners []func(...T) bool
-}
-
-// Add adds a function to a Listeners instance.
-func (b *Listeners[T]) Add(f func(...T) bool) {
- if b == nil {
- return
- }
- b.Lock()
- defer b.Unlock()
- b.listeners = append(b.listeners, f)
-}
-
-// Notify executes all listener functions.
-func (b *Listeners[T]) Notify(vs ...T) {
- b.Lock()
- defer b.Unlock()
- temp := b.listeners[:0]
- for _, notify := range b.listeners {
- if !notify(vs...) {
- temp = append(temp, notify)
- }
- }
- b.listeners = temp
+ translationProvider ResourceProvider
}
// ResourceProvider is used to create and refresh, and clone resources needed.
type ResourceProvider interface {
- NewResource(dst *Deps) error
- CloneResource(dst, src *Deps) error
+ Update(deps *Deps) error
+ Clone(deps *Deps) error
}
-func (d *Deps) Close() error {
- if d.isClosed {
- return nil
- }
- d.isClosed = true
+func (d *Deps) TemplateHandler() tpl.TemplateHandler {
+ return d.Tmpl.(tpl.TemplateHandler)
+}
- if d.MemCache != nil {
- d.MemCache.Stop()
+func (d *Deps) LoadResources() error {
+ // Note that the translations need to be loaded before the templates.
+ if err := d.translationProvider.Update(d); err != nil {
+ return err
}
- if d.WasmDispatchers != nil {
- d.WasmDispatchers.Close()
+
+ if err := d.templateProvider.Update(d); err != nil {
+ return err
}
- return d.BuildClosers.Close()
+
+ if th, ok := d.Tmpl.(tpl.TemplateHandler); ok {
+ th.PrintErrors()
+ }
+
+ return nil
+}
+
+func New(cfg DepsCfg) (*Deps, error) {
+ var (
+ logger = cfg.Logger
+ fs = cfg.Fs
+ )
+
+ if cfg.TemplateProvider == nil {
+ panic("Must have a TemplateProvider")
+ }
+
+ if cfg.TranslationProvider == nil {
+ panic("Must have a TranslationProvider")
+ }
+
+ if cfg.Language == nil {
+ panic("Must have a Language")
+ }
+
+ if logger == nil {
+ logger = jww.NewNotepad(jww.LevelError, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
+ }
+
+ if fs == nil {
+ // Default to the production file system.
+ fs = hugofs.NewDefault(cfg.Language)
+ }
+
+ ps, err := helpers.NewPathSpec(fs, cfg.Language)
+
+ if err != nil {
+ return nil, err
+ }
+
+ d := &Deps{
+ Fs: fs,
+ Log: logger,
+ templateProvider: cfg.TemplateProvider,
+ translationProvider: cfg.TranslationProvider,
+ WithTemplate: cfg.WithTemplate,
+ PathSpec: ps,
+ ContentSpec: helpers.NewContentSpec(cfg.Language),
+ Cfg: cfg.Language,
+ Language: cfg.Language,
+ }
+
+ return d, nil
+}
+
+// ForLanguage creates a copy of the Deps with the language dependent
+// parts switched out.
+func (d Deps) ForLanguage(l *helpers.Language) (*Deps, error) {
+ var err error
+
+ d.PathSpec, err = helpers.NewPathSpec(d.Fs, l)
+ if err != nil {
+ return nil, err
+ }
+
+ d.ContentSpec = helpers.NewContentSpec(l)
+ d.Cfg = l
+ d.Language = l
+
+ if err := d.translationProvider.Clone(&d); err != nil {
+ return nil, err
+ }
+
+ if err := d.templateProvider.Clone(&d); err != nil {
+ return nil, err
+ }
+
+ return &d, nil
+
}
// DepsCfg contains configuration options that can be used to configure Hugo
// on a global level, i.e. logging etc.
// Nil values will be given default values.
type DepsCfg struct {
- // The logger to use. Only set in some tests.
- // TODO(bep) get rid of this.
- TestLogger loggers.Logger
- // The logging level to use.
- LogLevel logg.Level
-
- // Logging output.
- StdErr io.Writer
-
- // The console output.
- StdOut io.Writer
+ // The Logger to use.
+ Logger *jww.Notepad
// The file systems to use
Fs *hugofs.Fs
- // The Site in use
- Site page.Site
+ // The language to use.
+ Language *helpers.Language
- Configs *allconfig.Configs
+ // The configuration to use.
+ Cfg config.Provider
// Template handling.
TemplateProvider ResourceProvider
+ WithTemplate func(templ tpl.TemplateHandler) error
// i18n handling.
TranslationProvider ResourceProvider
-
- // ChangesFromBuild for changes passed back to the server/watch process.
- ChangesFromBuild chan []identity.Identity
-}
-
-// BuildState are state used during a build.
-type BuildState struct {
- counter uint64
-
- mu sync.Mutex // protects state below.
-
- OnSignalRebuild func(ids ...identity.Identity)
-
- // A set of filenames in /public that
- // contains a post-processing prefix.
- filenamesWithPostPrefix map[string]bool
-
- DeferredExecutions *DeferredExecutions
-
- // Deferred executions grouped by rendering context.
- DeferredExecutionsGroupedByRenderingContext map[tpl.RenderingContext]*DeferredExecutions
-}
-
-// Misc counters.
-type Counters struct {
- // Counter for the math.Counter function.
- MathCounter atomic.Uint64
-}
-
-type DeferredExecutions struct {
- // A set of filenames in /public that
- // contains a post-processing prefix.
- FilenamesWithPostPrefix *maps.Cache[string, bool]
-
- // Maps a placeholder to a deferred execution.
- Executions *maps.Cache[string, *tpl.DeferredExecution]
-}
-
-var _ identity.SignalRebuilder = (*BuildState)(nil)
-
-// StartStageRender will be called before a stage is rendered.
-func (b *BuildState) StartStageRender(stage tpl.RenderingContext) {
-}
-
-// StopStageRender will be called after a stage is rendered.
-func (b *BuildState) StopStageRender(stage tpl.RenderingContext) {
- b.DeferredExecutionsGroupedByRenderingContext[stage] = b.DeferredExecutions
- b.DeferredExecutions = &DeferredExecutions{
- Executions: maps.NewCache[string, *tpl.DeferredExecution](),
- FilenamesWithPostPrefix: maps.NewCache[string, bool](),
- }
-}
-
-func (b *BuildState) SignalRebuild(ids ...identity.Identity) {
- b.OnSignalRebuild(ids...)
-}
-
-func (b *BuildState) AddFilenameWithPostPrefix(filename string) {
- b.mu.Lock()
- defer b.mu.Unlock()
- if b.filenamesWithPostPrefix == nil {
- b.filenamesWithPostPrefix = make(map[string]bool)
- }
- b.filenamesWithPostPrefix[filename] = true
-}
-
-func (b *BuildState) GetFilenamesWithPostPrefix() []string {
- b.mu.Lock()
- defer b.mu.Unlock()
- var filenames []string
- for filename := range b.filenamesWithPostPrefix {
- filenames = append(filenames, filename)
- }
- sort.Strings(filenames)
- return filenames
-}
-
-func (b *BuildState) Incr() int {
- return int(atomic.AddUint64(&b.counter, uint64(1)))
}
diff --git a/deps/deps_test.go b/deps/deps_test.go
deleted file mode 100644
index e92ed2327..000000000
--- a/deps/deps_test.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 deps_test
-
-import (
- "testing"
-
- qt "github.com/frankban/quicktest"
- "github.com/gohugoio/hugo/deps"
-)
-
-func TestBuildFlags(t *testing.T) {
- c := qt.New(t)
- var bf deps.BuildState
- bf.Incr()
- bf.Incr()
- bf.Incr()
-
- c.Assert(bf.Incr(), qt.Equals, 4)
-}
diff --git a/docs/.codespellrc b/docs/.codespellrc
deleted file mode 100644
index 564fc77c0..000000000
--- a/docs/.codespellrc
+++ /dev/null
@@ -1,13 +0,0 @@
-# Config file for codespell.
-# https://github.com/codespell-project/codespell#using-a-config-file
-
-[codespell]
-
-# Comma separated list of dirs to be skipped.
-skip = _vendor,.cspell.json,chroma.css,chroma_dark.css
-
-# Comma separated list of words to be ignored. Words must be lowercased.
-ignore-words-list = abl,edn,te,ue,trys,januar,womens,crossreferences
-
-# Check file names as well.
-check-filenames = true
diff --git a/docs/.cspell.json b/docs/.cspell.json
deleted file mode 100644
index bf61489da..000000000
--- a/docs/.cspell.json
+++ /dev/null
@@ -1,185 +0,0 @@
-{
- "version": "0.2",
- "allowCompoundWords": true,
- "files": [
- "**/*.md"
- ],
- "flagWords": [
- "alot",
- "hte",
- "langauge",
- "reccommend",
- "seperate",
- "teh"
- ],
- "ignorePaths": [
- "**/emojis.md",
- "**/commands/*",
- "**/showcase/*",
- "**/tools/*"
- ],
- "ignoreRegExpList": [
- "# cspell: ignore fenced code blocks",
- "^(\\s*`{3,}).*[\\s\\S]*?^\\1$",
- "# cspell: ignore words joined with dot",
- "\\w+\\.\\w+",
- "# cspell: ignore strings within backticks",
- "`.+`",
- "# cspell: ignore strings within double quotes",
- "\".+\"",
- "# cspell: ignore strings within brackets",
- "\\[.+\\]",
- "# cspell: ignore strings within parentheses",
- "\\(.+\\)",
- "# cspell: ignore words that begin with a slash",
- "/\\w+",
- "# cspell: ignore everything within action delimiters",
- "\\{\\{.+\\}\\}",
- "# cspell: ignore everything after a right arrow",
- "\\s+→\\s+.+"
- ],
- "language": "en",
- "words": [
- "composability",
- "configurators",
- "defang",
- "deindent",
- "downscale",
- "downscaling",
- "exif",
- "geolocalized",
- "grayscale",
- "marshal",
- "marshaling",
- "multihost",
- "multiplatfom",
- "performantly",
- "preconfigured",
- "prerendering",
- "redirection",
- "redirections",
- "subexpression",
- "suppressible",
- "synchronisation",
- "templating",
- "transpile",
- "unmarshal",
- "unmarshaling",
- "unmarshals",
- "# ----------------------------------------------------------------------",
- "# cspell: ignore hugo terminology",
- "# ----------------------------------------------------------------------",
- "alignx",
- "attrlink",
- "canonify",
- "codeowners",
- "dynacache",
- "eturl",
- "getenv",
- "gohugo",
- "gohugoio",
- "keyvals",
- "leftdelim",
- "linkify",
- "numworkermultiplier",
- "rightdelim",
- "shortcode",
- "stringifier",
- "struct",
- "toclevels",
- "unmarshal",
- "unpublishdate",
- "zgotmplz",
- "# ----------------------------------------------------------------------",
- "# cspell: ignore foreign language words",
- "# ----------------------------------------------------------------------",
- "bezpieczeństwo",
- "blatt",
- "buch",
- "descripción",
- "dokumentation",
- "erklärungen",
- "libros",
- "mercredi",
- "miesiąc",
- "miesiąc",
- "miesiąca",
- "miesiące",
- "miesięcy",
- "misérables",
- "mittwoch",
- "muchos",
- "novembre",
- "otro",
- "pocos",
- "produkte",
- "projekt",
- "prywatność",
- "referenz",
- "régime",
- "# ----------------------------------------------------------------------",
- "# cspell: ignore names",
- "# ----------------------------------------------------------------------",
- "Atishay",
- "Cosette",
- "Eliott",
- "Furet",
- "Gregor",
- "Jaco",
- "Lanczos",
- "Ninke",
- "Noll",
- "Pastorius",
- "Samsa",
- "Stucki",
- "Thénardier",
- "WASI",
- "# ----------------------------------------------------------------------",
- "# cspell: ignore operating systems and software packages",
- "# ----------------------------------------------------------------------",
- "asciidoctor",
- "brotli",
- "cifs",
- "corejs",
- "disqus",
- "docutils",
- "dpkg",
- "doas",
- "eopkg",
- "gitee",
- "goldmark",
- "katex",
- "kubuntu",
- "lubuntu",
- "mathjax",
- "nosql",
- "pandoc",
- "pkgin",
- "rclone",
- "xubuntu",
- "# ----------------------------------------------------------------------",
- "# cspell: ignore miscellaneous",
- "# ----------------------------------------------------------------------",
- "achristie",
- "ccpa",
- "cpra",
- "ddmaurier",
- "dring",
- "fleqn",
- "inor",
- "jausten",
- "jdoe",
- "jsmith",
- "leqno",
- "milli",
- "monokai",
- "mysanityprojectid",
- "rgba",
- "rsmith",
- "tdewolff",
- "tjones",
- "vcard",
- "wcag",
- "xfeff"
- ]
-}
diff --git a/docs/.editorconfig b/docs/.editorconfig
deleted file mode 100644
index dd2a0096f..000000000
--- a/docs/.editorconfig
+++ /dev/null
@@ -1,20 +0,0 @@
-# https://editorconfig.org
-
-root = true
-
-[*]
-charset = utf-8
-end_of_line = lf
-indent_size = 2
-indent_style = space
-trim_trailing_whitespace = true
-
-[*.go]
-indent_size = 8
-indent_style = tab
-
-[*.js]
-insert_final_newline = true
-
-[*.md]
-trim_trailing_whitespace = false
diff --git a/docs/.github/ISSUE_TEMPLATE/config.yml b/docs/.github/ISSUE_TEMPLATE/config.yml
deleted file mode 100644
index 3ba13e0ce..000000000
--- a/docs/.github/ISSUE_TEMPLATE/config.yml
+++ /dev/null
@@ -1 +0,0 @@
-blank_issues_enabled: false
diff --git a/docs/.github/ISSUE_TEMPLATE/default.md b/docs/.github/ISSUE_TEMPLATE/default.md
deleted file mode 100644
index ada35b3a5..000000000
--- a/docs/.github/ISSUE_TEMPLATE/default.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-name: Default
-about: This is the default issue template.
-labels:
- - NeedsTriage
----
diff --git a/docs/.github/SUPPORT.md b/docs/.github/SUPPORT.md
deleted file mode 100644
index 96a4400c3..000000000
--- a/docs/.github/SUPPORT.md
+++ /dev/null
@@ -1,3 +0,0 @@
-### Asking support questions
-
-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.
diff --git a/docs/.github/stale.yml b/docs/.github/stale.yml
deleted file mode 100644
index 1e72eb329..000000000
--- a/docs/.github/stale.yml
+++ /dev/null
@@ -1,22 +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
- - UndocumentedFeature
-# 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 you still think this is important, 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/docs/.github/workflows/codeql-analysis.yml b/docs/.github/workflows/codeql-analysis.yml
deleted file mode 100644
index 86441b845..000000000
--- a/docs/.github/workflows/codeql-analysis.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-name: "CodeQL"
-
-on:
- schedule:
- - cron: "0 0 1 * *"
-
-jobs:
- analyze:
- name: Analyze
- runs-on: ubuntu-latest
- permissions:
- actions: read
- contents: read
- security-events: write
-
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
-
- - name: Initialize CodeQL
- uses: github/codeql-action/init@v3
- with:
- languages: "javascript"
-
- - name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v3
diff --git a/docs/.github/workflows/spellcheck.yml b/docs/.github/workflows/spellcheck.yml
deleted file mode 100644
index e01ab1764..000000000
--- a/docs/.github/workflows/spellcheck.yml
+++ /dev/null
@@ -1,27 +0,0 @@
-name: "Check spelling"
-on:
- push:
- pull_request:
- branches-ignore:
- - "dependabot/**"
-
-permissions:
- contents: read
-
-jobs:
- spellcheck:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- - uses: streetsidesoftware/cspell-action@v5
- with:
- check_dot_files: false
- files: content/**/*.md
- incremental_files_only: true
- inline: warning
- strict: false
- - uses: codespell-project/actions-codespell@v2
- with:
- check_filenames: true
- check_hidden: true
- # by default, codespell uses configuration from the .codespellrc
diff --git a/docs/.github/workflows/super-linter.yml b/docs/.github/workflows/super-linter.yml
deleted file mode 100644
index d8e408ee2..000000000
--- a/docs/.github/workflows/super-linter.yml
+++ /dev/null
@@ -1,41 +0,0 @@
-name: Super Linter
-
-on:
- workflow_dispatch:
-
-permissions:
- contents: read # to fetch code (actions/checkout)
-
-jobs:
- build:
- permissions:
- contents: read # to fetch code (actions/checkout)
- statuses: write # to mark status of each linter run (github/super-linter/slim)
-
- name: Lint Code Base
- runs-on: ubuntu-latest
- if: ${{ github.actor != 'dependabot[bot]' }}
-
- steps:
- - name: Checkout Code
- uses: actions/checkout@v4
-
- - name: Lint Code Base
- uses: super-linter/super-linter/slim@v6
- env:
- DEFAULT_BRANCH: master
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- IGNORE_GITIGNORED_FILES: true
- LINTER_RULES_PATH: /
- LOG_LEVEL: NOTICE
- MARKDOWN_CONFIG_FILE: .markdownlint.yaml
- SUPPRESS_POSSUM: true
- VALIDATE_CSS: false
- VALIDATE_EDITORCONFIG: false
- VALIDATE_GITLEAKS: false
- VALIDATE_HTML: false
- VALIDATE_JAVASCRIPT_STANDARD: false
- VALIDATE_JSCPD: false
- VALIDATE_NATURAL_LANGUAGE: false
- VALIDATE_SHELL_SHFMT: false
- VALIDATE_XML: false
diff --git a/docs/.gitignore b/docs/.gitignore
index 5208c5c3a..665360d49 100644
--- a/docs/.gitignore
+++ b/docs/.gitignore
@@ -1,12 +1,2 @@
-.DS_Store
-.hugo_build.lock
/.idea
-/.vscode
-/dist
/public
-/resources
-hugo_stats.json
-node_modules/
-nohup.out
-package-lock.json
-trace.out
diff --git a/docs/.markdownlint.yaml b/docs/.markdownlint.yaml
deleted file mode 100644
index dbb5b2ee8..000000000
--- a/docs/.markdownlint.yaml
+++ /dev/null
@@ -1,27 +0,0 @@
-# https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md
-
-MD001: false
-MD002: false
-MD003: false
-MD004: false
-MD007: false
-MD012:
- maximum: 2
-MD013: false
-MD014: false
-MD022: false
-MD024: false
-MD031: false
-MD032: false
-MD033: false
-MD034: false
-MD036: false
-MD037: false
-MD038: false
-MD041: false
-MD046: false
-MD049: false
-MD050: false
-MD051: false
-MD053: false
-MD055: false
diff --git a/docs/.markdownlintignore b/docs/.markdownlintignore
deleted file mode 100644
index 4ac45b395..000000000
--- a/docs/.markdownlintignore
+++ /dev/null
@@ -1,6 +0,0 @@
-**/commands/**
-**/functions/**
-**/news/**
-**/showcase/**
-**/zh/**
-**/license.md
diff --git a/docs/.prettierignore b/docs/.prettierignore
deleted file mode 100644
index f24bbcef0..000000000
--- a/docs/.prettierignore
+++ /dev/null
@@ -1,18 +0,0 @@
-# Ignore all SVG icons.
-**/icons.html
-
-# These are whitespace sensitive.
-layouts/_default/_markup/render-code*
-layouts/_default/_markup/render-table*
-layouts/shortcodes/glossary-term.html
-layouts/shortcodes/glossary.html
-layouts/shortcodes/highlighting-styles.html
-layouts/shortcodes/list-pages-in-section.html
-layouts/shortcodes/quick-reference.html
-
-# No root node.
-layouts/partials/layouts/head/head.html
-
-# Auto generated.
-assets/css/components/chroma*.css
-assets/jsconfig.json
diff --git a/docs/.prettierrc b/docs/.prettierrc
deleted file mode 100644
index 395ae39af..000000000
--- a/docs/.prettierrc
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "plugins": [
- "prettier-plugin-go-template",
- "@awmottaz/prettier-plugin-void-html"
- ],
- "overrides": [
- {
- "files": ["*.html"],
- "options": {
- "parser": "go-template",
- "goTemplateBracketSpacing": true,
- "bracketSameLine": true
- }
- },
- {
- "files": ["*.js", "*.ts"],
- "options": {
- "useTabs": true,
- "printWidth": 120,
- "singleQuote": true
- }
- }
- ]
-}
diff --git a/docs/.textlintignore b/docs/.textlintignore
deleted file mode 100644
index 97a18e37c..000000000
--- a/docs/.textlintignore
+++ /dev/null
@@ -1,3 +0,0 @@
-**/news/**
-**/showcase/**
-**/zh/**
\ No newline at end of file
diff --git a/docs/.vscode/extensions.json b/docs/.vscode/extensions.json
deleted file mode 100644
index 76c6afe3f..000000000
--- a/docs/.vscode/extensions.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "recommendations": [
- "DavidAnson.vscode-markdownlint",
- "EditorConfig.EditorConfig",
- "streetsidesoftware.code-spell-checker"
- ]
-}
diff --git a/docs/LICENSE.md b/docs/LICENSE.md
deleted file mode 100644
index d4facbf8a..000000000
--- a/docs/LICENSE.md
+++ /dev/null
@@ -1,3 +0,0 @@
-See [content/LICENSE.md](content/LICENSE.md) for the license of the content of this repository.
-
-The theme (layouts, CSS, JavaScript etc.) of this repository has no open source license. It is custom made for the Hugo sites and is not meant for reuse.
diff --git a/docs/README.md b/docs/README.md
index 58d0e748c..60a554e54 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,27 +1,13 @@
-
+# Hugo Docs
-A fast and flexible static site generator built with love by [bep], [spf13], and [friends] in [Go].
+Documentation site for [Hugo](https://github.com/gohugoio/hugo), the very fast and flexible static site generator built with love in GoLang.
----
+## Branches
-[](https://app.netlify.com/sites/gohugoio/deploys)
-[](https://gohugo.io/contribute/documentation/)
+* The `master` branch is the **current Hugo version** and will be auto-deployed to [gohugo.io/](https://gohugo.io/).
+* Anything not relevant to the current Hugo version goes into the `dev` branch.
+* Changes in [hugo/docs](https://github.com/gohugoio/hugo/tree/master/docs) will, in general, be merged once every release, but can be manually merged/cherry picked if needed. This goes both ways.
+* All contributions that is not tightly coupled with code changes, should be made directly to `hugoDocs`.
+* But we also merge PRs into [hugo/docs](https://github.com/gohugoio/hugo/tree/master/docs), but preferably changes that is related to the code in the PR itself
-This is the repository for the [Hugo](https://github.com/gohugoio/hugo) documentation site.
-
-Please see the [contributing] section for guidelines, examples, and process.
-
-[bep]: https://github.com/bep
-[spf13]: https://github.com/spf13
-[friends]: https://github.com/gohugoio/hugo/graphs/contributors
-[go]: https://go.dev/
-[contributing]: https://gohugo.io/contribute/documentation
-
-# Install
-
-```sh
-npm i
-hugo server
-```
-
-**Note:** We're working on removing the need to run `npm i` for local development. Stay tuned.
+To summarize, we have two branches in this repository: `master` (current Hugo) and `dev` (next Hugo).
diff --git a/docs/archetypes/default.md b/docs/archetypes/default.md
index 58a60edc4..6d6497c4d 100644
--- a/docs/archetypes/default.md
+++ b/docs/archetypes/default.md
@@ -1,6 +1,6 @@
----
-title: {{ replace .File.ContentBaseName "-" " " | strings.FirstUpper }}
-description:
-categories: []
-keywords: []
----
++++
+weight = 5
+[menu]
+ [menu.main]
+ parent = "x"
++++
diff --git a/docs/archetypes/functions.md b/docs/archetypes/functions.md
deleted file mode 100644
index de2d72060..000000000
--- a/docs/archetypes/functions.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-title: {{ replace .File.ContentBaseName "-" " " | title }}
-description:
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType:
- signatures: []
----
diff --git a/docs/archetypes/glossary.md b/docs/archetypes/glossary.md
deleted file mode 100644
index 1eeb4ef4b..000000000
--- a/docs/archetypes/glossary.md
+++ /dev/null
@@ -1,13 +0,0 @@
----
-title: {{ replace .File.ContentBaseName "-" " " }}
-params:
- reference:
----
-
-
diff --git a/docs/archetypes/methods.md b/docs/archetypes/methods.md
deleted file mode 100644
index 944fe527c..000000000
--- a/docs/archetypes/methods.md
+++ /dev/null
@@ -1,10 +0,0 @@
----
-title: {{ replace .File.ContentBaseName "-" " " | title }}
-description:
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType:
- signatures: []
----
diff --git a/docs/archetypes/news.md b/docs/archetypes/news.md
deleted file mode 100644
index 04792a152..000000000
--- a/docs/archetypes/news.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: {{ replace .File.ContentBaseName "-" " " | strings.FirstUpper }}
-description:
-categories: []
-keywords: []
-publishDate: {{ .Date }}
----
diff --git a/docs/archetypes/showcase.md b/docs/archetypes/showcase.md
new file mode 100644
index 000000000..ebe87035a
--- /dev/null
+++ b/docs/archetypes/showcase.md
@@ -0,0 +1,14 @@
+---
+date: 2013-07-01T07:32:00Z
+description: ""
+license: ""
+licenseLink: ""
+sitelink: http://spf13.com/
+sourceLink: https://github.com/spf13/spf13.com
+tags:
+- personal
+- blog
+thumbnail: /img/spf13-tn.jpg
+title: spf13.com
+---
+
diff --git a/docs/assets/css/components/all.css b/docs/assets/css/components/all.css
deleted file mode 100644
index f5002fd50..000000000
--- a/docs/assets/css/components/all.css
+++ /dev/null
@@ -1,7 +0,0 @@
-/* The order of these does not matter. */
-@import "./content.css";
-@import "./fonts.css";
-@import "./helpers.css";
-@import "./shortcodes.css";
-@import "./tableofcontents.css";
-@import "./view-transitions.css";
diff --git a/docs/assets/css/components/chroma.css b/docs/assets/css/components/chroma.css
deleted file mode 100644
index 9d4c91f7b..000000000
--- a/docs/assets/css/components/chroma.css
+++ /dev/null
@@ -1,85 +0,0 @@
-/* Background */ .bg { background-color: var(--color-light); }
-/* PreWrapper */ .chroma { background-color: var(--color-light); }
-/* Other */ .chroma .x { }
-/* Error */ .chroma .err { color: #a61717; background-color: #e3d2d2 }
-/* CodeLine */ .chroma .cl { }
-/* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }
-/* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; }
-/* LineHighlight */ .chroma .hl { background-color: #ffffcc }
-/* LineNumbersTable */ .chroma .lnt { white-space: pre; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
-/* LineNumbers */ .chroma .ln { white-space: pre; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
-/* Line */ .chroma .line { display: flex; }
-/* Keyword */ .chroma .k { font-weight: bold }
-/* KeywordConstant */ .chroma .kc { font-weight: bold }
-/* KeywordDeclaration */ .chroma .kd { font-weight: bold }
-/* KeywordNamespace */ .chroma .kn { font-weight: bold }
-/* KeywordPseudo */ .chroma .kp { font-weight: bold }
-/* KeywordReserved */ .chroma .kr { font-weight: bold }
-/* KeywordType */ .chroma .kt { color: #445588; font-weight: bold }
-/* Name */ .chroma .n { }
-/* NameAttribute */ .chroma .na { color: #008080 }
-/* NameBuiltin */ .chroma .nb { color: #999999 }
-/* NameBuiltinPseudo */ .chroma .bp { }
-/* NameClass */ .chroma .nc { color: #445588; font-weight: bold }
-/* NameConstant */ .chroma .no { color: #008080 }
-/* NameDecorator */ .chroma .nd { }
-/* NameEntity */ .chroma .ni { color: #800080 }
-/* NameException */ .chroma .ne { color: #990000; font-weight: bold }
-/* NameFunction */ .chroma .nf { color: #990000; font-weight: bold }
-/* NameFunctionMagic */ .chroma .fm { }
-/* NameLabel */ .chroma .nl { }
-/* NameNamespace */ .chroma .nn { color: #555555 }
-/* NameOther */ .chroma .nx { }
-/* NameProperty */ .chroma .py { }
-/* NameTag */ .chroma .nt { color: #000080 }
-/* NameVariable */ .chroma .nv { color: #008080 }
-/* NameVariableClass */ .chroma .vc { }
-/* NameVariableGlobal */ .chroma .vg { }
-/* NameVariableInstance */ .chroma .vi { }
-/* NameVariableMagic */ .chroma .vm { }
-/* Literal */ .chroma .l { }
-/* LiteralDate */ .chroma .ld { }
-/* LiteralString */ .chroma .s { color: #bb8844 }
-/* LiteralStringAffix */ .chroma .sa { color: #bb8844 }
-/* LiteralStringBacktick */ .chroma .sb { color: #bb8844 }
-/* LiteralStringChar */ .chroma .sc { color: #bb8844 }
-/* LiteralStringDelimiter */ .chroma .dl { color: #bb8844 }
-/* LiteralStringDoc */ .chroma .sd { color: #bb8844 }
-/* LiteralStringDouble */ .chroma .s2 { color: #bb8844 }
-/* LiteralStringEscape */ .chroma .se { color: #bb8844 }
-/* LiteralStringHeredoc */ .chroma .sh { color: #bb8844 }
-/* LiteralStringInterpol */ .chroma .si { color: #bb8844 }
-/* LiteralStringOther */ .chroma .sx { color: #bb8844 }
-/* LiteralStringRegex */ .chroma .sr { color: #808000 }
-/* LiteralStringSingle */ .chroma .s1 { color: #bb8844 }
-/* LiteralStringSymbol */ .chroma .ss { color: #bb8844 }
-/* LiteralNumber */ .chroma .m { color: #009999 }
-/* LiteralNumberBin */ .chroma .mb { color: #009999 }
-/* LiteralNumberFloat */ .chroma .mf { color: #009999 }
-/* LiteralNumberHex */ .chroma .mh { color: #009999 }
-/* LiteralNumberInteger */ .chroma .mi { color: #009999 }
-/* LiteralNumberIntegerLong */ .chroma .il { color: #009999 }
-/* LiteralNumberOct */ .chroma .mo { color: #009999 }
-/* Operator */ .chroma .o { font-weight: bold }
-/* OperatorWord */ .chroma .ow { font-weight: bold }
-/* Punctuation */ .chroma .p { }
-/* Comment */ .chroma .c { color: #999988; font-style: italic }
-/* CommentHashbang */ .chroma .ch { color: #999988; font-style: italic }
-/* CommentMultiline */ .chroma .cm { color: #999988; font-style: italic }
-/* CommentSingle */ .chroma .c1 { color: #999988; font-style: italic }
-/* CommentSpecial */ .chroma .cs { color: #999999; font-weight: bold; font-style: italic }
-/* CommentPreproc */ .chroma .cp { color: #999999; font-weight: bold }
-/* CommentPreprocFile */ .chroma .cpf { color: #999999; font-weight: bold }
-/* Generic */ .chroma .g { }
-/* GenericDeleted */ .chroma .gd { color: #000000; background-color: #ffdddd }
-/* GenericEmph */ .chroma .ge { font-style: italic }
-/* GenericError */ .chroma .gr { color: #aa0000 }
-/* GenericHeading */ .chroma .gh { color: #999999 }
-/* GenericInserted */ .chroma .gi { color: #000000; background-color: #ddffdd }
-/* GenericOutput */ .chroma .go { color: #888888 }
-/* GenericPrompt */ .chroma .gp { color: #555555 }
-/* GenericStrong */ .chroma .gs { font-weight: bold }
-/* GenericSubheading */ .chroma .gu { color: #aaaaaa }
-/* GenericTraceback */ .chroma .gt { color: #aa0000 }
-/* GenericUnderline */ .chroma .gl { text-decoration: underline }
-/* TextWhitespace */ .chroma .w { color: #bbbbbb }
diff --git a/docs/assets/css/components/chroma_dark.css b/docs/assets/css/components/chroma_dark.css
deleted file mode 100644
index 0b0ae3000..000000000
--- a/docs/assets/css/components/chroma_dark.css
+++ /dev/null
@@ -1,85 +0,0 @@
-/* Background */.dark .bg { background-color: var(--color-dark); }
-/* PreWrapper */ .dark .chroma { background-color: var(--color-dark); }
-/* Other */ .dark .chroma .x { }
-/* Error */ .dark .chroma .err { color: #ef6155 }
-/* CodeLine */ .dark .chroma .cl { }
-/* LineTableTD */ .dark .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }
-/* LineTable */ .dark .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; }
-/* LineHighlight */ .dark .chroma .hl { background-color: rgb(0,19,28) }
-/* LineNumbersTable */ .dark .chroma .lnt { white-space: pre; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
-/* LineNumbers */ .dark .chroma .ln { white-space: pre; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
-/* Line */ .dark .chroma .line { display: flex; }
-/* Keyword */ .dark .chroma .k { color: #815ba4 }
-/* KeywordConstant */ .dark .chroma .kc { color: #815ba4 }
-/* KeywordDeclaration */ .dark .chroma .kd { color: #815ba4 }
-/* KeywordNamespace */ .dark .chroma .kn { color: #5bc4bf }
-/* KeywordPseudo */ .dark .chroma .kp { color: #815ba4 }
-/* KeywordReserved */ .dark .chroma .kr { color: #815ba4 }
-/* KeywordType */ .dark .chroma .kt { color: #fec418 }
-/* Name */ .dark .chroma .n { }
-/* NameAttribute */ .dark .chroma .na { color: #06b6ef }
-/* NameBuiltin */ .dark .chroma .nb { }
-/* NameBuiltinPseudo */ .dark .chroma .bp { }
-/* NameClass */ .dark .chroma .nc { color: #fec418 }
-/* NameConstant */ .dark .chroma .no { color: #ef6155 }
-/* NameDecorator */ .dark .chroma .nd { color: #5bc4bf }
-/* NameEntity */ .dark .chroma .ni { }
-/* NameException */ .dark .chroma .ne { color: #ef6155 }
-/* NameFunction */ .dark .chroma .nf { color: #06b6ef }
-/* NameFunctionMagic */ .dark .chroma .fm { }
-/* NameLabel */ .dark .chroma .nl { }
-/* NameNamespace */ .dark .chroma .nn { color: #fec418 }
-/* NameOther */ .dark .chroma .nx { color: #06b6ef }
-/* NameProperty */ .dark .chroma .py { }
-/* NameTag */ .dark .chroma .nt { color: #5bc4bf }
-/* NameVariable */ .dark .chroma .nv { color: #ef6155 }
-/* NameVariableClass */ .dark .chroma .vc { }
-/* NameVariableGlobal */ .dark .chroma .vg { }
-/* NameVariableInstance */ .dark .chroma .vi { }
-/* NameVariableMagic */ .dark .chroma .vm { }
-/* Literal */ .dark .chroma .l { color: #f99b15 }
-/* LiteralDate */ .dark .chroma .ld { color: #48b685 }
-/* LiteralString */ .dark .chroma .s { color: #48b685 }
-/* LiteralStringAffix */ .dark .chroma .sa { color: #48b685 }
-/* LiteralStringBacktick */ .dark .chroma .sb { color: #48b685 }
-/* LiteralStringChar */ .dark .chroma .sc { }
-/* LiteralStringDelimiter */ .dark .chroma .dl { color: #48b685 }
-/* LiteralStringDoc */ .dark .chroma .sd { color: #776e71 }
-/* LiteralStringDouble */ .dark .chroma .s2 { color: #48b685 }
-/* LiteralStringEscape */ .dark .chroma .se { color: #f99b15 }
-/* LiteralStringHeredoc */ .dark .chroma .sh { color: #48b685 }
-/* LiteralStringInterpol */ .dark .chroma .si { color: #f99b15 }
-/* LiteralStringOther */ .dark .chroma .sx { color: #48b685 }
-/* LiteralStringRegex */ .dark .chroma .sr { color: #48b685 }
-/* LiteralStringSingle */ .dark .chroma .s1 { color: #48b685 }
-/* LiteralStringSymbol */ .dark .chroma .ss { color: #48b685 }
-/* LiteralNumber */ .dark .chroma .m { color: #f99b15 }
-/* LiteralNumberBin */ .dark .chroma .mb { color: #f99b15 }
-/* LiteralNumberFloat */ .dark .chroma .mf { color: #f99b15 }
-/* LiteralNumberHex */ .dark .chroma .mh { color: #f99b15 }
-/* LiteralNumberInteger */ .dark .chroma .mi { color: #f99b15 }
-/* LiteralNumberIntegerLong */ .dark .chroma .il { color: #f99b15 }
-/* LiteralNumberOct */ .dark .chroma .mo { color: #f99b15 }
-/* Operator */ .dark .chroma .o { color: #5bc4bf }
-/* OperatorWord */ .dark .chroma .ow { color: #5bc4bf }
-/* Punctuation */ .dark .chroma .p { }
-/* Comment */ .dark .chroma .c { color: #776e71 }
-/* CommentHashbang */ .dark .chroma .ch { color: #776e71 }
-/* CommentMultiline */ .dark .chroma .cm { color: #776e71 }
-/* CommentSingle */ .dark .chroma .c1 { color: #776e71 }
-/* CommentSpecial */ .dark .chroma .cs { color: #776e71 }
-/* CommentPreproc */ .dark .chroma .cp { color: #776e71 }
-/* CommentPreprocFile */ .dark .chroma .cpf { color: #776e71 }
-/* Generic */ .dark .chroma .g { }
-/* GenericDeleted */ .dark .chroma .gd { color: #ef6155 }
-/* GenericEmph */ .dark .chroma .ge { font-style: italic }
-/* GenericError */ .dark .chroma .gr { }
-/* GenericHeading */ .dark .chroma .gh { font-weight: bold }
-/* GenericInserted */ .dark .chroma .gi { color: #48b685 }
-/* GenericOutput */ .dark .chroma .go { }
-/* GenericPrompt */ .dark .chroma .gp { color: #776e71; font-weight: bold }
-/* GenericStrong */ .dark .chroma .gs { font-weight: bold }
-/* GenericSubheading */ .dark .chroma .gu { color: #5bc4bf; font-weight: bold }
-/* GenericTraceback */ .dark .chroma .gt { }
-/* GenericUnderline */ .dark .chroma .gl { }
-/* TextWhitespace */ .dark .chroma .w { }
diff --git a/docs/assets/css/components/content.css b/docs/assets/css/components/content.css
deleted file mode 100644
index e9064f439..000000000
--- a/docs/assets/css/components/content.css
+++ /dev/null
@@ -1,49 +0,0 @@
-@import "./chroma_dark.css";
-@import "./chroma.css";
-@import "./highlight.css";
-
-/* Some contrast ratio fixes as reported by Google Page Speed. */
-.chroma .c1 {
- @apply text-gray-500;
-}
-
-.dark .chroma .c1 {
- @apply text-gray-400;
-}
-
-.highlight code {
- @apply text-sm/6;
-}
-
-.content {
- @apply prose prose-sm sm:prose-base prose-stone max-w-none dark:prose-invert dark:text-slate-200;
- /* headings */
- @apply prose-headings:font-semibold;
- /* lead */
- @apply prose-lead:text-slate-500 prose-lead:text-xl prose-lead:mt-2 sm:prose-lead:mt-4 prose-lead:leading-relaxed dark:prose-lead:text-slate-400;
- /* links */
- @apply prose-a:text-primary dark:prose-a:text-blue-500 prose-a:hover:text-blue-500 dark:prose-a:hover:text-blue-400 prose-a:underline;
- @apply prose-a:prose-code:underline prose-a:prose-code:hover:text-blue-500 prose-a:prose-code:hover:underline;
- /* pre */
- @apply prose-pre:text-gray-800 prose-pre:border-1 prose-pre:border-gray-100 prose-pre:bg-light dark:prose-pre:bg-dark dark:prose-pre:ring-1 dark:prose-pre:ring-slate-300/10;
- /* code */
- @apply prose-code:px-0.5 prose-code:text-gray-500 prose-code:dark:text-gray-300 border-none;
- @apply prose-code:before:hidden prose-code:after:hidden prose-code:font-mono;
- @apply prose-table:prose-th:prose-code:text-white;
- /* tables */
- @apply prose-table:w-auto prose-table:border-2 prose-table:border-gray-100 prose-table:dark:border-gray-800 prose-table:prose-th:font-bold prose-table:prose-th:bg-blue-500 dark:prose-table:prose-th:bg-blue-500/50 prose-table:prose-th:p-2 prose-table:prose-td:p-2 prose-table:prose-th:text-white;
- /* hr */
- @apply dark:prose-hr:border-slate-800;
- /* ol */
- @apply prose-ol:marker:prose dark:prose-ol:marker:text-gray-300;
- /* ul */
- @apply prose-ul:marker:text-gray-500 dark:prose-ul:marker:text-gray-300;
-}
-
-/* This will not match highlighting inside e.g. the code-toggle shortcode. */
-/* For more fine grained control of this, see components/shortcodes.css. */
-.content > .highlight,
-.content dd > .highlight,
-.content li > .highlight {
- @apply border-1 border-gray-200 dark:border-slate-600 mt-6 mb-8;
-}
diff --git a/docs/assets/css/components/fonts.css b/docs/assets/css/components/fonts.css
deleted file mode 100644
index 06f40b4bf..000000000
--- a/docs/assets/css/components/fonts.css
+++ /dev/null
@@ -1,15 +0,0 @@
-@font-face {
- font-family: "Mulish";
- font-style: normal;
- src: url("../fonts/Mulish-VariableFont_wght.ttf") format("truetype");
- font-weight: 1 999;
- font-display: swap;
-}
-
-@font-face {
- font-family: "Mulish";
- font-style: italic;
- src: url("../fonts/Mulish-Italic-VariableFont_wght.ttf") format("truetype");
- font-weight: 1 999;
- font-display: swap;
-}
diff --git a/docs/assets/css/components/helpers.css b/docs/assets/css/components/helpers.css
deleted file mode 100644
index 8eb6930b8..000000000
--- a/docs/assets/css/components/helpers.css
+++ /dev/null
@@ -1,19 +0,0 @@
-/* Helper class to limit a text block to two lines. */
-.two-lines-ellipsis {
- display: block;
- display: -webkit-box;
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-/* Helper class to limit a text block to three lines. */
-.three-lines-ellipsis {
- display: block;
- display: -webkit-box;
- -webkit-line-clamp: 3;
- -webkit-box-orient: vertical;
- overflow: hidden;
- text-overflow: ellipsis;
-}
diff --git a/docs/assets/css/components/highlight.css b/docs/assets/css/components/highlight.css
deleted file mode 100644
index 5f25fe368..000000000
--- a/docs/assets/css/components/highlight.css
+++ /dev/null
@@ -1,11 +0,0 @@
-.highlight {
- @apply bg-light dark:bg-dark rounded-none;
-}
-
-.highlight pre {
- @apply m-0 p-3 w-full h-full overflow-x-auto dark:border-black rounded-none;
-}
-
-.highlight pre code {
- @apply m-0 p-0 w-full h-full;
-}
diff --git a/docs/assets/css/components/shortcodes.css b/docs/assets/css/components/shortcodes.css
deleted file mode 100644
index 7314d5b20..000000000
--- a/docs/assets/css/components/shortcodes.css
+++ /dev/null
@@ -1,4 +0,0 @@
-.shortcode-code {
- .highlight {
- }
-}
diff --git a/docs/assets/css/components/tableofcontents.css b/docs/assets/css/components/tableofcontents.css
deleted file mode 100644
index 3640adf6d..000000000
--- a/docs/assets/css/components/tableofcontents.css
+++ /dev/null
@@ -1,14 +0,0 @@
-.tableofcontents {
- ul {
- @apply list-none;
- li {
- @apply mb-2;
- a {
- @apply text-primary;
- &:hover {
- @apply text-primary/60;
- }
- }
- }
- }
-}
diff --git a/docs/assets/css/components/view-transitions.css b/docs/assets/css/components/view-transitions.css
deleted file mode 100644
index cf68ed3d7..000000000
--- a/docs/assets/css/components/view-transitions.css
+++ /dev/null
@@ -1,20 +0,0 @@
-/* Global slight fade */
-::view-transition-old(root),
-::view-transition-new(root) {
- animation-duration: 200ms;
-}
-
-::view-transition-old(qr),
-::view-transition-new(qr) {
- animation-duration: 800ms;
- animation-delay: 250ms;
-}
-
-.view-transition-qr {
- view-transition-name: qr;
-}
-
-/* Turbo styles */
-.turbo-progress-bar {
- visibility: hidden;
-}
diff --git a/docs/assets/css/styles.css b/docs/assets/css/styles.css
deleted file mode 100644
index 6665a7e2b..000000000
--- a/docs/assets/css/styles.css
+++ /dev/null
@@ -1,131 +0,0 @@
-@import "tailwindcss";
-@plugin "@tailwindcss/typography";
-@variant dark (&:where(.dark, .dark *));
-
-@import "components/all.css";
-
-/* TailwindCSS ignores files in .gitignore, so make it explicit. */
-@source "hugo_stats.json";
-
-@theme {
- /* Breakpoints. */
- --breakpoint-sm: 40rem;
- --breakpoint-md: 48rem;
- --breakpoint-lg: 68rem; /* Default 64rem; */
- --breakpoint-xl: 80rem;
- --breakpoint-2xl: 96rem;
-
- /* Colors. */
- --color-primary: var(--color-blue-600);
- --color-dark: #000;
- --color-light: var(--color-gray-50);
- --color-accent: var(--color-orange-500);
- --color-accent-light: var(--color-pink-500);
- --color-accent-dark: var(--color-green-500);
-
- /* https://www.tints.dev/blue/0594CB */
- --color-blue-50: #e1f6fe;
- --color-blue-100: #c3edfe;
- --color-blue-200: #88dbfc;
- --color-blue-300: #4cc9fb;
- --color-blue-400: #15b9f9;
- --color-blue-500: #0594cb;
- --color-blue-600: #0477a4;
- --color-blue-700: #035677;
- --color-blue-800: #023a50;
- --color-blue-900: #011d28;
- --color-blue-950: #000e14;
-
- /* https://www.tints.dev/orange/EBB951 */
- --color-orange-50: #fdf8ed;
- --color-orange-100: #fbf1da;
- --color-orange-200: #f7e4ba;
- --color-orange-300: #f3d596;
- --color-orange-400: #efc976;
- --color-orange-500: #ebb951;
- --color-orange-600: #e5a51a;
- --color-orange-700: #a97a13;
- --color-orange-800: #72520d;
- --color-orange-900: #372806;
- --color-orange-950: #1b1403;
-
- /* https://www.tints.dev/pink/FF4088 */
- --color-pink-50: #ffebf2;
- --color-pink-100: #ffdbe9;
- --color-pink-200: #ffb3d0;
- --color-pink-300: #ff8fba;
- --color-pink-400: #ff66a1;
- --color-pink-500: #ff4088;
- --color-pink-600: #ff0062;
- --color-pink-700: #c2004a;
- --color-pink-800: #800031;
- --color-pink-900: #420019;
- --color-pink-950: #1f000c;
-
- /* https://www.tints.dev/green/33BA91 */
- --color-green-50: #ebfaf5;
- --color-green-100: #d3f3e9;
- --color-green-200: #abe8d6;
- --color-green-300: #7fdcc0;
- --color-green-400: #53d0aa;
- --color-green-500: #33ba91;
- --color-green-600: #299474;
- --color-green-700: #1f7058;
- --color-green-800: #154c3b;
- --color-green-900: #0a241c;
- --color-green-950: #051410;
-
- /* Fonts. */
- --font-sans:
- "Mulish", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
- "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
-}
-
-html {
- scroll-padding-top: 100px;
-}
-
-body {
- @apply antialiased font-sans text-black dark:text-gray-100;
-}
-
-.p-safe-area-x {
- padding-left: env(safe-area-inset-left);
- padding-right: env(safe-area-inset-right);
-}
-
-.p-safe-area-y {
- padding-top: env(safe-area-inset-top);
- padding-bottom: env(safe-area-inset-bottom);
-}
-
-.px-main {
- padding-left: max(env(safe-area-inset-left), 1rem);
- padding-right: max(env(safe-area-inset-right), 1rem);
-}
-
-@media screen(md) {
- .px-main {
- padding-left: max(env(safe-area-inset-left), 2rem);
- padding-right: max(env(safe-area-inset-right), 2rem);
- }
-}
-
-@media screen(lg) {
- .px-main {
- padding-left: max(env(safe-area-inset-left), 3rem);
- padding-right: max(env(safe-area-inset-right), 3rem);
- }
-}
-
-/* Algolia DocSearch */
-.algolia-docsearch-suggestion--highlight {
- color: var(--color-primary);
-}
-
-/* Footnotes */
-.footnote-backref,
-.footnote-ref {
- text-decoration: none;
- padding-left: .0625em;
-}
diff --git a/docs/assets/images/examples/landscape-exif-orientation-5.jpg b/docs/assets/images/examples/landscape-exif-orientation-5.jpg
deleted file mode 100644
index ad64835eb..000000000
Binary files a/docs/assets/images/examples/landscape-exif-orientation-5.jpg and /dev/null differ
diff --git a/docs/assets/images/examples/mask.png b/docs/assets/images/examples/mask.png
deleted file mode 100644
index c3005a669..000000000
Binary files a/docs/assets/images/examples/mask.png and /dev/null differ
diff --git a/docs/assets/images/examples/zion-national-park.jpg b/docs/assets/images/examples/zion-national-park.jpg
deleted file mode 100644
index 7980abccb..000000000
Binary files a/docs/assets/images/examples/zion-national-park.jpg and /dev/null differ
diff --git a/docs/assets/images/hugo-github-screenshot.png b/docs/assets/images/hugo-github-screenshot.png
deleted file mode 100644
index 275b6969d..000000000
Binary files a/docs/assets/images/hugo-github-screenshot.png and /dev/null differ
diff --git a/docs/assets/images/logos/logo-128x128.png b/docs/assets/images/logos/logo-128x128.png
deleted file mode 100644
index ec1a2d6e1..000000000
Binary files a/docs/assets/images/logos/logo-128x128.png and /dev/null differ
diff --git a/docs/assets/images/logos/logo-256x256.png b/docs/assets/images/logos/logo-256x256.png
deleted file mode 100644
index d9fdb888a..000000000
Binary files a/docs/assets/images/logos/logo-256x256.png and /dev/null differ
diff --git a/docs/assets/images/logos/logo-512x512.png b/docs/assets/images/logos/logo-512x512.png
deleted file mode 100644
index 76d463600..000000000
Binary files a/docs/assets/images/logos/logo-512x512.png and /dev/null differ
diff --git a/docs/assets/images/logos/logo-64x64.png b/docs/assets/images/logos/logo-64x64.png
deleted file mode 100644
index 9857bcea1..000000000
Binary files a/docs/assets/images/logos/logo-64x64.png and /dev/null differ
diff --git a/docs/assets/images/logos/logo-96x96.png b/docs/assets/images/logos/logo-96x96.png
deleted file mode 100644
index 48d0cb98e..000000000
Binary files a/docs/assets/images/logos/logo-96x96.png and /dev/null differ
diff --git a/docs/assets/images/sponsors/Route4MeLogoBlueOnWhite.svg b/docs/assets/images/sponsors/Route4MeLogoBlueOnWhite.svg
deleted file mode 100644
index d4334e8d8..000000000
--- a/docs/assets/images/sponsors/Route4MeLogoBlueOnWhite.svg
+++ /dev/null
@@ -1,15 +0,0 @@
-
diff --git a/docs/assets/images/sponsors/bep-consulting.svg b/docs/assets/images/sponsors/bep-consulting.svg
deleted file mode 100644
index 598a1eb71..000000000
--- a/docs/assets/images/sponsors/bep-consulting.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/docs/assets/images/sponsors/butter-dark.svg b/docs/assets/images/sponsors/butter-dark.svg
deleted file mode 100644
index 657b75c50..000000000
--- a/docs/assets/images/sponsors/butter-dark.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/docs/assets/images/sponsors/butter-light.svg b/docs/assets/images/sponsors/butter-light.svg
deleted file mode 100644
index a0697df08..000000000
--- a/docs/assets/images/sponsors/butter-light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/docs/assets/images/sponsors/cloudcannon-blue.svg b/docs/assets/images/sponsors/cloudcannon-blue.svg
deleted file mode 100644
index 79b13f431..000000000
--- a/docs/assets/images/sponsors/cloudcannon-blue.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
diff --git a/docs/assets/images/sponsors/cloudcannon-white.svg b/docs/assets/images/sponsors/cloudcannon-white.svg
deleted file mode 100644
index 83e319a6d..000000000
--- a/docs/assets/images/sponsors/cloudcannon-white.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
diff --git a/docs/assets/images/sponsors/esolia-logo.svg b/docs/assets/images/sponsors/esolia-logo.svg
deleted file mode 100644
index 3f5344c61..000000000
--- a/docs/assets/images/sponsors/esolia-logo.svg
+++ /dev/null
@@ -1,67 +0,0 @@
-
-
-
diff --git a/docs/assets/images/sponsors/goland.svg b/docs/assets/images/sponsors/goland.svg
deleted file mode 100644
index c32f25d7f..000000000
--- a/docs/assets/images/sponsors/goland.svg
+++ /dev/null
@@ -1,20 +0,0 @@
-
diff --git a/docs/assets/images/sponsors/graitykit-dark.svg b/docs/assets/images/sponsors/graitykit-dark.svg
deleted file mode 100644
index fd7d12f5c..000000000
--- a/docs/assets/images/sponsors/graitykit-dark.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/docs/assets/images/sponsors/linode-logo.svg b/docs/assets/images/sponsors/linode-logo.svg
deleted file mode 100644
index 873678398..000000000
--- a/docs/assets/images/sponsors/linode-logo.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/docs/assets/images/sponsors/linode-logo_standard_light_medium.png b/docs/assets/images/sponsors/linode-logo_standard_light_medium.png
deleted file mode 100644
index 269e6af84..000000000
Binary files a/docs/assets/images/sponsors/linode-logo_standard_light_medium.png and /dev/null differ
diff --git a/docs/assets/images/sponsors/your-company-dark.svg b/docs/assets/images/sponsors/your-company-dark.svg
deleted file mode 100644
index 58fd601f5..000000000
--- a/docs/assets/images/sponsors/your-company-dark.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/docs/assets/images/sponsors/your-company.svg b/docs/assets/images/sponsors/your-company.svg
deleted file mode 100644
index 3b85ece5c..000000000
--- a/docs/assets/images/sponsors/your-company.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/docs/assets/js/alpinejs/data/explorer.js b/docs/assets/js/alpinejs/data/explorer.js
deleted file mode 100644
index 783db58f4..000000000
--- a/docs/assets/js/alpinejs/data/explorer.js
+++ /dev/null
@@ -1,123 +0,0 @@
-var debug = 0 ? console.log.bind(console, '[explorer]') : function () {};
-
-// This is currently not used, but kept in case I change my mind.
-export const explorer = (Alpine) => ({
- uiState: {
- containerScrollTop: -1,
- lastActiveRef: '',
- },
- treeState: {
- // The href of the current page.
- currentNode: '',
- // The state of each node in the tree.
- nodes: {},
-
- // We currently only list the sections, not regular pages, in the side bar.
- // This strikes me as the right balance. The pages gets listed on the section pages.
- // This array is sorted by length, so we can find the longest prefix of the current page
- // without having to iterate over all the keys.
- nodeRefsByLength: [],
- },
- async init() {
- let keys = Reflect.ownKeys(this.$refs);
- for (let key of keys) {
- let n = {
- open: false,
- active: false,
- };
- this.treeState.nodes[key] = n;
- this.treeState.nodeRefsByLength.push(key);
- }
-
- this.treeState.nodeRefsByLength.sort((a, b) => b.length - a.length);
-
- this.setCurrentActive();
- },
-
- longestPrefix(ref) {
- let longestPrefix = '';
- for (let key of this.treeState.nodeRefsByLength) {
- if (ref.startsWith(key)) {
- longestPrefix = key;
- break;
- }
- }
- return longestPrefix;
- },
-
- setCurrentActive() {
- let ref = this.longestPrefix(window.location.pathname);
- let activeChanged = this.uiState.lastActiveRef !== ref;
- debug('setCurrentActive', this.uiState.lastActiveRef, window.location.pathname, '=>', ref, activeChanged);
- this.uiState.lastActiveRef = ref;
- if (this.uiState.containerScrollTop === -1 && activeChanged) {
- // Navigation outside of the explorer menu.
- let el = document.querySelector(`[x-ref="${ref}"]`);
- if (el) {
- this.$nextTick(() => {
- debug('scrolling to', ref);
- el.scrollIntoView({ behavior: 'smooth', block: 'center' });
- });
- }
- }
- this.treeState.currentNode = ref;
- for (let key in this.treeState.nodes) {
- let n = this.treeState.nodes[key];
- n.active = false;
- n.open = ref == key || ref.startsWith(key);
- if (n.open) {
- debug('open', key);
- }
- }
-
- let n = this.treeState.nodes[this.longestPrefix(ref)];
- if (n) {
- n.active = true;
- }
- },
-
- getScrollingContainer() {
- return document.getElementById('leftsidebar');
- },
-
- onLoad() {
- debug('onLoad', this.uiState.containerScrollTop);
- if (this.uiState.containerScrollTop >= 0) {
- debug('onLoad: scrolling to', this.uiState.containerScrollTop);
- this.getScrollingContainer().scrollTo(0, this.uiState.containerScrollTop);
- }
- this.uiState.containerScrollTop = -1;
- },
-
- onBeforeRender() {
- debug('onBeforeRender', this.uiState.containerScrollTop);
- this.setCurrentActive();
- },
-
- toggleNode(ref) {
- this.uiState.containerScrollTop = this.getScrollingContainer().scrollTop;
- this.uiState.lastActiveRef = '';
- debug('toggleNode', ref, this.uiState.containerScrollTop);
-
- let node = this.treeState.nodes[ref];
- if (!node) {
- debug('node not found', ref);
- return;
- }
- let wasOpen = node.open;
- },
-
- isCurrent(ref) {
- let n = this.treeState.nodes[ref];
- return n && n.active;
- },
-
- isOpen(ref) {
- let node = this.treeState.nodes[ref];
- if (!node) return false;
- if (node.open) {
- debug('isOpen', ref);
- }
- return node.open;
- },
-});
diff --git a/docs/assets/js/alpinejs/data/index.js b/docs/assets/js/alpinejs/data/index.js
deleted file mode 100644
index 7bf0532e3..000000000
--- a/docs/assets/js/alpinejs/data/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export * from './navbar';
-export * from './search';
-export * from './toc';
diff --git a/docs/assets/js/alpinejs/data/navbar.js b/docs/assets/js/alpinejs/data/navbar.js
deleted file mode 100644
index 1075f3f29..000000000
--- a/docs/assets/js/alpinejs/data/navbar.js
+++ /dev/null
@@ -1,28 +0,0 @@
-export const navbar = (Alpine) => ({
- init: function () {
- Alpine.bind(this.$root, this.root);
-
- return this.$nextTick(() => {
- let contentEl = document.querySelector('.content:not(.content--ready)');
- if (contentEl) {
- contentEl.classList.add('content--ready');
- let anchorTemplate = document.getElementById('anchor-heading');
- if (anchorTemplate) {
- let els = contentEl.querySelectorAll('h2[id], h3[id], h4[id], h5[id], h6[id], dt[id]');
- for (let i = 0; i < els.length; i++) {
- let el = els[i];
- el.classList.add('group');
- let a = anchorTemplate.content.cloneNode(true).firstElementChild;
- a.href = '#' + el.id;
- el.appendChild(a);
- }
- }
- }
- });
- },
- root: {
- ['@scroll.window.debounce.10ms'](event) {
- this.$store.nav.scroll.atTop = window.scrollY < 40 ? true : false;
- },
- },
-});
diff --git a/docs/assets/js/alpinejs/data/search.js b/docs/assets/js/alpinejs/data/search.js
deleted file mode 100644
index c633799a1..000000000
--- a/docs/assets/js/alpinejs/data/search.js
+++ /dev/null
@@ -1,119 +0,0 @@
-import { LRUCache } from '../../helpers';
-
-const designMode = false;
-
-const groupByLvl0 = (array) => {
- if (!array) return [];
- return array.reduce((result, currentValue) => {
- (result[currentValue.hierarchy.lvl0] = result[currentValue.hierarchy.lvl0] || []).push(currentValue);
- return result;
- }, {});
-};
-
-const applyHelperFuncs = (array) => {
- if (!array) return [];
- return array.map((item) => {
- item.getHeadingHTML = function () {
- let lvl2 = this._highlightResult.hierarchy.lvl2;
- let lvl3 = this._highlightResult.hierarchy.lvl3;
-
- if (!lvl3) {
- if (lvl2) {
- return lvl2.value;
- }
- return '';
- }
-
- if (!lvl2) {
- return lvl3.value;
- }
-
- return `${lvl2.value} > ${lvl3.value}`;
- };
- return item;
- });
-};
-
-export const search = (Alpine, cfg) => ({
- query: designMode ? 'apac' : '',
- open: designMode,
- result: {},
- cache: new LRUCache(10), // Small cache, avoids network requests on e.g. backspace.
- init() {
- Alpine.bind(this.$root, this.root);
-
- this.checkOpen();
- return this.$nextTick(() => {
- this.$watch('query', () => {
- this.search();
- });
- });
- },
- toggleOpen: function () {
- this.open = !this.open;
- this.checkOpen();
- },
- checkOpen: function () {
- if (!this.open) {
- return;
- }
- this.search();
- this.$nextTick(() => {
- this.$refs.input.focus();
- });
- },
-
- search: function () {
- if (!this.query) {
- this.result = {};
- return;
- }
-
- // Check cache first.
- const cached = this.cache.get(this.query);
- if (cached) {
- this.result = cached;
- return;
- }
- var queries = {
- requests: [
- {
- indexName: cfg.index,
- params: `query=${encodeURIComponent(this.query)}`,
- attributesToHighlight: ['hierarchy', 'content'],
- attributesToRetrieve: ['hierarchy', 'url', 'content'],
- },
- ],
- };
-
- const host = `https://${cfg.app_id}-dsn.algolia.net`;
- const url = `${host}/1/indexes/*/queries`;
-
- fetch(url, {
- method: 'POST',
- headers: {
- 'X-Algolia-Application-Id': cfg.app_id,
- 'X-Algolia-API-Key': cfg.api_key,
- },
- body: JSON.stringify(queries),
- })
- .then((response) => response.json())
- .then((data) => {
- this.result = groupByLvl0(applyHelperFuncs(data.results[0].hits));
- this.cache.put(this.query, this.result);
- });
- },
- root: {
- ['@click']() {
- if (!this.open) {
- this.toggleOpen();
- }
- },
- ['@search-toggle.window']() {
- this.toggleOpen();
- },
- ['@keydown.slash.window.prevent']() {
- this.toggleOpen();
- },
- },
-});
diff --git a/docs/assets/js/alpinejs/data/toc.js b/docs/assets/js/alpinejs/data/toc.js
deleted file mode 100644
index 233f8777f..000000000
--- a/docs/assets/js/alpinejs/data/toc.js
+++ /dev/null
@@ -1,71 +0,0 @@
-var debug = 0 ? console.log.bind(console, '[toc]') : function () {};
-
-export const toc = (Alpine) => ({
- contentScrollSpy: null,
- activeHeading: '',
- justClicked: false,
-
- setActive(id) {
- debug('setActive', id);
- this.activeHeading = id;
- // Prevent the intersection observer from changing the active heading right away.
- this.justClicked = true;
- setTimeout(() => {
- this.justClicked = false;
- }, 200);
- },
-
- init() {
- this.$watch('$store.nav.scroll.atTop', (value) => {
- if (!value) return;
- this.activeHeading = '';
- this.$root.scrollTop = 0;
- });
-
- return this.$nextTick(() => {
- let contentEl = document.getElementById('article');
- if (contentEl) {
- const handleIntersect = (entries) => {
- if (this.justClicked) {
- return;
- }
- for (let entry of entries) {
- if (entry.isIntersecting) {
- let id = entry.target.id;
- this.activeHeading = id;
- let liEl = this.$refs[id];
- if (liEl) {
- // If liEl is not in the viewport, scroll it into view.
- let bounding = liEl.getBoundingClientRect();
- if (bounding.top < 0 || bounding.bottom > window.innerHeight) {
- this.$root.scrollTop = liEl.offsetTop - 100;
- }
- }
- debug('intersecting', id);
- break;
- }
- }
- };
-
- let opts = {
- rootMargin: '0px 0px -75%',
- threshold: 0.75,
- };
-
- this.contentScrollSpy = new IntersectionObserver(handleIntersect, opts);
- // Observe all headings.
- let headings = contentEl.querySelectorAll('h2, h3, h4, h5, h6');
- for (let heading of headings) {
- this.contentScrollSpy.observe(heading);
- }
- }
- });
- },
-
- destroy() {
- if (this.contentScrollSpy) {
- debug('disconnecting');
- this.contentScrollSpy.disconnect();
- }
- },
-});
diff --git a/docs/assets/js/alpinejs/magics/helpers.js b/docs/assets/js/alpinejs/magics/helpers.js
deleted file mode 100644
index de9fa24e9..000000000
--- a/docs/assets/js/alpinejs/magics/helpers.js
+++ /dev/null
@@ -1,36 +0,0 @@
-'use strict';
-
-export function registerMagics(Alpine) {
- Alpine.magic('copy', (currentEl) => {
- return function (el) {
- if (!el) {
- el = currentEl;
- }
-
- // Select the element to copy.
- let range = document.createRange();
- range.selectNode(el);
- window.getSelection().removeAllRanges();
- window.getSelection().addRange(range);
-
- // Remove the selection after some time.
- setTimeout(() => {
- window.getSelection().removeAllRanges();
- }, 500);
-
- // Trim whitespace.
- let text = el.textContent.trim();
-
- navigator.clipboard.writeText(text);
- };
- });
-
- Alpine.magic('isScrollX', (currentEl) => {
- return function (el) {
- if (!el) {
- el = currentEl;
- }
- return el.clientWidth < el.scrollWidth;
- };
- });
-}
diff --git a/docs/assets/js/alpinejs/magics/index.js b/docs/assets/js/alpinejs/magics/index.js
deleted file mode 100644
index c5f595cf9..000000000
--- a/docs/assets/js/alpinejs/magics/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export * from './helpers';
diff --git a/docs/assets/js/alpinejs/stores/index.js b/docs/assets/js/alpinejs/stores/index.js
deleted file mode 100644
index 17e2a347b..000000000
--- a/docs/assets/js/alpinejs/stores/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export * from './nav.js';
diff --git a/docs/assets/js/alpinejs/stores/nav.js b/docs/assets/js/alpinejs/stores/nav.js
deleted file mode 100644
index 6409cd86c..000000000
--- a/docs/assets/js/alpinejs/stores/nav.js
+++ /dev/null
@@ -1,94 +0,0 @@
-var debug = 1 ? console.log.bind(console, '[navStore]') : function () {};
-
-var ColorScheme = {
- System: 1,
- Light: 2,
- Dark: 3,
-};
-
-const localStorageUserSettingsKey = 'hugoDocsUserSettings';
-
-export const navStore = (Alpine) => ({
- init() {
- // There is no $watch available in Alpine stores,
- // but this has the same effect.
- this.userSettings.onColorSchemeChanged = Alpine.effect(() => {
- if (this.userSettings.settings.colorScheme) {
- this.userSettings.isDark = isDark(this.userSettings.settings.colorScheme);
- toggleDarkMode(this.userSettings.isDark);
- }
- });
-
- // Also react to changes in system settings.
- window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
- this.userSettings.setColorScheme(ColorScheme.System);
- });
- },
-
- destroy() {},
-
- scroll: {
- atTop: true,
- },
-
- userSettings: {
- // settings gets persisted between page navigations.
- settings: Alpine.$persist({
- // light, dark or system mode.
- // If not set, we use the OS setting.
- colorScheme: ColorScheme.System,
- // Used to show the most relevant tab in config listings etc.
- configFileType: 'toml',
- }).as(localStorageUserSettingsKey),
-
- isDark: false,
-
- setColorScheme(colorScheme) {
- this.settings.colorScheme = colorScheme;
- this.isDark = isDark(colorScheme);
- },
-
- toggleColorScheme() {
- let next = this.settings.colorScheme + 1;
- if (next > ColorScheme.Dark) {
- next = ColorScheme.System;
- }
- this.setColorScheme(next);
- },
- colorScheme() {
- return this.settings.colorScheme ? this.settings.colorScheme : ColorScheme.System;
- },
- },
-});
-
-function isMediaDark() {
- return window.matchMedia('(prefers-color-scheme: dark)').matches;
-}
-
-function isDark(colorScheme) {
- if (!colorScheme || colorScheme == ColorScheme.System) {
- return isMediaDark();
- }
-
- return colorScheme == ColorScheme.Dark;
-}
-
-export function initColorScheme() {
- // The AlpineJS store has not have been initialized yet, so access the
- // localStorage directly.
- let settingsJSON = localStorage[localStorageUserSettingsKey];
- if (settingsJSON) {
- let settings = JSON.parse(settingsJSON);
- toggleDarkMode(isDark(settings.colorScheme));
- return;
- }
- toggleDarkMode(isDark(null));
-}
-
-const toggleDarkMode = function (dark) {
- if (dark) {
- document.documentElement.classList.add('dark');
- } else {
- document.documentElement.classList.remove('dark');
- }
-};
diff --git a/docs/assets/js/body-start.js b/docs/assets/js/body-start.js
deleted file mode 100644
index f9b596671..000000000
--- a/docs/assets/js/body-start.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import { initColorScheme } from './alpinejs/stores/index';
-
-(function () {
- // This allows us to initialize the color scheme before AlpineJS etc. is loaded.
- initColorScheme();
-})();
diff --git a/docs/assets/js/head-early.js b/docs/assets/js/head-early.js
deleted file mode 100644
index 250bdd6cb..000000000
--- a/docs/assets/js/head-early.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import { scrollToActive } from 'js/helpers/index';
-
-(function () {
- // Now we know that the browser has JS enabled.
- document.documentElement.classList.remove('no-js');
-
- // Add os-macos class to body if user is using macOS.
- if (navigator.userAgent.indexOf('Mac') > -1) {
- document.documentElement.classList.add('os-macos');
- }
-
- // Wait for the DOM to be ready.
- document.addEventListener('DOMContentLoaded', function () {
- scrollToActive('DOMContentLoaded');
- });
-})();
diff --git a/docs/assets/js/helpers/bridgeTurboAndAlpine.js b/docs/assets/js/helpers/bridgeTurboAndAlpine.js
deleted file mode 100644
index 0494d02f2..000000000
--- a/docs/assets/js/helpers/bridgeTurboAndAlpine.js
+++ /dev/null
@@ -1,67 +0,0 @@
-export function bridgeTurboAndAlpine(Alpine) {
- document.addEventListener('turbo:before-render', (event) => {
- event.detail.newBody.querySelectorAll('[data-alpine-generated]').forEach((el) => {
- if (el.hasAttribute('data-alpine-generated')) {
- el.removeAttribute('data-alpine-generated');
- el.remove();
- }
- });
- });
-
- document.addEventListener('turbo:render', () => {
- if (document.documentElement.hasAttribute('data-turbo-preview')) {
- return;
- }
-
- document.querySelectorAll('[data-alpine-ignored]').forEach((el) => {
- el.removeAttribute('x-ignore');
- el.removeAttribute('data-alpine-ignored');
- });
-
- document.body.querySelectorAll('[x-data]').forEach((el) => {
- if (el.hasAttribute('data-turbo-permanent')) {
- return;
- }
- Alpine.initTree(el);
- });
-
- Alpine.startObservingMutations();
- });
-
- // Cleanup Alpine state on navigation.
- document.addEventListener('turbo:before-cache', () => {
- // This will be restarted in turbo:render.
- Alpine.stopObservingMutations();
-
- document.body.querySelectorAll('[data-turbo-permanent]').forEach((el) => {
- if (!el.hasAttribute('x-ignore')) {
- el.setAttribute('x-ignore', true);
- el.setAttribute('data-alpine-ignored', true);
- }
- });
-
- document.body.querySelectorAll('[x-for],[x-if],[x-teleport]').forEach((el) => {
- if (el.hasAttribute('x-for') && el._x_lookup) {
- Object.values(el._x_lookup).forEach((el) => el.setAttribute('data-alpine-generated', true));
- }
-
- if (el.hasAttribute('x-if') && el._x_currentIfEl) {
- el._x_currentIfEl.setAttribute('data-alpine-generated', true);
- }
-
- if (el.hasAttribute('x-teleport') && el._x_teleport) {
- el._x_teleport.setAttribute('data-alpine-generated', true);
- }
- });
-
- document.body.querySelectorAll('[x-data]').forEach((el) => {
- if (!el.hasAttribute('data-turbo-permanent')) {
- Alpine.destroyTree(el);
- // Turbo leaks DOM elements via their data-turbo-permanent handling.
- // That needs to be fixed upstream, but until then.
- let clone = el.cloneNode(true);
- el.replaceWith(clone);
- }
- });
- });
-}
diff --git a/docs/assets/js/helpers/helpers.js b/docs/assets/js/helpers/helpers.js
deleted file mode 100644
index 818eac40c..000000000
--- a/docs/assets/js/helpers/helpers.js
+++ /dev/null
@@ -1,17 +0,0 @@
-export const scrollToActive = (when) => {
- let els = document.querySelectorAll('.scroll-active');
- if (!els.length) {
- return;
- }
- els.forEach((el) => {
- // Find scrolling container.
- let container = el.closest('[data-turbo-preserve-scroll-container]');
- if (container) {
- // Avoid scrolling if el is already in view.
- if (el.offsetTop >= container.scrollTop && el.offsetTop <= container.scrollTop + container.clientHeight) {
- return;
- }
- container.scrollTop = el.offsetTop - container.offsetTop;
- }
- });
-};
diff --git a/docs/assets/js/helpers/index.js b/docs/assets/js/helpers/index.js
deleted file mode 100644
index 41ffa3c39..000000000
--- a/docs/assets/js/helpers/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export * from './bridgeTurboAndAlpine';
-export * from './helpers';
-export * from './lrucache';
diff --git a/docs/assets/js/helpers/lrucache.js b/docs/assets/js/helpers/lrucache.js
deleted file mode 100644
index 258848c95..000000000
--- a/docs/assets/js/helpers/lrucache.js
+++ /dev/null
@@ -1,19 +0,0 @@
-// A simple LRU cache implementation backed by a map.
-export class LRUCache {
- constructor(maxSize) {
- this.maxSize = maxSize;
- this.cache = new Map();
- }
-
- get(key) {
- return this.cache.get(key);
- }
-
- put(key, value) {
- if (this.cache.size >= this.maxSize) {
- const firstKey = this.cache.keys().next().value;
- this.cache.delete(firstKey);
- }
- this.cache.set(key, value);
- }
-}
diff --git a/docs/assets/js/main.js b/docs/assets/js/main.js
deleted file mode 100644
index 14440044b..000000000
--- a/docs/assets/js/main.js
+++ /dev/null
@@ -1,89 +0,0 @@
-import Alpine from 'alpinejs';
-import { registerMagics } from './alpinejs/magics/index';
-import { navbar, search, toc } from './alpinejs/data/index';
-import { navStore, initColorScheme } from './alpinejs/stores/index';
-import { bridgeTurboAndAlpine } from './helpers/index';
-import persist from '@alpinejs/persist';
-import focus from '@alpinejs/focus';
-
-var debug = 0 ? console.log.bind(console, '[index]') : function () {};
-
-// Turbolinks init.
-(function () {
- document.addEventListener('turbo:render', function (e) {
- // This is also called right after the body start. This is added to prevent flicker on navigation.
- initColorScheme();
- });
-})();
-
-// Set up and start Alpine.
-(function () {
- // Register AlpineJS plugins.
- {
- Alpine.plugin(focus);
- Alpine.plugin(persist);
- }
- // Register AlpineJS magics and directives.
- {
- // Handles copy to clipboard etc.
- registerMagics(Alpine);
- }
-
- // Register AlpineJS controllers.
- {
- // Register AlpineJS data controllers.
- let searchConfig = {
- index: 'hugodocs',
- app_id: 'D1BPLZHGYQ',
- api_key: '6df94e1e5d55d258c56f60d974d10314',
- };
-
- Alpine.data('navbar', () => navbar(Alpine));
- Alpine.data('search', () => search(Alpine, searchConfig));
- Alpine.data('toc', () => toc(Alpine));
- }
-
- // Register AlpineJS stores.
- {
- Alpine.store('nav', navStore(Alpine));
- }
-
- // Start AlpineJS.
- Alpine.start();
-
- // Start the Turbo-Alpine bridge.
- bridgeTurboAndAlpine(Alpine);
-
- {
- let containerScrollTops = {};
-
- // To preserve scroll position in scrolling elements on navigation add data-turbo-preserve-scroll-container="somename" to the scrolling container.
- addEventListener('turbo:click', () => {
- document.querySelectorAll('[data-turbo-preserve-scroll-container]').forEach((el2) => {
- containerScrollTops[el2.dataset.turboPreserveScrollContainer] = el2.scrollTop;
- });
- });
-
- addEventListener('turbo:render', () => {
- document.querySelectorAll('[data-turbo-preserve-scroll-container]').forEach((ele) => {
- const containerScrollTop = containerScrollTops[ele.dataset.turboPreserveScrollContainer];
- if (containerScrollTop) {
- ele.scrollTop = containerScrollTop;
- } else {
- let els = ele.querySelectorAll('.scroll-active');
- if (els.length) {
- els.forEach((el) => {
- // Avoid scrolling if el is already in view.
- if (el.offsetTop >= ele.scrollTop && el.offsetTop <= ele.scrollTop + ele.clientHeight) {
- return;
- }
- ele.scrollTop = el.offsetTop - ele.offsetTop;
- });
- }
- }
- });
-
- containerScrollTops = {};
- });
- }
-})();
diff --git a/docs/assets/js/turbo.js b/docs/assets/js/turbo.js
deleted file mode 100644
index c007896f6..000000000
--- a/docs/assets/js/turbo.js
+++ /dev/null
@@ -1 +0,0 @@
-import * as Turbo from '@hotwired/turbo';
diff --git a/docs/assets/jsconfig.json b/docs/assets/jsconfig.json
deleted file mode 100644
index 377218ccb..000000000
--- a/docs/assets/jsconfig.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "compilerOptions": {
- "baseUrl": ".",
- "paths": {
- "*": [
- "*"
- ]
- }
- }
-}
\ No newline at end of file
diff --git a/docs/assets/opengraph/gohugoio-card-base-1.png b/docs/assets/opengraph/gohugoio-card-base-1.png
deleted file mode 100644
index 65555845b..000000000
Binary files a/docs/assets/opengraph/gohugoio-card-base-1.png and /dev/null differ
diff --git a/docs/assets/opengraph/mulish-black.ttf b/docs/assets/opengraph/mulish-black.ttf
deleted file mode 100644
index db680a088..000000000
Binary files a/docs/assets/opengraph/mulish-black.ttf and /dev/null differ
diff --git a/docs/config.toml b/docs/config.toml
new file mode 100644
index 000000000..4115e601a
--- /dev/null
+++ b/docs/config.toml
@@ -0,0 +1,135 @@
+title = "Hugo: A Fast and Flexible Website Generator"
+baseurl = "http://gohugo.io/"
+MetaDataFormat = "yaml"
+pluralizeListTitles = false
+# We do redirects via Netlify's _redirects file, generated by Hugo (see "outputs" below).
+disableAliases = true
+
+[blackfriday]
+ plainIDAnchors = true
+
+[outputs]
+home = [ "HTML", "RSS", "REDIR" ]
+
+[mediaTypes]
+[mediaTypes."text/netlify"]
+suffix = ""
+delimiter = ""
+
+[outputFormats]
+[outputFormats.REDIR]
+mediatype = "text/netlify"
+baseName = "_redirects"
+isPlainText = true
+notAlternative = true
+
+[params]
+ description = "Documentation of Hugo, a fast and flexible static site generator built with love by spf13, bep and friends in Go"
+ author = "Steve Francia (spf13) and friends"
+ release = "0.25"
+
+[taxonomies]
+ tag = "tags"
+ group = "groups"
+
+[[menu.main]]
+ name = "Download Hugo"
+ pre = ""
+ url = "https://github.com/gohugoio/hugo/releases"
+ weight = -200
+[[menu.main]]
+ name = "Site Showcase"
+ pre = ""
+ url = "/showcase/"
+ weight = -180
+[[menu.main]]
+ name = "Theme Showcase"
+ pre = ""
+ url = "http://themes.gohugo.io"
+ weight = -170
+[[menu.main]]
+ name = "Press & Articles"
+ pre = ""
+ url = "/community/press/"
+ weight = -160
+[[menu.main]]
+ name = "Discuss Hugo"
+ pre = ""
+ url = "https://discourse.gohugo.io/"
+ weight = -150
+[[menu.main]]
+ name = "About Hugo"
+ identifier = "about"
+ pre = ""
+ weight = -110
+[[menu.main]]
+ name = "Release Notes"
+ url = "/release-notes/"
+ pre = ""
+ weight = -111
+[[menu.main]]
+ name = "Getting Started"
+ identifier = "getting started"
+ pre = ""
+ weight = -100
+[[menu.main]]
+ name = "Content"
+ identifier = "content"
+ pre = ""
+ weight = -90
+[[menu.main]]
+ name = "Themes"
+ identifier = "themes"
+ pre = ""
+ weight = -85
+[[menu.main]]
+ parent = "themes"
+ name = "Theme Showcase"
+ url = "http://themes.gohugo.io"
+ weight = -170
+[[menu.main]]
+ name = "Templates"
+ identifier = "layout"
+ pre = ""
+ weight = -80
+[[menu.main]]
+ name = "Taxonomies"
+ identifier = "taxonomy"
+ pre = ""
+ weight = -70
+[[menu.main]]
+ name = "Extras"
+ identifier = "extras"
+ pre = ""
+ weight = -60
+[[menu.main]]
+ name = "Community"
+ identifier = "community"
+ pre = ""
+ weight = -50
+[[menu.main]]
+ parent = "community"
+ name = "Discussion Forum"
+ url = "https://discourse.gohugo.io/"
+ weight = 150
+[[menu.main]]
+ name = "Tutorials"
+ identifier = "tutorials"
+ pre = ""
+ weight = -40
+[[menu.main]]
+ name = "Troubleshooting"
+ identifier = "troubleshooting"
+ pre = ""
+ weight = -30
+[[menu.main]]
+ name = "Tools"
+ url = "/tools/"
+ pre = ""
+ weight = -25
+[[menu.main]]
+ name = "Hugo Cmd Reference"
+ identifier = "commands"
+ pre = ""
+ weight = -20
+ url = "/commands/"
diff --git a/docs/content/LICENSE.md b/docs/content/LICENSE.md
deleted file mode 100644
index b09cd7856..000000000
--- a/docs/content/LICENSE.md
+++ /dev/null
@@ -1,201 +0,0 @@
-Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- 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.
diff --git a/docs/content/commands/hugo.md b/docs/content/commands/hugo.md
new file mode 100644
index 000000000..a88861e5e
--- /dev/null
+++ b/docs/content/commands/hugo.md
@@ -0,0 +1,80 @@
+---
+date: 2017-07-06T10:34:39+02:00
+title: "hugo"
+slug: hugo
+url: /commands/hugo/
+---
+## hugo
+
+hugo builds your site
+
+### Synopsis
+
+
+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/.
+
+```
+hugo [flags]
+```
+
+### Options
+
+```
+ -b, --baseURL string hostname (and path) to the root, e.g. http://spf13.com/
+ -D, --buildDrafts include content marked as draft
+ -E, --buildExpired include expired content
+ -F, --buildFuture include content with publishdate in the future
+ --cacheDir string filesystem path to cache directory. Defaults: $TMPDIR/hugo_cache/
+ --canonifyURLs if true, all relative URLs will be canonicalized using baseURL
+ --cleanDestinationDir remove files from destination not found in static directories
+ --config string config file (default is path/config.yaml|json|toml)
+ -c, --contentDir string filesystem path to content directory
+ -d, --destination string filesystem path to write files to
+ --disable404 do not render 404 page
+ --disableKinds stringSlice disable different kind of pages (home, RSS etc.)
+ --disableRSS do not build RSS files
+ --disableSitemap do not build Sitemap file
+ --enableGitInfo add Git revision, date and author info to the pages
+ --forceSyncStatic copy all files when static is changed.
+ -h, --help help for hugo
+ --i18n-warnings print missing translations
+ --ignoreCache ignores the cache directory
+ -l, --layoutDir string filesystem path to layout directory
+ --log enable Logging
+ --logFile string log File path (if set, logging enabled automatically)
+ --noChmod don't sync permission mode of files
+ --noTimes don't sync modification time of files
+ --pluralizeListTitles pluralize titles in lists using inflect (default true)
+ --preserveTaxonomyNames preserve taxonomy names as written ("Gérard Depardieu" vs "gerard-depardieu")
+ --quiet build in quiet mode
+ --renderToMemory render to memory (only useful for benchmark testing)
+ -s, --source string filesystem path to read files relative from
+ --stepAnalysis display memory and timing of different steps of the program
+ -t, --theme string theme to use (located in /themes/THEMENAME/)
+ --themesDir string filesystem path to themes directory
+ --uglyURLs if true, use /filename.html instead of /filename/
+ -v, --verbose verbose output
+ --verboseLog verbose logging
+ -w, --watch watch filesystem for changes and recreate as needed
+```
+
+### SEE ALSO
+* [hugo benchmark](/commands/hugo_benchmark/) - Benchmark Hugo by building a site a number of times.
+* [hugo check](/commands/hugo_check/) - Contains some verification checks
+* [hugo config](/commands/hugo_config/) - Print the site configuration
+* [hugo convert](/commands/hugo_convert/) - Convert your content to different formats
+* [hugo env](/commands/hugo_env/) - Print Hugo version and environment info
+* [hugo gen](/commands/hugo_gen/) - A collection of several useful generators.
+* [hugo import](/commands/hugo_import/) - Import your site from others.
+* [hugo list](/commands/hugo_list/) - Listing out various types of content
+* [hugo new](/commands/hugo_new/) - Create new content for your site
+* [hugo server](/commands/hugo_server/) - A high performance webserver
+* [hugo undraft](/commands/hugo_undraft/) - Undraft resets the content's draft status
+* [hugo version](/commands/hugo_version/) - Print the version number of Hugo
+
+###### Auto generated by spf13/cobra on 6-Jul-2017
diff --git a/docs/content/commands/hugo_benchmark.md b/docs/content/commands/hugo_benchmark.md
new file mode 100644
index 000000000..a3e4dcc68
--- /dev/null
+++ b/docs/content/commands/hugo_benchmark.md
@@ -0,0 +1,72 @@
+---
+date: 2017-07-06T10:34:39+02:00
+title: "hugo benchmark"
+slug: hugo_benchmark
+url: /commands/hugo_benchmark/
+---
+## hugo benchmark
+
+Benchmark Hugo by building a site a number of times.
+
+### Synopsis
+
+
+Hugo can build a site many times over and analyze the running process
+creating a benchmark.
+
+```
+hugo benchmark [flags]
+```
+
+### Options
+
+```
+ -b, --baseURL string hostname (and path) to the root, e.g. http://spf13.com/
+ -D, --buildDrafts include content marked as draft
+ -E, --buildExpired include expired content
+ -F, --buildFuture include content with publishdate in the future
+ --cacheDir string filesystem path to cache directory. Defaults: $TMPDIR/hugo_cache/
+ --canonifyURLs if true, all relative URLs will be canonicalized using baseURL
+ --cleanDestinationDir remove files from destination not found in static directories
+ -c, --contentDir string filesystem path to content directory
+ -n, --count int number of times to build the site (default 13)
+ --cpuprofile string path/filename for the CPU profile file
+ -d, --destination string filesystem path to write files to
+ --disable404 do not render 404 page
+ --disableKinds stringSlice disable different kind of pages (home, RSS etc.)
+ --disableRSS do not build RSS files
+ --disableSitemap do not build Sitemap file
+ --enableGitInfo add Git revision, date and author info to the pages
+ --forceSyncStatic copy all files when static is changed.
+ -h, --help help for benchmark
+ --i18n-warnings print missing translations
+ --ignoreCache ignores the cache directory
+ -l, --layoutDir string filesystem path to layout directory
+ --memprofile string path/filename for the memory profile file
+ --noChmod don't sync permission mode of files
+ --noTimes don't sync modification time of files
+ --pluralizeListTitles pluralize titles in lists using inflect (default true)
+ --preserveTaxonomyNames preserve taxonomy names as written ("Gérard Depardieu" vs "gerard-depardieu")
+ --renderToMemory render to memory (only useful for benchmark testing)
+ -s, --source string filesystem path to read files relative from
+ --stepAnalysis display memory and timing of different steps of the program
+ -t, --theme string theme to use (located in /themes/THEMENAME/)
+ --themesDir string filesystem path to themes directory
+ --uglyURLs if true, use /filename.html instead of /filename/
+```
+
+### Options inherited from parent commands
+
+```
+ --config string config file (default is path/config.yaml|json|toml)
+ --log enable Logging
+ --logFile string log File path (if set, logging enabled automatically)
+ --quiet build in quiet mode
+ -v, --verbose verbose output
+ --verboseLog verbose logging
+```
+
+### SEE ALSO
+* [hugo](/commands/hugo/) - hugo builds your site
+
+###### Auto generated by spf13/cobra on 6-Jul-2017
diff --git a/docs/content/commands/hugo_check.md b/docs/content/commands/hugo_check.md
new file mode 100644
index 000000000..17b57c1c7
--- /dev/null
+++ b/docs/content/commands/hugo_check.md
@@ -0,0 +1,37 @@
+---
+date: 2017-07-06T10:34:39+02:00
+title: "hugo check"
+slug: hugo_check
+url: /commands/hugo_check/
+---
+## hugo check
+
+Contains some verification checks
+
+### Synopsis
+
+
+Contains some verification checks
+
+### Options
+
+```
+ -h, --help help for check
+```
+
+### Options inherited from parent commands
+
+```
+ --config string config file (default is path/config.yaml|json|toml)
+ --log enable Logging
+ --logFile string log File path (if set, logging enabled automatically)
+ --quiet build in quiet mode
+ -v, --verbose verbose output
+ --verboseLog verbose logging
+```
+
+### SEE ALSO
+* [hugo](/commands/hugo/) - hugo builds your site
+* [hugo check ulimit](/commands/hugo_check_ulimit/) - Check system ulimit settings
+
+###### Auto generated by spf13/cobra on 6-Jul-2017
diff --git a/docs/content/commands/hugo_check_ulimit.md b/docs/content/commands/hugo_check_ulimit.md
new file mode 100644
index 000000000..f9cfadaca
--- /dev/null
+++ b/docs/content/commands/hugo_check_ulimit.md
@@ -0,0 +1,41 @@
+---
+date: 2017-07-06T10:34:39+02:00
+title: "hugo check ulimit"
+slug: hugo_check_ulimit
+url: /commands/hugo_check_ulimit/
+---
+## hugo check ulimit
+
+Check system ulimit settings
+
+### Synopsis
+
+
+Hugo will inspect the current ulimit settings on the system.
+This is primarily to ensure that Hugo can watch enough files on some OSs
+
+```
+hugo check ulimit [flags]
+```
+
+### Options
+
+```
+ -h, --help help for ulimit
+```
+
+### Options inherited from parent commands
+
+```
+ --config string config file (default is path/config.yaml|json|toml)
+ --log enable Logging
+ --logFile string log File path (if set, logging enabled automatically)
+ --quiet build in quiet mode
+ -v, --verbose verbose output
+ --verboseLog verbose logging
+```
+
+### SEE ALSO
+* [hugo check](/commands/hugo_check/) - Contains some verification checks
+
+###### Auto generated by spf13/cobra on 6-Jul-2017
diff --git a/docs/content/commands/hugo_config.md b/docs/content/commands/hugo_config.md
new file mode 100644
index 000000000..c5abf9f9a
--- /dev/null
+++ b/docs/content/commands/hugo_config.md
@@ -0,0 +1,40 @@
+---
+date: 2017-07-06T10:34:39+02:00
+title: "hugo config"
+slug: hugo_config
+url: /commands/hugo_config/
+---
+## hugo config
+
+Print the site configuration
+
+### Synopsis
+
+
+Print the site configuration, both default and custom settings.
+
+```
+hugo config [flags]
+```
+
+### Options
+
+```
+ -h, --help help for config
+```
+
+### Options inherited from parent commands
+
+```
+ --config string config file (default is path/config.yaml|json|toml)
+ --log enable Logging
+ --logFile string log File path (if set, logging enabled automatically)
+ --quiet build in quiet mode
+ -v, --verbose verbose output
+ --verboseLog verbose logging
+```
+
+### SEE ALSO
+* [hugo](/commands/hugo/) - hugo builds your site
+
+###### Auto generated by spf13/cobra on 6-Jul-2017
diff --git a/docs/content/commands/hugo_convert.md b/docs/content/commands/hugo_convert.md
new file mode 100644
index 000000000..0996888f6
--- /dev/null
+++ b/docs/content/commands/hugo_convert.md
@@ -0,0 +1,44 @@
+---
+date: 2017-07-06T10:34:39+02:00
+title: "hugo convert"
+slug: hugo_convert
+url: /commands/hugo_convert/
+---
+## hugo convert
+
+Convert your content to different formats
+
+### Synopsis
+
+
+Convert your content (e.g. front matter) to different formats.
+
+See convert's subcommands toJSON, toTOML and toYAML for more information.
+
+### Options
+
+```
+ -h, --help help for convert
+ -o, --output string filesystem path to write files to
+ -s, --source string filesystem path to read files relative from
+ --unsafe enable less safe operations, please backup first
+```
+
+### Options inherited from parent commands
+
+```
+ --config string config file (default is path/config.yaml|json|toml)
+ --log enable Logging
+ --logFile string log File path (if set, logging enabled automatically)
+ --quiet build in quiet mode
+ -v, --verbose verbose output
+ --verboseLog verbose logging
+```
+
+### SEE ALSO
+* [hugo](/commands/hugo/) - hugo builds your site
+* [hugo convert toJSON](/commands/hugo_convert_tojson/) - Convert front matter to JSON
+* [hugo convert toTOML](/commands/hugo_convert_totoml/) - Convert front matter to TOML
+* [hugo convert toYAML](/commands/hugo_convert_toyaml/) - Convert front matter to YAML
+
+###### Auto generated by spf13/cobra on 6-Jul-2017
diff --git a/docs/content/commands/hugo_convert_toJSON.md b/docs/content/commands/hugo_convert_toJSON.md
new file mode 100644
index 000000000..7d3937540
--- /dev/null
+++ b/docs/content/commands/hugo_convert_toJSON.md
@@ -0,0 +1,44 @@
+---
+date: 2017-07-06T10:34:39+02:00
+title: "hugo convert toJSON"
+slug: hugo_convert_toJSON
+url: /commands/hugo_convert_tojson/
+---
+## hugo convert toJSON
+
+Convert front matter to JSON
+
+### Synopsis
+
+
+toJSON converts all front matter in the content directory
+to use JSON for the front matter.
+
+```
+hugo convert toJSON [flags]
+```
+
+### Options
+
+```
+ -h, --help help for toJSON
+```
+
+### Options inherited from parent commands
+
+```
+ --config string config file (default is path/config.yaml|json|toml)
+ --log enable Logging
+ --logFile string log File path (if set, logging enabled automatically)
+ -o, --output string filesystem path to write files to
+ --quiet build in quiet mode
+ -s, --source string filesystem path to read files relative from
+ --unsafe enable less safe operations, please backup first
+ -v, --verbose verbose output
+ --verboseLog verbose logging
+```
+
+### SEE ALSO
+* [hugo convert](/commands/hugo_convert/) - Convert your content to different formats
+
+###### Auto generated by spf13/cobra on 6-Jul-2017
diff --git a/docs/content/commands/hugo_convert_toTOML.md b/docs/content/commands/hugo_convert_toTOML.md
new file mode 100644
index 000000000..a18595780
--- /dev/null
+++ b/docs/content/commands/hugo_convert_toTOML.md
@@ -0,0 +1,44 @@
+---
+date: 2017-07-06T10:34:39+02:00
+title: "hugo convert toTOML"
+slug: hugo_convert_toTOML
+url: /commands/hugo_convert_totoml/
+---
+## hugo convert toTOML
+
+Convert front matter to TOML
+
+### Synopsis
+
+
+toTOML converts all front matter in the content directory
+to use TOML for the front matter.
+
+```
+hugo convert toTOML [flags]
+```
+
+### Options
+
+```
+ -h, --help help for toTOML
+```
+
+### Options inherited from parent commands
+
+```
+ --config string config file (default is path/config.yaml|json|toml)
+ --log enable Logging
+ --logFile string log File path (if set, logging enabled automatically)
+ -o, --output string filesystem path to write files to
+ --quiet build in quiet mode
+ -s, --source string filesystem path to read files relative from
+ --unsafe enable less safe operations, please backup first
+ -v, --verbose verbose output
+ --verboseLog verbose logging
+```
+
+### SEE ALSO
+* [hugo convert](/commands/hugo_convert/) - Convert your content to different formats
+
+###### Auto generated by spf13/cobra on 6-Jul-2017
diff --git a/docs/content/commands/hugo_convert_toYAML.md b/docs/content/commands/hugo_convert_toYAML.md
new file mode 100644
index 000000000..d49964f37
--- /dev/null
+++ b/docs/content/commands/hugo_convert_toYAML.md
@@ -0,0 +1,44 @@
+---
+date: 2017-07-06T10:34:39+02:00
+title: "hugo convert toYAML"
+slug: hugo_convert_toYAML
+url: /commands/hugo_convert_toyaml/
+---
+## hugo convert toYAML
+
+Convert front matter to YAML
+
+### Synopsis
+
+
+toYAML converts all front matter in the content directory
+to use YAML for the front matter.
+
+```
+hugo convert toYAML [flags]
+```
+
+### Options
+
+```
+ -h, --help help for toYAML
+```
+
+### Options inherited from parent commands
+
+```
+ --config string config file (default is path/config.yaml|json|toml)
+ --log enable Logging
+ --logFile string log File path (if set, logging enabled automatically)
+ -o, --output string filesystem path to write files to
+ --quiet build in quiet mode
+ -s, --source string filesystem path to read files relative from
+ --unsafe enable less safe operations, please backup first
+ -v, --verbose verbose output
+ --verboseLog verbose logging
+```
+
+### SEE ALSO
+* [hugo convert](/commands/hugo_convert/) - Convert your content to different formats
+
+###### Auto generated by spf13/cobra on 6-Jul-2017
diff --git a/docs/content/commands/hugo_env.md b/docs/content/commands/hugo_env.md
new file mode 100644
index 000000000..c6bc7c7e1
--- /dev/null
+++ b/docs/content/commands/hugo_env.md
@@ -0,0 +1,40 @@
+---
+date: 2017-07-06T10:34:39+02:00
+title: "hugo env"
+slug: hugo_env
+url: /commands/hugo_env/
+---
+## hugo env
+
+Print Hugo version and environment info
+
+### Synopsis
+
+
+Print Hugo version and environment info. This is useful in Hugo bug reports.
+
+```
+hugo env [flags]
+```
+
+### Options
+
+```
+ -h, --help help for env
+```
+
+### Options inherited from parent commands
+
+```
+ --config string config file (default is path/config.yaml|json|toml)
+ --log enable Logging
+ --logFile string log File path (if set, logging enabled automatically)
+ --quiet build in quiet mode
+ -v, --verbose verbose output
+ --verboseLog verbose logging
+```
+
+### SEE ALSO
+* [hugo](/commands/hugo/) - hugo builds your site
+
+###### Auto generated by spf13/cobra on 6-Jul-2017
diff --git a/docs/content/commands/hugo_gen.md b/docs/content/commands/hugo_gen.md
new file mode 100644
index 000000000..7fec7a597
--- /dev/null
+++ b/docs/content/commands/hugo_gen.md
@@ -0,0 +1,39 @@
+---
+date: 2017-07-06T10:34:39+02:00
+title: "hugo gen"
+slug: hugo_gen
+url: /commands/hugo_gen/
+---
+## hugo gen
+
+A collection of several useful generators.
+
+### Synopsis
+
+
+A collection of several useful generators.
+
+### Options
+
+```
+ -h, --help help for gen
+```
+
+### Options inherited from parent commands
+
+```
+ --config string config file (default is path/config.yaml|json|toml)
+ --log enable Logging
+ --logFile string log File path (if set, logging enabled automatically)
+ --quiet build in quiet mode
+ -v, --verbose verbose output
+ --verboseLog verbose logging
+```
+
+### SEE ALSO
+* [hugo](/commands/hugo/) - hugo builds your site
+* [hugo gen autocomplete](/commands/hugo_gen_autocomplete/) - Generate shell autocompletion script for Hugo
+* [hugo gen doc](/commands/hugo_gen_doc/) - Generate Markdown documentation for the Hugo CLI.
+* [hugo gen man](/commands/hugo_gen_man/) - Generate man pages for the Hugo CLI
+
+###### Auto generated by spf13/cobra on 6-Jul-2017
diff --git a/docs/content/commands/hugo_gen_autocomplete.md b/docs/content/commands/hugo_gen_autocomplete.md
new file mode 100644
index 000000000..e1bb01270
--- /dev/null
+++ b/docs/content/commands/hugo_gen_autocomplete.md
@@ -0,0 +1,58 @@
+---
+date: 2017-07-06T10:34:39+02:00
+title: "hugo gen autocomplete"
+slug: hugo_gen_autocomplete
+url: /commands/hugo_gen_autocomplete/
+---
+## hugo gen autocomplete
+
+Generate shell autocompletion script for Hugo
+
+### Synopsis
+
+
+Generates a shell autocompletion script for Hugo.
+
+NOTE: The current version supports Bash only.
+ This should work for *nix systems with Bash installed.
+
+By default, the file is written directly to /etc/bash_completion.d
+for convenience, and the command may need superuser rights, e.g.:
+
+ $ sudo hugo gen autocomplete
+
+Add `--completionfile=/path/to/file` flag to set alternative
+file-path and name.
+
+Logout and in again to reload the completion scripts,
+or just source them in directly:
+
+ $ . /etc/bash_completion
+
+```
+hugo gen autocomplete [flags]
+```
+
+### Options
+
+```
+ --completionfile string autocompletion file (default "/etc/bash_completion.d/hugo.sh")
+ -h, --help help for autocomplete
+ --type string autocompletion type (currently only bash supported) (default "bash")
+```
+
+### Options inherited from parent commands
+
+```
+ --config string config file (default is path/config.yaml|json|toml)
+ --log enable Logging
+ --logFile string log File path (if set, logging enabled automatically)
+ --quiet build in quiet mode
+ -v, --verbose verbose output
+ --verboseLog verbose logging
+```
+
+### SEE ALSO
+* [hugo gen](/commands/hugo_gen/) - A collection of several useful generators.
+
+###### Auto generated by spf13/cobra on 6-Jul-2017
diff --git a/docs/content/commands/hugo_gen_doc.md b/docs/content/commands/hugo_gen_doc.md
new file mode 100644
index 000000000..c4442de0b
--- /dev/null
+++ b/docs/content/commands/hugo_gen_doc.md
@@ -0,0 +1,47 @@
+---
+date: 2017-07-06T10:34:39+02:00
+title: "hugo gen doc"
+slug: hugo_gen_doc
+url: /commands/hugo_gen_doc/
+---
+## hugo gen doc
+
+Generate Markdown documentation for the Hugo CLI.
+
+### Synopsis
+
+
+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.
+
+```
+hugo gen doc [flags]
+```
+
+### Options
+
+```
+ --dir string the directory to write the doc. (default "/tmp/hugodoc/")
+ -h, --help help for doc
+```
+
+### Options inherited from parent commands
+
+```
+ --config string config file (default is path/config.yaml|json|toml)
+ --log enable Logging
+ --logFile string log File path (if set, logging enabled automatically)
+ --quiet build in quiet mode
+ -v, --verbose verbose output
+ --verboseLog verbose logging
+```
+
+### SEE ALSO
+* [hugo gen](/commands/hugo_gen/) - A collection of several useful generators.
+
+###### Auto generated by spf13/cobra on 6-Jul-2017
diff --git a/docs/content/commands/hugo_gen_man.md b/docs/content/commands/hugo_gen_man.md
new file mode 100644
index 000000000..e4e6af0f7
--- /dev/null
+++ b/docs/content/commands/hugo_gen_man.md
@@ -0,0 +1,43 @@
+---
+date: 2017-07-06T10:34:39+02:00
+title: "hugo gen man"
+slug: hugo_gen_man
+url: /commands/hugo_gen_man/
+---
+## hugo gen man
+
+Generate man pages for the Hugo CLI
+
+### Synopsis
+
+
+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.
+
+```
+hugo gen man [flags]
+```
+
+### Options
+
+```
+ --dir string the directory to write the man pages. (default "man/")
+ -h, --help help for man
+```
+
+### Options inherited from parent commands
+
+```
+ --config string config file (default is path/config.yaml|json|toml)
+ --log enable Logging
+ --logFile string log File path (if set, logging enabled automatically)
+ --quiet build in quiet mode
+ -v, --verbose verbose output
+ --verboseLog verbose logging
+```
+
+### SEE ALSO
+* [hugo gen](/commands/hugo_gen/) - A collection of several useful generators.
+
+###### Auto generated by spf13/cobra on 6-Jul-2017
diff --git a/docs/content/commands/hugo_import.md b/docs/content/commands/hugo_import.md
new file mode 100644
index 000000000..b5140760a
--- /dev/null
+++ b/docs/content/commands/hugo_import.md
@@ -0,0 +1,39 @@
+---
+date: 2017-07-06T10:34:39+02:00
+title: "hugo import"
+slug: hugo_import
+url: /commands/hugo_import/
+---
+## hugo import
+
+Import your site from others.
+
+### Synopsis
+
+
+Import your site from other web site generators like Jekyll.
+
+Import requires a subcommand, e.g. `hugo import jekyll jekyll_root_path target_path`.
+
+### Options
+
+```
+ -h, --help help for import
+```
+
+### Options inherited from parent commands
+
+```
+ --config string config file (default is path/config.yaml|json|toml)
+ --log enable Logging
+ --logFile string log File path (if set, logging enabled automatically)
+ --quiet build in quiet mode
+ -v, --verbose verbose output
+ --verboseLog verbose logging
+```
+
+### SEE ALSO
+* [hugo](/commands/hugo/) - hugo builds your site
+* [hugo import jekyll](/commands/hugo_import_jekyll/) - hugo import from Jekyll
+
+###### Auto generated by spf13/cobra on 6-Jul-2017
diff --git a/docs/content/commands/hugo_import_jekyll.md b/docs/content/commands/hugo_import_jekyll.md
new file mode 100644
index 000000000..cbdd01824
--- /dev/null
+++ b/docs/content/commands/hugo_import_jekyll.md
@@ -0,0 +1,43 @@
+---
+date: 2017-07-06T10:34:39+02:00
+title: "hugo import jekyll"
+slug: hugo_import_jekyll
+url: /commands/hugo_import_jekyll/
+---
+## hugo import jekyll
+
+hugo import from Jekyll
+
+### Synopsis
+
+
+hugo import from Jekyll.
+
+Import from Jekyll requires two paths, e.g. `hugo import jekyll jekyll_root_path target_path`.
+
+```
+hugo import jekyll [flags]
+```
+
+### Options
+
+```
+ --force allow import into non-empty target directory
+ -h, --help help for jekyll
+```
+
+### Options inherited from parent commands
+
+```
+ --config string config file (default is path/config.yaml|json|toml)
+ --log enable Logging
+ --logFile string log File path (if set, logging enabled automatically)
+ --quiet build in quiet mode
+ -v, --verbose verbose output
+ --verboseLog verbose logging
+```
+
+### SEE ALSO
+* [hugo import](/commands/hugo_import/) - Import your site from others.
+
+###### Auto generated by spf13/cobra on 6-Jul-2017
diff --git a/docs/content/commands/hugo_list.md b/docs/content/commands/hugo_list.md
new file mode 100644
index 000000000..902cfc38b
--- /dev/null
+++ b/docs/content/commands/hugo_list.md
@@ -0,0 +1,42 @@
+---
+date: 2017-07-06T10:34:39+02:00
+title: "hugo list"
+slug: hugo_list
+url: /commands/hugo_list/
+---
+## hugo list
+
+Listing out various types of content
+
+### Synopsis
+
+
+Listing out various types of content.
+
+List requires a subcommand, e.g. `hugo list drafts`.
+
+### Options
+
+```
+ -h, --help help for list
+ -s, --source string filesystem path to read files relative from
+```
+
+### Options inherited from parent commands
+
+```
+ --config string config file (default is path/config.yaml|json|toml)
+ --log enable Logging
+ --logFile string log File path (if set, logging enabled automatically)
+ --quiet build in quiet mode
+ -v, --verbose verbose output
+ --verboseLog verbose logging
+```
+
+### SEE ALSO
+* [hugo](/commands/hugo/) - hugo builds your site
+* [hugo list drafts](/commands/hugo_list_drafts/) - List all drafts
+* [hugo list expired](/commands/hugo_list_expired/) - List all posts already expired
+* [hugo list future](/commands/hugo_list_future/) - List all posts dated in the future
+
+###### Auto generated by spf13/cobra on 6-Jul-2017
diff --git a/docs/content/commands/hugo_list_drafts.md b/docs/content/commands/hugo_list_drafts.md
new file mode 100644
index 000000000..4e1de8460
--- /dev/null
+++ b/docs/content/commands/hugo_list_drafts.md
@@ -0,0 +1,41 @@
+---
+date: 2017-07-06T10:34:39+02:00
+title: "hugo list drafts"
+slug: hugo_list_drafts
+url: /commands/hugo_list_drafts/
+---
+## hugo list drafts
+
+List all drafts
+
+### Synopsis
+
+
+List all of the drafts in your content directory.
+
+```
+hugo list drafts [flags]
+```
+
+### Options
+
+```
+ -h, --help help for drafts
+```
+
+### Options inherited from parent commands
+
+```
+ --config string config file (default is path/config.yaml|json|toml)
+ --log enable Logging
+ --logFile string log File path (if set, logging enabled automatically)
+ --quiet build in quiet mode
+ -s, --source string filesystem path to read files relative from
+ -v, --verbose verbose output
+ --verboseLog verbose logging
+```
+
+### SEE ALSO
+* [hugo list](/commands/hugo_list/) - Listing out various types of content
+
+###### Auto generated by spf13/cobra on 6-Jul-2017
diff --git a/docs/content/commands/hugo_list_expired.md b/docs/content/commands/hugo_list_expired.md
new file mode 100644
index 000000000..7e48d61b4
--- /dev/null
+++ b/docs/content/commands/hugo_list_expired.md
@@ -0,0 +1,42 @@
+---
+date: 2017-07-06T10:34:39+02:00
+title: "hugo list expired"
+slug: hugo_list_expired
+url: /commands/hugo_list_expired/
+---
+## hugo list expired
+
+List all posts already expired
+
+### Synopsis
+
+
+List all of the posts in your content directory which has already
+expired.
+
+```
+hugo list expired [flags]
+```
+
+### Options
+
+```
+ -h, --help help for expired
+```
+
+### Options inherited from parent commands
+
+```
+ --config string config file (default is path/config.yaml|json|toml)
+ --log enable Logging
+ --logFile string log File path (if set, logging enabled automatically)
+ --quiet build in quiet mode
+ -s, --source string filesystem path to read files relative from
+ -v, --verbose verbose output
+ --verboseLog verbose logging
+```
+
+### SEE ALSO
+* [hugo list](/commands/hugo_list/) - Listing out various types of content
+
+###### Auto generated by spf13/cobra on 6-Jul-2017
diff --git a/docs/content/commands/hugo_list_future.md b/docs/content/commands/hugo_list_future.md
new file mode 100644
index 000000000..e1358b0fc
--- /dev/null
+++ b/docs/content/commands/hugo_list_future.md
@@ -0,0 +1,42 @@
+---
+date: 2017-07-06T10:34:39+02:00
+title: "hugo list future"
+slug: hugo_list_future
+url: /commands/hugo_list_future/
+---
+## hugo list future
+
+List all posts dated in the future
+
+### Synopsis
+
+
+List all of the posts in your content directory which will be
+posted in the future.
+
+```
+hugo list future [flags]
+```
+
+### Options
+
+```
+ -h, --help help for future
+```
+
+### Options inherited from parent commands
+
+```
+ --config string config file (default is path/config.yaml|json|toml)
+ --log enable Logging
+ --logFile string log File path (if set, logging enabled automatically)
+ --quiet build in quiet mode
+ -s, --source string filesystem path to read files relative from
+ -v, --verbose verbose output
+ --verboseLog verbose logging
+```
+
+### SEE ALSO
+* [hugo list](/commands/hugo_list/) - Listing out various types of content
+
+###### Auto generated by spf13/cobra on 6-Jul-2017
diff --git a/docs/content/commands/hugo_new.md b/docs/content/commands/hugo_new.md
new file mode 100644
index 000000000..8e6668eca
--- /dev/null
+++ b/docs/content/commands/hugo_new.md
@@ -0,0 +1,50 @@
+---
+date: 2017-07-06T10:34:39+02:00
+title: "hugo new"
+slug: hugo_new
+url: /commands/hugo_new/
+---
+## hugo new
+
+Create new content for your site
+
+### Synopsis
+
+
+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.
+
+```
+hugo new [path] [flags]
+```
+
+### Options
+
+```
+ --editor string edit new content with this editor, if provided
+ -h, --help help for new
+ -k, --kind string content type to create
+ -s, --source string filesystem path to read files relative from
+```
+
+### Options inherited from parent commands
+
+```
+ --config string config file (default is path/config.yaml|json|toml)
+ --log enable Logging
+ --logFile string log File path (if set, logging enabled automatically)
+ --quiet build in quiet mode
+ -v, --verbose verbose output
+ --verboseLog verbose logging
+```
+
+### SEE ALSO
+* [hugo](/commands/hugo/) - hugo builds your site
+* [hugo new site](/commands/hugo_new_site/) - Create a new site (skeleton)
+* [hugo new theme](/commands/hugo_new_theme/) - Create a new theme
+
+###### Auto generated by spf13/cobra on 6-Jul-2017
diff --git a/docs/content/commands/hugo_new_site.md b/docs/content/commands/hugo_new_site.md
new file mode 100644
index 000000000..af055195e
--- /dev/null
+++ b/docs/content/commands/hugo_new_site.md
@@ -0,0 +1,45 @@
+---
+date: 2017-07-06T10:34:39+02:00
+title: "hugo new site"
+slug: hugo_new_site
+url: /commands/hugo_new_site/
+---
+## hugo new site
+
+Create a new site (skeleton)
+
+### Synopsis
+
+
+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.
+
+```
+hugo new site [path] [flags]
+```
+
+### Options
+
+```
+ --force init inside non-empty directory
+ -f, --format string config & frontmatter format (default "toml")
+ -h, --help help for site
+```
+
+### Options inherited from parent commands
+
+```
+ --config string config file (default is path/config.yaml|json|toml)
+ --log enable Logging
+ --logFile string log File path (if set, logging enabled automatically)
+ --quiet build in quiet mode
+ -s, --source string filesystem path to read files relative from
+ -v, --verbose verbose output
+ --verboseLog verbose logging
+```
+
+### SEE ALSO
+* [hugo new](/commands/hugo_new/) - Create new content for your site
+
+###### Auto generated by spf13/cobra on 6-Jul-2017
diff --git a/docs/content/commands/hugo_new_theme.md b/docs/content/commands/hugo_new_theme.md
new file mode 100644
index 000000000..94a595176
--- /dev/null
+++ b/docs/content/commands/hugo_new_theme.md
@@ -0,0 +1,44 @@
+---
+date: 2017-07-06T10:34:39+02:00
+title: "hugo new theme"
+slug: hugo_new_theme
+url: /commands/hugo_new_theme/
+---
+## hugo new theme
+
+Create a new theme
+
+### Synopsis
+
+
+Create a new theme (skeleton) called [name] in the current directory.
+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.
+
+```
+hugo new theme [name] [flags]
+```
+
+### Options
+
+```
+ -h, --help help for theme
+```
+
+### Options inherited from parent commands
+
+```
+ --config string config file (default is path/config.yaml|json|toml)
+ --log enable Logging
+ --logFile string log File path (if set, logging enabled automatically)
+ --quiet build in quiet mode
+ -s, --source string filesystem path to read files relative from
+ -v, --verbose verbose output
+ --verboseLog verbose logging
+```
+
+### SEE ALSO
+* [hugo new](/commands/hugo_new/) - Create new content for your site
+
+###### Auto generated by spf13/cobra on 6-Jul-2017
diff --git a/docs/content/commands/hugo_server.md b/docs/content/commands/hugo_server.md
new file mode 100644
index 000000000..71e8ff6d5
--- /dev/null
+++ b/docs/content/commands/hugo_server.md
@@ -0,0 +1,87 @@
+---
+date: 2017-07-06T10:34:39+02:00
+title: "hugo server"
+slug: hugo_server
+url: /commands/hugo_server/
+---
+## hugo server
+
+A high performance webserver
+
+### Synopsis
+
+
+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.
+
+```
+hugo server [flags]
+```
+
+### Options
+
+```
+ --appendPort append port to baseURL (default true)
+ -b, --baseURL string hostname (and path) to the root, e.g. http://spf13.com/
+ --bind string interface to which the server will bind (default "127.0.0.1")
+ -D, --buildDrafts include content marked as draft
+ -E, --buildExpired include expired content
+ -F, --buildFuture include content with publishdate in the future
+ --cacheDir string filesystem path to cache directory. Defaults: $TMPDIR/hugo_cache/
+ --canonifyURLs if true, all relative URLs will be canonicalized using baseURL
+ --cleanDestinationDir remove files from destination not found in static directories
+ -c, --contentDir string filesystem path to content directory
+ -d, --destination string filesystem path to write files to
+ --disable404 do not render 404 page
+ --disableKinds stringSlice disable different kind of pages (home, RSS etc.)
+ --disableLiveReload watch without enabling live browser reload on rebuild
+ --disableRSS do not build RSS files
+ --disableSitemap do not build Sitemap file
+ --enableGitInfo add Git revision, date and author info to the pages
+ --forceSyncStatic copy all files when static is changed.
+ -h, --help help for server
+ --i18n-warnings print missing translations
+ --ignoreCache ignores the cache directory
+ -l, --layoutDir string filesystem path to layout directory
+ --meminterval string interval to poll memory usage (requires --memstats), valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". (default "100ms")
+ --memstats string log memory usage to this file
+ --navigateToChanged navigate to changed content file on live browser reload
+ --noChmod don't sync permission mode of files
+ --noTimes don't sync modification time of files
+ --pluralizeListTitles pluralize titles in lists using inflect (default true)
+ -p, --port int port on which the server will listen (default 1313)
+ --preserveTaxonomyNames preserve taxonomy names as written ("Gérard Depardieu" vs "gerard-depardieu")
+ --renderToDisk render to Destination path (default is render to memory & serve from there)
+ -s, --source string filesystem path to read files relative from
+ --stepAnalysis display memory and timing of different steps of the program
+ -t, --theme string theme to use (located in /themes/THEMENAME/)
+ --themesDir string filesystem path to themes directory
+ --uglyURLs if true, use /filename.html instead of /filename/
+ -w, --watch watch filesystem for changes and recreate as needed (default true)
+```
+
+### Options inherited from parent commands
+
+```
+ --config string config file (default is path/config.yaml|json|toml)
+ --log enable Logging
+ --logFile string log File path (if set, logging enabled automatically)
+ --quiet build in quiet mode
+ -v, --verbose verbose output
+ --verboseLog verbose logging
+```
+
+### SEE ALSO
+* [hugo](/commands/hugo/) - hugo builds your site
+
+###### Auto generated by spf13/cobra on 6-Jul-2017
diff --git a/docs/content/commands/hugo_undraft.md b/docs/content/commands/hugo_undraft.md
new file mode 100644
index 000000000..df986a2d2
--- /dev/null
+++ b/docs/content/commands/hugo_undraft.md
@@ -0,0 +1,42 @@
+---
+date: 2017-07-06T10:34:39+02:00
+title: "hugo undraft"
+slug: hugo_undraft
+url: /commands/hugo_undraft/
+---
+## hugo undraft
+
+Undraft resets the content's draft status
+
+### Synopsis
+
+
+Undraft resets the content's draft status
+and updates the date to the current date and time.
+If the content's draft status is 'False', nothing is done.
+
+```
+hugo undraft path/to/content [flags]
+```
+
+### Options
+
+```
+ -h, --help help for undraft
+```
+
+### Options inherited from parent commands
+
+```
+ --config string config file (default is path/config.yaml|json|toml)
+ --log enable Logging
+ --logFile string log File path (if set, logging enabled automatically)
+ --quiet build in quiet mode
+ -v, --verbose verbose output
+ --verboseLog verbose logging
+```
+
+### SEE ALSO
+* [hugo](/commands/hugo/) - hugo builds your site
+
+###### Auto generated by spf13/cobra on 6-Jul-2017
diff --git a/docs/content/commands/hugo_version.md b/docs/content/commands/hugo_version.md
new file mode 100644
index 000000000..053946df1
--- /dev/null
+++ b/docs/content/commands/hugo_version.md
@@ -0,0 +1,40 @@
+---
+date: 2017-07-06T10:34:39+02:00
+title: "hugo version"
+slug: hugo_version
+url: /commands/hugo_version/
+---
+## hugo version
+
+Print the version number of Hugo
+
+### Synopsis
+
+
+All software has versions. This is Hugo's.
+
+```
+hugo version [flags]
+```
+
+### Options
+
+```
+ -h, --help help for version
+```
+
+### Options inherited from parent commands
+
+```
+ --config string config file (default is path/config.yaml|json|toml)
+ --log enable Logging
+ --logFile string log File path (if set, logging enabled automatically)
+ --quiet build in quiet mode
+ -v, --verbose verbose output
+ --verboseLog verbose logging
+```
+
+### SEE ALSO
+* [hugo](/commands/hugo/) - hugo builds your site
+
+###### Auto generated by spf13/cobra on 6-Jul-2017
diff --git a/docs/content/community/contributing.md b/docs/content/community/contributing.md
new file mode 100644
index 000000000..052311911
--- /dev/null
+++ b/docs/content/community/contributing.md
@@ -0,0 +1,111 @@
+---
+aliases:
+- /doc/contributing/
+- /meta/contributing/
+lastmod: 2015-02-12
+date: 2013-07-01
+menu:
+ main:
+ parent: community
+next: /tutorials/automated-deployments
+prev: /community/mailing-list
+title: Contributing to Hugo
+weight: 30
+---
+
+All contributions to Hugo are welcome. Whether you want to scratch an itch or simply contribute to the project, feel free to pick something from the [roadmap]({{< relref "meta/roadmap.md" >}}) or contact the dev team via the [Forums](https://discourse.gohugo.io/) or [Gitter](https://gitter.im/gohugoio/hugo) about what may make sense to do next.
+
+You should fork the project and make your changes. *We encourage pull requests to discuss code changes.*
+
+
+When you're ready to create a pull request, be sure to:
+
+ * Have test cases for the new code. If you have questions about how to do it, please ask in your pull request.
+ * Run `go fmt`.
+ * Squash your commits into a single commit. `git rebase -i`. It's okay to force update your pull request.
+ * Run `make check` and ensure it succeeds. [Travis CI](https://travis-ci.org/gohugoio/hugo) and [Appveyor](https://ci.appveyor.com/project/gohugoio/hugo) will runs these checks and fail the build if `make check` fails.
+
+## Contribution Overview
+
+We wrote a [detailed guide]({{< relref "tutorials/how-to-contribute-to-hugo.md" >}}) for newcomers that guides you step by step to your first contribution. If you are more experienced, follow the guide below.
+
+
+# Building from source
+
+## Vendored Dependencies
+
+Hugo uses [govendor][] to vendor dependencies, but we don't commit the vendored packages themselves to the Hugo git repository.
+Therefore, a simple `go get` is not supported since `go get` is not vendor-aware.
+You **must use govendor** to fetch Hugo's dependencies.
+
+## Fetch the Sources
+
+ go get github.com/kardianos/govendor
+ govendor get github.com/gohugoio/hugo
+
+## Running Hugo
+
+ cd $HOME/go/src/github.com/gohugoio/hugo
+ go run main.go
+
+## Building Hugo
+
+ cd $HOME/go/src/github.com/gohugoio/hugo
+ make build
+ # or to install to $HOME/go/bin:
+ make install
+
+
+# Showcase additions
+
+You got your new website running and it's powered by Hugo? Great. You can add your website with a few steps to the [showcase](/showcase/).
+
+First, make sure that you created a [fork](https://help.github.com/articles/fork-a-repo/) of the [`hugoDocs`](https://github.com/gohugoio/hugodocs) repository on GitHub and cloned your fork on your local computer. **Next, create a separate branch for your additions**:
+
+```
+# You can choose a different descriptive branch name if you like
+git checkout -b showcase-addition
+```
+
+Let's create a new document that contains some metadata of your homepage. Replace `example` in the following examples with something unique like the name of your website. Inside the terminal enter the following commands:
+
+```
+cd docs
+hugo new showcase/example.md
+```
+
+You should find the new file at `content/showcase/example.md`. Open it in an editor. The file should contain a frontmatter with predefined variables like below:
+
+```
+---
+date: 2016-02-12T21:01:18+01:00
+description: ""
+license: ""
+licenseLink: ""
+sitelink: http://spf13.com/
+sourceLink: https://github.com/spf13/spf13.com
+tags:
+- personal
+- blog
+thumbnail: /img/spf13-tn.jpg
+title: example
+---
+```
+
+Add at least values for `sitelink`, `title`, `description` and a path for `thumbnail`.
+
+Furthermore, we need to create the thumbnail of your website. **It's important that the thumbnail has the required dimensions of 600px by 400px.** Give your thumbnail a name like `example-tn.png`. Save it under `static/img/`.
+
+Check a last time that everything works as expected. Start Hugo's built-in server in order to inspect your local copy of the showcase in the browser:
+
+ hugo server
+
+If everything looks fine, we are ready to commit your additions. For the sake of best practices, please make sure that your commit follows our [code contribution guideline](https://github.com/gohugoio/hugo#code-contribution-guideline).
+
+ git commit -m"Add example.com to the showcase"
+
+Last but not least, we're ready to create a [pull request](https://github.com/gohugoio/hugoDocs/compare).
+
+Don't forget to accept the contributor license agreement. Click on the yellow badge in the automatically added comment in the pull request.
+
+[govendor]: https://github.com/kardianos/govendor
diff --git a/docs/content/community/mailing-list.md b/docs/content/community/mailing-list.md
new file mode 100644
index 000000000..3bd9f58bf
--- /dev/null
+++ b/docs/content/community/mailing-list.md
@@ -0,0 +1,51 @@
+---
+lastmod: 2015-05-25
+date: 2013-07-01
+menu:
+ main:
+ parent: community
+next: /community/contributing
+prev: /extras/urls
+title: Mailing List
+weight: 10
+---
+
+## Discussion Forum
+
+Hugo has its own [discussion forum](https://discourse.gohugo.io/) powered by [Discourse](http://www.discourse.org/).
+
+Please use this for all discussions, questions, etc.
+
+### Twitter
+
+Get the latest bite-sized news and themes from the Hugo community on Twitter by following [@gohugoio](http://twitter.com/gohugoio).
+
+## Mailing List
+
+Hugo has two mailing lists:
+
+### Announcements
+Very low traffic. Only releases will be emailed here.
+
+https://groups.google.com/forum/#!forum/hugo-announce
+
+### Discussion (Archive)
+
+**This has been replaced with the [Hugo discussion forum](https://discourse.gohugo.io/).**
+
+It is available for archival purposes.
+
+https://groups.google.com/forum/#!forum/hugo-discuss
+
+
+## Other Resources
+
+### GoNuts
+
+For general Go questions or discussion please refer to the Go mailing list.
+
+https://groups.google.com/forum/#!forum/golang-nuts
+
+### GitHub Issues
+
+https://github.com/gohugoio/hugo/issues
diff --git a/docs/content/community/press.md b/docs/content/community/press.md
new file mode 100644
index 000000000..c5523d66e
--- /dev/null
+++ b/docs/content/community/press.md
@@ -0,0 +1,141 @@
+---
+lastmod: 2017-03-02
+date: 2014-03-24T20:00:00Z
+linktitle: Press
+notoc: true
+title: Press, Blogs and Media Coverage
+weight: 20
+---
+
+### Help keep this list up to date
+
+Know of a post, article or tutorial on Hugo? [Add it to this list](https://github.com/gohugoio/hugo/edit/master/docs/content/community/press.md).
+
+## Press and Articles
+
+Hugo has been featured in the following Blog Posts, Press and Media.
+
+
+| Title | Author | Date |
+| ------ | ------ | -----: |
+| [Build, Test, And Deploy Statically Generated Websites With Hugo & CircleCI](https://circleci.com/blog/build-test-deploy-hugo-sites/)| Ricardo N Feliciano | 2017-05-31 |
+| [Hugo Easy Gallery - Automagical PhotoSwipe image gallery with a one-line shortcode](https://www.liwen.id.au/heg/)| Li-Wen Yip | 2017-03-25 |
+| [Hugo Tutorial: How to Build & Host a (Very Fast) Static E-Commerce Site](https://snipcart.com/blog/hugo-tutorial-static-site-ecommerce) | Snipcart | 2017-03-12 |
+| [Automagical image gallery in Hugo with PhotoSwipe and jQuery](https://www.liwen.id.au/photoswipe/)| Li-Wen Yip | 2017-03-04 |
+| [Adding Isso Comments to Hugo](https://stiobhart.net/2017-02-24-isso-comments/) | Stíobhart Matulevicz | 2017-02-24 |
+| [Zero to HTTP/2 with AWS and Hugo](https://habd.as/zero-to-http-2-aws-hugo/) | Josh Habdas | 2017-02-16 |
+| [How to Password Protect a Hugo Site](https://www.aerobatic.com/blog/password-protect-a-hugo-site/) | Aerobatic | 2017-02-19 |
+| [Switching from Wordpress to Hugo](http://schnuddelhuddel.de/switching-from-wordpress-to-hugo/) | Mario Martelli | 2017-02-19 | ]
+| [Deploy a Hugo site to Aerobatic with CircleCI ](https://www.aerobatic.com/blog/hugo-github-circleci/) | Aerobatic | 2017-02-14 |
+| [NPM scripts for building and deploying Hugo site](https://www.aerobatic.com/blog/hugo-npm-buildtool-setup/) | Aerobatic | 2017-02-12 |
+| [Getting started with Hugo and the plain-blog theme, on NearlyFreeSpeech.Net](https://www.penwatch.net/cms/get_started_plain_blog/) | Li-aung “Lewis” Yip | 2017-02-12 |
+| [Build a Hugo site using Cloud9 IDE and host on App Engine](https://loyall.ch/lab/2017/01/build-a-static-website-with-cloud9-hugo-and-app-engine/)| Pascal Aubort | 2017-02-05 |
+| [Hugo Continuous Deployment with Bitbucket Pipelines and Aerobatic](https://www.aerobatic.com/blog/hugo-bitbucket-pipelines/) | Aerobatic | 2017-02-04 |
+| [How to use Firebase to host a Hugo site](https://www.m0d3rnc0ad.com/post/static-site-firebase/) | Andrew Cuga | 2017-02-04 |
+| [A publishing workflow for teams using static site generators](https://www.keybits.net/post/publishing-workflow-for-teams-using-static-site-generators/) | Tom Atkins | 2017-01-02 |
+| [How To Dynamically Use Google Fonts In A Hugo Website](https://stoned.io/web-development/hugo/How-To-Dynamically-Use-Google-Fonts-In-A-Hugo-Website/) | Hash Borgir | 2016-10-27 |
+| [Embedding Facebook In A Hugo Template](https://stoned.io/web-development/hugo/Embedding-Facebook-In-A-Hugo-Template/) | Hash Borgir | 2016-10-22 |
+| [通过 Gitlab-cl 将 Hugo blog 自动部署至 GitHub](https://zetaoyang.github.io/post/2016/10/17/gitlab-cl.html) (Chinese, Continious integration) | Zetao Yang | 2016-10-17 |
+| [A Step-by-Step Guide: Hugo on Netlify](https://www.netlify.com/blog/2016/09/21/a-step-by-step-guide-hugo-on-netlify/) | Eli Williamson | 2016-09-21 |
+| [Building our site: From Django & Wordpress to a static generator (Part I)](https://tryolabs.com/blog/2016/09/20/building-our-site-django-wordpress-to-static-part-i/) | Alan Descoins | 2016-09-20 |
+| [Webseitenmaschine - Statische Websites mit Hugo erzeugen](http://www.heise.de/ct/ausgabe/2016-12-Statische-Websites-mit-Hugo-erzeugen-3211704.html) (German, $) | Christian Helmbold | 2016-05-27 |
+| [Cómo hacer sitios web estáticos con Hugo y Go - Platzi](https://www.youtube.com/watch?v=qaXXpdiCHXE) (Video tutorial) | Verónica López | 2016-04-06 |
+| [CDNOverview: A CDN comparison site made with Hugo](https://www.cloakfusion.com/cdnoverview-cdn-comparison-site-made-hugo/) | Thijs de Zoete | 2016-02-23 |
+| [Hugo: A Modern WebSite Engine That Just Works](https://github.com/shekhargulati/52-technologies-in-2016/blob/master/07-hugo/README.md) | Shekhar Gulati | 2016-02-14 |
+| [Minify Hugo Generated HTML](http://ratson.name/blog/minify-hugo-generated-html/) | Ratson | 2016-02-02 |
+| [HugoのデプロイをWerckerからCircle CIに変更した - log](http://log.deprode.net/logs/2016-01-17/) | Deprode | 2016-01-17 |
+| [Static site generators: el futuro de las webs estáticas (Hugo, Jekyll, Flask y otros)](http://sitelabs.es/static-site-generators-futuro-las-webs-estaticas/) | Eneko Sarasola | 2016-01-09 |
+| [Writing a Lambda Function for Hugo](https://blog.jolexa.net/post/writing-a-lambda-function-for-hugo/) | Jeremy Olexa | 2016-01-01 |
+| [Ein Blog mit Hugo erstellen - Tutorial](http://privat.albicker.org/tags/hugo.html) (Deutsch/German) | Bernhard Albicker | 2015-12-30 |
+| [How to host Hugo static website generator on AWS Lambda](http://bezdelev.com/post/hugo-aws-lambda-static-website/) | Ilya Bezdelev | 2015-12-15 |
+| [Migrating from Pelican to Hugo](http://www.softinio.com/post/migrating-from-pelican-to-hugo/) | Salar Rahmanian | 2015-11-29 |
+| [Static Website Generators Reviewed: Jekyll, Middleman, Roots, Hugo](http://www.smashingmagazine.com/2015/11/static-website-generators-jekyll-middleman-roots-hugo-review/) | Mathias Biilmann Christensen | 2015-11-16 |
+| [How To Deploy a Hugo Site to Production with Git Hooks on Ubuntu 14.04](https://www.digitalocean.com/community/tutorials/how-to-deploy-a-hugo-site-to-production-with-git-hooks-on-ubuntu-14-04) | Justin Ellingwood | 2015-11-12 |
+| [How To Install and Use Hugo, a Static Site Generator, on Ubuntu 14.04](https://www.digitalocean.com/community/tutorials/how-to-install-and-use-hugo-a-static-site-generator-on-ubuntu-14-04) | Justin Ellingwood | 2015-11-09 |
+| [Switching from Wordpress to Hugo](http://justinfx.com/2015/11/08/switching-from-wordpress-to-hugo/) | Justin Israel | 2015-11-08 |
+| [Hands-on Experience with Hugo as a Static Site Generator](http://usersnap.com/blog/hands-on-experience-with-hugo-static-site-generator/) | Thomas Peham | 2015 -10-15 |
+| [Statische Webseites mit Hugo erstellen/Vortrag mit Foliensatz (deutsch)](http://sfd.koelnerlinuxtreffen.de/2015/HaraldWeidner/) | Harald Weidner | 2015-09-19 |
+| [Moving from WordPress to Hugo](http://abhipandey.com/2015/09/moving-to-hugo/) | Abhishek Pandey | 2015-09-15 |
+| [通过webhook将Hugo自动部署至GitHub Pages和GitCafe Pages (Automated deployment)](http://blog.coderzh.com/2015/09/13/use-webhook-automated-deploy-hugo/) | CoderZh | 2015-09-13 |
+| [使用hugo搭建个人博客站点 (Using Hugo to build a personal blog site)](http://blog.coderzh.com/2015/08/29/hugo/) | CoderZh | 2015-08-29 |
+| [Good-Bye Wordpress, Hello Hugo!](http://blog.arminhanisch.de/2015/08/blog-migration-zu-hugo/) (German) | Armin Hanisch | 2015-08-18 |
+| [Générer votre site web statique avec Hugo (Generate your static site with Hugo)](http://www.linux-pratique.com/?p=191) | Benoît Benedetti | 2015-06-26 |
+| [Hugo向けの新しいテーマを作った (I created a new theme for Hugo)](https://yet.unresolved.xyz/blog/2016/10/03/how-to-make-of-hugo-theme/) | Daisuke Tsuji | 2015-06-20 |
+| [Hugo - Gerando um site com conteúdo estático. (Portuguese Brazil)](http://blog.ffrizzo.com/posts/hugo/) | Fabiano Frizzo | 2015-06-02 |
+| [An Introduction to Static Site Generators](http://davidwalsh.name/introduction-static-site-generators) | Eduardo Bouças | 2015-05-20 |
+| [Hugo Still Rules](http://cheekycoder.com/2015/05/hugo-still-rules/) | Cheeky Coder | 2015-05-18 |
+| [hugo - Static Site Generator](http://gscacco.github.io/post/hugo/) | G Scaccoio | 2015-05-04 |
+| [WindowsでHugoを使う](http://ureta.net/2015/05/hugo-on-windows/) | うれ太郎 | 2015-05-01 |
+| [Hugoのshortcodesを用いてサイトにスライドなどを埋め込む](http://blog.yucchiy.com/2015/04/29/hugo-shortcode/) | Yucchiy | 2015-04-29 |
+| [HugoとCircleCIでGitHub PagesにBlogを公開してみたら超簡単だった](http://hori-ryota.github.io/blog/create-blog-with-hugo-and-circleci/) | Hori Ryota | 2015-04-17 |
+| [10 Best Static Site Generators](http://beebom.com/2015/04/best-static-site-generators) | Aniruddha Mysore | 2015-04-06 |
+| [Goodbye WordPress; Hello Hugo](http://willwarren.com/2015/04/05/goodbye-wordpress-hello-hugo/) | Will Warren | 2015-04-05 |
+| [Static Websites with Hugo on Google Cloud Storage](http://www.moxie.io/post/static-websites-with-hugo-on-google-cloud-storage/) | Moxie Input/Output | 2015-04-02 |
+| [De nuevo iniciando un blog](https://alvarolizama.net/) | Alvaro Lizama | 2015-03-29 |
+| [We moved our blog from Posthaven to Hugo after only three posts. Why?](http://blog.hypriot.com/post/moved-from-posthaven-to-hugo/) | Hypriot | 2015-03-27 |
+| [Top Static Site Generators in 2015](http://superdevresources.com/static-site-generators-2015/) | Kanishk Kunal | 2015-03-12 |
+| [Moving to Hugo](http://abiosoft.com/moving-to-hugo/) | Abiola Ibrahim | 2015-03-08 |
+| [Migrating a blog (yes, this one!) from Wordpress to Hugo](http://justindunham.net/migrating-from-wordpress-to-hugo/) | Justin Dunham | 2015-02-13 |
+| [blogをoctopressからHugoに乗り換えたメモ](http://blog.jigyakkuma.org/2015/02/11/hugo/) | jigyakkuma | 2015-02-11 |
+| [Hugoでブログをつくった](http://porgy13.github.io/post/new-hugo-blog/) | porgy13 | 2015-02-07 |
+| [Hugoにブログを移行した](http://keichi.net/post/first/) | Keichi Takahashi | 2015-02-04 |
+| [Hugo静态网站生成器中文教程](http://nanshu.wang/post/2015-01-31/) | Nanshu Wang | 2015-01-31 |
+| [Hugo + GitHub Pages + Wercker CI = ¥0(無料) でコマンド 1 発(自動化)でサイト ・ブログを公開・運営・分析・収益化](http://qiita.com/yoheimuta/items/8a619cac356bed89a4c9) | Yohei Yoshimuta | 2015-01-31 |
+| [Running Hugo websites on anynines](http://blog.anynines.com/running-hugo-websites-on-anynines/) | Julian Weber | 2015-01-30 |
+| [MiddlemanからHugoへ移行した](http://re-dzine.net/2015/01/hugo/) | Haruki Konishi | 2015-01-21 |
+| [WordPress から Hugo に乗り換えました](http://rakuishi.com/archives/wordpress-to-hugo/) | rakuishi | 2015-01-20 |
+| [HUGOを使ってサイトを立ち上げる方法](http://qiita.com/syui/items/869538099551f24acbbf) | Syui | 2015-01-17 |
+| [Jekyllが許されるのは小学生までだよね](http://t32k.me/mol/log/hugo/) | Ishimoto Koji | 2015-01-16 |
+| [Getting started with Hugo](http://anthonyfok.org/post/getting-started-with-hugo/) | Anthony Fok | 2015-01-12 |
+| [把这个博客静态化了 (Migrate to Hugo)](http://lich-eng.com/2015/01/03/migrate-to-hugo/)| Li Cheng | 2015-01-03 |
+| [Porting my blog with Hugo](http://blog.srackham.com/posts/porting-my-blog-with-hugo/) | Stuart Rackham | 2014-12-30 |
+| [Hugoを使ってみたときのメモ](http://machortz.github.io/posts/usinghugo/) | Machortz | 2014-12-29 |
+| [OctopressからHugoへ移行した](http://deeeet.com/writing/2014/12/25/hugo/) | Taichi Nakashima | 2014-12-25 |
+| [Migrating to Hugo From Octopress](http://nathanleclaire.com/blog/2014/12/22/migrating-to-hugo-from-octopress/) | Nathan LeClaire | 2014-12-22 |
+| [Dynamic Pages with GoHugo.io](http://cyrillschumacher.com/2014/12/21/dynamic-pages-with-gohugo.io/) | Cyrill Schumacher | 2014-12-21 |
+| [6 Static Blog Generators That Aren’t Jekyll](http://www.sitepoint.com/6-static-blog-generators-arent-jekyll/) | David Turnbull | 2014-12-08 |
+| [Travel Blogging Setup](http://www.stou.dk/2014/11/travel-blogging-setup/) | Rasmus Stougaard | 2014-11-23 |
+| [Hosting A Hugo Website Behind Nginx](http://www.bigbeeconsultants.co.uk/blog/hosting-hugo-website-behind-nginx) | Rick Beton | 2014-11-20 |
+| [使用Hugo搭建免费个人Blog (How to use Hugo)](http://ulricqin.com/post/how-to-use-hugo/) | Ulric Qin 秦晓辉 | 2014-11-11 |
+| [Built in Speed and Built for Speed by Hugo](http://cheekycoder.com/2014/10/built-for-speed-by-hugo/) | Cheeky Coder | 2014-10-30 |
+| [Hugo para crear sitios web estáticos](http://www.webbizarro.com/noticias/1076/hugo-para-crear-sitios-web-estaticos/) | Web Bizarro | 2014-08-19 |
+| [Going with hugo](http://www.markuseliasson.se/article/going-with-hugo/) | Markus Eliasson | 2014-08-18 |
+| [Benchmarking Jekyll, Hugo and Wintersmith](http://fredrikloch.me/post/2014-08-12-Jekyll-and-its-alternatives-from-a-site-generation-point-of-view/) | Fredrik Loch | 2014-08-12 |
+| [Goodbye Octopress, Hello Hugo!](http://andreimihu.com/blog/2014/08/11/goodbye-octopress-hello-hugo/) | Andrei Mihu | 2014-08-11 |
+| [Beautiful sites for Open Source projects](http://beautifulopen.com/2014/08/09/hugo/) | Beautiful Open | 2014-08-09 |
+| [Hugo: Beyond the Defaults](http://npf.io/2014/08/hugo-beyond-the-defaults/) | Nate Finch | 2014-08-08 |
+| [First Impressions of Hugo](https://peteraba.com/blog/first-impressions-of-hugo/) | Peter Aba | 2014-06-06 |
+| [New Site Workflow](http://vurt.co.uk/post/new_website/) | Giles Paterson | 2014-08-05 |
+| [How I Learned to Stop Worrying and Love the (Static) Web](http://cognition.ca/post/about-hugo/) | Joshua McKenty | 2014-08-04 |
+| [Hugo - Static Site Generator](http://kenwoo.io/blog/hugo---static-site-generator/) | Kenny Woo | 2014-08-03 |
+| [Hugo Is Friggin' Awesome](http://npf.io/2014/08/hugo-is-awesome/) | Nate Finch | 2014-08-01 |
+| [再次搬家 (Move from WordPress to Hugo)](http://www.chingli.com/misc/move-from-wordpress-to-hugo/) | 青砾 (chingli) | 2014-07-12 |
+| [Embedding Gists in Hugo](http://danmux.com/posts/embedded_gists/) | Dan Mull | 2014-07-05 |
+| [An Introduction To Hugo](http://www.cirrushosting.com/web-hosting-blog/an-introduction-to-hugo/) | Dan Silber | 2014-07-01 |
+| [Moving to Hugo](http://danmux.com/posts/hugo_based_blog/) | Dan Mull | 2014-05-29 |
+| [开源之静态站点生成器排行榜 (Leaderboard of open-source static website generators)](http://code.csdn.net/news/2819909) | CSDN.net | 2014-05-23 |
+| [Finally, a satisfying and effective blog setup](http://michaelwhatcott.com/now-powered-by-hugo/) | Michael Whatcott | 2014-05-20 |
+| [Hugo from scratch](http://zackofalltrades.com/notes/2014/05/hugo-from-scratch/) | Zack Williams | 2014-05-18 |
+| [Why I switched away from Jekyll](http://www.jakejanuzelli.com/why-I-switched-away-from-jekyll/) | Jake Januzelli | 2014-05-10 |
+| [Welcome our new blog](http://blog.ninya.io/posts/welcome-our-new-blog/) | Ninya.io | 2014-04-11 |
+| [Mission Not Accomplished](http://johnsto.co.uk/blog/mission-not-accomplished/) | Dave Johnston | 2014-04-03 |
+| [Hugo - A Static Site Builder in Go](http://deepfriedcode.com/post/hugo/) | Deep Fried Code | 2014-03-30 |
+| [Adventures in Angular Podcast](http://devchat.tv/adventures-in-angular/003-aia-gdes) | Matias Niemela | 2014-03-28 |
+| [Hugo](http://bra.am/post/hugo/) | bra.am | 2014-03-23 |
+| [Converting Blogger To Markdown](http://trishagee.github.io/project/atom-to-hugo/) | Trisha Gee | 2014-03-20 |
+| [Moving to Hugo Static Web Pages](http://tepid.org/tech/hugo-web/) | Tobias Weingartner | 2014-03-16 |
+| [New Blog Engine: Hugo](https://blog.afoolishmanifesto.com/posts/hugo/) | fREW Schmidt | 2014-03-15 |
+| [Hugo + gulp.js = Huggle](http://ktmud.github.io/huggle/en/intro/) ([English](http://ktmud.github.io/huggle/en/intro/), [中文](http://ktmud.github.io/huggle/zh/intro/)) | Jesse Yang 杨建超 | 2014-03-08 |
+| [Powered by Hugo](http://kieranhealy.org/blog/archives/2014/02/24/powered-by-hugo/) | Kieran Healy | 2014-02-24 |
+| [静的サイトを素早く構築するために GoLangで作られたジェネレータHugo](http://hamasyou.com/blog/2014/02/21/hugo/)|
Shogo Hamada 濱田章吾
| 2014-02-21 |
+| [Latest Roundup of Useful Tools For Developers](http://codegeekz.com/latest-roundup-of-useful-tools-for-developers/) | CodeGeekz | 2014-02-13 |
+| [Hugo: Static Site Generator written in Go](http://www.braveterry.com/2014/02/06/hugo-static-site-generator-written-in-go/) | Brave Terry | 2014-02-06 |
+| [10 Useful HTML5 Tools for Web Designers and Developers](http://designdizzy.com/10-useful-html5-tools-for-web-designers-and-developers/) | Design Dizzy | 2014-02-04 |
+| [Hugo – Fast, Flexible Static Site Generator](http://cube3x.com/hugo-fast-flexible-static-site-generator/) | Joby Joseph | 2014-01-18 |
+| [Hugo: A new way to build static website](http://www.w3update.com/opensource/hugo-a-new-way-to-build-static-website.html) | w3update | 2014-01-17 |
+| [Xaprb now uses Hugo](http://xaprb.com/blog/2014/01/15/using-hugo/) | Baron Schwartz | 2014-01-15 |
+| [New jQuery Plugins And Resources That Web Designers Need](http://www.designyourway.net/blog/resources/new-jquery-plugins-and-resources-that-web-designers-need/) | Design Your Way | 2014-01-01 |
+| [On Blog Construction](http://alexla.sh/post/on-blog-construction/) | Alexander Lash | 2013-12-27 |
+| [Hugo](http://onethingwell.org/post/69070926608/hugo) | One Thing Well | 2013-12-05 |
+| [In Praise Of Hugo](http://sound-guru.com/blog/post/hello-world/) | sound-guru.com | 2013-10-19 |
+| [Hosting a blog on S3 and Cloudfront](http://www.danesparza.net/2013/07/hosting-a-blog-on-s3-and-cloudfront/) | Dan Esparza | 2013-07-24 |
diff --git a/docs/content/content/archetypes.md b/docs/content/content/archetypes.md
new file mode 100644
index 000000000..88efdbd0f
--- /dev/null
+++ b/docs/content/content/archetypes.md
@@ -0,0 +1,330 @@
+---
+lastmod: 2016-10-01
+date: 2014-05-14T02:13:50Z
+menu:
+ main:
+ parent: content
+next: /content/ordering
+prev: /content/types
+title: Archetypes
+weight: 50
+toc: true
+---
+
+Typically, each piece of content you create within a Hugo project will have [front matter](/content/front-matter/) that follows a consistent structure. If you write blog posts, for instance, you might use the following front matter for the vast majority of those posts:
+
+```toml
++++
+title = ""
+date = ""
+slug = ""
+tags = [
+ ""
+]
+categories = [
+ ""
+]
+draft = true
++++
+```
+
+You can always add non-typical front matter to any piece of content, but since it takes extra work to develop a theme that handles unique metadata, consistency is simpler.
+
+With this in mind, Hugo has a convenient feature known as *archetypes* that allows users to define default front matter for new pieces of content.
+
+By using archetypes, we can:
+
+1. **Save time**. Stop writing the same front matter over and over again.
+2. **Avoid errors**. Reduce the odds of typos, improperly formatted syntax, and other simple mistakes.
+3. **Focus on more important things**. Avoid having to remember all of the fields that need to be associated with each piece of content. (This is particularly important for larger projects with complex front matter and a variety of content types.)
+
+Let's explore how they work.
+
+## Built-in Archetypes
+
+If you've been using Hugo for a while, there's a decent chance you've come across archetypes without even realizing it. This is because Hugo includes a basic, built-in archetype that is used by default whenever it generates a content file.
+
+To see this in action, open the command line, navigate into your project's directory, and run the following command:
+
+```bash
+hugo new hello-world.md
+```
+
+This `hugo new` command creates a new content file inside the project's `content` directory — in this case, a file named `hello-world.md` — and if you open this file, you'll notice it contains the following front matter:
+
+```toml
++++
+date = "2017-05-31T15:18:11+10:00"
+draft = true
+title = "hello world"
++++
+```
+
+Here, we can see that three fields have been added to the document: a `title` field that is based on the file name we defined, a `draft` field that ensures this content won't be published by default, and a `date` field that is auto-populated with the current date and time in the [RFC 3339](https://stackoverflow.com/questions/522251/whats-the-difference-between-iso-8601-and-rfc-3339-date-formats) format.
+
+This, in its most basic form, is an example of an archetype. To understand how useful they can be though, it's best if we create our own.
+
+## Creating Archetypes
+
+In this section, we're going to create an archetype that will override the built-in archetype, allowing us to define custom front matter that will be included in any content files that we generate with the `hugo new` command.
+
+To achieve this, create a file named `default.md` inside the `archetypes` folder of a Hugo project. (If the folder doesn't exist, create it.)
+
+Then, inside this file, define the following front matter:
+
+```toml
++++
+slug = ""
+tags = []
+categories = []
+draft = true
++++
+```
+
+You'll notice that we haven't defined a `title` or `date` field. This is because Hugo will automatically add these fields to the beginning of the front matter. We do, however, need to define the `draft` field if we want it to exist in our front matter.
+
+You'll also notice that we're writing the front matter in the TOML format. It's possible to define archetype front matter in other formats, but a setting needs to be changed in the configuration file for this to be possible. See the "[Archetype Formats](#archetype-formats)" section of this article for more details.
+
+Next, run the following command:
+
+```bash
+hugo new my-archetype-example.md
+```
+
+This command will generate a file named `my-archetype-example.md` inside the `content` directory, and this file will contain the following output:
+
+```toml
++++
+categories = []
+date = "2017-05-31T15:21:13+10:00"
+draft = true
+slug = ""
+tags = []
+title = "my archetype example"
++++
+```
+
+As we can see, the file contains the `title` and `date` property that Hugo created for us, along with the front matter that we defined in the `archetypes/default.md` file.
+
+You'll also notice that the fields have been sorted into alphabetical order. This is an unintentional side-effect that stems from the underlying code libraries that Hugo relies upon. It is, however, [a known issue that is actively being discussed](https://github.com/gohugoio/hugo/issues/452).
+
+## Section Archetypes
+
+By creating the `archetypes/default.md` file, we've created a default archetype that is more useful than the built-in archetype, but since Hugo encourages us to [organize our content into sections](/content/sections/), each of which will likely have different front matter requirements, a "one-size-fits-all" archetype isn't necessarily the best approach.
+
+To accommodate for this, Hugo allows us to create archetypes for each section of our project. This means, whenever we generate content for a certain section, the appropriate front matter for that section will be automatically included in the generated file.
+
+To see this in action, create a "photo" section by creating a directory named "photo" inside the `content` directory.
+
+Then create a file named `photo.md` inside the `archetypes` directory and include the following front matter inside this file:
+
+```toml
++++
+image_url = ""
+camera = ""
+lens = ""
+aperture = ""
+iso = ""
+draft = true
++++
+```
+
+Here, the critical detail is that the `photo.md` file in the `archetypes` directory is named after the `photo` section that we just created. By sharing a name, Hugo can understand that there's a relationship between them.
+
+Next, run the following command:
+
+```bash
+hugo new photo/my-pretty-cat.md
+```
+
+This command will generate a file named `my-pretty-cat.md` inside the `content/photo` directory, and this file will contain the following output:
+
+```toml
++++
+aperture = ""
+camera = ""
+date = "2017-05-31T15:25:18+10:00"
+draft = true
+image_url = ""
+iso = ""
+lens = ""
+title = "my pretty cat"
++++
+```
+
+As we can see, the `title` and `date` fields are still included by Hugo, but the rest of the front matter is being generated from the `photo.md` archetype instead of the `default.md` archetype.
+
+### Tip: Default Values
+
+To make archetypes more useful, define default values for any fields that will always be set to a range of limited options. In the case of the `photo.md` archetype, for instance, you could include lists of the various cameras and lenses that you own:
+
+```toml
++++
+image_url = ""
+camera = [
+ "Sony RX100 Mark IV",
+ "Canon 5D Mark III",
+ "iPhone 6S"
+]
+lens = [
+ "Canon EF 50mm f/1.8",
+ "Rokinon 14mm f/2.8"
+]
+aperture = ""
+iso = ""
+draft = true
++++
+```
+
+Then, after generating a content file, simply remove the values that aren't relevant. This saves you from typing out the same options over and over again while ensuring consistency in how they're written.
+
+## Scaffolding Content
+
+Archetypes aren't limited to defining default front matter. They can also be used to define a default structure for the body of Markdown documents.
+
+For example, imagine creating a `review.md` archetype for the purpose of writing camera reviews. This is what the front matter for such an archetype might look like:
+
+```toml
++++
+manufacturer = ""
+model = ""
+price = ""
+releaseDate = ""
+rating = ""
++++
+```
+
+But reviews tend to follow strict formats and need to answer specific questions, and it's with these expectations of precise structure that archetypes can prove to be even more useful.
+
+For the sake of writing reviews, for instance, we could define the structure of a review beneath the front matter of the `review.md` file:
+
+```markdown
++++
+manufacturer = ""
+model = ""
+price = ""
+releaseDate = ""
+rating = ""
++++
+
+## Introduction
+
+## Sample Photos
+
+## Conclusion
+```
+
+Then, whenever we use the `hugo new` command to create a new review, not only will the default front matter be copied into the newly created Markdown document, but the body of the `review.md` archetype will also be copied.
+
+To take this further though — and to ensure authors on multi-author websites are on the same page about how content should be written — we could include notes and reminders within the archetype:
+
+```markdown
++++
+manufacturer = ""
+model = ""
+price = ""
+releaseDate = ""
+rating = ""
++++
+
+## Introduction
+
+
+
+## Sample Photos
+
+
+
+## Conclusion
+
+
+
+```
+
+That way, each time we generate a new content file, we have a series of handy notes to push us closer to a piece of writing that's suitable for publishing.
+
+(If you're wondering why the notes are wrapped in the HTML comment syntax, it's to ensure they won't appear inside the preview window of whatever Markdown editor the author happens to be using. They're not strictly necessary though.)
+
+This is still a fairly simple example, but if your content usually contains a variety of components — headings, bullet-points, images, [short-codes](/extras/shortcodes/), etc — it's not hard to see the time-saving benefits of placing these components in the body of an archetype file.
+
+## Theme Archetypes
+
+Whenever you generate a content file with the `hugo new` command, Hugo will start by searching for archetypes in the `archetypes` directory, initially looking for an archetype that matches the content's section and falling-back on the `default.md` archetype (if one is present). If no archetypes are found in this directory, Hugo will continue its search in the `archetypes` directory of the currently active theme. In other words, it's possible for themes to come packaged with their own archetypes, ensuring that users of that theme format their content files with correctly structured front matter.
+
+To allow Hugo to use archetypes from a theme, [that theme must be activated via the project's configuration file](/themes/usage/):
+
+```toml
+theme = "ThemeNameGoesHere"
+```
+
+If an archetype doesn't exist in the `archetypes` directory at the top-level of a project or inside the `archetypes` directory of an active theme, the built-in archetype will be used.
+
+{{< figure src="/img/content/archetypes/archetype-hierarchy.png" alt="How Hugo Decides Which Archetype To Use" >}}
+
+## Archetype Formats
+
+By default, the `hugo new` command will generate front matter in the TOML format. This means, even if we define the front matter in our archetype files as YAML or JSON, it will be converted to the TOML format before it ends up in our content files.
+
+Fortunately, this functionality can be overwritten.
+
+Inside the project's configuration file, simply define a `metaDataFormat` property:
+
+```toml
+metaDataFormat = ""
+```
+
+Then set this property to any of the following values:
+
+* toml
+* yaml
+* json
+
+By defining this option, any front matter will be generated in your preferred format.
+
+It's worth noting, however, that when generating front matter in the TOML format, you might encounter the following error:
+
+```bash
+Error: cannot convert type to TomlTree
+```
+
+This is because, to generate TOML, all of the fields in the front matter need to have a default value, even if that default value is just an empty string.
+
+For example, this YAML would *not* successfully compile into the TOML format:
+
+```yaml
+---
+slug:
+tags:
+categories:
+draft:
+---
+```
+
+But this YAML *would* successfully compile:
+
+```yaml
+---
+slug: ""
+tags:
+ -
+categories:
+ -
+draft: true
+---
+```
+
+It's a subtle yet important detail to remember.
+
+## Notes
+
+* Prior to Hugo v0.13, some users received [an "EOF" error when using archetypes](https://github.com/gohugoio/hugo/issues/776), related to what text editor they used to create the archetype. As of Hugo v0.13, this error has been [resolved](https://github.com/gohugoio/hugo/pull/785).
diff --git a/docs/content/content/example.md b/docs/content/content/example.md
new file mode 100644
index 000000000..022764994
--- /dev/null
+++ b/docs/content/content/example.md
@@ -0,0 +1,91 @@
+---
+aliases:
+- /doc/example/
+lastmod: 2015-12-23
+date: 2013-07-01
+linktitle: Example
+menu:
+ main:
+ parent: content
+prev: /content/multilingual
+next: /content/using-index-md
+notoc: true
+title: Example Content File
+weight: 70
+---
+
+Some things are better shown than explained. The following is a very basic example of a content file written in [Markdown](https://help.github.com/articles/github-flavored-markdown/):
+
+**mysite/content/project/nitro.md → http://mysite.com/project/nitro.html**
+
+With TOML front matter:
+
+
+++
+date = "2013-06-21T11:27:27-04:00"
+title = "Nitro: A quick and simple profiler for Go"
+description = "Nitro is a simple profiler for your Golang applications"
+tags = [ "Development", "Go", "profiling" ]
+topics = [ "Development", "Go" ]
+slug = "nitro"
+project_url = "https://github.com/spf13/nitro"
++++
+# Nitro
+
+Quick and easy performance analyzer library for [Go](http://golang.org/).
+
+## Overview
+
+Nitro is a quick and easy performance analyzer library for Go.
+It is useful for comparing A/B against different drafts of functions
+or different functions.
+
+## Implementing Nitro
+
+Using Nitro is simple. First, use `go get` to install the latest version
+of the library.
+
+ $ go get github.com/spf13/nitro
+
+Next, include nitro in your application.
+
+
+You may also use the equivalent YAML front matter:
+
+```yaml
+---
+lastmod: 2015-12-23
+date: "2013-06-21T11:27:27-04:00"
+title: "Nitro: A quick and simple profiler for Go"
+description: "Nitro is a simple profiler for your Go lang applications"
+tags: [ "Development", "Go", "profiling" ]
+topics: [ "Development", "Go" ]
+slug: "nitro"
+project_url: "https://github.com/spf13/nitro"
+---
+```
+
+`nitro.md` would be rendered as follows:
+
+> # Nitro
+>
+> Quick and easy performance analyzer library for [Go](http://golang.org/).
+>
+> ## Overview
+>
+> Nitro is a quick and easy performance analyzer library for Go.
+> It is useful for comparing A/B against different drafts of functions
+> or different functions.
+>
+> ## Implementing Nitro
+>
+> Using Nitro is simple. First, use `go get` to install the latest version
+> of the library.
+>
+> $ go get github.com/spf13/nitro
+>
+> Next, include nitro in your application.
+
+The source `nitro.md` file is converted to HTML by the excellent
+[Blackfriday](https://github.com/russross/blackfriday) Markdown processor,
+which supports extended features found in the popular
+[GitHub Flavored Markdown](https://help.github.com/articles/github-flavored-markdown/).
diff --git a/docs/content/content/front-matter.md b/docs/content/content/front-matter.md
new file mode 100644
index 000000000..e61a48f55
--- /dev/null
+++ b/docs/content/content/front-matter.md
@@ -0,0 +1,119 @@
+---
+aliases:
+- /doc/front-matter/
+lastmod: 2015-12-23
+date: 2013-07-01
+menu:
+ main:
+ parent: content
+next: /content/sections
+prev: /content/organization
+title: Front Matter
+weight: 20
+toc: true
+---
+
+The **front matter** is one of the features that gives Hugo its strength. It enables
+you to include the meta data of the content right with it. Hugo supports a few
+different formats, each with their own identifying tokens.
+
+Supported formats:
+
+ * **[TOML][]**, identified by '`+++`'.
+ * **[YAML][]**, identified by '`---`'.
+ * **[JSON][]**, a single JSON object which is surrounded by '`{`' and '`}`', followed by a newline.
+
+[TOML]: https://github.com/toml-lang/toml "Tom's Obvious, Minimal Language"
+[YAML]: http://www.yaml.org/ "YAML Ain't Markup Language"
+[JSON]: http://www.json.org/ "JavaScript Object Notation"
+
+## TOML Example
+
+
+++
+title = "spf13-vim 3.0 release and new website"
+description = "spf13-vim is a cross platform distribution of vim plugins and resources for Vim."
+tags = [ ".vimrc", "plugins", "spf13-vim", "vim" ]
+date = "2012-04-06"
+categories = [
+ "Development",
+ "VIM"
+]
+slug = "spf13-vim-3-0-release-and-new-website"
++++
+Content of the file goes Here
+
+
+## YAML Example
+
+```yaml
+---
+title: "spf13-vim 3.0 release and new website"
+description: "spf13-vim is a cross platform distribution of vim plugins and resources for Vim."
+tags: [ ".vimrc", "plugins", "spf13-vim", "vim" ]
+lastmod: 2015-12-23
+date: "2012-04-06"
+categories:
+ - "Development"
+ - "VIM"
+slug: "spf13-vim-3-0-release-and-new-website"
+---
+
+Content of the file goes Here
+```
+
+## JSON Example
+
+```json
+{
+ "title": "spf13-vim 3.0 release and new website",
+ "description": "spf13-vim is a cross platform distribution of vim plugins and resources for Vim.",
+ "tags": [ ".vimrc", "plugins", "spf13-vim", "vim" ],
+ "date": "2012-04-06",
+ "categories": [
+ "Development",
+ "VIM"
+ ],
+ "slug": "spf13-vim-3-0-release-and-new-website"
+}
+
+Content of the file goes Here
+```
+
+## Variables
+
+There are a few predefined variables that Hugo is aware of and utilizes. The user can also create
+any variable they want. These will be placed into the `.Params` variable available to the templates.
+Field names are always normalized to lowercase (e.g. `camelCase: true` is available as `.Params.camelcase`).
+
+### Required variables
+
+* **title** The title for the content
+* **description** The description for the content
+* **date** The date the content will be sorted by
+* **taxonomies** These will use the field name of the plural form of the index (see tags and categories above)
+
+### Optional variables
+
+* **aliases** An array of one or more aliases
+ (e.g. old published path of a renamed content)
+ that would be created to redirect to this content.
+ See [Aliases]({{< relref "extras/aliases.md" >}}) for details.
+* **draft** If true, the content will not be rendered unless `hugo` is called with `--buildDrafts`
+* **publishdate** If in the future, content will not be rendered unless `hugo` is called with `--buildFuture`
+* **expirydate** Content already expired will not be rendered unless `hugo` is called with `--buildExpired`
+* **type** The type of the content (will be derived from the directory automatically if unset)
+* **isCJKLanguage** If true, explicitly treat the content as CJKLanguage (`.Summary` and `.WordCount` can work properly in CJKLanguage)
+* **weight** Used for sorting
+* **markup** *(Experimental)* Specify `"rst"` for reStructuredText (requires
+ `rst2html`) or `"md"` (default) for Markdown
+* **slug** appears as tail of the url. It can be used to change the part of the url that is based on the filename.
+* **url** The full path to the content from the web root. It makes no assumptions about the path of the content file. It also ignores any language prefixes of the multilingual feature.
+
+*If neither `slug` or `url` is present, the filename will be used.*
+
+## Configure Blackfriday rendering
+
+It's possible to set some options for Markdown rendering in the page's front matter as an override to the site wide configuration.
+
+See [Configuration]({{< ref "overview/configuration.md#configure-blackfriday-rendering" >}}) for more.
+
diff --git a/docs/content/content/markdown-extras.md b/docs/content/content/markdown-extras.md
new file mode 100644
index 000000000..7673c53da
--- /dev/null
+++ b/docs/content/content/markdown-extras.md
@@ -0,0 +1,49 @@
+---
+aliases:
+- /doc/supported-formats/
+lastmod: 2016-07-22
+date: 2016-07-22
+menu:
+ main:
+ parent: content
+prev: /content/summaries
+next: /content/multilingual
+title: Markdown Extras
+weight: 66
+toc: false
+---
+
+Hugo provides some convenient markdown extensions.
+
+## Task lists
+
+Hugo supports GitHub styled task lists (TODO lists) for the Blackfriday renderer (md-files). See [Blackfriday config](/overview/configuration/#configure-blackfriday-rendering) for how to turn it off.
+
+Example:
+
+```markdown
+- [ ] a task list item
+- [ ] list syntax required
+- [ ] incomplete
+- [x] completed
+```
+
+Renders as:
+
+- [ ] a task list item
+- [ ] list syntax required
+- [ ] incomplete
+- [x] completed
+
+
+And produces this HTML:
+
+```html
+
+
+
a task list item
+
list syntax required
+
incomplete
+
completed
+
+```
diff --git a/docs/content/content/multilingual.md b/docs/content/content/multilingual.md
new file mode 100644
index 000000000..5e09bc539
--- /dev/null
+++ b/docs/content/content/multilingual.md
@@ -0,0 +1,232 @@
+---
+date: 2016-01-02T21:21:00Z
+menu:
+ main:
+ parent: content
+prev: /content/markdown-extras
+next: /content/example
+title: Multilingual Mode
+weight: 68
+toc: true
+---
+Hugo supports multiple languages side-by-side (added in `Hugo 0.17`). Define the available languages in a `Languages` section in your top-level `config.toml` (or equivalent).
+
+Example:
+
+```
+DefaultContentLanguage = "en"
+copyright = "Everything is mine"
+
+[params.navigation]
+help = "Help"
+
+[Languages]
+[Languages.en]
+title = "My blog"
+weight = 1
+[Languages.en.params]
+linkedin = "english-link"
+
+[Languages.fr]
+copyright = "Tout est à moi"
+title = "Mon blog"
+weight = 2
+[Languages.fr.params]
+linkedin = "lien-francais"
+[Languages.fr.navigation]
+help = "Aide"
+
+```
+
+Anything not defined in a `[Languages]` block will fall back to the global
+value for that key (like `copyright` for the English (`en`) language in this example).
+
+With the config above, all content, sitemap, RSS feeds, paginations
+and taxonomy pages will be rendered below `/` in English (your default content language), and below `/fr` in French.
+
+When working with params in frontmatter pages, omit the `params` in the key for the translation.
+
+If you want all of the languages to be put below their respective language code, enable `defaultContentLanguageInSubdir: true` in your configuration.
+
+Only the obvious non-global options can be overridden per language. Examples of global options are `BaseURL`, `BuildDrafts`, etc.
+
+Taxonomies and Blackfriday configuration can also be set per language, example:
+
+```
+[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"
+```
+
+
+### Translating your content
+
+Translated articles are identified by the name of the content file.
+
+Example of translated articles:
+
+1. `/content/about.en.md`
+2. `/content/about.fr.md`
+
+You can also have:
+
+1. `/content/about.md`
+2. `/content/about.fr.md`
+
+In which case the config variable `defaultContentLanguage` will be used to affect the default language `about.md`. This way, you can
+slowly start to translate your current content without having to rename everything.
+
+If left unspecified, the value for `defaultContentLanguage` defaults to `en`.
+
+By having the same _base file name_, the content pieces are linked together as translated pieces.
+
+If you need distinct URLs per language you can set the slug in the non-default language file. Just define the custom slug for the french translation in your `/content/about.fr.md` file:
+
+```
+---
+slug: "a-propos"
+---
+```
+
+You will get both `/about/` and `/a-propos/` URLs in your build, properly linked as translated pieces.
+
+### Link to translated content
+
+To create a list of links to translated content, use a template similar to this:
+
+```
+{{ if .IsTranslated }}
+
+{{ end }}
+```
+The above can be put in a `partial` and included in any template, be it for a content page or the home page. It will not print anything if there are no translations for a given page, or if it is -- in the case of the home page, section listing etc. -- a site with only one language.
+
+The above also uses the `i18n` func, see [Translation of strings](#translation-of-strings).
+
+### Translation of strings
+
+Hugo uses [go-i18n](https://github.com/nicksnyder/go-i18n) to support string translations. Follow the link to find tools to manage your translation workflows.
+
+Translations are collected from the `themes/[name]/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.
+
+From within your templates, use the `i18n` function like this:
+
+```
+{{ i18n "home" }}
+```
+
+This uses a definition like this one in `i18n/en-US.toml`:
+
+```
+[home]
+other = "Home"
+```
+
+Often you will want to use to the page variables in the translations strings. To do that, pass on the "." context when calling `i18n`:
+
+```
+{{ i18n "wordCount" . }}
+```
+
+This uses a definition like this one in `i18n/en-US.toml`:
+
+```
+[wordCount]
+other = "This article has {{ .WordCount }} words."
+```
+An example of singular and plural form:
+
+```
+[readingTime]
+one = "One minute read"
+other = "{{.Count}} minutes read"
+```
+And then in the template:
+
+```
+{{ i18n "readingTime" .ReadingTime }}
+```
+To track down missing translation strings, run Hugo with the `--i18n-warnings` flag:
+
+```bash
+ hugo --i18n-warnings | grep i18n
+i18n|MISSING_TRANSLATION|en|wordCount
+```
+
+### Menus
+
+You can define your menus for each language independently. The [creation of a menu]({{< relref "extras/menus.md" >}}) works analogous to earlier versions of Hugo, except that they have to be defined in their language-specific block in the configuration file:
+
+```toml
+defaultContentLanguage = "en"
+
+[languages.en]
+weight = 0
+languageName = "English"
+
+[[languages.en.menu.main]]
+url = "/"
+name = "Home"
+weight = 0
+
+
+[languages.de]
+weight = 10
+languageName = "Deutsch"
+
+[[languages.de.menu.main]]
+url = "/"
+name = "Startseite"
+weight = 0
+```
+
+The rendering of the main navigation works as usual. `.Site.Menus` will just contain the menu of the current language. Pay attention to the generation of the menu links. `absLangURL` takes care that you link to the correct locale of your website. Otherwise, both menu entries would link to the English version because it's the default content language that resides in the root directory.
+
+```html
+
+
+```
+
+### 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 site, it can be handy to have a visual indicator of missing translations. The `EnableMissingTranslationPlaceholders` config option will flag all untranslated strings with the placeholder `[i18n] identifier`, where `identifier` is the id of the missing translation.
+
+**Remember: Hugo will generate your website with these placeholders. It might not be suited for production environments.**
+
+### Multilingual Themes support
+
+To support Multilingual mode in your themes, some considerations must be taken for the URLs in the templates. If there are more than one language, URLs must either come from the built-in `.Permalink` or `.URL`, be constructed with `relLangURL` or `absLangURL` template funcs -- or prefixed with `{{.LanguagePrefix }}`.
+
+If there are 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, so it is harmless for single-language sites.
diff --git a/docs/content/content/ordering.md b/docs/content/content/ordering.md
new file mode 100644
index 000000000..d0933a608
--- /dev/null
+++ b/docs/content/content/ordering.md
@@ -0,0 +1,41 @@
+---
+lastmod: 2015-12-23
+date: 2014-03-06
+linktitle: Ordering
+menu:
+ main:
+ parent: content
+next: /content/summaries
+prev: /content/archetypes
+title: Ordering Content
+weight: 60
+---
+
+Hugo provides you with all the flexibility you need to organize how your content is ordered.
+
+By default, content is ordered by weight, then by date with the most
+recent date first, but alternative sorting (by `title` and `linktitle`) is
+also available. The order the content would appear is specified in
+the [list template](/templates/list/).
+
+_Both the `date` and `weight` fields are optional._
+
+Unweighted pages appear at the end of the list. If no weights are provided (or
+if weights are the same), `date` will be used to sort. If neither is provided,
+content will be ordered based on how it's read off the disk, and no order is
+guaranteed.
+
+## Assigning weight to content
+
+```toml
++++
+weight = 4
+title = "Three"
+date = "2012-04-06"
++++
+Front Matter with Ordered Pages 3
+```
+
+## Ordering Content Within Taxonomies
+
+Please see the [Taxonomy Ordering Documentation](/taxonomies/ordering/).
diff --git a/docs/content/content/organization.md b/docs/content/content/organization.md
new file mode 100644
index 000000000..bd83fbf04
--- /dev/null
+++ b/docs/content/content/organization.md
@@ -0,0 +1,175 @@
+---
+aliases:
+- /doc/organization/
+lastmod: 2015-09-27
+date: 2013-07-01
+linktitle: Organization
+menu:
+ main:
+ parent: content
+next: /content/supported-formats
+prev: /overview/source-directory
+title: Content Organization
+weight: 10
+toc: true
+---
+
+Hugo uses files (see [supported formats](/content/supported-formats/)) with headers commonly called the *front matter*. Hugo
+respects the organization that you provide for your content to minimize any
+extra configuration, though this can be overridden by additional configuration
+in the front matter.
+
+## Organization
+
+In Hugo, the content should be arranged in the same way they are intended for
+the rendered website. Without any additional configuration, the following will
+just work. Hugo supports content nested at any level. The top level is special
+in Hugo and is used as the [section](/content/sections/).
+
+ .
+ └── content
+ └── about
+ | └── _index.md // <- http://1.com/about/
+ ├── post
+ | ├── firstpost.md // <- http://1.com/post/firstpost/
+ | ├── happy
+ | | └── ness.md // <- http://1.com/post/happy/ness/
+ | └── secondpost.md // <- http://1.com/post/secondpost/
+ └── quote
+ ├── first.md // <- http://1.com/quote/first/
+ └── second.md // <- http://1.com/quote/second/
+
+**Here's the same organization run with `hugo --uglyURLs`**
+
+ .
+ └── content
+ └── about
+ | └── _index.md // <- http://1.com/about/
+ ├── post
+ | ├── firstpost.md // <- http://1.com/post/firstpost.html
+ | ├── happy
+ | | └── ness.md // <- http://1.com/post/happy/ness.html
+ | └── secondpost.md // <- http://1.com/post/secondpost.html
+ └── quote
+ ├── first.md // <- http://1.com/quote/first.html
+ └── second.md // <- http://1.com/quote/second.html
+
+## Destinations
+
+Hugo believes that you organize your content with a purpose. The same structure
+that works to organize your source content is used to organize the rendered
+site. As displayed above, the organization of the source content will be
+mirrored in the destination.
+
+Notice that the first level `about/` page URL was created using a directory
+named "about" with a single `_index.md` file inside. Find out more about `_index.md` specifically in [content for the homepage and other list pages](https://gohugo.io/overview/source-directory#content-for-home-page-and-other-list-pages).
+
+There are times when one would need more control over their content. In these
+cases, there are a variety of things that can be specified in the front matter
+to determine the destination of a specific piece of content.
+
+The following items are defined in order; latter items in the list will override
+earlier settings.
+
+### filename
+This isn't in the front matter, but is the actual name of the file minus the
+extension. This will be the name of the file in the destination.
+
+### slug
+Defined in the front matter, the `slug` can take the place of the filename for the
+destination.
+
+### filepath
+The actual path to the file on disk. Destination will create the destination
+with the same path. Includes [section](/content/sections/).
+
+### section
+`section` is determined by its location on disk and *cannot* be specified in the front matter. See [section](/content/sections/).
+
+### type
+`type` is also determined by its location on disk but, unlike `section`, it *can* be specified in the front matter. See [type](/content/types/).
+
+### path
+`path` can be provided in the front matter. This will replace the actual
+path to the file on disk. Destination will create the destination with the same
+path. Includes [section](/content/sections/).
+
+### 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 "/").
+When a `url` is provided, it will be used exactly. Using `url` will ignore the
+`--uglyURLs` setting.
+
+
+## Path breakdown in Hugo
+
+### Content
+
+ . path slug
+ . ⊢-------^----⊣ ⊢------^-------⊣
+ content/extras/indexes/category-example/index.html
+
+
+ . section slug
+ . ⊢--^--⊣ ⊢------^-------⊣
+ content/extras/indexes/category-example/index.html
+
+
+ . section slug
+ . ⊢--^--⊣⊢--^--⊣
+ content/extras/indexes/index.html
+
+### Destination
+
+
+ permalink
+ ⊢--------------^-------------⊣
+ http://spf13.com/projects/hugo
+
+
+ baseURL section slug
+ ⊢-----^--------⊣ ⊢--^---⊣ ⊢-^⊣
+ http://spf13.com/projects/hugo
+
+
+ baseURL section slug
+ ⊢-----^--------⊣ ⊢--^--⊣ ⊢--^--⊣
+ http://spf13.com/extras/indexes/example
+
+
+ baseURL path slug
+ ⊢-----^--------⊣ ⊢------^-----⊣ ⊢--^--⊣
+ http://spf13.com/extras/indexes/example
+
+
+ baseURL url
+ ⊢-----^--------⊣ ⊢-----^-----⊣
+ http://spf13.com/projects/hugo
+
+
+ baseURL url
+ ⊢-----^--------⊣ ⊢--------^-----------⊣
+ http://spf13.com/extras/indexes/example
+
+
+
+**section** = which type the content is by default
+
+* based on content location
+* front matter overrides
+
+**slug** = name.ext or name/
+
+* based on content-name.md
+* front matter overrides
+
+**path** = section + path to file excluding slug
+
+* based on path to content location
+
+
+**url** = relative URL
+
+* defined in front matter
+* overrides all the above
+
diff --git a/docs/content/content/sections.md b/docs/content/content/sections.md
new file mode 100644
index 000000000..c603c0872
--- /dev/null
+++ b/docs/content/content/sections.md
@@ -0,0 +1,54 @@
+---
+lastmod: 2015-12-23
+date: 2013-07-01
+menu:
+ main:
+ parent: content
+next: /content/types
+notoc: true
+prev: /content/front-matter
+title: Sections
+weight: 30
+---
+
+Hugo believes that you organize your content with a purpose. The same structure
+that works to organize your source content is used to organize the rendered
+site (see [Organization](/content/organization/)). Following this pattern Hugo
+uses the top level of your content organization as **the Section**.
+
+The following example site uses two sections, "post" and "quote".
+
+{{< nohighlight >}}.
+└── content
+ ├── post
+ | ├── firstpost.md // <- http://1.com/post/firstpost/
+ | ├── happy
+ | | └── ness.md // <- http://1.com/post/happy/ness/
+ | └── secondpost.md // <- http://1.com/post/secondpost/
+ └── quote
+ ├── first.md // <- http://1.com/quote/first/
+ └── second.md // <- http://1.com/quote/second/
+{{< /nohighlight >}}
+
+## Section Lists
+
+Hugo will automatically create pages for each section root that list all
+of the content in that section. See [List Templates](/templates/list/)
+for details on customizing the way they appear.
+
+Section pages can also have a content file and frontmatter, see [Source Organization]({{< relref "overview/source-directory.md#content-for-home-page-and-other-list-pages" >}}).
+
+## Sections and Types
+
+By default everything created within a section will use the content type
+that matches the section name.
+
+Section defined in the front matter have the same impact.
+
+To change the type of a given piece of content, simply define the type
+in the front matter.
+
+If a layout for a given type hasn't been provided, a default type template will
+be used instead provided it exists.
+
+
diff --git a/docs/content/content/summaries.md b/docs/content/content/summaries.md
new file mode 100644
index 000000000..1f65d431d
--- /dev/null
+++ b/docs/content/content/summaries.md
@@ -0,0 +1,54 @@
+---
+lastmod: 2015-01-27
+date: 2013-07-01
+menu:
+ main:
+ parent: content
+notoc: true
+prev: /content/ordering
+next: /content/markdown-extras
+title: Summaries
+weight: 65
+---
+
+With the use of the `.Summary` [page variable](/templates/variables/), Hugo can generate summaries of content to show snippets in summary views. The summary view snippets are automatically generated by Hugo. Where a piece of content is split for the content summary depends on whether the split is Hugo-defined or user-defined.
+
+Content summaries may also provide links to the original content, usually in the form of a "Read More..." link, with the help of the `.RelPermalink` or `.Permalink` variable, as well as the `.Truncated` boolean variable to determine whether such "Read More..." link is necessary.
+
+## Hugo-defined: automatic summary split
+
+By default, Hugo automatically takes the first 70 words of your content as its summary and stores it into the `.Summary` variable, which you may use in your templates.
+
+* Pros: Automatic, no additional work on your part.
+* Cons: All HTML tags are stripped from the summary, and the first 70 words, whether they belong to a heading or to different paragraphs, are all lumped into one paragraph. Some people like it, but some people don't.
+
+## User-defined: manual summary split:
+
+Alternatively, you may add the <!--more--> summary divider[^1] (for org content, use # more) where you want to split the article. Content prior to the summary divider will be used as that content's summary, and stored into the `.Summary` variable with all HTML formatting intact.
+
+[^1]: The **summary divider** is also called "more tag", "excerpt separator", etc. in other literature.
+
+* Pros: Freedom, precision, and improved rendering. All formatting is preserved.
+* Cons: Need to remember to type <!--more--> (or # more for org content) in your content file. :-)
+
+Be careful to enter <!--more--> (or # more for org content) exactly, i.e. all lowercase with no whitespace, otherwise it would be treated as regular comment and ignored.
+
+If there is nothing but spaces and newlines after the summary divider then `.Truncated` will be false.
+
+## Showing Summaries
+
+You can show content summaries with the following code. You could do this, for example, on a [list](/templates/list/) page.
+
+ {{ range first 10 .Data.Pages }}
+
+ {{ end }}
+ {{ end }}
+
+Note how the `.Truncated` boolean valuable may be used to hide the "Read More..." link when the content is not truncated, i.e. when the summary contains the entire article.
diff --git a/docs/content/content/supported-formats.md b/docs/content/content/supported-formats.md
new file mode 100644
index 000000000..3fc905d6d
--- /dev/null
+++ b/docs/content/content/supported-formats.md
@@ -0,0 +1,27 @@
+---
+aliases:
+- /doc/supported-formats/
+lastmod: 2015-08-01
+date: 2015-08-01
+menu:
+ main:
+ parent: content
+next: /content/front-matter
+prev: /content/organization
+title: Supported Formats
+weight: 15
+toc: true
+---
+
+ Since 0.14, Hugo has defined a new concept called _external helpers_. It means that you can write your content using Asciidoc[tor], reStructuredText or Org-Mode. If you have files with associated extensions ([details](https://github.com/gohugoio/hugo/blob/77c60a3440806067109347d04eb5368b65ea0fe8/helpers/general.go#L65)), then Hugo will call external commands to generate the content (the exception being Org-Mode content, which is parsed natively).
+
+ This means that you will have to install the associated tool on your machine to be able to use those formats.
+
+ For example, for Asciidoc files, Hugo will try to call __asciidoctor__ or __asciidoc__ command.
+
+ To use those formats, just use the standard extension and the front matter exactly as you would do with natively supported _.md_ files.
+
+ Notes:
+
+ * as these are external commands, generation performance for that content will heavily depend on the performance of those external tools.
+ * this feature is still in early stage, hence feedback is even more welcome.
diff --git a/docs/content/content/types.md b/docs/content/content/types.md
new file mode 100644
index 000000000..277294881
--- /dev/null
+++ b/docs/content/content/types.md
@@ -0,0 +1,80 @@
+---
+lastmod: 2015-09-28
+date: 2013-07-01
+linktitle: Types
+menu:
+ main:
+ parent: content
+next: /content/archetypes
+prev: /content/sections
+title: Content Types
+weight: 40
+toc: true
+---
+
+Hugo has full support for different types of content. A content type can have a
+unique set of meta data, template and can be automatically created by the `hugo new`
+command through using content [archetypes](/content/archetypes/).
+
+A good example of when multiple types are needed is to look at [Tumblr](https://www.tumblr.com/). A piece
+of content could be a photo, quote or post, each with different meta data and
+rendered differently.
+
+## Assigning a content type
+
+Hugo assumes that your site will be organized into [sections](/content/sections/)
+and each section will use the corresponding type. If you are taking advantage of
+this, then each new piece of content you place into a section will automatically
+inherit the type.
+
+Alternatively, you can set the type in the meta data under the key "`type`".
+
+
+## Creating new content of a specific type
+
+Hugo has the ability to create a new content file and populate the front matter
+with the data set corresponding to that type. Hugo does this by utilizing
+[archetypes](/content/archetypes/).
+
+To create a new piece of content, use:
+
+ hugo new relative/path/to/content.md
+
+For example, if I wanted to create a new post inside the post section, I would type:
+
+ hugo new post/my-newest-post.md
+
+
+## Defining a content type
+
+Creating a new content type is easy in Hugo. You simply provide the templates and archetype
+that the new type will use. You only need to define the templates, archetypes and/or views
+unique to that content type. Hugo will fall back to using the general templates and default archetype
+whenever a specific file is not present.
+
+*Remember, all of the following are optional:*
+
+### Create Type Directory
+Create a directory with the name of the type in `/layouts`. Type is always singular. *E.g. `/layouts/post`*.
+
+### Create single template
+Create a file called `single.html` inside your directory. *E.g. `/layouts/post/single.html`*.
+
+### Create list template
+Create a file called `post.html` inside the section lists template directory, `/layouts/section`. *E.g. `/layouts/section/post.html`*.
+
+### Create views
+Many sites support rendering content in a few different ways, for
+instance, a single page view and a summary view to be used when
+displaying a [list of contents on a single page](/templates/list).
+Hugo makes no assumptions here about how you want to display your
+content, and will support as many different views of a content type
+as your site requires. All that is required for these additional
+views is that a template exists in each `/layouts/TYPE` directory
+with the same name.
+
+### Create a corresponding archetype
+
+Create a file called type.md in the `/archetypes` directory. *E.g. `/archetypes/post.md`*.
+
+More details about archetypes can be found at the [archetypes docs](/content/archetypes/).
diff --git a/docs/content/content/using-index-md.md b/docs/content/content/using-index-md.md
new file mode 100644
index 000000000..1f1298f67
--- /dev/null
+++ b/docs/content/content/using-index-md.md
@@ -0,0 +1,118 @@
+---
+aliases:
+- /doc/using-index-md/
+lastmod: 2017-02-22
+date: 2017-02-22
+linktitle: Using _index.md
+menu:
+ main:
+ parent: content
+prev: /content/example
+next: /themes/overview
+notoc: true
+title: Using _index.md
+weight: 70
+---
+# \_index.md and 'Everything is a Page'
+
+As of version v0.18 Hugo now treats '[everything as a page](http://bepsays.com/en/2016/12/19/hugo-018/)'. This allows you to add content and frontmatter to any page - including List pages like [Sections](/content/sections/), [Taxonomies](/taxonomies/overview/), [Taxonomy Terms pages](/templates/terms/) and even to potential 'special case' pages like the [Home page](/templates/homepage/).
+
+In order to take advantage of this behaviour you need to do a few things.
+
+1. Create an \_index.md file that contains the frontmatter and content you would like to apply.
+
+2. Place the \_index.md file in the correct place in the directory structure.
+
+3. Ensure that the respective template is configured to display `{{ .Content }}` if you wish for the content of the \_index.md file to be rendered on the respective page.
+
+## How \_index.md pages work
+
+Before continuing it's important to know that this page must reference certain templates to describe how the \_index.md page will be rendered. Hugo has a multitude of possible templates that can be used and placed in various places (think theme templates for instance). For simplicity/brevity the default/top level template location will be used to refer to the entire range of places the template can be placed.
+
+If this is confusing or you are unfamiliar with Hugo's template hierarchy, visit the various template pages listed below. You may need to find the 'active' template responsible for any particular page on your own site by going through the template hierarchy and matching it to your particular setup/theme you are using.
+
+- [Home page template](/templates/homepage/)
+- [Content List templates](/templates/list/)
+- [Single Content templates](/templates/content/)
+- [Taxonomy Terms templates](/templates/terms/)
+
+Now that you've got a handle on templates lets recap some Hugo basics to understand how to use an \_index.md file with a List page.
+
+1. Sections and Taxonomies are 'List' pages, NOT single pages.
+2. List pages are rendered using the template hierarchy found in the [Content - List Template](/templates/list/) docs.
+3. The Home page, though technically a List page, can have [its own template](/templates/homepage/) at layouts/index.html rather than \_default/list.html. Many themes exploit this behaviour so you are likely to encounter this specific use case.
+4. Taxonomy terms pages are 'lists of metadata' not lists of content, so [have their own templates](/templates/terms/).
+
+Let's put all this information together:
+
+> **\_index.md files used in List pages, Terms pages or the Home page are NOT rendered as single pages or with Single Content templates.**
+
+> **All pages, including List pages, can have frontmatter and frontmatter can have markdown content - meaning \_index.md files are the way to _provide_ frontmatter and content to the respective List/Terms/Home page.**
+
+Here are a couple of examples to make it clearer...
+
+| \_index.md location | Page affected | Rendered by |
+| ------------------- | ------------ | ----------- |
+| /content/post/\_index.md | site.com/post/ | /layouts/section/post.html |
+| /content/categories/hugo/\_index.md | site.com/categories/hugo/ | /layouts/taxonomy/hugo.html |
+
+## Why \_index.md files are used
+
+With a Single page such as a post it's possible to add the frontmatter and content directly into the .md page itself. With List/Terms/Home pages this is not possible so \_index.md files can be used to provide that frontmatter/content to them.
+
+## How to display content from \_index.md files
+
+From the information above it should follow that content within an \_index.md file won't be rendered in its own Single Page, instead it'll be made available to the respective List/Terms/Home page.
+
+To **_actually render that content_** you need to ensure that the relevant template responsible for rendering the List/Terms/Home page contains (at least) `{{ .Content }}`.
+
+This is the way to actually display the content within the \_index.md file on the List/Terms/Home page.
+
+A very simple/naive example of this would be:
+
+```html
+{{ partial "header.html" . }}
+
+ {{ .Content }}
+ {{ range .Paginator.Pages }}
+ {{ partial "summary.html" . }}
+ {{ end }}
+ {{ partial "pagination.html" . }}
+
+{{ partial "sidebar.html" . }}
+{{ partial "footer.html" . }}
+```
+
+You can see `{{ .Content }}` just after the `` element. For this particular example, the content of the \_index.md file will show before the main list of summaries.
+
+## Where to organise an \_index.md file
+
+To add content and frontmatter to the home page, a section, a taxonomy or a taxonomy terms listing, add a markdown file with the base name \_index on the relevant place on the file system.
+
+```bash
+└── content
+ ├── _index.md
+ ├── categories
+ │ ├── _index.md
+ │ └── photo
+ │ └── _index.md
+ ├── post
+ │ ├── _index.md
+ │ └── firstpost.md
+ └── tags
+ ├── _index.md
+ └── hugo
+ └── _index.md
+```
+
+In the above example \_index.md pages have been added to each section/taxonomy.
+
+An \_index.md file has also been added in the top level 'content' directory.
+
+### Where to place \_index.md for the Home page
+
+Hugo themes are designed to use the 'content' directory as the root of the website, so adding an \_index.md file here (like has been done in the example above) is how you would add frontmatter/content to the home page.
+
+
+
+
diff --git a/docs/content/en/_common/_index.md b/docs/content/en/_common/_index.md
deleted file mode 100644
index 612165e5c..000000000
--- a/docs/content/en/_common/_index.md
+++ /dev/null
@@ -1,13 +0,0 @@
----
-cascade:
- build:
- list: never
- publishResources: false
- render: never
----
-
-
diff --git a/docs/content/en/_common/content-format-table.md b/docs/content/en/_common/content-format-table.md
deleted file mode 100644
index c0a66a146..000000000
--- a/docs/content/en/_common/content-format-table.md
+++ /dev/null
@@ -1,13 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-Content format|Media type|Identifier|File extensions
-:--|:--|:--|:--
-Markdown|`text/markdown`|`markdown`|`markdown`,`md`, `mdown`
-HTML|`text/html`|`html`|`htm`, `html`
-Emacs Org Mode|`text/org`|`org`|`org`
-AsciiDoc|`text/asciidoc`|`asciidoc`|`ad`, `adoc`, `asciidoc`
-Pandoc|`text/pandoc`|`pandoc`|`pandoc`, `pdc`
-reStructuredText|`text/rst`|`rst`|`rst`
-
diff --git a/docs/content/en/_common/filter-sort-group.md b/docs/content/en/_common/filter-sort-group.md
deleted file mode 100644
index ac73766da..000000000
--- a/docs/content/en/_common/filter-sort-group.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-> [!note]
-> The [page collections quick reference guide] describes methods and functions to filter, sort, and group page collections.
-
-[page collections quick reference guide]: /quick-reference/page-collections/
diff --git a/docs/content/en/_common/functions/fmt/format-string.md b/docs/content/en/_common/functions/fmt/format-string.md
deleted file mode 100644
index 09a9ee867..000000000
--- a/docs/content/en/_common/functions/fmt/format-string.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-The documentation for Go's [fmt] package describes the structure and content of the format string.
-
-[fmt]: https://pkg.go.dev/fmt
diff --git a/docs/content/en/_common/functions/go-html-template-package.md b/docs/content/en/_common/functions/go-html-template-package.md
deleted file mode 100644
index 57992ea66..000000000
--- a/docs/content/en/_common/functions/go-html-template-package.md
+++ /dev/null
@@ -1,14 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-Hugo uses Go's [text/template] and [html/template] packages.
-
-The text/template package implements data-driven templates for generating textual output, while the html/template package implements data-driven templates for generating HTML output safe against code injection.
-
-By default, Hugo uses the html/template package when rendering HTML files.
-
-To generate HTML output that is safe against code injection, the html/template package escapes strings in certain contexts.
-
-[text/template]: https://pkg.go.dev/text/template
-[html/template]: https://pkg.go.dev/html/template
diff --git a/docs/content/en/_common/functions/go-template/text-template.md b/docs/content/en/_common/functions/go-template/text-template.md
deleted file mode 100644
index 4b934c1e9..000000000
--- a/docs/content/en/_common/functions/go-template/text-template.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-See Go's [text/template] documentation for more information.
-
-[text/template]: https://pkg.go.dev/text/template
diff --git a/docs/content/en/_common/functions/images/apply-image-filter.md b/docs/content/en/_common/functions/images/apply-image-filter.md
deleted file mode 100644
index 08e08238f..000000000
--- a/docs/content/en/_common/functions/images/apply-image-filter.md
+++ /dev/null
@@ -1,27 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-Apply the filter using the [`images.Filter`] function:
-
-[`images.Filter`]: /functions/images/filter/
-
-```go-html-template
-{{ with resources.Get "images/original.jpg" }}
- {{ with . | images.Filter $filter }}
-
- {{ end }}
-{{ end }}
-```
-
-You can also apply the filter using the [`Filter`] method on a `Resource` object:
-
-[`Filter`]: /methods/resource/filter/
-
-```go-html-template
-{{ with resources.Get "images/original.jpg" }}
- {{ with .Filter $filter }}
-
- {{ end }}
-{{ end }}
-```
diff --git a/docs/content/en/_common/functions/js/options.md b/docs/content/en/_common/functions/js/options.md
deleted file mode 100644
index 475429d05..000000000
--- a/docs/content/en/_common/functions/js/options.md
+++ /dev/null
@@ -1,108 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-params
-: (`map` or `slice`) 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., configuration settings. For larger data sets, please put/mount the files into `assets` and import them directly.
-
-minify
-: (`bool`) Whether to let `js.Build` handle the minification.
-
-loaders
-: {{< new-in 0.140.0 />}}
-: (`map`) Configuring a loader for a given file type lets you load that file type with an `import` statement or a `require` call. For example, configuring the `.png` file extension to use the data URL loader means importing a `.png` file gives you a data URL containing the contents of that image. Loaders available are `none`, `base64`, `binary`, `copy`, `css`, `dataurl`, `default`, `empty`, `file`, `global-css`, `js`, `json`, `jsx`, `local-css`, `text`, `ts`, `tsx`. See https://esbuild.github.io/api/#loader.
-
-inject
-: (`slice`) 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
-: (`map`) 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:
-
- ```go-html-template
- {{ $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/client';
- ```
-
-target
-: (`string`) The language target. One of: `es5`, `es2015`, `es2016`, `es2017`, `es2018`, `es2019`, `es2020`, `es2021`, `es2022`, `es2023`, `es2024`, or `esnext`. Default is `esnext`.
-
-platform
-: {{< new-in 0.140.0 />}}
-: (`string`) One of `browser`, `node`, `neutral`. Default is `browser`. See https://esbuild.github.io/api/#platform.
-
-externals
-: (`slice`) External dependencies. Use this to trim dependencies you know will never be executed. See https://esbuild.github.io/api/#external.
-
-defines
-: (`map`) This option allows you to define a set of string replacements to be performed when building. It must be a map where each key will be replaced by its value.
-
- ```go-html-template
- {{ $defines := dict "process.env.NODE_ENV" `"development"` }}
- ```
-
-drop
-: {{< new-in 0.144.0 />}}
-: (`string`) Edit your source code before building to drop certain constructs: One of `debugger` or `console`.
-: See https://esbuild.github.io/api/#drop
-
-sourceMap
-: (`string`) Whether to generate `inline`, `linked`, or `external` source maps from esbuild. Linked and external source maps will be written to the target with the output file name + ".map". When `linked` a `sourceMappingURL` will also be written to the output file. By default, source maps are not created. Note that the `linked` option was added in Hugo 0.140.0.
-
-sourcesContent
-: {{< new-in 0.140.0 />}}
-: (`bool`) Whether to include the content of the source files in the source map. By default, this is `true`.
-
-JSX
-: {{< new-in 0.124.0 />}}
-: (`string`) How to handle/transform JSX syntax. One of: `transform`, `preserve`, `automatic`. Default is `transform`. Notably, the `automatic` transform was introduced in React 17+ and will cause the necessary JSX helper functions to be imported automatically. See https://esbuild.github.io/api/#jsx.
-
-JSXImportSource
-: {{< new-in 0.124.0 />}}
-: (`string`) Which library to use to automatically import its JSX helper functions from. This only works if `JSX` is set to `automatic`. The specified library needs to be installed through npm and expose certain exports. See https://esbuild.github.io/api/#jsx-import-source.
-
- The combination of `JSX` and `JSXImportSource` is helpful if you want to use a non-React JSX library like Preact, e.g.:
-
- ```go-html-template
- {{ $js := resources.Get "js/main.jsx" | js.Build (dict "JSX" "automatic" "JSXImportSource" "preact") }}
- ```
-
- With the above, you can use Preact components and JSX without having to manually import `h` and `Fragment` every time:
-
- ```jsx
- import { render } from 'preact';
-
- const App = () => <>Hello world!>;
-
- const container = document.getElementById('app');
- if (container) render(, container);
- ```
diff --git a/docs/content/en/_common/functions/locales.md b/docs/content/en/_common/functions/locales.md
deleted file mode 100644
index 1cfd7a1e6..000000000
--- a/docs/content/en/_common/functions/locales.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-> [!note]
-> Localization of dates, currencies, numbers, and percentages is performed by the [gohugoio/locales] package. The language tag of the current site must match one of the listed locales.
-
-[gohugoio/locales]: https://github.com/gohugoio/locales
diff --git a/docs/content/en/_common/functions/regular-expressions.md b/docs/content/en/_common/functions/regular-expressions.md
deleted file mode 100644
index 58f81a2ee..000000000
--- a/docs/content/en/_common/functions/regular-expressions.md
+++ /dev/null
@@ -1,12 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-When specifying the regular expression, use a raw [string literal] (backticks) instead of an interpreted string literal (double quotes) to simplify the syntax. With an interpreted string literal you must escape backslashes.
-
-Go's regular expression package implements the [RE2 syntax]. The RE2 syntax is a subset of that accepted by [PCRE], roughly speaking, and with various [caveats]. Note that the RE2 `\C` escape sequence is not supported.
-
-[caveats]: https://swtch.com/~rsc/regexp/regexp3.html#caveats
-[PCRE]: https://www.pcre.org/
-[RE2 syntax]: https://github.com/google/re2/wiki/Syntax/
-[string literal]: https://go.dev/ref/spec#String_literals
diff --git a/docs/content/en/_common/functions/truthy-falsy.md b/docs/content/en/_common/functions/truthy-falsy.md
deleted file mode 100644
index e15e58d61..000000000
--- a/docs/content/en/_common/functions/truthy-falsy.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-The falsy values are `false`, `0`, any `nil` pointer or interface value, any array, slice, map, or string of length zero, and zero `time.Time` values.
-
-Everything else is truthy.
diff --git a/docs/content/en/_common/functions/urls/anchorize-vs-urlize.md b/docs/content/en/_common/functions/urls/anchorize-vs-urlize.md
deleted file mode 100644
index e00c181b8..000000000
--- a/docs/content/en/_common/functions/urls/anchorize-vs-urlize.md
+++ /dev/null
@@ -1,35 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-The [`anchorize`] and [`urlize`] functions are similar:
-
-[`anchorize`]: /functions/urls/anchorize/
-[`urlize`]: /functions/urls/urlize/
-
-- Use the `anchorize` function to generate an HTML `id` attribute value
-- Use the `urlize` function to sanitize a string for usage in a URL
-
-For example:
-
-```go-html-template
-{{ $s := "A B C" }}
-{{ $s | anchorize }} → a-b-c
-{{ $s | urlize }} → a-b-c
-
-{{ $s := "a b c" }}
-{{ $s | anchorize }} → a-b---c
-{{ $s | urlize }} → a-b-c
-
-{{ $s := "< a, b, & c >" }}
-{{ $s | anchorize }} → -a-b--c-
-{{ $s | urlize }} → a-b-c
-
-{{ $s := "main.go" }}
-{{ $s | anchorize }} → maingo
-{{ $s | urlize }} → main.go
-
-{{ $s := "Hugö" }}
-{{ $s | anchorize }} → hugö
-{{ $s | urlize }} → hug%C3%B6
-```
diff --git a/docs/content/en/_common/glob-patterns.md b/docs/content/en/_common/glob-patterns.md
deleted file mode 100644
index d3092dece..000000000
--- a/docs/content/en/_common/glob-patterns.md
+++ /dev/null
@@ -1,23 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-Path|Pattern|Match
-:--|:--|:--
-`images/foo/a.jpg`|`images/foo/*.jpg`|`true`
-`images/foo/a.jpg`|`images/foo/*.*`|`true`
-`images/foo/a.jpg`|`images/foo/*`|`true`
-`images/foo/a.jpg`|`images/*/*.jpg`|`true`
-`images/foo/a.jpg`|`images/*/*.*`|`true`
-`images/foo/a.jpg`|`images/*/*`|`true`
-`images/foo/a.jpg`|`*/*/*.jpg`|`true`
-`images/foo/a.jpg`|`*/*/*.*`|`true`
-`images/foo/a.jpg`|`*/*/*`|`true`
-`images/foo/a.jpg`|`**/*.jpg`|`true`
-`images/foo/a.jpg`|`**/*.*`|`true`
-`images/foo/a.jpg`|`**/*`|`true`
-`images/foo/a.jpg`|`**`|`true`
-`images/foo/a.jpg`|`*/*.jpg`|`false`
-`images/foo/a.jpg`|`*.jpg`|`false`
-`images/foo/a.jpg`|`*.*`|`false`
-`images/foo/a.jpg`|`*`|`false`
diff --git a/docs/content/en/_common/gomodules-info.md b/docs/content/en/_common/gomodules-info.md
deleted file mode 100644
index 5d88a6f9d..000000000
--- a/docs/content/en/_common/gomodules-info.md
+++ /dev/null
@@ -1,13 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-> [!note] Hugo Modules are Go Modules
-> You need [Go] version 1.18 or later and [Git] to use Hugo Modules. For older sites hosted on Netlify, please ensure the `GO_VERSION` environment variable is set to `1.18` or higher.
->
-> Go Modules resources:
-> - [go.dev/wiki/Modules](https://go.dev/wiki/Modules)
-> - [blog.golang.org/using-go-modules](https://go.dev/blog/using-go-modules)
-
-[Git]: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git
-[Go]: https://go.dev/doc/install
diff --git a/docs/content/en/_common/installation/01-editions.md b/docs/content/en/_common/installation/01-editions.md
deleted file mode 100644
index 634002822..000000000
--- a/docs/content/en/_common/installation/01-editions.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-Hugo is available in three editions: standard, extended, and extended/deploy. While the standard edition provides core functionality, the extended and extended/deploy editions offer advanced features.
-
-Feature|extended edition|extended/deploy edition
-:--|:-:|:-:
-Encode to the WebP format when [processing images]. You can decode WebP images with any edition.|:heavy_check_mark:|:heavy_check_mark:
-[Transpile Sass to CSS] using the embedded LibSass transpiler. You can use the [Dart Sass] transpiler with any edition.|:heavy_check_mark:|:heavy_check_mark:
-Deploy your site directly to a Google Cloud Storage bucket, an AWS S3 bucket, or an Azure Storage container. See [details].|:x:|:heavy_check_mark:
-
-[dart sass]: /functions/css/sass/#dart-sass
-[processing images]: /content-management/image-processing/
-[transpile sass to css]: /functions/css/sass/
-[details]: /host-and-deploy/deploy-with-hugo-deploy/
diff --git a/docs/content/en/_common/installation/02-prerequisites.md b/docs/content/en/_common/installation/02-prerequisites.md
deleted file mode 100644
index f27d9d56b..000000000
--- a/docs/content/en/_common/installation/02-prerequisites.md
+++ /dev/null
@@ -1,40 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-## Prerequisites
-
-Although not required in all cases, [Git], [Go], and [Dart Sass] are commonly used when working with Hugo.
-
-Git is required to:
-
-- Build Hugo from source
-- Use the [Hugo Modules] feature
-- Install a theme as a Git submodule
-- Access [commit information] from a local Git repository
-- Host your site with services such as [CloudCannon], [Cloudflare Pages], [GitHub Pages], [GitLab Pages], and [Netlify]
-
-Go is required to:
-
-- Build Hugo from source
-- Use the Hugo Modules feature
-
-Dart Sass is required to transpile Sass to CSS when using the latest features of the Sass language.
-
-Please refer to the relevant documentation for installation instructions:
-
-- [Git][git install]
-- [Go][go install]
-- [Dart Sass][dart sass install]
-
-[cloudcannon]: https://cloudcannon.com/
-[cloudflare pages]: https://pages.cloudflare.com/
-[dart sass install]: /functions/css/sass/#dart-sass
-[dart sass]: https://sass-lang.com/dart-sass
-[git install]: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git
-[git]: https://git-scm.com/
-[github pages]: https://pages.github.com/
-[gitlab pages]: https://docs.gitlab.com/ee/user/project/pages/
-[go install]: https://go.dev/doc/install
-[go]: https://go.dev/
-[netlify]: https://www.netlify.com/
diff --git a/docs/content/en/_common/installation/03-prebuilt-binaries.md b/docs/content/en/_common/installation/03-prebuilt-binaries.md
deleted file mode 100644
index 34411cddd..000000000
--- a/docs/content/en/_common/installation/03-prebuilt-binaries.md
+++ /dev/null
@@ -1,23 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-## Prebuilt binaries
-
-Prebuilt binaries are available for a variety of operating systems and architectures. Visit the [latest release] page, and scroll down to the Assets section.
-
-1. Download the archive for the desired edition, operating system, and architecture
-1. Extract the archive
-1. Move the executable to the desired directory
-1. Add this directory to the PATH environment variable
-1. Verify that you have _execute_ permission on the file
-
-Please consult your operating system documentation if you need help setting file permissions or modifying your PATH environment variable.
-
-If you do not see a prebuilt binary for the desired edition, operating system, and architecture, install Hugo using one of the methods described below.
-
-[commit information]: /methods/page/gitinfo/
-[Git]: https://git-scm.com/
-[Go]: https://go.dev/
-[Hugo Modules]: /hugo-modules/
-[latest release]: https://github.com/gohugoio/hugo/releases/latest
diff --git a/docs/content/en/_common/installation/04-build-from-source.md b/docs/content/en/_common/installation/04-build-from-source.md
deleted file mode 100644
index 3ce245f4a..000000000
--- a/docs/content/en/_common/installation/04-build-from-source.md
+++ /dev/null
@@ -1,38 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-## Build from source
-
-To build the extended or extended/deploy edition from source you must:
-
-1. Install [Git]
-1. Install [Go] version 1.23.0 or later
-1. Install a C compiler, either [GCC] or [Clang]
-1. Update your `PATH` environment variable as described in the [Go documentation]
-
-> The install directory is controlled by the `GOPATH` and `GOBIN` environment variables. If `GOBIN` is set, binaries are installed to that directory. If `GOPATH` is set, binaries are installed to the bin subdirectory of the first directory in the `GOPATH` list. Otherwise, binaries are installed to the bin subdirectory of the default `GOPATH` (`$HOME/go` or `%USERPROFILE%\go`).
-
-To build the standard edition:
-
-```sh
-go install github.com/gohugoio/hugo@latest
-```
-
-To build the extended edition:
-
-```sh
-CGO_ENABLED=1 go install -tags extended github.com/gohugoio/hugo@latest
-```
-
-To build the extended/deploy edition:
-
-```sh
-CGO_ENABLED=1 go install -tags extended,withdeploy github.com/gohugoio/hugo@latest
-```
-
-[Clang]: https://clang.llvm.org/
-[GCC]: https://gcc.gnu.org/
-[Git]: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git
-[Go documentation]: https://go.dev/doc/code#Command
-[Go]: https://go.dev/doc/install
diff --git a/docs/content/en/_common/installation/homebrew.md b/docs/content/en/_common/installation/homebrew.md
deleted file mode 100644
index 14f48174e..000000000
--- a/docs/content/en/_common/installation/homebrew.md
+++ /dev/null
@@ -1,13 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-### Homebrew
-
-[Homebrew] is a free and open-source package manager for macOS and Linux. To install the extended edition of Hugo:
-
-```sh
-brew install hugo
-```
-
-[Homebrew]: https://brew.sh/
diff --git a/docs/content/en/_common/menu-entries/pre-and-post.md b/docs/content/en/_common/menu-entries/pre-and-post.md
deleted file mode 100644
index da3d584d1..000000000
--- a/docs/content/en/_common/menu-entries/pre-and-post.md
+++ /dev/null
@@ -1,39 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-In this site configuration we enable rendering of [emoji shortcodes], and add emoji shortcodes before (pre) and after (post) each menu entry:
-
-{{< code-toggle file=hugo >}}
-enableEmoji = true
-
-[[menus.main]]
-name = 'About'
-pageRef = '/about'
-post = ':point_left:'
-pre = ':point_right:'
-weight = 10
-
-[[menus.main]]
-name = 'Contact'
-pageRef = '/contact'
-post = ':arrow_left:'
-pre = ':arrow_right:'
-weight = 20
-{{< /code-toggle >}}
-
-To render the menu:
-
-```go-html-template
-
-```
-
-[emoji shortcodes]: /quick-reference/emojis/
diff --git a/docs/content/en/_common/menu-entry-properties.md b/docs/content/en/_common/menu-entry-properties.md
deleted file mode 100644
index daeadd79d..000000000
--- a/docs/content/en/_common/menu-entry-properties.md
+++ /dev/null
@@ -1,31 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-
-
-identifier
-: (`string`) Required when two or more menu entries have the same `name`, or when localizing the `name` using translation tables. Must start with a letter, followed by letters, digits, or underscores.
-
-name
-: (`string`) The text to display when rendering the menu entry.
-
-params
-: (`map`) User-defined properties for the menu entry.
-
-parent
-: (`string`) The `identifier` of the parent menu entry. If `identifier` is not defined, use `name`. Required for child entries in a nested menu.
-
-post
-: (`string`) The HTML to append when rendering the menu entry.
-
-pre
-: (`string`) The HTML to prepend when rendering the menu entry.
-
-title
-: (`string`) The HTML `title` attribute of the rendered menu entry.
-
-weight
-: (`int`) A non-zero integer indicating the entry's position relative the root of the menu, or to its parent for a child entry. Lighter entries float to the top, while heavier entries sink to the bottom.
diff --git a/docs/content/en/_common/methods/page/next-and-prev.md b/docs/content/en/_common/methods/page/next-and-prev.md
deleted file mode 100644
index f859961a4..000000000
--- a/docs/content/en/_common/methods/page/next-and-prev.md
+++ /dev/null
@@ -1,60 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-Hugo determines the _next_ and _previous_ page by sorting the site's collection of regular pages according to this sorting hierarchy:
-
-Field|Precedence|Sort direction
-:--|:--|:--
-[`weight`]|1|descending
-[`date`]|2|descending
-[`linkTitle`]|3|descending
-[`path`]|4|descending
-
-[`date`]: /methods/page/date/
-[`weight`]: /methods/page/weight/
-[`linkTitle`]: /methods/page/linktitle/
-[`path`]: /methods/page/path/
-
-The sorted page collection used to determine the _next_ and _previous_ page is independent of other page collections, which may lead to unexpected behavior.
-
-For example, with this content structure:
-
-```text
-content/
-├── pages/
-│ ├── _index.md
-│ ├── page-1.md <-- front matter: weight = 10
-│ ├── page-2.md <-- front matter: weight = 20
-│ └── page-3.md <-- front matter: weight = 30
-└── _index.md
-```
-
-And these templates:
-
-```go-html-template {file="layouts/_default/list.html"}
-{{ range .Pages.ByWeight }}
-
-{{ end }}
-```
-
-```go-html-template {file="layouts/_default/single.html"}
-{{ with .Prev }}
- Previous
-{{ end }}
-
-{{ with .Next }}
- Next
-{{ end }}
-```
-
-When you visit page-2:
-
-- The `Prev` method points to page-3
-- The `Next` method points to page-1
-
-To reverse the meaning of _next_ and _previous_ you can change the sort direction in your [site configuration], or use the [`Next`] and [`Prev`] methods on a `Pages` object for more flexibility.
-
-[site configuration]: /configuration/page/
-[`Next`]: /methods/pages/prev
-[`Prev`]: /methods/pages/prev
diff --git a/docs/content/en/_common/methods/page/nextinsection-and-previnsection.md b/docs/content/en/_common/methods/page/nextinsection-and-previnsection.md
deleted file mode 100644
index 54d240eb4..000000000
--- a/docs/content/en/_common/methods/page/nextinsection-and-previnsection.md
+++ /dev/null
@@ -1,78 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-Hugo determines the _next_ and _previous_ page by sorting the current section's regular pages according to this sorting hierarchy:
-
-Field|Precedence|Sort direction
-:--|:--|:--
-[`weight`]|1|descending
-[`date`]|2|descending
-[`linkTitle`]|3|descending
-[`path`]|4|descending
-
-[`date`]: /methods/page/date/
-[`weight`]: /methods/page/weight/
-[`linkTitle`]: /methods/page/linktitle/
-[`path`]: /methods/page/path/
-
-The sorted page collection used to determine the _next_ and _previous_ page is independent of other page collections, which may lead to unexpected behavior.
-
-For example, with this content structure:
-
-```text
-content/
-├── pages/
-│ ├── _index.md
-│ ├── page-1.md <-- front matter: weight = 10
-│ ├── page-2.md <-- front matter: weight = 20
-│ └── page-3.md <-- front matter: weight = 30
-└── _index.md
-```
-
-And these templates:
-
-```go-html-template {file="layouts/_default/list.html"}
-{{ range .Pages.ByWeight }}
-
-{{ end }}
-```
-
-```go-html-template {file="layouts/_default/single.html"}
-{{ with .PrevInSection }}
- Previous
-{{ end }}
-
-{{ with .NextInSection }}
- Next
-{{ end }}
-```
-
-When you visit page-2:
-
-- The `PrevInSection` method points to page-3
-- The `NextInSection` method points to page-1
-
-To reverse the meaning of _next_ and _previous_ you can change the sort direction in your [site configuration], or use the [`Next`] and [`Prev`] methods on a `Pages` object for more flexibility.
-
-[site configuration]: /configuration/page/
-[`Next`]: /methods/pages/prev
-[`Prev`]: /methods/pages/prev
-
-## Example
-
-Code defensively by checking for page existence:
-
-```go-html-template
-{{ with .PrevInSection }}
- Previous
-{{ end }}
-
-{{ with .NextInSection }}
- Next
-{{ end }}
-```
-
-## Alternative
-
-Use the [`Next`] and [`Prev`] methods on a `Pages` object for more flexibility.
diff --git a/docs/content/en/_common/methods/page/output-format-methods.md b/docs/content/en/_common/methods/page/output-format-methods.md
deleted file mode 100644
index 1e914db03..000000000
--- a/docs/content/en/_common/methods/page/output-format-methods.md
+++ /dev/null
@@ -1,35 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-### Get IDENTIFIER
-
-(`any`) Returns the `OutputFormat` object with the given identifier.
-
-### MediaType
-
-(`media.Type`) Returns the media type of the output format.
-
-### MediaType.MainType
-
-(`string`) Returns the main type of the output format's media type.
-
-### MediaType.SubType
-
-(`string`) Returns the subtype of the current format's media type.
-
-### Name
-
-(`string`) Returns the output identifier of the output format.
-
-### Permalink
-
-(`string`) Returns the permalink of the page generated by the current output format.
-
-### Rel
-
-(`string`) Returns the `rel` value of the output format, either the default or as defined in the site configuration.
-
-### RelPermalink
-
-(`string`) Returns the relative permalink of the page generated by the current output format.
diff --git a/docs/content/en/_common/methods/pages/group-sort-order.md b/docs/content/en/_common/methods/pages/group-sort-order.md
deleted file mode 100644
index e2997a1bd..000000000
--- a/docs/content/en/_common/methods/pages/group-sort-order.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-For the optional sort order, specify either `asc` for ascending order, or `desc` for descending order.
diff --git a/docs/content/en/_common/methods/pages/next-and-prev.md b/docs/content/en/_common/methods/pages/next-and-prev.md
deleted file mode 100644
index 462545c3f..000000000
--- a/docs/content/en/_common/methods/pages/next-and-prev.md
+++ /dev/null
@@ -1,72 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-Hugo determines the _next_ and _previous_ page by sorting the page collection according to this sorting hierarchy:
-
-Field|Precedence|Sort direction
-:--|:--|:--
-[`weight`]|1|descending
-[`date`]|2|descending
-[`linkTitle`]|3|descending
-[`path`]|4|descending
-
-[`date`]: /methods/page/date/
-[`weight`]: /methods/page/weight/
-[`linkTitle`]: /methods/page/linktitle/
-[`path`]: /methods/page/path/
-
-The sorted page collection used to determine the _next_ and _previous_ page is independent of other page collections, which may lead to unexpected behavior.
-
-For example, with this content structure:
-
-```text
-content/
-├── pages/
-│ ├── _index.md
-│ ├── page-1.md <-- front matter: weight = 10
-│ ├── page-2.md <-- front matter: weight = 20
-│ └── page-3.md <-- front matter: weight = 30
-└── _index.md
-```
-
-And these templates:
-
-```go-html-template {file="layouts/_default/list.html"}
-{{ range .Pages.ByWeight }}
-
-{{ end }}
-```
-
-```go-html-template {file="layouts/_default/single.html"}
-{{ $pages := .CurrentSection.Pages.ByWeight }}
-
-{{ with $pages.Prev . }}
- Previous
-{{ end }}
-
-{{ with $pages.Next . }}
- Next
-{{ end }}
-```
-
-When you visit page-2:
-
-- The `Prev` method points to page-3
-- The `Next` method points to page-1
-
-To reverse the meaning of _next_ and _previous_ you can chain the [`Reverse`] method to the page collection definition:
-
-```go-html-template {file="layouts/_default/single.html"}
-{{ $pages := .CurrentSection.Pages.ByWeight.Reverse }}
-
-{{ with $pages.Prev . }}
- Previous
-{{ end }}
-
-{{ with $pages.Next . }}
- Next
-{{ end }}
-```
-
-[`Reverse`]: /methods/pages/reverse/
diff --git a/docs/content/en/_common/methods/resource/global-page-remote-resources.md b/docs/content/en/_common/methods/resource/global-page-remote-resources.md
deleted file mode 100644
index 49146aed4..000000000
--- a/docs/content/en/_common/methods/resource/global-page-remote-resources.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-> [!note]
-> Use this method with [global resources](g), [page resources](g), or [remote resources](g).
diff --git a/docs/content/en/_common/methods/resource/processing-spec.md b/docs/content/en/_common/methods/resource/processing-spec.md
deleted file mode 100644
index 395217328..000000000
--- a/docs/content/en/_common/methods/resource/processing-spec.md
+++ /dev/null
@@ -1,36 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-## Process specification
-
-The process specification is a space-delimited, case-insensitive list of one or more of the following in any sequence:
-
-action
-: Applicable to the [`Process`](/methods/resource/process) method only. Specify zero or one of `crop`, `fill`, `fit`, or `resize`. If you specify an action you must also provide dimensions.
-
-dimensions
-: Provide width _or_ height when using the [`Resize`](/methods/resource/resize) method, else provide both width _and_ height. See [details](/content-management/image-processing/#dimensions).
-
-anchor
-: Use with the [`Crop`](/methods/resource/crop) and [`Fill`](/methods/resource/fill) methods. Specify zero or one of `TopLeft`, `Top`, `TopRight`, `Left`, `Center`, `Right`, `BottomLeft`, `Bottom`, `BottomRight`, or `Smart`. Default is `Smart`. See [details](/content-management/image-processing/#anchor).
-
-rotation
-: Typically specify zero or one of `r90`, `r180`, or `r270`. Also supports arbitrary rotation angles. See [details](/content-management/image-processing/#rotation).
-
-target format
-: Specify zero or one of `gif`, `jpeg`, `png`, `tiff`, or `webp`. See [details](/content-management/image-processing/#target-format).
-
-quality
-: Applicable to JPEG and WebP images. Optionally specify `qN` where `N` is an integer in the range [0, 100]. Default is `75`. See [details](/content-management/image-processing/#quality).
-
-hint
-: Applicable to WebP images and equivalent to the `-preset` flag for the [`cwebp`] encoder. Specify zero or one of `drawing`, `icon`, `photo`, `picture`, or `text`. Default is `photo`. See [details](/content-management/image-processing/#hint).
-
-[`cwebp`]: https://developers.google.com/speed/webp/docs/cwebp
-
-background color
-: When converting a PNG or WebP with transparency to a format that does not support transparency, optionally specify a background color using a 3-digit or a 6-digit hexadecimal color code. Default is `#ffffff` (white). See [details](/content-management/image-processing/#background-color).
-
-resampling filter
-: Typically specify zero or one of `Box`, `Lanczos`, `CatmullRom`, `MitchellNetravali`, `Linear`, or `NearestNeighbor`. Other resampling filters are available. See [details](/content-management/image-processing/#resampling-filter).
diff --git a/docs/content/en/_common/methods/taxonomy/get-a-taxonomy-object.md b/docs/content/en/_common/methods/taxonomy/get-a-taxonomy-object.md
deleted file mode 100644
index 6fb729c17..000000000
--- a/docs/content/en/_common/methods/taxonomy/get-a-taxonomy-object.md
+++ /dev/null
@@ -1,67 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-Before we can use a `Taxonomy` method, we need to capture a `Taxonomy` object.
-
-## Capture a Taxonomy object
-
-Consider this site 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
-```
-
-To capture the "genres" `Taxonomy` object from within any template, use the [`Taxonomies`] method on a `Site` object.
-
-```go-html-template
-{{ $taxonomyObject := .Site.Taxonomies.genres }}
-```
-
-To capture the "genres" `Taxonomy` object when rendering its page with a taxonomy template, use the [`Terms`] method on the page's [`Data`] object:
-
-```go-html-template {file="layouts/_default/taxonomy.html"}
-{{ $taxonomyObject := .Data.Terms }}
-```
-
-To inspect the data structure:
-
-```go-html-template
-
{{ debug.Dump $taxonomyObject }}
-```
-
-Although the [`Alphabetical`] and [`ByCount`] methods provide a better data structure for ranging through the taxonomy, you can render the weighted pages by term directly from the `Taxonomy` object:
-
-```go-html-template
-{{ range $term, $weightedPages := $taxonomyObject }}
-
-{{ end }}
-```
-
-In the example above, the first anchor element is a link to the term page.
-
-[`Alphabetical`]: /methods/taxonomy/alphabetical/
-[`ByCount`]: /methods/taxonomy/bycount/
-
-[`data`]: /methods/page/data/
-[`terms`]: /methods/page/data/#in-a-taxonomy-template
-[`taxonomies`]: /methods/site/taxonomies/
diff --git a/docs/content/en/_common/methods/taxonomy/ordered-taxonomy-element-methods.md b/docs/content/en/_common/methods/taxonomy/ordered-taxonomy-element-methods.md
deleted file mode 100644
index ec5f8e406..000000000
--- a/docs/content/en/_common/methods/taxonomy/ordered-taxonomy-element-methods.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-An ordered taxonomy is a slice, where each element is an object that contains the term and a slice of its weighted pages.
-
-Each element of the slice provides these methods:
-
-Count
-: (`int`) Returns the number of pages to which the term is assigned.
-
-Page
-: (`page.Page`) Returns the term's `Page` object, useful for linking to the term page.
-
-Pages
-: (`page.Pages`) Returns a `Pages` object containing the `Page` objects to which the term is assigned, sorted by [taxonomic weight](g). To sort or group, use any of the [methods] available to the `Pages` object. For example, sort by the last modification date.
-
-Term
-: (`string`) Returns the term name.
-
-WeightedPages
-: (`page.WeightedPages`) Returns a slice of weighted pages to which the term is assigned, sorted by taxonomic weight. The `Pages` method above is more flexible, allowing you to sort and group.
-
-[methods]: /methods/pages/
diff --git a/docs/content/en/_common/parsable-date-time-strings.md b/docs/content/en/_common/parsable-date-time-strings.md
deleted file mode 100644
index 92842767e..000000000
--- a/docs/content/en/_common/parsable-date-time-strings.md
+++ /dev/null
@@ -1,14 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-Format|Time zone
-:--|:--
-`2023-10-15T13:18:50-07:00`|`America/Los_Angeles`
-`2023-10-15T13:18:50-0700`|`America/Los_Angeles`
-`2023-10-15T13:18:50Z`|`Etc/UTC`
-`2023-10-15T13:18:50`|Default is `Etc/UTC`
-`2023-10-15`|Default is `Etc/UTC`
-`15 Oct 2023`|Default is `Etc/UTC`
-
-The last three examples are not fully qualified, and default to the `Etc/UTC` time zone.
diff --git a/docs/content/en/_common/permalink-tokens.md b/docs/content/en/_common/permalink-tokens.md
deleted file mode 100644
index 4aec68fb8..000000000
--- a/docs/content/en/_common/permalink-tokens.md
+++ /dev/null
@@ -1,71 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-`:year`
-: The 4-digit year as defined in the front matter `date` field.
-
-`:month`
-: The 2-digit month as defined in the front matter `date` field.
-
-`:monthname`
-: The name of the month as defined in the front matter `date` field.
-
-`:day`
-: The 2-digit day as defined in the front matter `date` field.
-
-`:weekday`
-: The 1-digit day of the week as defined in the front matter `date` field (Sunday = `0`).
-
-`:weekdayname`
-: The name of the day of the week as defined in the front matter `date` field.
-
-`:yearday`
-: The 1- to 3-digit day of the year as defined in the front matter `date` field.
-
-`:section`
-: The content's section.
-
-`:sections`
-: The content's sections hierarchy. You can use a selection of the sections using _slice syntax_: `:sections[1:]` includes all but the first, `:sections[:last]` includes all but the last, `:sections[last]` includes only the last, `:sections[1:2]` includes section 2 and 3. Note that this slice access will not throw any out-of-bounds errors, so you don't have to be exact.
-
-`:title`
-: The `title` as defined in front matter, else the automatic title. Hugo generates titles automatically for section, taxonomy, and term pages that are not backed by a file.
-
-`:slug`
-: The `slug` as defined in front matter, else the `title` as defined in front matter, else the automatic title. Hugo generates titles automatically for section, taxonomy, and term pages that are not backed by a file.
-
-`:filename`
-: The content's file name without extension, applicable to the `page` page kind.
-
- {{< deprecated-in v0.144.0 >}}
- The `:filename` token has been deprecated. Use `:contentbasename` instead.
- {{< /deprecated-in >}}
-
-`:slugorfilename`
-: The `slug` as defined in front matter, else the content's file name without extension, applicable to the `page` page kind.
-
- {{< deprecated-in v0.144.0 >}}
- The `:slugorfilename` token has been deprecated. Use `:slugorcontentbasename` instead.
- {{< /deprecated-in >}}
-
-`:contentbasename`
-: {{< new-in 0.144.0 />}}
-: The [content base name].
-
-[content base name]: /methods/page/file/#contentbasename
-
-`:slugorcontentbasename`
-: {{< new-in 0.144.0 />}}
-: The `slug` as defined in front matter, else the [content base name].
-
-For time-related values, you can also use the layout string components defined in Go's [time package]. For example:
-
-[time package]: https://pkg.go.dev/time#pkg-constants
-
-{{< code-toggle file=hugo >}}
-permalinks:
- posts: /:06/:1/:2/:title/
-{{< /code-toggle >}}
-
-[content base name]: /methods/page/file/#contentbasename
diff --git a/docs/content/en/_common/ref-and-relref-error-handling.md b/docs/content/en/_common/ref-and-relref-error-handling.md
deleted file mode 100644
index 1d67bbc1f..000000000
--- a/docs/content/en/_common/ref-and-relref-error-handling.md
+++ /dev/null
@@ -1,10 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-By default, Hugo will throw an error and fail the build if it cannot resolve the path. You can change this to a warning in your site configuration, and specify a URL to return when the path cannot be resolved.
-
-{{< code-toggle file=hugo >}}
-refLinksErrorLevel = 'warning'
-refLinksNotFoundURL = '/some/other/url'
-{{< /code-toggle >}}
diff --git a/docs/content/en/_common/ref-and-relref-options.md b/docs/content/en/_common/ref-and-relref-options.md
deleted file mode 100644
index ed0dd14c6..000000000
--- a/docs/content/en/_common/ref-and-relref-options.md
+++ /dev/null
@@ -1,12 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-path
-: (`string`) The path to the target page. Paths without a leading slash (`/`) are resolved first relative to the current page, and then relative to the rest of the site.
-
-lang
-: (`string`) The language of the target page. Default is the current language. Optional.
-
-outputFormat
-: (`string`) The output format of the target page. Default is the current output format. Optional.
diff --git a/docs/content/en/_common/render-hooks/pageinner.md b/docs/content/en/_common/render-hooks/pageinner.md
deleted file mode 100644
index a598b880a..000000000
--- a/docs/content/en/_common/render-hooks/pageinner.md
+++ /dev/null
@@ -1,47 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-## PageInner details
-
-{{< new-in 0.125.0 />}}
-
-The primary use case for `PageInner` is to resolve links and [page resources](g) relative to an included `Page`. For example, create an "include" shortcode to compose a page from multiple content files, while preserving a global context for footnotes and the table of contents:
-
-```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/posts/p1.md"}
-{{%/* include "/posts/p2" */%}}
-```
-
-Any render hook triggered while rendering `/posts/p2` will get:
-
-- `/posts/p1` when calling `Page`
-- `/posts/p2` when calling `PageInner`
-
-`PageInner` falls back to the value of `Page` if not relevant, and always returns a value.
-
-> [!note]
-> The `PageInner` method is only relevant for shortcodes that invoke the [`RenderShortcodes`] method, and you must call the shortcode using [Markdown notation].
-
-As a practical example, Hugo's embedded link and image render hooks use the `PageInner` method to resolve markdown link and image destinations. See the source code for each:
-
-- [Embedded link render hook]
-- [Embedded image render hook]
-
-[`RenderShortcodes`]: /methods/page/rendershortcodes/
-[Markdown notation]: /content-management/shortcodes/#notation
-[Embedded link render hook]: {{% eturl render-link %}}
-[Embedded image render hook]: {{% eturl render-image %}}
diff --git a/docs/content/en/_common/scratch-pad-scope.md b/docs/content/en/_common/scratch-pad-scope.md
deleted file mode 100644
index b659497d8..000000000
--- a/docs/content/en/_common/scratch-pad-scope.md
+++ /dev/null
@@ -1,21 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-## Scope
-
-The method or function used to create a scratch pad determines its scope. For example, use the `Store` method on a `Page` object to create a scratch pad scoped to the page.
-
-Scope|Method or function
-:--|:--
-page|[`PAGE.Store`]
-site|[`SITE.Store`]
-global|[`hugo.Store`]
-local|[`collections.NewScratch`]
-shortcode|[`SHORTCODE.Store`]
-
-[`page.store`]: /methods/page/store
-[`site.store`]: /methods/site/store
-[`hugo.store`]: /functions/hugo/store
-[`collections.newscratch`]: functions/collections/newscratch
-[`shortcode.store`]: /methods/shortcode/store
diff --git a/docs/content/en/_common/store-methods.md b/docs/content/en/_common/store-methods.md
deleted file mode 100644
index 1dd776130..000000000
--- a/docs/content/en/_common/store-methods.md
+++ /dev/null
@@ -1,86 +0,0 @@
----
-# Do not remove front matter.
----
-
-## Methods
-
-### Set
-
-Sets the value of the given key.
-
-```go-html-template
-{{ .Store.Set "greeting" "Hello" }}
-```
-
-### Get
-
-Gets the value of the given key.
-
-```go-html-template
-{{ .Store.Set "greeting" "Hello" }}
-{{ .Store.Get "greeting" }} → Hello
-```
-
-### Add
-
-Adds the given value to the 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
-{{ .Store.Set "greeting" "Hello" }}
-{{ .Store.Add "greeting" "Welcome" }}
-{{ .Store.Get "greeting" }} → HelloWelcome
-```
-
-```go-html-template
-{{ .Store.Set "total" 3 }}
-{{ .Store.Add "total" 7 }}
-{{ .Store.Get "total" }} → 10
-```
-
-```go-html-template
-{{ .Store.Set "greetings" (slice "Hello") }}
-{{ .Store.Add "greetings" (slice "Welcome" "Cheers") }}
-{{ .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
-{{ .Store.SetInMap "greetings" "english" "Hello" }}
-{{ .Store.SetInMap "greetings" "french" "Bonjour" }}
-{{ .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
-{{ .Store.SetInMap "greetings" "english" "Hello" }}
-{{ .Store.SetInMap "greetings" "french" "Bonjour" }}
-{{ .Store.DeleteInMap "greetings" "english" }}
-{{ .Store.Get "greetings" }} → map[french:Bonjour]
-```
-
-### GetSortedMapValues
-
-Returns an array of values from `key` sorted by `mapKey`.
-
-```go-html-template
-{{ .Store.SetInMap "greetings" "english" "Hello" }}
-{{ .Store.SetInMap "greetings" "french" "Bonjour" }}
-{{ .Store.GetSortedMapValues "greetings" }} → [Hello Bonjour]
-```
-
-### Delete
-
-Removes the given key.
-
-```go-html-template
-{{ .Store.Set "greeting" "Hello" }}
-{{ .Store.Delete "greeting" }}
-```
diff --git a/docs/content/en/_common/syntax-highlighting-options.md b/docs/content/en/_common/syntax-highlighting-options.md
deleted file mode 100644
index 36144e090..000000000
--- a/docs/content/en/_common/syntax-highlighting-options.md
+++ /dev/null
@@ -1,56 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-anchorLineNos
-: (`bool`) Whether to render each line number as an HTML anchor element, setting the `id` attribute of the surrounding `span` element to the line number. Irrelevant if `lineNos` is `false`. Default is `false`.
-
-codeFences
-: (`bool`) Whether to highlight fenced code blocks. Default is `true`.
-
-guessSyntax
-: (`bool`) Whether to automatically detect the language if the `LANG` argument is blank or set to a language for which there is no corresponding [lexer](g). Falls back to a plain text lexer if unable to automatically detect the language. Default is `false`.
-
- > [!note]
- > The Chroma syntax highlighter includes lexers for approximately 250 languages, but only 5 of these have implemented automatic language detection.
-
-hl_Lines
-: (`string`) A space-delimited list of lines to emphasize within the highlighted code. To emphasize lines 2, 3, 4, and 7, set this value to `2-4 7`. This option is independent of the `lineNoStart` option.
-
-hl_inline
-: (`bool`) Whether to render the highlighted code without a wrapping container. Default is `false`.
-
-lineAnchors
-: (`string`) When rendering a line number as an HTML anchor element, prepend this value to the `id` attribute of the surrounding `span` element. This provides unique `id` attributes when a page contains two or more code blocks. Irrelevant if `lineNos` or `anchorLineNos` is `false`.
-
-lineNoStart
-: (`int`) The number to display at the beginning of the first line. Irrelevant if `lineNos` is `false`. Default is `1`.
-
-lineNos
-: (`any`) Controls line number display. Default is `false`.
- - `true`: Enable line numbers, controlled by `lineNumbersInTable`.
- - `false`: Disable line numbers.
- - `inline`: Enable inline line numbers (sets `lineNumbersInTable` to `false`).
- - `table`: Enable table-based line numbers (sets `lineNumbersInTable` to `true`).
-
-lineNumbersInTable
-: (`bool`) Whether to render the highlighted code in an HTML table with two cells. The left table cell contains the line numbers, while the right table cell contains the code. Irrelevant if `lineNos` is `false`. Default is `true`.
-
-noClasses
-: (`bool`) Whether to use inline CSS styles instead of an external CSS file. Default is `true`. To use an external CSS file, set this value to `false` and generate the CSS file from the command line:
-
- ```text
- hugo gen chromastyles --style=monokai > syntax.css
- ```
-
-style
-: (`string`) The CSS styles to apply to the highlighted code. Case-sensitive. Default is `monokai`. See [syntax highlighting styles].
-
-tabWidth
-: (`int`) Substitute this number of spaces for each tab character in your highlighted code. Irrelevant if `noClasses` is `false`. Default is `4`.
-
-wrapperClass
-: {{< new-in 0.140.2 />}}
-: (`string`) The class or classes to use for the outermost element of the highlighted code. Default is `highlight`.
-
-[syntax highlighting styles]: /quick-reference/syntax-highlighting-styles/
diff --git a/docs/content/en/_common/time-layout-string.md b/docs/content/en/_common/time-layout-string.md
deleted file mode 100644
index 3664eaef2..000000000
--- a/docs/content/en/_common/time-layout-string.md
+++ /dev/null
@@ -1,46 +0,0 @@
----
-_comment: Do not remove front matter.
----
-
-Format a `time.Time` value based on [Go's reference time]:
-
-[Go's reference time]: https://pkg.go.dev/time#pkg-constants
-
-```text
-Mon Jan 2 15:04:05 MST 2006
-```
-
-Create a layout string using these components:
-
-Description|Valid components
-:--|:--
-Year|`"2006" "06"`
-Month|`"Jan" "January" "01" "1"`
-Day of the week|`"Mon" "Monday"`
-Day of the month|`"2" "_2" "02"`
-Day of the year|`"__2" "002"`
-Hour|`"15" "3" "03"`
-Minute|`"4" "04"`
-Second|`"5" "05"`
-AM/PM mark|`"PM"`
-Time zone offsets|`"-0700" "-07:00" "-07" "-070000" "-07:00:00"`
-
-Replace the sign in the layout string with a Z to print Z instead of an offset for the UTC zone.
-
-Description|Valid components
-:--|:--
-Time zone offsets|`"Z0700" "Z07:00" "Z07" "Z070000" "Z07:00:00"`
-
-```go-html-template
-{{ $t := "2023-01-27T23:44:58-08:00" }}
-{{ $t = time.AsTime $t }}
-{{ $t = $t.Format "Jan 02, 2006 3:04 PM Z07:00" }}
-
-{{ $t }} → Jan 27, 2023 11:44 PM -08:00
-```
-
-Strings such as `PST` and `CET` are not time zones. They are time zone _abbreviations_.
-
-Strings such as `-07:00` and `+01:00` are not time zones. They are time zone _offsets_.
-
-A time zone is a geographic area with the same local time. For example, the time zone abbreviated by `PST` and `PDT` (depending on Daylight Savings Time) is `America/Los_Angeles`.
diff --git a/docs/content/en/_index.md b/docs/content/en/_index.md
deleted file mode 100644
index 358f6a4d9..000000000
--- a/docs/content/en/_index.md
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: The world's fastest framework for building websites
-description: Hugo is one of the most popular open-source static site generators. With its amazing speed and flexibility, Hugo makes building websites fun again.
----
diff --git a/docs/content/en/about/_index.md b/docs/content/en/about/_index.md
deleted file mode 100644
index e55800959..000000000
--- a/docs/content/en/about/_index.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
-title: About Hugo
-linkTitle: About
-description: Learn about Hugo and its features, privacy protections, and security model.
-categories: []
-keywords: []
-weight: 10
-aliases: [/about-hugo/,/docs/]
----
diff --git a/docs/content/en/about/features.md b/docs/content/en/about/features.md
deleted file mode 100644
index ff1a6b8eb..000000000
--- a/docs/content/en/about/features.md
+++ /dev/null
@@ -1,136 +0,0 @@
----
-title: Features
-description: Hugo's rich and powerful feature set provides the framework and tools to create static sites that build in seconds, often less.
-categories: []
-keywords: []
-weight: 20
----
-
-## Framework
-
-[Multiplatform]
-: Install Hugo's single executable on Linux, macOS, Windows, and more.
-
-[Multilingual]
-: Localize your project for each language and region, including translations, images, dates, currencies, numbers, percentages, and collation sequence. Hugo's multilingual framework supports single-host and multihost configurations.
-
-[Output formats]
-: Render each page of your site to one or more output formats, with granular control by page kind, section, and path. While HTML is the default output format, you can add JSON, RSS, CSV, and more. For example, create a REST API to access content.
-
-[Templates]
-: Create templates using variables, functions, and methods to transform your content, resources, and data into a published page. While HTML templates are the most common, you can create templates for any output format.
-
-[Themes]
-: Reduce development time and cost by using one of the hundreds of themes contributed by the Hugo community. Themes are available for corporate sites, documentation projects, image portfolios, landing pages, personal and professional blogs, resumes, CVs, and more.
-
-[Modules]
-: Reduce development time and cost by creating or importing packaged combinations of archetypes, assets, content, data, templates, translation tables, static files, or configuration settings. A module may serve as the basis for a new site, or to augment an existing site.
-
-[Privacy]
-: Configure your site to help comply with regional privacy regulations.
-
-[Security]
-: Hugo's security model is based on the premise that template and configuration authors are trusted, but content authors are not. This model enables generation of HTML output safe against code injection. Other protections prevent "shelling out" to arbitrary applications, limit access to specific environment variables, prevent connections to arbitrary remote data sources, and more.
-
-## Content authoring
-
-[Content formats]
-: Create your content using Markdown, HTML, AsciiDoc, Emacs Org Mode, Pandoc, or reStructuredText. Markdown is the default content format, conforming to the [CommonMark] and [GitHub Flavored Markdown] specifications.
-
-[Markdown attributes]
-: Apply HTML attributes such as `class` and `id` to Markdown images and block elements including blockquotes, fenced code blocks, headings, horizontal rules, lists, paragraphs, and tables.
-
-[Markdown extensions]
-: Leverage the embedded Markdown extensions to create tables, definition lists, footnotes, task lists, inserted text, mark text, subscripts, superscripts, and more.
-
-[Markdown render hooks]
-: Override the conversion of Markdown to HTML when rendering blockquotes, fenced code blocks, headings, images, links, and tables. For example, render every standalone image as an HTML `figure` element.
-
-[Diagrams]
-: Use fenced code blocks and Markdown render hooks to include diagrams in your content.
-
-[Mathematics]
-: Include mathematical equations and expressions in Markdown using LaTeX markup.
-
-[Syntax highlighting]
-: Syntactically highlight code examples using Hugo's embedded syntax highlighter, enabled by default for fenced code blocks in Markdown. The syntax highlighter supports hundreds of code languages and dozens of styles.
-
-[Shortcodes]
-: Use Hugo's embedded shortcodes, or create your own, to insert complex content. For example, use shortcodes to include `audio` and `video` elements, render tables from local or remote data sources, insert snippets from other pages, and more.
-
-## Content management
-
-[Content adapters]
-: Create content adapters to dynamically add content when building your site. For example, use a content adapter to create pages from a remote data source such as JSON, TOML, YAML, or XML.
-
-[Taxonomies]
-: Classify content to establish simple or complex logical relationships between pages. For example, create an authors taxonomy, and assign one or more authors to each page. Among other uses, the taxonomy system provides an inverted, weighted index to render a list of related pages, ordered by relevance.
-
-[Data]
-: Augment your content using local or remote data sources including CSV, JSON, TOML, YAML, and XML. For example, create a shortcode to render an HTML table from a remote CSV file.
-
-[Menus]
-: Provide rapid access to content via Hugo's menu system, configured automatically, globally, or on a page-by-page basis. The menu system is a key component of Hugo's multilingual architecture.
-
-[URL management]
-: Serve any page from any path via global configuration or on a page-by-page basis.
-
-## Asset pipelines
-
-[Image processing]
-: Convert, resize, crop, rotate, adjust colors, apply filters, overlay text and images, and extract EXIF data.
-
-[JavaScript bundling]
-: Transpile TypeScript and JSX to JavaScript, bundle, tree shake, minify, create source maps, and perform SRI hashing.
-
-[Sass processing]
-: Transpile Sass to CSS, bundle, tree shake, minify, create source maps, perform SRI hashing, and integrate with PostCSS.
-
-[Tailwind CSS processing]
-: Compile Tailwind CSS utility classes into standard CSS, bundle, tree shake, optimize, minify, perform SRI hashing, and integrate with PostCSS.
-
-## Performance
-
-[Caching]
-: Reduce build time and cost by rendering a partial template once then cache the result, either globally or within a given context. For example, cache the result of an asset pipeline to prevent reprocessing on every rendered page.
-
-[Segmentation]
-: Reduce build time and cost by partitioning your sites into segments. For example, render the home page and the "news section" every hour, and render the entire site once a week.
-
-[Minification]
-: Minify HTML, CSS, and JavaScript to reduce file size, bandwidth consumption, and loading times.
-
-[Multilingual]: /content-management/multilingual/
-[Multiplatform]: /installation/
-[Output formats]: /configuration/output-formats/
-[Templates]: /templates/introduction/
-[Themes]: https://themes.gohugo.io/
-[Modules]: /hugo-modules/
-[Privacy]: /configuration/privacy/
-[Security]: /about/security/
-
-[Content formats]: /content-management/formats/
-[CommonMark]: https://spec.commonmark.org/current/
-[GitHub Flavored Markdown]: https://github.github.com/gfm/
-[Markdown attributes]: /content-management/markdown-attributes/
-[Markdown extensions]: /configuration/markup/#extensions
-[Markdown render hooks]: /render-hooks/introduction/
-[Diagrams]: /content-management/diagrams/
-[Mathematics]: /content-management/mathematics/
-[Syntax highlighting]: /content-management/syntax-highlighting/
-[Shortcodes]: /content-management/shortcodes/
-
-[Content adapters]: /content-management/content-adapters/
-[Taxonomies]: /content-management/taxonomies/
-[Data]: /content-management/data-sources/
-[Menus]: /content-management/menus/
-[URL management]: /content-management/urls/
-
-[Image processing]: /content-management/image-processing/
-[JavaScript bundling]: /functions/js/build/
-[Sass processing]: /functions/css/Sass/
-[Tailwind CSS processing]: /functions/css/tailwindcss/
-
-[Caching]: /functions/partials/includecached/
-[Segmentation]: /configuration/segments/
-[Minification]: /configuration/minify/
diff --git a/docs/content/en/about/introduction.md b/docs/content/en/about/introduction.md
deleted file mode 100644
index 9586d08f8..000000000
--- a/docs/content/en/about/introduction.md
+++ /dev/null
@@ -1,34 +0,0 @@
----
-title: Introduction
-description: Hugo is a static site generator written in Go, optimized for speed and designed for flexibility.
-categories: []
-keywords: []
-weight: 10
-aliases: [/about/what-is-hugo/,/about/benefits/]
----
-
-Hugo is a [static site generator] written in [Go], optimized for speed and designed for flexibility. With its advanced templating system and fast asset pipelines, Hugo renders a complete site in seconds, often less.
-
-Due to its flexible framework, multilingual support, and powerful taxonomy system, Hugo is widely used to create:
-
-- Corporate, government, nonprofit, education, news, event, and project sites
-- Documentation sites
-- Image portfolios
-- Landing pages
-- Business, professional, and personal blogs
-- Resumes and CVs
-
-Use Hugo's embedded web server during development to instantly see changes to content, structure, behavior, and presentation. Then deploy the site to your host, or push changes to your Git provider for automated builds and deployment.
-
-And with [Hugo Modules], you can share content, assets, data, translations, themes, templates, and configuration with other projects via public or private Git repositories.
-
-Learn more about Hugo's [features], [privacy protections], and [security model].
-
-[Go]: https://go.dev
-[Hugo Modules]: /hugo-modules/
-[static site generator]: https://en.wikipedia.org/wiki/Static_site_generator
-[features]: /about/features/
-[security model]: about/security/
-[privacy protections]: /configuration/privacy
-
-{{< youtube 0RKpf3rK57I >}}
diff --git a/docs/content/en/about/license.md b/docs/content/en/about/license.md
deleted file mode 100644
index 06a3a695d..000000000
--- a/docs/content/en/about/license.md
+++ /dev/null
@@ -1,75 +0,0 @@
----
-title: License
-description: Hugo is released under the Apache 2.0 license.
-categories: []
-keywords: []
-weight: 40
----
-
-## Apache License
-
-_Version 2.0, January 2004_
-__
-
-### Terms and Conditions for use, reproduction, and distribution
-
-#### 1. Definitions
-
-“License” shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
-
-“Licensor” shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
-
-“Legal Entity” shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, “control” means **(i)** the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the outstanding shares, or **(iii)** beneficial ownership of such entity.
-
-“You” (or “Your”) shall mean an individual or Legal Entity exercising permissions granted by this License.
-
-“Source” form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
-
-“Object” form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
-
-“Work” shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
-
-“Derivative Works” shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
-
-“Contribution” shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, “submitted” means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as “Not a Contribution.”
-
-“Contributor” shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
-
-#### 2. Grant of Copyright License
-
-Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
-
-#### 3. Grant of Patent License
-
-Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
-
-#### 4. Redistribution
-
-You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
-
-* **(a)** You must give any other recipients of the Work or Derivative Works a copy of this License; and
-* **(b)** You must cause any modified files to carry prominent notices stating that You changed the files; and
-* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
-* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
-
-You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
-
-#### 5. Submission of Contributions
-
-Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
-
-#### 6. Trademarks
-
-This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
-
-#### 7. Disclaimer of Warranty
-
-Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
-
-#### 8. Limitation of Liability
-
-In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
-
-#### 9. Accepting Warranty or Additional Liability
-
-While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
diff --git a/docs/content/en/about/security.md b/docs/content/en/about/security.md
deleted file mode 100644
index 509ca6a75..000000000
--- a/docs/content/en/about/security.md
+++ /dev/null
@@ -1,58 +0,0 @@
----
-title: Security model
-linkTitle: Security
-description: A summary of Hugo's security model.
-categories: []
-keywords: []
-weight: 30
-aliases: [/about/security-model/]
----
-
-## Runtime security
-
-Hugo generates static websites, meaning the final output runs directly in the browser and interacts with any integrated APIs. However, during development and site building, the `hugo` executable itself is the runtime environment.
-
-Securing a runtime is a complex task. Hugo addresses this through a robust sandboxing approach and a strict security policy with default protections. Key features include:
-
-- Virtual file system: Hugo employs a virtual file system, limiting file access. Only the main project, not external components, can access files or directories outside the project root.
-- Read-Only access: User-defined components have read-only access to the file system, preventing unintended modifications.
-- Controlled external binaries: While Hugo utilizes external binaries for features like Asciidoctor support, these are strictly predefined with specific flags and are disabled by default. The [security policy] details these limitations.
-- No arbitrary commands: To mitigate risks, Hugo intentionally avoids implementing general functions that would allow users to execute arbitrary operating system commands.
-
-This combination of sandboxing and strict defaults effectively minimizes potential security vulnerabilities during the Hugo build process.
-
-[security policy]: /configuration/security/
-
-## Dependency security
-
-Hugo utilizes [Go Modules] to manage its dependencies, compiling as a static binary. Go Modules create a `go.sum` file, a critical security feature. This file acts as a database, storing the expected cryptographic checksums of all dependencies, including those required indirectly (transitive dependencies).
-
-[Hugo Modules], which extend Go Modules' functionality, also produce a `go.sum` file. To ensure dependency integrity, commit this `go.sum` file to your version control. If Hugo detects a checksum mismatch during the build process, it will fail, indicating a possible attempt to [tamper with your project's dependencies].
-
-[Go Modules]: https://go.dev/wiki/Modules#modules
-[Hugo Modules]: /hugo-modules/
-[tamper with your project's dependencies]: https://julienrenaux.fr/2019/12/20/github-actions-security-risk/
-
-## Web application security
-
-Hugo's security philosophy is rooted in established security standards, primarily aligning with the threats defined by [OWASP]. For HTML output, Hugo operates under a clear trust model. This model assumes that template and configuration authors, the developers, are trustworthy. However, the data supplied to these templates is inherently considered untrusted. This distinction is crucial for understanding how Hugo handles potential security risks.
-
-[OWASP]: https://en.wikipedia.org/wiki/OWASP
-
-To prevent unintended escaping of data that developers know is safe, Hugo provides [`safe`] functions, such as [`safeHTML`]. These functions allow developers to explicitly mark data as trusted, bypassing the default escaping mechanisms. This is essential for scenarios where data is generated or sourced from reliable sources. However, an exception exists: enabling [inline shortcodes]. By activating this feature, you are implicitly trusting the logic within the shortcodes and the data contained within your content files.
-
-[`safeHTML`]: /functions/safe/html/
-[inline shortcodes]: /content-management/shortcodes/#inline
-
-It's vital to remember that Hugo is a static site generator. This architectural choice significantly reduces the attack surface by eliminating the complexities and vulnerabilities associated with dynamic user input. Unlike dynamic websites, Hugo generates static HTML files, minimizing the risk of real-time attacks. Regarding content, Hugo's default Markdown renderer is [configured to sanitize] potentially unsafe content. This default behavior ensures that potentially malicious code or scripts are removed or escaped. However, this setting can be reconfigured if you have a high degree of confidence in the safety of your content sources.
-
-[configured to sanitize]: /configuration/markup/#rendererunsafe
-
-In essence, Hugo prioritizes secure output by establishing a clear trust boundary between developers and data. By default, it errs on the side of caution, sanitizing potentially unsafe content and escaping data. Developers have the flexibility to adjust these defaults through [`safe`] functions and [configuration options], but they must do so with a clear understanding of the security implications. Hugo's static site generation model further strengthens its security posture by minimizing dynamic vulnerabilities.
-
-[`safe`]: /functions/safe
-[configuration options]: /configuration/security
-
-## Configuration
-
-See [configure security](/configuration/security/).
diff --git a/docs/content/en/commands/_index.md b/docs/content/en/commands/_index.md
deleted file mode 100644
index 5869bfd9d..000000000
--- a/docs/content/en/commands/_index.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: Command line interface
-linkTitle: CLI
-description: Use the command line interface (CLI) to manage your site.
-categories: []
-keywords: []
-weight: 10
----
diff --git a/docs/content/en/commands/hugo.md b/docs/content/en/commands/hugo.md
deleted file mode 100644
index ef0bca9a5..000000000
--- a/docs/content/en/commands/hugo.md
+++ /dev/null
@@ -1,84 +0,0 @@
----
-title: "hugo"
-slug: hugo
-url: /commands/hugo/
----
-## hugo
-
-Build your site
-
-### Synopsis
-
-hugo is the main command, used to build your Hugo site.
-
-Hugo is a Fast and Flexible Static Site Generator
-built with love by spf13 and friends in Go.
-
-Complete documentation is available at https://gohugo.io/.
-
-```
-hugo [flags]
-```
-
-### Options
-
-```
- -b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
- -D, --buildDrafts include content marked as draft
- -E, --buildExpired include expired content
- -F, --buildFuture include content with publishdate in the future
- --cacheDir string filesystem path to cache directory
- --cleanDestinationDir remove files from destination not found in static directories
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -c, --contentDir string filesystem path to content directory
- -d, --destination string filesystem path to write files to
- --disableKinds strings disable different kind of pages (home, RSS etc.)
- --enableGitInfo add Git revision, date, author, and CODEOWNERS info to the pages
- -e, --environment string build environment
- --forceSyncStatic copy all files when static is changed.
- --gc enable to run some cleanup tasks (remove unused cache files) after the build
- -h, --help help for hugo
- --ignoreCache ignores the cache directory
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- -l, --layoutDir string filesystem path to layout directory
- --logLevel string log level (debug|info|warn|error)
- --minify minify any supported output format (HTML, XML etc.)
- --noBuildLock don't create .hugo_build.lock file
- --noChmod don't sync permission mode of files
- --noTimes don't sync modification time of files
- --panicOnWarning panic on first WARNING log
- --poll string set this to a poll interval, e.g --poll 700ms, to use a poll based approach to watch for file system changes
- --printI18nWarnings print missing translations
- --printMemoryUsage print memory usage to screen at intervals
- --printPathWarnings print warnings on duplicate target paths etc.
- --printUnusedTemplates print warnings on unused templates.
- --quiet build in quiet mode
- --renderSegments strings named segments to render (configured in the segments config)
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --templateMetrics display metrics about template executions
- --templateMetricsHints calculate some improvement hints when combined with --templateMetrics
- -t, --theme strings themes to use (located in /themes/THEMENAME/)
- --themesDir string filesystem path to themes directory
- --trace file write trace to file (not useful in general)
- -w, --watch watch filesystem for changes and recreate as needed
-```
-
-### SEE ALSO
-
-* [hugo build](/commands/hugo_build/) - Build your site
-* [hugo completion](/commands/hugo_completion/) - Generate the autocompletion script for the specified shell
-* [hugo config](/commands/hugo_config/) - Display site configuration
-* [hugo convert](/commands/hugo_convert/) - Convert front matter to another format
-* [hugo deploy](/commands/hugo_deploy/) - Deploy your site to a cloud provider
-* [hugo env](/commands/hugo_env/) - Display version and environment info
-* [hugo gen](/commands/hugo_gen/) - Generate documentation and syntax highlighting styles
-* [hugo import](/commands/hugo_import/) - Import a site from another system
-* [hugo list](/commands/hugo_list/) - List content
-* [hugo mod](/commands/hugo_mod/) - Manage modules
-* [hugo new](/commands/hugo_new/) - Create new content
-* [hugo server](/commands/hugo_server/) - Start the embedded web server
-* [hugo version](/commands/hugo_version/) - Display version
-
diff --git a/docs/content/en/commands/hugo_build.md b/docs/content/en/commands/hugo_build.md
deleted file mode 100644
index 582cbe511..000000000
--- a/docs/content/en/commands/hugo_build.md
+++ /dev/null
@@ -1,72 +0,0 @@
----
-title: "hugo build"
-slug: hugo_build
-url: /commands/hugo_build/
----
-## hugo build
-
-Build your site
-
-### Synopsis
-
-build is the main command, used to build your Hugo site.
-
-Hugo is a Fast and Flexible Static Site Generator
-built with love by spf13 and friends in Go.
-
-Complete documentation is available at https://gohugo.io/.
-
-```
-hugo build [flags]
-```
-
-### Options
-
-```
- -b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
- -D, --buildDrafts include content marked as draft
- -E, --buildExpired include expired content
- -F, --buildFuture include content with publishdate in the future
- --cacheDir string filesystem path to cache directory
- --cleanDestinationDir remove files from destination not found in static directories
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -c, --contentDir string filesystem path to content directory
- -d, --destination string filesystem path to write files to
- --disableKinds strings disable different kind of pages (home, RSS etc.)
- --enableGitInfo add Git revision, date, author, and CODEOWNERS info to the pages
- -e, --environment string build environment
- --forceSyncStatic copy all files when static is changed.
- --gc enable to run some cleanup tasks (remove unused cache files) after the build
- -h, --help help for build
- --ignoreCache ignores the cache directory
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- -l, --layoutDir string filesystem path to layout directory
- --logLevel string log level (debug|info|warn|error)
- --minify minify any supported output format (HTML, XML etc.)
- --noBuildLock don't create .hugo_build.lock file
- --noChmod don't sync permission mode of files
- --noTimes don't sync modification time of files
- --panicOnWarning panic on first WARNING log
- --poll string set this to a poll interval, e.g --poll 700ms, to use a poll based approach to watch for file system changes
- --printI18nWarnings print missing translations
- --printMemoryUsage print memory usage to screen at intervals
- --printPathWarnings print warnings on duplicate target paths etc.
- --printUnusedTemplates print warnings on unused templates.
- --quiet build in quiet mode
- --renderSegments strings named segments to render (configured in the segments config)
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --templateMetrics display metrics about template executions
- --templateMetricsHints calculate some improvement hints when combined with --templateMetrics
- -t, --theme strings themes to use (located in /themes/THEMENAME/)
- --themesDir string filesystem path to themes directory
- --trace file write trace to file (not useful in general)
- -w, --watch watch filesystem for changes and recreate as needed
-```
-
-### SEE ALSO
-
-* [hugo](/commands/hugo/) - Build your site
-
diff --git a/docs/content/en/commands/hugo_completion.md b/docs/content/en/commands/hugo_completion.md
deleted file mode 100644
index ac60dc148..000000000
--- a/docs/content/en/commands/hugo_completion.md
+++ /dev/null
@@ -1,46 +0,0 @@
----
-title: "hugo completion"
-slug: hugo_completion
-url: /commands/hugo_completion/
----
-## hugo completion
-
-Generate the autocompletion script for the specified shell
-
-### Synopsis
-
-Generate the autocompletion script for hugo for the specified shell.
-See each sub-command's help for details on how to use the generated script.
-
-
-### Options
-
-```
- -h, --help help for completion
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo](/commands/hugo/) - Build your site
-* [hugo completion bash](/commands/hugo_completion_bash/) - Generate the autocompletion script for bash
-* [hugo completion fish](/commands/hugo_completion_fish/) - Generate the autocompletion script for fish
-* [hugo completion powershell](/commands/hugo_completion_powershell/) - Generate the autocompletion script for powershell
-* [hugo completion zsh](/commands/hugo_completion_zsh/) - Generate the autocompletion script for zsh
-
diff --git a/docs/content/en/commands/hugo_completion_bash.md b/docs/content/en/commands/hugo_completion_bash.md
deleted file mode 100644
index 41fb47c0c..000000000
--- a/docs/content/en/commands/hugo_completion_bash.md
+++ /dev/null
@@ -1,65 +0,0 @@
----
-title: "hugo completion bash"
-slug: hugo_completion_bash
-url: /commands/hugo_completion_bash/
----
-## hugo completion bash
-
-Generate the autocompletion script for bash
-
-### Synopsis
-
-Generate the autocompletion script for the bash shell.
-
-This script depends on the 'bash-completion' package.
-If it is not installed already, you can install it via your OS's package manager.
-
-To load completions in your current shell session:
-
- source <(hugo completion bash)
-
-To load completions for every new session, execute once:
-
-#### Linux:
-
- hugo completion bash > /etc/bash_completion.d/hugo
-
-#### macOS:
-
- hugo completion bash > $(brew --prefix)/etc/bash_completion.d/hugo
-
-You will need to start a new shell for this setup to take effect.
-
-
-```
-hugo completion bash
-```
-
-### Options
-
-```
- -h, --help help for bash
- --no-descriptions disable completion descriptions
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo completion](/commands/hugo_completion/) - Generate the autocompletion script for the specified shell
-
diff --git a/docs/content/en/commands/hugo_completion_fish.md b/docs/content/en/commands/hugo_completion_fish.md
deleted file mode 100644
index 7f971c3ca..000000000
--- a/docs/content/en/commands/hugo_completion_fish.md
+++ /dev/null
@@ -1,56 +0,0 @@
----
-title: "hugo completion fish"
-slug: hugo_completion_fish
-url: /commands/hugo_completion_fish/
----
-## hugo completion fish
-
-Generate the autocompletion script for fish
-
-### Synopsis
-
-Generate the autocompletion script for the fish shell.
-
-To load completions in your current shell session:
-
- hugo completion fish | source
-
-To load completions for every new session, execute once:
-
- hugo completion fish > ~/.config/fish/completions/hugo.fish
-
-You will need to start a new shell for this setup to take effect.
-
-
-```
-hugo completion fish [flags]
-```
-
-### Options
-
-```
- -h, --help help for fish
- --no-descriptions disable completion descriptions
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo completion](/commands/hugo_completion/) - Generate the autocompletion script for the specified shell
-
diff --git a/docs/content/en/commands/hugo_completion_powershell.md b/docs/content/en/commands/hugo_completion_powershell.md
deleted file mode 100644
index 6ea17892b..000000000
--- a/docs/content/en/commands/hugo_completion_powershell.md
+++ /dev/null
@@ -1,53 +0,0 @@
----
-title: "hugo completion powershell"
-slug: hugo_completion_powershell
-url: /commands/hugo_completion_powershell/
----
-## hugo completion powershell
-
-Generate the autocompletion script for powershell
-
-### Synopsis
-
-Generate the autocompletion script for powershell.
-
-To load completions in your current shell session:
-
- hugo completion powershell | Out-String | Invoke-Expression
-
-To load completions for every new session, add the output of the above command
-to your powershell profile.
-
-
-```
-hugo completion powershell [flags]
-```
-
-### Options
-
-```
- -h, --help help for powershell
- --no-descriptions disable completion descriptions
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo completion](/commands/hugo_completion/) - Generate the autocompletion script for the specified shell
-
diff --git a/docs/content/en/commands/hugo_completion_zsh.md b/docs/content/en/commands/hugo_completion_zsh.md
deleted file mode 100644
index b9e79f9f3..000000000
--- a/docs/content/en/commands/hugo_completion_zsh.md
+++ /dev/null
@@ -1,67 +0,0 @@
----
-title: "hugo completion zsh"
-slug: hugo_completion_zsh
-url: /commands/hugo_completion_zsh/
----
-## hugo completion zsh
-
-Generate the autocompletion script for zsh
-
-### Synopsis
-
-Generate the autocompletion script for the zsh shell.
-
-If shell completion is not already enabled in your environment you will need
-to enable it. You can execute the following once:
-
- echo "autoload -U compinit; compinit" >> ~/.zshrc
-
-To load completions in your current shell session:
-
- source <(hugo completion zsh)
-
-To load completions for every new session, execute once:
-
-#### Linux:
-
- hugo completion zsh > "${fpath[1]}/_hugo"
-
-#### macOS:
-
- hugo completion zsh > $(brew --prefix)/share/zsh/site-functions/_hugo
-
-You will need to start a new shell for this setup to take effect.
-
-
-```
-hugo completion zsh [flags]
-```
-
-### Options
-
-```
- -h, --help help for zsh
- --no-descriptions disable completion descriptions
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo completion](/commands/hugo_completion/) - Generate the autocompletion script for the specified shell
-
diff --git a/docs/content/en/commands/hugo_config.md b/docs/content/en/commands/hugo_config.md
deleted file mode 100644
index 2b4eaafa1..000000000
--- a/docs/content/en/commands/hugo_config.md
+++ /dev/null
@@ -1,53 +0,0 @@
----
-title: "hugo config"
-slug: hugo_config
-url: /commands/hugo_config/
----
-## hugo config
-
-Display site configuration
-
-### Synopsis
-
-Display site configuration, both default and custom settings.
-
-```
-hugo config [command] [flags]
-```
-
-### Options
-
-```
- -b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
- --cacheDir string filesystem path to cache directory
- -c, --contentDir string filesystem path to content directory
- --format string preferred file format (toml, yaml or json) (default "toml")
- -h, --help help for config
- --lang string the language to display config for. Defaults to the first language defined.
- --printZero include config options with zero values (e.g. false, 0, "") in the output
- --renderSegments strings named segments to render (configured in the segments config)
- -t, --theme strings themes to use (located in /themes/THEMENAME/)
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo](/commands/hugo/) - Build your site
-* [hugo config mounts](/commands/hugo_config_mounts/) - Print the configured file mounts
-
diff --git a/docs/content/en/commands/hugo_config_mounts.md b/docs/content/en/commands/hugo_config_mounts.md
deleted file mode 100644
index 06a781220..000000000
--- a/docs/content/en/commands/hugo_config_mounts.md
+++ /dev/null
@@ -1,45 +0,0 @@
----
-title: "hugo config mounts"
-slug: hugo_config_mounts
-url: /commands/hugo_config_mounts/
----
-## hugo config mounts
-
-Print the configured file mounts
-
-```
-hugo config mounts [flags] [args]
-```
-
-### Options
-
-```
- -b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
- --cacheDir string filesystem path to cache directory
- -c, --contentDir string filesystem path to content directory
- -h, --help help for mounts
- --renderSegments strings named segments to render (configured in the segments config)
- -t, --theme strings themes to use (located in /themes/THEMENAME/)
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo config](/commands/hugo_config/) - Display site configuration
-
diff --git a/docs/content/en/commands/hugo_convert.md b/docs/content/en/commands/hugo_convert.md
deleted file mode 100644
index a8d0b6a38..000000000
--- a/docs/content/en/commands/hugo_convert.md
+++ /dev/null
@@ -1,47 +0,0 @@
----
-title: "hugo convert"
-slug: hugo_convert
-url: /commands/hugo_convert/
----
-## hugo convert
-
-Convert front matter to another format
-
-### Synopsis
-
-Convert front matter to another format.
-
-See convert's subcommands toJSON, toTOML and toYAML for more information.
-
-### Options
-
-```
- -h, --help help for convert
- -o, --output string filesystem path to write files to
- --unsafe enable less safe operations, please backup first
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo](/commands/hugo/) - Build your site
-* [hugo convert toJSON](/commands/hugo_convert_tojson/) - Convert front matter to JSON
-* [hugo convert toTOML](/commands/hugo_convert_totoml/) - Convert front matter to TOML
-* [hugo convert toYAML](/commands/hugo_convert_toyaml/) - Convert front matter to YAML
-
diff --git a/docs/content/en/commands/hugo_convert_toJSON.md b/docs/content/en/commands/hugo_convert_toJSON.md
deleted file mode 100644
index fe81146f9..000000000
--- a/docs/content/en/commands/hugo_convert_toJSON.md
+++ /dev/null
@@ -1,47 +0,0 @@
----
-title: "hugo convert toJSON"
-slug: hugo_convert_toJSON
-url: /commands/hugo_convert_tojson/
----
-## hugo convert toJSON
-
-Convert front matter to JSON
-
-### Synopsis
-
-toJSON converts all front matter in the content directory
-to use JSON for the front matter.
-
-```
-hugo convert toJSON [flags] [args]
-```
-
-### Options
-
-```
- -h, --help help for toJSON
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- -o, --output string filesystem path to write files to
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
- --unsafe enable less safe operations, please backup first
-```
-
-### SEE ALSO
-
-* [hugo convert](/commands/hugo_convert/) - Convert front matter to another format
-
diff --git a/docs/content/en/commands/hugo_convert_toTOML.md b/docs/content/en/commands/hugo_convert_toTOML.md
deleted file mode 100644
index 490b15ee6..000000000
--- a/docs/content/en/commands/hugo_convert_toTOML.md
+++ /dev/null
@@ -1,47 +0,0 @@
----
-title: "hugo convert toTOML"
-slug: hugo_convert_toTOML
-url: /commands/hugo_convert_totoml/
----
-## hugo convert toTOML
-
-Convert front matter to TOML
-
-### Synopsis
-
-toTOML converts all front matter in the content directory
-to use TOML for the front matter.
-
-```
-hugo convert toTOML [flags] [args]
-```
-
-### Options
-
-```
- -h, --help help for toTOML
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- -o, --output string filesystem path to write files to
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
- --unsafe enable less safe operations, please backup first
-```
-
-### SEE ALSO
-
-* [hugo convert](/commands/hugo_convert/) - Convert front matter to another format
-
diff --git a/docs/content/en/commands/hugo_convert_toYAML.md b/docs/content/en/commands/hugo_convert_toYAML.md
deleted file mode 100644
index 9b00ce247..000000000
--- a/docs/content/en/commands/hugo_convert_toYAML.md
+++ /dev/null
@@ -1,47 +0,0 @@
----
-title: "hugo convert toYAML"
-slug: hugo_convert_toYAML
-url: /commands/hugo_convert_toyaml/
----
-## hugo convert toYAML
-
-Convert front matter to YAML
-
-### Synopsis
-
-toYAML converts all front matter in the content directory
-to use YAML for the front matter.
-
-```
-hugo convert toYAML [flags] [args]
-```
-
-### Options
-
-```
- -h, --help help for toYAML
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- -o, --output string filesystem path to write files to
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
- --unsafe enable less safe operations, please backup first
-```
-
-### SEE ALSO
-
-* [hugo convert](/commands/hugo_convert/) - Convert front matter to another format
-
diff --git a/docs/content/en/commands/hugo_deploy.md b/docs/content/en/commands/hugo_deploy.md
deleted file mode 100644
index 696acf51f..000000000
--- a/docs/content/en/commands/hugo_deploy.md
+++ /dev/null
@@ -1,55 +0,0 @@
----
-title: "hugo deploy"
-slug: hugo_deploy
-url: /commands/hugo_deploy/
----
-## hugo deploy
-
-Deploy your site to a cloud provider
-
-### Synopsis
-
-Deploy your site to a cloud provider
-
-See https://gohugo.io/hosting-and-deployment/hugo-deploy/ for detailed
-documentation.
-
-
-```
-hugo deploy [flags] [args]
-```
-
-### Options
-
-```
- --confirm ask for confirmation before making changes to the target
- --dryRun dry run
- --force force upload of all files
- -h, --help help for deploy
- --invalidateCDN invalidate the CDN cache listed in the deployment target (default true)
- --maxDeletes int maximum # of files to delete, or -1 to disable (default 256)
- --target string target deployment from deployments section in config file; defaults to the first one
- --workers int number of workers to transfer files. defaults to 10 (default 10)
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo](/commands/hugo/) - Build your site
-
diff --git a/docs/content/en/commands/hugo_env.md b/docs/content/en/commands/hugo_env.md
deleted file mode 100644
index 7e21733a4..000000000
--- a/docs/content/en/commands/hugo_env.md
+++ /dev/null
@@ -1,44 +0,0 @@
----
-title: "hugo env"
-slug: hugo_env
-url: /commands/hugo_env/
----
-## hugo env
-
-Display version and environment info
-
-### Synopsis
-
-Display version and environment info. This is useful in Hugo bug reports
-
-```
-hugo env [flags] [args]
-```
-
-### Options
-
-```
- -h, --help help for env
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo](/commands/hugo/) - Build your site
-
diff --git a/docs/content/en/commands/hugo_gen.md b/docs/content/en/commands/hugo_gen.md
deleted file mode 100644
index ae11a0321..000000000
--- a/docs/content/en/commands/hugo_gen.md
+++ /dev/null
@@ -1,43 +0,0 @@
----
-title: "hugo gen"
-slug: hugo_gen
-url: /commands/hugo_gen/
----
-## hugo gen
-
-Generate documentation and syntax highlighting styles
-
-### Synopsis
-
-Generate documentation for your project using Hugo's documentation engine, including syntax highlighting for various programming languages.
-
-### Options
-
-```
- -h, --help help for gen
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo](/commands/hugo/) - Build your site
-* [hugo gen chromastyles](/commands/hugo_gen_chromastyles/) - Generate CSS stylesheet for the Chroma code highlighter
-* [hugo gen doc](/commands/hugo_gen_doc/) - Generate Markdown documentation for the Hugo CLI
-* [hugo gen man](/commands/hugo_gen_man/) - Generate man pages for the Hugo CLI
-
diff --git a/docs/content/en/commands/hugo_gen_chromastyles.md b/docs/content/en/commands/hugo_gen_chromastyles.md
deleted file mode 100644
index 2863e46b4..000000000
--- a/docs/content/en/commands/hugo_gen_chromastyles.md
+++ /dev/null
@@ -1,50 +0,0 @@
----
-title: "hugo gen chromastyles"
-slug: hugo_gen_chromastyles
-url: /commands/hugo_gen_chromastyles/
----
-## hugo gen chromastyles
-
-Generate CSS stylesheet for the Chroma code highlighter
-
-### Synopsis
-
-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
-
-```
-hugo gen chromastyles [flags] [args]
-```
-
-### Options
-
-```
- -h, --help help for chromastyles
- --highlightStyle string foreground and background colors for highlighted lines, e.g. --highlightStyle "#fff000 bg:#000fff"
- --lineNumbersInlineStyle string foreground and background colors for inline line numbers, e.g. --lineNumbersInlineStyle "#fff000 bg:#000fff"
- --lineNumbersTableStyle string foreground and background colors for table line numbers, e.g. --lineNumbersTableStyle "#fff000 bg:#000fff"
- --style string highlighter style (see https://xyproto.github.io/splash/docs/) (default "friendly")
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo gen](/commands/hugo_gen/) - Generate documentation and syntax highlighting styles
-
diff --git a/docs/content/en/commands/hugo_gen_doc.md b/docs/content/en/commands/hugo_gen_doc.md
deleted file mode 100644
index 3d808e75c..000000000
--- a/docs/content/en/commands/hugo_gen_doc.md
+++ /dev/null
@@ -1,50 +0,0 @@
----
-title: "hugo gen doc"
-slug: hugo_gen_doc
-url: /commands/hugo_gen_doc/
----
-## hugo gen doc
-
-Generate Markdown documentation for the Hugo CLI
-
-### Synopsis
-
-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.
-
-```
-hugo gen doc [flags] [args]
-```
-
-### Options
-
-```
- --dir string the directory to write the doc. (default "/tmp/hugodoc/")
- -h, --help help for doc
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo gen](/commands/hugo_gen/) - Generate documentation and syntax highlighting styles
-
diff --git a/docs/content/en/commands/hugo_gen_man.md b/docs/content/en/commands/hugo_gen_man.md
deleted file mode 100644
index 14fe859e3..000000000
--- a/docs/content/en/commands/hugo_gen_man.md
+++ /dev/null
@@ -1,47 +0,0 @@
----
-title: "hugo gen man"
-slug: hugo_gen_man
-url: /commands/hugo_gen_man/
----
-## hugo gen man
-
-Generate man pages for the Hugo CLI
-
-### Synopsis
-
-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.
-
-```
-hugo gen man [flags] [args]
-```
-
-### Options
-
-```
- --dir string the directory to write the man pages. (default "man/")
- -h, --help help for man
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo gen](/commands/hugo_gen/) - Generate documentation and syntax highlighting styles
-
diff --git a/docs/content/en/commands/hugo_import.md b/docs/content/en/commands/hugo_import.md
deleted file mode 100644
index 2b8e62951..000000000
--- a/docs/content/en/commands/hugo_import.md
+++ /dev/null
@@ -1,43 +0,0 @@
----
-title: "hugo import"
-slug: hugo_import
-url: /commands/hugo_import/
----
-## hugo import
-
-Import a site from another system
-
-### Synopsis
-
-Import a site from another system.
-
-Import requires a subcommand, e.g. `hugo import jekyll jekyll_root_path target_path`.
-
-### Options
-
-```
- -h, --help help for import
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo](/commands/hugo/) - Build your site
-* [hugo import jekyll](/commands/hugo_import_jekyll/) - hugo import from Jekyll
-
diff --git a/docs/content/en/commands/hugo_import_jekyll.md b/docs/content/en/commands/hugo_import_jekyll.md
deleted file mode 100644
index 8746c156e..000000000
--- a/docs/content/en/commands/hugo_import_jekyll.md
+++ /dev/null
@@ -1,47 +0,0 @@
----
-title: "hugo import jekyll"
-slug: hugo_import_jekyll
-url: /commands/hugo_import_jekyll/
----
-## hugo import jekyll
-
-hugo import from Jekyll
-
-### Synopsis
-
-hugo import from Jekyll.
-
-Import from Jekyll requires two paths, e.g. `hugo import jekyll jekyll_root_path target_path`.
-
-```
-hugo import jekyll [flags] [args]
-```
-
-### Options
-
-```
- --force allow import into non-empty target directory
- -h, --help help for jekyll
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo import](/commands/hugo_import/) - Import a site from another system
-
diff --git a/docs/content/en/commands/hugo_list.md b/docs/content/en/commands/hugo_list.md
deleted file mode 100644
index 741ca1d68..000000000
--- a/docs/content/en/commands/hugo_list.md
+++ /dev/null
@@ -1,47 +0,0 @@
----
-title: "hugo list"
-slug: hugo_list
-url: /commands/hugo_list/
----
-## hugo list
-
-List content
-
-### Synopsis
-
-List content.
-
-List requires a subcommand, e.g. hugo list drafts
-
-### Options
-
-```
- -h, --help help for list
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo](/commands/hugo/) - Build your site
-* [hugo list all](/commands/hugo_list_all/) - List all content
-* [hugo list drafts](/commands/hugo_list_drafts/) - List draft content
-* [hugo list expired](/commands/hugo_list_expired/) - List expired content
-* [hugo list future](/commands/hugo_list_future/) - List future content
-* [hugo list published](/commands/hugo_list_published/) - List published content
-
diff --git a/docs/content/en/commands/hugo_list_all.md b/docs/content/en/commands/hugo_list_all.md
deleted file mode 100644
index e0f1efdcb..000000000
--- a/docs/content/en/commands/hugo_list_all.md
+++ /dev/null
@@ -1,44 +0,0 @@
----
-title: "hugo list all"
-slug: hugo_list_all
-url: /commands/hugo_list_all/
----
-## hugo list all
-
-List all content
-
-### Synopsis
-
-List all content including draft, future, and expired.
-
-```
-hugo list all [flags] [args]
-```
-
-### Options
-
-```
- -h, --help help for all
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo list](/commands/hugo_list/) - List content
-
diff --git a/docs/content/en/commands/hugo_list_drafts.md b/docs/content/en/commands/hugo_list_drafts.md
deleted file mode 100644
index 25ddc78d3..000000000
--- a/docs/content/en/commands/hugo_list_drafts.md
+++ /dev/null
@@ -1,44 +0,0 @@
----
-title: "hugo list drafts"
-slug: hugo_list_drafts
-url: /commands/hugo_list_drafts/
----
-## hugo list drafts
-
-List draft content
-
-### Synopsis
-
-List draft content.
-
-```
-hugo list drafts [flags] [args]
-```
-
-### Options
-
-```
- -h, --help help for drafts
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo list](/commands/hugo_list/) - List content
-
diff --git a/docs/content/en/commands/hugo_list_expired.md b/docs/content/en/commands/hugo_list_expired.md
deleted file mode 100644
index 1936b9920..000000000
--- a/docs/content/en/commands/hugo_list_expired.md
+++ /dev/null
@@ -1,44 +0,0 @@
----
-title: "hugo list expired"
-slug: hugo_list_expired
-url: /commands/hugo_list_expired/
----
-## hugo list expired
-
-List expired content
-
-### Synopsis
-
-List content with a past expiration date.
-
-```
-hugo list expired [flags] [args]
-```
-
-### Options
-
-```
- -h, --help help for expired
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo list](/commands/hugo_list/) - List content
-
diff --git a/docs/content/en/commands/hugo_list_future.md b/docs/content/en/commands/hugo_list_future.md
deleted file mode 100644
index 3152639c2..000000000
--- a/docs/content/en/commands/hugo_list_future.md
+++ /dev/null
@@ -1,44 +0,0 @@
----
-title: "hugo list future"
-slug: hugo_list_future
-url: /commands/hugo_list_future/
----
-## hugo list future
-
-List future content
-
-### Synopsis
-
-List content with a future publication date.
-
-```
-hugo list future [flags] [args]
-```
-
-### Options
-
-```
- -h, --help help for future
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo list](/commands/hugo_list/) - List content
-
diff --git a/docs/content/en/commands/hugo_list_published.md b/docs/content/en/commands/hugo_list_published.md
deleted file mode 100644
index a7a08c7b4..000000000
--- a/docs/content/en/commands/hugo_list_published.md
+++ /dev/null
@@ -1,44 +0,0 @@
----
-title: "hugo list published"
-slug: hugo_list_published
-url: /commands/hugo_list_published/
----
-## hugo list published
-
-List published content
-
-### Synopsis
-
-List content that is not draft, future, or expired.
-
-```
-hugo list published [flags] [args]
-```
-
-### Options
-
-```
- -h, --help help for published
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo list](/commands/hugo_list/) - List content
-
diff --git a/docs/content/en/commands/hugo_mod.md b/docs/content/en/commands/hugo_mod.md
deleted file mode 100644
index 25a27185d..000000000
--- a/docs/content/en/commands/hugo_mod.md
+++ /dev/null
@@ -1,59 +0,0 @@
----
-title: "hugo mod"
-slug: hugo_mod
-url: /commands/hugo_mod/
----
-## hugo mod
-
-Manage modules
-
-### Synopsis
-
-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".
-
-
-Note that Hugo will always start out by resolving the components defined in the site
-configuration, provided by a _vendor directory (if no --ignoreVendorPaths flag provided),
-Go Modules, or a folder inside the themes directory, in that order.
-
-See https://gohugo.io/hugo-modules/ for more information.
-
-
-
-### Options
-
-```
- -h, --help help for mod
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo](/commands/hugo/) - Build your site
-* [hugo mod clean](/commands/hugo_mod_clean/) - Delete the Hugo Module cache for the current project
-* [hugo mod get](/commands/hugo_mod_get/) - Resolves dependencies in your current Hugo project
-* [hugo mod graph](/commands/hugo_mod_graph/) - Print a module dependency graph
-* [hugo mod init](/commands/hugo_mod_init/) - Initialize this project as a Hugo Module
-* [hugo mod npm](/commands/hugo_mod_npm/) - Various npm helpers
-* [hugo mod tidy](/commands/hugo_mod_tidy/) - Remove unused entries in go.mod and go.sum
-* [hugo mod vendor](/commands/hugo_mod_vendor/) - Vendor all module dependencies into the _vendor directory
-* [hugo mod verify](/commands/hugo_mod_verify/) - Verify dependencies
-
diff --git a/docs/content/en/commands/hugo_mod_clean.md b/docs/content/en/commands/hugo_mod_clean.md
deleted file mode 100644
index ff2255e53..000000000
--- a/docs/content/en/commands/hugo_mod_clean.md
+++ /dev/null
@@ -1,51 +0,0 @@
----
-title: "hugo mod clean"
-slug: hugo_mod_clean
-url: /commands/hugo_mod_clean/
----
-## hugo mod clean
-
-Delete the Hugo Module cache for the current project
-
-### Synopsis
-
-Delete the Hugo Module cache for the current project.
-
-```
-hugo mod clean [flags] [args]
-```
-
-### Options
-
-```
- --all clean entire module cache
- -b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
- --cacheDir string filesystem path to cache directory
- -c, --contentDir string filesystem path to content directory
- -h, --help help for clean
- --pattern string pattern matching module paths to clean (all if not set), e.g. "**hugo*"
- --renderSegments strings named segments to render (configured in the segments config)
- -t, --theme strings themes to use (located in /themes/THEMENAME/)
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo mod](/commands/hugo_mod/) - Manage modules
-
diff --git a/docs/content/en/commands/hugo_mod_get.md b/docs/content/en/commands/hugo_mod_get.md
deleted file mode 100644
index a5c9a9ea9..000000000
--- a/docs/content/en/commands/hugo_mod_get.md
+++ /dev/null
@@ -1,75 +0,0 @@
----
-title: "hugo mod get"
-slug: hugo_mod_get
-url: /commands/hugo_mod_get/
----
-## hugo mod get
-
-Resolves dependencies in your current Hugo project
-
-### Synopsis
-
-
-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 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.
-
-Note that Hugo will always start out by resolving the components defined in the site
-configuration, provided by a _vendor directory (if no --ignoreVendorPaths flag provided),
-Go Modules, or a folder inside the themes directory, in that order.
-
-See https://gohugo.io/hugo-modules/ for more information.
-
-
-
-```
-hugo mod get [flags] [args]
-```
-
-### Options
-
-```
- -h, --help help for get
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo mod](/commands/hugo_mod/) - Manage modules
-
diff --git a/docs/content/en/commands/hugo_mod_graph.md b/docs/content/en/commands/hugo_mod_graph.md
deleted file mode 100644
index cb2bdfb5a..000000000
--- a/docs/content/en/commands/hugo_mod_graph.md
+++ /dev/null
@@ -1,52 +0,0 @@
----
-title: "hugo mod graph"
-slug: hugo_mod_graph
-url: /commands/hugo_mod_graph/
----
-## hugo mod graph
-
-Print a module dependency graph
-
-### Synopsis
-
-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.
-
-
-```
-hugo mod graph [flags] [args]
-```
-
-### Options
-
-```
- -b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
- --cacheDir string filesystem path to cache directory
- --clean delete module cache for dependencies that fail verification
- -c, --contentDir string filesystem path to content directory
- -h, --help help for graph
- --renderSegments strings named segments to render (configured in the segments config)
- -t, --theme strings themes to use (located in /themes/THEMENAME/)
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo mod](/commands/hugo_mod/) - Manage modules
-
diff --git a/docs/content/en/commands/hugo_mod_init.md b/docs/content/en/commands/hugo_mod_init.md
deleted file mode 100644
index 3315e97d6..000000000
--- a/docs/content/en/commands/hugo_mod_init.md
+++ /dev/null
@@ -1,56 +0,0 @@
----
-title: "hugo mod init"
-slug: hugo_mod_init
-url: /commands/hugo_mod_init/
----
-## hugo mod init
-
-Initialize this project as a Hugo Module
-
-### Synopsis
-
-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.
-
-
-```
-hugo mod init [flags] [args]
-```
-
-### Options
-
-```
- -b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
- --cacheDir string filesystem path to cache directory
- -c, --contentDir string filesystem path to content directory
- -h, --help help for init
- --renderSegments strings named segments to render (configured in the segments config)
- -t, --theme strings themes to use (located in /themes/THEMENAME/)
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo mod](/commands/hugo_mod/) - Manage modules
-
diff --git a/docs/content/en/commands/hugo_mod_npm.md b/docs/content/en/commands/hugo_mod_npm.md
deleted file mode 100644
index 39a559e0f..000000000
--- a/docs/content/en/commands/hugo_mod_npm.md
+++ /dev/null
@@ -1,45 +0,0 @@
----
-title: "hugo mod npm"
-slug: hugo_mod_npm
-url: /commands/hugo_mod_npm/
----
-## hugo mod npm
-
-Various npm helpers
-
-### Synopsis
-
-Various npm (Node package manager) helpers.
-
-```
-hugo mod npm [command] [flags]
-```
-
-### Options
-
-```
- -h, --help help for npm
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo mod](/commands/hugo_mod/) - Manage modules
-* [hugo mod npm pack](/commands/hugo_mod_npm_pack/) - Experimental: Prepares and writes a composite package.json file for your project
-
diff --git a/docs/content/en/commands/hugo_mod_npm_pack.md b/docs/content/en/commands/hugo_mod_npm_pack.md
deleted file mode 100644
index 5ece05769..000000000
--- a/docs/content/en/commands/hugo_mod_npm_pack.md
+++ /dev/null
@@ -1,59 +0,0 @@
----
-title: "hugo mod npm pack"
-slug: hugo_mod_npm_pack
-url: /commands/hugo_mod_npm_pack/
----
-## hugo mod npm pack
-
-Experimental: Prepares and writes a composite package.json file for your project
-
-### Synopsis
-
-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.
-
-
-```
-hugo mod npm pack [flags] [args]
-```
-
-### Options
-
-```
- -b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
- --cacheDir string filesystem path to cache directory
- -c, --contentDir string filesystem path to content directory
- -h, --help help for pack
- --renderSegments strings named segments to render (configured in the segments config)
- -t, --theme strings themes to use (located in /themes/THEMENAME/)
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo mod npm](/commands/hugo_mod_npm/) - Various npm helpers
-
diff --git a/docs/content/en/commands/hugo_mod_tidy.md b/docs/content/en/commands/hugo_mod_tidy.md
deleted file mode 100644
index c7ae40625..000000000
--- a/docs/content/en/commands/hugo_mod_tidy.md
+++ /dev/null
@@ -1,45 +0,0 @@
----
-title: "hugo mod tidy"
-slug: hugo_mod_tidy
-url: /commands/hugo_mod_tidy/
----
-## hugo mod tidy
-
-Remove unused entries in go.mod and go.sum
-
-```
-hugo mod tidy [flags] [args]
-```
-
-### Options
-
-```
- -b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
- --cacheDir string filesystem path to cache directory
- -c, --contentDir string filesystem path to content directory
- -h, --help help for tidy
- --renderSegments strings named segments to render (configured in the segments config)
- -t, --theme strings themes to use (located in /themes/THEMENAME/)
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo mod](/commands/hugo_mod/) - Manage modules
-
diff --git a/docs/content/en/commands/hugo_mod_vendor.md b/docs/content/en/commands/hugo_mod_vendor.md
deleted file mode 100644
index dc403affe..000000000
--- a/docs/content/en/commands/hugo_mod_vendor.md
+++ /dev/null
@@ -1,51 +0,0 @@
----
-title: "hugo mod vendor"
-slug: hugo_mod_vendor
-url: /commands/hugo_mod_vendor/
----
-## hugo mod vendor
-
-Vendor all module dependencies into the _vendor directory
-
-### Synopsis
-
-Vendor all module dependencies into the _vendor directory.
- If a module is vendored, that is where Hugo will look for it's dependencies.
-
-
-```
-hugo mod vendor [flags] [args]
-```
-
-### Options
-
-```
- -b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
- --cacheDir string filesystem path to cache directory
- -c, --contentDir string filesystem path to content directory
- -h, --help help for vendor
- --renderSegments strings named segments to render (configured in the segments config)
- -t, --theme strings themes to use (located in /themes/THEMENAME/)
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo mod](/commands/hugo_mod/) - Manage modules
-
diff --git a/docs/content/en/commands/hugo_mod_verify.md b/docs/content/en/commands/hugo_mod_verify.md
deleted file mode 100644
index 2f22a2e49..000000000
--- a/docs/content/en/commands/hugo_mod_verify.md
+++ /dev/null
@@ -1,50 +0,0 @@
----
-title: "hugo mod verify"
-slug: hugo_mod_verify
-url: /commands/hugo_mod_verify/
----
-## hugo mod verify
-
-Verify dependencies
-
-### Synopsis
-
-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.
-
-```
-hugo mod verify [flags] [args]
-```
-
-### Options
-
-```
- -b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
- --cacheDir string filesystem path to cache directory
- --clean delete module cache for dependencies that fail verification
- -c, --contentDir string filesystem path to content directory
- -h, --help help for verify
- --renderSegments strings named segments to render (configured in the segments config)
- -t, --theme strings themes to use (located in /themes/THEMENAME/)
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo mod](/commands/hugo_mod/) - Manage modules
-
diff --git a/docs/content/en/commands/hugo_new.md b/docs/content/en/commands/hugo_new.md
deleted file mode 100644
index 2788ef168..000000000
--- a/docs/content/en/commands/hugo_new.md
+++ /dev/null
@@ -1,50 +0,0 @@
----
-title: "hugo new"
-slug: hugo_new
-url: /commands/hugo_new/
----
-## hugo new
-
-Create new content
-
-### Synopsis
-
-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.
-
-### Options
-
-```
- -h, --help help for new
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo](/commands/hugo/) - Build your site
-* [hugo new content](/commands/hugo_new_content/) - Create new content
-* [hugo new site](/commands/hugo_new_site/) - Create a new site (skeleton)
-* [hugo new theme](/commands/hugo_new_theme/) - Create a new theme (skeleton)
-
diff --git a/docs/content/en/commands/hugo_new_content.md b/docs/content/en/commands/hugo_new_content.md
deleted file mode 100644
index 9624e9a61..000000000
--- a/docs/content/en/commands/hugo_new_content.md
+++ /dev/null
@@ -1,59 +0,0 @@
----
-title: "hugo new content"
-slug: hugo_new_content
-url: /commands/hugo_new_content/
----
-## hugo new content
-
-Create new content
-
-### Synopsis
-
-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.
-
-```
-hugo new content [path] [flags]
-```
-
-### Options
-
-```
- -b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
- --cacheDir string filesystem path to cache directory
- -c, --contentDir string filesystem path to content directory
- --editor string edit new content with this editor, if provided
- -f, --force overwrite file if it already exists
- -h, --help help for content
- -k, --kind string content type to create
- --renderSegments strings named segments to render (configured in the segments config)
- -t, --theme strings themes to use (located in /themes/THEMENAME/)
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo new](/commands/hugo_new/) - Create new content
-
diff --git a/docs/content/en/commands/hugo_new_site.md b/docs/content/en/commands/hugo_new_site.md
deleted file mode 100644
index 0f0096ae4..000000000
--- a/docs/content/en/commands/hugo_new_site.md
+++ /dev/null
@@ -1,48 +0,0 @@
----
-title: "hugo new site"
-slug: hugo_new_site
-url: /commands/hugo_new_site/
----
-## hugo new site
-
-Create a new site (skeleton)
-
-### Synopsis
-
-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.
-
-```
-hugo new site [path] [flags]
-```
-
-### Options
-
-```
- -f, --force init inside non-empty directory
- --format string preferred file format (toml, yaml or json) (default "toml")
- -h, --help help for site
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo new](/commands/hugo_new/) - Create new content
-
diff --git a/docs/content/en/commands/hugo_new_theme.md b/docs/content/en/commands/hugo_new_theme.md
deleted file mode 100644
index b1c937bae..000000000
--- a/docs/content/en/commands/hugo_new_theme.md
+++ /dev/null
@@ -1,47 +0,0 @@
----
-title: "hugo new theme"
-slug: hugo_new_theme
-url: /commands/hugo_new_theme/
----
-## hugo new theme
-
-Create a new theme (skeleton)
-
-### Synopsis
-
-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
-according to your needs.
-
-```
-hugo new theme [name] [flags]
-```
-
-### Options
-
-```
- -h, --help help for theme
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo new](/commands/hugo_new/) - Create new content
-
diff --git a/docs/content/en/commands/hugo_server.md b/docs/content/en/commands/hugo_server.md
deleted file mode 100644
index d735f449a..000000000
--- a/docs/content/en/commands/hugo_server.md
+++ /dev/null
@@ -1,98 +0,0 @@
----
-title: "hugo server"
-slug: hugo_server
-url: /commands/hugo_server/
----
-## hugo server
-
-Start the embedded web server
-
-### Synopsis
-
-Hugo provides its own webserver which builds and serves the site.
-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 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.
-
-```
-hugo server [command] [flags]
-```
-
-### Options
-
-```
- --appendPort append port to baseURL (default true)
- -b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
- --bind string interface to which the server will bind (default "127.0.0.1")
- -D, --buildDrafts include content marked as draft
- -E, --buildExpired include expired content
- -F, --buildFuture include content with publishdate in the future
- --cacheDir string filesystem path to cache directory
- --cleanDestinationDir remove files from destination not found in static directories
- -c, --contentDir string filesystem path to content directory
- --disableBrowserError do not show build errors in the browser
- --disableFastRender enables full re-renders on changes
- --disableKinds strings disable different kind of pages (home, RSS etc.)
- --disableLiveReload watch without enabling live browser reload on rebuild
- --enableGitInfo add Git revision, date, author, and CODEOWNERS info to the pages
- --forceSyncStatic copy all files when static is changed.
- --gc enable to run some cleanup tasks (remove unused cache files) after the build
- -h, --help help for server
- --ignoreCache ignores the cache directory
- -l, --layoutDir string filesystem path to layout directory
- --liveReloadPort int port for live reloading (i.e. 443 in HTTPS proxy situations) (default -1)
- --minify minify any supported output format (HTML, XML etc.)
- -N, --navigateToChanged navigate to changed content file on live browser reload
- --noChmod don't sync permission mode of files
- --noHTTPCache prevent HTTP caching
- --noTimes don't sync modification time of files
- -O, --openBrowser open the site in a browser after server startup
- --panicOnWarning panic on first WARNING log
- --poll string set this to a poll interval, e.g --poll 700ms, to use a poll based approach to watch for file system changes
- -p, --port int port on which the server will listen (default 1313)
- --pprof enable the pprof server (port 8080)
- --printI18nWarnings print missing translations
- --printMemoryUsage print memory usage to screen at intervals
- --printPathWarnings print warnings on duplicate target paths etc.
- --printUnusedTemplates print warnings on unused templates.
- --renderSegments strings named segments to render (configured in the segments config)
- --renderStaticToDisk serve static files from disk and dynamic files from memory
- --templateMetrics display metrics about template executions
- --templateMetricsHints calculate some improvement hints when combined with --templateMetrics
- -t, --theme strings themes to use (located in /themes/THEMENAME/)
- --tlsAuto generate and use locally-trusted certificates.
- --tlsCertFile string path to TLS certificate file
- --tlsKeyFile string path to TLS key file
- --trace file write trace to file (not useful in general)
- -w, --watch watch filesystem for changes and recreate as needed (default true)
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo](/commands/hugo/) - Build your site
-* [hugo server trust](/commands/hugo_server_trust/) - Install the local CA in the system trust store
-
diff --git a/docs/content/en/commands/hugo_server_trust.md b/docs/content/en/commands/hugo_server_trust.md
deleted file mode 100644
index 22ca2491e..000000000
--- a/docs/content/en/commands/hugo_server_trust.md
+++ /dev/null
@@ -1,41 +0,0 @@
----
-title: "hugo server trust"
-slug: hugo_server_trust
-url: /commands/hugo_server_trust/
----
-## hugo server trust
-
-Install the local CA in the system trust store
-
-```
-hugo server trust [flags] [args]
-```
-
-### Options
-
-```
- -h, --help help for trust
- --uninstall Uninstall the local CA (but do not delete it).
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo server](/commands/hugo_server/) - Start the embedded web server
-
diff --git a/docs/content/en/commands/hugo_version.md b/docs/content/en/commands/hugo_version.md
deleted file mode 100644
index 14cc92a00..000000000
--- a/docs/content/en/commands/hugo_version.md
+++ /dev/null
@@ -1,44 +0,0 @@
----
-title: "hugo version"
-slug: hugo_version
-url: /commands/hugo_version/
----
-## hugo version
-
-Display version
-
-### Synopsis
-
-Display version and environment info. This is useful in Hugo bug reports.
-
-```
-hugo version [flags] [args]
-```
-
-### Options
-
-```
- -h, --help help for version
-```
-
-### Options inherited from parent commands
-
-```
- --clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
- --config string config file (default is hugo.yaml|json|toml)
- --configDir string config dir (default "config")
- -d, --destination string filesystem path to write files to
- -e, --environment string build environment
- --ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
- --logLevel string log level (debug|info|warn|error)
- --noBuildLock don't create .hugo_build.lock file
- --quiet build in quiet mode
- -M, --renderToMemory render to memory (mostly useful when running the server)
- -s, --source string filesystem path to read files relative from
- --themesDir string filesystem path to themes directory
-```
-
-### SEE ALSO
-
-* [hugo](/commands/hugo/) - Build your site
-
diff --git a/docs/content/en/configuration/_index.md b/docs/content/en/configuration/_index.md
deleted file mode 100644
index 7cb08cc73..000000000
--- a/docs/content/en/configuration/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Configuration
-description: Configure your site.
-categories: []
-keywords: []
-weight: 10
----
diff --git a/docs/content/en/configuration/all.md b/docs/content/en/configuration/all.md
deleted file mode 100644
index 9bc05057f..000000000
--- a/docs/content/en/configuration/all.md
+++ /dev/null
@@ -1,362 +0,0 @@
----
-title: All settings
-description: The complete list of Hugo configuration settings.
-categories: []
-keywords: []
-weight: 20
-aliases: [/getting-started/configuration/]
----
-
-## Settings
-
-archetypeDir
-: (`string`) The designated directory for [archetypes](g). Default is `archetypes`. {{% module-mounts-note %}}
-
-assetDir
-: (`string`) The designated directory for [global resources](g). Default is `assets`. {{% module-mounts-note %}}
-
-baseURL
-: (`string`) The absolute URL of your published site including the protocol, host, path, and a trailing slash.
-
-build
-: See [configure build](/configuration/build/).
-
-buildDrafts
-: (`bool`) Whether to include draft content when building a site. Default is `false`.
-
-buildExpired
-: (`bool`) Whether to include expired content when building a site. Default is `false`.
-
-buildFuture
-: (`bool`) Whether to include future content when building a site. Default is `false`.
-
-cacheDir
-: (`string`) The designated cache directory. See [details](#cache-directory).
-
-caches
-: See [configure file caches](/configuration/caches/).
-
-canonifyURLs
-: (`bool`) See [details](/content-management/urls/#canonical-urls) before enabling this feature. Default is `false`.
-
-capitalizeListTitles
-: {{< new-in 0.123.3 />}}
-: (`bool`) Whether to capitalize automatic list titles. Applicable to section, taxonomy, and term pages. Default is `true`. Use the [`titleCaseStyle`](#titlecasestyle) setting to configure capitalization rules.
-
-cascade
-: See [configure cascade](/configuration/cascade/).
-
-cleanDestinationDir
-: (`bool`) Whether to remove files from the site's destination directory that do not have corresponding files in the `static` directory during the build. Default is `false`.
-
-contentDir
-: (`string`) The designated directory for content files. Default is `content`. {{% module-mounts-note %}}
-
-copyright
-: (`string`) The copyright notice for a site, typically displayed in the footer.
-
-dataDir
-: (`string`) The designated directory for data files. Default is `data`. {{% module-mounts-note %}}
-
-defaultContentLanguage
-: (`string`) The project's default language key, conforming to the syntax described in [RFC 5646]. This value must match one of the defined language keys. Default is `en`.
-
-defaultContentLanguageInSubdir
-: (`bool`) Whether to publish the default language site to a subdirectory matching the `defaultContentLanguage`. Default is `false`.
-
-defaultOutputFormat
-: (`string`) The default output format for the site. If unspecified, the first available format in the defined order (by weight, then alphabetically) will be used.
-
-deployment
-: See [configure deployment](/configuration/deployment/).
-
-disableAliases
-: (`bool`) Whether to disable generation of alias redirects. Even if this option is enabled, the defined aliases will still be present on the page. This allows you to manage redirects separately, for example, by generating 301 redirects in an `.htaccess` file or a Netlify `_redirects` file using a custom output format. Default is `false`.
-
-disableDefaultLanguageRedirect
-: {{< new-in 0.140.0 />}}
-: (`bool`) Whether to disable generation of the alias redirect to the default language when `DefaultContentLanguageInSubdir` is `true`. Default is `false`.
-
-disableHugoGeneratorInject
-: (`bool`) Whether to disable injection of a `` tag into the home page. Default is `false`.
-
-disableKinds
-: (`[]string`) A slice of page [kinds](g) to disable during the build process, any of `404`, `home`, `page`, `robotstxt`, `rss`, `section`, `sitemap`, `taxonomy`, or `term`.
-
-disableLanguages
-: (`[]string]`) A slice of language keys representing the languages to disable during the build process. Although this is functional, consider using the [`disabled`] key under each language instead.
-
-disableLiveReload
-: (`bool`) Whether to disable automatic live reloading of the browser window. Default is `false`.
-
-disablePathToLower
-: (`bool`) Whether to disable transformation of page URLs to lower case.
-
-enableEmoji
-: (`bool`) Whether to allow emoji in Markdown. Default is `false`.
-
-enableGitInfo
-: (`bool`) For sites under Git version control, whether to enable the [`GitInfo`] object for each page. With the [default front matter configuration], the `Lastmod` method on a `Page` object will return the Git author date. Default is `false`.
-
-enableMissingTranslationPlaceholders
-: (`bool`) Whether to show a placeholder instead of the default value or an empty string if a translation is missing. Default is `false`.
-
-enableRobotsTXT
-: (`bool`) Whether to enable generation of a `robots.txt` file. Default is `false`.
-
-environment
-: (`string`) The build environment. Default is `production` when running `hugo` and `development` when running `hugo server`.
-
-frontmatter
-: See [configure front matter](/configuration/front-matter/).
-
-hasCJKLanguage
-: (`bool`) Whether to automatically detect [CJK](g) languages in content. Affects the values returned by the [`WordCount`] and [`FuzzyWordCount`] methods. Default is `false`.
-
-HTTPCache
-: See [configure HTTP cache](/configuration/http-cache/).
-
-i18nDir
-: (`string`) The designated directory for translation tables. Default is `i18n`. {{% module-mounts-note %}}
-
-ignoreCache
-: (`bool`) Whether to ignore the cache directory. Default is `false`.
-
-ignoreFiles
-: (`[]string]`) A slice of [regular expressions](g) used to exclude specific files from a build. These expressions are matched against the absolute file path and apply to files within the `content`, `data`, and `i18n` directories. For more advanced file exclusion options, see the section on [module mounts].
-
-ignoreLogs
-: (`[]string`) A slice of message identifiers corresponding to warnings and errors you wish to suppress. See [`erroridf`] and [`warnidf`].
-
-ignoreVendorPaths
-: (`string`) A [glob](g) pattern matching the module paths to exclude from the `_vendor` directory.
-
-imaging
-: See [configure imaging](/configuration/imaging/).
-
-languageCode
-: (`string`) The site's language tag, conforming to the syntax described in [RFC 5646]. This value does not affect translations or localization. Hugo uses this value to populate:
-
- - The `language` element in the [embedded RSS template]
- - The `lang` attribute of the `html` element in the [embedded alias template]
- - The `og:locale` `meta` element in the [embedded Open Graph template]
-
- When present in the root of the configuration, this value is ignored if one or more language keys exists. Please specify this value independently for each language key.
-
-languages
-: See [configure languages](/configuration/languages/).
-
-layoutDir
-: (`string`) The designated directory for templates. Default is `layouts`. {{% module-mounts-note %}}
-
-mainSections
-: (`string` or `[]string`) The main sections of a site. If set, the [`MainSections`] method on the `Site` object returns the given sections, otherwise it returns the section with the most pages.
-
-markup
-: See [configure markup](/configuration/markup/).
-
-mediaTypes
-: See [configure media types](/configuration/media-types/).
-
-menus
-: See [configure menus](/configuration/menus/).
-
-minify
-: See [configure minify](/configuration/minify/).
-
-module
-: See [configure modules](/configuration/module/).
-
-newContentEditor
-: (`string`) The editor to use when creating new content.
-
-noBuildLock
-: (`bool`) Whether to disable creation of the `.hugo_build.lock` file. Default is `false`.
-
-noChmod
-: (`bool`) Whether to disable synchronization of file permission modes. Default is `false`.
-
-noTimes
-: (`bool`) Whether to disable synchronization of file modification times. Default is `false`.
-
-outputFormats
-: See [configure output formats](/configuration/output-formats/).
-
-outputs
-: See [configure outputs](/configuration/outputs/).
-
-page
-: See [configure page](/configuration/page/).
-
-pagination
-: See [configure pagination](/configuration/pagination/).
-
-panicOnWarning
-: (`bool`) Whether to panic on the first WARNING. Default is `false`.
-
-params
-: See [configure params](/configuration/params/).
-
-permalinks
-: See [configure permalinks](/configuration/permalinks/).
-
-pluralizeListTitles
-: (`bool`) Whether to pluralize automatic list titles. Applicable to section pages. Default is `true`.
-
-printI18nWarnings
-: (`bool`) Whether to log WARNINGs for each missing translation. Default is `false`.
-
-printPathWarnings
-: (`bool`) Whether to log WARNINGs when Hugo publishes two or more files to the same path. Default is `false`.
-
-printUnusedTemplates
-: (`bool`) Whether to log WARNINGs for each unused template. Default is `false`.
-
-privacy
-: See [configure privacy](/configuration/privacy/).
-
-publishDir
-: (`string`) The designated directory for publishing the site. Default is `public`.
-
-refLinksErrorLevel
-: (`string`) The logging error level to use when the `ref` and `relref` functions, methods, and shortcodes are unable to resolve a reference to a page. Either `ERROR` or `WARNING`. Any `ERROR` will fail the build. Default is `ERROR`.
-
-refLinksNotFoundURL
-: (`string`) The URL to return when the `ref` and `relref` functions, methods, and shortcodes are unable to resolve a reference to a page.
-
-related
-: See [configure related content](/configuration/related-content/).
-
-relativeURLs
-: (`bool`) See [details](/content-management/urls/#relative-urls) before enabling this feature. Default is `false`.
-
-removePathAccents
-: (`bool`) Whether to remove [non-spacing marks](https://www.compart.com/en/unicode/category/Mn) from [composite characters](https://en.wikipedia.org/wiki/Precomposed_character) in content paths. Default is `false`.
-
-renderSegments
-: {{< new-in 0.124.0 />}}
-: (`[]string`) A slice of [segments](g) to render. If omitted, all segments are rendered. This option is typically set via a command-line flag, such as `hugo --renderSegments segment1,segment2`. The provided segment names must correspond to those defined in the [`segments`] configuration.
-
-resourceDir
-: (`string`) The designated directory for caching output from [asset pipelines](g). Default is `resources`.
-
-security
-: See [configure security](/configuration/security/).
-
-sectionPagesMenu
-: (`string`) When set, each top-level section will be added to the menu identified by the provided value. See [details](/content-management/menus/#define-automatically).
-
-segments
-: See [configure segments](/configuration/segments/).
-
-server
-: See [configure server](/configuration/server/).
-
-services
-: See [configure services](/configuration/services/).
-
-sitemap
-: See [configure sitemap](/configuration/sitemap/).
-
-staticDir
-: (`string`) The designated directory for static files. Default is `static`. {{% module-mounts-note %}}
-
-summaryLength
-: (`int`) Applicable to [automatic summaries], the minimum number of words returned by the [`Summary`] method on a `Page` object. The `Summary` method will return content truncated at the paragraph boundary closest to the specified `summaryLength`, but at least this minimum number of words.
-
-taxonomies
-: See [configure taxonomies](/configuration/taxonomies/).
-
-templateMetrics
-: (`bool`) Whether to print template execution metrics to the console. Default is `false`. See [details](/troubleshooting/performance/#template-metrics).
-
-templateMetricsHints
-: (`bool`) Whether to print template execution improvement hints to the console. Applicable when `templateMetrics` is `true`. Default is `false`. See [details](/troubleshooting/performance/#template-metrics).
-
-theme
-: (`string` or `[]string`) The [theme](g) to use. Multiple themes can be listed, with precedence given from left to right. See [details](/hugo-modules/theme-components/).
-
-themesDir
-: (`string`) The designated directory for themes. Default is `themes`.
-
-timeout
-: (`string`) The timeout for generating page content, either as a [duration] or in seconds. This timeout is used to prevent infinite recursion during content generation. You may need to increase this value if your pages take a long time to generate, for example, due to extensive image processing or reliance on remote content. Default is `30s`.
-
-timeZone
-: (`string`) The time zone used to parse dates without time zone offsets, including front matter date fields and values passed to the [`time.AsTime`] and [`time.Format`] template functions. The list of valid values may be system dependent, but should include `UTC`, `Local`, and any location in the [IANA Time Zone Database]. For example, `America/Los_Angeles` and `Europe/Oslo` are valid time zones.
-
-title
-: (`string`) The site title.
-
-titleCaseStyle
-: (`string`) The capitalization rules to follow when Hugo automatically generates a section title, or when using the [`strings.Title`] function. One of `ap`, `chicago`, `go`, `firstupper`, or `none`. Default is `ap`. See [details](#title-case-style).
-
-uglyurls
-: See [configure ugly URLs](/configuration/ugly-urls/).
-
-## Cache directory
-
-Hugo's file cache directory is configurable via the [`cacheDir`] configuration option or the `HUGO_CACHEDIR` environment variable. If neither is set, Hugo will use, in order of preference:
-
-1. If running on Netlify: `/opt/build/cache/hugo_cache/`. This means that if you run your builds on Netlify, all caches configured with `:cacheDir` will be saved and restored on the next build. For other [CI/CD](g) vendors, please read their documentation. For an CircleCI example, see [this configuration].
-1. In a `hugo_cache` directory below the OS user cache directory as defined by Go's [os.UserCacheDir] function. On Unix systems, per the [XDG base directory specification], this is `$XDG_CACHE_HOME` if non-empty, else `$HOME/.cache`. On MacOS, this is `$HOME/Library/Caches`. On Windows, this is`%LocalAppData%`. On Plan 9, this is `$home/lib/cache`.
-1. In a `hugo_cache_$USER` directory below the OS temp dir.
-
-To determine the current `cacheDir`:
-
-```sh
-hugo config | grep cachedir
-```
-
-## Title case style
-
-Hugo's [`titleCaseStyle`] setting governs capitalization for automatically generated section titles and the [`strings.Title`] function. By default, it follows the capitalization rules published in the Associated Press Stylebook. Change this setting to use other capitalization rules.
-
-ap
-: Use the capitalization rules published in the [Associated Press Stylebook]. This is the default.
-
-chicago
-: Use the capitalization rules published in the [Chicago Manual of Style].
-
-go
-: Capitalize the first letter of every word.
-
-firstupper
-: Capitalize the first letter of the first word.
-
-none
-: Disable transformation of automatic section titles, and disable the transformation performed by the `strings.Title` function. This is useful if you would prefer to manually capitalize section titles as needed, and to bypass opinionated theme usage of the `strings.Title` function.
-
-## Localized settings
-
-Some configuration settings, such as menus and custom parameters, can be defined separately for each language. See [configure languages](/configuration/languages/#localized-settings).
-
-[`cacheDir`]: #cachedir
-[`disabled`]: /configuration/languages/#disabled
-[`erroridf`]: /functions/fmt/erroridf/
-[`FuzzyWordCount`]: /methods/page/fuzzywordcount/
-[`GitInfo`]: /methods/page/gitinfo/
-[`MainSections`]: /methods/site/mainsections/
-[`segments`]: /configuration/segments/
-[`strings.Title`]: /functions/strings/title/
-[`strings.Title`]: /functions/strings/title
-[`Summary`]: /methods/page/summary/
-[`time.AsTime`]: /functions/time/astime/
-[`time.Format`]: /functions/time/format/
-[`titleCaseStyle`]: #titlecasestyle
-[`warnidf`]: /functions/fmt/warnidf/
-[`WordCount`]: /methods/page/wordcount/
-[Associated Press Stylebook]: https://www.apstylebook.com/
-[automatic summaries]: /content-management/summaries/#automatic-summary
-[Chicago Manual of Style]: https://www.chicagomanualofstyle.org/home.html
-[default front matter configuration]: /configuration/front-matter/
-[duration]: https://pkg.go.dev/time#Duration
-[embedded alias template]: {{% eturl alias %}}
-[embedded Open Graph template]: {{% eturl opengraph %}}
-[embedded RSS template]: {{% eturl rss %}}
-[IANA Time Zone Database]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
-[module mounts]: /configuration/module/#mounts
-[os.UserCacheDir]: https://pkg.go.dev/os#UserCacheDir
-[RFC 5646]: https://datatracker.ietf.org/doc/html/rfc5646#section-2.1
-[this configuration]: https://github.com/bep/hugo-sass-test/blob/6c3960a8f4b90e8938228688bc49bdcdd6b2d99e/.circleci/config.yml
-[XDG base directory specification]: https://specifications.freedesktop.org/basedir-spec/latest/
diff --git a/docs/content/en/configuration/build.md b/docs/content/en/configuration/build.md
deleted file mode 100644
index 116294f05..000000000
--- a/docs/content/en/configuration/build.md
+++ /dev/null
@@ -1,81 +0,0 @@
----
-title: Configure build
-linkTitle: Build
-description: Configure global build options.
-categories: []
-keywords: []
-aliases: [/getting-started/configuration-build/]
----
-
-The `build` configuration section contains global build-related configuration options.
-
-{{< code-toggle config=build />}}
-
-buildStats
-: See the [build stats](#build-stats) section below.
-
-cachebusters
-: See the [cache busters](#cache-busters) section below.
-
-noJSConfigInAssets
-: (`bool`) Whether to disable writing a `jsconfig.json` in your `assets` directory with mapping of imports from running [js.Build](/hugo-pipes/js). This file is intended to help with intellisense/navigation inside code editors such as [VS Code](https://code.visualstudio.com/). Note that if you do not use `js.Build`, no file will be written.
-
-useResourceCacheWhen
-: (`string`) When to use the resource file cache, one of `never`, `fallback`, or `always`. Applicable when transpiling Sass to CSS. Default is `fallback`.
-
-## Cache busters
-
-The `build.cachebusters` configuration option was added to support development using Tailwind 3.x's JIT compiler where a `build` configuration may look like this:
-
-{{< code-toggle file=hugo >}}
-[build]
- [build.buildStats]
- enable = true
- [[build.cachebusters]]
- source = "assets/watching/hugo_stats\\.json"
- target = "styles\\.css"
- [[build.cachebusters]]
- source = "(postcss|tailwind)\\.config\\.js"
- target = "css"
- [[build.cachebusters]]
- source = "assets/.*\\.(js|ts|jsx|tsx)"
- target = "js"
- [[build.cachebusters]]
- source = "assets/.*\\.(.*)$"
- target = "$1"
-{{< /code-toggle >}}
-
-When `buildStats` is enabled, Hugo writes a `hugo_stats.json` file on each build with HTML classes etc. that's used in the rendered output. Changes to this file will trigger a rebuild of the `styles.css` file. You also need to add `hugo_stats.json` to Hugo's server watcher. See [Hugo Starter Tailwind Basic](https://github.com/bep/hugo-starter-tailwind-basic) for a running example.
-
-source
-: (`string`) A [regular expression](g) matching file(s) relative to one of the virtual component directories in Hugo, typically `assets/...`.
-
-target
-: (`string`) A [regular expression](g) matching the keys in the resource cache that should be expired when `source` changes. You can use the matching regexp groups from `source` in the expression, e.g. `$1`.
-
-## Build stats
-
-{{< code-toggle config=build.buildStats />}}
-
-enable
-: (`bool`) Whether to create a `hugo_stats.json` file in the root of your project. This file contains arrays of the `class` attributes, `id` attributes, and tags of every HTML element within your published site. Use this file as data source when [removing unused CSS] from your site. This process is also known as pruning, purging, or tree shaking. Default is `false`.
-
-[removing unused CSS]: /functions/resources/postprocess/
-
-disableIDs
-: (`bool`) Whether to exclude `id` attributes. Default is `false`.
-
-disableTags
-: (`bool`) Whether to exclude element tags. Default is `false`.
-
-disableClasses
-: (`bool`) Whether to exclude `class` attributes. Default is `false`.
-
-> [!note]
-> Given that CSS purging is typically limited to production builds, place the `buildStats` object below [`config/production`].
->
-> Built for speed, there may be "false positive" detections (e.g., HTML elements that are not HTML elements) while parsing the published site. These "false positives" are infrequent and inconsequential.
-
-Due to the nature of partial server builds, new HTML entities are added while the server is running, but old values will not be removed until you restart the server or run a regular `hugo` build.
-
-[`config/production`]: /configuration/introduction/#configuration-directory
diff --git a/docs/content/en/configuration/caches.md b/docs/content/en/configuration/caches.md
deleted file mode 100644
index 03b499dcb..000000000
--- a/docs/content/en/configuration/caches.md
+++ /dev/null
@@ -1,30 +0,0 @@
----
-title: Configure file caches
-linkTitle: Caches
-description: Configure file caches.
-categories: []
-keywords: []
----
-
-This is the default configuration:
-
-{{< code-toggle config=caches />}}
-
-## Keys
-
-dir
-: (`string`) The absolute file system path where the cached files will be stored. You can begin the path with the `:cacheDir` or `:resourceDir` token. These tokens will be replaced with the actual configured cache directory and resource directory paths, respectively.
-
-maxAge
-: (`string`) The [duration](g) a cached entry remains valid before being evicted. A value of `0` disables the cache. A value of `-1` means the cache entry never expires (the default).
-
-## Tokens
-
-`:cacheDir`
-: (`string`) The designated cache directory. See [details](/configuration/all/#cachedir).
-
-`:project`
-: (`string`) The base directory name of the current Hugo project. By default, this ensures each project has isolated file caches, so running `hugo --gc` will only affect the current project's cache and not those of other Hugo projects on the same machine.
-
-`:resourceDir`
-: (`string`) The designated directory for caching output from [asset pipelines](g). See [details](/configuration/all/#resourcedir).
diff --git a/docs/content/en/configuration/cascade.md b/docs/content/en/configuration/cascade.md
deleted file mode 100644
index d91996301..000000000
--- a/docs/content/en/configuration/cascade.md
+++ /dev/null
@@ -1,77 +0,0 @@
----
-title: Configure cascade
-linkTitle: Cascade
-description: Configure cascade.
-categories: []
-keywords: []
----
-
-You can configure your site to cascade front matter values to the home page and any of its descendants. However, this cascading will be prevented if the descendant already defines the field, or if a closer ancestor [node](g) has already cascaded a value for the same field through its front matter's `cascade` key.
-
-> [!note]
-> You can also configure cascading behavior within a page's front matter. See [details].
-
-For example, to cascade a "color" parameter to the home page and all its descendants:
-
-{{< code-toggle file=hugo >}}
-title = 'Home'
-[cascade.params]
-color = 'red'
-{{< /code-toggle >}}
-
-## Target
-
-
-
-The `target`[^1] keyword allows you to target specific pages or [environments](g). For example, to cascade a "color" parameter to pages within the "articles" section, including the "articles" section page itself:
-
-[^1]: The `_target` alias for `target` is deprecated and will be removed in a future release.
-
-{{< code-toggle file=hugo >}}
-[cascade.params]
-color = 'red'
-[cascade.target]
-path = '{/articles,/articles/**}'
-{{< /code-toggle >}}
-
-Use any combination of these keywords to target pages and/or environments:
-
-environment
-: (`string`) A [glob](g) pattern matching the build [environment](g). For example: `{staging,production}`.
-
-kind
-: (`string`) A [glob](g) pattern matching the [page kind](g). For example: ` {taxonomy,term}`.
-
-lang
-: (`string`) A [glob](g) pattern matching the [page language]. For example: `{en,de}`.
-
-path
-: (`string`) A [glob](g) pattern matching the page's [logical path](g). For example: `{/books,/books/**}`.
-
-## Array
-
-Define an array of cascade parameters to apply different values to different targets. For example:
-
-{{< code-toggle file=hugo >}}
-[[cascade]]
-[cascade.params]
-color = 'red'
-[cascade.target]
-path = '{/books/**}'
-kind = 'page'
-lang = '{en,de}'
-[[cascade]]
-[cascade.params]
-color = 'blue'
-[cascade.target]
-path = '{/films/**}'
-kind = 'page'
-environment = 'production'
-{{< /code-toggle >}}
-
-[details]: /content-management/front-matter/#cascade-1
-[page language]: /methods/page/language/
diff --git a/docs/content/en/configuration/content-types.md b/docs/content/en/configuration/content-types.md
deleted file mode 100644
index 4c5b5a23b..000000000
--- a/docs/content/en/configuration/content-types.md
+++ /dev/null
@@ -1,63 +0,0 @@
----
-title: Configure content types
-linkTitle: Content types
-description: Configure content types.
-categories: []
-keywords: []
----
-
-{{< new-in 0.144.0 />}}
-
-Hugo supports six [content formats](g):
-
-{{% include "/_common/content-format-table.md" %}}
-
-These can be used as either page content or [page resources](g). When used as page resources, their [resource type](g) is `page`.
-
-Consider this example of a [page bundle](g):
-
-```text
-content/
-└── example/
- ├── index.md <-- content
- ├── a.adoc <-- resource (resource type: page)
- ├── b.html <-- resource (resource type: page)
- ├── c.md <-- resource (resource type: page)
- ├── d.org <-- resource (resource type: page)
- ├── e.pdc <-- resource (resource type: page)
- ├── f.rst <-- resource (resource type: page)
- ├── g.jpg <-- resource (resource type: image)
- └── h.png <-- resource (resource type: image)
-```
-
-The `index.md` file is the page's content, while the other files are page resources. Files `a` through `f` are of resource type `page`, while `g` and `h` are of resource type `image`.
-
-When you build a site, Hugo does not publish page resources having a resource type of `page`. For example, this is the result of building the site above:
-
-```text
-public/
-├── example/
-│ ├── g.jpg
-│ ├── h.png
-│ └── index.html
-└── index.html
-```
-
-The default behavior is appropriate in most cases. Given that page resources containing markup are typically intended for inclusion in the main content, publishing them independently is generally undesirable.
-
-The default behavior is determined by the `contentTypes` configuration:
-
-{{< code-toggle config=contentTypes />}}
-
-In this default configuration, page resources with those media types will have a resource type of `page`, and will not be automatically published. To change the resource type assignment from `page` to `text` for a given media type, remove the corresponding entry from the list.
-
-For example, to set the resource type of `text/html` files to `text`, thereby enabling automatic publication, remove the `text/html` entry:
-
-{{< code-toggle file=hugo >}}
-contentTypes:
- text/asciidoc: {}
- text/markdown: {}
- text/org: {}
- text/pandoc: {}
- text/rst: {}
-{{< /code-toggle >}}
diff --git a/docs/content/en/configuration/deployment.md b/docs/content/en/configuration/deployment.md
deleted file mode 100644
index fad50da63..000000000
--- a/docs/content/en/configuration/deployment.md
+++ /dev/null
@@ -1,159 +0,0 @@
----
-title: Configure deployment
-linkTitle: Deployment
-description: Configure deployments to Amazon S3, Azure Blob Storage, or Google Cloud Storage.
-categories: []
-keywords: []
----
-
-> [!note]
-> This configuration is only relevant when running `hugo deploy`. See [details](/host-and-deploy/deploy-with-hugo-deploy/).
-
-## Top-level options
-
-These settings control the overall behavior of the deployment process. This is the default configuration:
-
-{{< code-toggle file=hugo config=deployment />}}
-
-confirm
-: (`bool`) Whether to prompt for confirmation before deploying. Default is `false`.
-
-dryRun
-: (`bool`) Whether to simulate the deployment without any remote changes. Default is `false`.
-
-force
-: (`bool`) Whether to re-upload all files. Default is `false`.
-
-invalidateCDN
-: (`bool`) Whether to invalidate the CDN cache listed in the deployment target. Default is `true`.
-
-maxDeletes
-: (`int`) The maximum number of files to delete, or `-1` to disable. Default is `256`.
-
-matchers
-: (`[]*Matcher`) A slice of [matchers](#matchers-1).
-
-order
-: (`[]string`) An ordered slice of [regular expressions](g) that determines upload priority (left to right). Files not matching any expression are uploaded last in an arbitrary order.
-
-target
-: (`string`) The target deployment [`name`](#name). Defaults to the first target.
-
-targets
-: (`[]*Target`) A slice of [targets](#targets-1).
-
-workers
-: (`int`) The number of concurrent workers to use when uploading files. Default is `10`.
-
-## Targets
-
-A target represents a deployment target such as "staging" or "production".
-
-cloudFrontDistributionID
-: (`string`) The CloudFront Distribution ID, applicable if you are using the Amazon Web Services CloudFront CDN. Hugo will invalidate the CDN when deploying this target.
-
-exclude
-: (`string`) A [glob](g) pattern matching files to exclude when deploying to this target. Local files failing the include/exclude filters are not uploaded, and remote files failing these filters are not deleted.
-
-googleCloudCDNOrigin
-: (`string`) The Google Cloud project and CDN origin to invalidate when deploying this target, specified as `/`.
-
-include
-: (`string`) A [glob](g) pattern matching files to include when deploying to this target. Local files failing the include/exclude filters are not uploaded, and remote files failing these filters are not deleted.
-
-name
-: (`string`) An arbitrary name for this target.
-
-stripIndexHTML
-: (`bool`) Whether to map files named `/index.html` to `` on the remote (except for the root `index.html`). This is useful for key-value cloud storage (e.g., Amazon S3, Google Cloud Storage, Azure Blob Storage) to align canonical URLs with object keys. Default is `false`.
-
-url
-: (`string`) The [destination URL](#destination-urls) for deployment.
-
-## Matchers
-
-A Matcher represents a configuration to be applied to files whose paths match
-the specified pattern.
-
-cacheControl
-: (`string`) The caching attributes to use when serving the blob. See [details][cacheControl].
-
-contentEncoding
-: (`string`) The encoding used for the blob's content, if any. See [details][contentEncoding].
-
-contentType
-: (`string`) The media type of the blob being written. See [details][contentType].
-
-force
-: (`bool`) Whether matching files should be re-uploaded. Useful when other route-determined metadata (e.g., `contentType`) has changed. Default is `false`.
-
-gzip
-: (`bool`) Whether the file should be gzipped before upload. If so, the `ContentEncoding` field will automatically be set to `gzip`. Default is `false`.
-
-pattern
-: (`string`) A [regular expression](g) used to match paths. Paths are converted to use forward slashes (`/`) before matching.
-
-[cacheControl]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
-[contentEncoding]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding
-[contentType]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type
-
-## Destination URLs
-
-Service|URL example
-:--|:--
-Amazon Simple Storage Service (S3)|`s3://my-bucket?region=us-west-1`
-Azure Blob Storage|`azblob://my-container`
-Google Cloud Storage (GCS)|`gs://my-bucket`
-
-With Google Cloud Storage you can target a subdirectory:
-
-```text
-gs://my-bucket?prefix=a/subdirectory
-```
-
-You can also to deploy to storage servers compatible with Amazon S3 such as:
-
-- [Ceph]
-- [MinIO]
-- [SeaweedFS]
-
-[Ceph]: https://ceph.com/
-[Minio]: https://www.minio.io/
-[SeaweedFS]: https://github.com/chrislusf/seaweedfs
-
-For example, the `url` for a MinIO deployment target might resemble this:
-
-```text
-s3://my-bucket?endpoint=https://my.minio.instance&awssdk=v2&use_path_style=true&disable_https=false
-```
-
-## Example
-
-{{< code-toggle file=hugo >}}
-[deployment]
- order = ['.jpg$', '.gif$']
- [[deployment.matchers]]
- cacheControl = 'max-age=31536000, no-transform, public'
- gzip = true
- pattern = '^.+\.(js|css|svg|ttf)$'
- [[deployment.matchers]]
- cacheControl = 'max-age=31536000, no-transform, public'
- gzip = false
- pattern = '^.+\.(png|jpg)$'
- [[deployment.matchers]]
- contentType = 'application/xml'
- gzip = true
- pattern = '^sitemap\.xml$'
- [[deployment.matchers]]
- gzip = true
- pattern = '^.+\.(html|xml|json)$'
- [[deployment.targets]]
- url = 's3://my_production_bucket?region=us-west-1'
- cloudFrontDistributionID = 'E1234567890ABCDEF0'
- exclude = '**.{heic,psd}'
- name = 'production'
- [[deployment.targets]]
- url = 's3://my_staging_bucket?region=us-west-1'
- exclude = '**.{heic,psd}'
- name = 'staging'
-{{< /code-toggle >}}
diff --git a/docs/content/en/configuration/front-matter.md b/docs/content/en/configuration/front-matter.md
deleted file mode 100644
index 9f51b8a5a..000000000
--- a/docs/content/en/configuration/front-matter.md
+++ /dev/null
@@ -1,91 +0,0 @@
----
-title: Configure front matter
-linkTitle: Front matter
-description: Configure front matter.
-categories: []
-keywords: []
----
-
-## Dates
-
-There are four methods on a `Page` object that return a date.
-
-Method|Description
-:--|:--
-[`Date`]|Returns the date of the given page.
-[`ExpiryDate`]|Returns the expiry date of the given page.
-[`Lastmod`]|Returns the last modification date of the given page.
-[`PublishDate`]|Returns the publish date of the given page.
-
-[`Date`]: /methods/page/date
-[`ExpiryDate`]: /methods/page/expirydate
-[`Lastmod`]: /methods/page/lastmod
-[`PublishDate`]: /methods/page/publishdate
-
-Hugo determines the values to return based on this configuration:
-
-{{< code-toggle config=frontmatter />}}
-
-The `ExpiryDate` method, for example, returns the `expirydate` value if it exists, otherwise it returns `unpublishdate`.
-
-You can also use custom date parameters:
-
-{{< code-toggle file=hugo >}}
-[frontmatter]
-date = ["myDate", "date"]
-{{< /code-toggle >}}
-
-In the example above, the `Date` method returns the `myDate` value if it exists, otherwise it returns `date`.
-
-To fall back to the default sequence of dates, use the `:default` token:
-
-{{< code-toggle file=hugo >}}
-[frontmatter]
-date = ["myDate", ":default"]
-{{< /code-toggle >}}
-
-In the example above, the `Date` method returns the `myDate` value if it exists, otherwise it returns the first valid date from `date`, `publishdate`, `pubdate`, `published`, `lastmod`, and `modified`.
-
-## Aliases
-
-Some of the front matter fields have aliases.
-
-Front matter field|Aliases
-:--|:--
-`expiryDate`|`unpublishdate`
-`lastmod`|`modified`
-`publishDate`|`pubdate`, `published`
-
-The default front matter configuration includes these aliases.
-
-## Tokens
-
-Hugo provides several [tokens](g) to assist with front matter configuration.
-
-Token|Description
-:--|:--
-`:default`|The default ordered sequence of date fields.
-`:fileModTime`|The file's last modification timestamp.
-`:filename`|The date from the file name, if present.
-`:git`|The Git author date for the file's last revision.
-
-When Hugo extracts a date from a file name, it uses the rest of the file name to generate the page's [`slug`], but only if a slug isn't already specified in the page's front matter. For example, given the name `2025-02-01-article.md`, Hugo will set the `date` to `2025-02-01` and the `slug` to `article`.
-
-[`slug`]: /content-management/front-matter/#slug
-
-To enable access to the Git author date, set [`enableGitInfo`] to `true`, or use\
-the `--enableGitInfo` flag when building your site.
-
-[`enableGitInfo`]: /configuration/all/#enablegitinfo
-
-Consider this example:
-
-{{< code-toggle file=hugo >}}
-[frontmatter]
-date = [':filename', ':default']
-lastmod = ['lastmod', ':fileModTime']
-{{< /code-toggle >}}
-
-To determine `date`, Hugo tries to extract the date from the file name, falling back to the default ordered sequence of date fields.
-
-To determine `lastmod`, Hugo looks for a `lastmod` field in front matter, falling back to the file's last modification timestamp.
diff --git a/docs/content/en/configuration/http-cache.md b/docs/content/en/configuration/http-cache.md
deleted file mode 100644
index 788d22a08..000000000
--- a/docs/content/en/configuration/http-cache.md
+++ /dev/null
@@ -1,107 +0,0 @@
----
-title: Configure the HTTP cache
-linkTitle: HTTP cache
-description: Configure the HTTP cache.
-categories: []
-keywords: []
----
-
-> [!note]
-> This configuration is only relevant when using the [`resources.GetRemote`] function.
-
-## Layered caching
-
-Hugo employs a layered caching system.
-
-```goat {.w-40}
- .-----------.
-| dynacache |
- '-----+-----'
- |
- v
- .----------.
-| HTTP cache |
- '-----+----'
- |
- v
- .----------.
-| file cache |
- '-----+----'
-```
-
-Dynacache
-: An in-memory cache employing a Least Recently Used (LRU) eviction policy. Entries are removed from the cache when changes occur, when they match [cache-busting] patterns, or under low-memory conditions.
-
-HTTP Cache
-: An HTTP cache for remote resources as specified in [RFC 9111]. Optimal performance is achieved when resources include appropriate HTTP cache headers. The HTTP cache utilizes the file cache for storage and retrieval of cached resources.
-
-File cache
-: See [configure file caches].
-
-The HTTP cache involves two key aspects: determining which content to cache (the caching process itself) and defining the frequency with which to check for updates (the polling strategy).
-
-## HTTP caching
-
-The HTTP cache behavior is defined for a configured set of resources. Stale resources will be refreshed from the file cache, even if their configured Time-To-Live (TTL) has not expired. If HTTP caching is disabled for a resource, Hugo will bypass the cache and access the file directly.
-
-The default configuration disables everything:
-
-{{< code-toggle file=hugo >}}
-[HTTPCache.cache.for]
-excludes = ['**']
-includes = []
-{{< /code-toggle >}}
-
-cache.for.excludes
-: (`string`) A list of [glob](g) patterns to exclude from caching.
-
-cache.for.includes
-: (`string`) A list of [glob](g) patterns to cache.
-
-## HTTP polling
-
-Polling is used in watch mode (e.g., `hugo server`) to detect changes in remote resources. Polling can be enabled even if HTTP caching is disabled. Detected changes trigger a rebuild of pages using the affected resource. Polling can be disabled for specific resources, typically those known to be static.
-
-The default configuration disables everything:
-
-{{< code-toggle file=hugo >}}
-[[HTTPCache.polls]]
-disable = true
-high = '0s'
-low = '0s'
-[HTTPCache.polls.for]
-includes = ['**']
-excludes = []
-{{< /code-toggle >}}
-
-polls
-: A slice of polling configurations.
-
-polls.disable
-: (`bool`) Whether to disable polling for this configuration.
-
-polls.low
-: (`string`) The minimum polling interval expressed as a [duration](g). This is used after a recent change and gradually increases towards `polls.high`.
-
-polls.high
-: (`string`) The maximum polling interval expressed as a [duration](g). This is used when the resource is considered stable.
-
-polls.for.excludes
-: (`string`) A list of [glob](g) patterns to exclude from polling for this configuration.
-
-polls.for.includes
-: (`string`) A list of [glob](g) patterns to include in polling for this configuration.
-
-## Behavior
-
-Polling and HTTP caching interact as follows:
-
-- With polling enabled, rebuilds are triggered only by actual changes, detected via `eTag` changes (Hugo generates an MD5 hash if the server doesn't provide one).
-- If polling is enabled but HTTP caching is disabled, the remote is checked for changes only after the file cache's TTL expires (e.g., a `maxAge` of `10h` with a `1s` polling interval is inefficient).
-- If both polling and HTTP caching are enabled, changes are checked for even before the file cache's TTL expires. Cached `eTag` and `last-modified` values are sent in `if-none-match` and `if-modified-since` headers, respectively, and a cached response is returned on HTTP [304].
-
-[`resources.GetRemote`]: /functions/resources/getremote/
-[304]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304
-[cache-busting]: /configuration/build/#cache-busters
-[configure file caches]: /configuration/caches/
-[RFC 9111]: https://datatracker.ietf.org/doc/html/rfc9111
diff --git a/docs/content/en/configuration/imaging.md b/docs/content/en/configuration/imaging.md
deleted file mode 100644
index 13ecf9c26..000000000
--- a/docs/content/en/configuration/imaging.md
+++ /dev/null
@@ -1,69 +0,0 @@
----
-title: Configure imaging
-linkTitle: Imaging
-description: Configure imaging.
-categories: []
-keywords: []
----
-
-## Processing options
-
-These are the default settings for processing images:
-
-{{< code-toggle file=hugo >}}
-[imaging]
-anchor = 'Smart'
-bgColor = '#ffffff'
-hint = 'photo'
-quality = 75
-resampleFilter = 'box'
-{{< /code-toggle >}}
-
-anchor
-: (`string`) When using the [`Crop`] or [`Fill`] method, the anchor determines the placement of the crop box. One of `TopLeft`, `Top`, `TopRight`, `Left`, `Center`, `Right`, `BottomLeft`, `Bottom`, `BottomRight`, or `Smart`. Default is `Smart`.
-
-bgColor
-: (`string`) The background color of the resulting image. Applicable when converting from a format that supports transparency to a format that does not support transparency, for example, when converting from PNG to JPEG. Expressed as an RGB [hexadecimal] value. Default is `#ffffff`.
-
-[hexadecimal]: https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color
-
-hint
-: (`string`) Applicable to WebP images, this option corresponds to a set of predefined encoding parameters. One of `drawing`, `icon`, `photo`, `picture`, or `text`. Default is `photo`. See [details](/content-management/image-processing/#hint).
-
-quality
-: (`int`) Applicable to JPEG and WebP images, this value determines the quality of the converted image. Higher values produce better quality images, while lower values produce smaller files. Set this value to a whole number between `1` and `100`, inclusive. Default is `75`.
-
-resampleFilter
-: (`string`) The resampling filter used when resizing an image. Default is `box`. See [details](/content-management/image-processing/#resampling-filter)
-
-## EXIF data
-
-These are the default settings for extracting EXIF data from images:
-
-{{< code-toggle file=hugo >}}
-[imaging.exif]
-includeFields = ""
-excludeFields = ""
-disableDate = false
-disableLatLong = false
-{{< /code-toggle >}}
-
-disableDate
-: (`bool`) Whether to disable extraction of the image creation date/time. Default is `false`.
-
-disableLatLong
-: (`bool`) Whether to disable extraction of the GPS latitude and longitude. Default is `false`.
-
-excludeFields
-: (`string`) A [regular expression](g) matching the tags to exclude when extracting EXIF data.
-
-includeFields
-: (`string`) A [regular expression](g) matching the tags to include when extracting EXIF data. To include all available tags, set this value to `".*"`.
-
-> [!note]
-> To improve performance and decrease cache size, Hugo excludes the following tags: `ColorSpace`, `Contrast`, `Exif`, `Exposure[M|P|B]`, `Flash`, `GPS`, `JPEG`, `Metering`, `Resolution`, `Saturation`, `Sensing`, `Sharp`, and `WhiteBalance`.
->
-> To control tag availability, change the `excludeFields` or `includeFields` settings as described above.
-
-[`Crop`]: /methods/resource/crop/
-[`Fill`]: /methods/resource/fill/
diff --git a/docs/content/en/configuration/introduction.md b/docs/content/en/configuration/introduction.md
deleted file mode 100644
index 8f8ad4c1e..000000000
--- a/docs/content/en/configuration/introduction.md
+++ /dev/null
@@ -1,284 +0,0 @@
----
-title: Introduction
-description: Configure your site using files, directories, and environment variables.
-categories: []
-keywords: []
-weight: 10
----
-
-## Sensible defaults
-
-Hugo offers many configuration options, but its defaults are often sufficient. A new site requires only these settings:
-
-{{< code-toggle file=hugo >}}
-baseURL = 'https://example.org/'
-languageCode = 'en-us'
-title = 'My New Hugo Site'
-{{< /code-toggle >}}
-
-Only define settings that deviate from the defaults. A smaller configuration file is easier to read, understand, and debug. Keep your configuration concise.
-
-> [!note]
-> The best configuration file is a short configuration file.
-
-## Configuration file
-
-Create a site configuration file in the root of your project directory, naming it `hugo.toml`, `hugo.yaml`, or `hugo.json`, with that order of precedence.
-
-```text
-my-project/
-└── hugo.toml
-```
-
-> [!note]
-> For versions v0.109.0 and earlier, the site configuration file was named `config`. While you can still use this name, it's recommended to switch to the newer naming convention, `hugo`.
-
-A simple example:
-
-{{< code-toggle file=hugo >}}
-baseURL = 'https://example.org/'
-languageCode = 'en-us'
-title = 'ABC Widgets, Inc.'
-[params]
-subtitle = 'The Best Widgets on Earth'
-[params.contact]
-email = 'info@example.org'
-phone = '+1 202-555-1212'
-{{< /code-toggle >}}
-
-To use a different configuration file when building your site, use the `--config` flag:
-
-```sh
-hugo --config other.toml
-```
-
-Combine two or more configuration files, with left-to-right precedence:
-
-```sh
-hugo --config a.toml,b.yaml,c.json
-```
-
-> [!note]
-> See the specifications for each file format: [TOML], [YAML], and [JSON].
-
-## Configuration directory
-
-Instead of a single site configuration file, split your configuration by [environment](g), root configuration key, and language. For example:
-
-```text
-my-project/
-└── config/
- ├── _default/
- │ ├── hugo.toml
- │ ├── menus.en.toml
- │ ├── menus.de.toml
- │ └── params.toml
- └── production/
- └── params.toml
-```
-
-The root configuration keys are {{< root-configuration-keys >}}.
-
-### Omit the root key
-
-When splitting the configuration by root key, omit the root key in the component file. For example, these are equivalent:
-
-{{< code-toggle file=config/_default/hugo >}}
-[params]
-foo = 'bar'
-{{< /code-toggle >}}
-
-{{< code-toggle file=config/_default/params >}}
-foo = 'bar'
-{{< /code-toggle >}}
-
-### Recursive parsing
-
-Hugo parses the `config` directory recursively, allowing you to organize the files into subdirectories. For example:
-
-```text
-my-project/
-└── config/
- └── _default/
- ├── navigation/
- │ ├── menus.de.toml
- │ └── menus.en.toml
- └── hugo.toml
-```
-
-### Example
-
-```text
-my-project/
-└── config/
- ├── _default/
- │ ├── hugo.toml
- │ ├── menus.en.toml
- │ ├── menus.de.toml
- │ └── params.toml
- ├── production/
- │ ├── hugo.toml
- │ └── params.toml
- └── staging/
- ├── hugo.toml
- └── params.toml
-```
-
-Considering the structure above, when running `hugo --environment staging`, Hugo will use every setting from `config/_default` and merge `staging`'s on top of those.
-
-Let's take an example to understand this better. Let's say you are using Google Analytics for your website. This requires you to specify a [Google tag ID] in your site configuration:
-
-{{< code-toggle file=hugo >}}
-[services.googleAnalytics]
-ID = 'G-XXXXXXXXX'
-{{< /code-toggle >}}
-
-Now consider the following scenario:
-
-1. You don't want to load the analytics code when running `hugo server`.
-1. You want to use different Google tag IDs for your production and staging environments. For example:
- - `G-PPPPPPPPP` for production
- - `G-SSSSSSSSS` for staging
-
-To satisfy these requirements, configure your site as follows:
-
-1. `config/_default/hugo.toml`
- - Exclude the `services.googleAnalytics` section. This will prevent loading of the analytics code when you run `hugo server`.
- - By default, Hugo sets its `environment` to `development` when running `hugo server`. In the absence of a `config/development` directory, Hugo uses the `config/_default` directory.
-1. `config/production/hugo.toml`
- - Include this section only:
-
- {{< code-toggle file=hugo >}}
- [services.googleAnalytics]
- ID = 'G-PPPPPPPPP'
- {{< /code-toggle >}}
-
- - You do not need to include other parameters in this file. Include only those parameters that are specific to your production environment. Hugo will merge these parameters with the default configuration.
- - By default, Hugo sets its `environment` to `production` when running `hugo`. The analytics code will use the `G-PPPPPPPPP` tag ID.
-
-1. `config/staging/hugo.toml`
-
- - Include this section only:
-
- {{< code-toggle file=hugo >}}
- [services.googleAnalytics]
- ID = 'G-SSSSSSSSS'
- {{< /code-toggle >}}
-
- - You do not need to include other parameters in this file. Include only those parameters that are specific to your staging environment. Hugo will merge these parameters with the default configuration.
- - To build your staging site, run `hugo --environment staging`. The analytics code will use the `G-SSSSSSSSS` tag ID.
-
-## Merge configuration settings
-
-Hugo merges configuration settings from themes and modules, prioritizing the project's own settings. Given this simplified project structure with two themes:
-
-```text
-project/
-├── themes/
-│ ├── theme-a/
-│ │ └── hugo.toml
-│ └── theme-b/
-│ └── hugo.toml
-└── hugo.toml
-```
-
-and this project-level configuration:
-
-{{< code-toggle file=hugo >}}
-baseURL = 'https://example.org/'
-languageCode = 'en-us'
-title = 'My New Hugo Site'
-theme = ['theme-a','theme-b']
-{{< /code-toggle >}}
-
-Hugo merges settings in this order:
-
-1. Project configuration (`hugo.toml` in the project root)
-1. `theme-a` configuration
-1. `theme-b` configuration
-
-The `_merge` setting within each top-level configuration key controls _which_ settings are merged and _how_ they are merged.
-
-The value for `_merge` can be one of:
-
-none
-: No merge.
-
-shallow
-: Only add values for new keys.
-
-deep
-: Add values for new keys, merge existing.
-
-Note that you don't need to be so verbose as in the default setup below; a `_merge` value higher up will be inherited if not set.
-
-{{< code-toggle file=hugo dataKey="config_helpers.mergeStrategy" skipHeader=true />}}
-
-## Environment variables
-
-You can also configure settings using operating system environment variables:
-
-```sh
-export HUGO_BASEURL=https://example.org/
-export HUGO_ENABLEGITINFO=true
-export HUGO_ENVIRONMENT=staging
-hugo
-```
-
-The above sets the [`baseURL`], [`enableGitInfo`], and [`environment`] configuration options and then builds your site.
-
-> [!note]
-> An environment variable takes precedence over the values set in the configuration file. This means that if you set a configuration value with both an environment variable and in the configuration file, the value in the environment variable will be used.
-
-Environment variables simplify configuration for [CI/CD](g) deployments like GitHub Pages, GitLab Pages, and Netlify by allowing you to set values directly within their respective configuration and workflow files.
-
-> [!note]
-> Environment variable names must be prefixed with `HUGO_`.
->
-> To set custom site parameters, prefix the name with `HUGO_PARAMS_`.
-
-For snake_case variable names, the standard `HUGO_` prefix won't work. Hugo infers the delimiter from the first character following `HUGO`. This allows for variations like `HUGOxPARAMSxAPI_KEY=abcdefgh` using any [permitted delimiter].
-
-In addition to configuring standard settings, environment variables may be used to override default values for certain internal settings:
-
-DART_SASS_BINARY
-: (`string`) The absolute path to the Dart Sass executable. By default, Hugo searches for the executable in each of the paths in the `PATH` environment variable.
-
-HUGO_FILE_LOG_FORMAT
-: (`string`) A format string for the file path, line number, and column number displayed when reporting errors, or when calling the `Position` method from a shortcode or Markdown render hook. Valid tokens are `:file`, `:line`, and `:col`. Default is `:file::line::col`.
-
-HUGO_MEMORYLIMIT
-: {{< new-in 0.123.0 />}}
-: (`int`) The maximum amount of system memory, in gigabytes, that Hugo can use while rendering your site. Default is 25% of total system memory. Note that `HUGO_MEMORYLIMIT` is a "best effort" setting. Don't expect Hugo to build a million pages with only 1 GB of memory. You can get more information about how this behaves during the build by building with `hugo --logLevel info` and look for the `dynacache` label.
-
-HUGO_NUMWORKERMULTIPLIER
-: (`int`) The number of workers used in parallel processing. Default is the number of logical CPUs.
-
-## Current configuration
-
-Display the complete site configuration with:
-
-```sh
-hugo config
-```
-
-Display a specific configuration setting with:
-
-```sh
-hugo config | grep [key]
-```
-
-Display the configured file mounts with:
-
-```sh
-hugo config mounts
-```
-
-[`baseURL`]: /configuration/all#baseurl
-[`enableGitInfo`]: /configuration/all#enablegitinfo
-[`environment`]: /configuration/all#environment
-[Google tag ID]: https://support.google.com/tagmanager/answer/12326985?hl=en
-[JSON]: https://datatracker.ietf.org/doc/html/rfc7159
-[permitted delimiter]: https://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap08.html
-[TOML]: https://toml.io/en/latest
-[YAML]: https://yaml.org/spec/
diff --git a/docs/content/en/configuration/languages.md b/docs/content/en/configuration/languages.md
deleted file mode 100644
index 540cfd34f..000000000
--- a/docs/content/en/configuration/languages.md
+++ /dev/null
@@ -1,193 +0,0 @@
----
-title: Configure languages
-linkTitle: Languages
-description: Configure the languages in your multilingual site.
-categories: []
-keywords: []
----
-
-## Base settings
-
-Configure the following base settings within the site's root configuration:
-
-{{< code-toggle file=hugo >}}
-defaultContentLanguage = 'en'
-defaultContentLanguageInSubdir = false
-disableDefaultLanguageRedirect = false
-disableLanguages = []
-{{< /code-toggle >}}
-
-defaultContentLanguage
-: (`string`) The project's default language key, conforming to the syntax described in [RFC 5646]. This value must match one of the defined [language keys](#language-keys). Default is `en`.
-
-defaultContentLanguageInSubdir
-: (`bool`) Whether to publish the default language site to a subdirectory matching the `defaultContentLanguage`. Default is `false`.
-
-disableDefaultLanguageRedirect
-: {{< new-in 0.140.0 />}}
-: (`bool`) Whether to disable generation of the alias redirect to the default language when `DefaultContentLanguageInSubdir` is `true`. Default is `false`.
-
-disableLanguages
-: (`[]string]`) A slice of language keys representing the languages to disable during the build process. Although this is functional, consider using the [`disabled`](#disabled) key under each language instead.
-
-## Language settings
-
-Configure each language under the `languages` key:
-
-{{< code-toggle config=languages />}}
-
-In the above, `en` is the [language key](#language-keys).
-
-disabled
-: (`bool`) Whether to disable this language when building the site. Default is `false`.
-
-languageCode
-: (`string`) The language tag as described in [RFC 5646]. This value does not affect localization or URLs. Hugo uses this value to populate:
-
- - The `lang` attribute of the `html` element in the [embedded alias template]
- - The `language` element in the [embedded RSS template]
- - The `locale` property in the [embedded OpenGraph template]
-
- Access this value from a template using the [`Language.LanguageCode`] method on a `Site` or `Page` object.
-
-languageDirection
-: (`string`) The language direction, either left-to-right (`ltr`) or right-to-left (`rtl`). Use this value in your templates with the global [`dir`] HTML attribute. Access this value from a template using the [`Language.LanguageDirection`] method on a `Site` or `Page` object.
-
-languageName
-: (`string`) The language name, typically used when rendering a language switcher. Access this value from a template using the [`Language.LanguageName`] method on a `Site` or `Page` object.
-
-title
-: (`string`) The site title for this language. Access this value from a template using the [`Title`] method on a `Site` object.
-
-weight
-: (`int`) The language [weight](g). When set to a non-zero value, this is the primary sort criteria for this language. Access this value from a template using the [`Language.Weight`] method on a `Site` or `Page` object.
-
-## Localized settings
-
-Some configuration settings can be defined separately for each language. For example:
-
-{{< code-toggle file=hugo >}}
-[languages.en]
-languageCode = 'en-US'
-languageName = 'English'
-weight = 1
-title = 'Project Documentation'
-timeZone = 'America/New_York'
-[languages.en.pagination]
-path = 'page'
-[languages.en.params]
-subtitle = 'Reference, Tutorials, and Explanations'
-{{< /code-toggle >}}
-
-The following configuration keys can be defined separately for each language:
-
-{{< per-lang-config-keys >}}
-
-Any key not defined in a `languages` object will fall back to the global value in the root of the site configuration.
-
-## Language keys
-
-Language keys must conform to the syntax described in [RFC 5646]. For example:
-
-{{< code-toggle file=hugo >}}
-defaultContentLanguage = 'de'
-[languages.de]
- weight = 1
-[languages.en-US]
- weight = 2
-[languages.pt-BR]
- weight = 3
-{{< /code-toggle >}}
-
-Artificial languages with private use subtags as defined in [RFC 5646 § 2.2.7] are also supported. Omit the `art-x-` prefix from the language key. For example:
-
-{{< code-toggle file=hugo >}}
-defaultContentLanguage = 'en'
-[languages.en]
-weight = 1
-[languages.hugolang]
-weight = 2
-{{< /code-toggle >}}
-
-> [!note]
-> Private use subtags must not exceed 8 alphanumeric characters.
-
-## Example
-
-{{< code-toggle file=hugo >}}
-defaultContentLanguage = 'de'
-defaultContentLanguageInSubdir = true
-disableDefaultLanguageRedirect = false
-
-[languages.de]
-contentDir = 'content/de'
-disabled = false
-languageCode = 'de-DE'
-languageDirection = 'ltr'
-languageName = 'Deutsch'
-title = 'Projekt Dokumentation'
-weight = 1
-
-[languages.de.params]
-subtitle = 'Referenz, Tutorials und Erklärungen'
-
-[languages.en]
-contentDir = 'content/en'
-disabled = false
-languageCode = 'en-US'
-languageDirection = 'ltr'
-languageName = 'English'
-title = 'Project Documentation'
-weight = 2
-
-[languages.en.params]
-subtitle = 'Reference, Tutorials, and Explanations'
-{{< /code-toggle >}}
-
-> [!note]
-> In the example above, omit `contentDir` if [translating by file name].
-
-## Multihost
-
-Hugo supports multiple languages in a multihost configuration. This means you can configure a `baseURL` per `language`.
-
-> [!note]
-> If you define a `baseURL` for one language, you must define a unique `baseURL` for all languages.
-
-For example:
-
-{{< code-toggle file=hugo >}}
-defaultContentLanguage = 'fr'
-[languages]
- [languages.en]
- baseURL = 'https://en.example.org/'
- languageName = 'English'
- title = 'In English'
- weight = 2
- [languages.fr]
- baseURL = 'https://fr.example.org'
- languageName = 'Français'
- title = 'En Français'
- weight = 1
-{{ code-toggle >}}
-
-With the above, Hugo publishes two sites, each with their own root:
-
-```text
-public
-├── en
-└── fr
-```
-
-[`dir`]: https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/dir
-[`Language.LanguageCode`]: /methods/site/language/#languagecode
-[`Language.LanguageDirection`]: /methods/site/language/#languagedirection
-[`Language.LanguageName`]: /methods/site/language/#languagename
-[`Language.Weight`]: /methods/site/language/#weight
-[`Title`]: /methods/site/title/
-[embedded alias template]: {{% eturl alias %}}
-[embedded OpenGraph template]: {{% eturl opengraph %}}
-[embedded RSS template]: {{% eturl rss %}}
-[RFC 5646]: https://datatracker.ietf.org/doc/html/rfc5646#section-2.1
-[RFC 5646 § 2.2.7]: https://datatracker.ietf.org/doc/html/rfc5646#section-2.2.7
-[translating by file name]: /content-management/multilingual/#translation-by-file-name
diff --git a/docs/content/en/configuration/markup.md b/docs/content/en/configuration/markup.md
deleted file mode 100644
index b6135cee5..000000000
--- a/docs/content/en/configuration/markup.md
+++ /dev/null
@@ -1,341 +0,0 @@
----
-title: Configure markup
-linkTitle: Markup
-description: Configure markup.
-categories: []
-keywords: []
-aliases: [/getting-started/configuration-markup/]
----
-
-## Default handler
-
-In its default configuration, Hugo uses [Goldmark] to render Markdown to HTML.
-
-{{< code-toggle file=hugo >}}
-[markup]
-defaultMarkdownHandler = 'goldmark'
-{{< /code-toggle >}}
-
-Files with ending with `.md`, `.mdown`, or `.markdown` are processed as Markdown, unless you've explicitly set a different format using the `markup` field in your front matter.
-
-To use a different renderer for Markdown files, specify one of `asciidocext`, `org`, `pandoc`, or `rst` in your site configuration.
-
-`defaultMarkdownHandler`|Renderer
-:--|:--
-`asciidocext`|[AsciiDoc]
-`goldmark`|[Goldmark]
-`org`|[Emacs Org Mode]
-`pandoc`|[Pandoc]
-`rst`|[reStructuredText]
-
-To use AsciiDoc, Pandoc, or reStructuredText you must install the relevant renderer and update your [security policy].
-
-> [!note]
-> Unless you need a unique capability provided by one of the alternative Markdown handlers, we strongly recommend that you use the default setting. Goldmark is fast, well maintained, conforms to the [CommonMark] specification, and is compatible with [GitHub Flavored Markdown] (GFM).
-
-## Goldmark
-
-This is the default configuration for the Goldmark Markdown renderer:
-
-{{< code-toggle config=markup.goldmark />}}
-
-### Extensions
-
-The extensions below, excluding Extras and Passthrough, are enabled by default.
-
-Extension|Documentation|Enabled
-:--|:--|:-:
-`cjk`|[Goldmark Extensions: CJK]|:heavy_check_mark:
-`definitionList`|[PHP Markdown Extra: Definition lists]|:heavy_check_mark:
-`extras`|[Hugo Goldmark Extensions: Extras]||
-`footnote`|[PHP Markdown Extra: Footnotes]|:heavy_check_mark:
-`linkify`|[GitHub Flavored Markdown: Autolinks]|:heavy_check_mark:
-`passthrough`|[Hugo Goldmark Extensions: Passthrough]||
-`strikethrough`|[GitHub Flavored Markdown: Strikethrough]|:heavy_check_mark:
-`table`|[GitHub Flavored Markdown: Tables]|:heavy_check_mark:
-`taskList`|[GitHub Flavored Markdown: Task list items]|:heavy_check_mark:
-`typographer`|[Goldmark Extensions: Typographer]|:heavy_check_mark:
-
-#### Extras
-
-{{< new-in 0.126.0 />}}
-
-Enable [deleted text], [inserted text], [mark text], [subscript], and [superscript] elements in Markdown.
-
-Element|Markdown|Rendered
-:--|:--|:--
-Deleted text|`~~foo~~`|`foo`
-Inserted text|`++bar++`|`bar`
-Mark text|`==baz==`|`baz`
-Subscript|`H~2~O`|`H2O`
-Superscript|`1^st^`|`1st`
-
-To avoid a conflict when enabling the "subscript" feature of the Extras extension, if you want to render subscript and strikethrough text concurrently you must:
-
-1. Disable the Strikethrough extension
-1. Enable the "deleted text" feature of the Extras extension
-
-For example:
-
-{{< code-toggle file=hugo >}}
-[markup.goldmark.extensions]
-strikethrough = false
-
-[markup.goldmark.extensions.extras.delete]
-enable = true
-
-[markup.goldmark.extensions.extras.subscript]
-enable = true
-{{< /code-toggle >}}
-
-#### Passthrough
-
-{{< new-in 0.122.0 />}}
-
-Enable the Passthrough extension to include mathematical equations and expressions in Markdown using LaTeX markup. See [mathematics in Markdown] for details.
-
-#### Typographer
-
-The Typographer extension replaces certain character combinations with HTML entities as specified below:
-
-Markdown|Replaced by|Description
-:--|:--|:--
-`...`|`…`|horizontal ellipsis
-`'`|`’`|apostrophe
-`--`|`–`|en dash
-`---`|`—`|em dash
-`«`|`«`|left angle quote
-`“`|`“`|left double quote
-`‘`|`‘`|left single quote
-`»`|`»`|right angle quote
-`”`|`”`|right double quote
-`’`|`’`|right single quote
-
-### Settings explained
-
-Most of the Goldmark settings above are self-explanatory, but some require explanation.
-
-duplicateResourceFiles
-: {{< new-in 0.123.0 />}}
-: (`bool`) Whether to duplicate shared page resources for each language on multilingual single-host sites. See [multilingual page resources] for details. Default is `false`.
-
- > [!note]
- > With multilingual single-host sites, setting this parameter to `false` will enable Hugo's [embedded link render hook] and [embedded image render hook]. This is the default configuration for multilingual single-host sites.
-
-parser.wrapStandAloneImageWithinParagraph
-: (`bool`) Whether to wrap image elements without adjacent content within a `p` element when rendered. This is the default Markdown behavior. Set to `false` when using an [image render hook] to render standalone images as `figure` elements. Default is `true`.
-
-parser.autoDefinitionTermID
-: {{< new-in 0.144.0 />}}
-: (`bool`) Whether to automatically add `id` attributes to description list terms (i.e., `dt` elements). When `true`, the `id` attribute of each `dt` element is accessible through the [`Fragments.Identifiers`] method on a `Page` object.
-
-parser.autoHeadingID
-: (`bool`) Whether to automatically add `id` attributes to headings (i.e., `h1`, `h2`, `h3`, `h4`, `h5`, and `h6` elements).
-
-parser.autoIDType
-: (`string`) The strategy used to automatically generate `id` attributes, one of `github`, `github-ascii` or `blackfriday`.
-
- - `github` produces GitHub-compatible `id` attributes
- - `github-ascii` drops any non-ASCII characters after accent normalization
- - `blackfriday` produces `id` attributes compatible with the Blackfriday Markdown renderer
-
- This is also the strategy used by the [anchorize](/functions/urls/anchorize) template function. Default is `github`.
-
-parser.attribute.block
-: (`bool`) Whether to enable [Markdown attributes] for block elements. Default is `false`.
-
-parser.attribute.title
-: (`bool`) Whether to enable [Markdown attributes] for headings. Default is `true`.
-
-renderHooks.image.enableDefault
-: {{< new-in 0.123.0 />}}
-: (`bool`) Whether to enable the [embedded image render hook]. Default is `false`.
-
- > [!note]
- > The embedded image render hook is automatically enabled for multilingual single-host sites if [duplication of shared page resources] is disabled. This is the default configuration for multilingual single-host sites.
-
-renderHooks.link.enableDefault
-: {{< new-in 0.123.0 />}}
-: (`bool`) Whether to enable the [embedded link render hook]. Default is `false`.
-
- > [!note]
- > The embedded link render hook is automatically enabled for multilingual single-host sites if [duplication of shared page resources] is disabled. This is the default configuration for multilingual single-host sites.
-
-renderer.hardWraps
-: (`bool`) Whether to replace newline characters within a paragraph with `br` elements. Default is `false`.
-
-renderer.unsafe
-: (`bool`) Whether to render raw HTML mixed within Markdown. This is unsafe unless the content is under your control. Default is `false`.
-
-## AsciiDoc
-
-This is the default configuration for the AsciiDoc renderer:
-
-{{< code-toggle config=markup.asciidocExt />}}
-
-### Settings explained
-
-attributes
-: (`map`) A map of key-value pairs, each a document attribute. See Asciidoctor's [attributes].
-
-backend
-: (`string`) The backend output file format. Default is `html5`.
-
-extensions
-: (`string array`) An array of enabled extensions, one or more of `asciidoctor-html5s`, `asciidoctor-bibtex`, `asciidoctor-diagram`, `asciidoctor-interdoc-reftext`, `asciidoctor-katex`, `asciidoctor-latex`, `asciidoctor-mathematical`, or `asciidoctor-question`.
-
- > [!note]
- > To mitigate security risks, entries in the extension array may not contain forward slashes (`/`), backslashes (`\`), or periods. Due to this restriction, extensions must be in Ruby's `$LOAD_PATH`.
-
-failureLevel
-: (`string`) The minimum logging level that triggers a non-zero exit code (failure). Default is `fatal`.
-
-noHeaderOrFooter
-: (`bool`) Whether to output an embeddable document, which excludes the header, the footer, and everything outside the body of the document. Default is `true`.
-
-preserveTOC
-: (`bool`) Whether to preserve the table of contents (TOC) rendered by Asciidoctor. By default, to make the TOC compatible with existing themes, Hugo removes the TOC rendered by Asciidoctor. To render the TOC, use the [`TableOfContents`] method on a `Page` object in your templates. Default is `false`.
-
-safeMode
-: (`string`) The safe mode level, one of `unsafe`, `safe`, `server`, or `secure`. Default is `unsafe`.
-
-sectionNumbers
-: (`bool`) Whether to number each section title. Default is `false`.
-
-trace
-: (`bool`) Whether to include backtrace information on errors. Default is `false`.
-
-verbose
-: (`bool`) Whether to verbosely print processing information and configuration file checks to stderr. Default is `false`.
-
-workingFolderCurrent
-: (`bool`) Whether to set the working directory to be the same as that of the AsciiDoc file being processed, allowing [includes] to work with relative paths. Set to `true` to render diagrams with the [asciidoctor-diagram] extension. Default is `false`.
-
-### Configuration example
-
-{{< code-toggle file=hugo >}}
-[markup.asciidocExt]
- extensions = ["asciidoctor-html5s", "asciidoctor-diagram"]
- workingFolderCurrent = true
- [markup.asciidocExt.attributes]
- my-base-url = "https://example.com/"
- my-attribute-name = "my value"
-{{< /code-toggle >}}
-
-### Syntax highlighting
-
-Follow the steps below to enable syntax highlighting.
-
-#### Step 1
-
-Set the `source-highlighter` attribute in your site configuration. For example:
-
-{{< code-toggle file=hugo >}}
-[markup.asciidocExt.attributes]
-source-highlighter = 'rouge'
-{{< /code-toggle >}}
-
-#### Step 2
-
-Generate the highlighter CSS. For example:
-
-```text
-rougify style monokai.sublime > assets/css/syntax.css
-```
-
-#### Step 3
-
-In your base template add a link to the CSS file:
-
-```go-html-template {file="layouts/_default/baseof.html"}
-
- ...
- {{ with resources.Get "css/syntax.css" }}
-
- {{ end }}
- ...
-
-```
-
-Then add the code to be highlighted to your markup:
-
-```text
-[#hello,ruby]
-----
-require 'sinatra'
-
-get '/hi' do
- "Hello World!"
-end
-----
-```
-
-### Troubleshooting
-
-Run `hugo --logLevel debug` to examine Hugo's call to the Asciidoctor executable:
-
-```txt
-INFO 2019/12/22 09:08:48 Rendering book-as-pdf.adoc with C:\Ruby26-x64\bin\asciidoctor.bat using asciidoc args [--no-header-footer -r asciidoctor-html5s -b html5s -r asciidoctor-diagram --base-dir D:\prototypes\hugo_asciidoc_ddd\docs -a outdir=D:\prototypes\hugo_asciidoc_ddd\build -] ...
-```
-
-## Highlight
-
-This is the default configuration.
-
-{{< code-toggle config=markup.highlight />}}
-
-{{% include "/_common/syntax-highlighting-options.md" %}}
-
-## Table of contents
-
-This is the default configuration for the table of contents, applicable to Goldmark and Asciidoctor:
-
-{{< code-toggle config=markup.tableOfContents />}}
-
-startLevel
-: (`int`) Heading levels less than this value will be excluded from the table of contents. For example, to exclude `h1` elements from the table of contents, set this value to `2`. Default is `2`.
-
-endLevel
-: (`int`) Heading levels greater than this value will be excluded from the table of contents. For example, to exclude `h4`, `h5`, and `h6` elements from the table of contents, set this value to `3`. Default is `3`.
-
-ordered
-: (`bool`) Whether to generates an ordered list instead of an unordered list. Default is `false`.
-
-[`Fragments.Identifiers`]: /methods/page/fragments/#identifiers
-[`TableOfContents`]: /methods/page/tableofcontents/
-[asciidoctor-diagram]: https://asciidoctor.org/docs/asciidoctor-diagram/
-[attributes]: https://asciidoctor.org/docs/asciidoc-syntax-quick-reference/#attributes-and-substitutions
-[CommonMark]: https://spec.commonmark.org/current/
-[deleted text]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/del
-[duplication of shared page resources]: /configuration/markup/#duplicateresourcefiles
-[duplication of shared page resources]: /configuration/markup/#duplicateresourcefiles
-[embedded image render hook]: /render-hooks/images/#default
-[embedded image render hook]: /render-hooks/images/#default
-[embedded link render hook]: /render-hooks/links/#default
-[embedded link render hook]: /render-hooks/links/#default
-[GitHub Flavored Markdown]: https://github.github.com/gfm/
-[GitHub Flavored Markdown: Autolinks]: https://github.github.com/gfm/#autolinks-extension-
-[GitHub Flavored Markdown: Strikethrough]: https://github.github.com/gfm/#strikethrough-extension-
-[GitHub Flavored Markdown: Tables]: https://github.github.com/gfm/#tables-extension-
-[GitHub Flavored Markdown: Task list items]: https://github.github.com/gfm/#task-list-items-extension-
-[Goldmark]: https://github.com/yuin/goldmark/
-[Goldmark Extensions: CJK]: https://github.com/yuin/goldmark?tab=readme-ov-file#cjk-extension
-[Goldmark Extensions: Typographer]: https://github.com/yuin/goldmark?tab=readme-ov-file#typographer-extension
-[Hugo Goldmark Extensions: Extras]: https://github.com/gohugoio/hugo-goldmark-extensions?tab=readme-ov-file#extras-extension
-[Hugo Goldmark Extensions: Passthrough]: https://github.com/gohugoio/hugo-goldmark-extensions?tab=readme-ov-file#passthrough-extension
-[image render hook]: /render-hooks/images/
-[includes]: https://docs.asciidoctor.org/asciidoc/latest/syntax-quick-reference/#includes
-[inserted text]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ins
-[mark text]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/mark
-[Markdown attributes]: /content-management/markdown-attributes/
-[mathematics in Markdown]: content-management/mathematics/
-[multilingual page resources]: /content-management/page-resources/#multilingual
-[PHP Markdown Extra: Definition lists]: https://michelf.ca/projects/php-markdown/extra/#def-list
-[PHP Markdown Extra: Footnotes]: https://michelf.ca/projects/php-markdown/extra/#footnotes
-[security policy]: /configuration/security/
-[subscript]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sub
-[superscript]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sup
-[AsciiDoc]: https://asciidoc.org/
-[Emacs Org Mode]: https://orgmode.org/
-[Pandoc]: https://www.pandoc.org/
-[reStructuredText]: https://docutils.sourceforge.io/rst.html
diff --git a/docs/content/en/configuration/media-types.md b/docs/content/en/configuration/media-types.md
deleted file mode 100644
index ea89ee04a..000000000
--- a/docs/content/en/configuration/media-types.md
+++ /dev/null
@@ -1,82 +0,0 @@
----
-title: Configure media types
-linkTitle: Media types
-description: Configure media types.
-categories: []
-keywords: []
----
-
-{{% glossary-term "media type" %}}
-
-Configured media types serve multiple purposes in Hugo, including the definition of [output formats](g). This is the default media type configuration in tabular form:
-
-{{< datatable "config" "mediaTypes" "_key" "suffixes" >}}
-
-The `suffixes` column in the table above shows the suffixes associated with each media type. For example, Hugo associates `.html` and `.htm` files with the `text/html` media type.
-
-> [!note]
-> The first suffix is the primary suffix. Use the primary suffix when naming template files. For example, when creating a template for an RSS feed, use the `xml` suffix.
-
-## Default configuration
-
-The following is the default configuration that matches the table above:
-
-{{< code-toggle file=hugo config=mediaTypes />}}
-
-delimiter
-: (`string`) The delimiter between the file name and the suffix. The delimiter, in conjunction with the suffix, forms the file extension. Default is `"."`.
-
-suffixes
-: (`[]string`) The suffixes associated with this media type. The first suffix is the primary suffix.
-
-## Modify a media type
-
-You can modify any of the default media types. For example, to switch the primary suffix for `text/html` from `html` to `htm`:
-
-{{< code-toggle file=hugo >}}
-[mediaTypes.'text/html']
-suffixes = ['htm','html']
-{{< /code-toggle >}}
-
-If you alter a default media type, you must also explicitly redefine all output formats that utilize that media type. For example, to ensure the changes above affect the `html` output format, redefine the `html` output format:
-
-{{< code-toggle file=hugo >}}
-[outputFormats.html]
-mediaType = 'text/html'
-{{< /code-toggle >}}
-
-## Create a media type
-
-You can create new media types as needed. For example, to create a media type for an Atom feed:
-
-{{< code-toggle file=hugo >}}
-[mediaTypes.'application/atom+xml']
-suffixes = ['atom']
-{{< /code-toggle >}}
-
-## Media types without suffixes
-
-Occasionally, you may need to create a media type without a suffix or delimiter. For example, [Netlify] recognizes configuration files named `_redirects` and `_headers`, which Hugo can generate using custom [output formats](g).
-
-To support these custom output formats, register a custom media type with no suffix or delimiter:
-
-{{< code-toggle file=hugo >}}
-[mediaTypes."text/netlify"]
-delimiter = ""
-{{< /code-toggle >}}
-
-The custom output format definitions would look something like this:
-
-{{< code-toggle file=hugo >}}
-[outputFormats.redir]
-baseName = "_redirects"
-isPlainText = true
-mediatype = "text/netlify"
-[outputFormats.headers]
-baseName = "_headers"
-isPlainText = true
-mediatype = "text/netlify"
-notAlternative = true
-{{< /code-toggle >}}
-
-[Netlify]: https://www.netlify.com/
diff --git a/docs/content/en/configuration/menus.md b/docs/content/en/configuration/menus.md
deleted file mode 100644
index 759f53ff3..000000000
--- a/docs/content/en/configuration/menus.md
+++ /dev/null
@@ -1,135 +0,0 @@
----
-title: Configure menus
-linkTitle: Menus
-description: Centrally define menu entries for one or more menus.
-categories: []
-keywords: []
----
-
-> [!note]
-> To understand Hugo's menu system, please refer to the [menus] page.
-
-There are three ways to define menu entries:
-
-1. [Automatically]
-1. [In front matter]
-1. In site configuration
-
-This page covers the site configuration method.
-
-## Example
-
-To define entries for a "main" menu:
-
-{{< code-toggle file=hugo >}}
-[[menus.main]]
-name = 'Home'
-pageRef = '/'
-weight = 10
-
-[[menus.main]]
-name = 'Products'
-pageRef = '/products'
-weight = 20
-
-[[menus.main]]
-name = 'Services'
-pageRef = '/services'
-weight = 30
-{{< /code-toggle >}}
-
-This creates a menu structure that you can access with [`Menus`] method on a `Site` object:
-
-```go-html-template
-{{ range .Site.Menus.main }}
- ...
-{{ end }}
-```
-
-See [menu templates] for a detailed example.
-
-To define entries for a "footer" menu:
-
-{{< code-toggle file=hugo >}}
-[[menus.footer]]
-name = 'Terms'
-pageRef = '/terms'
-weight = 10
-
-[[menus.footer]]
-name = 'Privacy'
-pageRef = '/privacy'
-weight = 20
-{{< /code-toggle >}}
-
-Access this menu structure in the same way:
-
-```go-html-template
-{{ range .Site.Menus.footer }}
- ...
-{{ end }}
-```
-
-## Properties
-
-Menu entries usually include at least three properties: `name`, `weight`, and either `pageRef` or `url`. Use `pageRef` for internal page destinations and `url` for external destinations.
-
-These are the available menu entry properties:
-
-{{% include "/_common/menu-entry-properties.md" %}}
-
-pageRef
-: (`string`) The [logical path](g) of the target page. For example:
-
- page kind|pageRef
- :--|:--
- home|`/`
- page|`/books/book-1`
- section|`/books`
- taxonomy|`/tags`
- term|`/tags/foo`
-
-url
-: (`string`) The destination URL. Use this for external destinations only.
-
-## Nested menu
-
-This nested menu demonstrates some of the available properties:
-
-{{< code-toggle file=hugo >}}
-[[menus.main]]
-name = 'Products'
-pageRef = '/products'
-weight = 10
-
-[[menus.main]]
-name = 'Hardware'
-pageRef = '/products/hardware'
-parent = 'Products'
-weight = 1
-
-[[menus.main]]
-name = 'Software'
-pageRef = '/products/software'
-parent = 'Products'
-weight = 2
-
-[[menus.main]]
-name = 'Services'
-pageRef = '/services'
-weight = 20
-
-[[menus.main]]
-name = 'Hugo'
-pre = ''
-url = 'https://gohugo.io/'
-weight = 30
-[menus.main.params]
-rel = 'external'
-{{< /code-toggle >}}
-
-[`Menus`]: /methods/site/menus/
-[Automatically]: /content-management/menus/#define-automatically
-[In front matter]: /content-management/menus/#define-in-front-matter
-[menu templates]: /templates/menu/
-[menus]: /content-management/menus/
diff --git a/docs/content/en/configuration/minify.md b/docs/content/en/configuration/minify.md
deleted file mode 100644
index a530cb73d..000000000
--- a/docs/content/en/configuration/minify.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-title: Configure minify
-linkTitle: Minify
-description: Configure minify.
-categories: []
-keywords: []
----
-
-This is the default configuration:
-
-{{< code-toggle config=minify />}}
-
-See the [tdewolff/minify] project page for details.
-
-[tdewolff/minify]: https://github.com/tdewolff/minify
diff --git a/docs/content/en/configuration/module.md b/docs/content/en/configuration/module.md
deleted file mode 100644
index d736b7c6f..000000000
--- a/docs/content/en/configuration/module.md
+++ /dev/null
@@ -1,179 +0,0 @@
----
-title: Configure modules
-linkTitle: Modules
-description: Configure modules.
-categories: []
-keywords: []
-aliases: [/hugo-modules/configuration/]
----
-
-## Top-level options
-
-This is the default configuration:
-
-{{< code-toggle file=hugo >}}
-[module]
-noProxy = 'none'
-noVendor = ''
-private = '*.*'
-proxy = 'direct'
-vendorClosest = false
-workspace = 'off'
-{{< /code-toggle >}}
-
-auth
-: {{< new-in 0.144.0 />}}
-: (`string`) Configures `GOAUTH` when running the Go command for module operations. This is a semicolon-separated list of authentication commands for go-import and HTTPS module mirror interactions. This is useful for private repositories. See `go help goauth` for more information.
-
-noProxy
-: (`string`) A comma-separated list of [glob](g) patterns matching paths that should not use the [configured proxy server](#proxy).
-
-noVendor
-: (`string`) A [glob](g) pattern matching module paths to skip when vendoring.
-
-private
-: (`string`) A comma-separated list of [glob](g) patterns matching paths that should be treated as private.
-
-proxy
-: (`string`) The proxy server to use to download remote modules. Default is `direct`, which means `git clone` and similar.
-
-replacements
-: (`string`) Primarily useful for local module development, a comma-separated list of mappings from module paths to directories. Paths may be absolute or relative to the [`themesDir`].
-
- {{< code-toggle file=hugo >}}
- [module]
- replacements = 'github.com/bep/my-theme -> ../..,github.com/bep/shortcodes -> /some/path'
- {{< /code-toggle >}}
-
-vendorClosest
-: (`bool`) Whether to pick the vendored module closest to the module using it. The default behavior is to pick the first. Note that there can still be only one dependency of a given module path, so once it is in use it cannot be redefined. Default is `false`.
-
-workspace
-: (`string`) The Go workspace file to use, either as an absolute path or a path relative to the current working directory. Enabling this activates Go workspace mode and requires Go 1.18 or later. The default is `off`.
-
-You may also use environment variables to set any of the above. For example:
-
-```sh
-export HUGO_MODULE_PROXY="https://proxy.example.org"
-export HUGO_MODULE_REPLACEMENTS="github.com/bep/my-theme -> ../.."
-export HUGO_MODULE_WORKSPACE="/my/hugo.work"
-```
-
-{{% include "/_common/gomodules-info.md" %}}
-
-## Hugo version
-
-You can specify a required Hugo version for your module in the `module` section. Users will then receive a warning if their Hugo version is incompatible.
-
-This is the default configuration:
-
-{{< code-toggle config=module.hugoVersion />}}
-
-You can omit any of the settings above.
-
-extended
-: (`bool`) Whether the extended edition of Hugo is required, satisfied by installing either the extended or extended/deploy edition.
-
-max
-: (`string`) The maximum Hugo version supported, for example `0.143.0`.
-
-min
-: (`string`) The minimum Hugo version supported, for example `0.123.0`.
-
-[`themesDir`]: /configuration/all/#themesdir
-
-## Imports
-
-{{< code-toggle file=hugo >}}
-[[module.imports]]
-disable = false
-ignoreConfig = false
-ignoreImports = false
-path = "github.com/gohugoio/hugoTestModules1_linux/modh1_2_1v"
-[[module.imports]]
-path = "my-shortcodes"
-{{< /code-toggle >}}
-
-disable
-: (`bool`) Whether to disable the module but keep version information in the `go.*` files. Default is `false`.
-
-ignoreConfig
-: (`bool`) Whether to ignore module configuration files, for example, `hugo.toml`. This will also prevent loading of any transitive module dependencies. Default is `false`.
-
-ignoreImports
-: (`bool`) Whether to ignore module imports. Default is `false`.
-
-noMounts
-: (`bool`) Whether to disable directory mounting for this import. Default is `false`.
-
-noVendor
-: (`bool`) Whether to disable vendoring for this import. This setting is restricted to the main project. Default is `false`.
-
-path
-: (`string`) The module path, either a valid Go module path (e.g., `github.com/gohugoio/myShortcodes`) or the directory name if stored in the [`themesDir`].
-
-[`themesDir`]: /configuration/all#themesDir
-
-{{% include "/_common/gomodules-info.md" %}}
-
-## Mounts
-
-Before Hugo v0.56.0, custom component paths could only be configured by setting [`archetypeDir`], [`assetDir`], [`contentDir`], [`dataDir`], [`i18nDir`], [`layoutDi`], or [`staticDir`] in the site configuration. Module mounts offer greater flexibility than these legacy settings, but
-you cannot use both.
-
-[`archetypeDir`]: /configuration/all/
-[`assetDir`]: /configuration/all/
-[`contentDir`]: /configuration/all/
-[`dataDir`]: /configuration/all/
-[`i18nDir`]: /configuration/all/
-[`layoutDi`]: /configuration/all/
-[`staticDir`]: /configuration/all/
-
-> [!note]
-> If you use module mounts do not use the legacy settings.
-
-### Default mounts
-
-> [!note]
-> Adding a new mount to a target root will cause the existing default mount for that root to be ignored. If you still need the default mount, you must explicitly add it along with the new mount.
-
-The are the default mounts:
-
-{{< code-toggle config=module.mounts />}}
-
-source
-: (`string`) The source directory of the mount. For the main project, this can be either project-relative or absolute. For other modules it must be project-relative.
-
-target
-: (`string`) Where the mount will reside within Hugo's virtual file system. It must begin with one of Hugo's component directories: `archetypes`, `assets`, `content`, `data`, `i18n`, `layouts`, or `static`. For example, `content/blog`.
-
-disableWatch
-: {{< new-in 0.128.0 />}}
-: (`bool`) Whether to disable watching in watch mode for this mount. Default is `false`.
-
-lang
-: (`string`) The language code, e.g. "en". Relevant for `content` mounts, and `static` mounts when in multihost mode.
-
-includeFiles
-: (`string` or `[]string`) One or more [glob](g) patterns matching files or directories to include. If `excludeFiles` is not set, the files matching `includeFiles` will be the files mounted.
-
- The glob patterns are matched against file names relative to the source root. Use Unix-style forward slashes (`/`), even on Windows. A single forward slash (`/`) matches the mount root, and double asterisks (`**`) act as a recursive wildcard, matching all directories and files beneath a given point (e.g., `/posts/**.jpg`). The search is case-insensitive.
-
-excludeFiles
-: (`string` or `[]string`) One or more [glob](g) patterns matching files to exclude.
-
-### Example
-
-{{< code-toggle file=hugo >}}
-[module]
-[[module.mounts]]
- source="content"
- target="content"
- excludeFiles="docs/*"
-[[module.mounts]]
- source="node_modules"
- target="assets"
-[[module.mounts]]
- source="assets"
- target="assets"
-{{< /code-toggle >}}
diff --git a/docs/content/en/configuration/output-formats.md b/docs/content/en/configuration/output-formats.md
deleted file mode 100644
index 2627c6df4..000000000
--- a/docs/content/en/configuration/output-formats.md
+++ /dev/null
@@ -1,209 +0,0 @@
----
-title: Configure output formats
-linkTitle: Output formats
-description: Configure output formats.
-categories: []
-keywords: []
----
-
-{{% glossary-term "output format" %}}
-
-You can output a page in as many formats as you want. Define an infinite number of output formats, provided they each resolve to a unique file system path.
-
-This is the default output format configuration in tabular form:
-
-{{< datatable
- "config"
- "outputFormats"
- "_key"
- "mediaType"
- "weight"
- "baseName"
- "isHTML"
- "isPlainText"
- "noUgly"
- "notAlternative"
- "path"
- "permalinkable"
- "protocol"
- "rel"
- "root"
- "ugly"
->}}
-
-## Default configuration
-
-The following is the default configuration that matches the table above:
-
-{{< code-toggle config=outputFormats />}}
-
-baseName
-: (`string`) The base name of the published file. Default is `index`.
-
-isHTML
-: (`bool`) Whether to classify the output format as HTML. Hugo uses this value to determine when to create alias redirects and when to inject the LiveReload script. Default is `false`.
-
-isPlainText
-: (`bool`) Whether to parse templates for this output format with Go's [text/template] package instead of the [html/template] package. Default is `false`.
-
-mediaType
-: (`string`) The [media type](g) of the published file. This must match one of the [configured media types].
-
-notAlternative
-: (`bool`) Whether to exclude this output format from the values returned by the [`AlternativeOutputFormats`] method on a `Page` object. Default is `false`.
-
-noUgly
-: (`bool`) Whether to disable ugly URLs for this output format when [`uglyURLs`] are enabled in your site configuration. Default is `false`.
-
-path
-: (`string`) The published file's directory path, relative to the root of the publish directory. If not specified, the file will be published using its content path.
-
-permalinkable
-: (`bool`) Whether to return the rendering output format rather than main output format when invoking the [`Permalink`] and [`RelPermalink`] methods on a `Page` object. See [details](#link-to-output-formats). Enabled by default for the `html` and `amp` output formats. Default is `false`.
-
-protocol
-: (`string`) The protocol (scheme) of the URL for this output format. For example, `https://` or `webcal://`. Default is the scheme of the [`baseURL`] parameter in your site configuration, typically `https://`.
-
-rel
-: (`string`) If provided, you can assign this value to `rel` attributes in `link` elements when iterating over output formats in your templates. Default is `alternate`.
-
-root
-: (`bool`) Whether to publish files to the root of the publish directory. Default is `false`.
-
-ugly
-: (`bool`) Whether to enable uglyURLs for this output format when `uglyURLs` is `false` in your site configuration. Default is `false`.
-
-weight
-: (`int`) When set to a non-zero value, Hugo uses the `weight` as the first criteria when sorting output formats, falling back to the name of the output format. Lighter items float to the top, while heavier items sink to the bottom. Hugo renders output formats sequentially based on the sort order. Default is `0`, except for the `html` output format, which has a default weight of `10`.
-
-## Modify an output format
-
-You can modify any of the default output formats. For example, to prioritize `json` rendering over `html` rendering, when both are generated, adjust the [`weight`](#weight):
-
-{{< code-toggle file=hugo >}}
-[outputFormats.json]
-weight = 1
-[outputFormats.html]
-weight = 2
-{{< /code-toggle >}}
-
-The example above shows that when you modify a default content format, you only need to define the properties that differ from their default values.
-
-## Create an output format
-
-You can create new output formats as needed. For example, you may wish to create an output format to support Atom feeds.
-
-### Step 1
-
-Output formats require a specified media type. Because Atom feeds use `application/atom+xml`, which is not one of the [default media types], you must create it first.
-
-{{< code-toggle file=hugo >}}
-[mediaTypes.'application/atom+xml']
-suffixes = ['atom']
-{{< /code-toggle >}}
-
-See [configure media types] for more information.
-
-### Step 2
-
-Create a new output format:
-
-{{< code-toggle file=hugo >}}
-[outputFormats.atom]
-mediaType = 'application/atom+xml'
-noUgly = true
-{{< /code-toggle >}}
-
-Note that we use the default settings for all other output format properties.
-
-### Step 3
-
-Specify the page [kinds](g) for which to render this output format:
-
-{{< code-toggle file=hugo >}}
-[outputs]
-home = ['html', 'rss', 'atom']
-section = ['html', 'rss', 'atom']
-taxonomy = ['html', 'rss', 'atom']
-term = ['html', 'rss', 'atom']
-{{< /code-toggle >}}
-
-See [configure outputs] for more information.
-
-### Step 4
-
-Create a template to render the output format. Since Atom feeds are lists, you need to create a list template. Consult the [template lookup order] to find the correct template path:
-
-```text
-layouts/_default/list.atom.atom
-```
-
-We leave writing the template code as an exercise for you. Aim for a result similar to the [embedded RSS template].
-
-## List output formats
-
-To access output formats, each `Page` object provides two methods: [`OutputFormats`] (for all formats, including the current one) and [`AlternativeOutputFormats`]. Use `AlternativeOutputFormats` to create a link `rel` list within your site's `head` element, as shown below:
-
-```go-html-template
-{{ range .AlternativeOutputFormats }}
-
-{{ end }}
-```
-
-## Link to output formats
-
-By default, a `Page` object's [`Permalink`] and [`RelPermalink`] methods return the URL of the [primary output format](g), typically `html`. This behavior remains consistent regardless of the template used.
-
-For example, in `single.json.json`, you'll see:
-
-```go-html-template
-{{ .RelPermalink }} → /that-page/
-{{ with .OutputFormats.Get "json" }}
- {{ .RelPermalink }} → /that-page/index.json
-{{ end }}
-```
-
-To make these methods return the URL of the _current_ template's output format, you must set the [`permalinkable`] setting to `true` for that format.
-
-With `permalinkable` set to true for `json` in the same `single.json.json` template:
-
-```go-html-template
-{{ .RelPermalink }} → /that-page/index.json
-{{ with .OutputFormats.Get "html" }}
- {{ .RelPermalink }} → /that-page/
-{{ end }}
-```
-
-## Template lookup order
-
-Each output format requires a template conforming to the [template lookup order].
-
-For the highest specificity in the template lookup order, include the page kind, output format, and suffix in the file name:
-
-```text
-[page kind].[output format].[suffix]
-```
-
-For example, for section pages:
-
-Output format|Template path
-:--|:--
-`html`|`layouts/_default/section.html.html`
-`json`|`layouts/_default/section.json.json`
-`rss`|`layouts/_default/section.rss.xml`
-
-[`AlternativeOutputFormats`]: /methods/page/alternativeoutputformats/
-[`OutputFormats`]: /methods/page/outputformats/
-[`Permalink`]: /methods/page/permalink/
-[`RelPermalink`]: /methods/page/relpermalink/
-[`baseURL`]: /configuration/all/#baseurl
-[`permalinkable`]: #permalinkable
-[`uglyURLs`]: /configuration/ugly-urls/
-[configure media types]: /configuration/media-types/
-[configure outputs]: /configuration/outputs/
-[configured media types]: /configuration/media-types/
-[default media types]: /configuration/media-types/
-[embedded RSS template]: {{% eturl rss %}}
-[html/template]: https://pkg.go.dev/html/template
-[template lookup order]: /templates/lookup-order/
-[text/template]: https://pkg.go.dev/text/template
diff --git a/docs/content/en/configuration/outputs.md b/docs/content/en/configuration/outputs.md
deleted file mode 100644
index 9a83cb6e9..000000000
--- a/docs/content/en/configuration/outputs.md
+++ /dev/null
@@ -1,49 +0,0 @@
----
-title: Configure outputs
-linkTitle: Outputs
-description: Configure which output formats to render for each page kind.
-categories: []
-keywords: []
----
-
-{{% glossary-term "output format" %}}
-
-Learn more about creating and configuring output formats in the [configure output formats] section.
-
-## Outputs per page kind
-
-The following default configuration determines the output formats generated for each page kind:
-
-{{< code-toggle config=outputs />}}
-
-To render the built-in `json` output format for the `home` page kind, assuming you've already created the necessary template, add the following to your configuration:
-
-{{< code-toggle file=hugo >}}
-[outputs]
-home = ['html','rss','json']
-{{< /code-toggle >}}
-
-Notice in this example that we only specified the `home` page kind. You don't need to include entries for other page kinds unless you intend to modify their default output formats.
-
-> [!note]
-> The order of the output formats in the arrays above is important. The first element will be the _primary output format_ for that page kind, and in most cases that should be `html` as shown in the default configuration.
->
-> The primary output format for a given page kind determines the value returned by the [`Permalink`] and [`RelPermalink`] methods on a `Page` object.
->
-> See the [link to output formats] section for details.
-
-## Outputs per page
-
-Add output formats to a page's rendering using the `outputs` field in its front matter. For example, to include `json` in the output formats rendered for a specific page:
-
-{{< code-toggle file=content/example.md fm=true >}}
-title = 'Example'
-outputs = ['json']
-{{< /code-toggle >}}
-
-In its default configuration, Hugo will render both the `html` and `json` output formats for this page. The `outputs` field appends to, rather than replaces, the site's configured outputs.
-
-[`Permalink`]: /methods/page/permalink/
-[`RelPermalink`]: /methods/page/relpermalink/
-[configure output formats]: /configuration/output-formats/
-[link to output formats]: configuration/output-formats/#link-to-output-formats
diff --git a/docs/content/en/configuration/page.md b/docs/content/en/configuration/page.md
deleted file mode 100644
index 81169e546..000000000
--- a/docs/content/en/configuration/page.md
+++ /dev/null
@@ -1,34 +0,0 @@
----
-title: Configure page
-linkTitle: Page
-description: Configure page behavior.
-categories: []
-keywords: []
----
-
-{{< new-in 0.133.0 />}}
-
-{{% glossary-term "default sort order" %}}
-
-Hugo uses the default sort order to determine the _next_ and _previous_ page relative to the current page when calling these methods on a `Page` object:
-
-- [`Next`](/methods/page/next/) and [`Prev`](/methods/page/prev/)
-- [`NextInSection`](/methods/page/nextinsection/) and [`PrevInSection`](/methods/page/previnsection/)
-
-This is based on this default site configuration:
-
-{{< code-toggle config=page />}}
-
-To reverse the meaning of _next_ and _previous_:
-
-{{< code-toggle file=hugo >}}
-[page]
- nextPrevInSectionSortOrder = 'asc'
- nextPrevSortOrder = 'asc'
-{{< /code-toggle >}}
-
-> [!note]
-> These settings do not apply to the [`Next`] or [`Prev`] methods on a `Pages` object.
-
-[`Next`]: /methods/pages/next
-[`Prev`]: /methods/pages/next
diff --git a/docs/content/en/configuration/pagination.md b/docs/content/en/configuration/pagination.md
deleted file mode 100644
index 66b3b8cf4..000000000
--- a/docs/content/en/configuration/pagination.md
+++ /dev/null
@@ -1,45 +0,0 @@
----
-title: Configure pagination
-linkTitle: Pagination
-description: Configure pagination.
-categories: []
-keywords: []
----
-
-This is the default configuration:
-
-{{< code-toggle config=pagination />}}
-
-disableAliases
-: (`bool`) Whether to disable alias generation for the first pager. Default is `false`.
-
-pagerSize
-: (`int`) The number of pages per pager. Default is `10`.
-
-path
-: (`string`) The segment of each pager URL indicating that the target page is a pager. Default is `page`.
-
-With multilingual sites you can define the pagination behavior for each language:
-
-{{< code-toggle file=hugo >}}
-[languages.en]
-contentDir = 'content/en'
-languageCode = 'en-US'
-languageDirection = 'ltr'
-languageName = 'English'
-weight = 1
-[languages.en.pagination]
-disableAliases = true
-pagerSize = 10
-path = 'page'
-[languages.de]
-contentDir = 'content/de'
-languageCode = 'de-DE'
-languageDirection = 'ltr'
-languageName = 'Deutsch'
-weight = 2
-[languages.de.pagination]
-disableAliases = true
-pagerSize = 20
-path = 'blatt'
-{{< /code-toggle >}}
diff --git a/docs/content/en/configuration/params.md b/docs/content/en/configuration/params.md
deleted file mode 100644
index 239b0c2da..000000000
--- a/docs/content/en/configuration/params.md
+++ /dev/null
@@ -1,100 +0,0 @@
----
-title: Configure params
-linkTitle: Params
-description: Create custom site parameters.
-categories: []
-keywords: []
----
-
-Use the `params` key for custom parameters:
-
-{{< code-toggle file=hugo >}}
-baseURL = 'https://example.org/'
-title = 'Project Documentation'
-languageCode = 'en-US'
-[params]
-subtitle = 'Reference, Tutorials, and Explanations'
-[params.contact]
-email = 'info@example.org'
-phone = '+1 206-555-1212'
-{{< /code-toggle >}}
-
-Access the custom parameters from your templates using the [`Params`] method on a `Site` object:
-
-[`Params`]: /methods/site/params/
-
-```go-html-template
-{{ .Site.Params.subtitle }} → Reference, Tutorials, and Explanations
-{{ .Site.Params.contact.email }} → info@example.org
-```
-
-Key names should use camelCase or snake_case. While TOML, YAML, and JSON allow kebab-case keys, they are not valid [identifiers](g) and cannot be used when [chaining](g) identifiers.
-
-For example, you can do either of these:
-
-```go-html-template
-{{ .Site.params.camelCase.foo }}
-{{ .Site.params.snake_case.foo }}
-```
-
-But you cannot do this:
-
-```go-html-template
-{{ .Site.params.kebab-case.foo }}
-```
-
-## Multilingual sites
-
-For multilingual sites, create a `params` key under each language:
-
-{{< code-toggle file=hugo >}}
-baseURL = 'https://example.org/'
-defaultContentLanguage = 'en'
-
-[languages.de]
-languageCode = 'de-DE'
-languageDirection = 'ltr'
-languageName = 'Deutsch'
-title = 'Projekt Dokumentation'
-weight = 1
-
-[languages.de.params]
-subtitle = 'Referenz, Tutorials und Erklärungen'
-
-[languages.de.params.contact]
-email = 'info@de.example.org'
-phone = '+49 30 1234567'
-
-[languages.en]
-languageCode = 'en-US'
-languageDirection = 'ltr'
-languageName = 'English'
-title = 'Project Documentation'
-weight = 2
-
-[languages.en.params]
-subtitle = 'Reference, Tutorials, and Explanations'
-
-[languages.en.params.contact]
-email = 'info@example.org'
-phone = '+1 206-555-1212'
-{{< /code-toggle >}}
-
-## Namespacing
-
-To prevent naming conflicts, module and theme developers should namespace any custom parameters specific to their module or theme.
-
-{{< code-toggle file=hugo >}}
-[params.modules.myModule.colors]
-background = '#efefef'
-font = '#222222'
-{{< /code-toggle >}}
-
-To access the module/theme settings:
-
-```go-html-template
-{{ $cfg := .Site.Params.module.mymodule }}
-
-{{ $cfg.colors.background }} → #efefef
-{{ $cfg.colors.font }} → #222222
-```
diff --git a/docs/content/en/configuration/permalinks.md b/docs/content/en/configuration/permalinks.md
deleted file mode 100644
index 0810624a6..000000000
--- a/docs/content/en/configuration/permalinks.md
+++ /dev/null
@@ -1,162 +0,0 @@
----
-title: Configure permalinks
-linkTitle: Permalinks
-description: Configure permalinks.
-categories: []
-keywords: []
----
-
-This is the default configuration:
-
-{{< code-toggle config=permalinks />}}
-
-Define a URL pattern for each top-level section. Each URL pattern can target a given language and/or page kind.
-
-> [!note]
-> The [`url`] front matter field overrides any matching permalink pattern.
-
-## Monolingual example
-
-With this content structure:
-
-```text
-content/
-├── posts/
-│ ├── bash-in-slow-motion.md
-│ └── tls-in-a-nutshell.md
-├── tutorials/
-│ ├── git-for-beginners.md
-│ └── javascript-bundling-with-hugo.md
-└── _index.md
-```
-
-Render tutorials under "training", and render the posts under "articles" with a date-base hierarchy:
-
-{{< code-toggle file=hugo >}}
-[permalinks.page]
-posts = '/articles/:year/:month/:slug/'
-tutorials = '/training/:slug/'
-[permalinks.section]
-posts = '/articles/'
-tutorials = '/training/'
-{{< /code-toggle >}}
-
-The structure of the published site will be:
-
-```text
-public/
-├── articles/
-│ ├── 2023/
-│ │ ├── 04/
-│ │ │ └── bash-in-slow-motion/
-│ │ │ └── index.html
-│ │ └── 06/
-│ │ └── tls-in-a-nutshell/
-│ │ └── index.html
-│ └── index.html
-├── training/
-│ ├── git-for-beginners/
-│ │ └── index.html
-│ ├── javascript-bundling-with-hugo/
-│ │ └── index.html
-│ └── index.html
-└── index.html
-```
-
-To create a date-based hierarchy for regular pages in the content root:
-
-{{< code-toggle file=hugo >}}
-[permalinks.page]
-"/" = "/:year/:month/:slug/"
-{{< /code-toggle >}}
-
-Use the same approach with taxonomy terms. For example, to omit the taxonomy segment of the URL:
-
-{{< code-toggle file=hugo >}}
-[permalinks.term]
-'tags' = '/:slug/'
-{{< /code-toggle >}}
-
-## Multilingual example
-
-Use the `permalinks` configuration as a component of your localization strategy.
-
-With this content structure:
-
-```text
-content/
-├── en/
-│ ├── books/
-│ │ ├── les-miserables.md
-│ │ └── the-hunchback-of-notre-dame.md
-│ └── _index.md
-└── es/
- ├── books/
- │ ├── les-miserables.md
- │ └── the-hunchback-of-notre-dame.md
- └── _index.md
-```
-
-And this site configuration:
-
-{{< code-toggle file=hugo >}}
-defaultContentLanguage = 'en'
-defaultContentLanguageInSubdir = true
-
-[languages.en]
-contentDir = 'content/en'
-languageCode = 'en-US'
-languageDirection = 'ltr'
-languageName = 'English'
-weight = 1
-
-[languages.en.permalinks.page]
-books = "/books/:slug/"
-
-[languages.en.permalinks.section]
-books = "/books/"
-
-[languages.es]
-contentDir = 'content/es'
-languageCode = 'es-ES'
-languageDirection = 'ltr'
-languageName = 'Español'
-weight = 2
-
-[languages.es.permalinks.page]
-books = "/libros/:slug/"
-
-[languages.es.permalinks.section]
-books = "/libros/"
-{{< /code-toggle >}}
-
-The structure of the published site will be:
-
-```text
-public/
-├── en/
-│ ├── books/
-│ │ ├── les-miserables/
-│ │ │ └── index.html
-│ │ ├── the-hunchback-of-notre-dame/
-│ │ │ └── index.html
-│ │ └── index.html
-│ └── index.html
-├── es/
-│ ├── libros/
-│ │ ├── les-miserables/
-│ │ │ └── index.html
-│ │ ├── the-hunchback-of-notre-dame/
-│ │ │ └── index.html
-│ │ └── index.html
-│ └── index.html
-└── index.html
-```
-
-## Tokens
-
-Use these tokens when defining a URL pattern.
-
-{{% include "/_common/permalink-tokens.md" %}}
-
-[`url`]: /content-management/front-matter/#url
diff --git a/docs/content/en/configuration/privacy.md b/docs/content/en/configuration/privacy.md
deleted file mode 100644
index c94f2c1c3..000000000
--- a/docs/content/en/configuration/privacy.md
+++ /dev/null
@@ -1,43 +0,0 @@
----
-title: Configure privacy
-linkTitle: Privacy
-description: Configure your site to help comply with regional privacy regulations.
-categories: []
-keywords: []
-aliases: [/about/privacy/]
----
-
-## Responsibility
-
-Site authors are responsible for ensuring compliance with regional privacy regulations, including but not limited to:
-
-- GDPR (General Data Protection Regulation): Applies to individuals within the European Union and the European Economic Area.
-- CCPA (California Consumer Privacy Act): Applies to California residents.
-- CPRA (California Privacy Rights Act): Expands upon the CCPA with stronger consumer privacy protections.
-- Virginia Consumer Data Protection Act (CDPA): Applies to businesses that collect, process, or sell the personal data of Virginia residents.
-
-Hugo's privacy settings can assist in compliance efforts.
-
-## Embedded templates
-
-Hugo provides [embedded templates](g) to simplify site and content creation. Some of these templates interact with external services. For example, the `youtube` shortcode connects with YouTube's servers to embed videos on your site.
-
-Some of these templates include settings to enhance privacy.
-
-## Configuration
-
-> [!note]
-> These settings affect the behavior of some of Hugo's embedded templates. These settings may or may not affect the behavior of templates provided by third parties in their modules or themes.
-
-These are the default privacy settings for Hugo's embedded templates:
-
-{{< code-toggle config=privacy />}}
-
-See each template's documentation for a description of its privacy settings:
-
-- [Disqus partial](/templates/embedded/#privacy-disqus)
-- [Google Analytics partial](/templates/embedded/#privacy-google-analytics)
-- [Instagram shortcode](/shortcodes/instagram/#privacy)
-- [Vimeo shortcode](/shortcodes/vimeo/#privacy)
-- [X shortcode](/shortcodes/x/#privacy)
-- [YouTube shortcode](/shortcodes/youtube/#privacy)
diff --git a/docs/content/en/configuration/related-content.md b/docs/content/en/configuration/related-content.md
deleted file mode 100644
index c6e182fae..000000000
--- a/docs/content/en/configuration/related-content.md
+++ /dev/null
@@ -1,111 +0,0 @@
----
-title: Configure related content
-linkTitle: Related content
-description: Configure related content.
-categories: []
-keywords: []
----
-
-> [!note]
-> To understand Hugo's related content identification, please refer to the [related content] page.
-
-Hugo provides a sensible default configuration for identifying related content, but you can customize it in your site configuration, either globally or per language.
-
-## Default configuration
-
-This is the default configuration:
-
-{{< code-toggle config=related />}}
-
-> [!note]
-> Adding a `related` section to your site configuration requires you to provide a full configuration. You cannot override individual default values without specifying all related settings.
-
-## Top-level options
-
-threshold
-: (`int`) A value between 0-100, inclusive. A lower value will return more, but maybe not so relevant, matches.
-
-includeNewer
-: (`bool`) Whether 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. Default is `false`.
-
-toLower
-: (`bool`) Whether to transform keywords in both the indexes and the queries to lower case. This may give more accurate results at a slight performance penalty. Default is `false`.
-
-## Per-index options
-
-name
-: (`string`) The index name. This value maps directly to a page parameter. Hugo supports string values (`author` in the example) and lists (`tags`, `keywords` etc.) and time and date objects.
-
-type
-: (`string`) One of `basic` or `fragments`. Default is `basic`.
-
-applyFilter
-: (`string`) Apply a `type` specific filter to the result of a search. This is currently only used for the `fragments` type.
-
-weight
-: (`int`) 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. Default is `0`.
-
-cardinalityThreshold
-: (`int`) If between 1 and 100, this is a percentage. All keywords that are used in more than this percentage of documents are removed. For example, setting this to `60` will remove all keywords that are used in more than 60% of the documents in the index. If `0`, no keyword is removed from the index. Default is `0`.
-
-pattern
-: (`string`) 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
-: (`bool`) Whether to transform keywords in both the indexes and the queries to lower case. This may give more accurate results at a slight performance penalty. Default is `false`.
-
-## Example
-
-Imagine we're building a book review site. Our main content will be book reviews, and we'll use genres and authors as taxonomies. When someone views a book review, we want to show a short list of related reviews based on shared authors and genres.
-
-Create the content:
-
-```text
-content/
-└── book-reviews/
- ├── book-review-1.md
- ├── book-review-2.md
- ├── book-review-3.md
- ├── book-review-4.md
- └── book-review-5.md
-```
-
-Configure the taxonomies:
-
-{{< code-toggle file=hugo >}}
-[taxonomies]
-author = 'authors'
-genre = 'genres'
-{{< /code-toggle >}}
-
-Configure the related content identification:
-
-{{< code-toggle file=hugo >}}
-[related]
-includeNewer = true
-threshold = 80
-toLower = true
-[[related.indices]]
-name = 'authors'
-weight = 2
-[[related.indices]]
-name = 'genres'
-weight = 1
-{{< /code-toggle >}}
-
-We've configured the `authors` index with a weight of `2` and the `genres` index with a weight of `1`. This means Hugo prioritizes shared `authors` as twice as significant as shared `genres`.
-
-Then render a list of 5 related reviews with a partial template like this:
-
-```go-html-template {file="layouts/partials/related.html" copy=true}
-{{ with site.RegularPages.Related . | first 5 }}
-
-{{ end }}
-```
-
-[related content]: /content-management/related-content/
diff --git a/docs/content/en/configuration/security.md b/docs/content/en/configuration/security.md
deleted file mode 100644
index f950dd233..000000000
--- a/docs/content/en/configuration/security.md
+++ /dev/null
@@ -1,50 +0,0 @@
----
-title: Configure security
-linkTitle: Security
-description: Configure security.
-categories: []
-keywords: []
----
-
-Hugo's built-in security policy, which restricts access to `os/exec`, remote communication, and similar operations, is configured via allow lists. By default, access is restricted. If a build attempts to use a feature not included in the allow list, it will fail, providing a detailed message.
-
-This is the default security configuration:
-
-{{< code-toggle config=security />}}
-
-enableInlineShortcodes
-: (`bool`) Whether to enable [inline shortcodes]. Default is `false`.
-
-exec.allow
-: (`[]string`) A slice of [regular expressions](g) matching the names of external executables that Hugo is allowed to run.
-
-exec.osEnv
-: (`[]string`) A slice of [regular expressions](g) matching the names of operating system environment variables that Hugo is allowed to access.
-
-funcs.getenv
-: (`[]string`) A slice of [regular expressions](g) matching the names of operating system environment variables that Hugo is allowed to access with the [`os.Getenv`] function.
-
-http.methods
-: (`[]string`) A slice of [regular expressions](g) matching the HTTP methods that the [`resources.GetRemote`] function is allowed to use.
-
-http.mediaTypes
-: (`[]string`) Applicable to the `resources.GetRemote` function, a slice of [regular expressions](g) matching the `Content-Type` in HTTP responses that Hugo trusts, bypassing file content analysis for media type detection.
-
-http.urls
-: (`[]string`) A slice of [regular expressions](g) matching the URLs that the `resources.GetRemote` function is allowed to access.
-
-> [!note]
-> Setting an allow list to the string `none` will completely disable the associated feature.
-
-You can also override the site configuration with environment variables. For example, to block `resources.GetRemote` from accessing any URL:
-
-```txt
-export HUGO_SECURITY_HTTP_URLS=none
-```
-
-Learn more about [using environment variables] to configure your site.
-
-[`os.Getenv`]: /functions/os/getenv
-[`resources.GetRemote`]: /functions/resources/getremote
-[inline shortcodes]: /content-management/shortcodes/#inline
-[using environment variables]: /configuration/introduction/#environment-variables
diff --git a/docs/content/en/configuration/segments.md b/docs/content/en/configuration/segments.md
deleted file mode 100644
index 0c4098770..000000000
--- a/docs/content/en/configuration/segments.md
+++ /dev/null
@@ -1,77 +0,0 @@
----
-title: Configure segments
-linkTitle: Segments
-description: Configure your site for segmented rendering.
-categories: []
-keywords: []
----
-
-{{< new-in 0.124.0 />}}
-
-> [!note]
-> The `segments` configuration applies only to segmented rendering. While it controls when content is rendered, it doesn't restrict access to Hugo's complete object graph (sites and pages), which remains fully available.
-
-Segmented rendering offers several advantages:
-
-- Faster builds: Process large sites more efficiently.
-- Rapid development: Render only a subset of your site for quicker iteration.
-- Scheduled rebuilds: Rebuild specific sections at different frequencies (e.g., home page and news hourly, full site weekly).
-- Targeted output: Generate specific output formats (like JSON for search indexes).
-
-## Segment definition
-
-Each segment is defined by include and exclude filters:
-
-- Filters: Each segment has zero or more exclude filters and zero or more include filters.
-- Matchers: Each filter contains one or more field [glob](g) matchers.
-- Logic: Matchers within a filter use AND logic. Filters within a section (include or exclude) use OR logic.
-
-## Filter fields
-
-Available fields for filtering:
-
-kind
-: (`string`) A [glob](g) pattern matching the [page kind](g). For example: ` {taxonomy,term}`.
-
-lang
-: (`string`) A [glob](g) pattern matching the [page language]. For example: `{en,de}`.
-
-output
-: (`string`) A [glob](g) pattern matching the [output format](g) of the page. For example: `{html,json}`.
-
-path
-: (`string`) A [glob](g) pattern matching the page's [logical path](g). For example: `{/books,/books/**}`.
-
-## Example
-
-Place broad filters, such as those for language or output format, in the excludes section. For example:
-
-{{< code-toggle file=hugo >}}
-[segments.segment1]
- [[segments.segment1.excludes]]
- lang = "n*"
- [[segments.segment1.excludes]]
- lang = "en"
- output = "rss"
- [[segments.segment1.includes]]
- kind = "{home,term,taxonomy}"
- [[segments.segment1.includes]]
- path = "{/docs,/docs/**}"
-{{< /code-toggle >}}
-
-## Rendering segments
-
-Render specific segments using the [`renderSegments`] configuration or the `--renderSegments` flag:
-
-```bash
-hugo --renderSegments segment1
-```
-
-You can configure multiple segments and use a comma-separated list with `--renderSegments` to render them all.
-
-```bash
-hugo --renderSegments segment1,segment2
-```
-
-[`renderSegments`]: /configuration/all/#rendersegments
-[page language]: /methods/page/language/
diff --git a/docs/content/en/configuration/server.md b/docs/content/en/configuration/server.md
deleted file mode 100644
index 92f0f0cfa..000000000
--- a/docs/content/en/configuration/server.md
+++ /dev/null
@@ -1,128 +0,0 @@
----
-title: Configure server
-linkTitle: Server
-description: Configure the development server.
-categories: []
-keywords: []
----
-
-These settings are exclusive to Hugo's development server, so a dedicated [configuration directory] for development, where the server is configured accordingly, is the recommended approach.
-
-[configuration directory]: /configuration/introduction/#configuration-directory
-
-```text
-project/
-└── config/
- ├── _default/
- │ └── hugo.toml
- └── development/
- └── server.toml
-```
-
-## Default settings
-
-The development server defaults to redirecting to `/404.html` for any requests to URLs that don't exist. See the [404 errors](#404-errors) section below for details.
-
-{{< code-toggle config=server />}}
-
-force
-: (`bool`) Whether to force a redirect even if there is existing content in the path.
-
-from
-: (`string`) A [glob](g) pattern matching the requested URL. Either `from` or `fromRE` must be set. If both `from` and `fromRe` are specified, the URL must match both patterns.
-
-fromHeaders
-: {{< new-in 0.144.0 />}}
-: (`map[string][string]`) Headers to match for the redirect. This maps the HTTP header name to a [glob](g) pattern with values to match. If the map is empty, the redirect will always be triggered.
-
-fromRe
-: {{< new-in 0.144.0 />}}
-: (`string`) A [regular expression](g) used to match the requested URL. Either `from` or `fromRE` must be set. If both `from` and `fromRe` are specified, the URL must match both patterns. Capture groups from the regular expression are accessible in the `to` field as `$1`, `$2`, and so on.
-
-status
-: (`string`) The HTTP status code to use for the redirect. A status code of 200 will trigger a URL rewrite.
-
-to
-: (`string`) The URL to forward the request to.
-
-## Headers
-
-Include headers in every server response to facilitate testing, particularly for features like Content Security Policies.
-
-[Content Security Policies]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
-
-{{< code-toggle file=config/development/server >}}
-[[headers]]
-for = "/**"
-
-[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 >}}
-
-## Redirects
-
-You can define simple redirect rules.
-
-{{< code-toggle file=config/development/server >}}
-[[redirects]]
-from = "/myspa/**"
-to = "/myspa/"
-status = 200
-force = false
-{{< /code-toggle >}}
-
-The `200` status code in this example triggers a URL rewrite, which is typically the desired behavior for [single-page applications].
-
-[single-page applications]: https://en.wikipedia.org/wiki/Single-page_application
-
-## 404 errors
-
-The development server defaults to redirecting to /404.html for any requests to URLs that don't exist.
-
-{{< code-toggle config=server />}}
-
-If you've already defined other redirects, you must explicitly add the 404 redirect.
-
-{{< code-toggle file=config/development/server >}}
-[[redirects]]
-force = false
-from = "/**"
-to = "/404.html"
-status = 404
-{{< /code-toggle >}}
-
-For multilingual sites, ensure the default language 404 redirect is defined last:
-
-{{< code-toggle file=config/development/server >}}
-defaultContentLanguage = 'en'
-defaultContentLanguageInSubdir = false
-[[redirects]]
-from = '/fr/**'
-to = '/fr/404.html'
-status = 404
-
-[[redirects]] # Default language must be last.
-from = '/**'
-to = '/404.html'
-status = 404
-{{< /code-toggle >}}
-
-When the default language is served from a subdirectory:
-
-{{< code-toggle file=config/development/server >}}
-defaultContentLanguage = 'en'
-defaultContentLanguageInSubdir = true
-[[redirects]]
-from = '/fr/**'
-to = '/fr/404.html'
-status = 404
-
-[[redirects]] # Default language must be last.
-from = '/**'
-to = '/en/404.html'
-status = 404
-{{< /code-toggle >}}
diff --git a/docs/content/en/configuration/services.md b/docs/content/en/configuration/services.md
deleted file mode 100644
index dbe3893a7..000000000
--- a/docs/content/en/configuration/services.md
+++ /dev/null
@@ -1,52 +0,0 @@
----
-title: Configure services
-linkTitle: Services
-description: Configure embedded templates.
-categories: []
-keywords: []
----
-
-Hugo provides [embedded templates](g) to simplify site and content creation. Some of these templates are configurable. For example, the embedded Google Analytics template requires a Google tag ID.
-
-This is the default configuration:
-
-{{< code-toggle config=services />}}
-
-disqus.shortname
-: (`string`) The `shortname` used with the Disqus commenting system. See [details](/templates/embedded/#disqus). To access this value from a template:
-
- ```go-html-template
- {{ .Site.Config.Services.Disqus.Shortname }}
- ```
-
-googleAnalytics.id
-: (`string`) The Google tag ID for Google Analytics 4 properties. See [details](/templates/embedded/#google-analytics). To access this value from a template:
-
- ```go-html-template
- {{ .Site.Config.Services.GoogleAnalytics.ID }}
- ```
-
-instagram.accessToken
-: (`string`) Do not use. Deprecated in [v0.123.0]. The embedded `instagram` shortcode no longer uses this setting.
-
-instagram.disableInlineCSS
-: (`bool`) Do not use. Deprecated in [v0.123.0]. The embedded `instagram` shortcode no longer uses this setting.
-
-rss.limit
-: (`int`) The maximum number of items to include in an RSS feed. Set to `-1` for no limit. Default is `-1`. See [details](/templates/rss/). To access this value from a template:
-
- ```go-html-template
- {{ .Site.Config.Services.RSS.Limit }}
- ```
-
-twitter.disableInlineCSS
-: (`bool`) Do not use. Deprecated in [v0.141.0]. Use the `x` shortcode instead.
-
-x.disableInlineCSS
-: (`bool`) Whether to disable the inline CSS rendered by the embedded `x` shortode. See [details](/shortcodes/x/#privacy). Default is `false`. To access this value from a template:
-
- ```go-html-template
- {{ .Site.Config.Services.X.DisableInlineCSS }}
-
-[v0.141.0]: https://github.com/gohugoio/hugo/releases/tag/v0.141.0
-[v0.123.0]: https://github.com/gohugoio/hugo/releases/tag/v0.123.0
diff --git a/docs/content/en/configuration/sitemap.md b/docs/content/en/configuration/sitemap.md
deleted file mode 100644
index bc972994c..000000000
--- a/docs/content/en/configuration/sitemap.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-title: Configure sitemap
-linkTitle: Sitemap
-description: Configure the sitemap.
-categories: []
-keywords: []
----
-
-These are the default sitemap configuration values. They apply to all pages unless overridden in front matter.
-
-{{< code-toggle config=sitemap />}}
-
-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).
-
-disable
-: {{< new-in 0.125.0 />}}
-: (`bool`) Whether to disable page inclusion. Default is `false`. Set to `true` in front matter to exclude the page.
-
-filename
-: (`string`) The name of the generated file. Default is `sitemap.xml`.
-
-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).
diff --git a/docs/content/en/configuration/taxonomies.md b/docs/content/en/configuration/taxonomies.md
deleted file mode 100644
index 4b5ba97a5..000000000
--- a/docs/content/en/configuration/taxonomies.md
+++ /dev/null
@@ -1,68 +0,0 @@
----
-title: Configure taxonomies
-linkTitle: Taxonomies
-description: Configure taxonomies.
-categories: []
-keywords: []
----
-
-The default configuration defines two [taxonomies](g), `categories` and `tags`.
-
-{{< code-toggle config=taxonomies />}}
-
-When creating a taxonomy:
-
-- Use the singular form for the key (e.g., `category`).
-- Use the plural form for the value (e.g., `categories`).
-
-Then use the value as the key in front matter:
-
-{{< code-toggle file=content/example.md fm=true >}}
----
-title: Example
-categories:
- - vegetarian
- - gluten-free
-tags:
- - appetizer
- - main course
-{{< /code-toggle >}}
-
-If you do not expect to assign more than one [term](g) from a given taxonomy to a content page, you may use the singular form for both key and value:
-
-{{< code-toggle file=hugo >}}
-taxonomies:
- author: author
-{{< /code-toggle >}}
-
-Then in front matter:
-
-{{< code-toggle file=content/example.md fm=true >}}
----
-title: Example
-author:
- - Robert Smith
-{{< /code-toggle >}}
-
-The example above illustrates that even with a single term, the value is still provided as an array.
-
-You must explicitly define the default taxonomies to maintain them when adding a new one:
-
-{{< code-toggle file=hugo >}}
-taxonomies:
- author: author
- category: categories
- tag: tags
-{{< /code-toggle >}}
-
-To disable the taxonomy system, use the [`disableKinds`] setting in the root of your site configuration to disable the `taxonomy` and `term` page [kinds](g).
-
-{{< code-toggle file=hugo >}}
-disableKinds = ['categories','tags']
-{{< /code-toggle >}}
-
-[`disableKinds`]: /configuration/all/#disablekinds
-
-See the [taxonomies] section for more information.
-
-[taxonomies]: /content-management/taxonomies/
diff --git a/docs/content/en/configuration/ugly-urls.md b/docs/content/en/configuration/ugly-urls.md
deleted file mode 100644
index ec1dd8a49..000000000
--- a/docs/content/en/configuration/ugly-urls.md
+++ /dev/null
@@ -1,36 +0,0 @@
----
-title: Configure ugly URLs
-linkTitle: Ugly URLs
-description: Configure ugly URLs.
-categories: []
-keywords: []
----
-
-{{% glossary-term "ugly url" %}} For example:
-
-```text
-https://example.org/section/article.html
-```
-
-In its default configuration, Hugo generates [pretty URLs](g). For example:
-```text
-https://example.org/section/article/
-```
-
-This is the default configuration:
-
-{{< code-toggle config=uglyURLs />}}
-
-To generate ugly URLs for the entire site:
-
-{{< code-toggle file=hugo >}}
-uglyURLs = true
-{{< /code-toggle >}}
-
-To generate ugly URLs for specific sections of your site:
-
-{{< code-toggle file=hugo >}}
-[uglyURLs]
-books = true
-films = false
-{{< /code-toggle >}}
diff --git a/docs/content/en/content-management/_index.md b/docs/content/en/content-management/_index.md
deleted file mode 100644
index 4e2060756..000000000
--- a/docs/content/en/content-management/_index.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: Content management
-description: Hugo makes managing large static sites easy with support for archetypes, content types, menus, cross references, summaries, and more.
-categories: []
-keywords: []
-weight: 10
-aliases: [/content/,/content/organization]
----
diff --git a/docs/content/en/content-management/archetypes.md b/docs/content/en/content-management/archetypes.md
deleted file mode 100644
index db0838504..000000000
--- a/docs/content/en/content-management/archetypes.md
+++ /dev/null
@@ -1,186 +0,0 @@
----
-title: Archetypes
-description: An archetype is a template for new content.
-categories: []
-keywords: []
-aliases: [/content/archetypes/]
----
-
-## Overview
-
-A content file consists of [front matter](g) and markup. The markup is typically Markdown, but Hugo also supports other [content formats](g). Front matter can be TOML, YAML, or JSON.
-
-The `hugo new content` command creates a new file in the `content` directory, using an archetype as a template. This is the default archetype:
-
-{{< code-toggle file=archetypes/default.md fm=true >}}
-title = '{{ replace .File.ContentBaseName `-` ` ` | title }}'
-date = '{{ .Date }}'
-draft = true
-{{< /code-toggle >}}
-
-When you create new content, Hugo evaluates the [template actions](g) within the archetype. For example:
-
-```sh
-hugo new content posts/my-first-post.md
-```
-
-With the default archetype shown above, Hugo creates this content file:
-
-{{< code-toggle file=content/posts/my-first-post.md fm=true >}}
-title = 'My First Post'
-date = '2023-08-24T11:49:46-07:00'
-draft = true
-{{< /code-toggle >}}
-
-You can create an archetype for one or more [content types](g). For example, use one archetype for posts, and use the default archetype for everything else:
-
-```text
-archetypes/
-├── default.md
-└── posts.md
-```
-
-## Lookup order
-
-Hugo looks for archetypes in the `archetypes` directory in the root of your project, falling back to the `archetypes` directory in themes or installed modules. An archetype for a specific content type takes precedence over the default archetype.
-
-For example, with this command:
-
-```sh
-hugo new content posts/my-first-post.md
-```
-
-The archetype lookup order is:
-
-1. `archetypes/posts.md`
-1. `archetypes/default.md`
-1. `themes/my-theme/archetypes/posts.md`
-1. `themes/my-theme/archetypes/default.md`
-
-If none of these exists, Hugo uses a built-in default archetype.
-
-## Functions and context
-
-You can use any template [function](g) within an archetype. As shown above, the default archetype uses the [`replace`](/functions/strings/replace) function to replace hyphens with spaces when populating the title in front matter.
-
-Archetypes receive the following [context](g):
-
-Date
-: (`string`) The current date and time, formatted in compliance with RFC3339.
-
-File
-: (`hugolib.fileInfo`) Returns file information for the current page. See [details](/methods/page/file).
-
-Type
-: (`string`) The [content type](g) inferred from the top-level directory name, or as specified by the `--kind` flag passed to the `hugo new content` command.
-
-Site
-: (`page.Site`) The current site object. See [details](/methods/site/).
-
-## Date format
-
-To insert date and time with a different format, use the [`time.Now`] function:
-
-[`time.Now`]: /functions/time/now/
-
-{{< code-toggle file=archetypes/default.md fm=true >}}
-title = '{{ replace .File.ContentBaseName `-` ` ` | title }}'
-date = '{{ time.Now.Format "2006-01-02" }}'
-draft = true
-{{< /code-toggle >}}
-
-## Include content
-
-Although typically used as a front matter template, you can also use an archetype to populate content.
-
-For example, in a documentation site you might have a section (content type) for functions. Every page within this section should follow the same format: a brief description, the function signature, examples, and notes. We can pre-populate the page to remind content authors of the standard format.
-
-````text {file="archetypes/functions.md"}
----
-date: '{{ .Date }}'
-draft: true
-title: '{{ replace .File.ContentBaseName `-` ` ` | title }}'
----
-
-A brief description of what the function does, using simple present tense in the third person singular form. For example:
-
-`someFunction` returns the string `s` repeated `n` times.
-
-## Signature
-
-```text
-func someFunction(s string, n int) string
-```
-
-## Examples
-
-One or more practical examples, each within a fenced code block.
-
-## Notes
-
-Additional information to clarify as needed.
-````
-
-Although you can include [template actions](g) within the content body, remember that Hugo evaluates these once---at the time of content creation. In most cases, place template actions in a [template](g) where Hugo evaluates the actions every time you [build](g) the site.
-
-## Leaf bundles
-
-You can also create archetypes for [leaf bundles](g).
-
-For example, in a photography site you might have a section (content type) for galleries. Each gallery is leaf bundle with content and images.
-
-Create an archetype for galleries:
-
-```text
-archetypes/
-├── galleries/
-│ ├── images/
-│ │ └── .gitkeep
-│ └── index.md <-- same format as default.md
-└── default.md
-```
-
-Subdirectories within an archetype must contain at least one file. Without a file, Hugo will not create the subdirectory when you create new content. The name and size of the file are irrelevant. The example above includes a `.gitkeep` file, an empty file commonly used to preserve otherwise empty directories in a Git repository.
-
-To create a new gallery:
-
-```sh
-hugo new galleries/bryce-canyon
-```
-
-This produces:
-
-```text
-content/
-├── galleries/
-│ └── bryce-canyon/
-│ ├── images/
-│ │ └── .gitkeep
-│ └── index.md
-└── _index.md
-```
-
-## Specify archetype
-
-Use the `--kind` command line flag to specify an archetype when creating content.
-
-For example, let's say your site has two sections: articles and tutorials. Create an archetype for each content type:
-
-```text
-archetypes/
-├── articles.md
-├── default.md
-└── tutorials.md
-```
-
-To create an article using the articles archetype:
-
-```sh
-hugo new content articles/something.md
-```
-
-To create an article using the tutorials archetype:
-
-```sh
-hugo new content --kind tutorials articles/something.md
-```
diff --git a/docs/content/en/content-management/build-options.md b/docs/content/en/content-management/build-options.md
deleted file mode 100644
index 8c29a19b9..000000000
--- a/docs/content/en/content-management/build-options.md
+++ /dev/null
@@ -1,303 +0,0 @@
----
-title: Build options
-description: Build options help define how Hugo must treat a given page when building the site.
-categories: []
-keywords: []
-aliases: [/content/build-options/]
----
-
-
-
-Build options are stored in a reserved front matter object named `build`[^1] with these defaults:
-
-[^1]: The `_build` alias for `build` is deprecated and will be removed in a future release.
-
-{{< code-toggle file=content/example/index.md fm=true >}}
-[build]
-list = 'always'
-publishResources = true
-render = 'always'
-{{< /code-toggle >}}
-
-list
-: When to include the page within page collections. Specify one of:
-
- - `always`: Include the page in _all_ page collections. For example, `site.RegularPages`, `.Pages`, etc. This is the default value.
- - `local`: Include the page in _local_ page collections. For example, `.RegularPages`, `.Pages`, etc. Use this option to create fully navigable but headless content sections.
- - `never`: Do not include the page in _any_ page collection.
-
-publishResources
-: Applicable to [page bundles], determines whether to publish the associated [page resources]. Specify one of:
-
- - `true`: Always publish resources. This is the default value.
- - `false`: Only publish a resource when invoking its [`Permalink`], [`RelPermalink`], or [`Publish`] method within a template.
-
-render
-: When to render the page. Specify one of:
-
- - `always`: Always render the page to disk. This is the default value.
- - `link`: Do not render the page to disk, but assign `Permalink` and `RelPermalink` values.
- - `never`: Never render the page to disk, and exclude it from all page collections.
-
-> [!note]
-> Any page, regardless of its build options, will always be available by using the [`.Page.GetPage`] or [`.Site.GetPage`] method.
-
-## Example -- headless page
-
-Create a unpublished page whose content and resources can be included in other pages.
-
-```text
-content/
-├── headless/
-│ ├── a.jpg
-│ ├── b.jpg
-│ └── index.md <-- leaf bundle
-└── _index.md <-- home page
-```
-
-Set the build options in front matter:
-
-{{< code-toggle file=content/headless/index.md fm=true >}}
-title = 'Headless page'
-[build]
- list = 'never'
- publishResources = false
- render = 'never'
-{{< /code-toggle >}}
-
-To include the content and images on the home page:
-
-```go-html-template {file="layouts/_default/home.html"}
-{{ with .Site.GetPage "/headless" }}
- {{ .Content }}
- {{ range .Resources.ByType "image" }}
-
- {{ end }}
-{{ end }}
-```
-
-The published site will have this structure:
-
-```text
-public/
-├── headless/
-│ ├── a.jpg
-│ └── b.jpg
-└── index.html
-```
-
-In the example above, note that:
-
-1. Hugo did not publish an HTML file for the page.
-1. Despite setting `publishResources` to `false` in front matter, Hugo published the [page resources] because we invoked the [`RelPermalink`] method on each resource. This is the expected behavior.
-
-## Example -- headless section
-
-Create a unpublished section whose content and resources can be included in other pages.
-
-```text
-content/
-├── headless/
-│ ├── note-1/
-│ │ ├── a.jpg
-│ │ ├── b.jpg
-│ │ └── index.md <-- leaf bundle
-│ ├── note-2/
-│ │ ├── c.jpg
-│ │ ├── d.jpg
-│ │ └── index.md <-- leaf bundle
-│ └── _index.md <-- branch bundle
-└── _index.md <-- home page
-```
-
-Set the build options in front matter, using the `cascade` keyword to "cascade" the values down to descendant pages.
-
-{{< code-toggle file=content/headless/_index.md fm=true >}}
-title = 'Headless section'
-[[cascade]]
-[cascade.build]
- list = 'local'
- publishResources = false
- render = 'never'
-{{< /code-toggle >}}
-
-In the front matter above, note that we have set `list` to `local` to include the descendant pages in local page collections.
-
-To include the content and images on the home page:
-
-```go-html-template {file="layouts/_default/home.html"}
-{{ with .Site.GetPage "/headless" }}
- {{ range .Pages }}
- {{ .Content }}
- {{ range .Resources.ByType "image" }}
-
- {{ end }}
- {{ end }}
-{{ end }}
-```
-
-The published site will have this structure:
-
-```text
-public/
-├── headless/
-│ ├── note-1/
-│ │ ├── a.jpg
-│ │ └── b.jpg
-│ └── note-2/
-│ ├── c.jpg
-│ └── d.jpg
-└── index.html
-```
-
-In the example above, note that:
-
-1. Hugo did not publish an HTML file for the page.
-1. Despite setting `publishResources` to `false` in front matter, Hugo correctly published the [page resources] because we invoked the [`RelPermalink`] method on each resource. This is the expected behavior.
-
-## Example -- list without publishing
-
-Publish a section page without publishing the descendant pages. For example, to create a glossary:
-
-```text
-content/
-├── glossary/
-│ ├── _index.md
-│ ├── bar.md
-│ ├── baz.md
-│ └── foo.md
-└── _index.md
-```
-
-Set the build options in front matter, using the `cascade` keyword to "cascade" the values down to descendant pages.
-
-{{< code-toggle file=content/glossary/_index.md fm=true >}}
-title = 'Glossary'
-[build]
-render = 'always'
-[[cascade]]
-[cascade.build]
- list = 'local'
- publishResources = false
- render = 'never'
-{{< /code-toggle >}}
-
-To render the glossary:
-
-```go-html-template {file="layouts/glossary/list.html"}
-
- {{ range .Pages }}
-
{{ .Title }}
-
{{ .Content }}
- {{ end }}
-
-```
-
-The published site will have this structure:
-
-```text
-public/
-├── glossary/
-│ └── index.html
-└── index.html
-```
-
-## Example -- publish without listing
-
-Publish a section's descendant pages without publishing the section page itself.
-
-```text
-content/
-├── books/
-│ ├── _index.md
-│ ├── book-1.md
-│ └── book-2.md
-└── _index.md
-```
-
-Set the build options in front matter:
-
-{{< code-toggle file=content/books/_index.md fm=true >}}
-title = 'Books'
-[build]
-render = 'never'
-list = 'never'
-{{< /code-toggle >}}
-
-The published site will have this structure:
-
-```text
-public/
-├── books/
-│ ├── book-1/
-│ │ └── index.html
-│ └── book-2/
-│ └── index.html
-└── index.html
-```
-
-## Example -- conditionally hide section
-
-Consider this example. A documentation site has a team of contributors with access to 20 custom shortcodes. Each shortcode takes several arguments, and requires documentation for the contributors to reference when using them.
-
-Instead of external documentation for the shortcodes, include an "internal" section that is hidden when building the production site.
-
-```text
-content/
-├── internal/
-│ ├── shortcodes/
-│ │ ├── _index.md
-│ │ ├── shortcode-1.md
-│ │ └── shortcode-2.md
-│ └── _index.md
-├── reference/
-│ ├── _index.md
-│ ├── reference-1.md
-│ └── reference-2.md
-├── tutorials/
-│ ├── _index.md
-│ ├── tutorial-1.md
-│ └── tutorial-2.md
-└── _index.md
-```
-
-Set the build options in front matter, using the `cascade` keyword to "cascade" the values down to descendant pages, and use the `target` keyword to target the production environment.
-
-{{< code-toggle file=content/internal/_index.md >}}
-title = 'Internal'
-[[cascade]]
-[cascade.build]
-render = 'never'
-list = 'never'
-[cascade.target]
-environment = 'production'
-{{< /code-toggle >}}
-
-The production site will have this structure:
-
-```text
-public/
-├── reference/
-│ ├── reference-1/
-│ │ └── index.html
-│ ├── reference-2/
-│ │ └── index.html
-│ └── index.html
-├── tutorials/
-│ ├── tutorial-1/
-│ │ └── index.html
-│ ├── tutorial-2/
-│ │ └── index.html
-│ └── index.html
-└── index.html
-```
-
-[`.Page.GetPage`]: /methods/page/getpage/
-[`.Site.GetPage`]: /methods/site/getpage/
-[`Permalink`]: /methods/resource/permalink/
-[`Publish`]: /methods/resource/publish/
-[`RelPermalink`]: /methods/resource/relpermalink/
-[page bundles]: /content-management/page-bundles/
-[page resources]: /content-management/page-resources/
diff --git a/docs/content/en/content-management/comments.md b/docs/content/en/content-management/comments.md
deleted file mode 100644
index fee4fb372..000000000
--- a/docs/content/en/content-management/comments.md
+++ /dev/null
@@ -1,72 +0,0 @@
----
-title: Comments
-description: Hugo ships with an internal Disqus template, but this isn't the only commenting system that will work with your new Hugo website.
-categories: []
-keywords: []
-aliases: [/extras/comments/]
----
-
-Hugo ships with support for [Disqus](https://disqus.com/), a third-party service that provides comment and community capabilities to websites via JavaScript.
-
-Your theme may already support Disqus, but if not, it is easy to add to your templates via [Hugo's built-in Disqus partial][disquspartial].
-
-## Add Disqus
-
-Hugo comes with all the code you need to load Disqus into your templates. Before adding Disqus to your site, you'll need to [set up an account][disqussetup].
-
-### Configure Disqus
-
-Disqus comments require you set a single value in your [site's configuration file][configuration] like so:
-
-{{< code-toggle file=hugo >}}
-[services.disqus]
-shortname = 'your-disqus-shortname'
-{{ code-toggle >}}
-
-For many websites, this is enough configuration. However, you also have the option to set the following in the [front matter] of a single content file:
-
-- `disqus_identifier`
-- `disqus_title`
-- `disqus_url`
-
-### Render Hugo's built-in Disqus partial template
-
-Disqus has its own [internal template](/templates/embedded/#disqus) available, to render it add the following code where you want comments to appear:
-
-```go-html-template
-{{ template "_internal/disqus.html" . }}
-```
-
-## Alternatives
-
-Commercial commenting systems:
-
-- [Emote](https://emote.com/)
-- [Graph Comment](https://graphcomment.com/)
-- [Hyvor Talk](https://talk.hyvor.com/)
-- [IntenseDebate](https://intensedebate.com/)
-- [ReplyBox](https://getreplybox.com/)
-
-Open-source commenting systems:
-
-- [Cactus Comments](https://cactus.chat/docs/integrations/hugo/)
-- [Comentario](https://gitlab.com/comentario/comentario/)
-- [Comma](https://github.com/Dieterbe/comma/)
-- [Commento](https://commento.io/)
-- [Discourse](https://meta.discourse.org/t/embed-discourse-comments-on-another-website-via-javascript/31963)
-- [Giscus](https://giscus.app/)
-- [Isso](https://isso-comments.de/)
-- [Remark42](https://remark42.com/)
-- [Staticman](https://staticman.net/)
-- [Talkyard](https://blog-comments.talkyard.io/)
-- [Utterances](https://utteranc.es/)
-
-[configuration]: /configuration/
-[disquspartial]: /templates/embedded/#disqus
-[disqussetup]: https://disqus.com/profile/signup/
-[forum]: https://discourse.gohugo.io
-[front matter]: /content-management/front-matter/
-[kaijuissue]: https://github.com/spf13/kaiju/issues/new
-[issotutorial]: https://stiobhart.net/2017-02-24-isso-comments/
-[partials]: /templates/partial/
-[MongoDB]: https://www.mongodb.com/
diff --git a/docs/content/en/content-management/content-adapters.md b/docs/content/en/content-management/content-adapters.md
deleted file mode 100644
index 3468bb728..000000000
--- a/docs/content/en/content-management/content-adapters.md
+++ /dev/null
@@ -1,349 +0,0 @@
----
-title: Content adapters
-description: Create content adapters to dynamically add content when building your site.
-categories: []
-keywords: []
----
-
-{{< new-in 0.126.0 />}}
-
-## Overview
-
-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.
-
-Unlike templates that reside in the `layouts` directory, content adapters reside in the `content` directory, no more than one per directory per language. When a content adapter creates a page, the page's [logical path](g) will be relative to the content adapter.
-
-```text
-content/
-├── articles/
-│ ├── _index.md
-│ ├── article-1.md
-│ └── article-2.md
-├── books/
-│ ├── _content.gotmpl <-- content adapter
-│ └── _index.md
-└── films/
- ├── _content.gotmpl <-- content adapter
- └── _index.md
-```
-
-Each content adapter is named _content.gotmpl and uses the same [syntax] as templates in the `layouts` directory. You can use any of the [template functions] within a content adapter, as well as the methods described below.
-
-## Methods
-
-Use these methods within a content adapter.
-
-### AddPage
-
-Adds a page to the site.
-
-```go-html-template {file="content/books/_content.gotmpl"}
-{{ $content := dict
- "mediaType" "text/markdown"
- "value" "The _Hunchback of Notre Dame_ was written by Victor Hugo."
-}}
-{{ $page := dict
- "content" $content
- "kind" "page"
- "path" "the-hunchback-of-notre-dame"
- "title" "The Hunchback of Notre Dame"
-}}
-{{ .AddPage $page }}
-```
-
-### AddResource
-
-Adds a page resource to the site.
-
-```go-html-template {file="content/books/_content.gotmpl"}
-{{ with resources.Get "images/a.jpg" }}
- {{ $content := dict
- "mediaType" .MediaType.Type
- "value" .
- }}
- {{ $resource := dict
- "content" $content
- "path" "the-hunchback-of-notre-dame/cover.jpg"
- }}
- {{ $.AddResource $resource }}
-{{ end }}
-```
-
-Then retrieve the new page resource with something like:
-
-```go-html-template {file="layouts/_default/single.html"}
-{{ with .Resources.Get "cover.jpg" }}
-
-{{ end }}
-```
-
-### Site
-
-Returns the `Site` to which the pages will be added.
-
-```go-html-template {file="content/books/_content.gotmpl"}
-{{ .Site.Title }}
-```
-
-> [!note]
-> Note that the `Site` returned isn't fully built when invoked from the content adapters; if you try to call methods that depends on pages, e.g. `.Site.Pages`, you will get an error saying "this method cannot be called before the site is fully initialized".
-
-### Store
-
-Returns a persistent “scratch pad” to store and manipulate data. The main use case for this is to transfer values between executions when [EnableAllLanguages](#enablealllanguages) is set. See [examples](/methods/page/store/).
-
-```go-html-template {file="content/books/_content.gotmpl"}
-{{ .Store.Set "key" "value" }}
-{{ .Store.Get "key" }}
-```
-
-### EnableAllLanguages
-
-By default, Hugo executes the content adapter for the language defined by the _content.gotmpl file. Use this method to activate the content adapter for all languages.
-
-```go-html-template {file="content/books/_content.gotmpl"}
-{{ .EnableAllLanguages }}
-{{ $content := dict
- "mediaType" "text/markdown"
- "value" "The _Hunchback of Notre Dame_ was written by Victor Hugo."
-}}
-{{ $page := dict
- "content" $content
- "kind" "page"
- "path" "the-hunchback-of-notre-dame"
- "title" "The Hunchback of Notre Dame"
-}}
-{{ .AddPage $page }}
-```
-
-## Page map
-
-Set any [front matter field] in the map passed to the [`AddPage`](#addpage) method, excluding `markup`. Instead of setting the `markup` field, specify the `content.mediaType` as described below.
-
-This table describes the fields most commonly passed to the `AddPage` method.
-
-Key|Description|Required
-:--|:--|:-:
-`content.mediaType`|The content [media type]. Default is `text/markdown`. See [content formats] for examples.|
-`content.value`|The content value as a string.|
-`dates.date`|The page creation date as a `time.Time` value.|
-`dates.expiryDate`|The page expiry date as a `time.Time` value.|
-`dates.lastmod`|The page last modification date as a `time.Time` value.|
-`dates.publishDate`|The page publication date as a `time.Time` value.|
-`params`|A map of page parameters.|
-`path`|The page's [logical path](g) relative to the content adapter. Do not include a leading slash or file extension.|:heavy_check_mark:
-`title`|The page title.|
-
-> [!note]
-> While `path` is the only required field, we recommend setting `title` as well.
->
-> When setting the `path`, Hugo transforms the given string to a logical path. For example, setting `path` to `A B C` produces a logical path of `/section/a-b-c`.
-
-## Resource map
-
-Construct the map passed to the [`AddResource`](#addresource) method using the fields below.
-
-Key|Description|Required
-:--|:--|:-:
-`content.mediaType`|The content [media type].|:heavy_check_mark:
-`content.value`|The content value as a string or resource.|:heavy_check_mark:
-`name`|The resource name.|
-`params`|A map of resource parameters.|
-`path`|The resources's [logical path](g) relative to the content adapter. Do not include a leading slash.|:heavy_check_mark:
-`title`|The resource title.|
-
-> [!note]
-> If the `content.value` is a string Hugo creates a new resource. If the `content.value` is a resource, Hugo obtains the value from the existing resource.
->
-> When setting the `path`, Hugo transforms the given string to a logical path. For example, setting `path` to `A B C/cover.jpg` produces a logical path of `/section/a-b-c/cover.jpg`.
-
-## Example
-
-Create pages from remote data, where each page represents a book review.
-
-### Step 1
-
-Create the content structure.
-
-```text
-content/
-└── books/
- ├── _content.gotmpl <-- content adapter
- └── _index.md
-```
-
-### Step 2
-Inspect the remote data to determine how to map key-value pairs to front matter fields.\
-
-
-### Step 3
-
-Create the content adapter.
-
-```go-html-template {file="content/books/_content.gotmpl" copy=true}
-{{/* Get remote data. */}}
-{{ $data := dict }}
-{{ $url := "https://gohugo.io/shared/examples/data/books.json" }}
-{{ with try (resources.GetRemote $url) }}
- {{ with .Err }}
- {{ errorf "Unable to get remote resource %s: %s" $url . }}
- {{ else with .Value }}
- {{ $data = . | transform.Unmarshal }}
- {{ else }}
- {{ errorf "Unable to get remote resource %s" $url }}
- {{ end }}
-{{ end }}
-
-{{/* Add pages and page resources. */}}
-{{ range $data }}
-
- {{/* Add page. */}}
- {{ $content := dict "mediaType" "text/markdown" "value" .summary }}
- {{ $dates := dict "date" (time.AsTime .date) }}
- {{ $params := dict "author" .author "isbn" .isbn "rating" .rating "tags" .tags }}
- {{ $page := dict
- "content" $content
- "dates" $dates
- "kind" "page"
- "params" $params
- "path" .title
- "title" .title
- }}
- {{ $.AddPage $page }}
-
- {{/* Add page resource. */}}
- {{ $item := . }}
- {{ with $url := $item.cover }}
- {{ with try (resources.GetRemote $url) }}
- {{ with .Err }}
- {{ errorf "Unable to get remote resource %s: %s" $url . }}
- {{ else with .Value }}
- {{ $content := dict "mediaType" .MediaType.Type "value" .Content }}
- {{ $params := dict "alt" $item.title }}
- {{ $resource := dict
- "content" $content
- "params" $params
- "path" (printf "%s/cover.%s" $item.title .MediaType.SubType)
- }}
- {{ $.AddResource $resource }}
- {{ else }}
- {{ errorf "Unable to get remote resource %s" $url }}
- {{ end }}
- {{ end }}
- {{ end }}
-
-{{ end }}
-```
-
-### Step 4
-
-Create a single template to render each book review.
-
-```go-html-template {file="layouts/books/single.html" copy=true}
-{{ define "main" }}
-
{{ .Title }}
-
- {{ with .Resources.GetMatch "cover.*" }}
-
- {{ end }}
-
-
- {{ end }}
-
- {{ .Content }}
-{{ end }}
-```
-
-## Multilingual sites
-
-With multilingual sites you can:
-
-1. Create one content adapter for all languages using the [`EnableAllLanguages`](#enablealllanguages) method as described above.
-1. Create content adapters unique to each language. See the examples below.
-
-### Translations by file name
-
-With this site configuration:
-
-{{< code-toggle file=hugo >}}
-[languages.en]
-weight = 1
-
-[languages.de]
-weight = 2
-{{< /code-toggle >}}
-
-Include a language designator in the content adapter's file name.
-
-```text
-content/
-└── books/
- ├── _content.de.gotmpl
- ├── _content.en.gotmpl
- ├── _index.de.md
- └── _index.en.md
-```
-
-### Translations by content directory
-
-With this site configuration:
-
-{{< code-toggle file=hugo >}}
-[languages.en]
-contentDir = 'content/en'
-weight = 1
-
-[languages.de]
-contentDir = 'content/de'
-weight = 2
-{{< /code-toggle >}}
-
-Create a single content adapter in each directory:
-
-```text
-content/
-├── de/
-│ └── books/
-│ ├── _content.gotmpl
-│ └── _index.md
-└── en/
- └── books/
- ├── _content.gotmpl
- └── _index.md
-```
-
-## Page collisions
-
-Two or more pages collide when they have the same publication path. Due to concurrency, the content of the published page is indeterminate. Consider this example:
-
-```text
-content/
-└── books/
- ├── _content.gotmpl <-- content adapter
- ├── _index.md
- └── the-hunchback-of-notre-dame.md
-```
-
-If the content adapter also creates books/the-hunchback-of-notre-dame, the content of the published page is indeterminate. You can not define the processing order.
-
-To detect page collisions, use the `--printPathWarnings` flag when building your site.
-
-[content formats]: /content-management/formats/#classification
-[front matter field]: /content-management/front-matter/#fields
-[media type]: https://en.wikipedia.org/wiki/Media_type
-[syntax]: /templates/introduction/
-[template functions]: /functions/
diff --git a/docs/content/en/content-management/data-sources.md b/docs/content/en/content-management/data-sources.md
deleted file mode 100644
index 3fc98b36a..000000000
--- a/docs/content/en/content-management/data-sources.md
+++ /dev/null
@@ -1,111 +0,0 @@
----
-title: Data sources
-description: Use local and remote data sources to augment or create content.
-categories: []
-keywords: []
-aliases: [/extras/datafiles/,/extras/datadrivencontent/,/doc/datafiles/,/templates/data-templates/]
----
-
-Hugo can access and [unmarshal](g) local and remote data sources including CSV, JSON, TOML, YAML, and XML. Use this data to augment existing content or to create new content.
-
-A data source might be a file in the `data` directory, a [global resource](g), a [page resource](g), or a [remote resource](g).
-
-## Data directory
-
-The `data` directory in the root of your project may contain one or more data files, in either a flat or nested tree. Hugo merges the data files to create a single data structure, accessible with the `Data` method on a `Site` object.
-
-Hugo also merges data directories from themes and modules into this single data structure, where the `data` directory in the root of your project takes precedence.
-
-> [!note]
-> Hugo reads the combined data structure into memory and keeps it there for the entire build. For data that is infrequently accessed, use global or page resources instead.
-
-Theme and module authors may wish to namespace their data files to prevent collisions. For example:
-
-```text
-project/
-└── data/
- └── mytheme/
- └── foo.json
-```
-
-> [!note]
-> Do not place CSV files in the `data` directory. Access CSV files as page, global, or remote resources.
-
-See the documentation for the [`Data`] method on a `Site` object for details and examples.
-
-## Global resources
-
-Use the `resources.Get` and `transform.Unmarshal` functions to access data files that exist as global resources.
-
-See the [`transform.Unmarshal`](/functions/transform/unmarshal/#global-resource) documentation for details and examples.
-
-## Page resources
-
-Use the `Resources.Get` method on a `Page` object combined with the `transform.Unmarshal` function to access data files that exist as page resources.
-
-See the [`transform.Unmarshal`](/functions/transform/unmarshal/#page-resource) documentation for details and examples.
-
-## Remote resources
-
-Use the `resources.GetRemote` and `transform.Unmarshal` functions to access remote data.
-
-See the [`transform.Unmarshal`](/functions/transform/unmarshal/#remote-resource) documentation for details and examples.
-
-## Augment existing content
-
-Use data sources to augment existing content. For example, create a shortcode to render an HTML table from a global CSV resource.
-
-```csv {file="assets/pets.csv"}
-"name","type","breed","age"
-"Spot","dog","Collie","3"
-"Felix","cat","Malicious","7"
-```
-
-```text {file="content/example.md"}
-{{* csv-to-table "pets.csv" */>}}
-```
-
-```go-html-template {file="layouts/shortcodes/csv-to-table.html"}
-{{ with $file := .Get 0 }}
- {{ with resources.Get $file }}
- {{ with . | transform.Unmarshal }}
-
-
-
- {{ range index . 0 }}
-
{{ . }}
- {{ end }}
-
-
-
- {{ range after 1 . }}
-
- {{ range . }}
-
{{ . }}
- {{ end }}
-
- {{ end }}
-
-
- {{ end }}
- {{ else }}
- {{ errorf "The %q shortcode was unable to find %s. See %s" $.Name $file $.Position }}
- {{ end }}
-{{ else }}
- {{ errorf "The %q shortcode requires one positional argument, the path to the CSV file relative to the assets directory. See %s" .Name .Position }}
-{{ end }}
-```
-
-Hugo renders this to:
-
-name|type|breed|age
-:--|:--|:--|:--
-Spot|dog|Collie|3
-Felix|cat|Malicious|7
-
-## Create new content
-
-Use [content adapters] to create new content.
-
-[`Data`]: /methods/site/data/
-[content adapters]: /content-management/content-adapters/
diff --git a/docs/content/en/content-management/diagrams.md b/docs/content/en/content-management/diagrams.md
deleted file mode 100644
index 0070ced59..000000000
--- a/docs/content/en/content-management/diagrams.md
+++ /dev/null
@@ -1,260 +0,0 @@
----
-title: Diagrams
-description: Use fenced code blocks and Markdown render hooks to include diagrams in your content.
-categories: []
-keywords: []
----
-
-## GoAT diagrams (ASCII)
-
-Hugo natively supports [GoAT] diagrams with an [embedded code block render hook]. This means that this code block:
-
-````txt
-```goat
- . . . .--- 1 .-- 1 / 1
- / \ | | .---+ .-+ +
- / \ .---+---. .--+--. | '--- 2 | '-- 2 / \ 2
- + + | | | | ---+ ---+ +
- / \ / \ .-+-. .-+-. .+. .+. | .--- 3 | .-- 3 \ / 3
- / \ / \ | | | | | | | | '---+ '-+ +
- 1 2 3 4 1 2 3 4 1 2 3 4 '--- 4 '-- 4 \ 4
-
-```
-````
-
-Will be rendered as:
-
-```goat
-
- . . . .--- 1 .-- 1 / 1
- / \ | | .---+ .-+ +
- / \ .---+---. .--+--. | '--- 2 | '-- 2 / \ 2
- + + | | | | ---+ ---+ +
- / \ / \ .-+-. .-+-. .+. .+. | .--- 3 | .-- 3 \ / 3
- / \ / \ | | | | | | | | '---+ '-+ +
- 1 2 3 4 1 2 3 4 1 2 3 4 '--- 4 '-- 4 \ 4
-```
-
-## Mermaid diagrams
-
-Hugo does not provide a built-in template for Mermaid diagrams. Create your own using a [code block render hook]:
-
-```go-html-template {file="layouts/_default/_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/_default/baseof.html" copy=true}
-{{ if .Store.Get "hasMermaid" }}
-
-{{ end }}
-```
-
-With that you can use the `mermaid` language in Markdown code blocks:
-
-````text {copy=true}
-```mermaid
-sequenceDiagram
- participant Alice
- participant Bob
- Alice->>John: Hello John, how are you?
- loop Healthcheck
- John->>John: Fight against hypochondria
- end
- Note right of John: Rational thoughts prevail!
- John-->>Alice: Great!
- John->>Bob: How about you?
- Bob-->>John: Jolly good!
-```
-````
-
-## Goat ASCII diagram examples
-
-### Graphics
-
-```goat
- .
- 0 3 P * Eye / ^ /
- *-------* +y \ +) \ / Reflection
- 1 /| 2 /| ^ \ \ \ v
- *-------* | | v0 \ v3 --------*--------
- | |4 | |7 | *----\-----*
- | *-----|-* +-----> +x / v X \ .-.<-------- o
- |/ |/ / / o \ | / | Refraction / \
- *-------* v / \ +-' / \
- 5 6 +z v1 *------------------* v2 | o-----o
- v
-
-```
-
-### Complex
-
-```goat
-+-------------------+ ^ .---.
-| A Box |__.--.__ __.--> | .-. | |
-| | '--' v | * |<--- | |
-+-------------------+ '-' | |
- Round *---(-. |
- .-----------------. .-------. .----------. .-------. | | |
- | Mixed Rounded | | | / Diagonals \ | | | | | |
- | & Square Corners | '--. .--' / \ |---+---| '-)-' .--------.
- '--+------------+-' .--. | '-------+--------' | | | | / Search /
- | | | | '---. | '-------' | '-+------'
- |<---------->| | | | v Interior | ^
- ' <---' '----' .-----------. ---. .--- v |
- .------------------. Diag line | .-------. +---. \ / . |
- | if (a > b) +---. .--->| | | | | Curved line \ / / \ |
- | obj->fcn() | \ / | '-------' |<--' + / \ |
- '------------------' '--' '--+--------' .--. .--. | .-. +Done?+-'
- .---+-----. | ^ |\ | | /| .--+ | | \ /
- | | | Join \|/ | | Curved | \| |/ | | \ | \ /
- | | +----> o --o-- '-' Vertical '--' '--' '-- '--' + .---.
- <--+---+-----' | /|\ | | 3 |
- v not:line 'quotes' .-' '---'
- .-. .---+--------. / A || B *bold* | ^
- | | | Not a dot | <---+---<-- A dash--is not a line v |
- '-' '---------+--' / Nor/is this. ---
-
-```
-
-### Process
-
-```goat
- .
- .---------. / \
- | START | / \ .-+-------+-. ___________
- '----+----' .-------. A / \ B | |COMPLEX| | / \ .-.
- | | END |<-----+CHOICE +----->| | | +--->+ PREPARATION +--->| X |
- v '-------' \ / | |PROCESS| | \___________/ '-'
- .---------. \ / '-+---+---+-'
- / INPUT / \ /
- '-----+---' '
- | ^
- v |
- .-----------. .-----+-----. .-.
- | PROCESS +---------------->| PROCESS |<------+ X |
- '-----------' '-----------' '-'
-```
-
-### File tree
-
-Created from
-
-```goat {width=300 color="orange"}
-───Linux─┬─Android
- ├─Debian─┬─Ubuntu─┬─Lubuntu
- │ │ ├─Kubuntu
- │ │ ├─Xubuntu
- │ │ └─Xubuntu
- │ └─Mint
- ├─Centos
- └─Fedora
-```
-
-### Sequence diagram
-
-
-
-```goat {class="w-40"}
-┌─────┐ ┌───┐
-│Alice│ │Bob│
-└──┬──┘ └─┬─┘
- │ │
- │ Hello Bob! │
- │───────────>│
- │ │
- │Hello Alice!│
- │<───────────│
-┌──┴──┐ ┌─┴─┐
-│Alice│ │Bob│
-└─────┘ └───┘
-
-```
-
-### Flowchart
-
-
-
-```goat
- _________________
- ╱ ╲ ┌─────┐
- ╱ DO YOU UNDERSTAND ╲____________________________________________________│GOOD!│
- ╲ FLOW CHARTS? ╱yes └──┬──┘
- ╲_________________╱ │
- │no │
- _________▽_________ ______________________ │
- ╱ ╲ ╱ ╲ ┌────┐ │
-╱ OKAY, YOU SEE THE ╲________________╱ ... AND YOU CAN SEE ╲___│GOOD│ │
-╲ LINE LABELED 'YES'? ╱yes ╲ THE ONES LABELED 'NO'? ╱yes└──┬─┘ │
- ╲___________________╱ ╲______________________╱ │ │
- │no │no │ │
- ________▽_________ _________▽__________ │ │
- ╱ ╲ ┌───────────┐ ╱ ╲ │ │
- ╱ BUT YOU SEE THE ╲___│WAIT, WHAT?│ ╱ BUT YOU JUST ╲___ │ │
- ╲ ONES LABELED 'NO'? ╱yes└───────────┘ ╲ FOLLOWED THEM TWICE? ╱yes│ │ │
- ╲__________________╱ ╲____________________╱ │ │ │
- │no │no │ │ │
- ┌───▽───┐ │ │ │ │
- │LISTEN.│ └───────┬───────┘ │ │
- └───┬───┘ ┌──────▽─────┐ │ │
- ┌─────▽────┐ │(THAT WASN'T│ │ │
- │I HATE YOU│ │A QUESTION) │ │ │
- └──────────┘ └──────┬─────┘ │ │
- ┌────▽───┐ │ │
- │SCREW IT│ │ │
- └────┬───┘ │ │
- └─────┬─────┘ │
- │ │
- └─────┬─────┘
- ┌───────▽──────┐
- │LET'S GO DRING│
- └───────┬──────┘
- ┌─────────▽─────────┐
- │HEY, I SHOULD TRY │
- │INSTALLING FREEBSD!│
- └───────────────────┘
-
-```
-
-### Table
-
-
-
-```goat {class="w-80 dark-blue"}
-┌────────────────────────────────────────────────┐
-│ │
-├────────────────────────────────────────────────┤
-│SYNTAX = { PRODUCTION } . │
-├────────────────────────────────────────────────┤
-│PRODUCTION = IDENTIFIER "=" EXPRESSION "." . │
-├────────────────────────────────────────────────┤
-│EXPRESSION = TERM { "|" TERM } . │
-├────────────────────────────────────────────────┤
-│TERM = FACTOR { FACTOR } . │
-├────────────────────────────────────────────────┤
-│FACTOR = IDENTIFIER │
-├────────────────────────────────────────────────┤
-│ | LITERAL │
-├────────────────────────────────────────────────┤
-│ | "[" EXPRESSION "]" │
-├────────────────────────────────────────────────┤
-│ | "(" EXPRESSION ")" │
-├────────────────────────────────────────────────┤
-│ | "{" EXPRESSION "}" . │
-├────────────────────────────────────────────────┤
-│IDENTIFIER = letter { letter } . │
-├────────────────────────────────────────────────┤
-│LITERAL = """" character { character } """" .│
-└────────────────────────────────────────────────┘
-```
-
-[code block render hook]: /render-hooks/code-blocks/
-[embedded code block render hook]: {{% eturl render-codeblock-goat %}}
-[GoAT]: https://github.com/bep/goat
diff --git a/docs/content/en/content-management/formats.md b/docs/content/en/content-management/formats.md
deleted file mode 100644
index 1acaae063..000000000
--- a/docs/content/en/content-management/formats.md
+++ /dev/null
@@ -1,132 +0,0 @@
----
-title: Content formats
-description: Create your content using Markdown, HTML, Emacs Org Mode, AsciiDoc, Pandoc, or reStructuredText.
-categories: []
-keywords: []
-aliases: [/content/markdown-extras/,/content/supported-formats/,/doc/supported-formats/]
----
-
-## Introduction
-
-You may mix content formats throughout your site. For example:
-
-```text
-content/
-└── posts/
- ├── post-1.md
- ├── post-2.adoc
- ├── post-3.org
- ├── post-4.pandoc
- ├── post-5.rst
- └── post-6.html
-```
-
-Regardless of content format, all content must have [front matter], preferably including both `title` and `date`.
-
-Hugo selects the content renderer based on the `markup` identifier in front matter, falling back to the file extension. See the [classification] table below for a list of markup identifiers and recognized file extensions.
-
-[classification]: #classification
-[front matter]: /content-management/front-matter/
-
-## Formats
-
-### Markdown
-
-Create your content in [Markdown] preceded by front matter.
-
-Markdown is Hugo's default content format. Hugo natively renders Markdown to HTML using [Goldmark]. Goldmark is fast and conforms to the [CommonMark] and [GitHub Flavored Markdown] specifications. You can configure Goldmark in your [site configuration][configure goldmark].
-
-Hugo provides custom Markdown features including:
-
-[Attributes]
-: Apply HTML attributes such as `class` and `id` to Markdown images and block elements including blockquotes, fenced code blocks, headings, horizontal rules, lists, paragraphs, and tables.
-
-[Extensions]
-: Leverage the embedded Markdown extensions to create tables, definition lists, footnotes, task lists, inserted text, mark text, subscripts, superscripts, and more.
-
-[Mathematics]
-: Include mathematical equations and expressions in Markdown using LaTeX markup.
-
-[Render hooks]
-: Override the conversion of Markdown to HTML when rendering fenced code blocks, headings, images, and links. For example, render every standalone image as an HTML `figure` element.
-
-[Attributes]: /content-management/markdown-attributes/
-[CommonMark]: https://spec.commonmark.org/current/
-[Extensions]: /configuration/markup/#extensions
-[GitHub Flavored Markdown]: https://github.github.com/gfm/
-[Goldmark]: https://github.com/yuin/goldmark
-[Markdown]: https://daringfireball.net/projects/markdown/
-[Mathematics]: /content-management/mathematics/
-[Render hooks]: /render-hooks/introduction/
-[configure goldmark]: /configuration/markup/#goldmark
-
-### HTML
-
-Create your content in [HTML] preceded by front matter. The content is typically what you would place within an HTML document's `body` or `main` element.
-
-[HTML]: https://developer.mozilla.org/en-US/docs/Learn_web_development/Getting_started/Your_first_website/Creating_the_content
-
-### Emacs Org Mode
-
-Create your content in the [Emacs Org Mode] format preceded by front matter. You can use Org Mode keywords for front matter. See [details].
-
-[details]: /content-management/front-matter/#emacs-org-mode
-[Emacs Org Mode]: https://orgmode.org/
-
-### AsciiDoc
-
-Create your content in the [AsciiDoc] format preceded by front matter. Hugo renders AsciiDoc content to HTML using the Asciidoctor executable. You must install Asciidoctor and its dependencies (Ruby) to use the AsciiDoc content format.
-
-You can configure the AsciiDoc renderer in your [site configuration][configure asciidoc].
-
-In its default configuration, Hugo passes these CLI flags when calling the Asciidoctor executable:
-
-```text
---no-header-footer
-```
-
-The CLI flags passed to the Asciidoctor executable depend on configuration. You may inspect the flags when building your site:
-
-```text
-hugo --logLevel info
-```
-
-[AsciiDoc]: https://asciidoc.org/
-[configure the AsciiDoc renderer]: /configuration/markup/#asciidoc
-[configure asciidoc]: /configuration/markup/#asciidoc
-
-### Pandoc
-
-Create your content in the [Pandoc] format preceded by front matter. Hugo renders Pandoc content to HTML using the Pandoc executable. You must install Pandoc to use the Pandoc content format.
-
-Hugo passes these CLI flags when calling the Pandoc executable:
-
-```text
---mathjax
-```
-
-[Pandoc]: https://pandoc.org/
-
-### reStructuredText
-
-Create your content in the [reStructuredText] format preceded by front matter. Hugo renders reStructuredText content to HTML using [Docutils], specifically rst2html. You must install Docutils and its dependencies (Python) to use the reStructuredText content format.
-
-Hugo passes these CLI flags when calling the rst2html executable:
-
-```text
---leave-comments --initial-header-level=2
-```
-
-[Docutils]: https://docutils.sourceforge.io/
-[reStructuredText]: https://docutils.sourceforge.io/rst.html
-
-## Classification
-
-{{% include "/_common/content-format-table.md" %}}
-
-When converting content to HTML, Hugo uses:
-
-- Native renderers for Markdown, HTML, and Emacs Org mode
-- External renderers for AsciiDoc, Pandoc, and reStructuredText
-
-Native renderers are faster than external renderers.
diff --git a/docs/content/en/content-management/front-matter.md b/docs/content/en/content-management/front-matter.md
deleted file mode 100644
index 8bfbd1acc..000000000
--- a/docs/content/en/content-management/front-matter.md
+++ /dev/null
@@ -1,362 +0,0 @@
----
-title: Front matter
-description: Use front matter to add metadata to your content.
-categories: []
-keywords: []
-aliases: [/content/front-matter/]
----
-
-## Overview
-
-The front matter at the top of each content file is metadata that:
-
-- Describes the content
-- Augments the content
-- Establishes relationships with other content
-- Controls the published structure of your site
-- Determines template selection
-
-Provide front matter using a serialization format, one of [JSON], [TOML], or [YAML]. Hugo determines the front matter format by examining the delimiters that separate the front matter from the page content.
-
-[json]: https://www.json.org/
-[toml]: https://toml.io/
-[yaml]: https://yaml.org/
-
-See examples of front matter delimiters by toggling between the serialization formats below.
-
-{{< code-toggle file=content/example.md fm=true >}}
-title = 'Example'
-date = 2024-02-02T04:14:54-08:00
-draft = false
-weight = 10
-[params]
-author = 'John Smith'
-{{< /code-toggle >}}
-
-Front matter fields may be [boolean](g), [integer](g), [float](g), [string](g), [arrays](g), or [maps](g). Note that the TOML format also supports unquoted date/time values.
-
-## Fields
-
-The most common front matter fields are `date`, `draft`, `title`, and `weight`, but you can specify metadata using any of fields below.
-
-> [!note]
-> The field names below are reserved. For example, you cannot create a custom field named `type`. Create custom fields under the `params` key. See the [parameters] section for details.
-
-[parameters]: #parameters
-
-aliases
-: (`string array`) An array of one or more aliases, where each alias is a relative URL that will redirect the browser to the current location. Access these values from a template using the [`Aliases`] method on a `Page` object. See the [aliases] section for details.
-
-build
-: (`map`) A map of [build options].
-
-cascade
-: (`map`) A map of front matter keys whose values are passed down to the page's descendants unless overwritten by self or a closer ancestor's cascade. See the [cascade] section for details.
-
-date
-: (`string`) The date associated with the page, typically the creation date. Note that the TOML format also supports unquoted date/time values. See the [dates](#dates) section for examples. Access this value from a template using the [`Date`] method on a `Page` object.
-
-description
-: (`string`) Conceptually different than the page `summary`, the description is typically rendered within a `meta` element within the `head` element of the published HTML file. Access this value from a template using the [`Description`] method on a `Page` object.
-
-draft
-: (`bool`) Whether to disable rendering unless you pass the `--buildDrafts` flag to the `hugo` command. Access this value from a template using the [`Draft`] method on a `Page` object.
-
-expiryDate
-: (`string`) The page expiration date. On or after the expiration date, the page will not be rendered unless you pass the `--buildExpired` flag to the `hugo` command. Note that the TOML format also supports unquoted date/time values. See the [dates](#dates) section for examples. Access this value from a template using the [`ExpiryDate`] method on a `Page` object.
-
-headless
-: (`bool`) Applicable to [leaf bundles], whether to set the `render` and `list` [build options] to `never`, creating a headless bundle of [page resources].
-
-isCJKLanguage
-: (`bool`) Whether the content language is in the [CJK](g) family. This value determines how Hugo calculates word count, and affects the values returned by the [`WordCount`], [`FuzzyWordCount`], [`ReadingTime`], and [`Summary`] methods on a `Page` object.
-
-keywords
-: (`string array`) An array of keywords, typically rendered within a `meta` element within the `head` element of the published HTML file, or used as a [taxonomy](g) to classify content. Access these values from a template using the [`Keywords`] method on a `Page` object.
-
-lastmod
-: (`string`) The date that the page was last modified. Note that the TOML format also supports unquoted date/time values. See the [dates](#dates) section for examples. Access this value from a template using the [`Lastmod`] method on a `Page` object.
-
-layout
-: (`string`) Provide a template name to [target a specific template], overriding the default [template lookup order]. Set the value to the base file name of the template, excluding its extension. Access this value from a template using the [`Layout`] method on a `Page` object.
-
-linkTitle
-: (`string`) Typically a shorter version of the `title`. Access this value from a template using the [`LinkTitle`] method on a `Page` object.
-
-markup
-: (`string`) An identifier corresponding to one of the supported [content formats]. If not provided, Hugo determines the content renderer based on the file extension.
-
-menus
-: (`string`, `string array`, or `map`) If set, Hugo adds the page to the given menu or menus. See the [menus] page for details.
-
-modified
-: Alias to [lastmod](#lastmod).
-
-outputs
-: (`string array`) The [output formats] to render. See [configure outputs] for more information.
-
-params
-: {{< new-in 0.123.0 />}}
-: (`map`) A map of custom [page parameters].
-
-pubdate
-: Alias to [publishDate](#publishdate).
-
-publishDate
-: (`string`) The page publication date. Before the publication date, the page will not be rendered unless you pass the `--buildFuture` flag to the `hugo` command. Note that the TOML format also supports unquoted date/time values. See the [dates](#dates) section for examples. Access this value from a template using the [`PublishDate`] method on a `Page` object.
-
-published
-: Alias to [publishDate](#publishdate).
-
-resources
-: (`map array`) An array of maps to provide metadata for [page resources].
-
-sitemap
-: (`map`) A map of sitemap options. See the [sitemap templates] page for details. Access these values from a template using the [`Sitemap`] method on a `Page` object.
-
-slug
-: (`string`) Overrides the last segment of the URL path. Not applicable to section pages. See the [URL management] page for details. Access this value from a template using the [`Slug`] method on a `Page` object.
-
-summary
-: (`string`) Conceptually different than the page `description`, the summary either summarizes the content or serves as a teaser to encourage readers to visit the page. Access this value from a template using the [`Summary`] method on a `Page` object.
-
-title
-: (`string`) The page title. Access this value from a template using the [`Title`] method on a `Page` object.
-
-translationKey
-: (`string`) An arbitrary value used to relate two or more translations of the same page, useful when the translated pages do not share a common path. Access this value from a template using the [`TranslationKey`] method on a `Page` object.
-
-type
-: (`string`) The [content type](g), overriding the value derived from the top-level section in which the page resides. Access this value from a template using the [`Type`] method on a `Page` object.
-
-unpublishdate
-: Alias to [expirydate](#expirydate).
-
-url
-: (`string`) Overrides the entire URL path. Applicable to regular pages and section pages. See the [URL management] page for details.
-
-weight
-: (`int`) The page [weight](g), used to order the page within a [page collection](g). Access this value from a template using the [`Weight`] method on a `Page` object.
-
-[URL management]: /content-management/urls/#slug
-[`Summary`]: /methods/page/summary/
-[`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/
-[`publishdate`]: /methods/page/publishdate/
-[`readingtime`]: /methods/page/readingtime/
-[`sitemap`]: /methods/page/sitemap/
-[`slug`]: /methods/page/slug/
-[`summary`]: /methods/page/summary/
-[`title`]: /methods/page/title/
-[`translationkey`]: /methods/page/translationkey/
-[`type`]: /methods/page/type/
-[`weight`]: /methods/page/weight/
-[`wordcount`]: /methods/page/wordcount/
-[aliases]: /content-management/urls/#aliases
-[build options]: /content-management/build-options/
-[cascade]: #cascade-1
-[configure outputs]: /configuration/outputs/#outputs-per-page
-[content formats]: /content-management/formats/#classification
-[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/
-
-## Parameters
-
-{{< new-in 0.123.0 />}}
-
-Specify custom page parameters under the `params` key in front matter:
-
-{{< code-toggle file=content/example.md fm=true >}}
-title = 'Example'
-date = 2024-02-02T04:14:54-08:00
-draft = false
-weight = 10
-[params]
-author = 'John Smith'
-{{< /code-toggle >}}
-
-Access these values from a template using the [`Params`] or [`Param`] method on a `Page` object.
-
-[`param`]: /methods/page/param/
-[`params`]: /methods/page/params/
-
-Hugo provides [embedded templates] to optionally insert meta data within the `head` element of your rendered pages. These embedded templates expect the following front matter parameters:
-
-Parameter|Data type|Used by these embedded templates
-:--|:--|:--
-`audio`|`[]string`|[`opengraph.html`]
-`images`|`[]string`|[`opengraph.html`], [`schema.html`], [`twitter_cards.html`]
-`videos`|`[]string`|[`opengraph.html`]
-
-The embedded templates will skip a parameter if not provided in front matter, but will throw an error if the data type is unexpected.
-
-## Taxonomies
-
-Classify content by adding taxonomy terms to front matter. For example, with this site configuration:
-
-{{< code-toggle file=hugo >}}
-[taxonomies]
-tag = 'tags'
-genre = 'genres'
-{{< /code-toggle >}}
-
-Add taxonomy terms as shown below:
-
-{{< code-toggle file=content/example.md fm=true >}}
-title = 'Example'
-date = 2024-02-02T04:14:54-08:00
-draft = false
-weight = 10
-tags = ['red','blue']
-genres = ['mystery','romance']
-[params]
-author = 'John Smith'
-{{< /code-toggle >}}
-
-You can add taxonomy terms to the front matter of any these [page kinds](g):
-
-- `home`
-- `page`
-- `section`
-- `taxonomy`
-- `term`
-
-Access taxonomy terms from a template using the [`Params`] or [`GetTerms`] method on a `Page` object. For example:
-
-```go-html-template {file="layouts/_default/single.html"}
-{{ with .GetTerms "tags" }}
-
-{{ end }}
-```
-
-[`Params`]: /methods/page/params/
-[`GetTerms`]: /methods/page/getterms/
-
-## Cascade
-
-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.
-
-For example, to cascade a "color" parameter from the home page to all its descendants:
-
-{{< code-toggle file=content/_index.md fm=true >}}
-title = 'Home'
-[cascade.params]
-color = 'red'
-{{< /code-toggle >}}
-
-### 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:
-
-[^1]: The `_target` alias for `target` is deprecated and will be removed in a future release.
-
-{{< code-toggle file=content/_index.md fm=true >}}
-title = 'Home'
-[cascade.params]
-color = 'red'
-[cascade.target]
-path = '{/articles,/articles/**}'
-{{< /code-toggle >}}
-
-Use any combination of these keywords to target pages and/or environments:
-
-environment
-: (`string`) A [glob](g) pattern matching the build [environment](g). For example: `{staging,production}`.
-
-kind
-: (`string`) A [glob](g) pattern matching the [page kind](g). For example: ` {taxonomy,term}`.
-
-path
-: (`string`) A [glob](g) pattern matching the page's [logical path](g). For example: `{/books,/books/**}`.
-
-### Array
-
-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 >}}
-
-> [!note]
-> For multilingual sites, defining cascade values in your site 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 site, 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 site 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 site configuration
-1. The `Etc/UTC` time zone
-
-[`opengraph.html`]: {{% eturl opengraph %}}
-[`schema.html`]: {{% eturl schema %}}
-[`twitter_cards.html`]: {{% eturl twitter_cards %}}
-[embedded templates]: /templates/embedded/
diff --git a/docs/content/en/content-management/image-processing/index.md b/docs/content/en/content-management/image-processing/index.md
deleted file mode 100644
index 8d60c4f93..000000000
--- a/docs/content/en/content-management/image-processing/index.md
+++ /dev/null
@@ -1,447 +0,0 @@
----
-title: Image processing
-description: Resize, crop, rotate, filter, and convert images.
-categories: []
-keywords: []
----
-
-## Image resources
-
-To process an image you must access the file as a page resource, global resource, or remote resource.
-
-### Page resource
-
-{{% glossary-term "page resource" %}}
-
-```text
-content/
-└── posts/
- └── post-1/ <-- page bundle
- ├── index.md
- └── sunset.jpg <-- page resource
-```
-
-To access an image as a page resource:
-
-```go-html-template
-{{ $image := .Resources.Get "sunset.jpg" }}
-```
-
-### Global resource
-
-{{% glossary-term "global resource" %}}
-
-```text
-assets/
-└── images/
- └── sunset.jpg <-- global resource
-```
-
-To access an image as a global resource:
-
-```go-html-template
-{{ $image := resources.Get "images/sunset.jpg" }}
-```
-
-### Remote resource
-
-{{% glossary-term "remote resource" %}}
-
-To access an image as a remote resource:
-
-```go-html-template
-{{ $image := resources.GetRemote "https://gohugo.io/img/hugo-logo.png" }}
-```
-
-## Image rendering
-
-Once you have accessed an image as a resource, render it in your templates using the `Permalink`, `RelPermalink`, `Width`, and `Height` properties.
-
-Example 1: Throws an error if the resource is not found.
-
-```go-html-template
-{{ $image := .Resources.GetMatch "sunset.jpg" }}
-
-```
-
-Example 2: Skips image rendering if the resource is not found.
-
-```go-html-template
-{{ $image := .Resources.GetMatch "sunset.jpg" }}
-{{ with $image }}
-
-{{ end }}
-```
-
-Example 3: A more concise way to skip image rendering if the resource is not found.
-
-```go-html-template
-{{ with .Resources.GetMatch "sunset.jpg" }}
-
-{{ end }}
-```
-
-Example 4: Skips rendering if there's problem accessing a remote resource.
-
-```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 }}
-```
-
-## Image processing methods
-
-The `image` resource implements the [`Process`], [`Resize`], [`Fit`], [`Fill`], [`Crop`], [`Filter`], [`Colors`] and [`Exif`] methods.
-
-> [!note]
-> Metadata (EXIF, IPTC, XMP, etc.) is not preserved during image transformation. Use the `Exif` method with the _original_ image to extract EXIF metadata from JPEG, PNG, TIFF, and WebP images.
-
-### Process
-
-{{< new-in 0.119.0 />}}
-
-> [!note]
-> The `Process` method is also available as a filter, which is more effective if you need to apply multiple filters to an image. See [Process filter](/functions/images/process).
-
-Process processes the image with the given specification. The specification can contain an optional action, one of `resize`, `crop`, `fit` or `fill`. This means that you can use this method instead of [`Resize`], [`Fit`], [`Fill`], or [`Crop`].
-
-See [Options](#image-processing-options) for available options.
-
-You can also use this method apply image processing that does not need any scaling, e.g. format conversions:
-
-```go-html-template
-{{/* Convert the image from JPG to PNG. */}}
-{{ $png := $jpg.Process "png" }}
-```
-
-Some more examples:
-
-```go-html-template
-{{/* Rotate the image 90 degrees counter-clockwise. */}}
-{{ $image := $image.Process "r90" }}
-
-{{/* Scaling actions. */}}
-{{ $image := $image.Process "resize 600x" }}
-{{ $image := $image.Process "crop 600x400" }}
-{{ $image := $image.Process "fit 600x400" }}
-{{ $image := $image.Process "fill 600x400" }}
-```
-
-### Resize
-
-Resize an image to the given width and/or height.
-
-If you specify both width and height, the resulting image will be disproportionally scaled unless the original image has the same aspect ratio.
-
-```go-html-template
-{{/* Resize to a width of 600px and preserve aspect ratio */}}
-{{ $image := $image.Resize "600x" }}
-
-{{/* Resize to a height of 400px and preserve aspect ratio */}}
-{{ $image := $image.Resize "x400" }}
-
-{{/* Resize to a width of 600px and a height of 400px */}}
-{{ $image := $image.Resize "600x400" }}
-```
-
-### Fit
-
-Downscale an image to fit the given dimensions while maintaining aspect ratio. You must provide both width and height.
-
-```go-html-template
-{{ $image := $image.Fit "600x400" }}
-```
-
-### Fill
-
-Crop and resize an image to match the given dimensions. You must provide both width and height. Use the [`anchor`] option to change the crop box anchor point.
-
-```go-html-template
-{{ $image := $image.Fill "600x400" }}
-```
-
-### Crop
-
-Crop an image to match the given dimensions without resizing. You must provide both width and height. Use the [`anchor`] option to change the crop box anchor point.
-
-```go-html-template
-{{ $image := $image.Crop "600x400" }}
-```
-
-### Filter
-
-Apply one or more [filters] to an image.
-
-```go-html-template
-{{ $image := $image.Filter (images.GaussianBlur 6) (images.Pixelate 8) }}
-```
-
-Write this in a more functional style using pipes. Hugo applies the filters in the order given.
-
-```go-html-template
-{{ $image := $image | images.Filter (images.GaussianBlur 6) (images.Pixelate 8) }}
-```
-
-Sometimes it can be useful to create the filter chain once and then reuse it.
-
-```go-html-template
-{{ $filters := slice (images.GaussianBlur 6) (images.Pixelate 8) }}
-{{ $image1 := $image1.Filter $filters }}
-{{ $image2 := $image2.Filter $filters }}
-```
-
-### Colors
-
-`.Colors` returns a slice of hex strings with the dominant colors in the image using a simple histogram method.
-
-```go-html-template
-{{ $colors := $image.Colors }}
-```
-
-This method is fast, but if you also scale down your images, it would be good for performance to extract the colors from the scaled down image.
-
-### EXIF
-
-Provides an [EXIF] object containing image metadata.
-
-You may access EXIF data in JPEG, PNG, TIFF, and WebP images. To prevent errors when processing images without EXIF data, wrap the access in a [`with`] statement.
-
-```go-html-template
-{{ with $image.Exif }}
- Date: {{ .Date }}
- Lat/Long: {{ .Lat }}/{{ .Long }}
- Tags:
- {{ range $k, $v := .Tags }}
- TAG: {{ $k }}: {{ $v }}
- {{ end }}
-{{ end }}
-```
-
-You may also access EXIF fields individually, using the [`lang.FormatNumber`] function to format the fields as needed.
-
-```go-html-template
-{{ with $image.Exif }}
-
- {{ with .Date }}
Date: {{ .Format "January 02, 2006" }}
{{ end }}
- {{ with .Tags.ApertureValue }}
Aperture: {{ lang.FormatNumber 2 . }}
{{ end }}
- {{ with .Tags.BrightnessValue }}
Brightness: {{ lang.FormatNumber 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 }}
-
-{{ end }}
-```
-
-#### EXIF methods
-
-Date
-: (`time.Time`) Returns the image creation date/time. Format with the [`time.Format`]function.
-
-Lat
-: (`float64`) Returns the GPS latitude in degrees.
-
-Long
-: (`float64`) Returns the GPS longitude in degrees.
-
-Tags
-: (`exif.Tags`) Returns a collection of the available EXIF tags for this image. You may include or exclude specific tags from this collection in the [site configuration].
-
-## Image processing options
-
-The [`Resize`], [`Fit`], [`Fill`], and [`Crop`] methods accept a space-delimited, case-insensitive list of options. The order of the options within the list is irrelevant.
-
-### Dimensions
-
-With the [`Resize`] method you must specify width, height, or both. The [`Fit`], [`Fill`], and [`Crop`] methods require both width and height. All dimensions are in pixels.
-
-```go-html-template
-{{ $image := $image.Resize "600x" }}
-{{ $image := $image.Resize "x400" }}
-{{ $image := $image.Resize "600x400" }}
-{{ $image := $image.Fit "600x400" }}
-{{ $image := $image.Fill "600x400" }}
-{{ $image := $image.Crop "600x400" }}
-```
-
-### Rotation
-
-Rotates an image counter-clockwise by the given angle. Hugo performs rotation _before_ scaling. For example, if the original image is 600x400 and you wish to rotate the image 90 degrees counter-clockwise while scaling it by 50%:
-
-```go-html-template
-{{ $image = $image.Resize "200x r90" }}
-```
-
-In the example above, the width represents the desired width _after_ rotation.
-
-To rotate an image without scaling, use the dimensions of the original image:
-
-```go-html-template
-{{ with .Resources.GetMatch "sunset.jpg" }}
- {{ with .Resize (printf "%dx%d r90" .Height .Width) }}
-
- {{ end }}
-{{ end }}
-```
-
-In the example above, on the second line, we have reversed width and height to reflect the desired dimensions _after_ rotation.
-
-### Anchor
-
-When using the [`Crop`] or [`Fill`] method, the _anchor_ determines the placement of the crop box. You may specify `TopLeft`, `Top`, `TopRight`, `Left`, `Center`, `Right`, `BottomLeft`, `Bottom`, `BottomRight`, or `Smart`.
-
-The default value is `Smart`, which uses [Smartcrop] image analysis to determine the optimal placement of the crop box. You may override the default value in the [site configuration].
-
-For example, if you have a 400x200 image with a bird in the upper left quadrant, you can create a 200x100 thumbnail containing the bird:
-
-```go-html-template
-{{ $image.Crop "200x100 TopLeft" }}
-```
-
-If you apply [rotation](#rotation) when using the [`Crop`] or [`Fill`] method, specify the anchor relative to the rotated image.
-
-### Target format
-
-By default, Hugo encodes the image in the source format. You may convert the image to another format by specifying `bmp`, `gif`, `jpeg`, `jpg`, `png`, `tif`, `tiff`, or `webp`.
-
-```go-html-template
-{{ $image.Resize "600x webp" }}
-```
-
-To convert an image without scaling, use the dimensions of the original image:
-
-```go-html-template
-{{ with .Resources.GetMatch "sunset.jpg" }}
- {{ with .Resize (printf "%dx%d webp" .Width .Height) }}
-
- {{ end }}
-{{ end }}
-```
-
-### Quality
-
-Applicable to JPEG and WebP images, the `q` value determines the quality of the converted image. Higher values produce better quality images, while lower values produce smaller files. Set this value to a whole number between 1 and 100, inclusive.
-
-The default value is 75. You may override the default value in the [site configuration].
-
-```go-html-template
-{{ $image.Resize "600x webp q50" }}
-```
-
-### Hint
-
-Applicable to WebP images, this option corresponds to a set of predefined encoding parameters, and is equivalent to the `-preset` flag for the [`cwebp`] encoder.
-
-Value|Example
-:--|:--
-`drawing`|Hand or line drawing with high-contrast details
-`icon`|Small colorful image
-`photo`|Outdoor photograph with natural lighting
-`picture`|Indoor photograph such as a portrait
-`text`|Image that is primarily text
-
-The default value is `photo`. You may override the default value in the [site configuration].
-
-```go-html-template
-{{ $image.Resize "600x webp picture" }}
-```
-
-### Background color
-
-When converting an image from a format that supports transparency (e.g., PNG) to a format that does _not_ support transparency (e.g., JPEG), you may specify the background color of the resulting image.
-
-Use either a 3-digit or 6-digit hexadecimal color code (e.g., `#00f` or `#0000ff`).
-
-The default value is `#ffffff` (white). You may override the default value in the [site configuration].
-
-```go-html-template
-{{ $image.Resize "600x jpg #b31280" }}
-```
-
-### Resampling filter
-
-You may specify the resampling filter used when resizing an image. Commonly used resampling filters include:
-
-Filter|Description
-:--|:--
-`Box`|Simple and fast averaging filter appropriate for downscaling
-`Lanczos`|High-quality resampling filter for photographic images yielding sharp results
-`CatmullRom`|Sharp cubic filter that is faster than the Lanczos filter while providing similar results
-`MitchellNetravali`|Cubic filter that produces smoother results with less ringing artifacts than CatmullRom
-`Linear`|Bilinear resampling filter, produces smooth output, faster than cubic filters
-`NearestNeighbor`|Fastest resampling filter, no antialiasing
-
-The default value is `Box`. You may override the default value in the [site configuration].
-
-```go-html-template
-{{ $image.Resize "600x400 Lanczos" }}
-```
-
-See [github.com/disintegration/imaging] for the complete list of resampling filters. If you wish to improve image quality at the expense of performance, you may wish to experiment with the alternative filters.
-
-## Image processing examples
-
-_The photo of the sunset used in the examples below is Copyright [Bjørn Erik Pedersen](https://bep.is) (Creative Commons Attribution-Share Alike 4.0 International license)_
-
-{{< imgproc path="sunset.jpg" spec="resize 480x" alt="A sunset" />}}
-
-{{< imgproc path="sunset.jpg" spec="fill 120x150 left" alt="A sunset" />}}
-
-{{< imgproc path="sunset.jpg" spec="fill 120x150 right" alt="A sunset" />}}
-
-{{< imgproc path="sunset.jpg" spec="fit 120x120" alt="A sunset" />}}
-
-{{< imgproc path="sunset.jpg" spec="crop 240x240 center" alt="A sunset" />}}
-
-{{< imgproc path="sunset.jpg" spec="resize 360x q10" alt="A sunset" />}}
-
-## Configuration
-
-See [configure imaging](/configuration/imaging).
-
-## Smart cropping of images
-
-By default, Hugo uses the [Smartcrop] library when cropping images with the `Crop` or `Fill` methods. You can set the anchor point manually, but in most cases the `Smart` option will make a good choice.
-
-Examples using the sunset image from above:
-
-{{< imgproc path="sunset.jpg" spec="fill 200x200 smart" alt="A sunset" />}}
-
-{{< imgproc path="sunset.jpg" spec="crop 200x200 smart" alt="A sunset" />}}
-
-## Image processing performance consideration
-
-Hugo caches processed images in the `resources` directory. If you include this directory in source control, Hugo will not have to regenerate the images in a [CI/CD](g) workflow (e.g., GitHub Pages, GitLab Pages, Netlify, etc.). This results in faster builds.
-
-If you change image processing methods or options, or if you rename or remove images, the `resources` directory will contain unused images. To remove the unused images, perform garbage collection with:
-
-```sh
-hugo --gc
-```
-
-[`anchor`]: /content-management/image-processing#anchor
-[`Colors`]: #colors
-[`Crop`]: #crop
-[`cwebp`]: https://developers.google.com/speed/webp/docs/cwebp
-[`Exif`]: #exif
-[`Fill`]: #fill
-[`Filter`]: #filter
-[`Fit`]: #fit
-[`lang.FormatNumber`]: /functions/lang/formatnumber/
-[`Process`]: #process
-[`Resize`]: #resize
-[`time.Format`]: /functions/time/format/
-[`with`]: /functions/go-template/with/
-[EXIF]: https://en.wikipedia.org/wiki/Exif
-[filters]: /functions/images/filter/#image-filters
-[github.com/disintegration/imaging]: https://github.com/disintegration/imaging#image-resizing
-[site configuration]: /configuration/imaging/
-[Smartcrop]: https://github.com/muesli/smartcrop#smartcrop
diff --git a/docs/content/en/content-management/image-processing/sunset.jpg b/docs/content/en/content-management/image-processing/sunset.jpg
deleted file mode 100644
index 4dbcc0836..000000000
Binary files a/docs/content/en/content-management/image-processing/sunset.jpg and /dev/null differ
diff --git a/docs/content/en/content-management/markdown-attributes.md b/docs/content/en/content-management/markdown-attributes.md
deleted file mode 100644
index f52a48f17..000000000
--- a/docs/content/en/content-management/markdown-attributes.md
+++ /dev/null
@@ -1,108 +0,0 @@
----
-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` you can use shorthand notation:
-
-```text
-This is a paragraph.
-{.foo .bar #baz}
-```
-
-Hugo renders both of these to:
-
-```html
-
This is a paragraph.
-```
-
-## Block elements
-
-Update your site 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}
-
-```bash {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).
diff --git a/docs/content/en/content-management/mathematics.md b/docs/content/en/content-management/mathematics.md
deleted file mode 100644
index e0c8ba4d0..000000000
--- a/docs/content/en/content-management/mathematics.md
+++ /dev/null
@@ -1,238 +0,0 @@
----
-title: Mathematics in Markdown
-linkTitle: Mathematics
-description: Include mathematical equations and expressions in Markdown using LaTeX markup.
-categories: []
-keywords: []
----
-
-{{< new-in 0.122.0 />}}
-
-## 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, with 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}
-\]
-```
-
-The MathJax display engine renders this:
-
-\[
-\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 site.
->
-> 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 site 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 site 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 will need to double-escape the `$` symbol when used outside of math contexts to avoid unintended formatting.
->
-> See the [inline delimiters](#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](#engines) section.
-
-```go-html-template {file="layouts/partials/math.html" copy=true}
-
-
-```
-
-The delimiters above must match the delimiters in your site configuration.
-
-### Step 3
-
-Conditionally call the partial template from the base template.
-
-```go-html-template {file="layouts/_default/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 site configuration.
-
-### Step 4
-
-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^*
-$$
-```
-
-If you set the `math` parameter to `false` in your site 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 >}}
-
-## 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 `$` when outside of math contexts, regardless of whether mathematical rendering is enabled on the page. For example:
-
-```text
-A \\$5 bill _saved_ is a \\$5 bill _earned_.
-```
-
-> [!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](https://github.com/KaTeX/KaTeX/issues/437).
-
-## Engines
-
-MathJax and KaTeX are open-source JavaScript display engines. Both engines are fast, but at the time of this writing MathJax v3.2.2 is slightly faster than KaTeX v0.16.11.
-
-> [!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](https://github.com/KaTeX/KaTeX/issues/437).
->
->See the [inline delimiters](#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 site 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/
-[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
diff --git a/docs/content/en/content-management/menus.md b/docs/content/en/content-management/menus.md
deleted file mode 100644
index 6d01173dc..000000000
--- a/docs/content/en/content-management/menus.md
+++ /dev/null
@@ -1,97 +0,0 @@
----
-title: Menus
-description: Create menus by defining entries, localizing each entry, and rendering the resulting data structure.
-categories: []
-keywords: []
-aliases: [/extras/menus/]
----
-
-## Overview
-
-To create a menu for your site:
-
-1. Define the menu entries
-1. [Localize](multilingual/#menus) each entry
-1. Render the menu with a [template]
-
-Create multiple menus, either flat or nested. For example, create a main menu for the header, and a separate menu for the footer.
-
-There are three ways to define menu entries:
-
-1. Automatically
-1. In front matter
-1. In site configuration
-
-> [!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.
-
-## Define automatically
-
-To automatically define a menu entry for each top-level [section](g) of your site, enable the section pages menu in your site configuration.
-
-{{< code-toggle file=hugo >}}
-sectionPagesMenu = "main"
-{{< /code-toggle >}}
-
-This creates a menu structure that you can access with `site.Menus.main` in your templates. See [menu templates] for details.
-
-## Define in front matter
-
-To add a page to the "main" menu:
-
-{{< code-toggle file=content/about.md fm=true >}}
-title = 'About'
-menus = 'main'
-{{< /code-toggle >}}
-
-Access the entry with `site.Menus.main` in your templates. See [menu templates] for details.
-
-To add a page to the "main" and "footer" menus:
-
-{{< code-toggle file=content/contact.md fm=true >}}
-title = 'Contact'
-menus = ['main','footer']
-{{< /code-toggle >}}
-
-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`.
-
-### Properties
-
-Use these properties when defining menu entries in front matter:
-
-{{% include "/_common/menu-entry-properties.md" %}}
-
-### Example
-
-This front matter menu entry demonstrates some of the available properties:
-
-{{< 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 >}}
-
-Access the entry with `site.Menus.main` in your templates. See [menu templates] for details.
-
-## Define in site configuration
-
-See [configure menus](/configuration/menus/).
-
-## Localize
-
-Hugo provides two methods to localize your menu entries. See [multilingual].
-
-## Render
-
-See [menu templates].
-
-[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
deleted file mode 100644
index d419f4381..000000000
--- a/docs/content/en/content-management/multilingual.md
+++ /dev/null
@@ -1,429 +0,0 @@
----
-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/]
----
-
-## Configuration
-
-See [configure languages](/configuration/languages/).
-
-## 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 file name
-
-Considering the following example:
-
-1. `/content/about.en.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 file name__.
-
-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.
-
-### 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` parameter.
-
-{{< code-toggle file=hugo >}}
-languages:
- en:
- weight: 10
- languageName: "English"
- contentDir: "content/english"
- fr:
- weight: 20
- languageName: "Français"
- contentDir: "content/french"
-{{< /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.
-
-Considering the following example in conjunction with the configuration above:
-
-1. `/content/english/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.
-
-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
-
-Any pages sharing the same `translationKey` set in front matter will be linked as translated pages regardless of basename or location.
-
-Considering the following example:
-
-1. `/content/about-us.en.md`
-1. `/content/om.nn.md`
-1. `/content/presentation/a-propos.fr.md`
-
-{{< code-toggle file=hugo >}}
-translationKey: "about"
-{{< /code-toggle >}}
-
-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 file names are used to handle linking, all translated pages will share the same URL (apart from the language subdirectory).
-
-To localize URLs:
-
-- 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 can have its own localized slug.
-
-{{< 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/` without affecting the translation link.
-
-### 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.).
-
-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 file name (`image.jpg`, `image.fr.jpg`) and by directory (`english/about/header.jpg`, `french/about/header.jpg`).
-
-## Reference translated content
-
-To create a list of links to translated content, use a template similar to the following:
-
-```go-html-template {file="layouts/partials/i18nlist.html"}
-{{ if .IsTranslated }}
-
-{{ end }}
-```
-
-The above can be put in a `partial` (i.e., inside `layouts/partials/`) and included in any template. 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:
-
-```go-html-template {file="layouts/partials/allLanguages.html"}
-
-```
-
-## Translation of strings
-
-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.
-
-{{< code-toggle file=hugo >}}
-defaultContentLanguage = 'en'
-
-[languages]
-[languages.en]
-contentDir = 'content/en'
-languageName = 'English'
-weight = 1
-[languages.fr]
-contentDir = 'content/fr'
-languageName = 'Français'
-weight = 2
-[languages.de]
-contentDir = 'content/de'
-languageName = 'Deutsch'
-weight = 3
-
-{{< /code-toggle >}}
-
-### Dates
-
-With this front matter:
-
-{{< code-toggle file=hugo >}}
-date = 2021-11-03T12:34:56+01:00
-{{< /code-toggle >}}
-
-And this template code:
-
-```go-html-template
-{{ .Date | time.Format ":date_full" }}
-```
-
-The rendered page displays:
-
-Language|Value
-:--|:--
-English|Wednesday, November 3, 2021
-Français|mercredi 3 novembre 2021
-Deutsch|Mittwoch, 3. November 2021
-
-See [`time.Format`] for details.
-
-### Currency
-
-With this template code:
-
-```go-html-template
-{{ 512.5032 | lang.FormatCurrency 2 "USD" }}
-```
-
-The rendered page displays:
-
-Language|Value
-:--|:--
-English|$512.50
-Français|512,50 $US
-Deutsch|512,50 $
-
-See [lang.FormatCurrency] and [lang.FormatAccounting] for details.
-
-### Numbers
-
-With this template code:
-
-```go-html-template
-{{ 512.5032 | lang.FormatNumber 2 }}
-```
-
-The rendered page displays:
-
-Language|Value
-:--|:--
-English|512.50
-Français|512,50
-Deutsch|512,50
-
-See [lang.FormatNumber] and [lang.FormatNumberCustom] for details.
-
-### Percentages
-
-With this template code:
-
-```go-html-template
-{{ 512.5032 | lang.FormatPercent 2 }}
-```
-
-The rendered page displays:
-
-Language|Value
-:--|:--
-English|512.50%
-Français|512,50 %
-Deutsch|512,50 %
-
-See [lang.FormatPercent] for details.
-
-## Menus
-
-Localization of menu entries depends on how you define them:
-
-- 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 site 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.
-
-### Create language-specific menu entries
-
-#### Method 1 -- Use a single configuration file
-
-For a simple menu with a small number of entries, use a single configuration file. For example:
-
-{{< code-toggle file=hugo >}}
-[languages.de]
-languageCode = 'de-DE'
-languageName = 'Deutsch'
-weight = 1
-
-[[languages.de.menus.main]]
-name = 'Produkte'
-pageRef = '/products'
-weight = 10
-
-[[languages.de.menus.main]]
-name = 'Leistungen'
-pageRef = '/services'
-weight = 20
-
-[languages.en]
-languageCode = 'en-US'
-languageName = 'English'
-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
-[[main]]
-name = 'Leistungen'
-pageRef = '/services'
-weight = 20
-{{< /code-toggle >}}
-
-{{< code-toggle file=config/_default/menus.en >}}
-[[main]]
-name = 'Products'
-pageRef = '/products'
-weight = 10
-[[main]]
-name = 'Services'
-pageRef = '/services'
-weight = 20
-{{< /code-toggle >}}
-
-### 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 }}
-```
-
-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 site configuration] or [in front matter], set the `identifier` property to the desired value.
-
-For example, if you define menu entries in site 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.
-
-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 `--printI18nWarnings` flag:
-
-```sh
-hugo --printI18nWarnings | grep i18n
-i18n|MISSING_TRANSLATION|en|wordCount
-```
-
-## 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`] 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
-[i18func]: /functions/lang/translate/
-[in front matter]: /content-management/menus/#define-in-front-matter
-[in site configuration]: /content-management/menus/#define-in-site-configuration
-[lang.FormatAccounting]: /functions/lang/formataccounting/
-[lang.FormatCurrency]: /functions/lang/formatcurrency/
-[lang.FormatNumber]: /functions/lang/formatnumber/
-[lang.FormatNumberCustom]: /functions/lang/formatnumbercustom/
-[lang.FormatPercent]: /functions/lang/formatpercent/
-[lang.Merge]: /functions/lang/merge/
diff --git a/docs/content/en/content-management/organization/index.md b/docs/content/en/content-management/organization/index.md
deleted file mode 100644
index a7682bfad..000000000
--- a/docs/content/en/content-management/organization/index.md
+++ /dev/null
@@ -1,151 +0,0 @@
----
-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.
-categories: []
-keywords: []
-aliases: [/content/sections/]
----
-
-## 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](/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
-```
-
-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].
-
-Without any additional configuration, the following will automatically work:
-
-```txt
-.
-└── content
- └── about
- | └── index.md // <- https://example.org/about/
- ├── posts
- | ├── firstpost.md // <- https://example.org/posts/firstpost/
- | ├── happy
- | | └── ness.md // <- https://example.org/posts/happy/ness/
- | └── secondpost.md // <- https://example.org/posts/secondpost/
- └── quote
- ├── first.md // <- https://example.org/quote/first/
- └── second.md // <- https://example.org/quote/second/
-```
-
-## 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.org/"` in your [site's configuration file][config].
-
-### Index pages: `_index.md`
-
-`_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]
-> 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 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
-. ⊢--^-⊣⊢---^---⊣
-. file path
-. ⊢------^------⊣
-content/posts/_index.md
-```
-
-At build, this will output to the following destination with the associated values:
-
-```txt
-
- url ("/posts/")
- ⊢-^-⊣
- baseurl section ("posts")
-⊢--------^---------⊣⊢-^-⊣
- permalink
-⊢----------^-------------⊣
-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 content files in each of your sections will be rendered by a [single template]. Here is an example of a single `post` within `posts`:
-
-```txt
- path ("posts/my-first-hugo-post.md")
-. ⊢-----------^------------⊣
-. section slug
-. ⊢-^-⊣⊢--------^----------⊣
-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/")
- ⊢------------^----------⊣
- baseurl section slug
-⊢--------^--------⊣⊢-^--⊣⊢-------^---------⊣
- permalink
-⊢--------------------^---------------------⊣
-https://example.org/posts/my-first-hugo-post/index.html
-```
-
-## 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.
-
-### `slug`
-
-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 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.
-
-[config]: /configuration/
-[pretty]: /content-management/urls/#appearance
-[sections]: /content-management/sections/
-[single template]: /templates/types/#single
diff --git a/docs/content/en/content-management/page-bundles.md b/docs/content/en/content-management/page-bundles.md
deleted file mode 100644
index f6a5cf771..000000000
--- a/docs/content/en/content-management/page-bundles.md
+++ /dev/null
@@ -1,145 +0,0 @@
----
-title: Page bundles
-description: Use page bundles to logically associate one or more resources with content.
-categories: []
-keywords: []
----
-
-## Introduction
-
-A page bundle is a directory that encapsulates both content and associated resources.
-
-By way of example, this site has an "about" page and a "privacy" page:
-
-```text
-content/
-├── about/
-│ ├── index.md
-│ └── welcome.jpg
-└── privacy.md
-```
-
-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.
-
-Page bundles are either _leaf bundles_ or _branch bundles_.
-
-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.
-
-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.
-
-> [!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.
-
-## Comparison
-
-Page bundle characteristics vary by bundle type.
-
-| | 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.
-
-## Leaf bundles
-
-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.
-
-```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
-```
-
-There are four leaf bundles in the example above:
-
-about
-: This leaf bundle does not contain any page resources.
-
-my-post
-: This leaf bundle contains an index file, two resources of [resource type](g) `page`, and two resources of resource type `image`.
-
- - content-1, content-2
-
- 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
-
- These are resources of resource type `image`, accessible via the `Resources` method on the `Page` object
-
-my-other-post
-: This leaf bundle does not contain any page resources.
-
-another-leaf-bundle
-: This leaf bundle does not contain any page resources.
-
-> [!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
-
-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/
-│ ├── _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
-```
-
-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.
-
-> [!note]
-> Create branch bundles at any depth within the `content` directory. Branch bundles may have descendants.
-
-## Headless bundles
-
-Use [build options] in front matter to create an unpublished leaf or branch bundle whose content and resources you can include in other pages.
-
-[`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
deleted file mode 100644
index 204ca5301..000000000
--- a/docs/content/en/content-management/page-resources.md
+++ /dev/null
@@ -1,297 +0,0 @@
----
-title: Page resources
-description: Use page resources to logically associate assets with a page.
-categories: []
-keywords: []
----
-
-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`.
-
-```text
-content
-└── post
- ├── first-post
- │ ├── images
- │ │ ├── a.jpg
- │ │ ├── b.jpg
- │ │ └── c.jpg
- │ ├── index.md (root of page bundle)
- │ ├── latest.html
- │ ├── manual.json
- │ ├── notice.md
- │ ├── office.mp3
- │ ├── pocket.mp4
- │ ├── rating.pdf
- │ └── safety.txt
- └── second-post
- └── index.md (root of page bundle)
-```
-
-## Examples
-
-Use any of these methods on a `Page` object to capture page resources:
-
- - [`Resources.ByType`]
- - [`Resources.Get`]
- - [`Resources.GetMatch`]
- - [`Resources.Match`]
-
- Once you have captured a resource, use any of the applicable [`Resource`] methods to return a value or perform an action.
-
-The following examples assume this content structure:
-
-```text
-content/
-└── example/
- ├── data/
- │ └── books.json <-- page resource
- ├── images/
- │ ├── a.jpg <-- page resource
- │ └── b.jpg <-- page resource
- ├── snippets/
- │ └── text.md <-- page resource
- └── index.md
-```
-
-Render a single image, and throw an error if the file does not exist:
-
-```go-html-template
-{{ $path := "images/a.jpg" }}
-{{ with .Resources.Get $path }}
-
-{{ else }}
- {{ errorf "Unable to get page resource %q" $path }}
-{{ end }}
-```
-
-Render all images, resized to 300 px wide:
-
-```go-html-template
-{{ range .Resources.ByType "image" }}
- {{ with .Resize "300x" }}
-
- {{ end }}
-{{ end }}
-```
-
-Render the markdown snippet:
-
-```go-html-template
-{{ with .Resources.Get "snippets/text.md" }}
- {{ .Content }}
-{{ end }}
-```
-
-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 }}
-```
-
-## 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.
-
-name
-: (`string`) Sets the value returned in `Name`.
-
-> [!note]
-> The methods `Match`, `Get` and `GetMatch` use `Name` to match the resources.
-
-title
-: (`string`) Sets the value returned in `Title`
-
-params
-: (`map`) A map of custom key-value pairs.
-
-### Resources metadata example
-
-{{< 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"
-{{ code-toggle >}}
-
-From the example above:
-
-- `sunset.jpg` will receive a new `Name` and can now be found with `.GetMatch "header"`.
-- `documents/photo_specs.pdf` will get the `photo` icon.
-- `documents/checklist.pdf`, `documents/guide.pdf` and `documents/payment.docx` will get `Title` as set by `title`.
-- Every `PDF` in the bundle except `documents/photo_specs.pdf` will get the `pdf` icon.
-- 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.
-
-> [!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`
-
-The `:counter` is a special placeholder recognized in `name` and `title` parameters `resources`.
-
-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 file=content/inspections/engine/index.md fm=true >}}
-title = 'Engine inspections'
-[[resources]]
- src = "*specs.pdf"
- title = "Specification #:counter"
-[[resources]]
- src = "**.pdf"
- name = "pdf-file-:counter"
-{{ code-toggle >}}
-
-the `Name` and `Title` will be assigned to the resource files as follows:
-
-| Resource file | `Name` | `Title` |
-|-------------------|-------------------|-----------------------|
-| 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"` |
-
-## Multilingual
-
-{{< new-in 0.123.0 />}}
-
-By default, with a multilingual single-host site, Hugo does not duplicate shared page resources when building the site.
-
-> [!note]
-> This behavior is limited to Markdown content. Shared page resources for other [content formats] are copied into each language bundle.
-
-Consider this site configuration:
-
-{{< code-toggle file=hugo >}}
-defaultContentLanguage = 'de'
-defaultContentLanguageInSubdir = true
-
-[languages.de]
-languageCode = 'de-DE'
-languageName = 'Deutsch'
-weight = 1
-
-[languages.en]
-languageCode = 'en-US'
-languageName = 'English'
-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.
-
-> [!note]
-> 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.
->
-> By default, with multilingual single-host sites, Hugo enables its [embedded link render hook] and [embedded image render hook] to resolve Markdown link and image destinations.
->
-> You may override the embedded render hooks as needed, provided they capture the resource as described above.
-
-Although duplicating shared page resources is inefficient, you can enable this feature in your site 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.Get`]: /methods/page/resources/#get
-[`Resources.GetMatch`]: /methods/page/resources#getmatch
-[`Resources.Match`]: /methods/page/resources#match
-[content formats]: /content-management/formats/
-[embedded image render hook]: /render-hooks/images/#default
-[embedded link render hook]: /render-hooks/links/#default
diff --git a/docs/content/en/content-management/related-content.md b/docs/content/en/content-management/related-content.md
deleted file mode 100644
index d7b18dab0..000000000
--- a/docs/content/en/content-management/related-content.md
+++ /dev/null
@@ -1,102 +0,0 @@
----
-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/sections.md b/docs/content/en/content-management/sections.md
deleted file mode 100644
index f7a2296f5..000000000
--- a/docs/content/en/content-management/sections.md
+++ /dev/null
@@ -1,139 +0,0 @@
----
-title: Sections
-description: Organize content into sections.
-
-categories: []
-keywords: []
-aliases: [/content/sections/]
----
-
-## 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
-```
-
-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.
-
-## Explanation
-
-Sections and non-sections behave differently.
-
-||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:
-
-With the file structure from the [example above](#overview):
-
-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 list template.
-1. All directories in the products section have list pages; each directory is a section.
-
-## Template selection
-
-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.
-
-With the file structure from the [example above](#overview):
-
-Content directory|Section template
-:--|:--
-`content/products`|`layouts/products/list.html`
-`content/products/product-1`|`layouts/products/list.html`
-`content/products/product-1/benefits`|`layouts/products/list.html`
-
-Content directory|Single template
-:--|:--
-`content/products`|`layouts/products/single.html`
-`content/products/product-1`|`layouts/products/single.html`
-`content/products/product-1/benefits`|`layouts/products/single.html`
-
-If you need to use a different template for a subsection, specify `type` and/or `layout` in front matter.
-
-## Ancestors and descendants
-
-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):
-
-```text
-content/products/product-1/benefits/benefit-1.md
-```
-
-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"}
-
-```
-
-With this CSS:
-
-```css
-.breadcrumb ol {
- padding-left: 0;
-}
-
-.breadcrumb li {
- display: inline;
-}
-
-.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
-```
-
-[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
deleted file mode 100644
index 2de387f39..000000000
--- a/docs/content/en/content-management/shortcodes.md
+++ /dev/null
@@ -1,230 +0,0 @@
----
-title: Shortcodes
-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/]
----
-
-## Introduction
-
-{{% glossary-term shortcode %}}
-
-There are three types of shortcodes: embedded, custom, and inline.
-
-## Embedded
-
-Hugo's embedded shortcodes are pre-defined templates within the application. Refer to each shortcode's documentation for specific usage instructions and available arguments.
-
-{{% list-pages-in-section path=/shortcodes %}}
-
-## Custom
-
-Create custom shortcodes to simplify and standardize content creation. For example, the following shortcode template generates an audio player using a [global resource](g):
-
-```go-html-template {file="layouts/shortcodes/audio.html"}
-{{ with resources.Get (.Get "src") }}
-
-{{ end }}
-```
-
-Then call the shortcode from within markup:
-
-```text {file="content/example.md"}
-{{* audio src=/audio/test.mp3 */>}}
-```
-
-Learn more about creating shortcodes in the [shortcode templates] section.
-
-## Inline
-
-An inline shortcode is a shortcode template defined within content.
-
-Hugo's security model is based on the premise that template and configuration authors are trusted, but content authors are not. This model enables generation of HTML output safe against code injection.
-
-To conform with this security model, creating shortcode templates within content is disabled by default. If you trust your content authors, you can enable this functionality in your site's configuration:
-
-{{< code-toggle file=hugo >}}
-[security]
-enableInlineShortcodes = true
-{{< /code-toggle >}}
-
-For more information see [configure security](/configuration/security).
-
-The following example demonstrates an inline shortcode, `date.inline`, that accepts a single positional argument: a date/time [layout string].
-
-```text {file="content/example.md"}
-Today is
-{{* date.inline ":date_medium" */>}}
- {{- now | time.Format (.Get 0) -}}
-{{* /date.inline */>}}.
-
-Today is {{* date.inline ":date_full" /*/>}}.
-```
-
-In the example above, the inline shortcode is executed twice: once upon definition and again when subsequently called. Hugo renders this to:
-
-```html
-
Today is Jan 30, 2025.
-
Today is Thursday, January 30, 2025
-```
-
-Inline shortcodes process their inner content within the same context as regular shortcode templates, allowing you to use any available [shortcode method].
-
-> [!note]
-> You cannot [nest](#nesting) inline shortcodes.
-
-Learn more about creating shortcodes in the [shortcode templates] section.
-
-## Calling
-
-Shortcode calls involve three syntactical elements: tags, arguments, and notation.
-
-### Tags
-
-Some shortcodes expect content between opening and closing tags. For example, the embedded [`details`] shortcode requires an opening and closing tag:
-
-```text
-{{* details summary="See the details" */>}}
-This is a **bold** word.
-{{* /details */>}}
-```
-
-Some shortcodes do not accept content. For example, the embedded [`instagram`] shortcode requires a single _positional_ argument:
-
-```text
-{{* instagram CxOWiQNP2MO */>}}
-```
-
-Some shortcodes optionally accept content. For example, you can call the embedded [`qr`] shortcode with content:
-
-```text
-{{* qr */>}}
-https://gohugo.io
-{{* /qr */>}}
-```
-
-Or use the self-closing syntax with a trailing slash to pass the text as an argument:
-
-```text
-{{* qr text=https://gohugo.io /*/>}}
-```
-
-Refer to each shortcode's documentation for specific usage instructions and available arguments.
-
-### Arguments
-
-Shortcode arguments can be either _named_ or _positional_.
-
-Named arguments are passed as case-sensitive key-value pairs, as seen in this example with the embedded [`figure`] shortcode. The `src` argument, for instance, is required.
-
-```text
-{{* figure src=/images/kitten.jpg */>}}
-```
-
-Positional arguments, on the other hand, are determined by their position. The embedded `instagram` shortcode, for example, expects the first argument to be the Instagram post ID.
-
-```text
-{{* instagram CxOWiQNP2MO */>}}
-```
-
-Shortcode arguments are space-delimited, and arguments with internal spaces must be quoted.
-
-```text
-{{* figure src=/images/kitten.jpg alt="A white kitten" */>}}
-```
-
-Shortcodes accept [scalar](g) arguments, one of [string](g), [integer](g), [floating point](g), or [boolean](g).
-
-```text
-{{* my-shortcode name="John Smith" age=24 married=false */>}}
-```
-
-You can optionally use multiple lines when providing several arguments to a shortcode for better readability:
-
-```text
-{{* figure
- src=/images/kitten.jpg
- alt="A white kitten"
- caption="This is a white kitten"
- loading=lazy
-*/>}}
-```
-
-Use a [raw string literal](g) if you need to pass a multiline string:
-
-```text
-{{* myshortcode `This is some HTML,
-and a new line with a "quoted string".` */>}}
-```
-
-Shortcodes can accept named arguments, positional arguments, or both, but you must use either named or positional arguments exclusively within a single shortcode call; mixing them is not allowed.
-
-Refer to each shortcode's documentation for specific usage instructions and available arguments.
-
-### Notation
-
-Shortcodes can be called using two different notations, distinguished by their tag delimiters.
-
-Notation|Example
-:--|:--
-Markdown|`{{%/* foo */%}} ## Section 1 {{%/* /foo */%}}`
-Standard|`{{* foo */>}} ## Section 2 {{* /foo */>}}`
-
-#### Markdown notation
-
-Hugo processes the shortcode before the page content is rendered by the Markdown renderer. This means, for instance, that Markdown headings inside a Markdown-notation shortcode will be included when invoking the [`TableOfContents`] method on the `Page` object.
-
-#### Standard notation
-
-With standard notation, Hugo processes the shortcode separately, merging the output into the page content after Markdown rendering. This means, for instance, that Markdown headings inside a standard-notation shortcode will be excluded when invoking the `TableOfContents` method on the `Page` object.
-
-By way of example, with this shortcode template:
-
-```go-html-template {file="layouts/shortcodes/foo.html"}
-{{ .Inner }}
-```
-
-And this markdown:
-
-```text {file="content/example.md"}
-{{%/* foo */%}} ## Section 1 {{%/* /foo */%}}
-
-{{* foo */>}} ## Section 2 {{* /foo */>}}
-```
-
-Hugo renders this HTML:
-
-```html
-
Section 1
-
-## Section 2
-```
-
-In the above, "Section 1" will be included when invoking the `TableOfContents` method, while "Section 2" will not.
-
-The shortcode author determines which notation to use. Consult each shortcode's documentation for specific usage instructions and available arguments.
-
-## Nesting
-
-Shortcodes (excluding [inline](#inline) shortcodes) can be nested, creating parent-child relationships. For example, a gallery shortcode might contain several image shortcodes:
-
-```text {file="content/example.md"}
-{{* gallery class="content-gallery" */>}}
- {{* image src="/images/a.jpg" */>}}
- {{* image src="/images/b.jpg" */>}}
- {{* image src="/images/c.jpg" */>}}
-{{* /gallery */>}}
-```
-
-The [shortcode templates][nesting] section provides a detailed explanation and examples.
-
-[`details`]: /shortcodes/details
-[`figure`]: /shortcodes/figure
-[`instagram`]: /shortcodes/instagram
-[`qr`]: /shortcodes/qr
-[`TableOfContents`]: /methods/page/tableofcontents/
-[layout string]: /functions/time/format/#layout-string
-[nesting]: /templates/shortcode/#nesting
-[shortcode method]: /templates/shortcode/#methods
-[shortcode templates]: /templates/shortcode/
diff --git a/docs/content/en/content-management/summaries.md b/docs/content/en/content-management/summaries.md
deleted file mode 100644
index da61c2c8e..000000000
--- a/docs/content/en/content-management/summaries.md
+++ /dev/null
@@ -1,123 +0,0 @@
----
-title: Content summaries
-linkTitle: Summaries
-description: Create and render content summaries.
-categories: []
-keywords: []
-aliases: [/content/summaries/,/content-management/content-summaries/]
----
-
-
-
-
-
-
-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.
-
-Review the [comparison table](#comparison) below to understand the characteristics of each summary type.
-
-## Manual summary
-
-Use a `` divider to indicate the end of the summary. Hugo will not render the summary divider itself.
-
-```text {file="content/example.md"}
-+++
-title: 'Example'
-date: 2024-05-26T09:10:33-07:00
-+++
-
-This is the first paragraph.
-
-
-
-This is the second paragraph.
-```
-
-When using the Emacs Org Mode [content format], use a `# more` divider to indicate the end of the summary.
-
-[content format]: /content-management/formats/
-
-## Front matter summary
-
-Use front matter to define a summary independent of content.
-
-```text {file="content/example.md"}
-+++
-title: 'Example'
-date: 2024-05-26T09:10:33-07:00
-summary: 'This summary is independent of the content.'
-+++
-
-This is the first paragraph.
-
-This is the second paragraph.
-```
-
-## Automatic summary
-
-If you do not define the summary manually or in front matter, Hugo automatically defines the summary based on the [`summaryLength`] in your site configuration.
-
-[`summaryLength`]: /configuration/all/#summarylength
-
-```text {file="content/example.md"}
-+++
-title: 'Example'
-date: 2024-05-26T09:10:33-07:00
-+++
-
-This is the first paragraph.
-
-This is the second paragraph.
-
-This is the third paragraph.
-```
-
-For example, with a `summaryLength` of 7, the automatic summary will be:
-
-```html
-
This is the first paragraph.
-
This is the second paragraph.
-```
-
-## Comparison
-
-Each summary type has different characteristics:
-
-Type|Precedence|Renders markdown|Renders shortcodes|Wraps single lines with `
`
-:--|:-:|:-:|:-:|:-:
-Manual|1|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:
-Front matter|2|:heavy_check_mark:|:x:|:x:
-Automatic|3|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:
-
-## Rendering
-
-Render the summary in a template by calling the [`Summary`] method on a `Page` object.
-
-[`Summary`]: /methods/page/summary
-
-```go-html-template
-{{ range site.RegularPages }}
-
- {{ .Summary }}
- {{ if .Truncated }}
- More ...
- {{ end }}
-
-{{ end }}
-```
-
-## Alternative
-
-Instead of calling the `Summary` method on a `Page` object, use the [`strings.Truncate`] function for granular control of the summary length. For example:
-
-[`strings.Truncate`]: /functions/strings/truncate/
-
-```go-html-template
-{{ range site.RegularPages }}
-
-{{ end }}
-```
-
-[`first`]: /functions/collections/first/
-[`slice`]: /functions/collections/slice/
diff --git a/docs/content/en/functions/collections/Append.md b/docs/content/en/functions/collections/Append.md
deleted file mode 100644
index cf1d1a3f5..000000000
--- a/docs/content/en/functions/collections/Append.md
+++ /dev/null
@@ -1,100 +0,0 @@
----
-title: collections.Append
-description: Appends one or more elements to a slice and returns the resulting slice.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [append]
- returnType: any
- signatures:
- - collections.Append ELEMENT [ELEMENT...] COLLECTION
- - collections.Append COLLECTION1 COLLECTION2
-aliases: [/functions/append]
----
-
-This function appends all elements, excluding the last, to the last element. This allows [pipe](g) constructs as shown below.
-
-Append a single element to a slice:
-
-```go-html-template
-{{ $s := slice "a" "b" }}
-{{ $s }} → [a b]
-
-{{ $s = $s | append "c" }}
-{{ $s }} → [a b c]
-```
-
-Append two elements to a slice:
-
-```go-html-template
-{{ $s := slice "a" "b" }}
-{{ $s }} → [a b]
-
-{{ $s = $s | append "c" "d" }}
-{{ $s }} → [a b c d]
-```
-
-Append two elements, as a slice, to a slice. This produces the same result as the previous example:
-
-```go-html-template
-{{ $s := slice "a" "b" }}
-{{ $s }} → [a b]
-
-{{ $s = $s | append (slice "c" "d") }}
-{{ $s }} → [a b c d]
-```
-
-Start with an empty slice:
-
-```go-html-template
-{{ $s := slice }}
-{{ $s }} → []
-
-{{ $s = $s | append "a" }}
-{{ $s }} → [a]
-
-{{ $s = $s | append "b" "c" }}
-{{ $s }} → [a b c]
-
-{{ $s = $s | append (slice "d" "e") }}
-{{ $s }} → [a b c d e]
-```
-
-If you start with a slice of a slice:
-
-```go-html-template
-{{ $s := slice (slice "a" "b") }}
-{{ $s }} → [[a b]]
-
-{{ $s = $s | append (slice "c" "d") }}
-{{ $s }} → [[a b] [c d]]
-```
-
-To create a slice of slices, starting with an empty slice:
-
-```go-html-template
-{{ $s := slice }}
-{{ $s }} → []
-
-{{ $s = $s | append (slice (slice "a" "b")) }}
-{{ $s }} → [[a b]]
-
-{{ $s = $s | append (slice "c" "d") }}
-{{ $s }} → [[a b] [c d]]
-```
-
-Although the elements in the examples above are strings, you can use the `append` function with any data type, including Pages. For example, on the home page of a corporate site, to display links to the two most recent press releases followed by links to the four most recent articles:
-
-```go-html-template
-{{ $p := where site.RegularPages "Type" "press-releases" | first 2 }}
-{{ $p = $p | append (where site.RegularPages "Type" "articles" | first 4) }}
-
-{{ with $p }}
-
-{{ end }}
-```
-
-The page group you get from `group` is of the same type you get from the built-in [group methods](/quick-reference/page-collections/#group) in Hugo. The example above can be [paginated](/templates/pagination/).
diff --git a/docs/content/en/functions/collections/In.md b/docs/content/en/functions/collections/In.md
deleted file mode 100644
index e94b4b0ed..000000000
--- a/docs/content/en/functions/collections/In.md
+++ /dev/null
@@ -1,34 +0,0 @@
----
-title: collections.In
-description: Reports whether the given value is a member of the given set.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [in]
- returnType: bool
- signatures: [collections.In SET VALUE]
-aliases: [/functions/in]
----
-
-The `SET` can be an [array](g), [slice](g), or [string](g).
-
-```go-html-template
-{{ $s := slice "a" "b" "c" }}
-{{ in $s "b" }} → true
-```
-
-```go-html-template
-{{ $s := slice 1 2 3 }}
-{{ in $s 2 }} → true
-```
-
-```go-html-template
-{{ $s := slice 1.11 2.22 3.33 }}
-{{ in $s 2.22 }} → true
-```
-
-```go-html-template
-{{ $s := "abc" }}
-{{ in $s "b" }} → true
-```
diff --git a/docs/content/en/functions/collections/IndexFunction.md b/docs/content/en/functions/collections/IndexFunction.md
deleted file mode 100644
index 248595961..000000000
--- a/docs/content/en/functions/collections/IndexFunction.md
+++ /dev/null
@@ -1,50 +0,0 @@
----
-title: collections.Index
-description: Returns the object, element, or value associated with the given key or keys.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [index]
- returnType: any
- signatures: [collections.Index COLLECTION KEY...]
-aliases: [/functions/index,/functions/index-function]
----
-
-Each indexed item must be a map or a slice:
-
-```go-html-template
-{{ $s := slice "a" "b" "c" }}
-{{ index $s 0 }} → a
-{{ index $s 1 }} → b
-
-{{ $m := dict "a" 100 "b" 200 }}
-{{ index $m "b" }} → 200
-```
-
-Use two or more keys to access a nested value:
-
-```go-html-template
-{{ $m := dict "a" 100 "b" 200 "c" (slice 10 20 30) }}
-{{ index $m "c" 1 }} → 20
-
-{{ $m := dict "a" 100 "b" 200 "c" (dict "d" 10 "e" 20) }}
-{{ index $m "c" "e" }} → 20
-```
-
-You may also use a slice of keys to access a nested value:
-
-```go-html-template
-{{ $m := dict "a" 100 "b" 200 "c" (dict "d" 10 "e" 20) }}
-{{ $s := slice "c" "e" }}
-{{ index $m $s }} → 20
-```
-
-Use the `collections.Index` function to access a nested value when the key is variable. For example, these are equivalent:
-
-```go-html-template
-{{ .Site.Params.foo }}
-
-{{ $k := "foo" }}
-{{ index .Site.Params $k }}
-```
diff --git a/docs/content/en/functions/collections/Intersect.md b/docs/content/en/functions/collections/Intersect.md
deleted file mode 100644
index ffa9c8196..000000000
--- a/docs/content/en/functions/collections/Intersect.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-title: collections.Intersect
-description: Returns the common elements of two arrays or slices, in the same order as the first array.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [intersect]
- returnType: any
- signatures: [collections.Intersect SET1 SET2]
-aliases: [/functions/intersect]
----
-
-A useful example is to use it as `AND` filters when combined with where:
-
-```go-html-template
-{{ $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 parameters.
-
-See [union](/functions/collections/union) for `OR`.
diff --git a/docs/content/en/functions/collections/IsSet.md b/docs/content/en/functions/collections/IsSet.md
deleted file mode 100644
index 5457df5d4..000000000
--- a/docs/content/en/functions/collections/IsSet.md
+++ /dev/null
@@ -1,42 +0,0 @@
----
-title: collections.IsSet
-description: Reports whether the key exists within the collection.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [isset]
- returnType: bool
- signatures: [collections.IsSet COLLECTION KEY]
-aliases: [/functions/isset]
----
-
-For example, consider this site configuration:
-
-{{< code-toggle file=hugo >}}
-[params]
-showHeroImage = false
-{{< /code-toggle >}}
-
-It the value of `showHeroImage` is `true`, we can detect that it exists using either `if` or `with`:
-
-```go-html-template
-{{ if site.Params.showHeroImage }}
- {{ site.Params.showHeroImage }} → true
-{{ end }}
-
-{{ with site.Params.showHeroImage }}
- {{ . }} → true
-{{ end }}
-```
-
-But if the value of `showHeroImage` is `false`, we can't use either `if` or `with` to detect its existence. In this case, you must use the `isset` function:
-
-```go-html-template
-{{ if isset site.Params "showheroimage" }}
-
The showHeroImage parameter is set to {{ site.Params.showHeroImage }}.
-{{ end }}
-```
-
-> [!note]
-> When using the `isset` function you must reference the key using lower case. See the previous example.
diff --git a/docs/content/en/functions/collections/KeyVals.md b/docs/content/en/functions/collections/KeyVals.md
deleted file mode 100644
index bd58caea0..000000000
--- a/docs/content/en/functions/collections/KeyVals.md
+++ /dev/null
@@ -1,42 +0,0 @@
----
-title: collections.KeyVals
-description: Returns a KeyVals struct.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [keyVals]
- returnType: types.KeyValues
- signatures: [collections.KeyVals KEY VALUE...]
-aliases: [/functions/keyvals]
----
-
-The primary application for this function is the definition of the `namedSlices` value in the options map passed to the [`Related`] method on the `Pages` object.
-
-[`Related`]: /methods/pages/related/
-
-See [related content](/content-management/related-content/).
-
-```go-html-template
-{{ $kv := keyVals "foo" "a" "b" "c" }}
-```
-
-The resulting data structure is:
-
-```json
-{
- "Key": "foo",
- "Values": [
- "a",
- "b",
- "c"
- ]
-}
-```
-
-To extract the key and values:
-
-```go-html-template
-{{ $kv.Key }} → foo
-{{ $kv.Values }} → [a b c]
-```
diff --git a/docs/content/en/functions/collections/Last.md b/docs/content/en/functions/collections/Last.md
deleted file mode 100644
index f0cfff219..000000000
--- a/docs/content/en/functions/collections/Last.md
+++ /dev/null
@@ -1,34 +0,0 @@
----
-title: collections.Last
-description: Returns the given collection, limited to the last N elements.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [last]
- returnType: any
- signatures: [collections.Last N COLLECTION]
-aliases: [/functions/last]
----
-
-```go-html-template
-{{ range last 10 .Pages }}
- {{ .Render "summary" }}
-{{ end }}
-```
-
-Set `N` to zero to return an empty collection.
-
-```go-html-template
-{{ $emptyPageCollection := last 0 .Pages }}
-```
-
-Use `last` and [`where`] together.
-
-[`where`]: /functions/collections/where/
-
-```go-html-template
-{{ range where .Pages "Section" "articles" | last 5 }}
- {{ .Render "summary" }}
-{{ end }}
-```
diff --git a/docs/content/en/functions/collections/Merge.md b/docs/content/en/functions/collections/Merge.md
deleted file mode 100644
index c9998be39..000000000
--- a/docs/content/en/functions/collections/Merge.md
+++ /dev/null
@@ -1,67 +0,0 @@
----
-title: collections.Merge
-description: Returns the result of merging two or more maps.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [merge]
- returnType: any
- signatures: [collections.Merge MAP MAP...]
-aliases: [/functions/merge]
----
-
-Returns the result of merging two or more maps from left to right. If a key already exists, `merge` updates its value. If a key is absent, `merge` inserts the value under the new key.
-
-Key handling is case-insensitive.
-
-The following examples use these map definitions:
-
-```go-html-template
-{{ $m1 := dict "x" "foo" }}
-{{ $m2 := dict "x" "bar" "y" "wibble" }}
-{{ $m3 := dict "x" "baz" "y" "wobble" "z" (dict "a" "huey") }}
-```
-
-Example 1
-
-```go-html-template
-{{ $merged := merge $m1 $m2 $m3 }}
-
-{{ $merged.x }} → baz
-{{ $merged.y }} → wobble
-{{ $merged.z.a }} → huey
-```
-
-Example 2
-
-```go-html-template
-{{ $merged := merge $m3 $m2 $m1 }}
-
-{{ $merged.x }} → foo
-{{ $merged.y }} → wibble
-{{ $merged.z.a }} → huey
-```
-
-Example 3
-
-```go-html-template
-{{ $merged := merge $m2 $m3 $m1 }}
-
-{{ $merged.x }} → foo
-{{ $merged.y }} → wobble
-{{ $merged.z.a }} → huey
-```
-
-Example 4
-
-```go-html-template
-{{ $merged := merge $m1 $m3 $m2 }}
-
-{{ $merged.x }} → bar
-{{ $merged.y }} → wibble
-{{ $merged.z.a }} → huey
-```
-
-> [!note]
-> Regardless of depth, merging only applies to maps. For slices, use [append](/functions/collections/append).
diff --git a/docs/content/en/functions/collections/NewScratch.md b/docs/content/en/functions/collections/NewScratch.md
deleted file mode 100644
index 34fc7f5d6..000000000
--- a/docs/content/en/functions/collections/NewScratch.md
+++ /dev/null
@@ -1,119 +0,0 @@
----
-title: collections.NewScratch
-description: Returns a locally scoped "scratch pad" to store and manipulate data.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [newScratch]
- returnType: maps.Scratch
- signatures: [collections.NewScratch ]
----
-
-Use the `collections.NewScratch` function to create a locally scoped [scratch pad](g) to store and manipulate data. To create a scratch pad with a different [scope](g), refer to the [scope](#scope) section below.
-
-## Methods
-
-### Set
-
-Sets the value of the given key.
-
-```go-html-template
-{{ $s := newScratch }}
-{{ $s.Set "greeting" "Hello" }}
-```
-
-### Get
-
-Gets the value of the given key.
-
-```go-html-template
-{{ $s := newScratch }}
-{{ $s.Set "greeting" "Hello" }}
-{{ $s.Get "greeting" }} → Hello
-```
-
-### Add
-
-Adds the 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
-{{ $s := newScratch }}
-{{ $s.Set "greeting" "Hello" }}
-{{ $s.Add "greeting" "Welcome" }}
-{{ $s.Get "greeting" }} → HelloWelcome
-```
-
-```go-html-template
-{{ $s := newScratch }}
-{{ $s.Set "total" 3 }}
-{{ $s.Add "total" 7 }}
-{{ $s.Get "total" }} → 10
-```
-
-```go-html-template
-{{ $s := newScratch }}
-{{ $s.Set "greetings" (slice "Hello") }}
-{{ $s.Add "greetings" (slice "Welcome" "Cheers") }}
-{{ $s.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
-{{ $s := newScratch }}
-{{ $s.SetInMap "greetings" "english" "Hello" }}
-{{ $s.SetInMap "greetings" "french" "Bonjour" }}
-{{ $s.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
-{{ $s := newScratch }}
-{{ $s.SetInMap "greetings" "english" "Hello" }}
-{{ $s.SetInMap "greetings" "french" "Bonjour" }}
-{{ $s.DeleteInMap "greetings" "english" }}
-{{ $s.Get "greetings" }} → map[french:Bonjour]
-```
-
-### GetSortedMapValues
-
-Returns an array of values from `key` sorted by `mapKey`.
-
-```go-html-template
-{{ $s := newScratch }}
-{{ $s.SetInMap "greetings" "english" "Hello" }}
-{{ $s.SetInMap "greetings" "french" "Bonjour" }}
-{{ $s.GetSortedMapValues "greetings" }} → [Hello Bonjour]
-```
-
-### Delete
-
-Removes the given key.
-
-```go-html-template
-{{ $s := newScratch }}
-{{ $s.Set "greeting" "Hello" }}
-{{ $s.Delete "greeting" }}
-```
-
-### Values
-
-Returns the raw backing map. Do not use with `Store` methods on a `Page` object due to concurrency issues.
-
-```go-html-template
-{{ $s := newScratch }}
-{{ $s.SetInMap "greetings" "english" "Hello" }}
-{{ $s.SetInMap "greetings" "french" "Bonjour" }}
-
-{{ $map := $s.Values }}
-```
-
-{{% include "_common/scratch-pad-scope.md" %}}
diff --git a/docs/content/en/functions/collections/Querify.md b/docs/content/en/functions/collections/Querify.md
deleted file mode 100644
index fd74935b7..000000000
--- a/docs/content/en/functions/collections/Querify.md
+++ /dev/null
@@ -1,48 +0,0 @@
----
-title: collections.Querify
-description: Returns a URL query string composed of the given key-value pairs, encoded and sorted by key.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [querify]
- returnType: string
- signatures: ['collections.Querify [VALUE...]']
-aliases: [/functions/querify]
----
-
-Specify the key-value pairs as a map, a slice, or a sequence of scalar values. For example, the following are equivalent:
-
-```go-html-template
-{{ collections.Querify (dict "a" 1 "b" 2) }}
-{{ collections.Querify (slice "a" 1 "b" 2) }}
-{{ collections.Querify "a" 1 "b" 2 }}
-```
-
-To append a query string to a URL:
-
-```go-html-template
-{{ $qs := collections.Querify (dict "a" 1 "b" 2) }}
-{{ $href := printf "https://example.org?%s" $qs }}
-
-Link
-```
-
-Hugo renders this to:
-
-```html
-Link
-```
-
-You can also pass in a map from your site configuration or front matter. For example:
-
-{{< code-toggle file=content/example.md fm=true >}}
-title = 'Example'
-[params.query]
-a = 1
-b = 2
-{{< /code-toggle >}}
-
-```go-html-template
-{{ collections.Querify .Params.query }}
-```
diff --git a/docs/content/en/functions/collections/Reverse.md b/docs/content/en/functions/collections/Reverse.md
deleted file mode 100644
index ee455939c..000000000
--- a/docs/content/en/functions/collections/Reverse.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-title: collections.Reverse
-description: Reverses the order of a collection.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: any
- signatures: [collections.Reverse COLLECTION]
-aliases: [/functions/collections.reverse]
----
-
-```go-html-template
-{{ slice 2 1 3 | collections.Reverse }} → [3 1 2]
-```
diff --git a/docs/content/en/functions/collections/Seq.md b/docs/content/en/functions/collections/Seq.md
deleted file mode 100644
index e396f07e3..000000000
--- a/docs/content/en/functions/collections/Seq.md
+++ /dev/null
@@ -1,35 +0,0 @@
----
-title: collections.Seq
-description: Returns a slice of integers.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [seq]
- returnType: '[]int'
- signatures:
- - collections.Seq LAST
- - collections.Seq FIRST LAST
- - collections.Seq FIRST INCREMENT LAST
-aliases: [/functions/seq]
----
-
-```go-html-template
-{{ seq 2 }} → [1 2]
-{{ seq 0 2 }} → [0 1 2]
-{{ seq -2 2 }} → [-2 -1 0 1 2]
-{{ seq -2 2 2 }} → [-2 0 2]
-```
-
-A contrived example of iterating over a sequence of integers:
-
-```go-html-template
-{{ $product := 1 }}
-{{ range seq 4 }}
- {{ $product = mul $product . }}
-{{ end }}
-{{ $product }} → 24
-```
-
-> [!note]
-> The slice created by the `seq` function is limited to 2000 elements.
diff --git a/docs/content/en/functions/collections/Shuffle.md b/docs/content/en/functions/collections/Shuffle.md
deleted file mode 100644
index 3a27c099a..000000000
--- a/docs/content/en/functions/collections/Shuffle.md
+++ /dev/null
@@ -1,19 +0,0 @@
----
-title: collections.Shuffle
-description: Returns a random permutation of a given array or slice.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [shuffle]
- returnType: any
- signatures: [collections.Shuffle COLLECTION]
-aliases: [/functions/shuffle]
----
-
-```go-html-template
-{{ shuffle (seq 1 2 3) }} → [3 1 2]
-{{ shuffle (slice "a" "b" "c") }} → [b a c]
-```
-
-The result will vary from one build to the next.
diff --git a/docs/content/en/functions/collections/Slice.md b/docs/content/en/functions/collections/Slice.md
deleted file mode 100644
index 76180fabe..000000000
--- a/docs/content/en/functions/collections/Slice.md
+++ /dev/null
@@ -1,23 +0,0 @@
----
-title: collections.Slice
-description: Returns a slice composed of the given values.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [slice]
- returnType: any
- signatures: ['collections.Slice [VALUE...]']
-aliases: [/functions/slice]
----
-
-```go-html-template
-{{ $s := slice "a" "b" "c" }}
-{{ $s }} → [a b c]
-```
-
-To create an empty slice:
-
-```go-html-template
-{{ $s := slice }}
-```
diff --git a/docs/content/en/functions/collections/Sort.md b/docs/content/en/functions/collections/Sort.md
deleted file mode 100644
index 67e5de5cb..000000000
--- a/docs/content/en/functions/collections/Sort.md
+++ /dev/null
@@ -1,150 +0,0 @@
----
-title: collections.Sort
-description: Sorts slices, maps, and page collections.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [sort]
- returnType: any
- signatures: ['collections.Sort COLLECTION [KEY] [ORDER]']
-aliases: [/functions/sort]
----
-
-The `KEY` is optional when sorting slices in ascending order, otherwise it is required. When sorting slices, use the literal `value` in place of the `KEY`. See examples below.
-
-The `ORDER` may be either `asc` (ascending) or `desc` (descending). The default sort order is ascending.
-
-## Sort a slice
-
-The examples below assume this site configuration:
-
-{{< code-toggle file=hugo >}}
-[params]
-grades = ['b','a','c']
-{{< /code-toggle >}}
-
-### Ascending order {#slice-ascending-order}
-
-Sort slice elements in ascending order using either of these constructs:
-
-```go-html-template
-{{ sort site.Params.grades }} → [a b c]
-{{ sort site.Params.grades "value" "asc" }} → [a b c]
-```
-
-In the examples above, `value` is the `KEY` representing the value of the slice element.
-
-### Descending order {#slice-descending-order}
-
-Sort slice elements in descending order:
-
-```go-html-template
-{{ sort site.Params.grades "value" "desc" }} → [c b a]
-```
-
-In the example above, `value` is the `KEY` representing the value of the slice element.
-
-## Sort a map
-
-The examples below assume this site configuration:
-
-{{< code-toggle file=hugo >}}
-[params.authors.a]
-firstName = "Marius"
-lastName = "Pontmercy"
-[params.authors.b]
-firstName = "Victor"
-lastName = "Hugo"
-[params.authors.c]
-firstName = "Jean"
-lastName = "Valjean"
-{{< /code-toggle >}}
-
-> [!note]
-> When sorting maps, the `KEY` argument must be lowercase.
-
-### Ascending order {#map-ascending-order}
-
-Sort map objects in ascending order using either of these constructs:
-
-```go-html-template
-{{ range sort site.Params.authors "firstname" }}
- {{ .firstName }}
-{{ end }}
-
-{{ range sort site.Params.authors "firstname" "asc" }}
- {{ .firstName }}
-{{ end }}
-```
-
-These produce:
-
-```text
-Jean Marius Victor
-```
-
-### Descending order {#map-descending-order}
-
-Sort map objects in descending order:
-
-```go-html-template
-{{ range sort site.Params.authors "firstname" "desc" }}
- {{ .firstName }}
-{{ end }}
-```
-
-This produces:
-
-```text
-Victor Marius Jean
-```
-
-### First level key removal
-
-Hugo removes the first level keys when sorting a map.
-
-Original map:
-
-```json
-{
- "felix": {
- "breed": "malicious",
- "type": "cat"
- },
- "spot": {
- "breed": "boxer",
- "type": "dog"
- }
-}
-```
-
-After sorting:
-
-```json
-[
- {
- "breed": "malicious",
- "type": "cat"
- },
- {
- "breed": "boxer",
- "type": "dog"
- }
-]
-```
-
-## Sort a page collection
-
-> [!note]
-> Although you can use the `sort` function to sort a page collection, Hugo provides [sorting and grouping methods] as well.
-
-In this contrived example, sort the site's regular pages by `.Type` in descending order:
-
-```go-html-template
-{{ range sort site.RegularPages "Type" "desc" }}
-
-{{ end }}
-```
-
-[sorting and grouping methods]: /methods/pages/
diff --git a/docs/content/en/functions/collections/SymDiff.md b/docs/content/en/functions/collections/SymDiff.md
deleted file mode 100644
index 8974d2d3e..000000000
--- a/docs/content/en/functions/collections/SymDiff.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-title: collections.SymDiff
-description: Returns the symmetric difference of two collections.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [symdiff]
- returnType: any
- signatures: [COLLECTION | collections.SymDiff COLLECTION]
-aliases: [/functions/symdiff]
----
-
-Example:
-
-```go-html-template
-{{ slice 1 2 3 | symdiff (slice 3 4) }} → [1 2 4]
-```
-
-Also see .
diff --git a/docs/content/en/functions/collections/Union.md b/docs/content/en/functions/collections/Union.md
deleted file mode 100644
index ce6d6d010..000000000
--- a/docs/content/en/functions/collections/Union.md
+++ /dev/null
@@ -1,38 +0,0 @@
----
-title: collections.Union
-description: Given two arrays or slices, returns a new array that contains the elements that belong to either or both arrays/slices.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [union]
- returnType: any
- signatures: [collections.Union SET1 SET2]
-aliases: [/functions/union]
----
-
-Given two arrays (or slices) A and B, this function will return a new array that contains the elements or objects that belong to either A or to B or to both.
-
-```go-html-template
-{{ union (slice 1 2 3) (slice 3 4 5) }} → [1 2 3 4 5]
-
-{{ union (slice 1 2 3) nil }} → [1 2 3]
-
-{{ union nil (slice 1 2 3) }} → [1 2 3]
-
-{{ union nil nil }} → []
-```
-
-## OR filter in where query
-
-This is also very useful to use as `OR` filters when combined with where:
-
-```go-html-template
-{{ $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 parameters.
-
-See [intersect](/functions/collections/intersect) for `AND`.
diff --git a/docs/content/en/functions/collections/Uniq.md b/docs/content/en/functions/collections/Uniq.md
deleted file mode 100644
index d19298b21..000000000
--- a/docs/content/en/functions/collections/Uniq.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-title: collections.Uniq
-description: Returns the given collection, removing duplicate elements.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [uniq]
- returnType: any
- signatures: [collections.Uniq COLLECTION]
-aliases: [/functions/uniq]
----
-
-```go-html-template
-{{ slice 1 3 2 1 | uniq }} → [1 3 2]
-```
diff --git a/docs/content/en/functions/collections/Where.md b/docs/content/en/functions/collections/Where.md
deleted file mode 100644
index 84fd1d21e..000000000
--- a/docs/content/en/functions/collections/Where.md
+++ /dev/null
@@ -1,419 +0,0 @@
----
-title: collections.Where
-description: Returns the given collection, removing elements that do not satisfy the comparison condition.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [where]
- returnType: any
- signatures: ['collections.Where COLLECTION KEY [OPERATOR] VALUE']
-aliases: [/functions/where]
----
-
-The `where` function returns the given collection, removing elements that do not satisfy the comparison condition. The comparison condition is composed of the `KEY`, `OPERATOR`, and `VALUE` arguments:
-
-```text
-collections.Where COLLECTION KEY [OPERATOR] VALUE
- --------------------
- comparison condition
-```
-
-Hugo will test for equality if you do not provide an `OPERATOR` argument. For example:
-
-```go-html-template
-{{ $pages := where .Site.RegularPages "Section" "books" }}
-{{ $books := where .Site.Data.books "genres" "suspense" }}
-```
-
-## Arguments
-
-The where function takes three or four arguments. The `OPERATOR` argument is optional.
-
-COLLECTION
-: (`any`) A [page collection](g) or a [slice](g) of [maps](g).
-
-KEY
-: (`string`) The key of the page or map value to compare with `VALUE`. With page collections, commonly used comparison keys are `Section`, `Type`, and `Params`. To compare with a member of the page `Params` map, [chain](g) the subkey as shown below:
-
-```go-html-template
-{{ $result := where .Site.RegularPages "Params.foo" "bar" }}
-```
-
-OPERATOR
-: (`string`) The logical comparison [operator](#operators).
-
-VALUE
-: (`any`) The value with which to compare. The values to compare must have comparable data types. For example:
-
-Comparison|Result
-:--|:--
-`"123" "eq" "123"`|`true`
-`"123" "eq" 123`|`false`
-`false "eq" "false"`|`false`
-`false "eq" false`|`true`
-
-When one or both of the values to compare is a slice, use the `in`, `not in`, or `intersect` operators as described below.
-
-## Operators
-
-Use any of the following logical operators:
-
-`=`, `==`, `eq`
-: (`bool`) Reports whether the given field value is equal to `VALUE`.
-
-`!=`, `<>`, `ne`
-: (`bool`) Reports whether the given field value is not equal to `VALUE`.
-
-`>=`, `ge`
-: (`bool`) Reports whether the given field value is greater than or equal to `VALUE`.
-
-`>`, `gt`
-: `true` Reports whether the given field value is greater than `VALUE`.
-
-`<=`, `le`
-: (`bool`) Reports whether the given field value is less than or equal to `VALUE`.
-
-`<`, `lt`
-: (`bool`) Reports whether the given field value is less than `VALUE`.
-
-`in`
-: (`bool`) Reports whether the given field value is a member of `VALUE`. Compare string to slice, or string to string. See [details](/functions/collections/in).
-
-`not in`
-: (`bool`) Reports whether the given field value is not a member of `VALUE`. Compare string to slice, or string to string. See [details](/functions/collections/in).
-
-`intersect`
-: (`bool`) Reports whether the given field value (a slice) contains one or more elements in common with `VALUE`. See [details](/functions/collections/intersect).
-
-`like`
-: (`bool`) Reports whether the given field value matches the [regular expression](g) specified in `VALUE`. Use the `like` operator to compare `string` values. The `like` operator returns `false` when comparing other data types to the regular expression.
-
-> [!note]
-> The examples below perform comparisons within a page collection, but the same comparisons are applicable to a slice of maps.
-
-## String comparison
-
-Compare the value of the given field to a [`string`](g):
-
-```go-html-template
-{{ $pages := where .Site.RegularPages "Section" "eq" "books" }}
-{{ $pages := where .Site.RegularPages "Section" "ne" "books" }}
-```
-
-## Numeric comparison
-
-Compare the value of the given field to an [`int`](g) or [`float`](g):
-
-```go-html-template
-{{ $books := where site.RegularPages "Section" "eq" "books" }}
-
-{{ $pages := where $books "Params.price" "eq" 42 }}
-{{ $pages := where $books "Params.price" "ne" 42.67 }}
-{{ $pages := where $books "Params.price" "ge" 42 }}
-{{ $pages := where $books "Params.price" "gt" 42.67 }}
-{{ $pages := where $books "Params.price" "le" 42 }}
-{{ $pages := where $books "Params.price" "lt" 42.67 }}
-```
-
-## Boolean comparison
-
-Compare the value of the given field to a [`bool`](g):
-
-```go-html-template
-{{ $books := where site.RegularPages "Section" "eq" "books" }}
-
-{{ $pages := where $books "Params.fiction" "eq" true }}
-{{ $pages := where $books "Params.fiction" "eq" false }}
-{{ $pages := where $books "Params.fiction" "ne" true }}
-{{ $pages := where $books "Params.fiction" "ne" false }}
-```
-
-## Member comparison
-
-Compare a [`scalar`](g) to a [`slice`](g).
-
-For example, to return a collection of pages where the `color` page parameter is either "red" or "yellow":
-
-```go-html-template
-{{ $fruit := where site.RegularPages "Section" "eq" "fruit" }}
-
-{{ $colors := slice "red" "yellow" }}
-{{ $pages := where $fruit "Params.color" "in" $colors }}
-```
-
-To return a collection of pages where the "color" page parameter is neither "red" nor "yellow":
-
-```go-html-template
-{{ $fruit := where site.RegularPages "Section" "eq" "fruit" }}
-
-{{ $colors := slice "red" "yellow" }}
-{{ $pages := where $fruit "Params.color" "not in" $colors }}
-```
-
-## Intersection comparison
-
-Compare a `slice` to a `slice`, returning collection elements with common values. This is frequently used when comparing taxonomy terms.
-
-For example, to return a collection of pages where any of the terms in the "genres" taxonomy are "suspense" or "romance":
-
-```go-html-template
-{{ $books := where site.RegularPages "Section" "eq" "books" }}
-
-{{ $genres := slice "suspense" "romance" }}
-{{ $pages := where $books "Params.genres" "intersect" $genres }}
-```
-
-## Regular expression comparison
-
-To return a collection of pages where the "author" page parameter begins with either "victor" or "Victor":
-
-```go-html-template
-{{ $pages := where .Site.RegularPages "Params.author" "like" `(?i)^victor` }}
-```
-
-{{% include "/_common/functions/regular-expressions.md" %}}
-
-> [!note]
-> Use the `like` operator to compare string values. Comparing other data types will result in an empty collection.
-
-## Date comparison
-
-### Predefined dates
-
-There are four predefined front matter dates: [`date`], [`publishDate`], [`lastmod`], and [`expiryDate`]. Regardless of the front matter data format (TOML, YAML, or JSON) these are [`time.Time`] values, allowing precise comparisons.
-
-For example, to return a collection of pages that were created before the current year:
-
-```go-html-template
-{{ $startOfYear := time.AsTime (printf "%d-01-01" now.Year) }}
-{{ $pages := where .Site.RegularPages "Date" "lt" $startOfYear }}
-```
-
-### Custom dates
-
-With custom front matter dates, the comparison depends on the front matter data format (TOML, YAML, or JSON).
-
-> [!note]
-> Using TOML for pages with custom front matter dates enables precise date comparisons.
-
-With TOML, date values are first-class citizens. TOML has a date data type while JSON and YAML do not. If you quote a TOML date, it is a string. If you do not quote a TOML date value, it is [`time.Time`] value, enabling precise comparisons.
-
-In the TOML example below, note that the event date is not quoted.
-
-```text {file="content/events/2024-user-conference.md"}
-+++
-title = '2024 User Conference"
-eventDate = 2024-04-01
-+++
-```
-
-To return a collection of future events:
-
-```go-html-template
-{{ $events := where .Site.RegularPages "Type" "events" }}
-{{ $futureEvents := where $events "Params.eventDate" "gt" now }}
-```
-
-When working with YAML or JSON, or quoted TOML values, custom dates are strings; you cannot compare them with `time.Time` values. String comparisons may be possible if the custom date layout is consistent from one page to the next. To be safe, filter the pages by ranging through the collection:
-
-```go-html-template
-{{ $events := where .Site.RegularPages "Type" "events" }}
-{{ $futureEvents := slice }}
-{{ range $events }}
- {{ if gt (time.AsTime .Params.eventDate) now }}
- {{ $futureEvents = $futureEvents | append . }}
- {{ end }}
-{{ end }}
-```
-
-## Nil comparison
-
-To return a collection of pages where the "color" parameter is present in front matter, compare to `nil`:
-
-```go-html-template
-{{ $pages := where .Site.RegularPages "Params.color" "ne" nil }}
-```
-
-To return a collection of pages where the "color" parameter is not present in front matter, compare to `nil`:
-
-```go-html-template
-{{ $pages := where .Site.RegularPages "Params.color" "eq" nil }}
-```
-
-In both examples above, note that `nil` is not quoted.
-
-## Nested comparison
-
-These are equivalent:
-
-```go-html-template
-{{ $pages := where .Site.RegularPages "Type" "tutorials" }}
-{{ $pages = where $pages "Params.level" "eq" "beginner" }}
-```
-
-```go-html-template
-{{ $pages := where (where .Site.RegularPages "Type" "tutorials") "Params.level" "eq" "beginner" }}
-```
-
-## Portable section comparison
-
-Useful for theme authors, avoid hardcoding section names by using the `where` function with the [`MainSections`] method on a `Site` object.
-
-```go-html-template
-{{ $pages := where .Site.RegularPages "Section" "in" .Site.MainSections }}
-```
-
-With this construct, a theme author can instruct users to specify their main sections in the site configuration:
-
-{{< code-toggle file=hugo >}}
-mainSections = ['blog','galleries']
-{{< /code-toggle >}}
-
-If `mainSections` is not defined in the site configuration, the `MainSections` method returns a slice with one element---the top-level section with the most pages.
-
-## Boolean/undefined comparison
-
-Consider this site content:
-
-```text
-content/
-├── posts/
-│ ├── _index.md
-│ ├── post-1.md <-- front matter: exclude = false
-│ ├── post-2.md <-- front matter: exclude = true
-│ └── post-3.md <-- front matter: exclude not defined
-└── _index.md
-```
-
-The first two pages have an "exclude" field in front matter, but the last page does not. When testing for _equality_, the third page is _excluded_ from the result. When testing for _inequality_, the third page is _included_ in the result.
-
-### Equality test
-
-This template:
-
-```go-html-template
-
- {{ range where .Site.RegularPages "Params.exclude" "eq" false }}
-
-```
-
-To exclude a page with an undefined field from a boolean _inequality_ test:
-
-1. Create a collection using a boolean comparison
-1. Create a collection using a nil comparison
-1. Subtract the second collection from the first collection using the [`collections.Complement`] function.
-
-This template:
-
-```go-html-template
-{{ $p1 := where .Site.RegularPages "Params.exclude" "ne" true }}
-{{ $p2 := where .Site.RegularPages "Params.exclude" "eq" nil }}
-
-```
-
-[`collections.Complement`]: /functions/collections/complement/
-[`date`]: /methods/page/date/
-[`lastmod`]: /methods/page/lastmod/
-[`MainSections`]: /methods/site/mainsections/
-[`time.Time`]: https://pkg.go.dev/time#Time
diff --git a/docs/content/en/functions/collections/_index.md b/docs/content/en/functions/collections/_index.md
deleted file mode 100644
index c7b856f4f..000000000
--- a/docs/content/en/functions/collections/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Collections functions
-linkTitle: collections
-description: Use these functions to work with arrays, slices, maps, and page collections.
-categories: []
-keywords: []
----
diff --git a/docs/content/en/functions/compare/Conditional.md b/docs/content/en/functions/compare/Conditional.md
deleted file mode 100644
index c004bf4e6..000000000
--- a/docs/content/en/functions/compare/Conditional.md
+++ /dev/null
@@ -1,31 +0,0 @@
----
-title: compare.Conditional
-description: Returns one of two arguments depending on the value of the control argument.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [cond]
- returnType: any
- signatures: [compare.Conditional CONTROL ARG1 ARG2]
-aliases: [/functions/cond]
----
-
-If CONTROL is truthy the function returns ARG1, otherwise it returns ARG2.
-
-```go-html-template
-{{ $qty := 42 }}
-{{ cond (le $qty 3) "few" "many" }} → many
-```
-
-Unlike [ternary operators] in other languages, the `compare.Conditional` function does not perform [short-circuit evaluation]. It evaluates both ARG1 and ARG2 regardless of the CONTROL value.
-
-[short-circuit evaluation]: https://en.wikipedia.org/wiki/Short-circuit_evaluation
-[ternary operators]: https://en.wikipedia.org/wiki/Ternary_conditional_operator
-
-Due to the absence of short-circuit evaluation, these examples throw an error:
-
-```go-html-template
-{{ cond true "true" (div 1 0) }}
-{{ cond false (div 1 0) "false" }}
-```
diff --git a/docs/content/en/functions/compare/Default.md b/docs/content/en/functions/compare/Default.md
deleted file mode 100644
index f8bd06f06..000000000
--- a/docs/content/en/functions/compare/Default.md
+++ /dev/null
@@ -1,47 +0,0 @@
----
-title: compare.Default
-description: Returns the second argument if set, else the first argument.
-keywords: []
-params:
- functions_and_methods:
- aliases: [default]
- returnType: any
- signatures: [compare.Default DEFAULT INPUT]
-aliases: [/functions/default]
----
-
-The `default` function returns the second argument if set, else the first argument.
-
-> [!note]
-> When the second argument is the boolean `false` value, the `default` function returns `false`. All _other_ falsy values are considered unset.
->
-> The falsy values are `false`, `0`, any `nil` pointer or interface value, any array, slice, map, or string of length zero, and zero `time.Time` values.
->
-> Everything else is truthy.
->
-> To set a default value based on truthiness, use the [`or`] operator instead.
-
-The `default` function returns the second argument if set:
-
-```go-html-template
-{{ default 42 1 }} → 1
-{{ default 42 "foo" }} → foo
-{{ default 42 (dict "k" "v") }} → map[k:v]
-{{ default 42 (slice "a" "b") }} → [a b]
-{{ default 42 true }} → true
-
-
-{{ default 42 false }} → false
-```
-
-The `default` function returns the first argument if the second argument is not set:
-
-```go-html-template
-{{ default 42 0 }} → 42
-{{ default 42 "" }} → 42
-{{ default 42 dict }} → 42
-{{ default 42 slice }} → 42
-{{ default 42 }} → 42
-```
-
-[`or`]: /functions/go-template/or/
diff --git a/docs/content/en/functions/compare/Eq.md b/docs/content/en/functions/compare/Eq.md
deleted file mode 100644
index 583e2e495..000000000
--- a/docs/content/en/functions/compare/Eq.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-title: compare.Eq
-description: Returns the boolean truth of arg1 == arg2 || arg1 == arg3.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [eq]
- returnType: bool
- signatures: ['compare.Eq ARG1 ARG2 [ARG...]']
-aliases: [/functions/eq]
----
-
-```go-html-template
-{{ eq 1 1 }} → true
-{{ eq 1 2 }} → false
-
-{{ eq 1 1 1 }} → true
-{{ eq 1 1 2 }} → true
-{{ eq 1 2 1 }} → true
-{{ eq 1 2 2 }} → false
-```
-
-You can also use the `compare.Eq` function to compare strings, boolean values, dates, slices, maps, and pages.
diff --git a/docs/content/en/functions/compare/Ge.md b/docs/content/en/functions/compare/Ge.md
deleted file mode 100644
index fd793f0c9..000000000
--- a/docs/content/en/functions/compare/Ge.md
+++ /dev/null
@@ -1,35 +0,0 @@
----
-title: compare.Ge
-description: Returns the boolean truth of arg1 >= arg2 && arg1 >= arg3.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [ge]
- returnType: bool
- signatures: ['compare.Ge ARG1 ARG2 [ARG...]']
-aliases: [/functions/ge]
----
-
-```go-html-template
-{{ ge 1 1 }} → true
-{{ ge 1 2 }} → false
-{{ ge 2 1 }} → true
-
-{{ ge 1 1 1 }} → true
-{{ ge 1 1 2 }} → false
-{{ ge 1 2 1 }} → false
-{{ ge 1 2 2 }} → false
-
-{{ ge 2 1 1 }} → true
-{{ ge 2 1 2 }} → true
-{{ ge 2 2 1 }} → true
-```
-
-Use the `compare.Ge` function to compare other data types as well:
-
-```go-html-template
-{{ ge "ab" "a" }} → true
-{{ ge time.Now (time.AsTime "1964-12-30") }} → true
-{{ ge true false }} → true
-```
diff --git a/docs/content/en/functions/compare/Gt.md b/docs/content/en/functions/compare/Gt.md
deleted file mode 100644
index f48312cf7..000000000
--- a/docs/content/en/functions/compare/Gt.md
+++ /dev/null
@@ -1,35 +0,0 @@
----
-title: compare.Gt
-description: Returns the boolean truth of arg1 > arg2 && arg1 > arg3.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [gt]
- returnType: bool
- signatures: ['compare.Gt ARG1 ARG2 [ARG...]']
-aliases: [/functions/gt]
----
-
-```go-html-template
-{{ gt 1 1 }} → false
-{{ gt 1 2 }} → false
-{{ gt 2 1 }} → true
-
-{{ gt 1 1 1 }} → false
-{{ gt 1 1 2 }} → false
-{{ gt 1 2 1 }} → false
-{{ gt 1 2 2 }} → false
-
-{{ gt 2 1 1 }} → true
-{{ gt 2 1 2 }} → false
-{{ gt 2 2 1 }} → false
-```
-
-Use the `compare.Gt` function to compare other data types as well:
-
-```go-html-template
-{{ gt "ab" "a" }} → true
-{{ gt time.Now (time.AsTime "1964-12-30") }} → true
-{{ gt true false }} → true
-```
diff --git a/docs/content/en/functions/compare/Le.md b/docs/content/en/functions/compare/Le.md
deleted file mode 100644
index b490d6807..000000000
--- a/docs/content/en/functions/compare/Le.md
+++ /dev/null
@@ -1,35 +0,0 @@
----
-title: compare.Le
-description: Returns the boolean truth of arg1 <= arg2 && arg1 <= arg3.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [le]
- returnType: bool
- signatures: ['compare.Le ARG1 ARG2 [ARG...]']
-aliases: [/functions/le]
----
-
-```go-html-template
-{{ le 1 1 }} → true
-{{ le 1 2 }} → true
-{{ le 2 1 }} → false
-
-{{ le 1 1 1 }} → true
-{{ le 1 1 2 }} → true
-{{ le 1 2 1 }} → true
-{{ le 1 2 2 }} → true
-
-{{ le 2 1 1 }} → false
-{{ le 2 1 2 }} → false
-{{ le 2 2 1 }} → false
-```
-
-Use the `compare.Le` function to compare other data types as well:
-
-```go-html-template
-{{ le "ab" "a" }} → false
-{{ le time.Now (time.AsTime "1964-12-30") }} → false
-{{ le true false }} → false
-```
diff --git a/docs/content/en/functions/compare/Lt.md b/docs/content/en/functions/compare/Lt.md
deleted file mode 100644
index a5578cc84..000000000
--- a/docs/content/en/functions/compare/Lt.md
+++ /dev/null
@@ -1,35 +0,0 @@
----
-title: compare.Lt
-description: Returns the boolean truth of arg1 < arg2 && arg1 < arg3.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [lt]
- returnType: bool
- signatures: ['compare.Lt ARG1 ARG2 [ARG...]']
-aliases: [/functions/lt]
----
-
-```go-html-template
-{{ lt 1 1 }} → false
-{{ lt 1 2 }} → true
-{{ lt 2 1 }} → false
-
-{{ lt 1 1 1 }} → false
-{{ lt 1 1 2 }} → false
-{{ lt 1 2 1 }} → false
-{{ lt 1 2 2 }} → true
-
-{{ lt 2 1 1 }} → false
-{{ lt 2 1 2 }} → false
-{{ lt 2 2 1 }} → false
-```
-
-Use the `compare.Lt` function to compare other data types as well:
-
-```go-html-template
-{{ lt "ab" "a" }} → false
-{{ lt time.Now (time.AsTime "1964-12-30") }} → false
-{{ lt true false }} → false
-```
diff --git a/docs/content/en/functions/compare/Ne.md b/docs/content/en/functions/compare/Ne.md
deleted file mode 100644
index 8839983f8..000000000
--- a/docs/content/en/functions/compare/Ne.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-title: compare.Ne
-description: Returns the boolean truth of arg1 != arg2 && arg1 != arg3.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [ne]
- returnType: bool
- signatures: ['compare.Ne ARG1 ARG2 [ARG...]']
-aliases: [/functions/ne]
----
-
-```go-html-template
-{{ ne 1 1 }} → false
-{{ ne 1 2 }} → true
-
-{{ ne 1 1 1 }} → false
-{{ ne 1 1 2 }} → false
-{{ ne 1 2 1 }} → false
-{{ ne 1 2 2 }} → true
-```
-
-You can also use the `compare.Ne` function to compare strings, boolean values, dates, slices, maps, and pages.
diff --git a/docs/content/en/functions/compare/_index.md b/docs/content/en/functions/compare/_index.md
deleted file mode 100644
index 59673a2c8..000000000
--- a/docs/content/en/functions/compare/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Compare functions
-linkTitle: compare
-description: Use these functions to compare two or more values.
-categories: []
-keywords: []
----
diff --git a/docs/content/en/functions/crypto/FNV32a.md b/docs/content/en/functions/crypto/FNV32a.md
deleted file mode 100644
index 03bcc57e7..000000000
--- a/docs/content/en/functions/crypto/FNV32a.md
+++ /dev/null
@@ -1,18 +0,0 @@
----
-title: crypto.FNV32a
-description: Returns the 32-bit FNV (Fowler-Noll-Vo) non-cryptographic hash of the given string.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: int
- signatures: [crypto.FNV32a STRING]
-expiryDate: 2026-07-31 # deprecated 2024-07-31 in v0.129.0
----
-
-{{< deprecated-in 0.129.0 >}}
-Use [`hash.FNV32a`] instead.
-
-[`hash.FNV32a`]: /functions/hash/FNV32a/
-{{< /deprecated-in >}}
diff --git a/docs/content/en/functions/crypto/HMAC.md b/docs/content/en/functions/crypto/HMAC.md
deleted file mode 100644
index 5929826dd..000000000
--- a/docs/content/en/functions/crypto/HMAC.md
+++ /dev/null
@@ -1,27 +0,0 @@
----
-title: crypto.HMAC
-description: Returns a cryptographic hash that uses a key to sign a message.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [hmac]
- returnType: string
- signatures: ['crypto.HMAC HASH_TYPE KEY MESSAGE [ENCODING]']
-aliases: [/functions/hmac]
----
-
-Set the `HASH_TYPE` argument to `md5`, `sha1`, `sha256`, or `sha512`.
-
-Set the optional `ENCODING` argument to either `hex` (default) or `binary`.
-
-```go-html-template
-{{ hmac "sha256" "Secret key" "Secret message" }}
-5cceb491f45f8b154e20f3b0a30ed3a6ff3027d373f85c78ffe8983180b03c84
-
-{{ hmac "sha256" "Secret key" "Secret message" "hex" }}
-5cceb491f45f8b154e20f3b0a30ed3a6ff3027d373f85c78ffe8983180b03c84
-
-{{ hmac "sha256" "Secret key" "Secret message" "binary" | base64Encode }}
-XM60kfRfixVOIPOwow7Tpv8wJ9Nz+Fx4/+iYMYCwPIQ=
-```
diff --git a/docs/content/en/functions/crypto/MD5.md b/docs/content/en/functions/crypto/MD5.md
deleted file mode 100644
index 89bb8cc1b..000000000
--- a/docs/content/en/functions/crypto/MD5.md
+++ /dev/null
@@ -1,22 +0,0 @@
----
-title: crypto.MD5
-description: Hashes the given input and returns its MD5 checksum encoded to a hexadecimal string.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [md5]
- returnType: string
- signatures: [crypto.MD5 INPUT]
-aliases: [/functions/md5]
----
-
-```go-html-template
-{{ md5 "Hello world" }} → 3e25960a79dbc69b674cd4ec67a72c62
-```
-
-This can be useful if you want to use [Gravatar](https://en.gravatar.com/) for generating a unique avatar:
-
-```html
-
-```
diff --git a/docs/content/en/functions/crypto/SHA1.md b/docs/content/en/functions/crypto/SHA1.md
deleted file mode 100644
index c80dac0a4..000000000
--- a/docs/content/en/functions/crypto/SHA1.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-title: crypto.SHA1
-description: Hashes the given input and returns its SHA1 checksum encoded to a hexadecimal string.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [sha1]
- returnType: string
- signatures: [crypto.SHA1 INPUT]
-aliases: [/functions/sha,/functions/sha1]
----
-
-```go-html-template
-{{ sha1 "Hello world" }} → 7b502c3a1f48c8609ae212cdfb639dee39673f5e
-```
diff --git a/docs/content/en/functions/crypto/SHA256.md b/docs/content/en/functions/crypto/SHA256.md
deleted file mode 100644
index d0a66c069..000000000
--- a/docs/content/en/functions/crypto/SHA256.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-title: crypto.SHA256
-description: Hashes the given input and returns its SHA256 checksum encoded to a hexadecimal string.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [sha256]
- returnType: string
- signatures: [crypto.SHA256 INPUT]
-aliases: [/functions/sha256]
----
-
-```go-html-template
-{{ sha256 "Hello world" }} → 64ec88ca00b268e5ba1a35678a1b5316d212f4f366b2477232534a8aeca37f3c
-```
diff --git a/docs/content/en/functions/crypto/_index.md b/docs/content/en/functions/crypto/_index.md
deleted file mode 100644
index 5771630d4..000000000
--- a/docs/content/en/functions/crypto/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Crypto functions
-linkTitle: crypto
-description: Use these functions to create cryptographic hashes.
-categories: []
-keywords: []
----
diff --git a/docs/content/en/functions/css/PostCSS.md b/docs/content/en/functions/css/PostCSS.md
deleted file mode 100644
index 9cc698248..000000000
--- a/docs/content/en/functions/css/PostCSS.md
+++ /dev/null
@@ -1,128 +0,0 @@
----
-title: css.PostCSS
-description: Processes the given resource with PostCSS using any PostCSS plugin.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [postCSS]
- returnType: resource.Resource
- signatures: ['css.PostCSS [OPTIONS] RESOURCE']
----
-
-{{< new-in 0.128.0 />}}
-
-```go-html-template
-{{ with resources.Get "css/main.css" | postCSS }}
-
-{{ end }}
-```
-
-## Setup
-
-Follow the steps below to transform CSS using any of the available [PostCSS plugins].
-
-### Step 1
-
-Install [Node.js].
-
-### Step 2
-
-Install the required Node.js packages in the root of your project. For example, to add vendor prefixes to your CSS rules:
-
-```sh
-npm i -D postcss postcss-cli autoprefixer
-```
-
-### Step 3
-
-Create a PostCSS configuration file in the root of your project.
-
-```js {file="postcss.config.js"}
-module.exports = {
- plugins: [
- require('autoprefixer')
- ]
-};
-```
-
-> [!note]
-> If you are a Windows user, and the path to your project contains a space, you must place the PostCSS configuration within the package.json file. See [this example] and issue [#7333].
-
-### Step 4
-
-Place your CSS file within the `assets/css` directory.
-
-### Step 5
-
-Process the resource with PostCSS:
-
-```go-html-template
-{{ with resources.Get "css/main.css" | postCSS }}
-
-{{ end }}
-```
-
-## Options
-
-The `css.PostCSS` method takes an optional map of options.
-
-config
-: (`string`) The directory that contains the PostCSS configuration file. Default is the root of the project directory.
-
-noMap
-: (`bool`) Whether to disable inline source maps. Default is `false`.
-
-inlineImports
-: (`bool`) Whether to 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. Default is `false`.
-
-skipInlineImportsNotFound
-: (`bool`) Whether to allow the build process to continue despite unresolved import statements, preserving the original import declarations. If you have regular CSS imports in your CSS that you want to preserve, you can either use imports with URL or media queries (Hugo does not try to resolve those) or set this option to `true`. Default is `false`."
-
-```go-html-template
-{{ $opts := dict "config" "config-directory" "noMap" true }}
-{{ with resources.Get "css/main.css" | postCSS $opts }}
-
-{{ end }}
-```
-
-## No configuration file
-
-To avoid using a PostCSS configuration file, you can specify a minimal configuration using the options map.
-
-use
-: (`string`) A space-delimited list of PostCSS plugins to use.
-
-parser
-: (`string`) A custom PostCSS parser.
-
-stringifier
-: (`string`) A custom PostCSS stringifier.
-
-syntax
-: (`string`) Custom postcss syntax.
-
-```go-html-template
-{{ $opts := dict "use" "autoprefixer postcss-color-alpha" }}
-{{ with resources.Get "css/main.css" | postCSS $opts }}
-
-{{ end }}
-```
-
-## Check environment
-
-The current Hugo environment name (set by `--environment` or in configuration or OS environment) is available in the Node context, which allows constructs like this:
-
-```js
-const autoprefixer = require('autoprefixer');
-module.exports = {
- plugins: [
- process.env.HUGO_ENVIRONMENT !== 'development' ? autoprefixer : null
- ]
-}
-```
-
-[#7333]: https://github.com/gohugoio/hugo/issues/7333
-[Node.js]: https://nodejs.org/en
-[PostCSS plugins]: https://postcss.org/docs/postcss-plugins
-[this example]: https://github.com/postcss/postcss-load-config#packagejson
diff --git a/docs/content/en/functions/css/Sass.md b/docs/content/en/functions/css/Sass.md
deleted file mode 100644
index 03a4c7451..000000000
--- a/docs/content/en/functions/css/Sass.md
+++ /dev/null
@@ -1,204 +0,0 @@
----
-title: css.Sass
-description: Transpiles Sass to CSS.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [toCSS]
- returnType: resource.Resource
- signatures: ['css.Sass [OPTIONS] RESOURCE']
----
-
-{{< new-in 0.128.0 />}}
-
-Transpile Sass to CSS using the LibSass transpiler included in Hugo's extended and extended/deploy editions, or [install Dart Sass](#dart-sass) to use the latest features of the Sass language.
-
-Sass has two forms of syntax: [SCSS] and [indented]. Hugo supports both.
-
-[scss]: https://sass-lang.com/documentation/syntax#scss
-[indented]: https://sass-lang.com/documentation/syntax#the-indented-syntax
-
-## Options
-
-enableSourceMap
-: (`bool`) Whether to generate a source map. Default is `false`.
-
-includePaths
-: (`slice`) A slice of paths, relative to the project root, that the transpiler will use when resolving `@use` and `@import` statements.
-
-outputStyle
-: (`string`) The output style of the resulting CSS. With LibSass, one of `nested` (default), `expanded`, `compact`, or `compressed`. With Dart Sass, either `expanded` (default) or `compressed`.
-
-precision
-: (`int`) The precision of floating point math. Applicable to LibSass. Default is `8`.
-
-silenceDeprecations
-: {{< new-in 0.139.0 />}}
-: (`slice`) A slice of deprecation IDs to silence. IDs are enclosed in brackets within Dart Sass warning messages (e.g., `import` in `WARN Dart Sass: DEPRECATED [import]`). Applicable to Dart Sass. Default is `false`.
-
-silenceDependencyDeprecations
-: {{< new-in 0.146.0 />}}
-: (`bool`) Whether to silence deprecation warnings from dependencies, where a dependency is considered any file transitively imported through a load path. This does not apply to `@warn` or `@debug` rules.Default is `false`.
-
-sourceMapIncludeSources
-: (`bool`) Whether to embed sources in the generated source map. Applicable to Dart Sass. Default is `false`.
-
-targetPath
-: (`string`) The publish path for the transformed resource, relative to the[`publishDir`]. If unset, the target path defaults to the asset's original path with a `.css` extension.
-
-transpiler
-: (`string`) The transpiler to use, either `libsass` or `dartsass`. Hugo's extended and extended/deploy editions include the LibSass transpiler. To use the Dart Sass transpiler, see the [installation instructions](#dart-sass). Default is `libsass`.
-
-vars
-: (`map`) A map of key-value pairs that will be available in the `hugo:vars` namespace. Useful for [initializing Sass variables from Hugo templates](https://discourse.gohugo.io/t/42053/).
-
- ```scss
- // LibSass
- @import "hugo:vars";
-
- // Dart Sass
- @use "hugo:vars" as v;
- ```
-
-## Example
-
-```go-html-template {copy=true}
-{{ with resources.Get "sass/main.scss" }}
- {{ $opts := dict
- "enableSourceMap" (not hugo.IsProduction)
- "outputStyle" (cond hugo.IsProduction "compressed" "expanded")
- "targetPath" "css/main.css"
- "transpiler" "dartsass"
- "vars" site.Params.styles
- "includePaths" (slice "node_modules/bootstrap/scss")
- }}
- {{ with . | toCSS $opts }}
- {{ if hugo.IsProduction }}
- {{ with . | fingerprint }}
-
- {{ end }}
- {{ else }}
-
- {{ end }}
- {{ end }}
-{{ end }}
-```
-
-## Dart Sass
-
-Hugo's extended and extended/deploy editions include [LibSass] to transpile Sass to CSS. In 2020, the Sass team deprecated LibSass in favor of [Dart Sass].
-
-Use the latest features of the Sass language by installing Dart Sass in your development and production environments.
-
-### Installation overview
-
-Dart Sass is compatible with Hugo v0.114.0 and later.
-
-If you have been using Embedded Dart Sass[^1] with Hugo v0.113.0 and earlier, uninstall Embedded Dart Sass, then install Dart Sass. If you have installed both, Hugo will use Dart Sass.
-
-If you install Hugo as a [Snap package] there is no need to install Dart Sass. The Hugo Snap package includes Dart Sass.
-
-[^1]: In 2023, the Sass team deprecated Embedded Dart Sass in favor of Dart Sass.
-
-### Installing in a development environment
-
-When you install Dart Sass somewhere in your PATH, Hugo will find it.
-
-OS|Package manager|Site|Installation
-:--|:--|:--|:--
-Linux|Homebrew|[brew.sh]|`brew install sass/sass/sass`
-Linux|Snap|[snapcraft.io]|`sudo snap install dart-sass`
-macOS|Homebrew|[brew.sh]|`brew install sass/sass/sass`
-Windows|Chocolatey|[chocolatey.org]|`choco install sass`
-Windows|Scoop|[scoop.sh]|`scoop install sass`
-
-You may also install [prebuilt binaries] for Linux, macOS, and Windows.
-
-Run `hugo env` to list the active transpilers.
-
-> [!note]
-> If you build Hugo from source and run `mage test -v`, the test will fail if you install Dart Sass as a Snap package. This is due to the Snap package's strict confinement model.
-
-### Installing in a production environment
-
-For [CI/CD](g) deployments (e.g., GitHub Pages, GitLab Pages, Netlify, etc.) you must edit the workflow to install Dart Sass before Hugo builds the site[^2]. Some providers allow you to use one of the package managers above, or you can download and extract one of the prebuilt binaries.
-
-[^2]: You do not have to do this if (a) you have not modified the assets cache location, and (b) you have not set `useResourceCacheWhen` to `never` in your [site configuration], and (c) you add and commit your `resources` directory to your repository.
-
-#### GitHub Pages
-
-To install Dart Sass for your builds on GitHub Pages, add this step to the GitHub Pages workflow file:
-
-```yaml
-- name: Install Dart Sass
- run: sudo snap install dart-sass
-```
-
-#### GitLab Pages
-
-To install Dart Sass for your builds on GitLab Pages, the `.gitlab-ci.yml` file should look something like this:
-
-```yaml
-variables:
- HUGO_VERSION: 0.144.2
- DART_SASS_VERSION: 1.85.0
- GIT_DEPTH: 0
- GIT_STRATEGY: clone
- GIT_SUBMODULE_STRATEGY: recursive
- TZ: America/Los_Angeles
-image:
- name: golang:1.20-buster
-pages:
- script:
- # Install Dart Sass
- - curl -LJO https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz
- - tar -xf dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz
- - cp -r dart-sass/* /usr/local/bin
- - rm -rf dart-sass*
- # Install Hugo
- - curl -LJO https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb
- - apt install -y ./hugo_extended_${HUGO_VERSION}_linux-amd64.deb
- - rm hugo_extended_${HUGO_VERSION}_linux-amd64.deb
- # Build
- - hugo --gc --minify
- artifacts:
- paths:
- - public
- rules:
- - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
-```
-
-#### Netlify
-
-To install Dart Sass for your builds on Netlify, the `netlify.toml` file should look something like this:
-
-```toml
-[build.environment]
-HUGO_VERSION = "0.144.2"
-DART_SASS_VERSION = "1.85.0"
-NODE_VERSION = "22"
-TZ = "America/Los_Angeles"
-
-[build]
-publish = "public"
-command = """\
- curl -LJO https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz && \
- tar -xf dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz && \
- rm dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz && \
- export PATH=/opt/build/repo/dart-sass:$PATH && \
- hugo --gc --minify \
- """
-```
-
-[brew.sh]: https://brew.sh/
-[chocolatey.org]: https://community.chocolatey.org/packages/sass
-[dart sass]: https://sass-lang.com/dart-sass
-[libsass]: https://sass-lang.com/libsass
-[prebuilt binaries]: https://github.com/sass/dart-sass/releases/latest
-[scoop.sh]: https://scoop.sh/#/apps?q=sass
-[site configuration]: /configuration/build/
-[snap package]: /installation/linux/#snap
-[snapcraft.io]: https://snapcraft.io/dart-sass
-[starter workflow]: https://github.com/actions/starter-workflows/blob/main/pages/hugo.yml
-[`publishDir`]: /configuration/all/#publishdir
diff --git a/docs/content/en/functions/css/TailwindCSS.md b/docs/content/en/functions/css/TailwindCSS.md
deleted file mode 100644
index 6add7373a..000000000
--- a/docs/content/en/functions/css/TailwindCSS.md
+++ /dev/null
@@ -1,138 +0,0 @@
----
-title: css.TailwindCSS
-description: Processes the given resource with the Tailwind CSS CLI.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: resource.Resource
- signatures: ['css.TailwindCSS [OPTIONS] RESOURCE']
----
-
-{{< new-in 0.128.0 />}}
-
-Use the `css.TailwindCSS` function to process your Tailwind CSS files. This function uses the Tailwind CSS CLI to:
-
-1. Scan your templates for Tailwind CSS utility class usage.
-1. Compile those utility classes into standard CSS.
-1. Generate an optimized CSS output file.
-
-> [!caution]
-> Tailwind CSS v4.0 and later requires a relatively [modern browser](https://tailwindcss.com/docs/compatibility#browser-support) to render correctly.
-
-## Setup
-
-### Step 1
-
-Install the Tailwind CSS CLI v4.0 or later:
-
-```sh
-npm install --save-dev tailwindcss @tailwindcss/cli
-```
-
-The TailwindCSS CLI is also available as a [standalone executable] if you want to use it without installing Node.js.
-
-[standalone executable]: https://github.com/tailwindlabs/tailwindcss/releases/latest
-
-### Step 2
-
-Add this to your site configuration:
-
-{{< code-toggle file=hugo copy=true >}}
-[[module.mounts]]
-source = "assets"
-target = "assets"
-[[module.mounts]]
-source = "hugo_stats.json"
-target = "assets/notwatching/hugo_stats.json"
-disableWatch = true
-[build.buildStats]
-enable = true
-[[build.cachebusters]]
-source = "assets/notwatching/hugo_stats\\.json"
-target = "css"
-[[build.cachebusters]]
-source = "(postcss|tailwind)\\.config\\.js"
-target = "css"
-{{< /code-toggle >}}
-
-### Step 3
-
-Create a CSS entry file:
-
-```css {file="assets/css/main.css" copy=true}
-@import "tailwindcss";
-@source "hugo_stats.json";
-```
-
-Tailwind CSS respects `.gitignore` files. This means that if `hugo_stats.json` is listed in your `.gitignore` file, Tailwind CSS will ignore it. To make `hugo_stats.json` available to Tailwind CSS you must explicitly source it as shown in the example above.
-
-### Step 4
-
-Create a partial template to process the CSS with the Tailwind CSS CLI:
-
-```go-html-template {file="layouts/partials/css.html" copy=true}
-{{ with (templates.Defer (dict "key" "global")) }}
- {{ with resources.Get "css/main.css" }}
- {{ $opts := dict
- "minify" hugo.IsProduction
- "inlineImports" true
- }}
- {{ with . | css.TailwindCSS $opts }}
- {{ if hugo.IsProduction }}
- {{ with . | fingerprint }}
-
- {{ end }}
- {{ else }}
-
- {{ end }}
- {{ end }}
- {{ end }}
-{{ end }}
-```
-
-### Step 5
-
-Call the partial template from your base template:
-
-```go-html-template {file="layouts/_default/baseof.html"}
-
- ...
- {{ partialCached "css.html" . }}
- ...
-
-```
-
-### Step 6
-
-Optionally create a `tailwind.config.js` file in the root of your project as shown below. This is necessary if you use the [Tailwind CSS IntelliSense
-extension] for Visual Studio Code.
-
-[Tailwind CSS IntelliSense
-extension]: https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss
-
-```js {file="tailwind.config.js" copy=true}
-/*
-This file is present to satisfy a requirement of the Tailwind CSS IntelliSense
-extension for Visual Studio Code.
-
-https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss
-
-The rest of this file is intentionally empty.
-*/
-```
-
-## Options
-
-minify
-: (`bool`) Whether to optimize and minify the output. Default is `false`.
-
-optimize
-: (`bool`) Whether to optimize the output without minifying. Default is `false`.
-
-inlineImports
-: (`bool`) Whether to enable inlining of `@import` statements. Inlining is performed recursively, but currently once only per file. It is not possible to import the same file in different scopes (root, media query, etc.). Note that this import routine does not care about the CSS specification, so you can have `@import` statements anywhere in the file. Default is `false`.
-
-skipInlineImportsNotFound
-: (`bool`) Whether to allow the build process to continue despite unresolved import statements, preserving the original import declarations. It is important to note that the inline importer does not process URL-based imports or those with media queries, and these will remain unaltered even when this option is disabled. Default is `false`.
diff --git a/docs/content/en/functions/css/_index.md b/docs/content/en/functions/css/_index.md
deleted file mode 100644
index 9faabbbe9..000000000
--- a/docs/content/en/functions/css/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: CSS functions
-linkTitle: css
-description: Use these functions to work with CSS and Sass files.
-categories: []
-keywords: []
----
diff --git a/docs/content/en/functions/data/GetCSV.md b/docs/content/en/functions/data/GetCSV.md
deleted file mode 100644
index 39c71b06c..000000000
--- a/docs/content/en/functions/data/GetCSV.md
+++ /dev/null
@@ -1,144 +0,0 @@
----
-title: data.GetCSV
-description: Returns an array of arrays from a local or remote CSV file, or an error if the file does not exist.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [getCSV]
- returnType: '[][]string'
- signatures: ['data.GetCSV SEPARATOR INPUT... [OPTIONS]']
-expiryDate: 2026-02-19 # deprecated 2024-02-19 in v0.123.0
----
-
-{{< deprecated-in 0.123.0 >}}
-Instead, use [`transform.Unmarshal`] with a [global resource](g), [page resource](g), or [remote resource](g).
-
-See the [remote data example].
-
-[`transform.Unmarshal`]: /functions/transform/unmarshal/
-[remote data example]: /functions/resources/getremote/#remote-data
-{{< /deprecated-in >}}
-
-Given the following directory structure:
-
-```text
-my-project/
-└── other-files/
- └── pets.csv
-```
-
-Access the data with either of the following:
-
-```go-html-template
-{{ $data := getCSV "," "other-files/pets.csv" }}
-{{ $data := getCSV "," "other-files/" "pets.csv" }}
-```
-
-> [!note]
-> When working with local data, the file path is relative to the working directory.
->
-> You must not place CSV files in the project's `data` directory.
-
-Access remote data with either of the following:
-
-```go-html-template
-{{ $data := getCSV "," "https://example.org/pets.csv" }}
-{{ $data := getCSV "," "https://example.org/" "pets.csv" }}
-```
-
-The resulting data structure is an array of arrays:
-
-```json
-[
- ["name","type","breed","age"],
- ["Spot","dog","Collie","3"],
- ["Felix","cat","Malicious","7"]
-]
-```
-
-## Options
-
-Add headers to the request by providing an options map:
-
-```go-html-template
-{{ $opts := dict "Authorization" "Bearer abcd" }}
-{{ $data := getCSV "," "https://example.org/pets.csv" $opts }}
-```
-
-Add multiple headers using a slice:
-
-```go-html-template
-{{ $opts := dict "X-List" (slice "a" "b" "c") }}
-{{ $data := getCSV "," "https://example.org/pets.csv" $opts }}
-```
-
-## Global resource alternative
-
-Consider using the [`resources.Get`] function with [`transform.Unmarshal`] when accessing a global resource.
-
-```text
-my-project/
-└── assets/
- └── data/
- └── pets.csv
-```
-
-```go-html-template
-{{ $data := dict }}
-{{ $p := "data/pets.csv" }}
-{{ with resources.Get $p }}
- {{ $opts := dict "delimiter" "," }}
- {{ $data = . | transform.Unmarshal $opts }}
-{{ else }}
- {{ errorf "Unable to get resource %q" $p }}
-{{ end }}
-```
-
-## Page resource alternative
-
-Consider using the [`Resources.Get`] method with [`transform.Unmarshal`] when accessing a page resource.
-
-```text
-my-project/
-└── content/
- └── posts/
- └── my-pets/
- ├── index.md
- └── pets.csv
-```
-
-```go-html-template
-{{ $data := dict }}
-{{ $p := "pets.csv" }}
-{{ with .Resources.Get $p }}
- {{ $opts := dict "delimiter" "," }}
- {{ $data = . | transform.Unmarshal $opts }}
-{{ else }}
- {{ errorf "Unable to get resource %q" $p }}
-{{ end }}
-```
-
-## Remote resource alternative
-
-Consider using the [`resources.GetRemote`] function with [`transform.Unmarshal`] when accessing a remote resource to improve error handling and cache control.
-
-```go-html-template
-{{ $data := dict }}
-{{ $url := "https://example.org/pets.csv" }}
-{{ with try (resources.GetRemote $url) }}
- {{ with .Err }}
- {{ errorf "%s" . }}
- {{ else with .Value }}
- {{ $opts := dict "delimiter" "," }}
- {{ $data = . | transform.Unmarshal $opts }}
- {{ else }}
- {{ errorf "Unable to get remote resource %q" $url }}
- {{ end }}
-{{ end }}
-```
-
-[`Resources.Get`]: /methods/page/resources/
-[`resources.GetRemote`]: /functions/resources/getremote/
-[`resources.Get`]: /functions/resources/get/
-[`transform.Unmarshal`]: /functions/transform/unmarshal/
diff --git a/docs/content/en/functions/data/GetJSON.md b/docs/content/en/functions/data/GetJSON.md
deleted file mode 100644
index 9cdea9287..000000000
--- a/docs/content/en/functions/data/GetJSON.md
+++ /dev/null
@@ -1,146 +0,0 @@
----
-title: data.GetJSON
-description: Returns a JSON object from a local or remote JSON file, or an error if the file does not exist.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [getJSON]
- returnType: any
- signatures: ['data.GetJSON INPUT... [OPTIONS]']
-expiryDate: 2026-02-19 # deprecated 2024-02-19 in v0.123.0
----
-
-{{< deprecated-in 0.123.0 >}}
-Instead, use [`transform.Unmarshal`] with a [global resource](g), [page resource](g), or [remote resource](g).
-
-See the [remote data example].
-
-[`transform.Unmarshal`]: /functions/transform/unmarshal/
-[remote data example]: /functions/resources/getremote/#remote-data
-{{< /deprecated-in >}}
-
-Given the following directory structure:
-
-```text
-my-project/
-└── other-files/
- └── books.json
-```
-
-Access the data with either of the following:
-
-```go-html-template
-{{ $data := getJSON "other-files/books.json" }}
-{{ $data := getJSON "other-files/" "books.json" }}
-```
-
-> [!note]
-> When working with local data, the file path is relative to the working directory.
-
-Access remote data with either of the following:
-
-```go-html-template
-{{ $data := getJSON "https://example.org/books.json" }}
-{{ $data := getJSON "https://example.org/" "books.json" }}
-```
-
-The resulting data structure is a JSON object:
-
-```json
-[
- {
- "author": "Victor Hugo",
- "rating": 5,
- "title": "Les Misérables"
- },
- {
- "author": "Victor Hugo",
- "rating": 4,
- "title": "The Hunchback of Notre Dame"
- }
-]
-```
-
-## Options
-
-Add headers to the request by providing an options map:
-
-```go-html-template
-{{ $opts := dict "Authorization" "Bearer abcd" }}
-{{ $data := getJSON "https://example.org/books.json" $opts }}
-```
-
-Add multiple headers using a slice:
-
-```go-html-template
-{{ $opts := dict "X-List" (slice "a" "b" "c") }}
-{{ $data := getJSON "https://example.org/books.json" $opts }}
-```
-
-## Global resource alternative
-
-Consider using the [`resources.Get`] function with [`transform.Unmarshal`] when accessing a global resource.
-
-```text
-my-project/
-└── assets/
- └── data/
- └── books.json
-```
-
-```go-html-template
-{{ $data := dict }}
-{{ $p := "data/books.json" }}
-{{ with resources.Get $p }}
- {{ $data = . | transform.Unmarshal }}
-{{ else }}
- {{ errorf "Unable to get resource %q" $p }}
-{{ end }}
-```
-
-## Page resource alternative
-
-Consider using the [`Resources.Get`] method with [`transform.Unmarshal`] when accessing a page resource.
-
-```text
-my-project/
-└── content/
- └── posts/
- └── reading-list/
- ├── books.json
- └── index.md
-```
-
-```go-html-template
-{{ $data := dict }}
-{{ $p := "books.json" }}
-{{ with .Resources.Get $p }}
- {{ $data = . | transform.Unmarshal }}
-{{ else }}
- {{ errorf "Unable to get resource %q" $p }}
-{{ end }}
-```
-
-## Remote resource alternative
-
-Consider using the [`resources.GetRemote`] function with [`transform.Unmarshal`] when accessing a remote resource to improve error handling and cache control.
-
-```go-html-template
-{{ $data := dict }}
-{{ $url := "https://example.org/books.json" }}
-{{ with try (resources.GetRemote $url) }}
- {{ with .Err }}
- {{ errorf "%s" . }}
- {{ else with .Value }}
- {{ $data = . | transform.Unmarshal }}
- {{ else }}
- {{ errorf "Unable to get remote resource %q" $url }}
- {{ end }}
-{{ end }}
-```
-
-[`Resources.Get`]: /methods/page/resources/
-[`resources.GetRemote`]: /functions/resources/getremote/
-[`resources.Get`]: /functions/resources/get/
-[`transform.Unmarshal`]: /functions/transform/unmarshal/
diff --git a/docs/content/en/functions/data/_index.md b/docs/content/en/functions/data/_index.md
deleted file mode 100644
index 2177bc528..000000000
--- a/docs/content/en/functions/data/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Data functions
-linkTitle: data
-description: Use these functions to read local or remote data files.
-categories: []
-keywords: []
----
diff --git a/docs/content/en/functions/debug/Dump.md b/docs/content/en/functions/debug/Dump.md
deleted file mode 100644
index df846ac3d..000000000
--- a/docs/content/en/functions/debug/Dump.md
+++ /dev/null
@@ -1,33 +0,0 @@
----
-title: debug.Dump
-description: Returns an object dump as a string.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: string
- signatures: [debug.Dump VALUE]
----
-
-```go-html-template
-
{{ debug.Dump site.Data.books }}
-```
-
-```json
-[
- {
- "author": "Victor Hugo",
- "rating": 4,
- "title": "The Hunchback of Notre Dame"
- },
- {
- "author": "Victor Hugo",
- "rating": 5,
- "title": "Les Misérables"
- }
-]
-```
-
-> [!note]
-> Output from this function may change from one release to the next. Use for debugging only.
diff --git a/docs/content/en/functions/debug/Timer.md b/docs/content/en/functions/debug/Timer.md
deleted file mode 100644
index c2cd59211..000000000
--- a/docs/content/en/functions/debug/Timer.md
+++ /dev/null
@@ -1,37 +0,0 @@
----
-title: debug.Timer
-description: Creates a named timer that reports elapsed time to the console.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: debug.Timer
- signatures: [debug.Timer NAME]
----
-
-{{< new-in 0.120.0 />}}
-
-Use the `debug.Timer` function to determine execution time for a block of code, useful for finding performance bottlenecks in templates.
-
-The timer starts when you instantiate it, and stops when you call its `Stop` method.
-
-```go-html-template
-{{ $t := debug.Timer "TestSqrt" }}
-{{ range seq 2000 }}
- {{ $f := math.Sqrt . }}
-{{ end }}
-{{ $t.Stop }}
-```
-
-Use the `--logLevel info` command line flag when you build the site.
-
-```sh
-hugo --logLevel info
-```
-
-The results are displayed in the console at the end of the build. You can have as many timers as you want and if you don't stop them, they will be stopped at the end of build.
-
-```text
-INFO timer: name TestSqrt count 1002 duration 2.496017496s average 2.491035ms median 2.282291ms
-```
diff --git a/docs/content/en/functions/debug/_index.md b/docs/content/en/functions/debug/_index.md
deleted file mode 100644
index 49fe416ed..000000000
--- a/docs/content/en/functions/debug/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Debug functions
-linkTitle: debug
-description: Use these functions to debug your templates.
-categories: []
-keywords: []
----
diff --git a/docs/content/en/functions/diagrams/Goat.md b/docs/content/en/functions/diagrams/Goat.md
deleted file mode 100644
index e2f55eee0..000000000
--- a/docs/content/en/functions/diagrams/Goat.md
+++ /dev/null
@@ -1,115 +0,0 @@
----
-title: diagrams.Goat
-description: Returns an SVGDiagram object created from the given GoAT markup and options.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: diagrams.SVGDiagram
- signatures: [diagrams.Goat MARKUP]
----
-
-Useful in a [code block render hook], the `diagrams.Goat` function returns an SVGDiagram object created from the given [GoAT] markup.
-
-## Methods
-
-The SVGDiagram object has the following methods:
-
-Inner
-: (`template.HTML`) Returns the SVG child elements without a wrapping `svg` element, allowing you to create your own wrapper.
-
-Wrapped
-: (`template.HTML`) Returns the SVG child elements wrapped in an `svg` element.
-
-Width
-: (`int`) Returns the width of the rendered diagram, in pixels.
-
-Height
-: (`int`) Returns the height of the rendered diagram, in pixels.
-
-## GoAT Diagrams
-
-Hugo natively supports GoAT diagrams with an [embedded code block render hook].
-
-This Markdown:
-
-````text
-```goat
-.---. .-. .-. .-. .---.
-| A +--->| 1 |<--->| 2 |<--->| 3 |<---+ B |
-'---' '-' '+' '+' '---'
-```
-````
-
-Is rendered to:
-
-```html
-
-
-
-```
-
-Which appears in your browser as:
-
-```goat {class="mw6-ns"}
-.---. .-. .-. .-. .---.
-| A +--->| 1 |<--->| 2 |<--->| 3 |<---+ B |
-'---' '-' '+' '+' '---'
-```
-
-To customize rendering, override Hugo's [embedded code block render hook] for GoAT diagrams.
-
-## Code block render hook
-
-By way of example, let's create a code block render hook to render GoAT diagrams as `figure` elements with an optional caption.
-
-```go-html-template {file="layouts/_default/_markup/render-codeblock-goat.html"}
-{{ $caption := or .Attributes.caption "" }}
-{{ $class := or .Attributes.class "diagram" }}
-{{ $id := or .Attributes.id (printf "diagram-%d" (add 1 .Ordinal)) }}
-
-
- {{ with diagrams.Goat (trim .Inner "\n\r") }}
-
- {{ end }}
- {{ $caption }}
-
-```
-
-This Markdown:
-
-````text {file="content/example.md" }
-```goat {class="foo" caption="Diagram 1: Example"}
-.---. .-. .-. .-. .---.
-| A +--->| 1 |<--->| 2 |<--->| 3 |<---+ B |
-'---' '-' '+' '+' '---'
-```
-````
-
-Is rendered to:
-
-```html
-
-
- Diagram 1: Example
-
-```
-
-Use CSS to style the SVG as needed:
-
-```css
-svg.foo {
- font-family: "Segoe UI","Noto Sans",Helvetica,Arial,sans-serif
-}
-```
-
-[code block render hook]: /render-hooks/code-blocks/
-[embedded code block render hook]: {{% eturl render-codeblock-goat %}}
-[GoAT]: https://github.com/bep/goat
diff --git a/docs/content/en/functions/diagrams/_index.md b/docs/content/en/functions/diagrams/_index.md
deleted file mode 100644
index 6aa407071..000000000
--- a/docs/content/en/functions/diagrams/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Diagram functions
-linkTitle: diagrams
-description: Use these functions to render diagrams.
-categories: []
-keywords: []
----
diff --git a/docs/content/en/functions/encoding/Base64Decode.md b/docs/content/en/functions/encoding/Base64Decode.md
deleted file mode 100644
index 5237e904f..000000000
--- a/docs/content/en/functions/encoding/Base64Decode.md
+++ /dev/null
@@ -1,39 +0,0 @@
----
-title: encoding.Base64Decode
-description: Returns the base64 decoding of the given content.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [base64Decode]
- returnType: string
- signatures: [encoding.Base64Decode INPUT]
-aliases: [/functions/base64Decode]
----
-
-```go-html-template
-{{ "SHVnbw==" | base64Decode }} → Hugo
-```
-
-Use the `base64Decode` function to decode responses from APIs. For example, the result of this call to GitHub's API contains the base64-encoded representation of the repository's README file:
-
-```text
-https://api.github.com/repos/gohugoio/hugo/readme
-```
-
-To retrieve and render the content:
-
-```go-html-template
-{{ $url := "https://api.github.com/repos/gohugoio/hugo/readme" }}
-{{ with try (resources.GetRemote $url) }}
- {{ with .Err }}
- {{ errorf "%s" . }}
- {{ else with .Value }}
- {{ with . | transform.Unmarshal }}
- {{ .content | base64Decode | markdownify }}
- {{ end }}
- {{ else }}
- {{ errorf "Unable to get remote resource %q" $url }}
- {{ end }}
-{{ end }}
-```
diff --git a/docs/content/en/functions/encoding/Base64Encode.md b/docs/content/en/functions/encoding/Base64Encode.md
deleted file mode 100644
index e19d6773c..000000000
--- a/docs/content/en/functions/encoding/Base64Encode.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-title: encoding.Base64Encode
-description: Returns the base64 decoding of the given content.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [base64Encode]
- returnType: string
- signatures: [encoding.Base64Encode INPUT]
-aliases: [/functions/base64, /functions/base64Encode]
----
-
-```go-html-template
-{{ "Hugo" | base64Encode }} → SHVnbw==
-```
diff --git a/docs/content/en/functions/encoding/Jsonify.md b/docs/content/en/functions/encoding/Jsonify.md
deleted file mode 100644
index 1d60dd68d..000000000
--- a/docs/content/en/functions/encoding/Jsonify.md
+++ /dev/null
@@ -1,34 +0,0 @@
----
-title: encoding.Jsonify
-description: Encodes the given object to JSON.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [jsonify]
- returnType: template.HTML
- signatures: ['encoding.Jsonify [OPTIONS] INPUT']
-aliases: [/functions/jsonify]
----
-
-To customize the printing of the JSON, pass an options map as the first
-argument. Supported options are "prefix" and "indent". Each JSON element in
-the output will begin on a new line beginning with *prefix* followed by one or
-more copies of *indent* according to the indentation nesting.
-
-```go-html-template
-{{ dict "title" .Title "content" .Plain | jsonify }}
-{{ dict "title" .Title "content" .Plain | jsonify (dict "indent" " ") }}
-{{ dict "title" .Title "content" .Plain | jsonify (dict "prefix" " " "indent" " ") }}
-```
-
-## Options
-
-indent
-: (`string`) Indentation to use. Default is "".
-
-prefix
-: (`string`) Indentation prefix. Default is "".
-
-noHTMLEscape
-: (`bool`) Whether to disable escaping of problematic HTML characters inside JSON quoted strings. The default behavior is to escape `&`, `<`, and `>` to `\u0026`, `\u003c`, and `\u003e` to avoid certain safety problems that can arise when embedding JSON in HTML. Default is `false`.
diff --git a/docs/content/en/functions/encoding/_index.md b/docs/content/en/functions/encoding/_index.md
deleted file mode 100644
index f2819f0a7..000000000
--- a/docs/content/en/functions/encoding/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Encoding functions
-linkTitle: encoding
-description: Use these functions to encode and decode data.
-categories: []
-keywords: []
----
diff --git a/docs/content/en/functions/fmt/Errorf.md b/docs/content/en/functions/fmt/Errorf.md
deleted file mode 100644
index 799622f0e..000000000
--- a/docs/content/en/functions/fmt/Errorf.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-title: fmt.Errorf
-description: Log an ERROR from a template.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [errorf]
- returnType: string
- signatures: ['fmt.Errorf FORMAT [INPUT]']
-aliases: [/functions/errorf]
----
-
-{{% include "/_common/functions/fmt/format-string.md" %}}
-
-The `errorf` function evaluates the format string, then prints the result to the ERROR log and fails the build.
-
-```go-html-template
-{{ errorf "The %q shortcode requires a src argument. See %s" .Name .Position }}
-```
-
-Use the [`erroridf`] function to allow optional suppression of specific errors.
-
-[`erroridf`]: /functions/fmt/erroridf/
diff --git a/docs/content/en/functions/fmt/Erroridf.md b/docs/content/en/functions/fmt/Erroridf.md
deleted file mode 100644
index 97d628bac..000000000
--- a/docs/content/en/functions/fmt/Erroridf.md
+++ /dev/null
@@ -1,38 +0,0 @@
----
-title: fmt.Erroridf
-description: Log a suppressible ERROR from a template.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [erroridf]
- returnType: string
- signatures: ['fmt.Erroridf ID FORMAT [INPUT]']
-aliases: [/functions/erroridf]
----
-
-{{% include "/_common/functions/fmt/format-string.md" %}}
-
-The `erroridf` function evaluates the format string, then prints the result to the ERROR log and fails the build. Unlike the [`errorf`] function, you may suppress errors logged by the `erroridf` function by adding the message ID to the `ignoreLogs` array in your site configuration.
-
-This template code:
-
-```go-html-template
-{{ erroridf "error-42" "You should consider fixing this." }}
-```
-
-Produces this console log:
-
-```text
-ERROR You should consider fixing this.
-You can suppress this error by adding the following to your site configuration:
-ignoreLogs = ['error-42']
-```
-
-To suppress this message:
-
-{{< code-toggle file=hugo >}}
-ignoreLogs = ["error-42"]
-{{< /code-toggle >}}
-
-[`errorf`]: /functions/fmt/errorf/
diff --git a/docs/content/en/functions/fmt/Print.md b/docs/content/en/functions/fmt/Print.md
deleted file mode 100644
index f1d169cfa..000000000
--- a/docs/content/en/functions/fmt/Print.md
+++ /dev/null
@@ -1,18 +0,0 @@
----
-title: fmt.Print
-description: Prints the default representation of the given arguments using the standard `fmt.Print` function.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [print]
- returnType: string
- signatures: [fmt.Print INPUT]
-aliases: [/functions/print]
----
-
-```go-html-template
-{{ print "foo" }} → foo
-{{ print "foo" "bar" }} → foobar
-{{ print (slice 1 2 3) }} → [1 2 3]
-```
diff --git a/docs/content/en/functions/fmt/Printf.md b/docs/content/en/functions/fmt/Printf.md
deleted file mode 100644
index 68df98609..000000000
--- a/docs/content/en/functions/fmt/Printf.md
+++ /dev/null
@@ -1,37 +0,0 @@
----
-title: fmt.Printf
-description: Formats a string using the standard `fmt.Sprintf` function.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [printf]
- returnType: string
- signatures: ['fmt.Printf FORMAT [INPUT]']
-aliases: [/functions/printf]
----
-
-{{% include "/_common/functions/fmt/format-string.md" %}}
-
-```go-html-template
-{{ $var := "world" }}
-{{ printf "Hello %s." $var }} → Hello world.
-```
-
-```go-html-template
-{{ $pi := 3.14159265 }}
-{{ printf "Pi is approximately %.2f." $pi }} → 3.14
-```
-
-Use the `printf` function with the `safeHTMLAttr` function:
-
-```go-html-template
-{{ $desc := "Eat at Joe's" }}
-
-```
-
-Hugo renders this to:
-
-```html
-
-```
diff --git a/docs/content/en/functions/fmt/Println.md b/docs/content/en/functions/fmt/Println.md
deleted file mode 100644
index b7fe608ff..000000000
--- a/docs/content/en/functions/fmt/Println.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-title: fmt.Println
-description: Prints the default representation of the given argument using the standard `fmt.Print` function and enforces a line break.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [println]
- returnType: string
- signatures: [fmt.Println INPUT]
-aliases: [/functions/println]
----
-
-```go-html-template
-{{ println "foo" }} → foo\n
-```
diff --git a/docs/content/en/functions/fmt/Warnf.md b/docs/content/en/functions/fmt/Warnf.md
deleted file mode 100644
index 887a8d47f..000000000
--- a/docs/content/en/functions/fmt/Warnf.md
+++ /dev/null
@@ -1,34 +0,0 @@
----
-title: fmt.Warnf
-description: Log a WARNING from a template.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [warnf]
- returnType: string
- signatures: ['fmt.Warnf FORMAT [INPUT]']
-aliases: [/functions/warnf]
----
-
-{{% include "/_common/functions/fmt/format-string.md" %}}
-
-The `warnf` function evaluates the format string, then prints the result to the WARNING log. Hugo prints each unique message once to avoid flooding the log with duplicate warnings.
-
-```go-html-template
-{{ warnf "The %q shortcode was unable to find %s. See %s" .Name $file .Position }}
-```
-
-Use the [`warnidf`] function to allow optional suppression of specific warnings.
-
-To prevent suppression of duplicate messages when using `warnf` for debugging, make each message unique with the [`math.Counter`] function. For example:
-
-```go-html-template
-{{ range site.RegularPages }}
- {{ .Section | warnf "%#[2]v [%[1]d]" math.Counter }}
-{{ end }}
-```
-
-[`math.Counter`]: /functions/math/counter/
-
-[`warnidf`]: /functions/fmt/warnidf/
diff --git a/docs/content/en/functions/fmt/Warnidf.md b/docs/content/en/functions/fmt/Warnidf.md
deleted file mode 100644
index 79ebf81e6..000000000
--- a/docs/content/en/functions/fmt/Warnidf.md
+++ /dev/null
@@ -1,40 +0,0 @@
----
-title: fmt.Warnidf
-description: Log a suppressible WARNING from a template.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [warnidf]
- returnType: string
- signatures: ['fmt.Warnidf ID FORMAT [INPUT]']
-aliases: [/functions/warnidf]
----
-
-{{< new-in 0.123.0 />}}
-
-{{% include "/_common/functions/fmt/format-string.md" %}}
-
-The `warnidf` function evaluates the format string, then prints the result to the WARNING log. Unlike the [`warnf`] function, you may suppress warnings logged by the `warnidf` function by adding the message ID to the `ignoreLogs` array in your site configuration.
-
-This template code:
-
-```go-html-template
-{{ warnidf "warning-42" "You should consider fixing this." }}
-```
-
-Produces this console log:
-
-```text
-WARN You should consider fixing this.
-You can suppress this warning by adding the following to your site configuration:
-ignoreLogs = ['warning-42']
-```
-
-To suppress this message:
-
-{{< code-toggle file=hugo >}}
-ignoreLogs = ["warning-42"]
-{{< /code-toggle >}}
-
-[`warnf`]: /functions/fmt/warnf/
diff --git a/docs/content/en/functions/fmt/_index.md b/docs/content/en/functions/fmt/_index.md
deleted file mode 100644
index d388df112..000000000
--- a/docs/content/en/functions/fmt/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Fmt functions
-linkTitle: fmt
-description: Use these functions to print strings within a template or to print messages to the terminal.
-categories: []
-keywords: []
----
diff --git a/docs/content/en/functions/global/_index.md b/docs/content/en/functions/global/_index.md
deleted file mode 100644
index 3d935176c..000000000
--- a/docs/content/en/functions/global/_index.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Global functions
-linkTitle: global
-description: Use these global functions to access page and site data.
-categories: []
----
diff --git a/docs/content/en/functions/global/page.md b/docs/content/en/functions/global/page.md
deleted file mode 100644
index 0d4b8070f..000000000
--- a/docs/content/en/functions/global/page.md
+++ /dev/null
@@ -1,100 +0,0 @@
----
-title: page
-description: Provides global access to a Page object.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType:
- signatures: [page]
-aliases: [/functions/page]
----
-
-At the top level of a template that receives a `Page` object in context, these are equivalent:
-
-```go-html-template
-{{ .Params.foo }}
-{{ .Page.Params.foo }}
-{{ page.Params.foo }}
-```
-
-When a `Page` object is not in context, you can use the global `page` function:
-
-```go-html-template
-{{ page.Params.foo }}
-```
-
-> [!note]
-> Do not use the global `page` function in shortcodes, partials called by shortcodes, or cached partials. See [warnings](#warnings) below.
-
-## Explanation
-
-Hugo almost always passes a `Page` as the data context into the top-level template (e.g., `single.html`). The one exception is the multihost sitemap template. This means that you can access the current page with the `.` in the template.
-
-But when you are deeply nested inside of a [content view](g), [partial](g), or [render hook](g), it is not always practical or possible to access the `Page` object.
-
-Use the global `page` function to access the `Page` object from anywhere in any template.
-
-## Warnings
-
-### Be aware of top-level context
-
-The global `page` function accesses the `Page` object passed into the top-level template.
-
-With this content structure:
-
-```text
-content/
-├── posts/
-│ ├── post-1.md
-│ ├── post-2.md
-│ └── post-3.md
-└── _index.md <-- title is "My Home Page"
-```
-
-And this code in the home template:
-
-```go-html-template
-{{ range site.Sections }}
- {{ range .Pages }}
- {{ page.Title }}
- {{ end }}
-{{ end }}
-```
-
-The rendered output will be:
-
-```text
-My Home Page
-My Home Page
-My Home Page
-```
-
-In the example above, the global `page` function accesses the `Page` object passed into the home template; it does not access the `Page` object of the iterated pages.
-
-### Be aware of caching
-
-Do not use the global `page` function in:
-
-- Shortcodes
-- Partials called by shortcodes
-- Partials cached by the [`partialCached`] function
-
-Hugo caches rendered shortcodes. If you use the global `page` function within a shortcode, and the page content is rendered in two or more templates, the cached shortcode may be incorrect.
-
-Consider this section template:
-
-```go-html-template
-{{ range .Pages }}
-
- {{ .Summary }}
-{{ end }}
-```
-
-When you call the [`Summary`] method, Hugo renders the page content including shortcodes. In this case, within a shortcode, the global `page` function accesses the `Page` object of the section page, not the content page.
-
-If Hugo renders the section page before a content page, the cached rendered shortcode will be incorrect. You cannot control the rendering sequence due to concurrency.
-
-[`partialCached`]: /functions/partials/includecached/
-[`Summary`]: /methods/page/summary/
diff --git a/docs/content/en/functions/global/site.md b/docs/content/en/functions/global/site.md
deleted file mode 100644
index be0c6730e..000000000
--- a/docs/content/en/functions/global/site.md
+++ /dev/null
@@ -1,30 +0,0 @@
----
-title: site
-description: Provides global access to the current Site object.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType:
- signatures: [site]
-aliases: [/functions/site]
----
-
-Use the `site` function to return the `Site` object regardless of current context.
-
-```go-html-template
-{{ site.Params.foo }}
-```
-
-When the `Site` object is in context you can use the `Site` property:
-
-```go-html-template
-
-{{ .Site.Params.foo }}
-
-{{ $.Site.Params.foo }}
-```
-
-> [!note]
-> To simplify your templates, use the global `site` function regardless of whether the `Site` object is in context.
diff --git a/docs/content/en/functions/go-template/_index.md b/docs/content/en/functions/go-template/_index.md
deleted file mode 100644
index 627dc2849..000000000
--- a/docs/content/en/functions/go-template/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Go template functions, operators, and statements
-linkTitle: go template
-description: These are the functions, operators, and statements provided by Go's text/template package.
-categories: []
-keywords: []
----
diff --git a/docs/content/en/functions/go-template/and.md b/docs/content/en/functions/go-template/and.md
deleted file mode 100644
index 77906df52..000000000
--- a/docs/content/en/functions/go-template/and.md
+++ /dev/null
@@ -1,22 +0,0 @@
----
-title: and
-description: Returns the first falsy argument. If all arguments are truthy, returns the last argument.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: any
- signatures: [and VALUE...]
----
-
-{{% include "/_common/functions/truthy-falsy.md" %}}
-
-```go-html-template
-{{ and 1 0 "" }} → 0 (int)
-{{ and 1 false 0 }} → false (bool)
-
-{{ and 1 2 3 }} → 3 (int)
-{{ and "a" "b" "c" }} → c (string)
-{{ and "a" 1 true }} → true (bool)
-```
diff --git a/docs/content/en/functions/go-template/block.md b/docs/content/en/functions/go-template/block.md
deleted file mode 100644
index bffab1f8c..000000000
--- a/docs/content/en/functions/go-template/block.md
+++ /dev/null
@@ -1,53 +0,0 @@
----
-title: block
-description: Defines a template and executes it in place.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType:
- signatures: [block NAME CONTEXT]
----
-
-A block is shorthand for defining a template:
-
-```go-html-template
-{{ define "name" }} T1 {{ end }}
-```
-
-and then executing it in place:
-
-```go-html-template
-{{ template "name" pipeline }}
-```
-The typical use is to define a set of root templates that are then customized by redefining the block templates within.
-
-```go-html-template {file="layouts/_default/baseof.html"}
-
-
- {{ block "main" . }}
- {{ print "default value if 'main' template is empty" }}
- {{ end }}
-
-
-```
-
-```go-html-template {file="layouts/_default/single.html"}
-{{ define "main" }}
-
- {{ end }}
-{{ end }}
-```
-
-{{% include "/_common/functions/go-template/text-template.md" %}}
diff --git a/docs/content/en/functions/go-template/break.md b/docs/content/en/functions/go-template/break.md
deleted file mode 100644
index 9236ec91e..000000000
--- a/docs/content/en/functions/go-template/break.md
+++ /dev/null
@@ -1,31 +0,0 @@
----
-title: break
-description: Used with the range statement, stops the innermost iteration and bypasses all remaining iterations.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType:
- signatures: [break]
----
-
-This template code:
-
-```go-html-template
-{{ $s := slice "foo" "bar" "baz" }}
-{{ range $s }}
- {{ if eq . "bar" }}
- {{ break }}
- {{ end }}
-
{{ . }}
-{{ end }}
-```
-
-Is rendered to:
-
-```html
-
foo
-```
-
-{{% include "/_common/functions/go-template/text-template.md" %}}
diff --git a/docs/content/en/functions/go-template/continue.md b/docs/content/en/functions/go-template/continue.md
deleted file mode 100644
index 0b9339bf4..000000000
--- a/docs/content/en/functions/go-template/continue.md
+++ /dev/null
@@ -1,32 +0,0 @@
----
-title: continue
-description: Used with the range statement, stops the innermost iteration and continues to the next iteration.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType:
- signatures: [continue]
----
-
-This template code:
-
-```go-html-template
-{{ $s := slice "foo" "bar" "baz" }}
-{{ range $s }}
- {{ if eq . "bar" }}
- {{ continue }}
- {{ end }}
-
{{ . }}
-{{ end }}
-```
-
-Is rendered to:
-
-```html
-
foo
-
baz
-```
-
-{{% include "/_common/functions/go-template/text-template.md" %}}
diff --git a/docs/content/en/functions/go-template/define.md b/docs/content/en/functions/go-template/define.md
deleted file mode 100644
index 19762a3d6..000000000
--- a/docs/content/en/functions/go-template/define.md
+++ /dev/null
@@ -1,50 +0,0 @@
----
-title: define
-description: Defines a template.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType:
- signatures: [define NAME]
----
-
-Use with the [`block`] statement:
-
-```go-html-template
-{{ block "main" . }}
- {{ print "default value if 'main' template is empty" }}
-{{ end }}
-
-{{ define "main" }}
-
{{ .Title }}
- {{ .Content }}
-{{ end }}
-```
-
-Use with the [`partial`] function:
-
-```go-html-template
-{{ partial "inline/foo.html" (dict "answer" 42) }}
-
-{{ define "partials/inline/foo.html" }}
- {{ printf "The answer is %v." .answer }}
-{{ end }}
-```
-
-Use with the [`template`] function:
-
-```go-html-template
-{{ template "foo" (dict "answer" 42) }}
-
-{{ define "foo" }}
- {{ printf "The answer is %v." .answer }}
-{{ end }}
-```
-
-[`block`]: /functions/go-template/block/
-[`template`]: /functions/go-template/block/
-[`partial`]: /functions/partials/include/
-
-{{% include "/_common/functions/go-template/text-template.md" %}}
diff --git a/docs/content/en/functions/go-template/else.md b/docs/content/en/functions/go-template/else.md
deleted file mode 100644
index db3980070..000000000
--- a/docs/content/en/functions/go-template/else.md
+++ /dev/null
@@ -1,65 +0,0 @@
----
-title: else
-description: Begins an alternate block for if, with, and range statements.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType:
- signatures: [else VALUE]
----
-
-Use with the [`if`] statement:
-
-```go-html-template
-{{ $var := "foo" }}
-{{ if $var }}
- {{ $var }} → foo
-{{ else }}
- {{ print "var is falsy" }}
-{{ end }}
-```
-
-Use with the [`with`] statement:
-
-```go-html-template
-{{ $var := "foo" }}
-{{ with $var }}
- {{ . }} → foo
-{{ else }}
- {{ print "var is falsy" }}
-{{ end }}
-```
-
-Use with the [`range`] statement:
-
-```go-html-template
-{{ $var := slice 1 2 3 }}
-{{ range $var }}
- {{ . }} → 1 2 3
-{{ else }}
- {{ print "var is falsy" }}
-{{ end }}
-```
-
-Use `else if` to check multiple conditions.
-
-```go-html-template
-{{ $var := 12 }}
-{{ if eq $var 6 }}
- {{ print "var is 6" }}
-{{ else if eq $var 7 }}
- {{ print "var is 7" }}
-{{ else if eq $var 42 }}
- {{ print "var is 42" }}
-{{ else }}
- {{ print "var is something else" }}
-{{ end }}
-```
-
-{{% include "/_common/functions/go-template/text-template.md" %}}
-
-[`if`]: /functions/go-template/if/
-[`with`]: /functions/go-template/with/
-[`range`]: /functions/go-template/range/
diff --git a/docs/content/en/functions/go-template/end.md b/docs/content/en/functions/go-template/end.md
deleted file mode 100644
index 6de120724..000000000
--- a/docs/content/en/functions/go-template/end.md
+++ /dev/null
@@ -1,60 +0,0 @@
----
-title: end
-description: Terminates if, with, range, block, and define statements.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType:
- signatures: [end]
----
-
-Use with the [`if`] statement:
-
-```go-html-template
-{{ $var := "foo" }}
-{{ if $var }}
- {{ $var }} → foo
-{{ end }}
-```
-
-Use with the [`with`] statement:
-
-```go-html-template
-{{ $var := "foo" }}
-{{ with $var }}
- {{ . }} → foo
-{{ end }}
-```
-
-Use with the [`range`] statement:
-
-```go-html-template
-{{ $var := slice 1 2 3 }}
-{{ range $var }}
- {{ . }} → 1 2 3
-{{ end }}
-```
-
-Use with the [`block`] statement:
-
-```go-html-template
-{{ block "main" . }}{{ end }}
-```
-
-Use with the [`define`] statement:
-
-```go-html-template
-{{ define "main" }}
- {{ print "this is the main section" }}
-{{ end }}
-```
-
-{{% include "/_common/functions/go-template/text-template.md" %}}
-
-[`block`]: /functions/go-template/block/
-[`define`]: /functions/go-template/define/
-[`if`]: /functions/go-template/if/
-[`range`]: /functions/go-template/range/
-[`with`]: /functions/go-template/with/
diff --git a/docs/content/en/functions/go-template/if.md b/docs/content/en/functions/go-template/if.md
deleted file mode 100644
index af2989cca..000000000
--- a/docs/content/en/functions/go-template/if.md
+++ /dev/null
@@ -1,50 +0,0 @@
----
-title: if
-description: Executes the block if the expression is truthy.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType:
- signatures: [if EXPR]
----
-
-{{% include "/_common/functions/truthy-falsy.md" %}}
-
-```go-html-template
-{{ $var := "foo" }}
-{{ if $var }}
- {{ $var }} → foo
-{{ end }}
-```
-
-Use with the [`else`] statement:
-
-```go-html-template
-{{ $var := "foo" }}
-{{ if $var }}
- {{ $var }} → foo
-{{ else }}
- {{ print "var is falsy" }}
-{{ end }}
-```
-
-Use `else if` to check multiple conditions:
-
-```go-html-template
-{{ $var := 12 }}
-{{ if eq $var 6 }}
- {{ print "var is 6" }}
-{{ else if eq $var 7 }}
- {{ print "var is 7" }}
-{{ else if eq $var 42 }}
- {{ print "var is 42" }}
-{{ else }}
- {{ print "var is something else" }}
-{{ end }}
-```
-
-{{% include "/_common/functions/go-template/text-template.md" %}}
-
-[`else`]: /functions/go-template/else/
diff --git a/docs/content/en/functions/go-template/len.md b/docs/content/en/functions/go-template/len.md
deleted file mode 100644
index 6a13784e3..000000000
--- a/docs/content/en/functions/go-template/len.md
+++ /dev/null
@@ -1,47 +0,0 @@
----
-title: len
-description: Returns the length of a string, slice, map, or collection.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: int
- signatures: [len VALUE]
-aliases: [/functions/len]
----
-
-With a string:
-
-```go-html-template
-{{ "ab" | len }} → 2
-{{ "" | len }} → 0
-```
-
-With a slice:
-
-```go-html-template
-{{ slice "a" "b" | len }} → 2
-{{ slice | len }} → 0
-```
-
-With a map:
-
-```go-html-template
-{{ dict "a" 1 "b" 2 | len }} → 2
-{{ dict | len }} → 0
-```
-
-With a collection:
-
-```go-html-template
-{{ site.RegularPages | len }} → 42
-```
-
-You may also determine the number of pages in a collection with:
-
-```go-html-template
-{{ site.RegularPages.Len }} → 42
-```
-
-{{% include "/_common/functions/go-template/text-template.md" %}}
diff --git a/docs/content/en/functions/go-template/not.md b/docs/content/en/functions/go-template/not.md
deleted file mode 100644
index fd8b9afae..000000000
--- a/docs/content/en/functions/go-template/not.md
+++ /dev/null
@@ -1,33 +0,0 @@
----
-title: not
-description: Returns the boolean negation of its single argument.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: bool
- signatures: [not VALUE]
----
-
-Unlike the `and` and `or` operators, the `not` operator always returns a boolean value.
-
-```go-html-template
-{{ not true }} → false
-{{ not false }} → true
-
-{{ not 1 }} → false
-{{ not 0 }} → true
-
-{{ not "x" }} → false
-{{ not "" }} → true
-```
-
-Use the `not` operator, twice in succession, to cast any value to a boolean value. For example:
-
-```go-html-template
-{{ 42 | not | not }} → true
-{{ "" | not | not }} → false
-```
-
-{{% include "/_common/functions/go-template/text-template.md" %}}
diff --git a/docs/content/en/functions/go-template/or.md b/docs/content/en/functions/go-template/or.md
deleted file mode 100644
index 2f55fe479..000000000
--- a/docs/content/en/functions/go-template/or.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-title: or
-description: Returns the first truthy argument. If all arguments are falsy, returns the last argument.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: any
- signatures: [or VALUE...]
----
-
-{{% include "/_common/functions/truthy-falsy.md" %}}
-
-```go-html-template
-{{ or 0 1 2 }} → 1
-{{ or false "a" 1 }} → a
-{{ or 0 true "a" }} → true
-
-{{ or false "" 0 }} → 0
-{{ or 0 "" false }} → false
-```
-
-{{% include "/_common/functions/go-template/text-template.md" %}}
diff --git a/docs/content/en/functions/go-template/range.md b/docs/content/en/functions/go-template/range.md
deleted file mode 100644
index a06907c79..000000000
--- a/docs/content/en/functions/go-template/range.md
+++ /dev/null
@@ -1,190 +0,0 @@
----
-title: range
-description: Iterates over a non-empty collection, binds context (the dot) to successive elements, and executes the block.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType:
- signatures: [range COLLECTION]
-aliases: [/functions/range]
----
-
-{{% include "/_common/functions/truthy-falsy.md" %}}
-
-```go-html-template
-{{ $s := slice "foo" "bar" "baz" }}
-{{ range $s }}
- {{ . }} → foo bar baz
-{{ end }}
-```
-
-Use with the [`else`] statement:
-
-```go-html-template
-{{ $s := slice "foo" "bar" "baz" }}
-{{ range $s }}
-
{{ . }}
-{{ else }}
-
The collection is empty
-{{ end }}
-```
-
-Within a range block:
-
-- Use the [`continue`] statement to stop the innermost iteration and continue to the next iteration
-- Use the [`break`] statement to stop the innermost iteration and bypass all remaining iterations
-
-## Understanding context
-
-At the top of a page template, the [context](g) (the dot) is a `Page` object. Within the `range` block, the context is bound to each successive element.
-
-With this contrived example that uses the [`seq`] function to generate a slice of integers:
-
-```go-html-template
-{{ range seq 3 }}
- {{ .Title }}
-{{ end }}
-```
-
-Hugo will throw an error:
-
- can't evaluate field Title in type int
-
-The error occurs because we are trying to use the `.Title` method on an integer instead of a `Page` object. Within the `range` block, if we want to render the page title, we need to get the context passed into the template.
-
-> [!note]
-> Use the `$` to get the context passed into the template.
-
-This template will render the page title three times:
-
-```go-html-template
-{{ range seq 3 }}
- {{ $.Title }}
-{{ end }}
-```
-
-> [!note]
-> Gaining a thorough understanding of context is critical for anyone writing template code.
-
-## Array or slice of scalars
-
-This template code:
-
-```go-html-template
-{{ $s := slice "foo" "bar" "baz" }}
-{{ range $s }}
-
-{{ end }}
-```
-
-Is rendered to:
-
-```go-html-template
-
key = age value = 30
-
key = name value = John
-```
-
-Unlike ranging over an array or slice, Hugo sorts by key when ranging over a map.
-
-{{% include "/_common/functions/go-template/text-template.md" %}}
-
-[`break`]: /functions/go-template/break/
-[`continue`]: /functions/go-template/continue/
-[`else`]: /functions/go-template/else/
-[`seq`]: /functions/collections/seq/
diff --git a/docs/content/en/functions/go-template/return.md b/docs/content/en/functions/go-template/return.md
deleted file mode 100644
index eb6ba30cd..000000000
--- a/docs/content/en/functions/go-template/return.md
+++ /dev/null
@@ -1,96 +0,0 @@
----
-title: return
-description: Used within partial templates, terminates template execution and returns the given value, if any.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: any
- signatures: ['return [VALUE]']
----
-
-The `return` statement is a non-standard extension to Go's [text/template package]. Used within partial templates, the `return` statement terminates template execution and returns the given value, if any.
-
-The returned value may be of any data type including, but not limited to, [`bool`](g), [`float`](g), [`int`](g), [`map`](g), [`resource`](g), [`slice`](g), or [`string`](g).
-
-A `return` statement without a value returns an empty string of type `template.HTML`.
-
-> [!note]
-> Unlike `return` statements in other languages, Hugo executes the first occurrence of the `return` statement regardless of its position within logical blocks. See [usage](#usage) notes below.
-
-## Example
-
-By way of example, let's create a partial template that _renders_ HTML, describing whether the given number is odd or even:
-
-```go-html-template {file="layouts/partials/odd-or-even.html"}
-{{ if math.ModBool . 2 }}
-
{{ . }} is even
-{{ else }}
-
{{ . }} is odd
-{{ end }}
-```
-
-When called, the partial renders HTML:
-
-```go-html-template
-{{ partial "odd-or-even.html" 42 }} →
42 is even
-```
-
-Instead of rendering HTML, let's create a partial that _returns_ a boolean value, reporting whether the given number is even:
-
-```go-html-template {file="layouts/partials/is-even.html"}
-{{ return math.ModBool . 2 }}
-```
-
-With this template:
-
-```go-html-template
-{{ $number := 42 }}
-{{ if partial "is-even.html" $number }}
-
{{ $number }} is even
-{{ else }}
-
{{ $number }} is odd
-{{ end }}
-```
-
-Hugo renders:
-
-```html
-
42 is even
-```
-
-See additional examples in the [partial templates] section.
-
-## Usage
-
-> [!note]
-> Unlike `return` statements in other languages, Hugo executes the first occurrence of the `return` statement regardless of its position within logical blocks.
-
-A partial that returns a value must contain only one `return` statement, placed at the end of the template.
-
-For example:
-
-```go-html-template {file="layouts/partials/is-even.html"}
-{{ $result := false }}
-{{ if math.ModBool . 2 }}
- {{ $result = "even" }}
-{{ else }}
- {{ $result = "odd" }}
-{{ end }}
-{{ return $result }}
-```
-
-> [!note]
-> The construct below is incorrect; it contains more than one `return` statement.
-
-```go-html-template {file="layouts/partials/do-not-do-this.html"}
-{{ if math.ModBool . 2 }}
- {{ return "even" }}
-{{ else }}
- {{ return "odd" }}
-{{ end }}
-```
-
-[partial templates]: /templates/partial/#returning-a-value-from-a-partial
-[text/template package]: https://pkg.go.dev/text/template
diff --git a/docs/content/en/functions/go-template/template.md b/docs/content/en/functions/go-template/template.md
deleted file mode 100644
index 053cfcc22..000000000
--- a/docs/content/en/functions/go-template/template.md
+++ /dev/null
@@ -1,70 +0,0 @@
----
-title: template
-description: Executes the given template, optionally passing context.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType:
- signatures: ['template NAME [CONTEXT]']
----
-
-Use the `template` function to execute any of these [embedded templates](g):
-
-- [`disqus.html`]
-- [`google_analytics.html`]
-- [`opengraph.html`]
-- [`pagination.html`]
-- [`schema.html`]
-- [`twitter_cards.html`]
-
-
-
-For example:
-
-```go-html-template
-{{ range (.Paginate .Pages).Pages }}
-
-{{ end }}
-{{ template "_internal/pagination.html" . }}
-```
-
-You can also use the `template` function to execute a defined template:
-
-```go-html-template
-{{ template "foo" (dict "answer" 42) }}
-
-{{ define "foo" }}
- {{ printf "The answer is %v." .answer }}
-{{ end }}
-```
-
-The example above can be rewritten using an [inline partial] template:
-
-```go-html-template
-{{ partial "inline/foo.html" (dict "answer" 42) }}
-
-{{ define "partials/inline/foo.html" }}
- {{ printf "The answer is %v." .answer }}
-{{ end }}
-```
-
-The key distinctions between the preceding two examples are:
-
-1. Inline partials are globally scoped. That means that an inline partial defined in _one_ template may be called from _any_ template.
-2. Leveraging the [`partialCached`] function when calling an inline partial allows for performance optimization through result caching.
-3. An inline partial can [`return`] a value of any data type instead of rendering a string.
-
-{{% include "/_common/functions/go-template/text-template.md" %}}
-
-[`disqus.html`]: /templates/embedded/#disqus
-[`google_analytics.html`]: /templates/embedded/#google-analytics
-[`opengraph.html`]: /templates/embedded/#open-graph
-[`pagination.html`]: /templates/embedded/#pagination
-[`partialCached`]: /functions/partials/includecached/
-[`partial`]: /functions/partials/include/
-[`return`]: /functions/go-template/return/
-[`schema.html`]: /templates/embedded/#schema
-[`twitter_cards.html`]: /templates/embedded/#x-twitter-cards
-[inline partial]: /templates/partial/#inline-partials
diff --git a/docs/content/en/functions/go-template/try.md b/docs/content/en/functions/go-template/try.md
deleted file mode 100644
index 6aef4da36..000000000
--- a/docs/content/en/functions/go-template/try.md
+++ /dev/null
@@ -1,111 +0,0 @@
----
-title: try
-description: Returns a TryValue object after evaluating the given expression.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: TryValue
- signatures: ['try EXPRESSION']
----
-
-{{< new-in 0.141.0 />}}
-
-The `try` statement is a non-standard extension to Go's [text/template] package. It introduces a mechanism for handling errors within templates, mimicking the `try-catch` constructs found in other programming languages.
-
-## Methods
-
-The `TryValue` object encapsulates the result of evaluating the expression, and provides two methods:
-
-### Err
-
-(`string`) Returns a string representation of the error thrown by the expression, if an error occurred, or returns `nil` if the expression evaluated without errors.
-
-### Value
-
-(`any`) Returns the result of the expression if the evaluation was successful, or returns `nil` if an error occurred while evaluating the expression.
-
-## Explanation
-
-By way of example, let's divide a number by zero:
-
-```go-html-template
-{{ $x := 1 }}
-{{ $y := 0 }}
-{{ $result := div $x $y }}
-{{ printf "%v divided by %v equals %v" $x $y .Value }}
-```
-
-As expected, the example above throws an error and fails the build:
-
-```terminfo
-Error: error calling div: can't divide the value by 0
-```
-
-Instead of failing the build, we can catch the error and emit a warning:
-
-```go-html-template
-{{ $x := 1 }}
-{{ $y := 0 }}
-{{ with try (div $x $y) }}
- {{ with .Err }}
- {{ warnf "%s" . }}
- {{ else }}
- {{ printf "%v divided by %v equals %v" $x $y .Value }}
- {{ end }}
-{{ end }}
-```
-
-The error thrown by the expression is logged to the console as a warning:
-
-```terminfo
-WARN error calling div: can't divide the value by 0
-```
-
-Now let's change the arguments to avoid dividing by zero:
-
-```go-html-template
-{{ $x := 42 }}
-{{ $y := 6 }}
-{{ with try (div $x $y) }}
- {{ with .Err }}
- {{ warnf "%s" . }}
- {{ else }}
- {{ printf "%v divided by %v equals %v" $x $y .Value }}
- {{ end }}
-{{ end }}
-```
-
-Hugo renders the above to:
-
-```html
-42 divided by 6 equals 7
-```
-
-## Example
-
-Error handling is essential when using the [`resources.GetRemote`] function to capture remote resources such as data or images. When calling this function, if the HTTP request fails, Hugo will fail the build.
-
-Instead of failing the build, we can catch the error and emit a warning:
-
-```go-html-template
-{{ $url := "https://broken-example.org/images/a.jpg" }}
-{{ with try (resources.GetRemote $url) }}
- {{ with .Err }}
- {{ warnf "%s" . }}
- {{ else with .Value }}
-
- {{ else }}
- {{ warnf "Unable to get remote resource %q" $url }}
- {{ end }}
-{{ end }}
-```
-In the above, note that the [context](g) within the last conditional block is the `TryValue` object returned by the `try` statement. At this point neither the `Err` nor `Value` methods returned anything, so the current context is not useful. Use the `$` to access the [template context] if needed.
-
-> [!note]
-> Hugo does not classify an HTTP response with status code 404 as an error. In this case `resources.GetRemote` returns nil.
-
-[`resources.GetRemote`]: /functions/resources/getremote/
-[template context]: /templates/introduction/#template-context
-[text/template]: https://pkg.go.dev/text/template
diff --git a/docs/content/en/functions/go-template/urlquery.md b/docs/content/en/functions/go-template/urlquery.md
deleted file mode 100644
index dc97f867e..000000000
--- a/docs/content/en/functions/go-template/urlquery.md
+++ /dev/null
@@ -1,27 +0,0 @@
----
-title: urlquery
-description: Returns the escaped value of the textual representation of its arguments in a form suitable for embedding in a URL query.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: string
- signatures: ['urlquery VALUE [VALUE...]']
-aliases: [/functions/urlquery]
----
-
-This template code:
-
-```go-html-template
-{{ $u := urlquery "https://" "example.com" | safeURL }}
-Link
-```
-
-Is rendered to:
-
-```html
-Link
-```
-
-{{% include "/_common/functions/go-template/text-template.md" %}}
diff --git a/docs/content/en/functions/go-template/with.md b/docs/content/en/functions/go-template/with.md
deleted file mode 100644
index c25ce3fba..000000000
--- a/docs/content/en/functions/go-template/with.md
+++ /dev/null
@@ -1,92 +0,0 @@
----
-title: with
-description: Binds context (the dot) to the expression and executes the block if expression is truthy.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType:
- signatures: [with EXPR]
-aliases: [/functions/with]
----
-
-{{% include "/_common/functions/truthy-falsy.md" %}}
-
-```go-html-template
-{{ $var := "foo" }}
-{{ with $var }}
- {{ . }} → foo
-{{ end }}
-```
-
-Use with the [`else`] statement:
-
-```go-html-template
-{{ $var := "foo" }}
-{{ with $var }}
- {{ . }} → foo
-{{ else }}
- {{ print "var is falsy" }}
-{{ end }}
-```
-
-Use `else with` to check multiple conditions:
-
-```go-html-template
-{{ $v1 := 0 }}
-{{ $v2 := 42 }}
-{{ with $v1 }}
- {{ . }}
-{{ else with $v2 }}
- {{ . }} → 42
-{{ else }}
- {{ print "v1 and v2 are falsy" }}
-{{ end }}
-```
-
-Initialize a variable, scoped to the current block:
-
-```go-html-template
-{{ with $var := 42 }}
- {{ . }} → 42
- {{ $var }} → 42
-{{ end }}
-{{ $var }} → undefined
-```
-
-## Understanding context
-
-At the top of a page template, the [context](g) (the dot) is a `Page` object. Inside of the `with` block, the context is bound to the value passed to the `with` statement.
-
-With this contrived example:
-
-```go-html-template
-{{ with 42 }}
- {{ .Title }}
-{{ end }}
-```
-
-Hugo will throw an error:
-
- can't evaluate field Title in type int
-
-The error occurs because we are trying to use the `.Title` method on an integer instead of a `Page` object. Inside of the `with` block, if we want to render the page title, we need to get the context passed into the template.
-
-> [!note]
-> Use the `$` to get the context passed into the template.
-
-This template will render the page title as desired:
-
-```go-html-template
-{{ with 42 }}
- {{ $.Title }}
-{{ end }}
-```
-
-> [!note]
-> Gaining a thorough understanding of context is critical for anyone writing template code.
-
-{{% include "/_common/functions/go-template/text-template.md" %}}
-
-[`else`]: /functions/go-template/else/
diff --git a/docs/content/en/functions/hash/FNV32a.md b/docs/content/en/functions/hash/FNV32a.md
deleted file mode 100644
index b108acff8..000000000
--- a/docs/content/en/functions/hash/FNV32a.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-title: hash.FNV32a
-description: Returns the 32-bit FNV (Fowler-Noll-Vo) non-cryptographic hash of the given string.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: int
- signatures: [hash.FNV32a STRING]
-aliases: [/functions/crypto.fnv32a]
----
-
-```go-html-template
-{{ hash.FNV32a "Hello world" }} → 1498229191
-```
diff --git a/docs/content/en/functions/hash/XxHash.md b/docs/content/en/functions/hash/XxHash.md
deleted file mode 100644
index 6a92b2bdc..000000000
--- a/docs/content/en/functions/hash/XxHash.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: hash.XxHash
-description: Returns the 64-bit xxHash non-cryptographic hash of the given string.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [xxhash]
- returnType: string
- signatures: [hash.XxHash STRING]
----
-
-```go-html-template
-{{ hash.XxHash "Hello world" }} → c500b0c912b376d8
-```
-
-[xxHash](https://xxhash.com/) is a very fast non-cryptographic hash algorithm. Hugo uses [this Go implementation](https://github.com/cespare/xxhash).
diff --git a/docs/content/en/functions/hash/_index.md b/docs/content/en/functions/hash/_index.md
deleted file mode 100644
index 956f7fb8d..000000000
--- a/docs/content/en/functions/hash/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Hash functions
-linkTitle: hash
-description: Use these functions to create non-cryptographic hashes.
-categories: []
-keywords: []
----
diff --git a/docs/content/en/functions/hugo/BuildDate.md b/docs/content/en/functions/hugo/BuildDate.md
deleted file mode 100644
index a592283b9..000000000
--- a/docs/content/en/functions/hugo/BuildDate.md
+++ /dev/null
@@ -1,19 +0,0 @@
----
-title: hugo.BuildDate
-description: Returns the compile date of the Hugo binary.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: string
- signatures: [hugo.BuildDate]
----
-
-The `hugo.BuildDate` function returns the compile date of the Hugo binary, formatted per [RFC 3339].
-
-[RFC 3339]: https://datatracker.ietf.org/doc/html/rfc3339
-
-```go-html-template
-{{ hugo.BuildDate }} → 2023-11-01T17:57:00Z
-```
diff --git a/docs/content/en/functions/hugo/CommitHash.md b/docs/content/en/functions/hugo/CommitHash.md
deleted file mode 100644
index 324e985d1..000000000
--- a/docs/content/en/functions/hugo/CommitHash.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-title: hugo.CommitHash
-description: Returns the Git commit hash of the Hugo binary.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: string
- signatures: [hugo.CommitHash]
----
-
-```go-html-template
-{{ hugo.CommitHash }} → a4892a07b41b7b3f1f143140ee4ec0a9a5cf3970
-```
diff --git a/docs/content/en/functions/hugo/Deps.md b/docs/content/en/functions/hugo/Deps.md
deleted file mode 100644
index 9d8667ee5..000000000
--- a/docs/content/en/functions/hugo/Deps.md
+++ /dev/null
@@ -1,66 +0,0 @@
----
-title: hugo.Deps
-description: Returns a slice of project dependencies, either Hugo Modules or local theme components.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: '[]hugo.Dependency'
- signatures: [hugo.Deps]
----
-
-The `hugo.Deps` function returns a slice of project dependencies, either Hugo Modules or local theme components. Each dependency contains:
-
-Owner
-: (`hugo.Dependency`) In the dependency tree, this is the first module that defines this module as a dependency (e.g., `github.com/gohugoio/hugo-mod-bootstrap-scss/v5`).
-
-Path
-: (`string`) The module path or the path below your `themes` directory (e.g., `github.com/gohugoio/hugo-mod-jslibs-dist/popperjs/v2`).
-
-Replace
-: (`hugo.Dependency`) Replaced by this dependency.
-
-Time
-: (`time.Time`) The time that the version was created (e.g., `2022-02-13 15:11:28 +0000 UTC`).
-
-Vendor
-: (`bool`) Reports whether the dependency is vendored.
-
-Version
-: (`string`) The module version (e.g., `v2.21100.20000`).
-
-An example table listing the dependencies:
-
-```go-html-template
-
Dependencies
-
-
-
-
#
-
Owner
-
Path
-
Version
-
Time
-
Vendor
-
-
-
- {{ range $index, $element := hugo.Deps }}
-
-
{{ add $index 1 }}
-
{{ with $element.Owner }}{{ .Path }}{{ end }}
-
- {{ $element.Path }}
- {{ with $element.Replace }}
- => {{ .Path }}
- {{ end }}
-
-
{{ $element.Version }}
-
{{ with $element.Time }}{{ . }}{{ end }}
-
{{ $element.Vendor }}
-
- {{ end }}
-
-
-```
diff --git a/docs/content/en/functions/hugo/Environment.md b/docs/content/en/functions/hugo/Environment.md
deleted file mode 100644
index 551306255..000000000
--- a/docs/content/en/functions/hugo/Environment.md
+++ /dev/null
@@ -1,26 +0,0 @@
----
-title: hugo.Environment
-description: Returns the current running environment.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: string
- signatures: [hugo.Environment]
----
-
-The `hugo.Environment` function returns the current running [environment](g) as defined through the `--environment` command line flag.
-
-```go-html-template
-{{ hugo.Environment }} → production
-```
-
-Command line examples:
-
-Command|Environment
-:--|:--
-`hugo`|`production`
-`hugo --environment staging`|`staging`
-`hugo server`|`development`
-`hugo server --environment staging`|`staging`
diff --git a/docs/content/en/functions/hugo/Generator.md b/docs/content/en/functions/hugo/Generator.md
deleted file mode 100644
index dc72a7af2..000000000
--- a/docs/content/en/functions/hugo/Generator.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-title: hugo.Generator
-description: Renders an HTML meta element identifying the software that generated the site.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: template.HTML
- signatures: [hugo.Generator]
----
-
-```go-html-template
-{{ hugo.Generator }} →
-```
diff --git a/docs/content/en/functions/hugo/GoVersion.md b/docs/content/en/functions/hugo/GoVersion.md
deleted file mode 100644
index 94e310deb..000000000
--- a/docs/content/en/functions/hugo/GoVersion.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-title: hugo.GoVersion
-description: Returns the Go version used to compile the Hugo binary
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: string
- signatures: [hugo.GoVersion]
----
-
-```go-html-template
-{{ hugo.GoVersion }} → go1.21.1
-```
diff --git a/docs/content/en/functions/hugo/IsDevelopment.md b/docs/content/en/functions/hugo/IsDevelopment.md
deleted file mode 100644
index cea923acd..000000000
--- a/docs/content/en/functions/hugo/IsDevelopment.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: hugo.IsDevelopment
-description: Reports whether the current running environment is "development".
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: bool
- signatures: [hugo.IsDevelopment]
----
-
-{{< new-in 0.120.0 />}}
-
-```go-html-template
-{{ hugo.IsDevelopment }} → true/false
-```
diff --git a/docs/content/en/functions/hugo/IsExtended.md b/docs/content/en/functions/hugo/IsExtended.md
deleted file mode 100644
index ab7e0f7b1..000000000
--- a/docs/content/en/functions/hugo/IsExtended.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-title: hugo.IsExtended
-description: Reports whether the Hugo binary is either the extended or extended/deploy edition.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: bool
- signatures: [hugo.IsExtended]
----
-
-```go-html-template
-{{ hugo.IsExtended }} → true/false
-```
diff --git a/docs/content/en/functions/hugo/IsMultihost.md b/docs/content/en/functions/hugo/IsMultihost.md
deleted file mode 100644
index 605afa79a..000000000
--- a/docs/content/en/functions/hugo/IsMultihost.md
+++ /dev/null
@@ -1,39 +0,0 @@
----
-title: hugo.IsMultihost
-description: Reports whether each configured language has a unique base URL.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: bool
- signatures: [hugo.IsMultihost]
----
-
-{{< new-in 0.124.0 />}}
-
-Site configuration:
-
-{{< code-toggle file=hugo >}}
-defaultContentLanguage = 'de'
-defaultContentLanguageInSubdir = true
-[languages]
- [languages.de]
- baseURL = 'https://de.example.org/'
- languageCode = 'de-DE'
- languageName = 'Deutsch'
- title = 'Projekt Dokumentation'
- weight = 1
- [languages.en]
- baseURL = 'https://en.example.org/'
- languageCode = 'en-US'
- languageName = 'English'
- title = 'Project Documentation'
- weight = 2
-{{< /code-toggle >}}
-
-Template:
-
-```go-html-template
-{{ hugo.IsMultihost }} → true
-```
diff --git a/docs/content/en/functions/hugo/IsMultilingual.md b/docs/content/en/functions/hugo/IsMultilingual.md
deleted file mode 100644
index 85fc6550f..000000000
--- a/docs/content/en/functions/hugo/IsMultilingual.md
+++ /dev/null
@@ -1,37 +0,0 @@
----
-title: hugo.IsMultilingual
-description: Reports whether there are two or more configured languages.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: bool
- signatures: [hugo.IsMultilingual]
----
-
-{{< new-in 0.124.0 />}}
-
-Site configuration:
-
-{{< code-toggle file=hugo >}}
-defaultContentLanguage = 'de'
-defaultContentLanguageInSubdir = true
-[languages]
- [languages.de]
- languageCode = 'de-DE'
- languageName = 'Deutsch'
- title = 'Projekt Dokumentation'
- weight = 1
- [languages.en]
- languageCode = 'en-US'
- languageName = 'English'
- title = 'Project Documentation'
- weight = 2
-{{< /code-toggle >}}
-
-Template:
-
-```go-html-template
-{{ hugo.IsMultilingual }} → true
-```
diff --git a/docs/content/en/functions/hugo/IsProduction.md b/docs/content/en/functions/hugo/IsProduction.md
deleted file mode 100644
index e5433c239..000000000
--- a/docs/content/en/functions/hugo/IsProduction.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-title: hugo.IsProduction
-description: Reports whether the current running environment is "production".
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: bool
- signatures: [hugo.IsProduction]
----
-
-```go-html-template
-{{ hugo.IsProduction }} → true/false
-```
diff --git a/docs/content/en/functions/hugo/IsServer.md b/docs/content/en/functions/hugo/IsServer.md
deleted file mode 100644
index 840ff060d..000000000
--- a/docs/content/en/functions/hugo/IsServer.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: hugo.IsServer
-description: Reports whether the built-in development server is running.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: bool
- signatures: [hugo.IsServer]
----
-
-{{< new-in 0.120.0 />}}
-
-```go-html-template
-{{ hugo.IsServer }} → true/false
-```
diff --git a/docs/content/en/functions/hugo/Store.md b/docs/content/en/functions/hugo/Store.md
deleted file mode 100644
index 08c684146..000000000
--- a/docs/content/en/functions/hugo/Store.md
+++ /dev/null
@@ -1,117 +0,0 @@
----
-title: hugo.Store
-description: Returns a globally scoped "scratch pad" to store and manipulate data.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: maps.Scratch
- signatures: [hugo.Store]
----
-
-{{< new-in 0.139.0 />}}
-
-Use the `hugo.Store` function to create a globally scoped [scratch pad](g) to store and manipulate data. To create a scratch pad with a different [scope](g), refer to the [scope](#scope) section below.
-
-## Methods
-
-### Set
-
-Sets the value of the given key.
-
-```go-html-template
-{{ hugo.Store.Set "greeting" "Hello" }}
-```
-
-### Get
-
-Gets the value of the given key.
-
-```go-html-template
-{{ hugo.Store.Set "greeting" "Hello" }}
-{{ hugo.Store.Get "greeting" }} → Hello
-```
-
-### Add
-
-Adds the given value to the 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
-{{ hugo.Store.Set "greeting" "Hello" }}
-{{ hugo.Store.Add "greeting" "Welcome" }}
-{{ hugo.Store.Get "greeting" }} → HelloWelcome
-```
-
-```go-html-template
-{{ hugo.Store.Set "total" 3 }}
-{{ hugo.Store.Add "total" 7 }}
-{{ hugo.Store.Get "total" }} → 10
-```
-
-```go-html-template
-{{ hugo.Store.Set "greetings" (slice "Hello") }}
-{{ hugo.Store.Add "greetings" (slice "Welcome" "Cheers") }}
-{{ hugo.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
-{{ hugo.Store.SetInMap "greetings" "english" "Hello" }}
-{{ hugo.Store.SetInMap "greetings" "french" "Bonjour" }}
-{{ hugo.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
-{{ hugo.Store.SetInMap "greetings" "english" "Hello" }}
-{{ hugo.Store.SetInMap "greetings" "french" "Bonjour" }}
-{{ hugo.Store.DeleteInMap "greetings" "english" }}
-{{ hugo.Store.Get "greetings" }} → map[french:Bonjour]
- ```
-
-### GetSortedMapValues
-
-Returns an array of values from `key` sorted by `mapKey`.
-
-```go-html-template
-{{ hugo.Store.SetInMap "greetings" "english" "Hello" }}
-{{ hugo.Store.SetInMap "greetings" "french" "Bonjour" }}
-{{ hugo.Store.GetSortedMapValues "greetings" }} → [Hello Bonjour]
-```
-
-### Delete
-
-Removes the given key.
-
-```go-html-template
-{{ hugo.Store.Set "greeting" "Hello" }}
-{{ hugo.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, a partial template called by a shortcode, or by a Markdown render hook. 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 }}
-{{ hugo.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 }}
-{{ hugo.Store.Get "mykey" }}
-```
diff --git a/docs/content/en/functions/hugo/Version.md b/docs/content/en/functions/hugo/Version.md
deleted file mode 100644
index 7925af981..000000000
--- a/docs/content/en/functions/hugo/Version.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-title: hugo.Version
-description: Returns the current version of the Hugo binary.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: hugo.VersionString
- signatures: [hugo.Version]
----
-
-```go-html-template
-{{ hugo.Version }} → 0.144.2
-```
diff --git a/docs/content/en/functions/hugo/WorkingDir.md b/docs/content/en/functions/hugo/WorkingDir.md
deleted file mode 100644
index e50102558..000000000
--- a/docs/content/en/functions/hugo/WorkingDir.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-title: hugo.WorkingDir
-description: Returns the project working directory.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: string
- signatures: [hugo.WorkingDir]
----
-
-```go-html-template
-{{ hugo.WorkingDir }} → /home/user/projects/my-hugo-site
-```
diff --git a/docs/content/en/functions/hugo/_index.md b/docs/content/en/functions/hugo/_index.md
deleted file mode 100644
index b1c9216c4..000000000
--- a/docs/content/en/functions/hugo/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Hugo functions
-linkTitle: hugo
-description: Use these functions to access information about the Hugo application and the current environment.
-categories: []
-keywords: []
----
diff --git a/docs/content/en/functions/images/AutoOrient.md b/docs/content/en/functions/images/AutoOrient.md
deleted file mode 100644
index fd8d2ed14..000000000
--- a/docs/content/en/functions/images/AutoOrient.md
+++ /dev/null
@@ -1,48 +0,0 @@
----
-title: images.AutoOrient
-description: Returns an image filter that rotates and flips an image as needed per its EXIF orientation tag.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: images.filter
- signatures: [images.AutoOrient]
----
-
-{{< new-in 0.121.2 />}}
-
-## Usage
-
-Create the filter:
-
-```go-html-template
-{{ $filter := images.AutoOrient }}
-```
-
-{{% include "/_common/functions/images/apply-image-filter.md" %}}
-
-> [!note]
-> When using with other filters, specify `images.AutoOrient` first.
-
-```go-html-template
-{{ $filters := slice
- images.AutoOrient
- (images.Process "resize 200x")
-}}
-{{ with resources.Get "images/original.jpg" }}
- {{ with images.Filter $filters . }}
-
- {{ end }}
-{{ end }}
-```
-
-## Example
-
-{{< img
- src="images/examples/landscape-exif-orientation-5.jpg"
- alt="Zion National Park"
- filter="AutoOrient"
- filterArgs=""
- example=true
->}}
diff --git a/docs/content/en/functions/images/Brightness.md b/docs/content/en/functions/images/Brightness.md
deleted file mode 100644
index 0ddfcba55..000000000
--- a/docs/content/en/functions/images/Brightness.md
+++ /dev/null
@@ -1,33 +0,0 @@
----
-title: images.Brightness
-description: Returns an image filter that changes the brightness of an image.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: images.filter
- signatures: [images.Brightness PERCENTAGE]
----
-
-The percentage must be in the range [-100, 100] where 0 has no effect. A value of `-100` produces a solid black image, and a value of `100` produces a solid white image.
-
-## Usage
-
-Create the image filter:
-
-```go-html-template
-{{ $filter := images.Brightness 12 }}
-```
-
-{{% include "/_common/functions/images/apply-image-filter.md" %}}
-
-## Example
-
-{{< img
- src="images/examples/zion-national-park.jpg"
- alt="Zion National Park"
- filter="Brightness"
- filterArgs="12"
- example=true
->}}
diff --git a/docs/content/en/functions/images/ColorBalance.md b/docs/content/en/functions/images/ColorBalance.md
deleted file mode 100644
index be4a2bce8..000000000
--- a/docs/content/en/functions/images/ColorBalance.md
+++ /dev/null
@@ -1,33 +0,0 @@
----
-title: images.ColorBalance
-description: Returns an image filter that changes the color balance of an image.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: images.filter
- signatures: [images.ColorBalance PCTRED PCTGREEN PCTBLUE]
----
-
-The percentage for each channel (red, green, blue) must be in the range [-100, 500].
-
-## Usage
-
-Create the filter:
-
-```go-html-template
-{{ $filter := images.ColorBalance -10 10 50 }}
-```
-
-{{% include "/_common/functions/images/apply-image-filter.md" %}}
-
-## Example
-
-{{< img
- src="images/examples/zion-national-park.jpg"
- alt="Zion National Park"
- filter="ColorBalance"
- filterArgs="-10,10,50"
- example=true
->}}
diff --git a/docs/content/en/functions/images/Colorize.md b/docs/content/en/functions/images/Colorize.md
deleted file mode 100644
index 6b8cd5966..000000000
--- a/docs/content/en/functions/images/Colorize.md
+++ /dev/null
@@ -1,37 +0,0 @@
----
-title: images.Colorize
-description: Returns an image filter that produces a colorized version of an image.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: images.filter
- signatures: [images.Colorize HUE SATURATION PERCENTAGE]
----
-
-The hue is the angle on the color wheel, typically in the range [0, 360].
-
-The saturation must be in the range [0, 100].
-
-The percentage specifies the strength of the effect, and must be in the range [0, 100].
-
-## Usage
-
-Create the filter:
-
-```go-html-template
-{{ $filter := images.Colorize 180 50 20 }}
-```
-
-{{% include "/_common/functions/images/apply-image-filter.md" %}}
-
-## Example
-
-{{< img
- src="images/examples/zion-national-park.jpg"
- alt="Zion National Park"
- filter="Colorize"
- filterArgs="180,50,20"
- example=true
->}}
diff --git a/docs/content/en/functions/images/Config.md b/docs/content/en/functions/images/Config.md
deleted file mode 100644
index 59242fb95..000000000
--- a/docs/content/en/functions/images/Config.md
+++ /dev/null
@@ -1,30 +0,0 @@
----
-title: images.Config
-description: Returns an image.Config structure from the image at the specified path, relative to the working directory.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: image.Config
- signatures: [images.Config PATH]
-aliases: [/functions/imageconfig]
----
-
-See [image processing] for an overview of Hugo's image pipeline.
-
-```go-html-template
-{{ $ic := images.Config "/static/images/a.jpg" }}
-
-{{ $ic.Width }} → 600 (int)
-{{ $ic.Height }} → 400 (int)
-```
-
-Supported image formats include GIF, JPEG, PNG, TIFF, and WebP.
-
-> [!note]
-> This is a legacy function, superseded by the [`Width`] and [`Height`] methods for [global resources](g), [page resources](g), and [remote resources](g). See the [image processing] section for details.
-
-[`Height`]: /methods/resource/height/
-[`Width`]: /methods/resource/width/
-[image processing]: /content-management/image-processing/
diff --git a/docs/content/en/functions/images/Contrast.md b/docs/content/en/functions/images/Contrast.md
deleted file mode 100644
index f5d607440..000000000
--- a/docs/content/en/functions/images/Contrast.md
+++ /dev/null
@@ -1,33 +0,0 @@
----
-title: images.Contrast
-description: Returns an image filter that changes the contrast of an image.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: images.filter
- signatures: [images.Contrast PERCENTAGE]
----
-
-The percentage must be in the range [-100, 100] where 0 has no effect. A value of `-100` produces a solid grey image, and a value of `100` produces an over-contrasted image.
-
-## Usage
-
-Create the filter:
-
-```go-html-template
-{{ $filter := images.Contrast -20 }}
-```
-
-{{% include "/_common/functions/images/apply-image-filter.md" %}}
-
-## Example
-
-{{< img
- src="images/examples/zion-national-park.jpg"
- alt="Zion National Park"
- filter="Contrast"
- filterArgs="-20"
- example=true
->}}
diff --git a/docs/content/en/functions/images/Dither.md b/docs/content/en/functions/images/Dither.md
deleted file mode 100644
index eab7743f7..000000000
--- a/docs/content/en/functions/images/Dither.md
+++ /dev/null
@@ -1,156 +0,0 @@
----
-title: images.Dither
-description: Returns an image filter that dithers an image.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: images.filter
- signatures: ['images.Dither [OPTIONS]']
----
-
-{{< new-in 0.123.0 />}}
-
-## Options
-
-colors
-: (`string array`) A slice of two or more colors that make up the dithering palette, each expressed as an RGB or RGBA [hexadecimal] value, with or without a leading hash mark. The default values are opaque black (`000000ff`) and opaque white (`ffffffff`).
-
-[hexadecimal]: https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color
-
-method
-: (`string`) The dithering method. See the [dithering methods](#dithering-methods) section below for a list of the available methods. Default is `FloydSteinberg`.
-
-serpentine
-: (`bool`) Applicable to error diffusion dithering methods, whether to apply the error diffusion matrix in a serpentine manner, meaning that it goes right-to-left every other line. This greatly reduces line-type artifacts. Default is `true`.
-
-strength
-: (`float`) The strength at which to apply the dithering matrix, typically a value in the range [0, 1]. A value of `1.0` applies the dithering matrix at 100% strength (no modification of the dither matrix). The `strength` is inversely proportional to contrast; reducing the strength increases the contrast. Setting `strength` to a value such as `0.8` can be useful to reduce noise in the dithered image. Default is `1.0`.
-
-## Usage
-
-Create the options map:
-
-```go-html-template
-{{ $opts := dict
- "colors" (slice "222222" "808080" "dddddd")
- "method" "ClusteredDot4x4"
- "strength" 0.85
-}}
-```
-
-Create the filter:
-
-```go-html-template
-{{ $filter := images.Dither $opts }}
-```
-
-Or create the filter using the default settings:
-
-```go-html-template
-{{ $filter := images.Dither }}
-```
-
-{{% include "/_common/functions/images/apply-image-filter.md" %}}
-
-## Dithering methods
-
-See the [Go documentation] for descriptions of each of the dithering methods below.
-
-[Go documentation]: https://pkg.go.dev/github.com/makeworld-the-better-one/dither/v2#pkg-variables
-
-Error diffusion dithering methods:
-
-- Atkinson
-- Burkes
-- FalseFloydSteinberg
-- FloydSteinberg
-- JarvisJudiceNinke
-- Sierra
-- Sierra2
-- Sierra2_4A
-- Sierra3
-- SierraLite
-- Simple2D
-- StevenPigeon
-- Stucki
-- TwoRowSierra
-
-Ordered dithering methods:
-
-- ClusteredDot4x4
-- ClusteredDot6x6
-- ClusteredDot6x6_2
-- ClusteredDot6x6_3
-- ClusteredDot8x8
-- ClusteredDotDiagonal16x16
-- ClusteredDotDiagonal6x6
-- ClusteredDotDiagonal8x8
-- ClusteredDotDiagonal8x8_2
-- ClusteredDotDiagonal8x8_3
-- ClusteredDotHorizontalLine
-- ClusteredDotSpiral5x5
-- ClusteredDotVerticalLine
-- Horizontal3x5
-- Vertical5x3
-
-## Example
-
-This example uses the default dithering options.
-
-{{< img
- src="images/examples/zion-national-park.jpg"
- alt="Zion National Park"
- filter="Dither"
- filterArgs=""
- example=true
->}}
-
-## Recommendations
-
-Regardless of dithering method, do both of the following to obtain the best results:
-
-1. Scale the image _before_ dithering
-1. Output the image to a lossless format such as GIF or PNG
-
-The example below does both of these, and it sets the dithering palette to the three most dominant colors in the image.
-
-```go-html-template
-{{ with resources.Get "original.jpg" }}
- {{ $opts := dict
- "method" "ClusteredDotSpiral5x5"
- "colors" (first 3 .Colors)
- }}
- {{ $filters := slice
- (images.Process "resize 800x")
- (images.Dither $opts)
- (images.Process "png")
- }}
- {{ with . | images.Filter $filters }}
-
- {{ end }}
-{{ end }}
-```
-
-For best results, if the dithering palette is grayscale, convert the image to grayscale before dithering.
-
-```go-html-template
-{{ $opts := dict "colors" (slice "222" "808080" "ddd") }}
-{{ $filters := slice
- (images.Process "resize 800x")
- (images.Grayscale)
- (images.Dither $opts)
- (images.Process "png")
-}}
-{{ with images.Filter $filters . }}
-
-{{ end }}
-```
-
-The example above:
-
-1. Resizes the image to be 800 px wide
-1. Converts the image to grayscale
-1. Dithers the image using the default (`FloydSteinberg`) dithering method with a grayscale palette
-1. Converts the image to the PNG format
diff --git a/docs/content/en/functions/images/Filter.md b/docs/content/en/functions/images/Filter.md
deleted file mode 100644
index 1f2c268be..000000000
--- a/docs/content/en/functions/images/Filter.md
+++ /dev/null
@@ -1,65 +0,0 @@
----
-title: images.Filter
-description: Applies one or more image filters to the given image resource.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: images.ImageResource
- signatures: [images.Filter FILTERS... IMAGE]
----
-
-Apply one or more [image filters](#image-filters) to the given image.
-
-To apply a single filter:
-
-```go-html-template
-{{ with resources.Get "images/original.jpg" }}
- {{ with images.Filter images.Grayscale . }}
-
- {{ end }}
-{{ end }}
-```
-
-To apply two or more filters, executing from left to right:
-
-```go-html-template
-{{ $filters := slice
- images.Grayscale
- (images.GaussianBlur 8)
-}}
-{{ with resources.Get "images/original.jpg" }}
- {{ with images.Filter $filters . }}
-
- {{ end }}
-{{ end }}
-```
-
-You can also apply image filters using the [`Filter`] method on a `Resource` object.
-
-[`Filter`]: /methods/resource/filter/
-
-## Example
-
-```go-html-template
-{{ with resources.Get "images/original.jpg" }}
- {{ with images.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 `images.Filter` function, or with the `Filter` method on a `Resource` object.
-
-{{% list-pages-in-section path=/functions/images filter=functions_images_no_filters filterType=exclude %}}
diff --git a/docs/content/en/functions/images/Gamma.md b/docs/content/en/functions/images/Gamma.md
deleted file mode 100644
index d8cb076f1..000000000
--- a/docs/content/en/functions/images/Gamma.md
+++ /dev/null
@@ -1,33 +0,0 @@
----
-title: images.Gamma
-description: Returns an image filter that performs gamma correction on an image.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: images.filter
- signatures: [images.Gamma GAMMA]
----
-
-The gamma value must be positive. A value greater than 1 lightens the image, while a value less than 1 darkens the image. The filter has no effect when the gamma value is 1.
-
-## Usage
-
-Create the filter:
-
-```go-html-template
-{{ $filter := images.Gamma 1.667 }}
-```
-
-{{% include "/_common/functions/images/apply-image-filter.md" %}}
-
-## Example
-
-{{< img
- src="images/examples/zion-national-park.jpg"
- alt="Zion National Park"
- filter="Gamma"
- filterArgs="1.667"
- example=true
->}}
diff --git a/docs/content/en/functions/images/GaussianBlur.md b/docs/content/en/functions/images/GaussianBlur.md
deleted file mode 100644
index c5eb136e2..000000000
--- a/docs/content/en/functions/images/GaussianBlur.md
+++ /dev/null
@@ -1,33 +0,0 @@
----
-title: images.GaussianBlur
-description: Returns an image filter that applies a gaussian blur to an image.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: images.filter
- signatures: [images.GaussianBlur SIGMA]
----
-
-The sigma value must be positive, and indicates how much the image will be blurred. The blur-affected radius is approximately 3 times the sigma value.
-
-## Usage
-
-Create the filter:
-
-```go-html-template
-{{ $filter := images.GaussianBlur 5 }}
-```
-
-{{% include "/_common/functions/images/apply-image-filter.md" %}}
-
-## Example
-
-{{< img
- src="images/examples/zion-national-park.jpg"
- alt="Zion National Park"
- filter="GaussianBlur"
- filterArgs="5"
- example=true
->}}
diff --git a/docs/content/en/functions/images/Grayscale.md b/docs/content/en/functions/images/Grayscale.md
deleted file mode 100644
index d3651b8dc..000000000
--- a/docs/content/en/functions/images/Grayscale.md
+++ /dev/null
@@ -1,31 +0,0 @@
----
-title: images.Grayscale
-description: Returns an image filter that produces a grayscale version of an image.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: images.filter
- signatures: [images.Grayscale]
----
-
-## Usage
-
-Create the filter:
-
-```go-html-template
-{{ $filter := images.Grayscale }}
-```
-
-{{% include "/_common/functions/images/apply-image-filter.md" %}}
-
-## Example
-
-{{< img
- src="images/examples/zion-national-park.jpg"
- alt="Zion National Park"
- filter="Grayscale"
- filterArgs=""
- example=true
->}}
diff --git a/docs/content/en/functions/images/Hue.md b/docs/content/en/functions/images/Hue.md
deleted file mode 100644
index f334eebd8..000000000
--- a/docs/content/en/functions/images/Hue.md
+++ /dev/null
@@ -1,33 +0,0 @@
----
-title: images.Hue
-description: Returns an image filter that rotates the hue of an image.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: images.filter
- signatures: [images.Hue SHIFT]
----
-
-The hue angle shift is typically in the range [-180, 180] where 0 has no effect.
-
-## Usage
-
-Create the filter:
-
-```go-html-template
-{{ $filter := images.Hue -15 }}
-```
-
-{{% include "/_common/functions/images/apply-image-filter.md" %}}
-
-## Example
-
-{{< img
- src="images/examples/zion-national-park.jpg"
- alt="Zion National Park"
- filter="Hue"
- filterArgs="-15"
- example=true
->}}
diff --git a/docs/content/en/functions/images/Invert.md b/docs/content/en/functions/images/Invert.md
deleted file mode 100644
index 0f9f9a9d2..000000000
--- a/docs/content/en/functions/images/Invert.md
+++ /dev/null
@@ -1,31 +0,0 @@
----
-title: images.Invert
-description: Returns an image filter that negates the colors of an image.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: images.filter
- signatures: [images.Invert]
----
-
-## Usage
-
-Create the filter:
-
-```go-html-template
-{{ $filter := images.Invert }}
-```
-
-{{% include "/_common/functions/images/apply-image-filter.md" %}}
-
-## Example
-
-{{< img
- src="images/examples/zion-national-park.jpg"
- alt="Zion National Park"
- filter="Invert"
- filterArgs=""
- example=true
->}}
diff --git a/docs/content/en/functions/images/Mask.md b/docs/content/en/functions/images/Mask.md
deleted file mode 100644
index 4f3b4aa3f..000000000
--- a/docs/content/en/functions/images/Mask.md
+++ /dev/null
@@ -1,75 +0,0 @@
----
-title: images.Mask
-description: Returns an image filter that applies a mask to the source image.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: images.filter
- signatures: [images.Mask RESOURCE]
----
-
-{{< new-in 0.141.0 />}}
-
-The `images.Mask` filter applies a mask to an image. Black pixels in the mask make the corresponding areas of the base image transparent, while white pixels keep them opaque. Color images are converted to grayscale for masking purposes. The mask is automatically resized to match the dimensions of the base image.
-
-> [!note]
-> Of the formats supported by Hugo's imaging pipeline, only PNG and WebP have an alpha channel to support transparency. If your source image has a different format and you require transparent masked areas, convert it to either PNG or WebP as shown in the example below.
-
-When applying a mask to a non-transparent image format such as JPEG, the masked areas will be filled with the color specified by the `bgColor` parameter in your [site configuration]. You can override that color with a `Process` image filter:
-
-```go-html-template
-{{ $filter := images.Process "#00ff00" }}
-```
-
-## Usage
-
-Create a slice of filters, one for WebP conversion and the other for mask application:
-
-```go-html-template
-{{ $filter1 := images.Process "webp" }}
-{{ $filter2 := images.Mask (resources.Get "images/mask.png") }}
-{{ $filters := slice $filter1 $filter2 }}
-```
-
-Apply the filters using the [`images.Filter`] function:
-
-```go-html-template
-{{ with resources.Get "images/original.jpg" }}
- {{ with . | images.Filter $filters }}
-
- {{ end }}
-{{ end }}
-```
-
-You can also apply the filter using the [`Filter`] method on a 'Resource' object:
-
-```go-html-template
-{{ with resources.Get "images/original.jpg" }}
- {{ with .Filter $filters }}
-
- {{ end }}
-{{ end }}
-```
-
-## Example
-
-Mask
-
-{{< img
- src="images/examples/mask.png"
- example=false
->}}
-
-{{< img
- src="images/examples/zion-national-park.jpg"
- alt="Zion National Park"
- filter="mask"
- filterArgs="images/examples/mask.png"
- example=true
->}}
-
-[`Filter`]: /methods/resource/filter/
-[`images.Filter`]: /functions/images/filter/
-[site configuration]: /configuration/imaging/
diff --git a/docs/content/en/functions/images/Opacity.md b/docs/content/en/functions/images/Opacity.md
deleted file mode 100644
index b9dcf3fd2..000000000
--- a/docs/content/en/functions/images/Opacity.md
+++ /dev/null
@@ -1,49 +0,0 @@
----
-title: images.Opacity
-description: Returns an image filter that changes the opacity of an image.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: images.filter
- signatures: [images.Opacity OPACITY]
----
-
-{{< new-in 0.119.0 />}}
-
-The opacity value must be in the range [0, 1]. A value of `0` produces a transparent image, and a value of `1` produces an opaque image (no transparency).
-
-## Usage
-
-Create the filter:
-
-```go-html-template
-{{ $filter := images.Opacity 0.65 }}
-```
-
-{{% include "/_common/functions/images/apply-image-filter.md" %}}
-
-The `images.Opacity` filter is most useful for target formats such as PNG and WebP that support transparency. If the source image does not support transparency, combine this filter with the `images.Process` filter:
-
-```go-html-template
-{{ with resources.Get "images/original.jpg" }}
- {{ $filters := slice
- (images.Opacity 0.65)
- (images.Process "png")
- }}
- {{ with . | images.Filter $filters }}
-
- {{ end }}
-{{ end }}
-```
-
-## Example
-
-{{< img
- src="images/examples/zion-national-park.jpg"
- alt="Zion National Park"
- filter="Opacity"
- filterArgs="0.65"
- example=true
->}}
diff --git a/docs/content/en/functions/images/Overlay.md b/docs/content/en/functions/images/Overlay.md
deleted file mode 100644
index 8e5eec3d1..000000000
--- a/docs/content/en/functions/images/Overlay.md
+++ /dev/null
@@ -1,45 +0,0 @@
----
-title: images.Overlay
-description: Returns an image filter that overlays the source image at the given coordinates, relative to the upper left corner.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: images.filter
- signatures: [images.Overlay RESOURCE X Y]
----
-
-## Usage
-
-Capture the overlay image as a resource:
-
-```go-html-template
-{{ $overlay := "" }}
-{{ $path := "images/logo.png" }}
-{{ with resources.Get $path }}
- {{ $overlay = . }}
-{{ else }}
- {{ errorf "Unable to get resource %q" $path }}
-{{ end }}
-```
-
-The overlay image can be a [global resource](g), a [page resource](g), or a [remote resource](g).
-
-Create the filter:
-
-```go-html-template
-{{ $filter := images.Overlay $overlay 20 20 }}
-```
-
-{{% include "/_common/functions/images/apply-image-filter.md" %}}
-
-## Example
-
-{{< img
- src="images/examples/zion-national-park.jpg"
- alt="Zion National Park"
- filter="Overlay"
- filterArgs="images/logos/logo-64x64.png,20,20"
- example=true
->}}
diff --git a/docs/content/en/functions/images/Padding.md b/docs/content/en/functions/images/Padding.md
deleted file mode 100644
index da15a44ca..000000000
--- a/docs/content/en/functions/images/Padding.md
+++ /dev/null
@@ -1,72 +0,0 @@
----
-title: images.Padding
-description: Returns an image filter that resizes the image canvas without resizing the image.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: images.filter
- signatures: ['images.Padding V1 [V2] [V3] [V4] [COLOR]']
----
-
-{{< new-in 0.120.0 />}}
-
-The last argument is the canvas color, expressed as an RGB or RGBA [hexadecimal color]. The default value is `ffffffff` (opaque white). The preceding arguments are the padding values, in pixels, using the CSS [shorthand property] syntax. Negative padding values will crop the image.
-
-[hexadecimal color]: https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color
-[shorthand property]: https://developer.mozilla.org/en-US/docs/Web/CSS/Shorthand_properties#edges_of_a_box
-
-## Usage
-
-Create the filter:
-
-```go-html-template
-{{ $filter := images.Padding 20 40 "#976941" }}
-```
-
-{{% include "/_common/functions/images/apply-image-filter.md" %}}
-
-Combine with the [`Colors`] method to create a border with one of the image's most dominant colors:
-
-[`Colors`]: /methods/resource/colors/
-
-```go-html-template
-{{ with resources.Get "images/original.jpg" }}
- {{ $filter := images.Padding 20 40 (index .Colors 2) }}
- {{ with . | images.Filter $filter }}
-
- {{ end }}
-{{ end }}
-```
-
-## Example
-
-{{< img
- src="images/examples/zion-national-park.jpg"
- alt="Zion National Park"
- filter="Padding"
- filterArgs="20,40,20,40,#976941"
- example=true
->}}
-
-## Other recipes
-
-This example resizes an image to 300px wide, converts it to the WebP format, adds 20px vertical padding and 50px horizontal padding, then sets the canvas color to dark green with 33% opacity.
-
-Conversion to WebP is required to support transparency. PNG and WebP images have an alpha channel; JPEG and GIF do not.
-
-```go-html-template
-{{ $img := resources.Get "images/a.jpg" }}
-{{ $filters := slice
- (images.Process "resize 300x webp")
- (images.Padding 20 50 "#0705")
-}}
-{{ $img = $img.Filter $filters }}
-```
-
-To add a 2px gray border to an image:
-
-```go-html-template
-{{ $img = $img.Filter (images.Padding 2 "#777") }}
-```
diff --git a/docs/content/en/functions/images/Pixelate.md b/docs/content/en/functions/images/Pixelate.md
deleted file mode 100644
index 954950c8b..000000000
--- a/docs/content/en/functions/images/Pixelate.md
+++ /dev/null
@@ -1,31 +0,0 @@
----
-title: images.Pixelate
-description: Returns an image filter that applies a pixelation effect to an image.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: images.filter
- signatures: [images.Pixelate SIZE]
----
-
-## Usage
-
-Create the filter:
-
-```go-html-template
-{{ $filter := images.Pixelate 4 }}
-```
-
-{{% include "/_common/functions/images/apply-image-filter.md" %}}
-
-## Example
-
-{{< img
- src="images/examples/zion-national-park.jpg"
- alt="Zion National Park"
- filter="Pixelate"
- filterArgs="4"
- example=true
->}}
diff --git a/docs/content/en/functions/images/Process.md b/docs/content/en/functions/images/Process.md
deleted file mode 100644
index 134c40c5a..000000000
--- a/docs/content/en/functions/images/Process.md
+++ /dev/null
@@ -1,110 +0,0 @@
----
-title: images.Process
-description: Returns an image filter that processes the given image using the given specification.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: images.filter
- signatures: [images.Process SPEC]
----
-
-{{< new-in 0.119.0 />}}
-
-This filter has the same options as the [`Process`] method on a `Resource` object, but using it as a filter may be more effective if you need to apply multiple filters to an image.
-
-[`Process`]: /methods/resource/process/
-
-The process specification is a space-delimited, case-insensitive list of one or more of the following in any sequence:
-
-action
-: Specify zero or one of `crop`, `fill`, `fit`, or `resize`. If you specify an action you must also provide dimensions. See [details](content-management/image-processing/#image-processing-methods).
-
-```go-html-template
-{{ $filter := images.Process "resize 300x" }}
-```
-
-dimensions
-: Required if you specify an action. Provide width _or_ height when using `resize`, else provide both width _and_ height. See [details](/content-management/image-processing/#dimensions).
-
-```go-html-template
-{{ $filter := images.Process "crop 200x200" }}
-```
-
-anchor
-: Use with the `crop` or `fill` action. Specify zero or one of `TopLeft`, `Top`, `TopRight`, `Left`, `Center`, `Right`, `BottomLeft`, `Bottom`, `BottomRight`, or `Smart`. Default is `Smart`. See [details](/content-management/image-processing/#anchor).
-
-```go-html-template
-{{ $filter := images.Process "crop 200x200 center" }}
-```
-
-rotation
-: Typically specify zero or one of `r90`, `r180`, or `r270`. Also supports arbitrary rotation angles. See [details](/content-management/image-processing/#rotation).
-
-```go-html-template
-{{ $filter := images.Process "r90" }}
-{{ $filter := images.Process "crop 200x200 center r90" }}
-```
-
-target format
-: Specify zero or one of `gif`, `jpeg`, `png`, `tiff`, or `webp`. See [details](/content-management/image-processing/#target-format).
-
-```go-html-template
-{{ $filter := images.Process "webp" }}
-{{ $filter := images.Process "crop 200x200 center r90 webp" }}
-```
-
-quality
-: Applicable to JPEG and WebP images. Optionally specify `qN` where `N` is an integer in the range [0, 100]. Default is `75`. See [details](/content-management/image-processing/#quality).
-
-```go-html-template
-{{ $filter := images.Process "q50" }}
-{{ $filter := images.Process "crop 200x200 center r90 webp q50" }}
-```
-
-hint
-: Applicable to WebP images and equivalent to the `-preset` flag for the [`cwebp`] encoder. Specify zero or one of `drawing`, `icon`, `photo`, `picture`, or `text`. Default is `photo`. See [details](/content-management/image-processing/#hint).
-
-[`cwebp`]: https://developers.google.com/speed/webp/docs/cwebp
-
-```go-html-template
-{{ $filter := images.Process "webp" "icon" }}
-{{ $filter := images.Process "crop 200x200 center r90 webp q50 icon" }}
-```
-
-background color
-: When converting a PNG or WebP with transparency to a format that does not support transparency, optionally specify a background color using a 3-digit or a 6-digit hexadecimal color code. Default is `#ffffff` (white). See [details](/content-management/image-processing/#background-color).
-
-```go-html-template
-{{ $filter := images.Process "jpeg #000" }}
-{{ $filter := images.Process "crop 200x200 center r90 q50 jpeg #000" }}
-```
-
-resampling filter
-: Typically specify zero or one of `Box`, `Lanczos`, `CatmullRom`, `MitchellNetravali`, `Linear`, or `NearestNeighbor`. Other resampling filters are available. See [details](/content-management/image-processing/#resampling-filter).
-
-```go-html-template
-{{ $filter := images.Process "resize 300x lanczos" }}
-{{ $filter := images.Process "resize 300x r90 q50 jpeg #000 lanczos" }}
-```
-
-## Usage
-
-Create a filter:
-
-```go-html-template
-{{ $filter := images.Process "resize 256x q40 webp" }}
-```
-
-{{% include "/_common/functions/images/apply-image-filter.md" %}}
-
-## Example
-
-{{< img
- src="images/examples/zion-national-park.jpg"
- alt="Zion National Park"
- filter="Process"
- filterArgs="resize 256x q40 webp"
- example=true
->}}
diff --git a/docs/content/en/functions/images/QR.md b/docs/content/en/functions/images/QR.md
deleted file mode 100644
index eee2dff14..000000000
--- a/docs/content/en/functions/images/QR.md
+++ /dev/null
@@ -1,138 +0,0 @@
----
-title: images.QR
-description: Encodes the given text into a QR code using the specified options, returning an image resource.
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: images.ImageResource
- signatures: ['images.QR TEXT [OPTIONS]']
----
-
-{{< new-in 0.141.0 />}}
-
-The `images.QR` function encodes the given text into a [QR code] using the specified options, returning an image resource. The size of the generated image depends on three factors:
-
-- Data length: Longer text necessitates a larger image to accommodate the increased information density.
-- Error correction level: Higher error correction levels enhance the QR code's resistance to damage, but this typically results in a slightly larger image size to maintain readability.
-- Pixels per module: The number of image pixels assigned to each individual module (the smallest unit of the QR code) directly impacts the overall image size. A higher pixel count per module leads to a larger, higher-resolution image.
-
-Although the default option values are sufficient for most applications, you should test the rendered QR code both on-screen and in print.
-
-## Options
-
-level
-: (`string`) The error correction level to use when encoding the text, one of `low`, `medium`, `quartile`, or `high`. Default is `medium`.
-
- Error correction level|Redundancy
- :--|:--|:--
- low|20%
- medium|38%
- quartile|55%
- high|65%
-
-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. Use Unix-style slashes (`/`) to separarate path segments. If empty or not provided, the image is placed directly in the `publishDir` root. Hugo automatically creates the necessary subdirectories if they don't exist.
-
-## Examples
-
-To create a QR code using the default values for `level` and `scale`:
-
-```go-html-template
-{{ $text := "https://gohugo.io" }}
-{{ with images.QR $text }}
-
-{{ end }}
-```
-
-{{< qr text="https://gohugo.io" class="qrcode" targetDir="images/qr" />}}
-
-Specify `level`, `scale`, and `targetDir` as needed to achieve the desired result:
-
-```go-html-template
-{{ $text := "https://gohugo.io" }}
-{{ $opts := dict
- "level" "high"
- "scale" 3
- "targetDir" "images/qr"
-}}
-{{ with images.QR $text $opts }}
-
-{{ end }}
-```
-
-{{< qr text="https://gohugo.io" level="high" scale=3 targetDir="codes" class="qrcode" targetDir="images/qr" />}}
-
-To include a QR code that points to the `Permalink` of the current page:
-
-```go-html-template {file="layouts/_default/single.html"}
-{{ with images.QR .Permalink }}
-
-{{ end }}
-```
-
-Then hide the QR code with CSS unless printing the page:
-
-```css
-/* Hide QR code by default */
-.qr-code {
- display: none;
-}
-
-/* Show QR code when printing */
-@media print {
- .qr-code {
- display: block;
- }
-}
-```
-
-## Scale
-
-As you decrease the size of a QR code, the maximum distance at which it can be reliably scanned by a device also decreases.
-
-In the example above, we set the `scale` to `2`, resulting in a QR code where each module consists of 2x2 pixels. While this might be sufficient for on-screen display, it's likely to be problematic when printed at 600 dpi.
-
-\[ \frac{2\:px}{module} \times \frac{1\:inch}{600\:px} \times \frac{25.4\:mm}{1\:inch} = \frac{0.085\:mm}{module} \]
-
-This module size is half of the commonly recommended minimum of 0.170 mm.\
-If the QR code will be printed, use the default `scale` value of `4` pixels per module.
-
-Avoid using Hugo's image processing methods to resize QR codes. Resizing can introduce blurring due to anti-aliasing when a QR code module occupies a fractional number of pixels.
-
-> [!note]
-> Always test the rendered QR code both on-screen and in print.
-
-## Shortcode
-
-Call the `qr` shortcode to insert a QR code into your content.
-
-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 */>}}
-```
-
-The `qr` shortcode accepts several arguments including `level` and `scale`. See the [related documentation] for details.
-
-[`publishDir`]: /configuration/all/#publishdir
-[QR code]: https://en.wikipedia.org/wiki/QR_code
-[related documentation]: /shortcodes/qr/
diff --git a/docs/content/en/functions/images/Saturation.md b/docs/content/en/functions/images/Saturation.md
deleted file mode 100644
index d1dd48b24..000000000
--- a/docs/content/en/functions/images/Saturation.md
+++ /dev/null
@@ -1,33 +0,0 @@
----
-title: images.Saturation
-description: Returns an image filter that changes the saturation of an image.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: images.filter
- signatures: [images.Saturation PERCENTAGE]
----
-
-The percentage must be in the range [-100, 500] where 0 has no effect.
-
-## Usage
-
-Create the filter:
-
-```go-html-template
-{{ $filter := images.Saturation 65 }}
-```
-
-{{% include "/_common/functions/images/apply-image-filter.md" %}}
-
-## Example
-
-{{< img
- src="images/examples/zion-national-park.jpg"
- alt="Zion National Park"
- filter="Saturation"
- filterArgs="65"
- example=true
->}}
diff --git a/docs/content/en/functions/images/Sepia.md b/docs/content/en/functions/images/Sepia.md
deleted file mode 100644
index ae43045db..000000000
--- a/docs/content/en/functions/images/Sepia.md
+++ /dev/null
@@ -1,33 +0,0 @@
----
-title: images.Sepia
-description: Returns an image filter that produces a sepia-toned version of an image.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: images.filter
- signatures: [images.Sepia PERCENTAGE]
----
-
-The percentage must be in the range [0, 100] where 0 has no effect.
-
-## Usage
-
-Create the filter:
-
-```go-html-template
-{{ $filter := images.Sepia 75 }}
-```
-
-{{% include "/_common/functions/images/apply-image-filter.md" %}}
-
-## Example
-
-{{< img
- src="images/examples/zion-national-park.jpg"
- alt="Zion National Park"
- filter="Sepia"
- filterArgs="75"
- example=true
->}}
diff --git a/docs/content/en/functions/images/Sigmoid.md b/docs/content/en/functions/images/Sigmoid.md
deleted file mode 100644
index 9bfcaf91b..000000000
--- a/docs/content/en/functions/images/Sigmoid.md
+++ /dev/null
@@ -1,37 +0,0 @@
----
-title: images.Sigmoid
-description: Returns an image filter that changes the contrast of an image using a sigmoidal function.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: images.filter
- signatures: [images.Sigmoid MIDPOINT FACTOR]
----
-
-This is a non-linear contrast change useful for photo adjustments; it preserves highlight and shadow detail.
-
-The midpoint is the midpoint of contrast. It must be in the range [0, 1], typically 0.5.
-
-The factor indicates how much to increase or decrease the contrast, typically in the range [-10, 10] where 0 has no effect. A positive value increases contrast, while a negative value decrease contrast.
-
-## Usage
-
-Create the filter:
-
-```go-html-template
-{{ $filter := images.Sigmoid 0.6 -4 }}
-```
-
-{{% include "/_common/functions/images/apply-image-filter.md" %}}
-
-## Example
-
-{{< img
- src="images/examples/zion-national-park.jpg"
- alt="Zion National Park"
- filter="Sigmoid"
- filterArgs="0.6,-4"
- example=true
->}}
diff --git a/docs/content/en/functions/images/Text.md b/docs/content/en/functions/images/Text.md
deleted file mode 100644
index 8f7e730ba..000000000
--- a/docs/content/en/functions/images/Text.md
+++ /dev/null
@@ -1,121 +0,0 @@
----
-title: images.Text
-description: Returns an image filter that adds text to an image.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: images.filter
- signatures: ['images.Text TEXT [OPTIONS]']
----
-
-## Options
-
-Although none of the options are required, at a minimum you will want to set the `size` to be some reasonable percentage of the image height.
-
-alignx
-: {{< new-in 0.141.0 />}}
-: (`string`) The horizontal alignment of the text relative to the horizontal offset, one of `left`, `center`, or `right`. Default is `left`.
-
-aligny
-: (`string`) The vertical alignment of the text relative to the vertical offset, one of `top`, `center`, or `bottom`. Default is `top`.
-
-color
-: (`string`) The font color, either a 3-digit or 6-digit hexadecimal color code. Default is `#ffffff` (white).
-
-font
-: (`resource.Resource`) The font can be a [global resource](g), a [page resource](g), or a [remote resource](g). Default is [Go Regular], a proportional sans-serif TrueType font.
-
-linespacing
-: (`int`) The number of pixels between each line. For a line height of 1.4, set the `linespacing` to 0.4 multiplied by the `size`. Default is `2`.
-
-size
-: (`int`) The font size in pixels. Default is `20`.
-
-x
-: (`int`) The horizontal offset, in pixels, relative to the left of the image. Default is `10`.
-
-y
-: (`int`) The vertical offset, in pixels, relative to the top of the image. Default is `10`.
-
-[Go Regular]: https://go.dev/blog/go-fonts#sans-serif
-
-## Usage
-
-Set the text and paths:
-
-```go-html-template
-{{ $text := "Zion National Park" }}
-{{ $fontPath := "https://github.com/google/fonts/raw/main/ofl/lato/Lato-Regular.ttf" }}
-{{ $imagePath := "images/original.jpg" }}
-```
-
-Capture the font as a resource:
-
-```go-html-template
-{{ $font := "" }}
-{{ with try (resources.GetRemote $fontPath) }}
- {{ with .Err }}
- {{ errorf "%s" . }}
- {{ else with .Value }}
- {{ $font = . }}
- {{ else }}
- {{ errorf "Unable to get resource %s" $fontPath }}
- {{ end }}
-{{ end }}
-```
-
-Create the filter, centering the text horizontally and vertically:
-
-```go-html-template
-{{ $r := "" }}
-{{ $filter := "" }}
-{{ with $r = resources.Get $imagePath }}
- {{ $opts := dict
- "alignx" "center"
- "color" "#fbfaf5"
- "font" $font
- "linespacing" 8
- "size" 60
- "x" (mul .Width 0.5 | int)
- "y" (mul .Height 0.5 | int)
- }}
- {{ $filter = images.Text $text $opts }}
-{{ else }}
- {{ errorf "Unable to get resource %s" $imagePath }}
-{{ end }}
-```
-
-Apply the filter using the [`images.Filter`] function:
-
-```go-html-template
-{{ with $r }}
- {{ with . | images.Filter $filter }}
-
- {{ end }}
-{{ end }}
-```
-
-You can also apply the filter using the [`Filter`] method on a `Resource` object:
-
-```go-html-template
-{{ with $r }}
- {{ with .Filter $filter }}
-
- {{ end }}
-{{ end }}
-```
-
-[`images.Filter`]: /functions/images/filter/
-[`Filter`]: /methods/resource/filter/
-
-## Example
-
-{{< img
- src="images/examples/zion-national-park.jpg"
- alt="Zion National Park"
- filter="Text"
- filterArgs="Zion National Park,25,190,40,1.2,#fbfaf5"
- example=true
->}}
diff --git a/docs/content/en/functions/images/UnsharpMask.md b/docs/content/en/functions/images/UnsharpMask.md
deleted file mode 100644
index 9c06eb5e1..000000000
--- a/docs/content/en/functions/images/UnsharpMask.md
+++ /dev/null
@@ -1,37 +0,0 @@
----
-title: images.UnsharpMask
-description: Returns an image filter that sharpens an image.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: images.filter
- signatures: [images.UnsharpMask SIGMA AMOUNT THRESHOLD]
----
-
-The sigma argument is used in a gaussian function and affects the radius of effect. Sigma must be positive. The sharpen radius is approximately 3 times the sigma value.
-
-The amount argument controls how much darker and how much lighter the edge borders become. Typically between 0.5 and 1.5.
-
-The threshold argument controls the minimum brightness change that will be sharpened. Typically between 0 and 0.05.
-
-## Usage
-
-Create the filter:
-
-```go-html-template
-{{ $filter := images.UnsharpMask 10 0.4 0.03 }}
-```
-
-{{% include "/_common/functions/images/apply-image-filter.md" %}}
-
-## Example
-
-{{< img
- src="images/examples/zion-national-park.jpg"
- alt="Zion National Park"
- filter="UnsharpMask"
- filterArgs="10,0.4,0.03"
- example=true
->}}
diff --git a/docs/content/en/functions/images/_index.md b/docs/content/en/functions/images/_index.md
deleted file mode 100644
index f92e16e7a..000000000
--- a/docs/content/en/functions/images/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Image functions
-linkTitle: images
-description: Use these functions to create an image filter, apply an image filter to an image, and to retrieve image information.
-categories: []
-keywords: []
----
diff --git a/docs/content/en/functions/inflect/Humanize.md b/docs/content/en/functions/inflect/Humanize.md
deleted file mode 100644
index d3d785243..000000000
--- a/docs/content/en/functions/inflect/Humanize.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-title: inflect.Humanize
-description: Returns the humanized version of the input with the first letter capitalized.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [humanize]
- returnType: string
- signatures: [inflect.Humanize INPUT]
-aliases: [/functions/humanize]
----
-
-```go-html-template
-{{ humanize "my-first-post" }} → My first post
-{{ humanize "myCamelPost" }} → My camel post
-```
-
-If the input is an integer or a string representation of an integer, humanize returns the number with the proper ordinal appended.
-
-```go-html-template
-{{ humanize "52" }} → 52nd
-{{ humanize 103 }} → 103rd
-```
diff --git a/docs/content/en/functions/inflect/Pluralize.md b/docs/content/en/functions/inflect/Pluralize.md
deleted file mode 100644
index f168770d3..000000000
--- a/docs/content/en/functions/inflect/Pluralize.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-title: inflect.Pluralize
-description: Pluralizes the given word according to a set of common English pluralization rules.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [pluralize]
- returnType: string
- signatures: [inflect.Pluralize INPUT]
-aliases: [/functions/pluralize]
----
-
-```go-html-template
-{{ "cat" | pluralize }} → cats
-```
diff --git a/docs/content/en/functions/inflect/Singularize.md b/docs/content/en/functions/inflect/Singularize.md
deleted file mode 100644
index 41e05b784..000000000
--- a/docs/content/en/functions/inflect/Singularize.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-title: inflect.Singularize
-description: Singularizes the given word according to a set of common English singularization rules.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [singularize]
- returnType: string
- signatures: [inflect.Singularize INPUT]
-aliases: [/functions/singularize]
----
-
-```go-html-template
-{{ "cats" | singularize }} → cat
-```
diff --git a/docs/content/en/functions/inflect/_index.md b/docs/content/en/functions/inflect/_index.md
deleted file mode 100644
index 2afe4fe33..000000000
--- a/docs/content/en/functions/inflect/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Inflect functions
-linkTitle: inflect
-description: These functions provide word inflection features such as singularization and pluralization of English nouns.
-categories: []
-keywords: []
----
diff --git a/docs/content/en/functions/js/Babel.md b/docs/content/en/functions/js/Babel.md
deleted file mode 100644
index d0007aaa0..000000000
--- a/docs/content/en/functions/js/Babel.md
+++ /dev/null
@@ -1,99 +0,0 @@
----
-title: js.Babel
-description: Compile the given JavaScript resource with Babel.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [babel]
- returnType: resource.Resource
- signatures: ['js.Babel [OPTIONS] RESOURCE']
----
-
-```go-html-template
-{{ with resources.Get "js/main.js" }}
- {{ $opts := dict
- "minified" hugo.IsProduction
- "noComments" hugo.IsProduction
- "sourceMap" (cond hugo.IsProduction "none" "external")
- }}
- {{ with . | js.Babel $opts }}
- {{ if hugo.IsProduction }}
- {{ with . | fingerprint }}
-
- {{ end }}
- {{ else }}
-
- {{ end }}
- {{ end }}
-{{ end }}
-```
-
-## Setup
-
-### Step 1
-
-Install [Node.js](https://nodejs.org/en/download)
-
-### Step 2
-
-Install the required Node.js packages in the root of your project.
-
-```sh
-npm install --save-dev @babel/core @babel/cli
-```
-
-### Step 3
-
-Add the babel executable to Hugo's `security.exec.allow` list in your site configuration:
-
-{{< code-toggle file=hugo >}}
-[security.exec]
- allow = ['^(dart-)?sass(-embedded)?$', '^go$', '^npx$', '^postcss$', '^babel$']
-{{< /code-toggle >}}
-
-## Configuration
-
-We add the main project's `node_modules` to `NODE_PATH` when running Babel and similar tools. There are some known [issues](https://github.com/babel/babel/issues/5618) with Babel in this area, so if you have a `babel.config.js` living in a Hugo Module (and not in the project itself), we recommend using `require` to load the presets/plugins, e.g.:
-
-```js
-module.exports = {
- presets: [
- [
- require("@babel/preset-env"),
- {
- useBuiltIns: "entry",
- corejs: 3,
- },
- ],
- ],
-};
-```
-
-## Options
-
-compact
-: (`bool`) Whether to remove optional newlines and whitespace. Enabled when `minified` is `true`. Default is `false`
-
-config
-: (`string`) Path to the Babel configuration file. Hugo will, by default, look for a `babel.config.js` file in the root of your project. See [details](https://babeljs.io/docs/en/configuration).
-
-minified
-: (`bool`) Whether to minify the compiled code. Enables the `compact` option. Default is `false`.
-
-noBabelrc
-: (`string`) Whether to ignore `.babelrc` and `.babelignore` files. Default is `false`.
-
-noComments
-: (`bool`) Whether to remove comments. Default is `false`.
-
-sourceMap
-: (`string`) Whether to generate source maps, one of `external`, `inline`, or `none`. Default is `none`.
-
-verbose
-: (`bool`) Whether to enable verbose logging. Default is `false`
-
-
diff --git a/docs/content/en/functions/js/Batch.md b/docs/content/en/functions/js/Batch.md
deleted file mode 100644
index a2c8bb893..000000000
--- a/docs/content/en/functions/js/Batch.md
+++ /dev/null
@@ -1,307 +0,0 @@
----
-title: js.Batch
-description: Build JavaScript bundle groups with global code splitting and flexible hooks/runners setup.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: js.Batcher
- signatures: ['js.Batch [ID]']
----
-
-> [!note]
-> For a runnable example of this feature, see [this test and demo repo](https://github.com/bep/hugojsbatchdemo/).
-
-The Batch `ID` is used to create the base directory for this batch. Forward slashes are allowed. `js.Batch` returns an object with an API with this structure:
-
-- [Group]
- - [Script]
- - [SetOptions]
- - [Instance]
- - [SetOptions]
- - [Runner]
- - [SetOptions]
- - [Config]
- - [SetOptions]
-
-## Group
-
-The `Group` method take an `ID` (`string`) as argument. No slashes. It returns an object with these methods:
-
-### Script
-
-The `Script` method takes an `ID` (`string`) as argument. No slashes. It returns an [OptionsSetter] that can be used to set [script options] for this script.
-
-```go-html-template
-{{ with js.Batch "js/mybatch" }}
- {{ with .Group "mygroup" }}
- {{ with .Script "myscript" }}
- {{ .SetOptions (dict "resource" (resources.Get "myscript.js")) }}
- {{ end }}
- {{ end }}
-{{ end }}
-```
-
-`SetOptions` takes a [script options] map. Note that if you want the script to be handled by a [runner], you need to set the `export` option to match what you want to pass on to the runner (default is `*`).
-
-### Instance
-
-The `Instance` method takes two `string` arguments `SCRIPT_ID` and `INSTANCE_ID`. No slashes. It returns an [OptionsSetter] that can be used to set [params options] for this instance.
-
-```go-html-template
-{{ with js.Batch "js/mybatch" }}
- {{ with .Group "mygroup" }}
- {{ with .Instance "myscript" "myinstance" }}
- {{ .SetOptions (dict "params" (dict "param1" "value1")) }}
- {{ end }}
- {{ end }}
-{{ end }}
-```
-
-`SetOptions` takes a [params options] map. The instance options will be passed to any [runner] script in the same group, as JSON.
-
-### Runner
-
-The `Runner` method takes an `ID` (`string`) as argument. No slashes. It returns an [OptionsSetter] that can be used to set [script options] for this runner.
-
-```go-html-template
-{{ with js.Batch "js/mybatch" }}
- {{ with .Group "mygroup" }}
- {{ with .Runner "myrunner" }}
- {{ .SetOptions (dict "resource" (resources.Get "myrunner.js")) }}
- {{ end }}
- {{ end }}
-{{ end }}
-```
-
-`SetOptions` takes a [script options] map.
-
-The runner will receive a data structure with all instances for that group with a live binding of the [JavaScript import] of the defined `export`.
-
-The runner script's export must be a function that takes one argument, the group data structure. An example of a group data structure as JSON is:
-
-```json
-{
- "id": "leaflet",
- "scripts": [
- {
- "id": "mapjsx",
- "binding": JAVASCRIPT_BINDING,
- "instances": [
- {
- "id": "0",
- "params": {
- "c": "h-64",
- "lat": 48.8533173846729,
- "lon": 2.3497416090232535,
- "r": "map.jsx",
- "title": "Cathédrale Notre-Dame de Paris",
- "zoom": 23
- }
- },
- {
- "id": "1",
- "params": {
- "c": "h-64",
- "lat": 59.96300872062237,
- "lon": 10.663529183196863,
- "r": "map.jsx",
- "title": "Holmenkollen",
- "zoom": 3
- }
- }
- ]
- }
- ]
-}
-```
-
-Below is an example of a runner script that uses React to render elements. Note that the export (`default`) must match the `export` option in the [script options] (`default` is the default value for runner scripts) (runnable versions of examples on this page can be found at [js.Batch Demo Repo]):
-
-```js
-import * as ReactDOM from 'react-dom/client';
-import * as React from 'react';
-
-export default function Run(group) {
- console.log('Running react-create-elements.js', group);
- const scripts = group.scripts;
- for (const script of scripts) {
- for (const instance of script.instances) {
- /* This is a convention in this project. */
- let elId = `${script.id}-${instance.id}`;
- let el = document.getElementById(elId);
- if (!el) {
- console.warn(`Element with id ${elId} not found`);
- continue;
- }
- const root = ReactDOM.createRoot(el);
- const reactEl = React.createElement(script.binding, instance.params);
- root.render(reactEl);
- }
- }
-}
-```
-
-### Config
-
-Returns an [OptionsSetter] that can be used to set [build options] for the batch.
-
-These are mostly the same as for `js.Build`, but note that:
-
-- `targetPath` is set automatically (there may be multiple outputs).
-- ``format` must be `esm`, currently the only format supporting [code splitting].
-- ``params` will be available in the `@params/config` namespace in the scripts. This way you can import both the [script] or [runner] params and the [config] params with:
-
-```js
-import * as params from "@params";
-import * as config from "@params/config";
-```
-
-Setting the `Config` for a batch can be done from any template (including shortcode templates), but will only be set once (the first will win):
-
-```go-html-template
-{{ with js.Batch "js/mybatch" }}
- {{ with .Config }}
- {{ .SetOptions (dict
- "target" "es2023"
- "format" "esm"
- "jsx" "automatic"
- "loaders" (dict ".png" "dataurl")
- "minify" true
- "params" (dict "param1" "value1")
- )
- }}
- {{ end }}
-{{ end }}
-```
-
-## Options
-
-### Build options
-
-format
-: (`string`) Currently only `esm` is supported in ESBuild's [code splitting].
-
-{{% include "/_common/functions/js/options.md" %}}
-
-### Script options
-
-resource
-: The resource to build. This can be a file resource or a virtual resource.
-
-export
-: The export to bind the runner to. Set it to `*` to export the [entire namespace](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#namespace_import). Default is `default` for [runner] scripts and `*` for other [scripts](#script).
-
-importContext
-: An additional context for resolving imports. Hugo will always check this one first before falling back to `assets` and `node_modules`. A common use of this is to resolve imports inside a page bundle. See [import context](#import-context).
-
-params
-: A map of parameters that will be passed to the script as JSON. These gets bound to the `@params` namespace:
-
- ```js
- import * as params from '@params';
- ```
-
-### Params options
-
-params
-: A map of parameters that will be passed to the script as JSON.
-
-### Import context
-
-Hugo will, by default, first try to resolve any import in [assets](/hugo-pipes/introduction/#asset-directory) and, if not found, let [ESBuild] resolve it (e.g. from `node_modules`). The `importContext` option can be used to set the first context for resolving imports. A common use of this is to resolve imports inside a [page bundle](/content-management/page-bundles/).
-
-```go-html-template
-{{ $common := resources.Match "/js/headlessui/*.*" }}
-{{ $importContext := (slice $.Page ($common.Mount "/js/headlessui" ".")) }}
-```
-
-You can pass any object that implements [Resource.Get](/methods/page/resources/#get). Pass a slice to set multiple contexts.
-
-The example above uses [`Resources.Mount`] to resolve a directory inside `assets` relative to the page bundle.
-
-### OptionsSetter
-
-An `OptionsSetter` is a special object that is returned once only. This means that you should wrap it with [with]:
-
-```go-html-template
-{{ with .Script "myscript" }}
- {{ .SetOptions (dict "resource" (resources.Get "myscript.js"))}}
-{{ end }}
-```
-
-## Build
-
-The `Build` method returns an object with the following structure:
-
-- Groups (map)
- - [`Resources`]
-
-Each [`Resource`] will be of media type `application/javascript` or `text/css`.
-
-In a template you would typically handle one group with a given `ID` (e.g. scripts for the current section). Because of the concurrent build, this needs to be done in a [`templates.Defer`] block:
-
-> [!note]
-> The [`templates.Defer`] acts as a synchronisation point to handle scripts added concurrently by different templates. If you have a setup with where the batch is created in one go (in one template), you don't need it.
->
-> See [this discussion](https://discourse.gohugo.io/t/js-batch-with-simple-global-script/53002/5?u=bep) for more.
-
-```go-html-template
-{{ $group := .group }}
-{{ with (templates.Defer (dict "key" $group "data" $group )) }}
- {{ with (js.Batch "js/mybatch") }}
- {{ with .Build }}
- {{ with index .Groups $ }}
- {{ range . }}
- {{ $s := . }}
- {{ if eq $s.MediaType.SubType "css" }}
-
- {{ else }}
-
- {{ end }}
- {{ end }}
- {{ end }}
- {{ end }}
-{{ end }}
-```
-
-## Known Issues
-
-In the official documentation for ESBuild's [code splitting], there's a warning note in the header. The two issues are:
-
- - `esm` is currently the only implemented output format. This means that it will not work for very old browsers. See [caniuse](https://caniuse.com/?search=ESM).
- - There's a known import ordering issue.
-
-We have not seen the ordering issue as a problem during our [extensive testing](https://github.com/bep/hugojsbatchdemo) of this new feature with different libraries. There are two main cases:
-
-1. Undefined execution order of imports, see [this comment](https://github.com/evanw/esbuild/issues/399#issuecomment-1458680887)
-1. Only one execution order of imports, see [this comment](https://github.com/evanw/esbuild/issues/399#issuecomment-735355932)
-
-Many would say that both of the above are [code smells](https://en.wikipedia.org/wiki/Code_smell). The first one has a simple workaround in Hugo. Define the import order in its own script and make sure it gets passed early to ESBuild, e.g. by putting it in a script group with a name that comes early in the alphabet.
-
-```js
-import './lib2.js';
-import './lib1.js';
-
-console.log('entrypoints-workaround.js');
-```
-
-[`Resource`]: /methods/resource/
-[`Resources.Mount`]: /methods/page/resources/#mount
-[`Resources`]: /methods/page/resources/
-[`templates.Defer`]: /functions/templates/defer/
-[`templates.Defer`]: /functions/templates/defer/
-[build options]: #build-options
-[code splitting]: https://esbuild.github.io/api/#splitting
-[config]: #config
-[ESBuild]: https://github.com/evanw/esbuild
-[JavaScript import]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
-[js.Batch Demo Repo]: https://github.com/bep/hugojsbatchdemo/
-[OptionsSetter]: #optionssetter
-[params options]: #params-options
-[runner]: #runner
-[script]: #script
-[script options]: #script-options
-[SetOptions]: #optionssetter
-[with]: /functions/go-template/with/
diff --git a/docs/content/en/functions/js/Build.md b/docs/content/en/functions/js/Build.md
deleted file mode 100644
index 1bec6b16f..000000000
--- a/docs/content/en/functions/js/Build.md
+++ /dev/null
@@ -1,121 +0,0 @@
----
-title: js.Build
-description: Bundle, transpile, tree shake, and minify JavaScript resources.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: resource.Resource
- signatures: ['js.Build [OPTIONS] RESOURCE']
----
-
-The `js.Build` function uses the [evanw/esbuild] package to:
-
-- Bundle
-- Transpile (TypeScript and JSX)
-- Tree shake
-- Minify
-- Create source maps
-
-```go-html-template
-{{ with resources.Get "js/main.js" }}
- {{ $opts := dict
- "minify" hugo.IsProduction
- "sourceMap" (cond hugo.IsProduction "" "external")
- "targetPath" "js/main.js"
- }}
- {{ with . | js.Build $opts }}
- {{ if hugo.IsProduction }}
- {{ with . | fingerprint }}
-
- {{ end }}
- {{ else }}
-
- {{ end }}
- {{ end }}
-{{ end }}
-```
-
-## 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.
-
-format
-: (`string`) The output format. One of: `iife`, `cjs`, `esm`. Default is `iife`, a self-executing function, suitable for inclusion as a `
-```
-
-[evanw/esbuild]: https://github.com/evanw/esbuild
diff --git a/docs/content/en/functions/js/_index.md b/docs/content/en/functions/js/_index.md
deleted file mode 100644
index d3557a2d3..000000000
--- a/docs/content/en/functions/js/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: JavaScript functions
-linkTitle: js
-description: Use these functions to work with JavaScript and TypeScript files.
-categories: []
-keywords: []
----
diff --git a/docs/content/en/functions/lang/FormatAccounting.md b/docs/content/en/functions/lang/FormatAccounting.md
deleted file mode 100644
index d2a1d76f6..000000000
--- a/docs/content/en/functions/lang/FormatAccounting.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: lang.FormatAccounting
-description: Returns a currency representation of a number for the given currency and precision for the current language and region in accounting notation.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: string
- signatures: [lang.FormatAccounting PRECISION CURRENCY NUMBER]
----
-
-```go-html-template
-{{ 512.5032 | lang.FormatAccounting 2 "NOK" }} → NOK512.50
-```
-
-{{% include "/_common/functions/locales.md" %}}
diff --git a/docs/content/en/functions/lang/FormatCurrency.md b/docs/content/en/functions/lang/FormatCurrency.md
deleted file mode 100644
index 327413c07..000000000
--- a/docs/content/en/functions/lang/FormatCurrency.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: lang.FormatCurrency
-description: Returns a currency representation of a number for the given currency and precision for the current language and region.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: string
- signatures: [lang.FormatCurrency PRECISION CURRENCY NUMBER]
----
-
-```go-html-template
-{{ 512.5032 | lang.FormatCurrency 2 "USD" }} → $512.50
-```
-
-{{% include "/_common/functions/locales.md" %}}
diff --git a/docs/content/en/functions/lang/FormatNumber.md b/docs/content/en/functions/lang/FormatNumber.md
deleted file mode 100644
index 5bf37996a..000000000
--- a/docs/content/en/functions/lang/FormatNumber.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: lang.FormatNumber
-description: Returns a numeric representation of a number with the given precision for the current language and region.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: string
- signatures: [lang.FormatNumber PRECISION NUMBER]
----
-
-```go-html-template
-{{ 512.5032 | lang.FormatNumber 2 }} → 512.50
-```
-
-{{% include "/_common/functions/locales.md" %}}
diff --git a/docs/content/en/functions/lang/FormatNumberCustom.md b/docs/content/en/functions/lang/FormatNumberCustom.md
deleted file mode 100644
index 0a70cd938..000000000
--- a/docs/content/en/functions/lang/FormatNumberCustom.md
+++ /dev/null
@@ -1,30 +0,0 @@
----
-title: lang.FormatNumberCustom
-description: Returns a numeric representation of a number with the given precision using negative, decimal, and grouping options.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: string
- signatures: ['lang.FormatNumberCustom PRECISION NUMBER [OPTIONS...]']
-aliases: ['/functions/numfmt/']
----
-
-This function formats a number with the given precision. The first options parameter is a space-delimited string of characters to represent negativity, the decimal point, and grouping. The default value is `- . ,`. The second options parameter defines an alternative delimiting character.
-
-Note that numbers are rounded up at 5 or greater. So, with precision set to 0, 1.5 becomes 2, and 1.4 becomes 1.
-
-For a simpler function that adapts to the current language, see [`lang.FormatNumber`].
-
-```go-html-template
-{{ lang.FormatNumberCustom 2 12345.6789 }} → 12,345.68
-{{ lang.FormatNumberCustom 2 12345.6789 "- , ." }} → 12.345,68
-{{ lang.FormatNumberCustom 6 -12345.6789 "- ." }} → -12345.678900
-{{ lang.FormatNumberCustom 0 -12345.6789 "- . ," }} → -12,346
-{{ lang.FormatNumberCustom 0 -12345.6789 "-|.| " "|" }} → -12 346
-```
-
-{{% include "/_common/functions/locales.md" %}}
-
-[`lang.FormatNumber`]: /functions/lang/formatnumber/
diff --git a/docs/content/en/functions/lang/FormatPercent.md b/docs/content/en/functions/lang/FormatPercent.md
deleted file mode 100644
index aef1fb64c..000000000
--- a/docs/content/en/functions/lang/FormatPercent.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: lang.FormatPercent
-description: Returns a percentage representation of a number with the given precision for the current language and region.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: string
- signatures: [lang.FormatPercent PRECISION NUMBER]
----
-
-```go-html-template
-{{ 512.5032 | lang.FormatPercent 2 }} → 512.50%
-```
-
-{{% include "/_common/functions/locales.md" %}}
diff --git a/docs/content/en/functions/lang/Merge.md b/docs/content/en/functions/lang/Merge.md
deleted file mode 100644
index db40c2669..000000000
--- a/docs/content/en/functions/lang/Merge.md
+++ /dev/null
@@ -1,29 +0,0 @@
----
-title: lang.Merge
-description: Merge missing translations from other languages.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: any
- signatures: [lang.Merge FROM TO]
-aliases: [/functions/lang.merge]
----
-
-As an example:
-
-```sh
-{{ $pages := .Site.RegularPages | lang.Merge $frSite.RegularPages | lang.Merge $enSite.RegularPages }}
-```
-
-Will "fill in the gaps" in the current site with, from left to right, content from the French site, and lastly the English.
-
-A more practical example is to fill in the missing translations from the other languages:
-
-```sh
-{{ $pages := .Site.RegularPages }}
-{{ range .Site.Home.Translations }}
-{{ $pages = $pages | lang.Merge .Site.RegularPages }}
-{{ end }}
- ```
diff --git a/docs/content/en/functions/lang/Translate.md b/docs/content/en/functions/lang/Translate.md
deleted file mode 100644
index 00bb0e3f3..000000000
--- a/docs/content/en/functions/lang/Translate.md
+++ /dev/null
@@ -1,246 +0,0 @@
----
-title: lang.Translate
-description: Translates a string using the translation tables in the i18n directory.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [T, i18n]
- returnType: string
- signatures: ['lang.Translate KEY [CONTEXT]']
-aliases: [/functions/i18n]
----
-
-The `lang.Translate` function returns the value associated with given key as defined in the translation table for the current language.
-
-If the key is not found in the translation table for the current language, the `lang.Translate` function falls back to the translation table for the [`defaultContentLanguage`].
-
-If the key is not found in the translation table for the `defaultContentLanguage`, the `lang.Translate` function returns an empty string.
-
-> [!note]
-> To list missing and fallback translations, use the `--printI18nWarnings` flag when building your site.
->
-> To render placeholders for missing and fallback translations, set [`enableMissingTranslationPlaceholders`] to `true` in your site configuration.
-
-## Translation tables
-
-Create translation tables in the `i18n` directory, naming each file according to [RFC 5646]. Translation tables may be JSON, TOML, or YAML. For example:
-
-```text
-i18n/en.toml
-i18n/en-US.toml
-```
-
-The base name must match the [language key] as defined in your site configuration.
-
-Artificial languages with private use subtags as defined in [RFC 5646 § 2.2.7] are also supported. You may omit the `art-x-` prefix for brevity. For example:
-
-```text
-i18n/art-x-hugolang.toml
-i18n/hugolang.toml
-```
-
-> [!note]
-> Private use subtags must not exceed 8 alphanumeric characters.
-
-## Simple translations
-
-Let's say your multilingual site supports two languages, English and Polish. Create a translation table for each language in the `i18n` directory.
-
-```text
-i18n/
-├── en.toml
-└── pl.toml
-```
-
-The English translation table:
-
-{{< code-toggle file=i18n/en >}}
-privacy = 'privacy'
-security = 'security'
-{{< /code-toggle >}}
-
-The Polish translation table:
-
-{{< code-toggle file=i18n/pl >}}
-privacy = 'prywatność'
-security = 'bezpieczeństwo'
-{{< /code-toggle >}}
-
-> [!note]
-> The examples below use the `T` alias for brevity.
-
-When viewing the English language site:
-
-```go-html-template
-{{ T "privacy" }} → privacy
-{{ T "security" }} → security
-````
-
-When viewing the Polish language site:
-
-```go-html-template
-{{ T "privacy" }} → prywatność
-{{ T "security" }} → bezpieczeństwo
-```
-
-## Translations with pluralization
-
-Let's say your multilingual site supports two languages, English and Polish. Create a translation table for each language in the `i18n` directory.
-
-```text
-i18n/
-├── en.toml
-└── pl.toml
-```
-
-The Unicode [CLDR Plural Rules chart] describes the pluralization categories for each language.
-
-The English translation table:
-
-{{< code-toggle file=i18n/en >}}
-[day]
-one = 'day'
-other = 'days'
-
-[day_with_count]
-one = '{{ . }} day'
-other = '{{ . }} days'
-{{< /code-toggle >}}
-
-The Polish translation table:
-
-{{< code-toggle file=i18n/pl >}}
-[day]
-one = 'miesiąc'
-few = 'miesiące'
-many = 'miesięcy'
-other = 'miesiąca'
-
-[day_with_count]
-one = '{{ . }} miesiąc'
-few = '{{ . }} miesiące'
-many = '{{ . }} miesięcy'
-other = '{{ . }} miesiąca'
-{{< /code-toggle >}}
-
-> [!note]
-> The examples below use the `T` alias for brevity.
-
-When viewing the English language site:
-
-```go-html-template
-{{ T "day" 0 }} → days
-{{ T "day" 1 }} → day
-{{ T "day" 2 }} → days
-{{ T "day" 5 }} → days
-
-{{ T "day_with_count" 0 }} → 0 days
-{{ T "day_with_count" 1 }} → 1 day
-{{ T "day_with_count" 2 }} → 2 days
-{{ T "day_with_count" 5 }} → 5 days
-````
-
-When viewing the Polish language site:
-
-```go-html-template
-{{ T "day" 0 }} → miesięcy
-{{ T "day" 1 }} → miesiąc
-{{ T "day" 2 }} → miesiące
-{{ T "day" 5 }} → miesięcy
-
-{{ T "day_with_count" 0 }} → 0 miesięcy
-{{ T "day_with_count" 1 }} → 1 miesiąc
-{{ T "day_with_count" 2 }} → 2 miesiące
-{{ T "day_with_count" 5 }} → 5 miesięcy
-```
-
-In the pluralization examples above, we passed an integer in context (the second argument). You can also pass a map in context, providing a `count` key to control pluralization.
-
-Translation table:
-
-{{< code-toggle file=i18n/en >}}
-[age]
-one = '{{ .name }} is {{ .count }} year old.'
-other = '{{ .name }} is {{ .count }} years old.'
-{{< /code-toggle >}}
-
-Template code:
-
-```go-html-template
-{{ T "age" (dict "name" "Will" "count" 1) }} → Will is 1 year old.
-{{ T "age" (dict "name" "John" "count" 3) }} → John is 3 years old.
-```
-
-> [!note]
-> Translation tables may contain both simple translations and translations with pluralization.
-
-## Reserved keys
-
-Hugo uses the [go-i18n] package to look up values in translation tables. This package reserves the following keys for internal use:
-
-id
-: (`string`) Uniquely identifies the message.
-
-description
-: (`string`) Describes the message to give additional context to translators that may be relevant for translation.
-
-hash
-: (`string`) Uniquely identifies the content of the message that this message was translated from.
-
-leftdelim
-: (`string`) The left Go template delimiter.
-
-rightdelim
-: (`string`) The right Go template delimiter.
-
-zero
-: (`string`) The content of the message for the [CLDR] plural form "zero".
-
-one
-: (`string`) The content of the message for the [CLDR] plural form "one".
-
-two
-: (`string`) The content of the message for the [CLDR] plural form "two".
-
-few
-: (`string`) The content of the message for the [CLDR] plural form "few".
-
-many
-: (`string`) The content of the message for the [CLDR] plural form "many".
-
-other
-: (`string`) The content of the message for the [CLDR] plural form "other".
-
-If you need to provide a translation for one of the reserved keys, you can prepend the word with an underscore. For example:
-
-{{< code-toggle file=i18n/es >}}
-_description = 'descripción'
-_few = 'pocos'
-_many = 'muchos'
-_one = 'uno'
-_other = 'otro'
-_two = 'dos'
-_zero = 'cero'
-{{< /code-toggle >}}
-
-Then in your templates:
-
-```go-html-template
-{{ T "_description" }} → descripción
-{{ T "_few" }} → pocos
-{{ T "_many" }} → muchos
-{{ T "_one" }} → uno
-{{ T "_two" }} → dos
-{{ T "_zero" }} → cero
-{{ T "_other" }} → otro
-```
-
-[`defaultContentLanguage`]: /configuration/all/#defaultcontentlanguage
-[`enableMissingTranslationPlaceholders`]: /configuration/all/#enablemissingtranslationplaceholders
-[CLDR]: https://www.unicode.org/cldr/charts/43/supplemental/language_plural_rules.html
-[CLDR Plural Rules chart]: https://www.unicode.org/cldr/charts/43/supplemental/language_plural_rules.html
-[go-i18n]: https://github.com/nicksnyder/go-i18n
-[language key]: /configuration/languages/#language-keys
-[RFC 5646]: https://datatracker.ietf.org/doc/html/rfc5646
-[RFC 5646 § 2.2.7]: https://datatracker.ietf.org/doc/html/rfc5646#section-2.2.7
diff --git a/docs/content/en/functions/lang/_index.md b/docs/content/en/functions/lang/_index.md
deleted file mode 100644
index de75cad45..000000000
--- a/docs/content/en/functions/lang/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Lang functions
-linkTitle: lang
-description: Use these functions to adapt your site to meet language and regional requirements.
-categories: []
-keywords: []
----
diff --git a/docs/content/en/functions/math/Abs.md b/docs/content/en/functions/math/Abs.md
deleted file mode 100644
index 60d8d6d64..000000000
--- a/docs/content/en/functions/math/Abs.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-title: math.Abs
-description: Returns the absolute value of the given number.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: float64
- signatures: [math.Abs VALUE]
----
-
-```go-html-template
-{{ math.Abs -2.1 }} → 2.1
-```
diff --git a/docs/content/en/functions/math/Acos.md b/docs/content/en/functions/math/Acos.md
deleted file mode 100644
index 32537e2dd..000000000
--- a/docs/content/en/functions/math/Acos.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: math.Acos
-description: Returns the arccosine, in radians, of the given number.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: float64
- signatures: [math.Acos VALUE]
----
-
-{{< new-in 0.130.0 />}}
-
-```go-html-template
-{{ math.Acos 1 }} → 0
-```
diff --git a/docs/content/en/functions/math/Add.md b/docs/content/en/functions/math/Add.md
deleted file mode 100644
index cd137c6c7..000000000
--- a/docs/content/en/functions/math/Add.md
+++ /dev/null
@@ -1,23 +0,0 @@
----
-title: math.Add
-description: Adds two or more numbers.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [add]
- returnType: any
- signatures: [math.Add VALUE VALUE...]
----
-
-If one of the numbers is a [`float`](g), the result is a `float`.
-
-```go-html-template
-{{ add 12 3 2 }} → 17
-```
-
-You can also use the `add` function to concatenate strings.
-
-```go-html-template
-{{ add "hu" "go" }} → hugo
-```
diff --git a/docs/content/en/functions/math/Asin.md b/docs/content/en/functions/math/Asin.md
deleted file mode 100644
index 76114a72e..000000000
--- a/docs/content/en/functions/math/Asin.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: math.Asin
-description: Returns the arcsine, in radians, of the given number.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: float64
- signatures: [math.Asin VALUE]
----
-
-{{< new-in 0.130.0 />}}
-
-```go-html-template
-{{ math.Asin 1 }} → 1.5707963267948966
-```
diff --git a/docs/content/en/functions/math/Atan.md b/docs/content/en/functions/math/Atan.md
deleted file mode 100644
index 5c8268b47..000000000
--- a/docs/content/en/functions/math/Atan.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: math.Atan
-description: Returns the arctangent, in radians, of the given number.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: float64
- signatures: [math.Atan VALUE]
----
-
-{{< new-in 0.130.0 />}}
-
-```go-html-template
-{{ math.Atan 1 }} → 0.7853981633974483
-```
diff --git a/docs/content/en/functions/math/Atan2.md b/docs/content/en/functions/math/Atan2.md
deleted file mode 100644
index 942fffdf8..000000000
--- a/docs/content/en/functions/math/Atan2.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: math.Atan2
-description: Returns the arctangent, in radians, of the given number pair, determining the correct quadrant from their signs.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: float64
- signatures: [math.Atan2 VALUE VALUE]
----
-
-{{< new-in 0.130.0 />}}
-
-```go-html-template
-{{ math.Atan2 1 2 }} → 0.4636476090008061
-```
diff --git a/docs/content/en/functions/math/Ceil.md b/docs/content/en/functions/math/Ceil.md
deleted file mode 100644
index 22a9d0299..000000000
--- a/docs/content/en/functions/math/Ceil.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-title: math.Ceil
-description: Returns the least integer value greater than or equal to the given number.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: float64
- signatures: [math.Ceil VALUE]
----
-
-```go-html-template
-{{ math.Ceil 2.1 }} → 3
-```
diff --git a/docs/content/en/functions/math/Cos.md b/docs/content/en/functions/math/Cos.md
deleted file mode 100644
index 249a064bb..000000000
--- a/docs/content/en/functions/math/Cos.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: math.Cos
-description: Returns the cosine of the given radian number.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: float64
- signatures: [math.Cos VALUE]
----
-
-{{< new-in 0.130.0 />}}
-
-```go-html-template
-{{ math.Cos 1 }} → 0.5403023058681398
-```
diff --git a/docs/content/en/functions/math/Counter.md b/docs/content/en/functions/math/Counter.md
deleted file mode 100644
index 16456cec6..000000000
--- a/docs/content/en/functions/math/Counter.md
+++ /dev/null
@@ -1,33 +0,0 @@
----
-title: math.Counter
-description: Increments and returns a global counter.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: uint64
- signatures: [math.Counter]
----
-
-The counter is global for both monolingual and multilingual sites, and its initial value for each build is 1.
-
-```go-html-template
-{{ warnf "single.html called %d times" math.Counter }}
-```
-
-```sh
-WARN single.html called 1 times
-WARN single.html called 2 times
-WARN single.html called 3 times
-```
-
-Use this function to:
-
-- Create unique warnings as shown above; the [`warnf`] function suppresses duplicate messages
-- Create unique target paths for the `resources.FromString` function where the target path is also the cache key
-
-> [!note]
-> Due to concurrency, the value returned in a given template for a given page will vary from one build to the next. You cannot use this function to assign a static id to each page.
-
-[`warnf`]: /functions/fmt/warnf/
diff --git a/docs/content/en/functions/math/Div.md b/docs/content/en/functions/math/Div.md
deleted file mode 100644
index 0e338a9e9..000000000
--- a/docs/content/en/functions/math/Div.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: math.Div
-description: Divides the first number by one or more numbers.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [div]
- returnType: any
- signatures: [math.Div VALUE VALUE...]
----
-
-If one of the numbers is a [`float`](g), the result is a `float`.
-
-```go-html-template
-{{ div 12 3 2 }} → 2
-```
diff --git a/docs/content/en/functions/math/Floor.md b/docs/content/en/functions/math/Floor.md
deleted file mode 100644
index dbfd8620e..000000000
--- a/docs/content/en/functions/math/Floor.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-title: math.Floor
-description: Returns the greatest integer value less than or equal to the given number.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: float64
- signatures: [math.Floor VALUE]
----
-
-```go-html-template
-{{ math.Floor 1.9 }} → 1
-```
diff --git a/docs/content/en/functions/math/Log.md b/docs/content/en/functions/math/Log.md
deleted file mode 100644
index 123ffacd7..000000000
--- a/docs/content/en/functions/math/Log.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-title: math.Log
-description: Returns the natural logarithm of the given number.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: float64
- signatures: [math.Log VALUE]
----
-
-```go-html-template
-{{ math.Log 42 }} → 3.737
-```
diff --git a/docs/content/en/functions/math/Max.md b/docs/content/en/functions/math/Max.md
deleted file mode 100644
index d3a7676fc..000000000
--- a/docs/content/en/functions/math/Max.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-title: math.Max
-description: Returns the greater of all numbers. Accepts scalars, slices, or both.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: float64
- signatures: [math.Max VALUE...]
----
-
-```go-html-template
-{{ math.Max 1 (slice 2 3) 4 }} → 4
-```
diff --git a/docs/content/en/functions/math/Min.md b/docs/content/en/functions/math/Min.md
deleted file mode 100644
index 4f7d7b85a..000000000
--- a/docs/content/en/functions/math/Min.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-title: math.Min
-description: Returns the smaller of all numbers. Accepts scalars, slices, or both.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: float64
- signatures: [math.Min VALUE...]
----
-
-```go-html-template
-{{ math.Min 1 (slice 2 3) 4 }} → 1
-```
diff --git a/docs/content/en/functions/math/Mod.md b/docs/content/en/functions/math/Mod.md
deleted file mode 100644
index 6035a361e..000000000
--- a/docs/content/en/functions/math/Mod.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-title: math.Mod
-description: Returns the modulus of two integers.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [mod]
- returnType: int64
- signatures: [math.Mod VALUE1 VALUE2]
----
-
-```go-html-template
-{{ mod 15 3 }} → 0
-```
diff --git a/docs/content/en/functions/math/ModBool.md b/docs/content/en/functions/math/ModBool.md
deleted file mode 100644
index fc7813d67..000000000
--- a/docs/content/en/functions/math/ModBool.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-title: math.ModBool
-description: Reports whether the modulus of two integers equals 0.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [modBool]
- returnType: bool
- signatures: [math.ModBool VALUE1 VALUE2]
----
-
-```go-html-template
-{{ modBool 15 3 }} → true
-```
diff --git a/docs/content/en/functions/math/Mul.md b/docs/content/en/functions/math/Mul.md
deleted file mode 100644
index 69f2dbe1b..000000000
--- a/docs/content/en/functions/math/Mul.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: math.Mul
-description: Multiplies two or more numbers.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [mul]
- returnType: any
- signatures: [math.Mul VALUE VALUE...]
----
-
-If one of the numbers is a [`float`](g), the result is a `float`.
-
-```go-html-template
-{{ mul 12 3 2 }} → 72
-```
diff --git a/docs/content/en/functions/math/Pi.md b/docs/content/en/functions/math/Pi.md
deleted file mode 100644
index 0bc74bf03..000000000
--- a/docs/content/en/functions/math/Pi.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: math.Pi
-description: Returns the mathematical constant pi.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: float64
- signatures: [math.Pi]
----
-
-{{< new-in 0.130.0 />}}
-
-```go-html-template
-{{ math.Pi }} → 3.141592653589793
-```
diff --git a/docs/content/en/functions/math/Pow.md b/docs/content/en/functions/math/Pow.md
deleted file mode 100644
index a4384305d..000000000
--- a/docs/content/en/functions/math/Pow.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-title: math.Pow
-description: Returns the first number raised to the power of the second number.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [pow]
- returnType: float64
- signatures: [math.Pow VALUE1 VALUE2]
----
-
-```go-html-template
-{{ math.Pow 2 3 }} → 8
-```
diff --git a/docs/content/en/functions/math/Product.md b/docs/content/en/functions/math/Product.md
deleted file mode 100644
index ffb1afe46..000000000
--- a/docs/content/en/functions/math/Product.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-title: math.Product
-description: Returns the product of all numbers. Accepts scalars, slices, or both.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: float64
- signatures: [math.Product VALUE...]
----
-
-```go-html-template
-{{ math.Product 1 (slice 2 3) 4 }} → 24
-```
diff --git a/docs/content/en/functions/math/Rand.md b/docs/content/en/functions/math/Rand.md
deleted file mode 100644
index d659e651f..000000000
--- a/docs/content/en/functions/math/Rand.md
+++ /dev/null
@@ -1,43 +0,0 @@
----
-title: math.Rand
-description: Returns a pseudo-random number in the half-open interval [0.0, 1.0).
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: float64
- signatures: [math.Rand]
----
-
-{{< new-in 0.121.2 />}}
-
-The `math.Rand` function returns a pseudo-random number in the half-open [interval](g) [0.0, 1.0).
-
-```go-html-template
-{{ math.Rand }} → 0.6312770459590062
-```
-
-To generate a random integer in the closed interval [0, 5]:
-
-```go-html-template
-{{ math.Rand | mul 6 | math.Floor }}
-```
-
-To generate a random integer in the closed interval [1, 6]:
-
-```go-html-template
-{{ math.Rand | mul 6 | math.Ceil }}
-```
-
-To generate a random float, with one digit after the decimal point, in the closed interval [0, 4.9]:
-
-```go-html-template
-{{ div (math.Rand | mul 50 | math.Floor) 10 }}
-```
-
-To generate a random float, with one digit after the decimal point, in the closed interval [0.1, 5.0]:
-
-```go-html-template
-{{ div (math.Rand | mul 50 | math.Ceil) 10 }}
-```
diff --git a/docs/content/en/functions/math/Round.md b/docs/content/en/functions/math/Round.md
deleted file mode 100644
index 6bc015ce7..000000000
--- a/docs/content/en/functions/math/Round.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-title: math.Round
-description: Returns the nearest integer, rounding half away from zero.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: float64
- signatures: [math.Round VALUE]
----
-
-```go-html-template
-{{ math.Round 1.5 }} → 2
-```
diff --git a/docs/content/en/functions/math/Sin.md b/docs/content/en/functions/math/Sin.md
deleted file mode 100644
index b5ab86bb8..000000000
--- a/docs/content/en/functions/math/Sin.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: math.Sin
-description: Returns the sine of the given radian number.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: float64
- signatures: [math.Sin VALUE]
----
-
-{{< new-in 0.130.0 />}}
-
-```go-html-template
-{{ math.Sin 1 }} → 0.8414709848078965
-```
diff --git a/docs/content/en/functions/math/Sqrt.md b/docs/content/en/functions/math/Sqrt.md
deleted file mode 100644
index b2f49fdbd..000000000
--- a/docs/content/en/functions/math/Sqrt.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-title: math.Sqrt
-description: Returns the square root of the given number.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: float64
- signatures: [math.Sqrt VALUE]
----
-
-```go-html-template
-{{ math.Sqrt 81 }} → 9
-```
diff --git a/docs/content/en/functions/math/Sub.md b/docs/content/en/functions/math/Sub.md
deleted file mode 100644
index 49459cd71..000000000
--- a/docs/content/en/functions/math/Sub.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: math.Sub
-description: Subtracts one or more numbers from the first number.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [sub]
- returnType: any
- signatures: [math.Sub VALUE VALUE...]
----
-
-If one of the numbers is a [`float`](g), the result is a `float`.
-
-```go-html-template
-{{ sub 12 3 2 }} → 7
-```
diff --git a/docs/content/en/functions/math/Sum.md b/docs/content/en/functions/math/Sum.md
deleted file mode 100644
index e14bc924b..000000000
--- a/docs/content/en/functions/math/Sum.md
+++ /dev/null
@@ -1,14 +0,0 @@
----
-title: math.Sum
-description: Returns the sum of all numbers. Accepts scalars, slices, or both.
-categories: []
-params:
- functions_and_methods:
- aliases: []
- returnType: float64
- signatures: [math.Sum VALUE...]
----
-
-```go-html-template
-{{ math.Sum 1 (slice 2 3) 4 }} → 10
-```
diff --git a/docs/content/en/functions/math/Tan.md b/docs/content/en/functions/math/Tan.md
deleted file mode 100644
index c4f861c05..000000000
--- a/docs/content/en/functions/math/Tan.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: math.Tan
-description: Returns the tangent of the given radian number.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: float64
- signatures: [math.Tan VALUE]
----
-
-{{< new-in 0.130.0 />}}
-
-```go-html-template
-{{ math.Tan 1 }} → 1.557407724654902
-```
diff --git a/docs/content/en/functions/math/ToDegrees.md b/docs/content/en/functions/math/ToDegrees.md
deleted file mode 100644
index f01cd4728..000000000
--- a/docs/content/en/functions/math/ToDegrees.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: math.ToDegrees
-description: ToDegrees converts radians into degrees.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: float64
- signatures: [math.ToDegrees VALUE]
----
-
-{{< new-in 0.130.0 />}}
-
-```go-html-template
-{{ math.ToDegrees 1.5707963267948966 }} → 90
-```
diff --git a/docs/content/en/functions/math/ToRadians.md b/docs/content/en/functions/math/ToRadians.md
deleted file mode 100644
index b5acbb65b..000000000
--- a/docs/content/en/functions/math/ToRadians.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: math.ToRadians
-description: ToRadians converts degrees into radians.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: float64
- signatures: [math.ToRadians VALUE]
----
-
-{{< new-in 0.130.0 />}}
-
-```go-html-template
-{{ math.ToRadians 90 }} → 1.5707963267948966
-```
diff --git a/docs/content/en/functions/math/_index.md b/docs/content/en/functions/math/_index.md
deleted file mode 100644
index c58a5a704..000000000
--- a/docs/content/en/functions/math/_index.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Math functions
-linkTitle: math
-description: Use these functions to perform mathematical operations.
-categories: []
----
diff --git a/docs/content/en/functions/openapi3/Unmarshal.md b/docs/content/en/functions/openapi3/Unmarshal.md
deleted file mode 100644
index d1f928aeb..000000000
--- a/docs/content/en/functions/openapi3/Unmarshal.md
+++ /dev/null
@@ -1,72 +0,0 @@
----
-title: openapi3.Unmarshal
-description: Unmarshals the given resource into an OpenAPI 3 document.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: openapi3.OpenAPIDocument
- signatures: ['openapi3.Unmarshal RESOURCE']
----
-
-Use the `openapi3.Unmarshal` function with [global resources](g), [page resources](g), or [remote resources](g).
-
-[OpenAPI]: https://www.openapis.org/
-
-For example, to work with a remote [OpenAPI] definition:
-
-```go-html-template
-{{ $url := "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/examples/v3.0/petstore.json" }}
-{{ $api := "" }}
-{{ with try (resources.GetRemote $url) }}
- {{ with .Err }}
- {{ errorf "%s" . }}
- {{ else with .Value }}
- {{ $api = . | openapi3.Unmarshal }}
- {{ else }}
- {{ errorf "Unable to get remote resource %q" $url }}
- {{ end }}
-{{ end }}
-```
-
-To inspect the data structure:
-
-```go-html-template
-
{{ debug.Dump $api }}
-```
-
-To list the GET and POST operations for each of the API paths:
-
-```go-html-template
-{{ range $path, $details := $api.Paths }}
-
{{ $path }}
-
- {{ with $details.Get }}
-
GET
-
{{ .Summary }}
- {{ end }}
- {{ with $details.Post }}
-
POST
-
{{ .Summary }}
- {{ end }}
-
-{{ end }}
-```
-
-Hugo renders this to:
-
-```html
-
/pets
-
-
GET
-
List all pets
-
POST
-
Create a pet
-
-
/pets/{petId}
-
-
GET
-
Info for a specific pet
-
-```
diff --git a/docs/content/en/functions/openapi3/_index.md b/docs/content/en/functions/openapi3/_index.md
deleted file mode 100644
index 852daf7b5..000000000
--- a/docs/content/en/functions/openapi3/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: OpenAPI functions
-linkTitle: openapi3
-description: Use these functions to work with OpenAPI 3 definitions.
-categories: []
-keywords: []
----
diff --git a/docs/content/en/functions/os/FileExists.md b/docs/content/en/functions/os/FileExists.md
deleted file mode 100644
index b8a01a3e7..000000000
--- a/docs/content/en/functions/os/FileExists.md
+++ /dev/null
@@ -1,37 +0,0 @@
----
-title: os.FileExists
-description: Reports whether the file or directory exists.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [fileExists]
- returnType: bool
- signatures: [os.FileExists PATH]
-aliases: [/functions/fileexists]
----
-
-The `os.FileExists` function attempts to resolve the path relative to the root of your project directory. If a matching file or directory is not found, it will attempt to resolve the path relative to the [`contentDir`](/configuration/all/#contentdir). A leading path separator (`/`) is optional.
-
-With this directory structure:
-
-```text
-content/
-├── about.md
-├── contact.md
-└── news/
- ├── article-1.md
- └── article-2.md
-```
-
-The function returns these values:
-
-```go-html-template
-{{ fileExists "content" }} → true
-{{ fileExists "content/news" }} → true
-{{ fileExists "content/news/article-1" }} → false
-{{ fileExists "content/news/article-1.md" }} → true
-{{ fileExists "news" }} → true
-{{ fileExists "news/article-1" }} → false
-{{ fileExists "news/article-1.md" }} → true
-```
diff --git a/docs/content/en/functions/os/Getenv.md b/docs/content/en/functions/os/Getenv.md
deleted file mode 100644
index 04215e6c3..000000000
--- a/docs/content/en/functions/os/Getenv.md
+++ /dev/null
@@ -1,54 +0,0 @@
----
-title: os.Getenv
-description: Returns the value of an environment variable, or an empty string if the environment variable is not set.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [getenv]
- returnType: string
- signatures: [os.Getenv VARIABLE]
-aliases: [/functions/getenv]
----
-
-## Security
-
-By default, when using the `os.Getenv` function Hugo allows access to:
-
-- The `CI` environment variable
-- Any environment variable beginning with `HUGO_`
-
-To access other environment variables, adjust your site configuration. For example, to allow access to the `HOME` and `USER` environment variables:
-
-{{< code-toggle file=hugo >}}
-[security.funcs]
-getenv = ['^HUGO_', '^CI$', '^USER$', '^HOME$']
-{{< /code-toggle >}}
-
-For more information see [configure security](/configuration/security).
-
-## Examples
-
-```go-html-template
-{{ getenv "HOME" }} → /home/victor
-{{ getenv "USER" }} → victor
-```
-
-You can pass values when building your site:
-
-```sh
-MY_VAR1=foo MY_VAR2=bar hugo
-
-OR
-
-export MY_VAR1=foo
-export MY_VAR2=bar
-hugo
-```
-
-And then retrieve the values within a template:
-
-```go-html-template
-{{ getenv "MY_VAR1" }} → foo
-{{ getenv "MY_VAR2" }} → bar
-```
diff --git a/docs/content/en/functions/os/ReadDir.md b/docs/content/en/functions/os/ReadDir.md
deleted file mode 100644
index 65c398a31..000000000
--- a/docs/content/en/functions/os/ReadDir.md
+++ /dev/null
@@ -1,45 +0,0 @@
----
-title: os.ReadDir
-description: Returns an array of FileInfo structures sorted by file name, one element for each directory entry.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [readDir]
- returnType: os.FileInfo
- signatures: [os.ReadDir PATH]
-aliases: [/functions/readdir]
----
-
-The `os.ReadDir` function resolves the path relative to the root of your project directory. A leading path separator (`/`) is optional.
-
-With this directory structure:
-
-```text
-content/
-├── about.md
-├── contact.md
-└── news/
- ├── article-1.md
- └── article-2.md
-```
-
-This template code:
-
-```go-html-template
-{{ range readDir "content" }}
- {{ .Name }} → {{ .IsDir }}
-{{ end }}
-```
-
-Produces:
-
-```html
-about.md → false
-contact.md → false
-news → true
-```
-
-Note that `os.ReadDir` is not recursive.
-
-Details of the `FileInfo` structure are available in the [Go documentation](https://pkg.go.dev/io/fs#FileInfo).
diff --git a/docs/content/en/functions/os/ReadFile.md b/docs/content/en/functions/os/ReadFile.md
deleted file mode 100644
index 7f25327c8..000000000
--- a/docs/content/en/functions/os/ReadFile.md
+++ /dev/null
@@ -1,34 +0,0 @@
----
-title: os.ReadFile
-description: Returns the contents of a file.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [readFile]
- returnType: string
- signatures: [os.ReadFile PATH]
-aliases: [/functions/readfile]
----
-
-The `os.ReadFile` function attempts to resolve the path relative to the root of your project directory. If a matching file is not found, it will attempt to resolve the path relative to the [`contentDir`](/configuration/all/#contentdir). A leading path separator (`/`) is optional.
-
-With a file named README.md in the root of your project directory:
-
-```text
-This is **bold** text.
-```
-
-This template code:
-
-```go-html-template
-{{ readFile "README.md" }}
-```
-
-Produces:
-
-```html
-This is **bold** text.
-```
-
-Note that `os.ReadFile` returns raw (uninterpreted) content.
diff --git a/docs/content/en/functions/os/Stat.md b/docs/content/en/functions/os/Stat.md
deleted file mode 100644
index 63cb3f26a..000000000
--- a/docs/content/en/functions/os/Stat.md
+++ /dev/null
@@ -1,27 +0,0 @@
----
-title: os.Stat
-description: Returns a FileInfo structure describing a file or directory.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: os.FileInfo
- signatures: [os.Stat PATH]
-aliases: [/functions/os.stat]
----
-
-The `os.Stat` function attempts to resolve the path relative to the root of your project directory. If a matching file or directory is not found, it will attempt to resolve the path relative to the [`contentDir`](/configuration/all/#contentdir). A leading path separator (`/`) is optional.
-
-```go-html-template
-{{ $f := os.Stat "README.md" }}
-{{ $f.IsDir }} → false (bool)
-{{ $f.ModTime }} → 2021-11-25 10:06:49.315429236 -0800 PST (time.Time)
-{{ $f.Name }} → README.md (string)
-{{ $f.Size }} → 241 (int64)
-
-{{ $d := os.Stat "content" }}
-{{ $d.IsDir }} → true (bool)
-```
-
-Details of the `FileInfo` structure are available in the [Go documentation](https://pkg.go.dev/io/fs#FileInfo).
diff --git a/docs/content/en/functions/os/_index.md b/docs/content/en/functions/os/_index.md
deleted file mode 100644
index b125f7004..000000000
--- a/docs/content/en/functions/os/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: OS functions
-linkTitle: os
-description: Use these functions to interact with the operating system.
-categories: []
-keywords: []
----
diff --git a/docs/content/en/functions/partials/Include.md b/docs/content/en/functions/partials/Include.md
deleted file mode 100644
index eb7eeafdc..000000000
--- a/docs/content/en/functions/partials/Include.md
+++ /dev/null
@@ -1,84 +0,0 @@
----
-title: partials.Include
-description: Executes the given partial template, optionally passing context. If the partial template contains a return statement, returns the given value, else returns the rendered output.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [partial]
- returnType: any
- signatures: ['partials.Include NAME [CONTEXT]']
-aliases: [/functions/partial]
----
-
-Without a [`return`] statement, the `partial` function returns a string of type `template.HTML`. With a `return` statement, the `partial` function can return any data type.
-
-[`return`]: /functions/go-template/return/
-
-In this example we have three partial templates:
-
-```text
-layouts/
-└── partials/
- ├── average.html
- ├── breadcrumbs.html
- └── footer.html
-```
-
-The "average" partial returns the average of one or more numbers. We pass the numbers in context:
-
-```go-html-template
-{{ $numbers := slice 1 6 7 42 }}
-{{ $average := partial "average.html" $numbers }}
-```
-
-The "breadcrumbs" partial renders [breadcrumb navigation], and needs to receive the current page in context:
-
-```go-html-template
-{{ partial "breadcrumbs.html" . }}
-```
-
-The "footer" partial renders the site footer. In this contrived example, the footer does not need access to the current page, so we can omit context:
-
-```go-html-template
-{{ partial "footer.html" }}
-```
-
-You can pass anything in context: a page, a page collection, a scalar value, a slice, or a map. In this example we pass the current page and three scalar values:
-
-```go-html-template
-{{ $ctx := dict
- "page" .
- "name" "John Doe"
- "major" "Finance"
- "gpa" 4.0
-}}
-{{ partial "render-student-info.html" $ctx }}
-```
-
-Then, within the partial template:
-
-```go-html-template
-
-```
-
-To return a value from a partial template, it must contain only one `return` statement, placed at the end of the template:
-
-```go-html-template
-{{ $result := "" }}
-{{ if math.ModBool . 2 }}
- {{ $result = "even" }}
-{{ else }}
- {{ $result = "odd" }}
-{{ end }}
-{{ return $result }}
-```
-
-See [details][`return`].
-
-[`return`]: /functions/go-template/return/
-
-[breadcrumb navigation]: /content-management/sections/#ancestors-and-descendants
-[details]: /functions/go-template/return/
diff --git a/docs/content/en/functions/partials/IncludeCached.md b/docs/content/en/functions/partials/IncludeCached.md
deleted file mode 100644
index 3905ee15e..000000000
--- a/docs/content/en/functions/partials/IncludeCached.md
+++ /dev/null
@@ -1,57 +0,0 @@
----
-title: partials.IncludeCached
-description: Executes the given template and caches the result, optionally passing context. If the partial template contains a return statement, returns the given value, else returns the rendered output.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [partialCached]
- returnType: any
- signatures: ['partials.IncludeCached LAYOUT CONTEXT [VARIANT...]']
-aliases: [/functions/partialcached]
----
-
-Without a [`return`] statement, the `partialCached` function returns a string of type `template.HTML`. With a `return` statement, the `partialCached` function can return any data type.
-
-The `partialCached` function can offer significant performance gains for complex templates that don't need to be re-rendered on every invocation.
-
-> [!note]
-> Each Site (or language) has its own `partialCached` cache, so each site will execute a partial once.
->
-> Hugo renders pages in parallel, and will render the partial more than once with concurrent calls to the `partialCached` function. After Hugo caches the rendered partial, new pages entering the build pipeline will use the cached result.
-
-Here is the simplest usage:
-
-```go-html-template
-{{ partialCached "footer.html" . }}
-```
-
-Pass additional arguments to `partialCached` to create variants of the cached partial. For example, if you have a complex partial that should be identical when rendered for pages within the same section, use a variant based on section so that the partial is only rendered once per section:
-
-```go-html-template {file="layouts/_default/baseof.html"}
-{{ partialCached "footer.html" . .Section }}
-```
-
-Pass additional arguments, of any data type, as needed to create unique variants:
-
-```go-html-template
-{{ partialCached "footer.html" . .Params.country .Params.province }}
-```
-
-The variant arguments are not available to the underlying partial template; they are only used to create unique cache keys.
-
-To return a value from a partial template, it must contain only one `return` statement, placed at the end of the template:
-
-```go-html-template
-{{ $result := "" }}
-{{ if math.ModBool . 2 }}
- {{ $result = "even" }}
-{{ else }}
- {{ $result = "odd" }}
-{{ end }}
-{{ return $result }}
-```
-
-See [details][`return`].
-
-[`return`]: /functions/go-template/return/
diff --git a/docs/content/en/functions/partials/_index.md b/docs/content/en/functions/partials/_index.md
deleted file mode 100644
index 09b467399..000000000
--- a/docs/content/en/functions/partials/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Partial functions
-linkTitle: partials
-description: Use these functions to call partial templates.
-categories: []
-keywords: []
----
diff --git a/docs/content/en/functions/path/Base.md b/docs/content/en/functions/path/Base.md
deleted file mode 100644
index 39d52bfcb..000000000
--- a/docs/content/en/functions/path/Base.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-title: path.Base
-description: Replaces path separators with slashes (`/`) and returns the last element of the given path.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: string
- signatures: [path.Base PATH]
-aliases: [/functions/path.base]
----
-
-```go-html-template
-{{ path.Base "a/news.html" }} → news.html
-{{ path.Base "news.html" }} → news.html
-{{ path.Base "a/b/c" }} → c
-{{ path.Base "/x/y/z/" }} → z
-{{ path.Base "" }} → .
-```
diff --git a/docs/content/en/functions/path/BaseName.md b/docs/content/en/functions/path/BaseName.md
deleted file mode 100644
index 468898a6f..000000000
--- a/docs/content/en/functions/path/BaseName.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-title: path.BaseName
-description: Replaces path separators with slashes (`/`) and returns the last element of the given path, removing the extension if present.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: string
- signatures: [path.BaseName PATH]
-aliases: [/functions/path.basename]
----
-
-```go-html-template
-{{ path.BaseName "a/news.html" }} → news
-{{ path.BaseName "news.html" }} → news
-{{ path.BaseName "a/b/c" }} → c
-{{ path.BaseName "/x/y/z/" }} → z
-{{ path.BaseName "" }} → .
-```
diff --git a/docs/content/en/functions/path/Clean.md b/docs/content/en/functions/path/Clean.md
deleted file mode 100644
index b9f2de038..000000000
--- a/docs/content/en/functions/path/Clean.md
+++ /dev/null
@@ -1,27 +0,0 @@
----
-title: path.Clean
-description: Replaces path separators with slashes (`/`) and returns the shortest path name equivalent to the given path.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: string
- signatures: [path.Clean PATH]
-aliases: [/functions/path.clean]
----
-
-See Go's [`path.Clean`] documentation for details.
-
-[`path.Clean`]: https://pkg.go.dev/path#Clean
-
-```go-html-template
-{{ path.Clean "foo/bar" }} → foo/bar
-{{ path.Clean "/foo/bar" }} → /foo/bar
-{{ path.Clean "/foo/bar/" }} → /foo/bar
-{{ path.Clean "/foo//bar/" }} → /foo/bar
-{{ path.Clean "/foo/./bar/" }} → /foo/bar
-{{ path.Clean "/foo/../bar/" }} → /bar
-{{ path.Clean "/../foo/../bar/" }} → /bar
-{{ path.Clean "" }} → .
-```
diff --git a/docs/content/en/functions/path/Dir.md b/docs/content/en/functions/path/Dir.md
deleted file mode 100644
index 04d5500f5..000000000
--- a/docs/content/en/functions/path/Dir.md
+++ /dev/null
@@ -1,21 +0,0 @@
----
-title: path.Dir
-description: Replaces path separators with slashes (/) and returns all but the last element of the given path.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: string
- signatures: [path.Dir PATH]
-aliases: [/functions/path.dir]
----
-
-```go-html-template
-{{ path.Dir "a/news.html" }} → a
-{{ path.Dir "news.html" }} → .
-{{ path.Dir "a/b/c" }} → a/b
-{{ path.Dir "/a/b/c" }} → /a/b
-{{ path.Dir "/a/b/c/" }} → /a/b/c
-{{ path.Dir "" }} → .
-```
diff --git a/docs/content/en/functions/path/Ext.md b/docs/content/en/functions/path/Ext.md
deleted file mode 100644
index 3646d02d0..000000000
--- a/docs/content/en/functions/path/Ext.md
+++ /dev/null
@@ -1,18 +0,0 @@
----
-title: path.Ext
-description: Replaces path separators with slashes (`/`) and returns the file name extension of the given path.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: string
- signatures: [path.Ext PATH]
-aliases: [/functions/path.ext]
----
-
-The extension is the suffix beginning at the final dot in the final slash-separated element of path; it is empty if there is no dot.
-
-```go-html-template
-{{ path.Ext "a/b/c/news.html" }} → .html
-```
diff --git a/docs/content/en/functions/path/Join.md b/docs/content/en/functions/path/Join.md
deleted file mode 100644
index bda46737f..000000000
--- a/docs/content/en/functions/path/Join.md
+++ /dev/null
@@ -1,28 +0,0 @@
----
-title: path.Join
-description: Replaces path separators with slashes (`/`), joins the given path elements into a single path, and returns the shortest path name equivalent to the result.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: string
- signatures: [path.Join ELEMENT...]
-aliases: [/functions/path.join]
----
-
-See Go's [`path.Join`] and [`path.Clean`] documentation for details.
-
-[`path.Clean`]: https://pkg.go.dev/path#Clean
-[`path.Join`]: https://pkg.go.dev/path#Join
-
-```go-html-template
-{{ path.Join "partial" "news.html" }} → partial/news.html
-{{ path.Join "partial/" "news.html" }} → partial/news.html
-{{ path.Join "foo/bar" "baz" }} → foo/bar/baz
-{{ path.Join "foo" "bar" "baz" }} → foo/bar/baz
-{{ path.Join "foo" "" "baz" }} → foo/baz
-{{ path.Join "foo" "." "baz" }} → foo/baz
-{{ path.Join "foo" ".." "baz" }} → baz
-{{ path.Join "/.." "foo" ".." "baz" }} → baz
-```
diff --git a/docs/content/en/functions/path/Split.md b/docs/content/en/functions/path/Split.md
deleted file mode 100644
index d4f8d08e0..000000000
--- a/docs/content/en/functions/path/Split.md
+++ /dev/null
@@ -1,28 +0,0 @@
----
-title: path.Split
-description: Replaces path separators with slashes (`/`) and splits the resulting path immediately following the final slash, separating it into a directory and file name component.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: paths.DirFile
- signatures: [path.Split PATH]
-aliases: [/functions/path.split]
----
-
-If there is no slash in the given path, `path.Split` returns an empty directory, and file set to path. The returned values have the property that path = dir+file.
-
-```go-html-template
-{{ $dirFile := path.Split "a/news.html" }}
-{{ $dirFile.Dir }} → a/
-{{ $dirFile.File }} → news.html
-
-{{ $dirFile := path.Split "news.html" }}
-{{ $dirFile.Dir }} → "" (empty string)
-{{ $dirFile.File }} → news.html
-
-{{ $dirFile := path.Split "a/b/c" }}
-{{ $dirFile.Dir }} → a/b/
-{{ $dirFile.File }} → c
-```
diff --git a/docs/content/en/functions/path/_index.md b/docs/content/en/functions/path/_index.md
deleted file mode 100644
index 49b2927ce..000000000
--- a/docs/content/en/functions/path/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Path functions
-linkTitle: path
-description: Use these functions to work with file paths.
-categories: []
-keywords: []
----
diff --git a/docs/content/en/functions/reflect/IsMap.md b/docs/content/en/functions/reflect/IsMap.md
deleted file mode 100644
index 55b9811d7..000000000
--- a/docs/content/en/functions/reflect/IsMap.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: reflect.IsMap
-description: Reports whether the given value is a map.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: bool
- signatures: [reflect.IsMap INPUT]
-aliases: [/functions/reflect.ismap]
----
-
-```go-html-template
-{{ reflect.IsMap (dict "key" "value") }} → true
-{{ reflect.IsMap "yo" }} → false
-```
diff --git a/docs/content/en/functions/reflect/IsSlice.md b/docs/content/en/functions/reflect/IsSlice.md
deleted file mode 100644
index 3a5c1229b..000000000
--- a/docs/content/en/functions/reflect/IsSlice.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: reflect.IsSlice
-description: Reports whether the given value is a slice.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: bool
- signatures: [reflect.IsSlice INPUT]
-aliases: [/functions/reflect.isslice]
----
-
-```go-html-template
-{{ reflect.IsSlice (slice 1 2 3) }} → true
-{{ reflect.IsSlice "yo" }} → false
-```
diff --git a/docs/content/en/functions/reflect/_index.md b/docs/content/en/functions/reflect/_index.md
deleted file mode 100644
index 58f00c3de..000000000
--- a/docs/content/en/functions/reflect/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Reflect functions
-linkTitle: reflect
-description: Use these functions to determine a value's data type.
-categories: []
-keywords: []
----
diff --git a/docs/content/en/functions/resources/Babel.md b/docs/content/en/functions/resources/Babel.md
deleted file mode 100644
index 799823fc5..000000000
--- a/docs/content/en/functions/resources/Babel.md
+++ /dev/null
@@ -1,18 +0,0 @@
----
-title: resources.Babel
-description: Compiles the given JavaScript resource with Babel.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: resource.Resource
- signatures: ['resources.Babel [OPTIONS] RESOURCE']
-expiryDate: 2026-06-24 # deprecated 2024-06-24 in v0.128.0
----
-
-{{< deprecated-in 0.128.0 >}}
-Use [`js.Babel`] instead.
-
-[`js.Babel`]: /functions/js/babel/
-{{< /deprecated-in >}}
diff --git a/docs/content/en/functions/resources/ByType.md b/docs/content/en/functions/resources/ByType.md
deleted file mode 100644
index 99e2b9771..000000000
--- a/docs/content/en/functions/resources/ByType.md
+++ /dev/null
@@ -1,27 +0,0 @@
----
-title: resources.ByType
-description: Returns a collection of global resources of the given media type, or nil if none found.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: resource.Resources
- signatures: [resources.ByType MEDIATYPE]
----
-
-The [media type] is typically one of `image`, `text`, `audio`, `video`, or `application`.
-
-```go-html-template
-{{ range resources.ByType "image" }}
-
-{{ end }}
-```
-
-> [!note]
-> This function operates on global resources. A global resource is a file within the `assets` directory, or within any directory mounted to the `assets` directory.
->
-> For page resources, use the [`Resources.ByType`] method on a `Page` object.
-
-[`Resources.ByType`]: /methods/page/resources/
-[media type]: https://en.wikipedia.org/wiki/Media_type
diff --git a/docs/content/en/functions/resources/Concat.md b/docs/content/en/functions/resources/Concat.md
deleted file mode 100644
index fe7226e1b..000000000
--- a/docs/content/en/functions/resources/Concat.md
+++ /dev/null
@@ -1,26 +0,0 @@
----
-title: resources.Concat
-description: Returns a concatenated slice of resources.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: resource.Resource
- signatures: ['resources.Concat TARGETPATH [RESOURCE...]']
----
-
-The `resources.Concat` function returns a concatenated slice of resources, caching the result using the target path as its cache key. Each resource must have the same [media type].
-
-Hugo publishes the resource to the target path when you call its [`Publish`], [`Permalink`], or [`RelPermalink`] method.
-
-[media type]: https://en.wikipedia.org/wiki/Media_type
-[`publish`]: /methods/resource/publish/
-[`permalink`]: /methods/resource/permalink/
-[`relpermalink`]: /methods/resource/relpermalink/
-
-```go-html-template
-{{ $plugins := resources.Get "js/plugins.js" }}
-{{ $global := resources.Get "js/global.js" }}
-{{ $js := slice $plugins $global | resources.Concat "js/bundle.js" }}
-```
diff --git a/docs/content/en/functions/resources/Copy.md b/docs/content/en/functions/resources/Copy.md
deleted file mode 100644
index 220a3db4c..000000000
--- a/docs/content/en/functions/resources/Copy.md
+++ /dev/null
@@ -1,27 +0,0 @@
----
-title: resources.Copy
-description: Copies the given resource to the target path.
-categories: []
-params:
- functions_and_methods:
- aliases: []
- returnType: resource.Resource
- signatures: [resources.Copy TARGETPATH RESOURCE]
----
-
-```go-html-template
-{{ with resources.Get "images/a.jpg" }}
- {{ with resources.Copy "img/new-image-name.jpg" . }}
-
- {{ end }}
-{{ end }}
-```
-
-The relative URL of the new published resource will be:
-
-```text
-/img/new-image-name.jpg
-```
-
-> [!note]
-> Use the `resources.Copy` function with global, page, and remote resources.
diff --git a/docs/content/en/functions/resources/ExecuteAsTemplate.md b/docs/content/en/functions/resources/ExecuteAsTemplate.md
deleted file mode 100644
index bff83832e..000000000
--- a/docs/content/en/functions/resources/ExecuteAsTemplate.md
+++ /dev/null
@@ -1,61 +0,0 @@
----
-title: resources.ExecuteAsTemplate
-description: Returns a resource created from a Go template, parsed and executed with the given context.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: resource.Resource
- signatures: [resources.ExecuteAsTemplate TARGETPATH CONTEXT RESOURCE]
----
-
-The `resources.ExecuteAsTemplate` function returns a resource created from a Go template, parsed and executed with the given context, caching the result using the target path as its cache key.
-
-Hugo publishes the resource to the target path when you call its [`Publish`], [`Permalink`], or [`RelPermalink`] methods.
-
-Let's say you have a CSS file that you wish to populate with values from your site configuration:
-
-```go-html-template {file="assets/css/template.css"}
-body {
- background-color: {{ site.Params.style.bg_color }};
- color: {{ site.Params.style.text_color }};
-}
-```
-
-And your site configuration contains:
-
-{{< code-toggle file=hugo >}}
-[params.style]
-bg_color = '#fefefe'
-text_color = '#222'
-{{< /code-toggle >}}
-
-Place this in your baseof.html template:
-
-```go-html-template
-{{ with resources.Get "css/template.css" }}
- {{ with resources.ExecuteAsTemplate "css/main.css" $ . }}
-
- {{ end }}
-{{ end }}
-```
-
-The example above:
-
-1. Captures the template as a resource
-1. Executes the resource as a template, passing the current page in context
-1. Publishes the resource to css/main.css
-
-The result is:
-
-```css {file="public/css/main.css"}
-body {
- background-color: #fefefe;
- color: #222;
-}
-```
-
-[`publish`]: /methods/resource/publish/
-[`permalink`]: /methods/resource/permalink/
-[`relpermalink`]: /methods/resource/relpermalink/
diff --git a/docs/content/en/functions/resources/Fingerprint.md b/docs/content/en/functions/resources/Fingerprint.md
deleted file mode 100644
index 6757a0b6f..000000000
--- a/docs/content/en/functions/resources/Fingerprint.md
+++ /dev/null
@@ -1,36 +0,0 @@
----
-title: resources.Fingerprint
-description: Cryptographically hashes the content of the given resource.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [fingerprint]
- returnType: resource.Resource
- signatures: ['resources.Fingerprint [ALGORITHM] RESOURCE']
----
-
-```go-html-template
-{{ with resources.Get "js/main.js" }}
- {{ with . | fingerprint "sha256" }}
-
- {{ end }}
-{{ end }}
-```
-
-Hugo renders this to something like:
-
-```html
-
-```
-
-Although most commonly used with CSS and JavaScript resources, you can use the `resources.Fingerprint` function with any resource type.
-
-The hash algorithm may be one of `md5`, `sha256` (default), `sha384`, or `sha512`.
-
-After cryptographically hashing the resource content:
-
-1. The values returned by the `.Permalink` and `.RelPermalink` methods include the hash sum
-1. The resource's `.Data.Integrity` method returns a [Subresource Integrity] (SRI) value consisting of the name of the hash algorithm, one hyphen, and the base64-encoded hash sum
-
-[Subresource Integrity]: https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
diff --git a/docs/content/en/functions/resources/FromString.md b/docs/content/en/functions/resources/FromString.md
deleted file mode 100644
index 4cd04f609..000000000
--- a/docs/content/en/functions/resources/FromString.md
+++ /dev/null
@@ -1,76 +0,0 @@
----
-title: resources.FromString
-description: Returns a resource created from a string.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: resource.Resource
- signatures: [resources.FromString TARGETPATH STRING]
----
-
-The `resources.FromString` function returns a resource created from a string, caching the result using the target path as its cache key.
-
-Hugo publishes the resource to the target path when you call its [`Publish`], [`Permalink`], or [`RelPermalink`] methods.
-
-[`publish`]: /methods/resource/publish/
-[`permalink`]: /methods/resource/permalink/
-[`relpermalink`]: /methods/resource/relpermalink/
-
-Let's say you need to publish a file named "site.json" in the root of your `public` directory, containing the build date, the Hugo version used to build the site, and the date that the content was last modified. For example:
-
-```json
-{
- "build_date": "2025-01-16T19:14:41-08:00",
- "hugo_version": "0.141.0",
- "last_modified": "2025-01-16T19:14:46-08:00"
-}
-```
-
-Place this in your baseof.html template:
-
-```go-html-template
-{{ if .IsHome }}
- {{ $rfc3339 := "2006-01-02T15:04:05Z07:00" }}
- {{ $m := dict
- "hugo_version" hugo.Version
- "build_date" (now.Format $rfc3339)
- "last_modified" (site.Lastmod.Format $rfc3339)
- }}
- {{ $json := jsonify $m }}
- {{ $r := resources.FromString "site.json" $json }}
- {{ $r.Publish }}
-{{ end }}
-```
-
-The example above:
-
-1. Creates a map with the relevant key-value pairs using the [`dict`] function
-1. Encodes the map as a JSON string using the [`jsonify`] function
-1. Creates a resource from the JSON string using the `resources.FromString` function
-1. Publishes the file to the root of the `public` directory using the resource's `.Publish` method
-
-Combine `resources.FromString` with [`resources.ExecuteAsTemplate`] if your string contains template actions. Rewriting the example above:
-
-```go-html-template
-{{ if .IsHome }}
- {{ $string := `
- {{ $rfc3339 := "2006-01-02T15:04:05Z07:00" }}
- {{ $m := dict
- "hugo_version" hugo.Version
- "build_date" (now.Format $rfc3339)
- "last_modified" (site.Lastmod.Format $rfc3339)
- }}
- {{ $json := jsonify $m }}
- `
- }}
- {{ $r := resources.FromString "" $string }}
- {{ $r = $r | resources.ExecuteAsTemplate "site.json" . }}
- {{ $r.Publish }}
-{{ end }}
-```
-
-[`dict`]: /functions/collections/dictionary/
-[`jsonify`]: /functions/encoding/jsonify/
-[`resources.ExecuteAsTemplate`]: /functions/resources/executeastemplate/
diff --git a/docs/content/en/functions/resources/Get.md b/docs/content/en/functions/resources/Get.md
deleted file mode 100644
index db91f0a9a..000000000
--- a/docs/content/en/functions/resources/Get.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-title: resources.Get
-description: Returns a global resource from the given path, or nil if none found.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: resource.Resource
- signatures: [resources.Get PATH]
----
-
-```go-html-template
-{{ with resources.Get "images/a.jpg" }}
-
-{{ end }}
-```
-
-> [!note]
-> This function operates on global resources. A global resource is a file within the `assets` directory, or within any directory mounted to the `assets` directory.
->
-> For page resources, use the [`Resources.Get`] method on a `Page` object.
-
-[`Resources.Get`]: /methods/page/resources/
diff --git a/docs/content/en/functions/resources/GetMatch.md b/docs/content/en/functions/resources/GetMatch.md
deleted file mode 100644
index 8f1b004fe..000000000
--- a/docs/content/en/functions/resources/GetMatch.md
+++ /dev/null
@@ -1,28 +0,0 @@
----
-title: resources.GetMatch
-description: Returns the first global resource from paths matching the given glob pattern, or nil if none found.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: resource.Resource
- signatures: [resources.GetMatch PATTERN]
----
-
-```go-html-template
-{{ with resources.GetMatch "images/*.jpg" }}
-
-{{ end }}
-```
-
-> [!note]
-> This function operates on global resources. A global resource is a file within the `assets` directory, or within any directory mounted to the `assets` directory.
->
-> For page resources, use the [`Resources.GetMatch`] method on a `Page` object.
-
-Hugo determines a match using a case-insensitive [glob](g) pattern.
-
-{{% include "/_common/glob-patterns.md" %}}
-
-[`Resources.GetMatch`]: /methods/page/resources/
diff --git a/docs/content/en/functions/resources/GetRemote.md b/docs/content/en/functions/resources/GetRemote.md
deleted file mode 100644
index c6f6742b3..000000000
--- a/docs/content/en/functions/resources/GetRemote.md
+++ /dev/null
@@ -1,236 +0,0 @@
----
-title: resources.GetRemote
-description: Returns a remote resource from the given URL, or nil if none found.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: resource.Resource
- signatures: ['resources.GetRemote URL [OPTIONS]']
----
-
-{{< new-in 0.141.0 >}}
-The `Err` method on the returned resource was removed in v0.141.0.
-
-Use the [`try`] statement instead, as shown in the [error handling] example below.
-
-[`try`]: /functions/go-template/try
-[error handling]: #error-handling
-{{< /new-in >}}
-
-```go-html-template
-{{ $url := "https://example.org/images/a.jpg" }}
-{{ with try (resources.GetRemote $url) }}
- {{ with .Err }}
- {{ errorf "%s" . }}
- {{ else with .Value }}
-
- {{ else }}
- {{ errorf "Unable to get remote resource %q" $url }}
- {{ end }}
-{{ end }}
-```
-
-## Options
-
-The `resources.GetRemote` function takes an optional map of options.
-
-###### body
-
-(`string`) The data you want to transmit to the server.
-
-###### headers
-
-(`map[string][]string`) The collection of key-value pairs that provide additional information about the request.
-
-###### key
-
-(`string`) The cache key. Hugo derives the default value from the URL and options map. See [caching](#caching).
-
-###### method
-
-(`string`) The action to perform on the requested resource, typically one of `GET`, `POST`, or `HEAD`.
-
-###### responseHeaders
-{{< new-in 0.143.0 />}}
-
-(`[]string`) The headers to extract from the server's response, accessible through the resource's [`Data.Headers`] method. Header name matching is case-insensitive.
-
-[`Data.Headers`]: /methods/resource/data/#headers
-
-## Options examples
-
-> [!note]
-> For brevity, the examples below do not include [error handling].
-
-[error handling]: #error-handling
-
-To include a header:
-
-```go-html-template
-{{ $url := "https://example.org/api" }}
-{{ $opts := dict
- "headers" (dict "Authorization" "Bearer abcd")
-}}
-{{ $resource := resources.GetRemote $url $opts }}
-```
-
-To specify more than one value for the same header key, use a slice:
-
-```go-html-template
-{{ $url := "https://example.org/api" }}
-{{ $opts := dict
- "headers" (dict "X-List" (slice "a" "b" "c"))
-}}
-{{ $resource := resources.GetRemote $url $opts }}
-```
-
-To post data:
-
-```go-html-template
-{{ $url := "https://example.org/api" }}
-{{ $opts := dict
- "method" "post"
- "body" `{"complete": true}`
- "headers" (dict "Content-Type" "application/json")
-}}
-{{ $resource := resources.GetRemote $url $opts }}
-```
-
-To override the default cache key:
-
-```go-html-template
-{{ $url := "https://example.org/images/a.jpg" }}
-{{ $opts := dict
- "key" (print $url (now.Format "2006-01-02"))
-}}
-{{ $resource := resources.GetRemote $url $opts }}
-```
-
-To extract specific headers from the server's response:
-
-```go-html-template
-{{ $url := "https://example.org/images/a.jpg" }}
-{{ $opts := dict
- "method" "HEAD"
- "responseHeaders" (slice "X-Frame-Options" "Server")
-}}
-{{ $resource := resources.GetRemote $url $opts }}
-```
-
-## Remote data
-
-When retrieving remote data, use the [`transform.Unmarshal`] function to [unmarshal](g) the response.
-
-[`transform.Unmarshal`]: /functions/transform/unmarshal/
-
-```go-html-template
-{{ $data := dict }}
-{{ $url := "https://example.org/books.json" }}
-{{ with try (resources.GetRemote $url) }}
- {{ with .Err }}
- {{ errorf "%s" . }}
- {{ else with .Value }}
- {{ $data = . | transform.Unmarshal }}
- {{ else }}
- {{ errorf "Unable to get remote resource %q" $url }}
- {{ end }}
-{{ end }}
-```
-
-> [!note]
-> When retrieving remote data, a misconfigured server may send a response header with an incorrect [Content-Type]. For example, the server may set the Content-Type header to `application/octet-stream` instead of `application/json`.
->
-> In these cases, pass the resource `Content` through the `transform.Unmarshal` function instead of passing the resource itself. For example, in the above, do this instead:
->
-> `{{ $data = .Content | transform.Unmarshal }}`
-
-## Error handling
-
-Use the [`try`] statement to capture HTTP request errors. If you do not handle the error yourself, Hugo will fail the build.
-
-[`try`]: /functions/go-template/try
-
-> [!note]
-> Hugo does not classify an HTTP response with status code 404 as an error. In this case `resources.GetRemote` returns nil.
-
-```go-html-template
-{{ $url := "https://broken-example.org/images/a.jpg" }}
-{{ with try (resources.GetRemote $url) }}
- {{ with .Err }}
- {{ errorf "%s" . }}
- {{ else with .Value }}
-
- {{ else }}
- {{ errorf "Unable to get remote resource %q" $url }}
- {{ end }}
-{{ end }}
-```
-
-To log an error as a warning instead of an error:
-
-```go-html-template
-{{ $url := "https://broken-example.org/images/a.jpg" }}
-{{ with try (resources.GetRemote $url) }}
- {{ with .Err }}
- {{ warnf "%s" . }}
- {{ else with .Value }}
-
- {{ else }}
- {{ warnf "Unable to get remote resource %q" $url }}
- {{ end }}
-{{ end }}
-```
-
-## HTTP response
-
-The [`Data`] method on a resource returned by the `resources.GetRemote` function returns information from the HTTP response.
-
-[`Data`]: /methods/resource/data/
-
-## Caching
-
-Resources returned from `resources.GetRemote` are cached to disk. See [configure file caches] for details.
-
-By default, Hugo derives the cache key from the arguments passed to the function. Override the cache key by setting a `key` in the options map. Use this approach to have more control over how often Hugo fetches a remote resource.
-
-```go-html-template
-{{ $url := "https://example.org/images/a.jpg" }}
-{{ $cacheKey := print $url (now.Format "2006-01-02") }}
-{{ $opts := dict "key" $cacheKey }}
-{{ $resource := resources.GetRemote $url $opts }}
-```
-
-[configure file caches]: /configuration/caches/
-
-## Security
-
-To protect against malicious intent, the `resources.GetRemote` function inspects the server response including:
-
-- The [Content-Type] in the response header
-- The file extension, if any
-- The content itself
-
-If Hugo is unable to resolve the media type to an entry in its [allowlist], the function throws an error:
-
-```text
-ERROR error calling resources.GetRemote: failed to resolve media type...
-```
-
-For example, you will see the error above if you attempt to download an executable.
-
-Although the allowlist contains entries for common media types, you may encounter situations where Hugo is unable to resolve the media type of a file that you know to be safe. In these situations, edit your site configuration to add the media type to the allowlist. For example:
-
-{{< code-toggle file=hugo >}}
-[security.http]
-mediaTypes = ['^image/avif$','^application/vnd\.api\+json$']
-{{< /code-toggle >}}
-
-Note that the entry above is:
-
-- An _addition_ to the allowlist; it does not _replace_ the allowlist
-- An array of [regular expressions](g)
-
-[allowlist]: https://en.wikipedia.org/wiki/Whitelist
-[Content-Type]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type
diff --git a/docs/content/en/functions/resources/Match.md b/docs/content/en/functions/resources/Match.md
deleted file mode 100644
index 6c7d83649..000000000
--- a/docs/content/en/functions/resources/Match.md
+++ /dev/null
@@ -1,29 +0,0 @@
----
-title: resources.Match
-description: Returns a collection of global resources from paths matching the given glob pattern, or nil if none found.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: resource.Resources
- signatures: [resources.Match PATTERN]
----
-
-```go-html-template
-{{ range resources.Match "images/*.jpg" }}
-
-{{ end }}
-```
-
-> [!note]
-> This function operates on global resources. A global resource is a file within the `assets` directory, or within any directory mounted to the `assets` directory.
->
-> For page resources, use the [`Resources.Match`] method on a `Page` object.
-
-Hugo determines a match using a case-insensitive [glob pattern].
-
-{{% include "/_common/glob-patterns.md" %}}
-
-[`Resources.Match`]: /methods/page/resources/
-[glob pattern]: https://github.com/gobwas/glob#example
diff --git a/docs/content/en/functions/resources/Minify.md b/docs/content/en/functions/resources/Minify.md
deleted file mode 100644
index 183e6671a..000000000
--- a/docs/content/en/functions/resources/Minify.md
+++ /dev/null
@@ -1,18 +0,0 @@
----
-title: resources.Minify
-description: Minifies the given resource.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [minify]
- returnType: resource.Resource
- signatures: [resources.Minify RESOURCE]
----
-
-```go-html-template
-{{ $css := resources.Get "css/main.css" }}
-{{ $style := $css | minify }}
-```
-
-Any CSS, JS, JSON, HTML, SVG, or XML resource can be minified using resources.Minify which takes for argument the resource object.
diff --git a/docs/content/en/functions/resources/PostCSS.md b/docs/content/en/functions/resources/PostCSS.md
deleted file mode 100644
index 3ec0b84cf..000000000
--- a/docs/content/en/functions/resources/PostCSS.md
+++ /dev/null
@@ -1,18 +0,0 @@
----
-title: resources.PostCSS
-description: Processes the given resource with PostCSS using any PostCSS plugin.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: resource.Resource
- signatures: ['resources.PostCSS [OPTIONS] RESOURCE']
-expiryDate: 2026-06-24 # deprecated 2024-06-24 in v0.128.0
----
-
-{{< deprecated-in 0.128.0 >}}
-Use [`css.PostCSS`] instead.
-
-[`css.PostCSS`]: /functions/css/postcss/
-{{< /deprecated-in >}}
diff --git a/docs/content/en/functions/resources/PostProcess.md b/docs/content/en/functions/resources/PostProcess.md
deleted file mode 100644
index d70437694..000000000
--- a/docs/content/en/functions/resources/PostProcess.md
+++ /dev/null
@@ -1,148 +0,0 @@
----
-title: resources.PostProcess
-description: Processes the given resource after the build.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: postpub.PostPublishedResource
- signatures: [resources.PostProcess RESOURCE]
----
-
-The `resources.PostProcess` function delays resource transformation steps until the build is complete, primarily for tasks like removing unused CSS rules.
-
-## Example
-
-In this example, after the build is complete, Hugo will:
-
-1. Purge unused CSS using the [PurgeCSS] plugin for [PostCSS]
-2. Add vendor prefixes to CSS rules using the [Autoprefixer] plugin for PostCSS
-3. [Minify] the CSS
-4. [Fingerprint] the CSS
-
-Step 1
-: Install [Node.js].
-
-Step 2
-: Install the required Node.js packages in the root of your project:
-
-```sh
-npm i -D postcss postcss-cli autoprefixer @fullhuman/postcss-purgecss
-```
-
-Step 3
-: Enable creation of the `hugo_stats.json` file when building the site. If you are only using this for the production build, consider placing it below [`config/production`].
-
-{{< code-toggle file=hugo >}}
-[build.buildStats]
-enable = true
-{{< /code-toggle >}}
-
-See the [configure build] documentation for details and options.
-
-Step 4
-: Create a PostCSS configuration file in the root of your project.
-
-```js {file="postcss.config.js" copy=true}
-const autoprefixer = require('autoprefixer');
-const purgeCSSPlugin = require('@fullhuman/postcss-purgecss').default;
-
-const purgecss = purgeCSSPlugin({
- content: ['./hugo_stats.json'],
- defaultExtractor: content => {
- const els = JSON.parse(content).htmlElements;
- return [
- ...(els.tags || []),
- ...(els.classes || []),
- ...(els.ids || []),
- ];
- },
- // https://purgecss.com/safelisting.html
- safelist: []
-});
-
-module.exports = {
- plugins: [
- process.env.HUGO_ENVIRONMENT !== 'development' ? purgecss : null,
- autoprefixer,
- ]
-};
-```
-
-> [!note]
-> If you are a Windows user, and the path to your project contains a space, you must place the PostCSS configuration within the package.json file. See [this example] and issue [#7333].
-
-Step 5
-: Place your CSS file within the `assets/css` directory.
-
-Step 6
-: If the current environment is not `development`, process the resource with PostCSS:
-
-```go-html-template
-{{ with resources.Get "css/main.css" }}
- {{ if hugo.IsDevelopment }}
-
- {{ else }}
- {{ with . | postCSS | minify | fingerprint | resources.PostProcess }}
-
- {{ end }}
- {{ end }}
-{{ end }}
-```
-
-## Environment variables
-
-Hugo passes the environment variables below to PostCSS, allowing you to do something like:
-
-```js
-process.env.HUGO_ENVIRONMENT !== 'development' ? purgecss : null,
-```
-
-PWD
-: The absolute path to the project working directory.
-
-HUGO_ENVIRONMENT
-: The current Hugo environment, set with the `--environment` command line flag.
-Default is `production` for `hugo` and `development` for `hugo server`.
-
-HUGO_PUBLISHDIR
-: The absolute path to the publish directory, typically `public`. This value points to a directory on disk, even when rendering to memory with the `--renderToMemory` command line flag.
-
-HUGO_FILE_X
-: Hugo automatically mounts the following files from your project's root directory under `assets/_jsconfig`:
-
-- `babel.config.js`
-- `postcss.config.js`
-- `tailwind.config.js`
-
-For each file, Hugo creates a corresponding environment variable named `HUGO_FILE_:filename:`, where `:filename:` is the uppercase version of the filename with periods replaced by underscores. This allows you to access these files within your JavaScript, for example:
-
-```js
-let tailwindConfig = process.env.HUGO_FILE_TAILWIND_CONFIG_JS || './tailwind.config.js';
-```
-
-## Limitations
-
-Do not use `resources.PostProcess` when running Hugo's built-in development server. The examples above specifically prevent this by verifying that the current environment is not "development".
-
-The `resources.PostProcess` function only works within templates that produce HTML files.
-
-You cannot manipulate the values returned from the resource's methods. For example, the `strings.ToUpper` function in this example will not work as expected:
-
-```go-html-template
-{{ $css := resources.Get "css/main.css" }}
-{{ $css = $css | css.PostCSS | minify | fingerprint | resources.PostProcess }}
-{{ $css.RelPermalink | strings.ToUpper }}
-```
-
-[#7333]: https://github.com/gohugoio/hugo/issues/7333
-[`config/production`]: /configuration/introduction/#configuration-directory
-[Autoprefixer]: https://github.com/postcss/autoprefixer
-[configure build]: /configuration/build/
-[Fingerprint]: /functions/resources/fingerprint/
-[Minify]: /functions/resources/minify/
-[Node.js]: https://nodejs.org/en
-[PostCSS]: https://postcss.org/
-[PurgeCSS]: https://github.com/FullHuman/purgecss
-[this example]: https://github.com/postcss/postcss-load-config#packagejson
diff --git a/docs/content/en/functions/resources/ToCSS.md b/docs/content/en/functions/resources/ToCSS.md
deleted file mode 100644
index 7be1b8d45..000000000
--- a/docs/content/en/functions/resources/ToCSS.md
+++ /dev/null
@@ -1,18 +0,0 @@
----
-title: resources.ToCSS
-description: Transpiles Sass to CSS.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: resource.Resource
- signatures: ['resources.ToCSS [OPTIONS] RESOURCE']
-expiryDate: 2026-06-24 # deprecated 2024-06-24 in v0.128.0
----
-
-{{< deprecated-in 0.128.0 >}}
-Use [`css.Sass`] instead.
-
-[`css.Sass`]: /functions/css/sass/
-{{< /deprecated-in >}}
diff --git a/docs/content/en/functions/resources/_index.md b/docs/content/en/functions/resources/_index.md
deleted file mode 100644
index 030cafd42..000000000
--- a/docs/content/en/functions/resources/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Resource functions
-linkTitle: resources
-description: Use these functions to work with resources.
-categories: []
-keywords: []
----
diff --git a/docs/content/en/functions/safe/CSS.md b/docs/content/en/functions/safe/CSS.md
deleted file mode 100644
index 12ebbf8aa..000000000
--- a/docs/content/en/functions/safe/CSS.md
+++ /dev/null
@@ -1,62 +0,0 @@
----
-title: safe.CSS
-description: Declares the given string as a safe CSS string.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [safeCSS]
- returnType: template.CSS
- signatures: [safe.CSS INPUT]
-aliases: [/functions/safecss]
----
-
-## Introduction
-
-{{% include "/_common/functions/go-html-template-package.md" %}}
-
-## Usage
-
-Use the `safe.CSS` function to encapsulate known safe content that matches any of:
-
-1. The CSS3 stylesheet production, such as `p { color: purple }`.
-1. The CSS3 rule production, such as `a[href=~"https:"].foo#bar`.
-1. CSS3 declaration productions, such as `color: red; margin: 2px`.
-1. The CSS3 value production, such as `rgba(0, 0, 255, 127)`.
-
-Use of this type presents a security risk: the encapsulated content should come from a trusted source, as it will be included verbatim in the template output.
-
-See the [Go documentation] for details.
-
-## Example
-
-Without a safe declaration:
-
-```go-html-template
-{{ $style := "color: red;" }}
-
foo
-```
-
-Hugo renders the above to:
-
-```html
-
foo
-```
-
-> [!note]
-> `ZgotmplZ` is a special value that indicates that unsafe content reached a CSS or URL context at runtime.
-
-To declare the string as safe:
-
-```go-html-template
-{{ $style := "color: red;" }}
-
foo
-```
-
-Hugo renders the above to:
-
-```html
-
foo
-```
-
-[Go documentation]: https://pkg.go.dev/html/template#CSS
diff --git a/docs/content/en/functions/safe/HTML.md b/docs/content/en/functions/safe/HTML.md
deleted file mode 100644
index 25ffb3318..000000000
--- a/docs/content/en/functions/safe/HTML.md
+++ /dev/null
@@ -1,54 +0,0 @@
----
-title: safe.HTML
-description: Declares the given string as a safeHTML string.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [safeHTML]
- returnType: template.HTML
- signatures: [safe.HTML INPUT]
-aliases: [/functions/safehtml]
----
-
-## Introduction
-
-{{% include "/_common/functions/go-html-template-package.md" %}}
-
-## Usage
-
-Use the `safe.HTML` function to encapsulate a known safe HTML document fragment. It should not be used for HTML from a third-party, or HTML with unclosed tags or comments.
-
-Use of this type presents a security risk: the encapsulated content should come from a trusted source, as it will be included verbatim in the template output.
-
-See the [Go documentation] for details.
-
-[Go documentation]: https://pkg.go.dev/html/template#HTML
-
-## Example
-
-Without a safe declaration:
-
-```go-html-template
-{{ $html := "emphasized" }}
-{{ $html }}
-```
-
-Hugo renders the above to:
-
-```html
-<em>emphasized</em>
-```
-
-To declare the string as safe:
-
-```go-html-template
-{{ $html := "emphasized" }}
-{{ $html | safeHTML }}
-```
-
-Hugo renders the above to:
-
-```html
-emphasized
-```
diff --git a/docs/content/en/functions/safe/HTMLAttr.md b/docs/content/en/functions/safe/HTMLAttr.md
deleted file mode 100644
index 7cfefdfb2..000000000
--- a/docs/content/en/functions/safe/HTMLAttr.md
+++ /dev/null
@@ -1,60 +0,0 @@
----
-title: safe.HTMLAttr
-description: Declares the given key-value pair as a safe HTML attribute.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [safeHTMLAttr]
- returnType: template.HTMLAttr
- signatures: [safe.HTMLAttr INPUT]
-aliases: [/functions/safehtmlattr]
----
-
-## Introduction
-
-{{% include "/_common/functions/go-html-template-package.md" %}}
-
-## Usage
-
-Use the `safe.HTMLAttr` function to encapsulate an HTML attribute from a trusted source.
-
-Use of this type presents a security risk: the encapsulated content should come from a trusted source, as it will be included verbatim in the template output.
-
-See the [Go documentation] for details.
-
-[Go documentation]: https://pkg.go.dev/html/template#HTMLAttr
-
-## Example
-
-Without a safe declaration:
-
-```go-html-template
-{{ with .Date }}
- {{ $humanDate := time.Format "2 Jan 2006" . }}
- {{ $machineDate := time.Format "2006-01-02T15:04:05-07:00" . }}
-
-{{ end }}
-```
-
-Hugo renders the above to:
-
-```html
-
-```
-
-To declare the key-value pair as safe:
-
-```go-html-template
-{{ with .Date }}
- {{ $humanDate := time.Format "2 Jan 2006" . }}
- {{ $machineDate := time.Format "2006-01-02T15:04:05-07:00" . }}
-
-{{ end }}
-```
-
-Hugo renders the above to:
-
-```html
-
-```
diff --git a/docs/content/en/functions/safe/JS.md b/docs/content/en/functions/safe/JS.md
deleted file mode 100644
index 0c4d9009d..000000000
--- a/docs/content/en/functions/safe/JS.md
+++ /dev/null
@@ -1,60 +0,0 @@
----
-title: safe.JS
-description: Declares the given string as a safe JavaScript expression.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [safeJS]
- returnType: template.JS
- signatures: [safe.JS INPUT]
-aliases: [/functions/safejs]
----
-
-## Introduction
-
-{{% include "/_common/functions/go-html-template-package.md" %}}
-
-## Usage
-
-Use the `safe.JS` function to encapsulate a known safe EcmaScript5 Expression.
-
-Template authors are responsible for ensuring that typed expressions do not break the intended precedence and that there is no statement/expression ambiguity as when passing an expression like `{ foo: bar() }\n['foo']()`, which is both a valid Expression and a valid Program with a very different meaning.
-
-Use of this type presents a security risk: the encapsulated content should come from a trusted source, as it will be included verbatim in the template output.
-
-Using the `safe.JS` function to include valid but untrusted JSON is not safe. A safe alternative is to parse the JSON with the [`transform.Unmarshal`] function and then pass the resultant object into the template, where it will be converted to sanitized JSON when presented in a JavaScript context.
-
-[`transform.Unmarshal`]: /functions/transform/unmarshal/
-
-See the [Go documentation] for details.
-
-[Go documentation]: https://pkg.go.dev/html/template#JS
-
-## Example
-
-Without a safe declaration:
-
-```go-html-template
-{{ $js := "x + y" }}
-
-```
-
-Hugo renders the above to:
-
-```html
-
-```
-
-To declare the string as safe:
-
-```go-html-template
-{{ $js := "x + y" }}
-
-```
-
-Hugo renders the above to:
-
-```html
-
-```
diff --git a/docs/content/en/functions/safe/JSStr.md b/docs/content/en/functions/safe/JSStr.md
deleted file mode 100644
index 81946a14c..000000000
--- a/docs/content/en/functions/safe/JSStr.md
+++ /dev/null
@@ -1,62 +0,0 @@
----
-title: safe.JSStr
-description: Declares the given string as a safe JavaScript string.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [safeJSStr]
- returnType: template.JSStr
- signatures: [safe.JSStr INPUT]
-aliases: [/functions/safejsstr]
----
-
-## Introduction
-
-{{% include "/_common/functions/go-html-template-package.md" %}}
-
-## Usage
-
-Use the `safe.JSStr` function to encapsulate a sequence of characters meant to be embedded between quotes in a JavaScript expression.
-
-Use of this type presents a security risk: the encapsulated content should come from a trusted source, as it will be included verbatim in the template output.
-
-See the [Go documentation] for details.
-
-[Go documentation]: https://pkg.go.dev/html/template#JSStr
-
-## Example
-
-Without a safe declaration:
-
-```go-html-template
-{{ $title := "Lilo & Stitch" }}
-
-```
-
-Hugo renders the above to:
-
-```html
-
-```
-
-To declare the string as safe:
-
-```go-html-template
-{{ $title := "Lilo & Stitch" }}
-
-```
-
-Hugo renders the above to:
-
-```html
-
-```
diff --git a/docs/content/en/functions/safe/URL.md b/docs/content/en/functions/safe/URL.md
deleted file mode 100644
index 44bed8064..000000000
--- a/docs/content/en/functions/safe/URL.md
+++ /dev/null
@@ -1,61 +0,0 @@
----
-title: safe.URL
-description: Declares the given string as a safe URL or URL substring.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [safeURL]
- returnType: template.URL
- signatures: [safe.URL INPUT]
-aliases: [/functions/safeurl]
----
-
-## Introduction
-
-{{% include "/_common/functions/go-html-template-package.md" %}}
-
-## Usage
-
-Use the `safe.URL` function to encapsulate a known safe URL or URL substring. Schemes other than the following are considered unsafe:
-
-- `http:`
-- `https:`
-- `mailto:`
-
-Use of this type presents a security risk: the encapsulated content should come from a trusted source, as it will be included verbatim in the template output.
-
-See the [Go documentation] for details.
-
-## Example
-
-Without a safe declaration:
-
-```go-html-template
-{{ $href := "irc://irc.freenode.net/#golang" }}
-IRC
-```
-
-Hugo renders the above to:
-
-```html
-IRC
-```
-
-> [!note]
-> `ZgotmplZ` is a special value that indicates that unsafe content reached a CSS or URL context at runtime.
-
-To declare the string as safe:
-
-```go-html-template
-{{ $href := "irc://irc.freenode.net/#golang" }}
-IRC
-```
-
-Hugo renders the above to:
-
-```html
-IRC
-```
-
-[Go documentation]: https://pkg.go.dev/html/template#URL
diff --git a/docs/content/en/functions/safe/_index.md b/docs/content/en/functions/safe/_index.md
deleted file mode 100644
index 8d5697b8d..000000000
--- a/docs/content/en/functions/safe/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Safe functions
-linkTitle: safe
-description: Use these functions to declare a value as safe in the context of Go's html/template package.
-categories: []
-keywords: []
----
diff --git a/docs/content/en/functions/strings/Chomp.md b/docs/content/en/functions/strings/Chomp.md
deleted file mode 100644
index 1ff2b7f47..000000000
--- a/docs/content/en/functions/strings/Chomp.md
+++ /dev/null
@@ -1,22 +0,0 @@
----
-title: strings.Chomp
-description: Returns the given string, removing all trailing newline characters and carriage returns.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [chomp]
- returnType: any
- signatures: [strings.Chomp STRING]
-aliases: [/functions/chomp]
----
-
-If the argument is of type `template.HTML`, returns `template.HTML`, else returns a `string`.
-
-```go-html-template
-{{ chomp "foo\n" }} → foo
-{{ chomp "foo\n\n" }} → foo
-
-{{ chomp "foo\r\n" }} → foo
-{{ chomp "foo\r\n\r\n" }} → foo
-```
diff --git a/docs/content/en/functions/strings/Contains.md b/docs/content/en/functions/strings/Contains.md
deleted file mode 100644
index e0e3b087c..000000000
--- a/docs/content/en/functions/strings/Contains.md
+++ /dev/null
@@ -1,22 +0,0 @@
----
-title: strings.Contains
-description: Reports whether the given string contains the given substring.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: bool
- signatures: [strings.Contains STRING SUBSTRING]
-aliases: [/functions/strings.contains]
----
-
-```go-html-template
-{{ strings.Contains "Hugo" "go" }} → true
-```
-
-The check is case sensitive:
-
-```go-html-template
-{{ strings.Contains "Hugo" "Go" }} → false
-```
diff --git a/docs/content/en/functions/strings/ContainsAny.md b/docs/content/en/functions/strings/ContainsAny.md
deleted file mode 100644
index 521ff3421..000000000
--- a/docs/content/en/functions/strings/ContainsAny.md
+++ /dev/null
@@ -1,22 +0,0 @@
----
-title: strings.ContainsAny
-description: Reports whether the given string contains any character within the given set.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: bool
- signatures: [strings.ContainsAny STRING SET]
-aliases: [/functions/strings.containsany]
----
-
-```go-html-template
-{{ strings.ContainsAny "Hugo" "gm" }} → true
-```
-
-The check is case sensitive:
-
-```go-html-template
-{{ strings.ContainsAny "Hugo" "Gm" }} → false
-```
diff --git a/docs/content/en/functions/strings/ContainsNonSpace.md b/docs/content/en/functions/strings/ContainsNonSpace.md
deleted file mode 100644
index 7b8dcb730..000000000
--- a/docs/content/en/functions/strings/ContainsNonSpace.md
+++ /dev/null
@@ -1,22 +0,0 @@
----
-title: strings.ContainsNonSpace
-description: Reports whether the given string contains any non-space characters as defined by Unicode.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: bool
- signatures: [strings.ContainsNonSpace STRING]
-aliases: [/functions/strings.containsnonspace]
----
-
-Whitespace characters include `\t`, `\n`, `\v`, `\f`, `\r`, and characters in the [Unicode Space Separator] category.
-
-[Unicode Space Separator]: https://www.compart.com/en/unicode/category/Zs
-
-```go-html-template
-{{ strings.ContainsNonSpace "\n" }} → false
-{{ strings.ContainsNonSpace " " }} → false
-{{ strings.ContainsNonSpace "\n abc" }} → true
-```
diff --git a/docs/content/en/functions/strings/Count.md b/docs/content/en/functions/strings/Count.md
deleted file mode 100644
index 76378b27d..000000000
--- a/docs/content/en/functions/strings/Count.md
+++ /dev/null
@@ -1,21 +0,0 @@
----
-title: strings.Count
-description: Returns the number of non-overlapping instances of the given substring within the given string.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: int
- signatures: [strings.Count SUBSTR STRING]
-aliases: [/functions/strings.count]
----
-
-If `SUBSTR` is an empty string, this function returns 1 plus the number of Unicode code points in `STRING`.
-
-```go-html-template
-{{ "aaabaab" | strings.Count "a" }} → 5
-{{ "aaabaab" | strings.Count "aa" }} → 2
-{{ "aaabaab" | strings.Count "aaa" }} → 1
-{{ "aaabaab" | strings.Count "" }} → 8
-```
diff --git a/docs/content/en/functions/strings/CountRunes.md b/docs/content/en/functions/strings/CountRunes.md
deleted file mode 100644
index 8ad6b00da..000000000
--- a/docs/content/en/functions/strings/CountRunes.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-title: strings.CountRunes
-description: Returns the number of runes in the given string excluding whitespace.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [countrunes]
- returnType: int
- signatures: [strings.CountRunes INPUT]
-aliases: [/functions/countrunes]
----
-
-In contrast with the [`strings.RuneCount`] function, which counts every rune in a string, `strings.CountRunes` excludes whitespace.
-
-```go-html-template
-{{ "Hello, 世界" | strings.CountRunes }} → 8
-```
-
-[`strings.RuneCount`]: /functions/strings/runecount/
diff --git a/docs/content/en/functions/strings/CountWords.md b/docs/content/en/functions/strings/CountWords.md
deleted file mode 100644
index e50febf69..000000000
--- a/docs/content/en/functions/strings/CountWords.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-title: strings.CountWords
-description: Returns the number of words in the given string.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [countwords]
- returnType: int
- signatures: [strings.CountWords INPUT]
-aliases: [/functions/countwords]
----
-
-```go-html-template
-{{ "Hugo is a static site generator." | countwords }} → 6
-```
diff --git a/docs/content/en/functions/strings/Diff/diff-screen-capture.png b/docs/content/en/functions/strings/Diff/diff-screen-capture.png
deleted file mode 100644
index 62baa4563..000000000
Binary files a/docs/content/en/functions/strings/Diff/diff-screen-capture.png and /dev/null differ
diff --git a/docs/content/en/functions/strings/Diff/index.md b/docs/content/en/functions/strings/Diff/index.md
deleted file mode 100644
index 1426764a9..000000000
--- a/docs/content/en/functions/strings/Diff/index.md
+++ /dev/null
@@ -1,33 +0,0 @@
----
-title: strings.Diff
-description: Returns an anchored diff of the two texts OLD and NEW in the unified diff format. If OLD and NEW are identical, returns an empty string.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: string
- signatures: [strings.Diff OLDNAME OLD NEWNAME NEW]
----
-
-{{< new-in 0.125.0 />}}
-
-Use `strings.Diff` to compare two strings and render a highlighted diff:
-
-```go-html-template
-{{ $want := `
-
The product of 6 and 7 is 42.
-
The product of 7 and 6 is 42.
-`}}
-
-{{ $got := `
-
The product of 6 and 7 is 42.
-
The product of 7 and 6 is 13.
-`}}
-
-{{ $diff := strings.Diff "want" $want "got" $got }}
-{{ transform.Highlight $diff "diff" }}
-```
-
-Rendered:
-
-
diff --git a/docs/content/en/functions/strings/FindRESubmatch.md b/docs/content/en/functions/strings/FindRESubmatch.md
deleted file mode 100644
index d039607fb..000000000
--- a/docs/content/en/functions/strings/FindRESubmatch.md
+++ /dev/null
@@ -1,86 +0,0 @@
----
-title: strings.FindRESubmatch
-description: Returns a slice of all successive matches of the regular expression. Each element is a slice of strings holding the text of the leftmost match of the regular expression and the matches, if any, of its subexpressions.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [findRESubmatch]
- returnType: '[][]string'
- signatures: ['strings.FindRESubmatch PATTERN INPUT [LIMIT]']
-aliases: [/functions/findresubmatch]
----
-
-By default, `findRESubmatch` finds all matches. You can limit the number of matches with an optional LIMIT argument. A return value of nil indicates no match.
-
-{{% include "/_common/functions/regular-expressions.md" %}}
-
-## Demonstrative examples
-
-```go-html-template
-{{ findRESubmatch `a(x*)b` "-ab-" }} → [["ab" ""]]
-{{ findRESubmatch `a(x*)b` "-axxb-" }} → [["axxb" "xx"]]
-{{ findRESubmatch `a(x*)b` "-ab-axb-" }} → [["ab" ""] ["axb" "x"]]
-{{ findRESubmatch `a(x*)b` "-axxb-ab-" }} → [["axxb" "xx"] ["ab" ""]]
-{{ findRESubmatch `a(x*)b` "-axxb-ab-" 1 }} → [["axxb" "xx"]]
-```
-
-## Practical example
-
-This Markdown:
-
-```text
-- [Example](https://example.org)
-- [Hugo](https://gohugo.io)
-```
-
-Produces this HTML:
-
-```html
-
-```
-
-To match the anchor elements, capturing the link destination and text:
-
-```go-html-template
-{{ $regex := `(.+?)` }}
-{{ $matches := findRESubmatch $regex .Content }}
-```
-
-Viewed as JSON, the data structure of `$matches` in the code above is:
-
-```json
-[
- [
- "Example",
- "https://example.org",
- "Example"
- ],
- [
- "Hugo",
- "https://gohugo.io",
- "Hugo"
- ]
-]
-```
-
-To render the `href` attributes:
-
-```go-html-template
-{{ range $matches }}
- {{ index . 1 }}
-{{ end }}
-```
-
-Result:
-
-```text
-https://example.org
-https://gohugo.io
-```
-
-> [!note]
-> You can write and test your regular expression using [regex101.com](https://regex101.com/). Be sure to select the Go flavor before you begin.
diff --git a/docs/content/en/functions/strings/FindRe.md b/docs/content/en/functions/strings/FindRe.md
deleted file mode 100644
index 45129ec91..000000000
--- a/docs/content/en/functions/strings/FindRe.md
+++ /dev/null
@@ -1,32 +0,0 @@
----
-title: strings.FindRE
-description: Returns a slice of strings that match the regular expression.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [findRE]
- returnType: '[]string'
- signatures: ['strings.FindRE PATTERN INPUT [LIMIT]']
-aliases: [/functions/findre]
----
-By default, `findRE` finds all matches. You can limit the number of matches with an optional LIMIT argument.
-
-{{% include "/_common/functions/regular-expressions.md" %}}
-
-This example returns a slice of all second level headings (`h2` elements) within the rendered `.Content`:
-
-```go-html-template
-{{ findRE `(?s).*?` .Content }}
-```
-
-The `s` flag causes `.` to match `\n` as well, allowing us to find an `h2` element that contains newlines.
-
-To limit the number of matches to one:
-
-```go-html-template
-{{ findRE `(?s).*?` .Content 1 }}
-```
-
-> [!note]
-> You can write and test your regular expression using [regex101.com](https://regex101.com/). Be sure to select the Go flavor before you begin.
diff --git a/docs/content/en/functions/strings/FirstUpper.md b/docs/content/en/functions/strings/FirstUpper.md
deleted file mode 100644
index 41bf1f70a..000000000
--- a/docs/content/en/functions/strings/FirstUpper.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-title: strings.FirstUpper
-description: Returns the given string, capitalizing the first character.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: string
- signatures: [strings.FirstUpper STRING]
-aliases: [/functions/strings.firstupper]
----
-
-```go-html-template
-{{ strings.FirstUpper "foo" }} → Foo
-```
diff --git a/docs/content/en/functions/strings/HasPrefix.md b/docs/content/en/functions/strings/HasPrefix.md
deleted file mode 100644
index 2babe8552..000000000
--- a/docs/content/en/functions/strings/HasPrefix.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-title: strings.HasPrefix
-description: Reports whether the given string begins with the given prefix.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [hasPrefix]
- returnType: bool
- signatures: [strings.HasPrefix STRING PREFIX]
-aliases: [/functions/hasprefix,/functions/strings.hasprefix]
----
-
-```go-html-template
-{{ hasPrefix "Hugo" "Hu" }} → true
-```
diff --git a/docs/content/en/functions/strings/HasSuffix.md b/docs/content/en/functions/strings/HasSuffix.md
deleted file mode 100644
index c6b5f4ded..000000000
--- a/docs/content/en/functions/strings/HasSuffix.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-title: strings.HasSuffix
-description: Reports whether the given string ends with the given suffix.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [hasSuffix]
- returnType: bool
- signatures: [strings.HasSuffix STRING SUFFIX]
-aliases: [/functions/hassuffix,/functions/strings/hassuffix]
----
-
-```go-html-template
-{{ hasSuffix "Hugo" "go" }} → true
-```
diff --git a/docs/content/en/functions/strings/Repeat.md b/docs/content/en/functions/strings/Repeat.md
deleted file mode 100644
index b6027368e..000000000
--- a/docs/content/en/functions/strings/Repeat.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-title: strings.Repeat
-description: Returns a new string consisting of zero or more copies of another string.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: string
- signatures: [strings.Repeat COUNT INPUT]
-aliases: [/functions/strings.repeat]
----
-
-```go-html-template
-{{ strings.Repeat 3 "yo" }} → yoyoyo
-```
diff --git a/docs/content/en/functions/strings/Replace.md b/docs/content/en/functions/strings/Replace.md
deleted file mode 100644
index b449ea84d..000000000
--- a/docs/content/en/functions/strings/Replace.md
+++ /dev/null
@@ -1,23 +0,0 @@
----
-title: strings.Replace
-description: Returns a copy of INPUT, replacing all occurrences of OLD with NEW.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [replace]
- returnType: string
- signatures: ['strings.Replace INPUT OLD NEW [LIMIT]']
-aliases: [/functions/replace]
----
-
-```go-html-template
-{{ $s := "Batman and Robin" }}
-{{ replace $s "Robin" "Catwoman" }} → Batman and Catwoman
-```
-
-Limit the number of replacements using the `LIMIT` argument:
-
-```go-html-template
-{{ replace "aabbaabb" "a" "z" 2 }} → zzbbaabb
-```
diff --git a/docs/content/en/functions/strings/ReplaceRE.md b/docs/content/en/functions/strings/ReplaceRE.md
deleted file mode 100644
index dba4bd15a..000000000
--- a/docs/content/en/functions/strings/ReplaceRE.md
+++ /dev/null
@@ -1,38 +0,0 @@
----
-title: strings.ReplaceRE
-description: Returns a copy of INPUT, replacing all occurrences of a regular expression with a replacement pattern.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [replaceRE]
- returnType: string
- signatures: ['strings.ReplaceRE PATTERN REPLACEMENT INPUT [LIMIT]']
-aliases: [/functions/replacere]
----
-
-{{% include "/_common/functions/regular-expressions.md" %}}
-
-```go-html-template
-{{ $s := "a-b--c---d" }}
-{{ replaceRE `(-{2,})` "-" $s }} → a-b-c-d
-```
-
-Limit the number of replacements using the LIMIT argument:
-
-```go-html-template
-{{ $s := "a-b--c---d" }}
-{{ replaceRE `(-{2,})` "-" $s 1 }} → a-b-c---d
-```
-
-Use `$1`, `$2`, etc. within the replacement string to insert the content of each capturing group within the regular expression:
-
-```go-html-template
-{{ $s := "http://gohugo.io/docs" }}
-{{ replaceRE "^https?://([^/]+).*" "$1" $s }} → gohugo.io
-```
-
-> [!note]
-> You can write and test your regular expression using [regex101.com]. Be sure to select the Go flavor before you begin.
-
-[regex101.com]: https://regex101.com/
diff --git a/docs/content/en/functions/strings/RuneCount.md b/docs/content/en/functions/strings/RuneCount.md
deleted file mode 100644
index 8c0e24342..000000000
--- a/docs/content/en/functions/strings/RuneCount.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-title: strings.RuneCount
-description: Returns the number of runes in the given string.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: int
- signatures: [strings.RuneCount INPUT]
-aliases: [/functions/strings.runecount]
----
-
-In contrast with the [`strings.CountRunes`] function, which excludes whitespace, `strings.RuneCount` counts every rune in a string.
-
-```go-html-template
-{{ "Hello, 世界" | strings.RuneCount }} → 9
-```
-
-[`strings.CountRunes`]: /functions/strings/countrunes/
diff --git a/docs/content/en/functions/strings/SliceString.md b/docs/content/en/functions/strings/SliceString.md
deleted file mode 100644
index 69e4f6f33..000000000
--- a/docs/content/en/functions/strings/SliceString.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-title: strings.SliceString
-description: Returns a substring of the given string, beginning with the start position and ending before the end position.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [slicestr]
- returnType: string
- signatures: ['strings.SliceString STRING [START] [END]']
-aliases: [/functions/slicestr]
----
-
-The START and END positions are zero-based, where `0` represents the first character of the string. If START is not specified, the substring will begin at position `0`. If END is not specified, the substring will end after the last character.
-
-```go-html-template
-{{ slicestr "BatMan" }} → BatMan
-{{ slicestr "BatMan" 3 }} → Man
-{{ slicestr "BatMan" 0 3 }} → Bat
-```
-
-The START and END arguments represent the endpoints of a half-open [interval](g), a concept that may be difficult to grasp when first encountered. You may find that the [`strings.Substr`] function is easier to understand.
-
-[`strings.Substr`]: /functions/strings/substr/
diff --git a/docs/content/en/functions/strings/Split.md b/docs/content/en/functions/strings/Split.md
deleted file mode 100644
index bcab1b4d7..000000000
--- a/docs/content/en/functions/strings/Split.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-title: strings.Split
-description: Returns a slice of strings by splitting the given string by a delimiter.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [split]
- returnType: '[]string'
- signatures: [strings.Split STRING DELIM]
-aliases: [/functions/split]
----
-
-Examples:
-
-```go-html-template
-{{ split "tag1,tag2,tag3" "," }} → ["tag1", "tag2", "tag3"]
-{{ split "abc" "" }} → ["a", "b", "c"]
-```
-
-> [!note]
-> The `strings.Split` function essentially does the opposite of the [`collections.Delimit`] function. While `split` creates a slice from a string, `delimit` creates a string from a slice.
-
-[`collections.Delimit`]: /functions/collections/delimit/
diff --git a/docs/content/en/functions/strings/Substr.md b/docs/content/en/functions/strings/Substr.md
deleted file mode 100644
index a4c779f7d..000000000
--- a/docs/content/en/functions/strings/Substr.md
+++ /dev/null
@@ -1,36 +0,0 @@
----
-title: strings.Substr
-description: Returns a substring of the given string, beginning with the start position and ending after the given length.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [substr]
- returnType: string
- signatures: ['strings.Substr STRING [START] [LENGTH]']
-aliases: [/functions/substr]
----
-
-The start position is zero-based, where `0` represents the first character of the string. If START is not specified, the substring will begin at position `0`. Specify a negative START position to extract characters from the end of the string.
-
-If LENGTH is not specified, the substring will include all characters from the START position to the end of the string. If negative, that number of characters will be omitted from the end of string.
-
-```go-html-template
-{{ substr "abcdef" 0 }} → abcdef
-{{ substr "abcdef" 1 }} → bcdef
-
-{{ substr "abcdef" 0 1 }} → a
-{{ substr "abcdef" 1 1 }} → b
-
-{{ substr "abcdef" 0 -1 }} → abcde
-{{ substr "abcdef" 1 -1 }} → bcde
-
-{{ substr "abcdef" -1 }} → f
-{{ substr "abcdef" -2 }} → ef
-
-{{ substr "abcdef" -1 1 }} → f
-{{ substr "abcdef" -2 1 }} → e
-
-{{ substr "abcdef" -3 -1 }} → de
-{{ substr "abcdef" -3 -2 }} → d
-```
diff --git a/docs/content/en/functions/strings/Title.md b/docs/content/en/functions/strings/Title.md
deleted file mode 100644
index d1b4aca01..000000000
--- a/docs/content/en/functions/strings/Title.md
+++ /dev/null
@@ -1,29 +0,0 @@
----
-title: strings.Title
-description: Returns the given string, converting it to title case.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [title]
- returnType: string
- signatures: [strings.Title STRING]
-aliases: [/functions/title]
----
-
-```go-html-template
-{{ title "table of contents (TOC)" }} → Table of Contents (TOC)
-```
-
-By default, Hugo follows the capitalization rules published in the [Associated Press Stylebook]. Change your [site configuration] if you would prefer to:
-
-- Follow the capitalization rules published in the [Chicago Manual of Style]
-- Capitalize the first letter of every word
-- Capitalize the first letter of the first word
-- Disable the effects of the `title` function
-
-The last option is useful if your theme uses the `title` function, and you would prefer to manually capitalize strings as needed.
-
-[Associated Press Stylebook]: https://www.apstylebook.com/
-[Chicago Manual of Style]: https://www.chicagomanualofstyle.org/home.html
-[site configuration]: /configuration/all/#title-case-style
diff --git a/docs/content/en/functions/strings/ToLower.md b/docs/content/en/functions/strings/ToLower.md
deleted file mode 100644
index c329b5e55..000000000
--- a/docs/content/en/functions/strings/ToLower.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-title: strings.ToLower
-description: Returns the given string, converting all characters to lowercase.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [lower]
- returnType: string
- signatures: [strings.ToLower INPUT]
-aliases: [/functions/lower]
----
-
-```go-html-template
-{{ lower "BatMan" }} → batman
-```
diff --git a/docs/content/en/functions/strings/ToUpper.md b/docs/content/en/functions/strings/ToUpper.md
deleted file mode 100644
index acccb4124..000000000
--- a/docs/content/en/functions/strings/ToUpper.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-title: strings.ToUpper
-description: Returns the given string, converting all characters to uppercase.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [upper]
- returnType: string
- signatures: [strings.ToUpper INPUT]
-aliases: [/functions/upper]
----
-
-```go-html-template
-{{ upper "BatMan" }} → BATMAN
-```
diff --git a/docs/content/en/functions/strings/Trim.md b/docs/content/en/functions/strings/Trim.md
deleted file mode 100644
index 1afa0627f..000000000
--- a/docs/content/en/functions/strings/Trim.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-title: strings.Trim
-description: Returns the given string, removing leading and trailing characters specified in the cutset.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [trim]
- returnType: string
- signatures: [strings.Trim INPUT CUTSET]
-aliases: [/functions/trim]
----
-
-```go-html-template
-{{ trim "++foo--" "+-" }} → foo
-```
diff --git a/docs/content/en/functions/strings/TrimLeft.md b/docs/content/en/functions/strings/TrimLeft.md
deleted file mode 100644
index c5d6ba60f..000000000
--- a/docs/content/en/functions/strings/TrimLeft.md
+++ /dev/null
@@ -1,23 +0,0 @@
----
-title: strings.TrimLeft
-description: Returns the given string, removing leading characters specified in the cutset.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: string
- signatures: [strings.TrimLeft CUTSET STRING]
-aliases: [/functions/strings.trimleft]
----
-
-```go-html-template
-{{ strings.TrimLeft "a" "abba" }} → bba
-```
-
-The `strings.TrimLeft` function converts the arguments to strings if possible:
-
-```go-html-template
-{{ strings.TrimLeft 21 12345 }} → 345 (string)
-{{ strings.TrimLeft "rt" true }} → ue
-```
diff --git a/docs/content/en/functions/strings/TrimPrefix.md b/docs/content/en/functions/strings/TrimPrefix.md
deleted file mode 100644
index b897d8777..000000000
--- a/docs/content/en/functions/strings/TrimPrefix.md
+++ /dev/null
@@ -1,18 +0,0 @@
----
-title: strings.TrimPrefix
-description: Returns the given string, removing the prefix from the beginning of the string.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: string
- signatures: [strings.TrimPrefix PREFIX STRING]
-aliases: [/functions/strings.trimprefix]
----
-
-```go-html-template
-{{ strings.TrimPrefix "a" "aabbaa" }} → abbaa
-{{ strings.TrimPrefix "aa" "aabbaa" }} → bbaa
-{{ strings.TrimPrefix "aaa" "aabbaa" }} → aabbaa
-```
diff --git a/docs/content/en/functions/strings/TrimRight.md b/docs/content/en/functions/strings/TrimRight.md
deleted file mode 100644
index 05c2ed324..000000000
--- a/docs/content/en/functions/strings/TrimRight.md
+++ /dev/null
@@ -1,23 +0,0 @@
----
-title: strings.TrimRight
-description: Returns the given string, removing trailing characters specified in the cutset.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: string
- signatures: [strings.TrimRight CUTSET STRING]
-aliases: [/functions/strings.trimright]
----
-
-```go-html-template
-{{ strings.TrimRight "a" "abba" }} → abb
-```
-
-The `strings.TrimRight` function converts the arguments to strings if possible:
-
-```go-html-template
-{{ strings.TrimRight 54 12345 }} → 123 (string)
-{{ strings.TrimRight "eu" true }} → tr
-```
diff --git a/docs/content/en/functions/strings/TrimSpace.md b/docs/content/en/functions/strings/TrimSpace.md
deleted file mode 100644
index cb38b5ab1..000000000
--- a/docs/content/en/functions/strings/TrimSpace.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-title: strings.TrimSpace
-description: Returns the given string, removing leading and trailing whitespace as defined by Unicode.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: string
- signatures: [strings.TrimSpace INPUT]
----
-
-{{< new-in 0.136.3 />}}
-
-Whitespace characters include `\t`, `\n`, `\v`, `\f`, `\r`, and characters in the [Unicode Space Separator] category.
-
-[Unicode Space Separator]: https://www.compart.com/en/unicode/category/Zs
-
-```go-html-template
-{{ strings.TrimSpace "\n\r\t foo \n\r\t" }} → foo
-```
diff --git a/docs/content/en/functions/strings/TrimSuffix.md b/docs/content/en/functions/strings/TrimSuffix.md
deleted file mode 100644
index 802842105..000000000
--- a/docs/content/en/functions/strings/TrimSuffix.md
+++ /dev/null
@@ -1,18 +0,0 @@
----
-title: strings.TrimSuffix
-description: Returns the given string, removing the suffix from the end of the string.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: string
- signatures: [strings.TrimSuffix SUFFIX STRING]
-aliases: [/functions/strings.trimsuffix]
----
-
-```go-html-template
-{{ strings.TrimSuffix "a" "aabbaa" }} → aabba
-{{ strings.TrimSuffix "aa" "aabbaa" }} → aabb
-{{ strings.TrimSuffix "aaa" "aabbaa" }} → aabbaa
-```
diff --git a/docs/content/en/functions/strings/Truncate.md b/docs/content/en/functions/strings/Truncate.md
deleted file mode 100644
index c4198229e..000000000
--- a/docs/content/en/functions/strings/Truncate.md
+++ /dev/null
@@ -1,23 +0,0 @@
----
-title: strings.Truncate
-description: Returns the given string, truncating it to a maximum length without cutting words or leaving unclosed HTML tags.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [truncate]
- returnType: template.HTML
- signatures: ['strings.Truncate SIZE [ELLIPSIS] INPUT']
-aliases: [/functions/truncate]
----
-
-Since Go templates are HTML-aware, `truncate` will intelligently handle normal strings vs HTML strings:
-
-```go-html-template
-{{ "Keep my HTML" | safeHTML | truncate 10 }} → Keep my …
-```
-
-> [!note]
-> If you have a raw string that contains HTML tags you want to remain treated as HTML, you will need to convert the string to HTML using the [`safeHTML`]function before sending the value to `truncate`. Otherwise, the HTML tags will be escaped when passed through the `truncate` function.
-
-[`safeHTML`]: /functions/safe/html/
diff --git a/docs/content/en/functions/strings/_index.md b/docs/content/en/functions/strings/_index.md
deleted file mode 100644
index 28f26f170..000000000
--- a/docs/content/en/functions/strings/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: String functions
-linkTitle: strings
-description: Use these functions to work with strings.
-categories: []
-keywords: []
----
diff --git a/docs/content/en/functions/templates/Current.md b/docs/content/en/functions/templates/Current.md
deleted file mode 100644
index 805aeec05..000000000
--- a/docs/content/en/functions/templates/Current.md
+++ /dev/null
@@ -1,155 +0,0 @@
----
-title: templates.Current
-description: Returns information about the currently executing template.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: tpl.CurrentTemplateInfo
- signatures: [templates.Current]
----
-
-> [!note]
-> This function is experimental and subject to change.
-
-{{< new-in 0.146.0 />}}
-
-The `templates.Current` function provides introspection capabilities, allowing you to access details about the currently executing templates. This is useful for debugging complex template hierarchies and understanding the flow of execution during rendering.
-
-## Methods
-
-Ancestors
-: (`tpl.CurrentTemplateInfos`) Returns a slice containing information about each template in the current execution chain, starting from the parent of the current template and going up towards the initial template called. It excludes any base template applied via `define` and `block`. You can chain the `Reverse` method to this result to get the slice in chronological execution order.
-
-Base
-: (`tpl.CurrentTemplateInfoCommonOps`) Returns an object representing the base template that was applied to the current template, if any. This may be `nil`.
-
-Filename
-: (`string`) Returns the absolute path of the current template. This will be empty for embedded templates.
-
-Name
-: (`string`) Returns the name of the current template. This is usually the path relative to the layouts directory.
-
-Parent
-: (`tpl.CurrentTemplateInfo`) Returns an object representing the parent of the current template, if any. This may be `nil`.
-
-## Examples
-
-The examples below help visualize template execution and require a `debug` parameter set to `true` in your site configuration:
-
-{{< code-toggle file=hugo >}}
-[params]
-debug = true
-{{< /code-toggle >}}
-
-### Boundaries
-
-To visually mark where a template begins and ends execution:
-
-```go-html-template {file="layouts/_default/single.html"}
-{{ define "main" }}
- {{ if site.Params.debug }}
-
[entering {{ templates.Current.Filename }}]
- {{ end }}
-
-
{{ .Title }}
- {{ .Content }}
-
- {{ if site.Params.debug }}
-
[leaving {{ templates.Current.Filename }}]
- {{ end }}
-{{ end }}
-```
-
-### Call stack
-
-To display the chain of templates that led to the current one, create a partial template that iterates through its ancestors:
-
-```go-html-template {file="layouts/partials/template-call-stack.html" copy=true}
-{{ with templates.Current }}
-
- {{ range .Ancestors }}
- {{ .Filename }}
- {{ with .Base }}
- {{ .Filename }}
- {{ end }}
- {{ end }}
-
-{{ end }}
-```
-
-Then call the partial from any template:
-
-```go-html-template {file="layouts/partials/footer/copyright.html" copy=true}
-{{ if site.Params.debug }}
- {{ partial "template-call-stack.html" . }}
-{{ end }}
-```
-
-The rendered template stack would look something like this:
-
-```text
-/home/user/project/layouts/partials/footer/copyright.html
-/home/user/project/themes/foo/layouts/partials/footer.html
-/home/user/project/layouts/_default/single.html
-/home/user/project/themes/foo/layouts/_default/baseof.html
-```
-
-To reverse the order of the entries, chain the `Reverse` method to the `Ancestors` method:
-
-```go-html-template {file="layouts/partials/template-call-stack.html" copy=true}
-{{ with templates.Current }}
-
- {{ range .Ancestors.Reverse }}
- {{ with .Base }}
- {{ .Filename }}
- {{ end }}
- {{ .Filename }}
- {{ end }}
-
-{{ end }}
-```
-
-### VS Code
-
-To render links that, when clicked, will open the template in Microsoft Visual Studio Code, create a partial template with anchor elements that use the `vscode` URI scheme:
-
-```go-html-template {file="layouts/partials/template-open-in-vs-code.html" copy=true}
-{{ with templates.Current.Parent }}
-
-{{ end }}
-```
diff --git a/docs/content/en/functions/templates/Defer.md b/docs/content/en/functions/templates/Defer.md
deleted file mode 100644
index 6a9ca56ae..000000000
--- a/docs/content/en/functions/templates/Defer.md
+++ /dev/null
@@ -1,92 +0,0 @@
----
-title: templates.Defer
-description: Defer execution of a template until after all sites and output formats have been rendered.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: string
- signatures: [templates.Defer OPTIONS]
-aliases: [/functions/templates.defer]
----
-
-{{< new-in 0.128.0 />}}
-
-> [!note]
-> This feature is meant to be used in the main page layout files/templates, and has undefined behavior when used from shortcodes, partials or render hook templates. See [this issue](https://github.com/gohugoio/hugo/issues/13492#issuecomment-2734700391) for more info.
-
-In some rare use cases, you may need to defer the execution of a template until after all sites and output formats have been rendered. One such example could be [TailwindCSS](/functions/css/tailwindcss/) using the output of [hugo_stats.json](/configuration/build/) to determine which classes and other HTML identifiers are being used in the final output:
-
-```go-html-template
-{{ with (templates.Defer (dict "key" "global")) }}
- {{ $t := debug.Timer "tailwindcss" }}
- {{ with resources.Get "css/styles.css" }}
- {{ $opts := dict
- "inlineImports" true
- "optimize" hugo.IsProduction
- }}
- {{ with . | css.TailwindCSS $opts }}
- {{ if hugo.IsDevelopment }}
-
- {{ else }}
- {{ with . | minify | fingerprint }}
-
- {{ end }}
- {{ end }}
- {{ end }}
- {{ end }}
- {{ $t.Stop }}
-{{ end }}
-```
-
-> [!note]
-> This function only works in combination with the `with` keyword.
->
-> Variables defined on the outside are not visible on the inside and vice versa. To pass in data, use the `data` [option](#options).
-
-For the above to work well when running the server (or `hugo -w`), you want to have a configuration similar to this:
-
-{{< code-toggle file=hugo >}}
-[module]
-[[module.mounts]]
-source = "hugo_stats.json"
-target = "assets/notwatching/hugo_stats.json"
-disableWatch = true
-[build.buildStats]
-enable = true
-[[build.cachebusters]]
-source = "assets/notwatching/hugo_stats\\.json"
-target = "styles\\.css"
-[[build.cachebusters]]
-source = "(postcss|tailwind)\\.config\\.js"
-target = "css"
-{{< /code-toggle >}}
-
-## Options
-
-The `templates.Defer` function takes a single argument, a map with the following optional keys:
-
-key (`string`)
-: The key to use for the deferred template. This will, combined with a hash of the template content, be used as a cache key. If this is not set, Hugo will execute the deferred template on every render. This is not what you want for shared resources like CSS and JavaScript.
-
-data (`map`)
-: Optional map to pass as data to the deferred template. This will be available in the deferred template as `.` or `$`.
-
-```go-html-template
-Language Outside: {{ site.Language.Lang }}
-Page Outside: {{ .RelPermalink }}
-I18n Outside: {{ i18n "hello" }}
-{{ $data := (dict "page" . )}}
-{{ with (templates.Defer (dict "data" $data )) }}
- Language Inside: {{ site.Language.Lang }}
- Page Inside: {{ .page.RelPermalink }}
- I18n Inside: {{ i18n "hello" }}
-{{ end }}
-```
-
-The [output format](/configuration/output-formats/), [site](/methods/page/site/), and [language](/methods/site/language) will be the same, even if the execution is deferred. In the example above, this means that the `site.Language.Lang` and `.RelPermalink` will be the same on the inside and the outside of the deferred template.
diff --git a/docs/content/en/functions/templates/Exists.md b/docs/content/en/functions/templates/Exists.md
deleted file mode 100644
index 79fc561c8..000000000
--- a/docs/content/en/functions/templates/Exists.md
+++ /dev/null
@@ -1,27 +0,0 @@
----
-title: templates.Exists
-description: Reports whether a template file exists under the given path relative to the layouts directory.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: bool
- signatures: [templates.Exists PATH]
-aliases: [/functions/templates.exists]
----
-
-A template file is any file within the `layouts` directory of either the project or any of its theme components.
-
-Use the `templates.Exists` function with dynamic template paths:
-
-```go-html-template
-{{ $partialPath := printf "headers/%s.html" .Type }}
-{{ if templates.Exists ( printf "partials/%s" $partialPath ) }}
- {{ partial $partialPath . }}
-{{ else }}
- {{ partial "headers/default.html" . }}
-{{ end }}
-```
-
-In the example above, if a "headers" partial does not exist for the given content type, Hugo falls back to a default template.
diff --git a/docs/content/en/functions/templates/_index.md b/docs/content/en/functions/templates/_index.md
deleted file mode 100644
index a385604ea..000000000
--- a/docs/content/en/functions/templates/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Template functions
-linkTitle: templates
-description: Use these functions to query the template system.
-categories: []
-keywords: []
----
diff --git a/docs/content/en/functions/time/AsTime.md b/docs/content/en/functions/time/AsTime.md
deleted file mode 100644
index 760329a13..000000000
--- a/docs/content/en/functions/time/AsTime.md
+++ /dev/null
@@ -1,48 +0,0 @@
----
-title: time.AsTime
-description: Returns the given string representation of a date/time value as a time.Time value.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [time]
- returnType: time.Time
- signatures: ['time.AsTime INPUT [TIMEZONE]']
-aliases: [/functions/time]
----
-
-## Overview
-
-Hugo provides [functions] and [methods] to format, localize, parse, compare, and manipulate date/time values. Before you can do any of these with string representations of date/time values, you must first convert them to [`time.Time`] values using the `time.AsTime` function.
-
-```go-html-template
-{{ $t := "2023-10-15T13:18:50-07:00" }}
-{{ time.AsTime $t }} → 2023-10-15 13:18:50 -0700 PDT (time.Time)
-```
-
-## Parsable strings
-
-As shown above, the first argument must be a parsable string representation of a date/time value. For example:
-
-{{% include "/_common/parsable-date-time-strings.md" %}}
-
-To override the default time zone, set the [`timeZone`] in your site configuration or provide a second argument to the `time.AsTime` function. For example:
-
-```go-html-template
-{{ time.AsTime "15 Oct 2023" "America/Los_Angeles" }}
-```
-
-The list of valid time zones may be system dependent, but should include `UTC`, `Local`, or any location in the [IANA Time Zone database].
-
-The order of precedence for determining the time zone is:
-
-1. The time zone offset in the date/time string
-1. The time zone provided as the second argument to the `time.AsTime` function
-1. The time zone specified in your site configuration
-1. The `Etc/UTC` time zone
-
-[IANA Time Zone database]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
-[`time.Time`]: https://pkg.go.dev/time#Time
-[`timeZone`]: /configuration/all/#timezone
-[functions]: /functions/time/
-[methods]: /methods/time/
diff --git a/docs/content/en/functions/time/Duration.md b/docs/content/en/functions/time/Duration.md
deleted file mode 100644
index bd6adfbfa..000000000
--- a/docs/content/en/functions/time/Duration.md
+++ /dev/null
@@ -1,41 +0,0 @@
----
-title: time.Duration
-description: Returns a time.Duration value using the given time unit and number.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [duration]
- returnType: time.Duration
- signatures: [time.Duration TIME_UNIT NUMBER]
-aliases: [/functions/duration]
----
-
-The `time.Duration` function returns a [`time.Duration`] value that you can use with any of the `Duration` [methods].
-
-This template:
-
-```go-html-template
-{{ $duration := time.Duration "hour" 24 }}
-{{ printf "There are %.0f seconds in one day." $duration.Seconds }}
-```
-
-Is rendered to:
-
-```text
-There are 86400 seconds in one day.
-```
-
-The time unit must be one of the following:
-
-Duration|Valid time units
-:--|:--
-hours|`hour`, `h`
-minutes|`minute`, `m`
-seconds|`second`, `s`
-milliseconds|`millisecond`, `ms`
-microseconds|`microsecond`, `us`, `µs`
-nanoseconds|`nanosecond`, `ns`
-
-[`time.Duration`]: https://pkg.go.dev/time#Duration
-[methods]: /methods/duration/
diff --git a/docs/content/en/functions/time/Format.md b/docs/content/en/functions/time/Format.md
deleted file mode 100644
index f1b0d6d83..000000000
--- a/docs/content/en/functions/time/Format.md
+++ /dev/null
@@ -1,80 +0,0 @@
----
-title: time.Format
-description: Returns the given date/time as a formatted and localized string.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [dateFormat]
- returnType: string
- signatures: [time.Format LAYOUT INPUT]
-aliases: [/functions/dateformat]
----
-
-Use the `time.Format` function with `time.Time` values:
-
-```go-html-template
-{{ $t := time.AsTime "2023-10-15T13:18:50-07:00" }}
-{{ time.Format "2 Jan 2006" $t }} → 15 Oct 2023
-```
-
-Or use `time.Format` with a parsable string representation of a date/time value:
-
-```go-html-template
-{{ $t := "15 Oct 2023" }}
-{{ time.Format "January 2, 2006" $t }} → October 15, 2023
-```
-
-Examples of parsable string representations:
-
-{{% include "/_common/parsable-date-time-strings.md" %}}
-
-To override the default time zone, set the [`timeZone`] in your site 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 site configuration
-1. The `Etc/UTC` time zone
-
-[`timeZone`]: /configuration/all/#timezone
-
-## Layout string
-
-{{% include "/_common/time-layout-string.md" %}}
-
-## Localization
-
-Use the `time.Format` function to localize `time.Time` values for the current language and region.
-
-{{% include "/_common/functions/locales.md" %}}
-
-Use the layout string as described above, or one of the tokens below. For example:
-
-```go-html-template
-{{ .Date | time.Format ":date_medium" }} → Jan 27, 2023
-```
-
-Localized to en-US:
-
-Token|Result
-:--|:--
-`:date_full`|`Friday, January 27, 2023`
-`:date_long`|`January 27, 2023`
-`:date_medium`|`Jan 27, 2023`
-`:date_short`|`1/27/23`
-`:time_full`|`11:44:58 pm Pacific Standard Time`
-`:time_long`|`11:44:58 pm PST`
-`:time_medium`|`11:44:58 pm`
-`:time_short`|`11:44 pm`
-
-Localized to de-DE:
-
-Token|Result
-:--|:--
-`:date_full`|`Freitag, 27. Januar 2023`
-`:date_long`|`27. Januar 2023`
-`:date_medium`|`27.01.2023`
-`:date_short`|`27.01.23`
-`:time_full`|`23:44:58 Nordamerikanische Westküsten-Normalzeit`
-`:time_long`|`23:44:58 PST`
-`:time_medium`|`23:44:58`
-`:time_short`|`23:44`
diff --git a/docs/content/en/functions/time/In.md b/docs/content/en/functions/time/In.md
deleted file mode 100644
index 821eb99b7..000000000
--- a/docs/content/en/functions/time/In.md
+++ /dev/null
@@ -1,30 +0,0 @@
----
-title: time.In
-description: Returns the given date/time as represented in the specified IANA time zone.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: time.Time
- signatures: [time.In TIMEZONE INPUT]
----
-
-{{< new-in 0.146.0 />}}
-
-The `time.In` function returns the given date/time as represented in the specified [IANA](g) time zone.
-
-- If the time zone is an empty string or `UTC`, the time is returned in [UTC](g).
-- If the time zone is `Local`, the time is returned in the system's local time zone.
-- Otherwise, the time zone must be a valid IANA [time zone name].
-
-[time zone name]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
-
-```go-html-template
-{{ $layout := "2006-01-02T15:04:05-07:00" }}
-{{ $t := time.AsTime "2025-03-31T14:45:00-00:00" }}
-
-{{ $t | time.In "America/Denver" | time.Format $layout }} → 2025-03-31T08:45:00-06:00
-{{ $t | time.In "Australia/Adelaide" | time.Format $layout }} → 2025-04-01T01:15:00+10:30
-{{ $t | time.In "Europe/Oslo" | time.Format $layout }} → 2025-03-31T16:45:00+02:00
-```
diff --git a/docs/content/en/functions/time/Now.md b/docs/content/en/functions/time/Now.md
deleted file mode 100644
index 9b6fa4692..000000000
--- a/docs/content/en/functions/time/Now.md
+++ /dev/null
@@ -1,42 +0,0 @@
----
-title: time.Now
-description: Returns the current local time.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [now]
- returnType: time.Time
- signatures: [time.Now]
-aliases: [/functions/now]
----
-
-For example, when building a site on October 15, 2023 in the America/Los_Angeles time zone:
-
-```go-html-template
-{{ time.Now }}
-```
-
-This produces a `time.Time` value, with a string representation such as:
-
-```text
-2023-10-15 12:59:28.337140706 -0700 PDT m=+0.041752605
-```
-
-To format and [localize](g) the value, pass it through the [`time.Format`] function:
-
-```go-html-template
-{{ time.Now | time.Format "Jan 2006" }} → Oct 2023
-```
-
-The `time.Now` function returns a `time.Time` value, so you can chain any of the [time methods] to the resulting value. For example:
-
-```go-html-template
-{{ time.Now.Year }} → 2023 (int)
-{{ time.Now.Weekday.String }} → Sunday
-{{ time.Now.Month.String }} → October
-{{ time.Now.Unix }} → 1697400955 (int64)
-```
-
-[`time.Format`]: /functions/time/format/
-[time methods]: /methods/time/
diff --git a/docs/content/en/functions/time/ParseDuration.md b/docs/content/en/functions/time/ParseDuration.md
deleted file mode 100644
index af5d73ad5..000000000
--- a/docs/content/en/functions/time/ParseDuration.md
+++ /dev/null
@@ -1,32 +0,0 @@
----
-title: time.ParseDuration
-description: Returns a time.Duration value by parsing the given duration string.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: time.Duration
- signatures: [time.ParseDuration DURATION]
-aliases: [/functions/time.parseduration]
----
-
-The `time.ParseDuration` function returns a time.Duration value that you can use with any of the `Duration` [methods].
-
-A duration string is a possibly signed 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`.
-
-This template:
-
-```go-html-template
-{{ $duration := time.ParseDuration "24h" }}
-{{ printf "There are %.0f seconds in one day." $duration.Seconds }}
-```
-
-Is rendered to:
-
-```text
-There are 86400 seconds in one day.
-```
-
-[`time.Duration`]: https://pkg.go.dev/time#Duration
-[methods]: /methods/duration/
diff --git a/docs/content/en/functions/time/_index.md b/docs/content/en/functions/time/_index.md
deleted file mode 100644
index 9c2ff2161..000000000
--- a/docs/content/en/functions/time/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Time functions
-linkTitle: time
-description: Use these functions to work with time values.
-categories: []
-keywords: []
----
diff --git a/docs/content/en/functions/transform/CanHighlight.md b/docs/content/en/functions/transform/CanHighlight.md
deleted file mode 100644
index c00445605..000000000
--- a/docs/content/en/functions/transform/CanHighlight.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-title: transform.CanHighlight
-description: Reports whether the given code language is supported by the Chroma highlighter.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: bool
- signatures: [transform.CanHighlight LANGUAGE]
----
-
-```go-html-template
-{{ transform.CanHighlight "go" }} → true
-{{ transform.CanHighlight "klingon" }} → false
-```
diff --git a/docs/content/en/functions/transform/Emojify.md b/docs/content/en/functions/transform/Emojify.md
deleted file mode 100644
index 31c5dce70..000000000
--- a/docs/content/en/functions/transform/Emojify.md
+++ /dev/null
@@ -1,27 +0,0 @@
----
-title: transform.Emojify
-description: Runs a string through the Emoji emoticons processor.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [emojify]
- returnType: template.HTML
- signatures: [transform.Emojify INPUT]
-aliases: [/functions/emojify]
----
-
-`emojify` runs a passed string through the Emoji emoticons processor.
-
-See the list of [emoji shortcodes] for available emoticons.
-
-The `emojify` function can be called in your templates but not directly in your content files by default. For emojis in content files, set [`enableEmoji`] to `true` in your site's configuration. Then you can write emoji shorthand directly into your content files;
-
-```text
-I :heart: Hugo!
-```
-
-I :heart: Hugo!
-
-[`enableEmoji`]: /configuration/all/#enableemoji
-[emoji shortcodes]: /quick-reference/emojis/
diff --git a/docs/content/en/functions/transform/HTMLEscape.md b/docs/content/en/functions/transform/HTMLEscape.md
deleted file mode 100644
index 069fd92f2..000000000
--- a/docs/content/en/functions/transform/HTMLEscape.md
+++ /dev/null
@@ -1,29 +0,0 @@
----
-title: transform.HTMLEscape
-description: Returns the given string, escaping special characters by replacing them with HTML entities.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [htmlEscape]
- returnType: string
- signatures: [transform.HTMLEscape INPUT]
-aliases: [/functions/htmlescape]
----
-
-The `transform.HTMLEscape` function escapes five special characters by replacing them with [HTML entities]:
-
-- `&` → `&`
-- `<` → `<`
-- `>` → `>`
-- `'` → `'`
-- `"` → `"`
-
-For example:
-
-```go-html-template
-{{ htmlEscape "Lilo & Stitch" }} → Lilo & Stitch
-{{ htmlEscape "7 > 6" }} → 7 > 6
-```
-
-[html entities]: https://developer.mozilla.org/en-US/docs/Glossary/Entity
diff --git a/docs/content/en/functions/transform/HTMLUnescape.md b/docs/content/en/functions/transform/HTMLUnescape.md
deleted file mode 100644
index 563e63cf6..000000000
--- a/docs/content/en/functions/transform/HTMLUnescape.md
+++ /dev/null
@@ -1,29 +0,0 @@
----
-title: transform.HTMLUnescape
-description: Returns the given string, replacing each HTML entity with its corresponding character.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [htmlUnescape]
- returnType: string
- signatures: [transform.HTMLUnescape INPUT]
-aliases: [/functions/htmlunescape]
----
-
-The `transform.HTMLUnescape` function replaces [HTML entities] with their corresponding characters.
-
-```go-html-template
-{{ htmlUnescape "Lilo & Stitch" }} → Lilo & Stitch
-{{ htmlUnescape "7 > 6" }} → 7 > 6
-```
-
-In most contexts Go's [html/template] package will escape special characters. To bypass this behavior, pass the unescaped string through the [`safeHTML`] function.
-
-```go-html-template
-{{ htmlUnescape "Lilo & Stitch" | safeHTML }}
-```
-
-[`safehtml`]: /functions/safe/html/
-[html entities]: https://developer.mozilla.org/en-us/docs/glossary/entity
-[html/template]: https://pkg.go.dev/html/template
diff --git a/docs/content/en/functions/transform/Highlight.md b/docs/content/en/functions/transform/Highlight.md
deleted file mode 100644
index 5633f2256..000000000
--- a/docs/content/en/functions/transform/Highlight.md
+++ /dev/null
@@ -1,53 +0,0 @@
----
-title: transform.Highlight
-description: Renders code with a syntax highlighter.
-categories: []
-keywords: [highlight]
-params:
- functions_and_methods:
- aliases: [highlight]
- returnType: template.HTML
- signatures: ['transform.Highlight CODE LANG [OPTIONS]']
-aliases: [/functions/highlight]
----
-
-The `highlight` function uses the [Chroma] syntax highlighter, supporting over 200 languages with more than 40 [highlighting styles].
-
-[chroma]: https://github.com/alecthomas/chroma
-[highlighting styles]: /quick-reference/syntax-highlighting-styles/
-
-## Arguments
-
-The `transform.Highlight` shortcode takes three arguments.
-
-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
-: (`map or string`) A map or comma-separated key-value pairs wrapped in quotation marks. Set default values for each option in your [site configuration]. The key names are case-insensitive.
-
-[site configuration]: /configuration/markup#highlight
-[supported languages]: /content-management/syntax-highlighting#languages
-
-## Examples
-
-```go-html-template
-{{ $input := `fmt.Println("Hello World!")` }}
-{{ transform.Highlight $input "go" }}
-
-{{ $input := `console.log('Hello World!');` }}
-{{ $lang := "js" }}
-{{ transform.Highlight $input $lang "lineNos=table, style=api" }}
-
-{{ $input := `echo "Hello World!"` }}
-{{ $lang := "bash" }}
-{{ $opts := dict "lineNos" "table" "style" "dracula" }}
-{{ transform.Highlight $input $lang $opts }}
-```
-
-## Options
-
-{{% include "_common/syntax-highlighting-options.md" %}}
diff --git a/docs/content/en/functions/transform/HighlightCodeBlock.md b/docs/content/en/functions/transform/HighlightCodeBlock.md
deleted file mode 100644
index bbebf9459..000000000
--- a/docs/content/en/functions/transform/HighlightCodeBlock.md
+++ /dev/null
@@ -1,36 +0,0 @@
----
-title: transform.HighlightCodeBlock
-description: Highlights code received in context within a code block render hook.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: highlight.HighlightResult
- signatures: ['transform.HighlightCodeBlock CONTEXT [OPTIONS]']
----
-
-This function is only useful within a code block render hook.
-
-Given the context passed into a code block render hook, `transform.HighlightCodeBlock` returns a `HighlightResult` object with two methods.
-
-.Wrapped
-: (`template.HTML`) Returns highlighted code wrapped in `
`, `
`, and `` elements. This is identical to the value returned by the transform.Highlight function.
-
-.Inner
-: (`template.HTML`) Returns highlighted code without any wrapping elements, allowing you to create your own wrapper.
-
-```go-html-template
-{{ $result := transform.HighlightCodeBlock . }}
-{{ $result.Wrapped }}
-```
-
-To override the default [highlighting options]:
-
-```go-html-template
-{{ $opts := merge .Options (dict "linenos" true) }}
-{{ $result := transform.HighlightCodeBlock . $opts }}
-{{ $result.Wrapped }}
-```
-
-[highlighting options]: /functions/transform/highlight/#options
diff --git a/docs/content/en/functions/transform/Markdownify.md b/docs/content/en/functions/transform/Markdownify.md
deleted file mode 100644
index c22de1efe..000000000
--- a/docs/content/en/functions/transform/Markdownify.md
+++ /dev/null
@@ -1,27 +0,0 @@
----
-title: transform.Markdownify
-description: Renders Markdown to HTML.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [markdownify]
- returnType: template.HTML
- signatures: [transform.Markdownify INPUT]
-aliases: [/functions/markdownify]
----
-
-```go-html-template
-
{{ .Title | markdownify }}
-```
-
-If the resulting HTML is a single paragraph, Hugo removes the wrapping `p` tags to produce inline HTML as required per the example above.
-
-To keep the wrapping `p` tags for a single paragraph, use the [`RenderString`] method on the `Page` object, setting the `display` option to `block`.
-
-> [!note]
-> Although the `markdownify` function honors [Markdown render hooks] when rendering Markdown to HTML, use the `RenderString` method instead of `markdownify` if a render hook accesses `.Page` context. See issue [#9692] for details.
-
-[#9692]: https://github.com/gohugoio/hugo/issues/9692
-[`RenderString`]: /methods/page/renderstring/
-[Markdown render hooks]: /render-hooks/
diff --git a/docs/content/en/functions/transform/Plainify.md b/docs/content/en/functions/transform/Plainify.md
deleted file mode 100644
index 780cf461a..000000000
--- a/docs/content/en/functions/transform/Plainify.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-title: transform.Plainify
-description: Returns a string with all HTML tags removed.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [plainify]
- returnType: template.HTML
- signatures: [transform.Plainify INPUT]
-aliases: [/functions/plainify]
----
-
-```go-html-template
-{{ "BatMan" | plainify }} → BatMan
-```
diff --git a/docs/content/en/functions/transform/PortableText.md b/docs/content/en/functions/transform/PortableText.md
deleted file mode 100644
index 7baba99d4..000000000
--- a/docs/content/en/functions/transform/PortableText.md
+++ /dev/null
@@ -1,213 +0,0 @@
----
-title: transform.PortableText
-description: Converts Portable Text to Markdown.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: string
- signatures: [transform.PortableText MAP]
----
-
-{{< new-in "0.145.0" />}}
-
-[Portable Text](https://www.portabletext.org/) is a JSON structure that represent rich text content in the [Sanity](https://www.sanity.io/) CMS. In Hugo, this function is typically used in a [Content Adapter](https://gohugo.io/content-management/content-adapters/) that creates pages from Sanity data.
-
-## Types supported
-
-- `block` and `span`
-- `image`. Note that the image handling is currently very simple; we link to the `asset.url` using `asset.altText` as the image alt text and `asset.title` as the title. For more fine grained control you may want to process the images in a [image render hook](/render-hooks/images/).
-- `code` (see the [code-input](https://www.sanity.io/plugins/code-input) plugin). Code will be rendered as [fenced code blocks](/contribute/documentation/#fenced-code-blocks) with any file name provided passed on as a markdown attribute.
-
-> [!note]
-> Since the Portable Text gets converted to Markdown before it gets passed to Hugo, rendering of links, headings, images and code blocks can be controlled with [Render Hooks](https://gohugo.io/render-hooks/).
-
-## Example
-
-### Content Adapter
-
-```go-html-template {file="content/_content.gotmpl" copy=true}
-{{ $projectID := "mysanityprojectid" }}
-{{ $useCached := true }}
-{{ $api := "api" }}
-{{ if $useCached }}
- {{/* See https://www.sanity.io/docs/api-cdn */}}
- {{ $api = "apicdn" }}
-{{ end }}
-{{ $url := printf "https://%s.%s.sanity.io/v2021-06-07/data/query/production" $projectID $api }}
-
-{{/* prettier-ignore-start */ -}}
-{{ $q := `*[_type == 'post']{
- title, publishedAt, summary, slug, body[]{
- ...,
- _type == "image" => {
- ...,
- asset->{
- _id,
- path,
- url,
- altText,
- title,
- description,
- metadata {
- dimensions {
- aspectRatio,
- width,
- height
- }
- }
- }
- }
- },
- }`
-}}
-{{/* prettier-ignore-end */ -}}
-{{ $body := dict "query" $q | jsonify }}
-{{ $opts := dict "method" "post" "body" $body }}
-{{ $r := resources.GetRemote $url $opts }}
-{{ $m := $r | transform.Unmarshal }}
-{{ $result := $m.result }}
-{{ range $result }}
- {{ if not .slug }}
- {{ continue }}
- {{ end }}
- {{ $markdown := transform.PortableText .body }}
- {{ $content := dict
- "mediaType" "text/markdown"
- "value" $markdown
- }}
- {{ $params := dict
- "portabletext" (.body | jsonify (dict "indent" " "))
- }}
- {{ $page := dict
- "content" $content
- "kind" "page"
- "path" .slug.current
- "title" .title
- "date" (.publishedAt | time )
- "summary" .summary
- "params" $params
- }}
- {{ $.AddPage $page }}
-{{ end }}
-```
-
-### Sanity setup
-
-Below outlines a suitable Sanity studio setup for the above example.
-
-```ts {file="sanity.config.ts" copy=true}
-import {defineConfig} from 'sanity'
-import {structureTool} from 'sanity/structure'
-import {visionTool} from '@sanity/vision'
-import {schemaTypes} from './schemaTypes'
-import {media} from 'sanity-plugin-media'
-import {codeInput} from '@sanity/code-input'
-
-export default defineConfig({
- name: 'default',
- title: 'my-sanity-project',
-
- projectId: 'mysanityprojectid',
- dataset: 'production',
-
- plugins: [structureTool(), visionTool(), media(),codeInput()],
-
- schema: {
- types: schemaTypes,
- },
-})
-```
-
-Type/schema definition:
-
-```ts {file="schemaTypes/postType.ts" copy=true}
-import {defineField, defineType} from 'sanity'
-
-export const postType = defineType({
- name: 'post',
- title: 'Post',
- type: 'document',
- fields: [
- defineField({
- name: 'title',
- type: 'string',
- validation: (rule) => rule.required(),
- }),
- defineField({
- name: 'summary',
- type: 'string',
- validation: (rule) => rule.required(),
- }),
- defineField({
- name: 'slug',
- type: 'slug',
- options: {source: 'title'},
- validation: (rule) => rule.required(),
- }),
- defineField({
- name: 'publishedAt',
- type: 'datetime',
- initialValue: () => new Date().toISOString(),
- validation: (rule) => rule.required(),
- }),
- defineField({
- name: 'body',
- type: 'array',
- of: [
- {
- type: 'block',
- },
- {
- type: 'image'
- },
- {
- type: 'code',
- options: {
- language: 'css',
- languageAlternatives: [
- {title: 'HTML', value: 'html'},
- {title: 'CSS', value: 'css'},
- ],
- withFilename: true,
- },
- },
- ],
- }),
- ],
-})
-```
-
-Note that the above requires some additional plugins to be installed:
-
-```bash
-npm i sanity-plugin-media @sanity/code-input
-```
-
-```ts {file="schemaTypes/index.ts" copy=true}
-import {postType} from './postType'
-
-export const schemaTypes = [postType]
-```
-
-## Server setup
-
-Unfortunately, Sanity's API does not support [RFC 7234](https://tools.ietf.org/html/rfc7234) and their output changes even if the data has not. A recommended setup is therefore to use their cached `apicdn` endpoint (see above) and then set up a reasonable polling and file cache strategy in your Hugo configuration, e.g:
-
-{{< code-toggle file=hugo >}}
-[HTTPCache]
- [[HTTPCache.polls]]
- disable = false
- low = '30s'
- high = '3m'
- [HTTPCache.polls.for]
- includes = ['https://*.*.sanity.io/**']
-
-[caches.getresource]
- dir = ':cacheDir/:project'
- maxAge = "5m"
-{{< /code-toggle >}}
-
-The polling above will be used when running the server/watch mode and rebuild when you push new content in Sanity.
-
-See [Caching in resources.GetRemote](/functions/resources/getremote/#caching) for more fine grained control.
diff --git a/docs/content/en/functions/transform/Remarshal.md b/docs/content/en/functions/transform/Remarshal.md
deleted file mode 100644
index ecf7fc905..000000000
--- a/docs/content/en/functions/transform/Remarshal.md
+++ /dev/null
@@ -1,89 +0,0 @@
----
-title: transform.Remarshal
-description: Marshals a string of serialized data, or a map, into a string of serialized data in the specified format.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: string
- signatures: [transform.Remarshal FORMAT INPUT]
-aliases: [/functions/transform.remarshal]
----
-
-The format must be one of `json`, `toml`, `yaml`, or `xml`. If the input is a string of serialized data, it must be valid JSON, TOML, YAML, or XML.
-
-> [!note]
-> This function is primarily a helper for Hugo's documentation, used to convert configuration and front matter examples to JSON, TOML, and YAML.
->
-> This is not a general purpose converter, and may change without notice if required for Hugo's documentation site.
-
-Example 1
-: Convert a string of TOML to JSON.
-
-```go-html-template
-{{ $s := `
- baseURL = 'https://example.org/'
- languageCode = 'en-US'
- title = 'ABC Widgets'
-`}}
-
a: Hugo rocks!
-b:
- answer: 42
- question: What is 6x7?
-c:
-- foo
-- bar
-
-```
-
-Rendered in browser:
-
-```text
-a: Hugo rocks!
-b:
- answer: 42
- question: What is 6x7?
-c:
-- foo
-- bar
-```
diff --git a/docs/content/en/functions/transform/ToMath.md b/docs/content/en/functions/transform/ToMath.md
deleted file mode 100644
index a9f12c546..000000000
--- a/docs/content/en/functions/transform/ToMath.md
+++ /dev/null
@@ -1,164 +0,0 @@
----
-title: transform.ToMath
-description: Renders mathematical equations and expressions written in the LaTeX markup language.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: types.Result[template.HTML]
- signatures: ['transform.ToMath INPUT [OPTIONS]']
-aliases: [/functions/tomath]
----
-
-{{< new-in 0.132.0 />}}
-
-Hugo uses an embedded instance of the [KaTeX] display engine to render mathematical markup to HTML. You do not need to install the KaTeX display engine.
-
-```go-html-template
-{{ transform.ToMath "c = \\pm\\sqrt{a^2 + b^2}" }}
-```
-
-> [!note]
-> By default, Hugo renders mathematical markup to [MathML], and does not require any CSS to display the result.
->
-> To optimize rendering quality and accessibility, use the `htmlAndMathml` output option as described below. This approach requires an external stylesheet.
-
-```go-html-template
-{{ $opts := dict "output" "htmlAndMathml" }}
-{{ transform.ToMath "c = \\pm\\sqrt{a^2 + b^2}" $opts }}
-```
-
-## Options
-
-Pass a map of options as the second argument to the `transform.ToMath` function. The options below are a subset of the KaTeX [rendering options].
-
-displayMode
-: (`bool`) Whether to render in display mode instead of inline mode. Default is `false`.
-
-errorColor
-: (`string`) The color of the error messages expressed as an RGB [hexadecimal color]. Default is `#cc0000`.
-
-fleqn
-: (`bool`) Whether to render flush left with a 2em left margin. Default is `false`.
-
-macros
-: (`map`) A map of macros to be used in the math expression. Default is `{}`.
-
- ```go-html-template
- {{ $macros := dict
- "\\addBar" "\\bar{#1}"
- "\\bold" "\\mathbf{#1}"
- }}
- {{ $opts := dict "macros" $macros }}
- {{ transform.ToMath "\\addBar{y} + \\bold{H}" $opts }}
- ```
-
-minRuleThickness
-: (`float`) The minimum thickness of the fraction lines in `em`. Default is `0.04`.
-
-output
-: (`string`). Determines the markup language of the output, one of `html`, `mathml`, or `htmlAndMathml`. Default is `mathml`.
-
- With `html` and `htmlAndMathml` you must include the KaTeX style sheet within the `head` element of your base template.
-
- ```html
-
-
-throwOnError
-: (`bool`) Whether to throw a `ParseError` when KaTeX encounters an unsupported command or invalid LaTeX. Default is `true`.
-
-## Error handling
-
-There are three ways to handle errors:
-
-1. Let KaTeX throw an error and fail the build. This is the default behavior.
-1. Set the `throwOnError` option to `false` to make KaTeX render the expression as an error instead of throwing an error. See [options](#options).
-1. Handle the error in your template.
-
-The example below demonstrates error handing within a template.
-
-## 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.
-
-### Step 1
-
-Enable and configure the Goldmark [passthrough extension] in your site 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 = [['\(', '\)']]
-{{< /code-toggle >}}
-
-> [!note]
-> The configuration above precludes the use of the `$...$` delimiter pair for inline equations. Although you can add this delimiter pair to the configuration, you will need to double-escape the `$` symbol when used outside of math contexts to avoid unintended formatting.
-
-### Step 2
-
-Create a [passthrough render hook] to capture and render the LaTeX markup.
-
-```go-html-template {file="layouts/_default/_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 -}}
-```
-
-### Step 3
-
-In your base template, conditionally include the KaTeX CSS within the head element.
-
-```go-html-template {file="layouts/_default/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.
-
-### Step 4
-
-Add some mathematical markup to your content, then test.
-
-```text {file="content/example.md"}
-This is an inline \(a^*=x-b^*\) equation.
-
-These are block equations:
-
-\[a^*=x-b^*\]
-
-$$a^*=x-b^*$$
-```
-
-## Chemistry
-
-{{< new-in 0.144.0 />}}
-
-You can also use the `transform.ToMath` function to render chemical equations, leveraging the `\ce` and `\pu` functions from the [mhchem] package.
-
-```text
-$$C_p[\ce{H2O(l)}] = \pu{75.3 J // mol K}$$
-```
-
-$$C_p[\ce{H2O(l)}] = \pu{75.3 J // mol K}$$
-
-[hexadecimal color]: https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color
-[KaTeX]: https://katex.org/
-[MathML]: https://developer.mozilla.org/en-US/docs/Web/MathML
-[mhchem]: https://mhchem.github.io/MathJax-mhchem/
-[passthrough extension]: /configuration/markup/#passthrough
-[passthrough render hook]: /render-hooks/passthrough/
-[rendering options]: https://katex.org/docs/options.html
diff --git a/docs/content/en/functions/transform/Unmarshal.md b/docs/content/en/functions/transform/Unmarshal.md
deleted file mode 100644
index 93168294c..000000000
--- a/docs/content/en/functions/transform/Unmarshal.md
+++ /dev/null
@@ -1,372 +0,0 @@
----
-title: transform.Unmarshal
-description: Parses serialized data and returns a map or an array. Supports CSV, JSON, TOML, YAML, and XML.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [unmarshal]
- returnType: any
- signatures: ['transform.Unmarshal [OPTIONS] INPUT']
-aliases: [/functions/transform.unmarshal]
----
-
-The input can be a string or a [resource](g).
-
-## Unmarshal a string
-
-```go-html-template
-{{ $string := `
-title: Les Misérables
-author: Victor Hugo
-`}}
-
-{{ $book := unmarshal $string }}
-{{ $book.title }} → Les Misérables
-{{ $book.author }} → Victor Hugo
-```
-
-## Unmarshal a resource
-
-Use the `transform.Unmarshal` function with global, page, and remote resources.
-
-### Global resource
-
-A global resource is a file within the `assets` directory, or within any directory mounted to the `assets` directory.
-
-```text
-assets/
-└── data/
- └── books.json
-```
-
-```go-html-template
-{{ $data := dict }}
-{{ $path := "data/books.json" }}
-{{ with resources.Get $path }}
- {{ with . | transform.Unmarshal }}
- {{ $data = . }}
- {{ end }}
-{{ else }}
- {{ errorf "Unable to get global resource %q" $path }}
-{{ end }}
-
-{{ range where $data "author" "Victor Hugo" }}
- {{ .title }} → Les Misérables
-{{ end }}
-```
-
-### Page resource
-
-A page resource is a file within a [page bundle].
-
-```text
-content/
-├── post/
-│ └── book-reviews/
-│ ├── books.json
-│ └── index.md
-└── _index.md
-```
-
-```go-html-template
-{{ $data := dict }}
-{{ $path := "books.json" }}
-{{ with .Resources.Get $path }}
- {{ with . | transform.Unmarshal }}
- {{ $data = . }}
- {{ end }}
-{{ else }}
- {{ errorf "Unable to get page resource %q" $path }}
-{{ end }}
-
-{{ range where $data "author" "Victor Hugo" }}
- {{ .title }} → Les Misérables
-{{ end }}
-```
-
-### Remote resource
-
-A remote resource is a file on a remote server, accessible via HTTP or HTTPS.
-
-```go-html-template
-{{ $data := dict }}
-{{ $url := "https://example.org/books.json" }}
-{{ with try (resources.GetRemote $url) }}
- {{ with .Err }}
- {{ errorf "%s" . }}
- {{ else with .Value }}
- {{ $data = . | transform.Unmarshal }}
- {{ else }}
- {{ errorf "Unable to get remote resource %q" $url }}
- {{ end }}
-{{ end }}
-
-{{ range where $data "author" "Victor Hugo" }}
- {{ .title }} → Les Misérables
-{{ end }}
-```
-
-> [!note]
-> When retrieving remote data, a misconfigured server may send a response header with an incorrect [Content-Type]. For example, the server may set the Content-Type header to `application/octet-stream` instead of `application/json`.
->
-> In these cases, pass the resource `Content` through the `transform.Unmarshal` function instead of passing the resource itself. For example, in the above, do this instead:
->
-> `{{ $data = .Content | transform.Unmarshal }}`
-
-## Working with CSV
-
-### Options
-
-When unmarshaling a CSV file, provide an optional map of options.
-
-delimiter
-: (`string`) The delimiter used. Default is `,`.
-
-comment
-: (`string`) The comment character used in the CSV. If set, lines beginning with the comment character without preceding whitespace are ignored.
-
-lazyQuotes
-: {{< new-in 0.122.0 />}}
-: (`bool`) Whether to allow a quote in an unquoted field, or to allow a non-doubled quote in a quoted field. Default is `false`.
-
-targetType
-: {{< new-in 0.146.7 />}}
-: (`string`) The target data type, either `slice` or `map`. Default is `slice`.
-
-### Examples
-
-The examples below use this CSV file:
-
-```csv
-"name","type","breed","age"
-"Spot","dog","Collie",3
-"Rover","dog","Boxer",5
-"Felix","cat","Calico",7
-```
-
-To render an HTML table from a CSV file:
-
-```go-html-template
-{{ $data := slice }}
-{{ $file := "pets.csv" }}
-{{ with or (.Resources.Get $file) (resources.Get $file) }}
- {{ $opts := dict "targetType" "slice" }}
- {{ $data = transform.Unmarshal $opts . }}
-{{ end }}
-
-{{ with $data }}
-
-
-
- {{ range index . 0 }}
-
{{ . }}
- {{ end }}
-
-
-
- {{ range . | after 1 }}
-
- {{ range . }}
-
{{ . }}
- {{ end }}
-
- {{ end }}
-
-
-{{ end }}
-```
-
-To extract a subset of the data, or to sort the data, unmarshal to a map instead of a slice:
-
-```go-html-template
-{{ $data := slice }}
-{{ $file := "pets.csv" }}
-{{ with or (.Resources.Get $file) (resources.Get $file) }}
- {{ $opts := dict "targetType" "map" }}
- {{ $data = transform.Unmarshal $opts . }}
-{{ end }}
-
-{{ with sort (where $data "type" "dog") "name" "asc" }}
-
-
-
-
name
-
type
-
breed
-
age
-
-
-
- {{ range . }}
-
-
{{ .name }}
-
{{ .type }}
-
{{ .breed }}
-
{{ .age }}
-
- {{ end }}
-
-
-{{ end }}
-```
-
-## Working with XML
-
-When unmarshaling an XML file, do not include the root node when accessing data. For example, after unmarshaling the RSS feed below, access the feed title with `$data.channel.title`.
-
-```xml
-
-
-
- Books on Example Site
- https://example.org/books/
- Recent content in Books on Example Site
- en-US
-
-
- The Hunchback of Notre Dame
- Written by Victor Hugo
- https://example.org/books/the-hunchback-of-notre-dame/
- Mon, 09 Oct 2023 09:27:12 -0700
- https://example.org/books/the-hunchback-of-notre-dame/
-
-
- Les Misérables
- Written by Victor Hugo
- https://example.org/books/les-miserables/
- Mon, 09 Oct 2023 09:27:11 -0700
- https://example.org/books/les-miserables/
-
-
-
-```
-
-Get the remote data:
-
-```go-html-template
-{{ $data := dict }}
-{{ $url := "https://example.org/books/index.xml" }}
-{{ with try (resources.GetRemote $url) }}
- {{ with .Err }}
- {{ errorf "%s" . }}
- {{ else with .Value }}
- {{ $data = . | transform.Unmarshal }}
- {{ else }}
- {{ errorf "Unable to get remote resource %q" $url }}
- {{ end }}
-{{ end }}
-```
-
-Inspect the data structure:
-
-```go-html-template
-
{{ debug.Dump $data }}
-```
-
-List the book titles:
-
-```go-html-template
-{{ with $data.channel.item }}
-
- {{ range . }}
-
{{ .title }}
- {{ end }}
-
-{{ end }}
-```
-
-Hugo renders this to:
-
-```html
-
-
The Hunchback of Notre Dame
-
Les Misérables
-
-```
-
-### XML attributes and namespaces
-
-Let's add a `lang` attribute to the `title` nodes of our RSS feed, and a namespaced node for the ISBN number:
-
-```xml
-
-
-
- Books on Example Site
- https://example.org/books/
- Recent content in Books on Example Site
- en-US
-
-
- The Hunchback of Notre Dame
- Written by Victor Hugo
- 9780140443530
- https://example.org/books/the-hunchback-of-notre-dame/
- Mon, 09 Oct 2023 09:27:12 -0700
- https://example.org/books/the-hunchback-of-notre-dame/
-
-
- Les Misérables
- Written by Victor Hugo
- 9780451419439
- https://example.org/books/les-miserables/
- Mon, 09 Oct 2023 09:27:11 -0700
- https://example.org/books/les-miserables/
-
-
-
-```
-
-After retrieving the remote data, inspect the data structure:
-
-```go-html-template
-
{{ debug.Dump $data }}
-```
-
-Each item node looks like this:
-
-```json
-{
- "description": "Written by Victor Hugo",
- "guid": "https://example.org/books/the-hunchback-of-notre-dame/",
- "link": "https://example.org/books/the-hunchback-of-notre-dame/",
- "number": "9780140443530",
- "pubDate": "Mon, 09 Oct 2023 09:27:12 -0700",
- "title": {
- "#text": "The Hunchback of Notre Dame",
- "-lang": "en"
- }
-}
-```
-
-The title keys do not begin with an underscore or a letter---they are not valid [identifiers](g). Use the [`index`] function to access the values:
-
-```go-html-template
-{{ with $data.channel.item }}
-
- {{ range . }}
- {{ $title := index .title "#text" }}
- {{ $lang := index .title "-lang" }}
- {{ $ISBN := .number }}
-
{{ $title }} ({{ $lang }}) {{ $ISBN }}
- {{ end }}
-
-{{ end }}
-```
-
-Hugo renders this to:
-
-```html
-
-
The Hunchback of Notre Dame (en) 9780140443530
-
Les Misérables (fr) 9780451419439
-
-```
-
-[`index`]: /functions/collections/indexfunction/
-[Content-Type]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type
-[page bundle]: /content-management/page-bundles/
diff --git a/docs/content/en/functions/transform/XMLEscape.md b/docs/content/en/functions/transform/XMLEscape.md
deleted file mode 100644
index 9e6c77927..000000000
--- a/docs/content/en/functions/transform/XMLEscape.md
+++ /dev/null
@@ -1,40 +0,0 @@
----
-title: transform.XMLEscape
-description: Returns the given string, removing disallowed characters then escaping the result to its XML equivalent.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: string
- signatures: [transform.XMLEscape INPUT]
----
-
-{{< new-in 0.121.0 />}}
-
-The `transform.XMLEscape` function removes [disallowed characters] as defined in the XML specification, then escapes the result by replacing the following characters with [HTML entities]:
-
-- `"` → `"`
-- `'` → `'`
-- `&` → `&`
-- `<` → `<`
-- `>` → `>`
-- `\t` → ` `
-- `\n` → `
`
-- `\r` → `
`
-
-For example:
-
-```go-html-template
-{{ transform.XMLEscape "
abc
" }} → <p>abc</p>
-```
-
-When using `transform.XMLEscape` in a template rendered by Go's [html/template] package, declare the string to be safe HTML to avoid double escaping. For example, in an RSS template:
-
-```xml {file="layouts/_default/rss.xml"}
-{{ .Summary | transform.XMLEscape | safeHTML }}
-```
-
-[disallowed characters]: https://www.w3.org/TR/xml/#charsets
-[html entities]: https://developer.mozilla.org/en-us/docs/glossary/entity
-[html/template]: https://pkg.go.dev/html/template
diff --git a/docs/content/en/functions/transform/_index.md b/docs/content/en/functions/transform/_index.md
deleted file mode 100644
index 19c271b65..000000000
--- a/docs/content/en/functions/transform/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Transform functions
-linkTitle: transform
-description: Use these functions to transform values from one format to another.
-categories: []
-keywords: []
----
diff --git a/docs/content/en/functions/urls/AbsLangURL.md b/docs/content/en/functions/urls/AbsLangURL.md
deleted file mode 100644
index da45ca224..000000000
--- a/docs/content/en/functions/urls/AbsLangURL.md
+++ /dev/null
@@ -1,72 +0,0 @@
----
-title: urls.AbsLangURL
-description: Returns an absolute URL with a language prefix, if any.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [absLangURL]
- returnType: string
- signatures: [urls.AbsLangURL INPUT]
-aliases: [/functions/abslangurl]
----
-
-Use this function with both monolingual and multilingual configurations. The URL returned by this function depends on:
-
-- Whether the input begins with a slash (`/`)
-- The `baseURL` in your site configuration
-- The language prefix, if any
-
-This is the site configuration for the examples that follow:
-
-{{< code-toggle file=hugo >}}
-defaultContentLanguage = 'en'
-defaultContentLanguageInSubdir = true
-[languages.en]
-weight = 1
-[languages.es]
-weight = 2
-{{< /code-toggle >}}
-
-## Input does not begin with a slash
-
-If the input does not begin with a slash, the path in the resulting URL will be relative to the `baseURL` in your site configuration.
-
-When rendering the `en` site with `baseURL = https://example.org/`
-
-```go-html-template
-{{ absLangURL "" }} → https://example.org/en/
-{{ absLangURL "articles" }} → https://example.org/en/articles
-{{ absLangURL "style.css" }} → https://example.org/en/style.css
-```
-
-When rendering the `en` site with `baseURL = https://example.org/docs/`
-
-```go-html-template
-{{ absLangURL "" }} → https://example.org/docs/en/
-{{ absLangURL "articles" }} → https://example.org/docs/en/articles
-{{ absLangURL "style.css" }} → https://example.org/docs/en/style.css
-```
-
-## Input begins with a slash
-
-If the input begins with a slash, the path in the resulting URL will be relative to the protocol+host of the `baseURL` in your site configuration.
-
-When rendering the `en` site with `baseURL = https://example.org/`
-
-```go-html-template
-{{ absLangURL "/" }} → https://example.org/en/
-{{ absLangURL "/articles" }} → https://example.org/en/articles
-{{ absLangURL "/style.css" }} → https://example.org/en/style.css
-```
-
-When rendering the `en` site with `baseURL = https://example.org/docs/`
-
-```go-html-template
-{{ absLangURL "/" }} → https://example.org/en/
-{{ absLangURL "/articles" }} → https://example.org/en/articles
-{{ absLangURL "/style.css" }} → https://example.org/en/style.css
-```
-
-> [!note]
-> As illustrated by the previous example, using a leading slash is rarely desirable and can lead to unexpected outcomes. In nearly all cases, omit the leading slash.
diff --git a/docs/content/en/functions/urls/AbsURL.md b/docs/content/en/functions/urls/AbsURL.md
deleted file mode 100644
index 72613cd0b..000000000
--- a/docs/content/en/functions/urls/AbsURL.md
+++ /dev/null
@@ -1,62 +0,0 @@
----
-title: urls.AbsURL
-description: Returns an absolute URL.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [absURL]
- returnType: string
- signatures: [urls.AbsURL INPUT]
-aliases: [/functions/absurl]
----
-
-With multilingual configurations, use the [`urls.AbsLangURL`] function instead. The URL returned by this function depends on:
-
-- Whether the input begins with a slash (`/`)
-- The `baseURL` in your site configuration
-
-## Input does not begin with a slash
-
-If the input does not begin with a slash, the path in the resulting URL will be relative to the `baseURL` in your site configuration.
-
-With `baseURL = https://example.org/`
-
-```go-html-template
-{{ absURL "" }} → https://example.org/
-{{ absURL "articles" }} → https://example.org/articles
-{{ absURL "style.css" }} → https://example.org/style.css
-```
-
-With `baseURL = https://example.org/docs/`
-
-```go-html-template
-{{ absURL "" }} → https://example.org/docs/
-{{ absURL "articles" }} → https://example.org/docs/articles
-{{ absURL "style.css" }} → https://example.org/docs/style.css
-```
-
-## Input begins with a slash
-
-If the input begins with a slash, the path in the resulting URL will be relative to the protocol+host of the `baseURL` in your site configuration.
-
-With `baseURL = https://example.org/`
-
-```go-html-template
-{{ absURL "/" }} → https://example.org/
-{{ absURL "/articles" }} → https://example.org/articles
-{{ absURL "/style.css" }} → https://example.org/style.css
-```
-
-With `baseURL = https://example.org/docs/`
-
-```go-html-template
-{{ absURL "/" }} → https://example.org/
-{{ absURL "/articles" }} → https://example.org/articles
-{{ absURL "/style.css" }} → https://example.org/style.css
-```
-
-> [!note]
-> As illustrated by the previous example, using a leading slash is rarely desirable and can lead to unexpected outcomes. In nearly all cases, omit the leading slash.
-
-[`urls.AbsLangURL`]: /functions/urls/abslangurl/
diff --git a/docs/content/en/functions/urls/Anchorize.md b/docs/content/en/functions/urls/Anchorize.md
deleted file mode 100644
index d18bd9a4d..000000000
--- a/docs/content/en/functions/urls/Anchorize.md
+++ /dev/null
@@ -1,36 +0,0 @@
----
-title: urls.Anchorize
-description: Returns the given string, sanitized for usage in an HTML id attribute.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [anchorize]
- returnType: string
- signatures: [urls.Anchorize INPUT]
-aliases: [/functions/anchorize]
----
-
-{{% include "/_common/functions/urls/anchorize-vs-urlize.md" %}}
-
-## Sanitizing logic
-
-With the default Markdown renderer, Goldmark, the sanitizing logic is controlled by your site configuration:
-
-{{< code-toggle file=hugo >}}
-[markup.goldmark.parser]
-autoHeadingIDType = 'github'
-{{< /code-toggle >}}
-
-This controls the behavior of the `anchorize` function and the generation of heading IDs when rendering Markdown to HTML.
-
-Set `autoHeadingIDType` to one of:
-
-github
-: Compatible with GitHub. This is the default.
-
-github-ascii
-: Similar to the `github` setting, but removes non-ASCII characters.
-
-blackfriday
-: Provided for backwards compatibility with Hugo v0.59.1 and earlier. This option will be removed in a future release.
diff --git a/docs/content/en/functions/urls/JoinPath.md b/docs/content/en/functions/urls/JoinPath.md
deleted file mode 100644
index b9da7e437..000000000
--- a/docs/content/en/functions/urls/JoinPath.md
+++ /dev/null
@@ -1,27 +0,0 @@
----
-title: urls.JoinPath
-description: Joins the provided elements into a URL string and cleans the result of any ./ or ../ elements. If the argument list is empty, JoinPath returns an empty string.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: string
- signatures: [urls.JoinPath ELEMENT...]
-aliases: [/functions/urls.joinpath]
----
-
-```go-html-template
-{{ urls.JoinPath }} → "" (empty string)
-{{ urls.JoinPath "" }} → /
-{{ urls.JoinPath "a" }} → a
-{{ urls.JoinPath "a" "b" }} → a/b
-{{ urls.JoinPath "/a" "b" }} → /a/b
-{{ urls.JoinPath "https://example.org" "b" }} → https://example.org/b
-
-{{ urls.JoinPath (slice "a" "b") }} → a/b
-```
-
-Unlike the [`path.Join`] function, `urls.JoinPath` retains consecutive leading slashes.
-
-[`path.Join`]: /functions/path/join/
diff --git a/docs/content/en/functions/urls/Parse.md b/docs/content/en/functions/urls/Parse.md
deleted file mode 100644
index 7def5fb1d..000000000
--- a/docs/content/en/functions/urls/Parse.md
+++ /dev/null
@@ -1,35 +0,0 @@
----
-title: urls.Parse
-description: Parses a URL into a URL structure.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: []
- returnType: url.URL
- signatures: [urls.Parse URL]
-aliases: [/functions/urls.parse]
----
-
-The `urls.Parse` function parses a URL into a [URL structure](https://godoc.org/net/url#URL). The URL may be relative (a path, without a host) or absolute (starting with a [scheme]). Hugo throws an error when parsing an invalid URL.
-
-[scheme]: https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml#uri-schemes-1
-
-```go-html-template
-{{ $url := "https://example.org:123/foo?a=6&b=7#bar" }}
-{{ $u := urls.Parse $url }}
-
-{{ $u.String }} → https://example.org:123/foo?a=6&b=7#bar
-{{ $u.IsAbs }} → true
-{{ $u.Scheme }} → https
-{{ $u.Host }} → example.org:123
-{{ $u.Hostname }} → example.org
-{{ $u.RequestURI }} → /foo?a=6&b=7
-{{ $u.Path }} → /foo
-{{ $u.RawQuery }} → a=6&b=7
-{{ $u.Query }} → map[a:[6] b:[7]]
-{{ $u.Query.a }} → [6]
-{{ $u.Query.Get "a" }} → 6
-{{ $u.Query.Has "b" }} → true
-{{ $u.Fragment }} → bar
-```
diff --git a/docs/content/en/functions/urls/Ref.md b/docs/content/en/functions/urls/Ref.md
deleted file mode 100644
index 92abed91a..000000000
--- a/docs/content/en/functions/urls/Ref.md
+++ /dev/null
@@ -1,46 +0,0 @@
----
-title: urls.Ref
-description: Returns the absolute URL of the page with the given path, language, and output format.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [ref]
- returnType: string
- signatures:
- - urls.Ref PAGE PATH
- - urls.Ref PAGE OPTIONS
-aliases: [/functions/ref]
----
-
-## Usage
-
-The `ref` function takes two arguments:
-
-1. The context for resolving relative paths (typically the current page).
-1. Either the target page's path or an options map (see below).
-
-## 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
-{{ ref . "/books/book-1" }} → https://example.org/en/books/book-1/
-
-{{ $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/functions/urls/RelLangURL.md b/docs/content/en/functions/urls/RelLangURL.md
deleted file mode 100644
index af8bff3d7..000000000
--- a/docs/content/en/functions/urls/RelLangURL.md
+++ /dev/null
@@ -1,82 +0,0 @@
----
-title: urls.RelLangURL
-description: Returns a relative URL with a language prefix, if any.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [relLangURL]
- returnType: string
- signatures: [urls.RelLangURL INPUT]
-aliases: [/functions/rellangurl]
----
-
-Use this function with both monolingual and multilingual configurations. The URL returned by this function depends on:
-
-- Whether the input begins with a slash (`/`)
-- The `baseURL` in your site configuration
-- The language prefix, if any
-
-This is the site configuration for the examples that follow:
-
-{{< code-toggle file=hugo >}}
-defaultContentLanguage = 'en'
-defaultContentLanguageInSubdir = true
-[languages.en]
-weight = 1
-[languages.es]
-weight = 2
-{{< /code-toggle >}}
-
-## Input does not begin with a slash
-
-If the input does not begin with a slash, the resulting URL will be relative to the `baseURL` in your site configuration.
-
-When rendering the `en` site with `baseURL = https://example.org/`
-
-```go-html-template
-{{ relLangURL "" }} → /en/
-{{ relLangURL "articles" }} → /en/articles
-{{ relLangURL "style.css" }} → /en/style.css
-{{ relLangURL "https://example.org" }} → https://example.org
-{{ relLangURL "https://example.org/" }} → /en
-{{ relLangURL "https://www.example.org" }} → https://www.example.org
-{{ relLangURL "https://www.example.org/" }} → https://www.example.org/
-```
-
-When rendering the `en` site with `baseURL = https://example.org/docs/`
-
-```go-html-template
-{{ relLangURL "" }} → /docs/en/
-{{ relLangURL "articles" }} → /docs/en/articles
-{{ relLangURL "style.css" }} → /docs/en/style.css
-{{ relLangURL "https://example.org" }} → https://example.org
-{{ relLangURL "https://example.org/" }} → https://example.org/
-{{ relLangURL "https://example.org/docs" }} → https://example.org/docs
-{{ relLangURL "https://example.org/docs/" }} → /docs/en
-{{ relLangURL "https://www.example.org" }} → https://www.example.org
-{{ relLangURL "https://www.example.org/" }} → https://www.example.org/
-```
-
-## Input begins with a slash
-
-If the input begins with a slash, the resulting URL will be relative to the protocol+host of the `baseURL` in your site configuration.
-
-When rendering the `en` site with `baseURL = https://example.org/`
-
-```go-html-template
-{{ relLangURL "/" }} → /en/
-{{ relLangURL "/articles" }} → /en/articles
-{{ relLangURL "/style.css" }} → /en/style.css
-```
-
-When rendering the `en` site with `baseURL = https://example.org/docs/`
-
-```go-html-template
-{{ relLangURL "/" }} → /en/
-{{ relLangURL "/articles" }} → /en/articles
-{{ relLangURL "/style.css" }} → /en/style.css
-```
-
-> [!note]
-> As illustrated by the previous example, using a leading slash is rarely desirable and can lead to unexpected outcomes. In nearly all cases, omit the leading slash.
diff --git a/docs/content/en/functions/urls/RelRef.md b/docs/content/en/functions/urls/RelRef.md
deleted file mode 100644
index aa7acf50b..000000000
--- a/docs/content/en/functions/urls/RelRef.md
+++ /dev/null
@@ -1,46 +0,0 @@
----
-title: urls.RelRef
-description: Returns the relative URL of the page with the given path, language, and output format.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [relref]
- returnType: string
- signatures:
- - urls.RelRef PAGE PATH
- - urls.RelRef PAGE OPTIONS
-aliases: [/functions/relref]
----
-
-## Usage
-
-The `relref` function takes two arguments:
-
-1. The context for resolving relative paths (typically the current page).
-1. Either the target page's path or an options map (see below).
-
-## 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
-{{ relref . "/books/book-1" }} → /en/books/book-1/
-
-{{ $opts := dict "path" "/books/book-1" }}
-{{ relref . $opts }} → /en/books/book-1/
-
-{{ $opts := dict "path" "/books/book-1" "lang" "de" }}
-{{ relref . $opts }} → /de/books/book-1/
-
-{{ $opts := dict "path" "/books/book-1" "lang" "de" "outputFormat" "json" }}
-{{ relref . $opts }} → /de/books/book-1/index.json
-```
-
-## Error handling
-
-{{% include "_common/ref-and-relref-error-handling.md" %}}
diff --git a/docs/content/en/functions/urls/RelURL.md b/docs/content/en/functions/urls/RelURL.md
deleted file mode 100644
index 0aef4043f..000000000
--- a/docs/content/en/functions/urls/RelURL.md
+++ /dev/null
@@ -1,72 +0,0 @@
----
-title: urls.RelURL
-description: Returns a relative URL.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [relURL]
- returnType: string
- signatures: [urls.RelURL INPUT]
-aliases: [/functions/relurl]
----
-
-With multilingual configurations, use the [`urls.RelLangURL`] function instead. The URL returned by this function depends on:
-
-- Whether the input begins with a slash (`/`)
-- The `baseURL` in your site configuration
-
-## Input does not begin with a slash
-
-If the input does not begin with a slash, the resulting URL will be relative to the `baseURL` in your site configuration.
-
-With `baseURL = https://example.org/`
-
-```go-html-template
-{{ relURL "" }} → /
-{{ relURL "articles" }} → /articles
-{{ relURL "style.css" }} → /style.css
-{{ relURL "https://example.org" }} → https://example.org
-{{ relURL "https://example.org/" }} → /
-{{ relURL "https://www.example.org" }} → https://www.example.org
-{{ relURL "https://www.example.org/" }} → https://www.example.org/
-```
-
-With `baseURL = https://example.org/docs/`
-
-```go-html-template
-{{ relURL "" }} → /docs/
-{{ relURL "articles" }} → /docs/articles
-{{ relURL "style.css" }} → /docs/style.css
-{{ relURL "https://example.org" }} → https://example.org
-{{ relURL "https://example.org/" }} → https://example.org/
-{{ relURL "https://example.org/docs" }} → https://example.org/docs
-{{ relURL "https://example.org/docs/" }} → /docs
-{{ relURL "https://www.example.org" }} → https://www.example.org
-{{ relURL "https://www.example.org/" }} → https://www.example.org/
-```
-
-## Input begins with a slash
-
-If the input begins with a slash, the resulting URL will be relative to the protocol+host of the `baseURL` in your site configuration.
-
-With `baseURL = https://example.org/`
-
-```go-html-template
-{{ relURL "/" }} → /
-{{ relURL "/articles" }} → /articles
-{{ relURL "/style.css" }} → /style.css
-```
-
-With `baseURL = https://example.org/docs/`
-
-```go-html-template
-{{ relURL "/" }} → /
-{{ relURL "/articles" }} → /articles
-{{ relURL "/style.css" }} → /style.css
-```
-
-> [!note]
-> As illustrated by the previous example, using a leading slash is rarely desirable and can lead to unexpected outcomes. In nearly all cases, omit the leading slash.
-
-[`urls.RelLangURL`]: /functions/urls/rellangurl/
diff --git a/docs/content/en/functions/urls/URLize.md b/docs/content/en/functions/urls/URLize.md
deleted file mode 100644
index b0cc812ec..000000000
--- a/docs/content/en/functions/urls/URLize.md
+++ /dev/null
@@ -1,61 +0,0 @@
----
-title: urls.URLize
-description: Returns the given string, sanitized for usage in a URL.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- aliases: [urlize]
- returnType: string
- signatures: [urls.URLize INPUT]
-aliases: [/functions/urlize]
----
-
-{{% include "/_common/functions/urls/anchorize-vs-urlize.md" %}}
-
-## Example
-
-Use the `urlize` function to create a link to a [term page](g).
-
-Consider this site configuration:
-
-{{< code-toggle file=hugo >}}
-[taxonomies]
-author = 'authors'
-{{< /code-toggle >}}
-
-And this front matter:
-
-{{< code-toggle file=content/books/les-miserables.md fm=true >}}
-title = 'Les Misérables'
-authors = ['Victor Hugo']
-{{< /code-toggle >}}
-
-The published site will have this structure:
-
-```text
-public/
-├── authors/
-│ ├── victor-hugo/
-│ │ └── index.html
-│ └── index.html
-├── books/
-│ ├── les-miserables/
-│ │ └── index.html
-│ └── index.html
-└── index.html
-```
-
-To create a link to the term page:
-
-```go-html-template
-{{ $taxonomy := "authors" }}
-{{ $term := "Victor Hugo" }}
-{{ with index .Site.Taxonomies $taxonomy (urlize $term) }}
- {{ .Page.LinkTitle }}
-{{ end }}
-```
-
-To generate a list of term pages associated with a given content page, use the [`GetTerms`] method on a `Page` object.
-
-[`GetTerms`]: /methods/page/getterms/
diff --git a/docs/content/en/functions/urls/_index.md b/docs/content/en/functions/urls/_index.md
deleted file mode 100644
index 3a1962d7a..000000000
--- a/docs/content/en/functions/urls/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: URL functions
-linkTitle: urls
-description: Use these functions to work with URLs.
-categories: []
-keywords: []
----
diff --git a/docs/content/en/getting-started/_index.md b/docs/content/en/getting-started/_index.md
deleted file mode 100644
index 2e2f57127..000000000
--- a/docs/content/en/getting-started/_index.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: Getting started
-description: How to get started with Hugo.
-categories: []
-keywords: []
-weight: 10
-aliases: [/overview/introduction/]
----
diff --git a/docs/content/en/getting-started/directory-structure.md b/docs/content/en/getting-started/directory-structure.md
deleted file mode 100644
index 3feecd135..000000000
--- a/docs/content/en/getting-started/directory-structure.md
+++ /dev/null
@@ -1,207 +0,0 @@
----
-title: Directory structure
-description: An overview of Hugo's directory structure.
-categories: []
-keywords: []
-weight: 30
-aliases: [/overview/source-directory/]
----
-
-Each Hugo project is a directory, with subdirectories that contribute to the content, structure, behavior, and presentation of your site.
-
-## Site skeleton
-
-Hugo generates a project skeleton when you create a new site. For example, this command:
-
-```sh
-hugo new site my-site
-```
-
-Creates this directory structure:
-
-```txt
-my-site/
-├── archetypes/
-│ └── default.md
-├── assets/
-├── content/
-├── data/
-├── i18n/
-├── layouts/
-├── static/
-├── themes/
-└── hugo.toml <-- site configuration
-```
-
-Depending on requirements, you may wish to organize your site configuration into subdirectories:
-
-```txt
-my-site/
-├── archetypes/
-│ └── default.md
-├── assets/
-├── config/ <-- site configuration
-│ └── _default/
-│ └── hugo.toml
-├── content/
-├── data/
-├── i18n/
-├── layouts/
-├── static/
-└── themes/
-```
-
-When you build your site, Hugo creates a `public` directory, and typically a `resources` directory as well:
-
-```txt
-my-site/
-├── archetypes/
-│ └── default.md
-├── assets/
-├── config/
-│ └── _default/
-│ └── hugo.toml
-├── content/
-├── data/
-├── i18n/
-├── layouts/
-├── public/ <-- created when you build your site
-├── resources/ <-- created when you build your site
-├── static/
-└── themes/
-```
-
-## Directories
-
-Each of the subdirectories contributes to the content, structure, behavior, or presentation of your site.
-
-archetypes
-: The `archetypes` directory contains templates for new content. See [details](/content-management/archetypes/).
-
-assets
-: The `assets` directory contains global resources typically passed through an asset pipeline. This includes resources such as images, CSS, Sass, JavaScript, and TypeScript. See [details](/hugo-pipes/introduction/).
-
-config
-: The `config` directory contains your site configuration, possibly split into multiple subdirectories and files. For projects with minimal configuration or projects that do not need to behave differently in different environments, a single configuration file named `hugo.toml` in the root of the project is sufficient. See [details](/configuration/introduction/#configuration-directory).
-
-content
-: The `content` directory contains the markup files (typically Markdown) and page resources that comprise the content of your site. See [details](/content-management/organization/).
-
-data
-: The `data` directory contains data files (JSON, TOML, YAML, or XML) that augment content, configuration, localization, and navigation. See [details](/content-management/data-sources/).
-
-i18n
-: The `i18n` directory contains translation tables for multilingual sites. See [details](/content-management/multilingual/).
-
-layouts
-: The `layouts` directory contains templates to transform content, data, and resources into a complete website. See [details](/templates/).
-
-public
-: The `public` directory contains the published website, generated when you run the `hugo` or `hugo server` commands. Hugo recreates this directory and its content as needed. See [details](/getting-started/usage/#build-your-site).
-
-resources
-: The `resources` directory contains cached output from Hugo's asset pipelines, generated when you run the `hugo` or `hugo server` commands. By default this cache directory includes CSS and images. Hugo recreates this directory and its content as needed.
-
-static
-: The `static` directory contains files that will be copied to the `public` directory when you build your site. For example: `favicon.ico`, `robots.txt`, and files that verify site ownership. Before the introduction of [page bundles](g) and [asset pipelines](/hugo-pipes/introduction/), the `static` directory was also used for images, CSS, and JavaScript.
-
-themes
-: The `themes` directory contains one or more [themes](g), each in its own subdirectory.
-
-## Union file system
-
-Hugo creates a union file system, allowing you to mount two or more directories to the same location. For example, let's say your home directory contains a Hugo project in one directory, and shared content in another:
-
-```text
-home/
-└── user/
- ├── my-site/
- │ ├── content/
- │ │ ├── books/
- │ │ │ ├── _index.md
- │ │ │ ├── book-1.md
- │ │ │ └── book-2.md
- │ │ └── _index.md
- │ ├── themes/
- │ │ └── my-theme/
- │ └── hugo.toml
- └── shared-content/
- └── films/
- ├── _index.md
- ├── film-1.md
- └── film-2.md
-```
-
-You can include the shared content when you build your site using mounts. In your site configuration:
-
-{{< code-toggle file=hugo >}}
-[[module.mounts]]
-source = 'content'
-target = 'content'
-
-[[module.mounts]]
-source = '/home/user/shared-content'
-target = 'content'
-{{< /code-toggle >}}
-
-> [!note]
-> When you overlay one directory on top of another, you must mount both directories.
->
-> Hugo does not follow symbolic links. If you need the functionality provided by symbolic links, use Hugo's union file system instead.
-
-After mounting, the union file system has this structure:
-
-```text
-home/
-└── user/
- └── my-site/
- ├── content/
- │ ├── books/
- │ │ ├── _index.md
- │ │ ├── book-1.md
- │ │ └── book-2.md
- │ ├── films/
- │ │ ├── _index.md
- │ │ ├── film-1.md
- │ │ └── film-2.md
- │ └── _index.md
- ├── themes/
- │ └── my-theme/
- └── hugo.toml
-```
-
-> [!note]
-> When two or more files have the same path, the order of precedence follows the order of the mounts. For example, if the shared content directory contains `books/book-1.md`, it will be ignored because the project's `content` directory was mounted first.
-
-You can mount directories to `archetypes`, `assets`, `content`, `data`, `i18n`, `layouts`, and `static`. See [details](/configuration/module/#mounts).
-
-You can also mount directories from Git repositories using Hugo Modules. See [details](/hugo-modules/).
-
-## Theme skeleton
-
-Hugo generates a functional theme skeleton when you create a new theme. For example, this command:
-
-```text
-hugo new theme my-theme
-```
-
-Creates this directory structure (subdirectories not shown):
-
-```text
-my-theme/
-├── archetypes/
-├── assets/
-├── content/
-├── data/
-├── i18n/
-├── layouts/
-├── static/
-├── LICENSE
-├── README.md
-├── hugo.toml
-└── theme.toml
-```
-
-Using the union file system described above, Hugo mounts each of these directories to the corresponding location in the project. When two files have the same path, the file in the project directory takes precedence. This allows you, for example, to override a theme's template by placing a copy in the same location within the project directory.
-
-If you are simultaneously using components from two or more themes or modules, and there's a path collision, the first mount takes precedence.
diff --git a/docs/content/en/getting-started/external-learning-resources/build-websites-with-hugo.png b/docs/content/en/getting-started/external-learning-resources/build-websites-with-hugo.png
deleted file mode 100644
index ebed7e89f..000000000
Binary files a/docs/content/en/getting-started/external-learning-resources/build-websites-with-hugo.png and /dev/null differ
diff --git a/docs/content/en/getting-started/external-learning-resources/hugo-in-action.png b/docs/content/en/getting-started/external-learning-resources/hugo-in-action.png
deleted file mode 100644
index 7bc5c9930..000000000
Binary files a/docs/content/en/getting-started/external-learning-resources/hugo-in-action.png and /dev/null differ
diff --git a/docs/content/en/getting-started/external-learning-resources/index.md b/docs/content/en/getting-started/external-learning-resources/index.md
deleted file mode 100644
index 7838b6810..000000000
--- a/docs/content/en/getting-started/external-learning-resources/index.md
+++ /dev/null
@@ -1,83 +0,0 @@
----
-title: External learning resources
-linkTitle: External resources
-description: Use these third-party resources to learn Hugo.
-categories: []
-keywords: []
-weight: 40
----
-
-## Books
-
-### Hugo in Action
-
-Hugo in Action is a step-by-step guide to using Hugo to create static websites. Working with a complete example website and source code samples, you'll learn how to build and host a low-maintenance, high-performance site that will wow your users and stay stable without relying on a third-party server.
-
-[{{< img src="hugo-in-action.png" alt="Book cover: Hugo in Action" filter="process" filterArgs="resize x350 webp">}}](https://www.manning.com/books/hugo-in-action/)
-
-Author: Atishay Jain\
-Publisher: [Manning Publications](https://www.manning.com/books/hugo-in-action/)\
-Publication date: March 2022\
-Length: 488 pages\
-ISBN: 9781617297007
-
-### Build Websites with Hugo
-
-In this book, you'll use Hugo to build a personal portfolio site that you can use to showcase your skills and thoughts to the world. You'll build the basic skeleton, develop a custom theme, and use content templates to generate new pages quickly. You'll use internal and external data sources to embed content into your site and render some of your content in JSON and RSS. You'll add a blog section with posts and integrate Disqus with your site, and then make your site searchable.
-
-[{{< img src="build-websites-with-hugo.png" alt="Book cover: Build Websites with Hugo" filter="process" filterArgs="resize x350 webp">}}](https://pragprog.com/titles/bhhugo/build-websites-with-hugo/)
-
-Author: Brian P. Hogan\
-Publisher: [Pragmatic Bookshelf](https://pragprog.com/titles/bhhugo/build-websites-with-hugo/)\
-Publication date: May 2020\
-Length: 154 pages\
-ISBN: 9781680507263
-
-## Videos
-
-### Hugo Beginner Tutorial Series
-
-Welcome to this introduction to Hugo tutorial. This series aims to take you from a lion cub with basic web design knowledge to creating your first Hugo website. In this series, you'll learn how to set up a Hugo site, the basics of using Hugo layouts, partials, and templating, set up a blog, and finally, use data files. By the end of this series, you'll have the foundational knowledge to build your own Hugo sites.
-
-1. [Getting set up in Hugo](https://cloudcannon.com/tutorials/hugo-beginner-tutorial/)
-1. [Layouts in Hugo](https://cloudcannon.com/tutorials/hugo-beginner-tutorial/layouts-in-hugo/)
-1. [Hugo Partials](https://cloudcannon.com/tutorials/hugo-beginner-tutorial/hugo-partials/)
-1. [Hugo templating basics](https://cloudcannon.com/tutorials/hugo-beginner-tutorial/hugo-templating-basics/)
-1. [Blogging in Hugo](https://cloudcannon.com/tutorials/hugo-beginner-tutorial/blogging-in-hugo/)
-1. [Using Data in Hugo](https://cloudcannon.com/tutorials/hugo-beginner-tutorial/using-data-in-hugo/)
-
-Creator: Mike Neumegen\
-Affiliation: [CloudCannon](https://cloudcannon.com/)\
-Creation date: April 2022
-
-### Hugo Static Site Generator
-
-This course covers the basics of using the Hugo static site generator. Work your way through the articles, and we'll teach you everything you need to know to create a professional and scalable website or blog!
-
-1. [Introduction](https://www.giraffeacademy.com/static-site-generators/hugo/)
-1. [Windows Installation](https://www.giraffeacademy.com/static-site-generators/hugo/installing-hugo-on-windows/)
-1. [Mac Installation](https://www.giraffeacademy.com/static-site-generators/hugo/installing-hugo-on-mac/)
-1. [Creating A New Site](https://www.giraffeacademy.com/static-site-generators/hugo/hugo-directory-structure/)
-1. [Installing & Using Themes](https://www.giraffeacademy.com/static-site-generators/hugo/installing-using-themes/)
-1. [Content Organization](https://www.giraffeacademy.com/static-site-generators/hugo/content-organization/)
-1. [Front Matter](https://www.giraffeacademy.com/static-site-generators/hugo/front-matter/)
-1. [Archetypes](https://www.giraffeacademy.com/static-site-generators/hugo/archetypes/)
-1. [Shortcodes](https://www.giraffeacademy.com/static-site-generators/hugo/archetypes/)
-1. [Taxonomies](https://www.giraffeacademy.com/static-site-generators/hugo/taxonomies/)
-1. [Template Basics](https://www.giraffeacademy.com/static-site-generators/hugo/introduction-to-templates/)
-1. [List Page Templates](https://www.giraffeacademy.com/static-site-generators/hugo/list-page-templates/)
-1. [Single Page Templates](https://www.giraffeacademy.com/static-site-generators/hugo/single-page-templates/)
-1. [Home Page Templates](https://www.giraffeacademy.com/static-site-generators/hugo/home-page-templates/)
-1. [Section Templates](https://www.giraffeacademy.com/static-site-generators/hugo/section-templates/)
-1. [Block Templates](https://www.giraffeacademy.com/static-site-generators/hugo/block-templates/)
-1. [Variables](https://www.giraffeacademy.com/static-site-generators/hugo/variables/)
-1. [Functions](https://www.giraffeacademy.com/static-site-generators/hugo/functions/)
-1. [Conditionals](https://www.giraffeacademy.com/static-site-generators/hugo/conditionals/)
-1. [Data Templates](https://www.giraffeacademy.com/static-site-generators/hugo/data-templates/)
-1. [Partial Templates](https://www.giraffeacademy.com/static-site-generators/hugo/partial-templates/)
-1. [Shortcode Templates](https://www.giraffeacademy.com/static-site-generators/hugo/shortcode-templates/)
-1. [Building & Hosting](https://www.giraffeacademy.com/static-site-generators/hugo/building-&-hosting/)
-
-Creator: Mike Dane\
-Affiliation: [Giraffe Academy](https://www.giraffeacademy.com/)\
-Creation date: September 2017
diff --git a/docs/content/en/getting-started/quick-start.md b/docs/content/en/getting-started/quick-start.md
deleted file mode 100644
index dfb78f42e..000000000
--- a/docs/content/en/getting-started/quick-start.md
+++ /dev/null
@@ -1,218 +0,0 @@
----
-title: Quick start
-description: Create a Hugo site in minutes.
-categories: []
-keywords: []
-params:
- minVersion: v0.128.0
-weight: 10
-aliases: [/quickstart/,/overview/quickstart/]
----
-
-In this tutorial you will:
-
-1. Create a site
-1. Add content
-1. Configure the site
-1. Publish the site
-
-## Prerequisites
-
-Before you begin this tutorial you must:
-
-1. [Install Hugo] (extended or extended/deploy edition, {{% param "minVersion" %}} or later)
-1. [Install Git]
-
-You must also be comfortable working from the command line.
-
-## Create a site
-
-### Commands
-
-> [!note]
-> **If you are a Windows user:**
->
-> - Do not use the Command Prompt
-> - Do not use Windows PowerShell
-> - Run these commands from [PowerShell] or a Linux terminal such as WSL or Git > Bash
->
-> PowerShell and Windows PowerShell [are different applications].
-
-Verify that you have installed Hugo {{% param "minVersion" %}} or later.
-
-```text
-hugo version
-```
-
-Run these commands to create a Hugo site with the [Ananke] theme. The next section provides an explanation of each command.
-
-```text
-hugo new site quickstart
-cd quickstart
-git init
-git submodule add https://github.com/theNewDynamic/gohugo-theme-ananke.git themes/ananke
-echo "theme = 'ananke'" >> hugo.toml
-hugo server
-```
-
-View your site at the URL displayed in your terminal. Press `Ctrl + C` to stop Hugo's development server.
-
-### Explanation of commands
-
-Create the [directory structure] for your project in the `quickstart` directory.
-
-```text
-hugo new site quickstart
-```
-
-Change the current directory to the root of your project.
-
-```text
-cd quickstart
-```
-
-Initialize an empty Git repository in the current directory.
-
-```text
-git init
-```
-
-Clone the [Ananke] theme into the `themes` directory, adding it to your project as a [Git submodule].
-
-```text
-git submodule add https://github.com/theNewDynamic/gohugo-theme-ananke.git themes/ananke
-```
-
-Append a line to the site configuration file, indicating the current theme.
-
-```text
-echo "theme = 'ananke'" >> hugo.toml
-```
-
-Start Hugo's development server to view the site.
-
-```text
-hugo server
-```
-
-Press `Ctrl + C` to stop Hugo's development server.
-
-## Add content
-
-Add a new page to your site.
-
-```text
-hugo new content content/posts/my-first-post.md
-```
-
-Hugo created the file in the `content/posts` directory. Open the file with your editor.
-
-```text
-+++
-title = 'My First Post'
-date = 2024-01-14T07:07:07+01:00
-draft = true
-+++
-```
-
-Notice the `draft` value in the [front matter] is `true`. By default, Hugo does not publish draft content when you build the site. Learn more about [draft, future, and expired content].
-
-Add some [Markdown] to the body of the post, but do not change the `draft` value.
-
-```text
-+++
-title = 'My First Post'
-date = 2024-01-14T07:07:07+01:00
-draft = true
-+++
-## Introduction
-
-This is **bold** text, and this is *emphasized* text.
-
-Visit the [Hugo](https://gohugo.io) website!
-```
-
-Save the file, then start Hugo's development server to view the site. You can run either of the following commands to include draft content.
-
-```text
-hugo server --buildDrafts
-hugo server -D
-```
-
-View your site at the URL displayed in your terminal. Keep the development server running as you continue to add and change content.
-
-When satisfied with your new content, set the front matter `draft` parameter to `false`.
-
-> [!note]
-> Hugo's rendering engine conforms to the CommonMark [specification] for Markdown. The CommonMark organization provides a useful [live testing tool] powered by the reference implementation.
-
-## Configure the site
-
-With your editor, open the [site configuration] file (`hugo.toml`) in the root of your project.
-
-```text
-baseURL = 'https://example.org/'
-languageCode = 'en-us'
-title = 'My New Hugo Site'
-theme = 'ananke'
-```
-
-Make the following changes:
-
-1. Set the `baseURL` for your production site. This value must begin with the protocol and end with a slash, as shown above.
-1. Set the `languageCode` to your language and region.
-1. Set the `title` for your production site.
-
-Start Hugo's development server to see your changes, remembering to include draft content.
-
-```text
-hugo server -D
-```
-
-> [!note]
-> Most theme authors provide configuration guidelines and options. Make sure to visit your theme's repository or documentation site for details.
->
-> [The New Dynamic], authors of the Ananke theme, provide [documentation] for configuration and usage. They also provide a [demonstration site].
-
-## Publish the site
-
-In this step you will _publish_ your site, but you will not _deploy_ it.
-
-When you _publish_ your site, Hugo creates the entire static site in the `public` directory in the root of your project. This includes the HTML files, and assets such as images, CSS files, and JavaScript files.
-
-When you publish your site, you typically do _not_ want to include [draft, future, or expired content]. The command is simple.
-
-```text
-hugo
-```
-
-To learn how to _deploy_ your site, see the [host and deploy] section.
-
-## Ask for help
-
-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.
-
-## Other resources
-
-For other resources to help you learn Hugo, including books and video tutorials, see the [external learning resources](/getting-started/external-learning-resources/) page.
-
-[Ananke]: https://github.com/theNewDynamic/gohugo-theme-ananke
-[are different applications]: https://learn.microsoft.com/en-us/powershell/scripting/whats-new/differences-from-windows-powershell?view=powershell-7.3
-[demonstration site]: https://gohugo-ananke-theme-demo.netlify.app/
-[directory structure]: /getting-started/directory-structure/
-[documentation]: https://github.com/theNewDynamic/gohugo-theme-ananke#readme
-[draft, future, and expired content]: /getting-started/usage/#draft-future-and-expired-content
-[draft, future, or expired content]: /getting-started/usage/#draft-future-and-expired-content
-[forum]: https://discourse.gohugo.io/
-[front matter]: /content-management/front-matter/
-[Git submodule]: https://git-scm.com/book/en/v2/Git-Tools-Submodules
-[host and deploy]: /host-and-deploy/
-[Install Git]: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git
-[Install Hugo]: /installation/
-[live testing tool]: https://spec.commonmark.org/dingus/
-[Markdown]: https://daringfireball.net/projects/markdown
-[PowerShell]: https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows
-[requesting help]: https://discourse.gohugo.io/t/requesting-help/9132
-[site configuration]: /configuration/
-[specification]: https://spec.commonmark.org/
-[The New Dynamic]: https://www.thenewdynamic.com/
diff --git a/docs/content/en/getting-started/usage.md b/docs/content/en/getting-started/usage.md
deleted file mode 100644
index d6bc42550..000000000
--- a/docs/content/en/getting-started/usage.md
+++ /dev/null
@@ -1,166 +0,0 @@
----
-title: Basic usage
-description: Use the command-line interface (CLI) to perform basic tasks.
-categories: []
-keywords: []
-weight: 20
-aliases: [/overview/usage/,/extras/livereload/,/doc/usage/,/usage/]
----
-
-## Test your installation
-
-After [installing] Hugo, test your installation by running:
-
-```sh
-hugo version
-```
-
-You should see something like:
-
-```text
-hugo v0.123.0-3c8a4713908e48e6523f058ca126710397aa4ed5+extended linux/amd64 BuildDate=2024-02-19T16:32:38Z VendorInfo=gohugoio
-```
-
-## Display available commands
-
-To see a list of the available commands and flags:
-
-```sh
-hugo help
-```
-
-To get help with a subcommand, use the `--help` flag. For example:
-
-```sh
-hugo server --help
-```
-
-## Build your site
-
-To build your site, `cd` into your project directory and run:
-
-```sh
-hugo
-```
-
-The [`hugo`] command builds your site, publishing the files to the `public` directory. To publish your site to a different directory, use the [`--destination`] flag or set [`publishDir`] in your site configuration.
-
-> [!note]
-> Hugo does not clear the `public` directory before building your site. Existing files are overwritten, but not deleted. This behavior is intentional to prevent the inadvertent removal of files that you may have added to the `public` directory after the build.
->
-> Depending on your needs, you may wish to manually clear the contents of the `public` directory before every build.
-
-## Draft, future, and expired content
-
-Hugo allows you to set `draft`, `date`, `publishDate`, and `expiryDate` in the [front matter] of your content. By default, Hugo will not publish content when:
-
-- The `draft` value is `true`
-- The `date` is in the future
-- The `publishDate` is in the future
-- The `expiryDate` is in the past
-
-{{< new-in 0.123.0 />}}
-
-> [!note]
-> Hugo publishes descendants of draft, future, and expired [node](g) pages. To prevent publication of these descendants, use the [`cascade`] front matter field to cascade [build options] to the descendant pages.
-
-You can override the default behavior when running `hugo` or `hugo server` with command line flags:
-
-```sh
-hugo --buildDrafts # or -D
-hugo --buildExpired # or -E
-hugo --buildFuture # or -F
-```
-
-Although you can also set these values in your site configuration, it can lead to unwanted results unless all content authors are aware of, and understand, the settings.
-
-> [!note]
-> As noted above, Hugo does not clear the `public` directory before building your site. Depending on the _current_ evaluation of the four conditions above, after the build your `public` directory may contain extraneous files from a previous build.
->
-> A common practice is to manually clear the contents of the `public` directory before each build to remove draft, expired, and future content.
-
-## Develop and test your site
-
-To view your site while developing layouts or creating content, `cd` into your project directory and run:
-
-```sh
-hugo server
-```
-
-The [`hugo server`] command builds your site and serves your pages using a minimal HTTP server. When you run `hugo server` it will display the URL of your local site:
-
-```text
-Web Server is available at http://localhost:1313/
-```
-
-While the server is running, it watches your project directory for changes to assets, configuration, content, data, layouts, translations, and static files. When it detects a change, the server rebuilds your site and refreshes your browser using [LiveReload].
-
-Most Hugo builds are so fast that you may not notice the change unless you are looking directly at your browser.
-
-### LiveReload
-
-While the server is running, Hugo injects JavaScript into the generated HTML pages. The LiveReload script creates a connection from the browser to the server via web sockets. You do not need to install any software or browser plugins, nor is any configuration required.
-
-### Automatic redirection
-
-When editing content, if you want your browser to automatically redirect to the page you last modified, run:
-
-```sh
-hugo server --navigateToChanged
-```
-
-## Deploy your site
-
-> [!note]
-> As noted above, Hugo does not clear the `public` directory before building your site. Manually clear the contents of the `public` directory before each build to remove draft, expired, and future content.
-
-When you are ready to deploy your site, run:
-
-```sh
-hugo
-```
-
-This builds your site, publishing the files to the `public` directory. The directory structure will look something like this:
-
-```text
-public/
-├── categories/
-│ ├── index.html
-│ └── index.xml <-- RSS feed for this section
-├── posts/
-│ ├── my-first-post/
-│ │ └── index.html
-│ ├── index.html
-│ └── index.xml <-- RSS feed for this section
-├── tags/
-│ ├── index.html
-│ └── index.xml <-- RSS feed for this section
-├── index.html
-├── index.xml <-- RSS feed for the site
-└── sitemap.xml
-```
-
-In a simple hosting environment, where you typically `ftp`, `rsync`, or `scp` your files to the root of a virtual host, the contents of the `public` directory are all that you need.
-
-Most of our users deploy their sites using a [CI/CD](g) workflow, where a push[^1] to their GitHub or GitLab repository triggers a build and deployment. Popular providers include [AWS Amplify], [CloudCannon], [Cloudflare Pages], [GitHub Pages], [GitLab Pages], and [Netlify].
-
-Learn more in the [host and deploy] section.
-
-[^1]: The Git repository contains the entire project directory, typically excluding the `public` directory because the site is built _after_ the push.
-
-[`--destination`]: /commands/hugo/#options
-[`cascade`]: /content-management/front-matter/#cascade
-[`hugo server`]: /commands/hugo_server/
-[`hugo`]: /commands/hugo/
-[`publishDir`]: /configuration/all/#publishdir
-[AWS Amplify]: https://aws.amazon.com/amplify/
-[build options]: /content-management/build-options/
-[CloudCannon]: https://cloudcannon.com/
-[Cloudflare Pages]: https://pages.cloudflare.com/
-[front matter]: /content-management/front-matter/
-[GitHub Pages]: https://pages.github.com/
-[GitLab Pages]: https://docs.gitlab.com/ee/user/project/pages/
-[host and deploy]: /host-and-deploy/
-[installing]: /installation/
-[LiveReload]: https://github.com/livereload/livereload-js
-[Netlify]: https://www.netlify.com/
diff --git a/docs/content/en/host-and-deploy/_index.md b/docs/content/en/host-and-deploy/_index.md
deleted file mode 100644
index 627f12c36..000000000
--- a/docs/content/en/host-and-deploy/_index.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: Host and deploy
-description: Services and tools to host and deploy your site.
-categories: []
-keywords: []
-weight: 10
-aliases: [/hosting-and-deployment/]
----
diff --git a/docs/content/en/host-and-deploy/deploy-with-hugo-deploy.md b/docs/content/en/host-and-deploy/deploy-with-hugo-deploy.md
deleted file mode 100644
index 8feeccbae..000000000
--- a/docs/content/en/host-and-deploy/deploy-with-hugo-deploy.md
+++ /dev/null
@@ -1,97 +0,0 @@
----
-title: Deploy with hugo
-description: Deploy your site with the hugo CLI.
-categories: []
-keywords: []
-aliases: [/hosting-and-deployment/hugo-deploy/]
----
-
-Use the `hugo deploy` command to deploy your site Amazon S3, Azure Blob Storage, or Google Cloud Storage.
-
-> [!note]
-> This feature requires the Hugo extended/deploy edition. See the [installation] section for details.
-
-## Assumptions
-
-1. You have completed the [Quick Start] or have a Hugo website you are ready to deploy and share with the world.
-1. You have an account with the service provider ([AWS], [Azure], or [Google Cloud]) that you want to deploy to.
-1. You have authenticated.
- - AWS: [Install the CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html) and run [`aws configure`](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html).
- - Azure: [Install the CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) and run [`az login`](https://docs.microsoft.com/en-us/cli/azure/authenticate-azure-cli).
- - Google Cloud: [Install the CLI](https://cloud.google.com/sdk) and run [`gcloud auth login`](https://cloud.google.com/sdk/gcloud/reference/auth/login).
-
- Each service supports various authentication methods, including environment variables. See [details](https://gocloud.dev/howto/blob/#services).
-
-1. You have created a bucket to deploy to. If you want your site to be
- public, be sure to configure the bucket to be publicly readable as a static website.
- - AWS: [create a bucket](https://docs.aws.amazon.com/AmazonS3/latest/gsg/CreatingABucket.html) and [host a static website](https://docs.aws.amazon.com/AmazonS3/latest/userguide/WebsiteHosting.html)
- - Azure: [create a storage container](https://docs.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-portal) and [host a static website](https://learn.microsoft.com/en-us/azure/storage/blobs/storage-blob-static-website)
-
- - Google Cloud: [create a bucket](https://cloud.google.com/storage/docs/creating-buckets) and [host a static website](https://cloud.google.com/storage/docs/hosting-static-website)
-
-## Configuration
-
-Create a deployment target in your [site configuration]. The only required parameters are [`name`] and [`url`]:
-
-{{< code-toggle file=hugo >}}
-[deployment]
- [[deployment.targets]]
- name = 'production'
- url = 's3://my_bucket?region=us-west-1'
-{{< /code-toggle >}}
-
-## Deploy
-
-To deploy to a target:
-
-```bash
-hugo deploy [--target=]
-```
-
-This command syncs the contents of your local `public` directory (the default publish directory) with the destination bucket. If no target is specified, Hugo deploys to the first configured target.
-
-For more command-line options, see `hugo help deploy` or the [CLI documentation].
-
-### File list creation
-
-`hugo deploy` creates local and remote file lists by traversing the local publish directory and the remote bucket. Inclusion and exclusion are determined by the deployment target's [configuration]:
-
-- `include`: All files are skipped by default except those that match the pattern.
-- `exclude`: Files matching the pattern are skipped.
-
-> [!note]
-> During local file list creation, Hugo skips `.DS_Store` files and hidden directories (those starting with a period, like `.git`), except for the [`.well-known`] directory, which is traversed if present.
-
-### File list comparison
-
-Hugo compares the local and remote file lists to identify necessary changes. It first compares file names. If both exist, it compares sizes and MD5 checksums. Any difference triggers a re-upload, and remote files not present locally are deleted.
-
-> [!note]
-> Excluded remote files (due to `include`/`exclude` configuration) won't be deleted.
-
-The `--force` flag forces all files to be re-uploaded, even if Hugo detects no local/remote differences.
-
-The `--confirm` or `--dryRun` flags cause Hugo to display the detected differences and then pause or stop.
-
-### Synchronization
-
-Hugo applies the changes to the remote bucket: uploading missing or changed files and deleting remote files not present locally. Uploaded file headers are configured remotely based on the matchers configuration.
-
-> [!note]
-> To prevent accidental data loss, Hugo will not delete more than 256 remote files by default. Use the `--maxDeletes` flag to override this limit.
-
-## Advanced configuration
-
-See [configure deployment](/configuration/deployment/).
-
-[`.well-known`]: https://en.wikipedia.org/wiki/Well-known_URI
-[`name`]: /configuration/deployment/#name
-[`url`]: /configuration/deployment/#url
-[AWS]: https://aws.amazon.com
-[Azure]: https://azure.microsoft.com
-[CLI documentation]: /commands/hugo_deploy/
-[configuration]: /configuration/deployment/#targets-1
-[Google Cloud]: https://cloud.google.com/
-[installation]: /installation/
-[Quick Start]: /getting-started/quick-start/
-[site configuration]: /configuration/deployment/
diff --git a/docs/content/en/host-and-deploy/deploy-with-rclone.md b/docs/content/en/host-and-deploy/deploy-with-rclone.md
deleted file mode 100644
index 8f641bc5d..000000000
--- a/docs/content/en/host-and-deploy/deploy-with-rclone.md
+++ /dev/null
@@ -1,43 +0,0 @@
----
-title: Deploy with rclone
-description: Deploy your site with the rclone CLI.
-categories: []
-keywords: []
-aliases: [/hosting-and-deployment/deployment-with-rclone/]
----
-
-## Assumptions
-
-- A web host running a web server. This could be a shared hosting environment or a VPS.
-- Access to your web host with any of the [protocols supported by rclone](https://rclone.org/#providers), such as SFTP.
-- A functional static website built with Hugo
-- Deploying from an [Rclone](https://rclone.org) compatible operating system
-- You have [installed Rclone](https://rclone.org/install/).
-
-**NB**: You can remove ``--interactive`` in the commands below once you are comfortable with rclone, if you wish. Also, ``--gc`` and ``--minify`` are optional in the ``hugo`` commands below.
-
-## Getting started
-
-The spoiler is that you can even deploy your entire website from any compatible OS with no configuration. Using SFTP for example:
-
-```txt
-hugo --gc --minify
-rclone sync --interactive --sftp-host sftp.example.com --sftp-user www-data --sftp-ask-password public/ :sftp:www/
-```
-
-## Configure Rclone for even easier usage
-
-The easiest way is simply to run `rclone config`.
-
-The [Rclone docs](https://rclone.org/docs/) provide [an example of configuring Rclone to use SFTP](https://rclone.org/sftp/).
-
-For the next commands, we will assume you configured a remote you named ``hugo-www``
-
-The above 'spoiler' commands could become:
-
-```txt
-hugo --gc --minify
-rclone sync --interactive public/ hugo-www:www/
-```
-
-After you issue the above commands (and respond to any prompts), check your website and you will see that it is deployed.
diff --git a/docs/content/en/host-and-deploy/deploy-with-rsync.md b/docs/content/en/host-and-deploy/deploy-with-rsync.md
deleted file mode 100644
index d073107fe..000000000
--- a/docs/content/en/host-and-deploy/deploy-with-rsync.md
+++ /dev/null
@@ -1,135 +0,0 @@
----
-title: Deploy with rsync
-description: Deploy your site with the rsync CLI.
-categories: []
-keywords: []
-aliases: [/hosting-and-deployment/deployment-with-rsync/]
----
-
-## Assumptions
-
-- A web host running a web server. This could be a shared hosting environment or a VPS.
-- Access to your web host with SSH
-- A functional static website built with Hugo
-
-The spoiler is that you can deploy your entire website with a command that looks like the following:
-
-```txt
-hugo && rsync -avz --delete public/ www-data@ftp.topologix.fr:~/www/
-```
-
-As you will see, we'll put this command in a shell script file, which makes building and deployment as easy as executing `./deploy`.
-
-## Copy Your SSH Key to your host
-
-To make logging in to your server more secure and less interactive, you can upload your SSH key. If you have already installed your SSH key to your server, you can move on to the next section.
-
-First, install the ssh client. On Debian distributions, use the following command:
-
-```sh {file="install-openssh.sh"}
-sudo apt-get install openssh-client
-```
-
-Then generate your ssh key. First, create the `.ssh` directory in your home directory if it doesn't exist:
-
-```txt
-~$ cd && mkdir .ssh & cd .ssh
-```
-
-Next, execute this command to generate a new keypair called `rsa_id`:
-
-```txt
-~/.ssh/$ ssh-keygen -t rsa -q -C "For SSH" -f rsa_id
-```
-
-You'll be prompted for a passphrase, which is an extra layer of protection. Enter the passphrase you'd like to use, and then enter it again when prompted, or leave it blank if you don't want to have a passphrase. Not using a passphrase will let you transfer files non-interactively, as you won't be prompted for a password when you log in, but it is slightly less secure.
-
-To make logging in easier, add a definition for your web host to the file `~/.ssh/config` with the following command, replacing `HOST` with the IP address or hostname of your web host, and `USER` with the username you use to log in to your web host when transferring files:
-
-```txt
-~/.ssh/$ cat >> config <
- | Field | Value |
- | ----------------- | ------------------------------------------------ |
- | Environment | `Static Site` |
- | Build Command | `hugo --gc --minify` (or your own build command) |
- | Publish Directory | `./public` (or your own output directory) |
-
-That's it! Your site will be live on your 21YunBox URL (which looks like `yoursite.21yunbox.com`) as soon as the build is done.
-
-## Continuous deploys
-
-Now that 21YunBox is connected to your repo, it will automatically build and publish your site any time you push to GitHub.
-
-Every deploy automatically and instantly invalidates the CDN cache, so your users can always access the latest content on your site.
-
-## Custom domains
-
-Add your own domains to your site easily using 21YunBox's [custom domains](https://www.21cloudbox.com/dns-configuration.html) guide.
-
-## Support
-
-Click [here](https://www.21cloudbox.com/contact.html) to contact with 21YunBox' experts if you need help.
diff --git a/docs/content/en/host-and-deploy/host-on-aws-amplify/amplify-step-05.png b/docs/content/en/host-and-deploy/host-on-aws-amplify/amplify-step-05.png
deleted file mode 100644
index bb98d974a..000000000
Binary files a/docs/content/en/host-and-deploy/host-on-aws-amplify/amplify-step-05.png and /dev/null differ
diff --git a/docs/content/en/host-and-deploy/host-on-aws-amplify/amplify-step-06.png b/docs/content/en/host-and-deploy/host-on-aws-amplify/amplify-step-06.png
deleted file mode 100644
index 2e9b96e2b..000000000
Binary files a/docs/content/en/host-and-deploy/host-on-aws-amplify/amplify-step-06.png and /dev/null differ
diff --git a/docs/content/en/host-and-deploy/host-on-aws-amplify/amplify-step-07.png b/docs/content/en/host-and-deploy/host-on-aws-amplify/amplify-step-07.png
deleted file mode 100644
index b3260157b..000000000
Binary files a/docs/content/en/host-and-deploy/host-on-aws-amplify/amplify-step-07.png and /dev/null differ
diff --git a/docs/content/en/host-and-deploy/host-on-aws-amplify/amplify-step-08.png b/docs/content/en/host-and-deploy/host-on-aws-amplify/amplify-step-08.png
deleted file mode 100644
index 55e80e710..000000000
Binary files a/docs/content/en/host-and-deploy/host-on-aws-amplify/amplify-step-08.png and /dev/null differ
diff --git a/docs/content/en/host-and-deploy/host-on-aws-amplify/amplify-step-09.png b/docs/content/en/host-and-deploy/host-on-aws-amplify/amplify-step-09.png
deleted file mode 100644
index b422e3ad6..000000000
Binary files a/docs/content/en/host-and-deploy/host-on-aws-amplify/amplify-step-09.png and /dev/null differ
diff --git a/docs/content/en/host-and-deploy/host-on-aws-amplify/amplify-step-11.png b/docs/content/en/host-and-deploy/host-on-aws-amplify/amplify-step-11.png
deleted file mode 100644
index e147edfb9..000000000
Binary files a/docs/content/en/host-and-deploy/host-on-aws-amplify/amplify-step-11.png and /dev/null differ
diff --git a/docs/content/en/host-and-deploy/host-on-aws-amplify/index.md b/docs/content/en/host-and-deploy/host-on-aws-amplify/index.md
deleted file mode 100644
index aadb89116..000000000
--- a/docs/content/en/host-and-deploy/host-on-aws-amplify/index.md
+++ /dev/null
@@ -1,160 +0,0 @@
----
-title: Host on AWS Amplify
-description: Host your site on AWS Amplify.
-categories: []
-keywords: []
-aliases: [/hosting-and-deployment/hosting-on-aws-amplify/]
----
-
-## Prerequisites
-
-Please complete the following tasks before continuing:
-
-1. [Create an AWS account]
-1. [Install Git]
-1. [Create a Hugo site] and test it locally with `hugo server`
-1. Commit the changes to your local repository
-1. Push the local repository to your [GitHub], [GitLab], or [Bitbucket] account
-
-[Bitbucket]: https://bitbucket.org/product
-[Create a Hugo site]: /getting-started/quick-start/
-[Create an AWS account]: https://aws.amazon.com/resources/create-account/
-[GitHub]: https://github.com
-[GitLab]: https://about.gitlab.com/
-[Install Git]: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git
-
-## Procedure
-
-This procedure will enable continuous deployment from a GitHub repository. The procedure is essentially the same if you are using GitLab or Bitbucket.
-
-### Step 1
-
-Create a file named `amplify.yml` in the root of your project.
-
-```sh
-touch amplify.yml
-```
-
-### Step 2
-
-Copy and paste the YAML below into the file you created. Change the application versions and time zone as needed.
-
-```yaml {file="amplify.yml" copy=true}
-version: 1
-env:
- variables:
- # Application versions
- DART_SASS_VERSION: 1.85.0
- GO_VERSION: 1.23.3
- HUGO_VERSION: 0.144.2
- # Time zone
- TZ: America/Los_Angeles
- # Cache
- HUGO_CACHEDIR: ${PWD}/.hugo
- NPM_CONFIG_CACHE: ${PWD}/.npm
-frontend:
- phases:
- preBuild:
- commands:
- # Install Dart Sass
- - curl -LJO https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz
- - sudo tar -C /usr/local/bin -xf dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz
- - rm dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz
- - export PATH=/usr/local/bin/dart-sass:$PATH
-
- # Install Go
- - curl -LJO https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz
- - sudo tar -C /usr/local -xf go${GO_VERSION}.linux-amd64.tar.gz
- - rm go${GO_VERSION}.linux-amd64.tar.gz
- - export PATH=/usr/local/go/bin:$PATH
-
- # Install Hugo
- - curl -LJO https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz
- - sudo tar -C /usr/local/bin -xf hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz
- - rm hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz
- - export PATH=/usr/local/bin:$PATH
-
- # Check installed versions
- - go version
- - hugo version
- - node -v
- - npm -v
- - sass --embedded --version
-
- # Install Node.JS dependencies
- - "[[ -f package-lock.json || -f npm-shrinkwrap.json ]] && npm ci --prefer-offline || true"
-
- # https://github.com/gohugoio/hugo/issues/9810
- - git config --add core.quotepath false
- build:
- commands:
- - hugo --gc --minify
- artifacts:
- baseDirectory: public
- files:
- - '**/*'
- cache:
- paths:
- - ${HUGO_CACHEDIR}/**/*
- - ${NPM_CONFIG_CACHE}/**/*
-```
-
-### Step 3
-
-Commit and push the change to your GitHub repository.
-
-```sh
-git add -A
-git commit -m "Create amplify.yml"
-git push
-```
-
-### Step 4
-
-Log in to your AWS account, navigate to the [Amplify Console], then press the **Deploy an app** button.
-
-[Amplify Console]: https://console.aws.amazon.com/amplify/apps
-
-### Step 5
-
-Choose a source code provider, then press the **Next** button.
-
- 
-
-### Step 6
-
-Authorize AWS Amplify to access your GitHub account.
-
- 
-
-### Step 7
-
-Select your personal account or relevant organization.
-
- 
-
-### Step 8
-
-Authorize access to one or more repositories.
-
- 
-
-### Step 9
-
-Select a repository and branch, then press the **Next** button.
-
- 
-
-### Step 10
-
-On the "App settings" page, scroll to the bottom then press the **Next** button. Amplify reads the `amplify.yml` file you created in Steps 1-3 instead of using the values on this page.
-
-### Step 11
-
-On the "Review" page, scroll to the bottom then press the **Save and deploy** button.
-
-### Step 12
-
-When your site has finished deploying, press the **Visit deployed URL** button to view your published site.
-
- 
diff --git a/docs/content/en/host-and-deploy/host-on-azure-static-web-apps.md b/docs/content/en/host-and-deploy/host-on-azure-static-web-apps.md
deleted file mode 100644
index 68fe145ab..000000000
--- a/docs/content/en/host-and-deploy/host-on-azure-static-web-apps.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-title: Host on Azure Static Web Apps
-description: Host your site on Azure Static Web Apps.
-categories: []
-keywords: []
-aliases: [/hosting-and-deployment/hosting-on-azure-static-web-apps/]
----
-
-You can create and deploy a Hugo web application to Azure Static Web Apps. The final result is a new Azure Static Web App with associated GitHub Actions that give you control over how the app is built and published. You'll learn how to create a Hugo app, set up an Azure Static Web App and deploy the Hugo app to Azure.
-
-Here's the tutorial on how to [Publish a Hugo site to Azure Static Web Apps](https://docs.microsoft.com/en-us/azure/static-web-apps/publish-hugo).
diff --git a/docs/content/en/host-and-deploy/host-on-cloudflare-pages.md b/docs/content/en/host-and-deploy/host-on-cloudflare-pages.md
deleted file mode 100644
index 1c3627288..000000000
--- a/docs/content/en/host-and-deploy/host-on-cloudflare-pages.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-title: Host on Cloudflare Pages
-description: Host your site on Cloudflare Pages.
-categories: []
-keywords: []
-aliases: [/hosting-and-deployment/hosting-on-cloudflare-pages/]
----
-
-[Cloudflare Pages](https://developers.cloudflare.com/pages/) are super fast, always up-to-date, and deployed directly from your [Git provider](https://developers.cloudflare.com/pages/get-started/#connect-your-git-provider-to-pages).
-
-Cloudflare Pages docs have a detailed tutorial on [how to deploy a Hugo site](https://developers.cloudflare.com/pages/framework-guides/deploy-a-hugo-site/).
diff --git a/docs/content/en/host-and-deploy/host-on-codeberg-pages.md b/docs/content/en/host-and-deploy/host-on-codeberg-pages.md
deleted file mode 100644
index cd137c420..000000000
--- a/docs/content/en/host-and-deploy/host-on-codeberg-pages.md
+++ /dev/null
@@ -1,82 +0,0 @@
----
-title: Host on Codeberg Pages
-description: Host your site on Codeberg Pages.
-categories: []
-keywords: []
-aliases: [/hosting-and-deployment/hosting-on-codeberg/]
----
-
-## Assumptions
-
-- Working familiarity with [Git] for version control
-- Completion of the Hugo [Quick Start]
-- A [Codeberg account]
-- A Hugo website on your local machine that you are ready to publish
-
-[Codeberg account]: https://codeberg.org/user/login/
-[Git]: https://git-scm.com/
-[Quick Start]: /getting-started/quick-start/
-
-Any and all mentions of `` refer to your actual Codeberg username and must be substituted accordingly. Likewise, `` represents your actual website name.
-
-## BaseURL
-
-The [`baseURL`] in your site configuration must reflect the full URL provided by Codeberg Pages if using the default address (e.g. `https://.codeberg.page/`). If you want to use another domain, follow the instructions in the [custom domain section] of the official documentation.
-
-[`baseURL`]: /configuration/all/#baseurl
-[custom domain section]: https://docs.codeberg.org/codeberg-pages/using-custom-domain/
-
-For more details regarding the URL of your deployed website, refer to Codeberg Pages' [quickstart instructions].
-
-[quickstart instructions]: https://codeberg.page/
-
-## Manual deployment
-
-Create a public repository on your Codeberg account titled `pages` or create a branch of the same name in an existing public repository. Finally, push the contents of Hugo's output directory (by default, `public`) to it. Here's an example:
-
-```sh
-# build the website
-hugo
-
-# access the output directory
-cd public
-
-# initialize new git repository
-git init
-
-# commit and push code to main branch
-git add .
-git commit -m "Initial commit"
-git remote add origin https://codeberg.org//pages.git
-git push -u origin main
-```
-
-## Automated deployment
-
-In order to automatically deploy your Hugo website, you need to have or [request] access to Codeberg's CI, as well as add a `.woodpecker.yaml` file in the root of your project. A template and additional instructions are available in the official [examples repository].
-
-[request]: https://codeberg.org/Codeberg-e.V./requests/issues/new?template=ISSUE_TEMPLATE%2fWoodpecker-CI.yaml
-[examples repository]: https://codeberg.org/Codeberg-CI/examples/src/branch/main/Hugo/.woodpecker.yaml
-
-In this case, you must create a public repository on Codeberg (e.g. ``) and push your local project to it. Here's an example:
-
-```sh
-# initialize new git repository
-git init
-
-# add /public directory to our .gitignore file
-echo "/public" >> .gitignore
-
-# commit and push code to main branch
-git add .
-git commit -m "Initial commit"
-git remote add origin https://codeberg.org//.git
-git push -u origin main
-```
-
-Your project will then be built and deployed by Codeberg's CI.
-
-## Other resources
-
-- [Codeberg Pages](https://codeberg.page/)
-- [Codeberg Pages official documentation](https://docs.codeberg.org/codeberg-pages/)
diff --git a/docs/content/en/host-and-deploy/host-on-firebase.md b/docs/content/en/host-and-deploy/host-on-firebase.md
deleted file mode 100644
index 267c8d127..000000000
--- a/docs/content/en/host-and-deploy/host-on-firebase.md
+++ /dev/null
@@ -1,106 +0,0 @@
----
-title: Host on Firebase
-description: Host your site on Firebase.
-categories: []
-keywords: []
-aliases: [/hosting-and-deployment/hosting-on-firebase/]
----
-
-## Assumptions
-
-1. You have an account with [Firebase][signup]. (If you don't, you can sign up for free using your Google account.)
-1. You have completed the [Quick Start] or have a completed Hugo website ready for deployment.
-
-## Initial setup
-
-Go to the [Firebase console][console] and create a new project (unless you already have a project). You will need to globally install `firebase-tools` (node.js):
-
-```sh
-npm install -g firebase-tools
-```
-
-Log in to Firebase (setup on your local machine) using `firebase login`, which opens a browser where you can select your account. Use `firebase logout` in case you are already logged in but to the wrong account.
-
-```sh
-firebase login
-```
-
-In the root of your Hugo project, initialize the Firebase project with the `firebase init` command:
-
-```sh
-firebase init
-```
-
-From here:
-
-1. Choose Hosting in the feature question
-1. Choose the project you just set up
-1. Accept the default for your database rules file
-1. Accept the default for the publish directory, which is `public`
-1. Choose "No" in the question if you are deploying a single-page app
-
-## Using Firebase & GitHub CI/CD
-
-In new versions of Firebase, some other questions apply:
-
-6. Set up automatic builds and deploys with GitHub?
-
-Here you will be redirected to login in your GitHub account to get permissions. Confirm.
-
-7. For which GitHub repository would you like to set up a GitHub workflow? (format: user/repository)
-
-Include the repository you will use in the format above (Account/Repo)
-Firebase script with retrieve credentials, create a service account you can later manage in your GitHub settings.
-
-8. Set up the workflow to run a build script before every deploy?
-
-Here is your opportunity to include some commands before you run the deploy.
-
-9. Set up automatic deployment to your site's live channel when a PR is merged?
-
-You can let in the default option (main)
-
-After that Firebase has been set in your project with [CI/CD](g). After that run:
-
-```sh
-hugo && firebase deploy
-```
-
-With this you will have the app initialized manually. After that you can manage and fix your GitHub workflow from: https://github.com/your-account/your-repo/actions
-
-Don't forget to update your static pages before push!
-
-## Manual deploy
-
-To deploy your Hugo site, execute the `firebase deploy` command, and your site will be up in no time:
-
-```sh
-hugo && firebase deploy
-```
-
-## CI setup (other tools)
-
-You can generate a deploy token using
-
-```sh
-firebase login:ci
-```
-
-You can also set up your CI and add the token to a private variable like `$FIREBASE_DEPLOY_TOKEN`.
-
-> [!note]
-> This is a private secret and it should not appear in a public repository. Make sure you understand your chosen CI and that it's not visible to others.
-
-You can then add a step in your build to do the deployment using the token:
-
-```sh
-firebase deploy --token $FIREBASE_DEPLOY_TOKEN
-```
-
-## Reference links
-
-- [Firebase CLI Reference](https://firebase.google.com/docs/cli/#administrative_commands)
-
-[console]: https://console.firebase.google.com/
-[Quick Start]: /getting-started/quick-start/
-[signup]: https://console.firebase.google.com/
diff --git a/docs/content/en/host-and-deploy/host-on-github-pages/gh-pages-1.png b/docs/content/en/host-and-deploy/host-on-github-pages/gh-pages-1.png
deleted file mode 100644
index 29912f25c..000000000
Binary files a/docs/content/en/host-and-deploy/host-on-github-pages/gh-pages-1.png and /dev/null differ
diff --git a/docs/content/en/host-and-deploy/host-on-github-pages/gh-pages-2.png b/docs/content/en/host-and-deploy/host-on-github-pages/gh-pages-2.png
deleted file mode 100644
index 0050d33e2..000000000
Binary files a/docs/content/en/host-and-deploy/host-on-github-pages/gh-pages-2.png and /dev/null differ
diff --git a/docs/content/en/host-and-deploy/host-on-github-pages/gh-pages-3.png b/docs/content/en/host-and-deploy/host-on-github-pages/gh-pages-3.png
deleted file mode 100644
index d2904cae1..000000000
Binary files a/docs/content/en/host-and-deploy/host-on-github-pages/gh-pages-3.png and /dev/null differ
diff --git a/docs/content/en/host-and-deploy/host-on-github-pages/gh-pages-4.png b/docs/content/en/host-and-deploy/host-on-github-pages/gh-pages-4.png
deleted file mode 100644
index 75774462b..000000000
Binary files a/docs/content/en/host-and-deploy/host-on-github-pages/gh-pages-4.png and /dev/null differ
diff --git a/docs/content/en/host-and-deploy/host-on-github-pages/gh-pages-5.png b/docs/content/en/host-and-deploy/host-on-github-pages/gh-pages-5.png
deleted file mode 100644
index efe26129a..000000000
Binary files a/docs/content/en/host-and-deploy/host-on-github-pages/gh-pages-5.png and /dev/null differ
diff --git a/docs/content/en/host-and-deploy/host-on-github-pages/index.md b/docs/content/en/host-and-deploy/host-on-github-pages/index.md
deleted file mode 100644
index 7c3201099..000000000
--- a/docs/content/en/host-and-deploy/host-on-github-pages/index.md
+++ /dev/null
@@ -1,232 +0,0 @@
----
-title: Host on GitHub Pages
-description: Host your site on GitHub Pages.
-categories: []
-keywords: []
-aliases: [/hosting-and-deployment/hosting-on-github/]
----
-
-## Prerequisites
-
-Please complete the following tasks before continuing:
-
-1. [Create a GitHub account]
-1. [Install Git]
-1. [Create a Hugo site] and test it locally with `hugo server`.
-
-## Types of sites
-
-There are three types of GitHub Pages sites: project, user, and organization. Project sites are connected to a specific project hosted on GitHub. User and organization sites are connected to a specific account on GitHub.com.
-
-> [!note]
-> See the [GitHub Pages documentation] to understand the requirements for repository ownership and naming.
-
-## Procedure
-
-### Step 1
-
-Create a GitHub repository.
-
-### Step 2
-
-Push your local repository to GitHub.
-
-### Step 3
-
-Visit your GitHub repository. From the main menu choose **Settings** > **Pages**. In the center of your screen you will see this:
-
-
-{style="max-width: 280px"}
-
-### Step 4
-
-Change the **Source** to `GitHub Actions`. The change is immediate; you do not have to press a Save button.
-
-
-{style="max-width: 280px"}
-
-### Step 5
-
-In your site configuration, change the location of the image cache to the [`cacheDir`] as shown below:
-
-{{< code-toggle file=hugo >}}
-[caches.images]
-dir = ":cacheDir/images"
-{{< /code-toggle >}}
-
-See [configure file caches] for more information.
-
-### Step 6
-
-Create a file named `hugo.yaml` in a directory named `.github/workflows`.
-
-```text
-mkdir -p .github/workflows
-touch .github/workflows/hugo.yaml
-```
-
-### Step 7
-
-> [!note]
-> The workflow below ensures Hugo's `cacheDir` is persistent, preserving modules, processed images, and [`resources.GetRemote`] data between builds.
-
-Copy and paste the YAML below into the file you created. Change the branch name and Hugo version as needed.
-
-```yaml {file=".github/workflows/hugo.yaml" copy=true}
-# Sample workflow for building and deploying a Hugo site to GitHub Pages
-name: Deploy Hugo site to Pages
-
-on:
- # Runs on pushes targeting the default branch
- push:
- branches:
- - main
-
- # Allows you to run this workflow manually from the Actions tab
- workflow_dispatch:
-
-# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
-permissions:
- contents: read
- pages: write
- id-token: write
-
-# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
-# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
-concurrency:
- group: "pages"
- cancel-in-progress: false
-
-# Default to bash
-defaults:
- run:
- shell: bash
-
-jobs:
- # Build job
- build:
- runs-on: ubuntu-latest
- env:
- HUGO_VERSION: 0.145.0
- HUGO_ENVIRONMENT: production
- TZ: America/Los_Angeles
- steps:
- - name: Install Hugo CLI
- run: |
- wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \
- && sudo dpkg -i ${{ runner.temp }}/hugo.deb
- - name: Install Dart Sass
- run: sudo snap install dart-sass
- - name: Checkout
- uses: actions/checkout@v4
- with:
- submodules: recursive
- fetch-depth: 0
- - name: Setup Pages
- id: pages
- uses: actions/configure-pages@v5
- - name: Install Node.js dependencies
- run: "[[ -f package-lock.json || -f npm-shrinkwrap.json ]] && npm ci || true"
- - name: Cache Restore
- id: cache-restore
- uses: actions/cache/restore@v4
- with:
- path: |
- ${{ runner.temp }}/hugo_cache
- key: hugo-${{ github.run_id }}
- restore-keys:
- hugo-
- - name: Configure Git
- run: git config core.quotepath false
- - name: Build with Hugo
- run: |
- hugo \
- --gc \
- --minify \
- --baseURL "${{ steps.pages.outputs.base_url }}/" \
- --cacheDir "${{ runner.temp }}/hugo_cache"
- - name: Cache Save
- id: cache-save
- uses: actions/cache/save@v4
- with:
- path: |
- ${{ runner.temp }}/hugo_cache
- key: ${{ steps.cache-restore.outputs.cache-primary-key }}
- - name: Upload artifact
- uses: actions/upload-pages-artifact@v3
- with:
- path: ./public
-
- # Deployment job
- deploy:
- environment:
- name: github-pages
- url: ${{ steps.deployment.outputs.page_url }}
- runs-on: ubuntu-latest
- needs: build
- steps:
- - name: Deploy to GitHub Pages
- id: deployment
- uses: actions/deploy-pages@v4
-```
-
-### Step 8
-
-Commit and push the change to your GitHub repository.
-
-```sh
-git add -A
-git commit -m "Create hugo.yaml"
-git push
-```
-
-### Step 9
-
-From GitHub's main menu, choose **Actions**. You will see something like this:
-
-
-{style="max-width: 350px"}
-
-### Step 10
-
-When GitHub has finished building and deploying your site, the color of the status indicator will change to green.
-
-
-{style="max-width: 350px"}
-
-### Step 11
-
-Click on the commit message as shown above. You will see this:
-
-
-{style="max-width: 611px"}
-
-Under the deploy step, you will see a link to your live site.
-
-In the future, whenever you push a change from your local repository, GitHub will rebuild your site and deploy the changes.
-
-## Customize the workflow
-
-The example workflow above includes this step, which typically takes 10‑15 seconds:
-
-```yaml
-- name: Install Dart Sass
- run: sudo snap install dart-sass
-```
-
-You may remove this step if your site, themes, and modules do not transpile Sass to CSS using the [Dart Sass] transpiler.
-
-## Other resources
-
-- [Learn more about GitHub Actions](https://docs.github.com/en/actions)
-- [Caching dependencies to speed up workflows](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows)
-- [Manage a custom domain for your GitHub Pages site](https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/about-custom-domains-and-github-pages)
-
-[Create a GitHub account]: https://github.com/signup
-[Create a Hugo site]: /getting-started/quick-start/
-[Dart Sass]: /functions/css/sass/#dart-sass
-[GitHub Pages documentation]: https://docs.github.com/en/pages/getting-started-with-github-pages/about-github-pages#types-of-github-pages-sites
-[Install Git]: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git
-[`cacheDir`]: /configuration/all/#cachedir
-[`resources.GetRemote`]: /functions/resources/getremote/
-[configure file caches]: /configuration/caches/
diff --git a/docs/content/en/host-and-deploy/host-on-gitlab-pages.md b/docs/content/en/host-and-deploy/host-on-gitlab-pages.md
deleted file mode 100644
index 4750b0ff3..000000000
--- a/docs/content/en/host-and-deploy/host-on-gitlab-pages.md
+++ /dev/null
@@ -1,98 +0,0 @@
----
-title: Host on GitLab Pages
-description: Host your site on GitLab Pages.
-categories: []
-keywords: []
-aliases: [/hosting-and-deployment/hosting-on-gitlab/]
----
-
-## Assumptions
-
-- Working familiarity with Git for version control
-- Completion of the Hugo [Quick Start]
-- A [GitLab account](https://gitlab.com/users/sign_in)
-- A Hugo website on your local machine that you are ready to publish
-
-## BaseURL
-
-The `baseURL` in your [site configuration](/configuration/) must reflect the full URL of your GitLab pages repository if you are using the default GitLab Pages URL (e.g., `https://.gitlab.io//`) and not a custom domain.
-
-## Configure GitLab CI/CD
-
-Define your [CI/CD](g) jobs by creating a `.gitlab-ci.yml` file in the root of your project.
-
-```yaml {file=".gitlab-ci.yml" copy=true}
-variables:
- DART_SASS_VERSION: 1.87.0
- GIT_DEPTH: 0
- GIT_STRATEGY: clone
- GIT_SUBMODULE_STRATEGY: recursive
- HUGO_VERSION: 0.146.7
- NODE_VERSION: 22.x
- TZ: America/Los_Angeles
-image:
- name: golang:1.24.2-bookworm
-
-pages:
- script:
- # Install brotli
- - apt-get update
- - apt-get install -y brotli
- # Install Dart Sass
- - curl -LJO https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz
- - tar -xf dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz
- - cp -r dart-sass/ /usr/local/bin
- - rm -rf dart-sass*
- - export PATH=/usr/local/bin/dart-sass:$PATH
- # Install Hugo
- - curl -LJO https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb
- - apt-get install -y ./hugo_extended_${HUGO_VERSION}_linux-amd64.deb
- - rm hugo_extended_${HUGO_VERSION}_linux-amd64.deb
- # Install Node.js
- - curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION} | bash -
- - apt-get install -y nodejs
- # Install Node.js dependencies
- - "[[ -f package-lock.json || -f npm-shrinkwrap.json ]] && npm ci || true"
- # Configure Git
- - git config core.quotepath false
- # Build
- - hugo --gc --minify --baseURL ${CI_PAGES_URL}
- # Compress
- - find public -type f -regex '.*\.\(css\|html\|js\|txt\|xml\)$' -exec gzip -f -k {} \;
- - find public -type f -regex '.*\.\(css\|html\|js\|txt\|xml\)$' -exec brotli -f -k {} \;
- artifacts:
- paths:
- - public
- rules:
- - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
-```
-
-## Push your Hugo website to GitLab
-
-Next, create a new repository on GitLab. It is *not* necessary to make the repository public. In addition, you might want to add `/public` to your .gitignore file, as there is no need to push compiled assets to GitLab or keep your output website in version control.
-
-```sh
-# initialize new git repository
-git init
-
-# add /public directory to our .gitignore file
-echo "/public" >> .gitignore
-
-# commit and push code to master branch
-git add .
-git commit -m "Initial commit"
-git remote add origin https://gitlab.com/YourUsername/your-hugo-site.git
-git push -u origin master
-```
-
-## Wait for your page to build
-
-That's it! You can now follow the CI agent building your page at `https://gitlab.com///pipelines`.
-
-After the build has passed, your new website is available at `https://.gitlab.io//`.
-
-## Next steps
-
-GitLab supports using custom CNAME's and TLS certificates. For more details on GitLab Pages, see the [GitLab Pages setup documentation](https://about.gitlab.com/2016/04/07/gitlab-pages-setup/).
-
-[Quick Start]: /getting-started/quick-start/
diff --git a/docs/content/en/host-and-deploy/host-on-keycdn/index.md b/docs/content/en/host-and-deploy/host-on-keycdn/index.md
deleted file mode 100644
index 828e250c6..000000000
--- a/docs/content/en/host-and-deploy/host-on-keycdn/index.md
+++ /dev/null
@@ -1,86 +0,0 @@
----
-title: Host on KeyCDN
-description: Host your site on KeyCDN.
-categories: []
-keywords: []
-aliases: [/hosting-and-deployment/hosting-on-keycdn/]
----
-
-[KeyCDN](https://www.keycdn.com/) provides a multitude of features to help accelerate and secure your Hugo site globally including Brotli compression, Let's Encrypt support, Origin Shield, and more.
-
-## Assumptions
-
-- You already have a Hugo page configured
-- You have a GitLab account
-- You have a KeyCDN account
-
-## Create a KeyCDN Pull Zone
-
-The first step will be to log in to your KeyCDN account and create a new zone. Name this whatever you like and select the [Pull Zone](https://www.keycdn.com/support/create-a-pull-zone/) option. As for the origin URL, your site will be running on [GitLab Pages](https://docs.gitlab.com/ee/user/project/pages/getting_started_part_one.html) with a URL of `https://youruser.gitlab.io/reponame/`. Use this as the Origin URL.
-
-
-
-While the origin location doesn't exist yet, you will need to use your new Zone URL address (or [Zone Alias](https://www.keycdn.com/support/create-a-zone-alias/)) in the `.gitlab-ci.yml` file that will be uploaded to your GitLab project.
-
-Ensure that you use your Zone URL or Zone alias as the `BASEURL` variable in the example below. This will be the user-visible website address.
-
-## Configure Your .gitlab-ci.yml File
-
-Your `.gitlab-ci.yml` file should look similar to the example below. Be sure to modify any variables that are specific to your setup.
-
-```yml
-image: alpine:latest
-
-variables:
- BASEURL: "https://cipull-7bb7.kxcdn.com/"
- HUGO_VERSION: "0.26"
- HUGO_CHECKSUM: "67e4ba5ec2a02c8164b6846e30a17cc765b0165a5b183d5e480149baf54e1a50"
- KEYCDN_ZONE_ID: "75544"
-
-before_script:
- - apk update
- - apk add curl
-
-pages:
- stage: deploy
- script:
- - apk add git
- - git submodule update --init
- - curl -sSL https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_Linux-64bit.tar.gz -o /tmp/hugo.tar.gz
- - echo "${HUGO_CHECKSUM} /tmp/hugo.tar.gz" | sha256sum -c
- - tar xf /tmp/hugo.tar.gz hugo -C /tmp/ && cp /tmp/hugo /usr/bin
- - hugo --baseURL ${BASEURL}
- - curl "https://api.keycdn.com/zones/purge/${KEYCDN_ZONE_ID}.json" -u "${KEYCDN_API_KEY}:"
- artifacts:
- paths:
- - public
- only:
- - master
-```
-
-Using this integration method, you will have to specify the Zone ID and your [KeyCDN API](https://www.keycdn.com/api) key as secret variables. To do this, navigate to the top-left menu bar in GitLab and select Projects. Then, select your project and click on the Settings page. Finally, select Pipelines from the sub-menu and scroll down to the Secret Variable section.
-
-The Secret Variable for your Zone ID should look similar to:
-
-
-
-While the Secret Variable for your API Key will look similar to:
-
-
-
-While not strictly required, providing your Zone ID and API key is recommended for purging your zone. Without them, the CDN may continue serving outdated versions of your assets for an extended period.
-
-## Push your changes to GitLab
-
-Now it's time to push the newly created repository to GitLab:
-
-```sh
-git remote add origin git@gitlab.com:youruser/ci-example.git
-git push -u origin master
-```
-
-You can watch the progress and CI job output in your GitLab project under “Pipelines”.
-
-After verifying your CI job ran without issues, first check that your GitLab page shows up under `https://youruser.gitlab.io/reponame/` (it might look broken depending on your browser settings as all links point to your KeyCDN zone---don't worry about that) and then by heading to whatever Zone alias / Zone URL you defined.
-
-To learn more about Hugo hosting options with KeyCDN, check out the complete [Hugo hosting with KeyCDN integration guide](https://www.keycdn.com/support/hugo-hosting/).
diff --git a/docs/content/en/host-and-deploy/host-on-keycdn/keycdn-pull-zone.png b/docs/content/en/host-and-deploy/host-on-keycdn/keycdn-pull-zone.png
deleted file mode 100644
index 7cde4a6a2..000000000
Binary files a/docs/content/en/host-and-deploy/host-on-keycdn/keycdn-pull-zone.png and /dev/null differ
diff --git a/docs/content/en/host-and-deploy/host-on-keycdn/secret-api-key.png b/docs/content/en/host-and-deploy/host-on-keycdn/secret-api-key.png
deleted file mode 100644
index ad99341d5..000000000
Binary files a/docs/content/en/host-and-deploy/host-on-keycdn/secret-api-key.png and /dev/null differ
diff --git a/docs/content/en/host-and-deploy/host-on-keycdn/secret-zone-id.png b/docs/content/en/host-and-deploy/host-on-keycdn/secret-zone-id.png
deleted file mode 100644
index 2e5cf5f41..000000000
Binary files a/docs/content/en/host-and-deploy/host-on-keycdn/secret-zone-id.png and /dev/null differ
diff --git a/docs/content/en/host-and-deploy/host-on-netlify/index.md b/docs/content/en/host-and-deploy/host-on-netlify/index.md
deleted file mode 100644
index 4c89a6c1e..000000000
--- a/docs/content/en/host-and-deploy/host-on-netlify/index.md
+++ /dev/null
@@ -1,146 +0,0 @@
----
-title: Host on Netlify
-description: Host your site on Netlify.
-categories: []
-keywords: []
-aliases: [/hosting-and-deployment/hosting-on-netlify/]
----
-
-## Prerequisites
-
-Please complete the following tasks before continuing:
-
-1. [Create a Netlify account]
-1. [Install Git]
-1. [Create a Hugo site] and test it locally with `hugo server`
-1. Commit the changes to your local repository
-1. Push the local repository to your [GitHub], [GitLab], or [Bitbucket] account
-
-[Bitbucket]: https://bitbucket.org/product
-[Create a Hugo site]: /getting-started/quick-start/
-[Create a Netlify account]: https://app.netlify.com/signup
-[GitHub]: https://github.com
-[GitLab]: https://about.gitlab.com/
-[Install Git]: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git
-
-## Procedure
-
-This procedure will enable continuous deployment from a GitHub repository. The procedure is essentially the same if you are using GitLab or Bitbucket.
-
-### Step 1
-
-Log in to your Netlify account, navigate to the Sites page, press the **Add new site** button, and choose "Import an existing project" from the dropdown menu.
-
-### Step 2
-
-Select your deployment method.
-
- 
-
-### Step 3
-
-Authorize Netlify to connect with your GitHub account by pressing the **Authorize Netlify** button.
-
-
-
-### Step 4
-
-Press the **Configure Netlify on GitHub** button.
-
-
-
-### Step 5
-
-Install the Netlify app by selecting your GitHub account.
-
-
-
-### Step 6
-
-Press the **Install** button.
-
-
-
-### Step 7
-
-Click on the site's repository from the list.
-
-
-
-### Step 8
-
-Set the site name and branch from which to deploy.
-
-
-
-### Step 9
-
-Define the build settings, press the **Add environment variables** button, then press the **New variable** button.
-
-
-
-### Step 10
-
-Create a new environment variable named `HUGO_VERSION` and set the value to the [latest version].
-
-[latest version]: https://github.com/gohugoio/hugo/releases/latest
-
-
-
-### Step 11
-
-Press the "Deploy my new site" button at the bottom of the page.
-
-
-
-### Step 12
-
-At the bottom of the screen, wait for the deploy to complete, then click on the deploy log entry.
-
-
-
-### Step 13
-
-Press the **Open production deploy** button to view the live site.
-
-
-
-## Configuration file
-
-In the procedure above we configured our site using the Netlify user interface. Most site owners find it easier to use a configuration file checked into source control.
-
-Create a new file named netlify.toml in the root of your project directory. In its simplest form, the configuration file might look like this:
-
-```toml {file="netlify.toml"}
-[build.environment]
-GO_VERSION = "1.24"
-HUGO_VERSION = "0.146.7"
-NODE_VERSION = "22"
-TZ = "America/Los_Angeles"
-
-[build]
-publish = "public"
-command = "git config core.quotepath false && hugo --gc --minify"
-```
-
-If your site requires Dart Sass to transpile Sass to CSS, the configuration file should look something like this:
-
-```toml {file="netlify.toml"}
-[build.environment]
-DART_SASS_VERSION = "1.87.0"
-GO_VERSION = "1.24"
-HUGO_VERSION = "0.146.7"
-NODE_VERSION = "22"
-TZ = "America/Los_Angeles"
-
-[build]
-publish = "public"
-command = """\
- curl -LJO https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz && \
- tar -xf dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz && \
- rm dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz && \
- export PATH=/opt/build/repo/dart-sass:$PATH && \
- git config core.quotepath false && \
- hugo --gc --minify \
- """
-```
diff --git a/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-02.png b/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-02.png
deleted file mode 100644
index 31fceff27..000000000
Binary files a/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-02.png and /dev/null differ
diff --git a/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-03.png b/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-03.png
deleted file mode 100644
index 7b98e0b8f..000000000
Binary files a/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-03.png and /dev/null differ
diff --git a/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-04.png b/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-04.png
deleted file mode 100644
index 31304894b..000000000
Binary files a/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-04.png and /dev/null differ
diff --git a/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-05.png b/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-05.png
deleted file mode 100644
index 6d6eef01d..000000000
Binary files a/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-05.png and /dev/null differ
diff --git a/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-06.png b/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-06.png
deleted file mode 100644
index 1b766a785..000000000
Binary files a/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-06.png and /dev/null differ
diff --git a/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-07.png b/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-07.png
deleted file mode 100644
index 7bb3b6eca..000000000
Binary files a/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-07.png and /dev/null differ
diff --git a/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-08.png b/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-08.png
deleted file mode 100644
index df8e9e59f..000000000
Binary files a/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-08.png and /dev/null differ
diff --git a/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-09.png b/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-09.png
deleted file mode 100644
index 3f925accc..000000000
Binary files a/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-09.png and /dev/null differ
diff --git a/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-10.png b/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-10.png
deleted file mode 100644
index e9196d0ce..000000000
Binary files a/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-10.png and /dev/null differ
diff --git a/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-11.png b/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-11.png
deleted file mode 100644
index 2ac2b08af..000000000
Binary files a/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-11.png and /dev/null differ
diff --git a/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-12.png b/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-12.png
deleted file mode 100644
index e251305a4..000000000
Binary files a/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-12.png and /dev/null differ
diff --git a/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-13.png b/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-13.png
deleted file mode 100644
index f955f6369..000000000
Binary files a/docs/content/en/host-and-deploy/host-on-netlify/netlify-step-13.png and /dev/null differ
diff --git a/docs/content/en/host-and-deploy/host-on-render.md b/docs/content/en/host-and-deploy/host-on-render.md
deleted file mode 100644
index ac486fcbf..000000000
--- a/docs/content/en/host-and-deploy/host-on-render.md
+++ /dev/null
@@ -1,78 +0,0 @@
----
-title: Host on Render
-description: Host your on Render.
-categories: []
-keywords: []
-aliases: [/hosting-and-deployment/hosting-on-render/]
----
-
-## Introduction
-
-[Render](https://render.com) is a fully-managed cloud platform where you can host static sites, backend APIs, databases, cron jobs, and all your other apps in one place.
-
-Static sites are **completely free** on Render and include the following:
-
-- Continuous, automatic builds & deploys from [GitHub](https://render.com/docs/github) and [GitLab](https://render.com/docs/gitlab).
-- Automatic SSL certificates through [Let's Encrypt](https://letsencrypt.org).
-- Instant cache invalidation with a lightning fast, global CDN.
-- Unlimited collaborators.
-- Unlimited [custom domains](https://render.com/docs/custom-domains).
-- Automatic [Brotli compression](https://en.wikipedia.org/wiki/Brotli) for faster sites.
-- Native HTTP/2 support.
-- [Pull Request Previews](https://render.com/docs/pull-request-previews).
-- Automatic HTTP → HTTPS redirects.
-- Custom URL redirects and rewrites.
-
-## Assumptions
-
-- You have an account with GitHub or GitLab.
-- You have completed the [Quick Start] or have a Hugo website you are ready to deploy and share with the world.
-- You have a Render account. You can sign up at https://render.com/register.
-
-## Deployment
-
-You can set up a Hugo site on Render in two quick steps:
-
-1. Create a new **Static Site** on Render, and give Render permission to access your GitHub/GitLab repo.
-1. Use the following values during creation:
-
-Field | Value
-------------------- | -------------------
-**Build Command** | `hugo --gc --minify` (or your own build command)
-**Publish Directory** | `public` (or your own output directory)
-
-That's it! Your site will be live on your Render URL (which looks like `yoursite.onrender.com`) as soon as the build is done.
-
-## Continuous deploys
-
-Now that Render is connected to your repo, it will **automatically build and publish your site** any time you push to your GitHub/GitLab.
-
-You can choose to disable auto deploys under the **Settings** section for your site and deploy it manually from the Render dashboard.
-
-## CDN and cache invalidation
-
-Render hosts your site on a global, lightning fast CDN which ensures the fastest possible download times for all your users across the globe.
-
-Every deploy automatically and instantly invalidates the CDN cache, so your users can always access the latest content on your site.
-
-## Custom domains
-
-Add your own domains to your site easily using Render's [custom domains](https://render.com/docs/custom-domains) guide.
-
-## Pull Request previews
-
-With Pull Request (PR) previews, you can visualize changes introduced in a pull request instead of simply relying on code reviews.
-
-Once enabled, every PR for your site will automatically generate a new static site based on the code in the PR. It will have its own URL, and it will be deleted automatically when the PR is closed.
-
-Read more about [Pull Request Previews](https://render.com/docs/pull-request-previews) on Render.
-
-## Hugo themes
-
-Render automatically downloads all Git submodules defined in your Git repo on every build. This way Hugo themes added as submodules work as expected.
-
-## Support
-
-Chat with Render developers at https://render.com/chat or email `support@render.com` if you need help.
-
-[Quick Start]: /getting-started/quick-start/
diff --git a/docs/content/en/host-and-deploy/host-on-sourcehut-pages.md b/docs/content/en/host-and-deploy/host-on-sourcehut-pages.md
deleted file mode 100644
index 6b092fbf2..000000000
--- a/docs/content/en/host-and-deploy/host-on-sourcehut-pages.md
+++ /dev/null
@@ -1,94 +0,0 @@
----
-title: Host on SourceHut Pages
-description: Host your site on SourceHut Pages.
-categories: []
-keywords: []
-aliases: [/hosting-and-deployment/hosting-on-sourcehut/]
----
-
-## Assumptions
-
-- Working familiarity with [Git] or [Mercurial] for version control
-- Completion of the Hugo [Quick Start]
-- A [SourceHut account]
-- A Hugo website on your local machine that you are ready to publish
-
-[Git]: https://git-scm.com/
-[Mercurial]: https://www.mercurial-scm.org/
-[SourceHut account]: https://meta.sr.ht/login
-[Quick Start]: /getting-started/quick-start/
-
-Any and all mentions of `` refer to your actual SourceHut username and must be substituted accordingly.
-
-## BaseURL
-
-The [`baseURL`] in your site configuration must reflect the full URL provided by SourceHut Pages if you are using the default address (e.g. `https://.srht.site/`). If you want to use another domain, check the [custom domain section] of the official documentation.
-
-[`baseURL`]: /configuration/all/#baseurl
-[custom domain section]: https://srht.site/custom-domains
-
-## Manual deployment
-
-This method does not require a paid account. To proceed you will need to create a [SourceHut personal access token] and install and configure the [hut]CLI tool:
-
-[SourceHut personal access token]: https://meta.sr.ht/oauth2/personal-token/
-[hut]: https://sr.ht/~xenrox/hut/
-
-```sh
-hugo
-tar -C public -cvz . > site.tar.gz
-hut init
-hut pages publish -d .srht.site site.tar.gz
-```
-
-A TLS certificate will be automatically obtained for you, and your new website will be available at `https://.srht.site/` (or the provided custom domain).
-
-## Automated deployment
-
-This method requires a paid account and relies on the SourceHut build system.
-
-First, define your [build manifest] by creating a `.build.yml` file in the root of your project. The following is a bare-bones template:
-
-[build manifest]: https://man.sr.ht/builds.sr.ht/#build-manifests
-
-```yaml {file=".build.yml" copy=true}
-image: alpine/edge
-packages:
- - hugo
- - hut
-oauth: pages.sr.ht/PAGES:RW
-environment:
- site: .srht.site
-tasks:
-- package: |
- cd $site
- hugo
- tar -C public -cvz . > ../site.tar.gz
-- upload: |
- hut pages publish -d $site site.tar.gz
-```
-
-Now what's left is creating a repository titled `.srht.site` (or your custom domain, if applicable) and pushing your local project. Here's an example using Git:
-
-```sh
-# initialize new git repository
-git init
-
-# add /public directory to our .gitignore file
-echo "/public" >> .gitignore
-
-# commit and push code to main branch
-git add .
-git commit -m "Initial commit"
-git remote add origin https://git.sr.ht/~/.srht.site
-git push -u origin main
-```
-
-You can now follow the build progress of your page at `https://builds.sr.ht/`.
-
-After the build has passed, a TLS certificate will be automatically obtained for you and your new website will be available at `https://.srht.site/` (or the provided custom domain).
-
-## Other resources
-
-- [SourceHut Pages](https://srht.site/)
-- [SourceHut Builds user manual](https://man.sr.ht/builds.sr.ht/)
diff --git a/docs/content/en/hugo-modules/_index.md b/docs/content/en/hugo-modules/_index.md
deleted file mode 100644
index 7a538ea15..000000000
--- a/docs/content/en/hugo-modules/_index.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: Hugo Modules
-description: Use Hugo Modules to manage the content, presentation, and behavior of your site.
-categories: []
-keywords: []
-weight: 10
-aliases: [/themes/overview/,/themes/]
----
diff --git a/docs/content/en/hugo-modules/introduction.md b/docs/content/en/hugo-modules/introduction.md
deleted file mode 100644
index b45607dc0..000000000
--- a/docs/content/en/hugo-modules/introduction.md
+++ /dev/null
@@ -1,19 +0,0 @@
----
-title: Introduction
-description: A brief introduction to Hugo Modules.
-categories: []
-keywords: []
-weight: 10
----
-
-Hugo uses modules as its fundamental organizational units. A module can be a full Hugo project or a smaller, reusable piece providing one or more of Hugo's seven component types: static files, content, layouts, data, assets, internationalization (i18n) resources, and archetypes.
-
-Modules are combinable in any arrangement, and external directories (including those from non-Hugo projects) can be mounted, effectively creating a single, unified file system.
-
-Some example projects:
-
-[https://github.com/bep/docuapi](https://github.com/bep/docuapi)
-: 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 directory structure. It even shows a JS Bundler implementation in regular Go templates.
-
-[https://github.com/bep/my-modular-site](https://github.com/bep/my-modular-site)
-: A simple site used for testing.
diff --git a/docs/content/en/hugo-modules/theme-components.md b/docs/content/en/hugo-modules/theme-components.md
deleted file mode 100644
index 03891ed7a..000000000
--- a/docs/content/en/hugo-modules/theme-components.md
+++ /dev/null
@@ -1,35 +0,0 @@
----
-title: Theme components
-description: Hugo provides advanced theming support with theme components.
-categories: []
-keywords: []
-weight: 30
-aliases: [/themes/customize/,/themes/customizing/]
----
-
-A project can configure a theme as a composite of as many theme components as you need:
-
-{{< code-toggle file=hugo >}}
-theme = ["my-shortcodes", "base-theme", "hyde"]
-{{< /code-toggle >}}
-
-You can even nest this, and have the theme component itself include theme components in its own `hugo.toml` (theme inheritance).
-
-The theme definition example above in `hugo.toml` creates a theme with 3 theme components with precedence from left to right.
-
-For any given file, data entry, etc., Hugo will look first in the project and then in `my-shortcodes`, `base-theme`, and lastly `hyde`.
-
-Hugo uses two different algorithms to merge the file systems, depending on the file type:
-
-- For `i18n` and `data` files, Hugo merges deeply using the translation ID and data key inside the files.
-- For `static`, `layouts` (templates), and `archetypes` files, these are merged on file level. So the left-most file will be chosen.
-
-The name used in the `theme` definition above must match a directory in `/your-site/themes`, e.g. `/your-site/themes/my-shortcodes`.
-
-Also note that a component that is part of a theme can have its own configuration file, e.g. `hugo.toml`. There are currently some restrictions to what a theme component can configure:
-
-- `params` (global and per language)
-- `menu` (global and per language)
-- `outputformats` and `mediatypes`
-
-The same rules apply here: The left-most parameter/menu etc. with the same ID will win. There are some hidden and experimental namespace support in the above, which we will work to improve in the future, but theme authors are encouraged to create their own namespaces to avoid naming conflicts.
diff --git a/docs/content/en/hugo-modules/use-modules.md b/docs/content/en/hugo-modules/use-modules.md
deleted file mode 100644
index 86d2ad1cc..000000000
--- a/docs/content/en/hugo-modules/use-modules.md
+++ /dev/null
@@ -1,154 +0,0 @@
----
-title: Use Hugo Modules
-description: How to use Hugo Modules.
-categories: []
-keywords: []
-weight: 20
-aliases: [/themes/usage/,/themes/installing/,/installing-and-using-themes/]
----
-
-## Prerequisite
-
-{{% include "/_common/gomodules-info.md" %}}
-
-## Initialize a new module
-
-Use `hugo mod init` to initialize a new Hugo Module. If it fails to guess the module path, you must provide it as an argument, e.g.:
-
-```sh
-hugo mod init github.com//
-```
-
-Also see the [CLI Doc](/commands/hugo_mod_init/).
-
-## Use a module for a theme
-
-The easiest way to use a Module for a theme is to import it in the configuration.
-
-1. Initialize the hugo module system: `hugo mod init github.com//`
-1. Import the theme:
-
- {{< code-toggle file=hugo >}}
- [module]
- [[module.imports]]
- path = "github.com/spf13/hyde"
- {{< /code-toggle >}}
-
-## Update modules
-
-Modules will be downloaded and added when you add them as imports to your configuration. See [configure modules](/configuration/module/#imports).
-
-To update or manage versions, you can use `hugo mod get`.
-
-Some examples:
-
-### Update all modules
-
-```sh
-hugo mod get -u
-```
-
-### Update all modules recursively
-
-```sh
-hugo mod get -u ./...
-```
-
-### Update one module
-
-```sh
-hugo mod get -u github.com/gohugoio/myShortcodes
-```
-
-### Get a specific version
-
-```sh
-hugo mod get github.com/gohugoio/myShortcodes@v1.0.7
-```
-
-Also see the [CLI Doc](/commands/hugo_mod_get/).
-
-## Make and test changes in a module
-
-One way to do local development of a module imported in a project is to add a replace directive to a local directory with the source in `go.mod`:
-
-```sh
-replace github.com/bep/hugotestmods/mypartials => /Users/bep/hugotestmods/mypartials
-```
-
-If you have the `hugo server` running, the configuration will be reloaded and `/Users/bep/hugotestmods/mypartials` put on the watch list.
-
-Instead of modifying the `go.mod` files, you can also use the modules configuration [`replacements`](/configuration/module/#top-level-options) option.
-
-## Print dependency graph
-
-Use `hugo mod graph` from the relevant module directory and it will print the dependency graph, including vendoring, module replacement or disabled status.
-
-E.g.:
-
-```txt
-hugo mod graph
-
-github.com/bep/my-modular-site github.com/bep/hugotestmods/mymounts@v1.2.0
-github.com/bep/my-modular-site github.com/bep/hugotestmods/mypartials@v1.0.7
-github.com/bep/hugotestmods/mypartials@v1.0.7 github.com/bep/hugotestmods/myassets@v1.0.4
-github.com/bep/hugotestmods/mypartials@v1.0.7 github.com/bep/hugotestmods/myv2@v1.0.0
-DISABLED github.com/bep/my-modular-site github.com/spf13/hyde@v0.0.0-20190427180251-e36f5799b396
-github.com/bep/my-modular-site github.com/bep/hugo-fresh@v1.0.1
-github.com/bep/my-modular-site in-themesdir
-```
-
-Also see the [CLI Doc](/commands/hugo_mod_graph/).
-
-## Vendor your modules
-
-`hugo mod vendor` will write all the module dependencies to a `_vendor` directory, which will then be used for all subsequent builds.
-
-Note that:
-
-- You can run `hugo mod vendor` on any level in the module tree.
-- Vendoring will not store modules stored in your `themes` directory.
-- Most commands accept a `--ignoreVendorPaths` flag, which will then not use the vendored modules in `_vendor` for the module paths matching the given [glob](g) pattern.
-
-Also see the [CLI Doc](/commands/hugo_mod_vendor/).
-
-## Tidy go.mod, go.sum
-
-Run `hugo mod tidy` to remove unused entries in `go.mod` and `go.sum`.
-
-Also see the [CLI Doc](/commands/hugo_mod_clean/).
-
-## Clean module cache
-
-Run `hugo mod clean` to delete the entire modules cache.
-
-Note that you can also configure the `modules` cache with a `maxAge`. See [configure caches](/configuration/caches/).
-
-Also see the [CLI Doc](/commands/hugo_mod_clean/).
-
-## Module workspaces
-
-Workspace support was added in [Go 1.18](https://go.dev/blog/get-familiar-with-workspaces) and Hugo got solid support for it in the `v0.109.0` version.
-
-A common use case for a workspace is to simplify local development of a site with its theme modules.
-
-A workspace can be configured in a `*.work` file and activated with the [module.workspace](/configuration/module/) setting, which for this use is commonly controlled via the `HUGO_MODULE_WORKSPACE` OS environment variable.
-
-See the [hugo.work](https://github.com/gohugoio/hugo/blob/master/docs/hugo.work) file in the Hugo Docs repo for an example:
-
-```text
-go 1.20
-
-use .
-use ../gohugoioTheme
-```
-
-Using the `use` directive, list all the modules you want to work on, pointing to its relative location. As in the example above, it's recommended to always include the main project (the `.`) in the list.
-
-With that you can start the Hugo server with that workspace enabled:
-
-```sh
-HUGO_MODULE_WORKSPACE=hugo.work hugo server --ignoreVendorPaths "**"
-```
-
-The `--ignoreVendorPaths` flag is added above to ignore any of the vendored dependencies inside `_vendor`. If you don't use vendoring, you don't need that flag. But now the server is set up watching the files and directories in the workspace and you can see your local edits reloaded.
diff --git a/docs/content/en/hugo-pipes/_index.md b/docs/content/en/hugo-pipes/_index.md
deleted file mode 100755
index 51028152b..000000000
--- a/docs/content/en/hugo-pipes/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Hugo Pipes
-description: Use asset pipelines to transform and optimize images, stylesheets, and JavaScript.
-categories: []
-keywords: []
-weight: 10
----
diff --git a/docs/content/en/hugo-pipes/bundling.md b/docs/content/en/hugo-pipes/bundling.md
deleted file mode 100755
index 335cc5cae..000000000
--- a/docs/content/en/hugo-pipes/bundling.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
-title: Concat
-linkTitle: Concatenating assets
-description: Bundle any number of assets into one resource.
-categories: []
-keywords: []
----
-
-See the [`resources.Concat`](/functions/resources/concat/) function.
diff --git a/docs/content/en/hugo-pipes/fingerprint.md b/docs/content/en/hugo-pipes/fingerprint.md
deleted file mode 100755
index d38dfc106..000000000
--- a/docs/content/en/hugo-pipes/fingerprint.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
-title: Fingerprint
-linkTitle: Fingerprinting and SRI hashing
-description: Cryptographically hash the content of the given resource.
-categories: []
-keywords: []
----
-
-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
deleted file mode 100755
index d1b93094f..000000000
--- a/docs/content/en/hugo-pipes/introduction.md
+++ /dev/null
@@ -1,74 +0,0 @@
----
-title: Hugo Pipes
-linkTitle: Introduction
-description: Hugo Pipes is Hugo's asset processing set of functions.
-categories: []
-keywords: []
-weight: 10
-aliases: [/assets/]
----
-
-## Find resources in assets
-
-This is about global and remote resources.
-
-global resource
-: A file within the `assets` directory, or within any directory [mounted] to the `assets` directory.
-
-remote resource
-: A file on a remote server, accessible via HTTP or HTTPS.
-
-For `.Page` scoped resources, see the [page resources] section.
-
-[mounted]: /configuration/module/#mounts
-[page resources]: /content-management/page-resources/
-
-## Get a resource
-
-In order to process an asset with Hugo Pipes, it must be retrieved as a resource.
-
-For global resources, use:
-
-- [`resources.ByType`](/functions/resources/bytype/)
-- [`resources.Get`](/functions/resources/get/)
-- [`resources.GetMatch`](/functions/resources/getmatch/)
-- [`resources.Match`](/functions/resources/match/)
-
-For remote resources, use:
-
-- [`resources.GetRemote`](/functions/resources/getremote/)
-
-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.
-
-## Copy a resource
-
-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 publishing
-
-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" | css.Sass | resources.Minify | resources.Fingerprint }}
-
-```
-
-## Caching
-
-Hugo Pipes invocations are cached based on the entire *pipe chain*.
-
-An example of a pipe chain is:
-
-```go-html-template
-{{ $mainJs := resources.Get "js/main.js" | js.Build "main.js" | minify | fingerprint }}
-```
-
-The pipe chain is only invoked the first time it is encountered in a site build, and results are otherwise loaded from cache. As such, Hugo Pipes can be used in templates which are executed thousands or millions of times without negatively impacting the build performance.
diff --git a/docs/content/en/hugo-pipes/js.md b/docs/content/en/hugo-pipes/js.md
deleted file mode 100644
index 18572d538..000000000
--- a/docs/content/en/hugo-pipes/js.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
-title: JavaScript
-linkTitle: JavaScript building
-description: Bundle, transpile, tree shake, code split, and minify JavaScript resources.
-categories: []
-keywords: []
----
-
-See [JS functions](/functions/js/).
diff --git a/docs/content/en/hugo-pipes/minification.md b/docs/content/en/hugo-pipes/minification.md
deleted file mode 100755
index 4ba1ea641..000000000
--- a/docs/content/en/hugo-pipes/minification.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
-title: Minify
-linkTitle: Asset minification
-description: Minify a given resource.
-categories: []
-keywords: []
----
-
-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
deleted file mode 100755
index 1b47e8aef..000000000
--- a/docs/content/en/hugo-pipes/postcss.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: PostCSS
-description: Process the given resource with PostCSS using any PostCSS plugin.
-categories: []
-keywords: []
----
-
-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
deleted file mode 100755
index 72540fd5d..000000000
--- a/docs/content/en/hugo-pipes/postprocess.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: PostProcess
-description: Process the given resource after the build.
-categories: []
-keywords: []
----
-
-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
deleted file mode 100755
index ed5eaff80..000000000
--- a/docs/content/en/hugo-pipes/resource-from-string.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
-title: FromString
-linkTitle: Resource from string
-description: Create a resource from a string.
-categories: []
-keywords: []
----
-
-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
deleted file mode 100755
index bc50f630b..000000000
--- a/docs/content/en/hugo-pipes/resource-from-template.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
-title: ExecuteAsTemplate
-linkTitle: Resource from template
-description: Create a resource from a Go template, parsed and executed with the given context.
-categories: []
-keywords: []
----
-
-See the [`resources.ExecuteAsTemplate`](/functions/resources/executeastemplate/) function.
diff --git a/docs/content/en/hugo-pipes/transpile-sass-to-css.md b/docs/content/en/hugo-pipes/transpile-sass-to-css.md
deleted file mode 100644
index 756914297..000000000
--- a/docs/content/en/hugo-pipes/transpile-sass-to-css.md
+++ /dev/null
@@ -1,10 +0,0 @@
----
-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
deleted file mode 100644
index fdcb8f9eb..000000000
--- a/docs/content/en/installation/_index.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-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
deleted file mode 100644
index 2f6519e6d..000000000
--- a/docs/content/en/installation/bsd.md
+++ /dev/null
@@ -1,75 +0,0 @@
----
-title: BSD
-description: Install Hugo on BSD derivatives.
-categories: []
-keywords: []
-weight: 40
----
-
-## Editions
-
-{{% include "/_common/installation/01-editions.md" %}}
-
-Unless your specific deployment needs require the extended/deploy edition, we recommend the extended edition.
-
-{{% 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
deleted file mode 100644
index 591bf0818..000000000
--- a/docs/content/en/installation/linux.md
+++ /dev/null
@@ -1,218 +0,0 @@
----
-title: Linux
-description: Install Hugo on Linux.
-categories: []
-keywords: []
-weight: 20
----
-
-## Editions
-
-{{% include "/_common/installation/01-editions.md" %}}
-
-Unless your specific deployment needs require the extended/deploy edition, we recommend the extended edition.
-
-{{% 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
-```
-
-{{% 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 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/
-[most distributions]: https://snapcraft.io/docs/installing-snapd
-[MX Linux]: https://mxlinux.org/
-[openSUSE]: https://www.opensuse.org/
-[Pop!_OS]: https://pop.system76.com/
-[Red Hat Enterprise Linux]: https://www.redhat.com/
-[Snap]: https://snapcraft.io/
-[Solus]: https://getsol.us/
-[strictly confined]: https://snapcraft.io/docs/snap-confinement
-[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
deleted file mode 100644
index a873b512a..000000000
--- a/docs/content/en/installation/macos.md
+++ /dev/null
@@ -1,46 +0,0 @@
----
-title: macOS
-description: Install Hugo on macOS.
-categories: []
-keywords: []
-weight: 10
----
-
-## Editions
-
-{{% include "/_common/installation/01-editions.md" %}}
-
-Unless your specific deployment needs require the extended/deploy edition, we recommend the extended edition.
-
-{{% 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
deleted file mode 100644
index a5920d45f..000000000
--- a/docs/content/en/installation/windows.md
+++ /dev/null
@@ -1,74 +0,0 @@
----
-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.
-
-## Editions
-
-{{% include "/_common/installation/01-editions.md" %}}
-
-Unless your specific deployment needs require the extended/deploy edition, we recommend the extended edition.
-
-{{% 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/methods/_index.md b/docs/content/en/methods/_index.md
deleted file mode 100644
index 39f2b6146..000000000
--- a/docs/content/en/methods/_index.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-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
deleted file mode 100644
index 2e85797ea..000000000
--- a/docs/content/en/methods/duration/Abs.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-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
deleted file mode 100644
index 23655510e..000000000
--- a/docs/content/en/methods/duration/Hours.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-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
deleted file mode 100644
index c090316d0..000000000
--- a/docs/content/en/methods/duration/Microseconds.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-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
deleted file mode 100644
index 288f3695a..000000000
--- a/docs/content/en/methods/duration/Milliseconds.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-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
deleted file mode 100644
index aec904fa7..000000000
--- a/docs/content/en/methods/duration/Minutes.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-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
deleted file mode 100644
index fd1b9e496..000000000
--- a/docs/content/en/methods/duration/Nanoseconds.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-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
deleted file mode 100644
index dfd06253f..000000000
--- a/docs/content/en/methods/duration/Round.md
+++ /dev/null
@@ -1,18 +0,0 @@
----
-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
deleted file mode 100644
index 8b6d060b9..000000000
--- a/docs/content/en/methods/duration/Seconds.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-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
deleted file mode 100644
index 5a785a77a..000000000
--- a/docs/content/en/methods/duration/Truncate.md
+++ /dev/null
@@ -1,18 +0,0 @@
----
-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
deleted file mode 100644
index aeb113820..000000000
--- a/docs/content/en/methods/duration/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Duration methods
-linkTitle: Duration
-description: Use these methods with time.Duration values.
-categories: []
-keywords: []
----
diff --git a/docs/content/en/methods/menu-entry/Children.md b/docs/content/en/methods/menu-entry/Children.md
deleted file mode 100644
index ecad415fa..000000000
--- a/docs/content/en/methods/menu-entry/Children.md
+++ /dev/null
@@ -1,66 +0,0 @@
----
-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 site 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
deleted file mode 100644
index 809624459..000000000
--- a/docs/content/en/methods/menu-entry/Identifier.md
+++ /dev/null
@@ -1,41 +0,0 @@
----
-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 site, 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
deleted file mode 100644
index d614a5a87..000000000
--- a/docs/content/en/methods/menu-entry/KeyName.md
+++ /dev/null
@@ -1,39 +0,0 @@
----
-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 site, 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
deleted file mode 100644
index 074911eeb..000000000
--- a/docs/content/en/methods/menu-entry/Menu.md
+++ /dev/null
@@ -1,22 +0,0 @@
----
-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
deleted file mode 100644
index 706d0f8c8..000000000
--- a/docs/content/en/methods/menu-entry/Name.md
+++ /dev/null
@@ -1,28 +0,0 @@
----
-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 site 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`].
-
-[`LinkTitle`]: /methods/page/linktitle/
-[`Title`]: /methods/page/title/
-[automatically]: /content-management/menus/#define-automatically
-[in front matter]: /content-management/menus/#define-in-front-matter
-[in site configuration]: /content-management/menus/#define-in-site-configuration
-
-```go-html-template
-
-```
diff --git a/docs/content/en/methods/menu-entry/Page.md b/docs/content/en/methods/menu-entry/Page.md
deleted file mode 100644
index 489ee7acc..000000000
--- a/docs/content/en/methods/menu-entry/Page.md
+++ /dev/null
@@ -1,53 +0,0 @@
----
-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
deleted file mode 100644
index 979879b03..000000000
--- a/docs/content/en/methods/menu-entry/PageRef.md
+++ /dev/null
@@ -1,109 +0,0 @@
----
-title: PageRef
-description: Returns the `pageRef` property of the given menu entry.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: string
- signatures: [MENUENTRY.PageRef]
----
-
-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 site 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 the site 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-site-configuration
diff --git a/docs/content/en/methods/menu-entry/Params.md b/docs/content/en/methods/menu-entry/Params.md
deleted file mode 100644
index 20c4f7fc7..000000000
--- a/docs/content/en/methods/menu-entry/Params.md
+++ /dev/null
@@ -1,61 +0,0 @@
----
-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 site 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
deleted file mode 100644
index 2da8c38d8..000000000
--- a/docs/content/en/methods/menu-entry/Post.md
+++ /dev/null
@@ -1,12 +0,0 @@
----
-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
deleted file mode 100644
index 19af243e7..000000000
--- a/docs/content/en/methods/menu-entry/Pre.md
+++ /dev/null
@@ -1,12 +0,0 @@
----
-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
deleted file mode 100644
index 526132d7c..000000000
--- a/docs/content/en/methods/menu-entry/Title.md
+++ /dev/null
@@ -1,22 +0,0 @@
----
-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
deleted file mode 100644
index b96e2cc87..000000000
--- a/docs/content/en/methods/menu-entry/Weight.md
+++ /dev/null
@@ -1,31 +0,0 @@
----
-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 site configuration], the `Weight` method returns the `weight` property, falling back to the page's `Weight`.
-
-[`Weight`]: /methods/page/weight/
-[automatically]: /content-management/menus/#define-automatically
-[in front matter]: /content-management/menus/#define-in-front-matter
-[in site configuration]: /content-management/menus/#define-in-site-configuration
-
-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
deleted file mode 100644
index 013d37e13..000000000
--- a/docs/content/en/methods/menu/ByWeight.md
+++ /dev/null
@@ -1,71 +0,0 @@
----
-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
deleted file mode 100644
index 005fef144..000000000
--- a/docs/content/en/methods/menu/Limit.md
+++ /dev/null
@@ -1,50 +0,0 @@
----
-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/page/AlternativeOutputFormats.md b/docs/content/en/methods/page/AlternativeOutputFormats.md
deleted file mode 100644
index c4075d010..000000000
--- a/docs/content/en/methods/page/AlternativeOutputFormats.md
+++ /dev/null
@@ -1,42 +0,0 @@
----
-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/).
-
-## Methods
-
-{{% include "/_common/methods/page/output-format-methods.md" %}}
-
-## Example
-
-Generate a `link` element in the `` of each page for each of the alternative output formats:
-
-```go-html-template
-
- ...
- {{ $title := printf "%s | %s" .Title site.Title }}
- {{ if .IsHome }}
- {{ $title = site.Title }}
- {{ end }}
- {{ range .AlternativeOutputFormats }}
- {{ printf `` .Rel .MediaType.Type .Permalink $title | safeHTML }}
- {{ end }}
- ...
-
-```
-
-On the site's home page, Hugo renders this to:
-
-```html
-
-```
diff --git a/docs/content/en/methods/page/Ancestors.md b/docs/content/en/methods/page/Ancestors.md
deleted file mode 100644
index d8275cf76..000000000
--- a/docs/content/en/methods/page/Ancestors.md
+++ /dev/null
@@ -1,76 +0,0 @@
----
-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
deleted file mode 100644
index e919511da..000000000
--- a/docs/content/en/methods/page/BundleType.md
+++ /dev/null
@@ -1,33 +0,0 @@
----
-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
deleted file mode 100644
index 00afa7549..000000000
--- a/docs/content/en/methods/page/CodeOwners.md
+++ /dev/null
@@ -1,65 +0,0 @@
----
-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
deleted file mode 100644
index 21348ebe6..000000000
--- a/docs/content/en/methods/page/Content.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-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
deleted file mode 100644
index 4923b1197..000000000
--- a/docs/content/en/methods/page/ContentWithoutSummary.md
+++ /dev/null
@@ -1,22 +0,0 @@
----
-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
deleted file mode 100644
index 93457f13f..000000000
--- a/docs/content/en/methods/page/CurrentSection.md
+++ /dev/null
@@ -1,48 +0,0 @@
----
-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
deleted file mode 100644
index ae0bdc57f..000000000
--- a/docs/content/en/methods/page/Data.md
+++ /dev/null
@@ -1,101 +0,0 @@
----
-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 list templates. Although that syntax remains functional, use one of these methods instead: [`Pages`], [`RegularPages`], or [`RegularPagesRecursive`]
-
-The examples that follow are based on this site 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
deleted file mode 100644
index b6c2042c2..000000000
--- a/docs/content/en/methods/page/Date.md
+++ /dev/null
@@ -1,34 +0,0 @@
----
-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 site, in the site 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
-[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
deleted file mode 100644
index 5287aa699..000000000
--- a/docs/content/en/methods/page/Description.md
+++ /dev/null
@@ -1,27 +0,0 @@
----
-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/_default/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
deleted file mode 100644
index 482a370bf..000000000
--- a/docs/content/en/methods/page/Draft.md
+++ /dev/null
@@ -1,21 +0,0 @@
----
-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 site. To include draft pages when you build your site, 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
deleted file mode 100644
index 4947a4bfa..000000000
--- a/docs/content/en/methods/page/Eq.md
+++ /dev/null
@@ -1,21 +0,0 @@
----
-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 from a single template, we list all pages in the current section except for the current page.
-
-```go-html-template
-{{ $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
deleted file mode 100644
index a72155c33..000000000
--- a/docs/content/en/methods/page/ExpiryDate.md
+++ /dev/null
@@ -1,32 +0,0 @@
----
-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 site. 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
deleted file mode 100644
index 2af60a719..000000000
--- a/docs/content/en/methods/page/File.md
+++ /dev/null
@@ -1,194 +0,0 @@
----
-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
-
-{{< new-in 0.126.0 />}}
-
-(`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
deleted file mode 100644
index 73ddd2d7b..000000000
--- a/docs/content/en/methods/page/FirstSection.md
+++ /dev/null
@@ -1,48 +0,0 @@
----
-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
deleted file mode 100644
index 2c0460def..000000000
--- a/docs/content/en/methods/page/Fragments.md
+++ /dev/null
@@ -1,109 +0,0 @@
----
-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 site 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/0.30/#atx-headings
-[Markdown notation]: /content-management/shortcodes/#notation
-[setext]: https://spec.commonmark.org/0.30/#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
deleted file mode 100644
index 815a07402..000000000
--- a/docs/content/en/methods/page/FuzzyWordCount.md
+++ /dev/null
@@ -1,18 +0,0 @@
----
-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
deleted file mode 100644
index 02f6888e0..000000000
--- a/docs/content/en/methods/page/GetPage.md
+++ /dev/null
@@ -1,64 +0,0 @@
----
-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/single.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
deleted file mode 100644
index 53b996fc5..000000000
--- a/docs/content/en/methods/page/GetTerms.md
+++ /dev/null
@@ -1,41 +0,0 @@
----
-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
deleted file mode 100644
index 5fde05b07..000000000
--- a/docs/content/en/methods/page/GitInfo.md
+++ /dev/null
@@ -1,151 +0,0 @@
----
-title: GitInfo
-description: Returns Git information related to the last commit of the given page.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: source.GitInfo
- signatures: [PAGE.GitInfo]
----
-
-The `GitInfo` method on a `Page` object returns an object with additional methods.
-
-> [!note]
-> Hugo's Git integration is performant, but may increase build times on large sites.
-
-## Prerequisites
-
-Install [Git], create a repository, and commit your project files.
-
-You must also allow Hugo to access your repository. In your site configuration:
-
-{{< code-toggle file=hugo >}}
-enableGitInfo = true
-{{< /code-toggle >}}
-
-Alternatively, use the command line flag when building your site:
-
-```sh
-hugo --enableGitInfo
-```
-
-> [!note]
-> When you set `enableGitInfo` to `true`, or enable the feature with the command line flag, the last modification date for each content page will be the Author Date of the last commit for that file.
->
-> This is configurable. See [details].
-
-## Methods
-
-### AbbreviatedHash
-
-(`string`) The abbreviated commit hash.
-
-```go-html-template
-{{ with .GitInfo }}
- {{ .AbbreviatedHash }} → aab9ec0b3
-{{ end }}
-```
-
-### AuthorDate
-
-(`time.Time`) The author date.
-
-```go-html-template
-{{ with .GitInfo }}
- {{ .AuthorDate.Format "2006-01-02" }} → 2023-10-09
-{{ end }}
-```
-
-### AuthorEmail
-
-(`string`) The author's email address, respecting [gitmailmap].
-
-```go-html-template
-{{ with .GitInfo }}
- {{ .AuthorEmail }} → jsmith@example.org
-{{ end }}
-```
-
-### AuthorName
-
-(`string`) The author's name, respecting [gitmailmap].
-
-```go-html-template
-{{ with .GitInfo }}
- {{ .AuthorName }} → John Smith
-{{ end }}
-```
-
-### CommitDate
-
-(`time.Time`) The commit date.
-
-```go-html-template
-{{ with .GitInfo }}
- {{ .CommitDate.Format "2006-01-02" }} → 2023-10-09
-{{ end }}
-```
-
-### Hash
-
-(`string`) The commit hash.
-
-```go-html-template
-{{ with .GitInfo }}
- {{ .Hash }} → aab9ec0b31ebac916a1468c4c9c305f2bebf78d4
-{{ end }}
-```
-
-### Subject
-
-(`string`) The commit message subject.
-
-```go-html-template
-{{ with .GitInfo }}
- {{ .Subject }} → Add tutorials
-{{ end }}
-```
-
-### Body
-
-(`string`) The commit message body.
-
-```go-html-template
-{{ with .GitInfo }}
- {{ .Body }} → - Two new pages added.
-{{ end }}
-```
-
-## 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 [site configuration].
-
-## Hosting considerations
-
-When hosting your site in a [CI/CD](g) environment, 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 not be accurate---it may reflect the most recent repository commit, not the commit that last modified the file.
-
-Some providers perform deep clones by default, others allow you to configure the clone depth, and some only perform shallow clones.
-
-Hosting service | Default clone depth | Configurable
-:-- | :-- | :--
-AWS Amplify | Deep | N/A
-Cloudflare Pages | 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 | No
-Vercel | Shallow | No
-
-[^1]: To configure a Cloudflare Pages site for deep cloning, run `git fetch --unshallow` before building the site.
-
-[^2]: You can configure the GitHub Action to do a deep clone by specifying `fetch-depth: 0` in the applicable "checkout" step of your workflow file, as shown in the Hugo documentation's [example workflow file](/host-and-deploy/host-on-github-pages/#procedure).
-
-[^3]: You can configure the GitLab Runner's clone depth [as explained in the GitLab documentation](https://docs.gitlab.com/ee/ci/large_repositories/#shallow-cloning); see also the Hugo documentation's [example workflow file](/host-and-deploy/host-on-gitlab-pages/#configure-gitlab-cicd).
-
-[details]: /configuration/front-matter/#dates
-[gitmailmap]: https://git-scm.com/docs/gitmailmap
-[site configuration]: /configuration/front-matter/
diff --git a/docs/content/en/methods/page/HasMenuCurrent.md b/docs/content/en/methods/page/HasMenuCurrent.md
deleted file mode 100644
index 207882167..000000000
--- a/docs/content/en/methods/page/HasMenuCurrent.md
+++ /dev/null
@@ -1,33 +0,0 @@
----
-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 site configuration.
-
-[menu templates]: /templates/menu/#example
diff --git a/docs/content/en/methods/page/HasShortcode.md b/docs/content/en/methods/page/HasShortcode.md
deleted file mode 100644
index 616b6de09..000000000
--- a/docs/content/en/methods/page/HasShortcode.md
+++ /dev/null
@@ -1,50 +0,0 @@
----
-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/_default/baseof.html"}
-
- ...
- {{ if .HasShortcode "plotly" }}
-
- {{ end }}
- ...
-
-```
diff --git a/docs/content/en/methods/page/HeadingsFiltered.md b/docs/content/en/methods/page/HeadingsFiltered.md
deleted file mode 100644
index 86c989d43..000000000
--- a/docs/content/en/methods/page/HeadingsFiltered.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-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
deleted file mode 100644
index adca82d86..000000000
--- a/docs/content/en/methods/page/InSection.md
+++ /dev/null
@@ -1,91 +0,0 @@
----
-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
deleted file mode 100644
index fe1b78454..000000000
--- a/docs/content/en/methods/page/IsAncestor.md
+++ /dev/null
@@ -1,87 +0,0 @@
----
-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
deleted file mode 100644
index 6ee8d3c4f..000000000
--- a/docs/content/en/methods/page/IsDescendant.md
+++ /dev/null
@@ -1,87 +0,0 @@
----
-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
deleted file mode 100644
index 66d8180b0..000000000
--- a/docs/content/en/methods/page/IsHome.md
+++ /dev/null
@@ -1,26 +0,0 @@
----
-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
deleted file mode 100644
index 9bbacd018..000000000
--- a/docs/content/en/methods/page/IsMenuCurrent.md
+++ /dev/null
@@ -1,31 +0,0 @@
----
-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 site configuration.
-
-[menu templates]: /templates/menu/#example
diff --git a/docs/content/en/methods/page/IsNode.md b/docs/content/en/methods/page/IsNode.md
deleted file mode 100644
index 194a2cac8..000000000
--- a/docs/content/en/methods/page/IsNode.md
+++ /dev/null
@@ -1,32 +0,0 @@
----
-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 returns `true` if the [page kind](g) is `home`, `section`, `taxonomy`, or `term`.
-
-It returns `false` is the page kind is `page`.
-
-```text
-content/
-├── books/
-│ ├── book-1/
-│ │ └── index.md <-- kind = page, node = false
-│ ├── book-2.md <-- kind = page, node = false
-│ └── _index.md <-- kind = section, node = true
-├── tags/
-│ ├── fiction/
-│ │ └── _index.md <-- kind = term, node = true
-│ └── _index.md <-- kind = taxonomy, node = true
-└── _index.md <-- kind = home, node = true
-```
-
-```go-html-template
-{{ .IsNode }}
-```
diff --git a/docs/content/en/methods/page/IsPage.md b/docs/content/en/methods/page/IsPage.md
deleted file mode 100644
index 910a3a7e1..000000000
--- a/docs/content/en/methods/page/IsPage.md
+++ /dev/null
@@ -1,26 +0,0 @@
----
-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
deleted file mode 100644
index 7a04fbd8f..000000000
--- a/docs/content/en/methods/page/IsSection.md
+++ /dev/null
@@ -1,26 +0,0 @@
----
-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
deleted file mode 100644
index 2cdf911ac..000000000
--- a/docs/content/en/methods/page/IsTranslated.md
+++ /dev/null
@@ -1,56 +0,0 @@
----
-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 site configuration:
-
-{{< code-toggle file=hugo >}}
-defaultContentLanguage = 'en'
-
-[languages.en]
-contentDir = 'content/en'
-languageCode = 'en-US'
-languageName = 'English'
-weight = 1
-
-[languages.de]
-contentDir = 'content/de'
-languageCode = 'de-DE'
-languageName = 'Deutsch'
-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
deleted file mode 100644
index 7c940984e..000000000
--- a/docs/content/en/methods/page/Keywords.md
+++ /dev/null
@@ -1,46 +0,0 @@
----
-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
deleted file mode 100644
index a01877e8c..000000000
--- a/docs/content/en/methods/page/Kind.md
+++ /dev/null
@@ -1,32 +0,0 @@
----
-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
deleted file mode 100644
index 9fd604df3..000000000
--- a/docs/content/en/methods/page/Language.md
+++ /dev/null
@@ -1,69 +0,0 @@
----
-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. The language object points to the language definition in the site configuration.
-
-You can also use the `Language` method on a `Site` object. See [details].
-
-## Methods
-
-The examples below assume the following in your site configuration:
-
-{{< code-toggle file=hugo >}}
-[languages.de]
-languageCode = 'de-DE'
-languageDirection = 'ltr'
-languageName = 'Deutsch'
-weight = 2
-{{< /code-toggle >}}
-
-### Lang
-
-(`string`) The language tag as defined by [RFC 5646].
-
-```go-html-template
-{{ .Language.Lang }} → de
-```
-
-### LanguageCode
-
-(`string`) The language code from the site configuration. Falls back to `Lang` if not defined.
-
-```go-html-template
-{{ .Language.LanguageCode }} → de-DE
-```
-
-### LanguageDirection
-
-(`string`) The language direction from the site configuration, either `ltr` or `rtl`.
-
-```go-html-template
-{{ .Language.LanguageDirection }} → ltr
-```
-
-### LanguageName
-
-(`string`) The language name from the site configuration.
-
-```go-html-template
-{{ .Language.LanguageName }} → Deutsch
-```
-
-### Weight
-
-(`int`) The language weight from the site configuration which determines its order in the slice of languages returned by the `Languages` method on a `Site` object.
-
-```go-html-template
-{{ .Language.Weight }} → 2
-```
-
-[details]: /methods/site/language/
-[RFC 5646]: https://datatracker.ietf.org/doc/html/rfc5646
diff --git a/docs/content/en/methods/page/Lastmod.md b/docs/content/en/methods/page/Lastmod.md
deleted file mode 100644
index 643eddc5e..000000000
--- a/docs/content/en/methods/page/Lastmod.md
+++ /dev/null
@@ -1,36 +0,0 @@
----
-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
deleted file mode 100644
index f9aa5b6ab..000000000
--- a/docs/content/en/methods/page/Layout.md
+++ /dev/null
@@ -1,39 +0,0 @@
----
-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/
-└── _default/
- ├── baseof.html
- ├── contact.html
- ├── home.html
- ├── list.html
- └── single.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
deleted file mode 100644
index 010da88d1..000000000
--- a/docs/content/en/methods/page/Len.md
+++ /dev/null
@@ -1,14 +0,0 @@
----
-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
deleted file mode 100644
index fcfd5318d..000000000
--- a/docs/content/en/methods/page/LinkTitle.md
+++ /dev/null
@@ -1,29 +0,0 @@
----
-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
deleted file mode 100644
index 996603083..000000000
--- a/docs/content/en/methods/page/Next.md
+++ /dev/null
@@ -1,12 +0,0 @@
----
-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
deleted file mode 100644
index eb02c9492..000000000
--- a/docs/content/en/methods/page/NextInSection.md
+++ /dev/null
@@ -1,12 +0,0 @@
----
-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
deleted file mode 100644
index 0e648efaa..000000000
--- a/docs/content/en/methods/page/OutputFormats.md
+++ /dev/null
@@ -1,38 +0,0 @@
----
-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
-
-{{% include "/_common/methods/page/output-format-methods.md" %}}
-
-## Example
-
-To link to the RSS feed for the current page:
-
-```go-html-template
-{{ with .OutputFormats.Get "rss" }}
- RSS Feed
-{{ end }}
-```
-
-On the site's home page, Hugo renders this to:
-
-```html
-RSS Feed
-```
-
-Please see the [link to output formats] section to understand the importance of the construct above.
-
-[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
deleted file mode 100644
index 7c7728b2f..000000000
--- a/docs/content/en/methods/page/Page.md
+++ /dev/null
@@ -1,35 +0,0 @@
----
-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 [shortcodes](g) and page templates.
-
-```go-html-template {file="layouts/shortcodes/foo.html"}
-{{ partial "my-partial.html" . }}
-```
-
-When the shortcode calls the partial, it passes the current [context](g) (the dot). The context includes identifiers such as `Page`, `Params`, `Inner`, and `Name`.
-
-```go-html-template {file="layouts/_default/single.html"}
-{{ partial "my-partial.html" . }}
-```
-
-When the page template calls the partial, 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
deleted file mode 100644
index ba43c36a8..000000000
--- a/docs/content/en/methods/page/Pages.md
+++ /dev/null
@@ -1,82 +0,0 @@
----
-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
deleted file mode 100644
index 0b699d6b2..000000000
--- a/docs/content/en/methods/page/Paginate.md
+++ /dev/null
@@ -1,47 +0,0 @@
----
-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 [site 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 on the [home template], [section templates], [taxonomy templates], and [term templates].
-
-```go-html-template {file="layouts/_default/list.html"}
-{{ $pages := where .Site.RegularPages "Section" "articles" }}
-{{ $pages = $pages.ByTitle }}
-{{ range (.Paginate $pages 7).Pages }}
-
-{{ end }}
-{{ template "_internal/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 template]: /templates/types/#home
-[section templates]: /templates/types/#section
-[site configuration]: /configuration/pagination/
-[taxonomy templates]: /templates/types/#taxonomy
-[term templates]: /templates/types/#term
diff --git a/docs/content/en/methods/page/Paginator.md b/docs/content/en/methods/page/Paginator.md
deleted file mode 100644
index bff7ea90c..000000000
--- a/docs/content/en/methods/page/Paginator.md
+++ /dev/null
@@ -1,40 +0,0 @@
----
-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 [site configuration]. The default is `10`.
-
-You can invoke pagination on the [home template], [section templates], [taxonomy templates], 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/_default/list.html"}
-{{ range .Paginator.Pages }}
-
-{{ end }}
-{{ template "_internal/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 template]: /templates/types/#home
-[section templates]: /templates/types/#section
-[site configuration]: /configuration/pagination/
-[taxonomy templates]: /templates/types/#taxonomy
-[term templates]: /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
deleted file mode 100644
index b07c1cd92..000000000
--- a/docs/content/en/methods/page/Param.md
+++ /dev/null
@@ -1,48 +0,0 @@
----
-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
deleted file mode 100644
index eeb253437..000000000
--- a/docs/content/en/methods/page/Params.md
+++ /dev/null
@@ -1,42 +0,0 @@
----
-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
deleted file mode 100644
index 0946a7993..000000000
--- a/docs/content/en/methods/page/Parent.md
+++ /dev/null
@@ -1,52 +0,0 @@
----
-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
deleted file mode 100644
index db4e7d629..000000000
--- a/docs/content/en/methods/page/Path.md
+++ /dev/null
@@ -1,140 +0,0 @@
----
-title: Path
-description: Returns the logical path of the given page.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: string
- signatures: [PAGE.Path]
----
-
-{{< new-in 0.123.0 />}}
-
-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 site
-
-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
deleted file mode 100644
index cc74f3342..000000000
--- a/docs/content/en/methods/page/Permalink.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-title: Permalink
-description: Returns the permalink of the given page.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: string
- signatures: [PAGE.Permalink]
----
-
-Site 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
deleted file mode 100644
index 65d11166e..000000000
--- a/docs/content/en/methods/page/Plain.md
+++ /dev/null
@@ -1,23 +0,0 @@
----
-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
deleted file mode 100644
index 5749a21f9..000000000
--- a/docs/content/en/methods/page/PlainWords.md
+++ /dev/null
@@ -1,31 +0,0 @@
----
-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
deleted file mode 100644
index 5a6e2162d..000000000
--- a/docs/content/en/methods/page/Prev.md
+++ /dev/null
@@ -1,12 +0,0 @@
----
-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
deleted file mode 100644
index 14d3ca082..000000000
--- a/docs/content/en/methods/page/PrevInSection.md
+++ /dev/null
@@ -1,12 +0,0 @@
----
-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
deleted file mode 100644
index 7500a08aa..000000000
--- a/docs/content/en/methods/page/PublishDate.md
+++ /dev/null
@@ -1,32 +0,0 @@
----
-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 site. 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
deleted file mode 100644
index 41215ef53..000000000
--- a/docs/content/en/methods/page/RawContent.md
+++ /dev/null
@@ -1,23 +0,0 @@
----
-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
deleted file mode 100644
index 1bd7dea31..000000000
--- a/docs/content/en/methods/page/ReadingTime.md
+++ /dev/null
@@ -1,47 +0,0 @@
----
-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 site using site parameters.
-
-{{< code-toggle file=hugo >}}
-[languages]
- [languages.de]
- contentDir = 'content/de'
- languageCode = 'de-DE'
- languageName = 'Deutsch'
- weight = 2
- [languages.de.params]
- reading_speed = 179
- [languages.en]
- contentDir = 'content/en'
- languageCode = 'en-US'
- languageName = 'English'
- 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
deleted file mode 100644
index 35f9460ba..000000000
--- a/docs/content/en/methods/page/Ref.md
+++ /dev/null
@@ -1,37 +0,0 @@
----
-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
deleted file mode 100644
index 761de3af5..000000000
--- a/docs/content/en/methods/page/RegularPages.md
+++ /dev/null
@@ -1,79 +0,0 @@
----
-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
deleted file mode 100644
index d85cd0b48..000000000
--- a/docs/content/en/methods/page/RegularPagesRecursive.md
+++ /dev/null
@@ -1,83 +0,0 @@
----
-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/
-├── _default/
-│ ├── baseof.html
-│ ├── home.html
-│ ├── li.html <-- used for other content types
-│ ├── list.html
-│ ├── single.html
-│ └── summary.html
-└── books/
- ├── li.html <-- used when content type is "books"
- └── summary.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/_default/li.html
-```
-
-See [content views] for more examples.
-
-[content views]: /templates/content-view/
-[`partial`]: /functions/partials/include/
diff --git a/docs/content/en/methods/page/RenderShortcodes.md b/docs/content/en/methods/page/RenderShortcodes.md
deleted file mode 100644
index d124606f0..000000000
--- a/docs/content/en/methods/page/RenderShortcodes.md
+++ /dev/null
@@ -1,89 +0,0 @@
----
-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]
----
-
-{{< new-in 0.117.0 />}}
-
-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
deleted file mode 100644
index c7774774c..000000000
--- a/docs/content/en/methods/page/RenderString.md
+++ /dev/null
@@ -1,49 +0,0 @@
----
-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://www.pandoc.org/
diff --git a/docs/content/en/methods/page/Resources.md b/docs/content/en/methods/page/Resources.md
deleted file mode 100644
index dd472de88..000000000
--- a/docs/content/en/methods/page/Resources.md
+++ /dev/null
@@ -1,90 +0,0 @@
----
-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](g) pattern, 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](g) pattern, 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](g) pattern.
-
-{{% 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/Scratch.md b/docs/content/en/methods/page/Scratch.md
deleted file mode 100644
index 61c5dc19e..000000000
--- a/docs/content/en/methods/page/Scratch.md
+++ /dev/null
@@ -1,21 +0,0 @@
----
-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
deleted file mode 100644
index 04c6a8a24..000000000
--- a/docs/content/en/methods/page/Section.md
+++ /dev/null
@@ -1,54 +0,0 @@
----
-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
deleted file mode 100644
index 12f0a8c24..000000000
--- a/docs/content/en/methods/page/Sections.md
+++ /dev/null
@@ -1,62 +0,0 @@
----
-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
deleted file mode 100644
index 4649e5e00..000000000
--- a/docs/content/en/methods/page/Site.md
+++ /dev/null
@@ -1,18 +0,0 @@
----
-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
deleted file mode 100644
index bb1360493..000000000
--- a/docs/content/en/methods/page/Sitemap.md
+++ /dev/null
@@ -1,81 +0,0 @@
----
-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 the site 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
-
-{{< new-in 0.125.0 />}}
-
-(`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 site 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/_default/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
deleted file mode 100644
index 8677226d7..000000000
--- a/docs/content/en/methods/page/Sites.md
+++ /dev/null
@@ -1,68 +0,0 @@
----
-title: Sites
-description: Returns a collection of all Site objects, one for each language, ordered by language weight.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: page.Sites
- signatures: [PAGE.Sites]
----
-
-This is a convenience method to access `.Site.Sites`.
-
-With this site configuration:
-
-{{< code-toggle file=hugo >}}
-defaultContentLanguage = 'de'
-defaultContentLanguageInSubdir = false
-
-[languages.de]
-languageCode = 'de-DE'
-languageDirection = 'ltr'
-languageName = 'Deutsch'
-title = 'Projekt Dokumentation'
-weight = 1
-
-[languages.en]
-languageCode = 'en-US'
-languageDirection = 'ltr'
-languageName = 'English'
-title = 'Project Documentation'
-weight = 2
-{{< /code-toggle >}}
-
-This template:
-
-```go-html-template
-
-```
-
-To render a link to the home page of the site corresponding to the default content language:
-
-```go-html-template
-{{ with .Sites.Default }}
- {{ .Title }}
-{{ end }}
-```
-
-This is equivalent to:
-
-```go-html-template
-{{ with index .Sites 0 }}
- {{ .Title }}
-{{ end }}
-```
diff --git a/docs/content/en/methods/page/Slug.md b/docs/content/en/methods/page/Slug.md
deleted file mode 100644
index 34000b660..000000000
--- a/docs/content/en/methods/page/Slug.md
+++ /dev/null
@@ -1,25 +0,0 @@
----
-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
deleted file mode 100644
index 0b1049b0a..000000000
--- a/docs/content/en/methods/page/Store.md
+++ /dev/null
@@ -1,35 +0,0 @@
----
-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, a partial template called by a shortcode, or by a Markdown render hook. 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
deleted file mode 100644
index 9158e571d..000000000
--- a/docs/content/en/methods/page/Summary.md
+++ /dev/null
@@ -1,44 +0,0 @@
----
-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 }}
-```
-
-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
deleted file mode 100644
index 8c2573069..000000000
--- a/docs/content/en/methods/page/Truncated.md
+++ /dev/null
@@ -1,29 +0,0 @@
----
-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
deleted file mode 100644
index 6f855fbe3..000000000
--- a/docs/content/en/methods/page/Type.md
+++ /dev/null
@@ -1,51 +0,0 @@
----
-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
deleted file mode 100644
index c14af0257..000000000
--- a/docs/content/en/methods/page/Weight.md
+++ /dev/null
@@ -1,25 +0,0 @@
----
-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
deleted file mode 100644
index 3950244ca..000000000
--- a/docs/content/en/methods/page/WordCount.md
+++ /dev/null
@@ -1,18 +0,0 @@
----
-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
deleted file mode 100644
index c7ae7ad5d..000000000
--- a/docs/content/en/methods/page/_index.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-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
deleted file mode 100644
index 9cd58989b..000000000
--- a/docs/content/en/methods/pager/First.md
+++ /dev/null
@@ -1,38 +0,0 @@
----
-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/_index.md b/docs/content/en/methods/pager/_index.md
deleted file mode 100644
index 7a79bf42f..000000000
--- a/docs/content/en/methods/pager/_index.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Pager methods
-linkTitle: Pager
-description: Use these methods with Pager objects 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
deleted file mode 100644
index 18f1b985e..000000000
--- a/docs/content/en/methods/pages/ByDate.md
+++ /dev/null
@@ -1,28 +0,0 @@
----
-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 [site configuration], defaulting to the `date` field in front matter.
-
-[site 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
deleted file mode 100644
index 703988c4e..000000000
--- a/docs/content/en/methods/pages/ByExpiryDate.md
+++ /dev/null
@@ -1,28 +0,0 @@
----
-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 [site configuration], defaulting to the `expiryDate` field in front matter.
-
-[site configuration]: /configuration/front-matter/#dates
-
-```go-html-template
-{{ range .Pages.ByExpiryDate }}
-
-{{ end }}
-```
diff --git a/docs/content/en/methods/pages/ByLastmod.md b/docs/content/en/methods/pages/ByLastmod.md
deleted file mode 100644
index 3c03d2a6e..000000000
--- a/docs/content/en/methods/pages/ByLastmod.md
+++ /dev/null
@@ -1,28 +0,0 @@
----
-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 [site configuration], defaulting to the `lastmod` field in front matter.
-
-[site 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
deleted file mode 100644
index 4a024d25a..000000000
--- a/docs/content/en/methods/pages/ByLinkTitle.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-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
deleted file mode 100644
index 9544122a6..000000000
--- a/docs/content/en/methods/pages/ByParam.md
+++ /dev/null
@@ -1,34 +0,0 @@
----
-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 site 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
deleted file mode 100644
index 3dde6fd95..000000000
--- a/docs/content/en/methods/pages/ByPublishDate.md
+++ /dev/null
@@ -1,28 +0,0 @@
----
-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 [site configuration], defaulting to the `publishDate` field in front matter.
-
-[site 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
deleted file mode 100644
index ba255d3c3..000000000
--- a/docs/content/en/methods/pages/ByWeight.md
+++ /dev/null
@@ -1,26 +0,0 @@
----
-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
deleted file mode 100644
index 7ef4843a4..000000000
--- a/docs/content/en/methods/pages/GroupByDate.md
+++ /dev/null
@@ -1,63 +0,0 @@
----
-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 [site 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
-[site 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
deleted file mode 100644
index d209e6c2b..000000000
--- a/docs/content/en/methods/pages/GroupByExpiryDate.md
+++ /dev/null
@@ -1,63 +0,0 @@
----
-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 [site 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
-[site 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
deleted file mode 100644
index 8729cd3c9..000000000
--- a/docs/content/en/methods/pages/GroupByLastmod.md
+++ /dev/null
@@ -1,63 +0,0 @@
----
-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 [site 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
-[site 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
deleted file mode 100644
index b05a096d2..000000000
--- a/docs/content/en/methods/pages/GroupByParamDate.md
+++ /dev/null
@@ -1,60 +0,0 @@
----
-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
deleted file mode 100644
index 50e12f085..000000000
--- a/docs/content/en/methods/pages/GroupByPublishDate.md
+++ /dev/null
@@ -1,63 +0,0 @@
----
-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 [site 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
-[site 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
deleted file mode 100644
index ce091c1ab..000000000
--- a/docs/content/en/methods/pages/Next.md
+++ /dev/null
@@ -1,12 +0,0 @@
----
-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
deleted file mode 100644
index 004b9496d..000000000
--- a/docs/content/en/methods/pages/Prev.md
+++ /dev/null
@@ -1,12 +0,0 @@
----
-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
deleted file mode 100644
index 22eeb4dfa..000000000
--- a/docs/content/en/methods/pages/Related.md
+++ /dev/null
@@ -1,75 +0,0 @@
----
-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/_default/single.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
deleted file mode 100644
index 23c4b0324..000000000
--- a/docs/content/en/methods/pages/Reverse.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-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
deleted file mode 100644
index f2495ae49..000000000
--- a/docs/content/en/methods/pages/_index.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-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
deleted file mode 100644
index 14d0a40d8..000000000
--- a/docs/content/en/methods/resource/Colors.md
+++ /dev/null
@@ -1,174 +0,0 @@
----
-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 `Resources.Colors` method returns a slice of the most dominant colors in an image, ordered from most dominant to least dominant. This method is fast, but if you also downsize your image you can improve performance by extracting the colors from the scaled image.
-
-## Methods
-
-Each color is an object with the following methods:
-
-### ColorHex
-
-{{< new-in 0.125.0 />}}
-
-(`string`) Returns the [hexadecimal color] value, prefixed with a hash sign.
-
-### Luminance
-
-{{< new-in 0.125.0 />}}
-
-(`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 }}
-```
-
-[`images.Dither`]: /functions/images/dither/
-[`images.Padding`]: /functions/images/padding/
-[`images.Text`]: /functions/images/text/
-[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
-[WCAG]: https://en.wikipedia.org/wiki/Web_Content_Accessibility_Guidelines
diff --git a/docs/content/en/methods/resource/Content.md b/docs/content/en/methods/resource/Content.md
deleted file mode 100644
index 2c2c12d3a..000000000
--- a/docs/content/en/methods/resource/Content.md
+++ /dev/null
@@ -1,60 +0,0 @@
----
-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
deleted file mode 100644
index 97b3b95d3..000000000
--- a/docs/content/en/methods/resource/Crop.md
+++ /dev/null
@@ -1,42 +0,0 @@
----
-title: Crop
-description: Applicable to images, returns an image resource cropped to the given dimensions without resizing.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: images.ImageResource
- signatures: [RESOURCE.Crop SPEC]
----
-
-{{% include "/_common/methods/resource/global-page-remote-resources.md" %}}
-
-Crop an image to match the given dimensions without resizing. You must provide both width and height.
-
-```go-html-template
-{{ with resources.Get "images/original.jpg" }}
- {{ with .Crop "200x200" }}
-
- {{ end }}
-{{ end }}
-```
-
-{{% include "/_common/methods/resource/processing-spec.md" %}}
-
-## Example
-
-```go-html-template
-{{ with resources.Get "images/original.jpg" }}
- {{ with .Crop "200x200 topright webp q85 lanczos" }}
-
- {{ end }}
-{{ end }}
-```
-
-{{< img
- src="images/examples/zion-national-park.jpg"
- alt="Zion National Park"
- filter="Process"
- filterArgs="crop 200x200 topright webp q85 lanczos"
- example=true
->}}
diff --git a/docs/content/en/methods/resource/Data.md b/docs/content/en/methods/resource/Data.md
deleted file mode 100644
index 0709ca50a..000000000
--- a/docs/content/en/methods/resource/Data.md
+++ /dev/null
@@ -1,66 +0,0 @@
----
-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.
-
-[`resources.GetRemote`]: /functions/resources/getremote/
-
-## 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
deleted file mode 100644
index 591af8266..000000000
--- a/docs/content/en/methods/resource/Err.md
+++ /dev/null
@@ -1,60 +0,0 @@
----
-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 >}}
-
-The `Err` method on a resource returned by the [`resources.GetRemote`] function returns an error message if the HTTP request fails, else nil. If you do not handle the error yourself, Hugo will fail the build.
-
-[`resources.GetRemote`]: /functions/resources/getremote/
-
-In this example we send an HTTP request to a nonexistent domain:
-
-```go-html-template
-{{ $url := "https://broken-example.org/images/a.jpg" }}
-{{ with resources.GetRemote $url }}
- {{ with .Err }}
- {{ errorf "%s" . }}
- {{ else }}
-
- {{ end }}
-{{ else }}
- {{ errorf "Unable to get remote resource %q" $url }}
-{{ end }}
-```
-
-The code above captures the error from the HTTP request, then fails the build:
-
-```text
-ERROR error calling resources.GetRemote: Get "https://broken-example.org/images/a.jpg": dial tcp: lookup broken-example.org on 127.0.0.53:53: no such host
-```
-
-To log an error as a warning instead of an error:
-
-```go-html-template
-{{ $url := "https://broken-example.org/images/a.jpg" }}
-{{ with resources.GetRemote $url }}
- {{ with .Err }}
- {{ warnf "%s" . }}
- {{ else }}
-
- {{ end }}
-{{ else }}
- {{ errorf "Unable to get remote resource %q" $url }}
-{{ end }}
-```
-
-> [!note]
-> An HTTP response with a 404 status code is not an HTTP request error. To handle 404 status codes, code defensively using the nested `with-else-end` construct as shown above.
diff --git a/docs/content/en/methods/resource/Exif.md b/docs/content/en/methods/resource/Exif.md
deleted file mode 100644
index 443a0ee1a..000000000
--- a/docs/content/en/methods/resource/Exif.md
+++ /dev/null
@@ -1,82 +0,0 @@
----
-title: Exif
-description: Applicable to JPEG, PNG, TIFF, and WebP images, returns an EXIF object containing image metadata.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: exif.ExifInfo
- signatures: [RESOURCE.Exif]
----
-
-{{% include "/_common/methods/resource/global-page-remote-resources.md" %}}
-
-Applicable to JPEG, PNG, TIFF, and WebP images, the `Exif` method on an image `Resource` object returns an [EXIF] object containing image metadata.
-
-## Methods
-
-### Date
-
-(`time.Time`) Returns the image creation date/time. Format with the [`time.Format`] function.
-
-### Lat
-
-(`float64`) Returns the GPS latitude in degrees.
-
-### Long
-
-(`float64`) Returns the GPS longitude in degrees.
-
-### Tags
-
-(`exif.Tags`) Returns a collection of the available EXIF tags for this image. You may include or exclude specific tags from this collection. See [configure imaging].
-
-[configure imaging]: /configuration/imaging/#exif-data
-
-## Examples
-
-To list the creation date, location, and EXIF tags:
-
-```go-html-template
-{{ with resources.Get "images/a.jpg" }}
- {{ with .Exif }}
-
Date: {{ .Date }}
-
Lat/Long: {{ .Lat }}/{{ .Long }}
- {{ with .Tags }}
-
Tags
-
-
-
Tag
Value
-
-
- {{ range $k, $v := . }}
-
{{ $k }}
{{ $v }}
- {{ end }}
-
-
- {{ end }}
- {{ end }}
-{{ end }}
-```
-
-To list specific values:
-
-```go-html-template
-{{ with resources.Get "images/a.jpg" }}
- {{ with .Exif }}
-
- {{ with .Date }}
Date: {{ .Format "January 02, 2006" }}
{{ end }}
- {{ with .Tags.ApertureValue }}
Aperture: {{ lang.FormatNumber 2 . }}
{{ end }}
- {{ with .Tags.BrightnessValue }}
Brightness: {{ lang.FormatNumber 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 }}
-
- {{ end }}
-{{ end }}
-```
-
-[exif]: https://en.wikipedia.org/wiki/Exif
-[`time.Format`]: /functions/time/format/
diff --git a/docs/content/en/methods/resource/Fill.md b/docs/content/en/methods/resource/Fill.md
deleted file mode 100644
index 82c696c91..000000000
--- a/docs/content/en/methods/resource/Fill.md
+++ /dev/null
@@ -1,42 +0,0 @@
----
-title: Fill
-description: Applicable to images, returns an image resource cropped and resized to the given dimensions.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: images.ImageResource
- signatures: [RESOURCE.Fill SPEC]
----
-
-{{% include "/_common/methods/resource/global-page-remote-resources.md" %}}
-
-Crop and resize an image to match the given dimensions. You must provide both width and height.
-
-```go-html-template
-{{ with resources.Get "images/original.jpg" }}
- {{ with .Fill "200x200" }}
-
- {{ end }}
-{{ end }}
-```
-
-{{% include "/_common/methods/resource/processing-spec.md" %}}
-
-## Example
-
-```go-html-template
-{{ with resources.Get "images/original.jpg" }}
- {{ with .Fill "200x200 top webp q85 lanczos" }}
-
- {{ end }}
-{{ end }}
-```
-
-{{< img
- src="images/examples/zion-national-park.jpg"
- alt="Zion National Park"
- filter="Process"
- filterArgs="fill 200x200 top webp q85 lanczos"
- example=true
->}}
diff --git a/docs/content/en/methods/resource/Filter.md b/docs/content/en/methods/resource/Filter.md
deleted file mode 100644
index b83c3d8cb..000000000
--- a/docs/content/en/methods/resource/Filter.md
+++ /dev/null
@@ -1,66 +0,0 @@
----
-title: Filter
-description: Applicable to images, applies one or more image filters to the given image resource.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: images.ImageResource
- signatures: [RESOURCE.Filter FILTER...]
----
-
-{{% include "/_common/methods/resource/global-page-remote-resources.md" %}}
-
-Apply one or more [image filters](#image-filters) to the given image.
-
-To apply a single filter:
-
-```go-html-template
-{{ with resources.Get "images/original.jpg" }}
- {{ with .Filter images.Grayscale }}
-
- {{ end }}
-{{ end }}
-```
-
-To apply two or more filters, executing from left to right:
-
-```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.
-
-[`images.Filter`]: /functions/images/filter/
-
-## 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.
-
-{{% list-pages-in-section path=/functions/images filter=functions_images_no_filters filterType=exclude %}}
diff --git a/docs/content/en/methods/resource/Fit.md b/docs/content/en/methods/resource/Fit.md
deleted file mode 100644
index 7b416c4a1..000000000
--- a/docs/content/en/methods/resource/Fit.md
+++ /dev/null
@@ -1,42 +0,0 @@
----
-title: Fit
-description: Applicable to images, returns an image resource downscaled to fit the given dimensions while maintaining aspect ratio.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: images.ImageResource
- signatures: [RESOURCE.Fit SPEC]
----
-
-{{% include "/_common/methods/resource/global-page-remote-resources.md" %}}
-
-Downscale an image to fit the given dimensions while maintaining aspect ratio. You must provide both width and height.
-
-```go-html-template
-{{ with resources.Get "images/original.jpg" }}
- {{ with .Fit "200x200" }}
-
- {{ end }}
-{{ end }}
-```
-
-{{% include "/_common/methods/resource/processing-spec.md" %}}
-
-## Example
-
-```go-html-template
-{{ with resources.Get "images/original.jpg" }}
- {{ with .Fit "300x175 webp q85 lanczos" }}
-
- {{ end }}
-{{ end }}
-```
-
-{{< img
- src="images/examples/zion-national-park.jpg"
- alt="Zion National Park"
- filter="Process"
- filterArgs="fit 300x175 webp q85 lanczos"
- example=true
->}}
diff --git a/docs/content/en/methods/resource/Height.md b/docs/content/en/methods/resource/Height.md
deleted file mode 100644
index cc131378a..000000000
--- a/docs/content/en/methods/resource/Height.md
+++ /dev/null
@@ -1,26 +0,0 @@
----
-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" %}}
-
-```go-html-template
-{{ with resources.Get "images/a.jpg" }}
- {{ .Height }} → 400
-{{ end }}
-```
-
-Use the `Width` and `Height` methods together when rendering an `img` element:
-
-```go-html-template
-{{ with resources.Get "images/a.jpg" }}
-
-{{ end }}
-```
diff --git a/docs/content/en/methods/resource/MediaType.md b/docs/content/en/methods/resource/MediaType.md
deleted file mode 100644
index 7721f69ba..000000000
--- a/docs/content/en/methods/resource/MediaType.md
+++ /dev/null
@@ -1,66 +0,0 @@
----
-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/Name.md b/docs/content/en/methods/resource/Name.md
deleted file mode 100644
index c678c96c9..000000000
--- a/docs/content/en/methods/resource/Name.md
+++ /dev/null
@@ -1,88 +0,0 @@
----
-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
deleted file mode 100644
index 38f2ef6c2..000000000
--- a/docs/content/en/methods/resource/Params.md
+++ /dev/null
@@ -1,61 +0,0 @@
----
-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
deleted file mode 100644
index a8ec2d323..000000000
--- a/docs/content/en/methods/resource/Permalink.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-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
deleted file mode 100644
index fb27da54e..000000000
--- a/docs/content/en/methods/resource/Process.md
+++ /dev/null
@@ -1,60 +0,0 @@
----
-title: Process
-description: Applicable to images, returns an image resource processed with the given specification.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: images.ImageResource
- signatures: [RESOURCE.Process SPEC]
----
-
-{{% include "/_common/methods/resource/global-page-remote-resources.md" %}}
-
-Process an image with the given specification. The specification can contain an optional action, one of `crop`, `fill`, `fit`, or `resize`. This means that you can use this method instead of [`Crop`], [`Fill`], [`Fit`], or [`Resize`].
-
-```go-html-template
-{{ with resources.Get "images/original.jpg" }}
- {{ with .Process "crop 200x200" }}
-
- {{ end }}
-{{ end }}
-```
-
-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, which 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 q85 lanczos" }}
-
- {{ end }}
-{{ end }}
-```
-
-{{< img
- src="images/examples/zion-national-park.jpg"
- alt="Zion National Park"
- filter="Process"
- filterArgs="crop 200x200 topright webp q85 lanczos"
- example=true
->}}
-
-[`Crop`]: /methods/resource/crop/
-[`Fill`]: /methods/resource/fill/
-[`Fit`]: /methods/resource/fit/
-[`Resize`]: /methods/resource/resize/
-[`images.Process`]: /functions/images/process/
diff --git a/docs/content/en/methods/resource/Publish.md b/docs/content/en/methods/resource/Publish.md
deleted file mode 100644
index 0ecdf7e74..000000000
--- a/docs/content/en/methods/resource/Publish.md
+++ /dev/null
@@ -1,32 +0,0 @@
----
-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
deleted file mode 100644
index d4c907bff..000000000
--- a/docs/content/en/methods/resource/RelPermalink.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-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
deleted file mode 100644
index 93c029ba6..000000000
--- a/docs/content/en/methods/resource/Resize.md
+++ /dev/null
@@ -1,44 +0,0 @@
----
-title: Resize
-description: Applicable to images, returns an image resource resized to the given width and/or height.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: images.ImageResource
- signatures: [RESOURCE.Resize SPEC]
----
-
-{{% include "/_common/methods/resource/global-page-remote-resources.md" %}}
-
-Resize an image to the given width and/or height.
-
-If you specify both width and height, the resulting image will be disproportionally scaled unless the original image has the same aspect ratio.
-
-```go-html-template
-{{ with resources.Get "images/original.jpg" }}
- {{ with .Resize "300x" }}
-
- {{ end }}
-{{ end }}
-```
-
-{{% include "/_common/methods/resource/processing-spec.md" %}}
-
-## Example
-
-```go-html-template
-{{ with resources.Get "images/original.jpg" }}
- {{ with .Resize "300x webp q85 lanczos" }}
-
- {{ end }}
-{{ end }}
-```
-
-{{< img
- src="images/examples/zion-national-park.jpg"
- alt="Zion National Park"
- filter="Process"
- filterArgs="resize 300x webp q85 lanczos"
- example=true
->}}
diff --git a/docs/content/en/methods/resource/ResourceType.md b/docs/content/en/methods/resource/ResourceType.md
deleted file mode 100644
index 0ea9c0cf9..000000000
--- a/docs/content/en/methods/resource/ResourceType.md
+++ /dev/null
@@ -1,43 +0,0 @@
----
-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/single.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
deleted file mode 100644
index c02d29ff8..000000000
--- a/docs/content/en/methods/resource/Title.md
+++ /dev/null
@@ -1,81 +0,0 @@
----
-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
deleted file mode 100644
index e1b43f44c..000000000
--- a/docs/content/en/methods/resource/Width.md
+++ /dev/null
@@ -1,26 +0,0 @@
----
-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" %}}
-
-```go-html-template
-{{ with resources.Get "images/a.jpg" }}
- {{ .Width }} → 600
-{{ end }}
-```
-
-Use the `Width` and `Height` methods together when rendering an `img` element:
-
-```go-html-template
-{{ with resources.Get "images/a.jpg" }}
-
-{{ end }}
-```
diff --git a/docs/content/en/methods/resource/_index.md b/docs/content/en/methods/resource/_index.md
deleted file mode 100644
index edfbc5b14..000000000
--- a/docs/content/en/methods/resource/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Resource methods
-linkTitle: Resource
-description: Use these methods with global, page, and remote Resource objects.
-categories: []
-keywords: []
----
diff --git a/docs/content/en/methods/shortcode/Get.md b/docs/content/en/methods/shortcode/Get.md
deleted file mode 100644
index b9c01cfc4..000000000
--- a/docs/content/en/methods/shortcode/Get.md
+++ /dev/null
@@ -1,46 +0,0 @@
----
-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
deleted file mode 100644
index cdce4c1c3..000000000
--- a/docs/content/en/methods/shortcode/Inner.md
+++ /dev/null
@@ -1,143 +0,0 @@
----
-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/0.30/#indented-code-blocks
-[Markdown notation]: /content-management/shortcodes/#notation
-[raw HTML blocks]: https://spec.commonmark.org/0.31.2/#html-blocks
-[security model]: /about/security/
diff --git a/docs/content/en/methods/shortcode/InnerDeindent.md b/docs/content/en/methods/shortcode/InnerDeindent.md
deleted file mode 100644
index 0b8c8e2d8..000000000
--- a/docs/content/en/methods/shortcode/InnerDeindent.md
+++ /dev/null
@@ -1,98 +0,0 @@
----
-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/0.30/#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
deleted file mode 100644
index 1e0a7f00e..000000000
--- a/docs/content/en/methods/shortcode/IsNamedParams.md
+++ /dev/null
@@ -1,29 +0,0 @@
----
-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
deleted file mode 100644
index b5f9b6c17..000000000
--- a/docs/content/en/methods/shortcode/Name.md
+++ /dev/null
@@ -1,27 +0,0 @@
----
-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
deleted file mode 100644
index def0c016f..000000000
--- a/docs/content/en/methods/shortcode/Ordinal.md
+++ /dev/null
@@ -1,52 +0,0 @@
----
-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
deleted file mode 100644
index 774caf9fc..000000000
--- a/docs/content/en/methods/shortcode/Page.md
+++ /dev/null
@@ -1,36 +0,0 @@
----
-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
deleted file mode 100644
index f001e737f..000000000
--- a/docs/content/en/methods/shortcode/Params.md
+++ /dev/null
@@ -1,32 +0,0 @@
----
-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
deleted file mode 100644
index 91c445d2a..000000000
--- a/docs/content/en/methods/shortcode/Parent.md
+++ /dev/null
@@ -1,50 +0,0 @@
----
-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"}
-
-```
-
-```go-html-template {file="layouts/shortcodes/now.html"}
-{{- $dateFormat := "January 2, 2006 15:04:05" }}
-
-{{- with .Params }}
- {{- with .dateFormat }}
- {{- $dateFormat = . }}
- {{- end }}
-{{- else }}
- {{- with .Parent.Params }}
- {{- with .dateFormat }}
- {{- $dateFormat = . }}
- {{- end }}
- {{- end }}
-{{- end }}
-
-{{- now | time.Format $dateFormat -}}
-```
-
-The "now" shortcode formats the current time using:
-
-1. The `dateFormat` argument passed to the "now" shortcode, if present
-1. The `dateFormat` argument passed to the "greeting" shortcode, if present
-1. The default layout string defined at the top of the shortcode
diff --git a/docs/content/en/methods/shortcode/Position.md b/docs/content/en/methods/shortcode/Position.md
deleted file mode 100644
index 24810e825..000000000
--- a/docs/content/en/methods/shortcode/Position.md
+++ /dev/null
@@ -1,30 +0,0 @@
----
-title: Position
-description: Returns the file name and position from which the shortcode was called.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: text.Position
- signatures: [SHORTCODE.Position]
----
-
-The `Position` 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"
-```
-
-> [!note]
-> The position can be expensive to calculate. Limit its use to error reporting.
diff --git a/docs/content/en/methods/shortcode/Ref.md b/docs/content/en/methods/shortcode/Ref.md
deleted file mode 100644
index 3a877d568..000000000
--- a/docs/content/en/methods/shortcode/Ref.md
+++ /dev/null
@@ -1,37 +0,0 @@
----
-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: [SHORTCODE.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/shortcode/RelRef.md b/docs/content/en/methods/shortcode/RelRef.md
deleted file mode 100644
index 273705a95..000000000
--- a/docs/content/en/methods/shortcode/RelRef.md
+++ /dev/null
@@ -1,37 +0,0 @@
----
-title: RelRef
-description: Returns the relative URL of the page with the given path, language, and output format.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: string
- signatures: [SHORTCODE.RelRef OPTIONS]
----
-
-## Usage
-
-The `RelRef` 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" }}
-{{ .RelRef $opts }} → /en/books/book-1/
-
-{{ $opts := dict "path" "/books/book-1" "lang" "de" }}
-{{ .RelRef $opts }} → /de/books/book-1/
-
-{{ $opts := dict "path" "/books/book-1" "lang" "de" "outputFormat" "json" }}
-{{ .RelRef $opts }} → /de/books/book-1/index.json
-```
-
-## Error handling
-
-{{% include "_common/ref-and-relref-error-handling.md" %}}
diff --git a/docs/content/en/methods/shortcode/Scratch.md b/docs/content/en/methods/shortcode/Scratch.md
deleted file mode 100644
index 6efec2097..000000000
--- a/docs/content/en/methods/shortcode/Scratch.md
+++ /dev/null
@@ -1,21 +0,0 @@
----
-title: Scratch
-description: Returns a "scratch pad" to store and manipulate data, scoped to the current shortcode.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: maps.Scratch
- signatures: [SHORTCODE.Scratch]
-expiryDate: 2026-11-18 # deprecated 2024-11-18 (soft)
----
-
-{{< deprecated-in 0.139.0 >}}
-Use the [`SHORTCODE.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 `SHORTCODE.Store` as soon as possible.
-
-Beginning with v0.139.0 the `SHORTCODE.Scratch` method is aliased to `SHORTCODE.Store`.
-
-[`SHORTCODE.Store`]: /methods/shortcode/store/
-{{< /deprecated-in >}}
diff --git a/docs/content/en/methods/shortcode/Site.md b/docs/content/en/methods/shortcode/Site.md
deleted file mode 100644
index 4c5a9a9b5..000000000
--- a/docs/content/en/methods/shortcode/Site.md
+++ /dev/null
@@ -1,18 +0,0 @@
----
-title: Site
-description: Returns the Site object.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: page.siteWrapper
- signatures: [SHORTCODE.Site]
----
-
-See [Site methods].
-
-[Site methods]: /methods/site/
-
-```go-html-template
-{{ .Site.Title }}
-```
diff --git a/docs/content/en/methods/shortcode/Store.md b/docs/content/en/methods/shortcode/Store.md
deleted file mode 100644
index 76cb9237d..000000000
--- a/docs/content/en/methods/shortcode/Store.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-title: Store
-description: Returns a "scratch pad" to store and manipulate data, scoped to the current shortcode.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: maps.Scratch
- signatures: [SHORTCODE.Store]
----
-
-{{< new-in 0.139.0 />}}
-
-Use the `Store` method to create a [scratch pad](g) to store and manipulate data, scoped to the current shortcode. To create a scratch pad with a different [scope](g), refer to the [scope](#scope) section below.
-
-> [!note]
-> With the introduction of the [`newScratch`] function, and the ability to [assign values to template variables] after initialization, the `Store` method within a shortcode is mostly obsolete.
-
-{{% include "_common/store-methods.md" %}}
-
-{{% include "_common/scratch-pad-scope.md" %}}
-
-[`newScratch`]: /functions/collections/newScratch/
-[assign values to template variables]: https://go.dev/doc/go1.11#texttemplatepkgtexttemplate
diff --git a/docs/content/en/methods/shortcode/_index.md b/docs/content/en/methods/shortcode/_index.md
deleted file mode 100644
index 0064f42aa..000000000
--- a/docs/content/en/methods/shortcode/_index.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: Shortcode methods
-linkTitle: Shortcode
-description: Use these methods in your shortcode templates.
-categories: []
-keywords: []
-aliases: [/variables/shortcodes]
----
diff --git a/docs/content/en/methods/site/AllPages.md b/docs/content/en/methods/site/AllPages.md
deleted file mode 100644
index 90cceee8c..000000000
--- a/docs/content/en/methods/site/AllPages.md
+++ /dev/null
@@ -1,22 +0,0 @@
----
-title: AllPages
-description: Returns a collection of all pages in all languages.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: page.Pages
- signatures: [SITE.AllPages]
----
-
-This method returns all page [kinds](g) in all languages, 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.AllPages }}
-
-{{ end }}
-```
-
-Hugo renders this to:
-
-```html
-
Fiction
-
-
The Hunchback of Notre Dame (978-0140443530)
-
Les Misérables (978-0451419439)
-
-
Nonfiction
-
-
The Ancien Régime and the Revolution (978-0141441641)
-
Interpreting the French Revolution (978-0521280495)
-
-```
-
-To limit the listing to fiction, and sort by title:
-
-```go-html-template
-
- {{ range sort .Site.Data.books.fiction "title" }}
-
{{ .title }} ({{ .author }})
- {{ end }}
-
-```
-
-To find a fiction book by ISBN:
-
-```go-html-template
-{{ range where .Site.Data.books.fiction "isbn" "978-0140443530" }}
-
{{ .title }} ({{ .author }})
-{{ end }}
-```
-
-In the template examples 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. For example:
-
-```go-html-template
-{{ index .Site.Data.books "historical-fiction" }}
-```
-
-[`index`]: /functions/collections/indexfunction/
-[`transform.Unmarshal`]: /functions/transform/unmarshal/
-[mounted]: /configuration/module/#mounts
diff --git a/docs/content/en/methods/site/DisqusShortname.md b/docs/content/en/methods/site/DisqusShortname.md
deleted file mode 100644
index de679fd7e..000000000
--- a/docs/content/en/methods/site/DisqusShortname.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: DisqusShortname
-description: Returns the Disqus shortname as defined in the site configuration.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: string
- signatures: [SITE.DisqusShortname]
-expiryDate: 2025-10-30 # deprecated 2023-10-30 in v0.120.0
----
-
-{{< deprecated-in 0.120.0 >}}
-Use [`Site.Config.Services.Disqus.Shortname`] instead.
-
-[`Site.Config.Services.Disqus.Shortname`]: /methods/site/config/
-{{< /deprecated-in >}}
diff --git a/docs/content/en/methods/site/GetPage.md b/docs/content/en/methods/site/GetPage.md
deleted file mode 100644
index 2a3bd7d59..000000000
--- a/docs/content/en/methods/site/GetPage.md
+++ /dev/null
@@ -1,105 +0,0 @@
----
-title: GetPage
-description: Returns a Page object from the given path.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: page.Page
- signatures: [SITE.GetPage PATH]
----
-
-The `GetPage` method is also available on `Page` objects, allowing you to specify a path relative to the current page. See [details].
-
-[details]: /methods/page/getpage/
-
-When using the `GetPage` method on a `Site` object, specify a path relative to the `content` directory.
-
-If Hugo cannot resolve the path to a page, the method returns nil.
-
-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
-```
-
-This home template:
-
-```go-html-template
-{{ with .Site.GetPage "/works/paintings" }}
-
- {{ range .Pages }}
-
{{ .Title }} by {{ .Params.artist }}
- {{ end }}
-
-{{ end }}
-```
-
-Is rendered to:
-
-```html
-
-
Starry Night by Vincent van Gogh
-
The Mona Lisa by Leonardo da Vinci
-
-```
-
-To get a regular page instead of a section page:
-
-```go-html-template
-{{ 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 .Site.Sites "Language.Lang" "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
-{{ with .Site.GetPage "/headless" }}
- {{ range .Resources.ByType "image" }}
-
- {{ end }}
-{{ end }}
-```
diff --git a/docs/content/en/methods/site/GoogleAnalytics.md b/docs/content/en/methods/site/GoogleAnalytics.md
deleted file mode 100644
index e4d28bcce..000000000
--- a/docs/content/en/methods/site/GoogleAnalytics.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: GoogleAnalytics
-description: Returns the Google Analytics tracking ID as defined in the site configuration.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: string
- signatures: [SITE.GoogleAnalytics]
-expiryDate: 2025-10-30 # deprecated 2023-10-30 in v0.120.0
----
-
-{{< deprecated-in 0.120.0 >}}
-Use [`Site.Config.Services.GoogleAnalytics.ID`] instead.
-
-[`Site.Config.Services.GoogleAnalytics.ID`]: /methods/site/config/
-{{< /deprecated-in >}}
diff --git a/docs/content/en/methods/site/Home.md b/docs/content/en/methods/site/Home.md
deleted file mode 100644
index 19ab61747..000000000
--- a/docs/content/en/methods/site/Home.md
+++ /dev/null
@@ -1,25 +0,0 @@
----
-title: Home
-description: Returns the home Page object for the given site.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: page.Page
- signatures: [SITE.Home]
----
-
-This method is useful for obtaining a link to the home page.
-
-Site 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/
-```
diff --git a/docs/content/en/methods/site/IsDevelopment.md b/docs/content/en/methods/site/IsDevelopment.md
deleted file mode 100644
index cddd18818..000000000
--- a/docs/content/en/methods/site/IsDevelopment.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: IsDevelopment
-description: Reports whether the current running environment is “development”.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: bool
- signatures: [SITE.IsDevelopment]
-expiryDate: 2025-10-30 # deprecated 2023-10-30 in v0.120.0
----
-
-{{< deprecated-in 0.120.0 >}}
-Use [`hugo.IsDevelopment`] instead.
-
-[`hugo.IsDevelopment`]: /functions/hugo/isdevelopment/
-{{< /deprecated-in >}}
diff --git a/docs/content/en/methods/site/IsMultiLingual.md b/docs/content/en/methods/site/IsMultiLingual.md
deleted file mode 100644
index 3f9723f1c..000000000
--- a/docs/content/en/methods/site/IsMultiLingual.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: IsMultiLingual
-description: Reports whether there are two or more configured languages.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: bool
- signatures: [SITE.IsMultiLingual]
-expiryDate: 2026-03-16 # deprecated 2024-03-16 in 0.124.0
----
-
-{{< deprecated-in 0.124.0 >}}
-Use [`hugo.IsMultilingual`] instead.
-
-[`hugo.IsMultilingual`]: /functions/hugo/ismultilingual/
-{{< /deprecated-in >}}
diff --git a/docs/content/en/methods/site/IsServer.md b/docs/content/en/methods/site/IsServer.md
deleted file mode 100644
index 8b09c8492..000000000
--- a/docs/content/en/methods/site/IsServer.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: IsServer
-description: Reports whether the built-in development server is running.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: bool
- signatures: [SITE.IsServer]
-expiryDate: 2025-10-30 # deprecated 2023-10-30 in v0.120.0
----
-
-{{< deprecated-in 0.120.0 >}}
-Use [`hugo.IsServer`] instead.
-
-[`hugo.IsServer`]: /functions/hugo/isserver/
-{{< /deprecated-in >}}
diff --git a/docs/content/en/methods/site/Language.md b/docs/content/en/methods/site/Language.md
deleted file mode 100644
index 31f15b8cb..000000000
--- a/docs/content/en/methods/site/Language.md
+++ /dev/null
@@ -1,80 +0,0 @@
----
-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. The language object points to the language definition in the site configuration.
-
-You can also use the `Language` method on a `Page` object. See [details].
-
-## Methods
-
-The examples below assume the following in your site configuration:
-
-{{< code-toggle file=hugo >}}
-[languages.de]
-languageCode = 'de-DE'
-languageDirection = 'ltr'
-languageName = 'Deutsch'
-weight = 1
-{{< /code-toggle >}}
-
-### Lang
-
-(`string`) The language tag as defined by [RFC 5646].
-
-```go-html-template
-{{ .Site.Language.Lang }} → de
-```
-
-### LanguageCode
-
-(`string`) The language code from the site configuration. Falls back to `Lang` if not defined.
-
-```go-html-template
-{{ .Site.Language.LanguageCode }} → de-DE
-```
-
-### LanguageDirection
-
-(`string`) The language direction from the site configuration, either `ltr` or `rtl`.
-
-```go-html-template
-{{ .Site.Language.LanguageDirection }} → ltr
-```
-
-### LanguageName
-
-(`string`) The language name from the site configuration.
-
-```go-html-template
-{{ .Site.Language.LanguageName }} → Deutsch
-```
-
-### Weight
-
-(`int`) The language weight from the site configuration which determines its order in the slice of languages returned by the `Languages` method on a `Site` object.
-
-```go-html-template
-{{ .Site.Language.Weight }} → 1
-```
-
-## Example
-
-Some of the methods above are commonly used in a base template as attributes for the `html` element.
-
-```go-html-template
-{{ debug.Dump .Site.Languages }}
-```
diff --git a/docs/content/en/methods/site/LastChange.md b/docs/content/en/methods/site/LastChange.md
deleted file mode 100644
index e02937bf1..000000000
--- a/docs/content/en/methods/site/LastChange.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: LastChange
-description: Returns the last modification date of site content.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: time.Time
- signatures: [SITE.LastChange]
-expiryDate: 2026-02-19 # deprecated 2024-02-19 in v0.123.0
----
-
-{{< deprecated-in 0.123.0 >}}
-Use [`.Site.Lastmod`] instead.
-
-[`.Site.Lastmod`]: /methods/site/lastmod/
-{{< /deprecated-in >}}
diff --git a/docs/content/en/methods/site/Lastmod.md b/docs/content/en/methods/site/Lastmod.md
deleted file mode 100644
index 38f6da2fa..000000000
--- a/docs/content/en/methods/site/Lastmod.md
+++ /dev/null
@@ -1,23 +0,0 @@
----
-title: Lastmod
-description: Returns the last modification date of site content.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: time.Time
- signatures: [SITE.Lastmod]
----
-
-{{< new-in 0.123.0 />}}
-
-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
deleted file mode 100644
index bee4f2d57..000000000
--- a/docs/content/en/methods/site/MainSections.md
+++ /dev/null
@@ -1,54 +0,0 @@
----
-title: MainSections
-description: Returns a slice of the main section names as defined in the site configuration, falling back to the top-level section with the most pages.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: '[]string'
- signatures: [SITE.MainSections]
----
-
-Site configuration:
-
-{{< code-toggle file=hugo >}}
-mainSections = ['books','films']
-{{< /code-toggle >}}
-
-Template:
-
-```go-html-template
-{{ .Site.MainSections }} → [books films]
-```
-
-If `mainSections` is not defined in the site 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 site authors to set `mainSections` in their site configuration.
-
-Then your home template can do something like this:
-
-```go-html-template
-{{ 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
deleted file mode 100644
index 398a9b022..000000000
--- a/docs/content/en/methods/site/Menus.md
+++ /dev/null
@@ -1,89 +0,0 @@
----
-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
deleted file mode 100644
index a6ba5e029..000000000
--- a/docs/content/en/methods/site/Pages.md
+++ /dev/null
@@ -1,22 +0,0 @@
----
-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
deleted file mode 100644
index 929e30e98..000000000
--- a/docs/content/en/methods/site/Param.md
+++ /dev/null
@@ -1,28 +0,0 @@
----
-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 the site 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
deleted file mode 100644
index 8467be41d..000000000
--- a/docs/content/en/methods/site/Params.md
+++ /dev/null
@@ -1,42 +0,0 @@
----
-title: Params
-description: Returns a map of custom parameters as defined in the site configuration.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: maps.Params
- signatures: [SITE.Params]
----
-
-With this site 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
deleted file mode 100644
index 69a460529..000000000
--- a/docs/content/en/methods/site/RegularPages.md
+++ /dev/null
@@ -1,32 +0,0 @@
----
-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 }}
-
-```
-
-To render a link to the home page of the site corresponding to the default content language:
-
-```go-html-template
-{{ with .Site.Sites.Default }}
- {{ .Title }}
-{{ end }}
-```
-
-This is equivalent to:
-
-```go-html-template
-{{ with index .Site.Sites 0 }}
- {{ .Title }}
-{{ end }}
-```
diff --git a/docs/content/en/methods/site/Store.md b/docs/content/en/methods/site/Store.md
deleted file mode 100644
index 7dcf7d095..000000000
--- a/docs/content/en/methods/site/Store.md
+++ /dev/null
@@ -1,117 +0,0 @@
----
-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, a partial template called by a shortcode, or by a Markdown render hook. 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
deleted file mode 100644
index 92dc41a9b..000000000
--- a/docs/content/en/methods/site/Taxonomies.md
+++ /dev/null
@@ -1,176 +0,0 @@
----
-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 site 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
deleted file mode 100644
index 935edda0c..000000000
--- a/docs/content/en/methods/site/Title.md
+++ /dev/null
@@ -1,22 +0,0 @@
----
-title: Title
-description: Returns the title as defined in the site configuration.
-categories: []
-keywords: []
-params:
- functions_and_methods:
- returnType: string
- signatures: [SITE.Title]
----
-
-Site 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/_index.md b/docs/content/en/methods/site/_index.md
deleted file mode 100644
index f395a3693..000000000
--- a/docs/content/en/methods/site/_index.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: Site methods
-linkTitle: Site
-description: Use these methods with Site objects.
-categories: []
-keywords: []
-aliases: [/variables/site/]
----
diff --git a/docs/content/en/methods/taxonomy/Alphabetical.md b/docs/content/en/methods/taxonomy/Alphabetical.md
deleted file mode 100644
index af4af596c..000000000
--- a/docs/content/en/methods/taxonomy/Alphabetical.md
+++ /dev/null
@@ -1,69 +0,0 @@
----
-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
deleted file mode 100644
index fbf9bb4a1..000000000
--- a/docs/content/en/methods/taxonomy/ByCount.md
+++ /dev/null
@@ -1,69 +0,0 @@
----
-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).
-
-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
deleted file mode 100644
index 76af8ee04..000000000
--- a/docs/content/en/methods/taxonomy/Count.md
+++ /dev/null
@@ -1,22 +0,0 @@
----
-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
deleted file mode 100644
index 03c184868..000000000
--- a/docs/content/en/methods/taxonomy/Get.md
+++ /dev/null
@@ -1,67 +0,0 @@
----
-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 }}
-
-```
-
-To render a blockquote as an HTML `figure` element with an optional citation and caption:
-
-```go-html-template {file="layouts/_default/_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/_default/_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/
-└── _default/
- └── _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
deleted file mode 100755
index d1a01e9b0..000000000
--- a/docs/content/en/render-hooks/code-blocks.md
+++ /dev/null
@@ -1,127 +0,0 @@
----
-title: Code block render hooks
-linkTitle: Code blocks
-description: Create a code block render hook 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"}
-```bash {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.
-
-Ordinal
-: (`int`) The zero-based ordinal of the code block on the page.
-
-Page
-: (`page`) A reference to the current page.
-
-PageInner
-: {{< new-in 0.125.0 />}}
-: (`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/_default/_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/
-└── _default/
- └── _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/_default/_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/_default/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/0.31.2/#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/0.31.2/#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
deleted file mode 100755
index 89868d478..000000000
--- a/docs/content/en/render-hooks/headings.md
+++ /dev/null
@@ -1,64 +0,0 @@
----
-title: Heading render hooks
-linkTitle: Headings
-description: Create a heading render hook 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
-: {{< new-in 0.125.0 />}}
-: (`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/_default/_markup/render-heading.html" copy=true}
-
- {{- .Text -}}
-
-```
-
-To add an anchor link to the right of each heading:
-
-```go-html-template {file="layouts/_default/_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
deleted file mode 100755
index a4faac672..000000000
--- a/docs/content/en/render-hooks/images.md
+++ /dev/null
@@ -1,138 +0,0 @@
----
-title: Image render hooks
-linkTitle: Images
-description: Create an image render to hook 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
-: {{< new-in 0.125.0 />}}
-: (`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/_default/_markup/render-image.html" copy=true}
-
-{{- /* chomp trailing newline */ -}}
-```
-
-To render standalone images within `figure` elements:
-
-```go-html-template {file="layouts/_default/_markup/render-image.html" copy=true}
-{{- if .IsBlock -}}
-
-
- {{- with .Title }}{{ . }}{{ end -}}
-
-{{- else -}}
-
-{{- end -}}
-```
-
-Note that the above requires the following site configuration:
-
-{{< code-toggle file=hugo >}}
-[markup.goldmark.parser]
-wrapStandAloneImageWithinParagraph = false
-{{< /code-toggle >}}
-
-## Default
-
-{{< new-in 0.123.0 />}}
-
-Hugo includes an [embedded image render hook] to resolve Markdown image destinations. Disabled by default, you can enable it in your site configuration:
-
-{{< code-toggle file=hugo >}}
-[markup.goldmark.renderHooks.image]
-enableDefault = true
-{{< /code-toggle >}}
-
-A custom render hook, even when provided by a theme or module, will override the embedded render hook regardless of the configuration setting above.
-
-> [!note]
-> The embedded image render hook is automatically enabled for multilingual single-host sites if [duplication of shared page resources] is disabled. This is the default configuration for multilingual single-host sites.
-
-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 site 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
deleted file mode 100755
index 045d25c3d..000000000
--- a/docs/content/en/render-hooks/introduction.md
+++ /dev/null
@@ -1,81 +0,0 @@
----
-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/
-└── _default/
- └── _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/
-├── _default/
-│ └── _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
deleted file mode 100755
index 23f725eb7..000000000
--- a/docs/content/en/render-hooks/links.md
+++ /dev/null
@@ -1,108 +0,0 @@
----
-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
-: {{< new-in 0.125.0 />}}
-: (`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/_default/_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/_default/_markup/render-link.html" copy=true}
-{{- $u := urls.Parse .Destination -}}
-
- {{- with .Text }}{{ . }}{{ end -}}
-
-{{- /* chomp trailing newline */ -}}
-```
-
-## Default
-
-{{< new-in 0.123.0 />}}
-
-Hugo includes an [embedded link render hook] to resolve Markdown link destinations. Disabled by default, you can enable it in your site configuration:
-
-{{< code-toggle file=hugo >}}
-[markup.goldmark.renderHooks.link]
-enableDefault = true
-{{< /code-toggle >}}
-
-A custom render hook, even when provided by a theme or module, will override the embedded render hook regardless of the configuration setting above.
-
-> [!note]
-> The embedded link render hook is automatically enabled for multilingual single-host sites if [duplication of shared page resources] is disabled. This is the default configuration for multilingual single-host sites.
-
-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 site 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
deleted file mode 100755
index 356a030af..000000000
--- a/docs/content/en/render-hooks/passthrough.md
+++ /dev/null
@@ -1,124 +0,0 @@
----
-title: Passthrough render hooks
-linkTitle: Passthrough
-description: Create a passthrough render hook 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 site 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/_default/_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/_default/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/
-└── _default/
- └── _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
deleted file mode 100755
index c7671aff4..000000000
--- a/docs/content/en/render-hooks/tables.md
+++ /dev/null
@@ -1,100 +0,0 @@
----
-title: Table render hooks
-linkTitle: Tables
-description: Create a table render hook 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/_default/_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
deleted file mode 100644
index 826ee5796..000000000
--- a/docs/content/en/shortcodes/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-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
deleted file mode 100755
index 94502ac1c..000000000
--- a/docs/content/en/shortcodes/details.md
+++ /dev/null
@@ -1,74 +0,0 @@
----
-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
deleted file mode 100755
index 74af52fe7..000000000
--- a/docs/content/en/shortcodes/figure.md
+++ /dev/null
@@ -1,109 +0,0 @@
----
-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 site 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/gist.md b/docs/content/en/shortcodes/gist.md
deleted file mode 100755
index fd2b468ab..000000000
--- a/docs/content/en/shortcodes/gist.md
+++ /dev/null
@@ -1,35 +0,0 @@
----
-title: Gist shortcode
-linkTitle: Gist
-description: Embed a GitHub Gist in your content using the gist shortcode.
-categories: []
-keywords: []
-expiryDate: 2027-02-01 # deprecated 2025-02-01 in v0.143.0
----
-
-{{< deprecated-in 0.143.0 >}}
-The `gist` shortcode was deprecated in version 0.143.0 and will be removed in a future release. To continue embedding GitHub Gists in your content, you'll need to create a custom shortcode:
-
-1. Create a new file: Create a file named `gist.html` within the `layouts/shortcodes` directory.
-1. Copy the source code: Paste the [original source code]({{% eturl gist %}}) of the gist shortcode into the newly created `gist.html` file.
-
-This will allow you to maintain the functionality of embedding GitHub Gists in your content after the deprecation of the original shortcode.
-{{< /deprecated-in >}}
-
-To display a GitHub gist with this URL:
-
-```text
-https://gist.github.com/user/50a7482715eac222e230d1e64dd9a89b
-```
-
-Include this in your Markdown:
-
-```text
-{{* gist user 23932424365401ffa5e9d9810102a477 */>}}
-```
-
-To display a specific file within the gist:
-
-```text
-{{* gist user 23932424365401ffa5e9d9810102a477 list.html */>}}
-```
diff --git a/docs/content/en/shortcodes/highlight.md b/docs/content/en/shortcodes/highlight.md
deleted file mode 100755
index 371a3d46e..000000000
--- a/docs/content/en/shortcodes/highlight.md
+++ /dev/null
@@ -1,107 +0,0 @@
----
-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 [site 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 [site 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/
-[site 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
deleted file mode 100755
index 3256790c6..000000000
--- a/docs/content/en/shortcodes/instagram.md
+++ /dev/null
@@ -1,42 +0,0 @@
----
-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 site 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
deleted file mode 100755
index 133b2322a..000000000
--- a/docs/content/en/shortcodes/param.md
+++ /dev/null
@@ -1,38 +0,0 @@
----
-title: Param shortcode
-linkTitle: Param
-description: Insert a parameter from front matter or site 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
deleted file mode 100755
index 98d6cee4c..000000000
--- a/docs/content/en/shortcodes/qr.md
+++ /dev/null
@@ -1,107 +0,0 @@
----
-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]: https://en.wikipedia.org/wiki/VCard
diff --git a/docs/content/en/shortcodes/ref.md b/docs/content/en/shortcodes/ref.md
deleted file mode 100755
index a52c2bf6e..000000000
--- a/docs/content/en/shortcodes/ref.md
+++ /dev/null
@@ -1,62 +0,0 @@
----
-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, use a [link render hook] that resolves the link destination using the `GetPage` method on the `Page` object. You can either create your own, or simply enable the [embedded link render hook]. The embedded link render hook is automatically enabled for multilingual single-host projects.
-
-## 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" %}}
-
-[content format]: /content-management/formats/
-[embedded link render hook]: /render-hooks/links/#default
-[link render hook]: /render-hooks/links/
-[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
deleted file mode 100755
index 219eae81a..000000000
--- a/docs/content/en/shortcodes/relref.md
+++ /dev/null
@@ -1,62 +0,0 @@
----
-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, use a [link render hook] that resolves the link destination using the `GetPage` method on the `Page` object. You can either create your own, or simply enable the [embedded link render hook]. The embedded link render hook is automatically enabled for multilingual single-host projects.
-
-## 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]({{%/* 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" %}}
-
-[content format]: /content-management/formats/
-[embedded link render hook]: /render-hooks/links/#default
-[link render hook]: /render-hooks/links/
-[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
deleted file mode 100755
index 1164ce997..000000000
--- a/docs/content/en/shortcodes/vimeo.md
+++ /dev/null
@@ -1,73 +0,0 @@
----
-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/channels/staffpicks/55073825
-```
-
-Include this in your Markdown:
-
-```text
-{{* vimeo 55073825 */>}}
-```
-
-Hugo renders this to:
-
-{{< vimeo 55073825 >}}
-
-## Arguments
-
-id
-: (string) The video `id`. Optional if the `id` is provided as a positional argument as shown in the example above.
-
-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=55073825 allowFullScreen=false loading=lazy */>}}
-```
-
-## Privacy
-
-Adjust the relevant privacy settings in your site 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 [here].
-
-[here]: {{% eturl vimeo_simple %}}
-[source code]: {{% eturl vimeo %}}
diff --git a/docs/content/en/shortcodes/x.md b/docs/content/en/shortcodes/x.md
deleted file mode 100755
index f1eebdaf2..000000000
--- a/docs/content/en/shortcodes/x.md
+++ /dev/null
@@ -1,54 +0,0 @@
----
-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 site 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 [here].
-
-If you enable simple mode you may want to disable the hardcoded inline styles by setting `disableInlineCSS` to `true` in your site configuration. The default value for this setting is `false`.
-
-{{< code-toggle config=services.x />}}
-
-[here]: {{% eturl x_simple %}}
-[source code]: {{% eturl x %}}
diff --git a/docs/content/en/shortcodes/youtube.md b/docs/content/en/shortcodes/youtube.md
deleted file mode 100755
index ed3cf0632..000000000
--- a/docs/content/en/shortcodes/youtube.md
+++ /dev/null
@@ -1,91 +0,0 @@
----
-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 provided as a positional argument as shown in the example above.
-
-allowFullScreen
-: {{< new-in 0.125.0 />}}
-: (`bool`) Whether the `iframe` element can activate full screen mode. Default is `true`.
-
-autoplay
-: {{< new-in 0.125.0 />}}
-: (`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
-: {{< new-in 0.125.0 />}}
-: (`bool`) Whether to display the video controls. Default is `true`.
-
-end
-: {{< new-in 0.125.0 />}}
-: (`int`) The time, measured in seconds from the start of the video, when the player should stop playing the video.
-
-loading
-: {{< new-in 0.125.0 />}}
-: (`string`) The loading attribute of the `iframe` element, either `eager` or `lazy`. Default is `eager`.
-
-loop
-: {{< new-in 0.125.0 />}}
-: (`bool`) Whether to indefinitely repeat the video. Ignores the `start` and `end` arguments after the first play. Default is `false`.
-
-mute
-: {{< new-in 0.125.0 />}}
-: (`bool`) Whether to mute the video. Always `true` when `autoplay` is `true`. Default is `false`.
-
-start
-: {{< new-in 0.125.0 />}}
-: (`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 site 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
deleted file mode 100644
index 32d299bd4..000000000
--- a/docs/content/en/showcase/1password-support/bio.md
+++ /dev/null
@@ -1,3 +0,0 @@
-**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/featured.png b/docs/content/en/showcase/1password-support/featured.png
deleted file mode 100644
index 8e46495e6..000000000
Binary files a/docs/content/en/showcase/1password-support/featured.png and /dev/null differ
diff --git a/docs/content/en/showcase/1password-support/index.md b/docs/content/en/showcase/1password-support/index.md
deleted file mode 100644
index 54a30f849..000000000
--- a/docs/content/en/showcase/1password-support/index.md
+++ /dev/null
@@ -1,37 +0,0 @@
----
-title: 1Password Support
-date: 2018-02-22
-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
-
-Finding a tool that will make your customers, writers, designers, _and_ DevOps team happy is no easy task, but we managed it with Hugo:
-
-**Hugo is static**. We're a security company, so we swear by static sites and use them wherever possible. We feel much safer pointing customers at HTML files than at a complicated server which needs to be hardened.
-
-**Hugo is Go**. We love the Go programming language at 1Password, and we were delighted to learn that Hugo used the same Go template syntax that our designers and front-end developers had already mastered.
-
-**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 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.
-
-**Hugo has an amazing developer community**. Hugo updates are frequent and filled to the brim with features and fixes. As we developed the multilingual version of our site, we submitted PRs for features we needed and were helped through the process by [@bep](https://github.com/bep) and others.
-
-**Hugo is simple to deploy**. Hugo has just the right amount of configuration options to fit into our build system without being too complicated.
-
-### 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.
diff --git a/docs/content/en/showcase/_index.md b/docs/content/en/showcase/_index.md
deleted file mode 100644
index e618e8104..000000000
--- a/docs/content/en/showcase/_index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-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
deleted file mode 100644
index 5ea389617..000000000
--- a/docs/content/en/showcase/_template/bio.md
+++ /dev/null
@@ -1,6 +0,0 @@
-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/content/en/showcase/_template/featured.png b/docs/content/en/showcase/_template/featured.png
deleted file mode 100644
index 4f390132e..000000000
Binary files a/docs/content/en/showcase/_template/featured.png and /dev/null differ
diff --git a/docs/content/en/showcase/_template/index.md b/docs/content/en/showcase/_template/index.md
deleted file mode 100644
index 3103903e1..000000000
--- a/docs/content/en/showcase/_template/index.md
+++ /dev/null
@@ -1,31 +0,0 @@
----
-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 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/showcase/alora-labs/bio.md b/docs/content/en/showcase/alora-labs/bio.md
deleted file mode 100644
index f14a90b75..000000000
--- a/docs/content/en/showcase/alora-labs/bio.md
+++ /dev/null
@@ -1,3 +0,0 @@
-**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
deleted file mode 100644
index b8e1f302b..000000000
Binary files a/docs/content/en/showcase/alora-labs/featured.png and /dev/null differ
diff --git a/docs/content/en/showcase/alora-labs/index.md b/docs/content/en/showcase/alora-labs/index.md
deleted file mode 100644
index 5e676bad3..000000000
--- a/docs/content/en/showcase/alora-labs/index.md
+++ /dev/null
@@ -1,18 +0,0 @@
----
-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
deleted file mode 100644
index a08b26be7..000000000
--- a/docs/content/en/showcase/ampio-help/bio.md
+++ /dev/null
@@ -1,10 +0,0 @@
-__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
deleted file mode 100644
index 07974e7f1..000000000
Binary files a/docs/content/en/showcase/ampio-help/featured.png and /dev/null differ
diff --git a/docs/content/en/showcase/ampio-help/index.md b/docs/content/en/showcase/ampio-help/index.md
deleted file mode 100644
index 462452cb1..000000000
--- a/docs/content/en/showcase/ampio-help/index.md
+++ /dev/null
@@ -1,76 +0,0 @@
----
-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
deleted file mode 100644
index a6c98f9ba..000000000
--- a/docs/content/en/showcase/bypasscensorship/bio.md
+++ /dev/null
@@ -1,6 +0,0 @@
-Bypass Censorship find and promote tools that provide Internet access to everyone.
-
-The site is built by:
-
-- [Leyla Avsar](https://www.leylaavsar.com/) (designer)
-- [Fredrik Jonsson](https://xdeb.net/) (dev)
diff --git a/docs/content/en/showcase/bypasscensorship/featured.png b/docs/content/en/showcase/bypasscensorship/featured.png
deleted file mode 100644
index d6f429112..000000000
Binary files a/docs/content/en/showcase/bypasscensorship/featured.png and /dev/null differ
diff --git a/docs/content/en/showcase/bypasscensorship/index.md b/docs/content/en/showcase/bypasscensorship/index.md
deleted file mode 100644
index bd1a072c0..000000000
--- a/docs/content/en/showcase/bypasscensorship/index.md
+++ /dev/null
@@ -1,23 +0,0 @@
----
-title: Bypass Censorship
-date: 2019-06-16
-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.
-
-Websites of international news agencies are often blocked in many countries. In order to connect people to these sites, Bypass Censorship feature and recommend tools in the following languages: English, French, Spanish, Arabic, Farsi, Chinese, and Russian.
-
-One of the tools is the Bypass Censorship Extension for Firefox and Chrome. The extension help direct people to mirrors of partners sites if they are being censored.
-
-The first version of the site was built in Drupal 8 but it was relaunched as a static site built with Hugo in 2019.
-
-Security, page load time and easy of hosting is the main reasons for switching to a static site. As the lead developer I had good experience with Hugo and was interested in exploring the multilingual features.
-
-It's a simply site, basically one page in seven languages. I had no problems getting Hugo to output what I wanted. Found the multilingual support straight forward and easy to work with.
-
-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.
diff --git a/docs/content/en/showcase/digitalgov/bio.md b/docs/content/en/showcase/digitalgov/bio.md
deleted file mode 100644
index 70bb990b9..000000000
--- a/docs/content/en/showcase/digitalgov/bio.md
+++ /dev/null
@@ -1 +0,0 @@
-**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
deleted file mode 100644
index 7d065dce9..000000000
Binary files a/docs/content/en/showcase/digitalgov/featured.png and /dev/null differ
diff --git a/docs/content/en/showcase/digitalgov/index.md b/docs/content/en/showcase/digitalgov/index.md
deleted file mode 100644
index 7f0584712..000000000
--- a/docs/content/en/showcase/digitalgov/index.md
+++ /dev/null
@@ -1,64 +0,0 @@
----
-title: Digital.gov
-date: 2020-05-01
-description: 'Showcase: "Guidance on building better digital services in government."'
-siteURL: https://digital.gov/
-siteSource: https://github.com/gsa/digitalgov.gov
----
-
-For over a decade, Digital.gov has provided guidance, training, and community support to the people who are responsible for delivering digital services in the U.S. government. Essentially, it is a place where people can find examples of problems being solved in government, and get links to the tools and resources they need.
-
-Through collaboration in our communities of practice, Digital.gov is a window into the people who work in technology in government and the challenges they face making digital services stronger and more effective. [Read more about our site »](https://digital.gov/2019/12/19/a-new-digitalgov/)
-
-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.
-
-_More on the [USWDS maturity model »](https://designsystem.digital.gov/maturity-model/)_
-
-## 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).
-
-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.
-
-**Why Hugo?** Well, with around `~3,000` files _(and growing)_ and `~9,000` built pages, we needed a site generator that could handle that volume with lightning fast speed.
-
-Hugo was the clear option. The [Federalist](https://federalist.18f.gov/) team quickly added it to their available site generators, and we were off.
-
-At the moment, it takes around `32 seconds` to build close to `~10,000` pages!
-
-Take a look:
-
-```text
-
- | EN
--------------------+-------
- Pages | 7973
- Paginator pages | 600
- Non-page files | 108
- Static files | 851
- Processed images | 0
- Aliases | 1381
- Sitemaps | 1
- 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/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
deleted file mode 100644
index 2a5639aa7..000000000
--- a/docs/content/en/showcase/fireship/bio.md
+++ /dev/null
@@ -1,5 +0,0 @@
-**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/)
diff --git a/docs/content/en/showcase/fireship/featured.png b/docs/content/en/showcase/fireship/featured.png
deleted file mode 100644
index 33d1a47c5..000000000
Binary files a/docs/content/en/showcase/fireship/featured.png and /dev/null differ
diff --git a/docs/content/en/showcase/fireship/index.md b/docs/content/en/showcase/fireship/index.md
deleted file mode 100644
index 454ee87d7..000000000
--- a/docs/content/en/showcase/fireship/index.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: fireship.io
-date: 2019-02-02
-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"
----
-
-After careful consideration of JavaScript/JSX-based static site generators, it became clear that Hugo was the only tool capable of handling our project's complex demands. Not only do we have multiple content formats and taxonomies, but we often need to customize the experience at a more granular level. The problems Hugo has solved for us include:
-
-- **Render speed.** We know from past experience that JavaScript-based static site generators become very slow when you have thousands of pages and images.
-- **Feature-rich.** Our site has a long list of specialized needs and Hugo somehow manages to cover every single use case.
-- **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.
diff --git a/docs/content/en/showcase/forestry/bio.md b/docs/content/en/showcase/forestry/bio.md
deleted file mode 100644
index 23951a1c6..000000000
--- a/docs/content/en/showcase/forestry/bio.md
+++ /dev/null
@@ -1,3 +0,0 @@
-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/featured.png b/docs/content/en/showcase/forestry/featured.png
deleted file mode 100644
index 1ee315e78..000000000
Binary files a/docs/content/en/showcase/forestry/featured.png and /dev/null differ
diff --git a/docs/content/en/showcase/forestry/index.md b/docs/content/en/showcase/forestry/index.md
deleted file mode 100644
index 5b8872316..000000000
--- a/docs/content/en/showcase/forestry/index.md
+++ /dev/null
@@ -1,48 +0,0 @@
----
-title: Forestry.io
-date: 2018-03-16
-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?
-
-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:
-
-```text
- | EN
-+------------------+-----+
- Pages | 141
- Paginator pages | 4
- Non-page files | 0
- Static files | 537
- Processed images | 0
- Aliases | 60
- Sitemaps | 1
- Cleaned | 0
-
-Total in 739 ms
-```
-
-In fact, we liked Hugo so much that our wizard Chris made his workflow public and we started the open-source project [Create-Static-Site](https://github.com/forestryio/create-static-site). It's [a simple way to spin up sites](https://forestry.io/blog/up-and-running-with-hugo/) and set up a modern web development workflow with one line of code. Essentially it adds build configurations as a dependency for JS, CSS and Image Processing.
-
-Lastly, we want to take the opportunity to give some love to other amazing tools we used building our website.
-
-### 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://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
deleted file mode 100644
index fd849f844..000000000
--- a/docs/content/en/showcase/godot-tutorials/bio.md
+++ /dev/null
@@ -1,7 +0,0 @@
-[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)
diff --git a/docs/content/en/showcase/godot-tutorials/featured.png b/docs/content/en/showcase/godot-tutorials/featured.png
deleted file mode 100644
index fef13b996..000000000
Binary files a/docs/content/en/showcase/godot-tutorials/featured.png and /dev/null differ
diff --git a/docs/content/en/showcase/godot-tutorials/index.md b/docs/content/en/showcase/godot-tutorials/index.md
deleted file mode 100644
index fe4f9337e..000000000
--- a/docs/content/en/showcase/godot-tutorials/index.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: Godot Tutorials
-date: 2021-01-07
-description: "Teaching game development skills with love."
-siteURL: https://godottutorials.com
-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.
-
-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.
-
-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.
diff --git a/docs/content/en/showcase/hapticmedia/bio.md b/docs/content/en/showcase/hapticmedia/bio.md
deleted file mode 100644
index 4423edb70..000000000
--- a/docs/content/en/showcase/hapticmedia/bio.md
+++ /dev/null
@@ -1 +0,0 @@
-**Hapticmedia** provides interactive 3D configurators for eCommerce.
diff --git a/docs/content/en/showcase/hapticmedia/featured.png b/docs/content/en/showcase/hapticmedia/featured.png
deleted file mode 100644
index a47ea9c2c..000000000
Binary files a/docs/content/en/showcase/hapticmedia/featured.png and /dev/null differ
diff --git a/docs/content/en/showcase/hapticmedia/index.md b/docs/content/en/showcase/hapticmedia/index.md
deleted file mode 100644
index 52c3337bf..000000000
--- a/docs/content/en/showcase/hapticmedia/index.md
+++ /dev/null
@@ -1,31 +0,0 @@
----
-title: Hapticmedia Blog
-date: 2019-10-01
-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.
-
-## 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 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.
-- **[AWS](https://aws.amazon.com/)** for web hosting.
-- **[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
deleted file mode 100644
index 4cded7beb..000000000
--- a/docs/content/en/showcase/hartwell-insurance/bio.md
+++ /dev/null
@@ -1,5 +0,0 @@
-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.
-
-The site was built by [Tomango](https://www.tomango.co.uk)
diff --git a/docs/content/en/showcase/hartwell-insurance/featured.png b/docs/content/en/showcase/hartwell-insurance/featured.png
deleted file mode 100644
index ced251f98..000000000
Binary files a/docs/content/en/showcase/hartwell-insurance/featured.png and /dev/null differ
diff --git a/docs/content/en/showcase/hartwell-insurance/hartwell-columns.png b/docs/content/en/showcase/hartwell-insurance/hartwell-columns.png
deleted file mode 100644
index c9d36b67d..000000000
Binary files a/docs/content/en/showcase/hartwell-insurance/hartwell-columns.png and /dev/null differ
diff --git a/docs/content/en/showcase/hartwell-insurance/hartwell-lighthouse.png b/docs/content/en/showcase/hartwell-insurance/hartwell-lighthouse.png
deleted file mode 100644
index a882f01fd..000000000
Binary files a/docs/content/en/showcase/hartwell-insurance/hartwell-lighthouse.png and /dev/null differ
diff --git a/docs/content/en/showcase/hartwell-insurance/hartwell-webpagetest.png b/docs/content/en/showcase/hartwell-insurance/hartwell-webpagetest.png
deleted file mode 100644
index f60994ea1..000000000
Binary files a/docs/content/en/showcase/hartwell-insurance/hartwell-webpagetest.png and /dev/null differ
diff --git a/docs/content/en/showcase/hartwell-insurance/index.md b/docs/content/en/showcase/hartwell-insurance/index.md
deleted file mode 100644
index 07ee6182c..000000000
--- a/docs/content/en/showcase/hartwell-insurance/index.md
+++ /dev/null
@@ -1,63 +0,0 @@
----
-title: Hartwell Insurance
-date: 2018-02-09
-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.
-
-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._
-
-> **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.
-
-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.
-
-For example, this two column layout shown below. I used CSS Columns with a `break-after: always;` on the `
`. No multi-wrapper or difficult-to-clear shortcodes, just clean HTML.
-
-
-
-For the ripple effects on the section headings, I used JS to prepend a `