Compare commits

...

39 Commits

Author SHA1 Message Date
Bjørn Erik Pedersen ace564e362 Add titleCaseStyle none and firstupper
Fixes #11204
2023-07-05 19:32:57 +02:00
Anthony Fok bf7ee8a91a Bump github.com/bep/clock v0.3.0 to renamed github.com/bep/clocks v0.5.0 2023-07-04 09:14:48 +02:00
hugoreleaser d912491f2d releaser: Prepare repository for 0.116.0-DEV
[ci skip]
2023-07-03 17:39:56 +00:00
hugoreleaser 857374e693 releaser: Bump versions for release of 0.115.1
[ci skip]
2023-07-03 17:28:25 +00:00
Bjørn Erik Pedersen c27639b9a0 docs: Regen docs helper 2023-07-03 19:25:27 +02:00
Bjørn Erik Pedersen ceb486f98c Fix buildStats when tags and classes are disabled
Fixes #11202
2023-07-03 09:58:33 +02:00
Bjørn Erik Pedersen 5afc89f2bf Rework the build.writeStats struct
Mostly to make it easier to toggle on/off this feature from the env.

See #11191
2023-07-02 13:04:11 +02:00
Joe Mooring c1eac616d5 snap: Explicitly set security.exec.osenv during build
Fixes #11199
2023-07-01 17:55:23 -07:00
Joe Mooring 3c8256a137 snap: Restore security.exec.osenv whitelist
Fixes #11195
2023-07-01 14:55:57 -07:00
Bjørn Erik Pedersen 19d76ae96b github: Build for Dragonfly in CI build
To discover upstream issues before we release.
2023-07-01 18:53:48 +02:00
Bjørn Erik Pedersen 11ecea6106 Make build.writeStats a struct
So you can do

```toml
[build.writeStats]
  tags = true
  classes = true
  ids = false
```

Fixes #11191
2023-07-01 15:38:32 +02:00
dependabot[bot] da98724bc8 build(deps): bump github.com/evanw/esbuild from 0.18.10 to 0.18.11
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.18.10 to 0.18.11.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.18.10...v0.18.11)

