Compare commits

..

539 Commits

Author SHA1 Message Date
Bjørn Erik Pedersen 10ddc2ef92 Improve error messages for PostCSS etc.
Fixes #9730
2023-07-17 19:42:13 +02:00
Bjørn Erik Pedersen c406fd3a0e Fix setting config from env with complex (e.g. YAML) strings
So you can do

```
HUGO_OUTPUTS="home: [rss]"  hugo
```

And similar.

Fixes #11249
2023-07-16 18:08:23 +02:00
David Karlsson 286821e360 Fix for data mounts in sub folders
Before this change, data files from Hugo modules were always mounted at the
root of the `data` directory. The File and FileMetaInfo structs for modules
are different from 'native' data directories.

This changes how the keyParts for data files are generated so that data
from modules or native directories are treated the same.
2023-07-15 11:13:08 +02:00
hugoreleaser 79f15be5b0 releaser: Prepare repository for 0.116.0-DEV
[ci skip]
2023-07-13 16:23:50 +00:00
hugoreleaser 5c2e014a51 releaser: Bump versions for release of 0.115.3
[ci skip]
2023-07-13 16:11:34 +00:00
Bjørn Erik Pedersen cc44583cc3 Improve behavior of defaultContentLanguageInSubdir when only the default language is enabled
1 .Create sitemapindex in root of publishDir (will contain link to sitemap.xml in defaultContentLanguage site)
2. Create index.html in root of publishDir (will redirect to defaultContentLanguage site)

Fixes #11229
2023-07-13 18:06:36 +02:00
Bjørn Erik Pedersen 4da672af88 Return error when .Render is invoked without arg
Fixes #11243
2023-07-13 18:06:36 +02:00
Joe Mooring f1886f8c37 js: Pass tsconfig.json to esBuild
Note that esBuild only inspects/honors certain fields.
See https://esbuild.github.io/content-types/#tsconfig-json.

Fixes #11232
2023-07-13 15:19:04 +02:00
Bjørn Erik Pedersen 5bec50838c tpl/collections: Fix WordCount (etc.) regression in Where, Sort, Delimit
Fixes #11234
2023-07-11 12:11:39 +02:00
Joe Mooring f650e4d751 config/allconfig: Update timeout description 2023-07-08 23:22:26 +02:00
Joe Mooring c934a45069 docs: Refresh docs.json 2023-07-08 23:20:11 +02:00
hugoreleaser 91b02091a5 releaser: Prepare repository for 0.116.0-DEV
[ci skip]
2023-07-08 17:19:03 +00:00
hugoreleaser 8966424e0e releaser: Bump versions for release of 0.115.2
[ci skip]
2023-07-08 17:06:56 +00:00
Joe Mooring 72510969a6 snap: Allow access to AWS, Azure, and GCS config/credentials
Fixes #11122
2023-07-08 09:43:04 -07:00
Joe Mooring 70c5e485bb snap: Update metadata and security.exec.osEnv
See #11217
2023-07-08 09:43:04 -07:00
Bjørn Erik Pedersen a78b17d7f1 Make imageConfig work with modules
Fixes #11205
2023-07-08 17:20:18 +02:00
Bjørn Erik Pedersen a481942532 Restore language.disabled config
Fixes #11219
2023-07-08 16:51:48 +02:00
Bjørn Erik Pedersen 0f921ace6f Fix hugo mod vendor for modules with hugo.toml
Fixes #11221
2023-07-08 16:08:46 +02:00
Bjørn Erik Pedersen 6019953769 Fix static content files multilingual root regression
Fixes #11223
2023-07-08 16:08:46 +02:00
Bjørn Erik Pedersen 92e86702ea Fix defaultContentLanguageInSubdir with only 1 language
Fixes #10064
2023-07-08 16:08:46 +02:00
David Karlsson 6c9ea022a9 config: Expand default security.exec.osEnv policy
To better support private Hugo modules and automatically
launching a text editor on content creation, this change
adds the following environment variables to the default
security policy:

- HOME
- XDG_CONFIG_HOME
- USERPROFILE
- SSH_AUTH_SOCK
- DISPLAY
- LANG

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-23 12:17:35 +02:00
Bjørn Erik Pedersen 078226dd66 Fix broken nodeploy setup
Fixes #11149
2023-06-23 12:11:45 +02:00
Joe Mooring 06d228aad1 snap: Switch from Embedded Dart Sass to Dart Sass (#11146)
- Continues to get latest version of Dart Sass during each build
- Removes superfluous BUILD_TAGS logic (we only build extended)
2023-06-21 15:39:02 -07:00
Bjørn Erik Pedersen b1016d2e26 commands: Make hugo env respect --logLevel
Fixes #11145
2023-06-21 23:18:38 +02:00
Brian Knight 49336bfc58 commands: Update Jekyll post-import output
Update CLI output after a successful Jekyll import to
suggest a maintained theme and include clearer steps
to running a server locally.

Fixes #10715

Co-authored-by: Joe Mooring <joe@mooring.com>
2023-06-21 20:38:54 +02:00
dependabot[bot] 941818295d build(deps): bump gocloud.dev from 0.24.0 to 0.30.0
Bumps [gocloud.dev](https://github.com/google/go-cloud) from 0.24.0 to 0.30.0.
- [Release notes](https://github.com/google/go-cloud/releases)
- [Commits](https://github.com/google/go-cloud/compare/v0.24.0...v0.30.0)

---
updated-dependencies:
- dependency-name: gocloud.dev
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-20 14:25:37 +02:00
dependabot[bot] 5491e55477 build(deps): bump github.com/evanw/esbuild from 0.18.4 to 0.18.5
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.18.4 to 0.18.5.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.18.4...v0.18.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-20 09:57:34 +02:00
Joe Mooring bf7af9043d deps: Update github.com/tdewolff/minify/v2 v2.12.5 => v2.12.7
Fixes #11132
2023-06-20 09:18:18 +02:00
hugoreleaser 5e12bf7dc3 releaser: Prepare repository for 0.115.0-DEV
[ci skip]
2023-06-19 17:12:27 +00:00
hugoreleaser 9df2ec7988 releaser: Bump versions for release of 0.114.0
[ci skip]
2023-06-19 17:01:43 +00:00
Bjørn Erik Pedersen 59300faae4 Revert "build(deps): bump gocloud.dev from 0.24.0 to 0.29.0"
This reverts commit 7bed16c30f.

Again ...

It doesn't build on Dragonfly:

```
../gomodcache/github.com/!azure/azure-sdk-for-go/sdk/storage/azblob@v1.0.0/blockblob/chunkwriting.go:273:15: undefined: mmb
../gomodcache/github.com/!azure/azure-sdk-for-go/sdk/storage/azblob@v1.0.0/blockblob/chunkwriting.go:279:65: undefined: mmb
../gomodcache/github.com/!azure/azure-sdk-for-go/sdk/storage/azblob@v1.0.0/blockblob/chunkwriting.go:281:22: undefined: mmb
../gomodcache/github.com/!azure/azure-sdk-for-go/sdk/storage/azblob@v1.0.0/blockblob/chunkwriting.go:287:39: undefined: mmb
../gomodcache/github.com/!azure/azure-sdk-for-go/sdk/storage/azblob@v1.0.0/blockblob/chunkwriting.go:293:18: undefined: newMMB
../gomodcache/github.com/!azure/azure-sdk-for-go/sdk/storage/azblob@v1.0.0/blockblob/chunkwriting.go:303:37: undefined: mmb
• core: Total in 123.22s …
error running command: exit status 1

Exited with code exit status 1

```
2023-06-19 18:56:31 +02:00
Bjørn Erik Pedersen 9f98b3e718 docs: Regen docshelper 2023-06-19 17:52:52 +02:00
Bjørn Erik Pedersen 3ab84651c6 Add empty Environ when loading test config
To prevent OS environment vars confusing the tests.
2023-06-19 17:49:45 +02:00
Bjørn Erik Pedersen 7241b5fd51 docs: Regen CLI docs 2023-06-19 17:40:17 +02:00
Bjørn Erik Pedersen 6dfbd24797 common/loggers: Drop the bold INFO etc. prefixes
That looked a little too much.
2023-06-19 17:35:43 +02:00
Bjørn Erik Pedersen f59c3c0214 loggers: Avoid using Logf for the LevelLoggerToWriter
To prevent issues with percentage chars.
2023-06-19 15:56:44 +02:00
Bjørn Erik Pedersen 3ca29b1563 tocss/dartsas: Avoid using Logf for the internal Dart Sass logging
As that does not work when percentages are used in the log messages.
2023-06-19 13:27:03 +02:00
Bjørn Erik Pedersen fdb0b7fb19 helpers: Remove superflous formatting flag in deprecation warnings 2023-06-19 13:02:47 +02:00
Bjørn Erik Pedersen 49dd53a405 Revert "deps: Update github.com/tdewolff/minify/v2 v2.12.5 => v2.12.6"
This reverts commit 409c6c3fcd.

See #11132
2023-06-19 12:54:15 +02:00
Andreas Deininger 68d9d3ebd7 all: Fix some typos 2023-06-19 09:28:10 +02:00
Oleksandr Redko 9009c8cdca all: Fix typos in function names and comments 2023-06-19 09:26:29 +02:00
Bjørn Erik Pedersen 12dc9a6e4a deploy: Fix deploy defaults for non-zero flag values (e.g. maxDeletes, invalidateCDN)
This was broken in the config rewrite in Hugo 0.112.0.

The workaround is to be explicit about setting these flag values (even if just using the defaults), e.g.:

```
hugo deploy --invalidateCDN --maxDeletes 256
```

Fixes #11127
2023-06-18 19:38:34 +02:00
Bjørn Erik Pedersen 1b85303ac2 common/loggers: Re-add trailing newline in logger.Printf
Fixes #11130
2023-06-18 18:56:45 +02:00
dependabot[bot] 8a04d47ab7 build(deps): bump github.com/evanw/esbuild from 0.18.3 to 0.18.4
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.18.3 to 0.18.4.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.18.3...v0.18.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-18 13:43:00 +02:00
Bjørn Erik Pedersen 7c9fada778 Replace the old log setup, with structured logging etc.
Fixes #11124
2023-06-18 13:03:04 +02:00
Joe Mooring 0e79446586 Revert "snap: Transition base snap from core20 to core22 (#11101)" (#11125)
Closes #11116
2023-06-17 08:41:15 -07:00
dependabot[bot] aaf2e9693a build(deps): bump github.com/evanw/esbuild from 0.18.2 to 0.18.3
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.18.2 to 0.18.3.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.18.2...v0.18.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-16 07:42:25 +02:00
Bjørn Erik Pedersen ee359df172 Fix upstream Go templates bug with reversed key/value assignment
The template packages are based on go1.20.5 with the patch in befec5ddbbfbd81ec84e74e15a38044d67f8785b  added.

This also includes a security fix that now disallows Go template actions in JS literals (inside backticks).

This will throw an error saying "... appears in a JS template literal".

If you're really sure this isn't a security risk in your case, you can revert to the old behaviour:

```toml
[security]
[security.gotemplates]
allowActionJSTmpl = true
```

See https://github.com/golang/go/issues/59234

Fixes #11112
2023-06-15 23:04:33 +02:00
dependabot[bot] 0f989d5e21 build(deps): bump golang.org/x/sync from 0.2.0 to 0.3.0
Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.2.0 to 0.3.0.
- [Commits](https://github.com/golang/sync/compare/v0.2.0...v0.3.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sync
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-15 19:52:14 +02:00
Bjørn Erik Pedersen f73c567534 common/collections: Always make a copy of the input slice in Append
Fixes #10458.
2023-06-14 20:18:54 +02:00
Bjørn Erik Pedersen d178fe94fe tpl/collections: Fix append when appending a slice to a slice of slices
Fixes #11093
2023-06-14 20:18:54 +02:00
dependabot[bot] 732dcb848f build(deps): bump google.golang.org/api from 0.123.0 to 0.127.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.123.0 to 0.127.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.123.0...v0.127.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-14 18:59:07 +02:00
dependabot[bot] 944859f1ad build(deps): bump golang.org/x/tools from 0.9.1 to 0.9.3
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.9.1 to 0.9.3.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.9.1...v0.9.3)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-14 14:43:15 +02:00
Bjørn Erik Pedersen 90b2674ddc Re-add site.RSSLink (and deprecate it)
Fixes #11110
2023-06-14 12:18:11 +02:00
dependabot[bot] bb9377b5e5 build(deps): bump github.com/kyokomi/emoji/v2 from 2.2.11 to 2.2.12
Bumps [github.com/kyokomi/emoji/v2](https://github.com/kyokomi/emoji) from 2.2.11 to 2.2.12.
- [Release notes](https://github.com/kyokomi/emoji/releases)
- [Commits](https://github.com/kyokomi/emoji/compare/v2.2.11...v2.2.12)

---
updated-dependencies:
- dependency-name: github.com/kyokomi/emoji/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-14 12:06:11 +02:00
dependabot[bot] e88f1b80b8 build(deps): bump github.com/getkin/kin-openapi from 0.117.0 to 0.118.0
Bumps [github.com/getkin/kin-openapi](https://github.com/getkin/kin-openapi) from 0.117.0 to 0.118.0.
- [Release notes](https://github.com/getkin/kin-openapi/releases)
- [Commits](https://github.com/getkin/kin-openapi/compare/v0.117.0...v0.118.0)

---
updated-dependencies:
- dependency-name: github.com/getkin/kin-openapi
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-14 12:05:47 +02:00
dependabot[bot] 516f0cb6ce build(deps): bump golang.org/x/net from 0.10.0 to 0.11.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.10.0 to 0.11.0.
- [Commits](https://github.com/golang/net/compare/v0.10.0...v0.11.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-14 11:58:31 +02:00
Bjørn Erik Pedersen 21d17566a3 Fix .Width and .Height for animated gifs
Fixes #11079
2023-06-14 09:21:22 +02:00
Joe Mooring 35e9b3ed1e snap: Transition base snap from core20 to core22 (#11101)
- Syntax changes
- Bump Go from 1.19 to 1.20
- Remove unused build tag logic (we only build the extended version)
2023-06-13 14:52:19 -07:00
Bjørn Erik Pedersen a0009e070a Merge commit '3c1deaf201a35de08d23cc58f8f03682cace3349' 2023-06-13 20:43:03 +02:00
Bjørn Erik Pedersen 3c1deaf201 Squashed 'docs/' changes from 1d5548d73..85befbb4d
85befbb4d Update plainify.md
56e137043 Update comparison operators (#2104)
4241135e0 Front matter docu: improve link to TOML spec (#2102)
5115de3c0 netlify: Hugo 0.113.0
99b74a053 Merge branch 'temp01130'
1622d5d1a docs: Regenerate CLI docs
adb4297eb netlify: Hugo 0.112.7
cd6813309 Update editors.md to include vim-hugo (#2098)
f7c2406dd netlify: Hugo 0.112.6
b891028c3 docs: Regenerate CLI docs

git-subtree-dir: docs
git-subtree-split: 85befbb4dc
2023-06-13 20:43:03 +02:00
dependabot[bot] 7bed16c30f build(deps): bump gocloud.dev from 0.24.0 to 0.29.0
Bumps [gocloud.dev](https://github.com/google/go-cloud) from 0.24.0 to 0.29.0.
- [Release notes](https://github.com/google/go-cloud/releases)
- [Commits](https://github.com/google/go-cloud/compare/v0.24.0...v0.29.0)

---
updated-dependencies:
- dependency-name: gocloud.dev
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-13 20:42:04 +02:00
Bjørn Erik Pedersen 2ba2271e4a tpl/math: Allow variadic math functions to take slice args, add math.Product, math.Sum
* Update math.Min and math.Max to allow 1 or more args, either scalar or slice, or combination of the two
* Add math.Sum (allow 1 or more args, either scalar or slice, or combination of the two)
* Add math.Product (allow 1 or more args, either scalar or slice, or combination of the two)

Fixes #11030
2023-06-13 20:00:00 +02:00
Bjørn Erik Pedersen 60a2cdf72d Fix config merge regression with root slices (e.g. disableKinds)
Fixes #11089
2023-06-13 18:01:23 +02:00
dependabot[bot] e08cfc8ca0 build(deps): bump golang.org/x/image from 0.7.0 to 0.8.0
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.7.0 to 0.8.0.
- [Commits](https://github.com/golang/image/compare/v0.7.0...v0.8.0)

---
updated-dependencies:
- dependency-name: golang.org/x/image
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-13 17:55:34 +02:00
Bjørn Erik Pedersen ef147f4e87 commands: Remove flags log, verboseLog, add flag logLevel, deprecate flags verbose and debug
Fixes #9648
Fixes #11071
2023-06-13 17:49:38 +02:00
dependabot[bot] 489519566e build(deps): bump github.com/magefile/mage from 1.14.0 to 1.15.0
Bumps [github.com/magefile/mage](https://github.com/magefile/mage) from 1.14.0 to 1.15.0.
- [Release notes](https://github.com/magefile/mage/releases)
- [Changelog](https://github.com/magefile/mage/blob/master/.goreleaser.yml)
- [Commits](https://github.com/magefile/mage/compare/v1.14.0...v1.15.0)

---
updated-dependencies:
- dependency-name: github.com/magefile/mage
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-13 12:59:49 +02:00
dependabot[bot] 91c0b0f76b build(deps): bump github.com/pelletier/go-toml/v2 from 2.0.6 to 2.0.8
Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.0.6 to 2.0.8.
- [Release notes](https://github.com/pelletier/go-toml/releases)
- [Changelog](https://github.com/pelletier/go-toml/blob/v2/.goreleaser.yaml)
- [Commits](https://github.com/pelletier/go-toml/compare/v2.0.6...v2.0.8)

---
updated-dependencies:
- dependency-name: github.com/pelletier/go-toml/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-13 12:58:44 +02:00
dependabot[bot] baef235d50 build(deps): bump github.com/evanw/esbuild from 0.17.19 to 0.18.2
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.17.19 to 0.18.2.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.17.19...v0.18.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-13 12:57:46 +02:00
Bjørn Erik Pedersen 0541a1b57d Fix handling of aliases (e.g. hugo serve)
Fixes #11090
2023-06-13 10:47:51 +02:00
Erica Mays 258884f44f cache: Set default cache path based on $USER
Change the default cache directory to `$TMPDIR/hugo_cache_$USER`, so
that multi-user systems do not have caches that interfere with each
other.  The other cache-choosing logic (e.g. Netlify exceptions,
configuration options) are not affected.

Fixes #7391
2023-06-12 21:33:35 +02:00
dependabot[bot] 254c2b323c build(deps): bump go.uber.org/atomic from 1.10.0 to 1.11.0
Bumps [go.uber.org/atomic](https://github.com/uber-go/atomic) from 1.10.0 to 1.11.0.
- [Release notes](https://github.com/uber-go/atomic/releases)
- [Changelog](https://github.com/uber-go/atomic/blob/master/CHANGELOG.md)
- [Commits](https://github.com/uber-go/atomic/compare/v1.10.0...v1.11.0)

---
updated-dependencies:
- dependency-name: go.uber.org/atomic
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-12 21:02:49 +02:00
dependabot[bot] 84f71ba8ff build(deps): bump github.com/hairyhenderson/go-codeowners
Bumps [github.com/hairyhenderson/go-codeowners](https://github.com/hairyhenderson/go-codeowners) from 0.2.3-0.20201026200250-cdc7c0759690 to 0.3.0.
- [Release notes](https://github.com/hairyhenderson/go-codeowners/releases)
- [Commits](https://github.com/hairyhenderson/go-codeowners/commits/v0.3.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-12 18:00:11 +02:00
Bjørn Erik Pedersen 69f0e88a4a resources: Remove failing and superflous test assertion 2023-06-12 17:57:18 +02:00
dependabot[bot] 82adc972e8 build(deps): bump github.com/frankban/quicktest from 1.14.4 to 1.14.5
Bumps [github.com/frankban/quicktest](https://github.com/frankban/quicktest) from 1.14.4 to 1.14.5.
- [Commits](https://github.com/frankban/quicktest/compare/v1.14.4...v1.14.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-12 17:57:18 +02:00
dependabot[bot] 60533fdc06 build(deps): bump github.com/mattn/go-isatty from 0.0.17 to 0.0.19
Bumps [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) from 0.0.17 to 0.0.19.
- [Commits](https://github.com/mattn/go-isatty/compare/v0.0.17...v0.0.19)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-isatty
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-12 17:03:22 +02:00
dependabot[bot] 261143bbbc build(deps): bump github.com/spf13/afero from 1.9.3 to 1.9.5
Bumps [github.com/spf13/afero](https://github.com/spf13/afero) from 1.9.3 to 1.9.5.
- [Release notes](https://github.com/spf13/afero/releases)
- [Commits](https://github.com/spf13/afero/compare/v1.9.3...v1.9.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-12 17:03:04 +02:00
Oleksandr Redko b8526f32fa commands,config: Fix typo in log and error messages 2023-06-12 16:45:20 +02:00
Bjørn Erik Pedersen ed7e250068 helpers: Avoid url.Parse in RelURL and AbsURL if we can
```
RelURL-10            159ns ± 5%      18ns ± 4%   -88.89%  (p=0.029 n=4+4)
AbsURL/relurl-10     532ns ± 0%     537ns ± 0%    +1.09%  (p=0.029 n=4+4)
AbsURL/absurl-10     142ns ± 0%       4ns ± 3%   -96.91%  (p=0.029 n=4+4)

name              old alloc/op   new alloc/op   delta
RelURL-10             144B ± 0%        0B       -100.00%  (p=0.029 n=4+4)
AbsURL/relurl-10      544B ± 0%      544B ± 0%      ~     (all equal)
AbsURL/absurl-10      144B ± 0%        0B       -100.00%  (p=0.029 n=4+4)

name              old allocs/op  new allocs/op  delta
RelURL-10             1.00 ± 0%      0.00       -100.00%  (p=0.029 n=4+4)
AbsURL/relurl-10      10.0 ± 0%      10.0 ± 0%      ~     (all equal)
AbsURL/absurl-10      1.00 ± 0%      0.00       -100.00%  (p=0.029 n=4+4)
```
2023-06-12 16:15:59 +02:00
Bjørn Erik Pedersen 6a09e7f28e Adjust benchmarks 2023-06-12 16:06:11 +02:00
Bjørn Erik Pedersen 29e5cbb699 Adjust benchmark 2023-06-12 15:18:41 +02:00
Bjørn Erik Pedersen ded6866001 Add BenchmarkAbsURL 2023-06-12 15:15:28 +02:00
Joe Mooring e1d43021ca helpers: Improve schema detection when creating relative URLs
Fixes #11080
2023-06-12 15:01:30 +02:00
Bjørn Erik Pedersen 5db215d4d6 helpers: Add a basic benchmark for RelURL 2023-06-12 14:44:07 +02:00
Bjørn Erik Pedersen b7dc93ca11 config: Remove unexpected _merge keys introduced in author and social maps
Fixes #11083
2023-06-12 14:30:43 +02:00
Bjørn Erik Pedersen f210188da3 Upgrade to v2 of the Dart Sass Embedded Protocol
Fixes #11059
2023-06-12 13:47:38 +02:00
Bjørn Erik Pedersen c782ebd89c Fix indented SASS imports for Dart Sass
Fixes #11074
2023-06-12 13:47:38 +02:00
hugoreleaser 73779707a2 releaser: Prepare repository for 0.114.0-DEV
[ci skip]
2023-06-05 15:16:33 +00:00
hugoreleaser 085c1b3d61 releaser: Bump versions for release of 0.113.0
[ci skip]
2023-06-05 15:04:51 +00:00
Bjørn Erik Pedersen 6c955cc3df docs: Regenerate CLI docs 2023-06-05 11:54:57 +02:00
Bjørn Erik Pedersen 5446900dee commands: Update CLI docs vs server and production
Fixes #11067
2023-06-05 11:53:14 +02:00
Bjørn Erik Pedersen cf38c73f53 commands: Add TLS/HTTPS support to hugo server
* commands: Add TLS/HTTPS support to hugo server

The "auto cert" handling in this PR is backed by mkcert (see link below).

To get this up and running on a new PC, you can:

```
hugo server trust
hugo server --tlsAuto
```

When `--tlsAuto` (or `--tlsCertFile` and `--tlsKeyFile`) is set and no `--baseURL` is provided as a flag, the server is
started with TLS and `https` as the protocol.

Note that you only need to run `hugo server trust` once per PC.

If you already have the key and the cert file (e.g. by using mkcert directly), you can do:

```
hugo server --tlsCertFile mycert.pem --tlsKeyFile mykey.pem
```

See https://github.com/FiloSottile/mkcert

Fixes #11064
2023-06-05 09:53:53 +02:00
hugoreleaser 536bf71abe releaser: Prepare repository for 0.113.0-DEV
[ci skip]
2023-06-02 07:19:22 +00:00
hugoreleaser ea3c95a7b0 releaser: Bump versions for release of 0.112.7
[ci skip]
2023-06-02 07:07:11 +00:00
Bjørn Erik Pedersen 5e5ce00d41 Fix menuItem.URL when pageRef is not set
Fixes #11062
2023-06-02 09:04:00 +02:00
Bjørn Erik Pedersen a191b38ac8 Don't inject livereload script on hugo -w
Fixes #11061
2023-06-02 09:04:00 +02:00
Oleksandr Redko 382c726e63 markup: Fix typo in function and struct names 2023-06-01 17:59:44 +02:00
Oleksandr Redko 4c46f9400b all: Replace deprecated ioutil with io and os 2023-06-01 17:52:21 +02:00
hugoreleaser 8c7a4e995b releaser: Prepare repository for 0.113.0-DEV
[ci skip]
2023-06-01 09:15:08 +00:00
hugoreleaser 2ca0fcc448 releaser: Bump versions for release of 0.112.6
[ci skip]
2023-06-01 09:03:29 +00:00
Bjørn Erik Pedersen a6d774e809 docs: Regenerate CLI docs 2023-06-01 10:51:41 +02:00
Bjørn Erik Pedersen dfb1895e4b Squashed 'docs/' changes from 1798dc0d5..1d5548d73
1d5548d73 build.cachebusters
3c966904d netlify: Hugo 0.112.5
b32c3a811 Bump min Go version to 1.19 when building from source (#2095)
9dea2d2f9 netlify: Hugo 0.112.4
b3fb4a1bc docs: Regenerate CLI docs
291ecd301 docs: Regen CLI docs
c886958e2 Merge commit 'd3927310d5b2404c3238f9b899db3329ea516490'

git-subtree-dir: docs
git-subtree-split: 1d5548d735
2023-06-01 10:51:24 +02:00
Bjørn Erik Pedersen 9e4072ac23 Merge commit 'dfb1895e4b82b2249d9baaed37ac7ae5e855a126' 2023-06-01 10:51:24 +02:00
Bjørn Erik Pedersen 0ef2952846 commands: Add --lang to hugo config
Fixes #11057
2023-06-01 10:49:21 +02:00
Bjørn Erik Pedersen e3ae8f025d Make sure any default mounts show up in "hugo config"
Fixes #11040
2023-06-01 10:49:21 +02:00
Bjørn Erik Pedersen 06faee5beb github: Fix Windows build
Also update some Actions to get rid of some warnings.

Fixes #11052
2023-06-01 09:54:40 +02:00
Joe Mooring 409c6c3fcd deps: Update github.com/tdewolff/minify/v2 v2.12.5 => v2.12.6 2023-05-31 08:08:26 +02:00
Joe Mooring ff77a927f9 tpl/tplimpl: Use .Language.LanguageCode in built-in templates 2023-05-30 20:10:16 +02:00
Bjørn Erik Pedersen 9cdca1f958 Fail on invalid defaultContentLanguage
Fixes #11044
2023-05-30 15:59:43 +02:00
Bjørn Erik Pedersen 6462eecfbd Avoid panic in invalid language config
Fixes #11046
2023-05-30 15:59:43 +02:00
Bjørn Erik Pedersen a7d6b1413f Don't panic on empty yaml config params
Fixes #11047
2023-05-30 15:59:43 +02:00
Bjørn Erik Pedersen 3f497d496f Prevent double escaping of image alt-text in Goldmar typographer
Fixes #11045
2023-05-30 15:03:54 +02:00
Bjørn Erik Pedersen 32585696be Fix potential deadlock in ByParam
Fixes #11039
2023-05-29 14:01:07 +02:00
hugoreleaser d47225ce9e releaser: Bump versions for release of 0.112.5
[ci skip]
2023-05-29 06:43:02 +00:00
Bjørn Erik Pedersen e3dfc76fa8 Fix it so languageCode on top level config still works
This is common for monolingual sites, and we broke this in Hugo 0.112.4.

Fixes #11037
2023-05-28 18:42:10 +02:00
hugoreleaser cd59216de4 releaser: Prepare repository for 0.113.0-DEV
[ci skip]
2023-05-28 13:15:07 +00:00
hugoreleaser e285153d7f releaser: Bump versions for release of 0.112.4
[ci skip]
2023-05-28 13:04:00 +00:00
Bjørn Erik Pedersen 20ea2e0c65 docs: Regenerate CLI docs 2023-05-28 13:07:47 +02:00
Bjørn Erik Pedersen 51d0a0ab01 commands: Add the common build flags to the config commands
Which allows to see the config with `--theme` applied.

Updates #11018
2023-05-28 13:06:26 +02:00
Bjørn Erik Pedersen fd099331ec Fix Processed images count regression for multiple languages
Fixes #11002
2023-05-28 12:55:44 +02:00
Bjørn Erik Pedersen 43f1282e73 commands: Reinstate some of the removed build flags (e.g. --theme) to new and mod
Fixes #11018
2023-05-28 12:55:44 +02:00
Bjørn Erik Pedersen e96cdfe966 Don't create the public folder unless needed
Fixes #11031
2023-05-28 12:55:44 +02:00
Bjørn Erik Pedersen ffdbce5787 docs: Regen CLI docs 2023-05-27 17:03:14 +02:00
Bjørn Erik Pedersen a838a27e4c Merge commit 'd3927310d5b2404c3238f9b899db3329ea516490' 2023-05-27 17:02:23 +02:00
Bjørn Erik Pedersen d3927310d5 Squashed 'docs/' changes from 39af43ef1..1798dc0d5
1798dc0d5 Update theme
403fa716e Update CLI documentation (#2092)
aade5a09e Correct media subtype example
53cd9dea6 netlify: Hugo 0.112.3
b78b86cb1 Add source/target warning to resources.Copy (#2091)
50c299729 netlify: Hugo 0.112.2
73197046f Change config.xxx to hugo.xxx throughout the documentation (#2090)
d489d4c6f Add hugo.WorkingDir to docs (#2089)
7487df809 Fix typos (#2088)
6d0572cd6 netlify: Hugo 0.112.1
6838600b2 netlify: Hugo 0.112.0
513e7a80f Merge branch 'tempv0.112.0'
91eb44275 Some more about 0.112.0
bd3b33a27 docs: Regen docshelper
fb3027daf docs: Regen CLI docs
8e7b8e987 Merge commit 'f96384a3b596f9bc0a3a035970b09b2c601f0ccb'
a942ceef4 tpl/tplimpl: Add img loading attribute to figure shortcode  (#10927)
0e0c7b25e tpl/urls: Return empty string when JoinPath has zero args
310ce949a tpl/urls: Add JoinPath template function
ae435ca77 tpl: Add math.Abs
f340139f8 Revert "Update syntax-highlighting.md (#10929)" (#10930)
917a0e24d Update syntax-highlighting.md (#10929)

git-subtree-dir: docs
git-subtree-split: 1798dc0d54
2023-05-27 16:59:59 +02:00
Bjørn Erik Pedersen 273d9f69a6 commands: Fail the build when no config file or config dir
Fixes #11019
2023-05-27 16:56:54 +02:00
Bjørn Erik Pedersen a6257d8a44 langs: Remove the Language.Params deprecation message for now
Closes #11025
2023-05-27 16:56:54 +02:00
Bjørn Erik Pedersen 6c2db0dfb0 Add language.LanguageCode
But keep an alias at Site

Closes #11027
2023-05-27 16:56:54 +02:00
Bjørn Erik Pedersen 8f293a1855 Fix --renderStaticToDisk regression
Fixes #11026
2023-05-27 16:56:54 +02:00
Bjørn Erik Pedersen 901cd970db commands: Re-introduce the -f shorthand for hugo new site
Fixes #11015
2023-05-27 16:56:54 +02:00
Bjørn Erik Pedersen f86b5f70a6 commands: Move the --format flag to only the commands that support it
Fixes #11022
2023-05-27 16:56:54 +02:00
hugoreleaser 3297b395d8 releaser: Prepare repository for 0.113.0-DEV
[ci skip]
2023-05-24 14:54:43 +00:00
hugoreleaser ba6f74e404 releaser: Bump versions for release of 0.112.3
[ci skip]
2023-05-24 14:42:50 +00:00
Bjørn Erik Pedersen 231374a1fa Fix regression when loading config -e is empty or HUGO_ENV or HUGO_ENVIRONMENT is set
Fixes #11013
2023-05-24 16:39:31 +02:00
hugoreleaser 5adc837904 releaser: Prepare repository for 0.113.0-DEV
[ci skip]
2023-05-24 10:57:20 +00:00
hugoreleaser f89108f2b1 releaser: Bump versions for release of 0.112.2
[ci skip]
2023-05-24 10:45:31 +00:00
Bjørn Erik Pedersen dd67922013 minifiers: Make sure JS.Version always has a value
Fixes #11012
2023-05-24 12:42:56 +02:00
Bjørn Erik Pedersen 9a235d0afc Fix regression with site.IsServer when not running a server
Fixes #11006
2023-05-24 12:42:56 +02:00
hugoreleaser 99407c39ba releaser: Prepare repository for 0.113.0-DEV
[ci skip]
2023-05-23 17:38:44 +00:00
hugoreleaser 7c90c19d28 releaser: Bump versions for release of 0.112.1
[ci skip]
2023-05-23 17:26:16 +00:00
Bjørn Erik Pedersen ed906a86e2 Fix regression when config for OutputFormat.BaseName is an empty string
Fixes #11000
2023-05-23 19:23:39 +02:00
hugoreleaser d666edad71 releaser: Prepare repository for 0.113.0-DEV
[ci skip]
2023-05-23 08:25:56 +00:00
hugoreleaser 0a95d6704a releaser: Bump versions for release of 0.112.0
[ci skip]
2023-05-23 08:14:20 +00:00
Bjørn Erik Pedersen 70b2aaf873 circleci: Add github.com to known hosts 2023-05-23 10:12:16 +02:00
Bjørn Erik Pedersen f014921159 Revert "build(deps): bump gocloud.dev from 0.24.0 to 0.29.0"
This reverts commit baa5569042.

See #10993
2023-05-23 09:24:58 +02:00
Bjørn Erik Pedersen bd38e35f97 Revert "postcss: Improve validation of option 'config'"
This reverts commit 9a0370e8eb.

Closes #10990
2023-05-22 20:31:59 +02:00
Bjørn Erik Pedersen 85b13c105a Add --format to hugo config
Now default to TOML.
2023-05-22 20:26:02 +02:00
Bjørn Erik Pedersen b6e6438f7f docs: Regen docshelper 2023-05-22 18:54:34 +02:00
Bjørn Erik Pedersen 943ff7f7ce commands: Add missing gen docshelper command
Updates #10953
2023-05-22 18:54:34 +02:00
Bjørn Erik Pedersen 288be1976d Fix "unknown command" message when no suggestion
Updates #10953
2023-05-22 18:54:34 +02:00
Andreas Deininger 9a0370e8eb postcss: Improve validation of option 'config' 2023-05-22 18:14:10 +02:00
Bjørn Erik Pedersen 10d0fcc01f docs: Regen CLI docs 2023-05-22 16:48:58 +02:00
Bjørn Erik Pedersen b95e156940 Merge commit 'f96384a3b596f9bc0a3a035970b09b2c601f0ccb' 2023-05-22 16:47:07 +02:00
Bjørn Erik Pedersen f96384a3b5 Squashed 'docs/' changes from 6e32d0591..39af43ef1
39af43ef1 Update postprocess.md
3ec192d08 Update multilingual.md
7fc7bf862 Add a note about some changes in 0.112.0
742510ae8 Fix ordinal abbrev example
fe557031a Correct spelling for 'GitHub' and 'GitLab' (#2082)
84a059b9a Fix typo in hosting-on-azure.md (#2080)
3383786fe Add i18n to list of directories affected by ignoreFiles
5bfb95234 Update 404.md (#2076)
87545a4fd Update hosting-on-cloudflare-pages.md (#2078)
aa5952c28 Add default module mount to example (#2075)
ced5292c8 Align permalinks examples (#2073)
77b5009fd Fix typo
c79319a6a Clarify description of baseURL
e93a9807b Fix typo in frontmatter description (#2071)
05fe9163a Remove erroneous statement
aa59ef383 docs: Remove note about hugo server not using 404 (#2068)
4a387a6b8 Clarify findRESubmatch (#2065)
47a9181b5 Clarify findRE, replaceRE, and findRESubmatch (#2064)
e5eedbb5e Update theme
5d392c3d4 Clarify pageRef menu property (#2059)
a557b0ebf Fix typos on Configure Hugo page (#2058)
17ef283e6 Clarify module.replacements wording (#2052)
5db4aa421 Fixing broken links (#2057)
9afa0c2fa Fix broken links (#2055)
49b981b1f Correct repo URL for migration tool (contentful.com) (#2056)
969c24c16 Remove duplicate content
0b91e7676 Revert "Delete duplicate content"
3229e79f2 Delete duplicate content
ec4eddb98 Fix typo
6509159d5 Describe snap package strict confinement (#2050)
1589bcdb7 Remove hugo.Generator admonition (#2048)
7e553d11b Add example
48bec0335 Replace blockquotes with admonitions where appropriate (#2043)
98226fe61 Remove orphaned param fron admonition calls (#2042)
2a37a1d21 Clarify cast functions (#2041)
03fd1d404 Fix typo
1898013ef Fix typos
944e27430 Replace output shortcode calls
0c66fb055 Add example of shortcode calls within sample code
f25a79c69 Replace tip and warning shortcode calls
3afac22fc Refactor code shortcode
ad65d2931 Clarify seq function
59f8a1f48 Clarify title function
47535dc87 Cleanup hasPrefix hasSuffix
7bee3e4c1 Cleanup action delimiters
cc96070f0 Correct functions archetype
ffe5d39b9 Remove duplicate shortcodes
075c9f3fe Remove old todos
bc3ec033c Front matter cleanup (#2039)
928b94505 Add code fence types (#2038)
856fa293c Document .File.Filename (#2037)
0988c4a42 Update output-formats.md (#2036)
289da5658 Change findRe to findRE
1e50f0583 Update theme
f90fb1bf5 Improve type formatting (#2032)
7785fa7d9 Use code-toggle shortcode where appropriate
f11cabf37 Add space after and before action delimiters
ac333c795 Replace erroneous use of nocopy shortcode param
064896c06 Use bool param when calling code-toggle
fb33bf59b Update code-toggle shortcode
6ddeab4f8 Add missing go-html-template code fence type (#2030)
1bba4cefb Fix links (#2029)
77f4d6c32 Link destination cleanup (#2028)
fc0ecc027 Improve breadcrumb example (#2026)
6148be2de Update the breadcrumb navigation example (#2025)
6ebb37b1b Clarify sort function (#2024)
31269bad9 Add Winget installation method (#1988)
d6c5f940e Resource methods: add signatures, minor improvements (#2017)
d2e594cbc Modify inner variable shortcode-template explanation (#1985)
a54927a7f Update GitHub Pages starter workflow (#2023)
2964c2d44 Remove orphaned static files (#2022)
97e5567cc Complete documentation on '.Scratch' and '.Store' (#2016)
fa7b2e299 Fix typo
bdce77c57 Remove literal from example menu template
c0f23b216 Correct and improve menu documentation (#2010)
464368fd9 Document .Page.Store (#2011)
a3d7c4a3a Improve urls.Parse function (#2012)
d2cec3776 Clarify postcss config option (#2013)
eb3003fef Fixed typo (#2007)
90c82d7ea Clarify mermaid markdown example (#2004)
1b11dcd5c docs(Diagrams): Update mermaid import mechanism (#1967)
4aceb6855 Fingerprinting, asset management: minor improvements (#2003)
bcbc519bb resources.GetRemote: minor improvement (#2002)
d54185bef Clarify markdownify behavior (#1999)
afb582a80 Clarify usage of slug in front matter (#1998)
f71985315 Update hasSuffix.md
29ad622a3 netlify: Hugo 0.111.3
adf223ecc Merge branch 'tempv0.111.3'
06858c646 docs: Improve examples of variadic math functions
8b656994e tpl/math: Allow multi numbers in add, sub, mul, div, min and max
2a38c4046 tpl: Add hasSuffix alias
4e0b98d54 switch transfers to workers
11651ac0f customize parallel transfer count
142f5da81 Update GitHub hosting instructions (#1991)
ad7901d2f netlify: Hugo 0.111.2
0651a76e0 add headings to distinguish render hook context params
d96d75be4 netlify: Hugo 0.111.1
226cb9e3a Add a paragraph about the new page template function
4c0157a49 Add .Fragments docs
6c78c0679 netlify: Bump to Hugo 0.111.0
7b11c24cf Merge branch 'feat/related-fragments'
615d18ef8 Add Related fragments config
a36449b0c cods: Regen docs helper
0272fa45f Merge commit '336622d5e7afd9334cd2de7150d4f16bdf7c24f9'
c5a962b93 related: Add config option cardinalityThreshold
f91677377  docs: Another fix related docs example
17aa939ea docs: Fix related docs example
12c449150 Merge commit 'cf591b7c0c598d34896709db6d28598da37e3ff6'
cb998b3d6 Add page fragments support to Related

git-subtree-dir: docs
git-subtree-split: 39af43ef11
2023-05-22 16:43:12 +02:00
Bjørn Erik Pedersen 4cac5f5e30 Avoid writing to hugo_stats.json when there are no changes
Fixes #10985
2023-05-22 16:27:19 +02:00
Bjørn Erik Pedersen 2c3d4dfb74 Add cache busting config to support Tailwind 3
Fixes #10974
2023-05-22 14:14:35 +02:00
dependabot[bot] 1292d5a26a build(deps): bump github.com/tdewolff/parse/v2 from 2.6.5 to 2.6.6
Bumps [github.com/tdewolff/parse/v2](https://github.com/tdewolff/parse) from 2.6.5 to 2.6.6.
- [Release notes](https://github.com/tdewolff/parse/releases)
- [Commits](https://github.com/tdewolff/parse/compare/v2.6.5...v2.6.6)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/parse/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-22 13:58:40 +02:00
dependabot[bot] baa5569042 build(deps): bump gocloud.dev from 0.24.0 to 0.29.0
Bumps [gocloud.dev](https://github.com/google/go-cloud) from 0.24.0 to 0.29.0.
- [Release notes](https://github.com/google/go-cloud/releases)
- [Commits](https://github.com/google/go-cloud/compare/v0.24.0...v0.29.0)

---
updated-dependencies:
- dependency-name: gocloud.dev
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-22 13:58:15 +02:00
dependabot[bot] a5413c1f89 build(deps): bump github.com/gobuffalo/flect from 0.3.0 to 1.0.2
Bumps [github.com/gobuffalo/flect](https://github.com/gobuffalo/flect) from 0.3.0 to 1.0.2.
- [Release notes](https://github.com/gobuffalo/flect/releases)
- [Commits](https://github.com/gobuffalo/flect/compare/v0.3.0...v1.0.2)

---
updated-dependencies:
- dependency-name: github.com/gobuffalo/flect
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-22 12:28:49 +02:00
dependabot[bot] 9cea58a8a6 build(deps): bump golang.org/x/image from 0.5.0 to 0.7.0
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.5.0 to 0.7.0.
- [Commits](https://github.com/golang/image/compare/v0.5.0...v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/image
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-22 12:28:08 +02:00
dependabot[bot] 1a5dce4cc5 build(deps): bump github.com/tdewolff/minify/v2 from 2.12.4 to 2.12.5
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.12.4 to 2.12.5.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.12.4...v2.12.5)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-22 12:27:50 +02:00
Bjørn Erik Pedersen 6ca8a40f25 commands: Make all list commands list what 'all' did before
Also, always include the CSV header.

Updates #10953
2023-05-22 12:27:19 +02:00
InLaw 2db7ec622f tpl/tplimpl: Add img loading attribute to figure shortcode (#10927) 2023-05-21 12:28:45 -07:00
Bjørn Erik Pedersen e6dc8053bf commands: Fix build logic when listing expired/future draft content
Fixes #10972
2023-05-21 10:16:25 +02:00
Bjørn Erik Pedersen 2637b4ef4d Allow whitelisting mediaTypes used in resources.GetRemote
Fixes #10286
2023-05-20 20:16:45 +02:00
Bjørn Erik Pedersen 7c7baa6183 Add hugo.WorkingDir
Fixes #10969
2023-05-20 17:45:56 +02:00
Bjørn Erik Pedersen 4f085e80da Make language merging of markup etc. config without values in the root
Updates #10953
2023-05-20 12:40:32 +02:00
Joe Mooring 150d190ff0 tpl/urls: Return empty string when JoinPath has zero args 2023-05-20 11:14:18 +02:00
dependabot[bot] 065ae003a5 build(deps): bump github.com/dustin/go-humanize from 1.0.0 to 1.0.1
Bumps [github.com/dustin/go-humanize](https://github.com/dustin/go-humanize) from 1.0.0 to 1.0.1.
- [Release notes](https://github.com/dustin/go-humanize/releases)
- [Commits](https://github.com/dustin/go-humanize/compare/v1.0.0...v1.0.1)

---
updated-dependencies:
- dependency-name: github.com/dustin/go-humanize
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-19 20:04:32 +02:00
dependabot[bot] 1a7d57c0b6 build(deps): bump google.golang.org/api from 0.76.0 to 0.123.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.76.0 to 0.123.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.76.0...v0.123.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-19 20:03:46 +02:00
dependabot[bot] bba54e6949 build(deps): bump golang.org/x/tools from 0.4.0 to 0.9.1
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.4.0 to 0.9.1.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.4.0...v0.9.1)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-19 18:00:58 +02:00
dependabot[bot] 737054311a build(deps): bump github.com/cli/safeexec from 1.0.0 to 1.0.1
Bumps [github.com/cli/safeexec](https://github.com/cli/safeexec) from 1.0.0 to 1.0.1.
- [Release notes](https://github.com/cli/safeexec/releases)
- [Commits](https://github.com/cli/safeexec/compare/v1.0.0...v1.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-19 18:00:42 +02:00
dependabot[bot] f6269ee928 build(deps): bump github.com/getkin/kin-openapi from 0.110.0 to 0.117.0
Bumps [github.com/getkin/kin-openapi](https://github.com/getkin/kin-openapi) from 0.110.0 to 0.117.0.
- [Release notes](https://github.com/getkin/kin-openapi/releases)
- [Commits](https://github.com/getkin/kin-openapi/compare/v0.110.0...v0.117.0)

---
updated-dependencies:
- dependency-name: github.com/getkin/kin-openapi
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-19 18:00:28 +02:00
Bjørn Erik Pedersen 715d484045 deps: Update github.com/evanw/esbuild v0.17.0 => v0.17.19 2023-05-19 18:00:04 +02:00
Bjørn Erik Pedersen 0a51dfac9e commands: Fix data race
By wrapping all use of the shared config in a lock.

Updates #10953
2023-05-19 16:34:22 +02:00
Bjørn Erik Pedersen c371171ab8 deps: Update github.com/alecthomas/chroma/v2 v2.7.0 2023-05-19 12:35:05 +02:00
Bjørn Erik Pedersen d6197a41fa Re-add --printUnusedTemplates and --printPathWarnings
And now with tests.

Updates #10953
2023-05-19 11:37:05 +02:00
Bjørn Erik Pedersen e4e0313c80 tpl/urls: Fix build broken by a merge 2023-05-19 10:57:03 +02:00
Joe Mooring 5b3e165bad tpl/urls: Add JoinPath template function
See https://pkg.go.dev/net/url#JoinPath

Closes #9694
2023-05-19 10:31:17 +02:00
Bjørn Erik Pedersen 03cb38e6c6 Allow legacy taxonomyTerm in disableKinds
Updates #10953
2023-05-19 09:17:55 +02:00
Andreas Deininger ad4bc969da Fix warn message about custom params on the language top level 2023-05-19 08:43:02 +02:00
cui fliter 4003c7903f Fix some spelling mistakes 2023-05-19 08:40:08 +02:00
Oleksandr Redko 610cedaa61 all: Fix comments for exported functions and packages 2023-05-18 21:25:27 +02:00
Bjørn Erik Pedersen 24e7d0c174 deps: Update github.com/bep/golibsass v1.1.0 => v1.1.1
Fixes #10629
Fixes #10491
2023-05-18 21:24:33 +02:00
Benjamin Elder 008170c8a9 Make GOMAXPROCS to be CPU limit aware
Fixes #10950
2023-05-18 19:32:49 +02:00
Bjørn Erik Pedersen 7c647bcaeb Allow empty params.mainSections
Updates #10953
2023-05-18 17:55:29 +02:00
Oleksandr Redko 95818e27dc modules: Fix format flag in error 2023-05-18 16:26:20 +02:00
Bjørn Erik Pedersen 3f00f47535 commands: Load config before creating the filesystem
To allow publishDir to be set in config file.
2023-05-18 15:38:25 +02:00
Bjørn Erik Pedersen 834b3d7e41 Fix some recently introduced error handling issues
Updates #10953
2023-05-18 12:47:23 +02:00
Joe Mooring 1155bbca9d tpl/lang: document delimiter option for FormatNumberCustom
Closes gohugoio/hugoDocs#1760
2023-05-17 23:35:43 +02:00
Bjørn Erik Pedersen 86b2a27438 Re-add site.LanguagePrefix
Updates #10947
2023-05-17 23:27:40 +02:00
Bjørn Erik Pedersen 35955f50e6 github: Trim the test flow a little
We now have much improved end to end tests, so cut the building of the docs site to save some time of the PR builds.
2023-05-17 22:14:15 +02:00
Bjørn Erik Pedersen 8a69ccbb00 commands: Improve the common build flag handling
Updates #10947
2023-05-17 22:13:29 +02:00
Bjørn Erik Pedersen 7ce033a89d Support, but warn, about top level language custom params
Updates #10947
2023-05-17 22:13:29 +02:00
Bjørn Erik Pedersen 05542130ba Handle transient errors in config loading etc.
As in: Get the Kubernetes site to build with the new Hugo version.

Updates #10947
2023-05-17 22:13:29 +02:00
Bjørn Erik Pedersen 5251f015bf Re-establish all the server flags
Updates #10947
2023-05-17 22:13:29 +02:00
Bjørn Erik Pedersen 5d857165fe Deprecate site.Language.Params and some other fixes
Updates #10947
2023-05-17 22:13:29 +02:00
Bjørn Erik Pedersen 0106cf1a6d Revert "Make GOMAXPROCS CPU limit aware"
I shouldn't have merged this without running the tests.

This libray logs to stderr on init, which is a no go for us.

Re. test failure:

```
[stderr]
2023/05/17 18:21:04 maxprocs: Leaving GOMAXPROCS=2: CPU quota undefined
```
This reverts commit 59050f97f6.
2023-05-17 21:01:52 +02:00
Benjamin Elder 59050f97f6 Make GOMAXPROCS CPU limit aware
Fixes #10950
2023-05-17 20:17:22 +02:00
Bjørn Erik Pedersen faa6998f26 Add Sections to Site interface
See https://github.com/gohugoio/hugo/issues/10947#issuecomment-1550012671

Updates #10947
2023-05-16 18:53:34 +02:00
Oleksandr Redko 3d90871e9e helpers: simplify path tests with T.TempDir 2023-05-16 18:35:19 +02:00
Oleksandr Redko bda082c98c tpl: Add math.Abs
Fixes #10941.
2023-05-16 18:32:07 +02:00
Bjørn Erik Pedersen 241b21b0fd Create a struct with all of Hugo's config options
Primary motivation is documentation, but it will also hopefully simplify the code.

Also,

* Lower case the default output format names; this is in line with the custom ones (map keys) and how
it's treated all the places. This avoids doing `stringds.EqualFold` everywhere.

Closes #10896
Closes #10620
2023-05-16 18:01:29 +02:00
Bjørn Erik Pedersen 6aededf6b4 Improve date parsing performance for the common case
```
name                    old time/op    new time/op    delta
Baseline/skiprender-10    24.5ms ±10%    22.3ms ± 4%   -8.93%  (p=0.029 n=4+4)

name                    old alloc/op   new alloc/op   delta
Baseline/skiprender-10    30.0MB ± 0%    26.2MB ± 0%  -12.74%  (p=0.029 n=4+4)

name                    old allocs/op  new allocs/op  delta
Baseline/skiprender-10      373k ± 0%      279k ± 0%  -25.07%  (p=0.029 n=4+4)
```

Fixes #10942
2023-05-15 11:25:24 +02:00
Bjørn Erik Pedersen 0988b76a78 Add a counter helper 2023-05-15 09:23:26 +02:00
Bjørn Erik Pedersen e0e19a934f Expand the baseline benchmark a little 2023-05-15 08:39:52 +02:00
Joe Mooring bcd7ac7704 Revert "Update syntax-highlighting.md (#10929)" (#10930)
This reverts commit a4fb8dc6bc.
2023-05-06 09:36:29 -07:00
Kirill Bobyrev a4fb8dc6bc Update syntax-highlighting.md (#10929) 2023-05-06 07:00:37 -07:00
Bjørn Erik Pedersen 4f341fa1af Update README.md 2023-05-04 23:56:57 +02:00
Joe Mooring 5c7b79cf7f tpl/strings: Clarify findRESubmatch description 2023-04-27 22:02:41 +02:00
Joe Mooring 0cb6ca5906 langs/i18n: Fallback to defaultContentLanguage instead of English
Co-authored-by: 641bill <wo23636@126.com>

Fixes #9216
2023-04-24 21:20:09 +02:00
Bjørn Erik Pedersen f1062519ae tpl/debug: Add VisualizeSpaces 2023-04-20 11:27:55 +02:00
Bjørn Erik Pedersen 46a3cf6186 Update README.md 2023-04-18 08:35:37 +02:00
Bjørn Erik Pedersen 9906c1ae52 Prevent the global error collector to panic when sending on closed channel 2023-04-13 11:44:22 +02:00
Bjørn Erik Pedersen 5596dc24a0 markup/goldmark: Add config options for the typographer extension
Note that the config per language part of this will be handled in #10602.

Updates #9772
2023-04-12 14:41:32 +02:00
Kiril Isakov d01731d53c readme: Fix build command 2023-04-09 11:59:46 +02:00
Bjørn Erik Pedersen f1e8f010f5 Update README.md 2023-03-30 10:19:35 +02:00
Bjørn Erik Pedersen 5748133d50 Add test for ToC vs include
See #10866
2023-03-29 17:49:01 +02:00
Andreas Deininger 05c095a0e6 resources.functions: improve validation 2023-03-20 14:53:57 +01:00
Oleksandr Redko 891b2918d2 resources: Fix typos in error message and variables 2023-03-20 14:52:59 +01:00
Bjørn Erik Pedersen b0b1b76dc9 markup/goldmark: Fail on invalid Markdown attributes 2023-03-15 08:54:34 +01:00
Bjørn Erik Pedersen 0fbab7cbc5 commands: Fix data race in test
Note that this is a test fix only.
2023-03-14 12:18:42 +01:00
septs f5eddf89bf tpl/math: Return error if less than 2 input numbers
Fixes #10827
2023-03-14 09:28:38 +01:00
hugoreleaser 0e8ab20a84 releaser: Prepare repository for 0.112.0-DEV
[ci skip]
2023-03-12 11:51:49 +00:00
hugoreleaser 5d4eb5154e releaser: Bump versions for release of 0.111.3
[ci skip]
2023-03-12 11:40:50 +00:00
Niklas Fasching 1c841ec914 deps: Update go-org to v1.6.6
among other things, fixes a race condition in html writer and bug preventing
explicit line breaks immediately following emphasis
2023-03-12 12:38:12 +01:00
Bjørn Erik Pedersen e7148f335f Fix "unknown shortcode token" when calling shortcode within fenced code block
Fixes #10819
2023-03-12 11:39:38 +01:00
Andreas Deininger d55af2abf0 Run gofmt -s on source files 2023-03-12 10:32:29 +01:00
Joe Mooring b6f44aaf1a docs: Improve examples of variadic math functions 2023-03-12 10:32:01 +01:00
septs 84201e8d5d tpl/math: Allow multi numbers in add, sub, mul, div, min and max 2023-03-11 11:21:08 +01:00
Andreas Deininger 04b9811643 readme: Update dependency list 2023-03-11 10:15:16 +01:00
Andreas Deininger 9818724b5b Improve error message for unclosed shortcode with inner content 2023-03-10 18:41:17 +01:00
Bjørn Erik Pedersen 34a86e13f6 Don't fail when calling Paginate with an empty pages.PagesGroup
Fixes #10802
2023-03-10 16:34:17 +01:00
Oleksandr Redko 0f01bd4637 server: Replace golang.org/x/net/context with context 2023-03-08 10:19:25 +01:00
Jonathan Fisher d171d1543d tpl: Add hasSuffix alias
strings.HasPrefix already has an alias of hasPrefix
but strings.HasSuffix has no such alias.
This PR adds a hasSuffix alias to the tpl function with corresponding
function documentation.
It also adds a Minor update to the hasPrefix function documentation
re: keywords and relatedfuncs.

 Completes https://github.com/gohugoio/hugo/issues/10474
2023-03-08 10:18:34 +01:00
Oleksandr Redko 02ab77da3e watcher: use time.NewTicker to prevent leaks
Replace time.Tick with time.NewTicker.
2023-03-08 10:16:34 +01:00
davidejones 873be9f90a ensure we default to 10 correctly 2023-03-07 15:38:26 +01:00
davidejones bebb2b8d0a switch transfers to workers 2023-03-07 15:38:26 +01:00
davidejones e6f029bdee customize parallel transfer count 2023-03-07 15:38:26 +01:00
johannesengl bdbfacb868 metadecoders: Add support for native org dates in frontmatter
PR #7433 added support for Org timestamps for the DATE header. This PR widens the support with additional front matter headers LASTMOD, PUBLISHDATE and EXPIRYDATE.

Fixes #8536
2023-03-06 20:37:31 +01:00
hugoreleaser 32ea40aa82 releaser: Prepare repository for 0.112.0-DEV
[ci skip]
2023-03-05 12:43:18 +00:00
hugoreleaser 4164f8fef9 releaser: Bump versions for release of 0.111.2
[ci skip]
2023-03-05 12:32:20 +00:00
Bjørn Erik Pedersen b83050cb40 Fix .Fragments when called cross sites on uninitialized output format
Fixes #10794
2023-03-05 12:51:57 +01:00
Bjørn Erik Pedersen df5608f8a0 Allow page.TableOfContents on self in shortcode
Fixes #10791
2023-03-05 12:51:57 +01:00
Bjørn Erik Pedersen f56ce01ae1 tpl/partial: Consolidate GoDoc 2023-03-04 22:04:01 +01:00
Bjørn Erik Pedersen 3bbeb5688c Fix "context canceled" with partial
Make sure the context used for timeouts isn't created based on the incoming
context, as we have cases where this can cancel the context prematurely.

Fixes #10789
2023-03-04 21:29:05 +01:00
Oleksandr Redko 184a67ac47 cache: Fix --gc failure on Windows
Fixes "Error: failed to prune cache" on Windows and removes
work around from ec1c97e7e9.

Follows #10781.
2023-03-04 18:47:43 +01:00
Bjørn Erik Pedersen 6c798eba60 Page context handling in i18n
This is a workaround. We need to improve on this, but not today.

Fixes #10782
2023-03-04 17:19:14 +01:00
Bjørn Erik Pedersen ec1c97e7e9 Work around --gc failure on Windows <= 10
This applies two related fixes/improvements:

* The --gc now keeps empty `_resources/_gen/images` etc folders, even if empty. This should have been the behaviour
from the start.
* Also, if removal of an empty dir on Windows fails with the "used by another process" error, just ignore it for now.

Fixes #10781
2023-03-04 13:40:55 +01:00
Bjørn Erik Pedersen f10009e7f1 Update to Go 1.20.1
Fixes #10785
2023-03-04 10:36:59 +01:00
Joe Mooring a950950f1b snap: Fix dart-sass-embedded installation
Closes #10783
2023-03-04 10:15:03 +01:00
Oleksandr Redko 36ce3a4a9d Correct typos in Go comments 2023-03-02 16:32:32 +01:00
hugoreleaser 17e60b77e1 releaser: Prepare repository for 0.112.0-DEV
[ci skip]
2023-03-02 10:19:25 +00:00
hugoreleaser 39a4a3cf67 releaser: Bump versions for release of 0.111.1
[ci skip]
2023-03-02 10:08:26 +00:00
Bjørn Erik Pedersen 52f339dada Merge branch 'release-0.111.0' 2023-03-02 11:05:24 +01:00
Bjørn Erik Pedersen eef23a7f27 Fix "page" not defined
Fixes #10774
2023-03-02 10:39:44 +01:00
hugoreleaser 18cf75805c releaser: Prepare repository for 0.112.0-DEV
[ci skip]
2023-03-01 21:08:27 +00:00
hugoreleaser 3fa8bb8318 releaser: Bump versions for release of 0.111.0
[ci skip]
2023-03-01 20:57:44 +00:00
Bjørn Erik Pedersen db9f74d24d Revert "build(deps): bump gocloud.dev from 0.24.0 to 0.28.0 (#10610)"
This reverts commit c3a59a7d5a.

See #10770
2023-03-01 21:55:47 +01:00
Bjørn Erik Pedersen 60e6fa798f build: Bump build images
To fix changed Shuffle behaviour on Go 1.20.
2023-03-01 21:32:12 +01:00
Bjørn Erik Pedersen 7e51ba03ca build: Update Linux ARM build image 2023-03-01 18:55:22 +01:00
Joe Mooring 66f94b4945 tpl/tplimpl: Remove the Google News internal template
This was deprecated, with a warning message, on 21 Nov 2021.

Reference #9190
2023-03-01 18:11:44 +01:00
Khayyam Saleem c0d15a2897 strings: fix Truncate behavior for formatted html
Before this fix, strings.Truncate would erroneously re-include
attributes from the opening tag in the closing tag when closing
formatted html, due to a bug in how tagnames were extracted from the
regex capture group for html tags used in `truncate.go`. This change
ensures that only the tagname is retained and all attributes are discarded
when storing the tags for closing them later.

Fixes #10399
2023-03-01 17:59:50 +01:00
Bjørn Erik Pedersen 2a61910e82 tpl/strings: Adjust benchmark 2023-03-01 17:59:02 +01:00
Bjørn Erik Pedersen 079d1b6540 tpl/strings: Add BenchmarkTruncate 2023-03-01 17:51:09 +01:00
Bjørn Erik Pedersen a56b9071db cods: Regen docs helper 2023-03-01 16:35:23 +01:00
Bjørn Erik Pedersen 5c317c55e7 Move the Related doc counter to prevent a race
Closes #10768
2023-03-01 16:28:43 +01:00
Bjørn Erik Pedersen a669467d98 Misc ioutil deprecation adjustments
To make the tests pass.

* Replace io => os.ReadFile in magefile.go
* Adjust failing image test vs fs.DirEntry
* Adjust poller test

See #10732
2023-03-01 16:28:43 +01:00
Oleksandr Redko d453c12742 Replace deprecated ioutil with io and os
https://pkg.go.dev/io/ioutil is deprecated since Go 1.16.
2023-03-01 16:28:43 +01:00
Bjørn Erik Pedersen 336622d5e7 Squashed 'docs/' changes from 36dd5483f..6e32d0591
6e32d0591 Update quick-start.md (#1984)
eddd25ff4 Mermaid: reflect latest docs specifications
b2a23b6f5 Mermaid: switch to ESM import
43d83c131 Update index.md

git-subtree-dir: docs
git-subtree-split: 6e32d05910
2023-03-01 11:56:07 +01:00
Bjørn Erik Pedersen 97b010f521 Merge commit '336622d5e7afd9334cd2de7150d4f16bdf7c24f9' 2023-03-01 11:56:07 +01:00
dependabot[bot] 4d36b99a4e build(deps): bump github.com/tdewolff/parse/v2 from 2.6.4 to 2.6.5
Bumps [github.com/tdewolff/parse/v2](https://github.com/tdewolff/parse) from 2.6.4 to 2.6.5.
- [Release notes](https://github.com/tdewolff/parse/releases)
- [Commits](https://github.com/tdewolff/parse/compare/v2.6.4...v2.6.5)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/parse/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-01 09:28:29 +01:00
dependabot[bot] 807237bc0e build(deps): bump github.com/mattn/go-isatty from 0.0.16 to 0.0.17
Bumps [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) from 0.0.16 to 0.0.17.
- [Release notes](https://github.com/mattn/go-isatty/releases)
- [Commits](https://github.com/mattn/go-isatty/compare/v0.0.16...v0.0.17)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-isatty
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-27 11:56:40 +01:00
dependabot[bot] 79b03b3f79 build(deps): bump golang.org/x/image
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.0.0-20211028202545-6944b10bf410 to 0.5.0.
- [Release notes](https://github.com/golang/image/releases)
- [Commits](https://github.com/golang/image/commits/v0.5.0)

---
updated-dependencies:
- dependency-name: golang.org/x/image
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-27 11:56:09 +01:00
Oleksandr Redko e31441031d Remove unused temp directory 2023-02-27 09:21:16 +01:00
WaltCuller 39cc3a2a7e exif: Return the proper exposure time value in some special cases
Return value in float64 if exposure time is int or greater than 1, otherwise return in fraction.

Fixes #10738
2023-02-26 11:19:49 +01:00
Bjørn Erik Pedersen ce524d0b5e Add a page template func
Fixes #9339
2023-02-25 19:53:18 +01:00
Joe Mooring 2662faf61f dartsass: Import CSS without extension at compile time
Applicable to Dart Sass only:

- Sass imports with the .css extension indicate a plain CSS @import.
- Sass imports without the .css extension are imported at compile time.

Fixes #10592
2023-02-24 15:19:21 +01:00
Bjørn Erik Pedersen 271318ad78 Split parse and render for Goldmark
This also speeds up situations where you only need the fragments/toc and not the rendered content, e.g. Related
with fragments type indexing:

```bash

name            old time/op    new time/op    delta
RelatedSite-10    12.3ms ± 2%    10.7ms ± 1%  -12.95%  (p=0.029 n=4+4)

name            old alloc/op   new alloc/op   delta
RelatedSite-10    38.6MB ± 0%    38.2MB ± 0%   -1.08%  (p=0.029 n=4+4)

name            old allocs/op  new allocs/op  delta
RelatedSite-10      117k ± 0%      115k ± 0%   -1.36%  (p=0.029 n=4+4)
```

Fixes #10750
2023-02-24 10:40:06 +01:00
Bjørn Erik Pedersen e442a63bb7 related: Add config option cardinalityThreshold
Fixes #10744
2023-02-23 17:06:22 +01:00
Bjørn Erik Pedersen d5601e8391 docs: Another fix related docs example 2023-02-23 11:09:55 +01:00
Bjørn Erik Pedersen cedd04db31 docs: Fix related docs example 2023-02-23 10:54:30 +01:00
dependabot[bot] 4bf91b9759 build(deps): bump golang.org/x/net from 0.4.0 to 0.7.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.4.0 to 0.7.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/compare/v0.4.0...v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-23 09:58:21 +01:00
Bjørn Erik Pedersen ae48507d66 Fix shortcode error when closing without .Inner
Fixes #10672
2023-02-23 09:36:14 +01:00
Bjørn Erik Pedersen 7d78a498e1 Throw an error when shortcode is expected to be closed
Fixes #10675
2023-02-23 09:36:14 +01:00
Bjørn Erik Pedersen 0dbeac80cd Add some shortcode testcases
Closes #10671
2023-02-23 09:36:14 +01:00
Bjørn Erik Pedersen b99d073caf sass: Remove some unused leftover code 2023-02-23 07:57:53 +01:00
Joe Mooring e965cb679c resources/sass: Remove debug statements
Closes #10470
2023-02-23 07:53:46 +01:00
Bjørn Erik Pedersen cf591b7c0c Squashed 'docs/' changes from 1214f6ffb..36dd5483f
36dd5483f Clarify placement of 404 template
6f0a5f3f0 Update urls.Parse.md
c8070e578 Remove reference to Internet Explorer conditional comments (#1975)
3e3458f09 Describe default source map behavior for js.build (#1974)
08c9ed09a Simplify ordinal abbreviation example... (#1970)
b5aa8d598 docs(markdownify): mention a context limitation (#1968)
596af47f5 Fixing typo in configuration.md doc (#1966)
c47cadfcb Fix `hardWraps` config spelling (#1964)
5739a174e Add detail to tabWidth highlighting option
73a4bcd1f doc: Add hugo-lyra search engine (#1959)
6cc9ebdfd Update uniq function example (#1963)
686a65cf6 Update uniq.md
096f794d0 Update uniq.md
914ca0c38 remove `version` from SVG example (#1957)
58347d41f Update theme
7c806371f Fix 404 error for CloudCannon community learn docs (#1955)
58e42b03d Update theme
fd0385ee2 Update theme
513b7a43a Update findRe.md
4d39137ef Update configuration.md
b1c3b58a7 Update configuration.md
f827cce8d Update configuration.md
3d72ed8fb netlify: Hugo 0.110.0
e6f969c87 Merge branch 'feat/config-rename'
4c0b5a0b5 dos: Regen CLI docs
05d9db705 docs: Regen docshelper
f73bdb6e5 Merge commit 'ef6f101e75256c3bb88a6f1f3b5c1273bf8d7382'
e83141f88 Format config
4cadf795e Rename config.toml -> hugo.toml
c8aa8617f Move config/_default/config.toml -> config.toml
2943c031a Add fill HTTP Response info into .Data in resources.GetRemote

git-subtree-dir: docs
git-subtree-split: 36dd5483fb
2023-02-23 07:52:04 +01:00
Bjørn Erik Pedersen 7e539cb398 Merge commit 'cf591b7c0c598d34896709db6d28598da37e3ff6' 2023-02-23 07:52:04 +01:00
Bjørn Erik Pedersen 586fea0de6 page: Move the cache double check right after the lock 2023-02-22 21:24:41 +01:00
Bjørn Erik Pedersen fa2d7adf10 page: Add some concurrency to the building of the related page index
But only in the case where we know that we will need to access the Page fragments/tableofcontents.

In normal situations this will spread naturally across the CPU cores, but not in the situation where
`site.RegularPages.Related` gets called as part of e.g. the single template.

```bash
name            old time/op    new time/op    delta
RelatedSite-10    18.0ms ± 2%    11.9ms ± 1%  -34.17%  (p=0.029 n=4+4)

name            old alloc/op   new alloc/op   delta
RelatedSite-10    38.6MB ± 0%    38.6MB ± 0%     ~     (p=0.114 n=4+4)

name            old allocs/op  new allocs/op  delta
RelatedSite-10      117k ± 0%      117k ± 0%   +0.23%  (p=0.029 n=4+4)
```

See #10711
2023-02-22 20:37:41 +01:00
Bjørn Erik Pedersen 4346987faf related: Adjust benchmark 2023-02-22 19:17:22 +01:00
Aaron Hooper 2dad13c0e4 create: Fix typo in error message 2023-02-22 14:14:49 +01:00
Bjørn Erik Pedersen ecf3cd514f tocss: Simplify the hugo:vars type handling
Instead of maintaing a list of all CSS units and functions this commit:

* Uses 3 regexps to detect typed CSS values (e.g. `24px`) + properly handle numeric Go types.
* These regexps may have some false positives -- e.g. strings that needs to be quoted.
* For that rare case, you can mark the string with e.g. `"32xxx" | css.Quoted`
* For the opposite case:  `"32" | css.Unquoted`

Updates #10632
2023-02-22 13:26:10 +01:00
AcClassic a1a9c08b5f resource_transformers/tocss: Fixed hugo:vars casting
Variables passed via the hugo:vars function where passed as type string.
This caused problems when using the variables in sass functions because
these expect a specific type. Now we check if the passed variables have
to be quoted and therefore are of type string or if they should not be
quoted and let the type interpretation up to the sass compiler.

Fixes #10632
2023-02-22 13:26:10 +01:00
Bjørn Erik Pedersen 6abd15e781 Adjust tests for GO 1.20
Updates #10691
2023-02-22 11:26:52 +01:00
Bjørn Erik Pedersen 094135ff96 tpl/internal: Sync Go template src to Go 1.20
Updates #10691
2023-02-22 11:26:52 +01:00
Bjørn Erik Pedersen 4801e2e8ee build: Update to Go 1.20
Updates #10691
2023-02-22 11:26:52 +01:00
Bjørn Erik Pedersen 90da7664bf Add page fragments support to Related
The main topic of this commit is that you can now index fragments (content heading identifiers) when calling `.Related`.

You can do this by:

* Configure one or more indices with type `fragments`
* The name of those index configurations maps to an (optional) front matter slice with fragment references. This allows you to link
page<->fragment and page<->page.
* This also will index all the fragments (heading identifiers) of the pages.

It's also possible to use type `fragments` indices in shortcode, e.g.:

```
{{ $related := site.RegularPages.Related .Page }}
```

But, and this is important, you need to include the shortcode using the `{{<` delimiter. Not doing so will create infinite loops and timeouts.

This commit also:

* Adds two new methods to Page: Fragments (can also be used to build ToC) and HeadingsFiltered (this is only used in Related Content with
index type `fragments` and `enableFilter` set to true.
* Consolidates all `.Related*` methods into one, which takes either a `Page` or an options map as its only argument.
* Add `context.Context` to all of the content related Page API. Turns out it wasn't strictly needed for this particular feature, but it will
soon become usefil, e.g. in #9339.

Closes #10711
Updates #9339
Updates #10725
2023-02-21 17:56:41 +01:00
Bjørn Erik Pedersen 0afec0a9f4 related: Adjust benchmark
To make it easier to compare.
2023-02-21 11:06:29 +01:00
Bjørn Erik Pedersen 28540ed131 related: Add benchmark 2023-02-21 11:00:43 +01:00
Bjørn Erik Pedersen 9af78d1100 tpl/collections: Improve error message in Index 2023-02-08 10:22:20 +01:00
Bjørn Erik Pedersen d33a7ebcc1 Make the HTML collector parsing more robust
Most notably better handling self-closing elements

Closes #10698
2023-02-07 09:11:02 +01:00
Bjørn Erik Pedersen 2a364cca64 Revert "build(deps): bump github.com/getkin/kin-openapi from 0.110.0 to 0.114.0"
This reverts commit 87c78bd3e9.

There seem to be some breaking changes in the above that needs to be looked into.
2023-02-05 20:34:35 +01:00
Joe Mooring 3fb2417cb7 deps: Upgrade github.com/yuin/goldmark v1.5.3 => v1.5.4
Closes #10661
2023-02-05 20:09:06 +01:00
Bjørn Erik Pedersen fce0890484 tpl/strings: Add strings.ContainsNonSpace 2023-02-05 20:08:44 +01:00
dependabot[bot] 87c78bd3e9 build(deps): bump github.com/getkin/kin-openapi from 0.110.0 to 0.114.0
Bumps [github.com/getkin/kin-openapi](https://github.com/getkin/kin-openapi) from 0.110.0 to 0.114.0.
- [Release notes](https://github.com/getkin/kin-openapi/releases)
- [Commits](https://github.com/getkin/kin-openapi/compare/v0.110.0...v0.114.0)

---
updated-dependencies:
- dependency-name: github.com/getkin/kin-openapi
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-05 20:07:15 +01:00
Joe Mooring 69c369e114 deps: Upgrade github.com/alecthomas/chroma v2.4.0 => v2.5.0
Fixes #9890
Fixes #10692
2023-02-05 20:06:51 +01:00
Khayyam Saleem 73ece30d84 markup: Fix linenos codeblock hl option case regression
This fixes a regression introduced in v0.93.0 where previously, a
mixed-case key for lineNos would be successfully parsed. This change
moves the configuration key lowercasing step into the configuration
normalization stage, which is called whether the highlighting config
is being parsed from a `string` or a `map`.

Fixes #10682
2023-02-05 20:06:25 +01:00
Bjørn Erik Pedersen f9fc0e045b Fix slow HTML elements collector for the pre case
```
name                           old time/op    new time/op    delta
ElementsCollectorWriterPre-10    25.2µs ± 1%     3.4µs ± 0%  -86.54%  (p=0.029 n=4+4)

name                           old alloc/op   new alloc/op   delta
ElementsCollectorWriterPre-10      624B ± 0%      142B ± 0%  -77.18%  (p=0.029 n=4+4)

name                           old allocs/op  new allocs/op  delta
ElementsCollectorWriterPre-10      16.0 ± 0%       6.0 ± 0%  -62.50%  (p=0.029 n=4+4)
```

Fixes #10698
2023-02-05 20:01:39 +01:00
Bjørn Erik Pedersen 4f4a1c00bf publisher: Add benchmark 2023-02-05 16:39:31 +01:00
Anthony Fok 76c6140c52 snap: Install dart-sass-embedded for 32-bit ARM (armhf) too
See https://github.com/sass/dart-sass-embedded/releases/tag/1.54.7
which "Add[ed] support for 32-bit ARM releases on Linux" on 2022-08-30.
2023-02-03 14:58:11 +01:00
Anthony Fok d4482e8bf4 snap: Add read access for ~/.gitconfig.local and ~/.config/git/* too
in hugo:gitconfig plug (personal-files interface) in case end users need
the flexibility of using additional custom git config files.

Fixes #10337
2023-02-03 14:57:53 +01:00
Bjørn Erik Pedersen 1477d0ba90 commands: Fix server url rewrites (http status 200)
Broken by me in Hugo 0.103.0 it seems. A little bit mysterious as it has worked for me after that, but started to fail just
recently.

Closes #10668
2023-01-31 12:42:31 +01:00
dependabot[bot] dd37163f5a build(deps): bump github.com/kyokomi/emoji/v2 from 2.2.10 to 2.2.11
Bumps [github.com/kyokomi/emoji/v2](https://github.com/kyokomi/emoji) from 2.2.10 to 2.2.11.
- [Release notes](https://github.com/kyokomi/emoji/releases)
- [Commits](https://github.com/kyokomi/emoji/compare/v2.2.10...v2.2.11)

---
updated-dependencies:
- dependency-name: github.com/kyokomi/emoji/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-31 09:41:13 +01:00
dependabot[bot] c3a59a7d5a build(deps): bump gocloud.dev from 0.24.0 to 0.28.0 (#10610)
Bumps [gocloud.dev](https://github.com/google/go-cloud) from 0.24.0 to 0.28.0.
- [Release notes](https://github.com/google/go-cloud/releases)
- [Commits](https://github.com/google/go-cloud/compare/v0.24.0...v0.28.0)

---
updated-dependencies:
- dependency-name: gocloud.dev
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Closes #9778
2023-01-31 09:40:39 +01:00
Joe Mooring 4ccc8cfb4c Fix description of collections.Uniq 2023-01-31 08:12:45 +01:00
Bjørn Erik Pedersen e2cfc3d5a2 Update CONTRIBUTING.md 2023-01-31 08:08:52 +01:00
Bjørn Erik Pedersen 168858331f Fix shortcode detection in RenderString
Fixes #10654
2023-01-26 11:41:07 +01:00
Bjørn Erik Pedersen 4ef9baf5bd Only invoke a given cached partial once
Note that this is backed by a LRU cache (which we soon shall see more usage of), so if you're a heavy user of cached partials it may be evicted and
refreshed if needed. But in most cases every partial is only invoked once.

This commit also adds a timeout (the global `timeout` config option) to make infinite recursion in partials
easier to reason about.

```
name              old time/op    new time/op    delta
IncludeCached-10    8.92ms ± 0%    8.48ms ± 1%   -4.87%  (p=0.016 n=4+5)

name              old alloc/op   new alloc/op   delta
IncludeCached-10    6.65MB ± 0%    5.17MB ± 0%  -22.32%  (p=0.002 n=6+6)

name              old allocs/op  new allocs/op  delta
IncludeCached-10      117k ± 0%       71k ± 0%  -39.44%  (p=0.002 n=6+6)
```

Closes #4086
Updates #9588
2023-01-25 17:35:23 +01:00
Bjørn Erik Pedersen 93ed6e447a Update README.md 2023-01-24 15:04:06 +01:00
hugoreleaser 0d1161b263 releaser: Prepare repository for 0.111.0-DEV
[ci skip]
2023-01-17 12:28:17 +00:00
hugoreleaser e32a493b78 releaser: Bump versions for release of 0.110.0
[ci skip]
2023-01-17 12:16:09 +00:00
Bjørn Erik Pedersen 19e9605629 dos: Regen CLI docs 2023-01-17 12:52:53 +01:00
Bjørn Erik Pedersen 80e8bd3b72 docs: Regen docshelper 2023-01-17 12:52:08 +01:00
Bjørn Erik Pedersen b661132e0a Merge commit 'ef6f101e75256c3bb88a6f1f3b5c1273bf8d7382' 2023-01-17 12:51:46 +01:00
Bjørn Erik Pedersen ef6f101e75 Squashed 'docs/' changes from 2c0125b52..1214f6ffb
1214f6ffb Document cleanDestinationDir configuration setting
27ca65463 Clarify .Page.Param method (#1953)
3fa1792d2 Document the usage of `hardWrap` option for markdown rendering (#1951)
8b5afdfb4 Update theme
207e7f0a0 fix docs for getting remote font
1f7094b9e Correct typo
f9d6445c4 Added missing `/` in URL generator (#1946)
3a22ee7d6 Remove translations
b3b900f3f Update introduction.md
aca440052 rm Forestry - facing end-of-life shortly (#1944)
af0014e14 Update scss-sass.md
1c43bbbc9 Merge branch 'docs/goworkspace'
d034175ca netlify: Hugo 0.109.0
d3a6a5c3f Merge branch 'tempv0.109.0'
e033dbead docs: Regen docs helper JSON
452bf675c resource/page: Add Page.Ancestors
12edd7363 Add some docs for workspaces
8f0fcba6b Add HUGO_PUBLISHDIR to the Node environment
4e66d98ad Update theme
808aee6f6 config: Update to ga v4
1de2bc5a9 config: Update to ga v4
ddb5fd6b0 Merge commit '41bc6f702aa54200530efbf4267e5c823df3028d'
54c54bf76 modules: Adjust watch logic vs workspace use definitions

git-subtree-dir: docs
git-subtree-split: 1214f6ffbf
2023-01-17 12:51:01 +01:00
Bjørn Erik Pedersen d595419031 related: Handly []any
See #10624
2023-01-17 12:50:26 +01:00
Bjørn Erik Pedersen 671f64b2eb Fix permalinks issue with repeated sections
Fixes #10377
2023-01-17 12:49:16 +01:00
Bjørn Erik Pedersen 2fb40ece5d tpl/strings: Add findRESubmatch
Fixes #10594
2023-01-17 12:14:11 +01:00
Bjørn Erik Pedersen c6b3887696 config/security: Add O\w+ (e.g. GOROOT) to the default allowed list
Fixes #10429
2023-01-17 10:52:51 +01:00
Bjørn Erik Pedersen 21af5b359f Preserve front matter slice value types (e.g. int)
Fixes #10624
2023-01-17 08:58:05 +01:00
Bjørn Erik Pedersen f38a2fbd2e Make hugo.toml the new config.toml
Both will of course work, but hugo.toml will win if both are set.

We should have done this a long time ago, of course, but the reason I'm picking this up now is that my VS Code setup by default picks up some
JSON config schema from some random other software which also names its config files config.toml.

Fixes #8979
2023-01-16 15:34:16 +01:00
Bjørn Erik Pedersen 6a579ebac3 Add fill HTTP Response info into .Data in resources.GetRemote
See #10604
2023-01-16 14:44:15 +01:00
Bjørn Erik Pedersen f13531e608 Fix HEAD method in resources.GetRemote
Fixes #10604
2023-01-16 14:44:15 +01:00
Bjørn Erik Pedersen b5d485060f Fix order when reading custom headers in resources.GetRemote
Fixes #10616
2023-01-16 14:44:15 +01:00
Bjørn Erik Pedersen 6e9fa9e0fd deps: Upgrade github.com/evanw/esbuild v0.15.18 => v0.17.0
Also add a test to make sure legal comments are preserved in JS bundles.

Closes #10536
2023-01-16 14:41:13 +01:00
Bjørn Erik Pedersen c4f3a46ce6 Update README.md 2023-01-09 14:13:30 +01:00
Bjørn Erik Pedersen fbc3e08c64 resource: Fix Go Doc vs .Data.Integrity 2023-01-07 10:58:34 +01:00
Bjørn Erik Pedersen e402d91ee1 Misc doc, code refactoring to improve documentation 2023-01-04 18:01:26 +01:00
Bjørn Erik Pedersen 3c51625c71 Make readFile return nil when file not found (note)
Fixes #9620
2023-01-04 18:01:26 +01:00
Omar Kohl dd6d0a6de1 Remove reference to Goreleaser in code comment
Hugo no longer uses the Goreleaser tool.
2023-01-03 20:25:44 +01:00
acclassic f95fd57aac tpl/compare: Sort special float values as string
When sorting strings a worng order is returned. This happens because the strings are first converted
to floating values to check whether or not they should be sorted as
floating values. When an error is returned the strings will be
handled as string literals.
No error will be returned when parsing Inf, Infinity or NaN (case insensitive) because they
will be coverted to special floating point values and therefore are
legal float values.
Now we check if the returned converted values are special floating
values and treat them as string literals.

Fixes #10389
2023-01-02 17:35:08 +01:00
Bjørn Erik Pedersen e754d5cb3e tpl/diagrams: Move Goat to its own file 2022-12-25 14:02:22 +01:00
Bjørn Erik Pedersen 002cd52807 Update CONTRIBUTING.md 2022-12-24 15:53:27 +01:00
Bjørn Erik Pedersen a76c405d40 Update CONTRIBUTING.md 2022-12-24 15:49:00 +01:00
Bjørn Erik Pedersen c0a03a2a39 Update README.md 2022-12-24 15:48:45 +01:00
hugoreleaser e127d3e5c6 releaser: Prepare repository for 0.110.0-DEV
[ci skip]
2022-12-23 10:50:52 +00:00
hugoreleaser 47b12b83e6 releaser: Bump versions for release of 0.109.0
[ci skip]
2022-12-23 10:38:11 +00:00
Bjørn Erik Pedersen 180dfeba03 Adjust "you need the extended version" error message 2022-12-23 10:33:49 +01:00
Bjørn Erik Pedersen 10bb29d7f3 docs: Regen docs helper JSON 2022-12-23 10:15:48 +01:00
Bjørn Erik Pedersen eb0c8f9d02 resource/page: Slight adjustment of Page.Ancestors
Fixes #10567
2022-12-23 10:14:53 +01:00
Septs 3a216186b2 resource/page: Add Page.Ancestors
Fixes #10567
2022-12-23 10:14:53 +01:00
dependabot[bot] 7874b96815 build(deps): bump golang.org/x/tools from 0.3.0 to 0.4.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.3.0 to 0.4.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.3.0...v0.4.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-23 09:26:10 +01:00
Joe Mooring 71832328f8 Annotate test assertions 2022-12-22 18:53:12 +01:00
Joe Mooring 37ab1cf12a hugolib: Exclude non-linkable pages from translations map
Fixes #9073
2022-12-22 18:53:12 +01:00
Bjørn Erik Pedersen 59af05cabc Add HUGO_PUBLISHDIR to the Node environment
So you can do  `process.env.HUGO_PUBLISHDIR` in your `postcss.config.js` to figure out where Hugo publishes
its files.

Note that the value will always be an absolute file path and will point to a directory on disk even when running `hugo server` in memory mode.

If you write to this folder from PostCSS when running the server, you could run the server with one of these flags:

```
hugo server --renderToDisk
hugo server --renderStaticToDisk
```

Fixes #10554
2022-12-22 12:43:50 +01:00
Joe Mooring 4989da653d Revert "tpl/tplimpl: Use https in sitemap templates"
This reverts commit 3fd0b78498.
2022-12-22 08:53:58 +01:00
Bjørn Erik Pedersen cd1ed563a8 tpl: Improve template funcs GoDoc 2022-12-21 15:33:02 +01:00
Bjørn Erik Pedersen aa2c724195 tpl/resources: Fix data race in ToCSS
Fixes #10542
2022-12-21 12:26:42 +01:00
Bjørn Erik Pedersen effa6a4226 tocss: Add some more test cases
See #10555
2022-12-21 11:21:07 +01:00
Septs 5d5f0a2371 tocss: Fix unquote case with double quotes
See #10555
2022-12-21 11:21:07 +01:00
Bjørn Erik Pedersen d20d2651ea Allow "fast render mode" even if --disableLiveReload is set
Fixes #10561
2022-12-21 11:02:21 +01:00
Bjørn Erik Pedersen 41a080b268 tocss: Add vars option
This commit adds a new `vars` option to both the Sass transpilers (Dart Sass and Libsass).

This means that you can pass a map with key/value pairs to the transpiler:

```handlebars
{{ $vars := dict "$color1" "blue" "$color2" "green" "$font_size" "24px" }}
{{ $cssOpts := (dict "transpiler" "dartsass" "outputStyle" "compressed" "vars" $vars ) }}
{{ $r := resources.Get "scss/main.scss" |  toCSS $cssOpts }}
```

And the the variables will be available in the `hugo:vars` namespace. Example usage for Dart Sass:

```scss
@use "hugo:vars" as v;

p {
    color: v.$color1;
    font-size: v.$font_size;
}
```

Note that Libsass does not support the `use` keyword, so you need to `import` them as global variables:

```scss
@import "hugo:vars";

p {
    color: $color1;
    font-size: $font_size;
}
```

Hugo will:

* Add a missing leading `$` for the variable names if needed.
* Wrap the values in `unquote('VALUE')` (Sass built-in) to get proper handling of identifiers vs other strings.

This means that you can pull variables directly from e.g. the site config:

```toml
[params]
[params.sassvars]
color1 = "blue"
color2 = "green"
font_size = "24px"
image = "images/hero.jpg"
```

```handlebars
{{ $vars := site.Params.sassvars}}
{{ $cssOpts := (dict "transpiler" "dartsass" "outputStyle" "compressed" "vars" $vars ) }}
{{ $r := resources.Get "scss/main.scss" |  toCSS $cssOpts }}
```

Fixes #10555
2022-12-20 19:36:30 +01:00
Bjørn Erik Pedersen 41bc6f702a Squashed 'docs/' changes from 2201ac0e5..2c0125b52
2c0125b52 Remove .Site.Author
2cf8841b3 Update partialCached.md (#1924)
385487191 Update data-templates.md (#1926)
ce207e141 Remove redundant markdown and fix a few typos (#1936)
3687c2953 Make heading id linkable, take 2
45c79bea7 Make heading id linkable
b22079344 Delete duplicates the lines 557-569 and 570-582. (#1934)
0a90dc122 Rework the taxonomy variables page (#1935)
7f8979c50 Update theme
26e682a3a Update multilingual.md
d40e7693f Update postcss.md
375d75c01 Update postcss npm instructions (#1931)
63020094a Emphasize Window shell selection (#1930)
56824be2c Update configuration.md
b7b8f16b3 Docu 'Theme components': minor fix (#1929)
09dc81a05 Remove Docker from BSD page (#1927)
205fea204 netlify: Hugo 0.108.0
6abe49c28 Merge commit 'da670c38ee63a7fef25e2b9f42519232055b60dc'
12b59a4c5 docs: Add basic doc for wrapStandAloneImageWithinParagraph etc.
ba07bd970 dartsass: Add sourceMapIncludeSources option

git-subtree-dir: docs
git-subtree-split: 2c0125b529
2022-12-20 11:04:41 +01:00
Bjørn Erik Pedersen 9a215d6950 Merge commit '41bc6f702aa54200530efbf4267e5c823df3028d' 2022-12-20 11:04:41 +01:00
Bjørn Erik Pedersen eda1e720cd modules: Improve "module workspace" not found error 2022-12-20 09:06:38 +01:00
Bjørn Erik Pedersen 330fa89411 modules: Adjust watch logic vs workspace use definitions 2022-12-19 20:17:33 +01:00
Bjørn Erik Pedersen 6db527483d Add any configured Go Workspace file to the config watcher
Fixes #10556
2022-12-19 20:17:33 +01:00
Bjørn Erik Pedersen 0d4b17d4c0 modules: Make the module.workspace=off as default (note)
Also, resolve any workspace file relative to the workingDir.

Fixes #10553
2022-12-19 20:17:33 +01:00
Bjørn Erik Pedersen 3afaca7584 release: Add a note section in release notes 2022-12-19 13:13:38 +01:00
Joe Mooring 2d217cba51 helpers: Allow at signs in UnicodeSanitize (note)
Closes #10548
2022-12-19 09:58:56 +01:00
Bjørn Erik Pedersen 17055d1fa7 parser/metadecoders: Remove superflous cast in test 2022-12-14 20:10:17 +01:00
Bjørn Erik Pedersen 2a81a49499 parser/metadecoders: Simplify nil check in Unmarshal 2022-12-14 18:03:47 +01:00
AcClassic e30d711c29 parser/metadecoders: Add empty /data JSON file as empty map
When fetching JSON files from the /data folder that are empty they will
be added as empty map[string]any. This makes sure that no empty JSON
file causes the site to crash because of a failed unmarshal. This
happens because empty is not a valid JSON string. It is therefore
important to check the lenght of the data before passing it to the JSON
unmarshal function.

Fixes #8601
2022-12-14 18:03:47 +01:00
Bjørn Erik Pedersen ad2059878a Also consider wrapped errors when checking for file IsNotExist errors
Fixes #10534
2022-12-14 13:51:06 +01:00
Bjørn Erik Pedersen 87e898a17a tpl/openapi3: Wrap *kopenapi3.T
To make it easier to document.
2022-12-14 12:29:40 +01:00
Bjørn Erik Pedersen b54de1bd9b resources/js: Fix some import discrepancies between Hugo and ESBuild
This fixes the cases where

```js
import 'imp2/index.js';
import 'imp3/foo.js';
```

And these files lives in `assets` as:

```
imp2/index.ts
imp3/foo.ts
```

Fixes #10527
2022-12-13 18:58:17 +01:00
Bjørn Erik Pedersen c9354d5463 github: Update to Dart Sass 1.56.2 2022-12-13 16:58:33 +01:00
Bjørn Erik Pedersen d894269855 github: Use ruby/setup-ruby
Closes #10517
2022-12-11 19:43:46 +01:00
Joe Mooring 3fd0b78498 tpl/tplimpl: Use https in sitemap templates
Closes #10515
2022-12-11 18:53:30 +01:00
Francesco La Camera e0e63f35e4 parser/metadecoders: Fix spelling 2022-12-10 19:37:47 +01:00
hugoreleaser cc574ef120 releaser: Prepare repository for 0.109.0-DEV
[ci skip]
2022-12-06 13:50:51 +00:00
hugoreleaser a0d64a46e3 releaser: Bump versions for release of 0.108.0
[ci skip]
2022-12-06 13:37:56 +00:00
Bjørn Erik Pedersen f97544a830 Make the hugo env non verbose output slightly more verbose
This is how it may look like with a extended build:

```
hugo v0.107.0-6445b1e9ff963b07c55d9d69cb9abef8ef21fc5d+extended darwin/arm64 BuildDate=2022-12-06T11:21:50Z
GOOS="darwin"
GOARCH="arm64"
GOVERSION="go1.19.3"
github.com/sass/libsass="3.6.5"
github.com/webmproject/libwebp="v1.2.4"
github.com/sass/dart-sass-embedded/protocol="1.1.0"
github.com/sass/dart-sass-embedded/compiler="1.56.1"
github.com/sass/dart-sass-embedded/implementation="1.56.1"
```
2022-12-06 13:37:08 +01:00
Bjørn Erik Pedersen d8efe085ca Add dart-sass-embedded version info to hugo env -v
```
~ ❯❯❯ hugo env -v | grep dart
github.com/bep/godartsass="v0.16.0"
github.com/sass/dart-sass-embedded/compiler="1.56.1"
github.com/sass/dart-sass-embedded/implementation="1.56.1"
github.com/sass/dart-sass-embedded/protocol="1.1.0"
```
2022-12-06 13:37:08 +01:00
Bjørn Erik Pedersen f5b5b71c60 deps: Upgrade github.com/bep/godartsass v0.15.0 => v0.16.0 2022-12-06 13:37:08 +01:00
Razon Yang b82b547acb tpl/embedded: Make Open Graph's series optional 2022-12-06 10:48:34 +01:00
Bjørn Erik Pedersen da670c38ee Squashed 'docs/' changes from 4c1309cdf..2201ac0e5
2201ac0e5 Update theme

git-subtree-dir: docs
git-subtree-split: 2201ac0e5f
2022-12-06 10:42:36 +01:00
Bjørn Erik Pedersen c9f2fa2663 Merge commit 'da670c38ee63a7fef25e2b9f42519232055b60dc' 2022-12-06 10:42:36 +01:00
Bjørn Erik Pedersen 5067775a6f common/hugio: Fix multiWriteCloser.Close
Fixes #10505
2022-12-06 10:32:33 +01:00
dependabot[bot] 50549c867c build(deps): bump github.com/getkin/kin-openapi from 0.109.0 to 0.110.0
Bumps [github.com/getkin/kin-openapi](https://github.com/getkin/kin-openapi) from 0.109.0 to 0.110.0.
- [Release notes](https://github.com/getkin/kin-openapi/releases)
- [Commits](https://github.com/getkin/kin-openapi/compare/v0.109.0...v0.110.0)

---
updated-dependencies:
- dependency-name: github.com/getkin/kin-openapi
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-06 10:22:14 +01:00
Bjørn Erik Pedersen de9c5542ca docs: Add basic doc for wrapStandAloneImageWithinParagraph etc.
See #10492
2022-12-05 16:50:47 +01:00
Bjørn Erik Pedersen e93138dfdb dartsass: Add sourceMapIncludeSources option 2022-12-05 16:36:45 +01:00
Bjørn Erik Pedersen 7d16c3c0c1 github: Update Dart Sass Embedded to 1.56.1 2022-12-05 16:36:45 +01:00
Bjørn Erik Pedersen 63126c6359 markup/goldmark: Add removeSurroundingParagraph for Markdown images
* Removes any surrounding paragraph nodes
* And transfers any attributes from the surrounding paragraph down to the image node
* Adds IsBlock and Ordinal (zero based) field to the image context passed to the image render hooks

IsBlock is set to true if `wrapStandAloneImageWithinParagraph = false` and  the image's parent node has only one child.

Closes #8362
Fixes #10492
Fixes #10494
Fixes #10501
2022-12-05 15:00:47 +01:00
dependabot[bot] 535ea8cc9b build(deps): bump github.com/evanw/esbuild from 0.15.16 to 0.15.18
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.15.16 to 0.15.18.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/master/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.15.16...v0.15.18)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-05 10:35:54 +01:00
dependabot[bot] 8bbec426cd build(deps): bump golang.org/x/text from 0.4.0 to 0.5.0
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.4.0 to 0.5.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.4.0...v0.5.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-05 10:34:54 +01:00
dependabot[bot] 0bfa293dcd build(deps): bump github.com/evanw/esbuild from 0.15.15 to 0.15.16
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.15.15 to 0.15.16.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/master/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.15.15...v0.15.16)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-04 15:04:05 +01:00
Joe Mooring 0b976d2b40 tpl/tplimpl: Allow alternate comment syntax
Allow alternate comment syntax before block definitions:

{{/* foo */}}
{{- /* foo */}}
{{- /* foo */ -}}

Fixes #10495
2022-12-04 09:25:51 +01:00
Anton Sattarov a49e51fd0b resources: Increase timeout for http.Client
Increase timeout for http.Client in resources.GetRemote  from 10 second to 1 minute

Fixes #10478
2022-12-03 15:47:47 +01:00
Bjørn Erik Pedersen d373774cbe tpl/collections: Fix some index cases where the indices given is a slice and be more lenient with nil inputs
See adjusted tests for detail.

Fixes #10489
2022-12-03 11:54:33 +01:00
Bjørn Erik Pedersen 7d5e3ab8a8 tpl: Misco GoDoc improvements 2022-12-03 11:54:33 +01:00
septs dc44bca963 config/security: Add CI env var to whitelist 2022-12-02 12:13:34 +01:00
Bjørn Erik Pedersen da16527896 Squashed 'docs/' changes from 32cb8785e..4c1309cdf
4c1309cdf Fix broken link to front matter page (#1923)
8181fff20 Add footnote to Quick Start guide for Win users (#1922)
4fd934f61 Fix abslangurl.md (#1919)
bf2c45617 Quick start guide: fix broken links (#1915)
201b568df Update theme
874db199d Hide the news stripe on front page for now
aaf59e3e2 netlify: Hugo 0.107.0
79654c301 Merge branch 'tempv107'
8345e0347 docs: Regen docs helper
b5b4f15f9 Add assets directory to directory structure diagram (#1917)
76dd3a82a Follow-up: improve function signatures (#1914) (#1916)
656dc72ba Improving function signatures (#1914)
b715e8407 Explain how to create multilingual content (#1912)
8500ee417 Fix typo in Quick Start guide
ce60bb572 Add docu for undocumented functions (#1907)
372bf5e88 Improve Quick Start tutorial
71e81ec5f Remove asciicast from site
1e56c653f Update hosting-on-cloudflare-pages.md (#1903)
1686f60b1 Adding documentation page for function 'strings.FirstUpper' (#1901)
d533f7c9c Fixing typos (#1900)
2563eee45 Update multilingual.md (#1899)
2173ed799 Update theme
5591b8875 Update urls.md (#1897)
4a88890b5 netlify: Hugo 0.106.0
512879fdd docs: Regen CLI docs
f74b1d87d docs: Regenerate docs helper
9feacb48f Merge commit '00c4484c7092181729f6f470805bc7d72e8ad17b'

git-subtree-dir: docs
git-subtree-split: 4c1309cdfe
2022-12-02 09:19:23 +01:00
Bjørn Erik Pedersen ef518485ce Merge commit 'da16527896d3087585c5e758083ea498dcabc2c3' 2022-12-02 09:19:23 +01:00
Bjørn Erik Pedersen 83080df611 deps: Upgrade github.com/bep/godartsass v0.14.0 => v0.15.0 2022-12-02 09:08:22 +01:00
Joe Mooring b8d5c378ba tpl: Use consistent delimiter spacing in examples 2022-11-25 09:54:35 +01:00
Bjørn Erik Pedersen 75f782a5a7 docs: Regen docs helper 2022-11-24 15:17:22 +01:00
hugoreleaser 2221b5b30a releaser: Bump versions for release of 0.107.0
[ci skip]
2022-11-24 13:59:45 +00:00
dependabot[bot] 6a004b8d94 build(deps): bump github.com/getkin/kin-openapi from 0.108.0 to 0.109.0
Bumps [github.com/getkin/kin-openapi](https://github.com/getkin/kin-openapi) from 0.108.0 to 0.109.0.
- [Release notes](https://github.com/getkin/kin-openapi/releases)
- [Commits](https://github.com/getkin/kin-openapi/compare/v0.108.0...v0.109.0)

---
updated-dependencies:
- dependency-name: github.com/getkin/kin-openapi
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-24 13:21:23 +01:00
dependabot[bot] 0923622421 build(deps): bump github.com/evanw/esbuild from 0.15.14 to 0.15.15
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.15.14 to 0.15.15.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/master/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.15.14...v0.15.15)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-24 13:20:50 +01:00
Bjørn Erik Pedersen 7855b47f07 Add a cache for lexers.Get
```
name                            old time/op    new time/op    delta
Codeblocks/Default-10              152ms ±11%      12ms ± 1%  -92.44%  (p=0.029 n=4+4)
Codeblocks/Hook_no_higlight-10     142ms ± 0%       7ms ± 0%  -95.36%  (p=0.029 n=4+4)

name                            old alloc/op   new alloc/op   delta
Codeblocks/Default-10             11.9MB ± 0%    11.7MB ± 0%   -1.59%  (p=0.029 n=4+4)
Codeblocks/Hook_no_higlight-10    4.62MB ± 1%    4.43MB ± 0%   -4.08%  (p=0.029 n=4+4)

name                            old allocs/op  new allocs/op  delta
Codeblocks/Default-10               209k ± 0%      209k ± 0%   -0.03%  (p=0.029 n=4+4)
Codeblocks/Hook_no_higlight-10     68.4k ± 0%     68.3k ± 0%   -0.06%  (p=0.029 n=4+4)

```
2022-11-24 13:18:33 +01:00
Bjørn Erik Pedersen 34d1150d92 markup/goldmark: Improve benchmark 2022-11-24 12:04:44 +01:00
Joe Mooring 85e2ac1a44 commands: Create assets directory with new site
Closes #10460
2022-11-21 09:42:40 +01:00
dependabot[bot] 74776726df build(deps): bump github.com/frankban/quicktest from 1.14.3 to 1.14.4
Bumps [github.com/frankban/quicktest](https://github.com/frankban/quicktest) from 1.14.3 to 1.14.4.
- [Release notes](https://github.com/frankban/quicktest/releases)
- [Commits](https://github.com/frankban/quicktest/compare/v1.14.3...v1.14.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-19 17:19:15 +01:00
dependabot[bot] 63f7f0ff5a build(deps): bump golang.org/x/tools from 0.2.0 to 0.3.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.2.0 to 0.3.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.2.0...v0.3.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-19 16:33:23 +01:00
Bjørn Erik Pedersen bcb62d8911 deps: Upgrade github.com/alecthomas/chroma/v2 v2.4.0 2022-11-19 11:55:30 +01:00
Joe Mooring 00fe7e0408 hugo/parser: Fix shortcode boolean param parsing
Fixes #10451
2022-11-18 09:34:10 +01:00
hugoreleaser df85cb9ae2 releaser: Prepare repository for 0.107.0-DEV
[ci skip]
2022-11-17 18:46:38 +00:00
hugoreleaser e08ce30fe4 releaser: Bump versions for release of 0.106.0
[ci skip]
2022-11-17 18:33:40 +00:00
Bjørn Erik Pedersen a99fed4852 resources/tpl: Add a test for resources.Get
Updates #10101
2022-11-17 18:09:54 +01:00
shifterbit db945a6ed2 tlp/resources: resources.Get returns nil when given empty string 2022-11-17 18:09:54 +01:00
dependabot[bot] bafb389b38 build(deps): bump github.com/pelletier/go-toml/v2 from 2.0.4 to 2.0.6
Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.0.4 to 2.0.6.
- [Release notes](https://github.com/pelletier/go-toml/releases)
- [Changelog](https://github.com/pelletier/go-toml/blob/v2/.goreleaser.yaml)
- [Commits](https://github.com/pelletier/go-toml/compare/v2.0.4...v2.0.6)

---
updated-dependencies:
- dependency-name: github.com/pelletier/go-toml/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-17 17:07:40 +01:00
Bjørn Erik Pedersen 0a019a1a59 docs: Regen CLI docs 2022-11-17 16:19:22 +01:00
Bjørn Erik Pedersen 9f7fb0a73b docs: Regenerate docs helper 2022-11-17 16:16:53 +01:00
Bjørn Erik Pedersen f04cc581e1 Merge commit '00c4484c7092181729f6f470805bc7d72e8ad17b' 2022-11-17 16:16:19 +01:00
Bjørn Erik Pedersen 00c4484c70 Squashed 'docs/' changes from 392668f4f..32cb8785e
32cb8785e Fix page weights in content management section (#1896)
11977b96f Make relURL and related functions consistent (#1895)
f12180207 Clarify github deployment (#1894)
958877789 Remove remaining references to Highlight.js (#1893)
fc487d263 Minor edit to taxonomy page
3b6a224b2 Update theme
b28553b62 Change "flavor" to "edition" when referring to builds (#1892)
660e7581c Replaced sudo in OpenBSD with doas (#1891)
e3fcdea10 fix a few minor grammatical issues on Firebase docs (#1889)
e4c8b30eb update Static Web Apps docs (#1890)
da2197c9e Update hosting-on-firebase.md (#1347)
5f2a0c271 Adding deployment guide for Azure Static Web Apps (#1456)
5aaf570cd add Azure Static Web App config to 404 template
35fc54362 add Azure Static Web App config to 404 template
d48f67ba1 Update 01-flavors.md
11debae8d Cleaned Use of ref and relref section, added refs of index.md and _in… (#1744)
b77604078 docs: Add link to menu entry variables (#1827)
0fa8a6bf0 Misc copy edits (#1887)
c27b545ac Improve explanation of safeHTMLAttr's function (#1503)
b04a4b32e Make CLI command summaries meaningful (#1886)
dbf00a81f Fix a typo in diagrams documentation (#1885)
11f884327 docs: Clarify how to remove draft/future/expired content (#1831)
6dc9e9860 Improve complement function (#1884)
56448a51a Remove erroneous sourcemap desc (#1883)
a0d0d2829 Merge branch 'divinerites-patch-1'
10f20cb5e Add a plausible-hugo theme component
9f1413eb5 Minor edits to showcase example
7d78420db fix broken link to Isso Comments
925cb291f Make directory tree consistent with other examples
300fff092 Add link to security policy from getenv.md (#1746)
7b4c517a6 Fix docs menu weights
ce35775e0 Update faq.md (#1763)
f3fb791a4 Remove dated new-in flags (#1879)
b6c634629 Remove deprecated templating langs (#1880)
1b25ca34f Update the findRE and replaceRE functions (#1881)
28757ec73 Add Alora Labs website to showcase (#1494)
e3c4bc4e7 Remove unimplemented "ugly" property
86afd84ff Update editors.md (#1878)
44c093911 Add urlquery function docs (#1633)
16a8c3548 Update links to installation page (#1876)
9e357f078 Add missing sections to BSD installation page (#1875)
1b1291634 Promote "Installation" to a section
9dd51235b Add detail to description of .Plain page variable (#1870)
d333d0287 Minor markdown linting fix and URL updates (#1873)
d57c8aa50 Remove extraneous apostrophe (#1871)
8c25cfc5c Update index.md
09fea41e0 Add lang to fenced code block
35b904798 Add small documentation about .Site.Social.twitter variable (#1854)
672042f89 Consolidate site configuration
dfd4dd873 Add help.ampio.com showcase. (#1863)
e8d0e7bdf Include link to internal templates code (#1794)
7db6f0c01 Add example to split function (#1867)
be87dba80 Clarify split function docs (#1792)
a079193f1 Fix typo on data templates page
b234c70ee Fix data templates page (#1855)
074232b45 Update front-matter.md (#1856)
711c8fa80 Added missing default value (#1862)
034762882 Fixed some grammar issues and typos (#1865)
764574a4d Fix spelling error
2698f2d44 update URLs to prevent redirects (#1864)
68f05fdc8 Fenced code blocks should have a language specified (#1861)
24393315b GitHub Workflows security hardening (#1859)
3eeee13bf Markdown formatting: Add Fenced code block languages (#1858)
e152cdf1f netlify: Hugo 0.105.0
4c7fc9f7e Merge branch 'tempv0.105.0'
d16710afc Change anchor reference to use relref function calls (#1853)
f52af8e4a tpl/encoding: Add noHTMLEscape option to jsonify
eca0046c4 Update hosting-on-keycdn docs (#1852)
ffbe17a48 Add note for rsync deploy command (#1415)
c482133f1 docs: Update quick start to clarify the need of extended version (#1828)
1e3b33804 use correct URL for Google Search console verification (#1851)
dac034f63 Markdown and formatting fixes (#1850)
43f177e3c Fix LiveReload in quick-start (#1739)
f78deaa5f Add link for ''Hugo Shortcode Syntax Highlighting' VS Code extension (#1765)
08087ecd7 Remove some hidden pages (#1848)
b6cb5ae48 Markdown linting fixes (#1846)
527ec5941 Update hugo.md (#1742)
83e8f2168 Clarify that a shortcode with .Inner must be closed (#1785)
4193f4445 Add Super Linter GitHub Action (#1845)
fd91bfe1a Formatting and grammar fixes (#1844)
ab5a49c49 Create codeql-analysis GitHub Action (#1812)
63b3e082e Add tutorial on using fusejs to search examples (#1756)
54c253ab0 Note that Google Universal Analytics are deprecated (#1770)
385fa77c6 Update articles.toml (#1840)
5e336bd26 Replace awkward wording (ESL?) (#1842)
2446ad349 Added Introduction to Hugo tutorial/video series (#1736)
7b21b2e76 Don't use self-closing generator tag
ef73712ff Image processing. available methods: add method 'Colors' (#1837)
018f83bbe [comment platform] - add new alternative (#1751)
5636c208b Grammar and spelling fixes (#1836)
3f2e26f77 Change link of repojacking vulnerable link - JekyllToHugo (#1834)
301379fc3 fix: use shorter image URL to make it easier to read (#1835)
de5fa7b30 Update search.md to include Pagefind (#1826)
e9d72bcda Breadcrumb example: add basic accessibility (#1832)
6cffff87a netlify: Hugo 0.104.3
892360f61 Update output-formats.md
09a7a46ae Remove my defunct and little used migrator (#1824)
347434cca netlify: Hugo 0.104.2
f8c721162 Update postcss.md
c2baf7155 netlify: Hugo 0.104.1
05d1192cd Update diagrams.md (#1823)
3c43a8bbe netlify: Hugo 0.104.0
57973b334 Merge branch 'tempv0.104.0'
da775a36d docs: Regen docs helper
ae48b5901 docs: Regenerate CLI docs
af4a823b1 resources/images: Add $image.Colors
8e3f9ca64 Remove outdated IE conditional comments example (#1821)
d1a84701b fix typo in template introduction (#1820)
c0c7339e0 Update internal.md
17aefc515 Remove the recommendation about where to put the GA tempalte
263297236 Adjust GA template instructions
1cc265d99 Update the GA template usage section
e11968338 config/security: Allow proxy variables in subcommands
9218ab993 netlify: Hugo 0.103.1
0b0e890d1 Update markdownify and RenderString documentation (#1818)
50f5d4776 Fix internal link (#1817)
6beb443c5 netlify: Hugo 0.103.0
14b5af248 Merge branch 'tempv0.103.0'
548e7aa62 server: Add 404 support
3a20aa0ba Update theme

git-subtree-dir: docs
git-subtree-split: 32cb8785ea
2022-11-17 16:14:29 +01:00
dependabot[bot] cdd83bf3c8 build(deps): bump github.com/evanw/esbuild from 0.15.13 to 0.15.14
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.15.13 to 0.15.14.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/master/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.15.13...v0.15.14)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-16 19:15:58 +01:00
Bjørn Erik Pedersen e00220a06a deps: Update the libweb version string 2022-11-15 18:21:08 +01:00
Bjørn Erik Pedersen a662ddae13 deps: Upgrade github.com/bep/gowebp v0.1.0 => v0.2.0 2022-11-15 18:19:35 +01:00
Vignesh Mani 13adf3e028 readme: Update ToC 2022-11-15 18:19:16 +01:00
dependabot[bot] fe08d35f24 build(deps): bump github.com/yuin/goldmark from 1.5.2 to 1.5.3
Bumps [github.com/yuin/goldmark](https://github.com/yuin/goldmark) from 1.5.2 to 1.5.3.
- [Release notes](https://github.com/yuin/goldmark/releases)
- [Commits](https://github.com/yuin/goldmark/compare/v1.5.2...v1.5.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-15 11:48:20 +01:00
dependabot[bot] 4b675ddd4a build(deps): bump github.com/spf13/afero from 1.9.2 to 1.9.3
Bumps [github.com/spf13/afero](https://github.com/spf13/afero) from 1.9.2 to 1.9.3.
- [Release notes](https://github.com/spf13/afero/releases)
- [Commits](https://github.com/spf13/afero/compare/v1.9.2...v1.9.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-15 10:01:23 +01:00
dependabot[bot] 24eaa290cf build(deps): bump github.com/getkin/kin-openapi from 0.107.0 to 0.108.0
Bumps [github.com/getkin/kin-openapi](https://github.com/getkin/kin-openapi) from 0.107.0 to 0.108.0.
- [Release notes](https://github.com/getkin/kin-openapi/releases)
- [Commits](https://github.com/getkin/kin-openapi/compare/v0.107.0...v0.108.0)

---
updated-dependencies:
- dependency-name: github.com/getkin/kin-openapi
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-15 10:00:57 +01:00
Bjørn Erik Pedersen f6ab9553f4 tpl/internal: Sync go_templates
Closes #10411
2022-11-14 22:31:50 +01:00
dependabot[bot] 58a98c7758 build(deps): bump github.com/clbanning/mxj/v2 from 2.5.6 to 2.5.7
Bumps [github.com/clbanning/mxj/v2](https://github.com/clbanning/mxj) from 2.5.6 to 2.5.7.
- [Release notes](https://github.com/clbanning/mxj/releases)
- [Commits](https://github.com/clbanning/mxj/compare/v2.5.6...2.5.7)

---
updated-dependencies:
- dependency-name: github.com/clbanning/mxj/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-14 22:20:38 +01:00
dependabot[bot] 900904fd11 build(deps): bump golang.org/x/net from 0.1.0 to 0.2.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.1.0 to 0.2.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/compare/v0.1.0...v0.2.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-14 19:08:03 +01:00
dependabot[bot] 24eca0cbe9 build(deps): bump github.com/evanw/esbuild from 0.15.12 to 0.15.13
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.15.12 to 0.15.13.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/master/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.15.12...v0.15.13)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-14 19:07:26 +01:00
Vignesh Mani 60e0e2c1d9 Add Go 1.16+ install method to README
Changed installation method to reflect Go 1.16+, as cloning the repo and doing a go install achieves the same process.

Also changed "Advanced Installation" to "Using the Go toolchain"
2022-11-10 17:36:31 +01:00
Bjørn Erik Pedersen 52ea07d2eb Fix taxonomy weight sort regression
Fixes #10406
2022-11-01 18:45:34 +01:00
hugoreleaser 77fc74a5b2 releaser: Prepare repository for 0.106.0-DEV
[ci skip]
2022-10-28 12:41:15 +00:00
hugoreleaser 0e3b42b4a9 releaser: Bump versions for release of 0.105.0
[ci skip]
2022-10-28 12:29:05 +00:00
dependabot[bot] f50585442d build(deps): bump golang.org/x/tools from 0.1.12 to 0.2.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.1.12 to 0.2.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.1.12...v0.2.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-28 14:10:21 +02:00
dependabot[bot] 2aedccc9a9 build(deps): bump github.com/getkin/kin-openapi from 0.106.0 to 0.107.0
Bumps [github.com/getkin/kin-openapi](https://github.com/getkin/kin-openapi) from 0.106.0 to 0.107.0.
- [Release notes](https://github.com/getkin/kin-openapi/releases)
- [Commits](https://github.com/getkin/kin-openapi/compare/v0.106.0...v0.107.0)

---
updated-dependencies:
- dependency-name: github.com/getkin/kin-openapi
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-28 14:09:21 +02:00
dependabot[bot] c10931404f build(deps): bump golang.org/x/text from 0.3.7 to 0.4.0
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.3.7 to 0.4.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.3.7...v0.4.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-27 12:28:57 +02:00
dependabot[bot] 4732c47d1d build(deps): bump github.com/spf13/cobra from 1.5.0 to 1.6.1
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.5.0 to 1.6.1.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.5.0...v1.6.1)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-27 12:28:37 +02:00
dependabot[bot] 62780ec8dd build(deps): bump github.com/getkin/kin-openapi from 0.103.0 to 0.106.0
Bumps [github.com/getkin/kin-openapi](https://github.com/getkin/kin-openapi) from 0.103.0 to 0.106.0.
- [Release notes](https://github.com/getkin/kin-openapi/releases)
- [Commits](https://github.com/getkin/kin-openapi/compare/v0.103.0...v0.106.0)

---
updated-dependencies:
- dependency-name: github.com/getkin/kin-openapi
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-27 12:28:21 +02:00
dependabot[bot] 351d6b062b build(deps): bump github.com/tdewolff/minify/v2 from 2.12.1 to 2.12.4
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.12.1 to 2.12.4.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.12.1...v2.12.4)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-26 13:02:40 +02:00
Bjørn Erik Pedersen 631d768be9 Revise the fix for shortcode vs output format nilpointer
We do lazy initialization and (potentially) reuse of an output format's rendered content. We do this evaluation when we
start a new rendering a new output format. There are, however, situation where these borders gets crossed (e.g.
accessing content from another output format). We have a check for this in place for most cases, but not the content
rendering of inner markdown blocks inside shortcodes. This patch applies that same logic to the newly introduced
RenderContent method (which is not available from the templates).

Fixes #10391
2022-10-26 13:00:21 +02:00
davidejones e5d2a8f6a3 Avoid nilpointer when shortcode page content output nil
Updates #10391
2022-10-26 13:00:21 +02:00
Bjørn Erik Pedersen 00ff161b67 livereload: Use text/javascript here, too 2022-10-26 12:54:50 +02:00
Bjørn Erik Pedersen 588710a7ac media: Rename application/javascript, application/typescript to text/javascript etc.
The former is deprecated.

See https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
See https://discourse.gohugo.io/t/mime-type-for-javascript-is-now-text-javascript/40813
2022-10-26 10:38:27 +02:00
dependabot[bot] ed930db2f0 build(deps): bump github.com/yuin/goldmark from 1.4.15 to 1.5.2
Bumps [github.com/yuin/goldmark](https://github.com/yuin/goldmark) from 1.4.15 to 1.5.2.
- [Release notes](https://github.com/yuin/goldmark/releases)
- [Commits](https://github.com/yuin/goldmark/compare/v1.4.15...v1.5.2)

---
updated-dependencies:
- dependency-name: github.com/yuin/goldmark
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-26 10:34:24 +02:00
dependabot[bot] 05df964816 build(deps): bump github.com/fsnotify/fsnotify from 1.5.4 to 1.6.0
Bumps [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify) from 1.5.4 to 1.6.0.
- [Release notes](https://github.com/fsnotify/fsnotify/releases)
- [Changelog](https://github.com/fsnotify/fsnotify/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fsnotify/fsnotify/compare/v1.5.4...v1.6.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-26 10:25:34 +02:00
Bjørn Erik Pedersen 20ef6dcf95 Skip flakey server tests on GitHub Action on Windows 2022-10-26 09:29:55 +02:00
dependabot[bot] 9860e0e186 build(deps): bump github.com/magefile/mage from 1.13.0 to 1.14.0
Bumps [github.com/magefile/mage](https://github.com/magefile/mage) from 1.13.0 to 1.14.0.
- [Release notes](https://github.com/magefile/mage/releases)
- [Changelog](https://github.com/magefile/mage/blob/master/.goreleaser.yml)
- [Commits](https://github.com/magefile/mage/compare/v1.13.0...v1.14.0)

---
updated-dependencies:
- dependency-name: github.com/magefile/mage
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-25 16:13:08 +02:00
Bjørn Erik Pedersen d1cd1db0e7 github: Avoid duplicate test runs 2022-10-25 16:08:41 +02:00
Bjørn Erik Pedersen 09e10110a3 tpl/encoding: Add noHTMLEscape option to jsonify 2022-10-24 17:21:42 +02:00
dependabot[bot] 2ef60dbd2d build(deps): bump github.com/evanw/esbuild from 0.15.9 to 0.15.12
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.15.9 to 0.15.12.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/master/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.15.9...v0.15.12)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-24 17:19:54 +02:00
Steffen Windoffer 6275aad9e2 Update Go and Alpine version in Dockerfile 2022-10-19 19:58:25 +02:00
JB 01ebb6e304 Don't use self-closing generator tag 2022-10-14 12:16:53 +02:00
Bjørn Erik Pedersen a066e98851 build: Update to Go 1.19.2 2022-10-11 09:26:31 +02:00
Bjørn Erik Pedersen 1fd3320dcf github: Use SHA versions 2022-10-05 16:37:37 +02:00
jongwooo 0fb2b3d143 Resolve dependency-path not found error in workflow 2022-10-05 20:39:45 +09:00
jongwooo db05232d56 Use setup-go action to cache dependencies 2022-10-05 10:20:22 +02:00
hugoreleaser 2734f956c3 releaser: Prepare repository for 0.105.0-DEV
[ci skip]
2022-10-04 14:39:43 +00:00
hugoreleaser 58b8245813 releaser: Bump versions for release of 0.104.3
[ci skip]
2022-10-04 14:25:23 +00:00
Bjørn Erik Pedersen ec57cf2c30 resources: Update golden image dithering exception list 2022-10-04 16:19:52 +02:00
Bjørn Erik Pedersen 3a9cb7b0fb resources/images: Fix 2 animated GIF resize issues
* Fix resize of animated GIF when target != GIF
* Avoid processing all GIF frames if targetFormat != GIF

Fixes #10354
2022-10-04 15:38:12 +02:00
Anthony Fok 0addb302ac server: Fix flaky TestServerPathEncodingIssues tests
Set getNumHomes: 1 to enable 567 ms or 2 s of wait for the server
to be ready in TestServerPathEncodingIssues/Unicode_paths and
TestServerPathEncodingIssues/Windows_multilingual_404.

Fixes #10332
2022-10-04 14:48:29 +02:00
Joe Mooring b002d47953 commands: Remove extraneous newline from result of convert toTOML
Fixes #10351
2022-10-04 09:28:34 +02:00
Ricardo N Feliciano e3f31352d4 config/security: Fix filename 2022-10-02 12:52:04 +02:00
hugoreleaser ec02c537ed releaser: Prepare repository for 0.105.0-DEV
[ci skip]
2022-09-29 10:47:15 +00:00
hugoreleaser 84cbe72498 releaser: Bump versions for release of 0.104.2
[ci skip]
2022-09-29 10:31:09 +00:00
Bjørn Erik Pedersen 4611b6920b Fix htimes /: operation not permitted error on config changes
Regression introduced in v0.104.1.
2022-09-29 12:27:25 +02:00
Bjørn Erik Pedersen 2171e3c9a5 Revert "Adjust a test"
Committed by mistake.
This reverts commit cac773aeff.
2022-09-27 11:42:25 +02:00
Bjørn Erik Pedersen cac773aeff Adjust a test 2022-09-27 11:41:15 +02:00
hugoreleaser 51010a69bc releaser: Prepare repository for 0.105.0-DEV
[ci skip]
2022-09-26 17:17:27 +00:00
hugoreleaser 8958b8741f releaser: Bump versions for release of 0.104.1
[ci skip]
2022-09-26 17:05:45 +00:00
Bjørn Erik Pedersen 29ccb36069 Fix /static performance regression from Hugo 0.103.0
In `v0.103.0` we added support for `resources.PostProcess` for all file types, not just HTML. We had benchmarks that said we were fine in that department, but those did not consider the static file syncing.

This fixes that by:

* Making sure that the /static syncer always gets its own file system without any checks for the post process token.
* For dynamic files (e.g. rendered HTML files) we add an additional check to make sure that we skip binary files (e.g. images)

Fixes #10328
2022-09-26 19:02:25 +02:00
hugoreleaser d8aba18e05 releaser: Prepare repository for 0.105.0-DEV
[ci skip]
2022-09-23 14:44:31 +00:00
hugoreleaser c744dbd6ed releaser: Bump versions for release of 0.104.0
[ci skip]
2022-09-23 14:32:56 +00:00
Bjørn Erik Pedersen 5c41653364 Consolidate the glob case logic
Looking at the code as a whole, we ended up with a little to much "buttons". It turns out that doing case insensitive matching (lower both pattern and strings to match) performs just fine. Or at least, it
gives the penalty to the people who uses mixed case filenames.

```
GetGlob/Default_cache-10                          10.6ns ± 2%    10.6ns ± 1%   ~     (p=0.657 n=4+4)
GetGlob/Filenames_cache,_lowercase_searchs-10     10.6ns ± 2%    10.6ns ± 0%   ~     (p=1.000 n=4+4)
GetGlob/Filenames_cache,_mixed_case_searchs-10    29.7ns ± 1%    29.6ns ± 1%   ~     (p=0.886 n=4+4)
GetGlob/GetGlob-10                                13.7ns ± 1%    13.7ns ± 0%   ~     (p=0.429 n=4+4)

name                                            old alloc/op   new alloc/op   delta
GetGlob/Default_cache-10                           0.00B          0.00B        ~     (all equal)
GetGlob/Filenames_cache,_lowercase_searchs-10      0.00B          0.00B        ~     (all equal)
GetGlob/Filenames_cache,_mixed_case_searchs-10     5.00B ± 0%     5.00B ± 0%   ~     (all equal)
GetGlob/GetGlob-10                                 0.00B          0.00B        ~     (all equal)

name                                            old allocs/op  new allocs/op  delta
GetGlob/Default_cache-10                            0.00           0.00        ~     (all equal)
GetGlob/Filenames_cache,_lowercase_searchs-10       0.00           0.00        ~     (all equal)
GetGlob/Filenames_cache,_mixed_case_searchs-10      1.00 ± 0%      1.00 ± 0%   ~     (all equal)
GetGlob/GetGlob-10
```
2022-09-23 16:19:21 +02:00
satotake 281554ee97 hugofs: Fix glob case-sensitivity bug
On Linux, `hugofs.Glob` does not hit any directories which includes
uppercase letters. (This does not happen on macOS.)

Since `resources.GetMatch/Match` uses `Glob`,

```
{{ resources.GetMatch "Foo/bar.css" }}
```

this does not match `assets/Foo/bar.css` .

On the other hand, you can get it with

```
{{ resources.Get "Foo/bar.css" }}
```
2022-09-23 13:12:57 +02:00
Bjørn Erik Pedersen f3560aa0e1 server: Fix 404 redirects on Windows
Fixes #10314
2022-09-23 13:07:56 +02:00
dependabot[bot] edf9038a92 build(deps): bump github.com/evanw/esbuild from 0.15.8 to 0.15.9
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.15.8 to 0.15.9.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/master/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.15.8...v0.15.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-23 10:56:45 +02:00
dependabot[bot] 78f49b4c0d build(deps): bump github.com/yuin/goldmark from 1.4.14 to 1.4.15
Bumps [github.com/yuin/goldmark](https://github.com/yuin/goldmark) from 1.4.14 to 1.4.15.
- [Release notes](https://github.com/yuin/goldmark/releases)
- [Commits](https://github.com/yuin/goldmark/compare/v1.4.14...v1.4.15)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-22 19:25:55 +02:00
dependabot[bot] fa4b77e7ed build(deps): bump github.com/getkin/kin-openapi from 0.100.0 to 0.103.0
Bumps [github.com/getkin/kin-openapi](https://github.com/getkin/kin-openapi) from 0.100.0 to 0.103.0.
- [Release notes](https://github.com/getkin/kin-openapi/releases)
- [Commits](https://github.com/getkin/kin-openapi/compare/v0.100.0...v0.103.0)

---
updated-dependencies:
- dependency-name: github.com/getkin/kin-openapi
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-22 19:05:44 +02:00
Bjørn Erik Pedersen 8377c3cea1 docs: Regen docs helper 2022-09-22 18:55:00 +02:00
dependabot[bot] 4d909d4768 build(deps): bump github.com/alecthomas/chroma/v2 from 2.2.0 to 2.3.0
Bumps [github.com/alecthomas/chroma/v2](https://github.com/alecthomas/chroma) from 2.2.0 to 2.3.0.
- [Release notes](https://github.com/alecthomas/chroma/releases)
- [Changelog](https://github.com/alecthomas/chroma/blob/master/.goreleaser.yml)
- [Commits](https://github.com/alecthomas/chroma/compare/v2.2.0...v2.3.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-22 18:54:27 +02:00
dependabot[bot] 4eb6d97404 build(deps): bump github.com/evanw/esbuild from 0.15.7 to 0.15.8
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.15.7 to 0.15.8.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/master/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.15.7...v0.15.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-22 18:53:12 +02:00
Bjørn Erik Pedersen 4f9cb4f344 docs: Regenerate CLI docs 2022-09-22 17:57:13 +02:00
Bjørn Erik Pedersen 0171fb2010 Run go mod tidy 2022-09-22 17:42:48 +02:00
Bjørn Erik Pedersen a4028112e3 resources/images: Add $image.Colors
Which returns the most dominant colors of an image using a simple histogram method.

Fixes #10307
2022-09-22 15:57:35 +02:00
Bjørn Erik Pedersen 08f0984f91 commands: Skip flaky test on CI 2022-09-22 11:24:42 +02:00
Mathieu Parent 86653fa38e config/security: Allow proxy variables in subcommands
In particular for go get
2022-09-19 12:37:35 +02:00
1144 changed files with 36111 additions and 28445 deletions
+5 -2
View File
@@ -4,7 +4,7 @@ parameters:
defaults: &defaults
resource_class: large
docker:
- image: bepsays/ci-hugoreleaser:1.21900.20003
- image: bepsays/ci-hugoreleaser:1.22000.20100
environment: &buildenv
GOMODCACHE: /root/project/gomodcache
version: 2
@@ -60,7 +60,7 @@ jobs:
environment:
<<: [*buildenv]
docker:
- image: bepsays/ci-hugoreleaser-linux-arm64:1.21900.20003
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22000.20100
steps:
- *restore-cache
- &attach-workspace
@@ -80,6 +80,9 @@ jobs:
- *restore-cache
- *attach-workspace
- *git-config
- run:
name: Add github.com to known hosts
command: ssh-keyscan github.com >> ~/.ssh/known_hosts
- run:
command: |
cp -a /tmp/workspace/dist1/. ./hugo/dist
+73
View File
@@ -0,0 +1,73 @@
on:
push:
branches: [ master ]
pull_request:
name: TestDartSassV1
env:
GOPROXY: https://proxy.golang.org
GO111MODULE: on
DART_SASS_VERSION: 1.62.1
DART_SASS_SHA_LINUX: 3574da75a7322a539034648b8ff84ff2cca162eb924d72b663d718cd3936f075
permissions:
contents: read
jobs:
test:
strategy:
matrix:
go-version: [1.20.x]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
- name: Install Go
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753
with:
go-version: ${{ matrix.go-version }}
check-latest: true
cache: true
cache-dependency-path: |
**/go.sum
**/go.mod
- name: Install Ruby
uses: ruby/setup-ruby@ee2113536afb7f793eed4ce60e8d3b26db912da4
with:
ruby-version: '2.7'
bundler-cache: true #
- name: Install Python
uses: actions/setup-python@3105fb18c05ddd93efea5f9e0bef7a03a6e9e7df
with:
python-version: '3.x'
- name: Install Mage
run: go install github.com/magefile/mage@v1.15.0
- name: Install asciidoctor
uses: reitzig/actions-asciidoctor@7570212ae20b63653481675fb1ff62d1073632b0
- name: Install docutils
run: |
pip install docutils
rst2html.py --version
- if: matrix.os == 'ubuntu-latest'
name: Install pandoc on Linux
run: |
sudo apt-get update -y
sudo apt-get install -y pandoc
- if: matrix.os == 'macos-latest'
run: |
brew install pandoc
- if: matrix.os == 'windows-latest'
run: |
Choco-Install -PackageName pandoc
- run: pandoc -v
- name: Install dart-sass-embedded Linux
run: |
echo "Install Dart Sass version ${DART_SASS_VERSION} ..."
curl -LJO "https://github.com/sass/dart-sass-embedded/releases/download/${DART_SASS_VERSION}/sass_embedded-${DART_SASS_VERSION}-linux-x64.tar.gz";
echo "${DART_SASS_SHA_LINUX} sass_embedded-${DART_SASS_VERSION}-linux-x64.tar.gz" | sha256sum -c;
tar -xvf "sass_embedded-${DART_SASS_VERSION}-linux-x64.tar.gz";
echo "$GITHUB_WORKSPACE/sass_embedded/" >> $GITHUB_PATH
- name: Check
run: |
dart-sass-embedded --version
mage -v check;
env:
HUGO_BUILD_TAGS: extended
+57 -36
View File
@@ -1,40 +1,49 @@
on: [push, pull_request]
on:
push:
branches: [ master ]
pull_request:
name: Test
env:
GOPROXY: https://proxy.golang.org
GO111MODULE: on
SASS_VERSION: 1.63.2
DART_SASS_SHA_LINUX: 3ea33c95ad5c35fda6e9a0956199eef38a398f496cfb8750e02479d7d1dd42af
DART_SASS_SHA_MACOS: 11c70f259836b250b44a9cb57fed70e030f21f45069b467d371685855f1eb4f0
DART_SASS_SHA_WINDOWS: cd8cd36a619dd8e27f93d3186c52d70eb7d69472aa6c85f5094b29693e773f64
permissions:
contents: read
jobs:
test:
env:
GOPROXY: https://proxy.golang.org
GO111MODULE: on
strategy:
matrix:
# Note: We upgraded to Go 1.18 in Hugo v0.95.0
# Go 1.18 had some breaking changes on the source level which means Hugo cannot be built
# with older Go versions, but the improvements in Go 1.18 were too good to pass on (e.g. break and continue).
# Note that you don't need Go (or Go 1.18) to run a pre-built binary.
go-version: [1.18.x,1.19.x]
go-version: [1.19.x,1.20.x]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
- name: Install Go
uses: actions/setup-go@37335c7bb261b353407cff977110895fa0b4f7d8
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753
with:
go-version: ${{ matrix.go-version }}
check-latest: true
cache: true
cache-dependency-path: |
**/go.sum
**/go.mod
- name: Install Ruby
uses: actions/setup-ruby@5f29a1cd8dfebf420691c4c9a0e832e2fae5a526
uses: ruby/setup-ruby@ee2113536afb7f793eed4ce60e8d3b26db912da4
with:
ruby-version: '2.7'
ruby-version: '2.7'
bundler-cache: true #
- name: Install Python
uses: actions/setup-python@3105fb18c05ddd93efea5f9e0bef7a03a6e9e7df
with:
python-version: '3.x'
- name: Install Mage
run: go install github.com/magefile/mage@07afc7d24f4d6d6442305d49552f04fbda5ccb3e
run: go install github.com/magefile/mage@v1.15.0
- name: Install asciidoctor
uses: reitzig/actions-asciidoctor@7570212ae20b63653481675fb1ff62d1073632b0
- name: Checkout code
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Install docutils
run: |
pip install docutils
@@ -53,40 +62,52 @@ jobs:
- run: pandoc -v
- if: matrix.os == 'windows-latest'
run: |
Choco-Install -PackageName mingw -ArgumentList "--version","10.2.0","--allow-downgrade"
Choco-Install -PackageName mingw -ArgumentList "--version","12.2.0","--allow-downgrade"
- if: matrix.os == 'ubuntu-latest'
name: Install dart-sass-embedded Linux
run: |
curl -LJO https://github.com/sass/dart-sass-embedded/releases/download/1.0.0-beta.6/sass_embedded-1.0.0-beta.6-linux-x64.tar.gz;
echo "04fc1e5e28d29a4585a701941b6dace56771d94bfbe7f9e4db28d24417ceeec3 sass_embedded-1.0.0-beta.6-linux-x64.tar.gz" | sha256sum -c;
tar -xvf sass_embedded-1.0.0-beta.6-linux-x64.tar.gz;
echo "$GITHUB_WORKSPACE/sass_embedded/" >> $GITHUB_PATH
echo "Install Dart Sass version ${SASS_VERSION} ..."
curl -LJO "https://github.com/sass/dart-sass/releases/download/${SASS_VERSION}/dart-sass-${SASS_VERSION}-linux-x64.tar.gz";
echo "${DART_SASS_SHA_LINUX} dart-sass-${SASS_VERSION}-linux-x64.tar.gz" | sha256sum -c;
tar -xvf "dart-sass-${SASS_VERSION}-linux-x64.tar.gz";
echo "$GOBIN"
echo "$GITHUB_WORKSPACE/dart-sass/" >> $GITHUB_PATH
- if: matrix.os == 'macos-latest'
name: Install dart-sass-embedded MacOS
run: |
curl -LJO https://github.com/sass/dart-sass-embedded/releases/download/1.0.0-beta.6/sass_embedded-1.0.0-beta.6-macos-x64.tar.gz;
echo "b3b984675a9b04aa22f6f2302dda4191b507ac2ca124467db2dfe7e58e72fbad sass_embedded-1.0.0-beta.6-macos-x64.tar.gz" | shasum -a 256 -c;
tar -xvf sass_embedded-1.0.0-beta.6-macos-x64.tar.gz;
echo "$GITHUB_WORKSPACE/sass_embedded/" >> $GITHUB_PATH
echo "Install Dart Sass version ${SASS_VERSION} ..."
curl -LJO "https://github.com/sass/dart-sass/releases/download/${SASS_VERSION}/dart-sass-${SASS_VERSION}-macos-x64.tar.gz";
echo "${DART_SASS_SHA_MACOS} dart-sass-${SASS_VERSION}-macos-x64.tar.gz" | shasum -a 256 -c;
tar -xvf "dart-sass-${SASS_VERSION}-macos-x64.tar.gz";
echo "$GITHUB_WORKSPACE/dart-sass/" >> $GITHUB_PATH
- if: matrix.os == 'windows-latest'
name: Install dart-sass-embedded Windows
run: |
curl -LJO https://github.com/sass/dart-sass-embedded/releases/download/1.0.0-beta.6/sass_embedded-1.0.0-beta.6-windows-x64.zip;
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: Check
echo "Install Dart Sass version ${env:SASS_VERSION} ..."
curl -LJO "https://github.com/sass/dart-sass/releases/download/${env:SASS_VERSION}/dart-sass-${env:SASS_VERSION}-windows-x64.zip";
Expand-Archive -Path "dart-sass-${env:SASS_VERSION}-windows-x64.zip" -DestinationPath .;
echo "$env:GITHUB_WORKSPACE/dart-sass/" | Out-File -FilePath $Env:GITHUB_PATH -Encoding utf-8 -Append
- if: matrix.os != 'windows-latest'
name: Check
run: |
sass --version;
mage -v check;
env:
HUGO_BUILD_TAGS: extended
- name: Build Docs
- if: matrix.os == 'windows-latest'
# See issue #11052. We limit the build to regular test (no -race flag) on Windows for now.
name: Test
run: |
mage -v test;
env:
HUGO_BUILD_TAGS: extended
HUGO_TIMEOUT: 31000
HUGO_IGNOREERRORS: error-remote-getjson
HUGO_SERVICES_INSTAGRAM_ACCESSTOKEN: dummytoken
- name: Build tags
run: |
mage -v hugo
./hugo -s docs/
./hugo --renderToMemory -s docs/
go install -tags extended,nodeploy
- if: matrix.os == 'ubuntu-latest'
name: Build for dragonfly
run: |
go install
env:
GOARCH: amd64
GOOS: dragonfly
+2 -28
View File
@@ -1,29 +1,3 @@
/hugo
docs/public*
/.idea
.vscode/*
hugo.exe
*.test
*.prof
nohup.out
cover.out
*.swp
*.swo
.DS_Store
*~
vendor/*/
*.bench
*.debug
coverage*.out
dock.sh
GoBuilds
dist
hugolib/hugo_stats.json
resources/sunset.jpg
vendor
.hugo_build.lock
imports.*
+2 -4
View File
@@ -1,6 +1,6 @@
# Contributing to Hugo
>**Note:** We would apprecitate if you hold on with any big refactorings (like renaming deprecated Go packages), mainly because of potential for extra merge work for future coming in in the near future.
**Note March 16th 2022:** We are currently very constrained on human resources to do code reviews, so we currently require any new Pull Requests to be limited to bug fixes closing an existing issue. Also, we have updated to Go 1.18, but we will currently not accept any generic rewrites, "interface{} to any" replacements and similar.
# Contributing to Hugo
We welcome contributions to Hugo of any kind including documentation, themes,
organization, tutorials, blog posts, bug reports, issues, feature requests,
@@ -52,8 +52,6 @@ Hugo has become a fully featured static site generator, so any new functionality
If it is of some complexity, the contributor is expected to maintain and support the new feature in the future (answer questions on the forum, fix any bugs etc.).
It is recommended to open up a discussion on the [Hugo Forum](https://discourse.gohugo.io/) to get feedback on your idea before you begin.
Any non-trivial code change needs to update an open [issue](https://github.com/gohugoio/hugo/issues). A non-trivial code change without an issue reference with one of the labels `bug` or `enhancement` will not be merged.
Note that we do not accept new features that require [CGO](https://github.com/golang/go/wiki/cgo).
+2 -2
View File
@@ -2,7 +2,7 @@
# Twitter: https://twitter.com/gohugoio
# Website: https://gohugo.io/
FROM golang:1.18-alpine AS build
FROM golang:1.19-alpine AS build
# Optionally set HUGO_BUILD_TAGS to "extended" or "nodeploy" when building like so:
# docker build --build-arg HUGO_BUILD_TAGS=extended .
@@ -26,7 +26,7 @@ RUN mage hugo && mage install
# ---
FROM alpine:3.12
FROM alpine:3.16
COPY --from=build /go/bin/hugo /usr/bin/hugo
+212 -172
View File
@@ -1,250 +1,290 @@
[bep]: https://github.com/spf13
[bugs]: https://github.com/gohugoio/hugo/issues?q=is%3Aopen+is%3Aissue+label%3ABug
[contributing]: CONTRIBUTING.md
[create a proposal]: https://github.com/gohugoio/hugo/issues/new?labels=Proposal%2C+NeedsTriage&template=feature_request.md
[documentation repository]: https://github.com/gohugoio/hugoDocs
[documentation]: https://gohugo.io/documentation
[dragonfly bsd, freebsd, netbsd, and openbsd]: https://gohugo.io/installation/bsd
[forum]: https://discourse.gohugo.io
[friends]: https://github.com/gohugoio/hugo/graphs/contributors
[go]: https://go.dev/
[hugo modules]: https://gohugo.io/hugo-modules/
[installation]: https://gohugo.io/installation
[issue queue]: https://github.com/gohugoio/hugo/issues
[linux]: https://gohugo.io/installation/linux
[macos]: https://gohugo.io/installation/macos
[prebuilt binary]: https://github.com/gohugoio/hugo/releases/latest
[requesting help]: https://discourse.gohugo.io/t/requesting-help/9132
[spf13]: https://github.com/spf13
[static site generator]: https://en.wikipedia.org/wiki/Static_site_generator
[support]: https://discourse.gohugo.io
[themes]: https://themes.gohugo.io/
[twitter]: https://twitter.com/gohugoio
[website]: https://gohugo.io
[windows]: https://gohugo.io/installation/windows
<a href="https://gohugo.io/"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/static/images/hugo-logo-wide.svg?sanitize=true" alt="Hugo" width="565"></a>
A Fast and Flexible Static Site Generator built with love by [bep](https://github.com/bep), [spf13](https://spf13.com/) and [friends](https://github.com/gohugoio/hugo/graphs/contributors) in [Go](https://go.dev/).
A fast and flexible static site generator built with love by [bep], [spf13], and [friends] in [Go].
[Website](https://gohugo.io) |
[Forum](https://discourse.gohugo.io) |
[Documentation](https://gohugo.io/getting-started/) |
[Installation Guide](https://gohugo.io/getting-started/installing/) |
[Contribution Guide](CONTRIBUTING.md) |
[Twitter](https://twitter.com/gohugoio)
---
[![GoDoc](https://godoc.org/github.com/gohugoio/hugo?status.svg)](https://godoc.org/github.com/gohugoio/hugo)
[![Tests on Linux, MacOS and Windows](https://github.com/gohugoio/hugo/workflows/Test/badge.svg)](https://github.com/gohugoio/hugo/actions?query=workflow%3ATest)
[![Go Report Card](https://goreportcard.com/badge/github.com/gohugoio/hugo)](https://goreportcard.com/report/github.com/gohugoio/hugo)
* [Overview](#overview)
* [Banner Sponsors](#banner-sponsors)
* [Supported Architectures](#supported-architectures)
* [Choose How to Install](#choose-how-to-install)
* [Install Hugo as Your Site Generator (Binary Install)](#install-hugo-as-your-site-generator-binary-install)
* [Build and Install the Binaries from Source (Advanced Install)](#build-and-install-the-binaries-from-source-advanced-install)
* [The Hugo Documentation](#the-hugo-documentation)
* [Contributing to Hugo](#contributing-code-to-hugo)
* [Dependencies](#dependencies)
[Website] | [Installation] | [Documentation] | [Support] | [Contributing] | [Twitter]
## Overview
Hugo is a static HTML and CSS website generator written in [Go](https://go.dev/).
It is optimized for speed, ease of use, and configurability.
Hugo takes a directory with content and templates and renders them into a full HTML website.
Hugo is a [static site generator] written in [Go], optimized for speed and designed for flexibility. With its advanced templating system and fast asset pipelines, Hugo renders a complete site in seconds, often less.
Hugo relies on Markdown files with front matter for metadata, and you can run Hugo from any directory.
This works well for shared hosts and other systems where you dont have a privileged account.
Due to its flexible framework, multilingual support, and powerful taxonomy system, Hugo is widely used to create:
Hugo renders a typical website of moderate size in a fraction of a second.
A good rule of thumb is that each piece of content renders in around 1 millisecond.
- Corporate, government, nonprofit, education, news, event, and project sites
- Documentation sites
- Image portfolios
- Landing pages
- Business, professional, and personal blogs
- Resumes and CVs
Hugo is designed to work well for any kind of website including blogs, tumbles, and docs.
Use Hugo's embedded web server during development to instantly see changes to content, structure, behavior, and presentation. Then deploy the site to your host, or push changes to your Git provider for automated builds and deployment.
Hugo's fast asset pipelines include:
- CSS bundling &ndash; transpilation (Sass), tree shaking, minification, source maps, SRI hashing, and PostCSS integration
- JavaScript bundling &ndash; transpilation (TypeScript, JSX), tree shaking, minification, source maps, and SRI hashing
- Image processing &ndash; convert, resize, crop, rotate, adjust colors, apply filters, overlay text and images, and extract EXIF data
And with [Hugo Modules], you can share content, assets, data, translations, themes, templates, and configuration with other projects via public or private Git repositories.
## Sponsors
## Banner Sponsors
<p>&nbsp;</p>
<p float="left">
<a href="https://www.linode.com/?utm_campaign=hugosponsor&utm_medium=banner&utm_source=hugogithub" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/static/images/sponsors/linode-logo_standard_light_medium.png" width="200" alt="Linode"></a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a href="https://esolia.com/post/why-did-esolia-choose-hugo/?utm_campaign=hugosponsor&utm_medium=banner&utm_source=hugogithub" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/static/images/sponsors/esolia-logo.svg?sanitize=true" width="200" alt="eSOLIA"></a>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="https://buttercms.com/hugo-cms/?utm_campaign=sponsorship&utm_medium=banner&utm_source=hugogithub" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/static/images/sponsors/butter-dark.svg?sanitize=true" width="280" alt="ButterCMS"></a>
</p>
<a href="https://www.linode.com/?utm_campaign=hugosponsor&utm_medium=banner&utm_source=hugogithub" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/linode-logo_standard_light_medium.png" width="200" alt="Linode"></a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a href="https://cloudcannon.com/hugo-cms/?utm_campaign=HugoSponsorship&utm_source=sponsor&utm_content=gohugo" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/cloudcannon-blue.svg" width="220" alt="CloudCannon"></a>
<p>&nbsp;</p>
## Supported Architectures
## Installation
Currently, we provide pre-built Hugo binaries for Windows, Linux, FreeBSD, NetBSD, DragonFly BSD, OpenBSD, macOS (Darwin), and [Android](https://gist.github.com/bep/a0d8a26cf6b4f8bc992729b8e50b480b) for x64, i386 and ARM architectures.
Install Hugo from a [prebuilt binary], package manager, or package repository. Please see the installation instructions for your operating system:
Hugo may also be compiled from source wherever the Go compiler tool chain can run, e.g. for other operating systems including Plan 9 and Solaris.
- [macOS]
- [Linux]
- [Windows]
- [DragonFly BSD, FreeBSD, NetBSD, and OpenBSD]
**Complete documentation is available at [Hugo Documentation](https://gohugo.io/getting-started/).**
## Build from source
## Choose How to Install
Hugo is available in two editions: standard and extended. With the extended edition you can:
If you want to use Hugo as your site generator, simply install the Hugo binaries.
- Encode to the WebP format when processing images. You can decode WebP images with either edition.
- Transpile Sass to CSS using the embedded LibSass transpiler. The extended edition is not required to use the Dart Sass transpiler.
To contribute to the Hugo source code or documentation, you should [fork the Hugo GitHub project](https://github.com/gohugoio/hugo#fork-destination-box) and clone it to your local machine.
Prerequisites to build Hugo from source:
Finally, you can install the Hugo source code with `go`, build the binaries yourself, and run Hugo that way.
Building the binaries is an easy task for an experienced `go` getter.
- Standard edition: Go 1.19 or later
- Extended edition: Go 1.19 or later, and GCC
### Install Hugo as Your Site Generator (Binary Install)
Build the standard edition:
Use the [installation instructions in the Hugo documentation](https://gohugo.io/getting-started/installing/).
### Build and Install the Binaries from Source (Advanced Install)
#### Prerequisite Tools
* [Git](https://git-scm.com/)
* [Go (we test it with the last 2 major versions; but note that Hugo 0.95.0 only builds with >= Go 1.18.)](https://golang.org/dl/)
#### Fetch from GitHub
To fetch and build the source from GitHub:
```bash
mkdir $HOME/src
cd $HOME/src
git clone https://github.com/gohugoio/hugo.git
cd hugo
go install
```text
go install github.com/gohugoio/hugo@latest
```
**If you are a Windows user, substitute the `$HOME` environment variable above with `%USERPROFILE%`.**
Build the extended edition:
If you want to compile with Sass/SCSS support use `--tags extended` and make sure `CGO_ENABLED=1` is set in your go environment. If you don't want to have CGO enabled, you may use the following command to temporarily enable CGO only for hugo compilation:
```bash
CGO_ENABLED=1 go install --tags extended
```text
CGO_ENABLED=1 go install -tags extended github.com/gohugoio/hugo@latest
```
## The Hugo Documentation
## Documentation
The Hugo documentation now lives in its own repository, see https://github.com/gohugoio/hugoDocs. But we do keep a version of that documentation as a `git subtree` in this repository. To build the sub folder `/docs` as a Hugo site, you need to clone this repo:
Hugo's [documentation] includes installation instructions, a quick start guide, conceptual explanations, reference information, and examples.
```bash
git clone git@github.com:gohugoio/hugo.git
```
## Contributing code to Hugo
Please submit documentation issues and pull requests to the [documentation repository].
**Note March 16th 2022:** We are currently very constrained on human resources to do code reviews, so we currently require any new Pull Requests to be limited to bug fixes closing an existing issue. Also, we have updated to Go 1.18, but we will currently not accept any generic rewrites, "interface{} to any" replacements and similar.
## Support
Please **do not use the issue queue** for questions or troubleshooting. Unless you are certain that your issue is a software defect, use the [forum].
Hugos [forum] is an active community of users and developers who answer questions, share knowledge, and provide examples. A quick search of over 20,000 topics will often answer your question. Please be sure to read about [requesting help] before asking your first question.
## Contributing
You can contribute to the Hugo project by:
- Answering questions on the [forum]
- Improving the [documentation]
- Monitoring the [issue queue]
- Creating or improving [themes]
- Squashing [bugs]
Please submit documentation issues and pull requests to the [documentation repository].
If you have an idea for an enhancement or new feature, create a new topic on the [forum] in the "Feature" category. This will help you to:
- Determine if the capability already exists
- Measure interest
- Refine the concept
If there is sufficient interest, [create a proposal]. Do not submit a pull request until the project lead accepts the proposal.
For a complete guide to contributing to Hugo, see the [Contribution Guide](CONTRIBUTING.md).
We welcome contributions to Hugo of any kind including documentation, themes,
organization, tutorials, blog posts, bug reports, issues, feature requests,
feature implementations, pull requests, answering questions on the forum,
helping to manage issues, etc.
The Hugo community and maintainers are [very active](https://github.com/gohugoio/hugo/pulse/monthly) and helpful, and the project benefits greatly from this activity.
## Asking Support Questions
We have an active [discussion forum](https://discourse.gohugo.io) where users and developers can ask questions.
Please don't use the GitHub issue tracker to ask questions.
## Reporting Issues
If you believe you have found a defect in Hugo or its documentation, use
the GitHub issue tracker to report the problem to the Hugo maintainers.
If you're not sure if it's a bug or not, start by asking in the [discussion forum](https://discourse.gohugo.io).
When reporting the issue, please provide the version of Hugo in use (`hugo version`).
## Dependencies
Hugo stands on the shoulder of many great open source libraries.
Hugo stands on the shoulders of great open source libraries. Run `hugo env --logLevel info` to display a list of dependencies.
If you run `hugo env -v` you will get a complete and up to date list.
<details>
<summary>See current dependencies</summary>
In Hugo 0.100.1 that list is, in lexical order:
```
cloud.google.com/go/compute="v1.6.1"
cloud.google.com/go/iam="v0.3.0"
cloud.google.com/go/storage="v1.22.0"
cloud.google.com/go="v0.101.0"
github.com/Azure/azure-pipeline-go="v0.2.3"
github.com/Azure/azure-storage-blob-go="v0.14.0"
github.com/Azure/go-autorest/autorest/adal="v0.9.15"
github.com/Azure/go-autorest/autorest/date="v0.3.0"
github.com/Azure/go-autorest/autorest="v0.11.20"
github.com/Azure/go-autorest/logger="v0.2.1"
github.com/Azure/go-autorest/tracing="v0.6.0"
```text
cloud.google.com/go/compute/metadata="v0.2.3"
cloud.google.com/go/iam="v1.1.0"
cloud.google.com/go/storage="v1.30.1"
cloud.google.com/go="v0.110.2"
github.com/Azure/azure-sdk-for-go/sdk/azcore="v1.6.1"
github.com/Azure/azure-sdk-for-go/sdk/azidentity="v1.3.0"
github.com/Azure/azure-sdk-for-go/sdk/internal="v1.3.0"
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob="v1.0.0"
github.com/Azure/go-autorest/autorest/to="v0.4.0"
github.com/AzureAD/microsoft-authentication-library-for-go="v1.0.0"
github.com/BurntSushi/locker="v0.0.0-20171006230638-a6e239ea1c69"
github.com/PuerkitoBio/purell="v1.1.1"
github.com/PuerkitoBio/urlesc="v0.0.0-20170810143723-de5bf2ad4578"
github.com/alecthomas/chroma="v0.10.0"
github.com/alecthomas/chroma/v2="v2.7.0"
github.com/armon/go-radix="v1.0.0"
github.com/aws/aws-sdk-go-v2/config="v1.7.0"
github.com/aws/aws-sdk-go-v2/credentials="v1.4.0"
github.com/aws/aws-sdk-go-v2/feature/ec2/imds="v1.5.0"
github.com/aws/aws-sdk-go-v2/internal/ini="v1.2.2"
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url="v1.3.0"
github.com/aws/aws-sdk-go-v2/service/sso="v1.4.0"
github.com/aws/aws-sdk-go-v2/service/sts="v1.7.0"
github.com/aws/aws-sdk-go-v2="v1.9.0"
github.com/aws/aws-sdk-go="v1.43.5"
github.com/aws/smithy-go="v1.8.0"
github.com/bep/clock="v0.3.0"
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream="v1.4.10"
github.com/aws/aws-sdk-go-v2/config="v1.18.27"
github.com/aws/aws-sdk-go-v2/credentials="v1.13.26"
github.com/aws/aws-sdk-go-v2/feature/ec2/imds="v1.13.4"
github.com/aws/aws-sdk-go-v2/feature/s3/manager="v1.11.70"
github.com/aws/aws-sdk-go-v2/internal/configsources="v1.1.34"
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2="v2.4.28"
github.com/aws/aws-sdk-go-v2/internal/ini="v1.3.35"
github.com/aws/aws-sdk-go-v2/internal/v4a="v1.0.26"
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding="v1.9.11"
github.com/aws/aws-sdk-go-v2/service/internal/checksum="v1.1.29"
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url="v1.9.28"
github.com/aws/aws-sdk-go-v2/service/internal/s3shared="v1.14.3"
github.com/aws/aws-sdk-go-v2/service/s3="v1.35.0"
github.com/aws/aws-sdk-go-v2/service/sso="v1.12.12"
github.com/aws/aws-sdk-go-v2/service/ssooidc="v1.14.12"
github.com/aws/aws-sdk-go-v2/service/sts="v1.19.2"
github.com/aws/aws-sdk-go-v2="v1.18.1"
github.com/aws/aws-sdk-go="v1.44.284"
github.com/aws/smithy-go="v1.13.5"
github.com/bep/clocks="v0.5.0"
github.com/bep/debounce="v1.2.0"
github.com/bep/gitmap="v1.1.2"
github.com/bep/goat="v0.5.0"
github.com/bep/godartsass="v0.14.0"
github.com/bep/golibsass="v1.1.0"
github.com/bep/gowebp="v0.1.0"
github.com/bep/godartsass/v2="v2.0.0"
github.com/bep/godartsass="v1.2.0"
github.com/bep/golibsass="v1.1.1"
github.com/bep/gowebp="v0.2.0"
github.com/bep/lazycache="v0.2.0"
github.com/bep/logg="v0.2.0"
github.com/bep/mclib="v1.20400.20402"
github.com/bep/overlayfs="v0.6.0"
github.com/bep/simplecobra="v0.3.2"
github.com/bep/tmc="v0.5.1"
github.com/clbanning/mxj/v2="v2.5.5"
github.com/cli/safeexec="v1.0.0"
github.com/cpuguy83/go-md2man/v2="v2.0.1"
github.com/clbanning/mxj/v2="v2.5.7"
github.com/cli/safeexec="v1.0.1"
github.com/cpuguy83/go-md2man/v2="v2.0.2"
github.com/disintegration/gift="v1.2.1"
github.com/dlclark/regexp2="v1.4.0"
github.com/dustin/go-humanize="v1.0.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/dlclark/regexp2="v1.10.0"
github.com/dustin/go-humanize="v1.0.1"
github.com/evanw/esbuild="v0.18.5"
github.com/fatih/color="v1.15.0"
github.com/frankban/quicktest="v1.14.5"
github.com/fsnotify/fsnotify="v1.6.0"
github.com/getkin/kin-openapi="v0.118.0"
github.com/ghodss/yaml="v1.0.0"
github.com/go-openapi/jsonpointer="v0.19.5"
github.com/go-openapi/swag="v0.19.5"
github.com/gobuffalo/flect="v0.2.5"
github.com/go-openapi/jsonpointer="v0.19.6"
github.com/go-openapi/swag="v0.22.3"
github.com/gobuffalo/flect="v1.0.2"
github.com/gobwas/glob="v0.2.3"
github.com/gohugoio/go-i18n/v2="v2.1.3-0.20210430103248-4c28c89f8013"
github.com/gohugoio/locales="v0.14.0"
github.com/gohugoio/localescompressed="v1.0.1"
github.com/golang-jwt/jwt/v4="v4.0.0"
github.com/golang-jwt/jwt/v4="v4.5.0"
github.com/golang/groupcache="v0.0.0-20210331224755-41bb18bfe9da"
github.com/golang/protobuf="v1.5.2"
github.com/google/go-cmp="v0.5.8"
github.com/golang/protobuf="v1.5.3"
github.com/google/go-cmp="v0.5.9"
github.com/google/s2a-go="v0.1.4"
github.com/google/uuid="v1.3.0"
github.com/google/wire="v0.5.0"
github.com/googleapis/gax-go/v2="v2.3.0"
github.com/googleapis/go-type-adapters="v1.0.0"
github.com/googleapis/enterprise-certificate-proxy="v0.2.5"
github.com/googleapis/gax-go/v2="v2.11.0"
github.com/gorilla/websocket="v1.5.0"
github.com/hairyhenderson/go-codeowners="v0.2.3-0.20201026200250-cdc7c0759690"
github.com/inconshreveable/mousetrap="v1.0.0"
github.com/hairyhenderson/go-codeowners="v0.3.0"
github.com/hashicorp/golang-lru/v2="v2.0.1"
github.com/invopop/yaml="v0.1.0"
github.com/jdkato/prose="v1.2.1"
github.com/jmespath/go-jmespath="v0.4.0"
github.com/kr/pretty="v0.3.0"
github.com/josharian/intern="v1.0.0"
github.com/kr/pretty="v0.3.1"
github.com/kr/text="v0.2.0"
github.com/kyokomi/emoji/v2="v2.2.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/kylelemons/godebug="v1.1.0"
github.com/kyokomi/emoji/v2="v2.2.12"
github.com/mailru/easyjson="v0.7.7"
github.com/marekm4/color-extractor="v1.2.0"
github.com/mattn/go-colorable="v0.1.13"
github.com/mattn/go-isatty="v0.0.19"
github.com/mattn/go-runewidth="v0.0.9"
github.com/mitchellh/hashstructure="v1.1.0"
github.com/mitchellh/mapstructure="v1.5.0"
github.com/mohae/deepcopy="v0.0.0-20170929034955-c48cc78d4826"
github.com/muesli/smartcrop="v0.3.0"
github.com/niklasfasching/go-org="v1.6.2"
github.com/niklasfasching/go-org="v1.7.0"
github.com/olekukonko/tablewriter="v0.0.5"
github.com/pelletier/go-toml/v2="v2.0.0-beta.7.0.20220408132554-2377ac4bc04c"
github.com/rogpeppe/go-internal="v1.8.1"
github.com/pelletier/go-toml/v2="v2.0.8"
github.com/perimeterx/marshmallow="v1.1.4"
github.com/pkg/browser="v0.0.0-20210911075715-681adbf594b8"
github.com/pkg/errors="v0.9.1"
github.com/rogpeppe/go-internal="v1.10.1-0.20230508101108-a4f6fabd84c5"
github.com/russross/blackfriday/v2="v2.1.0"
github.com/rwcarlsen/goexif="v0.0.0-20190401172101-9e8deecbddbd"
github.com/sanity-io/litter="v1.5.5"
github.com/sass/dart-sass/compiler="1.63.6"
github.com/sass/dart-sass/implementation="1.63.6"
github.com/sass/dart-sass/protocol="2.1.0"
github.com/sass/libsass="3.6.5"
github.com/spf13/afero="v1.8.2"
github.com/spf13/cast="v1.5.0"
github.com/spf13/cobra="v1.4.0"
github.com/spf13/afero="v1.9.5"
github.com/spf13/cast="v1.5.1"
github.com/spf13/cobra="v1.7.0"
github.com/spf13/fsync="v0.9.0"
github.com/spf13/jwalterweatherman="v1.1.0"
github.com/spf13/pflag="v1.0.5"
github.com/tdewolff/minify/v2="v2.11.5"
github.com/tdewolff/parse/v2="v2.5.31"
github.com/webmproject/libwebp="v1.2.0"
github.com/yuin/goldmark="v1.4.12"
go.opencensus.io="v0.23.0"
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-20220422013727-9388b58f7150"
golang.org/x/text="v0.3.7"
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"
github.com/tdewolff/minify/v2="v2.12.7"
github.com/tdewolff/parse/v2="v2.6.6"
github.com/webmproject/libwebp="v1.2.4"
github.com/yuin/goldmark="v1.5.4"
go.opencensus.io="v0.24.0"
go.uber.org/atomic="v1.11.0"
go.uber.org/automaxprocs="v1.5.2"
gocloud.dev="v0.30.0"
golang.org/x/crypto="v0.10.0"
golang.org/x/exp="v0.0.0-20230321023759-10a507213a29"
golang.org/x/image="v0.8.0"
golang.org/x/mod="v0.10.0"
golang.org/x/net="v0.11.0"
golang.org/x/oauth2="v0.9.0"
golang.org/x/sync="v0.3.0"
golang.org/x/sys="v0.9.0"
golang.org/x/text="v0.10.0"
golang.org/x/tools="v0.9.3"
golang.org/x/xerrors="v0.0.0-20220907171357-04be3eba64a2"
google.golang.org/api="v0.128.0"
google.golang.org/appengine="v1.6.7"
google.golang.org/genproto/googleapis/api="v0.0.0-20230530153820-e85fd2cbaebc"
google.golang.org/genproto/googleapis/rpc="v0.0.0-20230530153820-e85fd2cbaebc"
google.golang.org/genproto="v0.0.0-20230530153820-e85fd2cbaebc"
google.golang.org/grpc="v1.56.0"
google.golang.org/protobuf="v1.30.0"
gopkg.in/yaml.v2="v2.4.0"
gopkg.in/yaml.v3="v3.0.1"
software.sslmate.com/src/go-pkcs12="v0.2.0"
```
</details>
+2
View File
@@ -0,0 +1,2 @@
// Package cache contains the different cache implementations.
package cache
+11 -26
View File
@@ -17,7 +17,6 @@ import (
"bytes"
"errors"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -36,7 +35,7 @@ import (
var ErrFatal = errors.New("fatal filecache error")
const (
filecacheRootDirname = "filecache"
FilecacheRootDirname = "filecache"
)
// Cache caches a set of files in a directory. This is usually a file on
@@ -207,7 +206,7 @@ func (c *Cache) GetOrCreateBytes(id string, create func() ([]byte, error)) (Item
if r := c.getOrRemove(id); r != nil {
defer r.Close()
b, err := ioutil.ReadAll(r)
b, err := io.ReadAll(r)
return info, b, err
}
@@ -242,14 +241,14 @@ func (c *Cache) GetBytes(id string) (ItemInfo, []byte, error) {
if r := c.getOrRemove(id); r != nil {
defer r.Close()
b, err := ioutil.ReadAll(r)
b, err := io.ReadAll(r)
return info, b, err
}
return info, nil, nil
}
// Get gets the file with the given id from the cahce, nil if none found.
// Get gets the file with the given id from the cache, nil if none found.
func (c *Cache) Get(id string) (ItemInfo, io.ReadCloser, error) {
id = cleanID(id)
@@ -302,7 +301,7 @@ func (c *Cache) isExpired(modTime time.Time) bool {
}
// For testing
func (c *Cache) getString(id string) string {
func (c *Cache) GetString(id string) string {
id = cleanID(id)
c.nlocker.Lock(id)
@@ -314,7 +313,7 @@ func (c *Cache) getString(id string) string {
}
defer f.Close()
b, _ := ioutil.ReadAll(f)
b, _ := io.ReadAll(f)
return string(b)
}
@@ -329,38 +328,24 @@ func (f Caches) Get(name string) *Cache {
// NewCaches creates a new set of file caches from the given
// configuration.
func NewCaches(p *helpers.PathSpec) (Caches, error) {
var dcfg Configs
if c, ok := p.Cfg.Get("filecacheConfigs").(Configs); ok {
dcfg = c
} else {
var err error
dcfg, err = DecodeConfig(p.Fs.Source, p.Cfg)
if err != nil {
return nil, err
}
}
dcfg := p.Cfg.GetConfigSection("caches").(Configs)
fs := p.Fs.Source
m := make(Caches)
for k, v := range dcfg {
var cfs afero.Fs
if v.isResourceDir {
if v.IsResourceDir {
cfs = p.BaseFs.ResourcesCache
} else {
cfs = fs
}
if cfs == nil {
// TODO(bep) we still have some places that do not initialize the
// full dependencies of a site, e.g. the import Jekyll command.
// That command does not need these caches, so let us just continue
// for now.
continue
panic("nil fs")
}
baseDir := v.Dir
baseDir := v.DirCompiled
if err := cfs.MkdirAll(baseDir, 0777); err != nil && !os.IsExist(err) {
return nil, err
@@ -369,7 +354,7 @@ func NewCaches(p *helpers.PathSpec) (Caches, error) {
bfs := afero.NewBasePathFs(cfs, baseDir)
var pruneAllRootDir string
if k == cacheKeyModules {
if k == CacheKeyModules {
pruneAllRootDir = "pkg"
}
+47 -56
View File
@@ -11,6 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Package filecache provides a file based cache for Hugo.
package filecache
import (
@@ -21,11 +22,8 @@ import (
"time"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
"errors"
"github.com/mitchellh/mapstructure"
@@ -33,98 +31,102 @@ import (
)
const (
cachesConfigKey = "caches"
resourcesGenDir = ":resourceDir/_gen"
cacheDirProject = ":cacheDir/:project"
)
var defaultCacheConfig = Config{
var defaultCacheConfig = FileCacheConfig{
MaxAge: -1, // Never expire
Dir: cacheDirProject,
}
const (
cacheKeyGetJSON = "getjson"
cacheKeyGetCSV = "getcsv"
cacheKeyImages = "images"
cacheKeyAssets = "assets"
cacheKeyModules = "modules"
cacheKeyGetResource = "getresource"
CacheKeyGetJSON = "getjson"
CacheKeyGetCSV = "getcsv"
CacheKeyImages = "images"
CacheKeyAssets = "assets"
CacheKeyModules = "modules"
CacheKeyGetResource = "getresource"
)
type Configs map[string]Config
type Configs map[string]FileCacheConfig
// For internal use.
func (c Configs) CacheDirModules() string {
return c[cacheKeyModules].Dir
return c[CacheKeyModules].DirCompiled
}
var defaultCacheConfigs = Configs{
cacheKeyModules: {
CacheKeyModules: {
MaxAge: -1,
Dir: ":cacheDir/modules",
},
cacheKeyGetJSON: defaultCacheConfig,
cacheKeyGetCSV: defaultCacheConfig,
cacheKeyImages: {
CacheKeyGetJSON: defaultCacheConfig,
CacheKeyGetCSV: defaultCacheConfig,
CacheKeyImages: {
MaxAge: -1,
Dir: resourcesGenDir,
},
cacheKeyAssets: {
CacheKeyAssets: {
MaxAge: -1,
Dir: resourcesGenDir,
},
cacheKeyGetResource: Config{
CacheKeyGetResource: FileCacheConfig{
MaxAge: -1, // Never expire
Dir: cacheDirProject,
},
}
type Config struct {
type FileCacheConfig struct {
// Max age of cache entries in this cache. Any items older than this will
// be removed and not returned from the cache.
// a negative value means forever, 0 means cache is disabled.
// A negative value means forever, 0 means cache is disabled.
// Hugo is lenient with what types it accepts here, but we recommend using
// a duration string, a sequence of decimal numbers, each with optional fraction and a unit suffix,
// such as "300ms", "1.5h" or "2h45m".
// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
MaxAge time.Duration
// The directory where files are stored.
Dir string
Dir string
DirCompiled string `json:"-"`
// Will resources/_gen will get its own composite filesystem that
// also checks any theme.
isResourceDir bool
IsResourceDir bool
}
// GetJSONCache gets the file cache for getJSON.
func (f Caches) GetJSONCache() *Cache {
return f[cacheKeyGetJSON]
return f[CacheKeyGetJSON]
}
// GetCSVCache gets the file cache for getCSV.
func (f Caches) GetCSVCache() *Cache {
return f[cacheKeyGetCSV]
return f[CacheKeyGetCSV]
}
// ImageCache gets the file cache for processed images.
func (f Caches) ImageCache() *Cache {
return f[cacheKeyImages]
return f[CacheKeyImages]
}
// ModulesCache gets the file cache for Hugo Modules.
func (f Caches) ModulesCache() *Cache {
return f[cacheKeyModules]
return f[CacheKeyModules]
}
// AssetsCache gets the file cache for assets (processed resources, SCSS etc.).
func (f Caches) AssetsCache() *Cache {
return f[cacheKeyAssets]
return f[CacheKeyAssets]
}
// GetResourceCache gets the file cache for remote resources.
func (f Caches) GetResourceCache() *Cache {
return f[cacheKeyGetResource]
return f[CacheKeyGetResource]
}
func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) {
func DecodeConfig(fs afero.Fs, bcfg config.BaseConfig, m map[string]any) (Configs, error) {
c := make(Configs)
valid := make(map[string]bool)
// Add defaults
@@ -133,8 +135,6 @@ func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) {
valid[k] = true
}
m := cfg.GetStringMap(cachesConfigKey)
_, isOsFs := fs.(*afero.OsFs)
for k, v := range m {
@@ -170,9 +170,6 @@ func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) {
c[name] = cc
}
// This is a very old flag in Hugo, but we need to respect it.
disabled := cfg.GetBool("ignoreCache")
for k, v := range c {
dir := filepath.ToSlash(filepath.Clean(v.Dir))
hadSlash := strings.HasPrefix(dir, "/")
@@ -180,12 +177,12 @@ func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) {
for i, part := range parts {
if strings.HasPrefix(part, ":") {
resolved, isResource, err := resolveDirPlaceholder(fs, cfg, part)
resolved, isResource, err := resolveDirPlaceholder(fs, bcfg, part)
if err != nil {
return c, err
}
if isResource {
v.isResourceDir = true
v.IsResourceDir = true
}
parts[i] = resolved
}
@@ -195,33 +192,29 @@ func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) {
if hadSlash {
dir = "/" + dir
}
v.Dir = filepath.Clean(filepath.FromSlash(dir))
v.DirCompiled = filepath.Clean(filepath.FromSlash(dir))
if !v.isResourceDir {
if isOsFs && !filepath.IsAbs(v.Dir) {
return c, fmt.Errorf("%q must resolve to an absolute directory", v.Dir)
if !v.IsResourceDir {
if isOsFs && !filepath.IsAbs(v.DirCompiled) {
return c, fmt.Errorf("%q must resolve to an absolute directory", v.DirCompiled)
}
// Avoid cache in root, e.g. / (Unix) or c:\ (Windows)
if len(strings.TrimPrefix(v.Dir, filepath.VolumeName(v.Dir))) == 1 {
return c, fmt.Errorf("%q is a root folder and not allowed as cache dir", v.Dir)
if len(strings.TrimPrefix(v.DirCompiled, filepath.VolumeName(v.DirCompiled))) == 1 {
return c, fmt.Errorf("%q is a root folder and not allowed as cache dir", v.DirCompiled)
}
}
if !strings.HasPrefix(v.Dir, "_gen") {
if !strings.HasPrefix(v.DirCompiled, "_gen") {
// We do cache eviction (file removes) and since the user can set
// his/hers own cache directory, we really want to make sure
// we do not delete any files that do not belong to this cache.
// We do add the cache name as the root, but this is an extra safe
// guard. We skip the files inside /resources/_gen/ because
// that would be breaking.
v.Dir = filepath.Join(v.Dir, filecacheRootDirname, k)
v.DirCompiled = filepath.Join(v.DirCompiled, FilecacheRootDirname, k)
} else {
v.Dir = filepath.Join(v.Dir, k)
}
if disabled {
v.MaxAge = 0
v.DirCompiled = filepath.Join(v.DirCompiled, k)
}
c[k] = v
@@ -231,17 +224,15 @@ func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) {
}
// Resolves :resourceDir => /myproject/resources etc., :cacheDir => ...
func resolveDirPlaceholder(fs afero.Fs, cfg config.Provider, placeholder string) (cacheDir string, isResource bool, err error) {
workingDir := cfg.GetString("workingDir")
func resolveDirPlaceholder(fs afero.Fs, bcfg config.BaseConfig, placeholder string) (cacheDir string, isResource bool, err error) {
switch strings.ToLower(placeholder) {
case ":resourcedir":
return "", true, nil
case ":cachedir":
d, err := helpers.GetCacheDir(fs, cfg)
return d, false, err
return bcfg.CacheDir, false, nil
case ":project":
return filepath.Base(workingDir), false, nil
return filepath.Base(bcfg.WorkingDir), false, nil
}
return "", false, fmt.Errorf("%q is not a valid placeholder (valid values are :cacheDir or :resourceDir)", placeholder)
+18 -70
View File
@@ -11,18 +11,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package filecache
package filecache_test
import (
"path/filepath"
"runtime"
"strings"
"testing"
"time"
"github.com/spf13/afero"
"github.com/gohugoio/hugo/cache/filecache"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/testconfig"
qt "github.com/frankban/quicktest"
)
@@ -57,22 +58,20 @@ dir = "/path/to/c4"
cfg, err := config.FromConfigString(configStr, "toml")
c.Assert(err, qt.IsNil)
fs := afero.NewMemMapFs()
decoded, err := DecodeConfig(fs, cfg)
c.Assert(err, qt.IsNil)
decoded := testconfig.GetTestConfigs(fs, cfg).Base.Caches
c.Assert(len(decoded), qt.Equals, 6)
c2 := decoded["getcsv"]
c.Assert(c2.MaxAge.String(), qt.Equals, "11h0m0s")
c.Assert(c2.Dir, qt.Equals, filepath.FromSlash("/path/to/c2/filecache/getcsv"))
c.Assert(c2.DirCompiled, qt.Equals, filepath.FromSlash("/path/to/c2/filecache/getcsv"))
c3 := decoded["images"]
c.Assert(c3.MaxAge, qt.Equals, time.Duration(-1))
c.Assert(c3.Dir, qt.Equals, filepath.FromSlash("/path/to/c3/filecache/images"))
c.Assert(c3.DirCompiled, qt.Equals, filepath.FromSlash("/path/to/c3/filecache/images"))
c4 := decoded["getresource"]
c.Assert(c4.MaxAge, qt.Equals, time.Duration(-1))
c.Assert(c4.Dir, qt.Equals, filepath.FromSlash("/path/to/c4/filecache/getresource"))
c.Assert(c4.DirCompiled, qt.Equals, filepath.FromSlash("/path/to/c4/filecache/getresource"))
}
func TestDecodeConfigIgnoreCache(t *testing.T) {
@@ -106,9 +105,7 @@ dir = "/path/to/c4"
cfg, err := config.FromConfigString(configStr, "toml")
c.Assert(err, qt.IsNil)
fs := afero.NewMemMapFs()
decoded, err := DecodeConfig(fs, cfg)
c.Assert(err, qt.IsNil)
decoded := testconfig.GetTestConfigs(fs, cfg).Base.Caches
c.Assert(len(decoded), qt.Equals, 6)
for _, v := range decoded {
@@ -118,7 +115,7 @@ dir = "/path/to/c4"
func TestDecodeConfigDefault(t *testing.T) {
c := qt.New(t)
cfg := newTestConfig()
cfg := config.New()
if runtime.GOOS == "windows" {
cfg.Set("resourceDir", "c:\\cache\\resources")
@@ -128,71 +125,22 @@ func TestDecodeConfigDefault(t *testing.T) {
cfg.Set("resourceDir", "/cache/resources")
cfg.Set("cacheDir", "/cache/thecache")
}
cfg.Set("workingDir", filepath.FromSlash("/my/cool/hugoproject"))
fs := afero.NewMemMapFs()
decoded, err := DecodeConfig(fs, cfg)
c.Assert(err, qt.IsNil)
decoded := testconfig.GetTestConfigs(fs, cfg).Base.Caches
c.Assert(len(decoded), qt.Equals, 6)
imgConfig := decoded[cacheKeyImages]
jsonConfig := decoded[cacheKeyGetJSON]
imgConfig := decoded[filecache.CacheKeyImages]
jsonConfig := decoded[filecache.CacheKeyGetJSON]
if runtime.GOOS == "windows" {
c.Assert(imgConfig.Dir, qt.Equals, filepath.FromSlash("_gen/images"))
c.Assert(imgConfig.DirCompiled, qt.Equals, filepath.FromSlash("_gen/images"))
} else {
c.Assert(imgConfig.Dir, qt.Equals, "_gen/images")
c.Assert(jsonConfig.Dir, qt.Equals, "/cache/thecache/hugoproject/filecache/getjson")
c.Assert(imgConfig.DirCompiled, qt.Equals, "_gen/images")
c.Assert(jsonConfig.DirCompiled, qt.Equals, "/cache/thecache/hugoproject/filecache/getjson")
}
c.Assert(imgConfig.isResourceDir, qt.Equals, true)
c.Assert(jsonConfig.isResourceDir, qt.Equals, false)
}
func TestDecodeConfigInvalidDir(t *testing.T) {
t.Parallel()
c := qt.New(t)
configStr := `
resourceDir = "myresources"
contentDir = "content"
dataDir = "data"
i18nDir = "i18n"
layoutDir = "layouts"
assetDir = "assets"
archeTypedir = "archetypes"
[caches]
[caches.getJSON]
maxAge = "10m"
dir = "/"
`
if runtime.GOOS == "windows" {
configStr = strings.Replace(configStr, "/", "c:\\\\", 1)
}
cfg, err := config.FromConfigString(configStr, "toml")
c.Assert(err, qt.IsNil)
fs := afero.NewMemMapFs()
_, err = DecodeConfig(fs, cfg)
c.Assert(err, qt.Not(qt.IsNil))
}
func newTestConfig() config.Provider {
cfg := config.NewWithTestDefaults()
cfg.Set("workingDir", filepath.FromSlash("/my/cool/hugoproject"))
cfg.Set("contentDir", "content")
cfg.Set("dataDir", "data")
cfg.Set("resourceDir", "resources")
cfg.Set("i18nDir", "i18n")
cfg.Set("layoutDir", "layouts")
cfg.Set("archetypeDir", "archetypes")
cfg.Set("assetDir", "assets")
return cfg
c.Assert(imgConfig.IsResourceDir, qt.Equals, true)
c.Assert(jsonConfig.IsResourceDir, qt.Equals, false)
}
+14 -7
View File
@@ -18,6 +18,7 @@ import (
"io"
"os"
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/hugofs"
"github.com/spf13/afero"
@@ -30,13 +31,12 @@ import (
func (c Caches) Prune() (int, error) {
counter := 0
for k, cache := range c {
count, err := cache.Prune(false)
counter += count
if err != nil {
if os.IsNotExist(err) {
if herrors.IsNotExist(err) {
continue
}
return counter, fmt.Errorf("failed to prune cache %q: %w", k, err)
@@ -57,6 +57,7 @@ func (c *Cache) Prune(force bool) (int, error) {
counter := 0
err := afero.Walk(c.Fs, "", func(name string, info os.FileInfo, err error) error {
if info == nil {
return nil
}
@@ -65,18 +66,24 @@ func (c *Cache) Prune(force bool) (int, error) {
if info.IsDir() {
f, err := c.Fs.Open(name)
if err != nil {
// This cache dir may not exist.
return nil
}
defer f.Close()
_, err = f.Readdirnames(1)
f.Close()
if err == io.EOF {
// Empty dir.
err = c.Fs.Remove(name)
if name == "." {
// e.g. /_gen/images -- keep it even if empty.
err = nil
} else {
err = c.Fs.Remove(name)
}
}
if err != nil && !os.IsNotExist(err) {
if err != nil && !herrors.IsNotExist(err) {
return err
}
@@ -97,7 +104,7 @@ func (c *Cache) Prune(force bool) (int, error) {
counter++
}
if err != nil && !os.IsNotExist(err) {
if err != nil && !herrors.IsNotExist(err) {
return err
}
@@ -112,7 +119,7 @@ func (c *Cache) Prune(force bool) (int, error) {
func (c *Cache) pruneRootDir(force bool) (int, error) {
info, err := c.Fs.Stat(c.pruneAllRootDir)
if err != nil {
if os.IsNotExist(err) {
if herrors.IsNotExist(err) {
return 0, nil
}
return 0, err
+7 -6
View File
@@ -11,13 +11,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package filecache
package filecache_test
import (
"fmt"
"testing"
"time"
"github.com/gohugoio/hugo/cache/filecache"
"github.com/spf13/afero"
qt "github.com/frankban/quicktest"
@@ -52,10 +53,10 @@ maxAge = "200ms"
dir = ":resourceDir/_gen"
`
for _, name := range []string{cacheKeyGetCSV, cacheKeyGetJSON, cacheKeyAssets, cacheKeyImages} {
for _, name := range []string{filecache.CacheKeyGetCSV, filecache.CacheKeyGetJSON, filecache.CacheKeyAssets, filecache.CacheKeyImages} {
msg := qt.Commentf("cache: %s", name)
p := newPathsSpec(t, afero.NewMemMapFs(), configStr)
caches, err := NewCaches(p)
caches, err := filecache.NewCaches(p)
c.Assert(err, qt.IsNil)
cache := caches[name]
for i := 0; i < 10; i++ {
@@ -75,7 +76,7 @@ dir = ":resourceDir/_gen"
for i := 0; i < 10; i++ {
id := fmt.Sprintf("i%d", i)
v := cache.getString(id)
v := cache.GetString(id)
if i < 5 {
c.Assert(v, qt.Equals, "")
} else {
@@ -83,7 +84,7 @@ dir = ":resourceDir/_gen"
}
}
caches, err = NewCaches(p)
caches, err = filecache.NewCaches(p)
c.Assert(err, qt.IsNil)
cache = caches[name]
// Touch one and then prune.
@@ -98,7 +99,7 @@ dir = ":resourceDir/_gen"
// Now only the i5 should be left.
for i := 0; i < 10; i++ {
id := fmt.Sprintf("i%d", i)
v := cache.getString(id)
v := cache.GetString(id)
if i != 5 {
c.Assert(v, qt.Equals, "")
} else {
+24 -87
View File
@@ -11,27 +11,22 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package filecache
package filecache_test
import (
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
"testing"
"time"
"github.com/gobwas/glob"
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/modules"
"github.com/gohugoio/hugo/cache/filecache"
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/testconfig"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
@@ -44,13 +39,8 @@ func TestFileCache(t *testing.T) {
t.Parallel()
c := qt.New(t)
tempWorkingDir, err := ioutil.TempDir("", "hugo_filecache_test_work")
c.Assert(err, qt.IsNil)
defer os.Remove(tempWorkingDir)
tempCacheDir, err := ioutil.TempDir("", "hugo_filecache_test_cache")
c.Assert(err, qt.IsNil)
defer os.Remove(tempCacheDir)
tempWorkingDir := t.TempDir()
tempCacheDir := t.TempDir()
osfs := afero.NewOsFs()
@@ -90,27 +80,19 @@ dir = ":cacheDir/c"
p := newPathsSpec(t, osfs, configStr)
caches, err := NewCaches(p)
caches, err := filecache.NewCaches(p)
c.Assert(err, qt.IsNil)
cache := caches.Get("GetJSON")
c.Assert(cache, qt.Not(qt.IsNil))
c.Assert(cache.maxAge.String(), qt.Equals, "10h0m0s")
bfs, ok := cache.Fs.(*afero.BasePathFs)
c.Assert(ok, qt.Equals, true)
filename, err := bfs.RealPath("key")
c.Assert(err, qt.IsNil)
if test.cacheDir != "" {
c.Assert(filename, qt.Equals, filepath.Join(test.cacheDir, "c/"+filecacheRootDirname+"/getjson/key"))
} else {
// Temp dir.
c.Assert(filename, qt.Matches, ".*hugo_cache.*"+filecacheRootDirname+".*key")
}
cache = caches.Get("Images")
c.Assert(cache, qt.Not(qt.IsNil))
c.Assert(cache.maxAge, qt.Equals, time.Duration(-1))
bfs, ok = cache.Fs.(*afero.BasePathFs)
c.Assert(ok, qt.Equals, true)
filename, _ = bfs.RealPath("key")
@@ -123,7 +105,7 @@ dir = ":cacheDir/c"
io.Closer
}{
strings.NewReader(s),
ioutil.NopCloser(nil),
io.NopCloser(nil),
}, nil
}
}
@@ -132,13 +114,13 @@ dir = ":cacheDir/c"
return []byte("bcd"), nil
}
for _, ca := range []*Cache{caches.ImageCache(), caches.AssetsCache(), caches.GetJSONCache(), caches.GetCSVCache()} {
for _, ca := range []*filecache.Cache{caches.ImageCache(), caches.AssetsCache(), caches.GetJSONCache(), caches.GetCSVCache()} {
for i := 0; i < 2; i++ {
info, r, err := ca.GetOrCreate("a", rf("abc"))
c.Assert(err, qt.IsNil)
c.Assert(r, qt.Not(qt.IsNil))
c.Assert(info.Name, qt.Equals, "a")
b, _ := ioutil.ReadAll(r)
b, _ := io.ReadAll(r)
r.Close()
c.Assert(string(b), qt.Equals, "abc")
@@ -154,7 +136,7 @@ dir = ":cacheDir/c"
_, r, err = ca.GetOrCreate("a", rf("bcd"))
c.Assert(err, qt.IsNil)
b, _ = ioutil.ReadAll(r)
b, _ = io.ReadAll(r)
r.Close()
c.Assert(string(b), qt.Equals, "abc")
}
@@ -167,13 +149,13 @@ dir = ":cacheDir/c"
c.Assert(info.Name, qt.Equals, "mykey")
io.WriteString(w, "Hugo is great!")
w.Close()
c.Assert(caches.ImageCache().getString("mykey"), qt.Equals, "Hugo is great!")
c.Assert(caches.ImageCache().GetString("mykey"), qt.Equals, "Hugo is great!")
info, r, err := caches.ImageCache().Get("mykey")
c.Assert(err, qt.IsNil)
c.Assert(r, qt.Not(qt.IsNil))
c.Assert(info.Name, qt.Equals, "mykey")
b, _ := ioutil.ReadAll(r)
b, _ := io.ReadAll(r)
r.Close()
c.Assert(string(b), qt.Equals, "Hugo is great!")
@@ -208,7 +190,7 @@ dir = "/cache/c"
p := newPathsSpec(t, afero.NewMemMapFs(), configStr)
caches, err := NewCaches(p)
caches, err := filecache.NewCaches(p)
c.Assert(err, qt.IsNil)
const cacheName = "getjson"
@@ -233,7 +215,7 @@ dir = "/cache/c"
return hugio.ToReadCloser(strings.NewReader(data)), nil
})
c.Assert(err, qt.IsNil)
b, _ := ioutil.ReadAll(r)
b, _ := io.ReadAll(r)
r.Close()
c.Assert(string(b), qt.Equals, data)
// Trigger some expiration.
@@ -251,24 +233,24 @@ func TestFileCacheReadOrCreateErrorInRead(t *testing.T) {
var result string
rf := func(failLevel int) func(info ItemInfo, r io.ReadSeeker) error {
return func(info ItemInfo, r io.ReadSeeker) error {
rf := func(failLevel int) func(info filecache.ItemInfo, r io.ReadSeeker) error {
return func(info filecache.ItemInfo, r io.ReadSeeker) error {
if failLevel > 0 {
if failLevel > 1 {
return ErrFatal
return filecache.ErrFatal
}
return errors.New("fail")
}
b, _ := ioutil.ReadAll(r)
b, _ := io.ReadAll(r)
result = string(b)
return nil
}
}
bf := func(s string) func(info ItemInfo, w io.WriteCloser) error {
return func(info ItemInfo, w io.WriteCloser) error {
bf := func(s string) func(info filecache.ItemInfo, w io.WriteCloser) error {
return func(info filecache.ItemInfo, w io.WriteCloser) error {
defer w.Close()
result = s
_, err := w.Write([]byte(s))
@@ -276,7 +258,7 @@ func TestFileCacheReadOrCreateErrorInRead(t *testing.T) {
}
}
cache := NewCache(afero.NewMemMapFs(), 100*time.Hour, "")
cache := filecache.NewCache(afero.NewMemMapFs(), 100*time.Hour, "")
const id = "a32"
@@ -290,60 +272,15 @@ func TestFileCacheReadOrCreateErrorInRead(t *testing.T) {
c.Assert(err, qt.IsNil)
c.Assert(result, qt.Equals, "v3")
_, err = cache.ReadOrCreate(id, rf(2), bf("v3"))
c.Assert(err, qt.Equals, ErrFatal)
}
func TestCleanID(t *testing.T) {
c := qt.New(t)
c.Assert(cleanID(filepath.FromSlash("/a/b//c.txt")), qt.Equals, filepath.FromSlash("a/b/c.txt"))
c.Assert(cleanID(filepath.FromSlash("a/b//c.txt")), qt.Equals, filepath.FromSlash("a/b/c.txt"))
}
func initConfig(fs afero.Fs, cfg config.Provider) error {
if _, err := langs.LoadLanguageSettings(cfg, nil); err != nil {
return err
}
modConfig, err := modules.DecodeConfig(cfg)
if err != nil {
return err
}
workingDir := cfg.GetString("workingDir")
themesDir := cfg.GetString("themesDir")
if !filepath.IsAbs(themesDir) {
themesDir = filepath.Join(workingDir, themesDir)
}
globAll := glob.MustCompile("**", '/')
modulesClient := modules.NewClient(modules.ClientConfig{
Fs: fs,
WorkingDir: workingDir,
ThemesDir: themesDir,
ModuleConfig: modConfig,
IgnoreVendor: globAll,
})
moduleConfig, err := modulesClient.Collect()
if err != nil {
return err
}
if err := modules.ApplyProjectConfigDefaults(cfg, moduleConfig.ActiveModules[len(moduleConfig.ActiveModules)-1]); err != nil {
return err
}
cfg.Set("allModules", moduleConfig.ActiveModules)
return nil
c.Assert(err, qt.Equals, filecache.ErrFatal)
}
func newPathsSpec(t *testing.T, fs afero.Fs, configStr string) *helpers.PathSpec {
c := qt.New(t)
cfg, err := config.FromConfigString(configStr, "toml")
c.Assert(err, qt.IsNil)
initConfig(fs, cfg)
config.SetBaseTestDefaults(cfg)
p, err := helpers.NewPathSpec(hugofs.NewFrom(fs, cfg), cfg, nil)
acfg := testconfig.GetTestConfig(fs, cfg)
p, err := helpers.NewPathSpec(hugofs.NewFrom(fs, acfg.BaseConfig()), acfg, nil)
c.Assert(err, qt.IsNil)
return p
}
+108
View File
@@ -0,0 +1,108 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package filecache_test
import (
"path/filepath"
"testing"
"time"
"github.com/bep/logg"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/htesting"
"github.com/gohugoio/hugo/hugolib"
)
// See issue #10781. That issue wouldn't have been triggered if we kept
// the empty root directories (e.g. _resources/gen/images).
// It's still an upstream Go issue that we also need to handle, but
// this is a test for the first part.
func TestPruneShouldPreserveEmptyCacheRoots(t *testing.T) {
files := `
-- hugo.toml --
baseURL = "https://example.com"
-- content/_index.md --
---
title: "Home"
---
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{T: t, TxtarString: files, RunGC: true, NeedsOsFS: true},
).Build()
_, err := b.H.BaseFs.ResourcesCache.Stat(filepath.Join("_gen", "images"))
b.Assert(err, qt.IsNil)
}
func TestPruneImages(t *testing.T) {
if htesting.IsCI() {
// TODO(bep)
t.Skip("skip flaky test on CI server")
}
files := `
-- hugo.toml --
baseURL = "https://example.com"
[caches]
[caches.images]
maxAge = "200ms"
dir = ":resourceDir/_gen"
-- content/_index.md --
---
title: "Home"
---
-- assets/a/pixel.png --
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
-- layouts/index.html --
{{ warnf "HOME!" }}
{{ $img := resources.GetMatch "**.png" }}
{{ $img = $img.Resize "3x3" }}
{{ $img.RelPermalink }}
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{T: t, TxtarString: files, Running: true, RunGC: true, NeedsOsFS: true, LogLevel: logg.LevelInfo},
).Build()
b.Assert(b.GCCount, qt.Equals, 0)
b.Assert(b.H, qt.IsNotNil)
imagesCacheDir := filepath.Join("_gen", "images")
_, err := b.H.BaseFs.ResourcesCache.Stat(imagesCacheDir)
b.Assert(err, qt.IsNil)
// TODO(bep) we need a way to test full rebuilds.
// For now, just sleep a little so the cache elements expires.
time.Sleep(300 * time.Millisecond)
b.RenameFile("assets/a/pixel.png", "assets/b/pixel2.png").Build()
b.Assert(b.GCCount, qt.Equals, 1)
// Build it again to GC the empty a dir.
b.Build()
_, err = b.H.BaseFs.ResourcesCache.Stat(filepath.Join(imagesCacheDir, "a"))
b.Assert(err, qt.Not(qt.IsNil))
_, err = b.H.BaseFs.ResourcesCache.Stat(imagesCacheDir)
b.Assert(err, qt.IsNil)
}
+10 -6
View File
@@ -452,12 +452,16 @@ func collectMethodsRecursive(pkg string, f []*ast.Field) []string {
}
if ident, ok := m.Type.(*ast.Ident); ok && ident.Obj != nil {
// Embedded interface
methodNames = append(
methodNames,
collectMethodsRecursive(
pkg,
ident.Obj.Decl.(*ast.TypeSpec).Type.(*ast.InterfaceType).Methods.List)...)
switch tt := ident.Obj.Decl.(*ast.TypeSpec).Type.(type) {
case *ast.InterfaceType:
// Embedded interface
methodNames = append(
methodNames,
collectMethodsRecursive(
pkg,
tt.Methods.List)...)
}
} else {
// Embedded, but in a different file/package. Return the
// package.Name and deal with that later.
+557 -452
View File
File diff suppressed because it is too large Load Diff
+20 -322
View File
@@ -1,4 +1,4 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,330 +14,28 @@
package commands
import (
"fmt"
"os"
"time"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/loggers"
hpaths "github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
"github.com/spf13/cobra"
"github.com/bep/simplecobra"
)
type commandsBuilder struct {
hugoBuilderCommon
commands []cmder
}
func newCommandsBuilder() *commandsBuilder {
return &commandsBuilder{}
}
func (b *commandsBuilder) addCommands(commands ...cmder) *commandsBuilder {
b.commands = append(b.commands, commands...)
return b
}
func (b *commandsBuilder) addAll() *commandsBuilder {
b.addCommands(
b.newServerCmd(),
newVersionCmd(),
newEnvCmd(),
b.newConfigCmd(),
b.newDeployCmd(),
b.newConvertCmd(),
b.newNewCmd(),
b.newListCmd(),
newImportCmd(),
newGenCmd(),
createReleaser(),
b.newModCmd(),
)
return b
}
func (b *commandsBuilder) build() *hugoCmd {
h := b.newHugoCmd()
addCommands(h.getCommand(), b.commands...)
return h
}
func addCommands(root *cobra.Command, commands ...cmder) {
for _, command := range commands {
cmd := command.getCommand()
if cmd == nil {
continue
}
root.AddCommand(cmd)
}
}
type baseCmd struct {
cmd *cobra.Command
}
var _ commandsBuilderGetter = (*baseBuilderCmd)(nil)
// Used in tests.
type commandsBuilderGetter interface {
getCommandsBuilder() *commandsBuilder
}
type baseBuilderCmd struct {
*baseCmd
*commandsBuilder
}
func (b *baseBuilderCmd) getCommandsBuilder() *commandsBuilder {
return b.commandsBuilder
}
func (c *baseCmd) getCommand() *cobra.Command {
return c.cmd
}
func newBaseCmd(cmd *cobra.Command) *baseCmd {
return &baseCmd{cmd: cmd}
}
func (b *commandsBuilder) newBuilderCmd(cmd *cobra.Command) *baseBuilderCmd {
bcmd := &baseBuilderCmd{commandsBuilder: b, baseCmd: &baseCmd{cmd: cmd}}
bcmd.hugoBuilderCommon.handleFlags(cmd)
return bcmd
}
func (b *commandsBuilder) newBuilderBasicCmd(cmd *cobra.Command) *baseBuilderCmd {
bcmd := &baseBuilderCmd{commandsBuilder: b, baseCmd: &baseCmd{cmd: cmd}}
bcmd.hugoBuilderCommon.handleCommonBuilderFlags(cmd)
return bcmd
}
func (c *baseCmd) flagsToConfig(cfg config.Provider) {
initializeFlags(c.cmd, cfg)
}
type hugoCmd struct {
*baseBuilderCmd
// Need to get the sites once built.
c *commandeer
}
var _ cmder = (*nilCommand)(nil)
type nilCommand struct{}
func (c *nilCommand) getCommand() *cobra.Command {
return nil
}
func (c *nilCommand) flagsToConfig(cfg config.Provider) {
}
func (b *commandsBuilder) newHugoCmd() *hugoCmd {
cc := &hugoCmd{}
cc.baseBuilderCmd = b.newBuilderCmd(&cobra.Command{
Use: "hugo",
Short: "hugo builds your site",
Long: `hugo is the main command, used to build your Hugo site.
Hugo is a Fast and Flexible Static Site Generator
built with love by spf13 and friends in Go.
Complete documentation is available at https://gohugo.io/.`,
RunE: func(cmd *cobra.Command, args []string) error {
defer cc.timeTrack(time.Now(), "Total")
cfgInit := func(c *commandeer) error {
if cc.buildWatch {
c.Set("disableLiveReload", true)
}
return nil
}
// prevent cobra printing error so it can be handled here (before the timeTrack prints)
cmd.SilenceErrors = true
c, err := initializeConfig(true, true, cc.buildWatch, &cc.hugoBuilderCommon, cc, cfgInit)
if err != nil {
cmd.PrintErrln("Error:", err.Error())
return err
}
cc.c = c
err = c.build()
if err != nil {
cmd.PrintErrln("Error:", err.Error())
}
return err
// newExec wires up all of Hugo's CLI.
func newExec() (*simplecobra.Exec, error) {
rootCmd := &rootCommand{
commands: []simplecobra.Commander{
newVersionCmd(),
newEnvCommand(),
newServerCommand(),
newDeployCommand(),
newConfigCommand(),
newNewCommand(),
newConvertCommand(),
newImportCommand(),
newListCommand(),
newModCommands(),
newGenCommand(),
newReleaseCommand(),
},
})
}
cc.cmd.PersistentFlags().StringVar(&cc.cfgFile, "config", "", "config file (default is path/config.yaml|json|toml)")
cc.cmd.PersistentFlags().StringVar(&cc.cfgDir, "configDir", "config", "config dir")
cc.cmd.PersistentFlags().BoolVar(&cc.quiet, "quiet", false, "build in quiet mode")
return simplecobra.New(rootCmd)
// Set bash-completion
_ = cc.cmd.PersistentFlags().SetAnnotation("config", cobra.BashCompFilenameExt, config.ValidConfigFileExtensions)
cc.cmd.PersistentFlags().BoolVarP(&cc.verbose, "verbose", "v", false, "verbose output")
cc.cmd.PersistentFlags().BoolVarP(&cc.debug, "debug", "", false, "debug output")
cc.cmd.PersistentFlags().BoolVar(&cc.logging, "log", false, "enable Logging")
cc.cmd.PersistentFlags().StringVar(&cc.logFile, "logFile", "", "log File path (if set, logging enabled automatically)")
cc.cmd.PersistentFlags().BoolVar(&cc.verboseLog, "verboseLog", false, "verbose logging")
cc.cmd.Flags().BoolVarP(&cc.buildWatch, "watch", "w", false, "watch filesystem for changes and recreate as needed")
cc.cmd.Flags().Bool("renderToMemory", false, "render to memory (only useful for benchmark testing)")
// Set bash-completion
_ = cc.cmd.PersistentFlags().SetAnnotation("logFile", cobra.BashCompFilenameExt, []string{})
cc.cmd.SetGlobalNormalizationFunc(helpers.NormalizeHugoFlags)
cc.cmd.SilenceUsage = true
return cc
}
type hugoBuilderCommon struct {
source string
baseURL string
environment string
buildWatch bool
poll string
clock string
gc bool
// Profile flags (for debugging of performance problems)
cpuprofile string
memprofile string
mutexprofile string
traceprofile string
printm bool
// TODO(bep) var vs string
logging bool
verbose bool
verboseLog bool
debug bool
quiet bool
cfgFile string
cfgDir string
logFile string
}
func (cc *hugoBuilderCommon) timeTrack(start time.Time, name string) {
if cc.quiet {
return
}
elapsed := time.Since(start)
fmt.Printf("%s in %v ms\n", name, int(1000*elapsed.Seconds()))
}
func (cc *hugoBuilderCommon) getConfigDir(baseDir string) string {
if cc.cfgDir != "" {
return hpaths.AbsPathify(baseDir, cc.cfgDir)
}
if v, found := os.LookupEnv("HUGO_CONFIGDIR"); found {
return hpaths.AbsPathify(baseDir, v)
}
return hpaths.AbsPathify(baseDir, "config")
}
func (cc *hugoBuilderCommon) getEnvironment(isServer bool) string {
if cc.environment != "" {
return cc.environment
}
if v, found := os.LookupEnv("HUGO_ENVIRONMENT"); found {
return v
}
// Used by Netlify and Forestry
if v, found := os.LookupEnv("HUGO_ENV"); found {
return v
}
if isServer {
return hugo.EnvironmentDevelopment
}
return hugo.EnvironmentProduction
}
func (cc *hugoBuilderCommon) handleCommonBuilderFlags(cmd *cobra.Command) {
cmd.PersistentFlags().StringVarP(&cc.source, "source", "s", "", "filesystem path to read files relative from")
cmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{})
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) {
cc.handleCommonBuilderFlags(cmd)
cmd.Flags().Bool("cleanDestinationDir", false, "remove files from destination not found in static directories")
cmd.Flags().BoolP("buildDrafts", "D", false, "include content marked as draft")
cmd.Flags().BoolP("buildFuture", "F", false, "include content with publishdate in the future")
cmd.Flags().BoolP("buildExpired", "E", false, "include expired content")
cmd.Flags().StringP("contentDir", "c", "", "filesystem path to content directory")
cmd.Flags().StringP("layoutDir", "l", "", "filesystem path to layout directory")
cmd.Flags().StringP("cacheDir", "", "", "filesystem path to cache directory. Defaults: $TMPDIR/hugo_cache/")
cmd.Flags().BoolP("ignoreCache", "", false, "ignores the cache directory")
cmd.Flags().StringP("destination", "d", "", "filesystem path to write files to")
cmd.Flags().StringSliceP("theme", "t", []string{}, "themes to use (located in /themes/THEMENAME/)")
cmd.Flags().StringVarP(&cc.baseURL, "baseURL", "b", "", "hostname (and path) to the root, e.g. https://spf13.com/")
cmd.Flags().Bool("enableGitInfo", false, "add Git revision, date, author, and CODEOWNERS info to the pages")
cmd.Flags().BoolVar(&cc.gc, "gc", false, "enable to run some cleanup tasks (remove unused cache files) after the build")
cmd.Flags().StringVar(&cc.poll, "poll", "", "set this to a poll interval, e.g --poll 700ms, to use a poll based approach to watch for file system changes")
cmd.Flags().BoolVar(&loggers.PanicOnWarning, "panicOnWarning", false, "panic on first WARNING log")
cmd.Flags().Bool("templateMetrics", false, "display metrics about template executions")
cmd.Flags().Bool("templateMetricsHints", false, "calculate some improvement hints when combined with --templateMetrics")
cmd.Flags().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.")
cmd.Flags().StringVarP(&cc.cpuprofile, "profile-cpu", "", "", "write cpu profile to `file`")
cmd.Flags().StringVarP(&cc.memprofile, "profile-mem", "", "", "write memory profile to `file`")
cmd.Flags().BoolVarP(&cc.printm, "printMemoryUsage", "", false, "print memory usage to screen at intervals")
cmd.Flags().StringVarP(&cc.mutexprofile, "profile-mutex", "", "", "write Mutex profile to `file`")
cmd.Flags().StringVarP(&cc.traceprofile, "trace", "", "", "write trace to `file` (not useful in general)")
// Hide these for now.
cmd.Flags().MarkHidden("profile-cpu")
cmd.Flags().MarkHidden("profile-mem")
cmd.Flags().MarkHidden("profile-mutex")
cmd.Flags().StringSlice("disableKinds", []string{}, "disable different kind of pages (home, RSS etc.)")
cmd.Flags().Bool("minify", false, "minify any supported output format (HTML, XML etc.)")
// Set bash-completion.
// Each flag must first be defined before using the SetAnnotation() call.
_ = cmd.Flags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{})
_ = cmd.Flags().SetAnnotation("cacheDir", cobra.BashCompSubdirsInDir, []string{})
_ = cmd.Flags().SetAnnotation("destination", cobra.BashCompSubdirsInDir, []string{})
_ = cmd.Flags().SetAnnotation("theme", cobra.BashCompSubdirsInDir, []string{"themes"})
}
func checkErr(logger loggers.Logger, err error, s ...string) {
if err == nil {
return
}
for _, message := range s {
logger.Errorln(message)
}
logger.Errorln(err)
}
-410
View File
@@ -1,410 +0,0 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/gohugoio/hugo/config"
"github.com/spf13/afero"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/common/types"
"github.com/spf13/cobra"
qt "github.com/frankban/quicktest"
)
func TestExecute(t *testing.T) {
c := qt.New(t)
createSite := func(c *qt.C) string {
dir := createSimpleTestSite(t, testSiteConfig{})
return dir
}
c.Run("hugo", func(c *qt.C) {
dir := createSite(c)
resp := Execute([]string{"-s=" + dir})
c.Assert(resp.Err, qt.IsNil)
result := resp.Result
c.Assert(len(result.Sites) == 1, qt.Equals, true)
c.Assert(len(result.Sites[0].RegularPages()) == 2, qt.Equals, true)
c.Assert(result.Sites[0].Info.Params()["myparam"], qt.Equals, "paramproduction")
})
c.Run("hugo, set environment", func(c *qt.C) {
dir := createSite(c)
resp := Execute([]string{"-s=" + dir, "-e=staging"})
c.Assert(resp.Err, qt.IsNil)
result := resp.Result
c.Assert(result.Sites[0].Info.Params()["myparam"], qt.Equals, "paramstaging")
})
c.Run("convert toJSON", func(c *qt.C) {
dir := createSite(c)
output := filepath.Join(dir, "myjson")
resp := Execute([]string{"convert", "toJSON", "-s=" + dir, "-e=staging", "-o=" + output})
c.Assert(resp.Err, qt.IsNil)
converted := readFileFrom(c, filepath.Join(output, "content", "p1.md"))
c.Assert(converted, qt.Equals, "{\n \"title\": \"P1\",\n \"weight\": 1\n}\n\nContent\n\n", qt.Commentf(converted))
})
c.Run("config, set environment", func(c *qt.C) {
dir := createSite(c)
out, err := captureStdout(func() error {
resp := Execute([]string{"config", "-s=" + dir, "-e=staging"})
return resp.Err
})
c.Assert(err, qt.IsNil)
c.Assert(out, qt.Contains, "params = map[myparam:paramstaging]", qt.Commentf(out))
})
c.Run("deploy, environment set", func(c *qt.C) {
dir := createSite(c)
resp := Execute([]string{"deploy", "-s=" + dir, "-e=staging", "--target=mydeployment", "--dryRun"})
c.Assert(resp.Err, qt.Not(qt.IsNil))
c.Assert(resp.Err.Error(), qt.Contains, `no driver registered for "hugocloud"`)
})
c.Run("list", func(c *qt.C) {
dir := createSite(c)
out, err := captureStdout(func() error {
resp := Execute([]string{"list", "all", "-s=" + dir, "-e=staging"})
return resp.Err
})
c.Assert(err, qt.IsNil)
c.Assert(out, qt.Contains, "p1.md")
})
c.Run("new theme", func(c *qt.C) {
dir := createSite(c)
themesDir := filepath.Join(dir, "mythemes")
resp := Execute([]string{"new", "theme", "mytheme", "-s=" + dir, "-e=staging", "--themesDir=" + themesDir})
c.Assert(resp.Err, qt.IsNil)
themeTOML := readFileFrom(c, filepath.Join(themesDir, "mytheme", "theme.toml"))
c.Assert(themeTOML, qt.Contains, "name = \"Mytheme\"")
})
c.Run("new site", func(c *qt.C) {
dir := createSite(c)
siteDir := filepath.Join(dir, "mysite")
resp := Execute([]string{"new", "site", siteDir, "-e=staging"})
c.Assert(resp.Err, qt.IsNil)
config := readFileFrom(c, filepath.Join(siteDir, "config.toml"))
c.Assert(config, qt.Contains, "baseURL = 'http://example.org/'")
checkNewSiteInited(c, siteDir)
})
}
func checkNewSiteInited(c *qt.C, basepath string) {
paths := []string{
filepath.Join(basepath, "layouts"),
filepath.Join(basepath, "content"),
filepath.Join(basepath, "archetypes"),
filepath.Join(basepath, "static"),
filepath.Join(basepath, "data"),
filepath.Join(basepath, "config.toml"),
}
for _, path := range paths {
_, err := os.Stat(path)
c.Assert(err, qt.IsNil)
}
}
func readFileFrom(c *qt.C, filename string) string {
c.Helper()
filename = filepath.Clean(filename)
b, err := afero.ReadFile(hugofs.Os, filename)
c.Assert(err, qt.IsNil)
return string(b)
}
func TestFlags(t *testing.T) {
c := qt.New(t)
noOpRunE := func(cmd *cobra.Command, args []string) error {
return nil
}
tests := []struct {
name string
args []string
check func(c *qt.C, cmd *serverCmd)
}{
{
// https://github.com/gohugoio/hugo/issues/7642
name: "ignoreVendorPaths",
args: []string{"server", "--ignoreVendorPaths=github.com/**"},
check: func(c *qt.C, cmd *serverCmd) {
cfg := config.NewWithTestDefaults()
cmd.flagsToConfig(cfg)
c.Assert(cfg.Get("ignoreVendorPaths"), qt.Equals, "github.com/**")
},
},
{
name: "Persistent flags",
args: []string{
"server",
"--config=myconfig.toml",
"--configDir=myconfigdir",
"--contentDir=mycontent",
"--disableKinds=page,home",
"--environment=testing",
"--configDir=myconfigdir",
"--layoutDir=mylayouts",
"--theme=mytheme",
"--gc",
"--themesDir=mythemes",
"--cleanDestinationDir",
"--navigateToChanged",
"--disableLiveReload",
"--noHTTPCache",
"--printI18nWarnings",
"--destination=/tmp/mydestination",
"-b=https://example.com/b/",
"--port=1366",
"--renderToDisk",
"--source=mysource",
"--printPathWarnings",
"--printUnusedTemplates",
},
check: func(c *qt.C, sc *serverCmd) {
c.Assert(sc, qt.Not(qt.IsNil))
c.Assert(sc.navigateToChanged, qt.Equals, true)
c.Assert(sc.disableLiveReload, qt.Equals, true)
c.Assert(sc.noHTTPCache, qt.Equals, true)
c.Assert(sc.renderToDisk, qt.Equals, true)
c.Assert(sc.serverPort, qt.Equals, 1366)
c.Assert(sc.environment, qt.Equals, "testing")
cfg := config.NewWithTestDefaults()
sc.flagsToConfig(cfg)
c.Assert(cfg.GetString("publishDir"), qt.Equals, "/tmp/mydestination")
c.Assert(cfg.GetString("contentDir"), qt.Equals, "mycontent")
c.Assert(cfg.GetString("layoutDir"), qt.Equals, "mylayouts")
c.Assert(cfg.GetStringSlice("theme"), qt.DeepEquals, []string{"mytheme"})
c.Assert(cfg.GetString("themesDir"), qt.Equals, "mythemes")
c.Assert(cfg.GetString("baseURL"), qt.Equals, "https://example.com/b/")
c.Assert(cfg.Get("disableKinds"), qt.DeepEquals, []string{"page", "home"})
c.Assert(cfg.GetBool("gc"), qt.Equals, true)
// The flag is named printPathWarnings
c.Assert(cfg.GetBool("logPathWarnings"), qt.Equals, true)
// The flag is named printI18nWarnings
c.Assert(cfg.GetBool("logI18nWarnings"), qt.Equals, true)
},
},
}
for _, test := range tests {
c.Run(test.name, func(c *qt.C) {
b := newCommandsBuilder()
root := b.addAll().build()
for _, cmd := range b.commands {
if cmd.getCommand() == nil {
continue
}
// We are only intereseted in the flag handling here.
cmd.getCommand().RunE = noOpRunE
}
rootCmd := root.getCommand()
rootCmd.SetArgs(test.args)
c.Assert(rootCmd.Execute(), qt.IsNil)
test.check(c, b.commands[0].(*serverCmd))
})
}
}
func TestCommandsExecute(t *testing.T) {
c := qt.New(t)
dir := createSimpleTestSite(t, testSiteConfig{})
dirOut := t.TempDir()
sourceFlag := fmt.Sprintf("-s=%s", dir)
tests := []struct {
commands []string
flags []string
expectErrToContain string
}{
// TODO(bep) permission issue on my OSX? "operation not permitted" {[]string{"check", "ulimit"}, nil, false},
{[]string{"env"}, nil, ""},
{[]string{"version"}, nil, ""},
// no args = hugo build
{nil, []string{sourceFlag}, ""},
{nil, []string{sourceFlag, "--renderToMemory"}, ""},
{[]string{"completion", "bash"}, nil, ""},
{[]string{"completion", "fish"}, nil, ""},
{[]string{"completion", "powershell"}, nil, ""},
{[]string{"completion", "zsh"}, nil, ""},
{[]string{"config"}, []string{sourceFlag}, ""},
{[]string{"convert", "toTOML"}, []string{sourceFlag, "-o=" + filepath.Join(dirOut, "toml")}, ""},
{[]string{"convert", "toYAML"}, []string{sourceFlag, "-o=" + filepath.Join(dirOut, "yaml")}, ""},
{[]string{"convert", "toJSON"}, []string{sourceFlag, "-o=" + filepath.Join(dirOut, "json")}, ""},
{[]string{"gen", "chromastyles"}, []string{"--style=manni"}, ""},
{[]string{"gen", "doc"}, []string{"--dir=" + filepath.Join(dirOut, "doc")}, ""},
{[]string{"gen", "man"}, []string{"--dir=" + filepath.Join(dirOut, "man")}, ""},
{[]string{"list", "drafts"}, []string{sourceFlag}, ""},
{[]string{"list", "expired"}, []string{sourceFlag}, ""},
{[]string{"list", "future"}, []string{sourceFlag}, ""},
{[]string{"new", "new-page.md"}, []string{sourceFlag}, ""},
{[]string{"new", "site", filepath.Join(dirOut, "new-site")}, nil, ""},
{[]string{"unknowncommand"}, nil, "unknown command"},
// TODO(bep) cli refactor fix https://github.com/gohugoio/hugo/issues/4450
//{[]string{"new", "theme", filepath.Join(dirOut, "new-theme")}, nil,false},
}
for _, test := range tests {
name := "hugo"
if len(test.commands) > 0 {
name = test.commands[0]
}
c.Run(name, func(c *qt.C) {
b := newCommandsBuilder().addAll().build()
hugoCmd := b.getCommand()
test.flags = append(test.flags, "--quiet")
hugoCmd.SetArgs(append(test.commands, test.flags...))
// TODO(bep) capture output and add some simple asserts
// TODO(bep) misspelled subcommands does not return an error. We should investigate this
// but before that, check for "Error: unknown command".
_, err := hugoCmd.ExecuteC()
if test.expectErrToContain != "" {
c.Assert(err, qt.Not(qt.IsNil))
c.Assert(err.Error(), qt.Contains, test.expectErrToContain)
} else {
c.Assert(err, qt.IsNil)
}
// Assert that we have not left any development debug artifacts in
// the code.
if b.c != nil {
_, ok := b.c.publishDirFs.(types.DevMarker)
c.Assert(ok, qt.Equals, false)
}
})
}
}
type testSiteConfig struct {
configTOML string
contentDir string
}
func createSimpleTestSite(t testing.TB, cfg testSiteConfig) string {
dir := t.TempDir()
cfgStr := `
baseURL = "https://example.org"
title = "Hugo Commands"
`
contentDir := "content"
if cfg.configTOML != "" {
cfgStr = cfg.configTOML
}
if cfg.contentDir != "" {
contentDir = cfg.contentDir
}
os.MkdirAll(filepath.Join(dir, "public"), 0777)
// Just the basic. These are for CLI tests, not site testing.
writeFile(t, filepath.Join(dir, "config.toml"), cfgStr)
writeFile(t, filepath.Join(dir, "config", "staging", "params.toml"), `myparam="paramstaging"`)
writeFile(t, filepath.Join(dir, "config", "staging", "deployment.toml"), `
[[targets]]
name = "mydeployment"
URL = "hugocloud://hugotestbucket"
`)
writeFile(t, filepath.Join(dir, "config", "testing", "params.toml"), `myparam="paramtesting"`)
writeFile(t, filepath.Join(dir, "config", "production", "params.toml"), `myparam="paramproduction"`)
writeFile(t, filepath.Join(dir, "static", "myfile.txt"), `Hello World!`)
writeFile(t, filepath.Join(dir, contentDir, "p1.md"), `
---
title: "P1"
weight: 1
---
Content
`)
writeFile(t, filepath.Join(dir, contentDir, "hügö.md"), `
---
weight: 2
---
This is hügö.
`)
writeFile(t, filepath.Join(dir, "layouts", "_default", "single.html"), `
Single: {{ .Title }}|{{ .Content }}
`)
writeFile(t, filepath.Join(dir, "layouts", "404.html"), `
404: {{ .Title }}|Not Found.
`)
writeFile(t, filepath.Join(dir, "layouts", "_default", "list.html"), `
List: {{ .Title }}
Environment: {{ hugo.Environment }}
For issue 9788:
{{ $foo :="abc" | resources.FromString "foo.css" | minify | resources.PostProcess }}
PostProcess: {{ $foo.RelPermalink }}
`)
return dir
}
func writeFile(t testing.TB, filename, content string) {
must(t, os.MkdirAll(filepath.Dir(filename), os.FileMode(0755)))
must(t, ioutil.WriteFile(filename, []byte(content), os.FileMode(0755)))
}
func must(t testing.TB, err error) {
if err != nil {
t.Fatal(err)
}
}
+138 -90
View File
@@ -1,4 +1,4 @@
// Copyright 2015 The Hugo Authors. All rights reserved.
// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -9,128 +9,135 @@
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.Print the version number of Hug
// limitations under the License.
package commands
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os"
"reflect"
"regexp"
"sort"
"strings"
"time"
"github.com/gohugoio/hugo/common/maps"
"github.com/bep/simplecobra"
"github.com/gohugoio/hugo/config/allconfig"
"github.com/gohugoio/hugo/modules"
"github.com/gohugoio/hugo/parser"
"github.com/gohugoio/hugo/parser/metadecoders"
"github.com/gohugoio/hugo/modules"
"github.com/spf13/cobra"
)
var _ cmder = (*configCmd)(nil)
type configCmd struct {
*baseBuilderCmd
}
func (b *commandsBuilder) newConfigCmd() *configCmd {
cc := &configCmd{}
cmd := &cobra.Command{
Use: "config",
Short: "Print the site configuration",
Long: `Print the site configuration, both default and custom settings.`,
RunE: cc.printConfig,
// newConfigCommand creates a new config command and its subcommands.
func newConfigCommand() *configCommand {
return &configCommand{
commands: []simplecobra.Commander{
&configMountsCommand{},
},
}
printMountsCmd := &cobra.Command{
Use: "mounts",
Short: "Print the configured file mounts",
RunE: cc.printMounts,
}
cmd.AddCommand(printMountsCmd)
cc.baseBuilderCmd = b.newBuilderBasicCmd(cmd)
return cc
}
func (c *configCmd) printMounts(cmd *cobra.Command, args []string) error {
cfg, err := initializeConfig(true, false, false, &c.hugoBuilderCommon, c, nil)
type configCommand struct {
r *rootCommand
format string
lang string
commands []simplecobra.Commander
}
func (c *configCommand) Commands() []simplecobra.Commander {
return c.commands
}
func (c *configCommand) Name() string {
return "config"
}
func (c *configCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
conf, err := c.r.ConfigFromProvider(c.r.configVersionID.Load(), flagsToCfg(cd, nil))
if err != nil {
return err
}
var config *allconfig.Config
if c.lang != "" {
var found bool
config, found = conf.configs.LanguageConfigMap[c.lang]
if !found {
return fmt.Errorf("language %q not found", c.lang)
}
} else {
config = conf.configs.LanguageConfigSlice[0]
}
allModules := cfg.Cfg.Get("allmodules").(modules.Modules)
var buf bytes.Buffer
dec := json.NewEncoder(&buf)
dec.SetIndent("", " ")
dec.SetEscapeHTML(false)
for _, m := range allModules {
if err := parser.InterfaceToConfig(&modMounts{m: m, verbose: c.verbose}, metadecoders.JSON, os.Stdout); err != nil {
if err := dec.Encode(parser.ReplacingJSONMarshaller{Value: config, KeysToLower: true, OmitEmpty: true}); err != nil {
return err
}
format := strings.ToLower(c.format)
switch format {
case "json":
os.Stdout.Write(buf.Bytes())
default:
// Decode the JSON to a map[string]interface{} and then unmarshal it again to the correct format.
var m map[string]interface{}
if err := json.Unmarshal(buf.Bytes(), &m); err != nil {
return err
}
}
return nil
}
func (c *configCmd) printConfig(cmd *cobra.Command, args []string) error {
cfg, err := initializeConfig(true, false, false, &c.hugoBuilderCommon, c, nil)
if err != nil {
return err
}
allSettings := cfg.Cfg.Get("").(maps.Params)
// We need to clean up this, but we store objects in the config that
// isn't really interesting to the end user, so filter these.
ignoreKeysRe := regexp.MustCompile("client|sorted|filecacheconfigs|allmodules|multilingual")
separator := ": "
if len(cfg.configFiles) > 0 && strings.HasSuffix(cfg.configFiles[0], ".toml") {
separator = " = "
}
var keys []string
for k := range allSettings {
if ignoreKeysRe.MatchString(k) {
continue
}
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
kv := reflect.ValueOf(allSettings[k])
if kv.Kind() == reflect.String {
fmt.Printf("%s%s\"%+v\"\n", k, separator, allSettings[k])
} else {
fmt.Printf("%s%s%+v\n", k, separator, allSettings[k])
switch format {
case "yaml":
return parser.InterfaceToConfig(m, metadecoders.YAML, os.Stdout)
case "toml":
return parser.InterfaceToConfig(m, metadecoders.TOML, os.Stdout)
default:
return fmt.Errorf("unsupported format: %q", format)
}
}
return nil
}
type modMounts struct {
verbose bool
m modules.Module
func (c *configCommand) Init(cd *simplecobra.Commandeer) error {
c.r = cd.Root.Command.(*rootCommand)
cmd := cd.CobraCommand
cmd.Short = "Print the site configuration"
cmd.Long = `Print the site configuration, both default and custom settings.`
cmd.Flags().StringVar(&c.format, "format", "toml", "preferred file format (toml, yaml or json)")
cmd.Flags().StringVar(&c.lang, "lang", "", "the language to display config for. Defaults to the first language defined.")
applyLocalFlagsBuildConfig(cmd, c.r)
return nil
}
type modMount struct {
func (c *configCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
return nil
}
type configModMount struct {
Source string `json:"source"`
Target string `json:"target"`
Lang string `json:"lang,omitempty"`
}
func (m *modMounts) MarshalJSON() ([]byte, error) {
var mounts []modMount
type configModMounts struct {
verbose bool
m modules.Module
}
// MarshalJSON is for internal use only.
func (m *configModMounts) MarshalJSON() ([]byte, error) {
var mounts []configModMount
for _, mount := range m.m.Mounts() {
mounts = append(mounts, modMount{
mounts = append(mounts, configModMount{
Source: mount.Source,
Target: mount.Target,
Lang: mount.Lang,
@@ -153,7 +160,7 @@ func (m *modMounts) MarshalJSON() ([]byte, error) {
Meta map[string]any `json:"meta"`
HugoVersion modules.HugoVersion `json:"hugoVersion"`
Mounts []modMount `json:"mounts"`
Mounts []configModMount `json:"mounts"`
}{
Path: m.m.Path(),
Version: m.m.Version(),
@@ -167,12 +174,12 @@ func (m *modMounts) MarshalJSON() ([]byte, error) {
}
return json.Marshal(&struct {
Path string `json:"path"`
Version string `json:"version"`
Time time.Time `json:"time"`
Owner string `json:"owner"`
Dir string `json:"dir"`
Mounts []modMount `json:"mounts"`
Path string `json:"path"`
Version string `json:"version"`
Time time.Time `json:"time"`
Owner string `json:"owner"`
Dir string `json:"dir"`
Mounts []configModMount `json:"mounts"`
}{
Path: m.m.Path(),
Version: m.m.Version(),
@@ -183,3 +190,44 @@ func (m *modMounts) MarshalJSON() ([]byte, error) {
})
}
type configMountsCommand struct {
r *rootCommand
configCmd *configCommand
}
func (c *configMountsCommand) Commands() []simplecobra.Commander {
return nil
}
func (c *configMountsCommand) Name() string {
return "mounts"
}
func (c *configMountsCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
r := c.configCmd.r
conf, err := r.ConfigFromProvider(r.configVersionID.Load(), flagsToCfg(cd, nil))
if err != nil {
return err
}
for _, m := range conf.configs.Modules {
if err := parser.InterfaceToConfig(&configModMounts{m: m, verbose: r.isVerbose()}, metadecoders.JSON, os.Stdout); err != nil {
return err
}
}
return nil
}
func (c *configMountsCommand) Init(cd *simplecobra.Commandeer) error {
c.r = cd.Root.Command.(*rootCommand)
cmd := cd.CobraCommand
cmd.Short = "Print the configured file mounts"
applyLocalFlagsBuildConfig(cmd, c.r)
return nil
}
func (c *configMountsCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
c.configCmd = cd.Parent.Command.(*configCommand)
return nil
}
+121 -103
View File
@@ -1,4 +1,4 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,122 +15,121 @@ package commands
import (
"bytes"
"context"
"fmt"
"path/filepath"
"strings"
"time"
"github.com/gohugoio/hugo/parser/pageparser"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/hugofs"
"github.com/bep/simplecobra"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/hugolib"
"github.com/gohugoio/hugo/parser"
"github.com/gohugoio/hugo/parser/metadecoders"
"github.com/gohugoio/hugo/hugolib"
"github.com/gohugoio/hugo/parser/pageparser"
"github.com/gohugoio/hugo/resources/page"
"github.com/spf13/cobra"
)
var _ cmder = (*convertCmd)(nil)
func newConvertCommand() *convertCommand {
var c *convertCommand
c = &convertCommand{
commands: []simplecobra.Commander{
&simpleCommand{
name: "toJSON",
short: "Convert front matter to JSON",
long: `toJSON converts all front matter in the content directory
to use JSON for the front matter.`,
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
return c.convertContents(metadecoders.JSON)
},
withc: func(cmd *cobra.Command, r *rootCommand) {
},
},
&simpleCommand{
name: "toTOML",
short: "Convert front matter to TOML",
long: `toTOML converts all front matter in the content directory
to use TOML for the front matter.`,
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
return c.convertContents(metadecoders.TOML)
},
withc: func(cmd *cobra.Command, r *rootCommand) {
},
},
&simpleCommand{
name: "toYAML",
short: "Convert front matter to YAML",
long: `toYAML converts all front matter in the content directory
to use YAML for the front matter.`,
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
return c.convertContents(metadecoders.YAML)
},
withc: func(cmd *cobra.Command, r *rootCommand) {
},
},
},
}
return c
}
type convertCmd struct {
type convertCommand struct {
// Flags.
outputDir string
unsafe bool
*baseBuilderCmd
// Deps.
r *rootCommand
h *hugolib.HugoSites
// Commands.
commands []simplecobra.Commander
}
func (b *commandsBuilder) newConvertCmd() *convertCmd {
cc := &convertCmd{}
cmd := &cobra.Command{
Use: "convert",
Short: "Convert your content to different formats",
Long: `Convert your content (e.g. front matter) to different formats.
See convert's subcommands toJSON, toTOML and toYAML for more information.`,
RunE: nil,
}
cmd.AddCommand(
&cobra.Command{
Use: "toJSON",
Short: "Convert front matter to JSON",
Long: `toJSON converts all front matter in the content directory
to use JSON for the front matter.`,
RunE: func(cmd *cobra.Command, args []string) error {
return cc.convertContents(metadecoders.JSON)
},
},
&cobra.Command{
Use: "toTOML",
Short: "Convert front matter to TOML",
Long: `toTOML converts all front matter in the content directory
to use TOML for the front matter.`,
RunE: func(cmd *cobra.Command, args []string) error {
return cc.convertContents(metadecoders.TOML)
},
},
&cobra.Command{
Use: "toYAML",
Short: "Convert front matter to YAML",
Long: `toYAML converts all front matter in the content directory
to use YAML for the front matter.`,
RunE: func(cmd *cobra.Command, args []string) error {
return cc.convertContents(metadecoders.YAML)
},
},
)
cmd.PersistentFlags().StringVarP(&cc.outputDir, "output", "o", "", "filesystem path to write files to")
cmd.PersistentFlags().BoolVar(&cc.unsafe, "unsafe", false, "enable less safe operations, please backup first")
cc.baseBuilderCmd = b.newBuilderBasicCmd(cmd)
return cc
func (c *convertCommand) Commands() []simplecobra.Commander {
return c.commands
}
func (cc *convertCmd) convertContents(format metadecoders.Format) error {
if cc.outputDir == "" && !cc.unsafe {
return newUserError("Unsafe operation not allowed, use --unsafe or set a different output path")
}
func (c *convertCommand) Name() string {
return "convert"
}
c, err := initializeConfig(true, false, false, &cc.hugoBuilderCommon, cc, nil)
if err != nil {
return err
}
c.Cfg.Set("buildDrafts", true)
h, err := hugolib.NewHugoSites(*c.DepsCfg)
if err != nil {
return err
}
if err := h.Build(hugolib.BuildCfg{SkipRender: true}); err != nil {
return err
}
site := h.Sites[0]
site.Log.Println("processing", len(site.AllPages()), "content files")
for _, p := range site.AllPages() {
if err := cc.convertAndSavePage(p, site, format); err != nil {
return err
}
}
func (c *convertCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
return nil
}
func (cc *convertCmd) convertAndSavePage(p page.Page, site *hugolib.Site, targetFormat metadecoders.Format) error {
func (c *convertCommand) Init(cd *simplecobra.Commandeer) error {
cmd := cd.CobraCommand
cmd.Short = "Convert your content to different formats"
cmd.Long = `Convert your content (e.g. front matter) to different formats.
See convert's subcommands toJSON, toTOML and toYAML for more information.`
cmd.PersistentFlags().StringVarP(&c.outputDir, "output", "o", "", "filesystem path to write files to")
cmd.PersistentFlags().BoolVar(&c.unsafe, "unsafe", false, "enable less safe operations, please backup first")
cmd.RunE = nil
return nil
}
func (c *convertCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
c.r = cd.Root.Command.(*rootCommand)
cfg := config.New()
cfg.Set("buildDrafts", true)
h, err := c.r.Hugo(flagsToCfg(cd, cfg))
if err != nil {
return err
}
c.h = h
return nil
}
func (c *convertCommand) convertAndSavePage(p page.Page, site *hugolib.Site, targetFormat metadecoders.Format) error {
// The resources are not in .Site.AllPages.
for _, r := range p.Resources().ByType("page") {
if err := cc.convertAndSavePage(r.(page.Page), site, targetFormat); err != nil {
if err := c.convertAndSavePage(r.(page.Page), site, targetFormat); err != nil {
return err
}
}
@@ -140,9 +139,9 @@ func (cc *convertCmd) convertAndSavePage(p page.Page, site *hugolib.Site, target
return nil
}
errMsg := fmt.Errorf("Error processing file %q", p.File().Path())
errMsg := fmt.Errorf("error processing file %q", p.File().Path())
site.Log.Infoln("Attempting to convert", p.File().Filename())
site.Log.Infoln("attempting to convert", p.File().Filename())
f := p.File()
file, err := f.FileInfo().Meta().Open()
@@ -182,26 +181,45 @@ func (cc *convertCmd) convertAndSavePage(p page.Page, site *hugolib.Site, target
newFilename := p.File().Filename()
if cc.outputDir != "" {
if c.outputDir != "" {
contentDir := strings.TrimSuffix(newFilename, p.File().Path())
contentDir = filepath.Base(contentDir)
newFilename = filepath.Join(cc.outputDir, contentDir, p.File().Path())
newFilename = filepath.Join(c.outputDir, contentDir, p.File().Path())
}
fs := hugofs.Os
if err := helpers.WriteToDisk(newFilename, &newContent, fs); err != nil {
return fmt.Errorf("Failed to save file %q:: %w", newFilename, err)
return fmt.Errorf("failed to save file %q:: %w", newFilename, err)
}
return nil
}
type parsedFile struct {
frontMatterFormat metadecoders.Format
frontMatterSource []byte
frontMatter map[string]any
func (c *convertCommand) convertContents(format metadecoders.Format) error {
if c.outputDir == "" && !c.unsafe {
return newUserError("Unsafe operation not allowed, use --unsafe or set a different output path")
}
// Everything after Front Matter
content []byte
if err := c.h.Build(hugolib.BuildCfg{SkipRender: true}); err != nil {
return err
}
site := c.h.Sites[0]
var pagesBackedByFile page.Pages
for _, p := range site.AllPages() {
if p.File().IsZero() {
continue
}
pagesBackedByFile = append(pagesBackedByFile, p)
}
site.Log.Println("processing", len(pagesBackedByFile), "content files")
for _, p := range site.AllPages() {
if err := c.convertAndSavePage(p, site, format); err != nil {
return err
}
}
return nil
}
+33 -48
View File
@@ -1,4 +1,4 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,73 +14,58 @@
//go:build !nodeploy
// +build !nodeploy
// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
import (
"context"
"github.com/bep/simplecobra"
"github.com/gohugoio/hugo/deploy"
"github.com/spf13/cobra"
)
var _ cmder = (*deployCmd)(nil)
func newDeployCommand() simplecobra.Commander {
// deployCmd supports deploying sites to Cloud providers.
type deployCmd struct {
*baseBuilderCmd
invalidateCDN bool
maxDeletes int
}
// TODO: In addition to the "deploy" command, consider adding a "--deploy"
// flag for the default command; this would build the site and then deploy it.
// It's not obvious how to do this; would all of the deploy-specific flags
// have to exist at the top level as well?
// TODO: The output files change every time "hugo" is executed, it looks
// like because of map order randomization. This means that you can
// run "hugo && hugo deploy" again and again and upload new stuff every time. Is
// this intended?
func (b *commandsBuilder) newDeployCmd() *deployCmd {
cc := &deployCmd{}
cmd := &cobra.Command{
Use: "deploy",
Short: "Deploy your site to a Cloud provider.",
Long: `Deploy your site to a Cloud provider.
return &simpleCommand{
name: "deploy",
short: "Deploy your site to a Cloud provider.",
long: `Deploy your site to a Cloud provider.
See https://gohugo.io/hosting-and-deployment/hugo-deploy/ for detailed
documentation.
`,
RunE: func(cmd *cobra.Command, args []string) error {
cfgInit := func(c *commandeer) error {
c.Set("invalidateCDN", cc.invalidateCDN)
c.Set("maxDeletes", cc.maxDeletes)
return nil
}
comm, err := initializeConfig(true, true, false, &cc.hugoBuilderCommon, cc, cfgInit)
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
h, err := r.Hugo(flagsToCfgWithAdditionalConfigBase(cd, nil, "deployment"))
if err != nil {
return err
}
deployer, err := deploy.New(comm.Cfg, comm.hugo().PathSpec.PublishFs)
deployer, err := deploy.New(h.Configs.GetFirstLanguageConfig(), h.Log, h.PathSpec.PublishFs)
if err != nil {
return err
}
return deployer.Deploy(context.Background())
return deployer.Deploy(ctx)
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.Flags().String("target", "", "target deployment from deployments section in config file; defaults to the first one")
cmd.Flags().Bool("confirm", false, "ask for confirmation before making changes to the target")
cmd.Flags().Bool("dryRun", false, "dry run")
cmd.Flags().Bool("force", false, "force upload of all files")
cmd.Flags().Bool("invalidateCDN", deploy.DefaultConfig.InvalidateCDN, "invalidate the CDN cache listed in the deployment target")
cmd.Flags().Int("maxDeletes", deploy.DefaultConfig.MaxDeletes, "maximum # of files to delete, or -1 to disable")
cmd.Flags().Int("workers", deploy.DefaultConfig.Workers, "number of workers to transfer files. defaults to 10")
},
}
cmd.Flags().String("target", "", "target deployment from deployments section in config file; defaults to the first one")
cmd.Flags().Bool("confirm", false, "ask for confirmation before making changes to the target")
cmd.Flags().Bool("dryRun", false, "dry run")
cmd.Flags().Bool("force", false, "force upload of all files")
cmd.Flags().BoolVar(&cc.invalidateCDN, "invalidateCDN", true, "invalidate the CDN cache listed in the deployment target")
cmd.Flags().IntVar(&cc.maxDeletes, "maxDeletes", 256, "maximum # of files to delete, or -1 to disable")
cc.baseBuilderCmd = b.newBuilderBasicCmd(cmd)
return cc
}
+49
View File
@@ -0,0 +1,49 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build nodeploy
// +build nodeploy
// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
import (
"context"
"github.com/bep/simplecobra"
"github.com/spf13/cobra"
)
func newDeployCommand() simplecobra.Commander {
return &simpleCommand{
name: "deploy",
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
return nil
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.Hidden = true
},
}
}
+39 -36
View File
@@ -1,4 +1,4 @@
// Copyright 2016 The Hugo Authors. All rights reserved.
// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,47 +14,50 @@
package commands
import (
"context"
"runtime"
"github.com/bep/simplecobra"
"github.com/gohugoio/hugo/common/hugo"
"github.com/spf13/cobra"
jww "github.com/spf13/jwalterweatherman"
)
var _ cmder = (*envCmd)(nil)
func newEnvCommand() simplecobra.Commander {
return &simpleCommand{
name: "env",
short: "Print Hugo version and environment info",
long: "Print Hugo version and environment info. This is useful in Hugo bug reports",
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
r.Printf("%s\n", hugo.BuildVersionString())
r.Printf("GOOS=%q\n", runtime.GOOS)
r.Printf("GOARCH=%q\n", runtime.GOARCH)
r.Printf("GOVERSION=%q\n", runtime.Version())
type envCmd struct {
*baseCmd
}
func newEnvCmd() *envCmd {
return &envCmd{
baseCmd: newBaseCmd(&cobra.Command{
Use: "env",
Short: "Print Hugo version and environment info",
Long: `Print Hugo version and environment info. This is useful in Hugo bug reports.
If you add the -v flag, you will get a full dependency list.
`,
RunE: func(cmd *cobra.Command, args []string) error {
printHugoVersion()
jww.FEEDBACK.Printf("GOOS=%q\n", runtime.GOOS)
jww.FEEDBACK.Printf("GOARCH=%q\n", runtime.GOARCH)
jww.FEEDBACK.Printf("GOVERSION=%q\n", runtime.Version())
isVerbose, _ := cmd.Flags().GetBool("verbose")
if isVerbose {
deps := hugo.GetDependencyList()
for _, dep := range deps {
jww.FEEDBACK.Printf("%s\n", dep)
}
if r.isVerbose() {
deps := hugo.GetDependencyList()
for _, dep := range deps {
r.Printf("%s\n", dep)
}
return nil
},
}),
} else {
// These are also included in the GetDependencyList above;
// always print these as these are most likely the most useful to know about.
deps := hugo.GetDependencyListNonGo()
for _, dep := range deps {
r.Printf("%s\n", dep)
}
}
return nil
},
}
}
func newVersionCmd() simplecobra.Commander {
return &simpleCommand{
name: "version",
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
r.Println(hugo.BuildVersionString())
return nil
},
short: "Print Hugo version and environment info",
long: "Print Hugo version and environment info. This is useful in Hugo bug reports.",
}
}
+231 -17
View File
@@ -1,4 +1,4 @@
// Copyright 2015 The Hugo Authors. All rights reserved.
// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,27 +14,241 @@
package commands
import (
"context"
"encoding/json"
"fmt"
"os"
"path"
"path/filepath"
"strings"
"github.com/alecthomas/chroma/v2"
"github.com/alecthomas/chroma/v2/formatters/html"
"github.com/alecthomas/chroma/v2/styles"
"github.com/bep/simplecobra"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/docshelper"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
)
var _ cmder = (*genCmd)(nil)
func newGenCommand() *genCommand {
var (
// Flags.
gendocdir string
genmandir string
// Chroma flags.
style string
highlightStyle string
linesStyle string
)
newChromaStyles := func() simplecobra.Commander {
return &simpleCommand{
name: "chromastyles",
short: "Generate CSS stylesheet for the Chroma code highlighter",
long: `Generate CSS stylesheet for the Chroma code highlighter for a given style. This stylesheet is needed if markup.highlight.noClasses is disabled in config.
See https://xyproto.github.io/splash/docs/all.html for a preview of the available styles`,
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
builder := styles.Get(style).Builder()
if highlightStyle != "" {
builder.Add(chroma.LineHighlight, highlightStyle)
}
if linesStyle != "" {
builder.Add(chroma.LineNumbers, linesStyle)
}
style, err := builder.Build()
if err != nil {
return err
}
formatter := html.New(html.WithAllClasses(true))
formatter.WriteCSS(os.Stdout, style)
return nil
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.PersistentFlags().StringVar(&style, "style", "friendly", "highlighter style (see https://xyproto.github.io/splash/docs/)")
cmd.PersistentFlags().StringVar(&highlightStyle, "highlightStyle", "bg:#ffffcc", "style used for highlighting lines (see https://github.com/alecthomas/chroma)")
cmd.PersistentFlags().StringVar(&linesStyle, "linesStyle", "", "style used for line numbers (see https://github.com/alecthomas/chroma)")
},
}
}
newMan := func() simplecobra.Commander {
return &simpleCommand{
name: "man",
short: "Generate man pages for the Hugo CLI",
long: `This command automatically generates up-to-date man pages of Hugo's
command-line interface. By default, it creates the man page files
in the "man" directory under the current directory.`,
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
header := &doc.GenManHeader{
Section: "1",
Manual: "Hugo Manual",
Source: fmt.Sprintf("Hugo %s", hugo.CurrentVersion),
}
if !strings.HasSuffix(genmandir, helpers.FilePathSeparator) {
genmandir += helpers.FilePathSeparator
}
if found, _ := helpers.Exists(genmandir, hugofs.Os); !found {
r.Println("Directory", genmandir, "does not exist, creating...")
if err := hugofs.Os.MkdirAll(genmandir, 0777); err != nil {
return err
}
}
cd.CobraCommand.Root().DisableAutoGenTag = true
r.Println("Generating Hugo man pages in", genmandir, "...")
doc.GenManTree(cd.CobraCommand.Root(), header, genmandir)
r.Println("Done.")
return nil
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.PersistentFlags().StringVar(&genmandir, "dir", "man/", "the directory to write the man pages.")
// For bash-completion
cmd.PersistentFlags().SetAnnotation("dir", cobra.BashCompSubdirsInDir, []string{})
},
}
}
newGen := func() simplecobra.Commander {
const gendocFrontmatterTemplate = `---
title: "%s"
slug: %s
url: %s
---
`
return &simpleCommand{
name: "doc",
short: "Generate Markdown documentation for the Hugo CLI.",
long: `Generate Markdown documentation for the Hugo CLI.
This command is, mostly, used to create up-to-date documentation
of Hugo's command-line interface for https://gohugo.io/.
It creates one Markdown file per command with front matter suitable
for rendering in Hugo.`,
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
cd.CobraCommand.VisitParents(func(c *cobra.Command) {
// Disable the "Auto generated by spf13/cobra on DATE"
// as it creates a lot of diffs.
c.DisableAutoGenTag = true
})
if !strings.HasSuffix(gendocdir, helpers.FilePathSeparator) {
gendocdir += helpers.FilePathSeparator
}
if found, _ := helpers.Exists(gendocdir, hugofs.Os); !found {
r.Println("Directory", gendocdir, "does not exist, creating...")
if err := hugofs.Os.MkdirAll(gendocdir, 0777); err != nil {
return err
}
}
prepender := func(filename string) string {
name := filepath.Base(filename)
base := strings.TrimSuffix(name, path.Ext(name))
url := "/commands/" + strings.ToLower(base) + "/"
return fmt.Sprintf(gendocFrontmatterTemplate, strings.Replace(base, "_", " ", -1), base, url)
}
linkHandler := func(name string) string {
base := strings.TrimSuffix(name, path.Ext(name))
return "/commands/" + strings.ToLower(base) + "/"
}
r.Println("Generating Hugo command-line documentation in", gendocdir, "...")
doc.GenMarkdownTreeCustom(cd.CobraCommand.Root(), gendocdir, prepender, linkHandler)
r.Println("Done.")
return nil
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.PersistentFlags().StringVar(&gendocdir, "dir", "/tmp/hugodoc/", "the directory to write the doc.")
// For bash-completion
cmd.PersistentFlags().SetAnnotation("dir", cobra.BashCompSubdirsInDir, []string{})
},
}
}
var docsHelperTarget string
newDocsHelper := func() simplecobra.Commander {
return &simpleCommand{
name: "docshelper",
short: "Generate some data files for the Hugo docs.",
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
r.Println("Generate docs data to", docsHelperTarget)
targetFile := filepath.Join(docsHelperTarget, "docs.json")
f, err := os.Create(targetFile)
if err != nil {
return err
}
defer f.Close()
enc := json.NewEncoder(f)
enc.SetIndent("", " ")
if err := enc.Encode(docshelper.GetDocProvider()); err != nil {
return err
}
r.Println("Done!")
return nil
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.Hidden = true
cmd.PersistentFlags().StringVarP(&docsHelperTarget, "dir", "", "docs/data", "data dir")
},
}
}
return &genCommand{
commands: []simplecobra.Commander{
newChromaStyles(),
newGen(),
newMan(),
newDocsHelper(),
},
}
type genCmd struct {
*baseCmd
}
func newGenCmd() *genCmd {
cc := &genCmd{}
cc.baseCmd = newBaseCmd(&cobra.Command{
Use: "gen",
Short: "A collection of several useful generators.",
})
type genCommand struct {
rootCmd *rootCommand
cc.cmd.AddCommand(
newGenDocCmd().getCommand(),
newGenManCmd().getCommand(),
createGenDocsHelper().getCommand(),
createGenChromaStyles().getCommand())
return cc
commands []simplecobra.Commander
}
func (c *genCommand) Commands() []simplecobra.Commander {
return c.commands
}
func (c *genCommand) Name() string {
return "gen"
}
func (c *genCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
return nil
}
func (c *genCommand) Init(cd *simplecobra.Commandeer) error {
cmd := cd.CobraCommand
cmd.Short = "A collection of several useful generators."
cmd.RunE = nil
return nil
}
func (c *genCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
c.rootCmd = cd.Root.Command.(*rootCommand)
return nil
}
-72
View File
@@ -1,72 +0,0 @@
// Copyright 2017-present The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
import (
"os"
"github.com/alecthomas/chroma/v2"
"github.com/alecthomas/chroma/v2/formatters/html"
"github.com/alecthomas/chroma/v2/styles"
"github.com/spf13/cobra"
)
var _ cmder = (*genChromaStyles)(nil)
type genChromaStyles struct {
style string
highlightStyle string
linesStyle string
*baseCmd
}
// TODO(bep) highlight
func createGenChromaStyles() *genChromaStyles {
g := &genChromaStyles{
baseCmd: newBaseCmd(&cobra.Command{
Use: "chromastyles",
Short: "Generate CSS stylesheet for the Chroma code highlighter",
Long: `Generate CSS stylesheet for the Chroma code highlighter for a given style. This stylesheet is needed if markup.highlight.noClasses is disabled in config.
See https://xyproto.github.io/splash/docs/all.html for a preview of the available styles`,
}),
}
g.cmd.RunE = func(cmd *cobra.Command, args []string) error {
return g.generate()
}
g.cmd.PersistentFlags().StringVar(&g.style, "style", "friendly", "highlighter style (see https://xyproto.github.io/splash/docs/)")
g.cmd.PersistentFlags().StringVar(&g.highlightStyle, "highlightStyle", "bg:#ffffcc", "style used for highlighting lines (see https://github.com/alecthomas/chroma)")
g.cmd.PersistentFlags().StringVar(&g.linesStyle, "linesStyle", "", "style used for line numbers (see https://github.com/alecthomas/chroma)")
return g
}
func (g *genChromaStyles) generate() error {
builder := styles.Get(g.style).Builder()
if g.highlightStyle != "" {
builder.Add(chroma.LineHighlight, g.highlightStyle)
}
if g.linesStyle != "" {
builder.Add(chroma.LineNumbers, g.linesStyle)
}
style, err := builder.Build()
if err != nil {
return err
}
formatter := html.New(html.WithAllClasses(true))
formatter.WriteCSS(os.Stdout, style)
return nil
}
-98
View File
@@ -1,98 +0,0 @@
// Copyright 2016 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
import (
"fmt"
"path"
"path/filepath"
"strings"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
jww "github.com/spf13/jwalterweatherman"
)
var _ cmder = (*genDocCmd)(nil)
type genDocCmd struct {
gendocdir string
*baseCmd
}
func newGenDocCmd() *genDocCmd {
const gendocFrontmatterTemplate = `---
title: "%s"
slug: %s
url: %s
---
`
cc := &genDocCmd{}
cc.baseCmd = newBaseCmd(&cobra.Command{
Use: "doc",
Short: "Generate Markdown documentation for the Hugo CLI.",
Long: `Generate Markdown documentation for the Hugo CLI.
This command is, mostly, used to create up-to-date documentation
of Hugo's command-line interface for https://gohugo.io/.
It creates one Markdown file per command with front matter suitable
for rendering in Hugo.`,
RunE: func(cmd *cobra.Command, args []string) error {
cmd.VisitParents(func(c *cobra.Command) {
// Disable the "Auto generated by spf13/cobra on DATE"
// as it creates a lot of diffs.
c.DisableAutoGenTag = true
})
if !strings.HasSuffix(cc.gendocdir, helpers.FilePathSeparator) {
cc.gendocdir += helpers.FilePathSeparator
}
if found, _ := helpers.Exists(cc.gendocdir, hugofs.Os); !found {
jww.FEEDBACK.Println("Directory", cc.gendocdir, "does not exist, creating...")
if err := hugofs.Os.MkdirAll(cc.gendocdir, 0777); err != nil {
return err
}
}
prepender := func(filename string) string {
name := filepath.Base(filename)
base := strings.TrimSuffix(name, path.Ext(name))
url := "/commands/" + strings.ToLower(base) + "/"
return fmt.Sprintf(gendocFrontmatterTemplate, strings.Replace(base, "_", " ", -1), base, url)
}
linkHandler := func(name string) string {
base := strings.TrimSuffix(name, path.Ext(name))
return "/commands/" + strings.ToLower(base) + "/"
}
jww.FEEDBACK.Println("Generating Hugo command-line documentation in", cc.gendocdir, "...")
doc.GenMarkdownTreeCustom(cmd.Root(), cc.gendocdir, prepender, linkHandler)
jww.FEEDBACK.Println("Done.")
return nil
},
})
cc.cmd.PersistentFlags().StringVar(&cc.gendocdir, "dir", "/tmp/hugodoc/", "the directory to write the doc.")
// For bash-completion
cc.cmd.PersistentFlags().SetAnnotation("dir", cobra.BashCompSubdirsInDir, []string{})
return cc
}
-71
View File
@@ -1,71 +0,0 @@
// Copyright 2017-present The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"github.com/gohugoio/hugo/docshelper"
"github.com/spf13/cobra"
)
var _ cmder = (*genDocsHelper)(nil)
type genDocsHelper struct {
target string
*baseCmd
}
func createGenDocsHelper() *genDocsHelper {
g := &genDocsHelper{
baseCmd: newBaseCmd(&cobra.Command{
Use: "docshelper",
Short: "Generate some data files for the Hugo docs.",
Hidden: true,
}),
}
g.cmd.RunE = func(cmd *cobra.Command, args []string) error {
return g.generate()
}
g.cmd.PersistentFlags().StringVarP(&g.target, "dir", "", "docs/data", "data dir")
return g
}
func (g *genDocsHelper) generate() error {
fmt.Println("Generate docs data to", g.target)
targetFile := filepath.Join(g.target, "docs.json")
f, err := os.Create(targetFile)
if err != nil {
return err
}
defer f.Close()
enc := json.NewEncoder(f)
enc.SetIndent("", " ")
if err := enc.Encode(docshelper.GetDocProvider()); err != nil {
return err
}
fmt.Println("Done!")
return nil
}
-77
View File
@@ -1,77 +0,0 @@
// Copyright 2016 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
import (
"fmt"
"strings"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
jww "github.com/spf13/jwalterweatherman"
)
var _ cmder = (*genManCmd)(nil)
type genManCmd struct {
genmandir string
*baseCmd
}
func newGenManCmd() *genManCmd {
cc := &genManCmd{}
cc.baseCmd = newBaseCmd(&cobra.Command{
Use: "man",
Short: "Generate man pages for the Hugo CLI",
Long: `This command automatically generates up-to-date man pages of Hugo's
command-line interface. By default, it creates the man page files
in the "man" directory under the current directory.`,
RunE: func(cmd *cobra.Command, args []string) error {
header := &doc.GenManHeader{
Section: "1",
Manual: "Hugo Manual",
Source: fmt.Sprintf("Hugo %s", hugo.CurrentVersion),
}
if !strings.HasSuffix(cc.genmandir, helpers.FilePathSeparator) {
cc.genmandir += helpers.FilePathSeparator
}
if found, _ := helpers.Exists(cc.genmandir, hugofs.Os); !found {
jww.FEEDBACK.Println("Directory", cc.genmandir, "does not exist, creating...")
if err := hugofs.Os.MkdirAll(cc.genmandir, 0777); err != nil {
return err
}
}
cmd.Root().DisableAutoGenTag = true
jww.FEEDBACK.Println("Generating Hugo man pages in", cc.genmandir, "...")
doc.GenManTree(cmd.Root(), header, cc.genmandir)
jww.FEEDBACK.Println("Done.")
return nil
},
})
cc.cmd.PersistentFlags().StringVar(&cc.genmandir, "dir", "man/", "the directory to write the man pages.")
// For bash-completion
cc.cmd.PersistentFlags().SetAnnotation("dir", cobra.BashCompSubdirsInDir, []string{})
return cc
}
+98 -43
View File
@@ -1,4 +1,4 @@
// Copyright 2018 The Hugo Authors. All rights reserved.
// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -11,16 +11,22 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Package commands defines and implements command-line commands and flags
// used by Hugo. Commands and flags are implemented using Cobra.
package commands
import (
"bytes"
"errors"
"fmt"
"regexp"
"log"
"os"
"path/filepath"
"strings"
"github.com/bep/simplecobra"
"github.com/gohugoio/hugo/config"
"github.com/spf13/cobra"
"github.com/gohugoio/hugo/helpers"
"github.com/spf13/afero"
"github.com/spf13/pflag"
)
const (
@@ -30,50 +36,99 @@ const (
showCursor = ansiEsc + "[?25h"
)
type flagsToConfigHandler interface {
flagsToConfig(cfg config.Provider)
func newUserError(a ...any) *simplecobra.CommandError {
return &simplecobra.CommandError{Err: errors.New(fmt.Sprint(a...))}
}
type cmder interface {
flagsToConfigHandler
getCommand() *cobra.Command
func setValueFromFlag(flags *pflag.FlagSet, key string, cfg config.Provider, targetKey string, force bool) {
key = strings.TrimSpace(key)
if (force && flags.Lookup(key) != nil) || flags.Changed(key) {
f := flags.Lookup(key)
configKey := key
if targetKey != "" {
configKey = targetKey
}
// Gotta love this API.
switch f.Value.Type() {
case "bool":
bv, _ := flags.GetBool(key)
cfg.Set(configKey, bv)
case "string":
cfg.Set(configKey, f.Value.String())
case "stringSlice":
bv, _ := flags.GetStringSlice(key)
cfg.Set(configKey, bv)
case "int":
iv, _ := flags.GetInt(key)
cfg.Set(configKey, iv)
default:
panic(fmt.Sprintf("update switch with %s", f.Value.Type()))
}
}
}
// commandError is an error used to signal different error situations in command handling.
type commandError struct {
s string
userError bool
func flagsToCfg(cd *simplecobra.Commandeer, cfg config.Provider) config.Provider {
return flagsToCfgWithAdditionalConfigBase(cd, cfg, "")
}
func (c commandError) Error() string {
return c.s
}
func (c commandError) isUserError() bool {
return c.userError
}
func newUserError(a ...any) commandError {
return commandError{s: fmt.Sprintln(a...), userError: true}
}
func newSystemError(a ...any) commandError {
return commandError{s: fmt.Sprintln(a...), userError: false}
}
func newSystemErrorF(format string, a ...any) commandError {
return commandError{s: fmt.Sprintf(format, a...), userError: false}
}
// Catch some of the obvious user errors from Cobra.
// We don't want to show the usage message for every error.
// The below may be to generic. Time will show.
var userErrorRegexp = regexp.MustCompile("unknown flag")
func isUserError(err error) bool {
if cErr, ok := err.(commandError); ok && cErr.isUserError() {
return true
func flagsToCfgWithAdditionalConfigBase(cd *simplecobra.Commandeer, cfg config.Provider, additionalConfigBase string) config.Provider {
if cfg == nil {
cfg = config.New()
}
return userErrorRegexp.MatchString(err.Error())
// Flags with a different name in the config.
keyMap := map[string]string{
"minify": "minifyOutput",
"destination": "publishDir",
"editor": "newContentEditor",
}
// Flags that we for some reason don't want to expose in the site config.
internalKeySet := map[string]bool{
"quiet": true,
"verbose": true,
"watch": true,
"disableLiveReload": true,
"liveReloadPort": true,
"renderToMemory": true,
"clock": true,
}
cmd := cd.CobraCommand
flags := cmd.Flags()
flags.VisitAll(func(f *pflag.Flag) {
if f.Changed {
targetKey := f.Name
if internalKeySet[targetKey] {
targetKey = "internal." + targetKey
} else if mapped, ok := keyMap[targetKey]; ok {
targetKey = mapped
}
setValueFromFlag(flags, f.Name, cfg, targetKey, false)
if additionalConfigBase != "" {
setValueFromFlag(flags, f.Name, cfg, additionalConfigBase+"."+targetKey, true)
}
}
})
return cfg
}
func mkdir(x ...string) {
p := filepath.Join(x...)
err := os.MkdirAll(p, 0777) // before umask
if err != nil {
log.Fatal(err)
}
}
func touchFile(fs afero.Fs, filename string) {
mkdir(filepath.Dir(filename))
err := helpers.WriteToDisk(filename, bytes.NewReader([]byte{}), fs)
if err != nil {
log.Fatal(err)
}
}
-1256
View File
File diff suppressed because it is too large Load Diff
-206
View File
@@ -1,206 +0,0 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package 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)
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)
}
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))
}
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2015 The Hugo Authors. All rights reserved.
// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
File diff suppressed because it is too large Load Diff
+404 -384
View File
@@ -1,4 +1,4 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,252 +15,97 @@ package commands
import (
"bytes"
"context"
"errors"
"fmt"
"io/ioutil"
"io"
"log"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
"unicode"
"github.com/gohugoio/hugo/parser/pageparser"
"github.com/bep/simplecobra"
"github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/parser/metadecoders"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/parser"
"github.com/gohugoio/hugo/parser/metadecoders"
"github.com/gohugoio/hugo/parser/pageparser"
"github.com/spf13/afero"
"github.com/spf13/cobra"
jww "github.com/spf13/jwalterweatherman"
)
var _ cmder = (*importCmd)(nil)
type importCmd struct {
*baseCmd
}
func newImportCmd() *importCmd {
cc := &importCmd{}
cc.baseCmd = newBaseCmd(&cobra.Command{
Use: "import",
Short: "Import your site from others.",
Long: `Import your site from other web site generators like Jekyll.
Import requires a subcommand, e.g. ` + "`hugo import jekyll jekyll_root_path target_path`.",
RunE: nil,
})
importJekyllCmd := &cobra.Command{
Use: "jekyll",
Short: "hugo import from Jekyll",
Long: `hugo import from Jekyll.
func newImportCommand() *importCommand {
var c *importCommand
c = &importCommand{
commands: []simplecobra.Commander{
&simpleCommand{
name: "jekyll",
short: "hugo import from Jekyll",
long: `hugo import from Jekyll.
Import from Jekyll requires two paths, e.g. ` + "`hugo import jekyll jekyll_root_path target_path`.",
RunE: cc.importFromJekyll,
}
importJekyllCmd.Flags().Bool("force", false, "allow import into non-empty target directory")
cc.cmd.AddCommand(importJekyllCmd)
return cc
}
func (i *importCmd) importFromJekyll(cmd *cobra.Command, args []string) error {
if len(args) < 2 {
return newUserError(`import from jekyll requires two paths, e.g. ` + "`hugo import jekyll jekyll_root_path target_path`.")
}
jekyllRoot, err := filepath.Abs(filepath.Clean(args[0]))
if err != nil {
return newUserError("path error:", args[0])
}
targetDir, err := filepath.Abs(filepath.Clean(args[1]))
if err != nil {
return newUserError("path error:", args[1])
}
jww.INFO.Println("Import Jekyll from:", jekyllRoot, "to:", targetDir)
if strings.HasPrefix(filepath.Dir(targetDir), jekyllRoot) {
return newUserError("abort: target path should not be inside the Jekyll root")
}
forceImport, _ := cmd.Flags().GetBool("force")
fs := afero.NewOsFs()
jekyllPostDirs, hasAnyPost := i.getJekyllDirInfo(fs, jekyllRoot)
if !hasAnyPost {
return errors.New("abort: jekyll root contains neither posts nor drafts")
}
err = i.createSiteFromJekyll(jekyllRoot, targetDir, jekyllPostDirs, forceImport)
if err != nil {
return newUserError(err)
}
jww.FEEDBACK.Println("Importing...")
fileCount := 0
callback := func(path string, fi hugofs.FileMetaInfo, err error) error {
if err != nil {
return err
}
if fi.IsDir() {
return nil
}
relPath, err := filepath.Rel(jekyllRoot, path)
if err != nil {
return newUserError("get rel path error:", path)
}
relPath = filepath.ToSlash(relPath)
draft := false
switch {
case strings.Contains(relPath, "_posts/"):
relPath = filepath.Join("content/post", strings.Replace(relPath, "_posts/", "", -1))
case strings.Contains(relPath, "_drafts/"):
relPath = filepath.Join("content/draft", strings.Replace(relPath, "_drafts/", "", -1))
draft = true
default:
return nil
}
fileCount++
return convertJekyllPost(path, relPath, targetDir, draft)
}
for jekyllPostDir, hasAnyPostInDir := range jekyllPostDirs {
if hasAnyPostInDir {
if err = helpers.SymbolicWalk(hugofs.Os, filepath.Join(jekyllRoot, jekyllPostDir), callback); err != nil {
return err
}
}
}
jww.FEEDBACK.Println("Congratulations!", fileCount, "post(s) imported!")
jww.FEEDBACK.Println("Now, start Hugo by yourself:\n" +
"$ git clone https://github.com/spf13/herring-cove.git " + args[1] + "/themes/herring-cove")
jww.FEEDBACK.Println("$ cd " + args[1] + "\n$ hugo server --theme=herring-cove")
return nil
}
func (i *importCmd) getJekyllDirInfo(fs afero.Fs, jekyllRoot string) (map[string]bool, bool) {
postDirs := make(map[string]bool)
hasAnyPost := false
if entries, err := ioutil.ReadDir(jekyllRoot); err == nil {
for _, entry := range entries {
if entry.IsDir() {
subDir := filepath.Join(jekyllRoot, entry.Name())
if isPostDir, hasAnyPostInDir := i.retrieveJekyllPostDir(fs, subDir); isPostDir {
postDirs[entry.Name()] = hasAnyPostInDir
if hasAnyPostInDir {
hasAnyPost = true
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
if len(args) < 2 {
return newUserError(`import from jekyll requires two paths, e.g. ` + "`hugo import jekyll jekyll_root_path target_path`.")
}
}
}
}
}
return postDirs, hasAnyPost
}
func (i *importCmd) retrieveJekyllPostDir(fs afero.Fs, dir string) (bool, bool) {
if strings.HasSuffix(dir, "_posts") || strings.HasSuffix(dir, "_drafts") {
isEmpty, _ := helpers.IsEmpty(dir, fs)
return true, !isEmpty
}
if entries, err := ioutil.ReadDir(dir); err == nil {
for _, entry := range entries {
if entry.IsDir() {
subDir := filepath.Join(dir, entry.Name())
if isPostDir, hasAnyPost := i.retrieveJekyllPostDir(fs, subDir); isPostDir {
return isPostDir, hasAnyPost
}
}
}
}
return false, true
}
func (i *importCmd) createSiteFromJekyll(jekyllRoot, targetDir string, jekyllPostDirs map[string]bool, force bool) error {
fs := &afero.OsFs{}
if exists, _ := helpers.Exists(targetDir, fs); exists {
if isDir, _ := helpers.IsDir(targetDir, fs); !isDir {
return errors.New("target path \"" + targetDir + "\" exists but is not a directory")
}
isEmpty, _ := helpers.IsEmpty(targetDir, fs)
if !isEmpty && !force {
return errors.New("target path \"" + targetDir + "\" exists and is not empty")
}
}
jekyllConfig := i.loadJekyllConfig(fs, jekyllRoot)
mkdir(targetDir, "layouts")
mkdir(targetDir, "content")
mkdir(targetDir, "archetypes")
mkdir(targetDir, "static")
mkdir(targetDir, "data")
mkdir(targetDir, "themes")
i.createConfigFromJekyll(fs, targetDir, "yaml", jekyllConfig)
i.copyJekyllFilesAndFolders(jekyllRoot, filepath.Join(targetDir, "static"), jekyllPostDirs)
return nil
}
func (i *importCmd) loadJekyllConfig(fs afero.Fs, jekyllRoot string) map[string]any {
path := filepath.Join(jekyllRoot, "_config.yml")
exists, err := helpers.Exists(path, fs)
if err != nil || !exists {
jww.WARN.Println("_config.yaml not found: Is the specified Jekyll root correct?")
return nil
}
f, err := fs.Open(path)
if err != nil {
return nil
}
defer f.Close()
b, err := ioutil.ReadAll(f)
if err != nil {
return nil
}
c, err := metadecoders.Default.UnmarshalToMap(b, metadecoders.YAML)
if err != nil {
return nil
return c.importFromJekyll(args)
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.Flags().BoolVar(&c.force, "force", false, "allow import into non-empty target directory")
},
},
},
}
return c
}
func (i *importCmd) createConfigFromJekyll(fs afero.Fs, inpath string, kind metadecoders.Format, jekyllConfig map[string]any) (err error) {
type importCommand struct {
r *rootCommand
force bool
commands []simplecobra.Commander
}
func (c *importCommand) Commands() []simplecobra.Commander {
return c.commands
}
func (c *importCommand) Name() string {
return "import"
}
func (c *importCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
return nil
}
func (c *importCommand) Init(cd *simplecobra.Commandeer) error {
cmd := cd.CobraCommand
cmd.Short = "Import your site from others."
cmd.Long = `Import your site from other web site generators like Jekyll.
Import requires a subcommand, e.g. ` + "`hugo import jekyll jekyll_root_path target_path`."
cmd.RunE = nil
return nil
}
func (c *importCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
c.r = cd.Root.Command.(*rootCommand)
return nil
}
func (i *importCommand) createConfigFromJekyll(fs afero.Fs, inpath string, kind metadecoders.Format, jekyllConfig map[string]any) (err error) {
title := "My New Hugo Site"
baseURL := "http://example.org/"
@@ -293,132 +138,115 @@ func (i *importCmd) createConfigFromJekyll(fs afero.Fs, inpath string, kind meta
return err
}
return helpers.WriteToDisk(filepath.Join(inpath, "config."+string(kind)), &buf, fs)
return helpers.WriteToDisk(filepath.Join(inpath, "hugo."+string(kind)), &buf, fs)
}
func (i *importCmd) copyJekyllFilesAndFolders(jekyllRoot, dest string, jekyllPostDirs map[string]bool) (err error) {
fs := hugofs.Os
fi, err := fs.Stat(jekyllRoot)
if err != nil {
return err
}
if !fi.IsDir() {
return errors.New(jekyllRoot + " is not a directory")
}
err = os.MkdirAll(dest, fi.Mode())
if err != nil {
return err
}
entries, err := ioutil.ReadDir(jekyllRoot)
if err != nil {
return err
}
for _, entry := range entries {
sfp := filepath.Join(jekyllRoot, entry.Name())
dfp := filepath.Join(dest, entry.Name())
if entry.IsDir() {
if entry.Name()[0] != '_' && entry.Name()[0] != '.' {
if _, ok := jekyllPostDirs[entry.Name()]; !ok {
err = hugio.CopyDir(fs, sfp, dfp, nil)
if err != nil {
jww.ERROR.Println(err)
func (c *importCommand) getJekyllDirInfo(fs afero.Fs, jekyllRoot string) (map[string]bool, bool) {
postDirs := make(map[string]bool)
hasAnyPost := false
if entries, err := os.ReadDir(jekyllRoot); err == nil {
for _, entry := range entries {
if entry.IsDir() {
subDir := filepath.Join(jekyllRoot, entry.Name())
if isPostDir, hasAnyPostInDir := c.retrieveJekyllPostDir(fs, subDir); isPostDir {
postDirs[entry.Name()] = hasAnyPostInDir
if hasAnyPostInDir {
hasAnyPost = true
}
}
}
} else {
lowerEntryName := strings.ToLower(entry.Name())
exceptSuffix := []string{
".md", ".markdown", ".html", ".htm",
".xml", ".textile", "rakefile", "gemfile", ".lock",
}
isExcept := false
for _, suffix := range exceptSuffix {
if strings.HasSuffix(lowerEntryName, suffix) {
isExcept = true
break
}
}
}
}
return postDirs, hasAnyPost
}
if !isExcept && entry.Name()[0] != '.' && entry.Name()[0] != '_' {
err = hugio.CopyFile(fs, sfp, dfp)
if err != nil {
jww.ERROR.Println(err)
}
}
func (c *importCommand) createSiteFromJekyll(jekyllRoot, targetDir string, jekyllPostDirs map[string]bool) error {
fs := &afero.OsFs{}
if exists, _ := helpers.Exists(targetDir, fs); exists {
if isDir, _ := helpers.IsDir(targetDir, fs); !isDir {
return errors.New("target path \"" + targetDir + "\" exists but is not a directory")
}
}
return nil
}
isEmpty, _ := helpers.IsEmpty(targetDir, fs)
func parseJekyllFilename(filename string) (time.Time, string, error) {
re := regexp.MustCompile(`(\d+-\d+-\d+)-(.+)\..*`)
r := re.FindAllStringSubmatch(filename, -1)
if len(r) == 0 {
return htime.Now(), "", errors.New("filename not match")
if !isEmpty && !c.force {
return errors.New("target path \"" + targetDir + "\" exists and is not empty")
}
}
postDate, err := time.Parse("2006-1-2", r[0][1])
if err != nil {
return htime.Now(), "", err
}
jekyllConfig := c.loadJekyllConfig(fs, jekyllRoot)
postName := r[0][2]
mkdir(targetDir, "layouts")
mkdir(targetDir, "content")
mkdir(targetDir, "archetypes")
mkdir(targetDir, "static")
mkdir(targetDir, "data")
mkdir(targetDir, "themes")
return postDate, postName, nil
}
c.createConfigFromJekyll(fs, targetDir, "yaml", jekyllConfig)
func convertJekyllPost(path, relPath, targetDir string, draft bool) error {
jww.TRACE.Println("Converting", path)
filename := filepath.Base(path)
postDate, postName, err := parseJekyllFilename(filename)
if err != nil {
jww.WARN.Printf("Failed to parse filename '%s': %s. Skipping.", filename, err)
return nil
}
jww.TRACE.Println(filename, postDate, postName)
targetFile := filepath.Join(targetDir, relPath)
targetParentDir := filepath.Dir(targetFile)
os.MkdirAll(targetParentDir, 0777)
contentBytes, err := ioutil.ReadFile(path)
if err != nil {
jww.ERROR.Println("Read file error:", path)
return err
}
pf, err := pageparser.ParseFrontMatterAndContent(bytes.NewReader(contentBytes))
if err != nil {
jww.ERROR.Println("Parse file error:", path)
return err
}
newmetadata, err := convertJekyllMetaData(pf.FrontMatter, postName, postDate, draft)
if err != nil {
jww.ERROR.Println("Convert metadata error:", path)
return err
}
content, err := convertJekyllContent(newmetadata, string(pf.Content))
if err != nil {
jww.ERROR.Println("Converting Jekyll error:", path)
return err
}
fs := hugofs.Os
if err := helpers.WriteToDisk(targetFile, strings.NewReader(content), fs); err != nil {
return fmt.Errorf("failed to save file %q: %s", filename, err)
}
c.copyJekyllFilesAndFolders(jekyllRoot, filepath.Join(targetDir, "static"), jekyllPostDirs)
return nil
}
func convertJekyllMetaData(m any, postName string, postDate time.Time, draft bool) (any, error) {
func (c *importCommand) convertJekyllContent(m any, content string) (string, error) {
metadata, _ := maps.ToStringMapE(m)
lines := strings.Split(content, "\n")
var resultLines []string
for _, line := range lines {
resultLines = append(resultLines, strings.Trim(line, "\r\n"))
}
content = strings.Join(resultLines, "\n")
excerptSep := "<!--more-->"
if value, ok := metadata["excerpt_separator"]; ok {
if str, strOk := value.(string); strOk {
content = strings.Replace(content, strings.TrimSpace(str), excerptSep, -1)
}
}
replaceList := []struct {
re *regexp.Regexp
replace string
}{
{regexp.MustCompile("(?i)<!-- more -->"), "<!--more-->"},
{regexp.MustCompile(`\{%\s*raw\s*%\}\s*(.*?)\s*\{%\s*endraw\s*%\}`), "$1"},
{regexp.MustCompile(`{%\s*endhighlight\s*%}`), "{{< / highlight >}}"},
}
for _, replace := range replaceList {
content = replace.re.ReplaceAllString(content, replace.replace)
}
replaceListFunc := []struct {
re *regexp.Regexp
replace func(string) string
}{
// Octopress image tag: http://octopress.org/docs/plugins/image-tag/
{regexp.MustCompile(`{%\s+img\s*(.*?)\s*%}`), c.replaceImageTag},
{regexp.MustCompile(`{%\s*highlight\s*(.*?)\s*%}`), c.replaceHighlightTag},
}
for _, replace := range replaceListFunc {
content = replace.re.ReplaceAllStringFunc(content, replace.replace)
}
var buf bytes.Buffer
if len(metadata) != 0 {
err := parser.InterfaceToFrontMatter(m, metadecoders.YAML, &buf)
if err != nil {
return "", err
}
}
buf.WriteString(content)
return buf.String(), nil
}
func (c *importCommand) convertJekyllMetaData(m any, postName string, postDate time.Time, draft bool) (any, error) {
metadata, err := maps.ToStringMapE(m)
if err != nil {
return nil, err
@@ -470,63 +298,235 @@ func convertJekyllMetaData(m any, postName string, postDate time.Time, draft boo
return metadata, nil
}
func convertJekyllContent(m any, content string) (string, error) {
metadata, _ := maps.ToStringMapE(m)
func (c *importCommand) convertJekyllPost(path, relPath, targetDir string, draft bool) error {
log.Println("Converting", path)
lines := strings.Split(content, "\n")
var resultLines []string
for _, line := range lines {
resultLines = append(resultLines, strings.Trim(line, "\r\n"))
filename := filepath.Base(path)
postDate, postName, err := c.parseJekyllFilename(filename)
if err != nil {
c.r.Printf("Failed to parse filename '%s': %s. Skipping.", filename, err)
return nil
}
content = strings.Join(resultLines, "\n")
log.Println(filename, postDate, postName)
excerptSep := "<!--more-->"
if value, ok := metadata["excerpt_separator"]; ok {
if str, strOk := value.(string); strOk {
content = strings.Replace(content, strings.TrimSpace(str), excerptSep, -1)
}
targetFile := filepath.Join(targetDir, relPath)
targetParentDir := filepath.Dir(targetFile)
os.MkdirAll(targetParentDir, 0777)
contentBytes, err := os.ReadFile(path)
if err != nil {
c.r.logger.Errorln("Read file error:", path)
return err
}
pf, err := pageparser.ParseFrontMatterAndContent(bytes.NewReader(contentBytes))
if err != nil {
return fmt.Errorf("failed to parse file %q: %s", filename, err)
}
newmetadata, err := c.convertJekyllMetaData(pf.FrontMatter, postName, postDate, draft)
if err != nil {
return fmt.Errorf("failed to convert metadata for file %q: %s", filename, err)
}
replaceList := []struct {
re *regexp.Regexp
replace string
}{
{regexp.MustCompile("(?i)<!-- more -->"), "<!--more-->"},
{regexp.MustCompile(`\{%\s*raw\s*%\}\s*(.*?)\s*\{%\s*endraw\s*%\}`), "$1"},
{regexp.MustCompile(`{%\s*endhighlight\s*%}`), "{{< / highlight >}}"},
content, err := c.convertJekyllContent(newmetadata, string(pf.Content))
if err != nil {
return fmt.Errorf("failed to convert content for file %q: %s", filename, err)
}
for _, replace := range replaceList {
content = replace.re.ReplaceAllString(content, replace.replace)
fs := hugofs.Os
if err := helpers.WriteToDisk(targetFile, strings.NewReader(content), fs); err != nil {
return fmt.Errorf("failed to save file %q: %s", filename, err)
}
replaceListFunc := []struct {
re *regexp.Regexp
replace func(string) string
}{
// Octopress image tag: http://octopress.org/docs/plugins/image-tag/
{regexp.MustCompile(`{%\s+img\s*(.*?)\s*%}`), replaceImageTag},
{regexp.MustCompile(`{%\s*highlight\s*(.*?)\s*%}`), replaceHighlightTag},
}
for _, replace := range replaceListFunc {
content = replace.re.ReplaceAllStringFunc(content, replace.replace)
}
var buf bytes.Buffer
if len(metadata) != 0 {
err := parser.InterfaceToFrontMatter(m, metadecoders.YAML, &buf)
if err != nil {
return "", err
}
}
buf.WriteString(content)
return buf.String(), nil
return nil
}
func replaceHighlightTag(match string) string {
func (c *importCommand) copyJekyllFilesAndFolders(jekyllRoot, dest string, jekyllPostDirs map[string]bool) (err error) {
fs := hugofs.Os
fi, err := fs.Stat(jekyllRoot)
if err != nil {
return err
}
if !fi.IsDir() {
return errors.New(jekyllRoot + " is not a directory")
}
err = os.MkdirAll(dest, fi.Mode())
if err != nil {
return err
}
entries, err := os.ReadDir(jekyllRoot)
if err != nil {
return err
}
for _, entry := range entries {
sfp := filepath.Join(jekyllRoot, entry.Name())
dfp := filepath.Join(dest, entry.Name())
if entry.IsDir() {
if entry.Name()[0] != '_' && entry.Name()[0] != '.' {
if _, ok := jekyllPostDirs[entry.Name()]; !ok {
err = hugio.CopyDir(fs, sfp, dfp, nil)
if err != nil {
c.r.logger.Errorln(err)
}
}
}
} else {
lowerEntryName := strings.ToLower(entry.Name())
exceptSuffix := []string{
".md", ".markdown", ".html", ".htm",
".xml", ".textile", "rakefile", "gemfile", ".lock",
}
isExcept := false
for _, suffix := range exceptSuffix {
if strings.HasSuffix(lowerEntryName, suffix) {
isExcept = true
break
}
}
if !isExcept && entry.Name()[0] != '.' && entry.Name()[0] != '_' {
err = hugio.CopyFile(fs, sfp, dfp)
if err != nil {
c.r.logger.Errorln(err)
}
}
}
}
return nil
}
func (c *importCommand) importFromJekyll(args []string) error {
jekyllRoot, err := filepath.Abs(filepath.Clean(args[0]))
if err != nil {
return newUserError("path error:", args[0])
}
targetDir, err := filepath.Abs(filepath.Clean(args[1]))
if err != nil {
return newUserError("path error:", args[1])
}
c.r.Println("Import Jekyll from:", jekyllRoot, "to:", targetDir)
if strings.HasPrefix(filepath.Dir(targetDir), jekyllRoot) {
return newUserError("abort: target path should not be inside the Jekyll root")
}
fs := afero.NewOsFs()
jekyllPostDirs, hasAnyPost := c.getJekyllDirInfo(fs, jekyllRoot)
if !hasAnyPost {
return errors.New("abort: jekyll root contains neither posts nor drafts")
}
err = c.createSiteFromJekyll(jekyllRoot, targetDir, jekyllPostDirs)
if err != nil {
return newUserError(err)
}
c.r.Println("Importing...")
fileCount := 0
callback := func(path string, fi hugofs.FileMetaInfo, err error) error {
if err != nil {
return err
}
if fi.IsDir() {
return nil
}
relPath, err := filepath.Rel(jekyllRoot, path)
if err != nil {
return newUserError("get rel path error:", path)
}
relPath = filepath.ToSlash(relPath)
draft := false
switch {
case strings.Contains(relPath, "_posts/"):
relPath = filepath.Join("content/post", strings.Replace(relPath, "_posts/", "", -1))
case strings.Contains(relPath, "_drafts/"):
relPath = filepath.Join("content/draft", strings.Replace(relPath, "_drafts/", "", -1))
draft = true
default:
return nil
}
fileCount++
return c.convertJekyllPost(path, relPath, targetDir, draft)
}
for jekyllPostDir, hasAnyPostInDir := range jekyllPostDirs {
if hasAnyPostInDir {
if err = helpers.SymbolicWalk(hugofs.Os, filepath.Join(jekyllRoot, jekyllPostDir), callback); err != nil {
return err
}
}
}
c.r.Println("Congratulations!", fileCount, "post(s) imported!")
c.r.Println("Now, start Hugo by yourself:\n")
c.r.Println("cd " + args[1])
c.r.Println("git init")
c.r.Println("git submodule add https://github.com/theNewDynamic/gohugo-theme-ananke themes/ananke")
c.r.Println("echo \"theme = 'ananke'\" > hugo.toml")
c.r.Println("hugo server")
return nil
}
func (c *importCommand) loadJekyllConfig(fs afero.Fs, jekyllRoot string) map[string]any {
path := filepath.Join(jekyllRoot, "_config.yml")
exists, err := helpers.Exists(path, fs)
if err != nil || !exists {
c.r.Println("_config.yaml not found: Is the specified Jekyll root correct?")
return nil
}
f, err := fs.Open(path)
if err != nil {
return nil
}
defer f.Close()
b, err := io.ReadAll(f)
if err != nil {
return nil
}
m, err := metadecoders.Default.UnmarshalToMap(b, metadecoders.YAML)
if err != nil {
return nil
}
return m
}
func (c *importCommand) parseJekyllFilename(filename string) (time.Time, string, error) {
re := regexp.MustCompile(`(\d+-\d+-\d+)-(.+)\..*`)
r := re.FindAllStringSubmatch(filename, -1)
if len(r) == 0 {
return htime.Now(), "", errors.New("filename not match")
}
postDate, err := time.Parse("2006-1-2", r[0][1])
if err != nil {
return htime.Now(), "", err
}
postName := r[0][2]
return postDate, postName, nil
}
func (c *importCommand) replaceHighlightTag(match string) string {
r := regexp.MustCompile(`{%\s*highlight\s*(.*?)\s*%}`)
parts := r.FindStringSubmatch(match)
lastQuote := rune(0)
@@ -570,35 +570,55 @@ func replaceHighlightTag(match string) string {
return result.String()
}
func replaceImageTag(match string) string {
func (c *importCommand) replaceImageTag(match string) string {
r := regexp.MustCompile(`{%\s+img\s*(\p{L}*)\s+([\S]*/[\S]+)\s+(\d*)\s*(\d*)\s*(.*?)\s*%}`)
result := bytes.NewBufferString("{{< figure ")
parts := r.FindStringSubmatch(match)
// Index 0 is the entire string, ignore
replaceOptionalPart(result, "class", parts[1])
replaceOptionalPart(result, "src", parts[2])
replaceOptionalPart(result, "width", parts[3])
replaceOptionalPart(result, "height", parts[4])
c.replaceOptionalPart(result, "class", parts[1])
c.replaceOptionalPart(result, "src", parts[2])
c.replaceOptionalPart(result, "width", parts[3])
c.replaceOptionalPart(result, "height", parts[4])
// title + alt
part := parts[5]
if len(part) > 0 {
splits := strings.Split(part, "'")
lenSplits := len(splits)
if lenSplits == 1 {
replaceOptionalPart(result, "title", splits[0])
c.replaceOptionalPart(result, "title", splits[0])
} else if lenSplits == 3 {
replaceOptionalPart(result, "title", splits[1])
c.replaceOptionalPart(result, "title", splits[1])
} else if lenSplits == 5 {
replaceOptionalPart(result, "title", splits[1])
replaceOptionalPart(result, "alt", splits[3])
c.replaceOptionalPart(result, "title", splits[1])
c.replaceOptionalPart(result, "alt", splits[3])
}
}
result.WriteString(">}}")
return result.String()
}
func replaceOptionalPart(buffer *bytes.Buffer, partName string, part string) {
func (c *importCommand) replaceOptionalPart(buffer *bytes.Buffer, partName string, part string) {
if len(part) > 0 {
buffer.WriteString(partName + "=\"" + part + "\" ")
}
}
func (c *importCommand) retrieveJekyllPostDir(fs afero.Fs, dir string) (bool, bool) {
if strings.HasSuffix(dir, "_posts") || strings.HasSuffix(dir, "_drafts") {
isEmpty, _ := helpers.IsEmpty(dir, fs)
return true, !isEmpty
}
if entries, err := os.ReadDir(dir); err == nil {
for _, entry := range entries {
if entry.IsDir() {
subDir := filepath.Join(dir, entry.Name())
if isPostDir, hasAnyPost := c.retrieveJekyllPostDir(fs, subDir); isPostDir {
return isPostDir, hasAnyPost
}
}
}
}
return false, true
}
-177
View File
@@ -1,177 +0,0 @@
// Copyright 2015 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
import (
"encoding/json"
"testing"
"time"
qt "github.com/frankban/quicktest"
)
func TestParseJekyllFilename(t *testing.T) {
c := qt.New(t)
filenameArray := []string{
"2015-01-02-test.md",
"2012-03-15-中文.markup",
}
expectResult := []struct {
postDate time.Time
postName string
}{
{time.Date(2015, time.January, 2, 0, 0, 0, 0, time.UTC), "test"},
{time.Date(2012, time.March, 15, 0, 0, 0, 0, time.UTC), "中文"},
}
for i, filename := range filenameArray {
postDate, postName, err := parseJekyllFilename(filename)
c.Assert(err, qt.IsNil)
c.Assert(expectResult[i].postDate.Format("2006-01-02"), qt.Equals, postDate.Format("2006-01-02"))
c.Assert(expectResult[i].postName, qt.Equals, postName)
}
}
func TestConvertJekyllMetadata(t *testing.T) {
c := qt.New(t)
testDataList := []struct {
metadata any
postName string
postDate time.Time
draft bool
expect string
}{
{
map[any]any{},
"testPost", time.Date(2015, 10, 1, 0, 0, 0, 0, time.UTC), false,
`{"date":"2015-10-01T00:00:00Z"}`,
},
{
map[any]any{},
"testPost", time.Date(2015, 10, 1, 0, 0, 0, 0, time.UTC), true,
`{"date":"2015-10-01T00:00:00Z","draft":true}`,
},
{
map[any]any{"Permalink": "/permalink.html", "layout": "post"},
"testPost", time.Date(2015, 10, 1, 0, 0, 0, 0, time.UTC), false,
`{"date":"2015-10-01T00:00:00Z","url":"/permalink.html"}`,
},
{
map[any]any{"permalink": "/permalink.html"},
"testPost", time.Date(2015, 10, 1, 0, 0, 0, 0, time.UTC), false,
`{"date":"2015-10-01T00:00:00Z","url":"/permalink.html"}`,
},
{
map[any]any{"category": nil, "permalink": 123},
"testPost", time.Date(2015, 10, 1, 0, 0, 0, 0, time.UTC), false,
`{"date":"2015-10-01T00:00:00Z"}`,
},
{
map[any]any{"Excerpt_Separator": "sep"},
"testPost", time.Date(2015, 10, 1, 0, 0, 0, 0, time.UTC), false,
`{"date":"2015-10-01T00:00:00Z","excerpt_separator":"sep"}`,
},
{
map[any]any{"category": "book", "layout": "post", "Others": "Goods", "Date": "2015-10-01 12:13:11"},
"testPost", time.Date(2015, 10, 1, 0, 0, 0, 0, time.UTC), false,
`{"Others":"Goods","categories":["book"],"date":"2015-10-01T12:13:11Z"}`,
},
}
for _, data := range testDataList {
result, err := convertJekyllMetaData(data.metadata, data.postName, data.postDate, data.draft)
c.Assert(err, qt.IsNil)
jsonResult, err := json.Marshal(result)
c.Assert(err, qt.IsNil)
c.Assert(string(jsonResult), qt.Equals, data.expect)
}
}
func TestConvertJekyllContent(t *testing.T) {
c := qt.New(t)
testDataList := []struct {
metadata any
content string
expect string
}{
{
map[any]any{},
"Test content\r\n<!-- more -->\npart2 content", "Test content\n<!--more-->\npart2 content",
},
{
map[any]any{},
"Test content\n<!-- More -->\npart2 content", "Test content\n<!--more-->\npart2 content",
},
{
map[any]any{"excerpt_separator": "<!--sep-->"},
"Test content\n<!--sep-->\npart2 content",
"---\nexcerpt_separator: <!--sep-->\n---\nTest content\n<!--more-->\npart2 content",
},
{map[any]any{}, "{% raw %}text{% endraw %}", "text"},
{map[any]any{}, "{%raw%} text2 {%endraw %}", "text2"},
{
map[any]any{},
"{% highlight go %}\nvar s int\n{% endhighlight %}",
"{{< highlight go >}}\nvar s int\n{{< / highlight >}}",
},
{
map[any]any{},
"{% highlight go linenos hl_lines=\"1 2\" %}\nvar s string\nvar i int\n{% endhighlight %}",
"{{< highlight go \"linenos=table,hl_lines=1 2\" >}}\nvar s string\nvar i int\n{{< / highlight >}}",
},
// Octopress image tag
{
map[any]any{},
"{% img http://placekitten.com/890/280 %}",
"{{< figure src=\"http://placekitten.com/890/280\" >}}",
},
{
map[any]any{},
"{% img left http://placekitten.com/320/250 Place Kitten #2 %}",
"{{< figure class=\"left\" src=\"http://placekitten.com/320/250\" title=\"Place Kitten #2\" >}}",
},
{
map[any]any{},
"{% img right http://placekitten.com/300/500 150 250 'Place Kitten #3' %}",
"{{< figure class=\"right\" src=\"http://placekitten.com/300/500\" width=\"150\" height=\"250\" title=\"Place Kitten #3\" >}}",
},
{
map[any]any{},
"{% img right http://placekitten.com/300/500 150 250 'Place Kitten #4' 'An image of a very cute kitten' %}",
"{{< figure class=\"right\" src=\"http://placekitten.com/300/500\" width=\"150\" height=\"250\" title=\"Place Kitten #4\" alt=\"An image of a very cute kitten\" >}}",
},
{
map[any]any{},
"{% img http://placekitten.com/300/500 150 250 'Place Kitten #4' 'An image of a very cute kitten' %}",
"{{< figure src=\"http://placekitten.com/300/500\" width=\"150\" height=\"250\" title=\"Place Kitten #4\" alt=\"An image of a very cute kitten\" >}}",
},
{
map[any]any{},
"{% img right /placekitten/300/500 'Place Kitten #4' 'An image of a very cute kitten' %}",
"{{< figure class=\"right\" src=\"/placekitten/300/500\" title=\"Place Kitten #4\" alt=\"An image of a very cute kitten\" >}}",
},
{
map[any]any{"category": "book", "layout": "post", "Date": "2015-10-01 12:13:11"},
"somecontent",
"---\nDate: \"2015-10-01 12:13:11\"\ncategory: book\nlayout: post\n---\nsomecontent",
},
}
for _, data := range testDataList {
result, err := convertJekyllContent(data.metadata, data.content)
c.Assert(result, qt.Equals, data.expect)
c.Assert(err, qt.IsNil)
}
}
-84
View File
@@ -1,84 +0,0 @@
// Copyright 2018 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
import (
"syscall"
"github.com/spf13/cobra"
jww "github.com/spf13/jwalterweatherman"
)
var _ cmder = (*limitCmd)(nil)
type limitCmd struct {
*baseCmd
}
func newLimitCmd() *limitCmd {
ccmd := &cobra.Command{
Use: "ulimit",
Short: "Check system ulimit settings",
Long: `Hugo will inspect the current ulimit settings on the system.
This is primarily to ensure that Hugo can watch enough files on some OSs`,
RunE: func(cmd *cobra.Command, args []string) error {
var rLimit syscall.Rlimit
err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
if err != nil {
return newSystemError("Error Getting rlimit ", err)
}
jww.FEEDBACK.Println("Current rLimit:", rLimit)
if rLimit.Cur >= newRlimit {
return nil
}
jww.FEEDBACK.Println("Attempting to increase limit")
rLimit.Cur = newRlimit
err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
if err != nil {
return newSystemError("Error Setting rLimit ", err)
}
err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
if err != nil {
return newSystemError("Error Getting rLimit ", err)
}
jww.FEEDBACK.Println("rLimit after change:", rLimit)
return nil
},
}
return &limitCmd{baseCmd: newBaseCmd(ccmd)}
}
const newRlimit = 10240
func tweakLimit() {
var rLimit syscall.Rlimit
err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
if err != nil {
jww.WARN.Println("Unable to get rlimit:", err)
return
}
if rLimit.Cur < newRlimit {
rLimit.Cur = newRlimit
err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
if err != nil {
// This may not succeed, see https://github.com/golang/go/issues/30401
jww.INFO.Println("Unable to increase number of open files limit:", err)
}
}
}
+156 -175
View File
@@ -1,4 +1,4 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,197 +14,178 @@
package commands
import (
"context"
"encoding/csv"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/bep/simplecobra"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/hugolib"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/resources/resource"
"github.com/spf13/cobra"
jww "github.com/spf13/jwalterweatherman"
)
var _ cmder = (*listCmd)(nil)
// newListCommand creates a new list command and its subcommands.
func newListCommand() *listCommand {
type listCmd struct {
*baseBuilderCmd
}
func (lc *listCmd) buildSites(config map[string]any) (*hugolib.HugoSites, error) {
cfgInit := func(c *commandeer) error {
for key, value := range config {
c.Set(key, value)
createRecord := func(workingDir string, p page.Page) []string {
return []string{
filepath.ToSlash(strings.TrimPrefix(p.File().Filename(), workingDir+string(os.PathSeparator))),
p.Slug(),
p.Title(),
p.Date().Format(time.RFC3339),
p.ExpiryDate().Format(time.RFC3339),
p.PublishDate().Format(time.RFC3339),
strconv.FormatBool(p.Draft()),
p.Permalink(),
}
}
list := func(cd *simplecobra.Commandeer, r *rootCommand, shouldInclude func(page.Page) bool, opts ...any) error {
bcfg := hugolib.BuildCfg{SkipRender: true}
cfg := config.New()
for i := 0; i < len(opts); i += 2 {
cfg.Set(opts[i].(string), opts[i+1])
}
h, err := r.Build(cd, bcfg, cfg)
if err != nil {
return err
}
writer := csv.NewWriter(r.Out)
defer writer.Flush()
writer.Write([]string{
"path",
"slug",
"title",
"date",
"expiryDate",
"publishDate",
"draft",
"permalink",
})
for _, p := range h.Pages() {
if shouldInclude(p) {
record := createRecord(h.Conf.BaseConfig().WorkingDir, p)
if err := writer.Write(record); err != nil {
return err
}
if err != nil {
return err
}
}
}
return nil
}
c, err := initializeConfig(true, true, false, &lc.hugoBuilderCommon, lc, cfgInit)
if err != nil {
return nil, err
return &listCommand{
commands: []simplecobra.Commander{
&simpleCommand{
name: "drafts",
short: "List all drafts",
long: `List all of the drafts in your content directory.`,
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
shouldInclude := func(p page.Page) bool {
if !p.Draft() || p.File().IsZero() {
return false
}
return true
}
return list(cd, r, shouldInclude,
"buildDrafts", true,
"buildFuture", true,
"buildExpired", true,
)
},
},
&simpleCommand{
name: "future",
short: "List all posts dated in the future",
long: `List all of the posts in your content directory which will be posted in the future.`,
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
shouldInclude := func(p page.Page) bool {
if !resource.IsFuture(p) || p.File().IsZero() {
return false
}
return true
}
return list(cd, r, shouldInclude,
"buildFuture", true,
"buildDrafts", true,
)
},
},
&simpleCommand{
name: "expired",
short: "List all posts already expired",
long: `List all of the posts in your content directory which has already expired.`,
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
shouldInclude := func(p page.Page) bool {
if !resource.IsExpired(p) || p.File().IsZero() {
return false
}
return true
}
return list(cd, r, shouldInclude,
"buildExpired", true,
"buildDrafts", true,
)
},
},
&simpleCommand{
name: "all",
short: "List all posts",
long: `List all of the posts in your content directory, include drafts, future and expired pages.`,
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
shouldInclude := func(p page.Page) bool {
return !p.File().IsZero()
}
return list(cd, r, shouldInclude, "buildDrafts", true, "buildFuture", true, "buildExpired", true)
},
},
},
}
sites, err := hugolib.NewHugoSites(*c.DepsCfg)
if err != nil {
return nil, newSystemError("Error creating sites", err)
}
if err := sites.Build(hugolib.BuildCfg{SkipRender: true}); err != nil {
return nil, newSystemError("Error Processing Source Content", err)
}
return sites, nil
}
func (b *commandsBuilder) newListCmd() *listCmd {
cc := &listCmd{}
cmd := &cobra.Command{
Use: "list",
Short: "Listing out various types of content",
Long: `Listing out various types of content.
List requires a subcommand, e.g. ` + "`hugo list drafts`.",
RunE: nil,
}
cmd.AddCommand(
&cobra.Command{
Use: "drafts",
Short: "List all drafts",
Long: `List all of the drafts in your content directory.`,
RunE: func(cmd *cobra.Command, args []string) error {
sites, err := cc.buildSites(map[string]any{"buildDrafts": true})
if err != nil {
return newSystemError("Error building sites", err)
}
for _, p := range sites.Pages() {
if p.Draft() {
jww.FEEDBACK.Println(strings.TrimPrefix(p.File().Filename(), sites.WorkingDir+string(os.PathSeparator)))
}
}
return nil
},
},
&cobra.Command{
Use: "future",
Short: "List all posts dated in the future",
Long: `List all of the posts in your content directory which will be posted in the future.`,
RunE: func(cmd *cobra.Command, args []string) error {
sites, err := cc.buildSites(map[string]any{"buildFuture": true})
if err != nil {
return newSystemError("Error building sites", err)
}
if err != nil {
return newSystemError("Error building sites", err)
}
writer := csv.NewWriter(os.Stdout)
defer writer.Flush()
for _, p := range sites.Pages() {
if resource.IsFuture(p) {
err := writer.Write([]string{
strings.TrimPrefix(p.File().Filename(), sites.WorkingDir+string(os.PathSeparator)),
p.PublishDate().Format(time.RFC3339),
})
if err != nil {
return newSystemError("Error writing future posts to stdout", err)
}
}
}
return nil
},
},
&cobra.Command{
Use: "expired",
Short: "List all posts already expired",
Long: `List all of the posts in your content directory which has already expired.`,
RunE: func(cmd *cobra.Command, args []string) error {
sites, err := cc.buildSites(map[string]any{"buildExpired": true})
if err != nil {
return newSystemError("Error building sites", err)
}
if err != nil {
return newSystemError("Error building sites", err)
}
writer := csv.NewWriter(os.Stdout)
defer writer.Flush()
for _, p := range sites.Pages() {
if resource.IsExpired(p) {
err := writer.Write([]string{
strings.TrimPrefix(p.File().Filename(), sites.WorkingDir+string(os.PathSeparator)),
p.ExpiryDate().Format(time.RFC3339),
})
if err != nil {
return newSystemError("Error writing expired posts to stdout", err)
}
}
}
return nil
},
},
&cobra.Command{
Use: "all",
Short: "List all posts",
Long: `List all of the posts in your content directory, include drafts, future and expired pages.`,
RunE: func(cmd *cobra.Command, args []string) error {
sites, err := cc.buildSites(map[string]any{
"buildExpired": true,
"buildDrafts": true,
"buildFuture": true,
})
if err != nil {
return newSystemError("Error building sites", err)
}
writer := csv.NewWriter(os.Stdout)
defer writer.Flush()
writer.Write([]string{
"path",
"slug",
"title",
"date",
"expiryDate",
"publishDate",
"draft",
"permalink",
})
for _, p := range sites.Pages() {
if !p.IsPage() {
continue
}
err := writer.Write([]string{
strings.TrimPrefix(p.File().Filename(), sites.WorkingDir+string(os.PathSeparator)),
p.Slug(),
p.Title(),
p.Date().Format(time.RFC3339),
p.ExpiryDate().Format(time.RFC3339),
p.PublishDate().Format(time.RFC3339),
strconv.FormatBool(p.Draft()),
p.Permalink(),
})
if err != nil {
return newSystemError("Error writing posts to stdout", err)
}
}
return nil
},
},
)
cc.baseBuilderCmd = b.newBuilderBasicCmd(cmd)
return cc
type listCommand struct {
commands []simplecobra.Commander
}
func (c *listCommand) Commands() []simplecobra.Commander {
return c.commands
}
func (c *listCommand) Name() string {
return "list"
}
func (c *listCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
// Do nothing.
return nil
}
func (c *listCommand) Init(cd *simplecobra.Commandeer) error {
cmd := cd.CobraCommand
cmd.Short = "Listing out various types of content"
cmd.Long = `Listing out various types of content.
List requires a subcommand, e.g. hugo list drafts`
cmd.RunE = nil
return nil
}
func (c *listCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
return nil
}
-68
View File
@@ -1,68 +0,0 @@
package commands
import (
"bytes"
"encoding/csv"
"io"
"os"
"path/filepath"
"strings"
"testing"
qt "github.com/frankban/quicktest"
)
func captureStdout(f func() error) (string, error) {
old := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
err := f()
w.Close()
os.Stdout = old
var buf bytes.Buffer
io.Copy(&buf, r)
return buf.String(), err
}
func TestListAll(t *testing.T) {
c := qt.New(t)
dir := createSimpleTestSite(t, testSiteConfig{})
hugoCmd := newCommandsBuilder().addAll().build()
cmd := hugoCmd.getCommand()
t.Cleanup(func() {
os.RemoveAll(dir)
})
cmd.SetArgs([]string{"-s=" + dir, "list", "all"})
out, err := captureStdout(func() error {
_, err := cmd.ExecuteC()
return err
})
c.Assert(err, qt.IsNil)
r := csv.NewReader(strings.NewReader(out))
header, err := r.Read()
c.Assert(err, qt.IsNil)
c.Assert(header, qt.DeepEquals, []string{
"path", "slug", "title",
"date", "expiryDate", "publishDate",
"draft", "permalink",
})
record, err := r.Read()
c.Assert(err, qt.IsNil)
c.Assert(record, qt.DeepEquals, []string{
filepath.Join("content", "p1.md"), "", "P1",
"0001-01-01T00:00:00Z", "0001-01-01T00:00:00Z", "0001-01-01T00:00:00Z",
"false", "https://example.org/p1/",
})
}
+248 -215
View File
@@ -1,4 +1,4 @@
// Copyright 2020 The Hugo Authors. All rights reserved.
// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,87 +14,18 @@
package commands
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"regexp"
"github.com/gohugoio/hugo/hugolib"
"github.com/gohugoio/hugo/modules"
"github.com/bep/simplecobra"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/modules/npm"
"github.com/spf13/cobra"
)
var _ cmder = (*modCmd)(nil)
type modCmd struct {
*baseBuilderCmd
}
func (c *modCmd) newVerifyCmd() *cobra.Command {
var clean bool
verifyCmd := &cobra.Command{
Use: "verify",
Short: "Verify dependencies.",
Long: `Verify checks that the dependencies of the current module, which are stored in a local downloaded source cache, have not been modified since being downloaded.
`,
RunE: func(cmd *cobra.Command, args []string) error {
return c.withModsClient(true, func(c *modules.Client) error {
return c.Verify(clean)
})
},
}
verifyCmd.Flags().BoolVarP(&clean, "clean", "", false, "delete module cache for dependencies that fail verification")
return verifyCmd
}
var moduleNotFoundRe = regexp.MustCompile("module.*not found")
func (c *modCmd) newCleanCmd() *cobra.Command {
var pattern string
var all bool
cmd := &cobra.Command{
Use: "clean",
Short: "Delete the Hugo Module cache for the current project.",
Long: `Delete the Hugo Module cache for the current project.
Note that after you run this command, all of your dependencies will be re-downloaded next time you run "hugo".
Also note that if you configure a positive maxAge for the "modules" file cache, it will also be cleaned as part of "hugo --gc".
`,
RunE: func(cmd *cobra.Command, args []string) error {
if all {
com, err := c.initConfig(false)
if err != nil && com == nil {
return err
}
count, err := com.hugo().FileCaches.ModulesCache().Prune(true)
com.logger.Printf("Deleted %d files from module cache.", count)
return err
}
return c.withModsClient(true, func(c *modules.Client) error {
return c.Clean(pattern)
})
},
}
cmd.Flags().StringVarP(&pattern, "pattern", "", "", `pattern matching module paths to clean (all if not set), e.g. "**hugo*"`)
cmd.Flags().BoolVarP(&all, "all", "", false, "clean entire module cache")
return cmd
}
func (b *commandsBuilder) newModCmd() *modCmd {
c := &modCmd{}
const commonUsage = `
const commonUsageMod = `
Note that Hugo will always start out by resolving the components defined in the site
configuration, provided by a _vendor directory (if no --ignoreVendorPaths flag provided),
Go Modules, or a folder inside the themes directory, in that order.
@@ -103,27 +34,171 @@ See https://gohugo.io/hugo-modules/ for more information.
`
cmd := &cobra.Command{
Use: "mod",
Short: "Various Hugo Modules helpers.",
Long: `Various helpers to help manage the modules in your project's dependency graph.
// buildConfigCommands creates a new config command and its subcommands.
func newModCommands() *modCommands {
var (
clean bool
pattern string
all bool
)
Most operations here requires a Go version installed on your system (>= Go 1.12) and the relevant VCS client (typically Git).
This is not needed if you only operate on modules inside /themes or if you have vendored them via "hugo mod vendor".
npmCommand := &simpleCommand{
name: "npm",
short: "Various npm helpers.",
long: `Various npm (Node package manager) helpers.`,
commands: []simplecobra.Commander{
&simpleCommand{
name: "pack",
short: "Experimental: Prepares and writes a composite package.json file for your project.",
long: `Prepares and writes a composite package.json file for your project.
` + commonUsage,
On first run it creates a "package.hugo.json" in the project root if not already there. This file will be used as a template file
with the base dependency set.
RunE: nil,
This set will be merged with all "package.hugo.json" files found in the dependency tree, picking the version closest to the project.
This command is marked as 'Experimental'. We think it's a great idea, so it's not likely to be
removed from Hugo, but we need to test this out in "real life" to get a feel of it,
so this may/will change in future versions of Hugo.
`,
withc: func(cmd *cobra.Command, r *rootCommand) {
applyLocalFlagsBuildConfig(cmd, r)
},
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
h, err := r.Hugo(flagsToCfg(cd, nil))
if err != nil {
return err
}
return npm.Pack(h.BaseFs.SourceFs, h.BaseFs.Assets.Dirs)
},
},
},
}
cmd.AddCommand(newModNPMCmd(c))
return &modCommands{
commands: []simplecobra.Commander{
&simpleCommand{
name: "init",
short: "Initialize this project as a Hugo Module.",
long: `Initialize this project as a Hugo Module.
It will try to guess the module path, but you may help by passing it as an argument, e.g:
hugo mod init github.com/gohugoio/testshortcodes
Note that Hugo Modules supports multi-module projects, so you can initialize a Hugo Module
inside a subfolder on GitHub, as one example.
`,
withc: func(cmd *cobra.Command, r *rootCommand) {
applyLocalFlagsBuildConfig(cmd, r)
},
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
h, err := r.Hugo(flagsToCfg(cd, nil))
if err != nil {
return err
}
var initPath string
if len(args) >= 1 {
initPath = args[0]
}
return h.Configs.ModulesClient.Init(initPath)
},
},
&simpleCommand{
name: "verify",
short: "Verify dependencies.",
long: `Verify checks that the dependencies of the current module, which are stored in a local downloaded source cache, have not been modified since being downloaded.`,
withc: func(cmd *cobra.Command, r *rootCommand) {
applyLocalFlagsBuildConfig(cmd, r)
cmd.Flags().BoolVarP(&clean, "clean", "", false, "delete module cache for dependencies that fail verification")
},
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
conf, err := r.ConfigFromProvider(r.configVersionID.Load(), flagsToCfg(cd, nil))
if err != nil {
return err
}
client := conf.configs.ModulesClient
return client.Verify(clean)
},
},
&simpleCommand{
name: "graph",
short: "Print a module dependency graph.",
long: `Print a module dependency graph with information about module status (disabled, vendored).
Note that for vendored modules, that is the version listed and not the one from go.mod.
`,
withc: func(cmd *cobra.Command, r *rootCommand) {
applyLocalFlagsBuildConfig(cmd, r)
cmd.Flags().BoolVarP(&clean, "clean", "", false, "delete module cache for dependencies that fail verification")
},
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
conf, err := r.ConfigFromProvider(r.configVersionID.Load(), flagsToCfg(cd, nil))
if err != nil {
return err
}
client := conf.configs.ModulesClient
return client.Graph(os.Stdout)
},
},
&simpleCommand{
name: "clean",
short: "Delete the Hugo Module cache for the current project.",
long: `Delete the Hugo Module cache for the current project.`,
withc: func(cmd *cobra.Command, r *rootCommand) {
applyLocalFlagsBuildConfig(cmd, r)
cmd.Flags().StringVarP(&pattern, "pattern", "", "", `pattern matching module paths to clean (all if not set), e.g. "**hugo*"`)
cmd.Flags().BoolVarP(&all, "all", "", false, "clean entire module cache")
},
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
h, err := r.Hugo(flagsToCfg(cd, nil))
if err != nil {
return err
}
if all {
modCache := h.ResourceSpec.FileCaches.ModulesCache()
count, err := modCache.Prune(true)
r.Printf("Deleted %d files from module cache.", count)
return err
}
cmd.AddCommand(
&cobra.Command{
Use: "get",
DisableFlagParsing: true,
Short: "Resolves dependencies in your current Hugo Project.",
Long: `
return h.Configs.ModulesClient.Clean(pattern)
},
},
&simpleCommand{
name: "tidy",
short: "Remove unused entries in go.mod and go.sum.",
withc: func(cmd *cobra.Command, r *rootCommand) {
applyLocalFlagsBuildConfig(cmd, r)
},
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
h, err := r.Hugo(flagsToCfg(cd, nil))
if err != nil {
return err
}
return h.Configs.ModulesClient.Tidy()
},
},
&simpleCommand{
name: "vendor",
short: "Vendor all module dependencies into the _vendor directory.",
long: `Vendor all module dependencies into the _vendor directory.
If a module is vendored, that is where Hugo will look for it's dependencies.
`,
withc: func(cmd *cobra.Command, r *rootCommand) {
applyLocalFlagsBuildConfig(cmd, r)
},
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
h, err := r.Hugo(flagsToCfg(cd, nil))
if err != nil {
return err
}
return h.Configs.ModulesClient.Vendor()
},
},
&simpleCommand{
name: "get",
short: "Resolves dependencies in your current Hugo Project.",
long: `
Resolves dependencies in your current Hugo Project.
Some examples:
@@ -142,152 +217,110 @@ Install the latest versions of all module dependencies:
hugo mod get -u ./... (recursive)
Run "go help get" for more information. All flags available for "go get" is also relevant here.
` + commonUsage,
RunE: func(cmd *cobra.Command, args []string) error {
// We currently just pass on the flags we get to Go and
// need to do the flag handling manually.
if len(args) == 1 && args[0] == "-h" {
return cmd.Help()
}
var lastArg string
if len(args) != 0 {
lastArg = args[len(args)-1]
}
if lastArg == "./..." {
args = args[:len(args)-1]
// Do a recursive update.
dirname, err := os.Getwd()
if err != nil {
return err
` + commonUsageMod,
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.DisableFlagParsing = true
},
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
// We currently just pass on the flags we get to Go and
// need to do the flag handling manually.
if len(args) == 1 && (args[0] == "-h" || args[0] == "--help") {
return errHelp
}
// Sanity check. We do recursive walking and want to avoid
// accidents.
if len(dirname) < 5 {
return errors.New("must not be run from the file system root")
var lastArg string
if len(args) != 0 {
lastArg = args[len(args)-1]
}
filepath.Walk(dirname, func(path string, info os.FileInfo, err error) error {
if info.IsDir() {
return nil
if lastArg == "./..." {
args = args[:len(args)-1]
// Do a recursive update.
dirname, err := os.Getwd()
if err != nil {
return err
}
if info.Name() == "go.mod" {
// Found a module.
dir := filepath.Dir(path)
fmt.Println("Update module in", dir)
c.source = dir
err := c.withModsClient(false, func(c *modules.Client) error {
if len(args) == 1 && args[0] == "-h" {
return cmd.Help()
}
return c.Get(args...)
})
if err != nil {
return err
// Sanity chesimplecobra. We do recursive walking and want to avoid
// accidents.
if len(dirname) < 5 {
return errors.New("must not be run from the file system root")
}
filepath.Walk(dirname, func(path string, info os.FileInfo, err error) error {
if info.IsDir() {
return nil
}
if info.Name() == "go.mod" {
// Found a module.
dir := filepath.Dir(path)
r.Println("Update module in", dir)
cfg := config.New()
cfg.Set("workingDir", dir)
conf, err := r.ConfigFromProvider(r.configVersionID.Load(), flagsToCfg(cd, cfg))
if err != nil {
return err
}
client := conf.configs.ModulesClient
return client.Get(args...)
}
}
return nil
})
return nil
})
return nil
}
return c.withModsClient(false, func(c *modules.Client) error {
return c.Get(args...)
})
} else {
conf, err := r.ConfigFromProvider(r.configVersionID.Load(), flagsToCfg(cd, nil))
if err != nil {
return err
}
client := conf.configs.ModulesClient
return client.Get(args...)
}
},
},
npmCommand,
},
&cobra.Command{
Use: "graph",
Short: "Print a module dependency graph.",
Long: `Print a module dependency graph with information about module status (disabled, vendored).
Note that for vendored modules, that is the version listed and not the one from go.mod.
`,
RunE: func(cmd *cobra.Command, args []string) error {
return c.withModsClient(true, func(c *modules.Client) error {
return c.Graph(os.Stdout)
})
},
},
&cobra.Command{
Use: "init",
Short: "Initialize this project as a Hugo Module.",
Long: `Initialize this project as a Hugo Module.
It will try to guess the module path, but you may help by passing it as an argument, e.g:
}
hugo mod init github.com/gohugoio/testshortcodes
Note that Hugo Modules supports multi-module projects, so you can initialize a Hugo Module
inside a subfolder on GitHub, as one example.
`,
RunE: func(cmd *cobra.Command, args []string) error {
var path string
if len(args) >= 1 {
path = args[0]
}
return c.withModsClient(false, func(c *modules.Client) error {
return c.Init(path)
})
},
},
&cobra.Command{
Use: "vendor",
Short: "Vendor all module dependencies into the _vendor directory.",
Long: `Vendor all module dependencies into the _vendor directory.
If a module is vendored, that is where Hugo will look for it's dependencies.
`,
RunE: func(cmd *cobra.Command, args []string) error {
return c.withModsClient(true, func(c *modules.Client) error {
return c.Vendor()
})
},
},
c.newVerifyCmd(),
&cobra.Command{
Use: "tidy",
Short: "Remove unused entries in go.mod and go.sum.",
RunE: func(cmd *cobra.Command, args []string) error {
return c.withModsClient(true, func(c *modules.Client) error {
return c.Tidy()
})
},
},
c.newCleanCmd(),
)
c.baseBuilderCmd = b.newBuilderCmd(cmd)
return c
}
func (c *modCmd) withModsClient(failOnMissingConfig bool, f func(*modules.Client) error) error {
com, err := c.initConfig(failOnMissingConfig)
type modCommands struct {
r *rootCommand
commands []simplecobra.Commander
}
func (c *modCommands) Commands() []simplecobra.Commander {
return c.commands
}
func (c *modCommands) Name() string {
return "mod"
}
func (c *modCommands) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
_, err := c.r.ConfigFromProvider(c.r.configVersionID.Load(), nil)
if err != nil {
return err
}
//config := conf.configs.Base
return f(com.hugo().ModulesClient)
return nil
}
func (c *modCmd) withHugo(f func(*hugolib.HugoSites) error) error {
com, err := c.initConfig(true)
if err != nil {
return err
}
func (c *modCommands) Init(cd *simplecobra.Commandeer) error {
cmd := cd.CobraCommand
cmd.Short = "Various Hugo Modules helpers."
cmd.Long = `Various helpers to help manage the modules in your project's dependency graph.
Most operations here requires a Go version installed on your system (>= Go 1.12) and the relevant VCS client (typically Git).
This is not needed if you only operate on modules inside /themes or if you have vendored them via "hugo mod vendor".
return f(com.hugo())
` + commonUsageMod
cmd.RunE = nil
return nil
}
func (c *modCmd) initConfig(failOnNoConfig bool) (*commandeer, error) {
com, err := initializeConfig(failOnNoConfig, false, false, &c.hugoBuilderCommon, c, nil)
if err != nil {
return nil, err
}
return com, nil
func (c *modCommands) PreRun(cd, runner *simplecobra.Commandeer) error {
c.r = cd.Root.Command.(*rootCommand)
return nil
}
-56
View File
@@ -1,56 +0,0 @@
// Copyright 2020 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
import (
"github.com/gohugoio/hugo/hugolib"
"github.com/gohugoio/hugo/modules/npm"
"github.com/spf13/cobra"
)
func newModNPMCmd(c *modCmd) *cobra.Command {
cmd := &cobra.Command{
Use: "npm",
Short: "Various npm helpers.",
Long: `Various npm (Node package manager) helpers.`,
RunE: func(cmd *cobra.Command, args []string) error {
return c.withHugo(func(h *hugolib.HugoSites) error {
return nil
})
},
}
cmd.AddCommand(&cobra.Command{
Use: "pack",
Short: "Experimental: Prepares and writes a composite package.json file for your project.",
Long: `Prepares and writes a composite package.json file for your project.
On first run it creates a "package.hugo.json" in the project root if not already there. This file will be used as a template file
with the base dependency set.
This set will be merged with all "package.hugo.json" files found in the dependency tree, picking the version closest to the project.
This command is marked as 'Experimental'. We think it's a great idea, so it's not likely to be
removed from Hugo, but we need to test this out in "real life" to get a feel of it,
so this may/will change in future versions of Hugo.
`,
RunE: func(cmd *cobra.Command, args []string) error {
return c.withHugo(func(h *hugolib.HugoSites) error {
return npm.Pack(h.BaseFs.SourceFs, h.BaseFs.Assets.Dirs)
})
},
})
return cmd
}
+322 -75
View File
@@ -1,4 +1,4 @@
// Copyright 2018 The Hugo Authors. All rights reserved.
// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,33 +15,38 @@ package commands
import (
"bytes"
"os"
"context"
"errors"
"fmt"
"path/filepath"
"strings"
"github.com/bep/simplecobra"
"github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/create"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugolib"
"github.com/gohugoio/hugo/parser"
"github.com/gohugoio/hugo/parser/metadecoders"
"github.com/spf13/afero"
"github.com/spf13/cobra"
jww "github.com/spf13/jwalterweatherman"
)
var _ cmder = (*newCmd)(nil)
func newNewCommand() *newCommand {
var (
force bool
contentType string
format string
)
type newCmd struct {
contentEditor string
contentType string
force bool
*baseBuilderCmd
}
func (b *commandsBuilder) newNewCmd() *newCmd {
cmd := &cobra.Command{
Use: "new [path]",
Short: "Create new content for your site",
Long: `Create a new content file and automatically set the date and title.
var c *newCommand
c = &newCommand{
commands: []simplecobra.Commander{
&simpleCommand{
name: "content",
use: "content [path]",
short: "Create new content for your site",
long: `Create a new content file and automatically set the date and title.
It will guess which kind of file to create based on the path provided.
You can also specify the kind with ` + "`-k KIND`" + `.
@@ -49,80 +54,322 @@ You can also specify the kind with ` + "`-k KIND`" + `.
If archetypes are provided in your theme or site, they will be used.
Ensure you run this within the root directory of your site.`,
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
if len(args) < 1 {
return newUserError("path needs to be provided")
}
h, err := r.Hugo(flagsToCfg(cd, nil))
if err != nil {
return err
}
return create.NewContent(h, contentType, args[0], force)
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.Flags().StringVarP(&contentType, "kind", "k", "", "content type to create")
cmd.Flags().String("editor", "", "edit new content with this editor, if provided")
cmd.Flags().BoolVarP(&force, "force", "f", false, "overwrite file if it already exists")
cmd.Flags().StringVar(&format, "format", "toml", "preferred file format (toml, yaml or json)")
applyLocalFlagsBuildConfig(cmd, r)
},
},
&simpleCommand{
name: "site",
use: "site [path]",
short: "Create a new site (skeleton)",
long: `Create a new site in the provided directory.
The new site will have the correct structure, but no content or theme yet.
Use ` + "`hugo new [contentPath]`" + ` to create new content.`,
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
if len(args) < 1 {
return newUserError("path needs to be provided")
}
createpath, err := filepath.Abs(filepath.Clean(args[0]))
if err != nil {
return err
}
cfg := config.New()
cfg.Set("workingDir", createpath)
cfg.Set("publishDir", "public")
conf, err := r.ConfigFromProvider(r.configVersionID.Load(), flagsToCfg(cd, cfg))
if err != nil {
return err
}
sourceFs := conf.fs.Source
archeTypePath := filepath.Join(createpath, "archetypes")
dirs := []string{
archeTypePath,
filepath.Join(createpath, "assets"),
filepath.Join(createpath, "content"),
filepath.Join(createpath, "data"),
filepath.Join(createpath, "layouts"),
filepath.Join(createpath, "static"),
filepath.Join(createpath, "themes"),
}
if exists, _ := helpers.Exists(createpath, sourceFs); exists {
if isDir, _ := helpers.IsDir(createpath, sourceFs); !isDir {
return errors.New(createpath + " already exists but not a directory")
}
isEmpty, _ := helpers.IsEmpty(createpath, sourceFs)
switch {
case !isEmpty && !force:
return errors.New(createpath + " already exists and is not empty. See --force.")
case !isEmpty && force:
all := append(dirs, filepath.Join(createpath, "hugo."+format))
for _, path := range all {
if exists, _ := helpers.Exists(path, sourceFs); exists {
return errors.New(path + " already exists")
}
}
}
}
for _, dir := range dirs {
if err := sourceFs.MkdirAll(dir, 0777); err != nil {
return fmt.Errorf("failed to create dir: %w", err)
}
}
c.newSiteCreateConfig(sourceFs, createpath, format)
// Create a default archetype file.
helpers.SafeWriteToDisk(filepath.Join(archeTypePath, "default.md"),
strings.NewReader(create.DefaultArchetypeTemplateTemplate), sourceFs)
r.Printf("Congratulations! Your new Hugo site is created in %s.\n\n", createpath)
r.Println(c.newSiteNextStepsText())
return nil
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.Flags().BoolVarP(&force, "force", "f", false, "init inside non-empty directory")
cmd.Flags().StringVar(&format, "format", "toml", "preferred file format (toml, yaml or json)")
},
},
&simpleCommand{
name: "theme",
use: "theme [name]",
short: "Create a new theme (skeleton)",
long: `Create a new theme (skeleton) called [name] in ./themes.
New theme is a skeleton. Please add content to the touched files. Add your
name to the copyright line in the license and adjust the theme.toml file
according to your needs.`,
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
if len(args) < 1 {
return newUserError("theme name needs to be provided")
}
h, err := r.Hugo(flagsToCfg(cd, nil))
if err != nil {
return err
}
ps := h.PathSpec
sourceFs := ps.Fs.Source
themesDir := h.Configs.LoadingInfo.BaseConfig.ThemesDir
createpath := ps.AbsPathify(filepath.Join(themesDir, args[0]))
r.Println("Creating theme at", createpath)
if x, _ := helpers.Exists(createpath, sourceFs); x {
return errors.New(createpath + " already exists")
}
for _, filename := range []string{
"index.html",
"404.html",
"_default/list.html",
"_default/single.html",
"partials/head.html",
"partials/header.html",
"partials/footer.html",
} {
touchFile(sourceFs, filepath.Join(createpath, "layouts", filename))
}
baseofDefault := []byte(`<!DOCTYPE html>
<html>
{{- partial "head.html" . -}}
<body>
{{- partial "header.html" . -}}
<div id="content">
{{- block "main" . }}{{- end }}
</div>
{{- partial "footer.html" . -}}
</body>
</html>
`)
err = helpers.WriteToDisk(filepath.Join(createpath, "layouts", "_default", "baseof.html"), bytes.NewReader(baseofDefault), sourceFs)
if err != nil {
return err
}
mkdir(createpath, "archetypes")
archDefault := []byte("+++\n+++\n")
err = helpers.WriteToDisk(filepath.Join(createpath, "archetypes", "default.md"), bytes.NewReader(archDefault), sourceFs)
if err != nil {
return err
}
mkdir(createpath, "static", "js")
mkdir(createpath, "static", "css")
by := []byte(`The MIT License (MIT)
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
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
`)
err = helpers.WriteToDisk(filepath.Join(createpath, "LICENSE"), bytes.NewReader(by), sourceFs)
if err != nil {
return err
}
c.createThemeMD(ps.Fs.Source, createpath)
return nil
},
},
},
}
cc := &newCmd{baseBuilderCmd: b.newBuilderCmd(cmd)}
return c
cmd.Flags().StringVarP(&cc.contentType, "kind", "k", "", "content type to create")
cmd.Flags().StringVar(&cc.contentEditor, "editor", "", "edit new content with this editor, if provided")
cmd.Flags().BoolVarP(&cc.force, "force", "f", false, "overwrite file if it already exists")
cmd.AddCommand(b.newNewSiteCmd().getCommand())
cmd.AddCommand(b.newNewThemeCmd().getCommand())
cmd.RunE = cc.newContent
return cc
}
func (n *newCmd) newContent(cmd *cobra.Command, args []string) error {
cfgInit := func(c *commandeer) error {
if cmd.Flags().Changed("editor") {
c.Set("newContentEditor", n.contentEditor)
}
return nil
type newCommand struct {
rootCmd *rootCommand
commands []simplecobra.Commander
}
func (c *newCommand) Commands() []simplecobra.Commander {
return c.commands
}
func (c *newCommand) Name() string {
return "new"
}
func (c *newCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
return nil
}
func (c *newCommand) Init(cd *simplecobra.Commandeer) error {
cmd := cd.CobraCommand
cmd.Short = "Create new content for your site"
cmd.Long = `Create a new content file and automatically set the date and title.
It will guess which kind of file to create based on the path provided.
You can also specify the kind with ` + "`-k KIND`" + `.
If archetypes are provided in your theme or site, they will be used.
Ensure you run this within the root directory of your site.`
cmd.RunE = nil
return nil
}
func (c *newCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
c.rootCmd = cd.Root.Command.(*rootCommand)
return nil
}
func (c *newCommand) newSiteCreateConfig(fs afero.Fs, inpath string, kind string) (err error) {
in := map[string]string{
"baseURL": "http://example.org/",
"title": "My New Hugo Site",
"languageCode": "en-us",
}
c, err := initializeConfig(true, true, false, &n.hugoBuilderCommon, n, cfgInit)
var buf bytes.Buffer
err = parser.InterfaceToConfig(in, metadecoders.FormatFromString(kind), &buf)
if err != nil {
return err
}
if len(args) < 1 {
return newUserError("path needs to be provided")
}
return create.NewContent(c.hugo(), n.contentType, args[0], n.force)
return helpers.WriteToDisk(filepath.Join(inpath, "hugo."+kind), &buf, fs)
}
func mkdir(x ...string) {
p := filepath.Join(x...)
func (c *newCommand) newSiteNextStepsText() string {
var nextStepsText bytes.Buffer
err := os.MkdirAll(p, 0777) // before umask
nextStepsText.WriteString(`Just a few more steps and you're ready to go:
1. Download a theme into the same-named folder.
Choose a theme from https://themes.gohugo.io/ or
create your own with the "hugo new theme <THEMENAME>" command.
2. Perhaps you want to add some content. You can add single files
with "hugo new `)
nextStepsText.WriteString(filepath.Join("<SECTIONNAME>", "<FILENAME>.<FORMAT>"))
nextStepsText.WriteString(`".
3. Start the built-in live server via "hugo server".
Visit https://gohugo.io/ for quickstart guide and full documentation.`)
return nextStepsText.String()
}
func (c *newCommand) createThemeMD(fs afero.Fs, inpath string) (err error) {
by := []byte(`# theme.toml template for a Hugo theme
# See https://github.com/gohugoio/hugoThemes#themetoml for an example
name = "` + strings.Title(helpers.MakeTitle(filepath.Base(inpath))) + `"
license = "MIT"
licenselink = "https://github.com/yourname/yourtheme/blob/master/LICENSE"
description = ""
homepage = "http://example.com/"
tags = []
features = []
min_version = "0.115.0"
[author]
name = ""
homepage = ""
# If porting an existing theme
[original]
name = ""
homepage = ""
repo = ""
`)
err = helpers.WriteToDisk(filepath.Join(inpath, "theme.toml"), bytes.NewReader(by), fs)
if err != nil {
jww.FATAL.Fatalln(err)
return
}
}
func touchFile(fs afero.Fs, x ...string) {
inpath := filepath.Join(x...)
mkdir(filepath.Dir(inpath))
err := helpers.WriteToDisk(inpath, bytes.NewReader([]byte{}), fs)
err = helpers.WriteToDisk(filepath.Join(inpath, "hugo.toml"), strings.NewReader("# Theme config.\n"), fs)
if err != nil {
jww.FATAL.Fatalln(err)
return
}
}
func newContentPathSection(h *hugolib.HugoSites, path string) (string, string) {
// Forward slashes is used in all examples. Convert if needed.
// Issue #1133
createpath := filepath.FromSlash(path)
if h != nil {
for _, dir := range h.BaseFs.Content.Dirs {
createpath = strings.TrimPrefix(createpath, dir.Meta().Filename)
}
}
var section string
// assume the first directory is the section (kind)
if strings.Contains(createpath[1:], helpers.FilePathSeparator) {
parts := strings.Split(strings.TrimPrefix(createpath, helpers.FilePathSeparator), helpers.FilePathSeparator)
if len(parts) > 0 {
section = parts[0]
}
}
return createpath, section
return nil
}
-165
View File
@@ -1,165 +0,0 @@
// Copyright 2018 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
import (
"bytes"
"errors"
"fmt"
"path/filepath"
"strings"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/parser/metadecoders"
"github.com/gohugoio/hugo/create"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/parser"
"github.com/spf13/cobra"
jww "github.com/spf13/jwalterweatherman"
)
var _ cmder = (*newSiteCmd)(nil)
type newSiteCmd struct {
configFormat string
*baseBuilderCmd
}
func (b *commandsBuilder) newNewSiteCmd() *newSiteCmd {
cc := &newSiteCmd{}
cmd := &cobra.Command{
Use: "site [path]",
Short: "Create a new site (skeleton)",
Long: `Create a new site in the provided directory.
The new site will have the correct structure, but no content or theme yet.
Use ` + "`hugo new [contentPath]`" + ` to create new content.`,
RunE: cc.newSite,
}
cmd.Flags().StringVarP(&cc.configFormat, "format", "f", "toml", "config file format")
cmd.Flags().Bool("force", false, "init inside non-empty directory")
cc.baseBuilderCmd = b.newBuilderBasicCmd(cmd)
return cc
}
func (n *newSiteCmd) doNewSite(fs *hugofs.Fs, basepath string, force bool) error {
archeTypePath := filepath.Join(basepath, "archetypes")
dirs := []string{
filepath.Join(basepath, "layouts"),
filepath.Join(basepath, "content"),
archeTypePath,
filepath.Join(basepath, "static"),
filepath.Join(basepath, "data"),
filepath.Join(basepath, "themes"),
}
if exists, _ := helpers.Exists(basepath, fs.Source); exists {
if isDir, _ := helpers.IsDir(basepath, fs.Source); !isDir {
return errors.New(basepath + " already exists but not a directory")
}
isEmpty, _ := helpers.IsEmpty(basepath, fs.Source)
switch {
case !isEmpty && !force:
return errors.New(basepath + " already exists and is not empty. See --force.")
case !isEmpty && force:
all := append(dirs, filepath.Join(basepath, "config."+n.configFormat))
for _, path := range all {
if exists, _ := helpers.Exists(path, fs.Source); exists {
return errors.New(path + " already exists")
}
}
}
}
for _, dir := range dirs {
if err := fs.Source.MkdirAll(dir, 0777); err != nil {
return fmt.Errorf("Failed to create dir: %w", err)
}
}
createConfig(fs, basepath, n.configFormat)
// Create a default archetype file.
helpers.SafeWriteToDisk(filepath.Join(archeTypePath, "default.md"),
strings.NewReader(create.DefaultArchetypeTemplateTemplate), fs.Source)
jww.FEEDBACK.Printf("Congratulations! Your new Hugo site is created in %s.\n\n", basepath)
jww.FEEDBACK.Println(nextStepsText())
return nil
}
// newSite creates a new Hugo site and initializes a structured Hugo directory.
func (n *newSiteCmd) newSite(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return newUserError("path needs to be provided")
}
createpath, err := filepath.Abs(filepath.Clean(args[0]))
if err != nil {
return newUserError(err)
}
forceNew, _ := cmd.Flags().GetBool("force")
cfg := config.New()
cfg.Set("workingDir", createpath)
cfg.Set("publishDir", "public")
return n.doNewSite(hugofs.NewDefault(cfg), createpath, forceNew)
}
func createConfig(fs *hugofs.Fs, inpath string, kind string) (err error) {
in := map[string]string{
"baseURL": "http://example.org/",
"title": "My New Hugo Site",
"languageCode": "en-us",
}
var buf bytes.Buffer
err = parser.InterfaceToConfig(in, metadecoders.FormatFromString(kind), &buf)
if err != nil {
return err
}
return helpers.WriteToDisk(filepath.Join(inpath, "config."+kind), &buf, fs.Source)
}
func nextStepsText() string {
var nextStepsText bytes.Buffer
nextStepsText.WriteString(`Just a few more steps and you're ready to go:
1. Download a theme into the same-named folder.
Choose a theme from https://themes.gohugo.io/ or
create your own with the "hugo new theme <THEMENAME>" command.
2. Perhaps you want to add some content. You can add single files
with "hugo new `)
nextStepsText.WriteString(filepath.Join("<SECTIONNAME>", "<FILENAME>.<FORMAT>"))
nextStepsText.WriteString(`".
3. Start the built-in live server via "hugo server".
Visit https://gohugo.io/ for quickstart guide and full documentation.`)
return nextStepsText.String()
}
-176
View File
@@ -1,176 +0,0 @@
// Copyright 2018 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
import (
"bytes"
"errors"
"path/filepath"
"strings"
"github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
"github.com/spf13/cobra"
jww "github.com/spf13/jwalterweatherman"
)
var _ cmder = (*newThemeCmd)(nil)
type newThemeCmd struct {
*baseBuilderCmd
}
func (b *commandsBuilder) newNewThemeCmd() *newThemeCmd {
cc := &newThemeCmd{}
cmd := &cobra.Command{
Use: "theme [name]",
Short: "Create a new theme",
Long: `Create a new theme (skeleton) called [name] in ./themes.
New theme is a skeleton. Please add content to the touched files. Add your
name to the copyright line in the license and adjust the theme.toml file
as you see fit.`,
RunE: cc.newTheme,
}
cc.baseBuilderCmd = b.newBuilderBasicCmd(cmd)
return cc
}
// newTheme creates a new Hugo theme template
func (n *newThemeCmd) newTheme(cmd *cobra.Command, args []string) error {
c, err := initializeConfig(false, false, false, &n.hugoBuilderCommon, n, nil)
if err != nil {
return err
}
if len(args) < 1 {
return newUserError("theme name needs to be provided")
}
createpath := c.hugo().PathSpec.AbsPathify(filepath.Join(c.Cfg.GetString("themesDir"), args[0]))
jww.FEEDBACK.Println("Creating theme at", createpath)
cfg := c.DepsCfg
if x, _ := helpers.Exists(createpath, cfg.Fs.Source); x {
return errors.New(createpath + " already exists")
}
mkdir(createpath, "layouts", "_default")
mkdir(createpath, "layouts", "partials")
touchFile(cfg.Fs.Source, createpath, "layouts", "index.html")
touchFile(cfg.Fs.Source, createpath, "layouts", "404.html")
touchFile(cfg.Fs.Source, createpath, "layouts", "_default", "list.html")
touchFile(cfg.Fs.Source, createpath, "layouts", "_default", "single.html")
baseofDefault := []byte(`<!DOCTYPE html>
<html>
{{- partial "head.html" . -}}
<body>
{{- partial "header.html" . -}}
<div id="content">
{{- block "main" . }}{{- end }}
</div>
{{- partial "footer.html" . -}}
</body>
</html>
`)
err = helpers.WriteToDisk(filepath.Join(createpath, "layouts", "_default", "baseof.html"), bytes.NewReader(baseofDefault), cfg.Fs.Source)
if err != nil {
return err
}
touchFile(cfg.Fs.Source, createpath, "layouts", "partials", "head.html")
touchFile(cfg.Fs.Source, createpath, "layouts", "partials", "header.html")
touchFile(cfg.Fs.Source, createpath, "layouts", "partials", "footer.html")
mkdir(createpath, "archetypes")
archDefault := []byte("+++\n+++\n")
err = helpers.WriteToDisk(filepath.Join(createpath, "archetypes", "default.md"), bytes.NewReader(archDefault), cfg.Fs.Source)
if err != nil {
return err
}
mkdir(createpath, "static", "js")
mkdir(createpath, "static", "css")
by := []byte(`The MIT License (MIT)
Copyright (c) ` + 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
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
`)
err = helpers.WriteToDisk(filepath.Join(createpath, "LICENSE"), bytes.NewReader(by), cfg.Fs.Source)
if err != nil {
return err
}
n.createThemeMD(cfg.Fs, createpath)
return nil
}
func (n *newThemeCmd) createThemeMD(fs *hugofs.Fs, inpath string) (err error) {
by := []byte(`# theme.toml template for a Hugo theme
# See https://github.com/gohugoio/hugoThemes#themetoml for an example
name = "` + strings.Title(helpers.MakeTitle(filepath.Base(inpath))) + `"
license = "MIT"
licenselink = "https://github.com/yourname/yourtheme/blob/master/LICENSE"
description = ""
homepage = "http://example.com/"
tags = []
features = []
min_version = "0.41.0"
[author]
name = ""
homepage = ""
# If porting an existing theme
[original]
name = ""
homepage = ""
repo = ""
`)
err = helpers.WriteToDisk(filepath.Join(inpath, "theme.toml"), bytes.NewReader(by), fs.Source)
if err != nil {
return
}
return nil
}
-51
View File
@@ -1,51 +0,0 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build nodeploy
// +build nodeploy
package commands
import (
"errors"
"github.com/spf13/cobra"
)
var _ cmder = (*deployCmd)(nil)
// deployCmd supports deploying sites to Cloud providers.
type deployCmd struct {
*baseBuilderCmd
}
func (b *commandsBuilder) newDeployCmd() *deployCmd {
cc := &deployCmd{}
cmd := &cobra.Command{
Use: "deploy",
Short: "Deploy your site to a Cloud provider.",
Long: `Deploy your site to a Cloud provider.
See https://gohugo.io/hosting-and-deployment/hugo-deploy/ for detailed
documentation.
`,
RunE: func(cmd *cobra.Command, args []string) error {
return errors.New("build without HUGO_BUILD_TAGS=nodeploy to use this command")
},
}
cc.baseBuilderCmd = b.newBuilderBasicCmd(cmd)
return cc
}
+27 -46
View File
@@ -1,7 +1,4 @@
//go:build release
// +build release
// Copyright 2017-present The Hugo Authors. All rights reserved.
// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -17,55 +14,39 @@
package commands
import (
"github.com/gohugoio/hugo/config"
"context"
"github.com/bep/simplecobra"
"github.com/gohugoio/hugo/releaser"
"github.com/spf13/cobra"
)
var _ cmder = (*releaseCommandeer)(nil)
// Note: This is a command only meant for internal use and must be run
// via "go run -tags release main.go release" on the actual code base that is in the release.
func newReleaseCommand() simplecobra.Commander {
type releaseCommandeer struct {
cmd *cobra.Command
var (
step int
skipPush bool
try bool
)
step int
skipPush bool
try bool
}
return &simpleCommand{
name: "release",
short: "Release a new version of Hugo.",
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
rel, err := releaser.New(skipPush, try, step)
if err != nil {
return err
}
func createReleaser() cmder {
// Note: This is a command only meant for internal use and must be run
// via "go run -tags release main.go release" on the actual code base that is in the release.
r := &releaseCommandeer{
cmd: &cobra.Command{
Use: "release",
Short: "Release a new version of Hugo.",
Hidden: true,
return rel.Run()
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.Hidden = true
cmd.PersistentFlags().BoolVarP(&skipPush, "skip-push", "", false, "skip pushing to remote")
cmd.PersistentFlags().BoolVarP(&try, "try", "", false, "no changes")
cmd.PersistentFlags().IntVarP(&step, "step", "", 0, "step to run (1: set new version 2: prepare next dev version)")
},
}
r.cmd.RunE = func(cmd *cobra.Command, args []string) error {
return r.release()
}
r.cmd.PersistentFlags().BoolVarP(&r.skipPush, "skip-push", "", false, "skip pushing to remote")
r.cmd.PersistentFlags().BoolVarP(&r.try, "try", "", false, "no changes")
r.cmd.PersistentFlags().IntVarP(&r.step, "step", "", 0, "step to run (1: set new version 2: prepare next dev version)")
return r
}
func (c *releaseCommandeer) getCommand() *cobra.Command {
return c.cmd
}
func (c *releaseCommandeer) flagsToConfig(cfg config.Provider) {
}
func (r *releaseCommandeer) release() error {
rel, err := releaser.New(r.skipPush, r.try, r.step)
if err != nil {
return err
}
return rel.Run()
}
+893 -434
View File
File diff suppressed because it is too large Load Diff
-31
View File
@@ -1,31 +0,0 @@
// Copyright 2018 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
import (
"bytes"
"io"
"net/url"
"github.com/gohugoio/hugo/transform"
"github.com/gohugoio/hugo/transform/livereloadinject"
)
func injectLiveReloadScript(src io.Reader, baseURL url.URL) string {
var b bytes.Buffer
chain := transform.Chain{livereloadinject.New(baseURL)}
chain.Apply(&b, src)
return b.String()
}
-369
View File
@@ -1,369 +0,0 @@
// Copyright 2015 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
import (
"fmt"
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
"golang.org/x/net/context"
"golang.org/x/sync/errgroup"
qt "github.com/frankban/quicktest"
)
// Issue 9518
func TestServerPanicOnConfigError(t *testing.T) {
c := qt.New(t)
config := `
[markup]
[markup.highlight]
linenos='table'
`
r := runServerTest(c,
serverTestOptions{
config: config,
},
)
c.Assert(r.err, qt.IsNotNil)
c.Assert(r.err.Error(), qt.Contains, "cannot parse 'Highlight.LineNos' as bool:")
}
func TestServer404(t *testing.T) {
c := qt.New(t)
r := runServerTest(c,
serverTestOptions{
test404: true,
getNumHomes: 1,
},
)
c.Assert(r.err, qt.IsNil)
c.Assert(r.content404, qt.Contains, "404: 404 Page not found|Not Found.")
}
// Issue 10287.
func TestServerUnicode(t *testing.T) {
c := qt.New(t)
r := runServerTest(c,
serverTestOptions{
pathsToGet: []string{"hügö/"},
},
)
c.Assert(r.err, qt.IsNil)
c.Assert(r.pathsResults["hügö/"], qt.Contains, "This is hügö")
}
func TestServerFlags(t *testing.T) {
c := qt.New(t)
assertPublic := func(c *qt.C, r serverTestResult, renderStaticToDisk bool) {
c.Assert(r.err, qt.IsNil)
c.Assert(r.homesContent[0], qt.Contains, "Environment: development")
c.Assert(r.publicDirnames["myfile.txt"], qt.Equals, renderStaticToDisk)
}
for _, test := range []struct {
flag string
assert func(c *qt.C, r serverTestResult)
}{
{"", func(c *qt.C, r serverTestResult) {
assertPublic(c, r, false)
}},
{"--renderToDisk", func(c *qt.C, r serverTestResult) {
assertPublic(c, r, true)
}},
{"--renderStaticToDisk", func(c *qt.C, r serverTestResult) {
assertPublic(c, r, true)
}},
} {
c.Run(test.flag, func(c *qt.C) {
config := `
baseURL="https://example.org"
`
var args []string
if test.flag != "" {
args = strings.Split(test.flag, "=")
}
opts := serverTestOptions{
config: config,
args: args,
getNumHomes: 1,
}
r := runServerTest(c, opts)
test.assert(c, r)
})
}
}
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)
}{
{"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")
}},
// 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, "=")
}
opts := serverTestOptions{
config: test.config,
getNumHomes: test.numservers,
test404: true,
args: args,
}
r := runServerTest(c, opts)
c.Assert(r.content404, qt.Contains, "404: 404 Page not found|Not Found.")
test.assert(c, r)
})
}
}
type serverTestResult struct {
err error
homesContent []string
content404 string
publicDirnames map[string]bool
pathsResults map[string]string
}
type serverTestOptions struct {
getNumHomes int
test404 bool
config string
pathsToGet []string
args []string
}
func runServerTest(c *qt.C, opts serverTestOptions) serverTestResult {
dir := createSimpleTestSite(c, testSiteConfig{configTOML: opts.config})
result := serverTestResult{
publicDirnames: make(map[string]bool),
pathsResults: make(map[string]string),
}
sp, err := helpers.FindAvailablePort()
c.Assert(err, qt.IsNil)
port := sp.Port
defer func() {
os.RemoveAll(dir)
}()
stop := make(chan bool)
b := newCommandsBuilder()
scmd := b.newServerCmdSignaled(stop)
cmd := scmd.getCommand()
args := append([]string{"-s=" + dir, fmt.Sprintf("-p=%d", port)}, opts.args...)
cmd.SetArgs(args)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
wg, ctx := errgroup.WithContext(ctx)
wg.Go(func() error {
_, err := cmd.ExecuteC()
return err
})
if opts.getNumHomes > 0 {
// Esp. on slow CI machines, we need to wait a little before the web
// server is ready.
wait := 567 * time.Millisecond
if os.Getenv("CI") != "" {
wait = 2 * time.Second
}
time.Sleep(wait)
result.homesContent = make([]string, opts.getNumHomes)
for i := 0; i < opts.getNumHomes; i++ {
func() {
resp, err := http.Get(fmt.Sprintf("http://localhost:%d/", port+i))
c.Assert(err, qt.IsNil)
c.Assert(resp.StatusCode, qt.Equals, http.StatusOK)
if err == nil {
defer resp.Body.Close()
result.homesContent[i] = helpers.ReaderToString(resp.Body)
}
}()
}
}
for _, path := range opts.pathsToGet {
func() {
resp, err := http.Get(fmt.Sprintf("http://localhost:%d/%s", port, path))
c.Assert(err, qt.IsNil)
c.Assert(resp.StatusCode, qt.Equals, http.StatusOK)
if err == nil {
defer resp.Body.Close()
result.pathsResults[path] = helpers.ReaderToString(resp.Body)
}
}()
}
if opts.test404 {
resp, err := http.Get(fmt.Sprintf("http://localhost:%d/this-page-does-not-exist", port))
c.Assert(err, qt.IsNil)
c.Assert(resp.StatusCode, qt.Equals, http.StatusNotFound)
if err == nil {
defer resp.Body.Close()
result.content404 = helpers.ReaderToString(resp.Body)
}
}
time.Sleep(1 * time.Second)
select {
case <-stop:
case stop <- true:
}
pubFiles, err := os.ReadDir(filepath.Join(dir, "public"))
c.Assert(err, qt.IsNil)
for _, f := range pubFiles {
result.publicDirnames[f.Name()] = true
}
result.err = wg.Wait()
return result
}
func TestFixURL(t *testing.T) {
type data struct {
TestName string
CLIBaseURL string
CfgBaseURL string
AppendPort bool
Port int
Result string
}
tests := []data{
{"Basic http localhost", "", "http://foo.com", true, 1313, "http://localhost:1313/"},
{"Basic https production, http localhost", "", "https://foo.com", true, 1313, "http://localhost:1313/"},
{"Basic subdir", "", "http://foo.com/bar", true, 1313, "http://localhost:1313/bar/"},
{"Basic production", "http://foo.com", "http://foo.com", false, 80, "http://foo.com/"},
{"Production subdir", "http://foo.com/bar", "http://foo.com/bar", false, 80, "http://foo.com/bar/"},
{"No http", "", "foo.com", true, 1313, "//localhost:1313/"},
{"Override configured port", "", "foo.com:2020", true, 1313, "//localhost:1313/"},
{"No http production", "foo.com", "foo.com", false, 80, "//foo.com/"},
{"No http production with port", "foo.com", "foo.com", true, 2020, "//foo.com:2020/"},
{"No config", "", "", true, 1313, "//localhost:1313/"},
}
for _, test := range tests {
t.Run(test.TestName, func(t *testing.T) {
b := newCommandsBuilder()
s := b.newServerCmd()
v := config.NewWithTestDefaults()
baseURL := test.CLIBaseURL
v.Set("baseURL", test.CfgBaseURL)
s.serverAppend = test.AppendPort
s.serverPort = test.Port
result, err := s.fixURL(v, baseURL, s.serverPort)
if err != nil {
t.Errorf("Unexpected error %s", err)
}
if result != test.Result {
t.Errorf("Expected %q, got %q", test.Result, result)
}
})
}
}
func TestRemoveErrorPrefixFromLog(t *testing.T) {
c := qt.New(t)
content := `ERROR 2018/10/07 13:11:12 Error while rendering "home": template: _default/baseof.html:4:3: executing "main" at <partial "logo" .>: error calling partial: template: partials/logo.html:5:84: executing "partials/logo.html" at <$resized.AHeight>: can't evaluate field AHeight in type *resource.Image
ERROR 2018/10/07 13:11:12 Rebuild failed: logged 1 error(s)
`
withoutError := removeErrorPrefixFromLog(content)
c.Assert(strings.Contains(withoutError, "ERROR"), qt.Equals, false)
}
func isWindowsCI() bool {
return runtime.GOOS == "windows" && os.Getenv("CI") != ""
}
-129
View File
@@ -1,129 +0,0 @@
// Copyright 2017 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
import (
"os"
"path/filepath"
"github.com/gohugoio/hugo/hugolib/filesystems"
"github.com/fsnotify/fsnotify"
"github.com/gohugoio/hugo/helpers"
"github.com/spf13/fsync"
)
type staticSyncer struct {
c *commandeer
}
func newStaticSyncer(c *commandeer) (*staticSyncer, error) {
return &staticSyncer{c: c}, nil
}
func (s *staticSyncer) isStatic(filename string) bool {
return s.c.hugo().BaseFs.SourceFilesystems.IsStatic(filename)
}
func (s *staticSyncer) syncsStaticEvents(staticEvents []fsnotify.Event) error {
c := s.c
syncFn := func(sourceFs *filesystems.SourceFilesystem) (uint64, error) {
publishDir := helpers.FilePathSeparator
if sourceFs.PublishFolder != "" {
publishDir = filepath.Join(publishDir, sourceFs.PublishFolder)
}
syncer := fsync.NewSyncer()
syncer.NoTimes = c.Cfg.GetBool("noTimes")
syncer.NoChmod = c.Cfg.GetBool("noChmod")
syncer.ChmodFilter = chmodFilter
syncer.SrcFs = sourceFs.Fs
syncer.DestFs = c.Fs.PublishDir
if c.renderStaticToDisk {
syncer.DestFs = c.Fs.PublishDirStatic
}
// prevent spamming the log on changes
logger := helpers.NewDistinctErrorLogger()
for _, ev := range staticEvents {
// Due to our approach of layering both directories and the content's rendered output
// into one we can't accurately remove a file not in one of the source directories.
// If a file is in the local static dir and also in the theme static dir and we remove
// it from one of those locations we expect it to still exist in the destination
//
// If Hugo generates a file (from the content dir) over a static file
// the content generated file should take precedence.
//
// Because we are now watching and handling individual events it is possible that a static
// event that occupies the same path as a content generated file will take precedence
// until a regeneration of the content takes places.
//
// Hugo assumes that these cases are very rare and will permit this bad behavior
// The alternative is to track every single file and which pipeline rendered it
// and then to handle conflict resolution on every event.
fromPath := ev.Name
relPath, found := sourceFs.MakePathRelative(fromPath)
if !found {
// Not member of this virtual host.
continue
}
// Remove || rename is harder and will require an assumption.
// Hugo takes the following approach:
// If the static file exists in any of the static source directories after this event
// Hugo will re-sync it.
// If it does not exist in all of the static directories Hugo will remove it.
//
// This assumes that Hugo has not generated content on top of a static file and then removed
// the source of that static file. In this case Hugo will incorrectly remove that file
// from the published directory.
if ev.Op&fsnotify.Rename == fsnotify.Rename || ev.Op&fsnotify.Remove == fsnotify.Remove {
if _, err := sourceFs.Fs.Stat(relPath); os.IsNotExist(err) {
// If file doesn't exist in any static dir, remove it
logger.Println("File no longer exists in static dir, removing", relPath)
_ = c.Fs.PublishDirStatic.RemoveAll(relPath)
} else if err == nil {
// If file still exists, sync it
logger.Println("Syncing", relPath, "to", publishDir)
if err := syncer.Sync(relPath, relPath); err != nil {
c.logger.Errorln(err)
}
} else {
c.logger.Errorln(err)
}
continue
}
// For all other event operations Hugo will sync static.
logger.Println("Syncing", relPath, "to", publishDir)
if err := syncer.Sync(filepath.Join(publishDir, relPath), relPath); err != nil {
c.logger.Errorln(err)
}
}
return 0, nil
}
_, err := c.doWithPublishDirs(syncFn)
return err
}
-44
View File
@@ -1,44 +0,0 @@
// Copyright 2015 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
import (
"github.com/gohugoio/hugo/common/hugo"
"github.com/spf13/cobra"
jww "github.com/spf13/jwalterweatherman"
)
var _ cmder = (*versionCmd)(nil)
type versionCmd struct {
*baseCmd
}
func newVersionCmd() *versionCmd {
return &versionCmd{
newBaseCmd(&cobra.Command{
Use: "version",
Short: "Print the version number of Hugo",
Long: `All software has versions. This is Hugo's.`,
RunE: func(cmd *cobra.Command, args []string) error {
printHugoVersion()
return nil
},
}),
}
}
func printHugoVersion() {
jww.FEEDBACK.Println(hugo.BuildVersionString())
}
+43 -3
View File
@@ -22,29 +22,64 @@ import (
// If length of from is one and the only element is a slice of same type as to,
// it will be appended.
func Append(to any, from ...any) (any, error) {
if len(from) == 0 {
return to, nil
}
tov, toIsNil := indirect(reflect.ValueOf(to))
toIsNil = toIsNil || to == nil
var tot reflect.Type
if !toIsNil {
if tov.Kind() == reflect.Slice {
// Create a copy of tov, so we don't modify the original.
c := reflect.MakeSlice(tov.Type(), tov.Len(), tov.Len()+len(from))
reflect.Copy(c, tov)
tov = c
}
if tov.Kind() != reflect.Slice {
return nil, fmt.Errorf("expected a slice, got %T", to)
}
tot = tov.Type().Elem()
if tot.Kind() == reflect.Slice {
totvt := tot.Elem()
fromvs := make([]reflect.Value, len(from))
for i, f := range from {
fromv := reflect.ValueOf(f)
fromt := fromv.Type()
if fromt.Kind() == reflect.Slice {
fromt = fromt.Elem()
}
if totvt != fromt {
return nil, fmt.Errorf("cannot append slice of %s to slice of %s", fromt, totvt)
} else {
fromvs[i] = fromv
}
}
return reflect.Append(tov, fromvs...).Interface(), nil
}
toIsNil = tov.Len() == 0
if len(from) == 1 {
fromv := reflect.ValueOf(from[0])
if !fromv.IsValid() {
// from[0] is nil
return appendToInterfaceSliceFromValues(tov, fromv)
}
fromt := fromv.Type()
if fromt.Kind() == reflect.Slice {
fromt = fromt.Elem()
}
if fromv.Kind() == reflect.Slice {
if toIsNil {
// If we get nil []string, we just return the []string
return from[0], nil
}
fromt := reflect.TypeOf(from[0]).Elem()
// If we get []string []string, we append the from slice to to
if tot == fromt {
return reflect.AppendSlice(tov, fromv).Interface(), nil
@@ -52,6 +87,7 @@ func Append(to any, from ...any) (any, error) {
// Fall back to a []interface{} slice.
return appendToInterfaceSliceFromValues(tov, fromv)
}
}
}
}
@@ -62,7 +98,7 @@ func Append(to any, from ...any) (any, error) {
for _, f := range from {
fv := reflect.ValueOf(f)
if !fv.Type().AssignableTo(tot) {
if !fv.IsValid() || !fv.Type().AssignableTo(tot) {
// Fall back to a []interface{} slice.
tov, _ := indirect(reflect.ValueOf(to))
return appendToInterfaceSlice(tov, from...)
@@ -77,6 +113,10 @@ func appendToInterfaceSliceFromValues(slice1, slice2 reflect.Value) ([]any, erro
var tos []any
for _, slice := range []reflect.Value{slice1, slice2} {
if !slice.IsValid() {
tos = append(tos, nil)
continue
}
for i := 0; i < slice.Len(); i++ {
tos = append(tos, slice.Index(i).Interface())
}
+58 -2
View File
@@ -24,7 +24,7 @@ func TestAppend(t *testing.T) {
t.Parallel()
c := qt.New(t)
for _, test := range []struct {
for i, test := range []struct {
start any
addend []any
expected any
@@ -74,6 +74,9 @@ func TestAppend(t *testing.T) {
[]any{"c"},
false,
},
{[]string{"a", "b"}, []any{nil}, []any{"a", "b", nil}},
{[]string{"a", "b"}, []any{nil, "d", nil}, []any{"a", "b", nil, "d", nil}},
{[]any{"a", nil, "c"}, []any{"d", nil, "f"}, []any{"a", nil, "c", "d", nil, "f"}},
} {
result, err := Append(test.start, test.addend...)
@@ -85,6 +88,59 @@ func TestAppend(t *testing.T) {
}
c.Assert(err, qt.IsNil)
c.Assert(result, qt.DeepEquals, test.expected)
c.Assert(result, qt.DeepEquals, test.expected, qt.Commentf("test: [%d] %v", i, test))
}
}
// #11093
func TestAppendToMultiDimensionalSlice(t *testing.T) {
t.Parallel()
c := qt.New(t)
for _, test := range []struct {
to any
from []any
expected any
}{
{[][]string{{"a", "b"}},
[]any{[]string{"c", "d"}},
[][]string{
{"a", "b"},
{"c", "d"},
},
},
{[][]string{{"a", "b"}},
[]any{[]string{"c", "d"}, []string{"e", "f"}},
[][]string{
{"a", "b"},
{"c", "d"},
{"e", "f"},
},
},
{[][]string{{"a", "b"}},
[]any{[]int{1, 2}},
false,
},
} {
result, err := Append(test.to, test.from...)
if b, ok := test.expected.(bool); ok && !b {
c.Assert(err, qt.Not(qt.IsNil))
} else {
c.Assert(err, qt.IsNil)
c.Assert(result, qt.DeepEquals, test.expected)
}
}
}
func TestAppendShouldMakeACopyOfTheInputSlice(t *testing.T) {
t.Parallel()
c := qt.New(t)
slice := make([]string, 0, 100)
slice = append(slice, "a", "b")
result, err := Append(slice, "c")
c.Assert(err, qt.IsNil)
slice[0] = "d"
c.Assert(result, qt.DeepEquals, []string{"a", "b", "c"})
c.Assert(slice, qt.DeepEquals, []string{"d", "b"})
}
+20
View File
@@ -15,6 +15,7 @@ package collections
import (
"reflect"
"sort"
)
// Slicer defines a very generic way to create a typed slice. This is used
@@ -74,3 +75,22 @@ func StringSliceToInterfaceSlice(ss []string) []any {
return result
}
type SortedStringSlice []string
// Contains returns true if s is in ss.
func (ss SortedStringSlice) Contains(s string) bool {
i := sort.SearchStrings(ss, s)
return i < len(ss) && ss[i] == s
}
// Count returns the number of times s is in ss.
func (ss SortedStringSlice) Count(s string) int {
var count int
i := sort.SearchStrings(ss, s)
for i < len(ss) && ss[i] == s {
count++
i++
}
return count
}
+15
View File
@@ -122,3 +122,18 @@ func TestSlice(t *testing.T) {
c.Assert(test.expected, qt.DeepEquals, result, errMsg)
}
}
func TestSortedStringSlice(t *testing.T) {
t.Parallel()
c := qt.New(t)
var s SortedStringSlice = []string{"a", "b", "b", "b", "c", "d"}
c.Assert(s.Contains("a"), qt.IsTrue)
c.Assert(s.Contains("b"), qt.IsTrue)
c.Assert(s.Contains("z"), qt.IsFalse)
c.Assert(s.Count("b"), qt.Equals, 3)
c.Assert(s.Count("z"), qt.Equals, 0)
c.Assert(s.Count("a"), qt.Equals, 1)
}
+2
View File
@@ -0,0 +1,2 @@
// Package common provides common helper functionality for Hugo.
package common
+12 -3
View File
@@ -16,7 +16,6 @@ package herrors
import (
"io"
"io/ioutil"
"path/filepath"
"strings"
@@ -34,7 +33,7 @@ type LineMatcher struct {
}
// LineMatcherFn is used to match a line with an error.
// It returns the column number or 0 if the line was found, but column could not be determinde. Returns -1 if no line match.
// It returns the column number or 0 if the line was found, but column could not be determined. Returns -1 if no line match.
type LineMatcherFn func(m LineMatcher) int
// SimpleLineMatcher simply matches by line number.
@@ -62,6 +61,16 @@ var OffsetMatcher = func(m LineMatcher) int {
return -1
}
// ContainsMatcher is a line matcher that matches by line content.
func ContainsMatcher(text string) func(m LineMatcher) int {
return func(m LineMatcher) int {
if idx := strings.Index(m.Line, text); idx != -1 {
return idx + 1
}
return -1
}
}
// ErrorContext contains contextual information about an error. This will
// typically be the lines surrounding some problem in a file.
type ErrorContext struct {
@@ -114,7 +123,7 @@ func locateError(r io.Reader, le FileError, matches LineMatcherFn) *ErrorContext
ectx := &ErrorContext{LinesPos: -1, Position: text.Position{Offset: -1}}
b, err := ioutil.ReadAll(r)
b, err := io.ReadAll(r)
if err != nil {
return ectx
}
+44 -4
View File
@@ -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.
@@ -19,6 +19,7 @@ import (
"errors"
"fmt"
"io"
"os"
"runtime"
"runtime/debug"
"strconv"
@@ -38,7 +39,8 @@ type ErrorSender interface {
// Recover is a helper function that can be used to capture panics.
// Put this at the top of a method/function that crashes in a template:
// defer herrors.Recover()
//
// defer herrors.Recover()
func Recover(args ...any) {
if r := recover(); r != nil {
fmt.Println("ERR:", r)
@@ -47,7 +49,7 @@ func Recover(args ...any) {
}
}
// Get the current goroutine id. Used only for debugging.
// GetGID the current goroutine id. Used only for debugging.
func GetGID() uint64 {
b := make([]byte, 64)
b = b[:runtime.Stack(b, false)]
@@ -57,11 +59,34 @@ func GetGID() uint64 {
return n
}
// IsFeatureNotAvailableError returns true if the given error is or contains a FeatureNotAvailableError.
func IsFeatureNotAvailableError(err error) bool {
return errors.Is(err, &FeatureNotAvailableError{})
}
// ErrFeatureNotAvailable denotes that a feature is unavailable.
//
// We will, at least to begin with, make some Hugo features (SCSS with libsass) optional,
// and this error is used to signal those situations.
var ErrFeatureNotAvailable = errors.New("this feature is not available in your current Hugo version, see https://goo.gl/YMrWcn for more information")
var ErrFeatureNotAvailable = &FeatureNotAvailableError{Cause: errors.New("this feature is not available in your current Hugo version, see https://goo.gl/YMrWcn for more information")}
// FeatureNotAvailableError is an error type used to signal that a feature is not available.
type FeatureNotAvailableError struct {
Cause error
}
func (e *FeatureNotAvailableError) Unwrap() error {
return e.Cause
}
func (e *FeatureNotAvailableError) Error() string {
return e.Cause.Error()
}
func (e *FeatureNotAvailableError) Is(target error) bool {
_, ok := target.(*FeatureNotAvailableError)
return ok
}
// Must panics if err != nil.
func Must(err error) {
@@ -69,3 +94,18 @@ func Must(err error) {
panic(err)
}
}
// IsNotExist returns true if the error is a file not found error.
// Unlike os.IsNotExist, this also considers wrapped errors.
func IsNotExist(err error) bool {
if os.IsNotExist(err) {
return true
}
// os.IsNotExist does not consider wrapped errors.
if os.IsNotExist(errors.Unwrap(err)) {
return true
}
return false
}
+46
View File
@@ -0,0 +1,46 @@
// 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
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package herrors
import (
"errors"
"fmt"
"testing"
qt "github.com/frankban/quicktest"
"github.com/spf13/afero"
)
func TestIsNotExist(t *testing.T) {
c := qt.New(t)
c.Assert(IsNotExist(afero.ErrFileNotFound), qt.Equals, true)
c.Assert(IsNotExist(afero.ErrFileExists), qt.Equals, false)
c.Assert(IsNotExist(afero.ErrDestinationExists), qt.Equals, false)
c.Assert(IsNotExist(nil), qt.Equals, false)
c.Assert(IsNotExist(fmt.Errorf("foo")), qt.Equals, false)
// os.IsNotExist returns false for wrapped errors.
c.Assert(IsNotExist(fmt.Errorf("foo: %w", afero.ErrFileNotFound)), qt.Equals, true)
}
func TestIsFeatureNotAvailableError(t *testing.T) {
c := qt.New(t)
c.Assert(IsFeatureNotAvailableError(ErrFeatureNotAvailable), qt.Equals, true)
c.Assert(IsFeatureNotAvailableError(&FeatureNotAvailableError{}), qt.Equals, true)
c.Assert(IsFeatureNotAvailableError(errors.New("asdf")), qt.Equals, false)
}
+30 -4
View File
@@ -15,11 +15,14 @@ package herrors
import (
"encoding/json"
godartsassv1 "github.com/bep/godartsass"
"fmt"
"io"
"path/filepath"
"github.com/bep/godartsass"
"github.com/bep/godartsass/v2"
"github.com/bep/golibsass/libsass/libsasserrors"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/text"
@@ -35,7 +38,7 @@ import (
type FileError interface {
error
// ErroContext holds some context information about the error.
// ErrorContext holds some context information about the error.
ErrorContext() *ErrorContext
text.Positioner
@@ -145,6 +148,8 @@ func (e *fileError) causeString() string {
// Avoid repeating the file info in the error message.
case godartsass.SassError:
return v.Message
case godartsassv1.SassError:
return v.Message
case libsasserrors.Error:
return v.Message
default:
@@ -292,7 +297,7 @@ func extractFileTypePos(err error) (string, text.Position) {
}
// The error type from the minifier contains line number and column number.
if line, col := exctractLineNumberAndColumnNumber(err); line >= 0 {
if line, col := extractLineNumberAndColumnNumber(err); line >= 0 {
pos.LineNumber = line
pos.ColumnNumber = col
return fileType, pos
@@ -364,7 +369,7 @@ func extractOffsetAndType(e error) (int, string) {
}
}
func exctractLineNumberAndColumnNumber(e error) (int, int) {
func extractLineNumberAndColumnNumber(e error) (int, int) {
switch v := e.(type) {
case *parse.Error:
return v.Line, v.Column
@@ -385,6 +390,13 @@ func extractPosition(e error) (pos text.Position) {
pos.Filename = filename
pos.Offset = start.Offset
pos.ColumnNumber = start.Column
case godartsassv1.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
@@ -392,3 +404,17 @@ func extractPosition(e error) (pos text.Position) {
}
return
}
// TextSegmentError is an error with a text segment attached.
type TextSegmentError struct {
Segment string
Err error
}
func (e TextSegmentError) Unwrap() error {
return e.Err
}
func (e TextSegmentError) Error() string {
return e.Err.Error()
}
+1 -1
View File
@@ -118,7 +118,7 @@ func SafeCommand(name string, arg ...string) (*exec.Cmd, error) {
return exec.Command(bin, arg...), nil
}
// Exec encorces a security policy for commands run via os/exec.
// Exec enforces a security policy for commands run via os/exec.
type Exec struct {
sc security.Config
+17
View File
@@ -208,6 +208,23 @@ func AsTime(v reflect.Value, loc *time.Location) (time.Time, bool) {
return time.Time{}, false
}
func CallMethodByName(cxt context.Context, name string, v reflect.Value) []reflect.Value {
fn := v.MethodByName(name)
var args []reflect.Value
tp := fn.Type()
if tp.NumIn() > 0 {
if tp.NumIn() > 1 {
panic("not supported")
}
first := tp.In(0)
if first.Implements(ContextInterface) {
args = append(args, reflect.ValueOf(cxt))
}
}
return fn.Call(args)
}
// Based on: https://github.com/golang/go/blob/178a2c42254166cffed1b25fb1d3c7a5727cada6/src/text/template/exec.go#L931
func indirectInterface(v reflect.Value) reflect.Value {
if v.Kind() != reflect.Interface {
+57
View File
@@ -0,0 +1,57 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hstrings
import (
"fmt"
"strings"
"github.com/gohugoio/hugo/compare"
)
var _ compare.Eqer = StringEqualFold("")
// StringEqualFold is a string that implements the compare.Eqer interface and considers
// two strings equal if they are equal when folded to lower case.
// The compare.Eqer interface is used in Hugo to compare values in templates (e.g. using the eq template function).
type StringEqualFold string
func (s StringEqualFold) EqualFold(s2 string) bool {
return strings.EqualFold(string(s), s2)
}
func (s StringEqualFold) String() string {
return string(s)
}
func (s StringEqualFold) Eq(s2 any) bool {
switch ss := s2.(type) {
case string:
return s.EqualFold(ss)
case fmt.Stringer:
return s.EqualFold(ss.String())
}
return false
}
// EqualAny returns whether a string is equal to any of the given strings.
func EqualAny(a string, b ...string) bool {
for _, s := range b {
if a == s {
return true
}
}
return false
}
+36
View File
@@ -0,0 +1,36 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hstrings
import (
"testing"
qt "github.com/frankban/quicktest"
)
func TestStringEqualFold(t *testing.T) {
c := qt.New(t)
s1 := "A"
s2 := "a"
c.Assert(StringEqualFold(s1).EqualFold(s2), qt.Equals, true)
c.Assert(StringEqualFold(s1).EqualFold(s1), qt.Equals, true)
c.Assert(StringEqualFold(s2).EqualFold(s1), qt.Equals, true)
c.Assert(StringEqualFold(s2).EqualFold(s2), qt.Equals, true)
c.Assert(StringEqualFold(s1).EqualFold("b"), qt.Equals, false)
c.Assert(StringEqualFold(s1).Eq(s2), qt.Equals, true)
c.Assert(StringEqualFold(s1).Eq("b"), qt.Equals, false)
}
+11 -2
View File
@@ -14,10 +14,11 @@
package htime
import (
"log"
"strings"
"time"
"github.com/bep/clock"
"github.com/bep/clocks"
"github.com/spf13/cast"
"github.com/gohugoio/locales"
@@ -74,7 +75,7 @@ var (
"December",
}
Clock = clock.System()
Clock = clocks.System()
)
func NewTimeFormatter(ltr locales.Translator) TimeFormatter {
@@ -163,3 +164,11 @@ func Since(t time.Time) time.Duration {
type AsTimeProvider interface {
AsTime(zone *time.Location) time.Time
}
// StopWatch is a simple helper to measure time during development.
func StopWatch(name string) func() {
start := time.Now()
return func() {
log.Printf("StopWatch %q took %s", name, time.Since(start))
}
}
+3 -4
View File
@@ -15,7 +15,6 @@ package hugio
import (
"io"
"io/ioutil"
)
// As implemented by strings.Builder.
@@ -34,7 +33,7 @@ type multiWriteCloser struct {
func (m multiWriteCloser) Close() error {
var err error
for _, c := range m.closers {
if closeErr := c.Close(); err != nil {
if closeErr := c.Close(); closeErr != nil {
err = closeErr
}
}
@@ -63,7 +62,7 @@ func ToWriteCloser(w io.Writer) io.WriteCloser {
io.Closer
}{
w,
ioutil.NopCloser(nil),
io.NopCloser(nil),
}
}
@@ -79,6 +78,6 @@ func ToReadCloser(r io.Reader) io.ReadCloser {
io.Closer
}{
r,
ioutil.NopCloser(nil),
io.NopCloser(nil),
}
}
+113 -29
View File
@@ -22,8 +22,14 @@ import (
"sort"
"strings"
"sync"
godartsassv1 "github.com/bep/godartsass"
"github.com/mitchellh/mapstructure"
"time"
"github.com/bep/godartsass/v2"
"github.com/gohugoio/hugo/common/hexec"
"github.com/gohugoio/hugo/hugofs/files"
"github.com/spf13/afero"
@@ -44,8 +50,8 @@ var (
vendorInfo string
)
// Info contains information about the current Hugo environment
type Info struct {
// HugoInfo contains information about the current Hugo environment
type HugoInfo struct {
CommitHash string
BuildDate string
@@ -58,36 +64,48 @@ type Info struct {
// version of go that the Hugo binary was built with
GoVersion string
conf ConfigProvider
deps []*Dependency
}
// Version returns the current version as a comparable version string.
func (i Info) Version() VersionString {
func (i HugoInfo) Version() VersionString {
return CurrentVersion.Version()
}
// Generator a Hugo meta generator HTML tag.
func (i Info) Generator() template.HTML {
return template.HTML(fmt.Sprintf(`<meta name="generator" content="Hugo %s" />`, CurrentVersion.String()))
func (i HugoInfo) Generator() template.HTML {
return template.HTML(fmt.Sprintf(`<meta name="generator" content="Hugo %s">`, CurrentVersion.String()))
}
func (i Info) IsProduction() bool {
func (i HugoInfo) IsProduction() bool {
return i.Environment == EnvironmentProduction
}
func (i Info) IsExtended() bool {
func (i HugoInfo) IsExtended() bool {
return IsExtended
}
// WorkingDir returns the project working directory.
func (i HugoInfo) WorkingDir() string {
return i.conf.WorkingDir()
}
// Deps gets a list of dependencies for this Hugo build.
func (i Info) Deps() []*Dependency {
func (i HugoInfo) Deps() []*Dependency {
return i.deps
}
// ConfigProvider represents the config options that are relevant for HugoInfo.
type ConfigProvider interface {
Environment() string
WorkingDir() string
}
// NewInfo creates a new Hugo Info object.
func NewInfo(environment string, deps []*Dependency) Info {
if environment == "" {
environment = EnvironmentProduction
func NewInfo(conf ConfigProvider, deps []*Dependency) HugoInfo {
if conf.Environment() == "" {
panic("environment not set")
}
var (
commitHash string
@@ -102,10 +120,11 @@ func NewInfo(environment string, deps []*Dependency) Info {
goVersion = bi.GoVersion
}
return Info{
return HugoInfo{
CommitHash: commitHash,
BuildDate: buildDate,
Environment: environment,
Environment: conf.Environment(),
conf: conf,
deps: deps,
GoVersion: goVersion,
}
@@ -113,7 +132,7 @@ func NewInfo(environment string, deps []*Dependency) Info {
// GetExecEnviron creates and gets the common os/exec environment used in the
// external programs we interact with via os/exec, e.g. postcss.
func GetExecEnviron(workDir string, cfg config.Provider, fs afero.Fs) []string {
func GetExecEnviron(workDir string, cfg config.AllProvider, fs afero.Fs) []string {
var env []string
nodepath := filepath.Join(workDir, "node_modules")
if np := os.Getenv("NODE_PATH"); np != "" {
@@ -121,8 +140,9 @@ func GetExecEnviron(workDir string, cfg config.Provider, fs afero.Fs) []string {
}
config.SetEnvVars(&env, "NODE_PATH", nodepath)
config.SetEnvVars(&env, "PWD", workDir)
config.SetEnvVars(&env, "HUGO_ENVIRONMENT", cfg.GetString("environment"))
config.SetEnvVars(&env, "HUGO_ENV", cfg.GetString("environment"))
config.SetEnvVars(&env, "HUGO_ENVIRONMENT", cfg.Environment())
config.SetEnvVars(&env, "HUGO_ENV", cfg.Environment())
config.SetEnvVars(&env, "HUGO_PUBLISHDIR", filepath.Join(workDir, cfg.BaseConfig().PublishDir))
if fs != nil {
fis, err := afero.ReadDir(fs, files.FolderJSConfig)
@@ -184,24 +204,15 @@ func getBuildInfo() *buildInfo {
return bInfo
}
func formatDep(path, version string) string {
return fmt.Sprintf("%s=%q", path, version)
}
// GetDependencyList returns a sorted dependency list on the format package="version".
// It includes both Go dependencies and (a manually maintained) list of C(++) dependencies.
func GetDependencyList() []string {
var deps []string
formatDep := func(path, version string) string {
return fmt.Sprintf("%s=%q", path, version)
}
if IsExtended {
deps = append(
deps,
// TODO(bep) consider adding a DepsNonGo() method to these upstream projects.
formatDep("github.com/sass/libsass", "3.6.5"),
formatDep("github.com/webmproject/libwebp", "v1.2.0"),
)
}
bi := getBuildInfo()
if bi == nil {
return deps
@@ -211,11 +222,39 @@ func GetDependencyList() []string {
deps = append(deps, formatDep(dep.Path, dep.Version))
}
deps = append(deps, GetDependencyListNonGo()...)
sort.Strings(deps)
return deps
}
// GetDependencyListNonGo returns a list of non-Go dependencies.
func GetDependencyListNonGo() []string {
var deps []string
if IsExtended {
deps = append(
deps,
formatDep("github.com/sass/libsass", "3.6.5"),
formatDep("github.com/webmproject/libwebp", "v1.2.4"),
)
}
if dartSass := dartSassVersion(); dartSass.ProtocolVersion != "" {
var dartSassPath = "github.com/sass/dart-sass-embedded"
if IsDartSassV2() {
dartSassPath = "github.com/sass/dart-sass"
}
deps = append(deps,
formatDep(dartSassPath+"/protocol", dartSass.ProtocolVersion),
formatDep(dartSassPath+"/compiler", dartSass.CompilerVersion),
formatDep(dartSassPath+"/implementation", dartSass.ImplementationVersion),
)
}
return deps
}
// IsRunningAsTest reports whether we are running as a test.
func IsRunningAsTest() bool {
for _, arg := range os.Args {
@@ -249,3 +288,48 @@ type Dependency struct {
// Replaced by this dependency.
Replace *Dependency
}
func dartSassVersion() godartsass.DartSassVersion {
if DartSassBinaryName == "" {
return godartsass.DartSassVersion{}
}
if IsDartSassV2() {
v, _ := godartsass.Version(DartSassBinaryName)
return v
}
v, _ := godartsassv1.Version(DartSassBinaryName)
var vv godartsass.DartSassVersion
mapstructure.WeakDecode(v, &vv)
return vv
}
// DartSassBinaryName is the name of the Dart Sass binary to use.
// TODO(beop) find a better place for this.
var DartSassBinaryName string
func init() {
DartSassBinaryName = os.Getenv("DART_SASS_BINARY")
if DartSassBinaryName == "" {
for _, name := range dartSassBinaryNamesV2 {
if hexec.InPath(name) {
DartSassBinaryName = name
break
}
}
if DartSassBinaryName == "" {
if hexec.InPath(dartSassBinaryNameV1) {
DartSassBinaryName = dartSassBinaryNameV1
}
}
}
}
var (
dartSassBinaryNameV1 = "dart-sass-embedded"
dartSassBinaryNamesV2 = []string{"dart-sass", "sass"}
)
func IsDartSassV2() bool {
return !strings.Contains(DartSassBinaryName, "embedded")
}
+17 -2
View File
@@ -23,10 +23,12 @@ import (
func TestHugoInfo(t *testing.T) {
c := qt.New(t)
hugoInfo := NewInfo("", nil)
conf := testConfig{environment: "production", workingDir: "/mywork"}
hugoInfo := NewInfo(conf, nil)
c.Assert(hugoInfo.Version(), qt.Equals, CurrentVersion.Version())
c.Assert(fmt.Sprintf("%T", VersionString("")), qt.Equals, fmt.Sprintf("%T", hugoInfo.Version()))
c.Assert(hugoInfo.WorkingDir(), qt.Equals, "/mywork")
bi := getBuildInfo()
if bi != nil {
@@ -39,6 +41,19 @@ func TestHugoInfo(t *testing.T) {
c.Assert(hugoInfo.IsProduction(), qt.Equals, true)
c.Assert(hugoInfo.IsExtended(), qt.Equals, IsExtended)
devHugoInfo := NewInfo("development", nil)
devHugoInfo := NewInfo(testConfig{environment: "development"}, nil)
c.Assert(devHugoInfo.IsProduction(), qt.Equals, false)
}
type testConfig struct {
environment string
workingDir string
}
func (c testConfig) Environment() string {
return c.environment
}
func (c testConfig) WorkingDir() string {
return c.workingDir
}
+1 -1
View File
@@ -17,7 +17,7 @@ package hugo
// This should be the only one.
var CurrentVersion = Version{
Major: 0,
Minor: 104,
Minor: 116,
PatchLevel: 0,
Suffix: "-DEV",
}
+106
View File
@@ -0,0 +1,106 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Some functions in this file (see comments) is based on the Go source code,
// copyright The Go Authors and governed by a BSD-style license.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// package loggers contains some basic logging setup.
package loggers
import (
"fmt"
"io"
"strings"
"sync"
"github.com/bep/logg"
"github.com/fatih/color"
)
var bold = color.New(color.Bold)
// levelColor mapping.
var levelColor = [...]*color.Color{
logg.LevelDebug: color.New(color.FgWhite),
logg.LevelInfo: color.New(color.FgBlue),
logg.LevelWarn: color.New(color.FgYellow),
logg.LevelError: color.New(color.FgRed),
}
// levelString mapping.
var levelString = [...]string{
logg.LevelDebug: "DEBUG",
logg.LevelInfo: "INFO ",
logg.LevelWarn: "WARN ",
logg.LevelError: "ERROR",
}
// newDefaultHandler handler.
func newDefaultHandler(outWriter, errWriter io.Writer) logg.Handler {
return &defaultHandler{
outWriter: outWriter,
errWriter: errWriter,
Padding: 0,
}
}
// Default Handler implementation.
// Based on https://github.com/apex/log/blob/master/handlers/cli/cli.go
type defaultHandler struct {
mu sync.Mutex
outWriter io.Writer // Defaults to os.Stdout.
errWriter io.Writer // Defaults to os.Stderr.
Padding int
}
// HandleLog implements logg.Handler.
func (h *defaultHandler) HandleLog(e *logg.Entry) error {
color := levelColor[e.Level]
level := levelString[e.Level]
h.mu.Lock()
defer h.mu.Unlock()
var w io.Writer
if e.Level > logg.LevelInfo {
w = h.errWriter
} else {
w = h.outWriter
}
var prefix string
for _, field := range e.Fields {
if field.Name == FieldNameCmd {
prefix = fmt.Sprint(field.Value)
break
}
}
if prefix != "" {
prefix = prefix + ": "
}
color.Fprintf(w, "%s %s%s", fmt.Sprintf("%*s", h.Padding+1, level), color.Sprint(prefix), e.Message)
for _, field := range e.Fields {
if strings.HasPrefix(field.Name, reservedFieldNamePrefix) {
continue
}
fmt.Fprintf(w, " %s %v", color.Sprint(field.Name), field.Value)
}
fmt.Fprintln(w)
return nil
}
+158
View File
@@ -0,0 +1,158 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Some functions in this file (see comments) is based on the Go source code,
// copyright The Go Authors and governed by a BSD-style license.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package loggers
import (
"fmt"
"strings"
"sync"
"github.com/bep/logg"
"github.com/gohugoio/hugo/identity"
)
// PanicOnWarningHook panics on warnings.
var PanicOnWarningHook = func(e *logg.Entry) error {
if e.Level != logg.LevelWarn {
return nil
}
panic(e.Message)
}
func newLogLevelCounter() *logLevelCounter {
return &logLevelCounter{
counters: make(map[logg.Level]int),
}
}
func newLogOnceHandler(threshold logg.Level) *logOnceHandler {
return &logOnceHandler{
threshold: threshold,
seen: make(map[uint64]bool),
}
}
func newStopHandler(h ...logg.Handler) *stopHandler {
return &stopHandler{
handlers: h,
}
}
func newSuppressStatementsHandler(statements map[string]bool) *suppressStatementsHandler {
return &suppressStatementsHandler{
statements: statements,
}
}
type logLevelCounter struct {
mu sync.RWMutex
counters map[logg.Level]int
}
func (h *logLevelCounter) HandleLog(e *logg.Entry) error {
h.mu.Lock()
defer h.mu.Unlock()
h.counters[e.Level]++
return nil
}
var stopError = fmt.Errorf("stop")
type logOnceHandler struct {
threshold logg.Level
mu sync.Mutex
seen map[uint64]bool
}
func (h *logOnceHandler) HandleLog(e *logg.Entry) error {
if e.Level < h.threshold {
// We typically only want to enable this for warnings and above.
// The common use case is that many go routines may log the same error.
return nil
}
h.mu.Lock()
defer h.mu.Unlock()
hash := identity.HashUint64(e.Level, e.Message, e.Fields)
if h.seen[hash] {
return stopError
}
h.seen[hash] = true
return nil
}
func (h *logOnceHandler) reset() {
h.mu.Lock()
defer h.mu.Unlock()
h.seen = make(map[uint64]bool)
}
type stopHandler struct {
handlers []logg.Handler
}
// HandleLog implements logg.Handler.
func (h *stopHandler) HandleLog(e *logg.Entry) error {
for _, handler := range h.handlers {
if err := handler.HandleLog(e); err != nil {
if err == stopError {
return nil
}
return err
}
}
return nil
}
type suppressStatementsHandler struct {
statements map[string]bool
}
func (h *suppressStatementsHandler) HandleLog(e *logg.Entry) error {
for _, field := range e.Fields {
if field.Name == FieldNameStatementID {
if h.statements[field.Value.(string)] {
return stopError
}
}
}
return nil
}
// replacer creates a new log handler that does string replacement in log messages.
func replacer(repl *strings.Replacer) logg.Handler {
return logg.HandlerFunc(func(e *logg.Entry) error {
e.Message = repl.Replace(e.Message)
for i, field := range e.Fields {
if s, ok := field.Value.(string); ok {
e.Fields[i].Value = repl.Replace(s)
}
}
return nil
})
}
// whiteSpaceTrimmer creates a new log handler that trims whitespace from log messages and string fields.
func whiteSpaceTrimmer() logg.Handler {
return logg.HandlerFunc(func(e *logg.Entry) error {
e.Message = strings.TrimSpace(e.Message)
for i, field := range e.Fields {
if s, ok := field.Value.(string); ok {
e.Fields[i].Value = strings.TrimSpace(s)
}
}
return nil
})
}
+90
View File
@@ -0,0 +1,90 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Some functions in this file (see comments) is based on the Go source code,
// copyright The Go Authors and governed by a BSD-style license.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package loggers
import (
"fmt"
"io"
"strings"
"sync"
"github.com/bep/logg"
)
// newNoColoursHandler creates a new NoColoursHandler
func newNoColoursHandler(outWriter, errWriter io.Writer, noLevelPrefix bool, predicate func(*logg.Entry) bool) *noColoursHandler {
if predicate == nil {
predicate = func(e *logg.Entry) bool { return true }
}
return &noColoursHandler{
noLevelPrefix: noLevelPrefix,
outWriter: outWriter,
errWriter: errWriter,
predicate: predicate,
}
}
type noColoursHandler struct {
mu sync.Mutex
outWriter io.Writer // Defaults to os.Stdout.
errWriter io.Writer // Defaults to os.Stderr.
predicate func(*logg.Entry) bool
noLevelPrefix bool
}
func (h *noColoursHandler) HandleLog(e *logg.Entry) error {
if !h.predicate(e) {
return nil
}
h.mu.Lock()
defer h.mu.Unlock()
var w io.Writer
if e.Level > logg.LevelInfo {
w = h.errWriter
} else {
w = h.outWriter
}
var prefix string
for _, field := range e.Fields {
if field.Name == FieldNameCmd {
prefix = fmt.Sprint(field.Value)
break
}
}
if prefix != "" {
prefix = prefix + ": "
}
if h.noLevelPrefix {
fmt.Fprintf(w, "%s%s", prefix, e.Message)
} else {
fmt.Fprintf(w, "%s %s%s", levelString[e.Level], prefix, e.Message)
}
for _, field := range e.Fields {
if strings.HasPrefix(field.Name, reservedFieldNamePrefix) {
continue
}
fmt.Fprintf(w, " %s %q", field.Name, field.Value)
}
fmt.Fprintln(w)
return nil
}
-65
View File
@@ -1,65 +0,0 @@
// Copyright 2020 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package loggers
import (
"fmt"
"strings"
)
// IgnorableLogger is a logger that ignores certain log statements.
type IgnorableLogger interface {
Logger
Errorsf(statementID, format string, v ...any)
Apply(logger Logger) IgnorableLogger
}
type ignorableLogger struct {
Logger
statements map[string]bool
}
// NewIgnorableLogger wraps the given logger and ignores the log statement IDs given.
func NewIgnorableLogger(logger Logger, statements ...string) IgnorableLogger {
statementsSet := make(map[string]bool)
for _, s := range statements {
statementsSet[strings.ToLower(s)] = true
}
return ignorableLogger{
Logger: logger,
statements: statementsSet,
}
}
// Errorsf logs statementID as an ERROR if not configured as ignoreable.
func (l ignorableLogger) Errorsf(statementID, format string, v ...any) {
if l.statements[statementID] {
// Ignore.
return
}
ignoreMsg := fmt.Sprintf(`
If you feel that this should not be logged as an ERROR, you can ignore it by adding this to your site config:
ignoreErrors = [%q]`, statementID)
format += ignoreMsg
l.Errorf(format, v...)
}
func (l ignorableLogger) Apply(logger Logger) IgnorableLogger {
return ignorableLogger{
Logger: logger,
statements: l.statements,
}
}
+307
View File
@@ -0,0 +1,307 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Some functions in this file (see comments) is based on the Go source code,
// copyright The Go Authors and governed by a BSD-style license.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package loggers
import (
"fmt"
"io"
"os"
"strings"
"time"
"github.com/bep/logg"
"github.com/bep/logg/handlers/multi"
"github.com/gohugoio/hugo/common/terminal"
)
var (
reservedFieldNamePrefix = "__h_field_"
// FieldNameCmd is the name of the field that holds the command name.
FieldNameCmd = reservedFieldNamePrefix + "_cmd"
// Used to suppress statements.
FieldNameStatementID = reservedFieldNamePrefix + "__h_field_statement_id"
)
// Options defines options for the logger.
type Options struct {
Level logg.Level
Stdout io.Writer
Stderr io.Writer
Distinct bool
StoreErrors bool
HandlerPost func(e *logg.Entry) error
SuppresssStatements map[string]bool
}
// New creates a new logger with the given options.
func New(opts Options) Logger {
if opts.Stdout == nil {
opts.Stdout = os.Stdout
}
if opts.Stderr == nil {
opts.Stderr = os.Stdout
}
if opts.Level == 0 {
opts.Level = logg.LevelWarn
}
var logHandler logg.Handler
if terminal.PrintANSIColors(os.Stdout) {
logHandler = newDefaultHandler(opts.Stdout, opts.Stderr)
} else {
logHandler = newNoColoursHandler(opts.Stdout, opts.Stderr, false, nil)
}
errorsw := &strings.Builder{}
logCounters := newLogLevelCounter()
handlers := []logg.Handler{
whiteSpaceTrimmer(),
logHandler,
logCounters,
}
if opts.HandlerPost != nil {
var hookHandler logg.HandlerFunc = func(e *logg.Entry) error {
opts.HandlerPost(e)
return nil
}
handlers = append(handlers, hookHandler)
}
if opts.StoreErrors {
h := newNoColoursHandler(io.Discard, errorsw, true, func(e *logg.Entry) bool {
return e.Level >= logg.LevelError
})
handlers = append(handlers, h)
}
logHandler = multi.New(handlers...)
var logOnce *logOnceHandler
if opts.Distinct {
logOnce = newLogOnceHandler(logg.LevelWarn)
logHandler = newStopHandler(logOnce, logHandler)
}
if opts.SuppresssStatements != nil && len(opts.SuppresssStatements) > 0 {
logHandler = newStopHandler(newSuppressStatementsHandler(opts.SuppresssStatements), logHandler)
}
logger := logg.New(
logg.Options{
Level: opts.Level,
Handler: logHandler,
},
)
l := logger.WithLevel(opts.Level)
reset := func() {
logCounters.mu.Lock()
defer logCounters.mu.Unlock()
logCounters.counters = make(map[logg.Level]int)
errorsw.Reset()
if logOnce != nil {
logOnce.reset()
}
}
return &logAdapter{
logCounters: logCounters,
errors: errorsw,
reset: reset,
out: opts.Stdout,
level: opts.Level,
logger: logger,
debugl: l.WithLevel(logg.LevelDebug),
infol: l.WithLevel(logg.LevelInfo),
warnl: l.WithLevel(logg.LevelWarn),
errorl: l.WithLevel(logg.LevelError),
}
}
// NewDefault creates a new logger with the default options.
func NewDefault() Logger {
opts := Options{
Distinct: true,
Level: logg.LevelWarn,
Stdout: os.Stdout,
Stderr: os.Stdout,
}
return New(opts)
}
func LevelLoggerToWriter(l logg.LevelLogger) io.Writer {
return logWriter{l: l}
}
type Logger interface {
Debugf(format string, v ...any)
Debugln(v ...any)
Error() logg.LevelLogger
Errorf(format string, v ...any)
Errorln(v ...any)
Errors() string
Errorsf(id, format string, v ...any)
Info() logg.LevelLogger
InfoCommand(command string) logg.LevelLogger
Infof(format string, v ...any)
Infoln(v ...any)
Level() logg.Level
LoggCount(logg.Level) int
Logger() logg.Logger
Out() io.Writer
Printf(format string, v ...any)
Println(v ...any)
PrintTimerIfDelayed(start time.Time, name string)
Reset()
Warn() logg.LevelLogger
WarnCommand(command string) logg.LevelLogger
Warnf(format string, v ...any)
Warnln(v ...any)
}
type logAdapter struct {
logCounters *logLevelCounter
errors *strings.Builder
reset func()
out io.Writer
level logg.Level
logger logg.Logger
debugl logg.LevelLogger
infol logg.LevelLogger
warnl logg.LevelLogger
errorl logg.LevelLogger
}
func (l *logAdapter) Debugf(format string, v ...any) {
l.debugl.Logf(format, v...)
}
func (l *logAdapter) Debugln(v ...any) {
l.debugl.Logf(l.sprint(v...))
}
func (l *logAdapter) Info() logg.LevelLogger {
return l.infol
}
func (l *logAdapter) InfoCommand(command string) logg.LevelLogger {
return l.infol.WithField(FieldNameCmd, command)
}
func (l *logAdapter) Infof(format string, v ...any) {
l.infol.Logf(format, v...)
}
func (l *logAdapter) Infoln(v ...any) {
l.infol.Logf(l.sprint(v...))
}
func (l *logAdapter) Level() logg.Level {
return l.level
}
func (l *logAdapter) LoggCount(level logg.Level) int {
l.logCounters.mu.RLock()
defer l.logCounters.mu.RUnlock()
return l.logCounters.counters[level]
}
func (l *logAdapter) Logger() logg.Logger {
return l.logger
}
func (l *logAdapter) Out() io.Writer {
return l.out
}
// PrintTimerIfDelayed prints a time statement to the FEEDBACK logger
// if considerable time is spent.
func (l *logAdapter) PrintTimerIfDelayed(start time.Time, name string) {
elapsed := time.Since(start)
milli := int(1000 * elapsed.Seconds())
if milli < 500 {
return
}
l.Printf("%s in %v ms", name, milli)
}
func (l *logAdapter) Printf(format string, v ...any) {
// Add trailing newline if not present.
if !strings.HasSuffix(format, "\n") {
format += "\n"
}
fmt.Fprintf(l.out, format, v...)
}
func (l *logAdapter) Println(v ...any) {
fmt.Fprintln(l.out, v...)
}
func (l *logAdapter) Reset() {
l.reset()
}
func (l *logAdapter) Warn() logg.LevelLogger {
return l.warnl
}
func (l *logAdapter) Warnf(format string, v ...any) {
l.warnl.Logf(format, v...)
}
func (l *logAdapter) WarnCommand(command string) logg.LevelLogger {
return l.warnl.WithField(FieldNameCmd, command)
}
func (l *logAdapter) Warnln(v ...any) {
l.warnl.Logf(l.sprint(v...))
}
func (l *logAdapter) Error() logg.LevelLogger {
return l.errorl
}
func (l *logAdapter) Errorf(format string, v ...any) {
l.errorl.Logf(format, v...)
}
func (l *logAdapter) Errorln(v ...any) {
l.errorl.Logf(l.sprint(v...))
}
func (l *logAdapter) Errors() string {
return l.errors.String()
}
func (l *logAdapter) Errorsf(id, format string, v ...any) {
l.errorl.WithField(FieldNameStatementID, id).Logf(format, v...)
}
func (l *logAdapter) sprint(v ...any) string {
return strings.TrimRight(fmt.Sprintln(v...), "\n")
}
type logWriter struct {
l logg.LevelLogger
}
func (w logWriter) Write(p []byte) (n int, err error) {
w.l.Log(logg.String(string(p)))
return len(p), nil
}
+156
View File
@@ -0,0 +1,156 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Some functions in this file (see comments) is based on the Go source code,
// copyright The Go Authors and governed by a BSD-style license.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package loggers_test
import (
"io"
"strings"
"testing"
"github.com/bep/logg"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/common/loggers"
)
func TestLogDistinct(t *testing.T) {
c := qt.New(t)
opts := loggers.Options{
Distinct: true,
StoreErrors: true,
Stdout: io.Discard,
Stderr: io.Discard,
}
l := loggers.New(opts)
for i := 0; i < 10; i++ {
l.Errorln("error 1")
l.Errorln("error 2")
l.Warnln("warn 1")
}
c.Assert(strings.Count(l.Errors(), "error 1"), qt.Equals, 1)
c.Assert(l.LoggCount(logg.LevelError), qt.Equals, 2)
c.Assert(l.LoggCount(logg.LevelWarn), qt.Equals, 1)
}
func TestHookLast(t *testing.T) {
c := qt.New(t)
opts := loggers.Options{
HandlerPost: func(e *logg.Entry) error {
panic(e.Message)
},
Stdout: io.Discard,
Stderr: io.Discard,
}
l := loggers.New(opts)
c.Assert(func() { l.Warnln("warn 1") }, qt.PanicMatches, "warn 1")
}
func TestOptionStoreErrors(t *testing.T) {
c := qt.New(t)
var sb strings.Builder
opts := loggers.Options{
StoreErrors: true,
Stderr: &sb,
Stdout: &sb,
}
l := loggers.New(opts)
l.Errorln("error 1")
l.Errorln("error 2")
errorsStr := l.Errors()
c.Assert(errorsStr, qt.Contains, "error 1")
c.Assert(errorsStr, qt.Not(qt.Contains), "ERROR")
c.Assert(sb.String(), qt.Contains, "error 1")
c.Assert(sb.String(), qt.Contains, "ERROR")
}
func TestLogCount(t *testing.T) {
c := qt.New(t)
opts := loggers.Options{
StoreErrors: true,
}
l := loggers.New(opts)
l.Errorln("error 1")
l.Errorln("error 2")
l.Warnln("warn 1")
c.Assert(l.LoggCount(logg.LevelError), qt.Equals, 2)
c.Assert(l.LoggCount(logg.LevelWarn), qt.Equals, 1)
c.Assert(l.LoggCount(logg.LevelInfo), qt.Equals, 0)
}
func TestSuppressStatements(t *testing.T) {
c := qt.New(t)
opts := loggers.Options{
StoreErrors: true,
SuppresssStatements: map[string]bool{
"error-1": true,
},
}
l := loggers.New(opts)
l.Error().WithField(loggers.FieldNameStatementID, "error-1").Logf("error 1")
l.Errorln("error 2")
errorsStr := l.Errors()
c.Assert(errorsStr, qt.Not(qt.Contains), "error 1")
c.Assert(errorsStr, qt.Contains, "error 2")
c.Assert(l.LoggCount(logg.LevelError), qt.Equals, 1)
}
func TestReset(t *testing.T) {
c := qt.New(t)
opts := loggers.Options{
StoreErrors: true,
Distinct: true,
Stdout: io.Discard,
Stderr: io.Discard,
}
l := loggers.New(opts)
for i := 0; i < 3; i++ {
l.Errorln("error 1")
l.Errorln("error 2")
l.Errorln("error 1")
c.Assert(l.LoggCount(logg.LevelError), qt.Equals, 2)
l.Reset()
errorsStr := l.Errors()
c.Assert(errorsStr, qt.Equals, "")
c.Assert(l.LoggCount(logg.LevelError), qt.Equals, 0)
}
}
+53
View File
@@ -0,0 +1,53 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Some functions in this file (see comments) is based on the Go source code,
// copyright The Go Authors and governed by a BSD-style license.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package loggers
import (
"sync"
"github.com/bep/logg"
)
func InitGlobalLogger(panicOnWarnings bool) {
logMu.Lock()
defer logMu.Unlock()
var logHookLast func(e *logg.Entry) error
if panicOnWarnings {
logHookLast = PanicOnWarningHook
}
log = New(
Options{
Distinct: true,
HandlerPost: logHookLast,
},
)
}
var logMu sync.Mutex
func Log() Logger {
logMu.Lock()
defer logMu.Unlock()
return log
}
// The global logger.
var log Logger
func init() {
InitGlobalLogger(false)
}
-355
View File
@@ -1,355 +0,0 @@
// Copyright 2020 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package loggers
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"regexp"
"runtime"
"time"
"github.com/gohugoio/hugo/common/terminal"
jww "github.com/spf13/jwalterweatherman"
)
var (
// Counts ERROR logs to the global jww logger.
GlobalErrorCounter *jww.Counter
PanicOnWarning bool
)
func init() {
GlobalErrorCounter = &jww.Counter{}
jww.SetLogListeners(jww.LogCounter(GlobalErrorCounter, jww.LevelError))
}
func LoggerToWriterWithPrefix(logger *log.Logger, prefix string) io.Writer {
return prefixWriter{
logger: logger,
prefix: prefix,
}
}
type prefixWriter struct {
logger *log.Logger
prefix string
}
func (w prefixWriter) Write(p []byte) (n int, err error) {
w.logger.Printf("%s: %s", w.prefix, p)
return len(p), nil
}
type Logger interface {
Printf(format string, v ...any)
Println(v ...any)
PrintTimerIfDelayed(start time.Time, name string)
Debug() *log.Logger
Debugf(format string, v ...any)
Debugln(v ...any)
Info() *log.Logger
Infof(format string, v ...any)
Infoln(v ...any)
Warn() *log.Logger
Warnf(format string, v ...any)
Warnln(v ...any)
Error() *log.Logger
Errorf(format string, v ...any)
Errorln(v ...any)
Errors() string
Out() io.Writer
Reset()
// Used in tests.
LogCounters() *LogCounters
}
type LogCounters struct {
ErrorCounter *jww.Counter
WarnCounter *jww.Counter
}
type logger struct {
*jww.Notepad
// The writer that represents stdout.
// Will be ioutil.Discard when in quiet mode.
out io.Writer
logCounters *LogCounters
// This is only set in server mode.
errors *bytes.Buffer
}
func (l *logger) Printf(format string, v ...any) {
l.FEEDBACK.Printf(format, v...)
}
func (l *logger) Println(v ...any) {
l.FEEDBACK.Println(v...)
}
func (l *logger) Debug() *log.Logger {
return l.DEBUG
}
func (l *logger) Debugf(format string, v ...any) {
l.DEBUG.Printf(format, v...)
}
func (l *logger) Debugln(v ...any) {
l.DEBUG.Println(v...)
}
func (l *logger) Infof(format string, v ...any) {
l.INFO.Printf(format, v...)
}
func (l *logger) Infoln(v ...any) {
l.INFO.Println(v...)
}
func (l *logger) Info() *log.Logger {
return l.INFO
}
const panicOnWarningMessage = "Warning trapped. Remove the --panicOnWarning flag to continue."
func (l *logger) Warnf(format string, v ...any) {
l.WARN.Printf(format, v...)
if PanicOnWarning {
panic(panicOnWarningMessage)
}
}
func (l *logger) Warnln(v ...any) {
l.WARN.Println(v...)
if PanicOnWarning {
panic(panicOnWarningMessage)
}
}
func (l *logger) Warn() *log.Logger {
return l.WARN
}
func (l *logger) Errorf(format string, v ...any) {
l.ERROR.Printf(format, v...)
}
func (l *logger) Errorln(v ...any) {
l.ERROR.Println(v...)
}
func (l *logger) Error() *log.Logger {
return l.ERROR
}
func (l *logger) LogCounters() *LogCounters {
return l.logCounters
}
func (l *logger) Out() io.Writer {
return l.out
}
// PrintTimerIfDelayed prints a time statement to the FEEDBACK logger
// if considerable time is spent.
func (l *logger) PrintTimerIfDelayed(start time.Time, name string) {
elapsed := time.Since(start)
milli := int(1000 * elapsed.Seconds())
if milli < 500 {
return
}
l.Printf("%s in %v ms", name, milli)
}
func (l *logger) PrintTimer(start time.Time, name string) {
elapsed := time.Since(start)
milli := int(1000 * elapsed.Seconds())
l.Printf("%s in %v ms", name, milli)
}
func (l *logger) Errors() string {
if l.errors == nil {
return ""
}
return ansiColorRe.ReplaceAllString(l.errors.String(), "")
}
// Reset resets the logger's internal state.
func (l *logger) Reset() {
l.logCounters.ErrorCounter.Reset()
if l.errors != nil {
l.errors.Reset()
}
}
// NewLogger creates a new Logger for the given thresholds
func NewLogger(stdoutThreshold, logThreshold jww.Threshold, outHandle, logHandle io.Writer, saveErrors bool) Logger {
return newLogger(stdoutThreshold, logThreshold, outHandle, logHandle, saveErrors)
}
// NewDebugLogger is a convenience function to create a debug logger.
func NewDebugLogger() Logger {
return NewBasicLogger(jww.LevelDebug)
}
// NewWarningLogger is a convenience function to create a warning logger.
func NewWarningLogger() Logger {
return NewBasicLogger(jww.LevelWarn)
}
// NewInfoLogger is a convenience function to create a info logger.
func NewInfoLogger() Logger {
return NewBasicLogger(jww.LevelInfo)
}
// NewErrorLogger is a convenience function to create an error logger.
func NewErrorLogger() Logger {
return NewBasicLogger(jww.LevelError)
}
// NewBasicLogger creates a new basic logger writing to Stdout.
func NewBasicLogger(t jww.Threshold) Logger {
return newLogger(t, jww.LevelError, os.Stdout, ioutil.Discard, false)
}
// NewBasicLoggerForWriter creates a new basic logger writing to w.
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)")
)
type ansiCleaner struct {
w io.Writer
}
func (a ansiCleaner) Write(p []byte) (n int, err error) {
return a.w.Write(ansiColorRe.ReplaceAll(p, []byte("")))
}
type labelColorizer struct {
w io.Writer
}
func (a labelColorizer) Write(p []byte) (n int, err error) {
replaced := errorRe.ReplaceAllStringFunc(string(p), func(m string) string {
switch m {
case "ERROR", "FATAL":
return terminal.Error(m)
case "WARN":
return terminal.Warning(m)
default:
return m
}
})
// io.MultiWriter will abort if we return a bigger write count than input
// bytes, so we lie a little.
_, err = a.w.Write([]byte(replaced))
return len(p), err
}
// InitGlobalLogger initializes the global logger, used in some rare cases.
func InitGlobalLogger(stdoutThreshold, logThreshold jww.Threshold, outHandle, logHandle io.Writer) {
outHandle, logHandle = getLogWriters(outHandle, logHandle)
jww.SetStdoutOutput(outHandle)
jww.SetLogOutput(logHandle)
jww.SetLogThreshold(logThreshold)
jww.SetStdoutThreshold(stdoutThreshold)
}
func getLogWriters(outHandle, logHandle io.Writer) (io.Writer, io.Writer) {
isTerm := terminal.PrintANSIColors(os.Stdout)
if logHandle != ioutil.Discard && isTerm {
// Remove any Ansi coloring from log output
logHandle = ansiCleaner{w: logHandle}
}
if isTerm {
outHandle = labelColorizer{w: outHandle}
}
return outHandle, logHandle
}
type fatalLogWriter int
func (s fatalLogWriter) Write(p []byte) (n int, err error) {
trace := make([]byte, 1500)
runtime.Stack(trace, true)
fmt.Printf("\n===========\n\n%s\n", trace)
os.Exit(-1)
return 0, nil
}
var fatalLogListener = func(t jww.Threshold) io.Writer {
if t != jww.LevelError {
// Only interested in ERROR
return nil
}
return new(fatalLogWriter)
}
func newLogger(stdoutThreshold, logThreshold jww.Threshold, outHandle, logHandle io.Writer, saveErrors bool) *logger {
errorCounter := &jww.Counter{}
warnCounter := &jww.Counter{}
outHandle, logHandle = getLogWriters(outHandle, logHandle)
listeners := []jww.LogListener{jww.LogCounter(errorCounter, jww.LevelError), jww.LogCounter(warnCounter, jww.LevelWarn)}
var errorBuff *bytes.Buffer
if saveErrors {
errorBuff = new(bytes.Buffer)
errorCapture := func(t jww.Threshold) io.Writer {
if t != jww.LevelError {
// Only interested in ERROR
return nil
}
return errorBuff
}
listeners = append(listeners, errorCapture)
}
return &logger{
Notepad: jww.NewNotepad(stdoutThreshold, logThreshold, outHandle, logHandle, "", log.Ldate|log.Ltime, listeners...),
out: outHandle,
logCounters: &LogCounters{
ErrorCounter: errorCounter,
WarnCounter: warnCounter,
},
errors: errorBuff,
}
}
-60
View File
@@ -1,60 +0,0 @@
// Copyright 2018 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package loggers
import (
"bytes"
"fmt"
"log"
"testing"
qt "github.com/frankban/quicktest"
)
func TestLogger(t *testing.T) {
c := qt.New(t)
l := NewWarningLogger()
l.Errorln("One error")
l.Errorln("Two error")
l.Warnln("A warning")
c.Assert(l.LogCounters().ErrorCounter.Count(), qt.Equals, uint64(2))
}
func TestLoggerToWriterWithPrefix(t *testing.T) {
c := qt.New(t)
var b bytes.Buffer
logger := log.New(&b, "", 0)
w := LoggerToWriterWithPrefix(logger, "myprefix")
fmt.Fprint(w, "Hello Hugo!")
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 |")
}
+27 -8
View File
@@ -43,25 +43,25 @@ func ToStringMapE(in any) (map[string]any, error) {
// ToParamsAndPrepare converts in to Params and prepares it for use.
// If in is nil, an empty map is returned.
// See PrepareParams.
func ToParamsAndPrepare(in any) (Params, bool) {
func ToParamsAndPrepare(in any) (Params, error) {
if types.IsNil(in) {
return Params{}, true
return Params{}, nil
}
m, err := ToStringMapE(in)
if err != nil {
return nil, false
return nil, err
}
PrepareParams(m)
return m, true
return m, nil
}
// MustToParamsAndPrepare calls ToParamsAndPrepare and panics if it fails.
func MustToParamsAndPrepare(in any) Params {
if p, ok := ToParamsAndPrepare(in); ok {
return p
} else {
panic(fmt.Sprintf("cannot convert %T to maps.Params", in))
p, err := ToParamsAndPrepare(in)
if err != nil {
panic(fmt.Sprintf("cannot convert %T to maps.Params: %s", in, err))
}
return p
}
// ToStringMap converts in to map[string]interface{}.
@@ -96,6 +96,8 @@ func ToSliceStringMap(in any) ([]map[string]any, error) {
switch v := in.(type) {
case []map[string]any:
return v, nil
case Params:
return []map[string]any{v}, nil
case []any:
var s []map[string]any
for _, entry := range v {
@@ -123,6 +125,23 @@ func LookupEqualFold[T any | string](m map[string]T, key string) (T, bool) {
return s, false
}
// MergeShallow merges src into dst, but only if the key does not already exist in dst.
// The keys are compared case insensitively.
func MergeShallow(dst, src map[string]any) {
for k, v := range src {
found := false
for dk := range dst {
if strings.EqualFold(dk, k) {
found = true
break
}
}
if !found {
dst[k] = v
}
}
}
type keyRename struct {
pattern glob.Glob
newKey string
+4 -4
View File
@@ -116,11 +116,11 @@ func TestToSliceStringMap(t *testing.T) {
func TestToParamsAndPrepare(t *testing.T) {
c := qt.New(t)
_, ok := ToParamsAndPrepare(map[string]any{"A": "av"})
c.Assert(ok, qt.IsTrue)
_, err := ToParamsAndPrepare(map[string]any{"A": "av"})
c.Assert(err, qt.IsNil)
params, ok := ToParamsAndPrepare(nil)
c.Assert(ok, qt.IsTrue)
params, err := ToParamsAndPrepare(nil)
c.Assert(err, qt.IsNil)
c.Assert(params, qt.DeepEquals, Params{})
}
+87 -29
View File
@@ -23,30 +23,37 @@ import (
// Params is a map where all keys are lower case.
type Params map[string]any
// Get does a lower case and nested search in this map.
// KeyParams is an utility struct for the WalkParams method.
type KeyParams struct {
Key string
Params Params
}
// GetNested does a lower case and nested search in this map.
// It will return nil if none found.
func (p Params) Get(indices ...string) any {
// Make all of these methods internal somehow.
func (p Params) GetNested(indices ...string) any {
v, _, _ := getNested(p, indices)
return v
}
// Set overwrites values in p with values in pp for common or new keys.
// SetParams overwrites values in dst with values in src for common or new keys.
// This is done recursively.
func (p Params) Set(pp Params) {
for k, v := range pp {
vv, found := p[k]
func SetParams(dst, src Params) {
for k, v := range src {
vv, found := dst[k]
if !found {
p[k] = v
dst[k] = v
} else {
switch vvv := vv.(type) {
case Params:
if pv, ok := v.(Params); ok {
vvv.Set(pv)
SetParams(vvv, pv)
} else {
p[k] = v
dst[k] = v
}
default:
p[k] = v
dst[k] = v
}
}
}
@@ -62,26 +69,25 @@ func (p Params) IsZero() bool {
return false
}
for k, _ := range p {
return k == mergeStrategyKey
for k := range p {
return k == MergeStrategyKey
}
return false
}
// Merge transfers values from pp to p for new keys.
// MergeParamsWithStrategy transfers values from src to dst for new keys using the merge strategy given.
// This is done recursively.
func (p Params) Merge(pp Params) {
p.merge("", pp)
func MergeParamsWithStrategy(strategy string, dst, src Params) {
dst.merge(ParamsMergeStrategy(strategy), src)
}
// MergeRoot transfers values from pp to p for new keys where p is the
// root of the tree.
// MergeParams transfers values from src to dst for new keys using the merge encoded in dst.
// This is done recursively.
func (p Params) MergeRoot(pp Params) {
ms, _ := p.GetMergeStrategy()
p.merge(ms, pp)
func MergeParams(dst, src Params) {
ms, _ := dst.GetMergeStrategy()
dst.merge(ms, src)
}
func (p Params) merge(ps ParamsMergeStrategy, pp Params) {
@@ -97,7 +103,7 @@ func (p Params) merge(ps ParamsMergeStrategy, pp Params) {
for k, v := range pp {
if k == mergeStrategyKey {
if k == MergeStrategyKey {
continue
}
vv, found := p[k]
@@ -116,8 +122,9 @@ func (p Params) merge(ps ParamsMergeStrategy, pp Params) {
}
}
// For internal use.
func (p Params) GetMergeStrategy() (ParamsMergeStrategy, bool) {
if v, found := p[mergeStrategyKey]; found {
if v, found := p[MergeStrategyKey]; found {
if s, ok := v.(ParamsMergeStrategy); ok {
return s, true
}
@@ -125,21 +132,23 @@ func (p Params) GetMergeStrategy() (ParamsMergeStrategy, bool) {
return ParamsMergeStrategyShallow, false
}
// For internal use.
func (p Params) DeleteMergeStrategy() bool {
if _, found := p[mergeStrategyKey]; found {
delete(p, mergeStrategyKey)
if _, found := p[MergeStrategyKey]; found {
delete(p, MergeStrategyKey)
return true
}
return false
}
func (p Params) SetDefaultMergeStrategy(s ParamsMergeStrategy) {
// For internal use.
func (p Params) SetMergeStrategy(s ParamsMergeStrategy) {
switch s {
case ParamsMergeStrategyDeep, ParamsMergeStrategyNone, ParamsMergeStrategyShallow:
default:
panic(fmt.Sprintf("invalid merge strategy %q", s))
}
p[mergeStrategyKey] = s
p[MergeStrategyKey] = s
}
func getNested(m map[string]any, indices []string) (any, string, map[string]any) {
@@ -187,7 +196,7 @@ func GetNestedParam(keyStr, separator string, candidates ...Params) (any, error)
keySegments := strings.Split(keyStr, separator)
for _, m := range candidates {
if v := m.Get(keySegments...); v != nil {
if v := m.GetNested(keySegments...); v != nil {
return v, nil
}
}
@@ -233,9 +242,58 @@ const (
// Add new keys, merge existing.
ParamsMergeStrategyDeep ParamsMergeStrategy = "deep"
mergeStrategyKey = "_merge"
MergeStrategyKey = "_merge"
)
// CleanConfigStringMapString removes any processing instructions from m,
// m will never be modified.
func CleanConfigStringMapString(m map[string]string) map[string]string {
if m == nil || len(m) == 0 {
return m
}
if _, found := m[MergeStrategyKey]; !found {
return m
}
// Create a new map and copy all the keys except the merge strategy key.
m2 := make(map[string]string, len(m)-1)
for k, v := range m {
if k != MergeStrategyKey {
m2[k] = v
}
}
return m2
}
// CleanConfigStringMap is the same as CleanConfigStringMapString but for
// map[string]any.
func CleanConfigStringMap(m map[string]any) map[string]any {
if m == nil || len(m) == 0 {
return m
}
if _, found := m[MergeStrategyKey]; !found {
return m
}
// Create a new map and copy all the keys except the merge strategy key.
m2 := make(map[string]any, len(m)-1)
for k, v := range m {
if k != MergeStrategyKey {
m2[k] = v
}
switch v2 := v.(type) {
case map[string]any:
m2[k] = CleanConfigStringMap(v2)
case Params:
var p Params = CleanConfigStringMap(v2)
m2[k] = p
case map[string]string:
m2[k] = CleanConfigStringMapString(v2)
}
}
return m2
}
func toMergeStrategy(v any) ParamsMergeStrategy {
s := ParamsMergeStrategy(cast.ToString(v))
switch s {
@@ -255,7 +313,7 @@ func PrepareParams(m Params) {
for k, v := range m {
var retyped bool
lKey := strings.ToLower(k)
if lKey == mergeStrategyKey {
if lKey == MergeStrategyKey {
v = toMergeStrategy(v)
retyped = true
} else {
+10 -10
View File
@@ -75,13 +75,13 @@ func TestParamsSetAndMerge(t *testing.T) {
createParamsPair := func() (Params, Params) {
p1 := Params{"a": "av", "c": "cv", "nested": Params{"al2": "al2v", "cl2": "cl2v"}}
p2 := Params{"b": "bv", "a": "abv", "nested": Params{"bl2": "bl2v", "al2": "al2bv"}, mergeStrategyKey: ParamsMergeStrategyDeep}
p2 := Params{"b": "bv", "a": "abv", "nested": Params{"bl2": "bl2v", "al2": "al2bv"}, MergeStrategyKey: ParamsMergeStrategyDeep}
return p1, p2
}
p1, p2 := createParamsPair()
p1.Set(p2)
SetParams(p1, p2)
c.Assert(p1, qt.DeepEquals, Params{
"a": "abv",
@@ -92,12 +92,12 @@ func TestParamsSetAndMerge(t *testing.T) {
"bl2": "bl2v",
},
"b": "bv",
mergeStrategyKey: ParamsMergeStrategyDeep,
MergeStrategyKey: ParamsMergeStrategyDeep,
})
p1, p2 = createParamsPair()
p1.Merge(p2)
MergeParamsWithStrategy("", p1, p2)
// Default is to do a shallow merge.
c.Assert(p1, qt.DeepEquals, Params{
@@ -111,8 +111,8 @@ func TestParamsSetAndMerge(t *testing.T) {
})
p1, p2 = createParamsPair()
p1.SetDefaultMergeStrategy(ParamsMergeStrategyNone)
p1.Merge(p2)
p1.SetMergeStrategy(ParamsMergeStrategyNone)
MergeParamsWithStrategy("", p1, p2)
p1.DeleteMergeStrategy()
c.Assert(p1, qt.DeepEquals, Params{
@@ -125,8 +125,8 @@ func TestParamsSetAndMerge(t *testing.T) {
})
p1, p2 = createParamsPair()
p1.SetDefaultMergeStrategy(ParamsMergeStrategyShallow)
p1.Merge(p2)
p1.SetMergeStrategy(ParamsMergeStrategyShallow)
MergeParamsWithStrategy("", p1, p2)
p1.DeleteMergeStrategy()
c.Assert(p1, qt.DeepEquals, Params{
@@ -140,8 +140,8 @@ func TestParamsSetAndMerge(t *testing.T) {
})
p1, p2 = createParamsPair()
p1.SetDefaultMergeStrategy(ParamsMergeStrategyDeep)
p1.Merge(p2)
p1.SetMergeStrategy(ParamsMergeStrategyDeep)
MergeParamsWithStrategy("", p1, p2)
p1.DeleteMergeStrategy()
c.Assert(p1, qt.DeepEquals, Params{
+1
View File
@@ -30,6 +30,7 @@ type Scratch struct {
// Scratcher provides a scratching service.
type Scratcher interface {
// Scratch returns a "scratch pad" that can be used to store state.
Scratch() *Scratch
}
+1 -1
View File
@@ -27,7 +27,7 @@ type Workers struct {
// Runner wraps the lifecycle methods of a new task set.
//
// Run wil block until a worker is available or the context is cancelled,
// Run will block until a worker is available or the context is cancelled,
// and then run the given func in a new goroutine.
// Wait will wait for all the running goroutines to finish.
type Runner interface {
+19 -8
View File
@@ -100,25 +100,25 @@ var isFileRe = regexp.MustCompile(`.*\..{1,6}$`)
// GetDottedRelativePath expects a relative path starting after the content directory.
// It returns a relative path with dots ("..") navigating up the path structure.
func GetDottedRelativePath(inPath string) string {
inPath = filepath.Clean(filepath.FromSlash(inPath))
inPath = path.Clean(filepath.ToSlash(inPath))
if inPath == "." {
return "./"
}
if !isFileRe.MatchString(inPath) && !strings.HasSuffix(inPath, FilePathSeparator) {
inPath += FilePathSeparator
if !isFileRe.MatchString(inPath) && !strings.HasSuffix(inPath, "/") {
inPath += "/"
}
if !strings.HasPrefix(inPath, FilePathSeparator) {
inPath = FilePathSeparator + inPath
if !strings.HasPrefix(inPath, "/") {
inPath = "/" + inPath
}
dir, _ := filepath.Split(inPath)
sectionCount := strings.Count(dir, FilePathSeparator)
sectionCount := strings.Count(dir, "/")
if sectionCount == 0 || dir == FilePathSeparator {
if sectionCount == 0 || dir == "/" {
return "./"
}
@@ -209,7 +209,7 @@ func extractFilename(in, ext, base, pathSeparator string) (name string) {
// return the filename minus the extension (and the ".")
name = base[:strings.LastIndex(base, ".")]
} else {
// no extension case so just return base, which willi
// no extension case so just return base, which will
// be the filename
name = base
}
@@ -263,3 +263,14 @@ func (n NamedSlice) String() string {
}
return fmt.Sprintf("%s%s{%s}", n.Name, FilePathSeparator, strings.Join(n.Slice, ","))
}
// DirFile holds the result from path.Split.
type DirFile struct {
Dir string
File string
}
// Used in test.
func (df DirFile) String() string {
return fmt.Sprintf("%s|%s", df.Dir, df.File)
}
+2
View File
@@ -24,6 +24,8 @@ import (
// Positioner represents a thing that knows its position in a text file or stream,
// typically an error.
type Positioner interface {
// Position returns the current position.
// Useful in error logging, e.g. {{ errorf "error in code block: %s" .Position }}.
Position() Position
}
@@ -1,7 +1,4 @@
//go:build !release
// +build !release
// Copyright 2018 The Hugo Authors. All rights reserved.
// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,8 +11,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
package css
func createReleaser() cmder {
return &nilCommand{}
}
// QuotedString is a string that needs to be quoted in CSS.
type QuotedString string
// UnquotedString is a string that does not need to be quoted in CSS.
type UnquotedString string
+4
View File
@@ -17,6 +17,7 @@ package types
import (
"fmt"
"reflect"
"sync/atomic"
"github.com/spf13/cast"
)
@@ -90,3 +91,6 @@ func IsNil(v any) bool {
type DevMarker interface {
DevOnly()
}
// This is only used for debugging purposes.
var InvocationCounter atomic.Int64
@@ -1,4 +1,4 @@
// Copyright 2018 The Hugo Authors. All rights reserved.
// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -11,32 +11,37 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package paths
package urls
import (
"fmt"
"net/url"
"strconv"
"strings"
)
// A BaseURL in Hugo is normally on the form scheme://path, but the
// form scheme: is also valid (mailto:hugo@rules.com).
type BaseURL struct {
url *url.URL
urlStr string
url *url.URL
WithPath string
WithoutPath string
BasePath string
}
func (b BaseURL) String() string {
if b.urlStr != "" {
return b.urlStr
}
return b.url.String()
return b.WithPath
}
func (b BaseURL) Path() string {
return b.url.Path
}
func (b BaseURL) Port() int {
p, _ := strconv.Atoi(b.url.Port())
return p
}
// HostURL returns the URL to the host root without any path elements.
func (b BaseURL) HostURL() string {
return strings.TrimSuffix(b.String(), b.Path())
@@ -44,7 +49,7 @@ func (b BaseURL) HostURL() string {
// WithProtocol returns the BaseURL prefixed with the given protocol.
// The Protocol is normally of the form "scheme://", i.e. "webcal://".
func (b BaseURL) WithProtocol(protocol string) (string, error) {
func (b BaseURL) WithProtocol(protocol string) (BaseURL, error) {
u := b.URL()
scheme := protocol
@@ -62,10 +67,16 @@ func (b BaseURL) WithProtocol(protocol string) (string, error) {
if isFullProtocol && u.Opaque != "" {
u.Opaque = "//" + u.Opaque
} else if isOpaqueProtocol && u.Opaque == "" {
return "", fmt.Errorf("cannot determine BaseURL for protocol %q", protocol)
return BaseURL{}, fmt.Errorf("cannot determine BaseURL for protocol %q", protocol)
}
return u.String(), nil
return newBaseURLFromURL(u)
}
func (b BaseURL) WithPort(port int) (BaseURL, error) {
u := b.URL()
u.Host = u.Hostname() + ":" + strconv.Itoa(port)
return newBaseURLFromURL(u)
}
// URL returns a copy of the internal URL.
@@ -75,13 +86,25 @@ func (b BaseURL) URL() *url.URL {
return &c
}
func newBaseURLFromString(b string) (BaseURL, error) {
var result BaseURL
base, err := url.Parse(b)
func NewBaseURLFromString(b string) (BaseURL, error) {
u, err := url.Parse(b)
if err != nil {
return result, err
return BaseURL{}, err
}
return newBaseURLFromURL(u)
}
func newBaseURLFromURL(u *url.URL) (BaseURL, error) {
baseURL := BaseURL{url: u, WithPath: u.String()}
var baseURLNoPath = baseURL.URL()
baseURLNoPath.Path = ""
baseURL.WithoutPath = baseURLNoPath.String()
basePath := u.Path
if basePath != "" && basePath != "/" {
baseURL.BasePath = basePath
}
return BaseURL{url: base, urlStr: base.String()}, nil
return baseURL, nil
}
@@ -1,4 +1,4 @@
// Copyright 2018 The Hugo Authors. All rights reserved.
// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package paths
package urls
import (
"testing"
@@ -21,46 +21,46 @@ import (
func TestBaseURL(t *testing.T) {
c := qt.New(t)
b, err := newBaseURLFromString("http://example.com")
b, err := NewBaseURLFromString("http://example.com")
c.Assert(err, qt.IsNil)
c.Assert(b.String(), qt.Equals, "http://example.com")
p, err := b.WithProtocol("webcal://")
c.Assert(err, qt.IsNil)
c.Assert(p, qt.Equals, "webcal://example.com")
c.Assert(p.String(), qt.Equals, "webcal://example.com")
p, err = b.WithProtocol("webcal")
c.Assert(err, qt.IsNil)
c.Assert(p, qt.Equals, "webcal://example.com")
c.Assert(p.String(), qt.Equals, "webcal://example.com")
_, err = b.WithProtocol("mailto:")
c.Assert(err, qt.Not(qt.IsNil))
b, err = newBaseURLFromString("mailto:hugo@rules.com")
b, err = NewBaseURLFromString("mailto:hugo@rules.com")
c.Assert(err, qt.IsNil)
c.Assert(b.String(), qt.Equals, "mailto:hugo@rules.com")
// These are pretty constructed
p, err = b.WithProtocol("webcal")
c.Assert(err, qt.IsNil)
c.Assert(p, qt.Equals, "webcal:hugo@rules.com")
c.Assert(p.String(), qt.Equals, "webcal:hugo@rules.com")
p, err = b.WithProtocol("webcal://")
c.Assert(err, qt.IsNil)
c.Assert(p, qt.Equals, "webcal://hugo@rules.com")
c.Assert(p.String(), qt.Equals, "webcal://hugo@rules.com")
// Test with "non-URLs". Some people will try to use these as a way to get
// relative URLs working etc.
b, err = newBaseURLFromString("/")
b, err = NewBaseURLFromString("/")
c.Assert(err, qt.IsNil)
c.Assert(b.String(), qt.Equals, "/")
b, err = newBaseURLFromString("")
b, err = NewBaseURLFromString("")
c.Assert(err, qt.IsNil)
c.Assert(b.String(), qt.Equals, "")
// BaseURL with sub path
b, err = newBaseURLFromString("http://example.com/sub")
b, err = NewBaseURLFromString("http://example.com/sub")
c.Assert(err, qt.IsNil)
c.Assert(b.String(), qt.Equals, "http://example.com/sub")
c.Assert(b.HostURL(), qt.Equals, "http://example.com")
+16
View File
@@ -36,3 +36,19 @@ type ProbablyEqer interface {
type Comparer interface {
Compare(other any) int
}
// Eq returns whether v1 is equal to v2.
// It will use the Eqer interface if implemented, which
// defines equals when two value are interchangeable
// in the Hugo templates.
func Eq(v1, v2 any) bool {
if v1 == nil || v2 == nil {
return v1 == v2
}
if eqer, ok := v1.(Eqer); ok {
return eqer.Eq(v2)
}
return v1 == v2
}
+949
View File
@@ -0,0 +1,949 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package allconfig contains the full configuration for Hugo.
// <docsmeta>{ "name": "Configuration", "description": "This section holds all configuration options in Hugo." }</docsmeta>
package allconfig
import (
"errors"
"fmt"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/gohugoio/hugo/cache/filecache"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/urls"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/privacy"
"github.com/gohugoio/hugo/config/security"
"github.com/gohugoio/hugo/config/services"
"github.com/gohugoio/hugo/deploy"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/markup/markup_config"
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/minifiers"
"github.com/gohugoio/hugo/modules"
"github.com/gohugoio/hugo/navigation"
"github.com/gohugoio/hugo/output"
"github.com/gohugoio/hugo/related"
"github.com/gohugoio/hugo/resources/images"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/resources/page/pagemeta"
"github.com/spf13/afero"
xmaps "golang.org/x/exp/maps"
)
// InternalConfig is the internal configuration for Hugo, not read from any user provided config file.
type InternalConfig struct {
// Server mode?
Running bool
Quiet bool
Verbose bool
Clock string
Watch bool
DisableLiveReload bool
LiveReloadPort int
}
// All non-params config keys for language.
var configLanguageKeys map[string]bool
func init() {
skip := map[string]bool{
"internal": true,
"c": true,
"rootconfig": true,
}
configLanguageKeys = make(map[string]bool)
addKeys := func(v reflect.Value) {
for i := 0; i < v.NumField(); i++ {
name := strings.ToLower(v.Type().Field(i).Name)
if skip[name] {
continue
}
configLanguageKeys[name] = true
}
}
addKeys(reflect.ValueOf(Config{}))
addKeys(reflect.ValueOf(RootConfig{}))
addKeys(reflect.ValueOf(config.CommonDirs{}))
addKeys(reflect.ValueOf(langs.LanguageConfig{}))
}
type Config struct {
// For internal use only.
Internal InternalConfig `mapstructure:"-" json:"-"`
// For internal use only.
C *ConfigCompiled `mapstructure:"-" json:"-"`
RootConfig
// Author information.
Author map[string]any
// Social links.
Social map[string]string
// The build configuration section contains build-related configuration options.
// <docsmeta>{"identifiers": ["build"] }</docsmeta>
Build config.BuildConfig `mapstructure:"-"`
// The caches configuration section contains cache-related configuration options.
// <docsmeta>{"identifiers": ["caches"] }</docsmeta>
Caches filecache.Configs `mapstructure:"-"`
// The markup configuration section contains markup-related configuration options.
// <docsmeta>{"identifiers": ["markup"] }</docsmeta>
Markup markup_config.Config `mapstructure:"-"`
// The mediatypes configuration section maps the MIME type (a string) to a configuration object for that type.
// <docsmeta>{"identifiers": ["mediatypes"], "refs": ["types:media:type"] }</docsmeta>
MediaTypes *config.ConfigNamespace[map[string]media.MediaTypeConfig, media.Types] `mapstructure:"-"`
Imaging *config.ConfigNamespace[images.ImagingConfig, images.ImagingConfigInternal] `mapstructure:"-"`
// The outputformats configuration sections maps a format name (a string) to a configuration object for that format.
OutputFormats *config.ConfigNamespace[map[string]output.OutputFormatConfig, output.Formats] `mapstructure:"-"`
// The outputs configuration section maps a Page Kind (a string) to a slice of output formats.
// This can be overridden in the front matter.
Outputs map[string][]string `mapstructure:"-"`
// The cascade configuration section contains the top level front matter cascade configuration options,
// a slice of page matcher and params to apply to those pages.
Cascade *config.ConfigNamespace[[]page.PageMatcherParamsConfig, map[page.PageMatcher]maps.Params] `mapstructure:"-"`
// Menu configuration.
// <docsmeta>{"refs": ["config:languages:menus"] }</docsmeta>
Menus *config.ConfigNamespace[map[string]navigation.MenuConfig, navigation.Menus] `mapstructure:"-"`
// The deployment configuration section contains for hugo deploy.
Deployment deploy.DeployConfig `mapstructure:"-"`
// Module configuration.
Module modules.Config `mapstructure:"-"`
// Front matter configuration.
Frontmatter pagemeta.FrontmatterConfig `mapstructure:"-"`
// Minification configuration.
Minify minifiers.MinifyConfig `mapstructure:"-"`
// Permalink configuration.
Permalinks map[string]map[string]string `mapstructure:"-"`
// Taxonomy configuration.
Taxonomies map[string]string `mapstructure:"-"`
// Sitemap configuration.
Sitemap config.SitemapConfig `mapstructure:"-"`
// Related content configuration.
Related related.Config `mapstructure:"-"`
// Server configuration.
Server config.Server `mapstructure:"-"`
// Privacy configuration.
Privacy privacy.Config `mapstructure:"-"`
// Security configuration.
Security security.Config `mapstructure:"-"`
// Services configuration.
Services services.Config `mapstructure:"-"`
// User provided parameters.
// <docsmeta>{"refs": ["config:languages:params"] }</docsmeta>
Params maps.Params `mapstructure:"-"`
// The languages configuration sections maps a language code (a string) to a configuration object for that language.
Languages map[string]langs.LanguageConfig `mapstructure:"-"`
// UglyURLs configuration. Either a boolean or a sections map.
UglyURLs any `mapstructure:"-"`
}
type configCompiler interface {
CompileConfig(logger loggers.Logger) error
}
func (c Config) cloneForLang() *Config {
x := c
x.C = nil
copyStringSlice := func(in []string) []string {
if in == nil {
return nil
}
out := make([]string, len(in))
copy(out, in)
return out
}
// Copy all the slices to avoid sharing.
x.DisableKinds = copyStringSlice(x.DisableKinds)
x.DisableLanguages = copyStringSlice(x.DisableLanguages)
x.MainSections = copyStringSlice(x.MainSections)
x.IgnoreErrors = copyStringSlice(x.IgnoreErrors)
x.IgnoreFiles = copyStringSlice(x.IgnoreFiles)
x.Theme = copyStringSlice(x.Theme)
// Collapse all static dirs to one.
x.StaticDir = x.staticDirs()
// These will go away soon ...
x.StaticDir0 = nil
x.StaticDir1 = nil
x.StaticDir2 = nil
x.StaticDir3 = nil
x.StaticDir4 = nil
x.StaticDir5 = nil
x.StaticDir6 = nil
x.StaticDir7 = nil
x.StaticDir8 = nil
x.StaticDir9 = nil
x.StaticDir10 = nil
return &x
}
func (c *Config) CompileConfig(logger loggers.Logger) error {
var transientErr error
s := c.Timeout
if _, err := strconv.Atoi(s); err == nil {
// A number, assume seconds.
s = s + "s"
}
timeout, err := time.ParseDuration(s)
if err != nil {
return fmt.Errorf("failed to parse timeout: %s", err)
}
disabledKinds := make(map[string]bool)
for _, kind := range c.DisableKinds {
kind = strings.ToLower(kind)
if kind == "taxonomyterm" {
// Legacy config.
kind = "term"
}
disabledKinds[kind] = true
}
kindOutputFormats := make(map[string]output.Formats)
isRssDisabled := disabledKinds["rss"]
outputFormats := c.OutputFormats.Config
for kind, formats := range c.Outputs {
if disabledKinds[kind] {
continue
}
for _, format := range formats {
if isRssDisabled && format == "rss" {
// Legacy config.
continue
}
f, found := outputFormats.GetByName(format)
if !found {
transientErr = fmt.Errorf("unknown output format %q for kind %q", format, kind)
continue
}
kindOutputFormats[kind] = append(kindOutputFormats[kind], f)
}
}
disabledLangs := make(map[string]bool)
for _, lang := range c.DisableLanguages {
if lang == c.DefaultContentLanguage {
return fmt.Errorf("cannot disable default content language %q", lang)
}
disabledLangs[lang] = true
}
for lang, language := range c.Languages {
if language.Disabled {
disabledLangs[lang] = true
if lang == c.DefaultContentLanguage {
return fmt.Errorf("cannot disable default content language %q", lang)
}
}
}
ignoredErrors := make(map[string]bool)
for _, err := range c.IgnoreErrors {
ignoredErrors[strings.ToLower(err)] = true
}
baseURL, err := urls.NewBaseURLFromString(c.BaseURL)
if err != nil {
return err
}
isUglyURL := func(section string) bool {
switch v := c.UglyURLs.(type) {
case bool:
return v
case map[string]bool:
return v[section]
default:
return false
}
}
ignoreFile := func(s string) bool {
return false
}
if len(c.IgnoreFiles) > 0 {
regexps := make([]*regexp.Regexp, len(c.IgnoreFiles))
for i, pattern := range c.IgnoreFiles {
var err error
regexps[i], err = regexp.Compile(pattern)
if err != nil {
return fmt.Errorf("failed to compile ignoreFiles pattern %q: %s", pattern, err)
}
}
ignoreFile = func(s string) bool {
for _, r := range regexps {
if r.MatchString(s) {
return true
}
}
return false
}
}
var clock time.Time
if c.Internal.Clock != "" {
var err error
clock, err = time.Parse(time.RFC3339, c.Internal.Clock)
if err != nil {
return fmt.Errorf("failed to parse clock: %s", err)
}
}
c.C = &ConfigCompiled{
Timeout: timeout,
BaseURL: baseURL,
BaseURLLiveReload: baseURL,
DisabledKinds: disabledKinds,
DisabledLanguages: disabledLangs,
IgnoredErrors: ignoredErrors,
KindOutputFormats: kindOutputFormats,
CreateTitle: helpers.GetTitleFunc(c.TitleCaseStyle),
IsUglyURLSection: isUglyURL,
IgnoreFile: ignoreFile,
MainSections: c.MainSections,
Clock: clock,
transientErr: transientErr,
}
for _, s := range allDecoderSetups {
if getCompiler := s.getCompiler; getCompiler != nil {
if err := getCompiler(c).CompileConfig(logger); err != nil {
return err
}
}
}
return nil
}
func (c *Config) IsKindEnabled(kind string) bool {
return !c.C.DisabledKinds[kind]
}
func (c *Config) IsLangDisabled(lang string) bool {
return c.C.DisabledLanguages[lang]
}
// ConfigCompiled holds values and functions that are derived from the config.
type ConfigCompiled struct {
Timeout time.Duration
BaseURL urls.BaseURL
BaseURLLiveReload urls.BaseURL
KindOutputFormats map[string]output.Formats
DisabledKinds map[string]bool
DisabledLanguages map[string]bool
IgnoredErrors map[string]bool
CreateTitle func(s string) string
IsUglyURLSection func(section string) bool
IgnoreFile func(filename string) bool
MainSections []string
Clock time.Time
// This is set to the last transient error found during config compilation.
// With themes/modules we compute the configuration in multiple passes, and
// errors with missing output format definitions may resolve itself.
transientErr error
mu sync.Mutex
}
// This may be set after the config is compiled.
func (c *ConfigCompiled) SetMainSectionsIfNotSet(sections []string) {
c.mu.Lock()
defer c.mu.Unlock()
if c.MainSections != nil {
return
}
c.MainSections = sections
}
// This is set after the config is compiled by the server command.
func (c *ConfigCompiled) SetBaseURL(baseURL, baseURLLiveReload urls.BaseURL) {
c.BaseURL = baseURL
c.BaseURLLiveReload = baseURLLiveReload
}
// RootConfig holds all the top-level configuration options in Hugo
type RootConfig struct {
// The base URL of the site.
// Note that the default value is empty, but Hugo requires a valid URL (e.g. "https://example.com/") to work properly.
// <docsmeta>{"identifiers": ["URL"] }</docsmeta>
BaseURL string
// Whether to build content marked as draft.X
// <docsmeta>{"identifiers": ["draft"] }</docsmeta>
BuildDrafts bool
// Whether to build content with expiryDate in the past.
// <docsmeta>{"identifiers": ["expiryDate"] }</docsmeta>
BuildExpired bool
// Whether to build content with publishDate in the future.
// <docsmeta>{"identifiers": ["publishDate"] }</docsmeta>
BuildFuture bool
// Copyright information.
Copyright string
// The language to apply to content without any language indicator.
DefaultContentLanguage string
// By default, we put the default content language in the root and the others below their language ID, e.g. /no/.
// Set this to true to put all languages below their language ID.
DefaultContentLanguageInSubdir bool
// Disable creation of alias redirect pages.
DisableAliases bool
// Disable lower casing of path segments.
DisablePathToLower bool
// Disable page kinds from build.
DisableKinds []string
// A list of languages to disable.
DisableLanguages []string
// Disable the injection of the Hugo generator tag on the home page.
DisableHugoGeneratorInject bool
// Enable replacement in Pages' Content of Emoji shortcodes with their equivalent Unicode characters.
// <docsmeta>{"identifiers": ["Content", "Unicode"] }</docsmeta>
EnableEmoji bool
// THe main section(s) of the site.
// If not set, Hugo will try to guess this from the content.
MainSections []string
// Enable robots.txt generation.
EnableRobotsTXT bool
// When enabled, Hugo will apply Git version information to each Page if possible, which
// can be used to keep lastUpdated in synch and to print version information.
// <docsmeta>{"identifiers": ["Page"] }</docsmeta>
EnableGitInfo bool
// Enable to track, calculate and print metrics.
TemplateMetrics bool
// Enable to track, print and calculate metric hints.
TemplateMetricsHints bool
// Enable to disable the build lock file.
NoBuildLock bool
// A list of error IDs to ignore.
IgnoreErrors []string
// A list of regexps that match paths to ignore.
// Deprecated: Use the settings on module imports.
IgnoreFiles []string
// Ignore cache.
IgnoreCache bool
// Enable to print greppable placeholders (on the form "[i18n] TRANSLATIONID") for missing translation strings.
EnableMissingTranslationPlaceholders bool
// Enable to panic on warning log entries. This may make it easier to detect the source.
PanicOnWarning bool
// The configured environment. Default is "development" for server and "production" for build.
Environment string
// The default language code.
LanguageCode string
// Enable if the site content has CJK language (Chinese, Japanese, or Korean). This affects how Hugo counts words.
HasCJKLanguage bool
// The default number of pages per page when paginating.
Paginate int
// The path to use when creating pagination URLs, e.g. "page" in /page/2/.
PaginatePath string
// Whether to pluralize default list titles.
// Note that this currently only works for English, but you can provide your own title in the content file's front matter.
PluralizeListTitles bool
// Make all relative URLs absolute using the baseURL.
// <docsmeta>{"identifiers": ["baseURL"] }</docsmeta>
CanonifyURLs bool
// Enable this to make all relative URLs relative to content root. Note that this does not affect absolute URLs.
RelativeURLs bool
// Removes non-spacing marks from composite characters in content paths.
RemovePathAccents bool
// Whether to track and print unused templates during the build.
PrintUnusedTemplates bool
// Enable to print warnings for missing translation strings.
PrintI18nWarnings bool
// ENable to print warnings for multiple files published to the same destination.
PrintPathWarnings bool
// URL to be used as a placeholder when a page reference cannot be found in ref or relref. Is used as-is.
RefLinksNotFoundURL string
// When using ref or relref to resolve page links and a link cannot be resolved, it will be logged with this log level.
// Valid values are ERROR (default) or WARNING. Any ERROR will fail the build (exit -1).
RefLinksErrorLevel string
// This will create a menu with all the sections as menu items and all the sections pages as “shadow-members”.
SectionPagesMenu string
// The length of text in words to show in a .Summary.
SummaryLength int
// The site title.
Title string
// The theme(s) to use.
// See Modules for more a more flexible way to load themes.
Theme []string
// Timeout for generating page contents, specified as a duration or in seconds.
Timeout string
// The time zone (or location), e.g. Europe/Oslo, used to parse front matter dates without such information and in the time function.
TimeZone string
// Set titleCaseStyle to specify the title style used by the title template function and the automatic section titles in Hugo.
// It defaults to AP Stylebook for title casing, but you can also set it to Chicago or Go (every word starts with a capital letter).
TitleCaseStyle string
// The editor used for opening up new content.
NewContentEditor string
// Don't sync modification time of files for the static mounts.
NoTimes bool
// Don't sync modification time of files for the static mounts.
NoChmod bool
// Clean the destination folder before a new build.
// This currently only handles static files.
CleanDestinationDir bool
// A Glob pattern of module paths to ignore in the _vendor folder.
IgnoreVendorPaths string
config.CommonDirs `mapstructure:",squash"`
// The odd constructs below are kept for backwards compatibility.
// Deprecated: Use module mount config instead.
StaticDir []string
// Deprecated: Use module mount config instead.
StaticDir0 []string
// Deprecated: Use module mount config instead.
StaticDir1 []string
// Deprecated: Use module mount config instead.
StaticDir2 []string
// Deprecated: Use module mount config instead.
StaticDir3 []string
// Deprecated: Use module mount config instead.
StaticDir4 []string
// Deprecated: Use module mount config instead.
StaticDir5 []string
// Deprecated: Use module mount config instead.
StaticDir6 []string
// Deprecated: Use module mount config instead.
StaticDir7 []string
// Deprecated: Use module mount config instead.
StaticDir8 []string
// Deprecated: Use module mount config instead.
StaticDir9 []string
// Deprecated: Use module mount config instead.
StaticDir10 []string
}
func (c RootConfig) staticDirs() []string {
var dirs []string
dirs = append(dirs, c.StaticDir...)
dirs = append(dirs, c.StaticDir0...)
dirs = append(dirs, c.StaticDir1...)
dirs = append(dirs, c.StaticDir2...)
dirs = append(dirs, c.StaticDir3...)
dirs = append(dirs, c.StaticDir4...)
dirs = append(dirs, c.StaticDir5...)
dirs = append(dirs, c.StaticDir6...)
dirs = append(dirs, c.StaticDir7...)
dirs = append(dirs, c.StaticDir8...)
dirs = append(dirs, c.StaticDir9...)
dirs = append(dirs, c.StaticDir10...)
return helpers.UniqueStringsReuse(dirs)
}
type Configs struct {
Base *Config
LoadingInfo config.LoadConfigResult
LanguageConfigMap map[string]*Config
LanguageConfigSlice []*Config
IsMultihost bool
Languages langs.Languages
LanguagesDefaultFirst langs.Languages
Modules modules.Modules
ModulesClient *modules.Client
configLangs []config.AllProvider
}
// transientErr returns the last transient error found during config compilation.
func (c *Configs) transientErr() error {
for _, l := range c.LanguageConfigSlice {
if l.C.transientErr != nil {
return l.C.transientErr
}
}
return nil
}
func (c *Configs) IsZero() bool {
// A config always has at least one language.
return c == nil || len(c.Languages) == 0
}
func (c *Configs) Init() error {
c.configLangs = make([]config.AllProvider, len(c.Languages))
for i, l := range c.LanguagesDefaultFirst {
c.configLangs[i] = ConfigLanguage{
m: c,
config: c.LanguageConfigMap[l.Lang],
baseConfig: c.LoadingInfo.BaseConfig,
language: l,
}
}
if len(c.Modules) == 0 {
return errors.New("no modules loaded (ned at least the main module)")
}
// Apply default project mounts.
if err := modules.ApplyProjectConfigDefaults(c.Modules[0], c.configLangs...); err != nil {
return err
}
// We should consolidate this, but to get a full view of the mounts in e.g. "hugo config" we need to
// transfer any default mounts added above to the config used to print the config.
for _, m := range c.Modules[0].Mounts() {
var found bool
for _, cm := range c.Base.Module.Mounts {
if cm.Source == m.Source && cm.Target == m.Target && cm.Lang == m.Lang {
found = true
break
}
}
if !found {
c.Base.Module.Mounts = append(c.Base.Module.Mounts, m)
}
}
// Transfer the changed mounts to the language versions (all share the same mount set, but can be displayed in different languages).
for _, l := range c.LanguageConfigSlice {
l.Module.Mounts = c.Base.Module.Mounts
}
return nil
}
func (c Configs) ConfigLangs() []config.AllProvider {
return c.configLangs
}
func (c Configs) GetFirstLanguageConfig() config.AllProvider {
return c.configLangs[0]
}
func (c Configs) GetByLang(lang string) config.AllProvider {
for _, l := range c.configLangs {
if l.Language().Lang == lang {
return l
}
}
return nil
}
// fromLoadConfigResult creates a new Config from res.
func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadConfigResult) (*Configs, error) {
if !res.Cfg.IsSet("languages") {
// We need at least one
lang := res.Cfg.GetString("defaultContentLanguage")
res.Cfg.Set("languages", maps.Params{lang: maps.Params{}})
}
bcfg := res.BaseConfig
cfg := res.Cfg
all := &Config{}
err := decodeConfigFromParams(fs, bcfg, cfg, all, nil)
if err != nil {
return nil, err
}
langConfigMap := make(map[string]*Config)
var langConfigs []*Config
languagesConfig := cfg.GetStringMap("languages")
var isMultiHost bool
if err := all.CompileConfig(logger); err != nil {
return nil, err
}
for k, v := range languagesConfig {
mergedConfig := config.New()
var differentRootKeys []string
switch x := v.(type) {
case maps.Params:
var params maps.Params
pv, found := x["params"]
if found {
params = pv.(maps.Params)
} else {
params = maps.Params{
maps.MergeStrategyKey: maps.ParamsMergeStrategyDeep,
}
x["params"] = params
}
for kk, vv := range x {
if kk == "_merge" {
continue
}
if kk != maps.MergeStrategyKey && !configLanguageKeys[kk] {
// This should have been placed below params.
// We accidentally allowed it in the past, so we need to support it a little longer,
// But log a warning.
if _, found := params[kk]; !found {
helpers.Deprecated(fmt.Sprintf("config: languages.%s.%s: custom params on the language top level", k, kk), fmt.Sprintf("Put the value below [languages.%s.params]. See https://gohugo.io/content-management/multilingual/#changes-in-hugo-01120", k), false)
params[kk] = vv
}
}
if kk == "baseurl" {
// baseURL configure don the language level is a multihost setup.
isMultiHost = true
}
mergedConfig.Set(kk, vv)
rootv := cfg.Get(kk)
if rootv != nil && cfg.IsSet(kk) {
// This overrides a root key and potentially needs a merge.
if !reflect.DeepEqual(rootv, vv) {
switch vvv := vv.(type) {
case maps.Params:
differentRootKeys = append(differentRootKeys, kk)
// Use the language value as base.
mergedConfigEntry := xmaps.Clone(vvv)
// Merge in the root value.
maps.MergeParams(mergedConfigEntry, rootv.(maps.Params))
mergedConfig.Set(kk, mergedConfigEntry)
default:
// Apply new values to the root.
differentRootKeys = append(differentRootKeys, "")
}
}
} else {
switch vv.(type) {
case maps.Params:
differentRootKeys = append(differentRootKeys, kk)
default:
// Apply new values to the root.
differentRootKeys = append(differentRootKeys, "")
}
}
}
differentRootKeys = helpers.UniqueStringsSorted(differentRootKeys)
if len(differentRootKeys) == 0 {
langConfigMap[k] = all
continue
}
// Create a copy of the complete config and replace the root keys with the language specific ones.
clone := all.cloneForLang()
if err := decodeConfigFromParams(fs, bcfg, mergedConfig, clone, differentRootKeys); err != nil {
return nil, fmt.Errorf("failed to decode config for language %q: %w", k, err)
}
if err := clone.CompileConfig(logger); err != nil {
return nil, err
}
langConfigMap[k] = clone
case maps.ParamsMergeStrategy:
default:
panic(fmt.Sprintf("unknown type in languages config: %T", v))
}
}
var languages langs.Languages
defaultContentLanguage := all.DefaultContentLanguage
for k, v := range langConfigMap {
languageConf := v.Languages[k]
language, err := langs.NewLanguage(k, defaultContentLanguage, v.TimeZone, languageConf)
if err != nil {
return nil, err
}
languages = append(languages, language)
}
// Sort the sites by language weight (if set) or lang.
sort.Slice(languages, func(i, j int) bool {
li := languages[i]
lj := languages[j]
if li.Weight != lj.Weight {
return li.Weight < lj.Weight
}
return li.Lang < lj.Lang
})
for _, l := range languages {
langConfigs = append(langConfigs, langConfigMap[l.Lang])
}
var languagesDefaultFirst langs.Languages
for _, l := range languages {
if l.Lang == defaultContentLanguage {
languagesDefaultFirst = append(languagesDefaultFirst, l)
}
}
for _, l := range languages {
if l.Lang != defaultContentLanguage {
languagesDefaultFirst = append(languagesDefaultFirst, l)
}
}
bcfg.PublishDir = all.PublishDir
res.BaseConfig = bcfg
cm := &Configs{
Base: all,
LanguageConfigMap: langConfigMap,
LanguageConfigSlice: langConfigs,
LoadingInfo: res,
IsMultihost: isMultiHost,
Languages: languages,
LanguagesDefaultFirst: languagesDefaultFirst,
}
return cm, nil
}
func decodeConfigFromParams(fs afero.Fs, bcfg config.BaseConfig, p config.Provider, target *Config, keys []string) error {
var decoderSetups []decodeWeight
if len(keys) == 0 {
for _, v := range allDecoderSetups {
decoderSetups = append(decoderSetups, v)
}
} else {
for _, key := range keys {
if v, found := allDecoderSetups[key]; found {
decoderSetups = append(decoderSetups, v)
} else {
return fmt.Errorf("unknown config key %q", key)
}
}
}
// Sort them to get the dependency order right.
sort.Slice(decoderSetups, func(i, j int) bool {
ki, kj := decoderSetups[i], decoderSetups[j]
if ki.weight == kj.weight {
return ki.key < kj.key
}
return ki.weight < kj.weight
})
for _, v := range decoderSetups {
p := decodeConfig{p: p, c: target, fs: fs, bcfg: bcfg}
if err := v.decode(v, p); err != nil {
return fmt.Errorf("failed to decode %q: %w", v.key, err)
}
}
return nil
}
func createDefaultOutputFormats(allFormats output.Formats) map[string][]string {
if len(allFormats) == 0 {
panic("no output formats")
}
rssOut, rssFound := allFormats.GetByName(output.RSSFormat.Name)
htmlOut, _ := allFormats.GetByName(output.HTMLFormat.Name)
defaultListTypes := []string{htmlOut.Name}
if rssFound {
defaultListTypes = append(defaultListTypes, rssOut.Name)
}
m := map[string][]string{
page.KindPage: {htmlOut.Name},
page.KindHome: defaultListTypes,
page.KindSection: defaultListTypes,
page.KindTerm: defaultListTypes,
page.KindTaxonomy: defaultListTypes,
}
// May be disabled
if rssFound {
m["rss"] = []string{rssOut.Name}
}
return m
}
+388
View File
@@ -0,0 +1,388 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package allconfig
import (
"fmt"
"strings"
"github.com/gohugoio/hugo/cache/filecache"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/privacy"
"github.com/gohugoio/hugo/config/security"
"github.com/gohugoio/hugo/config/services"
"github.com/gohugoio/hugo/deploy"
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/markup/markup_config"
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/minifiers"
"github.com/gohugoio/hugo/modules"
"github.com/gohugoio/hugo/navigation"
"github.com/gohugoio/hugo/output"
"github.com/gohugoio/hugo/related"
"github.com/gohugoio/hugo/resources/images"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/resources/page/pagemeta"
"github.com/mitchellh/mapstructure"
"github.com/spf13/afero"
"github.com/spf13/cast"
)
type decodeConfig struct {
p config.Provider
c *Config
fs afero.Fs
bcfg config.BaseConfig
}
type decodeWeight struct {
key string
decode func(decodeWeight, decodeConfig) error
getCompiler func(c *Config) configCompiler
weight int
}
var allDecoderSetups = map[string]decodeWeight{
"": {
key: "",
weight: -100, // Always first.
decode: func(d decodeWeight, p decodeConfig) error {
if err := mapstructure.WeakDecode(p.p.Get(""), &p.c.RootConfig); err != nil {
return err
}
// This need to match with Lang which is always lower case.
p.c.RootConfig.DefaultContentLanguage = strings.ToLower(p.c.RootConfig.DefaultContentLanguage)
return nil
},
},
"imaging": {
key: "imaging",
decode: func(d decodeWeight, p decodeConfig) error {
var err error
p.c.Imaging, err = images.DecodeConfig(p.p.GetStringMap(d.key))
return err
},
},
"caches": {
key: "caches",
decode: func(d decodeWeight, p decodeConfig) error {
var err error
p.c.Caches, err = filecache.DecodeConfig(p.fs, p.bcfg, p.p.GetStringMap(d.key))
if p.c.IgnoreCache {
// Set MaxAge in all caches to 0.
for k, cache := range p.c.Caches {
cache.MaxAge = 0
p.c.Caches[k] = cache
}
}
return err
},
},
"build": {
key: "build",
decode: func(d decodeWeight, p decodeConfig) error {
p.c.Build = config.DecodeBuildConfig(p.p)
return nil
},
getCompiler: func(c *Config) configCompiler {
return &c.Build
},
},
"frontmatter": {
key: "frontmatter",
decode: func(d decodeWeight, p decodeConfig) error {
var err error
p.c.Frontmatter, err = pagemeta.DecodeFrontMatterConfig(p.p)
return err
},
},
"markup": {
key: "markup",
decode: func(d decodeWeight, p decodeConfig) error {
var err error
p.c.Markup, err = markup_config.Decode(p.p)
return err
},
},
"server": {
key: "server",
decode: func(d decodeWeight, p decodeConfig) error {
var err error
p.c.Server, err = config.DecodeServer(p.p)
return err
},
getCompiler: func(c *Config) configCompiler {
return &c.Server
},
},
"minify": {
key: "minify",
decode: func(d decodeWeight, p decodeConfig) error {
var err error
p.c.Minify, err = minifiers.DecodeConfig(p.p.Get(d.key))
return err
},
},
"mediatypes": {
key: "mediatypes",
decode: func(d decodeWeight, p decodeConfig) error {
var err error
p.c.MediaTypes, err = media.DecodeTypes(p.p.GetStringMap(d.key))
return err
},
},
"outputs": {
key: "outputs",
decode: func(d decodeWeight, p decodeConfig) error {
defaults := createDefaultOutputFormats(p.c.OutputFormats.Config)
m := maps.CleanConfigStringMap(p.p.GetStringMap("outputs"))
p.c.Outputs = make(map[string][]string)
for k, v := range m {
s := types.ToStringSlicePreserveString(v)
for i, v := range s {
s[i] = strings.ToLower(v)
}
p.c.Outputs[k] = s
}
// Apply defaults.
for k, v := range defaults {
if _, found := p.c.Outputs[k]; !found {
p.c.Outputs[k] = v
}
}
return nil
},
},
"outputformats": {
key: "outputformats",
decode: func(d decodeWeight, p decodeConfig) error {
var err error
p.c.OutputFormats, err = output.DecodeConfig(p.c.MediaTypes.Config, p.p.Get(d.key))
return err
},
},
"params": {
key: "params",
decode: func(d decodeWeight, p decodeConfig) error {
p.c.Params = maps.CleanConfigStringMap(p.p.GetStringMap("params"))
if p.c.Params == nil {
p.c.Params = make(map[string]any)
}
// Before Hugo 0.112.0 this was configured via site Params.
if mainSections, found := p.c.Params["mainsections"]; found {
p.c.MainSections = types.ToStringSlicePreserveString(mainSections)
if p.c.MainSections == nil {
p.c.MainSections = []string{}
}
}
return nil
},
},
"module": {
key: "module",
decode: func(d decodeWeight, p decodeConfig) error {
var err error
p.c.Module, err = modules.DecodeConfig(p.p)
return err
},
},
"permalinks": {
key: "permalinks",
decode: func(d decodeWeight, p decodeConfig) error {
var err error
p.c.Permalinks, err = page.DecodePermalinksConfig(p.p.GetStringMap(d.key))
return err
},
},
"sitemap": {
key: "sitemap",
decode: func(d decodeWeight, p decodeConfig) error {
var err error
p.c.Sitemap, err = config.DecodeSitemap(config.SitemapConfig{Priority: -1, Filename: "sitemap.xml"}, p.p.GetStringMap(d.key))
return err
},
},
"taxonomies": {
key: "taxonomies",
decode: func(d decodeWeight, p decodeConfig) error {
p.c.Taxonomies = maps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
return nil
},
},
"related": {
key: "related",
weight: 100, // This needs to be decoded after taxonomies.
decode: func(d decodeWeight, p decodeConfig) error {
if p.p.IsSet(d.key) {
var err error
p.c.Related, err = related.DecodeConfig(p.p.GetParams(d.key))
if err != nil {
return fmt.Errorf("failed to decode related config: %w", err)
}
} else {
p.c.Related = related.DefaultConfig
if _, found := p.c.Taxonomies["tag"]; found {
p.c.Related.Add(related.IndexConfig{Name: "tags", Weight: 80})
}
}
return nil
},
},
"languages": {
key: "languages",
decode: func(d decodeWeight, p decodeConfig) error {
var err error
m := p.p.GetStringMap(d.key)
if len(m) == 1 {
// In v0.112.4 we moved this to the language config, but it's very commmon for mono language sites to have this at the top level.
var first maps.Params
var ok bool
for _, v := range m {
first, ok = v.(maps.Params)
if ok {
break
}
}
if first != nil {
if _, found := first["languagecode"]; !found {
first["languagecode"] = p.p.GetString("languagecode")
}
}
}
p.c.Languages, err = langs.DecodeConfig(m)
if err != nil {
return err
}
// Validate defaultContentLanguage.
var found bool
for lang := range p.c.Languages {
if lang == p.c.DefaultContentLanguage {
found = true
break
}
}
if !found {
return fmt.Errorf("config value %q for defaultContentLanguage does not match any language definition", p.c.DefaultContentLanguage)
}
return nil
},
},
"cascade": {
key: "cascade",
decode: func(d decodeWeight, p decodeConfig) error {
var err error
p.c.Cascade, err = page.DecodeCascadeConfig(p.p.Get(d.key))
return err
},
},
"menus": {
key: "menus",
decode: func(d decodeWeight, p decodeConfig) error {
var err error
p.c.Menus, err = navigation.DecodeConfig(p.p.Get(d.key))
return err
},
},
"privacy": {
key: "privacy",
decode: func(d decodeWeight, p decodeConfig) error {
var err error
p.c.Privacy, err = privacy.DecodeConfig(p.p)
return err
},
},
"security": {
key: "security",
decode: func(d decodeWeight, p decodeConfig) error {
var err error
p.c.Security, err = security.DecodeConfig(p.p)
return err
},
},
"services": {
key: "services",
decode: func(d decodeWeight, p decodeConfig) error {
var err error
p.c.Services, err = services.DecodeConfig(p.p)
return err
},
},
"deployment": {
key: "deployment",
decode: func(d decodeWeight, p decodeConfig) error {
var err error
p.c.Deployment, err = deploy.DecodeConfig(p.p)
return err
},
},
"author": {
key: "author",
decode: func(d decodeWeight, p decodeConfig) error {
p.c.Author = maps.CleanConfigStringMap(p.p.GetStringMap(d.key))
return nil
},
},
"social": {
key: "social",
decode: func(d decodeWeight, p decodeConfig) error {
p.c.Social = maps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
return nil
},
},
"uglyurls": {
key: "uglyurls",
decode: func(d decodeWeight, p decodeConfig) error {
v := p.p.Get(d.key)
switch vv := v.(type) {
case bool:
p.c.UglyURLs = vv
case string:
p.c.UglyURLs = vv == "true"
default:
p.c.UglyURLs = cast.ToStringMapBool(v)
}
return nil
},
},
"internal": {
key: "internal",
decode: func(d decodeWeight, p decodeConfig) error {
return mapstructure.WeakDecode(p.p.GetStringMap(d.key), &p.c.Internal)
},
},
}
func init() {
for k, v := range allDecoderSetups {
// Verify that k and v.key is all lower case.
if k != strings.ToLower(k) {
panic(fmt.Sprintf("key %q is not lower case", k))
}
if v.key != strings.ToLower(v.key) {
panic(fmt.Sprintf("key %q is not lower case", v.key))
}
if k != v.key {
panic(fmt.Sprintf("key %q is not the same as the map key %q", k, v.key))
}
}
}
+230
View File
@@ -0,0 +1,230 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package allconfig
import (
"time"
"github.com/gohugoio/hugo/common/urls"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/langs"
)
type ConfigLanguage struct {
config *Config
baseConfig config.BaseConfig
m *Configs
language *langs.Language
}
func (c ConfigLanguage) Language() *langs.Language {
return c.language
}
func (c ConfigLanguage) Languages() langs.Languages {
return c.m.Languages
}
func (c ConfigLanguage) LanguagesDefaultFirst() langs.Languages {
return c.m.LanguagesDefaultFirst
}
func (c ConfigLanguage) LanguagePrefix() string {
if c.DefaultContentLanguageInSubdir() && c.DefaultContentLanguage() == c.Language().Lang {
return c.Language().Lang
}
if !c.IsMultiLingual() || c.DefaultContentLanguage() == c.Language().Lang {
return ""
}
return c.Language().Lang
}
func (c ConfigLanguage) BaseURL() urls.BaseURL {
return c.config.C.BaseURL
}
func (c ConfigLanguage) BaseURLLiveReload() urls.BaseURL {
return c.config.C.BaseURLLiveReload
}
func (c ConfigLanguage) Environment() string {
return c.config.Environment
}
func (c ConfigLanguage) IsMultihost() bool {
return c.m.IsMultihost
}
func (c ConfigLanguage) IsMultiLingual() bool {
return len(c.m.Languages) > 1
}
func (c ConfigLanguage) TemplateMetrics() bool {
return c.config.TemplateMetrics
}
func (c ConfigLanguage) TemplateMetricsHints() bool {
return c.config.TemplateMetricsHints
}
func (c ConfigLanguage) IsLangDisabled(lang string) bool {
return c.config.C.DisabledLanguages[lang]
}
func (c ConfigLanguage) IgnoredErrors() map[string]bool {
return c.config.C.IgnoredErrors
}
func (c ConfigLanguage) NoBuildLock() bool {
return c.config.NoBuildLock
}
func (c ConfigLanguage) NewContentEditor() string {
return c.config.NewContentEditor
}
func (c ConfigLanguage) Timeout() time.Duration {
return c.config.C.Timeout
}
func (c ConfigLanguage) BaseConfig() config.BaseConfig {
return c.baseConfig
}
func (c ConfigLanguage) Dirs() config.CommonDirs {
return c.config.CommonDirs
}
func (c ConfigLanguage) DirsBase() config.CommonDirs {
return c.m.Base.CommonDirs
}
func (c ConfigLanguage) WorkingDir() string {
return c.m.Base.WorkingDir
}
func (c ConfigLanguage) Quiet() bool {
return c.m.Base.Internal.Quiet
}
// GetConfigSection is mostly used in tests. The switch statement isn't complete, but what's in use.
func (c ConfigLanguage) GetConfigSection(s string) any {
switch s {
case "security":
return c.config.Security
case "build":
return c.config.Build
case "frontmatter":
return c.config.Frontmatter
case "caches":
return c.config.Caches
case "markup":
return c.config.Markup
case "mediaTypes":
return c.config.MediaTypes.Config
case "outputFormats":
return c.config.OutputFormats.Config
case "permalinks":
return c.config.Permalinks
case "minify":
return c.config.Minify
case "activeModules":
return c.m.Modules
case "deployment":
return c.config.Deployment
default:
panic("not implemented: " + s)
}
}
func (c ConfigLanguage) GetConfig() any {
return c.config
}
func (c ConfigLanguage) CanonifyURLs() bool {
return c.config.CanonifyURLs
}
func (c ConfigLanguage) IsUglyURLs(section string) bool {
return c.config.C.IsUglyURLSection(section)
}
func (c ConfigLanguage) IgnoreFile(s string) bool {
return c.config.C.IgnoreFile(s)
}
func (c ConfigLanguage) DisablePathToLower() bool {
return c.config.DisablePathToLower
}
func (c ConfigLanguage) RemovePathAccents() bool {
return c.config.RemovePathAccents
}
func (c ConfigLanguage) DefaultContentLanguage() string {
return c.config.DefaultContentLanguage
}
func (c ConfigLanguage) DefaultContentLanguageInSubdir() bool {
return c.config.DefaultContentLanguageInSubdir
}
func (c ConfigLanguage) SummaryLength() int {
return c.config.SummaryLength
}
func (c ConfigLanguage) BuildExpired() bool {
return c.config.BuildExpired
}
func (c ConfigLanguage) BuildFuture() bool {
return c.config.BuildFuture
}
func (c ConfigLanguage) BuildDrafts() bool {
return c.config.BuildDrafts
}
func (c ConfigLanguage) Running() bool {
return c.config.Internal.Running
}
func (c ConfigLanguage) PrintUnusedTemplates() bool {
return c.config.PrintUnusedTemplates
}
func (c ConfigLanguage) EnableMissingTranslationPlaceholders() bool {
return c.config.EnableMissingTranslationPlaceholders
}
func (c ConfigLanguage) PrintI18nWarnings() bool {
return c.config.PrintI18nWarnings
}
func (c ConfigLanguage) CreateTitle(s string) string {
return c.config.C.CreateTitle(s)
}
func (c ConfigLanguage) Paginate() int {
return c.config.Paginate
}
func (c ConfigLanguage) PaginatePath() string {
return c.config.PaginatePath
}
func (c ConfigLanguage) StaticDirs() []string {
return c.config.staticDirs()
}
+89
View File
@@ -0,0 +1,89 @@
package allconfig_test
import (
"path/filepath"
"testing"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/config/allconfig"
"github.com/gohugoio/hugo/hugolib"
)
func TestDirsMount(t *testing.T) {
files := `
-- hugo.toml --
baseURL = "https://example.com"
disableKinds = ["taxonomy", "term"]
[languages]
[languages.en]
weight = 1
[languages.sv]
weight = 2
[[module.mounts]]
source = 'content/en'
target = 'content'
lang = 'en'
[[module.mounts]]
source = 'content/sv'
target = 'content'
lang = 'sv'
-- content/en/p1.md --
---
title: "p1"
---
-- content/sv/p1.md --
---
title: "p1"
---
-- layouts/_default/single.html --
Title: {{ .Title }}
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{T: t, TxtarString: files},
).Build()
//b.AssertFileContent("public/p1/index.html", "Title: p1")
sites := b.H.Sites
b.Assert(len(sites), qt.Equals, 2)
configs := b.H.Configs
mods := configs.Modules
b.Assert(len(mods), qt.Equals, 1)
mod := mods[0]
b.Assert(mod.Mounts(), qt.HasLen, 8)
enConcp := sites[0].Conf
enConf := enConcp.GetConfig().(*allconfig.Config)
b.Assert(enConcp.BaseURL().String(), qt.Equals, "https://example.com")
modConf := enConf.Module
b.Assert(modConf.Mounts, qt.HasLen, 8)
b.Assert(modConf.Mounts[0].Source, qt.Equals, filepath.FromSlash("content/en"))
b.Assert(modConf.Mounts[0].Target, qt.Equals, "content")
b.Assert(modConf.Mounts[0].Lang, qt.Equals, "en")
b.Assert(modConf.Mounts[1].Source, qt.Equals, filepath.FromSlash("content/sv"))
b.Assert(modConf.Mounts[1].Target, qt.Equals, "content")
b.Assert(modConf.Mounts[1].Lang, qt.Equals, "sv")
}
func TestConfigAliases(t *testing.T) {
files := `
-- hugo.toml --
baseURL = "https://example.com"
logI18nWarnings = true
logPathWarnings = true
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{T: t, TxtarString: files},
).Build()
conf := b.H.Configs.Base
b.Assert(conf.PrintI18nWarnings, qt.Equals, true)
b.Assert(conf.PrintPathWarnings, qt.Equals, true)
}
+571
View File
@@ -0,0 +1,571 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package allconfig contains the full configuration for Hugo.
package allconfig
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/gobwas/glob"
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/common/hexec"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
hglob "github.com/gohugoio/hugo/hugofs/glob"
"github.com/gohugoio/hugo/modules"
"github.com/gohugoio/hugo/parser/metadecoders"
"github.com/gohugoio/hugo/tpl"
"github.com/spf13/afero"
)
var ErrNoConfigFile = errors.New("Unable to locate config file or config directory. Perhaps you need to create a new site.\n Run `hugo help new` for details.\n")
func LoadConfig(d ConfigSourceDescriptor) (*Configs, error) {
if len(d.Environ) == 0 && !hugo.IsRunningAsTest() {
d.Environ = os.Environ()
}
if d.Logger == nil {
d.Logger = loggers.NewDefault()
}
l := &configLoader{ConfigSourceDescriptor: d, cfg: config.New()}
// Make sure we always do this, even in error situations,
// as we have commands (e.g. "hugo mod init") that will
// use a partial configuration to do its job.
defer l.deleteMergeStrategies()
res, _, err := l.loadConfigMain(d)
if err != nil {
return nil, fmt.Errorf("failed to load config: %w", err)
}
configs, err := fromLoadConfigResult(d.Fs, d.Logger, res)
if err != nil {
return nil, fmt.Errorf("failed to create config from result: %w", err)
}
moduleConfig, modulesClient, err := l.loadModules(configs)
if err != nil {
return nil, fmt.Errorf("failed to load modules: %w", err)
}
if len(l.ModulesConfigFiles) > 0 {
// Config merged in from modules.
// Re-read the config.
configs, err = fromLoadConfigResult(d.Fs, d.Logger, res)
if err != nil {
return nil, fmt.Errorf("failed to create config from modules config: %w", err)
}
if err := configs.transientErr(); err != nil {
return nil, fmt.Errorf("failed to create config from modules config: %w", err)
}
} else if err := configs.transientErr(); err != nil {
return nil, fmt.Errorf("failed to create config: %w", err)
}
configs.Modules = moduleConfig.ActiveModules
configs.ModulesClient = modulesClient
if err := configs.Init(); err != nil {
return nil, fmt.Errorf("failed to init config: %w", err)
}
// This is unfortunate, but these are global settings.
tpl.SetSecurityAllowActionJSTmpl(configs.Base.Security.GoTemplates.AllowActionJSTmpl)
loggers.InitGlobalLogger(configs.Base.PanicOnWarning)
return configs, nil
}
// ConfigSourceDescriptor describes where to find the config (e.g. config.toml etc.).
type ConfigSourceDescriptor struct {
Fs afero.Fs
Logger loggers.Logger
// Config received from the command line.
// These will override any config file settings.
Flags config.Provider
// Path to the config file to use, e.g. /my/project/config.toml
Filename string
// The (optional) directory for additional configuration files.
ConfigDir string
// production, development
Environment string
// Defaults to os.Environ if not set.
Environ []string
}
func (d ConfigSourceDescriptor) configFilenames() []string {
if d.Filename == "" {
return nil
}
return strings.Split(d.Filename, ",")
}
type configLoader struct {
cfg config.Provider
BaseConfig config.BaseConfig
ConfigSourceDescriptor
// collected
ModulesConfig modules.ModulesConfig
ModulesConfigFiles []string
}
// Handle some legacy values.
func (l configLoader) applyConfigAliases() error {
aliases := []types.KeyValueStr{
{Key: "indexes", Value: "taxonomies"},
{Key: "logI18nWarnings", Value: "printI18nWarnings"},
{Key: "logPathWarnings", Value: "printPathWarnings"},
}
for _, alias := range aliases {
if l.cfg.IsSet(alias.Key) {
vv := l.cfg.Get(alias.Key)
l.cfg.Set(alias.Value, vv)
}
}
return nil
}
func (l configLoader) applyDefaultConfig() error {
defaultSettings := maps.Params{
"baseURL": "",
"cleanDestinationDir": false,
"watch": false,
"contentDir": "content",
"resourceDir": "resources",
"publishDir": "public",
"publishDirOrig": "public",
"themesDir": "themes",
"assetDir": "assets",
"layoutDir": "layouts",
"i18nDir": "i18n",
"dataDir": "data",
"archetypeDir": "archetypes",
"configDir": "config",
"staticDir": "static",
"buildDrafts": false,
"buildFuture": false,
"buildExpired": false,
"params": maps.Params{},
"environment": hugo.EnvironmentProduction,
"uglyURLs": false,
"verbose": false,
"ignoreCache": false,
"canonifyURLs": false,
"relativeURLs": false,
"removePathAccents": false,
"titleCaseStyle": "AP",
"taxonomies": maps.Params{"tag": "tags", "category": "categories"},
"permalinks": maps.Params{},
"sitemap": maps.Params{"priority": -1, "filename": "sitemap.xml"},
"menus": maps.Params{},
"disableLiveReload": false,
"pluralizeListTitles": true,
"forceSyncStatic": false,
"footnoteAnchorPrefix": "",
"footnoteReturnLinkContents": "",
"newContentEditor": "",
"paginate": 10,
"paginatePath": "page",
"summaryLength": 70,
"rssLimit": -1,
"sectionPagesMenu": "",
"disablePathToLower": false,
"hasCJKLanguage": false,
"enableEmoji": false,
"defaultContentLanguage": "en",
"defaultContentLanguageInSubdir": false,
"enableMissingTranslationPlaceholders": false,
"enableGitInfo": false,
"ignoreFiles": make([]string, 0),
"disableAliases": false,
"debug": false,
"disableFastRender": false,
"timeout": "30s",
"timeZone": "",
"enableInlineShortcodes": false,
}
l.cfg.SetDefaults(defaultSettings)
return nil
}
func (l configLoader) normalizeCfg(cfg config.Provider) error {
if b, ok := cfg.Get("minifyOutput").(bool); ok && b {
cfg.Set("minify.minifyOutput", true)
} else if b, ok := cfg.Get("minify").(bool); ok && b {
cfg.Set("minify", maps.Params{"minifyOutput": true})
}
return nil
}
func (l configLoader) cleanExternalConfig(cfg config.Provider) error {
if cfg.IsSet("internal") {
cfg.Set("internal", nil)
}
return nil
}
func (l configLoader) applyFlagsOverrides(cfg config.Provider) error {
for _, k := range cfg.Keys() {
l.cfg.Set(k, cfg.Get(k))
}
return nil
}
func (l configLoader) applyOsEnvOverrides(environ []string) error {
if len(environ) == 0 {
return nil
}
const delim = "__env__delim"
// Extract all that start with the HUGO prefix.
// The delimiter is the following rune, usually "_".
const hugoEnvPrefix = "HUGO"
var hugoEnv []types.KeyValueStr
for _, v := range environ {
key, val := config.SplitEnvVar(v)
if strings.HasPrefix(key, hugoEnvPrefix) {
delimiterAndKey := strings.TrimPrefix(key, hugoEnvPrefix)
if len(delimiterAndKey) < 2 {
continue
}
// Allow delimiters to be case sensitive.
// It turns out there isn't that many allowed special
// chars in environment variables when used in Bash and similar,
// so variables on the form HUGOxPARAMSxFOO=bar is one option.
key := strings.ReplaceAll(delimiterAndKey[1:], delimiterAndKey[:1], delim)
key = strings.ToLower(key)
hugoEnv = append(hugoEnv, types.KeyValueStr{
Key: key,
Value: val,
})
}
}
for _, env := range hugoEnv {
existing, nestedKey, owner, err := maps.GetNestedParamFn(env.Key, delim, l.cfg.Get)
if err != nil {
return err
}
if existing != nil {
val, err := metadecoders.Default.UnmarshalStringTo(env.Value, existing)
if err != nil {
continue
}
if owner != nil {
owner[nestedKey] = val
} else {
l.cfg.Set(env.Key, val)
}
} else {
if nestedKey != "" {
owner[nestedKey] = env.Value
} else {
var val any = env.Value
if _, ok := allDecoderSetups[env.Key]; ok {
// A map.
val, err = metadecoders.Default.UnmarshalStringTo(env.Value, map[string]interface{}{})
}
if err == nil {
l.cfg.Set(strings.ReplaceAll(env.Key, delim, "."), val)
}
}
}
}
return nil
}
func (l *configLoader) loadConfigMain(d ConfigSourceDescriptor) (config.LoadConfigResult, modules.ModulesConfig, error) {
var res config.LoadConfigResult
if d.Flags != nil {
if err := l.normalizeCfg(d.Flags); err != nil {
return res, l.ModulesConfig, err
}
}
if d.Fs == nil {
return res, l.ModulesConfig, errors.New("no filesystem provided")
}
if d.Flags != nil {
if err := l.applyFlagsOverrides(d.Flags); err != nil {
return res, l.ModulesConfig, err
}
workingDir := filepath.Clean(l.cfg.GetString("workingDir"))
l.BaseConfig = config.BaseConfig{
WorkingDir: workingDir,
ThemesDir: paths.AbsPathify(workingDir, l.cfg.GetString("themesDir")),
}
}
names := d.configFilenames()
if names != nil {
for _, name := range names {
var filename string
filename, err := l.loadConfig(name)
if err == nil {
res.ConfigFiles = append(res.ConfigFiles, filename)
} else if err != ErrNoConfigFile {
return res, l.ModulesConfig, l.wrapFileError(err, filename)
}
}
} else {
for _, name := range config.DefaultConfigNames {
var filename string
filename, err := l.loadConfig(name)
if err == nil {
res.ConfigFiles = append(res.ConfigFiles, filename)
break
} else if err != ErrNoConfigFile {
return res, l.ModulesConfig, l.wrapFileError(err, filename)
}
}
}
if d.ConfigDir != "" {
absConfigDir := paths.AbsPathify(l.BaseConfig.WorkingDir, d.ConfigDir)
dcfg, dirnames, err := config.LoadConfigFromDir(l.Fs, absConfigDir, l.Environment)
if err == nil {
if len(dirnames) > 0 {
if err := l.normalizeCfg(dcfg); err != nil {
return res, l.ModulesConfig, err
}
if err := l.cleanExternalConfig(dcfg); err != nil {
return res, l.ModulesConfig, err
}
l.cfg.Set("", dcfg.Get(""))
res.ConfigFiles = append(res.ConfigFiles, dirnames...)
}
} else if err != ErrNoConfigFile {
if len(dirnames) > 0 {
return res, l.ModulesConfig, l.wrapFileError(err, dirnames[0])
}
return res, l.ModulesConfig, err
}
}
res.Cfg = l.cfg
if err := l.applyDefaultConfig(); err != nil {
return res, l.ModulesConfig, err
}
// Some settings are used before we're done collecting all settings,
// so apply OS environment both before and after.
if err := l.applyOsEnvOverrides(d.Environ); err != nil {
return res, l.ModulesConfig, err
}
workingDir := filepath.Clean(l.cfg.GetString("workingDir"))
l.BaseConfig = config.BaseConfig{
WorkingDir: workingDir,
CacheDir: l.cfg.GetString("cacheDir"),
ThemesDir: paths.AbsPathify(workingDir, l.cfg.GetString("themesDir")),
}
var err error
l.BaseConfig.CacheDir, err = helpers.GetCacheDir(l.Fs, l.BaseConfig.CacheDir)
if err != nil {
return res, l.ModulesConfig, err
}
res.BaseConfig = l.BaseConfig
l.cfg.SetDefaultMergeStrategy()
res.ConfigFiles = append(res.ConfigFiles, l.ModulesConfigFiles...)
if d.Flags != nil {
if err := l.applyFlagsOverrides(d.Flags); err != nil {
return res, l.ModulesConfig, err
}
}
if err := l.applyOsEnvOverrides(d.Environ); err != nil {
return res, l.ModulesConfig, err
}
if err = l.applyConfigAliases(); err != nil {
return res, l.ModulesConfig, err
}
return res, l.ModulesConfig, err
}
func (l *configLoader) loadModules(configs *Configs) (modules.ModulesConfig, *modules.Client, error) {
bcfg := configs.LoadingInfo.BaseConfig
conf := configs.Base
workingDir := bcfg.WorkingDir
themesDir := bcfg.ThemesDir
cfg := configs.LoadingInfo.Cfg
var ignoreVendor glob.Glob
if s := conf.IgnoreVendorPaths; s != "" {
ignoreVendor, _ = hglob.GetGlob(hglob.NormalizePath(s))
}
ex := hexec.New(conf.Security)
hook := func(m *modules.ModulesConfig) error {
for _, tc := range m.ActiveModules {
if len(tc.ConfigFilenames()) > 0 {
if tc.Watch() {
l.ModulesConfigFiles = append(l.ModulesConfigFiles, tc.ConfigFilenames()...)
}
// Merge in the theme config using the configured
// merge strategy.
cfg.Merge("", tc.Cfg().Get(""))
}
}
return nil
}
modulesClient := modules.NewClient(modules.ClientConfig{
Fs: l.Fs,
Logger: l.Logger,
Exec: ex,
HookBeforeFinalize: hook,
WorkingDir: workingDir,
ThemesDir: themesDir,
Environment: l.Environment,
CacheDir: conf.Caches.CacheDirModules(),
ModuleConfig: conf.Module,
IgnoreVendor: ignoreVendor,
})
moduleConfig, err := modulesClient.Collect()
// We want to watch these for changes and trigger rebuild on version
// changes etc.
if moduleConfig.GoModulesFilename != "" {
l.ModulesConfigFiles = append(l.ModulesConfigFiles, moduleConfig.GoModulesFilename)
}
if moduleConfig.GoWorkspaceFilename != "" {
l.ModulesConfigFiles = append(l.ModulesConfigFiles, moduleConfig.GoWorkspaceFilename)
}
return moduleConfig, modulesClient, err
}
func (l configLoader) loadConfig(configName string) (string, error) {
baseDir := l.BaseConfig.WorkingDir
var baseFilename string
if filepath.IsAbs(configName) {
baseFilename = configName
} else {
baseFilename = filepath.Join(baseDir, configName)
}
var filename string
if paths.ExtNoDelimiter(configName) != "" {
exists, _ := helpers.Exists(baseFilename, l.Fs)
if exists {
filename = baseFilename
}
} else {
for _, ext := range config.ValidConfigFileExtensions {
filenameToCheck := baseFilename + "." + ext
exists, _ := helpers.Exists(filenameToCheck, l.Fs)
if exists {
filename = filenameToCheck
break
}
}
}
if filename == "" {
return "", ErrNoConfigFile
}
m, err := config.FromFileToMap(l.Fs, filename)
if err != nil {
return filename, err
}
// Set overwrites keys of the same name, recursively.
l.cfg.Set("", m)
if err := l.normalizeCfg(l.cfg); err != nil {
return filename, err
}
if err := l.cleanExternalConfig(l.cfg); err != nil {
return filename, err
}
return filename, nil
}
func (l configLoader) deleteMergeStrategies() {
l.cfg.WalkParams(func(params ...maps.KeyParams) bool {
params[len(params)-1].Params.DeleteMergeStrategy()
return false
})
}
func (l configLoader) loadModulesConfig() (modules.Config, error) {
modConfig, err := modules.DecodeConfig(l.cfg)
if err != nil {
return modules.Config{}, err
}
return modConfig, nil
}
func (l configLoader) wrapFileError(err error, filename string) error {
fe := herrors.UnwrapFileError(err)
if fe != nil {
pos := fe.Position()
pos.Filename = filename
fe.UpdatePosition(pos)
return err
}
return herrors.NewFileErrorFromFile(err, filename, l.Fs, nil)
}
+67
View File
@@ -0,0 +1,67 @@
package allconfig
import (
"os"
"path/filepath"
"testing"
"github.com/spf13/afero"
)
func BenchmarkLoad(b *testing.B) {
tempDir := b.TempDir()
configFilename := filepath.Join(tempDir, "hugo.toml")
config := `
baseURL = "https://example.com"
defaultContentLanguage = 'en'
[module]
[[module.mounts]]
source = 'content/en'
target = 'content/en'
lang = 'en'
[[module.mounts]]
source = 'content/nn'
target = 'content/nn'
lang = 'nn'
[[module.mounts]]
source = 'content/no'
target = 'content/no'
lang = 'no'
[[module.mounts]]
source = 'content/sv'
target = 'content/sv'
lang = 'sv'
[[module.mounts]]
source = 'layouts'
target = 'layouts'
[languages]
[languages.en]
title = "English"
weight = 1
[languages.nn]
title = "Nynorsk"
weight = 2
[languages.no]
title = "Norsk"
weight = 3
[languages.sv]
title = "Svenska"
weight = 4
`
if err := os.WriteFile(configFilename, []byte(config), 0666); err != nil {
b.Fatal(err)
}
d := ConfigSourceDescriptor{
Fs: afero.NewOsFs(),
Filename: configFilename,
}
for i := 0; i < b.N; i++ {
_, err := LoadConfig(d)
if err != nil {
b.Fatal(err)
}
}
}
+245 -57
View File
@@ -15,59 +15,194 @@ package config
import (
"fmt"
"regexp"
"sort"
"strings"
"sync"
"github.com/bep/logg"
"github.com/gobwas/glob"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/types"
"github.com/gobwas/glob"
"github.com/gohugoio/hugo/common/herrors"
"github.com/mitchellh/mapstructure"
"github.com/spf13/cast"
jww "github.com/spf13/jwalterweatherman"
)
var DefaultBuild = Build{
UseResourceCacheWhen: "fallback",
WriteStats: false,
type BaseConfig struct {
WorkingDir string
CacheDir string
ThemesDir string
PublishDir string
}
// Build holds some build related configuration.
type Build struct {
type CommonDirs struct {
// The directory where Hugo will look for themes.
ThemesDir string
// Where to put the generated files.
PublishDir string
// The directory to put the generated resources files. This directory should in most situations be considered temporary
// and not be committed to version control. But there may be cached content in here that you want to keep,
// e.g. resources/_gen/images for performance reasons or CSS built from SASS when your CI server doesn't have the full setup.
ResourceDir string
// The project root directory.
WorkingDir string
// The root directory for all cache files.
CacheDir string
// The content source directory.
// Deprecated: Use module mounts.
ContentDir string
// Deprecated: Use module mounts.
// The data source directory.
DataDir string
// Deprecated: Use module mounts.
// The layout source directory.
LayoutDir string
// Deprecated: Use module mounts.
// The i18n source directory.
I18nDir string
// Deprecated: Use module mounts.
// The archetypes source directory.
ArcheTypeDir string
// Deprecated: Use module mounts.
// The assets source directory.
AssetDir string
}
type LoadConfigResult struct {
Cfg Provider
ConfigFiles []string
BaseConfig BaseConfig
}
var defaultBuild = BuildConfig{
UseResourceCacheWhen: "fallback",
BuildStats: BuildStats{},
CacheBusters: []CacheBuster{
{
Source: `assets/.*\.(js|ts|jsx|tsx)`,
Target: `(js|scripts|javascript)`,
},
{
Source: `assets/.*\.(css|sass|scss)$`,
Target: cssTargetCachebusterRe,
},
{
Source: `(postcss|tailwind)\.config\.js`,
Target: cssTargetCachebusterRe,
},
// This is deliberately coarse grained; it will cache bust resources with "json" in the cache key when js files changes, which is good.
{
Source: `assets/.*\.(.*)$`,
Target: `$1`,
},
},
}
// BuildConfig holds some build related configuration.
type BuildConfig struct {
UseResourceCacheWhen string // never, fallback, always. Default is fallback
// When enabled, will collect and write a hugo_stats.json with some build
// related aggregated data (e.g. CSS class names).
WriteStats bool
// Note that this was a bool <= v0.115.0.
BuildStats BuildStats
// Can be used to toggle off writing of the intellinsense /assets/jsconfig.js
// Can be used to toggle off writing of the IntelliSense /assets/jsconfig.js
// file.
NoJSConfigInAssets bool
// Can used to control how the resource cache gets evicted on rebuilds.
CacheBusters []CacheBuster
}
func (b Build) UseResourceCache(err error) bool {
// BuildStats configures if and what to write to the hugo_stats.json file.
type BuildStats struct {
Enable bool
DisableTags bool
DisableClasses bool
DisableIDs bool
}
func (w BuildStats) Enabled() bool {
if !w.Enable {
return false
}
return !w.DisableTags || !w.DisableClasses || !w.DisableIDs
}
func (b BuildConfig) clone() BuildConfig {
b.CacheBusters = append([]CacheBuster{}, b.CacheBusters...)
return b
}
func (b BuildConfig) UseResourceCache(err error) bool {
if b.UseResourceCacheWhen == "never" {
return false
}
if b.UseResourceCacheWhen == "fallback" {
return err == herrors.ErrFeatureNotAvailable
return herrors.IsFeatureNotAvailableError(err)
}
return true
}
func DecodeBuild(cfg Provider) Build {
// MatchCacheBuster returns the cache buster for the given path p, nil if none.
func (s BuildConfig) MatchCacheBuster(logger loggers.Logger, p string) (func(string) bool, error) {
var matchers []func(string) bool
for _, cb := range s.CacheBusters {
if matcher := cb.compiledSource(p); matcher != nil {
matchers = append(matchers, matcher)
}
}
if len(matchers) > 0 {
return (func(cacheKey string) bool {
for _, m := range matchers {
if m(cacheKey) {
return true
}
}
return false
}), nil
}
return nil, nil
}
func (b *BuildConfig) CompileConfig(logger loggers.Logger) error {
for i, cb := range b.CacheBusters {
if err := cb.CompileConfig(logger); err != nil {
return fmt.Errorf("failed to compile cache buster %q: %w", cb.Source, err)
}
b.CacheBusters[i] = cb
}
return nil
}
func DecodeBuildConfig(cfg Provider) BuildConfig {
m := cfg.GetStringMap("build")
b := DefaultBuild
b := defaultBuild.clone()
if m == nil {
return b
}
// writeStats was a bool <= v0.115.0.
if writeStats, ok := m["writestats"]; ok {
if bb, ok := writeStats.(bool); ok {
m["buildstats"] = BuildStats{Enable: bb}
}
}
err := mapstructure.WeakDecode(m, &b)
if err != nil {
return DefaultBuild
return b
}
b.UseResourceCacheWhen = strings.ToLower(b.UseResourceCacheWhen)
@@ -79,28 +214,19 @@ func DecodeBuild(cfg Provider) Build {
return b
}
// Sitemap configures the sitemap to be generated.
type Sitemap struct {
// SitemapConfig configures the sitemap to be generated.
type SitemapConfig struct {
// The page change frequency.
ChangeFreq string
Priority float64
Filename string
// The priority of the page.
Priority float64
// The sitemap filename.
Filename string
}
func DecodeSitemap(prototype Sitemap, input map[string]any) Sitemap {
for key, value := range input {
switch key {
case "changefreq":
prototype.ChangeFreq = cast.ToString(value)
case "priority":
prototype.Priority = cast.ToFloat64(value)
case "filename":
prototype.Filename = cast.ToString(value)
default:
jww.WARN.Printf("Unknown Sitemap field: %s\n", key)
}
}
return prototype
func DecodeSitemap(prototype SitemapConfig, input map[string]any) (SitemapConfig, error) {
err := mapstructure.WeakDecode(input, &prototype)
return prototype, err
}
// Config for the dev server.
@@ -108,25 +234,25 @@ type Server struct {
Headers []Headers
Redirects []Redirect
compiledInit sync.Once
compiledHeaders []glob.Glob
compiledRedirects []glob.Glob
}
func (s *Server) init() {
s.compiledInit.Do(func() {
for _, h := range s.Headers {
s.compiledHeaders = append(s.compiledHeaders, glob.MustCompile(h.For))
}
for _, r := range s.Redirects {
s.compiledRedirects = append(s.compiledRedirects, glob.MustCompile(r.From))
}
})
func (s *Server) CompileConfig(logger loggers.Logger) error {
if s.compiledHeaders != nil {
return nil
}
for _, h := range s.Headers {
s.compiledHeaders = append(s.compiledHeaders, glob.MustCompile(h.For))
}
for _, r := range s.Redirects {
s.compiledRedirects = append(s.compiledRedirects, glob.MustCompile(r.From))
}
return nil
}
func (s *Server) MatchHeaders(pattern string) []types.KeyValueStr {
s.init()
if s.compiledHeaders == nil {
return nil
}
@@ -150,8 +276,6 @@ func (s *Server) MatchHeaders(pattern string) []types.KeyValueStr {
}
func (s *Server) MatchRedirect(pattern string) Redirect {
s.init()
if s.compiledRedirects == nil {
return Redirect{}
}
@@ -191,18 +315,82 @@ type Redirect struct {
Force bool
}
// CacheBuster configures cache busting for assets.
type CacheBuster struct {
// Trigger for files matching this regexp.
Source string
// Cache bust targets matching this regexp.
// This regexp can contain group matches (e.g. $1) from the source regexp.
Target string
compiledSource func(string) func(string) bool
}
func (c *CacheBuster) CompileConfig(logger loggers.Logger) error {
if c.compiledSource != nil {
return nil
}
source := c.Source
target := c.Target
sourceRe, err := regexp.Compile(source)
if err != nil {
return fmt.Errorf("failed to compile cache buster source %q: %w", c.Source, err)
}
var compileErr error
debugl := logger.Logger().WithLevel(logg.LevelDebug).WithField(loggers.FieldNameCmd, "cachebuster")
c.compiledSource = func(s string) func(string) bool {
m := sourceRe.FindStringSubmatch(s)
matchString := "no match"
match := m != nil
if match {
matchString = "match!"
}
debugl.Logf("Matching %q with source %q: %s", s, source, matchString)
if !match {
return nil
}
groups := m[1:]
// Replace $1, $2 etc. in target.
for i, g := range groups {
target = strings.ReplaceAll(target, fmt.Sprintf("$%d", i+1), g)
}
targetRe, err := regexp.Compile(target)
if err != nil {
compileErr = fmt.Errorf("failed to compile cache buster target %q: %w", target, err)
return nil
}
return func(s string) bool {
match = targetRe.MatchString(s)
matchString := "no match"
if match {
matchString = "match!"
}
logger.Debugf("Matching %q with target %q: %s", s, target, matchString)
return match
}
}
return compileErr
}
func (r Redirect) IsZero() bool {
return r.From == ""
}
func DecodeServer(cfg Provider) (*Server, error) {
m := cfg.GetStringMap("server")
s := &Server{}
if m == nil {
return s, nil
}
const (
// Keep this a little coarse grained, some false positives are OK.
cssTargetCachebusterRe = `(css|styles|scss|sass)`
)
_ = mapstructure.WeakDecode(m, s)
func DecodeServer(cfg Provider) (Server, error) {
s := &Server{}
_ = mapstructure.WeakDecode(cfg.GetStringMap("server"), s)
for i, redir := range s.Redirects {
// Get it in line with the Hugo server for OK responses.
@@ -213,7 +401,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, 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)
return Server{}, 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
@@ -231,5 +419,5 @@ func DecodeServer(cfg Provider) (*Server, error) {
}
return s, nil
return *s, nil
}
+28 -2
View File
@@ -18,6 +18,7 @@ import (
"testing"
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/types"
qt "github.com/frankban/quicktest"
@@ -31,7 +32,7 @@ func TestBuild(t *testing.T) {
"useResourceCacheWhen": "always",
})
b := DecodeBuild(v)
b := DecodeBuildConfig(v)
c.Assert(b.UseResourceCacheWhen, qt.Equals, "always")
@@ -39,7 +40,7 @@ func TestBuild(t *testing.T) {
"useResourceCacheWhen": "foo",
})
b = DecodeBuild(v)
b = DecodeBuildConfig(v)
c.Assert(b.UseResourceCacheWhen, qt.Equals, "fallback")
@@ -91,6 +92,7 @@ status = 301
s, err := DecodeServer(cfg)
c.Assert(err, qt.IsNil)
c.Assert(s.CompileConfig(loggers.NewDefault()), qt.IsNil)
c.Assert(s.MatchHeaders("/foo.jpg"), qt.DeepEquals, []types.KeyValueStr{
{Key: "X-Content-Type-Options", Value: "nosniff"},
@@ -138,3 +140,27 @@ status = 301`,
}
}
func TestBuildConfigCacheBusters(t *testing.T) {
c := qt.New(t)
cfg := New()
conf := DecodeBuildConfig(cfg)
l := loggers.NewDefault()
c.Assert(conf.CompileConfig(l), qt.IsNil)
m, err := conf.MatchCacheBuster(l, "assets/foo/main.js")
c.Assert(err, qt.IsNil)
c.Assert(m, qt.IsNotNil)
c.Assert(m("scripts"), qt.IsTrue)
c.Assert(m("asdf"), qt.IsFalse)
m, _ = conf.MatchCacheBuster(l, "tailwind.config.js")
c.Assert(m("css"), qt.IsTrue)
c.Assert(m("js"), qt.IsFalse)
m, err = conf.MatchCacheBuster(l, "assets/foo.json")
c.Assert(err, qt.IsNil)
c.Assert(m, qt.IsNotNil)
c.Assert(m("json"), qt.IsTrue)
}
-117
View File
@@ -1,117 +0,0 @@
// Copyright 2021 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"github.com/gohugoio/hugo/common/maps"
)
// NewCompositeConfig creates a new composite Provider with a read-only base
// and a writeable layer.
func NewCompositeConfig(base, layer Provider) Provider {
return &compositeConfig{
base: base,
layer: layer,
}
}
// compositeConfig contains a read only config base with
// a possibly writeable config layer on top.
type compositeConfig struct {
base Provider
layer Provider
}
func (c *compositeConfig) GetBool(key string) bool {
if c.layer.IsSet(key) {
return c.layer.GetBool(key)
}
return c.base.GetBool(key)
}
func (c *compositeConfig) GetInt(key string) int {
if c.layer.IsSet(key) {
return c.layer.GetInt(key)
}
return c.base.GetInt(key)
}
func (c *compositeConfig) Merge(key string, value any) {
c.layer.Merge(key, value)
}
func (c *compositeConfig) GetParams(key string) maps.Params {
if c.layer.IsSet(key) {
return c.layer.GetParams(key)
}
return c.base.GetParams(key)
}
func (c *compositeConfig) GetStringMap(key string) map[string]any {
if c.layer.IsSet(key) {
return c.layer.GetStringMap(key)
}
return c.base.GetStringMap(key)
}
func (c *compositeConfig) GetStringMapString(key string) map[string]string {
if c.layer.IsSet(key) {
return c.layer.GetStringMapString(key)
}
return c.base.GetStringMapString(key)
}
func (c *compositeConfig) GetStringSlice(key string) []string {
if c.layer.IsSet(key) {
return c.layer.GetStringSlice(key)
}
return c.base.GetStringSlice(key)
}
func (c *compositeConfig) Get(key string) any {
if c.layer.IsSet(key) {
return c.layer.Get(key)
}
return c.base.Get(key)
}
func (c *compositeConfig) IsSet(key string) bool {
if c.layer.IsSet(key) {
return true
}
return c.base.IsSet(key)
}
func (c *compositeConfig) GetString(key string) string {
if c.layer.IsSet(key) {
return c.layer.GetString(key)
}
return c.base.GetString(key)
}
func (c *compositeConfig) Set(key string, value any) {
c.layer.Set(key, value)
}
func (c *compositeConfig) SetDefaults(params maps.Params) {
c.layer.SetDefaults(params)
}
func (c *compositeConfig) WalkParams(walkFn func(params ...KeyParams) bool) {
panic("not supported")
}
func (c *compositeConfig) SetDefaultMergeStrategy() {
panic("not supported")
}

Some files were not shown because too many files have changed in this diff Show More