mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-25 15:58:53 +00:00
Compare commits
138 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3fbcc4b7b3 | |||
| 223bf28004 | |||
| 72b0ccdb01 | |||
| 65e52a7f5c | |||
| bdf935d66c | |||
| 8ebcaa5394 | |||
| 241481931f | |||
| 5caed8a71f | |||
| d1278f696a | |||
| 92f31ae63b | |||
| fd3953c18c | |||
| 15463f835b | |||
| 466fa43c16 | |||
| 6072ce0bcd | |||
| 2c5943ddc6 | |||
| 0cb459a20a | |||
| 475f87f685 | |||
| 604cfffc5b | |||
| d863dde6c6 | |||
| 580b214a4c | |||
| ddb9547085 | |||
| 288b0fb15a | |||
| 3e1344632d | |||
| 7a9ce0eca5 | |||
| f2ba0cc8c5 | |||
| 62ceaabdce | |||
| 35fa192838 | |||
| 09ac733381 | |||
| 66da1b7b2f | |||
| 5a9ecb82a3 | |||
| cbc35c48d2 | |||
| 44f3c07969 | |||
| a5a4422aae | |||
| 617e094482 | |||
| cf12fa6161 | |||
| 2e1c81770a | |||
| 4276075c78 | |||
| d25cb2943f | |||
| 8b9bdc403d | |||
| 4e94d1db72 | |||
| 0566bbf7c7 | |||
| 534e7155bb | |||
| 953f215f32 | |||
| 8e2fd55923 | |||
| 311b8008bf | |||
| c7d5f9f067 | |||
| bfebd8c02c | |||
| 0afb4866e3 | |||
| b1ec0c226f | |||
| 212d9e3017 | |||
| 4daac654d9 | |||
| 27b077544d | |||
| 0f8343a2c2 | |||
| 3fcbee261a | |||
| db9d274275 | |||
| 95baafeac4 | |||
| e4bfe59c4e | |||
| 6f7bf3f2d7 | |||
| 9e904d756b | |||
| d2cfaede5b | |||
| 322d19a81f | |||
| 7cb484e121 | |||
| 0b395f0b41 | |||
| c1a83076bf | |||
| 0f8dc47037 | |||
| 3b478f50b7 | |||
| f343b8eb78 | |||
| 60ede146b6 | |||
| dd9eaf19fd | |||
| 46a2ea6d0d | |||
| 653ab2cc1f | |||
| 52edea0fec | |||
| 6a5acd753a | |||
| 805b21555e | |||
| 8ca7052528 | |||
| bb232a351a | |||
| 3854a6fa6c | |||
| cd0112a05a | |||
| 6f7fbe03b1 | |||
| 2fc2e9c871 | |||
| e164834f0a | |||
| d524067382 | |||
| 31ce89f7f4 | |||
| ee55fde5e5 | |||
| 2f9eac480f | |||
| 3a8189ee93 | |||
| 7bc3401eb5 | |||
| 1de333e7a3 | |||
| 35cb6eefb2 | |||
| 657d1a2d95 | |||
| 87a22eb6d6 | |||
| fc9f315d86 | |||
| 4b189d8fd9 | |||
| c2fa0a3320 | |||
| 48ea24f89a | |||
| 9f563856c1 | |||
| 5c96bda70a | |||
| 4a96df96d9 | |||
| e8537e6dd0 | |||
| 2fbdee7268 | |||
| 91fe1b6c69 | |||
| 7de629121d | |||
| 9d7f166244 | |||
| 51f08b0b6a | |||
| 860c51c314 | |||
| 855e5869c6 | |||
| 327aaed6d8 | |||
| 1c7759028e | |||
| 35c88a7f90 | |||
| e77ca3c105 | |||
| f2946da9e8 | |||
| 6eea32bd6b | |||
| a6d545854a | |||
| e5f2173169 | |||
| 89c1655ec8 | |||
| 165d299cde | |||
| e94dc6710a | |||
| a4fff5753f | |||
| 3902f9a476 | |||
| 4852a37653 | |||
| fa80fe3c8a | |||
| 11047534e4 | |||
| d7b54a4c37 | |||
| 13ceef7599 | |||
| 942d0dd2c7 | |||
| a022ca271b | |||
| d56b33955f | |||
| 55e28c239e | |||
| 9a888c243a | |||
| 05b45c35c8 | |||
| 625be77e08 | |||
| 097fd588ca | |||
| 41cc4e4ba3 | |||
| 078053a43d | |||
| 7d9f888080 | |||
| 9b352f04a3 | |||
| e66e2e9ce5 | |||
| 5de6f8a02c |
@@ -1,6 +1,6 @@
|
||||
defaults: &defaults
|
||||
docker:
|
||||
- image: bepsays/ci-goreleaser:1.1800.000
|
||||
- image: bepsays/ci-goreleaser:1.1800.300
|
||||
environment:
|
||||
CGO_ENABLED: "0"
|
||||
|
||||
|
||||
@@ -23,9 +23,9 @@ jobs:
|
||||
with:
|
||||
operations-per-run: 999
|
||||
days-before-issue-stale: 365
|
||||
days-before-pr-stale: 200
|
||||
days-before-issue-close: 21
|
||||
days-before-pr-close: 21
|
||||
days-before-pr-stale: 365
|
||||
days-before-issue-close: 56
|
||||
days-before-pr-close: 56
|
||||
stale-issue-message: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. The resources of the Hugo team are limited, and so we are asking for your help.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
on: [push, pull_request]
|
||||
name: Test
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
test:
|
||||
env:
|
||||
@@ -48,6 +50,7 @@ jobs:
|
||||
- if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
choco install pandoc
|
||||
choco install mingw --version 10.2.0 --allow-downgrade
|
||||
- run: pandoc -v
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
name: Install dart-sass-embedded Linux
|
||||
@@ -70,10 +73,11 @@ jobs:
|
||||
echo "6ae442129dbb3334bc21ef851261da6c0c1b560da790ca2e1350871d00ab816d sass_embedded-1.0.0-beta.6-windows-x64.zip" | sha256sum -c;
|
||||
unzip sass_embedded-1.0.0-beta.6-windows-x64.zip;
|
||||
echo "$env:GITHUB_WORKSPACE/sass_embedded/" | Out-File -FilePath $Env:GITHUB_PATH -Encoding utf-8 -Append
|
||||
- name: Test
|
||||
- name: Check
|
||||
run: |
|
||||
mage -v test
|
||||
mage -v check;
|
||||
env:
|
||||
HUGO_BUILD_TAGS: extended
|
||||
- name: Build Docs
|
||||
env:
|
||||
HUGO_BUILD_TAGS: extended
|
||||
|
||||
+11
-8
@@ -83,19 +83,22 @@ To make the contribution process as seamless as possible, we ask for the followi
|
||||
|
||||
### Git Commit Message Guidelines
|
||||
|
||||
This [blog article](http://chris.beams.io/posts/git-commit/) is a good resource for learning how to write good commit messages,
|
||||
This [blog article](https://cbea.ms/git-commit/) is a good resource for learning how to write good commit messages,
|
||||
the most important part being that each commit message should have a title/subject in imperative mood starting with a capital letter and no trailing period:
|
||||
*"Return error on wrong use of the Paginator"*, **NOT** *"returning some error."*
|
||||
*"js: Return error when option x is not set"*, **NOT** *"returning some error."*
|
||||
|
||||
Most title/subjects should have a lower-cased prefix with a colon and one whitespace. The prefix can be:
|
||||
|
||||
* The name of the package where (most of) the changes are made (e.g. `media: Add text/calendar`)
|
||||
* If the package name is deeply nested/long, try to shorten it from the left side, e.g. `markup/goldmark` is OK, `resources/resource_transformers/js` can be shortened to `js`.
|
||||
* If this commit touches several packages with a common functional topic, use that as a prefix, e.g. `errors: Resolve correct line numbers`)
|
||||
* If this commit touches many packages without a common functional topic, prefix with `all:` (e.g. `all: Reformat Go code`)
|
||||
* If this is a documentation update, prefix with `docs:`.
|
||||
* If nothing of the above applies, just leave the prefix out.
|
||||
|
||||
Also, if your commit references one or more GitHub issues, always end your commit message body with *See #1234* or *Fixes #1234*.
|
||||
Replace *1234* with the GitHub issue ID. The last example will close the issue when the commit is merged into *master*.
|
||||
|
||||
Sometimes it makes sense to prefix the commit message with the package name (or docs folder) all lowercased ending with a colon.
|
||||
That is fine, but the rest of the rules above apply.
|
||||
So it is "tpl: Add emojify template func", not "tpl: add emojify template func.", and "docs: Document emoji", not "doc: document emoji."
|
||||
|
||||
Please use a short and descriptive branch name, e.g. **NOT** "patch-1". It's very common but creates a naming conflict each time when a submission is pulled for a review.
|
||||
|
||||
An example:
|
||||
|
||||
```text
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<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](http://spf13.com/) and [friends](https://github.com/gohugoio/hugo/graphs/contributors) in [Go][].
|
||||
A Fast and Flexible Static Site Generator built with love by [bep](https://github.com/bep), [spf13](https://spf13.com/) and [friends](https://github.com/gohugoio/hugo/graphs/contributors) in [Go][].
|
||||
|
||||
[Website](https://gohugo.io) |
|
||||
[Forum](https://discourse.gohugo.io) |
|
||||
@@ -130,97 +130,119 @@ Hugo stands on the shoulder of many great open source libraries.
|
||||
|
||||
If you run `hugo env -v` you will get a complete and up to date list.
|
||||
|
||||
In Hugo 0.89.0 that list is, in lexical order:
|
||||
In Hugo 0.100.1 that list is, in lexical order:
|
||||
|
||||
```
|
||||
cloud.google.com/go/storage="v1.10.0"
|
||||
cloud.google.com/go="v0.87.0"
|
||||
github.com/Azure/azure-pipeline-go="v0.2.2"
|
||||
github.com/Azure/azure-storage-blob-go="v0.9.0"
|
||||
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"
|
||||
github.com/BurntSushi/locker="v0.0.0-20171006230638-a6e239ea1c69"
|
||||
github.com/BurntSushi/toml="v0.3.1"
|
||||
github.com/PuerkitoBio/purell="v1.1.1"
|
||||
github.com/PuerkitoBio/urlesc="v0.0.0-20170810143723-de5bf2ad4578"
|
||||
github.com/alecthomas/chroma="v0.9.4"
|
||||
github.com/alecthomas/chroma="v0.10.0"
|
||||
github.com/armon/go-radix="v1.0.0"
|
||||
github.com/aws/aws-sdk-go="v1.41.14"
|
||||
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/bep/debounce="v1.2.0"
|
||||
github.com/bep/gitmap="v1.1.2"
|
||||
github.com/bep/godartsass="v0.12.0"
|
||||
github.com/bep/golibsass="v1.0.0"
|
||||
github.com/bep/goat="v0.5.0"
|
||||
github.com/bep/godartsass="v0.14.0"
|
||||
github.com/bep/golibsass="v1.1.0"
|
||||
github.com/bep/gowebp="v0.1.0"
|
||||
github.com/bep/overlayfs="v0.6.0"
|
||||
github.com/bep/tmc="v0.5.1"
|
||||
github.com/clbanning/mxj/v2="v2.5.5"
|
||||
github.com/cli/safeexec="v1.0.0"
|
||||
github.com/cpuguy83/go-md2man/v2="v2.0.0"
|
||||
github.com/cpuguy83/go-md2man/v2="v2.0.1"
|
||||
github.com/disintegration/gift="v1.2.1"
|
||||
github.com/dlclark/regexp2="v1.4.0"
|
||||
github.com/dustin/go-humanize="v1.0.0"
|
||||
github.com/evanw/esbuild="v0.13.12"
|
||||
github.com/fsnotify/fsnotify="v1.5.1"
|
||||
github.com/getkin/kin-openapi="v0.80.0"
|
||||
github.com/evanw/esbuild="v0.14.42"
|
||||
github.com/frankban/quicktest="v1.14.3"
|
||||
github.com/fsnotify/fsnotify="v1.5.4"
|
||||
github.com/getkin/kin-openapi="v0.94.0"
|
||||
github.com/ghodss/yaml="v1.0.0"
|
||||
github.com/go-openapi/jsonpointer="v0.19.5"
|
||||
github.com/go-openapi/swag="v0.19.5"
|
||||
github.com/gobuffalo/flect="v0.2.3"
|
||||
github.com/gobuffalo/flect="v0.2.5"
|
||||
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="v0.14.0"
|
||||
github.com/golang/groupcache="v0.0.0-20200121045136-8c9f03a8e57e"
|
||||
github.com/gohugoio/localescompressed="v1.0.1"
|
||||
github.com/golang-jwt/jwt/v4="v4.0.0"
|
||||
github.com/golang/groupcache="v0.0.0-20210331224755-41bb18bfe9da"
|
||||
github.com/golang/protobuf="v1.5.2"
|
||||
github.com/google/go-cmp="v0.5.6"
|
||||
github.com/google/uuid="v1.1.2"
|
||||
github.com/google/wire="v0.4.0"
|
||||
github.com/googleapis/gax-go/v2="v2.0.5"
|
||||
github.com/googleapis/gax-go="v2.0.2+incompatible"
|
||||
github.com/gorilla/websocket="v1.4.2"
|
||||
github.com/google/go-cmp="v0.5.8"
|
||||
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/gorilla/websocket="v1.5.0"
|
||||
github.com/hairyhenderson/go-codeowners="v0.2.3-0.20201026200250-cdc7c0759690"
|
||||
github.com/inconshreveable/mousetrap="v1.0.0"
|
||||
github.com/jdkato/prose="v1.2.1"
|
||||
github.com/jmespath/go-jmespath="v0.4.0"
|
||||
github.com/kyokomi/emoji/v2="v2.2.8"
|
||||
github.com/kr/pretty="v0.3.0"
|
||||
github.com/kr/text="v0.2.0"
|
||||
github.com/kyokomi/emoji/v2="v2.2.9"
|
||||
github.com/mailru/easyjson="v0.0.0-20190626092158-b2ccc519800e"
|
||||
github.com/mattn/go-ieproxy="v0.0.1"
|
||||
github.com/mattn/go-isatty="v0.0.14"
|
||||
github.com/mattn/go-runewidth="v0.0.9"
|
||||
github.com/miekg/mmark="v1.3.6"
|
||||
github.com/mitchellh/hashstructure="v1.1.0"
|
||||
github.com/mitchellh/mapstructure="v1.4.2"
|
||||
github.com/mitchellh/mapstructure="v1.5.0"
|
||||
github.com/muesli/smartcrop="v0.3.0"
|
||||
github.com/niklasfasching/go-org="v1.5.0"
|
||||
github.com/niklasfasching/go-org="v1.6.2"
|
||||
github.com/olekukonko/tablewriter="v0.0.5"
|
||||
github.com/pelletier/go-toml/v2="v2.0.0-beta.3.0.20210727221244-fa0796069526"
|
||||
github.com/pkg/errors="v0.9.1"
|
||||
github.com/rogpeppe/go-internal="v1.8.0"
|
||||
github.com/russross/blackfriday/v2="v2.0.1"
|
||||
github.com/russross/blackfriday="v1.5.3-0.20200218234912-41c5fccfd6f6"
|
||||
github.com/pelletier/go-toml/v2="v2.0.0-beta.7.0.20220408132554-2377ac4bc04c"
|
||||
github.com/rogpeppe/go-internal="v1.8.1"
|
||||
github.com/russross/blackfriday/v2="v2.1.0"
|
||||
github.com/rwcarlsen/goexif="v0.0.0-20190401172101-9e8deecbddbd"
|
||||
github.com/sanity-io/litter="v1.5.1"
|
||||
github.com/sanity-io/litter="v1.5.5"
|
||||
github.com/sass/libsass="3.6.5"
|
||||
github.com/shurcooL/sanitized_anchor_name="v1.0.0"
|
||||
github.com/spf13/afero="v1.6.0"
|
||||
github.com/spf13/cast="v1.4.1"
|
||||
github.com/spf13/cobra="v1.2.1"
|
||||
github.com/spf13/afero="v1.8.2"
|
||||
github.com/spf13/cast="v1.5.0"
|
||||
github.com/spf13/cobra="v1.4.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.9.22"
|
||||
github.com/tdewolff/parse/v2="v2.5.21"
|
||||
github.com/tdewolff/minify/v2="v2.11.5"
|
||||
github.com/tdewolff/parse/v2="v2.5.31"
|
||||
github.com/webmproject/libwebp="v1.2.0"
|
||||
github.com/yuin/goldmark-highlighting="v0.0.0-20200307114337-60d527fdb691"
|
||||
github.com/yuin/goldmark="v1.4.2"
|
||||
github.com/yuin/goldmark="v1.4.12"
|
||||
go.opencensus.io="v0.23.0"
|
||||
gocloud.dev="v0.20.0"
|
||||
golang.org/x/image="v0.0.0-20210220032944-ac19c3e999fb"
|
||||
golang.org/x/net="v0.0.0-20210614182718-04defd469f4e"
|
||||
golang.org/x/oauth2="v0.0.0-20210628180205-a41e5a781914"
|
||||
go.uber.org/atomic="v1.9.0"
|
||||
gocloud.dev="v0.24.0"
|
||||
golang.org/x/crypto="v0.0.0-20211108221036-ceb1ce70b4fa"
|
||||
golang.org/x/image="v0.0.0-20211028202545-6944b10bf410"
|
||||
golang.org/x/net="v0.0.0-20220425223048-2871e0cb64e4"
|
||||
golang.org/x/oauth2="v0.0.0-20220411215720-9780585627b5"
|
||||
golang.org/x/sync="v0.0.0-20210220032951-036812b2e83c"
|
||||
golang.org/x/sys="v0.0.0-20210908233432-aa78b53d3365"
|
||||
golang.org/x/sys="v0.0.0-20220422013727-9388b58f7150"
|
||||
golang.org/x/text="v0.3.7"
|
||||
golang.org/x/xerrors="v0.0.0-20200804184101-5ec99f83aff1"
|
||||
google.golang.org/api="v0.51.0"
|
||||
google.golang.org/genproto="v0.0.0-20210716133855-ce7ef5c701ea"
|
||||
google.golang.org/grpc="v1.39.0"
|
||||
google.golang.org/protobuf="v1.27.1"
|
||||
golang.org/x/tools="v0.1.10"
|
||||
golang.org/x/xerrors="v0.0.0-20220411194840-2f41105eb62f"
|
||||
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.0"
|
||||
gopkg.in/yaml.v2="v2.4.0"
|
||||
```
|
||||
|
||||
|
||||
Vendored
+3
@@ -295,6 +295,9 @@ func (c *Cache) isExpired(modTime time.Time) bool {
|
||||
if c.maxAge < 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Note the use of time.Since here.
|
||||
// We cannot use Hugo's global Clock for this.
|
||||
return c.maxAge == 0 || time.Since(modTime) > c.maxAge
|
||||
}
|
||||
|
||||
|
||||
Vendored
+8
-6
@@ -14,6 +14,7 @@
|
||||
package filecache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -25,8 +26,9 @@ import (
|
||||
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
|
||||
"errors"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
@@ -153,7 +155,7 @@ func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) {
|
||||
}
|
||||
|
||||
if err := decoder.Decode(v); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decode filecache config")
|
||||
return nil, fmt.Errorf("failed to decode filecache config: %w", err)
|
||||
}
|
||||
|
||||
if cc.Dir == "" {
|
||||
@@ -162,7 +164,7 @@ func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) {
|
||||
|
||||
name := strings.ToLower(k)
|
||||
if !valid[name] {
|
||||
return nil, errors.Errorf("%q is not a valid cache name", name)
|
||||
return nil, fmt.Errorf("%q is not a valid cache name", name)
|
||||
}
|
||||
|
||||
c[name] = cc
|
||||
@@ -197,12 +199,12 @@ func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) {
|
||||
|
||||
if !v.isResourceDir {
|
||||
if isOsFs && !filepath.IsAbs(v.Dir) {
|
||||
return c, errors.Errorf("%q must resolve to an absolute directory", v.Dir)
|
||||
return c, fmt.Errorf("%q must resolve to an absolute directory", v.Dir)
|
||||
}
|
||||
|
||||
// Avoid cache in root, e.g. / (Unix) or c:\ (Windows)
|
||||
if len(strings.TrimPrefix(v.Dir, filepath.VolumeName(v.Dir))) == 1 {
|
||||
return c, errors.Errorf("%q is a root folder and not allowed as cache dir", v.Dir)
|
||||
return c, fmt.Errorf("%q is a root folder and not allowed as cache dir", v.Dir)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,5 +244,5 @@ func resolveDirPlaceholder(fs afero.Fs, cfg config.Provider, placeholder string)
|
||||
return filepath.Base(workingDir), false, nil
|
||||
}
|
||||
|
||||
return "", false, errors.Errorf("%q is not a valid placeholder (valid values are :cacheDir or :resourceDir)", placeholder)
|
||||
return "", false, fmt.Errorf("%q is not a valid placeholder (valid values are :cacheDir or :resourceDir)", placeholder)
|
||||
}
|
||||
|
||||
Vendored
+2
-2
@@ -14,12 +14,12 @@
|
||||
package filecache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
@@ -39,7 +39,7 @@ func (c Caches) Prune() (int, error) {
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return counter, errors.Wrapf(err, "failed to prune cache %q", k)
|
||||
return counter, fmt.Errorf("failed to prune cache %q: %w", k, err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+58
-41
@@ -14,8 +14,8 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
@@ -29,9 +29,11 @@ import (
|
||||
"golang.org/x/sync/semaphore"
|
||||
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/common/htime"
|
||||
"github.com/gohugoio/hugo/common/hugo"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
jww "github.com/spf13/jwalterweatherman"
|
||||
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
@@ -42,6 +44,7 @@ import (
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
"github.com/spf13/afero"
|
||||
|
||||
"github.com/bep/clock"
|
||||
"github.com/bep/debounce"
|
||||
"github.com/bep/overlayfs"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
@@ -95,13 +98,12 @@ type commandeer struct {
|
||||
|
||||
serverPorts []serverPortListener
|
||||
|
||||
languagesConfigured bool
|
||||
languages langs.Languages
|
||||
doLiveReload bool
|
||||
renderStaticToDisk bool
|
||||
fastRenderMode bool
|
||||
showErrorInBrowser bool
|
||||
wasError bool
|
||||
languages langs.Languages
|
||||
doLiveReload bool
|
||||
renderStaticToDisk bool
|
||||
fastRenderMode bool
|
||||
showErrorInBrowser bool
|
||||
wasError bool
|
||||
|
||||
configured bool
|
||||
paused bool
|
||||
@@ -128,6 +130,15 @@ func (c *commandeerHugoState) hugo() *hugolib.HugoSites {
|
||||
return c.hugoSites
|
||||
}
|
||||
|
||||
func (c *commandeerHugoState) hugoTry() *hugolib.HugoSites {
|
||||
select {
|
||||
case <-c.created:
|
||||
return c.hugoSites
|
||||
case <-time.After(time.Millisecond * 100):
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *commandeer) errCount() int {
|
||||
return int(c.logger.LogCounters().ErrorCounter.Count())
|
||||
}
|
||||
@@ -141,19 +152,11 @@ func (c *commandeer) getErrorWithContext() any {
|
||||
|
||||
m := make(map[string]any)
|
||||
|
||||
m["Error"] = errors.New(removeErrorPrefixFromLog(c.logger.Errors()))
|
||||
//xwm["Error"] = errors.New(cleanErrorLog(removeErrorPrefixFromLog(c.logger.Errors())))
|
||||
m["Error"] = errors.New(cleanErrorLog(removeErrorPrefixFromLog(c.logger.Errors())))
|
||||
m["Version"] = hugo.BuildVersionString()
|
||||
|
||||
fe := herrors.UnwrapErrorWithFileContext(c.buildErr)
|
||||
if fe != nil {
|
||||
m["File"] = fe
|
||||
}
|
||||
|
||||
if c.h.verbose {
|
||||
var b bytes.Buffer
|
||||
herrors.FprintStackTraceFromErr(&b, c.buildErr)
|
||||
m["StackTrace"] = b.String()
|
||||
}
|
||||
ferrors := herrors.UnwrapFileErrorsWithErrorContext(c.buildErr)
|
||||
m["Files"] = ferrors
|
||||
|
||||
return m
|
||||
}
|
||||
@@ -173,6 +176,21 @@ func (c *commandeer) initFs(fs *hugofs.Fs) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *commandeer) initClock(loc *time.Location) error {
|
||||
bt := c.Cfg.GetString("clock")
|
||||
if bt == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
t, err := cast.StringToDateInDefaultLocation(bt, loc)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`failed to parse "clock" flag: %s`, err)
|
||||
}
|
||||
|
||||
htime.Clock = clock.Start(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
func newCommandeer(mustHaveConfigFile, failOnInitErr, running bool, h *hugoBuilderCommon, f flagsToConfigHandler, cfgInit func(c *commandeer) error, subCmdVs ...*cobra.Command) (*commandeer, error) {
|
||||
var rebuildDebouncer func(f func())
|
||||
if running {
|
||||
@@ -350,9 +368,16 @@ func (c *commandeer) loadConfig() error {
|
||||
|
||||
c.configFiles = configFiles
|
||||
|
||||
if l, ok := c.Cfg.Get("languagesSorted").(langs.Languages); ok {
|
||||
c.languagesConfigured = true
|
||||
c.languages = l
|
||||
var ok bool
|
||||
loc := time.Local
|
||||
c.languages, ok = c.Cfg.Get("languagesSorted").(langs.Languages)
|
||||
if ok {
|
||||
loc = langs.GetLocation(c.languages[0])
|
||||
}
|
||||
|
||||
err = c.initClock(loc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set some commonly used flags
|
||||
@@ -395,23 +420,23 @@ func (c *commandeer) loadConfig() error {
|
||||
}
|
||||
|
||||
c.fsCreate.Do(func() {
|
||||
fs := hugofs.NewFrom(sourceFs, config)
|
||||
// Assume both source and destination are using same filesystem.
|
||||
fs := hugofs.NewFromSourceAndDestination(sourceFs, sourceFs, config)
|
||||
|
||||
if c.publishDirFs != nil {
|
||||
// Need to reuse the destination on server rebuilds.
|
||||
fs.PublishDir = c.publishDirFs
|
||||
fs.PublishDirServer = c.publishDirServerFs
|
||||
} else {
|
||||
publishDir := config.GetString("publishDir")
|
||||
publishDirStatic := config.GetString("publishDirStatic")
|
||||
workingDir := config.GetString("workingDir")
|
||||
absPublishDir := paths.AbsPathify(workingDir, publishDir)
|
||||
absPublishDirStatic := paths.AbsPathify(workingDir, publishDirStatic)
|
||||
|
||||
if c.renderStaticToDisk {
|
||||
// Writes the dynamic output oton memory,
|
||||
publishDirStatic := config.GetString("publishDirStatic")
|
||||
workingDir := config.GetString("workingDir")
|
||||
absPublishDirStatic := paths.AbsPathify(workingDir, publishDirStatic)
|
||||
|
||||
fs = hugofs.NewFromSourceAndDestination(sourceFs, afero.NewMemMapFs(), config)
|
||||
// Writes the dynamic output to memory,
|
||||
// while serve others directly from /public on disk.
|
||||
dynamicFs := afero.NewMemMapFs()
|
||||
dynamicFs := fs.PublishDir
|
||||
staticFs := afero.NewBasePathFs(afero.NewOsFs(), absPublishDirStatic)
|
||||
|
||||
// Serve from both the static and dynamic fs,
|
||||
@@ -427,18 +452,10 @@ func (c *commandeer) loadConfig() error {
|
||||
},
|
||||
},
|
||||
)
|
||||
fs.PublishDir = dynamicFs
|
||||
fs.PublishDirStatic = staticFs
|
||||
} else if createMemFs {
|
||||
// Hugo writes the output to memory instead of the disk.
|
||||
fs.PublishDir = new(afero.MemMapFs)
|
||||
fs.PublishDirServer = fs.PublishDir
|
||||
fs.PublishDirStatic = fs.PublishDir
|
||||
} else {
|
||||
// Write everything to disk.
|
||||
fs.PublishDir = afero.NewBasePathFs(afero.NewOsFs(), absPublishDir)
|
||||
fs.PublishDirServer = fs.PublishDir
|
||||
fs.PublishDirStatic = fs.PublishDir
|
||||
fs = hugofs.NewFromSourceAndDestination(sourceFs, afero.NewMemMapFs(), config)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -210,6 +210,7 @@ type hugoBuilderCommon struct {
|
||||
|
||||
buildWatch bool
|
||||
poll string
|
||||
clock string
|
||||
|
||||
gc bool
|
||||
|
||||
@@ -279,6 +280,7 @@ func (cc *hugoBuilderCommon) handleCommonBuilderFlags(cmd *cobra.Command) {
|
||||
cmd.PersistentFlags().StringVarP(&cc.environment, "environment", "e", "", "build environment")
|
||||
cmd.PersistentFlags().StringP("themesDir", "", "", "filesystem path to themes directory")
|
||||
cmd.PersistentFlags().StringP("ignoreVendorPaths", "", "", "ignores any _vendor for module paths matching the given Glob pattern")
|
||||
cmd.PersistentFlags().StringVar(&cc.clock, "clock", "", "set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00")
|
||||
}
|
||||
|
||||
func (cc *hugoBuilderCommon) handleFlags(cmd *cobra.Command) {
|
||||
@@ -303,6 +305,7 @@ func (cc *hugoBuilderCommon) handleFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().BoolP("forceSyncStatic", "", false, "copy all files when static is changed.")
|
||||
cmd.Flags().BoolP("noTimes", "", false, "don't sync modification time of files")
|
||||
cmd.Flags().BoolP("noChmod", "", false, "don't sync permission mode of files")
|
||||
cmd.Flags().BoolP("noBuildLock", "", false, "don't create .hugo_build.lock file")
|
||||
cmd.Flags().BoolP("printI18nWarnings", "", false, "print missing translations")
|
||||
cmd.Flags().BoolP("printPathWarnings", "", false, "print warnings on duplicate target paths etc.")
|
||||
cmd.Flags().BoolP("printUnusedTemplates", "", false, "print warnings on unused templates.")
|
||||
|
||||
@@ -375,6 +375,10 @@ Single: {{ .Title }}
|
||||
List: {{ .Title }}
|
||||
Environment: {{ hugo.Environment }}
|
||||
|
||||
For issue 9788:
|
||||
{{ $foo :="abc" | resources.FromString "foo.css" | minify | resources.PostProcess }}
|
||||
PostProcess: {{ $foo.RelPermalink }}
|
||||
|
||||
`)
|
||||
|
||||
return dir
|
||||
|
||||
+1
-3
@@ -31,8 +31,6 @@ import (
|
||||
"github.com/gohugoio/hugo/parser"
|
||||
"github.com/gohugoio/hugo/parser/metadecoders"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@@ -193,7 +191,7 @@ func (cc *convertCmd) convertAndSavePage(p page.Page, site *hugolib.Site, target
|
||||
|
||||
fs := hugofs.Os
|
||||
if err := helpers.WriteToDisk(newFilename, &newContent, fs); err != nil {
|
||||
return errors.Wrapf(err, "Failed to save file %q:", newFilename)
|
||||
return fmt.Errorf("Failed to save file %q:: %w", newFilename, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -16,9 +16,9 @@ package commands
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/alecthomas/chroma"
|
||||
"github.com/alecthomas/chroma/formatters/html"
|
||||
"github.com/alecthomas/chroma/styles"
|
||||
"github.com/alecthomas/chroma/v2"
|
||||
"github.com/alecthomas/chroma/v2/formatters/html"
|
||||
"github.com/alecthomas/chroma/v2/styles"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
||||
+34
-21
@@ -33,15 +33,14 @@ import (
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
"github.com/gohugoio/hugo/tpl"
|
||||
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/common/htime"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
|
||||
"github.com/gohugoio/hugo/resources/page"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/common/hugo"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/terminal"
|
||||
@@ -128,6 +127,12 @@ func initializeConfig(mustHaveConfigFile, failOnInitErr, running bool,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if h := c.hugoTry(); h != nil {
|
||||
for _, s := range h.Sites {
|
||||
s.RegisterMediaTypes()
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
@@ -190,6 +195,7 @@ func initializeFlags(cmd *cobra.Command, cfg config.Provider) {
|
||||
"buildDrafts",
|
||||
"buildFuture",
|
||||
"buildExpired",
|
||||
"clock",
|
||||
"uglyURLs",
|
||||
"canonifyURLs",
|
||||
"enableRobotsTXT",
|
||||
@@ -200,6 +206,7 @@ func initializeFlags(cmd *cobra.Command, cfg config.Provider) {
|
||||
"forceSyncStatic",
|
||||
"noTimes",
|
||||
"noChmod",
|
||||
"noBuildLock",
|
||||
"ignoreVendorPaths",
|
||||
"templateMetrics",
|
||||
"templateMetricsHints",
|
||||
@@ -277,10 +284,6 @@ func setValueFromFlag(flags *flag.FlagSet, key string, cfg config.Provider, targ
|
||||
}
|
||||
}
|
||||
|
||||
func isTerminal() bool {
|
||||
return terminal.IsTerminal(os.Stdout)
|
||||
}
|
||||
|
||||
func (c *commandeer) fullBuild(noBuildLock bool) error {
|
||||
var (
|
||||
g errgroup.Group
|
||||
@@ -290,7 +293,7 @@ func (c *commandeer) fullBuild(noBuildLock bool) error {
|
||||
if !c.h.quiet {
|
||||
fmt.Println("Start building sites … ")
|
||||
fmt.Println(hugo.BuildVersionString())
|
||||
if isTerminal() {
|
||||
if terminal.IsTerminal(os.Stdout) {
|
||||
defer func() {
|
||||
fmt.Print(showCursor + clearLine)
|
||||
}()
|
||||
@@ -300,14 +303,14 @@ func (c *commandeer) fullBuild(noBuildLock bool) error {
|
||||
copyStaticFunc := func() error {
|
||||
cnt, err := c.copyStatic()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error copying static files")
|
||||
return fmt.Errorf("Error copying static files: %w", err)
|
||||
}
|
||||
langCount = cnt
|
||||
return nil
|
||||
}
|
||||
buildSitesFunc := func() error {
|
||||
if err := c.buildSites(noBuildLock); err != nil {
|
||||
return errors.Wrap(err, "Error building site")
|
||||
return fmt.Errorf("Error building site: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -354,10 +357,10 @@ func (c *commandeer) initCPUProfile() (func(), error) {
|
||||
|
||||
f, err := os.Create(c.h.cpuprofile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create CPU profile")
|
||||
return nil, fmt.Errorf("failed to create CPU profile: %w", err)
|
||||
}
|
||||
if err := pprof.StartCPUProfile(f); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to start CPU profile")
|
||||
return nil, fmt.Errorf("failed to start CPU profile: %w", err)
|
||||
}
|
||||
return func() {
|
||||
pprof.StopCPUProfile()
|
||||
@@ -388,11 +391,11 @@ func (c *commandeer) initTraceProfile() (func(), error) {
|
||||
|
||||
f, err := os.Create(c.h.traceprofile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create trace file")
|
||||
return nil, fmt.Errorf("failed to create trace file: %w", err)
|
||||
}
|
||||
|
||||
if err := trace.Start(f); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to start trace")
|
||||
return nil, fmt.Errorf("failed to start trace: %w", err)
|
||||
}
|
||||
|
||||
return func() {
|
||||
@@ -682,6 +685,10 @@ func (c *commandeer) firstPathSpec() *helpers.PathSpec {
|
||||
}
|
||||
|
||||
func (c *commandeer) timeTrack(start time.Time, name string) {
|
||||
// Note the use of time.Since here and time.Now in the callers.
|
||||
// We have a htime.Sinnce, but that may be adjusted to the future,
|
||||
// and that does not make sense here, esp. when used before the
|
||||
// global Clock is initialized.
|
||||
elapsed := time.Since(start)
|
||||
c.logger.Printf("%s in %v ms", name, int(1000*elapsed.Seconds()))
|
||||
}
|
||||
@@ -732,15 +739,16 @@ func (c *commandeer) buildSites(noBuildLock bool) (err error) {
|
||||
|
||||
func (c *commandeer) handleBuildErr(err error, msg string) {
|
||||
c.buildErr = err
|
||||
|
||||
c.logger.Errorln(msg + ":\n")
|
||||
c.logger.Errorln(helpers.FirstUpper(err.Error()))
|
||||
if !c.h.quiet && c.h.verbose {
|
||||
herrors.PrintStackTraceFromErr(err)
|
||||
}
|
||||
c.logger.Errorln(msg + ": " + cleanErrorLog(err.Error()))
|
||||
}
|
||||
|
||||
func (c *commandeer) rebuildSites(events []fsnotify.Event) error {
|
||||
if c.buildErr != nil {
|
||||
ferrs := herrors.UnwrapFileErrorsWithErrorContext(c.buildErr)
|
||||
for _, err := range ferrs {
|
||||
events = append(events, fsnotify.Event{Name: err.Position().Filename, Op: fsnotify.Write})
|
||||
}
|
||||
}
|
||||
c.buildErr = nil
|
||||
visited := c.visitedURLs.PeekAllSet()
|
||||
if c.fastRenderMode {
|
||||
@@ -855,8 +863,13 @@ func (c *commandeer) newWatcher(pollIntervalStr string, dirList ...string) (*wat
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spec := c.hugo().Deps.SourceSpec
|
||||
|
||||
for _, d := range dirList {
|
||||
if d != "" {
|
||||
if spec.IgnoreFile(d) {
|
||||
continue
|
||||
}
|
||||
_ = watcher.Add(d)
|
||||
}
|
||||
}
|
||||
@@ -905,7 +918,7 @@ func (c *commandeer) printChangeDetected(typ string) {
|
||||
|
||||
c.logger.Println(msg)
|
||||
const layout = "2006-01-02 15:04:05.000 -0700"
|
||||
c.logger.Println(time.Now().Format(layout))
|
||||
c.logger.Println(htime.Now().Format(layout))
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
+176
-15
@@ -14,32 +14,193 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/bep/clock"
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/common/htime"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/spf13/afero"
|
||||
"golang.org/x/tools/txtar"
|
||||
)
|
||||
|
||||
// Issue #5662
|
||||
func TestHugoWithContentDirOverride(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
files := `
|
||||
-- config.toml --
|
||||
baseURL = "https://example.org"
|
||||
title = "Hugo Commands"
|
||||
-- mycontent/p1.md --
|
||||
---
|
||||
title: "P1"
|
||||
---
|
||||
-- layouts/_default/single.html --
|
||||
Page: {{ .Title }}|
|
||||
|
||||
`
|
||||
s := newTestHugoCmdBuilder(c, files, []string{"-c", "mycontent"}).Build()
|
||||
s.AssertFileContent("public/p1/index.html", `Page: P1|`)
|
||||
|
||||
}
|
||||
|
||||
// Issue #9794
|
||||
func TestHugoStaticFilesMultipleStaticAndManyFolders(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
files := `
|
||||
-- config.toml --
|
||||
baseURL = "https://example.org"
|
||||
theme = "mytheme"
|
||||
-- layouts/index.html --
|
||||
Home.
|
||||
|
||||
`
|
||||
const (
|
||||
numDirs = 33
|
||||
numFilesMax = 12
|
||||
)
|
||||
|
||||
r := rand.New(rand.NewSource(32))
|
||||
|
||||
for i := 0; i < numDirs; i++ {
|
||||
for j := 0; j < r.Intn(numFilesMax); j++ {
|
||||
if j%3 == 0 {
|
||||
files += fmt.Sprintf("-- themes/mytheme/static/d%d/f%d.txt --\nHellot%d-%d\n", i, j, i, j)
|
||||
files += fmt.Sprintf("-- themes/mytheme/static/d%d/ft%d.txt --\nHellot%d-%d\n", i, j, i, j)
|
||||
}
|
||||
files += fmt.Sprintf("-- static/d%d/f%d.txt --\nHello%d-%d\n", i, j, i, j)
|
||||
}
|
||||
}
|
||||
|
||||
r = rand.New(rand.NewSource(32))
|
||||
|
||||
s := newTestHugoCmdBuilder(c, files, []string{"-c", "mycontent"}).Build()
|
||||
for i := 0; i < numDirs; i++ {
|
||||
for j := 0; j < r.Intn(numFilesMax); j++ {
|
||||
if j%3 == 0 {
|
||||
if j%3 == 0 {
|
||||
s.AssertFileContent(fmt.Sprintf("public/d%d/ft%d.txt", i, j), fmt.Sprintf("Hellot%d-%d", i, j))
|
||||
}
|
||||
s.AssertFileContent(fmt.Sprintf("public/d%d/f%d.txt", i, j), fmt.Sprintf("Hello%d-%d", i, j))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Issue #8787
|
||||
func TestHugoListCommandsWithClockFlag(t *testing.T) {
|
||||
t.Cleanup(func() { htime.Clock = clock.System() })
|
||||
|
||||
c := qt.New(t)
|
||||
|
||||
files := `
|
||||
-- config.toml --
|
||||
baseURL = "https://example.org"
|
||||
title = "Hugo Commands"
|
||||
timeZone = "UTC"
|
||||
-- content/past.md --
|
||||
---
|
||||
title: "Past"
|
||||
date: 2000-11-06
|
||||
---
|
||||
-- content/future.md --
|
||||
---
|
||||
title: "Future"
|
||||
date: 2200-11-06
|
||||
---
|
||||
-- layouts/_default/single.html --
|
||||
Page: {{ .Title }}|
|
||||
|
||||
`
|
||||
s := newTestHugoCmdBuilder(c, files, []string{"list", "future"})
|
||||
s.captureOut = true
|
||||
s.Build()
|
||||
p := filepath.Join("content", "future.md")
|
||||
s.AssertStdout(p + ",2200-11-06T00:00:00Z")
|
||||
|
||||
s = newTestHugoCmdBuilder(c, files, []string{"list", "future", "--clock", "2300-11-06"}).Build()
|
||||
s.AssertStdout("")
|
||||
}
|
||||
|
||||
type testHugoCmdBuilder struct {
|
||||
*qt.C
|
||||
|
||||
fs afero.Fs
|
||||
dir string
|
||||
files string
|
||||
args []string
|
||||
|
||||
captureOut bool
|
||||
out string
|
||||
}
|
||||
|
||||
func newTestHugoCmdBuilder(c *qt.C, files string, args []string) *testHugoCmdBuilder {
|
||||
s := &testHugoCmdBuilder{C: c, files: files, args: args}
|
||||
s.dir = s.TempDir()
|
||||
s.fs = afero.NewBasePathFs(hugofs.Os, s.dir)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *testHugoCmdBuilder) Build() *testHugoCmdBuilder {
|
||||
data := txtar.Parse([]byte(s.files))
|
||||
|
||||
for _, f := range data.Files {
|
||||
filename := filepath.Clean(f.Name)
|
||||
data := bytes.TrimSuffix(f.Data, []byte("\n"))
|
||||
s.Assert(s.fs.MkdirAll(filepath.Dir(filename), 0777), qt.IsNil)
|
||||
s.Assert(afero.WriteFile(s.fs, filename, data, 0666), qt.IsNil)
|
||||
}
|
||||
|
||||
hugoCmd := newCommandsBuilder().addAll().build()
|
||||
cmd := hugoCmd.getCommand()
|
||||
args := append(s.args, "-s="+s.dir, "--quiet")
|
||||
cmd.SetArgs(args)
|
||||
|
||||
contentDir := "contentOverride"
|
||||
if s.captureOut {
|
||||
out, err := captureStdout(func() error {
|
||||
_, err := cmd.ExecuteC()
|
||||
return err
|
||||
})
|
||||
s.Assert(err, qt.IsNil)
|
||||
s.out = out
|
||||
} else {
|
||||
_, err := cmd.ExecuteC()
|
||||
s.Assert(err, qt.IsNil)
|
||||
}
|
||||
|
||||
cfgStr := `
|
||||
|
||||
baseURL = "https://example.org"
|
||||
title = "Hugo Commands"
|
||||
|
||||
contentDir = "thisdoesnotexist"
|
||||
|
||||
`
|
||||
dir := createSimpleTestSite(t, testSiteConfig{configTOML: cfgStr, contentDir: contentDir})
|
||||
|
||||
cmd.SetArgs([]string{"-s=" + dir, "-c=" + contentDir})
|
||||
|
||||
_, err := cmd.ExecuteC()
|
||||
c.Assert(err, qt.IsNil)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *testHugoCmdBuilder) AssertFileContent(filename string, matches ...string) {
|
||||
s.Helper()
|
||||
data, err := afero.ReadFile(s.fs, filename)
|
||||
s.Assert(err, qt.IsNil)
|
||||
content := strings.TrimSpace(string(data))
|
||||
for _, m := range matches {
|
||||
lines := strings.Split(m, "\n")
|
||||
for _, match := range lines {
|
||||
match = strings.TrimSpace(match)
|
||||
if match == "" || strings.HasPrefix(match, "#") {
|
||||
continue
|
||||
}
|
||||
s.Assert(content, qt.Contains, match, qt.Commentf(m))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *testHugoCmdBuilder) AssertStdout(match string) {
|
||||
s.Helper()
|
||||
content := strings.TrimSpace(s.out)
|
||||
s.Assert(content, qt.Contains, strings.TrimSpace(match))
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
|
||||
"github.com/gohugoio/hugo/parser/pageparser"
|
||||
|
||||
"github.com/gohugoio/hugo/common/htime"
|
||||
"github.com/gohugoio/hugo/common/hugio"
|
||||
|
||||
"github.com/gohugoio/hugo/parser/metadecoders"
|
||||
@@ -35,7 +36,6 @@ import (
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
"github.com/gohugoio/hugo/parser"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -201,12 +201,7 @@ func (i *importCmd) retrieveJekyllPostDir(fs afero.Fs, dir string) (bool, bool)
|
||||
}
|
||||
|
||||
func (i *importCmd) createSiteFromJekyll(jekyllRoot, targetDir string, jekyllPostDirs map[string]bool, force bool) error {
|
||||
s, err := hugolib.NewSiteDefaultLang()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fs := s.Fs.Source
|
||||
fs := &afero.OsFs{}
|
||||
if exists, _ := helpers.Exists(targetDir, fs); exists {
|
||||
if isDir, _ := helpers.IsDir(targetDir, fs); !isDir {
|
||||
return errors.New("target path \"" + targetDir + "\" exists but is not a directory")
|
||||
@@ -362,12 +357,12 @@ func parseJekyllFilename(filename string) (time.Time, string, error) {
|
||||
re := regexp.MustCompile(`(\d+-\d+-\d+)-(.+)\..*`)
|
||||
r := re.FindAllStringSubmatch(filename, -1)
|
||||
if len(r) == 0 {
|
||||
return time.Now(), "", errors.New("filename not match")
|
||||
return htime.Now(), "", errors.New("filename not match")
|
||||
}
|
||||
|
||||
postDate, err := time.Parse("2006-1-2", r[0][1])
|
||||
if err != nil {
|
||||
return time.Now(), "", err
|
||||
return htime.Now(), "", err
|
||||
}
|
||||
|
||||
postName := r[0][2]
|
||||
|
||||
@@ -99,6 +99,10 @@ List requires a subcommand, e.g. ` + "`hugo list drafts`.",
|
||||
return newSystemError("Error building sites", err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return newSystemError("Error building sites", err)
|
||||
}
|
||||
|
||||
writer := csv.NewWriter(os.Stdout)
|
||||
defer writer.Flush()
|
||||
|
||||
@@ -127,6 +131,10 @@ List requires a subcommand, e.g. ` + "`hugo list drafts`.",
|
||||
return newSystemError("Error building sites", err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return newSystemError("Error building sites", err)
|
||||
}
|
||||
|
||||
writer := csv.NewWriter(os.Stdout)
|
||||
defer writer.Flush()
|
||||
|
||||
|
||||
@@ -34,9 +34,9 @@ func TestListAll(t *testing.T) {
|
||||
hugoCmd := newCommandsBuilder().addAll().build()
|
||||
cmd := hugoCmd.getCommand()
|
||||
|
||||
defer func() {
|
||||
t.Cleanup(func() {
|
||||
os.RemoveAll(dir)
|
||||
}()
|
||||
})
|
||||
|
||||
cmd.SetArgs([]string{"-s=" + dir, "list", "all"})
|
||||
|
||||
|
||||
@@ -16,14 +16,13 @@ package commands
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/parser/metadecoders"
|
||||
|
||||
_errors "github.com/pkg/errors"
|
||||
|
||||
"github.com/gohugoio/hugo/create"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
@@ -94,7 +93,7 @@ func (n *newSiteCmd) doNewSite(fs *hugofs.Fs, basepath string, force bool) error
|
||||
|
||||
for _, dir := range dirs {
|
||||
if err := fs.Source.MkdirAll(dir, 0777); err != nil {
|
||||
return _errors.Wrap(err, "Failed to create dir")
|
||||
return fmt.Errorf("Failed to create dir: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@ import (
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gohugoio/hugo/common/htime"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -113,7 +113,7 @@ func (n *newThemeCmd) newTheme(cmd *cobra.Command, args []string) error {
|
||||
|
||||
by := []byte(`The MIT License (MIT)
|
||||
|
||||
Copyright (c) ` + time.Now().Format("2006") + ` YOUR_NAME_HERE
|
||||
Copyright (c) ` + htime.Now().Format("2006") + ` YOUR_NAME_HERE
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
||||
+91
-24
@@ -33,11 +33,12 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/gohugoio/hugo/common/htime"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
"github.com/gohugoio/hugo/tpl"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/gohugoio/hugo/livereload"
|
||||
|
||||
"github.com/gohugoio/hugo/config"
|
||||
@@ -167,8 +168,11 @@ func (sc *serverCmd) server(cmd *cobra.Command, args []string) error {
|
||||
c.Set("watch", true)
|
||||
}
|
||||
|
||||
// TODO(bep) yes, we should fix.
|
||||
if !c.languagesConfigured {
|
||||
// TODO(bep) see issue 9901
|
||||
// cfgInit is called twice, before and after the languages have been initialized.
|
||||
// The servers (below) can not be initialized before we
|
||||
// know if we're configured in a multihost setup.
|
||||
if len(c.languages) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -268,10 +272,6 @@ func (sc *serverCmd) server(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, s := range c.hugo().Sites {
|
||||
s.RegisterMediaTypes()
|
||||
}
|
||||
|
||||
// Watch runs its own server as part of the routine
|
||||
if sc.serverWatch {
|
||||
|
||||
@@ -366,7 +366,7 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string
|
||||
// We're only interested in the path
|
||||
u, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
return nil, nil, "", "", errors.Wrap(err, "Invalid baseURL")
|
||||
return nil, nil, "", "", fmt.Errorf("Invalid baseURL: %w", err)
|
||||
}
|
||||
|
||||
decorate := func(h http.Handler) http.Handler {
|
||||
@@ -475,12 +475,40 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string
|
||||
return mu, listener, u.String(), endpoint, nil
|
||||
}
|
||||
|
||||
var logErrorRe = regexp.MustCompile(`(?s)ERROR \d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} `)
|
||||
var (
|
||||
logErrorRe = regexp.MustCompile(`(?s)ERROR \d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} `)
|
||||
logDuplicateTemplateExecuteRe = regexp.MustCompile(`: template: .*?:\d+:\d+: executing ".*?"`)
|
||||
logDuplicateTemplateParseRe = regexp.MustCompile(`: template: .*?:\d+:\d*`)
|
||||
)
|
||||
|
||||
func removeErrorPrefixFromLog(content string) string {
|
||||
return logErrorRe.ReplaceAllLiteralString(content, "")
|
||||
}
|
||||
|
||||
var logReplacer = strings.NewReplacer(
|
||||
"can't", "can’t", // Chroma lexer does'nt do well with "can't"
|
||||
"*hugolib.pageState", "page.Page", // Page is the public interface.
|
||||
"Rebuild failed:", "",
|
||||
)
|
||||
|
||||
func cleanErrorLog(content string) string {
|
||||
content = strings.ReplaceAll(content, "\n", " ")
|
||||
content = logReplacer.Replace(content)
|
||||
content = logDuplicateTemplateExecuteRe.ReplaceAllString(content, "")
|
||||
content = logDuplicateTemplateParseRe.ReplaceAllString(content, "")
|
||||
seen := make(map[string]bool)
|
||||
parts := strings.Split(content, ": ")
|
||||
keep := make([]string, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
if seen[part] {
|
||||
continue
|
||||
}
|
||||
seen[part] = true
|
||||
keep = append(keep, part)
|
||||
}
|
||||
return strings.Join(keep, ": ")
|
||||
}
|
||||
|
||||
func (c *commandeer) serve(s *serverCmd) error {
|
||||
isMultiHost := c.hugo().IsMultihost()
|
||||
|
||||
@@ -500,10 +528,25 @@ func (c *commandeer) serve(s *serverCmd) error {
|
||||
roots = []string{""}
|
||||
}
|
||||
|
||||
templ, err := c.hugo().TextTmpl().Parse("__default_server_error", buildErrorTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
// Cache it here. The HugoSites object may be unavaialble later on due to intermitent configuration errors.
|
||||
// To allow the en user to change the error template while the server is running, we use
|
||||
// the freshest template we can provide.
|
||||
var (
|
||||
errTempl tpl.Template
|
||||
templHandler tpl.TemplateHandler
|
||||
)
|
||||
getErrorTemplateAndHandler := func(h *hugolib.HugoSites) (tpl.Template, tpl.TemplateHandler) {
|
||||
if h == nil {
|
||||
return errTempl, templHandler
|
||||
}
|
||||
templHandler := h.Tmpl()
|
||||
errTempl, found := templHandler.Lookup("_server/error.html")
|
||||
if !found {
|
||||
panic("template server/error.html not found")
|
||||
}
|
||||
return errTempl, templHandler
|
||||
}
|
||||
errTempl, templHandler = getErrorTemplateAndHandler(c.hugo())
|
||||
|
||||
srv := &fileServer{
|
||||
baseURLs: baseURLs,
|
||||
@@ -511,8 +554,11 @@ func (c *commandeer) serve(s *serverCmd) error {
|
||||
c: c,
|
||||
s: s,
|
||||
errorTemplate: func(ctx any) (io.Reader, error) {
|
||||
// hugoTry does not block, getErrorTemplateAndHandler will fall back
|
||||
// to cached values if nil.
|
||||
templ, handler := getErrorTemplateAndHandler(c.hugoTry())
|
||||
b := &bytes.Buffer{}
|
||||
err := c.hugo().Tmpl().Execute(templ, b, ctx)
|
||||
err := handler.Execute(templ, b, ctx)
|
||||
return b, err
|
||||
},
|
||||
}
|
||||
@@ -558,16 +604,37 @@ func (c *commandeer) serve(s *serverCmd) error {
|
||||
|
||||
jww.FEEDBACK.Println("Press Ctrl+C to stop")
|
||||
|
||||
if s.stop != nil {
|
||||
select {
|
||||
case <-sigs:
|
||||
case <-s.stop:
|
||||
err := func() error {
|
||||
if s.stop != nil {
|
||||
for {
|
||||
select {
|
||||
case <-sigs:
|
||||
return nil
|
||||
case <-s.stop:
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for {
|
||||
select {
|
||||
case <-sigs:
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
<-sigs
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
jww.ERROR.Println("Error:", err)
|
||||
}
|
||||
|
||||
c.hugo().Close()
|
||||
if h := c.hugoTry(); h != nil {
|
||||
h.Close()
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
@@ -627,7 +694,7 @@ func (sc *serverCmd) fixURL(cfg config.Provider, s string, port int) (string, er
|
||||
if strings.Contains(u.Host, ":") {
|
||||
u.Host, _, err = net.SplitHostPort(u.Host)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Failed to split baseURL hostpost")
|
||||
return "", fmt.Errorf("Failed to split baseURL hostpost: %w", err)
|
||||
}
|
||||
}
|
||||
u.Host += fmt.Sprintf(":%d", port)
|
||||
@@ -656,13 +723,13 @@ func memStats() error {
|
||||
go func() {
|
||||
var stats runtime.MemStats
|
||||
|
||||
start := time.Now().UnixNano()
|
||||
start := htime.Now().UnixNano()
|
||||
|
||||
for {
|
||||
runtime.ReadMemStats(&stats)
|
||||
if fileMemStats != nil {
|
||||
fileMemStats.WriteString(fmt.Sprintf("%d\t%d\t%d\t%d\t%d\n",
|
||||
(time.Now().UnixNano()-start)/1000000, stats.HeapSys, stats.HeapAlloc, stats.HeapIdle, stats.HeapReleased))
|
||||
(htime.Now().UnixNano()-start)/1000000, stats.HeapSys, stats.HeapAlloc, stats.HeapIdle, stats.HeapReleased))
|
||||
time.Sleep(interval)
|
||||
} else {
|
||||
break
|
||||
|
||||
@@ -22,67 +22,6 @@ import (
|
||||
"github.com/gohugoio/hugo/transform/livereloadinject"
|
||||
)
|
||||
|
||||
var buildErrorTemplate = `<!doctype html>
|
||||
<html class="no-js" lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Hugo Server: Error</title>
|
||||
<style type="text/css">
|
||||
body {
|
||||
font-family: "Muli",avenir, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
font-size: 16px;
|
||||
background-color: #2f1e2e;
|
||||
}
|
||||
main {
|
||||
margin: auto;
|
||||
width: 95%;
|
||||
padding: 1rem;
|
||||
}
|
||||
.version {
|
||||
color: #ccc;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
.stack {
|
||||
margin-top: 4rem;
|
||||
}
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
white-space: -moz-pre-wrap;
|
||||
white-space: -pre-wrap;
|
||||
white-space: -o-pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.highlight {
|
||||
overflow-x: auto;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
a {
|
||||
color: #0594cb;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
color: #ccc;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
{{ highlight .Error "apl" "linenos=false,noclasses=true,style=paraiso-dark" }}
|
||||
{{ with .File }}
|
||||
{{ $params := printf "noclasses=true,style=paraiso-dark,linenos=table,hl_lines=%d,linenostart=%d" (add .LinesPos 1) (sub .Position.LineNumber .LinesPos) }}
|
||||
{{ $lexer := .ChromaLexer | default "go-html-template" }}
|
||||
{{ highlight (delimit .Lines "\n") $lexer $params }}
|
||||
{{ end }}
|
||||
{{ with .StackTrace }}
|
||||
{{ highlight . "apl" "noclasses=true,style=paraiso-dark" }}
|
||||
{{ end }}
|
||||
<p class="version">{{ .Version }}</p>
|
||||
<a href="">Reload Page</a>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
func injectLiveReloadScript(src io.Reader, baseURL url.URL) string {
|
||||
var b bytes.Buffer
|
||||
chain := transform.Chain{livereloadinject.New(baseURL)}
|
||||
|
||||
+75
-22
@@ -31,16 +31,6 @@ import (
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
func TestServer(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
r := runServerTest(c, true, "")
|
||||
|
||||
c.Assert(r.err, qt.IsNil)
|
||||
c.Assert(r.homeContent, qt.Contains, "List: Hugo Commands")
|
||||
c.Assert(r.homeContent, qt.Contains, "Environment: development")
|
||||
}
|
||||
|
||||
// Issue 9518
|
||||
func TestServerPanicOnConfigError(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
@@ -51,7 +41,7 @@ func TestServerPanicOnConfigError(t *testing.T) {
|
||||
linenos='table'
|
||||
`
|
||||
|
||||
r := runServerTest(c, false, config)
|
||||
r := runServerTest(c, 0, config)
|
||||
|
||||
c.Assert(r.err, qt.IsNotNil)
|
||||
c.Assert(r.err.Error(), qt.Contains, "cannot parse 'Highlight.LineNos' as bool:")
|
||||
@@ -62,7 +52,7 @@ func TestServerFlags(t *testing.T) {
|
||||
|
||||
assertPublic := func(c *qt.C, r serverTestResult, renderStaticToDisk bool) {
|
||||
c.Assert(r.err, qt.IsNil)
|
||||
c.Assert(r.homeContent, qt.Contains, "Environment: development")
|
||||
c.Assert(r.homesContent[0], qt.Contains, "Environment: development")
|
||||
c.Assert(r.publicDirnames["myfile.txt"], qt.Equals, renderStaticToDisk)
|
||||
|
||||
}
|
||||
@@ -91,7 +81,7 @@ baseURL="https://example.org"
|
||||
args = strings.Split(test.flag, "=")
|
||||
}
|
||||
|
||||
r := runServerTest(c, true, config, args...)
|
||||
r := runServerTest(c, 1, config, args...)
|
||||
|
||||
test.assert(c, r)
|
||||
|
||||
@@ -101,13 +91,71 @@ baseURL="https://example.org"
|
||||
|
||||
}
|
||||
|
||||
func TestServerBugs(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
config string
|
||||
flag string
|
||||
numservers int
|
||||
assert func(c *qt.C, r serverTestResult)
|
||||
}{
|
||||
// Issue 9788
|
||||
{"PostProcess, memory", "", "", 1, func(c *qt.C, r serverTestResult) {
|
||||
c.Assert(r.err, qt.IsNil)
|
||||
c.Assert(r.homesContent[0], qt.Contains, "PostProcess: /foo.min.css")
|
||||
}},
|
||||
{"PostProcess, disk", "", "--renderToDisk", 1, func(c *qt.C, r serverTestResult) {
|
||||
c.Assert(r.err, qt.IsNil)
|
||||
c.Assert(r.homesContent[0], qt.Contains, "PostProcess: /foo.min.css")
|
||||
}},
|
||||
// Isue 9901
|
||||
{"Multihost", `
|
||||
defaultContentLanguage = 'en'
|
||||
[languages]
|
||||
[languages.en]
|
||||
baseURL = 'https://example.com'
|
||||
title = 'My blog'
|
||||
weight = 1
|
||||
[languages.fr]
|
||||
baseURL = 'https://example.fr'
|
||||
title = 'Mon blogue'
|
||||
weight = 2
|
||||
`, "", 2, func(c *qt.C, r serverTestResult) {
|
||||
c.Assert(r.err, qt.IsNil)
|
||||
for i, s := range []string{"My blog", "Mon blogue"} {
|
||||
c.Assert(r.homesContent[i], qt.Contains, s)
|
||||
}
|
||||
}},
|
||||
} {
|
||||
c.Run(test.name, func(c *qt.C) {
|
||||
if test.config == "" {
|
||||
test.config = `
|
||||
baseURL="https://example.org"
|
||||
`
|
||||
}
|
||||
|
||||
var args []string
|
||||
if test.flag != "" {
|
||||
args = strings.Split(test.flag, "=")
|
||||
}
|
||||
r := runServerTest(c, test.numservers, test.config, args...)
|
||||
test.assert(c, r)
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type serverTestResult struct {
|
||||
err error
|
||||
homeContent string
|
||||
homesContent []string
|
||||
publicDirnames map[string]bool
|
||||
}
|
||||
|
||||
func runServerTest(c *qt.C, getHome bool, config string, args ...string) (result serverTestResult) {
|
||||
func runServerTest(c *qt.C, getNumHomes int, config string, args ...string) (result serverTestResult) {
|
||||
dir := createSimpleTestSite(c, testSiteConfig{configTOML: config})
|
||||
|
||||
sp, err := helpers.FindAvailablePort()
|
||||
@@ -136,16 +184,21 @@ func runServerTest(c *qt.C, getHome bool, config string, args ...string) (result
|
||||
return err
|
||||
})
|
||||
|
||||
if getHome {
|
||||
if getNumHomes > 0 {
|
||||
// Esp. on slow CI machines, we need to wait a little before the web
|
||||
// server is ready.
|
||||
time.Sleep(567 * time.Millisecond)
|
||||
resp, err := http.Get(fmt.Sprintf("http://localhost:%d/", port))
|
||||
c.Check(err, qt.IsNil)
|
||||
c.Check(resp.StatusCode, qt.Equals, http.StatusOK)
|
||||
if err == nil {
|
||||
defer resp.Body.Close()
|
||||
result.homeContent = helpers.ReaderToString(resp.Body)
|
||||
result.homesContent = make([]string, getNumHomes)
|
||||
for i := 0; i < getNumHomes; i++ {
|
||||
func() {
|
||||
resp, err := http.Get(fmt.Sprintf("http://localhost:%d/", port+i))
|
||||
c.Check(err, qt.IsNil)
|
||||
c.Check(resp.StatusCode, qt.Equals, http.StatusOK)
|
||||
if err == nil {
|
||||
defer resp.Body.Close()
|
||||
result.homesContent[i] = helpers.ReaderToString(resp.Body)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+43
-141
@@ -1,4 +1,4 @@
|
||||
// Copyright 2018 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2022 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -21,8 +21,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/common/text"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
// LineMatcher contains the elements used to match an error to a line
|
||||
@@ -36,14 +34,33 @@ type LineMatcher struct {
|
||||
}
|
||||
|
||||
// LineMatcherFn is used to match a line with an error.
|
||||
type LineMatcherFn func(m LineMatcher) bool
|
||||
// It returns the column number or 0 if the line was found, but column could not be determinde. Returns -1 if no line match.
|
||||
type LineMatcherFn func(m LineMatcher) int
|
||||
|
||||
// SimpleLineMatcher simply matches by line number.
|
||||
var SimpleLineMatcher = func(m LineMatcher) bool {
|
||||
return m.Position.LineNumber == m.LineNumber
|
||||
var SimpleLineMatcher = func(m LineMatcher) int {
|
||||
if m.Position.LineNumber == m.LineNumber {
|
||||
// We found the line, but don't know the column.
|
||||
return 0
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
var _ text.Positioner = ErrorContext{}
|
||||
// NopLineMatcher is a matcher that always returns 1.
|
||||
// This will effectively give line 1, column 1.
|
||||
var NopLineMatcher = func(m LineMatcher) int {
|
||||
return 1
|
||||
}
|
||||
|
||||
// OffsetMatcher is a line matcher that matches by offset.
|
||||
var OffsetMatcher = func(m LineMatcher) int {
|
||||
if m.Offset+len(m.Line) >= m.Position.Offset {
|
||||
// We found the line, but return 0 to signal that we want to determine
|
||||
// the column from the error.
|
||||
return 0
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// ErrorContext contains contextual information about an error. This will
|
||||
// typically be the lines surrounding some problem in a file.
|
||||
@@ -56,125 +73,15 @@ type ErrorContext struct {
|
||||
// The position of the error in the Lines above. 0 based.
|
||||
LinesPos int
|
||||
|
||||
position text.Position
|
||||
// The position of the content in the file. Note that this may be different from the error's position set
|
||||
// in FileError.
|
||||
Position text.Position
|
||||
|
||||
// The lexer to use for syntax highlighting.
|
||||
// https://gohugo.io/content-management/syntax-highlighting/#list-of-chroma-highlighting-languages
|
||||
ChromaLexer string
|
||||
}
|
||||
|
||||
// Position returns the text position of this error.
|
||||
func (e ErrorContext) Position() text.Position {
|
||||
return e.position
|
||||
}
|
||||
|
||||
var _ causer = (*ErrorWithFileContext)(nil)
|
||||
|
||||
// ErrorWithFileContext is an error with some additional file context related
|
||||
// to that error.
|
||||
type ErrorWithFileContext struct {
|
||||
cause error
|
||||
ErrorContext
|
||||
}
|
||||
|
||||
func (e *ErrorWithFileContext) Error() string {
|
||||
pos := e.Position()
|
||||
if pos.IsValid() {
|
||||
return pos.String() + ": " + e.cause.Error()
|
||||
}
|
||||
return e.cause.Error()
|
||||
}
|
||||
|
||||
func (e *ErrorWithFileContext) Cause() error {
|
||||
return e.cause
|
||||
}
|
||||
|
||||
// WithFileContextForFile will try to add a file context with lines matching the given matcher.
|
||||
// If no match could be found, the original error is returned with false as the second return value.
|
||||
func WithFileContextForFile(e error, realFilename, filename string, fs afero.Fs, matcher LineMatcherFn) (error, bool) {
|
||||
f, err := fs.Open(filename)
|
||||
if err != nil {
|
||||
return e, false
|
||||
}
|
||||
defer f.Close()
|
||||
return WithFileContext(e, realFilename, f, matcher)
|
||||
}
|
||||
|
||||
// WithFileContextForFileDefault tries to add file context using the default line matcher.
|
||||
func WithFileContextForFileDefault(err error, filename string, fs afero.Fs) error {
|
||||
err, _ = WithFileContextForFile(
|
||||
err,
|
||||
filename,
|
||||
filename,
|
||||
fs,
|
||||
SimpleLineMatcher)
|
||||
return err
|
||||
}
|
||||
|
||||
// WithFileContextForFile will try to add a file context with lines matching the given matcher.
|
||||
// If no match could be found, the original error is returned with false as the second return value.
|
||||
func WithFileContext(e error, realFilename string, r io.Reader, matcher LineMatcherFn) (error, bool) {
|
||||
if e == nil {
|
||||
panic("error missing")
|
||||
}
|
||||
le := UnwrapFileError(e)
|
||||
|
||||
if le == nil {
|
||||
var ok bool
|
||||
if le, ok = ToFileError("", e).(FileError); !ok {
|
||||
return e, false
|
||||
}
|
||||
}
|
||||
|
||||
var errCtx ErrorContext
|
||||
|
||||
posle := le.Position()
|
||||
|
||||
if posle.Offset != -1 {
|
||||
errCtx = locateError(r, le, func(m LineMatcher) bool {
|
||||
if posle.Offset >= m.Offset && posle.Offset < m.Offset+len(m.Line) {
|
||||
lno := posle.LineNumber - m.Position.LineNumber + m.LineNumber
|
||||
m.Position = text.Position{LineNumber: lno}
|
||||
}
|
||||
return matcher(m)
|
||||
})
|
||||
} else {
|
||||
errCtx = locateError(r, le, matcher)
|
||||
}
|
||||
|
||||
pos := &errCtx.position
|
||||
|
||||
if pos.LineNumber == -1 {
|
||||
return e, false
|
||||
}
|
||||
|
||||
pos.Filename = realFilename
|
||||
|
||||
if le.Type() != "" {
|
||||
errCtx.ChromaLexer = chromaLexerFromType(le.Type())
|
||||
} else {
|
||||
errCtx.ChromaLexer = chromaLexerFromFilename(realFilename)
|
||||
}
|
||||
|
||||
return &ErrorWithFileContext{cause: e, ErrorContext: errCtx}, true
|
||||
}
|
||||
|
||||
// UnwrapErrorWithFileContext tries to unwrap an ErrorWithFileContext from err.
|
||||
// It returns nil if this is not possible.
|
||||
func UnwrapErrorWithFileContext(err error) *ErrorWithFileContext {
|
||||
for err != nil {
|
||||
switch v := err.(type) {
|
||||
case *ErrorWithFileContext:
|
||||
return v
|
||||
case causer:
|
||||
err = v.Cause()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func chromaLexerFromType(fileType string) string {
|
||||
switch fileType {
|
||||
case "html", "htm":
|
||||
@@ -196,31 +103,24 @@ func chromaLexerFromFilename(filename string) string {
|
||||
return chromaLexerFromType(ext)
|
||||
}
|
||||
|
||||
func locateErrorInString(src string, matcher LineMatcherFn) ErrorContext {
|
||||
func locateErrorInString(src string, matcher LineMatcherFn) *ErrorContext {
|
||||
return locateError(strings.NewReader(src), &fileError{}, matcher)
|
||||
}
|
||||
|
||||
func locateError(r io.Reader, le FileError, matches LineMatcherFn) ErrorContext {
|
||||
func locateError(r io.Reader, le FileError, matches LineMatcherFn) *ErrorContext {
|
||||
if le == nil {
|
||||
panic("must provide an error")
|
||||
}
|
||||
|
||||
errCtx := ErrorContext{position: text.Position{LineNumber: -1, ColumnNumber: 1, Offset: -1}, LinesPos: -1}
|
||||
ectx := &ErrorContext{LinesPos: -1, Position: text.Position{Offset: -1}}
|
||||
|
||||
b, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return errCtx
|
||||
return ectx
|
||||
}
|
||||
|
||||
pos := &errCtx.position
|
||||
lepos := le.Position()
|
||||
|
||||
lines := strings.Split(string(b), "\n")
|
||||
|
||||
if lepos.ColumnNumber >= 0 {
|
||||
pos.ColumnNumber = lepos.ColumnNumber
|
||||
}
|
||||
|
||||
lineNo := 0
|
||||
posBytes := 0
|
||||
|
||||
@@ -233,34 +133,36 @@ func locateError(r io.Reader, le FileError, matches LineMatcherFn) ErrorContext
|
||||
Offset: posBytes,
|
||||
Line: line,
|
||||
}
|
||||
if errCtx.LinesPos == -1 && matches(m) {
|
||||
pos.LineNumber = lineNo
|
||||
v := matches(m)
|
||||
if ectx.LinesPos == -1 && v != -1 {
|
||||
ectx.Position.LineNumber = lineNo
|
||||
ectx.Position.ColumnNumber = v
|
||||
break
|
||||
}
|
||||
|
||||
posBytes += len(line)
|
||||
}
|
||||
|
||||
if pos.LineNumber != -1 {
|
||||
low := pos.LineNumber - 3
|
||||
if ectx.Position.LineNumber > 0 {
|
||||
low := ectx.Position.LineNumber - 3
|
||||
if low < 0 {
|
||||
low = 0
|
||||
}
|
||||
|
||||
if pos.LineNumber > 2 {
|
||||
errCtx.LinesPos = 2
|
||||
if ectx.Position.LineNumber > 2 {
|
||||
ectx.LinesPos = 2
|
||||
} else {
|
||||
errCtx.LinesPos = pos.LineNumber - 1
|
||||
ectx.LinesPos = ectx.Position.LineNumber - 1
|
||||
}
|
||||
|
||||
high := pos.LineNumber + 2
|
||||
high := ectx.Position.LineNumber + 2
|
||||
if high > len(lines) {
|
||||
high = len(lines)
|
||||
}
|
||||
|
||||
errCtx.Lines = lines[low:high]
|
||||
ectx.Lines = lines[low:high]
|
||||
|
||||
}
|
||||
|
||||
return errCtx
|
||||
return ectx
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2018 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2022 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -24,8 +24,11 @@ import (
|
||||
func TestErrorLocator(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
lineMatcher := func(m LineMatcher) bool {
|
||||
return strings.Contains(m.Line, "THEONE")
|
||||
lineMatcher := func(m LineMatcher) int {
|
||||
if strings.Contains(m.Line, "THEONE") {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
lines := `LINE 1
|
||||
@@ -39,35 +42,41 @@ LINE 8
|
||||
`
|
||||
|
||||
location := locateErrorInString(lines, lineMatcher)
|
||||
pos := location.Position
|
||||
c.Assert(location.Lines, qt.DeepEquals, []string{"LINE 3", "LINE 4", "This is THEONE", "LINE 6", "LINE 7"})
|
||||
|
||||
pos := location.Position()
|
||||
c.Assert(pos.LineNumber, qt.Equals, 5)
|
||||
c.Assert(location.LinesPos, qt.Equals, 2)
|
||||
|
||||
c.Assert(locateErrorInString(`This is THEONE`, lineMatcher).Lines, qt.DeepEquals, []string{"This is THEONE"})
|
||||
locate := func(s string, m LineMatcherFn) *ErrorContext {
|
||||
ctx := locateErrorInString(s, m)
|
||||
return ctx
|
||||
}
|
||||
|
||||
c.Assert(locate(`This is THEONE`, lineMatcher).Lines, qt.DeepEquals, []string{"This is THEONE"})
|
||||
|
||||
location = locateErrorInString(`L1
|
||||
This is THEONE
|
||||
L2
|
||||
`, lineMatcher)
|
||||
c.Assert(location.Position().LineNumber, qt.Equals, 2)
|
||||
pos = location.Position
|
||||
c.Assert(pos.LineNumber, qt.Equals, 2)
|
||||
c.Assert(location.LinesPos, qt.Equals, 1)
|
||||
c.Assert(location.Lines, qt.DeepEquals, []string{"L1", "This is THEONE", "L2", ""})
|
||||
|
||||
location = locateErrorInString(`This is THEONE
|
||||
location = locate(`This is THEONE
|
||||
L2
|
||||
`, lineMatcher)
|
||||
c.Assert(location.LinesPos, qt.Equals, 0)
|
||||
c.Assert(location.Lines, qt.DeepEquals, []string{"This is THEONE", "L2", ""})
|
||||
|
||||
location = locateErrorInString(`L1
|
||||
location = locate(`L1
|
||||
This THEONE
|
||||
`, lineMatcher)
|
||||
c.Assert(location.Lines, qt.DeepEquals, []string{"L1", "This THEONE", ""})
|
||||
c.Assert(location.LinesPos, qt.Equals, 1)
|
||||
|
||||
location = locateErrorInString(`L1
|
||||
location = locate(`L1
|
||||
L2
|
||||
This THEONE
|
||||
`, lineMatcher)
|
||||
@@ -75,12 +84,16 @@ This THEONE
|
||||
c.Assert(location.LinesPos, qt.Equals, 2)
|
||||
|
||||
location = locateErrorInString("NO MATCH", lineMatcher)
|
||||
c.Assert(location.Position().LineNumber, qt.Equals, -1)
|
||||
pos = location.Position
|
||||
c.Assert(pos.LineNumber, qt.Equals, 0)
|
||||
c.Assert(location.LinesPos, qt.Equals, -1)
|
||||
c.Assert(len(location.Lines), qt.Equals, 0)
|
||||
|
||||
lineMatcher = func(m LineMatcher) bool {
|
||||
return m.LineNumber == 6
|
||||
lineMatcher = func(m LineMatcher) int {
|
||||
if m.LineNumber == 6 {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
location = locateErrorInString(`A
|
||||
@@ -93,14 +106,18 @@ G
|
||||
H
|
||||
I
|
||||
J`, lineMatcher)
|
||||
pos = location.Position
|
||||
|
||||
c.Assert(location.Lines, qt.DeepEquals, []string{"D", "E", "F", "G", "H"})
|
||||
c.Assert(location.Position().LineNumber, qt.Equals, 6)
|
||||
c.Assert(pos.LineNumber, qt.Equals, 6)
|
||||
c.Assert(location.LinesPos, qt.Equals, 2)
|
||||
|
||||
// Test match EOF
|
||||
lineMatcher = func(m LineMatcher) bool {
|
||||
return m.LineNumber == 4
|
||||
lineMatcher = func(m LineMatcher) int {
|
||||
if m.LineNumber == 4 {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
location = locateErrorInString(`A
|
||||
@@ -108,12 +125,17 @@ B
|
||||
C
|
||||
`, lineMatcher)
|
||||
|
||||
pos = location.Position
|
||||
|
||||
c.Assert(location.Lines, qt.DeepEquals, []string{"B", "C", ""})
|
||||
c.Assert(location.Position().LineNumber, qt.Equals, 4)
|
||||
c.Assert(pos.LineNumber, qt.Equals, 4)
|
||||
c.Assert(location.LinesPos, qt.Equals, 2)
|
||||
|
||||
offsetMatcher := func(m LineMatcher) bool {
|
||||
return m.Offset == 1
|
||||
offsetMatcher := func(m LineMatcher) int {
|
||||
if m.Offset == 1 {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
location = locateErrorInString(`A
|
||||
@@ -122,7 +144,9 @@ C
|
||||
D
|
||||
E`, offsetMatcher)
|
||||
|
||||
pos = location.Position
|
||||
|
||||
c.Assert(location.Lines, qt.DeepEquals, []string{"A", "B", "C", "D"})
|
||||
c.Assert(location.Position().LineNumber, qt.Equals, 2)
|
||||
c.Assert(pos.LineNumber, qt.Equals, 2)
|
||||
c.Assert(location.LinesPos, qt.Equals, 1)
|
||||
}
|
||||
|
||||
@@ -19,37 +19,11 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
|
||||
_errors "github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// As defined in https://godoc.org/github.com/pkg/errors
|
||||
type causer interface {
|
||||
Cause() error
|
||||
}
|
||||
|
||||
type stackTracer interface {
|
||||
StackTrace() _errors.StackTrace
|
||||
}
|
||||
|
||||
// PrintStackTraceFromErr prints the error's stack trace to stdoud.
|
||||
func PrintStackTraceFromErr(err error) {
|
||||
FprintStackTraceFromErr(os.Stdout, err)
|
||||
}
|
||||
|
||||
// FprintStackTraceFromErr prints the error's stack trace to w.
|
||||
func FprintStackTraceFromErr(w io.Writer, err error) {
|
||||
if err, ok := err.(stackTracer); ok {
|
||||
for _, f := range err.StackTrace() {
|
||||
fmt.Fprintf(w, "%+s:%d\n", f, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PrintStackTrace prints the current stacktrace to w.
|
||||
func PrintStackTrace(w io.Writer) {
|
||||
buf := make([]byte, 1<<16)
|
||||
|
||||
+313
-50
@@ -1,11 +1,11 @@
|
||||
// Copyright 2018 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2022 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// Unless required by applicable lfmtaw or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
@@ -15,61 +15,306 @@ package herrors
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/bep/godartsass"
|
||||
"github.com/bep/golibsass/libsass/libsasserrors"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/common/text"
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/tdewolff/parse/v2"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"errors"
|
||||
)
|
||||
|
||||
var _ causer = (*fileError)(nil)
|
||||
|
||||
// FileError represents an error when handling a file: Parsing a config file,
|
||||
// execute a template etc.
|
||||
type FileError interface {
|
||||
error
|
||||
|
||||
// ErroContext holds some context information about the error.
|
||||
ErrorContext() *ErrorContext
|
||||
|
||||
text.Positioner
|
||||
|
||||
// A string identifying the type of file, e.g. JSON, TOML, markdown etc.
|
||||
Type() string
|
||||
// UpdatePosition updates the position of the error.
|
||||
UpdatePosition(pos text.Position) FileError
|
||||
|
||||
// UpdateContent updates the error with a new ErrorContext from the content of the file.
|
||||
UpdateContent(r io.Reader, linematcher LineMatcherFn) FileError
|
||||
}
|
||||
|
||||
var _ FileError = (*fileError)(nil)
|
||||
// Unwrapper can unwrap errors created with fmt.Errorf.
|
||||
type Unwrapper interface {
|
||||
Unwrap() error
|
||||
}
|
||||
|
||||
var (
|
||||
_ FileError = (*fileError)(nil)
|
||||
_ Unwrapper = (*fileError)(nil)
|
||||
)
|
||||
|
||||
func (fe *fileError) UpdatePosition(pos text.Position) FileError {
|
||||
oldFilename := fe.Position().Filename
|
||||
if pos.Filename != "" && fe.fileType == "" {
|
||||
_, fe.fileType = paths.FileAndExtNoDelimiter(filepath.Clean(pos.Filename))
|
||||
}
|
||||
if pos.Filename == "" {
|
||||
pos.Filename = oldFilename
|
||||
}
|
||||
fe.position = pos
|
||||
return fe
|
||||
}
|
||||
|
||||
func (fe *fileError) UpdateContent(r io.Reader, linematcher LineMatcherFn) FileError {
|
||||
if linematcher == nil {
|
||||
linematcher = SimpleLineMatcher
|
||||
}
|
||||
|
||||
var (
|
||||
posle = fe.position
|
||||
ectx *ErrorContext
|
||||
)
|
||||
|
||||
if posle.LineNumber <= 1 && posle.Offset > 0 {
|
||||
// Try to locate the line number from the content if offset is set.
|
||||
ectx = locateError(r, fe, func(m LineMatcher) int {
|
||||
if posle.Offset >= m.Offset && posle.Offset < m.Offset+len(m.Line) {
|
||||
lno := posle.LineNumber - m.Position.LineNumber + m.LineNumber
|
||||
m.Position = text.Position{LineNumber: lno}
|
||||
return linematcher(m)
|
||||
}
|
||||
return -1
|
||||
})
|
||||
} else {
|
||||
ectx = locateError(r, fe, linematcher)
|
||||
}
|
||||
|
||||
if ectx.ChromaLexer == "" {
|
||||
if fe.fileType != "" {
|
||||
ectx.ChromaLexer = chromaLexerFromType(fe.fileType)
|
||||
} else {
|
||||
ectx.ChromaLexer = chromaLexerFromFilename(fe.Position().Filename)
|
||||
}
|
||||
}
|
||||
|
||||
fe.errorContext = ectx
|
||||
|
||||
if ectx.Position.LineNumber > 0 {
|
||||
fe.position.LineNumber = ectx.Position.LineNumber
|
||||
}
|
||||
|
||||
if ectx.Position.ColumnNumber > 0 {
|
||||
fe.position.ColumnNumber = ectx.Position.ColumnNumber
|
||||
}
|
||||
|
||||
return fe
|
||||
|
||||
}
|
||||
|
||||
type fileError struct {
|
||||
position text.Position
|
||||
position text.Position
|
||||
errorContext *ErrorContext
|
||||
|
||||
fileType string
|
||||
|
||||
cause error
|
||||
}
|
||||
|
||||
func (e *fileError) ErrorContext() *ErrorContext {
|
||||
return e.errorContext
|
||||
}
|
||||
|
||||
// Position returns the text position of this error.
|
||||
func (e fileError) Position() text.Position {
|
||||
return e.position
|
||||
}
|
||||
|
||||
func (e *fileError) Type() string {
|
||||
return e.fileType
|
||||
func (e *fileError) Error() string {
|
||||
return fmt.Sprintf("%s: %s", e.position, e.causeString())
|
||||
}
|
||||
|
||||
func (e *fileError) Error() string {
|
||||
func (e *fileError) causeString() string {
|
||||
if e.cause == nil {
|
||||
return ""
|
||||
}
|
||||
return e.cause.Error()
|
||||
switch v := e.cause.(type) {
|
||||
// Avoid repeating the file info in the error message.
|
||||
case godartsass.SassError:
|
||||
return v.Message
|
||||
case libsasserrors.Error:
|
||||
return v.Message
|
||||
default:
|
||||
return v.Error()
|
||||
}
|
||||
}
|
||||
|
||||
func (f *fileError) Cause() error {
|
||||
return f.cause
|
||||
func (e *fileError) Unwrap() error {
|
||||
return e.cause
|
||||
}
|
||||
|
||||
// NewFileError creates a new FileError.
|
||||
func NewFileError(fileType string, offset, lineNumber, columnNumber int, err error) FileError {
|
||||
pos := text.Position{Offset: offset, LineNumber: lineNumber, ColumnNumber: columnNumber}
|
||||
// NewFileError creates a new FileError that wraps err.
|
||||
// It will try to extract the filename and line number from err.
|
||||
func NewFileError(err error) FileError {
|
||||
// Filetype is used to determine the Chroma lexer to use.
|
||||
fileType, pos := extractFileTypePos(err)
|
||||
return &fileError{cause: err, fileType: fileType, position: pos}
|
||||
}
|
||||
|
||||
// NewFileErrorFromName creates a new FileError that wraps err.
|
||||
// The value for name should identify the file, the best
|
||||
// being the full filename to the file on disk.
|
||||
func NewFileErrorFromName(err error, name string) FileError {
|
||||
// Filetype is used to determine the Chroma lexer to use.
|
||||
fileType, pos := extractFileTypePos(err)
|
||||
pos.Filename = name
|
||||
if fileType == "" {
|
||||
_, fileType = paths.FileAndExtNoDelimiter(filepath.Clean(name))
|
||||
}
|
||||
|
||||
return &fileError{cause: err, fileType: fileType, position: pos}
|
||||
|
||||
}
|
||||
|
||||
// NewFileErrorFromPos will use the filename and line number from pos to create a new FileError, wrapping err.
|
||||
func NewFileErrorFromPos(err error, pos text.Position) FileError {
|
||||
// Filetype is used to determine the Chroma lexer to use.
|
||||
fileType, _ := extractFileTypePos(err)
|
||||
if fileType == "" {
|
||||
_, fileType = paths.FileAndExtNoDelimiter(filepath.Clean(pos.Filename))
|
||||
}
|
||||
return &fileError{cause: err, fileType: fileType, position: pos}
|
||||
|
||||
}
|
||||
|
||||
func NewFileErrorFromFileInErr(err error, fs afero.Fs, linematcher LineMatcherFn) FileError {
|
||||
fe := NewFileError(err)
|
||||
pos := fe.Position()
|
||||
if pos.Filename == "" {
|
||||
return fe
|
||||
}
|
||||
|
||||
f, realFilename, err2 := openFile(pos.Filename, fs)
|
||||
if err2 != nil {
|
||||
return fe
|
||||
}
|
||||
|
||||
pos.Filename = realFilename
|
||||
defer f.Close()
|
||||
return fe.UpdateContent(f, linematcher)
|
||||
}
|
||||
|
||||
func NewFileErrorFromFileInPos(err error, pos text.Position, fs afero.Fs, linematcher LineMatcherFn) FileError {
|
||||
if err == nil {
|
||||
panic("err is nil")
|
||||
}
|
||||
f, realFilename, err2 := openFile(pos.Filename, fs)
|
||||
if err2 != nil {
|
||||
return NewFileErrorFromPos(err, pos)
|
||||
}
|
||||
pos.Filename = realFilename
|
||||
defer f.Close()
|
||||
return NewFileErrorFromPos(err, pos).UpdateContent(f, linematcher)
|
||||
}
|
||||
|
||||
// NewFileErrorFromFile is a convenience method to create a new FileError from a file.
|
||||
func NewFileErrorFromFile(err error, filename string, fs afero.Fs, linematcher LineMatcherFn) FileError {
|
||||
if err == nil {
|
||||
panic("err is nil")
|
||||
}
|
||||
f, realFilename, err2 := openFile(filename, fs)
|
||||
if err2 != nil {
|
||||
return NewFileErrorFromName(err, realFilename)
|
||||
}
|
||||
defer f.Close()
|
||||
return NewFileErrorFromName(err, realFilename).UpdateContent(f, linematcher)
|
||||
}
|
||||
|
||||
func openFile(filename string, fs afero.Fs) (afero.File, string, error) {
|
||||
realFilename := filename
|
||||
|
||||
// We want the most specific filename possible in the error message.
|
||||
fi, err2 := fs.Stat(filename)
|
||||
if err2 == nil {
|
||||
if s, ok := fi.(interface {
|
||||
Filename() string
|
||||
}); ok {
|
||||
realFilename = s.Filename()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
f, err2 := fs.Open(filename)
|
||||
if err2 != nil {
|
||||
return nil, realFilename, err2
|
||||
}
|
||||
|
||||
return f, realFilename, nil
|
||||
}
|
||||
|
||||
// Cause returns the underlying error or itself if it does not implement Unwrap.
|
||||
func Cause(err error) error {
|
||||
if u := errors.Unwrap(err); u != nil {
|
||||
return u
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func extractFileTypePos(err error) (string, text.Position) {
|
||||
err = Cause(err)
|
||||
|
||||
var fileType string
|
||||
|
||||
// LibSass, DartSass
|
||||
if pos := extractPosition(err); pos.LineNumber > 0 || pos.Offset > 0 {
|
||||
_, fileType = paths.FileAndExtNoDelimiter(pos.Filename)
|
||||
return fileType, pos
|
||||
}
|
||||
|
||||
// Default to line 1 col 1 if we don't find any better.
|
||||
pos := text.Position{
|
||||
Offset: -1,
|
||||
LineNumber: 1,
|
||||
ColumnNumber: 1,
|
||||
}
|
||||
|
||||
// JSON errors.
|
||||
offset, typ := extractOffsetAndType(err)
|
||||
if fileType == "" {
|
||||
fileType = typ
|
||||
}
|
||||
|
||||
if offset >= 0 {
|
||||
pos.Offset = offset
|
||||
}
|
||||
|
||||
// The error type from the minifier contains line number and column number.
|
||||
if line, col := exctractLineNumberAndColumnNumber(err); line >= 0 {
|
||||
pos.LineNumber = line
|
||||
pos.ColumnNumber = col
|
||||
return fileType, pos
|
||||
}
|
||||
|
||||
// Look in the error message for the line number.
|
||||
for _, handle := range lineNumberExtractors {
|
||||
lno, col := handle(err)
|
||||
if lno > 0 {
|
||||
pos.ColumnNumber = col
|
||||
pos.LineNumber = lno
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if fileType == "" && pos.Filename != "" {
|
||||
_, fileType = paths.FileAndExtNoDelimiter(pos.Filename)
|
||||
}
|
||||
|
||||
return fileType, pos
|
||||
}
|
||||
|
||||
// UnwrapFileError tries to unwrap a FileError from err.
|
||||
// It returns nil if this is not possible.
|
||||
func UnwrapFileError(err error) FileError {
|
||||
@@ -77,49 +322,38 @@ func UnwrapFileError(err error) FileError {
|
||||
switch v := err.(type) {
|
||||
case FileError:
|
||||
return v
|
||||
case causer:
|
||||
err = v.Cause()
|
||||
default:
|
||||
return nil
|
||||
err = errors.Unwrap(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToFileErrorWithOffset will return a new FileError with a line number
|
||||
// with the given offset from the original.
|
||||
func ToFileErrorWithOffset(fe FileError, offset int) FileError {
|
||||
pos := fe.Position()
|
||||
return ToFileErrorWithLineNumber(fe, pos.LineNumber+offset)
|
||||
}
|
||||
|
||||
// ToFileErrorWithOffset will return a new FileError with the given line number.
|
||||
func ToFileErrorWithLineNumber(fe FileError, lineNumber int) FileError {
|
||||
pos := fe.Position()
|
||||
pos.LineNumber = lineNumber
|
||||
return &fileError{cause: fe, fileType: fe.Type(), position: pos}
|
||||
}
|
||||
|
||||
// ToFileError will convert the given error to an error supporting
|
||||
// the FileError interface.
|
||||
func ToFileError(fileType string, err error) FileError {
|
||||
for _, handle := range lineNumberExtractors {
|
||||
lno, col := handle(err)
|
||||
offset, typ := extractOffsetAndType(err)
|
||||
if fileType == "" {
|
||||
fileType = typ
|
||||
}
|
||||
|
||||
if lno > 0 || offset != -1 {
|
||||
return NewFileError(fileType, offset, lno, col, err)
|
||||
// UnwrapFileErrors tries to unwrap all FileError.
|
||||
func UnwrapFileErrors(err error) []FileError {
|
||||
var errs []FileError
|
||||
for err != nil {
|
||||
if v, ok := err.(FileError); ok {
|
||||
errs = append(errs, v)
|
||||
}
|
||||
err = errors.Unwrap(err)
|
||||
}
|
||||
// Fall back to the pointing to line number 1.
|
||||
return NewFileError(fileType, -1, 1, 1, err)
|
||||
return errs
|
||||
}
|
||||
|
||||
// UnwrapFileErrorsWithErrorContext tries to unwrap all FileError in err that has an ErrorContext.
|
||||
func UnwrapFileErrorsWithErrorContext(err error) []FileError {
|
||||
var errs []FileError
|
||||
for err != nil {
|
||||
if v, ok := err.(FileError); ok && v.ErrorContext() != nil {
|
||||
errs = append(errs, v)
|
||||
}
|
||||
err = errors.Unwrap(err)
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
func extractOffsetAndType(e error) (int, string) {
|
||||
e = errors.Cause(e)
|
||||
switch v := e.(type) {
|
||||
case *json.UnmarshalTypeError:
|
||||
return int(v.Offset), "json"
|
||||
@@ -129,3 +363,32 @@ func extractOffsetAndType(e error) (int, string) {
|
||||
return -1, ""
|
||||
}
|
||||
}
|
||||
|
||||
func exctractLineNumberAndColumnNumber(e error) (int, int) {
|
||||
switch v := e.(type) {
|
||||
case *parse.Error:
|
||||
return v.Line, v.Column
|
||||
case *toml.DecodeError:
|
||||
return v.Position()
|
||||
|
||||
}
|
||||
|
||||
return -1, -1
|
||||
}
|
||||
|
||||
func extractPosition(e error) (pos text.Position) {
|
||||
switch v := e.(type) {
|
||||
case godartsass.SassError:
|
||||
span := v.Span
|
||||
start := span.Start
|
||||
filename, _ := paths.UrlToFilename(span.Url)
|
||||
pos.Filename = filename
|
||||
pos.Offset = start.Offset
|
||||
pos.ColumnNumber = start.Column
|
||||
case libsasserrors.Error:
|
||||
pos.Filename = v.File
|
||||
pos.LineNumber = v.Line
|
||||
pos.ColumnNumber = v.Column
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2018 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2022 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -14,14 +14,44 @@
|
||||
package herrors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"errors"
|
||||
|
||||
"github.com/gohugoio/hugo/common/text"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
func TestToLineNumberError(t *testing.T) {
|
||||
func TestNewFileError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
c := qt.New(t)
|
||||
|
||||
fe := NewFileErrorFromName(errors.New("bar"), "foo.html")
|
||||
c.Assert(fe.Error(), qt.Equals, `"foo.html:1:1": bar`)
|
||||
|
||||
lines := ""
|
||||
for i := 1; i <= 100; i++ {
|
||||
lines += fmt.Sprintf("line %d\n", i)
|
||||
}
|
||||
|
||||
fe.UpdatePosition(text.Position{LineNumber: 32, ColumnNumber: 2})
|
||||
c.Assert(fe.Error(), qt.Equals, `"foo.html:32:2": bar`)
|
||||
fe.UpdatePosition(text.Position{LineNumber: 0, ColumnNumber: 0, Offset: 212})
|
||||
fe.UpdateContent(strings.NewReader(lines), nil)
|
||||
c.Assert(fe.Error(), qt.Equals, `"foo.html:32:0": bar`)
|
||||
errorContext := fe.ErrorContext()
|
||||
c.Assert(errorContext, qt.IsNotNil)
|
||||
c.Assert(errorContext.Lines, qt.DeepEquals, []string{"line 30", "line 31", "line 32", "line 33", "line 34"})
|
||||
c.Assert(errorContext.LinesPos, qt.Equals, 2)
|
||||
c.Assert(errorContext.ChromaLexer, qt.Equals, "go-html-template")
|
||||
|
||||
}
|
||||
|
||||
func TestNewFileErrorExtractFromMessage(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
c := qt.New(t)
|
||||
@@ -37,18 +67,16 @@ func TestToLineNumberError(t *testing.T) {
|
||||
{errors.New("parse failed: template: _default/bundle-resource-meta.html:11: unexpected in operand"), 0, 11, 1},
|
||||
{errors.New(`failed:: template: _default/bundle-resource-meta.html:2:7: executing "main" at <.Titles>`), 0, 2, 7},
|
||||
{errors.New(`failed to load translations: (6, 7): was expecting token =, but got "g" instead`), 0, 6, 7},
|
||||
{errors.New(`execute of template failed: template: index.html:2:5: executing "index.html" at <partial "foo.html" .>: error calling partial: "/layouts/partials/foo.html:3:6": execute of template failed: template: partials/foo.html:3:6: executing "partials/foo.html" at <.ThisDoesNotExist>: can't evaluate field ThisDoesNotExist in type *hugolib.pageStat`), 0, 2, 5},
|
||||
} {
|
||||
|
||||
got := ToFileError("template", test.in)
|
||||
got := NewFileErrorFromName(test.in, "test.txt")
|
||||
|
||||
errMsg := qt.Commentf("[%d][%T]", i, got)
|
||||
le, ok := got.(FileError)
|
||||
c.Assert(ok, qt.Equals, true)
|
||||
|
||||
c.Assert(ok, qt.Equals, true, errMsg)
|
||||
pos := le.Position()
|
||||
pos := got.Position()
|
||||
c.Assert(pos.LineNumber, qt.Equals, test.lineNumber, errMsg)
|
||||
c.Assert(pos.ColumnNumber, qt.Equals, test.columnNumber, errMsg)
|
||||
c.Assert(errors.Cause(got), qt.Not(qt.IsNil))
|
||||
c.Assert(errors.Unwrap(got), qt.Not(qt.IsNil))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,36 +16,22 @@ package herrors
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
)
|
||||
|
||||
var lineNumberExtractors = []lineNumberExtractor{
|
||||
// Template/shortcode parse errors
|
||||
newLineNumberErrHandlerFromRegexp(".*:(\\d+):(\\d*):"),
|
||||
newLineNumberErrHandlerFromRegexp(".*:(\\d+):"),
|
||||
newLineNumberErrHandlerFromRegexp(`:(\d+):(\d*):`),
|
||||
newLineNumberErrHandlerFromRegexp(`:(\d+):`),
|
||||
|
||||
// TOML parse errors
|
||||
tomlLineNumberExtractor,
|
||||
// YAML parse errors
|
||||
newLineNumberErrHandlerFromRegexp("line (\\d+):"),
|
||||
newLineNumberErrHandlerFromRegexp(`line (\d+):`),
|
||||
|
||||
// i18n bundle errors
|
||||
newLineNumberErrHandlerFromRegexp("\\((\\d+),\\s(\\d*)"),
|
||||
newLineNumberErrHandlerFromRegexp(`\((\d+),\s(\d*)`),
|
||||
}
|
||||
|
||||
type lineNumberExtractor func(e error) (int, int)
|
||||
|
||||
var tomlLineNumberExtractor = func(e error) (int, int) {
|
||||
e = errors.Cause(e)
|
||||
if terr, ok := e.(*toml.DecodeError); ok {
|
||||
return terr.Position()
|
||||
}
|
||||
return -1, -1
|
||||
}
|
||||
|
||||
func newLineNumberErrHandlerFromRegexp(expression string) lineNumberExtractor {
|
||||
re := regexp.MustCompile(expression)
|
||||
return extractLineNo(re)
|
||||
@@ -72,6 +58,6 @@ func extractLineNo(re *regexp.Regexp) lineNumberExtractor {
|
||||
return lno, col
|
||||
}
|
||||
|
||||
return -1, col
|
||||
return 0, col
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,9 @@ import (
|
||||
"context"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gohugoio/hugo/common/htime"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
)
|
||||
|
||||
@@ -168,6 +170,44 @@ func GetMethodIndexByName(tp reflect.Type, name string) int {
|
||||
return m.Index
|
||||
}
|
||||
|
||||
var (
|
||||
timeType = reflect.TypeOf((*time.Time)(nil)).Elem()
|
||||
asTimeProviderType = reflect.TypeOf((*htime.AsTimeProvider)(nil)).Elem()
|
||||
)
|
||||
|
||||
// IsTime returns whether tp is a time.Time type or if it can be converted into one
|
||||
// in ToTime.
|
||||
func IsTime(tp reflect.Type) bool {
|
||||
if tp == timeType {
|
||||
return true
|
||||
}
|
||||
|
||||
if tp.Implements(asTimeProviderType) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AsTime returns v as a time.Time if possible.
|
||||
// The given location is only used if the value implements AsTimeProvider (e.g. go-toml local).
|
||||
// A zero Time and false is returned if this isn't possible.
|
||||
// Note that this function does not accept string dates.
|
||||
func AsTime(v reflect.Value, loc *time.Location) (time.Time, bool) {
|
||||
if v.Kind() == reflect.Interface {
|
||||
return AsTime(v.Elem(), loc)
|
||||
}
|
||||
|
||||
if v.Type() == timeType {
|
||||
return v.Interface().(time.Time), true
|
||||
}
|
||||
|
||||
if v.Type().Implements(asTimeProviderType) {
|
||||
return v.Interface().(htime.AsTimeProvider).AsTime(loc), true
|
||||
}
|
||||
|
||||
return time.Time{}, false
|
||||
}
|
||||
|
||||
// Based on: https://github.com/golang/go/blob/178a2c42254166cffed1b25fb1d3c7a5727cada6/src/text/template/exec.go#L931
|
||||
func indirectInterface(v reflect.Value) reflect.Value {
|
||||
if v.Kind() != reflect.Interface {
|
||||
|
||||
+20
-5
@@ -17,10 +17,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bep/clock"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
toml "github.com/pelletier/go-toml/v2"
|
||||
|
||||
"github.com/gohugoio/locales"
|
||||
)
|
||||
|
||||
@@ -74,6 +73,8 @@ var (
|
||||
"November",
|
||||
"December",
|
||||
}
|
||||
|
||||
Clock = clock.System()
|
||||
)
|
||||
|
||||
func NewTimeFormatter(ltr locales.Translator) TimeFormatter {
|
||||
@@ -136,15 +137,29 @@ func (f TimeFormatter) Format(t time.Time, layout string) string {
|
||||
|
||||
func ToTimeInDefaultLocationE(i any, location *time.Location) (tim time.Time, err error) {
|
||||
switch vv := i.(type) {
|
||||
case toml.LocalDate:
|
||||
return vv.AsTime(location), nil
|
||||
case toml.LocalDateTime:
|
||||
case AsTimeProvider:
|
||||
return vv.AsTime(location), nil
|
||||
// issue #8895
|
||||
// datetimes parsed by `go-toml` have empty zone name
|
||||
// convert back them into string and use `cast`
|
||||
// TODO(bep) add tests, make sure we really need this.
|
||||
case time.Time:
|
||||
i = vv.Format(time.RFC3339)
|
||||
}
|
||||
return cast.ToTimeInDefaultLocationE(i, location)
|
||||
}
|
||||
|
||||
// Now returns time.Now() or time value based on the `clock` flag.
|
||||
// Use this function to fake time inside hugo.
|
||||
func Now() time.Time {
|
||||
return Clock.Now()
|
||||
}
|
||||
|
||||
func Since(t time.Time) time.Duration {
|
||||
return Clock.Since(t)
|
||||
}
|
||||
|
||||
// AsTimeProvider is implemented by go-toml's LocalDate and LocalDateTime.
|
||||
type AsTimeProvider interface {
|
||||
AsTime(zone *time.Location) time.Time
|
||||
}
|
||||
|
||||
@@ -14,13 +14,12 @@
|
||||
package hugio
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
@@ -60,7 +59,7 @@ func CopyDir(fs afero.Fs, from, to string, shouldCopy func(filename string) bool
|
||||
}
|
||||
|
||||
if !fi.IsDir() {
|
||||
return errors.Errorf("%q is not a directory", from)
|
||||
return fmt.Errorf("%q is not a directory", from)
|
||||
}
|
||||
|
||||
err = fs.MkdirAll(to, 0777) // before umask
|
||||
|
||||
@@ -38,6 +38,8 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
// buildDate allows vendor-specified build date when .git/ is unavailable.
|
||||
buildDate string
|
||||
// vendorInfo contains vendor notes about the current build.
|
||||
vendorInfo string
|
||||
)
|
||||
@@ -53,6 +55,9 @@ type Info struct {
|
||||
// It can be any string, but it will be all lower case.
|
||||
Environment string
|
||||
|
||||
// version of go that the Hugo binary was built with
|
||||
GoVersion string
|
||||
|
||||
deps []*Dependency
|
||||
}
|
||||
|
||||
@@ -87,12 +92,14 @@ func NewInfo(environment string, deps []*Dependency) Info {
|
||||
var (
|
||||
commitHash string
|
||||
buildDate string
|
||||
goVersion string
|
||||
)
|
||||
|
||||
bi := getBuildInfo()
|
||||
if bi != nil {
|
||||
commitHash = bi.Revision
|
||||
buildDate = bi.RevisionTime
|
||||
goVersion = bi.GoVersion
|
||||
}
|
||||
|
||||
return Info{
|
||||
@@ -100,6 +107,7 @@ func NewInfo(environment string, deps []*Dependency) Info {
|
||||
BuildDate: buildDate,
|
||||
Environment: environment,
|
||||
deps: deps,
|
||||
GoVersion: goVersion,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ func TestHugoInfo(t *testing.T) {
|
||||
if bi != nil {
|
||||
c.Assert(hugoInfo.CommitHash, qt.Equals, bi.Revision)
|
||||
c.Assert(hugoInfo.BuildDate, qt.Equals, bi.RevisionTime)
|
||||
c.Assert(hugoInfo.GoVersion, qt.Equals, bi.GoVersion)
|
||||
}
|
||||
c.Assert(hugoInfo.Environment, qt.Equals, "production")
|
||||
c.Assert(string(hugoInfo.Generator()), qt.Contains, fmt.Sprintf("Hugo %s", hugoInfo.Version()))
|
||||
|
||||
+87
-56
@@ -16,7 +16,9 @@ package hugo
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/compare"
|
||||
@@ -25,8 +27,9 @@ import (
|
||||
|
||||
// Version represents the Hugo build version.
|
||||
type Version struct {
|
||||
// Major and minor version.
|
||||
Number float32
|
||||
Major int
|
||||
|
||||
Minor int
|
||||
|
||||
// Increment this for bug releases
|
||||
PatchLevel int
|
||||
@@ -42,7 +45,7 @@ var (
|
||||
)
|
||||
|
||||
func (v Version) String() string {
|
||||
return version(v.Number, v.PatchLevel, v.Suffix)
|
||||
return version(v.Major, v.Minor, v.PatchLevel, v.Suffix)
|
||||
}
|
||||
|
||||
// Version returns the Hugo version.
|
||||
@@ -50,6 +53,11 @@ func (v Version) Version() VersionString {
|
||||
return VersionString(v.String())
|
||||
}
|
||||
|
||||
// Compare implements the compare.Comparer interface.
|
||||
func (h Version) Compare(other any) int {
|
||||
return compareVersions(h, other)
|
||||
}
|
||||
|
||||
// VersionString represents a Hugo version string.
|
||||
type VersionString string
|
||||
|
||||
@@ -60,7 +68,7 @@ func (h VersionString) String() string {
|
||||
// Compare implements the compare.Comparer interface.
|
||||
func (h VersionString) Compare(other any) int {
|
||||
v := MustParseVersion(h.String())
|
||||
return compareVersionsWithSuffix(v.Number, v.PatchLevel, v.Suffix, other)
|
||||
return compareVersions(v, other)
|
||||
}
|
||||
|
||||
// Eq implements the compare.Eqer interface.
|
||||
@@ -84,10 +92,7 @@ func ParseVersion(s string) (Version, error) {
|
||||
}
|
||||
}
|
||||
|
||||
v, p := parseVersion(s)
|
||||
|
||||
vv.Number = v
|
||||
vv.PatchLevel = p
|
||||
vv.Major, vv.Minor, vv.PatchLevel = parseVersion(s)
|
||||
|
||||
return vv, nil
|
||||
}
|
||||
@@ -110,18 +115,20 @@ func (v Version) ReleaseVersion() Version {
|
||||
|
||||
// Next returns the next Hugo release version.
|
||||
func (v Version) Next() Version {
|
||||
return Version{Number: v.Number + 0.01}
|
||||
return Version{Major: v.Major, Minor: v.Minor + 1}
|
||||
}
|
||||
|
||||
// Prev returns the previous Hugo release version.
|
||||
func (v Version) Prev() Version {
|
||||
return Version{Number: v.Number - 0.01}
|
||||
return Version{Major: v.Major, Minor: v.Minor - 1}
|
||||
}
|
||||
|
||||
// NextPatchLevel returns the next patch/bugfix Hugo version.
|
||||
// This will be a patch increment on the previous Hugo version.
|
||||
func (v Version) NextPatchLevel(level int) Version {
|
||||
return Version{Number: v.Number - 0.01, PatchLevel: level}
|
||||
prev := v.Prev()
|
||||
prev.PatchLevel = level
|
||||
return prev
|
||||
}
|
||||
|
||||
// BuildVersionString creates a version string. This is what you see when
|
||||
@@ -146,6 +153,10 @@ func BuildVersionString() string {
|
||||
osArch := bi.GoOS + "/" + bi.GoArch
|
||||
|
||||
date := bi.RevisionTime
|
||||
if date == "" {
|
||||
// Accept vendor-specified build date if .git/ is unavailable.
|
||||
date = buildDate
|
||||
}
|
||||
if date == "" {
|
||||
date = "unknown"
|
||||
}
|
||||
@@ -160,11 +171,11 @@ func BuildVersionString() string {
|
||||
return versionString
|
||||
}
|
||||
|
||||
func version(version float32, patchVersion int, suffix string) string {
|
||||
if patchVersion > 0 || version > 0.53 {
|
||||
return fmt.Sprintf("%.2f.%d%s", version, patchVersion, suffix)
|
||||
func version(major, minor, patch int, suffix string) string {
|
||||
if patch > 0 || minor > 53 {
|
||||
return fmt.Sprintf("%d.%d.%d%s", major, minor, patch, suffix)
|
||||
}
|
||||
return fmt.Sprintf("%.2f%s", version, suffix)
|
||||
return fmt.Sprintf("%d.%d%s", major, minor, suffix)
|
||||
}
|
||||
|
||||
// CompareVersion compares the given version string or number against the
|
||||
@@ -172,26 +183,41 @@ func version(version float32, patchVersion int, suffix string) string {
|
||||
// It returns -1 if the given version is less than, 0 if equal and 1 if greater than
|
||||
// the running version.
|
||||
func CompareVersion(version any) int {
|
||||
return compareVersionsWithSuffix(CurrentVersion.Number, CurrentVersion.PatchLevel, CurrentVersion.Suffix, version)
|
||||
return compareVersions(CurrentVersion, version)
|
||||
}
|
||||
|
||||
func compareVersions(inVersion float32, inPatchVersion int, in any) int {
|
||||
return compareVersionsWithSuffix(inVersion, inPatchVersion, "", in)
|
||||
}
|
||||
|
||||
func compareVersionsWithSuffix(inVersion float32, inPatchVersion int, suffix string, in any) int {
|
||||
func compareVersions(inVersion Version, in any) int {
|
||||
var c int
|
||||
switch d := in.(type) {
|
||||
case float64:
|
||||
c = compareFloatVersions(inVersion, float32(d))
|
||||
c = compareFloatWithVersion(d, inVersion)
|
||||
case float32:
|
||||
c = compareFloatVersions(inVersion, d)
|
||||
c = compareFloatWithVersion(float64(d), inVersion)
|
||||
case int:
|
||||
c = compareFloatVersions(inVersion, float32(d))
|
||||
c = compareFloatWithVersion(float64(d), inVersion)
|
||||
case int32:
|
||||
c = compareFloatVersions(inVersion, float32(d))
|
||||
c = compareFloatWithVersion(float64(d), inVersion)
|
||||
case int64:
|
||||
c = compareFloatVersions(inVersion, float32(d))
|
||||
c = compareFloatWithVersion(float64(d), inVersion)
|
||||
case Version:
|
||||
if d.Major == inVersion.Major && d.Minor == inVersion.Minor && d.PatchLevel == inVersion.PatchLevel {
|
||||
return strings.Compare(inVersion.Suffix, d.Suffix)
|
||||
}
|
||||
if d.Major > inVersion.Major {
|
||||
return 1
|
||||
} else if d.Major < inVersion.Major {
|
||||
return -1
|
||||
}
|
||||
if d.Minor > inVersion.Minor {
|
||||
return 1
|
||||
} else if d.Minor < inVersion.Minor {
|
||||
return -1
|
||||
}
|
||||
if d.PatchLevel > inVersion.PatchLevel {
|
||||
return 1
|
||||
} else if d.PatchLevel < inVersion.PatchLevel {
|
||||
return -1
|
||||
}
|
||||
default:
|
||||
s, err := cast.ToStringE(in)
|
||||
if err != nil {
|
||||
@@ -202,50 +228,55 @@ func compareVersionsWithSuffix(inVersion float32, inPatchVersion int, suffix str
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
return inVersion.Compare(v)
|
||||
|
||||
if v.Number == inVersion && v.PatchLevel == inPatchVersion {
|
||||
return strings.Compare(suffix, v.Suffix)
|
||||
}
|
||||
|
||||
if v.Number < inVersion || (v.Number == inVersion && v.PatchLevel < inPatchVersion) {
|
||||
return -1
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
if c == 0 && suffix != "" {
|
||||
return 1
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func parseVersion(s string) (float32, int) {
|
||||
var (
|
||||
v float32
|
||||
p int
|
||||
)
|
||||
|
||||
if strings.Count(s, ".") == 2 {
|
||||
li := strings.LastIndex(s, ".")
|
||||
p = cast.ToInt(s[li+1:])
|
||||
s = s[:li]
|
||||
func parseVersion(s string) (int, int, int) {
|
||||
var major, minor, patch int
|
||||
parts := strings.Split(s, ".")
|
||||
if len(parts) > 0 {
|
||||
major, _ = strconv.Atoi(parts[0])
|
||||
}
|
||||
if len(parts) > 1 {
|
||||
minor, _ = strconv.Atoi(parts[1])
|
||||
}
|
||||
if len(parts) > 2 {
|
||||
patch, _ = strconv.Atoi(parts[2])
|
||||
}
|
||||
|
||||
v = float32(cast.ToFloat64(s))
|
||||
|
||||
return v, p
|
||||
return major, minor, patch
|
||||
}
|
||||
|
||||
func compareFloatVersions(version float32, v float32) int {
|
||||
if v == version {
|
||||
// compareFloatWithVersion compares v1 with v2.
|
||||
// It returns -1 if v1 is less than v2, 0 if v1 is equal to v2 and 1 if v1 is greater than v2.
|
||||
func compareFloatWithVersion(v1 float64, v2 Version) int {
|
||||
mf, minf := math.Modf(v1)
|
||||
v1maj := int(mf)
|
||||
v1min := int(minf * 100)
|
||||
|
||||
if v2.Major == v1maj && v2.Minor == v1min {
|
||||
return 0
|
||||
}
|
||||
if v < version {
|
||||
|
||||
if v1maj > v2.Major {
|
||||
return 1
|
||||
|
||||
}
|
||||
|
||||
if v1maj < v2.Major {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
|
||||
if v1min > v2.Minor {
|
||||
return 1
|
||||
}
|
||||
|
||||
return -1
|
||||
|
||||
}
|
||||
|
||||
func GoMinorVersion() int {
|
||||
|
||||
@@ -16,7 +16,8 @@ package hugo
|
||||
// CurrentVersion represents the current build version.
|
||||
// This should be the only one.
|
||||
var CurrentVersion = Version{
|
||||
Number: 0.97,
|
||||
PatchLevel: 2,
|
||||
Suffix: "",
|
||||
Major: 0,
|
||||
Minor: 102,
|
||||
PatchLevel: 0,
|
||||
Suffix: "-DEV",
|
||||
}
|
||||
|
||||
+24
-25
@@ -22,10 +22,10 @@ import (
|
||||
func TestHugoVersion(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
c.Assert(version(0.15, 0, "-DEV"), qt.Equals, "0.15-DEV")
|
||||
c.Assert(version(0.15, 2, "-DEV"), qt.Equals, "0.15.2-DEV")
|
||||
c.Assert(version(0, 15, 0, "-DEV"), qt.Equals, "0.15-DEV")
|
||||
c.Assert(version(0, 15, 2, "-DEV"), qt.Equals, "0.15.2-DEV")
|
||||
|
||||
v := Version{Number: 0.21, PatchLevel: 0, Suffix: "-DEV"}
|
||||
v := Version{Minor: 21, Suffix: "-DEV"}
|
||||
|
||||
c.Assert(v.ReleaseVersion().String(), qt.Equals, "0.21")
|
||||
c.Assert(v.String(), qt.Equals, "0.21-DEV")
|
||||
@@ -39,37 +39,36 @@ func TestHugoVersion(t *testing.T) {
|
||||
|
||||
// We started to use full semver versions even for main
|
||||
// releases in v0.54.0
|
||||
v = Version{Number: 0.53, PatchLevel: 0}
|
||||
v = Version{Minor: 53, PatchLevel: 0}
|
||||
c.Assert(v.String(), qt.Equals, "0.53")
|
||||
c.Assert(v.Next().String(), qt.Equals, "0.54.0")
|
||||
c.Assert(v.Next().Next().String(), qt.Equals, "0.55.0")
|
||||
v = Version{Number: 0.54, PatchLevel: 0, Suffix: "-DEV"}
|
||||
v = Version{Minor: 54, PatchLevel: 0, Suffix: "-DEV"}
|
||||
c.Assert(v.String(), qt.Equals, "0.54.0-DEV")
|
||||
}
|
||||
|
||||
func TestCompareVersions(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
c.Assert(compareVersions(0.20, 0, 0.20), qt.Equals, 0)
|
||||
c.Assert(compareVersions(0.20, 0, float32(0.20)), qt.Equals, 0)
|
||||
c.Assert(compareVersions(0.20, 0, float64(0.20)), qt.Equals, 0)
|
||||
c.Assert(compareVersions(0.19, 1, 0.20), qt.Equals, 1)
|
||||
c.Assert(compareVersions(0.19, 3, "0.20.2"), qt.Equals, 1)
|
||||
c.Assert(compareVersions(0.19, 1, 0.01), qt.Equals, -1)
|
||||
c.Assert(compareVersions(0, 1, 3), qt.Equals, 1)
|
||||
c.Assert(compareVersions(0, 1, int32(3)), qt.Equals, 1)
|
||||
c.Assert(compareVersions(0, 1, int64(3)), qt.Equals, 1)
|
||||
c.Assert(compareVersions(0.20, 0, "0.20"), qt.Equals, 0)
|
||||
c.Assert(compareVersions(0.20, 1, "0.20.1"), qt.Equals, 0)
|
||||
c.Assert(compareVersions(0.20, 1, "0.20"), qt.Equals, -1)
|
||||
c.Assert(compareVersions(0.20, 0, "0.20.1"), qt.Equals, 1)
|
||||
c.Assert(compareVersions(0.20, 1, "0.20.2"), qt.Equals, 1)
|
||||
c.Assert(compareVersions(0.21, 1, "0.22.1"), qt.Equals, 1)
|
||||
c.Assert(compareVersions(0.22, 0, "0.22-DEV"), qt.Equals, -1)
|
||||
c.Assert(compareVersions(0.22, 0, "0.22.1-DEV"), qt.Equals, 1)
|
||||
c.Assert(compareVersionsWithSuffix(0.22, 0, "-DEV", "0.22"), qt.Equals, 1)
|
||||
c.Assert(compareVersionsWithSuffix(0.22, 1, "-DEV", "0.22"), qt.Equals, -1)
|
||||
c.Assert(compareVersionsWithSuffix(0.22, 1, "-DEV", "0.22.1-DEV"), qt.Equals, 0)
|
||||
c.Assert(compareVersions(MustParseVersion("0.20.0"), 0.20), qt.Equals, 0)
|
||||
c.Assert(compareVersions(MustParseVersion("0.20.0"), float32(0.20)), qt.Equals, 0)
|
||||
c.Assert(compareVersions(MustParseVersion("0.20.0"), float64(0.20)), qt.Equals, 0)
|
||||
c.Assert(compareVersions(MustParseVersion("0.19.1"), 0.20), qt.Equals, 1)
|
||||
c.Assert(compareVersions(MustParseVersion("0.19.3"), "0.20.2"), qt.Equals, 1)
|
||||
c.Assert(compareVersions(MustParseVersion("0.1"), 3), qt.Equals, 1)
|
||||
c.Assert(compareVersions(MustParseVersion("0.1"), int32(3)), qt.Equals, 1)
|
||||
c.Assert(compareVersions(MustParseVersion("0.1"), int64(3)), qt.Equals, 1)
|
||||
c.Assert(compareVersions(MustParseVersion("0.20"), "0.20"), qt.Equals, 0)
|
||||
c.Assert(compareVersions(MustParseVersion("0.20.1"), "0.20.1"), qt.Equals, 0)
|
||||
c.Assert(compareVersions(MustParseVersion("0.20.1"), "0.20"), qt.Equals, -1)
|
||||
c.Assert(compareVersions(MustParseVersion("0.20.0"), "0.20.1"), qt.Equals, 1)
|
||||
c.Assert(compareVersions(MustParseVersion("0.20.1"), "0.20.2"), qt.Equals, 1)
|
||||
c.Assert(compareVersions(MustParseVersion("0.21.1"), "0.22.1"), qt.Equals, 1)
|
||||
c.Assert(compareVersions(MustParseVersion("0.22.0"), "0.22-DEV"), qt.Equals, -1)
|
||||
c.Assert(compareVersions(MustParseVersion("0.22.0"), "0.22.1-DEV"), qt.Equals, 1)
|
||||
c.Assert(compareVersions(MustParseVersion("0.22.0-DEV"), "0.22"), qt.Equals, 1)
|
||||
c.Assert(compareVersions(MustParseVersion("0.22.1-DEV"), "0.22"), qt.Equals, -1)
|
||||
c.Assert(compareVersions(MustParseVersion("0.22.1-DEV"), "0.22.1-DEV"), qt.Equals, 0)
|
||||
}
|
||||
|
||||
func TestParseHugoVersion(t *testing.T) {
|
||||
|
||||
@@ -240,6 +240,11 @@ func NewBasicLoggerForWriter(t jww.Threshold, w io.Writer) Logger {
|
||||
return newLogger(t, jww.LevelError, w, ioutil.Discard, false)
|
||||
}
|
||||
|
||||
// RemoveANSIColours removes all ANSI colours from the given string.
|
||||
func RemoveANSIColours(s string) string {
|
||||
return ansiColorRe.ReplaceAllString(s, "")
|
||||
}
|
||||
|
||||
var (
|
||||
ansiColorRe = regexp.MustCompile("(?s)\\033\\[\\d*(;\\d*)*m")
|
||||
errorRe = regexp.MustCompile("^(ERROR|FATAL|WARN)")
|
||||
@@ -285,7 +290,7 @@ func InitGlobalLogger(stdoutThreshold, logThreshold jww.Threshold, outHandle, lo
|
||||
}
|
||||
|
||||
func getLogWriters(outHandle, logHandle io.Writer) (io.Writer, io.Writer) {
|
||||
isTerm := terminal.IsTerminal(os.Stdout)
|
||||
isTerm := terminal.PrintANSIColors(os.Stdout)
|
||||
if logHandle != ioutil.Discard && isTerm {
|
||||
// Remove any Ansi coloring from log output
|
||||
logHandle = ansiCleaner{w: logHandle}
|
||||
|
||||
@@ -46,3 +46,15 @@ func TestLoggerToWriterWithPrefix(t *testing.T) {
|
||||
|
||||
c.Assert(b.String(), qt.Equals, "myprefix: Hello Hugo!\n")
|
||||
}
|
||||
|
||||
func TestRemoveANSIColours(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
c.Assert(RemoveANSIColours(""), qt.Equals, "")
|
||||
c.Assert(RemoveANSIColours("\033[31m"), qt.Equals, "")
|
||||
c.Assert(RemoveANSIColours("\033[31mHello"), qt.Equals, "Hello")
|
||||
c.Assert(RemoveANSIColours("\033[31mHello\033[0m"), qt.Equals, "Hello")
|
||||
c.Assert(RemoveANSIColours("\033[31mHello\033[0m World"), qt.Equals, "Hello World")
|
||||
c.Assert(RemoveANSIColours("\033[31mHello\033[0m World\033[31m!"), qt.Equals, "Hello World!")
|
||||
c.Assert(RemoveANSIColours("\x1b[90m 5 |"), qt.Equals, " 5 |")
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -152,3 +153,29 @@ func Uglify(in string) string {
|
||||
// /section/name.html -> /section/name.html
|
||||
return path.Clean(in)
|
||||
}
|
||||
|
||||
// UrlToFilename converts the URL s to a filename.
|
||||
// If ParseRequestURI fails, the input is just converted to OS specific slashes and returned.
|
||||
func UrlToFilename(s string) (string, bool) {
|
||||
u, err := url.ParseRequestURI(s)
|
||||
|
||||
if err != nil {
|
||||
return filepath.FromSlash(s), false
|
||||
}
|
||||
|
||||
p := u.Path
|
||||
|
||||
if p == "" {
|
||||
p, _ = url.QueryUnescape(u.Opaque)
|
||||
return filepath.FromSlash(p), true
|
||||
}
|
||||
|
||||
p = filepath.FromSlash(p)
|
||||
|
||||
if u.Host != "" {
|
||||
// C:\data\file.txt
|
||||
p = strings.ToUpper(u.Host) + ":" + p
|
||||
}
|
||||
|
||||
return p, true
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2018 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2022 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -29,6 +29,15 @@ const (
|
||||
noticeColor = "\033[1;36m%s\033[0m"
|
||||
)
|
||||
|
||||
// PrintANSIColors returns false if NO_COLOR env variable is set,
|
||||
// else IsTerminal(f).
|
||||
func PrintANSIColors(f *os.File) bool {
|
||||
if os.Getenv("NO_COLOR") != "" {
|
||||
return false
|
||||
}
|
||||
return IsTerminal(f)
|
||||
}
|
||||
|
||||
// IsTerminal return true if the file descriptor is terminal and the TERM
|
||||
// environment variable isn't a dumb one.
|
||||
func IsTerminal(f *os.File) bool {
|
||||
|
||||
@@ -83,7 +83,7 @@ func createPositionStringFormatter(formatStr string) func(p Position) string {
|
||||
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
|
||||
if terminal.IsTerminal(os.Stdout) {
|
||||
if terminal.PrintANSIColors(os.Stdout) {
|
||||
return terminal.Notice(msg)
|
||||
}
|
||||
|
||||
|
||||
@@ -61,3 +61,18 @@ func Puts(s string) string {
|
||||
}
|
||||
return s + "\n"
|
||||
}
|
||||
|
||||
// VisitLinesAfter calls the given function for each line, including newlines, in the given string.
|
||||
func VisitLinesAfter(s string, fn func(line string)) {
|
||||
high := strings.IndexRune(s, '\n')
|
||||
for high != -1 {
|
||||
fn(s[:high+1])
|
||||
s = s[high+1:]
|
||||
|
||||
high = strings.IndexRune(s, '\n')
|
||||
}
|
||||
|
||||
if s != "" {
|
||||
fn(s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,3 +41,36 @@ func TestPuts(t *testing.T) {
|
||||
c.Assert(Puts("\nA\n"), qt.Equals, "\nA\n")
|
||||
c.Assert(Puts(""), qt.Equals, "")
|
||||
}
|
||||
|
||||
func TestVisitLinesAfter(t *testing.T) {
|
||||
const lines = `line 1
|
||||
line 2
|
||||
|
||||
line 3`
|
||||
|
||||
var collected []string
|
||||
|
||||
VisitLinesAfter(lines, func(s string) {
|
||||
collected = append(collected, s)
|
||||
})
|
||||
|
||||
c := qt.New(t)
|
||||
|
||||
c.Assert(collected, qt.DeepEquals, []string{"line 1\n", "line 2\n", "\n", "line 3"})
|
||||
|
||||
}
|
||||
|
||||
func BenchmarkVisitLinesAfter(b *testing.B) {
|
||||
const lines = `line 1
|
||||
line 2
|
||||
|
||||
line 3`
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
VisitLinesAfter(lines, func(s string) {
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,12 +17,15 @@ package compare
|
||||
// The semantics of equals is that the two value are interchangeable
|
||||
// in the Hugo templates.
|
||||
type Eqer interface {
|
||||
// Eq returns whether this value is equal to the other.
|
||||
// This is for internal use.
|
||||
Eq(other any) bool
|
||||
}
|
||||
|
||||
// ProbablyEqer is an equal check that may return false positives, but never
|
||||
// a false negative.
|
||||
type ProbablyEqer interface {
|
||||
// For internal use.
|
||||
ProbablyEq(other any) bool
|
||||
}
|
||||
|
||||
|
||||
@@ -14,12 +14,11 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
@@ -207,7 +206,7 @@ func DecodeServer(cfg Provider) (*Server, error) {
|
||||
// There are some tricky infinite loop situations when dealing
|
||||
// when the target does not have a trailing slash.
|
||||
// This can certainly be handled better, but not time for that now.
|
||||
return nil, errors.Errorf("unsupported redirect to value %q in server config; currently this must be either a remote destination or a local folder, e.g. \"/blog/\" or \"/blog/index.html\"", redir.To)
|
||||
return nil, fmt.Errorf("unsupported redirect to value %q in server config; currently this must be either a remote destination or a local folder, e.g. \"/blog/\" or \"/blog/index.html\"", redir.To)
|
||||
}
|
||||
s.Redirects[i] = redir
|
||||
}
|
||||
|
||||
+10
-4
@@ -14,14 +14,13 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
@@ -60,7 +59,14 @@ func FromConfigString(config, configType string) (Provider, error) {
|
||||
func FromFile(fs afero.Fs, filename string) (Provider, error) {
|
||||
m, err := loadConfigFromFile(fs, filename)
|
||||
if err != nil {
|
||||
return nil, herrors.WithFileContextForFileDefault(err, filename, fs)
|
||||
fe := herrors.UnwrapFileError(err)
|
||||
if fe != nil {
|
||||
pos := fe.Position()
|
||||
pos.Filename = filename
|
||||
fe.UpdatePosition(pos)
|
||||
return nil, err
|
||||
}
|
||||
return nil, herrors.NewFileErrorFromFile(err, filename, fs, nil)
|
||||
}
|
||||
return NewFrom(m), nil
|
||||
}
|
||||
@@ -132,7 +138,7 @@ func LoadConfigFromDir(sourceFs afero.Fs, configDir, environment string) (Provid
|
||||
if err != nil {
|
||||
// This will be used in error reporting, use the most specific value.
|
||||
dirnames = []string{path}
|
||||
return errors.Wrapf(err, "failed to unmarshl config for path %q", path)
|
||||
return fmt.Errorf("failed to unmarshl config for path %q: %w", path, err)
|
||||
}
|
||||
|
||||
var keyPath []string
|
||||
|
||||
+7
-7
@@ -27,7 +27,7 @@ import (
|
||||
"github.com/gohugoio/hugo/common/hexec"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"errors"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
|
||||
@@ -94,11 +94,11 @@ func NewContent(h *hugolib.HugoSites, kind, targetPath string) error {
|
||||
}
|
||||
|
||||
if ext == "" {
|
||||
return "", errors.Errorf("failed to resolve %q to a archetype template", targetPath)
|
||||
return "", fmt.Errorf("failed to resolve %q to a archetype template", targetPath)
|
||||
}
|
||||
|
||||
if !files.IsContentFile(b.targetPath) {
|
||||
return "", errors.Errorf("target path %q is not a known content format", b.targetPath)
|
||||
return "", fmt.Errorf("target path %q is not a known content format", b.targetPath)
|
||||
}
|
||||
|
||||
return b.buildFile()
|
||||
@@ -188,14 +188,14 @@ func (b *contentBuilder) buildDir() error {
|
||||
|
||||
in, err := meta.Open()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to open non-content file")
|
||||
return fmt.Errorf("failed to open non-content file: %w", err)
|
||||
}
|
||||
|
||||
targetFilename := filepath.Join(baseDir, b.targetPath, strings.TrimPrefix(filename, b.archetypeFilename))
|
||||
targetDir := filepath.Dir(targetFilename)
|
||||
|
||||
if err := b.sourceFs.MkdirAll(targetDir, 0o777); err != nil && !os.IsExist(err) {
|
||||
return errors.Wrapf(err, "failed to create target directory for %q", targetDir)
|
||||
return fmt.Errorf("failed to create target directory for %q: %w", targetDir, err)
|
||||
}
|
||||
|
||||
out, err := b.sourceFs.Create(targetFilename)
|
||||
@@ -329,7 +329,7 @@ func (b *contentBuilder) mapArcheTypeDir() error {
|
||||
w := hugofs.NewWalkway(walkCfg)
|
||||
|
||||
if err := w.Walk(); err != nil {
|
||||
return errors.Wrapf(err, "failed to walk archetype dir %q", b.archetypeFilename)
|
||||
return fmt.Errorf("failed to walk archetype dir %q: %w", b.archetypeFilename, err)
|
||||
}
|
||||
|
||||
b.dirMap = m
|
||||
@@ -374,7 +374,7 @@ func (b *contentBuilder) usesSiteVar(filename string) (bool, error) {
|
||||
}
|
||||
bb, err := afero.ReadFile(b.archeTypeFs, filename)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to open archetype file")
|
||||
return false, fmt.Errorf("failed to open archetype file: %w", err)
|
||||
}
|
||||
|
||||
return bytes.Contains(bb, []byte(".Site")) || bytes.Contains(bb, []byte("site.")), nil
|
||||
|
||||
+2
-1
@@ -34,11 +34,12 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"errors"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/gobwas/glob"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/afero"
|
||||
jww "github.com/spf13/jwalterweatherman"
|
||||
"golang.org/x/text/unicode/norm"
|
||||
|
||||
@@ -20,12 +20,13 @@ import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"errors"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
hglob "github.com/gohugoio/hugo/hugofs/glob"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const deploymentConfigKey = "deployment"
|
||||
@@ -126,7 +127,7 @@ func decodeConfig(cfg config.Provider) (deployConfig, error) {
|
||||
return dcfg, err
|
||||
}
|
||||
for _, tgt := range dcfg.Targets {
|
||||
if tgt == nil {
|
||||
if *tgt == (target{}) {
|
||||
return dcfg, errors.New("empty deployment target")
|
||||
}
|
||||
if err := tgt.parseIncludeExclude(); err != nil {
|
||||
@@ -135,7 +136,7 @@ func decodeConfig(cfg config.Provider) (deployConfig, error) {
|
||||
}
|
||||
var err error
|
||||
for _, m := range dcfg.Matchers {
|
||||
if m == nil {
|
||||
if *m == (matcher{}) {
|
||||
return dcfg, errors.New("empty deployment matcher")
|
||||
}
|
||||
m.re, err = regexp.Compile(m.Pattern)
|
||||
|
||||
Vendored
+6
-7
@@ -1,12 +1,11 @@
|
||||
package deps
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/gohugoio/hugo/cache/filecache"
|
||||
"github.com/gohugoio/hugo/common/hexec"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
@@ -186,11 +185,11 @@ func (d *Deps) SetTextTmpl(tmpl tpl.TemplateParseFinder) {
|
||||
func (d *Deps) LoadResources() error {
|
||||
// Note that the translations need to be loaded before the templates.
|
||||
if err := d.translationProvider.Update(d); err != nil {
|
||||
return errors.Wrap(err, "loading translations")
|
||||
return fmt.Errorf("loading translations: %w", err)
|
||||
}
|
||||
|
||||
if err := d.templateProvider.Update(d); err != nil {
|
||||
return errors.Wrap(err, "loading templates")
|
||||
return fmt.Errorf("loading templates: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -236,18 +235,18 @@ func New(cfg DepsCfg) (*Deps, error) {
|
||||
|
||||
securityConfig, err := security.DecodeConfig(cfg.Cfg)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(err, "failed to create security config from configuration")
|
||||
return nil, fmt.Errorf("failed to create security config from configuration: %w", err)
|
||||
}
|
||||
execHelper := hexec.New(securityConfig)
|
||||
|
||||
ps, err := helpers.NewPathSpec(fs, cfg.Language, logger)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "create PathSpec")
|
||||
return nil, fmt.Errorf("create PathSpec: %w", err)
|
||||
}
|
||||
|
||||
fileCaches, err := filecache.NewCaches(ps)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(err, "failed to create file caches from configuration")
|
||||
return nil, fmt.Errorf("failed to create file caches from configuration: %w", err)
|
||||
}
|
||||
|
||||
errorHandler := &globalErrHandler{}
|
||||
|
||||
@@ -163,7 +163,6 @@
|
||||
"Milli",
|
||||
"Mittwoch",
|
||||
"mkdir",
|
||||
"mmark",
|
||||
"modh",
|
||||
"monokai",
|
||||
"Morling",
|
||||
|
||||
+5
-2
@@ -5,12 +5,15 @@ on: # rebuild any PRs and main branch changes
|
||||
- "dependabot/**"
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
spellcheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: streetsidesoftware/cspell-action@3bc52c39528d0214a947ff7f8b5c23ad3273435b
|
||||
- uses: actions/checkout@v3
|
||||
- uses: streetsidesoftware/cspell-action@v2
|
||||
with:
|
||||
inline: warning
|
||||
strict: false
|
||||
|
||||
@@ -60,7 +60,7 @@ toc: true
|
||||
[content summaries]: /content-management/summaries/
|
||||
[content types]: /content-management/types/
|
||||
[Disqus]: https://disqus.com/
|
||||
[Dynamic menu]: /templates/menus/
|
||||
[Dynamic menu]: /templates/menu-templates/
|
||||
[Extremely fast]: https://github.com/bep/hugo-benchmark
|
||||
[front matter]: /content-management/front-matter/
|
||||
[functions]: /functions/
|
||||
@@ -73,13 +73,13 @@ toc: true
|
||||
[organization for your projects]: /getting-started/directory-structure/
|
||||
[pagevars]: /variables/page/
|
||||
[Permalink]: /content-management/urls/#permalinks
|
||||
[Powerful theming]: /themes/
|
||||
[Powerful theming]: /hugo-modules/theme-components/
|
||||
[Pretty URLs]: /content-management/urls/
|
||||
[RSS]: /templates/rss/
|
||||
[Shortcodes]: /content-management/shortcodes/
|
||||
[sort content]: /templates/
|
||||
[supported formats]: /content-management/formats/
|
||||
[Syntax highlighting]: /tools/syntax-highlighting/
|
||||
[Syntax highlighting]: /content-management/syntax-highlighting/
|
||||
[table of contents]: /content-management/toc/
|
||||
[taxonomies]: /content-management/taxonomies/
|
||||
[URLs]: /content-management/urls/
|
||||
|
||||
@@ -19,7 +19,7 @@ toc: true
|
||||
|
||||
Hugo is a general-purpose website framework. Technically speaking, Hugo is a [static site generator][]. Unlike systems that dynamically build a page with each visitor request, Hugo builds pages when you create or update your content. Since websites are viewed far more often than they are edited, Hugo is designed to provide an optimal viewing experience for your website's end users and an ideal writing experience for website authors.
|
||||
|
||||
Websites built with Hugo are extremely fast and secure. Hugo sites can be hosted anywhere, including [Netlify][], [Heroku][], [GoDaddy][], [DreamHost][], [GitHub Pages][], [GitLab Pages][], [Surge][], [Aerobatic][], [Firebase][], [Google Cloud Storage][], [Amazon S3][], [Rackspace][], [Azure][], and [CloudFront][] and work well with CDNs. Hugo sites run without the need for a database or dependencies on expensive runtimes like Ruby, Python, or PHP.
|
||||
Websites built with Hugo are extremely fast and secure. Hugo sites can be hosted anywhere, including [Netlify][], [Heroku][], [GoDaddy][], [DreamHost][], [GitHub Pages][], [GitLab Pages][], [Surge][], [Firebase][], [Google Cloud Storage][], [Amazon S3][], [Rackspace][], [Azure][], and [CloudFront][] and work well with CDNs. Hugo sites run without the need for a database or dependencies on expensive runtimes like Ruby, Python, or PHP.
|
||||
|
||||
We think of Hugo as the ideal website creation tool with nearly instant build times, able to rebuild whenever a change is made.
|
||||
|
||||
@@ -40,7 +40,6 @@ Hugo is for people who want to hand code their own website without worrying abou
|
||||
Hugo is for people building a blog, a company site, a portfolio site, documentation, a single landing page, or a website with thousands of pages.
|
||||
|
||||
[@spf13]: https://twitter.com/spf13
|
||||
[Aerobatic]: https://www.aerobatic.com/
|
||||
[Amazon S3]: https://aws.amazon.com/s3/
|
||||
[Azure]: https://docs.microsoft.com/en-us/azure/storage/blobs/storage-blob-static-website
|
||||
[CloudFront]: https://aws.amazon.com/cloudfront/ "Amazon CloudFront"
|
||||
|
||||
@@ -29,6 +29,7 @@ hugo [flags]
|
||||
-F, --buildFuture include content with publishdate in the future
|
||||
--cacheDir string filesystem path to cache directory. Defaults: $TMPDIR/hugo_cache/
|
||||
--cleanDestinationDir remove files from destination not found in static directories
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
@@ -46,6 +47,7 @@ hugo [flags]
|
||||
--log enable Logging
|
||||
--logFile string log File path (if set, logging enabled automatically)
|
||||
--minify minify any supported output format (HTML, XML etc.)
|
||||
--noBuildLock don't create .hugo_build.lock file
|
||||
--noChmod don't sync permission mode of files
|
||||
--noTimes don't sync modification time of files
|
||||
--panicOnWarning panic on first WARNING log
|
||||
|
||||
@@ -22,6 +22,7 @@ See each sub-command's help for details on how to use the generated script.
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -45,6 +45,7 @@ hugo completion bash
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -36,6 +36,7 @@ hugo completion fish [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -33,6 +33,7 @@ hugo completion powershell [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -43,6 +43,7 @@ hugo completion zsh [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -24,6 +24,7 @@ hugo config [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -20,6 +20,7 @@ hugo config mounts [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -24,6 +24,7 @@ See convert's subcommands toJSON, toTOML and toYAML for more information.
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -25,6 +25,7 @@ hugo convert toJSON [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -25,6 +25,7 @@ hugo convert toTOML [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -25,6 +25,7 @@ hugo convert toYAML [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -34,6 +34,7 @@ hugo deploy [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -27,6 +27,7 @@ hugo env [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -16,6 +16,7 @@ A collection of several useful generators.
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -29,6 +29,7 @@ hugo gen chromastyles [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -31,6 +31,7 @@ hugo gen doc [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -27,6 +27,7 @@ hugo gen man [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -22,6 +22,7 @@ Import requires a subcommand, e.g. `hugo import jekyll jekyll_root_path target_p
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -27,6 +27,7 @@ hugo import jekyll [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -22,6 +22,7 @@ List requires a subcommand, e.g. `hugo list drafts`.
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -24,6 +24,7 @@ hugo list all [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -24,6 +24,7 @@ hugo list drafts [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -24,6 +24,7 @@ hugo list expired [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -24,6 +24,7 @@ hugo list future [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -42,6 +42,7 @@ See https://gohugo.io/hugo-modules/ for more information.
|
||||
--ignoreCache ignores the cache directory
|
||||
-l, --layoutDir string filesystem path to layout directory
|
||||
--minify minify any supported output format (HTML, XML etc.)
|
||||
--noBuildLock don't create .hugo_build.lock file
|
||||
--noChmod don't sync permission mode of files
|
||||
--noTimes don't sync modification time of files
|
||||
--panicOnWarning panic on first WARNING log
|
||||
@@ -59,6 +60,7 @@ See https://gohugo.io/hugo-modules/ for more information.
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -32,6 +32,7 @@ hugo mod clean [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -50,6 +50,7 @@ hugo mod get [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -26,6 +26,7 @@ hugo mod graph [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -31,6 +31,7 @@ hugo mod init [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -24,6 +24,7 @@ hugo mod npm [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -34,6 +34,7 @@ hugo mod npm pack [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -20,6 +20,7 @@ hugo mod tidy [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -27,6 +27,7 @@ hugo mod vendor [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -26,6 +26,7 @@ hugo mod verify [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -43,6 +43,7 @@ hugo new [path] [flags]
|
||||
-k, --kind string content type to create
|
||||
-l, --layoutDir string filesystem path to layout directory
|
||||
--minify minify any supported output format (HTML, XML etc.)
|
||||
--noBuildLock don't create .hugo_build.lock file
|
||||
--noChmod don't sync permission mode of files
|
||||
--noTimes don't sync modification time of files
|
||||
--panicOnWarning panic on first WARNING log
|
||||
@@ -60,6 +61,7 @@ hugo new [path] [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -28,6 +28,7 @@ hugo new site [path] [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -27,6 +27,7 @@ hugo new theme [name] [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -54,6 +54,7 @@ hugo server [flags]
|
||||
--memstats string log memory usage to this file
|
||||
--minify minify any supported output format (HTML, XML etc.)
|
||||
--navigateToChanged navigate to changed content file on live browser reload
|
||||
--noBuildLock don't create .hugo_build.lock file
|
||||
--noChmod don't sync permission mode of files
|
||||
--noHTTPCache prevent HTTP caching
|
||||
--noTimes don't sync modification time of files
|
||||
@@ -76,6 +77,7 @@ hugo server [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -24,6 +24,7 @@ hugo version [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
|
||||
@@ -95,18 +95,15 @@ Website needs to showcase a few of the hundred "testimonials" available as conte
|
||||
|
||||
To avoid setting the build options on every testimonials, one can use [`cascade`]({{< relref "/content-management/front-matter#front-matter-cascade" >}}) on the testimonial section's content file.
|
||||
|
||||
```yaml
|
||||
#content/testimonials/_index.md
|
||||
{{< code-toggle >}}
|
||||
title: Testimonials
|
||||
# section build options:
|
||||
_build:
|
||||
render: true
|
||||
# children build options with cascade
|
||||
cascade:
|
||||
_build:
|
||||
render: false
|
||||
list: true # default
|
||||
```
|
||||
{{< /code-toggle >}}
|
||||
|
||||
```go-html-template
|
||||
{{/* layouts/_defaults/testimonials.html */}}
|
||||
|
||||
@@ -64,7 +64,7 @@ These are some alternatives to Disqus:
|
||||
* [Utterances](https://utteranc.es/) (Open source, GitHub comments widget built on GitHub issues)
|
||||
|
||||
[configuration]: /getting-started/configuration/
|
||||
[disquspartial]: /templates/partials/#disqus
|
||||
[disquspartial]: /templates/internal/#disqus
|
||||
[disqussetup]: https://disqus.com/profile/signup/
|
||||
[forum]: https://discourse.gohugo.io
|
||||
[front matter]: /content-management/front-matter/
|
||||
|
||||
@@ -126,4 +126,3 @@ refLinksNotFoundURL
|
||||
[lists]: /templates/lists/
|
||||
[output formats]: /templates/output-formats/
|
||||
[shortcode]: /content-management/shortcodes/
|
||||
[bfext]: /content-management/formats/#blackfriday-extensions
|
||||
|
||||
@@ -5,7 +5,7 @@ description: Both HTML and Markdown are supported content formats.
|
||||
date: 2017-01-10
|
||||
publishdate: 2017-01-10
|
||||
categories: [content management]
|
||||
keywords: [markdown,asciidoc,mmark,pandoc,content format]
|
||||
keywords: [markdown,asciidoc,pandoc,content format]
|
||||
menu:
|
||||
docs:
|
||||
parent: "content-management"
|
||||
@@ -29,8 +29,6 @@ The current list of content formats in Hugo:
|
||||
| Name | Markup identifiers | Comment |
|
||||
| ------------- | ------------- |-------------|
|
||||
| Goldmark | md, markdown, goldmark |Note that you can set the default handler of `md` and `markdown` to something else, see [Configure Markup](/getting-started/configuration-markup/).{{< new-in "0.60.0" >}} |
|
||||
| Blackfriday | blackfriday |Blackfriday will eventually be deprecated.|
|
||||
|MMark|mmark|Mmark is deprecated and will be removed in a future release.|
|
||||
|Emacs Org-Mode|org|See [go-org](https://github.com/niklasfasching/go-org).|
|
||||
|AsciiDoc|asciidocext, adoc, ad|Needs [Asciidoctor][ascii] installed.|
|
||||
|RST|rst|Needs [RST](https://docutils.sourceforge.io/rst.html) installed.|
|
||||
@@ -129,9 +127,6 @@ Markdown syntax is simple enough to learn in a single sitting. The following are
|
||||
|
||||
[`emojify` function]: /functions/emojify/
|
||||
[ascii]: https://asciidoctor.org/
|
||||
[bfconfig]: /getting-started/configuration/#configuring-blackfriday-rendering
|
||||
[blackfriday]: https://github.com/russross/blackfriday
|
||||
[mmark]: https://github.com/miekg/mmark
|
||||
[config]: /getting-started/configuration/
|
||||
[developer tools]: /tools/
|
||||
[emojis]: https://www.webpagefx.com/tools/emoji-cheat-sheet/
|
||||
@@ -146,7 +141,6 @@ Markdown syntax is simple enough to learn in a single sitting. The following are
|
||||
[mdcheatsheet]: https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet
|
||||
[mdguide]: https://www.markdownguide.org/
|
||||
[mdtutorial]: https://www.markdowntutorial.com/
|
||||
[Miek Gieben's website]: https://miek.nl/2016/march/05/mmark-syntax-document/
|
||||
[org]: https://orgmode.org/
|
||||
[pandoc]: https://www.pandoc.org/
|
||||
[rest]: https://docutils.sourceforge.io/rst.html
|
||||
|
||||
@@ -92,7 +92,7 @@ keywords
|
||||
: the meta keywords for the content.
|
||||
|
||||
layout
|
||||
: the layout Hugo should select from the [lookup order][lookup] when rendering the content. If a `type` is not specified in the front matter, Hugo will look for the layout of the same name in the layout directory that corresponds with a content's section. See ["Defining a Content Type"][definetype]
|
||||
: the layout Hugo should select from the [lookup order][lookup] when rendering the content. If a `type` is not specified in the front matter, Hugo will look for the layout of the same name in the layout directory that corresponds with a content's section. See [Content Types][content type].
|
||||
|
||||
lastmod
|
||||
: the datetime at which the content was last modified.
|
||||
@@ -217,7 +217,7 @@ You can assign content-specific `weight` in the front matter of your content. Th
|
||||
|
||||
## Override Global Markdown Configuration
|
||||
|
||||
It's possible to set some options for Markdown rendering in a content's front matter as an override to the [BlackFriday rendering options set in your project configuration][config].
|
||||
It's possible to set some options for Markdown rendering in a content's front matter as an override to the [Rendering options set in your project configuration][config].
|
||||
|
||||
## Front Matter Format Specs
|
||||
|
||||
@@ -232,10 +232,9 @@ It's possible to set some options for Markdown rendering in a content's front ma
|
||||
[config]: /getting-started/configuration/ "Hugo documentation for site configuration"
|
||||
[content type]: /content-management/types/
|
||||
[contentorg]: /content-management/organization/
|
||||
[definetype]: /content-management/types/#defining-a-content-type "Learn how to specify a type and a layout in a content's front matter"
|
||||
[headless-bundle]: /content-management/page-bundles/#headless-bundle
|
||||
[json]: https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf "Specification for JSON, JavaScript Object Notation"
|
||||
[lists]: /templates/lists/#ordering-content "See how to order content in list pages; for example, templates that look to specific _index.md for content and front matter."
|
||||
[lists]: /templates/lists/#order-content "See how to order content in list pages; for example, templates that look to specific _index.md for content and front matter."
|
||||
[lookup]: /templates/lookup-order/ "Hugo traverses your templates in a specific order when rendering content to allow for DRYer templating."
|
||||
[ordering]: /templates/lists/ "Hugo provides multiple ways to sort and order your content in list templates"
|
||||
[outputs]: /templates/output-formats/ "With the release of v22, you can output your content to any text format using Hugo's familiar templating"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user