commit 1c86d21f6437132bdb5b6a256b9a10ff96a1f19b Author: wehub-resource-sync Date: Mon Jul 13 11:58:46 2026 +0800 chore: import upstream snapshot with attribution diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..4a06e47 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,115 @@ +parameters: + +# v2: 11m. +defaults: &defaults + resource_class: large + docker: + - image: bepsays/ci-hugoreleaser:1.22600.20300 +environment: &buildenv + GOMODCACHE: /root/project/gomodcache +version: 2 +jobs: + prepare_release: + <<: *defaults + environment: &buildenv + GOMODCACHE: /root/project/gomodcache + steps: + - setup_remote_docker + - checkout: + path: hugo + - &git-config + run: + command: | + git config --global user.email "bjorn.erik.pedersen+hugoreleaser@gmail.com" + git config --global user.name "hugoreleaser" + - run: + command: | + cd hugo + go mod download + go run -tags release main.go release --step 1 + - save_cache: + key: git-sha-{{ .Revision }} + paths: + - hugo + - gomodcache + build_container1: + <<: [*defaults] + environment: + <<: [*buildenv] + steps: + - &restore-cache + restore_cache: + key: git-sha-{{ .Revision }} + - run: + no_output_timeout: 20m + command: | + mkdir -p /tmp/files/dist1 + cd hugo + hugoreleaser build -paths "builds/container1/**" -workers 3 -dist /tmp/files/dist1 -chunks $CIRCLE_NODE_TOTAL -chunk-index $CIRCLE_NODE_INDEX + - &persist-workspace + persist_to_workspace: + root: /tmp/files + paths: + - dist1 + - dist2 + parallelism: 7 + build_container2: + <<: [*defaults] + environment: + <<: [*buildenv] + docker: + - image: bepsays/ci-hugoreleaser-linux-arm64:1.22600.20300 + steps: + - *restore-cache + - &attach-workspace + attach_workspace: + at: /tmp/workspace + - run: + command: | + mkdir -p /tmp/files/dist2 + cd hugo + hugoreleaser build -paths "builds/container2/**" -workers 1 -dist /tmp/files/dist2 + - *persist-workspace + archive_and_release: + <<: [*defaults] + environment: + <<: [*buildenv] + steps: + - *restore-cache + - *attach-workspace + - *git-config + - run: + name: Add github.com to known hosts + command: ssh-keyscan github.com >> ~/.ssh/known_hosts + - run: + command: | + cp -a /tmp/workspace/dist1/. ./hugo/dist + cp -a /tmp/workspace/dist2/. ./hugo/dist + - run: + command: | + cd hugo + hugoreleaser archive + hugoreleaser release + go run -tags release main.go release --step 2 +workflows: + version: 2 + release: + jobs: + - prepare_release: + filters: + branches: + only: /release-.*/ + - build_container1: + requires: + - prepare_release + - build_container2: + requires: + - prepare_release + - archive_and_release: + context: org-global + requires: + - build_container1 + - build_container2 + + + diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a183f6f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +*.md +*.log +*.txt +.git +.github +.circleci +docs +examples +Dockerfile diff --git a/.gemini/config.yaml b/.gemini/config.yaml new file mode 100644 index 0000000..236e043 --- /dev/null +++ b/.gemini/config.yaml @@ -0,0 +1,13 @@ +have_fun: false +memory_config: + disabled: false +code_review: + disable: false + comment_severity_threshold: HIGH + max_review_comments: -1 + pull_request_opened: + help: true + summary: false + code_review: false + include_drafts: false +ignore_patterns: [] diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6994810 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +# Text files have auto line endings +* text=auto + +# Go source files always have LF line endings +*.go text eol=lf + +# SVG files should not be modified +*.svg -text diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..fa27914 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,23 @@ +--- +name: 'Bug report' +labels: 'Bug, NeedsTriage' +assignees: '' +about: Create a report to help us improve +--- + + + + + +### What version of Hugo are you using (`hugo version`)? + +
+$ hugo version
+
+
+ +### Does this issue reproduce with the latest release? diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..c84d327 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +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 new file mode 100644 index 0000000..c114b3d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,11 @@ +--- +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 new file mode 100644 index 0000000..cc9de09 --- /dev/null +++ b/.github/SUPPORT.md @@ -0,0 +1,3 @@ +### 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 new file mode 100644 index 0000000..1801e72 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +# 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 new file mode 100644 index 0000000..5d488c6 --- /dev/null +++ b/.github/workflows/image.yml @@ -0,0 +1,49 @@ +name: Build Docker image + +on: + release: + types: [published] + pull_request: +permissions: + packages: write + +env: + REGISTRY_IMAGE: ghcr.io/gohugoio/hugo + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Docker meta + id: meta + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 + with: + images: ${{ env.REGISTRY_IMAGE }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + + - name: Login to GHCR + # Login is only needed when the image is pushed + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + id: build + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + with: + context: . + provenance: mode=max + sbom: true + push: ${{ github.event_name != 'pull_request' }} + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: HUGO_BUILD_TAGS=extended,withdeploy \ No newline at end of file diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..89dbdde --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,52 @@ +name: 'Close stale and lock closed issues and PRs' +on: + workflow_dispatch: + schedule: + - cron: '30 1 * * *' +permissions: + contents: read +jobs: + stale: + permissions: + issues: write + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: dessant/lock-threads@f5f995c727ac99a91dec92781a8e34e7c839a65e # v6.0.0 + 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@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0 + with: + operations-per-run: 999 + days-before-issue-stale: 365 + days-before-pr-stale: 365 + days-before-issue-close: 56 + days-before-pr-close: 56 + stale-issue-message: > + This issue has been automatically marked as stale because it has not had + recent activity. The resources of the Hugo team are limited, and so we are asking for your help. + + If this is a **bug** and you can still reproduce this error on the master branch, please reply with all of the information you have about it in order to keep the issue open. + + If this is a **feature request**, and you feel that it is still relevant and valuable, please tell us why. + + This issue will automatically be closed in the near future if no further activity occurs. Thank you for all your contributions. + stale-pr-message: This PR has been automatically marked as stale because it has not had + recent activity. The resources of the Hugo team are limited, and so we are asking for your help. + + Please check https://github.com/gohugoio/hugo/blob/master/CONTRIBUTING.md#code-contribution and verify that this code contribution fits with the description. If yes, tell us in a comment. + + This PR will automatically be closed in the near future if no further activity occurs. Thank you for all your contributions. + stale-issue-label: 'Stale' + exempt-issue-labels: 'Keep,Security' + stale-pr-label: 'Stale' + exempt-pr-labels: 'Keep,Security' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..cee5b6b --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,140 @@ +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.26.x] + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - if: matrix.os == 'ubuntu-latest' + name: Free Disk Space (Ubuntu) + uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 + with: + tool-cache: true + android: true + dotnet: true + haskell: true + large-packages: true + docker-images: true + swap-storage: true + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Install Go + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version: ${{ matrix.go-version }} + check-latest: true + cache: true + - name: Install Node + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: "22" + - name: Install Ruby + uses: ruby/setup-ruby@7372622e62b60b3cb750dcd2b9e32c247ffec26a # v1.302.0 + with: + ruby-version: "3.4.5" + - name: Install Ruby gems + run: | + gem install asciidoctor -v "2.0.26" + gem install asciidoctor-diagram -v "3.1.0" + - name: Install GoAT + run: go install github.com/blampe/goat/cmd/goat@177de93b192b8ffae608e5d9ec421cc99bf68402 + - name: Install Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: "3.x" + - name: Install Mage + run: go install github.com/magefile/mage@v1.15.0 + - name: Install gotmplfmt + run: go install github.com/gohugoio/gotmplfmt@latest + - name: Install docutils + run: | + pip install docutils Pygments + 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: Check embedded go template formatting + run: "diff <(gotmplfmt -d tpl/tplimpl/embedded/templates) <(printf '')" + - if: matrix.os == 'ubuntu-latest' + name: Run staticcheck + run: | + export STATICCHECK_CACHE="${{ runner.temp }}/staticcheck" + staticcheck ./... + rm -rf ${{ runner.temp }}/staticcheck + - if: matrix.os != 'windows-latest' + name: Check + run: | + sass --version; + mage -v check; + env: + HUGO_BUILD_TAGS: extended,withdeploy + - if: matrix.os == 'windows-latest' + # See issue #11052. We limit the build to regular test (no -race flag) on Windows for now. + name: Test + run: | + mage -v test + env: + HUGO_BUILD_TAGS: extended,withdeploy + - if: matrix.os == 'ubuntu-latest' + name: Build for dragonfly + run: | + go install + go clean -i -cache + env: + GOARCH: amd64 + GOOS: dragonfly diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0e812bb --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ + +*.test +imports.* +dist/ +public/ +.DS_Store +cache/filecache/_gen/ +.claude/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e69de29 diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000..e93adab --- /dev/null +++ b/.mailmap @@ -0,0 +1,3 @@ +spf13 Steve Francia +bep Bjørn Erik Pedersen + diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..838f027 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +tpl/tplimpl/embedded/templates/** \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..60c9854 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,22 @@ + +* Brevity is good. +* Assume that the maintainers and readers of the code you write are Go experts: + * Don't use comments to explain the obvious. + * Use self-explanatory variable and function names. + * Use short variable names when the context is clear. +* If you need to add temporary debug printing, use `hdebug.Printf`.[^1] +* Never export symbols that's not needed outside of the package. +* Avoid global state at (almost) all cost. +* This is a project with a long history; assume that a similiar problem has been solved before, look hard for helper functions before creating new ones. +* In tests, almost always write end-to-end integration tests using `hugolib.Test` or one of its siblings. Write unit tests only for isolated utilities. +* In tests, use `qt` matchers (e.g. `b.Assert(err, qt.ErrorMatches, ...)`) instead of raw `if`/`t.Fatal` checks. +* In tests, always use the latest Hugo specification, e.g. for layouts, it's `layouts/page.html` and not `layouts/_default/single.html`, `layouts/list.html` and not `layouts/_default/list.html` +* Never name tests `TestIssue1234`; always give the test function a descriptive name, e.g. `TestDisablePathToLower`, and add any issue reference as a Go doc function comment, e.g. `// See issue 1234.`. +* If you borrow a test case (e.g. from the issue), that test's author must be added as co-author in the commit. +* If you're a security researcher, read @SECURITY.md carefully. +* Brevity is good. This applies to code, comments and commit messages. Don't write a novel. +* Use `./check.sh ./somepackage/...` when iterating. +* Use `./check.sh` when you're done. + + +[^1]: CI build fail if you forget to remove the debug printing. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..43c994c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..13db2dc --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,212 @@ +# Contributing to Hugo + +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. + +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) +* [Reporting Issues](#reporting-issues) +* [Submitting Patches](#submitting-patches) + * [Code Contribution Guidelines](#code-contribution-guidelines) + * [AI Assistance Notice](#ai-assistance-notice) + * [Git Commit Message Guidelines](#git-commit-message-guidelines) + * [Fetching the Sources From GitHub](#fetching-the-sources-from-github) + * [Building Hugo with Your Changes](#building-hugo-with-your-changes) + +## 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. + +## 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://go.dev/wiki/cgo). +We have one exception to this rule which is LibSASS. + +**Bug fixes are, of course, always welcome.** + +## 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. + +### 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. +* When you’re ready to create a pull request, be sure to: + * Sign the [CLA](https://cla-assistant.io/gohugoio/hugo). + * Have test cases for the new code. If you have questions about how to do this, please ask in your pull request. + * If you borrow a test case (e.g. from the issue), that test's author must be added as [co-author](https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/creating-a-commit-with-multiple-authors) in the commit. + * Run `go fmt`. + * Add documentation if you are adding new features or changing functionality. The docs site lives in `/docs`. + * Squash your commits into a single commit. `git rebase -i`. It’s okay to force update your pull request with `git push -f`. + * Ensure that `./check.sh` succeeds. Note that some tests are skipped when running locally, some because they are slow. To run these locally, do `CI_LOCAL=true ./check.sh ./somepackage/...`. + * Follow the **Git Commit Message Guidelines** below. + +## AI Assistance Notice + +If a substantial part of your contribution is autogenerated with AI, **this must be disclosed in the pull request**, along with the extent to which AI assistance was used. AI contributions from non-maintainers needs to have a fairly narrow scope (e.g. a bug fix), as we have limited review capacity. + +An example disclosure: + +> This PR was written primarily by Claude Code. + +Also, When using AI assistance: + +* We expect contributors to manually verify that the state of the pull request is OK (e.g. that the CLI is signed). +* We expect contributors to understand the code that is produced and be able to answer critical questions about it + +### Git Commit Message Guidelines + +This [blog article](https://cbea.ms/git-commit/) is a good resource for learning how to write good commit messages, +the most important part being that each commit message should have a title/subject in imperative mood starting with a capital letter and no trailing period: +*"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:". + +Also, if your commit references one or more GitHub issues, always end your commit message body with *See #1234* or *Fixes #1234*. +Replace *1234* with the GitHub issue ID. The last example will close the issue when the commit is merged into *master*. + +An example: + +```text +tpl: Add custom index function + +Add a custom index template function that deviates from the stdlib simply by not +returning an "index out of range" error if an array, slice or string index is +out of range. Instead, we just return nil values. This should help make the +new default function more useful for Hugo users. + +Fixes #1949 +``` + +### Fetching the Sources From GitHub + +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: + +```bash +mkdir $HOME/src +cd $HOME/src +git clone https://github.com/gohugoio/hugo.git +cd hugo +go install +``` + +For some convenient build and test targets, you also will want to install Mage: + +```bash +go install github.com/magefile/mage +``` + +Now, to make a change to Hugo's source: + +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 + ``` + +1. Fork Hugo in GitHub. + +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 + ``` + +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. + +```bash +cd $HOME/go/src/github.com/gohugoio/hugo +``` + +To build Hugo: + +```bash +mage hugo +``` + +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 new file mode 100755 index 0000000..ee9f9cc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,101 @@ +# GitHub: https://github.com/gohugoio +# Twitter: https://twitter.com/gohugoio +# Website: https://gohugo.io/ + +ARG GO_VERSION="1.26" +ARG ALPINE_VERSION="3.22" +ARG DART_SASS_VERSION="1.79.3" + +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 <Hugo + +A fast and flexible static site generator built with love by [bep][], [spf13][], and [friends][] in Go. + +--- + +[![GoDoc](https://godoc.org/github.com/gohugoio/hugo?status.svg)](https://godoc.org/github.com/gohugoio/hugo) +[![Tests on Linux, MacOS and Windows](https://github.com/gohugoio/hugo/workflows/Test/badge.svg)](https://github.com/gohugoio/hugo/actions?query=workflow%3ATest) +[![Go Report Card](https://goreportcard.com/badge/github.com/gohugoio/hugo)](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. + +Due to its flexible framework, multilingual support, and powerful taxonomy system, Hugo is widely used to create: + +- Corporate, government, nonprofit, education, news, event, and project sites +- Documentation sites +- Image portfolios +- Landing pages +- Business, professional, and personal blogs +- Resumes and CVs + +Use Hugo's embedded web server during development to instantly see changes to content, structure, behavior, and presentation. Then deploy the site to your host, or push changes to your Git provider for automated builds and deployment. + +Hugo's fast asset pipelines include: + +- CSS Processing – Bundle, transform, minify, create source maps, perform SRI hashing, and integrate with PostCSS. +- Image processing – Convert, resize, crop, rotate, adjust colors, apply filters, overlay text and images, and extract metadata +- JavaScript bundling – Transpile TypeScript and JSX to JavaScript, bundle, tree shake, minify, create source maps, and perform SRI hashing. +- Sass processing – Transpile Sass to CSS, bundle, tree shake, minify, create source maps, perform SRI hashing, and integrate with PostCSS +- Tailwind CSS processing – Compile Tailwind CSS utility classes into standard CSS, bundle, tree shake, optimize, minify, perform SRI hashing, and integrate with PostCSS + +And with [Hugo Modules][], you can share content, assets, data, translations, themes, templates, and configuration with other projects via public or private Git repositories. + +See the [features][] section of the documentation for a comprehensive summary of Hugo's capabilities. + +## Sponsors + +

 

+

+ The complete IDE crafted for professional Go developers. +     + CloudCannon +

+ +## Editions + +Hugo is available in several editions. Use the standard edition unless you need additional features. + +Feature|standard|deploy|extended|extended/deploy +:--|:-:|:-:|:-:|:-: +Core features|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark: +Direct cloud deployment (1)|:x:|:heavy_check_mark:|:x:|:heavy_check_mark: +LibSass support (2)|:x:|:x:|:heavy_check_mark:|:heavy_check_mark: + +(1) Deploy your site directly to a Google Cloud Storage bucket, an AWS S3 bucket, or an Azure Storage container. See [details][]. + +(2) [Transpile Sass to CSS][] via embedded LibSass. Note that embedded LibSass was deprecated in v0.153.0 and will be removed in a future release. Use the [Dart Sass][] transpiler instead, which is compatible with any edition. + +## Installation + +Install Hugo from a [prebuilt binary][], package manager, or package repository. Please see the installation instructions for your operating system: + +- [macOS][] +- [Linux][] +- [Windows][] +- [DragonFly BSD, FreeBSD, NetBSD, and OpenBSD][] + +## Build from source + +To build Hugo from source you must install: + +1. [Git][] +1. [Go][] version 1.26.0 or later + +### Standard edition + +To build and install the standard edition: + +```sh +CGO_ENABLED=0 go install github.com/gohugoio/hugo@latest +``` + +### Deploy edition + +To build and install the deploy edition: + +```sh +CGO_ENABLED=0 go install -tags withdeploy github.com/gohugoio/hugo@latest +``` + +### Extended edition + +To build and install the extended edition, first install a C compiler such as [GCC][] or [Clang][] and then run the following command. + +```sh +CGO_ENABLED=1 go install -tags extended github.com/gohugoio/hugo@latest +``` + +### Extended/deploy edition + +To build and install the extended/deploy edition, first install a C compiler such as [GCC][] or [Clang][] and then run the following command. + +```sh +CGO_ENABLED=1 go install -tags extended,withdeploy github.com/gohugoio/hugo@latest +``` + +## Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=gohugoio/hugo&type=Timeline)](https://star-history.com/#gohugoio/hugo&Timeline) + +## Documentation + +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. + +For a complete guide to contributing to Hugo, see the [Contribution Guide](CONTRIBUTING.md). + +## License + +For the Hugo source code, see [LICENSE](/LICENSE). + +We also bundle some libraries in binary/WASM form: + +- [libwebp](https://github.com/webmproject/libwebp), [BSD-3-Clause license](https://github.com/webmproject/libwebp?tab=BSD-3-Clause-1-ov-file#readme) +- [Katex](https://github.com/KaTeX/KaTeX), [MIT license](https://github.com/KaTeX/KaTeX?tab=MIT-1-ov-file#readme) +- [QuickJS](https://github.com/bellard/quickjs?tab=License-1-ov-file#readme), [License](https://github.com/bellard/quickjs?tab=License-1-ov-file#readme) + +## Dependencies + +Hugo stands on the shoulders of great open source libraries. Run `hugo env --logLevel info` to display a list of dependencies. + +
+See current dependencies + +```text +github.com/BurntSushi/locker="v0.0.0-20171006230638-a6e239ea1c69" +github.com/JohannesKaufmann/dom="v0.2.0" +github.com/JohannesKaufmann/html-to-markdown/v2="v2.5.0" +github.com/alecthomas/chroma/v2="v2.21.1" +github.com/aymerick/douceur="v0.2.0" +github.com/bep/clocks="v0.5.0" +github.com/bep/debounce="v1.2.0" +github.com/bep/gitmap="v1.9.0" +github.com/bep/goat="v0.5.0" +github.com/bep/godartsass/v2="v2.5.0" +github.com/bep/golibsass="v1.2.0" +github.com/bep/goportabletext="v0.1.0" +github.com/bep/helpers="v0.6.0" +github.com/bep/imagemeta="v0.12.0" +github.com/bep/lazycache="v0.8.0" +github.com/bep/logg="v0.4.0" +github.com/bep/mclib="v1.20400.20402" +github.com/bep/overlayfs="v0.10.0" +github.com/bep/simplecobra="v0.6.1" +github.com/bep/textandbinarywriter="v0.0.0-20251212174530-cd9f0732f60f" +github.com/bep/tmc="v0.5.1" +github.com/bits-and-blooms/bitset="v1.24.4" +github.com/cespare/xxhash/v2="v2.3.0" +github.com/clbanning/mxj/v2="v2.7.0" +github.com/clipperhouse/displaywidth="v0.6.0" +github.com/clipperhouse/stringish="v0.1.1" +github.com/clipperhouse/uax29/v2="v2.3.0" +github.com/cpuguy83/go-md2man/v2="v2.0.6" +github.com/disintegration/gift="v1.2.1" +github.com/dlclark/regexp2="v1.11.5" +github.com/evanw/esbuild="v0.27.2" +github.com/fatih/color="v1.18.0" +github.com/frankban/quicktest="v1.14.6" +github.com/fsnotify/fsnotify="v1.9.0" +github.com/getkin/kin-openapi="v0.133.0" +github.com/go-openapi/jsonpointer="v0.21.0" +github.com/go-openapi/swag="v0.23.0" +github.com/gobuffalo/flect="v1.0.3" +github.com/gobwas/glob="v0.2.3" +github.com/goccy/go-yaml="v1.19.1" +github.com/gohugoio/go-i18n/v2="v2.1.3-0.20251018145728-cfcc22d823c6" +github.com/gohugoio/go-radix="v1.2.0" +github.com/gohugoio/hashstructure="v0.6.0" +github.com/gohugoio/httpcache="v0.8.0" +github.com/gohugoio/hugo-goldmark-extensions/extras="v0.5.0" +github.com/gohugoio/hugo-goldmark-extensions/passthrough="v0.3.1" +github.com/gohugoio/locales="v0.14.0" +github.com/gohugoio/localescompressed="v1.0.1" +github.com/google/go-cmp="v0.7.0" +github.com/gorilla/css="v1.0.1" +github.com/gorilla/websocket="v1.5.3" +github.com/hairyhenderson/go-codeowners="v0.7.0" +github.com/hashicorp/golang-lru/v2="v2.0.7" +github.com/jdkato/prose="v1.2.1" +github.com/josharian/intern="v1.0.0" +github.com/kr/pretty="v0.3.1" +github.com/kr/text="v0.2.0" +github.com/kyokomi/emoji/v2="v2.2.13" +github.com/mailru/easyjson="v0.7.7" +github.com/makeworld-the-better-one/dither/v2="v2.4.0" +github.com/marekm4/color-extractor="v1.2.1" +github.com/mattn/go-colorable="v0.1.13" +github.com/mattn/go-isatty="v0.0.20" +github.com/mattn/go-runewidth="v0.0.19" +github.com/microcosm-cc/bluemonday="v1.0.27" +github.com/mitchellh/mapstructure="v1.5.1-0.20231216201459-8508981c8b6c" +github.com/mohae/deepcopy="v0.0.0-20170929034955-c48cc78d4826" +github.com/muesli/smartcrop="v0.3.0" +github.com/niklasfasching/go-org="v1.9.1" +github.com/oasdiff/yaml3="v0.0.0-20250309153720-d2182401db90" +github.com/oasdiff/yaml="v0.0.0-20250309154309-f31be36b4037" +github.com/olekukonko/cat="v0.0.0-20250911104152-50322a0618f6" +github.com/olekukonko/errors="v1.1.0" +github.com/olekukonko/ll="v0.1.3" +github.com/olekukonko/tablewriter="v1.1.2" +github.com/pbnjay/memory="v0.0.0-20210728143218-7b4eea64cf58" +github.com/pelletier/go-toml/v2="v2.2.4" +github.com/perimeterx/marshmallow="v1.1.5" +github.com/pkg/browser="v0.0.0-20240102092130-5ac0b6a4141c" +github.com/pkg/errors="v0.9.1" +github.com/rogpeppe/go-internal="v1.14.1" +github.com/russross/blackfriday/v2="v2.1.0" +github.com/sass/dart-sass/compiler="1.97.1" +github.com/sass/dart-sass/implementation="1.97.1" +github.com/sass/dart-sass/protocol="3.2.0" +github.com/spf13/afero="v1.15.0" +github.com/spf13/cast="v1.10.0" +github.com/spf13/cobra="v1.10.2" +github.com/spf13/fsync="v0.10.1" +github.com/spf13/pflag="v1.0.9" +github.com/tdewolff/minify/v2="v2.24.8" +github.com/tdewolff/parse/v2="v2.8.5" +github.com/tetratelabs/wazero="v1.10.1" +github.com/webmproject/libwebp="v1.6.0" +github.com/woodsbury/decimal128="v1.3.0" +github.com/yuin/goldmark-emoji="v1.0.6" +github.com/yuin/goldmark="v1.7.13" +go.uber.org/automaxprocs="v1.5.3" +go.yaml.in/yaml/v3="v3.0.4" +golang.org/x/crypto="v0.46.0" +golang.org/x/image="v0.34.0" +golang.org/x/mod="v0.31.0" +golang.org/x/net="v0.48.0" +golang.org/x/sync="v0.19.0" +golang.org/x/sys="v0.39.0" +golang.org/x/text="v0.32.0" +golang.org/x/tools="v0.40.0" +google.golang.org/protobuf="v1.36.10" +gopkg.in/yaml.v3="v3.0.1" +rsc.io/qr="v0.2.0" +software.sslmate.com/src/go-pkcs12="v0.2.0" +``` +
diff --git a/README.wehub.md b/README.wehub.md new file mode 100644 index 0000000..5ccb56a --- /dev/null +++ b/README.wehub.md @@ -0,0 +1,7 @@ +# WeHub 来源说明 + +- 原始项目:`gohugoio/hugo` +- 原始仓库:https://github.com/gohugoio/hugo +- 导入方式:上游默认分支的最新快照 +- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准 +- 本文件仅用于记录来源,不代表 WeHub 是原项目作者 diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..e6072ed --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,11 @@ +## Security Policy + +### Before You Report + +Please read [Hugo's Security Model](https://gohugo.io/about/security/) first. If the issue reproduces in an upstream project, please report it there — we cannot triage or patch on their behalf. + +### Reporting a Vulnerability + +If, after the above, you believe you have found a vulnerability in Hugo itself with a concrete, reproducible impact, report it privately to **[bjorn.erik.pedersen@gmail.com](mailto:bjorn.erik.pedersen@gmail.com)**. Include a minimal reproducer, the Hugo version, and the observed vs. expected behavior. + +You should receive an initial response within a few days. Confirmed issues are typically patched within days, depending on complexity. diff --git a/bufferpool/bufpool.go b/bufferpool/bufpool.go new file mode 100644 index 0000000..f05675e --- /dev/null +++ b/bufferpool/bufpool.go @@ -0,0 +1,38 @@ +// 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 bufferpool provides a pool of bytes buffers. +package bufferpool + +import ( + "bytes" + "sync" +) + +var bufferPool = &sync.Pool{ + New: func() any { + return &bytes.Buffer{} + }, +} + +// GetBuffer returns a buffer from the pool. +func GetBuffer() (buf *bytes.Buffer) { + return bufferPool.Get().(*bytes.Buffer) +} + +// PutBuffer returns a buffer to the pool. +// The buffer is reset before it is put back into circulation. +func PutBuffer(buf *bytes.Buffer) { + buf.Reset() + bufferPool.Put(buf) +} diff --git a/bufferpool/bufpool_test.go b/bufferpool/bufpool_test.go new file mode 100644 index 0000000..023724b --- /dev/null +++ b/bufferpool/bufpool_test.go @@ -0,0 +1,31 @@ +// Copyright 2016-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 bufferpool + +import ( + "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") + PutBuffer(buff) + + c.Assert(buff.Len(), qt.Equals, 0) +} diff --git a/cache/docs.go b/cache/docs.go new file mode 100644 index 0000000..b9c4984 --- /dev/null +++ b/cache/docs.go @@ -0,0 +1,2 @@ +// Package cache contains the different cache implementations. +package cache diff --git a/cache/dynacache/dynacache.go b/cache/dynacache/dynacache.go new file mode 100644 index 0000000..54bbf3a --- /dev/null +++ b/cache/dynacache/dynacache.go @@ -0,0 +1,647 @@ +// Copyright 2024 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dynacache + +import ( + "context" + "fmt" + "math" + "path" + "regexp" + "runtime" + "sync" + "time" + + "github.com/bep/lazycache" + "github.com/bep/logg" + "github.com/gohugoio/hugo/common/collections" + "github.com/gohugoio/hugo/common/herrors" + "github.com/gohugoio/hugo/common/loggers" + "github.com/gohugoio/hugo/common/paths" + "github.com/gohugoio/hugo/common/rungroup" + "github.com/gohugoio/hugo/config" + "github.com/gohugoio/hugo/helpers" + "github.com/gohugoio/hugo/identity" + "github.com/gohugoio/hugo/resources/resource" +) + +const minMaxSize = 10 + +type KeyIdentity struct { + Key any + Identity identity.Identity +} + +// New creates a new cache. +func New(opts Options) *Cache { + if opts.CheckInterval == 0 { + opts.CheckInterval = time.Second * 2 + } + + if opts.MaxSize == 0 { + opts.MaxSize = 100000 + } + if opts.Log == nil { + panic("nil Log") + } + + if opts.MinMaxSize == 0 { + opts.MinMaxSize = 30 + } + + stats := &stats{ + opts: opts, + adjustmentFactor: 1.0, + currentMaxSize: opts.MaxSize, + availableMemory: config.GetMemoryLimit(), + } + + infol := opts.Log.InfoCommand("dynacache") + + evictedIdentities := collections.NewStackThreadSafe[KeyIdentity]() + + onEvict := func(k, v any) { + if !opts.Watching { + return + } + identity.WalkIdentitiesShallow(v, func(level int, id identity.Identity) bool { + evictedIdentities.Push(KeyIdentity{Key: k, Identity: id}) + return false + }) + resource.MarkStale(v) + } + + c := &Cache{ + partitions: make(map[string]PartitionManager), + onEvict: onEvict, + evictedIdentities: evictedIdentities, + opts: opts, + stats: stats, + infol: infol, + } + + c.stop = c.start() + + return c +} + +// Options for the cache. +type Options struct { + Log loggers.Logger + CheckInterval time.Duration + MaxSize int + MinMaxSize int + Watching bool +} + +// Options for a partition. +type OptionsPartition struct { + // When to clear the this partition. + ClearWhen ClearWhen + + // Weight is a number between 1 and 100 that indicates how, in general, how big this partition may get. + Weight int +} + +func (o OptionsPartition) WeightFraction() float64 { + return float64(o.Weight) / 100 +} + +func (o OptionsPartition) CalculateMaxSize(maxSizePerPartition int) int { + return int(math.Floor(float64(maxSizePerPartition) * o.WeightFraction())) +} + +// A dynamic partitioned cache. +type Cache struct { + mu sync.RWMutex + + partitions map[string]PartitionManager + + onEvict func(k, v any) + evictedIdentities *collections.StackThreadSafe[KeyIdentity] + + opts Options + infol logg.LevelLogger + + stats *stats + stopOnce sync.Once + stop func() +} + +// DrainEvictedIdentities drains the evicted identities from the cache. +func (c *Cache) DrainEvictedIdentities() []KeyIdentity { + return c.evictedIdentities.Drain() +} + +// DrainEvictedIdentitiesMatching drains the evicted identities from the cache that match the given predicate. +func (c *Cache) DrainEvictedIdentitiesMatching(predicate func(KeyIdentity) bool) []KeyIdentity { + return c.evictedIdentities.DrainMatching(predicate) +} + +// ClearMatching clears all partition for which the predicate returns true. +func (c *Cache) ClearMatching(predicatePartition func(k string, p PartitionManager) bool, predicateValue func(k, v any) bool) { + if predicatePartition == nil { + predicatePartition = func(k string, p PartitionManager) bool { return true } + } + if predicateValue == nil { + panic("nil predicateValue") + } + g := rungroup.Run[PartitionManager](context.Background(), rungroup.Config[PartitionManager]{ + NumWorkers: len(c.partitions), + Handle: func(ctx context.Context, partition PartitionManager) error { + partition.clearMatching(predicateValue) + return nil + }, + }) + + for k, p := range c.partitions { + if !predicatePartition(k, p) { + continue + } + g.Enqueue(p) + } + + g.Wait() +} + +// ClearOnRebuild prepares the cache for a new rebuild taking the given changeset into account. +// predicate is optional and will clear any entry for which it returns true. +func (c *Cache) ClearOnRebuild(predicate func(k, v any) bool, changeset ...identity.Identity) { + g := rungroup.Run[PartitionManager](context.Background(), rungroup.Config[PartitionManager]{ + NumWorkers: len(c.partitions), + Handle: func(ctx context.Context, partition PartitionManager) error { + partition.clearOnRebuild(predicate, changeset...) + return nil + }, + }) + + for _, p := range c.partitions { + g.Enqueue(p) + } + + g.Wait() + + // Clear any entries marked as stale above. + g = rungroup.Run[PartitionManager](context.Background(), rungroup.Config[PartitionManager]{ + NumWorkers: len(c.partitions), + Handle: func(ctx context.Context, partition PartitionManager) error { + partition.clearStale() + return nil + }, + }) + + for _, p := range c.partitions { + g.Enqueue(p) + } + + g.Wait() +} + +type keysProvider interface { + Keys() []string +} + +// Keys returns a list of keys in all partitions. +func (c *Cache) Keys(predicate func(s string) bool) []string { + if predicate == nil { + predicate = func(s string) bool { return true } + } + var keys []string + for pn, g := range c.partitions { + pkeys := g.(keysProvider).Keys() + for _, k := range pkeys { + p := path.Join(pn, k) + if predicate(p) { + keys = append(keys, p) + } + } + + } + return keys +} + +func calculateMaxSizePerPartition(maxItemsTotal, totalWeightQuantity, numPartitions int) int { + if numPartitions == 0 { + panic("numPartitions must be > 0") + } + if totalWeightQuantity == 0 { + panic("totalWeightQuantity must be > 0") + } + + avgWeight := float64(totalWeightQuantity) / float64(numPartitions) + return int(math.Floor(float64(maxItemsTotal) / float64(numPartitions) * (100.0 / avgWeight))) +} + +// Stop stops the cache. +func (c *Cache) Stop() { + c.stopOnce.Do(func() { + c.stop() + }) +} + +func (c *Cache) adjustCurrentMaxSize() { + c.mu.RLock() + defer c.mu.RUnlock() + + if len(c.partitions) == 0 { + return + } + var m runtime.MemStats + runtime.ReadMemStats(&m) + s := c.stats + s.memstatsCurrent = m + // fmt.Printf("\n\nAvailable = %v\nAlloc = %v\nTotalAlloc = %v\nSys = %v\nNumGC = %v\nMaxSize = %d\nAdjustmentFactor=%f\n\n", helpers.FormatByteCount(s.availableMemory), helpers.FormatByteCount(m.Alloc), helpers.FormatByteCount(m.TotalAlloc), helpers.FormatByteCount(m.Sys), m.NumGC, c.stats.currentMaxSize, s.adjustmentFactor) + + if s.availableMemory >= s.memstatsCurrent.Alloc { + if s.adjustmentFactor <= 1.0 { + s.adjustmentFactor += 0.2 + } + } else { + // We're low on memory. + s.adjustmentFactor -= 0.4 + } + + if s.adjustmentFactor <= 0 { + s.adjustmentFactor = 0.05 + } + + if !s.adjustCurrentMaxSize() { + return + } + + totalWeight := 0 + for _, pm := range c.partitions { + totalWeight += pm.getOptions().Weight + } + + maxSizePerPartition := calculateMaxSizePerPartition(c.stats.currentMaxSize, totalWeight, len(c.partitions)) + + evicted := 0 + for _, p := range c.partitions { + evicted += p.adjustMaxSize(p.getOptions().CalculateMaxSize(maxSizePerPartition)) + } + + if evicted > 0 { + c.infol. + WithFields( + logg.Fields{ + {Name: "evicted", Value: evicted}, + {Name: "numGC", Value: m.NumGC}, + {Name: "limit", Value: helpers.FormatByteCount(c.stats.availableMemory)}, + {Name: "alloc", Value: helpers.FormatByteCount(m.Alloc)}, + {Name: "totalAlloc", Value: helpers.FormatByteCount(m.TotalAlloc)}, + }, + ).Logf("adjusted partitions' max size") + } +} + +func (c *Cache) start() func() { + ticker := time.NewTicker(c.opts.CheckInterval) + quit := make(chan struct{}) + + go func() { + for { + select { + case <-ticker.C: + c.adjustCurrentMaxSize() + // Reset the ticker to avoid drift. + ticker.Reset(c.opts.CheckInterval) + case <-quit: + ticker.Stop() + return + } + } + }() + + return func() { + close(quit) + } +} + +var partitionNameRe = regexp.MustCompile(`^\/[a-zA-Z0-9]{4}(\/[a-zA-Z0-9]+)?(\/[a-zA-Z0-9]+)?`) + +// GetOrCreatePartition gets or creates a partition with the given name. +func GetOrCreatePartition[K comparable, V any](c *Cache, name string, opts OptionsPartition) *Partition[K, V] { + if c == nil { + panic("nil Cache") + } + if opts.Weight < 1 || opts.Weight > 100 { + panic("invalid Weight, must be between 1 and 100") + } + + if !partitionNameRe.MatchString(name) { + panic(fmt.Sprintf("invalid partition name %q", name)) + } + + c.mu.RLock() + p, found := c.partitions[name] + c.mu.RUnlock() + if found { + return p.(*Partition[K, V]) + } + + c.mu.Lock() + defer c.mu.Unlock() + + // Double check. + p, found = c.partitions[name] + if found { + return p.(*Partition[K, V]) + } + + // At this point, we don't know the number of partitions or their configuration, but + // this will be re-adjusted later. + const numberOfPartitionsEstimate = 10 + maxSize := opts.CalculateMaxSize(c.opts.MaxSize / numberOfPartitionsEstimate) + + onEvict := func(k K, v V) { + c.onEvict(k, v) + } + + // Create a new partition and cache it. + partition := &Partition[K, V]{ + c: lazycache.New(lazycache.Options[K, V]{MaxEntries: maxSize, OnEvict: onEvict}), + maxSize: maxSize, + trace: c.opts.Log.Logger().WithLevel(logg.LevelTrace).WithField("partition", name), + opts: opts, + } + + c.partitions[name] = partition + + return partition +} + +// Partition is a partition in the cache. +type Partition[K comparable, V any] struct { + c *lazycache.Cache[K, V] + + zero V + + trace logg.LevelLogger + opts OptionsPartition + + maxSize int +} + +// GetOrCreate gets or creates a value for the given key. +func (p *Partition[K, V]) GetOrCreate(key K, create func(key K) (V, error)) (V, error) { + v, err := p.doGetOrCreate(key, create) + if err != nil { + return p.zero, err + } + if resource.StaleVersion(v) > 0 { + p.c.Delete(key) + return p.doGetOrCreate(key, create) + } + return v, err +} + +func (p *Partition[K, V]) doGetOrCreate(key K, create func(key K) (V, error)) (V, error) { + v, _, err := p.c.GetOrCreate(key, create) + return v, err +} + +func (p *Partition[K, V]) GetOrCreateWitTimeout(key K, duration time.Duration, create func(key K) (V, error)) (V, error) { + v, err := p.doGetOrCreateWitTimeout(key, duration, create) + if err != nil { + return p.zero, err + } + if resource.StaleVersion(v) > 0 { + p.c.Delete(key) + return p.doGetOrCreateWitTimeout(key, duration, create) + } + return v, err +} + +// GetOrCreateWitTimeout gets or creates a value for the given key and times out if the create function +// takes too long. +func (p *Partition[K, V]) doGetOrCreateWitTimeout(key K, duration time.Duration, create func(key K) (V, error)) (V, error) { + resultch := make(chan V, 1) + errch := make(chan error, 1) + + go func() { + var ( + v V + err error + ) + defer func() { + if r := recover(); r != nil { + if rerr, ok := r.(error); ok { + err = rerr + } else { + err = fmt.Errorf("panic: %v", r) + } + } + if err != nil { + errch <- err + } else { + resultch <- v + } + }() + v, _, err = p.c.GetOrCreate(key, create) + }() + + select { + case v := <-resultch: + return v, nil + case err := <-errch: + return p.zero, err + case <-time.After(duration): + return p.zero, &herrors.TimeoutError{ + Duration: duration, + } + } +} + +func (p *Partition[K, V]) clearMatching(predicate func(k, v any) bool) { + p.c.DeleteFunc(func(key K, v V) bool { + if predicate(key, v) { + p.trace.Log( + logg.StringFunc( + func() string { + return fmt.Sprintf("clearing cache key %v", key) + }, + ), + ) + return true + } + return false + }) +} + +func (p *Partition[K, V]) clearOnRebuild(predicate func(k, v any) bool, changeset ...identity.Identity) { + if predicate == nil { + predicate = func(k, v any) bool { + return false + } + } + opts := p.getOptions() + if opts.ClearWhen == ClearNever { + return + } + + if opts.ClearWhen == ClearOnRebuild { + // Clear all. + p.Clear() + return + } + + depsFinder := identity.NewFinder(identity.FinderConfig{}) + + shouldDelete := func(key K, v V) bool { + // We always clear elements marked as stale. + if resource.StaleVersion(v) > 0 { + return true + } + + // Now check if this entry has changed based on the changeset + // based on filesystem events. + if len(changeset) == 0 { + // Nothing changed. + return false + } + + var probablyDependent bool + identity.WalkIdentitiesShallow(v, func(level int, id2 identity.Identity) bool { + for _, id := range changeset { + if r := depsFinder.Contains(id, id2, -1); r > 0 { + // It's probably dependent, evict from cache. + probablyDependent = true + return true + } + } + return false + }) + + return probablyDependent + } + + // First pass. + // Second pass needs to be done in a separate loop to catch any + // elements marked as stale in the other partitions. + p.c.DeleteFunc(func(key K, v V) bool { + if predicate(key, v) || shouldDelete(key, v) { + p.trace.Log( + logg.StringFunc( + func() string { + return fmt.Sprintf("first pass: clearing cache key %v", key) + }, + ), + ) + return true + } + return false + }) +} + +func (p *Partition[K, V]) Keys() []K { + var keys []K + p.c.DeleteFunc(func(key K, v V) bool { + keys = append(keys, key) + return false + }) + return keys +} + +func (p *Partition[K, V]) clearStale() { + p.c.DeleteFunc(func(key K, v V) bool { + staleVersion := resource.StaleVersion(v) + if staleVersion > 0 { + p.trace.Log( + logg.StringFunc( + func() string { + return fmt.Sprintf("second pass: clearing cache key %v", key) + }, + ), + ) + } + + return staleVersion > 0 + }) +} + +// adjustMaxSize adjusts the max size of the and returns the number of items evicted. +func (p *Partition[K, V]) adjustMaxSize(newMaxSize int) int { + if newMaxSize < minMaxSize { + newMaxSize = minMaxSize + } + oldMaxSize := p.maxSize + if newMaxSize == oldMaxSize { + return 0 + } + p.maxSize = newMaxSize + // fmt.Println("Adjusting max size of partition from", oldMaxSize, "to", newMaxSize) + return p.c.Resize(newMaxSize) +} + +func (p *Partition[K, V]) getMaxSize() int { + return p.maxSize +} + +func (p *Partition[K, V]) getOptions() OptionsPartition { + return p.opts +} + +func (p *Partition[K, V]) Clear() { + p.c.DeleteFunc(func(key K, v V) bool { + return true + }) +} + +func (p *Partition[K, V]) Get(ctx context.Context, key K) (V, bool) { + return p.c.Get(key) +} + +type PartitionManager interface { + adjustMaxSize(addend int) int + getMaxSize() int + getOptions() OptionsPartition + clearOnRebuild(predicate func(k, v any) bool, changeset ...identity.Identity) + clearMatching(predicate func(k, v any) bool) + clearStale() +} + +const ( + ClearOnRebuild ClearWhen = iota + 1 + ClearOnChange + ClearNever +) + +type ClearWhen int + +type stats struct { + opts Options + memstatsCurrent runtime.MemStats + currentMaxSize int + availableMemory uint64 + + adjustmentFactor float64 +} + +func (s *stats) adjustCurrentMaxSize() bool { + newCurrentMaxSize := int(math.Floor(float64(s.opts.MaxSize) * s.adjustmentFactor)) + + if newCurrentMaxSize < s.opts.MinMaxSize { + newCurrentMaxSize = int(s.opts.MinMaxSize) + } + changed := newCurrentMaxSize != s.currentMaxSize + s.currentMaxSize = newCurrentMaxSize + return changed +} + +// CleanKey turns s into a format suitable for a cache key for this package. +// The key will be a Unix-styled path with a leading slash but no trailing slash. +func CleanKey(s string) string { + return path.Clean(paths.ToSlashPreserveLeading(s)) +} diff --git a/cache/dynacache/dynacache_test.go b/cache/dynacache/dynacache_test.go new file mode 100644 index 0000000..78b2fc8 --- /dev/null +++ b/cache/dynacache/dynacache_test.go @@ -0,0 +1,230 @@ +// Copyright 2024 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dynacache + +import ( + "errors" + "fmt" + "path/filepath" + "testing" + "time" + + qt "github.com/frankban/quicktest" + "github.com/gohugoio/hugo/common/loggers" + "github.com/gohugoio/hugo/identity" + "github.com/gohugoio/hugo/resources/resource" +) + +var ( + _ resource.StaleInfo = (*testItem)(nil) + _ identity.Identity = (*testItem)(nil) +) + +type testItem struct { + name string + staleVersion uint32 +} + +func (t testItem) StaleVersion() uint32 { + return t.staleVersion +} + +func (t testItem) IdentifierBase() string { + return t.name +} + +func TestCache(t *testing.T) { + t.Parallel() + c := qt.New(t) + + cache := New(Options{ + Log: loggers.NewDefault(), + }) + + c.Cleanup(func() { + cache.Stop() + }) + + opts := OptionsPartition{Weight: 30} + + c.Assert(cache, qt.Not(qt.IsNil)) + + p1 := GetOrCreatePartition[string, testItem](cache, "/aaaa/bbbb", opts) + c.Assert(p1, qt.Not(qt.IsNil)) + + p2 := GetOrCreatePartition[string, testItem](cache, "/aaaa/bbbb", opts) + + c.Assert(func() { GetOrCreatePartition[string, testItem](cache, "foo bar", opts) }, qt.PanicMatches, ".*invalid partition name.*") + c.Assert(func() { GetOrCreatePartition[string, testItem](cache, "/aaaa/cccc", OptionsPartition{Weight: 1234}) }, qt.PanicMatches, ".*invalid Weight.*") + + c.Assert(p2, qt.Equals, p1) + + p3 := GetOrCreatePartition[string, testItem](cache, "/aaaa/cccc", opts) + c.Assert(p3, qt.Not(qt.IsNil)) + c.Assert(p3, qt.Not(qt.Equals), p1) + + c.Assert(func() { New(Options{}) }, qt.PanicMatches, ".*nil Log.*") +} + +func TestCalculateMaxSizePerPartition(t *testing.T) { + t.Parallel() + c := qt.New(t) + + c.Assert(calculateMaxSizePerPartition(1000, 500, 5), qt.Equals, 200) + c.Assert(calculateMaxSizePerPartition(1000, 250, 5), qt.Equals, 400) + c.Assert(func() { calculateMaxSizePerPartition(1000, 250, 0) }, qt.PanicMatches, ".*must be > 0.*") + c.Assert(func() { calculateMaxSizePerPartition(1000, 0, 1) }, qt.PanicMatches, ".*must be > 0.*") +} + +func TestCleanKey(t *testing.T) { + c := qt.New(t) + + c.Assert(CleanKey("a/b/c"), qt.Equals, "/a/b/c") + c.Assert(CleanKey("/a/b/c"), qt.Equals, "/a/b/c") + c.Assert(CleanKey("a/b/c/"), qt.Equals, "/a/b/c") + c.Assert(CleanKey(filepath.FromSlash("/a/b/c/")), qt.Equals, "/a/b/c") +} + +func newTestCache(t *testing.T) *Cache { + cache := New( + Options{ + Log: loggers.NewDefault(), + }, + ) + + p1 := GetOrCreatePartition[string, testItem](cache, "/aaaa/bbbb", OptionsPartition{Weight: 30, ClearWhen: ClearOnRebuild}) + p2 := GetOrCreatePartition[string, testItem](cache, "/aaaa/cccc", OptionsPartition{Weight: 30, ClearWhen: ClearOnChange}) + + p1.GetOrCreate("clearOnRebuild", func(string) (testItem, error) { + return testItem{}, nil + }) + + p2.GetOrCreate("clearBecauseStale", func(string) (testItem, error) { + return testItem{ + staleVersion: 32, + }, nil + }) + + p2.GetOrCreate("clearBecauseIdentityChanged", func(string) (testItem, error) { + return testItem{ + name: "changed", + }, nil + }) + + p2.GetOrCreate("clearNever", func(string) (testItem, error) { + return testItem{ + staleVersion: 0, + }, nil + }) + + t.Cleanup(func() { + cache.Stop() + }) + + return cache +} + +func TestClear(t *testing.T) { + t.Parallel() + c := qt.New(t) + + predicateAll := func(string) bool { + return true + } + + cache := newTestCache(t) + + c.Assert(cache.Keys(predicateAll), qt.HasLen, 4) + + cache.ClearOnRebuild(nil) + + // Stale items are always cleared. + c.Assert(cache.Keys(predicateAll), qt.HasLen, 2) + + cache = newTestCache(t) + cache.ClearOnRebuild(nil, identity.StringIdentity("changed")) + + c.Assert(cache.Keys(nil), qt.HasLen, 1) + + cache = newTestCache(t) + + cache.ClearMatching(nil, func(k, v any) bool { + return k.(string) == "clearOnRebuild" + }) + + c.Assert(cache.Keys(predicateAll), qt.HasLen, 3) + + cache.adjustCurrentMaxSize() +} + +func TestPanicInCreate(t *testing.T) { + t.Parallel() + c := qt.New(t) + cache := newTestCache(t) + + p1 := GetOrCreatePartition[string, testItem](cache, "/aaaa/bbbb", OptionsPartition{Weight: 30, ClearWhen: ClearOnRebuild}) + + willPanic := func(i int) func() { + return func() { + p1.GetOrCreate(fmt.Sprintf("panic-%d", i), func(key string) (testItem, error) { + panic(errors.New(key)) + }) + } + } + + // GetOrCreateWitTimeout needs to recover from panics in the create func. + willErr := func(i int) error { + _, err := p1.GetOrCreateWitTimeout(fmt.Sprintf("error-%d", i), 10*time.Second, func(key string) (testItem, error) { + return testItem{}, errors.New(key) + }) + return err + } + + for i := range 3 { + for range 3 { + c.Assert(willPanic(i), qt.PanicMatches, fmt.Sprintf("panic-%d", i)) + c.Assert(willErr(i), qt.ErrorMatches, fmt.Sprintf("error-%d", i)) + } + } + + // Test the same keys again without the panic. + for i := range 3 { + for range 3 { + v, err := p1.GetOrCreate(fmt.Sprintf("panic-%d", i), func(key string) (testItem, error) { + return testItem{ + name: key, + }, nil + }) + c.Assert(err, qt.IsNil) + c.Assert(v.name, qt.Equals, fmt.Sprintf("panic-%d", i)) + + v, err = p1.GetOrCreateWitTimeout(fmt.Sprintf("error-%d", i), 10*time.Second, func(key string) (testItem, error) { + return testItem{ + name: key, + }, nil + }) + c.Assert(err, qt.IsNil) + c.Assert(v.name, qt.Equals, fmt.Sprintf("error-%d", i)) + } + } +} + +func TestAdjustCurrentMaxSize(t *testing.T) { + t.Parallel() + c := qt.New(t) + cache := newTestCache(t) + alloc := cache.stats.memstatsCurrent.Alloc + cache.adjustCurrentMaxSize() + c.Assert(cache.stats.memstatsCurrent.Alloc, qt.Not(qt.Equals), alloc) +} diff --git a/cache/filecache/filecache.go b/cache/filecache/filecache.go new file mode 100644 index 0000000..f5d5b09 --- /dev/null +++ b/cache/filecache/filecache.go @@ -0,0 +1,570 @@ +// Copyright 2026 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filecache + +import ( + "bytes" + "errors" + "fmt" + "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/bep/helpers/maphelpers" + "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 + + cfg FileCacheConfig + + entryLocker *lockTracker + + initOnce sync.Once + isInited bool + initErr error +} + +type lockTracker struct { + seen *maphelpers.ConcurrentSet[string] + + *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.seen.AddIfAbsent(id) + 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, cfg FileCacheConfig) *Cache { + if err := cfg.init(); err != nil { + panic(fmt.Sprintf("invalid cache config: %s", err)) + } + + return &Cache{ + Fs: fs, + entryLocker: &lockTracker{Locker: locker.NewLocker(), seen: maphelpers.NewConcurrentSet[string]()}, + cfg: cfg, + } +} + +// 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 { + if c == nil { + panic("cache is nil") + } + + c.initOnce.Do(func() { + c.isInited = true + // Create the base dir if it does not exist. + if err := c.Fs.MkdirAll("", 0o777); err != nil && !os.IsExist(err) { + err = fmt.Errorf("failled to create base cache directory: %s", err) + c.initErr = err + } + }) + return c.initErr +} + +// WriteCloser returns a transactional writer into the cache. +// It's important that it's closed when done. +func (c *Cache) WriteCloser(id string) (ItemInfo, io.WriteCloser, error) { + if err := c.init(); err != nil { + return ItemInfo{}, nil, err + } + + id = cleanID(id) + c.entryLocker.Lock(id) + + info := ItemInfo{Name: id} + + f, err := helpers.OpenFileForWriting(c.Fs, id) + if err != nil { + c.entryLocker.Unlock(id) + return info, nil, err + } + + return info, &lockedFile{ + File: f, + unlock: func() { c.entryLocker.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.entryLocker.Lock(id) + defer c.entryLocker.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.entryLocker.Lock(id) + return func() { + c.entryLocker.Unlock(id) + } +} + +// GetOrCreate tries to get the file with the given id from cache. If not found or expired, create will +// be invoked and the result cached. +// This method is protected by a named lock using the given id as identifier. +func (c *Cache) GetOrCreate(id string, create func() (io.ReadCloser, error)) (ItemInfo, io.ReadCloser, error) { + if err := c.init(); err != nil { + return ItemInfo{}, nil, err + } + id = cleanID(id) + + c.entryLocker.Lock(id) + defer c.entryLocker.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.cfg.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)) +} + +// AbsFilenameFromID returns the filename for the given id in the cache. +// This will be an absolute path. +func (c *Cache) AbsFilenameFromID(id string) string { + return filepath.Join(c.cfg.DirCompiled, cleanID(id)) +} + +// GetOrCreateInfo tries to get the item info with the given id from cache. If not found or expired, create will +// be invoked with the id. The create function is expected to create the cache item with the given id. The returned ItemInfo will have the id as Name. +// This method is protected by a named lock using the given id as identifier. +func (c *Cache) GetOrCreateInfo(id string, create func(id string) error) (ItemInfo, error) { + if err := c.init(); err != nil { + return ItemInfo{}, err + } + + id = cleanID(id) + + c.entryLocker.Lock(id) + defer c.entryLocker.Unlock(id) + + info := ItemInfo{Name: id} + + if !c.removeIfNeeded(id) { + // The file exists and is not expired, so we consider it a cache hit. + return info, nil + } + + if err := create(id); err != nil { + c.remove(id) + return info, err + } + + return info, nil +} + +func (c *Cache) writeReader(id string, r io.Reader) error { + dir := filepath.Dir(id) + if dir != "" { + _ = c.Fs.MkdirAll(dir, 0o777) + } + f, err := c.Fs.Create(id) + if err != nil { + return err + } + defer f.Close() + + _, _ = io.Copy(f, r) + + return nil +} + +// GetOrCreateBytes is the same as GetOrCreate, but produces a byte slice. +func (c *Cache) GetOrCreateBytes(id string, create func() ([]byte, error)) (ItemInfo, []byte, error) { + if err := c.init(); err != nil { + return ItemInfo{}, nil, err + } + id = cleanID(id) + + c.entryLocker.Lock(id) + defer c.entryLocker.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.cfg.MaxAge == 0 { + return info, b, nil + } + + if err := c.writeReader(id, bytes.NewReader(b)); err != nil { + return info, nil, err + } + + return info, b, nil +} + +// SetBytes sets the file content with the given id in the cache. +func (c *Cache) SetBytes(id string, data []byte) error { + if err := c.init(); err != nil { + return err + } + id = cleanID(id) + + c.entryLocker.Lock(id) + defer c.entryLocker.Unlock(id) + + if c.cfg.MaxAge == 0 { + // No caching. + return nil + } + + return c.writeReader(id, bytes.NewReader(data)) +} + +// GetBytes gets the file content with the given id from the cache, nil if none found. +func (c *Cache) GetBytes(id string) ([]byte, error) { + _, b, err := c.GetItemBytes(id) + return b, err +} + +// GetItemBytes gets the ItemInfo and file content with the given id from the cache, nil if none found. +func (c *Cache) GetItemBytes(id string) (ItemInfo, []byte, error) { + if err := c.init(); err != nil { + return ItemInfo{}, nil, err + } + id = cleanID(id) + + c.entryLocker.Lock(id) + defer c.entryLocker.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.entryLocker.Lock(id) + defer c.entryLocker.Unlock(id) + + info := ItemInfo{Name: id} + + r := c.getOrRemove(id) + + return info, r, nil +} + +// removeIfNeeded checks if the file with the given id should be re-created. +func (c *Cache) removeIfNeeded(id string) bool { + if c.cfg.MaxAge == 0 { + // No caching, remove. + c.remove(id) + return true + } + if removed, err := c.removeIfExpired(id); err != nil || removed { + return true + } + return false +} + +// getOrRemove gets the file with the given id. If it's expired, it will +// be removed. +func (c *Cache) getOrRemove(id string) hugio.ReadSeekCloser { + if c.cfg.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.cfg.MaxAge == 0 { + // No caching. + return nil, false + } + + f, err := c.Fs.Open(id) + if err != nil { + return nil, false + } + defer f.Close() + + b, err := io.ReadAll(f) + if err != nil { + return nil, false + } + + removed, err := c.removeIfExpired(id) + if err != nil { + return nil, false + } + + return b, removed +} + +func (c *Cache) removeIfExpired(id string) (bool, error) { + if c.cfg.MaxAge <= 0 { + return false, nil + } + + fi, err := c.Fs.Stat(id) + if err != nil { + return false, err + } + + if c.isExpired(fi.ModTime()) { + c.remove(id) + return true, nil + } + + return false, nil +} + +func (c *Cache) remove(id string) { + if c.cfg.entryIsDir { + c.Fs.RemoveAll(id) + } else { + c.Fs.Remove(id) + } +} + +func (c *Cache) isExpired(modTime time.Time) bool { + if c.cfg.MaxAge < 0 { + return false + } + + // Note the use of time.Since here. + // We cannot use Hugo's global Clock for this. + return c.cfg.MaxAge == 0 || time.Since(modTime) > c.cfg.MaxAge +} + +// For testing +func (c *Cache) GetString(id string) string { + id = cleanID(id) + + c.entryLocker.Lock(id) + defer c.entryLocker.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 + +func (f Caches) SetResourceFs(fs afero.Fs) { + for _, c := range f { + if c.cfg.IsResourceDir { + if c.isInited { + panic("cannot set resource fs after init") + } + c.Fs = hugofs.NewBasePathFs(fs, c.cfg.DirCompiled) + } + } +} + +// Get gets a named cache, nil if none found. +func (f Caches) Get(name string) *Cache { + return f[strings.ToLower(name)] +} + +// NewCaches creates a new set of file caches from the given +// configuration. +func NewCaches(dcfg Configs, sourceFs afero.Fs) (Caches, error) { + fs := sourceFs + + m := make(Caches) + for k, v := range dcfg { + var cfs afero.Fs + if v.IsResourceDir { + cfs = nil // Set later. TODO(bep) this needs to be cleanded up. + } else { + cfs = hugofs.NewBasePathFs(fs, v.DirCompiled) + } + + c := NewCache(cfs, v) + + m[k] = c + } + + 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.cfg.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 new file mode 100644 index 0000000..feaa07d --- /dev/null +++ b/cache/filecache/filecache_config.go @@ -0,0 +1,317 @@ +// 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 ( + "encoding/json" + "errors" + "fmt" + "path" + "path/filepath" + "strings" + "time" + + "github.com/gohugoio/hugo/common/hmaps" + "github.com/gohugoio/hugo/config" + + "github.com/mitchellh/mapstructure" + "github.com/spf13/afero" +) + +const ( + resourcesGenDir = ":resourceDir/_gen" + cacheDirProject = ":cacheDir/:project" +) + +const ( + CacheKeyImages = "images" + CacheKeyAssets = "assets" + CacheKeyModules = "modules" + CacheKeyModuleQueries = "modulequeries" + CacheKeyModuleGitInfo = "modulegitinfo" + CacheKeyGetResource = "getresource" + CacheKeyMisc = "misc" +) + +type Configs map[string]FileCacheConfig + +// CacheDirModules returns the compiled path to the modules cache. +// For internal use. +func (c Configs) CacheDirModules() string { + return c[CacheKeyModules].DirCompiled +} + +// CacheDirMisc returns the compiled path to the misc cache. +// For internal use. +func (c Configs) CacheDirMisc() string { + return c[CacheKeyMisc].DirCompiled +} + +var defaultCacheConfigs = Configs{ + CacheKeyModules: { + MaxAge: -1, + Dir: ":cacheDir/modules", + fileCacheConfigInternal: fileCacheConfigInternal{ + entryIsDir: true, + isReadOnly: true, // we need to make it writable when pruning. + }, + }, + CacheKeyModuleQueries: { + MaxAge: 24 * time.Hour, + Dir: ":cacheDir/modules", + }, + CacheKeyModuleGitInfo: { + MaxAge: 24 * time.Hour, + Dir: ":cacheDir/modules", + fileCacheConfigInternal: fileCacheConfigInternal{ + entryIsDir: true, + }, + }, + CacheKeyImages: { + MaxAge: -1, + Dir: resourcesGenDir, + }, + CacheKeyAssets: { + MaxAge: -1, + Dir: resourcesGenDir, + }, + CacheKeyGetResource: { + MaxAge: -1, // Never expire + Dir: cacheDirProject, + }, + CacheKeyMisc: { + MaxAge: -1, + Dir: cacheDirProject, + }, +} + +func init() { + for k, v := range defaultCacheConfigs { + v.name = k + defaultCacheConfigs[k] = v + } +} + +type 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 + + fileCacheConfigInternal `json:"-"` +} + +func (cfg *FileCacheConfig) init() error { + if cfg.DirCompiled == "" { + // From unit tests. Just check that it does not contain any placeholders. + if strings.Contains(cfg.Dir, ":") { + return fmt.Errorf("cache dir %q contains unresolved placeholders", cfg.Dir) + } + cfg.DirCompiled = cfg.Dir + } + // Sanity check the config. + if len(cfg.DirCompiled) < 5 { + panic(fmt.Sprintf("invalid cache dir: %q", cfg.DirCompiled)) + } + return nil +} + +type fileCacheConfigInternal struct { + DirCompiled string + + name string // The name of this cache, e.g. "images", "modules" etc. + entryIsDir bool // when set, the cache entries represents directories directly below the base dir. + isReadOnly bool // when set, the cache is read only and needs to be pruned differently. This is used for the Go modules cache. + IsResourceDir bool // resources/_gen will get its own composite filesystem that also checks any theme. TODO(bep) unexport this. +} + +// MarshalJSON marshals FileCacheConfig to JSON with MaxAge as a human-readable string. +func (c FileCacheConfig) MarshalJSON() ([]byte, error) { + var maxAge any + if c.MaxAge == -1 { + maxAge = -1 + } else { + maxAge = strings.TrimSuffix(c.MaxAge.String(), "0m0s") + } + return json.Marshal(&struct { + MaxAge any `json:"maxAge"` + Dir string `json:"dir"` + }{ + MaxAge: maxAge, + Dir: c.Dir, + }) +} + +// ImageCache gets the file cache for processed images. +func (f Caches) ImageCache() *Cache { + return f[CacheKeyImages] +} + +// ModulesCache gets the file cache for Hugo Modules. +func (f Caches) ModulesCache() *Cache { + return f[CacheKeyModules] +} + +// ModuleQueriesCache gets the file cache for Hugo Module version queries. +// Returns nil if not found. +func (f Caches) ModuleQueriesCache() *Cache { + c, ok := f[CacheKeyModuleQueries] + if !ok { + panic("module queries cache not set") + } + return c +} + +// ModuleGitInfoCache gets the file cache for Hugo Module git info. +func (f Caches) ModuleGitInfoCache() *Cache { + c, ok := f[CacheKeyModuleGitInfo] + if !ok { + panic("module git info cache not set") + } + return c +} + +// AssetsCache gets the file cache for assets (processed resources, SCSS etc.). +func (f Caches) AssetsCache() *Cache { + return f[CacheKeyAssets] +} + +// 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.(hmaps.Params); !ok { + continue + } + var ok bool + cc, ok := c[k] + if !ok { + return nil, fmt.Errorf("%q is not a valid cache name", k) + } + + dc := &mapstructure.DecoderConfig{ + Result: &cc, + 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") + } + + c[k] = 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 new file mode 100644 index 0000000..2215f13 --- /dev/null +++ b/cache/filecache/filecache_config_test.go @@ -0,0 +1,171 @@ +// 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 ( + "encoding/json" + "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.misc] +maxAge = "10m" +dir = "/path/to/c1" +[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["misc"] + c.Assert(c2.MaxAge.String(), qt.Equals, "10m0s") + c.Assert(c2.DirCompiled, qt.Equals, filepath.FromSlash("/path/to/c1/filecache/misc")) + + c3 := decoded["images"] + c.Assert(c3.MaxAge, qt.Equals, time.Duration(-1)) + c.Assert(c3.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.misc] +maxAge = 1234 +dir = "/path/to/c1" +[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] + miscConfig := decoded[filecache.CacheKeyMisc] + + 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(miscConfig.DirCompiled, qt.Equals, "/cache/thecache/hugoproject/filecache/misc") + } + + c.Assert(imgConfig.IsResourceDir, qt.Equals, true) + c.Assert(miscConfig.IsResourceDir, qt.Equals, false) +} + +func TestFileCacheConfigMarshalJSON(t *testing.T) { + c := qt.New(t) + + cfg := config.New() + cfg.Set("cacheDir", "/cache") + cfg.Set("workingDir", "/my/project") + + fs := afero.NewMemMapFs() + decoded := testconfig.GetTestConfigs(fs, cfg).Base.Caches + + moduleQueriesConfig := decoded[filecache.CacheKeyModuleQueries] + c.Assert(moduleQueriesConfig.MaxAge, qt.Equals, 24*time.Hour) + + // Also verify the new moduleGitInfo cache. + moduleGitInfoConfig := decoded[filecache.CacheKeyModuleGitInfo] + c.Assert(moduleGitInfoConfig.MaxAge, qt.Equals, 24*time.Hour) + + b, err := json.Marshal(moduleQueriesConfig) + c.Assert(err, qt.IsNil) + + c.Assert(string(b), qt.Contains, `"maxAge":"24h"`) + c.Assert(string(b), qt.Not(qt.Contains), "86400000000000") + c.Assert(string(b), qt.Not(qt.Contains), "8.64e") + + moduleQueriesConfig.MaxAge = -1 + b, err = json.Marshal(moduleQueriesConfig) + c.Assert(err, qt.IsNil) + c.Assert(string(b), qt.Contains, `"maxAge":-1`) +} diff --git a/cache/filecache/filecache_integration_test.go b/cache/filecache/filecache_integration_test.go new file mode 100644 index 0000000..88873c7 --- /dev/null +++ b/cache/filecache/filecache_integration_test.go @@ -0,0 +1,105 @@ +// Copyright 2024 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filecache_test + +import ( + "path/filepath" + "testing" + "time" + + qt "github.com/frankban/quicktest" + "github.com/gohugoio/hugo/htesting" + "github.com/gohugoio/hugo/hugolib" +) + +// See issue #10781. That issue wouldn't have been triggered if we kept +// the empty root directories (e.g. _resources/gen/images). +// It's still an upstream Go issue that we also need to handle, but +// this is a test for the first part. +func TestPruneShouldPreserveEmptyCacheRoots(t *testing.T) { + files := ` +-- hugo.toml -- +baseURL = "https://example.com" +-- content/_index.md -- +--- +title: "Home" +--- + +` + + b := hugolib.Test(t, files, hugolib.TestOptOsFs(), hugolib.TestOptWithConfig(func(c *hugolib.IntegrationTestConfig) { + c.RunGC = true + })) + + _, err := b.H.BaseFs.ResourcesCache.Stat(filepath.Join("_gen", "images")) + + b.Assert(err, qt.IsNil) +} + +func TestPruneImages(t *testing.T) { + if htesting.IsCI() { + // TODO(bep) + t.Skip("skip flaky test on CI server") + } + t.Skip("skip flaky test") + files := ` +-- hugo.toml -- +baseURL = "https://example.com" +[caches] +[caches.images] +maxAge = "200ms" +dir = ":resourceDir/_gen" +-- content/_index.md -- +--- +title: "Home" +--- +-- assets/a/pixel.png -- +iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== +-- layouts/home.html -- +{{ warnf "HOME!" }} +{{ $img := resources.GetMatch "**.png" }} +{{ $img = $img.Resize "3x3" }} +{{ $img.RelPermalink }} + + + +` + + b := hugolib.TestRunning(t, files, hugolib.TestOptOsFs(), hugolib.TestOptInfo(), hugolib.TestOptWithConfig(func(c *hugolib.IntegrationTestConfig) { + c.RunGC = true + })) + + b.Assert(b.GCCount, qt.Equals, 0) + b.Assert(b.H, qt.IsNotNil) + + imagesCacheDir := filepath.Join("_gen", "images") + _, err := b.H.BaseFs.ResourcesCache.Stat(imagesCacheDir) + + b.Assert(err, qt.IsNil) + + // TODO(bep) we need a way to test full rebuilds. + // For now, just sleep a little so the cache elements expires. + time.Sleep(500 * time.Millisecond) + + b.RenameFile("assets/a/pixel.png", "assets/b/pixel2.png").Build() + + b.Assert(b.GCCount, qt.Equals, 1) + // Build it again to GC the empty a dir. + b.Build() + + _, err = b.H.BaseFs.ResourcesCache.Stat(filepath.Join(imagesCacheDir, "a")) + b.Assert(err, qt.Not(qt.IsNil)) + _, err = b.H.BaseFs.ResourcesCache.Stat(imagesCacheDir) + b.Assert(err, qt.IsNil) +} diff --git a/cache/filecache/filecache_pruner.go b/cache/filecache/filecache_pruner.go new file mode 100644 index 0000000..d6322cb --- /dev/null +++ b/cache/filecache/filecache_pruner.go @@ -0,0 +1,172 @@ +// 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.cfg.entryIsDir { + return c.pruneRootDirs(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 && c.entryLocker.seen.Len() > 0 { + // Remove it if it's not been touched/used in the last build. + shouldRemove = !c.entryLocker.seen.Has(name) + } + + if 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) pruneRootDirs(force bool) (int, error) { + dirs, err := afero.ReadDir(c.Fs, "") + if err != nil { + if herrors.IsNotExist(err) { + return 0, nil + } + return 0, err + } + + counter := 0 + + for _, dir := range dirs { + if !dir.IsDir() { + continue + } + + count, err := c.pruneRootDir(dir.Name(), force) + if err != nil { + return counter, err + } + counter += count + } + + return counter, nil +} + +func (c *Cache) pruneRootDir(dirname string, force bool) (int, error) { + if err := c.init(); err != nil { + return 0, err + } + + // Sanity check. + if dirname != "pkg" && len(dirname) < 5 { + panic(fmt.Sprintf("invalid cache dir name: %q", dirname)) + } + + info, err := c.Fs.Stat(dirname) + if err != nil { + if herrors.IsNotExist(err) { + return 0, nil + } + return 0, err + } + + if !force && !c.isExpired(info.ModTime()) { + return 0, nil + } + + if c.cfg.isReadOnly { + return hugofs.MakeReadableAndRemoveAllModulePkgDir(c.Fs, dirname) + } + + return 1, c.Fs.RemoveAll(dirname) +} diff --git a/cache/filecache/filecache_pruner_test.go b/cache/filecache/filecache_pruner_test.go new file mode 100644 index 0000000..117ff34 --- /dev/null +++ b/cache/filecache/filecache_pruner_test.go @@ -0,0 +1,115 @@ +// 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" + "testing/synctest" + "time" + + "github.com/gohugoio/hugo/cache/filecache" + "github.com/spf13/afero" + + qt "github.com/frankban/quicktest" +) + +func TestPrune(t *testing.T) { + t.Parallel() + + synctest.Test(t, func(t *testing.T) { + c := qt.New(t) + + configStr := ` +resourceDir = "myresources" +contentDir = "content" +dataDir = "data" +i18nDir = "i18n" +layoutDir = "layouts" +assetDir = "assets" +archeTypedir = "archetypes" + +[caches] +[caches.misc] +maxAge = "200ms" +dir = "/cache/c" +[caches.assets] +maxAge = "200ms" +dir = ":resourceDir/_gen" +[caches.images] +maxAge = "200ms" +dir = ":resourceDir/_gen" +` + + for _, name := range []string{filecache.CacheKeyAssets, filecache.CacheKeyImages} { + msg := qt.Commentf("cache: %s", name) + fs := afero.NewMemMapFs() + p := newPathsSpec(t, fs, configStr) + fileCachConfig := p.Cfg.GetConfigSection("caches").(filecache.Configs) + caches, err := filecache.NewCaches(fileCachConfig, fs) + c.Assert(err, qt.IsNil) + caches.SetResourceFs(fs) + cache := caches[name] + for i := 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(fileCachConfig, fs) + c.Assert(err, qt.IsNil) + caches.SetResourceFs(fs) + 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 new file mode 100644 index 0000000..5beb405 --- /dev/null +++ b/cache/filecache/filecache_test.go @@ -0,0 +1,285 @@ +// 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/htesting" + + "github.com/gohugoio/hugo/cache/filecache" + "github.com/gohugoio/hugo/common/hugio" + "github.com/gohugoio/hugo/config" + "github.com/gohugoio/hugo/config/testconfig" + "github.com/gohugoio/hugo/helpers" + + "github.com/gohugoio/hugo/hugofs" + "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.misc] +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) + fileCachConfig := p.Cfg.GetConfigSection("caches").(filecache.Configs) + + caches, err := filecache.NewCaches(fileCachConfig, p.Fs.Source) + c.Assert(err, qt.IsNil) + caches.SetResourceFs(p.SourceFs) + + cache := caches.Get("Misc") + 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()} { + 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") + } + } + + 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().GetItemBytes("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) { + htesting.SkipSlowTestUnlessCI(t) + t.Parallel() + + c := qt.New(t) + + configStr := ` +resourceDir = "myresources" +contentDir = "content" +dataDir = "data" +i18nDir = "i18n" +layoutDir = "layouts" +assetDir = "assets" +archeTypedir = "archetypes" + +[caches] +[caches.misc] +maxAge = "1s" +dir = "/cache/c" + +` + + p := newPathsSpec(t, afero.NewMemMapFs(), configStr) + fileCachConfig := p.Cfg.GetConfigSection("caches").(filecache.Configs) + caches, err := filecache.NewCaches(fileCachConfig, p.Fs.Source) + c.Assert(err, qt.IsNil) + caches.SetResourceFs(p.Fs.Source) + + const cacheName = "misc" + + 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 + } + } + + cfg := filecache.FileCacheConfig{ + MaxAge: 100 * time.Hour, + Dir: "cache/c", + } + + cache := filecache.NewCache(afero.NewMemMapFs(), cfg) + + 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, nil) + c.Assert(err, qt.IsNil) + return p +} diff --git a/cache/httpcache/httpcache.go b/cache/httpcache/httpcache.go new file mode 100644 index 0000000..44f243b --- /dev/null +++ b/cache/httpcache/httpcache.go @@ -0,0 +1,240 @@ +// Copyright 2024 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package httpcache + +import ( + "encoding/json" + "time" + + "github.com/gobwas/glob" + "github.com/gohugoio/hugo/common/predicate" + "github.com/gohugoio/hugo/config" + "github.com/mitchellh/mapstructure" +) + +// DefaultConfig holds the default configuration for the HTTP cache. +var DefaultConfig = Config{ + RespectCacheControlNoStoreInRequest: true, + RespectCacheControlNoStoreInResponse: false, + Cache: Cache{ + For: GlobMatcher{ + Excludes: []string{"**"}, + }, + }, + Polls: []PollConfig{ + { + For: GlobMatcher{ + Includes: []string{"**"}, + }, + Disable: true, + }, + }, +} + +// Config holds the configuration for the HTTP cache. +type Config struct { + // When enabled and there's a Cache-Control: no-store directive in the request, response will never be stored in disk cache. + RespectCacheControlNoStoreInRequest bool + + // When enabled and there's a Cache-Control: no-store directive in the response, response will never be stored in disk cache. + RespectCacheControlNoStoreInResponse bool + + // Enables HTTP cache behavior (RFC 9111) for these resources. + // When this is not enabled for a resource, Hugo will go straight to the file cache. + Cache Cache + + // Polls holds a list of configurations for polling remote resources to detect changes in watch mode. + // This can be disabled for some resources, typically if they are known to not change. + Polls []PollConfig +} + +type Cache struct { + // Enable HTTP cache behavior (RFC 9111) for these resources. + For GlobMatcher +} + +func (c *Config) Compile() (ConfigCompiled, error) { + cc := ConfigCompiled{ + Base: *c, + } + + p, err := c.Cache.For.CompilePredicate() + if err != nil { + return cc, err + } + + cc.For = p + + for _, pc := range c.Polls { + + p, err := pc.For.CompilePredicate() + if err != nil { + return cc, err + } + + cc.PollConfigs = append(cc.PollConfigs, PollConfigCompiled{ + For: p, + Config: pc, + }) + } + + return cc, nil +} + +// PollConfig holds the configuration for polling remote resources to detect changes in watch mode. +type PollConfig struct { + // What remote resources to apply this configuration to. + For GlobMatcher + + // Disable polling for this configuration. + Disable bool + + // Low is the lower bound for the polling interval. + // This is the starting point when the resource has recently changed, + // if that resource stops changing, the polling interval will gradually increase towards High. + Low time.Duration + + // High is the upper bound for the polling interval. + // This is the interval used when the resource is stable. + High time.Duration +} + +func (c PollConfig) MarshalJSON() (b []byte, err error) { + // Marshal the durations as strings. + type Alias PollConfig + return json.Marshal(&struct { + Low string + High string + Alias + }{ + Low: c.Low.String(), + High: c.High.String(), + Alias: (Alias)(c), + }) +} + +type GlobMatcher struct { + // Excludes holds a list of glob patterns that will be excluded. + Excludes []string + + // Includes holds a list of glob patterns that will be included. + Includes []string +} + +func (gm GlobMatcher) IsZero() bool { + return len(gm.Includes) == 0 && len(gm.Excludes) == 0 +} + +type ConfigCompiled struct { + Base Config + For predicate.P[string] + PollConfigs []PollConfigCompiled +} + +func (c *ConfigCompiled) PollConfigFor(s string) PollConfigCompiled { + for _, pc := range c.PollConfigs { + if pc.For(s) { + return pc + } + } + return PollConfigCompiled{} +} + +func (c *ConfigCompiled) IsPollingDisabled() bool { + for _, pc := range c.PollConfigs { + if !pc.Config.Disable { + return false + } + } + return true +} + +type PollConfigCompiled struct { + For predicate.P[string] + Config PollConfig +} + +func (p PollConfigCompiled) IsZero() bool { + return p.For == nil +} + +func (gm *GlobMatcher) CompilePredicate() (func(string) bool, error) { + if gm.IsZero() { + panic("no includes or excludes") + } + var b predicate.PR[string] + for _, include := range gm.Includes { + g, err := glob.Compile(include, '/') + if err != nil { + return nil, err + } + fn := func(s string) predicate.Match { + return predicate.BoolMatch(g.Match(s)) + } + b = b.Or(fn) + } + + for _, exclude := range gm.Excludes { + g, err := glob.Compile(exclude, '/') + if err != nil { + return nil, err + } + fn := func(s string) predicate.Match { + return predicate.BoolMatch(!g.Match(s)) + } + b = b.And(fn) + } + + return b.BoolFunc(), nil +} + +func DecodeConfig(_ config.BaseConfig, m map[string]any) (Config, error) { + if len(m) == 0 { + return DefaultConfig, nil + } + + var c Config + + dc := &mapstructure.DecoderConfig{ + Result: &c, + DecodeHook: mapstructure.StringToTimeDurationHookFunc(), + WeaklyTypedInput: true, + } + + decoder, err := mapstructure.NewDecoder(dc) + if err != nil { + return c, err + } + + if err := decoder.Decode(m); err != nil { + return c, err + } + + if c.Cache.For.IsZero() { + c.Cache.For = DefaultConfig.Cache.For + } + + for pci := range c.Polls { + if c.Polls[pci].For.IsZero() { + c.Polls[pci].For = DefaultConfig.Cache.For + c.Polls[pci].Disable = true + } + } + + if len(c.Polls) == 0 { + c.Polls = DefaultConfig.Polls + } + + return c, nil +} diff --git a/cache/httpcache/httpcache_integration_test.go b/cache/httpcache/httpcache_integration_test.go new file mode 100644 index 0000000..4d6a5f7 --- /dev/null +++ b/cache/httpcache/httpcache_integration_test.go @@ -0,0 +1,95 @@ +// Copyright 2024 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package httpcache_test + +import ( + "testing" + "time" + + qt "github.com/frankban/quicktest" + "github.com/gohugoio/hugo/hugolib" +) + +func TestConfigCustom(t *testing.T) { + t.Parallel() + + files := ` +-- hugo.toml -- +[httpcache] +[httpcache.cache.for] +includes = ["**gohugo.io**"] +[[httpcache.polls]] +low = "5s" +high = "32s" +[httpcache.polls.for] +includes = ["**gohugo.io**"] + + +` + + b := hugolib.Test(t, files) + + httpcacheConf := b.H.Configs.Base.HTTPCache + compiled := b.H.Configs.Base.C.HTTPCache + + b.Assert(httpcacheConf.Cache.For.Includes, qt.DeepEquals, []string{"**gohugo.io**"}) + b.Assert(httpcacheConf.Cache.For.Excludes, qt.IsNil) + + pc := compiled.PollConfigFor("https://gohugo.io/foo.jpg") + b.Assert(pc.Config.Low, qt.Equals, 5*time.Second) + b.Assert(pc.Config.High, qt.Equals, 32*time.Second) + b.Assert(compiled.PollConfigFor("https://example.com/foo.jpg").IsZero(), qt.IsTrue) +} + +func TestConfigDefault(t *testing.T) { + t.Parallel() + + files := ` +-- hugo.toml -- +` + b := hugolib.Test(t, files) + + compiled := b.H.Configs.Base.C.HTTPCache + + b.Assert(compiled.For("https://gohugo.io/posts.json"), qt.IsFalse) + b.Assert(compiled.For("https://gohugo.io/foo.jpg"), qt.IsFalse) + b.Assert(compiled.PollConfigFor("https://gohugo.io/foo.jpg").Config.Disable, qt.IsTrue) +} + +func TestConfigPollsOnly(t *testing.T) { + t.Parallel() + files := ` +-- hugo.toml -- +[httpcache] +[[httpcache.polls]] +low = "5s" +high = "32s" +[httpcache.polls.for] +includes = ["**gohugo.io**"] + + +` + + b := hugolib.Test(t, files) + + compiled := b.H.Configs.Base.C.HTTPCache + + b.Assert(compiled.For("https://gohugo.io/posts.json"), qt.IsFalse) + b.Assert(compiled.For("https://gohugo.io/foo.jpg"), qt.IsFalse) + + pc := compiled.PollConfigFor("https://gohugo.io/foo.jpg") + b.Assert(pc.Config.Low, qt.Equals, 5*time.Second) + b.Assert(pc.Config.High, qt.Equals, 32*time.Second) + b.Assert(compiled.PollConfigFor("https://example.com/foo.jpg").IsZero(), qt.IsTrue) +} diff --git a/cache/httpcache/httpcache_test.go b/cache/httpcache/httpcache_test.go new file mode 100644 index 0000000..737dff2 --- /dev/null +++ b/cache/httpcache/httpcache_test.go @@ -0,0 +1,73 @@ +// Copyright 2024 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package httpcache + +import ( + "testing" + + qt "github.com/frankban/quicktest" + "github.com/gohugoio/hugo/config" +) + +func TestGlobMatcher(t *testing.T) { + c := qt.New(t) + + g := GlobMatcher{ + Includes: []string{"**/*.jpg", "**.png", "**/bar/**"}, + Excludes: []string{"**/foo.jpg", "**.css"}, + } + + p, err := g.CompilePredicate() + c.Assert(err, qt.IsNil) + + c.Assert(p("foo.jpg"), qt.IsFalse) + c.Assert(p("foo.png"), qt.IsTrue) + c.Assert(p("foo/bar.jpg"), qt.IsTrue) + c.Assert(p("foo/bar.png"), qt.IsTrue) + c.Assert(p("foo/bar/foo.jpg"), qt.IsFalse) + c.Assert(p("foo/bar/foo.css"), qt.IsFalse) + c.Assert(p("foo.css"), qt.IsFalse) + c.Assert(p("foo/bar/foo.css"), qt.IsFalse) + c.Assert(p("foo/bar/foo.xml"), qt.IsTrue) +} + +func TestDefaultConfig(t *testing.T) { + c := qt.New(t) + + _, err := DefaultConfig.Compile() + c.Assert(err, qt.IsNil) +} + +func TestDecodeConfigInjectsDefaultAndCompiles(t *testing.T) { + c := qt.New(t) + + cfg, err := DecodeConfig(config.BaseConfig{}, map[string]any{}) + c.Assert(err, qt.IsNil) + c.Assert(cfg, qt.DeepEquals, DefaultConfig) + + _, err = cfg.Compile() + c.Assert(err, qt.IsNil) + + cfg, err = DecodeConfig(config.BaseConfig{}, map[string]any{ + "cache": map[string]any{ + "polls": []map[string]any{ + {"disable": true}, + }, + }, + }) + c.Assert(err, qt.IsNil) + + _, err = cfg.Compile() + c.Assert(err, qt.IsNil) +} diff --git a/check.sh b/check.sh new file mode 100755 index 0000000..20aacb1 --- /dev/null +++ b/check.sh @@ -0,0 +1,82 @@ +#!/bin/bash + +set -e + +# Default to all packages if none specified +PACKAGES="${1:-./...}" + +echo "==> Checking packages: $PACKAGES" + +# Timing arrays +declare -a STEP_NAMES +declare -a STEP_TIMES + +time_step() { + local name="$1" + shift + local start=$(date +%s.%N) + "$@" + local end=$(date +%s.%N) + local elapsed=$(echo "$end - $start" | bc) + STEP_NAMES+=("$name") + STEP_TIMES+=("$elapsed") +} + +# Check gofmt +run_gofmt() { + echo "==> Running gofmt..." + # Convert package pattern to path (e.g., ./hugolib/... -> ./hugolib) + local path="${PACKAGES%/...}" + GOFMT_OUTPUT=$(gofmt -l "$path" 2>&1) || true + if [ -n "$GOFMT_OUTPUT" ]; then + echo "gofmt found issues in:" + echo "$GOFMT_OUTPUT" + exit 1 + fi + echo " OK" +} + +# Run staticcheck +run_staticcheck() { + # Check if staticcheck is installed, install if not + if ! command -v staticcheck &> /dev/null; then + echo "==> Installing staticcheck..." + go install honnef.co/go/tools/cmd/staticcheck@latest + fi + echo "==> Running staticcheck..." + staticcheck $PACKAGES + echo " OK" +} + +# Run tests +run_tests() { + echo "==> Running tests..." + local output + if ! output=$(go test -failfast $PACKAGES 2>&1); then + echo "$output" + exit 1 + fi + echo " OK" +} + +# Run all steps with timing +TOTAL_START=$(date +%s.%N) + +time_step "gofmt" run_gofmt +time_step "staticcheck" run_staticcheck +time_step "tests" run_tests + +TOTAL_END=$(date +%s.%N) +TOTAL_ELAPSED=$(echo "$TOTAL_END - $TOTAL_START" | bc) + +# Print timing summary +echo "" +echo "==> All checks passed!" +echo "" +echo "Timing summary:" +echo "---------------" +for i in "${!STEP_NAMES[@]}"; do + printf " %-15s %6.2fs\n" "${STEP_NAMES[$i]}" "${STEP_TIMES[$i]}" +done +echo "---------------" +printf " %-15s %6.2fs\n" "Total" "$TOTAL_ELAPSED" diff --git a/check_gofmt.sh b/check_gofmt.sh new file mode 100755 index 0000000..c77517d --- /dev/null +++ b/check_gofmt.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +diff <(gofmt -d .) <(printf '') \ No newline at end of file diff --git a/codegen/methods.go b/codegen/methods.go new file mode 100644 index 0000000..cfd425d --- /dev/null +++ b/codegen/methods.go @@ -0,0 +1,538 @@ +// 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.Pointer + + 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 m := range t.Methods() { + 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 new file mode 100644 index 0000000..bd36b5e --- /dev/null +++ b/codegen/methods2_test.go @@ -0,0 +1,20 @@ +// 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 new file mode 100644 index 0000000..bce80ec --- /dev/null +++ b/codegen/methods_test.go @@ -0,0 +1,96 @@ +// 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.TypeFor[IEmbed]() + zeroIEOnly = reflect.TypeFor[IEOnly]() + zeroI = reflect.TypeFor[I]() + ) + + 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/commandeer.go b/commands/commandeer.go new file mode 100644 index 0000000..6a9ffc6 --- /dev/null +++ b/commands/commandeer.go @@ -0,0 +1,711 @@ +// 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" + "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/hugo" + "github.com/gohugoio/hugo/common/loggers" + "github.com/gohugoio/hugo/common/paths" + "github.com/gohugoio/hugo/common/types" + "github.com/gohugoio/hugo/config" + "github.com/gohugoio/hugo/config/allconfig" + "github.com/gohugoio/hugo/deps" + "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") + +// Execute executes a command. +func Execute(args []string) error { + // Default GOMAXPROCS to be CPU limit aware, still respecting GOMAXPROCS env. + maxprocs.Set() + x, err := newExec() + if err != nil { + return err + } + args = mapLegacyArgs(args) + cd, err := x.Execute(context.Background(), args) + if cd != nil { + if closer, ok := cd.Root.Command.(types.Closer); ok { + closer.Close() + } + } + + if err != nil { + if err == errHelp { + cd.CobraCommand.Help() + fmt.Println() + return nil + } + if simplecobra.IsCommandError(err) { + // Print the help, but also return the error to fail the command. + cd.CobraCommand.Help() + fmt.Println() + } + } + return err +} + +type commonConfig struct { + mu *sync.Mutex + configs *allconfig.Configs + cfg config.Provider + fs *hugofs.Fs +} + +type configKey struct { + counter int32 + ignoreModulesDoesNotExists bool + skipNpmCheck 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 + panicOnWarning 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 +} + +// resolveEnvironment sets r.environment if not already set. +// server indicates whether the server command is running (defaults to development). +func (r *rootCommand) resolveEnvironment(server bool) { + if r.environment != "" { + return + } + if env := os.Getenv("HUGO_ENVIRONMENT"); env != "" { + r.environment = env + } else if env := os.Getenv("HUGO_ENV"); env != "" { + r.environment = env + } else if server { + r.environment = hugo.EnvironmentDevelopment + } else { + r.environment = hugo.EnvironmentProduction + } +} + +func (r *rootCommand) isVerbose() bool { + return r.logger.Level() <= logg.LevelInfo +} + +func (r *rootCommand) Close() error { + if r.hugoSites != nil { + r.hugoSites.DeleteFunc(func(key configKey, value *hugolib.HugoSites) bool { + if value != nil { + value.Close() + } + return false + }) + } + return nil +} + +func (r *rootCommand) Build(cd *simplecobra.Commandeer, bcfg hugolib.BuildCfg, cfg config.Provider) (*hugolib.HugoSites, error) { + h, err := r.Hugo(cfg) + if err != nil { + 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, + SkipNpmCheck: key.skipNpmCheck, + }, + ) + 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") + } + r.resolveEnvironment(false) + 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, + SkipNpmCheck: key.skipNpmCheck, + }, + ) + 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) { + if conf == nil { + return nil, fmt.Errorf("conf must be set") + } + k := configKey{counter: r.configVersionID.Load()} + h, _, err := r.hugoSites.GetOrCreate(k, func(key configKey) (*hugolib.HugoSites, error) { + depsCfg := r.newDepsConfig(conf) + return hugolib.NewHugoSites(depsCfg) + }) + return h, err +} + +func (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) + + r.Printf("Watching for changes in %s\n", strings.Join(watchGroups, ", ")) + 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) + } + } + } + + var logHookLast func(e *logg.Entry) error + if r.panicOnWarning { + logHookLast = loggers.PanicOnWarningHook + } + + optsLogger := loggers.Options{ + DistinctLevel: logg.LevelWarn, + Level: level, + StdOut: r.StdOut, + StdErr: r.StdErr, + StoreErrors: running, + HandlerPost: logHookLast, + } + + 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 project" + cmd.Long = `COMMAND_NAME is the main command, used to build your Hugo project. + +Hugo is a Fast and Flexible Static Site Generator +built with love by spf13 and friends in Go. + +Complete documentation is available at https://gohugo.io/.` + + cmd.Long = strings.ReplaceAll(cmd.Long, "COMMAND_NAME", commandName) + + // Configure persistent flags + cmd.PersistentFlags().StringVarP(&r.source, "source", "s", "", "filesystem path to read files relative from") + _ = cmd.MarkFlagDirname("source") + cmd.PersistentFlags().StringP("destination", "d", "", "filesystem path to write files to") + _ = cmd.MarkFlagDirname("destination") + + cmd.PersistentFlags().StringVarP(&r.environment, "environment", "e", "", "build environment") + _ = cmd.RegisterFlagCompletionFunc("environment", cobra.NoFileCompletions) + cmd.PersistentFlags().StringP("themesDir", "", "", "filesystem path to themes directory") + _ = cmd.MarkFlagDirname("themesDir") + cmd.PersistentFlags().StringP("ignoreVendorPaths", "", "", "ignores any _vendor for module paths matching the given Glob pattern") + cmd.PersistentFlags().BoolP("noBuildLock", "", false, "don't create .hugo_build.lock file") + _ = cmd.RegisterFlagCompletionFunc("ignoreVendorPaths", cobra.NoFileCompletions) + cmd.PersistentFlags().String("clock", "", "set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00") + _ = cmd.RegisterFlagCompletionFunc("clock", cobra.NoFileCompletions) + + cmd.PersistentFlags().StringVar(&r.cfgFile, "config", "", "config file (default is hugo.yaml|json|toml)") + _ = cmd.MarkFlagFilename("config", config.ValidConfigFileExtensions...) + cmd.PersistentFlags().StringVar(&r.cfgDir, "configDir", "config", "config dir") + _ = cmd.MarkFlagDirname("configDir") + cmd.PersistentFlags().BoolVar(&r.quiet, "quiet", false, "build in quiet mode") + cmd.PersistentFlags().BoolVarP(&r.renderToMemory, "renderToMemory", "M", false, "render to memory (mostly useful when running the server)") + + cmd.PersistentFlags().BoolVarP(&r.devMode, "devMode", "", false, "only used for internal testing, flag hidden.") + cmd.PersistentFlags().StringVar(&r.logLevel, "logLevel", "", "log level (debug|info|warn|error)") + _ = cmd.RegisterFlagCompletionFunc("logLevel", cobra.FixedCompletions([]string{"debug", "info", "warn", "error"}, cobra.ShellCompDirectiveNoFileComp)) + cmd.Flags().BoolVarP(&r.buildWatch, "watch", "w", false, "watch filesystem for changes and recreate as needed") + + cmd.PersistentFlags().MarkHidden("devMode") + + // Configure local flags + applyLocalFlagsBuild(cmd, r) + + 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, "ignore the configured file caches") + cmd.Flags().Bool("enableGitInfo", false, "add Git revision, date, author, and CODEOWNERS info to the pages") + cmd.Flags().StringP("layoutDir", "l", "", "filesystem path to layout directory") + _ = cmd.MarkFlagDirname("layoutDir") + cmd.Flags().BoolVar(&r.gc, "gc", false, "enable to run some cleanup tasks (remove unused cache files) after the build") + cmd.Flags().StringVar(&r.poll, "poll", "", "set this to a poll interval, e.g --poll 700ms, to use a poll based approach to watch for file system changes") + _ = cmd.RegisterFlagCompletionFunc("poll", cobra.NoFileCompletions) + cmd.Flags().BoolVar(&r.panicOnWarning, "panicOnWarning", false, "panic on first WARNING log") + cmd.Flags().Bool("templateMetrics", false, "display metrics about template executions") + cmd.Flags().Bool("templateMetricsHints", false, "calculate some improvement hints when combined with --templateMetrics") + cmd.Flags().BoolVar(&r.forceSyncStatic, "forceSyncStatic", false, "copy all files when static is changed.") + cmd.Flags().BoolP("noTimes", "", false, "don't sync modification time of files") + cmd.Flags().BoolP("noChmod", "", false, "don't sync permission mode of files") + cmd.Flags().BoolP("printI18nWarnings", "", false, "print missing translations") + cmd.Flags().BoolP("printPathWarnings", "", false, "print warnings on duplicate target paths etc.") + cmd.Flags().BoolP("printUnusedTemplates", "", false, "print warnings on unused templates.") + cmd.Flags().StringVarP(&r.cpuprofile, "profile-cpu", "", "", "write cpu profile to `file`") + cmd.Flags().StringVarP(&r.memprofile, "profile-mem", "", "", "write memory profile to `file`") + cmd.Flags().BoolVarP(&r.printm, "printMemoryUsage", "", false, "print memory usage to screen at intervals") + cmd.Flags().StringVarP(&r.mutexprofile, "profile-mutex", "", "", "write Mutex profile to `file`") + cmd.Flags().StringVarP(&r.traceprofile, "trace", "", "", "write trace to `file` (not useful in general)") + + // Hide these for now. + cmd.Flags().MarkHidden("profile-cpu") + cmd.Flags().MarkHidden("profile-mem") + cmd.Flags().MarkHidden("profile-mutex") + + cmd.Flags().StringSlice("disableKinds", []string{}, "disable different kind of pages (home, RSS etc.)") + _ = cmd.RegisterFlagCompletionFunc("disableKinds", cobra.FixedCompletions(kinds.AllKinds, cobra.ShellCompDirectiveNoFileComp)) + cmd.Flags().Bool("minify", false, "minify any supported output format (HTML, XML etc.)") +} + +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 + aliases []string + run func(ctx context.Context, cd *simplecobra.Commandeer, rootCmd *rootCommand, args []string) error + withc func(cmd *cobra.Command, r *rootCommand) + initc func(cd *simplecobra.Commandeer) error + + 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 + cmd.Aliases = c.aliases + 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], "project", "site", "theme", "content") { + // Insert "content" as the second argument + args = append(args[:1], append([]string{"content"}, args[1:]...)...) + } + return args +} diff --git a/commands/commands.go b/commands/commands.go new file mode 100644 index 0000000..10ab106 --- /dev/null +++ b/commands/commands.go @@ -0,0 +1,73 @@ +// Copyright 2024 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package 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 new file mode 100644 index 0000000..59433b3 --- /dev/null +++ b/commands/config.go @@ -0,0 +1,240 @@ +// Copyright 2024 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package commands + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "os" + "strings" + "time" + + "github.com/bep/simplecobra" + "github.com/gohugoio/hugo/common/hmaps" + "github.com/gohugoio/hugo/config/allconfig" + "github.com/gohugoio/hugo/hugolib/sitesmatrix" + "github.com/gohugoio/hugo/modules" + "github.com/gohugoio/hugo/parser" + "github.com/gohugoio/hugo/parser/metadecoders" + "github.com/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.LanguageConfigMap[conf.configs.Base.DefaultContentLanguage] + } + + 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 + } + hmaps.ConvertFloat64WithNoDecimalsToInt(m) + switch format { + case "yaml": + return parser.InterfaceToConfig(m, metadecoders.YAML, os.Stdout) + case "toml": + return parser.InterfaceToConfig(m, metadecoders.TOML, os.Stdout) + default: + return fmt.Errorf("unsupported format: %q", format) + } + } + + return nil +} + +func (c *configCommand) Init(cd *simplecobra.Commandeer) error { + c.r = cd.Root.Command.(*rootCommand) + cmd := cd.CobraCommand + cmd.Short = "Display project configuration" + cmd.Long = `Display project configuration, both default and custom settings.` + cmd.Flags().StringVar(&c.format, "format", "toml", "preferred file format (toml, yaml or json)") + _ = cmd.RegisterFlagCompletionFunc("format", cobra.FixedCompletions([]string{"toml", "yaml", "json"}, cobra.ShellCompDirectiveNoFileComp)) + cmd.Flags().StringVar(&c.lang, "lang", "", "the language to display config for. Defaults to the first language defined.") + cmd.Flags().BoolVar(&c.printZero, "printZero", false, `include config options with zero values (e.g. false, 0, "") in the output`) + _ = cmd.RegisterFlagCompletionFunc("lang", cobra.NoFileCompletions) + applyLocalFlagsBuildConfig(cmd, c.r) + + return nil +} + +func (c *configCommand) PreRun(cd, runner *simplecobra.Commandeer) error { + return nil +} + +type configModMount struct { + Source string `json:"source"` + Target string `json:"target"` + Sites sitesmatrix.Sites `json:"sites,omitzero"` +} + +type configModMounts struct { + verbose bool + m modules.Module +} + +// MarshalJSON is for internal use only. +func (m *configModMounts) MarshalJSON() ([]byte, error) { + var mounts []configModMount + + for _, mount := range m.m.Mounts() { + mounts = append(mounts, configModMount{ + Source: mount.Source, + Target: mount.Target, + Sites: mount.Sites, + }) + } + + 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 new file mode 100644 index 0000000..afc6fc7 --- /dev/null +++ b/commands/convert.go @@ -0,0 +1,315 @@ +// 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" + "fmt" + "path/filepath" + "slices" + "strings" + "time" + + "github.com/bep/simplecobra" + "github.com/gohugoio/hugo/common/hugio" + "github.com/gohugoio/hugo/config" + "github.com/gohugoio/hugo/helpers" + "github.com/gohugoio/hugo/hugofs" + "github.com/gohugoio/hugo/hugolib" + "github.com/gohugoio/hugo/parser" + "github.com/gohugoio/hugo/parser/metadecoders" + "github.com/gohugoio/hugo/parser/pageparser" + "github.com/gohugoio/hugo/resources/page" + "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 +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 +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 +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 +} + +type convertCommand struct { + // Flags. + outputDir string + unsafe bool + + // Deps. + r *rootCommand + h *hugolib.HugoSites + + // Commands. + commands []simplecobra.Commander +} + +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)) + 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 { + return err + } + } + + if p.File() == nil { + // No content file. + return nil + } + + errMsg := fmt.Errorf("error processing file %q", p.File().Path()) + + site.Log.Infoln("attempting to convert", p.File().Filename()) + + 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) + } + } + } + + 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) copyContentDirsForOutput(pagesBackedByFile page.Pages) error { + contentDirs := make(map[string]bool) + for _, p := range pagesBackedByFile { + filename := p.File().Filename() + contentDir := strings.TrimSuffix(filename, p.File().Path()) + if contentDir == filename { + continue + } + contentDirs[filepath.Clean(contentDir)] = true + } + + var contentDirList []string + for contentDir := range contentDirs { + contentDirList = append(contentDirList, contentDir) + } + slices.Sort(contentDirList) + + outputDirAbs, err := filepath.Abs(c.outputDir) + if err != nil { + return fmt.Errorf("failed to resolve output path %q: %w", c.outputDir, err) + } + + for _, contentDir := range contentDirList { + outputContentDirAbs := filepath.Join(outputDirAbs, filepath.Base(contentDir)) + + skipDirs := make(map[string]bool) + relToOutputDir, err := filepath.Rel(contentDir, outputDirAbs) + if err == nil && relToOutputDir != ".." && !strings.HasPrefix(relToOutputDir, ".."+string(filepath.Separator)) { + skipDirs[filepath.Clean(outputDirAbs)] = true + } + relToOutputContentDir, err := filepath.Rel(contentDir, outputContentDirAbs) + if err == nil && relToOutputContentDir != ".." && !strings.HasPrefix(relToOutputContentDir, ".."+string(filepath.Separator)) { + skipDirs[filepath.Clean(outputContentDirAbs)] = true + } + + var shouldCopy func(filename string) bool + if len(skipDirs) > 0 { + shouldCopy = func(filename string) bool { + return !skipDirs[filepath.Clean(filename)] + } + } + + if err := hugio.CopyDir(hugofs.Os, contentDir, outputContentDirAbs, shouldCopy); err != nil { + return fmt.Errorf("failed to copy %q to %q: %w", contentDir, outputContentDirAbs, err) + } + } + + return nil +} + +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] + + workingDir := c.h.Sites[0].Deps.Conf.WorkingDir() + string(filepath.Separator) + + isConvertible := func(p page.Page) bool { + // Skip pages not backed by a content file. + if p.File() == nil { + return false + } + // Skip content adapters. + if p.File().IsContentAdapter() { + return false + } + // Skip content files provided by modules, including vendored modules. + if !p.File().FileInfo().Meta().IsProject { + return false + } + // Skip content files in project mounts outside the working directory. + if !strings.HasPrefix(p.File().Filename(), workingDir) { + return false + } + return true + } + + seen := make(map[string]bool) + var pagesBackedByFile page.Pages + for _, p := range c.h.Pages() { + if !isConvertible(p) { + continue + } + filename := p.File().Filename() + if seen[filename] { + continue + } + seen[filename] = true + pagesBackedByFile = append(pagesBackedByFile, p) + } + + if c.outputDir != "" { + if err := c.copyContentDirsForOutput(pagesBackedByFile); err != nil { + return err + } + } + + site.Log.Println("processing", len(pagesBackedByFile), "content files") + for _, p := range pagesBackedByFile { + if err := c.convertAndSavePage(p, site, format); err != nil { + return err + } + } + return nil +} diff --git a/commands/deploy.go b/commands/deploy.go new file mode 100644 index 0000000..2f32bd8 --- /dev/null +++ b/commands/deploy.go @@ -0,0 +1,51 @@ +// 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 project to a cloud provider", + long: `Deploy your project to a cloud provider + +See https://gohugo.io/hosting-and-deployment/hugo-deploy/ for detailed +documentation. +`, + 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 new file mode 100644 index 0000000..d432654 --- /dev/null +++ b/commands/deploy_flags.go @@ -0,0 +1,33 @@ +// Copyright 2024 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package commands + +import ( + "github.com/gohugoio/hugo/deploy/deployconfig" + "github.com/spf13/cobra" +) + +func applyDeployFlags(cmd *cobra.Command, r *rootCommand) { + cmd.ValidArgsFunction = cobra.NoFileCompletions + cmd.Flags().String("target", "", "target deployment from deployments section in config file; defaults to the first one") + _ = cmd.RegisterFlagCompletionFunc("target", cobra.NoFileCompletions) + cmd.Flags().Bool("confirm", false, "ask for confirmation before making changes to the target") + cmd.Flags().Bool("dryRun", false, "dry run") + cmd.Flags().Bool("force", false, "force upload of all files") + cmd.Flags().Bool("invalidateCDN", deployconfig.DefaultConfig.InvalidateCDN, "invalidate the CDN cache listed in the deployment target") + cmd.Flags().Int("maxDeletes", deployconfig.DefaultConfig.MaxDeletes, "maximum # of files to delete, or -1 to disable") + _ = cmd.RegisterFlagCompletionFunc("maxDeletes", cobra.NoFileCompletions) + cmd.Flags().Int("workers", deployconfig.DefaultConfig.Workers, "number of workers to transfer files. defaults to 10") + _ = cmd.RegisterFlagCompletionFunc("workers", cobra.NoFileCompletions) +} diff --git a/commands/deploy_off.go b/commands/deploy_off.go new file mode 100644 index 0000000..8f5eaa2 --- /dev/null +++ b/commands/deploy_off.go @@ -0,0 +1,50 @@ +// Copyright 2024 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build !withdeploy + +// Copyright 2024 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package commands + +import ( + "context" + "errors" + + "github.com/bep/simplecobra" + "github.com/spf13/cobra" +) + +func newDeployCommand() simplecobra.Commander { + return &simpleCommand{ + name: "deploy", + run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error { + return errors.New("deploy not supported in this version of Hugo; install a release with 'withdeploy' in the archive filename or build yourself with the 'withdeploy' build tag. Also see https://github.com/gohugoio/hugo/pull/12995") + }, + withc: func(cmd *cobra.Command, r *rootCommand) { + applyDeployFlags(cmd, r) + cmd.Hidden = true + }, + } +} diff --git a/commands/env.go b/commands/env.go new file mode 100644 index 0000000..7535225 --- /dev/null +++ b/commands/env.go @@ -0,0 +1,70 @@ +// 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" + "runtime" + + "github.com/bep/simplecobra" + "github.com/gohugoio/hugo/common/hugo" + "github.com/spf13/cobra" +) + +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()) + + 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 + }, + } +} diff --git a/commands/gen.go b/commands/gen.go new file mode 100644 index 0000000..8d9224a --- /dev/null +++ b/commands/gen.go @@ -0,0 +1,395 @@ +// 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" + "path" + "path/filepath" + "slices" + "strings" + + "github.com/alecthomas/chroma/v2" + "github.com/alecthomas/chroma/v2/formatters/html" + "github.com/alecthomas/chroma/v2/styles" + "github.com/bep/simplecobra" + "github.com/goccy/go-yaml" + "github.com/gohugoio/hugo/common/hugo" + "github.com/gohugoio/hugo/docshelper" + "github.com/gohugoio/hugo/helpers" + "github.com/gohugoio/hugo/hugofs" + "github.com/gohugoio/hugo/hugolib" + "github.com/gohugoio/hugo/parser" + "github.com/spf13/cobra" + "github.com/spf13/cobra/doc" +) + +func newGenCommand() *genCommand { + var ( + // Flags. + gendocdir string + genmandir string + + // Chroma flags. + style string + mode string + modeSelector bool + highlightStyle string + lineNumbersInlineStyle string + lineNumbersTableStyle string + omitEmpty bool + omitClassComments bool + ) + + newChromaStyles := func() simplecobra.Commander { + return &simpleCommand{ + name: "chromastyles", + short: "Generate CSS stylesheet for the Chroma code highlighter", + long: `Generate CSS stylesheet for the Chroma code highlighter for a given style. This stylesheet is needed if markup.highlight.noClasses is disabled in config. + +See https://gohugo.io/quick-reference/syntax-highlighting-styles/ for a preview of the available styles.`, + + run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error { + style = strings.ToLower(style) + if !slices.Contains(styles.Names(), style) { + return fmt.Errorf("invalid style: %s", style) + } + var chromaStyle *chroma.Style + if mode != "" { + var chromaMode chroma.Mode + switch mode { + case "light": + chromaMode = chroma.Light + case "dark": + chromaMode = chroma.Dark + default: + return fmt.Errorf("invalid mode: %s", mode) + } + + chromaStyle = styles.GetForMode(style, chromaMode) + if chromaStyle.Mode() != chromaMode { + return fmt.Errorf("style %q does not have a %q mode", style, mode) + } + } else { + chromaStyle = styles.Get(style) + } + builder := chromaStyle.Builder() + if highlightStyle != "" { + builder.Add(chroma.LineHighlight, highlightStyle) + } + if lineNumbersInlineStyle != "" { + builder.Add(chroma.LineNumbers, lineNumbersInlineStyle) + } + if lineNumbersTableStyle != "" { + builder.Add(chroma.LineNumbersTable, lineNumbersTableStyle) + } + style, err := builder.Build() + if err != nil { + return err + } + + if omitEmpty { + // See https://github.com/alecthomas/chroma/commit/5b2a4c5a26c503c79bc86ba3c4ae5b330028bd3d + hugo.Deprecate("--omitEmpty", "Flag is no longer needed, empty classes are now always omitted.", "v0.149.0") + } + options := []html.Option{ + html.WithCSSComments(!omitClassComments), + } + if !modeSelector { + // Only needed for the shared-scope overlay; --modeSelector + // gives each mode its own scope and makes this redundant. + options = append(options, html.WithCustomCSS(chromaCSSOverrides(style))) + } + + formatter := html.New(options...) + + var buf bytes.Buffer + fmt.Fprintf(&buf, "/* Generated using: hugo %s */\n\n", strings.Join(os.Args[1:], " ")) + formatter.WriteCSS(&buf, style) + css := buf.String() + if modeSelector { + // Scope every selector under a top level mode class, e.g. ".dark .chroma". + // This allows generating both light and dark stylesheets and toggling + // them with a parent class on the page. + // There's no upstream option for this (I think), so do string replacements for now. + // TODO(bep) upstream option for this. + var prefix string + switch style.Mode() { + case chroma.Light: + prefix = ".light " + case chroma.Dark: + prefix = ".dark " + default: + return fmt.Errorf("style %q does not have a %q mode", style.Name, mode) + } + replacer := strings.NewReplacer( + ".bg {", prefix+".bg {", + ".chroma ", prefix+".chroma ", + ) + css = replacer.Replace(css) + } + + fmt.Print(css) + return nil + }, + withc: func(cmd *cobra.Command, r *rootCommand) { + cmd.ValidArgsFunction = cobra.NoFileCompletions + cmd.PersistentFlags().StringVar(&style, "style", "friendly", "highlighter style") + _ = cmd.RegisterFlagCompletionFunc("style", cobra.NoFileCompletions) + cmd.PersistentFlags().StringVar(&mode, "mode", "", `style mode ("light", "dark")`) + _ = cmd.RegisterFlagCompletionFunc("mode", cobra.FixedCompletions([]string{"light", "dark"}, cobra.ShellCompDirectiveNoFileComp)) + cmd.PersistentFlags().BoolVar(&modeSelector, "modeSelector", false, `scope selectors under a top level mode class, e.g. ".dark .chroma"`) + _ = cmd.RegisterFlagCompletionFunc("modeSelector", cobra.NoFileCompletions) + cmd.PersistentFlags().StringVar(&highlightStyle, "highlightStyle", "", `foreground and background colors for highlighted lines, e.g. --highlightStyle "#fff000 bg:#000fff"`) + _ = cmd.RegisterFlagCompletionFunc("highlightStyle", cobra.NoFileCompletions) + cmd.PersistentFlags().StringVar(&lineNumbersInlineStyle, "lineNumbersInlineStyle", "", `foreground and background colors for inline line numbers, e.g. --lineNumbersInlineStyle "#fff000 bg:#000fff"`) + _ = cmd.RegisterFlagCompletionFunc("lineNumbersInlineStyle", cobra.NoFileCompletions) + cmd.PersistentFlags().StringVar(&lineNumbersTableStyle, "lineNumbersTableStyle", "", `foreground and background colors for table line numbers, e.g. --lineNumbersTableStyle "#fff000 bg:#000fff"`) + _ = cmd.RegisterFlagCompletionFunc("lineNumbersTableStyle", cobra.NoFileCompletions) + cmd.PersistentFlags().BoolVar(&omitEmpty, "omitEmpty", false, `omit empty CSS rules (deprecated, no longer needed)`) + _ = cmd.RegisterFlagCompletionFunc("omitEmpty", cobra.NoFileCompletions) + cmd.PersistentFlags().BoolVar(&omitClassComments, "omitClassComments", false, `omit CSS class comment prefixes in the generated CSS`) + _ = cmd.RegisterFlagCompletionFunc("omitClassComments", cobra.NoFileCompletions) + }, + } + } + + newMan := func() simplecobra.Commander { + return &simpleCommand{ + name: "man", + short: "Generate man pages for the Hugo CLI", + long: `This command automatically generates up-to-date man pages of Hugo's + command-line interface. By default, it creates the man page files + in the "man" directory under the current directory.`, + + run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error { + header := &doc.GenManHeader{ + Section: "1", + Manual: "Hugo Manual", + Source: fmt.Sprintf("Hugo %s", hugo.CurrentVersion), + } + if !strings.HasSuffix(genmandir, helpers.FilePathSeparator) { + genmandir += helpers.FilePathSeparator + } + if found, _ := helpers.Exists(genmandir, hugofs.Os); !found { + r.Println("Directory", genmandir, "does not exist, creating...") + if err := hugofs.Os.MkdirAll(genmandir, 0o777); err != nil { + return err + } + } + cd.CobraCommand.Root().DisableAutoGenTag = true + + r.Println("Generating Hugo man pages in", genmandir, "...") + doc.GenManTree(cd.CobraCommand.Root(), header, genmandir) + + r.Println("Done.") + + return nil + }, + withc: func(cmd *cobra.Command, r *rootCommand) { + cmd.ValidArgsFunction = cobra.NoFileCompletions + cmd.PersistentFlags().StringVar(&genmandir, "dir", "man/", "the directory to write the man pages.") + _ = cmd.MarkFlagDirname("dir") + }, + } + } + + newGen := func() simplecobra.Commander { + const gendocFrontmatterTemplate = `--- +title: "%s" +slug: %s +url: %s +--- +` + + return &simpleCommand{ + name: "doc", + short: "Generate Markdown documentation for the Hugo CLI", + long: `Generate Markdown documentation for the Hugo CLI. + This command is, mostly, used to create up-to-date documentation + of Hugo's command-line interface for https://gohugo.io/. + + It creates one Markdown file per command with front matter suitable + for rendering in Hugo.`, + run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error { + cd.CobraCommand.VisitParents(func(c *cobra.Command) { + // Disable the "Auto generated by spf13/cobra on DATE" + // as it creates a lot of diffs. + c.DisableAutoGenTag = true + }) + if !strings.HasSuffix(gendocdir, helpers.FilePathSeparator) { + gendocdir += helpers.FilePathSeparator + } + if found, _ := helpers.Exists(gendocdir, hugofs.Os); !found { + r.Println("Directory", gendocdir, "does not exist, creating...") + if err := hugofs.Os.MkdirAll(gendocdir, 0o777); err != nil { + return err + } + } + prepender := func(filename string) string { + name := filepath.Base(filename) + base := strings.TrimSuffix(name, path.Ext(name)) + url := "/docs/reference/commands/" + strings.ToLower(base) + "/" + return fmt.Sprintf(gendocFrontmatterTemplate, strings.Replace(base, "_", " ", -1), base, url) + } + + linkHandler := func(name string) string { + base := strings.TrimSuffix(name, path.Ext(name)) + return "/docs/reference/commands/" + strings.ToLower(base) + "/" + } + r.Println("Generating Hugo command-line documentation in", gendocdir, "...") + doc.GenMarkdownTreeCustom(cd.CobraCommand.Root(), gendocdir, prepender, linkHandler) + r.Println("Done.") + + return nil + }, + withc: func(cmd *cobra.Command, r *rootCommand) { + cmd.ValidArgsFunction = cobra.NoFileCompletions + cmd.PersistentFlags().StringVar(&gendocdir, "dir", "/tmp/hugodoc/", "the directory to write the doc.") + _ = cmd.MarkFlagDirname("dir") + }, + } + } + + var docsHelperTarget string + + newDocsHelper := func() simplecobra.Commander { + return &simpleCommand{ + name: "docshelper", + short: "Generate some data files for the Hugo docs", + + run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error { + r.Println("Generate docs data to", docsHelperTarget) + + var buf bytes.Buffer + jsonEnc := json.NewEncoder(&buf) + + configProvider := func() docshelper.DocProvider { + conf := hugolib.DefaultConfig() + conf.CacheDir = "" // The default value does not make sense in the docs. + defaultConfig := parser.NullBoolJSONMarshaller{Wrapped: parser.LowerCaseCamelJSONMarshaller{Value: conf}} + return docshelper.DocProvider{"config": defaultConfig} + } + + docshelper.AddDocProviderFunc(configProvider) + if err := jsonEnc.Encode(docshelper.GetDocProvider()); err != nil { + return err + } + + // Decode the JSON to a map[string]interface{} and then unmarshal it again to the correct format. + var m map[string]any + if err := json.Unmarshal(buf.Bytes(), &m); err != nil { + return err + } + + targetFile := filepath.Join(docsHelperTarget, "docs.yaml") + + f, err := os.Create(targetFile) + if err != nil { + return err + } + defer f.Close() + yamlEnc := yaml.NewEncoder(f, yaml.UseSingleQuote(true), yaml.AutoInt()) + if err := yamlEnc.Encode(m); err != nil { + return err + } + + r.Println("Done!") + return nil + }, + withc: func(cmd *cobra.Command, r *rootCommand) { + cmd.Hidden = true + cmd.ValidArgsFunction = cobra.NoFileCompletions + cmd.PersistentFlags().StringVarP(&docsHelperTarget, "dir", "", "docs/data", "data dir") + }, + } + } + + return &genCommand{ + commands: []simplecobra.Commander{ + newChromaStyles(), + newGen(), + newMan(), + newDocsHelper(), + }, + } +} + +// chromaCSSOverrides re-emits leaf token colors that Chroma's minifier drops +// because they equal the style's default foreground (the .chroma color). Without +// modeSelector scoping, a paired light/dark stylesheet shares the same .chroma +// scope, and the light sheet's explicit rule (e.g. .chroma .nx) would otherwise +// leak into dark mode, since an explicit declaration beats inheritance. +func chromaCSSOverrides(style *chroma.Style) map[chroma.TokenType]string { + bg := style.Get(chroma.Background) + m := make(map[chroma.TokenType]string) + for tt := range chroma.StandardTypes { + if tt == chroma.Background || !style.Has(tt) || !chromaLeafToken(tt) { + continue + } + entry := style.Get(tt) + if !entry.Sub(bg).IsZero() || !entry.Colour.IsSet() { + continue + } + if css := html.StyleEntryToCSS(chroma.StyleEntry{Colour: entry.Colour}); css != "" { + m[tt] = css + } + } + return m +} + +func chromaLeafToken(tt chroma.TokenType) bool { + for other := range chroma.StandardTypes { + if other != tt && (other.Category() == tt || other.SubCategory() == tt) { + return false + } + } + return true +} + +type genCommand struct { + rootCmd *rootCommand + + 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 +} diff --git a/commands/helpers.go b/commands/helpers.go new file mode 100644 index 0000000..fbe1d36 --- /dev/null +++ b/commands/helpers.go @@ -0,0 +1,121 @@ +// 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": "minify.minifyOutput", + "destination": "publishDir", + "editor": "newContentEditor", + } + + // Flags that we for some reason don't want to expose in the project config. + internalKeySet := map[string]bool{ + "quiet": true, + "verbose": true, + "watch": true, + "liveReloadPort": true, + "renderToMemory": true, + "clock": true, + } + + 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_windows.go b/commands/hugo_windows.go new file mode 100644 index 0000000..c354e88 --- /dev/null +++ b/commands/hugo_windows.go @@ -0,0 +1,33 @@ +// Copyright 2024 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package commands + +import ( + // For time zone lookups on Windows without Go installed. + // See #8892 + _ "time/tzdata" + + "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. + + You need to open PowerShell and run Hugo from there. + + Visit https://gohugo.io/ for more information.` +} diff --git a/commands/hugobuilder.go b/commands/hugobuilder.go new file mode 100644 index 0000000..2ffe3fa --- /dev/null +++ b/commands/hugobuilder.go @@ -0,0 +1,1197 @@ +// Copyright 2024 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package commands + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "runtime" + "runtime/pprof" + "runtime/trace" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/bep/debounce" + "github.com/bep/simplecobra" + "github.com/fsnotify/fsnotify" + "github.com/gohugoio/hugo/common/herrors" + "github.com/gohugoio/hugo/common/hmaps" + "github.com/gohugoio/hugo/common/hstrings" + "github.com/gohugoio/hugo/common/htime" + "github.com/gohugoio/hugo/common/hugo" + "github.com/gohugoio/hugo/common/loggers" + "github.com/gohugoio/hugo/common/paths" + "github.com/gohugoio/hugo/common/terminal" + "github.com/gohugoio/hugo/common/types" + "github.com/gohugoio/hugo/config" + "github.com/gohugoio/hugo/helpers" + "github.com/gohugoio/hugo/hugofs" + "github.com/gohugoio/hugo/hugolib" + "github.com/gohugoio/hugo/hugolib/filesystems" + "github.com/gohugoio/hugo/identity" + "github.com/gohugoio/hugo/livereload" + "github.com/gohugoio/hugo/resources/page" + "github.com/gohugoio/hugo/watcher" + "github.com/spf13/fsync" + "golang.org/x/sync/errgroup" + "golang.org/x/sync/semaphore" +) + +type hugoBuilder struct { + r *rootCommand + + confmu sync.Mutex + confOld *commonConfig + conf *commonConfig + + // May be nil. + s *serverCommand + + // Currently only set when in "fast render mode". + changeDetector *fileChangeDetector + visitedURLs *types.EvictingQueue[string] + + fullRebuildSem *semaphore.Weighted + debounce func(f func()) + + onConfigLoaded func(reloaded bool) error + + fastRenderMode bool + showErrorInBrowser bool + + errState hugoBuilderErrState +} + +var errConfigNotSet = errors.New("config not set") + +func (c *hugoBuilder) withConfE(fn func(conf *commonConfig) error) error { + c.confmu.Lock() + defer c.confmu.Unlock() + if c.conf == nil { + return errConfigNotSet + } + return fn(c.conf) +} + +func (c *hugoBuilder) withConf(fn func(conf *commonConfig)) { + c.confmu.Lock() + defer c.confmu.Unlock() + fn(c.conf) +} + +func (c *hugoBuilder) withConfOrOldConf(fn func(conf *commonConfig)) { + c.confmu.Lock() + defer c.confmu.Unlock() + if c.conf != nil { + fn(c.conf) + } else if c.confOld != nil { + fn(c.confOld) + } +} + +func (c *hugoBuilder) withConfOrOldConfE(fn func(conf *commonConfig) error) error { + c.confmu.Lock() + defer c.confmu.Unlock() + if c.conf != nil { + return fn(c.conf) + } else if c.confOld != nil { + return fn(c.confOld) + } + return errConfigNotSet +} + +type hugoBuilderErrState struct { + mu sync.Mutex + paused bool + builderr error + waserr bool +} + +func (e *hugoBuilderErrState) setPaused(p bool) { + e.mu.Lock() + defer e.mu.Unlock() + e.paused = p +} + +func (e *hugoBuilderErrState) isPaused() bool { + e.mu.Lock() + defer e.mu.Unlock() + return e.paused +} + +func (e *hugoBuilderErrState) setBuildErr(err error) { + e.mu.Lock() + defer e.mu.Unlock() + e.builderr = err +} + +func (e *hugoBuilderErrState) buildErr() error { + e.mu.Lock() + defer e.mu.Unlock() + return e.builderr +} + +func (e *hugoBuilderErrState) setWasErr(w bool) { + e.mu.Lock() + defer e.mu.Unlock() + e.waserr = w +} + +func (e *hugoBuilderErrState) wasErr() bool { + e.mu.Lock() + defer e.mu.Unlock() + return e.waserr +} + +// getDirList provides NewWatcher() with a list of directories to watch for changes. +func (c *hugoBuilder) getDirList() ([]string, error) { + h, err := c.hugo() + if err != nil { + return nil, err + } + + return hstrings.UniqueStringsSorted(h.PathSpec.BaseFs.WatchFilenames()), nil +} + +func (c *hugoBuilder) initCPUProfile() (func(), error) { + if c.r.cpuprofile == "" { + return nil, nil + } + + f, err := os.Create(c.r.cpuprofile) + if err != nil { + return nil, fmt.Errorf("failed to create CPU profile: %w", err) + } + if err := pprof.StartCPUProfile(f); err != nil { + f.Close() + return nil, fmt.Errorf("failed to start CPU profile: %w", err) + } + return func() { + pprof.StopCPUProfile() + f.Close() + }, nil +} + +func (c *hugoBuilder) initMemProfile() { + if c.r.memprofile == "" { + return + } + + f, err := os.Create(c.r.memprofile) + if err != nil { + c.r.logger.Errorf("could not create memory profile: ", err) + } + defer f.Close() + runtime.GC() // get up-to-date statistics + if err := pprof.WriteHeapProfile(f); err != nil { + c.r.logger.Errorf("could not write memory profile: ", err) + } +} + +func (c *hugoBuilder) initMemTicker() func() { + memticker := time.NewTicker(5 * time.Second) + quit := make(chan struct{}) + printMem := func() { + var m runtime.MemStats + runtime.ReadMemStats(&m) + fmt.Printf("\n\nAlloc = %v\nTotalAlloc = %v\nSys = %v\nNumGC = %v\n\n", formatByteCount(m.Alloc), formatByteCount(m.TotalAlloc), formatByteCount(m.Sys), m.NumGC) + } + + go func() { + for { + select { + case <-memticker.C: + printMem() + case <-quit: + memticker.Stop() + printMem() + return + } + } + }() + + return func() { + close(quit) + } +} + +func (c *hugoBuilder) initMutexProfile() (func(), error) { + if c.r.mutexprofile == "" { + return nil, nil + } + + f, err := os.Create(c.r.mutexprofile) + if err != nil { + return nil, err + } + + runtime.SetMutexProfileFraction(1) + + return func() { + pprof.Lookup("mutex").WriteTo(f, 0) + f.Close() + }, nil +} + +func (c *hugoBuilder) initProfiling() (func(), error) { + stopCPUProf, err := c.initCPUProfile() + if err != nil { + return nil, err + } + + stopMutexProf, err := c.initMutexProfile() + if err != nil { + return nil, err + } + + stopTraceProf, err := c.initTraceProfile() + if err != nil { + return nil, err + } + + var stopMemTicker func() + if c.r.printm { + stopMemTicker = c.initMemTicker() + } + + return func() { + c.initMemProfile() + + if stopCPUProf != nil { + stopCPUProf() + } + if stopMutexProf != nil { + stopMutexProf() + } + + if stopTraceProf != nil { + stopTraceProf() + } + + if stopMemTicker != nil { + stopMemTicker() + } + }, nil +} + +func (c *hugoBuilder) initTraceProfile() (func(), error) { + if c.r.traceprofile == "" { + return nil, nil + } + + f, err := os.Create(c.r.traceprofile) + if err != nil { + return nil, fmt.Errorf("failed to create trace file: %w", err) + } + + if err := trace.Start(f); err != nil { + return nil, fmt.Errorf("failed to start trace: %w", err) + } + + return func() { + trace.Stop() + f.Close() + }, nil +} + +// newWatcher creates a new watcher to watch filesystem events. +func (c *hugoBuilder) newWatcher(pollIntervalStr string, dirList ...string) (*watcher.Batcher, error) { + staticSyncer := &staticSyncer{c: c} + + var pollInterval time.Duration + poll := pollIntervalStr != "" + if poll { + pollInterval, err := types.ToDurationE(pollIntervalStr) + if err != nil { + return nil, fmt.Errorf("invalid value for flag poll: %s", err) + } + c.r.logger.Printf("Use watcher with poll interval %v", pollInterval) + } + + if pollInterval == 0 { + pollInterval = 500 * time.Millisecond + } + + watcher, err := watcher.New(500*time.Millisecond, pollInterval, poll) + if err != nil { + return nil, err + } + + h, err := c.hugo() + if err != nil { + return nil, err + } + spec := h.Deps.SourceSpec + + for _, d := range dirList { + if d != "" { + if spec.IgnoreFile(d) { + continue + } + _ = watcher.Add(d) + } + } + + // Identifies changes to config (config.toml) files. + configSet := make(map[string]bool) + var configFiles []string + c.withConf(func(conf *commonConfig) { + configFiles = conf.configs.LoadingInfo.ConfigFiles + }) + + c.r.Println("Watching for config changes in", strings.Join(configFiles, ", ")) + for _, configFile := range configFiles { + watcher.Add(configFile) + configSet[configFile] = true + } + + go func() { + for { + select { + case changes := <-c.r.changesFromBuild: + unlock, err := h.LockBuild() + if err != nil { + c.r.logger.Errorf("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.Errorf("Failed to acquire a build lock: %s", err) + return + } + c.handleEvents(watcher, staticSyncer, evs, configSet) + if c.showErrorInBrowser && c.errState.buildErr() != nil { + // Need to reload browser to show the error + livereload.ForceRefresh() + } + unlock() + case err := <-watcher.Errors(): + if err != nil && !herrors.IsNotExist(err) { + c.r.logger.Errorln("Error while watching:", err) + } + } + } + }() + + return watcher, nil +} + +func (c *hugoBuilder) build() error { + stopProfiling, err := c.initProfiling() + if err != nil { + return err + } + + defer func() { + if stopProfiling != nil { + stopProfiling() + } + }() + + if err := c.fullBuild(false); err != nil { + return err + } + + if !c.r.quiet { + c.r.Println() + h, err := c.hugo() + if err != nil { + return err + } + + h.PrintProcessingStats(os.Stdout) + c.r.Println() + } + + return nil +} + +func (c *hugoBuilder) buildSites(noBuildLock bool) (err error) { + defer func() { + c.errState.setBuildErr(err) + }() + + var h *hugolib.HugoSites + h, err = c.hugo() + if err != nil { + return + } + err = h.Build(hugolib.BuildCfg{NoBuildLock: noBuildLock}) + return +} + +func (c *hugoBuilder) copyStatic() (map[string]uint64, error) { + m, err := c.doWithPublishDirs(c.copyStaticTo) + if err == nil || herrors.IsNotExist(err) { + return m, nil + } + return m, err +} + +func (c *hugoBuilder) copyStaticTo(sourceFs *filesystems.SourceFilesystem) (uint64, error) { + infol := c.r.logger.InfoCommand("static") + publishDir := helpers.FilePathSeparator + + if sourceFs.PublishFolder != "" { + publishDir = filepath.Join(publishDir, sourceFs.PublishFolder) + } + + fs := &countingStatFs{Fs: sourceFs.Fs} + + syncer := fsync.NewSyncer() + c.withConf(func(conf *commonConfig) { + syncer.NoTimes = conf.configs.Base.NoTimes + syncer.NoChmod = conf.configs.Base.NoChmod + syncer.ChmodFilter = chmodFilter + + syncer.DestFs = conf.fs.PublishDirStatic + // Now that we are using a unionFs for the static directories + // We can effectively clean the publishDir on initial sync + syncer.Delete = conf.configs.Base.CleanDestinationDir + }) + + syncer.SrcFs = fs + + if syncer.Delete { + infol.Logf("removing all files from destination that don't exist in static dirs") + + syncer.DeleteFilter = func(f fsync.FileInfo) bool { + name := f.Name() + + // Keep .gitignore and .gitattributes anywhere + if name == ".gitignore" || name == ".gitattributes" { + return true + } + + // Keep Hugo's original dot-directory behavior + return f.IsDir() && strings.HasPrefix(name, ".") + } + } + start := time.Now() + + // because we are using a baseFs (to get the union right). + // set sync src to root + err := syncer.Sync(publishDir, helpers.FilePathSeparator) + if err != nil { + return 0, err + } + loggers.TimeTrackf(infol, start, nil, "syncing static files to %s", publishDir) + + // Sync runs Stat 2 times for every source file. + numFiles := fs.statCounter / 2 + + return numFiles, err +} + +func (c *hugoBuilder) doWithPublishDirs(f func(sourceFs *filesystems.SourceFilesystem) (uint64, error)) (map[string]uint64, error) { + langCount := make(map[string]uint64) + + h, err := c.hugo() + if err != nil { + return nil, err + } + staticFilesystems := h.BaseFs.SourceFilesystems.Static + + if len(staticFilesystems) == 0 { + c.r.logger.Infoln("No static directories found to sync") + return langCount, nil + } + + for lang, fs := range staticFilesystems { + cnt, err := f(fs) + if err != nil { + return langCount, err + } + if lang == "" { + // Not multihost + c.withConf(func(conf *commonConfig) { + for _, l := range conf.configs.Languages { + langCount[l.Lang] = cnt + } + }) + } else { + langCount[lang] = cnt + } + } + + return langCount, nil +} + +func (c *hugoBuilder) progressIntermediate() { + terminal.ReportProgress(c.r.StdOut, terminal.ProgressIntermediate, 0) +} + +func (c *hugoBuilder) progressHidden() { + terminal.ReportProgress(c.r.StdOut, terminal.ProgressHidden, 0) +} + +func (c *hugoBuilder) fullBuild(noBuildLock bool) error { + var ( + g errgroup.Group + langCount map[string]uint64 + ) + + c.r.logger.Println("Start building sites … ") + c.r.logger.Println(hugo.BuildVersionString()) + c.r.logger.Println() + if terminal.IsTerminal(os.Stdout) { + defer func() { + fmt.Print(showCursor + clearLine) + }() + } + + copyStaticFunc := func() error { + cnt, err := c.copyStatic() + if err != nil { + return fmt.Errorf("error copying static files: %w", err) + } + langCount = cnt + return nil + } + buildSitesFunc := func() error { + if err := c.buildSites(noBuildLock); err != nil { + return fmt.Errorf("error building site: %w", err) + } + return nil + } + // Do not copy static files and build sites in parallel if cleanDestinationDir is enabled. + // This flag deletes all static resources in /public folder that are missing in /static, + // and it does so at the end of copyStatic() call. + var cleanDestinationDir bool + c.withConf(func(conf *commonConfig) { + cleanDestinationDir = conf.configs.Base.CleanDestinationDir + }) + if cleanDestinationDir { + if err := copyStaticFunc(); err != nil { + return err + } + if err := buildSitesFunc(); err != nil { + return err + } + } else { + g.Go(copyStaticFunc) + g.Go(buildSitesFunc) + if err := g.Wait(); err != nil { + return err + } + } + + h, err := c.hugo() + if err != nil { + return err + } + for _, s := range h.Sites { + s.ProcessingStats.Static = langCount[s.Language().Lang] + } + + if c.r.gc { + count, err := h.GC() + if err != nil { + return err + } + for _, s := range h.Sites { + // We have no way of knowing what site the garbage belonged to. + s.ProcessingStats.Cleaned = uint64(count) + } + } + + return nil +} + +func (c *hugoBuilder) fullRebuild(changeType string) { + if changeType == configChangeGoMod { + // go.mod may be changed during the build itself, and + // we really want to prevent superfluous builds. + if !c.fullRebuildSem.TryAcquire(1) { + return + } + c.fullRebuildSem.Release(1) + } + + c.fullRebuildSem.Acquire(context.Background(), 1) + + go func() { + defer c.fullRebuildSem.Release(1) + + c.printChangeDetected(changeType) + + defer func() { + // Allow any file system events to arrive basimplecobra. + // This will block any rebuild on config changes for the + // duration of the sleep. + time.Sleep(2 * time.Second) + }() + + defer c.postBuild("Rebuilt", time.Now()) + + err := c.reloadConfig() + if err != nil { + // Set the processing on pause until the state is recovered. + c.errState.setPaused(true) + c.handleBuildErr(err, "Failed to reload config") + if c.s.doLiveReload { + livereload.ForceRefresh() + } + } else { + c.errState.setPaused(false) + } + + if !c.errState.isPaused() { + _, err := c.copyStatic() + if err != nil { + c.r.logger.Errorln(err) + return + } + err = c.buildSites(false) + if err != nil { + c.r.logger.Errorln(err) + } else if c.s != nil && c.s.doLiveReload { + livereload.ForceRefresh() + } + } + }() +} + +func (c *hugoBuilder) handleBuildErr(err error, msg string) { + c.errState.setBuildErr(err) + c.r.logger.Errorln(msg + ": " + cleanErrorLog(err.Error())) +} + +func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher, + staticSyncer *staticSyncer, + evs []fsnotify.Event, + configSet map[string]bool, +) { + defer func() { + c.errState.setWasErr(false) + }() + + var isHandled bool + + // Filter out ghost events (from deleted, renamed directories). + // This seems to be a bug in fsnotify, or possibly MacOS. + var n int + for _, ev := range evs { + keep := true + // Write and rename operations are often followed by CHMOD. + // There may be valid use cases for rebuilding the site on CHMOD, + // but that will require more complex logic than this simple conditional. + // On OS X this seems to be related to Spotlight, see: + // https://github.com/go-fsnotify/fsnotify/issues/15 + // A workaround is to put your site(s) on the Spotlight exception list, + // but that may be a little mysterious for most end users. + // So, for now, we skip reload on CHMOD. + // We do have to check for WRITE though. On slower laptops a Chmod + // could be aggregated with other important events, and we still want + // to rebuild on those + if ev.Op == fsnotify.Chmod { + keep = false + } else if ev.Has(fsnotify.Create) || ev.Has(fsnotify.Write) { + if _, err := os.Stat(ev.Name); err != nil { + keep = false + } + } + if keep { + evs[n] = ev + n++ + } + } + evs = evs[:n] + + for _, ev := range evs { + isConfig := configSet[ev.Name] + configChangeType := configChangeConfig + if isConfig { + if strings.Contains(ev.Name, "go.mod") { + configChangeType = configChangeGoMod + } + if strings.Contains(ev.Name, ".work") { + configChangeType = configChangeGoWork + } + } + if !isConfig { + // It may be one of the /config folders + dirname := filepath.Dir(ev.Name) + if dirname != "." && configSet[dirname] { + isConfig = true + } + } + + if isConfig { + isHandled = true + + if ev.Op&fsnotify.Chmod == fsnotify.Chmod { + continue + } + + if ev.Op&fsnotify.Remove == fsnotify.Remove || ev.Op&fsnotify.Rename == fsnotify.Rename { + c.withConf(func(conf *commonConfig) { + for _, configFile := range conf.configs.LoadingInfo.ConfigFiles { + counter := 0 + for watcher.Add(configFile) != nil { + counter++ + if counter >= 100 { + break + } + time.Sleep(100 * time.Millisecond) + } + } + }) + } + + // Config file(s) changed. Need full rebuild. + c.fullRebuild(configChangeType) + + return + } + } + + if isHandled { + return + } + + if c.errState.isPaused() { + // Wait for the server to get into a consistent state before + // we continue with processing. + return + } + + if len(evs) > 50 { + // This is probably a mass edit of the content dir. + // Schedule a full rebuild for when it slows down. + c.debounce(func() { + c.fullRebuild("") + }) + return + } + + c.r.logger.Debugln("Received System Events:", evs) + + staticEvents := []fsnotify.Event{} + dynamicEvents := []fsnotify.Event{} + + filterDuplicateEvents := func(evs []fsnotify.Event) []fsnotify.Event { + seen := make(map[string]bool) + var n int + for _, ev := range evs { + if seen[ev.Name] { + continue + } + seen[ev.Name] = true + evs[n] = ev + n++ + } + return evs[:n] + } + + h, err := c.hugo() + if err != nil { + c.r.logger.Errorln("Error getting the Hugo object:", err) + return + } + n = 0 + for _, ev := range evs { + if h.ShouldSkipFileChangeEvent(ev) { + continue + } + evs[n] = ev + n++ + } + evs = evs[:n] + + for _, ev := range evs { + ext := filepath.Ext(ev.Name) + baseName := filepath.Base(ev.Name) + istemp := strings.HasSuffix(ext, "~") || + (ext == ".swp") || // vim + (ext == ".swx") || // vim + (ext == ".bck") || // helix + (ext == ".tmp") || // generic temp file + (ext == ".DS_Store") || // OSX Thumbnail + baseName == "4913" || // vim + strings.HasPrefix(ext, ".goutputstream") || // gnome + strings.HasSuffix(ext, "jb_old___") || // intelliJ + strings.HasSuffix(ext, "jb_tmp___") || // intelliJ + strings.HasSuffix(ext, "jb_bak___") || // intelliJ + strings.HasPrefix(ext, ".sb-") || // byword + strings.HasPrefix(baseName, ".#") || // emacs + strings.HasPrefix(baseName, "#") // emacs + if istemp { + continue + } + + if h.Deps.SourceSpec.IgnoreFile(ev.Name) { + continue + } + // Sometimes during rm -rf operations a '"": REMOVE' is triggered. Just ignore these + if ev.Name == "" { + continue + } + + walkAdder := func(ctx context.Context, path string, f hugofs.FileMetaInfo) error { + if f.IsDir() { + c.r.logger.Println("adding created directory to watchlist", path) + if err := watcher.Add(path); err != nil { + return err + } + } else if !staticSyncer.isStatic(h, path) { + // Hugo's rebuilding logic is entirely file based. When you drop a new folder into + // /content on OSX, the above logic will handle future watching of those files, + // but the initial CREATE is lost. + dynamicEvents = append(dynamicEvents, fsnotify.Event{Name: path, Op: fsnotify.Create}) + } + return nil + } + + // recursively add new directories to watch list + if ev.Has(fsnotify.Create) || ev.Has(fsnotify.Rename) { + c.withConf(func(conf *commonConfig) { + if s, err := conf.fs.Source.Stat(ev.Name); err == nil && s.Mode().IsDir() { + _ = helpers.Walk(conf.fs.Source, ev.Name, walkAdder) + } + }) + } + + if staticSyncer.isStatic(h, ev.Name) { + staticEvents = append(staticEvents, ev) + } else { + dynamicEvents = append(dynamicEvents, ev) + } + } + + lrl := c.r.logger.InfoCommand("livereload") + + staticEvents = filterDuplicateEvents(staticEvents) + dynamicEvents = filterDuplicateEvents(dynamicEvents) + + if len(staticEvents) > 0 { + c.printChangeDetected("Static files") + + if c.r.forceSyncStatic { + c.r.logger.Printf("Syncing all static files\n") + _, err := c.copyStatic() + if err != nil { + c.r.logger.Errorln("Error copying static files to publish dir:", err) + return + } + } else { + if err := staticSyncer.syncsStaticEvents(staticEvents); err != nil { + c.r.logger.Errorln("Error syncing static files to publish dir:", err) + return + } + } + + if c.s != nil && c.s.doLiveReload { + // Will block forever trying to write to a channel that nobody is reading if livereload isn't initialized + + if !c.errState.wasErr() && len(staticEvents) == 1 { + h, err := c.hugo() + if err != nil { + c.r.logger.Errorln("Error getting the Hugo object:", err) + return + } + + path := h.BaseFs.SourceFilesystems.MakeStaticPathRelative(staticEvents[0].Name) + path = h.RelURL(paths.ToSlashTrimLeading(path), false) + + lrl.Logf("refreshing static file %q", path) + livereload.RefreshPath(path) + } else { + lrl.Logf("got %d static file change events, force refresh", len(staticEvents)) + livereload.ForceRefresh() + } + } + } + + if len(dynamicEvents) > 0 { + partitionedEvents := partitionDynamicEvents( + h.BaseFs.SourceFilesystems, + dynamicEvents) + + onePageName := pickOneWriteOrCreatePath(h.Conf.ContentTypes(), partitionedEvents.ContentEvents) + + c.printChangeDetected("") + c.changeDetector.PrepareNew() + + func() { + defer c.postBuild("Total", time.Now()) + if err := c.rebuildSites(dynamicEvents); err != nil { + c.handleBuildErr(err, "Rebuild failed") + } + }() + + if c.s != nil && c.s.doLiveReload { + if c.errState.wasErr() { + livereload.ForceRefresh() + return + } + + changed := c.changeDetector.changed() + if c.changeDetector != nil { + if len(changed) >= 10 { + lrl.Logf("build changed %d files", len(changed)) + } else { + lrl.Logf("build changed %d files: %q", len(changed), changed) + } + if len(changed) == 0 { + // Nothing has changed. + return + } + } + + // If this change set also contains one or more CSS files, we need to + // refresh these as well. + var cssChanges []string + var otherChanges []string + + for _, ev := range changed { + if strings.HasSuffix(ev, ".css") { + cssChanges = append(cssChanges, ev) + } else { + otherChanges = append(otherChanges, ev) + } + } + + if len(partitionedEvents.ContentEvents) > 0 { + navigate := c.s != nil && c.s.navigateToChanged + // We have fetched the same page above, but it may have + // changed. + var p page.Page + + if navigate { + if onePageName != "" { + p = h.GetContentPage(onePageName) + } + } + + if p != nil && p.RelPermalink() != "" { + link, port := p.RelPermalink(), p.Site().ServerPort() + lrl.Logf("navigating to %q using port %d", link, port) + livereload.NavigateToPathForPort(link, port) + } else { + lrl.Logf("no page to navigate to, force refresh") + livereload.ForceRefresh() + } + } else if len(otherChanges) > 0 || len(cssChanges) > 0 { + if len(otherChanges) == 1 { + // Allow single changes to be refreshed without a full page reload. + pathToRefresh := h.PathSpec.RelURL(paths.ToSlashTrimLeading(otherChanges[0]), false) + lrl.Logf("refreshing %q", pathToRefresh) + livereload.RefreshPath(pathToRefresh) + } else if len(cssChanges) == 0 || len(otherChanges) > 1 { + lrl.Logf("force refresh") + livereload.ForceRefresh() + } + } else { + lrl.Logf("force refresh") + livereload.ForceRefresh() + } + + if len(cssChanges) > 0 { + // Allow some time for the live reload script to get reconnected. + if len(otherChanges) > 0 { + time.Sleep(200 * time.Millisecond) + } + for _, ev := range cssChanges { + pathToRefresh := h.PathSpec.RelURL(paths.ToSlashTrimLeading(ev), false) + lrl.Logf("refreshing CSS %q", pathToRefresh) + livereload.RefreshPath(pathToRefresh) + } + } + } + } +} + +func (c *hugoBuilder) postBuild(what string, start time.Time) { + if h, err := c.hugo(); err == nil && h.Conf.Running() { + h.LogServerAddresses() + } + c.r.timeTrack(start, what) +} + +func (c *hugoBuilder) hugo() (*hugolib.HugoSites, error) { + var h *hugolib.HugoSites + if err := c.withConfE(func(conf *commonConfig) error { + var err error + h, err = c.r.HugFromConfig(conf) + return err + }); err != nil { + return nil, err + } + + if c.s != nil { + // A running server, register the media types. + for _, s := range h.Sites { + s.RegisterMediaTypes() + } + } + return h, nil +} + +func (c *hugoBuilder) hugoTry() *hugolib.HugoSites { + var h *hugolib.HugoSites + c.withConf(func(conf *commonConfig) { + h, _ = c.r.HugFromConfig(conf) + }) + return h +} + +func (c *hugoBuilder) loadConfig(cd *simplecobra.Commandeer, running bool) error { + if terminal.PrintANSIColors(os.Stdout) { + defer c.progressHidden() + // If the configuration takes a while to load, we want to show some progress. + // This is typically loading of external modules. + d := debounce.New(500 * time.Millisecond) + d(func() { + c.progressIntermediate() + }) + defer d(func() {}) + } + + cfg := config.New() + cfg.Set("renderToMemory", c.r.renderToMemory) + watch := c.r.buildWatch || (c.s != nil && c.s.serverWatch) + // We need to set the environment as early as possible because we need it to load the correct config. + c.r.resolveEnvironment(c.s != nil) + cfg.Set("environment", c.r.environment) + + cfg.Set("internal", hmaps.Params{ + "running": running, + "watch": watch, + "verbose": c.r.isVerbose(), + "fastRenderMode": c.fastRenderMode, + }) + + conf, err := c.r.ConfigFromProvider(configKey{counter: c.r.configVersionID.Load()}, flagsToCfg(cd, cfg)) + if err != nil { + return err + } + + if len(conf.configs.LoadingInfo.ConfigFiles) == 0 { + //lint:ignore ST1005 end user message. + return errors.New("Unable to locate config file or config directory. Perhaps you need to create a new project.\nRun `hugo help new` for details.") + } + + c.conf = conf + c.confOld = conf + if c.onConfigLoaded != nil { + if err := c.onConfigLoaded(false); err != nil { + return err + } + } + + return nil +} + +var rebuildCounter atomic.Uint64 + +func (c *hugoBuilder) printChangeDetected(typ string) { + msg := "\nChange" + if typ != "" { + msg += " of " + typ + } + msg += fmt.Sprintf(" detected, rebuilding site (#%d).", rebuildCounter.Add(1)) + + c.r.logger.Println(msg) + const layout = "2006-01-02 15:04:05.000 -0700" + c.r.logger.Println(htime.Now().Format(layout)) +} + +func (c *hugoBuilder) rebuildSites(events []fsnotify.Event) (err error) { + defer func() { + c.errState.setBuildErr(err) + }() + if err := c.errState.buildErr(); err != nil { + ferrs := herrors.UnwrapFileErrorsWithErrorContext(err) + for _, err := range ferrs { + events = append(events, fsnotify.Event{Name: err.Position().Filename, Op: fsnotify.Write}) + } + } + var h *hugolib.HugoSites + h, err = c.hugo() + if err != nil { + return + } + err = h.Build(hugolib.BuildCfg{NoBuildLock: true, RecentlyTouched: c.visitedURLs, ErrRecovery: c.errState.wasErr()}, events...) + return +} + +func (c *hugoBuilder) rebuildSitesForChanges(ids []identity.Identity) (err error) { + defer func() { + c.errState.setBuildErr(err) + }() + + var h *hugolib.HugoSites + h, err = c.hugo() + if err != nil { + return + } + whatChanged := &hugolib.WhatChanged{} + whatChanged.Add(ids...) + err = h.Build(hugolib.BuildCfg{NoBuildLock: true, WhatChanged: whatChanged, RecentlyTouched: c.visitedURLs, ErrRecovery: c.errState.wasErr()}) + + return +} + +func (c *hugoBuilder) reloadConfig() error { + c.r.resetLogs() + c.r.configVersionID.Add(1) + + if err := c.withConfOrOldConfE(func(conf *commonConfig) error { + oldConf := conf + c.conf = nil + newConf, err := c.r.ConfigFromConfig(configKey{counter: c.r.configVersionID.Load()}, conf) + if err != nil { + return err + } + sameLen := len(oldConf.configs.Languages) == len(newConf.configs.Languages) + if !sameLen { + if oldConf.configs.IsMultihost || newConf.configs.IsMultihost { + return errors.New("multihost change detected, please restart server") + } + } + c.conf = newConf + return nil + }); err != nil { + return err + } + + if c.onConfigLoaded != nil { + if err := c.onConfigLoaded(true); err != nil { + return err + } + } + + return nil +} diff --git a/commands/import.go b/commands/import.go new file mode 100644 index 0000000..e07ebed --- /dev/null +++ b/commands/import.go @@ -0,0 +1,625 @@ +// Copyright 2024 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package commands + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "log" + "os" + "path/filepath" + "regexp" + "strconv" + "strings" + "time" + "unicode" + + "github.com/bep/simplecobra" + "github.com/gohugoio/hugo/common/hmaps" + "github.com/gohugoio/hugo/common/htime" + "github.com/gohugoio/hugo/common/hugio" + "github.com/gohugoio/hugo/helpers" + "github.com/gohugoio/hugo/hugofs" + "github.com/gohugoio/hugo/parser" + "github.com/gohugoio/hugo/parser/metadecoders" + "github.com/gohugoio/hugo/parser/pageparser" + "github.com/spf13/afero" + "github.com/spf13/cobra" +) + +func newImportCommand() *importCommand { + var c *importCommand + c = &importCommand{ + commands: []simplecobra.Commander{ + &simpleCommand{ + name: "jekyll", + short: "hugo import from Jekyll", + long: `hugo import from Jekyll. + +Import from Jekyll requires two paths, e.g. ` + "`hugo import jekyll jekyll_root_path target_path`.", + run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error { + if len(args) < 2 { + return newUserError(`import from jekyll requires two paths, e.g. ` + "`hugo import jekyll jekyll_root_path target_path`.") + } + return c.importFromJekyll(args) + }, + withc: func(cmd *cobra.Command, r *rootCommand) { + cmd.ValidArgsFunction = cobra.NoFileCompletions + cmd.Flags().BoolVar(&c.force, "force", false, "allow import into non-empty target directory") + }, + }, + }, + } + + return c +} + +type importCommand struct { + r *rootCommand + + force bool + + commands []simplecobra.Commander +} + +func (c *importCommand) Commands() []simplecobra.Commander { + return c.commands +} + +func (c *importCommand) Name() string { + return "import" +} + +func (c *importCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error { + return nil +} + +func (c *importCommand) Init(cd *simplecobra.Commandeer) error { + cmd := cd.CobraCommand + cmd.Short = "Import a project from another system" + cmd.Long = `Import a project from another system. + +Import requires a subcommand, e.g. ` + "`hugo import jekyll jekyll_root_path target_path`." + + cmd.RunE = nil + return nil +} + +func (c *importCommand) PreRun(cd, runner *simplecobra.Commandeer) error { + c.r = cd.Root.Command.(*rootCommand) + return nil +} + +func (i *importCommand) createConfigFromJekyll(fs afero.Fs, inpath string, kind metadecoders.Format, jekyllConfig map[string]any) (err error) { + title := "My New Hugo Project" + baseURL := "http://example.org/" + + for key, value := range jekyllConfig { + lowerKey := strings.ToLower(key) + + switch lowerKey { + case "title": + if str, ok := value.(string); ok { + title = str + } + + case "url": + if str, ok := value.(string); ok { + baseURL = str + } + } + } + + in := map[string]any{ + "baseURL": baseURL, + "title": title, + "locale": "en-us", + "disablePathToLower": true, + } + + var buf bytes.Buffer + err = parser.InterfaceToConfig(in, kind, &buf) + if err != nil { + return err + } + + return helpers.WriteToDisk(filepath.Join(inpath, "hugo."+string(kind)), &buf, fs) +} + +func (c *importCommand) getJekyllDirInfo(fs afero.Fs, jekyllRoot string) (map[string]bool, bool) { + postDirs := make(map[string]bool) + hasAnyPost := false + if entries, err := os.ReadDir(jekyllRoot); err == nil { + for _, entry := range entries { + if entry.IsDir() { + subDir := filepath.Join(jekyllRoot, entry.Name()) + if isPostDir, hasAnyPostInDir := c.retrieveJekyllPostDir(fs, subDir); isPostDir { + postDirs[entry.Name()] = hasAnyPostInDir + if hasAnyPostInDir { + hasAnyPost = true + } + } + } + } + } + return postDirs, hasAnyPost +} + +func (c *importCommand) createProjectFromJekyll(jekyllRoot, targetDir string, jekyllPostDirs map[string]bool) error { + fs := &afero.OsFs{} + if exists, _ := helpers.Exists(targetDir, fs); exists { + if isDir, _ := helpers.IsDir(targetDir, fs); !isDir { + return errors.New("target path \"" + targetDir + "\" exists but is not a directory") + } + + isEmpty, _ := helpers.IsEmpty(targetDir, fs) + + if !isEmpty && !c.force { + return errors.New("target path \"" + targetDir + "\" exists and is not empty") + } + } + + jekyllConfig := c.loadJekyllConfig(fs, jekyllRoot) + + mkdir(targetDir, "layouts") + mkdir(targetDir, "content") + mkdir(targetDir, "archetypes") + mkdir(targetDir, "static") + mkdir(targetDir, "data") + mkdir(targetDir, "themes") + + c.createConfigFromJekyll(fs, targetDir, "yaml", jekyllConfig) + + c.copyJekyllFilesAndFolders(jekyllRoot, filepath.Join(targetDir, "static"), jekyllPostDirs) + + return nil +} + +func (c *importCommand) convertJekyllContent(m any, content string) (string, error) { + metadata, _ := hmaps.ToStringMapE(m) + + lines := strings.Split(content, "\n") + var resultLines []string + for _, line := range lines { + resultLines = append(resultLines, strings.Trim(line, "\r\n")) + } + + content = strings.Join(resultLines, "\n") + + excerptSep := "" + if value, ok := metadata["excerpt_separator"]; ok { + if str, strOk := value.(string); strOk { + content = strings.Replace(content, strings.TrimSpace(str), excerptSep, -1) + } + } + + replaceList := []struct { + re *regexp.Regexp + replace string + }{ + {regexp.MustCompile("(?i)"), ""}, + {regexp.MustCompile(`\{%\s*raw\s*%\}\s*(.*?)\s*\{%\s*endraw\s*%\}`), "$1"}, + {regexp.MustCompile(`{%\s*endhighlight\s*%}`), "{{< / highlight >}}"}, + } + + for _, replace := range replaceList { + content = replace.re.ReplaceAllString(content, replace.replace) + } + + replaceListFunc := []struct { + re *regexp.Regexp + replace func(string) string + }{ + // Octopress image tag: http://octopress.org/docs/plugins/image-tag/ + {regexp.MustCompile(`{%\s+img\s*(.*?)\s*%}`), c.replaceImageTag}, + {regexp.MustCompile(`{%\s*highlight\s*(.*?)\s*%}`), c.replaceHighlightTag}, + } + + for _, replace := range replaceListFunc { + content = replace.re.ReplaceAllStringFunc(content, replace.replace) + } + + var buf bytes.Buffer + if len(metadata) != 0 { + err := parser.InterfaceToFrontMatter(m, metadecoders.YAML, &buf) + if err != nil { + return "", err + } + } + buf.WriteString(content) + + return buf.String(), nil +} + +func (c *importCommand) convertJekyllMetaData(m any, postName string, postDate time.Time, draft bool) (any, error) { + metadata, err := hmaps.ToStringMapE(m) + if err != nil { + return nil, err + } + + if draft { + metadata["draft"] = true + } + + for key, value := range metadata { + lowerKey := strings.ToLower(key) + + switch lowerKey { + case "layout": + delete(metadata, key) + case "permalink": + if str, ok := value.(string); ok { + metadata["url"] = str + } + delete(metadata, key) + case "category": + if str, ok := value.(string); ok { + metadata["categories"] = []string{str} + } + delete(metadata, key) + case "excerpt_separator": + if key != lowerKey { + delete(metadata, key) + metadata[lowerKey] = value + } + case "date": + if str, ok := value.(string); ok { + re := regexp.MustCompile(`(\d+):(\d+):(\d+)`) + r := re.FindAllStringSubmatch(str, -1) + if len(r) > 0 { + hour, _ := strconv.Atoi(r[0][1]) + minute, _ := strconv.Atoi(r[0][2]) + second, _ := strconv.Atoi(r[0][3]) + postDate = time.Date(postDate.Year(), postDate.Month(), postDate.Day(), hour, minute, second, 0, time.UTC) + } + } + delete(metadata, key) + } + + } + + metadata["date"] = postDate.Format(time.RFC3339) + + return metadata, nil +} + +func (c *importCommand) convertJekyllPost(path, relPath, targetDir string, draft bool) error { + log.Println("Converting", path) + + filename := filepath.Base(path) + postDate, postName, err := c.parseJekyllFilename(filename) + if err != nil { + c.r.Printf("Failed to parse filename '%s': %s. Skipping.", filename, err) + return nil + } + + log.Println(filename, postDate, postName) + + targetFile := filepath.Join(targetDir, relPath) + targetParentDir := filepath.Dir(targetFile) + os.MkdirAll(targetParentDir, 0o777) + + contentBytes, err := os.ReadFile(path) + if err != nil { + c.r.logger.Errorln("Read file error:", path) + return err + } + pf, err := pageparser.ParseFrontMatterAndContent(bytes.NewReader(contentBytes)) + if err != nil { + return fmt.Errorf("failed to parse file %q: %s", filename, err) + } + newmetadata, err := c.convertJekyllMetaData(pf.FrontMatter, postName, postDate, draft) + if err != nil { + return fmt.Errorf("failed to convert metadata for file %q: %s", filename, err) + } + + content, err := c.convertJekyllContent(newmetadata, string(pf.Content)) + if err != nil { + return fmt.Errorf("failed to convert content for file %q: %s", filename, err) + } + + fs := hugofs.Os + if err := helpers.WriteToDisk(targetFile, strings.NewReader(content), fs); err != nil { + return fmt.Errorf("failed to save file %q: %s", filename, err) + } + return nil +} + +func (c *importCommand) copyJekyllFilesAndFolders(jekyllRoot, dest string, jekyllPostDirs map[string]bool) (err error) { + fs := hugofs.Os + + fi, err := fs.Stat(jekyllRoot) + if err != nil { + return err + } + if !fi.IsDir() { + return errors.New(jekyllRoot + " is not a directory") + } + err = os.MkdirAll(dest, fi.Mode()) + if err != nil { + return err + } + entries, err := os.ReadDir(jekyllRoot) + if err != nil { + return err + } + + for _, entry := range entries { + sfp := filepath.Join(jekyllRoot, entry.Name()) + dfp := filepath.Join(dest, entry.Name()) + if entry.IsDir() { + if entry.Name()[0] != '_' && entry.Name()[0] != '.' { + if _, ok := jekyllPostDirs[entry.Name()]; !ok { + err = hugio.CopyDir(fs, sfp, dfp, nil) + if err != nil { + c.r.logger.Errorln(err) + } + } + } + } else { + lowerEntryName := strings.ToLower(entry.Name()) + exceptSuffix := []string{ + ".md", ".markdown", ".html", ".htm", + ".xml", ".textile", "rakefile", "gemfile", ".lock", + } + isExcept := false + for _, suffix := range exceptSuffix { + if strings.HasSuffix(lowerEntryName, suffix) { + isExcept = true + break + } + } + + if !isExcept && entry.Name()[0] != '.' && entry.Name()[0] != '_' { + err = hugio.CopyFile(fs, sfp, dfp) + if err != nil { + c.r.logger.Errorln(err) + } + } + } + + } + return nil +} + +func (c *importCommand) importFromJekyll(args []string) error { + jekyllRoot, err := filepath.Abs(filepath.Clean(args[0])) + if err != nil { + return newUserError("path error:", args[0]) + } + + targetDir, err := filepath.Abs(filepath.Clean(args[1])) + if err != nil { + return newUserError("path error:", args[1]) + } + + c.r.Println("Import Jekyll from:", jekyllRoot, "to:", targetDir) + + if strings.HasPrefix(filepath.Dir(targetDir), jekyllRoot) { + return newUserError("abort: target path should not be inside the Jekyll root") + } + + fs := afero.NewOsFs() + jekyllPostDirs, hasAnyPost := c.getJekyllDirInfo(fs, jekyllRoot) + if !hasAnyPost { + return errors.New("abort: jekyll root contains neither posts nor drafts") + } + + err = c.createProjectFromJekyll(jekyllRoot, targetDir, jekyllPostDirs) + if err != nil { + return newUserError(err) + } + + c.r.Println("Importing...") + + fileCount := 0 + callback := func(ctx context.Context, path string, fi hugofs.FileMetaInfo) error { + if fi.IsDir() { + return nil + } + + relPath, err := filepath.Rel(jekyllRoot, path) + if err != nil { + return newUserError("get rel path error:", path) + } + + relPath = filepath.ToSlash(relPath) + draft := false + + switch { + case strings.Contains(relPath, "_posts/"): + relPath = filepath.Join("content/post", strings.Replace(relPath, "_posts/", "", -1)) + case strings.Contains(relPath, "_drafts/"): + relPath = filepath.Join("content/draft", strings.Replace(relPath, "_drafts/", "", -1)) + draft = true + default: + return nil + } + + fileCount++ + return c.convertJekyllPost(path, relPath, targetDir, draft) + } + + for jekyllPostDir, hasAnyPostInDir := range jekyllPostDirs { + if hasAnyPostInDir { + if err = helpers.Walk(hugofs.Os, filepath.Join(jekyllRoot, jekyllPostDir), callback); err != nil { + return err + } + } + } + + c.r.Println("Congratulations!", fileCount, "post(s) imported!") + c.r.Println("Now, start Hugo by yourself:") + c.r.Println("cd " + args[1]) + c.r.Println("git init") + c.r.Println("git submodule add https://github.com/gohugo-ananke/ananke themes/ananke") + c.r.Println("echo \"theme: ananke\" >> hugo.yaml") + c.r.Println("hugo server") + + return nil +} + +func (c *importCommand) loadJekyllConfig(fs afero.Fs, jekyllRoot string) map[string]any { + for _, candidate := range []struct { + filename string + format metadecoders.Format + }{ + {"_config.yml", metadecoders.YAML}, + {"_config.yaml", metadecoders.YAML}, + {"_config.toml", metadecoders.TOML}, + } { + path := filepath.Join(jekyllRoot, candidate.filename) + exists, err := helpers.Exists(path, fs) + if err != nil || !exists { + continue + } + + f, err := fs.Open(path) + if err != nil { + continue + } + b, err := io.ReadAll(f) + f.Close() + if err != nil { + continue + } + + m, err := metadecoders.Default.UnmarshalToMap(b, candidate.format) + if err != nil { + continue + } + + return m + } + + c.r.Println("no config file (_config.yml, _config.yaml, or _config.toml) found: is the specified Jekyll root correct?") + return nil +} + +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/list.go b/commands/list.go new file mode 100644 index 0000000..42f3408 --- /dev/null +++ b/commands/list.go @@ -0,0 +1,213 @@ +// 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" + "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" +) + +// 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(), + } + } + + list := func(cd *simplecobra.Commandeer, r *rootCommand, shouldInclude func(page.Page) bool, opts ...any) error { + bcfg := hugolib.BuildCfg{SkipRender: true} + cfg := flagsToCfg(cd, nil) + for i := 0; i < len(opts); i += 2 { + cfg.Set(opts[i].(string), opts[i+1]) + } + h, err := r.Build(cd, bcfg, cfg) + if err != nil { + return err + } + + writer := csv.NewWriter(r.StdOut) + defer writer.Flush() + + writer.Write([]string{ + "path", + "slug", + "title", + "date", + "expiryDate", + "publishDate", + "draft", + "permalink", + "kind", + "section", + }) + + for _, p := range h.Pages() { + if shouldInclude(p) { + record := createRecord(h.Conf.BaseConfig().WorkingDir, p) + if err := writer.Write(record); err != nil { + return err + } + } + } + + 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 +} + +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 +} diff --git a/commands/mod.go b/commands/mod.go new file mode 100644 index 0000000..eed9cfb --- /dev/null +++ b/commands/mod.go @@ -0,0 +1,354 @@ +// 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/hugolib" + "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 project +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: "Merges module Node.js dependencies into an npm workspace", + long: `Merges Node.js dependencies from all Hugo modules into a "packages/hugoautogen" npm workspace. + +The merged dependencies are written to packages/hugoautogen/package.json, and the root package.json +is updated with a "workspaces" entry pointing to "packages/hugoautogen". + +The source entries are read from either package.hugo.json or package.json in the module root, with package.hugo.json taking precedence if both exist. + +See [Node.js dependencies](/hugo-modules/nodejs-dependencies/) for more information. +`, + withc: func(cmd *cobra.Command, r *rootCommand) { + cmd.ValidArgsFunction = cobra.NoFileCompletions + applyLocalFlagsBuildConfig(cmd, r) + }, + run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error { + cfg := flagsToCfg(cd, nil) + k := configKey{counter: r.configVersionID.Load(), skipNpmCheck: true} + h, _, err := r.hugoSites.GetOrCreate(k, func(key configKey) (*hugolib.HugoSites, error) { + conf, err := r.ConfigFromProvider(key, cfg) + if err != nil { + return nil, err + } + return hugolib.NewHugoSites(r.newDepsConfig(conf)) + }) + if err != nil { + return err + } + return npm.Pack(h.BaseFs.ProjectSourceFs, h.BaseFs.AssetsWithDuplicatesPreserved.Fs, h.Configs.Modules) + }, + }, + }, + } + + return &modCommands{ + commands: []simplecobra.Commander{ + &simpleCommand{ + name: "init", + short: "Initialize this project as a Hugo Module", + long: `Initialize this project as a Hugo Module. + It will try to guess the module path, but you may help by passing it as an argument, e.g: + + hugo mod init github.com/gohugoio/testshortcodes + + Note that Hugo Modules supports multi-module projects, so you can initialize a Hugo Module + inside a subfolder on GitHub, as one example. + `, + withc: func(cmd *cobra.Command, r *rootCommand) { + cmd.ValidArgsFunction = cobra.NoFileCompletions + applyLocalFlagsBuildConfig(cmd, r) + }, + run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error { + h, err := r.getOrCreateHugo(flagsToCfg(cd, nil), true) + if err != nil { + return err + } + var initPath string + if len(args) >= 1 { + initPath = args[0] + } + c := h.Configs.ModulesClient + if err := c.Init(initPath); err != nil { + return err + } + return nil + }, + }, + &simpleCommand{ + name: "verify", + short: "Verify dependencies", + long: `Verify checks that the dependencies of the current module, which are stored in a local downloaded source cache, have not been modified since being downloaded.`, + withc: func(cmd *cobra.Command, r *rootCommand) { + cmd.ValidArgsFunction = cobra.NoFileCompletions + applyLocalFlagsBuildConfig(cmd, r) + cmd.Flags().BoolVarP(&clean, "clean", "", false, "delete module cache for dependencies that fail verification") + }, + run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error { + conf, err := r.ConfigFromProvider(configKey{counter: r.configVersionID.Load()}, flagsToCfg(cd, nil)) + if err != nil { + return err + } + client := conf.configs.ModulesClient + return client.Verify(clean) + }, + }, + &simpleCommand{ + name: "graph", + short: "Print a module dependency graph", + long: `Print a module dependency graph with information about module status (disabled, vendored). +Note that for vendored modules, that is the version listed and not the one from go.mod. +`, + withc: func(cmd *cobra.Command, r *rootCommand) { + cmd.ValidArgsFunction = cobra.NoFileCompletions + applyLocalFlagsBuildConfig(cmd, r) + cmd.Flags().BoolVarP(&clean, "clean", "", false, "delete module cache for dependencies that fail verification") + }, + run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error { + conf, err := r.ConfigFromProvider(configKey{counter: r.configVersionID.Load()}, flagsToCfg(cd, nil)) + if err != nil { + return err + } + client := conf.configs.ModulesClient + return client.Graph(os.Stdout) + }, + }, + &simpleCommand{ + name: "clean", + short: "Delete the Hugo Module cache for the current project", + long: `Delete the Hugo Module cache for the current project.`, + withc: func(cmd *cobra.Command, r *rootCommand) { + cmd.ValidArgsFunction = cobra.NoFileCompletions + applyLocalFlagsBuildConfig(cmd, r) + cmd.Flags().StringVarP(&pattern, "pattern", "", "", `pattern matching module paths to clean (all if not set), e.g. "**hugo*"`) + _ = cmd.RegisterFlagCompletionFunc("pattern", cobra.NoFileCompletions) + cmd.Flags().BoolVarP(&all, "all", "", false, "clean entire module cache") + }, + run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error { + h, err := r.Hugo(flagsToCfg(cd, nil)) + if err != nil { + return err + } + if all { + modCache := h.ResourceSpec.FileCaches.ModulesCache() + count, err := modCache.Prune(true) + r.Printf("Deleted %d directories from module cache.", count) + return err + } + + 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 + if err := client.Get(args...); err != nil { + return err + } + return nil + } + }, + }, + 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 new file mode 100644 index 0000000..e82258a --- /dev/null +++ b/commands/new.go @@ -0,0 +1,230 @@ +// Copyright 2024 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package commands + +import ( + "bytes" + "context" + "path/filepath" + "strings" + + "github.com/bep/simplecobra" + "github.com/gohugoio/hugo/common/paths" + "github.com/gohugoio/hugo/config" + "github.com/gohugoio/hugo/create" + "github.com/gohugoio/hugo/create/skeletons" + "github.com/spf13/cobra" +) + +func newNewCommand() *newCommand { + var ( + force bool + contentType string + format string + ) + + var c *newCommand + c = &newCommand{ + commands: []simplecobra.Commander{ + &simpleCommand{ + name: "content", + use: "content [path]", + short: "Create new content", + long: `Create a new content file and automatically set the date and title. +It will guess which kind of file to create based on the path provided. + +You can also specify the kind with ` + "`-k KIND`" + `. + +If archetypes are provided in your theme or project, they will be used. + +Ensure you run this within the root directory of your project.`, + run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error { + if len(args) < 1 { + return newUserError("path needs to be provided") + } + cfg := flagsToCfg(cd, nil) + cfg.Set("BuildFuture", true) + h, err := r.Hugo(cfg) + if err != nil { + return err + } + return create.NewContent(h, contentType, args[0], force) + }, + withc: func(cmd *cobra.Command, r *rootCommand) { + cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return []string{}, cobra.ShellCompDirectiveNoFileComp + } + return []string{}, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveFilterDirs + } + cmd.Flags().StringVarP(&contentType, "kind", "k", "", "content type to create") + cmd.Flags().String("editor", "", "edit new content with this editor, if provided") + _ = cmd.RegisterFlagCompletionFunc("editor", cobra.NoFileCompletions) + cmd.Flags().BoolVarP(&force, "force", "f", false, "overwrite file if it already exists") + applyLocalFlagsBuildConfig(cmd, r) + }, + }, + &simpleCommand{ + name: "project", + use: "project [path]", + short: "Create a new project", + long: `Create a new project at the specified path.`, + aliases: []string{"site"}, + run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error { + if len(args) < 1 { + return newUserError("path needs to be provided") + } + createpath, err := filepath.Abs(filepath.Clean(args[0])) + if err != nil { + return err + } + + cfg := config.New() + cfg.Set("workingDir", createpath) + cfg.Set("publishDir", "public") + + conf, err := r.ConfigFromProvider(configKey{counter: r.configVersionID.Load()}, flagsToCfg(cd, cfg)) + if err != nil { + return err + } + sourceFs := conf.fs.Source + + err = skeletons.CreateProject(createpath, sourceFs, force, format) + if err != nil { + return err + } + + r.Printf("Congratulations! Your new Hugo project was created in %s.\n\n", createpath) + r.Println(c.newProjectNextStepsText(createpath, format)) + + return nil + }, + withc: func(cmd *cobra.Command, r *rootCommand) { + cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return []string{}, cobra.ShellCompDirectiveNoFileComp + } + return []string{}, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveFilterDirs + } + cmd.Flags().BoolVarP(&force, "force", "f", false, "init inside non-empty directory") + cmd.Flags().StringVar(&format, "format", "toml", "preferred file format (toml, yaml or json)") + _ = cmd.RegisterFlagCompletionFunc("format", cobra.FixedCompletions([]string{"toml", "yaml", "json"}, cobra.ShellCompDirectiveNoFileComp)) + }, + }, + &simpleCommand{ + name: "theme", + use: "theme [name]", + short: "Create a new theme", + long: `Create a new theme with the specified name in the ./themes directory. +This generates a functional theme including template examples and sample content.`, + run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error { + if len(args) < 1 { + return newUserError("theme name needs to be provided") + } + cfg := config.New() + cfg.Set("publishDir", "public") + + conf, err := r.ConfigFromProvider(configKey{counter: r.configVersionID.Load()}, flagsToCfg(cd, cfg)) + if err != nil { + return err + } + sourceFs := conf.fs.Source + createpath := paths.AbsPathify(conf.configs.Base.WorkingDir, filepath.Join(conf.configs.Base.ThemesDir, args[0])) + r.Println("Creating new theme in", createpath) + + err = skeletons.CreateTheme(createpath, sourceFs, format) + if err != nil { + return err + } + + return nil + }, + withc: func(cmd *cobra.Command, r *rootCommand) { + cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return []string{}, cobra.ShellCompDirectiveNoFileComp + } + return []string{}, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveFilterDirs + } + cmd.Flags().StringVar(&format, "format", "toml", "preferred file format (toml, yaml or json)") + _ = cmd.RegisterFlagCompletionFunc("format", cobra.FixedCompletions([]string{"toml", "yaml", "json"}, cobra.ShellCompDirectiveNoFileComp)) + }, + }, + }, + } + + return c +} + +type newCommand struct { + rootCmd *rootCommand + + commands []simplecobra.Commander +} + +func (c *newCommand) Commands() []simplecobra.Commander { + return c.commands +} + +func (c *newCommand) Name() string { + return "new" +} + +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 project, they will be used. + +Ensure you run this within the root directory of your project.` + + 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) newProjectNextStepsText(path string, format string) string { + format = strings.ToLower(format) + var nextStepsText bytes.Buffer + + nextStepsText.WriteString(`Just a few more steps... + +1. Change the current directory to ` + path + `. +2. Create or install a theme: + - Create a new theme with the command "hugo new theme " + - Or, install a theme from https://themes.gohugo.io/ +3. Edit hugo.` + format + `, setting the "theme" property to the theme name. +4. Create new content with the command "hugo new content `) + + nextStepsText.WriteString(filepath.Join("", ".")) + + nextStepsText.WriteString(`". +5. Start the embedded web server with the command "hugo server --buildDrafts". + +See documentation at https://gohugo.io/.`) + + return nextStepsText.String() +} diff --git a/commands/release.go b/commands/release.go new file mode 100644 index 0000000..ae97cad --- /dev/null +++ b/commands/release.go @@ -0,0 +1,55 @@ +// 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" + "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 + version string + ) + + return &simpleCommand{ + name: "release", + short: "Release a new version of Hugo", + run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error { + rel, err := releaser.New(skipPush, try, step, version) + if err != nil { + return err + } + + return rel.Run() + }, + withc: func(cmd *cobra.Command, r *rootCommand) { + cmd.Hidden = true + cmd.ValidArgsFunction = cobra.NoFileCompletions + cmd.PersistentFlags().BoolVarP(&skipPush, "skip-push", "", false, "skip pushing to remote") + cmd.PersistentFlags().BoolVarP(&try, "try", "", false, "no changes") + cmd.PersistentFlags().IntVarP(&step, "step", "", 0, "step to run (1: set new version 2: prepare next dev version)") + cmd.PersistentFlags().StringVarP(&version, "version", "", "", "version to release (derived from branch name if not set)") + _ = cmd.RegisterFlagCompletionFunc("step", cobra.FixedCompletions([]string{"1", "2"}, cobra.ShellCompDirectiveNoFileComp)) + }, + } +} diff --git a/commands/server.go b/commands/server.go new file mode 100644 index 0000000..045c375 --- /dev/null +++ b/commands/server.go @@ -0,0 +1,1261 @@ +// 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" + "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" + "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/common/paths" + "github.com/gohugoio/hugo/langs" + "github.com/gohugoio/hugo/tpl/tplimpl" + + "github.com/gohugoio/hugo/common/types" + "github.com/gohugoio/hugo/common/urls" + "github.com/gohugoio/hugo/config" + "github.com/gohugoio/hugo/helpers" + "github.com/gohugoio/hugo/hugofs" + "github.com/gohugoio/hugo/hugolib" + "github.com/gohugoio/hugo/hugolib/filesystems" + "github.com/gohugoio/hugo/livereload" + "github.com/gohugoio/hugo/transform" + "github.com/gohugoio/hugo/transform/livereloadinject" + "github.com/spf13/afero" + "github.com/spf13/cobra" + "github.com/spf13/fsync" + "golang.org/x/sync/errgroup" + "golang.org/x/sync/semaphore" +) + +var ( + logDuplicateTemplateExecuteRe = regexp.MustCompile(`: template: .*?:\d+:\d+: executing ".*?"`) + logDuplicateTemplateParseRe = regexp.MustCompile(`: template: .*?:\d+:\d*`) +) + +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:", "", +) + +const ( + configChangeConfig = "config file" + configChangeGoMod = "go.mod file" + configChangeGoWork = "go work file" +) + +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.withConfOrOldConf(func(conf *commonConfig) { + if lrport := conf.configs.GetFirstLanguageConfig().BaseURLLiveReload().Port(); lrport != 0 { + port = lrport + } + }) + lr := baseURL.URL() + lr.Host = fmt.Sprintf("%s:%d", lr.Hostname(), port) + fmt.Fprint(w, injectLiveReloadScript(r, lr)) + + 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) { + // See issue 14240. + // Hugo escapes the URL paths when generating them, + // that may not be the case when we receive it back from the browser. + // PathEscape will escape if it is not already escaped. + requestURI = paths.PathEscape(requestURI) + + if !f.c.visitedURLs.Contains(requestURI) { + // If not already on stack, re-render that single page. + if err := f.c.partialReRender(requestURI); err != nil { + 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 +} + +type filesOnlyFs struct { + fs http.FileSystem +} + +func (fs filesOnlyFs) Open(name string) (http.File, error) { + f, err := fs.fs.Open(name) + if err != nil { + return nil, err + } + return noDirFile{f}, nil +} + +type noDirFile struct { + http.File +} + +func (f noDirFile) Readdir(count int) ([]os.FileInfo, error) { + return nil, nil +} + +type serverCommand struct { + r *rootCommand + + commands []simplecobra.Commander + + *hugoBuilder + + quit chan bool // Closed when the server should shut down. Used in tests only. + serverPorts []serverPortListener + doLiveReload bool + + // Flags. + renderStaticToDisk bool + navigateToChanged bool + openBrowser bool + serverAppend bool + serverInterface string + tlsCertFile string + tlsKeyFile string + tlsAuto bool + pprof bool + serverPort int + liveReloadPort int + serverWatch bool + noHTTPCache bool + disableLiveReload bool + disableFastRender bool + disableBrowserError bool +} + +func (c *serverCommand) Name() string { + return "server" +} + +func (c *serverCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error { + if c.pprof { + go func() { + http.ListenAndServe("localhost:8080", nil) + }() + } + // Watch runs its own server as part of the routine + if c.serverWatch { + + watchDirs, err := c.getDirList() + if err != nil { + return err + } + + watchGroups := helpers.ExtractAndGroupRootPaths(watchDirs) + + c.r.Printf("Watching for changes in %s\n", strings.Join(watchGroups, ", ")) + watcher, err := c.newWatcher(c.r.poll, watchDirs...) + if err != nil { + return err + } + + defer watcher.Close() + + } + + err := func() error { + defer c.r.timeTrack(time.Now(), "Built") + return c.build() + }() + 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 project. +While hugo server is high performance, it is a webserver with limited options. + +The ` + "`" + `hugo server` + "`" + ` command will by default write and serve files from disk, but +you can render to memory by using the ` + "`" + `--renderToMemory` + "`" + ` flag. This can be +faster in some cases, but it will consume more memory. + +By default hugo will also watch your files for any changes you make and +automatically rebuild the project. It will then live reload any open browser pages +and push the latest content to them. As most Hugo projects are built in a fraction +of a second, you will be able to save and see your changes nearly instantly.` + cmd.Aliases = []string{"serve"} + + cmd.Flags().IntVarP(&c.serverPort, "port", "p", 1313, "port on which the server will listen") + _ = cmd.RegisterFlagCompletionFunc("port", cobra.NoFileCompletions) + cmd.Flags().IntVar(&c.liveReloadPort, "liveReloadPort", -1, "port for live reloading (i.e. 443 in HTTPS proxy situations)") + _ = cmd.RegisterFlagCompletionFunc("liveReloadPort", cobra.NoFileCompletions) + cmd.Flags().StringVarP(&c.serverInterface, "bind", "", "127.0.0.1", "interface to which the server will bind") + _ = cmd.RegisterFlagCompletionFunc("bind", cobra.NoFileCompletions) + cmd.Flags().StringVarP(&c.tlsCertFile, "tlsCertFile", "", "", "path to TLS certificate file") + _ = cmd.MarkFlagFilename("tlsCertFile", "pem") + cmd.Flags().StringVarP(&c.tlsKeyFile, "tlsKeyFile", "", "", "path to TLS key file") + _ = cmd.MarkFlagFilename("tlsKeyFile", "pem") + cmd.Flags().BoolVar(&c.tlsAuto, "tlsAuto", false, "generate and use locally-trusted certificates.") + cmd.Flags().BoolVar(&c.pprof, "pprof", false, "enable the pprof server (port 8080)") + cmd.Flags().BoolVarP(&c.serverWatch, "watch", "w", true, "watch filesystem for changes and recreate as needed") + cmd.Flags().BoolVar(&c.noHTTPCache, "noHTTPCache", false, "disable browser caching of pages served by the embedded web server") + cmd.Flags().BoolVarP(&c.serverAppend, "appendPort", "", true, "append port to baseURL") + cmd.Flags().BoolVar(&c.disableLiveReload, "disableLiveReload", false, "watch without enabling live browser reload on rebuild") + cmd.Flags().BoolVarP(&c.navigateToChanged, "navigateToChanged", "N", false, "navigate to changed content file on live browser reload") + cmd.Flags().BoolVarP(&c.openBrowser, "openBrowser", "O", false, "open the project in a browser after server startup") + cmd.Flags().BoolVar(&c.renderStaticToDisk, "renderStaticToDisk", false, "serve static files from disk and dynamic files from memory") + cmd.Flags().BoolVar(&c.disableFastRender, "disableFastRender", false, "enables full re-renders on changes") + cmd.Flags().BoolVar(&c.disableBrowserError, "disableBrowserError", false, "do not show build errors in the browser") + + r := cd.Root.Command.(*rootCommand) + applyLocalFlagsBuild(cmd, r) + + return nil +} + +func (c *serverCommand) PreRun(cd, runner *simplecobra.Commandeer) error { + c.r = cd.Root.Command.(*rootCommand) + + c.hugoBuilder = newHugoBuilder( + c.r, + c, + func(reloaded bool) error { + if !reloaded { + if err := c.createServerPorts(cd); err != nil { + return err + } + + if (c.tlsCertFile == "" || c.tlsKeyFile == "") && c.tlsAuto { + c.withConfE(func(conf *commonConfig) error { + return c.createCertificates(conf) + }) + } + } + + if err := c.setServerInfoInConfig(); err != nil { + return err + } + + if !reloaded && c.fastRenderMode { + c.withConf(func(conf *commonConfig) { + conf.fs.PublishDir = hugofs.NewHashingFs(conf.fs.PublishDir, c.changeDetector) + conf.fs.PublishDirStatic = hugofs.NewHashingFs(conf.fs.PublishDirStatic, c.changeDetector) + }) + } + + return nil + }, + ) + + destinationFlag := cd.CobraCommand.Flags().Lookup("destination") + if c.r.renderToMemory && (destinationFlag != nil && destinationFlag.Changed) { + return fmt.Errorf("cannot use --renderToMemory with --destination") + } + c.doLiveReload = !c.disableLiveReload + c.fastRenderMode = !c.disableFastRender + c.showErrorInBrowser = c.doLiveReload && !c.disableBrowserError + + if c.fastRenderMode { + // For now, fast render mode only. It should, however, be fast enough + // for the full variant, too. + c.changeDetector = &fileChangeDetector{ + // We use this detector to decide to do a Hot reload of a single path or not. + // We need to filter out source maps and possibly some other to be able + // to make that decision. + irrelevantRe: regexp.MustCompile(`\.map$`), + } + + c.changeDetector.PrepareNew() + + } + + err := c.loadConfig(cd, true) + 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.Languages { + isMultihost := conf.configs.IsMultihost + var serverPort int + if isMultihost { + serverPort = c.serverPorts[i].p + } else { + serverPort = c.serverPorts[0].p + } + langConfig := conf.configs.LanguageConfigMap[language.Lang] + baseURLStr, err := c.fixURL(langConfig.BaseURL, c.r.baseURL, serverPort) + if err != nil { + return err + } + 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 + } + + 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() + } + + // For now, store these in the Hugo cache dir. + // Hugo should probably introduce some concept of a less temporary application directory. + keyDir := filepath.Join(conf.configs.LoadingInfo.BaseConfig.CacheDir, "_mkcerts") + + // Create the directory if it doesn't exist. + if _, err := os.Stat(keyDir); os.IsNotExist(err) { + if err := os.MkdirAll(keyDir, 0o777); err != nil { + return err + } + } + + c.tlsCertFile = filepath.Join(keyDir, fmt.Sprintf("%s.pem", hostname)) + c.tlsKeyFile = filepath.Join(keyDir, fmt.Sprintf("%s-key.pem", hostname)) + + // Check if the certificate already exists and is valid. + certPEM, err := os.ReadFile(c.tlsCertFile) + if err == nil { + rootPem, err := os.ReadFile(filepath.Join(mclib.GetCAROOT(), "rootCA.pem")) + if err == nil { + if err := c.verifyCert(rootPem, certPEM, hostname); err == nil { + c.r.Println("Using existing", c.tlsCertFile, "and", c.tlsKeyFile) + return nil + } + } + } + + c.r.Println("Creating TLS certificates in", keyDir) + + // Yes, this is unfortunate, but it's currently the only way to use Mkcert as a library. + os.Args = []string{"-cert-file", c.tlsCertFile, "-key-file", c.tlsKeyFile, hostname} + return mclib.RunMain() +} + +func (c *serverCommand) verifyCert(rootPEM, certPEM []byte, name string) error { + roots := x509.NewCertPool() + ok := roots.AppendCertsFromPEM(rootPEM) + if !ok { + return fmt.Errorf("failed to parse root certificate") + } + + block, _ := pem.Decode(certPEM) + if block == nil { + return fmt.Errorf("failed to parse certificate PEM") + } + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return fmt.Errorf("failed to parse certificate: %v", err.Error()) + } + + opts := x509.VerifyOptions{ + DNSName: name, + Roots: roots, + } + + if _, err := cert.Verify(opts); err != nil { + return fmt.Errorf("failed to verify certificate: %v", err.Error()) + } + + return nil +} + +func (c *serverCommand) createServerPorts(cd *simplecobra.Commandeer) error { + flags := cd.CobraCommand.Flags() + var cerr error + c.withConf(func(conf *commonConfig) { + isMultihost := conf.configs.IsMultihost + c.serverPorts = make([]serverPortListener, 1) + if isMultihost { + if !c.serverAppend { + cerr = errors.New("--appendPort=false not supported when in multihost mode") + return + } + c.serverPorts = make([]serverPortListener, len(conf.configs.Languages)) + } + currentServerPort := c.serverPort + for i := range c.serverPorts { + l, err := net.Listen("tcp", net.JoinHostPort(c.serverInterface, strconv.Itoa(currentServerPort))) + if err == nil { + c.serverPorts[i] = serverPortListener{ln: l, p: currentServerPort} + } else { + if i == 0 && flags.Changed("port") { + // port set explicitly by user -- he/she probably meant it! + cerr = fmt.Errorf("server startup failed: %s", err) + return + } + c.r.Println("port", currentServerPort, "already in use, attempting to use an available port") + l, sp, err := helpers.TCPListen() + if err != nil { + cerr = fmt.Errorf("unable to find alternative port to use: %s", err) + return + } + c.serverPorts[i] = serverPortListener{ln: l, p: sp.Port} + } + + currentServerPort = c.serverPorts[i].p + 1 + } + }) + + return cerr +} + +// fixURL massages the baseURL into a form needed for serving +// all pages correctly. +func (c *serverCommand) fixURL(baseURLFromConfig, baseURLFromFlag string, port int) (string, error) { + certsSet := (c.tlsCertFile != "" && c.tlsKeyFile != "") || c.tlsAuto + useLocalhost := false + baseURL := baseURLFromFlag + if baseURL == "" { + baseURL = baseURLFromConfig + useLocalhost = true + } + + if !strings.HasSuffix(baseURL, "/") { + baseURL = baseURL + "/" + } + + // do an initial parse of the input string + u, err := url.Parse(baseURL) + if err != nil { + return "", err + } + + // if no Host is defined, then assume that no schema or double-slash were + // present in the url. Add a double-slash and make a best effort attempt. + if u.Host == "" && baseURL != "/" { + baseURL = "//" + baseURL + + u, err = url.Parse(baseURL) + if err != nil { + return "", err + } + } + + if useLocalhost { + if certsSet { + u.Scheme = "https" + } else if u.Scheme == "https" { + u.Scheme = "http" + } + u.Host = "localhost" + } + + if c.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) + } + } + u.Host += fmt.Sprintf(":%d", port) + } + + 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) + } + + var h *hugolib.HugoSites + h, err = c.hugo() + if err != nil { + return + } + + // Note: We do not set NoBuildLock as the file lock is not acquired at this stage. + err = h.Build(hugolib.BuildCfg{NoBuildLock: false, RecentlyTouched: visited, PartialReRender: true, ErrRecovery: c.errState.wasErr()}) + + return +} + +func (c *serverCommand) serve() error { + var ( + baseURLs []urls.BaseURL + roots []string + h *hugolib.HugoSites + ) + err := c.withConfE(func(conf *commonConfig) error { + isMultihost := conf.configs.IsMultihost + var err error + h, err = c.r.HugFromConfig(conf) + if err != nil { + return err + } + + // We need the server to share the same logger as the Hugo build (for error counts etc.) + c.r.logger = h.Log + + if isMultihost { + for _, l := range conf.configs.ConfigLangs() { + baseURLs = append(baseURLs, l.BaseURL()) + roots = append(roots, l.Language().(*langs.Language).Lang) + } + } else { + l := conf.configs.GetFirstLanguageConfig() + baseURLs = []urls.BaseURL{l.BaseURL()} + roots = []string{""} + } + + return nil + }) + if err != nil { + return err + } + + // Cache it here. The HugoSites object may be unavailable later on due to intermittent configuration errors. + // To allow the en user to change the error template while the server is running, we use + // the freshest template we can provide. + var ( + errTempl *tplimpl.TemplInfo + templHandler *tplimpl.TemplateStore + ) + getErrorTemplateAndHandler := func(h *hugolib.HugoSites) (*tplimpl.TemplInfo, *tplimpl.TemplateStore) { + if h == nil { + return errTempl, templHandler + } + templHandler := h.GetTemplateStore() + errTempl := templHandler.LookupByPath("/_server/error.html") + if errTempl == nil { + panic("template server/error.html not found") + } + return errTempl, templHandler + } + errTempl, templHandler = getErrorTemplateAndHandler(h) + + srv := &fileServer{ + baseURLs: baseURLs, + roots: roots, + c: c, + errorTemplate: func(ctx any) (io.Reader, error) { + // hugoTry does not block, getErrorTemplateAndHandler will fall back + // to cached values if nil. + templ, handler := getErrorTemplateAndHandler(c.hugoTry()) + b := &bytes.Buffer{} + err := handler.ExecuteWithContext(context.Background(), templ, b, ctx) + return b, err + }, + } + + doLiveReload := !c.disableLiveReload + + if doLiveReload { + livereload.Initialize() + } + + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + var servers []*http.Server + + wg1, ctx := errgroup.WithContext(context.Background()) + + for i := range baseURLs { + mu, listener, serverURL, endpoint, err := srv.createEndpoint(i) + var srv *http.Server + if c.tlsCertFile != "" && c.tlsKeyFile != "" { + srv = &http.Server{ + Addr: endpoint, + Handler: mu, + TLSConfig: &tls.Config{ + MinVersion: tls.VersionTLS12, + }, + } + } else { + srv = &http.Server{ + Addr: endpoint, + Handler: mu, + } + } + + servers = append(servers, srv) + + if doLiveReload { + baseURL := baseURLs[i] + mu.HandleFunc(baseURL.Path()+"livereload.js", livereload.ServeJS) + mu.HandleFunc(baseURL.Path()+"livereload", livereload.Handler) + } + c.r.Printf("Web Server is available at %s (bind address %s) %s\n", serverURL, c.serverInterface, roots[i]) + wg1.Go(func() error { + if c.tlsCertFile != "" && c.tlsKeyFile != "" { + err = srv.ServeTLS(listener, c.tlsCertFile, c.tlsKeyFile) + } else { + err = srv.Serve(listener) + } + if err != nil && err != http.ErrServerClosed { + return err + } + return nil + }) + } + + if c.r.IsTestRun() { + // Write a .ready file to disk to signal ready status. + // This is where the test is run from. + var baseURLs []string + for _, baseURL := range srv.baseURLs { + baseURLs = append(baseURLs, baseURL.String()) + } + testInfo := map[string]any{ + "baseURLs": baseURLs, + } + + dir := os.Getenv("WORK") + if dir != "" { + readyFile := filepath.Join(dir, ".ready") + // encode the test info as JSON into the .ready file. + b, err := json.Marshal(testInfo) + if err != nil { + return err + } + err = os.WriteFile(readyFile, b, 0o777) + if err != nil { + return err + } + } + + } + + c.r.Println("Press Ctrl+C to stop") + + if c.openBrowser { + // There may be more than one baseURL in multihost mode, open the first. + if err := browser.OpenURL(baseURLs[0].String()); err != nil { + c.r.logger.Warnf("Failed to open browser: %s", err) + } + } + + err = func() error { + for { + select { + case <-c.quit: + return nil + case <-sigs: + return nil + case <-ctx.Done(): + return ctx.Err() + } + } + }() + if err != nil { + c.r.Println("Error:", err) + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + wg2, ctx := errgroup.WithContext(ctx) + for _, srv := range servers { + wg2.Go(func() error { + return srv.Shutdown(ctx) + }) + } + + err1, err2 := wg1.Wait(), wg2.Wait() + if err1 != nil { + return err1 + } + return err2 +} + +type serverPortListener struct { + p int + ln net.Listener +} + +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) + } + } else { + c.r.logger.Errorln(err) + } + + continue + } + + // For all other event operations Hugo will sync static. + logger.Println("Syncing", relPath, "to", publishDir) + if err := syncer.Sync(filepath.Join(publishDir, relPath), relPath); err != nil { + c.r.logger.Errorln(err) + } + } + + return 0, nil + } + + _, err := c.doWithPublishDirs(syncFn) + return err +} + +// chmodFilter is a ChmodFilter for static syncing. +// Returns true to skip syncing permissions for directories and files without +// owner-write permission. The primary use case is files from the module cache (0444). +func chmodFilter(dst, src os.FileInfo) bool { + if src.IsDir() { + return true + } + return src.Mode().Perm()&0o200 == 0 +} + +func cleanErrorLog(content string) string { + content = logReplacer.Replace(content) + content = logDuplicateTemplateExecuteRe.ReplaceAllString(content, "") + content = logDuplicateTemplateParseRe.ReplaceAllString(content, "") + seen := make(map[string]bool) + parts := strings.Split(content, ": ") + keep := make([]string, 0, len(parts)) + for _, part := range parts { + if seen[part] { + continue + } + seen[part] = true + keep = append(keep, part) + } + return strings.Join(keep, ": ") +} + +func injectLiveReloadScript(src io.Reader, baseURL *url.URL) string { + var b bytes.Buffer + chain := transform.Chain{livereloadinject.New(baseURL)} + chain.Apply(&b, src) + + return b.String() +} + +func partitionDynamicEvents(sourceFs *filesystems.SourceFilesystems, events []fsnotify.Event) (de dynamicEvents) { + for _, e := range events { + if !sourceFs.IsContent(e.Name) { + de.AssetEvents = append(de.AssetEvents, e) + } else { + de.ContentEvents = append(de.ContentEvents, e) + } + } + return +} + +func pickOneWriteOrCreatePath(contentTypes config.ContentTypesProvider, events []fsnotify.Event) string { + name := "" + + for _, ev := range events { + if ev.Op&fsnotify.Write == fsnotify.Write || ev.Op&fsnotify.Create == fsnotify.Create { + if contentTypes.IsIndexContentFile(ev.Name) { + return ev.Name + } + + if contentTypes.IsContentFile(ev.Name) { + name = ev.Name + } + + } + } + + return name +} + +func formatByteCount(b uint64) string { + const unit = 1000 + if b < unit { + return fmt.Sprintf("%d B", b) + } + div, exp := int64(unit), 0 + for n := b / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + return fmt.Sprintf("%.1f %cB", + float64(b)/float64(div), "kMGTPE"[exp]) +} + +func canRedirect(requestURIWithoutQuery string, r *http.Request) bool { + if r.Header.Get(hugoHeaderRedirect) != "" { + return false + } + return isNavigation(requestURIWithoutQuery, r) +} + +// Sec-Fetch-Mode should be sent by all recent browser versions, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Fetch-Mode#navigate +// Fall back to the file extension if not set. +// The main take here is that we don't want to have CSS/JS files etc. partake in this logic. +func isNavigation(requestURIWithoutQuery string, r *http.Request) bool { + return r.Header.Get("Sec-Fetch-Mode") == "navigate" || isPropablyHTMLRequest(requestURIWithoutQuery) +} + +func isPropablyHTMLRequest(requestURIWithoutQuery string) bool { + if strings.HasSuffix(requestURIWithoutQuery, "/") || strings.HasSuffix(requestURIWithoutQuery, "html") || strings.HasSuffix(requestURIWithoutQuery, "htm") { + return true + } + return !strings.Contains(requestURIWithoutQuery, ".") +} diff --git a/common/collections/append.go b/common/collections/append.go new file mode 100644 index 0000000..8a5aee4 --- /dev/null +++ b/common/collections/append.go @@ -0,0 +1,140 @@ +// 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" + + "github.com/gohugoio/hugo/common/hreflect" +) + +// Append appends from to a slice to and returns the resulting slice. +// If length of from is one and the only element is a slice of same type as to, +// it will be appended. +func Append(to any, from ...any) (any, error) { + if len(from) == 0 { + return to, nil + } + tov, toIsNil := hreflect.Indirect(reflect.ValueOf(to)) + + toIsNil = toIsNil || to == nil + var tot reflect.Type + + if !toIsNil { + if tov.Kind() == reflect.Slice { + // Create a copy of tov, so we don't modify the original. + c := reflect.MakeSlice(tov.Type(), tov.Len(), tov.Len()+len(from)) + reflect.Copy(c, tov) + tov = c + } + + if tov.Kind() != reflect.Slice { + return nil, fmt.Errorf("expected a slice, got %T", to) + } + + tot = tov.Type().Elem() + if tot.Kind() == reflect.Slice { + totvt := tot.Elem() + fromvs := make([]reflect.Value, len(from)) + for i, f := range from { + fromv := reflect.ValueOf(f) + fromt := fromv.Type() + if fromt.Kind() == reflect.Slice { + fromt = fromt.Elem() + } + if totvt != fromt { + return nil, fmt.Errorf("cannot append slice of %s to slice of %s", fromt, totvt) + } else { + fromvs[i] = fromv + } + } + return reflect.Append(tov, fromvs...).Interface(), nil + + } + + toIsNil = tov.Len() == 0 + + if len(from) == 1 { + fromv := reflect.ValueOf(from[0]) + if !fromv.IsValid() { + // from[0] is nil + return appendToInterfaceSliceFromValues(tov, fromv) + } + fromt := fromv.Type() + if fromt.Kind() == reflect.Slice { + fromt = fromt.Elem() + } + if fromv.Kind() == reflect.Slice { + if toIsNil { + // If we get nil []string, we just return the []string + return from[0], nil + } + + // 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, _ := hreflect.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 +} diff --git a/common/collections/append_test.go b/common/collections/append_test.go new file mode 100644 index 0000000..c198ca0 --- /dev/null +++ b/common/collections/append_test.go @@ -0,0 +1,149 @@ +// 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" + "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"}) +} diff --git a/common/collections/collections.go b/common/collections/collections.go new file mode 100644 index 0000000..0b46abe --- /dev/null +++ b/common/collections/collections.go @@ -0,0 +1,21 @@ +// 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 new file mode 100644 index 0000000..4bdc3b4 --- /dev/null +++ b/common/collections/order.go @@ -0,0 +1,20 @@ +// 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 new file mode 100644 index 0000000..731f489 --- /dev/null +++ b/common/collections/slice.go @@ -0,0 +1,95 @@ +// 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 new file mode 100644 index 0000000..4008a5e --- /dev/null +++ b/common/collections/slice_test.go @@ -0,0 +1,172 @@ +// 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 new file mode 100644 index 0000000..3eb6ab5 --- /dev/null +++ b/common/collections/stack.go @@ -0,0 +1,135 @@ +// Copyright 2024 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package collections + +import ( + "iter" + "slices" + "sync" + + "github.com/gohugoio/hugo/common/hiter" +) + +// StackThreadSafe is a simple LIFO stack that is safe for concurrent use. +type StackThreadSafe[T any] struct { + items []T + zero T + mu sync.RWMutex +} + +func NewStackThreadSafe[T any]() *StackThreadSafe[T] { + return &StackThreadSafe[T]{} +} + +func (s *StackThreadSafe[T]) Push(item T) { + s.mu.Lock() + defer s.mu.Unlock() + s.items = append(s.items, item) +} + +func (s *StackThreadSafe[T]) Pop() (T, bool) { + s.mu.Lock() + defer s.mu.Unlock() + if len(s.items) == 0 { + return s.zero, false + } + item := s.items[len(s.items)-1] + s.items = s.items[:len(s.items)-1] + return item, true +} + +func (s *StackThreadSafe[T]) Peek() (T, bool) { + s.mu.RLock() + defer s.mu.RUnlock() + if len(s.items) == 0 { + return s.zero, false + } + return s.items[len(s.items)-1], true +} + +func (s *StackThreadSafe[T]) Len() int { + s.mu.RLock() + defer s.mu.RUnlock() + return len(s.items) +} + +// All returns all items in the stack, from bottom to top. +func (s *StackThreadSafe[T]) All() iter.Seq2[int, T] { + return hiter.Lock2(slices.All(s.items), s.mu.RLock, s.mu.RUnlock) +} + +func (s *StackThreadSafe[T]) Drain() []T { + s.mu.Lock() + defer s.mu.Unlock() + items := s.items + s.items = nil + return items +} + +func (s *StackThreadSafe[T]) DrainMatching(predicate func(T) bool) []T { + s.mu.Lock() + defer s.mu.Unlock() + var items []T + for i := len(s.items) - 1; i >= 0; i-- { + if predicate(s.items[i]) { + items = append(items, s.items[i]) + s.items = slices.Delete(s.items, i, i+1) + } + } + return items +} + +// Stack is a simple LIFO stack that is not safe for concurrent use. +type Stack[T any] struct { + items []T + zero T +} + +func NewStack[T any]() *Stack[T] { + return &Stack[T]{} +} + +func (s *Stack[T]) Push(item T) { + s.items = append(s.items, item) +} + +func (s *Stack[T]) Pop() (T, bool) { + if len(s.items) == 0 { + return s.zero, false + } + item := s.items[len(s.items)-1] + s.items = s.items[:len(s.items)-1] + return item, true +} + +func (s *Stack[T]) Peek() (T, bool) { + if len(s.items) == 0 { + return s.zero, false + } + return s.items[len(s.items)-1], true +} + +func (s *Stack[T]) Len() int { + return len(s.items) +} + +func (s *Stack[T]) All() iter.Seq2[int, T] { + return slices.All(s.items) +} + +func (s *Stack[T]) Drain() []T { + items := s.items + s.items = nil + return items +} diff --git a/common/collections/stack_test.go b/common/collections/stack_test.go new file mode 100644 index 0000000..4b2b1a5 --- /dev/null +++ b/common/collections/stack_test.go @@ -0,0 +1,77 @@ +package collections + +import ( + "testing" + + qt "github.com/frankban/quicktest" +) + +func TestNewStack(t *testing.T) { + t.Parallel() + c := qt.New(t) + + s := NewStackThreadSafe[int]() + + c.Assert(s, qt.IsNotNil) +} + +func TestStackBasic(t *testing.T) { + t.Parallel() + c := qt.New(t) + + s := NewStackThreadSafe[int]() + + c.Assert(s.Len(), qt.Equals, 0) + + s.Push(1) + s.Push(2) + s.Push(3) + + c.Assert(s.Len(), qt.Equals, 3) + + top, ok := s.Peek() + c.Assert(ok, qt.Equals, true) + c.Assert(top, qt.Equals, 3) + + popped, ok := s.Pop() + c.Assert(ok, qt.Equals, true) + c.Assert(popped, qt.Equals, 3) + + c.Assert(s.Len(), qt.Equals, 2) + + _, _ = s.Pop() + _, _ = s.Pop() + _, ok = s.Pop() + + c.Assert(ok, qt.Equals, false) +} + +func TestStackDrain(t *testing.T) { + t.Parallel() + c := qt.New(t) + + s := NewStackThreadSafe[string]() + s.Push("a") + s.Push("b") + + got := s.Drain() + + c.Assert(got, qt.DeepEquals, []string{"a", "b"}) + c.Assert(s.Len(), qt.Equals, 0) +} + +func TestStackDrainMatching(t *testing.T) { + t.Parallel() + c := qt.New(t) + + s := NewStackThreadSafe[int]() + s.Push(1) + s.Push(2) + s.Push(3) + s.Push(4) + + got := s.DrainMatching(func(v int) bool { return v%2 == 0 }) + + c.Assert(got, qt.DeepEquals, []int{4, 2}) + c.Assert(s.Drain(), qt.DeepEquals, []int{1, 3}) +} diff --git a/common/constants/constants.go b/common/constants/constants.go new file mode 100644 index 0000000..1bc0f6a --- /dev/null +++ b/common/constants/constants.go @@ -0,0 +1,44 @@ +// 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 ( + WarnRenderShortcodesInHTML = "warning-rendershortcodes-in-html" + WarnGoldmarkRawHTML = "warning-goldmark-raw-html" + WarnPartialSuperfluousPrefix = "warning-partial-superfluous-prefix" + WarnHomePageIsLeafBundle = "warning-home-page-is-leaf-bundle" +) + +// Field/method names with special meaning. +const ( + FieldRelPermalink = "RelPermalink" + FieldPermalink = "Permalink" +) + +// IsFieldRelOrPermalink returns whether the given name is a RelPermalink or Permalink. +func IsFieldRelOrPermalink(name string) bool { + return name == FieldRelPermalink || name == FieldPermalink +} + +// Resource transformations. +const ( + ResourceTransformationFingerprint = "fingerprint" +) + +// IsResourceTransformationPermalinkHash returns whether the given name is a resource transformation that changes the permalink based on the content. +func IsResourceTransformationPermalinkHash(name string) bool { + return name == ResourceTransformationFingerprint +} diff --git a/common/docs.go b/common/docs.go new file mode 100644 index 0000000..041a62a --- /dev/null +++ b/common/docs.go @@ -0,0 +1,2 @@ +// Package common provides common helper functionality for Hugo. +package common diff --git a/common/hashing/hashing.go b/common/hashing/hashing.go new file mode 100644 index 0000000..ad28bbc --- /dev/null +++ b/common/hashing/hashing.go @@ -0,0 +1,225 @@ +// Copyright 2024 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package hashing provides common hashing utilities. +package hashing + +import ( + "crypto/md5" + "encoding/hex" + "io" + "strconv" + "sync" + + "github.com/cespare/xxhash/v2" + "github.com/gohugoio/hashstructure" + "github.com/gohugoio/hugo/common/hugio" + "github.com/gohugoio/hugo/identity" +) + +// XXHashFromReader calculates the xxHash for the given reader. +func XXHashFromReader(r io.Reader) (uint64, int64, error) { + h := getXxHashReadFrom() + defer putXxHashReadFrom(h) + + size, err := io.Copy(h, r) + if err != nil { + return 0, 0, err + } + return h.Sum64(), size, nil +} + +type Hasher interface { + io.StringWriter + io.Writer + io.ReaderFrom + Sum64() uint64 +} + +type HashCloser interface { + Hasher + io.Closer +} + +// XxHasher returns a Hasher that uses xxHash. +// Remember to call Close when done. +func XxHasher() HashCloser { + h := getXxHashReadFrom() + return struct { + Hasher + io.Closer + }{ + Hasher: h, + Closer: hugio.CloserFunc(func() error { + putXxHashReadFrom(h) + return nil + }), + } +} + +// XxHashFromReaderHexEncoded calculates the xxHash for the given reader +// and returns the hash as a hex encoded string. +func XxHashFromReaderHexEncoded(r io.Reader) (string, error) { + h := getXxHashReadFrom() + defer putXxHashReadFrom(h) + _, err := io.Copy(h, r) + if err != nil { + return "", err + } + hash := h.Sum(nil) + return hex.EncodeToString(hash), nil +} + +// XXHashFromString calculates the xxHash for the given string. +func XXHashFromString(s string) (uint64, error) { + h := xxhash.New() + h.WriteString(s) + return h.Sum64(), nil +} + +// XxHashFromStringHexEncoded calculates the xxHash for the given strings +// and returns the hash as a hex encoded string. +func XxHashFromStringHexEncoded(s ...string) string { + h := xxhash.New() + for _, f := range s { + h.WriteString(f) + } + hash := h.Sum(nil) + return hex.EncodeToString(hash) +} + +// MD5FromStringHexEncoded returns the MD5 hash of the given string. +func MD5FromStringHexEncoded(f string) string { + h := md5.New() + h.Write([]byte(f)) + return hex.EncodeToString(h.Sum(nil)) +} + +// HashString returns a hash from the given elements. +// It will panic if the hash cannot be calculated. +// Note that this hash should be used primarily for identity, not for change detection as +// it in the more complex values (e.g. Page) will not hash the full content. +func HashString(vs ...any) string { + hash := HashUint64(vs...) + return strconv.FormatUint(hash, 10) +} + +// HashStringHex returns a hash from the given elements as a hex encoded string. +// See HashString for more information. +func HashStringHex(vs ...any) string { + hash := HashUint64(vs...) + return strconv.FormatUint(hash, 16) +} + +var hashOptsPool = sync.Pool{ + New: func() any { + return &hashstructure.HashOptions{ + Hasher: xxhash.New(), + } + }, +} + +func getHashOpts() *hashstructure.HashOptions { + return hashOptsPool.Get().(*hashstructure.HashOptions) +} + +func putHashOpts(opts *hashstructure.HashOptions) { + opts.Hasher.Reset() + hashOptsPool.Put(opts) +} + +// HashUint64 returns a hash from the given elements. +// It will panic if the hash cannot be calculated. +// Note that this hash should be used primarily for identity, not for change detection as +// it in the more complex values (e.g. Page) will not hash the full content. +func HashUint64(vs ...any) uint64 { + var o any + if len(vs) == 1 { + o = toHashable(vs[0]) + } else { + elements := make([]any, len(vs)) + for i, e := range vs { + elements[i] = toHashable(e) + } + o = elements + } + + hash, err := Hash(o) + if err != nil { + panic(err) + } + return hash +} + +// Hash returns a hash from vs. +func Hash(vs ...any) (uint64, error) { + hashOpts := getHashOpts() + defer putHashOpts(hashOpts) + var v any = vs + if len(vs) == 1 { + v = vs[0] + } + return hashstructure.Hash(v, hashOpts) +} + +type keyer interface { + Key() string +} + +// For structs, hashstructure.Hash only works on the exported fields, +// so rewrite the input slice for known identity types. +func toHashable(v any) any { + switch t := v.(type) { + case keyer: + return t.Key() + case identity.IdentityProvider: + return t.GetIdentity() + default: + return v + } +} + +type xxhashReadFrom struct { + buff []byte + *xxhash.Digest +} + +func (x *xxhashReadFrom) ReadFrom(r io.Reader) (int64, error) { + for { + n, err := r.Read(x.buff) + if n > 0 { + x.Digest.Write(x.buff[:n]) + } + if err != nil { + if err == io.EOF { + err = nil + } + return int64(n), err + } + } +} + +var xXhashReadFromPool = sync.Pool{ + New: func() any { + return &xxhashReadFrom{Digest: xxhash.New(), buff: make([]byte, 48*1024)} + }, +} + +func getXxHashReadFrom() *xxhashReadFrom { + return xXhashReadFromPool.Get().(*xxhashReadFrom) +} + +func putXxHashReadFrom(h *xxhashReadFrom) { + h.Reset() + xXhashReadFromPool.Put(h) +} diff --git a/common/hashing/hashing_test.go b/common/hashing/hashing_test.go new file mode 100644 index 0000000..de2fed8 --- /dev/null +++ b/common/hashing/hashing_test.go @@ -0,0 +1,152 @@ +// Copyright 2024 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package hashing + +import ( + "fmt" + "math" + "strings" + "sync" + "testing" + + qt "github.com/frankban/quicktest" +) + +func TestXxHashFromReader(t *testing.T) { + c := qt.New(t) + s := "Hello World" + r := strings.NewReader(s) + got, size, err := XXHashFromReader(r) + c.Assert(err, qt.IsNil) + c.Assert(size, qt.Equals, int64(len(s))) + c.Assert(got, qt.Equals, uint64(7148569436472236994)) +} + +func TestXxHashFromReaderPara(t *testing.T) { + c := qt.New(t) + + var wg sync.WaitGroup + for i := range 10 { + wg.Go(func() { + for j := range 100 { + s := strings.Repeat("Hello ", i+j+1*42) + r := strings.NewReader(s) + got, size, err := XXHashFromReader(r) + c.Assert(size, qt.Equals, int64(len(s))) + c.Assert(err, qt.IsNil) + expect, _ := XXHashFromString(s) + c.Assert(got, qt.Equals, expect) + } + }) + } + + wg.Wait() +} + +func TestXxHashFromString(t *testing.T) { + c := qt.New(t) + s := "Hello World" + got, err := XXHashFromString(s) + c.Assert(err, qt.IsNil) + c.Assert(got, qt.Equals, uint64(7148569436472236994)) +} + +func TestXxHashFromStringHexEncoded(t *testing.T) { + c := qt.New(t) + s := "The quick brown fox jumps over the lazy dog" + got := XxHashFromStringHexEncoded(s) + // Facit: https://asecuritysite.com/encryption/xxhash?val=The%20quick%20brown%20fox%20jumps%20over%20the%20lazy%20dog + c.Assert(got, qt.Equals, "0b242d361fda71bc") +} + +func BenchmarkXXHashFromReader(b *testing.B) { + r := strings.NewReader("Hello World") + + for b.Loop() { + XXHashFromReader(r) + r.Seek(0, 0) + } +} + +func BenchmarkXXHashFromString(b *testing.B) { + s := "Hello World" + + for b.Loop() { + XXHashFromString(s) + } +} + +func BenchmarkXXHashFromStringHexEncoded(b *testing.B) { + s := "The quick brown fox jumps over the lazy dog" + + for b.Loop() { + XxHashFromStringHexEncoded(s) + } +} + +func TestHashString(t *testing.T) { + c := qt.New(t) + + c.Assert(HashString("a", "b"), qt.Equals, "3176555414984061461") + c.Assert(HashString("ab"), qt.Equals, "7347350983217793633") + + var vals []any = []any{"a", "b", tstKeyer{"c"}} + + c.Assert(HashString(vals...), qt.Equals, "4438730547989914315") + c.Assert(vals[2], qt.Equals, tstKeyer{"c"}) +} + +type tstKeyer struct { + key string +} + +func (t tstKeyer) Key() string { + return t.key +} + +func (t tstKeyer) String() string { + return "key: " + t.key +} + +func BenchmarkHashString(b *testing.B) { + word := " hello " + + var tests []string + + for i := 1; i <= 5; i++ { + sentence := strings.Repeat(word, int(math.Pow(4, float64(i)))) + tests = append(tests, sentence) + } + + b.ResetTimer() + + for _, test := range tests { + b.Run(fmt.Sprintf("n%d", len(test)), func(b *testing.B) { + for b.Loop() { + HashString(test) + } + }) + } +} + +func BenchmarkHashMap(b *testing.B) { + m := map[string]any{} + for i := range 1000 { + m[fmt.Sprintf("key%d", i)] = i + } + + for b.Loop() { + HashString(m) + } +} diff --git a/common/hdebug/debug.go b/common/hdebug/debug.go new file mode 100644 index 0000000..a977367 --- /dev/null +++ b/common/hdebug/debug.go @@ -0,0 +1,61 @@ +// Copyright 2025 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package hdebug + +import ( + "fmt" + "strings" + + "github.com/gohugoio/hugo/common/types" + "github.com/gohugoio/hugo/htesting" +) + +// Printf is a debug print function that should be removed before committing code to the repository. +func Printf(format string, args ...any) { + panicIfRealCI() + if len(args) == 1 && !strings.Contains(format, "%") { + format = format + ": %v" + } + if !strings.HasSuffix(format, "\n") { + format = format + "\n" + } + fmt.Printf(format, args...) +} + +func AssertNotNil(a ...any) { + panicIfRealCI() + for _, v := range a { + if types.IsNil(v) { + panic("hdebug.AssertNotNil: value is nil") + } + } +} + +func Panicf(format string, args ...any) { + panicIfRealCI() + // fmt.Println(stack()) + if len(args) == 1 && !strings.Contains(format, "%") { + format = format + ": %v" + } + if !strings.HasSuffix(format, "\n") { + format = format + "\n" + } + panic(fmt.Sprintf(format, args...)) +} + +func panicIfRealCI() { + if htesting.IsRealCI() { + panic("This debug statement should be removed before committing code!") + } +} diff --git a/common/herrors/error_locator.go b/common/herrors/error_locator.go new file mode 100644 index 0000000..acaebb4 --- /dev/null +++ b/common/herrors/error_locator.go @@ -0,0 +1,170 @@ +// 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 new file mode 100644 index 0000000..62f1521 --- /dev/null +++ b/common/herrors/error_locator_test.go @@ -0,0 +1,152 @@ +// Copyright 2024 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package 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 new file mode 100644 index 0000000..5e223f4 --- /dev/null +++ b/common/herrors/errors.go @@ -0,0 +1,180 @@ +// 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" + "os" + "regexp" + "runtime" + "strings" + "time" +) + +// 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) + buf := make([]byte, 64<<10) + buf = buf[:runtime.Stack(buf, false)] + args = append(args, "stacktrace from panic: \n"+string(buf), "\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 new file mode 100644 index 0000000..2f53a1e --- /dev/null +++ b/common/herrors/errors_test.go @@ -0,0 +1,45 @@ +// Copyright 2024 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package herrors + +import ( + "errors" + "fmt" + "testing" + + qt "github.com/frankban/quicktest" + "github.com/spf13/afero" +) + +func TestIsNotExist(t *testing.T) { + c := qt.New(t) + + c.Assert(IsNotExist(afero.ErrFileNotFound), qt.Equals, true) + c.Assert(IsNotExist(afero.ErrFileExists), qt.Equals, false) + c.Assert(IsNotExist(afero.ErrDestinationExists), qt.Equals, false) + c.Assert(IsNotExist(nil), qt.Equals, false) + + c.Assert(IsNotExist(fmt.Errorf("foo")), qt.Equals, false) + + // os.IsNotExist returns false for wrapped errors. + c.Assert(IsNotExist(fmt.Errorf("foo: %w", afero.ErrFileNotFound)), qt.Equals, true) +} + +func TestIsFeatureNotAvailableError(t *testing.T) { + c := qt.New(t) + + c.Assert(IsFeatureNotAvailableError(ErrFeatureNotAvailable), qt.Equals, true) + c.Assert(IsFeatureNotAvailableError(&FeatureNotAvailableError{}), qt.Equals, true) + c.Assert(IsFeatureNotAvailableError(errors.New("asdf")), qt.Equals, false) +} diff --git a/common/herrors/file_error.go b/common/herrors/file_error.go new file mode 100644 index 0000000..62690de --- /dev/null +++ b/common/herrors/file_error.go @@ -0,0 +1,449 @@ +// 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 && ectx.Position.LineNumber > fe.position.LineNumber { + fe.position.LineNumber = ectx.Position.LineNumber + } + + if ectx.Position.ColumnNumber > 0 && ectx.Position.ColumnNumber > fe.position.ColumnNumber { + fe.position.ColumnNumber = ectx.Position.ColumnNumber + } + + return fe +} + +type fileError struct { + position text.Position + 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() + fe := NewFileErrorFromName(err, realFilename) + fe = fe.UpdateContent(f, linematcher) + return fe +} + +func openFile(filename string, fs afero.Fs) (afero.File, string, error) { + realFilename := filename + + // We want the most specific filename possible in the error message. + fi, err2 := fs.Stat(filename) + if err2 == nil { + if s, ok := fi.(interface { + Filename() string + }); ok { + realFilename = s.Filename() + } + } + + f, err2 := fs.Open(filename) + if err2 != nil { + return nil, realFilename, err2 + } + + return f, realFilename, nil +} + +// Cause returns the underlying error, that is, +// it unwraps errors until it finds one that does not implement +// the Unwrap method. +// For a shallow variant, see Unwrap. +func Cause(err error) error { + type unwrapper interface { + Unwrap() error + } + + for err != nil { + 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 +} + +// UnwrapFileErrors returns all FileError contained in err. +func UnwrapFileErrors(err error) []FileError { + if err == nil { + return nil + } + errs := Errors(err) + var fileErrors []FileError + for _, e := range errs { + if v, ok := e.(FileError); ok { + fileErrors = append(fileErrors, v) + } + fileErrors = append(fileErrors, UnwrapFileErrors(errors.Unwrap(e))...) + } + return fileErrors +} + +// UnwrapFileErrorsWithErrorContext tries to unwrap all FileError in err that has an ErrorContext. +func UnwrapFileErrorsWithErrorContext(err error) []FileError { + errs := UnwrapFileErrors(err) + var n int + for _, e := range errs { + if e.ErrorContext() != nil { + errs[n] = e + n++ + } + } + return errs[:n] +} + +// Errors returns the list of errors contained in err. +func Errors(err error) []error { + if err == nil { + return nil + } + + type unwrapper interface { + Unwrap() []error + } + if u, ok := err.(unwrapper); ok { + return u.Unwrap() + } + return []error{err} +} + +func extractFileTypePos(err error) (string, text.Position) { + err = Unwrap(err) + + var fileType string + + // LibSass, DartSass + if pos := extractPosition(err); pos.LineNumber > 0 || pos.Offset > 0 { + _, fileType = paths.FileAndExtNoDelimiter(pos.Filename) + return fileType, pos + } + + // Default to line 1 col 1 if we don't find any better. + pos := text.Position{ + Offset: -1, + LineNumber: 1, + ColumnNumber: 1, + } + + // JSON errors. + offset, typ := extractOffsetAndType(err) + if fileType == "" { + fileType = typ + } + + if offset >= 0 { + pos.Offset = offset + } + + // The error type from the minifier contains line number and column number. + if line, col := extractLineNumberAndColumnNumber(err); line >= 0 { + pos.LineNumber = line + pos.ColumnNumber = col + return fileType, pos + } + + // Look in the error message for the line number. + if lno, col := commonLineNumberExtractor(err); lno > 0 { + pos.ColumnNumber = col + pos.LineNumber = lno + } + + if fileType == "" && pos.Filename != "" { + _, fileType = paths.FileAndExtNoDelimiter(pos.Filename) + } + + return fileType, pos +} + +// 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 +} + +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 new file mode 100644 index 0000000..e7e66ca --- /dev/null +++ b/common/herrors/file_error_test.go @@ -0,0 +1,80 @@ +// Copyright 2024 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package 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`) + + var lines strings.Builder + for i := 1; i <= 100; i++ { + lines.WriteString(fmt.Sprintf("line %d\n", i)) + } + + fe.UpdatePosition(text.Position{LineNumber: 32, ColumnNumber: 2}) + c.Assert(fe.Error(), qt.Equals, `"foo.html:32:2": bar`) + fe.UpdatePosition(text.Position{LineNumber: 0, ColumnNumber: 0, Offset: 212}) + fe.UpdateContent(strings.NewReader(lines.String()), nil) + c.Assert(fe.Error(), qt.Equals, `"foo.html:32:0": bar`) + errorContext := fe.ErrorContext() + c.Assert(errorContext, qt.IsNotNil) + c.Assert(errorContext.Lines, qt.DeepEquals, []string{"line 30", "line 31", "line 32", "line 33", "line 34"}) + c.Assert(errorContext.LinesPos, qt.Equals, 2) + c.Assert(errorContext.ChromaLexer, qt.Equals, "go-html-template") +} + +func TestNewFileErrorExtractFromMessage(t *testing.T) { + t.Parallel() + + c := qt.New(t) + + 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 new file mode 100644 index 0000000..121506b --- /dev/null +++ b/common/herrors/line_number_extractors.go @@ -0,0 +1,73 @@ +// 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{ + // YAML parse errors. + newLineNumberErrHandlerFromRegexp(`\[(\d+):(\d+)\]`), + + // Template/shortcode parse errors + newLineNumberErrHandlerFromRegexp(`:(\d+):(\d*):`), + newLineNumberErrHandlerFromRegexp(`:(\d+):`), + + // i18n bundle errors + newLineNumberErrHandlerFromRegexp(`\((\d+),\s(\d*)`), +} + +func commonLineNumberExtractor(e error) (int, int) { + for _, handler := range lineNumberExtractors { + lno, col := handler(e) + if lno > 0 { + return lno, col + } + } + return 0, 0 +} + +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/herrors/line_number_extractors_test.go b/common/herrors/line_number_extractors_test.go new file mode 100644 index 0000000..482c0dd --- /dev/null +++ b/common/herrors/line_number_extractors_test.go @@ -0,0 +1,31 @@ +// Copyright 2025 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package herrors + +import ( + "errors" + "testing" + + qt "github.com/frankban/quicktest" +) + +func TestCommonLineNumberExtractor(t *testing.T) { + t.Parallel() + + c := qt.New(t) + + lno, col := commonLineNumberExtractor(errors.New("[4:9] value is not allowed in this context")) + c.Assert(lno, qt.Equals, 4) + c.Assert(col, qt.Equals, 9) +} diff --git a/common/hexec/esmloader.go b/common/hexec/esmloader.go new file mode 100644 index 0000000..2c643b7 --- /dev/null +++ b/common/hexec/esmloader.go @@ -0,0 +1,30 @@ +// Copyright 2026 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package hexec + +import ( + _ "embed" + "encoding/base64" + "sync" +) + +//go:embed esmloader.mjs +var esmLoaderSource string + +// nodeESMLoaderImportArg returns a "--import=data:..." argument that installs +// a Node.js ESM resolver hook making NODE_PATH a fallback for failed bare +// imports. See esmloader.mjs for the rationale. +var nodeESMLoaderImportArg = sync.OnceValue(func() string { + return "--import=data:text/javascript;base64," + base64.StdEncoding.EncodeToString([]byte(esmLoaderSource)) +}) diff --git a/common/hexec/esmloader.mjs b/common/hexec/esmloader.mjs new file mode 100644 index 0000000..725a434 --- /dev/null +++ b/common/hexec/esmloader.mjs @@ -0,0 +1,61 @@ +// Node.js ESM resolver hook installed by Hugo. +// +// Node's ESM resolver does not consult NODE_PATH, unlike CJS require(). +// That breaks postcss.config.js / babel.config.js / etc. files written in +// ESM and loaded from outside the project tree (typically the Hugo module +// cache): bare imports like `import x from "postcss-import"` cannot be +// resolved by walking up from the file's location. +// +// This hook makes the ESM resolver fall back to NODE_PATH for bare +// specifiers when Node's normal resolution fails. It is a no-op for +// relative/absolute paths and URL-scheme specifiers, and it never fires +// unless Node would itself have thrown ERR_MODULE_NOT_FOUND or +// ERR_ACCESS_DENIED. +// +// ERR_ACCESS_DENIED is handled because Node's resolver walks up the +// directory tree looking for node_modules. Under the permission model that +// walk can hit a node_modules outside the allow-list (e.g. Netlify stores +// its node_modules cache in the same tree as the Hugo file cache), aborting +// resolution even though the package is reachable via NODE_PATH. If the +// NODE_PATH fallback also fails we re-throw the original error so the +// access-denied resource is still reported. +// +// Uses the synchronous registerHooks API so it runs on the main thread and +// does not require --allow-worker under the Node permission model. + +import { registerHooks, createRequire } from 'node:module'; +import { pathToFileURL } from 'node:url'; + +const resolvers = []; +const np = process.env.NODE_PATH; +if (np) { + const sep = process.platform === 'win32' ? ';' : ':'; + for (const p of np.split(sep)) { + if (p) resolvers.push(createRequire(p + '/_')); + } +} + +function isBareSpecifier(s) { + if (!s) return false; + if (s.startsWith('.') || s.startsWith('/') || s.startsWith('#')) return false; + if (/^[a-z][a-z0-9+.-]*:/i.test(s)) return false; + return true; +} + +registerHooks({ + resolve(specifier, context, nextResolve) { + try { + return nextResolve(specifier, context); + } catch (err) { + if (err?.code !== 'ERR_MODULE_NOT_FOUND' && err?.code !== 'ERR_ACCESS_DENIED') throw err; + if (!isBareSpecifier(specifier)) throw err; + for (const r of resolvers) { + try { + const resolved = r.resolve(specifier); + return { url: pathToFileURL(resolved).href, shortCircuit: true, format: null }; + } catch (_) { /* try next */ } + } + throw err; + } + }, +}); diff --git a/common/hexec/exec.go b/common/hexec/exec.go new file mode 100644 index 0000000..1da5433 --- /dev/null +++ b/common/hexec/exec.go @@ -0,0 +1,557 @@ +// Copyright 2026 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package hexec + +import ( + "bufio" + "bytes" + "context" + "errors" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "regexp" + "slices" + "strings" + + "github.com/bep/logg" + "github.com/gohugoio/hugo/common/hmaps" + "github.com/gohugoio/hugo/common/loggers" + "github.com/gohugoio/hugo/config" + "github.com/gohugoio/hugo/config/security" +) + +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, + nodeRunnerCache: hmaps.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 + + // Additional absolute paths to allow reading from in the Node.js permission model. + nodeReadPaths []string + + nodeRunnerCache *hmaps.Cache[string, func(arg ...any) (Runner, error)] +} + +// SetNodeReadPaths sets additional absolute paths to allow reading from +// in the Node.js permission model (e.g. Hugo module cache directories). +func (e *Exec) SetNodeReadPaths(paths []string) { + e.nodeReadPaths = paths +} + +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 binaryLocationPath: + return "PATH" + } + return "unknown" +} + +const ( + binaryLocationNodeModules binaryLocation = iota + 1 + binaryLocationPath +) + +// Npx finds and runs a Node.js tool. The binary is located first in +// WORKINGDIR/node_modules/.bin, then in PATH. The tool is always invoked via +// "node [--permission ] + {{- else }} + {{- with . | fingerprint }} + + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/create/skeletons/theme/layouts/_partials/header.html b/create/skeletons/theme/layouts/_partials/header.html new file mode 100644 index 0000000..7980a00 --- /dev/null +++ b/create/skeletons/theme/layouts/_partials/header.html @@ -0,0 +1,2 @@ +

{{ site.Title }}

+{{ partial "menu.html" (dict "menuID" "main" "page" .) }} diff --git a/create/skeletons/theme/layouts/_partials/menu.html b/create/skeletons/theme/layouts/_partials/menu.html new file mode 100644 index 0000000..14245b5 --- /dev/null +++ b/create/skeletons/theme/layouts/_partials/menu.html @@ -0,0 +1,51 @@ +{{- /* +Renders a menu for the given menu ID. + +@context {page} page The current page. +@context {string} menuID The menu ID. + +@example: {{ partial "menu.html" (dict "menuID" "main" "page" .) }} +*/}} + +{{- $page := .page }} +{{- $menuID := .menuID }} + +{{- with index site.Menus $menuID }} + +{{- end }} + +{{- define "_partials/inline/menu/walk.html" }} + {{- $page := .page }} + {{- range .menuEntries }} + {{- $attrs := dict "href" .URL }} + {{- if $page.IsMenuCurrent .Menu . }} + {{- $attrs = merge $attrs (dict "class" "active" "aria-current" "page") }} + {{- else if $page.HasMenuCurrent .Menu .}} + {{- $attrs = merge $attrs (dict "class" "ancestor" "aria-current" "true") }} + {{- end }} + {{- $name := .Name }} + {{- with .Identifier }} + {{- with T . }} + {{- $name = . }} + {{- end }} + {{- end }} +
  • + {{ $name }} + {{- with .Children }} +
      + {{- partial "inline/menu/walk.html" (dict "page" $page "menuEntries" .) }} +
    + {{- end }} +
  • + {{- end }} +{{- end }} diff --git a/create/skeletons/theme/layouts/_partials/terms.html b/create/skeletons/theme/layouts/_partials/terms.html new file mode 100644 index 0000000..8a6ebec --- /dev/null +++ b/create/skeletons/theme/layouts/_partials/terms.html @@ -0,0 +1,23 @@ +{{- /* +For a given taxonomy, renders a list of terms assigned to the page. + +@context {page} page The current page. +@context {string} taxonomy The taxonomy. + +@example: {{ partial "terms.html" (dict "taxonomy" "tags" "page" .) }} +*/}} + +{{- $page := .page }} +{{- $taxonomy := .taxonomy }} + +{{- with $page.GetTerms $taxonomy }} + {{- $label := (index . 0).Parent.LinkTitle }} +
    +
    {{ $label }}:
    + +
    +{{- end }} diff --git a/create/skeletons/theme/layouts/baseof.html b/create/skeletons/theme/layouts/baseof.html new file mode 100644 index 0000000..7d17aa5 --- /dev/null +++ b/create/skeletons/theme/layouts/baseof.html @@ -0,0 +1,17 @@ + + + + {{ partial "head.html" . }} + + +
    + {{ partial "header.html" . }} +
    +
    + {{ block "main" . }}{{ end }} +
    +
    + {{ partial "footer.html" . }} +
    + + diff --git a/create/skeletons/theme/layouts/home.html b/create/skeletons/theme/layouts/home.html new file mode 100644 index 0000000..0c76425 --- /dev/null +++ b/create/skeletons/theme/layouts/home.html @@ -0,0 +1,9 @@ +{{ define "main" }} + {{ .Content }} + {{ range site.RegularPages }} +
    +

    {{ .LinkTitle }}

    + {{ .Summary }} +
    + {{ end }} +{{ end }} diff --git a/create/skeletons/theme/layouts/page.html b/create/skeletons/theme/layouts/page.html new file mode 100644 index 0000000..7e286c8 --- /dev/null +++ b/create/skeletons/theme/layouts/page.html @@ -0,0 +1,10 @@ +{{ define "main" }} +

    {{ .Title }}

    + + {{ $dateMachine := .Date | time.Format "2006-01-02T15:04:05-07:00" }} + {{ $dateHuman := .Date | time.Format ":date_long" }} + + + {{ .Content }} + {{ partial "terms.html" (dict "taxonomy" "tags" "page" .) }} +{{ end }} diff --git a/create/skeletons/theme/layouts/section.html b/create/skeletons/theme/layouts/section.html new file mode 100644 index 0000000..748f2f5 --- /dev/null +++ b/create/skeletons/theme/layouts/section.html @@ -0,0 +1,10 @@ +{{ define "main" }} +

    {{ .Title }}

    + {{ .Content }} + {{ range .Pages }} +
    +

    {{ .LinkTitle }}

    + {{ .Summary }} +
    + {{ end }} +{{ end }} diff --git a/create/skeletons/theme/layouts/taxonomy.html b/create/skeletons/theme/layouts/taxonomy.html new file mode 100644 index 0000000..c2e7875 --- /dev/null +++ b/create/skeletons/theme/layouts/taxonomy.html @@ -0,0 +1,7 @@ +{{ define "main" }} +

    {{ .Title }}

    + {{ .Content }} + {{ range .Pages }} +

    {{ .LinkTitle }}

    + {{ end }} +{{ end }} diff --git a/create/skeletons/theme/layouts/term.html b/create/skeletons/theme/layouts/term.html new file mode 100644 index 0000000..c2e7875 --- /dev/null +++ b/create/skeletons/theme/layouts/term.html @@ -0,0 +1,7 @@ +{{ define "main" }} +

    {{ .Title }}

    + {{ .Content }} + {{ range .Pages }} +

    {{ .LinkTitle }}

    + {{ end }} +{{ end }} diff --git a/create/skeletons/theme/static/favicon.ico b/create/skeletons/theme/static/favicon.ico new file mode 100644 index 0000000..67f8b77 Binary files /dev/null and b/create/skeletons/theme/static/favicon.ico differ diff --git a/deploy/cloudfront.go b/deploy/cloudfront.go new file mode 100644 index 0000000..3202a73 --- /dev/null +++ b/deploy/cloudfront.go @@ -0,0 +1,72 @@ +// 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 new file mode 100644 index 0000000..57e1f41 --- /dev/null +++ b/deploy/deploy.go @@ -0,0 +1,763 @@ +// 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 new file mode 100644 index 0000000..b1ce735 --- /dev/null +++ b/deploy/deploy_azure.go @@ -0,0 +1,21 @@ +// 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 new file mode 100644 index 0000000..bdc8299 --- /dev/null +++ b/deploy/deploy_test.go @@ -0,0 +1,1102 @@ +// 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 new file mode 100644 index 0000000..94dbc0c --- /dev/null +++ b/deploy/deployconfig/deployConfig.go @@ -0,0 +1,179 @@ +// 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/hglob" + "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 new file mode 100644 index 0000000..38d0aad --- /dev/null +++ b/deploy/deployconfig/deployConfig_test.go @@ -0,0 +1,198 @@ +// 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 new file mode 100644 index 0000000..5b302e9 --- /dev/null +++ b/deploy/google.go @@ -0,0 +1,39 @@ +// 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 new file mode 100644 index 0000000..8e7780c --- /dev/null +++ b/deps/deps.go @@ -0,0 +1,542 @@ +// Copyright 2025 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package deps + +import ( + "context" + "fmt" + "io" + "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/hmaps" + "github.com/gohugoio/hugo/common/loggers" + "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/tpl" + "github.com/spf13/afero" +) + +// Deps holds dependencies used by many. +// There will be normally 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:"-"` + + ExecHelper *hexec.Exec + + // The file systems to use. + Fs *hugofs.Fs `json:"-"` + + // The PathSpec to use + *helpers.PathSpec `json:"-"` + + // 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 + + // The translation func to use + Translate func(ctx context.Context, translationID string, templateData any) string `json:"-"` + + // The site building. + Site page.Site + + TemplateStore *tplimpl.TemplateStore + + // Used in tests + OverloadedTemplateFuncs map[string]any + + 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: hmaps.NewCache[string, *tpl.DeferredExecution](), + FilenamesWithPostPrefix: hmaps.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, nil) + if err != nil { + return err + } + d.PathSpec = pathSpec + } else { + var err error + d.PathSpec, err = helpers.NewPathSpec(d.Fs, d.Conf, d.Log, d.PathSpec.BaseFs) + if err != nil { + return err + } + } + + d.ExecHelper.SetNodeReadPaths(d.BaseFs.Assets.RealPaths("")) + + 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 + } + + d.Cfg.BaseConfig() + + fileCaches := d.Cfg.FileCaches().(filecache.Caches) + fileCaches.SetResourceFs(d.BaseFs.ResourcesCache) + + resourceSpec, err := resources.NewSpec(d.PathSpec, common, d.WasmDispatchers, 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 +} + +// ResourceProvider is used to create and refresh, and clone resources needed. +type ResourceProvider interface { + NewResource(dst *Deps) error + CloneResource(dst, src *Deps) error +} + +func (d *Deps) Close() error { + if d.isClosed { + return nil + } + d.isClosed = true + + if d.MemCache != nil { + d.MemCache.Stop() + } + if d.WasmDispatchers != nil { + d.WasmDispatchers.Close() + } + return d.BuildClosers.Close() +} + +// 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 file systems to use + Fs *hugofs.Fs + + // The Site in use + Site page.Site + + Configs *allconfig.Configs + + // Template handling. + TemplateProvider ResourceProvider + + // i18n handling. + TranslationProvider ResourceProvider + + // Build triggered by the IntegrationTest framework. + IsIntegrationTest bool + + // TestCfg holds configuration used only in tests. + // It is a programming error to set this when IsIntegrationTest is not set, + // and doing so will panic. + TestCfg TestConfig + + // ChangesFromBuild for changes passed back to the server/watch process. + ChangesFromBuild chan []identity.Identity +} + +// TestConfig holds configuration used only in tests. +// See DepsCfg.TestCfg. +type TestConfig struct { + // WarpcMemory, if set, overrides the memory limit in MiB for the WASM based + // image processors (WebP and AVIF). Used to provoke memory allocation failures. + WarpcMemory int +} + +// IsZero reports whether c holds no test configuration. +func (c TestConfig) IsZero() bool { + return c == TestConfig{} +} + +// BuildState are state used during a build. +type BuildState struct { + counter uint64 + + // Tracks invocations of the Build method. + BuildCounter atomic.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 *hmaps.Cache[string, bool] + + // Maps a placeholder to a deferred execution. + Executions *hmaps.Cache[string, *tpl.DeferredExecution] +} + +var _ identity.SignalRebuilder = (*BuildState)(nil) + +// IsRebuild reports whether this is a rebuild. +func (b *BuildState) IsRebuild() bool { + return b.BuildCounter.Load() > 0 +} + +// 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: hmaps.NewCache[string, *tpl.DeferredExecution](), + FilenamesWithPostPrefix: hmaps.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 new file mode 100644 index 0000000..e92ed23 --- /dev/null +++ b/deps/deps_test.go @@ -0,0 +1,31 @@ +// 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 new file mode 100644 index 0000000..eaa1c65 --- /dev/null +++ b/docs/.codespellrc @@ -0,0 +1,13 @@ +# Config file for codespell. +# https://github.com/codespell-project/codespell#using-a-config-file + +[codespell] + +# Comma separated list of dirs to be skipped. +skip = *.ai,chroma.css,chroma_dark.css,.cspell.json,./data/docs.yaml + +# Comma separated list of words to be ignored. Words must be lowercased. +ignore-words-list = abl,edn,ist,januar,te,trys,ue,womens + +# Check file names as well. +check-filenames = true diff --git a/docs/.cspell.json b/docs/.cspell.json new file mode 100644 index 0000000..36636b3 --- /dev/null +++ b/docs/.cspell.json @@ -0,0 +1,204 @@ +{ + "version": "0.2", + "allowCompoundWords": true, + "overrides": [ + { + "filename": "**/*", + "enabled": false + }, + { + "filename": "**/*.md", + "enabled": true + } + ], + "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", + "multiplatform", + "performantly", + "preconfigured", + "prerendering", + "redirection", + "redirections", + "slugified", + "slugify", + "subexpression", + "suppressible", + "synchronisation", + "templating", + "transpile", + "unmarshal", + "unmarshaled", + "unmarshaling", + "unmarshals", + // ------------------------------------------------------------------------ + // cspell: ignore hugo terminology + // ------------------------------------------------------------------------ + "alignx", + "aligny", + "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 + // ------------------------------------------------------------------------ + "Bokmål", + "Norsk", + "bezpieczeństwo", + "blatt", + "buch", + "descripción", + "dokumentation", + "erklärungen", + "español", + "français", + "libros", + "mercredi", + "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", + "Pontmercy", + "Samsa", + "Stucki", + "Thénardier", + "Vitter", + "WASI", + // ------------------------------------------------------------------------ + // cspell: ignore operating systems and software packages + // ------------------------------------------------------------------------ + "ananke", + "asciidoctor", + "brotli", + "cifs", + "corejs", + "disqus", + "docutils", + "dpkg", + "doas", + "eopkg", + "forgejo", + "gitee", + "goldmark", + "katex", + "kubuntu", + "lubuntu", + "mathjax", + "nosql", + "pandoc", + "pkgin", + "rclone", + "xubuntu", + // ------------------------------------------------------------------------ + // cspell: ignore miscellaneous + // ------------------------------------------------------------------------ + "achristie", + "ccpa", + "cpra", + "ddmaurier", + "dring", + "fleqn", + "inor", + "iptc", + "jausten", + "jdoe", + "jsmith", + "leqno", + "milli", + "monokai", + "mysanityprojectid", + "rgba", + "rsmith", + "tdewolff", + "tjones", + "vcard", + "wcag", + "xfeff" + ] +} diff --git a/docs/.editorconfig b/docs/.editorconfig new file mode 100644 index 0000000..dd2a009 --- /dev/null +++ b/docs/.editorconfig @@ -0,0 +1,20 @@ +# 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 new file mode 100644 index 0000000..3ba13e0 --- /dev/null +++ b/docs/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/docs/.github/ISSUE_TEMPLATE/default.md b/docs/.github/ISSUE_TEMPLATE/default.md new file mode 100644 index 0000000..ada35b3 --- /dev/null +++ b/docs/.github/ISSUE_TEMPLATE/default.md @@ -0,0 +1,6 @@ +--- +name: Default +about: This is the default issue template. +labels: + - NeedsTriage +--- diff --git a/docs/.github/SUPPORT.md b/docs/.github/SUPPORT.md new file mode 100644 index 0000000..96a4400 --- /dev/null +++ b/docs/.github/SUPPORT.md @@ -0,0 +1,3 @@ +### 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/workflows/codeql-analysis.yml b/docs/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..1354ffe --- /dev/null +++ b/docs/.github/workflows/codeql-analysis.yml @@ -0,0 +1,26 @@ +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@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + + - 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/lint.yml b/docs/.github/workflows/lint.yml new file mode 100644 index 0000000..00800ab --- /dev/null +++ b/docs/.github/workflows/lint.yml @@ -0,0 +1,61 @@ +name: Lint +on: + workflow_dispatch: + push: + branches: + - master + pull_request: + +permissions: + contents: read + +jobs: + markdownlint: + name: Lint Markdown + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Run Markdown linter + uses: DavidAnson/markdownlint-cli2-action@ded1f9488f68a970bc66ea5619e13e9b52e601cd # v23.2.0 + with: + globs: # set to null to override default of *.{md,markdown} + + spellcheck: + name: Check spelling + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Check spelling with cspell + uses: streetsidesoftware/cspell-action@de2a73e963e7443969755b648a1008f77033c5b2 # v8.4.0 + with: + incremental_files_only: true + strict: true + # cspell uses the .cspell.json configuration file + - name: Check spelling with codespell + uses: codespell-project/actions-codespell@8f01853be192eb0f849a5c7d721450e7a467c579 # v2.2 + with: + check_filenames: true + check_hidden: true + # codespell uses the .codespellrc file + + template-formatting: + name: Check template formatting + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Install Go + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version: "1.26" + check-latest: true + cache: true + cache-dependency-path: | + **/go.sum + **/go.mod + - name: Install gotmplfmt + run: go install github.com/gohugoio/gotmplfmt@623175f49b3d07a11da381ff85228d0d03101880 # v0.4.1 + - name: Check template formatting + run: "diff <(gotmplfmt -d layouts) <(printf '')" diff --git a/docs/.github/workflows/stale.yml b/docs/.github/workflows/stale.yml new file mode 100644 index 0000000..9513953 --- /dev/null +++ b/docs/.github/workflows/stale.yml @@ -0,0 +1,32 @@ +name: Close stale issues and pull requests +on: + workflow_dispatch: + schedule: + - cron: "30 1 * * *" +permissions: + contents: read +jobs: + stale: + permissions: + contents: read + issues: write + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0 + with: + days-before-stale: 90 # default is 60 + days-before-close: 14 # default is 7 + exempt-all-assignees: true + exempt-draft-pr: true + exempt-issue-labels: Keep, InProgress, NeedsTriage + exempt-pr-labels: Keep + operations-per-run: 100 + stale-issue-message: > + This issue has been marked as stale because there hasn't been any + recent activity. It will be closed soon if there are no further + updates. + stale-pr-message: > + This pull request has been marked as stale because there hasn't + been any recent activity. It will be closed soon if there are no + further updates. diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..a178881 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,13 @@ +.DS_Store +.hugo_build.lock +.hvm +/.idea +/.vscode +/dist +/public +/resources +hugo_stats.json +node_modules/ +nohup.out +package-lock.json +trace.out diff --git a/docs/.markdownlint-cli2.yaml b/docs/.markdownlint-cli2.yaml new file mode 100644 index 0000000..68c28a3 --- /dev/null +++ b/docs/.markdownlint-cli2.yaml @@ -0,0 +1,94 @@ +# Glob patterns to include +globs: + - "content/**/*.md" + +# Glob patterns to exclude +ignores: + - "content/**/commands/**" + - "content/en/about/license.md" + - "content/LICENSE.md" + +# Markdownlint rules and configuration +# https://github.com/DavidAnson/markdownlint?tab=readme-ov-file#rules--aliases +config: + MD001: true + # MD002 deprecated + MD003: + style: atx + MD004: + style: dash + MD005: true + # MD006 deprecated + MD007: false # if enabled, throws errors when definition descriptions contain list items + # MD008 deprecated + MD009: true + MD010: true + MD011: true + MD012: true + MD013: false + MD014: true + # MD015 deprecated + # MD016 deprecated + # MD017 deprecated + MD018: true + MD019: true + MD020: true + MD021: true + MD022: true + MD023: true + MD024: true + MD025: true + MD026: true + MD027: true + MD028: false + MD029: + style: one + MD030: true + MD031: true + MD032: true + MD033: true + MD034: false + MD035: + style: --- + MD036: true + MD037: true + MD038: true + MD039: true + MD040: true + MD041: false + MD042: true + MD043: false + MD044: false + MD045: true + MD046: false + MD047: true + MD048: + style: backtick + MD049: + style: underscore + MD050: + style: asterisk + MD051: false + MD052: true + MD053: true + MD054: + autolink: true + collapsed: true + full: true + inline: true + shortcut: true + url_inline: false + MD055: + style: consistent + MD056: false # interferes with Markdown attributes + # MD057 deprecated + MD058: true + MD059: + prohibited_texts: + - click here + - here + - link + - more + MD060: + aligned_delimiter: false + style: any diff --git a/docs/.prettierignore b/docs/.prettierignore new file mode 100644 index 0000000..bd712be --- /dev/null +++ b/docs/.prettierignore @@ -0,0 +1,3 @@ +# Auto generated. +assets/css/components/chroma*.css +assets/jsconfig.json diff --git a/docs/.prettierrc b/docs/.prettierrc new file mode 100644 index 0000000..11c748a --- /dev/null +++ b/docs/.prettierrc @@ -0,0 +1,12 @@ +{ + "overrides": [ + { + "files": ["*.js", "*.ts"], + "options": { + "useTabs": true, + "printWidth": 120, + "singleQuote": true + } + } + ] +} diff --git a/docs/AGENTS.md b/docs/AGENTS.md new file mode 100644 index 0000000..7524fd4 --- /dev/null +++ b/docs/AGENTS.md @@ -0,0 +1,25 @@ +## Theme + +### Role +You are an expert front-end and Tailwind CSS developer. Your primary goal is to build reliable, readable, and scalable user interfaces using the existing project structure and design system. + +### Directives +* **ALWAYS** use existing Tailwind utility classes for styling. +* **NEVER** write raw CSS in `.css` files unless absolutely necessary for a non-utility-based global reset. +* **NEVER** use inline styles (e.g., `
    `). + +### Project Knowledge + +* **Tech Stack:** Hugo (static site generator), AlpineJS, Tailwind CSS. Icons from https://heroicons.com/. +* **Core Files and Directories:** + - `hugo.toml`: Hugo configuration file. + - `assets/css/styles.css`: Main CSS file with Tailwind directives. + - `assets/js/main.js`: Main JavaScript with AlpineJS configuration. + - `package.json`: Project dependencies and scripts. + - `content/`: Directory for markdown content files. + - `layouts/`: Directory for Hugo templates and partials. + - `layouts/_partials/icons.html`: SVG icon sprites. + + + + diff --git a/docs/CLAUDE.md b/docs/CLAUDE.md new file mode 100644 index 0000000..eef4bd2 --- /dev/null +++ b/docs/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md \ No newline at end of file diff --git a/docs/LICENSE.md b/docs/LICENSE.md new file mode 100644 index 0000000..d4facbf --- /dev/null +++ b/docs/LICENSE.md @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000..06b952d --- /dev/null +++ b/docs/README.md @@ -0,0 +1,25 @@ +Hugo + +A fast and flexible static site generator built with love by [bep], [spf13], and [friends] in [Go]. + +--- + +[![Netlify Status](https://api.netlify.com/api/v1/badges/e0dbbfc7-34f1-4393-a679-c16e80162705/deploy-status)](https://app.netlify.com/sites/gohugoio/deploys) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://gohugo.io/contribute/documentation/) + +This is the repository for the [Hugo](https://github.com/gohugoio/hugo) documentation site. + +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 +``` diff --git a/docs/archetypes/default.md b/docs/archetypes/default.md new file mode 100644 index 0000000..58a60ed --- /dev/null +++ b/docs/archetypes/default.md @@ -0,0 +1,6 @@ +--- +title: {{ replace .File.ContentBaseName "-" " " | strings.FirstUpper }} +description: +categories: [] +keywords: [] +--- diff --git a/docs/archetypes/functions.md b/docs/archetypes/functions.md new file mode 100644 index 0000000..de2d720 --- /dev/null +++ b/docs/archetypes/functions.md @@ -0,0 +1,11 @@ +--- +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 new file mode 100644 index 0000000..ea3c3b8 --- /dev/null +++ b/docs/archetypes/glossary.md @@ -0,0 +1,13 @@ +--- +title: {{ replace .File.ContentBaseName "-" " " }} +params: + reference: +--- + + diff --git a/docs/archetypes/methods.md b/docs/archetypes/methods.md new file mode 100644 index 0000000..944fe52 --- /dev/null +++ b/docs/archetypes/methods.md @@ -0,0 +1,10 @@ +--- +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 new file mode 100644 index 0000000..04792a1 --- /dev/null +++ b/docs/archetypes/news.md @@ -0,0 +1,7 @@ +--- +title: {{ replace .File.ContentBaseName "-" " " | strings.FirstUpper }} +description: +categories: [] +keywords: [] +publishDate: {{ .Date }} +--- diff --git a/docs/assets/css/components/all.css b/docs/assets/css/components/all.css new file mode 100644 index 0000000..62c2b2a --- /dev/null +++ b/docs/assets/css/components/all.css @@ -0,0 +1,8 @@ +/* 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"; +@import "./todo-lists.css"; diff --git a/docs/assets/css/components/chroma.css b/docs/assets/css/components/chroma.css new file mode 100644 index 0000000..9d4c91f --- /dev/null +++ b/docs/assets/css/components/chroma.css @@ -0,0 +1,85 @@ +/* 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 new file mode 100644 index 0000000..0b0ae30 --- /dev/null +++ b/docs/assets/css/components/chroma_dark.css @@ -0,0 +1,85 @@ +/* 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 new file mode 100644 index 0000000..7bd5c62 --- /dev/null +++ b/docs/assets/css/components/content.css @@ -0,0 +1,49 @@ +@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-600 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 new file mode 100644 index 0000000..06f40b4 --- /dev/null +++ b/docs/assets/css/components/fonts.css @@ -0,0 +1,15 @@ +@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 new file mode 100644 index 0000000..8eb6930 --- /dev/null +++ b/docs/assets/css/components/helpers.css @@ -0,0 +1,19 @@ +/* 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 new file mode 100644 index 0000000..5f25fe3 --- /dev/null +++ b/docs/assets/css/components/highlight.css @@ -0,0 +1,11 @@ +.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 new file mode 100644 index 0000000..7314d5b --- /dev/null +++ b/docs/assets/css/components/shortcodes.css @@ -0,0 +1,4 @@ +.shortcode-code { + .highlight { + } +} diff --git a/docs/assets/css/components/tableofcontents.css b/docs/assets/css/components/tableofcontents.css new file mode 100644 index 0000000..3640adf --- /dev/null +++ b/docs/assets/css/components/tableofcontents.css @@ -0,0 +1,14 @@ +.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/todo-lists.css b/docs/assets/css/components/todo-lists.css new file mode 100644 index 0000000..886beb4 --- /dev/null +++ b/docs/assets/css/components/todo-lists.css @@ -0,0 +1,15 @@ +ul.todo { + padding-left: 0; +} + +ul.todo li { + display: flex; + gap: 0.8em; +} + +ul.todo li input[type="checkbox"] { + width: 1em; + height: 1em; + flex-shrink: 0; + margin-top: 0.3em; +} diff --git a/docs/assets/css/components/view-transitions.css b/docs/assets/css/components/view-transitions.css new file mode 100644 index 0000000..a63e7c8 --- /dev/null +++ b/docs/assets/css/components/view-transitions.css @@ -0,0 +1,24 @@ +/* Opt in to native cross-document view transitions on navigation. */ +@view-transition { + navigation: auto; +} + +/* Global slight fade */ +::view-transition-old(root), +::view-transition-new(root) { + animation-duration: 200ms; +} + +/* + * Persistent chrome (header/footer) is named so it gets its own snapshot + * instead of being part of the root crossfade. Holding it static prevents + * the flicker that the sticky header otherwise shows on every navigation. + */ +::view-transition-group(site-header), +::view-transition-group(site-footer), +::view-transition-old(site-header), +::view-transition-new(site-header), +::view-transition-old(site-footer), +::view-transition-new(site-footer) { + animation: none; +} diff --git a/docs/assets/css/styles.css b/docs/assets/css/styles.css new file mode 100644 index 0000000..79ce316 --- /dev/null +++ b/docs/assets/css/styles.css @@ -0,0 +1,143 @@ +@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", -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", + Helvetica, Arial, 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 (min-width: theme('--breakpoint-md')) { + .px-main { + padding-left: max(env(safe-area-inset-left), 2rem); + padding-right: max(env(safe-area-inset-right), 2rem); + } +} + +@media (min-width: theme('--breakpoint-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; +} + +/* Code spans within paragraphs, tables cells, list items, etc. */ +:not(pre) > code { + white-space: nowrap; +} + +/* Utility class for tables to prevent word wrapping in the first column. */ +.no-wrap-first-col td:first-child, +.no-wrap-first-col th:first-child { + white-space: nowrap; +} diff --git a/docs/assets/images/examples/landscape-exif-orientation-5.jpg b/docs/assets/images/examples/landscape-exif-orientation-5.jpg new file mode 100644 index 0000000..ad64835 Binary files /dev/null and b/docs/assets/images/examples/landscape-exif-orientation-5.jpg differ diff --git a/docs/assets/images/examples/mask.png b/docs/assets/images/examples/mask.png new file mode 100644 index 0000000..c3005a6 Binary files /dev/null and b/docs/assets/images/examples/mask.png differ diff --git a/docs/assets/images/examples/zion-national-park.jpg b/docs/assets/images/examples/zion-national-park.jpg new file mode 100644 index 0000000..7980abc Binary files /dev/null and b/docs/assets/images/examples/zion-national-park.jpg differ diff --git a/docs/assets/images/hugo-github-screenshot.png b/docs/assets/images/hugo-github-screenshot.png new file mode 100644 index 0000000..275b696 Binary files /dev/null and b/docs/assets/images/hugo-github-screenshot.png differ diff --git a/docs/assets/images/logos/logo-128x128.png b/docs/assets/images/logos/logo-128x128.png new file mode 100644 index 0000000..ec1a2d6 Binary files /dev/null and b/docs/assets/images/logos/logo-128x128.png differ diff --git a/docs/assets/images/logos/logo-256x256.png b/docs/assets/images/logos/logo-256x256.png new file mode 100644 index 0000000..d9fdb88 Binary files /dev/null and b/docs/assets/images/logos/logo-256x256.png differ diff --git a/docs/assets/images/logos/logo-512x512.png b/docs/assets/images/logos/logo-512x512.png new file mode 100644 index 0000000..76d4636 Binary files /dev/null and b/docs/assets/images/logos/logo-512x512.png differ diff --git a/docs/assets/images/logos/logo-64x64.png b/docs/assets/images/logos/logo-64x64.png new file mode 100644 index 0000000..9857bce Binary files /dev/null and b/docs/assets/images/logos/logo-64x64.png differ diff --git a/docs/assets/images/logos/logo-96x96.png b/docs/assets/images/logos/logo-96x96.png new file mode 100644 index 0000000..48d0cb9 Binary files /dev/null and b/docs/assets/images/logos/logo-96x96.png differ diff --git a/docs/assets/images/sponsors/Route4MeLogoBlueOnWhite.svg b/docs/assets/images/sponsors/Route4MeLogoBlueOnWhite.svg new file mode 100644 index 0000000..d4334e8 --- /dev/null +++ b/docs/assets/images/sponsors/Route4MeLogoBlueOnWhite.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/docs/assets/images/sponsors/bep-consulting.svg b/docs/assets/images/sponsors/bep-consulting.svg new file mode 100644 index 0000000..598a1eb --- /dev/null +++ b/docs/assets/images/sponsors/bep-consulting.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/assets/images/sponsors/butter-dark.svg b/docs/assets/images/sponsors/butter-dark.svg new file mode 100644 index 0000000..657b75c --- /dev/null +++ b/docs/assets/images/sponsors/butter-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/assets/images/sponsors/butter-light.svg b/docs/assets/images/sponsors/butter-light.svg new file mode 100644 index 0000000..a0697df --- /dev/null +++ b/docs/assets/images/sponsors/butter-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/assets/images/sponsors/cloudcannon-cms-logo.svg b/docs/assets/images/sponsors/cloudcannon-cms-logo.svg new file mode 100644 index 0000000..9860c59 --- /dev/null +++ b/docs/assets/images/sponsors/cloudcannon-cms-logo.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/docs/assets/images/sponsors/esolia-logo.svg b/docs/assets/images/sponsors/esolia-logo.svg new file mode 100644 index 0000000..3f5344c --- /dev/null +++ b/docs/assets/images/sponsors/esolia-logo.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/images/sponsors/goland.svg b/docs/assets/images/sponsors/goland.svg new file mode 100644 index 0000000..c32f25d --- /dev/null +++ b/docs/assets/images/sponsors/goland.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/images/sponsors/graitykit-dark.svg b/docs/assets/images/sponsors/graitykit-dark.svg new file mode 100644 index 0000000..fd7d12f --- /dev/null +++ b/docs/assets/images/sponsors/graitykit-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/assets/images/sponsors/linode-logo.svg b/docs/assets/images/sponsors/linode-logo.svg new file mode 100644 index 0000000..8736783 --- /dev/null +++ b/docs/assets/images/sponsors/linode-logo.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/images/sponsors/linode-logo_standard_light_medium.png b/docs/assets/images/sponsors/linode-logo_standard_light_medium.png new file mode 100644 index 0000000..269e6af Binary files /dev/null and b/docs/assets/images/sponsors/linode-logo_standard_light_medium.png differ diff --git a/docs/assets/images/sponsors/logo-pinme.svg b/docs/assets/images/sponsors/logo-pinme.svg new file mode 100644 index 0000000..fc807be --- /dev/null +++ b/docs/assets/images/sponsors/logo-pinme.svg @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/images/sponsors/your-company-dark.svg b/docs/assets/images/sponsors/your-company-dark.svg new file mode 100644 index 0000000..58fd601 --- /dev/null +++ b/docs/assets/images/sponsors/your-company-dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/assets/images/sponsors/your-company.svg b/docs/assets/images/sponsors/your-company.svg new file mode 100644 index 0000000..3b85ece --- /dev/null +++ b/docs/assets/images/sponsors/your-company.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/assets/js/alpinejs/data/index.js b/docs/assets/js/alpinejs/data/index.js new file mode 100644 index 0000000..7bf0532 --- /dev/null +++ b/docs/assets/js/alpinejs/data/index.js @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000..1075f3f --- /dev/null +++ b/docs/assets/js/alpinejs/data/navbar.js @@ -0,0 +1,28 @@ +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 new file mode 100644 index 0000000..4ad2e44 --- /dev/null +++ b/docs/assets/js/alpinejs/data/search.js @@ -0,0 +1,124 @@ +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 adjustHits = (array, isServer) => { + 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}`; + }; + + if (isServer) { + // Trim https://gohugo.io from the url to make it work locally. + item.url = item.url.replace('https://gohugo.io', ''); + } + 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(adjustHits(data.results[0].hits, cfg.params.isServer)); + 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 new file mode 100644 index 0000000..233f877 --- /dev/null +++ b/docs/assets/js/alpinejs/data/toc.js @@ -0,0 +1,71 @@ +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 new file mode 100644 index 0000000..de9fa24 --- /dev/null +++ b/docs/assets/js/alpinejs/magics/helpers.js @@ -0,0 +1,36 @@ +'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 new file mode 100644 index 0000000..c5f595c --- /dev/null +++ b/docs/assets/js/alpinejs/magics/index.js @@ -0,0 +1 @@ +export * from './helpers'; diff --git a/docs/assets/js/alpinejs/stores/index.js b/docs/assets/js/alpinejs/stores/index.js new file mode 100644 index 0000000..17e2a34 --- /dev/null +++ b/docs/assets/js/alpinejs/stores/index.js @@ -0,0 +1 @@ +export * from './nav.js'; diff --git a/docs/assets/js/alpinejs/stores/nav.js b/docs/assets/js/alpinejs/stores/nav.js new file mode 100644 index 0000000..1dad17c --- /dev/null +++ b/docs/assets/js/alpinejs/stores/nav.js @@ -0,0 +1,104 @@ +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, + }, + + mobileMenu: { + open: false, + toggle() { + this.open = !this.open; + }, + close() { + this.open = false; + }, + }, + + 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/head-early.js b/docs/assets/js/head-early.js new file mode 100644 index 0000000..b21eb58 --- /dev/null +++ b/docs/assets/js/head-early.js @@ -0,0 +1,20 @@ +import { scrollToActive } from 'js/helpers/index'; +import { initColorScheme } from './alpinejs/stores/index'; + +(function () { + // This allows us to initialize the color scheme before AlpineJS etc. is loaded. + initColorScheme(); + + // 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/helpers.js b/docs/assets/js/helpers/helpers.js new file mode 100644 index 0000000..6d721f2 --- /dev/null +++ b/docs/assets/js/helpers/helpers.js @@ -0,0 +1,17 @@ +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-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 new file mode 100644 index 0000000..203a954 --- /dev/null +++ b/docs/assets/js/helpers/index.js @@ -0,0 +1,2 @@ +export * from './helpers'; +export * from './lrucache'; diff --git a/docs/assets/js/helpers/lrucache.js b/docs/assets/js/helpers/lrucache.js new file mode 100644 index 0000000..258848c --- /dev/null +++ b/docs/assets/js/helpers/lrucache.js @@ -0,0 +1,19 @@ +// 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 new file mode 100644 index 0000000..96e8ed1 --- /dev/null +++ b/docs/assets/js/main.js @@ -0,0 +1,57 @@ +import Alpine from 'alpinejs'; +import { registerMagics } from './alpinejs/magics/index'; +import { navbar, search, toc } from './alpinejs/data/index'; +import { navStore } from './alpinejs/stores/index'; +import persist from '@alpinejs/persist'; +import focus from '@alpinejs/focus'; +import * as params from '@params'; + +var debug = 0 ? console.log.bind(console, '[index]') : function () {}; + +// 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', + params: params, + }; + + 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(); + + // On cross-document navigation the browser snapshots the current page for + // the view transition. An open overlay (e.g. the search modal) would + // otherwise linger in that outgoing snapshot while the page crossfades. + // `pageswap` runs right before the snapshot is taken, so hide such + // elements here to make them disappear instantly on navigation. + window.addEventListener('pageswap', () => { + document.querySelectorAll('[data-hide-on-navigate]').forEach((el) => { + el.classList.add('hidden'); + }); + }); +})(); diff --git a/docs/assets/jsconfig.json b/docs/assets/jsconfig.json new file mode 100644 index 0000000..8f9413a --- /dev/null +++ b/docs/assets/jsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "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 new file mode 100644 index 0000000..6555584 Binary files /dev/null and b/docs/assets/opengraph/gohugoio-card-base-1.png differ diff --git a/docs/assets/opengraph/mulish-black.ttf b/docs/assets/opengraph/mulish-black.ttf new file mode 100644 index 0000000..db680a0 Binary files /dev/null and b/docs/assets/opengraph/mulish-black.ttf differ diff --git a/docs/content/LICENSE.md b/docs/content/LICENSE.md new file mode 100644 index 0000000..b09cd78 --- /dev/null +++ b/docs/content/LICENSE.md @@ -0,0 +1,201 @@ +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/en/_common/_index.md b/docs/content/en/_common/_index.md new file mode 100644 index 0000000..612165e --- /dev/null +++ b/docs/content/en/_common/_index.md @@ -0,0 +1,13 @@ +--- +cascade: + build: + list: never + publishResources: false + render: never +--- + + diff --git a/docs/content/en/_common/configuration/locale.md b/docs/content/en/_common/configuration/locale.md new file mode 100644 index 0000000..32ad89a --- /dev/null +++ b/docs/content/en/_common/configuration/locale.md @@ -0,0 +1,22 @@ +--- +_comment: Do not remove front matter. +--- + +`locale` +: (`string`) The language tag as described in [RFC 5646][]. This is the primary value used by the [`language.Translate`][] function to select a translation table, and for localization of dates, currencies, numbers, and percentages, falling back to the [language key][] in both cases. + + Hugo also 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 Open Graph template][] + + Access this value from a template using the [`Language.Locale`][] method on a `Site` or `Page` object. + + [RFC 5646]: https://datatracker.ietf.org/doc/html/rfc5646#section-2.1 + [`Language.Locale`]: /methods/site/language/#locale + [`language.Translate`]: /functions/lang/translate/ + [embedded Open Graph template]: <{{% eturl opengraph %}}> + [embedded RSS template]: <{{% eturl rss %}}> + [embedded alias template]: <{{% eturl alias %}}> + [language key]: /configuration/languages/#language-keys diff --git a/docs/content/en/_common/configuration/page-matcher.md b/docs/content/en/_common/configuration/page-matcher.md new file mode 100644 index 0000000..807bad1 --- /dev/null +++ b/docs/content/en/_common/configuration/page-matcher.md @@ -0,0 +1,22 @@ +--- +_comment: Do not remove front matter. +--- + +A _page matcher_ filters pages by logical path, page kind, environment, or site. Specify filtering criteria using any combination of the following keywords. + +`environment` +: (`string`) A [glob pattern](g) matching the build [environment](g). For example: `{staging,production}`. + +`kind` +: (`string`) A [glob pattern](g) matching the [page kind](g). For example: `{taxonomy,term}`. + +`lang` +: {{< deprecated-in 0.153.0 />}} +: Use the [`sites`](#sites) setting instead. + +`path` +: (`string`) A [glob pattern](g) matching the page's [logical path](g). For example: `{/books,/books/**}`. + +`sites` +: {{< new-in 0.153.0 />}} +: (`map`) A [sites matrix](g) matching any combination of [content dimensions](g) including language, version, and role. diff --git a/docs/content/en/_common/content-format-table.md b/docs/content/en/_common/content-format-table.md new file mode 100644 index 0000000..c0a66a1 --- /dev/null +++ b/docs/content/en/_common/content-format-table.md @@ -0,0 +1,13 @@ +--- +_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/embedded-get-page-images.md b/docs/content/en/_common/embedded-get-page-images.md new file mode 100644 index 0000000..bd178e5 --- /dev/null +++ b/docs/content/en/_common/embedded-get-page-images.md @@ -0,0 +1,7 @@ +--- +_comment: Do not remove front matter. +--- + +When the `images` front matter parameter is set, Hugo processes each value. For internal paths, it searches page resources then global resources, using the resource permalink if found or converting the path to an absolute URL if not. External URLs are used as-is. + +When `images` is not set, Hugo searches page resources for a name matching `*feature*`, falling back to `*cover*` or `*thumbnail*` if none is found. If still no image is found, Hugo uses the first entry in the site configuration's `params.images` array, if present, and processes it as described above. diff --git a/docs/content/en/_common/filter-sort-group.md b/docs/content/en/_common/filter-sort-group.md new file mode 100644 index 0000000..82ec805 --- /dev/null +++ b/docs/content/en/_common/filter-sort-group.md @@ -0,0 +1,8 @@ +--- +_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 new file mode 100644 index 0000000..556f378 --- /dev/null +++ b/docs/content/en/_common/functions/fmt/format-string.md @@ -0,0 +1,7 @@ +--- +_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 new file mode 100644 index 0000000..497c1d4 --- /dev/null +++ b/docs/content/en/_common/functions/go-html-template-package.md @@ -0,0 +1,14 @@ +--- +_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. + +[`html/template`]: https://pkg.go.dev/html/template +[`text/template`]: https://pkg.go.dev/text/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 new file mode 100644 index 0000000..c321557 --- /dev/null +++ b/docs/content/en/_common/functions/go-template/text-template.md @@ -0,0 +1,7 @@ +--- +_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/hugo/sites-collection.md b/docs/content/en/_common/functions/hugo/sites-collection.md new file mode 100644 index 0000000..9f4b81b --- /dev/null +++ b/docs/content/en/_common/functions/hugo/sites-collection.md @@ -0,0 +1,9 @@ +--- +_comment: Do not remove front matter. +--- + +The returned collection follows a hierarchical sort where each subsequent dimension acts as a tie-breaker for the one above it. + +1. [Language](g) is sorted by [weight](g) in ascending order, falling back to lexicographical order if weights are tied or undefined. +1. [Version](g) is then sorted by weight in ascending order, with Hugo defaulting to a descending semantic sort for any ties. +1. [Role](g) is finally sorted by weight in ascending order, using lexicographical order as the final fallback. diff --git a/docs/content/en/_common/functions/images/apply-image-filter.md b/docs/content/en/_common/functions/images/apply-image-filter.md new file mode 100644 index 0000000..abde78b --- /dev/null +++ b/docs/content/en/_common/functions/images/apply-image-filter.md @@ -0,0 +1,26 @@ +--- +_comment: Do not remove front matter. +--- + +Apply the filter using the [`images.Filter`][] function: + +```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: + +```go-html-template +{{ with resources.Get "images/original.jpg" }} + {{ with .Filter $filter }} + + {{ end }} +{{ end }} +``` + +[`Filter`]: /methods/resource/filter/ +[`images.Filter`]: /functions/images/filter/ diff --git a/docs/content/en/_common/functions/js/options.md b/docs/content/en/_common/functions/js/options.md new file mode 100644 index 0000000..ae41fa4 --- /dev/null +++ b/docs/content/en/_common/functions/js/options.md @@ -0,0 +1,107 @@ +--- +_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 minify the generated JS code. Default is `false`. + +`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 . + +`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 . + +`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 . + +`externals` +: (`slice`) External dependencies. Use this to trim dependencies you know will never be executed. See . + +`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 + +`sourceMap` +: (`string`) The type of source map to generate. One of `external`, `inline`, `linked`, or `none`. Default is `none`. 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. + +`sourcesContent` +: {{< new-in 0.140.0 />}} +: (`bool`) Whether to include the content of the source files in the source map. Default is `true`. + +`JSX` +: (`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 . + +`JSXImportSource` +: (`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 . + + 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 new file mode 100644 index 0000000..6420599 --- /dev/null +++ b/docs/content/en/_common/functions/locales.md @@ -0,0 +1,9 @@ +--- +_comment: Do not remove front matter. +--- + +> [!NOTE] +> Localization of dates, currencies, numbers, and percentages is performed by the [`bep/golocales`][] package. Hugo determines the locale using the [`locale`][] configuration setting, falling back to the language key itself. The resolved value must be a locale supported by the package. + +[`bep/golocales`]: https://github.com/bep/golocales +[`locale`]: /configuration/all/#locale diff --git a/docs/content/en/_common/functions/reflect/image-reflection-functions.md b/docs/content/en/_common/functions/reflect/image-reflection-functions.md new file mode 100644 index 0000000..5fc1395 --- /dev/null +++ b/docs/content/en/_common/functions/reflect/image-reflection-functions.md @@ -0,0 +1,49 @@ +--- +_comment: Do not remove front matter. +--- + +## Image operations + +Use these functions to determine which operations Hugo supports for a given resource. While Hugo classifies a variety of file types as image resources, its ability to process them or extract metadata varies by format. + +- [`reflect.IsImageResource`][]: {{% get-page-desc "/functions/reflect/isimageresource" %}} +- [`reflect.IsImageResourceProcessable`][]: {{% get-page-desc "/functions/reflect/isimageresourceprocessable" %}} +- [`reflect.IsImageResourceWithMeta`][]: {{% get-page-desc "/functions/reflect/isimageresourcewithmeta" %}} + +The table below shows the values these functions return for various file formats. Use it to determine which checks are required before calling specific methods in your templates. + +|Format|IsImageResource|IsImageResourceProcessable|IsImageResourceWithMeta| +|:-----|:--------------|:-------------------------|:----------------------| +|AVIF |true |true |true | +|BMP |true |true |true | +|GIF |true |true |true | +|HEIC |true |**false** |true | +|HEIF |true |**false** |true | +|ICO |true |**false** |**false** | +|JPEG |true |true |true | +|PNG |true |true |true | +|SVG |true |**false** |**false** | +|TIFF |true |true |true | +|WebP |true |true |true | + +This contrived example demonstrates how to iterate through resources and use these functions to apply the appropriate handling for each image format. + +```go-html-template +{{ range resources.Match "**" }} + {{ if reflect.IsImageResource . }} + {{ if reflect.IsImageResourceProcessable . }} + {{ with .Process "resize 300x webp" }} + + {{ end }} + {{ else if reflect.IsImageResourceWithMeta . }} + + {{ else }} + + {{ end }} + {{ end }} +{{ end }} +``` + +[`reflect.IsImageResourceProcessable`]: /functions/reflect/isimageresourceprocessable/ +[`reflect.IsImageResourceWithMeta`]: /functions/reflect/isimageresourcewithmeta/ +[`reflect.IsImageResource`]: /functions/reflect/isimageresource/ diff --git a/docs/content/en/_common/functions/regular-expressions.md b/docs/content/en/_common/functions/regular-expressions.md new file mode 100644 index 0000000..4a8fd4a --- /dev/null +++ b/docs/content/en/_common/functions/regular-expressions.md @@ -0,0 +1,12 @@ +--- +_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. + +[PCRE]: https://www.pcre.org/ +[RE2 syntax]: https://github.com/google/re2/wiki/Syntax/ +[caveats]: https://swtch.com/~rsc/regexp/regexp3.html#caveats +[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 new file mode 100644 index 0000000..e15e58d --- /dev/null +++ b/docs/content/en/_common/functions/truthy-falsy.md @@ -0,0 +1,7 @@ +--- +_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 new file mode 100644 index 0000000..1d47844 --- /dev/null +++ b/docs/content/en/_common/functions/urls/anchorize-vs-urlize.md @@ -0,0 +1,35 @@ +--- +_comment: Do not remove front matter. +--- + +The [`anchorize`][] and [`urlize`][] functions are similar: + +- 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 +``` + +[`anchorize`]: /functions/urls/anchorize/ +[`urlize`]: /functions/urls/urlize/ diff --git a/docs/content/en/_common/gitignore-public.md b/docs/content/en/_common/gitignore-public.md new file mode 100644 index 0000000..7802347 --- /dev/null +++ b/docs/content/en/_common/gitignore-public.md @@ -0,0 +1,8 @@ +--- +_comment: Do not remove front matter. +--- + +> [!NOTE] +> Do not commit the contents of the [`publishDir`][] directory to your repository. Hugo recreates this directory when you build your project. + +[`publishDir`]: /configuration/all/#publishdir diff --git a/docs/content/en/_common/glob-patterns.md b/docs/content/en/_common/glob-patterns.md new file mode 100644 index 0000000..d3092de --- /dev/null +++ b/docs/content/en/_common/glob-patterns.md @@ -0,0 +1,23 @@ +--- +_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 new file mode 100644 index 0000000..8312356 --- /dev/null +++ b/docs/content/en/_common/gomodules-info.md @@ -0,0 +1,16 @@ +--- +_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 module resources: +> +> - [go.dev/wiki/Modules][] +> - [blog.golang.org/using-go-modules][] + +[Git]: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git +[Go]: https://go.dev/doc/install +[blog.golang.org/using-go-modules]: https://go.dev/blog/using-go-modules +[go.dev/wiki/Modules]: https://go.dev/wiki/Modules diff --git a/docs/content/en/_common/installation/01-editions.md b/docs/content/en/_common/installation/01-editions.md new file mode 100644 index 0000000..e16258d --- /dev/null +++ b/docs/content/en/_common/installation/01-editions.md @@ -0,0 +1,23 @@ +--- +_comment: Do not remove front matter. +--- + +## Editions + +Hugo is available in several editions. Use the standard edition unless you need additional features. + +Feature|standard|deploy (1)|extended|extended/deploy +:--|:-:|:-:|:-:|:-: +Core features|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark: +Direct cloud deployment (2)|:x:|:heavy_check_mark:|:x:|:heavy_check_mark: +LibSass support (3)|:x:|:x:|:heavy_check_mark:|:heavy_check_mark: + +(1) {{< new-in v0.159.2 />}} + +(2) Deploy your site directly to a Google Cloud Storage bucket, an AWS S3 bucket, or an Azure Storage container. See [details][]. + +(3) [Transpile Sass to CSS][] via embedded LibSass. Note that embedded LibSass was deprecated in v0.153.0 and will be removed in a future release. Use the [Dart Sass][] transpiler instead, which is compatible with any edition. + +[Dart Sass]: /functions/css/sass/#dart-sass +[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 new file mode 100644 index 0000000..63803dd --- /dev/null +++ b/docs/content/en/_common/installation/02-prerequisites.md @@ -0,0 +1,43 @@ +--- +_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 [Hugo modules][] +- Install a theme as a Git submodule +- Access [commit information][] from a local Git repository +- Host your site on [CI/CD](g) platforms such as [Cloudflare][], [GitHub Pages][], [GitLab Pages][], [Netlify][], [Render][], or [Vercel][] + +Go is required to: + +- Build Hugo from source +- Use Hugo modules + +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] + +[Cloudflare]: /host-and-deploy/host-on-cloudflare/ +[Dart Sass]: https://sass-lang.com/dart-sass +[GitHub Pages]: /host-and-deploy/host-on-github-pages/ +[GitLab Pages]: /host-and-deploy/host-on-gitlab-pages/ +[Git]: https://git-scm.com/ +[Go]: https://go.dev/ +[Hugo modules]: /hugo-modules/ +[Netlify]: /host-and-deploy/host-on-netlify/ +[Render]: /host-and-deploy/host-on-render/ +[Vercel]: /host-and-deploy/host-on-vercel/ +[commit information]: /methods/page/GitInfo/ +[dart sass install]: /functions/css/sass/#dart-sass +[git install]: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git +[go install]: https://go.dev/doc/install diff --git a/docs/content/en/_common/installation/03-prebuilt-binaries.md b/docs/content/en/_common/installation/03-prebuilt-binaries.md new file mode 100644 index 0000000..c5544cb --- /dev/null +++ b/docs/content/en/_common/installation/03-prebuilt-binaries.md @@ -0,0 +1,19 @@ +--- +_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. + +[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 new file mode 100644 index 0000000..e6b9d57 --- /dev/null +++ b/docs/content/en/_common/installation/04-build-from-source.md @@ -0,0 +1,49 @@ +--- +_comment: Do not remove front matter. +--- + +## Build from source + +To build Hugo from source you must install: + +1. [Git][] +1. [Go][] version {{% current-go-version %}} or later + +### Standard edition + +To build and install the standard edition: + +```sh +CGO_ENABLED=0 go install github.com/gohugoio/hugo@latest +``` + +### Deploy edition + +{{< new-in v0.159.2 />}} + +To build and install the deploy edition: + +```sh +CGO_ENABLED=0 go install -tags withdeploy github.com/gohugoio/hugo@latest +``` + +### Extended edition + +To build and install the extended edition, first install a C compiler such as [GCC][] or [Clang][] and then run the following command: + +```sh +CGO_ENABLED=1 go install -tags extended github.com/gohugoio/hugo@latest +``` + +### Extended/deploy edition + +To build and install the extended/deploy edition, first install a C compiler such as [GCC][] or [Clang][] and then run the following command: + +```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]: https://go.dev/doc/install diff --git a/docs/content/en/_common/installation/homebrew.md b/docs/content/en/_common/installation/homebrew.md new file mode 100644 index 0000000..6dacb40 --- /dev/null +++ b/docs/content/en/_common/installation/homebrew.md @@ -0,0 +1,13 @@ +--- +_comment: Do not remove front matter. +--- + +### Homebrew + +[Homebrew][] is a free and open-source package manager for macOS and Linux. To install the extended/deploy 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 new file mode 100644 index 0000000..03a2162 --- /dev/null +++ b/docs/content/en/_common/menu-entries/pre-and-post.md @@ -0,0 +1,39 @@ +--- +_comment: Do not remove front matter. +--- + +In this project 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 +
      + {{ range .Site.Menus.main }} +
    • + {{ .Pre | markdownify }} + {{ .Name }} + {{ .Post | markdownify }} +
    • + {{ end }} +
    +``` + +[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 new file mode 100644 index 0000000..ae49263 --- /dev/null +++ b/docs/content/en/_common/menu-entry-properties.md @@ -0,0 +1,31 @@ +--- +_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/media-type/core-methods.md b/docs/content/en/_common/methods/media-type/core-methods.md new file mode 100644 index 0000000..075dc4b --- /dev/null +++ b/docs/content/en/_common/methods/media-type/core-methods.md @@ -0,0 +1,18 @@ +--- +_comment: Do not remove front matter. +--- + +`Type` +: (`string`) Returns the media type. + +`MainType` +: (`string`) Returns the main type of the media type. + +`SubType` +: (`string`) Returns the subtype of the media type. + +`Suffixes` +: (`slice`) Returns a slice of possible file suffixes for the media type. + +`FirstSuffix.Suffix` +: (`string`) Returns the first of the possible file suffixes for the media type. diff --git a/docs/content/en/_common/methods/output-formats/to-use-this-method.md b/docs/content/en/_common/methods/output-formats/to-use-this-method.md new file mode 100644 index 0000000..027e89b --- /dev/null +++ b/docs/content/en/_common/methods/output-formats/to-use-this-method.md @@ -0,0 +1,9 @@ +--- +_comment: Do not remove front matter. +--- + +To use this method you must first select a specific [output format](g) from a page's [`OutputFormats`][] collection using the [`Get`][] or [`Canonical`][] methods. + +[`Canonical`]: /methods/page/outputformats/#canonical +[`Get`]: /methods/page/outputformats/#get +[`OutputFormats`]: /methods/page/outputformats/ diff --git a/docs/content/en/_common/methods/page/next-and-prev.md b/docs/content/en/_common/methods/page/next-and-prev.md new file mode 100644 index 0000000..18230fc --- /dev/null +++ b/docs/content/en/_common/methods/page/next-and-prev.md @@ -0,0 +1,59 @@ +--- +_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 + +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: + +```tree +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/section.html"} +{{ range .Pages.ByWeight }} +

    {{ .LinkTitle }}

    +{{ end }} +``` + +```go-html-template {file="layouts/page.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 [project configuration][], or use the [`Next`][] and [`Prev`][] methods on a `Pages` object for more flexibility. + +[`Next`]: /methods/pages/next/ +[`Prev`]: /methods/pages/prev/ +[`date`]: /methods/page/date/ +[`linkTitle`]: /methods/page/linktitle/ +[`path`]: /methods/page/path/ +[`weight`]: /methods/page/weight/ +[project configuration]: /configuration/page/ diff --git a/docs/content/en/_common/methods/page/nextinsection-and-previnsection.md b/docs/content/en/_common/methods/page/nextinsection-and-previnsection.md new file mode 100644 index 0000000..45c8e3c --- /dev/null +++ b/docs/content/en/_common/methods/page/nextinsection-and-previnsection.md @@ -0,0 +1,77 @@ +--- +_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 + +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: + +```tree +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/section.html"} +{{ range .Pages.ByWeight }} +

    {{ .LinkTitle }}

    +{{ end }} +``` + +```go-html-template {file="layouts/page.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 [project configuration][], or use the [`Next`][] and [`Prev`][] methods on a `Pages` object for more flexibility. + +## 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. + +[`Next`]: /methods/pages/next/ +[`Prev`]: /methods/pages/prev/ +[`date`]: /methods/page/date/ +[`linkTitle`]: /methods/page/linktitle/ +[`path`]: /methods/page/path/ +[`weight`]: /methods/page/weight/ +[project configuration]: /configuration/page/ diff --git a/docs/content/en/_common/methods/pages/group-sort-order.md b/docs/content/en/_common/methods/pages/group-sort-order.md new file mode 100644 index 0000000..e2997a1 --- /dev/null +++ b/docs/content/en/_common/methods/pages/group-sort-order.md @@ -0,0 +1,5 @@ +--- +_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 new file mode 100644 index 0000000..06a1cdf --- /dev/null +++ b/docs/content/en/_common/methods/pages/next-and-prev.md @@ -0,0 +1,71 @@ +--- +_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 + +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: + +```tree +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/section.html"} +{{ range .Pages.ByWeight }} +

    {{ .LinkTitle }}

    +{{ end }} +``` + +```go-html-template {file="layouts/page.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/page.html"} +{{ $pages := .CurrentSection.Pages.ByWeight.Reverse }} + +{{ with $pages.Prev . }} + Previous +{{ end }} + +{{ with $pages.Next . }} + Next +{{ end }} +``` + +[`Reverse`]: /methods/pages/reverse/ +[`date`]: /methods/page/date/ +[`linkTitle`]: /methods/page/linktitle/ +[`path`]: /methods/page/path/ +[`weight`]: /methods/page/weight/ 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 new file mode 100644 index 0000000..154b4d5 --- /dev/null +++ b/docs/content/en/_common/methods/resource/global-page-remote-resources.md @@ -0,0 +1,6 @@ +--- +_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 new file mode 100644 index 0000000..6aa018e --- /dev/null +++ b/docs/content/en/_common/methods/resource/processing-spec.md @@ -0,0 +1,67 @@ +--- +_comment: Do not remove front matter. +--- + +## Processing specification + +The processing specification is a space-delimited, case-insensitive list containing one or more of the following options in any sequence: + +action +: Specify one of `crop`, `fill`, `fit`, or `resize`. This is applicable to the [`Process`][] method and the [`images.Process`][] filter. If you specify an action, you must also provide [`dimensions`](#dimensions). + +anchor +: The focal point used when cropping or filling an image. Valid options include `TopLeft`, `Top`, `TopRight`, `Left`, `Center`, `Right`, `BottomLeft`, `Bottom`, `BottomRight`, or `Smart`. The `Smart` option utilizes the [`muesli/smartcrop`][] package to identify the most interesting area of the image. This defaults to the `anchor` setting in your [imaging configuration][]. + +background color +: The background color used when converting transparent images to formats that do not support transparency, such as PNG to JPEG. This color also fills the empty space created when rotating an image by a non-orthogonal angle if the space is not transparent and a background color is not specified in the processing specification. The value must be an RGB [hexadecimal color][]. This defaults to the `bgColor` setting in your [imaging configuration][]. + +compression +: {{< new-in 0.153.5 />}} +: The encoding strategy, applicable to AVIF and WebP images. Options are `lossy` or `lossless`. This defaults to the format-specific `compression` setting in your [imaging configuration][]. + +dimensions +: The dimensions of the resulting image, in pixels. The format is `WIDTHxHEIGHT` where `WIDTH` and `HEIGHT` are whole numbers. When resizing an image, you may specify only the width (such as `600x`) or only the height (such as `x400`) for proportional scaling. Specifying both width and height when resizing an image may result in non-proportional scaling. When cropping, fitting, or filling, you must provide both width and height such as `600x400`. + +format +: The format of the resulting image. Valid options include `avif`, `bmp`, `gif`, `jpeg`, `png`, `tiff`, or `webp`. This defaults to the format of the source image. + +hint +: The content hint, applicable to AVIF and WebP images. Valid options include `drawing`, `icon`, `photo`, `picture`, or `text`. This defaults to the format-specific `hint` setting in your [imaging configuration][]. + + 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 + +quality +: The visual fidelity, applicable to JPEG images and to AVIF and WebP images when using `lossy` compression. The format is `qQUALITY` where `QUALITY` is a whole number between `1` and `100`, inclusive. Lower numbers prioritize smaller file size, while higher numbers prioritize visual clarity. This defaults to the format-specific `quality` setting in your [imaging configuration][]. + +resampling filter +: The algorithm used to calculate new pixels when resizing, fitting, or filling an image. Common options include `box`, `lanczos`, `catmullRom`, `mitchellNetravali`, `linear`, or `nearestNeighbor`. This defaults to the `resampleFilter` setting in your [imaging configuration][]. + + 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 + + Refer to the [source documentation][] for a complete list of available resampling filters. If you wish to improve image quality at the expense of performance, you may wish to experiment with the alternative filters. + +rotation +: The number of whole degrees to rotate an image counter-clockwise. The format is `rDEGREES` where `DEGREES` is a whole number. Hugo performs rotation before any other transformations, so your [target dimensions](#dimensions) and any [anchor](#anchor) should refer to the image orientation after rotation. Use `r90`, `r180`, or `r270` for orthogonal rotations, or arbitrary angles such as `r45`. To rotate clockwise, use a negative number such as `r-45`. To automatically rotate an image based on its Exif orientation tag, use the [`images.AutoOrient`][] filter instead of manual rotation. + + Rotating by non-orthogonal values increases the image extents to fit the rotated corners. For formats supporting alpha channels such as AVIF, PNG, or WebP, this resulting empty space is transparent by default. If the target format does not support transparency such as JPEG, or if you explicitly specify a [background color](#background-color) in the processing specification, the space is filled. If a color is required but not specified in the processing string, it defaults to the `bgColor` setting in your [imaging configuration][]. + +[`Process`]: /methods/resource/process/ +[`images.AutoOrient`]: /functions/images/autoorient/ +[`images.Process`]: /functions/images/process/ +[`muesli/smartcrop`]: https://github.com/muesli/smartcrop +[hexadecimal color]: https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color +[imaging configuration]: /configuration/imaging/ +[source documentation]: https://github.com/disintegration/imaging#image-resizing 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 new file mode 100644 index 0000000..bfc8a29 --- /dev/null +++ b/docs/content/en/_common/methods/taxonomy/get-a-taxonomy-object.md @@ -0,0 +1,66 @@ +--- +_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 project configuration: + +{{< code-toggle file=hugo >}} +[taxonomies] +genre = 'genres' +author = 'authors' +{{< /code-toggle >}} + +And this content structure: + +```tree +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/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 }} +

    {{ .Page.LinkTitle }}

    + +{{ 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/ +[`Taxonomies`]: /methods/site/taxonomies/ +[`Terms`]: /methods/page/data/#in-a-taxonomy-template 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 new file mode 100644 index 0000000..07afadb --- /dev/null +++ b/docs/content/en/_common/methods/taxonomy/ordered-taxonomy-element-methods.md @@ -0,0 +1,24 @@ +--- +_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 new file mode 100644 index 0000000..9284276 --- /dev/null +++ b/docs/content/en/_common/parsable-date-time-strings.md @@ -0,0 +1,14 @@ +--- +_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 new file mode 100644 index 0000000..bf14e09 --- /dev/null +++ b/docs/content/en/_common/permalink-tokens.md @@ -0,0 +1,70 @@ +--- +_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. + +`:sectionslug` +: {{< new-in 0.149.0 />}} +: The content's section using slugified section name. The slugified section name is the `slug` as defined in front matter, else the `title` as defined in front matter, else the automatic title. + +`: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. + +`:sectionslugs` +: {{< new-in 0.149.0 />}} +: The content's sections hierarchy using slugified section names. The slugified section name is the `slug` as defined in front matter, else the `title` as defined in front matter, else the automatic title. You can use a selection of the sections using _slice syntax_: `:sectionslugs[1:]` includes all but the first, `:sectionslugs[:last]` includes all but the last, `:sectionslugs[last]` includes only the last, `:sectionslugs[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` +: {{< deprecated-in v0.144.0 />}} +: Use the [`:contentbasename`](#contentbasename) token instead. + +`:slugorfilename` +: {{< deprecated-in v0.144.0 />}} +: Use the [`:slugorcontentbasename`](#slugorcontentbasename) token instead. + +`:contentbasename` +: {{< new-in 0.144.0 />}} +: The [content base name][]. + +`: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: + +{{< code-toggle file=hugo >}} +permalinks: + posts: /:06/:1/:2/:title/ +{{< /code-toggle >}} + +[content base name]: /methods/page/file/#contentbasename +[time package]: https://pkg.go.dev/time#pkg-constants diff --git a/docs/content/en/_common/ref-and-relref-error-handling.md b/docs/content/en/_common/ref-and-relref-error-handling.md new file mode 100644 index 0000000..9ec185d --- /dev/null +++ b/docs/content/en/_common/ref-and-relref-error-handling.md @@ -0,0 +1,10 @@ +--- +_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 project 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 new file mode 100644 index 0000000..79808c3 --- /dev/null +++ b/docs/content/en/_common/ref-and-relref-options.md @@ -0,0 +1,12 @@ +--- +_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 new file mode 100644 index 0000000..dc9e59d --- /dev/null +++ b/docs/content/en/_common/render-hooks/pageinner.md @@ -0,0 +1,45 @@ +--- +_comment: Do not remove front matter. +--- + +## PageInner details + +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: + +```md {file="content/posts/post-1.md"} +{{%/* include "/posts/post-2" */%}} +``` + +Any render hook triggered while rendering `/posts/post-2` will get: + +- `/posts/post-1` when calling `Page` +- `/posts/post-2` 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][] + +[Embedded image render hook]: <{{% eturl render-image %}}> +[Embedded link render hook]: <{{% eturl render-link %}}> +[Markdown notation]: /content-management/shortcodes/#notation +[`RenderShortcodes`]: /methods/page/rendershortcodes/ diff --git a/docs/content/en/_common/scratch-pad-scope.md b/docs/content/en/_common/scratch-pad-scope.md new file mode 100644 index 0000000..b63877c --- /dev/null +++ b/docs/content/en/_common/scratch-pad-scope.md @@ -0,0 +1,21 @@ +--- +_comment: Do not remove front matter. +--- + +## Scope + +The method or function used to create the data structure determines its scope. For example, use the `Store` method on a `Page` object to create a data structure 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/ +[`SHORTCODE.Store`]: /methods/shortcode/store/ +[`SITE.Store`]: /methods/site/store/ +[`collections.NewScratch`]: /functions/collections/newscratch/ +[`hugo.Store`]: /functions/hugo/store/ diff --git a/docs/content/en/_common/store-methods.md b/docs/content/en/_common/store-methods.md new file mode 100644 index 0000000..d632c10 --- /dev/null +++ b/docs/content/en/_common/store-methods.md @@ -0,0 +1,81 @@ +--- +# Do not remove front matter. +--- + +## Methods + +Use these methods on the data structure. + +`Set` +: Sets the value of the given key. + + ```go-html-template + {{ .Store.Set "greeting" "Hello" }} + ``` + +`Get` +: (`any`) 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` +: (`[]any`) 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/store-scope.md b/docs/content/en/_common/store-scope.md new file mode 100644 index 0000000..b63877c --- /dev/null +++ b/docs/content/en/_common/store-scope.md @@ -0,0 +1,21 @@ +--- +_comment: Do not remove front matter. +--- + +## Scope + +The method or function used to create the data structure determines its scope. For example, use the `Store` method on a `Page` object to create a data structure 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/ +[`SHORTCODE.Store`]: /methods/shortcode/store/ +[`SITE.Store`]: /methods/site/store/ +[`collections.NewScratch`]: /functions/collections/newscratch/ +[`hugo.Store`]: /functions/hugo/store/ diff --git a/docs/content/en/_common/syntax-highlighting-options.md b/docs/content/en/_common/syntax-highlighting-options.md new file mode 100644 index 0000000..2f57cb0 --- /dev/null +++ b/docs/content/en/_common/syntax-highlighting-options.md @@ -0,0 +1,57 @@ +--- +_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 syntax highlighter includes lexers for approximately 300 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: + + ```sh + hugo gen chromastyles --style=monokai > syntax.css + ``` + +`style` +: (`string`) The CSS styles to apply to the highlighted code. This value is case-insensitive. 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 new file mode 100644 index 0000000..f07a322 --- /dev/null +++ b/docs/content/en/_common/time-layout-string.md @@ -0,0 +1,46 @@ +--- +_comment: Do not remove front matter. +--- + +Format a `time.Time` value based on [Go's reference time][]: + +```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`. + +[Go's reference time]: https://pkg.go.dev/time#pkg-constants diff --git a/docs/content/en/_index.md b/docs/content/en/_index.md new file mode 100644 index 0000000..358f6a4 --- /dev/null +++ b/docs/content/en/_index.md @@ -0,0 +1,4 @@ +--- +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 new file mode 100644 index 0000000..e558009 --- /dev/null +++ b/docs/content/en/about/_index.md @@ -0,0 +1,9 @@ +--- +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 new file mode 100644 index 0000000..cb93db9 --- /dev/null +++ b/docs/content/en/about/features.md @@ -0,0 +1,140 @@ +--- +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 project 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 project, or to augment an existing project. + +[Privacy][] +: Configure your project 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 + +[Multidimensional content model][] +: Generate pages across any combination of language, version, and role from a single source. This allows a single piece of content to be published to multiple [sites](g) within your project, removing the need to duplicate files for different audiences or versions. + +[Content adapters][] +: Create content adapters to dynamically add content when building your project. 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 + +[CSS Processing][] +: Bundle, transform, minify, create source maps, perform SRI hashing, and integrate with PostCSS. + +[Image processing][] +: Convert, resize, crop, rotate, adjust colors, apply filters, overlay text and images, and extract metadata. + +[JavaScript bundling][] +: Transpile TypeScript and JSX to JavaScript, bundle, tree shake, minify, create source maps, and perform SRI hashing. + +[Sass processing][] +: Transpile Sass to CSS, bundle, tree shake, minify, create source maps, perform SRI hashing, and integrate with PostCSS. + +[Tailwind CSS processing][] +: Compile Tailwind CSS utility classes into standard CSS, bundle, tree shake, optimize, minify, perform SRI hashing, and integrate with PostCSS. + +## 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 project once a week. + +[Minification][] +: Minify HTML, CSS, and JavaScript to reduce file size, bandwidth consumption, and loading times. + +[CSS Processing]: /functions/css/build/ +[Caching]: /functions/partials/includecached/ +[CommonMark]: https://spec.commonmark.org/current/ +[Content adapters]: /content-management/content-adapters/ +[Content formats]: /content-management/formats/ +[Data]: /content-management/data-sources/ +[Diagrams]: /content-management/diagrams/ +[GitHub Flavored Markdown]: https://github.github.com/gfm/ +[Image processing]: /content-management/image-processing/ +[JavaScript bundling]: /functions/js/build/ +[Markdown attributes]: /content-management/markdown-attributes/ +[Markdown extensions]: /configuration/markup/#extensions +[Markdown render hooks]: /render-hooks/introduction/ +[Mathematics]: /content-management/mathematics/ +[Menus]: /content-management/menus/ +[Minification]: /configuration/minify/ +[Modules]: /hugo-modules/ +[Multidimensional content model]: /quick-reference/glossary/#sites-matrix +[Multilingual]: /content-management/multilingual/ +[Multiplatform]: /installation/ +[Output formats]: /configuration/output-formats/ +[Privacy]: /configuration/privacy/ +[Sass processing]: /functions/css/sass/ +[Security]: /about/security/ +[Segmentation]: /configuration/segments/ +[Shortcodes]: /content-management/shortcodes/ +[Syntax highlighting]: /content-management/syntax-highlighting/ +[Tailwind CSS processing]: /functions/css/tailwindcss/ +[Taxonomies]: /content-management/taxonomies/ +[Templates]: /templates/introduction/ +[Themes]: https://themes.gohugo.io/ +[URL management]: /content-management/urls/ diff --git a/docs/content/en/about/introduction.md b/docs/content/en/about/introduction.md new file mode 100644 index 0000000..2a046a7 --- /dev/null +++ b/docs/content/en/about/introduction.md @@ -0,0 +1,34 @@ +--- +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 [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][]. + +{{< youtube 0RKpf3rK57I >}} + +[Go]: https://go.dev +[features]: /about/features/ +[modules]: /hugo-modules/ +[privacy protections]: /configuration/privacy/ +[security model]: /about/security/ +[static site generator]: https://en.wikipedia.org/wiki/Static_site_generator diff --git a/docs/content/en/about/license.md b/docs/content/en/about/license.md new file mode 100644 index 0000000..cd190da --- /dev/null +++ b/docs/content/en/about/license.md @@ -0,0 +1,75 @@ +--- +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 new file mode 100644 index 0000000..6ffd423 --- /dev/null +++ b/docs/content/en/about/security.md @@ -0,0 +1,63 @@ +--- +title: Security model +linkTitle: Security +description: A summary of Hugo's security model. +categories: [] +keywords: [] +weight: 30 +aliases: [/about/security-model/] +--- + +## Security Boundaries + +- The templates inside `layouts` are trusted. +- The assets inside `archetypes`, `assets`, `resources`, `data`, `i18n` and `static` are trusted. +- The content and the content produced by [content adapters][] inside `content` is not trusted. The one exception here is if [inline shortcodes][] is enabled. Note that for content adapters, this is scoped to the result of the adapter. +- The development server, `hugo server`, and its livereload script is trusted and meant for _local_ development only. + +## 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. +- Pragmatic defaults: The default [security policy][] aims to balance security and usability, enabling common workflows out of the box while keeping more sensitive capabilities opt-in. These defaults may be tightened in future releases, but each project is ultimately responsible for reviewing the policy and adjusting it to match its own trust model and requirements. + +This combination of sandboxing and strict defaults effectively minimizes potential security vulnerabilities during the Hugo build process. + +## 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 the functionality of Go modules, 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][]. + +## 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. + +To prevent unintended escaping of data that developers know is safe, Hugo provides [`safe`][] functions, such as [`safe.HTML`][]. 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. + +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. + +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 settings][], 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. + +## Configuration + +See [configure security][]. + +[Go modules]: https://go.dev/wiki/Modules#modules +[Hugo modules]: /hugo-modules/ +[OWASP]: https://en.wikipedia.org/wiki/OWASP +[`safe.HTML`]: /functions/safe/html/ +[`safe`]: /functions/safe/ +[configuration settings]: /configuration/security/ +[configure security]: /configuration/security/ +[configured to sanitize]: /configuration/markup/#rendererunsafe +[content adapters]: /content-management/content-adapters/ +[inline shortcodes]: /content-management/shortcodes/#inline +[security policy]: /configuration/security/ +[tamper with your project's dependencies]: https://julienrenaux.fr/2019/12/20/github-actions-security-risk/ diff --git a/docs/content/en/commands/_index.md b/docs/content/en/commands/_index.md new file mode 100644 index 0000000..b97b6e2 --- /dev/null +++ b/docs/content/en/commands/_index.md @@ -0,0 +1,8 @@ +--- +title: Command line interface +linkTitle: CLI +description: Use the command line interface (CLI) to manage your project. +categories: [] +keywords: [] +weight: 10 +--- diff --git a/docs/content/en/commands/hugo.md b/docs/content/en/commands/hugo.md new file mode 100644 index 0000000..5d3a349 --- /dev/null +++ b/docs/content/en/commands/hugo.md @@ -0,0 +1,83 @@ +--- +title: "hugo" +slug: hugo +url: /commands/hugo/ +--- +## hugo + +Build your project + +### Synopsis + +hugo is the main command, used to build your Hugo project. + +Hugo is a Fast and Flexible Static Site Generator +built with love by spf13 and friends in Go. + +Complete documentation is available at https://gohugo.io/. + +``` +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 ignore the configured file caches + --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 project +* [hugo completion](/commands/hugo_completion/) - Generate the autocompletion script for the specified shell +* [hugo config](/commands/hugo_config/) - Display project configuration +* [hugo convert](/commands/hugo_convert/) - Convert front matter to another format +* [hugo deploy](/commands/hugo_deploy/) - Deploy your project 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 project 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 new file mode 100644 index 0000000..b9311aa --- /dev/null +++ b/docs/content/en/commands/hugo_build.md @@ -0,0 +1,71 @@ +--- +title: "hugo build" +slug: hugo_build +url: /commands/hugo_build/ +--- +## hugo build + +Build your project + +### Synopsis + +build is the main command, used to build your Hugo project. + +Hugo is a Fast and Flexible Static Site Generator +built with love by spf13 and friends in Go. + +Complete documentation is available at https://gohugo.io/. + +``` +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 ignore the configured file caches + --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 project diff --git a/docs/content/en/commands/hugo_completion.md b/docs/content/en/commands/hugo_completion.md new file mode 100644 index 0000000..34b4114 --- /dev/null +++ b/docs/content/en/commands/hugo_completion.md @@ -0,0 +1,45 @@ +--- +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 project +* [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 new file mode 100644 index 0000000..9f18169 --- /dev/null +++ b/docs/content/en/commands/hugo_completion_bash.md @@ -0,0 +1,64 @@ +--- +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 new file mode 100644 index 0000000..3015d23 --- /dev/null +++ b/docs/content/en/commands/hugo_completion_fish.md @@ -0,0 +1,55 @@ +--- +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 new file mode 100644 index 0000000..780f7fc --- /dev/null +++ b/docs/content/en/commands/hugo_completion_powershell.md @@ -0,0 +1,52 @@ +--- +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 new file mode 100644 index 0000000..e5743e9 --- /dev/null +++ b/docs/content/en/commands/hugo_completion_zsh.md @@ -0,0 +1,66 @@ +--- +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 new file mode 100644 index 0000000..3de89f1 --- /dev/null +++ b/docs/content/en/commands/hugo_config.md @@ -0,0 +1,52 @@ +--- +title: "hugo config" +slug: hugo_config +url: /commands/hugo_config/ +--- +## hugo config + +Display project configuration + +### Synopsis + +Display project 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 project +* [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 new file mode 100644 index 0000000..eb540ac --- /dev/null +++ b/docs/content/en/commands/hugo_config_mounts.md @@ -0,0 +1,44 @@ +--- +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 project configuration diff --git a/docs/content/en/commands/hugo_convert.md b/docs/content/en/commands/hugo_convert.md new file mode 100644 index 0000000..f293fa4 --- /dev/null +++ b/docs/content/en/commands/hugo_convert.md @@ -0,0 +1,46 @@ +--- +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 project +* [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 new file mode 100644 index 0000000..59a6376 --- /dev/null +++ b/docs/content/en/commands/hugo_convert_toJSON.md @@ -0,0 +1,46 @@ +--- +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 new file mode 100644 index 0000000..de48f71 --- /dev/null +++ b/docs/content/en/commands/hugo_convert_toTOML.md @@ -0,0 +1,46 @@ +--- +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 new file mode 100644 index 0000000..ae14e8e --- /dev/null +++ b/docs/content/en/commands/hugo_convert_toYAML.md @@ -0,0 +1,46 @@ +--- +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 new file mode 100644 index 0000000..b8c08a3 --- /dev/null +++ b/docs/content/en/commands/hugo_deploy.md @@ -0,0 +1,54 @@ +--- +title: "hugo deploy" +slug: hugo_deploy +url: /commands/hugo_deploy/ +--- +## hugo deploy + +Deploy your project to a cloud provider + +### Synopsis + +Deploy your project 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 project diff --git a/docs/content/en/commands/hugo_env.md b/docs/content/en/commands/hugo_env.md new file mode 100644 index 0000000..007eb2a --- /dev/null +++ b/docs/content/en/commands/hugo_env.md @@ -0,0 +1,43 @@ +--- +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 project diff --git a/docs/content/en/commands/hugo_gen.md b/docs/content/en/commands/hugo_gen.md new file mode 100644 index 0000000..605a8fa --- /dev/null +++ b/docs/content/en/commands/hugo_gen.md @@ -0,0 +1,42 @@ +--- +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 project +* [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 new file mode 100644 index 0000000..522d357 --- /dev/null +++ b/docs/content/en/commands/hugo_gen_chromastyles.md @@ -0,0 +1,51 @@ +--- +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://gohugo.io/quick-reference/syntax-highlighting-styles/ 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" + --omitClassComments omit CSS class comment prefixes in the generated CSS + --omitEmpty omit empty CSS rules (deprecated, no longer needed) + --style string highlighter style (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 new file mode 100644 index 0000000..28f0617 --- /dev/null +++ b/docs/content/en/commands/hugo_gen_doc.md @@ -0,0 +1,49 @@ +--- +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 new file mode 100644 index 0000000..7ebc9ff --- /dev/null +++ b/docs/content/en/commands/hugo_gen_man.md @@ -0,0 +1,46 @@ +--- +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 new file mode 100644 index 0000000..5e38a68 --- /dev/null +++ b/docs/content/en/commands/hugo_import.md @@ -0,0 +1,42 @@ +--- +title: "hugo import" +slug: hugo_import +url: /commands/hugo_import/ +--- +## hugo import + +Import a project from another system + +### Synopsis + +Import a project 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 project +* [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 new file mode 100644 index 0000000..d1f16b9 --- /dev/null +++ b/docs/content/en/commands/hugo_import_jekyll.md @@ -0,0 +1,46 @@ +--- +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 project from another system diff --git a/docs/content/en/commands/hugo_list.md b/docs/content/en/commands/hugo_list.md new file mode 100644 index 0000000..90b8e6d --- /dev/null +++ b/docs/content/en/commands/hugo_list.md @@ -0,0 +1,46 @@ +--- +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 project +* [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 new file mode 100644 index 0000000..6b324ab --- /dev/null +++ b/docs/content/en/commands/hugo_list_all.md @@ -0,0 +1,43 @@ +--- +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 new file mode 100644 index 0000000..c6a8907 --- /dev/null +++ b/docs/content/en/commands/hugo_list_drafts.md @@ -0,0 +1,43 @@ +--- +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 new file mode 100644 index 0000000..b0618ca --- /dev/null +++ b/docs/content/en/commands/hugo_list_expired.md @@ -0,0 +1,43 @@ +--- +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 new file mode 100644 index 0000000..4f2fdca --- /dev/null +++ b/docs/content/en/commands/hugo_list_future.md @@ -0,0 +1,43 @@ +--- +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 new file mode 100644 index 0000000..a497687 --- /dev/null +++ b/docs/content/en/commands/hugo_list_published.md @@ -0,0 +1,43 @@ +--- +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 new file mode 100644 index 0000000..5cc9eef --- /dev/null +++ b/docs/content/en/commands/hugo_mod.md @@ -0,0 +1,58 @@ +--- +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 project +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 project +* [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 new file mode 100644 index 0000000..9abb1cb --- /dev/null +++ b/docs/content/en/commands/hugo_mod_clean.md @@ -0,0 +1,50 @@ +--- +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 new file mode 100644 index 0000000..a7eebd7 --- /dev/null +++ b/docs/content/en/commands/hugo_mod_get.md @@ -0,0 +1,74 @@ +--- +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 project +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 new file mode 100644 index 0000000..2e84091 --- /dev/null +++ b/docs/content/en/commands/hugo_mod_graph.md @@ -0,0 +1,51 @@ +--- +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 new file mode 100644 index 0000000..05d785c --- /dev/null +++ b/docs/content/en/commands/hugo_mod_init.md @@ -0,0 +1,55 @@ +--- +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 new file mode 100644 index 0000000..e9bd9d9 --- /dev/null +++ b/docs/content/en/commands/hugo_mod_npm.md @@ -0,0 +1,44 @@ +--- +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/) - Merges module Node.js dependencies into an npm workspace diff --git a/docs/content/en/commands/hugo_mod_npm_pack.md b/docs/content/en/commands/hugo_mod_npm_pack.md new file mode 100644 index 0000000..4482a4d --- /dev/null +++ b/docs/content/en/commands/hugo_mod_npm_pack.md @@ -0,0 +1,56 @@ +--- +title: "hugo mod npm pack" +slug: hugo_mod_npm_pack +url: /commands/hugo_mod_npm_pack/ +--- +## hugo mod npm pack + +Merges module Node.js dependencies into an npm workspace + +### Synopsis + +Merges Node.js dependencies from all Hugo modules into a "packages/hugoautogen" npm workspace. + +The merged dependencies are written to packages/hugoautogen/package.json, and the root package.json +is updated with a "workspaces" entry pointing to "packages/hugoautogen". + +The source entries are read from either package.hugo.json or package.json in the module root, with package.hugo.json taking precedence if both exist. + +See [Node.js dependencies](/hugo-modules/nodejs-dependencies/) for more information. + + +``` +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 new file mode 100644 index 0000000..1114772 --- /dev/null +++ b/docs/content/en/commands/hugo_mod_tidy.md @@ -0,0 +1,44 @@ +--- +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 new file mode 100644 index 0000000..4e828fe --- /dev/null +++ b/docs/content/en/commands/hugo_mod_vendor.md @@ -0,0 +1,50 @@ +--- +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 new file mode 100644 index 0000000..6c15a88 --- /dev/null +++ b/docs/content/en/commands/hugo_mod_verify.md @@ -0,0 +1,49 @@ +--- +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 new file mode 100644 index 0000000..a6587e4 --- /dev/null +++ b/docs/content/en/commands/hugo_new.md @@ -0,0 +1,49 @@ +--- +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 project, they will be used. + +Ensure you run this within the root directory of your project. + +### 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 project +* [hugo new content](/commands/hugo_new_content/) - Create new content +* [hugo new project](/commands/hugo_new_project/) - Create a new project +* [hugo new theme](/commands/hugo_new_theme/) - Create a new theme diff --git a/docs/content/en/commands/hugo_new_content.md b/docs/content/en/commands/hugo_new_content.md new file mode 100644 index 0000000..0a5b543 --- /dev/null +++ b/docs/content/en/commands/hugo_new_content.md @@ -0,0 +1,58 @@ +--- +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 project, they will be used. + +Ensure you run this within the root directory of your project. + +``` +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_project.md b/docs/content/en/commands/hugo_new_project.md new file mode 100644 index 0000000..652eefe --- /dev/null +++ b/docs/content/en/commands/hugo_new_project.md @@ -0,0 +1,45 @@ +--- +title: "hugo new project" +slug: hugo_new_project +url: /commands/hugo_new_project/ +--- +## hugo new project + +Create a new project + +### Synopsis + +Create a new project at the specified path. + +``` +hugo new project [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 project +``` + +### 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 new file mode 100644 index 0000000..7a7e040 --- /dev/null +++ b/docs/content/en/commands/hugo_new_theme.md @@ -0,0 +1,45 @@ +--- +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 with the specified name in the ./themes directory. +This generates a functional theme including template examples and sample content. + +``` +hugo new theme [name] [flags] +``` + +### Options + +``` + --format string preferred file format (toml, yaml or json) (default "toml") + -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 new file mode 100644 index 0000000..ec99b1a --- /dev/null +++ b/docs/content/en/commands/hugo_server.md @@ -0,0 +1,97 @@ +--- +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 project. +While hugo server is high performance, it is a webserver with limited options. + +The `hugo server` command will by default write and serve files from disk, but +you can render to memory by using the `--renderToMemory` flag. This can be +faster in some cases, but it will consume more memory. + +By default hugo will also watch your files for any changes you make and +automatically rebuild the project. It will then live reload any open browser pages +and push the latest content to them. As most Hugo projects are built in a fraction +of a second, you will be able to save and see your changes nearly instantly. + +``` +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 ignore the configured file caches + -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 disable browser caching of pages served by the embedded web server + --noTimes don't sync modification time of files + -O, --openBrowser open the project 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 project +* [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 new file mode 100644 index 0000000..8649838 --- /dev/null +++ b/docs/content/en/commands/hugo_server_trust.md @@ -0,0 +1,40 @@ +--- +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 new file mode 100644 index 0000000..e71abdc --- /dev/null +++ b/docs/content/en/commands/hugo_version.md @@ -0,0 +1,43 @@ +--- +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 project diff --git a/docs/content/en/configuration/_index.md b/docs/content/en/configuration/_index.md new file mode 100644 index 0000000..7cb08cc --- /dev/null +++ b/docs/content/en/configuration/_index.md @@ -0,0 +1,7 @@ +--- +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 new file mode 100644 index 0000000..5e57a25 --- /dev/null +++ b/docs/content/en/configuration/all.md @@ -0,0 +1,416 @@ +--- +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][]. + +`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][]. + +`canonifyURLs` +: (`bool`) See [details][canonical-urls] before enabling this feature. Default is `false`. + +`capitalizeListTitles` +: (`bool`) Whether to capitalize automatic list titles. Applicable to section, taxonomy, and term pages. Use the [`titleCaseStyle`](#titlecasestyle) setting to configure capitalization rules. Default is `true`. + +`cascade` +: See [configure cascade][]. + +`cleanDestinationDir` +: (`bool`) Whether to remove files from the [`publishDir`](#publishdir) that do not exist in the [`staticDir`](#staticdir) when building the site. This setting will not take effect if the `staticDir` does not exist. Note that `.gitignore` and `.gitattributes` files, along with directories named `.git`, are always preserved in the `publishDir`. 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 projects's [default language](g), conforming to the syntax described in [RFC 5646][]. + +`defaultContentLanguageInSubdir` +: (`bool`) Whether to publish the default content language to a subdirectory matching the [`defaultContentLanguage`](#defaultcontentlanguage). Default is `false`. + +`defaultContentRole` +: {{< new-in 0.153.0 />}} +: (`string`) The project's [default role](g). + +`defaultContentRoleInSubdir` +: {{< new-in 0.153.0 />}} +: (`bool`) Whether to publish the default content [role](g) to a subdirectory matching the [`defaultContentRole`](#defaultcontentrole). Default is `false`. + +`defaultContentVersion` +: {{< new-in 0.153.0 />}} +: (`string`) The project's [default version](g). + +`defaultContentVersionInSubdir` +: {{< new-in 0.153.0 />}} +: (`bool`) Whether to publish the default content version to a subdirectory matching the [`defaultContentVersion`](#defaultcontentversion). 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][]. + +`disableAliases` +: (`bool`) Whether to disable the generation of HTML redirect files for each path defined in the [`aliases`][aliases_front_matter] front matter field. When `true`, Hugo will not create physical files for [client-side redirection][], but the alias data remains available via the [`Aliases`][aliases_page_method] method on a `Page` object. Default is `false`. + +`disableDefaultLanguageRedirect` +: {{< new-in 0.140.0 />}} +: (`bool`) Whether to disable generation of the alias redirect for the default content language. When [`defaultContentLanguageInSubdir`](#defaultcontentlanguageinsubdir) is `true`, this setting prevents the root directory from redirecting to the language subdirectory. Conversely, when `defaultContentLanguageInSubdir` is `false`, this setting prevents the language subdirectory from redirecting to the root directory. This is superseded by the more general [`disableDefaultSiteRedirect`](#disabledefaultsiteredirect) setting. Default is `false`. + +`disableDefaultSiteRedirect` +: {{< new-in 0.154.5 />}} +: (`bool`) Whether to disable generation of the alias redirect to the [default site](g). When [`defaultContentLanguageInSubdir`](#defaultcontentlanguageinsubdir), [`defaultContentRoleInSubdir`](#defaultcontentroleinsubdir), or [`defaultContentVersionInSubdir`](#defaultcontentversioninsubdir) is `true`, this prevents the root directory from redirecting to the default site's subdirectory. Conversely, when these are `false`, it prevents the subdirectories from redirecting back to the root. 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. Default is `false`. + +`enableEmoji` +: (`bool`) Whether to allow emoji in Markdown. Default is `false`. + +`enableGitInfo` +: (`bool`) Whether to retrieve commit metadata from the Git history of your local project and any [modules](g). This enables the [`GitInfo`][] method on a `Page` object. With the default front matter configuration, the [`Lastmod`][] method on a `Page` object returns the Git author date of the last commit for that file. 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`. + +`frontmatter` +: See [configure 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][]. + +`i18nDir` +: (`string`) The designated directory for translation tables. Default is `i18n`. {{% module-mounts-note %}} + +`ignoreCache` +: (`bool`) Whether to ignore the configured file caches. 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 pattern](g) matching the module paths to exclude from the `_vendor` directory. + +`imaging` +: See [configure imaging][]. + +`languageCode` +: {{}} +: Use [`locale`](#locale) instead. + +`languages` +: See [configure languages][]. + +`layoutDir` +: (`string`) The designated directory for templates. Default is `layouts`. {{% module-mounts-note %}} + +{{% include "/_common/configuration/locale.md" %}} + + For a multilingual project, specify this value independently for each language key. See [configure languages][]. + +`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][]. + +`mediaTypes` +: See [configure media types][]. + +`menus` +: See [configure menus][]. + +`minify` +: See [configure minify][]. + +`module` +: See [configure modules][]. + +`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][]. + +`outputs` +: See [configure outputs][]. + +`page` +: See [configure page][]. + +`pagination` +: See [configure pagination][]. + +`panicOnWarning` +: (`bool`) Whether to panic on the first WARNING. Default is `false`. + +`params` +: See [configure params][]. + +`permalinks` +: See [configure 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][]. + +`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][]. + +`relativeURLs` +: (`bool`) See [details][relative-urls] before enabling this feature. Default is `false`. + +`removePathAccents` +: (`bool`) Whether to remove [non-spacing marks][] from [composite characters][] in content paths. Default is `false`. + +`renderSegments` +: (`[]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 build --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`. + +`roles` +: See [configure roles][]. + +`security` +: See [configure security][]. + +`sectionPagesMenu` +: (`string`) When set, each top-level section will be added to the menu identified by the provided value. See [details][define-automatically]. + +`segments` +: See [configure segments][]. + +`server` +: See [configure server][]. + +`services` +: See [configure services][]. + +`sitemap` +: See [configure 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. Default is `70`. + +`taxonomies` +: See [configure taxonomies][]. + +`templateMetrics` +: (`bool`) Whether to print template execution metrics to the console. Default is `false`. See [details][template-metrics]. + +`templateMetricsHints` +: (`bool`) Whether to print template execution improvement hints to the console. Applicable when `templateMetrics` is `true`. Default is `false`. See [details][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][]. + +`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 `60s`. + +`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`][] 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][]. + +`versions` +: See [configure versions][]. + +## Cache directory + +Hugo's file cache directory is configurable via the [`cacheDir`](#cachedir) setting 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) platforms, please read their documentation. For a 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`](#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][]. + +[Associated Press Stylebook]: https://www.apstylebook.com/ +[Chicago Manual of Style]: https://www.chicagomanualofstyle.org/home.html +[IANA Time Zone Database]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones +[RFC 5646]: https://datatracker.ietf.org/doc/html/rfc5646#section-2.1 +[XDG base directory specification]: https://specifications.freedesktop.org/basedir-spec/latest/ +[`FuzzyWordCount`]: /methods/page/fuzzywordcount/ +[`GitInfo`]: /methods/page/gitinfo/ +[`Lastmod`]: /methods/page/lastmod/ +[`MainSections`]: /methods/site/mainsections/ +[`Summary`]: /methods/page/summary/ +[`WordCount`]: /methods/page/wordcount/ +[`disabled`]: /configuration/languages/#disabled +[`erroridf`]: /functions/fmt/erroridf/ +[`os.UserCacheDir`]: https://pkg.go.dev/os#UserCacheDir +[`segments`]: /configuration/segments/ +[`strings.Title`]: /functions/strings/title/ +[`time.AsTime`]: /functions/time/astime/ +[`time.Format`]: /functions/time/format/ +[`warnidf`]: /functions/fmt/warnidf/ +[aliases_front_matter]: /content-management/front-matter/#aliases +[aliases_page_method]: /methods/page/aliases/ +[automatic summaries]: /content-management/summaries/#automatic-summary +[canonical-urls]: /content-management/urls/#canonical-urls +[client-side redirection]: /content-management/urls/#client-side-redirection +[composite characters]: https://en.wikipedia.org/wiki/Precomposed_character +[configure HTTP cache]: /configuration/http-cache/ +[configure build]: /configuration/build/ +[configure cascade]: /configuration/cascade/ +[configure deployment]: /configuration/deployment/ +[configure file caches]: /configuration/caches/ +[configure front matter]: /configuration/front-matter/ +[configure imaging]: /configuration/imaging/ +[configure languages]: /configuration/languages/ +[configure markup]: /configuration/markup/ +[configure media types]: /configuration/media-types/ +[configure menus]: /configuration/menus/ +[configure minify]: /configuration/minify/ +[configure modules]: /configuration/module/ +[configure output formats]: /configuration/output-formats/ +[configure outputs]: /configuration/outputs/ +[configure page]: /configuration/page/ +[configure pagination]: /configuration/pagination/ +[configure params]: /configuration/params/ +[configure permalinks]: /configuration/permalinks/ +[configure privacy]: /configuration/privacy/ +[configure related content]: /configuration/related-content/ +[configure roles]: /configuration/roles/ +[configure security]: /configuration/security/ +[configure segments]: /configuration/segments/ +[configure server]: /configuration/server/ +[configure services]: /configuration/services/ +[configure sitemap]: /configuration/sitemap/ +[configure taxonomies]: /configuration/taxonomies/ +[configure ugly URLs]: /configuration/ugly-urls/ +[configure versions]: /configuration/versions/ +[define-automatically]: /content-management/menus/#define-automatically +[details]: /hugo-modules/theme-components/ +[duration]: https://pkg.go.dev/time#Duration +[module mounts]: /configuration/module/#mounts +[non-spacing marks]: https://www.compart.com/en/unicode/category/Mn +[relative-urls]: /content-management/urls/#relative-urls +[template-metrics]: /troubleshooting/performance/#template-metrics +[this configuration]: https://github.com/bep/hugo-sass-test/blob/6c3960a8f4b90e8938228688bc49bdcdd6b2d99e/.circleci/config.yml diff --git a/docs/content/en/configuration/build.md b/docs/content/en/configuration/build.md new file mode 100644 index 0000000..595b0e8 --- /dev/null +++ b/docs/content/en/configuration/build.md @@ -0,0 +1,85 @@ +--- +title: Configure build +linkTitle: Build +description: Configure global build options. +categories: [] +keywords: [] +aliases: [/getting-started/configuration-build/] +--- + +This is the default configuration: + +{{< 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][]. This file is intended to help with intellisense/navigation inside code editors such as [VS Code][]. 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` setting 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][] 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`. + +`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 `hugo build`. + +[Hugo Starter Tailwind Basic]: https://github.com/bep/hugo-starter-tailwind-basic +[VS Code]: https://code.visualstudio.com/ +[`config/production`]: /configuration/introduction/#configuration-directory +[js.Build]: /functions/js/build/ +[removing unused CSS]: /functions/resources/postprocess/ diff --git a/docs/content/en/configuration/caches.md b/docs/content/en/configuration/caches.md new file mode 100644 index 0000000..a83d55a --- /dev/null +++ b/docs/content/en/configuration/caches.md @@ -0,0 +1,63 @@ +--- +title: Configure file caches +linkTitle: Caches +description: Configure file caches. +categories: [] +keywords: [] +--- + +This is the default configuration: + +{{< code-toggle config=caches />}} + +## Purpose + +Hugo uses file caches to store data on disk, avoiding repeated operations within the same build and persisting data from one build to the next. + +`assets` +: Caches processed CSS and Sass resources. + +`getresource` +: Caches files fetched from remote URLs via the [`resources.GetRemote`][] function. + +`images` +: Caches processed images. + +`misc` +: Caches miscellaneous data. + +`modulegitinfo` +: Caches Git information for modules. + +`modulequeries` +: Caches the results of module resolution queries. + +`modules` +: Caches downloaded modules. + +## Keys + +`dir` +: (`string`) The absolute file system path where Hugo stores the cached files. You can begin the path with the `:cacheDir` or `:resourceDir` [tokens](#tokens) to anchor the cache to specific system or project locations. + +`maxAge` +: (`string`) The duration a cached entry remains valid before being evicted, expressed as a [duration](g). A value of `0` disables the cache for that key, and a value of `-1` means the cache entry never expires. Default is `-1`. + +## Tokens + +`:cacheDir` +: (`string`) The designated cache directory. See [details][cachedir]. + +`:project` +: (`string`) The base directory name of the current Hugo project. This ensures isolated file caches for each project, preventing the `hugo build --gc` command from affecting other projects on the same machine. + +`:resourceDir` +: (`string`) The designated directory for caching output from [asset pipelines](g). See [details][resourcedir]. + +## Garbage collection + +As you modify your site or change your configuration, cached files from previous builds may remain on disk, consuming unnecessary space. Use the `hugo build --gc` command to remove these expired or unused entries from the file cache. + +[`resources.GetRemote`]: /functions/resources/getremote/ +[cachedir]: /configuration/all/#cachedir +[resourcedir]: /configuration/all/#resourcedir diff --git a/docs/content/en/configuration/cascade.md b/docs/content/en/configuration/cascade.md new file mode 100644 index 0000000..6ef4b71 --- /dev/null +++ b/docs/content/en/configuration/cascade.md @@ -0,0 +1,62 @@ +--- +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 [branch](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 the `color` page parameter to all pages: + +{{< code-toggle file=hugo >}} +[cascade.params] +color = 'red' +{{< /code-toggle >}} + +## Target + + + +The `target` key accepts a [page matcher](g) to limit cascaded values to a subset of pages.[^1] If a target is omitted, values cascade to all pages. + +{{% include "/_common/configuration/page-matcher.md" %}} + +For example, to cascade the `color` page parameter to the `articles` section and its descendants, but only for the English (`en`) and German (`de`) language sites: + +{{< code-toggle file=hugo >}} +[cascade.params] +color = 'red' +[cascade.target] +path = '{/articles,/articles/**}' +[cascade.target.sites.matrix] +languages = '{en,de}' +{{< /code-toggle >}} + +## Array + +Define an array of cascade maps to apply different values to different targets. For example: + +{{< code-toggle file=hugo >}} +[[cascade]] +[cascade.params] +color = 'red' +[cascade.target] +path = '{/articles,/articles/**}' +[[cascade]] +[cascade.params] +color = 'blue' +[cascade.target] +path = '{/tutorials,/tutorials/**}' +{{< /code-toggle >}} + +[^1]: The `_target` alias for `target` is deprecated and will be removed in a future release. + +[details]: /content-management/front-matter/#cascade-1 diff --git a/docs/content/en/configuration/content-types.md b/docs/content/en/configuration/content-types.md new file mode 100644 index 0000000..568b969 --- /dev/null +++ b/docs/content/en/configuration/content-types.md @@ -0,0 +1,63 @@ +--- +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): + +```tree +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: + +```tree +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 new file mode 100644 index 0000000..eebbcaf --- /dev/null +++ b/docs/content/en/configuration/deployment.md @@ -0,0 +1,159 @@ +--- +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][hugo deploy]. + +## Top-level settings + +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 pattern](g) 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 pattern](g) 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. + +## 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][] + +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 >}} + +[Ceph]: https://ceph.com/ +[MinIO]: https://www.minio.io/ +[SeaweedFS]: https://github.com/chrislusf/seaweedfs +[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 +[hugo deploy]: /host-and-deploy/deploy-with-hugo-deploy/ diff --git a/docs/content/en/configuration/front-matter.md b/docs/content/en/configuration/front-matter.md new file mode 100644 index 0000000..1e85c62 --- /dev/null +++ b/docs/content/en/configuration/front-matter.md @@ -0,0 +1,104 @@ +--- +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. + +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 the following [tokens](g) to help you configure your front matter: + +`:default` +: The default ordered sequence of date fields. + +`:fileModTime` +: The file's last modification timestamp. + +`:filename` +: Extracts the date from the file name, provided the file name begins with a date in one of the following formats: + + - `YYYY-MM-DD` + - `YYYY-MM-DD-HH-MM-SS` {{< new-in 0.148.0 />}} + + Within the `YYYY-MM-DD-HH-MM-SS` format, the date and time values may be separated by any character including a space (e.g., `2025-02-01T14-30-00`). + + Hugo resolves the extracted date to the [`timeZone`][] defined in your project configuration, falling back to the system time zone. Hugo also derives the page [`slug`][] from the remaining file name, unless the page already defines a `slug` in its front matter. + + Slug inference only occurs when `:filename` is the winning date source. If an earlier entry in the list provides a valid date, Hugo skips `:filename` entirely. For example, with `date = ["date", ":filename"]`, a page that defines `date` in its front matter will use that value, and the slug will not be inferred from the file name. + + For example, if you name your file `2025-02-01-article.md`, Hugo will set the date to `2025-02-01` and the slug to `article`. + +`:git` +: The Git author date for the file's last revision. To enable access to the Git author date, set [`enableGitInfo`][] to `true`. + +## Example + +Consider this project configuration: + +{{< code-toggle file=hugo >}} +[frontmatter] +date = [':filename', ':default'] +publishDate = [':filename', ':default'] +lastmod = ['lastmod', ':fileModTime'] +{{< /code-toggle >}} + +To determine `date` and `publishDate`, Hugo tries to extract the value 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. + +[`Date`]: /methods/page/date/ +[`ExpiryDate`]: /methods/page/expirydate/ +[`Lastmod`]: /methods/page/lastmod/ +[`PublishDate`]: /methods/page/publishdate/ +[`enableGitInfo`]: /configuration/all/#enablegitinfo +[`slug`]: /content-management/front-matter/#slug +[`timeZone`]: /configuration/all/#timezone diff --git a/docs/content/en/configuration/http-cache.md b/docs/content/en/configuration/http-cache.md new file mode 100644 index 0000000..de7f1b0 --- /dev/null +++ b/docs/content/en/configuration/http-cache.md @@ -0,0 +1,129 @@ +--- +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. + +This is the default configuration for HTTP caching: + +{{< code-toggle config=HTTPCache />}} + +`respectCacheControlNoStoreInRequest` +: {{< new-in 0.151.0 />}} +: (`bool`) Whether to respect the `no-store` directive in the server's `Cache-Control` request header when fetching remote resources via the [`resources.GetRemote`][] function. Default is `true`. + +`respectCacheControlNoStoreInResponse` +: {{< new-in 0.151.0 />}} +: (`bool`) Whether to respect the `no-store` directive in the server's `Cache-Control` response header when fetching remote resources via the [`resources.GetRemote`][] function. Default is `false`. + +`cache.for.excludes` +: (`[]string`) A slice of [glob patterns](g) to exclude from caching. In its default configuration HTTP caching excludes all files. + +`cache.for.includes` +: (`[]string`) A slice of [glob patterns](g) to cache. + +`polls` +: (`[]PollConfig`) A slice of polling configurations. + +`polls.disable` +: (`bool`) Whether to disable polling for this configuration. Default is `true`. + +`polls.high` +: (`string`) The maximum polling interval expressed as a [duration](g). This is used when the resource is considered stable. Default is `0s`. + +`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`. Default is `0s`. + +`polls.for.excludes` +: (`[]string`) A slice of [glob patterns](g) to exclude from polling for this configuration. + +`polls.for.includes` +: (`[]string`) A slice of [glob patterns](g) to include in polling for this configuration. + +## 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` +: (`[]PollConfig`) A slice of polling configurations. + +`polls.disable` +: (`bool`) Whether to disable polling for this configuration. Default is `true`. + +`polls.high` +: (`string`) The maximum polling interval expressed as a [duration](g). This is used when the resource is considered stable. Default is `0s`. + +`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`. Default is `0s`. + +`polls.for.excludes` +: (`[]string`) A list of [glob patterns](g) to exclude from polling for this configuration. + +`polls.for.includes` +: (`[]string`) A list of [glob patterns](g) 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][]. + +[304]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304 +[RFC 9111]: https://datatracker.ietf.org/doc/html/rfc9111 +[`resources.GetRemote`]: /functions/resources/getremote/ +[cache-busting]: /configuration/build/#cache-busters +[configure file caches]: /configuration/caches/ diff --git a/docs/content/en/configuration/imaging.md b/docs/content/en/configuration/imaging.md new file mode 100644 index 0000000..1675541 --- /dev/null +++ b/docs/content/en/configuration/imaging.md @@ -0,0 +1,155 @@ +--- +title: Configure imaging +linkTitle: Imaging +description: Configure imaging. +categories: [] +keywords: [] +--- + +These are the default settings for processing images: + +{{< code-toggle config=imaging />}} + +## Top-level settings + +These settings apply to all image formats. + +`anchor` +: (`string`) The focal point used when cropping or filling an image. Valid case-insensitive options include `TopLeft`, `Top`, `TopRight`, `Left`, `Center`, `Right`, `BottomLeft`, `Bottom`, `BottomRight`, or `Smart`. The `Smart` option utilizes the [`muesli/smartcrop`][] package to identify the most interesting area of the image. Default is `smart`. + +`bgColor` +: (`string`) The background color used when converting transparent images to formats that do not support transparency, such as PNG to JPEG. This color also fills the empty space created when rotating an image by a non-orthogonal angle if the space is not transparent and a background color is not specified in the processing specification. The value must be an RGB [hexadecimal color][]. Default is `#ffffff`. + +`compression` +: {{< deprecated-in 0.163.0 />}} +: Use the format-specific `compression` setting instead, applicable to [AVIF](#avif) and [WebP](#webp) images. + +`hint` +: {{< deprecated-in 0.163.0 />}} +: Use the format-specific `hint` setting instead, applicable to [AVIF](#avif) and [WebP](#webp) images. + +`quality` +: {{< deprecated-in 0.163.0 />}} +: Use the format-specific `quality` setting instead, applicable to [AVIF](#avif), [JPEG](#jpeg), and [WebP](#webp) images. + +`resampleFilter` +: (`string`) The algorithm used to calculate new pixels when resizing, fitting, or filling an image. Common case-insensitive options include `box`, `lanczos`, `catmullRom`, `mitchellNetravali`, `linear`, or `nearestNeighbor`. Default is `box`. + + 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 + + Refer to the [source documentation][] for a complete list of available resampling filters. If you wish to improve image quality at the expense of performance, you may wish to experiment with the alternative filters. + +## AVIF + +{{< new-in 0.162.0 />}} + +These settings apply when encoding AVIF images. + +> [!NOTE] +> When exporting HDR AVIF images from Lightroom, in the Export dialog under File Settings, uncheck Maximize Compatibility to improve Hugo's AVIF decoding speed. + +> [!NOTE] +> Encoding animated images to AVIF produces a single-frame (static) image. Converting an animated AVIF to another format such as GIF works as expected. + +{{< code-toggle config=imaging.avif />}} + +`compression` +: {{< new-in 0.163.0 />}} +: (`string`) The encoding strategy. Options are `lossy` or `lossless`. Default is `lossy`. + +`encoderSpeed` +: (`int`) The encoder speed. Expressed as a whole number from `1` to `10`, inclusive, equivalent to the `-s` flag for the [`avifenc`][] CLI. Lower numbers reduce file size at the cost of build time. At typical web image sizes, quality is indistinguishable across settings. Values below `5` may cause significantly longer build times. Default is `10`. + +`hint` +: {{< new-in 0.163.0 />}} +: (`string`) The content hint. Valid options include `drawing`, `icon`, `photo`, `picture`, or `text`. Hugo uses the `4:2:0` chroma subsampling format with `photo` and `picture`, and `4:4:4` with the remaining options. Default is `photo`. + + 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 + +`quality` +: {{< new-in 0.163.0 />}} +: (`int`) The visual fidelity when using `lossy` compression. Expressed as a whole number from `1` to `100`, inclusive. Lower numbers prioritize smaller file size, while higher numbers prioritize visual clarity. Default is `60`. Quality values are encoder-specific and not directly comparable across formats; a value of `60` for AVIF is perceptually similar to `75` for JPEG. + +## JPEG + +{{< new-in 0.163.0 />}} + +These settings apply when encoding JPEG images. + +{{< code-toggle config=imaging.jpeg />}} + +`quality` +: (`int`) The visual fidelity. Expressed as a whole number from `1` to `100`, inclusive. Lower numbers prioritize smaller file size, while higher numbers prioritize visual clarity. Default is `75`. + +## WebP + +{{< new-in 0.155.0 />}} + +These settings apply when encoding WebP images. + +{{< code-toggle config=imaging.webp />}} + +`compression` +: {{< new-in 0.163.0 />}} +: (`string`) The encoding strategy. Options are `lossy` or `lossless`. Default is `lossy`. + +`hint` +: (`string`) The content hint, equivalent to the `-preset` flag for the [`cwebp`][] CLI. Valid options include `drawing`, `icon`, `photo`, `picture`, or `text`. Default is `photo`. + + 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 + +`method` +: (`int`) The effort level of the compression algorithm. Expressed as a whole number from `0` to `6`, inclusive, equivalent to the `-m` flag for the [`cwebp`][] CLI. Lower numbers prioritize processing speed, while higher numbers prioritize compression efficiency and image quality. Default is `2`. + +`quality` +: {{< new-in 0.163.0 />}} +: (`int`) The visual fidelity when using `lossy` compression. Expressed as a whole number from `1` to `100`, inclusive. Lower numbers prioritize smaller file size, while higher numbers prioritize visual clarity. Default is `75`. + +`useSharpYuv` +: (`bool`) The conversion method used for RGB-to-YUV encoding, equivalent to the `-sharp_yuv` flag for the [`cwebp`][] CLI. Enabling this prioritizes image sharpness at the expense of processing speed. Default is `false`. + +## Exif method + +{{< deprecated-in 0.155.0 >}} +Use the [`Meta`](#meta-method) method instead. +{{< /deprecated-in >}} + +## Meta method + +{{< new-in 0.155.0 />}} + +The following parameters allow you to control how Hugo extracts and filters metadata when using the [`Meta`][] method, helping you balance data granularity with build performance. + +`fields` +: (`[]string`) A [glob slice](g) matching the fields to include when extracting metadata. If empty, a default set excluding technical metadata is used. Set to `['**']` to include all fields. + + > [!NOTE] + > By default, to improve performance and decrease cache size, Hugo excludes the following fields: `ColorSpace`, `Contrast`, `Exif`, `ExposureBias`, `ExposureMode`, `ExposureProgram`, `Flash`, `GPS`, `JPEG`, `Metering`, `Resolution`, `Saturation`, `Sensing`, `Sharp`, and `WhiteBalance`. + +`sources` +: (`[]string`) The metadata sources to include, one or more of `exif`, `iptc`, or `xmp`. Default is `['exif', 'iptc']`. The XMP metadata is excluded by default to improve performance. + +[`avifenc`]: https://github.com/aomediacodec/libavif +[`cwebp`]: https://developers.google.com/speed/webp/docs/cwebp +[`muesli/smartcrop`]: https://github.com/muesli/smartcrop +[hexadecimal color]: https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color +[source documentation]: https://github.com/disintegration/imaging#image-resizing diff --git a/docs/content/en/configuration/introduction.md b/docs/content/en/configuration/introduction.md new file mode 100644 index 0000000..dc31229 --- /dev/null +++ b/docs/content/en/configuration/introduction.md @@ -0,0 +1,315 @@ +--- +title: Introduction +description: Configure your site using files, directories, and environment variables. +categories: [] +keywords: [] +weight: 10 +--- + +## Sensible defaults + +Hugo offers many configuration settings, but its defaults are often sufficient. A new project requires only these settings: + +{{< code-toggle file=hugo >}} +baseURL = 'https://example.org/' +locale = '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 project configuration file in the root of your project directory, naming it `hugo.toml`, `hugo.yaml`, or `hugo.json`, with that order of precedence. + +```tree +my-project/ +└── hugo.toml +``` + +A simple example: + +{{< code-toggle file=hugo >}} +baseURL = 'https://example.org/' +locale = '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 project, use the `--config` flag: + +```sh +hugo build --config other.toml +``` + +Combine two or more configuration files, with left-to-right precedence: + +```sh +hugo build --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 project configuration file, split your configuration by [environment](g), root configuration key, and language. For example: + +```tree +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 >}}. + +### Root key + +{{< new-in 0.162.0 />}} + +When splitting the configuration by root key, you may omit or include 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 >}} + +This also applies to keys whose values are maps of slices, such as `menus`. For example, these are equivalent: + +{{< code-toggle file=config/_default/menus >}} +[[main]] +name = 'Home' +pageRef = '/' +weight = 10 +{{< /code-toggle >}} + +{{< code-toggle file=config/_default/menus >}} +[[menus.main]] +name = 'Home' +pageRef = '/' +weight = 10 +{{< /code-toggle >}} + +For pure slice-typed keys such as `cascade` and `permalinks`, including the root key is required. For example: + +{{< code-toggle file=config/_default/cascade >}} +[[cascade]] +[cascade.params] +color = 'red' +[cascade.target] +path = '/articles/**' +{{< /code-toggle >}} + +> [!NOTE] +> Hugo unwraps the root key only when it is the sole top-level key in the file and matches the file's basename. + +### Recursive parsing + +Hugo parses the `config` directory recursively, allowing you to organize the files into subdirectories. For example: + +```tree +my-project/ +└── config/ + └── _default/ + ├── navigation/ + │ ├── menus.de.toml + │ └── menus.en.toml + └── hugo.toml +``` + +### Example + +```tree +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 build --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 project 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 build`. 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 build --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: + +```tree +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/' +locale = '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 />}} + +> [!NOTE] +> Hugo can merge map configuration values from modules and themes into the project configuration, but cannot merge slice values. This applies to top-level slice keys such as `menus`, as well as to map keys whose values are slices, such as the per-kind format lists in `outputs`. + +## Environment variables + +You can also configure settings using operating system environment variables: + +```sh +export HUGO_BASEURL=https://example.org/ +export HUGO_ENABLEGITINFO=true +hugo +``` + +The above configures the [`baseURL`][] and [`enableGitInfo`][] settings 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) platforms 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_ENVIRONMENT` +: (`string`) The build environment. Default is `production` when running `hugo build` and `development` when running `hugo server`. + +`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` +: (`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 running `hugo build --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 project 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 +``` + +[Google tag ID]: https://support.google.com/tagmanager/answer/12326985?hl=en +[JSON]: https://datatracker.ietf.org/doc/html/rfc7159 +[TOML]: https://toml.io/en/latest +[YAML]: https://yaml.org/spec/ +[`baseURL`]: /configuration/all#baseurl +[`enableGitInfo`]: /configuration/all#enablegitinfo +[permitted delimiter]: https://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap08.html diff --git a/docs/content/en/configuration/languages.md b/docs/content/en/configuration/languages.md new file mode 100644 index 0000000..6313509 --- /dev/null +++ b/docs/content/en/configuration/languages.md @@ -0,0 +1,198 @@ +--- +title: Configure languages +linkTitle: Languages +description: Configure the languages in your multilingual project. +categories: [] +keywords: [] +--- + +## Base settings + +Configure the following base settings: + +{{< code-toggle file=hugo >}} +defaultContentLanguage = 'en' +defaultContentLanguageInSubdir = false +disableDefaultLanguageRedirect = false +disableLanguages = [] +{{< /code-toggle >}} + +`defaultContentLanguage` +: (`string`) The projects's default content language, 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 content language to a subdirectory matching the [`defaultContentLanguage`](#defaultcontentlanguage). Default is `false`. + +`disableDefaultLanguageRedirect` +: {{< new-in 0.140.0 />}} +: (`bool`) Whether to disable generation of the alias redirect for the default content language. When [`defaultContentLanguageInSubdir`](#defaultcontentlanguageinsubdir) is `true`, this setting prevents the root directory from redirecting to the language subdirectory. Conversely, when `defaultContentLanguageInSubdir` is `false`, this setting prevents the language subdirectory from redirecting to the root directory. This is superseded by the more general [`disableDefaultSiteRedirect`][] setting. 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). + +`direction` +: (`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.Direction`][] method on a `Site` or `Page` object. Default is `ltr`. + +`disabled` +: (`bool`) Whether to disable this language when building the site. Default is `false`. + +`label` +: (`string`) The language name, typically used when rendering a language switcher. Access this value from a template using the [`Language.Label`][] method on a `Site` or `Page` object. + +`languageCode` +: {{}} +: Use [`locale`](#locale) instead. + +`languageDirection` +: {{}} +: Use [`direction`](#direction) instead. + +`languageName` +: {{}} +: Use [`label`](#label) instead. + +{{% include "/_common/configuration/locale.md" %}} + +`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. + +## Sort order + +Hugo sorts languages by weight in ascending order, then lexicographically in ascending order. This affects build order and complement selection. + +## Localized settings + +Some configuration settings can be defined separately for each language. For example: + +{{< code-toggle file=hugo >}} +[languages.en] +label = 'English' +locale = 'en-US' +timeZone = 'America/New_York' +title = 'Project Documentation' +weight = 1 +[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 your project 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' +direction = 'ltr' +disabled = false +label = 'Deutsch' +locale = 'de-DE' +title = 'Projekt Dokumentation' +weight = 1 + +[languages.de.params] +subtitle = 'Referenz, Tutorials und Erklärungen' + +[languages.en] +contentDir = 'content/en' +direction = 'ltr' +disabled = false +label = 'English' +locale = 'en-US' +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.en] +baseURL = 'https://en.example.org/' +label = 'English' +title = 'In English' +weight = 2 +[languages.fr] +baseURL = 'https://fr.example.org' +label = 'Français' +title = 'En Français' +weight = 1 +{{}} + +With the above, Hugo publishes two sites, each with their own root: + +```tree +public +├── en +└── fr +``` + +[RFC 5646 § 2.2.7]: https://datatracker.ietf.org/doc/html/rfc5646#section-2.2.7 +[RFC 5646]: https://datatracker.ietf.org/doc/html/rfc5646#section-2.1 +[`Language.Direction`]: /methods/site/language/#direction +[`Language.Label`]: /methods/site/language/#label +[`Title`]: /methods/site/title/ +[`dir`]: https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/dir +[`disableDefaultSiteRedirect`]: /configuration/all/#disabledefaultsiteredirect +[language keys]: /configuration/languages/#language-keys +[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 new file mode 100644 index 0000000..95bdd00 --- /dev/null +++ b/docs/content/en/configuration/markup.md @@ -0,0 +1,365 @@ +--- +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 project 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 + +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[^1], if you enable the "subscript" feature of the Extras extension, you must disable the Strikethrough extension: + +{{< code-toggle file=hugo >}} +[markup.goldmark.extensions] +strikethrough = false + +[markup.goldmark.extensions.extras.subscript] +enable = true +{{< /code-toggle >}} + +If you still need to show deleted text after disabling the Strikethrough extension, enable the "deleted text" feature of the Extras extension: + +{{< code-toggle file=hugo >}} +[markup.goldmark.extensions] +strikethrough = false + +[markup.goldmark.extensions.extras.delete] +enable = true +{{< /code-toggle >}} + +With this configuration, to format text as deleted, wrap it with double-tildes. + +#### Footnote + +Enabled by default, the Footnote extension enables inclusion of footnotes in Markdown. + +`enable` +: {{< new-in 0.151.0 />}} +: (`bool`) Whether to enable the Footnotes extension. Default is `true`. + +`backlinkHTML` +: {{< new-in 0.151.0 />}} +: (`string`) The HTML to be displayed at the end of a footnote that links the user back to the corresponding reference in the main text. The default is ↩︎ (a return arrow symbol). + +`enableAutoIDPrefix` +: {{< new-in 0.151.0 />}} +: (`bool`) Whether to prepend a unique prefix to footnote IDs, preventing clashes when multiple documents are rendered together. This prefix is unique to each logical path, which means that the prefix is not unique across content dimensions such as language. Default is `false`. + +#### Passthrough + +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 + +### Goldmark settings explained + +Most of the Goldmark settings above are self-explanatory, but some require explanation. + +`duplicateResourceFiles` +: (`bool`) Whether to duplicate shared page resources for each language on multilingual single-host projects. See [multilingual page resources][] for details. Default is `false`. + + > [!NOTE] + > With multilingual single-host projects, 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 projects. + +`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`. Default is `github`. + + - `github`: Generate GitHub-compatible `id` attributes + - `github-ascii`: Drop any non-ASCII characters after accent normalization + - `blackfriday`: Generate `id` attributes compatible with the Blackfriday Markdown renderer + + This is also the strategy used by the [`urls.Anchorize`][] function. + +`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` +: {{< deprecated-in 0.148.0 />}} +: Use the `renderHooks.image.useEmbedded` setting instead. + +`renderHooks.image.useEmbedded` +: {{< new-in 0.148.0 />}} +: (`string`) When to use the [embedded image render hook][]. One of `auto`, `never`, `always`, or `fallback`. Default is `auto`. + + - `auto`: Use the embedded image render hook only for multilingual single-host projects where the [duplication of shared page resources][] feature is disabled. If custom image render hooks are defined by your project, modules, or themes, these will be used instead. + - `never`: Never use the embedded image render hook. If custom image render hooks are defined by your project, modules, or themes, these will be used instead. + - `always`: Always use the embedded image render hook, even if custom image render hooks are provided by your project, modules, or themes. + - `fallback`: Use the embedded image render hook only if custom image render hooks are not provided by your project, modules, or themes. If custom image render hooks exist, these will be used instead. + +`renderHooks.link.enableDefault` +: {{< deprecated-in 0.148.0 />}} +: Use the `renderHooks.link.useEmbedded` setting instead. + +`renderHooks.link.useEmbedded` +: (`string`) When to use the [embedded link render hook][]. One of `auto`, `never`, `always`, or `fallback`. Default is `auto`. + + - `auto`: Use the embedded link render hook only for multilingual single-host projects where the [duplication of shared page resources][] feature is disabled. If custom link render hooks are defined by your project, modules, or themes, these will be used instead. + - `never`: Never use the embedded link render hook. If custom link render hooks are defined by your project, modules, or themes, these will be used instead. + - `always`: Always use the embedded link render hook, even if custom link render hooks are provided by your project, modules, or themes. + - `fallback`: Use the embedded link render hook only if custom link render hooks are not provided by your project, modules, or themes. If custom link render hooks exist, these will be used instead. + +`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 />}} + +### AsciiDoc 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`) An array of enabled extensions, such as `asciidoctor-html5s`, `asciidoctor-bibtex`, or `asciidoctor-diagram`. + + > [!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 project configuration. For example: + + {{< code-toggle file=hugo >}} + [markup.asciidocExt.attributes] + source-highlighter = 'rouge' + {{< /code-toggle >}} + +Step 2 +: Generate the highlighter CSS. For example: + + ```sh + 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/baseof.html"} + + ... + {{ with resources.Get "css/syntax.css" }} + + {{ end }} + ... + + ``` + +Step 4 +: Add the code to be highlighted to your markup: + + ```text + [#hello,ruby] + ---- + require 'sinatra' + + get '/hi' do + "Hello World!" + end + ---- + ``` + +### Troubleshooting + +Run `hugo build --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`. + +[^1]: See [details](https://github.com/gohugoio/hugo-goldmark-extensions/commit/4d4fcd022fe45a9b51483df001c9e5f4e632d5a9). + +[AsciiDoc]: https://asciidoc.org/ +[CommonMark]: https://spec.commonmark.org/current/ +[Emacs Org Mode]: https://orgmode.org/ +[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- +[GitHub Flavored Markdown]: https://github.github.com/gfm/ +[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 +[Goldmark]: https://github.com/yuin/goldmark/ +[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 +[Markdown attributes]: /content-management/markdown-attributes/ +[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 +[Pandoc]: https://pandoc.org/ +[`Fragments.Identifiers`]: /methods/page/fragments/#identifiers +[`TableOfContents`]: /methods/page/tableofcontents/ +[`urls.Anchorize`]: /functions/urls/anchorize/ +[asciidoctor-diagram]: https://asciidoctor.org/docs/asciidoctor-diagram/ +[attributes]: https://asciidoctor.org/docs/asciidoc-syntax-quick-reference/#attributes-and-substitutions +[deleted text]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/del +[duplication of shared page resources]: /configuration/markup/#duplicateresourcefiles +[embedded image render hook]: /render-hooks/images/#embedded +[embedded link render hook]: /render-hooks/links/#embedded +[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 +[mathematics in Markdown]: /content-management/mathematics/ +[multilingual page resources]: /content-management/page-resources/#multilingual +[reStructuredText]: https://docutils.sourceforge.io/rst.html +[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 diff --git a/docs/content/en/configuration/media-types.md b/docs/content/en/configuration/media-types.md new file mode 100644 index 0000000..546761f --- /dev/null +++ b/docs/content/en/configuration/media-types.md @@ -0,0 +1,82 @@ +--- +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 new file mode 100644 index 0000000..a88ec7b --- /dev/null +++ b/docs/content/en/configuration/menus.md @@ -0,0 +1,137 @@ +--- +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 your project configuration + +This page covers the project 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 >}} + + +[Automatically]: /content-management/menus/#define-automatically +[`Menus`]: /methods/site/menus/ +[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 new file mode 100644 index 0000000..9329879 --- /dev/null +++ b/docs/content/en/configuration/minify.md @@ -0,0 +1,18 @@ +--- +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, but note the following: + +- `css.inline` is for internal use. Changing this setting has no effect. +- `html.keepConditionalComments` has been deprecated. Use `html.keepSpecialComments` instead. + +[`tdewolff/minify`]: https://github.com/tdewolff/minify diff --git a/docs/content/en/configuration/module.md b/docs/content/en/configuration/module.md new file mode 100644 index 0000000..114d0eb --- /dev/null +++ b/docs/content/en/configuration/module.md @@ -0,0 +1,208 @@ +--- +title: Configure modules +linkTitle: Modules +description: Configure modules. +categories: [] +keywords: [] +aliases: [/hugo-modules/configuration/] +--- + +{{% include "/_common/gomodules-info.md" %}} + +## Top-level settings + +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 patterns](g), matching paths that should not use the [configured proxy server](#proxy). + +`noVendor` +: (`string`) A [glob pattern](g) matching module paths to skip when vendoring. + +`private` +: (`string`) A comma-separated list of [glob patterns](g), 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" +``` + +## 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` +: {{< deprecated-in v0.153.0 />}} +: (`bool`) Whether the extended edition of Hugo is required, satisfied by installing either the extended or extended/deploy edition. + + > [!NOTE] + > The extended version check is disabled in v0.153.2 and later. + > + > Historically, certain features—specifically WebP encoding and LibSass—required the Hugo Extended binary. However, as of v0.153.0: + > + > - WebP encoding is now supported in all Hugo editions. + > - LibSass has been deprecated in favor of [Dart Sass][], which is compatible with any Hugo edition. + > + > Because these dependencies no longer require a specialized binary, the internal enforcement check for the extended version has been removed. Site and theme authors are encouraged to use Dart Sass to ensure cross-edition compatibility. + +`max` +: (`string`) The maximum Hugo version supported, for example `0.153.0`. + +`min` +: (`string`) The minimum Hugo version supported, for example `0.102.0`. + +## 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`. + +`usePackageJSON` +: {{< new-in 0.159.0 />}} +: (`string`) Whether to use the import's npm dependencies in [hugo mod npm pack](commands/hugo_mod_npm_pack/). One of `auto` (default), `always` or `never`. When set to `auto`, Hugo will enable this if either there is a Hugo config file (e.g. `hugo.toml`) or a `package.hugo.json` file in the module root. + +`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`][]. + +`version` +: {{< new-in 0.150.0 />}} +: (`string`) If set to a [version query][], this import becomes a direct dependency, in contrast to dependencies managed by Go modules. See [this issue][] for more information. + +## Mounts + +{{% glossary-term mount %}} + +> [!IMPORTANT] +> If you define one or more mounts to map a file system path to a component path, do not use these legacy configuration settings: [`archetypeDir`][], [`assetDir`][], [`contentDir`][], [`dataDir`][], [`i18nDir`][], [`layoutDir`][], or [`staticDir`][]. + +### Default mounts + +Defining a mount for a component within a project configuration removes the default mount for that component. + +Defining a mount for a component within a module configuration removes all default mounts for that module. + +If you still need any of the default mounts, you must explicitly add them along with the new mount. + +These 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 [unified file system](g). It must begin with one of Hugo's [component](g) directories: archetypes, assets, content, data, i18n, layouts, or static. For example, content/blog. + +`disableWatch` +: (`bool`) Whether to disable watching in watch mode for this mount. Default is `false`. + +`excludeFiles` +: {{< deprecated-in 0.153.0 />}} +: Use the [`files`](#files) setting instead. + +`files` +: {{< new-in 0.153.0 />}} +: (`[]string`) A [glob slice](g) defining the files to include or exclude. + +`includeFiles` +: {{< deprecated-in 0.153.0 />}} +: Use the [`files`](#files) setting instead. + +`lang` +: {{< deprecated-in 0.153.0 />}} +: Use the [`sites`](#sites) setting instead. + +`sites` +: {{< new-in 0.153.0 />}} +: (`map`) A map to define [sites matrix](g) and [sites complements](g) for the mount. Relevant for `content` and `layouts` mounts, and `static` mounts when in multihost mode. For `static` and `layouts`, only the `matrix` keyword is supported. + +### Example + +{{< code-toggle file=hugo >}} +[module] +[[module.mounts]] +source = 'content' +target = 'content' +files = ['! docs/*'] +[[module.mounts]] +source = 'node_modules' +target = 'assets' +[[module.mounts]] +source = 'assets' +target = 'assets' +{{< /code-toggle >}} + +[Dart Sass]: /functions/css/sass/#dart-sass +[`archetypeDir`]: /configuration/all/#archetypedir +[`assetDir`]: /configuration/all/#assetdir +[`contentDir`]: /configuration/all/#contentdir +[`dataDir`]: /configuration/all/#datadir +[`i18nDir`]: /configuration/all/#i18ndir +[`layoutDir`]: /configuration/all/#layoutdir +[`staticDir`]: /configuration/all/#staticdir +[`themesDir`]: /configuration/all/#themesdir +[this issue]: https://github.com/gohugoio/hugo/pull/13966 +[version query]: https://go.dev/ref/mod#version-queries diff --git a/docs/content/en/configuration/output-formats.md b/docs/content/en/configuration/output-formats.md new file mode 100644 index 0000000..99ca158 --- /dev/null +++ b/docs/content/en/configuration/output-formats.md @@ -0,0 +1,206 @@ +--- +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. This value determines when the LiveReload script is injected and, in conjunction with [`permalinkable`](#permalinkable), whether [alias redirects][] are generated. 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 project configuration. Default is `false`. + +`path` +: (`string`) The first segment of the publication path for this output format. This path segment is relative to the root of your [`publishDir`][]. If omitted, Hugo will use the file's original content path for publishing. + +`permalinkable` +: (`bool`) Whether to return the rendering output format rather than the main output format when invoking the [`Permalink`][] and [`RelPermalink`][] methods on a `Page` object. Along with [`isHTML`](#ishtml), this must be `true` to create [alias redirects][]. 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 project configuration, typically `https://`. + +`rel` +: (`string`) The relationship of the output format to the current page. Hugo uses this property to determine the [canonical output format](g) of the current page. For the predefined `html` output format, the default value is `canonical`; for all other predefined output formats, the default value 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 project 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/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 a `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 `page.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`](#permalinkable) setting to `true` for that format. + +With `permalinkable` set to true for `json` in the same `page.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/section.html.html` +`json`|`layouts/section.json.json` +`rss`|`layouts/section.rss.xml` + +[`AlternativeOutputFormats`]: /methods/page/alternativeoutputformats/ +[`OutputFormats`]: /methods/page/outputformats/ +[`Permalink`]: /methods/page/permalink/ +[`RelPermalink`]: /methods/page/relpermalink/ +[`baseURL`]: /configuration/all/#baseurl +[`html/template`]: https://pkg.go.dev/html/template +[`publishDir`]: /configuration/all/#publishdir +[`text/template`]: https://pkg.go.dev/text/template +[`uglyURLs`]: /configuration/ugly-urls/ +[alias redirects]: /content-management/urls/#aliases +[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 %}}> +[template lookup order]: /templates/lookup-order/ diff --git a/docs/content/en/configuration/outputs.md b/docs/content/en/configuration/outputs.md new file mode 100644 index 0000000..ff8c58f --- /dev/null +++ b/docs/content/en/configuration/outputs.md @@ -0,0 +1,49 @@ +--- +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 project'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 new file mode 100644 index 0000000..d5ed72a --- /dev/null +++ b/docs/content/en/configuration/page.md @@ -0,0 +1,42 @@ +--- +title: Configure page +linkTitle: Page +description: Configure page behavior. +categories: [] +keywords: [] +--- + +{{% 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`][] and [`Prev`][] +- [`NextInSection`][] and [`PrevInSection`][] + +This is based on this default project configuration: + +{{< code-toggle config=page />}} + +`nextPrevInSectionSortOrder` +: (`string`) The sort order used to determine the _next_ and _previous_ page within the same section when calling [`NextInSection`][] or [`PrevInSection`][] on a `Page` object. Valid values are `asc` (ascending) or `desc` (descending). Default is `desc`. + +`nextPrevSortOrder` +: (`string`) The sort order used to determine the _next_ and _previous_ page when calling [`Next`][] or [`Prev`][] on a `Page` object. Valid values are `asc` (ascending) or `desc` (descending). Default is `desc`. + +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`][next-pages] or [`Prev`][prev-pages] methods on a `Pages` object. + +[`NextInSection`]: /methods/page/nextinsection/ +[`Next`]: /methods/page/next/ +[`PrevInSection`]: /methods/page/previnsection/ +[`Prev`]: /methods/page/prev/ +[next-pages]: /methods/pages/next/ +[prev-pages]: /methods/pages/prev/ diff --git a/docs/content/en/configuration/pagination.md b/docs/content/en/configuration/pagination.md new file mode 100644 index 0000000..ded0b99 --- /dev/null +++ b/docs/content/en/configuration/pagination.md @@ -0,0 +1,45 @@ +--- +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 projects you can define the pagination behavior for each language: + +{{< code-toggle file=hugo >}} +[languages.en] +contentDir = 'content/en' +direction = 'ltr' +label = 'English' +locale = 'en-US' +weight = 1 +[languages.en.pagination] +disableAliases = true +pagerSize = 10 +path = 'page' +[languages.de] +contentDir = 'content/de' +direction = 'ltr' +label = 'Deutsch' +locale = 'de-DE' +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 new file mode 100644 index 0000000..b30ad98 --- /dev/null +++ b/docs/content/en/configuration/params.md @@ -0,0 +1,100 @@ +--- +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/' +locale = 'en-US' +title = 'Project Documentation' +[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: + +```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 projects + +For multilingual projects, create a `params` key under each language: + +{{< code-toggle file=hugo >}} +baseURL = 'https://example.org/' +defaultContentLanguage = 'en' + +[languages.de] +direction = 'ltr' +label = 'Deutsch' +locale = 'de-DE' +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] +direction = 'ltr' +label = 'English' +locale = 'en-US' +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 +``` + +[`Params`]: /methods/site/params/ diff --git a/docs/content/en/configuration/permalinks.md b/docs/content/en/configuration/permalinks.md new file mode 100644 index 0000000..7e50278 --- /dev/null +++ b/docs/content/en/configuration/permalinks.md @@ -0,0 +1,105 @@ +--- +title: Configure permalinks +linkTitle: Permalinks +description: Configure permalinks. +categories: [] +keywords: [] +--- + +Use the `permalinks` configuration to define custom URL patterns for your pages. Hugo supports two forms: a map form for simple section-based patterns, and an array form that supports [page matchers](g) for more precise targeting. + +> [!NOTE] +> The [`url`][] front matter field overrides any matching permalink pattern. + +## Map form + +Define URL patterns for each top-level [section](g), keyed by [page kind](g). For example, to configure URL patterns for the `articles` section: + +{{< code-toggle file=hugo >}} +[permalinks.page] +articles = '/blog/:year/:month/:slug/' +[permalinks.section] +articles = '/blog/' +{{< /code-toggle >}} + +To configure permalinks per language, nest the `permalinks` key under the language key: + +{{< code-toggle file=hugo >}} +[languages] + [languages.de] + label = 'Deutsch' + locale = 'de-DE' + weight = 1 + [languages.de.permalinks] + [languages.de.permalinks.page] + articles = '/artikel/:year/:month/:slug/' + [languages.de.permalinks.section] + articles = '/artikel/' + [languages.en] + label = 'English' + locale = 'en-US' + weight = 2 + [languages.en.permalinks] + [languages.en.permalinks.page] + articles = '/blog/:year/:month/:slug/' + [languages.en.permalinks.section] + articles = '/blog/' +{{< /code-toggle >}} + +## Array form + +{{< new-in 0.161.0 />}} + +Define an array of permalink entries to apply different URL patterns to different subsets of pages. Each entry requires a `pattern` key. Hugo applies the first matching pattern. + +The optional `target` key accepts a [page matcher](g). If `target` is omitted, the pattern applies to all pages. + +{{% include "/_common/configuration/page-matcher.md" %}} + +For example, to apply language-specific URL patterns to the `articles` section page and its leaf pages separately: + +{{< code-toggle file=hugo >}} +[[permalinks]] + pattern = '/artikel/' + [permalinks.target] + path = '{/articles}' + [permalinks.target.sites] + [permalinks.target.sites.matrix] + languages = ['de'] +[[permalinks]] + pattern = '/artikel/:year/:month/:slug/' + [permalinks.target] + path = '{/articles/**}' + [permalinks.target.sites] + [permalinks.target.sites.matrix] + languages = ['de'] +[[permalinks]] + pattern = '/blog/' + [permalinks.target] + path = '{/articles}' + [permalinks.target.sites] + [permalinks.target.sites.matrix] + languages = ['en'] +[[permalinks]] + pattern = '/blog/:year/:month/:slug/' + [permalinks.target] + path = '{/articles/**}' + [permalinks.target.sites] + [permalinks.target.sites.matrix] + languages = ['en'] +{{< /code-toggle >}} + +To define a fallback that matches any page not already matched by a preceding entry, place a pattern without a `target` key at the end: + +{{< code-toggle file=hugo >}} +[[permalinks]] +pattern = '/:section/:slug/' +{{< /code-toggle >}} + +## 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 new file mode 100644 index 0000000..65b5c75 --- /dev/null +++ b/docs/content/en/configuration/privacy.md @@ -0,0 +1,50 @@ +--- +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 project and content creation. Some of these templates interact with external services. For example, the `youtube` shortcode connects with YouTube's servers to embed videos. + +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][] +- [Google Analytics partial][] +- [Instagram shortcode][] +- [Vimeo shortcode][] +- [X shortcode][] +- [YouTube shortcode][] + +[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 new file mode 100644 index 0000000..92cf92e --- /dev/null +++ b/docs/content/en/configuration/related-content.md @@ -0,0 +1,111 @@ +--- +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 project configuration, either globally or per language. + +## Default configuration + +This is the default configuration: + +{{< code-toggle config=related />}} + +> [!NOTE] +> Adding a `related` section to your project configuration requires you to provide a full configuration. You cannot override individual default values without specifying all related settings. + +## Top-level settings + +`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 settings + +`applyFilter` +: (`string`) Apply a `type` specific filter to the result of a search. This is only used for the `fragments` type. + +`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`. + +`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. + +`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`. + +`type` +: (`string`) One of `basic` or `fragments`. Default is `basic`. + +`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`. + +## 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: + +```tree +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 }} +

    Related content:

    + +{{ end }} +``` + +[related content]: /content-management/related-content/ diff --git a/docs/content/en/configuration/roles.md b/docs/content/en/configuration/roles.md new file mode 100644 index 0000000..2d2c49c --- /dev/null +++ b/docs/content/en/configuration/roles.md @@ -0,0 +1,35 @@ +--- +title: Configure roles +linkTitle: Roles +description: Configure roles. +categories: [] +keywords: [] +--- + +{{< new-in 0.153.0 />}} + +This is the default configuration: + +{{< code-toggle config=roles />}} + +## Settings + +Use the following setting to define how Hugo orders roles. + +`weight` +: (`int`) The role [weight](g). + +## Sort order + +Hugo sorts roles by weight in ascending order, then lexicographically in ascending order. This affects build order and complement selection. + +## Example + +The following configuration demonstrates how to define multiple roles with specific weights. + +{{< code-toggle >}} +[roles.guest] +weight = 20 +[roles.member] +weight = 10 +{{< /code-toggle >}} diff --git a/docs/content/en/configuration/security.md b/docs/content/en/configuration/security.md new file mode 100644 index 0000000..9efbc01 --- /dev/null +++ b/docs/content/en/configuration/security.md @@ -0,0 +1,94 @@ +--- +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 allowlists. By default, access is restricted. If a build attempts to use a feature not included in the allowlist, it will fail, providing a detailed message. + +This is the default security configuration: + +{{< code-toggle config=security />}} + +`allowContent` +: {{< new-in 0.162.0 />}} +: (`[]string`) A slice of [regular expressions](g) matching the [media type](g) of [content formats](g) allowed in the `content` directory. By default, the HTML content format (media type `text/html`) is denied. Hugo emits HTML file content verbatim, which could allow arbitrary JavaScript execution. See the [classification][] table for a mapping of content formats to media types. + +`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. + +`node.permissions.disable` +: {{< new-in 0.161.0 />}} +: (`bool`) Whether to disable the Node.js [permission model][]. When `false`, Hugo runs Node.js tools with the `--permission` flag, restricting their file system and resource access to what is explicitly allowed below. Default is `false`. + +`node.permissions.allowAddons` +: {{< new-in 0.161.0 />}} +: (`[]string`) A slice of Node.js tool names permitted to load native addons (`--allow-addons`). + +`node.permissions.allowChildProcess` +: {{< new-in 0.161.0 />}} +: (`[]string`) A slice of Node.js tool names permitted to spawn child processes (`--allow-child-process`). + +`node.permissions.allowRead` +: {{< new-in 0.161.0 />}} +: (`[]string`) A slice of file system paths that Node.js tools are allowed to read (`--allow-fs-read`). Paths are relative to the working directory; `"."` means the working directory itself. Use `"*"` to allow all paths. + +`node.permissions.allowWorker` +: {{< new-in 0.161.0 />}} +: (`[]string`) A slice of Node.js tool names permitted to spawn worker threads (`--allow-worker`). + +`node.permissions.allowWrite` +: {{< new-in 0.161.0 />}} +: (`[]string`) A slice of file system paths that Node.js tools are allowed to write (`--allow-fs-write`). Paths are relative to the working directory; `"."` means the working directory itself. Use `"*"` to allow all paths. + +## Negation rules + +{{< new-in 0.161.0 />}} + +Any pattern in an allowlist can be negated by prefixing it with an exclamation mark (`!`) and one space to turn it into a deny rule. Deny rules take precedence over allow rules. An allowlist composed entirely of deny rules implicitly allows everything it does not deny. An empty allowlist rejects everything. + +For example, to allow all URLs except those pointing to `evil.example.com`: + +```toml +[security.http] +urls = ['.*', '! ^https?://evil\.example\.com'] +``` + +Setting an allowlist to the string `none` will completely disable the associated feature. + +## Environment variables + +You can also override your project 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/ +[classification]: /content-management/formats/#classification +[inline shortcodes]: /content-management/shortcodes/#inline +[permission model]: https://nodejs.org/api/permissions.html#permission-model +[using environment variables]: /configuration/introduction/#environment-variables diff --git a/docs/content/en/configuration/segments.md b/docs/content/en/configuration/segments.md new file mode 100644 index 0000000..0b124ae --- /dev/null +++ b/docs/content/en/configuration/segments.md @@ -0,0 +1,196 @@ +--- +title: Configure segments +linkTitle: Segments +description: Configure your site for segmented rendering. +categories: [] +keywords: [] +--- + +> [!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 an `includes` key and an `excludes` key, both of which accept an array of filters. + +A _filter_ is a collection of one or more conditions, represented as an item in the configuration array. A _condition_ compares a specific page [field](#fields) to a given [glob pattern](g). + +### Evaluation rules + +The evaluation logic adheres to three rules: + +- All conditions within a single filter item must match for that filter to evaluate as true, creating an AND relationship. +- If the `includes` or `excludes` array contains multiple filters, only one filter needs to evaluate as true for the entire array to match, creating an OR relationship. +- The `excludes` array takes absolute precedence. If a page matches any filter in the `excludes` array, Hugo omits it from the segment regardless of whether it matches the `includes` array. + +### Performance optimization + +Using the `excludes` array to target sites or output formats allows Hugo to skip entire groups of pages during evaluation instead of checking every page. This optimization helps with performance in larger setups. + +For example, excluding unwanted output formats is faster: + +{{< code-toggle file=hugo >}} +[segments] + [segments.segment1] + [[segments.segment1.excludes]] + output = '! json' +{{< /code-toggle >}} + +Including only the desired output format is slower: + +{{< code-toggle file=hugo >}} +[segments] + [segments.segment1] + [[segments.segment1.includes]] + output = 'json' +{{< /code-toggle >}} + +## Fields + +`kind` +: (`string`) A [glob pattern](g) matching the [page kind](g). For example: `{taxonomy,term}`. + +`lang` +: {{< deprecated-in 0.153.0 />}} +: Use [`sites`](#sites) instead. + +`output` +: (`string`) A [glob pattern](g) matching the [output format](g) of the page. For example: `{html,json}`. + +`path` +: (`string`) A [glob pattern](g) matching the page's [logical path](g). For example: `{/books,/books/**}`. + +`sites` +: {{< new-in 0.153.0 />}} +: (`map`) A map to define [sites matrix](g). + +## Targeting segments + +To specify which segments Hugo builds, add the [`renderSegments`][] setting to your project configuration: + +{{< code-toggle file=hugo >}} +renderSegments = ['segment1','segment2'] +{{< /code-toggle >}} + +Alternatively, pass the segment names directly to the `--renderSegments` command-line flag during a build: + +```sh +hugo build --renderSegments segment1 +``` + +You can target multiple segments by providing a comma-separated list: + +```sh +hugo build --renderSegments segment1,segment2 +``` + +## Example + + + +Consider a project with this content structure: + +```tree +content/ +├── books/ +│ ├── _index.en.md +│ ├── _index.nb.md +│ ├── _index.nn.md +│ ├── book-1.en.md +│ ├── book-1.nb.md +│ └── book-1.nn.md +├── films/ +│ ├── _index.en.md +│ ├── _index.nb.md +│ ├── _index.nn.md +│ ├── film-1.en.md +│ ├── film-1.nb.md +│ └── film-1.nn.md +├── _index.en.md +├── _index.nb.md +└── _index.nn.md +``` + +And this project configuration: + +{{< code-toggle file=hugo >}} +baseURL = 'https://example.org/' +title = 'Segmentation' +defaultContentLanguage = 'en' +defaultContentLanguageInSubdir = true + +[languages.en] + direction = 'ltr' + label = 'English' + locale = 'en-US' + weight = 1 + +[languages.nb] + locale = 'nb-NO' + direction = 'ltr' + label = 'Bokmål' + weight = 2 + +[languages.nn] + locale = 'nn-NO' + direction = 'ltr' + label = 'Norsk' + weight = 3 + +[segments] + [segments.segment1] + [[segments.segment1.excludes]] + [segments.segment1.excludes.sites.matrix] + languages = ['n*'] + [[segments.segment1.excludes]] + output = 'rss' + [segments.segment1.excludes.sites.matrix] + languages = ['en'] + [[segments.segment1.includes]] + kind = '{home,term,taxonomy}' + [[segments.segment1.includes]] + path = '{/books,/books/**}' + +[taxonomies] + tag = 'tags' +{{< /code-toggle >}} + +When you run this command: + +```sh +hugo build --renderSegments segment1 +``` + +The published project has this structure: + +```tree +public/ +├── en/ +│ ├── books/ +│ │ ├── book-1/ +│ │ │ └── index.html +│ │ └── index.html +│ ├── tags/ +│ │ ├── tag-a/ +│ │ │ └── index.html +│ │ ├── tag-b/ +│ │ │ └── index.html +│ │ └── index.html +│ └── index.html +└── index.html +``` + +[`renderSegments`]: /configuration/all/#rendersegments diff --git a/docs/content/en/configuration/server.md b/docs/content/en/configuration/server.md new file mode 100644 index 0000000..3bccdce --- /dev/null +++ b/docs/content/en/configuration/server.md @@ -0,0 +1,126 @@ +--- +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. + +```tree +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 pattern](g) 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 pattern](g) 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][]. + +{{< 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][]. + +## 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 projects, 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 >}} + +[Content Security Policies]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP +[configuration directory]: /configuration/introduction/#configuration-directory +[single-page applications]: https://en.wikipedia.org/wiki/Single-page_application diff --git a/docs/content/en/configuration/services.md b/docs/content/en/configuration/services.md new file mode 100644 index 0000000..50ff525 --- /dev/null +++ b/docs/content/en/configuration/services.md @@ -0,0 +1,46 @@ +--- +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][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][google-analytics]. To access this value from a template: + + ```go-html-template + {{ .Site.Config.Services.GoogleAnalytics.ID }} + ``` + +`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][rss]. To access this value from a template: + + ```go-html-template + {{ .Site.Config.Services.RSS.Limit }} + ``` + +`x.disableInlineCSS` +: (`bool`) Whether to disable the inline CSS rendered by the embedded `x` shortode. See [details][privacy]. Default is `false`. To access this value from a template: + + ```go-html-template + {{ .Site.Config.Services.X.DisableInlineCSS }} + ``` + +[disqus]: /templates/embedded/#disqus +[google-analytics]: /templates/embedded/#google-analytics +[privacy]: /shortcodes/x/#privacy +[rss]: /templates/rss/ diff --git a/docs/content/en/configuration/sitemap.md b/docs/content/en/configuration/sitemap.md new file mode 100644 index 0000000..7f45a25 --- /dev/null +++ b/docs/content/en/configuration/sitemap.md @@ -0,0 +1,26 @@ +--- +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][changefreqdef]. + +`disable` +: (`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][prioritydef]. + +[changefreqdef]: https://www.sitemaps.org/protocol.html#changefreqdef +[prioritydef]: https://www.sitemaps.org/protocol.html#prioritydef diff --git a/docs/content/en/configuration/taxonomies.md b/docs/content/en/configuration/taxonomies.md new file mode 100644 index 0000000..5bada37 --- /dev/null +++ b/docs/content/en/configuration/taxonomies.md @@ -0,0 +1,68 @@ +--- +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 project configuration to disable the `taxonomy` and `term` page [kinds](g). + +{{< code-toggle file=hugo >}} +disableKinds = ['taxonomy','term'] +{{< /code-toggle >}} + +See the [taxonomies][] section for more information. + +[`disableKinds`]: /configuration/all/#disablekinds +[taxonomies]: /content-management/taxonomies/ diff --git a/docs/content/en/configuration/ugly-urls.md b/docs/content/en/configuration/ugly-urls.md new file mode 100644 index 0000000..17b110a --- /dev/null +++ b/docs/content/en/configuration/ugly-urls.md @@ -0,0 +1,37 @@ +--- +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/configuration/versions.md b/docs/content/en/configuration/versions.md new file mode 100644 index 0000000..b796908 --- /dev/null +++ b/docs/content/en/configuration/versions.md @@ -0,0 +1,37 @@ +--- +title: Configure versions +linkTitle: Versions +description: Configure versions. +categories: [] +keywords: [] +--- + +{{< new-in 0.153.0 />}} + +This is the default configuration: + +{{< code-toggle config=versions />}} + +## Settings + +Use the following setting to define how Hugo orders versions. + +`weight` +: (`int`) The language [weight](g). + +## Sort order + +Hugo sorts versions by weight in ascending order, then by their [semantic version][] in descending order. This affects build order and complement selection. + +## Example + +The following configuration demonstrates how to define multiple versions with specific weights. + +{{< code-toggle >}} +[versions."v1.0.0"] +weight = 20 +[versions."v2.0.0"] +weight = 10 +{{< /code-toggle >}} + +[semantic version]: https://semver.org/ diff --git a/docs/content/en/content-management/_index.md b/docs/content/en/content-management/_index.md new file mode 100644 index 0000000..4e20607 --- /dev/null +++ b/docs/content/en/content-management/_index.md @@ -0,0 +1,8 @@ +--- +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 new file mode 100644 index 0000000..95351e9 --- /dev/null +++ b/docs/content/en/content-management/archetypes.md @@ -0,0 +1,188 @@ +--- +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: + +```tree +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, if you have enabled a theme named `my-theme` and you run this command: + +```sh +hugo new content posts/my-first-post.md +``` + +The archetype lookup order is: + +1. `archetypes/posts.md` +1. `themes/my-theme/archetypes/posts.md` +1. `archetypes/default.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 [`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. + +`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. + +## Date format + +To insert date and time with a different format, use the [`time.Now`][] function: + +{{< 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. + +````md {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: + +```tree +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: + +```tree +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: + +```tree +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 +``` + +[`strings.Replace`]: /functions/strings/replace/ +[`time.Now`]: /functions/time/now/ +[file information]: /methods/page/file/ diff --git a/docs/content/en/content-management/build-options.md b/docs/content/en/content-management/build-options.md new file mode 100644 index 0000000..d3799e7 --- /dev/null +++ b/docs/content/en/content-management/build-options.md @@ -0,0 +1,297 @@ +--- +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` with these defaults: + +{{< 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. + +```tree +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/home.html"} +{{ with .Site.GetPage "/headless" }} + {{ .Content }} + {{ range .Resources.ByType "image" }} + + {{ end }} +{{ end }} +``` + +The published site will have this structure: + +```tree +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. + +```tree +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/home.html"} +{{ with .Site.GetPage "/headless" }} + {{ range .Pages }} + {{ .Content }} + {{ range .Resources.ByType "image" }} + + {{ end }} + {{ end }} +{{ end }} +``` + +The published site will have this structure: + +```tree +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: + +```tree +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/section.html"} +
    + {{ range .Pages }} +
    {{ .Title }}
    +
    {{ .Content }}
    + {{ end }} +
    +``` + +The published site will have this structure: + +```tree +public/ +├── glossary/ +│ └── index.html +└── index.html +``` + +## Example -- publish without listing + +Publish a section's descendant pages without publishing the section page itself. + +```tree +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: + +```tree +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. + +```tree +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: + +```tree +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 new file mode 100644 index 0000000..ed11cfa --- /dev/null +++ b/docs/content/en/content-management/comments.md @@ -0,0 +1,86 @@ +--- +title: Comments +description: Hugo ships with an embedded Disqus partial, 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][], 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 [embedded partial][]. + +## 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][]. + +### Configure Disqus + +Disqus comments require you set a single value in your project configuration: + +{{< code-toggle file=hugo >}} +[services.disqus] +shortname = 'your-disqus-shortname' +{{}} + +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: + +- `params.disqus_identifier` +- `params.disqus_title` +- `params.disqus_url` + +### Render Hugo's embedded Disqus partial + +To render it, add the following code where you want comments to appear: + +```go-html-template +{{ partial "disqus.html" . }} +``` + +## Alternatives + +Commercial commenting systems: + +- [Commentix][] +- [Emote][] +- [FastComments][] +- [Graph Comment][] +- [Hyvor Talk][] +- [IntenseDebate][] +- [ReplyBox][] + +Open-source commenting systems: + +- [Cactus Comments][] +- [Comentario][] +- [Comma][] +- [Discourse][] +- [Giscus][] +- [Isso][] +- [Remark42][] +- [Staticman][] +- [Talkyard][] +- [Utterances][] +- [Zoomment][] + +[Cactus Comments]: https://cactus.chat/docs/integrations/hugo/ +[Comentario]: https://gitlab.com/comentario/comentario/ +[Comma]: https://github.com/Dieterbe/comma/ +[Commentix]: https://www.commentix.com/ +[Discourse]: https://meta.discourse.org/t/embed-discourse-comments-on-another-website-via-javascript/31963 +[Disqus]: https://disqus.com/ +[Emote]: https://emote.com/ +[FastComments]: https://fastcomments.com/commenting-system-for-hugo +[Giscus]: https://giscus.app/ +[Graph Comment]: https://graphcomment.com/ +[Hyvor Talk]: https://talk.hyvor.com/ +[IntenseDebate]: https://intensedebate.com/ +[Isso]: https://isso-comments.de/ +[Remark42]: https://remark42.com/ +[ReplyBox]: https://getreplybox.com/ +[Staticman]: https://staticman.net/ +[Talkyard]: https://blog-comments.talkyard.io/ +[Utterances]: https://utteranc.es/ +[Zoomment]: https://zoomment.com/ +[embedded partial]: /templates/embedded/#disqus +[set up an account]: https://disqus.com/profile/signup/ diff --git a/docs/content/en/content-management/content-adapters.md b/docs/content/en/content-management/content-adapters.md new file mode 100644 index 0000000..413a90a --- /dev/null +++ b/docs/content/en/content-management/content-adapters.md @@ -0,0 +1,348 @@ +--- +title: Content adapters +description: Create content adapters to dynamically add content when building your project. +categories: [] +keywords: [] +--- + +## 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. + +```tree +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/page.html"} + {{ with .Resources.Get "cover.jpg" }} + + {{ end }} + ``` + +`Site` +: (`Site`) Returns the site to which the pages will be added. + + ```go-html-template {file="content/books/_content.gotmpl"} + {{ .Site.Title }} + ``` + + > [!NOTE] + > The `Site` object is not fully initialized while Hugo executes a content adapter. + > Methods that depend on built pages, such as `Site.Pages`, are unavailable at this stage and return an error. + +`Store` +: (`maps.Scratch`) Returns a persistent data structure for storing and manipulating keyed values. The main use case for this is to transfer values between executions when [EnableAllLanguages](#enablealllanguages) is set. See [examples][]. + + ```go-html-template {file="content/books/_content.gotmpl"} + {{ .Store.Set "key" "value" }} + {{ .Store.Get "key" }} + ``` + +`EnableAllLanguages` +: By default, Hugo executes the content adapter only once for the first matching site in the [sites matrix](g). Use this method to expand execution to all languages while maintaining the current role and version. + + For more fine-grained control, define a `sites.matrix` in front matter or in a content mount. + + ```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 }} + ``` + +`EnableAllDimensions` +: By default, Hugo executes the content adapter only once for the first matching site in the [sites matrix](g). Use this method to expand execution to every possible combination of language, version, and role. + + For more fine-grained control, define a `sites.matrix` in front matter or in a content mount. + +## 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] +> When `content.value` is a string, Hugo generates a new resource with a publication path relative to the page. However, if `content.value` is already a resource, Hugo directly uses its value and publishes it relative to the site root. This latter method is more efficient. +> +> 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. + + ```tree + 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 _page_ template to render each book review. + + ```go-html-template {file="layouts/books/page.html" copy=true} + {{ define "main" }} +

    {{ .Title }}

    + + {{ with .Resources.GetMatch "cover.*" }} + {{ .Params.alt }} + {{ end }} + +

    Author: {{ .Params.author }}

    + +

    + ISBN: {{ .Params.isbn }}
    + Rating: {{ .Params.rating }}
    + Review date: {{ .Date | time.Format ":date_long" }} +

    + + {{ with .GetTerms "tags" }} +

    Tags:

    + + {{ end }} + + {{ .Content }} + {{ end }} + ``` + +## Multilingual projects + +With multilingual projects 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 project 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. + +```tree +content/ +└── books/ + ├── _content.de.gotmpl + ├── _content.en.gotmpl + ├── _index.de.md + └── _index.en.md +``` + +### Translations by content directory + +With this project 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: + +```tree +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: + +```tree +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 project. + +[content formats]: /content-management/formats/#classification +[examples]: /methods/page/store/ +[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 new file mode 100644 index 0000000..21b3ec8 --- /dev/null +++ b/docs/content/en/content-management/data-sources.md @@ -0,0 +1,114 @@ +--- +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: + +```tree +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`][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`][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`][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" +``` + +```md {file="content/example.md"} +{{}} +``` + +```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/ +[global-resource]: /functions/transform/unmarshal/#global-resource +[page-resource]: /functions/transform/unmarshal/#page-resource +[remote-resource]: /functions/transform/unmarshal/#remote-resource diff --git a/docs/content/en/content-management/diagrams.md b/docs/content/en/content-management/diagrams.md new file mode 100644 index 0000000..26df451 --- /dev/null +++ b/docs/content/en/content-management/diagrams.md @@ -0,0 +1,260 @@ +--- +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/_markup/render-codeblock-mermaid.html" copy=true} +
    +  {{ .Inner | htmlEscape | safeHTML }}
    +
    +{{ .Page.Store.Set "hasMermaid" true }} +``` + +Then include this snippet at the _bottom_ of your base template, before the closing `body` tag: + +```go-html-template {file="layouts/baseof.html" copy=true} +{{ if .Store.Get "hasMermaid" }} + +{{ end }} +``` + +With that you can use the `mermaid` language in Markdown code blocks: + +````md {file="content/example.md" 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 } """" .│ +└────────────────────────────────────────────────┘ +``` + +[GoAT]: https://github.com/bep/goat +[code block render hook]: /render-hooks/code-blocks/ +[embedded code block render hook]: <{{% eturl render-codeblock-goat %}}> diff --git a/docs/content/en/content-management/formats.md b/docs/content/en/content-management/formats.md new file mode 100644 index 0000000..86cb5ad --- /dev/null +++ b/docs/content/en/content-management/formats.md @@ -0,0 +1,128 @@ +--- +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: + +```tree +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](#classification) table below for a list of markup identifiers and recognized file extensions. + +## 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 [project 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. + +### 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. + +> [!NOTE] +> The HTML content format is denied by default. See [`security.allowContent`][]. + +### 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][]. + +### 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 render the AsciiDoc content format. + +You can configure the AsciiDoc renderer in your [project configuration][configure asciidoc]. + +In its default configuration, Hugo passes these CLI flags when calling the Asciidoctor executable: + +```sh +--no-header-footer +``` + +The CLI flags passed to the Asciidoctor executable depend on configuration. You may inspect the flags when building your project: + +```sh +hugo build --logLevel info +``` + +### 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 render the Pandoc content format. + +Hugo passes these CLI flags when calling the Pandoc executable: + +```sh +--mathjax +``` + +### 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 render the reStructuredText content format. + +Hugo passes these CLI flags when calling the rst2html executable: + +```sh +--leave-comments --initial-header-level=2 +``` + +## 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. + +[AsciiDoc]: https://asciidoc.org/ +[Attributes]: /content-management/markdown-attributes/ +[CommonMark]: https://spec.commonmark.org/current/ +[Docutils]: https://docutils.sourceforge.io/ +[Emacs Org Mode]: https://orgmode.org/ +[Extensions]: /configuration/markup/#extensions +[GitHub Flavored Markdown]: https://github.github.com/gfm/ +[Goldmark]: https://github.com/yuin/goldmark +[HTML]: https://developer.mozilla.org/en-US/docs/Learn_web_development/Getting_started/Your_first_website/Creating_the_content +[Markdown]: https://daringfireball.net/projects/markdown/ +[Mathematics]: /content-management/mathematics/ +[Pandoc]: https://pandoc.org/MANUAL.html#pandocs-markdown +[Render hooks]: /render-hooks/introduction/ +[`security.allowContent`]: /configuration/security/#allowcontent +[configure asciidoc]: /configuration/markup/#asciidoc +[configure goldmark]: /configuration/markup/#goldmark +[details]: /content-management/front-matter/#emacs-org-mode +[front matter]: /content-management/front-matter/ +[reStructuredText]: https://docutils.sourceforge.io/rst.html diff --git a/docs/content/en/content-management/front-matter.md b/docs/content/en/content-management/front-matter.md new file mode 100644 index 0000000..c207037 --- /dev/null +++ b/docs/content/en/content-management/front-matter.md @@ -0,0 +1,341 @@ +--- +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. + +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](#parameters) section for details. + +`aliases` +: (`[]string`) An array of one or more [page-relative](g) or [site-relative](g) paths that should redirect to the current page. Hugo resolves these to [server-relative](g) URLs during the build process. 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 (or array of maps) 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](#cascade-1) 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`) 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`, 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`) The [output formats][] to render. See [configure outputs][] for more information. + +`params` +: (`map`) A map of custom [page parameters](#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][]. Each element supports the `src`, `name`, `title`, and `params` keys. + +`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. + +`sites` +: {{< new-in 0.153.0 />}} +: (`map`) A map to define [sites matrix](g) and [sites complements](g) for the page. + + + + {{< code-toggle file=content/_index.md fm=true >}} + title = 'Home' + [sites.matrix] + languages = ["en","fr"] + versions = ["v1.2.*","v2.*.*"] + roles = ["**"] + [sites.complements] + versions = ["v3.*.*"] + {{< /code-toggle >}} + + + +`slug` +: (`string`) Overrides the last segment of the URL path. Not applicable to `home`, `section`, `taxonomy`, or `term` 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. + +## Parameters + +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. + +## Taxonomies + +Classify content by adding taxonomy terms to front matter. For example, with this project 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/page.html"} +{{ with .GetTerms "tags" }} +

    Tags

    + +{{ end }} +``` + +## Cascade + +> [!NOTE] + > For multilingual projects, defining cascade values in your project configuration is often more efficient. This avoids repeating the same cascade values for each language. See [details][]. + +A [branch](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 branch has already cascaded a value for that same field. + +For example, to cascade the `color` page 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` key accepts a [page matcher](g) to limit cascaded values to a subset of pages.[^1] If a target is not specified, values cascade to all descendant pages. + +{{% include "/_common/configuration/page-matcher.md" %}} + +For example, to cascade the `color` page parameter from the home page to the `articles` section and its descendants: + +{{< code-toggle file=hugo >}} +[cascade.params] +color = 'red' +[cascade.target] +path = '{/articles,/articles/**}' +{{< /code-toggle >}} + +### Array + +Define an array of cascade maps 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 = '{/articles,/articles/**}' +[[cascade]] +[cascade.params] +color = 'blue' +[cascade.target] +path = '{/tutorials,/tutorials/**}' +{{< /code-toggle >}} + +## 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 +``` + +## 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`][] in your project configuration. The order of precedence for determining the time zone is: + +1. The time zone offset in the date/time string +1. The time zone specified in your project configuration +1. The `Etc/UTC` time zone + +[^1]: The `_target` alias for `target` is deprecated and will be removed in a future release. + +[Emacs Org Mode]: https://orgmode.org/ +[JSON]: https://www.json.org/ +[TOML]: https://toml.io/ +[URL management]: /content-management/urls/#slug +[YAML]: https://yaml.org/ +[`Aliases`]: /methods/page/aliases/ +[`Date`]: /methods/page/date/ +[`Description`]: /methods/page/description/ +[`Draft`]: /methods/page/draft/ +[`ExpiryDate`]: /methods/page/expirydate/ +[`FuzzyWordCount`]: /methods/page/wordcount/ +[`GetTerms`]: /methods/page/getterms/ +[`Keywords`]: /methods/page/keywords/ +[`Lastmod`]: /methods/page/date/ +[`Layout`]: /methods/page/layout/ +[`LinkTitle`]: /methods/page/linktitle/ +[`Param`]: /methods/page/param/ +[`Params`]: /methods/page/params/ +[`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/ +[`timeZone`]: /configuration/all/#timezone +[aliases]: /content-management/urls/#aliases +[build options]: /content-management/build-options/ +[configure outputs]: /configuration/outputs/#outputs-per-page +[content format]: /content-management/formats/ +[content formats]: /content-management/formats/#classification +[details]: /configuration/cascade/ +[leaf bundles]: /content-management/page-bundles/#leaf-bundles +[menus]: /content-management/menus/#define-in-front-matter +[output formats]: /configuration/output-formats/ +[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/ diff --git a/docs/content/en/content-management/image-processing/index.md b/docs/content/en/content-management/image-processing/index.md new file mode 100644 index 0000000..9e8fa95 --- /dev/null +++ b/docs/content/en/content-management/image-processing/index.md @@ -0,0 +1,170 @@ +--- +title: Image processing +description: Transform images to change their size, shape, and appearance. +categories: [] +keywords: [] +--- + +Hugo provides methods to transform and analyze images during the build process. While Hugo can manage any image format as a resource, only [processable images](g) can be transformed using the methods below. The results are cached to ensure subsequent builds remain fast. + +> [!NOTE] +> Use the [`reflect.IsImageResourceProcessable`][] function to verify that an image can be processed. + +## Resources + +To process an image you must capture the file as a page resource, a global resource, or a remote resource. + +### Page + +{{% glossary-term "page resource" %}} + +```tree +content/ +└── posts/ + └── post-1/ <-- page bundle + ├── index.md + └── sunset.jpg <-- page resource +``` + +To capture an image as a page resource: + +```go-html-template +{{ $image := .Resources.Get "sunset.jpg" }} +``` + +### Global + +{{% glossary-term "global resource" %}} + +```tree +assets/ +└── images/ + └── sunset.jpg <-- global resource +``` + +To capture an image as a global resource: + +```go-html-template +{{ $image := resources.Get "images/sunset.jpg" }} +``` + +### Remote + +{{% glossary-term "remote resource" %}} + +To capture an image as a remote resource: + +```go-html-template +{{ $image := resources.GetRemote "https://gohugo.io/img/hugo-logo.png" }} +``` + +## Rendering + +Once you have captured an image as a resource, render it in your templates using the [`Permalink`][], [`RelPermalink`][], [`Width`][], and [`Height`][] methods. + +Example 1: Throw an error if the resource is not found. + +```go-html-template +{{ $image := .Resources.GetMatch "sunset.jpg" }} + +``` + +Example 2: Skip 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: Skip 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 }} +``` + +{{% include "/_common/functions/reflect/image-reflection-functions.md" %}} + +## Processing + +To transform an image, apply a processing method to the image resource. Hugo generates the processed image on demand, caches the result, and returns a new resource object. + +```go-html-template +{{ with .Resources.Get "sunset.jpg" }} + {{ with .Resize "400x" }} + + {{ end }} +{{ end }} +``` + +> [!NOTE] +> Metadata is not preserved during image transformation. Use the [`Meta`][] method with the original image resource to extract metadata from supported formats. + +Select a method from the table below for syntax and usage examples, depending on your specific transformation or metadata requirements: + +{{% render-table-of-pages-in-section + path=/methods/resource + filter=methods_resource_image_processing + filterType=include + headingColumn1=Method + headingColumn2=Description +%}}{class="!mt-0"} + +## Performance + +### Caching + +Hugo processes images on demand and returns a new resource object. To ensure subsequent builds remain fast, Hugo caches the results in the directory specified in the [file cache][] section of your project configuration. + +If you host your site with Netlify, include the following in your project configuration to persist the image cache between builds: + +```toml +[caches] + [caches.images] + dir = ':cacheDir/images' +``` + +### Garbage collection + +If you change image processing methods, or rename/remove images, the cache will eventually contain unused files. To remove them and reclaim disk space, run Hugo's garbage collection: + +```sh +hugo build --gc +``` + +### Resource usage + +The time and memory required to process an image increase with the image's dimensions. For example, a `4032x2268` image requires significantly more memory and processing time than a `1920x1080` image. + +If your source images are much larger than the maximum size you intend to publish, consider scaling them down before the build to optimize performance. + +## Configuration + +See [configure imaging][]. + +[`Height`]: /methods/resource/height/ +[`Meta`]: /methods/resource/meta/ +[`Permalink`]: /methods/resource/permalink/ +[`RelPermalink`]: /methods/resource/relpermalink/ +[`Width`]: /methods/resource/width/ +[`reflect.IsImageResourceProcessable`]: /functions/reflect/isimageresourceprocessable/ +[configure imaging]: /configuration/imaging/ +[file cache]: /configuration/caches/ diff --git a/docs/content/en/content-management/image-processing/sunset.jpg b/docs/content/en/content-management/image-processing/sunset.jpg new file mode 100644 index 0000000..4dbcc08 Binary files /dev/null and b/docs/content/en/content-management/image-processing/sunset.jpg differ diff --git a/docs/content/en/content-management/markdown-attributes.md b/docs/content/en/content-management/markdown-attributes.md new file mode 100644 index 0000000..c9ff839 --- /dev/null +++ b/docs/content/en/content-management/markdown-attributes.md @@ -0,0 +1,119 @@ +--- +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: + +```md +This is a paragraph. +{class="foo bar" id="baz"} +``` + +With `class` and `id` attributes you can also use short-form notation: + +```md +This is a paragraph. +{.foo .bar #baz} +``` + +Hugo renders both of the examples above to: + +```html +

    This is a paragraph.

    +``` + +With `class` and `id` attributes, whether you use long-form or short-form notation, the resulting values are available in [render hook templates][] via the `Attributes` method. For example: + +```go-html-template +{{ .Attributes.class }} → foo bar +{{ .Attributes.id }} → baz +``` + +## Block elements + +Update your project configuration to enable Markdown attributes for block-level elements. + +{{< code-toggle file=hugo >}} +[markup.goldmark.parser.attribute] +title = true # default is true +block = true # default is false +{{< /code-toggle >}} + +## Standalone images + +By default, when the [Goldmark][] Markdown renderer encounters a standalone image element (no other elements or text on the same line), it wraps the image element within a paragraph element per the [CommonMark][] specification. + +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`. + +> [!NOTE] +> Within fenced code blocks, Hugo interprets the `style` attribute as a syntax highlighting [option][option] rather than a global HTML attribute. + +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: + +```md +> This is a blockquote. +{class="foo bar" hidden=hidden} +``` + +Hugo renders this to: + +```html + +``` + +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: + +````md +## Section 1 {class=foo} + +```sh {class=foo linenos=inline} +declare a=1 +echo "${a}" +``` + +This is a paragraph. +{class=foo} +```` + +As shown above, the attribute list for fenced code blocks is not limited to HTML attributes. You can also configure syntax highlighting by passing one or more of [these options][option]. + +[CommonMark]: https://spec.commonmark.org/current/ +[Goldmark]: https://github.com/yuin/goldmark +[global HTML attributes]: https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes +[option]: /functions/transform/highlight/#options +[render hook templates]: /render-hooks/introduction/ diff --git a/docs/content/en/content-management/mathematics.md b/docs/content/en/content-management/mathematics.md new file mode 100644 index 0000000..529d669 --- /dev/null +++ b/docs/content/en/content-management/mathematics.md @@ -0,0 +1,223 @@ +--- +title: Mathematics in Markdown +linkTitle: Mathematics +description: Include mathematical equations and expressions in Markdown using LaTeX markup. +categories: [] +keywords: [] +--- + +## Overview + +Mathematical equations and expressions written in [LaTeX][] are common in academic and scientific publications. Your browser typically renders this mathematical markup using an open-source JavaScript display engine such as [MathJax][] or [KaTeX][]. + +For example, this LaTeX markup: + +```md +\[ +\begin{aligned} +KL(\hat{y} || y) &= \sum_{c=1}^{M}\hat{y}_c \log{\frac{\hat{y}_c}{y_c}} \\ +JS(\hat{y} || y) &= \frac{1}{2}(KL(y||\frac{y+\hat{y}}{2}) + KL(\hat{y}||\frac{y+\hat{y}}{2})) +\end{aligned} +\] +``` + +Is rendered to: + +\[ +\begin{aligned} +KL(\hat{y} || y) &= \sum_{c=1}^{M}\hat{y}_c \log{\frac{\hat{y}_c}{y_c}} \\ +JS(\hat{y} || y) &= \frac{1}{2}(KL(y||\frac{y+\hat{y}}{2}) + KL(\hat{y}||\frac{y+\hat{y}}{2})) +\end{aligned} +\] + +Equations and expressions can be displayed inline with other text, or as standalone blocks. Block presentation is also known as "display" mode. + +Whether an equation or expression appears inline, or as a block, depends on the delimiters that surround the mathematical markup. Delimiters are defined in pairs, where each pair consists of an opening and closing delimiter. The opening and closing delimiters may be the same, or different. + +> [!NOTE] +> You can configure Hugo to render mathematical markup on the client side using the MathJax or KaTeX display engine, or you can render the markup with the [`transform.ToMath`][] function while building your project. +> +> The first approach is described below. + +## Setup + +Follow these instructions to include mathematical equations and expressions in your Markdown using LaTeX markup. + +Step 1 +: Enable and configure the Goldmark [passthrough extension][] in your project configuration. The passthrough extension preserves raw Markdown within delimited snippets of text, including the delimiters themselves. + + {{< code-toggle file=hugo copy=true >}} + [markup.goldmark.extensions.passthrough] + enable = true + + [markup.goldmark.extensions.passthrough.delimiters] + block = [['\[', '\]'], ['$$', '$$']] + inline = [['\(', '\)']] + + [params] + math = true + {{< /code-toggle >}} + + The configuration above enables mathematical rendering on every page unless you set the `math` parameter to `false` in front matter. To enable mathematical rendering as needed, set the `math` parameter to `false` in your project configuration, and set the `math` parameter to `true` in front matter. Use this parameter in your base template as shown in [Step 3](#step-3). + + > [!NOTE] + > The configuration above precludes the use of the `$...$` delimiter pair for inline equations. Although you can add this delimiter pair to the configuration and JavaScript, you must double-escape the `$` symbol when used outside of math contexts to avoid unintended formatting. + > + > See the [inline delimiters](#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](#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 project configuration. + +Step 3 +: Conditionally call the _partial_ template from the base template. + + ```go-html-template {file="layouts/baseof.html"} + + ... + {{ if .Param "math" }} + {{ partialCached "math.html" . }} + {{ end }} + ... + + ``` + + The example above loads the _partial_ template if you have set the `math` parameter in front matter to `true`. If you have not set the `math` parameter in front matter, the conditional statement falls back to the `math` parameter in your project configuration. + +Step 4 +: If you set the `math` parameter to `false` in your project configuration, you must set the `math` parameter to `true` in front matter. For example: + + {{< code-toggle file=content/math-examples.md fm=true >}} + title = 'Math examples' + date = 2024-01-24T18:09:49-08:00 + [params] + math = true + {{< /code-toggle >}} + +Step 5 +: Include mathematical equations and expressions in Markdown using LaTeX markup. + + ```md {file="content/math-examples.md" copy=true} + This is an inline \(a^*=x-b^*\) equation. + + These are block equations: + + \[a^*=x-b^*\] + + \[ a^*=x-b^* \] + + \[ + a^*=x-b^* + \] + + These are also block equations: + + $$a^*=x-b^*$$ + + $$ a^*=x-b^* $$ + + $$ + a^*=x-b^* + $$ + ``` + +## Inline delimiters + +The configuration, JavaScript, and examples above use the `\(...\)` delimiter pair for inline equations. The `$...$` delimiter pair is a common alternative, but using it may result in unintended formatting if you use the `$` symbol outside of math contexts. + +If you add the `$...$` delimiter pair to your configuration and JavaScript, you must double-escape the `$` symbol when used outside of math contexts to avoid unintended formatting. For example: + +```md +I will give you \\$2 if you can solve $y = x^2$. +``` + +> [!NOTE] +> If you use the `$...$` delimiter pair for inline equations, and occasionally use the `$` symbol outside of math contexts, you must use MathJax instead of KaTeX to avoid unintended formatting caused by [this KaTeX limitation][]. + +## Engines + +MathJax and KaTeX are open-source JavaScript display engines. + +> [!NOTE] +> If you use the `$...$` delimiter pair for inline equations, and occasionally use the `$` symbol outside of math contexts, you must use MathJax instead of KaTeX to avoid unintended formatting caused by [this KaTeX limitation][]. +> +>See the [inline delimiters](#inline-delimiters) section for details. + +To use KaTeX instead of MathJax, replace the _partial_ template from [Step 2](#step-2) with this: + +```go-html-template {file="layouts/_partials/math.html" copy=true} + + + + + + + +``` + +The delimiters above must match the delimiters in your project configuration. + +## Chemistry + +Both MathJax and KaTeX provide support for chemical equations. For example: + +```md +$$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](#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][]. + +[KaTeX]: https://katex.org/ +[LaTeX]: https://www.latex-project.org/ +[MathJax]: https://www.mathjax.org/ +[`transform.ToMath`]: /functions/transform/tomath/ +[documentation]: https://katex.org/docs/libs +[passthrough extension]: /configuration/markup/#passthrough +[this KaTeX limitation]: https://github.com/KaTeX/KaTeX/issues/437 diff --git a/docs/content/en/content-management/menus.md b/docs/content/en/content-management/menus.md new file mode 100644 index 0000000..1590366 --- /dev/null +++ b/docs/content/en/content-management/menus.md @@ -0,0 +1,100 @@ +--- +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 your project 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 project 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 project configuration + +See [configure menus][]. + +## Localize + +Hugo provides two methods to localize your menu entries. See [multilingual][]. + +## Render + +See [menu templates][]. + +[configure menus]: /configuration/menus/ +[menu templates]: /templates/menu/ +[multilingual]: /content-management/multilingual/#menus +[template]: /templates/menu/ diff --git a/docs/content/en/content-management/multilingual.md b/docs/content/en/content-management/multilingual.md new file mode 100644 index 0000000..d652a8c --- /dev/null +++ b/docs/content/en/content-management/multilingual.md @@ -0,0 +1,399 @@ +--- +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][]. + +## 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] +> The language code in a file name must be lowercase. For example, use `about.en-us.md` instead of `about.en-US.md`. + +> [!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] +contentDir = 'content/english' +label = "English" +weight = 10 + +[languages.fr] +contentDir = 'content/french' +label = "Français" +weight = 20 +{{< /code-toggle >}} + +The value of `contentDir` can be any valid path -- even absolute path references. The only restriction is that the content directories cannot overlap. + +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`). + +## Translation of strings + +See the [`lang.Translate`][] function. + +## Localization + +The following localization examples assume your project's primary language is English, with translations to French and German. + +{{< code-toggle file=hugo >}} +defaultContentLanguage = 'en' + +[languages] +[languages.en] +contentDir = 'content/en' +label = 'English' +weight = 1 +[languages.fr] +contentDir = 'content/fr' +label = 'Français' +weight = 2 +[languages.de] +contentDir = 'content/de' +label = '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 your [project configuration][], you must create language-specific menu entries under each language key. If the names of the menu entries are insufficient, use translation tables to localize each entry. + +### 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] +label = 'Deutsch' +locale = 'de-DE' +weight = 1 + +[[languages.de.menus.main]] +name = 'Produkte' +pageRef = '/products' +weight = 10 + +[[languages.de.menus.main]] +name = 'Leistungen' +pageRef = '/services' +weight = 20 + +[languages.en] +label = 'English' +locale = 'en-US' +weight = 2 + +[[languages.en.menus.main]] +name = 'Products' +pageRef = '/products' +weight = 10 + +[[languages.en.menus.main]] +name = 'Services' +pageRef = '/services' +weight = 20 +{{< /code-toggle >}} + +#### Method 2 -- Use a configuration directory + +With a more complex menu structure, create a [configuration directory][] and split the menu entries into multiple files, one file per language. For example: + +```tree +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 your [project configuration][] or in [front matter][], set the `identifier` property to the desired value. + +For example, if you define menu entries in project configuration: + +{{< code-toggle file=hugo >}} +[[menus.main]] + identifier = 'products' + name = 'Products' + pageRef = '/products' + weight = 10 +[[menus.main]] + identifier = 'services' + name = 'Services' + pageRef = '/services' + weight = 20 +{{< / code-toggle >}} + +Create corresponding entries in the translation tables: + +{{< code-toggle file=i18n/de >}} +products = 'Produkte' +services = 'Leistungen' +{{< / code-toggle >}} + +## Missing translations + +If a string does not have a translation for the current language, Hugo will use the value from the default language. If no default value is set, an empty string will be shown. + +While translating a Hugo website, it can be helpful to have a visual indicator of missing translations. The [`enableMissingTranslationPlaceholders`][] configuration setting 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 build --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 [`urls.RelLangURL`][] or [`urls.AbsLangURL`][] 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 +``` + +[`enableMissingTranslationPlaceholders`]: /configuration/all/#enablemissingtranslationplaceholders +[`lang.Translate`]: /functions/lang/translate/ +[`slug`]: /content-management/urls/#slug +[`time.Format`]: /functions/time/format/ +[`url`]: /content-management/urls/#url +[`urls.AbsLangURL`]: /functions/urls/abslangurl/ +[`urls.RelLangURL`]: /functions/urls/rellangurl/ +[automatically]: /content-management/menus/#define-automatically +[configuration directory]: /configuration/introduction/#configuration-directory +[configure languages]: /configuration/languages/ +[example menu template]: /templates/menu/#example +[front matter]: /content-management/menus/#define-in-front-matter +[lang.FormatAccounting]: /functions/lang/formataccounting/ +[lang.FormatCurrency]: /functions/lang/formatcurrency/ +[lang.FormatNumberCustom]: /functions/lang/formatnumbercustom/ +[lang.FormatNumber]: /functions/lang/formatnumber/ +[lang.FormatPercent]: /functions/lang/formatpercent/ +[lang.Merge]: /functions/lang/merge/ +[project configuration]: /content-management/menus/#define-in-project-configuration diff --git a/docs/content/en/content-management/organization/index.md b/docs/content/en/content-management/organization/index.md new file mode 100644 index 0000000..b559876 --- /dev/null +++ b/docs/content/en/content-management/organization/index.md @@ -0,0 +1,155 @@ +--- +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 supports page-relative images and other resources packaged into `Page Bundles`. + +These terms are connected, and you also need to read about [page resources][] and [image processing][] to get the full picture. + +```tree +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 [project configuration][]. + +### 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 [page 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][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][url] for details. + +[image processing]: /content-management/image-processing/ +[page resources]: /content-management/page-resources/ +[page template]: /templates/types/#page +[pretty]: /content-management/urls/#appearance +[project configuration]: /configuration/ +[sections]: /content-management/sections/ +[slug]: /content-management/urls/#slug +[url]: /content-management/urls/#url diff --git a/docs/content/en/content-management/page-bundles.md b/docs/content/en/content-management/page-bundles.md new file mode 100644 index 0000000..ced0758 --- /dev/null +++ b/docs/content/en/content-management/page-bundles.md @@ -0,0 +1,145 @@ +--- +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: + +```tree +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. + +```tree +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. + +```tree +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 new file mode 100644 index 0000000..bef99ec --- /dev/null +++ b/docs/content/en/content-management/page-resources.md @@ -0,0 +1,273 @@ +--- +title: Page resources +description: Use page resources to logically associate assets with a page. +categories: [] +keywords: [] +--- + +Page resources are only accessible from [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`. + +```tree +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: + +```tree +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 parameter named `resources`. + +> [!NOTE] +> Resources of type `page` get `Title` etc. from their own front matter. + +`src` +: (`string`) Required. A [glob pattern](g) matching one or more page resources by file path, relative to the page bundle. Matching is case-insensitive. When the pattern matches multiple resources, the same metadata is applied to each. + +`name` +: (`string`) Sets the value returned by [`Name`][]. Supports the [`:counter`](#the-counter-placeholder-in-name-and-title) placeholder. After assignment, use `name`, not the original file path, with [`Resources.Get`][], [`Resources.Match`][], and [`Resources.GetMatch`][]. + +`title` +: (`string`) Sets the value returned by [`Title`][]. Supports the [`:counter`](#the-counter-placeholder-in-name-and-title) placeholder. + +`params` +: (`map`) A map of custom key-value pairs. When multiple array entries match the same resource, their `params` maps are merged; later entries take precedence for duplicate keys. + +### 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 + - 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 +{{}} + + +From the example above: + +- `sunset.jpg` will receive a new `Name` and can now be found with `.GetMatch "header"`. +- `documents/photo_specs.pdf`, `documents/guide.pdf`, `documents/checklist.pdf`, and `documents/payment.docx` will get `Title` as set by `title`. +- All `PDF` files will get the `pdf` icon and 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`. +- All `.docx` files will get the `word` icon. + +> [!NOTE] +> For `name` and `title`, the first matching array entry wins; later matches are ignored. For `params`, all matching entries contribute; later entries take precedence for duplicate keys. Place more specific `src` patterns before broader wildcards to control which `name` and `title` values are applied. + +### The `:counter` placeholder in `name` and `title` + +The `:counter` is a special placeholder recognized in `name` and `title` parameters `resources`. + +Each unique `src` pattern maintains independent counters for `name` and `title`, each starting at 1 with the first matching resource. + +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.pdf' +{{}} + +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 + +By default, with a multilingual single-host project, Hugo does not duplicate shared page during the build. + +> [!NOTE] +> This behavior is limited to Markdown content. Shared page resources for other [content formats][] are copied into each language bundle. + +Consider this project configuration: + +{{< code-toggle file=hugo >}} +defaultContentLanguage = 'de' +defaultContentLanguageInSubdir = true + +[languages.de] +label = 'Deutsch' +locale = 'de-DE' +weight = 1 + +[languages.en] +label = 'English' +locale = 'en-US' +weight = 2 +{{< /code-toggle >}} + +And this content: + +```tree +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 +``` + +Hugo places the shared resources in the page bundle for the default content language: + +```tree +public/ +├── de/ +│ ├── my-bundle/ +│ │ ├── a.jpg <-- shared page resource +│ │ ├── b.jpg <-- shared page resource +│ │ ├── c.de.jpg +│ │ └── index.html +│ └── index.html +├── en/ +│ ├── my-bundle/ +│ │ ├── c.en.jpg +│ │ └── index.html +│ └── index.html +└── index.html +``` + +This approach reduces build times, storage requirements, bandwidth consumption, and deployment times, ultimately reducing cost. + +> [!IMPORTANT] +> To resolve Markdown link and image destinations to the correct location, you must use link and image render hooks that capture the page resource with the [`Resources.Get`][] method, and then invoke its [`RelPermalink`][] method. +> +> In its default configuration, Hugo automatically uses the [embedded link render hook][] and the [embedded image render hook][] for multilingual single-host projects, specifically when the [duplication of shared page resources][] feature is disabled. This is the default behavior for such projects. If custom link or image render hooks are defined by your project, modules, or themes, these will be used instead. +> +> You can also configure Hugo to `always` use the embedded link or image render hook, use it only as a `fallback`, or `never` use it. See [details][]. + +Although duplicating shared page resources is inefficient, you can enable this feature in your project configuration if desired: + +{{< code-toggle file=hugo >}} +[markup.goldmark] +duplicateResourceFiles = true +{{< /code-toggle >}} + +[`Name`]: /methods/resource/name/ +[`RelPermalink`]: /methods/resource/relpermalink/ +[`Resource`]: /methods/resource/ +[`Resources.ByType`]: /methods/page/resources#bytype +[`Resources.GetMatch`]: /methods/page/resources#getmatch +[`Resources.Get`]: /methods/page/resources/#get +[`Resources.Match`]: /methods/page/resources#match +[`Title`]: /methods/resource/title/ +[content formats]: /content-management/formats/ +[details]: /configuration/markup/#renderhookslinkuseembedded +[duplication of shared page resources]: /configuration/markup/#duplicateresourcefiles +[embedded image render hook]: /render-hooks/images/#embedded +[embedded link render hook]: /render-hooks/links/#embedded +[page bundles]: /content-management/page-bundles/ diff --git a/docs/content/en/content-management/related-content.md b/docs/content/en/content-management/related-content.md new file mode 100644 index 0000000..3b132a6 --- /dev/null +++ b/docs/content/en/content-management/related-content.md @@ -0,0 +1,104 @@ +--- +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. + +## 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 }} +

    Related content:

    + +{{ 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][] 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 }} +

    See Also

    +
      + {{ range $i, $p := . }} +
    • + {{ .LinkTitle }} + {{ with .HeadingsFiltered }} +
        + {{ range . }} + {{ $link := printf "%s#%s" $p.RelPermalink .ID | safeURL }} +
      • + {{ .Title }} +
      • + {{ end }} +
      + {{ end }} +
    • + {{ end }} +
    +{{ end }} +``` + +## Configuration + +See [configure related content][]. + +[`keyVals`]: /functions/collections/keyvals/ +[configure related content]: /configuration/related-content/ +[this blog article]: https://regisphilibert.com/blog/2018/04/hugo-optmized-relashionships-with-related-content/ diff --git a/docs/content/en/content-management/sections.md b/docs/content/en/content-management/sections.md new file mode 100644 index 0000000..f4194ea --- /dev/null +++ b/docs/content/en/content-management/sections.md @@ -0,0 +1,139 @@ +--- +title: Sections +description: Organize content into sections. + +categories: [] +keywords: [] +aliases: [/content/sections/] +--- + +## Overview + +{{% glossary-term "section" %}} + +```tree +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 _section_ 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/section.html` +`content/products/product-1`|`layouts/products/section.html` +`content/products/product-1/benefits`|`layouts/products/section.html` + +Content directory|Page template +:--|:-- +`content/products`|`layouts/products/page.html` +`content/products/product-1`|`layouts/products/page.html` +`content/products/product-1/benefits`|`layouts/products/page.html` + +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 new file mode 100644 index 0000000..613e24f --- /dev/null +++ b/docs/content/en/content-management/shortcodes.md @@ -0,0 +1,232 @@ +--- +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. + +{{% render-list-of-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: + +```md {file="content/example.md"} +{{}} +``` + +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 project configuration: + +{{< code-toggle file=hugo >}} +[security] +enableInlineShortcodes = true +{{< /code-toggle >}} + +For more information see [configure security][]. + +The following example demonstrates an inline shortcode, `date.inline`, that accepts a single positional argument: a date/time [layout string][]. + +```md {file="content/example.md"} +Today is +{{}} + {{- now | time.Format (.Get 0) -}} +{{}}. + +Today is {{}}. +``` + +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: + +```md +{{}} +This is a **bold** word. +{{}} +``` + +Some shortcodes do not accept content. For example, the embedded [`instagram`][] shortcode requires a single _positional_ argument: + +```md +{{}} +``` + +Some shortcodes optionally accept content. For example, you can call the embedded [`qr`][] shortcode with content: + +```md +{{}} +https://gohugo.io +{{}} +``` + +Or use the self-closing syntax with a trailing slash to pass the text as an argument: + +```md +{{}} +``` + +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. + +```md +{{}} +``` + +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. + +```md +{{}} +``` + +Shortcode arguments are space-delimited, and arguments with internal spaces must be quoted. + +```md +{{}} +``` + +Shortcodes accept [scalar](g) arguments, one of [string](g), [integer](g), [floating point](g), or [boolean](g). + +```md +{{}} +``` + +You can optionally use multiple lines when providing several arguments to a shortcode for better readability: + +```md +{{}} +``` + +Use a [raw string literal](g) if you need to pass a multiline string: + +```md +{{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|`{{}} ## Section 2 {{}}` + +#### 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: + +```md {file="content/example.md"} +{{%/* foo */%}} ## Section 1 {{%/* /foo */%}} + +{{}} ## Section 2 {{}} +``` + +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. + +> [!NOTE] +> 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: + +```md {file="content/example.md"} +{{}} + {{}} + {{}} + {{}} +{{}} +``` + +The [shortcode templates][nesting] section provides a detailed explanation and examples. + +[`TableOfContents`]: /methods/page/tableofcontents/ +[`details`]: /shortcodes/details/ +[`figure`]: /shortcodes/figure/ +[`instagram`]: /shortcodes/instagram/ +[`qr`]: /shortcodes/qr/ +[configure security]: /configuration/security/ +[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 new file mode 100644 index 0000000..1534835 --- /dev/null +++ b/docs/content/en/content-management/summaries.md @@ -0,0 +1,153 @@ +--- +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. + +```md {file="content/example.md"} ++++ +title: 'Example' +date: 2024-05-26T09:10:33-07:00 ++++ + +This is the first paragraph. + + + +This is the second paragraph. +``` + +> [!NOTE] +> Place the summary divider on its own line. Do not place it inline with other content. + +Correct placement: + +```md {file="content/example.md"} +--- +title: 'Example' +--- + +This is an example of **strong text** in a sentence. This is another sentence. + + + +This is another paragraph. +``` + +Incorrect placement: + +```md {file="content/example.md"} +--- +title: 'Example' +--- + +This is an example of **strong text** in a sentence. This is another sentence. + +This is another paragraph. +``` + +When using the Emacs Org Mode [content format][], use a `# more` divider to indicate the end of the summary. + +## Front matter summary + +Use front matter to define a summary independent of content. + +```md {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 project configuration. + +```md {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.

    +``` + +> [!WARNING] +> Automatic `.Summary` may cut block tags (e.g., `blockquote`) in the middle when `summaryLength` is reached, causing the browser to recover the end tag (the end tag will be inserted before the parent's end tag), resulting in unexpected rendering behavior. To avoid this, wrap `.Summary` in a `
    `; alternatively, wrap it together with the heading tag using `
    `. You can avoid this entirely by using a manual summary. See issue [#14044][] for details. + +## 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. + +```go-html-template +{{ range site.RegularPages }} +

    {{ .LinkTitle }}

    +
    + {{ .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: + +```go-html-template +{{ range site.RegularPages }} +

    {{ .LinkTitle }}

    +
    + {{ .Content | strings.Truncate 42 }} +
    +{{ end }} +``` + +[#14044]: https://github.com/gohugoio/hugo/issues/14044 +[`Summary`]: /methods/page/summary/ +[`strings.Truncate`]: /functions/strings/truncate/ +[`summaryLength`]: /configuration/all/#summarylength +[content format]: /content-management/formats/ diff --git a/docs/content/en/content-management/syntax-highlighting.md b/docs/content/en/content-management/syntax-highlighting.md new file mode 100644 index 0000000..32abb77 --- /dev/null +++ b/docs/content/en/content-management/syntax-highlighting.md @@ -0,0 +1,101 @@ +--- +title: Syntax highlighting +description: Add syntax highlighting to code examples. +categories: [] +keywords: [highlight] +aliases: [/extras/highlighting/,/extras/highlight/,/tools/syntax-highlighting/] +--- + +Hugo provides several methods to add syntax highlighting to code examples: + +- Use the [`transform.Highlight`][] function within your templates +- Use the [`highlight`][] shortcode with any [content format](g) +- Use fenced code blocks with the Markdown content format + +## Fenced code blocks + +In its default configuration, Hugo highlights code examples within fenced code blocks, following this form: + +````md {file="content/example.md"} +```LANG [OPTIONS] +CODE +``` +```` + +`CODE` +: The code to highlight. + +`LANG` +: The language of the code to highlight. Choose from one of the [supported languages](#languages). This value is case-insensitive. If omitted or unsupported, Hugo renders the text as a plain text block without syntax highlighting. Consistent with the [CommonMark][] specification, fenced code blocks require a known language identifier to trigger semantic syntax highlighting. + +`OPTIONS` +: One or more space-separated or comma-separated key-value pairs wrapped in braces. Set default values for each option in your [project configuration][]. The key names are case-insensitive. + +For example, with this Markdown: + +````md {file="content/example.md"} +```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) + } +} +``` +```` + +Hugo renders this: + +```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) + } +} +``` + +## Options + +{{% include "_common/syntax-highlighting-options.md" %}} + +## Escaping + +When documenting shortcode usage, escape the tag delimiters: + +````md {file="content/example.md"} +```text {linenos=inline} +{{}} + +{{%/*/* shortcode-2 */*/%}} +``` +```` + +Hugo renders this to: + +```text {linenos=inline} +{{}} + +{{%/* shortcode-2 */%}} +``` + +## Languages + +These are the supported languages. Use one of the identifiers, not the language name, when specifying a language for: + +- The [`transform.Highlight`][] function +- The [`highlight`][] shortcode +- Fenced code blocks + +{{< chroma-lexers >}} + +[CommonMark]: https://spec.commonmark.org/current/#indented-code-blocks +[`highlight`]: /shortcodes/highlight/ +[`transform.Highlight`]: /functions/transform/highlight/ +[project configuration]: /configuration/markup/#highlight diff --git a/docs/content/en/content-management/taxonomies.md b/docs/content/en/content-management/taxonomies.md new file mode 100644 index 0000000..be26fb0 --- /dev/null +++ b/docs/content/en/content-management/taxonomies.md @@ -0,0 +1,179 @@ +--- +title: Taxonomies +description: Hugo includes support for user-defined taxonomies. +categories: [] +keywords: [] +aliases: [/taxonomies/overview/,/taxonomies/usage/,/indexes/overview/,/doc/indexes/,/extras/indexes] +--- + +## What is a taxonomy? + +Hugo includes support for user-defined groupings of content called **taxonomies**. Taxonomies are classifications of logical relationships between content. + +### Definitions + +Taxonomy +: A categorization that can be used to classify content + +Term +: A key within the taxonomy + +Value +: A piece of content assigned to a term + +## Example taxonomy: movie website + +Let's assume you are making a website about movies. You may want to include the following taxonomies: + +- Actors +- Directors +- Studios +- Genre +- Year +- Awards + +Then, in each of the movies, you would specify terms for each of these taxonomies (i.e., in the front matter of each of your movie content files). From these terms, Hugo would automatically create pages for each Actor, Director, Studio, Genre, Year, and Award, with each listing all of the Movies that matched that specific Actor, Director, Studio, Genre, Year, and Award. + +### Movie taxonomy organization + +To continue with the example of a movie site, the following demonstrates content relationships from the perspective of the taxonomy: + +```txt +Actor <- Taxonomy + Bruce Willis <- Term + The Sixth Sense <- Value + Unbreakable <- Value + Moonrise Kingdom <- Value + Samuel L. Jackson <- Term + Unbreakable <- Value + The Avengers <- Value + xXx <- Value +``` + +From the perspective of the content, the relationships would appear differently, although the data and labels used are the same: + +```txt +Unbreakable <- Value + Actors <- Taxonomy + Bruce Willis <- Term + Samuel L. Jackson <- Term + Director <- Taxonomy + M. Night Shyamalan <- Term + ... +Moonrise Kingdom <- Value + Actors <- Taxonomy + Bruce Willis <- Term + Bill Murray <- Term + Director <- Taxonomy + Wes Anderson <- Term + ... +``` + +### Default destinations + +When taxonomies are used Hugo will automatically create both a page listing all the taxonomy's terms and individual pages with lists of content associated with each term. For example, a `categories` taxonomy declared in your configuration and used in your content front matter will create the following pages: + +- A single page at `example.com/categories/` that lists all the terms within the taxonomy +- Individual taxonomy list pages (e.g., `/categories/development/`) for each of the terms that shows a listing of all pages marked as part of that taxonomy within any content file's front matter + +## Configuration + +See [configure taxonomies][]. + +## Assign terms to content + +To assign one or more terms to a page, create a front matter field using the plural name of the taxonomy, then add terms to the corresponding array. For example: + +{{< code-toggle file=content/example.md fm=true >}} +title = 'Example' +tags = ['Tag A','Tag B'] +categories = ['Category A','Category B'] +{{< /code-toggle >}} + +## Taxonomic weight + +{{% glossary-term "taxonomic weight" %}} + +Assign a taxonomic weight using a front matter key named `[taxonomy_name]_weight`. + +{{< code-toggle file="content/courses/organic-chemistry.md" fm=true >}} +title = 'Organic Chemistry' +weight = 10 +tags_weight = 1000 +tags = ['chemistry','science'] +{{}} + +With the front matter above, the `organic-chemistry` page will float towards the top of the list on section and home pages, and it will sink towards the bottom of the list on the `chemistry` and `science` term pages. + +## Metadata + +Display metadata about each term by creating a corresponding branch bundle in the `content` directory. + +For example, create an `authors` taxonomy: + +{{< code-toggle file=hugo >}} +[taxonomies] +author = 'authors' +{{< /code-toggle >}} + +Then create content with one [branch bundle](g) for each term: + +```tree +content/ +└── authors/ + ├── jsmith/ + │ ├── _index.md + │ └── portrait.jpg + └── rjones/ + ├── _index.md + └── portrait.jpg +``` + +Then add front matter to each term page: + +{{< code-toggle file=content/authors/jsmith/_index.md fm=true >}} +title = 'John Smith' +affiliation = 'University of Chicago' +{{< /code-toggle >}} + +Then create a _taxonomy_ template specific to the `authors` taxonomy: + +```go-html-template {file="layouts/authors/taxonomy.html"} +{{ define "main" }} +

    {{ .Title }}

    + {{ .Content }} + {{ range .Data.Terms.Alphabetical }} +

    {{ .Page.LinkTitle }}

    +

    Affiliation: {{ .Page.Params.Affiliation }}

    + {{ with .Page.Resources.Get "portrait.jpg" }} + {{ with .Fill "100x100" }} + portrait + {{ end }} + {{ end }} + {{ end }} +{{ end }} +``` + +In the example above we list each author including their affiliation and portrait. + +Or create a _term_ template specific to the `authors` taxonomy: + +```go-html-template {file="layouts/authors/term.html"} +{{ define "main" }} +

    {{ .Title }}

    +

    Affiliation: {{ .Params.affiliation }}

    + {{ with .Resources.Get "portrait.jpg" }} + {{ with .Fill "100x100" }} + portrait + {{ end }} + {{ end }} + {{ .Content }} + {{ range .Pages }} +

    {{ .LinkTitle }}

    + {{ end }} +{{ end }} +``` + +In the example above we display the author including their affiliation and portrait, then a list of associated content. + +[configure taxonomies]: /configuration/taxonomies/ diff --git a/docs/content/en/content-management/urls.md b/docs/content/en/content-management/urls.md new file mode 100644 index 0000000..a30361f --- /dev/null +++ b/docs/content/en/content-management/urls.md @@ -0,0 +1,259 @@ +--- +title: URL management +description: Control the structure and appearance of URLs through front matter entries and settings in your project configuration. +categories: [] +keywords: [] +aliases: [/extras/permalinks/,/extras/aliases/,/extras/urls/,/doc/redirects/,/doc/alias/,/doc/aliases/] +--- + +## Overview + +By default, when Hugo renders a page, the resulting URL matches the file path within the `content` directory. For example: + +```text +content/posts/post-1.md → https://example.org/posts/post-1/ +``` + +You can change the structure and appearance of URLs with front matter values and configuration settings. + +## Front matter + +### `slug` + +Set the `slug` in front matter to override the last segment of the path. This front matter field is not applicable to `home`, `section`, `taxonomy`, or `term` pages. + +{{< code-toggle file=content/posts/post-1.md fm=true >}} +title = 'My First Post' +slug = 'my-first-post' +{{< /code-toggle >}} + +The resulting URL will be: + +```text +https://example.org/posts/my-first-post/ +``` + +### `url` + +Set the `url` in front matter to override the entire path. Use this with either regular pages or section pages. + +> [!NOTE] +> Hugo does not sanitize the `url` front matter field, allowing you to generate: +> +> - File paths that contain characters reserved by the operating system. For example, file paths on Windows may not contain any of these [reserved characters][]. Hugo throws an error if a file path includes a character reserved by the current operating system. +> - URLs that contain disallowed characters. For example, the less than sign (`<`) is not allowed in a URL. + +If you set both `slug` and `url` in front matter, the `url` value takes precedence. + +#### Include a colon + +{{< new-in 0.136.0 />}} + +If you need to include a colon in the `url` front matter field, escape it with backslash characters. Use one backslash if you wrap the string within single quotes, or use two backslashes if you wrap the string within double quotes. With YAML front matter, use a single backslash if you omit quotation marks. + +For example, with this front matter: + +{{< code-toggle file=content/example.md fm=true >}} +title: Example +url: "my\\:example" +{{< /code-toggle >}} + +The resulting URL will be: + +```text +https://example.org/my:example/ +``` + +As described above, this will fail on Windows because the colon (`:`) is a reserved character. + +#### File extensions + +With this front matter: + +{{< code-toggle file=content/posts/post-1.md fm=true >}} +title = 'My First Article' +url = 'articles/my-first-article' +{{< /code-toggle >}} + +The resulting URL will be: + +```text +https://example.org/articles/my-first-article/ +``` + +If you include a file extension: + +{{< code-toggle file=content/posts/post-1.md fm=true >}} +title = 'My First Article' +url = 'articles/my-first-article.html' +{{< /code-toggle >}} + +The resulting URL will be: + +```text +https://example.org/articles/my-first-article.html +``` + +#### Leading slashes + +With monolingual projects, `url` values with or without a leading slash are relative to the [`baseURL`][]. With multilingual projects, `url` values with a leading slash are relative to the `baseURL`, and `url` values without a leading slash are relative to the `baseURL` plus the language prefix. + +Site type|Front matter `url`|Resulting URL +:--|:--|:-- +monolingual|`/about`|`https://example.org/about/` +monolingual|`about`|`https://example.org/about/` +multilingual|`/about`|`https://example.org/about/` +multilingual|`about`|`https://example.org/de/about/` + +#### Tokens + +You can also use tokens when setting the `url` value. This is typically used in `cascade` sections: + +{{< code-toggle file=content/foo/bar/_index.md fm=true >}} +title ='Bar' +[[cascade]] + url = '/:sections[last]/:slug' +{{< /code-toggle >}} + +Use any of these tokens: + +{{% include "/_common/permalink-tokens.md" %}} + +## Project configuration + +### Permalinks + +See [configure permalinks][]. + +### Appearance + +See [configure ugly URLs](/configuration/ugly-urls/). + +### Post-processing + +Hugo provides two mutually exclusive configuration settings to alter URLs _after_ it renders a page. + +#### Canonical URLs + +> [!CAUTION] +> This is a legacy configuration setting, superseded by template functions and Markdown render hooks, and will likely be [removed in a future release][]. +{class="!mt-6"} + +If enabled, Hugo performs a search and replace _after_ it renders the page. It searches for site-relative URLs (those with a leading slash) associated with `action`, `href`, `src`, `srcset`, and `url` attributes. It then prepends the `baseURL` to create absolute URLs. + +```html + + +``` + +This is an imperfect, brute force approach that can affect content as well as HTML attributes. As noted above, this is a legacy configuration setting that will likely be removed in a future release. + +To enable: + +{{< code-toggle file=hugo >}} +canonifyURLs = true +{{< /code-toggle >}} + +#### Relative URLs + +> [!CAUTION] +> Do not enable this option unless you are creating a serverless site, navigable via the file system. +{class="!mt-6"} + +If enabled, Hugo performs a search and replace _after_ it renders the page. It searches for site-relative URLs (those with a leading slash) associated with `action`, `href`, `src`, `srcset`, and `url` attributes. It then transforms the URL to be relative to the current page. + +For example, when rendering `content/posts/post-1`: + +```html + + +``` + +This is an imperfect, brute force approach that can affect content as well as HTML attributes. As noted above, do not enable this option unless you are creating a serverless site. + +To enable: + +{{< code-toggle file=hugo >}} +relativeURLs = true +{{< /code-toggle >}} + +## Aliases + +Aliases allow you to redirect old URLs to new URLs. This is essential for preventing broken links and ensuring that existing bookmarks or external links continue to function when you rename or move content. + +### Defining aliases + +To add redirects to a page, list the previous paths in the [`aliases`][aliases_field] field in your front matter. Hugo resolves these to [server-relative](g) paths during the build process, accounting for the [`baseURL`][] and [content dimension](g) prefixes such as language, version, or role. + +{{< code-toggle file=content/examples/example-1.en.md fm=true >}} +title = 'Example 1' +date = 2025-02-02 +aliases = ['/old-url', 'old-name', '../old/path'] +{{< /code-toggle >}} + +As shown in the example above, you can use [site-relative](g) paths or [page-relative](g) paths. Page-relative paths can also include directory traversal. Using the file `content/examples/example-1.en.md` as a reference point, here is how Hugo interprets those different path types: + +Path type|Alias|Server-relative path +:--|:--|:-- +site-relative|`/old-url`|`/en/old-url/` +page-relative|`old-name`|`/en/examples/old-name/` +page-relative|`../old/path`|`/en/old/path/` + +### Redirection methods + +There are two ways to implement aliases depending on your hosting environment and preferences: client-side redirection and server-side redirection. + +> [!NOTE] +> Alias data is only generated for [output formats](g) where both [`isHTML`][] and [`permalinkable`][] are `true`. This affects both the creation of client-side redirect files and the results returned by the [`Aliases`][aliases_method] method used in server-side redirection. + +#### Client-side redirection + +By default, Hugo uses client-side redirection, generating a small HTML file for every alias. This file contains a `meta http-equiv="refresh"` tag that instructs the browser to navigate to the new URL. This approach is portable across all hosting providers. + +When using this method, Hugo creates a physical directory and an `index.html` file at each alias location. For example, if a page at `content/posts/new.md` has a page-relative alias of `old-path`, a file is generated at `public/posts/old-path/index.html`. + +Unless you provide a custom layout, Hugo uses its [embedded alias template][] to generate the redirect files: + +```go-html-template + + + + {{ .Permalink }} + {{ with .OutputFormats.Canonical }}{{ end }} + + + + +``` + +To override this, create a file named `alias.html` in your `layouts` directory. This custom template has access to the following context: + +`Permalink` +: (`string`) The absolute URL of the destination page. + +`Page` +: (`page.Page`) The full `Page` object of the destination. + +#### Server-side redirection + +Alternatively, you can implement server-side redirection by using the [`Aliases`][aliases_method] method on a `Page` object to generate a single configuration file that the web server processes. This method is more efficient because the redirect happens at the HTTP header level before any page content is processed, whereas a meta refresh requires the browser to download and parse the HTML body before acting. Additionally, server-side redirection improves build and deployment times because Hugo doesn't need to write a physical directory and HTML file for every alias. + +To implement this, you typically create a single template to generate the necessary rules for your specific host or server. Common examples include: + +- A `_redirects` file for hosting services such as Cloudflare, GitLab Pages, and Netlify. +- An `.htaccess` file for web servers such as Apache and LiteSpeed. + +See the [`Aliases`][aliases_method] method page for a complete example of how to iterate through pages to generate these rules. + +If you implement server-side redirects, you should disable the generation of individual HTML files by setting [`disableAliases`][] to `true` in your project configuration. This setting only prevents the generation of the physical HTML files; the `Aliases` method on a `Page` object remains available for use in your configuration templates. + +[`baseURL`]: /configuration/all/#baseurl +[`disableAliases`]: /configuration/all/#disablealiases +[`isHTML`]: /configuration/output-formats/#ishtml +[`permalinkable`]: /configuration/output-formats/#permalinkable +[aliases_field]: /content-management/front-matter/#aliases +[aliases_method]: /methods/page/aliases/ +[configure permalinks]: /configuration/permalinks/ +[embedded alias template]: <{{% eturl alias %}}> +[removed in a future release]: https://github.com/gohugoio/hugo/issues/4733 +[reserved characters]: https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions diff --git a/docs/content/en/contribute/_index.md b/docs/content/en/contribute/_index.md new file mode 100644 index 0000000..d0ae954 --- /dev/null +++ b/docs/content/en/contribute/_index.md @@ -0,0 +1,9 @@ +--- +title: Contribute to the Hugo project +linkTitle: Contribute +description: Contribute to development, documentation, and themes. +categories: [] +keywords: [] +weight: 10 +aliases: [/tutorials/how-to-contribute-to-hugo/,/community/contributing/] +--- diff --git a/docs/content/en/contribute/development.md b/docs/content/en/contribute/development.md new file mode 100644 index 0000000..59dfca2 --- /dev/null +++ b/docs/content/en/contribute/development.md @@ -0,0 +1,148 @@ +--- +title: Development +description: Contribute to the development of Hugo. +categories: [] +keywords: [] +--- + +## Introduction + +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. + +For a complete guide to contributing to Hugo, see the [Contribution Guide][]. + +## Prerequisites + +To build Hugo from source you must install: + +1. Install [Git][] +1. Install [Go][] version {{% current-go-version %}} or later + +## GitHub workflow + +> [!NOTE] +> This section assumes that you have a working knowledge of Go, Git and GitHub, and are comfortable working on the command line. + +Use this workflow to create and submit pull requests. + +Step 1 +: Fork the [project repository][]. + +Step 2 +: Clone your fork. + +Step 3 +: Create a new branch with a descriptive name that includes the corresponding issue number. + + For a new feature: + + ```sh + git checkout -b feat/implement-some-feature-99999 + ``` + + For a bug fix: + + ```sh + git checkout -b fix/fix-some-bug-99999 + ``` + +Step 4 +: Make changes. + +Step 5 +: Build and install. + + To build and install the standard edition: + + ```sh + CGO_ENABLED=0 go install + ``` + + {{< new-in v0.159.2 />}} To build and install the deploy edition: + + ```sh + CGO_ENABLED=0 go install -tags withdeploy + ``` + + To build and install the extended edition, first install a C compiler such as [GCC][] or [Clang][] and then run the following command: + + ```sh + CGO_ENABLED=1 go install -tags extended + ``` + + To build and install the extended/deploy edition, first install a C compiler such as [GCC][] or [Clang][] and then run the following command: + + ```sh + CGO_ENABLED=1 go install -tags extended,withdeploy + ``` + +Step 6 +: Test your changes: + + ```sh + go test ./... + ``` + +Step 7 +: Commit your changes with a descriptive commit message: + + - Provide a summary on the first line, typically 50 characters or less, followed by a blank line. + - Begin the summary with the name of the package, followed by a colon, a space, and a brief description of the change beginning with a capital letter + - Use imperative present tense + - See the [commit message guidelines][] for requirements + - Optionally, provide a detailed description where each line is 72 characters or less, followed by a blank line. + - Add one or more "Fixes" or "Closes" keywords, each on its own line, referencing the [issues][] addressed by this change. + + For example: + + ```sh + git commit -m "tpl/strings: Create wrap function + + The strings.Wrap function wraps a string into one or more lines, + splitting the string after the given number of characters, but not + splitting in the middle of a word. + + Fixes #99998 + Closes #99999" + ``` + +Step 8 +: Push the new branch to your fork of the documentation repository. + +Step 9 +: Visit the [project repository][] and create a pull request (PR). + +Step 10 +: A project maintainer will review your PR and may request changes. You may delete your branch after the maintainer merges your PR. + +[Clang]: https://clang.llvm.org/ +[Contribution Guide]: https://github.com/gohugoio/hugo/blob/master/CONTRIBUTING.md +[GCC]: https://gcc.gnu.org/ +[Git]: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git +[Go]: https://go.dev/doc/install +[bugs]: https://github.com/gohugoio/hugo/issues?q=is%3Aopen+is%3Aissue+label%3ABug +[commit message guidelines]: https://github.com/gohugoio/hugo/blob/master/CONTRIBUTING.md#git-commit-message-guidelines +[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]: /documentation +[forum]: https://discourse.gohugo.io +[issue queue]: https://github.com/gohugoio/hugo/issues +[issues]: https://github.com/gohugoio/hugo/issues +[project repository]: https://github.com/gohugoio/hugo/ +[themes]: https://themes.gohugo.io/ diff --git a/docs/content/en/contribute/documentation/index.md b/docs/content/en/contribute/documentation/index.md new file mode 100644 index 0000000..d79ebdb --- /dev/null +++ b/docs/content/en/contribute/documentation/index.md @@ -0,0 +1,593 @@ +--- +title: Documentation +description: Help us to improve the documentation by identifying issues and suggesting changes. +categories: [] +keywords: [] +aliases: [/contribute/docs/] +--- + +## Introduction + +We welcome corrections and improvements to the documentation. The documentation lives in a separate repository from the main project. To contribute: + +- For corrections and improvements to existing documentation, submit issues and pull requests to the [documentation repository][]. +- For documentation of new features, include the documentation changes in your pull request to the [project repository][]. + +## Guidelines + +### Style + +Follow Google's [developer documentation style guide][] where practical. + +### Markdown + +Adhere to these Markdown conventions: + +- Use [ATX][] headings (levels 2-4), not [setext][] headings. +- Use [collapsed link references][] instead of full or shortcut references. For example: + + ```md {file="content/example.md"} + This is a [link][]. + + [link]: https://example.org + ``` + +- Use [inline links][] to link to fragments on the same page. For example: + + ```md {file="content/example.md"} + This is a link to a [fragment][#fragment]. + ``` + +- Use [fenced code blocks][] instead of [indented code blocks][]. +- Use hyphens, not asterisks, for unordered [list items][]. +- Prefix each ordered list item with `1.` instead of numbering the items sequentially. +- Use [callouts](#callouts) instead of bold text for emphasis. +- Do not mix [raw HTML][] within Markdown. +- Do not use bold text in place of a heading or description term (`dt`). +- Remove consecutive blank lines. +- Remove trailing spaces. + +### Glossary + +[Glossary][] terms are defined on individual pages, providing a central repository for definitions, though these pages are not directly linked from the site. + +Definitions must be complete sentences, with the first sentence defining the term. Italicize the first occurrence of the term and any referenced glossary terms for consistency. + +Link to glossary terms using this syntax: `[term](g)` + +Term lookups are case-insensitive, ignore formatting, and support singular and plural forms. For example, all of these variations will link to the same glossary term: + +```md {file="content/example.md"} +[global resource](g) +[Global Resource](g) +[Global Resources](g) +[`Global Resources`](g) +``` + +Use the [glossary-term shortcode](#glossary-term) to insert a term definition: + +```md {file="content/example.md"} +{{%/* glossary-term "global resource" */%}} +``` + +### Terminology + +Link to the [glossary][] as needed and use terms consistently. Pay particular attention to: + +- "client side" (noun), "client-side" (adjective) +- "file name" (two words) +- "flag" (instead of "option" for command-line flags) +- "front matter" (two words, except when referring to the configuration key) +- "home page" (two words) +- "map" (instead of "dictionary") +- "Markdown" (capitalized) +- "Node.js" (first mention per page), "Node" (subsequent mentions), "node" (for the executable), "npm" (always lowercase) +- "open source" (noun), "open-source" (adjective) +- "server side" (noun), "server-side" (adjective) +- "standalone" (noun or adjective) +- "stylesheet" (one word) +- "website" (one word) + +### Template types + +When you refer to a template type, italicize it: + +```md {file="content/example.md"} +When creating a _taxonomy_ template, do this... +``` + +However, if the template type is also a link, do not italicize it to avoid distracting formatting: + +```md {file="content/example.md"} +When creating a [taxonomy][] template, do this... +``` + +Do not italicize the template type in a title, heading, or front matter description. + +### Titles and headings + +- Use sentence-style capitalization. +- Avoid formatted strings. +- Keep them concise. + +### Page descriptions + +When writing the page `description` use imperative present tense when possible. For example: + +{{< code-toggle file=content/en/functions/data/_index.md fm=true >}} +title: Data functions +linkTitle: data +description: Use these functions to read local or remote data files. +{{< /code-toggle >}} + +### Writing style + +Prefer active voice and present tense. + +No → With Hugo you can build a static site.\ +Yes → Build a static site with Hugo. + +No → This will cause Hugo to generate HTML files in the `public` directory.\ +Yes → Hugo generates HTML files in the `public` directory. + +Use second person instead of third person. + +No → Users should exercise caution when deleting files.\ +Better → You must be cautious when deleting files.\ +Best → Be cautious when deleting files. + +Minimize adverbs. + +No → Hugo is extremely fast.\ +Yes → Hugo is fast. + +> [!NOTE] +> "It's an adverb, Sam. It's a lazy tool of a weak mind." (Outbreak, 1995). + +### Function and method descriptions + +Start descriptions in the functions and methods sections with "Returns", or for booelan values, "Reports whether". + +### File paths and names + +Enclose directory names, file names, and file paths in backticks, except when used in: + +- Page titles +- Section headings (`h1`-`h6`) +- The `description` field in front matter + +### Miscellaneous + +Other best practices: + +- Introduce lists with a sentence or phrase, not directly under a heading. +- Do not use Hugo's `ref` or `relref` shortcodes. +- Prioritize current best practices over multiple options or historical information. +- Use short, focused code examples. +- Use [basic english][] where possible for a global audience. + +## Front matter fields + +This site uses the front matter fields listed in the table below. + +Of the four required fields, only `title` and `description` require data. + +{{< code-toggle file=content/example.md fm=true >}} +title: The title +description: The description +categories: [] +keywords: [] +{{< /code-toggle >}} + +If quotation marks are required, prefer single quotes to double quotes when possible. + +Field|Description|Required +:--|:--|:-- +`title`|The page title|:heavy_check_mark: +`linkTitle`|A short version of the page title|  +`description`|A complete sentence describing the page|:heavy_check_mark: +`categories`|An array of terms in the categories taxonomy|:heavy_check_mark: [^1] +`keywords`|An array of keywords used to identify related content|:heavy_check_mark: [^1] +`publishDate`|Applicable to news items: the publication date|  +`params.alt_title`|An alternate title: used in the "see also" panel if provided|  +`params.functions_and_methods.aliases`|Applicable to function and method pages: an array of alias names|  +`params.functions_and_methods.returnType`|Applicable to function and method pages: the data type returned|  +`params.functions_and_methods.signatures`|Applicable to function and method pages: an array of signatures|  +`params.hide_in_this_section`|Whether to hide the "in this section" panel|  +`params.label`|Applicable to second-level section pages such as concepts, guides, references, and tutorials: a lowercase 3-letter code to identify the section, used in the `related` partial|  +`params.minversion`|Applicable to the quick start page: the minimum Hugo version required|  +`params.permalink`|Reserved for use by the news content adapter|  +`params.reference`|Applicable to glossary entries: a URL for additional information|  +`params.searchable`|Whether to add the content of this page to the search index. The default value is cascaded down from the project configuration; `true` if the page kind is `page`, and `false` if the page kind is one of `home`, `section`, `taxonomy`, or `term`. Add this field to override the default value.|  +`params.show_publish_date`|Whether to show the `publishDate` when rendering the page|  +`weight`|The page weight|  +`aliases`|Previous URLs used to access this page|  +`expirydate`|The expiration date|  + +## Related content + +When available, the "See also" sidebar displays related pages using Hugo's [related content][] feature, based on front matter keywords. We ensure consistent keyword usage by validating them against `data/keywords.yaml` during the build. If a keyword is not found, you'll be alerted and must either modify the keyword or update the data file. This validation process helps to refine the related content for better results. + +If the title in the "See also" sidebar is ambiguous or the same as another page, you can define an alternate title in the front matter: + +{{< code-toggle file=hugo >}} +title = 'Long descriptive title' +linkTitle = 'Short title' +[params] +alt_title = 'Whatever you want' +{{< /code-toggle >}} + +Use of the alternate title is limited to the "See also" sidebar. + +> [!NOTE] +> Think carefully before setting the `alt_title`. Use it only when necessary. + +## Code examples + +With examples of template code: + +- Indent with two spaces. +- Insert a space after an opening action delimiter. +- Insert a space before a closing action delimiter. +- Do not add white space removal syntax to action delimiters unless required. For example, inline elements like `img` and `a` require whitespace removal on both sides. + +````md {file="content/example.md"} +```go-html-template +{{ if eq $foo $bar }} + {{ fmt.Printf "%s is %s" $foo $bar }} +{{ end }} +``` +```` + +### Fenced code blocks + +The code block render hook performs several language substitutions: + +Given language|Substitution|Reason +:--|:--|:-- +`html`, `gotmpl`|`go-html-template`|Consistent rendering +`bash`, `ksh`, `sh`, `shell`, `zsh`|`text`|Avoid highlighting quirks +`md`, `mkd`|`text`|Avoid highlighting quirks +`tree`|`text`|There isn't a `tree` lexer + +Set the language to `go-html-template` when including template examples: + +````md {file="content/example.md"} +```go-html-template +{{ if eq $foo "bar" }} + {{ print "foo is bar" }} +{{ end }} +``` +```` + +Set the language to `md` when including Markdown examples: + +````md {file="content/example.md"} +```md +This is **bold** text. +``` +```` + +Set the language to `sh` when including command line examples: + +````md {file="content/example.md"} +```sh +hugo server -D +``` +```` + +Set the language to `tree` when documenting the output of the `tree` command: + +````md {file="content/example.md"} +```tree +assets/ +└── images/ + └── sunset.jpg +``` +```` + +To include a file name header and copy-to-clipboard button: + +````md {file="content/example.md"} +```go-html-template {file="layouts/_partials/foo.html" copy=true} +{{ if eq $foo "bar" }} + {{ print "foo is bar" }} +{{ end }} +``` +```` + +To wrap the code block within an initially-opened `details` element using a non-default summary: + +````md {file="content/example.md"} +```go-html-template {details=true open=true summary="layouts/_partials/foo.html" copy=true} +{{ if eq $foo "bar" }} + {{ print "foo is bar" }} +{{ end }} +``` +```` + +Whitespace trimming is enabled by default. To override this behavior and preserve leading and trailing whitespace: + +````md {file="content/example.md"} +```go-html-template {trim=false} + +{{ if eq $foo "bar" }} + {{ print "foo is bar" }} +{{ end }} + +``` +```` + +### Shortcode calls + +Use this syntax to escape the call within examples: + +```md {file="content/example.md"} +{{}} +{{%/*/* foo */*/%}} +``` + +### Project configuration + +Use the [code-toggle shortcode](#code-toggle) to include project configuration examples: + +```md {file="content/example.md"} +{{}} +baseURL = 'https://example.org/' +locale = 'en-US' +title = 'My Site' +{{}} +``` + +### Front matter + +Use the [code-toggle shortcode](#code-toggle) to include front matter example, setting the `fm` attribute to `true`: + +```md {file="content/example.md"} +{{}} +title = 'My first post' +date = 2023-11-09T12:56:07-08:00 +draft = false +{{}} +``` + +## Callouts + +Use callouts (admonitions) to visually emphasize important information. + +There are five callout types: `note`, `important`, `tip`, `warning`, and `caution`. These are the usage statistic as of 10 Jun 2026, including usage on this page: + +- note (302) +- tip (5) +- important (7) +- warning (7) +- caution (5) + +The predominant use of the `note` callout type is a historical artifact; the previous theme only provided a `note` shortcode, so the callouts were generic. + +In the examples below, the callout type (e.g., `note`, `warning`) is case-insensitive. + +```md {file="content/example.md"} +> [!NOTE] +> Useful information that users should know, even when skimming content. +``` + +> [!NOTE] +> Useful information that users should know, even when skimming content. + +```md {file="content/example.md"} +> [!IMPORTANT] +> Key information users need to know to achieve their goal. +``` + +> [!IMPORTANT] +> Key information users need to know to achieve their goal. + +```md {file="content/example.md"} +> [!TIP] +> Helpful advice for doing things better or more easily. +``` + +> [!TIP] +> Helpful advice for doing things better or more easily. + +```md {file="content/example.md"} +> [!WARNING] +> Urgent info that needs immediate user attention to avoid problems. +``` + +> [!WARNING] +> Urgent info that needs immediate user attention to avoid problems. + +```md {file="content/example.md"} +> [!CAUTION] +> Advises about risks or negative outcomes of certain actions. +``` + +> [!CAUTION] +> Advises about risks or negative outcomes of certain actions. + +## Shortcodes + +These shortcodes are commonly used throughout the documentation. Other shortcodes are available for specialized use. + +### code-toggle + +Use the `code-toggle` shortcode to display examples of project configuration, front matter, or data files. This shortcode takes these arguments: + +`config` +: (`string`) The section of `hugo.Data.docs.config` to render. + +`copy` +: (`bool`) Whether to display a copy-to-clipboard button. Default is `false`. + +`datakey` +: (`string`) The section of `hugo.Data.docs` to render. + +`file` +: (`string`) The file name to display above the rendered code. Omit the file extension for project configuration examples. + +`fm` +: (`bool`) Whether to render the code as front matter. Default is `false`. + +`skipHeader` +: (`bool`) Whether to omit top-level key(s) when rendering a section of `hugo.Data.docs.config`. + +```md {file="content/example.md"} +{{}} +baseURL = 'https://example.org/' +locale = 'en-US' +title = 'My Site' +{{}} +``` + +### deprecated-in + +Use the `deprecated-in` shortcode to indicate that a feature is deprecated: + +```md +{{}} +``` + +You can also include details: + +```md +{{}} +Use the [`hugo.IsServer`](/docs/reference/functions/hugo/isserver/) function instead. +{{}} +``` + +### eturl + +Use the embedded template URL (`eturl`) shortcode to insert an absolute URL to the source code for an embedded template. The shortcode takes a single argument, the base file name of the template (omit the file extension). + +```md {file="content/example.md"} +This is a link to the [embedded alias template][]. + +[embedded alias template]: <{{%/* eturl alias */%}}> +``` + +### glossary-term + +Use the `glossary-term` shortcode to insert the definition of the given glossary term. + +```md {file="content/example.md"} +{{%/* glossary-term scalar */%}} +``` + +### include + +Use the `include` shortcode to include content from another page. + +```md {file="content/example.md"} +{{%/* include "_common/glob-patterns.md" */%}} +``` + +### new-in + +Use the `new-in` shortcode to indicate a new feature: + +```md {file="content/example.md"} +{{}} +``` + +You can also include details: + +```md {file="content/example.md"} +{{}} +This is a new feature. +{{}} +``` + +## New features + +Use the [new-in](#new-in) shortcode to indicate a new feature. + +The new-in shortcode will trigger a build warning if the specified version is older than a predefined threshold, based on differences in major and minor versions. This serves as a reminder to remove this shortcode call. See [details][]. + +## Deprecated features + +Use the [deprecated-in](#deprecated-in) shortcode to indicate that a feature is deprecated. + +The deprecated-in shortcode will trigger a build warning if the specified version is older than a predefined threshold, based on differences in major and minor versions. This serves as a reminder to remove this shortcode call and the associated content. See [details][]. + +When deprecating a feature that has its own page, also set the `expiryDate` in front matter to two years from the date of deprecation. Include a brief comment to explain the setting: + +```yaml +expiryDate: 2028-03-03 # deprecated 2026-03-03 in v0.157.0 +``` + +## GitHub workflow + +> [!NOTE] +> This section assumes that you have a working knowledge of Git and GitHub, and are comfortable working on the command line. + +Use this workflow to create and submit pull requests. + +Step 1 +: Fork the [documentation repository][]. + +Step 2 +: Clone your fork. + +Step 3 +: Create a new branch with a descriptive name that includes the corresponding issue number, if any: + + ```sh + git checkout -b restructure-foo-page-99999 + ``` + +Step 4 +: Make changes. + +Step 5 +: Build the site locally to preview your changes. + +Step 6 +: Commit your changes with a descriptive commit message: + + - Provide a summary on the first line, typically 50 characters or less, followed by a blank line. + - Begin the summary with one of `content`, `theme`, `config`, `all`, or `misc`, followed by a colon, a space, and a brief description of the change beginning with a capital letter + - Use imperative present tense + - Optionally, provide a detailed description where each line is 72 characters or less, followed by a blank line. + - Optionally, add one or more "Fixes" or "Closes" keywords, each on its own line, referencing the [issues][] addressed by this change. + + For example: + + ```sh + git commit -m "content: Restructure the taxonomy page + + This restructures the taxonomy page by splitting topics into logical + sections, each with one or more examples. + + Fixes #9999 + Closes #9998" + ``` + +Step 7 +: Push the new branch to your fork of the documentation repository. + +Step 8 +: Visit the [documentation repository][] and create a pull request (PR). + +Step 9 +: A project maintainer will review your PR and may request changes. You may delete your branch after the maintainer merges your PR. + +[^1]: The field is required, but its data is not. + +[ATX]: https://spec.commonmark.org/current/#atx-headings +[Glossary]: /quick-reference/glossary/ +[basic english]: https://simple.wikipedia.org/wiki/Basic_English +[collapsed link references]: https://discourse.gohugo.io/t/55714 +[details]: https://github.com/gohugoio/hugoDocs/blob/master/layouts/_partials/layouts/blocks/feature-state.html +[developer documentation style guide]: https://developers.google.com/style +[documentation repository]: https://github.com/gohugoio/hugoDocs/ +[fenced code blocks]: https://spec.commonmark.org/current/#fenced-code-blocks +[indented code blocks]: https://spec.commonmark.org/current/#indented-code-blocks +[inline links]: https://discourse.gohugo.io/t/55714 +[issues]: https://github.com/gohugoio/hugoDocs/issues +[list items]: https://spec.commonmark.org/current/#list-items +[project repository]: https://github.com/gohugoio/hugo +[raw HTML]: https://spec.commonmark.org/current/#raw-html +[related content]: /content-management/related-content/ +[setext]: https://spec.commonmark.org/current/#setext-heading diff --git a/docs/content/en/contribute/themes.md b/docs/content/en/contribute/themes.md new file mode 100644 index 0000000..796b5cf --- /dev/null +++ b/docs/content/en/contribute/themes.md @@ -0,0 +1,25 @@ +--- +title: Themes +description: If you've built a Hugo theme and want to contribute back to the Hugo Community, please share it with us. +categories: [] +keywords: [] +aliases: [/contribute/theme/] +--- + +Visit [themes.gohugo.io][] to browse a collection of themes created by the Hugo community. + +To submit your theme: + +1. Read the [submission guidelines][] +1. Open a pull request in the [themes repository][] + +Other useful theme directories: + +- [jamstack.club][] +- [jamstackthemes.dev][] + +[jamstack.club]: https://jamstack.club/#ssg=hugo +[jamstackthemes.dev]: https://jamstackthemes.dev/ssg/hugo +[submission guidelines]: https://github.com/gohugoio/hugoThemesSiteBuilder/tree/main#readme +[themes repository]: https://github.com/gohugoio/hugoThemesSiteBuilder +[themes.gohugo.io]: https://themes.gohugo.io/ diff --git a/docs/content/en/documentation.md b/docs/content/en/documentation.md new file mode 100644 index 0000000..81ed045 --- /dev/null +++ b/docs/content/en/documentation.md @@ -0,0 +1,22 @@ +--- +title: Hugo Documentation +linkTitle: Docs +description: Hugo is the world's fastest static website engine. It's written in Go (aka Golang) and developed by bep, spf13 and friends. +layout: list +params: + searchable: false +--- + + diff --git a/docs/content/en/featured.png b/docs/content/en/featured.png new file mode 100644 index 0000000..09953ae Binary files /dev/null and b/docs/content/en/featured.png differ diff --git a/docs/content/en/functions/_index.md b/docs/content/en/functions/_index.md new file mode 100644 index 0000000..d308121 --- /dev/null +++ b/docs/content/en/functions/_index.md @@ -0,0 +1,8 @@ +--- +title: Functions +description: Use these functions within your templates and archetypes. +categories: [] +keywords: [] +weight: 10 +aliases: [/layout/functions/,/templates/functions] +--- diff --git a/docs/content/en/functions/cast/ToFloat.md b/docs/content/en/functions/cast/ToFloat.md new file mode 100644 index 0000000..5720429 --- /dev/null +++ b/docs/content/en/functions/cast/ToFloat.md @@ -0,0 +1,46 @@ +--- +title: cast.ToFloat +description: Converts a value to a decimal floating-point number (base 10). +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [float] + returnType: float64 + signatures: [cast.ToFloat INPUT] +aliases: [/functions/float] +--- + +With a decimal (base 10) input: + +```go-html-template +{{ float 11 }} → 11 (float64) +{{ float "11" }} → 11 (float64) + +{{ float 11.1 }} → 11.1 (float64) +{{ float "11.1" }} → 11.1 (float64) + +{{ float 11.9 }} → 11.9 (float64) +{{ float "11.9" }} → 11.9 (float64) +``` + +With a binary (base 2) input: + +```go-html-template +{{ float 0b11 }} → 3 (float64) +``` + +With an octal (base 8) input (use either notation): + +```go-html-template +{{ float 011 }} → 9 (float64) +{{ float "011" }} → 11 (float64) + +{{ float 0o11 }} → 9 (float64) +``` + +With a hexadecimal (base 16) input: + +```go-html-template +{{ float 0x11 }} → 17 (float64) +``` diff --git a/docs/content/en/functions/cast/ToInt.md b/docs/content/en/functions/cast/ToInt.md new file mode 100644 index 0000000..0fd1740 --- /dev/null +++ b/docs/content/en/functions/cast/ToInt.md @@ -0,0 +1,50 @@ +--- +title: cast.ToInt +description: Converts a value to a decimal integer (base 10). +keywords: [] +params: + functions_and_methods: + aliases: [int] + returnType: int + signatures: [cast.ToInt INPUT] +aliases: [/functions/int] +--- + +With a decimal (base 10) input: + +```go-html-template +{{ int 11 }} → 11 (int) +{{ int "11" }} → 11 (int) + +{{ int 11.1 }} → 11 (int) +{{ int 11.9 }} → 11 (int) +``` + +With a binary (base 2) input: + +```go-html-template +{{ int 0b11 }} → 3 (int) +{{ int "0b11" }} → 3 (int) +``` + +With an octal (base 8) input (use either notation): + +```go-html-template +{{ int 011 }} → 9 (int) +{{ int "011" }} → 9 (int) + +{{ int 0o11 }} → 9 (int) +{{ int "0o11" }} → 9 (int) +``` + +With a hexadecimal (base 16) input: + +```go-html-template +{{ int 0x11 }} → 17 (int) +{{ int "0x11" }} → 17 (int) +``` + +> [!NOTE] +> Values with a leading zero are octal (base 8). When casting a string representation of a decimal (base 10) number, remove leading zeros: + +`{{ strings.TrimLeft "0" "0011" | int }} → 11` diff --git a/docs/content/en/functions/cast/ToString.md b/docs/content/en/functions/cast/ToString.md new file mode 100644 index 0000000..1bba001 --- /dev/null +++ b/docs/content/en/functions/cast/ToString.md @@ -0,0 +1,49 @@ +--- +title: cast.ToString +description: Converts a value to a string. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [string] + returnType: string + signatures: [cast.ToString INPUT] +aliases: [/functions/string] +--- + +With a decimal (base 10) input: + +```go-html-template +{{ string 11 }} → 11 (string) +{{ string "11" }} → 11 (string) + +{{ string 11.1 }} → 11.1 (string) +{{ string "11.1" }} → 11.1 (string) + +{{ string 11.9 }} → 11.9 (string) +{{ string "11.9" }} → 11.9 (string) +``` + +With a binary (base 2) input: + +```go-html-template +{{ string 0b11 }} → 3 (string) +{{ string "0b11" }} → 0b11 (string) +``` + +With an octal (base 8) input (use either notation): + +```go-html-template +{{ string 011 }} → 9 (string) +{{ string "011" }} → 011 (string) + +{{ string 0o11 }} → 9 (string) +{{ string "0o11" }} → 0o11 (string) +``` + +With a hexadecimal (base 16) input: + +```go-html-template +{{ string 0x11 }} → 17 (string) +{{ string "0x11" }} → 0x11 (string) +``` diff --git a/docs/content/en/functions/cast/_index.md b/docs/content/en/functions/cast/_index.md new file mode 100644 index 0000000..1584ab1 --- /dev/null +++ b/docs/content/en/functions/cast/_index.md @@ -0,0 +1,7 @@ +--- +title: Cast functions +linkTitle: cast +description: Use these functions to cast a value from one data type to another. +categories: [] +keywords: [] +--- diff --git a/docs/content/en/functions/collections/After.md b/docs/content/en/functions/collections/After.md new file mode 100644 index 0000000..904d7af --- /dev/null +++ b/docs/content/en/functions/collections/After.md @@ -0,0 +1,68 @@ +--- +title: collections.After +description: Returns a slice containing the elements after the first N elements of the given slice. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [after] + returnType: '[]any' + signatures: [collections.After N SLICE] +aliases: [/functions/after] +--- + +The following shows `after` being used in conjunction with the [`slice`][] function: + +```go-html-template +{{ $data := slice "one" "two" "three" "four" }} +
      + {{ range after 2 $data }} +
    • {{ . }}
    • + {{ end }} +
    +``` + +The template above is rendered to: + +```html +
      +
    • three
    • +
    • four
    • +
    +``` + +## Example of `after` with `first`: 2nd–4th most recent articles + +You can use `after` in combination with the [`first`][] function and Hugo's [powerful sorting methods][]. Let's assume you have a `section` page at `example.com/articles`. You have 10 articles, but you want your template to show only two rows: + +1. The top row is titled "Featured" and shows only the most recently published article (i.e. by `publishdate` in the content files' front matter). +1. The second row is titled "Recent Articles" and shows only the 2nd- to 4th-most recently published articles. + +```go-html-template {file="layouts/section/articles.html"} +{{ define "main" }} +
    +

    Featured Article

    + {{ range first 1 .Pages.ByPublishDate.Reverse }} +
    +

    {{ .Title }}

    +
    +

    {{ .Description }}

    + {{ end }} +
    +
    +

    Recent Articles

    + {{ range first 3 (after 1 .Pages.ByPublishDate.Reverse) }} +
    +
    +

    {{ .Title }}

    +
    +

    {{ .Description }}

    +
    + {{ end }} +
    +{{ end }} +``` + +[`first`]: /functions/collections/first/ +[`slice`]: /functions/collections/slice/ +[powerful sorting methods]: /quick-reference/page-collections/#sort diff --git a/docs/content/en/functions/collections/Append.md b/docs/content/en/functions/collections/Append.md new file mode 100644 index 0000000..75b6a89 --- /dev/null +++ b/docs/content/en/functions/collections/Append.md @@ -0,0 +1,100 @@ +--- +title: collections.Append +description: Returns a slice by adding one or more elements, or an entire second slice, to the end of the given slice. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [append] + returnType: '[]any' + signatures: + - collections.Append ELEMENT [ELEMENT...] SLICE + - collections.Append SLICE1 SLICE2 +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 }} +``` diff --git a/docs/content/en/functions/collections/Apply.md b/docs/content/en/functions/collections/Apply.md new file mode 100644 index 0000000..1d4f9bf --- /dev/null +++ b/docs/content/en/functions/collections/Apply.md @@ -0,0 +1,26 @@ +--- +title: collections.Apply +description: Returns a slice by transforming each element of the given slice using a specific function and parameters. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [apply] + returnType: '[]any' + signatures: [collections.Apply SLICE FUNCTION PARAM...] +aliases: [/functions/apply] +--- + +The `apply` function takes three or more arguments, depending on the function being applied to the slice elements. + +The first argument is the slice itself, the second argument is the function name, and the remaining arguments are passed to the function, with the string `"."` representing the slice element. + +```go-html-template +{{ $s := slice "hello" "world" }} + +{{ $s = apply $s "strings.FirstUpper" "." }} +{{ $s }} → [Hello World] + +{{ $s = apply $s "strings.Replace" "." "l" "_" }} +{{ $s }} → [He__o Wor_d] +``` diff --git a/docs/content/en/functions/collections/Complement.md b/docs/content/en/functions/collections/Complement.md new file mode 100644 index 0000000..d89dd21 --- /dev/null +++ b/docs/content/en/functions/collections/Complement.md @@ -0,0 +1,73 @@ +--- +title: collections.Complement +description: Returns a slice by identifying elements in the last given slice that do not appear in any of the preceding slices. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [complement] + returnType: '[]any' + signatures: ['collections.Complement SLICE [SLICE...]'] +aliases: [/functions/complement] +--- + +To find the elements within `$c3` that do not exist in `$c1` or `$c2`: + +```go-html-template +{{ $c1 := slice 3 }} +{{ $c2 := slice 4 5 }} +{{ $c3 := slice 1 2 3 4 5 }} + +{{ complement $c1 $c2 $c3 }} → [1 2] +``` + +> [!NOTE] +> Make your code simpler to understand by using a [chained pipeline][]: + +```go-html-template +{{ $c3 | complement $c1 $c2 }} → [1 2] +``` + +You can also use the `complement` function with page collections. Let's say your site has five content types: + +```tree +content/ +├── blog/ +├── books/ +├── faqs/ +├── films/ +└── songs/ +``` + +To list everything except blog articles (`blog`) and frequently asked questions (`faqs`): + +```go-html-template +{{ $blog := where site.RegularPages "Type" "blog" }} +{{ $faqs := where site.RegularPages "Type" "faqs" }} +{{ range site.RegularPages | complement $blog $faqs }} + {{ .LinkTitle }} +{{ end }} +``` + +> [!NOTE] +> Although the example above demonstrates the `complement` function, you could use the [`where`][] function as well: + +```go-html-template +{{ range where site.RegularPages "Type" "not in" (slice "blog" "faqs") }} + {{ .LinkTitle }} +{{ end }} +``` + +In this example we use the `complement` function to remove [stop words][] from a sentence: + +```go-html-template +{{ $text := "The quick brown fox jumps over the lazy dog" }} +{{ $stopWords := slice "a" "an" "in" "over" "the" "under" }} +{{ $filtered := split $text " " | complement $stopWords }} + +{{ delimit $filtered " " }} → The quick brown fox jumps lazy dog +``` + +[`where`]: /functions/collections/where/ +[chained pipeline]: https://pkg.go.dev/text/template#hdr-Pipelines +[stop words]: https://en.wikipedia.org/wiki/Stop_word diff --git a/docs/content/en/functions/collections/D.md b/docs/content/en/functions/collections/D.md new file mode 100644 index 0000000..7150e15 --- /dev/null +++ b/docs/content/en/functions/collections/D.md @@ -0,0 +1,82 @@ +--- +title: collections.D +description: Returns a sorted slice of unique random integers based on a given seed, count, and maximum value. +categories: [] +keywords: [random] +params: + functions_and_methods: + aliases: [] + returnType: '[]int' + signatures: [collections.D SEED N HIGH] +--- + +{{< new-in 0.149.0 />}} + +The `collections.D` function returns a sorted slice of unique random integers in the half-open [interval](g) `[0, HIGH)` using the provided [`SEED`](g) value. The number of elements in the resulting slice is `N` or `HIGH`, whichever is less. + +- `N` and `H` must be integers in the closed interval `[0, 1000000]` +- `SEED` must be an integer in the closed interval `[0, 2^64 - 1]` + +## Return values + +Condition|Return value +:--|:--|:-- +`N <= HIGH`|A sorted random sample of size `N` using J. S. Vitter's [Method D][] for sequential random sampling +`N > HIGH`|The full, sorted range `[0, HIGH)` of size `HIGH` +`N == 0`|An empty slice +`N < 0`|Error +`N > 10^6`|Error +`HIGH == 0`|An empty slice +`HIGH < 0`|Error +`HIGH > 10^6`|Error +`SEED < 0`|Error +{.no-wrap-first-col} + +## Examples + +```go-html-template +{{ collections.D 6 7 42 }} → [4, 9, 10, 20, 22, 24, 41] +``` + +The example above generates the _same_ random numbers each time it is called. To generate a _different_ set of 7 random numbers in the same range, change the seed value. + +```go-html-template +{{ collections.D 2 7 42 }} → [3, 11, 19, 25, 32, 33, 38] +``` + +When `N` is greater than `HIGH`, this function returns the full, sorted range [0, `HIGH`) of size `HIGH`: + +```go-html-template +{{ collections.D 6 42 7 }} → [0 1 2 3 4 5 6] +``` + +A common use case is the selection of random pages from a page collection. For example, to render a list of 5 random pages using the [day of the year][] as the seed value: + +```go-html-template +
      + {{ $p := site.RegularPages }} + {{ range collections.D time.Now.YearDay 5 ($p | len) }} + {{ with (index $p .) }} +
    • {{ .LinkTitle }}
    • + {{ end }} + {{ end }} +
    +``` + +The construct above is significantly faster than using the [`collections.Shuffle`][] function. + +## Seed value + +Choosing an appropriate seed value depends on your objective. + +Objective|Seed example +:--|:-- +Consistent result|`42` +Different result on each call|`int time.Now.UnixNano` +Same result per day|`time.Now.YearDay` +Same result per page|`hash.FNV32a .Path` +Different result per page per day|`hash.FNV32a (print .Path time.Now.YearDay)` + +[Method D]: https://getkerf.wordpress.com/2016/03/30/the-best-algorithm-no-one-knows-about/ +[`collections.Shuffle`]: /functions/collections/shuffle/ +[day of the year]: /methods/time/yearday/ diff --git a/docs/content/en/functions/collections/Delimit.md b/docs/content/en/functions/collections/Delimit.md new file mode 100644 index 0000000..c3226f5 --- /dev/null +++ b/docs/content/en/functions/collections/Delimit.md @@ -0,0 +1,31 @@ +--- +title: collections.Delimit +description: Returns a string by joining the values of the given slice or map with a delimiter. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [delimit] + returnType: string + signatures: ['collections.Delimit SLICE|MAP DELIMITER [LAST]'] +aliases: [/functions/delimit] +--- + +Delimit a slice: + +```go-html-template +{{ $s := slice "b" "a" "c" }} +{{ delimit $s ", " }} → b, a, c +{{ delimit $s ", " " and "}} → b, a and c +``` + +Delimit a map: + +> [!NOTE] +> The `delimit` function sorts maps by key, returning the values. + +```go-html-template +{{ $m := dict "b" 2 "a" 1 "c" 3 }} +{{ delimit $m ", " }} → 1, 2, 3 +{{ delimit $m ", " " and "}} → 1, 2 and 3 +``` diff --git a/docs/content/en/functions/collections/Dictionary.md b/docs/content/en/functions/collections/Dictionary.md new file mode 100644 index 0000000..378d9b5 --- /dev/null +++ b/docs/content/en/functions/collections/Dictionary.md @@ -0,0 +1,51 @@ +--- +title: collections.Dictionary +description: Returns a map created from the given key-value pairs. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [dict] + returnType: map[string]any + signatures: ['collections.Dictionary [VALUE...]'] +aliases: [/functions/dict] +--- + +Specify the key-value pairs as individual arguments: + +```go-html-template +{{ $m := dict "a" 1 "b" 2 }} +``` + +The above produces this data structure: + +```json +{ + "a": 1, + "b": 2 +} +``` + +Note that the `key` can be either a `string` or a `[]string`. The latter is useful to create a deeply nested structure, e.g.: + +```go-html-template +{{ $m := dict (slice "a" "b" "c") "value" }} +``` + +The above produces this data structure: + +```json +{ + "a": { + "b": { + "c": "value" + } + } +} +``` + +To create an empty map: + +```go-html-template +{{ $m := dict }} +``` diff --git a/docs/content/en/functions/collections/First.md b/docs/content/en/functions/collections/First.md new file mode 100644 index 0000000..2df5ec0 --- /dev/null +++ b/docs/content/en/functions/collections/First.md @@ -0,0 +1,56 @@ +--- +title: collections.First +description: Returns the first N elements of the given slice or string. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [first] + returnType: 'any' + signatures: [collections.First N SLICE|STRING] +aliases: [/functions/first] +--- + +```go-html-template +{{ slice "a" "b" "c" | first 1 }} → [a] +{{ slice "a" "b" "c" | first 2 }} → [a b] +``` + +Given that a string is in effect a read-only slice of bytes, this function can be used to return the specified number of bytes from the beginning of the string: + +```go-html-template +{{ "abc" | first 1 }} → a +{{ "abc" | first 2 }} → ab +``` + +Note that a _character_ may consist of multiple _bytes_: + +```go-html-template +{{ "Schön" | first 3 }} → Sch +{{ "Schön" | first 4 }} → Sch\xc3 +{{ "Schön" | first 5 }} → Schö +``` + +To use the `collections.First` function with a page collection: + +```go-html-template +{{ range first 5 .Pages }} + {{ .Render "summary" }} +{{ end }} +``` + +Set `N` to zero to return an empty slice: + +```go-html-template +{{ $emptyPageCollection := first 0 .Pages }} +``` + +Use `first` and [`where`][] together: + +```go-html-template +{{ range where .Pages "Section" "articles" | first 5 }} + {{ .Render "summary" }} +{{ end }} +``` + +[`where`]: /functions/collections/where/ diff --git a/docs/content/en/functions/collections/Group.md b/docs/content/en/functions/collections/Group.md new file mode 100644 index 0000000..cfdbc91 --- /dev/null +++ b/docs/content/en/functions/collections/Group.md @@ -0,0 +1,34 @@ +--- +title: collections.Group +description: Returns a map by grouping the given page collection (slice) by a specific key. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [group] + returnType: page.PageGroup + signatures: [collections.Group KEY PAGES] +aliases: [/functions/group] +--- + +```go-html-template +{{ $new := .Site.RegularPages | first 10 | group "New" }} +{{ $old := .Site.RegularPages | last 10 | group "Old" }} +{{ $groups := slice $new $old }} +{{ range $groups }} +

    {{ .Key }}{{/* Prints "New", "Old" */}}

    +
      + {{ range .Pages }} +
    • + {{ .LinkTitle }} +
      {{ .Date.Format "Mon, Jan 2, 2006" }}
      +
    • + {{ end }} +
    +{{ end }} +``` + +The page group you get from `group` is of the same type you get from the built-in [group methods][] in Hugo. The example above can be [paginated][]. + +[group methods]: /quick-reference/page-collections/#group +[paginated]: /templates/pagination/ diff --git a/docs/content/en/functions/collections/In.md b/docs/content/en/functions/collections/In.md new file mode 100644 index 0000000..3ae72f1 --- /dev/null +++ b/docs/content/en/functions/collections/In.md @@ -0,0 +1,22 @@ +--- +title: collections.In +description: Reports whether a value exists within the given slice or string. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [in] + returnType: bool + signatures: [collections.In SLICE|STRING VALUE] +aliases: [/functions/in] +--- + +```go-html-template +{{ $s := slice "a" "b" "c" }} +{{ in $s "b" }} → 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 new file mode 100644 index 0000000..f7155c5 --- /dev/null +++ b/docs/content/en/functions/collections/IndexFunction.md @@ -0,0 +1,50 @@ +--- +title: collections.Index +description: Returns an element or value from the given slice or map at the specified key(s). +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [index] + returnType: any + signatures: [collections.Index SLICE|MAP 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 new file mode 100644 index 0000000..7c18d61 --- /dev/null +++ b/docs/content/en/functions/collections/Intersect.md @@ -0,0 +1,26 @@ +--- +title: collections.Intersect +description: Returns a slice containing the common elements found in two given slices, in the same order as the first slice. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [intersect] + returnType: '[]any' + signatures: [collections.Intersect SLICE1 SLICE2] +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][] for `OR`. + +[union]: /functions/collections/union/ diff --git a/docs/content/en/functions/collections/IsSet.md b/docs/content/en/functions/collections/IsSet.md new file mode 100644 index 0000000..8d88df9 --- /dev/null +++ b/docs/content/en/functions/collections/IsSet.md @@ -0,0 +1,42 @@ +--- +title: collections.IsSet +description: Reports whether a specific key or index exists in the given map or slice. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [isset] + returnType: bool + signatures: [collections.IsSet MAP|SLICE KEY|INDEX] +aliases: [/functions/isset] +--- + +For example, consider this project configuration: + +{{< code-toggle file=hugo >}} +[params] +showHeroImage = false +{{< /code-toggle >}} + +If 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 }} +``` + +However, 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 new file mode 100644 index 0000000..32a6fc0 --- /dev/null +++ b/docs/content/en/functions/collections/KeyVals.md @@ -0,0 +1,43 @@ +--- +title: collections.KeyVals +description: Returns a KeyVals struct by pairing a given key and values. +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. + +See [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] +``` + +[`Related`]: /methods/pages/related/ +[related content]: /content-management/related-content/ diff --git a/docs/content/en/functions/collections/Last.md b/docs/content/en/functions/collections/Last.md new file mode 100644 index 0000000..4f7d7b8a --- /dev/null +++ b/docs/content/en/functions/collections/Last.md @@ -0,0 +1,56 @@ +--- +title: collections.Last +description: Returns the last N elements of the given slice or string. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [last] + returnType: 'any' + signatures: [collections.Last N SLICE|STRING] +aliases: [/functions/last] +--- + +```go-html-template +{{ slice "a" "b" "c" | last 1 }} → [c] +{{ slice "a" "b" "c" | last 2 }} → [b c] +``` + +Given that a string is in effect a read-only slice of bytes, this function can be used to return the specified number of bytes from the end of the string: + +```go-html-template +{{ "abc" | last 1 }} → c +{{ "abc" | last 2 }} → bc +``` + +Note that a _character_ may consist of multiple _bytes_: + +```go-html-template +{{ "Schön" | last 1 }} → n +{{ "Schön" | last 2 }} → \xb6n +{{ "Schön" | last 3 }} → ön +``` + +To use the `collections.Last` function with a page collection: + +```go-html-template +{{ range last 5 .Pages }} + {{ .Render "summary" }} +{{ end }} +``` + +Set `N` to zero to return an empty slice: + +```go-html-template +{{ $emptyPageCollection := last 0 .Pages }} +``` + +Use `last` and [`where`][] together: + +```go-html-template +{{ range where .Pages "Section" "articles" | last 5 }} + {{ .Render "summary" }} +{{ end }} +``` + +[`where`]: /functions/collections/where/ diff --git a/docs/content/en/functions/collections/Merge.md b/docs/content/en/functions/collections/Merge.md new file mode 100644 index 0000000..4eabde3 --- /dev/null +++ b/docs/content/en/functions/collections/Merge.md @@ -0,0 +1,69 @@ +--- +title: collections.Merge +description: Returns a map by combining two or more given maps. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [merge] + returnType: map[string]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 the [`collections.Append`][] function. + +[`collections.Append`]: /functions/collections/append/ diff --git a/docs/content/en/functions/collections/NewScratch.md b/docs/content/en/functions/collections/NewScratch.md new file mode 100644 index 0000000..abf99a6 --- /dev/null +++ b/docs/content/en/functions/collections/NewScratch.md @@ -0,0 +1,113 @@ +--- +title: collections.NewScratch +description: Returns a locally scoped persistent data structure for storing and manipulating keyed values. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [newScratch] + returnType: maps.Scratch + signatures: [collections.NewScratch] +--- + +Use the `collections.NewScratch` function to create a locally scoped persistent data structure for storing and manipulating keyed values. To create a data structure with a different [scope](g), refer to the [scope](#scope) section below. + +## Methods + +Use these methods on the data structure. + +`Set` +: Sets the value of the given key. + + ```go-html-template + {{ $s := newScratch }} + {{ $s.Set "greeting" "Hello" }} + ``` + +`Get` +: (`any`) 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` +: (`[]any`) 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` +: (`map`) 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/store-scope.md" %}} diff --git a/docs/content/en/functions/collections/Querify.md b/docs/content/en/functions/collections/Querify.md new file mode 100644 index 0000000..53a30f0 --- /dev/null +++ b/docs/content/en/functions/collections/Querify.md @@ -0,0 +1,48 @@ +--- +title: collections.Querify +description: Returns a URL query string from the given map, slice, or sequence of key-value pairs. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [querify] + returnType: string + signatures: [collections.Querify MAP|SLICE|KEY 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 project 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 new file mode 100644 index 0000000..e443583 --- /dev/null +++ b/docs/content/en/functions/collections/Reverse.md @@ -0,0 +1,16 @@ +--- +title: collections.Reverse +description: Returns a slice by reversing the order of elements in the given slice. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [] + returnType: '[]any' + signatures: [collections.Reverse SLICE] +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 new file mode 100644 index 0000000..0bc950f --- /dev/null +++ b/docs/content/en/functions/collections/Seq.md @@ -0,0 +1,35 @@ +--- +title: collections.Seq +description: Returns a slice of integers starting from 1 or a given value, incrementing by 1 or a given value, and ending at a given value. +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 this function is limited to 1 million elements. diff --git a/docs/content/en/functions/collections/Shuffle.md b/docs/content/en/functions/collections/Shuffle.md new file mode 100644 index 0000000..17a675a --- /dev/null +++ b/docs/content/en/functions/collections/Shuffle.md @@ -0,0 +1,35 @@ +--- +title: collections.Shuffle +description: Returns a slice by randomizing the element order of the given slice. +categories: [] +keywords: [random] +params: + functions_and_methods: + aliases: [shuffle] + returnType: '[]any' + signatures: [collections.Shuffle SLICE] +aliases: [/functions/shuffle] +--- + +```go-html-template +{{ collections.Shuffle (slice "a" "b" "c") }} → [b a c] +``` + +The result will vary from one build to the next. + +To render an unordered list of 5 random pages from a page collection: + +```go-html-template +

      + {{ $p := site.RegularPages }} + {{ range $p | collections.Shuffle | first 5 }} +
    • {{ .LinkTitle }}
    • + {{ end }} +
    +``` + +{{< new-in 0.149.0 />}} + +Using the [`collections.D`][] function for the same task is significantly faster. + +[`collections.D`]: /functions/collections/D/ diff --git a/docs/content/en/functions/collections/Slice.md b/docs/content/en/functions/collections/Slice.md new file mode 100644 index 0000000..bcf9b9c --- /dev/null +++ b/docs/content/en/functions/collections/Slice.md @@ -0,0 +1,23 @@ +--- +title: collections.Slice +description: Returns a slice created from 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 new file mode 100644 index 0000000..bbb6c9b --- /dev/null +++ b/docs/content/en/functions/collections/Sort.md @@ -0,0 +1,150 @@ +--- +title: collections.Sort +description: Returns a sorted map or slice by reordering the given collection by a key and order. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [sort] + returnType: any + signatures: ['collections.Sort MAP|SLICE [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 project 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 project 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" }} +

    {{ .LinkTitle }}

    +{{ 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 new file mode 100644 index 0000000..a895da0 --- /dev/null +++ b/docs/content/en/functions/collections/SymDiff.md @@ -0,0 +1,20 @@ +--- +title: collections.SymDiff +description: Returns a slice containing the symmetric difference of two given slices. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [symdiff] + returnType: '[]any' + signatures: [SLICE1 | collections.SymDiff SLICE2] +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 new file mode 100644 index 0000000..da99356 --- /dev/null +++ b/docs/content/en/functions/collections/Union.md @@ -0,0 +1,37 @@ +--- +title: collections.Union +description: Returns a slice containing the unique elements from two given slices. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [union] + returnType: '[]any' + signatures: [collections.Union SLICE1 SLICE2] +aliases: [/functions/union] +--- + + + +```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 useful 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][] for `AND`. + +[intersect]: /functions/collections/intersect/ diff --git a/docs/content/en/functions/collections/Uniq.md b/docs/content/en/functions/collections/Uniq.md new file mode 100644 index 0000000..664c851 --- /dev/null +++ b/docs/content/en/functions/collections/Uniq.md @@ -0,0 +1,16 @@ +--- +title: collections.Uniq +description: Returns a slice by removing duplicate elements from the given slice. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [uniq] + returnType: '[]any' + signatures: [collections.Uniq SLICE] +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 new file mode 100644 index 0000000..8b3c2eb --- /dev/null +++ b/docs/content/en/functions/collections/Where.md @@ -0,0 +1,418 @@ +--- +title: collections.Where +description: Returns a slice by filtering the given slice based on a key, operator, and value. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [where] + returnType: '[]any' + signatures: ['collections.Where SLICE KEY [OPERATOR] VALUE'] +aliases: [/functions/where] +--- + +The `where` function returns the given slice, removing elements that do not satisfy the comparison condition. The comparison condition is composed of the `KEY`, `OPERATOR`, and `VALUE` arguments: + +```text +collections.Where SLICE 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 hugo.Data.books "genres" "suspense" }} +``` + +## Arguments + +The where function takes three or four arguments. The `OPERATOR` argument is optional. + +`SLICE` +: (`[]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. + +`not in` +: (`bool`) Reports whether the given field value is not a member of `VALUE`. Compare string to slice, or string to string. +`intersect` +: (`bool`) Reports whether the given field value (a slice) contains one or more elements in common with `VALUE`. + +`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 slice 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 slice 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 elements with common values. This is frequently used when comparing taxonomy terms. + +For example, to return a slice 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 slice 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 slice. + +## 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 slice 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. + +```md {file="content/events/2024-user-conference.md"} ++++ +title = '2024 User Conference" +eventDate = 2024-04-01 ++++ +``` + +To return a slice 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 over the slice: + +```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 slice 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 slice 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 their project configuration: + +{{< code-toggle file=hugo >}} +mainSections = ['blog','galleries'] +{{< /code-toggle >}} + +If `mainSections` is not defined in your project configuration, the `MainSections` method returns a slice with one element---the top-level section with the most pages. + +## Boolean/undefined comparison + +Consider this project structure: + +```tree +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 }} +
    • {{ .LinkTitle }}
    • + {{ end }} +
    +``` + +Is rendered to: + +```html + +``` + +This template: + +```go-html-template +
      + {{ range where .Site.RegularPages "Params.exclude" "eq" true }} +
    • {{ .LinkTitle }}
    • + {{ end }} +
    +``` + +Is rendered to: + +```html + +``` + +### Inequality test + +This template: + +```go-html-template +
      + {{ range where .Site.RegularPages "Params.exclude" "ne" false }} +
    • {{ .LinkTitle }}
    • + {{ end }} +
    +``` + +Is rendered to: + +```html + +``` + +This template: + +```go-html-template +
      + {{ range where .Site.RegularPages "Params.exclude" "ne" true }} +
    • {{ .LinkTitle }}
    • + {{ end }} +
    +``` + +Is rendered to: + +```html + +``` + +To exclude a page with an undefined field from a boolean _inequality_ test: + +1. Create a slice using a boolean comparison +1. Create a slice using a nil comparison +1. Subtract the second slice from the first slice 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 }} + +``` + +Is rendered to: + +```html + +``` + +This template: + +```go-html-template +{{ $p1 := where .Site.RegularPages "Params.exclude" "ne" false }} +{{ $p2 := where .Site.RegularPages "Params.exclude" "eq" nil }} + +``` + +Is rendered to: + +```html + +``` + +[`MainSections`]: /methods/site/mainsections/ +[`collections.Complement`]: /functions/collections/complement/ +[`date`]: /methods/page/date/ +[`lastmod`]: /methods/page/lastmod/ +[`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 new file mode 100644 index 0000000..0f07db5 --- /dev/null +++ b/docs/content/en/functions/collections/_index.md @@ -0,0 +1,7 @@ +--- +title: Collections functions +linkTitle: collections +description: Use these functions to manipulate and query maps, slices, and strings. +categories: [] +keywords: [] +--- diff --git a/docs/content/en/functions/compare/Conditional.md b/docs/content/en/functions/compare/Conditional.md new file mode 100644 index 0000000..50e5c2d --- /dev/null +++ b/docs/content/en/functions/compare/Conditional.md @@ -0,0 +1,31 @@ +--- +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. + +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" }} +``` + +[short-circuit evaluation]: https://en.wikipedia.org/wiki/Short-circuit_evaluation +[ternary operators]: https://en.wikipedia.org/wiki/Ternary_conditional_operator diff --git a/docs/content/en/functions/compare/Default.md b/docs/content/en/functions/compare/Default.md new file mode 100644 index 0000000..0b00450 --- /dev/null +++ b/docs/content/en/functions/compare/Default.md @@ -0,0 +1,47 @@ +--- +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 nil }} → 42 +``` + +[`or`]: /functions/go-template/or/ diff --git a/docs/content/en/functions/compare/Eq.md b/docs/content/en/functions/compare/Eq.md new file mode 100644 index 0000000..583e2e4 --- /dev/null +++ b/docs/content/en/functions/compare/Eq.md @@ -0,0 +1,24 @@ +--- +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 new file mode 100644 index 0000000..fd793f0 --- /dev/null +++ b/docs/content/en/functions/compare/Ge.md @@ -0,0 +1,35 @@ +--- +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 new file mode 100644 index 0000000..f48312c --- /dev/null +++ b/docs/content/en/functions/compare/Gt.md @@ -0,0 +1,35 @@ +--- +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 new file mode 100644 index 0000000..b490d68 --- /dev/null +++ b/docs/content/en/functions/compare/Le.md @@ -0,0 +1,35 @@ +--- +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 new file mode 100644 index 0000000..a5578cc --- /dev/null +++ b/docs/content/en/functions/compare/Lt.md @@ -0,0 +1,35 @@ +--- +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 new file mode 100644 index 0000000..8839983 --- /dev/null +++ b/docs/content/en/functions/compare/Ne.md @@ -0,0 +1,24 @@ +--- +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 new file mode 100644 index 0000000..59673a2 --- /dev/null +++ b/docs/content/en/functions/compare/_index.md @@ -0,0 +1,7 @@ +--- +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/HMAC.md b/docs/content/en/functions/crypto/HMAC.md new file mode 100644 index 0000000..5929826 --- /dev/null +++ b/docs/content/en/functions/crypto/HMAC.md @@ -0,0 +1,27 @@ +--- +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/Hash.md b/docs/content/en/functions/crypto/Hash.md new file mode 100644 index 0000000..44ebc62 --- /dev/null +++ b/docs/content/en/functions/crypto/Hash.md @@ -0,0 +1,36 @@ +--- +title: crypto.Hash +description: Hashes the given input with the given algorithm and returns its checksum encoded to a hexadecimal string. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [] + returnType: string + signatures: ['crypto.Hash [ALGORITHM] INPUT'] +--- + +The `ALGORITHM` is one of `md5`, `sha1`, `sha256` (the default), `sha384`, or `sha512`: + +```go-html-template +{{ crypto.Hash "sha256" "Hello world" }} → 64ec88ca00b268e5ba1a35678a1b5316d212f4f366b2477232534a8aeca37f3c +{{ "Hello world" | crypto.Hash "sha512" }} → b7f783baed8297f0db917462184ff4f08e69c2d5e5f79a942600f9725f58ce1f29c18139bf80b06c0fff2bdd34738452ecf40c488c22a7e3d80cdf6f9c1c0d47 +``` + +If you omit the algorithm, it defaults to `sha256`: + +```go-html-template +{{ "Hello world" | crypto.Hash }} → 64ec88ca00b268e5ba1a35678a1b5316d212f4f366b2477232534a8aeca37f3c +``` + +The supported algorithms match those used for the [Subresource Integrity] hash in [`.Data.Integrity`] on a fingerprinted resource. Combine `crypto.Hash` with [`encoding.HexDecode`] and [`encoding.Base64Encode`] to construct an SRI hash from a string: + +```go-html-template +{{ $algo := "sha256" }} +{{ $integrity := printf "%s-%s" $algo ("Hello world" | crypto.Hash $algo | encoding.HexDecode | encoding.Base64Encode) }} +``` + +[Subresource Integrity]: https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity +[`.Data.Integrity`]: /methods/resource/data/ +[`encoding.HexDecode`]: /functions/encoding/hexdecode/ +[`encoding.Base64Encode`]: /functions/encoding/base64encode/ diff --git a/docs/content/en/functions/crypto/MD5.md b/docs/content/en/functions/crypto/MD5.md new file mode 100644 index 0000000..6cc7790 --- /dev/null +++ b/docs/content/en/functions/crypto/MD5.md @@ -0,0 +1,24 @@ +--- +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][] for generating a unique avatar: + +```html + +``` + +[Gravatar]: https://en.gravatar.com/ diff --git a/docs/content/en/functions/crypto/SHA1.md b/docs/content/en/functions/crypto/SHA1.md new file mode 100644 index 0000000..c80dac0 --- /dev/null +++ b/docs/content/en/functions/crypto/SHA1.md @@ -0,0 +1,16 @@ +--- +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 new file mode 100644 index 0000000..d0a66c0 --- /dev/null +++ b/docs/content/en/functions/crypto/SHA256.md @@ -0,0 +1,16 @@ +--- +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 new file mode 100644 index 0000000..5771630 --- /dev/null +++ b/docs/content/en/functions/crypto/_index.md @@ -0,0 +1,7 @@ +--- +title: Crypto functions +linkTitle: crypto +description: Use these functions to create cryptographic hashes. +categories: [] +keywords: [] +--- diff --git a/docs/content/en/functions/css/Build.md b/docs/content/en/functions/css/Build.md new file mode 100644 index 0000000..59b6cb1 --- /dev/null +++ b/docs/content/en/functions/css/Build.md @@ -0,0 +1,381 @@ +--- +title: css.Build +description: Bundle, transform, and minify CSS resources. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [] + returnType: resource.Resource + signatures: ['css.Build [OPTIONS] RESOURCE'] +--- + +{{< new-in 0.158.0 />}} + +> [!NOTE] +> The `css.Build` function is backed by the [`evanw/esbuild`][] package, providing a mature, high-performance foundation for bundling, transformation, and minification. + +Use the `css.Build` function to: + +- Recursively replace `@import` statements in CSS files with the content of the imported files +- Transform syntax for browser compatibility +- Apply vendor prefixes for browser compatibility +- Minify the bundled CSS code +- Create a source map + +If an `@import` statement includes a media query, a feature query, or a cascade layer assignment, the function wraps the imported content in the corresponding `@media`, `@supports`, or `@layer` rule. + +## Usage + +In this example, Hugo bundles the local files referenced by `@import` statements to create and publish a single resource with inline content. + +```tree +assets/ +└── css/ + ├── components/ + │ ├── a.css + │ └── b.css + └── main.css +``` + +```css {file="assets/css/main.css" copy=true} +@import url('https://cdn.jsdelivr.net/npm/the-new-css-reset/css/reset.min.css'); + +@import './components/a.css'; +@import './components/b.css'; + +.c {color: blue; } +``` + +```css {file="assets/css/components/a.css" copy=true} +.a { color: red; } +``` + +```css {file="assets/css/components/b.css" copy=true} +.b { color: green; } +``` + +```go-html-template {file="layouts/_partials/css.html" copy=true} +{{ with resources.Get "css/main.css" | css.Build }} + {{ if hugo.IsDevelopment }} + + {{ else }} + {{ with . | fingerprint }} + + {{ end }} + {{ end }} +{{ end }} +``` + +```go-html-template {file="layouts/baseof.html" copy=true} +{{ partialCached "css.html" . }} +``` + +The generated CSS code: + +```css {file="public/css/main.css"} +@import "https://cdn.jsdelivr.net/npm/the-new-css-reset/css/reset.min.css"; + +.a { + color: red; +} + +.b { + color: green; +} + +.c { + color: blue; +} +``` + +To minify the generated CSS code, use the [`minify`](#minify) option as described below. + +## Options + +The `css.Build` function accepts an options map to fine-tune bundling, minification, and browser compatibility. + +`externals` +: (`[]string`) A slice of path patterns to exclude from bundling. The `@import` statements for these patterns remain as-is in the generated CSS code. See [details][esb external]. + + ```go-html-template + {{ $opts := dict "externals" (slice "./exclude-these/*" "./exclude-these-too/*") }} + {{ $r := resources.Get "css/main.css" | css.Build $opts }} + ``` + +`loaders` +: (`map`) A map of file extensions to loader types. This determines how files with a given extension are processed during bundling. By default, Hugo uses the `css` loader for `.css` files and the `file` loader for all others. Common loaders include: + + - `css`: Processes the file as a CSS file + - `dataurl`: Embeds the file as a base64-encoded data URL + - `empty`: Excludes the file from the bundle + - `file`: Copies the file to the output directory and rewrites the URL + - `text`: Loads the file content as a string + + See [details][esb loader]. + + ```go-html-template + {{ $opts := dict "loaders" (dict ".png" "dataurl" ".svg" "dataurl") }} + {{ $r := resources.Get "css/main.css" | css.Build $opts }} + ``` + +`mainFields` +: (`[]string`) A prioritized slice of field names in a `package.json` file that determine the CSS entry point of a Node package. The default is `["style", "main"]`. See [details][esb mainfields]. + + When an `@import` statement references a Node package, Hugo consults the metadata in the `package.json` file to find the stylesheet. Use this option to support packages that define a CSS entry point using non-standard fields. + + ```go-html-template + {{ $opts := dict "mainFields" (slice "css" "style" "main") }} + {{ $r := resources.Get "css/main.css" | css.Build $opts }} + ``` + +`minify` +: (`bool`) Whether to minify the generated CSS code. Default is `false`. See [details][esb minify]. + + ```go-html-template + {{ $opts := dict "minify" true }} + {{ $r := resources.Get "css/main.css" | css.Build $opts }} + ``` + +`sourceMap` +: (`string`) The type of source map to generate. One of `external`, `inline`, `linked`, or `none`. Default is `none`. See [details][esb sourcemap]. + + ```go-html-template + {{ $opts := dict "sourceMap" "linked" }} + {{ $r := resources.Get "css/main.css" | css.Build $opts }} + ``` + +`sourcesContent` +: (`bool`) Whether to include the content of the source files in the source map. Default is `true`. See [details][esb sourcesContent]. + + ```go-html-template + {{ $opts := dict "sourceMap" "linked" "sourcesContent" false }} + {{ $r := resources.Get "css/main.css" | css.Build $opts }} + ``` + +`target` +: (`[]string`) The target environment for the generated CSS code. This determines which syntax transformations to perform and which vendor prefixes to apply. If unset, no transformations or prefixing are performed. Each element consists of a target name and a version number. Supported targets include `chrome`, `edge`, `firefox`, `ie`, `ios`, `opera`, and `safari`. See [details][esb target]. + + ```go-html-template + {{ $target := slice "chrome115" "edge115" "firefox116" "ios16.4" "opera101" "safari16.4" }} + {{ $opts := dict "target" $target }} + {{ $r := resources.Get "css/main.css" | css.Build $opts }} + ``` + + In the example above, the target environment is roughly equivalent to the [browserlist][] "baseline widely available" profile as of March 2026. + +`targetPath` +: (`string`) The path to the generated CSS file, relative to the project's [`publishDir`][]. If unset, this defaults to the asset's original path with a `.css` extension. + + ```go-html-template + {{ $opts := dict "targetPath" "css/styles.css" }} + {{ $r := resources.Get "css/main.css" | css.Build $opts }} + ``` + +`vars` +: {{< new-in 0.160.0 />}} +: (`map`) A map of key-value pairs used to generate CSS variables. The `css.Build` function injects these variables into the stylesheet when it encounters the `hugo:vars` internal identifier within an `@import` statement. + + ```go-html-template + {{ $vars := dict + "font-family" "\"Times New Roman\", Times, serif" + "font-size" "24px" + "primary-color" "blue" + }} + {{ $opts := dict "vars" $vars }} + {{ $r := resources.Get "css/main.css" | css.Build $opts }} + ``` + + In the example above, using the identifier in your CSS allows you to access the values using standard CSS variable syntax. + + ```css + @import 'hugo:vars'; + + .element { + color: var(--primary-color); + font-family: var(--font-family); + font-size: var(--font-size); + } + ``` + + The above produces output equivalent to: + + ```css + :root { + --font-family: + "Times New Roman", + Times, + serif; + --font-size: 24px; + --primary-color: blue; + } + + .element { + color: var(--primary-color); + font-family: var(--font-family); + font-size: var(--font-size); + } + ``` + + {{< new-in 0.161.0 />}} + + The map may optionally contain nested maps. Each nested map is exposed as a separate `hugo:vars/` namespace, where `` is the key of the nested map (lowercased). Top-level scalar values and nested maps are independent. A top-level `@import 'hugo:vars'` only includes scalar values, while `@import 'hugo:vars/'` only includes the scalars from the named nested map. + + ```go-html-template + {{ $vars := dict + "font-family" "\"Times New Roman\", Times, serif" + "font-size" "24px" + "primary-color" "blue" + "mobile" (dict + "font-size" "12px" + "primary-color" "red" + ) + }} + {{ $opts := dict "vars" $vars }} + {{ $r := resources.Get "css/main.css" | css.Build $opts }} + ``` + + Because nested imports follow the same rules as regular `@import` statements, you can attach a media query, feature query, or cascade layer assignment to a `hugo:vars/` import. + + ```css + @import 'hugo:vars'; + @import 'hugo:vars/mobile' (max-width: 650px); + + body { + background-color: var(--primary-color); + font-family: var(--font-family); + } + ``` + + The above produces output equivalent to: + + ```css + :root { + --font-family: "Times New Roman", Times, serif; + --font-size: 24px; + --primary-color: blue; + } + + @media (max-width: 650px) { + :root { + --font-size: 12px; + --primary-color: red; + } + } + + body { + background-color: var(--primary-color); + font-family: var(--font-family); + } + ``` + + The `vars` option is useful for setting CSS variables within your project configuration. + + {{< code-toggle file=hugo >}} + [params.theme.style] + font-family = '"Times New Roman", Times, serif' + font-size = '24px' + primary-color = 'blue' + + [params.theme.style.mobile] + font-size = '12px' + primary-color = 'red' + {{< /code-toggle >}} + + ```go-html-template + {{ $opts := dict "vars" site.Params.theme.style }} + {{ $r := resources.Get "css/main.css" | css.Build $opts }} + ``` + + When passing a `vars` map to the `css.Build` function, you can use the [`css.Quoted`][] function to explicitly indicate that a value must be treated as a quoted string, most commonly for `font-family` names or the `content` property. + + > [!NOTE] + > If you're using TailwindCSS and want to use the `vars` option to inject CSS variables, see [this section in the TailwindCSS documentation](./TailwindCSS.md#inject-css-variables). + +## Example + +The example below uses several of the [options](#options) described above to bundle, transform, and minify CSS code. + +```go-html-template {file="layouts/_partials/css.html" copy=true} +{{ with resources.Get "css/main.css" }} + {{ $opts := dict + "loaders" (dict ".png" "dataurl" ".svg" "dataurl") + "minify" (cond hugo.IsDevelopment false true) + "sourceMap" (cond hugo.IsDevelopment "linked" "none") + "target" (slice "chrome115" "edge115" "firefox116" "ios16.4" "opera101" "safari16.4") + "targetPath" "css/styles.css" + }} + {{ with . | css.Build $opts }} + {{ if hugo.IsDevelopment }} + + {{ else }} + {{ with . | fingerprint }} + + {{ end }} + {{ end }} + {{ end }} +{{ end }} +``` + +Using the options above, Hugo does the following: + +- Embeds PNG and SVG images as data URLs in the generated CSS code +- Minifies the output in production but not in development +- Generates an external source map in development but not in production +- Transforms syntax for compatibility with the targeted browser versions +- Adds vendor prefixes for compatibility with the targeted browser versions +- Publishes the generated CSS code to `css/styles.css` +- In production, adds an SRI hash and inserts a file hash into the filename + +## Common patterns + +The examples below cover the most frequent use cases for referencing resources within your project or within Node packages. These patterns apply to both `@import` statements and the `url()` functional notation used for images and fonts. + +All resources referenced by a path, including images, fonts, and stylesheets, must reside in the `assets` directory of the [unified file system](g), or within a Node package. + +### Files in the assets directory + +To include a stylesheet from the `assets` directory, you can use a bare path, a relative path, or a root-relative path. When you use a bare path, Hugo searches relative to the current stylesheet, then relative to the `assets` directory. + +```css {file="/assets/css/main.css"} +/* A bare path */ +@import "variables.css"; + +/* A relative path */ +@import "./theme.css"; +@import "../layout.css"; + +/* A root-relative path */ +@import "/css/grid.css"; + +/* A url() reference using the same resolution logic */ +.logo { background: url("/images/logo.svg"); } +``` + +### Node packages + +When referencing a Node package by name, Hugo consults the `package.json` file within that package to find the entry point. + +```css {file="/assets/css/main.css"} +@import "bootstrap"; +``` + +### Files within a package + +To reference a specific file within a Node package, provide the path starting with the package name. + +```css {file="/assets/css/main.css"} +@import "bootstrap/dist/css/bootstrap-grid.css"; +``` + +[`css.Quoted`]: /functions/css/quoted/ +[`evanw/esbuild`]: https://github.com/evanw/esbuild +[`publishDir`]: /configuration/all/#publishdir +[browserlist]: https://browsersl.ist +[esb external]: https://esbuild.github.io/api/#external +[esb loader]: https://esbuild.github.io/api/#loader +[esb mainfields]: https://esbuild.github.io/api/#main-fields +[esb minify]: https://esbuild.github.io/api/#minify +[esb sourcemap]: https://esbuild.github.io/api/#sourcemap +[esb sourcesContent]: https://esbuild.github.io/api/#sources-content +[esb target]: https://esbuild.github.io/api/#target diff --git a/docs/content/en/functions/css/PostCSS.md b/docs/content/en/functions/css/PostCSS.md new file mode 100644 index 0000000..3e98ff1 --- /dev/null +++ b/docs/content/en/functions/css/PostCSS.md @@ -0,0 +1,136 @@ +--- +title: css.PostCSS +description: Process CSS resources using PostCSS. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [postCSS] + returnType: resource.Resource + signatures: ['css.PostCSS [OPTIONS] RESOURCE'] +aliases: [/functions/resources/postcss/] +--- + +The `css.PostCSS` function transforms CSS using [PostCSS][] and any of its [plugins][]. + +## Setup + +Step 1 +: Install [Node.js][]. + +Step 2 +: Install the required Node packages in the root of your project. For example, to install PostCSS, its command-line interface, and the plugin to automatically add vendor prefixes to your CSS: + + ```sh + npm install --save-dev postcss postcss-cli autoprefixer + ``` + +Step 3 +: Create a PostCSS configuration file in the root of your project. The current Hugo [environment](g) name is available in the Node context. For example, in this configuration, running `hugo server` disables vendor prefixes but enables an inline sourcemap. Conversely, when building for production, it applies vendor prefixes and disables the sourcemap: + + ```js {file="postcss.config.mjs" copy=true} + import autoprefixer from 'autoprefixer'; + + const isDev = process.env.HUGO_ENVIRONMENT === 'development'; + + export default { + plugins: [ + !isDev ? autoprefixer : null + ], + map: isDev ? { inline: true } : false + }; + ``` + +Step 4 +: Place your CSS file within the `assets/css` directory. + +Step 5 +: Create a partial template to process the CSS: + + ```go-html-template {file="layouts/_partials/css.html" copy=true} + {{ with resources.Get "css/main.css" }} + {{ $opts := dict + "inlineImports" true + }} + {{ with . | css.PostCSS $opts }} + {{ if hugo.IsDevelopment }} + + {{ else }} + {{ with . | minify | fingerprint }} + + {{ end }} + {{ end }} + {{ end }} + {{ end }} + ``` + +Step 6 +: Call the partial template from your base template: + + ```go-html-template {file="layouts/baseof.html" copy=true} + + {{ partial "css.html" . }} + + ``` + +## Options + +The `css.PostCSS` function accepts an options map. + +`config` +: (`string`) The path to the directory that contains the PostCSS configuration file. By default, Hugo searches the root of the project directory and any modules for `postcss.config.js`, `postcss.config.mjs`, and `postcss.config.cjs` in that order. Use this option only if your configuration file is located in a custom subdirectory. + +`inlineImports` +: (`bool`) Whether to enable inlining of import statements. It does so recursively, but will only import a file once. Hugo looks for imports relative to the module mount and respects theme overrides. Default is `false`. + + Note that Hugo's internal import routine does not adhere to the CSS specification; you can place `@import` statements anywhere in the file. However, external URL imports and imports with media queries are ignored during the inlining process. + + The following snippet illustrates an external URL import that Hugo will ignore: + + ```css + @import url('https://fonts.googleapis.com/css?family=Open+Sans&display=swap'); + ``` + +`skipInlineImportsNotFound` +: (`bool`) Whether to allow the build process to continue despite unresolved import statements, preserving the original import declarations. Set this option to `true` if you want to retain standard CSS imports unparsed. Default is `false`. + +To avoid using a PostCSS configuration file, you can specify a minimal configuration with these options: + +`noMap` +: (`bool`) Whether to disable the default inline source maps. Default is `false`. + +`parser` +: (`string`) A custom PostCSS parser. + +`stringifier` +: (`string`) A custom PostCSS stringifier. + +`syntax` +: (`string`) Custom PostCSS syntax. + +`use` +: (`string`) A space-delimited list of PostCSS plugins to use. + +For example, to pass your plugins and disable source maps directly through the options map instead of a configuration file: + +```go-html-template {file="layouts/_partials/css.html" copy=true} +{{ with resources.Get "css/main.css" }} + {{ $opts := dict + "noMap" true + "use" "autoprefixer postcss-color-alpha" + }} + {{ with . | css.PostCSS $opts }} + {{ if hugo.IsDevelopment }} + + {{ else }} + {{ with . | fingerprint }} + + {{ end }} + {{ end }} + {{ end }} +{{ end }} +``` + +[Node.js]: https://nodejs.org/en +[PostCSS]: https://postcss.org/ +[plugins]: https://postcss.org/docs/postcss-plugins diff --git a/docs/content/en/functions/css/Quoted.md b/docs/content/en/functions/css/Quoted.md new file mode 100644 index 0000000..bbc856f --- /dev/null +++ b/docs/content/en/functions/css/Quoted.md @@ -0,0 +1,61 @@ +--- +title: css.Quoted +description: Returns the given string, setting its data type to indicate that it must be quoted when used in CSS. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [] + returnType: css.QuotedString + signatures: [css.Quoted STRING] +--- + +> [!NOTE] +> This function is only applicable to the `vars` option passed to the [`css.Build`][] or [`css.Sass`][] functions. + +When passing a `vars` map to the `css.Sass` function, Hugo detects common typed CSS values such as `24px` or `#FF0000` using regular expression matching. If necessary, you can bypass automatic type inference by using the `css.Quoted` function to explicitly indicate that the value must be treated as a quoted string. + +For the `css.Build` function, use `css.Quoted` to explicitly indicate that a value must be treated as a quoted string, most commonly for `font-family` names or the `content` property. + +In the example below, we use `css.Quoted` to ensure the values for the `content` property are injected as strings. + +```go-html-template +{{ $vars := dict + "ol-li-after" ("6" | css.Quoted) + "ul-li-after" ("7" | css.Quoted) +}} + +{{ $opts := dict "vars" $vars "transpiler" "dartsass" }} +{{ with resources.Get "sass/main.scss" | css.Sass $opts }} + +{{ end }} +``` + +Using the `hugo:vars` identifier in your stylesheet: + +```scss +@use "hugo:vars" as h; + +ol li::after { + content: h.$ol-li-after; +} + +ul li::after { + content: h.$ul-li-after; +} +``` + +The resulting CSS contains quoted strings: + +```css +ol li::after { + content: "6"; +} + +ul li::after { + content: "7"; +} +``` + +[`css.Build`]: /functions/css/build/#vars +[`css.Sass`]: /functions/css/sass/#vars diff --git a/docs/content/en/functions/css/Sass.md b/docs/content/en/functions/css/Sass.md new file mode 100644 index 0000000..92b47f8 --- /dev/null +++ b/docs/content/en/functions/css/Sass.md @@ -0,0 +1,352 @@ +--- +title: css.Sass +description: Transpiles Sass to CSS. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [toCSS] + returnType: resource.Resource + signatures: ['css.Sass [OPTIONS] RESOURCE'] +aliases: [/functions/resources/tocss/] +--- + +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. + + + +> [!WARNING] +> The embedded LibSass transpiler was deprecated in [v0.153.0][] and will be removed in a future release. Use the Dart Sass transpiler instead by setting the `transpiler` option to `dartsass` as shown in the examples below. + +Sass has two forms of syntax: [SCSS][] and [indented][]. Hugo supports both. + +## Options + +The `css.Sass` function accepts an options map. + +`enableSourceMap` +: (`bool`) Whether to generate a source map. Default is `false`. + + ```go-html-template + {{ $opts := dict + "transpiler" "dartsass" + "enableSourceMap" true + }} + {{ $r := resources.Get "sass/main.scss" | css.Sass $opts }} + ``` + +`includePaths` +: (`slice`) A slice of paths, relative to the project root, that the transpiler will use when resolving `@use` and `@import` statements. + + ```go-html-template + {{ $opts := dict + "transpiler" "dartsass" + "includePaths" (slice "node_modules/bootstrap/scss") + }} + {{ $r := resources.Get "sass/main.scss" | css.Sass $opts }} + ``` + +`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`. + + ```go-html-template + {{ $opts := dict + "transpiler" "dartsass" + "outputStyle" "compressed" + }} + {{ $r := resources.Get "sass/main.scss" | css.Sass $opts }} + ``` + +`precision` +: (`int`) The precision of floating point math. Applicable to LibSass. Default is `8`. + + ```go-html-template + {{ $opts := dict + "transpiler" "dartsass" + "precision" 10 + }} + {{ $r := resources.Get "sass/main.scss" | css.Sass $opts }} + ``` + +`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. + + ```go-html-template + {{ $opts := dict + "transpiler" "dartsass" + "silenceDeprecations" (slice "import") + }} + {{ $r := resources.Get "sass/main.scss" | css.Sass $opts }} + ``` + +`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`. + + ```go-html-template + {{ $opts := dict + "transpiler" "dartsass" + "silenceDependencyDeprecations" true + }} + {{ $r := resources.Get "sass/main.scss" | css.Sass $opts }} + ``` + +`sourceMapIncludeSources` +: (`bool`) Whether to embed sources in the generated source map. Applicable to Dart Sass. Default is `false`. + + ```go-html-template + {{ $opts := dict + "transpiler" "dartsass" + "enableSourceMap" true "sourceMapIncludeSources" true + }} + {{ $r := resources.Get "sass/main.scss" | css.Sass $opts }} + ``` + +`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. + + ```go-html-template + {{ $opts := dict + "transpiler" "dartsass" + "targetPath" "css/bundle.css" + }} + {{ $r := resources.Get "sass/main.scss" | css.Sass $opts }} + ``` + +`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`. + + ```go-html-template + {{ $opts := dict "transpiler" "dartsass" }} + {{ $r := resources.Get "sass/main.scss" | css.Sass $opts }} + ``` + +`vars` +: (`map`) A map of key-value pairs used to generate Sass variables. The `css.Sass` function injects these variables into the stylesheet when it encounters the `hugo:vars` internal identifier within a `@use` or `@import` statement. + + ```go-html-template + {{ $vars := dict + "font-family" "\"Times New Roman\", Times, serif" + "font-size" "24px" + "primary-color" "blue" + }} + {{ $opts := dict + "transpiler" "dartsass" + "vars" $vars + }} + {{ $r := resources.Get "sass/main.scss" | css.Sass $opts }} + ``` + + In the example above, using the identifier in your stylesheet allows you to access the values as Sass variables in the `hugo:vars` namespace: + + ```scss + @use 'hugo:vars' as v; + + .element { + color: v.$primary-color; + font-family: v.$font-family; + font-size: v.$font-size; + } + ``` + + The above produces output equivalent to: + + ```css + .element { + color: blue; + font-family: "Times New Roman", Times, serif; + font-size: 24px; + } + ``` + + {{< new-in 0.161.0 />}} + + The map may optionally contain nested maps. Each nested map is exposed as a separate `hugo:vars/` namespace, where `` is the key of the nested map (lowercased). Top-level scalar values and nested maps are independent. A top-level `@use 'hugo:vars'` only includes scalar values, while `@use 'hugo:vars/'` only includes the scalars from the named nested map. + + ```go-html-template + {{ $vars := dict + "font-family" "\"Times New Roman\", Times, serif" + "font-size" "24px" + "primary-color" "blue" + "mobile" (dict + "font-size" "12px" + "primary-color" "red" + ) + }} + {{ $opts := dict + "transpiler" "dartsass" + "vars" $vars + }} + {{ $r := resources.Get "sass/main.scss" | css.Sass $opts }} + ``` + + In the stylesheet, reference each nested namespace with a separate `@use` statement. Assign an alias to access the variables from that namespace: + + ```scss + @use 'hugo:vars' as v; + @use 'hugo:vars/mobile' as mobile; + + body { + color: v.$primary-color; + font-family: v.$font-family; + font-size: v.$font-size; + } + + @media (max-width: 650px) { + body { + color: mobile.$primary-color; + font-size: mobile.$font-size; + } + } + ``` + + The above produces output equivalent to: + + ```css + body { + color: blue; + font-family: "Times New Roman", Times, serif; + font-size: 24px; + } + + @media (max-width: 650px) { + body { + color: red; + font-size: 12px; + } + } + ``` + + The `vars` option is useful for setting Sass variables within your project configuration. + + {{< code-toggle file=hugo >}} + [params.theme.style] + font-family = '"Times New Roman", Times, serif' + font-size = '24px' + primary-color = 'blue' + + [params.theme.style.mobile] + font-size = '12px' + primary-color = 'red' + {{< /code-toggle >}} + + ```go-html-template + {{ $opts := dict + "transpiler" "dartsass" + "vars" site.Params.theme.style }} + {{ $r := resources.Get "sass/main.scss" | css.Sass $opts }} + ``` + + When passing a `vars` map to the `css.Sass` function, Hugo detects common typed CSS values such as `24px` or `#FF0000` using regular expression matching. If necessary, you can bypass automatic type inference by using the [`css.Quoted`][] or [`css.Unquoted`][] function to explicitly indicate a value's type. + +## Example + +```go-html-template {copy=true} +{{ with resources.Get "sass/main.scss" }} + {{ $opts := dict + "enableSourceMap" hugo.IsDevelopment + "outputStyle" (cond hugo.IsDevelopment "expanded" "compressed") + "targetPath" "css/main.css" + "transpiler" "dartsass" + "vars" site.Params.styles + "includePaths" (slice "node_modules/bootstrap/scss") + }} + {{ with . | css.Sass $opts }} + {{ if hugo.IsDevelopment }} + + {{ else }} + {{ with . | fingerprint }} + + {{ end }} + {{ 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. + +### 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. You must install the prebuilt binary outside of your project directory and ensure its path is included in your system's PATH environment variable. + +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 + +To use Dart Sass with Hugo on a [CI/CD](g) platform, you typically must modify your build workflow to install Dart Sass before the Hugo site build begins. This is because these platforms don't have Dart Sass pre-installed, and Hugo needs it to process your Sass files. + +There's one key exception where you can skip this step: you have committed your `resources` directory to your repository. This is only possible if: + +- You have not changed Hugo's default asset cache location. +- You have not set [`useResourceCacheWhen`][] to never in your project configuration. + +By committing the `resources` directory, you're providing the pre-built CSS files directly to your CI/CD platform, so it doesn't need to run the Sass compilation itself. + +For examples of how to install Dart Sass in a production environment, see these hosting guides: + +- [Cloudflare][] +- [GitHub Pages][] +- [GitLab Pages][] +- [Netlify][] +- [Render][] +- [SourceHut][] +- [Vercel][] + +[^1]: In 2023, the Sass team deprecated Embedded Dart Sass in favor of Dart Sass. + +[Cloudflare]: /host-and-deploy/host-on-cloudflare/ +[Dart Sass]: https://sass-lang.com/dart-sass/ +[GitHub Pages]: /host-and-deploy/host-on-github-pages/ +[GitLab Pages]: /host-and-deploy/host-on-gitlab-pages/ +[LibSass]: https://sass-lang.com/libsass +[Netlify]: /host-and-deploy/host-on-netlify/ +[Render]: /host-and-deploy/host-on-render/ +[SCSS]: https://sass-lang.com/documentation/syntax#scss +[Snap package]: https://snapcraft.io/hugo +[SourceHut]: /host-and-deploy/host-on-sourcehut-pages/ +[Vercel]: /host-and-deploy/host-on-vercel/ +[`css.Quoted`]: /functions/css/quoted/ +[`css.Unquoted`]: /functions/css/unquoted/ +[`publishDir`]: /configuration/all/#publishdir +[`useResourceCacheWhen`]: /configuration/build/#useresourcecachewhen +[brew.sh]: https://brew.sh/ +[chocolatey.org]: https://community.chocolatey.org/packages/sass +[indented]: https://sass-lang.com/documentation/syntax#the-indented-syntax +[prebuilt binaries]: https://github.com/sass/dart-sass/releases/latest +[scoop.sh]: https://scoop.sh/#/apps?q=sass +[snapcraft.io]: https://snapcraft.io/dart-sass +[v0.153.0]: https://github.com/gohugoio/hugo/releases/tag/v0.153.0 diff --git a/docs/content/en/functions/css/TailwindCSS.md b/docs/content/en/functions/css/TailwindCSS.md new file mode 100644 index 0000000..c9bb308 --- /dev/null +++ b/docs/content/en/functions/css/TailwindCSS.md @@ -0,0 +1,144 @@ +--- +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'] +--- + +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. + +> [!NOTE] +> Use this function with Tailwind CSS v4.0 and later, which require a relatively [modern browser][] to render correctly. + +## Setup + +Step 1 +: Install Tailwind CSS v4.0 or later: + + ```sh {copy=true} + npm install --save-dev tailwindcss @tailwindcss/cli @tailwindcss/typography + ``` + + + + > [!NOTE] + > As of v0.161.0, Hugo no longer supports the Tailwind [standalone binary][]. You must now install the Tailwind CSS CLI via `npm` as shown above. + +Step 2 +: Add this to your project configuration: + + {{< code-toggle file=hugo copy=true >}} + [build] + [build.buildStats] + enable = true + [[build.cachebusters]] + source = 'assets/notwatching/hugo_stats\.json' + target = 'css' + [[build.cachebusters]] + source = '(postcss|tailwind)\.config\.js' + target = 'css' + [module] + [[module.mounts]] + source = 'assets' + target = 'assets' + [[module.mounts]] + disableWatch = true + source = 'hugo_stats.json' + target = 'assets/notwatching/hugo_stats.json' + {{< /code-toggle >}} + +Step 3 +: Create a CSS entry file: + + ```css {file="assets/css/main.css" copy=true} + @import "tailwindcss"; + @plugin "@tailwindcss/typography"; + @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 resources.Get "css/main.css" }} + {{ $opts := dict "minify" (not hugo.IsDevelopment) }} + {{ with . | css.TailwindCSS $opts }} + {{ if hugo.IsDevelopment }} + + {{ else }} + {{ with . | fingerprint }} + + {{ end }} + {{ end }} + {{ end }} + {{ end }} + ``` + +Step 5 +: Call the _partial_ template from your base template, deferring template execution until after all sites and output formats have been rendered: + + ```go-html-template {file="layouts/baseof.html" copy=true} + + {{ with (templates.Defer (dict "key" "global")) }} + {{ partial "css.html" . }} + {{ end }} + + ``` + +## Options + +The `css.TailwindCSS` function accepts an options map. + +`minify` +: (`bool`) Whether to optimize and minify the output. Default is `false`. + +`optimize` +: (`bool`) Whether to optimize the output without minifying. Default is `false`. + +`disableInlineImports` +: {{< new-in 0.147.4 />}} +: (`bool`) Whether to disable 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`. + +## Inject CSS variables + +The [`css.Build`][] function has a [`vars`][] option that can be used to inject CSS variables into your stylesheets. This is particularly useful for dynamically setting values based on your site's configuration or other data. To use this with Tailwind CSS, you can use `css.Build` as a preprocessor step before passing the result to `css.TailwindCSS`. Here's how you can do it: + +```go-html-template +{{ with resources.Get "css/styles.css" }} + {{ $cssOpts := dict + "vars" (dict "favourite-color" "#7f93c9") + "externals" (slice "tailwindcss") + }} + {{ $tailwindOpts := dict "disableInlineImports" true }} + {{ with . | css.Build $cssOpts | css.TailwindCSS $tailwindOpts }} + + {{ end }} +{{ end }} +``` + +Some notes to the above: + +- Marking `tailwindcss` as an external in the `css.Build` options prevents it from being processed by the build step, allowing it to be correctly handled by the Tailwind CSS CLI in the subsequent step. +- The `disableInlineImports` option is set to `true` for the Tailwind CSS step as imports are handled by the `css.Build`. + +[modern browser]: https://tailwindcss.com/docs/compatibility#browser-support +[standalone binary]: https://github.com/tailwindlabs/tailwindcss/releases/latest +[`css.Build`]: /functions/css/build/ +[`vars`]: /functions/css/build/#vars diff --git a/docs/content/en/functions/css/Unquoted.md b/docs/content/en/functions/css/Unquoted.md new file mode 100644 index 0000000..10687d5 --- /dev/null +++ b/docs/content/en/functions/css/Unquoted.md @@ -0,0 +1,49 @@ +--- +title: css.Unquoted +description: Returns the given string, setting its data type to indicate that it must not be quoted when used in CSS. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [] + returnType: css.UnquotedString + signatures: [css.Unquoted STRING] +--- + +> [!NOTE] +> This function is only applicable to the `vars` option passed to the [`css.Sass`][] function. + +When passing a `vars` map to the `css.Sass` function, Hugo detects common typed CSS values such as `24px` or `#FF0000` using regular expression matching. If necessary, you can bypass automatic type inference by using the `css.Unquoted` function to explicitly indicate that the value must be treated as an unquoted string. + +In the example below, we use `css.Unquoted` to ensure the value for the `font-family` property is injected without quotes. + +```go-html-template +{{ $vars := dict + "font-main" ("sans-serif" | css.Unquoted) +}} + +{{ $opts := dict "vars" $vars "transpiler" "dartsass" }} +{{ with resources.Get "sass/main.scss" | css.Sass $opts }} + +{{ end }} +``` + +Using the `hugo:vars` identifier in your stylesheet: + +```scss +@use "hugo:vars" as h; + +body { + font-family: h.$font-main; +} +``` + +The resulting CSS contains an unquoted string: + +```css +body { + font-family: sans-serif; +} +``` + +[`css.Sass`]: /functions/css/sass/#vars diff --git a/docs/content/en/functions/css/_index.md b/docs/content/en/functions/css/_index.md new file mode 100644 index 0000000..9faabbb --- /dev/null +++ b/docs/content/en/functions/css/_index.md @@ -0,0 +1,7 @@ +--- +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/debug/Dump.md b/docs/content/en/functions/debug/Dump.md new file mode 100644 index 0000000..62d4e09 --- /dev/null +++ b/docs/content/en/functions/debug/Dump.md @@ -0,0 +1,33 @@ +--- +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 hugo.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 new file mode 100644 index 0000000..4e15151 --- /dev/null +++ b/docs/content/en/functions/debug/Timer.md @@ -0,0 +1,35 @@ +--- +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] +--- + +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 2000 }} + {{ $f := math.Sqrt . }} +{{ end }} +{{ $t.Stop }} +``` + +Use the `--logLevel info` command line flag when you build the site. + +```sh +hugo build --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/VisualizeSpaces.md b/docs/content/en/functions/debug/VisualizeSpaces.md new file mode 100644 index 0000000..56202fc --- /dev/null +++ b/docs/content/en/functions/debug/VisualizeSpaces.md @@ -0,0 +1,15 @@ +--- +title: debug.VisualizeSpaces +description: Returns the given string with spaces replaced by a visible string. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [] + returnType: string + signatures: [debug.VisualizeSpaces STRING] +--- + +```go-html-template +{{ debug.VisualizeSpaces "foo bar" }} → foo[SPACE][SPACE]bar +``` diff --git a/docs/content/en/functions/debug/_index.md b/docs/content/en/functions/debug/_index.md new file mode 100644 index 0000000..49fe416 --- /dev/null +++ b/docs/content/en/functions/debug/_index.md @@ -0,0 +1,7 @@ +--- +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 new file mode 100644 index 0000000..68c168f --- /dev/null +++ b/docs/content/en/functions/diagrams/Goat.md @@ -0,0 +1,115 @@ +--- +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 + +Use these methods on the `SVGDiagram` object. + +`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: + +````md +```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/_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") }} + + {{ .Inner }} + + {{ end }} +
    {{ $caption }}
    +
    +``` + +This Markdown: + +````md {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 +} +``` + +[GoAT]: https://github.com/bep/goat +[code block render hook]: /render-hooks/code-blocks/ +[embedded code block render hook]: <{{% eturl render-codeblock-goat %}}> diff --git a/docs/content/en/functions/diagrams/_index.md b/docs/content/en/functions/diagrams/_index.md new file mode 100644 index 0000000..6aa4070 --- /dev/null +++ b/docs/content/en/functions/diagrams/_index.md @@ -0,0 +1,7 @@ +--- +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 new file mode 100644 index 0000000..5237e90 --- /dev/null +++ b/docs/content/en/functions/encoding/Base64Decode.md @@ -0,0 +1,39 @@ +--- +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 new file mode 100644 index 0000000..e19d677 --- /dev/null +++ b/docs/content/en/functions/encoding/Base64Encode.md @@ -0,0 +1,16 @@ +--- +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/HexDecode.md b/docs/content/en/functions/encoding/HexDecode.md new file mode 100644 index 0000000..f760951 --- /dev/null +++ b/docs/content/en/functions/encoding/HexDecode.md @@ -0,0 +1,15 @@ +--- +title: encoding.HexDecode +description: Returns the hexadecimal decoding of the given content. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [] + returnType: string + signatures: [encoding.HexDecode INPUT] +--- + +```go-html-template +{{ "48656c6c6f20776f726c64" | encoding.HexDecode }} → Hello world +``` diff --git a/docs/content/en/functions/encoding/HexEncode.md b/docs/content/en/functions/encoding/HexEncode.md new file mode 100644 index 0000000..6c2845a --- /dev/null +++ b/docs/content/en/functions/encoding/HexEncode.md @@ -0,0 +1,15 @@ +--- +title: encoding.HexEncode +description: Returns the hexadecimal encoding of the given content. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [] + returnType: string + signatures: [encoding.HexEncode INPUT] +--- + +```go-html-template +{{ "Hello world" | encoding.HexEncode }} → 48656c6c6f20776f726c64 +``` diff --git a/docs/content/en/functions/encoding/Jsonify.md b/docs/content/en/functions/encoding/Jsonify.md new file mode 100644 index 0000000..8dbd78a --- /dev/null +++ b/docs/content/en/functions/encoding/Jsonify.md @@ -0,0 +1,36 @@ +--- +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 + +The `encoding.Jsonify` function accepts an options map. + +`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 new file mode 100644 index 0000000..f2819f0 --- /dev/null +++ b/docs/content/en/functions/encoding/_index.md @@ -0,0 +1,7 @@ +--- +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 new file mode 100644 index 0000000..7917b9f --- /dev/null +++ b/docs/content/en/functions/fmt/Errorf.md @@ -0,0 +1,24 @@ +--- +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 new file mode 100644 index 0000000..31e18d0 --- /dev/null +++ b/docs/content/en/functions/fmt/Erroridf.md @@ -0,0 +1,38 @@ +--- +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 project 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 project 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 new file mode 100644 index 0000000..f1d169c --- /dev/null +++ b/docs/content/en/functions/fmt/Print.md @@ -0,0 +1,18 @@ +--- +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 new file mode 100644 index 0000000..c369806 --- /dev/null +++ b/docs/content/en/functions/fmt/Printf.md @@ -0,0 +1,39 @@ +--- +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 [`safe.HTMLAttr`] function: + +```go-html-template +{{ $desc := "Eat at Joe's" }} + +``` + +Hugo renders this to: + +```html + +``` + +[`safe.HTMLAttr`]: /functions/safe/htmlattr/ diff --git a/docs/content/en/functions/fmt/Println.md b/docs/content/en/functions/fmt/Println.md new file mode 100644 index 0000000..6bcda3b --- /dev/null +++ b/docs/content/en/functions/fmt/Println.md @@ -0,0 +1,17 @@ +--- +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 +{{ println "foo" "bar" }} → foo bar\n +``` diff --git a/docs/content/en/functions/fmt/Warnf.md b/docs/content/en/functions/fmt/Warnf.md new file mode 100644 index 0000000..f7bd7a2 --- /dev/null +++ b/docs/content/en/functions/fmt/Warnf.md @@ -0,0 +1,33 @@ +--- +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 new file mode 100644 index 0000000..fffb07d --- /dev/null +++ b/docs/content/en/functions/fmt/Warnidf.md @@ -0,0 +1,38 @@ +--- +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] +--- + +{{% 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 project 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 project 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 new file mode 100644 index 0000000..b2efd24 --- /dev/null +++ b/docs/content/en/functions/fmt/_index.md @@ -0,0 +1,7 @@ +--- +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 new file mode 100644 index 0000000..3d93517 --- /dev/null +++ b/docs/content/en/functions/global/_index.md @@ -0,0 +1,6 @@ +--- +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 new file mode 100644 index 0000000..6a63166 --- /dev/null +++ b/docs/content/en/functions/global/page.md @@ -0,0 +1,100 @@ +--- +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., `baseof.html`). The one exception is the multihost sitemap template. This means that you can access the current page with the `.` in the template. + +However, when deeply nested inside 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: + +```tree +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 {file="layouts/home.html"} +{{ 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 {file="layouts/section.html"} +{{ range .Pages }} +

    {{ .LinkTitle }}

    + {{ .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. + +[`Summary`]: /methods/page/summary/ +[`partialCached`]: /functions/partials/includecached/ diff --git a/docs/content/en/functions/global/site.md b/docs/content/en/functions/global/site.md new file mode 100644 index 0000000..7a02471 --- /dev/null +++ b/docs/content/en/functions/global/site.md @@ -0,0 +1,30 @@ +--- +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 new file mode 100644 index 0000000..627dc28 --- /dev/null +++ b/docs/content/en/functions/go-template/_index.md @@ -0,0 +1,7 @@ +--- +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 new file mode 100644 index 0000000..016a8cf --- /dev/null +++ b/docs/content/en/functions/go-template/and.md @@ -0,0 +1,28 @@ +--- +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" %}} + +The `and` function evaluates the arguments from left to right, and returns when the result is determined. + +```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) + +{{ and false (math.Div 1 0) }} → false (bool) +``` + +{{% include "/_common/functions/go-template/text-template.md" %}} diff --git a/docs/content/en/functions/go-template/block.md b/docs/content/en/functions/go-template/block.md new file mode 100644 index 0000000..d603a1f --- /dev/null +++ b/docs/content/en/functions/go-template/block.md @@ -0,0 +1,54 @@ +--- +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/baseof.html"} + +
    + {{ block "main" . }} + {{ print "default value if 'main' template is empty" }} + {{ end }} +
    + +``` + +```go-html-template {file="layouts/page.html"} +{{ define "main" }} +

    {{ .Title }}

    + {{ .Content }} +{{ end }} +``` + +```go-html-template {file="layouts/section.html"} +{{ define "main" }} +

    {{ .Title }}

    + {{ .Content }} + {{ range .Pages }} +

    {{ .LinkTitle }}

    + {{ 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 new file mode 100644 index 0000000..9236ec9 --- /dev/null +++ b/docs/content/en/functions/go-template/break.md @@ -0,0 +1,31 @@ +--- +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 new file mode 100644 index 0000000..0b9339b --- /dev/null +++ b/docs/content/en/functions/go-template/continue.md @@ -0,0 +1,32 @@ +--- +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 new file mode 100644 index 0000000..cecf036 --- /dev/null +++ b/docs/content/en/functions/go-template/define.md @@ -0,0 +1,50 @@ +--- +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 }} +``` + +{{% include "/_common/functions/go-template/text-template.md" %}} + +[`block`]: /functions/go-template/block/ +[`partial`]: /functions/partials/include/ +[`template`]: /functions/go-template/block/ diff --git a/docs/content/en/functions/go-template/else.md b/docs/content/en/functions/go-template/else.md new file mode 100644 index 0000000..f6ebae0 --- /dev/null +++ b/docs/content/en/functions/go-template/else.md @@ -0,0 +1,65 @@ +--- +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/ +[`range`]: /functions/go-template/range/ +[`with`]: /functions/go-template/with/ diff --git a/docs/content/en/functions/go-template/end.md b/docs/content/en/functions/go-template/end.md new file mode 100644 index 0000000..39d6e6a --- /dev/null +++ b/docs/content/en/functions/go-template/end.md @@ -0,0 +1,60 @@ +--- +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 new file mode 100644 index 0000000..4f615f9 --- /dev/null +++ b/docs/content/en/functions/go-template/if.md @@ -0,0 +1,50 @@ +--- +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 new file mode 100644 index 0000000..6a13784 --- /dev/null +++ b/docs/content/en/functions/go-template/len.md @@ -0,0 +1,47 @@ +--- +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 new file mode 100644 index 0000000..fd8b9af --- /dev/null +++ b/docs/content/en/functions/go-template/not.md @@ -0,0 +1,33 @@ +--- +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 new file mode 100644 index 0000000..1b85a5d --- /dev/null +++ b/docs/content/en/functions/go-template/or.md @@ -0,0 +1,28 @@ +--- +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" %}} + +The `or` function evaluates the arguments from left to right, and returns when the result is determined. + +```go-html-template +{{ or 0 1 2 }} → 1 (int) +{{ or false "a" 1 }} → a (string) +{{ or 0 true "a" }} → true (bool) + +{{ or false "" 0 }} → 0 (int) +{{ or 0 "" false }} → false (bool) + +{{ or true (math.Div 1 0) }} → true (bool) +``` + +{{% 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 new file mode 100644 index 0000000..3922939 --- /dev/null +++ b/docs/content/en/functions/go-template/range.md @@ -0,0 +1,220 @@ +--- +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] +--- + +The collection may be a slice, a map, or an integer. + +```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 + +See the [context][] section in the introduction to templating. + +For example, 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: + +```go-html-template +{{ $s := slice "foo" "bar" "baz" }} +{{ range $s }} + {{ .Title }} +{{ end }} +``` + +Hugo will throw an error: + +```text +can't evaluate field Title in type int +``` + +The error occurs because we are trying to use the `.Title` method on a string 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 +{{ $s := slice "foo" "bar" "baz" }} +{{ range $s }} + {{ $.Title }} +{{ end }} +``` + +> [!NOTE] +> Gaining a thorough understanding of context is critical for anyone writing template code. + +## Examples + +### Slice of scalars + +This template code: + +```go-html-template +{{ $s := slice "foo" "bar" "baz" }} +{{ range $s }} +

    {{ . }}

    +{{ end }} +``` + +Is rendered to: + +```html +

    foo

    +

    bar

    +

    baz

    +``` + +This template code: + +```go-html-template +{{ $s := slice "foo" "bar" "baz" }} +{{ range $v := $s }} +

    {{ $v }}

    +{{ end }} +``` + +Is rendered to: + +```html +

    foo

    +

    bar

    +

    baz

    +``` + +This template code: + +```go-html-template +{{ $s := slice "foo" "bar" "baz" }} +{{ range $k, $v := $s }} +

    {{ $k }}: {{ $v }}

    +{{ end }} +``` + +Is rendered to: + +```html +

    0: foo

    +

    1: bar

    +

    2: baz

    +``` + +### Slice of maps + +This template code: + +```go-html-template +{{ $m := slice + (dict "name" "John" "age" 30) + (dict "name" "Will" "age" 28) + (dict "name" "Joey" "age" 24) +}} +{{ range $m }} +

    {{ .name }} is {{ .age }}

    +{{ end }} +``` + +Is rendered to: + +```html +

    John is 30

    +

    Will is 28

    +

    Joey is 24

    +``` + +### Slice of pages + +This template code: + +```go-html-template +{{ range where site.RegularPages "Type" "articles" }} +

    {{ .LinkTitle }}

    +{{ end }} +``` + +Is rendered to: + +```html +

    Article 3

    +

    Article 2

    +

    Article 1

    +``` + +### Maps + +This template code: + +```go-html-template +{{ $m := dict "name" "John" "age" 30 }} +{{ range $k, $v := $m }} +

    key = {{ $k }} value = {{ $v }}

    +{{ 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. + +### Integers + +Ranging over a positive integer `n` executes the block `n` times, with the context starting at zero and incrementing by one in each iteration. + +```go-html-template +{{ $s := slice }} +{{ range 1 }} + {{ $s = $s | append . }} +{{ end }} +{{ $s }} → [0] +``` + +```go-html-template +{{ $s := slice }} +{{ range 3 }} + {{ $s = $s | append . }} +{{ end }} +{{ $s }} → [0 1 2] +``` + +Ranging over a non-positive integer executes the block zero times. + +{{% include "/_common/functions/go-template/text-template.md" %}} + +[`break`]: /functions/go-template/break/ +[`continue`]: /functions/go-template/continue/ +[`else`]: /functions/go-template/else/ +[context]: /templates/introduction/#context diff --git a/docs/content/en/functions/go-template/return.md b/docs/content/en/functions/go-template/return.md new file mode 100644 index 0000000..3825f44 --- /dev/null +++ b/docs/content/en/functions/go-template/return.md @@ -0,0 +1,93 @@ +--- +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

    +``` + +## 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 }} +``` + +[`text/template`]: 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 new file mode 100644 index 0000000..1e309a1 --- /dev/null +++ b/docs/content/en/functions/go-template/template.md @@ -0,0 +1,42 @@ +--- +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 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. +1. Leveraging the [`partialCached`][] function when calling an inline partial allows for performance optimization through result caching. +1. An inline partial can [`return`][] a value of any data type instead of rendering a string. + +{{% include "/_common/functions/go-template/text-template.md" %}} + +[`partialCached`]: /functions/partials/includecached/ +[`return`]: /functions/go-template/return/ diff --git a/docs/content/en/functions/go-template/try.md b/docs/content/en/functions/go-template/try.md new file mode 100644 index 0000000..fe33623 --- /dev/null +++ b/docs/content/en/functions/go-template/try.md @@ -0,0 +1,110 @@ +--- +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 + +Use these methods on the `TryValue` object. + +`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/ +[`text/template`]: https://pkg.go.dev/text/template +[template context]: /templates/introduction/#template-context diff --git a/docs/content/en/functions/go-template/urlquery.md b/docs/content/en/functions/go-template/urlquery.md new file mode 100644 index 0000000..dc97f86 --- /dev/null +++ b/docs/content/en/functions/go-template/urlquery.md @@ -0,0 +1,27 @@ +--- +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 new file mode 100644 index 0000000..579317a --- /dev/null +++ b/docs/content/en/functions/go-template/with.md @@ -0,0 +1,95 @@ +--- +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 + +See the [context][] section in the introduction to templating. + +For example, 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/ +[context]: /templates/introduction/#context diff --git a/docs/content/en/functions/hash/FNV32a.md b/docs/content/en/functions/hash/FNV32a.md new file mode 100644 index 0000000..fc2d673 --- /dev/null +++ b/docs/content/en/functions/hash/FNV32a.md @@ -0,0 +1,16 @@ +--- +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/,/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 new file mode 100644 index 0000000..24e1d06 --- /dev/null +++ b/docs/content/en/functions/hash/XxHash.md @@ -0,0 +1,20 @@ +--- +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][] is an exceptionally fast non-cryptographic hash algorithm. Hugo uses [this Go implementation][]. + +[this Go implementation]: https://github.com/cespare/xxhash +[xxHash]: https://xxhash.com/ diff --git a/docs/content/en/functions/hash/_index.md b/docs/content/en/functions/hash/_index.md new file mode 100644 index 0000000..956f7fb --- /dev/null +++ b/docs/content/en/functions/hash/_index.md @@ -0,0 +1,7 @@ +--- +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 new file mode 100644 index 0000000..371ae78 --- /dev/null +++ b/docs/content/en/functions/hugo/BuildDate.md @@ -0,0 +1,19 @@ +--- +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][]. + +```go-html-template +{{ hugo.BuildDate }} → 2023-11-01T17:57:00Z +``` + +[RFC 3339]: https://datatracker.ietf.org/doc/html/rfc3339 diff --git a/docs/content/en/functions/hugo/CommitHash.md b/docs/content/en/functions/hugo/CommitHash.md new file mode 100644 index 0000000..324e985 --- /dev/null +++ b/docs/content/en/functions/hugo/CommitHash.md @@ -0,0 +1,15 @@ +--- +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/Data.md b/docs/content/en/functions/hugo/Data.md new file mode 100644 index 0000000..b03d98f --- /dev/null +++ b/docs/content/en/functions/hugo/Data.md @@ -0,0 +1,105 @@ +--- +title: hugo.Data +description: Returns a data structure composed from the files in the data directory. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: map + signatures: [hugo.Data] +--- + +{{< new-in 0.156.0 />}} + +Use the `hugo.Data` function to access data within the `data` directory, or within any directory [mounted][] to the `data` directory. Supported data formats include JSON, TOML, YAML, and XML. + +> [!NOTE] +> Although Hugo can [unmarshal](g) CSV files with the [`transform.Unmarshal`][] function, do not place CSV files in the `data` directory. You cannot access data within CSV files using this method. + +Consider this `data` directory: + +```tree +data/ +├── books/ +│ ├── fiction.yaml +│ └── nonfiction.yaml +├── films.json +├── paintings.xml +└── sculptures.toml +``` + +And these data files: + +```yaml {file="data/books/fiction.yaml"} +- title: The Hunchback of Notre Dame + author: Victor Hugo + isbn: 978-0140443530 +- title: Les Misérables + author: Victor Hugo + isbn: 978-0451419439 +``` + +```yaml {file="data/books/nonfiction.yaml"} +- title: The Ancien Régime and the Revolution + author: Alexis de Tocqueville + isbn: 978-0141441641 +- title: Interpreting the French Revolution + author: François Furet + isbn: 978-0521280495 +``` + +Access the data by [chaining](g) the [identifiers](g): + +```go-html-template +{{ range $category, $books := hugo.Data.books }} +

    {{ $category | title }}

    +
      + {{ range $books }} +
    • {{ .title }} ({{ .isbn }})
    • + {{ end }} +
    +{{ 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 hugo.Data.books.fiction "title" }} +
    • {{ .title }} ({{ .author }})
    • + {{ end }} +
    +``` + +To find a fiction book by ISBN: + +```go-html-template +{{ range where hugo.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 hugo.Data.books "historical-fiction" }} +``` + +[`index`]: /functions/collections/indexfunction/ +[`transform.Unmarshal`]: /functions/transform/unmarshal/ +[mounted]: /configuration/module/#mounts diff --git a/docs/content/en/functions/hugo/Deps.md b/docs/content/en/functions/hugo/Deps.md new file mode 100644 index 0000000..85ae402 --- /dev/null +++ b/docs/content/en/functions/hugo/Deps.md @@ -0,0 +1,72 @@ +--- +title: hugo.Deps +description: Returns a slice of project dependencies, either 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 modules or local theme components. + +## Methods + +Use these methods on each `hugo.Dependency` object returned by `hugo.Deps`. + +`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`) Returns the module path or the path below your `themes` directory (e.g., `github.com/gohugoio/hugo-mod-jslibs-dist/popperjs/v2`). + +`Replace` +: (`hugo.Dependency`) Returns the dependency that replaces this dependency. + +`Time` +: (`time.Time`) Returns 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`) Returns the module version (e.g., `v2.21100.20000`). + +## Example + +An example table listing the dependencies: + +```go-html-template +

    Dependencies

    + + + + + + + + + + + + + {{ range $index, $element := hugo.Deps }} + + + + + + + + + {{ end }} + +
    #OwnerPathVersionTimeVendor
    {{ add $index 1 }}{{ with $element.Owner }}{{ .Path }}{{ end }} + {{ $element.Path }} + {{ with $element.Replace }} + => {{ .Path }} + {{ end }} + {{ $element.Version }}{{ with $element.Time }}{{ . }}{{ end }}{{ $element.Vendor }}
    +``` diff --git a/docs/content/en/functions/hugo/Environment.md b/docs/content/en/functions/hugo/Environment.md new file mode 100644 index 0000000..6f69dd3 --- /dev/null +++ b/docs/content/en/functions/hugo/Environment.md @@ -0,0 +1,26 @@ +--- +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 build`|`production` +`hugo build --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 new file mode 100644 index 0000000..026a4b7 --- /dev/null +++ b/docs/content/en/functions/hugo/Generator.md @@ -0,0 +1,15 @@ +--- +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 new file mode 100644 index 0000000..94e310d --- /dev/null +++ b/docs/content/en/functions/hugo/GoVersion.md @@ -0,0 +1,15 @@ +--- +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 new file mode 100644 index 0000000..2392c54 --- /dev/null +++ b/docs/content/en/functions/hugo/IsDevelopment.md @@ -0,0 +1,15 @@ +--- +title: hugo.IsDevelopment +description: Reports whether the current running environment is "development". +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [] + returnType: bool + signatures: [hugo.IsDevelopment] +--- + +```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 new file mode 100644 index 0000000..ab7e0f7 --- /dev/null +++ b/docs/content/en/functions/hugo/IsExtended.md @@ -0,0 +1,15 @@ +--- +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 new file mode 100644 index 0000000..d95b998 --- /dev/null +++ b/docs/content/en/functions/hugo/IsMultihost.md @@ -0,0 +1,37 @@ +--- +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] +--- + +Project configuration: + +{{< code-toggle file=hugo >}} +defaultContentLanguage = 'de' +defaultContentLanguageInSubdir = true +[languages] + [languages.de] + baseURL = 'https://de.example.org/' + label = 'Deutsch' + locale = 'de-DE' + title = 'Projekt Dokumentation' + weight = 1 + [languages.en] + baseURL = 'https://en.example.org/' + label = 'English' + locale = 'en-US' + 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 new file mode 100644 index 0000000..8c57a87 --- /dev/null +++ b/docs/content/en/functions/hugo/IsMultilingual.md @@ -0,0 +1,35 @@ +--- +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] +--- + +Project configuration: + +{{< code-toggle file=hugo >}} +defaultContentLanguage = 'de' +defaultContentLanguageInSubdir = true +[languages] + [languages.de] + label = 'Deutsch' + locale = 'de-DE' + title = 'Projekt Dokumentation' + weight = 1 + [languages.en] + label = 'English' + locale = 'en-US' + 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 new file mode 100644 index 0000000..e5433c2 --- /dev/null +++ b/docs/content/en/functions/hugo/IsProduction.md @@ -0,0 +1,15 @@ +--- +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 new file mode 100644 index 0000000..771d4eb --- /dev/null +++ b/docs/content/en/functions/hugo/IsServer.md @@ -0,0 +1,15 @@ +--- +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] +--- + +```go-html-template +{{ hugo.IsServer }} → true/false +``` diff --git a/docs/content/en/functions/hugo/Sites.md b/docs/content/en/functions/hugo/Sites.md new file mode 100644 index 0000000..a559826 --- /dev/null +++ b/docs/content/en/functions/hugo/Sites.md @@ -0,0 +1,82 @@ +--- +title: hugo.Sites +description: Returns a collection of all sites for all dimensions. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [] + returnType: page.Sites + signatures: [hugo.Sites] +--- + +{{< new-in 0.156.0 />}} + +{{% include "/_common/functions/hugo/sites-collection.md" %}} + +With this project configuration: + +{{< code-toggle file=hugo >}} +defaultContentLanguage = 'de' +defaultContentLanguageInSubdir = true +defaultContentVersionInSubdir = true + +[languages.de] +contentDir = 'content/de' +direction = 'ltr' +label = 'Deutsch' +locale = 'de-DE' +title = 'Projekt Dokumentation' +weight = 1 + +[languages.en] +contentDir = 'content/en' +direction = 'ltr' +label = 'English' +locale = 'en-US' +title = 'Project Documentation' +weight = 2 + +[versions.'v1.0.0'] +[versions.'v2.0.0'] +[versions.'v3.0.0'] +{{< /code-toggle >}} + +This template: + +```go-html-template + +``` + +Produces a list of links to each home page: + +```html + +``` + +To render a link to the home page of the [default site](g): + +```go-html-template +{{ with hugo.Sites.Default }} + {{ .Title }} +{{ end }} +``` + +This is equivalent to: + +```go-html-template +{{ with index hugo.Sites 0 }} + {{ .Title }} +{{ end }} +``` diff --git a/docs/content/en/functions/hugo/Store.md b/docs/content/en/functions/hugo/Store.md new file mode 100644 index 0000000..a36dfc8 --- /dev/null +++ b/docs/content/en/functions/hugo/Store.md @@ -0,0 +1,112 @@ +--- +title: hugo.Store +description: Returns a globally scoped persistent data structure for storing and manipulating keyed values. +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 persistent data structure for storing and manipulating keyed values. To create a data structure with a different [scope](g), refer to the [scope](#scope) section below. + +## Methods + +Use these methods on the data structure. + +`Set` +: Sets the value of the given key. + + ```go-html-template + {{ hugo.Store.Set "greeting" "Hello" }} + ``` + +`Get` +: (`any`) 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` +: (`[]any`) 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/store-scope.md" %}} + +## Determinate values + +The `Store` method is often used to set values within a _shortcode_ template, a _partial_ template called by a _shortcode_ template, or by a _render hook_ template. In all three cases, the stored values are indeterminate until Hugo renders the page content. + +If you need to access a stored 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 new file mode 100644 index 0000000..8925a03 --- /dev/null +++ b/docs/content/en/functions/hugo/Version.md @@ -0,0 +1,15 @@ +--- +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.163.3 +``` diff --git a/docs/content/en/functions/hugo/WorkingDir.md b/docs/content/en/functions/hugo/WorkingDir.md new file mode 100644 index 0000000..e501025 --- /dev/null +++ b/docs/content/en/functions/hugo/WorkingDir.md @@ -0,0 +1,15 @@ +--- +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 new file mode 100644 index 0000000..b1c9216 --- /dev/null +++ b/docs/content/en/functions/hugo/_index.md @@ -0,0 +1,7 @@ +--- +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 new file mode 100644 index 0000000..9db4d8f --- /dev/null +++ b/docs/content/en/functions/images/AutoOrient.md @@ -0,0 +1,46 @@ +--- +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] +--- + +## 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 new file mode 100644 index 0000000..0ddfcba --- /dev/null +++ b/docs/content/en/functions/images/Brightness.md @@ -0,0 +1,33 @@ +--- +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 new file mode 100644 index 0000000..be4a2bc --- /dev/null +++ b/docs/content/en/functions/images/ColorBalance.md @@ -0,0 +1,33 @@ +--- +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 new file mode 100644 index 0000000..6b8cd59 --- /dev/null +++ b/docs/content/en/functions/images/Colorize.md @@ -0,0 +1,37 @@ +--- +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 new file mode 100644 index 0000000..707a5d2 --- /dev/null +++ b/docs/content/en/functions/images/Config.md @@ -0,0 +1,28 @@ +--- +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] +--- + +> [!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. + +```go-html-template +{{ $ic := images.Config "/static/images/a.jpg" }} + +{{ $ic.Width }} → 600 (int) +{{ $ic.Height }} → 400 (int) +``` + +Supported image formats include AVIF, BMP, GIF, HEIC, HEIF, JPEG, PNG, TIFF, and WebP. + +[`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 new file mode 100644 index 0000000..f5d6074 --- /dev/null +++ b/docs/content/en/functions/images/Contrast.md @@ -0,0 +1,33 @@ +--- +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 new file mode 100644 index 0000000..a1c2648 --- /dev/null +++ b/docs/content/en/functions/images/Dither.md @@ -0,0 +1,155 @@ +--- +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]'] +--- + +## Options + +The `images.Dither` filter accepts an options map. + +`colors` +: (`[]string`) 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`). + +`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. + +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 + +[Go documentation]: https://pkg.go.dev/github.com/makeworld-the-better-one/dither/v2#pkg-variables +[hexadecimal]: https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color diff --git a/docs/content/en/functions/images/Filter.md b/docs/content/en/functions/images/Filter.md new file mode 100644 index 0000000..0a28fd1 --- /dev/null +++ b/docs/content/en/functions/images/Filter.md @@ -0,0 +1,75 @@ +--- +title: images.Filter +description: Applies one or more image filters to the given image resource. +categories: [] +keywords: [filter] +params: + functions_and_methods: + aliases: [] + returnType: images.ImageResource + signatures: [images.Filter FILTER... RESOURCE] +--- + +{{% include "/_common/methods/resource/global-page-remote-resources.md" %}} + +The `images.Filter` function returns a new resource from a [processable image](g) after applying one or more [image filters](#image-filters). + +> [!NOTE] +> Use the [`reflect.IsImageResourceProcessable`][] function to verify that an image can be processed. + +## Usage + +Use the `images.Filter` function to apply effects such as blurring, sharpening, or grayscale conversion. You can pass a single filter or a slice of filters. When providing a slice, Hugo applies the filters from left to right. + +To apply a single filter: + +```go-html-template +{{ with resources.Get "images/original.jpg" }} + {{ with 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. + +## 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. + +{{% render-list-of-pages-in-section path=/functions/images filter=functions_images_no_filters filterType=exclude %}} + +[`Filter`]: /methods/resource/filter/ +[`reflect.IsImageResourceProcessable`]: /functions/reflect/isimageresourceprocessable/ diff --git a/docs/content/en/functions/images/Gamma.md b/docs/content/en/functions/images/Gamma.md new file mode 100644 index 0000000..d8cb076 --- /dev/null +++ b/docs/content/en/functions/images/Gamma.md @@ -0,0 +1,33 @@ +--- +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 new file mode 100644 index 0000000..c5eb136 --- /dev/null +++ b/docs/content/en/functions/images/GaussianBlur.md @@ -0,0 +1,33 @@ +--- +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 new file mode 100644 index 0000000..d3651b8 --- /dev/null +++ b/docs/content/en/functions/images/Grayscale.md @@ -0,0 +1,31 @@ +--- +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 new file mode 100644 index 0000000..f334eeb --- /dev/null +++ b/docs/content/en/functions/images/Hue.md @@ -0,0 +1,33 @@ +--- +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 new file mode 100644 index 0000000..0f9f9a9 --- /dev/null +++ b/docs/content/en/functions/images/Invert.md @@ -0,0 +1,31 @@ +--- +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 new file mode 100644 index 0000000..32fce6c --- /dev/null +++ b/docs/content/en/functions/images/Mask.md @@ -0,0 +1,75 @@ +--- +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 AVIF, 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 AVIF, 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 [project 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 format 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/ +[project configuration]: /configuration/imaging/ diff --git a/docs/content/en/functions/images/Opacity.md b/docs/content/en/functions/images/Opacity.md new file mode 100644 index 0000000..aa59fb8 --- /dev/null +++ b/docs/content/en/functions/images/Opacity.md @@ -0,0 +1,47 @@ +--- +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] +--- + +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 AVIF, 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 new file mode 100644 index 0000000..8e5eec3 --- /dev/null +++ b/docs/content/en/functions/images/Overlay.md @@ -0,0 +1,45 @@ +--- +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 new file mode 100644 index 0000000..7aea53c --- /dev/null +++ b/docs/content/en/functions/images/Padding.md @@ -0,0 +1,69 @@ +--- +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]'] +--- + +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. + +## 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: + +```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. + +In the example below, conversion to WebP is required to support transparency. AVIF, 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") }} +``` + +[`Colors`]: /methods/resource/colors/ +[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 diff --git a/docs/content/en/functions/images/Pixelate.md b/docs/content/en/functions/images/Pixelate.md new file mode 100644 index 0000000..954950c --- /dev/null +++ b/docs/content/en/functions/images/Pixelate.md @@ -0,0 +1,31 @@ +--- +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 new file mode 100644 index 0000000..f03b799 --- /dev/null +++ b/docs/content/en/functions/images/Process.md @@ -0,0 +1,49 @@ +--- +title: images.Process +description: Returns an image filter that processes an image according to the given processing specification. +categories: [] +keywords: [process] +params: + functions_and_methods: + aliases: [] + returnType: images.filter + signatures: [images.Process SPECIFICATION] +--- + +Returns an image filter that processes an image according to the given [processing specification](#processing-specification). This versatile filter supports the full range of image transformations, including resizing, cropping, rotation, and format conversion, all within a single specification string. Use this as an argument to the [`Filter`][] method or the [`images.Filter`][] function. + +```go-html-template +{{ with resources.Get "images/original.jpg" }} + {{ $filter := images.Process "crop 200x200 TopRight webp q50" }} + {{ with .Filter $filter }} + + {{ end }} +{{ end }} +``` + +In the example above, `"crop 200x200 TopRight webp q50"` is the processing specification. + +{{% include "/_common/methods/resource/processing-spec.md" %}} + +## Usage + +Create a filter: + +```go-html-template +{{ $filter := images.Process "crop 200x200 TopRight webp q50" }} +``` + +{{% include "/_common/functions/images/apply-image-filter.md" %}} + +## Example + +{{< img + src="images/examples/zion-national-park.jpg" + alt="Zion National Park" + filter="Process" + filterArgs="crop 200x200 TopRight webp q50" + example=true +>}} + +[`Filter`]: /methods/resource/filter/ +[`images.Filter`]: /functions/images/filter/ diff --git a/docs/content/en/functions/images/QR.md b/docs/content/en/functions/images/QR.md new file mode 100644 index 0000000..5f7c02a --- /dev/null +++ b/docs/content/en/functions/images/QR.md @@ -0,0 +1,140 @@ +--- +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 + +The `images.QR` function accepts an options map. + +`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/page.html"} +{{ with images.QR .Permalink }} + QR code linking to {{ $.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: + +```md +{{}} +``` + +Or insert the text between the opening and closing tags: + +```md +{{}} +https://gohugo.io +{{}} +``` + +The `qr` shortcode accepts several arguments including `level` and `scale`. See the [related documentation][] for details. + +[QR code]: https://en.wikipedia.org/wiki/QR_code +[`publishDir`]: /configuration/all/#publishdir +[related documentation]: /shortcodes/qr/ diff --git a/docs/content/en/functions/images/Saturation.md b/docs/content/en/functions/images/Saturation.md new file mode 100644 index 0000000..d1dd48b --- /dev/null +++ b/docs/content/en/functions/images/Saturation.md @@ -0,0 +1,33 @@ +--- +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 new file mode 100644 index 0000000..ae43045 --- /dev/null +++ b/docs/content/en/functions/images/Sepia.md @@ -0,0 +1,33 @@ +--- +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 new file mode 100644 index 0000000..9bfcaf9 --- /dev/null +++ b/docs/content/en/functions/images/Sigmoid.md @@ -0,0 +1,37 @@ +--- +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 new file mode 100644 index 0000000..c5063a8 --- /dev/null +++ b/docs/content/en/functions/images/Text.md @@ -0,0 +1,124 @@ +--- +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 + +The `images.Text` filter accepts an options map. + +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` +: {{< new-in 0.147.0 />}} +: (`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`. + +## 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" + "aligny" "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 }} +``` + +## 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 +>}} + +[Go Regular]: https://go.dev/blog/go-fonts#sans-serif +[`Filter`]: /methods/resource/filter/ +[`images.Filter`]: /functions/images/filter/ diff --git a/docs/content/en/functions/images/UnsharpMask.md b/docs/content/en/functions/images/UnsharpMask.md new file mode 100644 index 0000000..9c06eb5 --- /dev/null +++ b/docs/content/en/functions/images/UnsharpMask.md @@ -0,0 +1,37 @@ +--- +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 new file mode 100644 index 0000000..f92e16e --- /dev/null +++ b/docs/content/en/functions/images/_index.md @@ -0,0 +1,7 @@ +--- +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 new file mode 100644 index 0000000..d3d7852 --- /dev/null +++ b/docs/content/en/functions/inflect/Humanize.md @@ -0,0 +1,24 @@ +--- +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 new file mode 100644 index 0000000..f168770 --- /dev/null +++ b/docs/content/en/functions/inflect/Pluralize.md @@ -0,0 +1,16 @@ +--- +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 new file mode 100644 index 0000000..41e05b7 --- /dev/null +++ b/docs/content/en/functions/inflect/Singularize.md @@ -0,0 +1,16 @@ +--- +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 new file mode 100644 index 0000000..2afe4fe --- /dev/null +++ b/docs/content/en/functions/inflect/_index.md @@ -0,0 +1,7 @@ +--- +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 new file mode 100644 index 0000000..26b5a16 --- /dev/null +++ b/docs/content/en/functions/js/Babel.md @@ -0,0 +1,119 @@ +--- +title: js.Babel +description: Transpile JavaScript resources using Babel. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [babel] + returnType: resource.Resource + signatures: ['js.Babel [OPTIONS] RESOURCE'] +aliases: [/functions/resources/babel/] +--- + +The `js.Babel` function transforms JavaScript using [Babel][]. + +## Setup + +Step 1 +: Install [Node.js][]. + +Step 2 +: Install the required Node packages in the root of your project. For example, to install Babel's core compiler, its command-line interface, and the preset for transpiling modern JavaScript based on your target environments: + + ```sh + npm install --save-dev @babel/core @babel/cli @babel/preset-env + ``` + +Step 3 +: Create a Babel configuration file in the root of your project. For example, to use the environment preset to target Google Chrome version 79 or later: + + ```js {file="babel.config.mjs" copy=true} + export default { + presets: [ + [ + '@babel/preset-env', + { + targets: { + chrome: "79" + } + } + ] + ] + }; + ``` + +Step 4 +: Place your JS file within the `assets/js` directory. + +Step 5 +: Add the Babel executable to Hugo's `security.exec.allow` list in your project configuration: + + {{< code-toggle file=hugo >}} + [security.exec] + allow = ['^(dart-)?sass(-embedded)?$', '^go$', '^git$', '^node$', '^postcss$', '^tailwindcss$', '^babel$'] + {{< /code-toggle >}} + +Step 6 +: Create a partial template to process the JavaScript: + + ```go-html-template {file="layouts/_partials/js.html" copy=true} + {{ with resources.Get "js/main.js" }} + {{ $opts := dict + "minified" (cond hugo.IsDevelopment false true) + "noComments" (cond hugo.IsDevelopment false true) + "sourceMap" (cond hugo.IsDevelopment "inline" "none") + }} + {{ with . | js.Babel $opts }} + {{ if hugo.IsDevelopment }} + + {{ else }} + {{ with . | fingerprint }} + + {{ end }} + {{ end }} + {{ end }} + {{ end }} + ``` + +Step 7 +: Call the partial template from your base template: + + ```go-html-template {file="layouts/baseof.html" copy=true} + + {{ partial "js.html" . }} + + ``` + +## Options + +The `js.Babel` function accepts an options map. + +`compact` +: (`bool`) Whether to remove optional newlines and whitespace. Enabled when `minified` is `true`. Default is `false`. + +`config` +: (`string`) The path to the Babel configuration file. By default, Hugo searches the root of the project directory and any modules for `babel.config.js`, `babel.config.mjs`, and `babel.config.cjs` in that order. Use this option only to point to a configuration file with a custom name or one located in a custom subdirectory. + +`minified` +: (`bool`) Whether to minify transpiled code. Enables the `compact` option. Default is `false`. + +`noBabelrc` +: (`bool`) 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`. + + + +[Babel]: https://babeljs.io/ +[Node.js]: https://nodejs.org/en/download diff --git a/docs/content/en/functions/js/Batch.md b/docs/content/en/functions/js/Batch.md new file mode 100644 index 0000000..703ad47 --- /dev/null +++ b/docs/content/en/functions/js/Batch.md @@ -0,0 +1,310 @@ +--- +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] +> The `js.Batch` function is backed by the [`evanw/esbuild`][] package, providing a mature, high-performance foundation for bundling, transformation, and minification. + +> [!NOTE] +> For a runnable example of this feature, see [this test and demo repo][js_batch_demo]. + +The Batch `ID` is used to create the base directory for this batch. Forward slashes are allowed. The `js.Batch` function returns an object with an API with this structure: + +- [Group](#group) + - [Script](#script) + - [SetOptions](#optionssetter) + - [Instance](#instance) + - [SetOptions](#optionssetter) + - [Runner](#runner) + - [SetOptions](#optionssetter) + - [Config](#config) + - [SetOptions](#optionssetter) + +## 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](#optionssetter) that can be used to set [script options](#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](#script-options) map. Note that if you want the script to be handled by a [Runner](#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](#optionssetter) that can be used to set [params options](#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](#params-options) map. The instance options will be passed to any [Runner](#runner) script in the same group, as JSON. + +### Runner + +The `Runner` method takes an `ID` (`string`) as argument. No slashes. It returns an [OptionsSetter](#optionssetter) that can be used to set [script options](#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](#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](#script-options) (`default` is the default value for runner scripts). Runnable versions of the examples on this page can be found in this `js.Batch` [demonstration repository][js_batch_demo]. + +```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](#optionssetter) that can be used to set [build options](#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 that supports [code splitting][]. +- `params` will be available in the `@params/config` namespace in the scripts. This way you can import both the [Script](#script) or [Runner](#runner) params and the [Config](#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, `esbuild` only supports `esm` output for [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][]. Default is `default` for [Runner](#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 the `assets` 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][]. + +```go-html-template +{{ $common := resources.Match "/js/headlessui/*.*" }} +{{ $importContext := (slice $.Page ($common.Mount "/js/headlessui" ".")) }} +``` + +You can pass any object that implements [`Resource.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][] for more information. + +```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 the `esbuild` [code splitting][] feature, 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 legacy browsers. See [caniuse][]. +- There's a known import ordering issue. + +We have not seen the ordering issue as a problem during our [extensive testing][] of this new feature with different libraries. There are two main cases: + +1. Undefined execution order of imports, see [this comment][comment-1] +1. Only one execution order of imports, see [this comment][comment-2] + +Many would say that both of the above are [code smells][]. 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'); +``` + +[JavaScript import]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import +[`Resource.Get`]: /methods/page/resources/#get +[`Resource`]: /methods/resource/ +[`Resources.Mount`]: /methods/page/resources/#mount +[`Resources`]: /methods/page/resources/ +[`evanw/esbuild`]: https://github.com/evanw/esbuild +[`templates.Defer`]: /functions/templates/defer/ +[`with`]: /functions/go-template/with/ +[caniuse]: https://caniuse.com/?search=ESM +[code smells]: https://en.wikipedia.org/wiki/Code_smell +[code splitting]: https://esbuild.github.io/api/#splitting +[comment-1]: https://github.com/evanw/esbuild/issues/399#issuecomment-1458680887 +[comment-2]: https://github.com/evanw/esbuild/issues/399#issuecomment-735355932 +[entire namespace]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#namespace_import +[extensive testing]: https://github.com/bep/hugojsbatchdemo +[js_batch_demo]: https://github.com/bep/hugojsbatchdemo/ +[page bundle]: /content-management/page-bundles/ +[this discussion]: https://discourse.gohugo.io/t/js-batch-with-simple-global-script/53002/5 diff --git a/docs/content/en/functions/js/Build.md b/docs/content/en/functions/js/Build.md new file mode 100644 index 0000000..56371dd --- /dev/null +++ b/docs/content/en/functions/js/Build.md @@ -0,0 +1,130 @@ +--- +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'] +--- + +> [!NOTE] +> The `js.Build` function is backed by the [`evanw/esbuild`][] package, providing a mature, high-performance foundation for bundling, transformation, and minification. + +Use the `js.Build` function to: + +- Bundle +- Transpile (TypeScript and JSX) +- Tree shake +- Minify +- Create source maps + +```go-html-template +{{ with resources.Get "js/main.js" }} + {{$opts := dict + "minify" (cond hugo.IsDevelopment false true) + "sourceMap" (cond hugo.IsDevelopment "linked" "none") + }} + {{ with . | js.Build $opts }} + {{ if hugo.IsDevelopment }} + + {{ else }} + {{ with . | fingerprint }} + + {{ end }} + {{ end }} + {{ end }} +{{ end }} +``` + +## Options + +The `js.Build` function accepts an options map. + +`format` +: (`string`) The output format. One of: `iife`, `cjs`, `esm`. Default is `iife`, a self-executing function, suitable for inclusion as a ` +``` + +[`esbuild`]: https://esbuild.github.io/ +[`evanw/esbuild`]: https://github.com/evanw/esbuild +[`hugo mod npm pack`]: /commands/hugo_mod_npm_pack/ +[test project]: https://github.com/gohugoio/hugoTestProjectJSModImports +[turn it off]: /configuration/build/#nojsconfiginassets diff --git a/docs/content/en/functions/js/_index.md b/docs/content/en/functions/js/_index.md new file mode 100644 index 0000000..d3557a2 --- /dev/null +++ b/docs/content/en/functions/js/_index.md @@ -0,0 +1,7 @@ +--- +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 new file mode 100644 index 0000000..d2a1d76 --- /dev/null +++ b/docs/content/en/functions/lang/FormatAccounting.md @@ -0,0 +1,17 @@ +--- +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 new file mode 100644 index 0000000..327413c --- /dev/null +++ b/docs/content/en/functions/lang/FormatCurrency.md @@ -0,0 +1,17 @@ +--- +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 new file mode 100644 index 0000000..5bf3799 --- /dev/null +++ b/docs/content/en/functions/lang/FormatNumber.md @@ -0,0 +1,17 @@ +--- +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 new file mode 100644 index 0000000..817dc1c --- /dev/null +++ b/docs/content/en/functions/lang/FormatNumberCustom.md @@ -0,0 +1,30 @@ +--- +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 new file mode 100644 index 0000000..aef1fb6 --- /dev/null +++ b/docs/content/en/functions/lang/FormatPercent.md @@ -0,0 +1,17 @@ +--- +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 new file mode 100644 index 0000000..f2ce160 --- /dev/null +++ b/docs/content/en/functions/lang/Merge.md @@ -0,0 +1,29 @@ +--- +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 new file mode 100644 index 0000000..3787d91 --- /dev/null +++ b/docs/content/en/functions/lang/Translate.md @@ -0,0 +1,251 @@ +--- +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 the given key by searching the current language's [translation tables](#translation-tables), then those for the [`defaultContentLanguage`][]. + +If not found, the function returns an empty string. + +> [!NOTE] +> To list missing and fallback translations, set [`printI18nWarnings`][] to `true` in your project configuration, or use the `--printI18nWarnings` flag when building your project. +> +> To render placeholders for missing and fallback translations, set [`enableMissingTranslationPlaceholders`][] to `true` in your project configuration. + +## Translation tables + +{{% glossary-term "translation table" %}} + +For example: + +```text +i18n/en.toml +i18n/pt-BR.toml +``` + +Hugo searches for a matching translation table using the following base names, in order: + +1. The [`locale`][] of the current language +1. The [key][] of the current language +1. The locale of the [`defaultContentLanguage`][] +1. The key of the [`defaultContentLanguage`][] + +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 project supports two languages, English and Polish. Create a translation table for each language in the `i18n` directory. + +```tree +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 project supports two languages, English and Polish. Create a translation table for each language in the `i18n` directory. + +```tree +i18n/ +├── en.toml +└── pl.toml +``` + +The Unicode [CLDR Plural Rules chart][CLDR] 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 [`nicksnyder/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 +``` + +[CLDR]: https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html +[RFC 5646 § 2.2.7]: https://datatracker.ietf.org/doc/html/rfc5646#section-2.2.7 +[`defaultContentLanguage`]: /configuration/all/#defaultcontentlanguage +[`enableMissingTranslationPlaceholders`]: /configuration/all/#enablemissingtranslationplaceholders +[`locale`]: /configuration/all/#locale +[`nicksnyder/go-i18n`]: https://github.com/nicksnyder/go-i18n +[`printI18nWarnings`]: /configuration/all/#printi18nwarnings +[key]: /configuration/languages/#language-keys diff --git a/docs/content/en/functions/lang/_index.md b/docs/content/en/functions/lang/_index.md new file mode 100644 index 0000000..de75cad --- /dev/null +++ b/docs/content/en/functions/lang/_index.md @@ -0,0 +1,7 @@ +--- +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 new file mode 100644 index 0000000..60d8d6d --- /dev/null +++ b/docs/content/en/functions/math/Abs.md @@ -0,0 +1,15 @@ +--- +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 new file mode 100644 index 0000000..cac9d82 --- /dev/null +++ b/docs/content/en/functions/math/Acos.md @@ -0,0 +1,15 @@ +--- +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] +--- + +```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 new file mode 100644 index 0000000..cd137c6 --- /dev/null +++ b/docs/content/en/functions/math/Add.md @@ -0,0 +1,23 @@ +--- +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 new file mode 100644 index 0000000..4b082fd --- /dev/null +++ b/docs/content/en/functions/math/Asin.md @@ -0,0 +1,15 @@ +--- +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] +--- + +```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 new file mode 100644 index 0000000..4bcde09 --- /dev/null +++ b/docs/content/en/functions/math/Atan.md @@ -0,0 +1,15 @@ +--- +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] +--- + +```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 new file mode 100644 index 0000000..01bb0ae --- /dev/null +++ b/docs/content/en/functions/math/Atan2.md @@ -0,0 +1,15 @@ +--- +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] +--- + +```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 new file mode 100644 index 0000000..22a9d02 --- /dev/null +++ b/docs/content/en/functions/math/Ceil.md @@ -0,0 +1,15 @@ +--- +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 new file mode 100644 index 0000000..08d8abe --- /dev/null +++ b/docs/content/en/functions/math/Cos.md @@ -0,0 +1,15 @@ +--- +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] +--- + +```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 new file mode 100644 index 0000000..f86dbe9 --- /dev/null +++ b/docs/content/en/functions/math/Counter.md @@ -0,0 +1,33 @@ +--- +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 projects, and its initial value for each build is 1. + +```go-html-template {file="layouts/page.html"} +{{ warnf "page.html called %d times" math.Counter }} +``` + +```text +WARN page.html called 1 times +WARN page.html called 2 times +WARN page.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 new file mode 100644 index 0000000..0e338a9 --- /dev/null +++ b/docs/content/en/functions/math/Div.md @@ -0,0 +1,17 @@ +--- +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 new file mode 100644 index 0000000..dbfd862 --- /dev/null +++ b/docs/content/en/functions/math/Floor.md @@ -0,0 +1,15 @@ +--- +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 new file mode 100644 index 0000000..123ffac --- /dev/null +++ b/docs/content/en/functions/math/Log.md @@ -0,0 +1,15 @@ +--- +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 new file mode 100644 index 0000000..d3a7676 --- /dev/null +++ b/docs/content/en/functions/math/Max.md @@ -0,0 +1,15 @@ +--- +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/MaxInt64.md b/docs/content/en/functions/math/MaxInt64.md new file mode 100644 index 0000000..ee4f68f --- /dev/null +++ b/docs/content/en/functions/math/MaxInt64.md @@ -0,0 +1,27 @@ +--- +title: math.MaxInt64 +description: Returns the maximum value for a signed 64-bit integer. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [] + returnType: int64 + signatures: [math.MaxInt64] +--- + +{{< new-in 0.147.3 />}} + +```go-html-template +{{ math.MaxInt64 }} → 9223372036854775807 +``` + +This function is helpful for simulating a loop that continues indefinitely until a break condition is met. For example: + +```go-html-template +{{ range math.MaxInt64 }} + {{ if eq . 42 }} + {{ break }} + {{ end }} +{{ end }} +``` diff --git a/docs/content/en/functions/math/Min.md b/docs/content/en/functions/math/Min.md new file mode 100644 index 0000000..4f7d7b85 --- /dev/null +++ b/docs/content/en/functions/math/Min.md @@ -0,0 +1,15 @@ +--- +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 new file mode 100644 index 0000000..6035a36 --- /dev/null +++ b/docs/content/en/functions/math/Mod.md @@ -0,0 +1,15 @@ +--- +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 new file mode 100644 index 0000000..fc7813d --- /dev/null +++ b/docs/content/en/functions/math/ModBool.md @@ -0,0 +1,15 @@ +--- +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 new file mode 100644 index 0000000..69f2dbe --- /dev/null +++ b/docs/content/en/functions/math/Mul.md @@ -0,0 +1,17 @@ +--- +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 new file mode 100644 index 0000000..45010d5 --- /dev/null +++ b/docs/content/en/functions/math/Pi.md @@ -0,0 +1,15 @@ +--- +title: math.Pi +description: Returns the mathematical constant pi. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [] + returnType: float64 + signatures: [math.Pi] +--- + +```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 new file mode 100644 index 0000000..a438430 --- /dev/null +++ b/docs/content/en/functions/math/Pow.md @@ -0,0 +1,15 @@ +--- +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 new file mode 100644 index 0000000..ffb1afe --- /dev/null +++ b/docs/content/en/functions/math/Product.md @@ -0,0 +1,15 @@ +--- +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 new file mode 100644 index 0000000..72338ac --- /dev/null +++ b/docs/content/en/functions/math/Rand.md @@ -0,0 +1,41 @@ +--- +title: math.Rand +description: Returns a pseudo-random number in the half-open interval [0.0, 1.0). +categories: [] +keywords: [random] +params: + functions_and_methods: + aliases: [] + returnType: float64 + signatures: [math.Rand] +--- + +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 new file mode 100644 index 0000000..6bc015c --- /dev/null +++ b/docs/content/en/functions/math/Round.md @@ -0,0 +1,15 @@ +--- +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 new file mode 100644 index 0000000..2a0ffcd --- /dev/null +++ b/docs/content/en/functions/math/Sin.md @@ -0,0 +1,15 @@ +--- +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] +--- + +```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 new file mode 100644 index 0000000..b2f49fd --- /dev/null +++ b/docs/content/en/functions/math/Sqrt.md @@ -0,0 +1,15 @@ +--- +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 new file mode 100644 index 0000000..49459cd --- /dev/null +++ b/docs/content/en/functions/math/Sub.md @@ -0,0 +1,17 @@ +--- +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 new file mode 100644 index 0000000..e14bc92 --- /dev/null +++ b/docs/content/en/functions/math/Sum.md @@ -0,0 +1,14 @@ +--- +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 new file mode 100644 index 0000000..b05b4b8 --- /dev/null +++ b/docs/content/en/functions/math/Tan.md @@ -0,0 +1,15 @@ +--- +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] +--- + +```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 new file mode 100644 index 0000000..6c3ed59 --- /dev/null +++ b/docs/content/en/functions/math/ToDegrees.md @@ -0,0 +1,15 @@ +--- +title: math.ToDegrees +description: ToDegrees converts radians into degrees. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [] + returnType: float64 + signatures: [math.ToDegrees VALUE] +--- + +```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 new file mode 100644 index 0000000..b445977 --- /dev/null +++ b/docs/content/en/functions/math/ToRadians.md @@ -0,0 +1,15 @@ +--- +title: math.ToRadians +description: ToRadians converts degrees into radians. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [] + returnType: float64 + signatures: [math.ToRadians VALUE] +--- + +```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 new file mode 100644 index 0000000..c58a5a7 --- /dev/null +++ b/docs/content/en/functions/math/_index.md @@ -0,0 +1,6 @@ +--- +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 new file mode 100644 index 0000000..139923d --- /dev/null +++ b/docs/content/en/functions/openapi3/Unmarshal.md @@ -0,0 +1,121 @@ +--- +title: openapi3.Unmarshal +description: Unmarshals the given resource into an OpenAPI 3 Description. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [] + returnType: openapi3.OpenAPIDocument + signatures: ['openapi3.Unmarshal RESOURCE [OPTIONS]'] +--- + +The resource passed to the `openapi3.Unmarshal` function must be an [OpenAPI Document][], typically in JSON or YAML format. This resource can be a [global resource](g) or a [remote resource](g). + +This function automatically resolves and includes all external references, both local and remote, and returns a complete [OpenAPI Description][] that fully describes the surface of an API and its semantics. + +## Options + +The `openapi3.Unmarshal` function accepts an options map. + +`getremote` +: {{< new-in 0.153.0 />}} +: (`map`) This is a map of the options for the [`resources.GetRemote`][] function, useful when an OpenAPI Document includes remote external references. + +## Examples + +### Remote resource + +To work with a remote resource: + +```go-html-template {copy=true} +{{ $api := "" }} +{{ $url := "https://petstore.swagger.io/v2/swagger.json" }} +{{ $opts := dict + "headers" (dict "Authorization" "Bearer abcd") +}} +{{ with try (resources.GetRemote $url $opts) }} + {{ with .Err }} + {{ errorf "%s" . }} + {{ else with .Value }} + {{ $api = openapi3.Unmarshal . (dict "getremote" $opts) }} + {{ else }} + {{ errorf "Unable to get remote resource %q" $url }} + {{ end }} +{{ end }} +``` + +In the example above, the same HTTP Authorization header is used for both the initial remote request made by the `resources.GetRemote` function and for subsequent requests by the `openapi.Unmarshal` function as it retrieve remote external references. + +### Global resource + +To work with a global resource: + +```go-html-template {copy=true} +{{ $api := "" }} +{{ $opts := dict + "method" "post" + "key" now.UnixNano +}} +{{ with resources.Get "api/petstore.json" }} + {{ $api = openapi3.Unmarshal . (dict "getremote" $opts) }} +{{ end }} +``` + +For global resources, local external reference paths starting with `/` are resolved relative to the `assets` directory. All other local paths are resolved relative to the entry point. In the example above, local paths are resolved relative to `assets/api/petstore.json`. + +## Inspection + +> [!NOTE] +> The unmarshaled data structure is created with [`kin-openapi`][]. Many fields are structs or pointers (not maps), and therefore require accessors or other methods for indexing and iteration. +> +> For example, `Paths` is a pointer rather than a map; to iterate over the API paths, you must use the `.Paths.Map` accessor as shown in the example below. +> +> See the [`kin-openapi` godoc for OpenAPI 3][] for full type definitions. + +To inspect the unmarshaled data structure: + +```go-html-template {copy=true} +
    {{ debug.Dump $api }}
    +``` + +To list the GET and POST operations for each of the API paths: + +```go-html-template {copy=true} +{{ range $path, $details := $api.Paths.Map }} +

    {{ $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
    +
    +``` + +[OpenAPI Description]: https://swagger.io/specification/#openapi-description +[OpenAPI Document]: https://swagger.io/specification/#openapi-document +[`kin-openapi` godoc for OpenAPI 3]: https://pkg.go.dev/github.com/getkin/kin-openapi/openapi3 +[`kin-openapi`]: https://github.com/getkin/kin-openapi +[`resources.GetRemote`]: /functions/resources/getremote/#options diff --git a/docs/content/en/functions/openapi3/_index.md b/docs/content/en/functions/openapi3/_index.md new file mode 100644 index 0000000..852daf7 --- /dev/null +++ b/docs/content/en/functions/openapi3/_index.md @@ -0,0 +1,7 @@ +--- +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 new file mode 100644 index 0000000..0d0a496 --- /dev/null +++ b/docs/content/en/functions/os/FileExists.md @@ -0,0 +1,39 @@ +--- +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`][]. A leading path separator (`/`) is optional. + +With this directory structure: + +```tree +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 +``` + +[`contentDir`]: /configuration/all/#contentdir diff --git a/docs/content/en/functions/os/Getenv.md b/docs/content/en/functions/os/Getenv.md new file mode 100644 index 0000000..4276ec5 --- /dev/null +++ b/docs/content/en/functions/os/Getenv.md @@ -0,0 +1,56 @@ +--- +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 project 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][]. + +## Examples + +```go-html-template +{{ getenv "HOME" }} → /home/victor +{{ getenv "USER" }} → victor +``` + +You can pass values when building your project: + +```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 +``` + +[configure security]: /configuration/security/ diff --git a/docs/content/en/functions/os/ReadDir.md b/docs/content/en/functions/os/ReadDir.md new file mode 100644 index 0000000..915cab9 --- /dev/null +++ b/docs/content/en/functions/os/ReadDir.md @@ -0,0 +1,47 @@ +--- +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: + +```tree +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][]. + +[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 new file mode 100644 index 0000000..f33169a --- /dev/null +++ b/docs/content/en/functions/os/ReadFile.md @@ -0,0 +1,36 @@ +--- +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`][]. A leading path separator (`/`) is optional. + +With a file named README.md in the root of your project directory: + +```md +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. + +[`contentDir`]: /configuration/all/#contentdir diff --git a/docs/content/en/functions/os/Stat.md b/docs/content/en/functions/os/Stat.md new file mode 100644 index 0000000..18f410d --- /dev/null +++ b/docs/content/en/functions/os/Stat.md @@ -0,0 +1,30 @@ +--- +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`][]. 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][]. + +[Go documentation]: https://pkg.go.dev/io/fs#FileInfo +[`contentDir`]: /configuration/all/#contentdir diff --git a/docs/content/en/functions/os/_index.md b/docs/content/en/functions/os/_index.md new file mode 100644 index 0000000..b125f70 --- /dev/null +++ b/docs/content/en/functions/os/_index.md @@ -0,0 +1,7 @@ +--- +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 new file mode 100644 index 0000000..b40b3a9 --- /dev/null +++ b/docs/content/en/functions/partials/Include.md @@ -0,0 +1,78 @@ +--- +title: partials.Include +description: Executes the given 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. + +In this example we have three _partial_ templates: + +```tree +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 +

    {{ .name }} is majoring in {{ .major }}.

    +

    Their grade point average is {{ .gpa }}.

    +

    See details.

    +``` + +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 }} +``` + +[`return`]: /functions/go-template/return/ +[breadcrumb navigation]: /content-management/sections/#ancestors-and-descendants diff --git a/docs/content/en/functions/partials/IncludeCached.md b/docs/content/en/functions/partials/IncludeCached.md new file mode 100644 index 0000000..1c19285 --- /dev/null +++ b/docs/content/en/functions/partials/IncludeCached.md @@ -0,0 +1,55 @@ +--- +title: partials.IncludeCached +description: Executes the given template and caches the result, optionally passing one or more variant keys. 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/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 }} +``` + +[`return`]: /functions/go-template/return/ diff --git a/docs/content/en/functions/partials/_index.md b/docs/content/en/functions/partials/_index.md new file mode 100644 index 0000000..09b4673 --- /dev/null +++ b/docs/content/en/functions/partials/_index.md @@ -0,0 +1,7 @@ +--- +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 new file mode 100644 index 0000000..39d52bf --- /dev/null +++ b/docs/content/en/functions/path/Base.md @@ -0,0 +1,20 @@ +--- +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 new file mode 100644 index 0000000..468898a --- /dev/null +++ b/docs/content/en/functions/path/BaseName.md @@ -0,0 +1,20 @@ +--- +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 new file mode 100644 index 0000000..240f400 --- /dev/null +++ b/docs/content/en/functions/path/Clean.md @@ -0,0 +1,27 @@ +--- +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. + +```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 "" }} → . +``` + +[`path.Clean`]: https://pkg.go.dev/path#Clean diff --git a/docs/content/en/functions/path/Dir.md b/docs/content/en/functions/path/Dir.md new file mode 100644 index 0000000..04d5500 --- /dev/null +++ b/docs/content/en/functions/path/Dir.md @@ -0,0 +1,21 @@ +--- +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 new file mode 100644 index 0000000..3646d02 --- /dev/null +++ b/docs/content/en/functions/path/Ext.md @@ -0,0 +1,18 @@ +--- +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 new file mode 100644 index 0000000..18bf5f7 --- /dev/null +++ b/docs/content/en/functions/path/Join.md @@ -0,0 +1,28 @@ +--- +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. + +```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 +``` + +[`path.Clean`]: https://pkg.go.dev/path#Clean +[`path.Join`]: https://pkg.go.dev/path#Join diff --git a/docs/content/en/functions/path/Split.md b/docs/content/en/functions/path/Split.md new file mode 100644 index 0000000..d4f8d08 --- /dev/null +++ b/docs/content/en/functions/path/Split.md @@ -0,0 +1,28 @@ +--- +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 new file mode 100644 index 0000000..49b2927 --- /dev/null +++ b/docs/content/en/functions/path/_index.md @@ -0,0 +1,7 @@ +--- +title: Path functions +linkTitle: path +description: Use these functions to work with file paths. +categories: [] +keywords: [] +--- diff --git a/docs/content/en/functions/reflect/IsImageResource.md b/docs/content/en/functions/reflect/IsImageResource.md new file mode 100644 index 0000000..f1a6dec --- /dev/null +++ b/docs/content/en/functions/reflect/IsImageResource.md @@ -0,0 +1,29 @@ +--- +title: reflect.IsImageResource +description: Reports whether the given value is a Resource object representing an image as defined by its media type. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [] + returnType: bool + signatures: [reflect.IsImageResource INPUT] +--- + +{{< new-in 0.154.0 />}} + +## Usage + +This example iterates through all project resources and uses `reflect.IsImageResource` to decide whether to render an image tag or provide a download link for non-image files. + +```go-html-template +{{ range resources.Match "**" }} + {{ if reflect.IsImageResource . }} + Image + {{ else }} + Download + {{ end }} +{{ end }} +``` + +{{% include "/_common/functions/reflect/image-reflection-functions.md" %}} diff --git a/docs/content/en/functions/reflect/IsImageResourceProcessable.md b/docs/content/en/functions/reflect/IsImageResourceProcessable.md new file mode 100644 index 0000000..d0d3d23 --- /dev/null +++ b/docs/content/en/functions/reflect/IsImageResourceProcessable.md @@ -0,0 +1,31 @@ +--- +title: reflect.IsImageResourceProcessable +description: Reports whether the given value is a Resource object representing an image from which Hugo can extract dimensions and perform processing such as converting, resizing, cropping, or filtering. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [] + returnType: bool + signatures: [reflect.IsImageResourceProcessable INPUT] +--- + +{{< new-in 0.157.0 />}} + +{{% glossary-term "processable image" %}} + +## Usage + +This example iterates through all project resources and uses `reflect.IsImageResourceProcessable` to ensure the image pipeline can perform transformations like resizing before processing begins. + +```go-html-template +{{ range resources.Match "**" }} + {{ if reflect.IsImageResourceProcessable . }} + {{ with .Process "resize 300x webp" }} + Processed Image + {{ end }} + {{ end }} +{{ end }} +``` + +{{% include "/_common/functions/reflect/image-reflection-functions.md" %}} diff --git a/docs/content/en/functions/reflect/IsImageResourceWithMeta.md b/docs/content/en/functions/reflect/IsImageResourceWithMeta.md new file mode 100644 index 0000000..642ce2d --- /dev/null +++ b/docs/content/en/functions/reflect/IsImageResourceWithMeta.md @@ -0,0 +1,30 @@ +--- +title: reflect.IsImageResourceWithMeta +description: Reports whether the given value is a Resource object representing an image from which Hugo can extract dimensions and, if present, Exif, IPTC, and XMP data. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [] + returnType: bool + signatures: [reflect.IsImageResourceWithMeta INPUT] +--- + +{{< new-in 0.157.0 />}} + +## Usage + +This example iterates through all project resources and uses `reflect.IsImageResourceWithMeta` to safely display image dimensions and metadata only for supported formats. + +```go-html-template +{{ range resources.Match "**" }} + {{ if reflect.IsImageResourceWithMeta . }} + Image with Meta + {{ with .Meta }} +

    Taken on: {{ .Date }}

    + {{ end }} + {{ end }} +{{ end }} +``` + +{{% include "/_common/functions/reflect/image-reflection-functions.md" %}} diff --git a/docs/content/en/functions/reflect/IsMap.md b/docs/content/en/functions/reflect/IsMap.md new file mode 100644 index 0000000..55b9811 --- /dev/null +++ b/docs/content/en/functions/reflect/IsMap.md @@ -0,0 +1,17 @@ +--- +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/IsPage.md b/docs/content/en/functions/reflect/IsPage.md new file mode 100644 index 0000000..eaf545c --- /dev/null +++ b/docs/content/en/functions/reflect/IsPage.md @@ -0,0 +1,23 @@ +--- +title: reflect.IsPage +description: Reports whether the given value is a Page object. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [] + returnType: bool + signatures: [reflect.IsPage INPUT] +--- + +{{< new-in 0.154.0 />}} + +```go-html-template {file="layouts/page.html"} +{{ with site.GetPage "/examples" }} + {{ reflect.IsPage . }} → true +{{ end }} + +{{ with .Site }} + {{ reflect.IsPage . }} → false +{{ end }} +``` diff --git a/docs/content/en/functions/reflect/IsResource.md b/docs/content/en/functions/reflect/IsResource.md new file mode 100644 index 0000000..52a2a1d --- /dev/null +++ b/docs/content/en/functions/reflect/IsResource.md @@ -0,0 +1,65 @@ +--- +title: reflect.IsResource +description: Reports whether the given value is a Resource object. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [] + returnType: bool + signatures: [reflect.IsResource INPUT] +--- + +{{< new-in 0.154.0 />}} + +With this project structure: + +```tree +project/ +├── assets/ +│ ├── a.json +│ ├── b.avif +│ └── c.jpg +└── content/ + └── example/ + ├── index.md + ├── d.json + ├── e.avif + └── f.jpg +``` + +These are the values returned by the `reflect.IsResource` function: + +```go-html-template {file="layouts/page.html"} +{{ with resources.Get "a.json" }} + {{ reflect.IsResource . }} → true +{{ end }} + +{{ with resources.Get "b.avif" }} + {{ reflect.IsResource . }} → true +{{ end }} + +{{ with resources.Get "c.jpg" }} + {{ reflect.IsResource . }} → true +{{ end }} +``` + +```go-html-template {file="layouts/page.html"} +{{ with .Resources.Get "d.json" }} + {{ reflect.IsResource . }} → true +{{ end }} + +{{ with .Resources.Get "e.avif" }} + {{ reflect.IsResource . }} → true +{{ end }} + +{{ with .Resources.Get "f.jpg" }} + {{ reflect.IsResource . }} → true +{{ end }} +``` + +```go-html-template {file="layouts/page.html"} +{{ with site.GetPage "/example" }} + {{ reflect.IsResource . }} → true +{{ end }} +``` diff --git a/docs/content/en/functions/reflect/IsSite.md b/docs/content/en/functions/reflect/IsSite.md new file mode 100644 index 0000000..9170dd4 --- /dev/null +++ b/docs/content/en/functions/reflect/IsSite.md @@ -0,0 +1,23 @@ +--- +title: reflect.IsSite +description: Reports whether the given value is a Site object. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [] + returnType: bool + signatures: [reflect.IsSite INPUT] +--- + +{{< new-in 0.154.0 />}} + +```go-html-template {file="layouts/page.html"} +{{ with .Site }} + {{ reflect.IsSite . }} → true +{{ end }} + +{{ with site.GetPage "/examples" }} + {{ reflect.IsSite . }} → false +{{ end }} +``` diff --git a/docs/content/en/functions/reflect/IsSlice.md b/docs/content/en/functions/reflect/IsSlice.md new file mode 100644 index 0000000..3a5c122 --- /dev/null +++ b/docs/content/en/functions/reflect/IsSlice.md @@ -0,0 +1,17 @@ +--- +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 new file mode 100644 index 0000000..58f00c3 --- /dev/null +++ b/docs/content/en/functions/reflect/_index.md @@ -0,0 +1,7 @@ +--- +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/ByType.md b/docs/content/en/functions/resources/ByType.md new file mode 100644 index 0000000..ca8a8b3 --- /dev/null +++ b/docs/content/en/functions/resources/ByType.md @@ -0,0 +1,27 @@ +--- +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 new file mode 100644 index 0000000..03673bf --- /dev/null +++ b/docs/content/en/functions/resources/Concat.md @@ -0,0 +1,25 @@ +--- +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](g). + +Hugo publishes the resource to the target path when you call its [`Publish`][], [`Permalink`][], or [`RelPermalink`][] method. + +```go-html-template +{{ $plugins := resources.Get "js/plugins.js" }} +{{ $global := resources.Get "js/global.js" }} +{{ $js := slice $plugins $global | resources.Concat "js/bundle.js" }} +``` + +[`Permalink`]: /methods/resource/permalink/ +[`Publish`]: /methods/resource/publish/ +[`RelPermalink`]: /methods/resource/relpermalink/ diff --git a/docs/content/en/functions/resources/Copy.md b/docs/content/en/functions/resources/Copy.md new file mode 100644 index 0000000..208f6d6 --- /dev/null +++ b/docs/content/en/functions/resources/Copy.md @@ -0,0 +1,23 @@ +--- +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 `TARGETPATH` is relative to the server root. A leading slash is optional and has no effect. + +> [!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 new file mode 100644 index 0000000..c449ec7 --- /dev/null +++ b/docs/content/en/functions/resources/ExecuteAsTemplate.md @@ -0,0 +1,61 @@ +--- +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 project 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 project 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; +} +``` + +[`Permalink`]: /methods/resource/permalink/ +[`Publish`]: /methods/resource/publish/ +[`RelPermalink`]: /methods/resource/relpermalink/ diff --git a/docs/content/en/functions/resources/Fingerprint.md b/docs/content/en/functions/resources/Fingerprint.md new file mode 100644 index 0000000..c42b025 --- /dev/null +++ b/docs/content/en/functions/resources/Fingerprint.md @@ -0,0 +1,36 @@ +--- +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 new file mode 100644 index 0000000..1722f50 --- /dev/null +++ b/docs/content/en/functions/resources/FromString.md @@ -0,0 +1,75 @@ +--- +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. + +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": "2026-04-04T10:46:21-07:00", + "hugo_version": "0.163.3", + "last_modified": "2026-04-04T10:46:26-07: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 }} +``` + +[`Permalink`]: /methods/resource/permalink/ +[`Publish`]: /methods/resource/publish/ +[`RelPermalink`]: /methods/resource/relpermalink/ +[`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 new file mode 100644 index 0000000..ea0819d --- /dev/null +++ b/docs/content/en/functions/resources/Get.md @@ -0,0 +1,24 @@ +--- +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/#get diff --git a/docs/content/en/functions/resources/GetMatch.md b/docs/content/en/functions/resources/GetMatch.md new file mode 100644 index 0000000..6d0cd3f --- /dev/null +++ b/docs/content/en/functions/resources/GetMatch.md @@ -0,0 +1,28 @@ +--- +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 pattern](g). + +{{% include "/_common/glob-patterns.md" %}} + +[`Resources.GetMatch`]: /methods/page/resources/#getmatch diff --git a/docs/content/en/functions/resources/GetRemote.md b/docs/content/en/functions/resources/GetRemote.md new file mode 100644 index 0000000..d8c3138 --- /dev/null +++ b/docs/content/en/functions/resources/GetRemote.md @@ -0,0 +1,239 @@ +--- +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](#error-handling) example below. +{{< /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 accepts an options map. + +`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. + +`timeout` +: {{< new-in 0.157.0 />}} +: (`string`) The duration after which the request is cancelled if it does not complete, expressed as a [duration](g). If not specified, the request will timeout after 2 minutes. + +## Options examples + +> [!NOTE] +> For brevity, the examples below do not include [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 }} +``` + +Use the `timeout` option to prevent slow external requests from stalling the build when fetching multiple remote feeds: + +```go-html-template +{{ $url := "https://example.org/feed.rss" }} +{{ $opts := dict "timeout" "10s" }} +{{ with try (resources.GetRemote $url $opts) }} + {{ with .Err }} + {{ warnf "Failed to fetch feed: %s" . }} + {{ else with .Value }} + {{ $data = . | transform.Unmarshal }} + {{ end }} +{{ end }} +``` + +## Remote data + +When retrieving remote data, use the [`transform.Unmarshal`][] function to [unmarshal](g) the response. + +```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. + +> [!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. + +## 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 }} +``` + +## 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 project configuration to add the media type to the allowlist. For example: + +{{< code-toggle file=hugo >}} +[security.http] +mediaTypes = ['^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) + +[Content-Type]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type +[`Data.Headers`]: /methods/resource/data/#headers +[`Data`]: /methods/resource/data/ +[`transform.Unmarshal`]: /functions/transform/unmarshal/ +[`try`]: /functions/go-template/try/ +[allowlist]: https://en.wikipedia.org/wiki/Whitelist +[configure file caches]: /configuration/caches/ diff --git a/docs/content/en/functions/resources/Match.md b/docs/content/en/functions/resources/Match.md new file mode 100644 index 0000000..ebc86f8 --- /dev/null +++ b/docs/content/en/functions/resources/Match.md @@ -0,0 +1,29 @@ +--- +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/#match +[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 new file mode 100644 index 0000000..183e667 --- /dev/null +++ b/docs/content/en/functions/resources/Minify.md @@ -0,0 +1,18 @@ +--- +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/PostProcess.md b/docs/content/en/functions/resources/PostProcess.md new file mode 100644 index 0000000..6e20bd8 --- /dev/null +++ b/docs/content/en/functions/resources/PostProcess.md @@ -0,0 +1,143 @@ +--- +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][] +1. Add vendor prefixes to CSS rules using the [Autoprefixer][] plugin for PostCSS +1. [Minify][] the CSS +1. [Fingerprint][] the CSS + +Step 1 +: Install [Node.js][]. + +Step 2 +: Install the required Node packages in the root of your project: + + ```sh {copy=true} + 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 copy=true >}} + [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.mjs" copy=true} + import autoprefixer from 'autoprefixer'; + import purgeCSSPlugin from '@fullhuman/postcss-purgecss'; + + 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: [] + }); + + export default { + plugins: [ + process.env.HUGO_ENVIRONMENT !== 'development' ? purgecss : null, + autoprefixer, + ] + }; + ``` + +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 {copy=true} + {{ 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 build` 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`, `babel.config.mjs`, `babel.config.cjs` +- `postcss.config.js`, `postcss.config.mjs`, `postcss.config.cjs` +- `tailwind.config.js`, `tailwind.config.mjs`, `tailwind.config.cjs` + +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 }} +``` + +[Autoprefixer]: https://github.com/postcss/autoprefixer +[Fingerprint]: /functions/resources/fingerprint/ +[Minify]: /functions/resources/minify/ +[Node.js]: https://nodejs.org/en +[PostCSS]: https://postcss.org/ +[PurgeCSS]: https://github.com/FullHuman/purgecss +[`config/production`]: /configuration/introduction/#configuration-directory +[configure build]: /configuration/build/ diff --git a/docs/content/en/functions/resources/_index.md b/docs/content/en/functions/resources/_index.md new file mode 100644 index 0000000..030cafd --- /dev/null +++ b/docs/content/en/functions/resources/_index.md @@ -0,0 +1,7 @@ +--- +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 new file mode 100644 index 0000000..3ee7e3a --- /dev/null +++ b/docs/content/en/functions/safe/CSS.md @@ -0,0 +1,62 @@ +--- +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 new file mode 100644 index 0000000..5f1e3d9 --- /dev/null +++ b/docs/content/en/functions/safe/HTML.md @@ -0,0 +1,54 @@ +--- +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. + +## 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 +``` + +[Go documentation]: https://pkg.go.dev/html/template#HTML diff --git a/docs/content/en/functions/safe/HTMLAttr.md b/docs/content/en/functions/safe/HTMLAttr.md new file mode 100644 index 0000000..35ead55 --- /dev/null +++ b/docs/content/en/functions/safe/HTMLAttr.md @@ -0,0 +1,60 @@ +--- +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. + +## 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 + +``` + +[Go documentation]: https://pkg.go.dev/html/template#HTMLAttr diff --git a/docs/content/en/functions/safe/JS.md b/docs/content/en/functions/safe/JS.md new file mode 100644 index 0000000..65fbe07 --- /dev/null +++ b/docs/content/en/functions/safe/JS.md @@ -0,0 +1,59 @@ +--- +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 an entirely 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. + +See the [Go documentation][] for details. + +## 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 + +``` + +[Go documentation]: https://pkg.go.dev/html/template#JS +[`transform.Unmarshal`]: /functions/transform/unmarshal/ diff --git a/docs/content/en/functions/safe/JSStr.md b/docs/content/en/functions/safe/JSStr.md new file mode 100644 index 0000000..444776a --- /dev/null +++ b/docs/content/en/functions/safe/JSStr.md @@ -0,0 +1,62 @@ +--- +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. + +## 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 + +``` + +[Go documentation]: https://pkg.go.dev/html/template#JSStr diff --git a/docs/content/en/functions/safe/URL.md b/docs/content/en/functions/safe/URL.md new file mode 100644 index 0000000..d7d3011 --- /dev/null +++ b/docs/content/en/functions/safe/URL.md @@ -0,0 +1,61 @@ +--- +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 new file mode 100644 index 0000000..8d5697b --- /dev/null +++ b/docs/content/en/functions/safe/_index.md @@ -0,0 +1,7 @@ +--- +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 new file mode 100644 index 0000000..1ff2b7f --- /dev/null +++ b/docs/content/en/functions/strings/Chomp.md @@ -0,0 +1,22 @@ +--- +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 new file mode 100644 index 0000000..e0e3b08 --- /dev/null +++ b/docs/content/en/functions/strings/Contains.md @@ -0,0 +1,22 @@ +--- +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 new file mode 100644 index 0000000..521ff34 --- /dev/null +++ b/docs/content/en/functions/strings/ContainsAny.md @@ -0,0 +1,22 @@ +--- +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 new file mode 100644 index 0000000..2d21a9d --- /dev/null +++ b/docs/content/en/functions/strings/ContainsNonSpace.md @@ -0,0 +1,22 @@ +--- +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. + +```go-html-template +{{ strings.ContainsNonSpace "\n" }} → false +{{ strings.ContainsNonSpace " " }} → false +{{ strings.ContainsNonSpace "\n abc" }} → true +``` + +[Unicode Space Separator]: https://www.compart.com/en/unicode/category/Zs diff --git a/docs/content/en/functions/strings/Count.md b/docs/content/en/functions/strings/Count.md new file mode 100644 index 0000000..76378b2 --- /dev/null +++ b/docs/content/en/functions/strings/Count.md @@ -0,0 +1,21 @@ +--- +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 new file mode 100644 index 0000000..1b06ec5 --- /dev/null +++ b/docs/content/en/functions/strings/CountRunes.md @@ -0,0 +1,20 @@ +--- +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 STRING] +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 new file mode 100644 index 0000000..14939a7 --- /dev/null +++ b/docs/content/en/functions/strings/CountWords.md @@ -0,0 +1,16 @@ +--- +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 STRING] +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 new file mode 100644 index 0000000..62baa45 Binary files /dev/null and b/docs/content/en/functions/strings/Diff/diff-screen-capture.png differ diff --git a/docs/content/en/functions/strings/Diff/index.md b/docs/content/en/functions/strings/Diff/index.md new file mode 100644 index 0000000..31d348e --- /dev/null +++ b/docs/content/en/functions/strings/Diff/index.md @@ -0,0 +1,31 @@ +--- +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] +--- + +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: + +![screen capture](diff-screen-capture.png) diff --git a/docs/content/en/functions/strings/FindRESubmatch.md b/docs/content/en/functions/strings/FindRESubmatch.md new file mode 100644 index 0000000..e9cdd5c --- /dev/null +++ b/docs/content/en/functions/strings/FindRESubmatch.md @@ -0,0 +1,88 @@ +--- +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: + +```md +- [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][]. Be sure to select the Go flavor before you begin. + +[regex101.com]: https://regex101.com/ diff --git a/docs/content/en/functions/strings/FindRe.md b/docs/content/en/functions/strings/FindRe.md new file mode 100644 index 0000000..0d6ba24 --- /dev/null +++ b/docs/content/en/functions/strings/FindRe.md @@ -0,0 +1,34 @@ +--- +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][]. Be sure to select the Go flavor before you begin. + +[regex101.com]: https://regex101.com/ diff --git a/docs/content/en/functions/strings/FirstUpper.md b/docs/content/en/functions/strings/FirstUpper.md new file mode 100644 index 0000000..41bf1f7 --- /dev/null +++ b/docs/content/en/functions/strings/FirstUpper.md @@ -0,0 +1,16 @@ +--- +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 new file mode 100644 index 0000000..2babe85 --- /dev/null +++ b/docs/content/en/functions/strings/HasPrefix.md @@ -0,0 +1,16 @@ +--- +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 new file mode 100644 index 0000000..c6b5f4d --- /dev/null +++ b/docs/content/en/functions/strings/HasSuffix.md @@ -0,0 +1,16 @@ +--- +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 new file mode 100644 index 0000000..d45216a --- /dev/null +++ b/docs/content/en/functions/strings/Repeat.md @@ -0,0 +1,16 @@ +--- +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 STRING] +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 new file mode 100644 index 0000000..b449ea8 --- /dev/null +++ b/docs/content/en/functions/strings/Replace.md @@ -0,0 +1,23 @@ +--- +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/ReplacePairs.md b/docs/content/en/functions/strings/ReplacePairs.md new file mode 100644 index 0000000..5e2face --- /dev/null +++ b/docs/content/en/functions/strings/ReplacePairs.md @@ -0,0 +1,111 @@ +--- +title: strings.ReplacePairs +description: Returns a copy of a string with multiple replacements performed in a single pass, using a slice of old and new string pairs. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [] + returnType: string + signatures: ['strings.ReplacePairs OLD NEW [OLD NEW ...] INPUT'] +--- + +{{< new-in 0.158.0 />}} + +Use the `strings.ReplacePairs` function to perform multiple replacements on a string in a single operation. This approach is faster than sequentially calling the [`strings.Replace`][] function. + +Replacing strings sequentially requires multiple function calls and variable re-assignments. + +```go-html-template +{{ $s := "aabbcc" }} +{{ $s = strings.Replace $s "a" "x" }} +{{ $s = strings.Replace $s "b" "y" }} +{{ $s = strings.Replace $s "c" "z" }} +{{ $s }} → xxyyzz +``` + +Using `strings.ReplacePairs` produces the same result with fewer function calls in less time. + +```go-html-template +{{ "aabbcc" | strings.ReplacePairs "a" "x" "b" "y" "c" "z" }} → xxyyzz +``` + +Pairs may also be passed as a single slice: + +```go-html-template +{{ $pairs := slice + "a" "x" + "b" "y" + "c" "z" +}} +{{ "aabbcc" | strings.ReplacePairs $pairs }} → xxyyzz +``` + +## Examples + +Observe that replacements are not applied recursively because the function scans the string only once. + +```go-html-template +{{ $pairs := slice + "a" "b" + "b" "c" +}} +{{ "a" | strings.ReplacePairs $pairs }} → b +``` + +Apply the first match when multiple old strings could match at the same position. + +```go-html-template +{{ $pairs := slice + "app" "pear" + "apple" "orange" +}} +{{ "apple" | strings.ReplacePairs $pairs }} → pearle +``` + +Delete specific strings by providing an empty string as the second value in a pair. + +```go-html-template +{{ $pairs := slice "b" "" }} +{{ "abc" | strings.ReplacePairs $pairs }} → ac +``` + +## Edge cases + +The table below outlines how the function handles various input scenarios. + +Scenario|Result +:--|:-- +Fewer than two arguments|Error +Odd number of slice elements|Error +Empty slice|Returns the input string +Empty input string|Returns an empty string +Empty old string|Returns the input string [interleaved](g) with the new string + +## Performance + +While `strings.Replace` and `strings.ReplacePairs` can produce the same results, they handle data differently. Choosing the right one can noticeably reduce the time Hugo takes to build your project. + +### Single pass vs. multiple passes + +When using `strings.Replace`, Hugo must scan the text from start to finish to find a match. If you chain three replacements together, Hugo performs three separate passes over the entire string. + +The `strings.ReplacePairs` function is more efficient because it performs a single pass. Hugo looks through the text once and applies all replacements simultaneously. + +### Caching + +Unlike `strings.Replace`, which performs a direct substitution, `strings.ReplacePairs` requires an initialization step to prepare the single-pass replacement logic. To make this efficient, Hugo manages this logic using a cache: + +- During the initial call, Hugo initializes and stores the logic for that specific set of pairs. +- During subsequent calls, Hugo retrieves the stored logic, skipping the initialization step and reducing the duration of the call. + +### Choosing the right function + +The efficiency of `strings.ReplacePairs` increases as the text gets longer or the number of pairs grows. Consider these scenarios when deciding which function to use: + +- For a single replacement on a short string like a title, `strings.Replace` is efficient. +- For multiple replacements or long strings like a long-form article, `strings.ReplacePairs` is much faster. + +For a document with about 8000 characters, which is roughly the length of a long-form article, `strings.ReplacePairs` outperforms five sequential `strings.Replace` calls during the initial call. Once cached, it is the faster choice for almost any situation with two or more pairs. + +[`strings.Replace`]: /functions/strings/replace/ diff --git a/docs/content/en/functions/strings/ReplaceRE.md b/docs/content/en/functions/strings/ReplaceRE.md new file mode 100644 index 0000000..00bd5b1 --- /dev/null +++ b/docs/content/en/functions/strings/ReplaceRE.md @@ -0,0 +1,38 @@ +--- +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 new file mode 100644 index 0000000..54d3f53 --- /dev/null +++ b/docs/content/en/functions/strings/RuneCount.md @@ -0,0 +1,20 @@ +--- +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 STRING] +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 new file mode 100644 index 0000000..456a3ee --- /dev/null +++ b/docs/content/en/functions/strings/SliceString.md @@ -0,0 +1,24 @@ +--- +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 new file mode 100644 index 0000000..d425fd9 --- /dev/null +++ b/docs/content/en/functions/strings/Split.md @@ -0,0 +1,24 @@ +--- +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 new file mode 100644 index 0000000..a4c779f --- /dev/null +++ b/docs/content/en/functions/strings/Substr.md @@ -0,0 +1,36 @@ +--- +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 new file mode 100644 index 0000000..e546336 --- /dev/null +++ b/docs/content/en/functions/strings/Title.md @@ -0,0 +1,29 @@ +--- +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 [project 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 +[project configuration]: /configuration/all/#title-case-style diff --git a/docs/content/en/functions/strings/ToLower.md b/docs/content/en/functions/strings/ToLower.md new file mode 100644 index 0000000..18ed6d4 --- /dev/null +++ b/docs/content/en/functions/strings/ToLower.md @@ -0,0 +1,16 @@ +--- +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 STRING] +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 new file mode 100644 index 0000000..a3e8ff9 --- /dev/null +++ b/docs/content/en/functions/strings/ToUpper.md @@ -0,0 +1,16 @@ +--- +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 STRING] +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 new file mode 100644 index 0000000..edbdcc2 --- /dev/null +++ b/docs/content/en/functions/strings/Trim.md @@ -0,0 +1,16 @@ +--- +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 STRING 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 new file mode 100644 index 0000000..c5d6ba6 --- /dev/null +++ b/docs/content/en/functions/strings/TrimLeft.md @@ -0,0 +1,23 @@ +--- +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 new file mode 100644 index 0000000..b897d87 --- /dev/null +++ b/docs/content/en/functions/strings/TrimPrefix.md @@ -0,0 +1,18 @@ +--- +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 new file mode 100644 index 0000000..05c2ed3 --- /dev/null +++ b/docs/content/en/functions/strings/TrimRight.md @@ -0,0 +1,23 @@ +--- +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 new file mode 100644 index 0000000..89ab85e --- /dev/null +++ b/docs/content/en/functions/strings/TrimSpace.md @@ -0,0 +1,20 @@ +--- +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 STRING] +--- + +{{< new-in 0.136.3 />}} + +Whitespace characters include `\t`, `\n`, `\v`, `\f`, `\r`, and characters in the [Unicode Space Separator][] category. + +```go-html-template +{{ strings.TrimSpace "\n\r\t foo \n\r\t" }} → foo +``` + +[Unicode Space Separator]: https://www.compart.com/en/unicode/category/Zs diff --git a/docs/content/en/functions/strings/TrimSuffix.md b/docs/content/en/functions/strings/TrimSuffix.md new file mode 100644 index 0000000..8028421 --- /dev/null +++ b/docs/content/en/functions/strings/TrimSuffix.md @@ -0,0 +1,18 @@ +--- +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 new file mode 100644 index 0000000..939e073 --- /dev/null +++ b/docs/content/en/functions/strings/Truncate.md @@ -0,0 +1,23 @@ +--- +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 [`safe.HTML`][] function before sending the value to `truncate`. Otherwise, the HTML tags will be escaped when passed through the `truncate` function. + +[`safe.HTML`]: /functions/safe/html/ diff --git a/docs/content/en/functions/strings/_index.md b/docs/content/en/functions/strings/_index.md new file mode 100644 index 0000000..28f26f1 --- /dev/null +++ b/docs/content/en/functions/strings/_index.md @@ -0,0 +1,7 @@ +--- +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 new file mode 100644 index 0000000..13ea77f --- /dev/null +++ b/docs/content/en/functions/templates/Current.md @@ -0,0 +1,157 @@ +--- +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 + +Use these methods on the `CurrentTemplateInfo` object. + +`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 project 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/page.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/page.html +/home/user/project/themes/foo/layouts/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 }} +
    + {{ .Name }} + {{ with .Base }} + {{ .Name }} + {{ end }} +
    +{{ end }} +``` + +Then call the partial from any template: + +```go-html-template {file="layouts/page.html" copy=true} +{{ define "main" }} +

    {{ .Title }}

    + {{ .Content }} + + {{ if site.Params.debug }} + {{ partial "template-open-in-vs-code.html" . }} + {{ end }} +{{ end }} +``` + +Use the same approach to render the entire call stack as links: + +```go-html-template {file="layouts/_partials/template-call-stack.html" copy=true} +{{ with templates.Current }} +
    + {{ range .Ancestors }} + {{ .Filename }}
    + {{ with .Base }} + {{ .Filename }}
    + {{ end }} + {{ end }} +
    +{{ end }} +``` diff --git a/docs/content/en/functions/templates/Defer.md b/docs/content/en/functions/templates/Defer.md new file mode 100644 index 0000000..5849363 --- /dev/null +++ b/docs/content/en/functions/templates/Defer.md @@ -0,0 +1,99 @@ +--- +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] +--- + +> [!NOTE] +> Do not call this function within a `partialCached` template. This restriction applies transitively: if `partialCached` calls a partial that calls `templates.Defer`, Hugo returns an error. Using this function within shortcode or render hook templates may also lead to unpredictable results. + +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 [css.TailwindCSS][] using the output of [`hugo_stats.json`][] to determine which classes and other HTML identifiers are being used in the final output: + +```go-html-template {file="layouts/baseof.html" copy=true} + + ... + {{ with (templates.Defer (dict "key" "global")) }} + {{ partial "css.html" . }} + {{ end }} + ... + +``` + +```go-html-template {file="layouts/_partials/css.html" copy=true} +{{ with resources.Get "css/main.css" }} + {{ $opts := dict "minify" (not hugo.IsDevelopment) }} + {{ with . | css.TailwindCSS $opts }} + {{ if hugo.IsDevelopment }} + + {{ else }} + {{ with . | fingerprint }} + + {{ end }} + {{ end }} + {{ end }} +{{ 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 >}} +[build] + [build.buildStats] + enable = true + [[build.cachebusters]] + source = 'assets/notwatching/hugo_stats\.json' + target = 'css' + [[build.cachebusters]] + source = '(postcss|tailwind)\.config\.js' + target = 'css' +[module] + [[module.mounts]] + source = 'assets' + target = 'assets' + [[module.mounts]] + disableWatch = true + source = 'hugo_stats.json' + target = 'assets/notwatching/hugo_stats.json' +{{< /code-toggle >}} + +## Options + +The `templates.Defer` function requires 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.Name }} +Page Outside: {{ .RelPermalink }} +I18n Outside: {{ i18n "hello" }} +{{ $data := (dict "page" . )}} +{{ with (templates.Defer (dict "data" $data )) }} + Language Inside: {{ site.Language.Name }} + Page Inside: {{ .page.RelPermalink }} + I18n Inside: {{ i18n "hello" }} +{{ end }} +``` + +The [output format][], [site][], and [language][] will be the same, even if the execution is deferred. In the example above, this means that the `site.Language.Name` and `.RelPermalink` will be the same on the inside and the outside of the deferred template. + +[`hugo_stats.json`]: /configuration/build/ +[css.TailwindCSS]: /functions/css/tailwindcss/ +[language]: /methods/site/language/ +[output format]: /configuration/output-formats/ +[site]: /methods/page/site/ diff --git a/docs/content/en/functions/templates/Exists.md b/docs/content/en/functions/templates/Exists.md new file mode 100644 index 0000000..a8d627f --- /dev/null +++ b/docs/content/en/functions/templates/Exists.md @@ -0,0 +1,27 @@ +--- +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/Inner.md b/docs/content/en/functions/templates/Inner.md new file mode 100644 index 0000000..15fab2d --- /dev/null +++ b/docs/content/en/functions/templates/Inner.md @@ -0,0 +1,71 @@ +--- +title: templates.Inner +description: Executes the content block enclosed by a partial call. +categories: [] +keywords: [decorator] +params: + functions_and_methods: + aliases: [inner] + returnType: any + signatures: ['templates.Inner [CONTEXT]'] +--- + +{{< new-in 0.154.0 />}} + +The `templates.Inner` function defines the injection point for code nested within a block style partial call. This is the core mechanism used to create a [partial decorator][]. + +## Overview + +The `templates.Inner` function acts as a placeholder within a partial template. When a partial is called as a decorator, it captures a block of code from the calling template rather than rendering it immediately. The `templates.Inner` function tells Hugo exactly where to inject that captured content. + +This signals a reversal of execution where the callee becomes the caller. The partial manages the outer structure while the calling template remains in control of the inner content. + +## Usage + +To use this function, the calling template must use the block style syntax with a [`with`][] statement. This allows decorators to be deeply nested. + +```go-html-template {file="layouts/home.html"} +{{ with partial "components/card.html" . }} +

    This content is passed to the partial.

    +{{ end }} +``` + +Inside the partial, call `templates.Inner` to render the captured block. + +```go-html-template {file="layouts/_partials/components/card.html"} +
    + {{ templates.Inner . }} +
    +``` + +## Arguments + +The function accepts one optional argument: the [context](g). This argument determines the value of the dot (`.`) inside the captured block when it is rendered. + +- If you provide an argument, such as `{{ templates.Inner .SomeData }}`, the dot inside the captured block is rebound to that specific data. +- If you do not provide an argument, the captured block uses the context of the caller where the partial was first invoked. + +## Context and scope + +When using decorators, the `with` statement creates a new [scope](g). Variables defined outside the with block in the calling template are not automatically available inside the captured block. + +By passing a context to `templates.Inner`, you ensure that the injected content has access to the correct data even when nested inside multiple layers of wrappers. This is critical when the decorator is used inside a loop or a specific data overlay. + +## Repeated execution + +A decorator can execute the captured content zero or more times. This is useful when the wrapper needs to repeat the same decoration for a collection of items, such as a list or a grid. + +```go-html-template {file="layouts/_partials/list-decorator.html"} +
      + {{ range .items }} +
    • + {{ templates.Inner . }} +
    • + {{ end }} +
    +``` + +In this example, the code provided by the caller is rendered once for every item in the .items collection, with the dot . updated to the current item in each iteration. + +[`with`]: /functions/go-template/with/ +[partial decorator]: /templates/partial-decorators/ diff --git a/docs/content/en/functions/templates/_index.md b/docs/content/en/functions/templates/_index.md new file mode 100644 index 0000000..a385604 --- /dev/null +++ b/docs/content/en/functions/templates/_index.md @@ -0,0 +1,7 @@ +--- +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 new file mode 100644 index 0000000..6db076a --- /dev/null +++ b/docs/content/en/functions/time/AsTime.md @@ -0,0 +1,48 @@ +--- +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 project 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 project 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 new file mode 100644 index 0000000..7430c98 --- /dev/null +++ b/docs/content/en/functions/time/Duration.md @@ -0,0 +1,41 @@ +--- +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 new file mode 100644 index 0000000..9d3c2ff --- /dev/null +++ b/docs/content/en/functions/time/Format.md @@ -0,0 +1,80 @@ +--- +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 project configuration. The order of precedence for determining the time zone is: + +1. The time zone offset in the date/time string +1. The time zone specified in your project configuration +1. The `Etc/UTC` time zone + +## 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` + +[`timeZone`]: /configuration/all/#timezone diff --git a/docs/content/en/functions/time/In.md b/docs/content/en/functions/time/In.md new file mode 100644 index 0000000..ca628de --- /dev/null +++ b/docs/content/en/functions/time/In.md @@ -0,0 +1,30 @@ +--- +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][]. + +```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 +``` + +[time zone name]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List diff --git a/docs/content/en/functions/time/Now.md b/docs/content/en/functions/time/Now.md new file mode 100644 index 0000000..67d2016 --- /dev/null +++ b/docs/content/en/functions/time/Now.md @@ -0,0 +1,42 @@ +--- +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 new file mode 100644 index 0000000..1e0c712 --- /dev/null +++ b/docs/content/en/functions/time/ParseDuration.md @@ -0,0 +1,32 @@ +--- +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 new file mode 100644 index 0000000..9c2ff21 --- /dev/null +++ b/docs/content/en/functions/time/_index.md @@ -0,0 +1,7 @@ +--- +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 new file mode 100644 index 0000000..8fa640a --- /dev/null +++ b/docs/content/en/functions/transform/CanHighlight.md @@ -0,0 +1,16 @@ +--- +title: transform.CanHighlight +description: Reports whether the given language is supported for syntax highlighting. +categories: [] +keywords: [highlight] +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 new file mode 100644 index 0000000..83fd86b --- /dev/null +++ b/docs/content/en/functions/transform/Emojify.md @@ -0,0 +1,27 @@ +--- +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 project configuration. Then you can write emoji shorthand directly into your content files; + +```md +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 new file mode 100644 index 0000000..09f5378 --- /dev/null +++ b/docs/content/en/functions/transform/HTMLEscape.md @@ -0,0 +1,29 @@ +--- +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 new file mode 100644 index 0000000..0f53a9c --- /dev/null +++ b/docs/content/en/functions/transform/HTMLUnescape.md @@ -0,0 +1,29 @@ +--- +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 [`safe.HTML`][] function. + +```go-html-template +{{ htmlUnescape "Lilo & Stitch" | safeHTML }} +``` + +[HTML entities]: https://developer.mozilla.org/en-US/docs/Glossary/Entity +[`html/template`]: https://pkg.go.dev/html/template +[`safe.HTML`]: /functions/safe/html/ diff --git a/docs/content/en/functions/transform/HTMLtoMarkdown.md b/docs/content/en/functions/transform/HTMLtoMarkdown.md new file mode 100644 index 0000000..18c2322 --- /dev/null +++ b/docs/content/en/functions/transform/HTMLtoMarkdown.md @@ -0,0 +1,37 @@ +--- +title: transform.HTMLToMarkdown +description: Converts HTML to Markdown. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [transform.HTMLToMarkdown INPUT] +--- + +{{< new-in 0.151.0 />}} + +> [!NOTE] +> This function is experimental and its API may change in the future. + +The `transform.HTMLToMarkdown` function converts HTML to Markdown by utilizing the [`html-to-markdown`][] Go package. + +## Usage + +```go-html-template +{{ .Content | transform.HTMLToMarkdown | safeHTML }} +``` + +## Plugins + +The conversion process is enabled by the following `html-to-markdown` plugins: + +Plugin|Description +:--|:-- +Base|Implements basic shared functionality +CommonMark|Implements Markdown according to the [Commonmark][] specification +Table|Implements tables according to the [GitHub Flavored Markdown][] specification + +[Commonmark]: https://spec.commonmark.org/current/ +[GitHub Flavored Markdown]: https://github.github.com/gfm/ +[`html-to-markdown`]: https://github.com/JohannesKaufmann/html-to-markdown?tab=readme-ov-file#readme diff --git a/docs/content/en/functions/transform/Highlight.md b/docs/content/en/functions/transform/Highlight.md new file mode 100644 index 0000000..9918ac8 --- /dev/null +++ b/docs/content/en/functions/transform/Highlight.md @@ -0,0 +1,63 @@ +--- +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 `transform.Highlight` function uses the [`alecthomas/chroma`][] package to generate syntax-highlighted HTML from the provided code, [language][], and [options](#options-1). + +## Arguments + +`CODE` +: (`string`) The code to highlight. + +`LANG` +: (`string`) The [language][] of the code to highlight. This value is case-insensitive. Optional; you can also set the language with the `type` key in OPTIONS. {{< new-in 0.162.0 />}} + +`OPTIONS` +: (`map or string`) A map or comma-separated key-value pairs wrapped in quotation marks. See the [options](#options-1) below; you can set default values for each option in your [project configuration][]. The key names are case-insensitive. + +## 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 }} + +{{ $input := `print("Hello World!")` }} +{{ $opts := dict "type" "python" "style" "dracula" }} +{{ transform.Highlight $input $opts }} +``` + +## Options + +The `transform.Highlight` function accepts an options map. + +{{% include "_common/syntax-highlighting-options.md" %}} + +`code` +: {{< new-in 0.162.0 />}} +: (`string`) Overrides the `CODE` argument. + +`type` +: {{< new-in 0.162.0 />}} +: (`string`) Overrides the `LANG` argument. + +[`alecthomas/chroma`]: https://github.com/alecthomas/chroma +[language]: /content-management/syntax-highlighting#languages +[project configuration]: /configuration/markup#highlight diff --git a/docs/content/en/functions/transform/HighlightCodeBlock.md b/docs/content/en/functions/transform/HighlightCodeBlock.md new file mode 100644 index 0000000..964adfa --- /dev/null +++ b/docs/content/en/functions/transform/HighlightCodeBlock.md @@ -0,0 +1,74 @@ +--- +title: transform.HighlightCodeBlock +description: Highlights code received in context within a code block render hook. +categories: [] +keywords: [highlight] +params: + functions_and_methods: + aliases: [] + returnType: highlight.HighlightResult + signatures: ['transform.HighlightCodeBlock CONTEXT [OPTIONS]'] +--- + +The `transform.HighlightCodeBlock` function uses the [`alecthomas/chroma`][] package to generate syntax-highlighted HTML from code received in context within a code block render hook. This function is only useful within a code block render hook. + +## Arguments + +CONTEXT +: The [context][] passed into a code block render hook. + +OPTIONS +: (`map`) A map of key-value pairs. See the [options](#options-1) below. The key names are case-insensitive. + +## Return value + +`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.
    +
    +## Examples
    +
    +```go-html-template
    +{{ $result := transform.HighlightCodeBlock . }}
    +{{ $result.Wrapped }}
    +```
    +
    +To override the default options:
    +
    +```go-html-template
    +{{ $opts := merge .Options (dict "lineNos" true) }}
    +{{ $result := transform.HighlightCodeBlock . $opts }}
    +{{ $result.Wrapped }}
    +```
    +
    +To fall back to plain text when the language is not supported by the highlighter:
    +
    +```go-html-template
    +{{ $opts := dict }}
    +{{ if not (transform.CanHighlight .Type) }}
    +  {{ $opts = dict "type" "text" }}
    +{{ end }}
    +{{ $result := transform.HighlightCodeBlock . $opts }}
    +{{ $result.Wrapped }}
    +```
    +
    +## Options
    +
    +The `transform.HighlightCodeBlock` function accepts an options map.
    +
    +{{% include "_common/syntax-highlighting-options.md" %}}
    +
    +`code`
    +: {{< new-in 0.162.0 />}}
    +: (`string`) Overrides the code received from the code block context.
    +
    +`type`
    +: {{< new-in 0.162.0 />}}
    +: (`string`) Overrides the language received from the code block context.
    +
    +[`alecthomas/chroma`]: https://github.com/alecthomas/chroma
    +[context]: /render-hooks/code-blocks/#context
    diff --git a/docs/content/en/functions/transform/Markdownify.md b/docs/content/en/functions/transform/Markdownify.md
    new file mode 100644
    index 0000000..56a4c6a
    --- /dev/null
    +++ b/docs/content/en/functions/transform/Markdownify.md
    @@ -0,0 +1,27 @@
    +---
    +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 +[Markdown render hooks]: /render-hooks/ +[`RenderString`]: /methods/page/renderstring/ diff --git a/docs/content/en/functions/transform/Plainify.md b/docs/content/en/functions/transform/Plainify.md new file mode 100644 index 0000000..780cf46 --- /dev/null +++ b/docs/content/en/functions/transform/Plainify.md @@ -0,0 +1,16 @@ +--- +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 new file mode 100644 index 0000000..8a8ef24 --- /dev/null +++ b/docs/content/en/functions/transform/PortableText.md @@ -0,0 +1,222 @@ +--- +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][] is a JSON structure that represents rich text content in the [Sanity][] CMS. In Hugo, this function is typically used in a [content adapter][] that creates pages from Sanity data. + +## Types supported + +- `block` and `span` +- `image`. Note that the image handling is currently basic; 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 an [image render hook][]. +- `code` (see the [code-input][] plugin). Code will be rendered as fenced code blocks with any file name provided passed 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][]. + +## 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 }} + +{{ $q := `*[_type == 'post']{ + title, publishedAt, summary, slug, body[]{ + ..., + _type == "image" => { + ..., + asset->{ + _id, + path, + url, + altText, + title, + description, + metadata { + dimensions { + aspectRatio, + width, + height + } + } + } + } + }, + }` +}} +{{ $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 + +The following 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: + +```sh +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][] 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 rebuilds when you push new content to Sanity. + +See [Caching in resources.GetRemote][] for more fine-grained control. + +[Caching in resources.GetRemote]: /functions/resources/getremote/#caching +[Portable Text]: https://www.portabletext.org/ +[RFC 7234]: https://tools.ietf.org/html/rfc7234 +[Sanity]: https://www.sanity.io/ +[code-input]: https://www.sanity.io/plugins/code-input +[content adapter]: /content-management/content-adapters/ +[image render hook]: /render-hooks/images/ +[render hooks]: /render-hooks/ diff --git a/docs/content/en/functions/transform/Remarshal.md b/docs/content/en/functions/transform/Remarshal.md new file mode 100644 index 0000000..547ef54 --- /dev/null +++ b/docs/content/en/functions/transform/Remarshal.md @@ -0,0 +1,89 @@ +--- +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/' + locale = 'en-US' + title = 'ABC Widgets' +`}} +
    {{ transform.Remarshal "json" $s }}
    +``` + +Resulting HTML: + +```html +
    {
    +   "baseURL": "https://example.org/",
    +   "locale": "en-US",
    +   "title": "ABC Widgets"
    +}
    +
    +``` + +Rendered in browser: + +```text +{ + "baseURL": "https://example.org/", + "locale": "en-US", + "title": "ABC Widgets" +} +``` + +Example 2 +: Convert a map to YAML. + +```go-html-template +{{ $m := dict + "a" "Hugo rocks!" + "b" (dict "question" "What is 6x7?" "answer" 42) + "c" (slice "foo" "bar") +}} +
    {{ transform.Remarshal "yaml" $m }}
    +``` + +Resulting HTML: + +```html +
    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 new file mode 100644 index 0000000..2333fe2 --- /dev/null +++ b/docs/content/en/functions/transform/ToMath.md @@ -0,0 +1,182 @@ +--- +title: transform.ToMath +description: Renders mathematical equations and expressions written in the LaTeX markup language. +categories: [] +keywords: [] +params: + functions_and_methods: + aliases: [] + returnType: template.HTML + signatures: ['transform.ToMath INPUT [OPTIONS]'] +aliases: [/functions/tomath] +--- + +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 + +The `transform.ToMath` function accepts an options map. These options 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 + + ``` + +`strict` +: {{< new-in 0.147.6 />}} +: (`string`) Controls how KaTeX handles LaTeX features that offer convenience but aren't officially supported, one of `error`, `ignore`, or `warn`. Default is `error`. + + - `error`: Throws an error when convenient, unsupported LaTeX features are encountered. + - `ignore`: Allows convenient, unsupported LaTeX features without any feedback. + - `warn`: {{< new-in 0.147.7 />}} Emits a warning when convenient, unsupported LaTeX features are encountered. + + The `newLineInDisplayMode` error code, which flags the use of `\\` or `\newline` in display mode outside an array or tabular environment, is intentionally designed not to throw an error, despite this behavior being questionable. + +`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 project configuration. The passthrough extension preserves raw Markdown within delimited snippets of text, including the delimiters themselves. + + {{< code-toggle file=hugo copy=true >}} + [markup.goldmark.extensions.passthrough] + enable = true + [markup.goldmark.extensions.passthrough.delimiters] + block = [['\[', '\]'], ['$$', '$$']] + inline = [['\(', '\)']] + {{< /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 must 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.4 + + ```go-html-template {file="layouts/_markup/render-passthrough.html" copy=true} + {{- $opts := dict "output" "htmlAndMathml" "displayMode" (eq .Type "block") }} + {{- with try (transform.ToMath .Inner $opts) }} + {{- with .Err }} + {{- errorf "Unable to render mathematical markup to HTML using the transform.ToMath function. The KaTeX display engine threw the following error: %s: see %s." . $.Position }} + {{- else }} + {{- .Value }} + {{- $.Page.Store.Set "hasMath" true }} + {{- end }} + {{- end -}} + ``` + +Step 3 +: In your base template, conditionally include the KaTeX CSS within the head element. + + ```go-html-template {file="layouts/baseof.html" copy=true} + + {{ $noop := .WordCount }} + {{ if .Page.Store.Get "hasMath" }} + + {{ end }} + + ``` + + In the above, note the use of a [noop](g) statement to force content rendering before we check the value of `hasMath` with the `Store.Get` method. + + > [!NOTE] + > This conditional approach only identifies math on the current page. Mathematical expressions will not display correctly when one page's content is embedded within another. For example, if a [list page](g) calls the [`Content`][] or [`Summary`][] methods while ranging through its page collection, the list page will not load the KaTeX CSS. + > + > If this affects your site, use this conditional logic instead: + > + > ```go-html-template {file="layouts/baseof.html" copy=true} + > {{ $noop := .WordCount }} + > {{ if or (.Page.Store.Get "hasMath") .IsNode }} + > + > {{ end }} + > ``` + +Step 4 +: Add some mathematical markup to your content, then test. + + ```md {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. + +```md +$$C_p[\ce{H2O(l)}] = \pu{75.3 J // mol K}$$ +``` + +$$C_p[\ce{H2O(l)}] = \pu{75.3 J // mol K}$$ + +[KaTeX]: https://katex.org/ +[MathML]: https://developer.mozilla.org/en-US/docs/Web/MathML +[`Content`]: /methods/page/content/ +[`Summary`]: /methods/page/summary/ +[`mhchem`]: https://mhchem.github.io/MathJax-mhchem/ +[hexadecimal color]: https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color +[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 new file mode 100644 index 0000000..8be76a1 --- /dev/null +++ b/docs/content/en/functions/transform/Unmarshal.md @@ -0,0 +1,373 @@ +--- +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). + +## Options + +The `transform.Unmarshal` function accepts an options map. + +`delimiter` +: (`string`) Applicable to CSV files. The delimiter used. Default is `,`. + +`comment` +: (`string`) Applicable to CSV files. The comment character used in the CSV. If set, lines beginning with the comment character without preceding whitespace are ignored. + +`format` +: {{< new-in 0.149.0 />}} +: (`string`) The serialization format of the input, one of `csv`, `json`, `org`, `toml`, `xml`, or `yaml`. If empty or unspecified, Hugo infers the format from the input. For resources, this option is only needed if the file lacks an extension or to override the inferred format. For strings, it's only required when the format is ambiguous. + +`lazyQuotes` +: (`bool`) Applicable to CSV files. 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`) Applicable to CSV files. The target data type, either `slice` or `map`. Default is `slice`. + +## Unmarshal a string + +```go-html-template +{{ $string := ` +title: Les Misérables +author: Victor Hugo +`}} + +{{ $book := transform.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. + +```tree +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][]. + +```tree +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 + +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 := dict }} +{{ $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" }} + + + + + + + + + + + {{ range . }} + + + + + + + {{ end }} + +
    nametypebreedage
    {{ .name }}{{ .type }}{{ .breed }}{{ .age }}
    +{{ 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
    • +
    +``` + +[Content-Type]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type +[`index`]: /functions/collections/indexfunction/ +[page bundle]: /content-management/page-bundles/ diff --git a/docs/content/en/functions/transform/XMLEscape.md b/docs/content/en/functions/transform/XMLEscape.md new file mode 100644 index 0000000..51c1df9 --- /dev/null +++ b/docs/content/en/functions/transform/XMLEscape.md @@ -0,0 +1,38 @@ +--- +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] +--- + +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/rss.xml"} +{{ .Summary | transform.XMLEscape | safeHTML }} +``` + +[HTML entities]: https://developer.mozilla.org/en-US/docs/Glossary/Entity +[`html/template`]: https://pkg.go.dev/html/template +[disallowed characters]: https://www.w3.org/TR/xml/#charsets diff --git a/docs/content/en/functions/transform/_index.md b/docs/content/en/functions/transform/_index.md new file mode 100644 index 0000000..19c271b --- /dev/null +++ b/docs/content/en/functions/transform/_index.md @@ -0,0 +1,7 @@ +--- +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 new file mode 100644 index 0000000..5191316 --- /dev/null +++ b/docs/content/en/functions/urls/AbsLangURL.md @@ -0,0 +1,72 @@ +--- +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 project configuration +- The language prefix, if any + +This is the project 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 project 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 project 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 new file mode 100644 index 0000000..60fc05f --- /dev/null +++ b/docs/content/en/functions/urls/AbsURL.md @@ -0,0 +1,62 @@ +--- +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 project 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 project 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 project 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 new file mode 100644 index 0000000..9d7062c --- /dev/null +++ b/docs/content/en/functions/urls/Anchorize.md @@ -0,0 +1,18 @@ +--- +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" %}} + +The `ursl.Anchorize` function sanitizes the resulting string per the [`autoIDType`][] setting in your project configuration. + +[`autoIDType`]: /configuration/markup/#parserautoidtype diff --git a/docs/content/en/functions/urls/JoinPath.md b/docs/content/en/functions/urls/JoinPath.md new file mode 100644 index 0000000..f518951 --- /dev/null +++ b/docs/content/en/functions/urls/JoinPath.md @@ -0,0 +1,27 @@ +--- +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 new file mode 100644 index 0000000..89e67f7 --- /dev/null +++ b/docs/content/en/functions/urls/Parse.md @@ -0,0 +1,36 @@ +--- +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][]. 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. + +```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 +``` + +[URL structure]: https://godoc.org/net/url#URL +[scheme]: https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml#uri-schemes-1 diff --git a/docs/content/en/functions/urls/PathEscape.md b/docs/content/en/functions/urls/PathEscape.md new file mode 100644 index 0000000..c842694 --- /dev/null +++ b/docs/content/en/functions/urls/PathEscape.md @@ -0,0 +1,22 @@ +--- +title: urls.PathEscape +description: Returns the given string, applying percent-encoding to special characters and reserved delimiters so it can be safely used as a segment within a URL path. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [urls.PathEscape INPUT] +--- + +{{< new-in v0.153.0 />}} + +The `urls.PathEscape` function does the inverse transformation of [`urls.PathUnescape`][]. + +```go-html-template +{{ urls.PathEscape "my café" }} → my%20caf%C3%A9 +``` + +Use this function to escape a string so that it can be safely used as an individual segment within a URL path. + +[`urls.PathUnescape`]: /functions/urls/PathUnescape/ diff --git a/docs/content/en/functions/urls/PathUnescape.md b/docs/content/en/functions/urls/PathUnescape.md new file mode 100644 index 0000000..8def072 --- /dev/null +++ b/docs/content/en/functions/urls/PathUnescape.md @@ -0,0 +1,22 @@ +--- +title: urls.PathUnescape +description: Returns the given string, replacing all percent-encoded sequences with the corresponding unescaped characters. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [urls.PathUnescape INPUT] +--- + +{{< new-in v0.153.0 />}} + +The `urls.PathUnescape` function does the inverse transformation of [`urls.PathEscape`][]. + +```go-html-template +{{ urls.PathUnescape "A%2Fb%2Fc%3Fd=%C3%A9&f=g+h" }} → A/b/c?d=é&f=g+h +``` + +Use this function to decode an individual segment within a URL path. + +[`urls.PathEscape`]: /functions/urls/PathEscape/ diff --git a/docs/content/en/functions/urls/Ref.md b/docs/content/en/functions/urls/Ref.md new file mode 100644 index 0000000..92abed9 --- /dev/null +++ b/docs/content/en/functions/urls/Ref.md @@ -0,0 +1,46 @@ +--- +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 new file mode 100644 index 0000000..8a2450b --- /dev/null +++ b/docs/content/en/functions/urls/RelLangURL.md @@ -0,0 +1,82 @@ +--- +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 project configuration +- The language prefix, if any + +This is the project 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 project 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 project 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 new file mode 100644 index 0000000..aa7acf5 --- /dev/null +++ b/docs/content/en/functions/urls/RelRef.md @@ -0,0 +1,46 @@ +--- +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 new file mode 100644 index 0000000..08ba915 --- /dev/null +++ b/docs/content/en/functions/urls/RelURL.md @@ -0,0 +1,72 @@ +--- +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 project 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 project 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 project 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 new file mode 100644 index 0000000..46f9474 --- /dev/null +++ b/docs/content/en/functions/urls/URLize.md @@ -0,0 +1,61 @@ +--- +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 project 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: + +```tree +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 new file mode 100644 index 0000000..3a1962d --- /dev/null +++ b/docs/content/en/functions/urls/_index.md @@ -0,0 +1,7 @@ +--- +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 new file mode 100644 index 0000000..2e2f571 --- /dev/null +++ b/docs/content/en/getting-started/_index.md @@ -0,0 +1,8 @@ +--- +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 new file mode 100644 index 0000000..4e0feb5 --- /dev/null +++ b/docs/content/en/getting-started/directory-structure.md @@ -0,0 +1,208 @@ +--- +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 content, structure, behavior, and presentation. + +## Project skeleton + +Hugo generates a project skeleton when you create a new project. For example, this command: + +```sh +hugo new project my-project +``` + +Creates this directory structure: + +```txt +my-project/ +├── archetypes/ +│ └── default.md +├── assets/ +├── content/ +├── data/ +├── i18n/ +├── layouts/ +├── static/ +├── themes/ +└── hugo.toml <-- project configuration +``` + +Depending on requirements, you may wish to organize your project configuration into subdirectories: + +```txt +my-project/ +├── archetypes/ +│ └── default.md +├── assets/ +├── config/ <-- project configuration +│ └── _default/ +│ └── hugo.toml +├── content/ +├── data/ +├── i18n/ +├── layouts/ +├── static/ +└── themes/ +``` + +When you build your project, Hugo creates a `public` directory, and typically a `resources` directory as well: + +```txt +my-project/ +├── archetypes/ +│ └── default.md +├── assets/ +├── config/ +│ └── _default/ +│ └── hugo.toml +├── content/ +├── data/ +├── i18n/ +├── layouts/ +├── public/ <-- created when you build your project +├── resources/ <-- created when you build your project +├── static/ +└── themes/ +``` + +## Directories + +Each of the subdirectories contributes to content, structure, behavior, or presentation. + +`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 project 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-directory]. + +`content` +: The `content` directory contains the markup files (typically Markdown) and page resources that comprise the content of your project. 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 projects. 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 build` or `hugo server` commands. Hugo recreates this directory and its content as needed. See [details][build-your-project]. + +`resources` +: The `resources` directory contains cached output from Hugo's asset pipelines, generated when you run the `hugo build` 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 project. For example: `favicon.ico`, `robots.txt`, and files that verify website ownership. Before the introduction of [page bundles](g) and [asset pipelines][], 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. + +## Unified file system + +Hugo creates a [unified file system](g), 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: + +```tree +home/ +└── user/ + ├── my-project/ + │ ├── 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 using mounts. In your project configuration: + +{{< code-toggle file=hugo >}} +[[module.mounts]] +source = 'content' +target = 'content' + +[[module.mounts]] +source = '/home/user/shared-content' +target = 'content' +{{< /code-toggle >}} + +> [!NOTE] +> Defining a custom mount replaces the default mounting for that [component](g). To overlay an external directory on top of the project default, you must explicitly mount both. +> +> Hugo does not follow symbolic links. If you need the functionality provided by symbolic links, use Hugo's unified file system instead. + +After mounting, the unified file system has this structure: + +```tree +home/ +└── user/ + └── my-project/ + ├── 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 +``` + +When two or more files share the same path, the version in the highest layer takes precedence. In the example above, if the `shared-content` directory contains `books/book-1.md`, it is ignored because the project's `content` directory is the first (highest) mount. + +You can mount directories to `archetypes`, `assets`, `content`, `data`, `i18n`, `layouts`, and `static`. See [details][mounts]. + +You can also mount directories from Git repositories using modules. See [details](/hugo-modules/). + +## Theme skeleton + +Hugo generates a functional theme skeleton when you create a new theme. For example, this command: + +```sh +hugo new theme my-theme +``` + +Creates this directory structure (subdirectories not shown): + +```tree +my-theme/ +├── archetypes/ +├── assets/ +├── content/ +├── data/ +├── i18n/ +├── layouts/ +├── static/ +└── hugo.toml +``` + +Using the unified 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. + +[asset pipelines]: /hugo-pipes/introduction/ +[build-your-project]: /getting-started/usage/#build-your-project +[configuration-directory]: /configuration/introduction/#configuration-directory +[mounts]: /configuration/module/#mounts 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 new file mode 100644 index 0000000..ebed7e8 Binary files /dev/null and b/docs/content/en/getting-started/external-learning-resources/build-websites-with-hugo.png 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 new file mode 100644 index 0000000..7bc5c99 Binary files /dev/null and b/docs/content/en/getting-started/external-learning-resources/hugo-in-action.png 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 new file mode 100644 index 0000000..2cfcb28 --- /dev/null +++ b/docs/content/en/getting-started/external-learning-resources/index.md @@ -0,0 +1,120 @@ +--- +title: External learning resources +linkTitle: External resources +description: Use these third-party resources to learn Hugo. +categories: [] +keywords: [] +weight: 40 +--- + +> [!NOTE] +> Many of the resources on this page, including older books and videos, may contain out-of-date information. The Hugo software has undergone significant changes since these resources were created. These changes include the introduction of a new template system, the deprecation of various functions and settings, and the addition of new features like Markdown render hooks, content adapters, and support for mathematical markup. While some concepts may still be relevant, it's recommended to consult the official Hugo documentation for the most current and accurate information. + +## 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][]\ +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][]\ +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][] +1. [Layouts in Hugo][] +1. [Hugo Partials][] +1. [Hugo templating basics][] +1. [Blogging in Hugo][] +1. [Using Data in Hugo][] + +Creator: Mike Neumegen\ +Affiliation: [CloudCannon][]\ +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][] +1. [Windows Installation][] +1. [Mac Installation][] +1. [Creating A New Site][] +1. [Installing & Using Themes][] +1. [Content Organization][] +1. [Front Matter][] +1. [Archetypes][] +1. [Shortcodes][] +1. [Taxonomies][] +1. [Template Basics][] +1. [List Page Templates][] +1. [Single Page Templates][] +1. [Home Page Templates][] +1. [Section Templates][] +1. [Block Templates][] +1. [Variables][] +1. [Functions][] +1. [Conditionals][] +1. [Data Templates][] +1. [Partial Templates][] +1. [Shortcode Templates][] +1. [Building & Hosting][] + +Creator: Mike Dane\ +Affiliation: [Giraffe Academy][]\ +Creation date: September 2017 + +[Archetypes]: https://www.giraffeacademy.com/static-site-generators/hugo/archetypes/ +[Block Templates]: https://www.giraffeacademy.com/static-site-generators/hugo/block-templates/ +[Blogging in Hugo]: https://cloudcannon.com/tutorials/hugo-beginner-tutorial/blogging-in-hugo/ +[Building & Hosting]: https://www.giraffeacademy.com/static-site-generators/hugo/building-&-hosting/ +[CloudCannon]: https://cloudcannon.com/ +[Conditionals]: https://www.giraffeacademy.com/static-site-generators/hugo/conditionals/ +[Content Organization]: https://www.giraffeacademy.com/static-site-generators/hugo/content-organization/ +[Creating A New Site]: https://www.giraffeacademy.com/static-site-generators/hugo/hugo-directory-structure/ +[Data Templates]: https://www.giraffeacademy.com/static-site-generators/hugo/data-templates/ +[Front Matter]: https://www.giraffeacademy.com/static-site-generators/hugo/front-matter/ +[Functions]: https://www.giraffeacademy.com/static-site-generators/hugo/functions/ +[Getting set up in Hugo]: https://cloudcannon.com/tutorials/hugo-beginner-tutorial/ +[Giraffe Academy]: https://www.giraffeacademy.com/ +[Home Page Templates]: https://www.giraffeacademy.com/static-site-generators/hugo/home-page-templates/ +[Hugo Partials]: https://cloudcannon.com/tutorials/hugo-beginner-tutorial/hugo-partials/ +[Hugo templating basics]: https://cloudcannon.com/tutorials/hugo-beginner-tutorial/hugo-templating-basics/ +[Installing & Using Themes]: https://www.giraffeacademy.com/static-site-generators/hugo/installing-using-themes/ +[Introduction]: https://www.giraffeacademy.com/static-site-generators/hugo/ +[Layouts in Hugo]: https://cloudcannon.com/tutorials/hugo-beginner-tutorial/layouts-in-hugo/ +[List Page Templates]: https://www.giraffeacademy.com/static-site-generators/hugo/list-page-templates/ +[Mac Installation]: https://www.giraffeacademy.com/static-site-generators/hugo/installing-hugo-on-mac/ +[Manning Publications]: https://www.manning.com/books/hugo-in-action/ +[Partial Templates]: https://www.giraffeacademy.com/static-site-generators/hugo/partial-templates/ +[Pragmatic Bookshelf]: https://pragprog.com/titles/bhhugo/build-websites-with-hugo/ +[Section Templates]: https://www.giraffeacademy.com/static-site-generators/hugo/section-templates/ +[Shortcode Templates]: https://www.giraffeacademy.com/static-site-generators/hugo/shortcode-templates/ +[Shortcodes]: https://www.giraffeacademy.com/static-site-generators/hugo/shortcodes/ +[Single Page Templates]: https://www.giraffeacademy.com/static-site-generators/hugo/single-page-templates/ +[Taxonomies]: https://www.giraffeacademy.com/static-site-generators/hugo/taxonomies/ +[Template Basics]: https://www.giraffeacademy.com/static-site-generators/hugo/introduction-to-templates/ +[Using Data in Hugo]: https://cloudcannon.com/tutorials/hugo-beginner-tutorial/using-data-in-hugo/ +[Variables]: https://www.giraffeacademy.com/static-site-generators/hugo/variables/ +[Windows Installation]: https://www.giraffeacademy.com/static-site-generators/hugo/installing-hugo-on-windows/ diff --git a/docs/content/en/getting-started/quick-start.md b/docs/content/en/getting-started/quick-start.md new file mode 100644 index 0000000..cf8e5c0 --- /dev/null +++ b/docs/content/en/getting-started/quick-start.md @@ -0,0 +1,213 @@ +--- +title: Quick start +description: Create your first Hugo project. +categories: [] +keywords: [] +params: + minVersion: v0.158.0 +weight: 10 +aliases: [/quickstart/,/overview/quickstart/] +--- + +In this tutorial you will: + +1. Create a project +1. Add content +1. Configure the project +1. Publish the project + +## Prerequisites + +Before you begin this tutorial you must: + +1. [Install Hugo][] (any edition, {{% param "minVersion" %}} or later) +1. [Install Git][] + +You must also be comfortable working from the command line. + +## Create a project + +### 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. + +```sh +hugo version +``` + +Run these commands to create a Hugo project with the [Ananke][] theme. The next section provides an explanation of each command. + +```sh +hugo new project quickstart +cd quickstart +git init +git submodule add https://github.com/gohugo-ananke/ananke themes/ananke +echo "theme = 'ananke'" >> hugo.toml +hugo server +``` + +View your project at the URL displayed in your terminal. Press `Ctrl + C` to stop Hugo's development server. + +### Explanation of commands + +Create the [project skeleton][] for your project in the `quickstart` directory. + +```sh +hugo new project quickstart +``` + +Change the current directory to the root of your project. + +```sh +cd quickstart +``` + +Initialize an empty Git repository in the current directory. + +```sh +git init +``` + +Clone the [Ananke][] theme into the `themes` directory, adding it to your project as a [Git submodule][]. + +```sh +git submodule add https://github.com/gohugo-ananke/ananke themes/ananke +``` + +Append a line to your project configuration file, indicating the current theme. + +```sh +echo "theme = 'ananke'" >> hugo.toml +``` + +Start Hugo's development server. + +```sh +hugo server +``` + +Press `Ctrl + C` to stop Hugo's development server. + +## Add content + +Add a new page to your project. + +```sh +hugo new content content/posts/my-first-post.md +``` + +Hugo created the file in the `content/posts` directory. Open the file with your editor. + +```md ++++ +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 project. Learn more about [draft, future, and expired content][]. + +Add some [Markdown][] to the body of the post, but do not change the `draft` value. + +```md ++++ +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. You can run either of the following commands to include draft content. + +```sh +hugo server --buildDrafts +hugo server -D +``` + +View your project 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 project + +With your editor, open the [project configuration][] file in the root of your project directory: + +```toml {file="hugo.toml"} +baseURL = 'https://example.org/' +locale = 'en-us' +title = 'My New Hugo Project' +theme = 'ananke' +``` + +Make the following changes: + +1. Set the `baseURL` for your project. This value must begin with the protocol and end with a slash, as shown above. +1. Set the `locale` to your locale. +1. Set the `title` for your project. + +Start Hugo's development server to see your changes, remembering to include draft content. + +```sh +hugo server -D +``` + +> [!NOTE] +> Now that you have the Ananke theme installed, check out their [documentation][] and [demonstration site][] to learn how to configure and customize it. + +## Publish the project + +In this step you will _publish_ your project, but you will not _deploy_ it. + +When you publish your project, Hugo renders all build artifacts to the `public` directory in the root of your project. This includes the HTML files for every site, along with assets such as images, CSS, and JavaScript. The command is simple. + +```sh +hugo +``` + +To learn how to _deploy_ your project, 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][] page. + +[Ananke]: https://github.com/theNewDynamic/gohugo-theme-ananke +[Git submodule]: https://git-scm.com/book/en/v2/Git-Tools-Submodules +[Install Git]: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git +[Install Hugo]: /installation/ +[Markdown]: https://daringfireball.net/projects/markdown +[PowerShell]: https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows +[are different applications]: https://learn.microsoft.com/en-us/powershell/scripting/whats-new/differences-from-windows-powershell?view=powershell-7.3 +[demonstration site]: https://ananke-theme.netlify.app/ +[documentation]: https://ananke-documentation.netlify.app/ +[draft, future, and expired content]: /getting-started/usage/#draft-future-and-expired-content +[external learning resources]: /getting-started/external-learning-resources/ +[forum]: https://discourse.gohugo.io/ +[front matter]: /content-management/front-matter/ +[host and deploy]: /host-and-deploy/ +[live testing tool]: https://spec.commonmark.org/dingus/ +[project configuration]: /configuration/ +[project skeleton]: /getting-started/directory-structure/#project-skeleton +[requesting help]: https://discourse.gohugo.io/t/requesting-help/9132 +[specification]: https://spec.commonmark.org/ diff --git a/docs/content/en/getting-started/usage.md b/docs/content/en/getting-started/usage.md new file mode 100644 index 0000000..1f9bef4 --- /dev/null +++ b/docs/content/en/getting-started/usage.md @@ -0,0 +1,150 @@ +--- +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 +``` + +## 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 project + +To build your project, `cd` into your project directory and run: + +```sh +hugo build +``` + +The [`hugo build`][] command builds your project, publishing the files to the `public` directory. To publish your project to a different directory, use the [`--destination`][] flag or set [`publishDir`][] in your project configuration. + +> [!NOTE] +> Hugo does not clear the `public` directory before building your project. 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 + +> [!NOTE] +> Hugo publishes descendants of draft, future, and expired [branch](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 build` or `hugo server` with command line flags: + +```sh +hugo build --buildDrafts # or -D +hugo build --buildExpired # or -E +hugo build --buildFuture # or -F +``` + +Although you can also set these values in your project 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 project. 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 project. 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: + +```tree +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 to a [CI/CD](g) platform, where a push[^1] to their remote Git repository triggers a build and deployment. 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. + +[LiveReload]: https://github.com/livereload/livereload-js +[`--destination`]: /commands/hugo/#options +[`cascade`]: /content-management/front-matter/#cascade +[`hugo build`]: /commands/hugo/ +[`hugo server`]: /commands/hugo_server/ +[`publishDir`]: /configuration/all/#publishdir +[build options]: /content-management/build-options/ +[front matter]: /content-management/front-matter/ +[host and deploy]: /host-and-deploy/ +[installing]: /installation/ diff --git a/docs/content/en/host-and-deploy/_index.md b/docs/content/en/host-and-deploy/_index.md new file mode 100644 index 0000000..627f12c --- /dev/null +++ b/docs/content/en/host-and-deploy/_index.md @@ -0,0 +1,8 @@ +--- +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 new file mode 100644 index 0000000..0fa3f40 --- /dev/null +++ b/docs/content/en/host-and-deploy/deploy-with-hugo-deploy.md @@ -0,0 +1,102 @@ +--- +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 deploy or 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`][]. + - Azure: [Install the CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) and run [`az login`][]. + - Google Cloud: [Install the CLI](https://cloud.google.com/sdk) and run [`gcloud auth login`][]. + + Each service supports various authentication methods, including environment variables. See [details][]. + +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][] 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 [project 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: + +```sh +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][]. + +[AWS]: https://aws.amazon.com +[Azure]: https://azure.microsoft.com +[CLI documentation]: /commands/hugo_deploy/ +[Google Cloud]: https://cloud.google.com/ +[Quick Start]: /getting-started/quick-start/ +[`.well-known`]: https://en.wikipedia.org/wiki/Well-known_URI +[`aws configure`]: https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html +[`az login`]: https://docs.microsoft.com/en-us/cli/azure/authenticate-azure-cli +[`gcloud auth login`]: https://cloud.google.com/sdk/gcloud/reference/auth/login +[`name`]: /configuration/deployment/#name +[`url`]: /configuration/deployment/#url +[configuration]: /configuration/deployment/#targets-1 +[configure deployment]: /configuration/deployment/ +[create a storage container]: https://docs.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-portal +[details]: https://gocloud.dev/howto/blob/#services +[installation]: /installation/ +[project 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 new file mode 100644 index 0000000..d0969e0 --- /dev/null +++ b/docs/content/en/host-and-deploy/deploy-with-rclone.md @@ -0,0 +1,49 @@ +--- +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][], such as SFTP +- A functional static website built with Hugo +- Deploying from an [Rclone][] compatible operating system +- You have [installed Rclone][] + +**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 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 build --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][] provide [an example of configuring Rclone to use SFTP][]. + +For the next commands, we will assume you configured a remote you named `hugo-www`. + +The above 'spoiler' commands could become: + +```txt +hugo build --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. + +[Rclone docs]: https://rclone.org/docs/ +[Rclone]: https://rclone.org +[an example of configuring Rclone to use SFTP]: https://rclone.org/sftp/ +[installed Rclone]: https://rclone.org/install/ +[protocols supported by rclone]: https://rclone.org/#providers diff --git a/docs/content/en/host-and-deploy/deploy-with-rsync.md b/docs/content/en/host-and-deploy/deploy-with-rsync.md new file mode 100644 index 0000000..b1f9d5c --- /dev/null +++ b/docs/content/en/host-and-deploy/deploy-with-rsync.md @@ -0,0 +1,135 @@ +--- +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 < /dev/null && echo "Dart Sass: $(sass --version)" || echo "Dart Sass: not installed" + command -v go &> /dev/null && echo "Go: $(go version)" || echo "Go: not installed" + command -v hugo &> /dev/null && echo "Hugo: $(hugo version)" || echo "Hugo: not installed" + command -v node &> /dev/null && echo "Node.js: $(node --version)" || echo "Node.js: not installed" + + # Configure Git + - | + echo "Configuring Git..." + git config --global core.quotepath false + + # Fetch full Git history + - | + if [[ $(git rev-parse --is-shallow-repository) == true ]]; then + echo "Fetching full Git history..." + git fetch --unshallow + fi + + # Initialize Git submodules + - | + if [[ -f .gitmodules ]]; then + echo "Initializing Git submodules..." + git submodule update --init --recursive + fi + + # Install Node.js dependencies + - | + if [[ -f package-lock.json ]]; then + echo "Installing Node.js dependencies..." + npm ci + fi + build: + commands: + - echo "Building the project..." + - hugo build --gc --minify + artifacts: + baseDirectory: public + files: + - '**/*' + cache: + paths: + - .cache/hugo/**/* + ``` + +Step 2 +: In your project configuration, change the location of the image cache to the [`cacheDir`][] as shown below: + + {{< code-toggle file=hugo copy=true >}} + [caches.images] + dir = ':cacheDir/images' + {{< /code-toggle >}} + + See [configure file caches][] for more information. + +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. + +Step 5 +: Choose a source code provider, then press the **Next** button. + + ![screen capture](amplify-01.png) + +Step 6 +: Authorize AWS Amplify to access your GitHub account. + + ![screen capture](amplify-02.png) + +Step 7 +: Select your personal account or relevant organization. + + ![screen capture](amplify-03.png) + +Step 8 +: Authorize access to one or more repositories. + + ![screen capture](amplify-04.png) + +Step 9 +: Select a repository and branch, then press the **Next** button. + + ![screen capture](amplify-05.png) + +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. + + ![screen capture](amplify-06.png) + +[Amplify Console]: https://console.aws.amazon.com/amplify/apps +[`cacheDir`]: /configuration/all/#cachedir +[configure file caches]: /configuration/caches/ +[remote]: https://git-scm.com/docs/git-remote 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 new file mode 100644 index 0000000..3d61286 --- /dev/null +++ b/docs/content/en/host-and-deploy/host-on-azure-static-web-apps.md @@ -0,0 +1,13 @@ +--- +title: Host on Azure Static Web Apps +description: Host your project 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][]. + +[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/cloudflare-01.png b/docs/content/en/host-and-deploy/host-on-cloudflare/cloudflare-01.png new file mode 100644 index 0000000..5449f51 Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-cloudflare/cloudflare-01.png differ diff --git a/docs/content/en/host-and-deploy/host-on-cloudflare/cloudflare-02.png b/docs/content/en/host-and-deploy/host-on-cloudflare/cloudflare-02.png new file mode 100644 index 0000000..c2542b9 Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-cloudflare/cloudflare-02.png differ diff --git a/docs/content/en/host-and-deploy/host-on-cloudflare/cloudflare-03.png b/docs/content/en/host-and-deploy/host-on-cloudflare/cloudflare-03.png new file mode 100644 index 0000000..ba3daed Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-cloudflare/cloudflare-03.png differ diff --git a/docs/content/en/host-and-deploy/host-on-cloudflare/cloudflare-04.png b/docs/content/en/host-and-deploy/host-on-cloudflare/cloudflare-04.png new file mode 100644 index 0000000..12fa0cb Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-cloudflare/cloudflare-04.png differ diff --git a/docs/content/en/host-and-deploy/host-on-cloudflare/cloudflare-05.png b/docs/content/en/host-and-deploy/host-on-cloudflare/cloudflare-05.png new file mode 100644 index 0000000..d907844 Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-cloudflare/cloudflare-05.png differ diff --git a/docs/content/en/host-and-deploy/host-on-cloudflare/cloudflare-06.png b/docs/content/en/host-and-deploy/host-on-cloudflare/cloudflare-06.png new file mode 100644 index 0000000..246723b Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-cloudflare/cloudflare-06.png differ diff --git a/docs/content/en/host-and-deploy/host-on-cloudflare/cloudflare-07.png b/docs/content/en/host-and-deploy/host-on-cloudflare/cloudflare-07.png new file mode 100644 index 0000000..6cfe038 Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-cloudflare/cloudflare-07.png differ diff --git a/docs/content/en/host-and-deploy/host-on-cloudflare/index.md b/docs/content/en/host-and-deploy/host-on-cloudflare/index.md new file mode 100644 index 0000000..e2e786b --- /dev/null +++ b/docs/content/en/host-and-deploy/host-on-cloudflare/index.md @@ -0,0 +1,277 @@ +--- +title: Host on Cloudflare +description: Host your project on Cloudflare. +categories: [] +keywords: [] +--- + +Use these instructions to enable continuous deployment from a GitHub repository. The same general steps apply for other Git providers such as GitLab or Bitbucket. + +{{% include "/_common/gitignore-public.md" %}} + +## Prerequisites + +Please complete the following tasks before continuing: + +1. [Create](https://dash.cloudflare.com/sign-up) a Cloudflare account. +1. [Log in](https://dash.cloudflare.com/login) to your Cloudflare account. +1. [Create](https://github.com/signup) a GitHub account. +1. [Log in](https://github.com/login) to your GitHub account. +1. [Create](https://github.com/new) a GitHub repository for your project. +1. [Create](https://git-scm.com/docs/git-init) a local Git repository for your project with a [remote][] reference to your GitHub repository. +1. Create a Hugo project within your local Git repository and test it with the `hugo server` command. +1. Commit the changes to your local Git repository and push to your GitHub repository. + +## Procedure + +Step 1 +: Create a `wrangler.jsonc` file in the root of your project. + + ```jsonc {file="wrangler.jsonc" copy=true} + { + // Set this to the name of your project. + "name": "test", + // Set this to today's date in YYYY-MM-DD format. + "compatibility_date": "2026-06-19", + "build": { + "command": "chmod a+x build.sh && ./build.sh" + }, + "assets": { + "directory": "./public", + "not_found_handling": "404-page" + } + } + ``` + +Step 2 +: Create a `build.sh` file in the root of your project, adjusting the tool versions and time zone as needed. + + ```sh {file="build.sh" copy=true} + #!/usr/bin/env bash + + #------------------------------------------------------------------------------ + # @file + # Builds a Hugo project hosted on a Cloudflare Worker. + #------------------------------------------------------------------------------ + + # Exit on error, undefined variables, or pipe failures + set -euo pipefail + + # Define tool versions + DART_SASS_VERSION=1.101.0 + GO_VERSION=1.26.4 + HUGO_VERSION=0.163.3 + NODE_VERSION=24.16.0 + + # Set the build time zone + TZ=Europe/Oslo + + # Set the build cache directory + HUGO_CACHEDIR="${PWD}/.cache/hugo" + + # Perform cleanup + cleanup() { + if [[ -n "${build_temp_dir:-}" && -d "${build_temp_dir}" ]]; then + rm -rf "${build_temp_dir}" + fi + } + + # Register the cleanup trap + trap cleanup EXIT SIGINT SIGTERM + + main() { + # Export the build time zone + export TZ + + # Export the build cache directory + export HUGO_CACHEDIR + + # Create a temporary directory for downloads + build_temp_dir=$(mktemp -d) + + # Create a local tools directory + mkdir -p "${HOME}/.local" + + # Install Dart Sass + echo "Installing Dart Sass ${DART_SASS_VERSION}..." + curl -sfL --output-dir "${build_temp_dir}" -O "https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz" + tar -C "${HOME}/.local" -xf "${build_temp_dir}/dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz" + export PATH="${HOME}/.local/dart-sass:${PATH}" + + # Install Go + if [[ -f "go.mod" ]]; then + echo "Installing Go ${GO_VERSION}..." + curl -sfL --output-dir "${build_temp_dir}" -O "https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz" + tar -C "${HOME}/.local" -xf "${build_temp_dir}/go${GO_VERSION}.linux-amd64.tar.gz" + export PATH="${HOME}/.local/go/bin:${PATH}" + fi + + # Install Hugo + echo "Installing Hugo ${HUGO_VERSION}..." + curl -sfL --output-dir "${build_temp_dir}" -O "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_linux-amd64.tar.gz" + mkdir -p "${HOME}/.local/hugo" + tar -C "${HOME}/.local/hugo" -xf "${build_temp_dir}/hugo_${HUGO_VERSION}_linux-amd64.tar.gz" + export PATH="${HOME}/.local/hugo:${PATH}" + + # Install Node.js + if [[ -f "package-lock.json" ]]; then + echo "Installing Node.js ${NODE_VERSION}..." + curl -sfL --output-dir "${build_temp_dir}" -O "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.gz" + tar -C "${HOME}/.local" -xf "${build_temp_dir}/node-v${NODE_VERSION}-linux-x64.tar.gz" + export PATH="${HOME}/.local/node-v${NODE_VERSION}-linux-x64/bin:${PATH}" + fi + + # Log tool versions + echo "Logging tool versions..." + command -v sass &> /dev/null && echo "Dart Sass: $(sass --version)" || echo "Dart Sass: not installed" + command -v go &> /dev/null && echo "Go: $(go version)" || echo "Go: not installed" + command -v hugo &> /dev/null && echo "Hugo: $(hugo version)" || echo "Hugo: not installed" + command -v node &> /dev/null && echo "Node.js: $(node --version)" || echo "Node.js: not installed" + + # Configure Git + echo "Configuring Git..." + git config --global core.quotepath false + + # Fetch full Git history + if [[ $(git rev-parse --is-shallow-repository) == true ]]; then + echo "Fetching full Git history..." + git fetch --unshallow + fi + + # Initialize Git submodules + if [[ -f .gitmodules ]]; then + echo "Initializing Git submodules..." + git submodule update --init --recursive + fi + + # Install Node.js dependencies + if [[ -f package-lock.json ]]; then + echo "Installing Node.js dependencies..." + npm ci + fi + + # Build the project + echo "Building the project..." + hugo build --gc --minify + } + + main "$@" + ``` + +Step 3 +: In your project configuration, change the location of the image cache to the [`cacheDir`][] as shown below: + + {{< code-toggle file=hugo copy=true >}} + [caches.images] + dir = ':cacheDir/images' + {{< /code-toggle >}} + + See [configure file caches][] for more information. + +Step 4 +: Commit the changes to your local Git repository and push to your GitHub repository. + +Step 5 +: In the upper right corner of the Cloudflare [dashboard][], press the **Add** button and select "Workers" from the drop down menu. + + ![screen capture](cloudflare-01.png) + +Step 6 +: Verify your account if prompted. + + ![screen capture](cloudflare-02.png) + +Step 7 +: On the "Create a Worker" page, under the "Ship something new" heading, press the **Connect GitHub** button. + + ![screen capture](cloudflare-03.png) + +Step 8 +: Select the GitHub account where you want to install the Cloudflare Workers and Pages application. + + ![screen capture](cloudflare-04.png) + +Step 9 +: Authorize the Cloudflare Workers and Pages application to access all repositories or only select repositories, then press the **Install & Authorize** button. + + ![screen capture](cloudflare-05.png) + +Step 10 +: On the "Create a Worker" page, under the "Select a repository" heading, select the repository to deploy, then press the **Next** button. + + ![screen capture](cloudflare-06.png) + +Step 11 +: On the "Create a Worker" page, under the "Set up your application" heading, perform the following steps: + + 1. Provide a **Project name**. + 1. Leave the **Build command** blank and ensure the **Deploy command** is `npx wrangler deploy`. + 1. Expand the **Advanced settings** panel. + 1. In the **Variable name** field, enter `SKIP_DEPENDENCY_INSTALL`. + 1. In the **Variable value** field, enter `true`. + 1. Press the **Deploy** button. + +Step 12 +: Wait for the site to build and deploy, then press the **Visit** button in the upper left corner of your screen. + + ![screen capture](cloudflare-07.png) + +In the future, whenever you push a change from your local Git repository, Cloudflare will rebuild and deploy your site. + +## Build cache + +The build script shown in [Step 2](#step-2) sets Hugo's [`cacheDir`][] to the path required by Cloudflare's build cache, which is disabled by default. To enable the Cloudflare build cache, you must complete two steps. + +First, your project must have both a `package.json` and `package-lock.json` file in the project root. If you have only a package.json file, run `npm install` to create the corresponding `package-lock.json` file. If your project does not require any Node.js packages, create both files by running `npm init -y && npm install`. + +Second, you must enable the build cache in your project dashboard. + +1. Navigate to Workers & Pages Overview on the [dashboard][]. +1. Find your Workers project. +1. Go to **Settings** > **Build** > **Build cache**. +1. Press the **Enable** button. + +## Scheduled builds + +If your site uses [`resources.GetRemote`][] to fetch external data at build time, that data is embedded in the static HTML when the site is built. Without a scheduled build, the data only refreshes when someone commits code to the repository. To keep content current, you can trigger a rebuild on a schedule by creating a Cloudflare deploy hook and calling it from a GitHub Actions workflow. + +Step 1 +: In the Cloudflare [dashboard][], go to **Workers & Pages**. Select your project, then navigate to **Settings** > **Builds** > **Deploy Hooks**. Press **Create deploy hook**, provide a name (e.g., `github-cron`), and copy the generated URL. + +Step 2 +: In your GitHub repository, go to **Settings** > **Secrets and variables** > **Actions**. Press **New repository secret**, name it `CLOUDFLARE_DEPLOY_HOOK`, paste the deploy hook URL as the value, and save. + +Step 3 +: Create a GitHub Actions workflow file in your repository. + + ```yaml {file=".github/workflows/scheduled-cloudflare-deploy.yaml" copy=true} + name: github-cron + on: + schedule: + - cron: "42 7 * * *" + timezone: Etc/UTC + + jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Trigger Cloudflare deploy hook + run: curl -X POST "${{ secrets.CLOUDFLARE_DEPLOY_HOOK }}" + ``` + + Adjust the [`cron`][] expression to set your desired build schedule. In the example above, the job is scheduled to run every day at 7:42 AM UTC. + +Step 4 +: Commit the changes to your local Git repository and push to your GitHub repository. + +> [!NOTE] +> The schedule event can be delayed during periods of high loads of GitHub Actions workflow runs. High load times include the start of every hour. If the load is sufficiently high enough, some queued jobs may be dropped. To decrease the chance of delay, schedule your workflow to run at a different time of the hour, or use a dedicated third-party scheduling service such as [Google Cloud Scheduler][] or [cron-job.org][]. + +[`cacheDir`]: /configuration/all/#cachedir +[`cron`]: https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#schedule +[`resources.GetRemote`]: /functions/resources/getremote/ +[configure file caches]: /configuration/caches/ +[cron-job.org]: https://cron-job.org/en/ +[dashboard]: https://dash.cloudflare.com/ +[remote]: https://git-scm.com/docs/git-remote +[Google Cloud Scheduler]: https://docs.cloud.google.com/scheduler/docs/overview diff --git a/docs/content/en/host-and-deploy/host-on-firebase.md b/docs/content/en/host-and-deploy/host-on-firebase.md new file mode 100644 index 0000000..4b92f9c --- /dev/null +++ b/docs/content/en/host-and-deploy/host-on-firebase.md @@ -0,0 +1,107 @@ +--- +title: Host on Firebase +description: Host your project on Firebase. +categories: [] +keywords: [] +aliases: [/hosting-and-deployment/hosting-on-firebase/] +--- + +## Assumptions + +1. You have an account with [Firebase][signup]. +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: + +1. Set up automatic builds and deploys with GitHub? + + Here you will be redirected to login in your GitHub account to get permissions. Confirm. + +1. 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. + +1. 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. + +1. 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 build && firebase deploy +``` + +With this you will have the app initialized manually. After that you can manage and fix your GitHub workflow from . + +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][] + +[Firebase CLI Reference]: https://firebase.google.com/docs/cli/#administrative_commands +[Quick Start]: /getting-started/quick-start/ +[console]: https://console.firebase.google.com/ +[signup]: https://console.firebase.google.com/ diff --git a/docs/content/en/host-and-deploy/host-on-github-pages/gh-pages-01.png b/docs/content/en/host-and-deploy/host-on-github-pages/gh-pages-01.png new file mode 100644 index 0000000..29912f2 Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-github-pages/gh-pages-01.png differ diff --git a/docs/content/en/host-and-deploy/host-on-github-pages/gh-pages-02.png b/docs/content/en/host-and-deploy/host-on-github-pages/gh-pages-02.png new file mode 100644 index 0000000..0050d33 Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-github-pages/gh-pages-02.png differ diff --git a/docs/content/en/host-and-deploy/host-on-github-pages/gh-pages-03.png b/docs/content/en/host-and-deploy/host-on-github-pages/gh-pages-03.png new file mode 100644 index 0000000..d2904ca Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-github-pages/gh-pages-03.png differ diff --git a/docs/content/en/host-and-deploy/host-on-github-pages/gh-pages-04.png b/docs/content/en/host-and-deploy/host-on-github-pages/gh-pages-04.png new file mode 100644 index 0000000..7577446 Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-github-pages/gh-pages-04.png differ diff --git a/docs/content/en/host-and-deploy/host-on-github-pages/gh-pages-05.png b/docs/content/en/host-and-deploy/host-on-github-pages/gh-pages-05.png new file mode 100644 index 0000000..efe2612 Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-github-pages/gh-pages-05.png 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 new file mode 100644 index 0000000..3ebeb11 --- /dev/null +++ b/docs/content/en/host-and-deploy/host-on-github-pages/index.md @@ -0,0 +1,233 @@ +--- +title: Host on GitHub Pages +description: Host your project on GitHub Pages. +categories: [] +keywords: [] +aliases: [/hosting-and-deployment/hosting-on-github/] +--- + +Use these instructions to enable continuous deployment from a GitHub repository to GitHub Pages. + +{{% include "/_common/gitignore-public.md" %}} + +## 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. + +## Prerequisites + +Please complete the following tasks before continuing: + +1. [Create](https://github.com/signup) a GitHub account. +1. [Log in][] to your GitHub account. +1. [Create](https://github.com/new) a GitHub repository for your project. +1. [Create](https://git-scm.com/docs/git-init) a local Git repository for your project with a [remote][] reference to your GitHub repository. +1. Create a Hugo project within your local Git repository and test it with the `hugo server` command. +1. Commit the changes to your local Git repository and push to your GitHub repository. + +## Procedure + +Step 1 +: Visit your GitHub repository. From the main menu choose **Settings** > **Pages**. In the center of your screen you will see this: + + ![screen capture](gh-pages-01.png) + + Change the **Source** to `GitHub Actions`. The change is immediate; you do not have to press a Save button. + + ![screen capture](gh-pages-02.png) + +Step 2 +: Create a `hugo.yaml` file in the `.github/workflows` directory, adjusting the tool versions and time zone as needed. + + ```yaml {file=".github/workflows/hugo.yaml" copy=true} + name: Build and deploy + on: + push: + branches: + - main + workflow_dispatch: + permissions: + contents: read + pages: write + id-token: write + concurrency: + group: pages + cancel-in-progress: false + defaults: + run: + shell: bash + jobs: + build: + runs-on: ubuntu-latest + env: + # Define tool versions + DART_SASS_VERSION: 1.101.0 + GO_VERSION: 1.26.4 + HUGO_VERSION: 0.163.3 + NODE_VERSION: 24.16.0 + + # Set the build time zone + TZ: Europe/Oslo + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + submodules: recursive + fetch-depth: 0 + + - name: Setup Pages + id: pages + uses: actions/configure-pages@v6 + + - name: Create a local tools directory + run: | + mkdir -p "${HOME}/.local" + + - name: Install Go + if: hashFiles('go.mod') != '' + uses: actions/setup-go@v6 + with: + go-version: ${{ env.GO_VERSION }} + cache: false + + - name: Install Node.js + if: hashFiles('package-lock.json') != '' + uses: actions/setup-node@v6 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Install Dart Sass + run: | + echo "Installing Dart Sass ${DART_SASS_VERSION}..." + curl -sfL --output-dir "${{ runner.temp }}" -O "https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz" + tar -C "${HOME}/.local" -xf "${{ runner.temp }}/dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz" + echo "${HOME}/.local/dart-sass" >> "${GITHUB_PATH}" + + - name: Install Hugo + run: | + echo "Installing Hugo ${HUGO_VERSION}..." + curl -sfL --output-dir "${{ runner.temp }}" -O "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz" + mkdir "${HOME}/.local/hugo" + tar -C "${HOME}/.local/hugo" -xf "${{ runner.temp }}/hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz" + echo "${HOME}/.local/hugo" >> "${GITHUB_PATH}" + + - name: Log tool versions + run: | + echo "Logging tool versions..." + command -v sass &> /dev/null && echo "Dart Sass: $(sass --version)" || echo "Dart Sass: not installed" + command -v go &> /dev/null && echo "Go: $(go version)" || echo "Go: not installed" + command -v hugo &> /dev/null && echo "Hugo: $(hugo version)" || echo "Hugo: not installed" + command -v node &> /dev/null && echo "Node.js: $(node --version)" || echo "Node.js: not installed" + + - name: Configure Git + run: | + echo "Configuring Git..." + git config --global core.quotepath false + + - name: Fetch full Git history + run: | + if [[ $(git rev-parse --is-shallow-repository) == true ]]; then + echo "Fetching full Git history..." + git fetch --unshallow + fi + + - name: Initialize Git submodules + run: | + if [[ -f .gitmodules ]]; then + echo "Initializing Git submodules..." + git submodule update --init --recursive + fi + + - name: Install Node.js dependencies + run: | + if [[ -f package-lock.json ]]; then + echo "Installing Node.js dependencies..." + npm ci + fi + + - name: Cache restore + id: cache-restore + uses: actions/cache/restore@v5 + with: + path: ${{ runner.temp }}/hugo_cache + key: hugo-${{ github.run_id }} + restore-keys: hugo- + + - name: Build + run: | + echo "Building the project..." + hugo build \ + --gc \ + --minify \ + --baseURL "${{ steps.pages.outputs.base_url }}/" \ + --cacheDir "${{ runner.temp }}/hugo_cache" + + - name: Cache save + uses: actions/cache/save@v5 + with: + path: ${{ runner.temp }}/hugo_cache + key: ${{ steps.cache-restore.outputs.cache-primary-key }} + + - name: Upload artifact + uses: actions/upload-pages-artifact@v5 + with: + include-hidden-files: false + path: ./public + deploy: + runs-on: ubuntu-latest + needs: build + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + uses: actions/deploy-pages@v5 + ``` + +Step 3 +: In your project configuration, change the location of the image cache to the [`cacheDir`][] as shown below: + + {{< code-toggle file=hugo copy=true >}} + [caches.images] + dir = ':cacheDir/images' + {{< /code-toggle >}} + + See [configure file caches][] for more information. + +Step 4 +: Commit the changes to your local Git repository and push to your GitHub repository. + +Step 5 +: From GitHub's main menu, choose **Actions**. You will see something like this: + + ![screen capture](gh-pages-03.png) + +Step 6 +: When GitHub has finished building and deploying your site, the color of the status indicator will change to green. + + ![screen capture](gh-pages-04.png) + +Step 7 +: Click on the commit message as shown above. Under the deploy step, you will see a link to your live site. + + ![screen capture](gh-pages-05.png) + +In the future, whenever you push a change from your local Git repository, GitHub Pages will rebuild and deploy your site. + +## Other resources + +- [Learn more about GitHub Actions][] +- [Caching dependencies to speed up workflows][] +- [Manage a custom domain for your GitHub Pages site][] + +[Caching dependencies to speed up workflows]: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows +[GitHub Pages documentation]: https://docs.github.com/en/pages/getting-started-with-github-pages/about-github-pages#types-of-github-pages-sites +[Learn more about GitHub Actions]: https://docs.github.com/en/actions +[Log in]: https://github.com/login +[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 +[`cacheDir`]: /configuration/all/#cachedir +[configure file caches]: /configuration/caches/ +[remote]: https://git-scm.com/docs/git-remote 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 new file mode 100644 index 0000000..3642fe5 --- /dev/null +++ b/docs/content/en/host-and-deploy/host-on-gitlab-pages.md @@ -0,0 +1,202 @@ +--- +title: Host on GitLab Pages +description: Host your project on GitLab Pages. +categories: [] +keywords: [] +aliases: [/hosting-and-deployment/hosting-on-gitlab/] +--- + +Use these instructions to enable continuous deployment from a GitLab repository to GitLab Pages. + +{{% include "/_common/gitignore-public.md" %}} + +## Prerequisites + +Please complete the following tasks before continuing: + +1. [Create](https://gitlab.com/users/sign_up) a GitLab account. +1. [Log in](https://gitlab.com/users/sign_in) to your GitLab account. +1. [Create](https://gitlab.com/projects/new) a GitLab repository for your project. +1. [Create](https://git-scm.com/docs/git-init) a local Git repository for your project with a [remote][] reference to your GitLab repository. +1. Create a Hugo project within your local Git repository and test it with the `hugo server` command. +1. Commit the changes to your local Git repository and push to your GitLab repository. + +## BaseURL + +The [`baseURL`][] in your project 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. + +## Procedure + +Step 1 +: Create a `.gitlab-ci.yml` file in the root of your project, adjusting the tool versions and time zone as needed. + + ```yaml {file=".gitlab-ci.yml" copy=true} + variables: + # Define tool versions + DART_SASS_VERSION: 1.101.0 + GO_VERSION: 1.26.4 + HUGO_VERSION: 0.163.3 + NODE_VERSION: 24.16.0 + + # Set the build timezone + TZ: Europe/Oslo + + # Set the build cache directory + HUGO_CACHEDIR: ${CI_PROJECT_DIR}/.cache/hugo + + # Set the repository clone and fetch strategy + GIT_DEPTH: 0 + GIT_STRATEGY: clone + GIT_SUBMODULE_STRATEGY: recursive + cache: + key: ${CI_COMMIT_REF_SLUG} + fallback_keys: + - ${CI_DEFAULT_BRANCH} + paths: + - .cache/hugo + image: + name: buildpack-deps:bookworm + pages: + stage: deploy + script: + - chmod a+x build.sh && ./build.sh + artifacts: + paths: + - public + rules: + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + ``` + +Step 2 +: Create a `build.sh` file in the root of your project. + + ```sh {file="build.sh" copy=true} + #!/usr/bin/env bash + + #------------------------------------------------------------------------------ + # @file + # Builds a Hugo project hosted on GitLab Pages. + #------------------------------------------------------------------------------ + + # Exit on error, undefined variables, or pipe failures + set -euo pipefail + + # Perform cleanup + cleanup() { + if [[ -n "${build_temp_dir:-}" && -d "${build_temp_dir}" ]]; then + rm -rf "${build_temp_dir}" + fi + } + + # Register the cleanup trap + trap cleanup EXIT SIGINT SIGTERM + + main() { + # Create a temporary directory for downloads + build_temp_dir=$(mktemp -d) + + # Create a local tools directory + mkdir -p "${HOME}/.local" + + # Install utilities + echo "Installing utilities..." + apt-get update > /dev/null + apt-get install -y brotli > /dev/null + + # Install Dart Sass + echo "Installing Dart Sass ${DART_SASS_VERSION}..." + curl -sfLO --output-dir "${build_temp_dir}" "https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz" + tar -C "${HOME}/.local" -xf "${build_temp_dir}/dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz" + export PATH="${HOME}/.local/dart-sass:${PATH}" + + # Install Go + if [[ -f "${CI_PROJECT_DIR}/go.mod" ]]; then + echo "Installing Go ${GO_VERSION}..." + curl -sfLO --output-dir "${build_temp_dir}" "https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz" + tar -C "${HOME}/.local" -xf "${build_temp_dir}/go${GO_VERSION}.linux-amd64.tar.gz" + export PATH="${HOME}/.local/go/bin:${PATH}" + fi + + # Install Hugo + echo "Installing Hugo ${HUGO_VERSION}..." + curl -sfLO --output-dir "${build_temp_dir}" "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_linux-amd64.tar.gz" + mkdir -p "${HOME}/.local/hugo" + tar -C "${HOME}/.local/hugo" -xf "${build_temp_dir}/hugo_${HUGO_VERSION}_linux-amd64.tar.gz" + export PATH="${HOME}/.local/hugo:${PATH}" + + # Install Node.js + if [[ -f "${CI_PROJECT_DIR}/package-lock.json" ]]; then + echo "Installing Node.js ${NODE_VERSION}..." + curl -sfLO --output-dir "${build_temp_dir}" "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.gz" + tar -C "${HOME}/.local" -xf "${build_temp_dir}/node-v${NODE_VERSION}-linux-x64.tar.gz" + export PATH="${HOME}/.local/node-v${NODE_VERSION}-linux-x64/bin:${PATH}" + fi + + # Log tool versions + echo "Logging tool versions..." + command -v sass &> /dev/null && echo "Dart Sass: $(sass --version)" || echo "Dart Sass: not installed" + command -v go &> /dev/null && echo "Go: $(go version)" || echo "Go: not installed" + command -v hugo &> /dev/null && echo "Hugo: $(hugo version)" || echo "Hugo: not installed" + command -v node &> /dev/null && echo "Node.js: $(node --version)" || echo "Node.js: not installed" + + # Configure Git + echo "Configuring Git..." + git config --global core.quotepath false + + # Fetch full Git history + if [[ $(git rev-parse --is-shallow-repository) == true ]]; then + echo "Fetching full Git history..." + git fetch --unshallow + fi + + # Initialize Git submodules + if [[ -f .gitmodules ]]; then + echo "Initializing Git submodules..." + git submodule update --init --recursive + fi + + # Install Node.js dependencies + if [[ -f package-lock.json ]]; then + echo "Installing Node.js dependencies..." + npm ci + fi + + # Build the project + echo "Building the project..." + hugo build --gc --minify + + # Compress published files + echo "Compressing published files..." + find public/ -type f -regextype posix-extended -regex '.+\.(cjs|css|html|js|json|mjs|svg|txt|xml)$' -print0 > "${build_temp_dir}/files.txt" + xargs --null --max-procs=0 --max-args=1 brotli --quality=10 --force --keep < "${build_temp_dir}/files.txt" + xargs --null --max-procs=0 --max-args=1 gzip -9 --force --keep < "${build_temp_dir}/files.txt" + } + + main "$@" + ``` + +Step 3 +: In your project configuration, change the location of the image cache to the [`cacheDir`][] as shown below: + + {{< code-toggle file=hugo copy=true >}} + [caches.images] + dir = ':cacheDir/images' + {{< /code-toggle >}} + + See [configure file caches][] for more information. + +Step 4 +: Commit the changes to your local Git repository and push to your GitLab repository. + +Step 5 +: From your GitLab repository, navigate to **Build** > **Pipelines** to follow the CI pipeline building your page. + +Step 6 +: When the pipeline has passed, your new website is available at `https://.gitlab.io//`. + +In the future, whenever you push a change from your local Git repository, GitLab Pages will rebuild and deploy your site. + +[`baseURL`]: /configuration/all/#baseurl +[`cacheDir`]: /configuration/all/#cachedir +[configure file caches]: /configuration/caches/ +[remote]: https://git-scm.com/docs/git-remote 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 new file mode 100644 index 0000000..0e0190e --- /dev/null +++ b/docs/content/en/host-and-deploy/host-on-netlify/index.md @@ -0,0 +1,134 @@ +--- +title: Host on Netlify +description: Host your project on Netlify. +categories: [] +keywords: [] +aliases: [/hosting-and-deployment/hosting-on-netlify/] +--- + +Use these instructions to enable continuous deployment from a GitHub repository. The same general steps apply for other Git providers such as GitLab or Bitbucket. + +{{% include "/_common/gitignore-public.md" %}} + +## Prerequisites + +Please complete the following tasks before continuing: + +1. [Create](https://app.netlify.com/signup) a Netlify account. +1. [Log in](https://app.netlify.com/login) to your Netlify account. +1. [Create](https://github.com/signup) a GitHub account. +1. [Log in](https://github.com/login) to your GitHub account. +1. [Create](https://github.com/new) a GitHub repository for your project. +1. [Create](https://git-scm.com/docs/git-init) a local Git repository for your project with a [remote][] reference to your GitHub repository. +1. Create a Hugo project within your local Git repository and test it with the `hugo server` command. +1. Commit the changes to your local Git repository and push to your GitHub repository. + +## Procedure + +Step 1 +: Create a `netlify.toml` file in the root of your project, adjusting the tool versions and time zone as needed. + + ```toml {file="netlify.toml" copy=true} + [build.environment] + GO_VERSION = "1.26.4" + HUGO_VERSION = "0.163.3" + NODE_VERSION = "24.16.0" + TZ = "Europe/Oslo" + + [build] + publish = "public" + command = """\ + git config --global core.quotepath false && \ + hugo build --gc --minify --baseURL "${URL}" + """ + ``` + + If your project requires Dart Sass to transpile Sass to CSS, set the `DART_SASS_VERSION` and include the Dart Sass installation in the build step. + + ```toml {file="netlify.toml" copy=true} + [build.environment] + DART_SASS_VERSION = "1.101.0" + GO_VERSION = "1.26.4" + HUGO_VERSION = "0.163.3" + NODE_VERSION = "24.16.0" + TZ = "Europe/Oslo" + + [build] + publish = "public" + command = """\ + curl -sfLO "https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz" && \ + tar -C "${HOME}/.local" -xf "dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz" && \ + rm "dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz" && \ + export PATH="${HOME}/.local/dart-sass:${PATH}" && \ + git config --global core.quotepath false && \ + hugo build --gc --minify --baseURL "${URL}" + """ + ``` + +Step 2 +: In your project configuration, change the location of the image cache to the [`cacheDir`][] as shown below: + + {{< code-toggle file=hugo copy=true >}} + [caches.images] + dir = ':cacheDir/images' + {{< /code-toggle >}} + + See [configure file caches][] for more information. + +Step 3 +: Commit the changes to your local Git repository and push to your GitHub repository. + +Step 4 +: In the upper right corner of the Netlify dashboard, press the **Add new project** button and select “Import an existing project". + + ![screen capture](netlify-01.png) + +Step 5 +: Connect to GitHub. + + ![screen capture](netlify-02.png) + +Step 6 +: Press the "Authorize Netlify" button to allow the Netlify application to access your GitHub account. + + ![screen capture](netlify-03.png) + +Step 7 +: Press the **Configure Netlify on GitHub** button. + + ![screen capture](netlify-04.png) + +Step 8 +: Select the GitHub account where you want to install the Netlify application. + + ![screen capture](netlify-05.png) + +Step 9 +: Authorize the Netlify application to access all repositories or only select repositories, then press the Install button. + + ![screen capture](netlify-06.png) + +Your browser will be redirected to the Netlify dashboard. + +Step 10 +: Click on the name of the repository you wish to import. + + ![screen capture](netlify-07.png) + +Step 11 +: On the "Review configuration" page, enter a project name, leave the settings at their default values, then press the **Deploy** button. + + ![screen capture](netlify-08.png) + + ![screen capture](netlify-09.png) + +Step 12 +: When the deployment completes, click on the link to your published site. + + ![screen capture](netlify-10.png) + +In the future, whenever you push a change from your local Git repository, Netlify will rebuild and deploy your site. + +[`cacheDir`]: /configuration/all/#cachedir +[configure file caches]: /configuration/caches/ +[remote]: https://git-scm.com/docs/git-remote diff --git a/docs/content/en/host-and-deploy/host-on-netlify/netlify-01.png b/docs/content/en/host-and-deploy/host-on-netlify/netlify-01.png new file mode 100644 index 0000000..5d09da9 Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-netlify/netlify-01.png differ diff --git a/docs/content/en/host-and-deploy/host-on-netlify/netlify-02.png b/docs/content/en/host-and-deploy/host-on-netlify/netlify-02.png new file mode 100644 index 0000000..0c76e1d Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-netlify/netlify-02.png differ diff --git a/docs/content/en/host-and-deploy/host-on-netlify/netlify-03.png b/docs/content/en/host-and-deploy/host-on-netlify/netlify-03.png new file mode 100644 index 0000000..5bfceab Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-netlify/netlify-03.png differ diff --git a/docs/content/en/host-and-deploy/host-on-netlify/netlify-04.png b/docs/content/en/host-and-deploy/host-on-netlify/netlify-04.png new file mode 100644 index 0000000..e059dca Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-netlify/netlify-04.png differ diff --git a/docs/content/en/host-and-deploy/host-on-netlify/netlify-05.png b/docs/content/en/host-and-deploy/host-on-netlify/netlify-05.png new file mode 100644 index 0000000..ffb8b98 Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-netlify/netlify-05.png differ diff --git a/docs/content/en/host-and-deploy/host-on-netlify/netlify-06.png b/docs/content/en/host-and-deploy/host-on-netlify/netlify-06.png new file mode 100644 index 0000000..0bf5a29 Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-netlify/netlify-06.png differ diff --git a/docs/content/en/host-and-deploy/host-on-netlify/netlify-07.png b/docs/content/en/host-and-deploy/host-on-netlify/netlify-07.png new file mode 100644 index 0000000..9842fac Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-netlify/netlify-07.png differ diff --git a/docs/content/en/host-and-deploy/host-on-netlify/netlify-08.png b/docs/content/en/host-and-deploy/host-on-netlify/netlify-08.png new file mode 100644 index 0000000..8d68747 Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-netlify/netlify-08.png differ diff --git a/docs/content/en/host-and-deploy/host-on-netlify/netlify-09.png b/docs/content/en/host-and-deploy/host-on-netlify/netlify-09.png new file mode 100644 index 0000000..5807f6b Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-netlify/netlify-09.png differ diff --git a/docs/content/en/host-and-deploy/host-on-netlify/netlify-10.png b/docs/content/en/host-and-deploy/host-on-netlify/netlify-10.png new file mode 100644 index 0000000..0577a46 Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-netlify/netlify-10.png differ diff --git a/docs/content/en/host-and-deploy/host-on-render/index.md b/docs/content/en/host-and-deploy/host-on-render/index.md new file mode 100644 index 0000000..68d8c8f --- /dev/null +++ b/docs/content/en/host-and-deploy/host-on-render/index.md @@ -0,0 +1,211 @@ +--- +title: Host on Render +description: Host your project on Render. +categories: [] +keywords: [] +aliases: [/hosting-and-deployment/hosting-on-render/] +--- + +Use these instructions to enable continuous deployment from a GitHub repository. The same general steps apply for other Git providers such as GitLab or Bitbucket. + +{{% include "/_common/gitignore-public.md" %}} + +## Prerequisites + +Please complete the following tasks before continuing: + +1. [Create](https://dashboard.render.com/register) a Render account. +1. [Log in](https://dashboard.render.com/login) to your Render account. +1. [Create](https://github.com/signup) a GitHub account. +1. [Log in](https://github.com/login) to your GitHub account. +1. [Create](https://github.com/new) a GitHub repository for your project. +1. [Create](https://git-scm.com/docs/git-init) a local Git repository for your project with a [remote][] reference to your GitHub repository. +1. Create a Hugo project within your local Git repository and test it with the `hugo server` command. +1. Commit the changes to your local Git repository and push to your GitHub repository. + +## Procedure + +Step 1 +: Create a `render.yaml` file in the root of your project, adjusting the tool versions and time zone as needed. + + ```yaml {file="render.yaml" copy=true} + services: + - type: web + name: hosting-render + repo: https://github.com/jmooring/hosting-render + runtime: static + buildCommand: chmod a+x build.sh && ./build.sh + staticPublishPath: public + envVars: + - key: DART_SASS_VERSION + value: 1.101.0 + - key: GO_VERSION + value: 1.26.4 + - key: HUGO_VERSION + value: 0.163.3 + - key: NODE_VERSION + value: 24.16.0 + - key: TZ + value: Europe/Oslo + ``` + +Step 2 +: Create a `build.sh` file in the root of your project. + + ```sh {file="build.sh" copy=true} + #!/usr/bin/env bash + + #------------------------------------------------------------------------------ + # @file + # Builds a Hugo project hosted on Render. + # + # Render automatically installs Node.js and any Node.js dependencies. + #------------------------------------------------------------------------------ + + # Exit on error, undefined variables, or pipe failures + set -euo pipefail + + # Set the build cache directory + HUGO_CACHEDIR="${PWD}/.cache/hugo" + + # Perform cleanup + cleanup() { + if [[ -n "${build_temp_dir:-}" && -d "${build_temp_dir}" ]]; then + rm -rf "${build_temp_dir}" + fi + } + + # Register the cleanup trap + trap cleanup EXIT SIGINT SIGTERM + + main() { + # Export the build cache directory + export HUGO_CACHEDIR + + # Create a temporary directory for downloads + build_temp_dir=$(mktemp -d) + + # Create a local tools directory + mkdir -p "${HOME}/.local" + + # Install Dart Sass + echo "Installing Dart Sass ${DART_SASS_VERSION}..." + curl -sfL --output-dir "${build_temp_dir}" -O "https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz" + tar -C "${HOME}/.local" -xf "${build_temp_dir}/dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz" + export PATH="${HOME}/.local/dart-sass:${PATH}" + + # Install Go + if [[ -f "go.mod" ]]; then + echo "Installing Go ${GO_VERSION}..." + curl -sfL --output-dir "${build_temp_dir}" -O "https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz" + tar -C "${HOME}/.local" -xf "${build_temp_dir}/go${GO_VERSION}.linux-amd64.tar.gz" + export PATH="${HOME}/.local/go/bin:${PATH}" + fi + + # Install Hugo + echo "Installing Hugo ${HUGO_VERSION}..." + curl -sfL --output-dir "${build_temp_dir}" -O "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_linux-amd64.tar.gz" + mkdir -p "${HOME}/.local/hugo" + tar -C "${HOME}/.local/hugo" -xf "${build_temp_dir}/hugo_${HUGO_VERSION}_linux-amd64.tar.gz" + export PATH="${HOME}/.local/hugo:${PATH}" + + # Log tool versions + echo "Logging tool versions..." + command -v sass &> /dev/null && echo "Dart Sass: $(sass --version)" || echo "Dart Sass: not installed" + command -v go &> /dev/null && echo "Go: $(go version)" || echo "Go: not installed" + command -v hugo &> /dev/null && echo "Hugo: $(hugo version)" || echo "Hugo: not installed" + command -v node &> /dev/null && echo "Node.js: $(node --version)" || echo "Node.js: not installed" + + # Configure Git + echo "Configuring Git..." + git config --global core.quotepath false + + # Fetch full Git history + if [[ $(git rev-parse --is-shallow-repository) == true ]]; then + echo "Fetching full Git history..." + git fetch --unshallow + fi + + # Initialize Git submodules + if [[ -f .gitmodules ]]; then + echo "Initializing Git submodules..." + git submodule update --init --recursive + fi + + # Build the project + echo "Building the project..." + hugo build --gc --minify + } + + main "$@" + ``` + +Step 3 +: In your project configuration, change the location of the image cache to the [`cacheDir`][] as shown below: + + {{< code-toggle file=hugo copy=true >}} + [caches.images] + dir = ':cacheDir/images' + {{< /code-toggle >}} + + See [configure file caches][] for more information. + +Step 4 +: Commit the changes to your local Git repository and push to your GitHub repository. + +Step 5 +: On the Render [dashboard][], press the **Add new** button and select "Blueprint" from the drop-down menu. + + ![screen capture](render-01.png) + +Step 6 +: Press the **GitHub** button to connect to your GitHub account. + + ![screen capture](render-02.png) + +Step 7 +: Press the **Authorize Render** button to allow the Render application to access your GitHub account. + + ![screen capture](render-03.png) + +Step 8 +: Select the GitHub account where you want to install the Render application. + + ![screen capture](render-04.png) + +Step 9 +: Authorize the Render application to access all repositories or only select repositories, then press the **Install** button. + + ![screen capture](render-05.png) + +Step 10 +: On the "Create a new Blueprint Instance in My Workspace" page, press the **Connect** button to the right of the name of your GitHub repository. + + ![screen capture](render-06.png) + +Step 11 +: Enter a unique name for your Blueprint, then press the **Deploy Blueprint** button at the bottom of the page. + + ![screen capture](render-07.png) + +Step 12 +: Wait for the site to build and deploy, then click on the "Resources" link on the left side of the page. + + ![screen capture](render-08.png) + +Step 13 +: Click on the link to the static site resource. + + ![screen capture](render-09.png) + +Step 14 +: Click on the link to your published site. + + ![screen capture](render-10.png) + +In the future, whenever you push a change from your local Git repository, Render will rebuild and deploy your site. + +[`cacheDir`]: /configuration/all/#cachedir +[configure file caches]: /configuration/caches/ +[dashboard]: https://dashboard.render.com/ +[remote]: https://git-scm.com/docs/git-remote diff --git a/docs/content/en/host-and-deploy/host-on-render/render-01.png b/docs/content/en/host-and-deploy/host-on-render/render-01.png new file mode 100644 index 0000000..b325a67 Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-render/render-01.png differ diff --git a/docs/content/en/host-and-deploy/host-on-render/render-02.png b/docs/content/en/host-and-deploy/host-on-render/render-02.png new file mode 100644 index 0000000..aa87535 Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-render/render-02.png differ diff --git a/docs/content/en/host-and-deploy/host-on-render/render-03.png b/docs/content/en/host-and-deploy/host-on-render/render-03.png new file mode 100644 index 0000000..731d8ff Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-render/render-03.png differ diff --git a/docs/content/en/host-and-deploy/host-on-render/render-04.png b/docs/content/en/host-and-deploy/host-on-render/render-04.png new file mode 100644 index 0000000..94e5ea8 Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-render/render-04.png differ diff --git a/docs/content/en/host-and-deploy/host-on-render/render-05.png b/docs/content/en/host-and-deploy/host-on-render/render-05.png new file mode 100644 index 0000000..0570709 Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-render/render-05.png differ diff --git a/docs/content/en/host-and-deploy/host-on-render/render-06.png b/docs/content/en/host-and-deploy/host-on-render/render-06.png new file mode 100644 index 0000000..7b00761 Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-render/render-06.png differ diff --git a/docs/content/en/host-and-deploy/host-on-render/render-07.png b/docs/content/en/host-and-deploy/host-on-render/render-07.png new file mode 100644 index 0000000..2a645d1 Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-render/render-07.png differ diff --git a/docs/content/en/host-and-deploy/host-on-render/render-08.png b/docs/content/en/host-and-deploy/host-on-render/render-08.png new file mode 100644 index 0000000..1dc75ae Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-render/render-08.png differ diff --git a/docs/content/en/host-and-deploy/host-on-render/render-09.png b/docs/content/en/host-and-deploy/host-on-render/render-09.png new file mode 100644 index 0000000..48e9404 Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-render/render-09.png differ diff --git a/docs/content/en/host-and-deploy/host-on-render/render-10.png b/docs/content/en/host-and-deploy/host-on-render/render-10.png new file mode 100644 index 0000000..4df0dcf Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-render/render-10.png differ 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 new file mode 100644 index 0000000..5d7a372 --- /dev/null +++ b/docs/content/en/host-and-deploy/host-on-sourcehut-pages.md @@ -0,0 +1,113 @@ +--- +title: Host on SourceHut Pages +description: Host your project on SourceHut Pages. +categories: [] +keywords: [] +aliases: [/hosting-and-deployment/hosting-on-sourcehut/] +--- + +Use these instructions to host your site on SourceHut Pages using either manual deployment or the SourceHut build system. + +{{% include "/_common/gitignore-public.md" %}} + +## Prerequisites + +- 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 + +Any and all mentions of `` refer to your actual SourceHut username and must be substituted accordingly. + +## BaseURL + +The [`baseURL`][] in your project 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. + +## 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: + +```sh +hugo build +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: + +```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 build + tar -C public -cvz . > ../site.tar.gz +- upload: | + hut pages publish -d $site site.tar.gz +``` + +If your site requires [Dart Sass][] to transpile Sass to CSS, set the DART_SASS_VERSION to the [latest version number][] and include the Dart Sass installation lines before running the Hugo build step. Note that for Alpine, the `linux-x64-musl` version is used. + +```yaml {file=".build.yml" copy=true} +image: alpine/edge +packages: + - hugo + - hut + - curl # For Dart Sass installation +oauth: pages.sr.ht/PAGES:RW +environment: + site: .srht.site +tasks: +- package: | + DART_SASS_VERSION=1.101.0 + mkdir -p $HOME/.local + curl -L https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/dart-sass-${DART_SASS_VERSION}-linux-x64-musl.tar.gz -o dart-sass.tar.gz + tar -xzf dart-sass.tar.gz -C $HOME/.local + rm dart-sass.tar.gz + chmod -R +x $HOME/.local/dart-sass/src + export PATH="$HOME/.local/dart-sass:$PATH" + sass --version # Verify installation + cd $site + hugo build + tar -C public -cvz . > ../site.tar.gz +- upload: | + hut pages publish -d $site site.tar.gz +``` + +Create a repository titled `.srht.site` (or your custom domain, if applicable) and push your local project to the repository. + +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][] +- [SourceHut Builds user manual][] + +[Dart Sass]: https://gohugo.io/functions/css/sass/#dart-sass +[Git]: https://git-scm.com/ +[Mercurial]: https://www.mercurial-scm.org/ +[Quick Start]: /getting-started/quick-start/ +[SourceHut Builds user manual]: https://man.sr.ht/builds.sr.ht/ +[SourceHut Pages]: https://srht.site/ +[SourceHut account]: https://meta.sr.ht/login +[SourceHut personal access token]: https://meta.sr.ht/oauth2/personal-token +[`baseURL`]: /configuration/all/#baseurl +[build manifest]: https://man.sr.ht/builds.sr.ht/#build-manifests +[custom domain section]: https://srht.site/custom-domains +[hut]: https://sr.ht/~xenrox/hut/ +[latest version number]: https://github.com/sass/dart-sass/releases diff --git a/docs/content/en/host-and-deploy/host-on-vercel/index.md b/docs/content/en/host-and-deploy/host-on-vercel/index.md new file mode 100644 index 0000000..152c347 --- /dev/null +++ b/docs/content/en/host-and-deploy/host-on-vercel/index.md @@ -0,0 +1,223 @@ +--- +title: Host on Vercel +description: Host your project on Vercel. +categories: [] +keywords: [] +--- + +Use these instructions to enable continuous deployment from a GitHub repository. The same general steps apply for other Git providers such as GitLab or Bitbucket. + +{{% include "/_common/gitignore-public.md" %}} + +## Prerequisites + +Please complete the following tasks before continuing: + +1. [Create](https://vercel.com/signup) a Vercel account. +1. [Log in](https://vercel.com/login) to your Vercel account. +1. [Create](https://github.com/signup) a GitHub account. +1. [Log in](https://github.com/login) to your GitHub account. +1. [Create](https://github.com/new) a GitHub repository for your project. +1. [Create](https://git-scm.com/docs/git-init) a local Git repository for your project with a [remote][] reference to your GitHub repository. +1. Create a Hugo project within your local Git repository and test it with the `hugo server` command. +1. Commit the changes to your local Git repository and push to your GitHub repository. + +## Procedure + +Step 1 +: Create a `vercel.json` file in the root of your project. + + ```json {file="vercel.json" copy=true} + { + "$schema": "https://openapi.vercel.sh/vercel.json", + "installCommand": "", + "buildCommand": "chmod a+x build.sh && ./build.sh", + "outputDirectory": "public" + } + ``` + +Step 2 +: Create a `build.sh` file in the root of your project, adjusting the tool versions and time zone as needed. + + ```sh {file="build.sh" copy=true} + #!/usr/bin/env bash + + #------------------------------------------------------------------------------ + # @file + # Builds a Hugo project hosted on Vercel. + #------------------------------------------------------------------------------ + + # Exit on error, undefined variables, or pipe failures + set -euo pipefail + + # Define tool versions + DART_SASS_VERSION=1.101.0 + GO_VERSION=1.26.4 + HUGO_VERSION=0.163.3 + NODE_VERSION=24.16.0 + + # Set the build time zone + TZ=Europe/Oslo + + # Set the build cache directory + HUGO_CACHEDIR="${PWD}/.vercel/cache/hugo" + + # Perform cleanup + cleanup() { + if [[ -n "${build_temp_dir:-}" && -d "${build_temp_dir}" ]]; then + rm -rf "${build_temp_dir}" + fi + } + + # Register the cleanup trap + trap cleanup EXIT SIGINT SIGTERM + + main() { + # Export the build time zone + export TZ + + # Export the build cache directory + export HUGO_CACHEDIR + + # Create a temporary directory for downloads + build_temp_dir=$(mktemp -d) + + # Create a local tools directory + mkdir -p "${HOME}/.local" + + # Install Dart Sass + echo "Installing Dart Sass ${DART_SASS_VERSION}..." + curl -sfL --output-dir "${build_temp_dir}" -O "https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz" + tar -C "${HOME}/.local" -xf "${build_temp_dir}/dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz" + export PATH="${HOME}/.local/dart-sass:${PATH}" + + # Install Go + if [[ -f "go.mod" ]]; then + echo "Installing Go ${GO_VERSION}..." + curl -sfL --output-dir "${build_temp_dir}" -O "https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz" + tar -C "${HOME}/.local" -xf "${build_temp_dir}/go${GO_VERSION}.linux-amd64.tar.gz" + export PATH="${HOME}/.local/go/bin:${PATH}" + fi + + # Install Hugo + echo "Installing Hugo ${HUGO_VERSION}..." + curl -sfL --output-dir "${build_temp_dir}" -O "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_linux-amd64.tar.gz" + mkdir -p "${HOME}/.local/hugo" + tar -C "${HOME}/.local/hugo" -xf "${build_temp_dir}/hugo_${HUGO_VERSION}_linux-amd64.tar.gz" + export PATH="${HOME}/.local/hugo:${PATH}" + + # Install Node.js + if [[ -f "package-lock.json" ]]; then + echo "Installing Node.js ${NODE_VERSION}..." + curl -sfL --output-dir "${build_temp_dir}" -O "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.gz" + tar -C "${HOME}/.local" -xf "${build_temp_dir}/node-v${NODE_VERSION}-linux-x64.tar.gz" + export PATH="${HOME}/.local/node-v${NODE_VERSION}-linux-x64/bin:${PATH}" + fi + + # Log tool versions + echo "Logging tool versions..." + command -v sass &> /dev/null && echo "Dart Sass: $(sass --version)" || echo "Dart Sass: not installed" + command -v go &> /dev/null && echo "Go: $(go version)" || echo "Go: not installed" + command -v hugo &> /dev/null && echo "Hugo: $(hugo version)" || echo "Hugo: not installed" + command -v node &> /dev/null && echo "Node.js: $(node --version)" || echo "Node.js: not installed" + + # Configure Git + echo "Configuring Git..." + git config --global core.quotepath false + + # Fetch full Git history + if [[ $(git rev-parse --is-shallow-repository) == true ]]; then + echo "Fetching full Git history..." + git fetch --unshallow + fi + + # Initialize Git submodules + if [[ -f .gitmodules ]]; then + echo "Initializing Git submodules..." + git submodule update --init --recursive + fi + + # Install Node.js dependencies + if [[ -f package-lock.json ]]; then + echo "Installing Node.js dependencies..." + npm ci + fi + + # Build the project + echo "Building the project..." + hugo build --gc --minify + } + + main "$@" + ``` + +Step 3 +: In your project configuration, change the location of the image cache to the [`cacheDir`][] as shown below: + + {{< code-toggle file=hugo copy=true >}} + [caches.images] + dir = ':cacheDir/images' + {{< /code-toggle >}} + + See [configure file caches][] for more information. + +Step 4 +: Commit the changes to your local Git repository and push to your GitHub repository. + +Step 5 +: In the upper right corner of the Vercel dashboard, press the **Add New** button and select "Project" from the drop down menu. + + ![screen capture](vercel-01.png) + +Step 6 +: Press the "Continue with GitHub" button. + + ![screen capture](vercel-02.png) + +Step 7 +: Press the **Authorize Vercel** button to allow the Vercel application to access your GitHub account. + + ![screen capture](vercel-03.png) + +Step 8 +: Press the **Install** button to install the Vercel application. + + ![screen capture](vercel-04.png) + +Step 9 +: Select the GitHub account where you want to install the Vercel application. + + ![screen capture](vercel-05.png) + +Step 10 +: Authorize the Vercel application to access all repositories or only select repositories, then press the **Install** button. + + ![screen capture](vercel-06.png) + + Your browser will be redirected to the Cloudflare dashboard. + +Step 11 +: Press the **Import** button to the right of the name of your GitHub repository. + + ![screen capture](vercel-07.png) + +Step 12 +: On the "New Project" page, leave the settings at their default values and press the **Deploy** button. + + ![screen capture](vercel-08.png) + +Step 13 +: When the deployment completes, press the **Continue to Dashboard" button at the bottom of the page. + + ![screen capture](vercel-09.png) + +Step 14 +: On the "Production Deployment" page, click on the link to your published site. + + ![screen capture](vercel-10.png) + +In the future, whenever you push a change from your local Git repository, Vercel will rebuild and deploy your site. + +[`cacheDir`]: /configuration/all/#cachedir +[configure file caches]: /configuration/caches/ +[remote]: https://git-scm.com/docs/git-remote diff --git a/docs/content/en/host-and-deploy/host-on-vercel/vercel-01.png b/docs/content/en/host-and-deploy/host-on-vercel/vercel-01.png new file mode 100644 index 0000000..dd0215d Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-vercel/vercel-01.png differ diff --git a/docs/content/en/host-and-deploy/host-on-vercel/vercel-02.png b/docs/content/en/host-and-deploy/host-on-vercel/vercel-02.png new file mode 100644 index 0000000..ef3a788 Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-vercel/vercel-02.png differ diff --git a/docs/content/en/host-and-deploy/host-on-vercel/vercel-03.png b/docs/content/en/host-and-deploy/host-on-vercel/vercel-03.png new file mode 100644 index 0000000..a86ba36 Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-vercel/vercel-03.png differ diff --git a/docs/content/en/host-and-deploy/host-on-vercel/vercel-04.png b/docs/content/en/host-and-deploy/host-on-vercel/vercel-04.png new file mode 100644 index 0000000..cbd0388 Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-vercel/vercel-04.png differ diff --git a/docs/content/en/host-and-deploy/host-on-vercel/vercel-05.png b/docs/content/en/host-and-deploy/host-on-vercel/vercel-05.png new file mode 100644 index 0000000..4ae341a Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-vercel/vercel-05.png differ diff --git a/docs/content/en/host-and-deploy/host-on-vercel/vercel-06.png b/docs/content/en/host-and-deploy/host-on-vercel/vercel-06.png new file mode 100644 index 0000000..3b34cd4 Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-vercel/vercel-06.png differ diff --git a/docs/content/en/host-and-deploy/host-on-vercel/vercel-07.png b/docs/content/en/host-and-deploy/host-on-vercel/vercel-07.png new file mode 100644 index 0000000..ec4355b Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-vercel/vercel-07.png differ diff --git a/docs/content/en/host-and-deploy/host-on-vercel/vercel-08.png b/docs/content/en/host-and-deploy/host-on-vercel/vercel-08.png new file mode 100644 index 0000000..c7fa13d Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-vercel/vercel-08.png differ diff --git a/docs/content/en/host-and-deploy/host-on-vercel/vercel-09.png b/docs/content/en/host-and-deploy/host-on-vercel/vercel-09.png new file mode 100644 index 0000000..35ae557 Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-vercel/vercel-09.png differ diff --git a/docs/content/en/host-and-deploy/host-on-vercel/vercel-10.png b/docs/content/en/host-and-deploy/host-on-vercel/vercel-10.png new file mode 100644 index 0000000..5fdd0aa Binary files /dev/null and b/docs/content/en/host-and-deploy/host-on-vercel/vercel-10.png differ diff --git a/docs/content/en/hugo-modules/_index.md b/docs/content/en/hugo-modules/_index.md new file mode 100644 index 0000000..8dbf661 --- /dev/null +++ b/docs/content/en/hugo-modules/_index.md @@ -0,0 +1,8 @@ +--- +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 new file mode 100644 index 0000000..13687f3 --- /dev/null +++ b/docs/content/en/hugo-modules/introduction.md @@ -0,0 +1,19 @@ +--- +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: + + +: 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. + + +: A simple site used for testing. diff --git a/docs/content/en/hugo-modules/nodejs-dependencies.md b/docs/content/en/hugo-modules/nodejs-dependencies.md new file mode 100644 index 0000000..98384e3 --- /dev/null +++ b/docs/content/en/hugo-modules/nodejs-dependencies.md @@ -0,0 +1,61 @@ +--- +title: Node.js dependencies +description: How to manage Node dependencies in Hugo modules. +categories: [] +keywords: [] +weight: 40 +--- + +Modules that need Node packages (e.g. for Tailwind CSS) can declare those dependencies in a standard `package.json` at the module root. Hugo consolidates dependencies from all modules into an [npm workspace][], so you only need a single `npm install` at the project level. + +## Declaring dependencies + +Each module declares its Node dependencies in a `package.json` file in its root directory, using the standard `dependencies` and `devDependencies` fields. + + + +> [!NOTE] +> We improved this setup greatly in Hugo [v0.159.0][], but we kept the old `package.hugo.json` in the search path. Mostly to preserve as much backward compatibility as possible, but it may also be useful in some situations to reserve a separate set of Node dependencies for Hugo. + +## Consolidating with `hugo mod npm pack` + +Run [`hugo mod npm pack`][] to collect Node dependencies from all modules and write them to `packages/hugoautogen/package.json`. Hugo also adds a `workspaces` entry to your project's root `package.json` pointing to this auto-generated package. + +The resulting project structure: + +```tree +project/ +├── package.json # your project's package.json (updated with workspaces entry) +├── packages/ +│ └── hugoautogen/ +│ ├── package.json # auto-generated, contains consolidated module deps +│ └── hugo_packagemeta.json # metadata and checksums for staleness detection +└── ... +``` + + + +> [!NOTE] +In Hugo < v0.159.0 Hugo wrote the dependencies into your project's package.json, so if you have used `hugo mod npm pack` on your project using older Hugo versions, now is the time to do a spring cleaning of your project `package.json` file: Only direct Node dependencies needs to live in this file, all incoming dependencies from imported modules gets written to `packages/hugoautogen/package.json`. + +When merging, the **topmost version, starting from the project, take precedence**. If a module declares `tailwindcss@4.1` but your project already has `tailwindcss@4.0`, the project version wins and the module dependency is excluded from the generated workspace package. + +## Staleness detection + +When Hugo detects that the npm dependency configuration has changed in one or more of the modules in use, you will get a warning in the console: + +```text +WARN npm dependencies are out of sync, please run "hugo mod npm pack" (you may also want to run "npm install" after that) +``` + +This ensures you don't forget to re-run `hugo mod npm pack` after updating module versions. + +[`hugo mod npm pack`]: /commands/hugo_mod_npm_pack/ +[npm workspace]: https://docs.npmjs.com/cli/using-npm/workspaces +[v0.159.0]: https://github.com/gohugoio/hugo/releases/tag/v0.159.0 diff --git a/docs/content/en/hugo-modules/theme-components.md b/docs/content/en/hugo-modules/theme-components.md new file mode 100644 index 0000000..03891ed --- /dev/null +++ b/docs/content/en/hugo-modules/theme-components.md @@ -0,0 +1,35 @@ +--- +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 new file mode 100644 index 0000000..17b8527 --- /dev/null +++ b/docs/content/en/hugo-modules/use-modules.md @@ -0,0 +1,200 @@ +--- +title: Use modules +description: Use modules to manage the content, layout, presentation, and behavior of your site. +categories: [] +keywords: [] +weight: 20 +aliases: [/themes/usage/,/themes/installing/,/installing-and-using-themes/] +--- + +> [!NOTE] +> To work with modules you must install [Git][] and [Go][] 1.18 or later. + +## Introduction + +{{% glossary-term module %}} + +- Modules can be imported in any combination or sequence. +- Module imports are recursive; importing Module A can trigger the import of Module B, and so on. +- Modules can provide configuration files and directories, subject to the constraints described in the [merge configuration settings][] section of the documentation. +- External directories, including those from non-Hugo projects, can be mounted to create a [unified file system](g). + +## Import + +To import a module, first initialize the project itself as a module. For example: + +```sh +hugo mod init github.com/user/project +``` + +This will generate a [`go.mod`][] file in the project root. + +> [!NOTE] +> The module name is a unique identifier rather than a hosting requirement. Using a name like `github.com/user/project` is a common convention but it does not mean you must use Git or host your code on GitHub. You can use any name you like if you do not plan to have others import your project as a module. For example, you could use a simple name such as `my-project` when you run the initialization command. + +Then define one or more imports in your project configuration. This contrived example imports three modules, each containing custom shortcodes: + +{{< code-toggle file=hugo >}} +[module] + [[module.imports]] + path = 'shortcodes-a' + [[module.imports]] + path = '/home/user/shortcodes-b' + [[module.imports]] + path = 'github.com/user/shortcodes-c' +{{< /code-toggle >}} + +Import precedence is top-down. For example, if `shortcodes-a`, `shortcodes-b`, and `shortcodes-c` each define an `image` shortcode, the `image` shortcode from `shortcodes-a` will take effect. + +> [!NOTE] +> If multiple modules contain data files or [translation tables](g) with identical paths, the data is deeply merged, following top-down precedence. + +When you build your project, Hugo will: + +1. Download the modules +1. Cache them for future use +1. Generate a [`go.sum`][] file in the project root + +See [configuring module imports][] for details and options. + +## Update + +When you import a module, Hugo creates `go.mod` and `go.sum` files in your project root, storing version and checksum data. Clearing the module cache and rebuilding will re-download the originally imported module version, as specified in the `go.mod` file, ensuring consistent builds. Modules can be updated to other versions as needed. + +To update a module to the latest version: + +```sh +hugo mod get -u github.com/user/shortcodes-c +``` + +To update a module to a specific version: + +```sh +hugo mod get -u github.com/user/shortcodes-c@v0.42.0 +``` + +To update all modules to the latest version: + +```sh +hugo mod get -u +``` + +To recursively update all modules to the latest version: + +```sh +hugo mod get -u ./... +``` + +## Tidy + +To remove unused entries from the `go.mod` and `go.sum` files: + +```sh +hugo mod tidy +``` + +## Cache + +Hugo caches modules to avoid repeated downloads during site builds. By default, these are stored in the `modules` directory within the [`cacheDir`][]. + +To clean the module cache for the current project: + +```sh +hugo mod clean +``` + +To clean the module cache for all projects: + +```sh +hugo mod clean --all +``` + +For details on cache location and eviction, see [configuring file caches][]. + +## Vendor + +{{% glossary-term vendor %}} + +Vendoring a module provides the benefits described above and allows for local inspection of its [components](g). + +```sh +hugo mod vendor +``` + +This command creates a `_vendor` directory containing copies of all imported modules, used in subsequent builds. Note that: + +- The `hugo mod vendor` command can be run from any module tree level. +- Modules within the `themes` directory are not vendored. +- The `--ignoreVendorPaths` flag allows you to exclude vendored modules matching a [glob pattern](g) from specific commands. + +> [!IMPORTANT] +> Instead of modifying files directly within the `_vendor` directory, override them by creating a corresponding file with the same relative path in your project's root. + +To remove the vendored modules, delete the `_vendor` directory. + +## Replace + +For local module development, use a `replace` directive in `go.mod` pointing to your local directory: + +```text +replace github.com/user/module => /home/user/projects/module +``` + +With `hugo serve`r running, this change will trigger a configuration reload and add the local directory to the watch list. Alternatively, configure replacements by setting the [`replacements`][] parameter in your project configuration. + +## Workspace + +{{% glossary-term "workspace" %}} + +Workspaces simplify local development of sites with modules. Create a `.work` file to define a workspace, and activate it via the [`workspace`][] configuration setting or the `HUGO_MODULE_WORKSPACE` environment variable. + +A `.work` file example: + +```text +go 1.26 + +use . +use ../my-hugo-module +``` + +Use the `use` directive to list module paths, including the main project (`.`). Start the Hugo server with the workspace enabled: + +```sh +HUGO_MODULE_WORKSPACE=hugo.work hugo server --ignoreVendorPaths "**" +``` + +The `--ignoreVendorPaths` flag, used to ignore vendored dependencies (if applicable), enables live reloading of local edits within the workspace. + +## Graph + +To generate a [dependency graph](g), including vendoring, module replacement, and disabled module information, execute `hugo mod graph` within the target module directory. For example: + +```sh +$ 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 +``` + +## Mounts + +Imported modules automatically mount their component directories to Hugo's [unified file system](g). You can also manually mount any directory, including those from non-Hugo projects, to component directories. + +See [configuring module mounts][] for details. + +[Git]: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git +[Go]: https://go.dev/doc/install +[`cacheDir`]: /configuration/all/#cachedir +[`go.mod`]: https://go.dev/ref/mod#go-mod-file +[`go.sum`]: https://go.dev/ref/mod#go-sum-files +[`replacements`]: /configuration/module/#replacements +[`workspace`]: /configuration/module/#workspace +[configuring file caches]: /configuration/caches/ +[configuring module imports]: /configuration/module/#imports +[configuring module mounts]: /configuration/module/#mounts +[merge configuration settings]: /configuration/introduction/#merge-configuration-settings diff --git a/docs/content/en/hugo-pipes/_index.md b/docs/content/en/hugo-pipes/_index.md new file mode 100755 index 0000000..5102815 --- /dev/null +++ b/docs/content/en/hugo-pipes/_index.md @@ -0,0 +1,7 @@ +--- +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 new file mode 100755 index 0000000..335cc5c --- /dev/null +++ b/docs/content/en/hugo-pipes/bundling.md @@ -0,0 +1,9 @@ +--- +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 new file mode 100755 index 0000000..d38dfc1 --- /dev/null +++ b/docs/content/en/hugo-pipes/fingerprint.md @@ -0,0 +1,9 @@ +--- +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 new file mode 100755 index 0000000..4a2cbad --- /dev/null +++ b/docs/content/en/hugo-pipes/introduction.md @@ -0,0 +1,79 @@ +--- +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. + +## 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`][] +- [`resources.Get`][] +- [`resources.GetMatch`][] +- [`resources.Match`][] + +For remote resources, use: + +- [`resources.GetRemote`][] + +## Copy a resource + +See the [`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][]: + +```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. + +[Go Pipes]: /templates/introduction/#pipes +[`resources.ByType`]: /functions/resources/bytype/ +[`resources.Copy`]: /functions/resources/copy/ +[`resources.GetMatch`]: /functions/resources/getmatch/ +[`resources.GetRemote`]: /functions/resources/getremote/ +[`resources.Get`]: /functions/resources/get/ +[`resources.Match`]: /functions/resources/match/ +[mounted]: /configuration/module/#mounts +[page resources]: /content-management/page-resources/ diff --git a/docs/content/en/hugo-pipes/js.md b/docs/content/en/hugo-pipes/js.md new file mode 100644 index 0000000..461cc84 --- /dev/null +++ b/docs/content/en/hugo-pipes/js.md @@ -0,0 +1,11 @@ +--- +title: JavaScript +linkTitle: JavaScript building +description: Bundle, transpile, tree shake, code split, and minify JavaScript resources. +categories: [] +keywords: [] +--- + +See [JS functions][]. + +[JS functions]: /functions/js/ diff --git a/docs/content/en/hugo-pipes/minification.md b/docs/content/en/hugo-pipes/minification.md new file mode 100755 index 0000000..4ba1ea6 --- /dev/null +++ b/docs/content/en/hugo-pipes/minification.md @@ -0,0 +1,9 @@ +--- +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 new file mode 100755 index 0000000..1b47e8a --- /dev/null +++ b/docs/content/en/hugo-pipes/postcss.md @@ -0,0 +1,8 @@ +--- +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 new file mode 100755 index 0000000..72540fd --- /dev/null +++ b/docs/content/en/hugo-pipes/postprocess.md @@ -0,0 +1,8 @@ +--- +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 new file mode 100755 index 0000000..ed5eaff --- /dev/null +++ b/docs/content/en/hugo-pipes/resource-from-string.md @@ -0,0 +1,9 @@ +--- +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 new file mode 100755 index 0000000..bc50f63 --- /dev/null +++ b/docs/content/en/hugo-pipes/resource-from-template.md @@ -0,0 +1,9 @@ +--- +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 new file mode 100644 index 0000000..7569142 --- /dev/null +++ b/docs/content/en/hugo-pipes/transpile-sass-to-css.md @@ -0,0 +1,10 @@ +--- +title: ToCSS +linkTitle: Transpile Sass to CSS +description: Transpile Sass to CSS. +categories: [] +keywords: [] +aliases: [/hugo-pipes/transform-to-css/] +--- + +See the [`css.Sass`](/functions/css/sass) function. diff --git a/docs/content/en/installation/_index.md b/docs/content/en/installation/_index.md new file mode 100644 index 0000000..fdcb8f9 --- /dev/null +++ b/docs/content/en/installation/_index.md @@ -0,0 +1,8 @@ +--- +title: Installation +description: Install Hugo on macOS, Linux, Windows, BSD, and on any machine that can run the Go compiler tool chain. +categories: [] +keywords: [] +weight: 10 +aliases: [/getting-started/installing/] +--- diff --git a/docs/content/en/installation/bsd.md b/docs/content/en/installation/bsd.md new file mode 100644 index 0000000..f49b00c --- /dev/null +++ b/docs/content/en/installation/bsd.md @@ -0,0 +1,67 @@ +--- +title: BSD +description: Install Hugo on BSD derivatives. +categories: [] +keywords: [] +weight: 40 +--- + +{{% include "/_common/installation/01-editions.md" %}} + +{{% include "/_common/installation/02-prerequisites.md" %}} + +{{% include "/_common/installation/03-prebuilt-binaries.md" %}} + +## Repository packages + +Most BSD derivatives maintain a repository for commonly installed applications. Please note that these repositories may not contain the [latest release][]. + +### DragonFly BSD + +[DragonFly BSD][] includes Hugo in its package repository. To install the extended edition of Hugo: + +```sh +sudo pkg install gohugo +``` + +### FreeBSD + +[FreeBSD][] includes Hugo in its package repository. To install the extended edition of Hugo: + +```sh +sudo pkg install gohugo +``` + +### NetBSD + +[NetBSD][] includes Hugo in its package repository. To install the extended edition of Hugo: + +```sh +sudo pkgin install go-hugo +``` + +### 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 +``` + +{{% 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: + +[DragonFly BSD]: https://www.dragonflybsd.org/ +[FreeBSD]: https://www.freebsd.org/ +[NetBSD]: https://www.netbsd.org/ +[OpenBSD]: https://www.openbsd.org/ +[latest release]: https://github.com/gohugoio/hugo/releases/latest diff --git a/docs/content/en/installation/linux.md b/docs/content/en/installation/linux.md new file mode 100644 index 0000000..bce3eae --- /dev/null +++ b/docs/content/en/installation/linux.md @@ -0,0 +1,215 @@ +--- +title: Linux +description: Install Hugo on Linux. +categories: [] +keywords: [] +weight: 20 +--- + +{{% include "/_common/installation/01-editions.md" %}} + +{{% include "/_common/installation/02-prerequisites.md" %}} + +{{% include "/_common/installation/03-prebuilt-binaries.md" %}} + +## Package managers + +### Snap + +[Snap][] is a free and open-source package manager for Linux. Available for [most distributions][], snap packages are simple to install and are automatically updated. + +The Hugo snap package is [strictly confined][]. Strictly confined snaps run in complete isolation, up to a minimal access level that's deemed always safe. The sites you create and build must be located within your home directory, or on removable media. + +To install the extended edition of Hugo: + +```sh +sudo snap install hugo +``` + +To control automatic updates: + +```sh +# disable automatic updates +sudo snap refresh --hold hugo + +# enable automatic updates +sudo snap refresh --unhold hugo +``` + +To control access to removable media: + +```sh +# allow access +sudo snap connect hugo:removable-media + +# revoke access +sudo snap disconnect hugo:removable-media +``` + +To control access to SSH keys: + +```sh +# allow access +sudo snap connect hugo:ssh-keys + +# revoke access +sudo snap disconnect hugo:ssh-keys +``` + +{{% 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/ +[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/ +[Linux Karmada]: https://linuxkamarada.com/ +[Linux Lite]: https://www.linuxliteos.com/ +[Linux Mint]: https://linuxmint.com/ +[MX Linux]: https://mxlinux.org/ +[Manjaro]: https://manjaro.org/ +[Pop!_OS]: https://pop.system76.com/ +[Red Hat Enterprise Linux]: https://www.redhat.com/ +[Snap]: https://snapcraft.io/ +[Solus]: https://getsol.us/ +[USE]: https://packages.gentoo.org/packages/www-apps/hugo +[Ubuntu]: https://ubuntu.com/ +[Void Linux]: https://voidlinux.org/ +[Zorin OS]: https://zorin.com/os/ +[elementary OS]: https://elementary.io/ +[latest release]: https://github.com/gohugoio/hugo/releases/latest +[latest version]: https://github.com/gohugoio/hugo/releases/latest +[most distributions]: https://snapcraft.io/docs/installing-snapd +[openSUSE]: https://www.opensuse.org/ +[strictly confined]: https://snapcraft.io/docs/snap-confinement diff --git a/docs/content/en/installation/macos.md b/docs/content/en/installation/macos.md new file mode 100644 index 0000000..d784021 --- /dev/null +++ b/docs/content/en/installation/macos.md @@ -0,0 +1,42 @@ +--- +title: macOS +description: Install Hugo on macOS. +categories: [] +keywords: [] +weight: 10 +--- + +{{% include "/_common/installation/01-editions.md" %}} + +{{% include "/_common/installation/02-prerequisites.md" %}} + +{{% include "/_common/installation/03-prebuilt-binaries.md" %}} + +## Package managers + +{{% include "/_common/installation/homebrew.md" %}} + +### MacPorts + +[MacPorts][] is a free and open-source package manager for macOS. To install the extended edition of Hugo: + +```sh +sudo port install hugo +``` + +{{% 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. + +[MacPorts]: https://www.macports.org/ diff --git a/docs/content/en/installation/windows.md b/docs/content/en/installation/windows.md new file mode 100644 index 0000000..5165759 --- /dev/null +++ b/docs/content/en/installation/windows.md @@ -0,0 +1,153 @@ +--- +title: Windows +description: Install Hugo on Windows. +categories: [] +keywords: [] +weight: 30 +--- + +> [!NOTE] +> Hugo requires Windows 10, Windows Server 2016, or later. + +{{% include "/_common/installation/01-editions.md" %}} + +{{% include "/_common/installation/02-prerequisites.md" %}} + +{{% include "/_common/installation/03-prebuilt-binaries.md" %}} + +## Package managers + +### Chocolatey + +[Chocolatey][] is a free and open-source package manager for Windows. To install the extended edition of Hugo: + +```sh +choco install hugo-extended +``` + +### Scoop + +[Scoop][] is a free and open-source package manager for Windows. To install the extended edition of Hugo: + +```sh +scoop install hugo-extended +``` + +### Winget + +[Winget][] is Microsoft's official free and open-source package manager for Windows. To install the extended edition of Hugo: + +```sh +winget install Hugo.Hugo.Extended +``` + +To uninstall the extended edition of Hugo: + +```sh +winget uninstall --name "Hugo (Extended)" +``` + +## Build from source + +To build Hugo from source you must install: + +1. [Git][] +1. [Go][] version {{% current-go-version %}} or later + +> [!NOTE] +> The Bash-style `KEY=VALUE cmd` syntax used in the macOS and Linux build-from-source instructions does not work in PowerShell or Command Prompt. Use the code block matching your shell. + +### Standard edition + +To build and install the standard edition: + +PowerShell: + +```powershell +$env:CGO_ENABLED=0; go install github.com/gohugoio/hugo@latest +``` + +Command Prompt: + +```bat +set CGO_ENABLED=0 +go install github.com/gohugoio/hugo@latest +``` + +### Deploy edition + +{{< new-in v0.159.2 />}} + +To build and install the deploy edition: + +PowerShell: + +```powershell +$env:CGO_ENABLED=0; go install -tags withdeploy github.com/gohugoio/hugo@latest +``` + +Command Prompt: + +```bat +set CGO_ENABLED=0 +go install -tags withdeploy github.com/gohugoio/hugo@latest +``` + +### Extended edition + +To build and install the extended edition, first install a C compiler such as [GCC][] or [Clang][] and then run the following command: + +PowerShell: + +```powershell +$env:CGO_ENABLED=1; go install -tags extended github.com/gohugoio/hugo@latest +``` + +Command Prompt: + +```bat +set CGO_ENABLED=1 +go install -tags extended github.com/gohugoio/hugo@latest +``` + +### Extended/deploy edition + +To build and install the extended/deploy edition, first install a C compiler such as [GCC][] or [Clang][] and then run the following command: + +PowerShell: + +```powershell +$env:CGO_ENABLED=1; go install -tags extended,withdeploy github.com/gohugoio/hugo@latest +``` + +Command Prompt: + +```bat +set CGO_ENABLED=1 +go install -tags extended,withdeploy github.com/gohugoio/hugo@latest +``` + +> [!NOTE] +> See these [detailed instructions][] 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/ +[Clang]: https://clang.llvm.org/ +[GCC]: https://gcc.gnu.org/ +[Git]: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git +[Go]: https://go.dev/doc/install +[Scoop]: https://scoop.sh/ +[Winget]: https://learn.microsoft.com/en-us/windows/package-manager/ +[detailed instructions]: https://discourse.gohugo.io/t/41370 diff --git a/docs/content/en/methods/_index.md b/docs/content/en/methods/_index.md new file mode 100644 index 0000000..39f2b61 --- /dev/null +++ b/docs/content/en/methods/_index.md @@ -0,0 +1,8 @@ +--- +title: Methods +description: Use these methods within your templates. +categories: [] +keywords: [] +weight: 10 +aliases: ['/variables/'] +--- diff --git a/docs/content/en/methods/duration/Abs.md b/docs/content/en/methods/duration/Abs.md new file mode 100644 index 0000000..2e85797 --- /dev/null +++ b/docs/content/en/methods/duration/Abs.md @@ -0,0 +1,15 @@ +--- +title: Abs +description: Returns the absolute value of the given time.Duration value. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: time.Duration + signatures: [DURATION.Abs] +--- + +```go-html-template +{{ $d = time.ParseDuration "-3h" }} +{{ $d.Abs }} → 3h0m0s +``` diff --git a/docs/content/en/methods/duration/Hours.md b/docs/content/en/methods/duration/Hours.md new file mode 100644 index 0000000..2365551 --- /dev/null +++ b/docs/content/en/methods/duration/Hours.md @@ -0,0 +1,15 @@ +--- +title: Hours +description: Returns the time.Duration value as a floating point number of hours. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: float64 + signatures: [DURATION.Hours] +--- + +```go-html-template +{{ $d = time.ParseDuration "3.5h2.5m1.5s" }} +{{ $d.Hours }} → 3.5420833333333333 +``` diff --git a/docs/content/en/methods/duration/Microseconds.md b/docs/content/en/methods/duration/Microseconds.md new file mode 100644 index 0000000..c090316 --- /dev/null +++ b/docs/content/en/methods/duration/Microseconds.md @@ -0,0 +1,15 @@ +--- +title: Microseconds +description: Returns the time.Duration value as an integer microsecond count. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: int64 + signatures: [DURATION.Microseconds] +--- + +```go-html-template +{{ $d = time.ParseDuration "3.5h2.5m1.5s" }} +{{ $d.Microseconds }} → 12751500000 +``` diff --git a/docs/content/en/methods/duration/Milliseconds.md b/docs/content/en/methods/duration/Milliseconds.md new file mode 100644 index 0000000..288f369 --- /dev/null +++ b/docs/content/en/methods/duration/Milliseconds.md @@ -0,0 +1,15 @@ +--- +title: Milliseconds +description: Returns the time.Duration value as an integer millisecond count. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: int64 + signatures: [DURATION.Milliseconds] +--- + +```go-html-template +{{ $d = time.ParseDuration "3.5h2.5m1.5s" }} +{{ $d.Milliseconds }} → 12751500 +``` diff --git a/docs/content/en/methods/duration/Minutes.md b/docs/content/en/methods/duration/Minutes.md new file mode 100644 index 0000000..aec904f --- /dev/null +++ b/docs/content/en/methods/duration/Minutes.md @@ -0,0 +1,15 @@ +--- +title: Minutes +description: Returns the time.Duration value as a floating point number of minutes. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: float64 + signatures: [DURATION.Minutes] +--- + +```go-html-template +{{ $d = time.ParseDuration "3.5h2.5m1.5s" }} +{{ $d.Minutes }} → 212.525 +``` diff --git a/docs/content/en/methods/duration/Nanoseconds.md b/docs/content/en/methods/duration/Nanoseconds.md new file mode 100644 index 0000000..fd1b9e4 --- /dev/null +++ b/docs/content/en/methods/duration/Nanoseconds.md @@ -0,0 +1,15 @@ +--- +title: Nanoseconds +description: Returns the time.Duration value as an integer nanosecond count. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: int64 + signatures: [DURATION.Nanoseconds] +--- + +```go-html-template +{{ $d = time.ParseDuration "3.5h2.5m1.5s" }} +{{ $d.Nanoseconds }} → 12751500000000 +``` diff --git a/docs/content/en/methods/duration/Round.md b/docs/content/en/methods/duration/Round.md new file mode 100644 index 0000000..dfd0625 --- /dev/null +++ b/docs/content/en/methods/duration/Round.md @@ -0,0 +1,18 @@ +--- +title: Round +description: Returns the result of rounding DURATION1 to the nearest multiple of DURATION2. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: + signatures: [DURATION1.Round DURATION2] +--- + +```go-html-template +{{ $d = time.ParseDuration "3.5h2.5m1.5s" }} + +{{ $d.Round (time.ParseDuration "2h") }} → 4h0m0s +{{ $d.Round (time.ParseDuration "3m") }} → 3h33m0s +{{ $d.Round (time.ParseDuration "4s") }} → 3h32m32s +``` diff --git a/docs/content/en/methods/duration/Seconds.md b/docs/content/en/methods/duration/Seconds.md new file mode 100644 index 0000000..8b6d060 --- /dev/null +++ b/docs/content/en/methods/duration/Seconds.md @@ -0,0 +1,15 @@ +--- +title: Seconds +description: Returns the time.Duration value as a floating point number of seconds. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: float64 + signatures: [DURATION.Seconds] +--- + +```go-html-template +{{ $d = time.ParseDuration "3.5h2.5m1.5s" }} +{{ $d.Seconds }} → 12751.5 +``` diff --git a/docs/content/en/methods/duration/Truncate.md b/docs/content/en/methods/duration/Truncate.md new file mode 100644 index 0000000..5a785a7 --- /dev/null +++ b/docs/content/en/methods/duration/Truncate.md @@ -0,0 +1,18 @@ +--- +title: Truncate +description: Returns the result of rounding DURATION1 toward zero to a multiple of DURATION2. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: time.Duration + signatures: [DURATION1.Truncate DURATION2] +--- + +```go-html-template +{{ $d = time.ParseDuration "3.5h2.5m1.5s" }} + +{{ $d.Truncate (time.ParseDuration "2h") }} → 2h0m0s +{{ $d.Truncate (time.ParseDuration "3m") }} → 3h30m0s +{{ $d.Truncate (time.ParseDuration "4s") }} → 3h32m28s +``` diff --git a/docs/content/en/methods/duration/_index.md b/docs/content/en/methods/duration/_index.md new file mode 100644 index 0000000..4c690ec --- /dev/null +++ b/docs/content/en/methods/duration/_index.md @@ -0,0 +1,7 @@ +--- +title: Duration methods +linkTitle: Duration +description: Use these methods with a time.Duration value. +categories: [] +keywords: [] +--- diff --git a/docs/content/en/methods/menu-entry/Children.md b/docs/content/en/methods/menu-entry/Children.md new file mode 100644 index 0000000..16cec71 --- /dev/null +++ b/docs/content/en/methods/menu-entry/Children.md @@ -0,0 +1,66 @@ +--- +title: Children +description: Returns a collection of child menu entries, if any, under the given menu entry. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: navigation.Menu + signatures: [MENUENTRY.Children] +--- + +Use the `Children` method when rendering a nested menu. + +With this project configuration: + +{{< code-toggle file=hugo >}} +[[menus.main]] +name = 'Products' +pageRef = '/product' +weight = 10 + +[[menus.main]] +name = 'Product 1' +pageRef = '/products/product-1' +parent = 'Products' +weight = 1 + +[[menus.main]] +name = 'Product 2' +pageRef = '/products/product-2' +parent = 'Products' +weight = 2 +{{< /code-toggle >}} + +And this template: + +```go-html-template +
      + {{ range .Site.Menus.main }} +
    • + {{ .Name }} + {{ if .HasChildren }} + + {{ end }} +
    • + {{ end }} +
    +``` + +Hugo renders this HTML: + +```html + +``` diff --git a/docs/content/en/methods/menu-entry/HasChildren.md b/docs/content/en/methods/menu-entry/HasChildren.md new file mode 100644 index 0000000..59ef2e1 --- /dev/null +++ b/docs/content/en/methods/menu-entry/HasChildren.md @@ -0,0 +1,66 @@ +--- +title: HasChildren +description: Reports whether the given menu entry has child menu entries. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: bool + signatures: [MENUENTRY.HasChildren] +--- + +Use the `HasChildren` method when rendering a nested menu. + +With this project configuration: + +{{< code-toggle file=hugo >}} +[[menus.main]] +name = 'Products' +pageRef = '/product' +weight = 10 + +[[menus.main]] +name = 'Product 1' +pageRef = '/products/product-1' +parent = 'Products' +weight = 1 + +[[menus.main]] +name = 'Product 2' +pageRef = '/products/product-2' +parent = 'Products' +weight = 2 +{{< /code-toggle >}} + +And this template: + +```go-html-template +
      + {{ range .Site.Menus.main }} +
    • + {{ .Name }} + {{ if .HasChildren }} + + {{ end }} +
    • + {{ end }} +
    +``` + +Hugo renders this HTML: + +```html + +``` diff --git a/docs/content/en/methods/menu-entry/Identifier.md b/docs/content/en/methods/menu-entry/Identifier.md new file mode 100644 index 0000000..97b8bef --- /dev/null +++ b/docs/content/en/methods/menu-entry/Identifier.md @@ -0,0 +1,41 @@ +--- +title: Identifier +description: Returns the `identifier` property of the given menu entry. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [MENUENTRY.Identifier] +--- + +The `Identifier` method returns the `identifier` property of the menu entry. If you define the menu entry [automatically][], it returns the page's section. + +{{< code-toggle file=hugo >}} +[[menus.main]] +identifier = 'about' +name = 'About' +pageRef = '/about' +weight = 10 + +[[menus.main]] +identifier = 'contact' +name = 'Contact' +pageRef = '/contact' +weight = 20 +{{< /code-toggle >}} + +This example uses the `Identifier` method when querying the translation table on a multilingual project, falling back the `name` property if a matching key in the translation table does not exist: + +```go-html-template + +``` + +> [!NOTE] +> In the menu definition above, note that the `identifier` property is only required when two or more menu entries have the same name, or when localizing the name using translation tables. + +[automatically]: /content-management/menus/#define-automatically diff --git a/docs/content/en/methods/menu-entry/KeyName.md b/docs/content/en/methods/menu-entry/KeyName.md new file mode 100644 index 0000000..6a4382b --- /dev/null +++ b/docs/content/en/methods/menu-entry/KeyName.md @@ -0,0 +1,39 @@ +--- +title: KeyName +description: Returns the `identifier` property of the given menu entry, falling back to its `name` property. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [MENUENTRY.KeyName] +--- + +In this menu definition, the second entry does not contain an `identifier`, so the `Identifier` method returns its `name` property instead: + +{{< code-toggle file=hugo >}} +[[menus.main]] +identifier = 'about' +name = 'About' +pageRef = '/about' +weight = 10 + +[[menus.main]] +name = 'Contact' +pageRef = '/contact' +weight = 20 +{{< /code-toggle >}} + +This example uses the `KeyName` method when querying the translation table on a multilingual project, falling back the `name` property if a matching key in the translation table does not exist: + +```go-html-template + +``` + +In the example above, we need to pass the value returned by `.KeyName` through the [`strings.ToLower`][] function because the keys in the translation table are lowercase. + +[`strings.ToLower`]: /functions/strings/tolower/ diff --git a/docs/content/en/methods/menu-entry/Menu.md b/docs/content/en/methods/menu-entry/Menu.md new file mode 100644 index 0000000..a172629 --- /dev/null +++ b/docs/content/en/methods/menu-entry/Menu.md @@ -0,0 +1,22 @@ +--- +title: Menu +description: Returns the identifier of the menu that contains the given menu entry. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [MENUENTRY.Menu] +--- + +```go-html-template +{{ range .Site.Menus.main }} + {{ .Menu }} → main +{{ end }} +``` + +Use this method with the [`IsMenuCurrent`][] and [`HasMenuCurrent`][] methods on a `Page` object to set "active" and "ancestor" classes on a rendered entry. See [this example][]. + +[`HasMenuCurrent`]: /methods/page/hasmenucurrent/ +[`IsMenuCurrent`]: /methods/page/ismenucurrent/ +[this example]: /templates/menu/#example diff --git a/docs/content/en/methods/menu-entry/Name.md b/docs/content/en/methods/menu-entry/Name.md new file mode 100644 index 0000000..7b909d7 --- /dev/null +++ b/docs/content/en/methods/menu-entry/Name.md @@ -0,0 +1,28 @@ +--- +title: Name +description: Returns the `name` property of the given menu entry. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [MENUENTRY.Name] +--- + +If you define the menu entry [automatically][], the `Name` method returns the page's [`LinkTitle`][], falling back to its [`Title`][]. + +If you define the menu entry in [front matter][] or in your [project configuration][], the `Name` method returns the `name` property of the given menu entry. If the `name` is not defined, and the menu entry resolves to a page, the `Name` returns the page [`LinkTitle`][], falling back to its [`Title`][]. + +```go-html-template +
      + {{ range .Site.Menus.main }} +
    • {{ .Name }}
    • + {{ end }} +
    +``` + +[`LinkTitle`]: /methods/page/linktitle/ +[`Title`]: /methods/page/title/ +[automatically]: /content-management/menus/#define-automatically +[front matter]: /content-management/menus/#define-in-front-matter +[project configuration]: /content-management/menus/#define-in-project-configuration diff --git a/docs/content/en/methods/menu-entry/Page.md b/docs/content/en/methods/menu-entry/Page.md new file mode 100644 index 0000000..2689e06 --- /dev/null +++ b/docs/content/en/methods/menu-entry/Page.md @@ -0,0 +1,53 @@ +--- +title: Page +description: Returns the Page object associated with the given menu entry. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Page + signatures: [MENUENTRY.Page] +--- + +Regardless of how you [define menu entries][], an entry associated with a page has access to its [methods][]. + +In this menu definition, the first two entries are associated with a page, the last entry is not: + +{{< code-toggle file=hugo >}} +[[menus.main]] +pageRef = '/about' +weight = 10 + +[[menus.main]] +pageRef = '/contact' +weight = 20 + +[[menus.main]] +name = 'Hugo' +url = 'https://gohugo.io' +weight = 30 +{{< /code-toggle >}} + +In this example, if the menu entry is associated with a page, we use page's [`RelPermalink`][] and [`LinkTitle`][] when rendering the anchor element. + +If the entry is not associated with a page, we use its `url` and `name` properties. + +```go-html-template +
      + {{ range .Site.Menus.main }} + {{ with .Page }} +
    • {{ .Title }}
    • + {{ else }} +
    • {{ .Name }}
    • + {{ end }} + {{ end }} +
    +``` + +See the [menu templates][] section for more information. + +[`LinkTitle`]: /methods/page/linktitle/ +[`RelPermalink`]: /methods/page/relpermalink/ +[define menu entries]: /content-management/menus/ +[menu templates]: /templates/menu/#page-references +[methods]: /methods/page/ diff --git a/docs/content/en/methods/menu-entry/PageRef.md b/docs/content/en/methods/menu-entry/PageRef.md new file mode 100644 index 0000000..ce1800b --- /dev/null +++ b/docs/content/en/methods/menu-entry/PageRef.md @@ -0,0 +1,109 @@ +--- +title: PageRef +description: Returns the `pageRef` property of the given menu entry. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [MENUENTRY.PageRef] +--- + +> [!NOTE] +> The use case for this method is rare. +> In almost also scenarios you should use the [`URL`][] method instead. + +## Explanation + +If you specify a `pageRef` property when [defining a menu entry][] in your project configuration, Hugo looks for a matching page when rendering the entry. + +If a matching page is found: + +- The [`URL`][] method returns the page's relative permalink +- The [`Page`][] method returns the corresponding `Page` object +- The [`HasMenuCurrent`][] and [`IsMenuCurrent`][] methods on a `Page` object return the expected values + +If a matching page is not found: + +- The [`URL`][] method returns the entry's `url` property if set, else an empty string +- The [`Page`][] method returns nil +- The [`HasMenuCurrent`][] and [`IsMenuCurrent`][] methods on a `Page` object return `false` + +> [!NOTE] +> In almost also scenarios you should use the [`URL`][] method instead. + +## Example + +This example is contrived. + +> [!NOTE] +> In almost also scenarios you should use the [`URL`][] method instead. + +Consider this content structure: + +```tree +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"} +
      + {{ range .Site.Menus.main }} +
    • {{ .Name }}
    • + {{ end }} +
    +``` + +Hugo render this HTML: + +```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"} +
      + {{ range .Site.Menus.main }} +
    • {{ .Name }}
    • + {{ end }} +
    +``` + +Hugo renders this HTML: + +```html + +``` + +In the above note that Hugo populates the `href` attribute of the second `anchor` element with the `pageRef` property as defined in your project configuration because the template code falls back to the `PageRef` method. + +[`HasMenuCurrent`]: /methods/page/hasmenucurrent/ +[`IsMenuCurrent`]: /methods/page/ismenucurrent/ +[`Page`]: /methods/menu-entry/page/ +[`URL`]: /methods/menu-entry/url/ +[defining a menu entry]: /content-management/menus/#define-in-project-configuration diff --git a/docs/content/en/methods/menu-entry/Params.md b/docs/content/en/methods/menu-entry/Params.md new file mode 100644 index 0000000..c05149f --- /dev/null +++ b/docs/content/en/methods/menu-entry/Params.md @@ -0,0 +1,61 @@ +--- +title: Params +description: Returns the `params` property of the given menu entry. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: maps.Params + signatures: [MENUENTRY.Params] +--- + +When you define menu entries in your [project configuration][] or in [front matter][], you can include a `params` key to attach additional information to the entry. For example: + +{{< code-toggle file=hugo >}} +[[menus.main]] +name = 'About' +pageRef = '/about' +weight = 10 + +[[menus.main]] +name = 'Contact' +pageRef = '/contact' +weight = 20 + +[[menus.main]] +name = 'Hugo' +url = 'https://gohugo.io' +weight = 30 +[menus.main.params] + rel = 'external' +{{< /code-toggle >}} + +With this template: + +```go-html-template + +``` + +Hugo renders: + +```html + +``` + +See the [menu templates][] section for more information. + +[front matter]: /content-management/menus/#define-in-front-matter +[menu templates]: /templates/menu/#menu-entry-parameters +[project configuration]: /content-management/menus/ diff --git a/docs/content/en/methods/menu-entry/Parent.md b/docs/content/en/methods/menu-entry/Parent.md new file mode 100644 index 0000000..7c61752 --- /dev/null +++ b/docs/content/en/methods/menu-entry/Parent.md @@ -0,0 +1,50 @@ +--- +title: Parent +description: Returns the `parent` property of the given menu entry. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [MENUENTRY.Parent] +--- + +With this menu definition: + +{{< 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 >}} + +This template renders the nested menu, listing the `parent` property next each of the child entries: + +```go-html-template +
      + {{ range .Site.Menus.main }} +
    • + {{ .Name }} + {{ if .HasChildren }} +
        + {{ range .Children }} +
      • {{ .Name }} ({{ .Parent }})
      • + {{ end }} +
      + {{ end }} +
    • + {{ end }} +
    +``` diff --git a/docs/content/en/methods/menu-entry/Post.md b/docs/content/en/methods/menu-entry/Post.md new file mode 100644 index 0000000..2da8c38 --- /dev/null +++ b/docs/content/en/methods/menu-entry/Post.md @@ -0,0 +1,12 @@ +--- +title: Post +description: Returns the `post` property of the given menu entry. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: template.HTML + signatures: [MENUENTRY.Post] +--- + +{{% include "/_common/menu-entries/pre-and-post.md" %}} diff --git a/docs/content/en/methods/menu-entry/Pre.md b/docs/content/en/methods/menu-entry/Pre.md new file mode 100644 index 0000000..19af243 --- /dev/null +++ b/docs/content/en/methods/menu-entry/Pre.md @@ -0,0 +1,12 @@ +--- +title: Pre +description: Returns the `pre` property of the given menu entry. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: template.HTML + signatures: [MENUENTRY.Pre] +--- + +{{% include "/_common/menu-entries/pre-and-post.md" %}} diff --git a/docs/content/en/methods/menu-entry/Title.md b/docs/content/en/methods/menu-entry/Title.md new file mode 100644 index 0000000..e5512fd --- /dev/null +++ b/docs/content/en/methods/menu-entry/Title.md @@ -0,0 +1,22 @@ +--- +title: Title +description: Returns the `title` property of the given menu entry. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [MENUENTRY.Title] +--- + +The `Title` method returns the `title` property of the given menu entry. If the `title` is not defined, and the menu entry resolves to a page, the `Title` returns the page [`Title`][]. + +```go-html-template +
      + {{ range .Site.Menus.main }} +
    • {{ .Name }}
    • + {{ end }} +
    +``` + +[`RelPermalink`]: /methods/page/relpermalink/ diff --git a/docs/content/en/methods/menu-entry/Weight.md b/docs/content/en/methods/menu-entry/Weight.md new file mode 100644 index 0000000..6c95232 --- /dev/null +++ b/docs/content/en/methods/menu-entry/Weight.md @@ -0,0 +1,31 @@ +--- +title: Weight +description: Returns the `weight` property of the given menu entry. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: int + signatures: [MENUENTRY.Weight] +--- + +If you define the menu entry [automatically][], the `Weight` method returns the page's [`Weight`][]. + +If you define the menu entry in [front matter][] or in your [project configuration][], the `Weight` method returns the `weight` property, falling back to the page's `Weight`. + +In this contrived example, we limit the number of menu entries based on weight: + +```go-html-template +
      + {{ range .Site.Menus.main }} + {{ if le .Weight 42 }} +
    • {{ .Name }}
    • + {{ end }} + {{ end }} +
    +``` + +[`Weight`]: /methods/page/weight/ +[automatically]: /content-management/menus/#define-automatically +[front matter]: /content-management/menus/#define-in-front-matter +[project configuration]: /content-management/menus/#define-in-project-configuration diff --git a/docs/content/en/methods/menu-entry/_index.md b/docs/content/en/methods/menu-entry/_index.md new file mode 100644 index 0000000..129e9bc --- /dev/null +++ b/docs/content/en/methods/menu-entry/_index.md @@ -0,0 +1,7 @@ +--- +title: Menu entry methods +linkTitle: Menu entry +description: Use these methods in your menu templates. +categories: [] +keywords: [] +--- diff --git a/docs/content/en/methods/menu/ByName.md b/docs/content/en/methods/menu/ByName.md new file mode 100644 index 0000000..e8a262e --- /dev/null +++ b/docs/content/en/methods/menu/ByName.md @@ -0,0 +1,65 @@ +--- +title: ByName +description: Returns the given menu with its entries sorted by name. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: navigation.Menu + signatures: [MENU.ByName] +--- + +The `Sort` method returns the given menu with its entries sorted by `name`. + +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`: + +```go-html-template +
      + {{ range .Site.Menus.main.ByName }} +
    • {{ .Name }}
    • + {{ end }} +
    +``` + +Hugo renders this to: + +```html + +``` + +You can also sort menu entries using the [`sort`][] function. For example, to sort by `name` in descending order: + +```go-html-template +
      + {{ range sort .Site.Menus.main "Name" "desc" }} +
    • {{ .Name }}
    • + {{ end }} +
    +``` + +When using the sort function with menu entries, specify any of the following keys: `Identifier`, `Name`, `Parent`, `Post`, `Pre`, `Title`, `URL`, or `Weight`. + +[`sort`]: /functions/collections/sort/ diff --git a/docs/content/en/methods/menu/ByWeight.md b/docs/content/en/methods/menu/ByWeight.md new file mode 100644 index 0000000..5ccfce1 --- /dev/null +++ b/docs/content/en/methods/menu/ByWeight.md @@ -0,0 +1,71 @@ +--- +title: ByWeight +description: Returns the given menu with its entries sorted by weight, then by name, then by identifier. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: navigation.Menu + signatures: [MENU.ByWeight] +--- + +The `ByWeight` method returns the given menu with its entries sorted by [`weight`](g), then by `name`, then by `identifier`. This is the default sort order. + +Consider this menu definition: + +{{< code-toggle file=hugo >}} +[[menus.main]] +identifier = 'about' +name = 'About' +pageRef = '/about' +weight = 20 + +[[menus.main]] +identifier = 'services' +name = 'Services' +pageRef = '/services' +weight = 10 + +[[menus.main]] +identifier = 'contact' +name = 'Contact' +pageRef = '/contact' +weight = 30 +{{< /code-toggle >}} + +To sort the entries by `weight`, then by `name`, then by `identifier`: + +```go-html-template +
      + {{ range .Site.Menus.main.ByWeight }} +
    • {{ .Name }}
    • + {{ end }} +
    +``` + +Hugo renders this to: + +```html + +``` + +> [!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" }} +
    • {{ .Name }}
    • + {{ end }} +
    +``` + +When using the sort function with menu entries, specify any of the following keys: `Identifier`, `Name`, `Parent`, `Post`, `Pre`, `Title`, `URL`, or `Weight`. + +[`sort`]: /functions/collections/sort/ diff --git a/docs/content/en/methods/menu/Limit.md b/docs/content/en/methods/menu/Limit.md new file mode 100644 index 0000000..005fef1 --- /dev/null +++ b/docs/content/en/methods/menu/Limit.md @@ -0,0 +1,50 @@ +--- +title: Limit +description: Returns the given menu, limited to the first N entries. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: navigation.Menu + signatures: [MENU.Limit N] +--- + +The `Limit` method returns the given menu, limited to the first N entries. + +Consider this menu definition: + +{{< code-toggle file=hugo >}} +[[menus.main]] +name = 'Services' +pageRef = '/services' +weight = 10 + +[[menus.main]] +name = 'About' +pageRef = '/about' +weight = 20 + +[[menus.main]] +name = 'Contact' +pageRef = '/contact' +weight = 30 +{{< /code-toggle >}} + +To sort the entries by name, and limit to the first 2 entries: + +```go-html-template +
      + {{ range .Site.Menus.main.ByName.Limit 2 }} +
    • {{ .Name }}
    • + {{ end }} +
    +``` + +Hugo renders this to: + +```html + +``` diff --git a/docs/content/en/methods/menu/Reverse.md b/docs/content/en/methods/menu/Reverse.md new file mode 100644 index 0000000..1ee31aa --- /dev/null +++ b/docs/content/en/methods/menu/Reverse.md @@ -0,0 +1,51 @@ +--- +title: Reverse +description: Returns the given menu, reversing the sort order of its entries. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: navigation.Menu + signatures: [MENU.Reverse] +--- + +The `Reverse` method returns the given menu, reversing the sort order of its 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 in descending order: + +```go-html-template +
      + {{ range .Site.Menus.main.ByName.Reverse }} +
    • {{ .Name }}
    • + {{ end }} +
    +``` + +Hugo renders this to: + +```html + +``` diff --git a/docs/content/en/methods/menu/_index.md b/docs/content/en/methods/menu/_index.md new file mode 100644 index 0000000..41084fd --- /dev/null +++ b/docs/content/en/methods/menu/_index.md @@ -0,0 +1,7 @@ +--- +title: Menu methods +linkTitle: Menu +description: Use these methods when ranging through menu entries. +categories: [] +keywords: [] +--- diff --git a/docs/content/en/methods/output-format/MediaType.md b/docs/content/en/methods/output-format/MediaType.md new file mode 100644 index 0000000..812d0d2 --- /dev/null +++ b/docs/content/en/methods/output-format/MediaType.md @@ -0,0 +1,32 @@ +--- +title: MediaType +description: Returns the media type of the given output format. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: media.Type + signatures: [OUTPUTFORMAT.MediaType] +--- + +{{% include "/_common/methods/output-formats/to-use-this-method.md" %}} + +## Example + +```go-html-template +{{ with .Site.Home.OutputFormats.Get "rss" }} + {{ with .MediaType }} + {{ .Type }} → application/rss+xml + {{ .MainType }} → application + {{ .SubType }} → rss + {{ .Suffixes }} → [rss] + {{ .FirstSuffix.Suffix }} → rss + {{ end }} +{{ end }} +``` + +## Methods + +Use these methods on the `MediaType` object. + +{{% include "/_common/methods/media-type/core-methods.md" %}} diff --git a/docs/content/en/methods/output-format/Name.md b/docs/content/en/methods/output-format/Name.md new file mode 100644 index 0000000..307f940 --- /dev/null +++ b/docs/content/en/methods/output-format/Name.md @@ -0,0 +1,18 @@ +--- +title: Name +description: Returns the identifier of the given output format. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [OUTPUTFORMAT.Name] +--- + +{{% include "/_common/methods/output-formats/to-use-this-method.md" %}} + +```go-html-template +{{ with .Site.Home.OutputFormats.Get "rss" }} + {{ .Name }} → rss +{{ end }} +``` diff --git a/docs/content/en/methods/output-format/Permalink.md b/docs/content/en/methods/output-format/Permalink.md new file mode 100644 index 0000000..09b98dc --- /dev/null +++ b/docs/content/en/methods/output-format/Permalink.md @@ -0,0 +1,18 @@ +--- +title: Permalink +description: Returns the permalink of the page generated by the current output format. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [OUTPUTFORMAT.Permalink] +--- + +{{% include "/_common/methods/output-formats/to-use-this-method.md" %}} + +```go-html-template +{{ with .Site.Home.OutputFormats.Get "rss" }} + {{ .Permalink }} → https://example.org/index.xml +{{ end }} +``` diff --git a/docs/content/en/methods/output-format/Rel.md b/docs/content/en/methods/output-format/Rel.md new file mode 100644 index 0000000..4c3db97 --- /dev/null +++ b/docs/content/en/methods/output-format/Rel.md @@ -0,0 +1,18 @@ +--- +title: Rel +description: Returns the rel value of the given output format, either the default or as defined in your project configuration. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [OUTPUTFORMAT.Rel] +--- + +{{% include "/_common/methods/output-formats/to-use-this-method.md" %}} + +```go-html-template +{{ with .Site.Home.OutputFormats.Get "rss" }} + {{ .Rel }} → alternate +{{ end }} +``` diff --git a/docs/content/en/methods/output-format/RelPermalink.md b/docs/content/en/methods/output-format/RelPermalink.md new file mode 100644 index 0000000..434100c --- /dev/null +++ b/docs/content/en/methods/output-format/RelPermalink.md @@ -0,0 +1,18 @@ +--- +title: RelPermalink +description: Returns the relative permalink of the page generated by the current output format. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [OUTPUTFORMAT.RelPermalink] +--- + +{{% include "/_common/methods/output-formats/to-use-this-method.md" %}} + +```go-html-template +{{ with .Site.Home.OutputFormats.Get "rss" }} + {{ .RelPermalink }} → /index.xml +{{ end }} +``` diff --git a/docs/content/en/methods/output-format/_index.md b/docs/content/en/methods/output-format/_index.md new file mode 100644 index 0000000..dd374de --- /dev/null +++ b/docs/content/en/methods/output-format/_index.md @@ -0,0 +1,7 @@ +--- +title: Output format methods +linkTitle: Output format +description: Use these methods with an OutputFormat object. +categories: [] +keywords: [] +--- diff --git a/docs/content/en/methods/page/Aliases.md b/docs/content/en/methods/page/Aliases.md new file mode 100644 index 0000000..17a52c0 --- /dev/null +++ b/docs/content/en/methods/page/Aliases.md @@ -0,0 +1,138 @@ +--- +title: Aliases +description: Returns the aliases defined in front matter as server-relative URLs, resolved according to the current content dimension. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: '[]string' + signatures: [PAGE.Aliases] +--- + +The `Aliases` method on a `Page` object returns the values defined in the [`aliases`][] front matter field as server-relative URLs, resolved according to the current [content dimension](g). + +The `Aliases` method is useful for generating a `_redirects` file, which contains a source URL, a target URL, and an HTTP status code for each alias. You can use a `_redirects` file with hosting services such as Cloudflare, GitLab Pages, and Netlify. + +## Redirects + +By default, Hugo handles aliases by creating individual HTML files for each alias path. These files contain a `meta http-equiv="refresh"` tag to redirect the visitor via the browser. + +While functional, generating a single `_redirects` file allows your hosting provider to handle redirects at the server level. This is more efficient than client-side redirection and improves performance by eliminating the need to load a middle-man HTML page. + +> [!TIP] +> You can use the same general approach to generate an `.htaccess` file. + +## Example + +The following example demonstrates how to configure your site and create a template to automate the generation of a `_redirects` file. + +### Content structure + +The content structure for this multilingual example looks like this: + +```tree +content/ +├── examples/ +│ ├── a.de.md aliases = ['a-old'] +│ ├── a.en.md aliases = ['a-old', 'a-older'] +│ ├── b.de.md aliases = ['b-old'] +│ └── b.en.md aliases = ['b-old', 'b-older'] +└── _index.md +``` + +In the example above, the aliases are [page-relative](g). To specify a [site-relative](g) path, preface the entry with a slash (`/`). Both forms are resolved to [server-relative](g) paths. + +Page-relative paths can also include directory traversal: + +| Path type | File path | Alias | Server-relative path | +| :--- | :--- | :--- | :--- | +| page-relative | `content/examples/a.en.md` | `a-old` | `/en/examples/a-old/` | +| page-relative | `content/examples/a.en.md` | `../a-old` | `/en/a-old/` | +| site-relative | `content/examples/a.en.md` | `/a-old` | `/en/a-old/` | + +### Project configuration + +To implement this, you must update your project configuration to: + +1. Disable the generation of default HTML redirect files by setting `disableAliases` to `true`. +1. Define a [media type][] named `text/redirects` to handle the file format. +1. Define a custom [output format][] named `redirects` to set the filename to `_redirects` and place it at the root of the published site. +1. Configure the home page [outputs][] to include the `redirects` format in addition to `html`. + +{{< code-toggle file=hugo >}} +baseURL = 'https://example.org/' +disableAliases = true + +defaultContentLanguage = 'en' +defaultContentLanguageInSubdir = true + +[languages.en] + locale = 'en-US' + direction = 'ltr' + name = 'English' + weight = 1 + title = 'My Site in English' + +[languages.de] + locale = 'de-DE' + direction = 'ltr' + name = 'Deutsch' + weight = 2 + title = 'My Site in German' + +[mediaTypes] + [mediaTypes.'text/redirects'] + delimiter = '' + +[outputFormats] + [outputFormats.redirects] + baseName = '_redirects' + isPlainText = true + mediaType = 'text/redirects' + root = true + +[outputs] + home = ['html', 'redirects'] +{{< /code-toggle >}} + +### Template implementation + +Next, create a home page template specifically for the `redirects` output format. The following template iterates through every page in every language and extracts its aliases. + +To ensure the resulting `_redirects` file is valid, the template uses the [`strings.FindRE`][] function to check for whitespace such as tabs or newlines within the alias string. If whitespace is detected, Hugo will throw an error and fail the build to prevent generating an invalid file. + +```go-html-template {file="layouts/home.redirects" copy=true} +{{- if site.IsDefault -}} + {{- range hugo.Sites -}} + {{- range $p := .Pages -}} + {{- range .Aliases -}} + {{- if findRE `\s` . -}} + {{- errorf "One of the front matter aliases in %q contains whitespace" $p.String -}} + {{- end -}} + {{- printf "%s %s 301\n" . $p.RelPermalink -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} +``` + +### Generated output + +Once Hugo processes the template, it produces a clean list of redirect rules. Each line follows the required format: the source URL, the destination URL, and the HTTP status code. + +The resulting `_redirects` file looks like this: + +```text +/de/examples/a-old /de/examples/a/ 301 +/de/examples/b-old /de/examples/b/ 301 +/en/examples/b-old /en/examples/b/ 301 +/en/examples/b-older /en/examples/b/ 301 +/en/examples/a-old /en/examples/a/ 301 +/en/examples/a-older /en/examples/a/ 301 +``` + +[`aliases`]: /content-management/front-matter/#aliases +[`strings.FindRE`]: /functions/strings/findre/ +[media type]: /configuration/media-types/ +[output format]: /configuration/output-formats/ +[outputs]: /configuration/outputs/ diff --git a/docs/content/en/methods/page/AllTranslations.md b/docs/content/en/methods/page/AllTranslations.md new file mode 100644 index 0000000..09578fc --- /dev/null +++ b/docs/content/en/methods/page/AllTranslations.md @@ -0,0 +1,88 @@ +--- +title: AllTranslations +description: Returns all translations of the given page, including the current language, sorted by language weight then language name. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pages + signatures: [PAGE.AllTranslations] +--- + +With this project configuration: + +{{< code-toggle file=hugo >}} +defaultContentLanguage = 'en' + +[languages.en] +contentDir = 'content/en' +label = 'English' +locale = 'en-US' +weight = 1 + +[languages.de] +contentDir = 'content/de' +label = 'Deutsch' +locale = 'de-DE' +weight = 2 + +[languages.fr] +contentDir = 'content/fr' +label = 'Français' +locale = 'fr-FR' +weight = 3 +{{< /code-toggle >}} + +And this content: + +```tree +content/ +├── de/ +│ ├── books/ +│ │ ├── book-1.md +│ │ └── book-2.md +│ └── _index.md +├── en/ +│ ├── books/ +│ │ ├── book-1.md +│ │ └── book-2.md +│ └── _index.md +├── fr/ +│ ├── books/ +│ │ └── book-1.md +│ └── _index.md +└── _index.md +``` + +And this template: + +```go-html-template +{{ with .AllTranslations }} + +{{ end }} +``` + +Hugo will render this list on the `book-1` page of each site: + +```html + +``` + +On the `book-2` page of the English and German sites, Hugo will render this: + +```html + +``` diff --git a/docs/content/en/methods/page/AlternativeOutputFormats.md b/docs/content/en/methods/page/AlternativeOutputFormats.md new file mode 100644 index 0000000..73a33cb --- /dev/null +++ b/docs/content/en/methods/page/AlternativeOutputFormats.md @@ -0,0 +1,31 @@ +--- +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][]. + +For example, to generate a `link` element for each of the alternative output formats: + +```go-html-template +{{ range .AlternativeOutputFormats }} + {{ printf "" .Rel .MediaType.Type .Permalink | safeHTML }} +{{ end }} +``` + +Hugo renders this to something like: + +```html + + +``` + +[details]: /configuration/output-formats/ diff --git a/docs/content/en/methods/page/Ancestors.md b/docs/content/en/methods/page/Ancestors.md new file mode 100644 index 0000000..f3abf0c --- /dev/null +++ b/docs/content/en/methods/page/Ancestors.md @@ -0,0 +1,76 @@ +--- +title: Ancestors +description: Returns a collection of Page objects, one for each ancestor section of the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pages + signatures: [PAGE.Ancestors] +--- + +With this content structure: + +```tree +content/ +├── auctions/ +│ ├── 2023-11/ +│ │ ├── _index.md <-- front matter: weight = 202311 +│ │ ├── auction-1.md +│ │ └── auction-2.md +│ ├── 2023-12/ +│ │ ├── _index.md <-- front matter: weight = 202312 +│ │ ├── auction-3.md +│ │ └── auction-4.md +│ ├── _index.md <-- front matter: weight = 30 +│ ├── bidding.md +│ └── payment.md +├── books/ +│ ├── _index.md <-- front matter: weight = 10 +│ ├── book-1.md +│ └── book-2.md +├── films/ +│ ├── _index.md <-- front matter: weight = 20 +│ ├── film-1.md +│ └── film-2.md +└── _index.md +``` + +And this template: + +```go-html-template +{{ range .Ancestors }} + {{ .LinkTitle }} +{{ end }} +``` + +On the November 2023 auctions page, Hugo renders: + +```html +Auctions in November 2023 +Auctions +Home +``` + +In the example above, notice that Hugo orders the ancestors from closest to furthest. This makes breadcrumb navigation simple: + +```go-html-template + +``` + +With some CSS, the code above renders something like this, where each breadcrumb links to its page: + +```text +Home > Auctions > Auctions in November 2023 > Auction 1 +``` diff --git a/docs/content/en/methods/page/BundleType.md b/docs/content/en/methods/page/BundleType.md new file mode 100644 index 0000000..cf7f8c0 --- /dev/null +++ b/docs/content/en/methods/page/BundleType.md @@ -0,0 +1,35 @@ +--- +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][]. + +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. + +```tree +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 }} +``` + +[details]: /content-management/page-bundles/ diff --git a/docs/content/en/methods/page/CodeOwners.md b/docs/content/en/methods/page/CodeOwners.md new file mode 100644 index 0000000..4aa0bf0 --- /dev/null +++ b/docs/content/en/methods/page/CodeOwners.md @@ -0,0 +1,64 @@ +--- +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. + +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: + +```tree +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. + +[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 +[`resources.GetRemote`]: /functions/resources/getremote/ diff --git a/docs/content/en/methods/page/Content.md b/docs/content/en/methods/page/Content.md new file mode 100644 index 0000000..21348eb --- /dev/null +++ b/docs/content/en/methods/page/Content.md @@ -0,0 +1,16 @@ +--- +title: Content +description: Returns the rendered content of the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: template.HTML + signatures: [PAGE.Content] +--- + +The `Content` method on a `Page` object renders Markdown and shortcodes to HTML. + +```go-html-template +{{ .Content }} +``` diff --git a/docs/content/en/methods/page/ContentWithoutSummary.md b/docs/content/en/methods/page/ContentWithoutSummary.md new file mode 100644 index 0000000..5c3edbb --- /dev/null +++ b/docs/content/en/methods/page/ContentWithoutSummary.md @@ -0,0 +1,22 @@ +--- +title: ContentWithoutSummary +description: Returns the rendered content of the given page, excluding the content summary. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: template.HTML + signatures: [PAGE.ContentWithoutSummary] +--- + +{{< new-in 0.134.0 />}} + +Applicable when using manual or automatic [content summaries][], the `ContentWithoutSummary` method on a `Page` object renders Markdown and shortcodes to HTML, excluding the content summary from the result. + +```go-html-template +{{ .ContentWithoutSummary }} +``` + +The `ContentWithoutSummary` method returns the same as `Content` if you define the content summary in front matter. + +[content summaries]: /content-management/summaries/#manual-summary diff --git a/docs/content/en/methods/page/CurrentSection.md b/docs/content/en/methods/page/CurrentSection.md new file mode 100644 index 0000000..0698c8f --- /dev/null +++ b/docs/content/en/methods/page/CurrentSection.md @@ -0,0 +1,48 @@ +--- +title: CurrentSection +description: Returns the Page object of the section in which the given page resides. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Page + signatures: [PAGE.CurrentSection] +--- + +{{% glossary-term section %}} + +> [!NOTE] +> The current section of a [section page](g), [taxonomy page](g), [term page](g), or the home page, is itself. + +Consider this content structure: + +```tree +content/ +├── auctions/ +│ ├── 2023-11/ +│ │ ├── _index.md <-- current section: 2023-11 +│ │ ├── auction-1.md +│ │ └── auction-2.md <-- current section: 2023-11 +│ ├── 2023-12/ +│ │ ├── _index.md +│ │ ├── auction-3.md +│ │ └── auction-4.md +│ ├── _index.md <-- current section: auctions +│ ├── bidding.md +│ └── payment.md <-- current section: auctions +├── books/ +│ ├── _index.md <-- current section: books +│ ├── book-1.md +│ └── book-2.md <-- current section: books +├── films/ +│ ├── _index.md <-- current section: films +│ ├── film-1.md +│ └── film-2.md <-- current section: films +└── _index.md <-- current section: home +``` + +To create a link to the current section page: + +```go-html-template +{{ .CurrentSection.LinkTitle }} +``` diff --git a/docs/content/en/methods/page/Data.md b/docs/content/en/methods/page/Data.md new file mode 100644 index 0000000..8963b0e --- /dev/null +++ b/docs/content/en/methods/page/Data.md @@ -0,0 +1,101 @@ +--- +title: Data +description: Returns a unique data object for each page kind. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Data + signatures: [PAGE.Data] +--- + +The `Data` method on a `Page` object returns a unique data object for each [page kind](g). + +> [!NOTE] +> The `Data` method is only useful within [taxonomy](g) and [term](g) templates. +> +> Themes that are not actively maintained may still use `.Data.Pages` in their templates. Although that syntax remains functional, use one of these methods instead: [`Pages`][], [`RegularPages`][], or [`RegularPagesRecursive`][] + +The examples that follow are based on this project configuration: + +{{< code-toggle file=hugo >}} +[taxonomies] +genre = 'genres' +author = 'authors' +{{< /code-toggle >}} + +And this content structure: + +```tree +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/ +[`RegularPagesRecursive`]: /methods/page/regularpagesrecursive/ +[`RegularPages`]: /methods/page/regularpages/ +[taxonomy methods]: /methods/taxonomy/ +[taxonomy templates]: /templates/types/#taxonomy +[term templates]: /templates/types/#term diff --git a/docs/content/en/methods/page/Date.md b/docs/content/en/methods/page/Date.md new file mode 100644 index 0000000..1cc7e9c --- /dev/null +++ b/docs/content/en/methods/page/Date.md @@ -0,0 +1,33 @@ +--- +title: Date +description: Returns the date of the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: time.Time + signatures: [PAGE.Date] +--- + +Set the date in front matter: + +{{< code-toggle file=content/news/article-1.md fm=true >}} +title = 'Article 1' +date = 2023-10-19T00:40:04-07:00 +{{< /code-toggle >}} + +> [!NOTE] +> The date field in front matter is often considered to be the creation date, You can change its meaning, and its effect on your project, in your project configuration. See [details][]. + +The date is a [time.Time][] value. Format and localize the value with the [`time.Format`][] function, or use it with any of the [time methods][]. + +```go-html-template +{{ .Date | time.Format ":date_medium" }} → Oct 19, 2023 +``` + +In the example above we explicitly set the date in front matter. With Hugo's default configuration, the `Date` method returns the front matter value. This behavior is configurable, allowing you to set fallback values if the date is not defined in front matter. See [details][]. + +[`time.Format`]: /functions/time/format/ +[details]: /configuration/front-matter/#dates +[time methods]: /methods/time/ +[time.Time]: https://pkg.go.dev/time#Time diff --git a/docs/content/en/methods/page/Description.md b/docs/content/en/methods/page/Description.md new file mode 100644 index 0000000..7e44bd0 --- /dev/null +++ b/docs/content/en/methods/page/Description.md @@ -0,0 +1,27 @@ +--- +title: Description +description: Returns the description of the given page as defined in front matter. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [PAGE.Description] +--- + +Conceptually different from a [content summary][], a page description is typically used in metadata about the page. + +{{< code-toggle file=content/recipes/sushi.md fm=true >}} +title = 'How to make spicy tuna hand rolls' +description = 'Instructions for making spicy tuna hand rolls.' +{{< /code-toggle >}} + +```go-html-template {file="layouts/baseof.html"} + + ... + + ... + +``` + +[content summary]: /content-management/summaries/ diff --git a/docs/content/en/methods/page/Draft.md b/docs/content/en/methods/page/Draft.md new file mode 100644 index 0000000..d814d85 --- /dev/null +++ b/docs/content/en/methods/page/Draft.md @@ -0,0 +1,21 @@ +--- +title: Draft +description: Reports whether the given page is a draft as defined in front matter. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: bool + signatures: [PAGE.Draft] +--- + +By default, Hugo does not publish draft pages when you build your project. To include draft pages when you build your project, use the `--buildDrafts` command line flag. + +{{< code-toggle file=content/posts/post-1.md fm=true >}} +title = 'Post 1' +draft = true +{{< /code-toggle >}} + +```go-html-template +{{ .Draft }} → true +``` diff --git a/docs/content/en/methods/page/Eq.md b/docs/content/en/methods/page/Eq.md new file mode 100644 index 0000000..0cfe1f1 --- /dev/null +++ b/docs/content/en/methods/page/Eq.md @@ -0,0 +1,21 @@ +--- +title: Eq +description: Reports whether two Page objects are equal. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: bool + signatures: [PAGE1.Eq PAGE2] +--- + +In this contrived example we list all pages in the current section except for the current page. + +```go-html-template {file="layouts/page.html"} +{{ $currentPage := . }} +{{ range .CurrentSection.Pages }} + {{ if not (.Eq $currentPage) }} + {{ .LinkTitle }} + {{ end }} +{{ end }} +``` diff --git a/docs/content/en/methods/page/ExpiryDate.md b/docs/content/en/methods/page/ExpiryDate.md new file mode 100644 index 0000000..e59687c --- /dev/null +++ b/docs/content/en/methods/page/ExpiryDate.md @@ -0,0 +1,32 @@ +--- +title: ExpiryDate +description: Returns the expiry date of the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: time.Time + signatures: [PAGE.ExpiryDate] +--- + +By default, Hugo excludes expired pages when building your project. To include expired pages, use the `--buildExpired` command line flag. + +Set the expiry date in front matter: + +{{< code-toggle file=content/news/article-1.md fm=true >}} +title = 'Article 1' +expiryDate = 2024-10-19T00:32:13-07:00 +{{< /code-toggle >}} + +The expiry date is a [time.Time][] value. Format and localize the value with the [`time.Format`][] function, or use it with any of the [time methods][]. + +```go-html-template +{{ .ExpiryDate | time.Format ":date_medium" }} → Oct 19, 2024 +``` + +In the example above we explicitly set the expiry date in front matter. With Hugo's default configuration, the `ExpiryDate` method returns the front matter value. This behavior is configurable, allowing you to set fallback values if the expiry date is not defined in front matter. See [details][]. + +[`time.Format`]: /functions/time/format/ +[details]: /configuration/front-matter/#dates +[time methods]: /methods/time/ +[time.Time]: https://pkg.go.dev/time#Time diff --git a/docs/content/en/methods/page/File.md b/docs/content/en/methods/page/File.md new file mode 100644 index 0000000..9644e0d --- /dev/null +++ b/docs/content/en/methods/page/File.md @@ -0,0 +1,183 @@ +--- +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: + +```tree +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 + +Use these methods on the `File` object. + +> [!NOTE] +> The path separators (slash or backslash) in `Path`, `Dir`, and `Filename` depend on the operating system. + +`BaseFileName` +: (`string`) The file name, excluding the extension. + + ```go-html-template + {{ with .File }} + {{ .BaseFileName }} + {{ end }} + ``` + +`ContentBaseName` +: (`string`) If the page is a branch or leaf bundle, the name of the containing directory, else the `TranslationBaseName`. + + ```go-html-template + {{ with .File }} + {{ .ContentBaseName }} + {{ end }} + ``` + +`Dir` +: (`string`) The file path, excluding the file name, relative to the `content` directory. + + ```go-html-template + {{ with .File }} + {{ .Dir }} + {{ end }} + ``` + +`Ext` +: (`string`) The file extension. + + ```go-html-template + {{ with .File }} + {{ .Ext }} + {{ end }} + ``` + +`Filename` +: (`string`) The absolute file path. + + ```go-html-template + {{ with .File }} + {{ .Filename }} + {{ end }} + ``` + +`IsContentAdapter` +: (`bool`) Reports whether the file is a [content adapter][]. + + ```go-html-template + {{ with .File }} + {{ .IsContentAdapter }} + {{ end }} + ``` + +`LogicalName` +: (`string`) The file name. + + ```go-html-template + {{ with .File }} + {{ .LogicalName }} + {{ end }} + ``` + +`Path` +: (`string`) The file path, relative to the `content` directory. + + ```go-html-template + {{ with .File }} + {{ .Path }} + {{ end }} + ``` + +`Section` +: (`string`) The name of the top-level section in which the file resides. + + ```go-html-template + {{ with .File }} + {{ .Section }} + {{ end }} + ``` + +`TranslationBaseName` +: (`string`) The file name, excluding the extension and language identifier. + + ```go-html-template + {{ with .File }} + {{ .TranslationBaseName }} + {{ end }} + ``` + +`UniqueID` +: (`string`) The MD5 hash of `.File.Path`. + + ```go-html-template + {{ with .File }} + {{ .UniqueID }} + {{ end }} + ``` + +## Examples + +Consider this content structure in a multilingual project: + +```tree +content/ +├── news/ +│ ├── b/ +│ │ ├── index.de.md <-- leaf bundle +│ │ └── index.en.md <-- leaf bundle +│ ├── a.de.md <-- regular content +│ ├── a.en.md <-- regular content +│ ├── _index.de.md <-- branch bundle +│ └── _index.en.md <-- branch bundle +├── _index.de.md +└── _index.en.md +``` + +With the English language site: + + |regular content|leaf bundle|branch bundle +:--|:--|:--|:-- +BaseFileName|a.en|index.en|_index.en +ContentBaseName|a|b|news +Dir|news/|news/b/|news/ +Ext|md|md|md +Filename|/home/user/...|/home/user/...|/home/user/... +IsContentAdapter|false|false|false +LogicalName|a.en.md|index.en.md|_index.en.md +Path|news/a.en.md|news/b/index.en.md|news/_index.en.md +Section|news|news|news +TranslationBaseName|a|index|_index +UniqueID|15be14b...|186868f...|7d9159d... + +## Defensive coding + +Some of the pages on a site may not be backed by a file. For example: + +- Top-level section pages +- Taxonomy pages +- Term pages + +Without a backing file, Hugo will throw an error if you attempt to access a `.File` property. To code defensively, first check for file existence: + +```go-html-template +{{ with .File }} + {{ .ContentBaseName }} +{{ end }} +``` + +[content adapter]: /content-management/content-adapters/ diff --git a/docs/content/en/methods/page/FirstSection.md b/docs/content/en/methods/page/FirstSection.md new file mode 100644 index 0000000..0f80782 --- /dev/null +++ b/docs/content/en/methods/page/FirstSection.md @@ -0,0 +1,48 @@ +--- +title: FirstSection +description: Returns the Page object of the top-level section of which the given page is a descendant. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Page + signatures: [PAGE.FirstSection] +--- + +{{% glossary-term section %}} + +> [!NOTE] +> When called on the home page, the `FirstSection` method returns the `Page` object of the home page itself. + +Consider this content structure: + +```tree +content/ +├── auctions/ +│ ├── 2023-11/ +│ │ ├── _index.md <-- first section: auctions +│ │ ├── auction-1.md +│ │ └── auction-2.md <-- first section: auctions +│ ├── 2023-12/ +│ │ ├── _index.md +│ │ ├── auction-3.md +│ │ └── auction-4.md +│ ├── _index.md <-- first section: auctions +│ ├── bidding.md +│ └── payment.md <-- first section: auctions +├── books/ +│ ├── _index.md <-- first section: books +│ ├── book-1.md +│ └── book-2.md <-- first section: books +├── films/ +│ ├── _index.md <-- first section: films +│ ├── film-1.md +│ └── film-2.md <-- first section: films +└── _index.md <-- first section: home +``` + +To link to the top-level section of which the current page is a descendant: + +```go-html-template +{{ .FirstSection.LinkTitle }} +``` diff --git a/docs/content/en/methods/page/Fragments.md b/docs/content/en/methods/page/Fragments.md new file mode 100644 index 0000000..ce8a131 --- /dev/null +++ b/docs/content/en/methods/page/Fragments.md @@ -0,0 +1,108 @@ +--- +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. Use the methods below to inspect, validate, and render page fragments. + +## Methods + +Use these methods on the `Fragments` object. + +`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][]. + + To inspect the data structure: + + ```go-html-template +
    {{ debug.Dump .Fragments.Identifiers }}
    + ``` + +`Identifiers.Contains ID` +: (`bool`) Reports whether one or more headings on the page has the given `id` attribute, useful for validating fragments within a link [render hook](g). + + ```go-html-template + {{ .Fragments.Identifiers.Contains "section-2" }} → true + ``` + +`Identifiers.Count ID` +: (`int`) The number of headings on a page with the given `id` attribute, useful for detecting duplicates. + + ```go-html-template + {{ .Fragments.Identifiers.Count "section-2" }} → 1 + ``` + +`ToHTML` +: (`template.HTML`) Returns a TOC as a nested list, either ordered or unordered, identical to the HTML returned by the [`TableOfContents`][] method. This method take three arguments: the start level (`int`), the end level (`int`), and a boolean (`true` to return an ordered list, `false` to return an unordered list). + + Use this method when you want to control the start level, end level, or list type independently from the table of contents settings in your project configuration. + + ```go-html-template + {{ $startLevel := 2 }} + {{ $endLevel := 3 }} + {{ $ordered := true }} + {{ .Fragments.ToHTML $startLevel $endLevel $ordered }} + ``` + + Hugo renders this to: + + ```html + + ``` + +## Notes + +> [!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. + +[ATX]: https://spec.commonmark.org/current/#atx-headings +[Markdown notation]: /content-management/shortcodes/#notation +[`TableOfContents`]: /methods/page/tableofcontents/ +[configure Markup]: /configuration/markup/#parserautodefinitiontermid +[setext]: https://spec.commonmark.org/current/#setext-headings +[standard notation]: /content-management/shortcodes/#notation +[table of contents]: /methods/page/tableofcontents/ diff --git a/docs/content/en/methods/page/FuzzyWordCount.md b/docs/content/en/methods/page/FuzzyWordCount.md new file mode 100644 index 0000000..36c0f25 --- /dev/null +++ b/docs/content/en/methods/page/FuzzyWordCount.md @@ -0,0 +1,18 @@ +--- +title: FuzzyWordCount +description: Returns the number of words in the content of the given page, rounded up to the nearest multiple of 100. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: int + signatures: [PAGE.FuzzyWordCount] +--- + +```go-html-template +{{ .FuzzyWordCount }} → 200 +``` + +To get the exact word count, use the [`WordCount`][] method. + +[`WordCount`]: /methods/page/wordcount/ diff --git a/docs/content/en/methods/page/GetPage.md b/docs/content/en/methods/page/GetPage.md new file mode 100644 index 0000000..641765d --- /dev/null +++ b/docs/content/en/methods/page/GetPage.md @@ -0,0 +1,64 @@ +--- +title: GetPage +description: Returns a Page object from the given path. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Page + signatures: [PAGE.GetPage PATH] +aliases: [/functions/getpage] +--- + +The `GetPage` method is also available on a `Site` object. See [details][]. + +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: + +```tree +content/ +├── works/ +│ ├── paintings/ +│ │ ├── _index.md +│ │ ├── starry-night.md +│ │ └── the-mona-lisa.md +│ ├── sculptures/ +│ │ ├── _index.md +│ │ ├── david.md +│ │ └── the-thinker.md +│ └── _index.md +└── _index.md +``` + +The examples below depict the result of rendering `works/paintings/the-mona-lisa.md`: + +```go-html-template {file="layouts/works/page.html"} +{{ with .GetPage "starry-night" }} + {{ .Title }} → Starry Night +{{ end }} + +{{ with .GetPage "./starry-night" }} + {{ .Title }} → Starry Night +{{ end }} + +{{ with .GetPage "../paintings/starry-night" }} + {{ .Title }} → Starry Night +{{ end }} + +{{ with .GetPage "/works/paintings/starry-night" }} + {{ .Title }} → Starry Night +{{ end }} + +{{ with .GetPage "../sculptures/david" }} + {{ .Title }} → David +{{ end }} + +{{ with .GetPage "/works/sculptures/david" }} + {{ .Title }} → David +{{ end }} +``` + +[details]: /methods/site/getpage/ diff --git a/docs/content/en/methods/page/GetTerms.md b/docs/content/en/methods/page/GetTerms.md new file mode 100644 index 0000000..53b996f --- /dev/null +++ b/docs/content/en/methods/page/GetTerms.md @@ -0,0 +1,41 @@ +--- +title: GetTerms +description: Returns a collection of term pages for terms defined on the given page in the given taxonomy, ordered according to the sequence in which they appear in front matter. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pages + signatures: [PAGE.GetTerms TAXONOMY] +--- + +Given this front matter: + +{{< code-toggle file=content/books/les-miserables.md fm=true >}} +title = 'Les Misérables' +tags = ['historical','classic','fiction'] +{{< /code-toggle >}} + +This template code: + +```go-html-template +{{ with .GetTerms "tags" }} +

    Tags

    + +{{ end }} +``` + +Is rendered to: + +```html +

    Tags

    + +``` diff --git a/docs/content/en/methods/page/GitInfo.md b/docs/content/en/methods/page/GitInfo.md new file mode 100644 index 0000000..3e7ced3 --- /dev/null +++ b/docs/content/en/methods/page/GitInfo.md @@ -0,0 +1,194 @@ +--- +title: GitInfo +description: Provides access to commit metadata for a given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: '*gitmap.GitInfo' + signatures: [PAGE.GitInfo] +--- + +The `GitInfo` method on a `Page` object provides access to commit metadata from your Git history, such as the author's name, the commit hash, and the commit message. + +> [!NOTE] +> Hugo's Git integration is performant, but may increase build times for large projects. + +## Prerequisites + +Install Git, create a repository, and commit your project files. + +You must also allow Hugo to access your repository by adding this to your project configuration: + +{{< code-toggle file=hugo >}} +enableGitInfo = true +{{< /code-toggle >}} + +> [!NOTE] +> When you set [`enableGitInfo`][] to `true`, the last modification date for each content page will automatically be the Author Date of the last commit for that file. +> +> This is configurable. See [details][]. + +## Scope + +Commit metadata is available for content stored in your local repository and for content provided by [modules](g). + +### Local content + +Hugo retrieves commit metadata for files tracked within your project's local repository. This includes all content files managed by Git in your main project directory. + +### Module content + +{{< new-in 0.157.0 />}} + +Hugo also retrieves commit metadata for content provided by modules. This allows you to display commit data for remote repositories that are mounted as content directories, such as when aggregating documentation from multiple sources. + +> [!NOTE] +> The `GitInfo` method returns nil for module content in these cases: +> +> - The module is vendored via `hugo mod vendor` +> - A [module replacement][] is configured via a `replace` directive in `go.mod` or the [`replacements`][] configuration parameter + +## Methods + +Use these methods on the `GitInfo` object. + +`AbbreviatedHash` +: (`string`) Returns the seven-character shortened version of the commit hash. + + ```go-html-template + {{ with .GitInfo }} + {{ .AbbreviatedHash }} → aab9ec0 + {{ end }} + ``` + +`AuthorDate` +: (`time.Time`) Returns the date the author originally created the commit. + + ```go-html-template + {{ with .GitInfo }} + {{ .AuthorDate.Format "2006-01-02" }} → 2023-10-09 + {{ end }} + ``` + +`AuthorEmail` +: (`string`) Returns the author's email address, respecting [gitmailmap][]. + + ```go-html-template + {{ with .GitInfo }} + {{ .AuthorEmail }} → jsmith@example.org + {{ end }} + ``` + +`AuthorName` +: (`string`) Returns the author's name, respecting [gitmailmap][]. + + ```go-html-template + {{ with .GitInfo }} + {{ .AuthorName }} → John Smith + {{ end }} + ``` + +`CommitDate` +: (`time.Time`) Returns the date the commit was applied to the branch. + + ```go-html-template + {{ with .GitInfo }} + {{ .CommitDate.Format "2006-01-02" }} → 2023-10-09 + {{ end }} + ``` + +`Hash` +: (`string`) Returns the full SHA-1 commit hash. + + ```go-html-template + {{ with .GitInfo }} + {{ .Hash }} → aab9ec0b31ebac916a1468c4c9c305f2bebf78d4 + {{ end }} + ``` + +`Subject` +: (`string`) Returns the first line of the commit message (the summary). + + ```go-html-template + {{ with .GitInfo }} + {{ .Subject }} → Add tutorials + {{ end }} + ``` + +`Body` +: (`string`) Returns the full content of the commit message, excluding the subject line. + + ```go-html-template + {{ with .GitInfo }} + {{ .Body }} → Two new pages added. + {{ end }} + ``` + +`Ancestors` +: (`gitmap.GitInfos`) Returns a list of previous commits for this specific file, ordered from most recent to oldest. + + For example, to list the last 5 commits: + + ```go-html-template + {{ with .GitInfo }} + {{ range .Ancestors | first 5 }} + {{ .CommitDate.Format "2006-01-02" }}: {{ .Subject }} + {{ end }} + {{ end }} + ``` + + To reverse the order: + + ```go-html-template + {{ with .GitInfo }} + {{ range .Ancestors.Reverse | first 5 }} + {{ .CommitDate.Format "2006-01-02" }}: {{ .Subject }} + {{ end }} + {{ end }} + ``` + +`Parent` +: (`*gitmap.GitInfo`) Returns the most recent ancestor commit for the file, if any. + +## Last modified date + +By default, when `enableGitInfo` is `true`, the `Lastmod` method on a `Page` object returns the Git AuthorDate of the last commit that included the file. + +You can change this behavior in your [project configuration][]. + +## Hosting considerations + +On a [CI/CD](g) platform, the step that clones your project repository must perform a deep clone. If the clone is shallow, the Git information for a given file may be inaccurate. It might incorrectly reflect the most recent repository commit, rather than the commit that actually modified the file. + +While some providers perform a deep clone by default, others require you to configure the depth yourself. + +Hosting service|Default clone depth|Configurable +:--|:--|:-- +AWS Amplify|Deep|N/A +Cloudflare|Shallow|Yes [^1] +DigitalOcean App Platform|Deep|N/A +GitHub Pages|Shallow|Yes [^2] +GitLab Pages|Shallow|Yes [^3] +Netlify|Deep|N/A +Render|Shallow|Yes [^1] +Vercel|Shallow|Yes [^1] + +[^1]: To perform a deep clone when hosting on Cloudflare, Render, or Vercel, include this code in the build script after the repository has been cloned: + + ```sh + if [ "$(git rev-parse --is-shallow-repository)" = "true" ]; then + git fetch --unshallow + fi + ``` + +[^2]: To perform a deep clone when hosting on GitHub Pages, set `fetch-depth: 0` in the `checkout` step of the GitHub Action. + +[^3]: To perform a deep clone when hosting on GitLab Pages, set the `GIT_DEPTH` environment variable to `0` in the workflow file. + +[`enableGitInfo`]: /configuration/all/#enablegitinfo +[`replacements`]: /configuration/module/#replacements +[details]: /configuration/front-matter/#dates +[gitmailmap]: https://git-scm.com/docs/gitmailmap +[module replacement]: /hugo-modules/use-modules/#replace +[project configuration]: /configuration/front-matter/ diff --git a/docs/content/en/methods/page/HasMenuCurrent.md b/docs/content/en/methods/page/HasMenuCurrent.md new file mode 100644 index 0000000..cbdc2ed --- /dev/null +++ b/docs/content/en/methods/page/HasMenuCurrent.md @@ -0,0 +1,33 @@ +--- +title: HasMenuCurrent +description: Reports whether the given Page object matches the Page object associated with one of the child menu entries under the given menu entry in the given menu. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: bool + signatures: [PAGE.HasMenuCurrent MENU MENUENTRY] +aliases: [/functions/hasmenucurrent] +--- + +If the `Page` object associated with the menu entry is a section, this method also returns `true` for any descendant of that section. + +```go-html-template +{{ $currentPage := . }} +{{ range site.Menus.main }} + {{ if $currentPage.IsMenuCurrent .Menu . }} + {{ .Name }} + {{ else if $currentPage.HasMenuCurrent .Menu . }} + {{ .Name }} + {{ else }} + {{ .Name }} + {{ end }} +{{ end }} +``` + +See [menu templates][] for a complete example. + +> [!NOTE] +> When using this method you must either define the menu entry in front matter, or specify a `pageRef` property when defining the menu entry in your project configuration. + +[menu templates]: /templates/menu/#example diff --git a/docs/content/en/methods/page/HasShortcode.md b/docs/content/en/methods/page/HasShortcode.md new file mode 100644 index 0000000..7b88416 --- /dev/null +++ b/docs/content/en/methods/page/HasShortcode.md @@ -0,0 +1,50 @@ +--- +title: HasShortcode +description: Reports whether the given shortcode is called by the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: bool + signatures: [PAGE.HasShortcode NAME] +--- + +By example, let's use [Plotly][] to render a chart: + +```md {file="content/example.md"} +{{}} +{ + "data": [ + { + "x": ["giraffes", "orangutans", "monkeys"], + "y": [20, 14, 23], + "type": "bar" + } + ], +} +{{}} +``` + +The shortcode is simple: + +```go-html-template {file="layouts/_shortcodes/plotly.html"} +{{ $id := printf "plotly-%02d" .Ordinal }} +
    + +``` + +Now we can selectively load the required JavaScript on pages that call the "plotly" shortcode: + +```go-html-template {file="layouts/baseof.html"} + + ... + {{ if .HasShortcode "plotly" }} + + {{ end }} + ... + +``` + +[Plotly]: https://plotly.com/javascript/ diff --git a/docs/content/en/methods/page/HeadingsFiltered.md b/docs/content/en/methods/page/HeadingsFiltered.md new file mode 100644 index 0000000..5f01f2c --- /dev/null +++ b/docs/content/en/methods/page/HeadingsFiltered.md @@ -0,0 +1,16 @@ +--- +title: HeadingsFiltered +description: Returns a slice of headings for each page related to the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: tableofcontents.Headings + signatures: [PAGE.HeadingsFiltered] +--- + +Use in conjunction with the [`Related`][] method on a [`Pages`][] object. See [details][]. + +[`Pages`]: /methods/pages/ +[`Related`]: /methods/pages/related/ +[details]: /content-management/related-content/#index-content-headings diff --git a/docs/content/en/methods/page/InSection.md b/docs/content/en/methods/page/InSection.md new file mode 100644 index 0000000..b2b9891 --- /dev/null +++ b/docs/content/en/methods/page/InSection.md @@ -0,0 +1,91 @@ +--- +title: InSection +description: Reports whether the given page is in the given section. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: bool + signatures: [PAGE.InSection SECTION] +--- + +{{% glossary-term section %}} + +The `InSection` method on a `Page` object reports whether the given page is in the given section. Note that the method returns `true` when comparing a page to a sibling. + +With this content structure: + +```tree +content/ +├── auctions/ +│ ├── 2023-11/ +│ │ ├── _index.md +│ │ ├── auction-1.md +│ │ └── auction-2.md +│ ├── 2023-12/ +│ │ ├── _index.md +│ │ ├── auction-3.md +│ │ └── auction-4.md +│ ├── _index.md +│ ├── bidding.md +│ └── payment.md +└── _index.md +``` + +When rendering the `auction-1` page: + +```go-html-template +{{ with .Site.GetPage "/" }} + {{ $.InSection . }} → false +{{ end }} + +{{ with .Site.GetPage "/auctions" }} + {{ $.InSection . }} → false +{{ end }} + +{{ with .Site.GetPage "/auctions/2023-11" }} + {{ $.InSection . }} → true +{{ end }} + +{{ with .Site.GetPage "/auctions/2023-11/auction-2" }} + {{ $.InSection . }} → true +{{ end }} +``` + +In the examples above we are coding defensively using the [`with`][] statement, returning nothing if the page does not exist. By adding an [`else`][] clause we can do some error reporting: + +```go-html-template +{{ $path := "/auctions/2023-11" }} +{{ with .Site.GetPage $path }} + {{ $.InSection . }} → true +{{ else }} + {{ errorf "Unable to find the section with path %s" $path }} +{{ end }} + ``` + +## Understanding context + +Inside of the `with` block, the [context](g) (the dot) is the section `Page` object, not the `Page` object passed into the template. If we were to use this syntax: + +```go-html-template +{{ with .Site.GetPage "/auctions" }} + {{ .InSection . }} → true +{{ end }} +``` + +The result would be wrong when rendering the `auction-1` page because we are comparing the section page to itself. + +> [!NOTE] +> Use the `$` to get the context passed into the template. + +```go-html-template +{{ with .Site.GetPage "/auctions" }} + {{ $.InSection . }} → true +{{ end }} +``` + +> [!NOTE] +> Gaining a thorough understanding of context is critical for anyone writing template code. + +[`else`]: /functions/go-template/else/ +[`with`]: /functions/go-template/with/ diff --git a/docs/content/en/methods/page/IsAncestor.md b/docs/content/en/methods/page/IsAncestor.md new file mode 100644 index 0000000..87c3441 --- /dev/null +++ b/docs/content/en/methods/page/IsAncestor.md @@ -0,0 +1,87 @@ +--- +title: IsAncestor +description: Reports whether PAGE1 is an ancestor of PAGE2. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: bool + signatures: [PAGE1.IsAncestor PAGE2] +--- + +With this content structure: + +```tree +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/IsBranch.md b/docs/content/en/methods/page/IsBranch.md new file mode 100644 index 0000000..a5bb4ac --- /dev/null +++ b/docs/content/en/methods/page/IsBranch.md @@ -0,0 +1,32 @@ +--- +title: IsBranch +description: Reports whether the given page is a branch. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: bool + signatures: [PAGE.IsBranch] +--- + +{{< new-in 0.163.0 />}} + +{{% glossary-term branch %}} + +```tree +content/ +├── books/ +│ ├── book-1/ +│ │ └── index.md <-- kind = page IsBranch = false +│ ├── book-2.md <-- kind = page IsBranch = false +│ └── _index.md <-- kind = section IsBranch = true +├── tags +│ ├── fiction +│ │ └── _index.md <-- kind = term IsBranch = true +│ └── _index.md <-- kind = taxonomy IsBranch = true +└── _index.md <-- kind = home IsBranch = true +``` + +```go-html-template +{{ .IsBranch }} +``` diff --git a/docs/content/en/methods/page/IsDescendant.md b/docs/content/en/methods/page/IsDescendant.md new file mode 100644 index 0000000..97dfd13 --- /dev/null +++ b/docs/content/en/methods/page/IsDescendant.md @@ -0,0 +1,87 @@ +--- +title: IsDescendant +description: Reports whether PAGE1 is a descendant of PAGE2. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: bool + signatures: [PAGE1.IsDescendant PAGE2] +--- + +With this content structure: + +```tree +content/ +├── auctions/ +│ ├── 2023-11/ +│ │ ├── _index.md +│ │ ├── auction-1.md +│ │ └── auction-2.md +│ ├── 2023-12/ +│ │ ├── _index.md +│ │ ├── auction-3.md +│ │ └── auction-4.md +│ ├── _index.md +│ ├── bidding.md +│ └── payment.md +└── _index.md +``` + +When rendering the `auctions` page: + +```go-html-template +{{ with .Site.GetPage "/" }} + {{ $.IsDescendant . }} → true +{{ end }} + +{{ with .Site.GetPage "/auctions" }} + {{ $.IsDescendant . }} → false +{{ end }} + +{{ with .Site.GetPage "/auctions/2023-11" }} + {{ $.IsDescendant . }} → false +{{ end }} + +{{ with .Site.GetPage "/auctions/2023-11/auction-2" }} + {{ $.IsDescendant . }} → false +{{ end }} +``` + +In the examples above we are coding defensively using the [`with`][] statement, returning nothing if the page does not exist. By adding an [`else`][] clause we can do some error reporting: + +```go-html-template +{{ $path := "/auctions/2023-11" }} +{{ with .Site.GetPage $path }} + {{ $.IsDescendant . }} → true +{{ else }} + {{ errorf "Unable to find the section with path %s" $path }} +{{ end }} + ``` + +## Understanding context + +Inside of the `with` block, the [context](g) (the dot) is the section `Page` object, not the `Page` object passed into the template. If we were to use this syntax: + +```go-html-template +{{ with .Site.GetPage "/auctions" }} + {{ .IsDescendant . }} → true +{{ end }} +``` + +The result would be wrong when rendering the `auction-1` page because we are comparing the section page to itself. + +> [!NOTE] +> Use the `$` to get the context passed into the template. + +```go-html-template +{{ with .Site.GetPage "/auctions" }} + {{ $.IsDescendant . }} → true +{{ end }} +``` + +> [!NOTE] +> Gaining a thorough understanding of context is critical for anyone writing template code. + +[`else`]: /functions/go-template/else/ +[`with`]: /functions/go-template/with/ diff --git a/docs/content/en/methods/page/IsHome.md b/docs/content/en/methods/page/IsHome.md new file mode 100644 index 0000000..9c7219d --- /dev/null +++ b/docs/content/en/methods/page/IsHome.md @@ -0,0 +1,26 @@ +--- +title: IsHome +description: Reports whether the given page is the home page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: bool + signatures: [PAGE.IsHome] +--- + +The `IsHome` method on a `Page` object returns `true` if the [page kind](g) is `home`. + +```tree +content/ +├── books/ +│ ├── book-1/ +│ │ └── index.md <-- kind = page +│ ├── book-2.md <-- kind = page +│ └── _index.md <-- kind = section +└── _index.md <-- kind = home +``` + +```go-html-template +{{ .IsHome }} +``` diff --git a/docs/content/en/methods/page/IsMenuCurrent.md b/docs/content/en/methods/page/IsMenuCurrent.md new file mode 100644 index 0000000..08ddc74 --- /dev/null +++ b/docs/content/en/methods/page/IsMenuCurrent.md @@ -0,0 +1,31 @@ +--- +title: IsMenuCurrent +description: Reports whether the given Page object matches the Page object associated with the given menu entry in the given menu. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: bool + signatures: [PAGE.IsMenuCurrent MENU MENUENTRY] +aliases: [/functions/ismenucurrent] +--- + +```go-html-template +{{ $currentPage := . }} +{{ range site.Menus.main }} + {{ if $currentPage.IsMenuCurrent .Menu . }} + {{ .Name }} + {{ else if $currentPage.HasMenuCurrent .Menu . }} + {{ .Name }} + {{ else }} + {{ .Name }} + {{ end }} +{{ end }} +``` + +See [menu templates][] for a complete example. + +> [!NOTE] +> When using this method you must either define the menu entry in front matter, or specify a `pageRef` property when defining the menu entry in your project configuration. + +[menu templates]: /templates/menu/#example diff --git a/docs/content/en/methods/page/IsNode.md b/docs/content/en/methods/page/IsNode.md new file mode 100644 index 0000000..55f7f7d --- /dev/null +++ b/docs/content/en/methods/page/IsNode.md @@ -0,0 +1,15 @@ +--- +title: IsNode +description: Reports whether the given page is a branch. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: bool + signatures: [PAGE.IsNode] +expiryDate: 2028-06-06 # deprecated 2026-06-06 in v0.163.0 +--- + +{{< deprecated-in 0.163.0 >}} +Use the [`IsBranch`](/methods/page/isbranch/) method instead. +{{< /deprecated-in >}} diff --git a/docs/content/en/methods/page/IsPage.md b/docs/content/en/methods/page/IsPage.md new file mode 100644 index 0000000..b122fbc --- /dev/null +++ b/docs/content/en/methods/page/IsPage.md @@ -0,0 +1,26 @@ +--- +title: IsPage +description: Reports whether the given page is a regular page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: bool + signatures: [PAGE.IsPage] +--- + +The `IsPage` method on a `Page` object returns `true` if the [page kind](g) is `page`. + +```tree +content/ +├── books/ +│ ├── book-1/ +│ │ └── index.md <-- kind = page +│ ├── book-2.md <-- kind = page +│ └── _index.md <-- kind = section +└── _index.md <-- kind = home +``` + +```go-html-template +{{ .IsPage }} +``` diff --git a/docs/content/en/methods/page/IsSection.md b/docs/content/en/methods/page/IsSection.md new file mode 100644 index 0000000..1749035 --- /dev/null +++ b/docs/content/en/methods/page/IsSection.md @@ -0,0 +1,26 @@ +--- +title: IsSection +description: Reports whether the given page is a section page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: bool + signatures: [PAGE.IsSection] +--- + +The `IsSection` method on a `Page` object returns `true` if the [page kind](g) is `section`. + +```tree +content/ +├── books/ +│ ├── book-1/ +│ │ └── index.md <-- kind = page +│ ├── book-2.md <-- kind = page +│ └── _index.md <-- kind = section +└── _index.md <-- kind = home +``` + +```go-html-template +{{ .IsSection }} +``` diff --git a/docs/content/en/methods/page/IsTranslated.md b/docs/content/en/methods/page/IsTranslated.md new file mode 100644 index 0000000..1aa554c --- /dev/null +++ b/docs/content/en/methods/page/IsTranslated.md @@ -0,0 +1,56 @@ +--- +title: IsTranslated +description: Reports whether the given page has one or more translations. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: bool + signatures: [PAGE.IsTranslated] +--- + +With this project configuration: + +{{< code-toggle file=hugo >}} +defaultContentLanguage = 'en' + +[languages.en] +contentDir = 'content/en' +label = 'English' +locale = 'en-US' +weight = 1 + +[languages.de] +contentDir = 'content/de' +label = 'Deutsch' +locale = 'de-DE' +weight = 2 +{{< /code-toggle >}} + +And this content: + +```tree +content/ +├── de/ +│ ├── books/ +│ │ └── book-1.md +│ └── _index.md +├── en/ +│ ├── books/ +│ │ ├── book-1.md +│ │ └── book-2.md +│ └── _index.md +└── _index.md +``` + +When rendering `content/en/books/book-1.md`: + +```go-html-template +{{ .IsTranslated }} → true +``` + +When rendering `content/en/books/book-2.md`: + +```go-html-template +{{ .IsTranslated }} → false +``` diff --git a/docs/content/en/methods/page/Keywords.md b/docs/content/en/methods/page/Keywords.md new file mode 100644 index 0000000..79bd073 --- /dev/null +++ b/docs/content/en/methods/page/Keywords.md @@ -0,0 +1,44 @@ +--- +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][]. + +{{< 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 +``` + +Keywords are also a useful [taxonomy][]: + +{{< code-toggle file=hugo >}} +[taxonomies] +tag = 'tags' +keyword = 'keywords' +category = 'categories' +{{< /code-toggle >}} + +[`delimit`]: /functions/collections/delimit/ +[related content]: /content-management/related-content/ +[taxonomy]: /content-management/taxonomies/ diff --git a/docs/content/en/methods/page/Kind.md b/docs/content/en/methods/page/Kind.md new file mode 100644 index 0000000..9cda910 --- /dev/null +++ b/docs/content/en/methods/page/Kind.md @@ -0,0 +1,32 @@ +--- +title: Kind +description: Returns the kind of the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [PAGE.Kind] +--- + +The [page kind](g) is one of `home`, `page`, `section`, `taxonomy`, or `term`. + +```tree +content/ +├── books/ +│ ├── book-1/ +│ │ └── index.md <-- kind = page +│ ├── book-2.md <-- kind = page +│ └── _index.md <-- kind = section +├── tags/ +│ ├── fiction/ +│ │ └── _index.md <-- kind = term +│ └── _index.md <-- kind = taxonomy +└── _index.md <-- kind = home +``` + +To get the value within a template: + +```go-html-template +{{ .Kind }} +``` diff --git a/docs/content/en/methods/page/Language.md b/docs/content/en/methods/page/Language.md new file mode 100644 index 0000000..68cbd88 --- /dev/null +++ b/docs/content/en/methods/page/Language.md @@ -0,0 +1,117 @@ +--- +title: Language +description: Returns the Language object for the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: langs.Language + signatures: [PAGE.Language] +--- + +The `Language` method on a `Page` object returns the `Language` object for the given page, derived from the language definition in your project configuration. + +You can also use the `Language` method on a `Site` object. See [details][]. + +## Methods + +Use these methods on the `Language` object. + +The examples below assume the following language definition. + +{{< code-toggle file=hugo >}} +[languages.de] +direction = 'ltr' +label = 'Deutsch' +locale = 'de-DE' +weight = 2 +{{< /code-toggle >}} + +`Direction` +: {{< new-in 0.158.0 />}} +: (`string`) Returns the [`direction`][] from the language definition. + + ```go-html-template + {{ .Language.Direction }} → ltr + ``` + +`IsDefault` +: {{< new-in 0.153.0 />}} +: (`bool`) Reports whether this is the [default language](g). + + ```go-html-template + {{ .Language.IsDefault }} → true + ``` + +`Label` +: {{< new-in 0.158.0 />}} +: (`string`) Returns the [`label`][] from the language definition. + + ```go-html-template + {{ .Language.Label }} → Deutsch + ``` + +`Lang` +: {{}} +: Use [`Name`](#name) instead. + +`LanguageCode` +: {{}} +: Use [`Locale`](#locale) instead. + +`LanguageDirection` +: {{}} + +Use [`Direction`](#direction) instead. +`LanguageName` +: {{}} +: Use [`Label`](#label) instead. + +`Locale` +: {{< new-in 0.158.0 />}} +: (`string`) Returns the [`locale`][] from the language definition, falling back to [`Name`](#name). + + ```go-html-template + {{ .Language.Locale }} → de-DE + ``` + +`Name` +: {{< new-in 0.153.0 />}} +: (`string`) Returns the language tag as defined by [RFC 5646][]. This is the lowercased key from the language definition. + + ```go-html-template + {{ .Language.Name }} → de + ``` + +`Weight` +: {{}} + +## Example + +Use the code below to create a language selector, allowing users to navigate between the different translated versions of the current page. + +```go-html-template {file="layouts/_partials/language-selector.html" copy=true} +{{ with .Rotate "language" }} + +{{ end }} +``` + +[RFC 5646]: https://datatracker.ietf.org/doc/html/rfc5646 +[`direction`]: /configuration/languages/#direction +[`label`]: /configuration/languages/#label +[`locale`]: /configuration/languages/#locale +[details]: /methods/site/language/ diff --git a/docs/content/en/methods/page/Lastmod.md b/docs/content/en/methods/page/Lastmod.md new file mode 100644 index 0000000..014ee0f --- /dev/null +++ b/docs/content/en/methods/page/Lastmod.md @@ -0,0 +1,36 @@ +--- +title: Lastmod +description: Returns the last modification date of the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: time.Time + signatures: [PAGE.Lastmod] +--- + +Set the last modification date in front matter: + +{{< code-toggle file=content/news/article-1.md fm=true >}} +title = 'Article 1' +lastmod = 2023-10-19T00:40:04-07:00 +{{< /code-toggle >}} + +The last modification date is a [`time.Time`][] value. Format and localize the value with the [`time.Format`][] function, or use it with any of the [time methods][]. + +```go-html-template +{{ .Lastmod | time.Format ":date_medium" }} → Oct 19, 2023 +``` + +In the example above we explicitly set the last modification date in front matter. With Hugo's default configuration, the `Lastmod` method returns the front matter value. This behavior is configurable, allowing you to: + +- Set the last modification date to the Author Date of the last Git commit for that file. See [`GitInfo`][] for details. +- Set fallback values if the last modification date is not defined in front matter. + +Learn more about [date configuration][]. + +[`GitInfo`]: /methods/page/gitinfo/ +[`time.Format`]: /functions/time/format/ +[date configuration]: /configuration/front-matter/#dates +[time methods]: /methods/time/ +[`time.Time`]: https://pkg.go.dev/time#Time diff --git a/docs/content/en/methods/page/Layout.md b/docs/content/en/methods/page/Layout.md new file mode 100644 index 0000000..4e34b1c --- /dev/null +++ b/docs/content/en/methods/page/Layout.md @@ -0,0 +1,40 @@ +--- +title: Layout +description: Returns the layout for the given page as defined in front matter. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [PAGE.Layout] +--- + +Specify the `layout` field in front matter to target a particular template. See [details][]. + +{{< code-toggle file=content/contact.md fm=true >}} +title = 'Contact' +layout = 'contact' +{{< /code-toggle >}} + +Hugo will render the page using contact.html. + +```tree +layouts/ +├── baseof.html +├── contact.html +├── home.html +├── page.html +├── section.html +├── taxonomy.html +└── term.html +``` + +Although rarely used within a template, you can access the value with: + +```go-html-template +{{ .Layout }} +``` + +The `Layout` method returns an empty string if the `layout` field in front matter is not defined. + +[details]: /templates/lookup-order/#target-a-template diff --git a/docs/content/en/methods/page/Len.md b/docs/content/en/methods/page/Len.md new file mode 100644 index 0000000..010da88 --- /dev/null +++ b/docs/content/en/methods/page/Len.md @@ -0,0 +1,14 @@ +--- +title: Len +description: Returns the length, in bytes, of the rendered content of the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: int + signatures: [PAGE.Len] +--- + +```go-html-template +{{ .Len }} → 42 +``` diff --git a/docs/content/en/methods/page/LinkTitle.md b/docs/content/en/methods/page/LinkTitle.md new file mode 100644 index 0000000..fea84b1 --- /dev/null +++ b/docs/content/en/methods/page/LinkTitle.md @@ -0,0 +1,29 @@ +--- +title: LinkTitle +description: Returns the link title of the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [PAGE.LinkTitle] +--- + +The `LinkTitle` method returns the `linkTitle` field as defined in front matter, falling back to the value returned by the [`Title`][] method. + +{{< 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 }} +``` + +[`Title`]: /methods/page/title/ diff --git a/docs/content/en/methods/page/Next.md b/docs/content/en/methods/page/Next.md new file mode 100644 index 0000000..9966030 --- /dev/null +++ b/docs/content/en/methods/page/Next.md @@ -0,0 +1,12 @@ +--- +title: Next +description: Returns the next page in a site's collection of regular pages, relative to the current page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Page + signatures: [PAGE.Next] +--- + +{{% include "/_common/methods/page/next-and-prev.md" %}} diff --git a/docs/content/en/methods/page/NextInSection.md b/docs/content/en/methods/page/NextInSection.md new file mode 100644 index 0000000..eb02c94 --- /dev/null +++ b/docs/content/en/methods/page/NextInSection.md @@ -0,0 +1,12 @@ +--- +title: NextInSection +description: Returns the next regular page in a section, relative to the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Page + signatures: [PAGE.NextInSection] +--- + +{{% include "/_common/methods/page/nextinsection-and-previnsection.md" %}} diff --git a/docs/content/en/methods/page/OutputFormats.md b/docs/content/en/methods/page/OutputFormats.md new file mode 100644 index 0000000..9814f37 --- /dev/null +++ b/docs/content/en/methods/page/OutputFormats.md @@ -0,0 +1,73 @@ +--- +title: OutputFormats +description: Returns a slice of OutputFormat objects, each representing one of the output formats enabled for the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: '[]OutputFormat' + signatures: [PAGE.OutputFormats] +--- + +{{% glossary-term "output format" %}} + +The `OutputFormats` method on a `Page` object returns a slice of `OutputFormat` objects, each representing one of the output formats enabled for the given page. See [details][]. + +## Methods + +Use these methods on the `OutputFormats` object. + +`Canonical` +: {{< new-in 0.154.4 />}} +: (`page.OutputFormat`) Returns the [canonical output format](g) for the current page, if defined. Once you have captured the object, use any of its [associated methods][]. + + ```go-html-template + {{ with .Site.Home.OutputFormats.Canonical }} + {{ .MediaType.Type }} → text/html + {{ .MediaType.MainType }} → text + {{ .MediaType.SubType }} → html + {{ .Name }} → html + {{ .Permalink }} → https://example.org/ + {{ .Rel }} → canonical + {{ .RelPermalink }} → / + {{ end }} + ``` + +`Get` +: (`page.OutputFormat`) Returns the `OutputFormat` object with the given identifier. Once you have captured the object, use any of its [associated methods][]. + + ```go-html-template + {{ with .Site.Home.OutputFormats.Get "rss" }} + {{ .MediaType.Type }} → application/rss+xml + {{ .MediaType.MainType }} → application + {{ .MediaType.SubType }} → rss + {{ .Name }} → rss + {{ .Permalink }} → https://example.org/index.xml + {{ .Rel }} → alternate + {{ .RelPermalink }} → /index.xml + {{ end }} + ``` + +## Examples + +To render a `link` element pointing to the [canonical output format](g) for the current page: + +```go-html-template +{{ with .OutputFormats.Canonical }} + {{ printf "" .Rel .MediaType.Type .Permalink | safeHTML }} +{{ end }} +``` + +To render an anchor element pointing to the `rss` output format for the current page: + +```go-html-template +{{ with .OutputFormats.Get "rss" }} + RSS Feed +{{ end }} +``` + +Please see the [link to output formats][] section to understand the importance of the construct above. + +[associated methods]: /methods/output-format/ +[details]: /configuration/output-formats/ +[link to output formats]: /configuration/output-formats/#link-to-output-formats diff --git a/docs/content/en/methods/page/Page.md b/docs/content/en/methods/page/Page.md new file mode 100644 index 0000000..5f10a69 --- /dev/null +++ b/docs/content/en/methods/page/Page.md @@ -0,0 +1,35 @@ +--- +title: Page +description: Returns the Page object of the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Page + signatures: [PAGE.Page] +--- + +This is a convenience method, useful within _partial_ templates that are called from both _shortcode_ and other template types. + +```go-html-template {file="layouts/_shortcodes/foo.html"} +{{ partial "my-partial.html" . }} +``` + +When the _shortcode_ template calls the _partial_ template, it passes the current [context](g) (the dot). The context includes identifiers such as `Page`, `Params`, `Inner`, and `Name`. + +```go-html-template {file="layouts/page.html"} +{{ partial "my-partial.html" . }} +``` + +When the _page_ template calls the _partial_ template, it also passes the current context (the dot). But in this case, the dot _is_ the `Page` object. + +```go-html-template {file="layouts/_partials/my-partial.html"} +The page title is: {{ .Page.Title }} +``` + +To handle both scenarios, the _partial_ template must be able to access the `Page` object with `Page.Page`. + +> [!NOTE] +> And yes, that means you can do `.Page.Page.Page.Page.Title` too. +> +> But don't. diff --git a/docs/content/en/methods/page/Pages.md b/docs/content/en/methods/page/Pages.md new file mode 100644 index 0000000..9640b80 --- /dev/null +++ b/docs/content/en/methods/page/Pages.md @@ -0,0 +1,82 @@ +--- +title: Pages +description: Returns a collection of regular pages within the current section, and section pages of immediate descendant sections. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pages + signatures: [PAGE.Pages] +--- + +The `Pages` method on a `Page` object is available to these [page kinds](g): `home`, `section`, `taxonomy`, and `term`. The templates for these page kinds receive a page [collection](g) in [context](g), in the [default sort order](g). + +Range through the page collection in your template: + +```go-html-template +{{ range .Pages.ByTitle }} +

    {{ .Title }}

    +{{ end }} +``` + +Consider this content structure: + +```tree +content/ +├── lessons/ +│ ├── lesson-1/ +│ │ ├── _index.md +│ │ ├── part-1.md +│ │ └── part-2.md +│ ├── lesson-2/ +│ │ ├── resources/ +│ │ │ ├── task-list.md +│ │ │ └── worksheet.md +│ │ ├── _index.md +│ │ ├── part-1.md +│ │ └── part-2.md +│ ├── _index.md +│ ├── grading-policy.md +│ └── lesson-plan.md +├── _index.md +├── contact.md +└── legal.md +``` + +When rendering the home page, the `Pages` method returns: + + contact.md + legal.md + lessons/_index.md + +When rendering the lessons page, the `Pages` method returns: + + lessons/grading-policy.md + lessons/lesson-plan.md + lessons/lesson-1/_index.md + lessons/lesson-2/_index.md + +When rendering lesson-1, the `Pages` method returns: + + lessons/lesson-1/part-1.md + lessons/lesson-1/part-2.md + +When rendering lesson-2, the `Pages` method returns: + + lessons/lesson-2/part-1.md + lessons/lesson-2/part-2.md + lessons/lesson-2/resources/task-list.md + lessons/lesson-2/resources/worksheet.md + +In the last example, the collection includes pages in the resources subdirectory. That directory is not a [section](g)---it does not contain an `_index.md` file. Its contents are part of the lesson-2 section. + +> [!NOTE] +> When used with a `Site` object, the `Pages` method recursively returns all pages within the site. See [details][]. + +```go-html-template +{{ range .Site.Pages.ByTitle }} +

    {{ .Title }}

    +{{ end }} +``` + +[details]: /methods/site/pages/ diff --git a/docs/content/en/methods/page/Paginate.md b/docs/content/en/methods/page/Paginate.md new file mode 100644 index 0000000..2e18e42 --- /dev/null +++ b/docs/content/en/methods/page/Paginate.md @@ -0,0 +1,47 @@ +--- +title: Paginate +description: Paginates a collection of pages. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pager + signatures: ['PAGE.Paginate COLLECTION [N]'] +--- + +Pagination is the process of splitting a list page into two or more pagers, where each pager contains a subset of the page collection and navigation links to other pagers. + +By default, the number of elements on each pager is determined by your [project configuration][]. The default is `10`. Override that value by providing a second argument, an integer, when calling the `Paginate` method. + +> [!NOTE] +> There is also a `Paginator` method on `Page` objects, but it can neither filter nor sort the page collection. +> +> The `Paginate` method is more flexible. + +You can invoke pagination in [home][], [section][], [taxonomy][], and [term][] templates. + +```go-html-template {file="layouts/section.html"} +{{ $pages := where .Site.RegularPages "Section" "articles" }} +{{ $pages = $pages.ByTitle }} +{{ range (.Paginate $pages 7).Pages }} +

    {{ .Title }}

    +{{ end }} +{{ partial "pagination.html" . }} +``` + +In the example above, we: + +1. Build a page collection +1. Sort the collection by title +1. Paginate the collection, with 7 elements per pager +1. Range over the paginated page collection, rendering a link to each page +1. Call the embedded pagination template to create navigation links between pagers + +> [!NOTE] +> Please note that the results of pagination are cached. Once you have invoked either the `Paginator` or `Paginate` method, the paginated collection is immutable. Additional invocations of these methods will have no effect. + +[home]: /templates/types/#home +[project configuration]: /configuration/pagination/ +[section]: /templates/types/#section +[taxonomy]: /templates/types/#taxonomy +[term]: /templates/types/#term diff --git a/docs/content/en/methods/page/Paginator.md b/docs/content/en/methods/page/Paginator.md new file mode 100644 index 0000000..08658dc --- /dev/null +++ b/docs/content/en/methods/page/Paginator.md @@ -0,0 +1,40 @@ +--- +title: Paginator +description: Paginates the collection of regular pages received in context. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pager + signatures: [PAGE.Paginator] +--- + +Pagination is the process of splitting a list page into two or more pagers, where each pager contains a subset of the page collection and navigation links to other pagers. + +The number of elements on each pager is determined by your [project configuration][]. The default is `10`. + +You can invoke pagination in [home][], [section][], [taxonomy][], and [term][] templates. Each of these receives a collection of regular pages in [context](g). When you invoke the `Paginator` method, it paginates the page collection received in context. + +```go-html-template {file="layouts/section.html"} +{{ range .Paginator.Pages }} +

    {{ .LinkTitle }}

    +{{ end }} +{{ partial "pagination.html" . }} +``` + +In the example above, the embedded pagination template creates navigation links between pagers. + +> [!NOTE] +> Although simple to invoke, with the `Paginator` method you can neither filter nor sort the page collection. It acts upon the page collection received in context. +> +> The [`Paginate`][] method is more flexible, and strongly recommended. + +> [!NOTE] +> Please note that the results of pagination are cached. Once you have invoked either the `Paginator` or `Paginate` method, the paginated collection is immutable. Additional invocations of these methods will have no effect. + +[`Paginate`]: /methods/page/paginate/ +[home]: /templates/types/#home +[project configuration]: /configuration/pagination/ +[section]: /templates/types/#section +[taxonomy]: /templates/types/#taxonomy +[term]: /templates/types/#term diff --git a/docs/content/en/methods/page/Param.md b/docs/content/en/methods/page/Param.md new file mode 100644 index 0000000..b07c1cd --- /dev/null +++ b/docs/content/en/methods/page/Param.md @@ -0,0 +1,48 @@ +--- +title: Param +description: Returns a page parameter with the given key, falling back to a site parameter if present. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: any + signatures: [PAGE.Param KEY] +aliases: [/functions/param] +--- + +The `Param` method on a `Page` object looks for the given `KEY` in page parameters, and returns the corresponding value. If it cannot find the `KEY` in page parameters, it looks for the `KEY` in site parameters. If it cannot find the `KEY` in either location, the `Param` method returns `nil`. + +Site and theme developers commonly set parameters at the site level, allowing content authors to override those parameters at the page level. + +For example, to show a table of contents on every page, but allow authors to hide the table of contents as needed: + +Configuration: + +{{< code-toggle file=hugo >}} +[params] +display_toc = true +{{< /code-toggle >}} + +Content: + +{{< code-toggle file=content/example.md fm=true >}} +title = 'Example' +date = 2023-01-01 +draft = false +[params] +display_toc = false +{{< /code-toggle >}} + +Template: + +```go-html-template +{{ if .Param "display_toc" }} + {{ .TableOfContents }} +{{ end }} +``` + +The `Param` method returns the value associated with the given `KEY`, regardless of whether the value is truthy or falsy. If you need to ignore falsy values, use this construct instead: + +```go-html-template +{{ or .Params.foo site.Params.foo }} +``` diff --git a/docs/content/en/methods/page/Params.md b/docs/content/en/methods/page/Params.md new file mode 100644 index 0000000..c65d418 --- /dev/null +++ b/docs/content/en/methods/page/Params.md @@ -0,0 +1,42 @@ +--- +title: Params +description: Returns a map of custom parameters as defined in the front matter of the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: maps.Params + signatures: [PAGE.Params] +--- + +By way of example, consider this front matter: + +{{< code-toggle file=content/annual-conference.md fm=true >}} +title = 'Annual conference' +date = 2023-10-17T15:11:37-07:00 +[params] +display_related = true +key-with-hyphens = 'must use index function' +[params.author] + email = 'jsmith@example.org' + name = 'John Smith' +{{< /code-toggle >}} + +The `title` and `date` fields are standard [front matter fields][], while the other fields are user-defined. + +Access the custom fields by [chaining](g) the [identifiers](g) when needed: + +```go-html-template +{{ .Params.display_related }} → true +{{ .Params.author.email }} → jsmith@example.org +{{ .Params.author.name }} → John Smith +``` + +In the template example above, each of the keys is a valid identifier. For example, none of the keys contains a hyphen. To access a key that is not a valid identifier, use the [`index`][] function: + +```go-html-template +{{ index .Params "key-with-hyphens" }} → must use index function +``` + +[`index`]: /functions/collections/indexfunction/ +[front matter fields]: /content-management/front-matter/#fields diff --git a/docs/content/en/methods/page/Parent.md b/docs/content/en/methods/page/Parent.md new file mode 100644 index 0000000..3e40e42 --- /dev/null +++ b/docs/content/en/methods/page/Parent.md @@ -0,0 +1,52 @@ +--- +title: Parent +description: Returns the Page object of the parent section of the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Page + signatures: [PAGE.Parent] +--- + +{{% glossary-term section %}} + +> [!NOTE] +> The parent section of a regular page is the [current section][]. + +Consider this content structure: + +```tree +content/ +├── auctions/ +│ ├── 2023-11/ +│ │ ├── _index.md <-- parent: auctions +│ │ ├── auction-1.md +│ │ └── auction-2.md <-- parent: 2023-11 +│ ├── 2023-12/ +│ │ ├── _index.md +│ │ ├── auction-3.md +│ │ └── auction-4.md +│ ├── _index.md <-- parent: home +│ ├── bidding.md +│ └── payment.md <-- parent: auctions +├── books/ +│ ├── _index.md <-- parent: home +│ ├── book-1.md +│ └── book-2.md <-- parent: books +├── films/ +│ ├── _index.md <-- parent: home +│ ├── film-1.md +│ └── film-2.md <-- parent: films +└── _index.md <-- parent: nil +``` + +In the example above, note the parent section of the home page is nil. Code defensively by verifying existence of the parent section before calling methods on its `Page` object. To create a link to the parent section page of the current page: + +```go-html-template +{{ with .Parent }} + {{ .LinkTitle }} +{{ end }} +``` + +[current section]: /methods/page/currentsection/ diff --git a/docs/content/en/methods/page/Path.md b/docs/content/en/methods/page/Path.md new file mode 100644 index 0000000..2ffa934 --- /dev/null +++ b/docs/content/en/methods/page/Path.md @@ -0,0 +1,131 @@ +--- +title: Path +description: Returns the logical path of the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [PAGE.Path] +--- + +The `Path` method on a `Page` object returns the logical path of the given page, regardless of whether the page is backed by a file. + +{{% glossary-term "logical path" %}} + +```go-html-template +{{ .Path }} → /posts/post-1 +``` + +The value returned by the `Path` method on a `Page` object is independent of content format, language, and URL modifiers such as the `slug` and `url` front matter fields. + +## Examples + +### Monolingual project + +Note that the logical path is independent of content format and URL modifiers. + +File path|Front matter slug|Logical path +:--|:--|:-- +`content/_index.md`||`/` +`content/posts/_index.md`||`/posts` +`content/posts/post-1.md`|`foo`|`/posts/post-1` +`content/posts/post-2.html`|`bar`|`/posts/post-2` + +### Multilingual site + +Note that the logical path is independent of content format, language identifiers, and URL modifiers. + +File path|Front matter slug|Logical path +:--|:--|:-- +`content/_index.en.md`||`/` +`content/_index.de.md`||`/` +`content/posts/_index.en.md`||`/posts` +`content/posts/_index.de.md`||`/posts` +`content/posts/posts-1.en.md`|`foo`|`/posts/post-1` +`content/posts/posts-1.de.md`|`foo`|`/posts/post-1` +`content/posts/posts-2.en.html`|`bar`|`/posts/post-2` +`content/posts/posts-2.de.html`|`bar`|`/posts/post-2` + +### Pages not backed by a file + +The `Path` method on a `Page` object returns a value regardless of whether the page is backed by a file. + +```tree +content/ +└── posts/ + └── post-1.md <-- front matter: tags = ['hugo'] +``` + +When you build the site: + +```tree +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: + +```tree +content/ +└── s1/ + ├── p1/ + │ └── index.md + └── p2.md +``` + +The same content represented as a logical tree: + +```tree +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/ +[`Shortcode.Ref`]: /methods/shortcode/ref/ +[`Shortcode.RelRef`]: /methods/shortcode/relref/ +[`Site.GetPage`]: /methods/site/getpage/ +[`ref`]: /shortcodes/ref/ +[`relref`]: /shortcodes/relref/ +[`urls.Ref`]: /functions/urls/ref/ +[`urls.RelRef`]: /functions/urls/relref/ diff --git a/docs/content/en/methods/page/Permalink.md b/docs/content/en/methods/page/Permalink.md new file mode 100644 index 0000000..ffd6ede --- /dev/null +++ b/docs/content/en/methods/page/Permalink.md @@ -0,0 +1,24 @@ +--- +title: Permalink +description: Returns the permalink of the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [PAGE.Permalink] +--- + +Project configuration: + +{{< code-toggle file=hugo >}} +title = 'Documentation' +baseURL = 'https://example.org/docs/' +{{< /code-toggle >}} + +Template: + +```go-html-template +{{ $page := .Site.GetPage "/about" }} +{{ $page.Permalink }} → https://example.org/docs/about/ +``` diff --git a/docs/content/en/methods/page/Plain.md b/docs/content/en/methods/page/Plain.md new file mode 100644 index 0000000..919e834 --- /dev/null +++ b/docs/content/en/methods/page/Plain.md @@ -0,0 +1,23 @@ +--- +title: Plain +description: Returns the rendered content of the given page, removing all HTML tags. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [PAGE.Plain] +--- + +The `Plain` method on a `Page` object renders Markdown and [shortcodes](g) to HTML, then strips the HTML [tags][]. It does not strip HTML [entities][]. + +To prevent Go's [`html/template`][] package from escaping HTML entities, pass the result through the [`htmlUnescape`][] function. + +```go-html-template +{{ .Plain | htmlUnescape }} +``` + +[`html/template`]: https://pkg.go.dev/html/template +[`htmlUnescape`]: /functions/transform/htmlunescape/ +[entities]: https://developer.mozilla.org/en-US/docs/Glossary/Entity +[tags]: https://developer.mozilla.org/en-US/docs/Glossary/Tag diff --git a/docs/content/en/methods/page/PlainWords.md b/docs/content/en/methods/page/PlainWords.md new file mode 100644 index 0000000..043f095 --- /dev/null +++ b/docs/content/en/methods/page/PlainWords.md @@ -0,0 +1,31 @@ +--- +title: PlainWords +description: Calls the Plain method, splits the result into a slice of words, and returns the slice. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: '[]string' + signatures: [PAGE.PlainWords] +--- + +The `PlainWords` method on a `Page` object calls the [`Plain`][] method, then uses Go's [`strings.Fields`][] function to split the result into words. + +> [!NOTE] +> `Fields` splits the string `s` around each instance of one or more consecutive whitespace characters, as defined by [`unicode.IsSpace`][], returning a slice of substrings of `s` or an empty slice if `s` contains only whitespace. + +As a result, elements within the slice may contain leading or trailing punctuation. + +```go-html-template +{{ .PlainWords }} +``` + +To determine the approximate number of unique words on a page: + +```go-html-template +{{ .PlainWords | uniq }} → 42 +``` + +[`Plain`]: /methods/page/plain/ +[`strings.Fields`]: https://pkg.go.dev/strings#Fields +[`unicode.IsSpace`]: https://pkg.go.dev/unicode#IsSpace diff --git a/docs/content/en/methods/page/Prev.md b/docs/content/en/methods/page/Prev.md new file mode 100644 index 0000000..5a6e216 --- /dev/null +++ b/docs/content/en/methods/page/Prev.md @@ -0,0 +1,12 @@ +--- +title: Prev +description: Returns the previous page in a site's collection of regular pages, relative to the current page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Page + signatures: [PAGE.Prev] +--- + +{{% include "/_common/methods/page/next-and-prev.md" %}} diff --git a/docs/content/en/methods/page/PrevInSection.md b/docs/content/en/methods/page/PrevInSection.md new file mode 100644 index 0000000..14d3ca0 --- /dev/null +++ b/docs/content/en/methods/page/PrevInSection.md @@ -0,0 +1,12 @@ +--- +title: PrevInSection +description: Returns the previous regular page in a section, relative to the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Page + signatures: [PAGE.PrevInSection] +--- + +{{% include "/_common/methods/page/nextinsection-and-previnsection.md" %}} diff --git a/docs/content/en/methods/page/PublishDate.md b/docs/content/en/methods/page/PublishDate.md new file mode 100644 index 0000000..6e4d532 --- /dev/null +++ b/docs/content/en/methods/page/PublishDate.md @@ -0,0 +1,32 @@ +--- +title: PublishDate +description: Returns the publish date of the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: time.Time + signatures: [PAGE.PublishDate] +--- + +By default, Hugo excludes pages with future publish dates when building your project. To include future pages, use the `--buildFuture` command line flag. + +Set the publish date in front matter: + +{{< code-toggle file=content/news/article-1.md fm=true >}} +title = 'Article 1' +publishDate = 2023-10-19T00:40:04-07:00 +{{< /code-toggle >}} + +The publish date is a [time.Time][] value. Format and localize the value with the [`time.Format`][] function, or use it with any of the [time methods][]. + +```go-html-template +{{ .PublishDate | time.Format ":date_medium" }} → Oct 19, 2023 +``` + +In the example above we explicitly set the publish date in front matter. With Hugo's default configuration, the `PublishDate` method returns the front matter value. This behavior is configurable, allowing you to set fallback values if the publish date is not defined in front matter. See [details][]. + +[`time.Format`]: /functions/time/format/ +[details]: /configuration/front-matter/#dates +[time methods]: /methods/time/ +[time.Time]: https://pkg.go.dev/time#Time diff --git a/docs/content/en/methods/page/RawContent.md b/docs/content/en/methods/page/RawContent.md new file mode 100644 index 0000000..ff08615 --- /dev/null +++ b/docs/content/en/methods/page/RawContent.md @@ -0,0 +1,23 @@ +--- +title: RawContent +description: Returns the raw content of the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [PAGE.RawContent] +--- + +The `RawContent` method on a `Page` object returns the raw content. The raw content does not include front matter. + +```go-html-template +{{ .RawContent }} +``` + +This is useful when rendering a page in a plain text [output format](g). + +> [!NOTE] +> [Shortcodes](g) within the content are not rendered. To get the raw content with shortcodes rendered, use the [`RenderShortcodes`][] method on a `Page` object. + +[`RenderShortcodes`]: /methods/page/rendershortcodes/ diff --git a/docs/content/en/methods/page/ReadingTime.md b/docs/content/en/methods/page/ReadingTime.md new file mode 100644 index 0000000..2fd2ea4 --- /dev/null +++ b/docs/content/en/methods/page/ReadingTime.md @@ -0,0 +1,47 @@ +--- +title: ReadingTime +description: Returns the estimated reading time, in minutes, for the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: int + signatures: [PAGE.ReadingTime] +--- + +The estimated reading time is calculated by dividing the number of words in the content by the reading speed. + +By default, Hugo assumes a reading speed of 212 words per minute. For CJK languages, it assumes 500 words per minute. + +```go-html-template +{{ printf "Estimated reading time: %d minutes" .ReadingTime }} +``` + +Reading speed varies by language. Create language-specific estimated reading times on your multilingual project using site parameters. + +{{< code-toggle file=hugo >}} +[languages] + [languages.de] + contentDir = 'content/de' + label = 'Deutsch' + locale = 'de-DE' + weight = 2 + [languages.de.params] + reading_speed = 179 + [languages.en] + contentDir = 'content/en' + label = 'English' + locale = 'en-US' + weight = 1 + [languages.en.params] + reading_speed = 228 +{{< /code-toggle >}} + +Then in your template: + +```go-html-template +{{ $readingTime := div (float .WordCount) .Site.Params.reading_speed }} +{{ $readingTime = math.Ceil $readingTime }} +``` + +We cast the `.WordCount` to a float to obtain a float when we divide by the reading speed. Then round up to the nearest integer. diff --git a/docs/content/en/methods/page/Ref.md b/docs/content/en/methods/page/Ref.md new file mode 100644 index 0000000..23da625 --- /dev/null +++ b/docs/content/en/methods/page/Ref.md @@ -0,0 +1,37 @@ +--- +title: Ref +description: Returns the absolute URL of the page with the given path, language, and output format. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [PAGE.Ref OPTIONS] +--- + +## Usage + +The `Ref` method requires a single argument: an options map. + +## Options + +{{% include "_common/ref-and-relref-options.md" %}} + +## Examples + +The following examples show the rendered output for a page on the English version of the site: + +```go-html-template +{{ $opts := dict "path" "/books/book-1" }} +{{ .Ref $opts }} → https://example.org/en/books/book-1/ + +{{ $opts := dict "path" "/books/book-1" "lang" "de" }} +{{ .Ref $opts }} → https://example.org/de/books/book-1/ + +{{ $opts := dict "path" "/books/book-1" "lang" "de" "outputFormat" "json" }} +{{ .Ref $opts }} → https://example.org/de/books/book-1/index.json +``` + +## Error handling + +{{% include "_common/ref-and-relref-error-handling.md" %}} diff --git a/docs/content/en/methods/page/RegularPages.md b/docs/content/en/methods/page/RegularPages.md new file mode 100644 index 0000000..0b0676b --- /dev/null +++ b/docs/content/en/methods/page/RegularPages.md @@ -0,0 +1,79 @@ +--- +title: RegularPages +description: Returns a collection of regular pages within the current section. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pages + signatures: [PAGE.RegularPages] +--- + +The `RegularPages` method on a `Page` object is available to these [page kinds](g): `home`, `section`, `taxonomy`, and `term`. The templates for these page kinds receive a page [collection](g) in [context](g), in the [default sort order](g). + +Range through the page collection in your template: + +```go-html-template +{{ range .RegularPages.ByTitle }} +

    {{ .Title }}

    +{{ end }} +``` + +Consider this content structure: + +```tree +content/ +├── lessons/ +│ ├── lesson-1/ +│ │ ├── _index.md +│ │ ├── part-1.md +│ │ └── part-2.md +│ ├── lesson-2/ +│ │ ├── resources/ +│ │ │ ├── task-list.md +│ │ │ └── worksheet.md +│ │ ├── _index.md +│ │ ├── part-1.md +│ │ └── part-2.md +│ ├── _index.md +│ ├── grading-policy.md +│ └── lesson-plan.md +├── _index.md +├── contact.md +└── legal.md +``` + +When rendering the home page, the `RegularPages` method returns: + + contact.md + legal.md + +When rendering the lessons page, the `RegularPages` method returns: + + lessons/grading-policy.md + lessons/lesson-plan.md + +When rendering lesson-1, the `RegularPages` method returns: + + lessons/lesson-1/part-1.md + lessons/lesson-1/part-2.md + +When rendering lesson-2, the `RegularPages` method returns: + + lessons/lesson-2/part-1.md + lessons/lesson-2/part-2.md + lessons/lesson-2/resources/task-list.md + lessons/lesson-2/resources/worksheet.md + +In the last example, the collection includes pages in the resources subdirectory. That directory is not a [section](g)---it does not contain an `_index.md` file. Its contents are part of the lesson-2 section. + +> [!NOTE] +> When used with the `Site` object, the `RegularPages` method recursively returns all regular pages within the site. See [details][]. + +```go-html-template +{{ range .Site.RegularPages.ByTitle }} +

    {{ .Title }}

    +{{ end }} +``` + +[details]: /methods/site/regularpages/ diff --git a/docs/content/en/methods/page/RegularPagesRecursive.md b/docs/content/en/methods/page/RegularPagesRecursive.md new file mode 100644 index 0000000..332a567 --- /dev/null +++ b/docs/content/en/methods/page/RegularPagesRecursive.md @@ -0,0 +1,83 @@ +--- +title: RegularPagesRecursive +description: Returns a collection of regular pages within the current section, and regular pages within all descendant sections. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pages + signatures: [PAGE.RegularPagesRecursive] +--- + +The `RegularPagesRecursive` method on a `Page` object is available to these [page kinds](g): `home`, `section`, `taxonomy`, and `term`. The templates for these page kinds receive a page [collection](g) in [context](g), in the [default sort order](g). + +Range through the page collection in your template: + +```go-html-template +{{ range .RegularPagesRecursive.ByTitle }} +

    {{ .Title }}

    +{{ end }} +``` + +Consider this content structure: + +```tree +content/ +├── lessons/ +│ ├── lesson-1/ +│ │ ├── _index.md +│ │ ├── part-1.md +│ │ └── part-2.md +│ ├── lesson-2/ +│ │ ├── resources/ +│ │ │ ├── task-list.md +│ │ │ └── worksheet.md +│ │ ├── _index.md +│ │ ├── part-1.md +│ │ └── part-2.md +│ ├── _index.md +│ ├── grading-policy.md +│ └── lesson-plan.md +├── _index.md +├── contact.md +└── legal.md +``` + +When rendering the home page, the `RegularPagesRecursive` method returns: + + contact.md + lessons/grading-policy.md + legal.md + lessons/lesson-plan.md + lessons/lesson-2/part-1.md + lessons/lesson-1/part-1.md + lessons/lesson-2/part-2.md + lessons/lesson-1/part-2.md + lessons/lesson-2/resources/task-list.md + lessons/lesson-2/resources/worksheet.md + +When rendering the lessons page, the `RegularPagesRecursive` method returns: + + lessons/grading-policy.md + lessons/lesson-plan.md + lessons/lesson-2/part-1.md + lessons/lesson-1/part-1.md + lessons/lesson-2/part-2.md + lessons/lesson-1/part-2.md + lessons/lesson-2/resources/task-list.md + lessons/lesson-2/resources/worksheet.md + +When rendering lesson-1, the `RegularPagesRecursive` method returns: + + lessons/lesson-1/part-1.md + lessons/lesson-1/part-2.md + +When rendering lesson-2, the `RegularPagesRecursive` method returns: + + lessons/lesson-2/part-1.md + lessons/lesson-2/part-2.md + lessons/lesson-2/resources/task-list.md + lessons/lesson-2/resources/worksheet.md + +> [!NOTE] +> The `RegularPagesRecursive` method is not available on a `Site` object. diff --git a/docs/content/en/methods/page/RelPermalink.md b/docs/content/en/methods/page/RelPermalink.md new file mode 100644 index 0000000..ba03dc1 --- /dev/null +++ b/docs/content/en/methods/page/RelPermalink.md @@ -0,0 +1,24 @@ +--- +title: RelPermalink +description: Returns the relative permalink of the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [PAGE.RelPermalink] +--- + +Project configuration: + +{{< code-toggle file=hugo >}} +title = 'Documentation' +baseURL = 'https://example.org/docs/' +{{< /code-toggle >}} + +Template: + +```go-html-template +{{ $page := .Site.GetPage "/about" }} +{{ $page.RelPermalink }} → /docs/about/ +``` diff --git a/docs/content/en/methods/page/RelRef.md b/docs/content/en/methods/page/RelRef.md new file mode 100644 index 0000000..21ab02c --- /dev/null +++ b/docs/content/en/methods/page/RelRef.md @@ -0,0 +1,37 @@ +--- +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: [PAGE.RelRef OPTIONS] +--- + +## Usage + +The `RelRef` method requires 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/page/Render.md b/docs/content/en/methods/page/Render.md new file mode 100644 index 0000000..0441217 --- /dev/null +++ b/docs/content/en/methods/page/Render.md @@ -0,0 +1,71 @@ +--- +title: Render +description: Renders the given template with the given page as context. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: template.HTML + signatures: [PAGE.Render NAME] +aliases: [/functions/render] +--- + +Typically used when ranging over a page collection, the `Render` method on a `Page` object renders the given template, passing the given page as context. + +```go-html-template +{{ range site.RegularPages }} +

    {{ .LinkTitle }}

    + {{ .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: + +```tree +layouts/ +├── books/ +│ └── li.html <-- used when content type is "books" +├── baseof.html +├── home.html +├── li.html <-- used for other content types +├── page.html +├── section.html +├── taxonomy.html +└── term.html +``` + +And this template: + +```go-html-template +
      + {{ range site.RegularPages.ByDate }} + {{ .Render "li" }} + {{ end }} +
    +``` + +When rendering content of type "books" the `Render` method calls: + +```text +layouts/books/li.html +``` + +For all other content types the `Render` methods calls: + +```text +layouts/li.html +``` + +See [content views][] for more examples. + +[`partial`]: /functions/partials/include/ +[content views]: /templates/types/#content-view diff --git a/docs/content/en/methods/page/RenderShortcodes.md b/docs/content/en/methods/page/RenderShortcodes.md new file mode 100644 index 0000000..c333c30 --- /dev/null +++ b/docs/content/en/methods/page/RenderShortcodes.md @@ -0,0 +1,87 @@ +--- +title: RenderShortcodes +description: Renders all shortcodes in the content of the given page, preserving the surrounding markup. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: template.HTML + signatures: [PAGE.RenderShortcodes] +--- + +Use this method in _shortcode_ templates to compose a page from multiple content files, while preserving a global context for footnotes and the table of contents. + +For example: + +```go-html-template {file="layouts/_shortcodes/include.html" copy=true} +{{ with .Get 0 }} + {{ with $.Page.GetPage . }} + {{- .RenderShortcodes }} + {{ else }} + {{ errorf "The %q shortcode was unable to find %q. See %s" $.Name . $.Position }} + {{ end }} +{{ else }} + {{ errorf "The %q shortcode requires a positional parameter indicating the logical path of the file to include. See %s" .Name .Position }} +{{ end }} +``` + +Then call the shortcode in your Markdown: + +```md {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: + +- `{{}}` 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 + +```md {file="content/about.md"} ++++ +title = 'About' +date = 2023-10-07T12:28:33-07:00 ++++ + +{{}} + +An *emphasized* word. +``` + +With this template code: + +```go-html-template +{{ $p := site.GetPage "/about" }} +{{ $p.RenderShortcodes }} +``` + +Hugo renders this:; + +```html +https://example.org/privacy/ + +An *emphasized* word. +``` + +Note that the shortcode within the content file was rendered, but the surrounding Markdown was preserved. + +## Limitations + +The primary use case for `.RenderShortcodes` is inclusion of Markdown content. If you try to use `.RenderShortcodes` inside `HTML` blocks when inside Markdown, you will get a warning similar to this: + +```text +WARN .RenderShortcodes detected inside HTML block in "/content/mypost.md"; this may not be what you intended ... +``` + +The above warning can be turned off is this is what you really want. diff --git a/docs/content/en/methods/page/RenderString.md b/docs/content/en/methods/page/RenderString.md new file mode 100644 index 0000000..745e71e --- /dev/null +++ b/docs/content/en/methods/page/RenderString.md @@ -0,0 +1,49 @@ +--- +title: RenderString +description: Renders markup to HTML. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: template.HTML + signatures: ['PAGE.RenderString [OPTIONS] MARKUP'] +aliases: [/functions/renderstring] +--- + +The `RenderString` method on a `Page` object renders markup to HTML. + +```go-html-template +{{ $s := "An *emphasized* word" }} +{{ $s | .RenderString }} → An emphasized word +``` + +## Options + +The `RenderString` method on a `Page` object accepts an options map. + +`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. + +## Examples + +Render Markdown content to HTML in block display mode: + +```go-html-template +{{ $opts := dict "display" "block" }} +{{ $s | .RenderString $opts }} →

    An emphasized word

    +``` + +Render [Pandoc] content to HTML in block display mode: + +```go-html-template +{{ $s := "H~2~O" }} + +{{ $opts := dict "markup" "pandoc" "display" "block" }} +{{ $s | .RenderString $opts }} → H2O +``` + +[Pandoc]: /content-management/formats/#pandoc +[markup identifier]: /content-management/formats/#classification diff --git a/docs/content/en/methods/page/Resources.md b/docs/content/en/methods/page/Resources.md new file mode 100644 index 0000000..8861ec0 --- /dev/null +++ b/docs/content/en/methods/page/Resources.md @@ -0,0 +1,88 @@ +--- +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 + +Use these methods on the `Resources` object. + +`ByType` +: (`resource.Resources`) Returns a collection of page resources of the given [media type][], or nil if none found. The media type is typically one of `image`, `text`, `audio`, `video`, or `application`. + + ```go-html-template + {{ range .Resources.ByType "image" }} + + {{ end }} + ``` + + When working with global resources instead of page resources, use the [`resources.ByType`][] function. + +`Get` +: (`resource.Resource`) Returns a page resource from the given path, or nil if none found. + + ```go-html-template + {{ with .Resources.Get "images/a.jpg" }} + + {{ end }} + ``` + + When working with global resources instead of page resources, use the [`resources.Get`][] function. + +`GetMatch` +: (`resource.Resource`) Returns the first page resource from paths matching the given [glob pattern](g), or nil if none found. + + ```go-html-template + {{ with .Resources.GetMatch "images/*.jpg" }} + + {{ end }} + ``` + + When working with global resources instead of page resources, use the [`resources.GetMatch`][] function. + +`Match` +: (`resource.Resources`) Returns a collection of page resources from paths matching the given [glob pattern](g), or nil if none found. + + ```go-html-template + {{ range .Resources.Match "images/*.jpg" }} + + {{ end }} + ``` + + When working with global resources instead of page resources, use the [`resources.Match`][] function. + +`Mount` +: {{< new-in 0.140.0 />}} +: (`ResourceGetter`) Mounts the given resources from the two arguments base (`string`) to the given target path (`string`) and returns an object that implements [Get](#get). Note that leading slashes in target marks an absolute path. Relative target paths allows you to mount resources relative to another set, e.g. a [Page bundle][]: + + ```go-html-template + {{ $common := resources.Match "/js/headlessui/*.*" }} + {{ $importContext := (slice $.Page ($common.Mount "/js/headlessui" ".")) }} + ``` + + This method is currently only useful when using the [`js.Batch`][] function. + +## Pattern matching + +With the `GetMatch` and `Match` methods, Hugo determines a match using a case-insensitive [glob pattern](g). + +{{% include "/_common/glob-patterns.md" %}} + +[Page bundle]: /content-management/page-bundles/ +[`js.Batch`]: /functions/js/batch/#import-context +[`resources.ByType`]: /functions/resources/bytype/ +[`resources.GetMatch`]: /functions/resources/getmatch/ +[`resources.Get`]: /functions/resources/get/ +[`resources.Match`]: /functions/resources/match/ +[`resources`]: /functions/resources/ +[media type]: https://en.wikipedia.org/wiki/Media_type diff --git a/docs/content/en/methods/page/Rotate.md b/docs/content/en/methods/page/Rotate.md new file mode 100644 index 0000000..643945d --- /dev/null +++ b/docs/content/en/methods/page/Rotate.md @@ -0,0 +1,55 @@ +--- +title: Rotate +description: Returns a collection of pages that vary along the specified dimension while sharing the current page's values for the other dimensions, including the current page, sorted by the dimension's default sort order. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pages + signatures: [PAGE.Rotate DIMENSION] +--- + +{{< new-in 0.153.0 />}} + +The rotate method on a page object returns a collection of pages that vary along the specified [dimension](g), while holding the other dimensions constant. The result includes the current page and is sorted according to the rules of the specified dimension. For example, rotating along [language](g) returns all language variants that share the current page's [version](g) and [role](g). + +The `DIMENSION` argument must be one of `language`, `version`, or `role`. + +## Sort order + +Use the following rules to understand how Hugo sorts the collection returned by the `Rotate` method. + +| Dimension | Primary Sort | Secondary Sort | +| :--- | :--- | :--- | +| Language | Weight ascending | Lexicographical ascending | +| Version | Weight ascending | Semantic version descending | +| Role | Weight ascending | Lexicographical ascending | + +## Examples + +To render a list of the current page's language variants, including the current page, while sharing its current version and role: + +```go-html-template +{{/* Returns languages sorted by weight ascending, then lexicographically ascending */}} +{{ range .Rotate "language" }} +

    {{ .LinkTitle }}

    +{{ end }} +``` + +To render a list of the current page's version variants, including the current page, while sharing its current language and role: + +```go-html-template +{{/* Returns versions sorted by weight ascending, then semantic version descending */}} +{{ range .Rotate "version" }} +

    {{ .LinkTitle }}

    +{{ end }} +``` + +To render a list of the current page's role variants, including the current page, while sharing its current language and version: + +```go-html-template +{{/* Returns roles sorted by weight ascending, then lexicographically ascending */}} +{{ range .Rotate "role" }} +

    {{ .LinkTitle }}

    +{{ end }} +``` diff --git a/docs/content/en/methods/page/Scratch.md b/docs/content/en/methods/page/Scratch.md new file mode 100644 index 0000000..1e4c5e6 --- /dev/null +++ b/docs/content/en/methods/page/Scratch.md @@ -0,0 +1,19 @@ +--- +title: Scratch +description: Returns a persistent data structure for storing and manipulating keyed values, 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`](/methods/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`. +{{< /deprecated-in >}} diff --git a/docs/content/en/methods/page/Section.md b/docs/content/en/methods/page/Section.md new file mode 100644 index 0000000..58318c5 --- /dev/null +++ b/docs/content/en/methods/page/Section.md @@ -0,0 +1,54 @@ +--- +title: Section +description: Returns the name of the top-level section in which the given page resides. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [PAGE.Section] +--- + +{{% glossary-term section %}} + +With this content structure: + +```tree +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" }} +

    {{ .LinkTitle }}

    +{{ end }} +``` + +This is similar to using the [`Type`][] method with the `where` function + +```go-html-template +{{ range where .Site.RegularPages "Type" "lessons" }} +

    {{ .LinkTitle }}

    +{{ 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`. + +[`Type`]: /methods/page/type/ +[`where`]: /functions/collections/where/ diff --git a/docs/content/en/methods/page/Sections.md b/docs/content/en/methods/page/Sections.md new file mode 100644 index 0000000..10cd27e --- /dev/null +++ b/docs/content/en/methods/page/Sections.md @@ -0,0 +1,62 @@ +--- +title: Sections +description: Returns a collection of section pages, one for each immediate descendant section of the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pages + signatures: [PAGE.Sections] +--- + +The `Sections` method on a `Page` object is available to these [page kinds](g): `home`, `section`, and `taxonomy`. The templates for these page kinds receive a page [collection](g) in [context](g), in the [default sort order](g). + +With this content structure: + +```tree +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 }} +

    {{ .LinkTitle }}

    +{{ end }} +``` + +On the home page, Hugo renders: + +```html +

    Films

    +

    Books

    +

    Auctions

    +``` + +On the auctions page, Hugo renders: + +```html +

    Auctions in November 2023

    +

    Auctions in December 2023

    +``` diff --git a/docs/content/en/methods/page/Site.md b/docs/content/en/methods/page/Site.md new file mode 100644 index 0000000..39a221c --- /dev/null +++ b/docs/content/en/methods/page/Site.md @@ -0,0 +1,18 @@ +--- +title: Site +description: Returns the Site object. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.siteWrapper + signatures: [PAGE.Site] +--- + +See [Site methods][]. + +```go-html-template +{{ .Site.Title }} +``` + +[Site methods]: /methods/site/ diff --git a/docs/content/en/methods/page/Sitemap.md b/docs/content/en/methods/page/Sitemap.md new file mode 100644 index 0000000..f5599cf --- /dev/null +++ b/docs/content/en/methods/page/Sitemap.md @@ -0,0 +1,80 @@ +--- +title: Sitemap +description: Returns the sitemap settings for the given page as defined in front matter, falling back to the sitemap settings as defined in your project configuration. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: config.SitemapConfig + signatures: [PAGE.Sitemap] +--- + +Access to the `Sitemap` method on a `Page` object is restricted to [sitemap templates][]. + +## Methods + +Use these methods on the `Sitemap` object. + +`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][changefreqdef]. + + ```go-html-template + {{ .Sitemap.ChangeFreq }} + ``` + +`Disable` +: (`bool`) Whether to disable page inclusion. Default is `false`. Set to `true` in front matter to exclude the page. + + ```go-html-template + {{ .Sitemap.Disable }} + ``` + +`Priority` +: (`float`) The priority of a page relative to any other page on the site. Valid values range from 0.0 to 1.0. With the default value of `-1` Hugo will omit this field from the sitemap. See [details][prioritydef]. + + ```go-html-template + {{ .Sitemap.Priority }} + ``` + +## Example + +With this project configuration: + +{{< code-toggle file=hugo >}} +[sitemap] +changeFreq = 'monthly' +{{< /code-toggle >}} + +And this content: + +{{< code-toggle file=content/news.md fm=true >}} +title = 'News' +[sitemap] +changeFreq = 'hourly' +{{< /code-toggle >}} + +And this simplistic sitemap template: + +```xml {file="layouts/sitemap.xml"} +{{ printf "" | safeHTML }} + + {{ range .Pages }} + + {{ .Permalink }} + {{ if not .Lastmod.IsZero }} + {{ .Lastmod.Format "2006-01-02T15:04:05-07:00" | safeHTML }} + {{ end }} + {{ with .Sitemap.ChangeFreq }} + {{ . }} + {{ end }} + + {{ end }} + +``` + +The change frequency will be `hourly` for the news page, and `monthly` for other pages. + +[changefreqdef]: https://www.sitemaps.org/protocol.html#changefreqdef +[prioritydef]: https://www.sitemaps.org/protocol.html#prioritydef +[sitemap templates]: /templates/sitemap/ diff --git a/docs/content/en/methods/page/Sites.md b/docs/content/en/methods/page/Sites.md new file mode 100644 index 0000000..cef0a43 --- /dev/null +++ b/docs/content/en/methods/page/Sites.md @@ -0,0 +1,15 @@ +--- +title: Sites +description: Returns a collection of all sites for all dimensions. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Sites + signatures: [PAGE.Sites] +expiryDate: '2028-02-18' # deprecated 2026-02-18 in v0.156.0 +--- + +{{< deprecated-in 0.156.0 >}} +Use the [`hugo.Sites`](/functions/hugo/sites/) function instead. +{{< /deprecated-in >}} diff --git a/docs/content/en/methods/page/Slug.md b/docs/content/en/methods/page/Slug.md new file mode 100644 index 0000000..34000b6 --- /dev/null +++ b/docs/content/en/methods/page/Slug.md @@ -0,0 +1,25 @@ +--- +title: Slug +description: Returns the URL slug of the given page as defined in front matter. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [PAGE.Slug] +--- + +{{< code-toggle file=content/recipes/spicy-tuna-hand-rolls.md fm=true >}} +title = 'How to make spicy tuna hand rolls' +slug = 'sushi' +{{< /code-toggle >}} + +This page will be served from: + + https://example.org/recipes/sushi + +To get the slug value within a template: + +```go-html-template +{{ .Slug }} → sushi +``` diff --git a/docs/content/en/methods/page/Store.md b/docs/content/en/methods/page/Store.md new file mode 100644 index 0000000..d48a321 --- /dev/null +++ b/docs/content/en/methods/page/Store.md @@ -0,0 +1,35 @@ +--- +title: Store +description: Returns a persistent data structure for storing and manipulating keyed values, 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 persistent data structure for storing and manipulating keyed values, scoped to the current page. To create a data structure with a different [scope](g), refer to the [scope](#scope) section below. + +{{% include "_common/store-methods.md" %}} + +{{% include "_common/store-scope.md" %}} + +## Determinate values + +The `Store` method is often used to set values within a _shortcode_ template, a _partial_ template called by a _shortcode_ template, or by a _render hook_ template. In all three cases, the stored values are indeterminate until Hugo renders the page content. + +If you need to access a stored value from a parent template, and the parent template has not yet rendered the page content, you can trigger content rendering by assigning the returned value to a [noop](g) variable: + +```go-html-template +{{ $noop := .Content }} +{{ .Store.Get "mykey" }} +``` + +You can also trigger content rendering with the `ContentWithoutSummary`, `FuzzyWordCount`, `Len`, `Plain`, `PlainWords`, `ReadingTime`, `Summary`, `Truncated`, and `WordCount` methods. For example: + +```go-html-template +{{ $noop := .WordCount }} +{{ .Store.Get "mykey" }} +``` diff --git a/docs/content/en/methods/page/Summary.md b/docs/content/en/methods/page/Summary.md new file mode 100644 index 0000000..0a1aae9 --- /dev/null +++ b/docs/content/en/methods/page/Summary.md @@ -0,0 +1,48 @@ +--- +title: Summary +description: Returns the summary of the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: template.HTML + signatures: [PAGE.Summary] +--- + + + + + + +You can define a [summary][] manually, in front matter, or automatically. A manual summary takes precedence over a front matter summary, and a front matter summary takes precedence over an automatic summary. + +To list the pages in a section with a summary beneath each link: + +```go-html-template +{{ range .Pages }} +

    {{ .LinkTitle }}

    + {{ .Summary }} +{{ end }} +``` + +> [!WARNING] +> Automatic `.Summary` may cut block tags (e.g., `blockquote`) in the middle, causing the browser to recover the end tag. See [automatic summary][] for details and for ways to avoid this. + +Depending on content length and how you define the summary, the summary may be equivalent to the content itself. To determine whether the content length exceeds the summary length, use the [`Truncated`][] method on a `Page` object. This is useful for conditionally rendering a “read more” link: + +```go-html-template +{{ range .Pages }} +

    {{ .LinkTitle }}

    + {{ .Summary }} + {{ if .Truncated }} + Read more... + {{ end }} +{{ end }} +``` + +> [!NOTE] +> The `Truncated` method returns `false` if you define the summary in front matter. + +[`Truncated`]: /methods/page/truncated/ +[automatic summary]: /content-management/summaries/#automatic-summary +[summary]: /content-management/summaries/ diff --git a/docs/content/en/methods/page/TableOfContents.md b/docs/content/en/methods/page/TableOfContents.md new file mode 100644 index 0000000..05bcdd7 --- /dev/null +++ b/docs/content/en/methods/page/TableOfContents.md @@ -0,0 +1,47 @@ +--- +title: TableOfContents +description: Returns a table of contents for the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: template.HTML + signatures: [PAGE.TableOfContents] +aliases: [/content-management/toc/] +--- + +The `TableOfContents` method on a `Page` object returns an ordered or unordered list of the Markdown [ATX][] and [setext][] headings within the page content. + +This template code: + +```go-html-template +{{ .TableOfContents }} +``` + +Produces this HTML: + +```html + +``` + +By default, the `TableOfContents` method returns an unordered list of level 2 and level 3 headings. You can adjust this in your project configuration: + +{{< code-toggle file=hugo >}} +[markup.tableOfContents] +endLevel = 3 +ordered = false +startLevel = 2 +{{< /code-toggle >}} + +[ATX]: https://spec.commonmark.org/current/#atx-headings +[setext]: https://spec.commonmark.org/current/#setext-headings diff --git a/docs/content/en/methods/page/Title.md b/docs/content/en/methods/page/Title.md new file mode 100644 index 0000000..a7cdc98 --- /dev/null +++ b/docs/content/en/methods/page/Title.md @@ -0,0 +1,46 @@ +--- +title: Title +description: Returns the title of the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [PAGE.Title] +--- + +With pages backed by a file, the `Title` method returns the `title` field as defined in front matter: + +{{< code-toggle file=content/about.md fm=true >}} +title = 'About us' +{{< /code-toggle >}} + +```go-html-template +{{ .Title }} → About us +``` + +When a page is not backed by a file, the value returned by the `Title` method depends on the page [kind](g). + +Page kind|Page title when the page is not backed by a file +:--|:-- +home|site title +section|section name (capitalized and pluralized) +taxonomy|taxonomy name (capitalized and pluralized) +term|term name (capitalized and pluralized) + +You can disable automatic capitalization and pluralization in your project configuration: + +{{< code-toggle file=hugo >}} +capitalizeListTitles = false +pluralizeListTitles = false +{{< /code-toggle >}} + +You can change the capitalization style in your project configuration to one of `ap`, `chicago`, `go`, `firstupper`, or `none`. For example: + +{{< code-toggle file=hugo >}} +titleCaseStyle = "firstupper" +{{< /code-toggle >}} + +See [details][]. + +[details]: /configuration/all/#title-case-style diff --git a/docs/content/en/methods/page/TranslationKey.md b/docs/content/en/methods/page/TranslationKey.md new file mode 100644 index 0000000..8588a8c --- /dev/null +++ b/docs/content/en/methods/page/TranslationKey.md @@ -0,0 +1,71 @@ +--- +title: TranslationKey +description: Returns the translation key of the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [PAGE.TranslationKey] +--- + +The translation key creates a relationship between all translations of a given page. The translation key is derived from the file path, or from the `translationKey` parameter if defined in front matter. + +With this project configuration: + +{{< code-toggle file=hugo >}} +defaultContentLanguage = 'en' + +[languages.en] +contentDir = 'content/en' +label = 'English' +locale = 'en-US' +weight = 1 + +[languages.de] +contentDir = 'content/de' +label = 'Deutsch' +locale = 'de-DE' +weight = 2 +{{< /code-toggle >}} + +And this content: + +```tree +content/ +├── de/ +│ ├── books/ +│ │ ├── buch-1.md +│ │ └── book-2.md +│ └── _index.md +├── en/ +│ ├── books/ +│ │ ├── book-1.md +│ │ └── book-2.md +│ └── _index.md +└── _index.md +``` + +And this front matter: + +{{< code-toggle file=content/en/books/book-1.md fm=true >}} +title = 'Book 1' +translationKey = 'foo' +{{< /code-toggle >}} + +{{< code-toggle file=content/de/books/buch-1.md fm=true >}} +title = 'Buch 1' +translationKey = 'foo' +{{< /code-toggle >}} + +When rendering either either of the pages above: + +```go-html-template +{{ .TranslationKey }} → page/foo +``` + +If the front matter of Book 2, in both languages, does not include a translation key: + +```go-html-template +{{ .TranslationKey }} → page/books/book-2 +``` diff --git a/docs/content/en/methods/page/Translations.md b/docs/content/en/methods/page/Translations.md new file mode 100644 index 0000000..76b8e77 --- /dev/null +++ b/docs/content/en/methods/page/Translations.md @@ -0,0 +1,86 @@ +--- +title: Translations +description: Returns all translations of the given page, excluding the current language, sorted by language weight then language name. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pages + signatures: [PAGE.Translations] +--- + +With this project configuration: + +{{< code-toggle file=hugo >}} +defaultContentLanguage = 'en' + +[languages.en] +contentDir = 'content/en' +label = 'English' +locale = 'en-US' +weight = 1 + +[languages.de] +contentDir = 'content/de' +label = 'Deutsch' +locale = 'de-DE' +weight = 2 + +[languages.fr] +contentDir = 'content/fr' +label = 'Français' +locale = 'fr-FR' +weight = 3 +{{< /code-toggle >}} + +And this content: + +```tree +content/ +├── de/ +│ ├── books/ +│ │ ├── book-1.md +│ │ └── book-2.md +│ └── _index.md +├── en/ +│ ├── books/ +│ │ ├── book-1.md +│ │ └── book-2.md +│ └── _index.md +├── fr/ +│ ├── books/ +│ │ └── book-1.md +│ └── _index.md +└── _index.md +``` + +And this template: + +```go-html-template +{{ with .Translations }} + +{{ end }} +``` + +Hugo will render this list on the `book-1` page of the English site: + +```html + +``` + +Hugo will render this list on the `book-2` page of the English site: + +```html + +``` diff --git a/docs/content/en/methods/page/Truncated.md b/docs/content/en/methods/page/Truncated.md new file mode 100644 index 0000000..98d02da --- /dev/null +++ b/docs/content/en/methods/page/Truncated.md @@ -0,0 +1,29 @@ +--- +title: Truncated +description: Reports whether the content length exceeds the summary length. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: bool + signatures: [PAGE.Truncated] +--- + +You can define a [summary][] manually, in front matter, or automatically. A manual summary takes precedence over a front matter summary, and a front matter summary takes precedence over an automatic summary. + +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 }} +

    {{ .LinkTitle }}

    + {{ .Summary }} + {{ if .Truncated }} + Read more... + {{ end }} +{{ end }} +``` + +> [!NOTE] +> The `Truncated` method returns `false` if you define the summary in front matter. + +[summary]: /content-management/summaries/ diff --git a/docs/content/en/methods/page/Type.md b/docs/content/en/methods/page/Type.md new file mode 100644 index 0000000..e6d33c0 --- /dev/null +++ b/docs/content/en/methods/page/Type.md @@ -0,0 +1,51 @@ +--- +title: Type +description: Returns the content type of the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [PAGE.Type] +--- + +The `Type` method on a `Page` object returns the [content type](g) of the given page. The content type is defined by the `type` field in front matter, or inferred from the top-level directory name if the `type` field in front matter is not defined. + +With this content structure: + +```tree +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" }} +

    {{ .Title }}

    +{{ end }} +``` + +Hugo renders this to; + +```html +

    Book 1

    +

    Book 2

    +

    Item 2

    +``` + +The `type` field in front matter is also useful for targeting a template. See [details][]. + +[details]: /templates/lookup-order/#target-a-template diff --git a/docs/content/en/methods/page/Weight.md b/docs/content/en/methods/page/Weight.md new file mode 100644 index 0000000..c14af02 --- /dev/null +++ b/docs/content/en/methods/page/Weight.md @@ -0,0 +1,25 @@ +--- +title: Weight +description: Returns the weight of the given page as defined in front matter. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: int + signatures: [PAGE.Weight] +--- + +The `Weight` method on a `Page` object returns the [weight](g) of the given page as defined in front matter. + +{{< code-toggle file=content/recipes/sushi.md fm=true >}} +title = 'How to make spicy tuna hand rolls' +weight = 42 +{{< /code-toggle >}} + +Page weight controls the position of a page within a collection that is sorted by weight. Assign weights using non-zero integers. Lighter items float to the top, while heavier items sink to the bottom. Unweighted or zero-weighted elements are placed at the end of the collection. + +Although rarely used within a template, you can access the value with: + +```go-html-template +{{ .Weight }} → 42 +``` diff --git a/docs/content/en/methods/page/WordCount.md b/docs/content/en/methods/page/WordCount.md new file mode 100644 index 0000000..e14b198 --- /dev/null +++ b/docs/content/en/methods/page/WordCount.md @@ -0,0 +1,18 @@ +--- +title: WordCount +description: Returns the number of words in the content of the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: int + signatures: [PAGE.WordCount] +--- + +```go-html-template +{{ .WordCount }} → 103 +``` + +To round up to nearest multiple of 100, use the [`FuzzyWordCount`][] method. + +[`FuzzyWordCount`]: /methods/page/fuzzywordcount/ diff --git a/docs/content/en/methods/page/_index.md b/docs/content/en/methods/page/_index.md new file mode 100644 index 0000000..c7ae7ad --- /dev/null +++ b/docs/content/en/methods/page/_index.md @@ -0,0 +1,8 @@ +--- +title: Page methods +linkTitle: Page +description: Use these methods with a Page object. +categories: [] +keywords: [] +aliases: [/variables/page/] +--- diff --git a/docs/content/en/methods/pager/First.md b/docs/content/en/methods/pager/First.md new file mode 100644 index 0000000..9cd5898 --- /dev/null +++ b/docs/content/en/methods/pager/First.md @@ -0,0 +1,38 @@ +--- +title: First +description: Returns the first pager in the pager collection. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pager + signatures: [PAGER.First] +--- + +Use the `First` method to build navigation between pagers. + +```go-html-template +{{ $pages := where site.RegularPages "Type" "posts" }} +{{ $paginator := .Paginate $pages }} + +{{ range $paginator.Pages }} +

    {{ .LinkTitle }}

    +{{ end }} + +{{ with $paginator }} +
      + {{ with .First }} +
    • First
    • + {{ end }} + {{ with .Prev }} +
    • Previous
    • + {{ end }} + {{ with .Next }} +
    • Next
    • + {{ end }} + {{ with .Last }} +
    • Last
    • + {{ end }} +
    +{{ end }} +``` diff --git a/docs/content/en/methods/pager/HasNext.md b/docs/content/en/methods/pager/HasNext.md new file mode 100644 index 0000000..cf3688e --- /dev/null +++ b/docs/content/en/methods/pager/HasNext.md @@ -0,0 +1,66 @@ +--- +title: HasNext +description: Reports whether there is a pager after the current pager. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: bool + signatures: [PAGER.HasNext] +--- + +Use the `HasNext` method to build navigation between pagers. + +```go-html-template +{{ $pages := where site.RegularPages "Type" "posts" }} +{{ $paginator := .Paginate $pages }} + +{{ range $paginator.Pages }} +

    {{ .LinkTitle }}

    +{{ end }} + +{{ with $paginator }} +
      + {{ with .First }} +
    • First
    • + {{ end }} + {{ if .HasPrev }} +
    • Previous
    • + {{ end }} + {{ if .HasNext }} +
    • Next
    • + {{ end }} + {{ with .Last }} +
    • Last
    • + {{ end }} +
    +{{ 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 }} +

    {{ .LinkTitle }}

    +{{ end }} + +{{ with $paginator }} +
      + {{ with .First }} +
    • First
    • + {{ end }} + {{ with .Prev }} +
    • Previous
    • + {{ end }} + {{ with .Next }} +
    • Next
    • + {{ end }} + {{ with .Last }} +
    • Last
    • + {{ end }} +
    +{{ end }} +``` diff --git a/docs/content/en/methods/pager/HasPrev.md b/docs/content/en/methods/pager/HasPrev.md new file mode 100644 index 0000000..4b486b7 --- /dev/null +++ b/docs/content/en/methods/pager/HasPrev.md @@ -0,0 +1,66 @@ +--- +title: HasPrev +description: Reports whether there is a pager before the current pager. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: bool + signatures: [PAGER.HasPrev] +--- + +Use the `HasPrev` method to build navigation between pagers. + +```go-html-template +{{ $pages := where site.RegularPages "Type" "posts" }} +{{ $paginator := .Paginate $pages }} + +{{ range $paginator.Pages }} +

    {{ .LinkTitle }}

    +{{ end }} + +{{ with $paginator }} +
      + {{ with .First }} +
    • First
    • + {{ end }} + {{ if .HasPrev }} +
    • Previous
    • + {{ end }} + {{ if .HasNext }} +
    • Next
    • + {{ end }} + {{ with .Last }} +
    • Last
    • + {{ end }} +
    +{{ 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 }} +

    {{ .LinkTitle }}

    +{{ end }} + +{{ with $paginator }} +
      + {{ with .First }} +
    • First
    • + {{ end }} + {{ with .Prev }} +
    • Previous
    • + {{ end }} + {{ with .Next }} +
    • Next
    • + {{ end }} + {{ with .Last }} +
    • Last
    • + {{ end }} +
    +{{ end }} +``` diff --git a/docs/content/en/methods/pager/Last.md b/docs/content/en/methods/pager/Last.md new file mode 100644 index 0000000..71dea18 --- /dev/null +++ b/docs/content/en/methods/pager/Last.md @@ -0,0 +1,38 @@ +--- +title: Last +description: Returns the last pager in the pager collection. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pager + signatures: [PAGER.Last] +--- + +Use the `Last` method to build navigation between pagers. + +```go-html-template +{{ $pages := where site.RegularPages "Type" "posts" }} +{{ $paginator := .Paginate $pages }} + +{{ range $paginator.Pages }} +

    {{ .LinkTitle }}

    +{{ end }} + +{{ with $paginator }} +
      + {{ with .First }} +
    • First
    • + {{ end }} + {{ with .Prev }} +
    • Previous
    • + {{ end }} + {{ with .Next }} +
    • Next
    • + {{ end }} + {{ with .Last }} +
    • Last
    • + {{ end }} +
    +{{ end }} +``` diff --git a/docs/content/en/methods/pager/Next.md b/docs/content/en/methods/pager/Next.md new file mode 100644 index 0000000..d7ea9ca --- /dev/null +++ b/docs/content/en/methods/pager/Next.md @@ -0,0 +1,38 @@ +--- +title: Next +description: Returns the next pager in the pager collection. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pager + signatures: [PAGER.Next] +--- + +Use the `Next` method to build navigation between pagers. + +```go-html-template +{{ $pages := where site.RegularPages "Type" "posts" }} +{{ $paginator := .Paginate $pages }} + +{{ range $paginator.Pages }} +

    {{ .LinkTitle }}

    +{{ end }} + +{{ with $paginator }} +
      + {{ with .First }} +
    • First
    • + {{ end }} + {{ with .Prev }} +
    • Previous
    • + {{ end }} + {{ with .Next }} +
    • Next
    • + {{ end }} + {{ with .Last }} +
    • Last
    • + {{ end }} +
    +{{ end }} +``` diff --git a/docs/content/en/methods/pager/NumberOfElements.md b/docs/content/en/methods/pager/NumberOfElements.md new file mode 100644 index 0000000..9f88126 --- /dev/null +++ b/docs/content/en/methods/pager/NumberOfElements.md @@ -0,0 +1,23 @@ +--- +title: NumberOfElements +description: Returns the number of pages in the current pager. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: int + signatures: [PAGER.NumberOfElements] +--- + +```go-html-template +{{ $pages := where site.RegularPages "Type" "posts" }} +{{ $paginator := .Paginate $pages }} + +{{ range $paginator.Pages }} +

    {{ .LinkTitle }}

    +{{ end }} + +{{ with $paginator }} + {{ .NumberOfElements }} +{{ end }} +``` diff --git a/docs/content/en/methods/pager/PageGroups.md b/docs/content/en/methods/pager/PageGroups.md new file mode 100644 index 0000000..cc6939b --- /dev/null +++ b/docs/content/en/methods/pager/PageGroups.md @@ -0,0 +1,28 @@ +--- +title: PageGroups +description: Returns the page groups in the current pager. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.PagesGroup + signatures: [PAGER.PageGroups] +--- + +Use the `PageGroups` method with any of the [grouping methods][]. + +```go-html-template +{{ $pages := where site.RegularPages "Type" "posts" }} +{{ $paginator := .Paginate ($pages.GroupByDate "Jan 2006") }} + +{{ range $paginator.PageGroups }} +

    {{ .Key }}

    + {{ range .Pages }} +

    {{ .LinkTitle }}

    + {{ end }} +{{ end }} + +{{ partial "pagination.html" . }} +``` + +[grouping methods]: /quick-reference/page-collections/#group diff --git a/docs/content/en/methods/pager/PageNumber.md b/docs/content/en/methods/pager/PageNumber.md new file mode 100644 index 0000000..6d0b8e3 --- /dev/null +++ b/docs/content/en/methods/pager/PageNumber.md @@ -0,0 +1,29 @@ +--- +title: PageNumber +description: Returns the current pager's number within the pager collection. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: int + signatures: [PAGER.PageNumber] +--- + +Use the `PageNumber` method to build navigation between pagers. + +```go-html-template +{{ $pages := where site.RegularPages "Type" "posts" }} +{{ $paginator := .Paginate $pages }} + +{{ range $paginator.Pages }} +

    {{ .LinkTitle }}

    +{{ end }} + +{{ with $paginator }} + +{{ end }} +``` diff --git a/docs/content/en/methods/pager/PagerSize.md b/docs/content/en/methods/pager/PagerSize.md new file mode 100644 index 0000000..a16592a --- /dev/null +++ b/docs/content/en/methods/pager/PagerSize.md @@ -0,0 +1,29 @@ +--- +title: PagerSize +description: Returns the number of pages per pager. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: int + signatures: [PAGER.PagerSize] +aliases: [/methods/pager/pagesize/] +--- + +The number of pages per pager is determined by the optional second argument passed to the [`Paginate`][] method, falling back to the `pagerSize` as defined in your [project configuration][]. + +```go-html-template +{{ $pages := where site.RegularPages "Type" "posts" }} +{{ $paginator := .Paginate $pages }} + +{{ range $paginator.Pages }} +

    {{ .LinkTitle }}

    +{{ end }} + +{{ with $paginator }} + {{ .PagerSize }} +{{ end }} +``` + +[`Paginate`]: /methods/page/paginate/ +[project configuration]: /templates/pagination/#configuration diff --git a/docs/content/en/methods/pager/Pagers.md b/docs/content/en/methods/pager/Pagers.md new file mode 100644 index 0000000..e431069 --- /dev/null +++ b/docs/content/en/methods/pager/Pagers.md @@ -0,0 +1,29 @@ +--- +title: Pagers +description: Returns the pagers collection. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.pagers + signatures: [PAGER.Pagers] +--- + +Use the `Pagers` method to build navigation between pagers. + +```go-html-template +{{ $pages := where site.RegularPages "Type" "posts" }} +{{ $paginator := .Paginate $pages }} + +{{ range $paginator.Pages }} +

    {{ .LinkTitle }}

    +{{ end }} + +{{ with $paginator }} + +{{ end }} +``` diff --git a/docs/content/en/methods/pager/Pages.md b/docs/content/en/methods/pager/Pages.md new file mode 100644 index 0000000..bb5ac92 --- /dev/null +++ b/docs/content/en/methods/pager/Pages.md @@ -0,0 +1,21 @@ +--- +title: Pages +description: Returns the pages in the current pager. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pages + signatures: [PAGER.Pages] +--- + +```go-html-template +{{ $pages := where site.RegularPages "Type" "posts" }} +{{ $paginator := .Paginate $pages }} + +{{ range $paginator.Pages }} +

    {{ .LinkTitle }}

    +{{ end }} + +{{ partial "pagination.html" . }} +``` diff --git a/docs/content/en/methods/pager/Prev.md b/docs/content/en/methods/pager/Prev.md new file mode 100644 index 0000000..eb79f96 --- /dev/null +++ b/docs/content/en/methods/pager/Prev.md @@ -0,0 +1,38 @@ +--- +title: Prev +description: Returns the previous pager in the pager collection. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pager + signatures: [PAGER.Prev] +--- + +Use the `Prev` method to build navigation between pagers. + +```go-html-template +{{ $pages := where site.RegularPages "Type" "posts" }} +{{ $paginator := .Paginate $pages }} + +{{ range $paginator.Pages }} +

    {{ .LinkTitle }}

    +{{ end }} + +{{ with $paginator }} +
      + {{ with .First }} +
    • First
    • + {{ end }} + {{ with .Prev }} +
    • Previous
    • + {{ end }} + {{ with .Next }} +
    • Next
    • + {{ end }} + {{ with .Last }} +
    • Last
    • + {{ end }} +
    +{{ end }} +``` diff --git a/docs/content/en/methods/pager/TotalNumberOfElements.md b/docs/content/en/methods/pager/TotalNumberOfElements.md new file mode 100644 index 0000000..ad29a01 --- /dev/null +++ b/docs/content/en/methods/pager/TotalNumberOfElements.md @@ -0,0 +1,23 @@ +--- +title: TotalNumberOfElements +description: Returns the number of pages in the pager collection. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: int + signatures: [PAGER.TotalNumberOfElements] +--- + +```go-html-template +{{ $pages := where site.RegularPages "Type" "posts" }} +{{ $paginator := .Paginate $pages }} + +{{ range $paginator.Pages }} +

    {{ .LinkTitle }}

    +{{ end }} + +{{ with $paginator }} + {{ .TotalNumberOfElements }} +{{ end }} +``` diff --git a/docs/content/en/methods/pager/TotalPages.md b/docs/content/en/methods/pager/TotalPages.md new file mode 100644 index 0000000..63da5d7 --- /dev/null +++ b/docs/content/en/methods/pager/TotalPages.md @@ -0,0 +1,39 @@ +--- +title: TotalPages +description: Returns the number of pagers in the pager collection. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: int + signatures: [PAGER.TotalPages] +--- + +Use the `TotalPages` method to build navigation between pagers. + +```go-html-template +{{ $pages := where site.RegularPages "Type" "posts" }} +{{ $paginator := .Paginate $pages }} + +{{ range $paginator.Pages }} +

    {{ .LinkTitle }}

    +{{ end }} + +{{ with $paginator }} +

    Pager {{ .PageNumber }} of {{ .TotalPages }}

    +
      + {{ with .First }} +
    • First
    • + {{ end }} + {{ with .Prev }} +
    • Previous
    • + {{ end }} + {{ with .Next }} +
    • Next
    • + {{ end }} + {{ with .Last }} +
    • Last
    • + {{ end }} +
    +{{ end }} +``` diff --git a/docs/content/en/methods/pager/URL.md b/docs/content/en/methods/pager/URL.md new file mode 100644 index 0000000..a3558ba --- /dev/null +++ b/docs/content/en/methods/pager/URL.md @@ -0,0 +1,38 @@ +--- +title: URL +description: Returns the URL of the current pager relative to the site root. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [PAGER.URL] +--- + +Use the `URL` method to build navigation between pagers. + +```go-html-template +{{ $pages := where site.RegularPages "Type" "posts" }} +{{ $paginator := .Paginate $pages }} + +{{ range $paginator.Pages }} +

    {{ .LinkTitle }}

    +{{ end }} + +{{ with $paginator }} +
      + {{ with .First }} +
    • First
    • + {{ end }} + {{ with .Prev }} +
    • Previous
    • + {{ end }} + {{ with .Next }} +
    • Next
    • + {{ end }} + {{ with .Last }} +
    • Last
    • + {{ end }} +
    +{{ end }} +``` diff --git a/docs/content/en/methods/pager/_index.md b/docs/content/en/methods/pager/_index.md new file mode 100644 index 0000000..200640c --- /dev/null +++ b/docs/content/en/methods/pager/_index.md @@ -0,0 +1,6 @@ +--- +title: Pager methods +linkTitle: Pager +description: Use these methods with a Pager object when building navigation for a paginated list page. +keywords: [] +--- diff --git a/docs/content/en/methods/pages/ByDate.md b/docs/content/en/methods/pages/ByDate.md new file mode 100644 index 0000000..4baca8c --- /dev/null +++ b/docs/content/en/methods/pages/ByDate.md @@ -0,0 +1,28 @@ +--- +title: ByDate +description: Returns the given page collection sorted by date in ascending order. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pages + signatures: [PAGES.ByDate] +--- + +When sorting by date, the value is determined by your [project configuration][], defaulting to the `date` field in front matter. + +```go-html-template +{{ range .Pages.ByDate }} +

    {{ .LinkTitle }}

    +{{ end }} +``` + +To sort in descending order: + +```go-html-template +{{ range .Pages.ByDate.Reverse }} +

    {{ .LinkTitle }}

    +{{ end }} +``` + +[project configuration]: /configuration/front-matter/#dates diff --git a/docs/content/en/methods/pages/ByExpiryDate.md b/docs/content/en/methods/pages/ByExpiryDate.md new file mode 100644 index 0000000..812248a --- /dev/null +++ b/docs/content/en/methods/pages/ByExpiryDate.md @@ -0,0 +1,28 @@ +--- +title: ByExpiryDate +description: Returns the given page collection sorted by expiration date in ascending order. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pages + signatures: [PAGES.ByExpiryDate] +--- + +When sorting by expiration date, the value is determined by your [project configuration][], defaulting to the `expiryDate` field in front matter. + +```go-html-template +{{ range .Pages.ByExpiryDate }} +

    {{ .LinkTitle }}

    +{{ end }} +``` + +To sort in descending order: + +```go-html-template +{{ range .Pages.ByExpiryDate.Reverse }} +

    {{ .LinkTitle }}

    +{{ end }} +``` + +[project configuration]: /configuration/front-matter/#dates diff --git a/docs/content/en/methods/pages/ByLanguage.md b/docs/content/en/methods/pages/ByLanguage.md new file mode 100644 index 0000000..751e1d8 --- /dev/null +++ b/docs/content/en/methods/pages/ByLanguage.md @@ -0,0 +1,45 @@ +--- +title: ByLanguage +description: Returns the given page collection sorted by language. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pages + signatures: [PAGES.ByLanguage] +--- + +When sorting by language, Hugo orders the page collection using the following priority: + +1. Language weight (ascending) +1. Date (descending) +1. LinkTitle (ascending) + +This method is rarely, if ever, needed. Page collections that already contain multiple languages, such as those returned by the [`Rotate`][], [`Translations`][], or [`AllTranslations`][] methods on a `Page` object, are already sorted by language weight. + +This contrived example aggregates pages from all sites and then sorts them by language: + +```go-html-template +{{ $p := slice }} +{{ range hugo.Sites }} + {{ range .Pages }} + {{ $p = $p | append . }} + {{ end }} +{{ end }} + +{{ range $p.ByLanguage }} +

    {{ .LinkTitle }}

    +{{ end }} +``` + +To sort in descending order: + +```go-html-template +{{ range $p.ByLanguage.Reverse }} +

    {{ .LinkTitle }}

    +{{ end }} +``` + +[`AllTranslations`]: /methods/page/alltranslations/ +[`Rotate`]: /methods/page/rotate/ +[`Translations`]: /methods/page/translations/ diff --git a/docs/content/en/methods/pages/ByLastmod.md b/docs/content/en/methods/pages/ByLastmod.md new file mode 100644 index 0000000..0009877 --- /dev/null +++ b/docs/content/en/methods/pages/ByLastmod.md @@ -0,0 +1,28 @@ +--- +title: ByLastmod +description: Returns the given page collection sorted by last modification date in ascending order. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pages + signatures: [PAGES.ByLastmod] +--- + +When sorting by last modification date, the value is determined by your [project configuration][], defaulting to the `lastmod` field in front matter. + +```go-html-template +{{ range .Pages.ByLastmod }} +

    {{ .LinkTitle }}

    +{{ end }} +``` + +To sort in descending order: + +```go-html-template +{{ range .Pages.ByLastmod.Reverse }} +

    {{ .LinkTitle }}

    +{{ end }} +``` + +[project configuration]: /configuration/front-matter/#dates diff --git a/docs/content/en/methods/pages/ByLength.md b/docs/content/en/methods/pages/ByLength.md new file mode 100644 index 0000000..c47bf98 --- /dev/null +++ b/docs/content/en/methods/pages/ByLength.md @@ -0,0 +1,24 @@ +--- +title: ByLength +description: Returns the given page collection sorted by content length in ascending order. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pages + signatures: [PAGES.ByLength] +--- + +```go-html-template +{{ range .Pages.ByLength }} +

    {{ .LinkTitle }}

    +{{ end }} +``` + +To sort in descending order: + +```go-html-template +{{ range .Pages.ByLength.Reverse }} +

    {{ .LinkTitle }}

    +{{ end }} +``` diff --git a/docs/content/en/methods/pages/ByLinkTitle.md b/docs/content/en/methods/pages/ByLinkTitle.md new file mode 100644 index 0000000..4a024d2 --- /dev/null +++ b/docs/content/en/methods/pages/ByLinkTitle.md @@ -0,0 +1,24 @@ +--- +title: ByLinkTitle +description: Returns the given page collection sorted by link title in ascending order, falling back to title if link title is not defined. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pages + signatures: [PAGES.ByLinkTitle] +--- + +```go-html-template +{{ range .Pages.ByLinkTitle }} +

    {{ .LinkTitle }}

    +{{ end }} +``` + +To sort in descending order: + +```go-html-template +{{ range .Pages.ByLinkTitle.Reverse }} +

    {{ .LinkTitle }}

    +{{ end }} +``` diff --git a/docs/content/en/methods/pages/ByParam.md b/docs/content/en/methods/pages/ByParam.md new file mode 100644 index 0000000..fcc9b28 --- /dev/null +++ b/docs/content/en/methods/pages/ByParam.md @@ -0,0 +1,34 @@ +--- +title: ByParam +description: Returns the given page collection sorted by the given parameter in ascending order. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pages + signatures: [PAGES.ByParam PARAM] +--- + +If the given parameter is not present in front matter, Hugo will use the matching parameter in your project configuration if present. + +```go-html-template +{{ range .Pages.ByParam "author" }} +

    {{ .LinkTitle }}

    +{{ end }} +``` + +To sort in descending order: + +```go-html-template +{{ range (.Pages.ByParam "author").Reverse }} +

    {{ .LinkTitle }}

    +{{ end }} +``` + +If the targeted parameter is nested, access the field using dot notation: + +```go-html-template +{{ range .Pages.ByParam "author.last_name" }} +

    {{ .LinkTitle }}

    +{{ end }} +``` diff --git a/docs/content/en/methods/pages/ByPublishDate.md b/docs/content/en/methods/pages/ByPublishDate.md new file mode 100644 index 0000000..3ba65e8 --- /dev/null +++ b/docs/content/en/methods/pages/ByPublishDate.md @@ -0,0 +1,28 @@ +--- +title: ByPublishDate +description: Returns the given page collection sorted by publish date in ascending order. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pages + signatures: [PAGES.ByPublishDate] +--- + +When sorting by publish date, the value is determined by your [project configuration][], defaulting to the `publishDate` field in front matter. + +```go-html-template +{{ range .Pages.ByPublishDate }} +

    {{ .LinkTitle }}

    +{{ end }} +``` + +To sort in descending order: + +```go-html-template +{{ range .Pages.ByPublishDate.Reverse }} +

    {{ .LinkTitle }}

    +{{ end }} +``` + +[project configuration]: /configuration/front-matter/#dates diff --git a/docs/content/en/methods/pages/ByTitle.md b/docs/content/en/methods/pages/ByTitle.md new file mode 100644 index 0000000..e10c417 --- /dev/null +++ b/docs/content/en/methods/pages/ByTitle.md @@ -0,0 +1,24 @@ +--- +title: ByTitle +description: Returns the given page collection sorted by title in ascending order. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pages + signatures: [PAGES.ByTitle] +--- + +```go-html-template +{{ range .Pages.ByTitle }} +

    {{ .Title }}

    +{{ end }} +``` + +To sort in descending order: + +```go-html-template +{{ range .Pages.ByTitle.Reverse }} +

    {{ .Title }}

    +{{ end }} +``` diff --git a/docs/content/en/methods/pages/ByWeight.md b/docs/content/en/methods/pages/ByWeight.md new file mode 100644 index 0000000..ba255d3 --- /dev/null +++ b/docs/content/en/methods/pages/ByWeight.md @@ -0,0 +1,26 @@ +--- +title: ByWeight +description: Returns the given page collection sorted by weight in ascending order. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pages + signatures: [PAGES.ByWeight] +--- + +Assign a [weight](g) to a page using the `weight` field in front matter. The weight must be a non-zero integer. Lighter items float to the top, while heavier items sink to the bottom. Unweighted or zero-weighted pages are placed at the end of the collection. + +```go-html-template +{{ range .Pages.ByWeight }} +

    {{ .LinkTitle }}

    +{{ end }} +``` + +To sort in descending order: + +```go-html-template +{{ range .Pages.ByWeight.Reverse }} +

    {{ .LinkTitle }}

    +{{ end }} +``` diff --git a/docs/content/en/methods/pages/GroupBy.md b/docs/content/en/methods/pages/GroupBy.md new file mode 100644 index 0000000..aff0800 --- /dev/null +++ b/docs/content/en/methods/pages/GroupBy.md @@ -0,0 +1,36 @@ +--- +title: GroupBy +description: Returns the given page collection grouped by the given field in ascending order. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.PagesGroup + signatures: ['PAGES.GroupBy FIELD [SORT]'] +--- + +{{% include "/_common/methods/pages/group-sort-order.md" %}} + +```go-html-template +{{ range .Pages.GroupBy "Section" }} +

    {{ .Key }}

    + +{{ end }} +``` + +To sort the groups in descending order: + +```go-html-template +{{ range .Pages.GroupBy "Section" "desc" }} +

    {{ .Key }}

    + +{{ end }} +``` diff --git a/docs/content/en/methods/pages/GroupByDate.md b/docs/content/en/methods/pages/GroupByDate.md new file mode 100644 index 0000000..c6413cd --- /dev/null +++ b/docs/content/en/methods/pages/GroupByDate.md @@ -0,0 +1,62 @@ +--- +title: GroupByDate +description: Returns the given page collection grouped by date in descending order. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.PagesGroup + signatures: ['PAGES.GroupByDate LAYOUT [SORT]'] +--- + +When grouping by date, the value is determined by your [project configuration][], defaulting to the `date` field in front matter. + +The [layout string](#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. + +{{% include "/_common/methods/pages/group-sort-order.md" %}} + +To group content by year and month: + +```go-html-template +{{ range .Pages.GroupByDate "January 2006" }} +

    {{ .Key }}

    + +{{ end }} +``` + +To sort the groups in ascending order: + +```go-html-template +{{ range .Pages.GroupByDate "January 2006" "asc" }} +

    {{ .Key }}

    + +{{ 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" }} +

    {{ .Key }}

    + +{{ end }} +``` + +## Layout string + +{{% include "/_common/time-layout-string.md" %}} + +[`time.Format`]: /functions/time/format/ +[project configuration]: /configuration/front-matter/#dates diff --git a/docs/content/en/methods/pages/GroupByExpiryDate.md b/docs/content/en/methods/pages/GroupByExpiryDate.md new file mode 100644 index 0000000..3618b90 --- /dev/null +++ b/docs/content/en/methods/pages/GroupByExpiryDate.md @@ -0,0 +1,62 @@ +--- +title: GroupByExpiryDate +description: Returns the given page collection grouped by expiration date in descending order. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.PagesGroup + signatures: ['PAGES.GroupByExpiryDate LAYOUT [SORT]'] +--- + +When grouping by expiration date, the value is determined by your [project configuration][], defaulting to the `expiryDate` field in front matter. + +The [layout string](#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. + +{{% include "/_common/methods/pages/group-sort-order.md" %}} + +To group content by year and month: + +```go-html-template +{{ range .Pages.GroupByExpiryDate "January 2006" }} +

    {{ .Key }}

    + +{{ end }} +``` + +To sort the groups in ascending order: + +```go-html-template +{{ range .Pages.GroupByExpiryDate "January 2006" "asc" }} +

    {{ .Key }}

    + +{{ 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" }} +

    {{ .Key }}

    + +{{ end }} +``` + +## Layout string + +{{% include "/_common/time-layout-string.md" %}} + +[`time.Format`]: /functions/time/format/ +[project configuration]: /configuration/front-matter/#dates diff --git a/docs/content/en/methods/pages/GroupByLastmod.md b/docs/content/en/methods/pages/GroupByLastmod.md new file mode 100644 index 0000000..4c79de5 --- /dev/null +++ b/docs/content/en/methods/pages/GroupByLastmod.md @@ -0,0 +1,62 @@ +--- +title: GroupByLastmod +description: Returns the given page collection grouped by last modification date in descending order. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.PagesGroup + signatures: ['PAGES.GroupByLastmod LAYOUT [SORT]'] +--- + +When grouping by last modification date, the value is determined by your [project configuration][], defaulting to the `lastmod` field in front matter. + +The [layout string](#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. + +{{% include "/_common/methods/pages/group-sort-order.md" %}} + +To group content by year and month: + +```go-html-template +{{ range .Pages.GroupByLastmod "January 2006" }} +

    {{ .Key }}

    + +{{ end }} +``` + +To sort the groups in ascending order: + +```go-html-template +{{ range .Pages.GroupByLastmod "January 2006" "asc" }} +

    {{ .Key }}

    + +{{ 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" }} +

    {{ .Key }}

    + +{{ end }} +``` + +## Layout string + +{{% include "/_common/time-layout-string.md" %}} + +[`time.Format`]: /functions/time/format/ +[project configuration]: /configuration/front-matter/#dates diff --git a/docs/content/en/methods/pages/GroupByParam.md b/docs/content/en/methods/pages/GroupByParam.md new file mode 100644 index 0000000..6764144 --- /dev/null +++ b/docs/content/en/methods/pages/GroupByParam.md @@ -0,0 +1,36 @@ +--- +title: GroupByParam +description: Returns the given page collection grouped by the given parameter in ascending order. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.PagesGroup + signatures: ['PAGES.GroupByParam PARAM [SORT]'] +--- + +{{% include "/_common/methods/pages/group-sort-order.md" %}} + +```go-html-template +{{ range .Pages.GroupByParam "color" }} +

    {{ .Key | title }}

    + +{{ end }} +``` + +To sort the groups in descending order: + +```go-html-template +{{ range .Pages.GroupByParam "color" "desc" }} +

    {{ .Key | title }}

    + +{{ end }} +``` diff --git a/docs/content/en/methods/pages/GroupByParamDate.md b/docs/content/en/methods/pages/GroupByParamDate.md new file mode 100644 index 0000000..3c0811c --- /dev/null +++ b/docs/content/en/methods/pages/GroupByParamDate.md @@ -0,0 +1,59 @@ +--- +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](#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. + +{{% include "/_common/methods/pages/group-sort-order.md" %}} + +To group content by year and month: + +```go-html-template +{{ range .Pages.GroupByParamDate "eventDate" "January 2006" }} +

    {{ .Key }}

    + +{{ end }} +``` + +To sort the groups in ascending order: + +```go-html-template +{{ range .Pages.GroupByParamDate "eventDate" "January 2006" "asc" }} +

    {{ .Key }}

    + +{{ 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" }} +

    {{ .Key }}

    + +{{ end }} +``` + +## Layout string + +{{% include "/_common/time-layout-string.md" %}} + +[`time.Format`]: /functions/time/format/ diff --git a/docs/content/en/methods/pages/GroupByPublishDate.md b/docs/content/en/methods/pages/GroupByPublishDate.md new file mode 100644 index 0000000..b47df3c --- /dev/null +++ b/docs/content/en/methods/pages/GroupByPublishDate.md @@ -0,0 +1,62 @@ +--- +title: GroupByPublishDate +description: Returns the given page collection grouped by publish date in descending order. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.PagesGroup + signatures: ['PAGES.GroupByPublishDate LAYOUT [SORT]'] +--- + +When grouping by publish date, the value is determined by your [project configuration][], defaulting to the `publishDate` field in front matter. + +The [layout string](#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. + +{{% include "/_common/methods/pages/group-sort-order.md" %}} + +To group content by year and month: + +```go-html-template +{{ range .Pages.GroupByPublishDate "January 2006" }} +

    {{ .Key }}

    + +{{ end }} +``` + +To sort the groups in ascending order: + +```go-html-template +{{ range .Pages.GroupByPublishDate "January 2006" "asc" }} +

    {{ .Key }}

    + +{{ 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" }} +

    {{ .Key }}

    + +{{ end }} +``` + +## Layout string + +{{% include "/_common/time-layout-string.md" %}} + +[`time.Format`]: /functions/time/format/ +[project configuration]: /configuration/front-matter/#dates diff --git a/docs/content/en/methods/pages/Len.md b/docs/content/en/methods/pages/Len.md new file mode 100644 index 0000000..85b3267 --- /dev/null +++ b/docs/content/en/methods/pages/Len.md @@ -0,0 +1,14 @@ +--- +title: Len +description: Returns the number of pages in the given page collection. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: int + signatures: [PAGES.Len] +--- + +```go-html-template +{{ .Pages.Len }} → 42 +``` diff --git a/docs/content/en/methods/pages/Limit.md b/docs/content/en/methods/pages/Limit.md new file mode 100644 index 0000000..6ee3de2 --- /dev/null +++ b/docs/content/en/methods/pages/Limit.md @@ -0,0 +1,16 @@ +--- +title: Limit +description: Returns the first N pages from the given page collection. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pages + signatures: [PAGES.Limit NUMBER] +--- + +```go-html-template +{{ range .Pages.Limit 3 }} +

    {{ .LinkTitle }}

    +{{ end }} +``` diff --git a/docs/content/en/methods/pages/Next.md b/docs/content/en/methods/pages/Next.md new file mode 100644 index 0000000..ce091c1 --- /dev/null +++ b/docs/content/en/methods/pages/Next.md @@ -0,0 +1,12 @@ +--- +title: Next +description: Returns the next page in a page collection, relative to the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Page + signatures: [PAGES.Next PAGE] +--- + +{{% include "/_common/methods/pages/next-and-prev.md" %}} diff --git a/docs/content/en/methods/pages/Prev.md b/docs/content/en/methods/pages/Prev.md new file mode 100644 index 0000000..004b949 --- /dev/null +++ b/docs/content/en/methods/pages/Prev.md @@ -0,0 +1,12 @@ +--- +title: Prev +description: Returns the previous page in a page collection, relative to the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pages + signatures: [PAGES.Prev PAGE] +--- + +{{% include "/_common/methods/pages/next-and-prev.md" %}} diff --git a/docs/content/en/methods/pages/Related.md b/docs/content/en/methods/pages/Related.md new file mode 100644 index 0000000..a20b8c2 --- /dev/null +++ b/docs/content/en/methods/pages/Related.md @@ -0,0 +1,74 @@ +--- +title: Related +description: Returns a collection of pages related to the given page. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pages + signatures: + - PAGES.Related PAGE + - PAGES.Related OPTIONS +--- + +Based on front matter, Hugo uses several factors to identify content related to the given page. Use the default [related content configuration][], or tune the results to the desired indices and parameters. See [details][]. + +The argument passed to the `Related` method may be a `Page` or an options map. For example, to pass the current page: + +```go-html-template {file="layouts/page.html"} +{{ with .Site.RegularPages.Related . | first 5 }} +

    Related pages:

    + +{{ end }} +``` + +To pass an options map: + +```go-html-template {file="layouts/page.html"} +{{ $opts := dict + "document" . + "indices" (slice "tags" "keywords") +}} +{{ with .Site.RegularPages.Related $opts | first 5 }} +

    Related pages:

    + +{{ 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. + +`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") +}} +``` + +[`keyVals`]: /functions/collections/keyvals/ +[details]: /content-management/related-content/ +[related content configuration]: /configuration/related-content/ diff --git a/docs/content/en/methods/pages/Reverse.md b/docs/content/en/methods/pages/Reverse.md new file mode 100644 index 0000000..23c4b03 --- /dev/null +++ b/docs/content/en/methods/pages/Reverse.md @@ -0,0 +1,16 @@ +--- +title: Reverse +description: Returns the given page collection in reverse order. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pages + signatures: [PAGES.Reverse] +--- + +```go-html-template +{{ range .Pages.ByDate.Reverse }} +

    {{ .LinkTitle }}

    +{{ end }} +``` diff --git a/docs/content/en/methods/pages/_index.md b/docs/content/en/methods/pages/_index.md new file mode 100644 index 0000000..f2495ae --- /dev/null +++ b/docs/content/en/methods/pages/_index.md @@ -0,0 +1,8 @@ +--- +title: Pages methods +linkTitle: Pages +description: Use these methods with a collection of Page objects. +categories: [] +keywords: [] +aliases: [/variables/pages] +--- diff --git a/docs/content/en/methods/resource/Colors.md b/docs/content/en/methods/resource/Colors.md new file mode 100644 index 0000000..104295e --- /dev/null +++ b/docs/content/en/methods/resource/Colors.md @@ -0,0 +1,174 @@ +--- +title: Colors +description: Applicable to images, returns a slice of the most dominant colors using a simple histogram method. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: '[]images.Color' + signatures: [RESOURCE.Colors] +--- + +{{% include "/_common/methods/resource/global-page-remote-resources.md" %}} + +The `Colors` method returns a slice of the most dominant colors in a [processable image](g), ordered from most dominant to least dominant. + +> [!NOTE] +> Use the [`reflect.IsImageResourceProcessable`][] function to verify that an image can be processed. + +## Usage + +This method is fast, but if you downscale your image first, you can further improve performance by extracting colors from the smaller resource. + +## Methods + +Each color in the slice is an object with the following methods: + +`ColorHex` +: (`string`) Returns the [hexadecimal color][] value, prefixed with a hash sign. + +`Luminance` +: (`float64`) Returns the [relative luminance][] of the color in the sRGB colorspace in the range [0, 1]. A value of `0` represents the darkest black, while a value of `1` represents the lightest white. + +> [!NOTE] +> Image filters such as [`images.Dither`][], [`images.Padding`][], and [`images.Text`][] accept either hexadecimal color values or `images.Color` objects as arguments. Hugo renders an `images.Color` object as a hexadecimal color value. + +## Sorting + +As a contrived example, create a table of an image's dominant colors with the most dominant color first, and display the relative luminance of each dominant color: + +```go-html-template +{{ with resources.Get "images/a.jpg" }} + + + + + + + + + {{ range .Colors }} + + + + + {{ end }} + +
    ColorRelative luminance
    {{ .ColorHex }}{{ .Luminance | lang.FormatNumber 4 }}
    +{{ end }} +``` + +Hugo renders this to: + +ColorHex|Relative luminance +:--|:-- +`#bebebd`|`0.5145` +`#514947`|`0.0697` +`#768a9a`|`0.2436` +`#647789`|`0.1771` +`#90725e`|`0.1877` +`#a48974`|`0.2704` + +To sort by dominance with the least dominant color first: + +```go-html-template +{{ range .Colors | collections.Reverse }} +``` + +To sort by relative luminance with the darkest color first: + +```go-html-template +{{ range sort .Colors "Luminance" }} +``` + +To sort by relative luminance with the lightest color first, use either of these constructs: + +```go-html-template +{{ range sort .Colors "Luminance" | collections.Reverse }} +{{ range sort .Colors "Luminance" "desc" }} +``` + +## Examples + +### Image borders + +To add a 5 pixel border to an image using the most dominant color: + +```go-html-template +{{ with resources.Get "images/a.jpg" }} + {{ $mostDominant := index .Colors 0 }} + {{ $filter := images.Padding 5 $mostDominant }} + {{ with .Filter $filter }} + + {{ end }} +{{ end }} +``` + +To add a 5 pixel border to an image using the darkest dominant color: + +```go-html-template +{{ with resources.Get "images/a.jpg" }} + {{ $darkest := index (sort .Colors "Luminance") 0 }} + {{ $filter := images.Padding 5 $darkest }} + {{ with .Filter $filter }} + + {{ end }} +{{ end }} +``` + +### Light text on dark background + +To create a text box where the foreground and background colors are derived from an image's lightest and darkest dominant colors: + +```go-html-template +{{ with resources.Get "images/a.jpg" }} + {{ $darkest := index (sort .Colors "Luminance") 0 }} + {{ $lightest := index (sort .Colors "Luminance" "desc") 0 }} +
    +
    +

    This is light text on a dark background.

    +
    +
    +{{ end }} +``` + +### WCAG contrast ratio + +In the previous example we placed light text on a dark background, but does this color combination conform to [WCAG][] guidelines for either the [minimum][] or the [enhanced][] contrast ratio? + +The WCAG defines the [contrast ratio][] as: + +$$contrast\ ratio = { L_1 + 0.05 \over L_2 + 0.05 }$$ + +where \(L_1\) is the relative luminance of the lightest color and \(L_2\) is the relative luminance of the darkest color. + +Calculate the contrast ratio to determine WCAG conformance: + +```go-html-template +{{ with resources.Get "images/a.jpg" }} + {{ $lightest := index (sort .Colors "Luminance" "desc") 0 }} + {{ $darkest := index (sort .Colors "Luminance") 0 }} + {{ $cr := div + (add $lightest.Luminance 0.05) + (add $darkest.Luminance 0.05) + }} + {{ if ge $cr 7.5 }} + {{ printf "The %.2f contrast ratio conforms to WCAG Level AAA." $cr }} + {{ else if ge $cr 4.5 }} + {{ printf "The %.2f contrast ratio conforms to WCAG Level AA." $cr }} + {{ else }} + {{ printf "The %.2f contrast ratio does not conform to WCAG guidelines." $cr }} + {{ end }} +{{ end }} +``` + +[WCAG]: https://en.wikipedia.org/wiki/Web_Content_Accessibility_Guidelines +[`images.Dither`]: /functions/images/dither/ +[`images.Padding`]: /functions/images/padding/ +[`images.Text`]: /functions/images/text/ +[`reflect.IsImageResourceProcessable`]: /functions/reflect/isimageresourceprocessable/ +[contrast ratio]: https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio +[enhanced]: https://www.w3.org/WAI/WCAG22/quickref/?showtechniques=145#contrast-enhanced +[hexadecimal color]: https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color +[minimum]: https://www.w3.org/WAI/WCAG22/quickref/?showtechniques=145#contrast-minimum +[relative luminance]: https://www.w3.org/TR/WCAG21/#dfn-relative-luminance diff --git a/docs/content/en/methods/resource/Content.md b/docs/content/en/methods/resource/Content.md new file mode 100644 index 0000000..b3f445e --- /dev/null +++ b/docs/content/en/methods/resource/Content.md @@ -0,0 +1,60 @@ +--- +title: Content +description: Returns the content of the given resource. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: any + signatures: [RESOURCE.Content] +--- + +{{% include "/_common/methods/resource/global-page-remote-resources.md" %}} + +The `Content` method on a `Resource` object returns `template.HTML` when the [resource type][] is `page`, otherwise it returns a `string`. + +```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 }} +``` + +[resource type]: /methods/resource/resourcetype/ diff --git a/docs/content/en/methods/resource/Crop.md b/docs/content/en/methods/resource/Crop.md new file mode 100644 index 0000000..4cbed6c --- /dev/null +++ b/docs/content/en/methods/resource/Crop.md @@ -0,0 +1,53 @@ +--- +title: Crop +description: Applicable to images, returns a new image resource cropped according to the given processing specification. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: images.ImageResource + signatures: [RESOURCE.Crop SPECIFICATION] +--- + +{{% include "/_common/methods/resource/global-page-remote-resources.md" %}} + +The `Crop` method returns a new resource from a [processable image](g) according to the given [processing specification](#processing-specification). + +> [!NOTE] +> Use the [`reflect.IsImageResourceProcessable`][] function to verify that an image can be processed. + +## Usage + +When cropping, you must provide both width and height (such as `200x200`) within the specification. This method does not perform any resizing; it simply extracts a region of the image based on the dimensions and the [anchor](#anchor) provided, if any. + +```go-html-template +{{ with resources.Get "images/original.jpg" }} + {{ with .Crop "200x200 TopRight" }} + + {{ end }} +{{ end }} +``` + +In the example above, `"200x200 TopRight"` is the processing specification. + +{{% include "/_common/methods/resource/processing-spec.md" %}} + +## Example + +```go-html-template +{{ with resources.Get "images/original.jpg" }} + {{ with .Crop "200x200 TopRight" }} + + {{ end }} +{{ end }} +``` + +{{< img + src="images/examples/zion-national-park.jpg" + alt="Zion National Park" + filter="Process" + filterArgs="crop 200x200 TopRight" + example=true +>}} + +[`reflect.IsImageResourceProcessable`]: /functions/reflect/isimageresourceprocessable/ diff --git a/docs/content/en/methods/resource/Data.md b/docs/content/en/methods/resource/Data.md new file mode 100644 index 0000000..4d6dbd1 --- /dev/null +++ b/docs/content/en/methods/resource/Data.md @@ -0,0 +1,60 @@ +--- +title: Data +description: Applicable to resources returned by the resources.GetRemote function, returns information from the HTTP response. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: map + signatures: [RESOURCE.Data] +--- + +The `Data` method on a resource returned by the [`resources.GetRemote`][] function returns information from the HTTP response. + +## Example + +```go-html-template +{{ $url := "https://example.org/images/a.jpg" }} +{{ $opts := dict "responseHeaders" (slice "Server") }} +{{ with try (resources.GetRemote $url) }} + {{ with .Err }} + {{ errorf "%s" . }} + {{ else with .Value }} + {{ with .Data }} + {{ .ContentLength }} → 42764 + {{ .ContentType }} → image/jpeg + {{ .Headers }} → map[Server:[Netlify]] + {{ .Status }} → 200 OK + {{ .StatusCode }} → 200 + {{ .TransferEncoding }} → [] + {{ end }} + {{ else }} + {{ errorf "Unable to get remote resource %q" $url }} + {{ end }} +{{ end }} +``` + +## Methods + +Use these methods on the `Data` object. + +`ContentLength` +: (`int`) The content length in bytes. + +`ContentType` +: (`string`) The content type. + +`Headers` +: (`map[string][]string`) A map of response headers matching those requested in the [`responseHeaders`][] option passed to the `resources.GetRemote` function. The header name matching is case-insensitive. In most cases there will be one value per header key. + +`Status` +: (`string`) The HTTP status text. + +`StatusCode` +: (`int`) The HTTP status code. + +`TransferEncoding` +: (`string`) The transfer encoding. + +[`resources.GetRemote`]: /functions/resources/getremote/ +[`responseHeaders`]: /functions/resources/getremote/#responseheaders diff --git a/docs/content/en/methods/resource/Err.md b/docs/content/en/methods/resource/Err.md new file mode 100644 index 0000000..cabaf20 --- /dev/null +++ b/docs/content/en/methods/resource/Err.md @@ -0,0 +1,15 @@ +--- +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`](/functions/go-template/try/) statement instead. +{{< /deprecated-in >}} diff --git a/docs/content/en/methods/resource/Exif.md b/docs/content/en/methods/resource/Exif.md new file mode 100644 index 0000000..3ec7a33 --- /dev/null +++ b/docs/content/en/methods/resource/Exif.md @@ -0,0 +1,15 @@ +--- +title: Exif +description: Returns an object containing Exif metadata for supported image formats. +categories: [] +keywords: ['metadata'] +params: + functions_and_methods: + returnType: meta.ExifInfo + signatures: [RESOURCE.Exif] +expiryDate: 2028-01-28 # deprecated 2026-01-28 in v0.155.0 +--- + +{{< deprecated-in 0.155.0 >}} +Use the [`Meta`](/methods/resource/meta/) method instead. +{{< /deprecated-in >}} diff --git a/docs/content/en/methods/resource/Fill.md b/docs/content/en/methods/resource/Fill.md new file mode 100644 index 0000000..63b47a5 --- /dev/null +++ b/docs/content/en/methods/resource/Fill.md @@ -0,0 +1,53 @@ +--- +title: Fill +description: Applicable to images, returns a new image resource cropped and resized according to the given processing specification. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: images.ImageResource + signatures: [RESOURCE.Fill SPECIFICATION] +--- + +{{% include "/_common/methods/resource/global-page-remote-resources.md" %}} + +The `Fill` method returns a new resource from a [processable image](g) according to the given [processing specification](#processing-specification). + +> [!NOTE] +> Use the [`reflect.IsImageResourceProcessable`][] function to verify that an image can be processed. + +## Usage + +When filling, you must provide both width and height (such as `500x200`) within the specification. `Fill` maintains the original aspect ratio by resizing the image to cover the target area and cropping any overflowing pixels based on the [anchor](#anchor) provided. + +```go-html-template +{{ with resources.Get "images/original.jpg" }} + {{ with .Fill "500x200 TopRight" }} + + {{ end }} +{{ end }} +``` + +In the example above, `"500x200 TopRight"` is the _processing specification. + +{{% include "/_common/methods/resource/processing-spec.md" %}} + +## Example + +```go-html-template +{{ with resources.Get "images/original.jpg" }} + {{ with .Fill "500x200 TopRight" }} + + {{ end }} +{{ end }} +``` + +{{< img + src="images/examples/zion-national-park.jpg" + alt="Zion National Park" + filter="Process" + filterArgs="fill 500x200 TopRight" + example=true +>}} + +[`reflect.IsImageResourceProcessable`]: /functions/reflect/isimageresourceprocessable/ diff --git a/docs/content/en/methods/resource/Filter.md b/docs/content/en/methods/resource/Filter.md new file mode 100644 index 0000000..291b747 --- /dev/null +++ b/docs/content/en/methods/resource/Filter.md @@ -0,0 +1,75 @@ +--- +title: Filter +description: Applicable to images, applies one or more image filters to the given image resource. +categories: [] +keywords: [filter] +params: + alt_title: RESOURCE.Filter + functions_and_methods: + returnType: images.ImageResource + signatures: [RESOURCE.Filter FILTER...] +--- + +{{% include "/_common/methods/resource/global-page-remote-resources.md" %}} + +The `Filter` method returns a new resource from a [processable image](g) after applying one or more [image filters](#image-filters). + +> [!NOTE] +> Use the [`reflect.IsImageResourceProcessable`][] function to verify that an image can be processed. + +## Usage + +Use the `Filter` method to apply effects such as blurring, sharpening, or grayscale conversion. You can pass a single filter or a slice of filters. When providing a slice, Hugo applies the filters from left to right. + +To apply a single filter: + +```go-html-template +{{ with resources.Get "images/original.jpg" }} + {{ with .Filter images.Grayscale }} + + {{ end }} +{{ end }} +``` + +To apply multiple filters: + +```go-html-template +{{ $filters := slice + images.Grayscale + (images.GaussianBlur 8) +}} +{{ with resources.Get "images/original.jpg" }} + {{ with .Filter $filters }} + + {{ end }} +{{ end }} +``` + +You can also apply image filters using the [`images.Filter`][] function. + +## Example + +```go-html-template +{{ with resources.Get "images/original.jpg" }} + {{ with .Filter images.Grayscale }} + + {{ end }} +{{ end }} +``` + +{{< img + src="images/examples/zion-national-park.jpg" + alt="Zion National Park" + filter="Grayscale" + filterArgs="" + example=true +>}} + +## Image filters + +Use any of these filters with the `Filter` method. + +{{% render-list-of-pages-in-section path=/functions/images filter=functions_images_no_filters filterType=exclude %}} + +[`images.Filter`]: /functions/images/filter/ +[`reflect.IsImageResourceProcessable`]: /functions/reflect/isimageresourceprocessable/ diff --git a/docs/content/en/methods/resource/Fit.md b/docs/content/en/methods/resource/Fit.md new file mode 100644 index 0000000..fe82075 --- /dev/null +++ b/docs/content/en/methods/resource/Fit.md @@ -0,0 +1,55 @@ +--- +title: Fit +description: Applicable to images, returns a new image resource downscaled to fit according to the given processing specification. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: images.ImageResource + signatures: [RESOURCE.Fit SPECIFICATION] +--- + +{{% include "/_common/methods/resource/global-page-remote-resources.md" %}} + +The `Fit` method returns a new resource from a [processable image](g) according to the given [processing specification](#processing-specification). + +> [!NOTE] +> Use the [`reflect.IsImageResourceProcessable`][] function to verify that an image can be processed. + +## Usage + +When fitting, you must provide both width and height (such as `300x175`) within the specification. `Fit` maintains the original aspect ratio by downscaling the image until it fits within the specified dimensions. Unlike [`Fill`][] or [`Resize`][], this method will never upscale an image; if the source image is smaller than the target dimensions, the dimensions of the resulting image are the same as the original. + +```go-html-template +{{ with resources.Get "images/original.jpg" }} + {{ with .Fit "300x175" }} + + {{ end }} +{{ end }} +``` + +In the example above, `"300x175"` is the processing specification. + +{{% include "/_common/methods/resource/processing-spec.md" %}} + +## Example + +```go-html-template +{{ with resources.Get "images/original.jpg" }} + {{ with .Fit "300x175" }} + + {{ end }} +{{ end }} +``` + +{{< img + src="images/examples/zion-national-park.jpg" + alt="Zion National Park" + filter="Process" + filterArgs="fit 300x175" + example=true +>}} + +[`Fill`]: /methods/resource/fill/ +[`Resize`]: /methods/resource/resize/ +[`reflect.IsImageResourceProcessable`]: /functions/reflect/isimageresourceprocessable/ diff --git a/docs/content/en/methods/resource/Height.md b/docs/content/en/methods/resource/Height.md new file mode 100644 index 0000000..726802c --- /dev/null +++ b/docs/content/en/methods/resource/Height.md @@ -0,0 +1,26 @@ +--- +title: Height +description: Applicable to images, returns the height of the given resource. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: int + signatures: [RESOURCE.Height] +--- + +{{% include "/_common/methods/resource/global-page-remote-resources.md" %}} + +Use the [`reflect.IsImageResourceWithMeta`][] function to verify that Hugo can determine the dimensions before calling the `Height` method. + +```go-html-template +{{ with resources.GetMatch "images/featured.*" }} + {{ if reflect.IsImageResourceWithMeta . }} + + {{ else }} + + {{ end }} +{{ end }} +``` + +[`reflect.IsImageResourceWithMeta`]: /functions/reflect/isimageresourcewithmeta/ diff --git a/docs/content/en/methods/resource/MediaType.md b/docs/content/en/methods/resource/MediaType.md new file mode 100644 index 0000000..19f3042 --- /dev/null +++ b/docs/content/en/methods/resource/MediaType.md @@ -0,0 +1,30 @@ +--- +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" %}} + +## Example + +```go-html-template +{{ with resources.Get "images/a.jpg" }} + {{ .MediaType.Type }} → image/jpeg + {{ .MediaType.MainType }} → image + {{ .MediaType.SubType }} → jpeg + {{ .MediaType.Suffixes }} → [jpg jpeg jpe jif jfif] + {{ .MediaType.FirstSuffix.Suffix }} → jpg +{{ end }} +``` + +## Methods + +Use these methods on the `MediaType` object. + +{{% include "/_common/methods/media-type/core-methods.md" %}} diff --git a/docs/content/en/methods/resource/Meta.md b/docs/content/en/methods/resource/Meta.md new file mode 100644 index 0000000..fa369fb --- /dev/null +++ b/docs/content/en/methods/resource/Meta.md @@ -0,0 +1,104 @@ +--- +title: Meta +description: Applicable to images, returns an object containing Exif, IPTC, and XMP metadata for supported image formats. +categories: [] +keywords: ['metadata'] +params: + functions_and_methods: + returnType: meta.MetaInfo + signatures: [RESOURCE.Meta] +--- + +{{< new-in 0.155.3 />}} + +{{% include "/_common/methods/resource/global-page-remote-resources.md" %}} + +The `Meta` method on an image `Resource` object returns an object containing [Exif][], [IPTC][], and [XMP][] metadata. + +While Hugo classifies many file types as images, only certain formats support metadata extraction. Supported formats include AVIF, BMP, GIF, HEIC, HEIF, JPEG, PNG, TIFF, and WebP. + +> [!NOTE] +> Metadata is not preserved during image transformation. Use this method with the _original_ image resource to extract metadata from supported formats. + +## Usage + +Use the [`reflect.IsImageResourceWithMeta`][] function to verify that a resource supports metadata extraction before calling the `Meta` method. + +```go-html-template +{{ with resources.GetMatch "images/featured.*" }} + {{ if reflect.IsImageResourceWithMeta . }} + {{ with .Meta }} + {{ .Date.Format "2006-01-02" }} + {{ end }} + {{ end }} +{{ end }} +``` + +## Methods + +Use these methods on the `Meta` object. + +`Date` +: (`time.Time`) Returns the image creation date/time. Format with the [`time.Format`][] function. + +`Lat` +: (`float64`) Returns the GPS latitude in degrees from Exif metadata, with a fallback to XMP metadata. + +`Long` +: (`float64`) Returns the GPS longitude in degrees from Exif metadata, with a fallback to XMP metadata. + +`Orientation` +: (`int`) Returns the value of the Exif `Orientation` tag, one of eight possible values. + + Value|Description + :--|:-- + `1`|Horizontal (normal) + `2`|Mirrored horizontal + `3`|Rotated 180 degrees + `4`|Mirrored vertical + `5`|Mirrored horizontal and rotated 270 degrees clockwise + `6`|Rotated 90 degrees clockwise + `7`|Mirrored horizontal and rotated 90 degrees clockwise + `8`|Rotated 270 degrees clockwise + + > [!TIP] + > Use the [`images.AutoOrient`][] image filter to rotate and flip an image as needed per its Exif orientation tag + +`Exif` +: (`meta.Tags`) Returns a collection of available Exif fields for this image. Availability is determined by the [`sources`][] setting and specific fields are managed via the [`fields`][] setting, both of which are managed in your project configuration. + +`IPTC` +: (`meta.Tags`) Returns a collection of available IPTC fields for this image. Availability is determined by the [`sources`][] setting and specific fields are managed via the [`fields`][] setting, both of which are managed in your project configuration. + +`XMP` +: (`meta.Tags`) Returns a collection of available XMP fields for this image. Availability is determined by the [`sources`][] setting and specific fields are managed via the [`fields`][] setting, both of which are managed in your project configuration. + +## Examples + +To list the creation date, latitude, longitude, and orientation: + +```go-html-template +{{ with resources.GetMatch "images/featured.*" }} + {{ if reflect.IsImageResourceWithMeta . }} + {{ with .Meta }} +
    +        {{ printf "%-25s %v\n" "Date" .Date }}
    +        {{ printf "%-25s %v\n" "Latitude" .Lat }}
    +        {{ printf "%-25s %v\n" "Longitude" .Long }}
    +        {{ printf "%-25s %v\n" "Orientation" .Orientation }}
    +      
    + {{ end }} + {{ end }} +{{ end }} +``` + +{{% include "/_common/functions/reflect/image-reflection-functions.md" %}} + +[Exif]: https://en.wikipedia.org/wiki/Exif +[IPTC]: https://en.wikipedia.org/wiki/IPTC_Information_Interchange_Model +[XMP]: https://en.wikipedia.org/wiki/Extensible_Metadata_Platform +[`fields`]: /configuration/imaging/#fields +[`images.AutoOrient`]: /functions/images/autoorient/ +[`reflect.IsImageResourceWithMeta`]: /functions/reflect/isimageresourcewithmeta/ +[`sources`]: /configuration/imaging/#sources +[`time.Format`]: /functions/time/format/ diff --git a/docs/content/en/methods/resource/Name.md b/docs/content/en/methods/resource/Name.md new file mode 100644 index 0000000..1f4b762 --- /dev/null +++ b/docs/content/en/methods/resource/Name.md @@ -0,0 +1,89 @@ +--- +title: Name +description: Returns the name of the given resource as optionally defined in front matter, falling back to its file path. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [RESOURCE.Name] +--- + +The value returned by the `Name` method on a `Resource` object depends on the resource type. + +## Global resource + +With a [global resource](g), the `Name` method returns the path to the resource, relative to the `assets` directory. + +```tree +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. + +```tree +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. + +```tree +content/ +├── example/ +│ ├── images/ +│ │ └── Sunrise in Bryce Canyon.jpg +│ └── index.md +└── _index.md +``` + +```go-html-template +{{ with .Resources.Get "images/Sunrise in Bryce Canyon.jpg" }} + {{ .Name }} → images/Sunrise in Bryce Canyon.jpg +{{ end }} +``` + +## Remote resource + +With a [remote resource](g), the `Name` method returns a hashed file name. + +```go-html-template +{{ with resources.GetRemote "https://example.org/images/a.jpg" }} + {{ .Name }} → /a_18432433023265451104.jpg +{{ end }} +``` diff --git a/docs/content/en/methods/resource/Params.md b/docs/content/en/methods/resource/Params.md new file mode 100644 index 0000000..2aa65b1 --- /dev/null +++ b/docs/content/en/methods/resource/Params.md @@ -0,0 +1,61 @@ +--- +title: Params +description: Returns a map of resource parameters as defined in front matter. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: map + signatures: [RESOURCE.Params] +--- + +Use the `Params` method with [page resources](g). It is not applicable to either [global resources](g) or [remote resources](g). + +With this content structure: + +```tree +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" }} +
    + {{ .Params.alt }} +
    {{ .Title }} is {{ .Params.temperament }}
    +
    +{{ end }} +``` + +Hugo renders: + +```html +
    + Photograph of black cat +
    Felix the cat is vicious
    +
    +``` + +See the [page resources][] section for more information. + +[page resources]: /content-management/page-resources/ diff --git a/docs/content/en/methods/resource/Permalink.md b/docs/content/en/methods/resource/Permalink.md new file mode 100644 index 0000000..a8ec2d3 --- /dev/null +++ b/docs/content/en/methods/resource/Permalink.md @@ -0,0 +1,20 @@ +--- +title: Permalink +description: Publishes the given resource and returns its permalink. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [RESOURCE.Permalink] +--- + +{{% include "/_common/methods/resource/global-page-remote-resources.md" %}} + +The `Permalink` method on a `Resource` object writes the resource to the publish directory, typically `public`, and returns its [permalink](g). + +```go-html-template +{{ with resources.Get "images/a.jpg" }} + {{ .Permalink }} → https://example.org/images/a.jpg +{{ end }} +``` diff --git a/docs/content/en/methods/resource/Process.md b/docs/content/en/methods/resource/Process.md new file mode 100644 index 0000000..93cebcb --- /dev/null +++ b/docs/content/en/methods/resource/Process.md @@ -0,0 +1,69 @@ +--- +title: Process +description: Applicable to images, returns a new image resource processed according to the given processing specification. +categories: [] +keywords: [process] +params: + alt_title: RESOURCE.Process + functions_and_methods: + returnType: images.ImageResource + signatures: [RESOURCE.Process SPECIFICATION] +--- + +{{% include "/_common/methods/resource/global-page-remote-resources.md" %}} + +The `Process` method returns a new resource from a [processable image](g) according to the given [processing specification](#processing-specification). + +> [!NOTE] +> Use the [`reflect.IsImageResourceProcessable`][] function to verify that an image can be processed. + +## Usage + +This versatile method supports the full range of image transformations including resizing, cropping, rotation, and format conversion within a single specification string. Unlike specialized methods such as [`Resize`][] or [`Crop`][], you must explicitly include the [action](#action) in the specification if you are changing the image dimensions. + +```go-html-template +{{ with resources.Get "images/original.jpg" }} + {{ with .Process "crop 200x200 TopRight webp q50" }} + + {{ end }} +{{ end }} +``` + +In the example above, `"crop 200x200 TopRight webp q50"` is the processing specification. + +You can also use this method to apply simple transformations such as rotation and conversion: + +```go-html-template +{{/* Rotate 90 degrees counter-clockwise. */}} +{{ $image := $image.Process "r90" }} + +{{/* Convert to WebP. */}} +{{ $image := $image.Process "webp" }} +``` + +The `Process` method is also available as a filter. This is more effective if you need to apply multiple filters to an image. See [`images.Process`][]. + +{{% include "/_common/methods/resource/processing-spec.md" %}} + +## Example + +```go-html-template +{{ with resources.Get "images/original.jpg" }} + {{ with .Process "crop 200x200 TopRight webp q50" }} + + {{ end }} +{{ end }} +``` + +{{< img + src="images/examples/zion-national-park.jpg" + alt="Zion National Park" + filter="Process" + filterArgs="crop 200x200 TopRight webp q50" + example=true +>}} + +[`Crop`]: /methods/resource/crop/ +[`Resize`]: /methods/resource/resize/ +[`images.Process`]: /functions/images/process/ +[`reflect.IsImageResourceProcessable`]: /functions/reflect/isimageresourceprocessable/ diff --git a/docs/content/en/methods/resource/Publish.md b/docs/content/en/methods/resource/Publish.md new file mode 100644 index 0000000..0ecdf7e --- /dev/null +++ b/docs/content/en/methods/resource/Publish.md @@ -0,0 +1,32 @@ +--- +title: Publish +description: Publishes the given resource. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: nil + signatures: [RESOURCE.Publish] +--- + +{{% include "/_common/methods/resource/global-page-remote-resources.md" %}} + +The `Publish` method on a `Resource` object writes the resource to the publish directory, typically `public`. + +```go-html-template +{{ with resources.Get "images/a.jpg" }} + {{ .Publish }} +{{ end }} +``` + +The `Permalink` and `RelPermalink` methods also publish a resource. `Publish` is a convenience method for publishing without a return value. For example, this: + +```go-html-template +{{ $resource.Publish }} +``` + +Instead of this: + +```go-html-template +{{ $noop := $resource.Permalink }} +``` diff --git a/docs/content/en/methods/resource/RelPermalink.md b/docs/content/en/methods/resource/RelPermalink.md new file mode 100644 index 0000000..d4c907b --- /dev/null +++ b/docs/content/en/methods/resource/RelPermalink.md @@ -0,0 +1,20 @@ +--- +title: RelPermalink +description: Publishes the given resource and returns its relative permalink. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [RESOURCE.RelPermalink] +--- + +{{% include "/_common/methods/resource/global-page-remote-resources.md" %}} + +The `Permalink` method on a `Resource` object writes the resource to the publish directory, typically `public`, and returns its [relative permalink](g). + +```go-html-template +{{ with resources.Get "images/a.jpg" }} + {{ .RelPermalink }} → /images/a.jpg +{{ end }} +``` diff --git a/docs/content/en/methods/resource/Resize.md b/docs/content/en/methods/resource/Resize.md new file mode 100644 index 0000000..743ed74 --- /dev/null +++ b/docs/content/en/methods/resource/Resize.md @@ -0,0 +1,55 @@ +--- +title: Resize +description: Applicable to images, returns a new image resource resized according to the given processing specification. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: images.ImageResource + signatures: [RESOURCE.Resize SPECIFICATION] +--- + +{{% include "/_common/methods/resource/global-page-remote-resources.md" %}} + +The `Resize` method returns a new resource from a [processable image](g) according to the given [processing specification](#processing-specification). + +> [!NOTE] +> Use the [`reflect.IsImageResourceProcessable`][] function to verify that an image can be processed. + +## Usage + +Resize an image according to the given processing specification. You may specify only the width (such as `300x`) or only the height (such as `x150`) for proportional scaling. + +If you specify both width and height (such as `300x150`), the resulting image will be scaled to those exact dimensions. If the target aspect ratio differs from the original, the image will be non-proportionally scaled (stretched or squashed). + +```go-html-template +{{ with resources.Get "images/original.jpg" }} + {{ with .Resize "300x" }} + + {{ end }} +{{ end }} +``` + +In the example above, `"300x"` is the processing specification. + +{{% include "/_common/methods/resource/processing-spec.md" %}} + +## Example + +```go-html-template +{{ with resources.Get "images/original.jpg" }} + {{ with .Resize "300x" }} + + {{ end }} +{{ end }} +``` + +{{< img + src="images/examples/zion-national-park.jpg" + alt="Zion National Park" + filter="Process" + filterArgs="resize 300x" + example=true +>}} + +[`reflect.IsImageResourceProcessable`]: /functions/reflect/isimageresourceprocessable/ diff --git a/docs/content/en/methods/resource/ResourceType.md b/docs/content/en/methods/resource/ResourceType.md new file mode 100644 index 0000000..514a1b7 --- /dev/null +++ b/docs/content/en/methods/resource/ResourceType.md @@ -0,0 +1,43 @@ +--- +title: ResourceType +description: Returns the main type of the given resource's media type. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [RESOURCE.ResourceType] +--- + +{{% include "/_common/methods/resource/global-page-remote-resources.md" %}} + +Common resource types include `audio`, `image`, `text`, and `video`. + +```go-html-template +{{ with resources.Get "image/a.jpg" }} + {{ .ResourceType }} → image + {{ .MediaType.MainType }} → image +{{ end }} +``` + +When working with content files, the resource type is `page`. + +```tree +content/ +├── lessons/ +│ ├── lesson-1/ +│ │ ├── _objectives.md <-- resource type = page +│ │ ├── _topics.md <-- resource type = page +│ │ ├── _example.jpg <-- resource type = image +│ │ └── index.md +│ └── _index.md +└── _index.md +``` + +With the structure above, we can range through page resources of type `page` to build content: + +```go-html-template {file="layouts/lessons/page.html"} +{{ range .Resources.ByType "page" }} + {{ .Content }} +{{ end }} +``` diff --git a/docs/content/en/methods/resource/Title.md b/docs/content/en/methods/resource/Title.md new file mode 100644 index 0000000..672343d --- /dev/null +++ b/docs/content/en/methods/resource/Title.md @@ -0,0 +1,81 @@ +--- +title: Title +description: Returns the title of the given resource as optionally defined in front matter, falling back to a relative path or hashed file name depending on resource type. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [RESOURCE.Title] +--- + +The value returned by the `Title` method on a `Resource` object depends on the resource type. + +## Global resource + +With a [global resource](g), the `Title` method returns the path to the resource, relative to the `assets` directory. + +```tree +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. + +```tree +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. + +```tree +content/ +├── example/ +│ ├── images/ +│ │ └── Sunrise in Bryce Canyon.jpg +│ └── index.md +└── _index.md +``` + +```go-html-template +{{ with .Resources.Get "Sunrise in Bryce Canyon.jpg" }} + {{ .Title }} → images/Sunrise in Bryce Canyon.jpg +{{ end }} +``` + +## Remote resource + +With a [remote resource](g), the `Title` method returns a hashed file name. + +```go-html-template +{{ with resources.GetRemote "https://example.org/images/a.jpg" }} + {{ .Title }} → /a_18432433023265451104.jpg +{{ end }} +``` diff --git a/docs/content/en/methods/resource/Width.md b/docs/content/en/methods/resource/Width.md new file mode 100644 index 0000000..74eb373 --- /dev/null +++ b/docs/content/en/methods/resource/Width.md @@ -0,0 +1,26 @@ +--- +title: Width +description: Applicable to images, returns the width of the given resource. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: int + signatures: [RESOURCE.Width] +--- + +{{% include "/_common/methods/resource/global-page-remote-resources.md" %}} + +Use the [`reflect.IsImageResourceWithMeta`][] function to verify that Hugo can determine the dimensions before calling the `Width` method. + +```go-html-template +{{ with resources.GetMatch "images/featured.*" }} + {{ if reflect.IsImageResourceWithMeta . }} + + {{ else }} + + {{ end }} +{{ end }} +``` + +[`reflect.IsImageResourceWithMeta`]: /functions/reflect/isimageresourcewithmeta/ diff --git a/docs/content/en/methods/resource/_index.md b/docs/content/en/methods/resource/_index.md new file mode 100644 index 0000000..c6012e7 --- /dev/null +++ b/docs/content/en/methods/resource/_index.md @@ -0,0 +1,7 @@ +--- +title: Resource methods +linkTitle: Resource +description: Use these methods with a global, page, or remote Resource object. +categories: [] +keywords: [] +--- diff --git a/docs/content/en/methods/shortcode/Get.md b/docs/content/en/methods/shortcode/Get.md new file mode 100644 index 0000000..bf7c94c --- /dev/null +++ b/docs/content/en/methods/shortcode/Get.md @@ -0,0 +1,46 @@ +--- +title: Get +description: Returns the value of the given argument. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: any + signatures: [SHORTCODE.Get ARG] +--- + +Specify the argument by position or by name. When calling a shortcode within Markdown, use either positional or named argument, but not both. + +> [!NOTE] +> Some shortcodes support positional arguments, some support named arguments, and others support both. Refer to the shortcode's documentation for usage details. + +## Positional arguments + +This shortcode call uses positional arguments: + +```md {file="content/about.md"} +{{}} +``` + +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: + +```md {file="content/about.md"} +{{}} +``` + +To retrieve arguments by name: + +```go-html-template {file="layouts/_shortcodes/myshortcode.html"} +{{ printf "%s %s." (.Get "greeting") (.Get "firstName") }} → Hello world. +``` + +> [!NOTE] +> Argument names are case-sensitive. diff --git a/docs/content/en/methods/shortcode/Inner.md b/docs/content/en/methods/shortcode/Inner.md new file mode 100644 index 0000000..5a20299 --- /dev/null +++ b/docs/content/en/methods/shortcode/Inner.md @@ -0,0 +1,143 @@ +--- +title: Inner +description: Returns the content between opening and closing shortcode tags, applicable when the shortcode call includes a closing tag. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: template.HTML + signatures: [SHORTCODE.Inner] +--- + +This content: + +```md {file="content/services.md"} +{{}} +We design the **best** widgets in the world. +{{}} +``` + +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"} +
    + {{ with .Get "title" }} +
    {{ . }}
    + {{ end }} +
    + {{ .Inner | strings.TrimSpace | .Page.RenderString }} +
    +
    +``` + +Hugo renders this to: + +```html +
    +
    Product design
    +
    + We produce the best widgets in the world. +
    +
    +``` + +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: + +```md {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 @@ +
    + {{ with .Get "title" }} +-
    {{ . }}
    ++
    {{ . }}
    + {{ end }} +
    +- {{ .Inner | strings.TrimSpace | .Page.RenderString }} ++ ++ {{ .Inner | strings.TrimSpace }} +
    +
    +``` + +> [!NOTE] +> Don't process the `Inner` value with `RenderString` or `markdownify` when using [Markdown notation][] to call the shortcode. + +[CommonMark]: https://spec.commonmark.org/current/ +[Markdown notation]: /content-management/shortcodes/#notation +[`RenderString`]: /methods/page/renderstring/ +[`markdownify`]: /functions/transform/markdownify/ +[`strings.TrimSpace`]: /functions/strings/trimspace/ +[details]: /methods/page/renderstring/ +[indentation]: https://spec.commonmark.org/current/#indented-code-blocks +[raw HTML blocks]: https://spec.commonmark.org/current/#html-blocks +[security model]: /about/security/ diff --git a/docs/content/en/methods/shortcode/InnerDeindent.md b/docs/content/en/methods/shortcode/InnerDeindent.md new file mode 100644 index 0000000..f09d9bd --- /dev/null +++ b/docs/content/en/methods/shortcode/InnerDeindent.md @@ -0,0 +1,97 @@ +--- +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: + +```md {file="content/about.md"} +- Gallery one + + {{}} + ![kitten a](thumbnails/a.jpg) + ![kitten b](thumbnails/b.jpg) + {{}} + +- Gallery two + + {{}} + ![kitten c](thumbnails/c.jpg) + ![kitten d](thumbnails/d.jpg) + {{}} +``` + +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"} + +``` + +Hugo renders the Markdown to: + +```html +
      +
    • +

      Gallery one

      + +
    • +
    • +

      Gallery two

      + +
    • +
    +``` + +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://spec.commonmark.org/current/#indented-code-blocks +[`Inner`]: /methods/shortcode/inner/ diff --git a/docs/content/en/methods/shortcode/IsNamedParams.md b/docs/content/en/methods/shortcode/IsNamedParams.md new file mode 100644 index 0000000..4cc1ae9 --- /dev/null +++ b/docs/content/en/methods/shortcode/IsNamedParams.md @@ -0,0 +1,29 @@ +--- +title: IsNamedParams +description: Reports whether the shortcode call uses named arguments. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: bool + signatures: [SHORTCODE.IsNamedParams] +--- + +To support both positional and named arguments when calling a shortcode, use the `IsNamedParams` method to determine how the shortcode was called. + +With this _shortcode_ template: + +```go-html-template {file="layouts/_shortcodes/myshortcode.html"} +{{ if .IsNamedParams }} + {{ printf "%s %s." (.Get "greeting") (.Get "firstName") }} +{{ else }} + {{ printf "%s %s." (.Get 0) (.Get 1) }} +{{ end }} +``` + +Both of these calls return the same value: + +```md {file="content/about.md"} +{{}} +{{}} +``` diff --git a/docs/content/en/methods/shortcode/Name.md b/docs/content/en/methods/shortcode/Name.md new file mode 100644 index 0000000..c42513c --- /dev/null +++ b/docs/content/en/methods/shortcode/Name.md @@ -0,0 +1,27 @@ +--- +title: Name +description: Returns the shortcode file name, excluding the file extension. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [SHORTCODE.Name] +--- + +The `Name` method is useful for error reporting. For example, if your shortcode requires a "greeting" argument: + +```go-html-template {file="layouts/_shortcodes/myshortcode.html"} +{{ $greeting := "" }} +{{ with .Get "greeting" }} + {{ $greeting = . }} +{{ else }} + {{ errorf "The %q shortcode requires a 'greeting' argument. See %s" .Name .Position }} +{{ end }} +``` + +In the absence of a "greeting" argument, Hugo will throw an error message and fail the build: + +```text +ERROR The "myshortcode" shortcode requires a 'greeting' argument. See "/home/user/project/content/about.md:11:1" +``` diff --git a/docs/content/en/methods/shortcode/Ordinal.md b/docs/content/en/methods/shortcode/Ordinal.md new file mode 100644 index 0000000..501365a --- /dev/null +++ b/docs/content/en/methods/shortcode/Ordinal.md @@ -0,0 +1,52 @@ +--- +title: Ordinal +description: Returns the zero-based ordinal of the shortcode in relation to its parent. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: int + signatures: [SHORTCODE.Ordinal] +--- + +The `Ordinal` method returns the zero-based ordinal of the shortcode in relation to its parent. If the parent is the page itself, the ordinal represents the position of this shortcode in the page content. + +> [!NOTE] +> Hugo increments the ordinal with each shortcode call, regardless of the specific shortcode type. This means that the ordinal value is tracked sequentially across all shortcodes within a given page. + +This method is useful for, among other things, assigning unique element IDs when a shortcode is called two or more times from the same page. For example: + +```md {file="content/about.md"} +{{}} + +{{}} +``` + +This shortcode performs error checking, then renders an HTML `img` element with a unique `id` attribute: + +```go-html-template {file="layouts/_shortcodes/img.html"} +{{ $src := "" }} +{{ with .Get "src" }} + {{ $src = . }} + {{ with resources.Get $src }} + {{ $id := printf "img-%03d" $.Ordinal }} + + {{ else }} + {{ errorf "The %q shortcode was unable to find %s. See %s" $.Name $src $.Position }} + {{ end }} +{{ else }} + {{ errorf "The %q shortcode requires a 'src' argument. See %s" .Name .Position }} +{{ end }} +``` + +Hugo renders the page to: + +```html + + +``` + +> [!NOTE] +> In the _shortcode_ template above, the [`with`][] statement is used to create conditional blocks. Remember that the `with` statement binds context (the dot) to its expression. Inside of a `with` block, preface shortcode method calls with a `$` to access the top-level context passed into the template. + +[`with`]: /functions/go-template/with/ diff --git a/docs/content/en/methods/shortcode/Page.md b/docs/content/en/methods/shortcode/Page.md new file mode 100644 index 0000000..f979e34 --- /dev/null +++ b/docs/content/en/methods/shortcode/Page.md @@ -0,0 +1,36 @@ +--- +title: Page +description: Returns the Page object from which the shortcode was called. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: hugolib.pageForShortcode + signatures: [SHORTCODE.Page] +--- + +With this content: + +{{< code-toggle file=content/books/les-miserables.md fm=true >}} +title = 'Les Misérables' +author = 'Victor Hugo' +publication_year = 1862 +isbn = '978-0451419439' +{{< /code-toggle >}} + +Calling this shortcode: + +```md +{{}} +``` + +We can access the front matter values using the `Page` method: + +```go-html-template {file="layouts/_shortcodes/book-details.html"} +
      +
    • Title: {{ .Page.Title }}
    • +
    • Author: {{ .Page.Params.author }}
    • +
    • Published: {{ .Page.Params.publication_year }}
    • +
    • ISBN: {{ .Page.Params.isbn }}
    • +
    +``` diff --git a/docs/content/en/methods/shortcode/Params.md b/docs/content/en/methods/shortcode/Params.md new file mode 100644 index 0000000..5c5e023 --- /dev/null +++ b/docs/content/en/methods/shortcode/Params.md @@ -0,0 +1,32 @@ +--- +title: Params +description: Returns a collection of the shortcode arguments. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: any + signatures: [SHORTCODE.Params] +--- + +When you call a shortcode using positional arguments, the `Params` method returns a slice. + +```md {file="content/about.md"} +{{}} +``` + +```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. + +```md {file="content/about.md"} +{{}} +``` + +```go-html-template {file="layouts/_shortcodes/myshortcode.html"} +{{ .Params.greeting }} → Hello +{{ .Params.name }} → world +``` diff --git a/docs/content/en/methods/shortcode/Parent.md b/docs/content/en/methods/shortcode/Parent.md new file mode 100644 index 0000000..38e1ba9 --- /dev/null +++ b/docs/content/en/methods/shortcode/Parent.md @@ -0,0 +1,50 @@ +--- +title: Parent +description: Returns the parent shortcode context in nested shortcodes. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: hugolib.ShortcodeWithPage + signatures: [SHORTCODE.Parent] +--- + +This is useful for inheritance of common shortcode arguments from the root. + +In this contrived example, the "greeting" shortcode is the parent, and the "now" shortcode is child. + +```md {file="content/welcome.md"} +{{}} +Welcome. Today is {{}}. +{{}} +``` + +```go-html-template {file="layouts/_shortcodes/greeting.html"} +
    + {{ .Inner | strings.TrimSpace | .Page.RenderString }} +
    +``` + +```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 new file mode 100644 index 0000000..3b00249 --- /dev/null +++ b/docs/content/en/methods/shortcode/Position.md @@ -0,0 +1,30 @@ +--- +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 new file mode 100644 index 0000000..2e172d0 --- /dev/null +++ b/docs/content/en/methods/shortcode/Ref.md @@ -0,0 +1,37 @@ +--- +title: Ref +description: Returns the absolute URL of the page with the given path, language, and output format. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [SHORTCODE.Ref OPTIONS] +--- + +## Usage + +The `Ref` method requires 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 new file mode 100644 index 0000000..92e6348 --- /dev/null +++ b/docs/content/en/methods/shortcode/RelRef.md @@ -0,0 +1,37 @@ +--- +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 requires 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 new file mode 100644 index 0000000..30304a5 --- /dev/null +++ b/docs/content/en/methods/shortcode/Scratch.md @@ -0,0 +1,19 @@ +--- +title: Scratch +description: Returns a persistent data structure for storing and manipulating keyed values, 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`](/methods/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`. +{{< /deprecated-in >}} diff --git a/docs/content/en/methods/shortcode/Site.md b/docs/content/en/methods/shortcode/Site.md new file mode 100644 index 0000000..bfffff4 --- /dev/null +++ b/docs/content/en/methods/shortcode/Site.md @@ -0,0 +1,18 @@ +--- +title: Site +description: Returns the Site object. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.siteWrapper + signatures: [SHORTCODE.Site] +--- + +See [Site methods][]. + +```go-html-template +{{ .Site.Title }} +``` + +[Site methods]: /methods/site/ diff --git a/docs/content/en/methods/shortcode/Store.md b/docs/content/en/methods/shortcode/Store.md new file mode 100644 index 0000000..e2d52d7 --- /dev/null +++ b/docs/content/en/methods/shortcode/Store.md @@ -0,0 +1,24 @@ +--- +title: Store +description: Returns a persistent data structure for storing and manipulating keyed values, 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 persistent data structure for storing and manipulating keyed values, scoped to the current shortcode. To create a data structure 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/store-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 new file mode 100644 index 0000000..0064f42 --- /dev/null +++ b/docs/content/en/methods/shortcode/_index.md @@ -0,0 +1,8 @@ +--- +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 new file mode 100644 index 0000000..84e5648 --- /dev/null +++ b/docs/content/en/methods/site/AllPages.md @@ -0,0 +1,15 @@ +--- +title: AllPages +description: Returns a collection of all pages in all languages. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pages + signatures: [SITE.AllPages] +expiryDate: '2028-02-18' # deprecated 2026-02-18 in v0.156.0 +--- + +{{< deprecated-in 0.156.0 >}} +See [details](https://discourse.gohugo.io/t/56732). +{{< /deprecated-in >}} diff --git a/docs/content/en/methods/site/BaseURL.md b/docs/content/en/methods/site/BaseURL.md new file mode 100644 index 0000000..dcaa2ba --- /dev/null +++ b/docs/content/en/methods/site/BaseURL.md @@ -0,0 +1,32 @@ +--- +title: BaseURL +description: Returns the base URL as defined in your project configuration. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [SITE.BaseURL] +--- + +Project configuration: + +{{< code-toggle file=hugo >}} +baseURL = 'https://example.org/docs/' +{{< /code-toggle >}} + +Template: + +```go-html-template +{{ .Site.BaseURL }} → https://example.org/docs/ +``` + +> [!NOTE] +> There is almost never a good reason to use this method in your templates. Its usage tends to be fragile due to misconfiguration. +> +> Use the [`absURL`][], [`absLangURL`][], [`relURL`][], or [`relLangURL`][] functions instead. + +[`absLangURL`]: /functions/urls/absLangURL/ +[`absURL`]: /functions/urls/absURL/ +[`relLangURL`]: /functions/urls/relLangURL/ +[`relURL`]: /functions/urls/relURL/ diff --git a/docs/content/en/methods/site/BuildDrafts.md b/docs/content/en/methods/site/BuildDrafts.md new file mode 100644 index 0000000..0a94adf --- /dev/null +++ b/docs/content/en/methods/site/BuildDrafts.md @@ -0,0 +1,15 @@ +--- +title: BuildDrafts +description: Reports reports whether draft publishing is enabled for the current build. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: bool + signatures: [SITE.BuildDrafts] +expiryDate: '2028-02-18' # deprecated 2026-02-18 in v0.156.0 +--- + +{{< deprecated-in 0.156.0 >}} +See [details](https://discourse.gohugo.io/t/56732). +{{< /deprecated-in >}} diff --git a/docs/content/en/methods/site/Config.md b/docs/content/en/methods/site/Config.md new file mode 100644 index 0000000..52d0192 --- /dev/null +++ b/docs/content/en/methods/site/Config.md @@ -0,0 +1,54 @@ +--- +title: Config +description: Returns a subset of your project configuration. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.SiteConfig + signatures: [SITE.Config] +--- + +The `Config` method on a `Site` object provides access to a subset of your project configuration, specifically the `services` and `privacy` keys. + +## Services + +See [configure services][]. + +For example, to use Hugo's built-in Google Analytics template you must add a [Google tag ID][]: + +{{< code-toggle file=hugo >}} +[services.googleAnalytics] +id = 'G-XXXXXXXXX' +{{< /code-toggle >}} + +To access this value from a template: + +```go-html-template +{{ .Site.Config.Services.GoogleAnalytics.ID }} → G-XXXXXXXXX +``` + +You must capitalize each identifier as shown above. + +## Privacy + +See [configure privacy][]. + +For example, to disable usage of the built-in YouTube shortcode: + +{{< code-toggle file=hugo >}} +[privacy.youtube] +disable = true +{{< /code-toggle >}} + +To access this value from a template: + +```go-html-template +{{ .Site.Config.Privacy.YouTube.Disable }} → true +``` + +You must capitalize each identifier as shown above. + +[Google tag ID]: https://support.google.com/tagmanager/answer/12326985?hl=en +[configure privacy]: /configuration/privacy/ +[configure services]: /configuration/services/ diff --git a/docs/content/en/methods/site/Copyright.md b/docs/content/en/methods/site/Copyright.md new file mode 100644 index 0000000..e73ff66 --- /dev/null +++ b/docs/content/en/methods/site/Copyright.md @@ -0,0 +1,22 @@ +--- +title: Copyright +description: Returns the copyright notice as defined in your project configuration. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [SITE.Copyright] +--- + +Project configuration: + +{{< code-toggle file=hugo >}} +copyright = '© 2023 ABC Widgets, Inc.' +{{< /code-toggle >}} + +Template: + +```go-html-template +{{ .Site.Copyright }} → © 2023 ABC Widgets, Inc. +``` diff --git a/docs/content/en/methods/site/Data.md b/docs/content/en/methods/site/Data.md new file mode 100644 index 0000000..78ae10b --- /dev/null +++ b/docs/content/en/methods/site/Data.md @@ -0,0 +1,15 @@ +--- +title: Data +description: Returns a data structure composed from the files in the data directory. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: map + signatures: [SITE.Data] +expiryDate: '2028-02-18' # deprecated 2026-02-18 in v0.156.0 +--- + +{{< deprecated-in 0.156.0 >}} +Use the [`hugo.Data`](/functions/hugo/data/) function instead. +{{< /deprecated-in >}} diff --git a/docs/content/en/methods/site/Dimension.md b/docs/content/en/methods/site/Dimension.md new file mode 100644 index 0000000..7c20016 --- /dev/null +++ b/docs/content/en/methods/site/Dimension.md @@ -0,0 +1,36 @@ +--- +title: Dimension +description: Returns the dimension object for the given dimension for the given site. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.SiteDimension + signatures: [SITE.Dimension DIMENSION] +--- + +{{< new-in 0.153.0 />}} + +The `Dimension` method on a `Site` object returns the dimension object for the given [dimension](g). + +The `DIMENSION` argument must be one of `language`, `version`, or `role`. + +Example|Returns|Equivalent to +:--|:--|:-- +`{{ .Site.Dimension "language" }}`|`langs.Language`|`{{ .Site.Language }}` +`{{ .Site.Dimension "version" }}`|`version.Version`|`{{ .Site.Version }}` +`{{ .Site.Dimension "role" }}`|`roles.Role`|`{{ .Site.Role }}` + +```go-html-template +{{ $languageObject := .Site.Dimension "language" }} +{{ $languageObject.IsDefault }} → true +{{ $languageObject.Name }} → en + +{{ $versionObject := .Site.Dimension "version" }} +{{ $versionObject.IsDefault }} → true +{{ $versionObject.Name }} → v1.0.0 + +{{ $roleObject := .Site.Dimension "role" }} +{{ $roleObject.IsDefault }} → true +{{ $roleObject.Name }} → guest +``` diff --git a/docs/content/en/methods/site/GetPage.md b/docs/content/en/methods/site/GetPage.md new file mode 100644 index 0000000..84032b0 --- /dev/null +++ b/docs/content/en/methods/site/GetPage.md @@ -0,0 +1,105 @@ +--- +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][]. + +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: + +```tree +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 {file="layouts/home.html"} +{{ 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 {file="layouts/home.html"} +{{ with .Site.GetPage "/works/paintings/starry-night" }} + {{ .Title }} → Starry Night + {{ .Params.artist }} → Vincent van Gogh +{{ end }} +``` + +## Multilingual projects + +With multilingual projects, the `GetPage` method on a `Site` object resolves the given path to a page in the current language. + +To get a page from a different language, query the `Sites` object: + +```go-html-template +{{ with where hugo.Sites "Language.Name" "eq" "de" }} + {{ with index . 0 }} + {{ with .GetPage "/works/paintings/starry-night" }} + {{ .Title }} → Sternenklare Nacht + {{ end }} + {{ end }} +{{ end }} +``` + +## Page bundles + +Consider this content structure: + +```tree +content/ +├── headless/ +│ ├── a.jpg +│ ├── b.jpg +│ ├── c.jpg +│ └── index.md <-- front matter: headless = true +└── _index.md +``` + +In the _home_ template, use the `GetPage` method on a `Site` object to render all the images in the headless [page bundle](g): + +```go-html-template {file="layouts/home.html"} +{{ with .Site.GetPage "/headless" }} + {{ range .Resources.ByType "image" }} + + {{ end }} +{{ end }} +``` + +[details]: /methods/page/getpage/ diff --git a/docs/content/en/methods/site/Home.md b/docs/content/en/methods/site/Home.md new file mode 100644 index 0000000..bc9d7ed --- /dev/null +++ b/docs/content/en/methods/site/Home.md @@ -0,0 +1,39 @@ +--- +title: Home +description: Returns the home Page object for the given site. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Page + signatures: [SITE.Home] +--- + +The `Home` method on a `Site` object is a convenient way to access the home page, and is functionally equivalent to: + +```go-html-template +{{ .Site.GetPage "/" }} +``` + +Because it returns a `Page` object, you can use any of the available [page methods][] by chaining them. For example: + +```go-html-template +{{ .Site.Home.Store.Set "greeting" "Hello" }} +``` + +This method is commonly used to generate a link to the home page. For example: + +Project configuration: + +{{< code-toggle file=hugo >}} +baseURL = 'https://example.org/docs/' +{{< /code-toggle >}} + +Template: + +```go-html-template +{{ .Site.Home.Permalink }} → https://example.org/docs/ +{{ .Site.Home.RelPermalink }} → /docs/ +``` + +[page methods]: /methods/page/ diff --git a/docs/content/en/methods/site/IsDefault.md b/docs/content/en/methods/site/IsDefault.md new file mode 100644 index 0000000..543bc4f --- /dev/null +++ b/docs/content/en/methods/site/IsDefault.md @@ -0,0 +1,50 @@ +--- +title: IsDefault +description: Reports whether the given site is the default site across all dimensions. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: bool + signatures: [SITE.IsDefault] +--- + +{{< new-in 0.156.0 />}} + +The `IsDefault` method on a `Site` object reports whether the given site is the [default site](g) across all dimensions: [language](g), [version](g), and [role](g). This is useful to ensure that a block of code executes only once per build, regardless of the number of [sites](g) generated by your [dimensions](g). + +For example, the following configuration defines a matrix of sites across language and version dimensions. + +{{< code-toggle file=hugo >}} +[languages.de] +contentDir = 'content/de' +direction = 'ltr' +label = 'Deutsch' +locale = 'de-DE' +title = 'Projekt Dokumentation' +weight = 1 + +[languages.en] +contentDir = 'content/en' +direction = 'ltr' +label = 'English' +locale = 'en-US' +title = 'Project Documentation' +weight = 2 + +[versions.'v1.0.0'] +[versions.'v2.0.0'] +[versions.'v3.0.0'] +{{< /code-toggle >}} + +If you call an initialization partial to handle one-time build logic or global variable setup, wrap that call in an [`if`][] statement using this function. This prevents the logic from being executed for every dimensional variation. + +```go-html-template +{{ if .Site.IsDefault }} + {{ partial "init.html" . }} +{{ end }} +``` + +In this setup, the code block is only executed for the English version v3.0.0 site. English is selected because it has the lowest weight, and version v3.0.0 is selected because it is the first version when sorted semantically in descending order. + +[`if`]: /functions/go-template/if/ diff --git a/docs/content/en/methods/site/Language.md b/docs/content/en/methods/site/Language.md new file mode 100644 index 0000000..1743f2f --- /dev/null +++ b/docs/content/en/methods/site/Language.md @@ -0,0 +1,104 @@ +--- +title: Language +description: Returns the Language object for the given site. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: langs.Language + signatures: [SITE.Language] +--- + +The `Language` method on a `Site` object returns the `Language` object for the given site, derived from the language definition in your project configuration. + +You can also use the `Language` method on a `Page` object. See [details][]. + +## Methods + +Use these methods on the `Language` object. + +The examples below assume the following language definition. + +{{< code-toggle file=hugo >}} +[languages.de] +direction = 'ltr' +label = 'Deutsch' +locale = 'de-DE' +weight = 2 +{{< /code-toggle >}} + +`Direction` +: {{< new-in 0.158.0 />}} +: (`string`) Returns the [`direction`][] from the language definition. + + ```go-html-template + {{ .Site.Language.Direction }} → ltr + ``` + +`IsDefault` +: {{< new-in 0.153.0 />}} +: (`bool`) Reports whether this is the [default language](g). + + ```go-html-template + {{ .Site.Language.IsDefault }} → true + ``` + +`Label` +: {{< new-in 0.158.0 />}} +: (`string`) Returns the [`label`][] from the language definition. + + ```go-html-template + {{ .Site.Language.Label }} → Deutsch + ``` + +`Lang` +: {{}} +: Use [`Name`](#name) instead. + +`LanguageCode` +: {{}} +: Use [`Locale`](#locale) instead. + +`LanguageDirection` +: {{}} +: Use [`Direction`](#direction) instead. + +`LanguageName` +: {{}} +: Use [`Label`](#label) instead. + +`Locale` +: {{< new-in 0.158.0 />}} +: (`string`) Returns the [`locale`][] from the language definition, falling back to [`Name`](#name). + + ```go-html-template + {{ .Site.Language.Locale }} → de-DE + ``` + +`Name` +: {{< new-in 0.153.0 />}} +: (`string`) Returns the language tag as defined by [RFC 5646][]. This is the lowercased key from the language definition. + + ```go-html-template + {{ .Site.Language.Name }} → de + ``` + +`Weight` +: {{}} + +## Example + +Some of the methods above are commonly used in a base template as attributes for the `html` element. + +```go-html-template + +``` + +[RFC 5646]: https://datatracker.ietf.org/doc/html/rfc5646 +[`direction`]: /configuration/languages/#direction +[`label`]: /configuration/languages/#label +[`locale`]: /configuration/languages/#locale +[details]: /methods/page/language/ diff --git a/docs/content/en/methods/site/LanguagePrefix.md b/docs/content/en/methods/site/LanguagePrefix.md new file mode 100644 index 0000000..59e7d3a --- /dev/null +++ b/docs/content/en/methods/site/LanguagePrefix.md @@ -0,0 +1,51 @@ +--- +title: LanguagePrefix +description: Returns the URL language prefix, if any, for the given site. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [SITE.LanguagePrefix] +--- + +Consider this project configuration: + +{{< code-toggle file=hugo >}} +defaultContentLanguage = 'de' +defaultContentLanguageInSubdir = false + +[languages.de] +direction = 'ltr' +label = 'Deutsch' +locale = 'de-DE' +title = 'Projekt Dokumentation' +weight = 1 + +[languages.en] +direction = 'ltr' +label = 'English' +locale = 'en-US' +title = 'Project Documentation' +weight = 2 +{{< /code-toggle >}} + +When visiting the German language site: + +```go-html-template +{{ .Site.LanguagePrefix }} → "" +``` + +When visiting the English language site: + +```go-html-template +{{ .Site.LanguagePrefix }} → /en +``` + +If you change `defaultContentLanguageInSubdir` to `true`, when visiting the German language site: + +```go-html-template +{{ .Site.LanguagePrefix }} → /de +``` + +You may use the `LanguagePrefix` method with both monolingual and multilingual projects. diff --git a/docs/content/en/methods/site/Languages.md b/docs/content/en/methods/site/Languages.md new file mode 100644 index 0000000..fa51f1e --- /dev/null +++ b/docs/content/en/methods/site/Languages.md @@ -0,0 +1,15 @@ +--- +title: Languages +description: Returns a collection of language objects for all sites, ordered by language weight. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: langs.Languages + signatures: [SITE.Languages] +expiryDate: '2028-02-18' # deprecated 2026-02-18 in v0.156.0 +--- + +{{< deprecated-in 0.156.0 >}} +See [details](https://discourse.gohugo.io/t/56732). +{{< /deprecated-in >}} diff --git a/docs/content/en/methods/site/Lastmod.md b/docs/content/en/methods/site/Lastmod.md new file mode 100644 index 0000000..87e303b --- /dev/null +++ b/docs/content/en/methods/site/Lastmod.md @@ -0,0 +1,21 @@ +--- +title: Lastmod +description: Returns the last modification date of site content. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: time.Time + signatures: [SITE.Lastmod] +--- + +The `Lastmod` method on a `Site` object returns a [`time.Time`][] value. Use this with time [functions][] and [methods][]. For example: + +```go-html-template +{{ .Site.Lastmod | time.Format ":date_long" }} → January 31, 2024 + +``` + +[`time.Time`]: https://pkg.go.dev/time#Time +[functions]: /functions/time/ +[methods]: /methods/time/ diff --git a/docs/content/en/methods/site/MainSections.md b/docs/content/en/methods/site/MainSections.md new file mode 100644 index 0000000..dca6619 --- /dev/null +++ b/docs/content/en/methods/site/MainSections.md @@ -0,0 +1,54 @@ +--- +title: MainSections +description: Returns a slice of the main section names as defined in your project configuration, falling back to the top-level section with the most pages. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: '[]string' + signatures: [SITE.MainSections] +--- + +Project configuration: + +{{< code-toggle file=hugo >}} +mainSections = ['books','films'] +{{< /code-toggle >}} + +Template: + +```go-html-template +{{ .Site.MainSections }} → [books films] +``` + +If `mainSections` is not defined in your project configuration, this method returns a slice with one element---the top-level section with the most pages. + +With this content structure, the `films` section has the most pages: + +```tree +content/ +├── books/ +│ ├── book-1.md +│ └── book-2.md +├── films/ +│ ├── film-1.md +│ ├── film-2.md +│ └── film-3.md +└── _index.md +``` + +Template: + +```go-html-template +{{ .Site.MainSections }} → [films] +``` + +When creating a theme, instead of hardcoding section names when listing the most relevant pages on the front page, instruct users to set `mainSections` in their project configuration. + +Then your _home_ template can do something like this: + +```go-html-template {file="layouts/home.html"} +{{ range where .Site.RegularPages "Section" "in" .Site.MainSections }} +

    {{ .LinkTitle }}

    +{{ end }} +``` diff --git a/docs/content/en/methods/site/Menus.md b/docs/content/en/methods/site/Menus.md new file mode 100644 index 0000000..2546e1e --- /dev/null +++ b/docs/content/en/methods/site/Menus.md @@ -0,0 +1,89 @@ +--- +title: Menus +description: Returns a collection of menu objects for the given site. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: navigation.Menus + signatures: [SITE.Menus] +--- + +The `Menus` method on a `Site` object returns a collection of menus, where each menu contains one or more entries, either flat or nested. Each entry points to a page within the site, or to an external resource. + +> [!NOTE] +> Menus can be defined and localized in several ways. Please see the [menus][] section for a complete explanation and examples. + +A site can have multiple menus. For example, a main menu and a footer menu: + +{{< code-toggle file=hugo >}} +[[menus.main]] +name = 'Home' +pageRef = '/' +weight = 10 + +[[menus.main]] +name = 'Books' +pageRef = '/books' +weight = 20 + +[[menus.main]] +name = 'Films' +pageRef = '/films' +weight = 30 + +[[menus.footer]] +name = 'Legal' +pageRef = '/legal' +weight = 10 + +[[menus.footer]] +name = 'Privacy' +pageRef = '/privacy' +weight = 20 +{{< /code-toggle >}} + +This template renders the main menu: + +```go-html-template +{{ with site.Menus.main }} + +{{ end }} +``` + +When viewing the home page, the result is: + +```html + +``` + +When viewing the `books` page, the result is: + +```html + +``` + +You will typically render a menu using a _partial_ template. As the active menu entry will be different on each page, use the [`partial`][] function to call the template. Do not use the [`partialCached`][] function. + +The example above is simplistic. Please see the [menu templates][] section for more information. + +[`partialCached`]: /functions/partials/includecached/ +[`partial`]: /functions/partials/include/ +[menu templates]: /templates/menu/ +[menus]: /content-management/menus/ diff --git a/docs/content/en/methods/site/Pages.md b/docs/content/en/methods/site/Pages.md new file mode 100644 index 0000000..3f92c03 --- /dev/null +++ b/docs/content/en/methods/site/Pages.md @@ -0,0 +1,22 @@ +--- +title: Pages +description: Returns a collection of all pages. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pages + signatures: [SITE.Pages] +--- + +This method returns all page [kinds](g) in the current language, in the [default sort order](g). That includes the home page, section pages, taxonomy pages, term pages, and regular pages. + +In most cases you should use the [`RegularPages`][] method instead. + +```go-html-template +{{ range .Site.Pages }} +

    {{ .LinkTitle }}

    +{{ end }} +``` + +[`RegularPages`]: /methods/site/regularpages/ diff --git a/docs/content/en/methods/site/Param.md b/docs/content/en/methods/site/Param.md new file mode 100644 index 0000000..b2ec5d9 --- /dev/null +++ b/docs/content/en/methods/site/Param.md @@ -0,0 +1,28 @@ +--- +title: Param +description: Returns the site parameter with the given key. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: any + signatures: [SITE.Param KEY] +--- + +The `Param` method on a `Site` object is a convenience method to return the value of a user-defined parameter in your project configuration. + +{{< code-toggle file=hugo >}} +[params] +display_toc = true +{{< /code-toggle >}} + +```go-html-template +{{ .Site.Param "display_toc" }} → true +``` + +The above is equivalent to either of these: + +```go-html-template +{{ .Site.Params.display_toc }} +{{ index .Site.Params "display_toc" }} +``` diff --git a/docs/content/en/methods/site/Params.md b/docs/content/en/methods/site/Params.md new file mode 100644 index 0000000..42df53b --- /dev/null +++ b/docs/content/en/methods/site/Params.md @@ -0,0 +1,42 @@ +--- +title: Params +description: Returns a map of custom parameters as defined in your project configuration. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: maps.Params + signatures: [SITE.Params] +--- + +With this project configuration: + +{{< code-toggle file=hugo >}} +[params] + subtitle = 'The Best Widgets on Earth' + copyright-year = '2023' + [params.author] + email = 'jsmith@example.org' + name = 'John Smith' + [params.layouts] + rfc_1123 = 'Mon, 02 Jan 2006 15:04:05 MST' + rfc_3339 = '2006-01-02T15:04:05-07:00' +{{< /code-toggle >}} + +Access the custom parameters by [chaining](g) the [identifiers](g): + +```go-html-template +{{ .Site.Params.subtitle }} → The Best Widgets on Earth +{{ .Site.Params.author.name }} → John Smith + +{{ $layout := .Site.Params.layouts.rfc_1123 }} +{{ .Site.Lastmod.Format $layout }} → Tue, 17 Oct 2023 13:21:02 PDT +``` + +In the template example above, each of the keys is a valid identifier. For example, none of the keys contains a hyphen. To access a key that is not a valid identifier, use the [`index`][] function: + +```go-html-template +{{ index .Site.Params "copyright-year" }} → 2023 +``` + +[`index`]: /functions/collections/indexfunction/ diff --git a/docs/content/en/methods/site/RegularPages.md b/docs/content/en/methods/site/RegularPages.md new file mode 100644 index 0000000..08d1935 --- /dev/null +++ b/docs/content/en/methods/site/RegularPages.md @@ -0,0 +1,32 @@ +--- +title: RegularPages +description: Returns a collection of all regular pages. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pages + signatures: [SITE.RegularPages] +--- + +The `RegularPages` method on a `Site` object returns a collection of all [regular pages](g), in the [default sort order](g). + +```go-html-template +{{ range .Site.RegularPages }} +

    {{ .LinkTitle }}

    +{{ 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 }} +

    {{ .Title }}

    +{{ end }} +``` + +[sorting methods]: /methods/pages/ diff --git a/docs/content/en/methods/site/Role.md b/docs/content/en/methods/site/Role.md new file mode 100644 index 0000000..9aa4150 --- /dev/null +++ b/docs/content/en/methods/site/Role.md @@ -0,0 +1,34 @@ +--- +title: Role +description: Returns the Role object for the given site. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: roles.Role + signatures: [SITE.Role] +--- + +{{< new-in 0.153.0 />}} + +## Overview + +The `Role` method on a `Site` object returns the `Role` object for the given site, derived from the role definition in your project configuration. + +## Methods + +Use these methods on the `Role` object. + +`IsDefault` +: (`bool`) Reports whether this is the [default role](g). + + ```go-html-template + {{ .Site.Role.IsDefault }} → true + ``` + +`Name` +: (`string`) Returns the role name. This is the lowercased key from your project configuration. + + ```go-html-template + {{ .Site.Role.Name }} → guest + ``` diff --git a/docs/content/en/methods/site/Sections.md b/docs/content/en/methods/site/Sections.md new file mode 100644 index 0000000..f3715df --- /dev/null +++ b/docs/content/en/methods/site/Sections.md @@ -0,0 +1,40 @@ +--- +title: Sections +description: Returns a collection of top-level section pages. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Pages + signatures: [SITE.Sections] +--- + +The `Sections` method on a `Site` object returns a collection of top-level [section pages](g), in the [default sort order](g). + +Given this content structure: + +```tree +content/ +├── books/ +│ ├── book-1.md +│ └── book-2.md +├── films/ +│ ├── film-1.md +│ └── film-2.md +└── _index.md +``` + +This template: + +```go-html-template +{{ range .Site.Sections }} +

    {{ .LinkTitle }}

    +{{ end }} +``` + +Is rendered to: + +```html +

    Books

    +

    Films

    +``` diff --git a/docs/content/en/methods/site/Sites.md b/docs/content/en/methods/site/Sites.md new file mode 100644 index 0000000..84e668d --- /dev/null +++ b/docs/content/en/methods/site/Sites.md @@ -0,0 +1,15 @@ +--- +title: Sites +description: Returns a collection of all sites for all dimensions. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Sites + signatures: [SITE.Sites] +expiryDate: '2028-02-18' # deprecated 2026-02-18 in v0.156.0 +--- + +{{< deprecated-in 0.156.0 >}} +Use the [`hugo.Sites`](/functions/hugo/sites/) function instead. +{{< /deprecated-in >}} diff --git a/docs/content/en/methods/site/Store.md b/docs/content/en/methods/site/Store.md new file mode 100644 index 0000000..e7af6b5 --- /dev/null +++ b/docs/content/en/methods/site/Store.md @@ -0,0 +1,112 @@ +--- +title: Store +description: Returns a persistent data structure for storing and manipulating keyed values, 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 persistent data structure for storing and manipulating keyed values, scoped to the current site. To create a data structure with a different [scope](g), refer to the [scope](#scope) section below. + +## Methods + +Use these methods on the data structure. + +`Set` +: Sets the value of a given key. + + ```go-html-template + {{ site.Store.Set "greeting" "Hello" }} + ``` + +`Get` +: (`any`) 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` +: (`[]any`) 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/store-scope.md" %}} + +## Determinate values + +The `Store` method is often used to set values within a _shortcode_ template, a _partial_ template called by a _shortcode_ template, or by a _render hook_ template. In all three cases, the stored values are indeterminate until Hugo renders the page content. + +If you need to access a stored value from a parent template, and the parent template has not yet rendered the page content, you can trigger content rendering by assigning the returned value to a [noop](g) variable: + +```go-html-template +{{ $noop := .Content }} +{{ site.Store.Get "mykey" }} +``` + +You can also trigger content rendering with the `ContentWithoutSummary`, `FuzzyWordCount`, `Len`, `Plain`, `PlainWords`, `ReadingTime`, `Summary`, `Truncated`, and `WordCount` methods. For example: + +```go-html-template +{{ $noop := .WordCount }} +{{ site.Store.Get "mykey" }} +``` diff --git a/docs/content/en/methods/site/Taxonomies.md b/docs/content/en/methods/site/Taxonomies.md new file mode 100644 index 0000000..13f846b --- /dev/null +++ b/docs/content/en/methods/site/Taxonomies.md @@ -0,0 +1,181 @@ +--- +title: Taxonomies +description: Returns a data structure containing the site's Taxonomy objects, the terms within each Taxonomy object, and the pages to which the terms are assigned. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.TaxonomyList + signatures: [SITE.Taxonomies] +--- + +Conceptually, the `Taxonomies` method on a `Site` object returns a data structure such as: + + +{{< code-toggle file=hugo >}} +taxonomy a: + - term 1: + - page 1 + - page 2 + - term 2: + - page 1 +taxonomy b: + - term 1: + - page 2 + - term 2: + - page 1 + - page 2 +{{< /code-toggle >}} + + +For example, on a book review site you might create two taxonomies; one for genres and another for authors. + +With this project configuration: + +{{< code-toggle file=hugo >}} +[taxonomies] +genre = 'genres' +author = 'authors' +{{< /code-toggle >}} + +And this content structure: + +```tree +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 +
      + {{ range .Site.Taxonomies.genres.suspense }} +
    • {{ .LinkTitle }}
    • + {{ end }} +
    +``` + +Hugo renders this to: + +```html + +``` + +> [!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 is 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 + +``` + +### Render a site's taxonomies + +The following example displays all terms in a site's tags taxonomy: + +```go-html-template + +``` + +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 }} + + {{ end }} +{{ end }} +``` + +[taxonomies]: /content-management/taxonomies/ diff --git a/docs/content/en/methods/site/Title.md b/docs/content/en/methods/site/Title.md new file mode 100644 index 0000000..ad8a48c --- /dev/null +++ b/docs/content/en/methods/site/Title.md @@ -0,0 +1,22 @@ +--- +title: Title +description: Returns the title as defined in your project configuration. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [SITE.Title] +--- + +Project configuration: + +{{< code-toggle file=hugo >}} +title = 'My Documentation Site' +{{< /code-toggle >}} + +Template: + +```go-html-template +{{ .Site.Title }} → My Documentation Site +``` diff --git a/docs/content/en/methods/site/Version.md b/docs/content/en/methods/site/Version.md new file mode 100644 index 0000000..701e5e4 --- /dev/null +++ b/docs/content/en/methods/site/Version.md @@ -0,0 +1,34 @@ +--- +title: Version +description: Returns the Version object for the given site. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: versions.Version + signatures: [SITE.Version] +--- + +{{< new-in 0.153.0 />}} + +## Overview + +The `Version` method on a `Site` object returns the `Version` object for the given site, derived from the version definition in your project configuration. + +## Methods + +Use these methods on the `Version` object. + +`IsDefault` +: (`bool`) Reports whether this is the [default version](g). + + ```go-html-template + {{ .Site.Version.IsDefault }} → true + ``` + +`Name` +: (`string`) Returns the version name. This is the lowercased key from your project configuration. + + ```go-html-template + {{ .Site.Version.Name }} → v1.0.0 + ``` diff --git a/docs/content/en/methods/site/_index.md b/docs/content/en/methods/site/_index.md new file mode 100644 index 0000000..4f878a1 --- /dev/null +++ b/docs/content/en/methods/site/_index.md @@ -0,0 +1,8 @@ +--- +title: Site methods +linkTitle: Site +description: Use these methods with a Site object. +categories: [] +keywords: [] +aliases: [/variables/site/] +--- diff --git a/docs/content/en/methods/taxonomy/Alphabetical.md b/docs/content/en/methods/taxonomy/Alphabetical.md new file mode 100644 index 0000000..af4af59 --- /dev/null +++ b/docs/content/en/methods/taxonomy/Alphabetical.md @@ -0,0 +1,69 @@ +--- +title: Alphabetical +description: Returns an ordered taxonomy, sorted alphabetically by term. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.OrderedTaxonomy + signatures: [TAXONOMY.Alphabetical] +--- + +The `Alphabetical` method on a `Taxonomy` object returns an [ordered taxonomy](g), sorted alphabetically by [term](g). + +While a `Taxonomy` object is a [map](g), an ordered taxonomy is a [slice](g), where each element is an object that contains the term and a slice of its [weighted pages](g). + +{{% include "/_common/methods/taxonomy/get-a-taxonomy-object.md" %}} + +## Get the ordered taxonomy + +Now that we have captured the “genres” Taxonomy object, let's get the ordered taxonomy sorted alphabetically by term: + +```go-html-template +{{ $taxonomyObject.Alphabetical }} +``` + +To reverse the sort order: + +```go-html-template +{{ $taxonomyObject.Alphabetical.Reverse }} +``` + +To inspect the data structure: + +```go-html-template +
    {{ debug.Dump $taxonomyObject.Alphabetical }}
    +``` + +{{% include "/_common/methods/taxonomy/ordered-taxonomy-element-methods.md" %}} + +## Example + +With this template: + +```go-html-template +{{ range $taxonomyObject.Alphabetical }} +

    {{ .Page.LinkTitle }} ({{ .Count }})

    + +{{ end }} +``` + +Hugo renders: + +```html +

    romance (2)

    + +

    suspense (3)

    + +``` diff --git a/docs/content/en/methods/taxonomy/ByCount.md b/docs/content/en/methods/taxonomy/ByCount.md new file mode 100644 index 0000000..196b317 --- /dev/null +++ b/docs/content/en/methods/taxonomy/ByCount.md @@ -0,0 +1,69 @@ +--- +title: ByCount +description: Returns an ordered taxonomy, sorted by the number of pages associated with each term. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.OrderedTaxonomy + signatures: [TAXONOMY.ByCount] +--- + +The `ByCount` method on a `Taxonomy` object returns an [ordered taxonomy](g), sorted by the number of pages associated with each [term](g), then sorted alphabetically by term in the event of a tie. + +While a `Taxonomy` object is a [map](g), an ordered taxonomy is a [slice](g), where each element is an object that contains the term and a slice of its [weighted pages](g). + +{{% include "/_common/methods/taxonomy/get-a-taxonomy-object.md" %}} + +## Get the ordered taxonomy + +Now that we have captured the “genres” Taxonomy object, let's get the ordered taxonomy sorted by the number of pages associated with each term: + +```go-html-template +{{ $taxonomyObject.ByCount }} +``` + +To reverse the sort order: + +```go-html-template +{{ $taxonomyObject.ByCount.Reverse }} +``` + +To inspect the data structure: + +```go-html-template +
    {{ debug.Dump $taxonomyObject.ByCount }}
    +``` + +{{% include "/_common/methods/taxonomy/ordered-taxonomy-element-methods.md" %}} + +## Example + +With this template: + +```go-html-template +{{ range $taxonomyObject.ByCount }} +

    {{ .Page.LinkTitle }} ({{ .Count }})

    + +{{ end }} +``` + +Hugo renders: + +```html +

    suspense (3)

    + +

    romance (2)

    + +``` diff --git a/docs/content/en/methods/taxonomy/Count.md b/docs/content/en/methods/taxonomy/Count.md new file mode 100644 index 0000000..5f62b2f --- /dev/null +++ b/docs/content/en/methods/taxonomy/Count.md @@ -0,0 +1,22 @@ +--- +title: Count +description: Returns the number of number of weighted pages to which the given term has been assigned. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: int + signatures: [TAXONOMY.Count TERM] +--- + +The `Count` method on a `Taxonomy` object returns the number of number of [weighted pages](g) to which the given [term](g) has been assigned. + +{{% include "/_common/methods/taxonomy/get-a-taxonomy-object.md" %}} + +## Count the weighted pages + +Now that we have captured the `genres` `Taxonomy` object, let's count the number of weighted pages to which the `suspense` term has been assigned: + +```go-html-template +{{ $taxonomyObject.Count "suspense" }} → 3 +``` diff --git a/docs/content/en/methods/taxonomy/Get.md b/docs/content/en/methods/taxonomy/Get.md new file mode 100644 index 0000000..6605c8a --- /dev/null +++ b/docs/content/en/methods/taxonomy/Get.md @@ -0,0 +1,67 @@ +--- +title: Get +description: Returns a slice of weighted pages to which the given term has been assigned. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.WeightedPages + signatures: [TAXONOMY.Get TERM] +--- + +The `Get` method on a `Taxonomy` object returns a slice of [weighted pages](g) to which the given [term](g) has been assigned. + +{{% include "/_common/methods/taxonomy/get-a-taxonomy-object.md" %}} + +## Get the weighted pages + +Now that we have captured the `genres` `Taxonomy` object, let's get the weighted pages to which the `suspense` term has been assigned: + +```go-html-template +{{ $weightedPages := $taxonomyObject.Get "suspense" }} +``` + +The above is equivalent to: + +```go-html-template +{{ $weightedPages := $taxonomyObject.suspense }} +``` + +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 }} +

    {{ .LinkTitle }}

    +{{ end }} +``` + +Hugo renders: + +```html +

    Jamaica inn

    +

    Death on the nile

    +

    And then there were none

    +``` + +[`index`]: /functions/collections/indexfunction/ diff --git a/docs/content/en/methods/taxonomy/Page.md b/docs/content/en/methods/taxonomy/Page.md new file mode 100644 index 0000000..628148e --- /dev/null +++ b/docs/content/en/methods/taxonomy/Page.md @@ -0,0 +1,24 @@ +--- +title: Page +description: Returns the taxonomy page or nil if the taxonomy has no terms. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: page.Page + signatures: [TAXONOMY.Page] +--- + +This `TAXONOMY` method returns nil if the taxonomy has no terms, so you must code defensively: + +```go-html-template +{{ with .Site.Taxonomies.tags.Page }} + {{ .LinkTitle }} +{{ end }} +``` + +This is rendered to: + +```html +Tags +``` diff --git a/docs/content/en/methods/taxonomy/_index.md b/docs/content/en/methods/taxonomy/_index.md new file mode 100644 index 0000000..9cf4c74 --- /dev/null +++ b/docs/content/en/methods/taxonomy/_index.md @@ -0,0 +1,7 @@ +--- +title: Taxonomy methods +linkTitle: Taxonomy +description: Use these methods with a Taxonomy object. +keywords: [] +aliases: [/variables/taxonomy/] +--- diff --git a/docs/content/en/methods/time/Add.md b/docs/content/en/methods/time/Add.md new file mode 100644 index 0000000..e518a16 --- /dev/null +++ b/docs/content/en/methods/time/Add.md @@ -0,0 +1,20 @@ +--- +title: Add +description: Returns the given time plus the given duration. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: time.Time + signatures: [TIME.Add DURATION] +--- + +```go-html-template +{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }} + +{{ $d1 = time.ParseDuration "3h20m10s" }} +{{ $d2 = time.ParseDuration "-3h20m10s" }} + +{{ $t.Add $d1 }} → 2023-01-28 03:05:08 -0800 PST +{{ $t.Add $d2 }} → 2023-01-27 20:24:48 -0800 PST +``` diff --git a/docs/content/en/methods/time/AddDate.md b/docs/content/en/methods/time/AddDate.md new file mode 100644 index 0000000..d8417cc --- /dev/null +++ b/docs/content/en/methods/time/AddDate.md @@ -0,0 +1,39 @@ +--- +title: AddDate +description: Returns the time corresponding to adding the given number of years, months, and days to the given time.Time value. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: time.Time + signatures: [TIME.AddDate YEARS MONTHS DAYS] +aliases: [/functions/adddate] +--- + +```go-html-template +{{ $d := "2022-01-01" | time.AsTime }} + +{{ $d.AddDate 0 0 1 | time.Format "2006-01-02" }} → 2022-01-02 +{{ $d.AddDate 0 1 1 | time.Format "2006-01-02" }} → 2022-02-02 +{{ $d.AddDate 1 1 1 | time.Format "2006-01-02" }} → 2023-02-02 + +{{ $d.AddDate -1 -1 -1 | time.Format "2006-01-02" }} → 2020-11-30 +``` + +> [!NOTE] +> When adding months or years, Hugo normalizes the final `time.Time` value if the resulting day does not exist. For example, adding one month to 31 January produces 2 March or 3 March, depending on the year. +> +> See [this explanation][] from the Go team. + +```go-html-template +{{ $d := "2023-01-31" | time.AsTime }} +{{ $d.AddDate 0 1 0 | time.Format "2006-01-02" }} → 2023-03-03 + +{{ $d := "2024-01-31" | time.AsTime }} +{{ $d.AddDate 0 1 0 | time.Format "2006-01-02" }} → 2024-03-02 + +{{ $d := "2024-02-29" | time.AsTime }} +{{ $d.AddDate 1 0 0 | time.Format "2006-01-02" }} → 2025-03-01 +``` + +[this explanation]: https://github.com/golang/go/issues/31145#issuecomment-479067967 diff --git a/docs/content/en/methods/time/After.md b/docs/content/en/methods/time/After.md new file mode 100644 index 0000000..1c8d41f --- /dev/null +++ b/docs/content/en/methods/time/After.md @@ -0,0 +1,17 @@ +--- +title: After +description: Reports whether TIME1 is after TIME2. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: bool + signatures: [TIME1.After TIME2] +--- + +```go-html-template +{{ $t1 := time.AsTime "2023-01-01T17:00:00-08:00" }} +{{ $t2 := time.AsTime "2010-01-01T17:00:00-08:00" }} + +{{ $t1.After $t2 }} → true +``` diff --git a/docs/content/en/methods/time/Before.md b/docs/content/en/methods/time/Before.md new file mode 100644 index 0000000..f6dc3a8 --- /dev/null +++ b/docs/content/en/methods/time/Before.md @@ -0,0 +1,16 @@ +--- +title: Before +description: Reports whether TIME1 is before TIME2. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: bool + signatures: [TIME1.Before TIME2] +--- + +```go-html-template +{{ $t1 := time.AsTime "2023-01-01T17:00:00-08:00" }} +{{ $t2 := time.AsTime "2030-01-01T17:00:00-08:00" }} + +{{ $t1.Before $t2 }} → true diff --git a/docs/content/en/methods/time/Day.md b/docs/content/en/methods/time/Day.md new file mode 100644 index 0000000..e9e6787 --- /dev/null +++ b/docs/content/en/methods/time/Day.md @@ -0,0 +1,15 @@ +--- +title: Day +description: Returns the day of the month of the given time.Time value. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: int + signatures: [TIME.Day] +--- + +```go-html-template +{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }} +{{ $t.Day }} → 27 +``` diff --git a/docs/content/en/methods/time/Equal.md b/docs/content/en/methods/time/Equal.md new file mode 100644 index 0000000..6db1042 --- /dev/null +++ b/docs/content/en/methods/time/Equal.md @@ -0,0 +1,17 @@ +--- +title: Equal +description: Reports whether TIME1 is equal to TIME2. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: bool + signatures: [TIME1.Equal TIME2] +--- + +```go-html-template +{{ $t1 := time.AsTime "2023-01-01T17:00:00-08:00" }} +{{ $t2 := time.AsTime "2023-01-01T20:00:00-05:00" }} + +{{ $t1.Equal $t2 }} → true +``` diff --git a/docs/content/en/methods/time/Format.md b/docs/content/en/methods/time/Format.md new file mode 100644 index 0000000..cdac662 --- /dev/null +++ b/docs/content/en/methods/time/Format.md @@ -0,0 +1,89 @@ +--- +title: Format +description: Returns a textual representation of the time.Time value formatted according to the layout string. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: string + signatures: [TIME.Format LAYOUT] +aliases: [/methods/time/format] +--- + +```go-template +{{ $t := "2023-01-27T23:44:58-08:00" }} +{{ $t = time.AsTime $t }} +{{ $format := "2 Jan 2006" }} + +{{ $t.Format $format }} → 27 Jan 2023 +``` + +> [!NOTE] +> To [localize](g) the return value, use the [`time.Format`][] function instead. + +Use the `Format` method with any `time.Time` value, including the four predefined front matter dates: + +```go-html-template +{{ $format := "2 Jan 2006" }} + +{{ .Date.Format $format }} +{{ .PublishDate.Format $format }} +{{ .ExpiryDate.Format $format }} +{{ .Lastmod.Format $format }} +``` + +> [!NOTE] +> Use the [`time.Format`][] function to format string representations of dates, and to format raw TOML dates that exclude time and time zone offset. + +## Layout string + +{{% include "/_common/time-layout-string.md" %}} + +## Examples + +Given this front matter: + +{{< code-toggle fm=true >}} +title = "About time" +date = 2023-01-27T23:44:58-08:00 +{{< /code-toggle >}} + +The examples below were rendered in the `America/Los_Angeles` time zone: + +Format string|Result +:--|:-- +`Monday, January 2, 2006`|`Friday, January 27, 2023` +`Mon Jan 2 2006`|`Fri Jan 27 2023` +`January 2006`|`January 2023` +`2006-01-02`|`2023-01-27` +`Monday`|`Friday` +`02 Jan 06 15:04 MST`|`27 Jan 23 23:44 PST` +`Mon, 02 Jan 2006 15:04:05 MST`|`Fri, 27 Jan 2023 23:44:58 PST` +`Mon, 02 Jan 2006 15:04:05 -0700`|`Fri, 27 Jan 2023 23:44:58 -0800` + +## UTC and local time + +Convert and format any `time.Time` value to either Coordinated Universal Time (UTC) or local time. + +```go-html-template +{{ $t := "2023-01-27T23:44:58-08:00" }} +{{ $t = time.AsTime $t }} +{{ $format := "2 Jan 2006 3:04:05 PM MST" }} + +{{ $t.UTC.Format $format }} → 28 Jan 2023 7:44:58 AM UTC +{{ $t.Local.Format $format }} → 27 Jan 2023 11:44:58 PM PST +``` + +## Ordinal representation + +Use the [`humanize`][] function to render the day of the month as an ordinal number: + +```go-html-template +{{ $t := "2023-01-27T23:44:58-08:00" }} +{{ $t = time.AsTime $t }} + +{{ humanize $t.Day }} of {{ $t.Format "January 2006" }} → 27th of January 2023 +``` + +[`humanize`]: /functions/inflect/humanize/ +[`time.Format`]: /functions/time/format/ diff --git a/docs/content/en/methods/time/Hour.md b/docs/content/en/methods/time/Hour.md new file mode 100644 index 0000000..28ecf62 --- /dev/null +++ b/docs/content/en/methods/time/Hour.md @@ -0,0 +1,15 @@ +--- +title: Hour +description: Returns the hour within the day of the given time.Time value, in the range [0, 23]. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: int + signatures: [TIME.Hour] +--- + +```go-html-template +{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }} +{{ $t.Hour }} → 23 +``` diff --git a/docs/content/en/methods/time/IsDST.md b/docs/content/en/methods/time/IsDST.md new file mode 100644 index 0000000..28177b1 --- /dev/null +++ b/docs/content/en/methods/time/IsDST.md @@ -0,0 +1,18 @@ +--- +title: IsDST +description: Reports whether the given time.Time value is in Daylight Savings Time. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: bool + signatures: [TIME.IsDST] +--- + +```go-html-template +{{ $t1 := time.AsTime "2023-01-01T00:00:00-08:00" }} +{{ $t2 := time.AsTime "2023-07-01T00:00:00-07:00" }} + +{{ $t1.IsDST }} → false +{{ $t2.IsDST }} → true +``` diff --git a/docs/content/en/methods/time/IsZero.md b/docs/content/en/methods/time/IsZero.md new file mode 100644 index 0000000..4001727 --- /dev/null +++ b/docs/content/en/methods/time/IsZero.md @@ -0,0 +1,18 @@ +--- +title: IsZero +description: Reports whether the given time.Time value represents the zero time instant, January 1, year 1, 00:00:00 UTC. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: bool + signatures: [TIME.IsZero] +--- + +````go-html-template +{{ $t1 := time.AsTime "2023-01-01T00:00:00-08:00" }} +{{ $t2 := time.AsTime "0001-01-01T00:00:00-00:00" }} + +{{ $t1.IsZero }} → false +{{ $t2.IsZero }} → true +``` diff --git a/docs/content/en/methods/time/Local.md b/docs/content/en/methods/time/Local.md new file mode 100644 index 0000000..74fe889 --- /dev/null +++ b/docs/content/en/methods/time/Local.md @@ -0,0 +1,15 @@ +--- +title: Local +description: Returns the given time.Time value with the location set to local time. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: time.Time + signatures: [TIME.Local] +--- + +```go-html-template +{{ $t := time.AsTime "2023-01-28T07:44:58+00:00" }} +{{ $t.Local }} → 2023-01-27 23:44:58 -0800 PST +``` diff --git a/docs/content/en/methods/time/Minute.md b/docs/content/en/methods/time/Minute.md new file mode 100644 index 0000000..b53db6d --- /dev/null +++ b/docs/content/en/methods/time/Minute.md @@ -0,0 +1,15 @@ +--- +title: Minute +description: Returns the minute offset within the hour of the given time.Time value, in the range [0, 59]. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: int + signatures: [TIME.Minute] +--- + +```go-html-template +{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }} +{{ $t.Minute }} → 44 +``` diff --git a/docs/content/en/methods/time/Month.md b/docs/content/en/methods/time/Month.md new file mode 100644 index 0000000..b0ccea9 --- /dev/null +++ b/docs/content/en/methods/time/Month.md @@ -0,0 +1,24 @@ +--- +title: Month +description: Returns the month of the year of the given time.Time value. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: time.Month + signatures: [TIME.Month] +--- + +To convert the `time.Month` value to a string: + +```go-html-template +{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }} +{{ $t.Month.String }} → January +``` + +To convert the `time.Month` value to an integer. + +```go-html-template +{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }} +{{ $t.Month | int }} → 1 +``` diff --git a/docs/content/en/methods/time/Nanosecond.md b/docs/content/en/methods/time/Nanosecond.md new file mode 100644 index 0000000..d895f96 --- /dev/null +++ b/docs/content/en/methods/time/Nanosecond.md @@ -0,0 +1,15 @@ +--- +title: Nanosecond +description: Returns the nanosecond offset within the second of the given time.Time value, in the range [0, 999999999]. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: int + signatures: [TIME.Nanosecond] +--- + +```go-html-template +{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }} +{{ $t.Nanosecond }} → 0 +``` diff --git a/docs/content/en/methods/time/Round.md b/docs/content/en/methods/time/Round.md new file mode 100644 index 0000000..afda0e2 --- /dev/null +++ b/docs/content/en/methods/time/Round.md @@ -0,0 +1,21 @@ +--- +title: Round +description: Returns the result of rounding TIME to the nearest multiple of DURATION since January 1, 0001, 00:00:00 UTC. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: time.Time + signatures: [TIME.Round DURATION] +--- + +The rounding behavior for halfway values is to round up. + +The `Round` method operates on TIME as an absolute duration since the [zero time](g); it does not operate on the presentation form of the time. If DURATION is a multiple of one hour, `Round` may return a time with a non-zero minute, depending on the time zone. + +```go-html-template +{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }} +{{ $d := time.ParseDuration "1h" }} + +{{ ($t.Round $d).Format "2006-01-02T15:04:05-00:00" }} → 2023-01-28T00:00:00-00:00 +``` diff --git a/docs/content/en/methods/time/Second.md b/docs/content/en/methods/time/Second.md new file mode 100644 index 0000000..3af086f --- /dev/null +++ b/docs/content/en/methods/time/Second.md @@ -0,0 +1,15 @@ +--- +title: Second +description: Returns the second offset within the minute of the given time.Time value, in the range [0, 59]. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: int + signatures: [TIME.Second] +--- + +```go-html-template +{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }} +{{ $t.Second }} → 58 +``` diff --git a/docs/content/en/methods/time/Sub.md b/docs/content/en/methods/time/Sub.md new file mode 100644 index 0000000..d48bf34 --- /dev/null +++ b/docs/content/en/methods/time/Sub.md @@ -0,0 +1,17 @@ +--- +title: Sub +description: Returns the duration computed by subtracting TIME2 from TIME1. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: time.Duration + signatures: [TIME1.Sub TIME2] +--- + +```go-html-template +{{ $t1 := time.AsTime "2023-01-27T23:44:58-08:00" }} +{{ $t2 := time.AsTime "2023-01-26T22:34:38-08:00" }} + +{{ $t1.Sub $t2 }} → 25h10m20s +``` diff --git a/docs/content/en/methods/time/Truncate.md b/docs/content/en/methods/time/Truncate.md new file mode 100644 index 0000000..b90c384 --- /dev/null +++ b/docs/content/en/methods/time/Truncate.md @@ -0,0 +1,19 @@ +--- +title: Truncate +description: Returns the result of rounding TIME down to a multiple of DURATION since January 1, 0001, 00:00:00 UTC. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: time.Time + signatures: [TIME.Truncate DURATION] +--- + +The `Truncate` method operates on TIME as an absolute duration since the [zero time](g); it does not operate on the presentation form of the time. If DURATION is a multiple of one hour, `Truncate` may return a time with a non-zero minute, depending on the time zone. + +```go-html-template +{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }} +{{ $d := time.ParseDuration "1h" }} + +{{ ($t.Truncate $d).Format "2006-01-02T15:04:05-00:00" }} → 2023-01-27T23:00:00-00:00 +``` diff --git a/docs/content/en/methods/time/UTC.md b/docs/content/en/methods/time/UTC.md new file mode 100644 index 0000000..e131a00 --- /dev/null +++ b/docs/content/en/methods/time/UTC.md @@ -0,0 +1,14 @@ +--- +title: UTC +description: Returns the given time.Time value with the location set to UTC. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: time.Time + signatures: [TIME.UTC] +--- + +```go-html-template +{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }} +{{ $t.UTC }} → 2023-01-28 07:44:58 +0000 UTC diff --git a/docs/content/en/methods/time/Unix.md b/docs/content/en/methods/time/Unix.md new file mode 100644 index 0000000..6cce134 --- /dev/null +++ b/docs/content/en/methods/time/Unix.md @@ -0,0 +1,19 @@ +--- +title: Unix +description: Returns the given time.Time value expressed as the number of seconds elapsed since January 1, 1970 UTC. +categories: [] +params: + functions_and_methods: + returnType: int64 + signatures: [TIME.Unix] +aliases: [/functions/unix] +--- + +See [Unix epoch][]. + +```go-html-template +{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }} +{{ $t.Unix }} → 1674891898 +``` + +[Unix epoch]: https://en.wikipedia.org/wiki/Unix_time diff --git a/docs/content/en/methods/time/UnixMicro.md b/docs/content/en/methods/time/UnixMicro.md new file mode 100644 index 0000000..3159b27 --- /dev/null +++ b/docs/content/en/methods/time/UnixMicro.md @@ -0,0 +1,19 @@ +--- +title: UnixMicro +description: Returns the given time.Time value expressed as the number of microseconds elapsed since January 1, 1970 UTC. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: int64 + signatures: [TIME.UnixMicro] +--- + +See [Unix epoch][]. + +```go-html-template +{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }} +{{ $t.UnixMicro }} → 1674891898000000 +``` + +[Unix epoch]: https://en.wikipedia.org/wiki/Unix_time diff --git a/docs/content/en/methods/time/UnixMilli.md b/docs/content/en/methods/time/UnixMilli.md new file mode 100644 index 0000000..963d42d --- /dev/null +++ b/docs/content/en/methods/time/UnixMilli.md @@ -0,0 +1,19 @@ +--- +title: UnixMilli +description: Returns the given time.Time value expressed as the number of milliseconds elapsed since January 1, 1970 UTC. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: int64 + signatures: [TIME.UnixMilli] +--- + +See [Unix epoch][]. + +```go-html-template +{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }} +{{ $t.UnixMilli }} → 1674891898000 +``` + +[Unix epoch]: https://en.wikipedia.org/wiki/Unix_time diff --git a/docs/content/en/methods/time/UnixNano.md b/docs/content/en/methods/time/UnixNano.md new file mode 100644 index 0000000..c6b2a76 --- /dev/null +++ b/docs/content/en/methods/time/UnixNano.md @@ -0,0 +1,19 @@ +--- +title: UnixNano +description: Returns the given time.Time value expressed as the number of nanoseconds elapsed since January 1, 1970 UTC. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: int64 + signatures: [TIME.UnixNano] +--- + +See [Unix epoch][]. + +```go-html-template +{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }} +{{ $t.UnixNano }} → 1674891898000000000 +``` + +[Unix epoch]: https://en.wikipedia.org/wiki/Unix_time diff --git a/docs/content/en/methods/time/Weekday.md b/docs/content/en/methods/time/Weekday.md new file mode 100644 index 0000000..da939ff --- /dev/null +++ b/docs/content/en/methods/time/Weekday.md @@ -0,0 +1,23 @@ +--- +title: Weekday +description: Returns the day of the week of the given time.Time value. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: time.Weekday + signatures: [TIME.Weekday] +--- + +To convert the `time.Weekday` value to a string: + +```go-html-template +{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }} +{{ $t.Weekday.String }} → Friday +``` + +To convert the `time.Weekday` value to an integer. + +```go-html-template +{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }} +{{ $t.Weekday | int }} → 5 diff --git a/docs/content/en/methods/time/Year.md b/docs/content/en/methods/time/Year.md new file mode 100644 index 0000000..3f647ea --- /dev/null +++ b/docs/content/en/methods/time/Year.md @@ -0,0 +1,15 @@ +--- +title: Year +description: Returns the year of the given time.Time value. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: int + signatures: [TIME.Year] +--- + +```go-html-template +{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }} +{{ $t.Year }} → 2023 +``` diff --git a/docs/content/en/methods/time/YearDay.md b/docs/content/en/methods/time/YearDay.md new file mode 100644 index 0000000..a93158b --- /dev/null +++ b/docs/content/en/methods/time/YearDay.md @@ -0,0 +1,15 @@ +--- +title: YearDay +description: Returns the day of the year of the given time.Time value, in the range [1, 365] for non-leap years, and [1, 366] in leap years. +categories: [] +keywords: [] +params: + functions_and_methods: + returnType: int + signatures: [TIME.YearDay] +--- + +```go-html-template +{{ $t := time.AsTime "2023-01-27T23:44:58-08:00" }} +{{ $t.YearDay }} → 27 +``` diff --git a/docs/content/en/methods/time/_index.md b/docs/content/en/methods/time/_index.md new file mode 100644 index 0000000..067d91a --- /dev/null +++ b/docs/content/en/methods/time/_index.md @@ -0,0 +1,7 @@ +--- +title: Time methods +linkTitle: Time +description: Use these methods with a time.Time value. +categories: [] +keywords: [] +--- diff --git a/docs/content/en/news/_content.gotmpl b/docs/content/en/news/_content.gotmpl new file mode 100644 index 0000000..af3cf47 --- /dev/null +++ b/docs/content/en/news/_content.gotmpl @@ -0,0 +1,31 @@ +{{/* Get releases from GitHub. */}} +{{ $u := "https://api.github.com/repos/gohugoio/hugo/releases" }} +{{ $releases := partial "helpers/funcs/get-remote-data.html" $u }} +{{ $releases = where $releases "draft" false }} +{{ $releases = where $releases "prerelease" false }} + +{{/* Add pages. */}} +{{ range $releases | first 24 }} + {{ $publishDate := .published_at | time.AsTime }} + + {{/* Correct the v0.138.0 release date. See https://github.com/gohugoio/hugo/issues/13066. */}} + {{ if eq .name "v0.138.0" }} + {{ $publishDate = "2024-11-06T11:22:34Z" }} + {{ end }} + + {{ $content := dict "mediaType" "text/markdown" "value" "" }} + {{ $dates := dict "publishDate" (time.AsTime $publishDate) }} + {{ $params := dict "permalink" .html_url }} + {{ $build := dict "render" "never" "list" "local" }} + {{ $page := dict + "build" $build + "content" $content + "dates" $dates + "kind" "page" + "params" $params + "path" (strings.Replace .name "." "-") + "slug" .name + "title" (printf "Release %s" .name) + }} + {{ $.AddPage $page }} +{{ end }} diff --git a/docs/content/en/news/_index.md b/docs/content/en/news/_index.md new file mode 100644 index 0000000..23c082c --- /dev/null +++ b/docs/content/en/news/_index.md @@ -0,0 +1,9 @@ +--- +title: News +description: Stay up-to-date with the latest news and announcements. +outputs: + - html + - rss +weight: 10 +aliases: [/release-notes/] +--- diff --git a/docs/content/en/quick-reference/_index.md b/docs/content/en/quick-reference/_index.md new file mode 100644 index 0000000..98f978f --- /dev/null +++ b/docs/content/en/quick-reference/_index.md @@ -0,0 +1,8 @@ +--- +title: Quick reference guides +linkTitle: Quick reference +description: Use these quick reference guides for quick access to key information. +categories: [] +keywords: [] +weight: 10 +--- diff --git a/docs/content/en/quick-reference/emojis.md b/docs/content/en/quick-reference/emojis.md new file mode 100644 index 0000000..df7e81e --- /dev/null +++ b/docs/content/en/quick-reference/emojis.md @@ -0,0 +1,1679 @@ +--- +title: Emojis +description: Include emoji shortcodes in your Markdown or templates. +categories: [] +keywords: [] +params: + searchable: false +--- + +## Attribution + +This quick reference guide was generated using the [ikatyang/emoji-cheat-sheet][] project which reads from the [GitHub Emoji API][] and the [Unicode Full Emoji List][]. + +Note that GitHub [custom emoji](#github-custom-emoji) are not supported. + +## Usage + +Configure Hugo to enable emoji processing in Markdown: + +{{< code-toggle file=hugo >}} +enableEmoji = true +{{< /code-toggle >}} + +With emoji processing enabled, this Markdown: + +```md +Hello! :wave: +``` + +Is rendered to: + +```html +Hello! 👋 +``` + +And in your browser... Hello! :wave: + +To process an emoji shortcode from within a template, use the [`emojify`][] function or pass the string through the [`RenderString`][] method on a `Page` object: + +```go-html-template +{{ "Hello! :wave:" | .RenderString }} +``` + +[GitHub Emoji API]: https://api.github.com/emojis +[Unicode Full Emoji List]: https://unicode.org/emoji/charts/full-emoji-list.html +[`RenderString`]: /methods/page/renderstring/ +[`emojify`]: /functions/transform/emojify/ +[ikatyang/emoji-cheat-sheet]: https://github.com/ikatyang/emoji-cheat-sheet/ + + + +## Table of Contents + +- [Smileys & Emotion](#smileys--emotion) +- [People & Body](#people--body) +- [Animals & Nature](#animals--nature) +- [Food & Drink](#food--drink) +- [Travel & Places](#travel--places) +- [Activities](#activities) +- [Objects](#objects) +- [Symbols](#symbols) +- [Flags](#flags) +- [GitHub Custom Emoji](#github-custom-emoji) + +## Smileys & Emotion + +- [Face Smiling](#face-smiling) +- [Face Affection](#face-affection) +- [Face Tongue](#face-tongue) +- [Face Hand](#face-hand) +- [Face Neutral Skeptical](#face-neutral-skeptical) +- [Face Sleepy](#face-sleepy) +- [Face Unwell](#face-unwell) +- [Face Hat](#face-hat) +- [Face Glasses](#face-glasses) +- [Face Concerned](#face-concerned) +- [Face Negative](#face-negative) +- [Face Costume](#face-costume) +- [Cat Face](#cat-face) +- [Monkey Face](#monkey-face) +- [Heart](#heart) +- [Emotion](#emotion) + +### Face Smiling + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#smileys--emotion) | :grinning: | `:grinning:` | :smiley: | `:smiley:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :smile: | `:smile:` | :grin: | `:grin:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :laughing: | `:laughing:` `:satisfied:` | :sweat_smile: | `:sweat_smile:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :rofl: | `:rofl:` | :joy: | `:joy:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :slightly_smiling_face: | `:slightly_smiling_face:` | :upside_down_face: | `:upside_down_face:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :melting_face: | `:melting_face:` | :wink: | `:wink:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :blush: | `:blush:` | :innocent: | `:innocent:` | [top](#table-of-contents) | + +### Face Affection + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#smileys--emotion) | :smiling_face_with_three_hearts: | `:smiling_face_with_three_hearts:` | :heart_eyes: | `:heart_eyes:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :star_struck: | `:star_struck:` | :kissing_heart: | `:kissing_heart:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :kissing: | `:kissing:` | :relaxed: | `:relaxed:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :kissing_closed_eyes: | `:kissing_closed_eyes:` | :kissing_smiling_eyes: | `:kissing_smiling_eyes:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :smiling_face_with_tear: | `:smiling_face_with_tear:` | | | [top](#table-of-contents) | + +### Face Tongue + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#smileys--emotion) | :yum: | `:yum:` | :stuck_out_tongue: | `:stuck_out_tongue:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :stuck_out_tongue_winking_eye: | `:stuck_out_tongue_winking_eye:` | :zany_face: | `:zany_face:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :stuck_out_tongue_closed_eyes: | `:stuck_out_tongue_closed_eyes:` | :money_mouth_face: | `:money_mouth_face:` | [top](#table-of-contents) | + +### Face Hand + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#smileys--emotion) | :hugs: | `:hugs:` | :hand_over_mouth: | `:hand_over_mouth:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :face_with_open_eyes_and_hand_over_mouth: | `:face_with_open_eyes_and_hand_over_mouth:` | :face_with_peeking_eye: | `:face_with_peeking_eye:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :shushing_face: | `:shushing_face:` | :thinking: | `:thinking:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :saluting_face: | `:saluting_face:` | | | [top](#table-of-contents) | + +### Face Neutral Skeptical + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#smileys--emotion) | :zipper_mouth_face: | `:zipper_mouth_face:` | :raised_eyebrow: | `:raised_eyebrow:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :neutral_face: | `:neutral_face:` | :expressionless: | `:expressionless:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :no_mouth: | `:no_mouth:` | :dotted_line_face: | `:dotted_line_face:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :face_in_clouds: | `:face_in_clouds:` | :smirk: | `:smirk:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :unamused: | `:unamused:` | :roll_eyes: | `:roll_eyes:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :grimacing: | `:grimacing:` | :face_exhaling: | `:face_exhaling:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :lying_face: | `:lying_face:` | :shaking_face: | `:shaking_face:` | [top](#table-of-contents) | + +### Face Sleepy + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#smileys--emotion) | :relieved: | `:relieved:` | :pensive: | `:pensive:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :sleepy: | `:sleepy:` | :drooling_face: | `:drooling_face:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :sleeping: | `:sleeping:` | | | [top](#table-of-contents) | + +### Face Unwell + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#smileys--emotion) | :mask: | `:mask:` | :face_with_thermometer: | `:face_with_thermometer:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :face_with_head_bandage: | `:face_with_head_bandage:` | :nauseated_face: | `:nauseated_face:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :vomiting_face: | `:vomiting_face:` | :sneezing_face: | `:sneezing_face:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :hot_face: | `:hot_face:` | :cold_face: | `:cold_face:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :woozy_face: | `:woozy_face:` | :dizzy_face: | `:dizzy_face:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :face_with_spiral_eyes: | `:face_with_spiral_eyes:` | :exploding_head: | `:exploding_head:` | [top](#table-of-contents) | + +### Face Hat + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#smileys--emotion) | :cowboy_hat_face: | `:cowboy_hat_face:` | :partying_face: | `:partying_face:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :disguised_face: | `:disguised_face:` | | | [top](#table-of-contents) | + +### Face Glasses + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#smileys--emotion) | :sunglasses: | `:sunglasses:` | :nerd_face: | `:nerd_face:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :monocle_face: | `:monocle_face:` | | | [top](#table-of-contents) | + +### Face Concerned + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#smileys--emotion) | :confused: | `:confused:` | :face_with_diagonal_mouth: | `:face_with_diagonal_mouth:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :worried: | `:worried:` | :slightly_frowning_face: | `:slightly_frowning_face:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :frowning_face: | `:frowning_face:` | :open_mouth: | `:open_mouth:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :hushed: | `:hushed:` | :astonished: | `:astonished:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :flushed: | `:flushed:` | :pleading_face: | `:pleading_face:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :face_holding_back_tears: | `:face_holding_back_tears:` | :frowning: | `:frowning:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :anguished: | `:anguished:` | :fearful: | `:fearful:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :cold_sweat: | `:cold_sweat:` | :disappointed_relieved: | `:disappointed_relieved:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :cry: | `:cry:` | :sob: | `:sob:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :scream: | `:scream:` | :confounded: | `:confounded:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :persevere: | `:persevere:` | :disappointed: | `:disappointed:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :sweat: | `:sweat:` | :weary: | `:weary:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :tired_face: | `:tired_face:` | :yawning_face: | `:yawning_face:` | [top](#table-of-contents) | + +### Face Negative + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#smileys--emotion) | :triumph: | `:triumph:` | :pout: | `:pout:` `:rage:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :angry: | `:angry:` | :cursing_face: | `:cursing_face:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :smiling_imp: | `:smiling_imp:` | :imp: | `:imp:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :skull: | `:skull:` | :skull_and_crossbones: | `:skull_and_crossbones:` | [top](#table-of-contents) | + +### Face Costume + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#smileys--emotion) | :hankey: | `:hankey:` `:poop:` `:shit:` | :clown_face: | `:clown_face:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :japanese_ogre: | `:japanese_ogre:` | :japanese_goblin: | `:japanese_goblin:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :ghost: | `:ghost:` | :alien: | `:alien:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :space_invader: | `:space_invader:` | :robot: | `:robot:` | [top](#table-of-contents) | + +### Cat Face + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#smileys--emotion) | :smiley_cat: | `:smiley_cat:` | :smile_cat: | `:smile_cat:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :joy_cat: | `:joy_cat:` | :heart_eyes_cat: | `:heart_eyes_cat:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :smirk_cat: | `:smirk_cat:` | :kissing_cat: | `:kissing_cat:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :scream_cat: | `:scream_cat:` | :crying_cat_face: | `:crying_cat_face:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :pouting_cat: | `:pouting_cat:` | | | [top](#table-of-contents) | + +### Monkey Face + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#smileys--emotion) | :see_no_evil: | `:see_no_evil:` | :hear_no_evil: | `:hear_no_evil:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :speak_no_evil: | `:speak_no_evil:` | | | [top](#table-of-contents) | + +### Heart + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#smileys--emotion) | :love_letter: | `:love_letter:` | :cupid: | `:cupid:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :gift_heart: | `:gift_heart:` | :sparkling_heart: | `:sparkling_heart:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :heartpulse: | `:heartpulse:` | :heartbeat: | `:heartbeat:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :revolving_hearts: | `:revolving_hearts:` | :two_hearts: | `:two_hearts:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :heart_decoration: | `:heart_decoration:` | :heavy_heart_exclamation: | `:heavy_heart_exclamation:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :broken_heart: | `:broken_heart:` | :heart_on_fire: | `:heart_on_fire:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :mending_heart: | `:mending_heart:` | :heart: | `:heart:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :pink_heart: | `:pink_heart:` | :orange_heart: | `:orange_heart:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :yellow_heart: | `:yellow_heart:` | :green_heart: | `:green_heart:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :blue_heart: | `:blue_heart:` | :light_blue_heart: | `:light_blue_heart:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :purple_heart: | `:purple_heart:` | :brown_heart: | `:brown_heart:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :black_heart: | `:black_heart:` | :grey_heart: | `:grey_heart:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :white_heart: | `:white_heart:` | | | [top](#table-of-contents) | + +### Emotion + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#smileys--emotion) | :kiss: | `:kiss:` | :100: | `:100:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :anger: | `:anger:` | :boom: | `:boom:` `:collision:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :dizzy: | `:dizzy:` | :sweat_drops: | `:sweat_drops:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :dash: | `:dash:` | :hole: | `:hole:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :speech_balloon: | `:speech_balloon:` | :eye_speech_bubble: | `:eye_speech_bubble:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :left_speech_bubble: | `:left_speech_bubble:` | :right_anger_bubble: | `:right_anger_bubble:` | [top](#table-of-contents) | +| [top](#smileys--emotion) | :thought_balloon: | `:thought_balloon:` | :zzz: | `:zzz:` | [top](#table-of-contents) | + +## People & Body + +- [Hand Fingers Open](#hand-fingers-open) +- [Hand Fingers Partial](#hand-fingers-partial) +- [Hand Single Finger](#hand-single-finger) +- [Hand Fingers Closed](#hand-fingers-closed) +- [Hands](#hands) +- [Hand Prop](#hand-prop) +- [Body Parts](#body-parts) +- [Person](#person) +- [Person Gesture](#person-gesture) +- [Person Role](#person-role) +- [Person Fantasy](#person-fantasy) +- [Person Activity](#person-activity) +- [Person Sport](#person-sport) +- [Person Resting](#person-resting) +- [Family](#family) +- [Person Symbol](#person-symbol) + +### Hand Fingers Open + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#people--body) | :wave: | `:wave:` | :raised_back_of_hand: | `:raised_back_of_hand:` | [top](#table-of-contents) | +| [top](#people--body) | :raised_hand_with_fingers_splayed: | `:raised_hand_with_fingers_splayed:` | :hand: | `:hand:` `:raised_hand:` | [top](#table-of-contents) | +| [top](#people--body) | :vulcan_salute: | `:vulcan_salute:` | :rightwards_hand: | `:rightwards_hand:` | [top](#table-of-contents) | +| [top](#people--body) | :leftwards_hand: | `:leftwards_hand:` | :palm_down_hand: | `:palm_down_hand:` | [top](#table-of-contents) | +| [top](#people--body) | :palm_up_hand: | `:palm_up_hand:` | :leftwards_pushing_hand: | `:leftwards_pushing_hand:` | [top](#table-of-contents) | +| [top](#people--body) | :rightwards_pushing_hand: | `:rightwards_pushing_hand:` | | | [top](#table-of-contents) | + +### Hand Fingers Partial + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#people--body) | :ok_hand: | `:ok_hand:` | :pinched_fingers: | `:pinched_fingers:` | [top](#table-of-contents) | +| [top](#people--body) | :pinching_hand: | `:pinching_hand:` | :v: | `:v:` | [top](#table-of-contents) | +| [top](#people--body) | :crossed_fingers: | `:crossed_fingers:` | :hand_with_index_finger_and_thumb_crossed: | `:hand_with_index_finger_and_thumb_crossed:` | [top](#table-of-contents) | +| [top](#people--body) | :love_you_gesture: | `:love_you_gesture:` | :metal: | `:metal:` | [top](#table-of-contents) | +| [top](#people--body) | :call_me_hand: | `:call_me_hand:` | | | [top](#table-of-contents) | + +### Hand Single Finger + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#people--body) | :point_left: | `:point_left:` | :point_right: | `:point_right:` | [top](#table-of-contents) | +| [top](#people--body) | :point_up_2: | `:point_up_2:` | :fu: | `:fu:` `:middle_finger:` | [top](#table-of-contents) | +| [top](#people--body) | :point_down: | `:point_down:` | :point_up: | `:point_up:` | [top](#table-of-contents) | +| [top](#people--body) | :index_pointing_at_the_viewer: | `:index_pointing_at_the_viewer:` | | | [top](#table-of-contents) | + +### Hand Fingers Closed + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#people--body) | :+1: | `:+1:` `:thumbsup:` | :-1: | `:-1:` `:thumbsdown:` | [top](#table-of-contents) | +| [top](#people--body) | :fist: | `:fist:` `:fist_raised:` | :facepunch: | `:facepunch:` `:fist_oncoming:` `:punch:` | [top](#table-of-contents) | +| [top](#people--body) | :fist_left: | `:fist_left:` | :fist_right: | `:fist_right:` | [top](#table-of-contents) | + +### Hands + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#people--body) | :clap: | `:clap:` | :raised_hands: | `:raised_hands:` | [top](#table-of-contents) | +| [top](#people--body) | :heart_hands: | `:heart_hands:` | :open_hands: | `:open_hands:` | [top](#table-of-contents) | +| [top](#people--body) | :palms_up_together: | `:palms_up_together:` | :handshake: | `:handshake:` | [top](#table-of-contents) | +| [top](#people--body) | :pray: | `:pray:` | | | [top](#table-of-contents) | + +### Hand Prop + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#people--body) | :writing_hand: | `:writing_hand:` | :nail_care: | `:nail_care:` | [top](#table-of-contents) | +| [top](#people--body) | :selfie: | `:selfie:` | | | [top](#table-of-contents) | + +### Body Parts + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#people--body) | :muscle: | `:muscle:` | :mechanical_arm: | `:mechanical_arm:` | [top](#table-of-contents) | +| [top](#people--body) | :mechanical_leg: | `:mechanical_leg:` | :leg: | `:leg:` | [top](#table-of-contents) | +| [top](#people--body) | :foot: | `:foot:` | :ear: | `:ear:` | [top](#table-of-contents) | +| [top](#people--body) | :ear_with_hearing_aid: | `:ear_with_hearing_aid:` | :nose: | `:nose:` | [top](#table-of-contents) | +| [top](#people--body) | :brain: | `:brain:` | :anatomical_heart: | `:anatomical_heart:` | [top](#table-of-contents) | +| [top](#people--body) | :lungs: | `:lungs:` | :tooth: | `:tooth:` | [top](#table-of-contents) | +| [top](#people--body) | :bone: | `:bone:` | :eyes: | `:eyes:` | [top](#table-of-contents) | +| [top](#people--body) | :eye: | `:eye:` | :tongue: | `:tongue:` | [top](#table-of-contents) | +| [top](#people--body) | :lips: | `:lips:` | :biting_lip: | `:biting_lip:` | [top](#table-of-contents) | + +### Person + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#people--body) | :baby: | `:baby:` | :child: | `:child:` | [top](#table-of-contents) | +| [top](#people--body) | :boy: | `:boy:` | :girl: | `:girl:` | [top](#table-of-contents) | +| [top](#people--body) | :adult: | `:adult:` | :blond_haired_person: | `:blond_haired_person:` | [top](#table-of-contents) | +| [top](#people--body) | :man: | `:man:` | :bearded_person: | `:bearded_person:` | [top](#table-of-contents) | +| [top](#people--body) | :man_beard: | `:man_beard:` | :woman_beard: | `:woman_beard:` | [top](#table-of-contents) | +| [top](#people--body) | :red_haired_man: | `:red_haired_man:` | :curly_haired_man: | `:curly_haired_man:` | [top](#table-of-contents) | +| [top](#people--body) | :white_haired_man: | `:white_haired_man:` | :bald_man: | `:bald_man:` | [top](#table-of-contents) | +| [top](#people--body) | :woman: | `:woman:` | :red_haired_woman: | `:red_haired_woman:` | [top](#table-of-contents) | +| [top](#people--body) | :person_red_hair: | `:person_red_hair:` | :curly_haired_woman: | `:curly_haired_woman:` | [top](#table-of-contents) | +| [top](#people--body) | :person_curly_hair: | `:person_curly_hair:` | :white_haired_woman: | `:white_haired_woman:` | [top](#table-of-contents) | +| [top](#people--body) | :person_white_hair: | `:person_white_hair:` | :bald_woman: | `:bald_woman:` | [top](#table-of-contents) | +| [top](#people--body) | :person_bald: | `:person_bald:` | :blond_haired_woman: | `:blond_haired_woman:` `:blonde_woman:` | [top](#table-of-contents) | +| [top](#people--body) | :blond_haired_man: | `:blond_haired_man:` | :older_adult: | `:older_adult:` | [top](#table-of-contents) | +| [top](#people--body) | :older_man: | `:older_man:` | :older_woman: | `:older_woman:` | [top](#table-of-contents) | + +### Person Gesture + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#people--body) | :frowning_person: | `:frowning_person:` | :frowning_man: | `:frowning_man:` | [top](#table-of-contents) | +| [top](#people--body) | :frowning_woman: | `:frowning_woman:` | :pouting_face: | `:pouting_face:` | [top](#table-of-contents) | +| [top](#people--body) | :pouting_man: | `:pouting_man:` | :pouting_woman: | `:pouting_woman:` | [top](#table-of-contents) | +| [top](#people--body) | :no_good: | `:no_good:` | :ng_man: | `:ng_man:` `:no_good_man:` | [top](#table-of-contents) | +| [top](#people--body) | :ng_woman: | `:ng_woman:` `:no_good_woman:` | :ok_person: | `:ok_person:` | [top](#table-of-contents) | +| [top](#people--body) | :ok_man: | `:ok_man:` | :ok_woman: | `:ok_woman:` | [top](#table-of-contents) | +| [top](#people--body) | :information_desk_person: | `:information_desk_person:` `:tipping_hand_person:` | :sassy_man: | `:sassy_man:` `:tipping_hand_man:` | [top](#table-of-contents) | +| [top](#people--body) | :sassy_woman: | `:sassy_woman:` `:tipping_hand_woman:` | :raising_hand: | `:raising_hand:` | [top](#table-of-contents) | +| [top](#people--body) | :raising_hand_man: | `:raising_hand_man:` | :raising_hand_woman: | `:raising_hand_woman:` | [top](#table-of-contents) | +| [top](#people--body) | :deaf_person: | `:deaf_person:` | :deaf_man: | `:deaf_man:` | [top](#table-of-contents) | +| [top](#people--body) | :deaf_woman: | `:deaf_woman:` | :bow: | `:bow:` | [top](#table-of-contents) | +| [top](#people--body) | :bowing_man: | `:bowing_man:` | :bowing_woman: | `:bowing_woman:` | [top](#table-of-contents) | +| [top](#people--body) | :facepalm: | `:facepalm:` | :man_facepalming: | `:man_facepalming:` | [top](#table-of-contents) | +| [top](#people--body) | :woman_facepalming: | `:woman_facepalming:` | :shrug: | `:shrug:` | [top](#table-of-contents) | +| [top](#people--body) | :man_shrugging: | `:man_shrugging:` | :woman_shrugging: | `:woman_shrugging:` | [top](#table-of-contents) | + +### Person Role + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#people--body) | :health_worker: | `:health_worker:` | :man_health_worker: | `:man_health_worker:` | [top](#table-of-contents) | +| [top](#people--body) | :woman_health_worker: | `:woman_health_worker:` | :student: | `:student:` | [top](#table-of-contents) | +| [top](#people--body) | :man_student: | `:man_student:` | :woman_student: | `:woman_student:` | [top](#table-of-contents) | +| [top](#people--body) | :teacher: | `:teacher:` | :man_teacher: | `:man_teacher:` | [top](#table-of-contents) | +| [top](#people--body) | :woman_teacher: | `:woman_teacher:` | :judge: | `:judge:` | [top](#table-of-contents) | +| [top](#people--body) | :man_judge: | `:man_judge:` | :woman_judge: | `:woman_judge:` | [top](#table-of-contents) | +| [top](#people--body) | :farmer: | `:farmer:` | :man_farmer: | `:man_farmer:` | [top](#table-of-contents) | +| [top](#people--body) | :woman_farmer: | `:woman_farmer:` | :cook: | `:cook:` | [top](#table-of-contents) | +| [top](#people--body) | :man_cook: | `:man_cook:` | :woman_cook: | `:woman_cook:` | [top](#table-of-contents) | +| [top](#people--body) | :mechanic: | `:mechanic:` | :man_mechanic: | `:man_mechanic:` | [top](#table-of-contents) | +| [top](#people--body) | :woman_mechanic: | `:woman_mechanic:` | :factory_worker: | `:factory_worker:` | [top](#table-of-contents) | +| [top](#people--body) | :man_factory_worker: | `:man_factory_worker:` | :woman_factory_worker: | `:woman_factory_worker:` | [top](#table-of-contents) | +| [top](#people--body) | :office_worker: | `:office_worker:` | :man_office_worker: | `:man_office_worker:` | [top](#table-of-contents) | +| [top](#people--body) | :woman_office_worker: | `:woman_office_worker:` | :scientist: | `:scientist:` | [top](#table-of-contents) | +| [top](#people--body) | :man_scientist: | `:man_scientist:` | :woman_scientist: | `:woman_scientist:` | [top](#table-of-contents) | +| [top](#people--body) | :technologist: | `:technologist:` | :man_technologist: | `:man_technologist:` | [top](#table-of-contents) | +| [top](#people--body) | :woman_technologist: | `:woman_technologist:` | :singer: | `:singer:` | [top](#table-of-contents) | +| [top](#people--body) | :man_singer: | `:man_singer:` | :woman_singer: | `:woman_singer:` | [top](#table-of-contents) | +| [top](#people--body) | :artist: | `:artist:` | :man_artist: | `:man_artist:` | [top](#table-of-contents) | +| [top](#people--body) | :woman_artist: | `:woman_artist:` | :pilot: | `:pilot:` | [top](#table-of-contents) | +| [top](#people--body) | :man_pilot: | `:man_pilot:` | :woman_pilot: | `:woman_pilot:` | [top](#table-of-contents) | +| [top](#people--body) | :astronaut: | `:astronaut:` | :man_astronaut: | `:man_astronaut:` | [top](#table-of-contents) | +| [top](#people--body) | :woman_astronaut: | `:woman_astronaut:` | :firefighter: | `:firefighter:` | [top](#table-of-contents) | +| [top](#people--body) | :man_firefighter: | `:man_firefighter:` | :woman_firefighter: | `:woman_firefighter:` | [top](#table-of-contents) | +| [top](#people--body) | :cop: | `:cop:` `:police_officer:` | :policeman: | `:policeman:` | [top](#table-of-contents) | +| [top](#people--body) | :policewoman: | `:policewoman:` | :detective: | `:detective:` | [top](#table-of-contents) | +| [top](#people--body) | :male_detective: | `:male_detective:` | :female_detective: | `:female_detective:` | [top](#table-of-contents) | +| [top](#people--body) | :guard: | `:guard:` | :guardsman: | `:guardsman:` | [top](#table-of-contents) | +| [top](#people--body) | :guardswoman: | `:guardswoman:` | :ninja: | `:ninja:` | [top](#table-of-contents) | +| [top](#people--body) | :construction_worker: | `:construction_worker:` | :construction_worker_man: | `:construction_worker_man:` | [top](#table-of-contents) | +| [top](#people--body) | :construction_worker_woman: | `:construction_worker_woman:` | :person_with_crown: | `:person_with_crown:` | [top](#table-of-contents) | +| [top](#people--body) | :prince: | `:prince:` | :princess: | `:princess:` | [top](#table-of-contents) | +| [top](#people--body) | :person_with_turban: | `:person_with_turban:` | :man_with_turban: | `:man_with_turban:` | [top](#table-of-contents) | +| [top](#people--body) | :woman_with_turban: | `:woman_with_turban:` | :man_with_gua_pi_mao: | `:man_with_gua_pi_mao:` | [top](#table-of-contents) | +| [top](#people--body) | :woman_with_headscarf: | `:woman_with_headscarf:` | :person_in_tuxedo: | `:person_in_tuxedo:` | [top](#table-of-contents) | +| [top](#people--body) | :man_in_tuxedo: | `:man_in_tuxedo:` | :woman_in_tuxedo: | `:woman_in_tuxedo:` | [top](#table-of-contents) | +| [top](#people--body) | :person_with_veil: | `:person_with_veil:` | :man_with_veil: | `:man_with_veil:` | [top](#table-of-contents) | +| [top](#people--body) | :bride_with_veil: | `:bride_with_veil:` `:woman_with_veil:` | :pregnant_woman: | `:pregnant_woman:` | [top](#table-of-contents) | +| [top](#people--body) | :pregnant_man: | `:pregnant_man:` | :pregnant_person: | `:pregnant_person:` | [top](#table-of-contents) | +| [top](#people--body) | :breast_feeding: | `:breast_feeding:` | :woman_feeding_baby: | `:woman_feeding_baby:` | [top](#table-of-contents) | +| [top](#people--body) | :man_feeding_baby: | `:man_feeding_baby:` | :person_feeding_baby: | `:person_feeding_baby:` | [top](#table-of-contents) | + +### Person Fantasy + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#people--body) | :angel: | `:angel:` | :santa: | `:santa:` | [top](#table-of-contents) | +| [top](#people--body) | :mrs_claus: | `:mrs_claus:` | :mx_claus: | `:mx_claus:` | [top](#table-of-contents) | +| [top](#people--body) | :superhero: | `:superhero:` | :superhero_man: | `:superhero_man:` | [top](#table-of-contents) | +| [top](#people--body) | :superhero_woman: | `:superhero_woman:` | :supervillain: | `:supervillain:` | [top](#table-of-contents) | +| [top](#people--body) | :supervillain_man: | `:supervillain_man:` | :supervillain_woman: | `:supervillain_woman:` | [top](#table-of-contents) | +| [top](#people--body) | :mage: | `:mage:` | :mage_man: | `:mage_man:` | [top](#table-of-contents) | +| [top](#people--body) | :mage_woman: | `:mage_woman:` | :fairy: | `:fairy:` | [top](#table-of-contents) | +| [top](#people--body) | :fairy_man: | `:fairy_man:` | :fairy_woman: | `:fairy_woman:` | [top](#table-of-contents) | +| [top](#people--body) | :vampire: | `:vampire:` | :vampire_man: | `:vampire_man:` | [top](#table-of-contents) | +| [top](#people--body) | :vampire_woman: | `:vampire_woman:` | :merperson: | `:merperson:` | [top](#table-of-contents) | +| [top](#people--body) | :merman: | `:merman:` | :mermaid: | `:mermaid:` | [top](#table-of-contents) | +| [top](#people--body) | :elf: | `:elf:` | :elf_man: | `:elf_man:` | [top](#table-of-contents) | +| [top](#people--body) | :elf_woman: | `:elf_woman:` | :genie: | `:genie:` | [top](#table-of-contents) | +| [top](#people--body) | :genie_man: | `:genie_man:` | :genie_woman: | `:genie_woman:` | [top](#table-of-contents) | +| [top](#people--body) | :zombie: | `:zombie:` | :zombie_man: | `:zombie_man:` | [top](#table-of-contents) | +| [top](#people--body) | :zombie_woman: | `:zombie_woman:` | :troll: | `:troll:` | [top](#table-of-contents) | + +### Person Activity + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#people--body) | :massage: | `:massage:` | :massage_man: | `:massage_man:` | [top](#table-of-contents) | +| [top](#people--body) | :massage_woman: | `:massage_woman:` | :haircut: | `:haircut:` | [top](#table-of-contents) | +| [top](#people--body) | :haircut_man: | `:haircut_man:` | :haircut_woman: | `:haircut_woman:` | [top](#table-of-contents) | +| [top](#people--body) | :walking: | `:walking:` | :walking_man: | `:walking_man:` | [top](#table-of-contents) | +| [top](#people--body) | :walking_woman: | `:walking_woman:` | :standing_person: | `:standing_person:` | [top](#table-of-contents) | +| [top](#people--body) | :standing_man: | `:standing_man:` | :standing_woman: | `:standing_woman:` | [top](#table-of-contents) | +| [top](#people--body) | :kneeling_person: | `:kneeling_person:` | :kneeling_man: | `:kneeling_man:` | [top](#table-of-contents) | +| [top](#people--body) | :kneeling_woman: | `:kneeling_woman:` | :person_with_probing_cane: | `:person_with_probing_cane:` | [top](#table-of-contents) | +| [top](#people--body) | :man_with_probing_cane: | `:man_with_probing_cane:` | :woman_with_probing_cane: | `:woman_with_probing_cane:` | [top](#table-of-contents) | +| [top](#people--body) | :person_in_motorized_wheelchair: | `:person_in_motorized_wheelchair:` | :man_in_motorized_wheelchair: | `:man_in_motorized_wheelchair:` | [top](#table-of-contents) | +| [top](#people--body) | :woman_in_motorized_wheelchair: | `:woman_in_motorized_wheelchair:` | :person_in_manual_wheelchair: | `:person_in_manual_wheelchair:` | [top](#table-of-contents) | +| [top](#people--body) | :man_in_manual_wheelchair: | `:man_in_manual_wheelchair:` | :woman_in_manual_wheelchair: | `:woman_in_manual_wheelchair:` | [top](#table-of-contents) | +| [top](#people--body) | :runner: | `:runner:` `:running:` | :running_man: | `:running_man:` | [top](#table-of-contents) | +| [top](#people--body) | :running_woman: | `:running_woman:` | :dancer: | `:dancer:` `:woman_dancing:` | [top](#table-of-contents) | +| [top](#people--body) | :man_dancing: | `:man_dancing:` | :business_suit_levitating: | `:business_suit_levitating:` | [top](#table-of-contents) | +| [top](#people--body) | :dancers: | `:dancers:` | :dancing_men: | `:dancing_men:` | [top](#table-of-contents) | +| [top](#people--body) | :dancing_women: | `:dancing_women:` | :sauna_person: | `:sauna_person:` | [top](#table-of-contents) | +| [top](#people--body) | :sauna_man: | `:sauna_man:` | :sauna_woman: | `:sauna_woman:` | [top](#table-of-contents) | +| [top](#people--body) | :climbing: | `:climbing:` | :climbing_man: | `:climbing_man:` | [top](#table-of-contents) | +| [top](#people--body) | :climbing_woman: | `:climbing_woman:` | | | [top](#table-of-contents) | + +### Person Sport + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#people--body) | :person_fencing: | `:person_fencing:` | :horse_racing: | `:horse_racing:` | [top](#table-of-contents) | +| [top](#people--body) | :skier: | `:skier:` | :snowboarder: | `:snowboarder:` | [top](#table-of-contents) | +| [top](#people--body) | :golfing: | `:golfing:` | :golfing_man: | `:golfing_man:` | [top](#table-of-contents) | +| [top](#people--body) | :golfing_woman: | `:golfing_woman:` | :surfer: | `:surfer:` | [top](#table-of-contents) | +| [top](#people--body) | :surfing_man: | `:surfing_man:` | :surfing_woman: | `:surfing_woman:` | [top](#table-of-contents) | +| [top](#people--body) | :rowboat: | `:rowboat:` | :rowing_man: | `:rowing_man:` | [top](#table-of-contents) | +| [top](#people--body) | :rowing_woman: | `:rowing_woman:` | :swimmer: | `:swimmer:` | [top](#table-of-contents) | +| [top](#people--body) | :swimming_man: | `:swimming_man:` | :swimming_woman: | `:swimming_woman:` | [top](#table-of-contents) | +| [top](#people--body) | :bouncing_ball_person: | `:bouncing_ball_person:` | :basketball_man: | `:basketball_man:` `:bouncing_ball_man:` | [top](#table-of-contents) | +| [top](#people--body) | :basketball_woman: | `:basketball_woman:` `:bouncing_ball_woman:` | :weight_lifting: | `:weight_lifting:` | [top](#table-of-contents) | +| [top](#people--body) | :weight_lifting_man: | `:weight_lifting_man:` | :weight_lifting_woman: | `:weight_lifting_woman:` | [top](#table-of-contents) | +| [top](#people--body) | :bicyclist: | `:bicyclist:` | :biking_man: | `:biking_man:` | [top](#table-of-contents) | +| [top](#people--body) | :biking_woman: | `:biking_woman:` | :mountain_bicyclist: | `:mountain_bicyclist:` | [top](#table-of-contents) | +| [top](#people--body) | :mountain_biking_man: | `:mountain_biking_man:` | :mountain_biking_woman: | `:mountain_biking_woman:` | [top](#table-of-contents) | +| [top](#people--body) | :cartwheeling: | `:cartwheeling:` | :man_cartwheeling: | `:man_cartwheeling:` | [top](#table-of-contents) | +| [top](#people--body) | :woman_cartwheeling: | `:woman_cartwheeling:` | :wrestling: | `:wrestling:` | [top](#table-of-contents) | +| [top](#people--body) | :men_wrestling: | `:men_wrestling:` | :women_wrestling: | `:women_wrestling:` | [top](#table-of-contents) | +| [top](#people--body) | :water_polo: | `:water_polo:` | :man_playing_water_polo: | `:man_playing_water_polo:` | [top](#table-of-contents) | +| [top](#people--body) | :woman_playing_water_polo: | `:woman_playing_water_polo:` | :handball_person: | `:handball_person:` | [top](#table-of-contents) | +| [top](#people--body) | :man_playing_handball: | `:man_playing_handball:` | :woman_playing_handball: | `:woman_playing_handball:` | [top](#table-of-contents) | +| [top](#people--body) | :juggling_person: | `:juggling_person:` | :man_juggling: | `:man_juggling:` | [top](#table-of-contents) | +| [top](#people--body) | :woman_juggling: | `:woman_juggling:` | | | [top](#table-of-contents) | + +### Person Resting + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#people--body) | :lotus_position: | `:lotus_position:` | :lotus_position_man: | `:lotus_position_man:` | [top](#table-of-contents) | +| [top](#people--body) | :lotus_position_woman: | `:lotus_position_woman:` | :bath: | `:bath:` | [top](#table-of-contents) | +| [top](#people--body) | :sleeping_bed: | `:sleeping_bed:` | | | [top](#table-of-contents) | + +### Family + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#people--body) | :people_holding_hands: | `:people_holding_hands:` | :two_women_holding_hands: | `:two_women_holding_hands:` | [top](#table-of-contents) | +| [top](#people--body) | :couple: | `:couple:` | :two_men_holding_hands: | `:two_men_holding_hands:` | [top](#table-of-contents) | +| [top](#people--body) | :couplekiss: | `:couplekiss:` | :couplekiss_man_woman: | `:couplekiss_man_woman:` | [top](#table-of-contents) | +| [top](#people--body) | :couplekiss_man_man: | `:couplekiss_man_man:` | :couplekiss_woman_woman: | `:couplekiss_woman_woman:` | [top](#table-of-contents) | +| [top](#people--body) | :couple_with_heart: | `:couple_with_heart:` | :couple_with_heart_woman_man: | `:couple_with_heart_woman_man:` | [top](#table-of-contents) | +| [top](#people--body) | :couple_with_heart_man_man: | `:couple_with_heart_man_man:` | :couple_with_heart_woman_woman: | `:couple_with_heart_woman_woman:` | [top](#table-of-contents) | +| [top](#people--body) | :family_man_woman_boy: | `:family_man_woman_boy:` | :family_man_woman_girl: | `:family_man_woman_girl:` | [top](#table-of-contents) | +| [top](#people--body) | :family_man_woman_girl_boy: | `:family_man_woman_girl_boy:` | :family_man_woman_boy_boy: | `:family_man_woman_boy_boy:` | [top](#table-of-contents) | +| [top](#people--body) | :family_man_woman_girl_girl: | `:family_man_woman_girl_girl:` | :family_man_man_boy: | `:family_man_man_boy:` | [top](#table-of-contents) | +| [top](#people--body) | :family_man_man_girl: | `:family_man_man_girl:` | :family_man_man_girl_boy: | `:family_man_man_girl_boy:` | [top](#table-of-contents) | +| [top](#people--body) | :family_man_man_boy_boy: | `:family_man_man_boy_boy:` | :family_man_man_girl_girl: | `:family_man_man_girl_girl:` | [top](#table-of-contents) | +| [top](#people--body) | :family_woman_woman_boy: | `:family_woman_woman_boy:` | :family_woman_woman_girl: | `:family_woman_woman_girl:` | [top](#table-of-contents) | +| [top](#people--body) | :family_woman_woman_girl_boy: | `:family_woman_woman_girl_boy:` | :family_woman_woman_boy_boy: | `:family_woman_woman_boy_boy:` | [top](#table-of-contents) | +| [top](#people--body) | :family_woman_woman_girl_girl: | `:family_woman_woman_girl_girl:` | :family_man_boy: | `:family_man_boy:` | [top](#table-of-contents) | +| [top](#people--body) | :family_man_boy_boy: | `:family_man_boy_boy:` | :family_man_girl: | `:family_man_girl:` | [top](#table-of-contents) | +| [top](#people--body) | :family_man_girl_boy: | `:family_man_girl_boy:` | :family_man_girl_girl: | `:family_man_girl_girl:` | [top](#table-of-contents) | +| [top](#people--body) | :family_woman_boy: | `:family_woman_boy:` | :family_woman_boy_boy: | `:family_woman_boy_boy:` | [top](#table-of-contents) | +| [top](#people--body) | :family_woman_girl: | `:family_woman_girl:` | :family_woman_girl_boy: | `:family_woman_girl_boy:` | [top](#table-of-contents) | +| [top](#people--body) | :family_woman_girl_girl: | `:family_woman_girl_girl:` | | | [top](#table-of-contents) | + +### Person Symbol + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#people--body) | :speaking_head: | `:speaking_head:` | :bust_in_silhouette: | `:bust_in_silhouette:` | [top](#table-of-contents) | +| [top](#people--body) | :busts_in_silhouette: | `:busts_in_silhouette:` | :people_hugging: | `:people_hugging:` | [top](#table-of-contents) | +| [top](#people--body) | :family: | `:family:` | :footprints: | `:footprints:` | [top](#table-of-contents) | + +## Animals & Nature + +- [Animal Mammal](#animal-mammal) +- [Animal Bird](#animal-bird) +- [Animal Amphibian](#animal-amphibian) +- [Animal Reptile](#animal-reptile) +- [Animal Marine](#animal-marine) +- [Animal Bug](#animal-bug) +- [Plant Flower](#plant-flower) +- [Plant Other](#plant-other) + +### Animal Mammal + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#animals--nature) | :monkey_face: | `:monkey_face:` | :monkey: | `:monkey:` | [top](#table-of-contents) | +| [top](#animals--nature) | :gorilla: | `:gorilla:` | :orangutan: | `:orangutan:` | [top](#table-of-contents) | +| [top](#animals--nature) | :dog: | `:dog:` | :dog2: | `:dog2:` | [top](#table-of-contents) | +| [top](#animals--nature) | :guide_dog: | `:guide_dog:` | :service_dog: | `:service_dog:` | [top](#table-of-contents) | +| [top](#animals--nature) | :poodle: | `:poodle:` | :wolf: | `:wolf:` | [top](#table-of-contents) | +| [top](#animals--nature) | :fox_face: | `:fox_face:` | :raccoon: | `:raccoon:` | [top](#table-of-contents) | +| [top](#animals--nature) | :cat: | `:cat:` | :cat2: | `:cat2:` | [top](#table-of-contents) | +| [top](#animals--nature) | :black_cat: | `:black_cat:` | :lion: | `:lion:` | [top](#table-of-contents) | +| [top](#animals--nature) | :tiger: | `:tiger:` | :tiger2: | `:tiger2:` | [top](#table-of-contents) | +| [top](#animals--nature) | :leopard: | `:leopard:` | :horse: | `:horse:` | [top](#table-of-contents) | +| [top](#animals--nature) | :moose: | `:moose:` | :donkey: | `:donkey:` | [top](#table-of-contents) | +| [top](#animals--nature) | :racehorse: | `:racehorse:` | :unicorn: | `:unicorn:` | [top](#table-of-contents) | +| [top](#animals--nature) | :zebra: | `:zebra:` | :deer: | `:deer:` | [top](#table-of-contents) | +| [top](#animals--nature) | :bison: | `:bison:` | :cow: | `:cow:` | [top](#table-of-contents) | +| [top](#animals--nature) | :ox: | `:ox:` | :water_buffalo: | `:water_buffalo:` | [top](#table-of-contents) | +| [top](#animals--nature) | :cow2: | `:cow2:` | :pig: | `:pig:` | [top](#table-of-contents) | +| [top](#animals--nature) | :pig2: | `:pig2:` | :boar: | `:boar:` | [top](#table-of-contents) | +| [top](#animals--nature) | :pig_nose: | `:pig_nose:` | :ram: | `:ram:` | [top](#table-of-contents) | +| [top](#animals--nature) | :sheep: | `:sheep:` | :goat: | `:goat:` | [top](#table-of-contents) | +| [top](#animals--nature) | :dromedary_camel: | `:dromedary_camel:` | :camel: | `:camel:` | [top](#table-of-contents) | +| [top](#animals--nature) | :llama: | `:llama:` | :giraffe: | `:giraffe:` | [top](#table-of-contents) | +| [top](#animals--nature) | :elephant: | `:elephant:` | :mammoth: | `:mammoth:` | [top](#table-of-contents) | +| [top](#animals--nature) | :rhinoceros: | `:rhinoceros:` | :hippopotamus: | `:hippopotamus:` | [top](#table-of-contents) | +| [top](#animals--nature) | :mouse: | `:mouse:` | :mouse2: | `:mouse2:` | [top](#table-of-contents) | +| [top](#animals--nature) | :rat: | `:rat:` | :hamster: | `:hamster:` | [top](#table-of-contents) | +| [top](#animals--nature) | :rabbit: | `:rabbit:` | :rabbit2: | `:rabbit2:` | [top](#table-of-contents) | +| [top](#animals--nature) | :chipmunk: | `:chipmunk:` | :beaver: | `:beaver:` | [top](#table-of-contents) | +| [top](#animals--nature) | :hedgehog: | `:hedgehog:` | :bat: | `:bat:` | [top](#table-of-contents) | +| [top](#animals--nature) | :bear: | `:bear:` | :polar_bear: | `:polar_bear:` | [top](#table-of-contents) | +| [top](#animals--nature) | :koala: | `:koala:` | :panda_face: | `:panda_face:` | [top](#table-of-contents) | +| [top](#animals--nature) | :sloth: | `:sloth:` | :otter: | `:otter:` | [top](#table-of-contents) | +| [top](#animals--nature) | :skunk: | `:skunk:` | :kangaroo: | `:kangaroo:` | [top](#table-of-contents) | +| [top](#animals--nature) | :badger: | `:badger:` | :feet: | `:feet:` `:paw_prints:` | [top](#table-of-contents) | + +### Animal Bird + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#animals--nature) | :turkey: | `:turkey:` | :chicken: | `:chicken:` | [top](#table-of-contents) | +| [top](#animals--nature) | :rooster: | `:rooster:` | :hatching_chick: | `:hatching_chick:` | [top](#table-of-contents) | +| [top](#animals--nature) | :baby_chick: | `:baby_chick:` | :hatched_chick: | `:hatched_chick:` | [top](#table-of-contents) | +| [top](#animals--nature) | :bird: | `:bird:` | :penguin: | `:penguin:` | [top](#table-of-contents) | +| [top](#animals--nature) | :dove: | `:dove:` | :eagle: | `:eagle:` | [top](#table-of-contents) | +| [top](#animals--nature) | :duck: | `:duck:` | :swan: | `:swan:` | [top](#table-of-contents) | +| [top](#animals--nature) | :owl: | `:owl:` | :dodo: | `:dodo:` | [top](#table-of-contents) | +| [top](#animals--nature) | :feather: | `:feather:` | :flamingo: | `:flamingo:` | [top](#table-of-contents) | +| [top](#animals--nature) | :peacock: | `:peacock:` | :parrot: | `:parrot:` | [top](#table-of-contents) | +| [top](#animals--nature) | :wing: | `:wing:` | :black_bird: | `:black_bird:` | [top](#table-of-contents) | +| [top](#animals--nature) | :goose: | `:goose:` | | | [top](#table-of-contents) | + +### Animal Amphibian + +| | ico | shortcode | | +| - | :-: | - | - | +| [top](#animals--nature) | :frog: | `:frog:` | [top](#table-of-contents) | + +### Animal Reptile + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#animals--nature) | :crocodile: | `:crocodile:` | :turtle: | `:turtle:` | [top](#table-of-contents) | +| [top](#animals--nature) | :lizard: | `:lizard:` | :snake: | `:snake:` | [top](#table-of-contents) | +| [top](#animals--nature) | :dragon_face: | `:dragon_face:` | :dragon: | `:dragon:` | [top](#table-of-contents) | +| [top](#animals--nature) | :sauropod: | `:sauropod:` | :t-rex: | `:t-rex:` | [top](#table-of-contents) | + +### Animal Marine + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#animals--nature) | :whale: | `:whale:` | :whale2: | `:whale2:` | [top](#table-of-contents) | +| [top](#animals--nature) | :dolphin: | `:dolphin:` `:flipper:` | :seal: | `:seal:` | [top](#table-of-contents) | +| [top](#animals--nature) | :fish: | `:fish:` | :tropical_fish: | `:tropical_fish:` | [top](#table-of-contents) | +| [top](#animals--nature) | :blowfish: | `:blowfish:` | :shark: | `:shark:` | [top](#table-of-contents) | +| [top](#animals--nature) | :octopus: | `:octopus:` | :shell: | `:shell:` | [top](#table-of-contents) | +| [top](#animals--nature) | :coral: | `:coral:` | :jellyfish: | `:jellyfish:` | [top](#table-of-contents) | + +### Animal Bug + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#animals--nature) | :snail: | `:snail:` | :butterfly: | `:butterfly:` | [top](#table-of-contents) | +| [top](#animals--nature) | :bug: | `:bug:` | :ant: | `:ant:` | [top](#table-of-contents) | +| [top](#animals--nature) | :bee: | `:bee:` `:honeybee:` | :beetle: | `:beetle:` | [top](#table-of-contents) | +| [top](#animals--nature) | :lady_beetle: | `:lady_beetle:` | :cricket: | `:cricket:` | [top](#table-of-contents) | +| [top](#animals--nature) | :cockroach: | `:cockroach:` | :spider: | `:spider:` | [top](#table-of-contents) | +| [top](#animals--nature) | :spider_web: | `:spider_web:` | :scorpion: | `:scorpion:` | [top](#table-of-contents) | +| [top](#animals--nature) | :mosquito: | `:mosquito:` | :fly: | `:fly:` | [top](#table-of-contents) | +| [top](#animals--nature) | :worm: | `:worm:` | :microbe: | `:microbe:` | [top](#table-of-contents) | + +### Plant Flower + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#animals--nature) | :bouquet: | `:bouquet:` | :cherry_blossom: | `:cherry_blossom:` | [top](#table-of-contents) | +| [top](#animals--nature) | :white_flower: | `:white_flower:` | :lotus: | `:lotus:` | [top](#table-of-contents) | +| [top](#animals--nature) | :rosette: | `:rosette:` | :rose: | `:rose:` | [top](#table-of-contents) | +| [top](#animals--nature) | :wilted_flower: | `:wilted_flower:` | :hibiscus: | `:hibiscus:` | [top](#table-of-contents) | +| [top](#animals--nature) | :sunflower: | `:sunflower:` | :blossom: | `:blossom:` | [top](#table-of-contents) | +| [top](#animals--nature) | :tulip: | `:tulip:` | :hyacinth: | `:hyacinth:` | [top](#table-of-contents) | + +### Plant Other + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#animals--nature) | :seedling: | `:seedling:` | :potted_plant: | `:potted_plant:` | [top](#table-of-contents) | +| [top](#animals--nature) | :evergreen_tree: | `:evergreen_tree:` | :deciduous_tree: | `:deciduous_tree:` | [top](#table-of-contents) | +| [top](#animals--nature) | :palm_tree: | `:palm_tree:` | :cactus: | `:cactus:` | [top](#table-of-contents) | +| [top](#animals--nature) | :ear_of_rice: | `:ear_of_rice:` | :herb: | `:herb:` | [top](#table-of-contents) | +| [top](#animals--nature) | :shamrock: | `:shamrock:` | :four_leaf_clover: | `:four_leaf_clover:` | [top](#table-of-contents) | +| [top](#animals--nature) | :maple_leaf: | `:maple_leaf:` | :fallen_leaf: | `:fallen_leaf:` | [top](#table-of-contents) | +| [top](#animals--nature) | :leaves: | `:leaves:` | :empty_nest: | `:empty_nest:` | [top](#table-of-contents) | +| [top](#animals--nature) | :nest_with_eggs: | `:nest_with_eggs:` | :mushroom: | `:mushroom:` | [top](#table-of-contents) | + +## Food & Drink + +- [Food Fruit](#food-fruit) +- [Food Vegetable](#food-vegetable) +- [Food Prepared](#food-prepared) +- [Food Asian](#food-asian) +- [Food Marine](#food-marine) +- [Food Sweet](#food-sweet) +- [Drink](#drink) +- [Dishware](#dishware) + +### Food Fruit + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#food--drink) | :grapes: | `:grapes:` | :melon: | `:melon:` | [top](#table-of-contents) | +| [top](#food--drink) | :watermelon: | `:watermelon:` | :mandarin: | `:mandarin:` `:orange:` `:tangerine:` | [top](#table-of-contents) | +| [top](#food--drink) | :lemon: | `:lemon:` | :banana: | `:banana:` | [top](#table-of-contents) | +| [top](#food--drink) | :pineapple: | `:pineapple:` | :mango: | `:mango:` | [top](#table-of-contents) | +| [top](#food--drink) | :apple: | `:apple:` | :green_apple: | `:green_apple:` | [top](#table-of-contents) | +| [top](#food--drink) | :pear: | `:pear:` | :peach: | `:peach:` | [top](#table-of-contents) | +| [top](#food--drink) | :cherries: | `:cherries:` | :strawberry: | `:strawberry:` | [top](#table-of-contents) | +| [top](#food--drink) | :blueberries: | `:blueberries:` | :kiwi_fruit: | `:kiwi_fruit:` | [top](#table-of-contents) | +| [top](#food--drink) | :tomato: | `:tomato:` | :olive: | `:olive:` | [top](#table-of-contents) | +| [top](#food--drink) | :coconut: | `:coconut:` | | | [top](#table-of-contents) | + +### Food Vegetable + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#food--drink) | :avocado: | `:avocado:` | :eggplant: | `:eggplant:` | [top](#table-of-contents) | +| [top](#food--drink) | :potato: | `:potato:` | :carrot: | `:carrot:` | [top](#table-of-contents) | +| [top](#food--drink) | :corn: | `:corn:` | :hot_pepper: | `:hot_pepper:` | [top](#table-of-contents) | +| [top](#food--drink) | :bell_pepper: | `:bell_pepper:` | :cucumber: | `:cucumber:` | [top](#table-of-contents) | +| [top](#food--drink) | :leafy_green: | `:leafy_green:` | :broccoli: | `:broccoli:` | [top](#table-of-contents) | +| [top](#food--drink) | :garlic: | `:garlic:` | :onion: | `:onion:` | [top](#table-of-contents) | +| [top](#food--drink) | :peanuts: | `:peanuts:` | :beans: | `:beans:` | [top](#table-of-contents) | +| [top](#food--drink) | :chestnut: | `:chestnut:` | :ginger_root: | `:ginger_root:` | [top](#table-of-contents) | +| [top](#food--drink) | :pea_pod: | `:pea_pod:` | | | [top](#table-of-contents) | + +### Food Prepared + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#food--drink) | :bread: | `:bread:` | :croissant: | `:croissant:` | [top](#table-of-contents) | +| [top](#food--drink) | :baguette_bread: | `:baguette_bread:` | :flatbread: | `:flatbread:` | [top](#table-of-contents) | +| [top](#food--drink) | :pretzel: | `:pretzel:` | :bagel: | `:bagel:` | [top](#table-of-contents) | +| [top](#food--drink) | :pancakes: | `:pancakes:` | :waffle: | `:waffle:` | [top](#table-of-contents) | +| [top](#food--drink) | :cheese: | `:cheese:` | :meat_on_bone: | `:meat_on_bone:` | [top](#table-of-contents) | +| [top](#food--drink) | :poultry_leg: | `:poultry_leg:` | :cut_of_meat: | `:cut_of_meat:` | [top](#table-of-contents) | +| [top](#food--drink) | :bacon: | `:bacon:` | :hamburger: | `:hamburger:` | [top](#table-of-contents) | +| [top](#food--drink) | :fries: | `:fries:` | :pizza: | `:pizza:` | [top](#table-of-contents) | +| [top](#food--drink) | :hotdog: | `:hotdog:` | :sandwich: | `:sandwich:` | [top](#table-of-contents) | +| [top](#food--drink) | :taco: | `:taco:` | :burrito: | `:burrito:` | [top](#table-of-contents) | +| [top](#food--drink) | :tamale: | `:tamale:` | :stuffed_flatbread: | `:stuffed_flatbread:` | [top](#table-of-contents) | +| [top](#food--drink) | :falafel: | `:falafel:` | :egg: | `:egg:` | [top](#table-of-contents) | +| [top](#food--drink) | :fried_egg: | `:fried_egg:` | :shallow_pan_of_food: | `:shallow_pan_of_food:` | [top](#table-of-contents) | +| [top](#food--drink) | :stew: | `:stew:` | :fondue: | `:fondue:` | [top](#table-of-contents) | +| [top](#food--drink) | :bowl_with_spoon: | `:bowl_with_spoon:` | :green_salad: | `:green_salad:` | [top](#table-of-contents) | +| [top](#food--drink) | :popcorn: | `:popcorn:` | :butter: | `:butter:` | [top](#table-of-contents) | +| [top](#food--drink) | :salt: | `:salt:` | :canned_food: | `:canned_food:` | [top](#table-of-contents) | + +### Food Asian + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#food--drink) | :bento: | `:bento:` | :rice_cracker: | `:rice_cracker:` | [top](#table-of-contents) | +| [top](#food--drink) | :rice_ball: | `:rice_ball:` | :rice: | `:rice:` | [top](#table-of-contents) | +| [top](#food--drink) | :curry: | `:curry:` | :ramen: | `:ramen:` | [top](#table-of-contents) | +| [top](#food--drink) | :spaghetti: | `:spaghetti:` | :sweet_potato: | `:sweet_potato:` | [top](#table-of-contents) | +| [top](#food--drink) | :oden: | `:oden:` | :sushi: | `:sushi:` | [top](#table-of-contents) | +| [top](#food--drink) | :fried_shrimp: | `:fried_shrimp:` | :fish_cake: | `:fish_cake:` | [top](#table-of-contents) | +| [top](#food--drink) | :moon_cake: | `:moon_cake:` | :dango: | `:dango:` | [top](#table-of-contents) | +| [top](#food--drink) | :dumpling: | `:dumpling:` | :fortune_cookie: | `:fortune_cookie:` | [top](#table-of-contents) | +| [top](#food--drink) | :takeout_box: | `:takeout_box:` | | | [top](#table-of-contents) | + +### Food Marine + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#food--drink) | :crab: | `:crab:` | :lobster: | `:lobster:` | [top](#table-of-contents) | +| [top](#food--drink) | :shrimp: | `:shrimp:` | :squid: | `:squid:` | [top](#table-of-contents) | +| [top](#food--drink) | :oyster: | `:oyster:` | | | [top](#table-of-contents) | + +### Food Sweet + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#food--drink) | :icecream: | `:icecream:` | :shaved_ice: | `:shaved_ice:` | [top](#table-of-contents) | +| [top](#food--drink) | :ice_cream: | `:ice_cream:` | :doughnut: | `:doughnut:` | [top](#table-of-contents) | +| [top](#food--drink) | :cookie: | `:cookie:` | :birthday: | `:birthday:` | [top](#table-of-contents) | +| [top](#food--drink) | :cake: | `:cake:` | :cupcake: | `:cupcake:` | [top](#table-of-contents) | +| [top](#food--drink) | :pie: | `:pie:` | :chocolate_bar: | `:chocolate_bar:` | [top](#table-of-contents) | +| [top](#food--drink) | :candy: | `:candy:` | :lollipop: | `:lollipop:` | [top](#table-of-contents) | +| [top](#food--drink) | :custard: | `:custard:` | :honey_pot: | `:honey_pot:` | [top](#table-of-contents) | + +### Drink + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#food--drink) | :baby_bottle: | `:baby_bottle:` | :milk_glass: | `:milk_glass:` | [top](#table-of-contents) | +| [top](#food--drink) | :coffee: | `:coffee:` | :teapot: | `:teapot:` | [top](#table-of-contents) | +| [top](#food--drink) | :tea: | `:tea:` | :sake: | `:sake:` | [top](#table-of-contents) | +| [top](#food--drink) | :champagne: | `:champagne:` | :wine_glass: | `:wine_glass:` | [top](#table-of-contents) | +| [top](#food--drink) | :cocktail: | `:cocktail:` | :tropical_drink: | `:tropical_drink:` | [top](#table-of-contents) | +| [top](#food--drink) | :beer: | `:beer:` | :beers: | `:beers:` | [top](#table-of-contents) | +| [top](#food--drink) | :clinking_glasses: | `:clinking_glasses:` | :tumbler_glass: | `:tumbler_glass:` | [top](#table-of-contents) | +| [top](#food--drink) | :pouring_liquid: | `:pouring_liquid:` | :cup_with_straw: | `:cup_with_straw:` | [top](#table-of-contents) | +| [top](#food--drink) | :bubble_tea: | `:bubble_tea:` | :beverage_box: | `:beverage_box:` | [top](#table-of-contents) | +| [top](#food--drink) | :mate: | `:mate:` | :ice_cube: | `:ice_cube:` | [top](#table-of-contents) | + +### Dishware + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#food--drink) | :chopsticks: | `:chopsticks:` | :plate_with_cutlery: | `:plate_with_cutlery:` | [top](#table-of-contents) | +| [top](#food--drink) | :fork_and_knife: | `:fork_and_knife:` | :spoon: | `:spoon:` | [top](#table-of-contents) | +| [top](#food--drink) | :hocho: | `:hocho:` `:knife:` | :jar: | `:jar:` | [top](#table-of-contents) | +| [top](#food--drink) | :amphora: | `:amphora:` | | | [top](#table-of-contents) | + +## Travel & Places + +- [Place Map](#place-map) +- [Place Geographic](#place-geographic) +- [Place Building](#place-building) +- [Place Religious](#place-religious) +- [Place Other](#place-other) +- [Transport Ground](#transport-ground) +- [Transport Water](#transport-water) +- [Transport Air](#transport-air) +- [Hotel](#hotel) +- [Time](#time) +- [Sky & Weather](#sky--weather) + +### Place Map + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#travel--places) | :earth_africa: | `:earth_africa:` | :earth_americas: | `:earth_americas:` | [top](#table-of-contents) | +| [top](#travel--places) | :earth_asia: | `:earth_asia:` | :globe_with_meridians: | `:globe_with_meridians:` | [top](#table-of-contents) | +| [top](#travel--places) | :world_map: | `:world_map:` | :japan: | `:japan:` | [top](#table-of-contents) | +| [top](#travel--places) | :compass: | `:compass:` | | | [top](#table-of-contents) | + +### Place Geographic + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#travel--places) | :mountain_snow: | `:mountain_snow:` | :mountain: | `:mountain:` | [top](#table-of-contents) | +| [top](#travel--places) | :volcano: | `:volcano:` | :mount_fuji: | `:mount_fuji:` | [top](#table-of-contents) | +| [top](#travel--places) | :camping: | `:camping:` | :beach_umbrella: | `:beach_umbrella:` | [top](#table-of-contents) | +| [top](#travel--places) | :desert: | `:desert:` | :desert_island: | `:desert_island:` | [top](#table-of-contents) | +| [top](#travel--places) | :national_park: | `:national_park:` | | | [top](#table-of-contents) | + +### Place Building + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#travel--places) | :stadium: | `:stadium:` | :classical_building: | `:classical_building:` | [top](#table-of-contents) | +| [top](#travel--places) | :building_construction: | `:building_construction:` | :bricks: | `:bricks:` | [top](#table-of-contents) | +| [top](#travel--places) | :rock: | `:rock:` | :wood: | `:wood:` | [top](#table-of-contents) | +| [top](#travel--places) | :hut: | `:hut:` | :houses: | `:houses:` | [top](#table-of-contents) | +| [top](#travel--places) | :derelict_house: | `:derelict_house:` | :house: | `:house:` | [top](#table-of-contents) | +| [top](#travel--places) | :house_with_garden: | `:house_with_garden:` | :office: | `:office:` | [top](#table-of-contents) | +| [top](#travel--places) | :post_office: | `:post_office:` | :european_post_office: | `:european_post_office:` | [top](#table-of-contents) | +| [top](#travel--places) | :hospital: | `:hospital:` | :bank: | `:bank:` | [top](#table-of-contents) | +| [top](#travel--places) | :hotel: | `:hotel:` | :love_hotel: | `:love_hotel:` | [top](#table-of-contents) | +| [top](#travel--places) | :convenience_store: | `:convenience_store:` | :school: | `:school:` | [top](#table-of-contents) | +| [top](#travel--places) | :department_store: | `:department_store:` | :factory: | `:factory:` | [top](#table-of-contents) | +| [top](#travel--places) | :japanese_castle: | `:japanese_castle:` | :european_castle: | `:european_castle:` | [top](#table-of-contents) | +| [top](#travel--places) | :wedding: | `:wedding:` | :tokyo_tower: | `:tokyo_tower:` | [top](#table-of-contents) | +| [top](#travel--places) | :statue_of_liberty: | `:statue_of_liberty:` | | | [top](#table-of-contents) | + +### Place Religious + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#travel--places) | :church: | `:church:` | :mosque: | `:mosque:` | [top](#table-of-contents) | +| [top](#travel--places) | :hindu_temple: | `:hindu_temple:` | :synagogue: | `:synagogue:` | [top](#table-of-contents) | +| [top](#travel--places) | :shinto_shrine: | `:shinto_shrine:` | :kaaba: | `:kaaba:` | [top](#table-of-contents) | + +### Place Other + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#travel--places) | :fountain: | `:fountain:` | :tent: | `:tent:` | [top](#table-of-contents) | +| [top](#travel--places) | :foggy: | `:foggy:` | :night_with_stars: | `:night_with_stars:` | [top](#table-of-contents) | +| [top](#travel--places) | :cityscape: | `:cityscape:` | :sunrise_over_mountains: | `:sunrise_over_mountains:` | [top](#table-of-contents) | +| [top](#travel--places) | :sunrise: | `:sunrise:` | :city_sunset: | `:city_sunset:` | [top](#table-of-contents) | +| [top](#travel--places) | :city_sunrise: | `:city_sunrise:` | :bridge_at_night: | `:bridge_at_night:` | [top](#table-of-contents) | +| [top](#travel--places) | :hotsprings: | `:hotsprings:` | :carousel_horse: | `:carousel_horse:` | [top](#table-of-contents) | +| [top](#travel--places) | :playground_slide: | `:playground_slide:` | :ferris_wheel: | `:ferris_wheel:` | [top](#table-of-contents) | +| [top](#travel--places) | :roller_coaster: | `:roller_coaster:` | :barber: | `:barber:` | [top](#table-of-contents) | +| [top](#travel--places) | :circus_tent: | `:circus_tent:` | | | [top](#table-of-contents) | + +### Transport Ground + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#travel--places) | :steam_locomotive: | `:steam_locomotive:` | :railway_car: | `:railway_car:` | [top](#table-of-contents) | +| [top](#travel--places) | :bullettrain_side: | `:bullettrain_side:` | :bullettrain_front: | `:bullettrain_front:` | [top](#table-of-contents) | +| [top](#travel--places) | :train2: | `:train2:` | :metro: | `:metro:` | [top](#table-of-contents) | +| [top](#travel--places) | :light_rail: | `:light_rail:` | :station: | `:station:` | [top](#table-of-contents) | +| [top](#travel--places) | :tram: | `:tram:` | :monorail: | `:monorail:` | [top](#table-of-contents) | +| [top](#travel--places) | :mountain_railway: | `:mountain_railway:` | :train: | `:train:` | [top](#table-of-contents) | +| [top](#travel--places) | :bus: | `:bus:` | :oncoming_bus: | `:oncoming_bus:` | [top](#table-of-contents) | +| [top](#travel--places) | :trolleybus: | `:trolleybus:` | :minibus: | `:minibus:` | [top](#table-of-contents) | +| [top](#travel--places) | :ambulance: | `:ambulance:` | :fire_engine: | `:fire_engine:` | [top](#table-of-contents) | +| [top](#travel--places) | :police_car: | `:police_car:` | :oncoming_police_car: | `:oncoming_police_car:` | [top](#table-of-contents) | +| [top](#travel--places) | :taxi: | `:taxi:` | :oncoming_taxi: | `:oncoming_taxi:` | [top](#table-of-contents) | +| [top](#travel--places) | :car: | `:car:` `:red_car:` | :oncoming_automobile: | `:oncoming_automobile:` | [top](#table-of-contents) | +| [top](#travel--places) | :blue_car: | `:blue_car:` | :pickup_truck: | `:pickup_truck:` | [top](#table-of-contents) | +| [top](#travel--places) | :truck: | `:truck:` | :articulated_lorry: | `:articulated_lorry:` | [top](#table-of-contents) | +| [top](#travel--places) | :tractor: | `:tractor:` | :racing_car: | `:racing_car:` | [top](#table-of-contents) | +| [top](#travel--places) | :motorcycle: | `:motorcycle:` | :motor_scooter: | `:motor_scooter:` | [top](#table-of-contents) | +| [top](#travel--places) | :manual_wheelchair: | `:manual_wheelchair:` | :motorized_wheelchair: | `:motorized_wheelchair:` | [top](#table-of-contents) | +| [top](#travel--places) | :auto_rickshaw: | `:auto_rickshaw:` | :bike: | `:bike:` | [top](#table-of-contents) | +| [top](#travel--places) | :kick_scooter: | `:kick_scooter:` | :skateboard: | `:skateboard:` | [top](#table-of-contents) | +| [top](#travel--places) | :roller_skate: | `:roller_skate:` | :busstop: | `:busstop:` | [top](#table-of-contents) | +| [top](#travel--places) | :motorway: | `:motorway:` | :railway_track: | `:railway_track:` | [top](#table-of-contents) | +| [top](#travel--places) | :oil_drum: | `:oil_drum:` | :fuelpump: | `:fuelpump:` | [top](#table-of-contents) | +| [top](#travel--places) | :wheel: | `:wheel:` | :rotating_light: | `:rotating_light:` | [top](#table-of-contents) | +| [top](#travel--places) | :traffic_light: | `:traffic_light:` | :vertical_traffic_light: | `:vertical_traffic_light:` | [top](#table-of-contents) | +| [top](#travel--places) | :stop_sign: | `:stop_sign:` | :construction: | `:construction:` | [top](#table-of-contents) | + +### Transport Water + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#travel--places) | :anchor: | `:anchor:` | :ring_buoy: | `:ring_buoy:` | [top](#table-of-contents) | +| [top](#travel--places) | :boat: | `:boat:` `:sailboat:` | :canoe: | `:canoe:` | [top](#table-of-contents) | +| [top](#travel--places) | :speedboat: | `:speedboat:` | :passenger_ship: | `:passenger_ship:` | [top](#table-of-contents) | +| [top](#travel--places) | :ferry: | `:ferry:` | :motor_boat: | `:motor_boat:` | [top](#table-of-contents) | +| [top](#travel--places) | :ship: | `:ship:` | | | [top](#table-of-contents) | + +### Transport Air + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#travel--places) | :airplane: | `:airplane:` | :small_airplane: | `:small_airplane:` | [top](#table-of-contents) | +| [top](#travel--places) | :flight_departure: | `:flight_departure:` | :flight_arrival: | `:flight_arrival:` | [top](#table-of-contents) | +| [top](#travel--places) | :parachute: | `:parachute:` | :seat: | `:seat:` | [top](#table-of-contents) | +| [top](#travel--places) | :helicopter: | `:helicopter:` | :suspension_railway: | `:suspension_railway:` | [top](#table-of-contents) | +| [top](#travel--places) | :mountain_cableway: | `:mountain_cableway:` | :aerial_tramway: | `:aerial_tramway:` | [top](#table-of-contents) | +| [top](#travel--places) | :artificial_satellite: | `:artificial_satellite:` | :rocket: | `:rocket:` | [top](#table-of-contents) | +| [top](#travel--places) | :flying_saucer: | `:flying_saucer:` | | | [top](#table-of-contents) | + +### Hotel + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#travel--places) | :bellhop_bell: | `:bellhop_bell:` | :luggage: | `:luggage:` | [top](#table-of-contents) | + +### Time + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#travel--places) | :hourglass: | `:hourglass:` | :hourglass_flowing_sand: | `:hourglass_flowing_sand:` | [top](#table-of-contents) | +| [top](#travel--places) | :watch: | `:watch:` | :alarm_clock: | `:alarm_clock:` | [top](#table-of-contents) | +| [top](#travel--places) | :stopwatch: | `:stopwatch:` | :timer_clock: | `:timer_clock:` | [top](#table-of-contents) | +| [top](#travel--places) | :mantelpiece_clock: | `:mantelpiece_clock:` | :clock12: | `:clock12:` | [top](#table-of-contents) | +| [top](#travel--places) | :clock1230: | `:clock1230:` | :clock1: | `:clock1:` | [top](#table-of-contents) | +| [top](#travel--places) | :clock130: | `:clock130:` | :clock2: | `:clock2:` | [top](#table-of-contents) | +| [top](#travel--places) | :clock230: | `:clock230:` | :clock3: | `:clock3:` | [top](#table-of-contents) | +| [top](#travel--places) | :clock330: | `:clock330:` | :clock4: | `:clock4:` | [top](#table-of-contents) | +| [top](#travel--places) | :clock430: | `:clock430:` | :clock5: | `:clock5:` | [top](#table-of-contents) | +| [top](#travel--places) | :clock530: | `:clock530:` | :clock6: | `:clock6:` | [top](#table-of-contents) | +| [top](#travel--places) | :clock630: | `:clock630:` | :clock7: | `:clock7:` | [top](#table-of-contents) | +| [top](#travel--places) | :clock730: | `:clock730:` | :clock8: | `:clock8:` | [top](#table-of-contents) | +| [top](#travel--places) | :clock830: | `:clock830:` | :clock9: | `:clock9:` | [top](#table-of-contents) | +| [top](#travel--places) | :clock930: | `:clock930:` | :clock10: | `:clock10:` | [top](#table-of-contents) | +| [top](#travel--places) | :clock1030: | `:clock1030:` | :clock11: | `:clock11:` | [top](#table-of-contents) | +| [top](#travel--places) | :clock1130: | `:clock1130:` | | | [top](#table-of-contents) | + +### Sky & Weather + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#travel--places) | :new_moon: | `:new_moon:` | :waxing_crescent_moon: | `:waxing_crescent_moon:` | [top](#table-of-contents) | +| [top](#travel--places) | :first_quarter_moon: | `:first_quarter_moon:` | :moon: | `:moon:` `:waxing_gibbous_moon:` | [top](#table-of-contents) | +| [top](#travel--places) | :full_moon: | `:full_moon:` | :waning_gibbous_moon: | `:waning_gibbous_moon:` | [top](#table-of-contents) | +| [top](#travel--places) | :last_quarter_moon: | `:last_quarter_moon:` | :waning_crescent_moon: | `:waning_crescent_moon:` | [top](#table-of-contents) | +| [top](#travel--places) | :crescent_moon: | `:crescent_moon:` | :new_moon_with_face: | `:new_moon_with_face:` | [top](#table-of-contents) | +| [top](#travel--places) | :first_quarter_moon_with_face: | `:first_quarter_moon_with_face:` | :last_quarter_moon_with_face: | `:last_quarter_moon_with_face:` | [top](#table-of-contents) | +| [top](#travel--places) | :thermometer: | `:thermometer:` | :sunny: | `:sunny:` | [top](#table-of-contents) | +| [top](#travel--places) | :full_moon_with_face: | `:full_moon_with_face:` | :sun_with_face: | `:sun_with_face:` | [top](#table-of-contents) | +| [top](#travel--places) | :ringed_planet: | `:ringed_planet:` | :star: | `:star:` | [top](#table-of-contents) | +| [top](#travel--places) | :star2: | `:star2:` | :stars: | `:stars:` | [top](#table-of-contents) | +| [top](#travel--places) | :milky_way: | `:milky_way:` | :cloud: | `:cloud:` | [top](#table-of-contents) | +| [top](#travel--places) | :partly_sunny: | `:partly_sunny:` | :cloud_with_lightning_and_rain: | `:cloud_with_lightning_and_rain:` | [top](#table-of-contents) | +| [top](#travel--places) | :sun_behind_small_cloud: | `:sun_behind_small_cloud:` | :sun_behind_large_cloud: | `:sun_behind_large_cloud:` | [top](#table-of-contents) | +| [top](#travel--places) | :sun_behind_rain_cloud: | `:sun_behind_rain_cloud:` | :cloud_with_rain: | `:cloud_with_rain:` | [top](#table-of-contents) | +| [top](#travel--places) | :cloud_with_snow: | `:cloud_with_snow:` | :cloud_with_lightning: | `:cloud_with_lightning:` | [top](#table-of-contents) | +| [top](#travel--places) | :tornado: | `:tornado:` | :fog: | `:fog:` | [top](#table-of-contents) | +| [top](#travel--places) | :wind_face: | `:wind_face:` | :cyclone: | `:cyclone:` | [top](#table-of-contents) | +| [top](#travel--places) | :rainbow: | `:rainbow:` | :closed_umbrella: | `:closed_umbrella:` | [top](#table-of-contents) | +| [top](#travel--places) | :open_umbrella: | `:open_umbrella:` | :umbrella: | `:umbrella:` | [top](#table-of-contents) | +| [top](#travel--places) | :parasol_on_ground: | `:parasol_on_ground:` | :zap: | `:zap:` | [top](#table-of-contents) | +| [top](#travel--places) | :snowflake: | `:snowflake:` | :snowman_with_snow: | `:snowman_with_snow:` | [top](#table-of-contents) | +| [top](#travel--places) | :snowman: | `:snowman:` | :comet: | `:comet:` | [top](#table-of-contents) | +| [top](#travel--places) | :fire: | `:fire:` | :droplet: | `:droplet:` | [top](#table-of-contents) | +| [top](#travel--places) | :ocean: | `:ocean:` | | | [top](#table-of-contents) | + +## Activities + +- [Event](#event) +- [Award Medal](#award-medal) +- [Sport](#sport) +- [Game](#game) +- [Arts & Crafts](#arts--crafts) + +### Event + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#activities) | :jack_o_lantern: | `:jack_o_lantern:` | :christmas_tree: | `:christmas_tree:` | [top](#table-of-contents) | +| [top](#activities) | :fireworks: | `:fireworks:` | :sparkler: | `:sparkler:` | [top](#table-of-contents) | +| [top](#activities) | :firecracker: | `:firecracker:` | :sparkles: | `:sparkles:` | [top](#table-of-contents) | +| [top](#activities) | :balloon: | `:balloon:` | :tada: | `:tada:` | [top](#table-of-contents) | +| [top](#activities) | :confetti_ball: | `:confetti_ball:` | :tanabata_tree: | `:tanabata_tree:` | [top](#table-of-contents) | +| [top](#activities) | :bamboo: | `:bamboo:` | :dolls: | `:dolls:` | [top](#table-of-contents) | +| [top](#activities) | :flags: | `:flags:` | :wind_chime: | `:wind_chime:` | [top](#table-of-contents) | +| [top](#activities) | :rice_scene: | `:rice_scene:` | :red_envelope: | `:red_envelope:` | [top](#table-of-contents) | +| [top](#activities) | :ribbon: | `:ribbon:` | :gift: | `:gift:` | [top](#table-of-contents) | +| [top](#activities) | :reminder_ribbon: | `:reminder_ribbon:` | :tickets: | `:tickets:` | [top](#table-of-contents) | +| [top](#activities) | :ticket: | `:ticket:` | | | [top](#table-of-contents) | + +### Award Medal + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#activities) | :medal_military: | `:medal_military:` | :trophy: | `:trophy:` | [top](#table-of-contents) | +| [top](#activities) | :medal_sports: | `:medal_sports:` | :1st_place_medal: | `:1st_place_medal:` | [top](#table-of-contents) | +| [top](#activities) | :2nd_place_medal: | `:2nd_place_medal:` | :3rd_place_medal: | `:3rd_place_medal:` | [top](#table-of-contents) | + +### Sport + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#activities) | :soccer: | `:soccer:` | :baseball: | `:baseball:` | [top](#table-of-contents) | +| [top](#activities) | :softball: | `:softball:` | :basketball: | `:basketball:` | [top](#table-of-contents) | +| [top](#activities) | :volleyball: | `:volleyball:` | :football: | `:football:` | [top](#table-of-contents) | +| [top](#activities) | :rugby_football: | `:rugby_football:` | :tennis: | `:tennis:` | [top](#table-of-contents) | +| [top](#activities) | :flying_disc: | `:flying_disc:` | :bowling: | `:bowling:` | [top](#table-of-contents) | +| [top](#activities) | :cricket_game: | `:cricket_game:` | :field_hockey: | `:field_hockey:` | [top](#table-of-contents) | +| [top](#activities) | :ice_hockey: | `:ice_hockey:` | :lacrosse: | `:lacrosse:` | [top](#table-of-contents) | +| [top](#activities) | :ping_pong: | `:ping_pong:` | :badminton: | `:badminton:` | [top](#table-of-contents) | +| [top](#activities) | :boxing_glove: | `:boxing_glove:` | :martial_arts_uniform: | `:martial_arts_uniform:` | [top](#table-of-contents) | +| [top](#activities) | :goal_net: | `:goal_net:` | :golf: | `:golf:` | [top](#table-of-contents) | +| [top](#activities) | :ice_skate: | `:ice_skate:` | :fishing_pole_and_fish: | `:fishing_pole_and_fish:` | [top](#table-of-contents) | +| [top](#activities) | :diving_mask: | `:diving_mask:` | :running_shirt_with_sash: | `:running_shirt_with_sash:` | [top](#table-of-contents) | +| [top](#activities) | :ski: | `:ski:` | :sled: | `:sled:` | [top](#table-of-contents) | +| [top](#activities) | :curling_stone: | `:curling_stone:` | | | [top](#table-of-contents) | + +### Game + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#activities) | :dart: | `:dart:` | :yo_yo: | `:yo_yo:` | [top](#table-of-contents) | +| [top](#activities) | :kite: | `:kite:` | :gun: | `:gun:` | [top](#table-of-contents) | +| [top](#activities) | :8ball: | `:8ball:` | :crystal_ball: | `:crystal_ball:` | [top](#table-of-contents) | +| [top](#activities) | :magic_wand: | `:magic_wand:` | :video_game: | `:video_game:` | [top](#table-of-contents) | +| [top](#activities) | :joystick: | `:joystick:` | :slot_machine: | `:slot_machine:` | [top](#table-of-contents) | +| [top](#activities) | :game_die: | `:game_die:` | :jigsaw: | `:jigsaw:` | [top](#table-of-contents) | +| [top](#activities) | :teddy_bear: | `:teddy_bear:` | :pinata: | `:pinata:` | [top](#table-of-contents) | +| [top](#activities) | :mirror_ball: | `:mirror_ball:` | :nesting_dolls: | `:nesting_dolls:` | [top](#table-of-contents) | +| [top](#activities) | :spades: | `:spades:` | :hearts: | `:hearts:` | [top](#table-of-contents) | +| [top](#activities) | :diamonds: | `:diamonds:` | :clubs: | `:clubs:` | [top](#table-of-contents) | +| [top](#activities) | :chess_pawn: | `:chess_pawn:` | :black_joker: | `:black_joker:` | [top](#table-of-contents) | +| [top](#activities) | :mahjong: | `:mahjong:` | :flower_playing_cards: | `:flower_playing_cards:` | [top](#table-of-contents) | + +### Arts & Crafts + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#activities) | :performing_arts: | `:performing_arts:` | :framed_picture: | `:framed_picture:` | [top](#table-of-contents) | +| [top](#activities) | :art: | `:art:` | :thread: | `:thread:` | [top](#table-of-contents) | +| [top](#activities) | :sewing_needle: | `:sewing_needle:` | :yarn: | `:yarn:` | [top](#table-of-contents) | +| [top](#activities) | :knot: | `:knot:` | | | [top](#table-of-contents) | + +## Objects + +- [Clothing](#clothing) +- [Sound](#sound) +- [Music](#music) +- [Musical Instrument](#musical-instrument) +- [Phone](#phone) +- [Computer](#computer) +- [Light & Video](#light--video) +- [Book Paper](#book-paper) +- [Money](#money) +- [Mail](#mail) +- [Writing](#writing) +- [Office](#office) +- [Lock](#lock) +- [Tool](#tool) +- [Science](#science) +- [Medical](#medical) +- [Household](#household) +- [Other Object](#other-object) + +### Clothing + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#objects) | :eyeglasses: | `:eyeglasses:` | :dark_sunglasses: | `:dark_sunglasses:` | [top](#table-of-contents) | +| [top](#objects) | :goggles: | `:goggles:` | :lab_coat: | `:lab_coat:` | [top](#table-of-contents) | +| [top](#objects) | :safety_vest: | `:safety_vest:` | :necktie: | `:necktie:` | [top](#table-of-contents) | +| [top](#objects) | :shirt: | `:shirt:` `:tshirt:` | :jeans: | `:jeans:` | [top](#table-of-contents) | +| [top](#objects) | :scarf: | `:scarf:` | :gloves: | `:gloves:` | [top](#table-of-contents) | +| [top](#objects) | :coat: | `:coat:` | :socks: | `:socks:` | [top](#table-of-contents) | +| [top](#objects) | :dress: | `:dress:` | :kimono: | `:kimono:` | [top](#table-of-contents) | +| [top](#objects) | :sari: | `:sari:` | :one_piece_swimsuit: | `:one_piece_swimsuit:` | [top](#table-of-contents) | +| [top](#objects) | :swim_brief: | `:swim_brief:` | :shorts: | `:shorts:` | [top](#table-of-contents) | +| [top](#objects) | :bikini: | `:bikini:` | :womans_clothes: | `:womans_clothes:` | [top](#table-of-contents) | +| [top](#objects) | :folding_hand_fan: | `:folding_hand_fan:` | :purse: | `:purse:` | [top](#table-of-contents) | +| [top](#objects) | :handbag: | `:handbag:` | :pouch: | `:pouch:` | [top](#table-of-contents) | +| [top](#objects) | :shopping: | `:shopping:` | :school_satchel: | `:school_satchel:` | [top](#table-of-contents) | +| [top](#objects) | :thong_sandal: | `:thong_sandal:` | :mans_shoe: | `:mans_shoe:` `:shoe:` | [top](#table-of-contents) | +| [top](#objects) | :athletic_shoe: | `:athletic_shoe:` | :hiking_boot: | `:hiking_boot:` | [top](#table-of-contents) | +| [top](#objects) | :flat_shoe: | `:flat_shoe:` | :high_heel: | `:high_heel:` | [top](#table-of-contents) | +| [top](#objects) | :sandal: | `:sandal:` | :ballet_shoes: | `:ballet_shoes:` | [top](#table-of-contents) | +| [top](#objects) | :boot: | `:boot:` | :hair_pick: | `:hair_pick:` | [top](#table-of-contents) | +| [top](#objects) | :crown: | `:crown:` | :womans_hat: | `:womans_hat:` | [top](#table-of-contents) | +| [top](#objects) | :tophat: | `:tophat:` | :mortar_board: | `:mortar_board:` | [top](#table-of-contents) | +| [top](#objects) | :billed_cap: | `:billed_cap:` | :military_helmet: | `:military_helmet:` | [top](#table-of-contents) | +| [top](#objects) | :rescue_worker_helmet: | `:rescue_worker_helmet:` | :prayer_beads: | `:prayer_beads:` | [top](#table-of-contents) | +| [top](#objects) | :lipstick: | `:lipstick:` | :ring: | `:ring:` | [top](#table-of-contents) | +| [top](#objects) | :gem: | `:gem:` | | | [top](#table-of-contents) | + +### Sound + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#objects) | :mute: | `:mute:` | :speaker: | `:speaker:` | [top](#table-of-contents) | +| [top](#objects) | :sound: | `:sound:` | :loud_sound: | `:loud_sound:` | [top](#table-of-contents) | +| [top](#objects) | :loudspeaker: | `:loudspeaker:` | :mega: | `:mega:` | [top](#table-of-contents) | +| [top](#objects) | :postal_horn: | `:postal_horn:` | :bell: | `:bell:` | [top](#table-of-contents) | +| [top](#objects) | :no_bell: | `:no_bell:` | | | [top](#table-of-contents) | + +### Music + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#objects) | :musical_score: | `:musical_score:` | :musical_note: | `:musical_note:` | [top](#table-of-contents) | +| [top](#objects) | :notes: | `:notes:` | :studio_microphone: | `:studio_microphone:` | [top](#table-of-contents) | +| [top](#objects) | :level_slider: | `:level_slider:` | :control_knobs: | `:control_knobs:` | [top](#table-of-contents) | +| [top](#objects) | :microphone: | `:microphone:` | :headphones: | `:headphones:` | [top](#table-of-contents) | +| [top](#objects) | :radio: | `:radio:` | | | [top](#table-of-contents) | + +### Musical Instrument + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#objects) | :saxophone: | `:saxophone:` | :accordion: | `:accordion:` | [top](#table-of-contents) | +| [top](#objects) | :guitar: | `:guitar:` | :musical_keyboard: | `:musical_keyboard:` | [top](#table-of-contents) | +| [top](#objects) | :trumpet: | `:trumpet:` | :violin: | `:violin:` | [top](#table-of-contents) | +| [top](#objects) | :banjo: | `:banjo:` | :drum: | `:drum:` | [top](#table-of-contents) | +| [top](#objects) | :long_drum: | `:long_drum:` | :maracas: | `:maracas:` | [top](#table-of-contents) | +| [top](#objects) | :flute: | `:flute:` | | | [top](#table-of-contents) | + +### Phone + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#objects) | :iphone: | `:iphone:` | :calling: | `:calling:` | [top](#table-of-contents) | +| [top](#objects) | :phone: | `:phone:` `:telephone:` | :telephone_receiver: | `:telephone_receiver:` | [top](#table-of-contents) | +| [top](#objects) | :pager: | `:pager:` | :fax: | `:fax:` | [top](#table-of-contents) | + +### Computer + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#objects) | :battery: | `:battery:` | :low_battery: | `:low_battery:` | [top](#table-of-contents) | +| [top](#objects) | :electric_plug: | `:electric_plug:` | :computer: | `:computer:` | [top](#table-of-contents) | +| [top](#objects) | :desktop_computer: | `:desktop_computer:` | :printer: | `:printer:` | [top](#table-of-contents) | +| [top](#objects) | :keyboard: | `:keyboard:` | :computer_mouse: | `:computer_mouse:` | [top](#table-of-contents) | +| [top](#objects) | :trackball: | `:trackball:` | :minidisc: | `:minidisc:` | [top](#table-of-contents) | +| [top](#objects) | :floppy_disk: | `:floppy_disk:` | :cd: | `:cd:` | [top](#table-of-contents) | +| [top](#objects) | :dvd: | `:dvd:` | :abacus: | `:abacus:` | [top](#table-of-contents) | + +### Light & Video + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#objects) | :movie_camera: | `:movie_camera:` | :film_strip: | `:film_strip:` | [top](#table-of-contents) | +| [top](#objects) | :film_projector: | `:film_projector:` | :clapper: | `:clapper:` | [top](#table-of-contents) | +| [top](#objects) | :tv: | `:tv:` | :camera: | `:camera:` | [top](#table-of-contents) | +| [top](#objects) | :camera_flash: | `:camera_flash:` | :video_camera: | `:video_camera:` | [top](#table-of-contents) | +| [top](#objects) | :vhs: | `:vhs:` | :mag: | `:mag:` | [top](#table-of-contents) | +| [top](#objects) | :mag_right: | `:mag_right:` | :candle: | `:candle:` | [top](#table-of-contents) | +| [top](#objects) | :bulb: | `:bulb:` | :flashlight: | `:flashlight:` | [top](#table-of-contents) | +| [top](#objects) | :izakaya_lantern: | `:izakaya_lantern:` `:lantern:` | :diya_lamp: | `:diya_lamp:` | [top](#table-of-contents) | + +### Book Paper + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#objects) | :notebook_with_decorative_cover: | `:notebook_with_decorative_cover:` | :closed_book: | `:closed_book:` | [top](#table-of-contents) | +| [top](#objects) | :book: | `:book:` `:open_book:` | :green_book: | `:green_book:` | [top](#table-of-contents) | +| [top](#objects) | :blue_book: | `:blue_book:` | :orange_book: | `:orange_book:` | [top](#table-of-contents) | +| [top](#objects) | :books: | `:books:` | :notebook: | `:notebook:` | [top](#table-of-contents) | +| [top](#objects) | :ledger: | `:ledger:` | :page_with_curl: | `:page_with_curl:` | [top](#table-of-contents) | +| [top](#objects) | :scroll: | `:scroll:` | :page_facing_up: | `:page_facing_up:` | [top](#table-of-contents) | +| [top](#objects) | :newspaper: | `:newspaper:` | :newspaper_roll: | `:newspaper_roll:` | [top](#table-of-contents) | +| [top](#objects) | :bookmark_tabs: | `:bookmark_tabs:` | :bookmark: | `:bookmark:` | [top](#table-of-contents) | +| [top](#objects) | :label: | `:label:` | | | [top](#table-of-contents) | + +### Money + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#objects) | :moneybag: | `:moneybag:` | :coin: | `:coin:` | [top](#table-of-contents) | +| [top](#objects) | :yen: | `:yen:` | :dollar: | `:dollar:` | [top](#table-of-contents) | +| [top](#objects) | :euro: | `:euro:` | :pound: | `:pound:` | [top](#table-of-contents) | +| [top](#objects) | :money_with_wings: | `:money_with_wings:` | :credit_card: | `:credit_card:` | [top](#table-of-contents) | +| [top](#objects) | :receipt: | `:receipt:` | :chart: | `:chart:` | [top](#table-of-contents) | + +### Mail + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#objects) | :envelope: | `:envelope:` | :e-mail: | `:e-mail:` `:email:` | [top](#table-of-contents) | +| [top](#objects) | :incoming_envelope: | `:incoming_envelope:` | :envelope_with_arrow: | `:envelope_with_arrow:` | [top](#table-of-contents) | +| [top](#objects) | :outbox_tray: | `:outbox_tray:` | :inbox_tray: | `:inbox_tray:` | [top](#table-of-contents) | +| [top](#objects) | :package: | `:package:` | :mailbox: | `:mailbox:` | [top](#table-of-contents) | +| [top](#objects) | :mailbox_closed: | `:mailbox_closed:` | :mailbox_with_mail: | `:mailbox_with_mail:` | [top](#table-of-contents) | +| [top](#objects) | :mailbox_with_no_mail: | `:mailbox_with_no_mail:` | :postbox: | `:postbox:` | [top](#table-of-contents) | +| [top](#objects) | :ballot_box: | `:ballot_box:` | | | [top](#table-of-contents) | + +### Writing + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#objects) | :pencil2: | `:pencil2:` | :black_nib: | `:black_nib:` | [top](#table-of-contents) | +| [top](#objects) | :fountain_pen: | `:fountain_pen:` | :pen: | `:pen:` | [top](#table-of-contents) | +| [top](#objects) | :paintbrush: | `:paintbrush:` | :crayon: | `:crayon:` | [top](#table-of-contents) | +| [top](#objects) | :memo: | `:memo:` `:pencil:` | | | [top](#table-of-contents) | + +### Office + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#objects) | :briefcase: | `:briefcase:` | :file_folder: | `:file_folder:` | [top](#table-of-contents) | +| [top](#objects) | :open_file_folder: | `:open_file_folder:` | :card_index_dividers: | `:card_index_dividers:` | [top](#table-of-contents) | +| [top](#objects) | :date: | `:date:` | :calendar: | `:calendar:` | [top](#table-of-contents) | +| [top](#objects) | :spiral_notepad: | `:spiral_notepad:` | :spiral_calendar: | `:spiral_calendar:` | [top](#table-of-contents) | +| [top](#objects) | :card_index: | `:card_index:` | :chart_with_upwards_trend: | `:chart_with_upwards_trend:` | [top](#table-of-contents) | +| [top](#objects) | :chart_with_downwards_trend: | `:chart_with_downwards_trend:` | :bar_chart: | `:bar_chart:` | [top](#table-of-contents) | +| [top](#objects) | :clipboard: | `:clipboard:` | :pushpin: | `:pushpin:` | [top](#table-of-contents) | +| [top](#objects) | :round_pushpin: | `:round_pushpin:` | :paperclip: | `:paperclip:` | [top](#table-of-contents) | +| [top](#objects) | :paperclips: | `:paperclips:` | :straight_ruler: | `:straight_ruler:` | [top](#table-of-contents) | +| [top](#objects) | :triangular_ruler: | `:triangular_ruler:` | :scissors: | `:scissors:` | [top](#table-of-contents) | +| [top](#objects) | :card_file_box: | `:card_file_box:` | :file_cabinet: | `:file_cabinet:` | [top](#table-of-contents) | +| [top](#objects) | :wastebasket: | `:wastebasket:` | | | [top](#table-of-contents) | + +### Lock + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#objects) | :lock: | `:lock:` | :unlock: | `:unlock:` | [top](#table-of-contents) | +| [top](#objects) | :lock_with_ink_pen: | `:lock_with_ink_pen:` | :closed_lock_with_key: | `:closed_lock_with_key:` | [top](#table-of-contents) | +| [top](#objects) | :key: | `:key:` | :old_key: | `:old_key:` | [top](#table-of-contents) | + +### Tool + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#objects) | :hammer: | `:hammer:` | :axe: | `:axe:` | [top](#table-of-contents) | +| [top](#objects) | :pick: | `:pick:` | :hammer_and_pick: | `:hammer_and_pick:` | [top](#table-of-contents) | +| [top](#objects) | :hammer_and_wrench: | `:hammer_and_wrench:` | :dagger: | `:dagger:` | [top](#table-of-contents) | +| [top](#objects) | :crossed_swords: | `:crossed_swords:` | :bomb: | `:bomb:` | [top](#table-of-contents) | +| [top](#objects) | :boomerang: | `:boomerang:` | :bow_and_arrow: | `:bow_and_arrow:` | [top](#table-of-contents) | +| [top](#objects) | :shield: | `:shield:` | :carpentry_saw: | `:carpentry_saw:` | [top](#table-of-contents) | +| [top](#objects) | :wrench: | `:wrench:` | :screwdriver: | `:screwdriver:` | [top](#table-of-contents) | +| [top](#objects) | :nut_and_bolt: | `:nut_and_bolt:` | :gear: | `:gear:` | [top](#table-of-contents) | +| [top](#objects) | :clamp: | `:clamp:` | :balance_scale: | `:balance_scale:` | [top](#table-of-contents) | +| [top](#objects) | :probing_cane: | `:probing_cane:` | :link: | `:link:` | [top](#table-of-contents) | +| [top](#objects) | :chains: | `:chains:` | :hook: | `:hook:` | [top](#table-of-contents) | +| [top](#objects) | :toolbox: | `:toolbox:` | :magnet: | `:magnet:` | [top](#table-of-contents) | +| [top](#objects) | :ladder: | `:ladder:` | | | [top](#table-of-contents) | + +### Science + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#objects) | :alembic: | `:alembic:` | :test_tube: | `:test_tube:` | [top](#table-of-contents) | +| [top](#objects) | :petri_dish: | `:petri_dish:` | :dna: | `:dna:` | [top](#table-of-contents) | +| [top](#objects) | :microscope: | `:microscope:` | :telescope: | `:telescope:` | [top](#table-of-contents) | +| [top](#objects) | :satellite: | `:satellite:` | | | [top](#table-of-contents) | + +### Medical + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#objects) | :syringe: | `:syringe:` | :drop_of_blood: | `:drop_of_blood:` | [top](#table-of-contents) | +| [top](#objects) | :pill: | `:pill:` | :adhesive_bandage: | `:adhesive_bandage:` | [top](#table-of-contents) | +| [top](#objects) | :crutch: | `:crutch:` | :stethoscope: | `:stethoscope:` | [top](#table-of-contents) | +| [top](#objects) | :x_ray: | `:x_ray:` | | | [top](#table-of-contents) | + +### Household + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#objects) | :door: | `:door:` | :elevator: | `:elevator:` | [top](#table-of-contents) | +| [top](#objects) | :mirror: | `:mirror:` | :window: | `:window:` | [top](#table-of-contents) | +| [top](#objects) | :bed: | `:bed:` | :couch_and_lamp: | `:couch_and_lamp:` | [top](#table-of-contents) | +| [top](#objects) | :chair: | `:chair:` | :toilet: | `:toilet:` | [top](#table-of-contents) | +| [top](#objects) | :plunger: | `:plunger:` | :shower: | `:shower:` | [top](#table-of-contents) | +| [top](#objects) | :bathtub: | `:bathtub:` | :mouse_trap: | `:mouse_trap:` | [top](#table-of-contents) | +| [top](#objects) | :razor: | `:razor:` | :lotion_bottle: | `:lotion_bottle:` | [top](#table-of-contents) | +| [top](#objects) | :safety_pin: | `:safety_pin:` | :broom: | `:broom:` | [top](#table-of-contents) | +| [top](#objects) | :basket: | `:basket:` | :roll_of_paper: | `:roll_of_paper:` | [top](#table-of-contents) | +| [top](#objects) | :bucket: | `:bucket:` | :soap: | `:soap:` | [top](#table-of-contents) | +| [top](#objects) | :bubbles: | `:bubbles:` | :toothbrush: | `:toothbrush:` | [top](#table-of-contents) | +| [top](#objects) | :sponge: | `:sponge:` | :fire_extinguisher: | `:fire_extinguisher:` | [top](#table-of-contents) | +| [top](#objects) | :shopping_cart: | `:shopping_cart:` | | | [top](#table-of-contents) | + +### Other Object + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#objects) | :smoking: | `:smoking:` | :coffin: | `:coffin:` | [top](#table-of-contents) | +| [top](#objects) | :headstone: | `:headstone:` | :funeral_urn: | `:funeral_urn:` | [top](#table-of-contents) | +| [top](#objects) | :nazar_amulet: | `:nazar_amulet:` | :hamsa: | `:hamsa:` | [top](#table-of-contents) | +| [top](#objects) | :moyai: | `:moyai:` | :placard: | `:placard:` | [top](#table-of-contents) | +| [top](#objects) | :identification_card: | `:identification_card:` | | | [top](#table-of-contents) | + +## Symbols + +- [Transport Sign](#transport-sign) +- [Warning](#warning) +- [Arrow](#arrow) +- [Religion](#religion) +- [Zodiac](#zodiac) +- [Av Symbol](#av-symbol) +- [Gender](#gender) +- [Math](#math) +- [Punctuation](#punctuation) +- [Currency](#currency) +- [Other Symbol](#other-symbol) +- [Keycap](#keycap) +- [Alphanum](#alphanum) +- [Geometric](#geometric) + +### Transport Sign + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#symbols) | :atm: | `:atm:` | :put_litter_in_its_place: | `:put_litter_in_its_place:` | [top](#table-of-contents) | +| [top](#symbols) | :potable_water: | `:potable_water:` | :wheelchair: | `:wheelchair:` | [top](#table-of-contents) | +| [top](#symbols) | :mens: | `:mens:` | :womens: | `:womens:` | [top](#table-of-contents) | +| [top](#symbols) | :restroom: | `:restroom:` | :baby_symbol: | `:baby_symbol:` | [top](#table-of-contents) | +| [top](#symbols) | :wc: | `:wc:` | :passport_control: | `:passport_control:` | [top](#table-of-contents) | +| [top](#symbols) | :customs: | `:customs:` | :baggage_claim: | `:baggage_claim:` | [top](#table-of-contents) | +| [top](#symbols) | :left_luggage: | `:left_luggage:` | | | [top](#table-of-contents) | + +### Warning + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#symbols) | :warning: | `:warning:` | :children_crossing: | `:children_crossing:` | [top](#table-of-contents) | +| [top](#symbols) | :no_entry: | `:no_entry:` | :no_entry_sign: | `:no_entry_sign:` | [top](#table-of-contents) | +| [top](#symbols) | :no_bicycles: | `:no_bicycles:` | :no_smoking: | `:no_smoking:` | [top](#table-of-contents) | +| [top](#symbols) | :do_not_litter: | `:do_not_litter:` | :non-potable_water: | `:non-potable_water:` | [top](#table-of-contents) | +| [top](#symbols) | :no_pedestrians: | `:no_pedestrians:` | :no_mobile_phones: | `:no_mobile_phones:` | [top](#table-of-contents) | +| [top](#symbols) | :underage: | `:underage:` | :radioactive: | `:radioactive:` | [top](#table-of-contents) | +| [top](#symbols) | :biohazard: | `:biohazard:` | | | [top](#table-of-contents) | + +### Arrow + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#symbols) | :arrow_up: | `:arrow_up:` | :arrow_upper_right: | `:arrow_upper_right:` | [top](#table-of-contents) | +| [top](#symbols) | :arrow_right: | `:arrow_right:` | :arrow_lower_right: | `:arrow_lower_right:` | [top](#table-of-contents) | +| [top](#symbols) | :arrow_down: | `:arrow_down:` | :arrow_lower_left: | `:arrow_lower_left:` | [top](#table-of-contents) | +| [top](#symbols) | :arrow_left: | `:arrow_left:` | :arrow_upper_left: | `:arrow_upper_left:` | [top](#table-of-contents) | +| [top](#symbols) | :arrow_up_down: | `:arrow_up_down:` | :left_right_arrow: | `:left_right_arrow:` | [top](#table-of-contents) | +| [top](#symbols) | :leftwards_arrow_with_hook: | `:leftwards_arrow_with_hook:` | :arrow_right_hook: | `:arrow_right_hook:` | [top](#table-of-contents) | +| [top](#symbols) | :arrow_heading_up: | `:arrow_heading_up:` | :arrow_heading_down: | `:arrow_heading_down:` | [top](#table-of-contents) | +| [top](#symbols) | :arrows_clockwise: | `:arrows_clockwise:` | :arrows_counterclockwise: | `:arrows_counterclockwise:` | [top](#table-of-contents) | +| [top](#symbols) | :back: | `:back:` | :end: | `:end:` | [top](#table-of-contents) | +| [top](#symbols) | :on: | `:on:` | :soon: | `:soon:` | [top](#table-of-contents) | +| [top](#symbols) | :top: | `:top:` | | | [top](#table-of-contents) | + +### Religion + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#symbols) | :place_of_worship: | `:place_of_worship:` | :atom_symbol: | `:atom_symbol:` | [top](#table-of-contents) | +| [top](#symbols) | :om: | `:om:` | :star_of_david: | `:star_of_david:` | [top](#table-of-contents) | +| [top](#symbols) | :wheel_of_dharma: | `:wheel_of_dharma:` | :yin_yang: | `:yin_yang:` | [top](#table-of-contents) | +| [top](#symbols) | :latin_cross: | `:latin_cross:` | :orthodox_cross: | `:orthodox_cross:` | [top](#table-of-contents) | +| [top](#symbols) | :star_and_crescent: | `:star_and_crescent:` | :peace_symbol: | `:peace_symbol:` | [top](#table-of-contents) | +| [top](#symbols) | :menorah: | `:menorah:` | :six_pointed_star: | `:six_pointed_star:` | [top](#table-of-contents) | +| [top](#symbols) | :khanda: | `:khanda:` | | | [top](#table-of-contents) | + +### Zodiac + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#symbols) | :aries: | `:aries:` | :taurus: | `:taurus:` | [top](#table-of-contents) | +| [top](#symbols) | :gemini: | `:gemini:` | :cancer: | `:cancer:` | [top](#table-of-contents) | +| [top](#symbols) | :leo: | `:leo:` | :virgo: | `:virgo:` | [top](#table-of-contents) | +| [top](#symbols) | :libra: | `:libra:` | :scorpius: | `:scorpius:` | [top](#table-of-contents) | +| [top](#symbols) | :sagittarius: | `:sagittarius:` | :capricorn: | `:capricorn:` | [top](#table-of-contents) | +| [top](#symbols) | :aquarius: | `:aquarius:` | :pisces: | `:pisces:` | [top](#table-of-contents) | +| [top](#symbols) | :ophiuchus: | `:ophiuchus:` | | | [top](#table-of-contents) | + +### Av Symbol + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#symbols) | :twisted_rightwards_arrows: | `:twisted_rightwards_arrows:` | :repeat: | `:repeat:` | [top](#table-of-contents) | +| [top](#symbols) | :repeat_one: | `:repeat_one:` | :arrow_forward: | `:arrow_forward:` | [top](#table-of-contents) | +| [top](#symbols) | :fast_forward: | `:fast_forward:` | :next_track_button: | `:next_track_button:` | [top](#table-of-contents) | +| [top](#symbols) | :play_or_pause_button: | `:play_or_pause_button:` | :arrow_backward: | `:arrow_backward:` | [top](#table-of-contents) | +| [top](#symbols) | :rewind: | `:rewind:` | :previous_track_button: | `:previous_track_button:` | [top](#table-of-contents) | +| [top](#symbols) | :arrow_up_small: | `:arrow_up_small:` | :arrow_double_up: | `:arrow_double_up:` | [top](#table-of-contents) | +| [top](#symbols) | :arrow_down_small: | `:arrow_down_small:` | :arrow_double_down: | `:arrow_double_down:` | [top](#table-of-contents) | +| [top](#symbols) | :pause_button: | `:pause_button:` | :stop_button: | `:stop_button:` | [top](#table-of-contents) | +| [top](#symbols) | :record_button: | `:record_button:` | :eject_button: | `:eject_button:` | [top](#table-of-contents) | +| [top](#symbols) | :cinema: | `:cinema:` | :low_brightness: | `:low_brightness:` | [top](#table-of-contents) | +| [top](#symbols) | :high_brightness: | `:high_brightness:` | :signal_strength: | `:signal_strength:` | [top](#table-of-contents) | +| [top](#symbols) | :wireless: | `:wireless:` | :vibration_mode: | `:vibration_mode:` | [top](#table-of-contents) | +| [top](#symbols) | :mobile_phone_off: | `:mobile_phone_off:` | | | [top](#table-of-contents) | + +### Gender + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#symbols) | :female_sign: | `:female_sign:` | :male_sign: | `:male_sign:` | [top](#table-of-contents) | +| [top](#symbols) | :transgender_symbol: | `:transgender_symbol:` | | | [top](#table-of-contents) | + +### Math + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#symbols) | :heavy_multiplication_x: | `:heavy_multiplication_x:` | :heavy_plus_sign: | `:heavy_plus_sign:` | [top](#table-of-contents) | +| [top](#symbols) | :heavy_minus_sign: | `:heavy_minus_sign:` | :heavy_division_sign: | `:heavy_division_sign:` | [top](#table-of-contents) | +| [top](#symbols) | :heavy_equals_sign: | `:heavy_equals_sign:` | :infinity: | `:infinity:` | [top](#table-of-contents) | + +### Punctuation + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#symbols) | :bangbang: | `:bangbang:` | :interrobang: | `:interrobang:` | [top](#table-of-contents) | +| [top](#symbols) | :question: | `:question:` | :grey_question: | `:grey_question:` | [top](#table-of-contents) | +| [top](#symbols) | :grey_exclamation: | `:grey_exclamation:` | :exclamation: | `:exclamation:` `:heavy_exclamation_mark:` | [top](#table-of-contents) | +| [top](#symbols) | :wavy_dash: | `:wavy_dash:` | | | [top](#table-of-contents) | + +### Currency + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#symbols) | :currency_exchange: | `:currency_exchange:` | :heavy_dollar_sign: | `:heavy_dollar_sign:` | [top](#table-of-contents) | + +### Other Symbol + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#symbols) | :medical_symbol: | `:medical_symbol:` | :recycle: | `:recycle:` | [top](#table-of-contents) | +| [top](#symbols) | :fleur_de_lis: | `:fleur_de_lis:` | :trident: | `:trident:` | [top](#table-of-contents) | +| [top](#symbols) | :name_badge: | `:name_badge:` | :beginner: | `:beginner:` | [top](#table-of-contents) | +| [top](#symbols) | :o: | `:o:` | :white_check_mark: | `:white_check_mark:` | [top](#table-of-contents) | +| [top](#symbols) | :ballot_box_with_check: | `:ballot_box_with_check:` | :heavy_check_mark: | `:heavy_check_mark:` | [top](#table-of-contents) | +| [top](#symbols) | :x: | `:x:` | :negative_squared_cross_mark: | `:negative_squared_cross_mark:` | [top](#table-of-contents) | +| [top](#symbols) | :curly_loop: | `:curly_loop:` | :loop: | `:loop:` | [top](#table-of-contents) | +| [top](#symbols) | :part_alternation_mark: | `:part_alternation_mark:` | :eight_spoked_asterisk: | `:eight_spoked_asterisk:` | [top](#table-of-contents) | +| [top](#symbols) | :eight_pointed_black_star: | `:eight_pointed_black_star:` | :sparkle: | `:sparkle:` | [top](#table-of-contents) | +| [top](#symbols) | :copyright: | `:copyright:` | :registered: | `:registered:` | [top](#table-of-contents) | +| [top](#symbols) | :tm: | `:tm:` | | | [top](#table-of-contents) | + +### Keycap + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#symbols) | :hash: | `:hash:` | :asterisk: | `:asterisk:` | [top](#table-of-contents) | +| [top](#symbols) | :zero: | `:zero:` | :one: | `:one:` | [top](#table-of-contents) | +| [top](#symbols) | :two: | `:two:` | :three: | `:three:` | [top](#table-of-contents) | +| [top](#symbols) | :four: | `:four:` | :five: | `:five:` | [top](#table-of-contents) | +| [top](#symbols) | :six: | `:six:` | :seven: | `:seven:` | [top](#table-of-contents) | +| [top](#symbols) | :eight: | `:eight:` | :nine: | `:nine:` | [top](#table-of-contents) | +| [top](#symbols) | :keycap_ten: | `:keycap_ten:` | | | [top](#table-of-contents) | + +### Alphanum + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#symbols) | :capital_abcd: | `:capital_abcd:` | :abcd: | `:abcd:` | [top](#table-of-contents) | +| [top](#symbols) | :1234: | `:1234:` | :symbols: | `:symbols:` | [top](#table-of-contents) | +| [top](#symbols) | :abc: | `:abc:` | :a: | `:a:` | [top](#table-of-contents) | +| [top](#symbols) | :ab: | `:ab:` | :b: | `:b:` | [top](#table-of-contents) | +| [top](#symbols) | :cl: | `:cl:` | :cool: | `:cool:` | [top](#table-of-contents) | +| [top](#symbols) | :free: | `:free:` | :information_source: | `:information_source:` | [top](#table-of-contents) | +| [top](#symbols) | :id: | `:id:` | :m: | `:m:` | [top](#table-of-contents) | +| [top](#symbols) | :new: | `:new:` | :ng: | `:ng:` | [top](#table-of-contents) | +| [top](#symbols) | :o2: | `:o2:` | :ok: | `:ok:` | [top](#table-of-contents) | +| [top](#symbols) | :parking: | `:parking:` | :sos: | `:sos:` | [top](#table-of-contents) | +| [top](#symbols) | :up: | `:up:` | :vs: | `:vs:` | [top](#table-of-contents) | +| [top](#symbols) | :koko: | `:koko:` | :sa: | `:sa:` | [top](#table-of-contents) | +| [top](#symbols) | :u6708: | `:u6708:` | :u6709: | `:u6709:` | [top](#table-of-contents) | +| [top](#symbols) | :u6307: | `:u6307:` | :ideograph_advantage: | `:ideograph_advantage:` | [top](#table-of-contents) | +| [top](#symbols) | :u5272: | `:u5272:` | :u7121: | `:u7121:` | [top](#table-of-contents) | +| [top](#symbols) | :u7981: | `:u7981:` | :accept: | `:accept:` | [top](#table-of-contents) | +| [top](#symbols) | :u7533: | `:u7533:` | :u5408: | `:u5408:` | [top](#table-of-contents) | +| [top](#symbols) | :u7a7a: | `:u7a7a:` | :congratulations: | `:congratulations:` | [top](#table-of-contents) | +| [top](#symbols) | :secret: | `:secret:` | :u55b6: | `:u55b6:` | [top](#table-of-contents) | +| [top](#symbols) | :u6e80: | `:u6e80:` | | | [top](#table-of-contents) | + +### Geometric + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#symbols) | :red_circle: | `:red_circle:` | :orange_circle: | `:orange_circle:` | [top](#table-of-contents) | +| [top](#symbols) | :yellow_circle: | `:yellow_circle:` | :green_circle: | `:green_circle:` | [top](#table-of-contents) | +| [top](#symbols) | :large_blue_circle: | `:large_blue_circle:` | :purple_circle: | `:purple_circle:` | [top](#table-of-contents) | +| [top](#symbols) | :brown_circle: | `:brown_circle:` | :black_circle: | `:black_circle:` | [top](#table-of-contents) | +| [top](#symbols) | :white_circle: | `:white_circle:` | :red_square: | `:red_square:` | [top](#table-of-contents) | +| [top](#symbols) | :orange_square: | `:orange_square:` | :yellow_square: | `:yellow_square:` | [top](#table-of-contents) | +| [top](#symbols) | :green_square: | `:green_square:` | :blue_square: | `:blue_square:` | [top](#table-of-contents) | +| [top](#symbols) | :purple_square: | `:purple_square:` | :brown_square: | `:brown_square:` | [top](#table-of-contents) | +| [top](#symbols) | :black_large_square: | `:black_large_square:` | :white_large_square: | `:white_large_square:` | [top](#table-of-contents) | +| [top](#symbols) | :black_medium_square: | `:black_medium_square:` | :white_medium_square: | `:white_medium_square:` | [top](#table-of-contents) | +| [top](#symbols) | :black_medium_small_square: | `:black_medium_small_square:` | :white_medium_small_square: | `:white_medium_small_square:` | [top](#table-of-contents) | +| [top](#symbols) | :black_small_square: | `:black_small_square:` | :white_small_square: | `:white_small_square:` | [top](#table-of-contents) | +| [top](#symbols) | :large_orange_diamond: | `:large_orange_diamond:` | :large_blue_diamond: | `:large_blue_diamond:` | [top](#table-of-contents) | +| [top](#symbols) | :small_orange_diamond: | `:small_orange_diamond:` | :small_blue_diamond: | `:small_blue_diamond:` | [top](#table-of-contents) | +| [top](#symbols) | :small_red_triangle: | `:small_red_triangle:` | :small_red_triangle_down: | `:small_red_triangle_down:` | [top](#table-of-contents) | +| [top](#symbols) | :diamond_shape_with_a_dot_inside: | `:diamond_shape_with_a_dot_inside:` | :radio_button: | `:radio_button:` | [top](#table-of-contents) | +| [top](#symbols) | :white_square_button: | `:white_square_button:` | :black_square_button: | `:black_square_button:` | [top](#table-of-contents) | + +## Flags + +- [Flag](#flag) +- [Country Flag](#country-flag) +- [Subdivision Flag](#subdivision-flag) + +### Flag + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#flags) | :checkered_flag: | `:checkered_flag:` | :triangular_flag_on_post: | `:triangular_flag_on_post:` | [top](#table-of-contents) | +| [top](#flags) | :crossed_flags: | `:crossed_flags:` | :black_flag: | `:black_flag:` | [top](#table-of-contents) | +| [top](#flags) | :white_flag: | `:white_flag:` | :rainbow_flag: | `:rainbow_flag:` | [top](#table-of-contents) | +| [top](#flags) | :transgender_flag: | `:transgender_flag:` | :pirate_flag: | `:pirate_flag:` | [top](#table-of-contents) | + +### Country Flag + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#flags) | :ascension_island: | `:ascension_island:` | :andorra: | `:andorra:` | [top](#table-of-contents) | +| [top](#flags) | :united_arab_emirates: | `:united_arab_emirates:` | :afghanistan: | `:afghanistan:` | [top](#table-of-contents) | +| [top](#flags) | :antigua_barbuda: | `:antigua_barbuda:` | :anguilla: | `:anguilla:` | [top](#table-of-contents) | +| [top](#flags) | :albania: | `:albania:` | :armenia: | `:armenia:` | [top](#table-of-contents) | +| [top](#flags) | :angola: | `:angola:` | :antarctica: | `:antarctica:` | [top](#table-of-contents) | +| [top](#flags) | :argentina: | `:argentina:` | :american_samoa: | `:american_samoa:` | [top](#table-of-contents) | +| [top](#flags) | :austria: | `:austria:` | :australia: | `:australia:` | [top](#table-of-contents) | +| [top](#flags) | :aruba: | `:aruba:` | :aland_islands: | `:aland_islands:` | [top](#table-of-contents) | +| [top](#flags) | :azerbaijan: | `:azerbaijan:` | :bosnia_herzegovina: | `:bosnia_herzegovina:` | [top](#table-of-contents) | +| [top](#flags) | :barbados: | `:barbados:` | :bangladesh: | `:bangladesh:` | [top](#table-of-contents) | +| [top](#flags) | :belgium: | `:belgium:` | :burkina_faso: | `:burkina_faso:` | [top](#table-of-contents) | +| [top](#flags) | :bulgaria: | `:bulgaria:` | :bahrain: | `:bahrain:` | [top](#table-of-contents) | +| [top](#flags) | :burundi: | `:burundi:` | :benin: | `:benin:` | [top](#table-of-contents) | +| [top](#flags) | :st_barthelemy: | `:st_barthelemy:` | :bermuda: | `:bermuda:` | [top](#table-of-contents) | +| [top](#flags) | :brunei: | `:brunei:` | :bolivia: | `:bolivia:` | [top](#table-of-contents) | +| [top](#flags) | :caribbean_netherlands: | `:caribbean_netherlands:` | :brazil: | `:brazil:` | [top](#table-of-contents) | +| [top](#flags) | :bahamas: | `:bahamas:` | :bhutan: | `:bhutan:` | [top](#table-of-contents) | +| [top](#flags) | :bouvet_island: | `:bouvet_island:` | :botswana: | `:botswana:` | [top](#table-of-contents) | +| [top](#flags) | :belarus: | `:belarus:` | :belize: | `:belize:` | [top](#table-of-contents) | +| [top](#flags) | :canada: | `:canada:` | :cocos_islands: | `:cocos_islands:` | [top](#table-of-contents) | +| [top](#flags) | :congo_kinshasa: | `:congo_kinshasa:` | :central_african_republic: | `:central_african_republic:` | [top](#table-of-contents) | +| [top](#flags) | :congo_brazzaville: | `:congo_brazzaville:` | :switzerland: | `:switzerland:` | [top](#table-of-contents) | +| [top](#flags) | :cote_divoire: | `:cote_divoire:` | :cook_islands: | `:cook_islands:` | [top](#table-of-contents) | +| [top](#flags) | :chile: | `:chile:` | :cameroon: | `:cameroon:` | [top](#table-of-contents) | +| [top](#flags) | :cn: | `:cn:` | :colombia: | `:colombia:` | [top](#table-of-contents) | +| [top](#flags) | :clipperton_island: | `:clipperton_island:` | :costa_rica: | `:costa_rica:` | [top](#table-of-contents) | +| [top](#flags) | :cuba: | `:cuba:` | :cape_verde: | `:cape_verde:` | [top](#table-of-contents) | +| [top](#flags) | :curacao: | `:curacao:` | :christmas_island: | `:christmas_island:` | [top](#table-of-contents) | +| [top](#flags) | :cyprus: | `:cyprus:` | :czech_republic: | `:czech_republic:` | [top](#table-of-contents) | +| [top](#flags) | :de: | `:de:` | :diego_garcia: | `:diego_garcia:` | [top](#table-of-contents) | +| [top](#flags) | :djibouti: | `:djibouti:` | :denmark: | `:denmark:` | [top](#table-of-contents) | +| [top](#flags) | :dominica: | `:dominica:` | :dominican_republic: | `:dominican_republic:` | [top](#table-of-contents) | +| [top](#flags) | :algeria: | `:algeria:` | :ceuta_melilla: | `:ceuta_melilla:` | [top](#table-of-contents) | +| [top](#flags) | :ecuador: | `:ecuador:` | :estonia: | `:estonia:` | [top](#table-of-contents) | +| [top](#flags) | :egypt: | `:egypt:` | :western_sahara: | `:western_sahara:` | [top](#table-of-contents) | +| [top](#flags) | :eritrea: | `:eritrea:` | :es: | `:es:` | [top](#table-of-contents) | +| [top](#flags) | :ethiopia: | `:ethiopia:` | :eu: | `:eu:` `:european_union:` | [top](#table-of-contents) | +| [top](#flags) | :finland: | `:finland:` | :fiji: | `:fiji:` | [top](#table-of-contents) | +| [top](#flags) | :falkland_islands: | `:falkland_islands:` | :micronesia: | `:micronesia:` | [top](#table-of-contents) | +| [top](#flags) | :faroe_islands: | `:faroe_islands:` | :fr: | `:fr:` | [top](#table-of-contents) | +| [top](#flags) | :gabon: | `:gabon:` | :gb: | `:gb:` `:uk:` | [top](#table-of-contents) | +| [top](#flags) | :grenada: | `:grenada:` | :georgia: | `:georgia:` | [top](#table-of-contents) | +| [top](#flags) | :french_guiana: | `:french_guiana:` | :guernsey: | `:guernsey:` | [top](#table-of-contents) | +| [top](#flags) | :ghana: | `:ghana:` | :gibraltar: | `:gibraltar:` | [top](#table-of-contents) | +| [top](#flags) | :greenland: | `:greenland:` | :gambia: | `:gambia:` | [top](#table-of-contents) | +| [top](#flags) | :guinea: | `:guinea:` | :guadeloupe: | `:guadeloupe:` | [top](#table-of-contents) | +| [top](#flags) | :equatorial_guinea: | `:equatorial_guinea:` | :greece: | `:greece:` | [top](#table-of-contents) | +| [top](#flags) | :south_georgia_south_sandwich_islands: | `:south_georgia_south_sandwich_islands:` | :guatemala: | `:guatemala:` | [top](#table-of-contents) | +| [top](#flags) | :guam: | `:guam:` | :guinea_bissau: | `:guinea_bissau:` | [top](#table-of-contents) | +| [top](#flags) | :guyana: | `:guyana:` | :hong_kong: | `:hong_kong:` | [top](#table-of-contents) | +| [top](#flags) | :heard_mcdonald_islands: | `:heard_mcdonald_islands:` | :honduras: | `:honduras:` | [top](#table-of-contents) | +| [top](#flags) | :croatia: | `:croatia:` | :haiti: | `:haiti:` | [top](#table-of-contents) | +| [top](#flags) | :hungary: | `:hungary:` | :canary_islands: | `:canary_islands:` | [top](#table-of-contents) | +| [top](#flags) | :indonesia: | `:indonesia:` | :ireland: | `:ireland:` | [top](#table-of-contents) | +| [top](#flags) | :israel: | `:israel:` | :isle_of_man: | `:isle_of_man:` | [top](#table-of-contents) | +| [top](#flags) | :india: | `:india:` | :british_indian_ocean_territory: | `:british_indian_ocean_territory:` | [top](#table-of-contents) | +| [top](#flags) | :iraq: | `:iraq:` | :iran: | `:iran:` | [top](#table-of-contents) | +| [top](#flags) | :iceland: | `:iceland:` | :it: | `:it:` | [top](#table-of-contents) | +| [top](#flags) | :jersey: | `:jersey:` | :jamaica: | `:jamaica:` | [top](#table-of-contents) | +| [top](#flags) | :jordan: | `:jordan:` | :jp: | `:jp:` | [top](#table-of-contents) | +| [top](#flags) | :kenya: | `:kenya:` | :kyrgyzstan: | `:kyrgyzstan:` | [top](#table-of-contents) | +| [top](#flags) | :cambodia: | `:cambodia:` | :kiribati: | `:kiribati:` | [top](#table-of-contents) | +| [top](#flags) | :comoros: | `:comoros:` | :st_kitts_nevis: | `:st_kitts_nevis:` | [top](#table-of-contents) | +| [top](#flags) | :north_korea: | `:north_korea:` | :kr: | `:kr:` | [top](#table-of-contents) | +| [top](#flags) | :kuwait: | `:kuwait:` | :cayman_islands: | `:cayman_islands:` | [top](#table-of-contents) | +| [top](#flags) | :kazakhstan: | `:kazakhstan:` | :laos: | `:laos:` | [top](#table-of-contents) | +| [top](#flags) | :lebanon: | `:lebanon:` | :st_lucia: | `:st_lucia:` | [top](#table-of-contents) | +| [top](#flags) | :liechtenstein: | `:liechtenstein:` | :sri_lanka: | `:sri_lanka:` | [top](#table-of-contents) | +| [top](#flags) | :liberia: | `:liberia:` | :lesotho: | `:lesotho:` | [top](#table-of-contents) | +| [top](#flags) | :lithuania: | `:lithuania:` | :luxembourg: | `:luxembourg:` | [top](#table-of-contents) | +| [top](#flags) | :latvia: | `:latvia:` | :libya: | `:libya:` | [top](#table-of-contents) | +| [top](#flags) | :morocco: | `:morocco:` | :monaco: | `:monaco:` | [top](#table-of-contents) | +| [top](#flags) | :moldova: | `:moldova:` | :montenegro: | `:montenegro:` | [top](#table-of-contents) | +| [top](#flags) | :st_martin: | `:st_martin:` | :madagascar: | `:madagascar:` | [top](#table-of-contents) | +| [top](#flags) | :marshall_islands: | `:marshall_islands:` | :macedonia: | `:macedonia:` | [top](#table-of-contents) | +| [top](#flags) | :mali: | `:mali:` | :myanmar: | `:myanmar:` | [top](#table-of-contents) | +| [top](#flags) | :mongolia: | `:mongolia:` | :macau: | `:macau:` | [top](#table-of-contents) | +| [top](#flags) | :northern_mariana_islands: | `:northern_mariana_islands:` | :martinique: | `:martinique:` | [top](#table-of-contents) | +| [top](#flags) | :mauritania: | `:mauritania:` | :montserrat: | `:montserrat:` | [top](#table-of-contents) | +| [top](#flags) | :malta: | `:malta:` | :mauritius: | `:mauritius:` | [top](#table-of-contents) | +| [top](#flags) | :maldives: | `:maldives:` | :malawi: | `:malawi:` | [top](#table-of-contents) | +| [top](#flags) | :mexico: | `:mexico:` | :malaysia: | `:malaysia:` | [top](#table-of-contents) | +| [top](#flags) | :mozambique: | `:mozambique:` | :namibia: | `:namibia:` | [top](#table-of-contents) | +| [top](#flags) | :new_caledonia: | `:new_caledonia:` | :niger: | `:niger:` | [top](#table-of-contents) | +| [top](#flags) | :norfolk_island: | `:norfolk_island:` | :nigeria: | `:nigeria:` | [top](#table-of-contents) | +| [top](#flags) | :nicaragua: | `:nicaragua:` | :netherlands: | `:netherlands:` | [top](#table-of-contents) | +| [top](#flags) | :norway: | `:norway:` | :nepal: | `:nepal:` | [top](#table-of-contents) | +| [top](#flags) | :nauru: | `:nauru:` | :niue: | `:niue:` | [top](#table-of-contents) | +| [top](#flags) | :new_zealand: | `:new_zealand:` | :oman: | `:oman:` | [top](#table-of-contents) | +| [top](#flags) | :panama: | `:panama:` | :peru: | `:peru:` | [top](#table-of-contents) | +| [top](#flags) | :french_polynesia: | `:french_polynesia:` | :papua_new_guinea: | `:papua_new_guinea:` | [top](#table-of-contents) | +| [top](#flags) | :philippines: | `:philippines:` | :pakistan: | `:pakistan:` | [top](#table-of-contents) | +| [top](#flags) | :poland: | `:poland:` | :st_pierre_miquelon: | `:st_pierre_miquelon:` | [top](#table-of-contents) | +| [top](#flags) | :pitcairn_islands: | `:pitcairn_islands:` | :puerto_rico: | `:puerto_rico:` | [top](#table-of-contents) | +| [top](#flags) | :palestinian_territories: | `:palestinian_territories:` | :portugal: | `:portugal:` | [top](#table-of-contents) | +| [top](#flags) | :palau: | `:palau:` | :paraguay: | `:paraguay:` | [top](#table-of-contents) | +| [top](#flags) | :qatar: | `:qatar:` | :reunion: | `:reunion:` | [top](#table-of-contents) | +| [top](#flags) | :romania: | `:romania:` | :serbia: | `:serbia:` | [top](#table-of-contents) | +| [top](#flags) | :ru: | `:ru:` | :rwanda: | `:rwanda:` | [top](#table-of-contents) | +| [top](#flags) | :saudi_arabia: | `:saudi_arabia:` | :solomon_islands: | `:solomon_islands:` | [top](#table-of-contents) | +| [top](#flags) | :seychelles: | `:seychelles:` | :sudan: | `:sudan:` | [top](#table-of-contents) | +| [top](#flags) | :sweden: | `:sweden:` | :singapore: | `:singapore:` | [top](#table-of-contents) | +| [top](#flags) | :st_helena: | `:st_helena:` | :slovenia: | `:slovenia:` | [top](#table-of-contents) | +| [top](#flags) | :svalbard_jan_mayen: | `:svalbard_jan_mayen:` | :slovakia: | `:slovakia:` | [top](#table-of-contents) | +| [top](#flags) | :sierra_leone: | `:sierra_leone:` | :san_marino: | `:san_marino:` | [top](#table-of-contents) | +| [top](#flags) | :senegal: | `:senegal:` | :somalia: | `:somalia:` | [top](#table-of-contents) | +| [top](#flags) | :suriname: | `:suriname:` | :south_sudan: | `:south_sudan:` | [top](#table-of-contents) | +| [top](#flags) | :sao_tome_principe: | `:sao_tome_principe:` | :el_salvador: | `:el_salvador:` | [top](#table-of-contents) | +| [top](#flags) | :sint_maarten: | `:sint_maarten:` | :syria: | `:syria:` | [top](#table-of-contents) | +| [top](#flags) | :swaziland: | `:swaziland:` | :tristan_da_cunha: | `:tristan_da_cunha:` | [top](#table-of-contents) | +| [top](#flags) | :turks_caicos_islands: | `:turks_caicos_islands:` | :chad: | `:chad:` | [top](#table-of-contents) | +| [top](#flags) | :french_southern_territories: | `:french_southern_territories:` | :togo: | `:togo:` | [top](#table-of-contents) | +| [top](#flags) | :thailand: | `:thailand:` | :tajikistan: | `:tajikistan:` | [top](#table-of-contents) | +| [top](#flags) | :tokelau: | `:tokelau:` | :timor_leste: | `:timor_leste:` | [top](#table-of-contents) | +| [top](#flags) | :turkmenistan: | `:turkmenistan:` | :tunisia: | `:tunisia:` | [top](#table-of-contents) | +| [top](#flags) | :tonga: | `:tonga:` | :tr: | `:tr:` | [top](#table-of-contents) | +| [top](#flags) | :trinidad_tobago: | `:trinidad_tobago:` | :tuvalu: | `:tuvalu:` | [top](#table-of-contents) | +| [top](#flags) | :taiwan: | `:taiwan:` | :tanzania: | `:tanzania:` | [top](#table-of-contents) | +| [top](#flags) | :ukraine: | `:ukraine:` | :uganda: | `:uganda:` | [top](#table-of-contents) | +| [top](#flags) | :us_outlying_islands: | `:us_outlying_islands:` | :united_nations: | `:united_nations:` | [top](#table-of-contents) | +| [top](#flags) | :us: | `:us:` | :uruguay: | `:uruguay:` | [top](#table-of-contents) | +| [top](#flags) | :uzbekistan: | `:uzbekistan:` | :vatican_city: | `:vatican_city:` | [top](#table-of-contents) | +| [top](#flags) | :st_vincent_grenadines: | `:st_vincent_grenadines:` | :venezuela: | `:venezuela:` | [top](#table-of-contents) | +| [top](#flags) | :british_virgin_islands: | `:british_virgin_islands:` | :us_virgin_islands: | `:us_virgin_islands:` | [top](#table-of-contents) | +| [top](#flags) | :vietnam: | `:vietnam:` | :vanuatu: | `:vanuatu:` | [top](#table-of-contents) | +| [top](#flags) | :wallis_futuna: | `:wallis_futuna:` | :samoa: | `:samoa:` | [top](#table-of-contents) | +| [top](#flags) | :kosovo: | `:kosovo:` | :yemen: | `:yemen:` | [top](#table-of-contents) | +| [top](#flags) | :mayotte: | `:mayotte:` | :south_africa: | `:south_africa:` | [top](#table-of-contents) | +| [top](#flags) | :zambia: | `:zambia:` | :zimbabwe: | `:zimbabwe:` | [top](#table-of-contents) | + +### Subdivision Flag + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#flags) | :england: | `:england:` | :scotland: | `:scotland:` | [top](#table-of-contents) | +| [top](#flags) | :wales: | `:wales:` | | | [top](#table-of-contents) | + +## GitHub Custom Emoji + +| | ico | shortcode | ico | shortcode | | +| - | :-: | - | :-: | - | - | +| [top](#github-custom-emoji) | :accessibility: | `:accessibility:` | :atom: | `:atom:` | [top](#table-of-contents) | +| [top](#github-custom-emoji) | :basecamp: | `:basecamp:` | :basecampy: | `:basecampy:` | [top](#table-of-contents) | +| [top](#github-custom-emoji) | :bowtie: | `:bowtie:` | :dependabot: | `:dependabot:` | [top](#table-of-contents) | +| [top](#github-custom-emoji) | :electron: | `:electron:` | :feelsgood: | `:feelsgood:` | [top](#table-of-contents) | +| [top](#github-custom-emoji) | :finnadie: | `:finnadie:` | :fishsticks: | `:fishsticks:` | [top](#table-of-contents) | +| [top](#github-custom-emoji) | :goberserk: | `:goberserk:` | :godmode: | `:godmode:` | [top](#table-of-contents) | +| [top](#github-custom-emoji) | :hurtrealbad: | `:hurtrealbad:` | :neckbeard: | `:neckbeard:` | [top](#table-of-contents) | +| [top](#github-custom-emoji) | :octocat: | `:octocat:` | :rage1: | `:rage1:` | [top](#table-of-contents) | +| [top](#github-custom-emoji) | :rage2: | `:rage2:` | :rage3: | `:rage3:` | [top](#table-of-contents) | +| [top](#github-custom-emoji) | :rage4: | `:rage4:` | :shipit: | `:shipit:` | [top](#table-of-contents) | +| [top](#github-custom-emoji) | :suspect: | `:suspect:` | :trollface: | `:trollface:` | [top](#table-of-contents) | diff --git a/docs/content/en/quick-reference/functions.md b/docs/content/en/quick-reference/functions.md new file mode 100644 index 0000000..72235d0 --- /dev/null +++ b/docs/content/en/quick-reference/functions.md @@ -0,0 +1,8 @@ +--- +title: Functions +description: A quick reference guide to Hugo's functions, grouped by namespace. Aliases, if any, appear in parentheses to the right of the function name. +categories: [] +keywords: [] +--- + +{{% quick-reference section="functions" %}} diff --git a/docs/content/en/quick-reference/glob-patterns.md b/docs/content/en/quick-reference/glob-patterns.md new file mode 100644 index 0000000..13cd01f --- /dev/null +++ b/docs/content/en/quick-reference/glob-patterns.md @@ -0,0 +1,38 @@ +--- +title: Glob patterns +description: A quick reference guide to glob pattern syntax and matching rules for wildcards, character sets, and delimiters, featuring illustrative examples. +categories: [] +keywords: [] +--- + +{{% glossary-term "glob pattern" %}} + +The table below details the supported glob pattern syntax and its matching behavior. Each example illustrates a specific match type, the pattern used, and the expected boolean result when evaluated against a test string. + +| Match type | Glob pattern | Test string | Match? | +| :--- | :--- | :--- | :--- | +| Simple wildcard | `a/*.md` | `a/page.md` | true | +| Literal match | `'a/*.md'` | `a/*.md` | true | +| Single-level wildcard | `a/*/page.md` | `a/b/page.md` | true | +| Single-level wildcard | `a/*/page.md` | `a/b/c/page.md` | false | +| Multi-level wildcard | `a/**/page.md` | `a/b/c/page.md` | true | +| Single character | `file.???` | `file.txt` | true | +| Single character | `file.???` | `file.js` | false | +| Delimiter exclusion | `?at` | `f/at` | false | +| Character list | `f.[jt]xt` | `f.txt` | true | +| Negated list | `f.[!j]xt` | `f.txt` | true | +| Character range | `f.[a-c].txt` | `f.b.txt` | true | +| Character range | `f.[a-c].txt` | `f.z.txt` | false | +| Negated range | `f.[!a-c].txt` | `f.z.txt` | true | +| Pattern alternates | `*.{jpg,png}` | `logo.png` | true | +| No match | `*.{jpg,png}` | `logo.webp` | false | + +The matching logic follows these rules: + +- Standard wildcard (`*`) matches any character except for a delimiter. +- Super wildcard (`**`) matches any character including delimiters. +- Single character (`?`) matches exactly one character, excluding delimiters. +- Negation (`!`) matches any character except those specified in a list or range when used inside brackets. +- Character ranges (`[a-z]`) match any single character within the specified range. + +The delimiter is a slash (`/`), except when matching semantic version strings, where the delimiter is a dot (`.`). diff --git a/docs/content/en/quick-reference/glossary/_index.md b/docs/content/en/quick-reference/glossary/_index.md new file mode 100644 index 0000000..8940fd7 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/_index.md @@ -0,0 +1,20 @@ +--- +title: Glossary +description: Terms commonly used throughout the documentation. +categories: [] +keywords: [] +build: + render: always + list: always +cascade: + build: + render: never + list: local +layout: single +params: + hide_in_this_section: true + searchable: true +aliases: [/getting-started/glossary/] +--- + +{{% glossary %}} diff --git a/docs/content/en/quick-reference/glossary/action.md b/docs/content/en/quick-reference/glossary/action.md new file mode 100644 index 0000000..ced8773 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/action.md @@ -0,0 +1,5 @@ +--- +title: action +--- + +See [_template action_](g). diff --git a/docs/content/en/quick-reference/glossary/archetype.md b/docs/content/en/quick-reference/glossary/archetype.md new file mode 100644 index 0000000..d22fae9 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/archetype.md @@ -0,0 +1,7 @@ +--- +title: archetype +params: + reference: /content-management/archetypes/ +--- + +An _archetype_ is a template for new content. diff --git a/docs/content/en/quick-reference/glossary/argument.md b/docs/content/en/quick-reference/glossary/argument.md new file mode 100644 index 0000000..912951d --- /dev/null +++ b/docs/content/en/quick-reference/glossary/argument.md @@ -0,0 +1,5 @@ +--- +title: argument +--- + +An _argument_ is a [_scalar_](g), [_array_](g), [_slice_](g), [_map_](g), or [_object_](g) passed to a [_function_](g), [_method_](g), or [_shortcode_](g). diff --git a/docs/content/en/quick-reference/glossary/array.md b/docs/content/en/quick-reference/glossary/array.md new file mode 100644 index 0000000..0df45f2 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/array.md @@ -0,0 +1,6 @@ +--- +title: array +reference: https://go.dev/ref/spec#Array_types +--- + +An _array_ is a numbered sequence of [_elements_](g). Unlike Go's [_slice_](g) data type, an array has a fixed length. Elements within an array can be [_scalars_](g), slices, [_maps_](g), pages, or other arrays. diff --git a/docs/content/en/quick-reference/glossary/asset-pipeline.md b/docs/content/en/quick-reference/glossary/asset-pipeline.md new file mode 100644 index 0000000..5f3264a --- /dev/null +++ b/docs/content/en/quick-reference/glossary/asset-pipeline.md @@ -0,0 +1,5 @@ +--- +title: asset pipeline +--- + +An _asset pipeline_ is a system that automates and optimizes the handling of static assets like images, stylesheets, and JavaScript files. diff --git a/docs/content/en/quick-reference/glossary/bool.md b/docs/content/en/quick-reference/glossary/bool.md new file mode 100644 index 0000000..a4f33b5 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/bool.md @@ -0,0 +1,5 @@ +--- +title: bool +--- + +See [_boolean_](g). diff --git a/docs/content/en/quick-reference/glossary/boolean.md b/docs/content/en/quick-reference/glossary/boolean.md new file mode 100644 index 0000000..e727c40 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/boolean.md @@ -0,0 +1,5 @@ +--- +title: boolean +--- + +A _boolean_ is a data type with two possible values, either `true` or `false`. diff --git a/docs/content/en/quick-reference/glossary/branch-bundle.md b/docs/content/en/quick-reference/glossary/branch-bundle.md new file mode 100644 index 0000000..d5688ba --- /dev/null +++ b/docs/content/en/quick-reference/glossary/branch-bundle.md @@ -0,0 +1,6 @@ +--- +title: branch bundle +reference: /content-management/page-bundles +--- + +A _branch bundle_ is a top-level content directory or any content directory containing an `_index.md` file. Analogous to a physical branch, a branch bundle may have descendants including [_leaf bundles_](g) and other branch bundles. A branch bundle may also contain [_page resources_](g) such as images. diff --git a/docs/content/en/quick-reference/glossary/branch.md b/docs/content/en/quick-reference/glossary/branch.md new file mode 100644 index 0000000..c4cb63f --- /dev/null +++ b/docs/content/en/quick-reference/glossary/branch.md @@ -0,0 +1,5 @@ +--- +title: branch +--- + +A _branch_ is a [_node_](g) with a [_page kind_](g) of `home`, `section`, `taxonomy`, or `term`. A branch may have descendants. diff --git a/docs/content/en/quick-reference/glossary/build-artifacts.md b/docs/content/en/quick-reference/glossary/build-artifacts.md new file mode 100644 index 0000000..c3c2dbd --- /dev/null +++ b/docs/content/en/quick-reference/glossary/build-artifacts.md @@ -0,0 +1,5 @@ +--- +title: build artifacts +--- + +The _build artifacts_ are the static files produced during the [_build_](g) process. These assets are stored in the `public` directory by default and represent the final, ready-to-deploy output of the project. diff --git a/docs/content/en/quick-reference/glossary/build.md b/docs/content/en/quick-reference/glossary/build.md new file mode 100644 index 0000000..4d4f73c --- /dev/null +++ b/docs/content/en/quick-reference/glossary/build.md @@ -0,0 +1,5 @@ +--- +title: build +--- + +To _build_ (verb) is to generate the static files for a [_project_](g), including HTML, images, CSS, and JavaScript. This process involves rendering templates, transforming resources, and resolving the matrix of [_language_](g), [_role_](g), and [_version_](g) defined in your project configuration. diff --git a/docs/content/en/quick-reference/glossary/bundle.md b/docs/content/en/quick-reference/glossary/bundle.md new file mode 100644 index 0000000..fddc28e --- /dev/null +++ b/docs/content/en/quick-reference/glossary/bundle.md @@ -0,0 +1,5 @@ +--- +title: bundle +--- + +See [_page bundle_](g). diff --git a/docs/content/en/quick-reference/glossary/cache.md b/docs/content/en/quick-reference/glossary/cache.md new file mode 100644 index 0000000..a86068e --- /dev/null +++ b/docs/content/en/quick-reference/glossary/cache.md @@ -0,0 +1,5 @@ +--- +title: cache +--- + +A _cache_ is a software component that stores data so that future requests for the same data are faster. diff --git a/docs/content/en/quick-reference/glossary/canonical-output-format.md b/docs/content/en/quick-reference/glossary/canonical-output-format.md new file mode 100644 index 0000000..d360083 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/canonical-output-format.md @@ -0,0 +1,14 @@ +--- +title: canonical output format +--- + +The _canonical output format_ is the [_output format_](g) for the current page where the format's [`rel`][] property is set to `canonical` in your project configuration, if such a format exists. If there is only one _output format_ for the current page, that is the _canonical output format_, regardless of whether the format's `rel` property is set to `canonical`. + + By default, `html` is the only predefined _output format_ with this setting; the `rel` property for all others is set to `alternate`. If two or more _output formats_ for the current page have their `rel` property set to `canonical`, the _canonical output format_ is the first one specified in: + + - The [`outputs`][outputs_front_matter] front matter field of the current page, or + - The [`outputs`][outputs_project_config] section of your project configuration for the current [_page kind_](g). + + [`rel`]: /configuration/output-formats/#rel + [outputs_front_matter]: /configuration/outputs/#outputs-per-page + [outputs_project_config]: /configuration/outputs/#outputs-per-page-kind diff --git a/docs/content/en/quick-reference/glossary/chain.md b/docs/content/en/quick-reference/glossary/chain.md new file mode 100644 index 0000000..c5d19b1 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/chain.md @@ -0,0 +1,5 @@ +--- +title: chain +--- + +To _chain_ (verb) is to connect one or more [_identifiers_](g) with a dot. An identifier can represent a [_method_](g), [_object_](g), or [_field_](g). For example, `.Site.Params.author.name` or `.Date.UTC.Hour`. diff --git a/docs/content/en/quick-reference/glossary/cicd.md b/docs/content/en/quick-reference/glossary/cicd.md new file mode 100644 index 0000000..594a60e --- /dev/null +++ b/docs/content/en/quick-reference/glossary/cicd.md @@ -0,0 +1,16 @@ +--- +title: CI/CD +params: + reference: https://en.wikipedia.org/wiki/CI/CD +--- + +The term _CI/CD_ is an abbreviation for Continuous Integration and Continuous Delivery or Continuous Deployment depending on context. + + Popular _CI/CD_ platforms for building and deploying Hugo sites include [Cloudflare][], [GitHub Pages][], [GitLab Pages][], [Netlify][], [Render][], and [Vercel][]. + + [Cloudflare]: /host-and-deploy/host-on-cloudflare/ + [GitHub Pages]: /host-and-deploy/host-on-github-pages/ + [GitLab Pages]: /host-and-deploy/host-on-gitlab-pages/ + [Netlify]: /host-and-deploy/host-on-netlify/ + [Render]: /host-and-deploy/host-on-render/ + [Vercel]: /host-and-deploy/host-on-vercel/ diff --git a/docs/content/en/quick-reference/glossary/cjk.md b/docs/content/en/quick-reference/glossary/cjk.md new file mode 100644 index 0000000..05a294d --- /dev/null +++ b/docs/content/en/quick-reference/glossary/cjk.md @@ -0,0 +1,5 @@ +--- +title: CJK +--- + +_CJK_ is a collective term for the Chinese, Japanese, and Korean languages. diff --git a/docs/content/en/quick-reference/glossary/cli.md b/docs/content/en/quick-reference/glossary/cli.md new file mode 100644 index 0000000..8f898e3 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/cli.md @@ -0,0 +1,5 @@ +--- +title: CLI +--- + +_CLI_ stands for command-line interface, a text-based method for interacting with computer programs or operating systems. diff --git a/docs/content/en/quick-reference/glossary/collection.md b/docs/content/en/quick-reference/glossary/collection.md new file mode 100644 index 0000000..30e1ef8 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/collection.md @@ -0,0 +1,5 @@ +--- +title: collection +--- + +A _collection_ is an [_array_](g), [_slice_](g), or [_map_](g). diff --git a/docs/content/en/quick-reference/glossary/component.md b/docs/content/en/quick-reference/glossary/component.md new file mode 100644 index 0000000..1843b18 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/component.md @@ -0,0 +1,16 @@ +--- +title: component +--- + +A _component_ is a collection of related files, housed within the [_unified file system_](g), that fulfills a specific function in building a Hugo [_project_](g). These components are categorized into seven types: [_archetypes_](g), assets, content, data, templates, [_translation tables_](g), and static files, and can be defined within the project or provided by [_modules_](g). Each component has a dedicated directory within the unified file system: + + Component|Directory within the unified file system + :--|:-- + archetypes|`archetypes` + assets|`assets` + content|`content` + data|`data` + templates|`layouts` + translation tables|`i18n` + static files|`static` + {class="!mt-0"} diff --git a/docs/content/en/quick-reference/glossary/content-adapter.md b/docs/content/en/quick-reference/glossary/content-adapter.md new file mode 100644 index 0000000..974e61d --- /dev/null +++ b/docs/content/en/quick-reference/glossary/content-adapter.md @@ -0,0 +1,6 @@ +--- +title: content adapter +reference: /content-management/content-adapters +--- + +A _content adapter_ is a template that dynamically creates pages when building a site. For example, use a content adapter to create pages from a remote data source such as JSON, TOML, YAML, or XML. diff --git a/docs/content/en/quick-reference/glossary/content-dimension.md b/docs/content/en/quick-reference/glossary/content-dimension.md new file mode 100644 index 0000000..7317740 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/content-dimension.md @@ -0,0 +1,5 @@ +--- +title: content dimension +--- + +See [_dimension_](g). diff --git a/docs/content/en/quick-reference/glossary/content-format.md b/docs/content/en/quick-reference/glossary/content-format.md new file mode 100644 index 0000000..ea459de --- /dev/null +++ b/docs/content/en/quick-reference/glossary/content-format.md @@ -0,0 +1,6 @@ +--- +title: content format +reference: /content-management/formats +--- + +A _content format_ is a markup language for creating content. Typically Markdown, but may also be HTML, AsciiDoc, Org, Pandoc, or reStructuredText. diff --git a/docs/content/en/quick-reference/glossary/content-type.md b/docs/content/en/quick-reference/glossary/content-type.md new file mode 100644 index 0000000..758800c --- /dev/null +++ b/docs/content/en/quick-reference/glossary/content-type.md @@ -0,0 +1,5 @@ +--- +title: content type +--- + +A _content type_ is a classification of content inferred from the top-level directory name or the `type` set in [_front matter_](g). Pages in the root of the `content` directory, including the home page, are of type "page". The content type is a contributing factor in the template lookup order and determines which [_archetype_](g) template to use when creating new content. diff --git a/docs/content/en/quick-reference/glossary/content-view.md b/docs/content/en/quick-reference/glossary/content-view.md new file mode 100644 index 0000000..3eeb42d --- /dev/null +++ b/docs/content/en/quick-reference/glossary/content-view.md @@ -0,0 +1,8 @@ +--- +title: content view +reference: /templates/types/#content-view +--- + +A _content view_ is a template called with the [`Render`][] method on a `Page` object. + + [`Render`]: /methods/page/render/ diff --git a/docs/content/en/quick-reference/glossary/context.md b/docs/content/en/quick-reference/glossary/context.md new file mode 100644 index 0000000..75afc70 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/context.md @@ -0,0 +1,6 @@ +--- +title: context +reference: /templates/introduction/#context +--- + +Represented by a dot (`.`) within a [_template action_](g), _context_ is the current location in a data structure. For example, while iterating over a [_collection_](g) of pages, the context within each iteration is the page's data structure. The context received by each template depends on template type and/or how it was called. diff --git a/docs/content/en/quick-reference/glossary/default-language.md b/docs/content/en/quick-reference/glossary/default-language.md new file mode 100644 index 0000000..5264343 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/default-language.md @@ -0,0 +1,9 @@ +--- +title: default language +--- + +The _default language_ is the value defined by the [`defaultContentLanguage`][] setting, falling back to the first language in the project, and finally to `en`. The first language is identified by the lowest [_weight_](g), using lexicographical order as the final fallback if weights are tied or undefined. + + See also: [_language_](g). + + [`defaultContentLanguage`]: /configuration/all/#defaultcontentlanguage diff --git a/docs/content/en/quick-reference/glossary/default-role.md b/docs/content/en/quick-reference/glossary/default-role.md new file mode 100644 index 0000000..f065189 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/default-role.md @@ -0,0 +1,9 @@ +--- +title: default role +--- + +The _default role_ is the value defined by the [`defaultContentRole`][] setting, falling back to the first role in the project, and finally to `guest`. The first role is identified by the lowest [_weight_](g), using lexicographical order as the final fallback if weights are tied or undefined. + + See also: [_role_](g). + + [`defaultContentRole`]: /configuration/all/#defaultcontentrole diff --git a/docs/content/en/quick-reference/glossary/default-site.md b/docs/content/en/quick-reference/glossary/default-site.md new file mode 100644 index 0000000..3acad31 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/default-site.md @@ -0,0 +1,5 @@ +--- +title: default site +--- + +The _default site_ is the [_site_](g) with the [_default language_](g), [_default version_](g), and [_default role_](g). diff --git a/docs/content/en/quick-reference/glossary/default-sort-order.md b/docs/content/en/quick-reference/glossary/default-sort-order.md new file mode 100644 index 0000000..152f0d6 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/default-sort-order.md @@ -0,0 +1,15 @@ +--- +title: default sort order +--- + +The _default sort order_ for [_page collections_](g), used when no other criteria are set, follows this priority: + + 1. [`weight`][] (ascending) + 1. [`date`][] (descending) + 1. [`linkTitle`][] falling back to [`title`][] (ascending) + 1. [logical path](g) (ascending) + + [`date`]: /content-management/front-matter/#date + [`linkTitle`]: /content-management/front-matter/#linktitle + [`title`]: /content-management/front-matter/#title + [`weight`]: /content-management/front-matter/#weight diff --git a/docs/content/en/quick-reference/glossary/default-version.md b/docs/content/en/quick-reference/glossary/default-version.md new file mode 100644 index 0000000..dc29047 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/default-version.md @@ -0,0 +1,9 @@ +--- +title: default version +--- + +The _default version_ is the value defined by the [`defaultContentVersion`][] setting, falling back to the first version in the project, and finally to `v1.0.0`. The first version is identified by the lowest [_weight_](g), using a descending semantic sort as the final fallback if weights are tied or undefined. + + See also: [_version_](g). + + [`defaultContentVersion`]: /configuration/all/#defaultcontentversion diff --git a/docs/content/en/quick-reference/glossary/dependency-graph.md b/docs/content/en/quick-reference/glossary/dependency-graph.md new file mode 100644 index 0000000..946e5b1 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/dependency-graph.md @@ -0,0 +1,5 @@ +--- +title: dependency graph +--- + +A _dependency graph_ visually represents the relationships between the [_modules_](g) used in a Hugo project. It shows how modules depend on each other, forming a network of dependencies. diff --git a/docs/content/en/quick-reference/glossary/dimension.md b/docs/content/en/quick-reference/glossary/dimension.md new file mode 100644 index 0000000..9db6d64 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/dimension.md @@ -0,0 +1,5 @@ +--- +title: dimension +--- + +A _dimension_ is a categorized axis of content variation that allows multiple variations of a logical page to exist simultaneously. The three dimensions are [_language_](g), [_role_](g), and [_version_](g). For example, a logical page may exist in 6 languages, 4 versions, and 2 roles. diff --git a/docs/content/en/quick-reference/glossary/duration.md b/docs/content/en/quick-reference/glossary/duration.md new file mode 100644 index 0000000..21fd3c8 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/duration.md @@ -0,0 +1,5 @@ +--- +title: duration +--- + +A _duration_ is a data type that represent a length of time, expressed using units such as seconds (represented by `s`), minutes (represented by `m`), and hours (represented by `h`). For example, `42s` means 42 seconds, `6m7s` means 6 minutes and 7 seconds, and `6h7m42s` means 6 hours, 7 minutes, and 42 seconds. diff --git a/docs/content/en/quick-reference/glossary/element.md b/docs/content/en/quick-reference/glossary/element.md new file mode 100644 index 0000000..39f5df6 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/element.md @@ -0,0 +1,5 @@ +--- +title: element +--- + +An _element_ is a member of a [_slice_](g) or [_array_](g). diff --git a/docs/content/en/quick-reference/glossary/embedded-template.md b/docs/content/en/quick-reference/glossary/embedded-template.md new file mode 100644 index 0000000..3a08716 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/embedded-template.md @@ -0,0 +1,5 @@ +--- +title: embedded template +--- + +An _embedded template_ is a built-in component within the Hugo application. This includes features like [_partials_](g), [_shortcodes_](g), and [_render hooks_](g) that provide pre-defined structures or functionalities for creating website content. diff --git a/docs/content/en/quick-reference/glossary/environment.md b/docs/content/en/quick-reference/glossary/environment.md new file mode 100644 index 0000000..0cfba95 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/environment.md @@ -0,0 +1,9 @@ +--- +title: environment +--- + +Typically one of `development`, `staging`, or `production`, each _environment_ may exhibit different behavior depending on configuration and template logic. For example, in a production environment you might minify and fingerprint CSS, but that probably doesn't make sense in a development environment. + + When running the built-in development server with the `hugo server` command, the environment is set to `development`. When building your project with the `hugo build` command, the environment is set to `production`. To override the environment value, use the `--environment` command line flag or the `HUGO_ENVIRONMENT` environment variable. + + To determine the current environment within a template, use the [`hugo.Environment`](/functions/hugo/environment/) function. diff --git a/docs/content/en/quick-reference/glossary/field.md b/docs/content/en/quick-reference/glossary/field.md new file mode 100644 index 0000000..a32eb3a --- /dev/null +++ b/docs/content/en/quick-reference/glossary/field.md @@ -0,0 +1,5 @@ +--- +title: field +--- + +A _field_ is a predefined key-value pair in front matter such as `date` or `title`. diff --git a/docs/content/en/quick-reference/glossary/flag.md b/docs/content/en/quick-reference/glossary/flag.md new file mode 100644 index 0000000..e7b6c57 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/flag.md @@ -0,0 +1,6 @@ +--- +title: flag +reference: /commands/hugo +--- + +A _flag_ is an option passed to a command-line program, beginning with one or two hyphens. diff --git a/docs/content/en/quick-reference/glossary/float.md b/docs/content/en/quick-reference/glossary/float.md new file mode 100644 index 0000000..86f2c8f --- /dev/null +++ b/docs/content/en/quick-reference/glossary/float.md @@ -0,0 +1,6 @@ +--- +title: float +alias: true +--- + +See [floating point](g). diff --git a/docs/content/en/quick-reference/glossary/floating-point.md b/docs/content/en/quick-reference/glossary/floating-point.md new file mode 100644 index 0000000..38ba9f0 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/floating-point.md @@ -0,0 +1,5 @@ +--- +title: floating point +--- + +The term _floating point_ refers to a numeric data type with a fractional component. For example, `3.14159`. diff --git a/docs/content/en/quick-reference/glossary/fragment.md b/docs/content/en/quick-reference/glossary/fragment.md new file mode 100644 index 0000000..57ef1b4 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/fragment.md @@ -0,0 +1,5 @@ +--- +title: fragment +--- + +A _fragment_ is the final segment of a URL, beginning with a hash (`#`) mark, that references an `id` attribute of an HTML element on the page. diff --git a/docs/content/en/quick-reference/glossary/front-matter.md b/docs/content/en/quick-reference/glossary/front-matter.md new file mode 100644 index 0000000..5a3cd30 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/front-matter.md @@ -0,0 +1,6 @@ +--- +title: front matter +reference: /content-management/front-matter +--- + +The term _front matter_ refers to the metadata at the beginning of each content page, separated from the content by format-specific delimiters. diff --git a/docs/content/en/quick-reference/glossary/function.md b/docs/content/en/quick-reference/glossary/function.md new file mode 100644 index 0000000..a7da52c --- /dev/null +++ b/docs/content/en/quick-reference/glossary/function.md @@ -0,0 +1,6 @@ +--- +title: function +reference: /functions +--- + +Used within a [_template action_](g), a _function_ takes one or more [_arguments_](g) and returns a value. Unlike [_methods_](g), functions are not associated with an [_object_](g). diff --git a/docs/content/en/quick-reference/glossary/glob-pattern.md b/docs/content/en/quick-reference/glossary/glob-pattern.md new file mode 100644 index 0000000..c57b452 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/glob-pattern.md @@ -0,0 +1,6 @@ +--- +title: glob pattern +reference: /quick-reference/glob-patterns/ +--- + +A _glob pattern_ is a pattern used to match sets of values. It is a shorthand for specifying multiple targets at once, making it easier to work with groups of data or configurations. diff --git a/docs/content/en/quick-reference/glossary/glob-slice.md b/docs/content/en/quick-reference/glossary/glob-slice.md new file mode 100644 index 0000000..df37718 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/glob-slice.md @@ -0,0 +1,16 @@ +--- +title: glob slice +--- + +A _glob slice_ is a [_slice_](g) of [_glob patterns_](g). Within the _slice_, a _glob_ can be negated by prefixing it with an exclamation mark (`!`) and one space. Matches in negated patterns short-circuit the evaluation of the rest of the _slice_, and are useful for early coarse grained exclusions. + + The following example illustrates how to use _glob slices_ to define a [_sites matrix_](g) in your project configuration: + + ```toml + [sites.matrix] + languages = [ "! no", "**" ] + versions = [ "! v1.2.3", "v1.*.*", "v2.*.*" ] + roles = [ "{member, guest}" ] + ``` + + The `versions` example above evaluates as: `(not v1.2.3) AND (v1.*.* OR v2.*.*)`. diff --git a/docs/content/en/quick-reference/glossary/global-resource.md b/docs/content/en/quick-reference/glossary/global-resource.md new file mode 100644 index 0000000..a4df65f --- /dev/null +++ b/docs/content/en/quick-reference/glossary/global-resource.md @@ -0,0 +1,5 @@ +--- +title: global resource +--- + +A _global resource_ is file within the `assets` directory, or within any directory mounted to the `assets` directory. diff --git a/docs/content/en/quick-reference/glossary/headless-bundle.md b/docs/content/en/quick-reference/glossary/headless-bundle.md new file mode 100644 index 0000000..ac7bf79 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/headless-bundle.md @@ -0,0 +1,6 @@ +--- +title: headless bundle +reference: /content-management/build-options/ +--- + +A _headless bundle_ is an unpublished [_leaf bundle_](g) or an unpublished [_branch bundle_](g) whose content and resources you can include in other pages. diff --git a/docs/content/en/quick-reference/glossary/i18n.md b/docs/content/en/quick-reference/glossary/i18n.md new file mode 100644 index 0000000..168828a --- /dev/null +++ b/docs/content/en/quick-reference/glossary/i18n.md @@ -0,0 +1,5 @@ +--- +title: i18n +--- + +See [_internationalization_](g). diff --git a/docs/content/en/quick-reference/glossary/iana.md b/docs/content/en/quick-reference/glossary/iana.md new file mode 100644 index 0000000..89497f7 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/iana.md @@ -0,0 +1,6 @@ +--- +title: IANA +reference: https://www.iana.org/about +--- + +_IANA_ is an abbreviation for the Internet Assigned Numbers Authority, a non-profit organization that manages the allocation of global IP addresses, autonomous system numbers, DNS root zone, media types, and other Internet Protocol-related resources. diff --git a/docs/content/en/quick-reference/glossary/identifier.md b/docs/content/en/quick-reference/glossary/identifier.md new file mode 100644 index 0000000..1dcb530 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/identifier.md @@ -0,0 +1,7 @@ +--- +title: identifier +--- + +An _identifier_ is a string that represents a variable, method, object, or field. It must conform to Go's [language specification][], beginning with a letter or underscore, followed by zero or more letters, digits, or underscores. + + [language specification]: https://go.dev/ref/spec#Identifiers diff --git a/docs/content/en/quick-reference/glossary/int.md b/docs/content/en/quick-reference/glossary/int.md new file mode 100644 index 0000000..0896f1c --- /dev/null +++ b/docs/content/en/quick-reference/glossary/int.md @@ -0,0 +1,5 @@ +--- +title: int +--- + +See [_integer_](g). diff --git a/docs/content/en/quick-reference/glossary/integer.md b/docs/content/en/quick-reference/glossary/integer.md new file mode 100644 index 0000000..0af8213 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/integer.md @@ -0,0 +1,5 @@ +--- +title: integer +--- + +An _integer_ is a numeric data type without a fractional component. For example, `42`. diff --git a/docs/content/en/quick-reference/glossary/interleave.md b/docs/content/en/quick-reference/glossary/interleave.md new file mode 100644 index 0000000..077ff7e --- /dev/null +++ b/docs/content/en/quick-reference/glossary/interleave.md @@ -0,0 +1,5 @@ +--- +title: interleave +--- + +To _interleave_ (verb) is to insert a string at the beginning, the end, and between every character of another string. diff --git a/docs/content/en/quick-reference/glossary/internationalization.md b/docs/content/en/quick-reference/glossary/internationalization.md new file mode 100644 index 0000000..4e8b019 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/internationalization.md @@ -0,0 +1,5 @@ +--- +title: internationalization +--- + +The term _internationalization_ refers to software design and development efforts that enable [_localization_](g). diff --git a/docs/content/en/quick-reference/glossary/interpreted-string-literal.md b/docs/content/en/quick-reference/glossary/interpreted-string-literal.md new file mode 100644 index 0000000..fa92c97 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/interpreted-string-literal.md @@ -0,0 +1,6 @@ +--- +title: interpreted string literal +reference: https://go.dev/ref/spec#String_literals +--- + +An _interpreted string literal_ is a character sequence between double quotes, as in `"foo"`. Within the quotes, any character may appear except a newline and an unescaped double quote. The text between the quotes forms the value of the literal, with backslash escapes interpreted. diff --git a/docs/content/en/quick-reference/glossary/interval.md b/docs/content/en/quick-reference/glossary/interval.md new file mode 100644 index 0000000..a7ed8a6 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/interval.md @@ -0,0 +1,11 @@ +--- +title: interval +--- + +An [_interval_](https://en.wikipedia.org/wiki/Interval_(mathematics)) is a range of numbers between two endpoints: closed, open, or half-open. + + - A _closed interval_, denoted by brackets, includes its endpoints. For example, [0, 1] is the interval where `0 <= x <= 1`. + + - An _open interval_, denoted by parentheses, excludes its endpoints. For example, (0, 1) is the interval where `0 < x < 1`. + + - A _half-open interval_ includes only one of its endpoints. For example, (0, 1] is the _left-open_ interval where `0 < x <= 1`, while [0, 1) is the _right-open_ interval where `0 <= x < 1`. diff --git a/docs/content/en/quick-reference/glossary/kind.md b/docs/content/en/quick-reference/glossary/kind.md new file mode 100644 index 0000000..a214dfb --- /dev/null +++ b/docs/content/en/quick-reference/glossary/kind.md @@ -0,0 +1,5 @@ +--- +title: kind +--- + +See [_page kind_](g). diff --git a/docs/content/en/quick-reference/glossary/l10n.md b/docs/content/en/quick-reference/glossary/l10n.md new file mode 100644 index 0000000..013d0ab --- /dev/null +++ b/docs/content/en/quick-reference/glossary/l10n.md @@ -0,0 +1,5 @@ +--- +title: l10n +--- + +See [_localization_](g). diff --git a/docs/content/en/quick-reference/glossary/language.md b/docs/content/en/quick-reference/glossary/language.md new file mode 100644 index 0000000..551dc69 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/language.md @@ -0,0 +1,7 @@ +--- +title: language +--- + +A _language_ is a [_dimension_](g) that facilitates the localization and internationalization of content. While [_version_](g) focuses on lifecycle and [_role_](g) focuses on audience, the language dimension allows a logical page to be represented in different locales across the project. + + See also: [_default language_](g). diff --git a/docs/content/en/quick-reference/glossary/layout.md b/docs/content/en/quick-reference/glossary/layout.md new file mode 100644 index 0000000..a4e6b33 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/layout.md @@ -0,0 +1,5 @@ +--- +title: layout +--- + +See [_template_](g). diff --git a/docs/content/en/quick-reference/glossary/leaf-bundle.md b/docs/content/en/quick-reference/glossary/leaf-bundle.md new file mode 100644 index 0000000..aa41384 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/leaf-bundle.md @@ -0,0 +1,6 @@ +--- +title: leaf bundle +reference: /content-management/page-bundles/ +--- + +A _leaf bundle_ is a directory that contains an `index.md` file and zero or more [_resources_](g). Analogous to a physical leaf, a leaf bundle is at the end of a [_branch bundle_](g). It has no descendants. diff --git a/docs/content/en/quick-reference/glossary/lexer.md b/docs/content/en/quick-reference/glossary/lexer.md new file mode 100644 index 0000000..a462231 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/lexer.md @@ -0,0 +1,5 @@ +--- +title: lexer +--- + +A _lexer_ is a software component that identifies keywords, identifiers, operators, numbers, and other basic building blocks of a programming language within the input text. diff --git a/docs/content/en/quick-reference/glossary/list-page.md b/docs/content/en/quick-reference/glossary/list-page.md new file mode 100644 index 0000000..d18efcf --- /dev/null +++ b/docs/content/en/quick-reference/glossary/list-page.md @@ -0,0 +1,5 @@ +--- +title: list page +--- + +A list page is any [_page kind_](g) that receives a page [_collection_](g) in [_context_](g). This includes the home page, [_section pages_](g), [_taxonomy pages_](g), and [_term pages_](g). diff --git a/docs/content/en/quick-reference/glossary/localization.md b/docs/content/en/quick-reference/glossary/localization.md new file mode 100644 index 0000000..01f4506 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/localization.md @@ -0,0 +1,6 @@ +--- +title: localization +reference: /content-management/multilingual/ +--- + +The term _localization_ refers to the process of adapting a site to meet language and regional requirements. This includes translations, date formats, number formats, currency formats, and collation order. diff --git a/docs/content/en/quick-reference/glossary/logical-path.md b/docs/content/en/quick-reference/glossary/logical-path.md new file mode 100644 index 0000000..37874b9 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/logical-path.md @@ -0,0 +1,8 @@ +--- +title: logical path +reference: /methods/page/path/#logical-tree +--- + +A _logical path_ is a page or page resource identifier derived from the file path, excluding its extension and language identifier. This value is neither a file path nor a URL. Starting with a file path relative to the `content` directory, Hugo determines the logical path by stripping the file extension and language identifier, converting to lower case, then replacing spaces with hyphens. Path segments are separated with a slash (`/`). + + When used to describe content, the logical path is the path between two [_nodes_](g) in the [_logical tree_](g), either relative to each other or absolute from the root of the tree. diff --git a/docs/content/en/quick-reference/glossary/logical-tree.md b/docs/content/en/quick-reference/glossary/logical-tree.md new file mode 100644 index 0000000..1908d76 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/logical-tree.md @@ -0,0 +1,6 @@ +--- +title: logical tree +reference: /methods/page/path/#logical-tree +--- + +A _logical tree_ is the hierarchy of [_nodes_](g) in a Hugo site, organized by their [_logical paths_](g). Just as file paths form a file tree, logical paths form a logical tree. Unlike the file tree, the logical tree abstracts away file extensions, language identifiers, and physical directory structure, providing a consistent way to address and navigate content. diff --git a/docs/content/en/quick-reference/glossary/map.md b/docs/content/en/quick-reference/glossary/map.md new file mode 100644 index 0000000..b4605d2 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/map.md @@ -0,0 +1,6 @@ +--- +title: map +reference: https://go.dev/ref/spec#Map_types +--- + +A _map_ is an unordered group of elements, each indexed by a unique key. diff --git a/docs/content/en/quick-reference/glossary/markdown-attribute.md b/docs/content/en/quick-reference/glossary/markdown-attribute.md new file mode 100644 index 0000000..f5c57c7 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/markdown-attribute.md @@ -0,0 +1,6 @@ +--- +title: Markdown attribute +reference: /content-management/markdown-attributes/ +--- + +A _Markdown attribute_ is a key-value pair attached to a Markdown element. These attributes are commonly used to add HTML attributes, like `class` and `id`, to the element when it's rendered into HTML. They provide a way to extend the basic Markdown syntax and add more semantic meaning or styling hooks to your content. diff --git a/docs/content/en/quick-reference/glossary/marshal.md b/docs/content/en/quick-reference/glossary/marshal.md new file mode 100644 index 0000000..ce7d769 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/marshal.md @@ -0,0 +1,6 @@ +--- +title: marshal +reference: /functions/transform/remarshal/ +--- + +To _marshal_ (verb) is to transform a data structure into a serialized object. For example, transforming a [_map_](g) into a JSON string. diff --git a/docs/content/en/quick-reference/glossary/media-type.md b/docs/content/en/quick-reference/glossary/media-type.md new file mode 100644 index 0000000..f10c6dc --- /dev/null +++ b/docs/content/en/quick-reference/glossary/media-type.md @@ -0,0 +1,6 @@ +--- +title: media type +reference: /configuration/media-types/ +--- + +A _media type_ (formerly known as a MIME type) is a two-part identifier for file formats and transmitted content. For example, the media type for HTML content is `text/html`. diff --git a/docs/content/en/quick-reference/glossary/method.md b/docs/content/en/quick-reference/glossary/method.md new file mode 100644 index 0000000..634cd4b --- /dev/null +++ b/docs/content/en/quick-reference/glossary/method.md @@ -0,0 +1,5 @@ +--- +title: method +--- + +Used within a [_template action_](g) and associated with an [_object_](g), a _method_ takes zero or more [_arguments_](g) and either returns a value or performs an action. For example, `IsHome` is a method on a `Page` object which returns `true` if the current page is the home page. See also [_function_](g). diff --git a/docs/content/en/quick-reference/glossary/module.md b/docs/content/en/quick-reference/glossary/module.md new file mode 100644 index 0000000..30ec49e --- /dev/null +++ b/docs/content/en/quick-reference/glossary/module.md @@ -0,0 +1,5 @@ +--- +title: module +--- + +A _module_ is a packaged combination of [_components_](g) which may include [_archetypes_](g), assets, content, data, templates, [_translation tables_](g), and static files. A module may be a [_theme_](g), a complete project, or a smaller collection of one or more components. diff --git a/docs/content/en/quick-reference/glossary/mount.md b/docs/content/en/quick-reference/glossary/mount.md new file mode 100644 index 0000000..1608e75 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/mount.md @@ -0,0 +1,7 @@ +--- +title: mount +params: + reference: /configuration/module +--- + +A _mount_ is a configuration object that maps a file path (source) to a [_component_](g) path (target) within Hugo's [_unified file system_](g). diff --git a/docs/content/en/quick-reference/glossary/node.md b/docs/content/en/quick-reference/glossary/node.md new file mode 100644 index 0000000..6033853 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/node.md @@ -0,0 +1,5 @@ +--- +title: node +--- + +A _node_ is any page in the [_logical tree_](g). A node may be a [_branch_](g), with a [_page kind_](g) of `home`, `section`, `taxonomy`, or `term`, or a regular page with a page kind of `page`. diff --git a/docs/content/en/quick-reference/glossary/noop.md b/docs/content/en/quick-reference/glossary/noop.md new file mode 100644 index 0000000..bd159bb --- /dev/null +++ b/docs/content/en/quick-reference/glossary/noop.md @@ -0,0 +1,5 @@ +--- +title: noop +--- + +An abbreviated form of "no operation", a _noop_ is a statement that does nothing. diff --git a/docs/content/en/quick-reference/glossary/object.md b/docs/content/en/quick-reference/glossary/object.md new file mode 100644 index 0000000..216609d --- /dev/null +++ b/docs/content/en/quick-reference/glossary/object.md @@ -0,0 +1,5 @@ +--- +title: object +--- + +An _object_ is a data structure with or without associated [_methods_](g). diff --git a/docs/content/en/quick-reference/glossary/ordered-taxonomy.md b/docs/content/en/quick-reference/glossary/ordered-taxonomy.md new file mode 100644 index 0000000..e187518 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/ordered-taxonomy.md @@ -0,0 +1,8 @@ +--- +title: ordered taxonomy +--- + +Created by invoking the [`Alphabetical`][] or [`ByCount`][] method on a [`Taxonomy`](g) object, which is a [_map_](g), an _ordered taxonomy_ is a [_slice_](g), where each element is an object that contains the [_term_](g) and a slice of its [_weighted pages_](g). + + [`Alphabetical`]: /methods/taxonomy/alphabetical/ + [`ByCount`]: /methods/taxonomy/bycount/ diff --git a/docs/content/en/quick-reference/glossary/output-format.md b/docs/content/en/quick-reference/glossary/output-format.md new file mode 100644 index 0000000..aa5ddbd --- /dev/null +++ b/docs/content/en/quick-reference/glossary/output-format.md @@ -0,0 +1,6 @@ +--- +title: output format +reference: /configuration/output-formats/ +--- + +An _output format_ is a collection of settings that defines how Hugo renders a file when building a site. For example, `html`, `json`, and `rss` are built-in output formats. You can create multiple output formats and control their generation based on [_page kind_](g), or by enabling one or more output formats for specific pages. diff --git a/docs/content/en/quick-reference/glossary/page-bundle.md b/docs/content/en/quick-reference/glossary/page-bundle.md new file mode 100644 index 0000000..af76da2 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/page-bundle.md @@ -0,0 +1,6 @@ +--- +title: page bundle +reference: /content-management/page-bundles/ +--- + +A _page bundle_ is a directory that encapsulates both content and associated [_resources_](g). There are two types of page bundles: [_leaf bundles_](g) and [_branch bundles_](g). diff --git a/docs/content/en/quick-reference/glossary/page-collection.md b/docs/content/en/quick-reference/glossary/page-collection.md new file mode 100644 index 0000000..f078ecf --- /dev/null +++ b/docs/content/en/quick-reference/glossary/page-collection.md @@ -0,0 +1,5 @@ +--- +title: page collection +--- + +A _page collection_ is a slice of `Page` objects. diff --git a/docs/content/en/quick-reference/glossary/page-kind.md b/docs/content/en/quick-reference/glossary/page-kind.md new file mode 100644 index 0000000..ee47fe0 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/page-kind.md @@ -0,0 +1,6 @@ +--- +title: page kind +reference: /methods/page/kind/ +--- + +A _page kind_ is a classification of pages, one of `home`, `page`, `section`, `taxonomy`, or `term`. diff --git a/docs/content/en/quick-reference/glossary/page-matcher.md b/docs/content/en/quick-reference/glossary/page-matcher.md new file mode 100644 index 0000000..ec5b23e --- /dev/null +++ b/docs/content/en/quick-reference/glossary/page-matcher.md @@ -0,0 +1,8 @@ +--- +title: page matcher +--- + +A _page matcher_ is a set of criteria used to filter pages by [_logical path_](g), [_page kind_](g), [_environment_](g), or [_site_](g). For example, use page matchers when configuring [cascade][] and [permalink][] targets. + + [cascade]: /configuration/cascade/ + [permalink]: /configuration/permalinks/ diff --git a/docs/content/en/quick-reference/glossary/page-relative.md b/docs/content/en/quick-reference/glossary/page-relative.md new file mode 100644 index 0000000..d12327b --- /dev/null +++ b/docs/content/en/quick-reference/glossary/page-relative.md @@ -0,0 +1,7 @@ +--- +title: page-relative +--- + +A _page-relative_ path is resolved relative to the current page's location in the content hierarchy. These paths do not begin with a leading slash. Examples include `old-name`, `./old-name`, and `../old-name`. + + See also: [_site-relative_](g), [_server-relative_](g). diff --git a/docs/content/en/quick-reference/glossary/page-resource.md b/docs/content/en/quick-reference/glossary/page-resource.md new file mode 100644 index 0000000..dab119e --- /dev/null +++ b/docs/content/en/quick-reference/glossary/page-resource.md @@ -0,0 +1,5 @@ +--- +title: page resource +--- + +A _page resource_ is a file within a [_page bundle_](g). diff --git a/docs/content/en/quick-reference/glossary/pager.md b/docs/content/en/quick-reference/glossary/pager.md new file mode 100644 index 0000000..f58874e --- /dev/null +++ b/docs/content/en/quick-reference/glossary/pager.md @@ -0,0 +1,5 @@ +--- +title: pager +--- + +Created during [_pagination_](g), a _pager_ contains a subset of a list page and navigation links to other pagers. diff --git a/docs/content/en/quick-reference/glossary/paginate.md b/docs/content/en/quick-reference/glossary/paginate.md new file mode 100644 index 0000000..1caf9a0 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/paginate.md @@ -0,0 +1,5 @@ +--- +title: paginate +--- + +To _paginate_ (verb) is to split a list page into two or more subsets. diff --git a/docs/content/en/quick-reference/glossary/pagination.md b/docs/content/en/quick-reference/glossary/pagination.md new file mode 100644 index 0000000..136341d --- /dev/null +++ b/docs/content/en/quick-reference/glossary/pagination.md @@ -0,0 +1,6 @@ +--- +title: pagination +reference: /templates/pagination +--- + +The term _pagination_ refers to the process of [_paginating_](g) a list page. diff --git a/docs/content/en/quick-reference/glossary/paginator.md b/docs/content/en/quick-reference/glossary/paginator.md new file mode 100644 index 0000000..4d19734 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/paginator.md @@ -0,0 +1,5 @@ +--- +title: paginator +--- + +A _paginator_ is a collection of [_pagers_](g). diff --git a/docs/content/en/quick-reference/glossary/parameter.md b/docs/content/en/quick-reference/glossary/parameter.md new file mode 100644 index 0000000..f1a45ea --- /dev/null +++ b/docs/content/en/quick-reference/glossary/parameter.md @@ -0,0 +1,5 @@ +--- +title: parameter +--- + +A _parameter_ is typically a user-defined key-value pair at the site or page level, but may also refer to a configuration setting or an [_argument_](g). diff --git a/docs/content/en/quick-reference/glossary/partial-decorator.md b/docs/content/en/quick-reference/glossary/partial-decorator.md new file mode 100644 index 0000000..149e17e --- /dev/null +++ b/docs/content/en/quick-reference/glossary/partial-decorator.md @@ -0,0 +1,8 @@ +--- +title: partial decorator +reference: /templates/partial-decorators/ +--- + +A _partial decorator_ is specific type of [_partial_](g) that functions as a [_wrapper component_](g). While a standard partial simply renders data within a fixed template, a decorator uses composition to enclose an entire block of content. It utilizes the [`templates.Inner`][] function as a placeholder to define exactly where that external content should be injected within the wrapper's layout. + + [`templates.Inner`]: /functions/templates/inner/ diff --git a/docs/content/en/quick-reference/glossary/partial.md b/docs/content/en/quick-reference/glossary/partial.md new file mode 100644 index 0000000..a5dd5e5 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/partial.md @@ -0,0 +1,5 @@ +--- +title: partial +--- + +A _partial_ is a [_template_](g) called from any other template including [_shortcodes_](g), [render hooks](g), and other partials. A partial either renders something or returns something. A partial can also call itself, for example, to [_walk_](g) a data structure. diff --git a/docs/content/en/quick-reference/glossary/permalink.md b/docs/content/en/quick-reference/glossary/permalink.md new file mode 100644 index 0000000..45651de --- /dev/null +++ b/docs/content/en/quick-reference/glossary/permalink.md @@ -0,0 +1,5 @@ +--- +title: permalink +--- + +A _permalink_ is the absolute URL of a published resource or a rendered page, including scheme and host. diff --git a/docs/content/en/quick-reference/glossary/pipe.md b/docs/content/en/quick-reference/glossary/pipe.md new file mode 100644 index 0000000..f587eec --- /dev/null +++ b/docs/content/en/quick-reference/glossary/pipe.md @@ -0,0 +1,5 @@ +--- +title: pipe +--- + +See [_pipeline_](g). diff --git a/docs/content/en/quick-reference/glossary/pipeline.md b/docs/content/en/quick-reference/glossary/pipeline.md new file mode 100644 index 0000000..6dab525 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/pipeline.md @@ -0,0 +1,7 @@ +--- +title: pipeline +--- + +Within a [_template action_](g), a _pipeline_ is a possibly chained sequence of values, [_function_](g) calls, or [_method_](g) calls. Functions and methods in the pipeline may take multiple [_arguments_](g). + + A pipeline may be chained by separating a sequence of commands with pipeline characters (`|`). In a chained pipeline, the result of each command is passed as the last argument to the following command. The output of the final command in the pipeline is the value of the pipeline. diff --git a/docs/content/en/quick-reference/glossary/pretty-url.md b/docs/content/en/quick-reference/glossary/pretty-url.md new file mode 100644 index 0000000..b15ef9f --- /dev/null +++ b/docs/content/en/quick-reference/glossary/pretty-url.md @@ -0,0 +1,5 @@ +--- +title: pretty URL +--- + +A _pretty URL_ is a URL that does not include a file extension. diff --git a/docs/content/en/quick-reference/glossary/primary-output-format.md b/docs/content/en/quick-reference/glossary/primary-output-format.md new file mode 100644 index 0000000..fdad677 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/primary-output-format.md @@ -0,0 +1,10 @@ +--- +title: primary output format +details: /configuration/outputs/ +--- + +A _primary output format_ defines the default URL returned by the [`Permalink`][] and [`RelPermalink`][] methods for a given [_page kind_](g). It is specified as the first entry within the [outputs configuration][] for that page kind. + + [`Permalink`]: /methods/page/permalink/ + [`RelPermalink`]: /methods/page/relpermalink/ + [outputs configuration]: /configuration/outputs/ diff --git a/docs/content/en/quick-reference/glossary/processable-image.md b/docs/content/en/quick-reference/glossary/processable-image.md new file mode 100644 index 0000000..f0e5389 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/processable-image.md @@ -0,0 +1,20 @@ +--- +title: processable image +--- + +A _processable image_ is an image file characterized by one of the following [_media types_](g): + + - `image/avif` + - `image/bmp` + - `image/gif` + - `image/jpeg` + - `image/png` + - `image/tiff` + - `image/webp` + + Hugo can decode and encode these image formats, allowing you to use any of the [resource methods][] applicable to images such as `Width`, `Height`, `Crop`, `Fill`, `Fit`, `Filter`, `Process`, `Resize`, etc. + + Use the [`reflect.IsImageResourceProcessable`][] function to determine if an image can be processed. + + [`reflect.IsImageResourceProcessable`]: /functions/reflect/isimageresourceprocessable/ + [resource methods]: /methods/resource/ diff --git a/docs/content/en/quick-reference/glossary/project.md b/docs/content/en/quick-reference/glossary/project.md new file mode 100644 index 0000000..86a3f63 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/project.md @@ -0,0 +1,5 @@ +--- +title: project +--- + +A _project_ is a collection of [_components_](g) used to generate one or more [sites](g). While a project may consist of only a single site, Hugo allows a single project to generate a matrix of sites based on [_language_](g), [role](g), and [_version_](g). The project serves as the parent container for the common assets and logic used across all sites within the build. diff --git a/docs/content/en/quick-reference/glossary/publish.md b/docs/content/en/quick-reference/glossary/publish.md new file mode 100644 index 0000000..743e87a --- /dev/null +++ b/docs/content/en/quick-reference/glossary/publish.md @@ -0,0 +1,5 @@ +--- +title: publish +--- + +See [_build_](g). diff --git a/docs/content/en/quick-reference/glossary/raw-string-literal.md b/docs/content/en/quick-reference/glossary/raw-string-literal.md new file mode 100644 index 0000000..056c0e6 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/raw-string-literal.md @@ -0,0 +1,6 @@ +--- +title: raw string literal +reference: https://go.dev/ref/spec#String_literals +--- + +A _raw string literal_ is a character sequence between backticks, as in `` `bar` ``. Within the backticks, any character may appear except a backtick. Backslashes have no special meaning and the string may contain newlines. Carriage return characters (`\r`) inside raw string literals are discarded from the raw string value. diff --git a/docs/content/en/quick-reference/glossary/regular-expression.md b/docs/content/en/quick-reference/glossary/regular-expression.md new file mode 100644 index 0000000..4998498 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/regular-expression.md @@ -0,0 +1,7 @@ +--- +title: regular expression +--- + +A _regular expression_, also known as a _regex_, is a sequence of characters that defines a search pattern. Use the [RE2 syntax][] when defining regular expressions in your templates or in your project configuration. + + [RE2 syntax]: https://github.com/google/re2/wiki/syntax diff --git a/docs/content/en/quick-reference/glossary/regular-page.md b/docs/content/en/quick-reference/glossary/regular-page.md new file mode 100644 index 0000000..5ff0419 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/regular-page.md @@ -0,0 +1,5 @@ +--- +title: regular page +--- + +A _regular page_ is a page with the `page` [_page kind_](g). See also [_section page_](g). diff --git a/docs/content/en/quick-reference/glossary/relative-permalink.md b/docs/content/en/quick-reference/glossary/relative-permalink.md new file mode 100644 index 0000000..a272133 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/relative-permalink.md @@ -0,0 +1,5 @@ +--- +title: relative permalink +--- + +A _relative permalink_ is the host-relative URL of a published resource or a rendered page. diff --git a/docs/content/en/quick-reference/glossary/remote-resource.md b/docs/content/en/quick-reference/glossary/remote-resource.md new file mode 100644 index 0000000..1637497 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/remote-resource.md @@ -0,0 +1,5 @@ +--- +title: remote resource +--- + +A _remote resource_ is a file on a remote server, accessible via HTTP or HTTPS. diff --git a/docs/content/en/quick-reference/glossary/render-hook.md b/docs/content/en/quick-reference/glossary/render-hook.md new file mode 100644 index 0000000..2cdb98e --- /dev/null +++ b/docs/content/en/quick-reference/glossary/render-hook.md @@ -0,0 +1,6 @@ +--- +title: render hook +reference: /render-hooks +--- + +A _render hook_ is a [_template_](g) that overrides standard Markdown rendering. diff --git a/docs/content/en/quick-reference/glossary/resource-type.md b/docs/content/en/quick-reference/glossary/resource-type.md new file mode 100644 index 0000000..b10cd59 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/resource-type.md @@ -0,0 +1,5 @@ +--- +title: resource type +--- + +A _resource type_ is the main type of a resource's [_media type_](g). Content files such as Markdown, HTML, AsciiDoc, Pandoc, reStructuredText, and Emacs Org Mode have resource type `page`. Other resource types include `image`, `text`, `video`, and others. Retrieve the resource type using the [`ResourceType`](/methods/resource/resourcetype/) method on a `Resource` object. diff --git a/docs/content/en/quick-reference/glossary/resource.md b/docs/content/en/quick-reference/glossary/resource.md new file mode 100644 index 0000000..0864bc4 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/resource.md @@ -0,0 +1,7 @@ +--- +title: resource +--- + +A _resource_ is any file consumed by the build process to augment or generate content, structure, behavior, or presentation. For example: images, videos, content snippets, CSS, Sass, JavaScript, and data. + + Hugo supports three types of resources: [_global resources_](g), [_page resources_](g), and [_remote resources_](g). diff --git a/docs/content/en/quick-reference/glossary/role.md b/docs/content/en/quick-reference/glossary/role.md new file mode 100644 index 0000000..f491d36 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/role.md @@ -0,0 +1,7 @@ +--- +title: role +--- + +A _role_ is a [_dimension_](g) that allows a logical page to be served in different forms depending on the target audience. While [_language_](g) focuses on localization and [_version_](g) focuses on lifecycle, the role dimension allows a project to generate variations of a page without duplicating content. + + See also: [_default role_](g). diff --git a/docs/content/en/quick-reference/glossary/rune-literal.md b/docs/content/en/quick-reference/glossary/rune-literal.md new file mode 100644 index 0000000..8a19b48 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/rune-literal.md @@ -0,0 +1,10 @@ +--- +title: rune literal +reference: https://go.dev/ref/spec#Rune_literals +--- + +A _rune literal_ is the textual representation of a [_rune_](g) within a [_template_](g). It consists of a character sequence enclosed in single quotes, such as `'x'`, `'\n'`, or `'ü'`. + + Unlike [_interpreted string literals_](g) or [_raw string literals_](g), which represent a sequence of characters, a _rune literal_ represents a single [_integer_](g) value identifying a Unicode [code point][]. Within the quotes, any character may appear except a newline or an unescaped single quote. Multi-character sequences starting with a backslash (`\`) can be used to encode specific values, such as `\n` for a newline or `\u00FC` for the letter `ü`. + + [code point]: https://en.wikipedia.org/wiki/Code_point diff --git a/docs/content/en/quick-reference/glossary/rune.md b/docs/content/en/quick-reference/glossary/rune.md new file mode 100644 index 0000000..74dfa4b --- /dev/null +++ b/docs/content/en/quick-reference/glossary/rune.md @@ -0,0 +1,11 @@ +--- +title: rune +--- + +A _rune_ is a way to represent a single character as a number. In Hugo and Go, text is stored as a sequence of bytes. However, while a basic letter like `x` uses only one byte, a single character such as the German `ü` is made up of multiple bytes. A _rune_ represents the entire character as one single value, no matter how many bytes it takes to store it. + + Technically, a _rune_ is just another name for a 32-bit [_integer_](g). It stores the Unicode [code point][], which is the official number assigned to that specific character. + + When you want to manipulate text character-by-character rather than by raw data size, you are working with _runes_. You write a _rune_ in a [_template_](g) using a [_rune literal_](g), such as `'x'`, `'\n'`, or `'ü'`. + + [code point]: https://en.wikipedia.org/wiki/Code_point diff --git a/docs/content/en/quick-reference/glossary/scalar.md b/docs/content/en/quick-reference/glossary/scalar.md new file mode 100644 index 0000000..63ae27d --- /dev/null +++ b/docs/content/en/quick-reference/glossary/scalar.md @@ -0,0 +1,5 @@ +--- +title: scalar +--- + +A _scalar_ is a single value, one of [_string_](g), [_integer_](g), [floating point](g), or [_boolean_](g). diff --git a/docs/content/en/quick-reference/glossary/scope.md b/docs/content/en/quick-reference/glossary/scope.md new file mode 100644 index 0000000..5230340 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/scope.md @@ -0,0 +1,5 @@ +--- +title: scope +--- + +The term _scope_ refers to the specific region of code where a [_variable_](g) or [_object_](g) is accessible. For example, a variable initialized in one [_template_](g) is not available within another. diff --git a/docs/content/en/quick-reference/glossary/section-page.md b/docs/content/en/quick-reference/glossary/section-page.md new file mode 100644 index 0000000..d61902e --- /dev/null +++ b/docs/content/en/quick-reference/glossary/section-page.md @@ -0,0 +1,5 @@ +--- +title: section page +--- + +A _section page_ is a page with the `section` [_page kind_](g). Typically a listing of [_regular pages_](g) and/or other section pages within the current [_section_](g). diff --git a/docs/content/en/quick-reference/glossary/section.md b/docs/content/en/quick-reference/glossary/section.md new file mode 100644 index 0000000..45d1203 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/section.md @@ -0,0 +1,5 @@ +--- +title: section +--- + +A _section_ is a top-level content directory or any content directory containing an `_index.md` file. diff --git a/docs/content/en/quick-reference/glossary/seed.md b/docs/content/en/quick-reference/glossary/seed.md new file mode 100644 index 0000000..bbb6e48 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/seed.md @@ -0,0 +1,6 @@ +--- +title: seed +reference: https://en.wikipedia.org/wiki/Random_seed +--- + +A _seed_ is the starting point for a computer algorithm that generates pseudo-random numbers. Using the same seed will always produce the identical sequence of numbers, which is essential for reproducibility in areas like simulations, cryptography, and video games. diff --git a/docs/content/en/quick-reference/glossary/segment.md b/docs/content/en/quick-reference/glossary/segment.md new file mode 100644 index 0000000..21c6255 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/segment.md @@ -0,0 +1,7 @@ +--- +title: segment +params: + reference: /configuration/segments/ +--- + +A _segment_ is a subset of a site, filtered by [_logical path_](g), [_sites matrix_](g), [_page kind_](g), or [_output format_](g). diff --git a/docs/content/en/quick-reference/glossary/server-relative.md b/docs/content/en/quick-reference/glossary/server-relative.md new file mode 100644 index 0000000..19a72c7 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/server-relative.md @@ -0,0 +1,9 @@ +--- +title: server-relative +--- + +A _server-relative_ path is the final path from the web server's root, used in the generated site. These paths always begin with a leading slash and account for the [`baseURL`][] and [_content dimension_](g) prefixes such as language, [_role_](g), or version. For example, `/en/examples/old-name/` is a server-relative path. + + See also: [_page-relative_](g), [_site-relative_](g). + + [`baseURL`]: /configuration/all/#baseurl diff --git a/docs/content/en/quick-reference/glossary/shortcode.md b/docs/content/en/quick-reference/glossary/shortcode.md new file mode 100644 index 0000000..a6503ea --- /dev/null +++ b/docs/content/en/quick-reference/glossary/shortcode.md @@ -0,0 +1,6 @@ +--- +title: shortcode +reference: /content-management/shortcodes +--- + +A _shortcode_ is a [_template_](g) invoked within markup, accepting any number of [_arguments_](g). They can be used with any [_content format_](g) to insert elements such as videos, images, and social media embeds into your content. diff --git a/docs/content/en/quick-reference/glossary/site-relative.md b/docs/content/en/quick-reference/glossary/site-relative.md new file mode 100644 index 0000000..b434fd1 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/site-relative.md @@ -0,0 +1,7 @@ +--- +title: site-relative +--- + +A _site-relative_ path is resolved relative to the root of the content directory. These paths begin with a leading slash. For example, `/old-name` is a site-relative path. + + See also: [_page-relative_](g), [_server-relative_](g). diff --git a/docs/content/en/quick-reference/glossary/site-root.md b/docs/content/en/quick-reference/glossary/site-root.md new file mode 100644 index 0000000..1066acb --- /dev/null +++ b/docs/content/en/quick-reference/glossary/site-root.md @@ -0,0 +1,13 @@ +--- +title: site root +--- + +The _site root_ is the root directory of the current [_site_](g), relative to the [`publishDir`][]. The _site root_ may include one or more content [_dimension_](g) prefixes, such as [_language_](g), [_role_](g), or [_version_](g). + + Project description|Site root examples + :--|:--|:-- + Monolingual|`/`, `/guest`, `/guest/v1.2.3` + Multilingual single-host|`/en`, `/guest/en`, `/guest/v1.2.3/en` + Multilingual multihost|`/en`, `/en/guest`, `/en/guest/v1.2.3` + + [`publishDir`]: /configuration/all/#publishdir diff --git a/docs/content/en/quick-reference/glossary/site.md b/docs/content/en/quick-reference/glossary/site.md new file mode 100644 index 0000000..f3be3a1 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/site.md @@ -0,0 +1,5 @@ +--- +title: site +--- + +A _site_ is a specific instance of your [_project_](g) representing a unique combination of [_language_](g), [_role_](g), and [_version_](g). While a simple project may consist of only a single site, Hugo's multidimensional content model allows a single codebase to generate a matrix of sites simultaneously. diff --git a/docs/content/en/quick-reference/glossary/sites-complements.md b/docs/content/en/quick-reference/glossary/sites-complements.md new file mode 100644 index 0000000..80213e1 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/sites-complements.md @@ -0,0 +1,5 @@ +--- +title: sites complements +--- + +A _sites complements_ is a configuration object defined in content front matter or a file mount. The links will point to the complementary [_sites_](g). The configuration is structured as a map of [_glob slices_](g). diff --git a/docs/content/en/quick-reference/glossary/sites-matrix.md b/docs/content/en/quick-reference/glossary/sites-matrix.md new file mode 100644 index 0000000..36dd8bb --- /dev/null +++ b/docs/content/en/quick-reference/glossary/sites-matrix.md @@ -0,0 +1,11 @@ +--- +title: sites matrix +--- + +A _sites matrix_ is a configuration object defined in content front matter or a file mount to precisely control which [_sites_](g) the content should be generated for. When defined in a file mount for templates, it controls which sites the template will be applied to. In Hugo multidimensional content model, the matrix defines the intersection of three dimensions: [_language_](g), [_role_](g), and [_version_](g). The configuration is structured as a map of [_glob slices_](g). + + See also [_sites complements_](g), [front matter: sites][], [module mounts: sites][], and [segments: sites][]. + + [front matter: sites]: /content-management/front-matter/#sites + [module mounts: sites]: /configuration/module/#sites + [segments: sites]: /configuration/segments/#sites diff --git a/docs/content/en/quick-reference/glossary/slice.md b/docs/content/en/quick-reference/glossary/slice.md new file mode 100644 index 0000000..5d83bed --- /dev/null +++ b/docs/content/en/quick-reference/glossary/slice.md @@ -0,0 +1,6 @@ +--- +title: slice +reference: https://go.dev/ref/spec#Slice_types +--- + +A _slice_ is a numbered sequence of elements. Unlike Go's [_array_](g) data type, slices are dynamically sized. [_Elements_](g) within a slice can be [_scalars_](g), [_arrays_](g), [_maps_](g), pages, or other slices. diff --git a/docs/content/en/quick-reference/glossary/string.md b/docs/content/en/quick-reference/glossary/string.md new file mode 100644 index 0000000..54ed4c1 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/string.md @@ -0,0 +1,5 @@ +--- +title: string +--- + +A _string_ is a sequence of bytes. For example, `"What is 6 times 7?"`. diff --git a/docs/content/en/quick-reference/glossary/taxonomic-weight.md b/docs/content/en/quick-reference/glossary/taxonomic-weight.md new file mode 100644 index 0000000..90bd804 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/taxonomic-weight.md @@ -0,0 +1,6 @@ +--- +title: taxonomic weight +reference: /content-management/taxonomies/#taxonomic-weight +--- + +Defined in front matter and unique to each taxonomy, a _taxonomic weight_ is a [_weight_](g) that determines the sort order of page collections contained within a [`Taxonomy`](g) object. diff --git a/docs/content/en/quick-reference/glossary/taxonomy-object.md b/docs/content/en/quick-reference/glossary/taxonomy-object.md new file mode 100644 index 0000000..525446a --- /dev/null +++ b/docs/content/en/quick-reference/glossary/taxonomy-object.md @@ -0,0 +1,5 @@ +--- +title: taxonomy object +--- + +A _taxonomy object_ is a [_map_](g) of [_terms_](g) and the [_weighted pages_](g) associated with each term. diff --git a/docs/content/en/quick-reference/glossary/taxonomy-page.md b/docs/content/en/quick-reference/glossary/taxonomy-page.md new file mode 100644 index 0000000..481e6a6 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/taxonomy-page.md @@ -0,0 +1,5 @@ +--- +title: taxonomy page +--- + +A _taxonomy page_ is a page with the `taxonomy` [_page kind_](g). Typically a listing of [_terms_](g) within a given [_taxonomy_](g). diff --git a/docs/content/en/quick-reference/glossary/taxonomy.md b/docs/content/en/quick-reference/glossary/taxonomy.md new file mode 100644 index 0000000..e48bd3e --- /dev/null +++ b/docs/content/en/quick-reference/glossary/taxonomy.md @@ -0,0 +1,5 @@ +--- +title: taxonomy +reference: /content-management/taxonomies +--- +A _taxonomy_ is a group of related [_terms_](g) used to classify content. For example, a `colors` taxonomy might include the terms `red`, `green`, and `blue`. diff --git a/docs/content/en/quick-reference/glossary/template-action.md b/docs/content/en/quick-reference/glossary/template-action.md new file mode 100644 index 0000000..e40c228 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/template-action.md @@ -0,0 +1,6 @@ +--- +title: template action +reference: https://pkg.go.dev/text/template#hdr-Actions +--- + +A data evaluation or control structure within a [_template_](g), delimited by `{{` and `}}`. diff --git a/docs/content/en/quick-reference/glossary/template.md b/docs/content/en/quick-reference/glossary/template.md new file mode 100644 index 0000000..d442c0a --- /dev/null +++ b/docs/content/en/quick-reference/glossary/template.md @@ -0,0 +1,6 @@ +--- +title: template +reference: /templates +--- + +A _template_ is a file with [_template actions_](g), located within the `layouts` directory of a project, theme, or module. diff --git a/docs/content/en/quick-reference/glossary/term-page.md b/docs/content/en/quick-reference/glossary/term-page.md new file mode 100644 index 0000000..a66419e --- /dev/null +++ b/docs/content/en/quick-reference/glossary/term-page.md @@ -0,0 +1,5 @@ +--- +title: term page +--- + +A _term page_ is a page with the `term` [_page kind_](g). Typically a listing of [_regular pages_](g) and [_section pages_](g) with a given [_term_](g). diff --git a/docs/content/en/quick-reference/glossary/term.md b/docs/content/en/quick-reference/glossary/term.md new file mode 100644 index 0000000..cafd925 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/term.md @@ -0,0 +1,6 @@ +--- +title: term +reference: /content-management/taxonomies +--- + +A _term_ is a member of a [_taxonomy_](g), used to classify content. diff --git a/docs/content/en/quick-reference/glossary/theme.md b/docs/content/en/quick-reference/glossary/theme.md new file mode 100644 index 0000000..4c6d15e --- /dev/null +++ b/docs/content/en/quick-reference/glossary/theme.md @@ -0,0 +1,5 @@ +--- +title: theme +--- + +A _theme_ is a [_module_](g) that delivers a complete set of [_components_](g) defining a site's layout, presentation, and behavior. While every theme is a module, not every module is a theme. diff --git a/docs/content/en/quick-reference/glossary/token.md b/docs/content/en/quick-reference/glossary/token.md new file mode 100644 index 0000000..8d6890a --- /dev/null +++ b/docs/content/en/quick-reference/glossary/token.md @@ -0,0 +1,15 @@ +--- +title: token +--- + +A _token_ is an identifier within a format string, beginning with a colon and replaced with a value when rendered. Use tokens when: + + - Configuring [file caches][], [front matter][], and [permalinks][] + - Localizing [dates][] + - Setting the [`url`][] in front matter + + [`url`]: /content-management/urls/#tokens + [dates]: /functions/time/format/#localization + [file caches]: /configuration/caches/#tokens + [front matter]: /configuration/front-matter/#tokens + [permalinks]: /configuration/permalinks/#tokens diff --git a/docs/content/en/quick-reference/glossary/translation-table.md b/docs/content/en/quick-reference/glossary/translation-table.md new file mode 100644 index 0000000..8a89983 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/translation-table.md @@ -0,0 +1,7 @@ +--- +title: translation table +--- + +A _translation table_ is a JSON, TOML, or YAML file within the `i18n` directory, named according to [RFC 5646][] and holding translations for a single language. + +[RFC 5646]: https://datatracker.ietf.org/doc/html/rfc5646 diff --git a/docs/content/en/quick-reference/glossary/type.md b/docs/content/en/quick-reference/glossary/type.md new file mode 100644 index 0000000..18b9ab5 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/type.md @@ -0,0 +1,5 @@ +--- +title: type +--- + +See [_content type_](g). diff --git a/docs/content/en/quick-reference/glossary/ugly-url.md b/docs/content/en/quick-reference/glossary/ugly-url.md new file mode 100644 index 0000000..4083f93 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/ugly-url.md @@ -0,0 +1,5 @@ +--- +title: ugly URL +--- + +An _ugly URL_ is a URL that includes a file extension. diff --git a/docs/content/en/quick-reference/glossary/unified-file-system.md b/docs/content/en/quick-reference/glossary/unified-file-system.md new file mode 100644 index 0000000..4e5f430 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/unified-file-system.md @@ -0,0 +1,5 @@ +--- +title: unified file system +--- + +Hugo's _unified file system_ provides a layered view for each of its seven [_component_](g) types: [_archetypes_](g), assets, content, data, templates, [_translation tables_](g), and static files. Project component directories are layered over [_module_](g) component directories. When multiple layers contain the same file, Hugo uses the version from the highest layer. diff --git a/docs/content/en/quick-reference/glossary/unmarshal.md b/docs/content/en/quick-reference/glossary/unmarshal.md new file mode 100644 index 0000000..d449b0b --- /dev/null +++ b/docs/content/en/quick-reference/glossary/unmarshal.md @@ -0,0 +1,6 @@ +--- +title: unmarshal +reference: /functions/transform/unmarshal/ +--- + +To _unmarshal_ (verb) is to transform a serialized object into a data structure. For example, transforming a JSON file into a [_map_](g) that you can access within a template. diff --git a/docs/content/en/quick-reference/glossary/utc.md b/docs/content/en/quick-reference/glossary/utc.md new file mode 100644 index 0000000..a4627be --- /dev/null +++ b/docs/content/en/quick-reference/glossary/utc.md @@ -0,0 +1,6 @@ +--- +title: UTC +reference: https://en.wikipedia.org/wiki/Coordinated_Universal_Time +--- + +_UTC_ is an abbreviation for Coordinated Universal Time, the primary time standard used worldwide to regulate clocks and time. It is the basis for civil time and time zones across the globe. diff --git a/docs/content/en/quick-reference/glossary/variable.md b/docs/content/en/quick-reference/glossary/variable.md new file mode 100644 index 0000000..f8139a4 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/variable.md @@ -0,0 +1,5 @@ +--- +title: variable +--- + +A _variable_ is a user-defined [_identifier_](g) prepended with a `$` symbol, representing a value of any data type, initialized or assigned within a [_template action_](g). For example, `$foo` and `$bar` are variables. diff --git a/docs/content/en/quick-reference/glossary/vendor.md b/docs/content/en/quick-reference/glossary/vendor.md new file mode 100644 index 0000000..f745a4e --- /dev/null +++ b/docs/content/en/quick-reference/glossary/vendor.md @@ -0,0 +1,7 @@ +--- +title: vendor +--- + +To _vendor_ (verb) in a software context is the process of including the source code of third-party dependencies directly within your own project's repository, rather than downloading them on the fly from an external package manager. + + When you are asked to "vendor the dependencies into the project root," you are being told to move those external libraries from a temporary cache into a dedicated folder that gets committed to your version control system. diff --git a/docs/content/en/quick-reference/glossary/version.md b/docs/content/en/quick-reference/glossary/version.md new file mode 100644 index 0000000..c272659 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/version.md @@ -0,0 +1,9 @@ +--- +title: version +--- + +A _version_ is a [_dimension_](g) that represents a specific iteration, release, or lifecycle stage of content. While [_language_](g) focuses on localization and [_role_](g) focuses on audience, the version dimension allows you to maintain multiple states of the same content simultaneously using [semantic versioning][]. + + See also: [_default version_](g). + + [semantic versioning]: https://semver.org/ diff --git a/docs/content/en/quick-reference/glossary/walk.md b/docs/content/en/quick-reference/glossary/walk.md new file mode 100644 index 0000000..2b2a844 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/walk.md @@ -0,0 +1,5 @@ +--- +title: walk +--- + +To _walk_ (verb) is to recursively traverse a nested data structure. For example, rendering a multilevel menu. diff --git a/docs/content/en/quick-reference/glossary/weight.md b/docs/content/en/quick-reference/glossary/weight.md new file mode 100644 index 0000000..01b91fe --- /dev/null +++ b/docs/content/en/quick-reference/glossary/weight.md @@ -0,0 +1,5 @@ +--- +title: weight +--- + +A _weight_ is a numeric value used to position an element within a sorted [_collection_](g). Assign weights using non-zero integers. Lighter items float to the top, while heavier items sink to the bottom. Unweighted or zero-weighted elements are placed at the end of the collection. Weights are typically assigned to pages, menu entries, languages, [_roles_](g), versions, and output formats. diff --git a/docs/content/en/quick-reference/glossary/weighted-page.md b/docs/content/en/quick-reference/glossary/weighted-page.md new file mode 100644 index 0000000..e6cf8d6 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/weighted-page.md @@ -0,0 +1,5 @@ +--- +title: weighted page +--- + +Contained within a [_taxonomy object_](g), a _weighted page_ is a [_map_](g) with two [_elements_](g): a `Page` object, and its [_taxonomic weight_](g) as defined in front matter. Access the elements using the `Page` and `Weight` keys. diff --git a/docs/content/en/quick-reference/glossary/workspace.md b/docs/content/en/quick-reference/glossary/workspace.md new file mode 100644 index 0000000..6eda9ba --- /dev/null +++ b/docs/content/en/quick-reference/glossary/workspace.md @@ -0,0 +1,7 @@ +--- +title: workspace +params: + reference: /hugo-modules/use-modules/#workspace +--- + +A _workspace_ is a collection of [_modules_](g) on disk. diff --git a/docs/content/en/quick-reference/glossary/wrapper-component.md b/docs/content/en/quick-reference/glossary/wrapper-component.md new file mode 100644 index 0000000..ada5d46 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/wrapper-component.md @@ -0,0 +1,7 @@ +--- +title: wrapper component +--- + +A _wrapper component_ is an interface pattern that encloses other content through composition rather than fixed parameters. It provides a reusable shell to handle layout, styling, or logic, allowing the calling template to inject arbitrary content into the component's interior. + + See also: [_partial decorator_](g). diff --git a/docs/content/en/quick-reference/glossary/zero-time.md b/docs/content/en/quick-reference/glossary/zero-time.md new file mode 100644 index 0000000..32697b0 --- /dev/null +++ b/docs/content/en/quick-reference/glossary/zero-time.md @@ -0,0 +1,7 @@ +--- +title: zero time +--- + +The _zero time_ is January 1, 0001, 00:00:00 UTC. Formatted per [RFC3339][] the _zero time_ is 0001-01-01T00:00:00-00:00. + + [RFC3339]: https://www.rfc-editor.org/rfc/rfc3339 diff --git a/docs/content/en/quick-reference/methods.md b/docs/content/en/quick-reference/methods.md new file mode 100644 index 0000000..524713c --- /dev/null +++ b/docs/content/en/quick-reference/methods.md @@ -0,0 +1,8 @@ +--- +title: Methods +description: A quick reference guide to Hugo's methods, grouped by object. +categories: [] +keywords: [] +--- + +{{% quick-reference section="methods" %}} diff --git a/docs/content/en/quick-reference/page-collections.md b/docs/content/en/quick-reference/page-collections.md new file mode 100644 index 0000000..14beded --- /dev/null +++ b/docs/content/en/quick-reference/page-collections.md @@ -0,0 +1,38 @@ +--- +title: Page collections +description: A quick reference guide to Hugo's page collections. +categories: [] +keywords: [] +--- + +## Page + +Use these `Page` methods when rendering lists on [section pages](g), [taxonomy pages](g), [term pages](g), and the home page. + +{{% render-list-of-pages-in-section path=/methods/page filter=methods_page_page_collections filterType=include titlePrefix=PAGE. %}} + +## Site + +Use these `Site` methods when rendering lists on any page. + +{{% render-list-of-pages-in-section path=/methods/site filter=methods_site_page_collections filterType=include titlePrefix=SITE. %}} + +## Filter + +Use the [`where`][] function to filter page collections. + +## Sort + +{{% glossary-term "default sort order" %}} + +Use these methods to sort page collections by different criteria. + +{{% render-list-of-pages-in-section path=/methods/pages filter=methods_pages_sort filterType=include titlePrefix=. titlePrefix=PAGES. %}} + +## Group + +Use these methods to group page collections. + +{{% render-list-of-pages-in-section path=/methods/pages filter=methods_pages_group filterType=include titlePrefix=. titlePrefix=PAGES. %}} + +[`where`]: /functions/collections/where/ diff --git a/docs/content/en/quick-reference/syntax-highlighting-styles.md b/docs/content/en/quick-reference/syntax-highlighting-styles.md new file mode 100644 index 0000000..fd49d00 --- /dev/null +++ b/docs/content/en/quick-reference/syntax-highlighting-styles.md @@ -0,0 +1,36 @@ +--- +title: Syntax highlighting styles +description: Highlight code examples using one of these styles. +categories: [] +keywords: [highlight] +--- + +## Overview + +Hugo provides several methods to add syntax highlighting to code examples: + +- Use the [`transform.Highlight`][] function within your templates +- Use the [`highlight`][] shortcode with any [content format](g) +- Use [fenced code blocks][] with the Markdown content format + +Regardless of method, use any of the syntax highlighting styles below. + +Set the default syntax highlighting style in your project configuration: + +{{< code-toggle file=hugo >}} +[markup.highlight] +style = 'monokai' +{{< /code-toggle >}} + +See [configure Markup][]. + +## Styles + +This gallery demonstrates the application of each syntax highlighting style with code examples written in different programming languages. + +{{% syntax-highlighting-styles %}} + +[`highlight`]: /shortcodes/highlight/ +[`transform.Highlight`]: /functions/transform/highlight/ +[configure Markup]: /configuration/markup/#highlight +[fenced code blocks]: /content-management/syntax-highlighting/#fenced-code-blocks diff --git a/docs/content/en/render-hooks/_index.md b/docs/content/en/render-hooks/_index.md new file mode 100644 index 0000000..a957cb4 --- /dev/null +++ b/docs/content/en/render-hooks/_index.md @@ -0,0 +1,8 @@ +--- +title: Render hooks +description: Create render hook templates to override the rendering of Markdown to HTML. +categories: [] +keywords: [] +weight: 10 +aliases: [/templates/render-hooks/] +--- diff --git a/docs/content/en/render-hooks/blockquotes.md b/docs/content/en/render-hooks/blockquotes.md new file mode 100755 index 0000000..6a47780 --- /dev/null +++ b/docs/content/en/render-hooks/blockquotes.md @@ -0,0 +1,181 @@ +--- +title: Blockquote render hooks +linkTitle: Blockquotes +description: Create blockquote render hook templates to override the rendering of Markdown blockquotes to HTML. +categories: [] +keywords: [] +--- + +## Context + +Blockquote _render hook_ templates receive the following [context](g): + +`AlertType` +: (`string`) Applicable when [`Type`](#type) is `alert`, this is the alert type converted to lowercase. See the [alerts](#alerts) section below. + +`AlertTitle` +: {{< new-in 0.134.0 />}} +: (`template.HTML`) Applicable when [`Type`](#type) is `alert`, this is the alert title. See the [alerts](#alerts) section below. + +`AlertSign` +: {{< new-in 0.134.0 />}} +: (`string`) Applicable when [`Type`](#type) is `alert`, this is the alert sign. Typically used to indicate whether an alert is graphically foldable, this is one of `+`, `-`, or an empty string. See the [alerts](#alerts) section below. + +`Attributes` +: (`map`) The [Markdown attributes][], available if you configure your site as follows: + + {{< code-toggle file=hugo >}} + [markup.goldmark.parser.attribute] + block = true + {{< /code-toggle >}} + +`Ordinal` +: (`int`) The zero-based ordinal of the blockquote on the page. + +`Page` +: (`page`) A reference to the current page. + +`PageInner` +: (`page`) A reference to a page nested via the [`RenderShortcodes`][] method. [See details](#pageinner-details). + +`Position` +: (`string`) The position of the blockquote within the page content. + +`Text` +: (`template.HTML`) The blockquote text, excluding the first line if [`Type`](#type) is `alert`. See the [alerts](#alerts) section below. + +`Type` +: (`string`) The blockquote type. Returns `alert` if the blockquote has an alert designator, else `regular`. See the [alerts](#alerts) section below. + +## Examples + +In its default configuration, Hugo renders Markdown blockquotes according to the [CommonMark][] specification. To create a render hook that does the same thing: + +```go-html-template {file="layouts/_markup/render-blockquote.html" copy=true} +
    + {{ .Text }} +
    +``` + +To render a blockquote as an HTML `figure` element with an optional citation and caption: + +```go-html-template {file="layouts/_markup/render-blockquote.html" copy=true} +
    +
    + {{ .Text }} +
    + {{ with .Attributes.caption }} +
    + {{ . | safeHTML }} +
    + {{ end }} +
    +``` + +Then in your markdown: + +```md +> 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: + +```md {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: + +```md {file="content/example.md"} +> [!WARNING]+ Radiation hazard +> Do not approach or handle without protective gear. +``` + +The extended syntax is compatible with [Obsidian][]. + +> [!NOTE] +> The extended syntax is not compatible with GitHub or Typora. If you include an alert sign or an alert title, these applications render the Markdown as a blockquote. + +### Example + +This blockquote render hook renders a multilingual alert if an alert designator is present, otherwise it renders a blockquote according to the CommonMark specification. + +```go-html-template {file="layouts/_markup/render-blockquote.html" copy=true} +{{ $emojis := dict + "caution" ":exclamation:" + "important" ":information_source:" + "note" ":information_source:" + "tip" ":bulb:" + "warning" ":information_source:" +}} + +{{ if eq .Type "alert" }} +
    +

    + {{ transform.Emojify (index $emojis .AlertType) }} + {{ with .AlertTitle }} + {{ . }} + {{ else }} + {{ or (i18n .AlertType) (title .AlertType) }} + {{ end }} +

    + {{ .Text }} +
    +{{ else }} +
    + {{ .Text }} +
    +{{ 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: + +```tree +layouts/ + └── _markup/ + ├── render-blockquote-alert.html + └── render-blockquote-regular.html +``` + +{{% include "/_common/render-hooks/pageinner.md" %}} + +[CommonMark]: 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 +[`RenderShortcodes`]: /methods/page/rendershortcodes/ diff --git a/docs/content/en/render-hooks/code-blocks.md b/docs/content/en/render-hooks/code-blocks.md new file mode 100755 index 0000000..74ac689 --- /dev/null +++ b/docs/content/en/render-hooks/code-blocks.md @@ -0,0 +1,135 @@ +--- +title: Code block render hooks +linkTitle: Code blocks +description: Create code block render hook templates to override the rendering of Markdown code blocks to HTML. +categories: [] +keywords: [] +--- + +## Markdown + +This Markdown example contains a fenced code block: + +````md {file="content/example.md"} +```sh {class="my-class" id="my-codeblock" lineNos=inline tabWidth=2} +declare a=1 +echo "$a" +exit +``` +```` + +A fenced code block consists of: + +- A leading [code fence][] +- An optional [info string][] +- A code sample +- A trailing code fence + +In the example above, 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 renders the code sample using its built-in syntax highlighter. 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` +: (`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 + +By default, Hugo renders fenced code blocks using its built-in syntax highlighter and wraps the result. To create a render hook that does the same thing: + +```go-html-template {file="layouts/_markup/render-codeblock.html" copy=true} +{{ $result := transform.HighlightCodeBlock . }} +{{ $result.Wrapped }} +``` + +To fall back to plain text when the language is not specified or not supported by the highlighter: + +```go-html-template {file="layouts/_markup/render-codeblock.html" copy=true} +{{- $opts := dict }} +{{- if not (transform.CanHighlight .Type) }} + {{- $opts = dict "type" "text" }} +{{- end }} +{{- $result := transform.HighlightCodeBlock . $opts }} +{{- $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. + +```tree +layouts/ + └── _markup/ + ├── render-codeblock-mermaid.html + ├── render-codeblock-python.html + └── render-codeblock.html +``` + +For example, to create a code block render hook to render [Mermaid][] diagrams: + +```go-html-template {file="layouts/_markup/render-codeblock-mermaid.html" copy=true} +
    +  {{ .Inner | htmlEscape | safeHTML }}
    +
    +{{ .Page.Store.Set "hasMermaid" true }} +``` + +Then include this snippet at the _bottom_ of your base template, before the closing `body` tag: + +```go-html-template {file="layouts/baseof.html" copy=true} +{{ if .Store.Get "hasMermaid" }} + +{{ end }} +``` + +See the [diagrams][] page for details. + +## Embedded + +Hugo includes an [embedded code block render hook][] to render [GoAT diagrams][]. + +{{% include "/_common/render-hooks/pageinner.md" %}} + +[GoAT diagrams]: /content-management/diagrams/#goat-diagrams-ascii +[Mermaid]: https://mermaid.js.org/ +[`RenderShortcodes`]: /methods/page/rendershortcodes/ +[code fence]: https://spec.commonmark.org/current/#code-fence +[diagrams]: /content-management/diagrams/#mermaid-diagrams +[embedded code block render hook]: <{{% eturl render-codeblock-goat %}}> +[highlighting options]: /functions/transform/highlight/#options +[info string]: https://spec.commonmark.org/current/#info-string diff --git a/docs/content/en/render-hooks/headings.md b/docs/content/en/render-hooks/headings.md new file mode 100755 index 0000000..ea883fb --- /dev/null +++ b/docs/content/en/render-hooks/headings.md @@ -0,0 +1,70 @@ +--- +title: Heading render hooks +linkTitle: Headings +description: Create heading render hook templates to override the rendering of Markdown headings to HTML. +categories: [] +keywords: [] +--- + +## Context + +Heading _render hook_ templates receive the following [context](g): + +`Anchor` +: (`string`) The `id` attribute of the heading element. + +`Attributes` +: (`map`) The [Markdown attributes][], available if you configure your site as follows: + + {{< code-toggle file=hugo >}} + [markup.goldmark.parser.attribute] + title = true + {{< /code-toggle >}} + +`Level` +: (`int`) The heading level, 1 through 6. + +`Ordinal` +: {{< new-in 0.160.0 />}} +: (`int`) The zero-based ordinal of the heading on the page. + +`Page` +: (`page`) A reference to the current page. + +`PageInner` +: (`page`) A reference to a page nested via the [`RenderShortcodes`][] method. [See details](#pageinner-details). + +`PlainText` +: (`string`) The heading text as plain text. + +`Position` +: {{< new-in 0.160.0 />}} +: (`string`) The position of the heading within the page content. + +`Text` +: (`template.HTML`) The heading text. + +## 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: + +```go-html-template {file="layouts/_markup/render-heading.html" copy=true} + + {{- .Text -}} + +``` + +To add an anchor link to the right of each heading: + +```go-html-template {file="layouts/_markup/render-heading.html" copy=true} + + {{ .Text }} + # + +``` + +{{% include "/_common/render-hooks/pageinner.md" %}} + +[CommonMark]: https://spec.commonmark.org/current/ +[Markdown attributes]: /content-management/markdown-attributes/ +[`RenderShortcodes`]: /methods/page/rendershortcodes/ diff --git a/docs/content/en/render-hooks/images.md b/docs/content/en/render-hooks/images.md new file mode 100755 index 0000000..e052b34 --- /dev/null +++ b/docs/content/en/render-hooks/images.md @@ -0,0 +1,140 @@ +--- +title: Image render hooks +linkTitle: Images +description: Create image render hook templates to override the rendering of Markdown images to HTML. +categories: [] +keywords: [] +--- + +## Markdown + +A Markdown image has three components: the image description, the image destination, and optionally the image title. + +```text +![white kitten](/images/kitten.jpg "A kitten!") + ------------ ------------------ --------- + 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` +: {{< new-in v0.160.0 />}} +: (`int`) The zero-based ordinal of the image on the page. + +`Page` +: (`page`) A reference to the current page. + +`PageInner` +: (`page`) A reference to a page nested via the [`RenderShortcodes`][] method. [See details](#pageinner-details). + +`PlainText` +: (`string`) The image description as plain text. + +`Position` +: {{< new-in 0.160.0 />}} +: (`string`) The position of the image within the page content. + +`Text` +: (`template.HTML`) The image description. + +`Title` +: (`string`) The image title. + +## Examples + +> [!NOTE] +> With inline elements such as images and links, remove leading and trailing whitespace using the `{{‑ ‑}}` delimiter notation to prevent whitespace between adjacent inline elements and text. + +In its default configuration, Hugo renders Markdown images according to the [CommonMark][] specification. To create a render hook that does the same thing: + +```go-html-template {file="layouts/_markup/render-image.html" copy=true} +{{ . }} +{{- /* chomp trailing newline */ -}} +``` + +To render standalone images within `figure` elements: + +```go-html-template {file="layouts/_markup/render-image.html" copy=true} +{{- if .IsBlock -}} +
    + {{ . }} + {{- with .Title }}
    {{ . }}
    {{ end -}} +
    +{{- else -}} + {{ . }} +{{- end -}} +``` + +Note that the above requires the following project configuration: + +{{< code-toggle file=hugo >}} +[markup.goldmark.parser] +wrapStandAloneImageWithinParagraph = false +{{< /code-toggle >}} + +## Embedded + +Hugo includes an [embedded image render hook][] to resolve Markdown image destinations. You can adjust its behavior in your project configuration. This is the default setting: + +{{< code-toggle file=hugo >}} +[markup.goldmark.renderHooks.image] +useEmbedded = 'auto' +{{< /code-toggle >}} + +When set to `auto` as shown above, Hugo automatically uses the embedded image render hook for multilingual single-host projects, specifically when the [duplication of shared page resources][] feature is disabled. This is the default behavior for such projects. If custom image render hooks are defined by your project, modules, or themes, these will be used instead. + +You can also configure Hugo to `always` use the embedded image render hook, use it only as a `fallback`, or `never` use it. See [details][]. + +The embedded image render hook resolves internal Markdown destinations by looking for a matching [page resource](g), falling back to a matching [global resource](g). Remote destinations are passed through, and the render hook will not throw an error or warning if unable to resolve a destination. + +You must place global resources in the `assets` directory. If you have placed your resources in the `static` directory, and you are unable or unwilling to move them, you must mount the `static` directory to the `assets` directory by including both of these entries in your project configuration: + +{{< code-toggle file=hugo >}} +[[module.mounts]] +source = 'assets' +target = 'assets' + +[[module.mounts]] +source = 'static' +target = 'assets' +{{< /code-toggle >}} + +Note that the embedded image render hook does not perform image processing. Its sole purpose is to resolve Markdown image destinations. + +{{% include "/_common/render-hooks/pageinner.md" %}} + +[CommonMark]: https://spec.commonmark.org/current/ +[Markdown attributes]: /content-management/markdown-attributes/ +[`RenderShortcodes`]: /methods/page/rendershortcodes/ +[details]: /configuration/markup/#renderhooksimageuseembedded +[duplication of shared page resources]: /configuration/markup/#duplicateresourcefiles +[embedded image render hook]: <{{% eturl render-image %}}> diff --git a/docs/content/en/render-hooks/introduction.md b/docs/content/en/render-hooks/introduction.md new file mode 100755 index 0000000..31a013b --- /dev/null +++ b/docs/content/en/render-hooks/introduction.md @@ -0,0 +1,86 @@ +--- +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][] +- [Code blocks][] +- [Headings][] +- [Images][] +- [Links][] +- [Passthrough elements][] +- [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: + +```md +[Hugo](https://gohugo.io) + +![kitten](kitten.jpg) +``` + +Without link or image render hooks, the example above is rendered to: + +```html +

    Hugo

    +

    kitten

    +``` + +By creating link and image render hooks, you can alter the conversion from Markdown to HTML. For example: + +```html +

    Hugo

    +

    kitten

    +``` + +Each render hook is a template, with one template for each supported element type: + +```tree +layouts/ + └── _markup/ + ├── render-blockquote.html + ├── render-codeblock.html + ├── render-heading.html + ├── render-image.html + ├── render-link.html + ├── render-passthrough.html + └── render-table.html +``` + +The template lookup order allows you to create different render hooks for each page [type](g), [kind](g), language, and [output format](g). For example: + +```tree +layouts/ +├── _markup/ +│ ├── render-link.html +│ └── render-link.rss.xml +├── books/ +│ └── _markup/ +│ ├── render-link.html +│ └── render-link.rss.xml +└── films/ + └── _markup/ + ├── render-link.html + └── render-link.rss.xml +``` + +The remaining pages in this section describe each type of render hook, including examples and the context received by each template. + +[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/ +[content formats]: /content-management/formats/ diff --git a/docs/content/en/render-hooks/links.md b/docs/content/en/render-hooks/links.md new file mode 100755 index 0000000..70f89c8 --- /dev/null +++ b/docs/content/en/render-hooks/links.md @@ -0,0 +1,113 @@ +--- +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. + +`Ordinal` +: {{< new-in v0.160.0 />}} +: (`int`) The zero-based ordinal of the link on the page. + +`Page` +: (`page`) A reference to the current page. + +`PageInner` +: (`page`) A reference to a page nested via the [`RenderShortcodes`][] method. [See details](#pageinner-details). + +`PlainText` +: (`string`) The link description as plain text. + +`Position` +: {{< new-in 0.160.0 />}} +: (`string`) The position of the link within the page content. + +`Text` +: (`template.HTML`) The link description. + +`Title` +: (`string`) The link title. + +## Examples + +> [!NOTE] +> With inline elements such as images and links, remove leading and trailing whitespace using the `{{‑ ‑}}` delimiter notation to prevent whitespace between adjacent inline elements and text. + +In its default configuration, Hugo renders Markdown links according to the [CommonMark][] specification. To create a render hook that does the same thing: + +```go-html-template {file="layouts/_markup/render-link.html" copy=true} + + {{- with .Text }}{{ . }}{{ end -}} + +{{- /* chomp trailing newline */ -}} +``` + +To include a `rel` attribute set to `external` for external links: + +```go-html-template {file="layouts/_markup/render-link.html" copy=true} +{{- $u := urls.Parse .Destination -}} + + {{- with .Text }}{{ . }}{{ end -}} + +{{- /* chomp trailing newline */ -}} +``` + +## Embedded + +Hugo includes an [embedded link render hook][] to resolve Markdown link destinations. You can adjust its behavior in your project configuration. This is the default setting: + +{{< code-toggle file=hugo >}} +[markup.goldmark.renderHooks.link] +useEmbedded = 'auto' +{{< /code-toggle >}} + +When set to `auto` as shown above, Hugo automatically uses the embedded link render hook for multilingual single-host projects, specifically when the [duplication of shared page resources][] feature is disabled. This is the default behavior for such projects. If custom link render hooks are defined by your project, modules, or themes, these will be used instead. + +You can also configure Hugo to `always` use the embedded link render hook, use it only as a `fallback`, or `never` use it. See [details][]. + +The embedded link render hook resolves internal Markdown destinations by looking for a matching page, falling back to a matching [page resource](g), then falling back to a matching [global resource](g). Remote destinations are passed through, and the render hook will not throw an error or warning if unable to resolve a destination. + +You must place global resources in the `assets` directory. If you have placed your resources in the `static` directory, and you are unable or unwilling to move them, you must mount the `static` directory to the `assets` directory by including both of these entries in your project configuration: + +{{< code-toggle file=hugo >}} +[[module.mounts]] +source = 'assets' +target = 'assets' + +[[module.mounts]] +source = 'static' +target = 'assets' +{{< /code-toggle >}} + +{{% include "/_common/render-hooks/pageinner.md" %}} + +[CommonMark]: https://spec.commonmark.org/current/ +[`RenderShortcodes`]: /methods/page/rendershortcodes/ +[details]: /configuration/markup/#renderhookslinkuseembedded +[duplication of shared page resources]: /configuration/markup/#duplicateresourcefiles +[embedded link render hook]: <{{% eturl render-link %}}> diff --git a/docs/content/en/render-hooks/passthrough.md b/docs/content/en/render-hooks/passthrough.md new file mode 100755 index 0000000..f4a2072 --- /dev/null +++ b/docs/content/en/render-hooks/passthrough.md @@ -0,0 +1,118 @@ +--- +title: Passthrough render hooks +linkTitle: Passthrough +description: Create passthrough render hook templates to override the rendering of text snippets captured by the Goldmark Passthrough extension. +categories: [] +keywords: [] +--- + +## 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_. + +Depending on your choice of delimiters, Hugo will classify a passthrough element as either _block_ or _inline_. Consider this contrived example: + +```md {file="content/example.md"} +This is a + +\[block\] + +passthrough element with opening and closing block delimiters. + +This is an \(inline\) passthrough element with opening and closing inline delimiters. +``` + +Update your project configuration to enable the Passthrough extension and define opening and closing delimiters for each passthrough element type, either `block` or `inline`. For example: + +{{< code-toggle file=hugo >}} +[markup.goldmark.extensions.passthrough] +enable = true +[markup.goldmark.extensions.passthrough.delimiters] +block = [['\[', '\]'], ['$$', '$$']] +inline = [['\(', '\)']] +{{< /code-toggle >}} + +In the example above there are two sets of `block` delimiters. You may use either one in your Markdown. + +The Passthrough extension is often used in conjunction with the MathJax or KaTeX display engine to render [mathematical expressions][] written in the LaTeX markup language. + +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`. + +## 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. + +```go-html-template {file="layouts/_markup/render-passthrough.html" copy=true} +{{- $opts := dict "output" "htmlAndMathml" "displayMode" (eq .Type "block") }} +{{- with try (transform.ToMath .Inner $opts) }} + {{- with .Err }} + {{- errorf "Unable to render mathematical markup to HTML using the transform.ToMath function. The KaTeX display engine threw the following error: %s: see %s." . $.Position }} + {{- else }} + {{- .Value }} + {{- $.Page.Store.Set "hasMath" true }} + {{- end }} +{{- end -}} +``` + +Then, in your base template, conditionally include the KaTeX CSS within the head element: + +```go-html-template {file="layouts/baseof.html" copy=true} + + {{ $noop := .WordCount }} + {{ if .Page.Store.Get "hasMath" }} + + {{ end }} + +``` + +In the above, note the use of a [noop](g) statement to force content rendering before we check the value of `hasMath` with the `Store.Get` method. + +Although you can use one template with conditional logic as shown above, you can also create separate templates for each [`Type`](#type) of passthrough element: + +```tree +layouts/ + └── _markup/ + ├── render-passthrough-block.html + └── render-passthrough-inline.html +``` + +{{% include "/_common/render-hooks/pageinner.md" %}} + +[Goldmark]: https://github.com/yuin/goldmark +[Markdown attributes]: /content-management/markdown-attributes/ +[Passthrough]: /configuration/markup/#passthrough +[`RenderShortcodes`]: /methods/page/rendershortcodes/ +[`transform.ToMath`]: /functions/transform/tomath/ +[mathematical expressions]: /content-management/mathematics/ diff --git a/docs/content/en/render-hooks/tables.md b/docs/content/en/render-hooks/tables.md new file mode 100755 index 0000000..d7b98f8 --- /dev/null +++ b/docs/content/en/render-hooks/tables.md @@ -0,0 +1,99 @@ +--- +title: Table render hooks +linkTitle: Tables +description: Create table render hook templates to override the rendering of Markdown tables to HTML. +categories: [] +keywords: [] +--- + +{{< new-in 0.134.0 />}} + +## Context + +Table _render hook_ templates receive the following [context](g): + +`Attributes` +: (`map`) The [Markdown attributes][], available if you configure your site as follows: + + {{< code-toggle file=hugo >}} + [markup.goldmark.parser.attribute] + block = true + {{< /code-toggle >}} + +`Ordinal` +: (`int`) The zero-based ordinal of the table on the page. + +`Page` +: (`page`) A reference to the current page. + +`PageInner` +: (`page`) A reference to a page nested via the [`RenderShortcodes`][] method. [See details](#pageinner-details). + +`Position` +: (`string`) The position of the table within the page content. + +`THead` +: (`slice`) A slice of table header rows, where each element is a slice of table cells. + +`TBody` +: (`slice`) A slice of table body rows, where each element is a slice of table cells. + +## 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: + +```go-html-template {file="layouts/_markup/render-table.html" copy=true} + + + {{- range .THead }} + + {{- range . }} + + {{- end }} + + {{- end }} + + + {{- range .TBody }} + + {{- range . }} + + {{- end }} + + {{- end }} + +
    + {{- .Text -}} +
    + {{- .Text -}} +
    +``` + +{{% include "/_common/render-hooks/pageinner.md" %}} + +[GitHub Flavored Markdown specification]: https://github.github.com/gfm/#tables-extension- +[Markdown attributes]: /content-management/markdown-attributes/ +[`RenderShortcodes`]: /methods/page/rendershortcodes/ diff --git a/docs/content/en/shortcodes/_index.md b/docs/content/en/shortcodes/_index.md new file mode 100644 index 0000000..826ee57 --- /dev/null +++ b/docs/content/en/shortcodes/_index.md @@ -0,0 +1,7 @@ +--- +title: Shortcodes +description: Insert elements such as videos, images, and social media embeds into your content using Hugo's embedded shortcodes. +categories: [] +keywords: [] +weight: 10 +--- diff --git a/docs/content/en/shortcodes/details.md b/docs/content/en/shortcodes/details.md new file mode 100755 index 0000000..b469900 --- /dev/null +++ b/docs/content/en/shortcodes/details.md @@ -0,0 +1,74 @@ +--- +title: Details shortcode +linkTitle: Details +description: Insert an HTML details element into your content using the details shortcode. +categories: [] +keywords: [] +--- + +{{< new-in 0.140.0 />}} + +> [!NOTE] +> To override Hugo's embedded `details` shortcode, copy the [source code][] to a file with the same name in the `layouts/_shortcodes` directory. + +## Example + +With this Markdown: + +```md +{{}} +This is a **bold** word. +{{}} +``` + +Hugo renders this HTML: + +```html +
    + See the details +

    This is a bold word.

    +
    +``` + +Which looks like this in your browser: + +{{< details summary="See the details" >}} +This is a **bold** word. +{{< /details >}} + +## Arguments + +`summary` +: (`string`) The content of the child `summary` element rendered from Markdown to HTML. Default is `Details`. + +`open` +: (`bool`) Whether to initially display the content of the `details` element. Default is `false`. + +`class` +: (`string`) The `class` attribute of the `details` element. + +`name` +: (`string`) The `name` attribute of the `details` element. + +`title` +: (`string`) The `title` attribute of the `details` element. + +## Styling + +Use CSS to style the `details` element, the `summary` element, and the content itself. + +```css +/* target the details element */ +details { } + +/* target the summary element */ +details > summary { } + +/* target the children of the summary element */ +details > summary > * { } + +/* target the content */ +details > :not(summary) { } +``` + +[source code]: <{{% eturl details %}}> diff --git a/docs/content/en/shortcodes/figure.md b/docs/content/en/shortcodes/figure.md new file mode 100755 index 0000000..23e1695 --- /dev/null +++ b/docs/content/en/shortcodes/figure.md @@ -0,0 +1,109 @@ +--- +title: Figure shortcode +linkTitle: Figure +description: Insert an HTML figure element into your content using the figure shortcode. +categories: [] +keywords: [] +--- + +> [!NOTE] +> To override Hugo's embedded `figure` shortcode, copy the [source code][] to a file with the same name in the `layouts/_shortcodes` directory. + +## Example + +With this Markdown: + +```md +{{}} +``` + +Hugo renders this HTML: + +```html +
    + + A photograph of Zion National Park + +
    +

    Zion National Park

    +
    +
    +``` + +Which looks like this in your browser: + +{{< figure + src="/images/examples/zion-national-park.jpg" + alt="A photograph of Zion National Park" + link="https://www.nps.gov/zion/index.htm" + caption="Zion National Park" + class="ma0 w-75" +>}} + +## Arguments + +`src` +: (`string`) The `src` attribute of the `img` element. Typically this is a [page resource](g) or a [global resource](g). + +`alt` +: (`string`) The `alt` attribute of the `img` element. + +`width` +: (`int`) The `width` attribute of the `img` element. + +`height` +: (`int`) The `height` attribute of the `img` element. + +`loading` +: (`string`) The `loading` attribute of the `img` element. + +`class` +: (`string`) The `class` attribute of the `figure` element. + +`link` +: (`string`) The `href` attribute of the anchor element that wraps the `img` element. + +`target` +: (`string`) The `target` attribute of the anchor element that wraps the `img` element. + +`rel` +: (`rel`) The `rel` attribute of the anchor element that wraps the `img` element. + +`title` +: (`string`) Within the `figurecaption` element, the title is at the top, wrapped within an `h4` element. + +`caption` +: (`string`) Within the `figurecaption` element, the caption is at the bottom and may contain plain text or markdown. + +`attr` +: (`string`) Within the `figurecaption` element, the attribution appears next to the caption and may contain plain text or markdown. + +`attrlink` +: (`string`) The `href` attribute of the anchor element that wraps the attribution. + +## Image location + +The `figure` shortcode resolves internal Markdown destinations by looking for a matching [page resource](g), falling back to a matching [global resource](g). Remote destinations are passed through, and the render hook will not throw an error or warning if unable to resolve a destination. + +You must place global resources in the `assets` directory. If you have placed your resources in the `static` directory, and you are unable or unwilling to move them, you must mount the `static` directory to the `assets` directory by including both of these entries in your project configuration: + +{{< code-toggle file=hugo >}} +[[module.mounts]] +source = 'assets' +target = 'assets' + +[[module.mounts]] +source = 'static' +target = 'assets' +{{< /code-toggle >}} + +[source code]: <{{% eturl figure %}}> diff --git a/docs/content/en/shortcodes/highlight.md b/docs/content/en/shortcodes/highlight.md new file mode 100755 index 0000000..d0611fc --- /dev/null +++ b/docs/content/en/shortcodes/highlight.md @@ -0,0 +1,103 @@ +--- +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 to generate syntax-highlighted HTML from the provided code, [language][], and [options](#options-1). + +## Arguments + +The `highlight` shortcode takes three arguments. + +```md +{{}} +CODE +{{}} +``` + +`CODE` +: (`string`) The code to highlight. + +`LANG` +: (`string`) The [language][] of the code to highlight. This value is case-insensitive. + +`OPTIONS` +: (`string`) Zero or more space-separated key-value pairs wrapped in quotation marks. You can set default values for each option in your [project configuration][]. The key names are case-insensitive. + +## Example + +```md {file="content/example.md"} +{{}} +package main + +import "fmt" + +func main() { + for i := 0; i < 3; i++ { + fmt.Println("Value of i:", i) + } +} +{{}} +``` + +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: + +```md +This is some {{}}fmt.Println("inline"){{}} 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 }} +``` + +```md +This is some {{}}fmt.Println("inline"){{}} code. +``` + +Hugo renders this to: + +This is some {{< hl >}}fmt.Println("inline"){{< /hl >}} code. + +## Options + +{{% include "_common/syntax-highlighting-options.md" %}} + +[`transform.Highlight`]: /functions/transform/highlight/ +[content format]: /content-management/formats/ +[language]: /content-management/syntax-highlighting/#languages +[project configuration]: /configuration/markup/#highlight +[source code]: <{{% eturl highlight %}}> diff --git a/docs/content/en/shortcodes/instagram.md b/docs/content/en/shortcodes/instagram.md new file mode 100755 index 0000000..f6173ab --- /dev/null +++ b/docs/content/en/shortcodes/instagram.md @@ -0,0 +1,42 @@ +--- +title: Instagram shortcode +linkTitle: Instagram +description: Embed an Instagram post in your content using the instagram shortcode. +categories: [] +keywords: [] +--- + +> [!NOTE] +> To override Hugo's embedded `instagram` shortcode, copy the [source code][] to a file with the same name in the `layouts/_shortcodes` directory. + +## Example + +To display an Instagram post with this URL: + +```text +https://www.instagram.com/p/CxOWiQNP2MO/ +``` + +Include this in your Markdown: + +```md +{{}} +``` + +Huge renders this to: + +{{< instagram CxOWiQNP2MO >}} + +## Privacy + +Adjust the relevant privacy settings in your project configuration. + +{{< code-toggle config=privacy.instagram />}} + +`disable` +: (`bool`) Whether to disable the shortcode. Default is `false`. + +`simple` +: (`bool`) Whether to enable simple mode for image card generation. If `true`, Hugo creates a static card without JavaScript. This mode only supports image cards, and the image is fetched directly from Instagram's servers. Default is `false`. + +[source code]: <{{% eturl instagram %}}> diff --git a/docs/content/en/shortcodes/param.md b/docs/content/en/shortcodes/param.md new file mode 100755 index 0000000..cd17756 --- /dev/null +++ b/docs/content/en/shortcodes/param.md @@ -0,0 +1,38 @@ +--- +title: Param shortcode +linkTitle: Param +description: Insert a parameter from front matter or your project configuration into your content using the param shortcode. +categories: [] +keywords: [] +--- + +> [!NOTE] +> To override Hugo's embedded `param` shortcode, copy the [source code][] to a file with the same name in the `layouts/_shortcodes` directory. + +The `param` shortcode renders a parameter from front matter, falling back to a site parameter of the same name. The shortcode throws an error if the parameter does not exist. + +```md {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): + +```md +{{%/* param my.nested.param */%}} +``` + +[source code]: <{{% eturl param %}}> diff --git a/docs/content/en/shortcodes/qr.md b/docs/content/en/shortcodes/qr.md new file mode 100755 index 0000000..b91214d --- /dev/null +++ b/docs/content/en/shortcodes/qr.md @@ -0,0 +1,107 @@ +--- +title: QR shortcode +linkTitle: QR +description: Insert a QR code into your content using the qr shortcode. +categories: [] +keywords: [] +--- + +{{< new-in 0.141.0 />}} + +> [!NOTE] +> To override Hugo's embedded `qr` shortcode, copy the [source code][] to a file with the same name in the `layouts/_shortcodes` directory. + +The `qr` shortcode encodes the given text into a [QR code][] using the specified options and renders the resulting image. + +Internally this shortcode calls the `images.QR` function. Please read the [related documentation][] for implementation details and guidance. + +## Examples + +Use the self-closing syntax to pass the text as an argument: + +```md +{{}} +``` + +Or insert the text between the opening and closing tags: + +```md +{{}} +https://gohugo.io +{{}} +``` + +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: + +```md +{{}} +``` + +{{< qr text="tel:+12065550101" class="qrcode" targetDir="images/qr" />}} + +To create a QR code containing contact information in the [vCard][] format: + +```md +{{}} +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 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. + +[QR code]: https://en.wikipedia.org/wiki/QR_code +[`publishDir`]: /configuration/all/#publishdir +[related documentation]: /functions/images/qr/ +[source code]: <{{% eturl qr %}}> +[vCard]: diff --git a/docs/content/en/shortcodes/ref.md b/docs/content/en/shortcodes/ref.md new file mode 100755 index 0000000..2beb218 --- /dev/null +++ b/docs/content/en/shortcodes/ref.md @@ -0,0 +1,66 @@ +--- +title: Ref shortcode +linkTitle: Ref +description: Insert a permalink to the given page reference using the ref shortcode. +categories: [] +keywords: [] +--- + +> [!NOTE] +> To override Hugo's embedded `ref` shortcode, copy the [source code][] to a file with the same name in the `layouts/_shortcodes` directory. + +> [!NOTE] +> When working with Markdown this shortcode is obsolete. Instead, to properly resolve Markdown link destinations, use the [embedded link render hook][] or create your own. +> +> In its default configuration, Hugo automatically uses the embedded link render hook for multilingual single-host projects, specifically when the [duplication of shared page resources][] feature is disabled. This is the default behavior for such projects. If custom link render hooks are defined by your project, modules, or themes, these will be used instead. +> +> You can also configure Hugo to `always` use the embedded link render hook, use it only as a `fallback`, or `never` use it. See [details][]. + +## 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" %}} + +[Markdown notation]: /content-management/shortcodes/#notation +[details]: /configuration/markup/#renderhookslinkuseembedded +[duplication of shared page resources]: /configuration/markup/#duplicateresourcefiles +[embedded link render hook]: /render-hooks/links/#embedded +[source code]: <{{% eturl ref %}}> diff --git a/docs/content/en/shortcodes/relref.md b/docs/content/en/shortcodes/relref.md new file mode 100755 index 0000000..53620c6 --- /dev/null +++ b/docs/content/en/shortcodes/relref.md @@ -0,0 +1,66 @@ +--- +title: Relref shortcode +linkTitle: Relref +description: Insert a relative permalink to the given page reference using the relref shortcode. +categories: [] +keywords: [] +--- + +> [!NOTE] +> To override Hugo's embedded `relref` shortcode, copy the [source code][] to a file with the same name in the `layouts/_shortcodes` directory. + +> [!NOTE] +> When working with Markdown this shortcode is obsolete. Instead, to properly resolve Markdown link destinations, use the [embedded link render hook][] or create your own. +> +> In its default configuration, Hugo automatically uses the embedded link render hook for multilingual single-host projects, specifically when the [duplication of shared page resources][] feature is disabled. This is the default behavior for such projects. If custom link render hooks are defined by your project, modules, or themes, these will be used instead. +> +> You can also configure Hugo to `always` use the embedded link render hook, use it only as a `fallback`, or `never` use it. See [details][]. + +## Usage + +The `relref` shortcode accepts either a single positional argument (the path) or one or more named arguments, as listed below. + +## Arguments + +{{% include "_common/ref-and-relref-options.md" %}} + +## Examples + +The `relref` shortcode typically provides the destination for a Markdown link. + +> [!NOTE] +> Always use [Markdown notation][] notation when calling this shortcode. + +The following examples show the rendered output for a page on the English version of the site: + +```md +[Link A]({{%/* relref "/books/book-1" */%}}) + +[Link B]({{%/* relref path="/books/book-1" */%}}) + +[Link C]({{%/* relref path="/books/book-1" lang="de" */%}}) + +[Link D]({{%/* relref path="/books/book-1" lang="de" outputFormat="json" */%}}) +``` + +Rendered: + +```html +Link A + +Link B + +Link C + +Link D +``` + +## Error handling + +{{% include "_common/ref-and-relref-error-handling.md" %}} + +[Markdown notation]: /content-management/shortcodes/#notation +[details]: /configuration/markup/#renderhookslinkuseembedded +[duplication of shared page resources]: /configuration/markup/#duplicateresourcefiles +[embedded link render hook]: /render-hooks/links/#embedded +[source code]: <{{% eturl relref %}}> diff --git a/docs/content/en/shortcodes/vimeo.md b/docs/content/en/shortcodes/vimeo.md new file mode 100755 index 0000000..9163a0d --- /dev/null +++ b/docs/content/en/shortcodes/vimeo.md @@ -0,0 +1,73 @@ +--- +title: Vimeo shortcode +linkTitle: Vimeo +description: Embed a Vimeo video in your content using the vimeo shortcode. +categories: [] +keywords: [] +--- + +> [!NOTE] +> To override Hugo's embedded `vimeo` shortcode, copy the [source code][] to a file with the same name in the `layouts/_shortcodes` directory. + +## Example + +To display a Vimeo video with this URL: + +```text +https://vimeo.com/19899678 +``` + +Include this in your Markdown: + +```md +{{}} +``` + +Hugo renders this to: + +{{< vimeo 19899678 >}} + +## Arguments + +`id` +: (string) The video `id`. Optional if the `id` is the first and only positional argument. + +`allowFullScreen` +: {{< new-in 0.146.0 />}} +: (`bool`) Whether the `iframe` element can activate full screen mode. Default is `true`. + +`class` +: (`string`) The `class` attribute of the wrapping `div` element. Adding one or more CSS classes disables inline styling. + +`loading` +: {{< new-in 0.146.0 />}} +: (`string`) The loading attribute of the `iframe` element, either `eager` or `lazy`. Default is `eager`. + +`title` +: (`string`) The `title` attribute of the `iframe` element. + +Here's an example using some of the available arguments: + +```md +{{}} +``` + +## Privacy + +Adjust the relevant privacy settings in your project configuration. + +{{< code-toggle config=privacy.vimeo />}} + +`disable` +: (`bool`) Whether to disable the shortcode. Default is `false`. + +`enableDNT` +: (`bool`) Whether to block the Vimeo player from tracking session data and analytics. Default is `false`. + +`simple` +: (`bool`) Whether to enable simple mode. If `true`, the video thumbnail is fetched from Vimeo and overlaid with a play button. Clicking the thumbnail opens the video in a new Vimeo tab. Default is `false`. + +The source code for the simple version of the shortcode is available [in this file][]. + +[in this file]: <{{% eturl vimeo_simple %}}> +[source code]: <{{% eturl vimeo %}}> diff --git a/docs/content/en/shortcodes/x.md b/docs/content/en/shortcodes/x.md new file mode 100755 index 0000000..88d35bd --- /dev/null +++ b/docs/content/en/shortcodes/x.md @@ -0,0 +1,54 @@ +--- +title: X shortcode +linkTitle: X +description: Embed an X post in your content using the x shortcode. +categories: [] +keywords: [] +--- + +{{< new-in 0.141.0 />}} + +> [!NOTE] +> To override Hugo's embedded `x` shortcode, copy the [source code][] to a file with the same name in the `layouts/_shortcodes` directory. + +## Example + +To display an X post with this URL: + +```txt +https://x.com/SanDiegoZoo/status/1453110110599868418 +``` + +Include this in your Markdown: + +```md +{{}} +``` + +Rendered: + +{{< x user="SanDiegoZoo" id="1453110110599868418" >}} + +## Privacy + +Adjust the relevant privacy settings in your project configuration. + +{{< code-toggle config=privacy.x />}} + +`disable` +: (`bool`) Whether to disable the shortcode. Default is `false`. + +`enableDNT` +: (`bool`) Whether to prevent X from using post and embedded page data for personalized suggestions and ads. Default is `false`. + +`simple` +: (`bool`) Whether to enable simple mode. If `true`, Hugo builds a static version of the of the post without JavaScript. Default is `false`. + +The source code for the simple version of the shortcode is available [in this file][]. + +If you enable simple mode you may want to disable the hardcoded inline styles by setting `disableInlineCSS` to `true` in your project configuration. The default value for this setting is `false`. + +{{< code-toggle config=services.x />}} + +[in this file]: <{{% eturl x_simple %}}> +[source code]: <{{% eturl x %}}> diff --git a/docs/content/en/shortcodes/youtube.md b/docs/content/en/shortcodes/youtube.md new file mode 100755 index 0000000..f19c7c6 --- /dev/null +++ b/docs/content/en/shortcodes/youtube.md @@ -0,0 +1,83 @@ +--- +title: YouTube shortcode +linkTitle: YouTube +description: Embed a YouTube video in your content using the youtube shortcode. +categories: [] +keywords: [] +--- + +> [!NOTE] +> To override Hugo's embedded `youtube` shortcode, copy the [source code][] to a file with the same name in the `layouts/_shortcodes` directory. + +## Example + +To display a YouTube video with this URL: + +```text +https://www.youtube.com/watch?v=0RKpf3rK57I +``` + +Include this in your Markdown: + +```md +{{}} +``` + +Hugo renders this to: + +{{< youtube 0RKpf3rK57I >}} + +## Arguments + +`id` +: (`string`) The video `id`. Optional if the `id` is the first and only positional argument. + +`allowFullScreen` +: (`bool`) Whether the `iframe` element can activate full screen mode. Default is `true`. + +`autoplay` +: (`bool`) Whether to automatically play the video. Forces `mute` to `true`. Default is `false`. + +`class` +: (`string`) The `class` attribute of the wrapping `div` element. When specified, removes the `style` attributes from the `iframe` element and its wrapping `div` element. + +`controls` +: (`bool`) Whether to display the video controls. Default is `true`. + +`end` +: (`int`) The time, measured in seconds from the start of the video, when the player should stop playing the video. + +`loading` +: (`string`) The loading attribute of the `iframe` element, either `eager` or `lazy`. Default is `eager`. + +`loop` +: (`bool`) Whether to indefinitely repeat the video. Ignores the `start` and `end` arguments after the first play. Default is `false`. + +`mute` +: (`bool`) Whether to mute the video. Always `true` when `autoplay` is `true`. Default is `false`. + +`start` +: (`int`) The time, measured in seconds from the start of the video, when the player should start playing the video. + +`title` +: (`string`) The `title` attribute of the `iframe` element. Defaults to "YouTube video". + +Here's an example using some of the available arguments: + +```md +{{}} +``` + +## Privacy + +Adjust the relevant privacy settings in your project configuration. + +{{< code-toggle config=privacy.youTube />}} + +`disable` +: (`bool`) Whether to disable the shortcode. Default is `false`. + +`privacyEnhanced` +: (`bool`) Whether to block YouTube from storing information about visitors on your website unless the user plays the embedded video. Default is `false`. + +[source code]: <{{% eturl youtube %}}> diff --git a/docs/content/en/templates/404.md b/docs/content/en/templates/404.md new file mode 100644 index 0000000..eb8ad66 --- /dev/null +++ b/docs/content/en/templates/404.md @@ -0,0 +1,50 @@ +--- +title: Custom 404 page +linkTitle: 404 templates +description: Create a template to render a 404 error page. +categories: [] +keywords: [] +weight: 200 +--- + +To render a 404 error page in the root of your site, create a 404 template in the root of the `layouts` directory. For example: + +```go-html-template {file="layouts/404.html"} +{{ define "main" }} +

    404 Not Found

    +

    The page you requested cannot be found.

    +

    + + Return to the home page + +

    +{{ end }} +``` + +For multilingual projects, add the language key to the file name: + +```tree +layouts/ +├── 404.de.html +├── 404.en.html +└── 404.fr.html +``` + +Your production server redirects the browser to the 404 page when a page is not found. Capabilities and configuration vary by host. + +Host|Capabilities and configuration +:--|:-- +Amazon CloudFront|See [details](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/GeneratingCustomErrorResponses.html). +Amazon S3|See [details](https://docs.aws.amazon.com/AmazonS3/latest/userguide/CustomErrorDocSupport.html). +Apache|See [details](https://httpd.apache.org/docs/2.4/custom-error.html). +Azure Static Web Apps|See [details](https://learn.microsoft.com/en-us/azure/static-web-apps/configuration#response-overrides). +Azure Storage|See [details](https://learn.microsoft.com/en-us/azure/storage/blobs/storage-blob-static-website#setting-up-a-static-website). +Caddy|See [details](https://caddyserver.com/docs/caddyfile/directives/handle_errors). +Codeberg Pages|See [details](https://docs.codeberg.org/codeberg-pages/advanced-usage/#custom-error-page-for-404s). +Cloudflare Pages|See [details](https://developers.cloudflare.com/pages/configuration/serving-pages/#not-found-behavior). +DigitalOcean App Platform|See [details](https://docs.digitalocean.com/products/app-platform/how-to/manage-static-sites/#configure-a-static-site). +Firebase|See [details](https://firebase.google.com/docs/hosting/full-config#404). +GitHub Pages|Redirection to is automatic and not configurable. +GitLab Pages|See [details](https://docs.gitlab.com/ee/user/project/pages/introduction.html#custom-error-codes-pages). +NGINX|See [details](https://nginx.org/en/docs/http/ngx_http_core_module.html#error_page). +Netlify|See [details](https://docs.netlify.com/routing/redirects/redirect-options/). diff --git a/docs/content/en/templates/_index.md b/docs/content/en/templates/_index.md new file mode 100644 index 0000000..ea293e8 --- /dev/null +++ b/docs/content/en/templates/_index.md @@ -0,0 +1,8 @@ +--- +title: Templates +description: Create templates to render your content, resources, and data. +categories: [] +keywords: [] +weight: 10 +aliases: [/templates/overview/,/templates/content] +--- diff --git a/docs/content/en/templates/embedded.md b/docs/content/en/templates/embedded.md new file mode 100644 index 0000000..78798fb --- /dev/null +++ b/docs/content/en/templates/embedded.md @@ -0,0 +1,332 @@ +--- +title: Embedded partial templates +description: Hugo provides embedded partial templates for common use cases. +categories: [] +keywords: [] +weight: 180 +aliases: [/templates/internal] +--- + +## Disqus + +> [!NOTE] +> To override Hugo's embedded Disqus template, copy the [source code](<{{% eturl disqus %}}>) to a file with the same name in the `layouts/_partials` directory, then call it from your templates using the [`partial`][] function: +> +> `{{ partial "disqus.html" . }}` + +Hugo includes an embedded template for [Disqus][], a commenting system for both static and dynamic websites. To use this template, you must first obtain a Disqus shortname by [signing up][] for the free service. + +To include the embedded template: + +```go-html-template +{{ partial "disqus.html" . }} +``` + +### Configuration {#configuration-disqus} + +To use Hugo's Disqus template, first set up a single configuration value: + +{{< code-toggle file=hugo >}} +[services.disqus] +shortname = 'your-disqus-shortname' +{{}} + +You can also set the following in the [front matter][] for a given page: + +`disqus_identifier` +: (`string`) A unique identifier for the page's discussion thread. Set this to preserve comment threads across URL changes. + +`disqus_title` +: (`string`) The title of the discussion thread. + +`disqus_url` +: (`string`) The canonical URL for the discussion thread. Use this to override the URL Disqus uses to identify the thread, for example when the same content is served at multiple URLs. + +{{< code-toggle file=content/blog/my-post.md fm=true >}} +[params] +disqus_identifier = 'unique-identifier' +disqus_title = 'Post title' +disqus_url = 'https://example.org/blog/my-post/' +{{}} + +> [!NOTE] +> When previewing your site locally, Hugo replaces the Disqus widget with the message "Disqus comments not available by default when the website is previewed locally." + +### Privacy {#privacy-disqus} + +Adjust the relevant privacy settings in your project configuration. + +{{< code-toggle config=privacy.disqus />}} + +`disable` +: (`bool`) Whether to disable the template. Default is `false`. + +## Google Analytics + +> [!NOTE] +> To override Hugo's embedded Google Analytics template, copy the [source code](<{{% eturl google_analytics %}}>) to a file with the same name in the `layouts/_partials` directory, then call it from your templates using the [`partial`][] function: +> +> `{{ partial "google_analytics.html" . }}` + +Hugo includes an embedded template for [Google Analytics 4][]. + +To include the embedded template: + +```go-html-template +{{ partial "google_analytics.html" . }} +``` + +### Configuration {#configuration-google-analytics} + +Provide your tracking ID in your configuration file: + +{{< code-toggle file=hugo >}} +[services.googleAnalytics] +id = 'G-MEASUREMENT_ID' +{{}} + +> [!NOTE] +> If the configured ID begins with `ua-` (case-insensitive), Hugo logs a warning and renders nothing. Google Universal Analytics (UA) was replaced by Google Analytics 4 (GA4) effective 1 July 2023. Create a GA4 property and data stream, then update your project configuration with the new measurement ID. + +### Privacy {#privacy-google-analytics} + +Adjust the relevant privacy settings in your project configuration. + +{{< code-toggle config=privacy.googleAnalytics />}} + +`disable` +: (`bool`) Whether to disable the template. Default is `false`. + +`respectDoNotTrack` +: (`bool`) Whether to respect the browser's "do not track" setting. Default is `true`. + +## Open Graph + +> [!NOTE] +> To override Hugo's embedded Open Graph template, copy the [source code](<{{% eturl opengraph %}}>) to a file with the same name in the `layouts/_partials` directory, then call it from your templates using the [`partial`][] function: +> +> `{{ partial "opengraph.html" . }}` + +Hugo includes an embedded template for the [Open Graph protocol][]. This metadata transforms your pages into rich objects when shared across major social media and messaging platforms. + +To include the embedded template: + +```go-html-template +{{ partial "opengraph.html" . }} +``` + +### Configuration {#configuration-open-graph} + +Hugo's Open Graph template is configured using a mix of configuration settings and [front matter][] values on individual pages. + +{{< code-toggle file=hugo >}} +title = 'My cool site' +[params] + description = 'Text about my cool site' + images = ['site-feature-image.jpg'] + [params.social] + facebook_app_id = '12345678' +[taxonomies] + series = 'series' +{{}} + +{{< code-toggle file=content/blog/my-post.md fm=true >}} +title = 'Post title' +description = 'Text about this post' +date = 2024-03-08T08:18:11-08:00 +images = ["post-cover.png"] +audio = [] +videos = [] +series = [] +tags = [] +locale = 'en-US' +{{}} + +### Metadata {#metadata-open-graph} + +Hugo emits the following metadata: + +`og:url` +: The page permalink. + +`og:site_name` +: The site title, falling back to the site configuration's `params.title` value. + +`og:title` +: The page title, falling back to the site title, then the site configuration's `params.title` value. + +`og:description` +: The page description, falling back to the page summary, then the site configuration's `params.description` value. + +`og:locale` +: The `locale` front matter value, falling back to the site language's `locale`; hyphens are replaced with underscores (e.g. `en-US` → `en_US`). + +`og:type` +: The value is `article` for pages and `website` for list and home pages. + +For article pages, Hugo also emits: + +`article:section` +: The page's top-level section. + +`article:published_time` +: The page's publish date. + +`article:modified_time` +: The page's last modified date. + +`article:tag` +: The first 6 tags. + +For image metadata, Hugo emits up to 6 `og:image` tags. + +{{% include "/_common/embedded-get-page-images.md" %}} + +`audio` and `videos` are `[]string` front matter parameters. Hugo emits up to 6 `og:audio` and `og:video` tags, passing each value through `absURL`, which converts relative paths to absolute URLs. Unlike `images`, Hugo does not search page resources or global resources for these values. + +The `series` taxonomy is used to populate `og:see_also` metadata. Hugo emits up to 7 `og:see_also` tags using the first 7 pages in the same series as the current page, excluding the current page itself. + +For Facebook metadata, if the site configuration's `params.social.facebook_app_id` value is set, Hugo emits `fb:app_id`. Otherwise, if the site configuration's `params.social.facebook_admin` value is set, Hugo emits `fb:admins`. + +## Pagination + +> [!NOTE] +> To override Hugo's embedded pagination template, copy the [source code](<{{% eturl pagination %}}>) to a file with the same name in the `layouts/_partials` directory, then call it from your templates using the [`partial`][] function: +> +> `{{ partial "pagination.html" . }}` + +Hugo includes an embedded template for rendering navigation links between pagers. To include the embedded template: + +```go-html-template +{{ partial "pagination.html" . }} +``` + +The embedded pagination template has two formats: `default` and `terse`. The `terse` format has fewer controls and page slots, consuming less space when styled as a horizontal list. See [pagination][] for details. + +## Schema + +> [!NOTE] +> To override Hugo's embedded Schema template, copy the [source code](<{{% eturl schema %}}>) to a file with the same name in the `layouts/_partials` directory, then call it from your templates using the [`partial`][] function: +> +> `{{ partial "schema.html" . }}` + +Hugo includes an embedded template to render [microdata][] `meta` elements within the `head` element of your templates. + +To include the embedded template: + +```go-html-template +{{ partial "schema.html" . }} +``` + +### Configuration {#configuration-schema} + +Hugo's Schema template uses a mix of page data and [front matter][] values on individual pages. + +{{< code-toggle file=hugo >}} +title = 'My cool site' +[params] + description = 'Text about my cool site' +{{}} + +{{< code-toggle file=content/blog/my-post.md fm=true >}} +title = 'Post title' +description = 'Text about this post' +date = 2024-03-08T08:18:11-08:00 +lastmod = 2024-03-09T12:00:00-08:00 +images = ['post-cover.png'] +keywords = ['ssg', 'hugo'] +{{}} + +### Metadata {#metadata-schema} + +Hugo emits the following microdata: + +`name` +: The page title, falling back to the site title. + +`description` +: The page description, falling back to the page summary, then the site configuration's `params.description` value. + +`datePublished` +: The page's publish date. + +`dateModified` +: The page's last modified date. + +`wordCount` +: The page's word count. + +For image metadata, Hugo emits up to 6 `image` tags. + +{{% include "/_common/embedded-get-page-images.md" %}} + +For keyword metadata, Hugo uses the following order of precedence: + +1. Titles of `keywords` taxonomy terms, if `keywords` is defined as a taxonomy +1. The `keywords` front matter value, if `keywords` is not a taxonomy +1. Titles of `tags` taxonomy terms +1. Titles of all taxonomy terms + +## X (Twitter) Cards + +> [!NOTE] +> To override Hugo's embedded Twitter Cards template, copy the [source code](<{{% eturl twitter_cards %}}>) to a file with the same name in the `layouts/_partials` directory, then call it from your templates using the [`partial`][] function: +> +> `{{ partial "twitter_cards.html" . }}` + +Hugo includes an embedded template for [X (Twitter) Cards][], metadata used to attach rich media to Tweets linking to your site. + +To include the embedded template: + +```go-html-template +{{ partial "twitter_cards.html" . }} +``` + +### Configuration {#configuration-x-cards} + +Hugo's X (Twitter) Card template is configured using a mix of configuration settings and [front matter][] values on individual pages. + +{{< code-toggle file=hugo >}} +[params] + description = 'Text about my cool site' + images = ["site-feature-image.jpg"] + [params.social] + twitter = 'GoHugoIO' +{{}} + +{{< code-toggle file=content/blog/my-post.md fm=true >}} +title = 'Post title' +description = 'Text about this post' +images = ["post-cover.png"] +{{}} + +### Metadata {#metadata-x-cards} + +If an image is found, Hugo sets `twitter:card` to `summary_large_image` and emits a `twitter:image` tag using the first image found. If no image is found, Hugo sets `twitter:card` to `summary` and omits the image tag. + +{{% include "/_common/embedded-get-page-images.md" %}} + +Hugo also emits the following metadata: + +`twitter:title` +: The page title, falling back to the site title, then the site configuration's `params.title` value. + +`twitter:description` +: The page description, falling back to the page summary, then the site configuration's `params.description` value. + +`twitter:site` +: The site configuration's `params.social.twitter` value. The `@` prefix is added automatically if not already present. For example, with `twitter = 'GoHugoIO'` in your configuration, Hugo renders: + + ```html + + ``` + +[Disqus]: https://disqus.com +[Google Analytics 4]: https://support.google.com/analytics/answer/10089681 +[Open Graph protocol]: https://ogp.me/ +[X (Twitter) Cards]: https://developer.x.com/en/docs/twitter-for-websites/cards/overview/abouts-cards +[`partial`]: /functions/partials/include/ +[front matter]: /content-management/front-matter/ +[microdata]: https://html.spec.whatwg.org/multipage/microdata.html#microdata +[pagination]: /templates/pagination/ +[signing up]: https://disqus.com/profile/signup/ diff --git a/docs/content/en/templates/introduction.md b/docs/content/en/templates/introduction.md new file mode 100644 index 0000000..348cc78 --- /dev/null +++ b/docs/content/en/templates/introduction.md @@ -0,0 +1,581 @@ +--- +title: Introduction to templating +linkTitle: Introduction +description: An introduction to Hugo's templating syntax. +categories: [] +keywords: [] +weight: 10 +--- + +{{< newtemplatesystem >}} + +{{% glossary-term template %}} + +Templates use [variables](#variables), [functions][], and [methods][] to transform your content, resources, and data into a published page. + +> [!NOTE] +> 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. + +For example, this HTML template initializes the `$v1` and `$v2` variables, then displays them and their product within an HTML paragraph. + +```go-html-template +{{ $v1 := 6 }} +{{ $v2 := 7 }} +

    The product of {{ $v1 }} and {{ $v2 }} is {{ mul $v1 $v2 }}.

    +``` + +While HTML templates are the most common, you can create templates for any [output format](g) including CSV, JSON, RSS, and plain text. + +## Context + +The most important concept to understand before creating a template is _context_, the data passed into each template. The data may be a simple value, or more commonly [objects](g) and associated [methods](g). + +For example, a _page_ template receives a `Page` object, and the `Page` object provides methods to return values or perform actions. + +### Current context + +Within a template, the dot (`.`) represents the current context. + +```go-html-template {file="layouts/page.html"} +

    {{ .Title }}

    +``` + +In the example above the dot represents the `Page` object, and we call its [`Title`][] method to return the title as defined in [front matter][]. + +The current context may change within a template. For example, at the top of a template the context might be a `Page` object, but we rebind the context to another value or object within [`range`][] or [`with`][] blocks. + +```go-html-template {file="layouts/page.html"} +

    {{ .Title }}

    + +{{ range slice "foo" "bar" }} +

    {{ . }}

    +{{ end }} + +{{ with "baz" }} +

    {{ . }}

    +{{ end }} +``` + +In the example above, the context changes as we `range` through the [slice](g) of values. In the first iteration the context is "foo", and in the second iteration the context is "bar". Inside of the `with` block the context is "baz". Hugo renders the above to: + +```html +

    My Page Title

    +

    foo

    +

    bar

    +

    baz

    +``` + +### Template context + +Within a `range` or `with` block you can access the context passed into the template by prepending a dollar sign (`$`) to the dot: + +```go-html-template {file="layouts/page.html"} +{{ with "foo" }} +

    {{ $.Title }} - {{ . }}

    +{{ end }} +``` + +Hugo renders this to: + +```html +

    My Page Title - foo

    +``` + +> [!NOTE] +> Make sure that you thoroughly understand the concept of _context_ before you continue reading. The most common templating errors made by new users relate to context. + +## Actions + +In the examples above, the paired opening and closing braces represent the beginning and end of a template action, a data evaluation or control structure within a template. + +A template action may contain literal values ([boolean](g), [string](g), [integer](g), and [float](g)), the [current context](#current-context), [variables](#variables), [functions](#functions), [methods](#methods), and the [`nil`](#nil) keyword. + +```go-html-template {file="layouts/page.html"} +{{ $convertToLower := true }} +{{ if $convertToLower }} +

    {{ strings.ToLower .Title }}

    +{{ end }} +``` + +In the example above: + +- `$convertToLower` is a variable +- `true` is a literal boolean value +- `if` is the beginning of a control structure +- `strings.ToLower` is a function that converts all characters to lowercase +- `Title` is a method on a the `Page` object +- `end` is the end of a control structure + +Hugo renders the above to: + +```html {trim=false} + +

    my page title

    + +``` + +### Whitespace + +Notice the blank lines and indentation in the previous example? Although irrelevant in production when you typically minify the output, you can remove the adjacent whitespace by using template action delimiters with hyphens: + +```go-html-template {file="layouts/page.html"} +{{- $convertToLower := true -}} +{{- if $convertToLower -}} +

    {{ strings.ToLower .Title }}

    +{{- end -}} +``` + +Hugo renders this to: + +```html +

    my page title

    +``` + +Whitespace includes spaces, horizontal tabs, carriage returns, and newlines. + +### Quote characters + +Hugo templates use different quote characters to define how text and characters are processed. + +Use double quotes for [interpreted string literals](g). These interpret backslashes as special instructions: + +```go-html-template +{{ print "Hello world\u0021" }} → Hello world! +``` + +Use backticks for [raw string literals](g). These ignore backslashes and treat every character literally: + +```go-html-template +{{ print `Hello world\u0021` }} → Hello world\u0021 +``` + +Use single quotes for [rune literals](g). Unlike strings, these represent a single character as its numerical Unicode value: + +```go-html-template +{{ print '!' }} → 33 +``` + +In practical terms, you will rarely, if ever, use rune literals in your template code. They are most commonly used in low-level programming; in a Hugo template, you will almost always want a string instead. + +### Pipes + +Within a template action you may [pipe](g) a value to a function or method. The piped value becomes the final argument to the function or method. For example, these are equivalent: + +```go-html-template +{{ strings.ToLower "Hugo" }} → hugo +{{ "Hugo" | strings.ToLower }} → hugo +``` + +You can pipe the result of one function or method into another. For example, these are equivalent: + +```go-html-template +{{ strings.TrimSuffix "o" (strings.ToLower "Hugo") }} → hug +{{ "Hugo" | strings.ToLower | strings.TrimSuffix "o" }} → hug +``` + +These are also equivalent: + +```go-html-template +{{ mul 6 (add 2 5) }} → 42 +{{ 5 | add 2 | mul 6 }} → 42 +``` + +> [!NOTE] +> Remember that the piped value becomes the final argument to the function or method to which you are piping. + +### Line splitting + +You can split a template action over two or more lines. For example, these are equivalent: + +```go-html-template +{{ $v := or $arg1 $arg2 }} + +{{ $v := or + $arg1 + $arg2 +}} +``` + +You can also split [raw string literals](g) over two or more lines. For example, these are equivalent: + +```go-html-template +{{ $msg := "This is line one.\nThis is line two." }} + +{{ $msg := `This is line one. +This is line two.` +}} +``` + +### Nil + +Other than using the `nil` keyword in comparisons, you may not use it as an argument to any function or method, nor may you assign it to a variable. For example, these are valid uses of the `nil` keyword: + +```go-html-template +{{ if gt 42 nil }} +

    42 is greater than nil

    +{{ end }} + +{{ $pages := where .Site.RegularPages "Params.color" "ne" nil }} +``` + +These, on the other hand, are invalid: + +```go-html-template +{{ $a := nil }} +{{ add 3 nil }} +{{ nil | print}} +``` + +The actions above throw an error. + +## Variables + +A variable is a user-defined [identifier](g) prepended with a dollar sign (`$`), representing a value of any data type, initialized or assigned within a template action. For example, `$foo` and `$bar` are variables. + +Variables may contain [scalars](g), [slices](g), [maps](g), or [objects](g). + +Use `:=` to initialize a variable, and use `=` to assign a value to a variable that has been previously initialized. For example: + +```go-html-template +{{ $total := 3 }} +{{ range slice 7 11 21 }} + {{ $total = add $total . }} +{{ end }} +{{ $total }} → 42 +``` + +Variables initialized inside of an `if`, `range`, or `with` block are scoped to the block. Variables initialized outside of these blocks are scoped to the template. + +With variables that represent a slice or map, use the [`index`][] function to return the desired value. + +```go-html-template +{{ $slice := slice "foo" "bar" "baz" }} +{{ index $slice 2 }} → baz + +{{ $map := dict "a" "foo" "b" "bar" "c" "baz" }} +{{ index $map "c" }} → baz +``` + +> [!NOTE] +> Slices and arrays are zero-based; element 0 is the first element. + +With variables that represent a map or object, [chain](g) identifiers to return the desired value or to access the desired method. + +```go-html-template +{{ $map := dict "a" "foo" "b" "bar" "c" "baz" }} +{{ $map.c }} → baz + +{{ $homePage := .Site.Home }} +{{ $homePage.Title }} → My Homepage +``` + +> [!NOTE] +> As seen above, object and method names are capitalized. Although not required, to avoid confusion we recommend beginning variable and map key names with a lowercase letter or underscore. + +## Functions + +Used within a template action, a function takes one or more arguments and returns a value. Unlike methods, functions are not associated with an object. + +Go's `text/template` and `html/template` packages provide a small set of functions, operators, and statements for general use. See the [go-templates][] section of the function documentation for details. + +Hugo provides hundreds of custom [functions][] categorized by namespace. For example, the `strings` namespace includes these and other functions: + +Function|Alias +:--|:-- +[`strings.ToLower`][]|`lower` +[`strings.ToUpper`][]|`upper` +[`strings.Replace`][]|`replace` + +As shown above, frequently used functions have an alias. Use aliases in your templates to reduce code length. + +When calling a function, separate the arguments from the function, and from each other, with a space. For example: + +```go-html-template +{{ $total := add 1 2 3 4 }} +``` + +## Methods + +Used within a template action and associated with an object, a method takes zero or more arguments and either returns a value or performs an action. + +The most commonly accessed objects are the [`Page`][] and [`Site`][] objects. This is a small sampling of the [methods][] available to each object. + +Object|Method|Description +:--|:--|:-- +`Page`|[`Date`](methods/page/date/)|Returns the date of the given page. +`Page`|[`Params`](methods/page/params/)|Returns a map of custom parameters as defined in the front matter of the given page. +`Page`|[`Title`](methods/page/title/)|Returns the title of the given page. +`Site`|[`Data`](methods/site/data/)|Returns a data structure composed from the files in the `data` directory. +`Site`|[`Params`](methods/site/params/)|Returns a map of custom parameters as defined in your project configuration. +`Site`|[`Title`](methods/site/title/)|Returns the title as defined in the your project configuration. + +Chain the method to its object with a dot (`.`) as shown below, remembering that the leading dot represents the [current context](#current-context). + +```go-html-template {file="layouts/page.html"} +{{ .Site.Title }} → My Site Title +{{ .Page.Title }} → My Page Title +``` + +The context passed into most templates is a `Page` object, so this is equivalent to the previous example: + +```go-html-template {file="layouts/page.html"} +{{ .Site.Title }} → My Site Title +{{ .Title }} → My Page Title +``` + +Some methods take an argument. Separate the argument from the method with a space. For example: + +```go-html-template {file="layouts/page.html"} +{{ $page := .Page.GetPage "/books/les-miserables" }} +{{ $page.Title }} → Les Misérables +``` + +## Comments + +> [!NOTE] +> Do not attempt to use HTML comment delimiters to comment out template code. +> +> Hugo strips HTML comments when rendering a page, but first evaluates any template code within the HTML comment delimiters. Depending on the template code within the HTML comment delimiters, this could cause unexpected results or fail the build. + +Template comments are similar to template actions. Paired opening and closing braces represent the beginning and end of a comment. For example: + +```go-html-template +{{/* This is an inline comment. */}} +{{- /* This is an inline comment with adjacent whitespace removed. */ -}} +``` + +Code within a comment is not parsed, executed, or displayed. Comments may be inline, as shown above, or in block form: + +```go-html-template +{{/* +This is a block comment. +*/}} + +{{- /* +This is a block comment with +adjacent whitespace removed. +*/ -}} +``` + +You may not nest one comment inside of another. + +To render an HTML comment, pass a string through the [`safe.HTML`][] function. For example: + +```go-html-template +{{ "" | safeHTML }} +{{ printf "" .Site.Title | safeHTML }} +``` + +## Include + +Use the [`template`][] function to include one or more of Hugo's [embedded templates][]: + +```go-html-template +{{ partial "google_analytics.html" . }} +{{ partial "opengraph" . }} +{{ partial "pagination.html" . }} +{{ partial "schema.html" . }} +{{ partial "twitter_cards.html" . }} +``` + +Use the [`partial`][] or [`partialCached`][] function to include one or more [partial templates][]: + +```go-html-template +{{ partial "breadcrumbs.html" . }} +{{ partialCached "css.html" . }} +``` + +Create your _partial_ templates in the `layouts/_partials` directory. + +> [!NOTE] +> In the examples above, note that we are passing the current context (the dot) to each of the templates. + +## Examples + +This limited set of contrived examples demonstrates some of concepts described above. Please see the [functions][], [methods][], and [templates][] documentation for specific examples. + +### Conditional blocks + +See documentation for [`if`][], [`else`][], and [`end`][]. + +```go-html-template +{{ $var := 42 }} +{{ 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 }} +``` + +### Logical operators + +See documentation for [`and`][] and [`or`][]. + +```go-html-template +{{ $v1 := true }} +{{ $v2 := false }} +{{ $v3 := false }} +{{ $result := false }} + +{{ if and $v1 $v2 $v3 }} + {{ $result = true }} +{{ end }} +{{ $result }} → false + +{{ if or $v1 $v2 $v3 }} + {{ $result = true }} +{{ end }} +{{ $result }} → true +``` + +### Loops + +See documentation for [`range`][], [`else`][], and [`end`][]. + +```go-html-template +{{ $s := slice "foo" "bar" "baz" }} +{{ range $s }} +

    {{ . }}

    +{{ else }} +

    The collection is empty

    +{{ end }} +``` + +To loop a specified number of times: + +```go-html-template +{{ $s := slice }} +{{ range 3 }} + {{ $s = $s | append . }} +{{ end }} +{{ $s }} → [0 1 2] +``` + +### Rebind context + +See documentation for [`with`][], [`else`][], and [`end`][]. + +```go-html-template +{{ $var := "foo" }} +{{ with $var }} + {{ . }} → foo +{{ else }} + {{ print "var is falsy" }} +{{ end }} +``` + +To test multiple conditions: + +```go-html-template +{{ $v1 := 0 }} +{{ $v2 := 42 }} +{{ with $v1 }} + {{ . }} +{{ else with $v2 }} + {{ . }} → 42 +{{ else }} + {{ print "v1 and v2 are falsy" }} +{{ end }} +``` + +### Access site parameters + +See documentation for the [`Params`][params_site] method on a `Site` object. + +With this project configuration: + +{{< code-toggle file=hugo >}} +title = 'ABC Widgets' +baseURL = 'https://example.org' +[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 site parameters by chaining the identifiers: + +```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 +``` + +### Access page parameters + +See documentation for the [`Params`][params_page] method on a `Page` object. + +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 +``` + +[`Page`]: /methods/page/ +[`Site`]: /methods/site/ +[`Title`]: /methods/page/title/ +[`and`]: /functions/go-template/and/ +[`else`]: /functions/go-template/else/ +[`end`]: /functions/go-template/end/ +[`html/template`]: https://pkg.go.dev/html/template +[`if`]: /functions/go-template/if/ +[`index`]: /functions/collections/indexfunction/ +[`or`]: /functions/go-template/or/ +[`partialCached`]: /functions/partials/includecached/ +[`partial`]: /functions/partials/include/ +[`range`]: /functions/go-template/range/ +[`safe.HTML`]: /functions/safe/html/ +[`strings.Replace`]: /functions/strings/replace/ +[`strings.ToLower`]: /functions/strings/tolower/ +[`strings.ToUpper`]: /functions/strings/toupper/ +[`template`]: /functions/go-template/template/ +[`text/template`]: https://pkg.go.dev/text/template +[`with`]: /functions/go-template/with/ +[embedded templates]: /templates/embedded/ +[front matter fields]: /content-management/front-matter/#fields +[front matter]: /content-management/front-matter/ +[functions]: /functions/ +[go-templates]: /functions/go-template/ +[methods]: /methods/ +[params_page]: /methods/page/params/ +[params_site]: /methods/site/params/ +[partial templates]: /templates/types/#partial +[templates]: /templates/ diff --git a/docs/content/en/templates/lookup-order.md b/docs/content/en/templates/lookup-order.md new file mode 100644 index 0000000..580b0a4 --- /dev/null +++ b/docs/content/en/templates/lookup-order.md @@ -0,0 +1,95 @@ +--- +title: Template lookup order +linkTitle: Lookup order +description: Hugo uses the rules below to select a template for a given page, starting from the most specific. +categories: [] +keywords: [] +weight: 20 +--- + +{{< newtemplatesystem >}} + +## Lookup rules + +Hugo takes the parameters listed below into consideration when choosing a template for a given page. The templates are ordered by specificity. This should feel natural, but look at the table below for concrete examples of the different parameter variations. + +Kind +: The page `Kind` (the home page is one). See the example tables below per kind. This also determines if it is a **single page** (i.e. a regular content page. We then look for a template in `_default/single.html` for HTML) or a **list page** (section listings, home page, taxonomy lists, taxonomy terms. We then look for a template in `_default/list.html` for HTML). + +Layout +: Can be set in front matter. + +Output Format +: See [configure output formats](/configuration/output-formats/). An output format has both a `name` (e.g. `rss`, `amp`, `html`) and a `suffix` (e.g. `xml`, `html`). We prefer matches with both (e.g. `index.amp.html`), but look for less specific templates. + +Note that if the output format's Media Type has more than one suffix defined, only the first is considered. + +Language +: We will consider a language tag in the template name. If the site language is `fr`, `index.fr.amp.html` will win over `index.amp.html`, but `index.amp.html` will be chosen before `index.fr.html`. + +Type +: Is value of `type` if set in front matter, else it is the name of the root section (e.g. "blog"). It will always have a value, so if not set, the value is "page". + +Section +: Is relevant for `section`, `taxonomy` and `term` types. + +> [!NOTE] +> Templates can live in either the project's or the themes' `layout` directories, and the most specific templates will be chosen. Hugo will interleave the lookups listed below, finding the most specific one either in the project or themes. + +## Target a template + +You cannot change the lookup order to target a content page, but you can change a content page to target a template. Specify `type`, `layout`, or both in front matter. + +Consider this content structure: + +```tree +content/ +├── about.md +└── contact.md +``` + +Files in the root of the `content` directory have a [content type](g) of `page`. To render these pages with a unique template, create a matching subdirectory: + +```tree +layouts/ +└── page/ + └── single.html +``` + +The contact page, however, probably has a form and requires a different template. In the front matter specify `layout`: + +{{< code-toggle file=content/contact.md fm=true >}} +title = 'Contact' +layout = 'contact' +{{< /code-toggle >}} + +Then create the template for the contact page: + +```tree +layouts/ +└── page/ + └── contact.html <-- renders contact.md + └── single.html <-- renders about.md +``` + +As a content type, the word `page` is vague. Perhaps `miscellaneous` would be better. Add `type` to the front matter of each page: + +{{< code-toggle file=content/about.md fm=true >}} +title = 'About' +type = 'miscellaneous' +{{< /code-toggle >}} + +{{< code-toggle file=content/contact.md fm=true >}} +title = 'Contact' +type = 'miscellaneous' +layout = 'contact' +{{< /code-toggle >}} + +Now place the layouts in the corresponding directory: + +```tree +layouts/ +└── miscellaneous/ + └── contact.html <-- renders contact.md + └── single.html <-- renders about.md +``` diff --git a/docs/content/en/templates/menu.md b/docs/content/en/templates/menu.md new file mode 100644 index 0000000..cdef8c0 --- /dev/null +++ b/docs/content/en/templates/menu.md @@ -0,0 +1,127 @@ +--- +title: Menu templates +description: Create templates to render one or more menus. +categories: [] +keywords: [] +weight: 150 +aliases: [/templates/menus/,/templates/menu-templates/] +--- + +## Overview + +After [defining menu entries][], use [menu methods][] to render a menu. + +Three factors determine how to render a menu: + +1. The method used to define the menu entries: [automatic][], in [front matter][], or in your [project configuration][] +1. The menu structure: flat or nested +1. The method used to [localize the menu entries][]: project configuration or translation tables + +The example below handles every combination. + +## Example + +This _partial_ template recursively "walks" a menu structure, rendering a localized, accessible nested list. + +```go-html-template {file="layouts/_partials/menu.html" copy=true} +{{- $page := .page }} +{{- $menuID := .menuID }} + +{{- with index site.Menus $menuID }} + +{{- end }} + +{{- define "_partials/inline/menu/walk.html" }} + {{- $page := .page }} + {{- range .menuEntries }} + {{- $attrs := dict "href" .URL }} + {{- if $page.IsMenuCurrent .Menu . }} + {{- $attrs = merge $attrs (dict "class" "active" "aria-current" "page") }} + {{- else if $page.HasMenuCurrent .Menu .}} + {{- $attrs = merge $attrs (dict "class" "ancestor" "aria-current" "true") }} + {{- end }} + {{- $name := .Name }} + {{- with .Identifier }} + {{- with T . }} + {{- $name = . }} + {{- end }} + {{- end }} +
  • + {{ $name }} + {{- with .Children }} +
      + {{- partial "inline/menu/walk.html" (dict "page" $page "menuEntries" .) }} +
    + {{- end }} +
  • + {{- end }} +{{- end }} +``` + +Call the partial above, passing a menu ID and the current page in context. + +```go-html-template {file="layouts/page.html"} +{{ partial "menu.html" (dict "menuID" "main" "page" .) }} +{{ partial "menu.html" (dict "menuID" "footer" "page" .) }} +``` + +## Page references + +Regardless of how you [define menu entries][], an entry associated with a page has access to page context. + +This simplistic example renders a page parameter named `version` next to each entry's `name`. Code defensively using `with` or `if` to handle entries where (a) the entry points to an external resource, or (b) the `version` parameter is not defined. + +```go-html-template {file="layouts/page.html"} +{{- range site.Menus.main }} + + {{ .Name }} + {{- with .Page }} + {{- with .Params.version -}} + ({{ . }}) + {{- end }} + {{- end }} + +{{- end }} +``` + +## Menu entry parameters + +When you define menu entries in your [project configuration][] or in [front matter][], you can include a `params` key as shown in these examples: + +- [Menu entry defined in your project configuration][] +- [Menu entry defined in front matter][] + +This simplistic example renders a `class` attribute for each anchor element. Code defensively using `with` or `if` to handle entries where `params.class` is not defined. + +```go-html-template {file="layouts/_partials/menu.html"} +{{- range site.Menus.main }} + + {{ .Name }} + +{{- end }} +``` + +## Localize + +Hugo provides two methods to localize your menu entries. See [multilingual][]. + +[Menu entry defined in front matter]: /content-management/menus/#example +[Menu entry defined in your project configuration]: /configuration/menus/ +[automatic]: /content-management/menus/#define-automatically +[define menu entries]: /content-management/menus/ +[defining menu entries]: /content-management/menus/ +[front matter]: /content-management/menus/#define-in-front-matter +[localize the menu entries]: /content-management/multilingual/#menus +[menu methods]: /methods/menu/ +[multilingual]: /content-management/multilingual/#menus +[project configuration]: /content-management/menus/#define-in-project-configuration diff --git a/docs/content/en/templates/new-templatesystem-overview.md b/docs/content/en/templates/new-templatesystem-overview.md new file mode 100644 index 0000000..9ba69c6 --- /dev/null +++ b/docs/content/en/templates/new-templatesystem-overview.md @@ -0,0 +1,98 @@ +--- +title: New template system in Hugo v0.146.0 +linktitle: New template system +description: Overview of the new template system in Hugo v0.146.0. +categories: [] +keywords: [] +weight: 1 +--- + +In [Hugo v0.146.0][], we performed a full re-implementation of how Go templates are handled in Hugo. This includes structural changes to the `layouts` folder and a new, more powerful template lookup system. + +We have aimed to maintain as much backward compatibility as possible by mapping "old to new," but some reported breakages have occurred. We're working on a full overhaul of the documentation on this topic – until then, this is a one-pager with the most important changes. + +## Changes to the `layouts` folder + +| Description | Action required | +|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| The `_default` folder is removed. | Move all files in `layouts/_default` up to the `layouts/` root. | +| The `layouts/partials` folder is renamed to `layouts/_partials`. | Rename the folder. | +| The `layouts/shortcodes` folder is renamed to `layouts/_shortcodes`. | Rename the folder. | +| Any folder in `layouts` that does not start with `_` represents the root of a [Page path][]. In [Hugo v0.146.0][], this can be nested as deeply as needed, and `_shortcodes` and `_markup` folders can be placed at any level in the tree. | No action required. | +| The above also means that there's no top-level `layouts/taxonomy` or `layouts/section` folders anymore, unless it represents a [Page path][]. | Move them up to `layouts/` with one of the [Page kinds][] `section`, `taxonomy` or `term` as the base name, or place the layouts into the taxonomy [Page path][]. | +| A template named `taxonomy.html` used to be a candidate for both Page kind `term` and `taxonomy`, now it's only considered for `taxonomy`. | Create both `taxonomy.html` and `term.html` or create a more general layout, e.g. `list.html`. | +| For base templates (e.g., `baseof.html`), in previous Hugo versions, you could prepend one identifier (layout, type, or kind) with a hyphen in front of the baseof keyword. | Move that identifier after the first "dot," e.g., rename`list-baseof.html` to `baseof.list.html`. | +| We have added a new `all` "catch-all" layout. This means that if you have, e.g., `layouts/all.html` and that is the only template, that layout will be used for all HTML page rendering. | | +| We have removed the concept of `_internal` Hugo templates.[^internal] | Replace constructs similar to `{{ template "_internal/opengraph.html" . }}` with `{{ partial "opengraph.html" . }}`. | +| The identifiers that can be used in a template filename are one of the [Page kinds][] (`home`, `page`, `section`, `taxonomy`, or `term`), one of the standard layouts (`list`, `single`, or `all`), a custom layout (as defined in the `layout` front matter field), a language (e.g., `en`), an output format (e.g., `html`, `rss`), and a suffix representing the media type. E.g., `all.en.html` and `home.rss.xml`. | | +| The above means that there's no such thing as an `index.html` template for the home page anymore. | Rename `index.html` to `home.html`. | + +Also, see the [Example folder structure](#example-folder-structure) below for a more concrete example of the new layout system. + +## Changes to template lookup order + +We have consolidated the template lookup so it works the same across all [template types][]. The previous setup was difficult to understand and had a massive number of variants. The new setup aims to feel natural with few surprises. + +The identifiers used in the template weighting, in order of importance, are: + +| Identifier | Description | +|--------------------|-------------------------------------------------------| +| Layout custom | The custom `layout` set in front matter. | +| [Page kinds][] | One of `home`, `section`, `taxonomy`, `term`, `page`. | +| Layouts standard 1 | `list` or `single`. | +| Output format | The output format (e.g., `html`, `rss`). | +| Layouts standard 2 | `all`. | +| Language | The language (e.g., `en`). | +| Media type | The media type (e.g., `text/html`). | +| [Page path][] | The page path (e.g., `/blog/mypost`). | +| Type | `type` set in front matter.[^type] | + +For templates placed in a `layouts` folder partly or completely matching a [Page path][], a closer match upwards will be considered _better_. In the [Example folder structure](#example-folder-structure) below, this means that: + +- `layouts/docs/api/_markup/render-link.html` will be used to render links from the Page path `/docs/api` and below. +- `layouts/docs/baseof.html` will be used as the base template for the Page path `/docs` and below. +- `layouts/tags/term.html` will be used for all `term` rendering in the `tags` taxonomy, except for the `blue` term, which will use `layouts/tags/blue/list.html`. + +## Example folder structure + +```tree +layouts +├── baseof.html +├── baseof.term.html +├── home.html +├── page.html +├── section.html +├── taxonomy.html +├── term.html +├── term.mylayout.en.rss.xml +├── _markup +│ ├── render-codeblock-go.term.mylayout.no.rss.xml +│ └── render-link.html +├── _partials +│ └── mypartial.html +├── _shortcodes +│ ├── myshortcode.html +│ └── myshortcode.section.mylayout.en.rss.xml +├── docs +│ ├── baseof.html +│ ├── _shortcodes +│ │ └── myshortcode.html +│ └── api +│ ├── mylayout.html +│ ├── page.html +│ └── _markup +│ └── render-link.html +└── tags + ├── taxonomy.html + ├── term.html + └── blue + └── list.html +``` + +[^internal]: The old way of doing it made it difficult or impossible to, e.g., override `_internal/disqus.html` in a theme. Now you can just create a partial with the same name. +[^type]: The `type` set in front matter will effectively replace the `section` folder in [Page path][] when doing lookups. + +[Hugo v0.146.0]: https://github.com/gohugoio/hugo/releases/tag/v0.146.0 +[Page kinds]: https://gohugo.io/methods/page/kind/ +[Page path]: https://gohugo.io/methods/page/path/ +[template types]: /templates/types/ diff --git a/docs/content/en/templates/pagination.md b/docs/content/en/templates/pagination.md new file mode 100644 index 0000000..5835303 --- /dev/null +++ b/docs/content/en/templates/pagination.md @@ -0,0 +1,241 @@ +--- +title: Pagination +description: Split a list page into two or more subsets. +categories: [] +keywords: [] +weight: 160 +aliases: [/extras/pagination,/doc/pagination/] +--- + +Displaying a large page collection on a list page is not user-friendly: + +- A massive list can be intimidating and difficult to navigate. Users may get lost in the sheer volume of information. +- Large pages take longer to load, which can frustrate users and lead to them abandoning the site. +- Without any filtering or organization, finding a specific item becomes a tedious scrolling exercise. + +Improve usability by paginating `home`, `section`, `taxonomy`, and `term` pages. + +> [!NOTE] +> The most common templating mistake related to pagination is invoking pagination more than once for a given list page. See the [caching](#caching) section below. + +## Terminology + +paginate +: To split a [list page](g) into two or more subsets. + +pagination +: The process of paginating a list page. + +pager +: Created during pagination, a pager contains a subset of a list page and navigation links to other pagers. + +paginator +: A collection of pagers. + +## Configuration + +See [configure pagination][]. + +## Methods + +To paginate a `home`, `section`, `taxonomy`, or `term` page, invoke either of these methods on the `Page` object in the corresponding template: + +- [`Paginate`][] +- [`Paginator`][] + +The `Paginate` method is more flexible, allowing you to: + +- Paginate any page collection +- Filter, sort, and group the page collection +- Override the number of pages per pager as defined in your project configuration + +By comparison, the `Paginator` method paginates the page collection passed into the template, and you cannot override the number of pages per pager. + +## Examples + +To paginate a list page using the `Paginate` method: + +```go-html-template +{{ $pages := where site.RegularPages "Type" "posts" }} +{{ $paginator := .Paginate $pages.ByTitle 7 }} + +{{ range $paginator.Pages }} +

    {{ .LinkTitle }}

    +{{ end }} + +{{ partial "pagination.html" . }} +``` + +In the example above, we: + +1. Build a page collection +1. Sort the page collection by title +1. Paginate the page collection, with 7 pages 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 + +To paginate a list page using the `Paginator` method: + +```go-html-template +{{ range .Paginator.Pages }} +

    {{ .LinkTitle }}

    +{{ end }} + +{{ partial "pagination.html" . }} +``` + +In the example above, we: + +1. Paginate the page collection passed into the template, with the default number of pages 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 + +## Caching + +> [!NOTE] +> The most common templating mistake related to pagination is invoking pagination more than once for a given list page. + +Regardless of pagination method, the initial invocation is cached and cannot be changed. If you invoke pagination more than once for a given list page, subsequent invocations use the cached result. This means that subsequent invocations will not behave as written. + +When paginating conditionally, do not use the `compare.Conditional` function due to its eager evaluation of arguments. Use an `if-else` construct instead. + +## Grouping + +Use pagination with any of the [grouping methods][]. For example: + +```go-html-template +{{ $pages := where site.RegularPages "Type" "posts" }} +{{ $paginator := .Paginate ($pages.GroupByDate "Jan 2006") }} + +{{ range $paginator.PageGroups }} +

    {{ .Key }}

    + {{ range .Pages }} +

    {{ .LinkTitle }}

    + {{ end }} +{{ end }} + +{{ partial "pagination.html" . }} +``` + +## Navigation + +As shown in the examples above, the easiest way to add navigation between pagers is with Hugo's embedded pagination template: + +```go-html-template +{{ partial "pagination.html" . }} +``` + +The embedded pagination template has two formats: `default` and `terse`. The above is equivalent to: + +```go-html-template +{{ partial "pagination.html" (dict "page" . "format" "default") }} +``` + +The `terse` format has fewer controls and page slots, consuming less space when styled as a horizontal list. To use the `terse` format: + +```go-html-template +{{ partial "pagination.html" (dict "page" . "format" "terse") }} +``` + +> [!NOTE] +> To override Hugo's embedded pagination template, copy the [source code][] to a file with the same name in the `layouts/_partials` directory, then call it from your templates using the [`partial`][] function: +> +> `{{ partial "pagination.html" . }}` + +Create custom navigation components using any of the `Pager` methods: + +{{% render-list-of-pages-in-section path=/methods/pager %}} + +## Structure + +The example below depicts the published site structure when paginating a list page. + +With this content: + +```tree +content/ +├── posts/ +│ ├── _index.md +│ ├── post-1.md +│ ├── post-2.md +│ ├── post-3.md +│ └── post-4.md +└── _index.md +``` + +And this project configuration: + +{{< code-toggle file=hugo >}} +[pagination] + disableAliases = false + pagerSize = 2 + path = 'page' +{{< /code-toggle >}} + +And this _section_ template: + +```go-html-template {file="layouts/section.html"} +{{ range (.Paginate .Pages).Pages }} +

    {{ .LinkTitle }}

    +{{ end }} + +{{ partial "pagination.html" . }} +``` + +The published site has this structure: + +```tree +public/ +├── posts/ +│ ├── page/ +│ │ ├── 1/ +│ │ │ └── index.html <-- alias to public/posts/index.html +│ │ └── 2/ +│ │ └── index.html +│ ├── post-1/ +│ │ └── index.html +│ ├── post-2/ +│ │ └── index.html +│ ├── post-3/ +│ │ └── index.html +│ ├── post-4/ +│ │ └── index.html +│ └── index.html +└── index.html +``` + +To disable alias generation for the first pager, change your project configuration: + +{{< code-toggle file=hugo >}} +[pagination] + disableAliases = true + pagerSize = 2 + path = 'page' +{{< /code-toggle >}} + +Now the published site will have this structure: + +```tree +public/ +├── posts/ +│ ├── page/ +│ │ └── 2/ +│ │ └── index.html +│ ├── post-1/ +│ │ └── index.html +│ ├── post-2/ +│ │ └── index.html +│ ├── post-3/ +│ │ └── index.html +│ ├── post-4/ +│ │ └── index.html +│ └── index.html +└── index.html +``` + +[`Paginate`]: /methods/page/paginate/ +[`Paginator`]: /methods/page/paginator/ +[`partial`]: /functions/partials/include/ +[configure pagination]: /configuration/pagination/ +[grouping methods]: /quick-reference/page-collections/#group +[source code]: <{{% eturl pagination %}}> diff --git a/docs/content/en/templates/partial-decorators.md b/docs/content/en/templates/partial-decorators.md new file mode 100644 index 0000000..deca416 --- /dev/null +++ b/docs/content/en/templates/partial-decorators.md @@ -0,0 +1,125 @@ +--- +title: Partial decorators +description: Use partial decorators to create reusable wrapper components that enclose and compose template content. +categories: [] +keywords: [decorator] +weight: 170 +--- + +{{< new-in 0.154.0 />}} + +## Overview + +{{% glossary-term "partial decorator" %}} + +This approach creates a connection between two files. The calling template provides a block of code and the partial decorator determines where that code appears. This allows the partial to wrap around content without needing to know the specific markup or internal logic of the enclosed block. + +## Implementation + +To use a partial decorator, use a block-style call in your templates. The [`with`][] statement is required to initiate the partial and create a container for the content. This block can include any valid template code including page methods and functions. + +```go-html-template {file="layouts/home.html" copy=true} +{{ with partial "components/wrapper.html" . }} +

    Everything in this block will be wrapped.

    +

    {{ .Content | transform.Plainify | strings.Truncate 200 }}

    +{{ end }} +``` + +Inside the partial template, place the `templates.Inner` function call where the wrapped content should appear. + +```go-html-template {file="layouts/_partials/components/wrapper.html" copy=true} +
    + {{ templates.Inner . }} +
    +``` + +The `with` statement creates a new [scope](g). Variables defined outside of the `with` block are not available inside it. To use external data within the wrapped content, you must ensure it is part of the [context](g) passed in the partial call. + +A key feature of the `templates.Inner` function is its ability to accept a context argument. By passing a context to the function, you define what the dot (`.`) represents inside the wrapped block. This ensures that the injected content has access to the correct data even when nested inside multiple layers of wrappers. + +## Benefits of composition + +Using partial decorators to build wrapper components provides several advantages: + +- It eliminates the need to use separate partials for opening and closing tags when encapsulating a block of code. +- It prevents parameter bloat because a standard partial no longer requires an extensive list of arguments to account for every possible variation of the content inside it. +- It enables clean composition where the wrapped block can execute any template logic without the wrapper needing to receive or process that data. + +This approach separates container logic from content logic. The wrapper handles structural requirements like specific class hierarchies or CSS grid containers. The calling template retains control over the inner markup and how data is displayed. + +## Example + +The following templates illustrate how to nest three wrapper components including a section, a column, and a card while passing context through each layer. + +The _home_ template initiates the structure by calling the section, column, and card partials as decorators: + +```go-html-template {file="layouts/home.html" copy=true} +{{ $ctx := dict + "page" . + "label" "Recent Posts" + "pageCollection" ((site.GetPage "/posts").RegularPages) +}} + +{{ with partial "components/section.html" $ctx }} +
    + {{ range .pageCollection }} + {{ with partial "components/column.html" (dict "page" . "class" "col-half") }} + {{ with partial "components/card.html" (dict "page" .page "url" .page.RelPermalink "title" .page.LinkTitle) }} +

    + {{ .page.Content | plainify | strings.Truncate 240 }} +

    + {{ end }} + {{ end }} + {{ end }} +
    +{{ end }} +``` + +The section component provides a semantic container and an optional heading: + +```go-html-template {file="layouts/_partials/components/section.html" copy=true} +
    + {{ with .label }} + + {{ end }} +
    + {{ templates.Inner . }} +
    +
    +``` + +The column component manages layout width by applying a CSS class: + +```go-html-template {file="layouts/_partials/components/column.html" copy=true} +
    + {{ templates.Inner . }} +
    +``` + +The card component defines the visual boundary for the content: + +```go-html-template {file="layouts/_partials/components/card.html" copy=true} +
    + {{ with .title }} +

    + {{ if $.url }} + {{ . }} + {{ else }} + {{ . }} + {{ end }} +

    + {{ end }} + +
    + {{ templates.Inner . }} +
    + + {{ with .url }} + + {{ end }} +
    +``` + +[`with`]: /functions/go-template/with/ diff --git a/docs/content/en/templates/robots.md b/docs/content/en/templates/robots.md new file mode 100644 index 0000000..1dc3781 --- /dev/null +++ b/docs/content/en/templates/robots.md @@ -0,0 +1,51 @@ +--- +title: robots.txt template +linkTitle: robots.txt templates +description: Hugo can generate a customized robots.txt in the same way as any other template. +categories: [] +keywords: [] +weight: 190 +aliases: [/extras/robots-txt/] +--- + +To generate a robots.txt file from a template, change your project configuration: + +{{< code-toggle file=hugo >}} +enableRobotsTXT = true +{{< /code-toggle >}} + +By default, Hugo generates robots.txt using an [embedded template][]. + +```text +User-agent: * +``` + +Search engines that honor the Robots Exclusion Protocol will interpret this as permission to crawl everything on the site. + +## robots.txt template lookup order + +You may overwrite the internal template with a custom template. Hugo selects the template using this lookup order: + +1. `/layouts/robots.txt` +1. `/themes//layouts/robots.txt` + +## robots.txt template example + +```text {file="layouts/robots.txt"} +User-agent: * +{{ range .Pages }} +Disallow: {{ .RelPermalink }} +{{ end }} +``` + +This template creates a robots.txt file with a `Disallow` directive for each page on the site. Search engines that honor the Robots Exclusion Protocol will not crawl any page on the site. + +> [!NOTE] +> To create a robots.txt file without using a template: +> +> 1. Set `enableRobotsTXT` to `false` in your project configuration. +> 1. Create a robots.txt file in the `static` directory. +> +> Remember that Hugo copies everything in the static director to the root of `publishDir` (typically `public`) when you build your project. + +[embedded template]: <{{% eturl robots %}}> diff --git a/docs/content/en/templates/rss.md b/docs/content/en/templates/rss.md new file mode 100644 index 0000000..fd30239 --- /dev/null +++ b/docs/content/en/templates/rss.md @@ -0,0 +1,75 @@ +--- +title: RSS templates +description: Use the embedded RSS template, or create your own. +categories: [] +keywords: [] +weight: 140 +--- + +## Configuration + +By default, when you build your project, Hugo generates RSS feeds for home, section, taxonomy, and term pages. Control feed generation in your project configuration. For example, to generate feeds for home and section pages, but not for taxonomy and term pages: + +{{< code-toggle file=hugo >}} +[outputs] +home = ['html', 'rss'] +section = ['html', 'rss'] +taxonomy = ['html'] +term = ['html'] +{{< /code-toggle >}} + +To disable feed generation for all [page kinds](g): + +{{< code-toggle file=hugo >}} +disableKinds = ['rss'] +{{< /code-toggle >}} + +By default, the number of items in each feed is unlimited. Change this as needed in your project configuration: + +{{< code-toggle file=hugo >}} +[services.rss] +limit = 42 +{{< /code-toggle >}} + +Set `limit` to `-1` to generate an unlimited number of items per feed. + +The built-in RSS template will render the following values, if present, from your project configuration: + +{{< code-toggle file=hugo >}} +copyright = '© 2023 ABC Widgets, Inc.' +[params.author] +name = 'John Doe' +email = 'jdoe@example.org' +{{< /code-toggle >}} + +## Include feed reference + +To include a feed reference in the `head` element of your rendered pages, place this within the `head` element of your templates: + +```go-html-template +{{ with .OutputFormats.Get "rss" }} + {{ printf `` .Rel .MediaType.Type .Permalink site.Title | safeHTML }} +{{ end }} +``` + +Hugo will render this to: + +```html + +``` + +## Custom templates + +Override Hugo's [embedded RSS template][] by creating one or more of your own. For example, to use different templates for home, section, taxonomy, and term pages: + +```tree +layouts/ + ├── home.rss.xml + ├── section.rss.xml + ├── taxonomy.rss.xml + └── term.rss.xml +``` + +RSS templates receive the `.Page` and `.Site` objects in context. + +[embedded RSS template]: <{{% eturl rss %}}> diff --git a/docs/content/en/templates/shortcode.md b/docs/content/en/templates/shortcode.md new file mode 100644 index 0000000..aef4896 --- /dev/null +++ b/docs/content/en/templates/shortcode.md @@ -0,0 +1,340 @@ +--- +title: Shortcode templates +description: Create custom shortcodes to simplify and standardize content creation. +categories: [] +keywords: [] +weight: 120 +aliases: [/templates/shortcode-templates/] +--- + +{{< newtemplatesystem >}} + +> [!NOTE] +> Before creating custom shortcodes, please review the [shortcodes][] page in the [content management][] section. Understanding the usage details will help you design and create better templates. + +## Introduction + +Hugo provides [embedded shortcodes][] for many common tasks, but you'll likely need to create your own for more specific needs. Some examples of custom shortcodes you might develop include: + +- Audio players +- Video players +- Image galleries +- Diagrams +- Maps +- Tables +- And many other custom elements + +## Directory structure + +Create _shortcode_ templates within the `layouts/_shortcodes` directory, either at its root or organized into subdirectories. + +```tree +layouts/ +└── _shortcodes/ + ├── diagrams/ + │ ├── kroki.html + │ └── plotly.html + ├── media/ + │ ├── audio.html + │ ├── gallery.html + │ └── video.html + ├── capture.html + ├── column.html + ├── include.html + └── row.html +``` + +When calling a shortcode in a subdirectory, specify its path relative to the `_shortcode` directory, excluding the file extension. + +```md +{{}} +``` + +## Lookup order + +Hugo selects _shortcode_ templates based on the shortcode name, the current output format, and the current language. The examples below are sorted by specificity in descending order. The least specific path is at the bottom of the list. + +Shortcode name|Output format|Language|Template path +:--|:--|:--|:-- +foo|html|en|`layouts/_shortcodes/foo.en.html` +foo|html|en|`layouts/_shortcodes/foo.html.html` +foo|html|en|`layouts/_shortcodes/foo.html` +foo|html|en|`layouts/_shortcodes/foo.html.en.html` + +Shortcode name|Output format|Language|Template path +:--|:--|:--|:-- +foo|rss|en|`layouts/_shortcodes/foo.en.rss.xml` +foo|rss|en|`layouts/_shortcodes/foo.rss.xml` +foo|rss|en|`layouts/_shortcodes/foo.en.xml` +foo|rss|en|`layouts/_shortcodes/foo.xml` + +## Methods + +Use these methods in your _shortcode_ templates. Refer to each method's documentation for details and examples. + +{{% render-list-of-pages-in-section path=/methods/shortcode %}} + +## Examples + +These examples range in complexity from simple to moderately advanced, with some simplified for clarity. + +### Insert year + +Create a shortcode to insert the current year: + +```go-html-template {file="layouts/_shortcodes/year.html"} +{{- now.Format "2006" -}} +``` + +Then call the shortcode from within your markup: + +```md {file="content/example.md"} +This is {{}}, and look at how far we've come. +``` + +This shortcode can be used inline or as a block on its own line. If a shortcode might be used inline, remove the surrounding [whitespace][] by using [template action](g) delimiters with hyphens. + +### Insert image + +This example assumes the following content structure, where `content/example/index.md` is a [page bundle](g) containing one or more [page resources](g). + +```tree +content/ +├── example/ +│ ├── a.jpg +│ └── index.md +└── _index.md +``` + +Create a shortcode to capture an image as a page resource, resize it to the given width, convert it to the WebP format, and add an `alt` attribute: + +```go-html-template {file="layouts/_shortcodes/image.html"} +{{- with .Page.Resources.Get (.Get "path") }} + {{- with .Process (printf "resize %dx wepb" ($.Get "width")) -}} + {{ $.Get + {{- end }} +{{- end -}} +``` + +Then call the shortcode from within your markup: + +```md {file="content/example/index.md"} +{{}} +``` + +The example above uses: + +- The [`with`][] statement to rebind the [context](g) after each successful operation +- The [`Get`][] method to retrieve arguments by name +- The `$` to access the template context + +> [!NOTE] +> Make sure that you thoroughly understand the concept of context. The most common templating errors made by new users relate to context. +> +> Read more about context in the [introduction to templating][]. + +### Insert image with error handling + +The previous example, while functional, silently fails if the image is missing, and does not gracefully exit if a required argument is missing. We'll add error handling to address these issues: + +```go-html-template {file="layouts/_shortcodes/image.html"} +{{- with .Get "path" }} + {{- with $r := $.Page.Resources.Get ($.Get "path") }} + {{- with $.Get "width" }} + {{- with $r.Process (printf "resize %dx wepb" ($.Get "width" )) }} + {{- $alt := or ($.Get "alt") "" -}} + {{ $alt }} + {{- end }} + {{- else }} + {{- errorf "The %q shortcode requires a 'width' argument: see %s" $.Name $.Position }} + {{- end }} + {{- else }} + {{- warnf "The %q shortcode was unable to find %s: see %s" $.Name ($.Get "path") $.Position }} + {{- end }} +{{- else }} + {{- errorf "The %q shortcode requires a 'path' argument: see %s" .Name .Position }} +{{- end -}} +``` + +This template throws an error and gracefully fails the build if the author neglected to provide a `path` or `width` argument, and it emits a warning if it cannot find the image at the specified path. If the author does not provide an `alt` argument, the `alt` attribute is set to an empty string. + +The [`Name`][] and [`Position`][] methods provide helpful context for errors and warnings. For example, a missing `width` argument causes the shortcode to throw this error: + +```text +ERROR The "image" shortcode requires a 'width' argument: see "/home/user/project/content/example/index.md:7:1" +``` + +### Positional arguments + +Shortcode arguments can be [named or positional][]. We used named arguments previously; let's explore positional arguments. Here's the named argument version of our example: + +```md {file="content/example/index.md"} +{{}} +``` + +Here's how to call it with positional arguments: + +```md {file="content/example/index.md"} +{{}} +``` + +Using the `Get` method with zero-indexed keys, we'll initialize variables with descriptive names in our template: + +```go-html-template {file="layouts/_shortcodes/image.html"} +{{ $path := .Get 0 }} +{{ $width := .Get 1 }} +{{ $alt := .Get 2 }} +``` + +> [!NOTE] +> Positional arguments work well for frequently used shortcodes with one or two arguments. Since you'll use them often, the argument order will be easy to remember. For less frequently used shortcodes, or those with more than two arguments, named arguments improve readability and reduce the chance of errors. + +### Named and positional arguments + +You can create a shortcode that will accept both named and positional arguments, but not at the same time. Use the [`IsNamedParams`][] method to determine whether the shortcode call used named or positional arguments: + +```go-html-template {file="layouts/_shortcodes/image.html"} +{{ $path := cond (.IsNamedParams) (.Get "path") (.Get 0) }} +{{ $width := cond (.IsNamedParams) (.Get "width") (.Get 1) }} +{{ $alt := cond (.IsNamedParams) (.Get "alt") (.Get 2) }} +``` + +This example uses the `cond` alias for the [`compare.Conditional`][] function to get the argument by name if `IsNamedParams` returns `true`, otherwise get the argument by position. + +### Argument collection + +Use the [`Params`][] method to access the arguments as a collection. + +When using named arguments, the `Params` method returns a map: + +```md {file="content/example/index.md"} +{{}} +``` + +```go-html-template {file="layouts/_shortcodes/image.html"} +{{ .Params.path }} → a.jpg +{{ .Params.width }} → 300 +{{ .Params.alt }} → A white kitten +``` + + When using positional arguments, the `Params` method returns a slice: + +```md {file="content/example/index.md"} +{{}} +``` + +```go-html-template {file="layouts/_shortcodes/image.html"} +{{ index .Params 0 }} → a.jpg +{{ index .Params 1 }} → 300 +{{ index .Params 1 }} → A white kitten +``` + +Combine the `Params` method with the [`collections.IsSet`][] function to determine if a parameter is set, even if its value is falsy. + +### Inner content + +Extract the content enclosed within shortcode tags using the [`Inner`][] method. This example demonstrates how to pass both content and a title to a shortcode. The shortcode then generates a `div` element containing an `h2` element (displaying the title) and the provided content. + +```md {file="content/example.md"} +{{}} +This is a **bold** word, and this is an _emphasized_ word. +{{}} +``` + +```go-html-template {file="layouts/_shortcodes/contrived.html"} +
    +

    {{ .Get "title" }}

    + {{ .Inner | .Page.RenderString }} +
    +``` + +The preceding example called the shortcode using [standard notation][], requiring us to process the inner content with the [`RenderString`][] method to convert the Markdown to HTML. This conversion is unnecessary when calling a shortcode using [Markdown notation][]. + +### Nesting + +The [`Parent`][] method provides access to the parent shortcode context when the shortcode in question is called within the context of a parent shortcode. This provides an inheritance model. + +The following example is contrived but demonstrates the concept. Assume you have a `gallery` shortcode that expects one named `class` argument: + +```go-html-template {file="layouts/_shortcodes/gallery.html"} +
    + {{ .Inner }} +
    +``` + +You also have an `img` shortcode with a single named `src` argument that you want to call inside of `gallery` and other shortcodes, so that the parent defines the context of each `img`: + +```go-html-template {file="layouts/_shortcodes/img.html"} +{{ $src := .Get "src" }} +{{ with .Parent }} + +{{ else }} + +{{ end }} +``` + +You can then call your shortcode in your content as follows: + +```md {file="content/example.md"} +{{}} + {{}} + {{}} +{{}} +{{}} +``` + +This will output the following HTML. Note how the first two `img` shortcodes inherit the `class` value of `content-gallery` set with the call to the parent `gallery`, whereas the third `img` only uses `src`: + +```html + + +``` + +### Other examples + +For guidance, consider examining Hugo's embedded shortcodes. The source code, available on [GitHub][], can provide a useful model. + +## Detection + +The [`HasShortcode`][] method allows you to check if a specific shortcode has been called on a page. For example, consider a custom audio shortcode: + +```md {file="content/example.md"} +{{}} +``` + +You can use the `HasShortcode` method in your base template to conditionally load CSS if the audio shortcode was used on the page: + +```go-html-template {file="layouts/baseof.html"} + + ... + {{ if .HasShortcode "audio" }} + + {{ end }} + ... + +``` + +[GitHub]: https://github.com/gohugoio/hugo/tree/master/tpl/tplimpl/embedded/templates/_shortcodes +[Markdown notation]: /content-management/shortcodes/#markdown-notation +[`Get`]: /methods/shortcode/get/ +[`HasShortcode`]: /methods/page/hasshortcode/ +[`Inner`]: /methods/shortcode/inner/ +[`IsNamedParams`]: /methods/shortcode/isnamedparams/ +[`Name`]: /methods/shortcode/name/ +[`Params`]: /methods/shortcode/params/ +[`Parent`]: /methods/shortcode/parent/ +[`Position`]: /methods/shortcode/position/ +[`RenderString`]: /methods/page/renderstring/ +[`collections.IsSet`]: /functions/collections/isset/ +[`compare.Conditional`]: /functions/compare/conditional/ +[`with`]: /functions/go-template/with/ +[content management]: /content-management/shortcodes/ +[embedded shortcodes]: /shortcodes/ +[introduction to templating]: /templates/introduction/ +[named or positional]: /content-management/shortcodes/#arguments +[shortcodes]: /content-management/shortcodes/ +[standard notation]: /content-management/shortcodes/#standard-notation +[whitespace]: /templates/introduction/#whitespace diff --git a/docs/content/en/templates/sitemap.md b/docs/content/en/templates/sitemap.md new file mode 100644 index 0000000..f89642f --- /dev/null +++ b/docs/content/en/templates/sitemap.md @@ -0,0 +1,55 @@ +--- +title: Sitemap templates +description: Hugo provides built-in sitemap templates. +categories: [] +keywords: [] +weight: 130 +aliases: [/layout/sitemap/,/templates/sitemap-template/] +--- + +## Overview + +Hugo's embedded sitemap templates conform to v0.9 of the [sitemap protocol][]. + +With a monolingual project, Hugo generates a sitemap.xml file in the root of the [`publishDir`][] using the [embedded sitemap template][]. + +With a multilingual project, Hugo generates: + +- A sitemap.xml file in the root of each site (language) using the [embedded sitemap template][] +- A sitemap.xml file in the root of the [`publishDir`][] using the [embedded sitemapindex template][] + +## Configuration + +See [configure sitemap][]. + +## Override default values + +Override the default values for a given page in front matter. + +{{< code-toggle file=news.md fm=true >}} +title = 'News' +[sitemap] + changefreq = 'weekly' + disable = true + priority = 0.8 +{{}} + +## Override built-in templates + +To override the built-in sitemap.xml template, create a new `layouts/sitemap.xml` file. When ranging through the page collection, access the _change frequency_ and _priority_ with `.Sitemap.ChangeFreq` and `.Sitemap.Priority` respectively. + +To override the built-in sitemapindex.xml template, create a new `layouts/sitemapindex.xml` file. + +## Disable sitemap generation + +You may disable sitemap generation in your project configuration: + +{{< code-toggle file=hugo >}} +disableKinds = ['sitemap'] +{{}} + +[`publishDir`]: /configuration/all/#publishdir +[configure sitemap]: /configuration/sitemap/ +[embedded sitemap template]: <{{% eturl sitemap %}}> +[embedded sitemapindex template]: <{{% eturl sitemapindex %}}> +[sitemap protocol]: https://www.sitemaps.org/protocol.html diff --git a/docs/content/en/templates/types.md b/docs/content/en/templates/types.md new file mode 100644 index 0000000..c8d95b6 --- /dev/null +++ b/docs/content/en/templates/types.md @@ -0,0 +1,410 @@ +--- +title: Template types +description: Create templates of different types to render your content, resources, and data. +categories: [] +keywords: [] +weight: 30 +aliases: [ + '/templates/base/', + '/templates/content-view/', + '/templates/home/', + '/templates/lists/', + '/templates/partial/', + '/templates/section/', + '/templates/single/', + '/templates/taxonomy/', + '/templates/term/', +] +--- + +## Structure + +Create templates in the `layouts` directory in the root of your project. + +Although your site may not require each of these templates, the example below is typical for a site of medium complexity. + +```tree +layouts/ +├── _markup/ +│ ├── render-image.html <-- render hook +│ └── render-link.html <-- render hook +├── _partials/ +│ ├── footer.html +│ └── header.html +├── _shortcodes/ +│ ├── audio.html +│ └── video.html +├── books/ +│ ├── page.html +│ └── section.html +├── films/ +│ ├── view_card.html <-- content view +│ ├── view_li.html <-- content view +│ ├── page.html +│ └── section.html +├── baseof.html +├── home.html +├── page.html +├── section.html +├── taxonomy.html +└── term.html +``` + +Hugo's [template lookup order][] determines the template path, allowing you to create unique templates for any page. + +> [!NOTE] +> You must have thorough understanding of the template lookup order when creating templates. Template selection is based on template type, page kind, content type, section, language, and output format. + +The purpose of each template type is described below. + +## Base + +A _base_ template serves as a foundational layout that other templates can build upon. It typically defines the common structural components of your HTML, such as the `html`, `head`, and `body` elements. It also often includes recurring features like headers, footers, navigation, and script inclusions that appear across multiple pages of your site. By defining these common aspects once in a _base_ template, you avoid redundancy, ensure consistency, and simplify the maintenance of your website. + +Hugo can apply a _base_ template to the following template types: [home](#home), [page](#page), [section](#section), [taxonomy](#taxonomy), [term](#term), [single](#single), [list](#list), and [all](#all). When Hugo parses any of these template types, it will apply a _base_ template only if the template being parsed meets these specific conditions: + +- It must include at least one [`define`][] [action](g). +- It can only contain `define` actions, whitespace, and [template comments][]. No other content is allowed. + +> [!NOTE] +> If a template doesn't meet all these criteria, Hugo executes it exactly as provided, without applying a _base_ template. + +When Hugo applies a _base_ template, it replaces its [`block`][] actions with content from the corresponding `define` actions found in the template to which the base template is applied. + +For example, the _base_ template below calls the [`partial`][] function to include `head`, `header`, and `footer` elements. The `block` action acts as a placeholder, and its content will be replaced by a matching `define` action from the template to which it is applied. + +```go-html-template {file="layouts/baseof.html"} + + + + {{ partial "head.html" . }} + + +
    + {{ partial "header.html" . }} +
    +
    + {{ block "main" . }} + This will be replaced with content from the + corresponding "define" action found in the template + to which this base template is applied. + {{ end }} +
    +
    + {{ partial "footer.html" . }} +
    + + +``` + +```go-html-template {file="layouts/home.html"} +{{ define "main" }} + This will replace the content of the "block" action + found in the base template. +{{ end }} +``` + +## Home + +A _home_ template renders your site's home page. + +For example, Hugo applies a _base_ template to the _home_ template below, then renders the page content and a list of the site's regular pages. + +```go-html-template {file="layouts/home.html"} +{{ define "main" }} + {{ .Content }} + {{ range .Site.RegularPages }} +

    {{ .LinkTitle }}

    + {{ end }} +{{ end }} +``` + +{{% include "/_common/filter-sort-group.md" %}} + +## Page + +A _page_ template renders a regular page. + +For example, Hugo applies a _base_ template to the _page_ template below, then renders the page title and page content. + +```go-html-template {file="layouts/page.html"} +{{ define "main" }} +

    {{ .Title }}

    + {{ .Content }} +{{ end }} +``` + +## Section + +A _section_ template renders a list of pages within a [section](g). + +For example, Hugo applies a _base_ template to the _section_ template below, then renders the page title, page content, and a list of pages in the current section. + +```go-html-template {file="layouts/section.html"} +{{ define "main" }} +

    {{ .Title }}

    + {{ .Content }} + {{ range .Pages }} +

    {{ .LinkTitle }}

    + {{ end }} +{{ end }} +``` + +{{% include "/_common/filter-sort-group.md" %}} + +## Taxonomy + +A _taxonomy_ template renders a list of terms in a [taxonomy](g). + +For example, Hugo applies a _base_ template to the _taxonomy_ template below, then renders the page title, page content, and a list of [terms](g) in the current taxonomy. + +```go-html-template {file="layouts/taxonomy.html"} +{{ define "main" }} +

    {{ .Title }}

    + {{ .Content }} + {{ range .Pages }} +

    {{ .LinkTitle }}

    + {{ end }} +{{ end }} +``` + +{{% include "/_common/filter-sort-group.md" %}} + +Within a _taxonomy_ template, the [`Data`][] object provides these taxonomy-specific methods: + +- [`Singular`][taxonomy-singular] +- [`Plural`][taxonomy-plural] +- [`Terms`][] + +The `Terms` method returns a [taxonomy object](g), allowing you to call any of its methods including [`Alphabetical`][] and [`ByCount`][]. For example, use the `ByCount` method to render a list of terms sorted by the number of pages associated with each term: + +```go-html-template {file="layouts/taxonomy.html"} +{{ define "main" }} +

    {{ .Title }}

    + {{ .Content }} + {{ range .Data.Terms.ByCount }} +

    {{ .Page.LinkTitle }} ({{ .Count }})

    + {{ end }} +{{ end }} +``` + +## Term + +A _term_ template renders a list of pages associated with a [term](g). + +For example, Hugo applies a _base_ template to the _term_ template below, then renders the page title, page content, and a list of pages associated with the current term. + +```go-html-template {file="layouts/term.html"} +{{ define "main" }} +

    {{ .Title }}

    + {{ .Content }} + {{ range .Pages }} +

    {{ .LinkTitle }}

    + {{ end }} +{{ end }} +``` + +{{% include "/_common/filter-sort-group.md" %}} + +Within a _term_ template, the [`Data`][] object provides these term-specific methods: + +- [`Singular`][term-singular] +- [`Plural`][term-plural] +- [`Term`][] + +## Single + +A _single_ template is a fallback for a _page_ template. If a _page_ template does not exist, Hugo will look for a _single_ template instead. + +For example, Hugo applies a _base_ template to the _single_ template below, then renders the page title and page content. + +```go-html-template {file="layouts/single.html"} +{{ define "main" }} +

    {{ .Title }}

    + {{ .Content }} +{{ end }} +``` + +## List + +A _list_ template is a fallback for [home](#home), [section](#section), [taxonomy](#taxonomy), and [term](#term) templates. If one of these template types does not exist, Hugo will look for a _list_ template instead. + +For example, Hugo applies a _base_ template to the _list_ template below, then renders the page title, page content, and a list of pages. + +```go-html-template {file="layouts/list.html"} +{{ define "main" }} +

    {{ .Title }}

    + {{ .Content }} + {{ range .Pages }} +

    {{ .LinkTitle }}

    + {{ end }} +{{ end }} +``` + +## All + +An _all_ template is a fallback for [home](#home), [page](#page), [section](#section), [taxonomy](#taxonomy), [term](#term), [single](#single), and [list](#list) templates. If one of these template types does not exist, Hugo will look for an _all_ template instead. + +For example, Hugo applies a _base_ template to the _all_ template below, then conditionally renders a page based on its page kind. + +```go-html-template {file="layouts/all.html"} +{{ define "main" }} + {{ if eq .Kind "home" }} + {{ .Content }} + {{ range .Site.RegularPages }} +

    {{ .LinkTitle }}

    + {{ end }} + {{ else if eq .Kind "page" }} +

    {{ .Title }}

    + {{ .Content }} + {{ else if in (slice "section" "taxonomy" "term") .Kind }} +

    {{ .Title }}

    + {{ .Content }} + {{ range .Pages }} +

    {{ .LinkTitle }}

    + {{ end }} + {{ else }} + {{ errorf "Unsupported page kind: %s" .Kind }} + {{ end }} +{{ end }} +``` + +## Partial + +A _partial_ template is typically used to render a component of your site, though you may also create _partial_ templates that return values. + +For example, the _partial_ template below renders copyright information: + +```go-html-template {file="layouts/_partials/footer.html"} +

    Copyright {{ now.Year }}. All rights reserved.

    +``` + +Execute the _partial_ template by calling the [`partial`][] or [`partialCached`][] function, optionally passing context as the second argument: + +```go-html-template {file="layouts/baseof.html"} +{{ partial "footer.html" . }} +``` + + +Unlike other template types, Hugo does not consider the current page kind, content type, logical path, language, or output format when searching for a matching _partial_ template. However, it _does_ apply the same _name_ matching logic it uses for other template types. This means it tries to find the most specific match first, then progressively looks for more general versions if the specific one isn't found. + +For example, with this call: + +```go-html-template {file="layouts/baseof.html"} +{{ partial "footer.section.de.html" . }} +``` + +Hugo uses this lookup order to find a matching template: + +1. `layouts/_partials/footer.section.de.html` +1. `layouts/_partials/footer.section.html` +1. `layouts/_partials/footer.de.html` +1. `layouts/_partials/footer.html` + +A _partial_ template can also be defined inline within another template. However, it's important to note that the template namespace is global; ensuring unique names for these _partial_ templates is necessary to prevent conflicts. + +```go-html-template +Value: {{ partial "my-inline-partial.html" . }} + +{{ define "_partials/my-inline-partial.html" }} + {{ $value := 32 }} + {{ return $value }} +{{ end }} +``` + +## Content view + +A _content view_ template is similar to a _partial_ template, invoked by calling the [`Render`][] method on a `Page` object. Unlike _partial_ templates, _content view_ templates: + +- Inherit the context of the current page +- Can target any page kind, content type, logical path, language, or output format +- Can reside at any level within the `layouts` directory + +For example, Hugo applies a _base_ template to the _home_ template below, then renders the page content and a card component for each page within the `films` section of your site. + +```go-html-template {file="layouts/home.html"} +{{ define "main" }} + {{ .Content }} +
      + {{ range where site.RegularPages "Section" "films" }} + {{ .Render "view_card" }} + {{ end }} +
    +{{ end }} +``` + +```go-html-template {file="layouts/films/view_card.html"} +
    +

    {{ .LinkTitle }}

    + {{ .Summary }} +
    +``` + +In the example above, the content view template's name starts with `view_`. While not strictly required, this naming convention helps distinguish content view templates from other templates within the same directory, improving organization and clarity. + +## Render hook + +A _render hook_ template overrides the conversion of Markdown to HTML. + +For example, the _render hook_ template below adds an anchor link to the right of each heading. + +```go-html-template {file="layouts/_markup/render-heading.html"} + + {{ .Text }} + # + +``` + +Learn more about [render hook templates][]. + +## Shortcode + +A _shortcode_ template is used to render a component of your site. Unlike _partial_ or _content view_ templates, _shortcode_ templates are called from content pages. + +For example, the _shortcode_ template below renders an audio element from 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: + +```md {file="content/example.md"} +{{}} +``` + +Learn more about [shortcode templates][]. + +## Other + +Use other specialized templates to create: + +- [Sitemaps][] +- [RSS feeds][] +- [404 error pages][] +- [robots.txt files][] + +[404 error pages]: /templates/404/ +[RSS feeds]: /templates/rss/ +[Sitemaps]: /templates/sitemap/ +[`Alphabetical`]: /methods/taxonomy/alphabetical/ +[`ByCount`]: /methods/taxonomy/bycount/ +[`Data`]: /methods/page/data/ +[`Render`]: /methods/page/render/ +[`Term`]: /methods/page/data/#term +[`Terms`]: /methods/page/data/#terms +[`block`]: /functions/go-template/block/ +[`define`]: /functions/go-template/define/ +[`partialCached`]: /functions/partials/includeCached/ +[`partial`]: /functions/partials/include/ +[render hook templates]: /render-hooks/ +[robots.txt files]: /templates/robots/ +[shortcode templates]: /templates/shortcode/ +[taxonomy-plural]: /methods/page/data/#plural +[taxonomy-singular]: /methods/page/data/#singular +[template comments]: /templates/introduction/#comments +[template lookup order]: /templates/lookup-order/ +[term-plural]: /methods/page/data/#plural-1 +[term-singular]: /methods/page/data/#singular-1 diff --git a/docs/content/en/tools/_index.md b/docs/content/en/tools/_index.md new file mode 100644 index 0000000..3acc287 --- /dev/null +++ b/docs/content/en/tools/_index.md @@ -0,0 +1,7 @@ +--- +title: Developer tools +description: Third-party tools to help you create and manage sites. +categories: [] +keywords: [] +weight: 10 +--- diff --git a/docs/content/en/tools/editors.md b/docs/content/en/tools/editors.md new file mode 100644 index 0000000..f5d02a3 --- /dev/null +++ b/docs/content/en/tools/editors.md @@ -0,0 +1,62 @@ +--- +title: Editor plugins +description: The Hugo community uses a wide range of tools and has developed plugins for some of the most popular text editors to help automate parts of your workflow. +categories: [] +keywords: [] +weight: 10 +--- + +## Visual Studio Code + +[gotmplfmt](https://marketplace.visualstudio.com/items?itemName=GoHugoIO.gotmplfmt) +: Developed and maintained by the Hugo authors, this extension formats [templates](g) using the [gotmplfmt](https://github.com/gohugoio/gotmplfmt) CLI. + +[Front Matter](https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter) +: This extension maintains article metadata such as creation date, modified date, slug, title, SEO check, and more. + +[Hugo Helper](https://marketplace.visualstudio.com/items?itemName=rusnasonov.vscode-hugo) +: This extension provides some useful commands. The source code is available on its [GitHub repository](https://github.com/rusnasonov/vscode-hugo). + +[Hugo Language and Syntax Support](https://marketplace.visualstudio.com/items?itemName=budparr.language-hugo-vscode) +: This extension provides syntax highlighting and snippets. The source code is available on its [GitHub repository](https://github.com/budparr/language-hugo-vscode). + +[Hugo Shortcodes](https://marketplace.visualstudio.com/items?itemName=thuliteio.hugo-shortcodes) +: This extension adds syntax highlighting and intelligent completions for shortcodes in Markdown, including shortcode names and arguments discovered from workspace templates. The source code is available on its [GitHub repository](https://github.com/thuliteio/hugo-shortcodes). + +[Hugo Themer](https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-hugo-themer) +: This extension simplifies theme development, making it easy to navigate theme files. + +[Hugofy](https://marketplace.visualstudio.com/items?itemName=akmittal.hugofy) +: This extension streamlines project development. The source code is available on its [GitHub repository](https://github.com/akmittal/hugofy-vscode). + +[Syntax Highlighting for Hugo Shortcodes](https://marketplace.visualstudio.com/items?itemName=kaellarkin.hugo-shortcode-syntax) +: This extension adds syntax highlighting for [shortcodes](g), making visual identification of individual pieces easier. + +## JetBrains IDEs + +[Smart Hugo](https://smarthugo.dev) +: This plugin for IntelliJ IDEA, WebStorm, PhpStorm, and other JetBrains IDEs adds template support including syntax highlighting, actions completion, code formatting, and optional advanced features. + +## Emacs + +[emacs-easy-hugo](https://github.com/masasam/emacs-easy-hugo) +: This major Emacs mode supports writing blogs using various markup formats, including Markdown, Org mode, AsciiDoc, reStructuredText, mmark, and HTML. + +[ox-hugo.el](https://ox-hugo.scripter.co) +: This native Org mode exporter exports to Blackfriday Markdown with [front-matter](g). It supports two common Org blogging flows: exporting multiple Org subtrees in a single file to multiple posts, and exporting a single Org file to a single post. It also leverages the Org tag and property inheritance features. See [Why ox-hugo?](https://ox-hugo.scripter.co/doc/why-ox-hugo/) for more. + +## Sublime Text + +[Hugo Snippets](https://packagecontrol.io/packages/Hugo%20Snippets) +: This plugin adds automatic snippets. + +[Hugofy](https://github.com/akmittal/Hugofy) +: This plugin streamlines project development. + +## Vim + +[Vim Hugo Helper](https://github.com/robertbasic/vim-hugo-helper) +: This plugin facilitates authoring pages and blog posts. + +[vim-hugo](https://github.com/phelipetls/vim-hugo) +: This plugin provides syntax highlighting for templates and a few other features. diff --git a/docs/content/en/tools/front-ends.md b/docs/content/en/tools/front-ends.md new file mode 100644 index 0000000..3bf2f6c --- /dev/null +++ b/docs/content/en/tools/front-ends.md @@ -0,0 +1,51 @@ +--- +title: Front-end interfaces +linkTitle: Front-ends +description: Do you prefer a graphical user interface over a text editor? Give these front-ends a try. +categories: [] +keywords: [] +weight: 20 +aliases: [/tools/frontends/] +--- + +## Commercial + +[CloudCannon][] +: The intuitive Git-based CMS for your Hugo website. CloudCannon syncs changes from your Git repository and pushes content changes back, so your development and content teams are always in sync. Edit all of your content on the page with visual editing, build entire pages with reusable custom components and then publish confidently. + +[DatoCMS][] +: DatoCMS is a fully customizable administrative area for your static websites. Use your favorite website generator, let your clients publish new content independently, and the host the site anywhere you like. + +[GitCMS][] +: GitCMS is an AI-focused CMS for markdown-based content sites that brings together Content Agents for ChatGPT and Claude using MCP app, a friendly notion-like interface for non-technical team members, and a structured editorial publishing workflow. It is built for teams that want to manage blogs, documentation, changelogs, and other markdown content with the speed of AI assistance and the reliability of a review-driven publishing process. + +[PubCrank][] +: PubCrank is a static site editor which lets you define templates for different front matter layouts in your site. This gives writers an easy-to-use visual interface to create and edit content while maintaining the guardrails that the developer has created. PubCrank is free for local editing. + +[Sitepins][] +: Sitepins is a Git-based CMS built for static site generators like Hugo. It offers a clean visual editor, media management, role-based permissions, shortcode support, and more. To get started, simply connect your GitHub repository, configure your content folders, and start visually editing your Hugo site with Sitepins. + +## Open-source + +[Decap CMS][] +: Decap CMS is an open-source, serverless solution for managing Git based content in static sites, and it works on any platform that can host static sites. A [Hugo/Decap CMS starter][] is available to get new projects running quickly. + +[Pages CMS][] +: Pages CMS is an open-source, Git-based CMS for static sites and apps. It lets you edit Hugo content stored in a GitHub repository through a web interface, with support for front matter fields, media management, rich text editing, etc. + +[Quiqr Desktop][] +: Quiqr Desktop is a open-source, cross-platform, offline desktop CMS for Hugo with built-in Git functionality for deploying static sites to any hosting server. + +[Sveltia CMS][] +: Sveltia CMS is a drop-in replacement for Decap CMS which is built from the ground up with powerful and performant modern UI library Svelte. Sveltia CMS incorporates i18n into every corner of the product, while striving to radically improve UX, performance and productivity. + +[CloudCannon]: https://cloudcannon.com/hugo-cms/ +[DatoCMS]: https://www.datocms.com +[Decap CMS]: https://decapcms.org/ +[GitCMS]: https://gitcms.dev +[Hugo/Decap CMS starter]: https://github.com/decaporg/one-click-hugo-cms +[Pages CMS]: https://pagescms.org/ +[PubCrank]: https://www.pubcrank.com/ +[Quiqr Desktop]: https://quiqr.org/ +[Sitepins]: https://sitepins.com +[Sveltia CMS]: https://github.com/sveltia/sveltia-cms/ diff --git a/docs/content/en/tools/migrations.md b/docs/content/en/tools/migrations.md new file mode 100644 index 0000000..8e10224 --- /dev/null +++ b/docs/content/en/tools/migrations.md @@ -0,0 +1,129 @@ +--- +title: Migrate to Hugo +linkTitle: Migrations +description: A list of community-developed tools for migrating from your existing static site generator or content management system to Hugo. +categories: [] +keywords: [] +weight: 40 +aliases: [/developer-tools/migrations/, /developer-tools/migrated/] +--- + +This section highlights some independently developed projects related to Hugo. These tools extend functionality or help you to get started. + +Take a look at this list of migration tools if you currently use other blogging tools like Jekyll or WordPress but intend to switch to Hugo instead. They'll help you export your content into Hugo-friendly formats. + +## Jekyll + +Alternatively, you can use the [Jekyll import command](/commands/hugo_import_jekyll/). + +[JekyllToHugo][] +: A Small script for converting Jekyll blog posts to a Hugo site. + +[ConvertToHugo][] +: Convert your blog from Jekyll to Hugo. + +## Octopress + +[octohug][] +: Octopress to Hugo migrator. + +## DokuWiki + +[dokuwiki-to-hugo][] +: Migrates your DokuWiki source pages from [DokuWiki syntax][] to Hugo Markdown syntax. Includes extras like the TODO plugin. Written with extensibility in mind using Python 3. Also generates a TOML header for each page. Designed to copy-paste the wiki directory into your `content` directory. + +## WordPress + +[wordpress-to-hugo-exporter][] +: A one-click WordPress plugin that converts all posts, pages, taxonomies, metadata, and settings to Markdown and YAML which can be dropped into Hugo. (Note: If you have trouble using this plugin, you can [export your site for Jekyll][] and use Hugo's built-in Jekyll converter listed above.) + +[blog2md][] +: Works with [exported xml](https://en.support.wordpress.com/export/) file of your free YOUR-TLD.wordpress.com website. It also saves approved comments to `YOUR-POST-NAME-comments.md` file along with posts. + +[wordhugopress][] +: A small utility written in Java that exports the entire WordPress site from the database and resource (e.g., images) files stored locally or remotely. Therefore, migration from the backup files is possible. Supports merging multiple WordPress sites into a single Hugo site. + +[wp2hugo][] +: A Go-based CLI tool to migrate WordPress websites to Hugo. It preserves original URLs, GUIDs, image URLs, code highlights, tables of contents, and WordPress navigation categories. It migrates WordPress custom post types, custom taxonomies, custom fields, and page hierarchy. It supports translated WordPress blogs via Polylang or WPML. It imports a WordPress media library database with original titles and dates. The tool can download all media or only media inserted into pages from the original server. It converts WordPress shortcodes and Gutenberg blocks to Hugo shortcodes including galleries, images, audio, YouTube embeds, Gists, and Google Maps. + +## Medium + +[medium2md][] +: A simple Medium to Hugo exporter able to import stories in one command, including front matter. + +[medium-to-hugo][] +: A CLI tool written in Go to export medium posts into a Hugo-compatible Markdown format. Tags and images are included. All images will be downloaded locally and linked appropriately. + +## Tumblr + +[tumblr-importr][] +: An importer that uses the Tumblr API to create a Hugo static site. + +[tumblr2hugomarkdown][] +: Export all your Tumblr content to Hugo Markdown files with preserved original formatting. + +[Tumblr to Hugo][] +: A migration tool that converts each of your Tumblr posts to a content file with a proper title and path. It also generates a CSV file to help you set up URL redirects. + +## Drupal + +[drupal2hugo][] +: Convert a Drupal site to Hugo. + +## Joomla + +[hugojoomla][] +: This utility written in Java takes a Joomla database and converts all the content into Markdown files. It changes any URLs that are in Joomla's internal format and converts them to a suitable form. + +## Blogger + +[blogimport][] +: A tool to import from Blogger posts to Hugo. + +[blogger-to-hugo][] +: Another tool to import Blogger posts to Hugo. It also downloads embedded images so they will be stored locally. + +[blog2md][] +: Works with [exported xml](https://support.google.com/blogger/answer/41387?hl=en) file of your YOUR-TLD.blogspot.com website. It also saves comments to `YOUR-POST-NAME-comments.md` file along with posts. + +[BloggerToHugo][] +: Yet another tool to import Blogger posts to Hugo. For Windows platform only, and .NET Framework 4.5 is required. See README.md before using this tool. + +[blogger2hugo][] +: Converts a Blogger backup file (`.atom`) from [Google Takeout][] to Markdown (`.md`) files. The tool generates output compatible with the Hugo `content/` structure. + +## Contentful + +[contentful-hugo][] +: A tool to create content-files for Hugo from content on [Contentful][]. + +## BlogML + +[BlogML2Hugo][] +: A tool that helps you convert BlogML xml file to Hugo Markdown files. Users need to take care of links to attachments and images by themselves. This helps the blogs that export BlogML files (e.g. BlogEngine.NET) transform to hugo sites easily. + +[BlogML2Hugo]: https://github.com/jijiechen/BlogML2Hugo +[BloggerToHugo]: https://github.com/huanlin/blogger-to-hugo +[Contentful]: https://www.contentful.com/ +[ConvertToHugo]: https://github.com/coderzh/ConvertToHugo +[DokuWiki syntax]: https://www.dokuwiki.org/wiki:syntax +[Google Takeout]: https://takeout.google.com/takeout/custom/blogger?hl=en +[JekyllToHugo]: https://github.com/fredrikloch/JekyllToHugo +[Tumblr to Hugo]: https://github.com/jipiboily/tumblr-to-hugo +[blog2md]: https://github.com/palaniraja/blog2md +[blogger-to-hugo]: https://pypi.org/project/blogger-to-hugo/ +[blogger2hugo]: https://github.com/noorkhafidzin/blogger2hugo +[blogimport]: https://github.com/natefinch/blogimport +[contentful-hugo]: https://github.com/ModiiMedia/contentful-hugo +[dokuwiki-to-hugo]: https://github.com/wgroeneveld/dokuwiki-to-hugo +[drupal2hugo]: https://github.com/danapsimer/drupal2hugo +[export your site for Jekyll]: https://wordpress.org/plugins/jekyll-exporter/ +[hugojoomla]: https://github.com/davetcc/hugojoomla +[medium-to-hugo]: https://github.com/bgadrian/medium-to-hugo +[medium2md]: https://github.com/gautamdhameja/medium-2-md +[octohug]: https://github.com/codebrane/octohug +[tumblr-importr]: https://github.com/carlmjohnson/tumblr-importr +[tumblr2hugomarkdown]: https://github.com/Wysie/tumblr2hugomarkdown +[wordhugopress]: https://github.com/nantipov/wordhugopress +[wordpress-to-hugo-exporter]: https://github.com/SchumacherFM/wordpress-to-hugo-exporter +[wp2hugo]: https://github.com/ashishb/wp2hugo diff --git a/docs/content/en/tools/other.md b/docs/content/en/tools/other.md new file mode 100644 index 0000000..3fdc4c3 --- /dev/null +++ b/docs/content/en/tools/other.md @@ -0,0 +1,29 @@ +--- +title: Other community projects +linkTitle: Other projects +description: Some interesting projects developed by the Hugo community that don't quite fit into our other developer tool categories. +categories: [] +keywords: [] +weight: 50 +--- + +And for all the other community projects around Hugo: + +- [diego][] - A CLI tool that integrates with Hugo to assist in importing and utilizing exported social media data from popular services on Hugo websites. +- [Emacs Easy Hugo][] - Emacs package for writing blog posts in Markdown or org-mode and building your project with Hugo. +- [HugoPhotoSwipe][] - Make it easy to create image galleries using PhotoSwipe. +- [JAMStack Themes][] - A collection of site themes filterable by static site generator and supported CMS to help build CMS-connected sites using Hugo (linking to Hugo-specific themes). +- [flickr-hugo-embed][] - Print shortcodes to embed a set of images from an album on Flickr into Hugo. +- [hugo-gallery][] - Create an image gallery for Hugo sites. +- [hugo-openapispec-shortcode][] - A shortcode that allows you to include [Open API Spec][] (formerly known as Swagger Spec) in a page. +- [plausible-hugo][] - Easy Hugo integration for Plausible Analytics, a simple, open-source, lightweight and privacy-friendly web analytics alternative to Google Analytics. + +[Emacs Easy Hugo]: https://github.com/masasam/emacs-easy-hugo +[HugoPhotoSwipe]: https://github.com/GjjvdBurg/HugoPhotoSwipe +[JAMStack Themes]: https://jamstackthemes.dev/ssg/hugo/ +[Open API Spec]: https://openapis.org +[diego]: https://github.com/ttybitnik/diego +[flickr-hugo-embed]: https://github.com/nikhilm/flickr-hugo-embed +[hugo-gallery]: https://github.com/icecreammatt/hugo-gallery +[hugo-openapispec-shortcode]: https://github.com/tenfourty/hugo-openapispec-shortcode +[plausible-hugo]: https://github.com/divinerites/plausible-hugo diff --git a/docs/content/en/tools/search.md b/docs/content/en/tools/search.md new file mode 100644 index 0000000..5d84386 --- /dev/null +++ b/docs/content/en/tools/search.md @@ -0,0 +1,73 @@ +--- +title: Search tools +linkTitle: Search +description: See some of the open-source and commercial search options for your newly created Hugo website. +categories: [] +keywords: [] +weight: 30 +--- + +A static website with a dynamic search function? Yes, Hugo provides an alternative to embeddable scripts from Google or other search engines for static websites. Hugo allows you to provide your visitors with a custom search function by indexing your content files directly. + +## Open-source + +[Pagefind][] +: A fully static search library that aims to perform well on large sites, while using as little of your users' bandwidth as possible. + +[GitHub Gist for Hugo Workflow][] +: This gist contains a simple workflow to create a search index for your static website. It uses a simple Grunt script to index all your content files and [lunr.js][] to serve the search results. + +[hugo-lunr][] +: A simple way to add site search to your static Hugo site using [lunr.js][]. Hugo-lunr will create an index file of any HTML and Markdown documents in your Hugo project. + +[hugo-lunr-zh][] +: A bit like Hugo-lunr, but Hugo-lunr-zh can help you separate the Chinese keywords. + +[GitHub Gist for Fuse.js integration][] +: This gist demonstrates how to leverage Hugo's existing build time processing to generate a searchable JSON index used by [Fuse.js][] on the client side. Although this gist uses Fuse.js for fuzzy matching, any client-side search tool capable of reading JSON indexes will work. Does not require npm, grunt, or other build-time tools except Hugo! + +[hugo-search-index][] +: A library containing Gulp tasks and a prebuilt browser script that implements search. Gulp generates a search index from project Markdown files. + +[hugofastsearch][] +: A usability and speed update to "GitHub Gist for Fuse.js integration" — global, keyboard-optimized search. + +[JS & Fuse.js tutorial][] +: A simple client-side search solution, using FuseJS (does not require jQuery). + +[Hugo Lyra][] +: Hugo-Lyra is a JavaScript module to integrate [Lyra][] into a Hugo website. It contains the server-side part to generate the index and the client-side library (optional) to bootstrap the search engine easily. + +[INFINI Pizza for WebAssembly][] +: Pizza is a super-lightweight yet fully featured search engine written in Rust. You can quickly add offline search functionality to your Hugo website in just five minutes with only three lines of code. For a step-by-step guide on integrating it with Hugo, check out [this blog tutorial][]. + +## Commercial + +[Algolia DocSearch][] +: Algolia DocSearch is free for public technical documentation sites and easy to set up. For other use cases, [Algolia's Search API][] makes it easy to deliver a great search experience in your apps and websites. Algolia Search provides hosted full-text, numerical, faceted, and geolocalized search. + +[Bonsai][] +: Bonsai is a fully-managed hosted Elasticsearch service that is fast, reliable, and simple to set up. Easily ingest your docs from Hugo into Elasticsearch following [this guide from the docs][]. + +[ExpertRec][] +: ExpertRec is a hosted search-as-a-service solution that is fast and scalable. Set-up and integration is extremely easy and takes only a few minutes. The search settings can be modified without coding using a dashboard. + +[Algolia DocSearch]: https://docsearch.algolia.com/ +[Algolia's Search API]: https://www.algolia.com +[Bonsai]: https://www.bonsai.io +[ExpertRec]: https://www.expertrec.com/ +[Fuse.js]: https://fusejs.io/ +[GitHub Gist for Fuse.js integration]: https://gist.github.com/eddiewebb/735feb48f50f0ddd65ae5606a1cb41ae +[GitHub Gist for Hugo Workflow]: https://gist.github.com/sebz/efddfc8fdcb6b480f567 +[Hugo Lyra]: https://github.com/paolomainardi/hugo-lyra +[INFINI Pizza for WebAssembly]: https://github.com/infinilabs/pizza-docsearch +[JS & Fuse.js tutorial]: https://makewithhugo.com/add-search-to-a-hugo-site/ +[Lyra]: https://github.com/LyraSearch/lyra +[Pagefind]: https://github.com/cloudcannon/pagefind +[hugo-lunr-zh]: https://www.npmjs.com/package/hugo-lunr-zh +[hugo-lunr]: https://www.npmjs.com/package/hugo-lunr +[hugo-search-index]: https://www.npmjs.com/package/hugo-search-index +[hugofastsearch]: https://gist.github.com/cmod/5410eae147e4318164258742dd053993 +[lunr.js]: https://lunrjs.com/ +[this blog tutorial]: https://dev.to/medcl/adding-search-functionality-to-a-hugo-static-site-based-on-infini-pizza-for-webassembly-4h5e +[this guide from the docs]: https://bonsai.io/docs/hugo diff --git a/docs/content/en/troubleshooting/_index.md b/docs/content/en/troubleshooting/_index.md new file mode 100644 index 0000000..fd51b4f --- /dev/null +++ b/docs/content/en/troubleshooting/_index.md @@ -0,0 +1,8 @@ +--- +title: Troubleshooting +description: Use these techniques when troubleshooting your site. +categories: [] +keywords: [] +weight: 10 +aliases: [/templates/template-debugging/] +--- diff --git a/docs/content/en/troubleshooting/audit/index.md b/docs/content/en/troubleshooting/audit/index.md new file mode 100644 index 0000000..ce674ea --- /dev/null +++ b/docs/content/en/troubleshooting/audit/index.md @@ -0,0 +1,69 @@ +--- +title: Site audit +linkTitle: Audit +description: Run this audit before deploying your production site. +categories: [] +keywords: [] +--- + +There are several conditions that can produce errors in your published site which are not detected during the build. Run this audit before your final build. + +```sh {copy=true} +HUGO_MINIFY_TDEWOLFF_HTML_KEEPCOMMENTS=true HUGO_ENABLEMISSINGTRANSLATIONPLACEHOLDERS=true hugo && grep -inorE "<\!-- raw HTML omitted -->|ZgotmplZ|\[i18n\]|\(\)|(<nil>)|hahahugo" public/ +``` + +_Tested with GNU Bash 5.1 and GNU grep 3.7._ + +## Example output + +![site audit terminal output](screen-capture.png) + +## Explanation + +### Environment variables + +`HUGO_MINIFY_TDEWOLFF_HTML_KEEPCOMMENTS=true` +: Retain HTML comments even if minification is enabled. This takes precedence over `minify.tdewolff.html.keepComments` in your project configuration. If you minify without keeping HTML comments when performing this audit, you will not be able to detect when raw HTML has been omitted. + +`HUGO_ENABLEMISSINGTRANSLATIONPLACEHOLDERS=true` +: Show a placeholder instead of the default value or an empty string if a translation is missing. This takes precedence over `enableMissingTranslationPlaceholders` in your project configuration. + +### Grep options + +`-i, --ignore-case` +: Ignore case distinctions in patterns and input data, so that characters that differ only in case match each other. + +`-n, --line-number` +: Prefix each line of output with the 1-based line number within its input file. + +`-o, --only-matching` +: Print only the matched (non-empty) parts of a matching line, with each such part on a separate output line. + +`-r, --recursive` +: Read all files under each directory, recursively, following symbolic links only if they are on the command line. + +`-E, --extended-regexp` +: Interpret PATTERNS as extended regular expressions. + +### Patterns + +`` +: By default, Hugo strips raw HTML from your Markdown prior to rendering, and leaves this HTML comment in its place. + +`ZgotmplZ` +: ZgotmplZ is a special value that indicates that unsafe content reached a CSS or URL context at runtime. See [details][]. + +`[i18n]` +: This is the placeholder produced instead of the default value or an empty string if a translation is missing. + +`()` +: This string will appear in the rendered HTML when passing a nil value to the `printf` function. + +`(<nil>)` +: Same as above when the value returned from the `printf` function has not been passed through the [`safe.HTML`][] function. + +`HAHAHUGO` +: Under certain conditions a rendered shortcode may include all or a portion of the string HAHAHUGOSHORTCODE in either uppercase or lowercase. This is difficult to detect in all circumstances, but a case-insensitive search of the output for `HAHAHUGO` is likely to catch the majority of cases without producing false positives. + +[`safe.HTML`]: /functions/safe/html/ +[details]: https://pkg.go.dev/html/template diff --git a/docs/content/en/troubleshooting/audit/screen-capture.png b/docs/content/en/troubleshooting/audit/screen-capture.png new file mode 100644 index 0000000..221abff Binary files /dev/null and b/docs/content/en/troubleshooting/audit/screen-capture.png differ diff --git a/docs/content/en/troubleshooting/deprecation.md b/docs/content/en/troubleshooting/deprecation.md new file mode 100644 index 0000000..8e5815c --- /dev/null +++ b/docs/content/en/troubleshooting/deprecation.md @@ -0,0 +1,51 @@ +--- +title: Deprecation +description: The Hugo project follows a formal and consistent process to deprecate functions, methods, and configuration settings. +categories: [] +keywords: [] +--- + +When a project _deprecates_ something, they are telling its users: + +1. Don't use Thing One anymore. +1. Use Thing Two instead. +1. We're going to remove Thing One at some point in the future. + +Common [reasons for deprecation][]: + +- A feature has been replaced by a more powerful alternative. +- A feature contains a design flaw. +- A feature is considered extraneous, and will be removed in the future in order to simplify the system as a whole. +- A future version of the software will make major structural changes, making it impossible or impractical to support older features. +- Standardization or increased consistency in naming. +- A feature that once was available only independently is now combined with its co-feature. + +After the project team deprecates something in code, Hugo will: + +1. Log an INFO message for 3 minor releases[^1] +1. Log a WARN message for another 12 minor releases +1. Log an ERROR message and fail the build thereafter + +The project team will: + +1. On the deprecation date, update the documentation with a note describing the deprecation and any relevant alternatives. +1. Remove the code six or more minor releases after Hugo begins logging ERROR messages and failing the build. At that point, Hugo will throw an error, but the error message will no longer mention the deprecation. +1. Remove the corresponding documentation two years after the deprecation date. + +To see the INFO messages, you must use the `--logLevel` command line flag: + +```sh +hugo build --logLevel info +``` + +To limit the output to deprecation notices: + +```sh +hugo build --logLevel info | grep deprecate +``` + +Run the above command every time you upgrade Hugo. + +[^1]: For example, v0.1.1 => v0.2.0 is a minor release. + +[reasons for deprecation]: https://en.wikipedia.org/wiki/Deprecation diff --git a/docs/content/en/troubleshooting/faq.md b/docs/content/en/troubleshooting/faq.md new file mode 100644 index 0000000..08ec15e --- /dev/null +++ b/docs/content/en/troubleshooting/faq.md @@ -0,0 +1,112 @@ +--- +title: Frequently asked questions +linkTitle: FAQs +description: These questions are frequently asked by new users. +categories: [] +keywords: [] +--- + +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. + +These are just a few of the questions most frequently asked by new users. + +An error message indicates that a feature is not available. Why? +: When you attempt to use a feature that is not available in the edition that you installed, Hugo throws this error: + + ```go-html-template + this feature is not available in this edition of Hugo + ``` + + To resolve, install a different edition. See the [installation][] section for details. + +Why do I see "Page Not Found" when visiting the home page? +: In the `content/_index.md` file: + + - Is `draft` set to `true`? + - Is the `date` in the future? + - Is the `publishDate` in the future? + - Is the `expiryDate` in the past? + + If the answer to any of these questions is yes, either change the field values, or use one of these command line flags: `--buildDrafts`, `--buildFuture`, or `--buildExpired`. + +Why is a given page not published? +: In the `content/section/page.md` file, or in the `content/section/page/index.md` file: + + - Is `draft` set to `true`? + - Is the `date` in the future? + - Is the `publishDate` in the future? + - Is the `expiryDate` in the past? + + If the answer to any of these questions is yes, either change the field values, or use one of these command line flags: `--buildDrafts`, `--buildFuture`, or `--buildExpired`. + +Why can't I see any of a page's descendants? +: You may have an `index.md` file instead of an `_index.md` file. See [details][page bundles]. + +What is the difference between an `index.md` file and an `_index.md` file? +: A directory with an `index.md file` is a [leaf bundle](g). A directory with an `_index.md` file is a [branch bundle](g). See [details][page bundles]. + +Why is my _partial_ template not rendered as expected? +: You may have neglected to pass the required [context](g) when calling the partial. For example: + + ```go-html-template + {{/* incorrect */}} + {{ partial "pagination.html" }} + + {{/* correct */}} + {{ partial "pagination.html" . }} + ``` + +In a template, what's the difference between `:=` and `=` when assigning values to variables? +: Use `:=` to initialize a variable, and use `=` to assign a value to a variable that has been previously initialized. See [details][variables]. + +When I paginate a list page, why is the page collection not filtered as specified? +: You are probably invoking the [`Paginate`][] or [`Paginator`][] method more than once on the same page. See [details][pagination]. + +Why are there two ways to call a shortcode? +: Use the `{{%/* shortcode */%}}` notation if the _shortcode_ template, or the content between the opening and closing shortcode tags, contains Markdown. Otherwise use the\ +`{{}}` notation. See [details][notation]. + +Can I use environment variables to control configuration? +: Yes. See [details][environment-variables]. + +Why am I seeing inconsistent output from one build to the next? +: The most common causes are page collisions (publishing two pages to the same path) and the effects of concurrency. Use the `--printPathWarnings` command line flag to check for page collisions, and create a topic on the [forum][] if you suspect concurrency problems. + +Why isn't Hugo's development server detecting file changes? +: In its default configuration, Hugo's file watcher may not be able detect file changes when: + + - Running Hugo within Windows Subsystem for Linux (WSL/WSL2) with project files on a Windows partition + - Running Hugo locally with project files on a removable drive + - Running Hugo locally with project files on a storage server accessed via the NFS, SMB, or CIFS protocols + + In these cases, instead of monitoring native file system events, use the `--poll` command line flag. For example, to poll the project files every 700 milliseconds, use `--poll 700ms`. + +Why is my page Store missing a value? +: The [`Store`][] method on a `Page` object creates a persistent data structure for storing and manipulating keyed values on the given page. Values are often set within a _shortcode_ template, a _partial_ template called by a _shortcode_ template, or by a _render hook_ template. In all three cases, the stored values are not determinate until Hugo renders the page content. + + If you need to access a stored 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 trigger content rendering with other methods as well. See next FAQ. + +Which page methods trigger content rendering? +: The following methods on a `Page` object trigger content rendering: `Content`, `ContentWithoutSummary`, `FuzzyWordCount`, `Len`, `Plain`, `PlainWords`, `ReadingTime`, `Summary`, `Truncated`, and `WordCount`. + +> [!NOTE] +> For other questions please visit the [forum][]. 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. + +[`Paginate`]: /methods/page/paginate/ +[`Paginator`]: /methods/page/paginator/ +[`Store`]: /methods/page/store/ +[environment-variables]: /configuration/introduction/#environment-variables +[forum]: https://discourse.gohugo.io +[installation]: /installation/ +[notation]: /content-management/shortcodes/#notation +[page bundles]: /content-management/page-bundles/ +[pagination]: /templates/pagination/ +[requesting help]: https://discourse.gohugo.io/t/requesting-help/9132 +[variables]: https://pkg.go.dev/text/template#hdr-Variables diff --git a/docs/content/en/troubleshooting/inspection.md b/docs/content/en/troubleshooting/inspection.md new file mode 100644 index 0000000..fc45597 --- /dev/null +++ b/docs/content/en/troubleshooting/inspection.md @@ -0,0 +1,44 @@ +--- +title: Data inspection +linkTitle: Inspection +description: Use template functions to inspect values and data structures. +categories: [] +keywords: [] +--- + +Use the [`debug.Dump`][] function to inspect a data structure: + +```go-html-template +
    {{ debug.Dump .Params }}
    +``` + +```text +{ + "date": "2023-11-10T15:10:42-08:00", + "draft": false, + "iscjklanguage": false, + "lastmod": "2023-11-10T15:10:42-08:00", + "publishdate": "2023-11-10T15:10:42-08:00", + "tags": [ + "foo", + "bar" + ], + "title": "My first post" +} +``` + +Use the [`printf`][] function (render) or [`warnf`][] function (log to console) to inspect simple data structures. The layout string below displays both value and data type. + +```go-html-template +{{ $value := 42 }} +{{ printf "%[1]v (%[1]T)" $value }} → 42 (int) +``` + +{{< new-in 0.146.0 />}} + +Use the [`templates.Current`][] function to visually mark template execution boundaries or to display the template call stack. + +[`debug.Dump`]: /functions/debug/dump/ +[`printf`]: /functions/fmt/printf/ +[`templates.Current`]: /functions/templates/current/ +[`warnf`]: /functions/fmt/warnf/ diff --git a/docs/content/en/troubleshooting/logging.md b/docs/content/en/troubleshooting/logging.md new file mode 100644 index 0000000..e6c0294 --- /dev/null +++ b/docs/content/en/troubleshooting/logging.md @@ -0,0 +1,65 @@ +--- +title: Logging +description: Enable logging to inspect events while building your project. +categories: [] +keywords: [] +--- + +## Command line + +Enable console logging with the `--logLevel` command line flag. + +Hugo has four logging levels: + +`error` +: Display error messages only. + + ```sh + hugo build --logLevel error + ``` + +`warn` +: Display warning and error messages. + + ```sh + hugo build --logLevel warn + ``` + +`info` +: Display information, warning, and error messages. + + ```sh + hugo build --logLevel info + ``` + +`debug` +: Display debug, information, warning, and error messages. + + ```sh + hugo build --logLevel debug + ``` + +> [!NOTE] +> If you do not specify a logging level with the `--logLevel` flag, warnings and errors are always displayed. + +## Template functions + +You can also use template functions to print warnings or errors to the console. These functions are typically used to report data validation errors, missing files, etc. + +{{% render-list-of-pages-in-section path=/functions/fmt filter=functions_fmt_logging filterType=include %}} + +## LiveReload + +To log Hugo's LiveReload requests in your browser, add this query string to the URL when running Hugo's development server: + +```text +debug=LR-verbose +``` + +For example: + +```text +http://localhost:1313/?debug=LR-verbose +``` + +Then monitor the reload requests in your browser's dev tools console. Make sure the dev tools "preserve log" option is enabled. diff --git a/docs/content/en/troubleshooting/performance.md b/docs/content/en/troubleshooting/performance.md new file mode 100644 index 0000000..cbd634f --- /dev/null +++ b/docs/content/en/troubleshooting/performance.md @@ -0,0 +1,103 @@ +--- +title: Performance +description: Tools and suggestions for evaluating and improving performance. +categories: [] +keywords: [] +aliases: [/troubleshooting/build-performance/] +--- + +## Virus scanning + +Virus scanners are an essential component of system protection, but the performance impact can be severe for applications like Hugo that frequently read and write to disk. For example, with Microsoft Defender Antivirus, build times for some sites may increase by 400% or more. + +Before building a site, your virus scanner has already evaluated the files in your project directory. Scanning them again while building the site is superfluous. To improve performance, add Hugo's executable to your virus scanner's process exclusion list. + +For example, with Microsoft Defender Antivirus: + +**Start** > **Settings** > **Privacy & security** > **Windows Security** > **Open Windows Security** > **Virus & threat protection** > **Manage settings** > **Add or remove exclusions** > **Add an exclusion** > **Process** + +Then type `hugo.exe` add press the **Add** button. + +> [!NOTE] +> Virus scanning exclusions are common, but use caution when changing these settings. See the [Microsoft Defender Antivirus documentation][] for details. + +Other virus scanners have similar exclusion mechanisms. See their respective documentation. + +## Template metrics + +Hugo is fast, but inefficient templates impede performance. Enable template metrics to determine which templates take the most time, and to identify caching opportunities: + +```sh +hugo build --templateMetrics --templateMetricsHints +``` + +The result will look something like this: + +```text +Template Metrics: + + cumulative average maximum cache percent cached total + duration duration duration potential cached count count template + ---------- -------- -------- --------- ------- ------ ----- -------- + 36.037476822s 135.990478ms 225.765245ms 11 0 0 265 _partials/head.html + 35.920040902s 164.018451ms 233.475072ms 0 0 0 219 articles/page.html + 34.163268129s 128.917992ms 224.816751ms 23 0 0 265 _partials/head/meta/opengraph.html + 1.041227437s 3.92916ms 186.303376ms 47 0 0 265 _partials/head/meta/schema.html + 805.628827ms 27.780304ms 114.678523ms 0 0 0 29 section.html + 624.08354ms 15.221549ms 108.420729ms 8 0 0 41 _partials/utilities/render-page-collection.html + 545.968801ms 775.523µs 105.045775ms 0 0 0 704 summary.html + 334.680981ms 1.262947ms 127.412027ms 100 0 0 265 _partials/head/js.html + 272.763205ms 2.050851ms 24.371757ms 0 0 0 133 _markup/render-codeblock.html + 163.951469ms 14.904679ms 70.267953ms 0 0 0 11 articles/section.html + 153.07021ms 577.623µs 73.593597ms 100 0 0 265 _partials/head/init.html + 150.910984ms 150.910984ms 150.910984ms 0 0 0 1 page.html + 146.785804ms 146.785804ms 146.785804ms 0 0 0 1 contact.html + 115.364617ms 115.364617ms 115.364617ms 0 0 0 1 authors/term.html + 87.392071ms 329.781µs 10.687132ms 100 0 0 265 _partials/head/css.html + 86.803122ms 86.803122ms 86.803122ms 0 0 0 1 home.html +``` + +From left to right, the columns represent: + +cumulative duration +: The cumulative time spent executing the template. + +average duration +: The average time spent executing the template. + +maximum duration +: The maximum time spent executing the template. + +cache potential +: Displayed as a percentage, any _partial_ template with a 100% cache potential should be called with the [`partialCached`][] function instead of the [`partial`][] function. See the [caching](#caching) section below. + +percent cached +: The number of times the rendered templated was cached divided by the number of times the template was executed. + +cached count +: The number of times the rendered templated was cached. + +total count +: The number of times the template was executed. + +template +: The path to the template, relative to the `layouts` directory. + +> [!NOTE] +> Hugo builds pages in parallel where multiple pages are generated simultaneously. Because of this parallelism, the sum of "cumulative duration" values is usually greater than the actual time it takes to build a site. + +## Caching + +Some _partial_ templates such as sidebars or menus are executed many times during a site build. Depending on the content within the _partial_ template and the desired output, the template may benefit from caching to reduce the number of executions. The [`partialCached`][] function provides caching capabilities for _partial_ templates. + +> [!NOTE] +> Note that you can create cached variants of each partial by passing additional arguments to `partialCached` beyond the initial context. See the `partialCached` documentation for more details. + +## Timers + +Use the [`debug.Timer`][] function to determine execution time for a block of code, useful for finding performance bottlenecks in templates. + +[Microsoft Defender Antivirus documentation]: https://support.microsoft.com/en-us/topic/how-to-add-a-file-type-or-process-exclusion-to-windows-security-e524cbc2-3975-63c2-f9d1-7c2eb5331e53 +[`debug.Timer`]: /functions/debug/timer/ +[`partialCached`]: /functions/partials/includecached/ +[`partial`]: /functions/partials/include/ diff --git a/docs/data/articles.toml b/docs/data/articles.toml new file mode 100644 index 0000000..37b6692 --- /dev/null +++ b/docs/data/articles.toml @@ -0,0 +1,731 @@ +[[article]] + title = "A visit to the Workshop: Hugo/Unix/Vim integration" + url = "https://blog.afoolishmanifesto.com/posts/hugo-unix-vim-integration/" + author = "fREW Schmidt" + date = "2017-07-22" + +[[article]] + title = "Hugo Easy Gallery - Automagical PhotoSwipe image gallery with a one-line shortcode" + url = "https://www.liwen.id.au/heg/" + author = "Li-Wen Yip" + date = "2017-03-25" + +[[article]] + title = "Automagical Image Gallery in Hugo with PhotoSwipe and jQuery" + url = "https://www.liwen.id.au/photoswipe/" + author = "Li-Wen Yip" + date = "2017-03-04" + +[[article]] + title = "Adding Isso Comments to Hugo" + url = "https://stiobhart.net/2017-02-24-isso-comments/" + author = "Stíobhart Matulevicz" + date = "2017-02-24" + +[[article]] + title = "Hugo Tutorial: How to Build & Host a (Very Fast) Static E-Commerce Site" + url = "https://snipcart.com/blog/hugo-tutorial-static-site-ecommerce" + author = "Snipcart" + date = "2017-02-23" + +[[article]] + title = "How to Password Protect a Hugo Site" + url = "https://www.aerobatic.com/blog/password-protect-a-hugo-site/" + author = "Aerobatic" + date = "2017-02-19" + +[[article]] + title = "Switching from WordPress to Hugo" + url = "http://schnuddelhuddel.de/switching-from-wordpress-to-hugo/" + author = "Mario Martelli" + date = "2017-02-19" + +[[article]] + title = "Zero to HTTP/2 with AWS and Hugo" + url = "https://habd.as/zero-to-http-2-aws-hugo/" + author = "Josh Habdas" + date = "2017-02-16" + +[[article]] + title = "Deploy a Hugo site to Aerobatic with CircleCI" + url = "https://www.aerobatic.com/blog/hugo-github-circleci/" + author = "Aerobatic" + date = "2017-02-14" + +[[article]] + title = "NPM scripts for building and deploying Hugo site" + url = "https://www.aerobatic.com/blog/hugo-npm-buildtool-setup/" + author = "Aerobatic" + date = "2017-02-12" + +[[article]] + title = "Getting started with Hugo and the plain-blog theme, on NearlyFreeSpeech.Net" + url = "https://www.penwatch.net/cms/get_started_plain_blog/" + author = "Li-aung “Lewis” Yip" + date = "2017-02-12" + +[[article]] + title = "Choose Hugo over Jekyll" + url = "https://habd.as/choose-hugo-over-jekyll/" + author = "Josh Habdas" + date = "2017-02-10" + +[[article]] + title = "Build a Hugo site using Cloud9 IDE and host on App Engine" + url = "https://loyall.ch/lab/2017/01/build-a-static-website-with-cloud9-hugo-and-app-engine/" + author = "Pascal Aubort" + date = "2017-02-05" + +[[article]] + title = "Hugo Continuous Deployment with Bitbucket Pipelines and Aerobatic" + url = "https://www.aerobatic.com/blog/hugo-bitbucket-pipelines/" + author = "Aerobatic" + date = "2017-02-04" + +[[article]] + title = "How to use Firebase to host a Hugo site" + url = "https://code.selfmadefighter.com/post/static-site-firebase/" + author = "Andrew Cuga" + date= "2017-02-04" + +[[article]] + title = "A publishing workflow for teams using static site generators" + url = "https://www.keybits.net/post/publishing-workflow-for-teams-using-static-site-generators/" + author = "Tom Atkins" + date = "2017-01-02" + +[[article]] + title = "How To Dynamically Use Google Fonts In A Hugo Website" + url = "https://stoned.io/web-development/hugo/How-To-Dynamically-Use-Google-Fonts-In-A-Hugo-Website/" + author = "Hash Borgir" + date = "2016-10-27" + +[[article]] + title = "Embedding Facebook In A Hugo Template" + url = "https://stoned.io/web-development/hugo/Embedding-Facebook-In-A-Hugo-Template/" + author = "Hash Borgir" + date = "2016-10-22" + +[[article]] + title = "通过 Gitlab-cl 将 Hugo blog 自动部署至 GitHub (Chinese, Continuous integration)" + url = "https://zetaoyang.github.io/post/2016/10/17/gitlab-cl.html" + author = "Zetao Yang" + date = "2016-10-17" + +[[article]] + title = "A Step-by-Step Guide: Hugo on Netlify" + url = "https://www.netlify.com/blog/2016/09/21/a-step-by-step-guide-hugo-on-netlify/" + author = "Eli Williamson" + date = "2016-09-21" + +[[article]] + title = "Building our site: From Django & WordPress to a static generator (Part I)" + url = "https://tryolabs.com/blog/2016/09/20/building-our-site-django-wordpress-to-static-part-i/" + author = "Alan Descoins" + date = "2016-09-20" + +[[article]] + title = "Webseitenmaschine - Statische Websites mit Hugo erzeugen (German, $)" + url = "http://www.heise.de/ct/ausgabe/2016-12-Statische-Websites-mit-Hugo-erzeugen-3211704.html" + author = "Christian Helmbold" + date = "2016-05-27" + +[[article]] + title = "Cómo hacer sitios web estáticos con Hugo y Go - Platzi (Video tutorial)" + url = "https://www.youtube.com/watch?v=qaXXpdiCHXE" + author = "Verónica López" + date = "2016-04-06" + +[[article]] + title = "CDNOverview: A CDN comparison site made with Hugo" + url = "https://www.cloakfusion.com/cdnoverview-cdn-comparison-site-made-hugo/" + author = "Thijs de Zoete" + date = "2016-02-23" + +[[article]] + title = "Hugo: A Modern Website Engine That Just Works" + url = "https://github.com/shekhargulati/52-technologies-in-2016/blob/master/07-hugo/README.md" + author = "Shekhar Gulati" + date = "2016-02-14" + +[[article]] + title = "Minify Hugo Generated HTML" + url = "http://ratson.name/blog/minify-hugo-generated-html/" + author = "Ratson" + date = "2016-02-02" + +[[article]] + title = "HugoのデプロイをWerckerからCircle CIに変更した - log" + url = "http://log.deprode.net/logs/2016-01-17/" + author = "Deprode" + date = "2016-01-17" + +[[article]] + title = "Static site generators: el futuro de las webs estáticas
    (Hugo, Jekyll, Flask y otros)" + url = "http://sitelabs.es/static-site-generators-futuro-las-webs-estaticas/" + author = "Eneko Sarasola" + date = "2016-01-09" + +[[article]] + title = "Writing a Lambda Function for Hugo" + url = "https://blog.jolexa.net/post/writing-a-lambda-function-for-hugo/" + author = "Jeremy Olexa" + date = "2016-01-01" + +[[article]] + title = "Ein Blog mit Hugo erstellen - Tutorial (Deutsch/German)" + url = "http://privat.albicker.org/tags/hugo.html" + author = "Bernhard Albicker" + date = "2015-12-30" + +[[article]] + title = "How to host Hugo static website generator on AWS Lambda" + url = "http://bezdelev.com/post/hugo-aws-lambda-static-website/" + author = "Ilya Bezdelev" + date = "2015-12-15" + +[[article]] + title = "Migrating from Pelican to Hugo" + url = "http://www.softinio.com/post/migrating-from-pelican-to-hugo/" + author = "Salar Rahmanian" + date = "2015-11-29" + +[[article]] + title = "Static Website Generators Reviewed: Jekyll, Middleman, Roots, Hugo" + url = "http://www.smashingmagazine.com/2015/11/static-website-generators-jekyll-middleman-roots-hugo-review/" + author = "Mathias Biilmann Christensen" + date = "2015-11-16" + +[[article]] + title = "How To Deploy a Hugo Site to Production with Git Hooks on Ubuntu 14.04" + url = "https://www.digitalocean.com/community/tutorials/how-to-deploy-a-hugo-site-to-production-with-git-hooks-on-ubuntu-14-04" + author = "Justin Ellingwood" + date = "2015-11-12" + +[[article]] + title = "How To Install and Use Hugo, a Static Site Generator, on Ubuntu 14.04" + url = "https://www.digitalocean.com/community/tutorials/how-to-install-and-use-hugo-a-static-site-generator-on-ubuntu-14-04" + author = "Justin Ellingwood" + date = "2015-11-09" + +[[article]] + title = "Switching from Wordpress to Hugo" + url = "http://justinfx.com/2015/11/08/switching-from-wordpress-to-hugo/" + author = "Justin Israel" + date = "2015-11-08" + +[[article]] + title = "Hands-on Experience with Hugo as a Static Site Generator" + url = "http://usersnap.com/blog/hands-on-experience-with-hugo-static-site-generator/" + author = "Thomas Peham" + date = "2015-10-15" + +[[article]] + title = "Statische Webseites mit Hugo erstellen/Vortrag mit Foliensatz (deutsch)" + url = "http://sfd.koelnerlinuxtreffen.de/2015/HaraldWeidner/" + author = "Harald Weidner" + date = "2015-09-19" + +[[article]] + title = "Moving from WordPress to Hugo" + url = "http://abhipandey.com/2015/09/moving-to-hugo/" + author = "Abhishek Pandey" + date = "2015-09-15" + +[[article]] + title = "通过webhook将Hugo自动部署至GitHub Pages和GitCafe Pages (Automated deployment)" + url = "http://blog.coderzh.com/2015/09/13/use-webhook-automated-deploy-hugo/" + author = "CoderZh" + date = "2015-09-13" + +[[article]] + title = "使用hugo搭建个人博客站点 (Using Hugo to build a personal blog site)" + url = "http://blog.coderzh.com/2015/08/29/hugo/" + author = "CoderZh" + date = "2015-08-29" + +[[article]] + title = "Good-Bye Wordpress, Hello Hugo! (German)" + url = "http://blog.arminhanisch.de/2015/08/blog-migration-zu-hugo/" + author = "Armin Hanisch" + date = "2015-08-18" + +[[article]] + title = "Générer votre site web statique avec Hugo (Generate your static site with Hugo)" + url = "http://www.linux-pratique.com/?p=191" + author = "Benoît Benedetti" + date = "2015-06-26" + +[[article]] + title = "Hugo向けの新しいテーマを作った (I created a new theme for Hugo)" + url = "https://yet.unresolved.xyz/blog/2016/10/03/how-to-make-of-hugo-theme/" + author = "Daisuke Tsuji" + date = "2015-06-20" + +[[article]] + title = "Hugo - Gerando um site com conteúdo estático. (Portuguese Brazil)" + url = "http://blog.ffrizzo.com/posts/hugo/" + author = "Fabiano Frizzo" + date = "2015-06-02" + +[[article]] + title = "An Introduction to Static Site Generators" + url = "http://davidwalsh.name/introduction-static-site-generators" + author = "Eduardo Bouças" + date = "2015-05-20" + +[[article]] + title = "Hugo Still Rules" + url = "http://cheekycoder.com/2015/05/hugo-still-rules/" + author = "Cheeky Coder" + date = "2015-05-18" + +[[article]] + title = "hugo - Static Site Generator" + url = "http://gscacco.github.io/post/hugo/" + author = "G Scaccoio" + date = "2015-05-04" + +[[article]] + title = "WindowsでHugoを使う" + url = "http://ureta.net/2015/05/hugo-on-windows/" + author = "うれ太郎" + date = "2015-05-01" + +[[article]] + title = "Hugoのshortcodesを用いてサイトにスライドなどを埋め込む" + url = "http://blog.yucchiy.com/2015/04/29/hugo-shortcode/" + author = "Yucchiy" + date = "2015-04-29" + +[[article]] + title = "HugoとCircleCIでGitHub PagesにBlogを公開してみたら超簡単だった" + url = "http://hori-ryota.github.io/blog/create-blog-with-hugo-and-circleci/" + author = "Hori Ryota" + date = "2015-04-17" + +[[article]] + title = "10 Best Static Site Generators" + url = "http://beebom.com/2015/04/best-static-site-generators" + author = "Aniruddha Mysore" + date = "2015-04-06" + +[[article]] + title = "Goodbye WordPress; Hello Hugo" + url = "http://willwarren.com/2015/04/05/goodbye-wordpress-hello-hugo/" + author = "Will Warren" + date = "2015-04-05" + +[[article]] + title = "Static Websites with Hugo on Google Cloud Storage" + url = "http://www.moxie.io/post/static-websites-with-hugo-on-google-cloud-storage/" + author = "Moxie Input/Output" + date = "2015-04-02" + +[[article]] + title = "De nuevo iniciando un blog" + url = "https://alvarolizama.net/" + author = "Alvaro Lizama" + date = "2015-03-29" + +[[article]] + title = "We moved our blog from Posthaven to Hugo after only three posts. Why?" + url = "http://blog.hypriot.com/post/moved-from-posthaven-to-hugo/" + author = "Hypriot" + date = "2015-03-27" + +[[article]] + title = "Top Static Site Generators in 2015" + url = "http://superdevresources.com/static-site-generators-2015/" + author = "Kanishk Kunal" + date = "2015-03-12" + +[[article]] + title = "Moving to Hugo" + url = "http://abiosoft.com/moving-to-hugo/" + author = "Abiola Ibrahim" + date = "2015-03-08" + +[[article]] + title = "Migrating a blog (yes, this one!) from Wordpress to Hugo" + url = "http://justindunham.net/migrating-from-wordpress-to-hugo/" + author = "Justin Dunham" + date = "2015-02-13" + +[[article]] + title = "blogをoctopressからHugoに乗り換えたメモ" + url = "http://blog.jigyakkuma.org/2015/02/11/hugo/" + author = "jigyakkuma" + date = "2015-02-11" + +[[article]] + title = "Hugoでブログをつくった" + url = "http://porgy13.github.io/post/new-hugo-blog/" + author = "porgy13" + date = "2015-02-07" + +[[article]] + title = "Hugoにブログを移行した" + url = "http://keichi.net/post/first/" + author = "Keichi Takahashi" + date = "2015-02-04" + +[[article]] + title = "Hugo静态网站生成器中文教程" + url = "http://nanshu.wang/post/2015-01-31/" + author = "Nanshu Wang" + date = "2015-01-31" + +[[article]] + title = "Hugo + Github Pages + Wercker CI = ¥0(無料)
    でコマンド 1 発(自動化)でサイト
    ・ブログを公開・運営・分析・収益化
    " + url = "http://qiita.com/yoheimuta/items/8a619cac356bed89a4c9" + author = "Yohei Yoshimuta" + date = "2015-01-31" + +[[article]] + title = "Running Hugo websites on anynines" + url = "http://blog.anynines.com/running-hugo-websites-on-anynines/" + author = "Julian Weber" + date = "2015-01-30" + +[[article]] + title = "MiddlemanからHugoへ移行した" + url = "http://re-dzine.net/2015/01/hugo/" + author = "Haruki Konishi" + date = "2015-01-21" + +[[article]] + title = "WordPress から Hugo に乗り換えました" + url = "http://rakuishi.com/archives/wordpress-to-hugo/" + author = "rakuishi" + date = "2015-01-20" + +[[article]] + title = "HUGOを使ってサイトを立ち上げる方法" + url = "http://qiita.com/syui/items/869538099551f24acbbf" + author = "Syui" + date = "2015-01-17" + +[[article]] + title = "Jekyllが許されるのは小学生までだよね" + url = "http://t32k.me/mol/log/hugo/" + author = "Ishimoto Koji" + date = "2015-01-16" + +[[article]] + title = "Getting started with Hugo" + url = "http://anthonyfok.org/post/getting-started-with-hugo/" + author = "Anthony Fok" + date = "2015-01-12" + +[[article]] + title = "把这个博客静态化了 (Migrate to Hugo)" + url = "http://lich-eng.com/2015/01/03/migrate-to-hugo/" + author = "Li Cheng" + date = "2015-01-03" + +[[article]] + title = "Porting my blog with Hugo" + url = "http://blog.srackham.com/posts/porting-my-blog-with-hugo/" + author = "Stuart Rackham" + date = "2014-12-30" + +[[article]] + title = "Hugoを使ってみたときのメモ" + url = "http://machortz.github.io/posts/usinghugo/" + author = "Machortz" + date = "2014-12-29" + +[[article]] + title = "OctopressからHugoへ移行した" + url = "http://deeeet.com/writing/2014/12/25/hugo/" + author = "Taichi Nakashima" + date = "2014-12-25" + +[[article]] + title = "Migrating to Hugo From Octopress" + url = "http://nathanleclaire.com/blog/2014/12/22/migrating-to-hugo-from-octopress/" + author = "Nathan LeClaire" + date = "2014-12-22" + +[[article]] + title = "Dynamic Pages with GoHugo.io" + url = "http://cyrillschumacher.com/2014/12/21/dynamic-pages-with-gohugo.io/" + author = "Cyrill Schumacher" + date = "2014-12-21" + +[[article]] + title = "6 Static Blog Generators That Aren’t Jekyll" + url = "http://www.sitepoint.com/6-static-blog-generators-arent-jekyll/" + author = "David Turnbull" + date = "2014-12-08" + +[[article]] + title = "Travel Blogging Setup" + url = "http://www.stou.dk/2014/11/travel-blogging-setup/" + author = "Rasmus Stougaard" + date = "2014-11-23" + +[[article]] + title = "Hosting A Hugo Website Behind Nginx" + url = "http://www.bigbeeconsultants.co.uk/blog/hosting-hugo-website-behind-nginx" + author = "Rick Beton" + date = "2014-11-20" + +[[article]] + title = "使用Hugo搭建免费个人Blog (How to use Hugo)" + url = "http://ulricqin.com/post/how-to-use-hugo/" + author = "Ulric Qin 秦晓辉" + date = "2014-11-11" + +[[article]] + title = "Built in Speed and Built for Speed by Hugo" + url = "http://cheekycoder.com/2014/10/built-for-speed-by-hugo/" + author = "Cheeky Coder" + date = "2014-10-30" + +[[article]] + title = "Hugo para crear sitios web estáticos" + url = "http://www.webbizarro.com/noticias/1076/hugo-para-crear-sitios-web-estaticos/" + author = "Web Bizarro" + date = "2014-08-19" + +[[article]] + title = "Going with Hugo" + url = "http://www.markuseliasson.se/article/going-with-hugo/" + author = "Markus Eliasson" + date = "2014-08-18" + +[[article]] + title = "Benchmarking Jekyll, Hugo and Wintersmith" + url = "http://fredrikloch.me/post/2014-08-12-Jekyll-and-its-alternatives-from-a-site-generation-point-of-view/" + author = "Fredrik Loch" + date = "2014-08-12" + +[[article]] + title = "Goodbye Octopress, Hello Hugo!" + url = "http://andreimihu.com/blog/2014/08/11/goodbye-octopress-hello-hugo/" + author = "Andrei Mihu" + date = "2014-08-11" + +[[article]] + title = "Beautiful sites for Open Source Projects" + url = "http://beautifulopen.com/2014/08/09/hugo/" + author = "Beautiful Open" + date = "2014-08-09" + +[[article]] + title = "Hugo: Beyond the Defaults" + url = "http://npf.io/2014/08/hugo-beyond-the-defaults/" + author = "Nate Finch" + date = "2014-08-08" + +[[article]] + title = "First Impressions of Hugo" + url = "https://peteraba.com/blog/first-impressions-of-hugo/" + author = "Peter Aba" + date = "2014-06-06" + +[[article]] + title = "New Site Workflow" + url = "http://vurt.co.uk/post/new_website/" + author = "Giles Paterson" + date = "2014-08-05" + +[[article]] + title = "How I Learned to Stop Worrying and Love the (Static) Web" + url = "http://cognition.ca/post/about-hugo/" + author = "Joshua McKenty" + date = "2014-08-04" + +[[article]] + title = "Hugo - Static Site Generator" + url = "http://kenwoo.io/blog/hugo---static-site-generator/" + author = "Kenny Woo" + date = "2014-08-03" + +[[article]] + title = "Hugo Is Freakin' Awesome" + url = "http://npf.io/2014/08/hugo-is-awesome/" + author = "Nate Finch" + date = "2014-08-01" + +[[article]] + title = "再次搬家 (Move from WordPress to Hugo)" + url = "http://www.chingli.com/misc/move-from-wordpress-to-hugo/" + author = "青砾 (chingli)" + date = "2014-07-12" + +[[article]] + title = "Embedding Gists in Hugo" + url = "http://danmux.com/posts/embedded_gists/" + author = "Dan Mull" + date = "2014-07-05" + +[[article]] + title = "An Introduction To Hugo" + url = "http://www.cirrushosting.com/web-hosting-blog/an-introduction-to-hugo/" + author = "Dan Silber" + date = "2014-07-01" + +[[article]] + title = "Moving to Hugo" + url = "http://danmux.com/posts/hugo_based_blog/" + author = "Dan Mull" + date = "2014-05-29" + +[[article]] + title = "开源之静态站点生成器排行榜
    (Leaderboard of open-source static website generators)" + url = "http://code.csdn.net/news/2819909" + author = "CSDN.net" + date = "2014-05-23" + +[[article]] + title = "Finally, a satisfying and effective blog setup" + url = "http://michaelwhatcott.com/now-powered-by-hugo/" + author = "Michael Whatcott" + date = "2014-05-20" + +[[article]] + title = "Hugo from scratch" + url = "http://zackofalltrades.com/notes/2014/05/hugo-from-scratch/" + author = "Zack Williams" + date = "2014-05-18" + +[[article]] + title = "Why I switched away from Jekyll" + url = "http://www.jakejanuzelli.com/why-I-switched-away-from-jekyll/" + author = "Jake Januzelli" + date = "2014-05-10" + +[[article]] + title = "Welcome our new blog" + url = "http://blog.ninya.io/posts/welcome-our-new-blog/" + author = "Ninya.io" + date = "2014-04-11" + +[[article]] + title = "Mission Not Accomplished" + url = "http://johnsto.co.uk/blog/mission-not-accomplished/" + author = "Dave Johnston" + date = "2014-04-03" + +[[article]] + title = "Hugo - A Static Site Builder in Go" + url = "http://deepfriedcode.com/post/hugo/" + author = "Deep Fried Code" + date = "2014-03-30" + +[[article]] + title = "Adventures in Angular Podcast" + url = "http://devchat.tv/adventures-in-angular/003-aia-gdes" + author = "Matias Niemela" + date = "2014-03-28" + +[[article]] + title = "Hugo" + url = "http://bra.am/post/hugo/" + author = "bra.am" + date = "2014-03-23" + +[[article]] + title = "Converting Blogger To Markdown" + url = "http://trishagee.github.io/project/atom-to-hugo/" + author = "Trisha Gee" + date = "2014-03-20" + +[[article]] + title = "Moving to Hugo Static Web Pages" + url = "http://tepid.org/tech/hugo-web/" + author = "Tobias Weingartner" + date = "2014-03-16" + +[[article]] + title = "New Blog Engine: Hugo" + url = "https://blog.afoolishmanifesto.com/posts/hugo/" + author = "fREW Schmidt" + date = "2014-03-15" + +[[article]] + title = "Hugo + gulp.js = Huggle" + url = "http://ktmud.github.io/huggle/en/intro/)" + author = "Jesse Yang 杨建超" + date = "2014-03-08" + +[[article]] + title = "Powered by Hugo" + url = "http://kieranhealy.org/blog/archives/2014/02/24/powered-by-hugo/" + author = "Kieran Healy" + date = "2014-02-24" + +[[article]] + title = "静的サイトを素早く構築するために
    GoLangで作られたジェネレータHugo
    " + url = "http://hamasyou.com/blog/2014/02/21/hugo/" + author = "
    Shogo Hamada
    濱田章吾
    " + date = "2014-02-21" + +[[article]] + title = "Latest Roundup of Useful Tools For Developers" + url = "http://codegeekz.com/latest-roundup-of-useful-tools-for-developers/" + author = "CodeGeekz" + date = "2014-02-13" + +[[article]] + title = "Hugo: Static Site Generator written in Go" + url = "http://www.braveterry.com/2014/02/06/hugo-static-site-generator-written-in-go/" + author = "Brave Terry" + date = "2014-02-06" + +[[article]] + title = "10 Useful HTML5 Tools for Web Designers and Developers" + url = "http://designdizzy.com/10-useful-html5-tools-for-web-designers-and-developers/" + author = "Design Dizzy" + date = "2014-02-04" + +[[article]] + title = "Hugo – Fast, Flexible Static Site Generator" + url = "http://cube3x.com/hugo-fast-flexible-static-site-generator/" + author = "Joby Joseph" + date = "2014-01-18" + +[[article]] + title = "Hugo: A new way to build static website" + url = "http://www.w3update.com/opensource/hugo-a-new-way-to-build-static-website.html" + author = "w3update" + date = "2014-01-17" + +[[article]] + title = "Xaprb now uses Hugo" + url = "http://xaprb.com/blog/2014/01/15/using-hugo/" + author = "Baron Schwartz" + date = "2014-01-15" + +[[article]] + title = "New jQuery Plugins And Resources That Web Designers Need" + url = "http://www.designyourway.net/blog/resources/new-jquery-plugins-and-resources-that-web-designers-need/" + author = "Design Your Way" + date = "2014-01-01" + +[[article]] + title = "On Blog Construction" + url = "http://alexla.sh/post/on-blog-construction/" + author = "Alexander Lash" + date = "2013-12-27" + +[[article]] + title = "Hugo" + url = "http://onethingwell.org/post/69070926608/hugo" + author = "One Thing Well" + date = "2013-12-05" + +[[article]] + title = "In Praise Of Hugo" + url = "http://sound-guru.com/blog/post/hello-world/" + author = "sound-guru.com" + date = "2013-10-19" + +[[article]] + title = "Hosting a blog on S3 and Cloudfront" + url = "http://www.danesparza.net/2013/07/hosting-a-blog-on-s3-and-cloudfront/" + author = "Dan Esparza" + date = "2013-07-24" diff --git a/docs/data/docs.yaml b/docs/data/docs.yaml new file mode 100644 index 0000000..19b950d --- /dev/null +++ b/docs/data/docs.yaml @@ -0,0 +1,4992 @@ +chroma: + lexers: + - Aliases: + - abap + Name: ABAP + - Aliases: + - abnf + Name: ABNF + - Aliases: + - as + - actionscript + Name: ActionScript + - Aliases: + - as3 + - actionscript3 + Name: ActionScript 3 + - Aliases: + - ada + - ada95 + - ada2005 + Name: Ada + - Aliases: + - agda + Name: Agda + - Aliases: + - al + Name: AL + - Aliases: + - alloy + Name: Alloy + - Aliases: + - ampl + Name: AMPL + - Aliases: + - ng2 + Name: Angular2 + - Aliases: + - antlr + Name: ANTLR + - Aliases: + - apacheconf + - aconf + - apache + Name: ApacheConf + - Aliases: + - apl + Name: APL + - Aliases: + - applescript + Name: AppleScript + - Aliases: + - aql + Name: ArangoDB AQL + - Aliases: + - arduino + Name: Arduino + - Aliases: + - armasm + Name: ArmAsm + - Aliases: + - arturo + - art + Name: Arturo + - Aliases: + - atl + Name: ATL + - Aliases: + - autohotkey + - ahk + Name: AutoHotkey + - Aliases: + - autoit + Name: AutoIt + - Aliases: + - awk + - gawk + - mawk + - nawk + Name: Awk + - Aliases: + - ballerina + Name: Ballerina + - Aliases: + - bash + - sh + - ksh + - zsh + - shell + Name: Bash + - Aliases: + - bash-session + - console + - shell-session + Name: Bash Session + - Aliases: + - bat + - batch + - dosbatch + - winbatch + Name: Batchfile + - Aliases: + - beef + Name: Beef + - Aliases: + - bib + - bibtex + Name: BibTeX + - Aliases: + - bicep + Name: Bicep + - Aliases: + - blitzbasic + - b3d + - bplus + Name: BlitzBasic + - Aliases: + - bnf + Name: BNF + - Aliases: + - bqn + Name: BQN + - Aliases: + - brainfuck + - bf + Name: Brainfuck + - Aliases: + - c + Name: C + - Aliases: + - csharp + - 'c#' + Name: 'C#' + - Aliases: + - cpp + - c++ + Name: C++ + - Aliases: + - c3 + Name: C3 + - Aliases: + - caddyfile + - caddy + Name: Caddyfile + - Aliases: + - caddyfile-directives + - caddyfile-d + - caddy-d + Name: Caddyfile Directives + - Aliases: + - capnp + Name: Cap'n Proto + - Aliases: + - cassandra + - cql + Name: Cassandra CQL + - Aliases: + - ceylon + Name: Ceylon + - Aliases: + - cfengine3 + - cf3 + Name: CFEngine3 + - Aliases: + - cfs + Name: cfstatement + - Aliases: + - chai + - chaiscript + Name: ChaiScript + - Aliases: + - chapel + - chpl + Name: Chapel + - Aliases: + - cheetah + - spitfire + Name: Cheetah + - Aliases: + - clojure + - clj + - edn + Name: Clojure + - Aliases: + - cmake + Name: CMake + - Aliases: + - cobol + Name: COBOL + - Aliases: + - coffee-script + - coffeescript + - coffee + Name: CoffeeScript + - Aliases: + - common-lisp + - cl + - lisp + Name: Common Lisp + - Aliases: + - coq + Name: Coq + - Aliases: + - core + Name: Core + - Aliases: + - cr + - crystal + Name: Crystal + - Aliases: + - css + Name: CSS + - Aliases: + - csv + Name: CSV + - Aliases: + - cue + Name: CUE + - Aliases: + - cython + - pyx + - pyrex + Name: Cython + - Aliases: + - d + Name: D + - Aliases: + - dart + Name: Dart + - Aliases: + - dax + Name: Dax + - Aliases: + - desktop + - desktop_entry + Name: Desktop file + - Aliases: + - devicetree + - dts + Name: Devicetree + - Aliases: + - diff + - udiff + Name: Diff + - Aliases: + - django + - jinja + Name: Django/Jinja + - Aliases: + - zone + - bind + Name: dns + - Aliases: + - docker + - dockerfile + - containerfile + Name: Docker + - Aliases: + - dtd + Name: DTD + - Aliases: + - dylan + Name: Dylan + - Aliases: + - ebnf + Name: EBNF + - Aliases: + - elixir + - ex + - exs + Name: Elixir + - Aliases: + - elm + Name: Elm + - Aliases: + - emacs + - elisp + - emacs-lisp + Name: EmacsLisp + - Aliases: + - erb + - html+erb + - html+ruby + - rhtml + Name: ERB + - Aliases: + - erlang + Name: Erlang + - Aliases: + - factor + Name: Factor + - Aliases: + - fennel + - fnl + Name: Fennel + - Aliases: + - fish + - fishshell + Name: Fish + - Aliases: + - forth + Name: Forth + - Aliases: + - fortran + - f90 + Name: Fortran + - Aliases: + - fortranfixed + Name: FortranFixed + - Aliases: + - fsharp + Name: FSharp + - Aliases: + - gas + - asm + Name: GAS + - Aliases: + - gdscript + - gd + Name: GDScript + - Aliases: + - gdscript3 + - gd3 + Name: GDScript3 + - Aliases: + - gemfile-lock + - gemfilelock + Name: Gemfile.lock + - Aliases: + - gemtext + - gmi + - gmni + - gemini + Name: Gemtext + - Aliases: + - genshi + - kid + - xml+genshi + - xml+kid + Name: Genshi + - Aliases: + - html+genshi + - html+kid + Name: Genshi HTML + - Aliases: + - genshitext + Name: Genshi Text + - Aliases: + - pot + - po + Name: Gettext + - Aliases: + - cucumber + - Cucumber + - gherkin + - Gherkin + Name: Gherkin + - Aliases: + - gleam + Name: Gleam + - Aliases: + - glsl + Name: GLSL + - Aliases: + - gnuplot + Name: Gnuplot + - Aliases: + - go + - golang + Name: Go + - Aliases: + - go-html-template + Name: Go HTML Template + - Aliases: + - go-template + Name: Go Template + - Aliases: + - go-text-template + Name: Go Text Template + - Aliases: + - graphql + - graphqls + - gql + Name: GraphQL + - Aliases: + - groff + - nroff + - man + Name: Groff + - Aliases: + - groovy + Name: Groovy + - Aliases: + - handlebars + - hbs + Name: Handlebars + - Aliases: + - hare + Name: Hare + - Aliases: + - haskell + - hs + Name: Haskell + - Aliases: + - hx + - haxe + - hxsl + Name: Haxe + - Aliases: + - hcl + Name: HCL + - Aliases: + - hexdump + Name: Hexdump + - Aliases: + - hlb + Name: HLB + - Aliases: + - hlsl + Name: HLSL + - Aliases: + - holyc + Name: HolyC + - Aliases: + - html + Name: HTML + - Aliases: + - http + Name: HTTP + - Aliases: + - hylang + Name: Hy + - Aliases: + - idris + - idr + Name: Idris + - Aliases: + - igor + - igorpro + Name: Igor + - Aliases: + - ini + - cfg + - dosini + Name: INI + - Aliases: + - io + Name: Io + - Aliases: + - iscdhcpd + Name: ISCdhcpd + - Aliases: + - j + Name: J + - Aliases: + - janet + Name: Janet + - Aliases: + - java + Name: Java + - Aliases: + - js + - javascript + Name: JavaScript + - Aliases: + - json + - jsonl + Name: JSON + - Aliases: + - jsonata + Name: JSONata + - Aliases: + - jsonnet + Name: Jsonnet + - Aliases: + - julia + - jl + Name: Julia + - Aliases: + - jungle + Name: Jungle + - Aliases: + - kak + - kakoune + - kakrc + - kakscript + Name: Kakoune + - Aliases: + - kdl + Name: KDL + - Aliases: + - kotlin + Name: Kotlin + - Aliases: + - lateralus + - ltl + Name: Lateralus + - Aliases: + - lean4 + - lean + Name: Lean4 + - Aliases: + - lighty + - lighttpd + Name: Lighttpd configuration file + - Aliases: + - lilypond + Name: LilyPond + - Aliases: + - llvm + Name: LLVM + - Aliases: null + Name: lox + - Aliases: + - lua + Name: Lua + - Aliases: + - luau + Name: Luau + - Aliases: + - make + - makefile + - mf + - bsdmake + Name: Makefile + - Aliases: + - mako + Name: Mako + - Aliases: + - md + - mkd + Name: markdown + - Aliases: + - mess + Name: Markless + - Aliases: + - mason + Name: Mason + - Aliases: + - materialize + - mzsql + Name: Materialize SQL dialect + - Aliases: + - mathematica + - mma + - nb + Name: Mathematica + - Aliases: + - matlab + Name: Matlab + - Aliases: + - mcfunction + - mcf + Name: MCFunction + - Aliases: + - meson + - meson.build + Name: Meson + - Aliases: + - metal + Name: Metal + - Aliases: + - µcad + Name: microcad + - Aliases: + - minizinc + - MZN + - mzn + Name: MiniZinc + - Aliases: + - mlir + Name: MLIR + - Aliases: + - modelica + Name: Modelica + - Aliases: + - modula2 + - m2 + Name: Modula-2 + - Aliases: + - mojo + - 🔥 + Name: Mojo + - Aliases: + - monkeyc + Name: MonkeyC + - Aliases: + - moonbit + - mbt + Name: MoonBit + - Aliases: + - moonscript + - moon + Name: MoonScript + - Aliases: + - morrowind + - mwscript + Name: MorrowindScript + - Aliases: + - myghty + Name: Myghty + - Aliases: + - mysql + - mariadb + Name: MySQL + - Aliases: + - nasm + Name: NASM + - Aliases: + - natural + Name: Natural + - Aliases: + - ndisasm + Name: NDISASM + - Aliases: + - newspeak + Name: Newspeak + - Aliases: + - nginx + Name: Nginx configuration file + - Aliases: + - nim + - nimrod + Name: Nim + - Aliases: + - nixos + - nix + Name: Nix + - Aliases: + - nsis + - nsi + - nsh + Name: NSIS + - Aliases: + - nu + Name: Nu + - Aliases: + - objective-c + - objectivec + - obj-c + - objc + Name: Objective-C + - Aliases: + - objectpascal + Name: ObjectPascal + - Aliases: + - ocaml + Name: OCaml + - Aliases: + - octave + Name: Octave + - Aliases: + - odin + Name: Odin + - Aliases: + - ones + - onesenterprise + - 1S + - 1S:Enterprise + Name: OnesEnterprise + - Aliases: + - openedge + - abl + - progress + - openedgeabl + Name: OpenEdge ABL + - Aliases: + - openscad + Name: OpenSCAD + - Aliases: + - org + - orgmode + Name: Org Mode + - Aliases: + - pacmanconf + Name: PacmanConf + - Aliases: + - perl + - pl + Name: Perl + - Aliases: + - php + - php3 + - php4 + - php5 + Name: PHP + - Aliases: + - phtml + Name: PHTML + - Aliases: + - pig + Name: Pig + - Aliases: + - pkgconfig + Name: PkgConfig + - Aliases: + - plpgsql + Name: PL/pgSQL + - Aliases: + - text + - plain + - no-highlight + Name: plaintext + - Aliases: + - plutus-core + - plc + Name: Plutus Core + - Aliases: + - pony + Name: Pony + - Aliases: + - postgresql + - postgres + Name: PostgreSQL SQL dialect + - Aliases: + - postscript + - postscr + Name: PostScript + - Aliases: + - pov + Name: POVRay + - Aliases: + - powerquery + - pq + Name: PowerQuery + - Aliases: + - powershell + - posh + - ps1 + - psm1 + - psd1 + - pwsh + Name: PowerShell + - Aliases: + - prolog + Name: Prolog + - Aliases: + - promela + Name: Promela + - Aliases: + - promql + Name: PromQL + - Aliases: + - java-properties + Name: properties + - Aliases: + - protobuf + - proto + Name: Protocol Buffer + - Aliases: + - txtpb + Name: Protocol Buffer Text Format + - Aliases: + - prql + Name: PRQL + - Aliases: + - psl + Name: PSL + - Aliases: + - puppet + Name: Puppet + - Aliases: + - python + - py + - sage + - python3 + - py3 + - starlark + Name: Python + - Aliases: + - python2 + - py2 + Name: Python 2 + - Aliases: + - qbasic + - basic + Name: QBasic + - Aliases: + - qml + - qbs + Name: QML + - Aliases: + - splus + - s + - r + Name: R + - Aliases: + - racket + - rkt + Name: Racket + - Aliases: + - ragel + Name: Ragel + - Aliases: + - perl6 + - pl6 + - raku + Name: Raku + - Aliases: + - jsx + - react + Name: react + - Aliases: + - reason + - reasonml + Name: ReasonML + - Aliases: + - registry + Name: reg + - Aliases: + - rego + Name: Rego + - Aliases: + - rst + - rest + - restructuredtext + Name: reStructuredText + - Aliases: + - rexx + - arexx + Name: Rexx + - Aliases: + - rgbasm + Name: RGBDS Assembly + - Aliases: + - ring + Name: Ring + - Aliases: + - SQLRPGLE + - RPG IV + Name: RPGLE + - Aliases: + - spec + Name: RPMSpec + - Aliases: + - rb + - ruby + - duby + Name: Ruby + - Aliases: + - rust + - rs + Name: Rust + - Aliases: + - sas + Name: SAS + - Aliases: + - sass + Name: Sass + - Aliases: + - scala + Name: Scala + - Aliases: + - scdoc + Name: scdoc + - Aliases: + - scheme + - scm + Name: Scheme + - Aliases: + - scilab + Name: Scilab + - Aliases: + - scss + Name: SCSS + - Aliases: + - sed + - gsed + - ssed + Name: Sed + - Aliases: + - sieve + Name: Sieve + - Aliases: + - smali + Name: Smali + - Aliases: + - smalltalk + - squeak + - st + Name: Smalltalk + - Aliases: + - smarty + Name: Smarty + - Aliases: + - snbt + Name: SNBT + - Aliases: + - snobol + Name: Snobol + - Aliases: + - sol + - solidity + Name: Solidity + - Aliases: + - sp + Name: SourcePawn + - Aliases: + - spade + Name: Spade + - Aliases: + - sparql + Name: SPARQL + - Aliases: + - sql + Name: SQL + - Aliases: + - squidconf + - squid.conf + - squid + Name: SquidConf + - Aliases: + - sml + Name: Standard ML + - Aliases: null + Name: stas + - Aliases: + - stylus + Name: Stylus + - Aliases: + - svelte + Name: Svelte + - Aliases: + - swift + Name: Swift + - Aliases: + - systemd + Name: SYSTEMD + - Aliases: + - systemverilog + - sv + Name: systemverilog + - Aliases: + - tablegen + Name: TableGen + - Aliases: + - tal + - uxntal + Name: Tal + - Aliases: + - tasm + Name: TASM + - Aliases: + - tcl + Name: Tcl + - Aliases: + - tcsh + - csh + Name: Tcsh + - Aliases: + - templ + Name: Templ + - Aliases: + - termcap + Name: Termcap + - Aliases: + - terminfo + Name: Terminfo + - Aliases: + - terraform + - tf + - hcl + Name: Terraform + - Aliases: + - tex + - latex + Name: TeX + - Aliases: + - thrift + Name: Thrift + - Aliases: + - toml + Name: TOML + - Aliases: + - tradingview + - tv + Name: TradingView + - Aliases: + - tsql + - t-sql + Name: Transact-SQL + - Aliases: + - turing + Name: Turing + - Aliases: + - turtle + Name: Turtle + - Aliases: + - twig + Name: Twig + - Aliases: + - ts + - tsx + - typescript + Name: TypeScript + - Aliases: + - typoscript + Name: TypoScript + - Aliases: + - typoscriptcssdata + Name: TypoScriptCssData + - Aliases: + - typoscripthtmldata + Name: TypoScriptHtmlData + - Aliases: + - typst + Name: Typst + - Aliases: null + Name: ucode + - Aliases: + - v + - vlang + Name: V + - Aliases: + - vsh + - vshell + Name: V shell + - Aliases: + - vala + - vapi + Name: Vala + - Aliases: + - vb.net + - vbnet + Name: VB.net + - Aliases: + - verilog + - v + Name: verilog + - Aliases: + - vhdl + Name: VHDL + - Aliases: + - vhs + - tape + - cassette + Name: VHS + - Aliases: + - vim + Name: VimL + - Aliases: + - vue + - vuejs + Name: vue + - Aliases: null + Name: WDTE + - Aliases: + - wast + - wat + Name: WebAssembly Text Format + - Aliases: + - wgsl + Name: WebGPU Shading Language + - Aliases: + - vtt + Name: WebVTT + - Aliases: + - whiley + Name: Whiley + - Aliases: + - xml + Name: XML + - Aliases: + - xorg.conf + Name: Xorg + - Aliases: + - yaml + Name: YAML + - Aliases: + - yaml+jinja + - salt + - sls + - ansible + Name: YAML+Jinja + - Aliases: + - yang + Name: YANG + - Aliases: + - z80 + Name: Z80 Assembly + - Aliases: + - zed + Name: Zed + - Aliases: + - zig + Name: Zig + styles: + - counterpart: '' + mode: light + name: abap + - counterpart: '' + mode: light + name: algol + - counterpart: '' + mode: light + name: algol_nu + - counterpart: '' + mode: light + name: arduino + - counterpart: '' + mode: dark + name: ashen + - counterpart: '' + mode: dark + name: aura-theme-dark + - counterpart: '' + mode: dark + name: aura-theme-dark-soft + - counterpart: '' + mode: light + name: autumn + - counterpart: '' + mode: dark + name: average + - counterpart: '' + mode: dark + name: base16-snazzy + - counterpart: '' + mode: light + name: borland + - counterpart: '' + mode: light + name: bw + - counterpart: '' + mode: dark + name: catppuccin-frappe + - counterpart: catppuccin-mocha + mode: light + name: catppuccin-latte + - counterpart: '' + mode: dark + name: catppuccin-macchiato + - counterpart: catppuccin-latte + mode: dark + name: catppuccin-mocha + - counterpart: '' + mode: light + name: colorful + - counterpart: '' + mode: dark + name: darcula + - counterpart: '' + mode: dark + name: doom-one + - counterpart: '' + mode: dark + name: doom-one2 + - counterpart: '' + mode: dark + name: dracula + - counterpart: '' + mode: light + name: emacs + - counterpart: '' + mode: dark + name: evergarden + - counterpart: '' + mode: light + name: friendly + - counterpart: '' + mode: dark + name: fruity + - counterpart: github-dark + mode: light + name: github + - counterpart: github + mode: dark + name: github-dark + - counterpart: gruvbox-light + mode: dark + name: gruvbox + - counterpart: gruvbox + mode: light + name: gruvbox-light + - counterpart: '' + mode: light + name: hr_high_contrast + - counterpart: '' + mode: light + name: hrdark + - counterpart: '' + mode: light + name: igor + - counterpart: '' + mode: dark + name: kanagawa-dragon + - counterpart: kanagawa-wave + mode: light + name: kanagawa-lotus + - counterpart: kanagawa-lotus + mode: dark + name: kanagawa-wave + - counterpart: '' + mode: light + name: lovelace + - counterpart: '' + mode: light + name: manni + - counterpart: modus-vivendi + mode: light + name: modus-operandi + - counterpart: modus-operandi + mode: dark + name: modus-vivendi + - counterpart: monokailight + mode: dark + name: monokai + - counterpart: monokai + mode: light + name: monokailight + - counterpart: '' + mode: light + name: murphy + - counterpart: '' + mode: dark + name: native + - counterpart: '' + mode: dark + name: nord + - counterpart: '' + mode: dark + name: nordic + - counterpart: '' + mode: dark + name: onedark + - counterpart: '' + mode: light + name: onesenterprise + - counterpart: paraiso-light + mode: dark + name: paraiso-dark + - counterpart: paraiso-dark + mode: light + name: paraiso-light + - counterpart: '' + mode: light + name: pastie + - counterpart: '' + mode: light + name: perldoc + - counterpart: '' + mode: light + name: pygments + - counterpart: '' + mode: light + name: rainbow_dash + - counterpart: rose-pine-dawn + mode: dark + name: rose-pine + - counterpart: rose-pine + mode: light + name: rose-pine-dawn + - counterpart: '' + mode: dark + name: rose-pine-moon + - counterpart: '' + mode: light + name: rpgle + - counterpart: '' + mode: dark + name: rrt + - counterpart: solarized-light + mode: dark + name: solarized-dark + - counterpart: '' + mode: dark + name: solarized-dark256 + - counterpart: solarized-dark + mode: light + name: solarized-light + - counterpart: '' + mode: dark + name: swapoff + - counterpart: '' + mode: light + name: tango + - counterpart: tokyonight-night + mode: light + name: tokyonight-day + - counterpart: '' + mode: dark + name: tokyonight-moon + - counterpart: tokyonight-day + mode: dark + name: tokyonight-night + - counterpart: '' + mode: dark + name: tokyonight-storm + - counterpart: '' + mode: light + name: trac + - counterpart: '' + mode: dark + name: vim + - counterpart: '' + mode: light + name: vs + - counterpart: '' + mode: dark + name: vulcan + - counterpart: '' + mode: dark + name: witchhazel + - counterpart: xcode-dark + mode: light + name: xcode + - counterpart: xcode + mode: dark + name: xcode-dark +config: + HTTPCache: + cache: + for: + excludes: + - '**' + includes: null + polls: + - disable: true + for: + excludes: null + includes: + - '**' + high: 0s + low: 0s + respectCacheControlNoStoreInRequest: true + respectCacheControlNoStoreInResponse: false + archeTypeDir: archetypes + assetDir: assets + author: {} + baseURL: '' + build: + buildStats: + disableClasses: false + disableIDs: false + disableTags: false + enable: false + cacheBusters: + - source: '(postcss|tailwind)\.config\.(js|mjs|cjs)' + target: (css|styles|scss|sass) + noJSConfigInAssets: false + useResourceCacheWhen: fallback + buildDrafts: false + buildExpired: false + buildFuture: false + cacheDir: '' + caches: + assets: + dir: :resourceDir/_gen + maxAge: -1 + getresource: + dir: :cacheDir/:project + maxAge: -1 + images: + dir: :resourceDir/_gen + maxAge: -1 + misc: + dir: :cacheDir/:project + maxAge: -1 + modulegitinfo: + dir: :cacheDir/modules + maxAge: 24h + modulequeries: + dir: :cacheDir/modules + maxAge: 24h + modules: + dir: :cacheDir/modules + maxAge: -1 + canonifyURLs: false + capitalizeListTitles: true + cascade: null + cleanDestinationDir: false + contentDir: content + contentTypes: + text/asciidoc: {} + text/html: {} + text/markdown: {} + text/org: {} + text/pandoc: {} + text/rst: {} + copyright: '' + dataDir: data + defaultContentLanguage: en + defaultContentLanguageInSubdir: false + defaultContentRole: guest + defaultContentRoleInSubdir: false + defaultContentVersion: '' + defaultContentVersionInSubdir: false + defaultOutputFormat: html + deployment: + confirm: false + dryRun: false + force: false + invalidateCDN: true + matchers: null + maxDeletes: 256 + order: null + target: '' + targets: null + workers: 10 + disableAliases: false + disableDefaultLanguageRedirect: false + disableDefaultSiteRedirect: false + disableHugoGeneratorInject: false + disableKinds: null + disableLanguages: null + disableLiveReload: false + disablePathToLower: false + enableEmoji: false + enableGitInfo: false + enableMissingTranslationPlaceholders: false + enableRobotsTXT: false + environment: production + frontmatter: + date: + - date + - publishdate + - pubdate + - published + - lastmod + - modified + expiryDate: + - expirydate + - unpublishdate + lastmod: + - :git + - lastmod + - modified + - date + - publishdate + - pubdate + - published + publishDate: + - publishdate + - pubdate + - published + - date + hasCJKLanguage: false + i18nDir: i18n + ignoreCache: false + ignoreFiles: null + ignoreLogs: null + ignoreVendorPaths: '' + imaging: + anchor: smart + avif: + compression: lossy + encoderSpeed: 10 + hint: photo + quality: 60 + bgColor: ffffff + exif: + disableDate: false + disableLatLong: false + excludeFields: GPS|Exif|Exposure[M|P|B]|Contrast|Resolution|Sharp|JPEG|Metering|Sensing|Saturation|ColorSpace|Flash|WhiteBalance + includeFields: '' + jpeg: + quality: 75 + meta: + fields: + - '! *{GPS,Exif,Exposure[MPB],Contrast,Resolution,Sharp,JPEG,Metering,Sensing,Saturation,ColorSpace,Flash,WhiteBalance}*' + sources: + - exif + - iptc + resampleFilter: box + webp: + compression: lossy + hint: photo + method: 2 + quality: 75 + useSharpYuv: false + languages: + en: + direction: '' + disabled: false + label: '' + locale: '' + title: '' + weight: 0 + layoutDir: layouts + locale: '' + mainSections: null + markup: + asciiDocExt: + attributes: {} + backend: html5 + extensions: [] + failureLevel: fatal + noHeaderOrFooter: true + preserveTOC: false + safeMode: unsafe + sectionNumbers: false + trace: false + verbose: false + workingFolderCurrent: false + defaultMarkdownHandler: goldmark + goldmark: + duplicateResourceFiles: false + extensions: + cjk: + eastAsianLineBreaks: false + eastAsianLineBreaksStyle: simple + enable: false + escapedSpace: false + definitionList: true + extras: + delete: + enable: false + insert: + enable: false + mark: + enable: false + subscript: + enable: false + superscript: + enable: false + footnote: + backlinkHTML: '↩︎' + enable: true + enableAutoIDPrefix: false + linkify: true + linkifyProtocol: https + passthrough: + delimiters: + block: [] + inline: [] + enable: false + strikethrough: true + table: true + taskList: true + typographer: + apostrophe: '’' + disable: false + ellipsis: '…' + emDash: '—' + enDash: '–' + leftAngleQuote: '«' + leftDoubleQuote: '“' + leftSingleQuote: '‘' + rightAngleQuote: '»' + rightDoubleQuote: '”' + rightSingleQuote: '’' + parser: + attribute: + block: false + title: true + autoDefinitionTermID: false + autoHeadingID: true + autoIDType: github + wrapStandAloneImageWithinParagraph: true + renderHooks: + image: + enableDefault: false + useEmbedded: auto + link: + enableDefault: false + useEmbedded: auto + renderer: + hardWraps: false + unsafe: false + xhtml: false + highlight: + anchorLineNos: false + codeFences: true + guessSyntax: false + hl_Lines: '' + hl_inline: false + lineAnchors: '' + lineNoStart: 1 + lineNos: false + lineNumbersInTable: true + noClasses: true + style: monokai + tabWidth: 4 + wrapperClass: highlight + tableOfContents: + endLevel: 3 + ordered: false + startLevel: 2 + mediaTypes: + application/json: + delimiter: . + suffixes: + - json + application/manifest+json: + delimiter: . + suffixes: + - webmanifest + application/octet-stream: + delimiter: . + application/pdf: + delimiter: . + suffixes: + - pdf + application/rss+xml: + delimiter: . + suffixes: + - xml + - rss + application/toml: + delimiter: . + suffixes: + - toml + application/wasm: + delimiter: . + suffixes: + - wasm + application/xml: + delimiter: . + suffixes: + - xml + application/yaml: + delimiter: . + suffixes: + - yaml + - yml + font/otf: + delimiter: . + suffixes: + - otf + font/ttf: + delimiter: . + suffixes: + - ttf + image/avif: + delimiter: . + suffixes: + - avif + image/bmp: + delimiter: . + suffixes: + - bmp + image/gif: + delimiter: . + suffixes: + - gif + image/heic: + delimiter: . + suffixes: + - heic + image/heif: + delimiter: . + suffixes: + - heif + image/jpeg: + delimiter: . + suffixes: + - jpg + - jpeg + - jpe + - jif + - jfif + image/png: + delimiter: . + suffixes: + - png + image/svg+xml: + delimiter: . + suffixes: + - svg + image/tiff: + delimiter: . + suffixes: + - tif + - tiff + image/webp: + delimiter: . + suffixes: + - webp + text/asciidoc: + delimiter: . + suffixes: + - adoc + - asciidoc + - ad + text/calendar: + delimiter: . + suffixes: + - ics + text/css: + delimiter: . + suffixes: + - css + text/csv: + delimiter: . + suffixes: + - csv + text/html: + delimiter: . + suffixes: + - html + - htm + text/javascript: + delimiter: . + suffixes: + - js + - jsm + - mjs + text/jsx: + delimiter: . + suffixes: + - jsx + text/markdown: + delimiter: . + suffixes: + - md + - mdown + - markdown + text/org: + delimiter: . + suffixes: + - org + text/pandoc: + delimiter: . + suffixes: + - pandoc + - pdc + text/plain: + delimiter: . + suffixes: + - txt + text/rst: + delimiter: . + suffixes: + - rst + text/tsx: + delimiter: . + suffixes: + - tsx + text/typescript: + delimiter: . + suffixes: + - ts + text/x-gotmpl: + delimiter: . + suffixes: + - gotmpl + text/x-sass: + delimiter: . + suffixes: + - sass + text/x-scss: + delimiter: . + suffixes: + - scss + video/3gpp: + delimiter: . + suffixes: + - 3gpp + - 3gp + video/mp4: + delimiter: . + suffixes: + - mp4 + video/mpeg: + delimiter: . + suffixes: + - mpg + - mpeg + video/ogg: + delimiter: . + suffixes: + - ogv + video/webm: + delimiter: . + suffixes: + - webm + video/x-msvideo: + delimiter: . + suffixes: + - avi + menus: {} + minify: + disableCSS: false + disableHTML: false + disableJS: false + disableJSON: false + disableSVG: false + disableXML: false + minifyOutput: false + tdewolff: + css: + inline: false + precision: 0 + version: 0 + html: + keepComments: false + keepConditionalComments: false + keepDefaultAttrVals: true + keepDocumentTags: true + keepEndTags: true + keepQuotes: false + keepSpecialComments: true + keepWhitespace: false + templateDelims: + - '' + - '' + js: + keepVarNames: false + precision: 0 + version: 2022 + json: + keepNumbers: false + precision: 0 + svg: + keepComments: false + keepNamespaces: + - '' + - x-bind + precision: 0 + xml: + keepWhitespace: false + module: + auth: '' + hugoVersion: + extended: false + max: '' + min: '' + imports: null + mounts: + - disableWatch: false + files: null + sites: + complements: + languages: null + roles: null + versions: null + matrix: + languages: null + roles: null + versions: null + source: content + target: content + - disableWatch: false + files: null + sites: + complements: + languages: null + roles: null + versions: null + matrix: + languages: null + roles: null + versions: null + source: data + target: data + - disableWatch: false + files: null + sites: + complements: + languages: null + roles: null + versions: null + matrix: + languages: null + roles: null + versions: null + source: layouts + target: layouts + - disableWatch: false + files: null + sites: + complements: + languages: null + roles: null + versions: null + matrix: + languages: null + roles: null + versions: null + source: i18n + target: i18n + - disableWatch: false + files: null + sites: + complements: + languages: null + roles: null + versions: null + matrix: + languages: null + roles: null + versions: null + source: archetypes + target: archetypes + - disableWatch: false + files: null + sites: + complements: + languages: null + roles: null + versions: null + matrix: + languages: null + roles: null + versions: null + source: assets + target: assets + - disableWatch: false + files: null + sites: + complements: + languages: null + roles: null + versions: null + matrix: + languages: null + roles: null + versions: null + source: static + target: static + noProxy: none + noVendor: '' + params: null + private: '*.*' + proxy: direct + replacements: null + vendorClosest: false + workspace: 'off' + newContentEditor: '' + noBuildLock: false + noChmod: false + noTimes: false + outputFormats: + '404': + baseName: '' + isHTML: true + isPlainText: false + mediaType: text/html + noUgly: false + notAlternative: true + path: '' + permalinkable: true + protocol: '' + rel: '' + root: false + ugly: true + weight: 0 + alias: + baseName: '' + isHTML: true + isPlainText: false + mediaType: text/html + noUgly: false + notAlternative: false + path: '' + permalinkable: false + protocol: '' + rel: '' + root: false + ugly: true + weight: 0 + amp: + baseName: index + isHTML: true + isPlainText: false + mediaType: text/html + noUgly: false + notAlternative: false + path: amp + permalinkable: true + protocol: '' + rel: amphtml + root: false + ugly: false + weight: 0 + calendar: + baseName: index + isHTML: false + isPlainText: true + mediaType: text/calendar + noUgly: false + notAlternative: false + path: '' + permalinkable: false + protocol: webcal:// + rel: alternate + root: false + ugly: false + weight: 0 + css: + baseName: styles + isHTML: false + isPlainText: true + mediaType: text/css + noUgly: false + notAlternative: true + path: '' + permalinkable: false + protocol: '' + rel: stylesheet + root: false + ugly: false + weight: 0 + csv: + baseName: index + isHTML: false + isPlainText: true + mediaType: text/csv + noUgly: false + notAlternative: false + path: '' + permalinkable: false + protocol: '' + rel: alternate + root: false + ugly: false + weight: 0 + gotmpl: + baseName: '' + isHTML: false + isPlainText: true + mediaType: text/x-gotmpl + noUgly: false + notAlternative: true + path: '' + permalinkable: false + protocol: '' + rel: '' + root: false + ugly: false + weight: 0 + html: + baseName: index + isHTML: true + isPlainText: false + mediaType: text/html + noUgly: false + notAlternative: false + path: '' + permalinkable: true + protocol: '' + rel: canonical + root: false + ugly: false + weight: 10 + json: + baseName: index + isHTML: false + isPlainText: true + mediaType: application/json + noUgly: false + notAlternative: false + path: '' + permalinkable: false + protocol: '' + rel: alternate + root: false + ugly: false + weight: 0 + markdown: + baseName: index + isHTML: false + isPlainText: true + mediaType: text/markdown + noUgly: false + notAlternative: false + path: '' + permalinkable: false + protocol: '' + rel: alternate + root: false + ugly: false + weight: 0 + robots: + baseName: robots + isHTML: false + isPlainText: true + mediaType: text/plain + noUgly: false + notAlternative: false + path: '' + permalinkable: false + protocol: '' + rel: alternate + root: true + ugly: false + weight: 0 + rss: + baseName: index + isHTML: false + isPlainText: false + mediaType: application/rss+xml + noUgly: true + notAlternative: false + path: '' + permalinkable: false + protocol: '' + rel: alternate + root: false + ugly: false + weight: 0 + sitemap: + baseName: sitemap + isHTML: false + isPlainText: false + mediaType: application/xml + noUgly: false + notAlternative: false + path: '' + permalinkable: false + protocol: '' + rel: sitemap + root: false + ugly: true + weight: 0 + sitemapindex: + baseName: sitemap + isHTML: false + isPlainText: false + mediaType: application/xml + noUgly: false + notAlternative: false + path: '' + permalinkable: false + protocol: '' + rel: sitemap + root: true + ugly: true + weight: 0 + webappmanifest: + baseName: manifest + isHTML: false + isPlainText: true + mediaType: application/manifest+json + noUgly: false + notAlternative: true + path: '' + permalinkable: false + protocol: '' + rel: manifest + root: false + ugly: false + weight: 0 + outputs: + home: + - html + - rss + page: + - html + rss: + - rss + section: + - html + - rss + taxonomy: + - html + - rss + term: + - html + - rss + page: + nextPrevInSectionSortOrder: desc + nextPrevSortOrder: desc + pagination: + disableAliases: false + pagerSize: 10 + path: page + panicOnWarning: false + params: {} + permalinks: null + pluralizeListTitles: true + printI18nWarnings: false + printPathWarnings: false + printUnusedTemplates: false + privacy: + disqus: + disable: false + googleAnalytics: + disable: false + respectDoNotTrack: true + instagram: + disable: false + simple: false + vimeo: + disable: false + enableDNT: false + simple: false + x: + disable: false + enableDNT: false + simple: false + youTube: + disable: false + privacyEnhanced: false + publishDir: public + refLinksErrorLevel: '' + refLinksNotFoundURL: '' + related: + includeNewer: false + indices: + - applyFilter: false + cardinalityThreshold: 0 + name: keywords + pattern: '' + toLower: false + type: basic + weight: 100 + - applyFilter: false + cardinalityThreshold: 0 + name: date + pattern: '' + toLower: false + type: basic + weight: 10 + - applyFilter: false + cardinalityThreshold: 0 + name: tags + pattern: '' + toLower: false + type: basic + weight: 80 + threshold: 80 + toLower: false + relativeURLs: false + removePathAccents: false + renderSegments: null + resourceDir: resources + roles: + guest: + weight: 0 + sectionPagesMenu: '' + security: + allowContent: + - '! ^text/html$' + enableInlineShortcodes: false + exec: + allow: + - ^(dart-)?sass(-embedded)?$ + - ^go$ + - ^git$ + - ^node$ + - ^postcss$ + - ^tailwindcss$ + osEnv: + - '(?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\w+|(XDG_CONFIG_)?HOME|USERPROFILE|SSH_AUTH_SOCK|DISPLAY|LANG|SYSTEMDRIVE|PROGRAMDATA)$' + funcs: + getenv: + - ^HUGO_ + - ^CI$ + http: + mediaTypes: null + methods: + - (?i)GET|POST + urls: + - (?i)^https?://[a-z0-9] + - '! ^https?://\d+\.' + - '! (?i)localhost' + - '! (?i)^https?://[^/?#]*@' + node: + permissions: + allowAddons: + - tailwindcss + allowChildProcess: + - tailwindcss + allowRead: + - . + allowWorker: + - tailwindcss + allowWrite: [] + disable: false + segments: {} + server: + headers: null + redirects: + - force: false + from: /** + fromHeaders: null + fromRe: '' + status: 404 + to: /404.html + services: + disqus: + shortname: '' + googleAnalytics: + id: '' + rss: + limit: -1 + x: + disableInlineCSS: false + sitemap: + changeFreq: '' + disable: false + filename: sitemap.xml + priority: -1 + social: null + staticDir: + - static + staticDir0: null + staticDir1: null + staticDir10: null + staticDir2: null + staticDir3: null + staticDir4: null + staticDir5: null + staticDir6: null + staticDir7: null + staticDir8: null + staticDir9: null + summaryLength: 70 + taxonomies: + category: categories + tag: tags + templateMetrics: false + templateMetricsHints: false + theme: null + themesDir: themes + timeZone: '' + timeout: 60s + title: '' + titleCaseStyle: AP + uglyURLs: {} + versions: + v1.0.0: + weight: 0 + workingDir: '' +config_helpers: + mergeStrategy: + build: + _merge: none + caches: + _merge: none + cascade: + _merge: none + contenttypes: + _merge: none + deployment: + _merge: none + frontmatter: + _merge: none + httpcache: + _merge: none + imaging: + _merge: none + languages: + _merge: none + en: + _merge: none + menus: + _merge: shallow + params: + _merge: deep + markup: + _merge: none + mediatypes: + _merge: shallow + menus: + _merge: shallow + minify: + _merge: none + module: + _merge: none + outputformats: + _merge: shallow + outputs: + _merge: none + page: + _merge: none + pagination: + _merge: none + params: + _merge: deep + permalinks: + _merge: none + privacy: + _merge: none + related: + _merge: none + roles: + _merge: none + security: + _merge: none + segments: + _merge: none + server: + _merge: none + services: + _merge: none + sitemap: + _merge: none + taxonomies: + _merge: none + versions: + _merge: none +output: + layouts: {} +tpl: + funcs: + cast: + ToFloat: + Aliases: + - float + Args: + - v + Description: ToFloat converts v to a float. + Examples: + - - '{{ "1234" | float | printf "%T" }}' + - float64 + ToInt: + Aliases: + - int + Args: + - v + Description: ToInt converts v to an int. + Examples: + - - '{{ "1234" | int | printf "%T" }}' + - int + ToString: + Aliases: + - string + Args: + - v + Description: ToString converts v to a string. + Examples: + - - '{{ 1234 | string | printf "%T" }}' + - string + collections: + After: + Aliases: + - after + Args: + - 'n' + - l + Description: After returns all the items after the first n items in list l. + Examples: [] + Append: + Aliases: + - append + Args: + - args + Description: |- + Append appends args up to the last one to the slice in the last argument. + This construct allows template constructs like this: + + {{ $pages = $pages | append $p2 $p1 }} + + Note that with 2 arguments where both are slices of the same type, + the first slice will be appended to the second: + + {{ $pages = $pages | append .Site.RegularPages }} + Examples: [] + Apply: + Aliases: + - apply + Args: + - ctx + - c + - fname + - args + Description: Apply takes an array or slice c and returns a new slice with the function fname applied over it. + Examples: [] + Complement: + Aliases: + - complement + Args: + - ls + Description: |- + Complement gives the elements in the last element of ls that are not in + any of the others. + + All elements of ls must be slices or arrays of comparable types. + + The reasoning behind this rather clumsy API is so we can do this in the templates: + + {{ $c := .Pages | complement $last4 }} + Examples: + - - '{{ slice "a" "b" "c" "d" "e" "f" | complement (slice "b" "c") (slice "d" "e") }}' + - '[a f]' + D: + Aliases: null + Args: + - seed + - 'n' + - hi + Description: 'D returns a sorted slice of unique random integers in the half-open interval\n[0, hi) using the provided seed value. The number of elements in the\nresulting slice is n or hi, whichever is less.\n\nIf n <= hi, it returns a sorted random sample of size n using J. S. Vitter’s\nMethod D for sequential random sampling.\n\nIf n > hi, it returns the full, sorted range [0, hi) of size hi.\n\nIf n == 0 or hi == 0, it returns an empty slice.\n\nReference:\n\n\tJ. S. Vitter, "An efficient algorithm for sequential random sampling," ACM Trans. Math. Softw., vol. 11, no. 1, pp. 37–57, 1985.\n\tSee also: https://getkerf.wordpress.com/2016/03/30/the-best-algorithm-no-one-knows-about/' + Examples: [] + Delimit: + Aliases: + - delimit + Args: + - ctx + - l + - sep + - last + Description: |- + Delimit takes a given list l and returns a string delimited by sep. + If last is passed to the function, it will be used as the final delimiter. + Examples: + - - '{{ delimit (slice "A" "B" "C") ", " " and " }}' + - A, B and C + Dictionary: + Aliases: + - dict + Args: + - values + Description: |- + Dictionary creates a new map from the given parameters by + treating values as key-value pairs. The number of values must be even. + The keys can be string slices, which will create the needed nested structure. + Examples: [] + First: + Aliases: + - first + Args: + - limit + - l + Description: First returns the first limit items in list l. + Examples: [] + Group: + Aliases: + - group + Args: + - key + - items + Description: |- + Group groups a set of items by the given key. + This is currently only supported for Pages. + Examples: [] + In: + Aliases: + - in + Args: + - l + - v + Description: In returns whether v is in the list l. l may be an array or slice. + Examples: + - - '{{ if in "this string contains a substring" "substring" }}Substring found!{{ end }}' + - Substring found! + Index: + Aliases: + - index + Args: + - item + - args + Description: |- + Index returns the result of indexing its first argument by the following + arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each + indexed item must be a map, slice, or array. + + Adapted from Go stdlib src/text/template/funcs.go. + + We deviate from the stdlib mostly because of https://github.com/golang/go/issues/14751. + Examples: [] + Intersect: + Aliases: + - intersect + Args: + - l1 + - l2 + Description: |- + Intersect returns the common elements in the given sets, l1 and l2. l1 and + l2 must be of the same type and may be either arrays or slices. + Examples: [] + IsSet: + Aliases: + - isSet + - isset + Args: + - c + - key + Description: |- + IsSet returns whether a given array, channel, slice, or map in c has the given key + defined. + Examples: [] + KeyVals: + Aliases: + - keyVals + Args: + - key + - values + Description: KeyVals creates a key and values wrapper. + Examples: + - - '{{ keyVals "key" "a" "b" }}' + - 'key: [a b]' + Last: + Aliases: + - last + Args: + - limit + - l + Description: Last returns the last limit items in the list l. + Examples: [] + Merge: + Aliases: + - merge + Args: + - params + Description: |- + Merge creates a copy of the final parameter in params and merges the preceding + parameters into it in reverse order. + + Currently only maps are supported. Key handling is case insensitive. + Examples: + - - '{{ dict "title" "Hugo Rocks!" | collections.Merge (dict "title" "Default Title" "description" "Yes, Hugo Rocks!") | sort }}' + - '[Yes, Hugo Rocks! Hugo Rocks!]' + - - '{{ merge (dict "title" "Default Title" "description" "Yes, Hugo Rocks!") (dict "title" "Hugo Rocks!") | sort }}' + - '[Yes, Hugo Rocks! Hugo Rocks!]' + - - '{{ merge (dict "title" "Default Title" "description" "Yes, Hugo Rocks!") (dict "title" "Hugo Rocks!") (dict "extra" "For reals!") | sort }}' + - '[Yes, Hugo Rocks! For reals! Hugo Rocks!]' + NewScratch: + Aliases: + - newScratch + Args: null + Description: |- + NewScratch creates a new Scratch which can be used to store values in a + thread safe way. + Examples: + - - '{{ $scratch := newScratch }}{{ $scratch.Add "b" 2 }}{{ $scratch.Add "b" 2 }}{{ $scratch.Get "b" }}' + - '4' + Querify: + Aliases: + - querify + Args: + - params + Description: |- + Querify returns a URL query string composed of the given key-value pairs, + encoded and sorted by key. + Examples: + - - '{{ (querify "foo" 1 "bar" 2 "baz" "with spaces" "qux" "this&that=those") | safeHTML }}' + - bar=2&baz=with+spaces&foo=1&qux=this%26that%3Dthose + - - Search + - Search + - - '{{ slice "foo" 1 "bar" 2 | querify | safeHTML }}' + - bar=2&foo=1 + Reverse: + Aliases: null + Args: + - l + Description: Reverse creates a copy of the list l and reverses it. + Examples: [] + Seq: + Aliases: + - seq + Args: + - args + Description: |- + Seq creates a sequence of integers from args. It's named and used as GNU's seq. + + Examples: + + 3 => 1, 2, 3 + 1 2 4 => 1, 3 + -3 => -1, -2, -3 + 1 4 => 1, 2, 3, 4 + 1 -2 => 1, 0, -1, -2 + Examples: + - - '{{ seq 3 }}' + - '[1 2 3]' + Shuffle: + Aliases: + - shuffle + Args: + - l + Description: Shuffle returns list l in a randomized order. + Examples: [] + Slice: + Aliases: + - slice + Args: + - args + Description: Slice returns a slice of all passed arguments. + Examples: + - - '{{ slice "B" "C" "A" | sort }}' + - '[A B C]' + Sort: + Aliases: + - sort + Args: + - ctx + - l + - args + Description: Sort returns a sorted copy of the list l. + Examples: [] + SymDiff: + Aliases: + - symdiff + Args: + - s2 + - s1 + Description: |- + SymDiff returns the symmetric difference of s1 and s2. + Arguments must be either a slice or an array of comparable types. + Examples: + - - '{{ slice 1 2 3 | symdiff (slice 3 4) }}' + - '[1 2 4]' + Union: + Aliases: + - union + Args: + - l1 + - l2 + Description: |- + Union returns the union of the given sets, l1 and l2. l1 and + l2 must be of the same type and may be either arrays or slices. + If l1 and l2 aren't of the same type then l1 will be returned. + If either l1 or l2 is nil then the non-nil list will be returned. + Examples: + - - '{{ union (slice 1 2 3) (slice 3 4 5) }}' + - '[1 2 3 4 5]' + Uniq: + Aliases: + - uniq + Args: + - l + Description: Uniq returns a new list with duplicate elements in the list l removed. + Examples: + - - '{{ slice 1 2 3 2 | uniq }}' + - '[1 2 3]' + Where: + Aliases: + - where + Args: + - ctx + - c + - key + - args + Description: Where returns a filtered subset of collection c. + Examples: [] + compare: + Conditional: + Aliases: + - cond + Args: + - cond + - v1 + - v2 + Description: |- + Conditional can be used as a ternary operator. + + It returns v1 if cond is true, else v2. + Examples: + - - '{{ cond (eq (add 2 2) 4) "2+2 is 4" "what?" | safeHTML }}' + - 2+2 is 4 + Default: + Aliases: + - default + Args: + - defaultv + - givenv + Description: |- + Default checks whether a givenv is set and returns the default value defaultv if it + is not. "Set" in this context means non-zero for numeric types and times; + non-zero length for strings, arrays, slices, and maps; + any boolean or struct value; or non-nil for any other types. + Examples: + - - '{{ "Hugo Rocks!" | default "Hugo Rules!" }}' + - Hugo Rocks! + - - '{{ "" | default "Hugo Rules!" }}' + - Hugo Rules! + Eq: + Aliases: + - eq + Args: + - first + - others + Description: Eq returns the boolean truth of arg1 == arg2 || arg1 == arg3 || arg1 == arg4. + Examples: + - - '{{ if eq .Section "blog" }}current-section{{ end }}' + - current-section + Ge: + Aliases: + - ge + Args: + - first + - others + Description: Ge returns the boolean truth of arg1 >= arg2 && arg1 >= arg3 && arg1 >= arg4. + Examples: + - - '{{ if ge hugo.Version "0.80" }}Reasonable new Hugo version!{{ end }}' + - Reasonable new Hugo version! + Gt: + Aliases: + - gt + Args: + - first + - others + Description: Gt returns the boolean truth of arg1 > arg2 && arg1 > arg3 && arg1 > arg4. + Examples: [] + Le: + Aliases: + - le + Args: + - first + - others + Description: Le returns the boolean truth of arg1 <= arg2 && arg1 <= arg3 && arg1 <= arg4. + Examples: [] + Lt: + Aliases: + - lt + Args: + - first + - others + Description: Lt returns the boolean truth of arg1 < arg2 && arg1 < arg3 && arg1 < arg4. + Examples: [] + LtCollate: + Aliases: null + Args: + - collator + - first + - others + Description: |- + LtCollate returns the boolean truth of arg1 < arg2 && arg1 < arg3 && arg1 < arg4. + The provided collator will be used for string comparisons. + This is for internal use. + Examples: [] + Ne: + Aliases: + - ne + Args: + - first + - others + Description: Ne returns the boolean truth of arg1 != arg2 && arg1 != arg3 && arg1 != arg4. + Examples: [] + crypto: + HMAC: + Aliases: + - hmac + Args: + - h + - k + - m + - e + Description: HMAC returns a cryptographic hash that uses a key to sign a message. + Examples: + - - '{{ hmac "sha256" "Secret key" "Hello world, gophers!" }}' + - b6d11b6c53830b9d87036272ca9fe9d19306b8f9d8aa07b15da27d89e6e34f40 + MD5: + Aliases: + - md5 + Args: + - v + Description: MD5 hashes the v and returns its MD5 checksum. + Examples: + - - '{{ md5 "Hello world, gophers!" }}' + - b3029f756f98f79e7f1b7f1d1f0dd53b + - - '{{ crypto.MD5 "Hello world, gophers!" }}' + - b3029f756f98f79e7f1b7f1d1f0dd53b + SHA1: + Aliases: + - sha1 + Args: + - v + Description: SHA1 hashes v and returns its SHA1 checksum. + Examples: + - - '{{ sha1 "Hello world, gophers!" }}' + - c8b5b0e33d408246e30f53e32b8f7627a7a649d4 + SHA256: + Aliases: + - sha256 + Args: + - v + Description: SHA256 hashes v and returns its SHA256 checksum. + Examples: + - - '{{ sha256 "Hello world, gophers!" }}' + - 6ec43b78da9669f50e4e422575c54bf87536954ccd58280219c393f2ce352b46 + css: + Build: + Aliases: null + Args: + - args + Description: |- + Build processes the given CSS Resource with ESBuild. + Note that this method is identical to the one in the js Namespace. + Examples: [] + PostCSS: + Aliases: + - postCSS + Args: + - args + Description: PostCSS processes the given Resource with PostCSS. + Examples: [] + Quoted: + Aliases: null + Args: + - v + Description: Quoted returns a string that needs to be quoted in CSS. + Examples: [] + Sass: + Aliases: + - toCSS + Args: + - args + Description: Sass processes the given Resource with SASS. + Examples: [] + TailwindCSS: + Aliases: null + Args: + - args + Description: TailwindCSS processes the given Resource with tailwindcss. + Examples: [] + Unquoted: + Aliases: null + Args: + - v + Description: Unquoted returns a string that does not need to be quoted in CSS. + Examples: [] + debug: + Dump: + Aliases: null + Args: + - val + Description: |- + Dump returns a object dump of val as a string. + Note that not every value passed to Dump will print so nicely, but + we'll improve on that. + + We recommend using the "go" Chroma lexer to format the output + nicely. + + Also note that the output from Dump may change from Hugo version to the next, + so don't depend on a specific output. + Examples: + - - '{{ $m := newScratch }}\n{{ $m.Set "Hugo" "Rocks!" }}\n{{ $m.Values | debug.Dump | safeHTML }}' + - '{\n "Hugo": "Rocks!"\n}' + TestDeprecationErr: + Aliases: null + Args: + - item + - alternative + Description: Internal template func, used in tests only. + Examples: [] + TestDeprecationInfo: + Aliases: null + Args: + - item + - alternative + Description: Internal template func, used in tests only. + Examples: [] + TestDeprecationWarn: + Aliases: null + Args: + - item + - alternative + Description: Internal template func, used in tests only. + Examples: [] + Timer: + Aliases: null + Args: + - name + Description: '' + Examples: [] + VisualizeSpaces: + Aliases: null + Args: + - val + Description: VisualizeSpaces returns a string with spaces replaced by a visible string. + Examples: [] + diagrams: + Goat: + Aliases: null + Args: + - v + Description: Goat creates a new SVG diagram from input v. + Examples: [] + encoding: + Base64Decode: + Aliases: + - base64Decode + Args: + - content + Description: Base64Decode returns the base64 decoding of the given content. + Examples: + - - '{{ "SGVsbG8gd29ybGQ=" | base64Decode }}' + - Hello world + - - '{{ 42 | base64Encode | base64Decode }}' + - '42' + Base64Encode: + Aliases: + - base64Encode + Args: + - content + Description: Base64Encode returns the base64 encoding of the given content. + Examples: + - - '{{ "Hello world" | base64Encode }}' + - SGVsbG8gd29ybGQ= + Jsonify: + Aliases: + - jsonify + Args: + - args + Description: |- + Jsonify encodes a given object to JSON. To pretty print the JSON, pass a map + or dictionary of options as the first value in args. 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. + Examples: + - - '{{ (slice "A" "B" "C") | jsonify }}' + - '["A","B","C"]' + - - '{{ (slice "A" "B" "C") | jsonify (dict "indent" " ") }}' + - '[\n "A",\n "B",\n "C"\n]' + fmt: + Errorf: + Aliases: + - errorf + Args: + - format + - args + Description: |- + Errorf formats args according to a format specifier and logs an ERROR. + It returns an empty string. + Examples: + - - '{{ errorf "%s." "failed" }}' + - '' + Erroridf: + Aliases: + - erroridf + Args: + - id + - format + - args + Description: |- + Erroridf formats args according to a format specifier and logs an ERROR and + an information text that the error with the given id can be suppressed in config. + It returns an empty string. + Examples: + - - '{{ erroridf "my-err-id" "%s." "failed" }}' + - '' + Errormf: + Aliases: null + Args: + - m + - format + - args + Description: Errormf is experimental and subject to change at any time. + Examples: [] + Print: + Aliases: + - print + Args: + - args + Description: Print returns a string representation of args. + Examples: + - - '{{ print "works!" }}' + - works! + Printf: + Aliases: + - printf + Args: + - format + - args + Description: Printf returns string representation of args formatted with the layout in format. + Examples: + - - '{{ printf "%s!" "works" }}' + - works! + Println: + Aliases: + - println + Args: + - args + Description: Println returns string representation of args ending with a newline. + Examples: + - - '{{ println "works!" }}' + - | + works! + Warnf: + Aliases: + - warnf + Args: + - format + - args + Description: |- + Warnf formats args according to a format specifier and logs a WARNING. + It returns an empty string. + Examples: + - - '{{ warnf "%s." "warning" }}' + - '' + Warnidf: + Aliases: + - warnidf + Args: + - id + - format + - args + Description: |- + Warnidf formats args according to a format specifier and logs an WARNING and + an information text that the warning with the given id can be suppressed in config. + It returns an empty string. + Examples: + - - '{{ warnidf "my-warn-id" "%s." "warning" }}' + - '' + Warnmf: + Aliases: null + Args: + - m + - format + - args + Description: Warnmf is experimental and subject to change at any time. + Examples: [] + hash: + FNV32a: + Aliases: null + Args: + - v + Description: FNV32a hashes v using fnv32a algorithm. + Examples: + - - '{{ hash.FNV32a "Hugo Rocks!!" }}' + - '1515779328' + XxHash: + Aliases: + - xxhash + Args: + - v + Description: XxHash returns the xxHash of the input string. + Examples: + - - '{{ hash.XxHash "The quick brown fox jumps over the lazy dog" }}' + - 0b242d361fda71bc + hugo: + Data: + Aliases: null + Args: null + Description: '' + Examples: null + Deps: + Aliases: null + Args: null + Description: '' + Examples: null + Environment: + Aliases: null + Args: null + Description: '' + Examples: null + ForEeachIdentityByName: + Aliases: null + Args: null + Description: '' + Examples: null + Generator: + Aliases: null + Args: null + Description: '' + Examples: null + IsDevelopment: + Aliases: null + Args: null + Description: '' + Examples: null + IsExtended: + Aliases: null + Args: null + Description: '' + Examples: null + IsMultiHost: + Aliases: null + Args: null + Description: '' + Examples: null + IsMultihost: + Aliases: null + Args: null + Description: '' + Examples: null + IsMultilingual: + Aliases: null + Args: null + Description: '' + Examples: null + IsProduction: + Aliases: null + Args: null + Description: '' + Examples: null + IsServer: + Aliases: null + Args: null + Description: '' + Examples: null + Sites: + Aliases: null + Args: null + Description: '' + Examples: null + Store: + Aliases: null + Args: null + Description: '' + Examples: null + Version: + Aliases: null + Args: null + Description: '' + Examples: null + WorkingDir: + Aliases: null + Args: null + Description: '' + Examples: null + images: + AutoOrient: + Aliases: null + Args: null + Description: '' + Examples: null + Brightness: + Aliases: null + Args: null + Description: '' + Examples: null + ColorBalance: + Aliases: null + Args: null + Description: '' + Examples: null + Colorize: + Aliases: null + Args: null + Description: '' + Examples: null + Config: + Aliases: + - imageConfig + Args: + - path + Description: |- + Config returns the image.Config for the specified path relative to the + working directory. + Examples: [] + Contrast: + Aliases: null + Args: null + Description: '' + Examples: null + Dither: + Aliases: null + Args: null + Description: '' + Examples: null + Filter: + Aliases: null + Args: + - args + Description: Filter applies the given filters to the image given as the last element in args. + Examples: [] + Gamma: + Aliases: null + Args: null + Description: '' + Examples: null + GaussianBlur: + Aliases: null + Args: null + Description: '' + Examples: null + Grayscale: + Aliases: null + Args: null + Description: '' + Examples: null + Hue: + Aliases: null + Args: null + Description: '' + Examples: null + Invert: + Aliases: null + Args: null + Description: '' + Examples: null + Mask: + Aliases: null + Args: null + Description: '' + Examples: null + Opacity: + Aliases: null + Args: null + Description: '' + Examples: null + Overlay: + Aliases: null + Args: null + Description: '' + Examples: null + Padding: + Aliases: null + Args: null + Description: '' + Examples: null + Pixelate: + Aliases: null + Args: null + Description: '' + Examples: null + Process: + Aliases: null + Args: null + Description: '' + Examples: null + QR: + Aliases: null + Args: + - args + Description: |- + QR encodes the given text into a QR code using the specified options, + returning an image resource. + Examples: [] + Saturation: + Aliases: null + Args: null + Description: '' + Examples: null + Sepia: + Aliases: null + Args: null + Description: '' + Examples: null + Sigmoid: + Aliases: null + Args: null + Description: '' + Examples: null + Text: + Aliases: null + Args: null + Description: '' + Examples: null + UnsharpMask: + Aliases: null + Args: null + Description: '' + Examples: null + inflect: + Humanize: + Aliases: + - humanize + Args: + - v + Description: |- + Humanize returns the humanized form of v. + + If v is either an integer or a string containing an integer + value, the behavior is to add the appropriate ordinal. + Examples: + - - '{{ humanize "my-first-post" }}' + - My first post + - - '{{ humanize "myCamelPost" }}' + - My camel post + - - '{{ humanize "52" }}' + - 52nd + - - '{{ humanize 103 }}' + - 103rd + Pluralize: + Aliases: + - pluralize + Args: + - v + Description: Pluralize returns the plural form of the single word in v. + Examples: + - - '{{ "cat" | pluralize }}' + - cats + Singularize: + Aliases: + - singularize + Args: + - v + Description: Singularize returns the singular form of a single word in v. + Examples: + - - '{{ "cats" | singularize }}' + - cat + js: + Babel: + Aliases: + - babel + Args: + - args + Description: Babel processes the given Resource with Babel. + Examples: [] + Batch: + Aliases: null + Args: + - id + Description: |- + Batch creates a new Batcher with the given ID. + Repeated calls with the same ID will return the same Batcher. + The ID will be used to name the root directory of the batch. + Forward slashes in the ID is allowed. + Examples: [] + Build: + Aliases: null + Args: + - args + Description: Build processes the given JavaScript Resource with ESBuild. + Examples: [] + lang: + FormatAccounting: + Aliases: null + Args: + - precision + - currency + - number + Description: |- + FormatAccounting returns the currency representation of number for the given currency and precision + for the current language in accounting notation. + + The return value is formatted with at least two decimal places. + Examples: + - - '{{ 512.5032 | lang.FormatAccounting 2 "NOK" }}' + - NOK512.50 + FormatCurrency: + Aliases: null + Args: + - precision + - currency + - number + Description: |- + FormatCurrency returns the currency representation of number for the given currency and precision + for the current language. + + The return value is formatted with at least two decimal places. + Examples: + - - '{{ 512.5032 | lang.FormatCurrency 2 "USD" }}' + - $512.50 + FormatNumber: + Aliases: null + Args: + - precision + - number + Description: FormatNumber formats number with the given precision for the current language. + Examples: + - - '{{ 512.5032 | lang.FormatNumber 2 }}' + - '512.50' + FormatNumberCustom: + Aliases: null + Args: + - precision + - number + - options + Description: 'FormatNumberCustom formats a number with the given precision. The first\noptions parameter is a space-delimited string of characters to represent\nnegativity, the decimal point, and grouping. The default value is `- . ,`.\nThe second options parameter defines an alternate delimiting character.\n\nNote that numbers are rounded up at 5 or greater.\nSo, with precision set to 0, 1.5 becomes `2`, and 1.4 becomes `1`.\n\nFor a simpler function that adapts to the current language, see FormatNumber.' + Examples: + - - '{{ 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 + - - '{{ -98765.4321 | lang.FormatNumberCustom 2 }}' + - -98,765.43 + FormatPercent: + Aliases: null + Args: + - precision + - number + Description: |- + FormatPercent formats number with the given precision for the current language. + Note that the number is assumed to be a percentage. + Examples: + - - '{{ 512.5032 | lang.FormatPercent 2 }}' + - 512.50% + Merge: + Aliases: null + Args: + - p2 + - p1 + Description: Merge creates a union of pages from two languages. + Examples: [] + Translate: + Aliases: + - i18n + - T + Args: + - ctx + - id + - args + Description: Translate returns a translated string for id. + Examples: [] + math: + Abs: + Aliases: null + Args: + - 'n' + Description: Abs returns the absolute value of n. + Examples: + - - '{{ math.Abs -2.1 }}' + - '2.1' + Acos: + Aliases: null + Args: + - 'n' + Description: Acos returns the arccosine, in radians, of n. + Examples: + - - '{{ math.Acos 1 }}' + - '0' + Add: + Aliases: + - add + Args: + - inputs + Description: Add adds the multivalued addends n1 and n2 or more values. + Examples: + - - '{{ add 1 2 }}' + - '3' + Asin: + Aliases: null + Args: + - 'n' + Description: Asin returns the arcsine, in radians, of n. + Examples: + - - '{{ math.Asin 1 }}' + - '1.5707963267948966' + Atan: + Aliases: null + Args: + - 'n' + Description: Atan returns the arctangent, in radians, of n. + Examples: + - - '{{ math.Atan 1 }}' + - '0.7853981633974483' + Atan2: + Aliases: null + Args: + - 'n' + - m + Description: Atan2 returns the arc tangent of n/m, using the signs of the two to determine the quadrant of the return value. + Examples: + - - '{{ math.Atan2 1 2 }}' + - '0.4636476090008061' + Ceil: + Aliases: null + Args: + - 'n' + Description: Ceil returns the least integer value greater than or equal to n. + Examples: + - - '{{ math.Ceil 2.1 }}' + - '3' + Cos: + Aliases: null + Args: + - 'n' + Description: Cos returns the cosine of the radian argument n. + Examples: + - - '{{ math.Cos 1 }}' + - '0.5403023058681398' + Counter: + Aliases: null + Args: null + Description: 'Counter increments and returns a global counter.\nThis was originally added to be used in tests where now.UnixNano did not\nhave the needed precision (especially on Windows).\nNote that given the parallel nature of Hugo, you cannot use this to get sequences of numbers,\nand the counter will reset on new builds.\n{"identifiers": ["now.UnixNano"] }' + Examples: [] + Div: + Aliases: + - div + Args: + - inputs + Description: Div divides n1 by n2. + Examples: + - - '{{ div 6 3 }}' + - '2' + Floor: + Aliases: null + Args: + - 'n' + Description: Floor returns the greatest integer value less than or equal to n. + Examples: + - - '{{ math.Floor 1.9 }}' + - '1' + Log: + Aliases: null + Args: + - 'n' + Description: Log returns the natural logarithm of the number n. + Examples: + - - '{{ math.Log 1 }}' + - '0' + Max: + Aliases: null + Args: + - inputs + Description: Max returns the greater of all numbers in inputs. Any slices in inputs are flattened. + Examples: + - - '{{ math.Max 1 2 }}' + - '2' + MaxInt64: + Aliases: null + Args: null + Description: MaxInt64 returns the maximum value for a signed 64-bit integer. + Examples: + - - '{{ math.MaxInt64 }}' + - '9223372036854775807' + Min: + Aliases: null + Args: + - inputs + Description: Min returns the smaller of all numbers in inputs. Any slices in inputs are flattened. + Examples: + - - '{{ math.Min 1 2 }}' + - '1' + Mod: + Aliases: + - mod + Args: + - n1 + - n2 + Description: Mod returns n1 % n2. + Examples: + - - '{{ mod 15 3 }}' + - '0' + ModBool: + Aliases: + - modBool + Args: + - n1 + - n2 + Description: ModBool returns the boolean of n1 % n2. If n1 % n2 == 0, return true. + Examples: + - - '{{ modBool 15 3 }}' + - 'true' + Mul: + Aliases: + - mul + Args: + - inputs + Description: Mul multiplies the multivalued numbers n1 and n2 or more values. + Examples: + - - '{{ mul 2 3 }}' + - '6' + Pi: + Aliases: null + Args: null + Description: Pi returns the mathematical constant pi. + Examples: + - - '{{ math.Pi }}' + - '3.141592653589793' + Pow: + Aliases: + - pow + Args: + - n1 + - n2 + Description: Pow returns n1 raised to the power of n2. + Examples: + - - '{{ math.Pow 2 3 }}' + - '8' + Product: + Aliases: null + Args: + - inputs + Description: Product returns the product of all numbers in inputs. Any slices in inputs are flattened. + Examples: [] + Rand: + Aliases: null + Args: null + Description: Rand returns, as a float64, a pseudo-random number in the half-open interval [0.0,1.0). + Examples: + - - '{{ math.Rand }}' + - '0.6312770459590062' + Round: + Aliases: null + Args: + - 'n' + Description: Round returns the integer nearest to n, rounding half away from zero. + Examples: + - - '{{ math.Round 1.5 }}' + - '2' + Sin: + Aliases: null + Args: + - 'n' + Description: Sin returns the sine of the radian argument n. + Examples: + - - '{{ math.Sin 1 }}' + - '0.8414709848078965' + Sqrt: + Aliases: null + Args: + - 'n' + Description: Sqrt returns the square root of the number n. + Examples: + - - '{{ math.Sqrt 81 }}' + - '9' + Sub: + Aliases: + - sub + Args: + - inputs + Description: Sub subtracts multivalued. + Examples: + - - '{{ sub 3 2 }}' + - '1' + Sum: + Aliases: null + Args: + - inputs + Description: Sum returns the sum of all numbers in inputs. Any slices in inputs are flattened. + Examples: [] + Tan: + Aliases: null + Args: + - 'n' + Description: Tan returns the tangent of the radian argument n. + Examples: + - - '{{ math.Tan 1 }}' + - '1.557407724654902' + ToDegrees: + Aliases: null + Args: + - 'n' + Description: ToDegrees converts radians into degrees. + Examples: + - - '{{ math.ToDegrees 1.5707963267948966 }}' + - '90' + ToRadians: + Aliases: null + Args: + - 'n' + Description: ToRadians converts degrees into radians. + Examples: + - - '{{ math.ToRadians 90 }}' + - '1.5707963267948966' + openapi3: + Unmarshal: + Aliases: null + Args: null + Description: '' + Examples: [] + os: + FileExists: + Aliases: + - fileExists + Args: + - i + Description: FileExists checks whether a file exists under the given path. + Examples: + - - '{{ fileExists "foo.txt" }}' + - 'false' + Getenv: + Aliases: + - getenv + Args: + - key + Description: |- + Getenv retrieves the value of the environment variable named by the key. + It returns the value, which will be empty if the variable is not present. + Examples: [] + ReadDir: + Aliases: + - readDir + Args: + - i + Description: ReadDir lists the directory contents relative to the configured WorkingDir. + Examples: + - - '{{ range (readDir "files") }}{{ .Name }}{{ end }}' + - README.txt + ReadFile: + Aliases: + - readFile + Args: + - i + Description: |- + ReadFile reads the file named by filename relative to the configured WorkingDir. + It returns the contents as a string. + There is an upper size limit set at 1 megabytes. + Examples: + - - '{{ readFile "files/README.txt" }}' + - Hugo Rocks! + Stat: + Aliases: null + Args: + - i + Description: Stat returns the os.FileInfo structure describing file. + Examples: [] + partials: + Include: + Aliases: + - partial + Args: + - ctx + - name + - contextList + Description: |- + Include executes the named partial. + If the partial contains a return statement, that value will be returned. + Else, the rendered output will be returned: + A string if the partial is a text/template, or template.HTML when html/template. + Note that ctx is provided by Hugo, not the end user. + Examples: + - - '{{ partial "header.html" . }}' + - Hugo Rocks! + IncludeCached: + Aliases: + - partialCached + Args: + - ctx + - name + - context + - variants + Description: |- + IncludeCached executes and caches partial templates. The cache is created with name+variants as the key. + Note that ctx is provided by Hugo, not the end user. + Examples: [] + path: + Base: + Aliases: null + Args: + - path + Description: |- + Base returns the last element of path. + Trailing slashes are removed before extracting the last element. + If the path is empty, Base returns ".". + If the path consists entirely of slashes, Base returns "/". + The input path is passed into filepath.ToSlash converting any Windows slashes + to forward slashes. + Examples: [] + BaseName: + Aliases: null + Args: + - path + Description: |- + BaseName returns the last element of path, removing the extension if present. + Trailing slashes are removed before extracting the last element. + If the path is empty, Base returns ".". + If the path consists entirely of slashes, Base returns "/". + The input path is passed into filepath.ToSlash converting any Windows slashes + to forward slashes. + Examples: [] + Clean: + Aliases: null + Args: + - path + Description: |- + Clean replaces the separators used with standard slashes and then + extraneous slashes are removed. + Examples: [] + Dir: + Aliases: null + Args: + - path + Description: |- + Dir returns all but the last element of path, typically the path's directory. + After dropping the final element using Split, the path is Cleaned and trailing + slashes are removed. + If the path is empty, Dir returns ".". + If the path consists entirely of slashes followed by non-slash bytes, Dir + returns a single slash. In any other case, the returned path does not end in a + slash. + The input path is passed into filepath.ToSlash converting any Windows slashes + to forward slashes. + Examples: [] + Ext: + Aliases: null + Args: + - path + Description: |- + Ext returns the file name extension used by path. + 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. + The input path is passed into filepath.ToSlash converting any Windows slashes + to forward slashes. + Examples: [] + Join: + Aliases: null + Args: + - elements + Description: |- + Join joins any number of path elements into a single path, adding a + separating slash if necessary. All the input + path elements are passed into filepath.ToSlash converting any Windows slashes + to forward slashes. + The result is Cleaned; in particular, + all empty strings are ignored. + Examples: + - - '{{ slice "my/path" "filename.txt" | path.Join }}' + - my/path/filename.txt + - - '{{ path.Join "my" "path" "filename.txt" }}' + - my/path/filename.txt + - - '{{ "my/path/filename.txt" | path.Ext }}' + - .txt + - - '{{ "my/path/filename.txt" | path.Base }}' + - filename.txt + - - '{{ "my/path/filename.txt" | path.Dir }}' + - my/path + Split: + Aliases: null + Args: + - path + Description: |- + Split splits path immediately following the final slash, + separating it into a directory and file name component. + If there is no slash in path, Split returns an empty dir and + file set to path. + The input path is passed into filepath.ToSlash converting any Windows slashes + to forward slashes. + The returned values have the property that path = dir+file. + Examples: + - - '{{ "/my/path/filename.txt" | path.Split }}' + - /my/path/|filename.txt + - - '{{ "/my/path/filename.txt" | path.Split }}' + - /my/path/|filename.txt + reflect: + IsImageResource: + Aliases: null + Args: null + Description: '' + Examples: null + IsImageResourceProcessable: + Aliases: null + Args: null + Description: '' + Examples: null + IsImageResourceWithMeta: + Aliases: null + Args: null + Description: '' + Examples: null + IsMap: + Aliases: null + Args: + - v + Description: IsMap reports whether v is a map. + Examples: + - - '{{ if reflect.IsMap (dict "a" 1) }}Map{{ end }}' + - Map + IsPage: + Aliases: null + Args: null + Description: '' + Examples: null + IsResource: + Aliases: null + Args: null + Description: '' + Examples: null + IsSite: + Aliases: null + Args: null + Description: '' + Examples: null + IsSlice: + Aliases: null + Args: + - v + Description: IsSlice reports whether v is a slice. + Examples: + - - '{{ if reflect.IsSlice (slice 1 2 3) }}Slice{{ end }}' + - Slice + resources: + ByType: + Aliases: null + Args: + - typ + Description: ByType returns resources of a given resource type (e.g. "image"). + Examples: [] + Concat: + Aliases: null + Args: + - targetPathIn + - r + Description: |- + Concat concatenates a slice of Resource objects. These resources must + (currently) be of the same Media Type. + Examples: [] + Copy: + Aliases: null + Args: + - s + - r + Description: Copy copies r to the new targetPath in s. + Examples: [] + ExecuteAsTemplate: + Aliases: null + Args: + - ctx + - args + Description: |- + ExecuteAsTemplate creates a Resource from a Go template, parsed and executed with + the given data, and published to the relative target path. + Examples: [] + Fingerprint: + Aliases: + - fingerprint + Args: + - args + Description: |- + Fingerprint transforms the given Resource with a MD5 hash of the content in + the RelPermalink and Permalink. + Examples: [] + FromString: + Aliases: null + Args: + - targetPathIn + - contentIn + Description: FromString creates a Resource from a string published to the relative target path. + Examples: [] + Get: + Aliases: null + Args: + - filename + Description: |- + Get locates the filename given in Hugo's assets filesystem + and creates a Resource object that can be used for further transformations. + Examples: [] + GetMatch: + Aliases: null + Args: null + Description: '' + Examples: null + GetRemote: + Aliases: null + Args: + - args + Description: 'GetRemote gets the URL (via HTTP(s)) in the first argument in args and creates Resource object that can be used for\nfurther transformations.\n\nA second argument may be provided with an option map.\n\nNote: This method does not return any error as a second return value,\nfor any error situations the error can be checked in .Err.' + Examples: [] + Match: + Aliases: null + Args: + - pattern + Description: |- + Match gets all resources matching the given base path prefix, e.g + "*.png" will match all png files. The "*" does not match path delimiters (/), + so if you organize your resources in sub-folders, you need to be explicit about it, e.g.: + "images/*.png". To match any PNG image anywhere in the bundle you can do "**.png", and + to match all PNG images below the images folder, use "images/**.jpg". + + The matching is case insensitive. + + Match matches by using the files name with path relative to the file system root + with Unix style slashes (/) and no leading slash, e.g. "images/logo.png". + + See https://github.com/gobwas/glob for the full rules set. + + It looks for files in the assets file system. + + See Match for a more complete explanation about the rules used. + Examples: [] + Minify: + Aliases: + - minify + Args: + - r + Description: |- + Minify minifies the given Resource using the MediaType to pick the correct + minifier. + Examples: [] + PostProcess: + Aliases: null + Args: + - r + Description: PostProcess processes r after the build. + Examples: [] + safe: + CSS: + Aliases: + - safeCSS + Args: + - s + Description: CSS returns the string s as html/template CSS content. + Examples: + - - '{{ "Bat&Man" | safeCSS | safeCSS }}' + - Bat&Man + HTML: + Aliases: + - safeHTML + Args: + - s + Description: HTML returns the string s as html/template HTML content. + Examples: + - - '{{ "Bat&Man" | safeHTML | safeHTML }}' + - Bat&Man + - - '{{ "Bat&Man" | safeHTML }}' + - Bat&Man + HTMLAttr: + Aliases: + - safeHTMLAttr + Args: + - s + Description: HTMLAttr returns the string s as html/template HTMLAttr content. + Examples: [] + JS: + Aliases: + - safeJS + Args: + - s + Description: JS returns the given string as a html/template JS content. + Examples: + - - '{{ "(1*2)" | safeJS | safeJS }}' + - (1*2) + JSStr: + Aliases: + - safeJSStr + Args: + - s + Description: JSStr returns the given string as a html/template JSStr content. + Examples: [] + URL: + Aliases: + - safeURL + Args: + - s + Description: URL returns the string s as html/template URL content. + Examples: + - - '{{ "http://gohugo.io" | safeURL | safeURL }}' + - http://gohugo.io + site: + AllPages: + Aliases: null + Args: null + Description: '' + Examples: null + BaseURL: + Aliases: null + Args: null + Description: '' + Examples: null + BuildDrafts: + Aliases: null + Args: null + Description: '' + Examples: null + CheckReady: + Aliases: null + Args: null + Description: '' + Examples: null + Config: + Aliases: null + Args: null + Description: '' + Examples: null + Copyright: + Aliases: null + Args: null + Description: '' + Examples: null + Current: + Aliases: null + Args: null + Description: '' + Examples: null + Data: + Aliases: null + Args: null + Description: '' + Examples: null + Dimension: + Aliases: null + Args: null + Description: '' + Examples: null + ForEeachIdentityByName: + Aliases: null + Args: null + Description: '' + Examples: null + GetPage: + Aliases: null + Args: null + Description: '' + Examples: null + Home: + Aliases: null + Args: null + Description: '' + Examples: null + Hugo: + Aliases: null + Args: null + Description: '' + Examples: null + IsDefault: + Aliases: null + Args: null + Description: '' + Examples: null + Key: + Aliases: null + Args: null + Description: '' + Examples: null + Language: + Aliases: null + Args: null + Description: '' + Examples: null + LanguageCode: + Aliases: null + Args: null + Description: '' + Examples: null + LanguagePrefix: + Aliases: null + Args: null + Description: '' + Examples: null + Languages: + Aliases: null + Args: null + Description: '' + Examples: null + Lastmod: + Aliases: null + Args: null + Description: '' + Examples: null + MainSections: + Aliases: null + Args: null + Description: '' + Examples: null + Menus: + Aliases: null + Args: null + Description: '' + Examples: null + Pages: + Aliases: null + Args: null + Description: '' + Examples: null + Param: + Aliases: null + Args: null + Description: '' + Examples: null + Params: + Aliases: null + Args: null + Description: '' + Examples: null + RegularPages: + Aliases: null + Args: null + Description: '' + Examples: null + Role: + Aliases: null + Args: null + Description: '' + Examples: null + Sections: + Aliases: null + Args: null + Description: '' + Examples: null + ServerPort: + Aliases: null + Args: null + Description: '' + Examples: null + Sites: + Aliases: null + Args: null + Description: '' + Examples: null + Store: + Aliases: null + Args: null + Description: '' + Examples: null + String: + Aliases: null + Args: null + Description: '' + Examples: null + Taxonomies: + Aliases: null + Args: null + Description: '' + Examples: null + Title: + Aliases: null + Args: null + Description: '' + Examples: null + Version: + Aliases: null + Args: null + Description: '' + Examples: null + strings: + Chomp: + Aliases: + - chomp + Args: + - s + Description: Chomp returns a copy of s with all trailing newline characters removed. + Examples: + - - '{{ chomp "

    Blockhead

    \n" | safeHTML }}' + -

    Blockhead

    + Contains: + Aliases: null + Args: + - s + - substr + Description: Contains reports whether substr is in s. + Examples: + - - '{{ strings.Contains "abc" "b" }}' + - 'true' + - - '{{ strings.Contains "abc" "d" }}' + - 'false' + ContainsAny: + Aliases: null + Args: + - s + - chars + Description: ContainsAny reports whether any Unicode code points in chars are within s. + Examples: + - - '{{ strings.ContainsAny "abc" "bcd" }}' + - 'true' + - - '{{ strings.ContainsAny "abc" "def" }}' + - 'false' + ContainsNonSpace: + Aliases: null + Args: + - s + Description: 'ContainsNonSpace reports whether s contains any non-space characters as defined\nby Unicode''s White Space property,\n{"newIn": "0.111.0" }' + Examples: [] + Count: + Aliases: null + Args: + - substr + - s + Description: |- + Count counts the number of non-overlapping instances of substr in s. + If substr is an empty string, Count returns 1 + the number of Unicode code points in s. + Examples: + - - '{{ "aabab" | strings.Count "a" }}' + - '3' + CountRunes: + Aliases: + - countrunes + Args: + - s + Description: CountRunes returns the number of runes in s, excluding whitespace. + Examples: [] + CountWords: + Aliases: + - countwords + Args: + - s + Description: CountWords returns the approximate word count in s. + Examples: [] + Diff: + Aliases: null + Args: + - oldname + - old + - newname + - new + Description: |- + Diff returns an anchored diff of the two texts old and new in the “unified + diff” format. If old and new are identical, Diff returns an empty string. + Examples: [] + FindRE: + Aliases: + - findRE + Args: + - expr + - content + - limit + Description: |- + FindRE returns a list of strings that match the regular expression. By default all matches + will be included. The number of matches can be limited with an optional third parameter. + Examples: + - - '{{ findRE "[G|g]o" "Hugo is a static side generator written in Go." 1 }}' + - '[go]' + FindRESubmatch: + Aliases: + - findRESubmatch + Args: + - expr + - content + - limit + Description: |- + FindRESubmatch returns a slice of all successive matches of the regular + expression in content. 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. + + By default all matches will be included. The number of matches can be + limited with the optional limit parameter. A return value of nil indicates + no match. + Examples: + - - '{{ findRESubmatch `(.+?)` `
  • Foo
  • Bar
  • ` | print | safeHTML }}' + - '[[Foo #foo Foo] [Bar #bar Bar]]' + FirstUpper: + Aliases: null + Args: + - s + Description: FirstUpper converts s making the first character upper case. + Examples: + - - '{{ "hugo rocks!" | strings.FirstUpper }}' + - Hugo rocks! + HasPrefix: + Aliases: + - hasPrefix + Args: + - s + - prefix + Description: HasPrefix tests whether the input s begins with prefix. + Examples: + - - '{{ hasPrefix "Hugo" "Hu" }}' + - 'true' + - - '{{ hasPrefix "Hugo" "Fu" }}' + - 'false' + HasSuffix: + Aliases: + - hasSuffix + Args: + - s + - suffix + Description: HasSuffix tests whether the input s begins with suffix. + Examples: + - - '{{ hasSuffix "Hugo" "go" }}' + - 'true' + - - '{{ hasSuffix "Hugo" "du" }}' + - 'false' + Repeat: + Aliases: null + Args: + - 'n' + - s + Description: Repeat returns a new string consisting of n copies of the string s. + Examples: + - - '{{ "yo" | strings.Repeat 4 }}' + - yoyoyoyo + Replace: + Aliases: + - replace + Args: + - s + - old + - new + - limit + Description: |- + Replace returns a copy of the string s with all occurrences of old replaced + with new. The number of replacements can be limited with an optional fourth + parameter. + Examples: + - - '{{ replace "Batman and Robin" "Robin" "Catwoman" }}' + - Batman and Catwoman + - - '{{ replace "aabbaabb" "a" "z" 2 }}' + - zzbbaabb + ReplacePairs: + Aliases: null + Args: + - args + Description: |- + ReplacePairs returns a copy of a string with multiple replacements performed + in a single pass. The last argument is the source string. Preceding arguments + are old/new string pairs, either as a slice or as individual arguments. + Examples: + - - '{{ "aab" | strings.ReplacePairs "a" "b" "b" "c" }}' + - bbc + - - '{{ "aab" | strings.ReplacePairs (slice "a" "b" "b" "c") }}' + - bbc + ReplaceRE: + Aliases: + - replaceRE + Args: + - pattern + - repl + - s + - 'n' + Description: |- + ReplaceRE returns a copy of s, replacing all matches of the regular + expression pattern with the replacement text repl. The number of replacements + can be limited with an optional fourth parameter. + Examples: + - - '{{ replaceRE "a+b" "X" "aabbaabbab" }}' + - XbXbX + - - '{{ replaceRE "a+b" "X" "aabbaabbab" 1 }}' + - Xbaabbab + RuneCount: + Aliases: null + Args: + - s + Description: RuneCount returns the number of runes in s. + Examples: [] + SliceString: + Aliases: + - slicestr + Args: + - a + - startEnd + Description: |- + SliceString slices a string by specifying a half-open range with + two indices, start and end. 1 and 4 creates a slice including elements 1 through 3. + The end index can be omitted, it defaults to the string's length. + Examples: + - - '{{ slicestr "BatMan" 0 3 }}' + - Bat + - - '{{ slicestr "BatMan" 3 }}' + - Man + Split: + Aliases: + - split + Args: + - a + - delimiter + Description: Split slices an input string into all substrings separated by delimiter. + Examples: [] + Substr: + Aliases: + - substr + Args: + - a + - nums + Description: 'Substr extracts parts of a string, beginning at the character at the specified\nposition, and returns the specified number of characters.\n\nIt normally takes two parameters: start and length.\nIt can also take one parameter: start, i.e. length is omitted, in which case\nthe substring starting from start until the end of the string will be returned.\n\nTo extract characters from the end of the string, use a negative start number.\n\nIn addition, borrowing from the extended behavior described at http://php.net/substr,\nif length is given and is negative, then that many characters will be omitted from\nthe end of string.' + Examples: + - - '{{ substr "BatMan" 0 -3 }}' + - Bat + - - '{{ substr "BatMan" 3 3 }}' + - Man + Title: + Aliases: + - title + Args: + - s + Description: |- + Title returns a copy of the input s with all Unicode letters that begin words + mapped to their title case. + Examples: + - - '{{ title "Bat man" }}' + - Bat Man + - - '{{ title "somewhere over the rainbow" }}' + - Somewhere Over the Rainbow + ToLower: + Aliases: + - lower + Args: + - s + Description: |- + ToLower returns a copy of the input s with all Unicode letters mapped to their + lower case. + Examples: + - - '{{ lower "BatMan" }}' + - batman + ToUpper: + Aliases: + - upper + Args: + - s + Description: |- + ToUpper returns a copy of the input s with all Unicode letters mapped to their + upper case. + Examples: + - - '{{ upper "BatMan" }}' + - BATMAN + Trim: + Aliases: + - trim + Args: + - s + - cutset + Description: |- + Trim returns converts the strings s removing all leading and trailing characters defined + contained. + Examples: + - - '{{ trim "++Batman--" "+-" }}' + - Batman + TrimLeft: + Aliases: null + Args: + - cutset + - s + Description: |- + TrimLeft returns a slice of the string s with all leading characters + contained in cutset removed. + Examples: + - - '{{ "aabbaa" | strings.TrimLeft "a" }}' + - bbaa + TrimPrefix: + Aliases: null + Args: + - prefix + - s + Description: |- + TrimPrefix returns s without the provided leading prefix string. If s doesn't + start with prefix, s is returned unchanged. + Examples: + - - '{{ "aabbaa" | strings.TrimPrefix "a" }}' + - abbaa + - - '{{ "aabbaa" | strings.TrimPrefix "aa" }}' + - bbaa + TrimRight: + Aliases: null + Args: + - cutset + - s + Description: |- + TrimRight returns a slice of the string s with all trailing characters + contained in cutset removed. + Examples: + - - '{{ "aabbaa" | strings.TrimRight "a" }}' + - aabb + TrimSpace: + Aliases: null + Args: + - s + Description: |- + TrimSpace returns the given string, removing leading and trailing whitespace + as defined by Unicode. + Examples: [] + TrimSuffix: + Aliases: null + Args: + - suffix + - s + Description: |- + TrimSuffix returns s without the provided trailing suffix string. If s + doesn't end with suffix, s is returned unchanged. + Examples: + - - '{{ "aabbaa" | strings.TrimSuffix "a" }}' + - aabba + - - '{{ "aabbaa" | strings.TrimSuffix "aa" }}' + - aabb + Truncate: + Aliases: + - truncate + Args: + - s + - options + Description: Truncate truncates the string in s to the specified length. + Examples: + - - '{{ "this is a very long text" | truncate 10 " ..." }}' + - this is a ... + - - '{{ "With [Markdown](/markdown) inside." | markdownify | truncate 14 }}' + - With Markdown … + templates: + Current: + Aliases: null + Args: + - ctx + Description: Get information about the currently executing template. + Examples: [] + Defer: + Aliases: null + Args: + - ctx + - args + Description: Defer defers the execution of a template block. + Examples: [] + DoDefer: + Aliases: + - doDefer + Args: + - ctx + - id + - optsv + Description: |- + DoDefer defers the execution of a template block. + For internal use only. + Examples: [] + Exists: + Aliases: null + Args: + - name + Description: |- + Exists returns whether the template with the given name exists. + Note that this is the Unix-styled relative path including filename suffix, + e.g. partials/header.html + Examples: + - - '{{ if (templates.Exists "_partials/header.html") }}Yes!{{ end }}' + - Yes! + - - '{{ if not (templates.Exists "_partials/doesnotexist.html") }}No!{{ end }}' + - No! + Inner: + Aliases: + - inner + Args: + - ctx + - data + Description: |- + Inner executes the inner content of a partial decorator. + Note that there is only one inner block per partial decorator, but inner may be called multiple times with, typically, different data. + Examples: [] + time: + AsTime: + Aliases: null + Args: + - v + - args + Description: |- + AsTime converts the textual representation of the datetime string into + a time.Time interface. + Examples: + - - '{{ (time "2015-01-21").Year }}' + - '2015' + Duration: + Aliases: + - duration + Args: + - unit + - number + Description: |- + Duration converts the given number to a time.Duration. + Unit is one of nanosecond/ns, microsecond/us/µs, millisecond/ms, second/s, minute/m or hour/h. + Examples: + - - '{{ mul 60 60 | duration "second" }}' + - 1h0m0s + Format: + Aliases: + - dateFormat + Args: + - layout + - v + Description: |- + Format converts the textual representation of the datetime string in v into + time.Time if needed and formats it with the given layout. + Examples: + - - 'dateFormat: {{ dateFormat "Monday, Jan 2, 2006" "2015-01-21" }}' + - 'dateFormat: Wednesday, Jan 21, 2015' + In: + Aliases: null + Args: + - timeZoneName + - t + Description: |- + In returns the time t in the IANA time zone specified by timeZoneName. + If timeZoneName is "" or "UTC", the time is returned in UTC. + If timeZoneName is "Local", the time is returned in the system's local time zone. + Otherwise, timeZoneName must be a valid IANA location name (e.g., "Europe/Oslo"). + Examples: [] + Now: + Aliases: + - now + Args: null + Description: Now returns the current local time or `clock` time + Examples: [] + ParseDuration: + Aliases: null + Args: + - s + Description: 'ParseDuration parses the duration string s.\nA duration string is a possibly signed sequence of\ndecimal numbers, each with optional fraction and a unit suffix,\nsuch as "300ms", "-1.5h" or "2h45m".\nValid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".\nSee https://golang.org/pkg/time/#ParseDuration' + Examples: + - - '{{ "1h12m10s" | time.ParseDuration }}' + - 1h12m10s + transform: + CanHighlight: + Aliases: null + Args: + - language + Description: CanHighlight returns whether the given code language is supported by the Chroma highlighter. + Examples: [] + Emojify: + Aliases: + - emojify + Args: + - s + Description: |- + Emojify returns a copy of s with all emoji codes replaced with actual emojis. + + See http://www.emoji-cheat-sheet.com/ + Examples: + - - '{{ "I :heart: Hugo" | emojify }}' + - I ❤️ Hugo + HTMLEscape: + Aliases: + - htmlEscape + Args: + - s + Description: HTMLEscape returns a copy of s with reserved HTML characters escaped. + Examples: + - - '{{ htmlEscape "Cathal Garvey & The Sunshine Band " | safeHTML }}' + - Cathal Garvey & The Sunshine Band <cathal@foo.bar> + - - '{{ htmlEscape "Cathal Garvey & The Sunshine Band " }}' + - Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt; + - - '{{ htmlEscape "Cathal Garvey & The Sunshine Band " | htmlUnescape | safeHTML }}' + - Cathal Garvey & The Sunshine Band + HTMLToMarkdown: + Aliases: null + Args: + - ctx + - args + Description: |- + This was added in Hugo v0.151.0 and should be considered experimental for now. + We need to test this out in the wild for a while before committing to this API, + and there will eventually be more options here. + Examples: [] + HTMLUnescape: + Aliases: + - htmlUnescape + Args: + - s + Description: |- + HTMLUnescape returns a copy of s with HTML escape requences converted to plain + text. + Examples: + - - '{{ htmlUnescape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>" | safeHTML }}' + - Cathal Garvey & The Sunshine Band + - - '{{ "Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;" | htmlUnescape | htmlUnescape | safeHTML }}' + - Cathal Garvey & The Sunshine Band + - - '{{ "Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;" | htmlUnescape | htmlUnescape }}' + - Cathal Garvey & The Sunshine Band <cathal@foo.bar> + - - '{{ htmlUnescape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>" | htmlEscape | safeHTML }}' + - Cathal Garvey & The Sunshine Band <cathal@foo.bar> + Highlight: + Aliases: + - highlight + Args: + - s + - args + Description: |- + Highlight returns a copy of CODE as an HTML string with syntax + highlighting applied. + + transform.Highlight CODE [LANG] [OPTIONS] + + LANG is optional; it can also be set via the type option in OPTIONS, which + makes this work the same way as HighlightCodeBlock. + Examples: [] + HighlightCodeBlock: + Aliases: null + Args: + - ctx + - opts + Description: HighlightCodeBlock highlights a code block on the form received in the codeblock render hooks. + Examples: [] + Markdownify: + Aliases: + - markdownify + Args: + - ctx + - s + Description: Markdownify renders s from Markdown to HTML. + Examples: + - - '{{ .Title | markdownify }}' + - BatMan + Plainify: + Aliases: + - plainify + Args: + - s + Description: Plainify returns a copy of s with all HTML tags removed. + Examples: + - - '{{ plainify "Hello world, gophers!" }}' + - Hello world, gophers! + PortableText: + Aliases: null + Args: + - v + Description: |- + PortableText converts the portable text in v to Markdown. + We may add more options in the future. + Examples: [] + Remarshal: + Aliases: null + Args: + - format + - data + Description: |- + Remarshal is used in the Hugo documentation to convert configuration + examples from YAML to JSON, TOML (and possibly the other way around). + The is primarily a helper for the Hugo docs site. + It is not a general purpose YAML to TOML converter etc., and may + change without notice if it serves a purpose in the docs. + Format is one of json, yaml or toml. + Examples: + - - '{{ "title = \"Hello World\"" | transform.Remarshal "json" | safeHTML }}' + - '{\n "title": "Hello World"\n}\n' + ToMath: + Aliases: null + Args: + - ctx + - args + Description: |- + ToMath converts a LaTeX string to math in the given format, default MathML. + This uses KaTeX to render the math, see https://katex.org/. + Examples: [] + Unmarshal: + Aliases: + - unmarshal + Args: + - args + Description: |- + Unmarshal unmarshals the data given, which can be either a string, json.RawMessage + or a Resource. Supported formats are JSON, TOML, YAML, and CSV. + You can optionally provide an options map as the first argument. + Examples: + - - '{{ "hello = \"Hello World\"" | transform.Unmarshal }}' + - map[hello:Hello World] + - - '{{ "hello = \"Hello World\"" | resources.FromString "data/greetings.toml" | transform.Unmarshal }}' + - map[hello:Hello World] + XMLEscape: + Aliases: null + Args: + - s + Description: |- + XMLEscape returns the given string, removing disallowed characters then + escaping the result to its XML equivalent. + Examples: + - - '{{ transform.XMLEscape "

    abc

    " }}' + - '<p>abc</p>' + urls: + AbsLangURL: + Aliases: + - absLangURL + Args: + - s + Description: |- + AbsLangURL the string s and converts it to an absolute URL according + to a page's position in the project directory structure and the current + language. + Examples: [] + AbsURL: + Aliases: + - absURL + Args: + - s + Description: AbsURL takes the string s and converts it to an absolute URL. + Examples: [] + Anchorize: + Aliases: + - anchorize + Args: + - s + Description: |- + Anchorize creates sanitized anchor name version of the string s that is compatible + with how your configured markdown renderer does it. + Examples: + - - '{{ "This is a title" | anchorize }}' + - this-is-a-title + JoinPath: + Aliases: null + Args: + - elements + Description: |- + JoinPath 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. + Examples: + - - '{{ urls.JoinPath "https://example.org" "foo" }}' + - https://example.org/foo + - - '{{ urls.JoinPath (slice "a" "b") }}' + - a/b + Parse: + Aliases: null + Args: + - rawurl + Description: |- + Parse parses rawurl into a URL structure. The rawurl may be relative or + absolute. + Examples: [] + PathEscape: + Aliases: null + Args: + - s + Description: |- + PathEscape returns the given string, applying percent-encoding to special + characters and reserved delimiters so it can be safely used as a segment + within a URL path. + Examples: [] + PathUnescape: + Aliases: null + Args: + - s + Description: |- + PathUnescape returns the given string, replacing all percent-encoded + sequences with the corresponding unescaped characters. + Examples: [] + Ref: + Aliases: + - ref + Args: + - p + - args + Description: Ref returns the absolute URL path to a given content item from Page p. + Examples: [] + RelLangURL: + Aliases: + - relLangURL + Args: + - s + Description: |- + RelLangURL takes the string s and prepends the relative path according to a + page's position in the project directory structure and the current language. + Examples: [] + RelRef: + Aliases: + - relref + Args: + - p + - args + Description: RelRef returns the relative URL path to a given content item from Page p. + Examples: [] + RelURL: + Aliases: + - relURL + Args: + - s + Description: |- + RelURL takes the string s and prepends the relative path according to a + page's position in the project directory structure. + Examples: [] + URLize: + Aliases: + - urlize + Args: + - s + Description: URLize returns the strings s formatted as an URL. + Examples: [] diff --git a/docs/data/embedded_template_urls.toml b/docs/data/embedded_template_urls.toml new file mode 100644 index 0000000..b2a796c --- /dev/null +++ b/docs/data/embedded_template_urls.toml @@ -0,0 +1,42 @@ +# Used by the embedded template URL (eturl.html) shortcode. +# Quoted all keys because some are not valid identifiers. + +# BaseURL +'base_url' = 'https://github.com/gohugoio/hugo/blob/master/tpl/tplimpl/embedded/templates' + +# Partials +'disqus' = '_partials/disqus.html' +'google_analytics' = '_partials/google_analytics.html' +'opengraph' = '_partials/opengraph.html' +'pagination' = '_partials/pagination.html' +'schema' = '_partials/schema.html' +'twitter_cards' = '_partials/twitter_cards.html' + +# Render hooks +'render-codeblock-goat' = '_markup/render-codeblock-goat.html' +'render-image' = '_markup/render-image.html' +'render-link' = '_markup/render-link.html' +'render-table' = '_markup/render-table.html' + +# Shortcodes +'details' = '_shortcodes/details.html' +'figure' = '_shortcodes/figure.html' +'gist' = '_shortcodes/gist.html' +'highlight' = '_shortcodes/highlight.html' +'instagram' = '_shortcodes/instagram.html' +'param' = '_shortcodes/param.html' +'qr' = '_shortcodes/qr.html' +'ref' = '_shortcodes/ref.html' +'relref' = '_shortcodes/relref.html' +'vimeo' = '_shortcodes/vimeo.html' +'vimeo_simple' = '_shortcodes/vimeo_simple.html' +'x' = '_shortcodes/x.html' +'x_simple' = '_shortcodes/x_simple.html' +'youtube' = '_shortcodes/youtube.html' + +# Other +'alias' = 'alias.html' +'robots' = 'robots.txt' +'rss' = 'rss.xml' +'sitemap' = 'sitemap.xml' +'sitemapindex' = 'sitemapindex.xml' diff --git a/docs/data/homepagetweets.toml b/docs/data/homepagetweets.toml new file mode 100644 index 0000000..b440c21 --- /dev/null +++ b/docs/data/homepagetweets.toml @@ -0,0 +1,265 @@ +[[tweet]] +name = "Heinrich Hartmann" +twitter_handle = "@heinrichhartman" +quote = "Working with @GoHugoIO is such a joy. Having worked with #Jekyll in the past, the near instant preview is a big win! Did not expect this to make such a huge difference." +link = "https://x.com/heinrichhartman/status/1199736512264462341" +date = 2019-11-12T00:00:00Z + +[[tweet]] +name = "Joshua Steven‏‏" +twitter_handle = "@jscarto" +quote = "Can't overstate how much I enjoy @GoHugoIO. My site is relatively small, but *18 ms* to build the whole thing made template development and proofing a breeze." +link = "https://x.com/jscarto/status/1039648827815485440" +date = 2018-09-12T00:00:00Z + +[[tweet]] +name = "Christophe Diericx" +twitter_handle = "@spcrngr_" +quote = "The more I use gohugo.io, the more I really like it. Super intuitive/powerful static site generator...great job @GoHugoIO" +link = "https://x.com/spcrngr_/status/870863020905435136" +date = 2017-06-03T00:00:00Z + +[[tweet]] +name = "marcoscan" +twitter_handle = "@marcoscan" +quote = "Blog migrated from @WordPress to @GoHugoIO, with a little refresh of my theme, Vim shortcuts and a full featured deploy script #gohugo" +link = "https://x.com/marcoscan/status/869661175960752129" +date = 2017-05-30T00:00:00Z + +[[tweet]] +name = "Sandra Kuipers" +twitter_handle = "@SKuipersDesign" +quote = "Who knew static site building could be fun 🤔 Learning #gohugo today" +link = "https://x.com/SKuipersDesign/status/868796256902029312" +date = 2017-05-28T00:00:00Z + +[[tweet]] +name = "Netlify" +twitter_handle = "@Netlify" +quote = "Top Ten Static Site Generators of 2017. Congrats to the top 3: 1. @Jekyllrb 2. @GoHugoIO 3. @hexojs" +link = "https://x.com/Netlify/status/868122279221362688" +date = 2017-05-26T00:00:00Z + +[[tweet]] +name = "Phil Hawksworth" +twitter_handle = "@philhawksworth" +quote = "I've been keen on #JAMStack for some time, but @GoHugoIO is wooing me all over again. Great fun to build with. And speeeeedy." +link = "https://x.com/philhawksworth/status/866684170512326657" +date = 2017-05-22T00:00:00Z + +[[tweet]] +name = "Aras Pranckevicius" +twitter_handle = "@aras_p" +quote = "I've probably said it before...but having Hugo rebuild the whole website in 300ms is amazing. gohugo.io, #gohugo" +link = "https://x.com/aras_p/status/861157286823288832" +date = 2017-05-07T00:00:00Z + +[[tweet]] +name = "Hans Beck" +twitter_handle = "@EnrichedGamesHB" +quote = "Diving deeper into @GoHugoIO. A lot of docs there, top work! But I've the impressed that #gohugo is far easier than its feels from the docs!" +link = "https://x.com/EnrichedGamesHB/status/836854762440130560" +date = 2017-03-01T00:00:00Z + +[[tweet]] +name = "Alan Richardson" +twitter_handle = "@eviltester" +quote = "I migrated the @BlackOpsTesting .com website from docpad to Hugo last weekend. http://gohugo.io/ Super Fast HTML Generation @spf13 " +link = "https://x.com/eviltester/status/553520335115808768" +date = 2015-01-09T00:00:00Z + +[[tweet]] +name = "Janez Čadež‏" +twitter_handle = "@jamziSLO" +quote = "Building @garazaFRI website in #hugo. This static site generator is soooo damn fast! #gohugo #golang" +link = "https://x.com/jamziSLO/status/817720283977183234" +date = 2017-01-07T00:00:00Z + +[[tweet]] +name = "Execute‏‏" +twitter_handle = "@executerun" +quote = "Hah, #gohugo. I was working with #gohugo on #linux but now I realised how easy is to set-up it on #windows. Just need to add binary to #path!" +link = "https://x.com/executerun/status/809753145270272005" +date = 2016-12-16T00:00:00Z + +[[tweet]] +name = "Baron Schwartz" +twitter_handle = "@xaprb" +quote = "Hugo is impressively capable. It's a static site generator by @spf13 written in #golang . Just upgraded to latest release; very powerful. " +link = "https://x.com/xaprb/status/556894866488455169" +date = 2015-01-18T00:00:00Z + +[[tweet]] +name = "Dave Cottlehuber" +twitter_handle = "@dch__" +quote = "I just fell in love with #hugo, a static site/blog engine written by @spf13 in #golang + stellar docs" +link = "https://x.com/dch__/status/460158115498176512" +date = 2014-04-26T00:00:00Z + +[[tweet]] +name = "David Caunt" +twitter_handle = "@dcaunt" +quote = "I had a play with Hugo and it was good, uses Markdown files for content" +link = "https://x.com/dcaunt/statuses/406466996277374976" +date = 2013-11-29T00:00:00Z + +[[tweet]] +name = "David Gay" +twitter_handle = "@oddshocks" +quote = "Hugo is super-rad." +link = "https://x.com/oddshocks/statuses/405083217893421056" +date = 2013-11-25T00:00:00Z + +[[tweet]] +name = "Diti" +twitter_handle = "@DitiPengi" +quote = "The dev version of Hugo is AWESOME! <3 I promise, I will try to learn go ASAP and help contribute to the project! Just too great!" +link = "https://x.com/DitiPengi/status/472470974051676160" +date = 2014-05-30T00:00:00Z + +[[tweet]] +name = "Douglas Stephen " +twitter_handle = "@DougStephenJr" +quote = "Even as a long-time Octopress fan, I’ve gotta admit that this project Hugo looks very very cool" +link = "https://x.com/DougStephenJr/statuses/364512471660249088" +date = 2013-08-05T00:00:00Z + +[[tweet]] +name = "Hugo Rodger-Brown" +twitter_handle = "@hugorodgerbrown" +quote = "Finally someone builds me my own static site generator" +link = "https://x.com/hugorodgerbrown/statuses/364417910153818112" +date = 2013-05-08T00:00:00Z + +[[tweet]] +name = "Hugo Roy" +twitter_handle = "@hugoroyd" +quote = "Finally the answer to the question my parents have been asking: What does Hugo do?" +link = "https://x.com/hugoroyd/status/501704796727173120" +date = 2014-08-19T00:00:00Z + +[[tweet]] +name = "Daniel Miessler" +twitter_handle = "@DanielMiessler" +quote = "Websites for named vulnerabilities should run on static site generator platforms like Hugo. Read-only + burst traffic = static." +link = "https://x.com/DanielMiessler/status/704703841673957376" +date = 2016-03-01T00:00:00Z + +[[tweet]] +name = "Javier Segura" +twitter_handle = "@jsegura" +quote = "Another site generated with Hugo here! I'm getting in love with it." +link = "https://x.com/jsegura/status/465978434154659841" +date = 2014-05-12T00:00:00Z + +[[tweet]] +name = "Jim Biancolo" +twitter_handle = "@jimbiancolo" +quote = "I’m loving the static site generator renaissance we are currently enjoying. Hugo is new, looks great, written in Go" +link = "https://x.com/jimbiancolo/statuses/408678420348813314" +date = 2013-05-12T00:00:00Z + +[[tweet]] +name = "Jip J. Dekker" +twitter_handle = "@jipjdekker" +quote = "Building a personal website in Hugo. Works like a charm. And written in @golang!" +link = "https://x.com/jipjdekker/status/413783548735152131" +date = 2013-12-19T00:00:00Z + +[[tweet]] +name = "Jose Gonzalvo" +twitter_handle = "@jgonzalvo" +quote = "Checking out Hugo; Loving it so far. Like Jekyll but not so blog-oriented and written in go" +link = "https://x.com/jgonzalvo/statuses/408177855819173888" +date = 2013-12-04T00:00:00Z + +[[tweet]] +name = "Josh Matz" +twitter_handle = "@joshmatz" +quote = "A static site generator without the long build times? Yes, please!" +link = "https://x.com/joshmatz/statuses/364437436870696960" +date = 2013-08-05T00:00:00Z + +[[tweet]] +name = "Kieran Healy" +twitter_handle = "@kjhealy" +quote = "OK, so in today's speed battle of static site generators, @spf13's hugo is kicking everyone's ass, by miles." +link = "https://x.com/kjhealy/status/437349384809115648" +date = 2014-02-22T00:00:00Z + +[[tweet]] +name = "Ludovic Chabant" +twitter_handle = "@ludovicchabant" +quote = "Good work on Hugo, I’m impressed with the speed!" +link = "https://x.com/ludovicchabant/statuses/408806199602053120" +date = 2013-12-06T00:00:00Z + +[[tweet]] +name = "Luke Holder" +twitter_handle = "@lukeholder" +quote = "this is AWESOME. a single little executable and so fast." +link = "https://x.com/lukeholder/status/430352287936946176" +date = 2014-02-03T00:00:00Z + +[[tweet]] +name = "Markus Eliasson" +twitter_handle = "@markuseliasson" +quote = "Hugo is fast, dead simple to setup and well documented" +link = "https://x.com/markuseliasson/status/501594865877008384" +date = 2014-08-19T00:00:00Z + +[[tweet]] +name = "mercime" +twitter_handle = "@mercime_one" +quote = "Hugo: Makes the Web Fun Again" +link = "https://x.com/mercime_one/status/500547145087205377" +date = 2014-08-16T00:00:00Z + +[[tweet]] +name = "Michael Whatcott" +twitter_handle = "@mdwhatcott" +quote = "One more satisfied #Hugo blogger. Thanks @spf13 and friends!" +link = "https://x.com/mdwhatcott/status/469980686531571712" +date = 2014-05-23T00:00:00Z + +[[tweet]] +name = "Nathan Toups" +twitter_handle = "@rojoroboto" +quote = "I love Hugo! My site is generated with it now http://rjrbt.io" +link = "https://x.com/rojoroboto/status/423439915620106242" +date = 2014-01-15T00:00:00Z + +[[tweet]] +name = "Ruben Solvang" +twitter_handle = "@messo85" +quote = "#Hugo is the new @jekyllrb / @middlemanapp! Faster, easier and runs everywhere." +link = "https://x.com/messo85/status/472825062027182081" +date = 2014-05-31T00:00:00Z + +[[tweet]] +name = "Ryan Martinsen" +twitter_handle = "@popthestack" +quote = "Also, I re-launched my blog (it looks the same as before) using Hugo, a *fast* static engine. Very happy with it. gohugo.io" +link = "https://x.com/popthestack/status/549972754125307904" +date = 2014-12-30T00:00:00Z + +[[tweet]] +name = "The Lone Cuber" +twitter_handle = "@TheLoneCuber" +quote = "Jekyll is dead to me these days though... long live Hugo! Hugo is *by far* the best in its field. Thanks for making it happen." +link = "https://x.com/TheLoneCuber/status/495716684456398848" +date = 2014-08-02T00:00:00Z + +[[tweet]] +name = "The Lone Cuber" +twitter_handle = "@TheLoneCuber" +quote = "Finally, a publishing platform that's a joy to use. #NoMoreBarriers" +link = "https://x.com/TheLoneCuber/status/495731334711488512" +date = 2014-08-02T00:00:00Z + +[[tweet]] +name = "WorkHTML" +twitter_handle = "@workhtml" +quote = " #Hugo A very good alternative for #wordpress !!! A fast and modern static website engine gohugo.io " +link = "https://x.com/workhtml/status/563064361301053440" +date = 2015-02-04T00:00:00Z diff --git a/docs/data/keywords.yaml b/docs/data/keywords.yaml new file mode 100644 index 0000000..5cc0e93 --- /dev/null +++ b/docs/data/keywords.yaml @@ -0,0 +1,17 @@ +# We use the front matter keywords field to determine related content. To +# ensure consistency, during site build we validate each keyword against the +# entries in data/keywords.yaml. + +# As of March 5, 2025, this feature is experimental, pending usability +# assessment. We anticipate that the number of additions to data/keywords.yaml +# will decrease over time, though the initial implementation will require some +# effort. + +- decorator +- filter +- highlight +- menu +- metadata +- process +- random +- resource diff --git a/docs/data/page_filters.yaml b/docs/data/page_filters.yaml new file mode 100644 index 0000000..ce29b07 --- /dev/null +++ b/docs/data/page_filters.yaml @@ -0,0 +1,93 @@ +# Do not delete. This file defines filters that can be applied to lists of +# pages when rendering. The filters are defined as lists of page paths. A +# filter can be applied to a list of pages by specifying the filter name and +# filter type (include or exclude). Used by: +# +# - layouts/_shortcodes/render-list-of-pages-in-section.html +# - layouts/_shortcodes/render-table-of-pages-in-section.html + +functions_fmt_logging: + - /functions/fmt/errorf + - /functions/fmt/erroridf + - /functions/fmt/warnf + - /functions/fmt/warnidf +functions_images_no_filters: + - /functions/images/filter + - /functions/images/config +methods_site_multilingual: + - /methods/site/ismultilingual + - /methods/site/language + - /methods/site/languageprefix + - /methods/site/languages +methods_site_page_collections: + - /methods/site/allpages + - /methods/site/pages + - /methods/site/regularpages + - /methods/site/sections +methods_page_dates: + - /methods/page/date + - /methods/page/expirydate + - /methods/page/lastmod + - /methods/page/publishdate +methods_page_menu: + - /methods/page/hasmenucurrent + - /methods/page/ismenucurrent +methods_page_multilingual: + - /methods/page/alltranslations + - /methods/page/istranslated + - /methods/page/language + - /methods/page/translationkey + - /methods/page/translations +methods_page_page_collections: + - /methods/page/pages + - /methods/page/regularpages + - /methods/page/regularpagesrecursive + - /methods/page/sections +methods_page_parameters: + - /methods/page/param + - /methods/page/params +methods_page_sections: + - /methods/page/ancestors + - /methods/page/currentsection + - /methods/page/firstsection + - /methods/page/insection + - /methods/page/isancestor + - /methods/page/isdescendant + - /methods/page/parent + - /methods/page/sections + - /methods/page/section +methods_pages_sort: + - /methods/pages/bydate + - /methods/pages/byexpirydate + - /methods/pages/bylanguage + - /methods/pages/bylastmod + - /methods/pages/bylength + - /methods/pages/bylinktitle + - /methods/pages/byparam + - /methods/pages/bypublishdate + - /methods/pages/bytitle + - /methods/pages/byweight + - /methods/pages/reverse +methods_pages_group: + - /methods/pages/groupby + - /methods/pages/groupbydate + - /methods/pages/groupbyexpirydate + - /methods/pages/groupbylastmod + - /methods/pages/groupbyparam + - /methods/pages/groupbyparamdate + - /methods/pages/groupbypublishdate +methods_pages_navigation: + - /methods/pages/next + - /methods/pages/prev +methods_page_navigation: + - /methods/page/next + - /methods/page/nextinsection + - /methods/page/prev + - /methods/page/previnsection +methods_resource_image_processing: + - /methods/resource/crop + - /methods/resource/fill + - /methods/resource/filter + - /methods/resource/fit + - /methods/resource/fit + - /methods/resource/resize diff --git a/docs/data/sponsors.toml b/docs/data/sponsors.toml new file mode 100644 index 0000000..be29450 --- /dev/null +++ b/docs/data/sponsors.toml @@ -0,0 +1,22 @@ +[[banners]] + name = "GoLand" + title = "The complete IDE crafted for professional Go developers." + no_query_params = true + link = "https://www.jetbrains.com/go/?utm_source=OSS&utm_medium=referral&utm_campaign=hugo" + logo = "images/sponsors/goland.svg" + bgcolor = "#f4f4f4" + +[[banners]] + name = "CloudCannon" + link = "https://cloudcannon.com/hugo-cms/?utm_campaign=HugoSponsorship&utm_content=gohugo&utm_medium=banner&utm_source=sponsor" + logo = "images/sponsors/cloudcannon-cms-logo.svg" + no_query_params = true + bgcolor = "#ffffff" + +[[banners]] + name = "Your Company?" + link = "https://bep.is/en/hugo-sponsor-2023-01/" + utm_campaign = "hugosponsor" + show_on_hover = true + bgcolor = "#4e4f4f" + link_attr = "style='color: #ffffff; font-weight: bold; text-decoration: none; text-align: center'" diff --git a/docs/go.mod b/docs/go.mod new file mode 100644 index 0000000..1574f91 --- /dev/null +++ b/docs/go.mod @@ -0,0 +1,3 @@ +module github.com/gohugoio/hugoDocs + +go 1.26.0 diff --git a/docs/hugo.toml b/docs/hugo.toml new file mode 100644 index 0000000..cfac829 --- /dev/null +++ b/docs/hugo.toml @@ -0,0 +1,191 @@ +baseURL = "https://gohugo.io/" +defaultContentLanguage = "en" +enableEmoji = true +pluralizeListTitles = false +timeZone = "Europe/Oslo" +title = "Hugo" + +# We do redirects via Netlify's _redirects file, generated by Hugo (see "outputs" below). +disableAliases = true + +[build] + [build.buildStats] + disableIDs = true + enable = true + [[build.cachebusters]] + source = "assets/notwatching/hugo_stats\\.json" + target = "css" + [[build.cachebusters]] + source = "(postcss|tailwind)\\.config\\.js" + target = "css" + +[caches] + [caches.images] + dir = ":cacheDir/images" + maxAge = "1440h" + [caches.getresource] + dir = ':cacheDir/:project' + maxAge = "1h" + +[[cascade]] + [cascade.params] + hide_in_this_section = true + show_publish_date = true + [cascade.target] + kind = 'page' + path = '{/news/**}' +[[cascade]] + [cascade.params] + searchable = true + [cascade.target] + kind = 'page' +[[cascade]] + [cascade.params] + searchable = false + [cascade.target] + kind = '{home,section,taxonomy,term}' +[[cascade]] + [cascade.params] + isFunctionOrMethod = true + [cascade.target] + path = '{/functions/**,/methods/**}' + kind = 'page' + +[frontmatter] + date = ['date'] # do not add publishdate; it will affect page sorting + expiryDate = ['expirydate'] + lastmod = [':git', 'lastmod', 'publishdate', 'date'] + publishDate = ['publishdate', 'date'] + +[languages] + [languages.en] + direction = 'ltr' + label = 'English' + locale = 'en-US' + weight = 1 + +[markup] + [markup.goldmark] + [markup.goldmark.extensions] + [markup.goldmark.extensions.passthrough] + enable = true + [markup.goldmark.extensions.passthrough.delimiters] + block = [['\[', '\]'], ['$$', '$$']] + inline = [['\(', '\)']] + [markup.goldmark.parser] + autoDefinitionTermID = true + wrapStandAloneImageWithinParagraph = false + [markup.goldmark.parser.attribute] + block = true + [markup.highlight] + lineNumbersInTable = false + noClasses = false + style = 'solarized-dark' + wrapperClass = 'highlight not-prose' + +[mediaTypes] + [mediaTypes."text/netlify"] + delimiter = "" + +[module] + [module.hugoVersion] + min = "0.144.0" + [[module.mounts]] + source = "assets" + target = "assets" + [[module.mounts]] + source = 'content/en' + target = 'content' + [module.mounts.sites.matrix] + languages = ['en'] + [[module.mounts]] + disableWatch = true + source = "hugo_stats.json" + target = "assets/notwatching/hugo_stats.json" + +[outputFormats] + [outputFormats.redir] + baseName = "_redirects" + isPlainText = true + mediatype = "text/netlify" + [outputFormats.headers] + baseName = "_headers" + isPlainText = true + mediatype = "text/netlify" + notAlternative = true + +[outputs] + home = ["html", "rss", "redir", "headers"] + page = ["html"] + section = ["html"] + taxonomy = ["html"] + term = ["html"] + +[params] + description = "The world’s fastest framework for building websites" + ghrepo = "https://github.com/gohugoio/hugoDocs/" + [params.render_hooks.link] + errorLevel = 'warning' # ignore (default), warning, or error (fails the build) + [params.social.mastodon] + url = "https://fosstodon.org/@gohugoio" + +[related] + includeNewer = true + threshold = 80 + toLower = true + [[related.indices]] + name = 'keywords' + weight = 1 + +[security] + [security.funcs] + getenv = ['^HUGO_', '^REPOSITORY_URL$', '^BRANCH$'] + +[server] + [[server.headers]] + for = "/*" + [server.headers.values] + X-Frame-Options = "DENY" + X-XSS-Protection = "1; mode=block" + X-Content-Type-Options = "nosniff" + Referrer-Policy = "no-referrer" + [[server.headers]] + for = "/**.{css,js}" + +[services] + [services.googleAnalytics] + ID = 'G-MBZGKNMDWC' + +[taxonomies] + category = 'categories' + + ######## GLOBAL ITEMS TO BE SHARED WITH THE HUGO SITES ######## + +[menus] + [[menus.global]] + identifier = 'news' + name = 'News' + pageRef = '/news/' + weight = 1 + [[menus.global]] + identifier = 'docs' + name = 'Docs' + url = '/documentation/' + weight = 5 + [[menus.global]] + identifier = 'themes' + name = 'Themes' + url = 'https://themes.gohugo.io/' + weight = 10 + [[menus.global]] + identifier = 'community' + name = 'Community' + post = 'external' + url = 'https://discourse.gohugo.io/' + weight = 150 + [[menus.global]] + identifier = 'github' + name = 'GitHub' + post = 'external' + url = 'https://github.com/gohugoio/hugo' + weight = 200 diff --git a/docs/hugo.work b/docs/hugo.work new file mode 100644 index 0000000..957f5b9 --- /dev/null +++ b/docs/hugo.work @@ -0,0 +1,3 @@ +go 1.26.0 + +use . diff --git a/docs/hugoreleaser.yaml b/docs/hugoreleaser.yaml new file mode 100644 index 0000000..9f8671e --- /dev/null +++ b/docs/hugoreleaser.yaml @@ -0,0 +1,29 @@ +project: hugoDocs +release_settings: + name: ${HUGORELEASER_TAG} + type: github + repository: hugoDocs + repository_owner: gohugoio + draft: true + prerelease: false + release_notes_settings: + generate: true + generate_on_host: false + short_threshold: 10 + short_title: What's Changed + groups: + - regexp: snapcraft:|Merge commit|Merge branch|netlify:|release:|Squashed + ignore: true + - title: Typo fixes + regexp: typo + ordinal: 20 + - title: Dependency Updates + regexp: deps + ordinal: 30 + - title: Improvements + regexp: .* + ordinal: 10 +releases: + - paths: + - archives/** + path: myrelease diff --git a/docs/layouts/404.html b/docs/layouts/404.html new file mode 100644 index 0000000..4384cdf --- /dev/null +++ b/docs/layouts/404.html @@ -0,0 +1,22 @@ +{{ define "main" }} +
    +
    +

    + Page not found + gopher +

    + + +
    +
    +{{ end }} diff --git a/docs/layouts/_markup/render-blockquote.html b/docs/layouts/_markup/render-blockquote.html new file mode 100644 index 0000000..af44192 --- /dev/null +++ b/docs/layouts/_markup/render-blockquote.html @@ -0,0 +1,33 @@ +{{- if eq .Type "alert" }} + {{- $alerts := dict + "caution" (dict "color" "red" "icon" "exclamation-triangle") + "important" (dict "color" "blue" "icon" "exclamation-circle") + "note" (dict "color" "blue" "icon" "information-circle") + "tip" (dict "color" "green" "icon" "light-bulb") + "warning" (dict "color" "orange" "icon" "exclamation-triangle") + }} + + {{- $alertTypes := slice }} + {{- range $k, $_ := $alerts }} + {{- $alertTypes = $alertTypes | append $k }} + {{- end }} + {{- $alertTypes = $alertTypes | sort }} + + {{- $alertType := strings.ToLower .AlertType }} + {{- if in $alertTypes $alertType }} + {{- partial "layouts/blocks/alert.html" (dict + "color" (or ((index $alerts $alertType).color) "blue") + "icon" (or ((index $alerts $alertType).icon) "information-circle") + "text" .Text + "title" .AlertTitle + "class" .Attributes.class + ) + }} + {{- else }} + {{- errorf `Invalid blockquote alert type. Received %s. Expected one of %s (case-insensitive). See %s.` .AlertType (delimit $alertTypes ", " ", or ") .Page.String }} + {{- end }} +{{- else }} +
    + {{ .Text }} +
    +{{- end }} diff --git a/docs/layouts/_markup/render-codeblock.html b/docs/layouts/_markup/render-codeblock.html new file mode 100644 index 0000000..65f1552 --- /dev/null +++ b/docs/layouts/_markup/render-codeblock.html @@ -0,0 +1,113 @@ +{{/* +Renders a highlighted code block using the given options and attributes. + +In addition to the options available to the transform.Highlight function, you +may also specify the following parameters: + +@param {bool} [copy=false] Whether to display a copy-to-clipboard button. +@param {string} [file] The file name to display above the rendered code. +@param {bool} [details=false] Whether to wrap the highlighted code block within a details element. +@param {bool} [open=false] Whether to initially display the content of the details element. +@param {string} [summary=Details] The content of the details summary element rendered from Markdown to HTML. +@param {bool} [trim=true] Whether to trim leading and trailing whitespace from the content of the code block. + +@returns {template.HTML} + +@examples + + ```go + fmt.Println("Hello world!") + ``` + + ```go {linenos=true file="layouts/index.html" copy=true} + fmt.Println("Hello world!") + ``` +*/}} + +{{- $copy := false }} +{{- $file := or .Attributes.file "" }} +{{- $details := false }} +{{- $open := "" }} +{{- $summary := or .Attributes.summary "Details" | .Page.RenderString }} +{{- $trim := true }} +{{- $ext := strings.TrimPrefix "." (path.Ext $file) }} +{{- $lang := or .Type $ext "text" }} +{{- if in (slice "html" "gotmpl") $lang }} + {{- $lang = "go-html-template" }} +{{- else if in (slice "bash" "ksh" "sh" "shell" "zsh") $lang }} + {{- $lang = "text" }} +{{- else if in (slice "md" "mkd") $lang }} + {{- $lang = "text" }} +{{- else if eq $lang "tree" }} + {{- $lang = "text" }} +{{- end }} + +{{- if collections.IsSet .Attributes "copy" }} + {{- if in (slice true "true" 1) .Attributes.copy }} + {{- $copy = true }} + {{- else if in (slice false "false" 0) .Attributes.copy }} + {{- $copy = false }} + {{- end }} +{{- end }} + +{{- if collections.IsSet .Attributes "details" }} + {{- if in (slice true "true" 1) .Attributes.details }} + {{- $details = true }} + {{- else if in (slice false "false" 0) .Attributes.details }} + {{- $details = false }} + {{- end }} +{{- end }} + +{{- if collections.IsSet .Attributes "open" }} + {{- if in (slice true "true" 1) .Attributes.open }} + {{- $open = "open" }} + {{- else if in (slice false "false" 0) .Attributes.open }} + {{- $open = "" }} + {{- end }} +{{- end }} + +{{- if collections.IsSet .Attributes "trim" }} + {{- if in (slice true "true" 1) .Attributes.trim }} + {{- $trim = true }} + {{- else if in (slice false "false" 0) .Attributes.trim }} + {{- $trim = false }} + {{- end }} +{{- end }} + +{{- if $details }} +
    + {{ $summary }} +{{- end }} + +
    + {{- $fileSelectClass := "select-none" }} + {{- if $copy }} + {{- $fileSelectClass = "select-text" }} + + + + {{- end }} + {{- with $file }} +
    + {{ . }} +
    + {{- end }} + +
    + {{- $content := .Inner }} + {{- if $trim }} + {{- $content = strings.TrimSpace .Inner }} + {{- end }} + {{- transform.Highlight $content $lang .Options }} +
    +
    + +{{- if $details }} +
    +{{- end }} diff --git a/docs/layouts/_markup/render-link.html b/docs/layouts/_markup/render-link.html new file mode 100644 index 0000000..6e38d50 --- /dev/null +++ b/docs/layouts/_markup/render-link.html @@ -0,0 +1,300 @@ +{{/* +This render hook resolves internal destinations by looking for a matching: + + 1. Content page + 2. Page resource (a file in the current page bundle) + 3. Section resource (a file in the current section) + 4. Global resource (a file in the assets directory) + +It skips the section resource lookup if the current page is a leaf bundle. + +External destinations are not modified. + +You must place global resources in the assets directory. If you have placed +your resources in the static directory, and you are unable or unwilling to move +them, you must mount the static directory to the assets directory by including +both of these entries in your project configuration: + + [[module.mounts]] + source = 'assets' + target = 'assets' + + [[module.mounts]] + source = 'static' + target = 'assets' + +By default, if this render hook is unable to resolve a destination, including a +fragment if present, it passes the destination through without modification. To +emit a warning or error, set the error level in your project configuration: + + [params.render_hooks.link] + errorLevel = 'warning' # ignore (default), warning, or error (fails the build) + +When you set the error level to warning, and you are in a development +environment, you can visually highlight broken internal links: + + [params.render_hooks.link] + errorLevel = 'warning' # ignore (default), warning, or error (fails the build) + highlightBroken = true # true or false (default) + +This will add a "broken" class to anchor elements with invalid src attributes. +Add a rule to your CSS targeting the broken links: + + a.broken { + background: #ff0; + border: 2px solid #f00; + padding: 0.1em 0.2em; + } + +This render hook may be unable to resolve destinations created with the ref and +relref shortcodes. Unless you set the error level to ignore you should not use +either of these shortcodes in conjunction with this render hook. + +@context {string} Destination The link destination. +@context {page} Page A reference to the page containing the link. +@context {string} PlainText The link description as plain text. +@context {string} Text The link description. +@context {string} Title The link title. + +@returns {template.html} +*/}} + +{{- /* Initialize. */}} +{{- $renderHookName := "link" }} + +{{- /* Verify minimum required version. */}} +{{- $minHugoVersion := "0.141.0" }} +{{- if lt hugo.Version $minHugoVersion }} + {{- errorf "The %q render hook requires Hugo v%s or later." $renderHookName $minHugoVersion }} +{{- end }} + +{{- /* Error level when unable to resolve destination: ignore, warning, or error. */}} +{{- $errorLevel := or site.Params.render_hooks.link.errorLevel "ignore" | lower }} + +{{- /* If true, adds "broken" class to broken links. Applicable in development environment when errorLevel is warning. */}} +{{- $highlightBrokenLinks := or site.Params.render_hooks.link.highlightBroken false }} + +{{- /* Validate error level. */}} +{{- if not (in (slice "ignore" "warning" "error") $errorLevel) }} + {{- errorf "The %q render hook is misconfigured. The errorLevel %q is invalid. Please check your project configuration." $renderHookName $errorLevel }} +{{- end }} + +{{- /* Determine content path for warning and error messages. */}} +{{- $contentPath := .Page.String }} + +{{- /* Parse destination. */}} +{{- $u := urls.Parse .Destination }} + +{{- /* Set common message. */}} +{{- $msg := printf "The %q render hook was unable to resolve the destination %q in %s" $renderHookName $u.String $contentPath }} + +{{- /* Set attributes for anchor element. */}} +{{- $attrs := dict "href" $u.String }} +{{- if eq $u.String "g" }} + {{- /* Destination is a glossary term. */}} + {{- $ctx := dict + "contentPath" $contentPath + "errorLevel" $errorLevel + "renderHookName" $renderHookName + "text" .Text + }} + {{- $attrs = partial "inline/h-rh-l/get-glossary-link-attributes.html" $ctx }} +{{- else if $u.IsAbs }} + {{- /* Destination is a remote resource. */}} + {{- $attrs = merge $attrs (dict "rel" "external nofollow") }} +{{- else }} + {{- with $u.Path }} + {{- with $p := or ($.PageInner.GetPage .) ($.PageInner.GetPage (strings.TrimRight "/" .)) }} + {{- /* Destination is a page. */}} + {{- $href := .RelPermalink }} + {{- with $u.RawQuery }} + {{- $href = printf "%s?%s" $href . }} + {{- end }} + {{- with $u.Fragment }} + {{- $ctx := dict + "contentPath" $contentPath + "errorLevel" $errorLevel + "page" $p + "parsedURL" $u + "renderHookName" $renderHookName + }} + {{- partial "inline/h-rh-l/validate-fragment.html" $ctx }} + {{- $href = printf "%s#%s" $href . }} + {{- end }} + {{- $attrs = dict "href" $href }} + {{- else with $.PageInner.Resources.Get $u.Path }} + {{- /* Destination is a page resource; drop query and fragment. */}} + {{- $attrs = dict "href" .RelPermalink }} + {{- else with (and (ne $.Page.BundleType "leaf") ($.Page.CurrentSection.Resources.Get $u.Path)) }} + {{- /* Destination is a section resource, and current page is not a leaf bundle. */}} + {{- $attrs = dict "href" .RelPermalink }} + {{- else with resources.Get $u.Path }} + {{- /* Destination is a global resource; drop query and fragment. */}} + {{- $attrs = dict "href" .RelPermalink }} + {{- else }} + {{- if eq $errorLevel "warning" }} + {{- warnf $msg }} + {{- if and $highlightBrokenLinks hugo.IsDevelopment }} + {{- $attrs = merge $attrs (dict "class" "broken") }} + {{- end }} + {{- else if eq $errorLevel "error" }} + {{- errorf $msg }} + {{- end }} + {{- end }} + {{- else }} + {{- with $u.Fragment }} + {{- /* Destination is on the same page; prepend relative permalink. */}} + {{- $ctx := dict + "contentPath" $contentPath + "errorLevel" $errorLevel + "page" $.Page + "parsedURL" $u + "renderHookName" $renderHookName + }} + {{- partial "inline/h-rh-l/validate-fragment.html" $ctx }} + {{- $attrs = dict "href" (printf "%s#%s" $.Page.RelPermalink .) }} + {{- else }} + {{- if eq $errorLevel "warning" }} + {{- warnf $msg }} + {{- if and $highlightBrokenLinks hugo.IsDevelopment }} + {{- $attrs = merge $attrs (dict "class" "broken") }} + {{- end }} + {{- else if eq $errorLevel "error" }} + {{- errorf $msg }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} + +{{- /* Render anchor element. */ -}} +{{ .Text }} + +{{- define "_partials/inline/h-rh-l/validate-fragment.html" }} +{{- /* + Validates the fragment portion of a link destination. + + @context {string} contentPath The page containing the link. + @context {string} errorLevel The error level when unable to resolve destination; ignore (default), warning, or error. + @context {page} page The page corresponding to the link destination + @context {struct} parsedURL The link destination parsed by urls.Parse. + @context {string} renderHookName The name of the render hook. + */}} + +{{- /* Initialize. */}} +{{- $contentPath := .contentPath }} +{{- $errorLevel := .errorLevel }} +{{- $p := .page }} +{{- $u := .parsedURL }} +{{- $renderHookName := .renderHookName }} + +{{- /* Validate. */}} +{{- with $u.Fragment }} + {{- if $p.Fragments.Identifiers.Contains . }} + {{- if gt ($p.Fragments.Identifiers.Count .) 1 }} + {{- $msg := printf "The %q render hook detected duplicate heading IDs %q in %s" $renderHookName . $contentPath }} + {{- if eq $errorLevel "warning" }} + {{- warnf $msg }} + {{- else if eq $errorLevel "error" }} + {{- errorf $msg }} + {{- end }} + {{- end }} + {{- else }} + {{- /* Determine target path for warning and error message. */}} + {{- $targetPath := "" }} + {{- with $p.File }} + {{- $targetPath = .Path }} + {{- else }} + {{- $targetPath = .Path }} + {{- end }} + {{- /* Set common message. */}} + {{- $msg := printf "The %q render hook was unable to find heading ID %q in %s. See %s" $renderHookName . $targetPath $contentPath }} + {{- if eq $targetPath $contentPath }} + {{- $msg = printf "The %q render hook was unable to find heading ID %q in %s" $renderHookName . $targetPath }} + {{- end }} + {{- /* Throw warning or error. */}} + {{- if eq $errorLevel "warning" }} + {{- warnf $msg }} + {{- else if eq $errorLevel "error" }} + {{- errorf $msg }} + {{- end }} + {{- end }} +{{- end }} +{{- end }} + +{{- define "_partials/inline/h-rh-l/get-glossary-link-attributes.html" }} +{{- /* + Returns the anchor element attributes for a link to the given glossary term. + + It first checks for the existence of a glossary page for the given term. If + no page is found, it then checks for a glossary page for the singular form of + the term. If neither page exists it throws a warning or error dependent on + the errorLevel setting + + The returned href attribute does not point to the glossary term page. + Instead, via its fragment, it points to an entry on the glossary page. + + @context {string} contentPath The page containing the link. + @context {string} errorLevel The error level when unable to resolve destination; ignore (default), warning, or error. + @context {string} renderHookName The name of the render hook. + @context {string} text The link text. + */}} + +{{- /* Get context.. */}} +{{- $contentPath := .contentPath }} +{{- $errorLevel := .errorLevel }} +{{- $renderHookName := .renderHookName }} +{{- $text := .text | transform.Plainify | strings.ToLower }} + +{{- /* Initialize. */}} +{{- $glossaryPath := "/quick-reference/glossary" }} +{{- $termGiven := $text }} +{{- $termActual := "" }} +{{- $termSingular := inflect.Singularize $termGiven }} + +{{- /* Verify that the glossary page exists. */}} +{{- $glossaryPage := site.GetPage $glossaryPath }} +{{- if not $glossaryPage }} + {{- errorf "The %q render hook was unable to find %s: see %s" $renderHookName $glossaryPath $contentPath }} +{{- end }} + +{{- /* There's a better way to handle this, but it works for now. */}} +{{- $cheating := dict + "chaining" "chain" + "ci/cd" "cicd" + "interleaved" "interleave" + "localize" "localization" + "localized" "localization" + "paginating" "paginate" + "walking" "walk" +}} + +{{- /* Verify that a glossary term page exists for the given term. */}} +{{- if site.GetPage (urls.JoinPath $glossaryPath ($termGiven | urlize)) }} + {{- $termActual = $termGiven }} +{{- else if site.GetPage (urls.JoinPath $glossaryPath ($termSingular | urlize)) }} + {{- $termActual = $termSingular }} +{{- else }} + {{- $termToTest := index $cheating $termGiven }} + {{- if site.GetPage (urls.JoinPath $glossaryPath ($termToTest | urlize)) }} + {{- $termActual = $termToTest }} + {{- end }} +{{- end }} + +{{- if not $termActual }} + {{- errorf "The %q render hook was unable to find a glossary page for either the singular or plural form of the term %q: see %s" $renderHookName $termGiven $contentPath }} +{{- end }} + +{{- /* Create the href attribute. */}} +{{- $href := "" }} +{{- if $termActual }} + {{- $href = fmt.Printf "%s#%s" $glossaryPage.RelPermalink (anchorize $termActual) }} +{{- end }} + +{{- return (dict "href" $href) }} +{{- end -}} diff --git a/docs/layouts/_markup/render-passthrough.html b/docs/layouts/_markup/render-passthrough.html new file mode 100644 index 0000000..3a26d5c --- /dev/null +++ b/docs/layouts/_markup/render-passthrough.html @@ -0,0 +1,9 @@ +{{- $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 -}} diff --git a/docs/layouts/_markup/render-table.html b/docs/layouts/_markup/render-table.html new file mode 100644 index 0000000..4bf12db --- /dev/null +++ b/docs/layouts/_markup/render-table.html @@ -0,0 +1,31 @@ +
    + + + {{- range .THead }} + + {{- range . }} + + {{- end }} + + {{- end }} + + + {{- range .TBody }} + + {{- range . }} + + {{- end }} + + {{- end }} + +
    + {{- .Text -}} +
    + {{- .Text -}} +
    +
    diff --git a/docs/layouts/_partials/docs/functions-aliases.html b/docs/layouts/_partials/docs/functions-aliases.html new file mode 100644 index 0000000..02b1b18 --- /dev/null +++ b/docs/layouts/_partials/docs/functions-aliases.html @@ -0,0 +1,12 @@ +{{- with .Params.functions_and_methods.aliases }} + {{- $label := "Alias" }} + {{- if gt (len .) 1 }} + {{- $label = "Aliases" }} + {{- end }} +

    {{ $label }}

    + {{- range . }} +
    + {{- . -}} +
    + {{- end }} +{{- end -}} diff --git a/docs/layouts/_partials/docs/functions-return-type.html b/docs/layouts/_partials/docs/functions-return-type.html new file mode 100644 index 0000000..fdb4f48 --- /dev/null +++ b/docs/layouts/_partials/docs/functions-return-type.html @@ -0,0 +1,6 @@ +{{- with .Params.functions_and_methods.returnType }} +

    Returns

    +
    + {{- . -}} +
    +{{- end -}} diff --git a/docs/layouts/_partials/docs/functions-signatures.html b/docs/layouts/_partials/docs/functions-signatures.html new file mode 100644 index 0000000..0714e09 --- /dev/null +++ b/docs/layouts/_partials/docs/functions-signatures.html @@ -0,0 +1,12 @@ +{{- with .Params.functions_and_methods.signatures }} +

    Syntax

    + {{- range . }} + {{- $signature := . }} + {{- if $.Params.function.returnType }} + {{- $signature = printf "%s ⟼ %s" . $.Params.function.returnType }} + {{- end }} +
    + {{- $signature -}} +
    + {{- end }} +{{- end -}} diff --git a/docs/layouts/_partials/helpers/debug/list-item-metadata.html b/docs/layouts/_partials/helpers/debug/list-item-metadata.html new file mode 100644 index 0000000..7c73a2c --- /dev/null +++ b/docs/layouts/_partials/helpers/debug/list-item-metadata.html @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + +
    weight: + {{ .Weight }} +
    keywords: + {{ delimit (or .Keywords "") ", " }} +
    categories: + {{ delimit (or .Params.categories "") ", " }} +
    diff --git a/docs/layouts/_partials/helpers/funcs/color-from-string.html b/docs/layouts/_partials/helpers/funcs/color-from-string.html new file mode 100644 index 0000000..950ebfd --- /dev/null +++ b/docs/layouts/_partials/helpers/funcs/color-from-string.html @@ -0,0 +1,25 @@ +{{ $colors := slice "slate" "green" "cyan" "blue" }} +{{ with .single }} + {{ $colors = slice . }} +{{ end }} + +{{ $shades := slice 300 400 500 }} +{{ if not .dark }} + {{ $shades = slice 700 800 }} +{{ end }} +{{ $hash := (hash.FNV32a .text) }} +{{ $i := mod $hash (len $colors) }} +{{ $j := mod $hash (len $shades) }} +{{ $color := index $colors $i }} +{{ $shade1 := index $shades $j }} +{{ $shade2 := 0 }} +{{ $shade3 := 0 }} +{{ if gt $shade1 500 }} + {{ $shade2 = math.Min (sub $shade1 500) 100 | int }} + {{ $shade3 = sub $shade1 100 }} +{{ else }} + {{ $shade2 = math.Max (add $shade1 500) 700 | int }} + {{ $shade3 = add $shade1 200 }} +{{ end }} +{{ $res := dict "color" $color "shade1" $shade1 "shade2" $shade2 "shade3" $shade3 }} +{{ return $res }} diff --git a/docs/layouts/_partials/helpers/funcs/get-github-info.html b/docs/layouts/_partials/helpers/funcs/get-github-info.html new file mode 100644 index 0000000..1956532 --- /dev/null +++ b/docs/layouts/_partials/helpers/funcs/get-github-info.html @@ -0,0 +1,28 @@ +{{ $url := "https://api.github.com/repos/gohugoio/hugo" }} +{{ $cacheKey := print $url (now.Format "2006-01-02") }} +{{ $headers := dict }} +{{ with os.Getenv "HUGO_GH_TOKEN" }} + {{ $headers = dict "Authorization" (printf "Bearer %s" .) }} +{{ end }} +{{ $opts := dict "headers" $headers "key" $cacheKey }} +{{ $githubRepoInfo := dict }} +{{ with try (resources.GetRemote $url $opts) }} + {{ with .Err }} + {{ warnf "Failed to get GitHub repo info: %s" . }} + {{ else with (.Value | transform.Unmarshal) }} + {{ $githubRepoInfo = dict + "html_url" .html_url + "stargazers_url" .stargazers_url + "watchers_count" .watchers_count + "stargazers_count" .stargazers_count + "forks_count" .forks_count + "contributors_url" .contributors_url + "releases_url" .releases_url + "forks_count" .forks_count + }} + {{ else }} + {{ errorf "Unable to get remote resource %q" $url }} + {{ end }} +{{ end }} + +{{ return $githubRepoInfo }} diff --git a/docs/layouts/_partials/helpers/funcs/get-remote-data.html b/docs/layouts/_partials/helpers/funcs/get-remote-data.html new file mode 100644 index 0000000..8f1aa88 --- /dev/null +++ b/docs/layouts/_partials/helpers/funcs/get-remote-data.html @@ -0,0 +1,23 @@ +{{/* +Parses the serialized data from the given URL and returns a map or an array. + +Supports CSV, JSON, TOML, YAML, and XML. + +@param {string} . The URL from which to retrieve the serialized data. +@returns {any} + +@example {{ partial "get-remote-data.html" "https://example.org/foo.json" }} +*/}} + +{{ $url := . }} +{{ $data := dict }} +{{ with try (resources.GetRemote $url) }} + {{ with .Err }} + {{ errorf "%s" . }} + {{ else with .Value }} + {{ $data = .Content | transform.Unmarshal }} + {{ else }} + {{ errorf "Unable to get remote resource %q" $url }} + {{ end }} +{{ end }} +{{ return $data }} diff --git a/docs/layouts/_partials/helpers/gtag.html b/docs/layouts/_partials/helpers/gtag.html new file mode 100644 index 0000000..dd1e02b --- /dev/null +++ b/docs/layouts/_partials/helpers/gtag.html @@ -0,0 +1,25 @@ +{{ with site.Config.Services.GoogleAnalytics.ID }} + + +{{ end }} diff --git a/docs/layouts/_partials/helpers/linkcss.html b/docs/layouts/_partials/helpers/linkcss.html new file mode 100644 index 0000000..179fa54 --- /dev/null +++ b/docs/layouts/_partials/helpers/linkcss.html @@ -0,0 +1,28 @@ +{{ $r := .r }} +{{ $attr := .attributes | default dict }} + +{{ if hugo.IsDevelopment }} + +{{ else }} + {{ with $r | minify | fingerprint }} + + {{ end }} +{{ end }} + +{{ define "render-attributes" }} +{{- range $k, $v := . -}} + {{- if $v -}} + {{- printf ` %s=%q` $k $v | safeHTMLAttr -}} + {{- else -}} + {{- printf ` %s` $k | safeHTMLAttr -}} + {{- end -}} +{{- end -}} +{{ end }} diff --git a/docs/layouts/_partials/helpers/linkjs.html b/docs/layouts/_partials/helpers/linkjs.html new file mode 100644 index 0000000..88f27d0 --- /dev/null +++ b/docs/layouts/_partials/helpers/linkjs.html @@ -0,0 +1,17 @@ +{{ $r := .r }} +{{ $attr := .attributes | default dict }} +{{ if hugo.IsDevelopment }} + +{{ else }} + {{ with $r | fingerprint }} + + {{ end }} +{{ end }} diff --git a/docs/layouts/_partials/helpers/picture.html b/docs/layouts/_partials/helpers/picture.html new file mode 100644 index 0000000..53a35cd --- /dev/null +++ b/docs/layouts/_partials/helpers/picture.html @@ -0,0 +1,27 @@ +{{ $image := .image }} +{{ $width := .width | default 1000 }} +{{ $width1x := div $width 2 }} +{{ $imageWebp := $image.Resize (printf "%dx webp" $width) }} +{{ $image1x := $image.Resize (printf "%dx" $width1x) }} +{{ $image1xWebp := $image.Resize (printf "%dx webp" $width1x) }} +{{ $class := .class | default "h-64 tablet:h-96 lg:h-full w-full object-cover lg:absolute" }} +{{ $loading := .loading | default "eager" }} + + + + + + + diff --git a/docs/layouts/_partials/helpers/validation/validate-keywords.html b/docs/layouts/_partials/helpers/validation/validate-keywords.html new file mode 100644 index 0000000..cd23eea --- /dev/null +++ b/docs/layouts/_partials/helpers/validation/validate-keywords.html @@ -0,0 +1,21 @@ +{{/* +We use the front matter keywords field to determine related content. To ensure +consistency, during site build we validate each keyword against the entries in +data/keywords.yaml. + +As of March 5, 2025, this feature is experimental, pending usability +assessment. We anticipate that the number of additions to data/keywords.yaml +will decrease over time, though the initial implementation will require some +effort. +*/}} + +{{- $t := debug.Timer "validateKeywords" }} +{{- $allowedKeywords := collections.Apply hugo.Data.keywords "strings.ToLower" "." }} +{{- range $p := site.Pages }} + {{- range .Params.keywords }} + {{- if not (in $allowedKeywords (lower .)) }} + {{- warnf "The word or phrase %q is not in the keywords data file. See %s." . $p.Page.String }} + {{- end }} + {{- end }} +{{- end }} +{{- $t.Stop }} diff --git a/docs/layouts/_partials/layouts/blocks/alert.html b/docs/layouts/_partials/layouts/blocks/alert.html new file mode 100644 index 0000000..535b605 --- /dev/null +++ b/docs/layouts/_partials/layouts/blocks/alert.html @@ -0,0 +1,25 @@ +{{- $title := .title | default "" }} +{{- $color := .color | default "yellow" }} +{{- $icon := .icon | default "exclamation-triangle" }} +{{- $text := .text | default "" }} +{{- $class := .class | default "mt-6 mb-8" }} +
    +
    +
    + + + +
    +
    + {{- with $title }} +

    + {{ . }} +

    + {{- end }} +
    + {{ $text }} +
    +
    +
    +
    diff --git a/docs/layouts/_partials/layouts/blocks/feature-state.html b/docs/layouts/_partials/layouts/blocks/feature-state.html new file mode 100644 index 0000000..966d57a --- /dev/null +++ b/docs/layouts/_partials/layouts/blocks/feature-state.html @@ -0,0 +1,98 @@ +{{- /* +This must be used in conjunction with the "deprecated-in" and "new-in" +shortcodes. The "deprecated-in" shortcode should be used to indicate when a +feature was deprecated, and the "new-in" shortcode should be used to indicate +when a feature was introduced. This template will render the appropriate +admonition or badge based on the feature status and version information provided +by the shortcodes. + +@param {string} inner The inner content of the shortcode, if any. +@param {string} name The name of the shortcode. +@param {string} page The page context in which the shortcode is used. +@param {string} position The position of the shortcode in the source file. +@param {string} status The feature status, either "deprecated" or "new". +@param {string} version The version in which the feature was deprecated or introduced. + +@config {int} majorVersionDiffThreshold The major version difference before warning. +@config {int} minorVersionDiffThresholdDeprecatedFeature Minor versions to wait before warning about a deprecated feature. +@config {int} minorVersionDiffThresholdNewFeature Minor versions to wait before warning about a "new" feature. +@config {slice} validStatusValues Allowed values for the status parameter. +@config {string} classAnchorBase Base classes for the anchor element. +@config {string} classSpanBase Base classes for the span/badge element. + +@example + + {{- partial "layouts/blocks/feature-state.html" (dict + "inner" (strings.TrimSpace $.Inner) + "name" $.Name + "page" $.Page + "position" $.Position + "status" "deprecated" + "version" $version + ) + }} + +*/}} + +{{- /* Configuration */ -}} +{{- $majorVersionDiffThreshold := 0 }} +{{- $minorVersionDiffThresholdDeprecatedFeature := 30 }} +{{- $minorVersionDiffThresholdNewFeature := 30 }} +{{- $validStatusValues := slice "deprecated" "new" }} +{{- $classAnchorBase := "dark:text-black no-underline" }} +{{- $classSpanBase := "not-prose inline-flex items-center px-2 mr-1 rounded text-sm font-medium" }} + +{{- /* Initialization */ -}} +{{- $classAnchor := $classAnchorBase }} +{{- $classSpan := $classSpanBase }} +{{- $color := "" }} +{{- $expiryMessage := "" }} +{{- $icon := "" }} +{{- $minorVersionDiffThreshold := 0 }} +{{- $text := "" }} + +{{- if and $.name $.page $.position $.status $.version }} + {{- if in $validStatusValues $.status }} + {{- if eq $.status "deprecated" }} + {{- $classAnchor = printf "text-orange-800 hover:text-orange-600 %s" $classAnchor }} + {{- $classSpan = printf "bg-orange-200 dark:bg-orange-400 fill-orange-600 %s" $classSpan }} + {{- $color = "orange" }} + {{- $expiryMessage = "The deprecation period has ended. Remove this shortcode call and the associated content" }} + {{- $icon = "exclamation" }} + {{- $minorVersionDiffThreshold = $minorVersionDiffThresholdDeprecatedFeature }} + {{- $text = "Deprecated in" }} + {{- else if eq $.status "new" }} + {{- $classAnchor = printf "text-green-800 hover:text-green-600 %s" $classAnchor }} + {{- $classSpan = printf "bg-green-200 dark:bg-green-400 fill-green-600 %s" $classSpan }} + {{- $color = "green" }} + {{- $expiryMessage = "This feature is no longer new. Remove this shortcode call" }} + {{- $icon = "exclamation" }} + {{- $minorVersionDiffThreshold = $minorVersionDiffThresholdNewFeature }} + {{- $text = "New in" }} + {{- else }} + {{- errorf "BUG: The %q template does not support the %q feature status: see %s" templates.Current.Name $.status $.position }} + {{- end }} + + {{- $hv := split hugo.Version "." }} + {{- $sv := split $.version "." }} + {{- $majorDiff := sub (index $hv 0 | int) (index $sv 0 | int) }} + {{- $minorDiff := sub (index $hv 1 | int) (index $sv 1 | int) }} + + {{- if or (gt $majorDiff $majorVersionDiffThreshold) (gt $minorDiff $minorVersionDiffThreshold) }} + {{- warnf "%s: %s" $expiryMessage $.position }} + {{- end }} + + {{- $href := printf "https://github.com/gohugoio/hugo/releases/tag/v%s" $.version }} + {{- if $.inner }} + {{- $text = printf "%s [v%s](%s)\n\n%s" $text $.version $href $.inner | $.page.RenderString (dict "display" "block") }} + {{- partial "layouts/blocks/alert.html" (dict "color" $color "icon" $icon "text" $text) }} + {{- else }} + {{- $target := "_blank" }} + {{- printf "%s v%s" $classSpan $classAnchor $href $target $text $.version | safeHTML }} + {{- end }} + {{- else }} + {{- errorf "The %q template does not support the %q feature status: see %s" templates.Current.Name $.status $.position }} + {{- end }} +{{- else }} + {{- errorf "The %q template requires the following context: name, page, position, status, version: see %s" templates.Current.Name $.position }} +{{- end }} diff --git a/docs/layouts/_partials/layouts/blocks/modal.html b/docs/layouts/_partials/layouts/blocks/modal.html new file mode 100644 index 0000000..5fe317b --- /dev/null +++ b/docs/layouts/_partials/layouts/blocks/modal.html @@ -0,0 +1,30 @@ +
    +
    + {{ .modal_button }} +
    + +
    diff --git a/docs/layouts/_partials/layouts/breadcrumbs.html b/docs/layouts/_partials/layouts/breadcrumbs.html new file mode 100644 index 0000000..9701a20 --- /dev/null +++ b/docs/layouts/_partials/layouts/breadcrumbs.html @@ -0,0 +1,44 @@ +{{ $documentation := site.GetPage "/documentation" }} + + + + +{{ define "breadcrumbs-arrow" }} + + + +{{ end }} diff --git a/docs/layouts/_partials/layouts/date.html b/docs/layouts/_partials/layouts/date.html new file mode 100644 index 0000000..aeb19ec --- /dev/null +++ b/docs/layouts/_partials/layouts/date.html @@ -0,0 +1,5 @@ +{{ $humanDate := time.Format "January 2, 2006" . }} +{{ $machineDate := time.Format "2006-01-02T15:04:05-07:00" . }} + diff --git a/docs/layouts/_partials/layouts/docsheader.html b/docs/layouts/_partials/layouts/docsheader.html new file mode 100644 index 0000000..1f4fd51 --- /dev/null +++ b/docs/layouts/_partials/layouts/docsheader.html @@ -0,0 +1,9 @@ +
    + {{ partial "layouts/breadcrumbs.html" . }} + {{ if and .IsPage (not (eq .Layout "list")) }} +

    + {{ .Title }} +

    + {{ end }} +
    diff --git a/docs/layouts/_partials/layouts/footer.html b/docs/layouts/_partials/layouts/footer.html new file mode 100644 index 0000000..948f1ed --- /dev/null +++ b/docs/layouts/_partials/layouts/footer.html @@ -0,0 +1,72 @@ +
    +
    +
    + {{/* Column 1 */}} +
    +
    + By the + Hugo Authors
    +
    + + Hugo Logo + + +
    + + {{/* Sponsors */}} +
    + {{ partial "layouts/home/sponsors.html" (dict + "ctx" . + "gtag" "footer" + ) + }} +
    +
    +
    +

    + The Hugo logos are copyright © Steve Francia 2013–{{ now.Year }}. The + Hugo Gopher is based on an original work by Renée French. +

    +
    +
    +
    diff --git a/docs/layouts/_partials/layouts/head/head-js.html b/docs/layouts/_partials/layouts/head/head-js.html new file mode 100644 index 0000000..7d06232 --- /dev/null +++ b/docs/layouts/_partials/layouts/head/head-js.html @@ -0,0 +1,8 @@ +{{ $githubInfo := partialCached "helpers/funcs/get-github-info.html" . "-" }} +{{ $opts := dict "minify" true "params" (dict "isServer" hugo.IsServer) }} +{{ with resources.Get "js/head-early.js" | js.Build $opts }} + {{ partial "helpers/linkjs.html" (dict "r" . "attributes" (dict "async" "")) }} +{{ end }} +{{ with resources.Get "js/main.js" | js.Build $opts }} + {{ partial "helpers/linkjs.html" (dict "r" . "attributes" (dict "defer" "")) }} +{{ end }} diff --git a/docs/layouts/_partials/layouts/head/head.html b/docs/layouts/_partials/layouts/head/head.html new file mode 100644 index 0000000..e21b751 --- /dev/null +++ b/docs/layouts/_partials/layouts/head/head.html @@ -0,0 +1,41 @@ + +{{ hugo.Generator }} + +{{ if hugo.IsProduction }} + +{{ else }} + +{{ end }} + + + {{ with .Title }}{{ . }} |{{ end }} + {{ .Site.Title }} + + + + + + + + + + + +{{ partial "layouts/head/speculationrules.html" . }} + +{{ range .AlternativeOutputFormats -}} + +{{ end -}} + + + +{{ partial "opengraph/opengraph.html" . }} +{{ partial "schema.html" . }} +{{ partial "twitter_cards.html" . }} + +{{ if hugo.IsProduction }} + {{ partial "helpers/gtag.html" . }} +{{ end }} diff --git a/docs/layouts/_partials/layouts/head/speculationrules.html b/docs/layouts/_partials/layouts/head/speculationrules.html new file mode 100644 index 0000000..6c9abe9 --- /dev/null +++ b/docs/layouts/_partials/layouts/head/speculationrules.html @@ -0,0 +1,17 @@ +{{- /* + Speculation Rules API: prerender same-origin pages on moderate intent + (pointer/focus held ~200ms) for near-instant navigation. This replaces + the prefetch/SPA behavior previously provided by Turbo. + https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API +*/ -}} + diff --git a/docs/layouts/_partials/layouts/header/githubstars.html b/docs/layouts/_partials/layouts/header/githubstars.html new file mode 100644 index 0000000..0d0ab24 --- /dev/null +++ b/docs/layouts/_partials/layouts/header/githubstars.html @@ -0,0 +1,16 @@ +{{ with partialCached "helpers/funcs/get-github-info.html" . "-" }} + + + + + + + + {{ printf "%0.1fk" (div .stargazers_count 1000) }} + + +{{ end }} diff --git a/docs/layouts/_partials/layouts/header/header.html b/docs/layouts/_partials/layouts/header/header.html new file mode 100644 index 0000000..2e606ae --- /dev/null +++ b/docs/layouts/_partials/layouts/header/header.html @@ -0,0 +1,83 @@ +
    +
    + {{ with site.Home }} + HUGO + {{ end }} +
    + + +
    + {{/* Search. */}} + {{ partial "layouts/search/input.html" . }} + {{/* Theme selector - shown inline on mobile */}} +
    + {{ partial "layouts/header/theme.html" . }} +
    +
    + + + {{/* Mobile menu hamburger button - only visible on small screens */}} + +
    + +{{/* Mobile menu panel */}} +{{ partial "layouts/header/mobilemenu.html" . }} diff --git a/docs/layouts/_partials/layouts/header/mastodon.html b/docs/layouts/_partials/layouts/header/mastodon.html new file mode 100644 index 0000000..15427c4 --- /dev/null +++ b/docs/layouts/_partials/layouts/header/mastodon.html @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/docs/layouts/_partials/layouts/header/mobilemenu.html b/docs/layouts/_partials/layouts/header/mobilemenu.html new file mode 100644 index 0000000..555e352 --- /dev/null +++ b/docs/layouts/_partials/layouts/header/mobilemenu.html @@ -0,0 +1,106 @@ +{{/* Mobile menu overlay and panel - only visible on small screens */}} +
    +
    + +{{/* Mobile menu panel */}} +
    + + {{/* Header with close button */}} +
    + Menu + +
    + + {{/* Navigation links */}} + + + {{/* Divider */}} +
    + + {{/* Search trigger */}} +
    + +
    + + {{/* Divider */}} +
    + + {{/* Social links */}} +
    + {{/* GitHub */}} + {{ with partialCached "helpers/funcs/get-github-info.html" . "-" }} + + + + + GitHub + {{ .stargazers_count }} stars + + {{ end }} + + {{/* Mastodon */}} + {{ with site.Params.social.mastodon.url }} + + + + + Mastodon + + {{ end }} +
    +
    diff --git a/docs/layouts/_partials/layouts/header/qr.html b/docs/layouts/_partials/layouts/header/qr.html new file mode 100644 index 0000000..578cd8c --- /dev/null +++ b/docs/layouts/_partials/layouts/header/qr.html @@ -0,0 +1,26 @@ +{{ $t := debug.Timer "qr" }} +{{ $qr := partial "_inline/qr" (dict + "page" $ + "img_class" "w-10 bg-white" + ) +}} +{{ $qrBig := partial "_inline/qr" (dict "page" $ "img_class" "w-64 p-4") }} +{{ $t.Stop }} + + +{{ define "_partials/_inline/qr" }} +{{ $img_class := .img_class | default "w-10" }} +{{ with images.QR $.page.Permalink (dict "targetDir" "images/qr") }} + + QR code linking to {{ $.page.Permalink }} +{{ end }} +{{ end }} diff --git a/docs/layouts/_partials/layouts/header/theme.html b/docs/layouts/_partials/layouts/header/theme.html new file mode 100644 index 0000000..c9131e1 --- /dev/null +++ b/docs/layouts/_partials/layouts/header/theme.html @@ -0,0 +1,35 @@ +
    + +
    diff --git a/docs/layouts/_partials/layouts/home/features.html b/docs/layouts/_partials/layouts/home/features.html new file mode 100644 index 0000000..8c17b39 --- /dev/null +++ b/docs/layouts/_partials/layouts/home/features.html @@ -0,0 +1,52 @@ +{{/* icons source: https://heroicons.com/ */}} +{{ $dataTOML := ` + [[features]] + heading = "Optimized for speed" + copy = "Written in Go, optimized for speed and designed for flexibility. With its advanced templating system and fast asset pipelines, Hugo renders a large site in seconds, often less." + icon = """ + + + """ + [[features]] + heading = "Flexible framework" + copy = "With its multilingual support, and powerful taxonomy system, Hugo is widely used to create documentation sites, landing pages, corporate, government, nonprofit, education, news, event, and project sites." + icon = """ + + + """ + [[features]] + heading = "Fast assets pipeline" + copy = "Image processing (convert, resize, crop, rotate, adjust colors, apply filters, overlay text and images, and extract EXIF data), JavaScript bundling (tree shake, code splitting), Sass processing, great TailwindCSS support." + icon = """ + + + """ + [[features]] + heading = "Embedded web server" + copy = "Use Hugo's embedded web server during development to instantly see changes to content, structure, behavior, and presentation. " + icon = """ + + + """ + ` }} +{{ $data := $dataTOML | transform.Unmarshal }} +
    +
    +
    + {{ range $data.features }} +
    +
    +
    + {{ .icon | safeHTML }} +
    + {{ .heading }} +
    +
    + {{ .copy }} +
    +
    + {{ end }} +
    +
    +
    diff --git a/docs/layouts/_partials/layouts/home/opensource.html b/docs/layouts/_partials/layouts/home/opensource.html new file mode 100644 index 0000000..9efc61f --- /dev/null +++ b/docs/layouts/_partials/layouts/home/opensource.html @@ -0,0 +1,110 @@ +{{ $githubInfo := partialCached "helpers/funcs/get-github-info.html" . "-" }} +
    +
    +
    +
    +

    + Open source +

    +

    + Hugo is open source and free to use. It is distributed under the + Apache 2.0 License. +

    +
    +
    +
    + + + + Popular. +
    +
    + Hugo has + {{ $githubInfo.stargazers_count | lang.FormatNumber 0 }} + stars on GitHub as of {{ now.Format "January 2, 2006" }}. + Join the crowd and hit the + Star button. +
    +
    +
    +
    + + + + Active. +
    +
    + Hugo has a large and active community. If you have questions or + need help, you can ask in the + Hugo forums. +
    +
    +
    +
    + + + + Frequent releases. +
    +
    + Hugo has a fast + release + cycle. The project is actively maintained and new features are + added regularly. +
    +
    +
    +
    +
    + {{ partial "helpers/picture.html" (dict + "image" (resources.Get "images/hugo-github-screenshot.png") + "alt" "Hugo GitHub Repository" + "width" 640 + "class" "w-full max-w-[38rem] ring-1 shadow-xl dark:shadow-gray-500 ring-gray-400/10" + ) + }} +
    +
    diff --git a/docs/layouts/_partials/layouts/home/sponsors.html b/docs/layouts/_partials/layouts/home/sponsors.html new file mode 100644 index 0000000..329cc83 --- /dev/null +++ b/docs/layouts/_partials/layouts/home/sponsors.html @@ -0,0 +1,44 @@ +{{ $gtag := .gtag | default "unknown" }} +{{ $gtag := .gtag | default "unknown" }} +{{ $isFooter := (eq $gtag "footer") }} +{{ $utmSource := cond $isFooter "hugofooter" "hugohome" }} +{{ $containerClass := .containerClass | default "mx-auto max-w-7xl px-6 lg:px-8" }} +{{ with hugo.Data.sponsors }} +
    +

    Hugo Sponsors

    +
    + {{ range .banners }} +
    + {{ $query_params := .query_params | default "" }} + {{ $url := .link }} + {{ if not .no_query_params }} + {{ $url = printf "%s?%s%s" .link $query_params (querify "utm_source" (.utm_source | default $utmSource) "utm_medium" (.utm_medium | default "banner") "utm_campaign" (.utm_campaign | default "hugosponsor") "utm_content" (.utm_content | default "gohugoio")) | safeURL }} + {{ end }} + {{ $logo := resources.Get .logo }} + {{ $gtagID := printf "Sponsor %s %s" .name $gtag | title }} + + + +
    + {{ end }} +
    +
    +{{ end }} diff --git a/docs/layouts/_partials/layouts/hooks/body-end.html b/docs/layouts/_partials/layouts/hooks/body-end.html new file mode 100644 index 0000000..bbe512a --- /dev/null +++ b/docs/layouts/_partials/layouts/hooks/body-end.html @@ -0,0 +1,3 @@ +{{- if .IsHome }} + {{- partial "helpers/validation/validate-keywords.html" }} +{{- end }} diff --git a/docs/layouts/_partials/layouts/hooks/body-main-start.html b/docs/layouts/_partials/layouts/hooks/body-main-start.html new file mode 100644 index 0000000..adf78a9 --- /dev/null +++ b/docs/layouts/_partials/layouts/hooks/body-main-start.html @@ -0,0 +1,8 @@ +{{ if or .IsSection .IsPage }} + +{{ end }} diff --git a/docs/layouts/_partials/layouts/hooks/body-start.html b/docs/layouts/_partials/layouts/hooks/body-start.html new file mode 100644 index 0000000..e69de29 diff --git a/docs/layouts/_partials/layouts/icons.html b/docs/layouts/_partials/layouts/icons.html new file mode 100644 index 0000000..5eba65e --- /dev/null +++ b/docs/layouts/_partials/layouts/icons.html @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +{{/* https://heroicons.com/mini exclamation-circle */}} + + + + +{{/* https://heroicons.com/mini exclamation-triangle */}} + + + + +{{/* https://heroicons.com/mini information-circle */}} + + + + +{{/* https://heroicons.com/mini light-bulb */}} + + + + +{{/* https://heroicons.com/outline bars-3 (hamburger menu) */}} + + + + +{{/* https://heroicons.com/outline x-mark (close) */}} + + + diff --git a/docs/layouts/_partials/layouts/in-this-section.html b/docs/layouts/_partials/layouts/in-this-section.html new file mode 100644 index 0000000..054040d --- /dev/null +++ b/docs/layouts/_partials/layouts/in-this-section.html @@ -0,0 +1,34 @@ +{{- with .CurrentSection.RegularPages }} + {{ $hasTocOrRelated := or ($.Store.Get "hasToc") ($.Store.Get "hasRelated") }} +
    +

    + In this section +

    + + +
    +{{- end }} diff --git a/docs/layouts/_partials/layouts/page-edit.html b/docs/layouts/_partials/layouts/page-edit.html new file mode 100644 index 0000000..d610654 --- /dev/null +++ b/docs/layouts/_partials/layouts/page-edit.html @@ -0,0 +1,27 @@ +
    +
    + +
    + Last updated: + {{ .Lastmod.Format "January 2, 2006" }} + {{ with .GitInfo }} + : + {{ .Subject }} ({{ .AbbreviatedHash }}) + {{ end }} +
    + + {{ with .File }} + {{ if not .IsContentAdapter }} + {{ $href := printf "%sedit/master/content/%s/%s" site.Params.ghrepo $.Lang .Path }} + + Improve this page + + {{ end }} + {{ end }} +
    diff --git a/docs/layouts/_partials/layouts/related.html b/docs/layouts/_partials/layouts/related.html new file mode 100644 index 0000000..bef9f3a --- /dev/null +++ b/docs/layouts/_partials/layouts/related.html @@ -0,0 +1,20 @@ +{{- $heading := "See also" }} +{{- $related := site.Pages.Related . | first 7 }} + +{{- with $related }} + {{ $.Store.Set "hasRelated" true }} +

    + {{ $heading }} +

    + +{{- end }} diff --git a/docs/layouts/_partials/layouts/search/algolialogo.html b/docs/layouts/_partials/layouts/search/algolialogo.html new file mode 100644 index 0000000..979d92e --- /dev/null +++ b/docs/layouts/_partials/layouts/search/algolialogo.html @@ -0,0 +1,45 @@ +
    + Search by + + + + + + + + + + +
    diff --git a/docs/layouts/_partials/layouts/search/button.html b/docs/layouts/_partials/layouts/search/button.html new file mode 100644 index 0000000..81d5ae9 --- /dev/null +++ b/docs/layouts/_partials/layouts/search/button.html @@ -0,0 +1,22 @@ +{{ $textColor := "text-gray-300" }} +{{ $fillColor := "fill-slate-400 dark:fill-slate-500" }} +{{ if .standalone }} + {{ $textColor = "text-gray-800 dark:text-gray-300 " }} + {{ $fillColor = "fill-slate-500 dark:fill-slate-400" }} +{{ end }} + + + diff --git a/docs/layouts/_partials/layouts/search/input.html b/docs/layouts/_partials/layouts/search/input.html new file mode 100644 index 0000000..f86c0b6 --- /dev/null +++ b/docs/layouts/_partials/layouts/search/input.html @@ -0,0 +1,4 @@ +
    + {{ partial "layouts/search/button.html" (dict "page" . "standalone" false) }} + {{ partial "layouts/search/results.html" . }} +
    diff --git a/docs/layouts/_partials/layouts/search/results.html b/docs/layouts/_partials/layouts/search/results.html new file mode 100644 index 0000000..8c53c82 --- /dev/null +++ b/docs/layouts/_partials/layouts/search/results.html @@ -0,0 +1,91 @@ + diff --git a/docs/layouts/_partials/layouts/templates.html b/docs/layouts/_partials/layouts/templates.html new file mode 100644 index 0000000..30c681e --- /dev/null +++ b/docs/layouts/_partials/layouts/templates.html @@ -0,0 +1,7 @@ + diff --git a/docs/layouts/_partials/layouts/toc.html b/docs/layouts/_partials/layouts/toc.html new file mode 100644 index 0000000..84eecaf --- /dev/null +++ b/docs/layouts/_partials/layouts/toc.html @@ -0,0 +1,46 @@ +{{ with .Fragments }} + {{ with .Headings }} +
    +

    + On this page +

    + +
    + {{ end }} +{{ end }} + +{{ define "render-toc-level" }} +{{ range .h }} + {{ if and .ID (and (ge .Level 2) (le .Level 4)) }} + {{ $indentation := "ml-0" }} + {{ if eq .Level 3 }} + {{ $indentation = "ml-2 lg:ml-3" }} + {{ else if eq .Level 4 }} + {{ $indentation = "ml-4 lg:ml-6" }} + {{ end }} + {{ $.p.Store.Set "hasToc" true }} +
  • + + {{ .Title | safeHTML }} + +
  • + {{ end }} + {{ with .Headings }} +
      + {{ template "render-toc-level" (dict "h" . "p" $.p) }} +
    + {{ end }} +{{ end }} +{{ end }} diff --git a/docs/layouts/_partials/opengraph/get-featured-image.html b/docs/layouts/_partials/opengraph/get-featured-image.html new file mode 100644 index 0000000..bee46d2 --- /dev/null +++ b/docs/layouts/_partials/opengraph/get-featured-image.html @@ -0,0 +1,26 @@ +{{ $images := $.Resources.ByType "image" }} +{{ $featured := $images.GetMatch "*feature*" }} +{{ if not $featured }} + {{ $featured = $images.GetMatch "{*cover*,*thumbnail*}" }} +{{ end }} +{{ if not $featured }} + {{ $featured = resources.Get "/opengraph/gohugoio-card-base-1.png" }} + {{ $size := 80 }} + {{ $title := $.LinkTitle }} + {{ if gt (len $title) 20 }} + {{ $size = 70 }} + {{ end }} + + {{ $text := $title }} + {{ $textOptions := dict + "color" "#FFF" + "size" $size + "lineSpacing" 10 + "x" 65 "y" 80 + "font" (resources.Get "/opengraph/mulish-black.ttf") + }} + + {{ $featured = $featured | images.Filter (images.Text $text $textOptions) }} +{{ end }} + +{{ return $featured }} diff --git a/docs/layouts/_partials/opengraph/opengraph.html b/docs/layouts/_partials/opengraph/opengraph.html new file mode 100644 index 0000000..3388ac8 --- /dev/null +++ b/docs/layouts/_partials/opengraph/opengraph.html @@ -0,0 +1,84 @@ + + + + + +{{- with $.Params.images -}} + {{- range first 6 . }} + + {{ end -}} +{{- else -}} + {{- $featured := partial "opengraph/get-featured-image.html" . }} + {{- with $featured -}} + + {{- else -}} + {{- with $.Site.Params.images }} + + {{ end -}} + {{- end -}} +{{- end -}} + +{{- if .IsPage }} + {{- $iso8601 := "2006-01-02T15:04:05-07:00" -}} + + {{ with .PublishDate }} + + {{ end }} + {{ with .Lastmod }} + + {{ end }} +{{- end -}} + +{{- with .Params.audio }}{{ end }} +{{- with .Params.locale }} + +{{ end }} +{{- with .Site.Params.title }} + +{{ end }} +{{- with .Params.videos }} + {{- range . }} + + {{ end }} + +{{ end }} + +{{- /* If it is part of a series, link to related articles */}} +{{- $permalink := .Permalink }} +{{- $siteSeries := .Site.Taxonomies.series }} +{{ with .Params.series }} + {{- range $name := . }} + {{- $series := index $siteSeries ($name | urlize) }} + {{- range $page := first 6 $series.Pages }} + {{- if ne $page.Permalink $permalink }} + + {{ end }} + {{- end }} + {{ end }} + +{{ end }} + +{{- /* Facebook Page Admin ID for Domain Insights */}} +{{- with site.Params.social.facebook_admin }} + +{{ end }} diff --git a/docs/layouts/_shortcodes/chroma-lexers.html b/docs/layouts/_shortcodes/chroma-lexers.html new file mode 100644 index 0000000..eaff9d9 --- /dev/null +++ b/docs/layouts/_shortcodes/chroma-lexers.html @@ -0,0 +1,27 @@ +{{/* +Renders an HTML template of Chroma lexers and their aliases. + +@example {{< chroma-lexers >}} +*/}} + +
    + + + + + + + {{- range hugo.Data.docs.chroma.lexers }} + + + + + {{- end }} + +
    LanguageIdentifiers
    {{ .Name }} + {{- range $k, $_ := .Aliases }} + {{- if $k }},{{ end }} + {{ . }} + {{- end }} +
    +
    diff --git a/docs/layouts/_shortcodes/code-toggle.html b/docs/layouts/_shortcodes/code-toggle.html new file mode 100644 index 0000000..5f42670 --- /dev/null +++ b/docs/layouts/_shortcodes/code-toggle.html @@ -0,0 +1,119 @@ +{{/* +Renders syntax-highlighted configuration data in JSON, TOML, and YAML formats. + +@param {string} [config] The section of hugo.Data.docs.config to render. +@param {bool} [copy=false] Whether to display a copy-to-clipboard button. +@param {string} [dataKey] The section of hugo.Data.docs to render. +@param {string} [file] The file name to display above the rendered code. +@param {bool} [fm=false] Whether to render the code as front matter. +@param {bool} [skipHeader=false] Whether to omit top level key(s) when rendering a section of hugo.Data.docs.config. + +@example {{< code-toggle file=hugo config=build />}} + +@example {{< code-toggle file=content/example.md fm="true" }} + title='Example' + draft='false + {{< /code-toggle }} +*/}} + +{{- /* Initialize. */}} +{{- $config := "" }} +{{- $copy := false }} +{{- $dataKey := "" }} +{{- $file := "" }} +{{- $fm := false }} +{{- $skipHeader := false }} + +{{- /* Get parameters. */}} +{{- $config = .Get "config" }} +{{- $dataKey = .Get "dataKey" }} +{{- $file = .Get "file" }} +{{- if in (slice "false" false 0) (.Get "copy") }} + {{- $copy = false }} +{{- else if in (slice "true" true 1) (.Get "copy") }} + {{- $copy = true }} +{{- end }} +{{- if in (slice "false" false 0) (.Get "fm") }} + {{- $fm = false }} +{{- else if in (slice "true" true 1) (.Get "fm") }} + {{- $fm = true }} +{{- end }} +{{- if in (slice "false" false 0) (.Get "skipHeader") }} + {{- $skipHeader = false }} +{{- else if in (slice "true" true 1) (.Get "skipHeader") }} + {{- $skipHeader = true }} +{{- end }} + +{{- /* Define constants. */}} +{{- $delimiters := dict "toml" "+++" "yaml" "---" }} +{{- $langs := slice "yaml" "toml" "json" }} +{{- $placeHolder := "#-hugo-placeholder-#" }} + +{{- /* Render. */}} +{{- $code := "" }} +{{- if $config }} + {{- $file = $file | default "hugo" }} + {{- $sections := (split $config ".") }} + {{- $configSection := index hugo.Data.docs.config $sections }} + {{- $code = dict $sections $configSection }} + {{- if $skipHeader }} + {{- $code = $configSection }} + {{- end }} +{{- else if $dataKey }} + {{- $file = $file | default $dataKey }} + {{- $sections := (split $dataKey ".") }} + {{- $code = index hugo.Data.docs $sections }} +{{- else }} + {{- $code = $.Inner }} +{{- end }} + + +
    + {{- if $copy }} + + + + {{- end }} + + {{- if $code }} + {{- range $i, $lang := $langs }} +
    + {{- $hCode := $code | transform.Remarshal . }} + {{- if and $fm (in (slice "toml" "yaml") .) }} + {{- $hCode = printf "%s\n%s\n%s" $placeHolder $hCode $placeHolder }} + {{- end }} + {{- $hCode = $hCode | replaceRE `\n+` "\n" }} + {{- highlight $hCode . "" | replaceRE $placeHolder (index $delimiters .) | safeHTML }} +
    + {{- end }} + {{- end }} +
    diff --git a/docs/layouts/_shortcodes/current-go-version.html b/docs/layouts/_shortcodes/current-go-version.html new file mode 100644 index 0000000..319bc66 --- /dev/null +++ b/docs/layouts/_shortcodes/current-go-version.html @@ -0,0 +1,5 @@ +{{/* +Prints the first patch release of the Go minor version used to compile the Hugo +executable that built this site (e.g., 1.26.3 → 1.26.0). +*/}} +{{- hugo.GoVersion | strings.ReplaceRE `go(\d+\.\d+)\..*` `$1.0` -}} diff --git a/docs/layouts/_shortcodes/datatable.html b/docs/layouts/_shortcodes/datatable.html new file mode 100644 index 0000000..c669a0c --- /dev/null +++ b/docs/layouts/_shortcodes/datatable.html @@ -0,0 +1,38 @@ +{{ $package := (index .Params 0) }} +{{ $listname := (index .Params 1) }} +{{ $list := (index (index hugo.Data.docs $package) $listname) }} +{{ $fields := after 2 .Params }} + +
    + + + + {{ range $fields }} + {{ $s := . }} + {{ if eq $s "_key" }} + {{ $s = "type" }} + {{ end }} + + {{ end }} + + + + {{ range $k1, $v1 := $list }} + + {{ range $k2, $v2 := . }} + {{ $.Scratch.Set $k2 $v2 }} + {{ end }} + {{ range $fields }} + {{ $s := "" }} + {{ if eq . "_key" }} + {{ $s = $k1 }} + {{ else }} + {{ $s = $.Scratch.Get . }} + {{ end }} + + {{ end }} + + {{ end }} + +
    {{ $s }}
    {{ $s }}
    +
    diff --git a/docs/layouts/_shortcodes/deprecated-in.html b/docs/layouts/_shortcodes/deprecated-in.html new file mode 100644 index 0000000..d066d1d --- /dev/null +++ b/docs/layouts/_shortcodes/deprecated-in.html @@ -0,0 +1,29 @@ +{{/* +Renders an admonition or badge indicating the version in which a feature was deprecated. + +To render an admonition, include descriptive text between the opening and closing +tags. To render a badge, omit the descriptive text and call the shortcode with a +self-closing tag. + +@param {string} 0 The semantic version string, with or without a leading v. + +@example {{< deprecated-in 0.144.0 />}} + +@example {{< deprecated-in 0.144.0 >}} + Some descriptive text here. + {{< /deprecated-in >}} +*/}} + +{{- with $version := .Get 0 | strings.TrimLeft "vV" }} + {{- partial "layouts/blocks/feature-state.html" (dict + "inner" (strings.TrimSpace $.Inner) + "name" $.Name + "page" $.Page + "position" $.Position + "status" "deprecated" + "version" $version + ) + }} +{{- else }} + {{- errorf "The %q shortcode requires a single positional parameter indicating version. See %s" .Name .Position }} +{{- end }} diff --git a/docs/layouts/_shortcodes/eturl.html b/docs/layouts/_shortcodes/eturl.html new file mode 100644 index 0000000..96de25b --- /dev/null +++ b/docs/layouts/_shortcodes/eturl.html @@ -0,0 +1,25 @@ +{{/* +Renders an absolute URL to the source code for an embedded template. + +Accepts either positional or named parameters, and depends on the +embedded_templates.toml file in the data directory. + +@param {string} filename The embedded template's file name, excluding extension. + +@example {{% et robots.txt %}} +@example {{% et filename=robots.txt %}} +*/}} + +{{- with $filename := or (.Get "filename") (.Get 0) }} + {{- with hugo.Data.embedded_template_urls }} + {{- with index . $filename }} + {{- urls.JoinPath hugo.Data.embedded_template_urls.base_url . }} + {{- else }} + {{- errorf "The %q shortcode was unable to find a URL for the embedded template named %q. Check the name. See %s" $.Name $filename $.Position }} + {{- end }} + {{- else }} + {{- errorf "The %q shortcode was unable to find the embedded_template_urls data file in the site's data directory. See %s" $.Name $.Position }} + {{- end }} +{{- else }} + {{- errorf "The %q shortcodes requires a named or positional parameter, the file name of the embedded template, excluding its extension. See %s" .Name .Position }} +{{- end -}} diff --git a/docs/layouts/_shortcodes/get-page-desc.html b/docs/layouts/_shortcodes/get-page-desc.html new file mode 100644 index 0000000..313700e --- /dev/null +++ b/docs/layouts/_shortcodes/get-page-desc.html @@ -0,0 +1,18 @@ +{{/* +Returns the Description of the page specified by the logical path in the first +positional argument. + +@param {string} logicalPath The logical path to the page. + +@example {{% get-page-desc "/functions/reflect/isimageresource" %}} +*/}} + +{{- with $logicalPath := .Get 0 }} + {{- with $.Page.GetPage $logicalPath }} + {{- .Description }} + {{- else }} + {{- errorf "The %q shortcode was unable to find %s: see %s" $.Name $logicalPath $.Position }} + {{- end }} +{{- else }} + {{- errorf "The %q shortcode requires a positional argument with the logical path to the page: see %s" $.Name $logicalPath $.Position }} +{{- end -}} diff --git a/docs/layouts/_shortcodes/glossary-term.html b/docs/layouts/_shortcodes/glossary-term.html new file mode 100644 index 0000000..b71318d --- /dev/null +++ b/docs/layouts/_shortcodes/glossary-term.html @@ -0,0 +1,21 @@ +{{/* gotmplfmt-ignore-all */ -}} + +{{/* +Renders the definition of the given glossary term. + +@param {string} (.Get 0) The glossary term. + +@example {{% glossary-term float %}} +@example {{% glossary-term "floating point" %}} +*/}} + +{{- with .Get 0 }} + {{- $path := printf "/quick-reference/glossary/%s" (urlize .) }} + {{- with site.GetPage $path }} +{{ .RenderShortcodes }}{{/* Do not indent. */}} + {{- else }} + {{- errorf "The glossary term (%s) shortcode was unable to find %s: see %s" $.Name $path $.Position }} + {{- end }} +{{- else }} + {{- errorf "The glossary term (%s) shortcode requires one positional parameter: see %s" $.Name $.Position }} +{{- end -}} diff --git a/docs/layouts/_shortcodes/glossary.html b/docs/layouts/_shortcodes/glossary.html new file mode 100644 index 0000000..934345e --- /dev/null +++ b/docs/layouts/_shortcodes/glossary.html @@ -0,0 +1,61 @@ +{{/* gotmplfmt-ignore-all */ -}} + +{{/* +Renders the glossary of terms. + +When you call this shortcode using the {{% %}} notation, the glossary terms are +description term (dt) elements which means they are members of .Fragments. This +allows the link render hook to verify links to glossary terms. + +Yes, the terms themselves are pages, but we don't want to link to the pages, at +least not right now. Instead, we want to link to the ids rendered by this +shortcode. + +@example {{% glossary %}} +*/}} + +{{- $path := "/quick-reference/glossary" }} +{{- with site.GetPage $path }} + + {{- /* Build and render alphabetical index. */}} + {{- $m := dict }} + {{- range $p := .Pages.ByTitle }} + {{- $k := substr .Title 0 1 | strings.ToUpper }} + {{- if index $m $k }} + {{- continue }} + {{- end }} + {{- $anchor := path.BaseName .Path | anchorize }} + {{- $m = merge $m (dict $k $anchor) }} + {{- end }} + {{- range $k, $v := $m }} +[{{ $k }}](#{{ $v }}) {{/* Do not indent. */}} + {{- end }} + + {{/* Render glossary terms. */}} + {{- range $p := .Pages.ByTitle }} +{{ .Title }}{{/* Do not indent. */}} +: {{ .RawContent | strings.TrimSpace | safeHTML }}{{/* Do not indent. */}} + {{ with .Params.reference }} + {{- $destination := "" }} + {{- with $u := urls.Parse . }} + {{- if $u.IsAbs }} + {{- $destination = $u.String }} + {{- else }} + {{- with site.GetPage $u.Path }} + {{- if $u.Fragment }} + {{- $destination = printf "%s#%s" .Path $u.Fragment }} + {{ else }} + {{- $destination = .Path }} + {{- end }} + {{- else }} + {{- errorf "The %q shortcode was unable to find the reference link %s: see %s" $.Name . $p.String }} + {{- end }} + {{- end }} + {{- end }} +: See [details]({{ $destination }}).{{/* Do not indent. */}} + {{- end }} + {{ end }} + +{{- else }} + {{- errorf "The %q shortcode was unable to get %s: see %s" .Name $path .Position }} +{{- end }} diff --git a/docs/layouts/_shortcodes/hl.html b/docs/layouts/_shortcodes/hl.html new file mode 100644 index 0000000..9a1ee25 --- /dev/null +++ b/docs/layouts/_shortcodes/hl.html @@ -0,0 +1,13 @@ +{{/* +Returns syntax-highlighted code from the given text. + +This is useful as a terse way to highlight inline code snippets. Calling the +highlight shortcode for inline snippets is verbose. + +@example This is {{< hl python >}}inline{{< /hl >}} code. +*/}} + +{{- $code := .Inner | strings.TrimSpace }} +{{- $lang := or (.Get 0) "go" }} +{{- $opts := dict "hl_inline" true "noClasses" true }} +{{- transform.Highlight $code $lang $opts }} diff --git a/docs/layouts/_shortcodes/img.html b/docs/layouts/_shortcodes/img.html new file mode 100644 index 0000000..8ecd5dd --- /dev/null +++ b/docs/layouts/_shortcodes/img.html @@ -0,0 +1,390 @@ +{{/* +Renders the given image using the given filter, if any. + +When using the text filter, provide the arguments in this order: + +0. The text +1. The horizontal offset, in pixels, relative to the left of the image (default 20) +2. The vertical offset, in pixels, relative to the top of the image (default 20) +3. The font size in pixels (default 64) +4. The line height (default 1.2) +5. The font color (default #ffffff) + +When using the padding filter, provide all arguments in this order: + +0. Padding top +1. Padding right +2. Padding bottom +3. Padding right +4. Canvas color + +@param {string} src The path to the image which must be a remote, page, or global resource. +@param {string} [filter] The filter to apply to the image (case-insensitive). +@param {string} [filterArgs] A comma-delimited list of arguments to pass to the filter. +@param {bool} [example=false] If true, renders a before/after example. +@param {int} [exampleWidth=384] Image width, in pixels, when rendering a before/after example. + +@example {{< img src="zion-national-park.jpg" >}} +@example {{< img src="zion-national-park.jpg" alt="Zion National Park" >}} + +@example {{< img + src="zion-national-park.jpg" + alt="Zion National Park" + filter="grayscale" + >}} + +@example {{< img + src="zion-national-park.jpg" + alt="Zion National Park" + filter="process" + filterArgs="resize 400x webp" + >}} + +@example {{< img + src="zion-national-park.jpg" + alt="Zion National Park" + filter="colorize" + filterArgs="180,50,20" + >}} + +@example {{< img + src="zion-national-park.jpg" + alt="Zion National Park" + filter="grayscale" + example=true + >}} + +@example {{< img + src="zion-national-park.jpg" + alt="Zion National Park" + filter="grayscale" + example=true + exampleWidth=400 + >}} + +@example {{< img + src="images/examples/zion-national-park.jpg" + alt="Zion National Park" + filter="Text" + filterArgs="Zion National Park,25,250,56" + example=true + >}} + +@example {{< img + src="images/examples/zion-national-park.jpg" + alt="Zion National Park" + filter="Padding" + filterArgs="20,50,20,50,#0705" + example=true + >}} +*/}} + +{{- /* Initialize. */}} +{{- $alt := "" }} +{{- $src := "" }} +{{- $filter := "" }} +{{- $filterArgs := slice }} +{{- $example := false }} +{{- $exampleWidth := 384 }} + +{{- /* Default values to use with the text filter. */}} +{{ $textFilterOpts := dict + "xOffset" 20 + "yOffset" 20 + "fontSize" 64 + "lineHeight" 1.2 + "fontColor" "#ffffff" + "fontPath" "https://github.com/google/fonts/raw/refs/heads/main/ofl/lato/Lato-Regular.ttf" +}} + +{{- /* Get and validate parameters. */}} +{{- with .Get "alt" }} + {{- $alt = . }} +{{- end }} + +{{- with .Get "src" }} + {{- $src = . }} +{{- else }} + {{- errorf "The %q shortcode requires a file parameter. See %s" .Name .Position }} +{{- end }} + +{{- with .Get "filter" }} + {{- $filter = . | lower }} +{{- end }} + +{{- $validFilters := slice + "autoorient" "brightness" "colorbalance" "colorize" "contrast" "dither" + "gamma" "gaussianblur" "grayscale" "hue" "invert" "mask" "none" "opacity" + "overlay" "padding" "pixelate" "process" "saturation" "sepia" "sigmoid" "text" + "unsharpmask" +}} + +{{- with $filter }} + {{- if not (in $validFilters .) }} + {{- errorf "The filter passed to the %q shortcode is invalid. The filter must be one of %s. See %s" $.Name (delimit $validFilters ", " ", or ") $.Position }} + {{- end }} +{{- end }} + +{{- with .Get "filterArgs" }} + {{- $filterArgs = split . "," }} + {{- $filterArgs = apply $filterArgs "trim" "." " " }} +{{- end }} + +{{- if in (slice "false" false 0) (.Get "example") }} + {{- $example = false }} +{{- else if in (slice "true" true 1) (.Get "example") }} + {{- $example = true }} +{{- end }} + +{{- with .Get "exampleWidth" }} + {{- $exampleWidth = . | int }} +{{- end }} + +{{- /* Get image. */}} +{{- $ctx := dict "page" .Page "src" $src "name" .Name "position" .Position }} +{{- $i := partial "inline/get-resource.html" $ctx }} + +{{- /* Resize if rendering before/after examples. */}} +{{- if $example }} + {{- $i = $i.Resize (printf "%dx" $exampleWidth) }} +{{- end }} + +{{- /* Create filter. */}} +{{- $f := "" }} +{{- $ctx := dict "filter" $filter "args" $filterArgs "name" .Name "position" .Position }} +{{- if eq $filter "autoorient" }} + {{- $ctx = merge $ctx (dict "argsRequired" 0) }} + {{- template "validate-arg-count" $ctx }} + {{- $f = images.AutoOrient }} +{{- else if eq $filter "brightness" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "percentage" "argValue" (index $filterArgs 0) "min" -100 "max" 100) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.Brightness (index $filterArgs 0) }} +{{- else if eq $filter "colorbalance" }} + {{- $ctx = merge $ctx (dict "argsRequired" 3) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "percentage red" "argValue" (index $filterArgs 0) "min" -100 "max" 500) }} + {{- template "validate-arg-value" $ctx }} + {{- $ctx = merge $ctx (dict "argName" "percentage green" "argValue" (index $filterArgs 1) "min" -100 "max" 500) }} + {{- template "validate-arg-value" $ctx }} + {{- $ctx = merge $ctx (dict "argName" "percentage blue" "argValue" (index $filterArgs 2) "min" -100 "max" 500) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.ColorBalance (index $filterArgs 0) (index $filterArgs 1) (index $filterArgs 2) }} +{{- else if eq $filter "colorize" }} + {{- $ctx = merge $ctx (dict "argsRequired" 3) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "hue" "argValue" (index $filterArgs 0) "min" 0 "max" 360) }} + {{- template "validate-arg-value" $ctx }} + {{- $ctx = merge $ctx (dict "argName" "saturation" "argValue" (index $filterArgs 1) "min" 0 "max" 100) }} + {{- template "validate-arg-value" $ctx }} + {{- $ctx = merge $ctx (dict "argName" "percentage" "argValue" (index $filterArgs 2) "min" 0 "max" 100) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.Colorize (index $filterArgs 0) (index $filterArgs 1) (index $filterArgs 2) }} +{{- else if eq $filter "contrast" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "percentage" "argValue" (index $filterArgs 0) "min" -100 "max" 100) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.Contrast (index $filterArgs 0) }} +{{- else if eq $filter "dither" }} + {{- $f = images.Dither }} +{{- else if eq $filter "gamma" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "gamma" "argValue" (index $filterArgs 0) "min" 0 "max" 100) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.Gamma (index $filterArgs 0) }} +{{- else if eq $filter "gaussianblur" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "sigma" "argValue" (index $filterArgs 0) "min" 0 "max" 1000) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.GaussianBlur (index $filterArgs 0) }} +{{- else if eq $filter "grayscale" }} + {{- $ctx = merge $ctx (dict "argsRequired" 0) }} + {{- template "validate-arg-count" $ctx }} + {{- $f = images.Grayscale }} +{{- else if eq $filter "hue" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "shift" "argValue" (index $filterArgs 0) "min" -180 "max" 180) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.Hue (index $filterArgs 0) }} +{{- else if eq $filter "invert" }} + {{- $ctx = merge $ctx (dict "argsRequired" 0) }} + {{- template "validate-arg-count" $ctx }} + {{- $f = images.Invert }} +{{- else if eq $filter "mask" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $ctx := dict "src" (index $filterArgs 0) "name" .Name "position" .Position }} + {{- $maskImage := partial "inline/get-resource.html" $ctx }} + {{- $f = images.Mask $maskImage }} +{{- else if eq $filter "opacity" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "opacity" "argValue" (index $filterArgs 0) "min" 0 "max" 1) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.Opacity (index $filterArgs 0) }} +{{- else if eq $filter "overlay" }} + {{- $ctx = merge $ctx (dict "argsRequired" 3) }} + {{- template "validate-arg-count" $ctx }} + {{- $ctx := dict "src" (index $filterArgs 0) "name" .Name "position" .Position }} + {{- $overlayImg := partial "inline/get-resource.html" $ctx }} + {{- $f = images.Overlay $overlayImg (index $filterArgs 1 | float) (index $filterArgs 2 | float) }} +{{- else if eq $filter "padding" }} + {{- $ctx = merge $ctx (dict "argsRequired" 5) }} + {{- template "validate-arg-count" $ctx }} + {{- $f = images.Padding + (index $filterArgs 0 | int) + (index $filterArgs 1 | int) + (index $filterArgs 2 | int) + (index $filterArgs 3 | int) + (index $filterArgs 4) + }} +{{- else if eq $filter "pixelate" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "size" "argValue" (index $filterArgs 0) "min" 0 "max" 1000) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.Pixelate (index $filterArgs 0) }} +{{- else if eq $filter "process" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $f = images.Process (index $filterArgs 0) }} +{{- else if eq $filter "saturation" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "percentage" "argValue" (index $filterArgs 0) "min" -100 "max" 500) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.Saturation (index $filterArgs 0) }} +{{- else if eq $filter "sepia" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "percentage" "argValue" (index $filterArgs 0) "min" 0 "max" 100) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.Sepia (index $filterArgs 0) }} +{{- else if eq $filter "sigmoid" }} + {{- $ctx = merge $ctx (dict "argsRequired" 2) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "midpoint" "argValue" (index $filterArgs 0) "min" 0 "max" 1) }} + {{- template "validate-arg-value" $ctx }} + {{- $ctx = merge $ctx (dict "argName" "factor" "argValue" (index $filterArgs 1) "min" -10 "max" 10) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.Sigmoid (index $filterArgs 0) (index $filterArgs 1) }} +{{- else if eq $filter "text" }} + {{- $ctx = merge $ctx (dict "argsRequired" 1) }} + {{- template "validate-arg-count" $ctx }} + {{- $ctx := dict "src" $textFilterOpts.fontPath "name" .Name "position" .Position }} + {{- $font := or (partial "inline/get-resource.html" $ctx) }} + {{- $fontSize := or (index $filterArgs 3 | int) $textFilterOpts.fontSize }} + {{- $lineHeight := math.Max (or (index $filterArgs 4 | float) $textFilterOpts.lineHeight) 1 }} + {{- $opts := dict + "x" (or (index $filterArgs 1 | int) $textFilterOpts.xOffset) + "y" (or (index $filterArgs 2 | int) $textFilterOpts.yOffset) + "size" $fontSize + "linespacing" (mul (sub $lineHeight 1) $fontSize) + "color" (or (index $filterArgs 5) $textFilterOpts.fontColor) + "font" $font + }} + {{- $f = images.Text (index $filterArgs 0) $opts }} +{{- else if eq $filter "unsharpmask" }} + {{- $ctx = merge $ctx (dict "argsRequired" 3) }} + {{- template "validate-arg-count" $ctx }} + {{- $filterArgs = apply $filterArgs "float" "." }} + {{- $ctx = merge $ctx (dict "argName" "sigma" "argValue" (index $filterArgs 0) "min" 0 "max" 500) }} + {{- template "validate-arg-value" $ctx }} + {{- $ctx = merge $ctx (dict "argName" "amount" "argValue" (index $filterArgs 1) "min" 0 "max" 100) }} + {{- template "validate-arg-value" $ctx }} + {{- $ctx = merge $ctx (dict "argName" "threshold" "argValue" (index $filterArgs 2) "min" 0 "max" 1) }} + {{- template "validate-arg-value" $ctx }} + {{- $f = images.UnsharpMask (index $filterArgs 0) (index $filterArgs 1) (index $filterArgs 2) }} +{{- end }} + +{{- /* Apply filter. */}} +{{- $fi := $i }} +{{- with $f }} + {{- $fi = $i.Filter . }} +{{- end }} + +{{- /* Render. */}} +{{- $class := "border-1 border-gray-300 dark:border-gray-500" }} +{{- if $example }} +

    Original

    + {{ $alt }} +

    Processed

    + {{ $alt }} +{{- else -}} + {{ $alt }} +{{- end }} + +{{- define "validate-arg-count" }} +{{- $msg := "When using the %q filter, the %q shortcode requires an args parameter with %d %s. See %s" }} +{{- if lt (len .args) .argsRequired }} + {{- $text := "values" }} + {{- if eq 1 .argsRequired }} + {{- $text = "value" }} + {{- end }} + {{- errorf $msg .filter .name .argsRequired $text .position }} +{{- end }} +{{- end }} + +{{- define "validate-arg-value" }} +{{- $msg := "The %q argument passed to the %q shortcode is invalid. Expected a value in the range [%v,%v], but received %v. See %s" }} +{{- if or (lt .argValue .min) (gt .argValue .max) }} + {{- errorf $msg .argName .name .min .max .argValue .position }} +{{- end }} +{{- end }} + +{{- define "_partials/inline/get-resource.html" }} +{{- $r := "" }} +{{- $u := urls.Parse .src }} +{{- $msg := "The %q shortcode was unable to resolve %s. See %s" }} +{{- if $u.IsAbs }} + {{- with try (resources.GetRemote $u.String) }} + {{- with .Err }} + {{- errorf "%s" . }} + {{- else with .Value }} + {{- /* This is a remote resource. */}} + {{- $r = . }} + {{- else }} + {{- errorf $msg $.name $u.String $.position }} + {{- end }} + {{- end }} +{{- else }} + {{- with .page.Resources.Get (strings.TrimPrefix "./" $u.Path) }} + {{- /* This is a page resource. */}} + {{- $r = . }} + {{- else }} + {{- with resources.Get $u.Path }} + {{- /* This is a global resource. */}} + {{- $r = . }} + {{- else }} + {{- errorf $msg $.name $u.Path $.position }} + {{- end }} + {{- end }} +{{- end }} +{{- return $r }} +{{- end -}} diff --git a/docs/layouts/_shortcodes/imgproc.html b/docs/layouts/_shortcodes/imgproc.html new file mode 100644 index 0000000..1c31b7e --- /dev/null +++ b/docs/layouts/_shortcodes/imgproc.html @@ -0,0 +1,38 @@ +{{/* +Renders the given image using the given process specification. + +@param {string} path The path to the image, either a page resource or a global resource. +@param {string} spec The image processing specification. +@param {string} alt The alt attribute of the img element. + +@example {{< imgproc path="sunset.jpg" spec="resize 300x" alt="A sunset" >}} +*/}} + +{{- with $.Get "path" }} + {{- with $i := or ($.Page.Resources.Get .) (resources.Get .) }} + {{- with $spec := $.Get "spec" }} + {{- with $i.Process . }} +
    + {{ $.Get `alt` }} +
    + {{- with $.Inner }} + {{ . }} + {{- else }} + {{ $spec }} + {{- end }} +
    +
    + {{- end }} + {{- else }} + {{- errorf "The %q shortcode requires a 'spec' argument containing the image processing specification. See %s" $.Name $.Position }} + {{- end }} + {{- else }} + {{- errorf "The %q shortcode was unable to find %q. See %s" $.Name . $.Position }} + {{- end }} +{{- else }} + {{- errorf "The %q shortcode requires a 'path' argument indicating the image path. The image must be a page resource or a global resource. See %s" $.Name $.Position }} +{{- end }} diff --git a/docs/layouts/_shortcodes/include.html b/docs/layouts/_shortcodes/include.html new file mode 100644 index 0000000..e1e6a14 --- /dev/null +++ b/docs/layouts/_shortcodes/include.html @@ -0,0 +1,19 @@ +{{/* +Renders the page using the RenderShortcode method on the Page object. + +You must call this shortcode using the {{% %}} notation. + +@param {string} (positional parameter 0) The path to the page, relative to the content directory. + +@example {{% include "functions/_common/glob-patterns" %}} +*/}} + +{{- with .Get 0 }} + {{- with or ($.Page.GetPage .) (site.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 path of the file to include. See %s" .Name .Position }} +{{- end }} diff --git a/docs/layouts/_shortcodes/module-mounts-note.html b/docs/layouts/_shortcodes/module-mounts-note.html new file mode 100644 index 0000000..ba89abc --- /dev/null +++ b/docs/layouts/_shortcodes/module-mounts-note.html @@ -0,0 +1,2 @@ +For a more flexible approach to configuring this directory, consult the section +on [module mounts](/configuration/module/#mounts). diff --git a/docs/layouts/_shortcodes/new-in.html b/docs/layouts/_shortcodes/new-in.html new file mode 100644 index 0000000..ac398ab --- /dev/null +++ b/docs/layouts/_shortcodes/new-in.html @@ -0,0 +1,29 @@ +{{/* +Renders an admonition or badge indicating the version in which a feature was introduced. + +To render an admonition, include descriptive text between the opening and closing +tags. To render a badge, omit the descriptive text and call the shortcode with a +self-closing tag. + +@param {string} 0 The semantic version string, with or without a leading v. + +@example {{< new-in 0.144.0 />}} + +@example {{< new-in 0.144.0 >}} + Some descriptive text here. + {{< /new-in >}} +*/}} + +{{- with $version := .Get 0 | strings.TrimLeft "vV" }} + {{- partial "layouts/blocks/feature-state.html" (dict + "inner" (strings.TrimSpace $.Inner) + "name" $.Name + "page" $.Page + "position" $.Position + "status" "new" + "version" $version + ) + }} +{{- else }} + {{- errorf "The %q shortcode requires a single positional parameter indicating version. See %s" .Name .Position }} +{{- end }} diff --git a/docs/layouts/_shortcodes/newtemplatesystem.html b/docs/layouts/_shortcodes/newtemplatesystem.html new file mode 100644 index 0000000..4f8fe0f --- /dev/null +++ b/docs/layouts/_shortcodes/newtemplatesystem.html @@ -0,0 +1,11 @@ +{{ $text := `We did a complete overhaul of Hugo's template system in v0.146.0. + We're working on getting all of the relevant documentation up to date, but until + then, see [this page](/templates/new-templatesystem-overview/). ` }} +{{ partial "layouts/blocks/alert.html" + (dict + "color" "orange" + "icon" "information-circle" + "text" ($text | $.Page.RenderString) + "title" "" + ) +}} diff --git a/docs/layouts/_shortcodes/per-lang-config-keys.html b/docs/layouts/_shortcodes/per-lang-config-keys.html new file mode 100644 index 0000000..3ae9e6b --- /dev/null +++ b/docs/layouts/_shortcodes/per-lang-config-keys.html @@ -0,0 +1,70 @@ +{{/* +Renders a responsive grid of the configuration keys that can be defined +separately for each language. +*/}} + +{{- $siteConfigKeys := slice + (dict "baseURL" "/configuration/all/#baseurl") + (dict "buildDrafts" "/configuration/all/#builddrafts") + (dict "buildExpired" "/configuration/all/#buildexpired") + (dict "buildFuture" "/configuration/all/#buildfuture") + (dict "canonifyURLs" "/configuration/all/#canonifyurls") + (dict "capitalizeListTitles" "/configuration/all/#capitalizelisttitles") + (dict "contentDir" "/configuration/all/#contentdir") + (dict "copyright" "/configuration/all/#copyright") + (dict "disableAliases" "/configuration/all/#disablealiases") + (dict "disableHugoGeneratorInject" "/configuration/all/#disablehugogeneratorinject") + (dict "disableKinds" "/configuration/all/#disablekinds") + (dict "disableLiveReload" "/configuration/all/#disablelivereload") + (dict "disablePathToLower" "/configuration/all/#disablepathtolower") + (dict "enableEmoji " "/configuration/all/#enableemoji") + (dict "frontmatter" "/configuration/front-matter/") + (dict "hasCJKLanguage" "/configuration/all/#hascjklanguage") + (dict "locale" "/configuration/all/#locale") + (dict "mainSections" "/configuration/all/#mainsections") + (dict "markup" "/configuration/markup/") + (dict "mediaTypes" "/configuration/media-types/") + (dict "menus" "/configuration/menus/") + (dict "outputFormats" "/configuration/output-formats") + (dict "outputs" "/configuration/outputs/") + (dict "page" "/configuration/page/") + (dict "pagination" "/configuration/pagination/") + (dict "params" "/configuration/params/") + (dict "permalinks" "/configuration/permalinks/") + (dict "pluralizeListTitles" "/configuration/all/#pluralizelisttitles") + (dict "privacy" "/configuration/privacy/") + (dict "refLinksErrorLevel" "/configuration/all/#reflinkserrorlevel") + (dict "refLinksNotFoundURL" "/configuration/all/#reflinksnotfoundurl") + (dict "related" "/configuration/related-content/") + (dict "relativeURLs" "/configuration/all/#relativeurls") + (dict "removePathAccents" "/configuration/all/#removepathaccents") + (dict "renderSegments" "/configuration/all/#rendersegments") + (dict "sectionPagesMenu" "/configuration/all/#sectionpagesmenu") + (dict "security" "/configuration/security/") + (dict "services" "/configuration/services/") + (dict "sitemap" "/configuration/sitemap/") + (dict "staticDir" "/configuration/all/#staticdir") + (dict "summaryLength" "/configuration/all/#summarylength") + (dict "taxonomies" "/configuration/taxonomies/") + (dict "timeZone" "/configuration/all/#timezone") + (dict "title" "/configuration/all/#title") + (dict "titleCaseStyle" "/configuration/all/#titlecasestyle") +}} + +{{- $a := len $siteConfigKeys }} +{{- $b := math.Ceil (div $a 2.) }} +{{- $c := math.Ceil (div $a 3.) }} + + +
    + {{- range $siteConfigKeys }} + {{ range $k, $v := . }} + {{ $u := urls.Parse $v }} + {{ if not (site.GetPage $u.Path) }} + {{ errorf "The %q shorcode was unable to find %s. See %s." $.Name $u.Path $.Position }} + {{ end }} + {{ $k }} + {{ end }} + {{- end }} +
    diff --git a/docs/layouts/_shortcodes/quick-reference.html b/docs/layouts/_shortcodes/quick-reference.html new file mode 100644 index 0000000..ba1565c --- /dev/null +++ b/docs/layouts/_shortcodes/quick-reference.html @@ -0,0 +1,38 @@ +{{/* gotmplfmt-ignore-all */ -}} + +{{/* +Renders the child sections of the given top-level section, listing each child's +immediate descendants. + +@param {string} section The top-level section to render. + +@example {{% quick-reference section="/functions" %}} +*/}} + +{{ $section := "" }} +{{ with .Get "section" }} + {{ $section = . }} +{{ else }} + {{ errorf "The %q shortcode requires a 'section' parameter. See %s" .Name .Position }} +{{ end }} + +{{ with site.GetPage $section }} + {{ range .Sections }} +## {{ .LinkTitle }}{{/* Do not indent. */}} +{{ .Description }}{{/* Do not indent. */}} + {{ .Content }} + {{ with .Pages }} + {{ range . }} + {{/* Use page Path as the link destination for render hook to resolve correctly. */}} + {{ if .Params.isFunctionOrMethod }} +[`{{ .LinkTitle }}`]({{ .Path }}){{/* Do not indent. */}} + {{ else }} +[{{ .LinkTitle }}]({{ .Path }}){{/* Do not indent. */}} + {{ end }} +: {{ .Description }}{{/* Do not indent. */}} + {{ end }} + {{ end }} + {{ end }} +{{ else }} + {{ errorf "The %q shortcodes was unable to find the %q section. See %s" .Name $section .Position }} +{{ end }} diff --git a/docs/layouts/_shortcodes/render-list-of-pages-in-section.html b/docs/layouts/_shortcodes/render-list-of-pages-in-section.html new file mode 100644 index 0000000..b046f4e --- /dev/null +++ b/docs/layouts/_shortcodes/render-list-of-pages-in-section.html @@ -0,0 +1,76 @@ +{{/* gotmplfmt-ignore-all */ -}} + +{{/* +Renders a description list of the pages in the given section. + +Render a subset of the pages in the section by specifying a predefined filter, +and whether to include those pages. + +Filters are defined in the data directory, in the file named page_filters. Each +filter is an array of paths to a file, relative to the root of the content +directory. Hugo will throw an error if the specified filter does not exist, or +if any of the pages in the filter do not exist. + +@param {string} path The path to the section. +@param {string} [filter=""] The name of filter list. +@param {string} [filterType=""] The type of filter, either include or exclude. +@param {string} [titlePrefix=""] The string to prepend to the link title. + +@example {{% render-list-of-pages-in-section path=/methods/resource %}} +@example {{% render-list-of-pages-in-section path=/functions/images filter=some_filter filterType=exclude %}} +@example {{% render-list-of-pages-in-section path=/functions/images filter=some_filter filterType=exclude titlePrefix=foo %}} +*/}} + +{{- /* Initialize. */}} +{{- $filter := or "" (.Get "filter" | lower) }} +{{- $filterType := or (.Get "filterType") "none" | lower }} +{{- $filteredPages := slice }} +{{- $titlePrefix := or (.Get "titlePrefix") "" }} + +{{- /* Build slice of filtered pages. */}} +{{- with $filter }} + {{- with index hugo.Data.page_filters . }} + {{- range . }} + {{- with site.GetPage . }} + {{- $filteredPages = $filteredPages | append . }} + {{- else }} + {{- errorf "The %q shortcode was unable to find %q as specified in the page_filters data file. See %s" $.Name . $.Position }} + {{- end }} + {{- end }} + {{- else }} + {{- errorf "The %q shortcode was unable to find the %q filter in the page_filters data file. See %s" $.Name . $.Position }} + {{- end }} +{{- end }} + +{{- /* Render. */}} +{{- with $sectionPath := .Get "path" }} + {{- with site.GetPage . }} + {{- with .RegularPages }} + {{- range $page := .ByTitle }} + {{- if or + (and (eq $filterType "include") (in $filteredPages $page)) + (and (eq $filterType "exclude") (not (in $filteredPages $page))) + (eq $filterType "none") + }} + {{- $linkTitle := .LinkTitle }} + {{- with $titlePrefix }} + {{- $linkTitle = printf "%s%s" . $linkTitle }} + {{- end }} + {{- /* Use page Path as the link destination for render hook to resolve correctly. */}} + {{- if $page.Params.isFunctionOrMethod }} +[`{{ $linkTitle }}`]({{ $page.Path }}){{/* Do not indent. */}} + {{- else }} +[{{ $linkTitle }}]({{ $page.Path }}){{/* Do not indent. */}} + {{- end }} +: {{ $page.Description }}{{/* Do not indent. */}} + {{ end }} + {{- end }} + {{- else }} + {{- warnf "The %q shortcode found no pages in the %q section. See %s" $.Name $sectionPath $.Position }} + {{- end }} + {{- else }} + {{- errorf "The %q shortcode was unable to find %q. See %s" $.Name $sectionPath $.Position }} + {{- end }} +{{- else }} + {{- errorf "The %q shortcode requires a 'path' parameter indicating the path to the section. See %s" $.Name $.Position }} +{{- end }} diff --git a/docs/layouts/_shortcodes/render-table-of-pages-in-section.html b/docs/layouts/_shortcodes/render-table-of-pages-in-section.html new file mode 100644 index 0000000..6e43de7 --- /dev/null +++ b/docs/layouts/_shortcodes/render-table-of-pages-in-section.html @@ -0,0 +1,88 @@ +{{/* gotmplfmt-ignore-all */ -}} + +{{/* +Renders a table of the pages in the given section. + +Render a subset of the pages in the section by specifying a predefined filter, +and whether to include those pages. + +Filters are defined in the data directory, in the file named page_filters. Each +filter is an array of paths to a file, relative to the root of the content +directory. Hugo will throw an error if the specified filter does not exist, or +if any of the pages in the filter do not exist. + +@param {string} path The path to the section. +@param {string} [filter=""] The name of filter list. +@param {string} [filterType=""] The type of filter, either include or exclude. +@param {string} [titlePrefix=""] The string to prepend to the link title. +@param {string} [headingColumn1="Item"] The heading for the first column of the table. +@param {string} [headingColumn2="Description"] The heading for the second column of the table. + +@example + +{{% render-table-of-pages-in-section + path=/methods/resource + filter=methods_resource_image_processing + filterType=include + headingColumn1=Method + headingColumn2=Description +%}} + +*/}} + +{{- /* Initialize. */}} +{{- $filter := or "" (.Get "filter" | lower) }} +{{- $filterType := or (.Get "filterType") "none" | lower }} +{{- $filteredPages := slice }} +{{- $titlePrefix := or (.Get "titlePrefix") "" }} +{{- $headingColumn1 := or (.Get "headingColumn1") "Item" }} +{{- $headingColumn2 := or (.Get "headingColumn2") "Description" }} + +{{- /* Build slice of filtered pages. */}} +{{- with $filter }} + {{- with index hugo.Data.page_filters . }} + {{- range . }} + {{- with site.GetPage . }} + {{- $filteredPages = $filteredPages | append . }} + {{- else }} + {{- errorf "The %q shortcode was unable to find %q as specified in the page_filters data file. See %s" $.Name . $.Position }} + {{- end }} + {{- end }} + {{- else }} + {{- errorf "The %q shortcode was unable to find the %q filter in the page_filters data file. See %s" $.Name . $.Position }} + {{- end }} +{{- end }} + +{{- /* Render. */}} +{{- with $sectionPath := .Get "path" }} + {{- with site.GetPage . }} + {{- with .RegularPages }} +{{ $headingColumn1 }}|{{ $headingColumn2 }}{{/* Do not indent. */}} +:--|:--{{/* Do not indent. */}} + {{- range $page := .ByTitle }} + {{- if or + (and (eq $filterType "include") (in $filteredPages $page)) + (and (eq $filterType "exclude") (not (in $filteredPages $page))) + (eq $filterType "none") + }} + {{- $linkTitle := .LinkTitle }} + {{- with $titlePrefix }} + {{- $linkTitle = printf "%s%s" . $linkTitle }} + {{- end }} + {{- /* Use page Path as the link destination for render hook to resolve correctly. */}} + {{- if $page.Params.isFunctionOrMethod }} +[`{{ $linkTitle }}`]({{ $page.Path }})|{{ $page.Description }}{{/* Do not indent. */}} + {{- else }} +[{{ $linkTitle }}]({{ $page.Path }})|{{ $page.Description }}{{/* Do not indent. */}} + {{- end }} + {{- end }} + {{- end }} + {{- else }} + {{- warnf "The %q shortcode found no pages in the %q section. See %s" $.Name $sectionPath $.Position }} + {{- end }} + {{- else }} + {{- errorf "The %q shortcode was unable to find %q. See %s" $.Name $sectionPath $.Position }} + {{- end }} +{{- else }} + {{- errorf "The %q shortcode requires a 'path' parameter indicating the path to the section. See %s" $.Name $.Position }} +{{- end }} diff --git a/docs/layouts/_shortcodes/root-configuration-keys.html b/docs/layouts/_shortcodes/root-configuration-keys.html new file mode 100644 index 0000000..41ef496 --- /dev/null +++ b/docs/layouts/_shortcodes/root-configuration-keys.html @@ -0,0 +1,44 @@ +{{/* +Renders a comma-separated list of links to the root key configuration pages. + +@example {{< root-configuration-keys >}} +*/}} + +{{- /* Create scratch map of key:filename. */}} +{{- $s := newScratch }} +{{- range $k, $v := hugo.Data.docs.config }} + {{- if or (reflect.IsMap .) (reflect.IsSlice .) }} + {{- $s.Set $k ($k | humanize | anchorize) }} + {{- end }} +{{- end }} + +{{- /* Deprecated. */}} +{{- $s.Delete "author" }} + +{{- /* Use mounts instead. */}} +{{- $s.Delete "staticDir" }} +{{- $s.Delete "ignoreFiles" }} + +{{- /* This key is "HTTPCache" not "httpCache". */}} +{{- $s.Set "HTTPCache" "http-cache" }} + +{{- /* This key is "frontmatter" not "frontMatter" */}} +{{- $s.Set "frontmatter" "front-matter" }} + +{{- /* The page title is "Related content" not "related". */}} +{{- $s.Set "related" "related-content" }} + +{{- /* It can be configured as bool or map; we want to show map. */}} +{{- $s.Set "uglyURLs" "ugly-urls" }} + +{{- $links := slice }} +{{- range $k, $v := $s.Values }} + {{- $path := printf "/configuration/%s" $v }} + {{- with site.GetPage $path }} + {{- $links = $links | append (printf "%s" .RelPermalink $k) }} + {{- else }} + {{- errorf "The %q shortcode was unable to find the page %s. See %s." $.Name $path $.Position }} + {{- end }} +{{- end }} + +{{- delimit $links ", " ", and " | safeHTML -}} diff --git a/docs/layouts/_shortcodes/syntax-highlighting-styles.html b/docs/layouts/_shortcodes/syntax-highlighting-styles.html new file mode 100644 index 0000000..e9c5710 --- /dev/null +++ b/docs/layouts/_shortcodes/syntax-highlighting-styles.html @@ -0,0 +1,73 @@ +{{/* gotmplfmt-ignore-all */ -}} + +{{/* +Renders a gallery a Chroma syntax highlighting styles. + +@example {{% syntax-highlighting-styles %}} +*/}} + +{{- $examples := slice }} + +{{- /* Example: css */}} +{{- $example := dict "lang" "css" "code" ` +body { + font-size: 16px; /* comment */ +} +` }} +{{- $examples = $examples | append $example }} + +{{- /* Example: html */}} +{{- $example = dict "lang" "html" "code" ` +Example +` }} +{{- $examples = $examples | append $example }} + +{{- /* Example: go-html-template */}} +{{- $example = dict "lang" "go-html-template" "code" ` +{{ with $.Page.Params.content }} + {{ . | $.Page.RenderString }} {{/* comment */}} +{{ end }} +` }} +{{- $examples = $examples | append $example }} + +{{- /* Example: javascript */}} +{{- $example = dict "lang" "javascript" "code" ` +if ([1,"one",2,"two"].includes(value)){ + console.log("Number is either 1 or 2."); // comment +} +` }} +{{- $examples := $examples | append $example }} + +{{- /* Example: markdown */}} +{{- $example = dict "lang" "markdown" "code" ` +{{< figure src="kitten.jpg" >}} +[example](https://example.org "An example") +` }} +{{- $examples := $examples | append $example }} + +{{- /* Example: toml */}} +{{- $example = dict "lang" "toml" "code" ` +[params] +bool = true # comment +string = 'foo' +` }} +{{- $examples := $examples | append $example }} + +{{- /* Render */}} +{{- with hugo.Data.docs.chroma.styles }} + {{- range $style := . }} + +### {{ $style }} {class="!mt-7 !mb-6"}{{/* Do not indent. */}} + + {{- range $examples }} + +{{ .lang }}{{/* Do not indent. */}} +{class="text-sm !-mt-3 !-mb-5"}{{/* Do not indent. */}} + +```{{ .lang }} {noClasses=true style="{{ $style }}"}{{/* Do not indent. */}} +{{- .code | safeHTML -}}{{/* Do not indent. */}} +```{{/* Do not indent. */}} + + {{- end }} + {{- end }} +{{- end }} diff --git a/docs/layouts/baseof.html b/docs/layouts/baseof.html new file mode 100644 index 0000000..1c9aa4c --- /dev/null +++ b/docs/layouts/baseof.html @@ -0,0 +1,77 @@ + + + + + + {{ .Title }} + + + + {{ partial "layouts/head/head-js.html" . }} + {{ with (templates.Defer (dict "key" "global")) }} + {{ $t := debug.Timer "tailwindcss" }} + {{ with resources.Get "css/styles.css" }} + {{ $opts := dict "minify" (not hugo.IsDevelopment) }} + {{ with . | css.TailwindCSS $opts }} + {{ partial "helpers/linkcss.html" (dict "r" .) }} + {{ end }} + {{ end }} + {{ $t.Stop }} + {{ end }} + {{ $noop := .WordCount }} + {{ if .Page.Store.Get "hasMath" }} + + {{ end }} + {{ partial "layouts/head/head.html" . }} + + + {{ $bodyClass := printf "flex flex-col min-h-full bg-white dark:bg-blue-950 kind-%s" .Kind }} + {{ if .Params.searchable }} + {{ $bodyClass = printf "%s searchable" $bodyClass }} + {{ end }} + + + {{ partial "layouts/hooks/body-start.html" . }} + {{/* Layout. */}} + {{ block "header" . }} + {{ partial "layouts/header/header.html" . }} + {{ end }} + {{ block "subheader" . }} + {{ end }} + {{ block "hero" . }} + {{ end }} +
    +
    + {{ partial "layouts/hooks/body-main-start.html" . }} + {{ block "main" . }}{{ end }} +
    + {{ block "rightsidebar" . }} + + {{ end }} +
    + {{/* Common icons. */}} + {{ partial "layouts/icons.html" . }} + {{/* Common templates. */}} + {{ partial "layouts/templates.html" . }} + {{/* Footer. */}} + {{ block "footer" . }} + {{ partial "layouts/footer.html" . }} + {{ end }} + {{ partial "layouts/hooks/body-end.html" . }} + + diff --git a/docs/layouts/home.headers b/docs/layouts/home.headers new file mode 100644 index 0000000..1216e42 --- /dev/null +++ b/docs/layouts/home.headers @@ -0,0 +1,5 @@ +/* + X-Frame-Options: DENY + X-XSS-Protection: 1; mode=block + X-Content-Type-Options: nosniff + Referrer-Policy: origin-when-cross-origin diff --git a/docs/layouts/home.html b/docs/layouts/home.html new file mode 100644 index 0000000..8e69ae0 --- /dev/null +++ b/docs/layouts/home.html @@ -0,0 +1,52 @@ +{{ define "main" }} +
    + {{ partial "layouts/home/opensource.html" . }} +
    + {{ partial "layouts/home/sponsors.html" (dict "ctx" . "gtag" "home") }} +
    + {{ partial "layouts/home/features.html" . }} +
    +{{ end }} + +{{ define "hero" }} +
    +
    +
    + Hugo Logo +

    + The world’s fastest framework for building websites +

    +
    + Hugo is one of the most popular open-source static site generators. + With its amazing speed and flexibility, Hugo makes building websites + fun again. +
    +
    + {{ with site.GetPage "/getting-started" }} + {{ .LinkTitle }} + {{ end }} +
    + {{ partial "layouts/search/button.html" (dict "page" . "standalone" true) }} +
    +
    +
    +
    +
    +{{ end }} + +{{ define "rightsidebar" }} +{{ printf "%c" '\u00A0' }} +{{ end }} + +{{ define "leftsidebar" }} +{{ printf "%c" '\u00A0' }} +{{ end }} diff --git a/docs/layouts/home.redir b/docs/layouts/home.redir new file mode 100644 index 0000000..bb72f96 --- /dev/null +++ b/docs/layouts/home.redir @@ -0,0 +1,6 @@ +# Netlify redirects. See https://www.netlify.com/docs/redirects/ +{{ range $p := .Site.Pages -}} +{{ range .Aliases }} +{{ . | printf "%-35s" }} {{ $p.RelPermalink -}} +{{ end -}} +{{- end -}} diff --git a/docs/layouts/list.html b/docs/layouts/list.html new file mode 100644 index 0000000..9f6de99 --- /dev/null +++ b/docs/layouts/list.html @@ -0,0 +1,69 @@ +{{ define "main" }} +{{ $pages := "" }} +{{ if .IsPage }} + {{/* We currently have a slightly odd content structure with no top level /docs section. */}} + {{ $pages = .CurrentSection.Pages }} +{{ else }} + {{ $pages = .Pages }} + {{ if eq .Section "news" }} + {{ $pages = $pages.ByPublishDate.Reverse }} + {{ end }} +{{ end }} + + +
    + {{ partial "layouts/docsheader.html" . }} + +
    +{{ end }} + +{{ define "rightsidebar" }} +{{ printf "%c" '\u00A0' }} +{{ end }} diff --git a/docs/layouts/list.rss.xml b/docs/layouts/list.rss.xml new file mode 100644 index 0000000..7fc59d2 --- /dev/null +++ b/docs/layouts/list.rss.xml @@ -0,0 +1,33 @@ +{{- printf "" | safeHTML }} + + + Hugo News + Recent news about Hugo, a static site generator written in Go, optimized for speed and designed for flexibility. + {{ .Permalink }} + Hugo {{ hugo.Version }} + {{ site.Language.Locale }} + {{- with site.Copyright }} + {{ . }} + {{- end }} + {{- with .OutputFormats.Get "rss" }} + {{ printf "" .Permalink .MediaType | safeHTML }} + {{- end }} + {{- $limit := cond (gt site.Config.Services.RSS.Limit 0) site.Config.Services.RSS.Limit 999 }} + {{- $pages := "" }} + {{- with site.GetPage "/news" }} + {{- $pages = .Pages.ByPublishDate.Reverse | first $limit }} + {{- else }} + {{- errorf "The list.rss.xml layout was unable to find the 'news' page." }} + {{- end }} + {{ (index $pages 0).PublishDate.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }} + {{- range $pages }} + + {{ .Title }} + {{ or .Params.permalink .Permalink }} + {{ .PublishDate.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }} + {{ or .Params.permalink .Permalink }} + {{ .Summary | transform.XMLEscape | safeHTML }} + + {{- end }} + + diff --git a/docs/layouts/single.html b/docs/layouts/single.html new file mode 100644 index 0000000..8561b24 --- /dev/null +++ b/docs/layouts/single.html @@ -0,0 +1,80 @@ +{{ define "main" }} +{{ $ttop := debug.Timer "single" }} +
    + {{ partial "layouts/docsheader.html" . }} +
    + {{ with .Params.description }} +
    + {{ . | markdownify }} +
    + {{ end }} + {{ if .Params.show_publish_date }} + {{ with .PublishDate }} +

    + {{ partial "layouts/date.html" . }} +

    + {{ end }} + {{ end }} + {{ $t := debug.Timer "single.categories" }} + {{ $categories := .GetTerms "categories" }} + {{ with $categories }} +
    + {{ range . }} + {{ $text := .LinkTitle }} + {{ $class := "" }} + {{ range (slice true false) }} + {{ $color := partial "helpers/funcs/color-from-string.html" (dict "text" $text "dark" . "--single" "green") }} + + {{ $prefix := "" }} + {{ if . }} + {{ $prefix = "dark:" }} + {{ end }} + {{ $class = printf "%sbg-%s-%d %stext-%s-%d border %sborder-%s-%d" + $prefix $color.color $color.shade1 + $prefix $color.color $color.shade2 + $prefix $color.color $color.shade3 + }} + {{ end }} + + + + {{ .LinkTitle }} + + {{ end }} +
    + {{ end }} + {{ $t.Stop }} + + {{ if .Params.functions_and_methods.signatures }} +
    + {{- partial "docs/functions-signatures.html" . -}} + {{- partial "docs/functions-return-type.html" . -}} + {{- partial "docs/functions-aliases.html" . -}} +
    + {{ end }} + {{ $t := debug.Timer "single.content" }} + {{ .Content }} + {{ $t.Stop }} + {{ $t := debug.Timer "single.page-edit" }} + {{ partial "layouts/page-edit.html" . }} + {{ $t.Stop }} +
    +
    +{{ $ttop.Stop }} +{{ end }} + +{{ define "rightsidebar_content" }} +{{/* in-this-section.html depends on these being reneredc first. */}} +{{ $related := partial "layouts/related.html" . }} +{{ $toc := partial "layouts/toc.html" . }} +{{ if not .Params.hide_in_this_section }} + {{ partial "layouts/in-this-section.html" . }} +{{ end }} +{{ $related }} +{{ if $.Store.Get "hasToc" }} + {{ $toc }} +{{ end }} +{{ end }} diff --git a/docs/netlify.toml b/docs/netlify.toml new file mode 100644 index 0000000..74db79a --- /dev/null +++ b/docs/netlify.toml @@ -0,0 +1,55 @@ +[build] + publish = "public" + command = "npm ls && hugo --gc --minify" + + [build.environment] + HUGO_VERSION = "0.163.3" + +[context.production.environment] + HUGO_ENV = "production" + HUGO_ENABLEGITINFO = "true" + +[context.split1] + command = "npm ls && hugo --gc --minify --enableGitInfo" + + [context.split1.environment] + HUGO_ENV = "production" + +[context.deploy-preview] + command = "npm ls && hugo --gc --minify --buildFuture -b $DEPLOY_PRIME_URL --enableGitInfo" + +[context.branch-deploy] + command = "npm ls && hugo --gc --minify -b $DEPLOY_PRIME_URL" + +[context.next.environment] + HUGO_ENABLEGITINFO = "true" + +[[headers]] + for = "/*.jpg" + + [headers.values] + Cache-Control = "public, max-age=31536000" + +[[headers]] + for = "/*.png" + + [headers.values] + Cache-Control = "public, max-age=31536000" + +[[headers]] + for = "/*.css" + + [headers.values] + Cache-Control = "public, max-age=31536000" + +[[headers]] + for = "/*.js" + + [headers.values] + Cache-Control = "public, max-age=31536000" + +[[headers]] + for = "/*.ttf" + + [headers.values] + Cache-Control = "public, max-age=31536000" diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 0000000..e995d04 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,22 @@ +{ + "name": "hugoDocs", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "", + "devDependencies": { + "@tailwindcss/cli": "~4.1.18", + "@tailwindcss/typography": "~0.5.19", + "prettier": "~3.7.4", + "tailwindcss": "~4.1.18" + }, + "dependencies": { + "@alpinejs/focus": "3.15.3", + "@alpinejs/persist": "3.15.3", + "alpinejs": "3.15.3" + } +} diff --git a/docs/static/android-chrome-144x144.png b/docs/static/android-chrome-144x144.png new file mode 100644 index 0000000..975cb33 Binary files /dev/null and b/docs/static/android-chrome-144x144.png differ diff --git a/docs/static/android-chrome-192x192.png b/docs/static/android-chrome-192x192.png new file mode 100644 index 0000000..7ab6c38 Binary files /dev/null and b/docs/static/android-chrome-192x192.png differ diff --git a/docs/static/android-chrome-256x256.png b/docs/static/android-chrome-256x256.png new file mode 100644 index 0000000..ed88a22 Binary files /dev/null and b/docs/static/android-chrome-256x256.png differ diff --git a/docs/static/android-chrome-36x36.png b/docs/static/android-chrome-36x36.png new file mode 100644 index 0000000..3695eb0 Binary files /dev/null and b/docs/static/android-chrome-36x36.png differ diff --git a/docs/static/android-chrome-48x48.png b/docs/static/android-chrome-48x48.png new file mode 100644 index 0000000..ca275da Binary files /dev/null and b/docs/static/android-chrome-48x48.png differ diff --git a/docs/static/android-chrome-72x72.png b/docs/static/android-chrome-72x72.png new file mode 100644 index 0000000..966891f Binary files /dev/null and b/docs/static/android-chrome-72x72.png differ diff --git a/docs/static/android-chrome-96x96.png b/docs/static/android-chrome-96x96.png new file mode 100644 index 0000000..feb1d3e Binary files /dev/null and b/docs/static/android-chrome-96x96.png differ diff --git a/docs/static/apple-touch-icon.png b/docs/static/apple-touch-icon.png new file mode 100644 index 0000000..ecf1fc0 Binary files /dev/null and b/docs/static/apple-touch-icon.png differ diff --git a/docs/static/favicon-16x16.png b/docs/static/favicon-16x16.png new file mode 100644 index 0000000..c62ce6f Binary files /dev/null and b/docs/static/favicon-16x16.png differ diff --git a/docs/static/favicon-32x32.png b/docs/static/favicon-32x32.png new file mode 100644 index 0000000..57a018e Binary files /dev/null and b/docs/static/favicon-32x32.png differ diff --git a/docs/static/favicon.ico b/docs/static/favicon.ico new file mode 100644 index 0000000..dc007a9 Binary files /dev/null and b/docs/static/favicon.ico differ diff --git a/docs/static/fonts/Mulish-Italic-VariableFont_wght.ttf b/docs/static/fonts/Mulish-Italic-VariableFont_wght.ttf new file mode 100644 index 0000000..e5425c7 Binary files /dev/null and b/docs/static/fonts/Mulish-Italic-VariableFont_wght.ttf differ diff --git a/docs/static/fonts/Mulish-VariableFont_wght.ttf b/docs/static/fonts/Mulish-VariableFont_wght.ttf new file mode 100644 index 0000000..410f7aa Binary files /dev/null and b/docs/static/fonts/Mulish-VariableFont_wght.ttf differ diff --git a/docs/static/images/gopher-hero.svg b/docs/static/images/gopher-hero.svg new file mode 100644 index 0000000..36d9f1c --- /dev/null +++ b/docs/static/images/gopher-hero.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/static/images/gopher-side_color.svg b/docs/static/images/gopher-side_color.svg new file mode 100644 index 0000000..85f9497 --- /dev/null +++ b/docs/static/images/gopher-side_color.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/static/images/hugo-logo-wide.svg b/docs/static/images/hugo-logo-wide.svg new file mode 100644 index 0000000..1f6a79e --- /dev/null +++ b/docs/static/images/hugo-logo-wide.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/docs/static/img/examples/trees.svg b/docs/static/img/examples/trees.svg new file mode 100644 index 0000000..0aaccfc --- /dev/null +++ b/docs/static/img/examples/trees.svg @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1 +2 +3 +4 +1 +2 +3 +4 +1 +2 +3 +4 +1 +2 +3 +4 +1 +2 +3 +4 +1 +2 +3 +4 + + diff --git a/docs/static/img/hugo-logo-med.png b/docs/static/img/hugo-logo-med.png new file mode 100644 index 0000000..11d91b3 Binary files /dev/null and b/docs/static/img/hugo-logo-med.png differ diff --git a/docs/static/img/hugo-logo.png b/docs/static/img/hugo-logo.png new file mode 100644 index 0000000..0a78f8e Binary files /dev/null and b/docs/static/img/hugo-logo.png differ diff --git a/docs/static/img/hugo.png b/docs/static/img/hugo.png new file mode 100644 index 0000000..48acf34 Binary files /dev/null and b/docs/static/img/hugo.png differ diff --git a/docs/static/img/hugoSM.png b/docs/static/img/hugoSM.png new file mode 100644 index 0000000..f64f430 Binary files /dev/null and b/docs/static/img/hugoSM.png differ diff --git a/docs/static/manifest.json b/docs/static/manifest.json new file mode 100644 index 0000000..e671ac4 --- /dev/null +++ b/docs/static/manifest.json @@ -0,0 +1,45 @@ +{ + "name": "Hugo", + "short_name": "Hugo", + "icons": [ + { + "src": "/android-chrome-36x36.png", + "sizes": "36x36", + "type": "image/png" + }, + { + "src": "/android-chrome-48x48.png", + "sizes": "48x48", + "type": "image/png" + }, + { + "src": "/android-chrome-72x72.png", + "sizes": "72x72", + "type": "image/png" + }, + { + "src": "/android-chrome-96x96.png", + "sizes": "96x96", + "type": "image/png" + }, + { + "src": "/android-chrome-144x144.png", + "sizes": "144x144", + "type": "image/png" + }, + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-256x256.png", + "sizes": "256x256", + "type": "image/png" + } + ], + "start_url": "./?utm_source=web_app_manifest", + "theme_color": "#0A1922", + "background_color": "#FFF", + "display": "standalone" +} diff --git a/docs/static/mstile-144x144.png b/docs/static/mstile-144x144.png new file mode 100644 index 0000000..e54b4bd Binary files /dev/null and b/docs/static/mstile-144x144.png differ diff --git a/docs/static/mstile-150x150.png b/docs/static/mstile-150x150.png new file mode 100644 index 0000000..c7b84c6 Binary files /dev/null and b/docs/static/mstile-150x150.png differ diff --git a/docs/static/mstile-310x310.png b/docs/static/mstile-310x310.png new file mode 100644 index 0000000..2cde5c0 Binary files /dev/null and b/docs/static/mstile-310x310.png differ diff --git a/docs/static/safari-pinned-tab.svg b/docs/static/safari-pinned-tab.svg new file mode 100644 index 0000000..80ff2da --- /dev/null +++ b/docs/static/safari-pinned-tab.svg @@ -0,0 +1,22 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + diff --git a/docs/static/shared/examples/data/books.json b/docs/static/shared/examples/data/books.json new file mode 100644 index 0000000..ae2f36d --- /dev/null +++ b/docs/static/shared/examples/data/books.json @@ -0,0 +1,55 @@ +[ + { + "author": "Victor Hugo", + "cover": "https://gohugo.io/shared/examples/images/the-hunchback-of-notre-dame.webp", + "date": "2024-05-06", + "isbn": "978-0140443530", + "rating": 4, + "summary": "In the vaulted Gothic towers of **Notre-Dame Cathedral** lives Quasimodo, the hunchbacked bellringer. Mocked and shunned for his appearance, he is pitied only by Esmerelda, a beautiful gypsy dancer to whom he becomes completely devoted. Esmerelda, however, has also attracted the attention of the sinister archdeacon Claude Frollo, and when she rejects his lecherous approaches, Frollo hatches a plot to destroy her, that only Quasimodo can prevent. Victor Hugo's sensational, evocative novel brings life to the medieval Paris he loved, and mourns its passing in one of the greatest historical romances of the nineteenth century.", + "tags": [ + "fiction", + "historical" + ], + "title": "The Hunchback of Notre Dame" + }, + { + "author": "Victor Hugo", + "cover": "https://gohugo.io/shared/examples/images/les-misérables.webp", + "date": "2022-12-30", + "isbn": "978-0451419439", + "rating": 5, + "summary": "Introducing one of the most **famous characters** in literature, Jean Valjean—the noble peasant imprisoned for stealing a loaf of bread—Les Misérables ranks among the greatest novels of all time. In it, Victor Hugo takes readers deep into the Parisian underworld, immerses them in a battle between good and evil, and carries them to the barricades during the uprising of 1832 with a breathtaking realism that is unsurpassed in modern prose.", + "tags": [ + "fiction", + "historical", + "revolution" + ], + "title": "Les Misérables" + }, + { + "author": "Alexis de Tocqueville", + "cover": "https://gohugo.io/shared/examples/images/the-ancien-régime-and-the-revolution.webp", + "date": "2023-04-01", + "isbn": "978-0141441641", + "rating": 3, + "summary": "The Ancien Régime and the Revolution is a comparison of **revolutionary France** and the despotic rule it toppled. Alexis de Tocqueville (1805–59) is an objective observer of both periods – providing a merciless critique of the ancien régime, with its venality, oppression and inequality, yet acknowledging the reforms introduced under Louis XVI, and claiming that the post-Revolution state was in many ways as tyrannical as that of the King; its once lofty and egalitarian ideals corrupted and forgotten. Writing in the 1850s, Tocqueville wished to expose the return to despotism he witnessed in his own time under Napoleon III, by illuminating the grand, but ultimately doomed, call to liberty made by the French people in 1789. His eloquent and instructive study raises questions about liberty, nationalism and justice that remain urgent today.", + "tags": [ + "nonfiction", + "revolution" + ], + "title": "The Ancien Régime and the Revolution" + }, + { + "author": "François Furet", + "cover": "https://gohugo.io/shared/examples/images/interpreting-the-french-revolution.webp", + "date": "2024-01-12", + "isbn": "978-0521280495", + "rating": 5, + "summary": "The French Revolution is an historical event **unlike any other**. It is more than just a topic of intellectual interest: it has become part of a moral and political heritage. But after two centuries, this central event in French history has usually been thought of in much the same terms as it was by its contemporaries. There have been many accounts of the French Revolution, and though their opinions differ, they have often been commemorative or anniversary interpretations of the original event. The dividing line of revolutionary historiography, in intellectual terms, is therefore not between the right and the left, but between commemorative and conceptual history, as exemplified respectively in the works of Michelet and Tocquevifle. In this book, François Furet analyses how an event like the French Revolution can be conceptualised, and identifies the radically new changes the Revolution produced as well as the continuity it provided, albeit under the appearance of change. This question has become a riddle for the European left, answered neither by Marx nor by the theorists of our own century. In his analysis of the tragic relevance of the Revolution, Furet both refers to contemporary experience and discusses various elements in the work of Alexis de Tocclueville and that of Augustin Cochin, which has never been systematically applied by historians of the Revolution. Furet's book is based on the complementary ideas of these two writers in an attempt to cut through the apparent and misleading clarity of various contradictory views of the Revolution, and to help decipher some of the enigmatic problems of revolutionary ideology. It will be of value to historians of modern Europe and their students; to political, social and economic historians; to sociologists; and to students of political thought.", + "tags": [ + "nonfiction", + "revolution" + ], + "title": "Interpreting the French Revolution" + } +] diff --git a/docs/static/shared/examples/images/interpreting-the-french-revolution.webp b/docs/static/shared/examples/images/interpreting-the-french-revolution.webp new file mode 100644 index 0000000..4004b66 Binary files /dev/null and b/docs/static/shared/examples/images/interpreting-the-french-revolution.webp differ diff --git a/docs/static/shared/examples/images/les-misérables.webp b/docs/static/shared/examples/images/les-misérables.webp new file mode 100644 index 0000000..e336a5f Binary files /dev/null and b/docs/static/shared/examples/images/les-misérables.webp differ diff --git a/docs/static/shared/examples/images/the-ancien-régime-and-the-revolution.webp b/docs/static/shared/examples/images/the-ancien-régime-and-the-revolution.webp new file mode 100644 index 0000000..f351839 Binary files /dev/null and b/docs/static/shared/examples/images/the-ancien-régime-and-the-revolution.webp differ diff --git a/docs/static/shared/examples/images/the-hunchback-of-notre-dame.webp b/docs/static/shared/examples/images/the-hunchback-of-notre-dame.webp new file mode 100644 index 0000000..f13e222 Binary files /dev/null and b/docs/static/shared/examples/images/the-hunchback-of-notre-dame.webp differ diff --git a/docshelper/docs.go b/docshelper/docs.go new file mode 100644 index 0000000..b138ff8 --- /dev/null +++ b/docshelper/docs.go @@ -0,0 +1,47 @@ +// 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 docshelper provides some helpers for the Hugo documentation, and +// is of limited interest for the general Hugo user. +package docshelper + +import "fmt" + +type ( + DocProviderFunc = func() DocProvider + DocProvider map[string]any +) + +var docProviderFuncs []DocProviderFunc + +func AddDocProviderFunc(fn DocProviderFunc) { + docProviderFuncs = append(docProviderFuncs, fn) +} + +func GetDocProvider() DocProvider { + provider := make(DocProvider) + + for _, fn := range docProviderFuncs { + p := fn() + for k, v := range p { + if _, found := provider[k]; found { + // We use to merge config, but not anymore. + // These constructs will eventually go away, so just make it simple. + panic(fmt.Sprintf("Duplicate doc provider key: %q", k)) + } + provider[k] = v + } + } + + return provider +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e28acb1 --- /dev/null +++ b/go.mod @@ -0,0 +1,189 @@ +module github.com/gohugoio/hugo + +require ( + github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69 + github.com/JohannesKaufmann/html-to-markdown/v2 v2.5.2 + github.com/alecthomas/chroma/v2 v2.27.0 + github.com/aws/aws-sdk-go-v2 v1.41.6 + github.com/aws/aws-sdk-go-v2/service/cloudfront v1.61.1 + github.com/bep/clocks v0.5.0 + github.com/bep/debounce v1.2.0 + github.com/bep/gitmap v1.9.0 + github.com/bep/goat v0.5.0 + github.com/bep/godartsass/v2 v2.5.0 + github.com/bep/golibsass v1.2.0 + github.com/bep/golocales v0.2.0 + github.com/bep/goportabletext v0.2.0 + github.com/bep/helpers v0.12.0 + github.com/bep/imagemeta v0.17.2 + github.com/bep/lazycache v0.8.1 + github.com/bep/logg v0.4.0 + github.com/bep/mclib v1.20401.20400 + github.com/bep/overlayfs v0.11.0 + github.com/bep/simplecobra v0.7.0 + github.com/bep/textandbinarywriter v0.1.0 + github.com/bep/tmc v0.6.0 + github.com/bits-and-blooms/bitset v1.24.5 + github.com/cespare/xxhash/v2 v2.3.0 + github.com/clbanning/mxj/v2 v2.7.0 + github.com/dustin/go-humanize v1.0.1 + github.com/evanw/esbuild v0.28.1 + github.com/fatih/color v1.18.0 + github.com/fortytw2/leaktest v1.3.0 + github.com/frankban/quicktest v1.14.6 + github.com/fsnotify/fsnotify v1.9.0 + github.com/getkin/kin-openapi v0.140.0 + github.com/gobuffalo/flect v1.0.3 + github.com/gobwas/glob v0.2.3 + github.com/goccy/go-yaml v1.19.2 + github.com/gohugoio/gift v0.2.0 + github.com/gohugoio/go-i18n/v2 v2.1.3-0.20251018145728-cfcc22d823c6 + github.com/gohugoio/go-radix v1.2.0 + github.com/gohugoio/hashstructure v0.6.0 + github.com/gohugoio/httpcache v0.8.0 + github.com/gohugoio/hugo-goldmark-extensions/extras v0.7.0 + github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.5.0 + github.com/google/go-cmp v0.7.0 + github.com/gorilla/websocket v1.5.3 + github.com/hairyhenderson/go-codeowners v0.7.0 + github.com/jdkato/prose v1.2.1 + github.com/kyokomi/emoji/v2 v2.2.13 + github.com/magefile/mage v1.17.2 + github.com/makeworld-the-better-one/dither/v2 v2.4.0 + github.com/marekm4/color-extractor v1.2.1 + github.com/mattn/go-isatty v0.0.22 + github.com/microcosm-cc/bluemonday v1.0.27 + github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c + github.com/muesli/smartcrop v0.3.0 + github.com/niklasfasching/go-org v1.9.1 + github.com/olekukonko/tablewriter v1.1.4 + github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 + github.com/pelletier/go-toml/v2 v2.4.3 + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c + github.com/rogpeppe/go-internal v1.15.0 + github.com/spf13/afero v1.15.0 + github.com/spf13/cast v1.10.0 + github.com/spf13/cobra v1.10.2 + github.com/spf13/fsync v0.10.1 + github.com/spf13/pflag v1.0.10 + github.com/tdewolff/minify/v2 v2.24.13 + github.com/tdewolff/parse/v2 v2.8.12 + github.com/tetratelabs/wazero v1.12.0 + github.com/yuin/goldmark v1.8.2 + github.com/yuin/goldmark-emoji v1.0.6 + go.uber.org/automaxprocs v1.5.3 + gocloud.dev v0.45.0 + golang.org/x/image v0.43.0 + golang.org/x/mod v0.37.0 + golang.org/x/net v0.56.0 + golang.org/x/sync v0.21.0 + golang.org/x/text v0.38.0 + golang.org/x/tools v0.47.0 + google.golang.org/api v0.276.0 + rsc.io/qr v0.2.0 +) + +require ( + cel.dev/expr v0.25.1 // indirect + cloud.google.com/go v0.123.0 // indirect + cloud.google.com/go/auth v0.20.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect + cloud.google.com/go/compute/metadata v0.9.0 // indirect + cloud.google.com/go/iam v1.5.3 // indirect + cloud.google.com/go/monitoring v1.24.3 // indirect + cloud.google.com/go/storage v1.57.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.3 // indirect + github.com/Azure/go-autorest v14.2.0+incompatible // indirect + github.com/Azure/go-autorest/autorest/to v0.4.1 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 // indirect + github.com/JohannesKaufmann/dom v0.3.1 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect + github.com/aws/aws-sdk-go-v2/config v1.32.2 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.19.2 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 // indirect + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.20.12 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.22 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.22 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 // indirect + github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.2 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.5 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.10 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.41.2 // indirect + github.com/aws/smithy-go v1.25.0 // indirect + github.com/aymerick/douceur v0.2.0 // indirect + github.com/clipperhouse/displaywidth v0.10.0 // indirect + github.com/clipperhouse/uax29/v2 v2.6.0 // indirect + github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect + github.com/dlclark/regexp2 v1.12.0 // indirect + github.com/dlclark/regexp2/v2 v2.2.1 // indirect + github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect + github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-jose/go-jose/v4 v4.1.4 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/jsonpointer v0.22.5 // indirect + github.com/go-openapi/swag/jsonname v0.25.5 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect + github.com/google/s2a-go v0.1.9 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/google/wire v0.7.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect + github.com/googleapis/gax-go/v2 v2.21.0 // indirect + github.com/gorilla/css v1.0.1 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-runewidth v0.0.19 // indirect + github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect + github.com/oasdiff/yaml v0.1.0 // indirect + github.com/oasdiff/yaml3 v0.0.13 // indirect + github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect + github.com/olekukonko/errors v1.2.0 // indirect + github.com/olekukonko/ll v0.1.6 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect + github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect + go.opentelemetry.io/otel v1.43.0 // indirect + go.opentelemetry.io/otel/metric v1.43.0 // indirect + go.opentelemetry.io/otel/sdk v1.43.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect + go.opentelemetry.io/otel/trace v1.43.0 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.53.0 // indirect + golang.org/x/oauth2 v0.36.0 // indirect + golang.org/x/sys v0.46.0 // indirect + golang.org/x/time v0.15.0 // indirect + golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect + google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect + google.golang.org/grpc v1.80.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect + howett.net/plist v1.0.1 // indirect + software.sslmate.com/src/go-pkcs12 v0.7.0 // indirect +) + +go 1.26.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c3714f7 --- /dev/null +++ b/go.sum @@ -0,0 +1,880 @@ +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= +cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= +cloud.google.com/go/auth v0.20.0 h1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA= +cloud.google.com/go/auth v0.20.0/go.mod h1:942/yi/itH1SsmpyrbnTMDgGfdy2BUqIKyd0cyYLc5Q= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= +cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= +cloud.google.com/go/logging v1.13.2 h1:qqlHCBvieJT9Cdq4QqYx1KPadCQ2noD4FK02eNqHAjA= +cloud.google.com/go/logging v1.13.2/go.mod h1:zaybliM3yun1J8mU2dVQ1/qDzjbOqEijZCn6hSBtKak= +cloud.google.com/go/longrunning v0.8.0 h1:LiKK77J3bx5gDLi4SMViHixjD2ohlkwBi+mKA7EhfW8= +cloud.google.com/go/longrunning v0.8.0/go.mod h1:UmErU2Onzi+fKDg2gR7dusz11Pe26aknR4kHmJJqIfk= +cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE= +cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.57.2 h1:sVlym3cHGYhrp6XZKkKb+92I1V42ks2qKKpB0CF5Mb4= +cloud.google.com/go/storage v1.57.2/go.mod h1:n5ijg4yiRXXpCu0sJTD6k+eMf7GRrJmPyr9YxLXGHOk= +cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U= +cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1 h1:/Zt+cDPnpC3OVDm/JKLOs7M2DKmLRIIp3XIx9pHHiig= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1/go.mod h1:Ng3urmn6dYe8gnbCMoHHVl5APYz2txho3koEkV2o2HA= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.3 h1:ZJJNFaQ86GVKQ9ehwqyAFE6pIfyicpuJ8IkVaPBc6/4= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.3/go.mod h1:URuDvhmATVKqHBH9/0nOiNKk0+YcwfQ3WkK5PqHKxc8= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest/to v0.4.1 h1:CxNHBqdzTr7rLtdrtb5CMjJcDut+WNGCVv7OmS5+lTc= +github.com/Azure/go-autorest/autorest/to v0.4.1/go.mod h1:EtaofgU4zmtvn1zT2ARsjRFdq9vXx0YWtmElwL+GZ9M= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= +github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69 h1:+tu3HOoMXB7RXEINRVIpxJCT+KdYiI7LAEAUrOw3dIU= +github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69/go.mod h1:L1AbZdiDllfyYH5l5OkAaZtk7VkWe89bPJFmnDBNHxg= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 h1:DHa2U07rk8syqvCge0QIGMCE1WxGj9njT44GH7zNJLQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 h1:lhhYARPUu3LmHysQ/igznQphfzynnqI3D75oUyw1HXk= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0/go.mod h1:l9rva3ApbBpEJxSNYnwT9N4CDLrWgtq3u8736C5hyJw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0 h1:xfK3bbi6F2RDtaZFtUdKO3osOBIhNb+xTs8lFW6yx9o= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 h1:s0WlVbf9qpvkh1c/uDAPElam0WrL7fHRIidgZJ7UqZI= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc= +github.com/JohannesKaufmann/dom v0.3.1 h1:J16l9JAHWgkFPR3VIPbQ1gvS0cWab6laK1q7PFL3qh0= +github.com/JohannesKaufmann/dom v0.3.1/go.mod h1:BZPkf8ZeYrBgABjwJn9iiKt8aiCtkxpHkevms+Yp2DE= +github.com/JohannesKaufmann/html-to-markdown/v2 v2.5.2 h1:XFJZFWESIWlUEHHjzBuv8RvrtCWnSGlimEX17ysSDb8= +github.com/JohannesKaufmann/html-to-markdown/v2 v2.5.2/go.mod h1:BHWO8lJzttJLqwuV8Rb1B3OG2OSzLbssZDI1FRg2eAA= +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/chroma/v2 v2.27.0 h1:FodwmyOBgJULFYmDqibcp9pvfDLWdtPRh9v/r5BXYZs= +github.com/alecthomas/chroma/v2 v2.27.0/go.mod h1:NjJ3ciIgrqBNeIkWZ4e46nseoLDslxU1LmfCoL+wcY8= +github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= +github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/aws/aws-sdk-go-v2 v1.41.6 h1:1AX0AthnBQzMx1vbmir3Y4WsnJgiydmnJjiLu+LvXOg= +github.com/aws/aws-sdk-go-v2 v1.41.6/go.mod h1:dy0UzBIfwSeot4grGvY1AqFWN5zgziMmWGzysDnHFcQ= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI= +github.com/aws/aws-sdk-go-v2/config v1.32.2 h1:4liUsdEpUUPZs5WVapsJLx5NPmQhQdez7nYFcovrytk= +github.com/aws/aws-sdk-go-v2/config v1.32.2/go.mod h1:l0hs06IFz1eCT+jTacU/qZtC33nvcnLADAPL/XyrkZI= +github.com/aws/aws-sdk-go-v2/credentials v1.19.2 h1:qZry8VUyTK4VIo5aEdUcBjPZHL2v4FyQ3QEOaWcFLu4= +github.com/aws/aws-sdk-go-v2/credentials v1.19.2/go.mod h1:YUqm5a1/kBnoK+/NY5WEiMocZihKSo15/tJdmdXnM5g= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 h1:WZVR5DbDgxzA0BJeudId89Kmgy6DIU4ORpxwsVHz0qA= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14/go.mod h1:Dadl9QO0kHgbrH1GRqGiZdYtW5w+IXXaBNCHTIaheM4= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.20.12 h1:Zy6Tme1AA13kX8x3CnkHx5cqdGWGaj/anwOiWGnA0Xo= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.20.12/go.mod h1:ql4uXYKoTM9WUAUSmthY4AtPVrlTBZOvnBJTiCUdPxI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.22 h1:GmLa5Kw1ESqtFpXsx5MmC84QWa/ZrLZvlJGa2y+4kcQ= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.22/go.mod h1:6sW9iWm9DK9YRpRGga/qzrzNLgKpT2cIxb7Vo2eNOp0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.22 h1:dY4kWZiSaXIzxnKlj17nHnBcXXBfac6UlsAx2qL6XrU= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.22/go.mod h1:KIpEUx0JuRZLO7U6cbV204cWAEco2iC3l061IxlwLtI= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 h1:rWyie/PxDRIdhNf4DzRk0lvjVOqFJuNnO8WwaIRVxzQ= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22/go.mod h1:zd/JsJ4P7oGfUhXn1VyLqaRZwPmZwg44Jf2dS84Dm3Y= +github.com/aws/aws-sdk-go-v2/service/cloudfront v1.61.1 h1:LSv6jOIn/yEsGLeL4TLggsLA+I+XbuZ8sKmUIEWKrzI= +github.com/aws/aws-sdk-go-v2/service/cloudfront v1.61.1/go.mod h1:XUduecWr236DyG8nZwJMewFbS4QcL8NZHxohdYDoPhM= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 h1:JRaIgADQS/U6uXDqlPiefP32yXTda7Kqfx+LgspooZM= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13/go.mod h1:CEuVn5WqOMilYl+tbccq8+N2ieCy0gVn3OtRb0vBNNM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 h1:ZlvrNcHSFFWURB8avufQq9gFsheUgjVD9536obIknfM= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21/go.mod h1:cv3TNhVrssKR0O/xxLJVRfd2oazSnZnkUeTf6ctUwfQ= +github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 h1:HwxWTbTrIHm5qY+CAEur0s/figc3qwvLWsNkF4RPToo= +github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3/go.mod h1:uoA43SdFwacedBfSgfFSjjCvYe8aYBS7EnU5GZ/YKMM= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.2 h1:MxMBdKTYBjPQChlJhi4qlEueqB1p1KcbTEa7tD5aqPs= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.2/go.mod h1:iS6EPmNeqCsGo+xQmXv0jIMjyYtQfnwg36zl2FwEouk= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.5 h1:ksUT5KtgpZd3SAiFJNJ0AFEJVva3gjBmN7eXUZjzUwQ= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.5/go.mod h1:av+ArJpoYf3pgyrj6tcehSFW+y9/QvAY8kMooR9bZCw= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.10 h1:GtsxyiF3Nd3JahRBJbxLCCdYW9ltGQYrFWg8XdkGDd8= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.10/go.mod h1:/j67Z5XBVDx8nZVp9EuFM9/BS5dvBznbqILGuu73hug= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.2 h1:a5UTtD4mHBU3t0o6aHQZFJTNKVfxFWfPX7J0Lr7G+uY= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.2/go.mod h1:6TxbXoDSgBQ225Qd8Q+MbxUxUh6TtNKwbRt/EPS9xso= +github.com/aws/smithy-go v1.25.0 h1:Sz/XJ64rwuiKtB6j98nDIPyYrV1nVNJ4YU74gttcl5U= +github.com/aws/smithy-go v1.25.0/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/bep/clocks v0.5.0 h1:hhvKVGLPQWRVsBP/UB7ErrHYIO42gINVbvqxvYTPVps= +github.com/bep/clocks v0.5.0/go.mod h1:SUq3q+OOq41y2lRQqH5fsOoxN8GbxSiT6jvoVVLCVhU= +github.com/bep/debounce v1.2.0 h1:wXds8Kq8qRfwAOpAxHrJDbCXgC5aHSzgQb/0gKsHQqo= +github.com/bep/debounce v1.2.0/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= +github.com/bep/gitmap v1.9.0 h1:2pyb1ex+cdwF6c4tsrhEgEKfyNfxE34d5K+s2sa9byc= +github.com/bep/gitmap v1.9.0/go.mod h1:Juq6e1qqCRvc1W7nzgadPGI9IGV13ZncEebg5atj4Vo= +github.com/bep/goat v0.5.0 h1:S8jLXHCVy/EHIoCY+btKkmcxcXFd34a0Q63/0D4TKeA= +github.com/bep/goat v0.5.0/go.mod h1:Md9x7gRxiWKs85yHlVTvHQw9rg86Bm+Y4SuYE8CTH7c= +github.com/bep/godartsass/v2 v2.5.0 h1:tKRvwVdyjCIr48qgtLa4gHEdtRkPF8H1OeEhJAEv7xg= +github.com/bep/godartsass/v2 v2.5.0/go.mod h1:rjsi1YSXAl/UbsGL85RLDEjRKdIKUlMQHr6ChUNYOFU= +github.com/bep/golibsass v1.2.0 h1:nyZUkKP/0psr8nT6GR2cnmt99xS93Ji82ZD9AgOK6VI= +github.com/bep/golibsass v1.2.0/go.mod h1:DL87K8Un/+pWUS75ggYv41bliGiolxzDKWJAq3eJ1MA= +github.com/bep/golocales v0.2.0 h1:4H1H5UPw3ainpj5zykeEfiMRQngyaIC/t+I4Dvn+fvE= +github.com/bep/golocales v0.2.0/go.mod h1:Hl78nje8mNL3LzLeJvYN9NsIZgyFJGrGfvgO9r1+mwE= +github.com/bep/goportabletext v0.2.0 h1:CZ9f8jADBWqHwBymQiJJPCTSV/tHSA+PYzlUf86Yze0= +github.com/bep/goportabletext v0.2.0/go.mod h1:xDeA5+qcgKzJq6Q6XjAiBKtxLD3Yn7f6XP4joD3J3qU= +github.com/bep/helpers v0.12.0 h1:tD6V2DQW0B+FUynF2etR/106S/TO9akm+vA/Hk24GxY= +github.com/bep/helpers v0.12.0/go.mod h1:PfE7MGdA8sSQ19nyDh4tYbs5rAlStlJaDI21f/fnNps= +github.com/bep/imagemeta v0.17.2 h1:fDyXM1eAqCfBeqGLqS6UsN4OfuLM0cdu70KuLCehjOg= +github.com/bep/imagemeta v0.17.2/go.mod h1:+Hlp195TfZpzsqCxtDKTG6eWdyz2+F2V/oCYfr3CZKA= +github.com/bep/lazycache v0.8.1 h1:ko6ASLjkPxyV5DMWoNNZ8B2M0weyjqXX8IZkjBoBtvg= +github.com/bep/lazycache v0.8.1/go.mod h1:pbEiFsZoq7cLXvrTll0AHOPEurB1aGGxx4jKjOtlx9w= +github.com/bep/logg v0.4.0 h1:luAo5mO4ZkhA5M1iDVDqDqnBBnlHjmtZF6VAyTp+nCQ= +github.com/bep/logg v0.4.0/go.mod h1:Ccp9yP3wbR1mm++Kpxet91hAZBEQgmWgFgnXX3GkIV0= +github.com/bep/mclib v1.20401.20400 h1:silTOMNlNI7yHBb+HxEE0THIVFVWo/0I4SCH69FxtmU= +github.com/bep/mclib v1.20401.20400/go.mod h1:v5Hh3EIinPn7epigP28uf9JCkZlYzBS2vEOPe2wrHzM= +github.com/bep/overlayfs v0.11.0 h1:aymHDGC0CHpvn0XvTfgpK6skCp16oMi+tdUF32l6pPs= +github.com/bep/overlayfs v0.11.0/go.mod h1:L+ggdoKm+Y7Xb4a1osd+/LOPG4qsY62snqRqJH5Mspc= +github.com/bep/simplecobra v0.7.0 h1:kG8ZPwEc1o96hlIVGXcrrvwC8RornBqvMD3+pS0Z7y0= +github.com/bep/simplecobra v0.7.0/go.mod h1:PDXvBWH1ZMX05DRQ25ub/C6kKUuq+jROPgjbVz8wO1g= +github.com/bep/textandbinarywriter v0.1.0 h1:KXmXsRN2Uhwhm1G3e/snM8+5SPQBJrCEpIosdIBR3po= +github.com/bep/textandbinarywriter v0.1.0/go.mod h1:dAcHveajlWWU7PXhp6Dn4PIAYDg2H13Huif9xMS2w8w= +github.com/bep/tmc v0.6.0 h1:5zWy4L+3gS+Kk8czzLC4g7ETaC3wkX9ZtTRdAdL8V4s= +github.com/bep/tmc v0.6.0/go.mod h1:SNHxc3o2WSNMAYqJcAO0rxFY+pbhZzMwjIHe5xaAue0= +github.com/bits-and-blooms/bitset v1.24.5 h1:654xBVHc23gJMAgOTkPNoCVfiRxuIOAUnAZFtopqJ4w= +github.com/bits-and-blooms/bitset v1.24.5/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= +github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/clipperhouse/displaywidth v0.10.0 h1:GhBG8WuerxjFQQYeuZAeVTuyxuX+UraiZGD4HJQ3Y8g= +github.com/clipperhouse/displaywidth v0.10.0/go.mod h1:XqJajYsaiEwkxOj4bowCTMcT1SgvHo9flfF3jQasdbs= +github.com/clipperhouse/uax29/v2 v2.6.0 h1:z0cDbUV+aPASdFb2/ndFnS9ts/WNXgTNNGFoKXuhpos= +github.com/clipperhouse/uax29/v2 v2.6.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= +github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8= +github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dlclark/regexp2/v2 v2.2.1 h1:mf4KkFUj0gJuarK8P+LgiS+Lit7m9N1yAwEfPbee7R0= +github.com/dlclark/regexp2/v2 v2.2.1/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= +github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= +github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g= +github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4= +github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= +github.com/evanw/esbuild v0.28.1 h1:ds+yuRyUaZGx++GR56CrCeuXh8PVhVM4xq8v7PNELFc= +github.com/evanw/esbuild v0.28.1/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/getkin/kin-openapi v0.140.0 h1:JFn675aXRFjyiZKa/BFWploGldQlI0gobp4J5k0EZ2g= +github.com/getkin/kin-openapi v0.140.0/go.mod h1:lISrB64F0CPcuDJ3LdtPTMJBY8VENjR9wJBdrcT6J3g= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= +github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA= +github.com/go-openapi/jsonpointer v0.22.5/go.mod h1:gyUR3sCvGSWchA2sUBJGluYMbe1zazrYWIkWPjjMUY0= +github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo= +github.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU= +github.com/go-openapi/testify/v2 v2.4.0 h1:8nsPrHVCWkQ4p8h1EsRVymA2XABB4OT40gcvAu+voFM= +github.com/go-openapi/testify/v2 v2.4.0/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= +github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= +github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= +github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4= +github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= +github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/gohugoio/gift v0.2.0 h1:vA31pP0rTVmBxBrhpY3WEt+4zM4g+1sDqYeemwsYeqc= +github.com/gohugoio/gift v0.2.0/go.mod h1:1Mrm5CjF33KpD749Dwj+UAjWZ3LC6cBXGuTMa5XwoP4= +github.com/gohugoio/go-i18n/v2 v2.1.3-0.20251018145728-cfcc22d823c6 h1:pxlAea9eRwuAnt/zKbGqlFO2ZszpIe24YpOVLf+N+4I= +github.com/gohugoio/go-i18n/v2 v2.1.3-0.20251018145728-cfcc22d823c6/go.mod h1:m5hu1im5Qc7LDycVLvee6MPobJiRLBYHklypFJR0/aE= +github.com/gohugoio/go-radix v1.2.0 h1:D5GTk8jIoeXirBSc2P4E4NdHKDrenk9k9N0ctU5Yrhg= +github.com/gohugoio/go-radix v1.2.0/go.mod h1:k6vDa0ebpbpgtzSj9lPGJcA4AZwJ9xUNObUy2vczPFM= +github.com/gohugoio/hashstructure v0.6.0 h1:7wMB/2CfXoThFYhdWRGv3u3rUM761Cq29CxUW+NltUg= +github.com/gohugoio/hashstructure v0.6.0/go.mod h1:lapVLk9XidheHG1IQ4ZSbyYrXcaILU1ZEP/+vno5rBQ= +github.com/gohugoio/httpcache v0.8.0 h1:hNdsmGSELztetYCsPVgjA960zSa4dfEqqF/SficorCU= +github.com/gohugoio/httpcache v0.8.0/go.mod h1:fMlPrdY/vVJhAriLZnrF5QpN3BNAcoBClgAyQd+lGFI= +github.com/gohugoio/hugo-goldmark-extensions/extras v0.7.0 h1:I/n6v7VImJ3aISLnn73JAHXyjcQsMVvbguQPTk9Ehus= +github.com/gohugoio/hugo-goldmark-extensions/extras v0.7.0/go.mod h1:9LJNfKWFmhEJ7HW0in5znezMwH+FYMBIhNZ3VWtRcRs= +github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.5.0 h1:p13Q0DBCrBRpJGtbtlgkYNCs4TnIlZJh8vHgnAiofrI= +github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.5.0/go.mod h1:ob9PCHy/ocsQhTz68uxhyInaYCbbVNpOOrJkIoSeD+8= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-replayers/grpcreplay v1.3.0 h1:1Keyy0m1sIpqstQmgz307zhiJ1pV4uIlFds5weTmxbo= +github.com/google/go-replayers/grpcreplay v1.3.0/go.mod h1:v6NgKtkijC0d3e3RW8il6Sy5sqRVUwoQa4mHOGEy8DI= +github.com/google/go-replayers/httpreplay v1.2.0 h1:VM1wEyyjaoU53BwrOnaf9VhAyQQEEioJvFYxYcLRKzk= +github.com/google/go-replayers/httpreplay v1.2.0/go.mod h1:WahEFFZZ7a1P4VM1qEeHy+tME4bwyqPcwWbNlUI1Mcg= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4= +github.com/google/wire v0.7.0/go.mod h1:n6YbUQD9cPKTnHXEBN2DXlOp/mVADhVErcMFb0v3J18= +github.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8= +github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.21.0 h1:h45NjjzEO3faG9Lg/cFrBh2PgegVVgzqKzuZl/wMbiI= +github.com/googleapis/gax-go/v2 v2.21.0/go.mod h1:But/NJU6TnZsrLai/xBAQLLz+Hc7fHZJt/hsCz3Fih4= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= +github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hairyhenderson/go-codeowners v0.7.0 h1:s0W4wF8bdsBEjTWzwzSlsatSthWtTAF2xLgo4a4RwAo= +github.com/hairyhenderson/go-codeowners v0.7.0/go.mod h1:wUlNgQ3QjqC4z8DnM5nnCYVq/icpqXJyJOukKx5U8/Q= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jdkato/prose v1.2.1 h1:Fp3UnJmLVISmlc57BgKUzdjr0lOtjqTZicL3PaYy6cU= +github.com/jdkato/prose v1.2.1/go.mod h1:AiRHgVagnEx2JbQRQowVBKjG0bcs/vtkGCH1dYAL1rA= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= +github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/kyokomi/emoji/v2 v2.2.13 h1:GhTfQa67venUUvmleTNFnb+bi7S3aocF7ZCXU9fSO7U= +github.com/kyokomi/emoji/v2 v2.2.13/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE= +github.com/magefile/mage v1.17.2 h1:fyXVu1eadI8Ap1HCCNgEhJ5McIWiYhLR8uol64ZZc40= +github.com/magefile/mage v1.17.2/go.mod h1:Yj51kqllmsgFpvvSzgrZPK9WtluG3kUhFaBUVLo4feA= +github.com/makeworld-the-better-one/dither/v2 v2.4.0 h1:Az/dYXiTcwcRSe59Hzw4RI1rSnAZns+1msaCXetrMFE= +github.com/makeworld-the-better-one/dither/v2 v2.4.0/go.mod h1:VBtN8DXO7SNtyGmLiGA7IsFeKrBkQPze1/iAeM95arc= +github.com/marekm4/color-extractor v1.2.1 h1:3Zb2tQsn6bITZ8MBVhc33Qn1k5/SEuZ18mrXGUqIwn0= +github.com/marekm4/color-extractor v1.2.1/go.mod h1:90VjmiHI6M8ez9eYUaXLdcKnS+BAOp7w+NpwBdkJmpA= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= +github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= +github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= +github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= +github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/montanaflynn/stats v0.6.3/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/muesli/smartcrop v0.3.0 h1:JTlSkmxWg/oQ1TcLDoypuirdE8Y/jzNirQeLkxpA6Oc= +github.com/muesli/smartcrop v0.3.0/go.mod h1:i2fCI/UorTfgEpPPLWiFBv4pye+YAG78RwcQLUkocpI= +github.com/neurosnap/sentences v1.0.6/go.mod h1:pg1IapvYpWCJJm/Etxeh0+gtMf1rI1STY9S7eUCPbDc= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/niklasfasching/go-org v1.9.1 h1:/3s4uTPOF06pImGa2Yvlp24yKXZoTYM+nsIlMzfpg/0= +github.com/niklasfasching/go-org v1.9.1/go.mod h1:ZAGFFkWvUQcpazmi/8nHqwvARpr1xpb+Es67oUGX/48= +github.com/oasdiff/yaml v0.1.0 h1:0bqZjfKc/8S9urj4JuwepX41WX9EoA6ifhU3SV06cXg= +github.com/oasdiff/yaml v0.1.0/go.mod h1:kOlRmMdL2X3vucLCEQO5u61SU22RysnfXvcttrZA1O0= +github.com/oasdiff/yaml3 v0.0.13 h1:06svmvOHOVBqF81+sY2EUScvUI/iS/vl2VIeUUxZQwg= +github.com/oasdiff/yaml3 v0.0.13/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= +github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc= +github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0= +github.com/olekukonko/errors v1.2.0 h1:10Zcn4GeV59t/EGqJc8fUjtFT/FuUh5bTMzZ1XwmCRo= +github.com/olekukonko/errors v1.2.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= +github.com/olekukonko/ll v0.1.6 h1:lGVTHO+Qc4Qm+fce/2h2m5y9LvqaW+DCN7xW9hsU3uA= +github.com/olekukonko/ll v0.1.6/go.mod h1:NVUmjBb/aCtUpjKk75BhWrOlARz3dqsM+OtszpY4o88= +github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVWj1drm6I= +github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY= +github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= +github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= +github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY= +github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.15.0 h1:D0RCU5rMAp+SpgkiNdrjfJ+LX4J1M32V2NeCY7EJ6hc= +github.com/rogpeppe/go-internal v1.15.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc= +github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= +github.com/sebdah/goldie/v2 v2.8.0 h1:dZb9wR8q5++oplmEiJT+U/5KyotVD+HNGCAc5gNr8rc= +github.com/sebdah/goldie/v2 v2.8.0/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= +github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= +github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/shogo82148/go-shuffle v0.0.0-20180218125048-27e6095f230d/go.mod h1:2htx6lmL0NGLHlO8ZCf+lQBGBHIbEujyywxJArf+2Yc= +github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/fsync v0.10.1 h1:JRnB7G72b+gIBaBcpn5ibJSd7ww1iEahXSX2B8G6dSE= +github.com/spf13/fsync v0.10.1/go.mod h1:y+B41vYq5i6Boa3Z+BVoPbDeOvxVkNU5OBXhoT8i4TQ= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= +github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tdewolff/minify/v2 v2.24.13 h1:xrcF7gKDnUszseEY9WX9mUlZII2v2Go/QAcAwRASw58= +github.com/tdewolff/minify/v2 v2.24.13/go.mod h1:emvwoYeIl8bfAKqRU5ww95LX9Gpggpqv/naal9a8Yq0= +github.com/tdewolff/parse/v2 v2.8.12 h1:5BBjfaCv482v3nltlS0u6wH1xJaxjR6ofDrWttNvROg= +github.com/tdewolff/parse/v2 v2.8.12/go.mod h1:Hwlni2tiVNKyzR1o6nUs4FOF07URA+JLBLd6dlIXYqo= +github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= +github.com/tdewolff/test v1.0.12 h1:7F21DqIajswxuche0geHdrUZRCWE4oko4b7bcmkkrxk= +github.com/tdewolff/test v1.0.12/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= +github.com/tetratelabs/wazero v1.12.0 h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU= +github.com/tetratelabs/wazero v1.12.0/go.mod h1:LvKtzl2RqO4gyF27BiXU+nKAjcV8f38U+kP/q2vgxh0= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE= +github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +github.com/yuin/goldmark-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9HTHs= +github.com/yuin/goldmark-emoji v1.0.6/go.mod h1:ukxJDKFpdFb5x0a5HqbdlcKtebh086iJpI31LTKmWuA= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE= +go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0 h1:6VjV6Et+1Hd2iLZEPtdV7vie80Yyqf7oikJLjQ/myi0= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0/go.mod h1:u8hcp8ji5gaM/RfcOo8z9NMnf1pVLfVY7lBY2VOGuUU= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= +go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +gocloud.dev v0.45.0 h1:WknIK8IbRdmynDvara3Q7G6wQhmEiOGwpgJufbM39sY= +gocloud.dev v0.45.0/go.mod h1:0kXKmkCLG6d31N7NyLZWzt7jDSQura9zD/mWgiB6THI= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.43.0 h1:FLxcP4ec2350nTfOC8ysKtqYSIFbk/QGjw1ZHNP4tsY= +golang.org/x/image v0.43.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= +golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.276.0 h1:nVArUtfLEihtW+b0DdcqRGK1xoEm2+ltAihyztq7MKY= +google.golang.org/api v0.276.0/go.mod h1:Fnag/EWUPIcJXuIkP1pjoTgS5vdxlk3eeemL7Do6bvw= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0= +google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I= +google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 h1:41r6JMbpzBMen0R/4TZeeAmGXSJC7DftGINUodzTkPI= +google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= +google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/neurosnap/sentences.v1 v1.0.6/go.mod h1:YlK+SN+fLQZj+kY3r8DkGDhDr91+S3JmTb5LSxFRQo0= +gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM= +howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY= +rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +software.sslmate.com/src/go-pkcs12 v0.7.0 h1:Db8W44cB54TWD7stUFFSWxdfpdn6fZVcDl0w3R4RVM0= +software.sslmate.com/src/go-pkcs12 v0.7.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= diff --git a/helpers/content.go b/helpers/content.go new file mode 100644 index 0000000..6edcf88 --- /dev/null +++ b/helpers/content.go @@ -0,0 +1,185 @@ +// 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 helpers implements general utility functions that work with +// and on content. The helper functions defined here lay down the +// foundation of how Hugo works with files and filepaths, and perform +// string operations on content. +package helpers + +import ( + "bytes" + "html/template" + "strings" + "unicode" + + "github.com/gohugoio/hugo/common/hexec" + "github.com/gohugoio/hugo/common/loggers" + "github.com/gohugoio/hugo/media" + + "github.com/spf13/afero" + + "github.com/gohugoio/hugo/markup/converter" + + "github.com/gohugoio/hugo/markup" + + "github.com/gohugoio/hugo/config" +) + +// ContentSpec provides functionality to render markdown content. +type ContentSpec struct { + Converters markup.ConverterProvider + anchorNameSanitizer converter.AnchorNameSanitizer + Cfg config.AllProvider +} + +// NewContentSpec returns a ContentSpec initialized +// with the appropriate fields from the given config.Provider. +func NewContentSpec(cfg config.AllProvider, logger loggers.Logger, contentFs afero.Fs, ex *hexec.Exec) (*ContentSpec, error) { + spec := &ContentSpec{ + Cfg: cfg, + } + + converterProvider, err := markup.NewConverterProvider(converter.ProviderConfig{ + Conf: cfg, + ContentFs: contentFs, + Logger: logger, + Exec: ex, + }) + if err != nil { + return nil, err + } + + spec.Converters = converterProvider + p := converterProvider.Get("markdown") + conv, err := p.New(converter.DocumentContext{}) + if err != nil { + return nil, err + } + if as, ok := conv.(converter.AnchorNameSanitizer); ok { + spec.anchorNameSanitizer = as + } else { + // Use Goldmark's sanitizer + p := converterProvider.Get("goldmark") + conv, err := p.New(converter.DocumentContext{}) + if err != nil { + return nil, err + } + spec.anchorNameSanitizer = conv.(converter.AnchorNameSanitizer) + } + + return spec, nil +} + +// stripEmptyNav strips out empty