mirror of
https://github.com/gohugoio/hugo.git
synced 2026-09-02 03:32:38 +00:00
Compare commits
318 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 643b5ae9cf | |||
| 2f7feca9a1 | |||
| 673cde1eb1 | |||
| e46e9ceb29 | |||
| 883e71c96a | |||
| e9669fed14 | |||
| fefb1caacf | |||
| 0327da050f | |||
| 9b8b6d34e2 | |||
| 376704d382 | |||
| 41b5bc9637 | |||
| c1398b91a9 | |||
| 5a1b394f2a | |||
| 0746908247 | |||
| 75084bfc72 | |||
| 12d00d288c | |||
| 260ff1374d | |||
| 14915a0c32 | |||
| fd0c1a5e9b | |||
| 3ad39001df | |||
| 39261b689e | |||
| cff14144a6 | |||
| 5f67151550 | |||
| f7109771a0 | |||
| e1f696911e | |||
| 5f65c17a12 | |||
| 579ff9b652 | |||
| 928a896962 | |||
| 2e54c00933 | |||
| 10928a4f78 | |||
| afd63bf7d5 | |||
| 228126b7f5 | |||
| 0f80be341f | |||
| 78afdb88ab | |||
| 3ed83227ba | |||
| 1a257bb2ba | |||
| 97514f17d6 | |||
| 0df096b868 | |||
| 308ad611b6 | |||
| 6bffcdbd26 | |||
| 7248f43188 | |||
| 08fdca9d93 | |||
| 2c20f5bc00 | |||
| 723b2c485b | |||
| 06bac57ab0 | |||
| ec8b767fa6 | |||
| 6407b2cd01 | |||
| 6ff39fd908 | |||
| 96c0bdf3aa | |||
| e97d3c640f | |||
| 7732da9f93 | |||
| aebde49b88 | |||
| 4ada09415d | |||
| bddcfd911c | |||
| d485f95431 | |||
| a87be597af | |||
| b0eea00756 | |||
| ef8b781b14 | |||
| 3136ff67d8 | |||
| 929808190f | |||
| 667f3a4ba8 | |||
| c061b253a4 | |||
| 0927cf739f | |||
| 26a5e89faa | |||
| e9fa7e81b3 | |||
| ff545f4276 | |||
| b2a827c52c | |||
| 77c7059ff8 | |||
| f4c90bd6ba | |||
| d16228334d | |||
| ea6bcd6944 | |||
| f2e7b49acf | |||
| 923419d7fd | |||
| 837fdfdf45 | |||
| 6819feab6c | |||
| ea54a99ca5 | |||
| 9563c7d13b | |||
| 230a495941 | |||
| c707b71cdf | |||
| b0a9cf0a75 | |||
| 9433cc2562 | |||
| cdf6a0d622 | |||
| bf1fa71379 | |||
| 4f4cec73be | |||
| da4866c2be | |||
| d1109f590a | |||
| a7d182cea1 | |||
| a2a660ed12 | |||
| f7bc4cc505 | |||
| 54f8d8a70a | |||
| 3036d0ac9a | |||
| 6a238a7276 | |||
| f60714b5a1 | |||
| 215a715ddd | |||
| d128d260b5 | |||
| c4aaf1d516 | |||
| 94f10cf4f9 | |||
| b06c2103b8 | |||
| d22f7795c7 | |||
| 39f69ca7f0 | |||
| 64f75adcf6 | |||
| 9262719092 | |||
| 3336762939 | |||
| 5ca40c8f7c | |||
| ef7d14a241 | |||
| c05c99f0c5 | |||
| ff7689ce02 | |||
| 85e2e86266 | |||
| 515f8a6d8e | |||
| f22c4aba04 | |||
| 85d31f7bfb | |||
| 22055176d2 | |||
| 7a080b624e | |||
| 2655739940 | |||
| 348d300a71 | |||
| 6f07bdb152 | |||
| 55a9bc1e70 | |||
| 20a7ce7c1b | |||
| 8cd4492407 | |||
| 9d8f318aab | |||
| fcbbbef22d | |||
| 6041adc164 | |||
| 408da43657 | |||
| ed04ed5745 | |||
| b354940369 | |||
| bd89aef8ba | |||
| f2bc13dd9e | |||
| 098254f175 | |||
| a8e9fc699a | |||
| cdcd15b6c2 | |||
| 25d645f47a | |||
| fbb3c181cb | |||
| 9af4ca3861 | |||
| 7396aa945a | |||
| d82cef5c53 | |||
| 74f0777c5f | |||
| e334a40661 | |||
| 5bd3c8df40 | |||
| 0aca99fe01 | |||
| c8b5ab75b7 | |||
| 85c5b89593 | |||
| 965760835d | |||
| 4a0b55330c | |||
| 1651beb2c1 | |||
| 2b6063c3e3 | |||
| 56ab83a597 | |||
| dad0dc8d86 | |||
| d3c4fdb8ff | |||
| d632dd7d74 | |||
| 0671ef5593 | |||
| 672481f1f0 | |||
| 1dbfc0f930 | |||
| 1798bd3fd1 | |||
| f0b55a68ee | |||
| 623dda7174 | |||
| aee9e11a40 | |||
| bd63c1aa56 | |||
| 9eb05807c6 | |||
| 654f513a4c | |||
| 759cdf3fc8 | |||
| f423505746 | |||
| af165d5b6d | |||
| 6779117f72 | |||
| ce04011096 | |||
| cdc73526a8 | |||
| 425c7d90f0 | |||
| fca266ebbb | |||
| 0016e21cd6 | |||
| 728feaecf0 | |||
| d1dc0e9a54 | |||
| 072bca69d8 | |||
| e26e13fbb7 | |||
| 6df2f080c9 | |||
| b84745d497 | |||
| 44954497bc | |||
| 22ef5da20d | |||
| 5758c370ea | |||
| 8ee6de6d96 | |||
| a4b9f1a92c | |||
| f4389e48ce | |||
| 803f572e66 | |||
| 6183184b96 | |||
| 45e6fdb315 | |||
| a037be774d | |||
| 8a005538db | |||
| 657d0272e3 | |||
| 159120cddd | |||
| 3f0d49e502 | |||
| 4890788974 | |||
| 3075eaa3b0 | |||
| 3bc6830411 | |||
| e4d6ec94b5 | |||
| 6260455ba7 | |||
| c397975af8 | |||
| 965a6cbff9 | |||
| 34a96290fa | |||
| dd0d3fdbbe | |||
| b921752331 | |||
| 0fa40ce58f | |||
| bf537f1c6d | |||
| e86b331138 | |||
| 6c841a691e | |||
| 8d9511a08f | |||
| e71d715b9b | |||
| e61cdf335f | |||
| 6c3bc5ebad | |||
| 283394a4fd | |||
| 5538507e90 | |||
| b4f27ef8e7 | |||
| 3473e31eb1 | |||
| fa0da004a9 | |||
| 24a893cf83 | |||
| bf1564bd2c | |||
| cd44d409ba | |||
| 9a326d5636 | |||
| b10381fbe0 | |||
| 0eaaa8fee3 | |||
| 58adbeef88 | |||
| 93572e5318 | |||
| 94f149b21e | |||
| 66753416b5 | |||
| 133e4bfbee | |||
| f122771fb1 | |||
| 08a863e1e8 | |||
| 8aa7257f65 | |||
| 75a823a36a | |||
| 5e0947c5b5 | |||
| 0b70b46aad | |||
| ab01ba6e7e | |||
| cc08c09592 | |||
| f97da9eca9 | |||
| 2e70f61fb0 | |||
| 1ed8069a3a | |||
| c88cdb5610 | |||
| e1064d2195 | |||
| bf489b96d3 | |||
| b8155452ac | |||
| 08552a7a4c | |||
| fdad91fd96 | |||
| 5f3f60898c | |||
| 057d02de25 | |||
| 43ac59da85 | |||
| ab5c6990a5 | |||
| 9369d13e59 | |||
| 805c24c32c | |||
| 63e3a5ebb2 | |||
| eaa6c96a79 | |||
| cf3eb580bf | |||
| 2b01c85d14 | |||
| c09f5c5fd3 | |||
| 9232e28446 | |||
| b6a4ae4ad5 | |||
| 84de0c3255 | |||
| a074102262 | |||
| da4406ea50 | |||
| 166862a096 | |||
| 82c33c7105 | |||
| 41e9e9fe14 | |||
| ade966b84b | |||
| a93d4ba64e | |||
| 110512a65c | |||
| f503b63957 | |||
| 30aba7fb09 | |||
| 04a3b45db4 | |||
| 0cc39af682 | |||
| ed6fd26ce8 | |||
| 7fa66425aa | |||
| 69210cfdf3 | |||
| 4b36498a85 | |||
| aa5ac36a3e | |||
| 3a977485eb | |||
| 0f248606d5 | |||
| e82cbd746f | |||
| 3339c2bb61 | |||
| 03bbdba8be | |||
| a772b8fc38 | |||
| dce49d1333 | |||
| 61c5b7a2e6 | |||
| 1d60bd1efa | |||
| 75c9b893d9 | |||
| 64e1613fb3 | |||
| b959ecbc81 | |||
| 70e454812e | |||
| 355ff83e74 | |||
| 471ed91c60 | |||
| 94a5bac5b2 | |||
| 9830ca9e31 | |||
| b64fd0577b | |||
| 2706437a7d | |||
| ec7c993cfe | |||
| 32c6f656d9 | |||
| e02e0727e5 | |||
| 096f5e1921 | |||
| ba35e69856 | |||
| c7957c90e8 | |||
| bb05377033 | |||
| 9185e11eff | |||
| 168a3aab46 | |||
| 8bcfa3bdf6 | |||
| d7331aaa7e | |||
| cd4e67af18 | |||
| e6ad1f0e76 | |||
| 625d2c257a | |||
| e6e44b7c41 | |||
| 64abc83fc4 | |||
| f8d132d731 | |||
| 26f1919ae0 | |||
| e55466ce70 | |||
| ecf025f006 | |||
| fab1e43de5 | |||
| e03f82eef2 | |||
| a864ffe9ac | |||
| b49da33280 | |||
| 7c21eca74f | |||
| 13ad8408fc | |||
| 1cabf61ddf | |||
| 268e3069f3 | |||
| 3efc2e2af8 |
@@ -1,6 +1,6 @@
|
||||
defaults: &defaults
|
||||
docker:
|
||||
- image: bepsays/ci-goreleaser:1.16.7
|
||||
- image: bepsays/ci-goreleaser:1.1700.600
|
||||
environment:
|
||||
CGO_ENABLED: "0"
|
||||
|
||||
@@ -18,7 +18,6 @@ jobs:
|
||||
go mod download
|
||||
sleep 5
|
||||
go mod verify
|
||||
go test -p 1 ./...
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths: .
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: 'Bug report'
|
||||
labels: 'bug'
|
||||
labels: 'Bug, NeedsTriage'
|
||||
assignees: ''
|
||||
about: Create a report to help us improve
|
||||
---
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
---
|
||||
name: Proposal
|
||||
about: Suggest an idea for Hugo
|
||||
about: Propose a new feature for Hugo
|
||||
title: ''
|
||||
labels: 'Proposal'
|
||||
labels: 'Proposal, NeedsTriage'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
<!-- Describe this new feature. Think about if it really belongs in the Hugo core module; you may want to discuss it on https://discourse.gohugo.io/ first. -->
|
||||
@@ -1,23 +0,0 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 120
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 30
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- Keep
|
||||
- Security
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: Stale
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. The resources of the Hugo team are limited, and so we are asking for your help.
|
||||
|
||||
If this is a **bug** and you can still reproduce this error on the <code>master</code> branch, please reply with all of the information you have about it in order to keep the issue open.
|
||||
|
||||
If this is a **feature request**, and you feel that it is still relevant and valuable, please tell us why.
|
||||
|
||||
This issue will automatically be closed in the near future if no further activity occurs. Thank you for all your contributions.
|
||||
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
||||
@@ -0,0 +1,47 @@
|
||||
name: 'Close stale and lock closed issues and PRs'
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '30 1 * * *'
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@08e671be8ac8944d0e132aa71d0ae8ccfb347675
|
||||
with:
|
||||
issue-inactive-days: 21
|
||||
add-issue-labels: 'Outdated'
|
||||
issue-comment: >
|
||||
This issue has been automatically locked since there
|
||||
has not been any recent activity after it was closed.
|
||||
Please open a new issue for related bugs.
|
||||
pr-comment: >
|
||||
This pull request has been automatically locked since there
|
||||
has not been any recent activity after it was closed.
|
||||
Please open a new issue for related bugs.
|
||||
- uses: actions/stale@04a1828bc18ada028d85a0252a47cd2963a91abe
|
||||
with:
|
||||
operations-per-run: 90
|
||||
days-before-issue-stale: 365
|
||||
days-before-pr-stale: 200
|
||||
days-before-issue-close: 21
|
||||
days-before-pr-close: 21
|
||||
stale-issue-message: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. The resources of the Hugo team are limited, and so we are asking for your help.
|
||||
|
||||
If this is a **bug** and you can still reproduce this error on the <code>master</code> branch, please reply with all of the information you have about it in order to keep the issue open.
|
||||
|
||||
If this is a **feature request**, and you feel that it is still relevant and valuable, please tell us why.
|
||||
|
||||
This issue will automatically be closed in the near future if no further activity occurs. Thank you for all your contributions.
|
||||
stale-pr-message: This PR has been automatically marked as stale because it has not had
|
||||
recent activity. The resources of the Hugo team are limited, and so we are asking for your help.
|
||||
|
||||
Please check https://github.com/gohugoio/hugo/blob/master/CONTRIBUTING.md#code-contribution and verify that this code contribution fits with the description. If yes, tell is in a comment.
|
||||
|
||||
This PR will automatically be closed in the near future if no further activity occurs. Thank you for all your contributions.
|
||||
stale-issue-label: 'Stale'
|
||||
exempt-issue-labels: 'Keep,Security'
|
||||
stale-pr-label: 'Stale'
|
||||
exempt-pr-labels: 'Keep,Security'
|
||||
@@ -1,6 +1,7 @@
|
||||
/hugo
|
||||
docs/public*
|
||||
/.idea
|
||||
.vscode/*
|
||||
hugo.exe
|
||||
*.test
|
||||
*.prof
|
||||
@@ -25,3 +26,4 @@ resources/sunset.jpg
|
||||
|
||||
vendor
|
||||
|
||||
.hugo_build.lock
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ COPY . /go/src/github.com/gohugoio/hugo/
|
||||
|
||||
# gcc/g++ are required to build SASS libraries for extended version
|
||||
RUN apk update && \
|
||||
apk add --no-cache gcc g++ musl-dev && \
|
||||
apk add --no-cache gcc g++ musl-dev git && \
|
||||
go get github.com/magefile/mage
|
||||
|
||||
RUN mage hugo && mage install
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/static/images/hugo-logo-wide.svg?sanitize=true" alt="Hugo" width="565">
|
||||
<a href="https://gohugo.io/"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/static/images/hugo-logo-wide.svg?sanitize=true" alt="Hugo" width="565"></a>
|
||||
|
||||
A Fast and Flexible Static Site Generator built with love by [bep](https://github.com/bep), [spf13](http://spf13.com/) and [friends](https://github.com/gohugoio/hugo/graphs/contributors) in [Go][].
|
||||
|
||||
@@ -29,7 +29,7 @@ Hugo is designed to work well for any kind of website including blogs, tumbles,
|
||||
|
||||
#### Supported Architectures
|
||||
|
||||
Currently, we provide pre-built Hugo binaries for Windows, Linux, FreeBSD, NetBSD, DragonFly BSD, Open BSD, macOS (Darwin), and [Android](https://gist.github.com/bep/a0d8a26cf6b4f8bc992729b8e50b480b) for x64, i386 and ARM architectures.
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
@@ -119,8 +119,6 @@ If you are unfamiliar with the contribution process, the Hugo team will help you
|
||||
|
||||
For a complete guide to contributing code to Hugo, see the [Contribution Guide](CONTRIBUTING.md).
|
||||
|
||||
[](https://github.com/igrigorik/ga-beacon)
|
||||
|
||||
[Go]: https://golang.org/
|
||||
[Hugo Documentation]: https://gohugo.io/overview/introduction/
|
||||
|
||||
@@ -130,97 +128,97 @@ Hugo stands on the shoulder of many great open source libraries.
|
||||
|
||||
If you run `hugo env -v` you will get a complete and up to date list.
|
||||
|
||||
In Hugo 0.83.0 that list is, in lexical order:
|
||||
In Hugo 0.89.0 that list is, in lexical order:
|
||||
|
||||
```json
|
||||
```
|
||||
cloud.google.com/go/storage="v1.10.0"
|
||||
cloud.google.com/go="v0.81.0"
|
||||
cloud.google.com/go="v0.87.0"
|
||||
github.com/Azure/azure-pipeline-go="v0.2.2"
|
||||
github.com/Azure/azure-storage-blob-go="v0.9.0"
|
||||
github.com/BurntSushi/locker="v0.0.0-20171006230638-a6e239ea1c69"
|
||||
github.com/BurntSushi/toml="v0.3.1"
|
||||
github.com/PuerkitoBio/purell="v1.1.1"
|
||||
github.com/PuerkitoBio/urlesc="v0.0.0-20170810143723-de5bf2ad4578"
|
||||
github.com/alecthomas/chroma="v0.9.1"
|
||||
github.com/alecthomas/chroma="v0.9.4"
|
||||
github.com/armon/go-radix="v1.0.0"
|
||||
github.com/aws/aws-sdk-go="v1.38.23"
|
||||
github.com/aws/aws-sdk-go="v1.41.14"
|
||||
github.com/bep/debounce="v1.2.0"
|
||||
github.com/bep/gitmap="v1.1.2"
|
||||
github.com/bep/godartsass="v0.12.0"
|
||||
github.com/bep/golibsass="v1.0.0"
|
||||
github.com/bep/gowebp="v0.1.0"
|
||||
github.com/bep/tmc="v0.5.1"
|
||||
github.com/cli/safeexec="v1.0.0"
|
||||
github.com/cpuguy83/go-md2man/v2="v2.0.0"
|
||||
github.com/danwakefield/fnmatch="v0.0.0-20160403171240-cbb64ac3d964"
|
||||
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.11.16"
|
||||
github.com/fsnotify/fsnotify="v1.4.9"
|
||||
github.com/getkin/kin-openapi="v0.61.0"
|
||||
github.com/evanw/esbuild="v0.13.12"
|
||||
github.com/fsnotify/fsnotify="v1.5.1"
|
||||
github.com/getkin/kin-openapi="v0.80.0"
|
||||
github.com/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.2"
|
||||
github.com/gobuffalo/flect="v0.2.3"
|
||||
github.com/gobwas/glob="v0.2.3"
|
||||
github.com/gohugoio/go-i18n/v2="v2.1.3-0.20210430103248-4c28c89f8013"
|
||||
github.com/gohugoio/locales="v0.14.0"
|
||||
github.com/gohugoio/localescompressed="v0.14.0"
|
||||
github.com/golang/groupcache="v0.0.0-20200121045136-8c9f03a8e57e"
|
||||
github.com/golang/protobuf="v1.5.2"
|
||||
github.com/google/go-cmp="v0.5.5"
|
||||
github.com/google/go-cmp="v0.5.6"
|
||||
github.com/google/uuid="v1.1.2"
|
||||
github.com/google/wire="v0.4.0"
|
||||
github.com/googleapis/gax-go/v2="v2.0.5"
|
||||
github.com/googleapis/gax-go="v2.0.2+incompatible"
|
||||
github.com/gorilla/websocket="v1.4.2"
|
||||
github.com/hashicorp/hcl="v1.0.0"
|
||||
github.com/inconshreveable/mousetrap="v1.0.0"
|
||||
github.com/jdkato/prose="v1.2.1"
|
||||
github.com/jmespath/go-jmespath="v0.4.0"
|
||||
github.com/kyokomi/emoji/v2="v2.2.8"
|
||||
github.com/magiconair/properties="v1.8.1"
|
||||
github.com/mailru/easyjson="v0.0.0-20190626092158-b2ccc519800e"
|
||||
github.com/mattn/go-ieproxy="v0.0.1"
|
||||
github.com/mattn/go-isatty="v0.0.12"
|
||||
github.com/mattn/go-isatty="v0.0.14"
|
||||
github.com/mattn/go-runewidth="v0.0.9"
|
||||
github.com/miekg/mmark="v1.3.6"
|
||||
github.com/mitchellh/hashstructure="v1.0.0"
|
||||
github.com/mitchellh/mapstructure="v1.3.3"
|
||||
github.com/mitchellh/hashstructure="v1.1.0"
|
||||
github.com/mitchellh/mapstructure="v1.4.2"
|
||||
github.com/muesli/smartcrop="v0.3.0"
|
||||
github.com/niklasfasching/go-org="v1.5.0"
|
||||
github.com/olekukonko/tablewriter="v0.0.5"
|
||||
github.com/pelletier/go-toml="v1.9.0"
|
||||
github.com/pelletier/go-toml/v2="v2.0.0-beta.3.0.20210727221244-fa0796069526"
|
||||
github.com/pkg/errors="v0.9.1"
|
||||
github.com/rogpeppe/go-internal="v1.8.0"
|
||||
github.com/russross/blackfriday/v2="v2.0.1"
|
||||
github.com/russross/blackfriday="v1.5.3-0.20200218234912-41c5fccfd6f6"
|
||||
github.com/rwcarlsen/goexif="v0.0.0-20190401172101-9e8deecbddbd"
|
||||
github.com/sanity-io/litter="v1.5.0"
|
||||
github.com/sanity-io/litter="v1.5.1"
|
||||
github.com/sass/libsass="3.6.5"
|
||||
github.com/shurcooL/sanitized_anchor_name="v1.0.0"
|
||||
github.com/spf13/afero="v1.6.0"
|
||||
github.com/spf13/cast="v1.3.1"
|
||||
github.com/spf13/cobra="v1.1.3"
|
||||
github.com/spf13/cast="v1.4.1"
|
||||
github.com/spf13/cobra="v1.2.1"
|
||||
github.com/spf13/fsync="v0.9.0"
|
||||
github.com/spf13/jwalterweatherman="v1.1.0"
|
||||
github.com/spf13/pflag="v1.0.5"
|
||||
github.com/spf13/viper="v1.7.1"
|
||||
github.com/subosito/gotenv="v1.2.0"
|
||||
github.com/tdewolff/minify/v2="v2.9.16"
|
||||
github.com/tdewolff/parse/v2="v2.5.14"
|
||||
github.com/tdewolff/minify/v2="v2.9.22"
|
||||
github.com/tdewolff/parse/v2="v2.5.21"
|
||||
github.com/webmproject/libwebp="v1.2.0"
|
||||
github.com/yuin/goldmark-highlighting="v0.0.0-20200307114337-60d527fdb691"
|
||||
github.com/yuin/goldmark="v1.3.5"
|
||||
github.com/yuin/goldmark="v1.4.2"
|
||||
go.opencensus.io="v0.23.0"
|
||||
gocloud.dev="v0.20.0"
|
||||
golang.org/x/image="v0.0.0-20210220032944-ac19c3e999fb"
|
||||
golang.org/x/net="v0.0.0-20210316092652-d523dce5a7f4"
|
||||
golang.org/x/oauth2="v0.0.0-20210413134643-5e61552d6c78"
|
||||
golang.org/x/net="v0.0.0-20210614182718-04defd469f4e"
|
||||
golang.org/x/oauth2="v0.0.0-20210628180205-a41e5a781914"
|
||||
golang.org/x/sync="v0.0.0-20210220032951-036812b2e83c"
|
||||
golang.org/x/sys="v0.0.0-20210412220455-f1c623a9e750"
|
||||
golang.org/x/text="v0.3.6"
|
||||
golang.org/x/sys="v0.0.0-20210908233432-aa78b53d3365"
|
||||
golang.org/x/text="v0.3.7"
|
||||
golang.org/x/xerrors="v0.0.0-20200804184101-5ec99f83aff1"
|
||||
google.golang.org/api="v0.45.0"
|
||||
google.golang.org/genproto="v0.0.0-20210413151531-c14fb6ef47c3"
|
||||
google.golang.org/grpc="v1.37.0"
|
||||
google.golang.org/protobuf="v1.26.0"
|
||||
gopkg.in/ini.v1="v1.51.1"
|
||||
google.golang.org/api="v0.51.0"
|
||||
google.golang.org/genproto="v0.0.0-20210716133855-ce7ef5c701ea"
|
||||
google.golang.org/grpc="v1.39.0"
|
||||
google.golang.org/protobuf="v1.27.1"
|
||||
gopkg.in/yaml.v2="v2.4.0"
|
||||
|
||||
```
|
||||
|
||||
|
||||
Vendored
+12
-2
@@ -175,7 +175,12 @@ func (c *Cache) GetOrCreate(id string, create func() (io.ReadCloser, error)) (It
|
||||
return info, r, nil
|
||||
}
|
||||
|
||||
r, err := create()
|
||||
var (
|
||||
r io.ReadCloser
|
||||
err error
|
||||
)
|
||||
|
||||
r, err = create()
|
||||
if err != nil {
|
||||
return info, nil, err
|
||||
}
|
||||
@@ -206,7 +211,12 @@ func (c *Cache) GetOrCreateBytes(id string, create func() ([]byte, error)) (Item
|
||||
return info, b, err
|
||||
}
|
||||
|
||||
b, err := create()
|
||||
var (
|
||||
b []byte
|
||||
err error
|
||||
)
|
||||
|
||||
b, err = create()
|
||||
if err != nil {
|
||||
return info, nil, err
|
||||
}
|
||||
|
||||
Vendored
+17
-6
@@ -34,19 +34,21 @@ const (
|
||||
cachesConfigKey = "caches"
|
||||
|
||||
resourcesGenDir = ":resourceDir/_gen"
|
||||
cacheDirProject = ":cacheDir/:project"
|
||||
)
|
||||
|
||||
var defaultCacheConfig = Config{
|
||||
MaxAge: -1, // Never expire
|
||||
Dir: ":cacheDir/:project",
|
||||
Dir: cacheDirProject,
|
||||
}
|
||||
|
||||
const (
|
||||
cacheKeyGetJSON = "getjson"
|
||||
cacheKeyGetCSV = "getcsv"
|
||||
cacheKeyImages = "images"
|
||||
cacheKeyAssets = "assets"
|
||||
cacheKeyModules = "modules"
|
||||
cacheKeyGetJSON = "getjson"
|
||||
cacheKeyGetCSV = "getcsv"
|
||||
cacheKeyImages = "images"
|
||||
cacheKeyAssets = "assets"
|
||||
cacheKeyModules = "modules"
|
||||
cacheKeyGetResource = "getresource"
|
||||
)
|
||||
|
||||
type Configs map[string]Config
|
||||
@@ -70,6 +72,10 @@ var defaultCacheConfigs = Configs{
|
||||
MaxAge: -1,
|
||||
Dir: resourcesGenDir,
|
||||
},
|
||||
cacheKeyGetResource: Config{
|
||||
MaxAge: -1, // Never expire
|
||||
Dir: cacheDirProject,
|
||||
},
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
@@ -111,6 +117,11 @@ func (f Caches) AssetsCache() *Cache {
|
||||
return f[cacheKeyAssets]
|
||||
}
|
||||
|
||||
// GetResourceCache gets the file cache for remote resources.
|
||||
func (f Caches) GetResourceCache() *Cache {
|
||||
return f[cacheKeyGetResource]
|
||||
}
|
||||
|
||||
func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) {
|
||||
c := make(Configs)
|
||||
valid := make(map[string]bool)
|
||||
|
||||
+11
-5
@@ -50,7 +50,8 @@ maxAge = "11h"
|
||||
dir = "/path/to/c2"
|
||||
[caches.images]
|
||||
dir = "/path/to/c3"
|
||||
|
||||
[caches.getResource]
|
||||
dir = "/path/to/c4"
|
||||
`
|
||||
|
||||
cfg, err := config.FromConfigString(configStr, "toml")
|
||||
@@ -59,7 +60,7 @@ dir = "/path/to/c3"
|
||||
decoded, err := DecodeConfig(fs, cfg)
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
c.Assert(len(decoded), qt.Equals, 5)
|
||||
c.Assert(len(decoded), qt.Equals, 6)
|
||||
|
||||
c2 := decoded["getcsv"]
|
||||
c.Assert(c2.MaxAge.String(), qt.Equals, "11h0m0s")
|
||||
@@ -68,6 +69,10 @@ dir = "/path/to/c3"
|
||||
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"))
|
||||
|
||||
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"))
|
||||
}
|
||||
|
||||
func TestDecodeConfigIgnoreCache(t *testing.T) {
|
||||
@@ -94,7 +99,8 @@ maxAge = 3456
|
||||
dir = "/path/to/c2"
|
||||
[caches.images]
|
||||
dir = "/path/to/c3"
|
||||
|
||||
[caches.getResource]
|
||||
dir = "/path/to/c4"
|
||||
`
|
||||
|
||||
cfg, err := config.FromConfigString(configStr, "toml")
|
||||
@@ -103,7 +109,7 @@ dir = "/path/to/c3"
|
||||
decoded, err := DecodeConfig(fs, cfg)
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
c.Assert(len(decoded), qt.Equals, 5)
|
||||
c.Assert(len(decoded), qt.Equals, 6)
|
||||
|
||||
for _, v := range decoded {
|
||||
c.Assert(v.MaxAge, qt.Equals, time.Duration(0))
|
||||
@@ -129,7 +135,7 @@ func TestDecodeConfigDefault(t *testing.T) {
|
||||
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
c.Assert(len(decoded), qt.Equals, 5)
|
||||
c.Assert(len(decoded), qt.Equals, 6)
|
||||
|
||||
imgConfig := decoded[cacheKeyImages]
|
||||
jsonConfig := decoded[cacheKeyGetJSON]
|
||||
|
||||
+10
-1
@@ -61,6 +61,8 @@ type commandeer struct {
|
||||
logger loggers.Logger
|
||||
serverConfig *config.Server
|
||||
|
||||
buildLock func() (unlock func(), err error)
|
||||
|
||||
// Loading state
|
||||
mustHaveConfigFile bool
|
||||
failOnInitErr bool
|
||||
@@ -323,8 +325,11 @@ func (c *commandeer) loadConfig() error {
|
||||
// We should improve the error handling here,
|
||||
// but with hugo mod init and similar there is a chicken and egg situation
|
||||
// with modules already configured in config.toml, so ignore those errors.
|
||||
if c.mustHaveConfigFile || !moduleNotFoundRe.MatchString(err.Error()) {
|
||||
if c.mustHaveConfigFile || (c.failOnInitErr && !moduleNotFoundRe.MatchString(err.Error())) {
|
||||
return err
|
||||
} else {
|
||||
// Just make it a warning.
|
||||
c.logger.Warnln(err)
|
||||
}
|
||||
} else if c.mustHaveConfigFile && len(configFiles) == 0 {
|
||||
return hugolib.ErrNoConfigFile
|
||||
@@ -416,6 +421,10 @@ func (c *commandeer) loadConfig() error {
|
||||
err = createErr
|
||||
}
|
||||
c.hugoSites = h
|
||||
// TODO(bep) improve.
|
||||
if c.buildLock == nil && h != nil {
|
||||
c.buildLock = h.LockBuild
|
||||
}
|
||||
close(c.created)
|
||||
})
|
||||
|
||||
|
||||
@@ -48,7 +48,6 @@ func (b *commandsBuilder) addAll() *commandsBuilder {
|
||||
newVersionCmd(),
|
||||
newEnvCmd(),
|
||||
b.newConfigCmd(),
|
||||
newCheckCmd(),
|
||||
b.newDeployCmd(),
|
||||
b.newConvertCmd(),
|
||||
b.newNewCmd(),
|
||||
@@ -131,8 +130,7 @@ type hugoCmd struct {
|
||||
|
||||
var _ cmder = (*nilCommand)(nil)
|
||||
|
||||
type nilCommand struct {
|
||||
}
|
||||
type nilCommand struct{}
|
||||
|
||||
func (c *nilCommand) getCommand() *cobra.Command {
|
||||
return nil
|
||||
@@ -281,7 +279,6 @@ func (cc *hugoBuilderCommon) handleCommonBuilderFlags(cmd *cobra.Command) {
|
||||
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().BoolP("ignoreVendor", "", false, "ignores any _vendor directory")
|
||||
cmd.PersistentFlags().StringP("ignoreVendorPaths", "", "", "ignores any _vendor for module paths matching the given Glob pattern")
|
||||
}
|
||||
|
||||
@@ -298,20 +295,21 @@ func (cc *hugoBuilderCommon) handleFlags(cmd *cobra.Command) {
|
||||
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. http://spf13.com/")
|
||||
cmd.Flags().Bool("enableGitInfo", false, "add Git revision, date and author info to the pages")
|
||||
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("i18n-warnings", "", false, "print missing translations")
|
||||
cmd.Flags().BoolP("path-warnings", "", false, "print warnings on duplicate target paths etc.")
|
||||
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, "print-mem", "", false, "print memory usage to screen at intervals")
|
||||
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)")
|
||||
|
||||
|
||||
+12
-18
@@ -162,16 +162,6 @@ func TestFlags(t *testing.T) {
|
||||
args []string
|
||||
check func(c *qt.C, cmd *serverCmd)
|
||||
}{
|
||||
{
|
||||
// https://github.com/gohugoio/hugo/issues/7642
|
||||
name: "ignoreVendor as bool",
|
||||
args: []string{"server", "--ignoreVendor"},
|
||||
check: func(c *qt.C, cmd *serverCmd) {
|
||||
cfg := config.New()
|
||||
cmd.flagsToConfig(cfg)
|
||||
c.Assert(cfg.Get("ignoreVendor"), qt.Equals, true)
|
||||
},
|
||||
},
|
||||
{
|
||||
// https://github.com/gohugoio/hugo/issues/7642
|
||||
name: "ignoreVendorPaths",
|
||||
@@ -200,13 +190,14 @@ func TestFlags(t *testing.T) {
|
||||
"--navigateToChanged",
|
||||
"--disableLiveReload",
|
||||
"--noHTTPCache",
|
||||
"--i18n-warnings",
|
||||
"--printI18nWarnings",
|
||||
"--destination=/tmp/mydestination",
|
||||
"-b=https://example.com/b/",
|
||||
"--port=1366",
|
||||
"--renderToDisk",
|
||||
"--source=mysource",
|
||||
"--path-warnings",
|
||||
"--printPathWarnings",
|
||||
"--printUnusedTemplates",
|
||||
},
|
||||
check: func(c *qt.C, sc *serverCmd) {
|
||||
c.Assert(sc, qt.Not(qt.IsNil))
|
||||
@@ -230,10 +221,10 @@ func TestFlags(t *testing.T) {
|
||||
|
||||
c.Assert(cfg.GetBool("gc"), qt.Equals, true)
|
||||
|
||||
// The flag is named path-warnings
|
||||
// The flag is named printPathWarnings
|
||||
c.Assert(cfg.GetBool("logPathWarnings"), qt.Equals, true)
|
||||
|
||||
// The flag is named i18n-warnings
|
||||
// The flag is named printI18nWarnings
|
||||
c.Assert(cfg.GetBool("logI18nWarnings"), qt.Equals, true)
|
||||
},
|
||||
},
|
||||
@@ -284,11 +275,14 @@ func TestCommandsExecute(t *testing.T) {
|
||||
// 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", "autocomplete"}, []string{"--completionfile=" + filepath.Join(dirOut, "autocomplete.txt")}, ""},
|
||||
{[]string{"gen", "chromastyles"}, []string{"--style=manni"}, ""},
|
||||
{[]string{"gen", "doc"}, []string{"--dir=" + filepath.Join(dirOut, "doc")}, ""},
|
||||
{[]string{"gen", "man"}, []string{"--dir=" + filepath.Join(dirOut, "man")}, ""},
|
||||
@@ -335,7 +329,7 @@ type testSiteConfig struct {
|
||||
contentDir string
|
||||
}
|
||||
|
||||
func createSimpleTestSite(t *testing.T, cfg testSiteConfig) (string, func(), error) {
|
||||
func createSimpleTestSite(t testing.TB, cfg testSiteConfig) (string, func(), error) {
|
||||
d, clean, e := htesting.CreateTempDir(hugofs.Os, "hugo-cli")
|
||||
if e != nil {
|
||||
return "", nil, e
|
||||
@@ -398,12 +392,12 @@ Environment: {{ hugo.Environment }}
|
||||
return d, clean, nil
|
||||
}
|
||||
|
||||
func writeFile(t *testing.T, filename, content string) {
|
||||
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.T, err error) {
|
||||
func must(t testing.TB, err error) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
+3
-3
@@ -142,7 +142,7 @@ func (cc *convertCmd) convertAndSavePage(p page.Page, site *hugolib.Site, target
|
||||
return nil
|
||||
}
|
||||
|
||||
errMsg := fmt.Errorf("Error processing file %q", p.Path())
|
||||
errMsg := fmt.Errorf("Error processing file %q", p.File().Path())
|
||||
|
||||
site.Log.Infoln("Attempting to convert", p.File().Filename())
|
||||
|
||||
@@ -185,10 +185,10 @@ func (cc *convertCmd) convertAndSavePage(p page.Page, site *hugolib.Site, target
|
||||
newFilename := p.File().Filename()
|
||||
|
||||
if cc.outputDir != "" {
|
||||
contentDir := strings.TrimSuffix(newFilename, p.Path())
|
||||
contentDir := strings.TrimSuffix(newFilename, p.File().Path())
|
||||
contentDir = filepath.Base(contentDir)
|
||||
|
||||
newFilename = filepath.Join(cc.outputDir, contentDir, p.Path())
|
||||
newFilename = filepath.Join(cc.outputDir, contentDir, p.File().Path())
|
||||
}
|
||||
|
||||
fs := hugofs.Os
|
||||
|
||||
@@ -31,7 +31,6 @@ func newGenCmd() *genCmd {
|
||||
})
|
||||
|
||||
cc.cmd.AddCommand(
|
||||
newGenautocompleteCmd().getCommand(),
|
||||
newGenDocCmd().getCommand(),
|
||||
newGenManCmd().getCommand(),
|
||||
createGenDocsHelper().getCommand(),
|
||||
|
||||
@@ -1,93 +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 (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
jww "github.com/spf13/jwalterweatherman"
|
||||
)
|
||||
|
||||
var _ cmder = (*genautocompleteCmd)(nil)
|
||||
|
||||
type genautocompleteCmd struct {
|
||||
autocompleteTarget string
|
||||
|
||||
// bash, zsh, fish or powershell
|
||||
autocompleteType string
|
||||
|
||||
*baseCmd
|
||||
}
|
||||
|
||||
func newGenautocompleteCmd() *genautocompleteCmd {
|
||||
cc := &genautocompleteCmd{}
|
||||
|
||||
cc.baseCmd = newBaseCmd(&cobra.Command{
|
||||
Use: "autocomplete",
|
||||
Short: "Generate shell autocompletion script for Hugo",
|
||||
Long: `Generates a shell autocompletion script for Hugo.
|
||||
|
||||
The script is written to the console (stdout).
|
||||
|
||||
To write to file, add the ` + "`--completionfile=/path/to/file`" + ` flag.
|
||||
|
||||
Add ` + "`--type={bash, zsh, fish or powershell}`" + ` flag to set alternative
|
||||
shell type.
|
||||
|
||||
Logout and in again to reload the completion scripts,
|
||||
or just source them in directly:
|
||||
|
||||
$ . /etc/bash_completion or /path/to/file`,
|
||||
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
var err error
|
||||
var target io.Writer
|
||||
|
||||
if cc.autocompleteTarget == "" {
|
||||
target = os.Stdout
|
||||
} else {
|
||||
target, _ = os.OpenFile(cc.autocompleteTarget, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
}
|
||||
|
||||
switch cc.autocompleteType {
|
||||
case "bash":
|
||||
err = cmd.Root().GenBashCompletion(target)
|
||||
case "zsh":
|
||||
err = cmd.Root().GenZshCompletion(target)
|
||||
case "fish":
|
||||
err = cmd.Root().GenFishCompletion(target, true)
|
||||
case "powershell":
|
||||
err = cmd.Root().GenPowerShellCompletion(target)
|
||||
default:
|
||||
return newUserError("Unsupported completion type")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cc.autocompleteTarget != "" {
|
||||
jww.FEEDBACK.Println(cc.autocompleteType+" completion file for Hugo saved to", cc.autocompleteTarget)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
cc.cmd.PersistentFlags().StringVarP(&cc.autocompleteTarget, "completionfile", "f", "", "autocompletion file, defaults to stdout")
|
||||
cc.cmd.PersistentFlags().StringVarP(&cc.autocompleteType, "type", "t", "bash", "autocompletion type (bash, zsh, fish, or powershell)")
|
||||
|
||||
return cc
|
||||
}
|
||||
+1
-1
@@ -68,7 +68,7 @@ func newSystemErrorF(format string, a ...interface{}) commandError {
|
||||
// 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("argument|flag|shorthand")
|
||||
var userErrorRegexp = regexp.MustCompile("unknown flag")
|
||||
|
||||
func isUserError(err error) bool {
|
||||
if cErr, ok := err.(commandError); ok && cErr.isUserError() {
|
||||
|
||||
+43
-22
@@ -30,6 +30,9 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
"github.com/gohugoio/hugo/tpl"
|
||||
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
@@ -197,7 +200,6 @@ func initializeFlags(cmd *cobra.Command, cfg config.Provider) {
|
||||
"forceSyncStatic",
|
||||
"noTimes",
|
||||
"noChmod",
|
||||
"ignoreVendor",
|
||||
"ignoreVendorPaths",
|
||||
"templateMetrics",
|
||||
"templateMetricsHints",
|
||||
@@ -215,7 +217,8 @@ func initializeFlags(cmd *cobra.Command, cfg config.Provider) {
|
||||
"dryRun",
|
||||
"force",
|
||||
"gc",
|
||||
"i18n-warnings",
|
||||
"printI18nWarnings",
|
||||
"printUnusedTemplates",
|
||||
"invalidateCDN",
|
||||
"layoutDir",
|
||||
"logFile",
|
||||
@@ -242,8 +245,8 @@ func initializeFlags(cmd *cobra.Command, cfg config.Provider) {
|
||||
|
||||
// Set some "config aliases"
|
||||
setValueFromFlag(cmd.Flags(), "destination", cfg, "publishDir", false)
|
||||
setValueFromFlag(cmd.Flags(), "i18n-warnings", cfg, "logI18nWarnings", false)
|
||||
setValueFromFlag(cmd.Flags(), "path-warnings", cfg, "logPathWarnings", false)
|
||||
setValueFromFlag(cmd.Flags(), "printI18nWarnings", cfg, "logI18nWarnings", false)
|
||||
setValueFromFlag(cmd.Flags(), "printPathWarnings", cfg, "logPathWarnings", false)
|
||||
}
|
||||
|
||||
func setValueFromFlag(flags *flag.FlagSet, key string, cfg config.Provider, targetKey string, force bool) {
|
||||
@@ -278,7 +281,7 @@ func isTerminal() bool {
|
||||
return terminal.IsTerminal(os.Stdout)
|
||||
}
|
||||
|
||||
func (c *commandeer) fullBuild() error {
|
||||
func (c *commandeer) fullBuild(noBuildLock bool) error {
|
||||
var (
|
||||
g errgroup.Group
|
||||
langCount map[string]uint64
|
||||
@@ -303,7 +306,7 @@ func (c *commandeer) fullBuild() error {
|
||||
return nil
|
||||
}
|
||||
buildSitesFunc := func() error {
|
||||
if err := c.buildSites(); err != nil {
|
||||
if err := c.buildSites(noBuildLock); err != nil {
|
||||
return errors.Wrap(err, "Error building site")
|
||||
}
|
||||
return nil
|
||||
@@ -496,11 +499,10 @@ func (c *commandeer) build() error {
|
||||
}
|
||||
}()
|
||||
|
||||
if err := c.fullBuild(); err != nil {
|
||||
if err := c.fullBuild(false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO(bep) Feedback?
|
||||
if !c.h.quiet {
|
||||
fmt.Println()
|
||||
c.hugo().PrintProcessingStats(os.Stdout)
|
||||
@@ -512,6 +514,11 @@ func (c *commandeer) build() error {
|
||||
c.logger.Warnln("Duplicate target paths:", dupes)
|
||||
}
|
||||
}
|
||||
|
||||
unusedTemplates := c.hugo().Tmpl().(tpl.UnusedTemplatesProvider).UnusedTemplates()
|
||||
for _, unusedTemplate := range unusedTemplates {
|
||||
c.logger.Warnf("Template %s is unused, source file %s", unusedTemplate.Name(), unusedTemplate.Filename())
|
||||
}
|
||||
}
|
||||
|
||||
if c.h.buildWatch {
|
||||
@@ -539,7 +546,6 @@ func (c *commandeer) build() error {
|
||||
}
|
||||
|
||||
func (c *commandeer) serverBuild() error {
|
||||
|
||||
stopProfiling, err := c.initProfiling()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -551,7 +557,7 @@ func (c *commandeer) serverBuild() error {
|
||||
}
|
||||
}()
|
||||
|
||||
if err := c.fullBuild(); err != nil {
|
||||
if err := c.fullBuild(false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -721,8 +727,8 @@ func (c *commandeer) getDirList() ([]string, error) {
|
||||
return filenames, nil
|
||||
}
|
||||
|
||||
func (c *commandeer) buildSites() (err error) {
|
||||
return c.hugo().Build(hugolib.BuildCfg{})
|
||||
func (c *commandeer) buildSites(noBuildLock bool) (err error) {
|
||||
return c.hugo().Build(hugolib.BuildCfg{NoBuildLock: noBuildLock})
|
||||
}
|
||||
|
||||
func (c *commandeer) handleBuildErr(err error, msg string) {
|
||||
@@ -736,7 +742,6 @@ func (c *commandeer) handleBuildErr(err error, msg string) {
|
||||
}
|
||||
|
||||
func (c *commandeer) rebuildSites(events []fsnotify.Event) error {
|
||||
|
||||
c.buildErr = nil
|
||||
visited := c.visitedURLs.PeekAllSet()
|
||||
if c.fastRenderMode {
|
||||
@@ -750,7 +755,7 @@ func (c *commandeer) rebuildSites(events []fsnotify.Event) error {
|
||||
visited[home] = true
|
||||
}
|
||||
}
|
||||
return c.hugo().Build(hugolib.BuildCfg{RecentlyVisited: visited, ErrRecovery: c.wasError}, events...)
|
||||
return c.hugo().Build(hugolib.BuildCfg{NoBuildLock: true, RecentlyVisited: visited, ErrRecovery: c.wasError}, events...)
|
||||
}
|
||||
|
||||
func (c *commandeer) partialReRender(urls ...string) error {
|
||||
@@ -762,7 +767,9 @@ func (c *commandeer) partialReRender(urls ...string) error {
|
||||
for _, url := range urls {
|
||||
visited[url] = true
|
||||
}
|
||||
return c.hugo().Build(hugolib.BuildCfg{RecentlyVisited: visited, PartialReRender: true, ErrRecovery: c.wasError})
|
||||
|
||||
// Note: We do not set NoBuildLock as the file lock is not acquired at this stage.
|
||||
return c.hugo().Build(hugolib.BuildCfg{NoBuildLock: false, RecentlyVisited: visited, PartialReRender: true, ErrRecovery: c.wasError})
|
||||
}
|
||||
|
||||
func (c *commandeer) fullRebuild(changeType string) {
|
||||
@@ -809,7 +816,7 @@ func (c *commandeer) fullRebuild(changeType string) {
|
||||
return
|
||||
}
|
||||
|
||||
err = c.buildSites()
|
||||
err = c.buildSites(true)
|
||||
if err != nil {
|
||||
c.logger.Errorln(err)
|
||||
} else if !c.h.buildWatch && !c.Cfg.GetBool("disableLiveReload") {
|
||||
@@ -840,6 +847,10 @@ func (c *commandeer) newWatcher(pollIntervalStr string, dirList ...string) (*wat
|
||||
c.logger.Printf("Use watcher with poll interval %v", pollInterval)
|
||||
}
|
||||
|
||||
if pollInterval == 0 {
|
||||
pollInterval = 500 * time.Millisecond
|
||||
}
|
||||
|
||||
watcher, err := watcher.New(500*time.Millisecond, pollInterval, poll)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -864,13 +875,19 @@ func (c *commandeer) newWatcher(pollIntervalStr string, dirList ...string) (*wat
|
||||
for {
|
||||
select {
|
||||
case evs := <-watcher.Events:
|
||||
unlock, err := c.buildLock()
|
||||
if err != nil {
|
||||
c.logger.Errorln("Failed to acquire a build lock: %s", err)
|
||||
return
|
||||
}
|
||||
c.handleEvents(watcher, staticSyncer, evs, configSet)
|
||||
if c.showErrorInBrowser && c.errCount() > 0 {
|
||||
// Need to reload browser to show the error
|
||||
livereload.ForceRefresh()
|
||||
}
|
||||
unlock()
|
||||
case err := <-watcher.Errors():
|
||||
if err != nil {
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
c.logger.Errorln("Error while watching:", err)
|
||||
}
|
||||
}
|
||||
@@ -1193,12 +1210,16 @@ func partitionDynamicEvents(sourceFs *filesystems.SourceFilesystems, events []fs
|
||||
func pickOneWriteOrCreatePath(events []fsnotify.Event) string {
|
||||
name := ""
|
||||
|
||||
// Some editors (for example notepad.exe on Windows) triggers a change
|
||||
// both for directory and file. So we pick the longest path, which should
|
||||
// be the file itself.
|
||||
for _, ev := range events {
|
||||
if (ev.Op&fsnotify.Write == fsnotify.Write || ev.Op&fsnotify.Create == fsnotify.Create) && len(ev.Name) > len(name) {
|
||||
name = ev.Name
|
||||
if ev.Op&fsnotify.Write == fsnotify.Write || ev.Op&fsnotify.Create == fsnotify.Create {
|
||||
if files.IsIndexContentFile(ev.Name) {
|
||||
return ev.Name
|
||||
}
|
||||
|
||||
if files.IsContentFile(ev.Name) {
|
||||
name = ev.Name
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -71,11 +71,12 @@ Also note that if you configure a positive maxAge for the "modules" file cache,
|
||||
if all {
|
||||
com, err := c.initConfig(false)
|
||||
|
||||
if err != nil && !moduleNotFoundRe.MatchString(err.Error()) {
|
||||
if err != nil && com == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = com.hugo().FileCaches.ModulesCache().Prune(true)
|
||||
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 {
|
||||
@@ -95,7 +96,7 @@ func (b *commandsBuilder) newModCmd() *modCmd {
|
||||
|
||||
const commonUsage = `
|
||||
Note that Hugo will always start out by resolving the components defined in the site
|
||||
configuration, provided by a _vendor directory (if no --ignoreVendor flag provided),
|
||||
configuration, provided by a _vendor directory (if no --ignoreVendorPaths flag provided),
|
||||
Go Modules, or a folder inside the themes directory, in that order.
|
||||
|
||||
See https://gohugo.io/hugo-modules/ for more information.
|
||||
|
||||
+1
-11
@@ -80,17 +80,7 @@ func (n *newCmd) newContent(cmd *cobra.Command, args []string) error {
|
||||
return newUserError("path needs to be provided")
|
||||
}
|
||||
|
||||
createPath := args[0]
|
||||
|
||||
var kind string
|
||||
|
||||
createPath, kind = newContentPathSection(c.hugo(), createPath)
|
||||
|
||||
if n.contentType != "" {
|
||||
kind = n.contentType
|
||||
}
|
||||
|
||||
return create.NewContent(c.hugo(), kind, createPath)
|
||||
return create.NewContent(c.hugo(), n.contentType, args[0])
|
||||
}
|
||||
|
||||
func mkdir(x ...string) {
|
||||
|
||||
@@ -52,7 +52,7 @@ Use ` + "`hugo new [contentPath]`" + ` to create new content.`,
|
||||
RunE: cc.newSite,
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&cc.configFormat, "format", "f", "toml", "config & frontmatter format")
|
||||
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)
|
||||
@@ -102,7 +102,7 @@ func (n *newSiteCmd) doNewSite(fs *hugofs.Fs, basepath string, force bool) error
|
||||
|
||||
// Create a default archetype file.
|
||||
helpers.SafeWriteToDisk(filepath.Join(archeTypePath, "default.md"),
|
||||
strings.NewReader(create.ArchetypeTemplateTemplate), fs.Source)
|
||||
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())
|
||||
|
||||
+38
-11
@@ -29,12 +29,33 @@ import (
|
||||
)
|
||||
|
||||
func TestServer(t *testing.T) {
|
||||
if isWindowsCI() {
|
||||
// TODO(bep) not sure why server tests have started to fail on the Windows CI server.
|
||||
t.Skip("Skip server test on appveyor")
|
||||
}
|
||||
c := qt.New(t)
|
||||
dir, clean, err := createSimpleTestSite(t, testSiteConfig{})
|
||||
|
||||
homeContent, err := runServerTestAndGetHome(c, "")
|
||||
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(homeContent, qt.Contains, "List: Hugo Commands")
|
||||
c.Assert(homeContent, qt.Contains, "Environment: development")
|
||||
}
|
||||
|
||||
// Issue 9518
|
||||
func TestServerPanicOnConfigError(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
config := `
|
||||
[markup]
|
||||
[markup.highlight]
|
||||
linenos='table'
|
||||
`
|
||||
|
||||
_, err := runServerTestAndGetHome(c, config)
|
||||
|
||||
c.Assert(err, qt.IsNotNil)
|
||||
c.Assert(err.Error(), qt.Contains, "cannot parse 'Highlight.LineNos' as bool:")
|
||||
}
|
||||
|
||||
func runServerTestAndGetHome(c *qt.C, config string) (string, error) {
|
||||
dir, clean, err := createSimpleTestSite(c, testSiteConfig{configTOML: config})
|
||||
defer clean()
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
@@ -45,6 +66,7 @@ func TestServer(t *testing.T) {
|
||||
os.RemoveAll(dir)
|
||||
}()
|
||||
|
||||
errors := make(chan error)
|
||||
stop := make(chan bool)
|
||||
|
||||
b := newCommandsBuilder()
|
||||
@@ -54,25 +76,30 @@ func TestServer(t *testing.T) {
|
||||
cmd.SetArgs([]string{"-s=" + dir, fmt.Sprintf("-p=%d", port)})
|
||||
|
||||
go func() {
|
||||
_, err = cmd.ExecuteC()
|
||||
c.Assert(err, qt.IsNil)
|
||||
_, err := cmd.ExecuteC()
|
||||
if err != nil {
|
||||
errors <- err
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
// There is no way to know exactly when the server is ready for connections.
|
||||
// We could improve by something like https://golang.org/pkg/net/http/httptest/#Server
|
||||
// But for now, let us sleep and pray!
|
||||
time.Sleep(2 * time.Second)
|
||||
case <-time.After(2 * time.Second):
|
||||
case err := <-errors:
|
||||
return "", err
|
||||
}
|
||||
|
||||
resp, err := http.Get("http://localhost:1331/")
|
||||
c.Assert(err, qt.IsNil)
|
||||
defer resp.Body.Close()
|
||||
homeContent := helpers.ReaderToString(resp.Body)
|
||||
|
||||
c.Assert(homeContent, qt.Contains, "List: Hugo Commands")
|
||||
c.Assert(homeContent, qt.Contains, "Environment: development")
|
||||
|
||||
// Stop the server.
|
||||
stop <- true
|
||||
|
||||
return homeContent, nil
|
||||
}
|
||||
|
||||
func TestFixURL(t *testing.T) {
|
||||
|
||||
@@ -64,3 +64,13 @@ func Slice(args ...interface{}) interface{} {
|
||||
}
|
||||
return slice.Interface()
|
||||
}
|
||||
|
||||
// StringSliceToInterfaceSlice converts ss to []interface{}.
|
||||
func StringSliceToInterfaceSlice(ss []string) []interface{} {
|
||||
result := make([]interface{}, len(ss))
|
||||
for i, s := range ss {
|
||||
result[i] = s
|
||||
}
|
||||
return result
|
||||
|
||||
}
|
||||
|
||||
@@ -88,3 +88,10 @@ func GetGID() uint64 {
|
||||
// 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")
|
||||
|
||||
// Must panics if err != nil.
|
||||
func Must(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
// 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 hexec
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/cli/safeexec"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/config/security"
|
||||
)
|
||||
|
||||
var WithDir = func(dir string) func(c *commandeer) {
|
||||
return func(c *commandeer) {
|
||||
c.dir = dir
|
||||
}
|
||||
}
|
||||
|
||||
var WithContext = func(ctx context.Context) func(c *commandeer) {
|
||||
return func(c *commandeer) {
|
||||
c.ctx = ctx
|
||||
}
|
||||
}
|
||||
|
||||
var WithStdout = func(w io.Writer) func(c *commandeer) {
|
||||
return func(c *commandeer) {
|
||||
c.stdout = w
|
||||
}
|
||||
}
|
||||
|
||||
var WithStderr = func(w io.Writer) func(c *commandeer) {
|
||||
return func(c *commandeer) {
|
||||
c.stderr = w
|
||||
}
|
||||
}
|
||||
|
||||
var WithStdin = func(r io.Reader) func(c *commandeer) {
|
||||
return func(c *commandeer) {
|
||||
c.stdin = r
|
||||
}
|
||||
}
|
||||
|
||||
var WithEnviron = func(env []string) func(c *commandeer) {
|
||||
return func(c *commandeer) {
|
||||
setOrAppend := func(s string) {
|
||||
k1, _ := config.SplitEnvVar(s)
|
||||
var found bool
|
||||
for i, v := range c.env {
|
||||
k2, _ := config.SplitEnvVar(v)
|
||||
if k1 == k2 {
|
||||
found = true
|
||||
c.env[i] = s
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
c.env = append(c.env, s)
|
||||
}
|
||||
}
|
||||
|
||||
for _, s := range env {
|
||||
setOrAppend(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// New creates a new Exec using the provided security config.
|
||||
func New(cfg security.Config) *Exec {
|
||||
var baseEnviron []string
|
||||
for _, v := range os.Environ() {
|
||||
k, _ := config.SplitEnvVar(v)
|
||||
if cfg.Exec.OsEnv.Accept(k) {
|
||||
baseEnviron = append(baseEnviron, v)
|
||||
}
|
||||
}
|
||||
|
||||
return &Exec{
|
||||
sc: cfg,
|
||||
baseEnviron: baseEnviron,
|
||||
}
|
||||
}
|
||||
|
||||
// IsNotFound reports whether this is an error about a binary not found.
|
||||
func IsNotFound(err error) bool {
|
||||
var notFoundErr *NotFoundError
|
||||
return errors.As(err, ¬FoundErr)
|
||||
}
|
||||
|
||||
// SafeCommand is a wrapper around os/exec Command which uses a LookPath
|
||||
// implementation that does not search in current directory before looking in PATH.
|
||||
// See https://github.com/cli/safeexec and the linked issues.
|
||||
func SafeCommand(name string, arg ...string) (*exec.Cmd, error) {
|
||||
bin, err := safeexec.LookPath(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return exec.Command(bin, arg...), nil
|
||||
}
|
||||
|
||||
// Exec encorces a security policy for commands run via os/exec.
|
||||
type Exec struct {
|
||||
sc security.Config
|
||||
|
||||
// os.Environ filtered by the Exec.OsEnviron whitelist filter.
|
||||
baseEnviron []string
|
||||
}
|
||||
|
||||
// New will fail if name is not allowed according to the configured security policy.
|
||||
// Else a configured Runner will be returned ready to be Run.
|
||||
func (e *Exec) New(name string, arg ...interface{}) (Runner, error) {
|
||||
if err := e.sc.CheckAllowedExec(name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
env := make([]string, len(e.baseEnviron))
|
||||
copy(env, e.baseEnviron)
|
||||
|
||||
cm := &commandeer{
|
||||
name: name,
|
||||
env: env,
|
||||
}
|
||||
|
||||
return cm.command(arg...)
|
||||
|
||||
}
|
||||
|
||||
// Npx is a convenience method to create a Runner running npx --no-install <name> <args.
|
||||
func (e *Exec) Npx(name string, arg ...interface{}) (Runner, error) {
|
||||
arg = append(arg[:0], append([]interface{}{"--no-install", name}, arg[0:]...)...)
|
||||
return e.New("npx", arg...)
|
||||
}
|
||||
|
||||
// Sec returns the security policies this Exec is configured with.
|
||||
func (e *Exec) Sec() security.Config {
|
||||
return e.sc
|
||||
}
|
||||
|
||||
type NotFoundError struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (e *NotFoundError) Error() string {
|
||||
return fmt.Sprintf("binary with name %q not found", e.name)
|
||||
}
|
||||
|
||||
// Runner wraps a *os.Cmd.
|
||||
type Runner interface {
|
||||
Run() error
|
||||
StdinPipe() (io.WriteCloser, error)
|
||||
}
|
||||
|
||||
type cmdWrapper struct {
|
||||
name string
|
||||
c *exec.Cmd
|
||||
|
||||
outerr *bytes.Buffer
|
||||
}
|
||||
|
||||
var notFoundRe = regexp.MustCompile(`(?s)not found:|could not determine executable`)
|
||||
|
||||
func (c *cmdWrapper) Run() error {
|
||||
err := c.c.Run()
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if notFoundRe.MatchString(c.outerr.String()) {
|
||||
return &NotFoundError{name: c.name}
|
||||
}
|
||||
return fmt.Errorf("failed to execute binary %q with args %v: %s", c.name, c.c.Args[1:], c.outerr.String())
|
||||
}
|
||||
|
||||
func (c *cmdWrapper) StdinPipe() (io.WriteCloser, error) {
|
||||
return c.c.StdinPipe()
|
||||
}
|
||||
|
||||
type commandeer struct {
|
||||
stdout io.Writer
|
||||
stderr io.Writer
|
||||
stdin io.Reader
|
||||
dir string
|
||||
ctx context.Context
|
||||
|
||||
name string
|
||||
env []string
|
||||
}
|
||||
|
||||
func (c *commandeer) command(arg ...interface{}) (*cmdWrapper, error) {
|
||||
if c == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var args []string
|
||||
for _, a := range arg {
|
||||
switch v := a.(type) {
|
||||
case string:
|
||||
args = append(args, v)
|
||||
case func(*commandeer):
|
||||
v(c)
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid argument to command: %T", a)
|
||||
}
|
||||
}
|
||||
|
||||
bin, err := safeexec.LookPath(c.name)
|
||||
if err != nil {
|
||||
return nil, &NotFoundError{
|
||||
name: c.name,
|
||||
}
|
||||
}
|
||||
|
||||
outerr := &bytes.Buffer{}
|
||||
if c.stderr == nil {
|
||||
c.stderr = outerr
|
||||
} else {
|
||||
c.stderr = io.MultiWriter(c.stderr, outerr)
|
||||
}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
|
||||
if c.ctx != nil {
|
||||
cmd = exec.CommandContext(c.ctx, bin, args...)
|
||||
} else {
|
||||
cmd = exec.Command(bin, args...)
|
||||
}
|
||||
|
||||
cmd.Stdin = c.stdin
|
||||
cmd.Stderr = c.stderr
|
||||
cmd.Stdout = c.stdout
|
||||
cmd.Env = c.env
|
||||
cmd.Dir = c.dir
|
||||
|
||||
return &cmdWrapper{outerr: outerr, c: cmd, name: c.name}, nil
|
||||
}
|
||||
|
||||
// InPath reports whether binaryName is in $PATH.
|
||||
func InPath(binaryName string) bool {
|
||||
if strings.Contains(binaryName, "/") {
|
||||
panic("binary name should not contain any slash")
|
||||
}
|
||||
_, err := safeexec.LookPath(binaryName)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// LookPath finds the path to binaryName in $PATH.
|
||||
// Returns "" if not found.
|
||||
func LookPath(binaryName string) string {
|
||||
if strings.Contains(binaryName, "/") {
|
||||
panic("binary name should not contain any slash")
|
||||
}
|
||||
s, err := safeexec.LookPath(binaryName)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return s
|
||||
}
|
||||
@@ -1,45 +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 hexec
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"os/exec"
|
||||
|
||||
"github.com/cli/safeexec"
|
||||
)
|
||||
|
||||
// SafeCommand is a wrapper around os/exec Command which uses a LookPath
|
||||
// implementation that does not search in current directory before looking in PATH.
|
||||
// See https://github.com/cli/safeexec and the linked issues.
|
||||
func SafeCommand(name string, arg ...string) (*exec.Cmd, error) {
|
||||
bin, err := safeexec.LookPath(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return exec.Command(bin, arg...), nil
|
||||
}
|
||||
|
||||
// SafeCommandContext wraps CommandContext
|
||||
// See SafeCommand for more context.
|
||||
func SafeCommandContext(ctx context.Context, name string, arg ...string) (*exec.Cmd, error) {
|
||||
bin, err := safeexec.LookPath(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return exec.CommandContext(ctx, bin, arg...), nil
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
package hreflect
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
@@ -124,3 +125,5 @@ func indirectInterface(v reflect.Value) reflect.Value {
|
||||
}
|
||||
return v.Elem()
|
||||
}
|
||||
|
||||
var ContextInterface = reflect.TypeOf((*context.Context)(nil)).Elem()
|
||||
|
||||
+11
-2
@@ -123,9 +123,13 @@ func (f TimeFormatter) Format(t time.Time, layout string) string {
|
||||
dayIdx := t.Weekday()
|
||||
|
||||
s = strings.ReplaceAll(s, longMonthNames[monthIdx], f.ltr.MonthWide(t.Month()))
|
||||
s = strings.ReplaceAll(s, shortMonthNames[monthIdx], f.ltr.MonthAbbreviated(t.Month()))
|
||||
if !strings.Contains(s, f.ltr.MonthWide(t.Month())) {
|
||||
s = strings.ReplaceAll(s, shortMonthNames[monthIdx], f.ltr.MonthAbbreviated(t.Month()))
|
||||
}
|
||||
s = strings.ReplaceAll(s, longDayNames[dayIdx], f.ltr.WeekdayWide(t.Weekday()))
|
||||
s = strings.ReplaceAll(s, shortDayNames[dayIdx], f.ltr.WeekdayAbbreviated(t.Weekday()))
|
||||
if !strings.Contains(s, f.ltr.WeekdayWide(t.Weekday())) {
|
||||
s = strings.ReplaceAll(s, shortDayNames[dayIdx], f.ltr.WeekdayAbbreviated(t.Weekday()))
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
@@ -136,6 +140,11 @@ func ToTimeInDefaultLocationE(i interface{}, location *time.Location) (tim time.
|
||||
return vv.AsTime(location), nil
|
||||
case toml.LocalDateTime:
|
||||
return vv.AsTime(location), nil
|
||||
// issue #8895
|
||||
// datetimes parsed by `go-toml` have empty zone name
|
||||
// convert back them into string and use `cast`
|
||||
case time.Time:
|
||||
i = vv.Format(time.RFC3339)
|
||||
}
|
||||
return cast.ToTimeInDefaultLocationE(i, location)
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
translators "github.com/gohugoio/localescompressed"
|
||||
qt "github.com/frankban/quicktest"
|
||||
translators "github.com/gohugoio/localescompressed"
|
||||
)
|
||||
|
||||
func TestTimeFormatter(t *testing.T) {
|
||||
@@ -27,6 +27,12 @@ func TestTimeFormatter(t *testing.T) {
|
||||
june06, _ := time.Parse("2006-Jan-02", "2018-Jun-06")
|
||||
june06 = june06.Add(7777 * time.Second)
|
||||
|
||||
jan06, _ := time.Parse("2006-Jan-02", "2018-Jan-06")
|
||||
jan06 = jan06.Add(32 * time.Second)
|
||||
|
||||
mondayNovemberFirst, _ := time.Parse("2006-Jan-02", "2021-11-01")
|
||||
mondayNovemberFirst = mondayNovemberFirst.Add(33 * time.Second)
|
||||
|
||||
c.Run("Norsk nynorsk", func(c *qt.C) {
|
||||
f := NewTimeFormatter(translators.GetTranslator("nn"))
|
||||
|
||||
@@ -73,6 +79,37 @@ func TestTimeFormatter(t *testing.T) {
|
||||
c.Assert(f.Format(june06, "Mon Mon"), qt.Equals, "Wed Wed")
|
||||
})
|
||||
|
||||
c.Run("Weekdays German", func(c *qt.C) {
|
||||
tr := translators.GetTranslator("de")
|
||||
f := NewTimeFormatter(tr)
|
||||
|
||||
// Issue #9107
|
||||
for i, weekDayWideGerman := range []string{"Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag"} {
|
||||
date := mondayNovemberFirst.Add(time.Duration(i*24) * time.Hour)
|
||||
c.Assert(tr.WeekdayWide(date.Weekday()), qt.Equals, weekDayWideGerman)
|
||||
c.Assert(f.Format(date, "Monday"), qt.Equals, weekDayWideGerman)
|
||||
}
|
||||
|
||||
for i, weekDayAbbreviatedGerman := range []string{"Mo.", "Di.", "Mi.", "Do.", "Fr.", "Sa.", "So."} {
|
||||
date := mondayNovemberFirst.Add(time.Duration(i*24) * time.Hour)
|
||||
c.Assert(tr.WeekdayAbbreviated(date.Weekday()), qt.Equals, weekDayAbbreviatedGerman)
|
||||
c.Assert(f.Format(date, "Mon"), qt.Equals, weekDayAbbreviatedGerman)
|
||||
}
|
||||
})
|
||||
|
||||
c.Run("Months German", func(c *qt.C) {
|
||||
tr := translators.GetTranslator("de")
|
||||
f := NewTimeFormatter(tr)
|
||||
|
||||
// Issue #9107
|
||||
for i, monthWideNorway := range []string{"Januar", "Februar", "März", "April", "Mai", "Juni", "Juli"} {
|
||||
date := jan06.Add(time.Duration(i*24*31) * time.Hour)
|
||||
c.Assert(tr.MonthWide(date.Month()), qt.Equals, monthWideNorway)
|
||||
c.Assert(f.Format(date, "January"), qt.Equals, monthWideNorway)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func BenchmarkTimeFormatter(b *testing.B) {
|
||||
|
||||
@@ -31,6 +31,11 @@ type ReadSeekCloser interface {
|
||||
io.Closer
|
||||
}
|
||||
|
||||
// ReadSeekCloserProvider provides a ReadSeekCloser.
|
||||
type ReadSeekCloserProvider interface {
|
||||
ReadSeekCloser() (ReadSeekCloser, error)
|
||||
}
|
||||
|
||||
// ReadSeekerNoOpCloser implements ReadSeekCloser by doing nothing in Close.
|
||||
// TODO(bep) rename this and similar to ReadSeekerNopCloser, naming used in stdlib, which kind of makes sense.
|
||||
type ReadSeekerNoOpCloser struct {
|
||||
|
||||
@@ -18,6 +18,14 @@ import (
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// As implemented by strings.Builder.
|
||||
type FlexiWriter interface {
|
||||
io.Writer
|
||||
io.ByteWriter
|
||||
WriteString(s string) (int, error)
|
||||
WriteRune(r rune) (int, error)
|
||||
}
|
||||
|
||||
type multiWriteCloser struct {
|
||||
io.Writer
|
||||
closers []io.WriteCloser
|
||||
|
||||
+47
-8
@@ -21,6 +21,7 @@ import (
|
||||
"runtime/debug"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
|
||||
@@ -57,6 +58,8 @@ type Info struct {
|
||||
// This can also be set by the user.
|
||||
// It can be any string, but it will be all lower case.
|
||||
Environment string
|
||||
|
||||
deps []*Dependency
|
||||
}
|
||||
|
||||
// Version returns the current version as a comparable version string.
|
||||
@@ -77,8 +80,13 @@ func (i Info) IsExtended() bool {
|
||||
return IsExtended
|
||||
}
|
||||
|
||||
// Deps gets a list of dependencies for this Hugo build.
|
||||
func (i Info) Deps() []*Dependency {
|
||||
return i.deps
|
||||
}
|
||||
|
||||
// NewInfo creates a new Hugo Info object.
|
||||
func NewInfo(environment string) Info {
|
||||
func NewInfo(environment string, deps []*Dependency) Info {
|
||||
if environment == "" {
|
||||
environment = EnvironmentProduction
|
||||
}
|
||||
@@ -86,11 +94,14 @@ func NewInfo(environment string) Info {
|
||||
CommitHash: commitHash,
|
||||
BuildDate: buildDate,
|
||||
Environment: environment,
|
||||
deps: deps,
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
env := os.Environ()
|
||||
var env []string
|
||||
nodepath := filepath.Join(workDir, "node_modules")
|
||||
if np := os.Getenv("NODE_PATH"); np != "" {
|
||||
nodepath = workDir + string(os.PathListSeparator) + np
|
||||
@@ -98,12 +109,16 @@ 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"))
|
||||
fis, err := afero.ReadDir(fs, files.FolderJSConfig)
|
||||
if err == nil {
|
||||
for _, fi := range fis {
|
||||
key := fmt.Sprintf("HUGO_FILE_%s", strings.ReplaceAll(strings.ToUpper(fi.Name()), ".", "_"))
|
||||
value := fi.(hugofs.FileMetaInfo).Meta().Filename
|
||||
config.SetEnvVars(&env, key, value)
|
||||
config.SetEnvVars(&env, "HUGO_ENV", cfg.GetString("environment"))
|
||||
|
||||
if fs != nil {
|
||||
fis, err := afero.ReadDir(fs, files.FolderJSConfig)
|
||||
if err == nil {
|
||||
for _, fi := range fis {
|
||||
key := fmt.Sprintf("HUGO_FILE_%s", strings.ReplaceAll(strings.ToUpper(fi.Name()), ".", "_"))
|
||||
value := fi.(hugofs.FileMetaInfo).Meta().Filename
|
||||
config.SetEnvVars(&env, key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,3 +166,27 @@ func IsRunningAsTest() bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Dependency is a single dependency, which can be either a Hugo Module or a local theme.
|
||||
type Dependency struct {
|
||||
// Returns the path to this module.
|
||||
// This will either be the module path, e.g. "github.com/gohugoio/myshortcodes",
|
||||
// or the path below your /theme folder, e.g. "mytheme".
|
||||
Path string
|
||||
|
||||
// The module version.
|
||||
Version string
|
||||
|
||||
// Whether this dependency is vendored.
|
||||
Vendor bool
|
||||
|
||||
// Time version was created.
|
||||
Time time.Time
|
||||
|
||||
// In the dependency tree, this is the first module that defines this module
|
||||
// as a dependency.
|
||||
Owner *Dependency
|
||||
|
||||
// Replaced by this dependency.
|
||||
Replace *Dependency
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
func TestHugoInfo(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
hugoInfo := NewInfo("")
|
||||
hugoInfo := NewInfo("", nil)
|
||||
|
||||
c.Assert(hugoInfo.Version(), qt.Equals, CurrentVersion.Version())
|
||||
c.Assert(fmt.Sprintf("%T", VersionString("")), qt.Equals, fmt.Sprintf("%T", hugoInfo.Version()))
|
||||
@@ -34,6 +34,6 @@ func TestHugoInfo(t *testing.T) {
|
||||
c.Assert(hugoInfo.IsProduction(), qt.Equals, true)
|
||||
c.Assert(hugoInfo.IsExtended(), qt.Equals, IsExtended)
|
||||
|
||||
devHugoInfo := NewInfo("development")
|
||||
devHugoInfo := NewInfo("development", nil)
|
||||
c.Assert(devHugoInfo.IsProduction(), qt.Equals, false)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ package hugo
|
||||
// CurrentVersion represents the current build version.
|
||||
// This should be the only one.
|
||||
var CurrentVersion = Version{
|
||||
Number: 0.88,
|
||||
PatchLevel: 1,
|
||||
Number: 0.93,
|
||||
PatchLevel: 2,
|
||||
Suffix: "",
|
||||
}
|
||||
|
||||
@@ -29,8 +29,11 @@ import (
|
||||
jww "github.com/spf13/jwalterweatherman"
|
||||
)
|
||||
|
||||
// Counts ERROR logs to the global jww logger.
|
||||
var GlobalErrorCounter *jww.Counter
|
||||
var (
|
||||
// Counts ERROR logs to the global jww logger.
|
||||
GlobalErrorCounter *jww.Counter
|
||||
PanicOnWarning bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
GlobalErrorCounter = &jww.Counter{}
|
||||
@@ -130,12 +133,20 @@ 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 ...interface{}) {
|
||||
l.WARN.Printf(format, v...)
|
||||
if PanicOnWarning {
|
||||
panic(panicOnWarningMessage)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *logger) Warnln(v ...interface{}) {
|
||||
l.WARN.Println(v...)
|
||||
if PanicOnWarning {
|
||||
panic(panicOnWarningMessage)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *logger) Warn() *log.Logger {
|
||||
|
||||
@@ -1,31 +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 maps
|
||||
|
||||
import (
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
// GetString tries to get a value with key from map m and convert it to a string.
|
||||
// It will return an empty string if not found or if it cannot be convertd to a string.
|
||||
func GetString(m map[string]interface{}, key string) string {
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
v, found := m[key]
|
||||
if !found {
|
||||
return ""
|
||||
}
|
||||
return cast.ToString(v)
|
||||
}
|
||||
+1
-57
@@ -16,7 +16,6 @@ package paths
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@@ -36,8 +35,7 @@ type filepathPathBridge interface {
|
||||
Separator() string
|
||||
}
|
||||
|
||||
type filepathBridge struct {
|
||||
}
|
||||
type filepathBridge struct{}
|
||||
|
||||
func (filepathBridge) Base(in string) string {
|
||||
return filepath.Base(in)
|
||||
@@ -65,11 +63,6 @@ func (filepathBridge) Separator() string {
|
||||
|
||||
var fpb filepathBridge
|
||||
|
||||
// ToSlashTrimLeading is just a filepath.ToSlash with an added / prefix trimmer.
|
||||
func ToSlashTrimLeading(s string) string {
|
||||
return strings.TrimPrefix(filepath.ToSlash(s), "/")
|
||||
}
|
||||
|
||||
// MakeTitle converts the path given to a suitable title, trimming whitespace
|
||||
// and replacing hyphens with whitespace.
|
||||
func MakeTitle(inpath string) string {
|
||||
@@ -233,23 +226,6 @@ func GetRelativePath(path, base string) (final string, err error) {
|
||||
return name, nil
|
||||
}
|
||||
|
||||
// PathPrep prepares the path using the uglify setting to create paths on
|
||||
// either the form /section/name/index.html or /section/name.html.
|
||||
func PathPrep(ugly bool, in string) string {
|
||||
if ugly {
|
||||
return Uglify(in)
|
||||
}
|
||||
return PrettifyPath(in)
|
||||
}
|
||||
|
||||
// PrettifyPath is the same as PrettifyURLPath but for file paths.
|
||||
// /section/name.html becomes /section/name/index.html
|
||||
// /section/name/ becomes /section/name/index.html
|
||||
// /section/name/index.html becomes /section/name/index.html
|
||||
func PrettifyPath(in string) string {
|
||||
return prettifyPath(in, fpb)
|
||||
}
|
||||
|
||||
func prettifyPath(in string, b filepathPathBridge) string {
|
||||
if filepath.Ext(in) == "" {
|
||||
// /section/name/ -> /section/name/index.html
|
||||
@@ -278,35 +254,3 @@ func (n NamedSlice) String() string {
|
||||
}
|
||||
return fmt.Sprintf("%s%s{%s}", n.Name, FilePathSeparator, strings.Join(n.Slice, ","))
|
||||
}
|
||||
|
||||
// FindCWD returns the current working directory from where the Hugo
|
||||
// executable is run.
|
||||
func FindCWD() (string, error) {
|
||||
serverFile, err := filepath.Abs(os.Args[0])
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("can't get absolute path for executable: %v", err)
|
||||
}
|
||||
|
||||
path := filepath.Dir(serverFile)
|
||||
realFile, err := filepath.EvalSymlinks(serverFile)
|
||||
if err != nil {
|
||||
if _, err = os.Stat(serverFile + ".exe"); err == nil {
|
||||
realFile = filepath.Clean(serverFile + ".exe")
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil && realFile != serverFile {
|
||||
path = filepath.Dir(realFile)
|
||||
}
|
||||
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// AddTrailingSlash adds a trailing Unix styled slash (/) if not already
|
||||
// there.
|
||||
func AddTrailingSlash(path string) string {
|
||||
if !strings.HasSuffix(path, "/") {
|
||||
path += "/"
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
@@ -226,27 +226,3 @@ func TestFileAndExt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindCWD(t *testing.T) {
|
||||
type test struct {
|
||||
expectedDir string
|
||||
expectedErr error
|
||||
}
|
||||
|
||||
// cwd, _ := os.Getwd()
|
||||
data := []test{
|
||||
//{cwd, nil},
|
||||
// Commenting this out. It doesn't work properly.
|
||||
// There's a good reason why we don't use os.Getwd(), it doesn't actually work the way we want it to.
|
||||
// I really don't know a better way to test this function. - SPF 2014.11.04
|
||||
}
|
||||
for i, d := range data {
|
||||
dir, err := FindCWD()
|
||||
if d.expectedDir != dir {
|
||||
t.Errorf("Test %d failed. Expected %q but got %q", i, d.expectedDir, dir)
|
||||
}
|
||||
if d.expectedErr != err {
|
||||
t.Errorf("Test %d failed. Expected %q but got %q", i, d.expectedErr, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-59
@@ -18,12 +18,9 @@ import (
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/PuerkitoBio/purell"
|
||||
)
|
||||
|
||||
type pathBridge struct {
|
||||
}
|
||||
type pathBridge struct{}
|
||||
|
||||
func (pathBridge) Base(in string) string {
|
||||
return path.Base(in)
|
||||
@@ -51,51 +48,6 @@ func (pathBridge) Separator() string {
|
||||
|
||||
var pb pathBridge
|
||||
|
||||
func sanitizeURLWithFlags(in string, f purell.NormalizationFlags) string {
|
||||
s, err := purell.NormalizeURLString(in, f)
|
||||
if err != nil {
|
||||
return in
|
||||
}
|
||||
|
||||
// Temporary workaround for the bug fix and resulting
|
||||
// behavioral change in purell.NormalizeURLString():
|
||||
// a leading '/' was inadvertently added to relative links,
|
||||
// but no longer, see #878.
|
||||
//
|
||||
// I think the real solution is to allow Hugo to
|
||||
// make relative URL with relative path,
|
||||
// e.g. "../../post/hello-again/", as wished by users
|
||||
// in issues #157, #622, etc., without forcing
|
||||
// relative URLs to begin with '/'.
|
||||
// Once the fixes are in, let's remove this kludge
|
||||
// and restore SanitizeURL() to the way it was.
|
||||
// -- @anthonyfok, 2015-02-16
|
||||
//
|
||||
// Begin temporary kludge
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(u.Path) > 0 && !strings.HasPrefix(u.Path, "/") {
|
||||
u.Path = "/" + u.Path
|
||||
}
|
||||
return u.String()
|
||||
// End temporary kludge
|
||||
|
||||
// return s
|
||||
|
||||
}
|
||||
|
||||
// SanitizeURL sanitizes the input URL string.
|
||||
func SanitizeURL(in string) string {
|
||||
return sanitizeURLWithFlags(in, purell.FlagsSafe|purell.FlagRemoveTrailingSlash|purell.FlagRemoveDotSegments|purell.FlagRemoveDuplicateSlashes|purell.FlagRemoveUnnecessaryHostDots|purell.FlagRemoveEmptyPortSeparator)
|
||||
}
|
||||
|
||||
// SanitizeURLKeepTrailingSlash is the same as SanitizeURL, but will keep any trailing slash.
|
||||
func SanitizeURLKeepTrailingSlash(in string) string {
|
||||
return sanitizeURLWithFlags(in, purell.FlagsSafe|purell.FlagRemoveDotSegments|purell.FlagRemoveDuplicateSlashes|purell.FlagRemoveUnnecessaryHostDots|purell.FlagRemoveEmptyPortSeparator)
|
||||
}
|
||||
|
||||
// MakePermalink combines base URL with content path to create full URL paths.
|
||||
// Example
|
||||
// base: http://spf13.com/
|
||||
@@ -127,16 +79,6 @@ func MakePermalink(host, plink string) *url.URL {
|
||||
return base
|
||||
}
|
||||
|
||||
// IsAbsURL determines whether the given path points to an absolute URL.
|
||||
func IsAbsURL(path string) bool {
|
||||
url, err := url.Parse(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return url.IsAbs() || strings.HasPrefix(path, "//")
|
||||
}
|
||||
|
||||
// AddContextRoot adds the context root to an URL if it's not already set.
|
||||
// For relative URL entries on sites with a base url with a context root set (i.e. http://example.com/mysite),
|
||||
// relative URLs must not include the context root if canonifyURLs is enabled. But if it's disabled, it must be set.
|
||||
|
||||
@@ -14,41 +14,11 @@
|
||||
package paths
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
func TestSanitizeURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"http://foo.bar/", "http://foo.bar"},
|
||||
{"http://foo.bar", "http://foo.bar"}, // issue #1105
|
||||
{"http://foo.bar/zoo/", "http://foo.bar/zoo"}, // issue #931
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
o1 := SanitizeURL(test.input)
|
||||
o2 := SanitizeURLKeepTrailingSlash(test.input)
|
||||
|
||||
expected2 := test.expected
|
||||
|
||||
if strings.HasSuffix(test.input, "/") && !strings.HasSuffix(expected2, "/") {
|
||||
expected2 += "/"
|
||||
}
|
||||
|
||||
if o1 != test.expected {
|
||||
t.Errorf("[%d] 1: Expected %#v, got %#v\n", i, test.expected, o1)
|
||||
}
|
||||
if o2 != expected2 {
|
||||
t.Errorf("[%d] 2: Expected %#v, got %#v\n", i, expected2, o2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakePermalink(t *testing.T) {
|
||||
type test struct {
|
||||
host, link, output string
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
package text
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode"
|
||||
|
||||
@@ -45,3 +46,18 @@ func RemoveAccentsString(s string) string {
|
||||
accentTransformerPool.Put(t)
|
||||
return s
|
||||
}
|
||||
|
||||
// Chomp removes trailing newline characters from s.
|
||||
func Chomp(s string) string {
|
||||
return strings.TrimRightFunc(s, func(r rune) bool {
|
||||
return r == '\n' || r == '\r'
|
||||
})
|
||||
}
|
||||
|
||||
// Puts adds a trailing \n none found.
|
||||
func Puts(s string) string {
|
||||
if s == "" || s[len(s)-1] == '\n' {
|
||||
return s
|
||||
}
|
||||
return s + "\n"
|
||||
}
|
||||
|
||||
@@ -26,3 +26,18 @@ func TestRemoveAccents(t *testing.T) {
|
||||
c.Assert(string(RemoveAccents([]byte("Hugo Rocks!"))), qt.Equals, "Hugo Rocks!")
|
||||
c.Assert(string(RemoveAccentsString("Resumé")), qt.Equals, "Resume")
|
||||
}
|
||||
|
||||
func TestChomp(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
c.Assert(Chomp("\nA\n"), qt.Equals, "\nA")
|
||||
c.Assert(Chomp("A\r\n"), qt.Equals, "A")
|
||||
}
|
||||
|
||||
func TestPuts(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
c.Assert(Puts("A"), qt.Equals, "A\n")
|
||||
c.Assert(Puts("\nA\n"), qt.Equals, "\nA\n")
|
||||
c.Assert(Puts(""), qt.Equals, "")
|
||||
}
|
||||
|
||||
@@ -44,6 +44,8 @@ var (
|
||||
"permalinks": true,
|
||||
"related": true,
|
||||
"sitemap": true,
|
||||
"privacy": true,
|
||||
"security": true,
|
||||
"taxonomies": true,
|
||||
}
|
||||
|
||||
|
||||
@@ -21,8 +21,6 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/gohugoio/hugo/common/para"
|
||||
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
@@ -205,7 +203,6 @@ func TestDefaultConfigProvider(t *testing.T) {
|
||||
|
||||
// Issue #8679
|
||||
c.Run("Merge typed maps", func(c *qt.C) {
|
||||
|
||||
for _, left := range []interface{}{
|
||||
map[string]string{
|
||||
"c": "cv1",
|
||||
@@ -249,7 +246,6 @@ func TestDefaultConfigProvider(t *testing.T) {
|
||||
"b": "bv1",
|
||||
},
|
||||
} {
|
||||
|
||||
for _, right := range []interface{}{
|
||||
map[string]string{
|
||||
"b": "bv2",
|
||||
@@ -277,9 +273,7 @@ func TestDefaultConfigProvider(t *testing.T) {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
// Issue #8701
|
||||
@@ -397,13 +391,6 @@ func BenchmarkDefaultConfigProvider(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
b.Run("Viper", func(b *testing.B) {
|
||||
v := viper.New()
|
||||
for i := 0; i < b.N; i++ {
|
||||
runMethods(b, v)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("Custom", func(b *testing.B) {
|
||||
cfg := New()
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2018 The Hugo Authors. All rights reserved.
|
||||
// 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.
|
||||
@@ -11,25 +11,16 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build !darwin
|
||||
|
||||
package commands
|
||||
package security
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/gohugoio/hugo/docshelper"
|
||||
)
|
||||
|
||||
var _ cmder = (*checkCmd)(nil)
|
||||
func init() {
|
||||
docsProvider := func() docshelper.DocProvider {
|
||||
|
||||
type checkCmd struct {
|
||||
*baseCmd
|
||||
}
|
||||
|
||||
func newCheckCmd() *checkCmd {
|
||||
return &checkCmd{baseCmd: &baseCmd{
|
||||
cmd: &cobra.Command{
|
||||
Use: "check",
|
||||
Short: "Contains some verification checks",
|
||||
},
|
||||
}}
|
||||
return docshelper.DocProvider{"config": DefaultConfig.ToSecurityMap()}
|
||||
}
|
||||
docshelper.AddDocProviderFunc(docsProvider)
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
// 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 security
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/parser"
|
||||
"github.com/gohugoio/hugo/parser/metadecoders"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
const securityConfigKey = "security"
|
||||
|
||||
// DefaultConfig holds the default security policy.
|
||||
var DefaultConfig = Config{
|
||||
Exec: Exec{
|
||||
Allow: NewWhitelist(
|
||||
"^dart-sass-embedded$",
|
||||
"^go$", // for Go Modules
|
||||
"^npx$", // used by all Node tools (Babel, PostCSS).
|
||||
"^postcss$",
|
||||
),
|
||||
// These have been tested to work with Hugo's external programs
|
||||
// on Windows, Linux and MacOS.
|
||||
OsEnv: NewWhitelist("(?i)^(PATH|PATHEXT|APPDATA|TMP|TEMP|TERM)$"),
|
||||
},
|
||||
Funcs: Funcs{
|
||||
Getenv: NewWhitelist("^HUGO_"),
|
||||
},
|
||||
HTTP: HTTP{
|
||||
URLs: NewWhitelist(".*"),
|
||||
Methods: NewWhitelist("(?i)GET|POST"),
|
||||
},
|
||||
}
|
||||
|
||||
// Config is the top level security config.
|
||||
type Config struct {
|
||||
// Restricts access to os.Exec.
|
||||
Exec Exec `json:"exec"`
|
||||
|
||||
// Restricts access to certain template funcs.
|
||||
Funcs Funcs `json:"funcs"`
|
||||
|
||||
// Restricts access to resources.Get, getJSON, getCSV.
|
||||
HTTP HTTP `json:"http"`
|
||||
|
||||
// Allow inline shortcodes
|
||||
EnableInlineShortcodes bool `json:"enableInlineShortcodes"`
|
||||
}
|
||||
|
||||
// Exec holds os/exec policies.
|
||||
type Exec struct {
|
||||
Allow Whitelist `json:"allow"`
|
||||
OsEnv Whitelist `json:"osEnv"`
|
||||
}
|
||||
|
||||
// Funcs holds template funcs policies.
|
||||
type Funcs struct {
|
||||
// OS env keys allowed to query in os.Getenv.
|
||||
Getenv Whitelist `json:"getenv"`
|
||||
}
|
||||
|
||||
type HTTP struct {
|
||||
// URLs to allow in remote HTTP (resources.Get, getJSON, getCSV).
|
||||
URLs Whitelist `json:"urls"`
|
||||
|
||||
// HTTP methods to allow.
|
||||
Methods Whitelist `json:"methods"`
|
||||
}
|
||||
|
||||
// ToTOML converts c to TOML with [security] as the root.
|
||||
func (c Config) ToTOML() string {
|
||||
sec := c.ToSecurityMap()
|
||||
|
||||
var b bytes.Buffer
|
||||
|
||||
if err := parser.InterfaceToConfig(sec, metadecoders.TOML, &b); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return strings.TrimSpace(b.String())
|
||||
}
|
||||
|
||||
func (c Config) CheckAllowedExec(name string) error {
|
||||
if !c.Exec.Allow.Accept(name) {
|
||||
return &AccessDeniedError{
|
||||
name: name,
|
||||
path: "security.exec.allow",
|
||||
policies: c.ToTOML(),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (c Config) CheckAllowedGetEnv(name string) error {
|
||||
if !c.Funcs.Getenv.Accept(name) {
|
||||
return &AccessDeniedError{
|
||||
name: name,
|
||||
path: "security.funcs.getenv",
|
||||
policies: c.ToTOML(),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Config) CheckAllowedHTTPURL(url string) error {
|
||||
if !c.HTTP.URLs.Accept(url) {
|
||||
return &AccessDeniedError{
|
||||
name: url,
|
||||
path: "security.http.urls",
|
||||
policies: c.ToTOML(),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Config) CheckAllowedHTTPMethod(method string) error {
|
||||
if !c.HTTP.Methods.Accept(method) {
|
||||
return &AccessDeniedError{
|
||||
name: method,
|
||||
path: "security.http.method",
|
||||
policies: c.ToTOML(),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToSecurityMap converts c to a map with 'security' as the root key.
|
||||
func (c Config) ToSecurityMap() map[string]interface{} {
|
||||
// Take it to JSON and back to get proper casing etc.
|
||||
asJson, err := json.Marshal(c)
|
||||
herrors.Must(err)
|
||||
m := make(map[string]interface{})
|
||||
herrors.Must(json.Unmarshal(asJson, &m))
|
||||
|
||||
// Add the root
|
||||
sec := map[string]interface{}{
|
||||
"security": m,
|
||||
}
|
||||
return sec
|
||||
|
||||
}
|
||||
|
||||
// DecodeConfig creates a privacy Config from a given Hugo configuration.
|
||||
func DecodeConfig(cfg config.Provider) (Config, error) {
|
||||
sc := DefaultConfig
|
||||
if cfg.IsSet(securityConfigKey) {
|
||||
m := cfg.GetStringMap(securityConfigKey)
|
||||
dec, err := mapstructure.NewDecoder(
|
||||
&mapstructure.DecoderConfig{
|
||||
WeaklyTypedInput: true,
|
||||
Result: &sc,
|
||||
DecodeHook: stringSliceToWhitelistHook(),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return sc, err
|
||||
}
|
||||
|
||||
if err = dec.Decode(m); err != nil {
|
||||
return sc, err
|
||||
}
|
||||
}
|
||||
|
||||
if !sc.EnableInlineShortcodes {
|
||||
// Legacy
|
||||
sc.EnableInlineShortcodes = cfg.GetBool("enableInlineShortcodes")
|
||||
}
|
||||
|
||||
return sc, nil
|
||||
|
||||
}
|
||||
|
||||
func stringSliceToWhitelistHook() mapstructure.DecodeHookFuncType {
|
||||
return func(
|
||||
f reflect.Type,
|
||||
t reflect.Type,
|
||||
data interface{}) (interface{}, error) {
|
||||
|
||||
if t != reflect.TypeOf(Whitelist{}) {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
wl := types.ToStringSlicePreserveString(data)
|
||||
|
||||
return NewWhitelist(wl...), nil
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// AccessDeniedError represents a security policy conflict.
|
||||
type AccessDeniedError struct {
|
||||
path string
|
||||
name string
|
||||
policies string
|
||||
}
|
||||
|
||||
func (e *AccessDeniedError) Error() string {
|
||||
return fmt.Sprintf("access denied: %q is not whitelisted in policy %q; the current security configuration is:\n\n%s\n\n", e.name, e.path, e.policies)
|
||||
}
|
||||
|
||||
// IsAccessDenied reports whether err is an AccessDeniedError
|
||||
func IsAccessDenied(err error) bool {
|
||||
var notFoundErr *AccessDeniedError
|
||||
return errors.As(err, ¬FoundErr)
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
// 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 security
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
)
|
||||
|
||||
func TestDecodeConfigFromTOML(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
c.Run("Slice whitelist", func(c *qt.C) {
|
||||
c.Parallel()
|
||||
tomlConfig := `
|
||||
|
||||
|
||||
someOtherValue = "bar"
|
||||
|
||||
[security]
|
||||
enableInlineShortcodes=true
|
||||
[security.exec]
|
||||
allow=["a", "b"]
|
||||
osEnv=["a", "b", "c"]
|
||||
[security.funcs]
|
||||
getEnv=["a", "b"]
|
||||
|
||||
`
|
||||
|
||||
cfg, err := config.FromConfigString(tomlConfig, "toml")
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
pc, err := DecodeConfig(cfg)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(pc, qt.Not(qt.IsNil))
|
||||
c.Assert(pc.EnableInlineShortcodes, qt.IsTrue)
|
||||
c.Assert(pc.Exec.Allow.Accept("a"), qt.IsTrue)
|
||||
c.Assert(pc.Exec.Allow.Accept("d"), qt.IsFalse)
|
||||
c.Assert(pc.Exec.OsEnv.Accept("a"), qt.IsTrue)
|
||||
c.Assert(pc.Exec.OsEnv.Accept("e"), qt.IsFalse)
|
||||
c.Assert(pc.Funcs.Getenv.Accept("a"), qt.IsTrue)
|
||||
c.Assert(pc.Funcs.Getenv.Accept("c"), qt.IsFalse)
|
||||
|
||||
})
|
||||
|
||||
c.Run("String whitelist", func(c *qt.C) {
|
||||
c.Parallel()
|
||||
tomlConfig := `
|
||||
|
||||
|
||||
someOtherValue = "bar"
|
||||
|
||||
[security]
|
||||
[security.exec]
|
||||
allow="a"
|
||||
osEnv="b"
|
||||
|
||||
`
|
||||
|
||||
cfg, err := config.FromConfigString(tomlConfig, "toml")
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
pc, err := DecodeConfig(cfg)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(pc, qt.Not(qt.IsNil))
|
||||
c.Assert(pc.Exec.Allow.Accept("a"), qt.IsTrue)
|
||||
c.Assert(pc.Exec.Allow.Accept("d"), qt.IsFalse)
|
||||
c.Assert(pc.Exec.OsEnv.Accept("b"), qt.IsTrue)
|
||||
c.Assert(pc.Exec.OsEnv.Accept("e"), qt.IsFalse)
|
||||
|
||||
})
|
||||
|
||||
c.Run("Default exec.osEnv", func(c *qt.C) {
|
||||
c.Parallel()
|
||||
tomlConfig := `
|
||||
|
||||
|
||||
someOtherValue = "bar"
|
||||
|
||||
[security]
|
||||
[security.exec]
|
||||
allow="a"
|
||||
|
||||
`
|
||||
|
||||
cfg, err := config.FromConfigString(tomlConfig, "toml")
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
pc, err := DecodeConfig(cfg)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(pc, qt.Not(qt.IsNil))
|
||||
c.Assert(pc.Exec.Allow.Accept("a"), qt.IsTrue)
|
||||
c.Assert(pc.Exec.OsEnv.Accept("PATH"), qt.IsTrue)
|
||||
c.Assert(pc.Exec.OsEnv.Accept("e"), qt.IsFalse)
|
||||
|
||||
})
|
||||
|
||||
c.Run("Enable inline shortcodes, legacy", func(c *qt.C) {
|
||||
c.Parallel()
|
||||
tomlConfig := `
|
||||
|
||||
|
||||
someOtherValue = "bar"
|
||||
enableInlineShortcodes=true
|
||||
|
||||
[security]
|
||||
[security.exec]
|
||||
allow="a"
|
||||
osEnv="b"
|
||||
|
||||
`
|
||||
|
||||
cfg, err := config.FromConfigString(tomlConfig, "toml")
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
pc, err := DecodeConfig(cfg)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(pc.EnableInlineShortcodes, qt.IsTrue)
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestToTOML(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
got := DefaultConfig.ToTOML()
|
||||
|
||||
c.Assert(got, qt.Equals,
|
||||
"[security]\n enableInlineShortcodes = false\n [security.exec]\n allow = ['^dart-sass-embedded$', '^go$', '^npx$', '^postcss$']\n osEnv = ['(?i)^(PATH|PATHEXT|APPDATA|TMP|TEMP|TERM)$']\n\n [security.funcs]\n getenv = ['^HUGO_']\n\n [security.http]\n methods = ['(?i)GET|POST']\n urls = ['.*']",
|
||||
)
|
||||
}
|
||||
|
||||
func TestDecodeConfigDefault(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
pc, err := DecodeConfig(config.New())
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(pc, qt.Not(qt.IsNil))
|
||||
c.Assert(pc.Exec.Allow.Accept("a"), qt.IsFalse)
|
||||
c.Assert(pc.Exec.Allow.Accept("npx"), qt.IsTrue)
|
||||
c.Assert(pc.Exec.Allow.Accept("Npx"), qt.IsFalse)
|
||||
c.Assert(pc.Exec.OsEnv.Accept("a"), qt.IsFalse)
|
||||
c.Assert(pc.Exec.OsEnv.Accept("PATH"), qt.IsTrue)
|
||||
c.Assert(pc.Exec.OsEnv.Accept("e"), qt.IsFalse)
|
||||
|
||||
c.Assert(pc.HTTP.URLs.Accept("https://example.org"), qt.IsTrue)
|
||||
c.Assert(pc.HTTP.Methods.Accept("POST"), qt.IsTrue)
|
||||
c.Assert(pc.HTTP.Methods.Accept("GET"), qt.IsTrue)
|
||||
c.Assert(pc.HTTP.Methods.Accept("get"), qt.IsTrue)
|
||||
c.Assert(pc.HTTP.Methods.Accept("DELETE"), qt.IsFalse)
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// 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 security
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
acceptNoneKeyword = "none"
|
||||
)
|
||||
|
||||
// Whitelist holds a whitelist.
|
||||
type Whitelist struct {
|
||||
acceptNone bool
|
||||
patterns []*regexp.Regexp
|
||||
|
||||
// Store this for debugging/error reporting
|
||||
patternsStrings []string
|
||||
}
|
||||
|
||||
func (w Whitelist) MarshalJSON() ([]byte, error) {
|
||||
if w.acceptNone {
|
||||
return json.Marshal(acceptNoneKeyword)
|
||||
}
|
||||
|
||||
return json.Marshal(w.patternsStrings)
|
||||
}
|
||||
|
||||
// NewWhitelist creates a new Whitelist from zero or more patterns.
|
||||
// An empty patterns list or a pattern with the value 'none' will create
|
||||
// a whitelist that will Accept noone.
|
||||
func NewWhitelist(patterns ...string) Whitelist {
|
||||
if len(patterns) == 0 {
|
||||
return Whitelist{acceptNone: true}
|
||||
}
|
||||
|
||||
var acceptSome bool
|
||||
var patternsStrings []string
|
||||
|
||||
for _, p := range patterns {
|
||||
if p == acceptNoneKeyword {
|
||||
acceptSome = false
|
||||
break
|
||||
}
|
||||
|
||||
if ps := strings.TrimSpace(p); ps != "" {
|
||||
acceptSome = true
|
||||
patternsStrings = append(patternsStrings, ps)
|
||||
}
|
||||
}
|
||||
|
||||
if !acceptSome {
|
||||
return Whitelist{
|
||||
acceptNone: true,
|
||||
}
|
||||
}
|
||||
|
||||
var patternsr []*regexp.Regexp
|
||||
|
||||
for i := 0; i < len(patterns); i++ {
|
||||
p := strings.TrimSpace(patterns[i])
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
patternsr = append(patternsr, regexp.MustCompile(p))
|
||||
}
|
||||
|
||||
return Whitelist{patterns: patternsr, patternsStrings: patternsStrings}
|
||||
}
|
||||
|
||||
// Accepted reports whether name is whitelisted.
|
||||
func (w Whitelist) Accept(name string) bool {
|
||||
if w.acceptNone {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, p := range w.patterns {
|
||||
if p.MatchString(name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (w Whitelist) String() string {
|
||||
return fmt.Sprint(w.patternsStrings)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// 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 security
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
func TestWhitelist(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
c.Run("none", func(c *qt.C) {
|
||||
c.Assert(NewWhitelist("none", "foo").Accept("foo"), qt.IsFalse)
|
||||
c.Assert(NewWhitelist().Accept("foo"), qt.IsFalse)
|
||||
c.Assert(NewWhitelist("").Accept("foo"), qt.IsFalse)
|
||||
c.Assert(NewWhitelist(" ", " ").Accept("foo"), qt.IsFalse)
|
||||
c.Assert(Whitelist{}.Accept("foo"), qt.IsFalse)
|
||||
})
|
||||
|
||||
c.Run("One", func(c *qt.C) {
|
||||
w := NewWhitelist("^foo.*")
|
||||
c.Assert(w.Accept("foo"), qt.IsTrue)
|
||||
c.Assert(w.Accept("mfoo"), qt.IsFalse)
|
||||
})
|
||||
|
||||
c.Run("Multiple", func(c *qt.C) {
|
||||
w := NewWhitelist("^foo.*", "^bar.*")
|
||||
c.Assert(w.Accept("foo"), qt.IsTrue)
|
||||
c.Assert(w.Accept("bar"), qt.IsTrue)
|
||||
c.Assert(w.Accept("mbar"), qt.IsFalse)
|
||||
})
|
||||
|
||||
}
|
||||
+257
-214
@@ -16,16 +16,19 @@ package create
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs/glob"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hexec"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hexec"
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
@@ -33,125 +36,169 @@ import (
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
"github.com/spf13/afero"
|
||||
jww "github.com/spf13/jwalterweatherman"
|
||||
)
|
||||
|
||||
// NewContent creates a new content file in the content directory based upon the
|
||||
// given kind, which is used to lookup an archetype.
|
||||
func NewContent(
|
||||
sites *hugolib.HugoSites, kind, targetPath string) error {
|
||||
targetPath = filepath.Clean(targetPath)
|
||||
ext := paths.Ext(targetPath)
|
||||
ps := sites.PathSpec
|
||||
archetypeFs := ps.BaseFs.SourceFilesystems.Archetypes.Fs
|
||||
sourceFs := ps.Fs.Source
|
||||
const (
|
||||
// DefaultArchetypeTemplateTemplate is the template used in 'hugo new site'
|
||||
// and the template we use as a fall back.
|
||||
DefaultArchetypeTemplateTemplate = `---
|
||||
title: "{{ replace .Name "-" " " | title }}"
|
||||
date: {{ .Date }}
|
||||
draft: true
|
||||
---
|
||||
|
||||
jww.INFO.Printf("attempting to create %q of %q of ext %q", targetPath, kind, ext)
|
||||
`
|
||||
)
|
||||
|
||||
archetypeFilename, isDir := findArchetype(ps, kind, ext)
|
||||
contentPath, s := resolveContentPath(sites, sourceFs, targetPath)
|
||||
|
||||
if isDir {
|
||||
|
||||
langFs, err := hugofs.NewLanguageFs(sites.LanguageSet(), archetypeFs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cm, err := mapArcheTypeDir(ps, langFs, archetypeFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cm.siteUsed {
|
||||
if err := sites.Build(hugolib.BuildCfg{SkipRender: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
name := filepath.Base(targetPath)
|
||||
return newContentFromDir(archetypeFilename, sites, sourceFs, cm, name, contentPath)
|
||||
// NewContent creates a new content file in h (or a full bundle if the archetype is a directory)
|
||||
// in targetPath.
|
||||
func NewContent(h *hugolib.HugoSites, kind, targetPath string) error {
|
||||
if h.BaseFs.Content.Dirs == nil {
|
||||
return errors.New("no existing content directory configured for this project")
|
||||
}
|
||||
|
||||
// Building the sites can be expensive, so only do it if really needed.
|
||||
siteUsed := false
|
||||
|
||||
if archetypeFilename != "" {
|
||||
cf := hugolib.NewContentFactory(h)
|
||||
|
||||
if kind == "" {
|
||||
var err error
|
||||
siteUsed, err = usesSiteVar(archetypeFs, archetypeFilename)
|
||||
kind, err = cf.SectionFromFilename(targetPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if siteUsed {
|
||||
if err := sites.Build(hugolib.BuildCfg{SkipRender: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
b := &contentBuilder{
|
||||
archeTypeFs: h.PathSpec.BaseFs.Archetypes.Fs,
|
||||
sourceFs: h.PathSpec.Fs.Source,
|
||||
ps: h.PathSpec,
|
||||
h: h,
|
||||
cf: cf,
|
||||
|
||||
kind: kind,
|
||||
targetPath: targetPath,
|
||||
}
|
||||
|
||||
content, err := executeArcheTypeAsTemplate(s, "", kind, targetPath, archetypeFilename)
|
||||
ext := paths.Ext(targetPath)
|
||||
|
||||
b.setArcheTypeFilenameToUse(ext)
|
||||
|
||||
withBuildLock := func() (string, error) {
|
||||
unlock, err := h.BaseFs.LockBuild()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to acquire a build lock: %s", err)
|
||||
}
|
||||
defer unlock()
|
||||
|
||||
if b.isDir {
|
||||
return "", b.buildDir()
|
||||
}
|
||||
|
||||
if ext == "" {
|
||||
return "", errors.Errorf("failed to resolve %q to a archetype template", targetPath)
|
||||
}
|
||||
|
||||
if !files.IsContentFile(b.targetPath) {
|
||||
return "", errors.Errorf("target path %q is not a known content format", b.targetPath)
|
||||
}
|
||||
|
||||
return b.buildFile()
|
||||
|
||||
}
|
||||
|
||||
filename, err := withBuildLock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := helpers.SafeWriteToDisk(contentPath, bytes.NewReader(content), s.Fs.Source); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jww.FEEDBACK.Println(contentPath, "created")
|
||||
|
||||
editor := s.Cfg.GetString("newContentEditor")
|
||||
if editor != "" {
|
||||
jww.FEEDBACK.Printf("Editing %s with %q ...\n", targetPath, editor)
|
||||
|
||||
editorCmd := append(strings.Fields(editor), contentPath)
|
||||
cmd, err := hexec.SafeCommand(editorCmd[0], editorCmd[1:]...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
return cmd.Run()
|
||||
if filename != "" {
|
||||
return b.openInEditorIfConfigured(filename)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func targetSite(sites *hugolib.HugoSites, fi hugofs.FileMetaInfo) *hugolib.Site {
|
||||
for _, s := range sites.Sites {
|
||||
if fi.Meta().Lang == s.Language().Lang {
|
||||
return s
|
||||
type contentBuilder struct {
|
||||
archeTypeFs afero.Fs
|
||||
sourceFs afero.Fs
|
||||
|
||||
ps *helpers.PathSpec
|
||||
h *hugolib.HugoSites
|
||||
cf hugolib.ContentFactory
|
||||
|
||||
// Builder state
|
||||
archetypeFilename string
|
||||
targetPath string
|
||||
kind string
|
||||
isDir bool
|
||||
dirMap archetypeMap
|
||||
}
|
||||
|
||||
func (b *contentBuilder) buildDir() error {
|
||||
// Split the dir into content files and the rest.
|
||||
if err := b.mapArcheTypeDir(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var contentTargetFilenames []string
|
||||
var baseDir string
|
||||
|
||||
for _, fi := range b.dirMap.contentFiles {
|
||||
targetFilename := filepath.Join(b.targetPath, strings.TrimPrefix(fi.Meta().Path, b.archetypeFilename))
|
||||
abs, err := b.cf.CreateContentPlaceHolder(targetFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if baseDir == "" {
|
||||
baseDir = strings.TrimSuffix(abs, targetFilename)
|
||||
}
|
||||
|
||||
contentTargetFilenames = append(contentTargetFilenames, abs)
|
||||
}
|
||||
|
||||
var contentInclusionFilter *glob.FilenameFilter
|
||||
if !b.dirMap.siteUsed {
|
||||
// We don't need to build everything.
|
||||
contentInclusionFilter = glob.NewFilenameFilterForInclusionFunc(func(filename string) bool {
|
||||
filename = strings.TrimPrefix(filename, string(os.PathSeparator))
|
||||
for _, cn := range contentTargetFilenames {
|
||||
if strings.Contains(cn, filename) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
if err := b.h.Build(hugolib.BuildCfg{NoBuildLock: true, SkipRender: true, ContentInclusionFilter: contentInclusionFilter}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, filename := range contentTargetFilenames {
|
||||
if err := b.applyArcheType(filename, b.dirMap.contentFiles[i].Meta().Path); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return sites.Sites[0]
|
||||
}
|
||||
|
||||
func newContentFromDir(
|
||||
archetypeDir string,
|
||||
sites *hugolib.HugoSites,
|
||||
targetFs afero.Fs,
|
||||
cm archetypeMap, name, targetPath string) error {
|
||||
for _, f := range cm.otherFiles {
|
||||
// Copy the rest as is.
|
||||
for _, f := range b.dirMap.otherFiles {
|
||||
meta := f.Meta()
|
||||
filename := meta.Path
|
||||
// Just copy the file to destination.
|
||||
|
||||
in, err := meta.Open()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to open non-content file")
|
||||
}
|
||||
|
||||
targetFilename := filepath.Join(targetPath, strings.TrimPrefix(filename, archetypeDir))
|
||||
|
||||
targetFilename := filepath.Join(baseDir, b.targetPath, strings.TrimPrefix(filename, b.archetypeFilename))
|
||||
targetDir := filepath.Dir(targetFilename)
|
||||
if err := targetFs.MkdirAll(targetDir, 0777); err != nil && !os.IsExist(err) {
|
||||
return errors.Wrapf(err, "failed to create target directory for %s:", targetDir)
|
||||
|
||||
if err := b.sourceFs.MkdirAll(targetDir, 0o777); err != nil && !os.IsExist(err) {
|
||||
return errors.Wrapf(err, "failed to create target directory for %q", targetDir)
|
||||
}
|
||||
|
||||
out, err := targetFs.Create(targetFilename)
|
||||
out, err := b.sourceFs.Create(targetFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -165,40 +212,85 @@ func newContentFromDir(
|
||||
out.Close()
|
||||
}
|
||||
|
||||
for _, f := range cm.contentFiles {
|
||||
filename := f.Meta().Path
|
||||
s := targetSite(sites, f)
|
||||
targetFilename := filepath.Join(targetPath, strings.TrimPrefix(filename, archetypeDir))
|
||||
|
||||
content, err := executeArcheTypeAsTemplate(s, name, archetypeDir, targetFilename, filename)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to execute archetype template")
|
||||
}
|
||||
|
||||
if err := helpers.SafeWriteToDisk(targetFilename, bytes.NewReader(content), targetFs); err != nil {
|
||||
return errors.Wrap(err, "failed to save results")
|
||||
}
|
||||
}
|
||||
|
||||
jww.FEEDBACK.Println(targetPath, "created")
|
||||
b.h.Log.Printf("Content dir %q created", filepath.Join(baseDir, b.targetPath))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type archetypeMap struct {
|
||||
// These needs to be parsed and executed as Go templates.
|
||||
contentFiles []hugofs.FileMetaInfo
|
||||
// These are just copied to destination.
|
||||
otherFiles []hugofs.FileMetaInfo
|
||||
// If the templates needs a fully built site. This can potentially be
|
||||
// expensive, so only do when needed.
|
||||
siteUsed bool
|
||||
func (b *contentBuilder) buildFile() (string, error) {
|
||||
contentPlaceholderAbsFilename, err := b.cf.CreateContentPlaceHolder(b.targetPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
usesSite, err := b.usesSiteVar(b.archetypeFilename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var contentInclusionFilter *glob.FilenameFilter
|
||||
if !usesSite {
|
||||
// We don't need to build everything.
|
||||
contentInclusionFilter = glob.NewFilenameFilterForInclusionFunc(func(filename string) bool {
|
||||
filename = strings.TrimPrefix(filename, string(os.PathSeparator))
|
||||
return strings.Contains(contentPlaceholderAbsFilename, filename)
|
||||
})
|
||||
}
|
||||
|
||||
if err := b.h.Build(hugolib.BuildCfg{NoBuildLock: true, SkipRender: true, ContentInclusionFilter: contentInclusionFilter}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := b.applyArcheType(contentPlaceholderAbsFilename, b.archetypeFilename); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
b.h.Log.Printf("Content %q created", contentPlaceholderAbsFilename)
|
||||
|
||||
return contentPlaceholderAbsFilename, nil
|
||||
}
|
||||
|
||||
func mapArcheTypeDir(
|
||||
ps *helpers.PathSpec,
|
||||
fs afero.Fs,
|
||||
archetypeDir string) (archetypeMap, error) {
|
||||
func (b *contentBuilder) setArcheTypeFilenameToUse(ext string) {
|
||||
var pathsToCheck []string
|
||||
|
||||
if b.kind != "" {
|
||||
pathsToCheck = append(pathsToCheck, b.kind+ext)
|
||||
}
|
||||
|
||||
pathsToCheck = append(pathsToCheck, "default"+ext)
|
||||
|
||||
for _, p := range pathsToCheck {
|
||||
fi, err := b.archeTypeFs.Stat(p)
|
||||
if err == nil {
|
||||
b.archetypeFilename = p
|
||||
b.isDir = fi.IsDir()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (b *contentBuilder) applyArcheType(contentFilename, archetypeFilename string) error {
|
||||
p := b.h.GetContentPage(contentFilename)
|
||||
if p == nil {
|
||||
panic(fmt.Sprintf("[BUG] no Page found for %q", contentFilename))
|
||||
}
|
||||
|
||||
f, err := b.sourceFs.Create(contentFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if archetypeFilename == "" {
|
||||
return b.cf.AppplyArchetypeTemplate(f, p, b.kind, DefaultArchetypeTemplateTemplate)
|
||||
}
|
||||
|
||||
return b.cf.AppplyArchetypeFilename(f, p, b.kind, archetypeFilename)
|
||||
|
||||
}
|
||||
|
||||
func (b *contentBuilder) mapArcheTypeDir() error {
|
||||
var m archetypeMap
|
||||
|
||||
walkFn := func(path string, fi hugofs.FileMetaInfo, err error) error {
|
||||
@@ -215,7 +307,7 @@ func mapArcheTypeDir(
|
||||
if files.IsContentFile(path) {
|
||||
m.contentFiles = append(m.contentFiles, fil)
|
||||
if !m.siteUsed {
|
||||
m.siteUsed, err = usesSiteVar(fs, path)
|
||||
m.siteUsed, err = b.usesSiteVar(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -230,120 +322,71 @@ func mapArcheTypeDir(
|
||||
|
||||
walkCfg := hugofs.WalkwayConfig{
|
||||
WalkFn: walkFn,
|
||||
Fs: fs,
|
||||
Root: archetypeDir,
|
||||
Fs: b.archeTypeFs,
|
||||
Root: b.archetypeFilename,
|
||||
}
|
||||
|
||||
w := hugofs.NewWalkway(walkCfg)
|
||||
|
||||
if err := w.Walk(); err != nil {
|
||||
return m, errors.Wrapf(err, "failed to walk archetype dir %q", archetypeDir)
|
||||
return errors.Wrapf(err, "failed to walk archetype dir %q", b.archetypeFilename)
|
||||
}
|
||||
|
||||
return m, nil
|
||||
b.dirMap = m
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func usesSiteVar(fs afero.Fs, filename string) (bool, error) {
|
||||
f, err := fs.Open(filename)
|
||||
func (b *contentBuilder) openInEditorIfConfigured(filename string) error {
|
||||
editor := b.h.Cfg.GetString("newContentEditor")
|
||||
if editor == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
editorExec := strings.Fields(editor)[0]
|
||||
editorFlags := strings.Fields(editor)[1:]
|
||||
|
||||
var args []interface{}
|
||||
for _, editorFlag := range editorFlags {
|
||||
args = append(args, editorFlag)
|
||||
}
|
||||
args = append(
|
||||
args,
|
||||
filename,
|
||||
hexec.WithStdin(os.Stdin),
|
||||
hexec.WithStderr(os.Stderr),
|
||||
hexec.WithStdout(os.Stdout),
|
||||
)
|
||||
|
||||
b.h.Log.Printf("Editing %q with %q ...\n", filename, editorExec)
|
||||
|
||||
cmd, err := b.h.Deps.ExecHelper.New(editorExec, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func (b *contentBuilder) usesSiteVar(filename string) (bool, error) {
|
||||
if filename == "" {
|
||||
return false, nil
|
||||
}
|
||||
bb, err := afero.ReadFile(b.archeTypeFs, filename)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to open archetype file")
|
||||
}
|
||||
defer f.Close()
|
||||
return helpers.ReaderContains(f, []byte(".Site")), nil
|
||||
|
||||
return bytes.Contains(bb, []byte(".Site")) || bytes.Contains(bb, []byte("site.")), nil
|
||||
|
||||
}
|
||||
|
||||
// Resolve the target content path.
|
||||
func resolveContentPath(sites *hugolib.HugoSites, fs afero.Fs, targetPath string) (string, *hugolib.Site) {
|
||||
targetDir := filepath.Dir(targetPath)
|
||||
first := sites.Sites[0]
|
||||
|
||||
var (
|
||||
s *hugolib.Site
|
||||
siteContentDir string
|
||||
)
|
||||
|
||||
// Try the filename: my-post.en.md
|
||||
for _, ss := range sites.Sites {
|
||||
if strings.Contains(targetPath, "."+ss.Language().Lang+".") {
|
||||
s = ss
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var dirLang string
|
||||
|
||||
for _, dir := range sites.BaseFs.Content.Dirs {
|
||||
meta := dir.Meta()
|
||||
contentDir := meta.Filename
|
||||
|
||||
if !strings.HasSuffix(contentDir, helpers.FilePathSeparator) {
|
||||
contentDir += helpers.FilePathSeparator
|
||||
}
|
||||
|
||||
if strings.HasPrefix(targetPath, contentDir) {
|
||||
siteContentDir = contentDir
|
||||
dirLang = meta.Lang
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if s == nil && dirLang != "" {
|
||||
for _, ss := range sites.Sites {
|
||||
if ss.Lang() == dirLang {
|
||||
s = ss
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if s == nil {
|
||||
s = first
|
||||
}
|
||||
|
||||
if targetDir != "" && targetDir != "." {
|
||||
exists, _ := helpers.Exists(targetDir, fs)
|
||||
|
||||
if exists {
|
||||
return targetPath, s
|
||||
}
|
||||
}
|
||||
|
||||
if siteContentDir == "" {
|
||||
}
|
||||
|
||||
if siteContentDir != "" {
|
||||
pp := filepath.Join(siteContentDir, strings.TrimPrefix(targetPath, siteContentDir))
|
||||
return s.PathSpec.AbsPathify(pp), s
|
||||
} else {
|
||||
var contentDir string
|
||||
for _, dir := range sites.BaseFs.Content.Dirs {
|
||||
contentDir = dir.Meta().Filename
|
||||
if dir.Meta().Lang == s.Lang() {
|
||||
break
|
||||
}
|
||||
}
|
||||
return s.PathSpec.AbsPathify(filepath.Join(contentDir, targetPath)), s
|
||||
}
|
||||
}
|
||||
|
||||
// FindArchetype takes a given kind/archetype of content and returns the path
|
||||
// to the archetype in the archetype filesystem, blank if none found.
|
||||
func findArchetype(ps *helpers.PathSpec, kind, ext string) (outpath string, isDir bool) {
|
||||
fs := ps.BaseFs.Archetypes.Fs
|
||||
|
||||
var pathsToCheck []string
|
||||
|
||||
if kind != "" {
|
||||
pathsToCheck = append(pathsToCheck, kind+ext)
|
||||
}
|
||||
pathsToCheck = append(pathsToCheck, "default"+ext, "default")
|
||||
|
||||
for _, p := range pathsToCheck {
|
||||
fi, err := fs.Stat(p)
|
||||
if err == nil {
|
||||
return p, fi.IsDir()
|
||||
}
|
||||
}
|
||||
|
||||
return "", false
|
||||
type archetypeMap struct {
|
||||
// These needs to be parsed and executed as Go templates.
|
||||
contentFiles []hugofs.FileMetaInfo
|
||||
// These are just copied to destination.
|
||||
otherFiles []hugofs.FileMetaInfo
|
||||
// If the templates needs a fully built site. This can potentially be
|
||||
// expensive, so only do when needed.
|
||||
siteUsed bool
|
||||
}
|
||||
|
||||
@@ -1,149 +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 create
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/source"
|
||||
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
"github.com/gohugoio/hugo/tpl"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
// ArchetypeFileData represents the data available to an archetype template.
|
||||
type ArchetypeFileData struct {
|
||||
// The archetype content type, either given as --kind option or extracted
|
||||
// from the target path's section, i.e. "blog/mypost.md" will resolve to
|
||||
// "blog".
|
||||
Type string
|
||||
|
||||
// The current date and time as a RFC3339 formatted string, suitable for use in front matter.
|
||||
Date string
|
||||
|
||||
// The Site, fully equipped with all the pages etc. Note: This will only be set if it is actually
|
||||
// used in the archetype template. Also, if this is a multilingual setup,
|
||||
// this site is the site that best matches the target content file, based
|
||||
// on the presence of language code in the filename.
|
||||
Site *hugolib.SiteInfo
|
||||
|
||||
// Name will in most cases be the same as TranslationBaseName, e.g. "my-post".
|
||||
// But if that value is "index" (bundles), the Name is instead the owning folder.
|
||||
// This is the value you in most cases would want to use to construct the title in your
|
||||
// archetype template.
|
||||
Name string
|
||||
|
||||
// The target content file. Note that the .Content will be empty, as that
|
||||
// has not been created yet.
|
||||
source.File
|
||||
}
|
||||
|
||||
const (
|
||||
// ArchetypeTemplateTemplate is used as initial template when adding an archetype template.
|
||||
ArchetypeTemplateTemplate = `---
|
||||
title: "{{ replace .Name "-" " " | title }}"
|
||||
date: {{ .Date }}
|
||||
draft: true
|
||||
---
|
||||
|
||||
`
|
||||
)
|
||||
|
||||
var (
|
||||
archetypeShortcodeReplacementsPre = strings.NewReplacer(
|
||||
"{{<", "{x{<",
|
||||
"{{%", "{x{%",
|
||||
">}}", ">}x}",
|
||||
"%}}", "%}x}")
|
||||
|
||||
archetypeShortcodeReplacementsPost = strings.NewReplacer(
|
||||
"{x{<", "{{<",
|
||||
"{x{%", "{{%",
|
||||
">}x}", ">}}",
|
||||
"%}x}", "%}}")
|
||||
)
|
||||
|
||||
func executeArcheTypeAsTemplate(s *hugolib.Site, name, kind, targetPath, archetypeFilename string) ([]byte, error) {
|
||||
var (
|
||||
archetypeContent []byte
|
||||
archetypeTemplate []byte
|
||||
err error
|
||||
)
|
||||
|
||||
f, err := s.SourceSpec.NewFileInfoFrom(targetPath, targetPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
name = f.TranslationBaseName()
|
||||
|
||||
if name == "index" || name == "_index" {
|
||||
// Page bundles; the directory name will hopefully have a better name.
|
||||
dir := strings.TrimSuffix(f.Dir(), helpers.FilePathSeparator)
|
||||
_, name = filepath.Split(dir)
|
||||
}
|
||||
}
|
||||
|
||||
data := ArchetypeFileData{
|
||||
Type: kind,
|
||||
Date: time.Now().Format(time.RFC3339),
|
||||
Name: name,
|
||||
File: f,
|
||||
Site: s.Info,
|
||||
}
|
||||
|
||||
if archetypeFilename == "" {
|
||||
// TODO(bep) archetype revive the issue about wrong tpl funcs arg order
|
||||
archetypeTemplate = []byte(ArchetypeTemplateTemplate)
|
||||
} else {
|
||||
archetypeTemplate, err = afero.ReadFile(s.BaseFs.Archetypes.Fs, archetypeFilename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read archetype file %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// The archetype template may contain shortcodes, and these does not play well
|
||||
// with the Go templates. Need to set some temporary delimiters.
|
||||
archetypeTemplate = []byte(archetypeShortcodeReplacementsPre.Replace(string(archetypeTemplate)))
|
||||
|
||||
// Reuse the Hugo template setup to get the template funcs properly set up.
|
||||
templateHandler := s.Deps.Tmpl().(tpl.TemplateManager)
|
||||
templateName := paths.Filename(archetypeFilename)
|
||||
if err := templateHandler.AddTemplate("_text/"+templateName, string(archetypeTemplate)); err != nil {
|
||||
return nil, errors.Wrapf(err, "Failed to parse archetype file %q:", archetypeFilename)
|
||||
}
|
||||
|
||||
templ, _ := templateHandler.Lookup(templateName)
|
||||
|
||||
var buff bytes.Buffer
|
||||
if err := templateHandler.Execute(templ, &buff, data); err != nil {
|
||||
return nil, errors.Wrapf(err, "Failed to process archetype file %q:", archetypeFilename)
|
||||
}
|
||||
|
||||
archetypeContent = []byte(archetypeShortcodeReplacementsPost.Replace(buff.String()))
|
||||
|
||||
return archetypeContent, nil
|
||||
}
|
||||
+203
-44
@@ -34,27 +34,34 @@ import (
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
func TestNewContent(t *testing.T) {
|
||||
// TODO(bep) clean this up. Export the test site builder in Hugolib or something.
|
||||
func TestNewContentFromFile(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
kind string
|
||||
path string
|
||||
expected []string
|
||||
expected interface{}
|
||||
}{
|
||||
{"post", "post/sample-1.md", []string{`title = "Post Arch title"`, `test = "test1"`, "date = \"2015-01-12T19:20:04-07:00\""}},
|
||||
{"post", "post/org-1.org", []string{`#+title: ORG-1`}},
|
||||
{"emptydate", "post/sample-ed.md", []string{`title = "Empty Date Arch title"`, `test = "test1"`}},
|
||||
{"stump", "stump/sample-2.md", []string{`title: "Sample 2"`}}, // no archetype file
|
||||
{"", "sample-3.md", []string{`title: "Sample 3"`}}, // no archetype
|
||||
{"product", "product/sample-4.md", []string{`title = "SAMPLE-4"`}}, // empty archetype front matter
|
||||
{"lang", "post/lang-1.md", []string{`Site Lang: en|Name: Lang 1|i18n: Hugo Rocks!`}},
|
||||
{"lang", "post/lang-2.en.md", []string{`Site Lang: en|Name: Lang 2|i18n: Hugo Rocks!`}},
|
||||
{"lang", "content/post/lang-3.nn.md", []string{`Site Lang: nn|Name: Lang 3|i18n: Hugo Rokkar!`}},
|
||||
{"lang", "content_nn/post/lang-4.md", []string{`Site Lang: nn|Name: Lang 4|i18n: Hugo Rokkar!`}},
|
||||
{"lang", "content_nn/post/lang-5.en.md", []string{`Site Lang: en|Name: Lang 5|i18n: Hugo Rocks!`}},
|
||||
{"lang", "post/my-bundle/index.md", []string{`Site Lang: en|Name: My Bundle|i18n: Hugo Rocks!`}},
|
||||
{"lang", "post/my-bundle/index.en.md", []string{`Site Lang: en|Name: My Bundle|i18n: Hugo Rocks!`}},
|
||||
{"lang", "content/post/my-bundle/index.nn.md", []string{`Site Lang: nn|Name: My Bundle|i18n: Hugo Rokkar!`}},
|
||||
{"shortcodes", "shortcodes/go.md", []string{
|
||||
{"Post", "post", "post/sample-1.md", []string{`title = "Post Arch title"`, `test = "test1"`, "date = \"2015-01-12T19:20:04-07:00\""}},
|
||||
{"Post org-mode", "post", "post/org-1.org", []string{`#+title: ORG-1`}},
|
||||
{"Post, unknown content filetype", "post", "post/sample-1.pdoc", false},
|
||||
{"Empty date", "emptydate", "post/sample-ed.md", []string{`title = "Empty Date Arch title"`, `test = "test1"`}},
|
||||
{"Archetype file not found", "stump", "stump/sample-2.md", []string{`title: "Sample 2"`}}, // no archetype file
|
||||
{"No archetype", "", "sample-3.md", []string{`title: "Sample 3"`}}, // no archetype
|
||||
{"Empty archetype", "product", "product/sample-4.md", []string{`title = "SAMPLE-4"`}}, // empty archetype front matter
|
||||
{"Filenames", "filenames", "content/mypage/index.md", []string{"title = \"INDEX\"\n+++\n\n\nContentBaseName: mypage"}},
|
||||
{"Branch Name", "name", "content/tags/tag-a/_index.md", []string{"+++\ntitle = 'Tag A'\n+++"}},
|
||||
|
||||
{"Lang 1", "lang", "post/lang-1.md", []string{`Site Lang: en|Name: Lang 1|i18n: Hugo Rocks!`}},
|
||||
{"Lang 2", "lang", "post/lang-2.en.md", []string{`Site Lang: en|Name: Lang 2|i18n: Hugo Rocks!`}},
|
||||
{"Lang nn file", "lang", "content/post/lang-3.nn.md", []string{`Site Lang: nn|Name: Lang 3|i18n: Hugo Rokkar!`}},
|
||||
{"Lang nn dir", "lang", "content_nn/post/lang-4.md", []string{`Site Lang: nn|Name: Lang 4|i18n: Hugo Rokkar!`}},
|
||||
{"Lang en in nn dir", "lang", "content_nn/post/lang-5.en.md", []string{`Site Lang: en|Name: Lang 5|i18n: Hugo Rocks!`}},
|
||||
{"Lang en default", "lang", "post/my-bundle/index.md", []string{`Site Lang: en|Name: My Bundle|i18n: Hugo Rocks!`}},
|
||||
{"Lang en file", "lang", "post/my-bundle/index.en.md", []string{`Site Lang: en|Name: My Bundle|i18n: Hugo Rocks!`}},
|
||||
{"Lang nn bundle", "lang", "content/post/my-bundle/index.nn.md", []string{`Site Lang: nn|Name: My Bundle|i18n: Hugo Rokkar!`}},
|
||||
{"Site", "site", "content/mypage/index.md", []string{"RegularPages .Site: 10", "RegularPages site: 10"}},
|
||||
{"Shortcodes", "shortcodes", "shortcodes/go.md", []string{
|
||||
`title = "GO"`,
|
||||
"{{< myshortcode >}}",
|
||||
"{{% myshortcode %}}",
|
||||
@@ -62,28 +69,41 @@ func TestNewContent(t *testing.T) {
|
||||
}}, // shortcodes
|
||||
}
|
||||
|
||||
c := qt.New(t)
|
||||
|
||||
for i, cas := range cases {
|
||||
cas := cas
|
||||
t.Run(fmt.Sprintf("%s-%d", cas.kind, i), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
c.Run(cas.name, func(c *qt.C) {
|
||||
c.Parallel()
|
||||
|
||||
mm := afero.NewMemMapFs()
|
||||
c.Assert(initFs(mm), qt.IsNil)
|
||||
cfg, fs := newTestCfg(c, mm)
|
||||
h, err := hugolib.NewHugoSites(deps.DepsCfg{Cfg: cfg, Fs: fs})
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
c.Assert(create.NewContent(h, cas.kind, cas.path), qt.IsNil)
|
||||
err = create.NewContent(h, cas.kind, cas.path)
|
||||
|
||||
if b, ok := cas.expected.(bool); ok && !b {
|
||||
if !b {
|
||||
c.Assert(err, qt.Not(qt.IsNil))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
fname := filepath.FromSlash(cas.path)
|
||||
if !strings.HasPrefix(fname, "content") {
|
||||
fname = filepath.Join("content", fname)
|
||||
}
|
||||
content := readFileFromFs(t, fs.Source, fname)
|
||||
for _, v := range cas.expected {
|
||||
content := readFileFromFs(c, fs.Source, fname)
|
||||
|
||||
for _, v := range cas.expected.([]string) {
|
||||
found := strings.Contains(content, v)
|
||||
if !found {
|
||||
t.Fatalf("[%d] %q missing from output:\n%q", i, v, content)
|
||||
c.Fatalf("[%d] %q missing from output:\n%q", i, v, content)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -96,10 +116,10 @@ func TestNewContentFromDir(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
archetypeDir := filepath.Join("archetypes", "my-bundle")
|
||||
c.Assert(mm.MkdirAll(archetypeDir, 0755), qt.IsNil)
|
||||
c.Assert(mm.MkdirAll(archetypeDir, 0o755), qt.IsNil)
|
||||
|
||||
archetypeThemeDir := filepath.Join("themes", "mytheme", "archetypes", "my-theme-bundle")
|
||||
c.Assert(mm.MkdirAll(archetypeThemeDir, 0755), qt.IsNil)
|
||||
c.Assert(mm.MkdirAll(archetypeThemeDir, 0o755), qt.IsNil)
|
||||
|
||||
contentFile := `
|
||||
File: %s
|
||||
@@ -108,15 +128,15 @@ Name: {{ replace .Name "-" " " | title }}
|
||||
i18n: {{ T "hugo" }}
|
||||
`
|
||||
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "index.md"), []byte(fmt.Sprintf(contentFile, "index.md")), 0755), qt.IsNil)
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "index.nn.md"), []byte(fmt.Sprintf(contentFile, "index.nn.md")), 0755), qt.IsNil)
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "index.md"), []byte(fmt.Sprintf(contentFile, "index.md")), 0o755), qt.IsNil)
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "index.nn.md"), []byte(fmt.Sprintf(contentFile, "index.nn.md")), 0o755), qt.IsNil)
|
||||
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "pages", "bio.md"), []byte(fmt.Sprintf(contentFile, "bio.md")), 0755), qt.IsNil)
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "resources", "hugo1.json"), []byte(`hugo1: {{ printf "no template handling in here" }}`), 0755), qt.IsNil)
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "resources", "hugo2.xml"), []byte(`hugo2: {{ printf "no template handling in here" }}`), 0755), qt.IsNil)
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "pages", "bio.md"), []byte(fmt.Sprintf(contentFile, "bio.md")), 0o755), qt.IsNil)
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "resources", "hugo1.json"), []byte(`hugo1: {{ printf "no template handling in here" }}`), 0o755), qt.IsNil)
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "resources", "hugo2.xml"), []byte(`hugo2: {{ printf "no template handling in here" }}`), 0o755), qt.IsNil)
|
||||
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeThemeDir, "index.md"), []byte(fmt.Sprintf(contentFile, "index.md")), 0755), qt.IsNil)
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeThemeDir, "resources", "hugo1.json"), []byte(`hugo1: {{ printf "no template handling in here" }}`), 0755), qt.IsNil)
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeThemeDir, "index.md"), []byte(fmt.Sprintf(contentFile, "index.md")), 0o755), qt.IsNil)
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeThemeDir, "resources", "hugo1.json"), []byte(`hugo1: {{ printf "no template handling in here" }}`), 0o755), qt.IsNil)
|
||||
|
||||
c.Assert(initFs(mm), qt.IsNil)
|
||||
cfg, fs := newTestCfg(c, mm)
|
||||
@@ -135,15 +155,105 @@ i18n: {{ T "hugo" }}
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/index.md")), `File: index.md`, `Site Lang: en`, `Name: My Post`, `i18n: Hugo Rocks!`)
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/index.nn.md")), `File: index.nn.md`, `Site Lang: nn`, `Name: My Post`, `i18n: Hugo Rokkar!`)
|
||||
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/pages/bio.md")), `File: bio.md`, `Site Lang: en`, `Name: My Post`)
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/pages/bio.md")), `File: bio.md`, `Site Lang: en`, `Name: Bio`)
|
||||
|
||||
c.Assert(create.NewContent(h, "my-theme-bundle", "post/my-theme-post"), qt.IsNil)
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-theme-post/index.md")), `File: index.md`, `Site Lang: en`, `Name: My Theme Post`, `i18n: Hugo Rocks!`)
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-theme-post/resources/hugo1.json")), `hugo1: {{ printf "no template handling in here" }}`)
|
||||
}
|
||||
|
||||
func TestNewContentFromDirSiteFunction(t *testing.T) {
|
||||
mm := afero.NewMemMapFs()
|
||||
c := qt.New(t)
|
||||
|
||||
archetypeDir := filepath.Join("archetypes", "my-bundle")
|
||||
defaultArchetypeDir := filepath.Join("archetypes", "default")
|
||||
c.Assert(mm.MkdirAll(archetypeDir, 0o755), qt.IsNil)
|
||||
c.Assert(mm.MkdirAll(defaultArchetypeDir, 0o755), qt.IsNil)
|
||||
|
||||
contentFile := `
|
||||
File: %s
|
||||
site RegularPages: {{ len site.RegularPages }}
|
||||
|
||||
`
|
||||
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "index.md"), []byte(fmt.Sprintf(contentFile, "index.md")), 0o755), qt.IsNil)
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(defaultArchetypeDir, "index.md"), []byte("default archetype index.md"), 0o755), qt.IsNil)
|
||||
|
||||
c.Assert(initFs(mm), qt.IsNil)
|
||||
cfg, fs := newTestCfg(c, mm)
|
||||
|
||||
h, err := hugolib.NewHugoSites(deps.DepsCfg{Cfg: cfg, Fs: fs})
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(len(h.Sites), qt.Equals, 2)
|
||||
|
||||
c.Assert(create.NewContent(h, "my-bundle", "post/my-post"), qt.IsNil)
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/index.md")), `site RegularPages: 10`)
|
||||
|
||||
// Default bundle archetype
|
||||
c.Assert(create.NewContent(h, "", "post/my-post2"), qt.IsNil)
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post2/index.md")), `default archetype index.md`)
|
||||
|
||||
// Regular file with bundle kind.
|
||||
c.Assert(create.NewContent(h, "my-bundle", "post/foo.md"), qt.IsNil)
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/foo.md")), `draft: true`)
|
||||
|
||||
// Regular files should fall back to the default archetype (we have no regular file archetype).
|
||||
c.Assert(create.NewContent(h, "my-bundle", "mypage.md"), qt.IsNil)
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "mypage.md")), `draft: true`)
|
||||
|
||||
}
|
||||
|
||||
func TestNewContentFromDirNoSite(t *testing.T) {
|
||||
mm := afero.NewMemMapFs()
|
||||
c := qt.New(t)
|
||||
|
||||
archetypeDir := filepath.Join("archetypes", "my-bundle")
|
||||
c.Assert(mm.MkdirAll(archetypeDir, 0o755), qt.IsNil)
|
||||
|
||||
archetypeThemeDir := filepath.Join("themes", "mytheme", "archetypes", "my-theme-bundle")
|
||||
c.Assert(mm.MkdirAll(archetypeThemeDir, 0o755), qt.IsNil)
|
||||
|
||||
contentFile := `
|
||||
File: %s
|
||||
Name: {{ replace .Name "-" " " | title }}
|
||||
i18n: {{ T "hugo" }}
|
||||
`
|
||||
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "index.md"), []byte(fmt.Sprintf(contentFile, "index.md")), 0o755), qt.IsNil)
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "index.nn.md"), []byte(fmt.Sprintf(contentFile, "index.nn.md")), 0o755), qt.IsNil)
|
||||
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "pages", "bio.md"), []byte(fmt.Sprintf(contentFile, "bio.md")), 0o755), qt.IsNil)
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "resources", "hugo1.json"), []byte(`hugo1: {{ printf "no template handling in here" }}`), 0o755), qt.IsNil)
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "resources", "hugo2.xml"), []byte(`hugo2: {{ printf "no template handling in here" }}`), 0o755), qt.IsNil)
|
||||
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeThemeDir, "index.md"), []byte(fmt.Sprintf(contentFile, "index.md")), 0o755), qt.IsNil)
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeThemeDir, "resources", "hugo1.json"), []byte(`hugo1: {{ printf "no template handling in here" }}`), 0o755), qt.IsNil)
|
||||
|
||||
c.Assert(initFs(mm), qt.IsNil)
|
||||
cfg, fs := newTestCfg(c, mm)
|
||||
|
||||
h, err := hugolib.NewHugoSites(deps.DepsCfg{Cfg: cfg, Fs: fs})
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(len(h.Sites), qt.Equals, 2)
|
||||
|
||||
c.Assert(create.NewContent(h, "my-bundle", "post/my-post"), qt.IsNil)
|
||||
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/resources/hugo1.json")), `hugo1: {{ printf "no template handling in here" }}`)
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/resources/hugo2.xml")), `hugo2: {{ printf "no template handling in here" }}`)
|
||||
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/index.md")), `File: index.md`, `Name: My Post`, `i18n: Hugo Rocks!`)
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/index.nn.md")), `File: index.nn.md`, `Name: My Post`, `i18n: Hugo Rokkar!`)
|
||||
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/pages/bio.md")), `File: bio.md`, `Name: Bio`)
|
||||
|
||||
c.Assert(create.NewContent(h, "my-theme-bundle", "post/my-theme-post"), qt.IsNil)
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-theme-post/index.md")), `File: index.md`, `Name: My Theme Post`, `i18n: Hugo Rocks!`)
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-theme-post/resources/hugo1.json")), `hugo1: {{ printf "no template handling in here" }}`)
|
||||
}
|
||||
|
||||
func initFs(fs afero.Fs) error {
|
||||
perm := os.FileMode(0755)
|
||||
perm := os.FileMode(0o755)
|
||||
var err error
|
||||
|
||||
// create directories
|
||||
@@ -159,7 +269,16 @@ func initFs(fs afero.Fs) error {
|
||||
}
|
||||
}
|
||||
|
||||
// create files
|
||||
// create some dummy content
|
||||
for i := 1; i <= 10; i++ {
|
||||
filename := filepath.Join("content", fmt.Sprintf("page%d.md", i))
|
||||
afero.WriteFile(fs, filename, []byte(`---
|
||||
title: Test
|
||||
---
|
||||
`), 0666)
|
||||
}
|
||||
|
||||
// create archetype files
|
||||
for _, v := range []struct {
|
||||
path string
|
||||
content string
|
||||
@@ -172,11 +291,40 @@ func initFs(fs afero.Fs) error {
|
||||
path: filepath.Join("archetypes", "post.org"),
|
||||
content: "#+title: {{ .BaseFileName | upper }}",
|
||||
},
|
||||
{
|
||||
path: filepath.Join("archetypes", "name.md"),
|
||||
content: `+++
|
||||
title = '{{ replace .Name "-" " " | title }}'
|
||||
+++`,
|
||||
},
|
||||
{
|
||||
path: filepath.Join("archetypes", "product.md"),
|
||||
content: `+++
|
||||
title = "{{ .BaseFileName | upper }}"
|
||||
+++`,
|
||||
},
|
||||
{
|
||||
path: filepath.Join("archetypes", "filenames.md"),
|
||||
content: `...
|
||||
title = "{{ .BaseFileName | upper }}"
|
||||
+++
|
||||
|
||||
|
||||
ContentBaseName: {{ .File.ContentBaseName }}
|
||||
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: filepath.Join("archetypes", "site.md"),
|
||||
content: `...
|
||||
title = "{{ .BaseFileName | upper }}"
|
||||
+++
|
||||
|
||||
Len RegularPages .Site: {{ len .Site.RegularPages }}
|
||||
Len RegularPages site: {{ len site.RegularPages }}
|
||||
|
||||
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: filepath.Join("archetypes", "emptydate.md"),
|
||||
@@ -184,7 +332,7 @@ title = "{{ .BaseFileName | upper }}"
|
||||
},
|
||||
{
|
||||
path: filepath.Join("archetypes", "lang.md"),
|
||||
content: `Site Lang: {{ .Site.Language.Lang }}|Name: {{ replace .Name "-" " " | title }}|i18n: {{ T "hugo" }}`,
|
||||
content: `Site Lang: {{ site.Language.Lang }}|Name: {{ replace .Name "-" " " | title }}|i18n: {{ T "hugo" }}`,
|
||||
},
|
||||
// #3623x
|
||||
{
|
||||
@@ -227,7 +375,7 @@ func cContains(c *qt.C, v interface{}, matches ...string) {
|
||||
}
|
||||
|
||||
// TODO(bep) extract common testing package with this and some others
|
||||
func readFileFromFs(t *testing.T, fs afero.Fs, filename string) string {
|
||||
func readFileFromFs(t testing.TB, fs afero.Fs, filename string) string {
|
||||
t.Helper()
|
||||
filename = filepath.FromSlash(filename)
|
||||
b, err := afero.ReadFile(fs, filename)
|
||||
@@ -257,23 +405,34 @@ languageName = "English"
|
||||
[languages.nn]
|
||||
weight = 2
|
||||
languageName = "Nynorsk"
|
||||
contentDir = "content_nn"
|
||||
|
||||
[module]
|
||||
[[module.mounts]]
|
||||
source = 'archetypes'
|
||||
target = 'archetypes'
|
||||
[[module.mounts]]
|
||||
source = 'content'
|
||||
target = 'content'
|
||||
lang = 'en'
|
||||
[[module.mounts]]
|
||||
source = 'content_nn'
|
||||
target = 'content'
|
||||
lang = 'nn'
|
||||
`
|
||||
if mm == nil {
|
||||
mm = afero.NewMemMapFs()
|
||||
}
|
||||
|
||||
mm.MkdirAll(filepath.FromSlash("content_nn"), 0777)
|
||||
mm.MkdirAll(filepath.FromSlash("content_nn"), 0o777)
|
||||
|
||||
mm.MkdirAll(filepath.FromSlash("themes/mytheme"), 0777)
|
||||
mm.MkdirAll(filepath.FromSlash("themes/mytheme"), 0o777)
|
||||
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join("i18n", "en.toml"), []byte(`[hugo]
|
||||
other = "Hugo Rocks!"`), 0755), qt.IsNil)
|
||||
other = "Hugo Rocks!"`), 0o755), qt.IsNil)
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join("i18n", "nn.toml"), []byte(`[hugo]
|
||||
other = "Hugo Rokkar!"`), 0755), qt.IsNil)
|
||||
other = "Hugo Rokkar!"`), 0o755), qt.IsNil)
|
||||
|
||||
c.Assert(afero.WriteFile(mm, "config.toml", []byte(cfg), 0755), qt.IsNil)
|
||||
c.Assert(afero.WriteFile(mm, "config.toml", []byte(cfg), 0o755), qt.IsNil)
|
||||
|
||||
v, _, err := hugolib.LoadConfig(hugolib.ConfigSourceDescriptor{Fs: mm, Filename: "config.toml"})
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !nodeploy
|
||||
// +build !nodeploy
|
||||
|
||||
package deploy
|
||||
@@ -24,6 +25,7 @@ import (
|
||||
hglob "github.com/gohugoio/hugo/hugofs/glob"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const deploymentConfigKey = "deployment"
|
||||
@@ -124,12 +126,18 @@ func decodeConfig(cfg config.Provider) (deployConfig, error) {
|
||||
return dcfg, err
|
||||
}
|
||||
for _, tgt := range dcfg.Targets {
|
||||
if tgt == nil {
|
||||
return dcfg, errors.New("empty deployment target")
|
||||
}
|
||||
if err := tgt.parseIncludeExclude(); err != nil {
|
||||
return dcfg, err
|
||||
}
|
||||
}
|
||||
var err error
|
||||
for _, m := range dcfg.Matchers {
|
||||
if m == nil {
|
||||
return dcfg, errors.New("empty deployment matcher")
|
||||
}
|
||||
m.re, err = regexp.Compile(m.Pattern)
|
||||
if err != nil {
|
||||
return dcfg, fmt.Errorf("invalid deployment.matchers.pattern: %v", err)
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !nodeploy
|
||||
// +build !nodeploy
|
||||
|
||||
package deploy
|
||||
@@ -21,7 +22,6 @@ import (
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
|
||||
)
|
||||
|
||||
func TestDecodeConfigFromTOML(t *testing.T) {
|
||||
@@ -169,3 +169,31 @@ func TestDecodeConfigDefault(t *testing.T) {
|
||||
c.Assert(len(dcfg.Targets), qt.Equals, 0)
|
||||
c.Assert(len(dcfg.Matchers), qt.Equals, 0)
|
||||
}
|
||||
|
||||
func TestEmptyTarget(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
tomlConfig := `
|
||||
[deployment]
|
||||
[[deployment.targets]]
|
||||
`
|
||||
cfg, err := config.FromConfigString(tomlConfig, "toml")
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
_, err = decodeConfig(cfg)
|
||||
c.Assert(err, qt.Not(qt.IsNil))
|
||||
}
|
||||
|
||||
func TestEmptyMatcher(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
tomlConfig := `
|
||||
[deployment]
|
||||
[[deployment.matchers]]
|
||||
`
|
||||
cfg, err := config.FromConfigString(tomlConfig, "toml")
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
_, err = decodeConfig(cfg)
|
||||
c.Assert(err, qt.Not(qt.IsNil))
|
||||
}
|
||||
|
||||
Vendored
+16
-5
@@ -8,8 +8,10 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/gohugoio/hugo/cache/filecache"
|
||||
"github.com/gohugoio/hugo/common/hexec"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/config/security"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
@@ -36,6 +38,8 @@ type Deps struct {
|
||||
// Used to log errors that may repeat itself many times.
|
||||
LogDistinct loggers.Logger
|
||||
|
||||
ExecHelper *hexec.Exec
|
||||
|
||||
// The templates to use. This will usually implement the full tpl.TemplateManager.
|
||||
tmpl tpl.TemplateHandler
|
||||
|
||||
@@ -230,6 +234,12 @@ func New(cfg DepsCfg) (*Deps, error) {
|
||||
cfg.OutputFormats = output.DefaultFormats
|
||||
}
|
||||
|
||||
securityConfig, err := security.DecodeConfig(cfg.Cfg)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(err, "failed to create security config from configuration")
|
||||
}
|
||||
execHelper := hexec.New(securityConfig)
|
||||
|
||||
ps, err := helpers.NewPathSpec(fs, cfg.Language, logger)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "create PathSpec")
|
||||
@@ -243,17 +253,17 @@ func New(cfg DepsCfg) (*Deps, error) {
|
||||
errorHandler := &globalErrHandler{}
|
||||
buildState := &BuildState{}
|
||||
|
||||
resourceSpec, err := resources.NewSpec(ps, fileCaches, buildState, logger, errorHandler, cfg.OutputFormats, cfg.MediaTypes)
|
||||
resourceSpec, err := resources.NewSpec(ps, fileCaches, buildState, logger, errorHandler, execHelper, cfg.OutputFormats, cfg.MediaTypes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
contentSpec, err := helpers.NewContentSpec(cfg.Language, logger, ps.BaseFs.Content.Fs)
|
||||
contentSpec, err := helpers.NewContentSpec(cfg.Language, logger, ps.BaseFs.Content.Fs, execHelper)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sp := source.NewSourceSpec(ps, fs.Source)
|
||||
sp := source.NewSourceSpec(ps, nil, fs.Source)
|
||||
|
||||
timeoutms := cfg.Language.GetInt("timeout")
|
||||
if timeoutms <= 0 {
|
||||
@@ -269,6 +279,7 @@ func New(cfg DepsCfg) (*Deps, error) {
|
||||
Fs: fs,
|
||||
Log: ignorableLogger,
|
||||
LogDistinct: logDistinct,
|
||||
ExecHelper: execHelper,
|
||||
templateProvider: cfg.TemplateProvider,
|
||||
translationProvider: cfg.TranslationProvider,
|
||||
WithTemplate: cfg.WithTemplate,
|
||||
@@ -311,7 +322,7 @@ func (d Deps) ForLanguage(cfg DepsCfg, onCreated func(d *Deps) error) (*Deps, er
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d.ContentSpec, err = helpers.NewContentSpec(l, d.Log, d.BaseFs.Content.Fs)
|
||||
d.ContentSpec, err = helpers.NewContentSpec(l, d.Log, d.BaseFs.Content.Fs, d.ExecHelper)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -322,7 +333,7 @@ func (d Deps) ForLanguage(cfg DepsCfg, onCreated func(d *Deps) error) (*Deps, er
|
||||
// TODO(bep) clean up these inits.
|
||||
resourceCache := d.ResourceSpec.ResourceCache
|
||||
postBuildAssets := d.ResourceSpec.PostBuildAssets
|
||||
d.ResourceSpec, err = resources.NewSpec(d.PathSpec, d.ResourceSpec.FileCaches, d.BuildState, d.Log, d.globalErrHandler, cfg.OutputFormats, cfg.MediaTypes)
|
||||
d.ResourceSpec, err = resources.NewSpec(d.PathSpec, d.ResourceSpec.FileCaches, d.BuildState, d.Log, d.globalErrHandler, d.ExecHelper, cfg.OutputFormats, cfg.MediaTypes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+4
-1
@@ -1,6 +1,9 @@
|
||||
/.idea
|
||||
/.vscode
|
||||
/public
|
||||
node_modules
|
||||
nohup.out
|
||||
.DS_Store
|
||||
trace.out
|
||||
trace.out
|
||||
.hugo_build.lock
|
||||
resources/_gen/images/
|
||||
@@ -0,0 +1,4 @@
|
||||
# https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md
|
||||
|
||||
MD013: false # Line length
|
||||
MD033: false # Inline HTML
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"EditorConfig.EditorConfig",
|
||||
"esbenp.prettier-vscode",
|
||||
]
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 237 KiB |
BIN
Binary file not shown.
+10
-2
@@ -47,9 +47,9 @@
|
||||
{{ end }}
|
||||
{{ partial "site-manifest.html" . }}
|
||||
{{- partial "head-additions.html" . -}}
|
||||
{{- template "_internal/opengraph.html" . -}}
|
||||
{{- partial "opengraph/opengraph.html" . -}}
|
||||
{{- template "_internal/schema.html" . -}}
|
||||
{{- template "_internal/twitter_cards.html" . -}}
|
||||
{{- partial "opengraph/twitter_cards.html" . -}}
|
||||
|
||||
{{ if eq (getenv "HUGO_ENV") "production" | or (eq .Site.Params.env "production") }}
|
||||
{{ partial "gtag" . }}
|
||||
@@ -66,6 +66,14 @@
|
||||
|
||||
{{ block "footer" . }}{{ partialCached "site-footer.html" . }}{{ end }}
|
||||
|
||||
{{ if .Page.Store.Get "hasMermaid" }}
|
||||
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
|
||||
<script>
|
||||
mermaid.initialize({ startOnLoad: true });
|
||||
</script>
|
||||
{{ end }}
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
+2
-5
@@ -1,6 +1,3 @@
|
||||
<h6 class="f4 dark-gray mb2">
|
||||
<a href="{{ .Permalink }}" class="hide-child link primary-color">
|
||||
<span class="nl3 child">{{ partial "svg/link-permalink.svg" (dict "size" "14px") }}</span>
|
||||
“{{ .Title }}”
|
||||
</a> was last updated: {{ .Lastmod.Format "January 2, 2006" }}{{ with .GitInfo }}: <a class="hide-child link primary-color" href="{{$.Site.Params.ghrepo}}commit/{{ .Hash }}">{{ .Subject }} ({{ .AbbreviatedHash }})</a>{{end }}
|
||||
<h6 class="f6 dark-gray mb2">
|
||||
Last updated: {{ .Lastmod.Format "January 2, 2006" }}{{ with .GitInfo }}: <a class="hide-child link primary-color" href="{{$.Site.Params.ghrepo}}commit/{{ .Hash }}">{{ .Subject }} ({{ .AbbreviatedHash }})</a>{{end }}
|
||||
</h6>
|
||||
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
{{ $currentPage := . }}
|
||||
{{ $menu := .Site.Menus.docs.ByWeight }}
|
||||
<ul class="list dib ph0 ma0 scrolling-touch tc">
|
||||
<ul class="list dib ph0 scrolling-touch tc">
|
||||
{{ range $menu }}{{ $post := printf "%s" .Post }}
|
||||
<li class="tl dib ma0 hover-bg-black w-100{{ if eq $post "break" }} mb2 bb b--mid-gray{{ end }}">
|
||||
<a href="{{.URL}}" class="ttu f6 link primary-color-light hover-white db brand-font mb1 ma0 w-100 pv2 ph4{{if $currentPage.IsMenuCurrent "main" . }} bg-primary-color{{end}}">
|
||||
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
{{ $currentPage := . }}
|
||||
{{ $menu := .Site.Menus.global }}
|
||||
<ul class="list hidden dib ph0 ma0 scrolling-touch tc">
|
||||
<ul class="list hidden dib ph0 scrolling-touch tc">
|
||||
{{ range $menu }}
|
||||
<li class="tl dib ma0 hover-bg-black w-100">
|
||||
<a href="{{.URL}}" class="ttu f6 link primary-color-light overflow hover-white db brand-font ma0 w-100 pv3 ph4{{if $currentPage.IsMenuCurrent "main" . }} bg-primary-color{{end}}">
|
||||
|
||||
+4
-4
@@ -1,12 +1,12 @@
|
||||
<div class="globalmenu mobilemenu pb3 dn">
|
||||
<div class="globalmenu mobilemenu pb1 dn">
|
||||
{{ partial "nav-links-global-mobile.html" . }}
|
||||
</div>
|
||||
<div class="docsmenu mobilemenu pb3 dn">
|
||||
<div class="docsmenu mobilemenu pb1 dn">
|
||||
{{ partial "nav-links-docs-mobile.html" . }}
|
||||
</div>
|
||||
|
||||
<div class="flex dn-l justify-between">
|
||||
<button class="js-toggle flex-auto dib dn-l f6 tc db mt4-ns ph3 pv2 link mr2 white bg-primary-color-dark hover-bg-primary-color ba b--white-40 w-auto" data-target=".globalmenu">Menu</button>
|
||||
<button class="js-toggle flex-auto dib dn-l f6 tc db ph3 pv2 link mr2 white bg-primary-color-dark hover-bg-primary-color ba b--white-40 w-auto" data-target=".globalmenu">Menu</button>
|
||||
|
||||
<button class="js-toggle flex-auto dib dn-l f6 tc db mt4-ns ph3 pv2 link white bg-primary-color-dark hover-bg-primary-color ba b--white-40 w-auto" data-target=".docsmenu">Docs Menu</button>
|
||||
<button class="js-toggle flex-auto dib dn-l f6 tc db ph3 pv2 link white bg-primary-color-dark hover-bg-primary-color ba b--white-40 w-auto" data-target=".docsmenu">Docs Menu</button>
|
||||
</div>
|
||||
|
||||
Generated
+24
@@ -0,0 +1,24 @@
|
||||
{{ $images := $.Resources.ByType "image" }}
|
||||
{{ $featured := $images.GetMatch "*feature*" }}
|
||||
{{ if not $featured }}{{ $featured = $images.GetMatch "{*cover*,*thumbnail*}" }}{{ end }}
|
||||
{{ if not $featured }}
|
||||
{{ $featured = resources.Get "/opengraph/gohugoio-card-base-1.png" }}
|
||||
{{ $size := 80 }}
|
||||
{{ $title := $.LinkTitle }}
|
||||
{{ if gt (len $title) 20 }}
|
||||
{{ $size = 70 }}
|
||||
{{ end }}
|
||||
|
||||
{{ $text := $title }}
|
||||
{{ $textOptions := dict
|
||||
"color" "#FFF"
|
||||
"size" $size
|
||||
"lineSpacing" 10
|
||||
"x" 65 "y" 80
|
||||
"font" (resources.Get "/opengraph/mulish-black.ttf")
|
||||
}}
|
||||
|
||||
{{ $featured = $featured | images.Filter (images.Text $text $textOptions) }}
|
||||
{{ end }}
|
||||
|
||||
{{ return $featured }}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
<meta property="og:title" content="{{ .Title }}" />
|
||||
<meta property="og:description" content="{{ with .Description }}{{ . }}{{ else }}{{if .IsPage}}{{ .Summary }}{{ else }}{{ with .Site.Params.description }}{{ . }}{{ end }}{{ end }}{{ end }}" />
|
||||
<meta property="og:type" content="{{ if .IsPage }}article{{ else }}website{{ end }}" />
|
||||
<meta property="og:url" content="{{ .Permalink }}" />
|
||||
|
||||
{{- with $.Params.images -}}
|
||||
{{- range first 6 . }}<meta property="og:image" content="{{ . | absURL }}" />{{ end -}}
|
||||
{{- else -}}
|
||||
{{- $featured := partial "opengraph/get-featured-image.html" . }}
|
||||
{{- with $featured -}}
|
||||
<meta property="og:image" content="{{ $featured.Permalink }}"/>
|
||||
{{- else -}}
|
||||
{{- with $.Site.Params.images }}<meta property="og:image" content="{{ index . 0 | absURL }}"/>{{ end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if .IsPage }}
|
||||
{{- $iso8601 := "2006-01-02T15:04:05-07:00" -}}
|
||||
<meta property="article:section" content="{{ .Section }}" />
|
||||
{{ with .PublishDate }}<meta property="article:published_time" {{ .Format $iso8601 | printf "content=%q" | safeHTMLAttr }} />{{ end }}
|
||||
{{ with .Lastmod }}<meta property="article:modified_time" {{ .Format $iso8601 | printf "content=%q" | safeHTMLAttr }} />{{ end }}
|
||||
{{- end -}}
|
||||
|
||||
{{- with .Params.audio }}<meta property="og:audio" content="{{ . }}" />{{ end }}
|
||||
{{- with .Params.locale }}<meta property="og:locale" content="{{ . }}" />{{ end }}
|
||||
{{- with .Site.Params.title }}<meta property="og:site_name" content="{{ . }}" />{{ end }}
|
||||
{{- with .Params.videos }}{{- range . }}
|
||||
<meta property="og:video" content="{{ . | absURL }}" />
|
||||
{{ end }}{{ end }}
|
||||
|
||||
{{- /* If it is part of a series, link to related articles */}}
|
||||
{{- $permalink := .Permalink }}
|
||||
{{- $siteSeries := .Site.Taxonomies.series }}
|
||||
{{ with .Params.series }}{{- range $name := . }}
|
||||
{{- $series := index $siteSeries ($name | urlize) }}
|
||||
{{- range $page := first 6 $series.Pages }}
|
||||
{{- if ne $page.Permalink $permalink }}<meta property="og:see_also" content="{{ $page.Permalink }}" />{{ end }}
|
||||
{{- end }}
|
||||
{{ end }}{{ end }}
|
||||
|
||||
{{- /* Facebook Page Admin ID for Domain Insights */}}
|
||||
{{- with .Site.Social.facebook_admin }}<meta property="fb:admins" content="{{ . }}" />{{ end }}
|
||||
Generated
+22
@@ -0,0 +1,22 @@
|
||||
{{- with $.Params.images -}}
|
||||
<meta name="twitter:card" content="summary_large_image"/>
|
||||
<meta name="twitter:image" content="{{ index . 0 | absURL }}"/>
|
||||
{{ else -}}
|
||||
{{- $featured := partial "opengraph/get-featured-image.html" . }}
|
||||
{{- with $featured -}}
|
||||
<meta name="twitter:card" content="summary_large_image"/>
|
||||
<meta name="twitter:image" content="{{ $featured.Permalink }}"/>
|
||||
{{- else -}}
|
||||
{{- with $.Site.Params.images -}}
|
||||
<meta name="twitter:card" content="summary_large_image"/>
|
||||
<meta name="twitter:image" content="{{ index . 0 | absURL }}"/>
|
||||
{{ else -}}
|
||||
<meta name="twitter:card" content="summary"/>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end }}
|
||||
<meta name="twitter:title" content="{{ .Title }}"/>
|
||||
<meta name="twitter:description" content="{{ with .Description }}{{ . }}{{ else }}{{if .IsPage}}{{ .Summary }}{{ else }}{{ with .Site.Params.description }}{{ . }}{{ end }}{{ end }}{{ end -}}"/>
|
||||
{{ with .Site.Social.twitter -}}
|
||||
<meta name="twitter:site" content="@{{ . }}"/>
|
||||
{{ end -}}
|
||||
+1
-1
@@ -41,7 +41,7 @@
|
||||
|
||||
<img src="/images/gopher-side_color.svg" alt="" class="absolute-l bottom-0 dn db-l h4 right-0 z-999"/>
|
||||
|
||||
<div class="bg-primary-color-dark bottom-0 left-0 right-0 dn-l fixed pb3 ph3 w-100">
|
||||
<div class="bg-primary-color-dark bottom-0 left-0 right-0 dn-l fixed pb3 ph3 pt3 w-100">
|
||||
{{- partial "nav-mobile.html" . -}}
|
||||
</div>
|
||||
|
||||
|
||||
+1
-1
@@ -3,4 +3,4 @@
|
||||
<label class="clip" for="search-input">Search</label>
|
||||
<input type="search" id="search-input" class="needs-js bg-left bn f5 input-reset lh-solid mt3 mt0-ns pl4 pv2 w5 white" placeholder="Search the Docs" name="search-input" value="" style="background: url('/images/icon-search.png') 5px 11px/16px 16px no-repeat; background-color: rgba(255,255,255,0);">
|
||||
</fieldset>
|
||||
</form>
|
||||
</form>
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@
|
||||
"highlight.js": "^9.13.1",
|
||||
"lazysizes": "^5.2.1",
|
||||
"mini-css-extract-plugin": "^0.4.4",
|
||||
"postcss": "^7.0.32",
|
||||
"postcss": "^7.0.36",
|
||||
"postcss-cssnext": "^3.1.0",
|
||||
"postcss-import": "^12.0.1",
|
||||
"postcss-loader": "^3.0.0",
|
||||
|
||||
@@ -1 +1 @@
|
||||
# github.com/gohugoio/gohugoioTheme v0.0.0-20210409071416-c88da48134b7
|
||||
# github.com/gohugoio/gohugoioTheme v0.0.0-20211211134334-0fe25799bb58
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
linktitle: ""
|
||||
description: ""
|
||||
godocref: ""
|
||||
publishdate: ""
|
||||
lastmod: ""
|
||||
categories: []
|
||||
tags: []
|
||||
weight: 00
|
||||
slug: ""
|
||||
aliases: []
|
||||
toc: false
|
||||
---
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
linktitle: ""
|
||||
description: ""
|
||||
godocref: ""
|
||||
publishdate: ""
|
||||
lastmod: ""
|
||||
categories: [functions]
|
||||
@@ -14,4 +13,4 @@ aliases: []
|
||||
relatedfuncs: []
|
||||
toc: false
|
||||
deprecated: false
|
||||
---
|
||||
---
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: "{{ replace .Name "-" " " | title }}"
|
||||
description: ""
|
||||
date: {{ .Date }}
|
||||
---
|
||||
|
||||
@@ -2,6 +2,7 @@ baseURL = "https://gohugo.io/"
|
||||
paginate = 100
|
||||
defaultContentLanguage = "en"
|
||||
enableEmoji = true
|
||||
timeZone = "Europe/Oslo"
|
||||
# Set the unicode character used for the "return" link in page footnotes.
|
||||
footnotereturnlinkcontents = "↩"
|
||||
languageCode = "en-us"
|
||||
|
||||
@@ -42,8 +42,8 @@ maxAge = -1
|
||||
dir = ":cacheDir/:project"
|
||||
maxAge = -1
|
||||
[caches.images]
|
||||
dir = ":resourceDir/_gen"
|
||||
maxAge = -1
|
||||
dir = ":cacheDir/images"
|
||||
maxAge = "1440h"
|
||||
[caches.assets]
|
||||
dir = ":resourceDir/_gen"
|
||||
maxAge = -1
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
enableInlineShortcodes = false
|
||||
|
||||
[exec]
|
||||
allow = ['^go$']
|
||||
osEnv = ['^PATH$']
|
||||
|
||||
[funcs]
|
||||
getenv = ['^HUGO_', '^REPOSITORY_URL$', '^BRANCH$']
|
||||
|
||||
[http]
|
||||
methods = ['(?i)GET|POST']
|
||||
urls = ['.*']
|
||||
@@ -21,20 +21,35 @@ Hugo produces static output, so once built, the runtime is the browser (assuming
|
||||
|
||||
But when developing and building your site, the runtime is the `hugo` executable. Securing a runtime can be [a real challenge](https://blog.logrocket.com/how-to-protect-your-node-js-applications-from-malicious-dependencies-5f2e60ea08f9/).
|
||||
|
||||
**Hugo's main approach is that of sandboxing:**
|
||||
**Hugo's main approach is that of sandboxing and a security policy with strict defaults:**
|
||||
|
||||
* Hugo has a virtual file system and only the main project (not third-party components) is allowed to mount directories or files outside the project root.
|
||||
* Only the main project can walk symbolic links.
|
||||
* User-defined components have only read-access to the filesystem.
|
||||
* We shell out to some external binaries to support [Asciidoctor](/content-management/formats/#list-of-content-formats) and similar, but those binaries and their flags are predefined. General functions to run arbitrary external OS commands have been [discussed](https://github.com/gohugoio/hugo/issues/796), but not implemented because of security concerns.
|
||||
* User-defined components have read-only access to the filesystem.
|
||||
* We shell out to some external binaries to support [Asciidoctor](/content-management/formats/#list-of-content-formats) and similar, but those binaries and their flags are predefined and disabled by default (see [Security Policy](#security-policy)). General functions to run arbitrary external OS commands have been [discussed](https://github.com/gohugoio/hugo/issues/796), but not implemented because of security concerns.
|
||||
|
||||
Hugo will soon introduce a concept of _Content Source Plugins_ (AKA _Pages from Data_), but the above will still hold true.
|
||||
|
||||
## Security Policy
|
||||
|
||||
{{< new-in "0.91.0" >}}
|
||||
|
||||
Hugo has a built-in security policy that restricts access to [os/exec](https://pkg.go.dev/os/exec), remote communication and similar.
|
||||
|
||||
The default configuration is listed below. Any build using features not in the allow list of the security policy will fail with a detailed message about what needs to be done. Most of these settings are allow lists (string or slice, [Regular Expressions](https://pkg.go.dev/regexp) or `none` which matches nothing).
|
||||
|
||||
{{< code-toggle config="security" />}}
|
||||
|
||||
Note that these and other config settings in Hugo can be overridden by the OS environment. If you want to block all remote HTTP fetching of data:
|
||||
|
||||
```
|
||||
HUGO_SECURITY_HTTP_URLS=none hugo
|
||||
```
|
||||
|
||||
## Dependency Security
|
||||
|
||||
Hugo builds as a static binary using [Go Modules](https://github.com/golang/go/wiki/Modules) to manage its dependencies. Go Modules have several safeguards, one of them being the `go.sum` file. This is a database of the expected cryptographic checksums of all of your dependencies, including any transitive.
|
||||
Hugo is built as a static binary using [Go Modules](https://github.com/golang/go/wiki/Modules) to manage its dependencies. Go Modules have several safeguards, one of them being the `go.sum` file. This is a database of the expected cryptographic checksums of all of your dependencies, including transitive dependencies.
|
||||
|
||||
[Hugo Modules](/hugo-modules/) is built on top of Go Modules functionality, and a Hugo project using Hugo Modules will have a `go.sum` file. We recommend that you commit this file to your version control system. The Hugo build will fail if there is a checksum mismatch, which would be an indication of [dependency tampering](https://julienrenaux.fr/2019/12/20/github-actions-security-risk/).
|
||||
[Hugo Modules](/hugo-modules/) is a feature built on top of the functionality of Go Modules. Like Go Modules, a Hugo project using Hugo Modules will have a `go.sum` file. We recommend that you commit this file to your version control system. The Hugo build will fail if there is a checksum mismatch, which would be an indication of [dependency tampering](https://julienrenaux.fr/2019/12/20/github-actions-security-risk/).
|
||||
|
||||
## Web Application Security
|
||||
|
||||
|
||||
@@ -35,14 +35,12 @@ hugo [flags]
|
||||
--debug debug output
|
||||
-d, --destination string filesystem path to write files to
|
||||
--disableKinds strings disable different kind of pages (home, RSS etc.)
|
||||
--enableGitInfo add Git revision, date and author info to the pages
|
||||
--enableGitInfo add Git revision, date, author, and CODEOWNERS info to the pages
|
||||
-e, --environment string build environment
|
||||
--forceSyncStatic copy all files when static is changed.
|
||||
--gc enable to run some cleanup tasks (remove unused cache files) after the build
|
||||
-h, --help help for hugo
|
||||
--i18n-warnings print missing translations
|
||||
--ignoreCache ignores the cache directory
|
||||
--ignoreVendor ignores any _vendor directory
|
||||
--ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
|
||||
-l, --layoutDir string filesystem path to layout directory
|
||||
--log enable Logging
|
||||
@@ -50,9 +48,12 @@ hugo [flags]
|
||||
--minify minify any supported output format (HTML, XML etc.)
|
||||
--noChmod don't sync permission mode of files
|
||||
--noTimes don't sync modification time of files
|
||||
--path-warnings print warnings on duplicate target paths etc.
|
||||
--panicOnWarning panic on first WARNING log
|
||||
--poll string set this to a poll interval, e.g --poll 700ms, to use a poll based approach to watch for file system changes
|
||||
--print-mem print memory usage to screen at intervals
|
||||
--printI18nWarnings print missing translations
|
||||
--printMemoryUsage print memory usage to screen at intervals
|
||||
--printPathWarnings print warnings on duplicate target paths etc.
|
||||
--printUnusedTemplates print warnings on unused templates.
|
||||
--quiet build in quiet mode
|
||||
--renderToMemory render to memory (only useful for benchmark testing)
|
||||
-s, --source string filesystem path to read files relative from
|
||||
@@ -68,8 +69,7 @@ hugo [flags]
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [hugo check](/commands/hugo_check/) - Contains some verification checks
|
||||
* [hugo completion](/commands/hugo_completion/) - generate the autocompletion script for the specified shell
|
||||
* [hugo completion](/commands/hugo_completion/) - Generate the autocompletion script for the specified shell
|
||||
* [hugo config](/commands/hugo_config/) - Print the site configuration
|
||||
* [hugo convert](/commands/hugo_convert/) - Convert your content to different formats
|
||||
* [hugo deploy](/commands/hugo_deploy/) - Deploy your site to a Cloud provider.
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
---
|
||||
title: "hugo check"
|
||||
slug: hugo_check
|
||||
url: /commands/hugo_check/
|
||||
---
|
||||
## hugo check
|
||||
|
||||
Contains some verification checks
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for check
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
-e, --environment string build environment
|
||||
--ignoreVendor ignores any _vendor directory
|
||||
--ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
|
||||
--log enable Logging
|
||||
--logFile string log File path (if set, logging enabled automatically)
|
||||
--quiet build in quiet mode
|
||||
-s, --source string filesystem path to read files relative from
|
||||
--themesDir string filesystem path to themes directory
|
||||
-v, --verbose verbose output
|
||||
--verboseLog verbose logging
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [hugo](/commands/hugo/) - hugo builds your site
|
||||
* [hugo check ulimit](/commands/hugo_check_ulimit/) - Check system ulimit settings
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
---
|
||||
title: "hugo check ulimit"
|
||||
slug: hugo_check_ulimit
|
||||
url: /commands/hugo_check_ulimit/
|
||||
---
|
||||
## hugo check ulimit
|
||||
|
||||
Check system ulimit settings
|
||||
|
||||
### Synopsis
|
||||
|
||||
Hugo will inspect the current ulimit settings on the system.
|
||||
This is primarily to ensure that Hugo can watch enough files on some OSs
|
||||
|
||||
```
|
||||
hugo check ulimit [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for ulimit
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
-e, --environment string build environment
|
||||
--ignoreVendor ignores any _vendor directory
|
||||
--ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
|
||||
--log enable Logging
|
||||
--logFile string log File path (if set, logging enabled automatically)
|
||||
--quiet build in quiet mode
|
||||
-s, --source string filesystem path to read files relative from
|
||||
--themesDir string filesystem path to themes directory
|
||||
-v, --verbose verbose output
|
||||
--verboseLog verbose logging
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [hugo check](/commands/hugo_check/) - Contains some verification checks
|
||||
|
||||
@@ -5,11 +5,10 @@ url: /commands/hugo_completion/
|
||||
---
|
||||
## hugo completion
|
||||
|
||||
generate the autocompletion script for the specified shell
|
||||
Generate the autocompletion script for the specified shell
|
||||
|
||||
### Synopsis
|
||||
|
||||
|
||||
Generate the autocompletion script for hugo for the specified shell.
|
||||
See each sub-command's help for details on how to use the generated script.
|
||||
|
||||
@@ -27,7 +26,6 @@ See each sub-command's help for details on how to use the generated script.
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
-e, --environment string build environment
|
||||
--ignoreVendor ignores any _vendor directory
|
||||
--ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
|
||||
--log enable Logging
|
||||
--logFile string log File path (if set, logging enabled automatically)
|
||||
@@ -41,8 +39,8 @@ See each sub-command's help for details on how to use the generated script.
|
||||
### SEE ALSO
|
||||
|
||||
* [hugo](/commands/hugo/) - hugo builds your site
|
||||
* [hugo completion bash](/commands/hugo_completion_bash/) - generate the autocompletion script for bash
|
||||
* [hugo completion fish](/commands/hugo_completion_fish/) - generate the autocompletion script for fish
|
||||
* [hugo completion powershell](/commands/hugo_completion_powershell/) - generate the autocompletion script for powershell
|
||||
* [hugo completion zsh](/commands/hugo_completion_zsh/) - generate the autocompletion script for zsh
|
||||
* [hugo completion bash](/commands/hugo_completion_bash/) - Generate the autocompletion script for bash
|
||||
* [hugo completion fish](/commands/hugo_completion_fish/) - Generate the autocompletion script for fish
|
||||
* [hugo completion powershell](/commands/hugo_completion_powershell/) - Generate the autocompletion script for powershell
|
||||
* [hugo completion zsh](/commands/hugo_completion_zsh/) - Generate the autocompletion script for zsh
|
||||
|
||||
|
||||
@@ -5,27 +5,31 @@ url: /commands/hugo_completion_bash/
|
||||
---
|
||||
## hugo completion bash
|
||||
|
||||
generate the autocompletion script for bash
|
||||
Generate the autocompletion script for bash
|
||||
|
||||
### Synopsis
|
||||
|
||||
|
||||
Generate the autocompletion script for the bash shell.
|
||||
|
||||
This script depends on the 'bash-completion' package.
|
||||
If it is not installed already, you can install it via your OS's package manager.
|
||||
|
||||
To load completions in your current shell session:
|
||||
$ source <(hugo completion bash)
|
||||
|
||||
source <(hugo completion bash)
|
||||
|
||||
To load completions for every new session, execute once:
|
||||
Linux:
|
||||
$ hugo completion bash > /etc/bash_completion.d/hugo
|
||||
MacOS:
|
||||
$ hugo completion bash > /usr/local/etc/bash_completion.d/hugo
|
||||
|
||||
#### Linux:
|
||||
|
||||
hugo completion bash > /etc/bash_completion.d/hugo
|
||||
|
||||
#### macOS:
|
||||
|
||||
hugo completion bash > /usr/local/etc/bash_completion.d/hugo
|
||||
|
||||
You will need to start a new shell for this setup to take effect.
|
||||
|
||||
|
||||
|
||||
```
|
||||
hugo completion bash
|
||||
@@ -45,7 +49,6 @@ hugo completion bash
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
-e, --environment string build environment
|
||||
--ignoreVendor ignores any _vendor directory
|
||||
--ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
|
||||
--log enable Logging
|
||||
--logFile string log File path (if set, logging enabled automatically)
|
||||
@@ -58,5 +61,5 @@ hugo completion bash
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [hugo completion](/commands/hugo_completion/) - generate the autocompletion script for the specified shell
|
||||
* [hugo completion](/commands/hugo_completion/) - Generate the autocompletion script for the specified shell
|
||||
|
||||
|
||||
@@ -5,18 +5,19 @@ url: /commands/hugo_completion_fish/
|
||||
---
|
||||
## hugo completion fish
|
||||
|
||||
generate the autocompletion script for fish
|
||||
Generate the autocompletion script for fish
|
||||
|
||||
### Synopsis
|
||||
|
||||
|
||||
Generate the autocompletion script for the fish shell.
|
||||
|
||||
To load completions in your current shell session:
|
||||
$ hugo completion fish | source
|
||||
|
||||
hugo completion fish | source
|
||||
|
||||
To load completions for every new session, execute once:
|
||||
$ hugo completion fish > ~/.config/fish/completions/hugo.fish
|
||||
|
||||
hugo completion fish > ~/.config/fish/completions/hugo.fish
|
||||
|
||||
You will need to start a new shell for this setup to take effect.
|
||||
|
||||
@@ -39,7 +40,6 @@ hugo completion fish [flags]
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
-e, --environment string build environment
|
||||
--ignoreVendor ignores any _vendor directory
|
||||
--ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
|
||||
--log enable Logging
|
||||
--logFile string log File path (if set, logging enabled automatically)
|
||||
@@ -52,5 +52,5 @@ hugo completion fish [flags]
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [hugo completion](/commands/hugo_completion/) - generate the autocompletion script for the specified shell
|
||||
* [hugo completion](/commands/hugo_completion/) - Generate the autocompletion script for the specified shell
|
||||
|
||||
|
||||
@@ -5,15 +5,15 @@ url: /commands/hugo_completion_powershell/
|
||||
---
|
||||
## hugo completion powershell
|
||||
|
||||
generate the autocompletion script for powershell
|
||||
Generate the autocompletion script for powershell
|
||||
|
||||
### Synopsis
|
||||
|
||||
|
||||
Generate the autocompletion script for powershell.
|
||||
|
||||
To load completions in your current shell session:
|
||||
PS C:\> hugo completion powershell | Out-String | Invoke-Expression
|
||||
|
||||
hugo completion powershell | Out-String | Invoke-Expression
|
||||
|
||||
To load completions for every new session, add the output of the above command
|
||||
to your powershell profile.
|
||||
@@ -37,7 +37,6 @@ hugo completion powershell [flags]
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
-e, --environment string build environment
|
||||
--ignoreVendor ignores any _vendor directory
|
||||
--ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
|
||||
--log enable Logging
|
||||
--logFile string log File path (if set, logging enabled automatically)
|
||||
@@ -50,5 +49,5 @@ hugo completion powershell [flags]
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [hugo completion](/commands/hugo_completion/) - generate the autocompletion script for the specified shell
|
||||
* [hugo completion](/commands/hugo_completion/) - Generate the autocompletion script for the specified shell
|
||||
|
||||
|
||||
@@ -5,23 +5,26 @@ url: /commands/hugo_completion_zsh/
|
||||
---
|
||||
## hugo completion zsh
|
||||
|
||||
generate the autocompletion script for zsh
|
||||
Generate the autocompletion script for zsh
|
||||
|
||||
### Synopsis
|
||||
|
||||
|
||||
Generate the autocompletion script for the zsh shell.
|
||||
|
||||
If shell completion is not already enabled in your environment you will need
|
||||
to enable it. You can execute the following once:
|
||||
|
||||
$ echo "autoload -U compinit; compinit" >> ~/.zshrc
|
||||
echo "autoload -U compinit; compinit" >> ~/.zshrc
|
||||
|
||||
To load completions for every new session, execute once:
|
||||
# Linux:
|
||||
$ hugo completion zsh > "${fpath[1]}/_hugo"
|
||||
# macOS:
|
||||
$ hugo completion zsh > /usr/local/share/zsh/site-functions/_hugo
|
||||
|
||||
#### Linux:
|
||||
|
||||
hugo completion zsh > "${fpath[1]}/_hugo"
|
||||
|
||||
#### macOS:
|
||||
|
||||
hugo completion zsh > /usr/local/share/zsh/site-functions/_hugo
|
||||
|
||||
You will need to start a new shell for this setup to take effect.
|
||||
|
||||
@@ -44,7 +47,6 @@ hugo completion zsh [flags]
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
-e, --environment string build environment
|
||||
--ignoreVendor ignores any _vendor directory
|
||||
--ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
|
||||
--log enable Logging
|
||||
--logFile string log File path (if set, logging enabled automatically)
|
||||
@@ -57,5 +59,5 @@ hugo completion zsh [flags]
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [hugo completion](/commands/hugo_completion/) - generate the autocompletion script for the specified shell
|
||||
* [hugo completion](/commands/hugo_completion/) - Generate the autocompletion script for the specified shell
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ hugo config [flags]
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
-e, --environment string build environment
|
||||
--ignoreVendor ignores any _vendor directory
|
||||
--ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
|
||||
--log enable Logging
|
||||
--logFile string log File path (if set, logging enabled automatically)
|
||||
|
||||
@@ -24,7 +24,6 @@ hugo config mounts [flags]
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
-e, --environment string build environment
|
||||
--ignoreVendor ignores any _vendor directory
|
||||
--ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
|
||||
--log enable Logging
|
||||
--logFile string log File path (if set, logging enabled automatically)
|
||||
|
||||
@@ -28,7 +28,6 @@ See convert's subcommands toJSON, toTOML and toYAML for more information.
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
-e, --environment string build environment
|
||||
--ignoreVendor ignores any _vendor directory
|
||||
--ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
|
||||
--log enable Logging
|
||||
--logFile string log File path (if set, logging enabled automatically)
|
||||
|
||||
@@ -29,7 +29,6 @@ hugo convert toJSON [flags]
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
-e, --environment string build environment
|
||||
--ignoreVendor ignores any _vendor directory
|
||||
--ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
|
||||
--log enable Logging
|
||||
--logFile string log File path (if set, logging enabled automatically)
|
||||
|
||||
@@ -29,7 +29,6 @@ hugo convert toTOML [flags]
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
-e, --environment string build environment
|
||||
--ignoreVendor ignores any _vendor directory
|
||||
--ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
|
||||
--log enable Logging
|
||||
--logFile string log File path (if set, logging enabled automatically)
|
||||
|
||||
@@ -29,7 +29,6 @@ hugo convert toYAML [flags]
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
-e, --environment string build environment
|
||||
--ignoreVendor ignores any _vendor directory
|
||||
--ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
|
||||
--log enable Logging
|
||||
--logFile string log File path (if set, logging enabled automatically)
|
||||
|
||||
@@ -38,7 +38,6 @@ hugo deploy [flags]
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
-e, --environment string build environment
|
||||
--ignoreVendor ignores any _vendor directory
|
||||
--ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
|
||||
--log enable Logging
|
||||
--logFile string log File path (if set, logging enabled automatically)
|
||||
|
||||
@@ -31,7 +31,6 @@ hugo env [flags]
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
-e, --environment string build environment
|
||||
--ignoreVendor ignores any _vendor directory
|
||||
--ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
|
||||
--log enable Logging
|
||||
--logFile string log File path (if set, logging enabled automatically)
|
||||
|
||||
@@ -20,7 +20,6 @@ A collection of several useful generators.
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
-e, --environment string build environment
|
||||
--ignoreVendor ignores any _vendor directory
|
||||
--ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
|
||||
--log enable Logging
|
||||
--logFile string log File path (if set, logging enabled automatically)
|
||||
@@ -34,7 +33,6 @@ A collection of several useful generators.
|
||||
### SEE ALSO
|
||||
|
||||
* [hugo](/commands/hugo/) - hugo builds your site
|
||||
* [hugo gen autocomplete](/commands/hugo_gen_autocomplete/) - Generate shell autocompletion script for Hugo
|
||||
* [hugo gen chromastyles](/commands/hugo_gen_chromastyles/) - Generate CSS stylesheet for the Chroma code highlighter
|
||||
* [hugo gen doc](/commands/hugo_gen_doc/) - Generate Markdown documentation for the Hugo CLI.
|
||||
* [hugo gen man](/commands/hugo_gen_man/) - Generate man pages for the Hugo CLI
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
---
|
||||
title: "hugo gen autocomplete"
|
||||
slug: hugo_gen_autocomplete
|
||||
url: /commands/hugo_gen_autocomplete/
|
||||
---
|
||||
## hugo gen autocomplete
|
||||
|
||||
Generate shell autocompletion script for Hugo
|
||||
|
||||
### Synopsis
|
||||
|
||||
Generates a shell autocompletion script for Hugo.
|
||||
|
||||
The script is written to the console (stdout).
|
||||
|
||||
To write to file, add the `--completionfile=/path/to/file` flag.
|
||||
|
||||
Add `--type={bash, zsh, fish or powershell}` flag to set alternative
|
||||
shell type.
|
||||
|
||||
Logout and in again to reload the completion scripts,
|
||||
or just source them in directly:
|
||||
|
||||
$ . /etc/bash_completion or /path/to/file
|
||||
|
||||
```
|
||||
hugo gen autocomplete [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-f, --completionfile string autocompletion file, defaults to stdout
|
||||
-h, --help help for autocomplete
|
||||
-t, --type string autocompletion type (bash, zsh, fish, or powershell) (default "bash")
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
-e, --environment string build environment
|
||||
--ignoreVendor ignores any _vendor directory
|
||||
--ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
|
||||
--log enable Logging
|
||||
--logFile string log File path (if set, logging enabled automatically)
|
||||
--quiet build in quiet mode
|
||||
-s, --source string filesystem path to read files relative from
|
||||
--themesDir string filesystem path to themes directory
|
||||
-v, --verbose verbose output
|
||||
--verboseLog verbose logging
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [hugo gen](/commands/hugo_gen/) - A collection of several useful generators.
|
||||
|
||||
@@ -9,9 +9,9 @@ Generate CSS stylesheet for the Chroma code highlighter
|
||||
|
||||
### Synopsis
|
||||
|
||||
Generate CSS stylesheet for the Chroma code highlighter for a given style. This stylesheet is needed if pygmentsUseClasses is enabled in config.
|
||||
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://help.farbox.com/pygments.html for preview of available styles
|
||||
See https://xyproto.github.io/splash/docs/all.html for a preview of the available styles
|
||||
|
||||
```
|
||||
hugo gen chromastyles [flags]
|
||||
@@ -23,7 +23,7 @@ hugo gen chromastyles [flags]
|
||||
-h, --help help for chromastyles
|
||||
--highlightStyle string style used for highlighting lines (see https://github.com/alecthomas/chroma) (default "bg:#ffffcc")
|
||||
--linesStyle string style used for line numbers (see https://github.com/alecthomas/chroma)
|
||||
--style string highlighter style (see https://help.farbox.com/pygments.html) (default "friendly")
|
||||
--style string highlighter style (see https://xyproto.github.io/splash/docs/) (default "friendly")
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
@@ -33,7 +33,6 @@ hugo gen chromastyles [flags]
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
-e, --environment string build environment
|
||||
--ignoreVendor ignores any _vendor directory
|
||||
--ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
|
||||
--log enable Logging
|
||||
--logFile string log File path (if set, logging enabled automatically)
|
||||
|
||||
@@ -35,7 +35,6 @@ hugo gen doc [flags]
|
||||
--configDir string config dir (default "config")
|
||||
--debug debug output
|
||||
-e, --environment string build environment
|
||||
--ignoreVendor ignores any _vendor directory
|
||||
--ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
|
||||
--log enable Logging
|
||||
--logFile string log File path (if set, logging enabled automatically)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user