mirror of
https://github.com/gohugoio/hugo.git
synced 2026-09-03 12:12:37 +00:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ace564e362 | |||
| bf7ee8a91a | |||
| d912491f2d | |||
| 857374e693 | |||
| c27639b9a0 | |||
| ceb486f98c | |||
| 5afc89f2bf | |||
| c1eac616d5 | |||
| 3c8256a137 | |||
| 19d76ae96b | |||
| 11ecea6106 | |||
| da98724bc8 | |||
| 4d470bb732 | |||
| 0ff8e13c1c | |||
| ffd37d4f75 | |||
| b4b65245b2 | |||
| 87886f40d1 | |||
| 67caf50698 | |||
| 7917961d59 | |||
| 80ecb95895 | |||
| 58e09cc6c7 | |||
| 635cc346ce | |||
| 12646750aa | |||
| 79639c981c | |||
| 9b313cec1b | |||
| b74b8d6478 | |||
| 793e38f5ce | |||
| 7f698c8934 | |||
| fa0e16f4c7 | |||
| 12e4c4d5dc | |||
| bac03f4076 | |||
| cc14c6a52c | |||
| e3308a0bbc | |||
| 019299b0b0 | |||
| 23ed087c4e | |||
| a018259bcf | |||
| ad5e04daa4 | |||
| 5b4bfc2db7 | |||
| 92f55f1121 |
@@ -104,3 +104,10 @@ jobs:
|
||||
- name: Build tags
|
||||
run: |
|
||||
go install -tags extended,nodeploy
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
name: Build for dragonfly
|
||||
run: |
|
||||
go install
|
||||
env:
|
||||
GOARCH: amd64
|
||||
GOOS: dragonfly
|
||||
|
||||
@@ -1,243 +1,290 @@
|
||||
[bep]: https://github.com/spf13
|
||||
[bugs]: https://github.com/gohugoio/hugo/issues?q=is%3Aopen+is%3Aissue+label%3ABug
|
||||
[contributing]: CONTRIBUTING.md
|
||||
[create a proposal]: https://github.com/gohugoio/hugo/issues/new?labels=Proposal%2C+NeedsTriage&template=feature_request.md
|
||||
[documentation repository]: https://github.com/gohugoio/hugoDocs
|
||||
[documentation]: https://gohugo.io/documentation
|
||||
[dragonfly bsd, freebsd, netbsd, and openbsd]: https://gohugo.io/installation/bsd
|
||||
[forum]: https://discourse.gohugo.io
|
||||
[friends]: https://github.com/gohugoio/hugo/graphs/contributors
|
||||
[go]: https://go.dev/
|
||||
[hugo modules]: https://gohugo.io/hugo-modules/
|
||||
[installation]: https://gohugo.io/installation
|
||||
[issue queue]: https://github.com/gohugoio/hugo/issues
|
||||
[linux]: https://gohugo.io/installation/linux
|
||||
[macos]: https://gohugo.io/installation/macos
|
||||
[prebuilt binary]: https://github.com/gohugoio/hugo/releases/latest
|
||||
[requesting help]: https://discourse.gohugo.io/t/requesting-help/9132
|
||||
[spf13]: https://github.com/spf13
|
||||
[static site generator]: https://en.wikipedia.org/wiki/Static_site_generator
|
||||
[support]: https://discourse.gohugo.io
|
||||
[themes]: https://themes.gohugo.io/
|
||||
[twitter]: https://twitter.com/gohugoio
|
||||
[website]: https://gohugo.io
|
||||
[windows]: https://gohugo.io/installation/windows
|
||||
|
||||
<a href="https://gohugo.io/"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/static/images/hugo-logo-wide.svg?sanitize=true" alt="Hugo" width="565"></a>
|
||||
|
||||
A Fast and Flexible Static Site Generator built with love by [bep](https://github.com/bep), [spf13](https://spf13.com/) and [friends](https://github.com/gohugoio/hugo/graphs/contributors) in [Go](https://go.dev/).
|
||||
A fast and flexible static site generator built with love by [bep], [spf13], and [friends] in [Go].
|
||||
|
||||
[Website](https://gohugo.io) |
|
||||
[Forum](https://discourse.gohugo.io) |
|
||||
[Documentation](https://gohugo.io/getting-started/) |
|
||||
[Installation Guide](https://gohugo.io/getting-started/installing/) |
|
||||
[Contribution Guide](CONTRIBUTING.md) |
|
||||
[Twitter](https://twitter.com/gohugoio)
|
||||
---
|
||||
|
||||
[](https://godoc.org/github.com/gohugoio/hugo)
|
||||
[](https://github.com/gohugoio/hugo/actions?query=workflow%3ATest)
|
||||
[](https://goreportcard.com/report/github.com/gohugoio/hugo)
|
||||
|
||||
* [Overview](#overview)
|
||||
* [Banner Sponsors](#banner-sponsors)
|
||||
* [Supported Architectures](#supported-architectures)
|
||||
* [Choose How to Install](#choose-how-to-install)
|
||||
* [Install Hugo as Your Site Generator (Binary Install)](#install-hugo-as-your-site-generator-binary-install)
|
||||
* [Build and Install the Binary from Source (Using the Go toolchain)](#build-and-install-the-binary-from-source-using-the-go-toolchain)
|
||||
* [The Hugo Documentation](#the-hugo-documentation)
|
||||
* [Contributing to Hugo](#contributing-code-to-hugo)
|
||||
* [Dependencies](#dependencies)
|
||||
[Website] | [Installation] | [Documentation] | [Support] | [Contributing] | [Twitter]
|
||||
|
||||
## Overview
|
||||
|
||||
Hugo is a static HTML and CSS website generator written in [Go](https://go.dev/).
|
||||
It is optimized for speed, ease of use, and configurability.
|
||||
Hugo takes a directory with content and templates and renders them into a full HTML website.
|
||||
Hugo is a [static site generator] written in [Go], optimized for speed and designed for flexibility. With its advanced templating system and fast asset pipelines, Hugo renders a complete site in seconds, often less.
|
||||
|
||||
Hugo relies on Markdown files with front matter for metadata, and you can run Hugo from any directory.
|
||||
This works well for shared hosts and other systems where you don’t have a privileged account.
|
||||
Due to its flexible framework, multilingual support, and powerful taxonomy system, Hugo is widely used to create:
|
||||
|
||||
Hugo renders a typical website of moderate size in a fraction of a second.
|
||||
A good rule of thumb is that each piece of content renders in around 1 millisecond.
|
||||
- Corporate, government, nonprofit, education, news, event, and project sites
|
||||
- Documentation sites
|
||||
- Image portfolios
|
||||
- Landing pages
|
||||
- Business, professional, and personal blogs
|
||||
- Resumes and CVs
|
||||
|
||||
Hugo is designed to work well for any kind of website including blogs, tumbles, and docs.
|
||||
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 bundling – transpilation (Sass), tree shaking, minification, source maps, SRI hashing, and PostCSS integration
|
||||
- JavaScript bundling – transpilation (TypeScript, JSX), tree shaking, minification, source maps, and SRI hashing
|
||||
- Image processing – convert, resize, crop, rotate, adjust colors, apply filters, overlay text and images, and extract EXIF data
|
||||
|
||||
And with [Hugo Modules], you can share content, assets, data, translations, themes, templates, and configuration with other projects via public or private Git repositories.
|
||||
|
||||
## Sponsors
|
||||
|
||||
## Banner Sponsors
|
||||
<p> </p>
|
||||
<p float="left">
|
||||
<a href="https://www.linode.com/?utm_campaign=hugosponsor&utm_medium=banner&utm_source=hugogithub" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/linode-logo_standard_light_medium.png" width="200" alt="Linode"></a>
|
||||
<a href="https://cloudcannon.com/hugo-cms/?utm_campaign=HugoSponsorship&utm_source=sponsor&utm_content=gohugo" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/cloudcannon-blue.svg" width="220" alt="CloudCannon"></a>
|
||||
<p> </p>
|
||||
|
||||
## Supported Architectures
|
||||
## Installation
|
||||
|
||||
Currently, we provide pre-built Hugo binaries for Windows, Linux, FreeBSD, NetBSD, DragonFly BSD, OpenBSD, macOS (Darwin), and [Android](https://gist.github.com/bep/a0d8a26cf6b4f8bc992729b8e50b480b) for x64, i386 and ARM architectures.
|
||||
Install Hugo from a [prebuilt binary], package manager, or package repository. Please see the installation instructions for your operating system:
|
||||
|
||||
Hugo may also be compiled from source wherever the Go compiler tool chain can run, e.g. for other operating systems including Plan 9 and Solaris.
|
||||
- [macOS]
|
||||
- [Linux]
|
||||
- [Windows]
|
||||
- [DragonFly BSD, FreeBSD, NetBSD, and OpenBSD]
|
||||
|
||||
**Complete documentation is available at [Hugo Documentation](https://gohugo.io/getting-started/).**
|
||||
## Build from source
|
||||
|
||||
## Choose How to Install
|
||||
Hugo is available in two editions: standard and extended. With the extended edition you can:
|
||||
|
||||
If you want to use Hugo as your site generator, simply install the Hugo binaries.
|
||||
- Encode to the WebP format when processing images. You can decode WebP images with either edition.
|
||||
- Transpile Sass to CSS using the embedded LibSass transpiler. The extended edition is not required to use the Dart Sass transpiler.
|
||||
|
||||
To contribute to the Hugo source code or documentation, you should [fork the Hugo GitHub project](https://github.com/gohugoio/hugo#fork-destination-box) and clone it to your local machine.
|
||||
Prerequisites to build Hugo from source:
|
||||
|
||||
Finally, you can install the Hugo source code with `go`, build the binaries yourself, and run Hugo that way.
|
||||
Building the binaries is an easy task for an experienced `go` getter.
|
||||
- Standard edition: Go 1.19 or later
|
||||
- Extended edition: Go 1.19 or later, and GCC
|
||||
|
||||
### Install Hugo as Your Site Generator (Binary Install)
|
||||
Build the standard edition:
|
||||
|
||||
Use the [installation instructions in the Hugo documentation](https://gohugo.io/getting-started/installing/).
|
||||
|
||||
### Build and Install the Binary from Source (Using the Go toolchain)
|
||||
|
||||
#### Prerequisite Tools
|
||||
|
||||
* [Go (we test it with the last 2 major versions; but note that Hugo 0.95.0 only builds with >= Go 1.18.)](https://golang.org/dl/)
|
||||
|
||||
#### Fetch from GitHub
|
||||
|
||||
To fetch, build and install from the Github source:
|
||||
|
||||
```bash
|
||||
```text
|
||||
go install github.com/gohugoio/hugo@latest
|
||||
```
|
||||
|
||||
If you want to compile with Sass/SCSS support use `-tags extended` and make sure `CGO_ENABLED=1` is set in your go environment. If you don't want to have CGO enabled, you may use the following command to temporarily enable CGO only for hugo compilation:
|
||||
Build the extended edition:
|
||||
|
||||
```bash
|
||||
```text
|
||||
CGO_ENABLED=1 go install -tags extended github.com/gohugoio/hugo@latest
|
||||
```
|
||||
|
||||
## The Hugo Documentation
|
||||
## Documentation
|
||||
|
||||
The Hugo documentation now lives in its own repository, see https://github.com/gohugoio/hugoDocs. But we do keep a version of that documentation as a `git subtree` in this repository. To build the sub folder `/docs` as a Hugo site, you need to clone this repo:
|
||||
Hugo's [documentation] includes installation instructions, a quick start guide, conceptual explanations, reference information, and examples.
|
||||
|
||||
```bash
|
||||
git clone git@github.com:gohugoio/hugo.git
|
||||
```
|
||||
## Contributing code to Hugo
|
||||
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).
|
||||
|
||||
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.
|
||||
|
||||
## 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`).
|
||||
|
||||
## Dependencies
|
||||
|
||||
Hugo stands on the shoulder of many great open source libraries.
|
||||
Hugo stands on the shoulders of great open source libraries. Run `hugo env --logLevel info` to display a list of dependencies.
|
||||
|
||||
If you run `hugo env -v` you will get a complete and up to date list.
|
||||
<details>
|
||||
<summary>See current dependencies</summary>
|
||||
|
||||
In Hugo 0.111.2 that list is, in lexical order:
|
||||
|
||||
```
|
||||
cloud.google.com/go/compute="v1.6.1"
|
||||
cloud.google.com/go/iam="v0.3.0"
|
||||
cloud.google.com/go/storage="v1.22.0"
|
||||
cloud.google.com/go="v0.101.0"
|
||||
github.com/Azure/azure-pipeline-go="v0.2.3"
|
||||
github.com/Azure/azure-storage-blob-go="v0.14.0"
|
||||
github.com/Azure/go-autorest/autorest/adal="v0.9.15"
|
||||
github.com/Azure/go-autorest/autorest/date="v0.3.0"
|
||||
github.com/Azure/go-autorest/autorest="v0.11.20"
|
||||
github.com/Azure/go-autorest/logger="v0.2.1"
|
||||
github.com/Azure/go-autorest/tracing="v0.6.0"
|
||||
```text
|
||||
cloud.google.com/go/compute/metadata="v0.2.3"
|
||||
cloud.google.com/go/iam="v1.1.0"
|
||||
cloud.google.com/go/storage="v1.30.1"
|
||||
cloud.google.com/go="v0.110.2"
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore="v1.6.1"
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity="v1.3.0"
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal="v1.3.0"
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob="v1.0.0"
|
||||
github.com/Azure/go-autorest/autorest/to="v0.4.0"
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go="v1.0.0"
|
||||
github.com/BurntSushi/locker="v0.0.0-20171006230638-a6e239ea1c69"
|
||||
github.com/PuerkitoBio/purell="v1.1.1"
|
||||
github.com/PuerkitoBio/urlesc="v0.0.0-20170810143723-de5bf2ad4578"
|
||||
github.com/alecthomas/chroma/v2="v2.5.0"
|
||||
github.com/alecthomas/chroma/v2="v2.7.0"
|
||||
github.com/armon/go-radix="v1.0.0"
|
||||
github.com/aws/aws-sdk-go-v2/config="v1.7.0"
|
||||
github.com/aws/aws-sdk-go-v2/credentials="v1.4.0"
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds="v1.5.0"
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini="v1.2.2"
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url="v1.3.0"
|
||||
github.com/aws/aws-sdk-go-v2/service/sso="v1.4.0"
|
||||
github.com/aws/aws-sdk-go-v2/service/sts="v1.7.0"
|
||||
github.com/aws/aws-sdk-go-v2="v1.9.0"
|
||||
github.com/aws/aws-sdk-go="v1.43.5"
|
||||
github.com/aws/smithy-go="v1.8.0"
|
||||
github.com/bep/clock="v0.3.0"
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream="v1.4.10"
|
||||
github.com/aws/aws-sdk-go-v2/config="v1.18.27"
|
||||
github.com/aws/aws-sdk-go-v2/credentials="v1.13.26"
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds="v1.13.4"
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager="v1.11.70"
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources="v1.1.34"
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2="v2.4.28"
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini="v1.3.35"
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a="v1.0.26"
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding="v1.9.11"
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum="v1.1.29"
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url="v1.9.28"
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared="v1.14.3"
|
||||
github.com/aws/aws-sdk-go-v2/service/s3="v1.35.0"
|
||||
github.com/aws/aws-sdk-go-v2/service/sso="v1.12.12"
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc="v1.14.12"
|
||||
github.com/aws/aws-sdk-go-v2/service/sts="v1.19.2"
|
||||
github.com/aws/aws-sdk-go-v2="v1.18.1"
|
||||
github.com/aws/aws-sdk-go="v1.44.284"
|
||||
github.com/aws/smithy-go="v1.13.5"
|
||||
github.com/bep/clocks="v0.5.0"
|
||||
github.com/bep/debounce="v1.2.0"
|
||||
github.com/bep/gitmap="v1.1.2"
|
||||
github.com/bep/goat="v0.5.0"
|
||||
github.com/bep/godartsass="v0.16.0"
|
||||
github.com/bep/golibsass="v1.1.0"
|
||||
github.com/bep/godartsass/v2="v2.0.0"
|
||||
github.com/bep/godartsass="v1.2.0"
|
||||
github.com/bep/golibsass="v1.1.1"
|
||||
github.com/bep/gowebp="v0.2.0"
|
||||
github.com/bep/lazycache="v0.2.0"
|
||||
github.com/bep/logg="v0.2.0"
|
||||
github.com/bep/mclib="v1.20400.20402"
|
||||
github.com/bep/overlayfs="v0.6.0"
|
||||
github.com/bep/simplecobra="v0.3.2"
|
||||
github.com/bep/tmc="v0.5.1"
|
||||
github.com/clbanning/mxj/v2="v2.5.7"
|
||||
github.com/cli/safeexec="v1.0.0"
|
||||
github.com/cli/safeexec="v1.0.1"
|
||||
github.com/cpuguy83/go-md2man/v2="v2.0.2"
|
||||
github.com/disintegration/gift="v1.2.1"
|
||||
github.com/dlclark/regexp2="v1.7.0"
|
||||
github.com/dustin/go-humanize="v1.0.0"
|
||||
github.com/evanw/esbuild="v0.17.0"
|
||||
github.com/frankban/quicktest="v1.14.4"
|
||||
github.com/dlclark/regexp2="v1.10.0"
|
||||
github.com/dustin/go-humanize="v1.0.1"
|
||||
github.com/evanw/esbuild="v0.18.5"
|
||||
github.com/fatih/color="v1.15.0"
|
||||
github.com/frankban/quicktest="v1.14.5"
|
||||
github.com/fsnotify/fsnotify="v1.6.0"
|
||||
github.com/getkin/kin-openapi="v0.110.0"
|
||||
github.com/getkin/kin-openapi="v0.118.0"
|
||||
github.com/ghodss/yaml="v1.0.0"
|
||||
github.com/go-openapi/jsonpointer="v0.19.5"
|
||||
github.com/go-openapi/swag="v0.19.5"
|
||||
github.com/gobuffalo/flect="v0.3.0"
|
||||
github.com/go-openapi/jsonpointer="v0.19.6"
|
||||
github.com/go-openapi/swag="v0.22.3"
|
||||
github.com/gobuffalo/flect="v1.0.2"
|
||||
github.com/gobwas/glob="v0.2.3"
|
||||
github.com/gohugoio/go-i18n/v2="v2.1.3-0.20210430103248-4c28c89f8013"
|
||||
github.com/gohugoio/locales="v0.14.0"
|
||||
github.com/gohugoio/localescompressed="v1.0.1"
|
||||
github.com/golang-jwt/jwt/v4="v4.0.0"
|
||||
github.com/golang-jwt/jwt/v4="v4.5.0"
|
||||
github.com/golang/groupcache="v0.0.0-20210331224755-41bb18bfe9da"
|
||||
github.com/golang/protobuf="v1.5.2"
|
||||
github.com/golang/protobuf="v1.5.3"
|
||||
github.com/google/go-cmp="v0.5.9"
|
||||
github.com/google/s2a-go="v0.1.4"
|
||||
github.com/google/uuid="v1.3.0"
|
||||
github.com/google/wire="v0.5.0"
|
||||
github.com/googleapis/gax-go/v2="v2.3.0"
|
||||
github.com/googleapis/go-type-adapters="v1.0.0"
|
||||
github.com/googleapis/enterprise-certificate-proxy="v0.2.5"
|
||||
github.com/googleapis/gax-go/v2="v2.11.0"
|
||||
github.com/gorilla/websocket="v1.5.0"
|
||||
github.com/hairyhenderson/go-codeowners="v0.2.3-0.20201026200250-cdc7c0759690"
|
||||
github.com/hairyhenderson/go-codeowners="v0.3.0"
|
||||
github.com/hashicorp/golang-lru/v2="v2.0.1"
|
||||
github.com/invopop/yaml="v0.1.0"
|
||||
github.com/jdkato/prose="v1.2.1"
|
||||
github.com/jmespath/go-jmespath="v0.4.0"
|
||||
github.com/josharian/intern="v1.0.0"
|
||||
github.com/kr/pretty="v0.3.1"
|
||||
github.com/kr/text="v0.2.0"
|
||||
github.com/kyokomi/emoji/v2="v2.2.11"
|
||||
github.com/mailru/easyjson="v0.0.0-20190626092158-b2ccc519800e"
|
||||
github.com/kylelemons/godebug="v1.1.0"
|
||||
github.com/kyokomi/emoji/v2="v2.2.12"
|
||||
github.com/mailru/easyjson="v0.7.7"
|
||||
github.com/marekm4/color-extractor="v1.2.0"
|
||||
github.com/mattn/go-ieproxy="v0.0.1"
|
||||
github.com/mattn/go-isatty="v0.0.17"
|
||||
github.com/mattn/go-colorable="v0.1.13"
|
||||
github.com/mattn/go-isatty="v0.0.19"
|
||||
github.com/mattn/go-runewidth="v0.0.9"
|
||||
github.com/mitchellh/hashstructure="v1.1.0"
|
||||
github.com/mitchellh/mapstructure="v1.5.0"
|
||||
github.com/mohae/deepcopy="v0.0.0-20170929034955-c48cc78d4826"
|
||||
github.com/muesli/smartcrop="v0.3.0"
|
||||
github.com/niklasfasching/go-org="v1.6.5"
|
||||
github.com/niklasfasching/go-org="v1.7.0"
|
||||
github.com/olekukonko/tablewriter="v0.0.5"
|
||||
github.com/pelletier/go-toml/v2="v2.0.6"
|
||||
github.com/rogpeppe/go-internal="v1.9.0"
|
||||
github.com/pelletier/go-toml/v2="v2.0.8"
|
||||
github.com/perimeterx/marshmallow="v1.1.4"
|
||||
github.com/pkg/browser="v0.0.0-20210911075715-681adbf594b8"
|
||||
github.com/pkg/errors="v0.9.1"
|
||||
github.com/rogpeppe/go-internal="v1.10.1-0.20230508101108-a4f6fabd84c5"
|
||||
github.com/russross/blackfriday/v2="v2.1.0"
|
||||
github.com/rwcarlsen/goexif="v0.0.0-20190401172101-9e8deecbddbd"
|
||||
github.com/sanity-io/litter="v1.5.5"
|
||||
github.com/sass/dart-sass/compiler="1.63.6"
|
||||
github.com/sass/dart-sass/implementation="1.63.6"
|
||||
github.com/sass/dart-sass/protocol="2.1.0"
|
||||
github.com/sass/libsass="3.6.5"
|
||||
github.com/spf13/afero="v1.9.3"
|
||||
github.com/spf13/cast="v1.5.0"
|
||||
github.com/spf13/cobra="v1.6.1"
|
||||
github.com/spf13/afero="v1.9.5"
|
||||
github.com/spf13/cast="v1.5.1"
|
||||
github.com/spf13/cobra="v1.7.0"
|
||||
github.com/spf13/fsync="v0.9.0"
|
||||
github.com/spf13/jwalterweatherman="v1.1.0"
|
||||
github.com/spf13/pflag="v1.0.5"
|
||||
github.com/tdewolff/minify/v2="v2.12.4"
|
||||
github.com/tdewolff/parse/v2="v2.6.5"
|
||||
github.com/tdewolff/minify/v2="v2.12.7"
|
||||
github.com/tdewolff/parse/v2="v2.6.6"
|
||||
github.com/webmproject/libwebp="v1.2.4"
|
||||
github.com/yuin/goldmark="v1.5.4"
|
||||
go.opencensus.io="v0.24.0"
|
||||
go.uber.org/atomic="v1.10.0"
|
||||
gocloud.dev="v0.24.0"
|
||||
golang.org/x/crypto="v0.3.0"
|
||||
golang.org/x/exp="v0.0.0-20221031165847-c99f073a8326"
|
||||
golang.org/x/image="v0.5.0"
|
||||
golang.org/x/net="v0.7.0"
|
||||
golang.org/x/oauth2="v0.2.0"
|
||||
golang.org/x/sync="v0.1.0"
|
||||
golang.org/x/sys="v0.5.0"
|
||||
golang.org/x/text="v0.7.0"
|
||||
golang.org/x/tools="v0.4.0"
|
||||
go.uber.org/atomic="v1.11.0"
|
||||
go.uber.org/automaxprocs="v1.5.2"
|
||||
gocloud.dev="v0.30.0"
|
||||
golang.org/x/crypto="v0.10.0"
|
||||
golang.org/x/exp="v0.0.0-20230321023759-10a507213a29"
|
||||
golang.org/x/image="v0.8.0"
|
||||
golang.org/x/mod="v0.10.0"
|
||||
golang.org/x/net="v0.11.0"
|
||||
golang.org/x/oauth2="v0.9.0"
|
||||
golang.org/x/sync="v0.3.0"
|
||||
golang.org/x/sys="v0.9.0"
|
||||
golang.org/x/text="v0.10.0"
|
||||
golang.org/x/tools="v0.9.3"
|
||||
golang.org/x/xerrors="v0.0.0-20220907171357-04be3eba64a2"
|
||||
google.golang.org/api="v0.76.0"
|
||||
google.golang.org/genproto="v0.0.0-20220426171045-31bebdecfb46"
|
||||
google.golang.org/grpc="v1.46.0"
|
||||
google.golang.org/protobuf="v1.28.1"
|
||||
google.golang.org/api="v0.128.0"
|
||||
google.golang.org/appengine="v1.6.7"
|
||||
google.golang.org/genproto/googleapis/api="v0.0.0-20230530153820-e85fd2cbaebc"
|
||||
google.golang.org/genproto/googleapis/rpc="v0.0.0-20230530153820-e85fd2cbaebc"
|
||||
google.golang.org/genproto="v0.0.0-20230530153820-e85fd2cbaebc"
|
||||
google.golang.org/grpc="v1.56.0"
|
||||
google.golang.org/protobuf="v1.30.0"
|
||||
gopkg.in/yaml.v2="v2.4.0"
|
||||
gopkg.in/yaml.v3="v3.0.1"
|
||||
software.sslmate.com/src/go-pkcs12="v0.2.0"
|
||||
```
|
||||
</details>
|
||||
|
||||
+10
-12
@@ -30,7 +30,7 @@ import (
|
||||
|
||||
"go.uber.org/automaxprocs/maxprocs"
|
||||
|
||||
"github.com/bep/clock"
|
||||
"github.com/bep/clocks"
|
||||
"github.com/bep/lazycache"
|
||||
"github.com/bep/logg"
|
||||
"github.com/bep/overlayfs"
|
||||
@@ -110,12 +110,10 @@ type rootCommand struct {
|
||||
environment string
|
||||
|
||||
// Common build flags.
|
||||
baseURL string
|
||||
gc bool
|
||||
poll string
|
||||
forceSyncStatic bool
|
||||
printPathWarnings bool
|
||||
printUnusedTemplates bool
|
||||
baseURL string
|
||||
gc bool
|
||||
poll string
|
||||
forceSyncStatic bool
|
||||
|
||||
// Profile flags (for debugging of performance problems)
|
||||
cpuprofile string
|
||||
@@ -175,7 +173,7 @@ func (r *rootCommand) ConfigFromConfig(key int32, oldConf *commonConfig) (*commo
|
||||
|
||||
if !configs.Base.C.Clock.IsZero() {
|
||||
// TODO(bep) find a better place for this.
|
||||
htime.Clock = clock.Start(configs.Base.C.Clock)
|
||||
htime.Clock = clocks.Start(configs.Base.C.Clock)
|
||||
}
|
||||
|
||||
return &commonConfig{
|
||||
@@ -285,10 +283,10 @@ func (r *rootCommand) ConfigFromProvider(key int32, cfg config.Provider) (*commo
|
||||
|
||||
if !base.C.Clock.IsZero() {
|
||||
// TODO(bep) find a better place for this.
|
||||
htime.Clock = clock.Start(configs.Base.C.Clock)
|
||||
htime.Clock = clocks.Start(configs.Base.C.Clock)
|
||||
}
|
||||
|
||||
if base.LogPathWarnings {
|
||||
if base.PrintPathWarnings {
|
||||
// Note that we only care about the "dynamic creates" here,
|
||||
// so skip the static fs.
|
||||
fs.PublishDir = hugofs.NewCreateCountingFs(fs.PublishDir)
|
||||
@@ -544,8 +542,8 @@ func applyLocalFlagsBuild(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.Flags().BoolP("noChmod", "", false, "don't sync permission mode of files")
|
||||
cmd.Flags().BoolP("noBuildLock", "", false, "don't create .hugo_build.lock file")
|
||||
cmd.Flags().BoolP("printI18nWarnings", "", false, "print missing translations")
|
||||
cmd.Flags().BoolVarP(&r.printPathWarnings, "printPathWarnings", "", false, "print warnings on duplicate target paths etc.")
|
||||
cmd.Flags().BoolVarP(&r.printUnusedTemplates, "printUnusedTemplates", "", false, "print warnings on unused templates.")
|
||||
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")
|
||||
|
||||
@@ -110,6 +110,7 @@ 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.PersistentFlags().BoolVar(&c.unsafe, "unsafe", false, "enable less safe operations, please backup first")
|
||||
|
||||
cmd.RunE = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,9 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"context"
|
||||
|
||||
"github.com/bep/simplecobra"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
||||
@@ -243,6 +243,8 @@ func (c *genCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args [
|
||||
func (c *genCommand) Init(cd *simplecobra.Commandeer) error {
|
||||
cmd := cd.CobraCommand
|
||||
cmd.Short = "A collection of several useful generators."
|
||||
|
||||
cmd.RunE = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+3
-5
@@ -79,11 +79,9 @@ func flagsToCfgWithAdditionalConfigBase(cd *simplecobra.Commandeer, cfg config.P
|
||||
|
||||
// Flags with a different name in the config.
|
||||
keyMap := map[string]string{
|
||||
"minify": "minifyOutput",
|
||||
"destination": "publishDir",
|
||||
"printI18nWarnings": "logI18nWarnings",
|
||||
"printPathWarnings": "logPathWarnings",
|
||||
"editor": "newContentEditor",
|
||||
"minify": "minifyOutput",
|
||||
"destination": "publishDir",
|
||||
"editor": "newContentEditor",
|
||||
}
|
||||
|
||||
// Flags that we for some reason don't want to expose in the site config.
|
||||
|
||||
@@ -43,9 +43,7 @@ import (
|
||||
"github.com/gohugoio/hugo/hugolib/filesystems"
|
||||
"github.com/gohugoio/hugo/livereload"
|
||||
"github.com/gohugoio/hugo/resources/page"
|
||||
"github.com/gohugoio/hugo/tpl"
|
||||
"github.com/gohugoio/hugo/watcher"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/fsync"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"golang.org/x/sync/semaphore"
|
||||
@@ -419,25 +417,6 @@ func (c *hugoBuilder) build() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.r.printPathWarnings {
|
||||
hugofs.WalkFilesystems(h.Fs.PublishDir, func(fs afero.Fs) bool {
|
||||
if dfs, ok := fs.(hugofs.DuplicatesReporter); ok {
|
||||
dupes := dfs.ReportDuplicates()
|
||||
if dupes != "" {
|
||||
c.r.logger.Warnln("Duplicate target paths:", dupes)
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
if c.r.printUnusedTemplates {
|
||||
unusedTemplates := h.Tmpl().(tpl.UnusedTemplatesProvider).UnusedTemplates()
|
||||
for _, unusedTemplate := range unusedTemplates {
|
||||
c.r.logger.Warnf("Template %s is unused, source file %s", unusedTemplate.Name(), unusedTemplate.Filename())
|
||||
}
|
||||
}
|
||||
|
||||
h.PrintProcessingStats(os.Stdout)
|
||||
c.r.Println()
|
||||
}
|
||||
|
||||
@@ -96,6 +96,7 @@ func (c *importCommand) Init(cd *simplecobra.Commandeer) error {
|
||||
|
||||
Import requires a subcommand, e.g. ` + "`hugo import jekyll jekyll_root_path target_path`."
|
||||
|
||||
cmd.RunE = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -182,6 +182,7 @@ func (c *listCommand) Init(cd *simplecobra.Commandeer) error {
|
||||
|
||||
List requires a subcommand, e.g. hugo list drafts`
|
||||
|
||||
cmd.RunE = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -224,7 +224,7 @@ Run "go help get" for more information. All flags available for "go get" is also
|
||||
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" {
|
||||
if len(args) == 1 && (args[0] == "-h" || args[0] == "--help") {
|
||||
return errHelp
|
||||
}
|
||||
|
||||
|
||||
+22
-15
@@ -47,16 +47,16 @@ func newNewCommand() *newCommand {
|
||||
use: "content [path]",
|
||||
short: "Create new content for your site",
|
||||
long: `Create a new content file and automatically set the date and title.
|
||||
It will guess which kind of file to create based on the path provided.
|
||||
|
||||
You can also specify the kind with ` + "`-k KIND`" + `.
|
||||
|
||||
If archetypes are provided in your theme or site, they will be used.
|
||||
|
||||
Ensure you run this within the root directory of your site.`,
|
||||
It will guess which kind of file to create based on the path provided.
|
||||
|
||||
You can also specify the kind with ` + "`-k KIND`" + `.
|
||||
|
||||
If archetypes are provided in your theme or site, they will be used.
|
||||
|
||||
Ensure you run this within the root directory of your site.`,
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
if len(args) < 1 {
|
||||
return errors.New("path needs to be provided")
|
||||
return newUserError("path needs to be provided")
|
||||
}
|
||||
h, err := r.Hugo(flagsToCfg(cd, nil))
|
||||
if err != nil {
|
||||
@@ -82,7 +82,7 @@ The new site will have the correct structure, but no content or theme yet.
|
||||
Use ` + "`hugo new [contentPath]`" + ` to create new content.`,
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
if len(args) < 1 {
|
||||
return errors.New("path needs to be provided")
|
||||
return newUserError("path needs to be provided")
|
||||
}
|
||||
createpath, err := filepath.Abs(filepath.Clean(args[0]))
|
||||
if err != nil {
|
||||
@@ -150,16 +150,21 @@ Use ` + "`hugo new [contentPath]`" + ` to create new content.`,
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
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)")
|
||||
},
|
||||
},
|
||||
&simpleCommand{
|
||||
name: "theme",
|
||||
use: "theme [path]",
|
||||
short: "Create a new site (skeleton)",
|
||||
long: `Create a new site in the provided directory.
|
||||
The new site will have the correct structure, but no content or theme yet.
|
||||
Use ` + "`hugo new [contentPath]`" + ` to create new content.`,
|
||||
use: "theme [name]",
|
||||
short: "Create a new theme (skeleton)",
|
||||
long: `Create a new theme (skeleton) called [name] in ./themes.
|
||||
New theme is a skeleton. Please add content to the touched files. Add your
|
||||
name to the copyright line in the license and adjust the theme.toml file
|
||||
according to your needs.`,
|
||||
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")
|
||||
}
|
||||
h, err := r.Hugo(flagsToCfg(cd, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -284,6 +289,8 @@ You can also specify the kind with ` + "`-k KIND`" + `.
|
||||
If archetypes are provided in your theme or site, they will be used.
|
||||
|
||||
Ensure you run this within the root directory of your site.`
|
||||
|
||||
cmd.RunE = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -341,7 +348,7 @@ description = ""
|
||||
homepage = "http://example.com/"
|
||||
tags = []
|
||||
features = []
|
||||
min_version = "0.114.1"
|
||||
min_version = "0.115.0"
|
||||
|
||||
[author]
|
||||
name = ""
|
||||
|
||||
@@ -66,6 +66,10 @@ func Append(to any, from ...any) (any, error) {
|
||||
|
||||
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()
|
||||
@@ -94,7 +98,7 @@ func Append(to any, from ...any) (any, error) {
|
||||
|
||||
for _, f := range from {
|
||||
fv := reflect.ValueOf(f)
|
||||
if !fv.Type().AssignableTo(tot) {
|
||||
if !fv.IsValid() || !fv.Type().AssignableTo(tot) {
|
||||
// Fall back to a []interface{} slice.
|
||||
tov, _ := indirect(reflect.ValueOf(to))
|
||||
return appendToInterfaceSlice(tov, from...)
|
||||
@@ -109,6 +113,10 @@ func appendToInterfaceSliceFromValues(slice1, slice2 reflect.Value) ([]any, erro
|
||||
var tos []any
|
||||
|
||||
for _, slice := range []reflect.Value{slice1, slice2} {
|
||||
if !slice.IsValid() {
|
||||
tos = append(tos, nil)
|
||||
continue
|
||||
}
|
||||
for i := 0; i < slice.Len(); i++ {
|
||||
tos = append(tos, slice.Index(i).Interface())
|
||||
}
|
||||
|
||||
@@ -74,6 +74,9 @@ func TestAppend(t *testing.T) {
|
||||
[]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"}},
|
||||
} {
|
||||
|
||||
result, err := Append(test.start, test.addend...)
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bep/clock"
|
||||
"github.com/bep/clocks"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/gohugoio/locales"
|
||||
@@ -75,7 +75,7 @@ var (
|
||||
"December",
|
||||
}
|
||||
|
||||
Clock = clock.System()
|
||||
Clock = clocks.System()
|
||||
)
|
||||
|
||||
func NewTimeFormatter(ltr locales.Translator) TimeFormatter {
|
||||
|
||||
@@ -17,7 +17,7 @@ package hugo
|
||||
// This should be the only one.
|
||||
var CurrentVersion = Version{
|
||||
Major: 0,
|
||||
Minor: 114,
|
||||
PatchLevel: 1,
|
||||
Suffix: "",
|
||||
Minor: 116,
|
||||
PatchLevel: 0,
|
||||
Suffix: "-DEV",
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ type Config struct {
|
||||
Minify minifiers.MinifyConfig `mapstructure:"-"`
|
||||
|
||||
// Permalink configuration.
|
||||
Permalinks map[string]string `mapstructure:"-"`
|
||||
Permalinks map[string]map[string]string `mapstructure:"-"`
|
||||
|
||||
// Taxonomy configuration.
|
||||
Taxonomies map[string]string `mapstructure:"-"`
|
||||
@@ -484,12 +484,6 @@ type RootConfig struct {
|
||||
// Enable to print greppable placeholders (on the form "[i18n] TRANSLATIONID") for missing translation strings.
|
||||
EnableMissingTranslationPlaceholders bool
|
||||
|
||||
// Enable to print warnings for missing translation strings.
|
||||
LogI18nWarnings bool
|
||||
|
||||
// ENable to print warnings for multiple files published to the same destination.
|
||||
LogPathWarnings bool
|
||||
|
||||
// Enable to panic on warning log entries. This may make it easier to detect the source.
|
||||
PanicOnWarning bool
|
||||
|
||||
@@ -525,6 +519,12 @@ type RootConfig struct {
|
||||
// Whether to track and print unused templates during the build.
|
||||
PrintUnusedTemplates bool
|
||||
|
||||
// Enable to print warnings for missing translation strings.
|
||||
PrintI18nWarnings bool
|
||||
|
||||
// ENable to print warnings for multiple files published to the same destination.
|
||||
PrintPathWarnings bool
|
||||
|
||||
// URL to be used as a placeholder when a page reference cannot be found in ref or relref. Is used as-is.
|
||||
RefLinksNotFoundURL string
|
||||
|
||||
|
||||
@@ -138,8 +138,8 @@ var allDecoderSetups = map[string]decodeWeight{
|
||||
return err
|
||||
},
|
||||
},
|
||||
"mediaTypes": {
|
||||
key: "mediaTypes",
|
||||
"mediatypes": {
|
||||
key: "mediatypes",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
var err error
|
||||
p.c.MediaTypes, err = media.DecodeTypes(p.p.GetStringMap(d.key))
|
||||
@@ -168,8 +168,8 @@ var allDecoderSetups = map[string]decodeWeight{
|
||||
return nil
|
||||
},
|
||||
},
|
||||
"outputFormats": {
|
||||
key: "outputFormats",
|
||||
"outputformats": {
|
||||
key: "outputformats",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
var err error
|
||||
p.c.OutputFormats, err = output.DecodeConfig(p.c.MediaTypes.Config, p.p.Get(d.key))
|
||||
@@ -206,8 +206,9 @@ var allDecoderSetups = map[string]decodeWeight{
|
||||
"permalinks": {
|
||||
key: "permalinks",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
p.c.Permalinks = maps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
|
||||
return nil
|
||||
var err error
|
||||
p.c.Permalinks, err = page.DecodePermalinksConfig(p.p.GetStringMap(d.key))
|
||||
return err
|
||||
},
|
||||
},
|
||||
"sitemap": {
|
||||
@@ -369,3 +370,19 @@ var allDecoderSetups = map[string]decodeWeight{
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
for k, v := range allDecoderSetups {
|
||||
// Verify that k and v.key is all lower case.
|
||||
if k != strings.ToLower(k) {
|
||||
panic(fmt.Sprintf("key %q is not lower case", k))
|
||||
}
|
||||
if v.key != strings.ToLower(v.key) {
|
||||
panic(fmt.Sprintf("key %q is not lower case", v.key))
|
||||
}
|
||||
|
||||
if k != v.key {
|
||||
panic(fmt.Sprintf("key %q is not the same as the map key %q", k, v.key))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,8 +199,8 @@ func (c ConfigLanguage) EnableMissingTranslationPlaceholders() bool {
|
||||
return c.config.EnableMissingTranslationPlaceholders
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) LogI18nWarnings() bool {
|
||||
return c.config.LogI18nWarnings
|
||||
func (c ConfigLanguage) PrintI18nWarnings() bool {
|
||||
return c.config.PrintI18nWarnings
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) CreateTitle(s string) string {
|
||||
|
||||
@@ -69,3 +69,21 @@ Title: {{ .Title }}
|
||||
b.Assert(modConf.Mounts[1].Lang, qt.Equals, "sv")
|
||||
|
||||
}
|
||||
|
||||
func TestConfigAliases(t *testing.T) {
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
logI18nWarnings = true
|
||||
logPathWarnings = true
|
||||
`
|
||||
b := hugolib.NewIntegrationTestBuilder(
|
||||
hugolib.IntegrationTestConfig{T: t, TxtarString: files},
|
||||
).Build()
|
||||
|
||||
conf := b.H.Configs.Base
|
||||
|
||||
b.Assert(conf.PrintI18nWarnings, qt.Equals, true)
|
||||
b.Assert(conf.PrintPathWarnings, qt.Equals, true)
|
||||
}
|
||||
|
||||
@@ -139,7 +139,11 @@ type configLoader struct {
|
||||
|
||||
// Handle some legacy values.
|
||||
func (l configLoader) applyConfigAliases() error {
|
||||
aliases := []types.KeyValueStr{{Key: "taxonomies", Value: "indexes"}}
|
||||
aliases := []types.KeyValueStr{
|
||||
{Key: "indexes", Value: "taxonomies"},
|
||||
{Key: "logI18nWarnings", Value: "printI18nWarnings"},
|
||||
{Key: "logPathWarnings", Value: "printPathWarnings"},
|
||||
}
|
||||
|
||||
for _, alias := range aliases {
|
||||
if l.cfg.IsSet(alias.Key) {
|
||||
|
||||
+27
-3
@@ -82,7 +82,7 @@ type LoadConfigResult struct {
|
||||
|
||||
var defaultBuild = BuildConfig{
|
||||
UseResourceCacheWhen: "fallback",
|
||||
WriteStats: false,
|
||||
BuildStats: BuildStats{},
|
||||
|
||||
CacheBusters: []CacheBuster{
|
||||
{
|
||||
@@ -111,7 +111,8 @@ type BuildConfig struct {
|
||||
|
||||
// When enabled, will collect and write a hugo_stats.json with some build
|
||||
// related aggregated data (e.g. CSS class names).
|
||||
WriteStats bool
|
||||
// Note that this was a bool <= v0.115.0.
|
||||
BuildStats BuildStats
|
||||
|
||||
// Can be used to toggle off writing of the IntelliSense /assets/jsconfig.js
|
||||
// file.
|
||||
@@ -121,6 +122,21 @@ type BuildConfig struct {
|
||||
CacheBusters []CacheBuster
|
||||
}
|
||||
|
||||
// BuildStats configures if and what to write to the hugo_stats.json file.
|
||||
type BuildStats struct {
|
||||
Enable bool
|
||||
DisableTags bool
|
||||
DisableClasses bool
|
||||
DisableIDs bool
|
||||
}
|
||||
|
||||
func (w BuildStats) Enabled() bool {
|
||||
if !w.Enable {
|
||||
return false
|
||||
}
|
||||
return !w.DisableTags || !w.DisableClasses || !w.DisableIDs
|
||||
}
|
||||
|
||||
func (b BuildConfig) clone() BuildConfig {
|
||||
b.CacheBusters = append([]CacheBuster{}, b.CacheBusters...)
|
||||
return b
|
||||
@@ -171,14 +187,22 @@ func (b *BuildConfig) CompileConfig(logger loggers.Logger) error {
|
||||
|
||||
func DecodeBuildConfig(cfg Provider) BuildConfig {
|
||||
m := cfg.GetStringMap("build")
|
||||
|
||||
b := defaultBuild.clone()
|
||||
if m == nil {
|
||||
return b
|
||||
}
|
||||
|
||||
// writeStats was a bool <= v0.115.0.
|
||||
if writeStats, ok := m["writestats"]; ok {
|
||||
if bb, ok := writeStats.(bool); ok {
|
||||
m["buildstats"] = BuildStats{Enable: bb}
|
||||
}
|
||||
}
|
||||
|
||||
err := mapstructure.WeakDecode(m, &b)
|
||||
if err != nil {
|
||||
return defaultBuild
|
||||
return b
|
||||
}
|
||||
|
||||
b.UseResourceCacheWhen = strings.ToLower(b.UseResourceCacheWhen)
|
||||
|
||||
@@ -57,7 +57,7 @@ type AllProvider interface {
|
||||
EnableMissingTranslationPlaceholders() bool
|
||||
TemplateMetrics() bool
|
||||
TemplateMetricsHints() bool
|
||||
LogI18nWarnings() bool
|
||||
PrintI18nWarnings() bool
|
||||
CreateTitle(s string) string
|
||||
IgnoreFile(s string) bool
|
||||
NewContentEditor() string
|
||||
|
||||
@@ -34,7 +34,7 @@ const securityConfigKey = "security"
|
||||
// DefaultConfig holds the default security policy.
|
||||
var DefaultConfig = Config{
|
||||
Exec: Exec{
|
||||
Allow: NewWhitelist(
|
||||
Allow: MustNewWhitelist(
|
||||
"^(dart-)?sass(-embedded)?$", // sass, dart-sass, dart-sass-embedded.
|
||||
"^go$", // for Go Modules
|
||||
"^npx$", // used by all Node tools (Babel, PostCSS).
|
||||
@@ -42,14 +42,14 @@ var DefaultConfig = Config{
|
||||
),
|
||||
// These have been tested to work with Hugo's external programs
|
||||
// on Windows, Linux and MacOS.
|
||||
OsEnv: NewWhitelist(`(?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\w+)$`),
|
||||
OsEnv: MustNewWhitelist(`(?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\w+)$`),
|
||||
},
|
||||
Funcs: Funcs{
|
||||
Getenv: NewWhitelist("^HUGO_", "^CI$"),
|
||||
Getenv: MustNewWhitelist("^HUGO_", "^CI$"),
|
||||
},
|
||||
HTTP: HTTP{
|
||||
URLs: NewWhitelist(".*"),
|
||||
Methods: NewWhitelist("(?i)GET|POST"),
|
||||
URLs: MustNewWhitelist(".*"),
|
||||
Methods: MustNewWhitelist("(?i)GET|POST"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -221,7 +221,7 @@ func stringSliceToWhitelistHook() mapstructure.DecodeHookFuncType {
|
||||
|
||||
wl := types.ToStringSlicePreserveString(data)
|
||||
|
||||
return NewWhitelist(wl...), nil
|
||||
return NewWhitelist(wl...)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,9 +45,9 @@ func (w Whitelist) MarshalJSON() ([]byte, error) {
|
||||
// NewWhitelist creates a new Whitelist from zero or more patterns.
|
||||
// An empty patterns list or a pattern with the value 'none' will create
|
||||
// a whitelist that will Accept none.
|
||||
func NewWhitelist(patterns ...string) Whitelist {
|
||||
func NewWhitelist(patterns ...string) (Whitelist, error) {
|
||||
if len(patterns) == 0 {
|
||||
return Whitelist{acceptNone: true}
|
||||
return Whitelist{acceptNone: true}, nil
|
||||
}
|
||||
|
||||
var acceptSome bool
|
||||
@@ -68,7 +68,7 @@ func NewWhitelist(patterns ...string) Whitelist {
|
||||
if !acceptSome {
|
||||
return Whitelist{
|
||||
acceptNone: true,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
var patternsr []*regexp.Regexp
|
||||
@@ -78,10 +78,23 @@ func NewWhitelist(patterns ...string) Whitelist {
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
patternsr = append(patternsr, regexp.MustCompile(p))
|
||||
re, err := regexp.Compile(p)
|
||||
if err != nil {
|
||||
return Whitelist{}, fmt.Errorf("failed to compile whitelist pattern %q: %w", p, err)
|
||||
}
|
||||
patternsr = append(patternsr, re)
|
||||
}
|
||||
|
||||
return Whitelist{patterns: patternsr, patternsStrings: patternsStrings}
|
||||
return Whitelist{patterns: patternsr, patternsStrings: patternsStrings}, nil
|
||||
}
|
||||
|
||||
// MustNewWhitelist creates a new Whitelist from zero or more patterns and panics on error.
|
||||
func MustNewWhitelist(patterns ...string) Whitelist {
|
||||
w, err := NewWhitelist(patterns...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
// Accept reports whether name is whitelisted.
|
||||
|
||||
@@ -24,21 +24,21 @@ func TestWhitelist(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
c.Run("none", func(c *qt.C) {
|
||||
c.Assert(NewWhitelist("none", "foo").Accept("foo"), qt.IsFalse)
|
||||
c.Assert(NewWhitelist().Accept("foo"), qt.IsFalse)
|
||||
c.Assert(NewWhitelist("").Accept("foo"), qt.IsFalse)
|
||||
c.Assert(NewWhitelist(" ", " ").Accept("foo"), qt.IsFalse)
|
||||
c.Assert(MustNewWhitelist("none", "foo").Accept("foo"), qt.IsFalse)
|
||||
c.Assert(MustNewWhitelist().Accept("foo"), qt.IsFalse)
|
||||
c.Assert(MustNewWhitelist("").Accept("foo"), qt.IsFalse)
|
||||
c.Assert(MustNewWhitelist(" ", " ").Accept("foo"), qt.IsFalse)
|
||||
c.Assert(Whitelist{}.Accept("foo"), qt.IsFalse)
|
||||
})
|
||||
|
||||
c.Run("One", func(c *qt.C) {
|
||||
w := NewWhitelist("^foo.*")
|
||||
w := MustNewWhitelist("^foo.*")
|
||||
c.Assert(w.Accept("foo"), qt.IsTrue)
|
||||
c.Assert(w.Accept("mfoo"), qt.IsFalse)
|
||||
})
|
||||
|
||||
c.Run("Multiple", func(c *qt.C) {
|
||||
w := NewWhitelist("^foo.*", "^bar.*")
|
||||
w := MustNewWhitelist("^foo.*", "^bar.*")
|
||||
c.Assert(w.Accept("foo"), qt.IsTrue)
|
||||
c.Assert(w.Accept("bar"), qt.IsTrue)
|
||||
c.Assert(w.Accept("mbar"), qt.IsFalse)
|
||||
|
||||
@@ -11,9 +11,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !nodeploy
|
||||
// +build !nodeploy
|
||||
|
||||
package deploy
|
||||
|
||||
import (
|
||||
|
||||
@@ -89,67 +89,149 @@ If you set both `slug` and `url` in front matter, the `url` value takes preceden
|
||||
|
||||
### Permalinks
|
||||
|
||||
In your site configuration, set a URL pattern for regular pages within a top-level section. This is recursive, affecting descendant regular pages.
|
||||
In your site configuration, define a URL pattern for each top-level section. Each URL pattern can target a given language and/or [page kind].
|
||||
|
||||
{{% note %}}
|
||||
The `permalinks` defined in your site configuration do not apply to section pages. To adjust the URL for section pages, set `url` in front matter.
|
||||
{{% /note %}}
|
||||
Front matter `url` values override the URL patterns defined in the `permalinks` section of your site configuration.
|
||||
|
||||
#### Examples {#permalinks-examples}
|
||||
[page kind]: https://gohugo.io/templates/section-templates/#page-kinds
|
||||
|
||||
#### Monolingual examples {#permalinks-monolingual-examples}
|
||||
|
||||
With this content structure:
|
||||
|
||||
```text
|
||||
content/
|
||||
├── posts/
|
||||
│ ├── _index.md
|
||||
│ ├── post-1.md
|
||||
│ └── post-2.md
|
||||
│ ├── bash-in-slow-motion.md
|
||||
│ └── tls-in-a-nutshell.md
|
||||
├── tutorials/
|
||||
│ ├── git-for-beginners.md
|
||||
│ └── javascript-bundling-with-hugo.md
|
||||
└── _index.md
|
||||
```
|
||||
|
||||
Create a date-based hierarchy, recursively, for regular pages within the `posts` section:
|
||||
Render tutorials under "training", and render the posts under "articles" with a date-base hierarchy:
|
||||
|
||||
{{< code-toggle file="hugo" copy=false >}}
|
||||
[permalinks]
|
||||
posts = '/posts/:year/:month/:title/'
|
||||
[permalinks.page]
|
||||
posts = '/articles/:year/:month/:slug/'
|
||||
tutorials = '/training/:slug/'
|
||||
[permalinks.section]
|
||||
posts = '/articles/'
|
||||
tutorials = '/training/'
|
||||
{{< /code-toggle >}}
|
||||
|
||||
The structure of the published site will be:
|
||||
|
||||
```text
|
||||
public/
|
||||
├── posts/
|
||||
│ ├── 2023/
|
||||
│ │ └── 03/
|
||||
│ │ ├── post-1/
|
||||
│ │ │ └── index.html
|
||||
│ │ └── post-2/
|
||||
│ │ └── index.html
|
||||
│ └── index.html
|
||||
├── favicon.ico
|
||||
├── articles/
|
||||
│ ├── 2023/
|
||||
│ │ ├── 04/
|
||||
│ │ │ └── bash-in-slow-motion/
|
||||
│ │ │ └── index.html
|
||||
│ │ └── 06/
|
||||
│ │ └── tls-in-a-nutshell/
|
||||
│ │ └── index.html
|
||||
│ └── index.html
|
||||
├── training/
|
||||
│ ├── git-for-beginners/
|
||||
│ │ └── index.html
|
||||
│ ├── javascript-bundling-with-hugo/
|
||||
│ │ └── index.html
|
||||
│ └── index.html
|
||||
└── index.html
|
||||
```
|
||||
|
||||
To create a date-based hierarchy for regular pages in the content root:
|
||||
|
||||
{{< code-toggle file="hugo" copy=false >}}
|
||||
[permalinks]
|
||||
'/' = '/:year/:month/:title/'
|
||||
[permalinks.page]
|
||||
"/" = "/:year/:month/:slug/"
|
||||
{{< /code-toggle >}}
|
||||
|
||||
{{% note %}}
|
||||
A URL pattern defined for the content root is not recursive.
|
||||
{{% /note %}}
|
||||
|
||||
Use the same approach with taxonomies. For example, to omit the taxonomy segment of the URL:
|
||||
Use the same approach with taxonomy terms. For example, to omit the taxonomy segment of the URL:
|
||||
|
||||
{{< code-toggle file="hugo" copy=false >}}
|
||||
[permalinks]
|
||||
'tags' = '/:title/'
|
||||
[permalinks.term]
|
||||
'tags' = '/:slug/'
|
||||
{{< /code-toggle >}}
|
||||
|
||||
Front matter `url` values take precedence over URL patterns defined in `permalinks`.
|
||||
#### Multilingual example {#permalinks-multilingual-example}
|
||||
|
||||
Use the `permalinks` configuration as a component of your localization strategy.
|
||||
|
||||
With this content structure:
|
||||
|
||||
```text
|
||||
content/
|
||||
├── de/
|
||||
│ ├── books/
|
||||
│ │ ├── les-miserables.md
|
||||
│ │ └── the-hunchback-of-notre-dame.md
|
||||
│ └── _index.md
|
||||
└── en/
|
||||
├── books/
|
||||
│ ├── les-miserables.md
|
||||
│ └── the-hunchback-of-notre-dame.md
|
||||
└── _index.md
|
||||
```
|
||||
|
||||
And this site configuration:
|
||||
|
||||
{{< code-toggle file="hugo" copy=false >}}
|
||||
defaultContentLanguage = 'en'
|
||||
defaultContentLanguageInSubdir = true
|
||||
|
||||
[languages.en]
|
||||
contentDir = 'content/en'
|
||||
languageCode = 'en-US'
|
||||
languageDirection = 'ltr'
|
||||
languageName = 'English'
|
||||
weight = 1
|
||||
|
||||
[languages.en.permalinks.page]
|
||||
books = "/books/:slug/"
|
||||
|
||||
[languages.en.permalinks.section]
|
||||
books = "/books/"
|
||||
|
||||
[languages.es]
|
||||
contentDir = 'content/de'
|
||||
languageCode = 'es-ES'
|
||||
languageDirection = 'ltr'
|
||||
languageName = 'Español'
|
||||
weight = 2
|
||||
|
||||
[languages.es.permalinks.page]
|
||||
books = "/libros/:slug/"
|
||||
|
||||
[languages.es.permalinks.section]
|
||||
books = "/libros/"
|
||||
{{< /code-toggle >}}
|
||||
|
||||
The structure of the published site will be:
|
||||
|
||||
```text
|
||||
public/
|
||||
├── en/
|
||||
│ ├── books/
|
||||
│ │ ├── les-miserables/
|
||||
│ │ │ └── index.html
|
||||
│ │ ├── the-hunchback-of-notre-dame/
|
||||
│ │ │ └── index.html
|
||||
│ │ └── index.html
|
||||
│ └── index.html
|
||||
├── es/
|
||||
│ ├── libros/
|
||||
│ │ ├── les-miserables/
|
||||
│ │ │ └── index.html
|
||||
│ │ ├── the-hunchback-of-notre-dame/
|
||||
│ │ │ └── index.html
|
||||
│ │ └── index.html
|
||||
│ └── index.html
|
||||
└── index.html
|
||||
````
|
||||
|
||||
#### Tokens
|
||||
|
||||
|
||||
@@ -623,7 +623,13 @@ status = 404
|
||||
|
||||
Set `titleCaseStyle` to specify the title style used by the [title](/functions/title/) template function and the automatic section titles in Hugo.
|
||||
|
||||
By default, Hugo adheres to the capitalization rules in the [Associated Press (AP) Stylebook]. Set `titleCaseStyle` to `chicago` if you would prefer to follow the [Chicago Manual of Style], or set if to `go` to use Go's convention of capitalizing every word.
|
||||
Can be one of:
|
||||
|
||||
* `ap` (default), the capitalization rules in the [Associated Press (AP) Stylebook]
|
||||
* `chicago`, the [Chicago Manual of Style]
|
||||
* `go`, Go's convention of capitalizing every word.
|
||||
* `firstupper`, capitalize the first letter of the first word.
|
||||
* `none`, no capitalization.
|
||||
|
||||
[Associated Press (AP) Stylebook]: https://www.apstylebook.com/
|
||||
[Chicago Manual of Style]: https://www.chicagomanualofstyle.org/home.html
|
||||
|
||||
+44
-1
@@ -73,6 +73,12 @@
|
||||
"applescript"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "ArangoDB AQL",
|
||||
"Aliases": [
|
||||
"aql"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Arduino",
|
||||
"Aliases": [
|
||||
@@ -85,6 +91,19 @@
|
||||
"armasm"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "AutoHotkey",
|
||||
"Aliases": [
|
||||
"autohotkey",
|
||||
"ahk"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "AutoIt",
|
||||
"Aliases": [
|
||||
"autoit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Awk",
|
||||
"Aliases": [
|
||||
@@ -666,6 +685,12 @@
|
||||
"io"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "ISCdhcpd",
|
||||
"Aliases": [
|
||||
"iscdhcpd"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "J",
|
||||
"Aliases": [
|
||||
@@ -1230,6 +1255,12 @@
|
||||
"sieve"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Smali",
|
||||
"Aliases": [
|
||||
"smali"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Smalltalk",
|
||||
"Aliases": [
|
||||
@@ -1324,6 +1355,13 @@
|
||||
"tablegen"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Tal",
|
||||
"Aliases": [
|
||||
"tal",
|
||||
"uxntal"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "TASM",
|
||||
"Aliases": [
|
||||
@@ -1505,6 +1543,12 @@
|
||||
"Name": "WDTE",
|
||||
"Aliases": null
|
||||
},
|
||||
{
|
||||
"Name": "WebGPU Shading Language",
|
||||
"Aliases": [
|
||||
"wgsl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Whiley",
|
||||
"Aliases": [
|
||||
@@ -1723,7 +1767,6 @@
|
||||
"js": {
|
||||
"precision": 0,
|
||||
"keepVarNames": false,
|
||||
"noNullishOperator": false,
|
||||
"version": 2022
|
||||
},
|
||||
"json": {
|
||||
|
||||
@@ -3,10 +3,10 @@ module github.com/gohugoio/hugo
|
||||
require (
|
||||
github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69
|
||||
github.com/PuerkitoBio/purell v1.1.1
|
||||
github.com/alecthomas/chroma/v2 v2.7.0
|
||||
github.com/alecthomas/chroma/v2 v2.8.0
|
||||
github.com/armon/go-radix v1.0.0
|
||||
github.com/aws/aws-sdk-go v1.43.5
|
||||
github.com/bep/clock v0.3.0
|
||||
github.com/bep/clocks v0.5.0
|
||||
github.com/bep/debounce v1.2.0
|
||||
github.com/bep/gitmap v1.1.2
|
||||
github.com/bep/goat v0.5.0
|
||||
@@ -25,7 +25,7 @@ require (
|
||||
github.com/cli/safeexec v1.0.1
|
||||
github.com/disintegration/gift v1.2.1
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/evanw/esbuild v0.18.5
|
||||
github.com/evanw/esbuild v0.18.11
|
||||
github.com/fatih/color v1.15.0
|
||||
github.com/fortytw2/leaktest v1.3.0
|
||||
github.com/frankban/quicktest v1.14.5
|
||||
@@ -50,7 +50,7 @@ require (
|
||||
github.com/mitchellh/hashstructure v1.1.0
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/muesli/smartcrop v0.3.0
|
||||
github.com/niklasfasching/go-org v1.6.6
|
||||
github.com/niklasfasching/go-org v1.7.0
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/pelletier/go-toml/v2 v2.0.8
|
||||
github.com/rogpeppe/go-internal v1.10.1-0.20230508101108-a4f6fabd84c5
|
||||
@@ -104,7 +104,6 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.18.3 // indirect
|
||||
github.com/aws/smithy-go v1.13.5 // indirect
|
||||
github.com/bep/clocks v0.5.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/dlclark/regexp2 v1.10.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
|
||||
@@ -126,8 +126,8 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink=
|
||||
github.com/alecthomas/chroma/v2 v2.7.0 h1:hm1rY6c/Ob4eGclpQ7X/A3yhqBOZNUTk9q+yhyLIViI=
|
||||
github.com/alecthomas/chroma/v2 v2.7.0/go.mod h1:yrkMI9807G1ROx13fhe1v6PN2DDeaR73L3d+1nmYQtw=
|
||||
github.com/alecthomas/chroma/v2 v2.8.0 h1:w9WJUjFFmHHB2e8mRpL9jjy3alYDlU0QLDezj1xE264=
|
||||
github.com/alecthomas/chroma/v2 v2.8.0/go.mod h1:yrkMI9807G1ROx13fhe1v6PN2DDeaR73L3d+1nmYQtw=
|
||||
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
|
||||
@@ -174,8 +174,6 @@ github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAm
|
||||
github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8=
|
||||
github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/bep/clock v0.3.0 h1:vfOA6+wVb6pPQEiXow9f/too92vNTLe9MuwO13PfI0M=
|
||||
github.com/bep/clock v0.3.0/go.mod h1:6Gz2lapnJ9vxpvPxQ2u6FcXFRoj4kkiqQ6pm0ERZlwk=
|
||||
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=
|
||||
@@ -260,8 +258,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/evanw/esbuild v0.18.5 h1:B474TK1dD7ZQJOEE7BKqCmTerOYEIzxVH35UY4MOM0s=
|
||||
github.com/evanw/esbuild v0.18.5/go.mod h1:iINY06rn799hi48UqEnaQvVfZWe6W9bET78LbvN8VWk=
|
||||
github.com/evanw/esbuild v0.18.11 h1:Mb0qb9KyJQraob0Y7LA0DOdD8ijJs2TxhqgqrDeGq4w=
|
||||
github.com/evanw/esbuild v0.18.11/go.mod h1:iINY06rn799hi48UqEnaQvVfZWe6W9bET78LbvN8VWk=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
@@ -504,8 +502,8 @@ github.com/muesli/smartcrop v0.3.0/go.mod h1:i2fCI/UorTfgEpPPLWiFBv4pye+YAG78Rwc
|
||||
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.6.6 h1:U6+mJ80p3weR4oP+Z+Pb2EVkSbt1MUwweBbUcF1hVqQ=
|
||||
github.com/niklasfasching/go-org v1.6.6/go.mod h1:o3pMQpO9n6RNBXz2Oc2DiRkaVwjns0JElyKiG7yXwA4=
|
||||
github.com/niklasfasching/go-org v1.7.0 h1:vyMdcMWWTe/XmANk19F4k8XGBYg0GQ/gJGMimOjGMek=
|
||||
github.com/niklasfasching/go-org v1.7.0/go.mod h1:WuVm4d45oePiE0eX25GqTDQIt/qPW1T9DGkRscqLW5o=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||
|
||||
@@ -205,6 +205,8 @@ func ReaderContains(r io.Reader, subslice []byte) bool {
|
||||
// - "Go" (strings.Title)
|
||||
// - "AP" (see https://www.apstylebook.com/)
|
||||
// - "Chicago" (see http://www.chicagomanualofstyle.org/home.html)
|
||||
// - "FirstUpper" (only the first character is upper case)
|
||||
// - "None" (no transformation)
|
||||
//
|
||||
// If an unknown or empty style is provided, AP style is what you get.
|
||||
func GetTitleFunc(style string) func(s string) string {
|
||||
@@ -214,6 +216,10 @@ func GetTitleFunc(style string) func(s string) string {
|
||||
case "chicago":
|
||||
tc := transform.NewTitleConverter(transform.ChicagoStyle)
|
||||
return tc.Title
|
||||
case "none":
|
||||
return func(s string) string { return s }
|
||||
case "firstupper":
|
||||
return FirstUpper
|
||||
default:
|
||||
tc := transform.NewTitleConverter(transform.APStyle)
|
||||
return tc.Title
|
||||
|
||||
@@ -203,7 +203,7 @@ func TestReaderContains(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetTitleFunc(t *testing.T) {
|
||||
title := "somewhere over the rainbow"
|
||||
title := "somewhere over the Rainbow"
|
||||
c := qt.New(t)
|
||||
|
||||
c.Assert(helpers.GetTitleFunc("go")(title), qt.Equals, "Somewhere Over The Rainbow")
|
||||
@@ -213,6 +213,8 @@ func TestGetTitleFunc(t *testing.T) {
|
||||
c.Assert(helpers.GetTitleFunc("ap")(title), qt.Equals, "Somewhere Over the Rainbow")
|
||||
c.Assert(helpers.GetTitleFunc("")(title), qt.Equals, "Somewhere Over the Rainbow")
|
||||
c.Assert(helpers.GetTitleFunc("unknown")(title), qt.Equals, "Somewhere Over the Rainbow")
|
||||
c.Assert(helpers.GetTitleFunc("none")(title), qt.Equals, title)
|
||||
c.Assert(helpers.GetTitleFunc("firstupper")(title), qt.Equals, "Somewhere over the Rainbow")
|
||||
}
|
||||
|
||||
func BenchmarkReaderContains(b *testing.B) {
|
||||
|
||||
@@ -1144,6 +1144,48 @@ LanguageCode: {{ .Site.LanguageCode }}|{{ site.Language.LanguageCode }}|
|
||||
|
||||
}
|
||||
|
||||
// See #11159
|
||||
func TestConfigOutputFormatsPerLanguage(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
[languages]
|
||||
[languages.en]
|
||||
title = "English Title"
|
||||
[languages.sv]
|
||||
title = "Swedish Title"
|
||||
[languages.sv.outputFormats.html]
|
||||
path = "foo"
|
||||
[languages.sv.mediatypes."text/html"]
|
||||
suffixes = ["bar"]
|
||||
|
||||
-- layouts/index.html --
|
||||
Home.
|
||||
|
||||
|
||||
`
|
||||
b := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
},
|
||||
).Build()
|
||||
|
||||
b.AssertFileContent("public/index.html", "Home.")
|
||||
|
||||
enConfig := b.H.Sites[0].conf
|
||||
m, _ := enConfig.MediaTypes.Config.GetByType("text/html")
|
||||
b.Assert(m.Suffixes(), qt.DeepEquals, []string{"html"})
|
||||
|
||||
svConfig := b.H.Sites[1].conf
|
||||
f, _ := svConfig.OutputFormats.Config.GetByName("html")
|
||||
b.Assert(f.Path, qt.Equals, "foo")
|
||||
m, _ = svConfig.MediaTypes.Config.GetByType("text/html")
|
||||
b.Assert(m.Suffixes(), qt.DeepEquals, []string{"bar"})
|
||||
|
||||
}
|
||||
|
||||
func TestConfigMiscPanics(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -680,7 +680,14 @@ func (m *contentMap) splitKey(k string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
return strings.Split(k, "/")[1:]
|
||||
parts := strings.Split(k, "/")[1:]
|
||||
if len(parts) == 0 {
|
||||
return nil
|
||||
}
|
||||
if parts[len(parts)-1] == "" {
|
||||
parts = parts[:len(parts)-1]
|
||||
}
|
||||
return parts
|
||||
}
|
||||
|
||||
func (m *contentMap) testDump() string {
|
||||
|
||||
@@ -77,6 +77,8 @@ type HugoSites struct {
|
||||
contentInit sync.Once
|
||||
content *pageMaps
|
||||
|
||||
postRenderInit sync.Once
|
||||
|
||||
// Keeps track of bundle directories and symlinks to enable partial rebuilding.
|
||||
ContentChanges *contentChangeMap
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"github.com/bep/logg"
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
"github.com/gohugoio/hugo/publisher"
|
||||
"github.com/gohugoio/hugo/tpl"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
|
||||
@@ -143,6 +144,11 @@ func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error {
|
||||
if err := h.render(infol, conf); err != nil {
|
||||
h.SendError(fmt.Errorf("render: %w", err))
|
||||
}
|
||||
|
||||
if err := h.postRenderOnce(); err != nil {
|
||||
h.SendError(fmt.Errorf("postRenderOnce: %w", err))
|
||||
}
|
||||
|
||||
if err := h.postProcess(infol); err != nil {
|
||||
h.SendError(fmt.Errorf("postProcess: %w", err))
|
||||
}
|
||||
@@ -314,6 +320,34 @@ func (h *HugoSites) render(l logg.LevelLogger, config *BuildCfg) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HugoSites) postRenderOnce() error {
|
||||
h.postRenderInit.Do(func() {
|
||||
conf := h.Configs.Base
|
||||
if conf.PrintPathWarnings {
|
||||
// We need to do this before any post processing, as that may write to the same files twice
|
||||
// and create false positives.
|
||||
hugofs.WalkFilesystems(h.Fs.PublishDir, func(fs afero.Fs) bool {
|
||||
if dfs, ok := fs.(hugofs.DuplicatesReporter); ok {
|
||||
dupes := dfs.ReportDuplicates()
|
||||
if dupes != "" {
|
||||
h.Log.Warnln("Duplicate target paths:", dupes)
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
if conf.PrintUnusedTemplates {
|
||||
unusedTemplates := h.Tmpl().(tpl.UnusedTemplatesProvider).UnusedTemplates()
|
||||
for _, unusedTemplate := range unusedTemplates {
|
||||
h.Log.Warnf("Template %s is unused, source file %s", unusedTemplate.Name(), unusedTemplate.Filename())
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HugoSites) postProcess(l logg.LevelLogger) error {
|
||||
defer h.timeTrack(l, time.Now(), "postProcess")
|
||||
|
||||
@@ -441,7 +475,7 @@ func (h *HugoSites) writeBuildStats() error {
|
||||
if h.ResourceSpec == nil {
|
||||
panic("h.ResourceSpec is nil")
|
||||
}
|
||||
if !h.ResourceSpec.BuildConfig().WriteStats {
|
||||
if !h.ResourceSpec.BuildConfig().BuildStats.Enabled() {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -147,6 +147,15 @@ func (s *IntegrationTestBuilder) AssertFileContent(filename string, matches ...s
|
||||
if match == "" || strings.HasPrefix(match, "#") {
|
||||
continue
|
||||
}
|
||||
var negate bool
|
||||
if strings.HasPrefix(match, "! ") {
|
||||
negate = true
|
||||
match = strings.TrimPrefix(match, "! ")
|
||||
}
|
||||
if negate {
|
||||
s.Assert(content, qt.Not(qt.Contains), match, qt.Commentf(m))
|
||||
continue
|
||||
}
|
||||
s.Assert(content, qt.Contains, match, qt.Commentf(m))
|
||||
}
|
||||
}
|
||||
@@ -205,6 +214,10 @@ func (s *IntegrationTestBuilder) Build() *IntegrationTestBuilder {
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *IntegrationTestBuilder) LogString() string {
|
||||
return s.logBuff.String()
|
||||
}
|
||||
|
||||
func (s *IntegrationTestBuilder) BuildE() (*IntegrationTestBuilder, error) {
|
||||
s.Helper()
|
||||
if err := s.initBuilder(); err != nil {
|
||||
@@ -381,7 +394,8 @@ func (s *IntegrationTestBuilder) initBuilder() error {
|
||||
s.Assert(os.Chdir(s.Cfg.WorkingDir), qt.IsNil)
|
||||
s.C.Cleanup(func() { os.Chdir(wd) })
|
||||
sc := security.DefaultConfig
|
||||
sc.Exec.Allow = security.NewWhitelist("npm")
|
||||
sc.Exec.Allow, err = security.NewWhitelist("npm")
|
||||
s.Assert(err, qt.IsNil)
|
||||
ex := hexec.New(sc)
|
||||
command, err := ex.New("npm", "install")
|
||||
s.Assert(err, qt.IsNil)
|
||||
|
||||
+21
-14
@@ -18,6 +18,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
|
||||
"github.com/gohugoio/hugo/resources/page"
|
||||
)
|
||||
@@ -108,14 +109,16 @@ func createTargetPathDescriptor(s *Site, p page.Page, pm *pageMeta) (page.Target
|
||||
)
|
||||
|
||||
d := s.Deps
|
||||
var classifier files.ContentClass
|
||||
|
||||
if !p.File().IsZero() {
|
||||
dir = p.File().Dir()
|
||||
baseName = p.File().TranslationBaseName()
|
||||
contentBaseName = p.File().ContentBaseName()
|
||||
classifier = p.File().FileInfo().Meta().Classifier
|
||||
}
|
||||
|
||||
if baseName != contentBaseName {
|
||||
if classifier == files.ContentClassLeaf {
|
||||
// See https://github.com/gohugoio/hugo/issues/4870
|
||||
// A leaf bundle
|
||||
dir = strings.TrimSuffix(dir, contentBaseName+helpers.FilePathSeparator)
|
||||
@@ -143,22 +146,26 @@ func createTargetPathDescriptor(s *Site, p page.Page, pm *pageMeta) (page.Target
|
||||
desc.PrefixFilePath = s.getLanguageTargetPathLang(alwaysInSubDir)
|
||||
desc.PrefixLink = s.getLanguagePermalinkLang(alwaysInSubDir)
|
||||
|
||||
// Expand only page.KindPage and page.KindTaxonomy; don't expand other Kinds of Pages
|
||||
// like page.KindSection or page.KindTaxonomyTerm because they are "shallower" and
|
||||
// the permalink configuration values are likely to be redundant, e.g.
|
||||
// naively expanding /category/:slug/ would give /category/categories/ for
|
||||
// the "categories" page.KindTaxonomyTerm.
|
||||
if p.Kind() == page.KindPage || p.Kind() == page.KindTerm {
|
||||
opath, err := d.ResourceSpec.Permalinks.Expand(p.Section(), p)
|
||||
if err != nil {
|
||||
return desc, err
|
||||
opath, err := d.ResourceSpec.Permalinks.Expand(p.Section(), p)
|
||||
if err != nil {
|
||||
return desc, err
|
||||
}
|
||||
|
||||
if opath != "" {
|
||||
opath, _ = url.QueryUnescape(opath)
|
||||
if strings.HasSuffix(opath, "//") {
|
||||
// When rewriting the _index of the section the permalink config is applied to,
|
||||
// we get douple slashes at the end sometimes; clear them up here
|
||||
opath = strings.TrimSuffix(opath, "/")
|
||||
}
|
||||
|
||||
if opath != "" {
|
||||
opath, _ = url.QueryUnescape(opath)
|
||||
desc.ExpandedPermalink = opath
|
||||
}
|
||||
desc.ExpandedPermalink = opath
|
||||
|
||||
if !p.File().IsZero() {
|
||||
s.Log.Debugf("Set expanded permalink path for %s %s to %#v", p.Kind(), p.File().Path(), opath)
|
||||
} else {
|
||||
s.Log.Debugf("Set expanded permalink path for %s in %v to %#v", p.Kind(), desc.Sections, opath)
|
||||
}
|
||||
}
|
||||
|
||||
return desc, nil
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/bep/clock"
|
||||
"github.com/bep/clocks"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/markup/asciidocext"
|
||||
"github.com/gohugoio/hugo/markup/rst"
|
||||
@@ -1502,8 +1502,8 @@ func TestShouldBuild(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestShouldBuildWithClock(t *testing.T) {
|
||||
htime.Clock = clock.Start(time.Date(2021, 11, 17, 20, 34, 58, 651387237, time.UTC))
|
||||
t.Cleanup(func() { htime.Clock = clock.System() })
|
||||
htime.Clock = clocks.Start(time.Date(2021, 11, 17, 20, 34, 58, 651387237, time.UTC))
|
||||
t.Cleanup(func() { htime.Clock = clocks.System() })
|
||||
past := time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
|
||||
future := time.Date(2037, 11, 17, 20, 34, 58, 651387237, time.UTC)
|
||||
zero := time.Time{}
|
||||
|
||||
@@ -1162,6 +1162,91 @@ Some text.
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassCollectorConfigWriteStats(t *testing.T) {
|
||||
r := func(writeStatsConfig string) *IntegrationTestBuilder {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
WRITE_STATS_CONFIG
|
||||
-- layouts/_default/list.html --
|
||||
<div id="myid" class="myclass">Foo</div>
|
||||
|
||||
`
|
||||
files = strings.Replace(files, "WRITE_STATS_CONFIG", writeStatsConfig, 1)
|
||||
|
||||
b := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
NeedsOsFS: true,
|
||||
},
|
||||
).Build()
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// Legacy config.
|
||||
b := r(`
|
||||
[build]
|
||||
writeStats = true
|
||||
`)
|
||||
|
||||
b.AssertFileContent("hugo_stats.json", "myclass", "div", "myid")
|
||||
|
||||
b = r(`
|
||||
[build]
|
||||
writeStats = false
|
||||
`)
|
||||
|
||||
b.AssertDestinationExists("hugo_stats.json", false)
|
||||
|
||||
b = r(`
|
||||
[build.buildStats]
|
||||
enable = true
|
||||
`)
|
||||
|
||||
b.AssertFileContent("hugo_stats.json", "myclass", "div", "myid")
|
||||
|
||||
b = r(`
|
||||
[build.buildStats]
|
||||
enable = true
|
||||
disableids = true
|
||||
`)
|
||||
|
||||
b.AssertFileContent("hugo_stats.json", "myclass", "div", "! myid")
|
||||
|
||||
b = r(`
|
||||
[build.buildStats]
|
||||
enable = true
|
||||
disableclasses = true
|
||||
`)
|
||||
|
||||
b.AssertFileContent("hugo_stats.json", "! myclass", "div", "myid")
|
||||
|
||||
b = r(`
|
||||
[build.buildStats]
|
||||
enable = true
|
||||
disabletags = true
|
||||
`)
|
||||
|
||||
b.AssertFileContent("hugo_stats.json", "myclass", "! div", "myid")
|
||||
|
||||
b = r(`
|
||||
[build.buildStats]
|
||||
enable = true
|
||||
disabletags = true
|
||||
disableclasses = true
|
||||
`)
|
||||
|
||||
b.AssertFileContent("hugo_stats.json", "! myclass", "! div", "myid")
|
||||
|
||||
b = r(`
|
||||
[build.buildStats]
|
||||
enable = false
|
||||
`)
|
||||
b.AssertDestinationExists("hugo_stats.json", false)
|
||||
|
||||
}
|
||||
|
||||
func TestClassCollectorStress(t *testing.T) {
|
||||
statsFilename := "hugo_stats.json"
|
||||
defer os.Remove(statsFilename)
|
||||
|
||||
@@ -160,3 +160,25 @@ Do not go gentle into that good night.
|
||||
th.assertFileContent(filepath.Join("public", "ss1", "index.html"), "P1|URL: /ss1/|Next: /ss1/page/2/")
|
||||
th.assertFileContent(filepath.Join("public", "ss1", "page", "2", "index.html"), "P2|URL: /ss1/page/2/|Next: /ss1/page/3/")
|
||||
}
|
||||
|
||||
func TestSectionsEntries(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
-- content/withfile/_index.md --
|
||||
-- content/withoutfile/p1.md --
|
||||
-- layouts/_default/list.html --
|
||||
SectionsEntries: {{ .SectionsEntries }}
|
||||
|
||||
|
||||
`
|
||||
|
||||
b := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
},
|
||||
).Build()
|
||||
|
||||
b.AssertFileContent("public/withfile/index.html", "SectionsEntries: [withfile]")
|
||||
b.AssertFileContent("public/withoutfile/index.html", "SectionsEntries: [withoutfile]")
|
||||
}
|
||||
|
||||
@@ -834,7 +834,9 @@ func (s *sitesBuilder) GetPageRel(p page.Page, ref string) page.Page {
|
||||
|
||||
func (s *sitesBuilder) NpmInstall() hexec.Runner {
|
||||
sc := security.DefaultConfig
|
||||
sc.Exec.Allow = security.NewWhitelist("npm")
|
||||
var err error
|
||||
sc.Exec.Allow, err = security.NewWhitelist("npm")
|
||||
s.Assert(err, qt.IsNil)
|
||||
ex := hexec.New(sc)
|
||||
command, err := ex.New("npm", "install")
|
||||
s.Assert(err, qt.IsNil)
|
||||
|
||||
+5
-2
@@ -1,7 +1,10 @@
|
||||
# Release env.
|
||||
# These will be replaced by script before release.
|
||||
HUGORELEASER_TAG=v0.114.0
|
||||
HUGORELEASER_COMMITISH=9df2ec7988e5a217a14901cc76c0b7e76b2e9f02
|
||||
HUGORELEASER_TAG=v0.115.1
|
||||
HUGORELEASER_COMMITISH=857374e69358f788bd31ddc55255c5c8e3dcfd80
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -119,7 +119,7 @@ func (t Translator) initFuncs(bndl *i18n.Bundle) {
|
||||
t.logger.Warnf("Failed to get translated string for language %q and ID %q: %s", currentLangStr, translationID, err)
|
||||
}
|
||||
|
||||
if t.cfg.LogI18nWarnings() {
|
||||
if t.cfg.PrintI18nWarnings() {
|
||||
t.logger.Warnf("i18n|MISSING_TRANSLATION|%s|%s", currentLangStr, translationID)
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,9 @@ func TestConvert(t *testing.T) {
|
||||
}
|
||||
c := qt.New(t)
|
||||
sc := security.DefaultConfig
|
||||
sc.Exec.Allow = security.NewWhitelist("pandoc")
|
||||
var err error
|
||||
sc.Exec.Allow, err = security.NewWhitelist("pandoc")
|
||||
c.Assert(err, qt.IsNil)
|
||||
p, err := Provider.New(converter.ProviderConfig{Exec: hexec.New(sc), Logger: loggers.NewDefault()})
|
||||
c.Assert(err, qt.IsNil)
|
||||
conv, err := p.New(converter.DocumentContext{})
|
||||
|
||||
@@ -31,7 +31,7 @@ func TestConvert(t *testing.T) {
|
||||
}
|
||||
c := qt.New(t)
|
||||
sc := security.DefaultConfig
|
||||
sc.Exec.Allow = security.NewWhitelist("rst", "python")
|
||||
sc.Exec.Allow = security.MustNewWhitelist("rst", "python")
|
||||
|
||||
p, err := Provider.New(
|
||||
converter.ProviderConfig{
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
|
||||
"golang.org/x/net/html"
|
||||
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
)
|
||||
|
||||
@@ -46,8 +47,9 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func newHTMLElementsCollector() *htmlElementsCollector {
|
||||
func newHTMLElementsCollector(conf config.BuildStats) *htmlElementsCollector {
|
||||
return &htmlElementsCollector{
|
||||
conf: conf,
|
||||
elementSet: make(map[string]bool),
|
||||
}
|
||||
}
|
||||
@@ -93,6 +95,8 @@ type htmlElement struct {
|
||||
}
|
||||
|
||||
type htmlElementsCollector struct {
|
||||
conf config.BuildStats
|
||||
|
||||
// Contains the raw HTML string. We will get the same element
|
||||
// several times, and want to avoid costly reparsing when this
|
||||
// is used for aggregated data only.
|
||||
@@ -113,7 +117,9 @@ func (c *htmlElementsCollector) getHTMLElements() HTMLElements {
|
||||
for _, el := range c.elements {
|
||||
classes = append(classes, el.Classes...)
|
||||
ids = append(ids, el.IDs...)
|
||||
tags = append(tags, el.Tag)
|
||||
if !c.conf.DisableTags {
|
||||
tags = append(tags, el.Tag)
|
||||
}
|
||||
}
|
||||
|
||||
classes = helpers.UniqueStringsSorted(classes)
|
||||
@@ -246,7 +252,7 @@ func (w *htmlElementsCollectorWriter) lexElementInside(resolve htmlCollectorStat
|
||||
}
|
||||
|
||||
// Parse each collected element.
|
||||
el, err := parseHTMLElement(s)
|
||||
el, err := w.parseHTMLElement(s)
|
||||
if err != nil {
|
||||
w.err = err
|
||||
return resolve
|
||||
@@ -363,7 +369,8 @@ func htmlLexToEndOfComment(w *htmlElementsCollectorWriter) htmlCollectorStateFun
|
||||
return htmlLexToEndOfComment
|
||||
}
|
||||
|
||||
func parseHTMLElement(elStr string) (el htmlElement, err error) {
|
||||
func (w *htmlElementsCollectorWriter) parseHTMLElement(elStr string) (el htmlElement, err error) {
|
||||
conf := w.collector.conf
|
||||
|
||||
tagName := parseStartTag(elStr)
|
||||
|
||||
@@ -390,8 +397,13 @@ func parseHTMLElement(elStr string) (el htmlElement, err error) {
|
||||
switch {
|
||||
case strings.EqualFold(a.Key, "id"):
|
||||
// There should be only one, but one never knows...
|
||||
el.IDs = append(el.IDs, a.Val)
|
||||
if !conf.DisableIDs {
|
||||
el.IDs = append(el.IDs, a.Val)
|
||||
}
|
||||
default:
|
||||
if conf.DisableClasses {
|
||||
continue
|
||||
}
|
||||
if classAttrRe.MatchString(a.Key) {
|
||||
el.Classes = append(el.Classes, strings.Fields(a.Val)...)
|
||||
} else {
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/config/testconfig"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
"github.com/gohugoio/hugo/minifiers"
|
||||
@@ -136,7 +137,9 @@ func TestClassCollector(t *testing.T) {
|
||||
} {
|
||||
|
||||
c.Run(fmt.Sprintf("%s--minify-%t", test.name, variant.minify), func(c *qt.C) {
|
||||
w := newHTMLElementsCollectorWriter(newHTMLElementsCollector())
|
||||
w := newHTMLElementsCollectorWriter(newHTMLElementsCollector(
|
||||
config.BuildStats{Enable: true},
|
||||
))
|
||||
if variant.minify {
|
||||
if skipMinifyTest[test.name] {
|
||||
c.Skip("skip minify test")
|
||||
@@ -240,7 +243,9 @@ func BenchmarkElementsCollectorWriter(b *testing.B) {
|
||||
</html>
|
||||
`
|
||||
for i := 0; i < b.N; i++ {
|
||||
w := newHTMLElementsCollectorWriter(newHTMLElementsCollector())
|
||||
w := newHTMLElementsCollectorWriter(newHTMLElementsCollector(
|
||||
config.BuildStats{Enable: true},
|
||||
))
|
||||
fmt.Fprint(w, benchHTML)
|
||||
|
||||
}
|
||||
@@ -262,7 +267,9 @@ func BenchmarkElementsCollectorWriterPre(b *testing.B) {
|
||||
<div class="foo"></div>
|
||||
|
||||
`
|
||||
w := newHTMLElementsCollectorWriter(newHTMLElementsCollector())
|
||||
w := newHTMLElementsCollectorWriter(newHTMLElementsCollector(
|
||||
config.BuildStats{Enable: true},
|
||||
))
|
||||
for i := 0; i < b.N; i++ {
|
||||
fmt.Fprint(w, benchHTML)
|
||||
|
||||
|
||||
@@ -81,8 +81,8 @@ func NewDestinationPublisher(rs *resources.Spec, outputFormats output.Formats, m
|
||||
fs := rs.BaseFs.PublishFs
|
||||
cfg := rs.Cfg
|
||||
var classCollector *htmlElementsCollector
|
||||
if rs.BuildConfig().WriteStats {
|
||||
classCollector = newHTMLElementsCollector()
|
||||
if rs.BuildConfig().BuildStats.Enabled() {
|
||||
classCollector = newHTMLElementsCollector(rs.BuildConfig().BuildStats)
|
||||
}
|
||||
pub = DestinationPublisher{fs: fs, htmlElementsCollector: classCollector}
|
||||
pub.min, err = minifiers.New(mediaTypes, outputFormats, cfg)
|
||||
|
||||
@@ -221,7 +221,7 @@ func generateFileIsZeroWrappers(c *codegen.Inspector) error {
|
||||
methods := c.MethodsFromTypes([]reflect.Type{reflect.TypeOf((*source.File)(nil)).Elem()}, nil)
|
||||
|
||||
for _, m := range methods {
|
||||
if m.Name == "IsZero" {
|
||||
if m.Name == "IsZero" || m.Name == "Classifier" {
|
||||
continue
|
||||
}
|
||||
fmt.Fprint(&buff, m.DeclarationNamed("zeroFile"))
|
||||
@@ -255,6 +255,11 @@ func (zeroFile) IsZero() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (z zeroFile) Classifier() files.ContentClass {
|
||||
z.log.Warnln(".File.Classifier on zero object. Wrap it in if or with: {{ with .File }}{{ .Classifier }}{{ end }}")
|
||||
return files.ContentClassZero
|
||||
}
|
||||
|
||||
%s
|
||||
|
||||
`, header, importsString(pkgImports), buff.String())
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
|
||||
"errors"
|
||||
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
)
|
||||
|
||||
@@ -35,7 +36,7 @@ type PermalinkExpander struct {
|
||||
// to be used to replace that tag.
|
||||
knownPermalinkAttributes map[string]pageToPermaAttribute
|
||||
|
||||
expanders map[string]func(Page) (string, error)
|
||||
expanders map[string]map[string]func(Page) (string, error)
|
||||
|
||||
urlize func(uri string) string
|
||||
}
|
||||
@@ -68,7 +69,7 @@ func (p PermalinkExpander) callback(attr string) (pageToPermaAttribute, bool) {
|
||||
|
||||
// NewPermalinkExpander creates a new PermalinkExpander configured by the given
|
||||
// urlize func.
|
||||
func NewPermalinkExpander(urlize func(uri string) string, patterns map[string]string) (PermalinkExpander, error) {
|
||||
func NewPermalinkExpander(urlize func(uri string) string, patterns map[string]map[string]string) (PermalinkExpander, error) {
|
||||
p := PermalinkExpander{urlize: urlize}
|
||||
|
||||
p.knownPermalinkAttributes = map[string]pageToPermaAttribute{
|
||||
@@ -87,12 +88,15 @@ func NewPermalinkExpander(urlize func(uri string) string, patterns map[string]st
|
||||
"filename": p.pageToPermalinkFilename,
|
||||
}
|
||||
|
||||
e, err := p.parse(patterns)
|
||||
if err != nil {
|
||||
return p, err
|
||||
}
|
||||
p.expanders = make(map[string]map[string]func(Page) (string, error))
|
||||
|
||||
p.expanders = e
|
||||
for kind, patterns := range patterns {
|
||||
e, err := p.parse(patterns)
|
||||
if err != nil {
|
||||
return p, err
|
||||
}
|
||||
p.expanders[kind] = e
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
@@ -100,7 +104,13 @@ func NewPermalinkExpander(urlize func(uri string) string, patterns map[string]st
|
||||
// Expand expands the path in p according to the rules defined for the given key.
|
||||
// If no rules are found for the given key, an empty string is returned.
|
||||
func (l PermalinkExpander) Expand(key string, p Page) (string, error) {
|
||||
expand, found := l.expanders[key]
|
||||
expanders, found := l.expanders[p.Kind()]
|
||||
|
||||
if !found {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
expand, found := expanders[key]
|
||||
|
||||
if !found {
|
||||
return "", nil
|
||||
@@ -247,11 +257,13 @@ func (l PermalinkExpander) pageToPermalinkTitle(p Page, _ string) (string, error
|
||||
|
||||
// pageToPermalinkFilename returns the URL-safe form of the filename
|
||||
func (l PermalinkExpander) pageToPermalinkFilename(p Page, _ string) (string, error) {
|
||||
name := p.File().TranslationBaseName()
|
||||
name := l.translationBaseName(p)
|
||||
if name == "index" {
|
||||
// Page bundles; the directory name will hopefully have a better name.
|
||||
dir := strings.TrimSuffix(p.File().Dir(), helpers.FilePathSeparator)
|
||||
_, name = filepath.Split(dir)
|
||||
} else if name == "_index" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return l.urlize(name), nil
|
||||
@@ -281,6 +293,13 @@ func (l PermalinkExpander) pageToPermalinkSections(p Page, _ string) (string, er
|
||||
return p.CurrentSection().SectionsPath(), nil
|
||||
}
|
||||
|
||||
func (l PermalinkExpander) translationBaseName(p Page) string {
|
||||
if p.File().IsZero() {
|
||||
return ""
|
||||
}
|
||||
return p.File().TranslationBaseName()
|
||||
}
|
||||
|
||||
var (
|
||||
nilSliceFunc = func(s []string) []string {
|
||||
return nil
|
||||
@@ -333,7 +352,10 @@ func (l PermalinkExpander) toSliceFunc(cut string) func(s []string) []string {
|
||||
return func(ss []string) int {
|
||||
// Prevent out of bound situations. It would not make
|
||||
// much sense to panic here.
|
||||
if n > len(ss) {
|
||||
if n >= len(ss) {
|
||||
if low {
|
||||
return -1
|
||||
}
|
||||
return len(ss)
|
||||
}
|
||||
return n
|
||||
@@ -349,7 +371,11 @@ func (l PermalinkExpander) toSliceFunc(cut string) func(s []string) []string {
|
||||
if len(s) == 0 {
|
||||
return nil
|
||||
}
|
||||
v := s[toN(s)]
|
||||
n := toN(s)
|
||||
if n < 0 {
|
||||
return []string{}
|
||||
}
|
||||
v := s[n]
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
@@ -363,7 +389,59 @@ func (l PermalinkExpander) toSliceFunc(cut string) func(s []string) []string {
|
||||
if len(s) == 0 {
|
||||
return nil
|
||||
}
|
||||
return s[toN1(s):toN2(s)]
|
||||
n1, n2 := toN1(s), toN2(s)
|
||||
if n1 < 0 || n2 < 0 {
|
||||
return []string{}
|
||||
}
|
||||
return s[n1:n2]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var permalinksKindsSuppurt = []string{KindPage, KindSection, KindTaxonomy, KindTerm}
|
||||
|
||||
// DecodePermalinksConfig decodes the permalinks configuration in the given map
|
||||
func DecodePermalinksConfig(m map[string]any) (map[string]map[string]string, error) {
|
||||
permalinksConfig := make(map[string]map[string]string)
|
||||
|
||||
permalinksConfig[KindPage] = make(map[string]string)
|
||||
permalinksConfig[KindSection] = make(map[string]string)
|
||||
permalinksConfig[KindTaxonomy] = make(map[string]string)
|
||||
permalinksConfig[KindTerm] = make(map[string]string)
|
||||
|
||||
config := maps.CleanConfigStringMap(m)
|
||||
for k, v := range config {
|
||||
switch v := v.(type) {
|
||||
case string:
|
||||
// [permalinks]
|
||||
// key = '...'
|
||||
|
||||
// To sucessfully be backward compatible, "default" patterns need to be set for both page and term
|
||||
permalinksConfig[KindPage][k] = v
|
||||
permalinksConfig[KindTerm][k] = v
|
||||
|
||||
case maps.Params:
|
||||
// [permalinks.key]
|
||||
// xyz = ???
|
||||
|
||||
if helpers.InStringArray(permalinksKindsSuppurt, k) {
|
||||
// TODO: warn if we overwrite an already set value
|
||||
for k2, v2 := range v {
|
||||
switch v2 := v2.(type) {
|
||||
case string:
|
||||
permalinksConfig[k][k2] = v2
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("permalinks configuration invalid: unknown value %q for key %q for kind %q", v2, k2, k)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("permalinks configuration not supported for kind %q, supported kinds are %v", k, permalinksKindsSuppurt)
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("permalinks configuration invalid: unknown value %q for key %q", v, k)
|
||||
}
|
||||
}
|
||||
return permalinksConfig, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package page_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/bep/logg"
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
)
|
||||
|
||||
func TestPermalinks(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- layouts/_default/list.html --
|
||||
List|{{ .Kind }}|{{ .RelPermalink }}|
|
||||
-- layouts/_default/single.html --
|
||||
Single|{{ .Kind }}|{{ .RelPermalink }}|
|
||||
-- hugo.toml --
|
||||
[taxonomies]
|
||||
tag = "tags"
|
||||
[permalinks.page]
|
||||
withpageslug = '/pageslug/:slug/'
|
||||
withallbutlastsection = '/:sections[:last]/:slug/'
|
||||
[permalinks.section]
|
||||
withfilefilename = '/sectionwithfilefilename/:filename/'
|
||||
withfilefiletitle = '/sectionwithfilefiletitle/:title/'
|
||||
withfileslug = '/sectionwithfileslug/:slug/'
|
||||
nofileslug = '/sectionnofileslug/:slug/'
|
||||
nofilefilename = '/sectionnofilefilename/:filename/'
|
||||
nofiletitle1 = '/sectionnofiletitle1/:title/'
|
||||
nofiletitle2 = '/sectionnofiletitle2/:sections[:last]/'
|
||||
[permalinks.term]
|
||||
tags = '/tagsslug/tag/:slug/'
|
||||
[permalinks.taxonomy]
|
||||
tags = '/tagsslug/:slug/'
|
||||
-- content/withpageslug/p1.md --
|
||||
---
|
||||
slug: "p1slugvalue"
|
||||
tags: ["mytag"]
|
||||
---
|
||||
-- content/withfilefilename/_index.md --
|
||||
-- content/withfileslug/_index.md --
|
||||
---
|
||||
slug: "withfileslugvalue"
|
||||
---
|
||||
-- content/nofileslug/p1.md --
|
||||
-- content/nofilefilename/p1.md --
|
||||
-- content/nofiletitle1/p1.md --
|
||||
-- content/nofiletitle2/asdf/p1.md --
|
||||
-- content/withallbutlastsection/subsection/p1.md --
|
||||
-- content/tags/_index.md --
|
||||
---
|
||||
slug: "tagsslug"
|
||||
---
|
||||
-- content/tags/mytag/_index.md --
|
||||
---
|
||||
slug: "mytagslug"
|
||||
---
|
||||
|
||||
|
||||
`
|
||||
|
||||
b := hugolib.NewIntegrationTestBuilder(
|
||||
hugolib.IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
LogLevel: logg.LevelWarn,
|
||||
}).Build()
|
||||
|
||||
t.Log(b.LogString())
|
||||
// No .File.TranslationBaseName on zero object etc. warnings.
|
||||
b.Assert(b.H.Log.LoggCount(logg.LevelWarn), qt.Equals, 0)
|
||||
b.AssertFileContent("public/pageslug/p1slugvalue/index.html", "Single|page|/pageslug/p1slugvalue/|")
|
||||
b.AssertFileContent("public/sectionwithfilefilename/index.html", "List|section|/sectionwithfilefilename/|")
|
||||
b.AssertFileContent("public/sectionwithfileslug/withfileslugvalue/index.html", "List|section|/sectionwithfileslug/withfileslugvalue/|")
|
||||
b.AssertFileContent("public/sectionnofilefilename/index.html", "List|section|/sectionnofilefilename/|")
|
||||
b.AssertFileContent("public/sectionnofileslug/nofileslugs/index.html", "List|section|/sectionnofileslug/nofileslugs/|")
|
||||
b.AssertFileContent("public/sectionnofiletitle1/nofiletitle1s/index.html", "List|section|/sectionnofiletitle1/nofiletitle1s/|")
|
||||
b.AssertFileContent("public/sectionnofiletitle2/index.html", "List|section|/sectionnofiletitle2/|")
|
||||
|
||||
b.AssertFileContent("public/tagsslug/tag/mytagslug/index.html", "List|term|/tagsslug/tag/mytagslug/|")
|
||||
b.AssertFileContent("public/tagsslug/tagsslug/index.html", "List|taxonomy|/tagsslug/tagsslug/|")
|
||||
|
||||
permalinksConf := b.H.Configs.Base.Permalinks
|
||||
b.Assert(permalinksConf, qt.DeepEquals, map[string]map[string]string{
|
||||
"page": {"withallbutlastsection": "/:sections[:last]/:slug/", "withpageslug": "/pageslug/:slug/"},
|
||||
"section": {"nofilefilename": "/sectionnofilefilename/:filename/", "nofileslug": "/sectionnofileslug/:slug/", "nofiletitle1": "/sectionnofiletitle1/:title/", "nofiletitle2": "/sectionnofiletitle2/:sections[:last]/", "withfilefilename": "/sectionwithfilefilename/:filename/", "withfilefiletitle": "/sectionwithfilefiletitle/:title/", "withfileslug": "/sectionwithfileslug/:slug/"},
|
||||
"taxonomy": {"tags": "/tagsslug/:slug/"},
|
||||
"term": {"tags": "/tagsslug/tag/:slug/"},
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestPermalinksOldSetup(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- layouts/_default/list.html --
|
||||
List|{{ .Kind }}|{{ .RelPermalink }}|
|
||||
-- layouts/_default/single.html --
|
||||
Single|{{ .Kind }}|{{ .RelPermalink }}|
|
||||
-- hugo.toml --
|
||||
[permalinks]
|
||||
withpageslug = '/pageslug/:slug/'
|
||||
-- content/withpageslug/p1.md --
|
||||
---
|
||||
slug: "p1slugvalue"
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
`
|
||||
|
||||
b := hugolib.NewIntegrationTestBuilder(
|
||||
hugolib.IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
LogLevel: logg.LevelWarn,
|
||||
}).Build()
|
||||
|
||||
t.Log(b.LogString())
|
||||
// No .File.TranslationBaseName on zero object etc. warnings.
|
||||
b.Assert(b.H.Log.LoggCount(logg.LevelWarn), qt.Equals, 0)
|
||||
b.AssertFileContent("public/pageslug/p1slugvalue/index.html", "Single|page|/pageslug/p1slugvalue/|")
|
||||
|
||||
permalinksConf := b.H.Configs.Base.Permalinks
|
||||
b.Assert(permalinksConf, qt.DeepEquals, map[string]map[string]string{
|
||||
"page": {"withpageslug": "/pageslug/:slug/"},
|
||||
"section": {},
|
||||
"taxonomy": {},
|
||||
"term": {"withpageslug": "/pageslug/:slug/"},
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestPermalinksNestedSections(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
[permalinks.page]
|
||||
books = '/libros/:sections[1:]/:filename'
|
||||
|
||||
[permalinks.section]
|
||||
books = '/libros/:sections[1:]'
|
||||
-- content/books/_index.md --
|
||||
---
|
||||
title: Books
|
||||
---
|
||||
-- content/books/fiction/_index.md --
|
||||
---
|
||||
title: Fiction
|
||||
---
|
||||
-- content/books/fiction/2023/_index.md --
|
||||
---
|
||||
title: 2023
|
||||
---
|
||||
-- content/books/fiction/2023/book1/index.md --
|
||||
---
|
||||
title: Book 1
|
||||
---
|
||||
-- layouts/_default/single.html --
|
||||
Single.
|
||||
-- layouts/_default/list.html --
|
||||
List.
|
||||
`
|
||||
|
||||
b := hugolib.NewIntegrationTestBuilder(
|
||||
hugolib.IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
LogLevel: logg.LevelWarn,
|
||||
}).Build()
|
||||
|
||||
t.Log(b.LogString())
|
||||
// No .File.TranslationBaseName on zero object etc. warnings.
|
||||
b.Assert(b.H.Log.LoggCount(logg.LevelWarn), qt.Equals, 0)
|
||||
|
||||
b.AssertFileContent("public/libros/index.html", "List.")
|
||||
b.AssertFileContent("public/libros/fiction/index.html", "List.")
|
||||
b.AssertFileContent("public/libros/fiction/2023/book1/index.html", "Single.")
|
||||
|
||||
}
|
||||
@@ -69,6 +69,7 @@ func TestPermalinkExpansion(t *testing.T) {
|
||||
page.date = d
|
||||
page.section = "blue"
|
||||
page.slug = "The Slug"
|
||||
page.kind = "page"
|
||||
|
||||
for _, item := range testdataPermalinks {
|
||||
if !item.valid {
|
||||
@@ -79,8 +80,10 @@ func TestPermalinkExpansion(t *testing.T) {
|
||||
name := specNameCleaner.ReplaceAllString(item.spec, "")
|
||||
|
||||
c.Run(name, func(c *qt.C) {
|
||||
patterns := map[string]string{
|
||||
"posts": item.spec,
|
||||
patterns := map[string]map[string]string{
|
||||
"page": {
|
||||
"posts": item.spec,
|
||||
},
|
||||
}
|
||||
expander, err := NewPermalinkExpander(urlize, patterns)
|
||||
c.Assert(err, qt.IsNil)
|
||||
@@ -103,14 +106,18 @@ func TestPermalinkExpansionMultiSection(t *testing.T) {
|
||||
page.date = d
|
||||
page.section = "blue"
|
||||
page.slug = "The Slug"
|
||||
page.kind = "page"
|
||||
|
||||
page_slug_fallback := newTestPageWithFile("/page-filename/index.md")
|
||||
page_slug_fallback.title = "Page Title"
|
||||
page_slug_fallback.kind = "page"
|
||||
|
||||
permalinksConfig := map[string]string{
|
||||
"posts": "/:slug",
|
||||
"blog": "/:section/:year",
|
||||
"recipes": "/:slugorfilename",
|
||||
permalinksConfig := map[string]map[string]string{
|
||||
"page": {
|
||||
"posts": "/:slug",
|
||||
"blog": "/:section/:year",
|
||||
"recipes": "/:slugorfilename",
|
||||
},
|
||||
}
|
||||
expander, err := NewPermalinkExpander(urlize, permalinksConfig)
|
||||
c.Assert(err, qt.IsNil)
|
||||
@@ -137,8 +144,10 @@ func TestPermalinkExpansionConcurrent(t *testing.T) {
|
||||
|
||||
c := qt.New(t)
|
||||
|
||||
permalinksConfig := map[string]string{
|
||||
"posts": "/:slug/",
|
||||
permalinksConfig := map[string]map[string]string{
|
||||
"page": {
|
||||
"posts": "/:slug/",
|
||||
},
|
||||
}
|
||||
|
||||
expander, err := NewPermalinkExpander(urlize, permalinksConfig)
|
||||
@@ -151,6 +160,7 @@ func TestPermalinkExpansionConcurrent(t *testing.T) {
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
page := newTestPage()
|
||||
page.kind = "page"
|
||||
for j := 1; j < 20; j++ {
|
||||
page.slug = fmt.Sprintf("slug%d", i+j)
|
||||
expanded, err := expander.Expand("posts", page)
|
||||
@@ -169,36 +179,45 @@ func TestPermalinkExpansionSliceSyntax(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
exp, err := NewPermalinkExpander(urlize, nil)
|
||||
c.Assert(err, qt.IsNil)
|
||||
slice := []string{"a", "b", "c", "d"}
|
||||
fn := func(s string) []string {
|
||||
return exp.toSliceFunc(s)(slice)
|
||||
slice4 := []string{"a", "b", "c", "d"}
|
||||
fn4 := func(s string) []string {
|
||||
return exp.toSliceFunc(s)(slice4)
|
||||
}
|
||||
|
||||
slice1 := []string{"a"}
|
||||
fn1 := func(s string) []string {
|
||||
return exp.toSliceFunc(s)(slice1)
|
||||
}
|
||||
|
||||
c.Run("Basic", func(c *qt.C) {
|
||||
c.Assert(fn("[1:3]"), qt.DeepEquals, []string{"b", "c"})
|
||||
c.Assert(fn("[1:]"), qt.DeepEquals, []string{"b", "c", "d"})
|
||||
c.Assert(fn("[:2]"), qt.DeepEquals, []string{"a", "b"})
|
||||
c.Assert(fn("[0:2]"), qt.DeepEquals, []string{"a", "b"})
|
||||
c.Assert(fn("[:]"), qt.DeepEquals, []string{"a", "b", "c", "d"})
|
||||
c.Assert(fn(""), qt.DeepEquals, []string{"a", "b", "c", "d"})
|
||||
c.Assert(fn("[last]"), qt.DeepEquals, []string{"d"})
|
||||
c.Assert(fn("[:last]"), qt.DeepEquals, []string{"a", "b", "c"})
|
||||
c.Assert(fn4("[1:3]"), qt.DeepEquals, []string{"b", "c"})
|
||||
c.Assert(fn4("[1:]"), qt.DeepEquals, []string{"b", "c", "d"})
|
||||
c.Assert(fn4("[:2]"), qt.DeepEquals, []string{"a", "b"})
|
||||
c.Assert(fn4("[0:2]"), qt.DeepEquals, []string{"a", "b"})
|
||||
c.Assert(fn4("[:]"), qt.DeepEquals, []string{"a", "b", "c", "d"})
|
||||
c.Assert(fn4(""), qt.DeepEquals, []string{"a", "b", "c", "d"})
|
||||
c.Assert(fn4("[last]"), qt.DeepEquals, []string{"d"})
|
||||
c.Assert(fn4("[:last]"), qt.DeepEquals, []string{"a", "b", "c"})
|
||||
c.Assert(fn1("[last]"), qt.DeepEquals, []string{"a"})
|
||||
c.Assert(fn1("[:last]"), qt.DeepEquals, []string{})
|
||||
c.Assert(fn1("[1:last]"), qt.DeepEquals, []string{})
|
||||
c.Assert(fn1("[1]"), qt.DeepEquals, []string{})
|
||||
|
||||
})
|
||||
|
||||
c.Run("Out of bounds", func(c *qt.C) {
|
||||
c.Assert(fn("[1:5]"), qt.DeepEquals, []string{"b", "c", "d"})
|
||||
c.Assert(fn("[-1:5]"), qt.DeepEquals, []string{"a", "b", "c", "d"})
|
||||
c.Assert(fn("[5:]"), qt.DeepEquals, []string{})
|
||||
c.Assert(fn("[5:]"), qt.DeepEquals, []string{})
|
||||
c.Assert(fn("[5:32]"), qt.DeepEquals, []string{})
|
||||
c.Assert(fn4("[1:5]"), qt.DeepEquals, []string{"b", "c", "d"})
|
||||
c.Assert(fn4("[-1:5]"), qt.DeepEquals, []string{"a", "b", "c", "d"})
|
||||
c.Assert(fn4("[5:]"), qt.DeepEquals, []string{})
|
||||
c.Assert(fn4("[5:]"), qt.DeepEquals, []string{})
|
||||
c.Assert(fn4("[5:32]"), qt.DeepEquals, []string{})
|
||||
c.Assert(exp.toSliceFunc("[:1]")(nil), qt.DeepEquals, []string(nil))
|
||||
c.Assert(exp.toSliceFunc("[:1]")([]string{}), qt.DeepEquals, []string(nil))
|
||||
|
||||
// These all return nil
|
||||
c.Assert(fn("[]"), qt.IsNil)
|
||||
c.Assert(fn("[1:}"), qt.IsNil)
|
||||
c.Assert(fn("foo"), qt.IsNil)
|
||||
c.Assert(fn4("[]"), qt.IsNil)
|
||||
c.Assert(fn4("[1:}"), qt.IsNil)
|
||||
c.Assert(fn4("foo"), qt.IsNil)
|
||||
|
||||
})
|
||||
|
||||
@@ -209,9 +228,12 @@ func BenchmarkPermalinkExpand(b *testing.B) {
|
||||
page.title = "Hugo Rocks"
|
||||
d, _ := time.Parse("2006-01-02", "2019-02-28")
|
||||
page.date = d
|
||||
page.kind = "page"
|
||||
|
||||
permalinksConfig := map[string]string{
|
||||
"posts": "/:year-:month-:title",
|
||||
permalinksConfig := map[string]map[string]string{
|
||||
"page": {
|
||||
"posts": "/:year-:month-:title",
|
||||
},
|
||||
}
|
||||
expander, err := NewPermalinkExpander(urlize, permalinksConfig)
|
||||
if err != nil {
|
||||
|
||||
+11
-9
@@ -41,6 +41,17 @@ environment:
|
||||
pandoc_datadir: $SNAP/usr/share/pandoc
|
||||
PYTHONHOME: /usr:$SNAP/usr
|
||||
RUBYLIB: $SNAP/usr/lib/ruby/vendor_ruby/2.7.0:$SNAP/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/ruby/vendor_ruby/2.7.0:$SNAP/usr/lib/ruby/vendor_ruby:$SNAP/usr/lib/ruby/2.7.0:$SNAP/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/ruby/2.7.0
|
||||
# HUGO_SECURITY_EXEC_OSENV
|
||||
#
|
||||
# Default value:
|
||||
# (?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\w+)$
|
||||
# Bundled applications require additional access:
|
||||
# git: GIT_EXEC_PATH and LD_LIBRARY_PATH
|
||||
# npx: npm_config_{cache,init_module,userconfig}
|
||||
# pandoc: pandoc_datadir
|
||||
# rst2html: PYTHONHOME and SNAP
|
||||
# asciidoctor: RUBYLIB
|
||||
HUGO_SECURITY_EXEC_OSENV: (?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\w+|GIT_EXEC_PATH|LD_LIBRARY_PATH|npm_config_(cache|init_module|userconfig)|pandoc_datadir|PYTHONHOME|SNAP|RUBYLIB)$
|
||||
|
||||
apps:
|
||||
hugo:
|
||||
@@ -92,15 +103,6 @@ parts:
|
||||
export GOPATH=$(realpath ../go)
|
||||
export PATH=$GOPATH/bin:$PATH
|
||||
|
||||
echo " * Patch securityConfig.go to allow"
|
||||
echo " - GIT_EXEC_PATH and LD_LIBRARY_PATH to be passed to git"
|
||||
echo " - npm_config_{cache,init_module,userconfig} to be passed to npx"
|
||||
echo " - pandoc_datadir to be passed to pandoc"
|
||||
echo " - PYTHONHOME and SNAP to be passed to rst2html"
|
||||
echo " - RUBYLIB to be passed to asciidoctor"
|
||||
sed -i '/OsEnv: NewWhitelist/s/)\$/|GIT_EXEC_PATH|LD_LIBRARY_PATH|npm_config_(cache|init_module|userconfig)|pandoc_datadir|PYTHONHOME|RUBYLIB|SNAP&/' config/security/securityConfig.go
|
||||
git diff config/security/securityConfig.go
|
||||
|
||||
HUGO_BUILD_TAGS="extended"
|
||||
echo " * Building hugo (HUGO_BUILD_TAGS=\"$HUGO_BUILD_TAGS\")..."
|
||||
go build -v -ldflags "-X github.com/gohugoio/hugo/common/hugo.vendorInfo=snap:$(git describe --tags --always --match 'v[0-9]*' | sed 's/^v//; s/-/+git/; s/-g/./')" -tags "$HUGO_BUILD_TAGS"
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
hugo --printPathWarnings
|
||||
|
||||
! stdout 'Duplicate'
|
||||
|
||||
-- hugo.toml --
|
||||
-- assets/css/styles.css --
|
||||
body {
|
||||
background-color: #000;
|
||||
}
|
||||
-- content/p1.md --
|
||||
-- content/p2.md --
|
||||
-- content/p3.md --
|
||||
-- layouts/index.html --
|
||||
Home.
|
||||
-- layouts/_default/single.html --
|
||||
{{ $css := resources.Get "css/styles.css" }}
|
||||
{{ $css := $css | minify | fingerprint | resources.PostProcess }}
|
||||
CSS: {{ $css.RelPermalink }}
|
||||
{{ .Title }}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
hugo --printPathWarnings
|
||||
|
||||
stdout 'Duplicate'
|
||||
|
||||
-- hugo.toml --
|
||||
-- assets/css/styles.css --
|
||||
body {
|
||||
background-color: #000;
|
||||
}
|
||||
-- content/p1.md --
|
||||
---
|
||||
url: /p1/
|
||||
---
|
||||
-- content/p2.md --
|
||||
---
|
||||
url: /p1/
|
||||
---
|
||||
-- content/p3.md --
|
||||
---
|
||||
url: /p1/
|
||||
---
|
||||
-- layouts/index.html --
|
||||
Home.
|
||||
-- layouts/_default/single.html --
|
||||
Single.
|
||||
|
||||
@@ -22,6 +22,10 @@ stdout 'Deleted 2\d{2} files from module cache\.'
|
||||
cd submod
|
||||
hugo mod init testsubmod
|
||||
cmpenv go.mod $WORK/golden/go.mod.testsubmod
|
||||
hugo mod get -h
|
||||
stdout 'hugo mod get \[flags\] \[args\]'
|
||||
hugo mod get --help
|
||||
stdout 'hugo mod get \[flags\] \[args\]'
|
||||
-- hugo.toml --
|
||||
title = "Hugo Modules Test"
|
||||
[module]
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
|
||||
hugo new site -h
|
||||
stdout 'Create a new site in the provided directory'
|
||||
hugo new site my-yaml-site --format yml
|
||||
checkfile my-yaml-site/hugo.yml
|
||||
hugo new site mysite -f
|
||||
stdout 'Congratulations! Your new Hugo site is created in'
|
||||
cd mysite
|
||||
checkfile hugo.toml
|
||||
|
||||
hugo new theme -h
|
||||
stdout 'Create a new site in the provided directory'
|
||||
stdout 'Create a new theme \(skeleton\) called \[name\] in ./themes'
|
||||
hugo new theme mytheme
|
||||
stdout 'Creating theme'
|
||||
cd themes
|
||||
@@ -45,6 +47,3 @@ draft: true
|
||||
---
|
||||
|
||||
Dummy content.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -85,6 +85,57 @@ func TestAppendSliceToASliceOfSlices(t *testing.T) {
|
||||
{{ $obj = $obj | append (slice "b") }}
|
||||
{{ $obj = $obj | append (slice "c") }}
|
||||
|
||||
{{ $obj }}
|
||||
|
||||
`
|
||||
|
||||
b := hugolib.NewIntegrationTestBuilder(
|
||||
hugolib.IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
},
|
||||
).Build()
|
||||
|
||||
b.AssertFileContent("public/index.html", "[[a] [b] [c]]")
|
||||
|
||||
}
|
||||
|
||||
func TestAppendNilToSlice(t *testing.T) {
|
||||
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
-- layouts/index.html --
|
||||
{{ $obj := (slice "a") }}
|
||||
{{ $obj = $obj | append nil }}
|
||||
|
||||
{{ $obj }}
|
||||
|
||||
|
||||
`
|
||||
|
||||
b := hugolib.NewIntegrationTestBuilder(
|
||||
hugolib.IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
},
|
||||
).Build()
|
||||
|
||||
b.AssertFileContent("public/index.html", "[a <nil>]")
|
||||
|
||||
}
|
||||
|
||||
func TestAppendNilsToSliceWithNils(t *testing.T) {
|
||||
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
-- layouts/index.html --
|
||||
{{ $obj := (slice "a" nil "c") }}
|
||||
{{ $obj = $obj | append nil }}
|
||||
|
||||
{{ $obj }}
|
||||
|
||||
|
||||
@@ -99,7 +150,7 @@ func TestAppendSliceToASliceOfSlices(t *testing.T) {
|
||||
},
|
||||
).Build()
|
||||
|
||||
b.AssertFileContent("public/index.html", "[[a] [b] [c]]")
|
||||
b.AssertFileContent("public/index.html", "[a <nil> c <nil>]")
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -754,15 +754,15 @@ func (t *templateHandler) applyTemplateTransformers(ns *templateNamespace, ts *t
|
||||
//go:embed embedded/templates/*
|
||||
//go:embed embedded/templates/_default/*
|
||||
//go:embed embedded/templates/_server/*
|
||||
var embededTemplatesFs embed.FS
|
||||
var embeddedTemplatesFs embed.FS
|
||||
|
||||
func (t *templateHandler) loadEmbedded() error {
|
||||
return fs.WalkDir(embededTemplatesFs, ".", func(path string, d fs.DirEntry, err error) error {
|
||||
return fs.WalkDir(embeddedTemplatesFs, ".", func(path string, d fs.DirEntry, err error) error {
|
||||
if d == nil || d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
templb, err := embededTemplatesFs.ReadFile(path)
|
||||
templb, err := embeddedTemplatesFs.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user