---
updated-dependencies:
- dependency-name: github.com/evanw/esbuild
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-01 15:33:24 +02:00
dependabot[bot] 4d470bb732 build(deps): bump github.com/alecthomas/chroma/v2 from 2.7.0 to 2.8.0
Bumps [github.com/alecthomas/chroma/v2](https://github.com/alecthomas/chroma) from 2.7.0 to 2.8.0.
- [Release notes](https://github.com/alecthomas/chroma/releases)
- [Changelog](https://github.com/alecthomas/chroma/blob/master/.goreleaser.yml)
- [Commits](https://github.com/alecthomas/chroma/compare/v2.7.0...v2.8.0)

---
updated-dependencies:
- dependency-name: github.com/alecthomas/chroma/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-01 10:33:49 +02:00
Bjørn Erik Pedersen 0ff8e13c1c commands: Fix index out of range in hugo mod get
Fixes #11190
2023-06-30 15:57:46 +02:00
Bjørn Erik Pedersen ffd37d4f75 Only print the path warnings once
We could reset and rerun it on server rebuilds, but that report needs a full build to make sense.

Also clean up the config vs flags in this area: Make all config settings match the flags e.g. `printPathWarnings`, but set up aliases for the
old.

Fixes #11187
2023-06-30 10:24:28 +02:00
Bjørn Erik Pedersen b4b65245b2 Update README.md 2023-06-30 08:01:25 +02:00
hugoreleaser 87886f40d1 releaser: Prepare repository for 0.116.0-DEV
[ci skip]
2023-06-29 16:09:06 +00:00
hugoreleaser 67caf50698 releaser: Bump versions for release of 0.115.0
[ci skip]
2023-06-29 15:56:39 +00:00
Bjørn Erik Pedersen 7917961d59 Misc permalinks adjustments
* Move config loading to the page package
* Fix a lower bound panic for the `:sections` slice syntax.
* Always return the `:title`
* Add some permalinks integration tests
* Also see issues below

Fixes #9448
Fixes #11184
See #8523
2023-06-29 10:14:19 +02:00
Bjørn Erik Pedersen 80ecb95895 commands: Handle hugo mod get --help
Fixes #11141
2023-06-29 08:53:50 +02:00
Joe Mooring 58e09cc6c7 Update README.md 2023-06-28 20:14:41 +02:00
Andreas Deininger 635cc346ce commands: Fix panic when running hugo new theme without theme name
Closes #11162.
2023-06-28 16:20:54 +02:00
Mos Roshanavand 12646750aa Print help message when triggered with no flags 2023-06-28 14:58:36 +02:00
Bjørn Erik Pedersen 79639c981c Fix output formats and media type per language config regression
Fixes #11159
2023-06-28 12:33:33 +02:00
dependabot[bot] 9b313cec1b build(deps): bump github.com/evanw/esbuild from 0.18.5 to 0.18.10
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.18.5 to 0.18.10.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.18.5...v0.18.10)

---
updated-dependencies:
- dependency-name: github.com/evanw/esbuild
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-28 10:22:19 +02:00
khayyam b74b8d6478 common/collections: Fix append regression to allow appending nil
Closes #11180
2023-06-28 09:41:36 +02:00
Andreas Deininger 793e38f5ce commands: Fix help message for hugo new theme
Closes #11161.
2023-06-28 09:30:27 +02:00
Bjørn Erik Pedersen 7f698c8934 Don't panic on invalid security whitelist regexp
Fixes #11176
2023-06-28 08:57:28 +02:00
Bjørn Erik Pedersen fa0e16f4c7 Fix false path warnings with resources.PostProcess
Fixes #7735
2023-06-27 21:55:35 +02:00
Joe Mooring 12e4c4d5dc docs: Update permalinks documentation
See #8523
See #10847
2023-06-27 11:04:49 +02:00
Bjørn Erik Pedersen bac03f4076 Merge branch 'master' of github.com:gohugoio/hugo 2023-06-26 20:48:44 +02:00
Mai-Lapyst cc14c6a52c resources/page: Allow section and taxonomy pages to have a permalink configuration
Allows using permalink configuration for sections (branch bundles) and
also for taxonomy pages. Extends the current permalink configuration to
be able to specified per page kind while also staying backward compatible:
all permalink patterns not dedicated to a certain kind, get automatically
added for both normal pages and term pages.

Fixes #8523
2023-06-26 15:31:01 +02:00
Oleksandr Redko e3308a0bbc tpl/tplimpl: Fix typo in global variable name 2023-06-26 15:12:21 +02:00
Joe Mooring 019299b0b0 commands: Enable format flag with hugo new site
Fixes #11155
2023-06-24 20:26:54 +02:00
Bjørn Erik Pedersen 23ed087c4e Update README.md 2023-06-24 11:28:29 +02:00
Bjørn Erik Pedersen a018259bcf Merge branch 'release-0.114.1' 2023-06-23 13:18:21 +02:00
hugoreleaser ad5e04daa4 releaser: Prepare repository for 0.115.0-DEV
[ci skip]
2023-06-23 11:14:00 +00:00
Bjørn Erik Pedersen 5b4bfc2db7 Fix broken nodeploy setup
Fixes #11149
2023-06-23 12:40:43 +02:00
dependabot[bot] 92f55f1121 build(deps): bump github.com/niklasfasching/go-org from 1.6.6 to 1.7.0
Bumps [github.com/niklasfasching/go-org](https://github.com/niklasfasching/go-org) from 1.6.6 to 1.7.0.
- [Commits](https://github.com/niklasfasching/go-org/compare/v1.6.6...v1.7.0)

---
updated-dependencies:
- dependency-name: github.com/niklasfasching/go-org
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-23 12:17:35 +02:00
62 changed files with 1295 additions and 389 deletions
+7
View File
@@ -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
+194 -147
View File
@@ -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)
---
[![GoDoc](https://godoc.org/github.com/gohugoio/hugo?status.svg)](https://godoc.org/github.com/gohugoio/hugo)
[![Tests on Linux, MacOS and Windows](https://github.com/gohugoio/hugo/workflows/Test/badge.svg)](https://github.com/gohugoio/hugo/actions?query=workflow%3ATest)
[![Go Report Card](https://goreportcard.com/badge/github.com/gohugoio/hugo)](https://goreportcard.com/report/github.com/gohugoio/hugo)
* [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 dont 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 &ndash; transpilation (Sass), tree shaking, minification, source maps, SRI hashing, and PostCSS integration
- JavaScript bundling &ndash; transpilation (TypeScript, JSX), tree shaking, minification, source maps, and SRI hashing
- Image processing &ndash; convert, resize, crop, rotate, adjust colors, apply filters, overlay text and images, and extract EXIF data
And with [Hugo Modules], you can share content, assets, data, translations, themes, templates, and configuration with other projects via public or private Git repositories.
## Sponsors
## Banner Sponsors
<p>&nbsp;</p>
<p float="left">
<a href="https://www.linode.com/?utm_campaign=hugosponsor&utm_medium=banner&utm_source=hugogithub" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/linode-logo_standard_light_medium.png" width="200" alt="Linode"></a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<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>&nbsp;</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].
Hugos [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
View File
@@ -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")
+1
View File
@@ -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
}
+2 -1
View File
@@ -30,8 +30,9 @@
package commands
import (
"errors"
"context"
"github.com/bep/simplecobra"
"github.com/spf13/cobra"
)
+2
View File
@@ -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
View File
@@ -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.
-21
View File
@@ -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()
}
+1
View File
@@ -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
}
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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 = ""
+9 -1
View File
@@ -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())
}
+3
View File
@@ -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...)
+2 -2
View File
@@ -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 {
+3 -3
View File
@@ -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",
}
+7 -7
View File
@@ -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
+23 -6
View File
@@ -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))
}
}
}
+2 -2
View File
@@ -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 {
+18
View File
@@ -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)
}
+5 -1
View File
@@ -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
View File
@@ -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)
+1 -1
View File
@@ -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
+6 -6
View File
@@ -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...)
}
}
+18 -5
View File
@@ -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.
+6 -6
View File
@@ -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)
-3
View File
@@ -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 (
+112 -30
View File
@@ -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
View File
@@ -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": {
+4 -5
View File
@@ -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
+6 -8
View File
@@ -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=
+6
View File
@@ -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
+3 -1
View File
@@ -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) {
+42
View File
@@ -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()
+8 -1
View File
@@ -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 {
+2
View File
@@ -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
+35 -1
View File
@@ -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
}
+15 -1
View File
@@ -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
View File
@@ -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
+3 -3
View File
@@ -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{}
+85
View File
@@ -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)
+22
View File
@@ -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]")
}
+3 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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)
}
+3 -1
View File
@@ -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{})
+1 -1
View File
@@ -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{
+17 -5
View File
@@ -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 {
+10 -3
View File
@@ -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)
+2 -2
View File
@@ -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())
+90 -12
View File
@@ -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.")
}
+51 -29
View File
@@ -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
View File
@@ -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.
+4
View File
@@ -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]
+3 -4
View File
@@ -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.
+52 -1
View File
@@ -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 &lt;nil&gt;]")
}
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 &lt;nil&gt; c &lt;nil&gt;]")
}
+3 -3
View File
@@ -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
}