mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-28 17:22:38 +00:00
Compare commits
506 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 207913f349 | |||
| 830b1a94de | |||
| 605cff4071 | |||
| 6dd60fca73 | |||
| 138a025916 | |||
| 58ac83a98c | |||
| 2b8e8e6d96 | |||
| aef8369865 | |||
| f9e798e8c4 | |||
| ee56efffcb | |||
| 2c4e76e962 | |||
| 9c7d6e475c | |||
| 4482958f96 | |||
| e1ec3bc2b2 | |||
| 1cfa63b9ec | |||
| b9318e4315 | |||
| 5e39eb20a6 | |||
| e556848805 | |||
| 634938908e | |||
| c63db7f1f6 | |||
| 5e2a547cb5 | |||
| ee090c0940 | |||
| edc5c4741c | |||
| 05e358fd33 | |||
| a2e85d9a75 | |||
| 4fba78dd0e | |||
| c011b4667f | |||
| 35348b4b34 | |||
| 34915777c2 | |||
| 0f4a837ed1 | |||
| b395d686e9 | |||
| 97987e5c02 | |||
| 111344113b | |||
| 4855c186d8 | |||
| 0c3d2b67e0 | |||
| 6f07ec7e9e | |||
| 4318dc72f8 | |||
| acdc27a32d | |||
| 3acde9ae04 | |||
| 473b6610d5 | |||
| 0bce97703c | |||
| b254532b52 | |||
| 05a2289292 | |||
| 8e553dcdef | |||
| d4fc70a3b3 | |||
| d905abc002 | |||
| 8f3946746d | |||
| b01b2564ee | |||
| 9fa5ebe2c4 | |||
| efaed306b1 | |||
| a4a7bab734 | |||
| 2a9dce423f | |||
| 30159b58e9 | |||
| cd00f7f966 | |||
| 214afe4c1b | |||
| cd830bb027 | |||
| a8458bfb25 | |||
| fef924baf1 | |||
| 07b5e602f8 | |||
| 70f16e84e2 | |||
| abadf29542 | |||
| 377ad87a51 | |||
| 292b0e26ec | |||
| 534ae9c57a | |||
| df56682a10 | |||
| be2404c8b1 | |||
| c8da8eb1f5 | |||
| 787da6bd5f | |||
| 85ba9bfffb | |||
| 9df60b62f9 | |||
| 4fad43c8bd | |||
| fb0f2cc718 | |||
| 748fd4cb0d | |||
| b82f440c59 | |||
| c8143efa5d | |||
| c80132bbe5 | |||
| 75fa4c5c95 | |||
| fd7969e0b0 | |||
| b7fa3c4bba | |||
| 6a848cbc3a | |||
| 746ba803af | |||
| 612b7d376f | |||
| 6f4ff1a461 | |||
| ddeca45933 | |||
| 31f2091f58 | |||
| d4611c4322 | |||
| 20af9a0781 | |||
| 9a1e6d15a3 | |||
| 84adecf97b | |||
| 573558a078 | |||
| 8b10c22f82 | |||
| 195bd1243b | |||
| 6a544ece24 | |||
| 4b430d456a | |||
| b9f10c75cb | |||
| 537c598e9a | |||
| 67348676f7 | |||
| f9cc0ec76e | |||
| b5483eed6e | |||
| 90285f4750 | |||
| 4949bdc2ef | |||
| f7c1b5fe1c | |||
| 183e862607 | |||
| f50ee6bbe5 | |||
| c0655ba6ce | |||
| a2dda22c36 | |||
| 910d81a692 | |||
| 8c490a73b3 | |||
| e6cd9da42d | |||
| dcf25c0b49 | |||
| 7d7771b673 | |||
| b9e4f5898b | |||
| e820b366b9 | |||
| 4055c12184 | |||
| 3ba7c92530 | |||
| 19ef27b98e | |||
| c6b661de82 | |||
| cdfd1c99ba | |||
| 047af7cfe5 | |||
| f9ebaaed1b | |||
| d39636a5fc | |||
| ad01aea3f4 | |||
| ec37420468 | |||
| ae63c2b5c9 | |||
| e627449c0a | |||
| c2235c6a62 | |||
| 4f69ade711 | |||
| f8b8b091fc | |||
| cb39847dee | |||
| 5f42590144 | |||
| f3cb0be35a | |||
| bffc4e12fe | |||
| 12f6a1cdc0 | |||
| 2fa851e650 | |||
| 21dbfa1f11 | |||
| e5591e89d3 | |||
| 88929bc23f | |||
| 850c18bfe4 | |||
| da0437b481 | |||
| 90fe00df07 | |||
| 02efadc243 | |||
| 00e00da233 | |||
| 45c665d396 | |||
| a06c06a5c2 | |||
| 0256959a35 | |||
| eded9ac2a0 | |||
| 8d72512825 | |||
| e81aef0a95 | |||
| 673e622fa9 | |||
| 28bd06265e | |||
| 9f9191471e | |||
| 3727a9d111 | |||
| 48565de62f | |||
| 808e126216 | |||
| aa85a46dc6 | |||
| 35011bcb26 | |||
| 084624baac | |||
| 15163266c1 | |||
| a74f7d3cc0 | |||
| cb84d98169 | |||
| c91dbe4ce9 | |||
| 6e0452e189 | |||
| d2b1162680 | |||
| 6265184309 | |||
| 797127010d | |||
| 1672a332da | |||
| 823ce055ed | |||
| 5f7a65a080 | |||
| 25e3da3343 | |||
| 9df98ec49c | |||
| 2fc3380707 | |||
| f1916f114b | |||
| 6aa5c9117f | |||
| 12a65e76df | |||
| 58c0f5e617 | |||
| a1c3e3c1f3 | |||
| 42e150fbfa | |||
| 028b356787 | |||
| e9f87c4e3f | |||
| defd7106bf | |||
| beb6c03bc8 | |||
| 4a3efea7ef | |||
| c66dc6c74f | |||
| 127d5feb32 | |||
| 2d42ba912b | |||
| 5b7b5dea1f | |||
| ccfaeb678b | |||
| 057b1377c5 | |||
| 48dbb593f7 | |||
| f0266e2ef3 | |||
| 77aa385b84 | |||
| 545a1c1ced | |||
| 47aaa52e39 | |||
| 428907cc34 | |||
| a78b3e3415 | |||
| cfcb014515 | |||
| 0b579db80f | |||
| ee5d027cd5 | |||
| 4a340ba254 | |||
| 3466884e36 | |||
| 6ff435aa3f | |||
| 82abca32fa | |||
| fc045e12a9 | |||
| 9679023f2b | |||
| 0a9172672a | |||
| efa74c5c6e | |||
| 6408c1cbc8 | |||
| 522ba1cd98 | |||
| 889dc47ceb | |||
| f720fe56db | |||
| d6ed17c60f | |||
| 145b3fcce3 | |||
| 01e249e97c | |||
| 4b560cc119 | |||
| e3e627e6b7 | |||
| 83d03a5201 | |||
| fc0f13b684 | |||
| 7eeebe1e5a | |||
| 740fa4a91d | |||
| f8c67f93e1 | |||
| 3d9235e8fc | |||
| f7d909f394 | |||
| 8a7ef3cf4e | |||
| 2dfe242ea6 | |||
| 7a1464e547 | |||
| 41d50b4dd3 | |||
| 4d53ae697e | |||
| 626b16e024 | |||
| 9e1dcefc5f | |||
| 2919a6a503 | |||
| 432885c499 | |||
| 6a3e89743c | |||
| 9613e3e8a8 | |||
| c950c86b4e | |||
| 915202494b | |||
| b3e4f911f4 | |||
| a301f6b2ae | |||
| 646bc85088 | |||
| 5c0d10045d | |||
| 81f5633245 | |||
| 9698b0dab1 | |||
| 32344fe3db | |||
| 4d7fa9f114 | |||
| a985efcecf | |||
| 6c3c6686f5 | |||
| c34bf48560 | |||
| 833d16d467 | |||
| 06150c87be | |||
| 330e52ebe6 | |||
| 9e78235374 | |||
| 7cd66c53b9 | |||
| 723ec555e7 | |||
| 518d149646 | |||
| e0e81b280f | |||
| c9403cbcea | |||
| 3cc41523be | |||
| 2fd0a5a678 | |||
| 6e051c053e | |||
| 423b8f2fb8 | |||
| 991934497e | |||
| 558c09305e | |||
| b69a36140f | |||
| a0103864ab | |||
| b342e8fbdb | |||
| 6205d56b85 | |||
| a5039dddaf | |||
| 7f47b99ea9 | |||
| b98e2f66b0 | |||
| 57ebab7c23 | |||
| 0e314925fb | |||
| e4621446ce | |||
| 89044b8f87 | |||
| 01befcce35 | |||
| 04b1a6d997 | |||
| dd31e80007 | |||
| 6add6d77b4 | |||
| 2a171ff1c5 | |||
| 67f920419a | |||
| c03ea2b660 | |||
| fe60b7d9e4 | |||
| c2d9fd1ebe | |||
| ec9dcf3048 | |||
| 03802ff3cb | |||
| 5e31198c98 | |||
| 8d5766d417 | |||
| 5c41f41ad4 | |||
| 27a4c4410c | |||
| ade27699e5 | |||
| 17661debb6 | |||
| 8549189e8e | |||
| c2c7a4ce5b | |||
| 49e6c8cb4e | |||
| f37e77f2d3 | |||
| 27af5a339a | |||
| b3c825756f | |||
| 5146dc614f | |||
| 736f84b2d5 | |||
| cd4d820201 | |||
| 2b28e5a9cb | |||
| 102ec2da7a | |||
| feaa582cbe | |||
| ee67dbeff5 | |||
| 4a3f2427ed | |||
| 4205844bce | |||
| 9b55d13589 | |||
| 6f56a636f6 | |||
| 5ec7fa343a | |||
| b7ff4dc23e | |||
| da3c3e5fbd | |||
| 30748decf1 | |||
| 095bf64c99 | |||
| 7791a804e2 | |||
| c774b230e9 | |||
| 2f721f8ec6 | |||
| 8568928aa8 | |||
| 1bc93021e3 | |||
| 7eba37ae9b | |||
| efc61d6f3b | |||
| d8d6a25b57 | |||
| 9f12be54ee | |||
| 4437e918cd | |||
| 4de3ecdc26 | |||
| 1123711b09 | |||
| 3d84ef9720 | |||
| c494c37a45 | |||
| 9c9987535f | |||
| 4a39564efe | |||
| b6e097cfe6 | |||
| 19a8accc9f | |||
| 157669a0e6 | |||
| 8f49df946a | |||
| 1a68ad4e31 | |||
| 523d51948f | |||
| 971b28904b | |||
| 63bbb40dfb | |||
| 73ae6d581d | |||
| e18a2ad3f4 | |||
| cfa73050a4 | |||
| 244e49c0ea | |||
| 98dc46e1a9 | |||
| 87dd5725de | |||
| 8c6a03a92b | |||
| 1ce3e7d521 | |||
| c9dc316ad1 | |||
| 1664a0e893 | |||
| 1e67854b1a | |||
| e1b609af9b | |||
| 8e5566af6e | |||
| 1005f754e0 | |||
| efde7078ed | |||
| 2ebb9f5484 | |||
| 7204b354a9 | |||
| 574c2959b8 | |||
| 99958f90fe | |||
| 1d91d8e14b | |||
| 95f492114e | |||
| c947351d7c | |||
| aa54803a84 | |||
| cc2a5d52a4 | |||
| 52c159c452 | |||
| c7b6d74e89 | |||
| 1a8af7d4f0 | |||
| 03b93bb988 | |||
| 94fb4dc3dd | |||
| 4f44227bd8 | |||
| 99d36237c0 | |||
| e371162c3f | |||
| 5eadc4c0a8 | |||
| 18cb21ff2e | |||
| df298558a5 | |||
| b1106f8715 | |||
| c0177fe2b2 | |||
| 5914f91b6c | |||
| 5b4659fa0b | |||
| 7f1da3efcd | |||
| f7d4b01c6d | |||
| b809b9680d | |||
| a9c91361c2 | |||
| 63393230c9 | |||
| 6b61f2a5bb | |||
| 14e369b961 | |||
| 6cceef65c2 | |||
| ffcb4aeb8e | |||
| 8279d2e227 | |||
| c4fa2f0799 | |||
| 108314444b | |||
| 51e178a6a2 | |||
| cb12f41a96 | |||
| 78c3c78fca | |||
| 713132cd4b | |||
| bbaefd7e3e | |||
| cc1a718860 | |||
| 6a34f88dcc | |||
| ae383f04c8 | |||
| ee31e61fb0 | |||
| 760a87a45a | |||
| 3d3fa5c3fe | |||
| ee3d02134d | |||
| 8947c3fa0b | |||
| d7798906d8 | |||
| 305ce1c9ec | |||
| 3e9db2ad95 | |||
| 449deb7f9c | |||
| 1746e8a9b2 | |||
| 6f48146e75 | |||
| b0d850321e | |||
| b66d38c419 | |||
| 05a74eaec0 | |||
| 33ae621083 | |||
| 1352bc880d | |||
| d184e5059c | |||
| 322c285ba2 | |||
| 211ba42a95 | |||
| 0ac528d746 | |||
| d8bde266c1 | |||
| 0bd6356c6d | |||
| ca68abf0bc | |||
| a524124beb | |||
| c1eb625124 | |||
| 1510f07780 | |||
| aa2ef45421 | |||
| 76b2afe642 | |||
| f460530347 | |||
| 4c2a0de412 | |||
| 6be6684cc5 | |||
| dbaa15d40f | |||
| a449e87da7 | |||
| 7ef5a4c83e | |||
| a70bbd0696 | |||
| 24afe2b822 | |||
| 9fd7d3957e | |||
| c45025cb90 | |||
| a5ebdf7d17 | |||
| 9bdedb251c | |||
| dce210ab56 | |||
| 0b96aba022 | |||
| fa520a2d98 | |||
| 4b670bc8cc | |||
| 82029c1ec9 | |||
| 7489a86459 | |||
| da54787cfa | |||
| c7975b48b6 | |||
| b2dcd53e3c | |||
| 36983e6189 | |||
| d73e37387c | |||
| aa3e183056 | |||
| 1b7acfe763 | |||
| 19e12caf8c | |||
| 20f2211fce | |||
| a21a9373e0 | |||
| 775c7c2474 | |||
| eada236f87 | |||
| e5329f13c0 | |||
| 5b145ddc4c | |||
| 3c568ad013 | |||
| 54bdcaacae | |||
| 1622510a5c | |||
| 56d0b65887 | |||
| 75c3787fc2 | |||
| 9babb1f0c4 | |||
| 8a5124d6b3 | |||
| 898a0a96af | |||
| 3b721110d5 | |||
| 40ba7e6d63 | |||
| 4f43c9022a | |||
| c327e75d05 | |||
| 8bd8d4fe9a | |||
| ad7c38cd66 | |||
| b78576fd38 | |||
| 18888e09bb | |||
| 1e5eb8679e | |||
| 3c0036805d | |||
| 7f0ebd4a3c | |||
| 23ea43180b | |||
| 6a74cbe916 | |||
| 241db8f78a | |||
| 7624ab0281 | |||
| 8490a0aa9d | |||
| bd731d27be | |||
| 80dd6ddde2 | |||
| 2997310124 | |||
| 2bbc865f7b | |||
| 0792cfa9fa | |||
| b3f0674b80 | |||
| ef78a0d18a | |||
| 5859586453 | |||
| 2d159e9cc7 | |||
| f45cb31728 | |||
| 49ef647203 | |||
| 8f08cdd0ac | |||
| 281abb18ee | |||
| d8e685154d | |||
| 934ee21fac | |||
| c7427a50ed | |||
| 49e2931eb2 | |||
| e8831a056b | |||
| 74b6c4e5ff | |||
| 8df5d76e70 | |||
| 8ae2c9c3d6 | |||
| ce9aceb749 | |||
| fd32849bfe | |||
| 417f9ddf55 | |||
| 0df7bd62df | |||
| f441f67512 | |||
| 7ed22e9fb6 | |||
| fb974ae87f |
@@ -1,6 +1,6 @@
|
||||
defaults: &defaults
|
||||
docker:
|
||||
- image: bepsays/ci-goreleaser:1.13-4
|
||||
- image: bepsays/ci-goreleaser:1.15.1
|
||||
environment:
|
||||
CGO_ENABLED: "0"
|
||||
|
||||
@@ -17,6 +17,7 @@ jobs:
|
||||
cd hugo
|
||||
go mod download
|
||||
sleep 5
|
||||
go mod verify
|
||||
go test -p 1 ./...
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: 'Bug report'
|
||||
labels: ''
|
||||
labels: 'bug'
|
||||
assignees: ''
|
||||
about: Create a report to help us improve
|
||||
---
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# See https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates#package-ecosystem
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
@@ -20,6 +20,7 @@ dock.sh
|
||||
GoBuilds
|
||||
dist
|
||||
|
||||
hugolib/hugo_stats.json
|
||||
resources/sunset.jpg
|
||||
|
||||
vendor
|
||||
|
||||
+4
-4
@@ -11,10 +11,9 @@ env:
|
||||
|
||||
git:
|
||||
depth: false
|
||||
|
||||
go:
|
||||
- "1.12.13"
|
||||
- "1.13.4"
|
||||
- "1.14.8"
|
||||
- "1.15.1"
|
||||
- master
|
||||
|
||||
arch:
|
||||
@@ -64,7 +63,8 @@ install:
|
||||
- go get github.com/magefile/mage
|
||||
|
||||
script:
|
||||
- go mod download || true
|
||||
- go mod download
|
||||
- go mod verify
|
||||
- mage -v test
|
||||
- if [ "$TRAVIS_ARCH" = "amd64" ]; then
|
||||
mage -v check;
|
||||
|
||||
+4
-4
@@ -48,15 +48,15 @@ Hugo has become a fully featured static site generator, so any new functionality
|
||||
* strive not to break existing sites.
|
||||
* close or update an open [Hugo issue](https://github.com/gohugoio/hugo/issues)
|
||||
|
||||
If it is of some complexity, the contributor is expected to maintain and support the new future (answer questions on the forum, fix any bugs etc.).
|
||||
If it is of some complexity, the contributor is expected to maintain and support the new feature in the future (answer questions on the forum, fix any bugs etc.).
|
||||
|
||||
It is recommended to open up a discussion on the [Hugo Forum](https://discourse.gohugo.io/) to get feedback on your idea before you begin. If you are submitting a complex feature, create a small design proposal on the [Hugo issue tracker](https://github.com/gohugoio/hugo/issues) before you start.
|
||||
|
||||
Note that we do not accept new features that require [CGO](https://github.com/golang/go/wiki/cgo).
|
||||
We have one exception to this rule which is LibSASS.
|
||||
|
||||
**Bug fixes are, of course, always welcome.**
|
||||
|
||||
|
||||
|
||||
## Submitting Patches
|
||||
|
||||
The Hugo project welcomes all contributors and contributions regardless of skill or experience level. If you are interested in helping with the project, we will help you with your contribution.
|
||||
@@ -144,7 +144,7 @@ Now, to make a change to Hugo's source:
|
||||
1. Add your fork as a new remote (the remote name, "fork" in this example, is arbitrary):
|
||||
|
||||
```bash
|
||||
git remote add fork git://github.com/USERNAME/hugo.git
|
||||
git remote add fork git@github.com:USERNAME/hugo.git
|
||||
```
|
||||
|
||||
1. Push the changes to your new remote:
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ COPY --from=build /go/bin/hugo /usr/bin/hugo
|
||||
# libc6-compat & libstdc++ are required for extended SASS libraries
|
||||
# ca-certificates are required to fetch outside resources (like Twitter oEmbeds)
|
||||
RUN apk update && \
|
||||
apk add --no-cache ca-certificates libc6-compat libstdc++
|
||||
apk add --no-cache ca-certificates libc6-compat libstdc++ git
|
||||
|
||||
VOLUME /site
|
||||
WORKDIR /site
|
||||
|
||||
@@ -54,7 +54,7 @@ Use the [installation instructions in the Hugo documentation](https://gohugo.io/
|
||||
#### Prerequisite Tools
|
||||
|
||||
* [Git](https://git-scm.com/)
|
||||
* [Go (at least Go 1.11)](https://golang.org/dl/)
|
||||
* [Go (we test it with the last 2 major versions)](https://golang.org/dl/)
|
||||
|
||||
#### Fetch from GitHub
|
||||
|
||||
@@ -69,7 +69,13 @@ go install
|
||||
```
|
||||
|
||||
**If you are a Windows user, substitute the `$HOME` environment variable above with `%USERPROFILE%`.**
|
||||
|
||||
|
||||
If you want to compile with Sass/SCSS support use `--tags extended` and make sure `CGO_ENABLED=1` is set in your go environment. If you don't want to have CGO enabled, you may use the following command to temporarily enable CGO only for hugo compilation:
|
||||
|
||||
```bash
|
||||
CGO_ENABLED=1 go install --tags extended
|
||||
```
|
||||
|
||||
## The Hugo Documentation
|
||||
|
||||
The Hugo documentation now lives in its own repository, see https://github.com/gohugoio/hugoDocs. But we do keep a version of that documentation as a `git subtree` in this repository. To build the sub folder `/docs` as a Hugo site, you need to clone this repo:
|
||||
@@ -124,64 +130,67 @@ Hugo stands on the shoulder of many great open source libraries, in lexical orde
|
||||
|
||||
| Dependency | License |
|
||||
| :------------- | :------------- |
|
||||
| [github.com/alecthomas/chroma](https://github.com/alecthomas/chroma) | MIT License |
|
||||
| [github.com/armon/go-radix](https://github.com/armon/go-radix) | MIT License |
|
||||
| [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) | Apache License 2.0 |
|
||||
| [github.com/bep/debounce](https://github.com/bep/debounce) | MIT License |
|
||||
| [github.com/bep/gitmap](https://github.com/bep/gitmap) | MIT License |
|
||||
| [github.com/bep/golibsass](https://github.com/bep/golibsass) | MIT License |
|
||||
| [github.com/bep/tmc](https://github.com/bep/tmc) | MIT License |
|
||||
| [github.com/BurntSushi/locker](https://github.com/BurntSushi/locker) | The Unlicense |
|
||||
| [github.com/BurntSushi/toml](https://github.com/BurntSushi/toml) | MIT License |
|
||||
| [github.com/PuerkitoBio/purell](https://github.com/PuerkitoBio/purell) | BSD 3-Clause "New" or "Revised" License |
|
||||
| [github.com/PuerkitoBio/urlesc](https://github.com/PuerkitoBio/urlesc) | BSD 3-Clause "New" or "Revised" License |
|
||||
| [github.com/alecthomas/chroma](https://github.com/alecthomas/chroma) | MIT License |
|
||||
| [github.com/bep/debounce](https://github.com/bep/debounce) | MIT License |
|
||||
| [github.com/bep/gitmap](https://github.com/bep/gitmap) | MIT License |
|
||||
| [github.com/bep/go-tocss](https://github.com/bep/go-tocss) | MIT License |
|
||||
| [github.com/niklasfasching/go-org](https://github.com/niklasfasching/go-org) | MIT License |
|
||||
| [github.com/cpuguy83/go-md2man](https://github.com/cpuguy83/go-md2man) | MIT License |
|
||||
| [github.com/danwakefield/fnmatch](https://github.com/danwakefield/fnmatch) | BSD 2-Clause "Simplified" License |
|
||||
| [github.com/disintegration/imaging](https://github.com/disintegration/imaging) | MIT License |
|
||||
| [github.com/dlclark/regexp2](https://github.com/dlclark/regexp2) | MIT License |
|
||||
| [github.com/eknkc/amber](https://github.com/eknkc/amber) | MIT License |
|
||||
| [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify) | BSD 3-Clause "New" or "Revised" License |
|
||||
| [github.com/disintegration/gift](https://github.com/disintegration/gift) | MIT License |
|
||||
| [github.com/dustin/go-humanize](https://github.com/dustin/go-humanize) | MIT License |
|
||||
| [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify) | BSD 3-Clause "New" or "Revised" License |
|
||||
| [github.com/gobwas/glob](https://github.com/gobwas/glob) | MIT License |
|
||||
| [github.com/gorilla/websocket](https://github.com/gorilla/websocket) | BSD 2-Clause "Simplified" License |
|
||||
| [github.com/hashicorp/go-immutable-radix](https://github.com/hashicorp/go-immutable-radix) | Mozilla Public License 2.0 |
|
||||
| [github.com/gorilla/websocket](https://github.com/gorilla/websocket) | BSD 2-Clause "Simplified" License |
|
||||
| [github.com/hashicorp/golang-lru](https://github.com/hashicorp/golang-lru) | Mozilla Public License 2.0 |
|
||||
| [github.com/hashicorp/hcl](https://github.com/hashicorp/hcl) | Mozilla Public License 2.0 |
|
||||
| [github.com/jdkato/prose](https://github.com/jdkato/prose) | MIT License |
|
||||
| [github.com/kyokomi/emoji](https://github.com/kyokomi/emoji) | MIT License |
|
||||
| [github.com/hashicorp/hcl](https://github.com/hashicorp/hcl) | Mozilla Public License 2.0 |
|
||||
| [github.com/jdkato/prose](https://github.com/jdkato/prose) | MIT License |
|
||||
| [github.com/kr/pretty](https://github.com/kr/pretty) | MIT License |
|
||||
| [github.com/kyokomi/emoji](https://github.com/kyokomi/emoji) | MIT License |
|
||||
| [github.com/magiconair/properties](https://github.com/magiconair/properties) | BSD 2-Clause "Simplified" License |
|
||||
| [github.com/markbates/inflect](https://github.com/markbates/inflect) | MIT License |
|
||||
| [github.com/markbates/inflect](https://github.com/markbates/inflect) | MIT License |
|
||||
| [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) | MIT License |
|
||||
| [github.com/mattn/go-runewidth](https://github.com/mattn/go-runewidth) | MIT License |
|
||||
| [github.com/miekg/mmark](https://github.com/miekg/mmark) | Simplified BSD License |
|
||||
| [github.com/mitchellh/hashstructure](https://github.com/mitchellh/hashstructure) | MIT License |
|
||||
| [github.com/mitchellh/mapstructure](https://github.com/mitchellh/mapstructure) | MIT License |
|
||||
| [github.com/muesli/smartcrop](https://github.com/muesli/smartcrop) | MIT License |
|
||||
| [github.com/muesli/smartcrop](https://github.com/muesli/smartcrop) | MIT License |
|
||||
| [github.com/nicksnyder/go-i18n](https://github.com/nicksnyder/go-i18n) | MIT License |
|
||||
| [github.com/niklasfasching/go-org](https://github.com/niklasfasching/go-org) | MIT License |
|
||||
| [github.com/olekukonko/tablewriter](https://github.com/olekukonko/tablewriter) | MIT License |
|
||||
| [github.com/pelletier/go-toml](https://github.com/pelletier/go-toml) | MIT License |
|
||||
| [github.com/pkg/errors](https://github.com/pkg/errors) | BSD 2-Clause "Simplified" License |
|
||||
| [github.com/pelletier/go-toml](https://github.com/pelletier/go-toml) | MIT License |
|
||||
| [github.com/pkg/errors](https://github.com/pkg/errors) | BSD 2-Clause "Simplified" License |
|
||||
| [github.com/PuerkitoBio/purell](https://github.com/PuerkitoBio/purell) | BSD 3-Clause "New" or "Revised" License |
|
||||
| [github.com/PuerkitoBio/urlesc](https://github.com/PuerkitoBio/urlesc) | BSD 3-Clause "New" or "Revised" License |
|
||||
| [github.com/rogpeppe/go-internal](https://github.com/rogpeppe/go-internal) | BSD 3-Clause "New" or "Revised" License |
|
||||
| [github.com/russross/blackfriday](https://github.com/russross/blackfriday) | Simplified BSD License |
|
||||
| [github.com/shurcooL/sanitized_anchor_name](https://github.com/shurcooL/sanitized_anchor_name) | MIT License |
|
||||
| [github.com/rwcarlsen/goexif](https://github.com/rwcarlsen/goexif) | BSD 2-Clause "Simplified" License |
|
||||
| [github.com/spf13/afero](https://github.com/spf13/afero) | Apache License 2.0 |
|
||||
| [github.com/spf13/cast](https://github.com/spf13/cast) | MIT License |
|
||||
| [github.com/spf13/cast](https://github.com/spf13/cast) | MIT License |
|
||||
| [github.com/spf13/cobra](https://github.com/spf13/cobra) | Apache License 2.0 |
|
||||
| [github.com/spf13/fsync](https://github.com/spf13/fsync) | MIT License |
|
||||
| [github.com/spf13/jwalterweatherman](https://github.com/spf13/jwalterweatherman) | MIT License |
|
||||
| [github.com/spf13/nitro](https://github.com/spf13/nitro) | Apache License 2.0 |
|
||||
| [github.com/spf13/pflag](https://github.com/spf13/pflag) | BSD 3-Clause "New" or "Revised" License |
|
||||
| [github.com/spf13/viper](https://github.com/spf13/viper) | MIT License |
|
||||
| [github.com/tdewolff/minify](https://github.com/tdewolff/minify) | MIT License |
|
||||
| [github.com/tdewolff/parse](https://github.com/tdewolff/parse) | MIT License |
|
||||
| [github.com/wellington/go-libsass](https://github.com/wellington/go-libsass) | Apache License 2.0 |
|
||||
| [github.com/yosssi/ace](https://github.com/yosssi/ace) | MIT License |
|
||||
| [github.com/yuin/goldmark](https://github.com/yuin/goldmark) | MIT License |
|
||||
| [github.com/yuin/goldmark-highlighting](https://github.com/yuin/goldmark-highlighting) | MIT License |
|
||||
| [go.opencensus.io](https://go.opencensus.io) | Apache License 2.0 |
|
||||
| [go.uber.org/atomic](https://go.uber.org/atomic) | MIT License |
|
||||
| [gocloud.dev](https://gocloud.dev) | Apache License 2.0 |
|
||||
| [golang.org/x/image](https://golang.org/x/image) | BSD 3-Clause "New" or "Revised" License |
|
||||
| [golang.org/x/net](https://golang.org/x/net) | BSD 3-Clause "New" or "Revised" License |
|
||||
| [golang.org/x/oauth2](https://golang.org/x/oauth2) | BSD 3-Clause "New" or "Revised" License |
|
||||
| [golang.org/x/sync](https://golang.org/x/sync) | BSD 3-Clause "New" or "Revised" License |
|
||||
| [golang.org/x/sys](https://golang.org/x/sys) | BSD 3-Clause "New" or "Revised" License |
|
||||
| [golang.org/x/text](https://golang.org/x/text) | BSD 3-Clause "New" or "Revised" License
|
||||
| [gopkg.in/yaml.v2](https://gopkg.in/yaml.v2) | Apache License 2.0 |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
| [golang.org/x/text](https://golang.org/x/text) | BSD 3-Clause "New" or "Revised" License |
|
||||
| [golang.org/x/xerrors](https://golang.org/x/xerrors) | BSD 3-Clause "New" or "Revised" License |
|
||||
| [google.golang.org/api](https://google.golang.org/api) | BSD 3-Clause "New" or "Revised" License |
|
||||
| [google.golang.org/genproto](https://google.golang.org/genproto) | Apache License 2.0 |
|
||||
| [gopkg.in/ini.v1](https://gopkg.in/ini.v1) | Apache License 2.0 |
|
||||
| [gopkg.in/yaml.v2](https://gopkg.in/yaml.v2) | Apache License 2.0 |
|
||||
|
||||
Vendored
+3
-13
@@ -17,6 +17,8 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
@@ -121,18 +123,6 @@ func (c *Cache) pruneRootDir(force bool) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
counter := 0
|
||||
// Module cache has 0555 directories; make them writable in order to remove content.
|
||||
afero.Walk(c.Fs, c.pruneAllRootDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if info.IsDir() {
|
||||
counter++
|
||||
c.Fs.Chmod(path, 0777)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return 1, c.Fs.RemoveAll(c.pruneAllRootDir)
|
||||
return hugofs.MakeReadableAndRemoveAllModulePkgDir(c.Fs, c.pruneAllRootDir)
|
||||
|
||||
}
|
||||
|
||||
Vendored
+4
-1
@@ -25,6 +25,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
"github.com/gohugoio/hugo/modules"
|
||||
|
||||
@@ -314,12 +316,13 @@ func initConfig(fs afero.Fs, cfg config.Provider) error {
|
||||
if !filepath.IsAbs(themesDir) {
|
||||
themesDir = filepath.Join(workingDir, themesDir)
|
||||
}
|
||||
globAll := glob.MustCompile("**", '/')
|
||||
modulesClient := modules.NewClient(modules.ClientConfig{
|
||||
Fs: fs,
|
||||
WorkingDir: workingDir,
|
||||
ThemesDir: themesDir,
|
||||
ModuleConfig: modConfig,
|
||||
IgnoreVendor: true,
|
||||
IgnoreVendor: globAll,
|
||||
})
|
||||
|
||||
moduleConfig, err := modulesClient.Collect()
|
||||
|
||||
Vendored
-99
@@ -1,99 +0,0 @@
|
||||
// Copyright 2017-present The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Partition represents a cache partition where Load is the callback
|
||||
// for when the partition is needed.
|
||||
type Partition struct {
|
||||
Key string
|
||||
Load func() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// Lazy represents a lazily loaded cache.
|
||||
type Lazy struct {
|
||||
initSync sync.Once
|
||||
initErr error
|
||||
cache map[string]interface{}
|
||||
load func() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// NewLazy creates a lazy cache with the given load func.
|
||||
func NewLazy(load func() (map[string]interface{}, error)) *Lazy {
|
||||
return &Lazy{load: load}
|
||||
}
|
||||
|
||||
func (l *Lazy) init() error {
|
||||
l.initSync.Do(func() {
|
||||
c, err := l.load()
|
||||
l.cache = c
|
||||
l.initErr = err
|
||||
|
||||
})
|
||||
|
||||
return l.initErr
|
||||
}
|
||||
|
||||
// Get initializes the cache if not already initialized, then looks up the
|
||||
// given key.
|
||||
func (l *Lazy) Get(key string) (interface{}, bool, error) {
|
||||
l.init()
|
||||
if l.initErr != nil {
|
||||
return nil, false, l.initErr
|
||||
}
|
||||
v, found := l.cache[key]
|
||||
return v, found, nil
|
||||
}
|
||||
|
||||
// PartitionedLazyCache is a lazily loaded cache paritioned by a supplied string key.
|
||||
type PartitionedLazyCache struct {
|
||||
partitions map[string]*Lazy
|
||||
}
|
||||
|
||||
// NewPartitionedLazyCache creates a new NewPartitionedLazyCache with the supplied
|
||||
// partitions.
|
||||
func NewPartitionedLazyCache(partitions ...Partition) *PartitionedLazyCache {
|
||||
lazyPartitions := make(map[string]*Lazy, len(partitions))
|
||||
for _, partition := range partitions {
|
||||
lazyPartitions[partition.Key] = NewLazy(partition.Load)
|
||||
}
|
||||
cache := &PartitionedLazyCache{partitions: lazyPartitions}
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
// Get initializes the partition if not already done so, then looks up the given
|
||||
// key in the given partition, returns nil if no value found.
|
||||
func (c *PartitionedLazyCache) Get(partition, key string) (interface{}, error) {
|
||||
p, found := c.partitions[partition]
|
||||
|
||||
if !found {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
v, found, err := p.Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if found {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
|
||||
}
|
||||
Vendored
-138
@@ -1,138 +0,0 @@
|
||||
// Copyright 2017-present The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
func TestNewPartitionedLazyCache(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
c := qt.New(t)
|
||||
|
||||
p1 := Partition{
|
||||
Key: "p1",
|
||||
Load: func() (map[string]interface{}, error) {
|
||||
return map[string]interface{}{
|
||||
"p1_1": "p1v1",
|
||||
"p1_2": "p1v2",
|
||||
"p1_nil": nil,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
p2 := Partition{
|
||||
Key: "p2",
|
||||
Load: func() (map[string]interface{}, error) {
|
||||
return map[string]interface{}{
|
||||
"p2_1": "p2v1",
|
||||
"p2_2": "p2v2",
|
||||
"p2_3": "p2v3",
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
cache := NewPartitionedLazyCache(p1, p2)
|
||||
|
||||
v, err := cache.Get("p1", "p1_1")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(v, qt.Equals, "p1v1")
|
||||
|
||||
v, err = cache.Get("p1", "p2_1")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(v, qt.IsNil)
|
||||
|
||||
v, err = cache.Get("p1", "p1_nil")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(v, qt.IsNil)
|
||||
|
||||
v, err = cache.Get("p2", "p2_3")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(v, qt.Equals, "p2v3")
|
||||
|
||||
v, err = cache.Get("doesnotexist", "p1_1")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(v, qt.IsNil)
|
||||
|
||||
v, err = cache.Get("p1", "doesnotexist")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(v, qt.IsNil)
|
||||
|
||||
errorP := Partition{
|
||||
Key: "p3",
|
||||
Load: func() (map[string]interface{}, error) {
|
||||
return nil, errors.New("Failed")
|
||||
},
|
||||
}
|
||||
|
||||
cache = NewPartitionedLazyCache(errorP)
|
||||
|
||||
v, err = cache.Get("p1", "doesnotexist")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(v, qt.IsNil)
|
||||
|
||||
_, err = cache.Get("p3", "doesnotexist")
|
||||
c.Assert(err, qt.Not(qt.IsNil))
|
||||
|
||||
}
|
||||
|
||||
func TestConcurrentPartitionedLazyCache(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
c := qt.New(t)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
p1 := Partition{
|
||||
Key: "p1",
|
||||
Load: func() (map[string]interface{}, error) {
|
||||
return map[string]interface{}{
|
||||
"p1_1": "p1v1",
|
||||
"p1_2": "p1v2",
|
||||
"p1_nil": nil,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
p2 := Partition{
|
||||
Key: "p2",
|
||||
Load: func() (map[string]interface{}, error) {
|
||||
return map[string]interface{}{
|
||||
"p2_1": "p2v1",
|
||||
"p2_2": "p2v2",
|
||||
"p2_3": "p2v3",
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
cache := NewPartitionedLazyCache(p1, p2)
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < 10; j++ {
|
||||
v, err := cache.Get("p1", "p1_1")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(v, qt.Equals, "p1v1")
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
+20
-21
@@ -18,9 +18,9 @@ import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/sync/semaphore"
|
||||
hconfig "github.com/gohugoio/hugo/config"
|
||||
|
||||
"github.com/gohugoio/hugo/modules"
|
||||
"golang.org/x/sync/semaphore"
|
||||
|
||||
"io/ioutil"
|
||||
|
||||
@@ -60,7 +60,8 @@ type commandeerHugoState struct {
|
||||
type commandeer struct {
|
||||
*commandeerHugoState
|
||||
|
||||
logger *loggers.Logger
|
||||
logger *loggers.Logger
|
||||
serverConfig *config.Server
|
||||
|
||||
// Currently only set when in "fast render mode". But it seems to
|
||||
// be fast enough that we could maybe just add it for all server modes.
|
||||
@@ -74,7 +75,7 @@ type commandeer struct {
|
||||
|
||||
visitedURLs *types.EvictingStringQueue
|
||||
|
||||
doWithCommandeer func(c *commandeer) error
|
||||
cfgInit func(c *commandeer) error
|
||||
|
||||
// We watch these for changes.
|
||||
configFiles []string
|
||||
@@ -154,7 +155,7 @@ func (c *commandeer) initFs(fs *hugofs.Fs) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func newCommandeer(mustHaveConfigFile, running bool, h *hugoBuilderCommon, f flagsToConfigHandler, doWithCommandeer func(c *commandeer) error, subCmdVs ...*cobra.Command) (*commandeer, error) {
|
||||
func newCommandeer(mustHaveConfigFile, running bool, h *hugoBuilderCommon, f flagsToConfigHandler, cfgInit func(c *commandeer) error, subCmdVs ...*cobra.Command) (*commandeer, error) {
|
||||
|
||||
var rebuildDebouncer func(f func())
|
||||
if running {
|
||||
@@ -173,12 +174,12 @@ func newCommandeer(mustHaveConfigFile, running bool, h *hugoBuilderCommon, f fla
|
||||
h: h,
|
||||
ftch: f,
|
||||
commandeerHugoState: newCommandeerHugoState(),
|
||||
doWithCommandeer: doWithCommandeer,
|
||||
cfgInit: cfgInit,
|
||||
visitedURLs: types.NewEvictingStringQueue(10),
|
||||
debounce: rebuildDebouncer,
|
||||
fullRebuildSem: semaphore.NewWeighted(1),
|
||||
// This will be replaced later, but we need something to log to before the configuration is read.
|
||||
logger: loggers.NewLogger(jww.LevelError, jww.LevelError, out, ioutil.Discard, running),
|
||||
logger: loggers.NewLogger(jww.LevelWarn, jww.LevelError, out, ioutil.Discard, running),
|
||||
}
|
||||
|
||||
return c, c.loadConfig(mustHaveConfigFile, running)
|
||||
@@ -286,12 +287,12 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
doWithCommandeer := func(cfg config.Provider) error {
|
||||
cfgSetAndInit := func(cfg config.Provider) error {
|
||||
c.Cfg = cfg
|
||||
if c.doWithCommandeer == nil {
|
||||
if c.cfgInit == nil {
|
||||
return nil
|
||||
}
|
||||
err := c.doWithCommandeer(c)
|
||||
err := c.cfgInit(c)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -309,17 +310,11 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
|
||||
AbsConfigDir: c.h.getConfigDir(dir),
|
||||
Environ: os.Environ(),
|
||||
Environment: environment},
|
||||
doWithCommandeer,
|
||||
cfgSetAndInit,
|
||||
doWithConfig)
|
||||
|
||||
if err != nil {
|
||||
if mustHaveConfigFile {
|
||||
return err
|
||||
}
|
||||
if err != hugolib.ErrNoConfigFile && !modules.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if err != nil && mustHaveConfigFile {
|
||||
return err
|
||||
} else if mustHaveConfigFile && len(configFiles) == 0 {
|
||||
return hugolib.ErrNoConfigFile
|
||||
}
|
||||
@@ -338,8 +333,8 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
|
||||
|
||||
// This is potentially double work, but we need to do this one more time now
|
||||
// that all the languages have been configured.
|
||||
if c.doWithCommandeer != nil {
|
||||
if err := c.doWithCommandeer(c); err != nil {
|
||||
if c.cfgInit != nil {
|
||||
if err := c.cfgInit(c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -351,6 +346,10 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
|
||||
|
||||
cfg.Logger = logger
|
||||
c.logger = logger
|
||||
c.serverConfig, err = hconfig.DecodeServer(cfg.Cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
createMemFs := config.GetBool("renderToMemory")
|
||||
|
||||
|
||||
+13
-4
@@ -47,12 +47,12 @@ func (b *commandsBuilder) addAll() *commandsBuilder {
|
||||
b.newServerCmd(),
|
||||
newVersionCmd(),
|
||||
newEnvCmd(),
|
||||
newConfigCmd(),
|
||||
b.newConfigCmd(),
|
||||
newCheckCmd(),
|
||||
newDeployCmd(),
|
||||
newConvertCmd(),
|
||||
b.newDeployCmd(),
|
||||
b.newConvertCmd(),
|
||||
b.newNewCmd(),
|
||||
newListCmd(),
|
||||
b.newListCmd(),
|
||||
newImportCmd(),
|
||||
newGenCmd(),
|
||||
createReleaser(),
|
||||
@@ -111,6 +111,12 @@ func (b *commandsBuilder) newBuilderCmd(cmd *cobra.Command) *baseBuilderCmd {
|
||||
return bcmd
|
||||
}
|
||||
|
||||
func (b *commandsBuilder) newBuilderBasicCmd(cmd *cobra.Command) *baseBuilderCmd {
|
||||
bcmd := &baseBuilderCmd{commandsBuilder: b, baseCmd: &baseCmd{cmd: cmd}}
|
||||
bcmd.hugoBuilderCommon.handleCommonBuilderFlags(cmd)
|
||||
return bcmd
|
||||
}
|
||||
|
||||
func (c *baseCmd) flagsToConfig(cfg config.Provider) {
|
||||
initializeFlags(c.cmd, cfg)
|
||||
}
|
||||
@@ -206,6 +212,7 @@ type hugoBuilderCommon struct {
|
||||
memprofile string
|
||||
mutexprofile string
|
||||
traceprofile string
|
||||
printm bool
|
||||
|
||||
// TODO(bep) var vs string
|
||||
logging bool
|
||||
@@ -266,6 +273,7 @@ func (cc *hugoBuilderCommon) handleCommonBuilderFlags(cmd *cobra.Command) {
|
||||
cmd.PersistentFlags().StringVarP(&cc.environment, "environment", "e", "", "build environment")
|
||||
cmd.PersistentFlags().StringP("themesDir", "", "", "filesystem path to themes directory")
|
||||
cmd.PersistentFlags().BoolP("ignoreVendor", "", false, "ignores any _vendor directory")
|
||||
cmd.PersistentFlags().StringP("ignoreVendorPaths", "", "", "ignores any _vendor for module paths matching the given Glob pattern")
|
||||
}
|
||||
|
||||
func (cc *hugoBuilderCommon) handleFlags(cmd *cobra.Command) {
|
||||
@@ -293,6 +301,7 @@ func (cc *hugoBuilderCommon) handleFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().BoolP("path-warnings", "", false, "print warnings on duplicate target paths etc.")
|
||||
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().StringVarP(&cc.mutexprofile, "profile-mutex", "", "", "write Mutex profile to `file`")
|
||||
cmd.Flags().StringVarP(&cc.traceprofile, "trace", "", "", "write trace to `file` (not useful in general)")
|
||||
|
||||
|
||||
+222
-94
@@ -20,6 +20,12 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/gohugoio/hugo/htesting"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@@ -32,21 +38,120 @@ func TestExecute(t *testing.T) {
|
||||
|
||||
c := qt.New(t)
|
||||
|
||||
dir, err := createSimpleTestSite(t, testSiteConfig{})
|
||||
c.Assert(err, qt.IsNil)
|
||||
createSite := func(c *qt.C) (string, func()) {
|
||||
dir, clean, err := createSimpleTestSite(t, testSiteConfig{})
|
||||
c.Assert(err, qt.IsNil)
|
||||
return dir, clean
|
||||
}
|
||||
|
||||
defer func() {
|
||||
os.RemoveAll(dir)
|
||||
}()
|
||||
c.Run("hugo", func(c *qt.C) {
|
||||
dir, clean := createSite(c)
|
||||
defer clean()
|
||||
resp := Execute([]string{"-s=" + dir})
|
||||
c.Assert(resp.Err, qt.IsNil)
|
||||
result := resp.Result
|
||||
c.Assert(len(result.Sites) == 1, qt.Equals, true)
|
||||
c.Assert(len(result.Sites[0].RegularPages()) == 1, qt.Equals, true)
|
||||
c.Assert(result.Sites[0].Info.Params()["myparam"], qt.Equals, "paramproduction")
|
||||
})
|
||||
|
||||
c.Run("hugo, set environment", func(c *qt.C) {
|
||||
dir, clean := createSite(c)
|
||||
defer clean()
|
||||
resp := Execute([]string{"-s=" + dir, "-e=staging"})
|
||||
c.Assert(resp.Err, qt.IsNil)
|
||||
result := resp.Result
|
||||
c.Assert(result.Sites[0].Info.Params()["myparam"], qt.Equals, "paramstaging")
|
||||
})
|
||||
|
||||
c.Run("convert toJSON", func(c *qt.C) {
|
||||
dir, clean := createSite(c)
|
||||
output := filepath.Join(dir, "myjson")
|
||||
defer clean()
|
||||
resp := Execute([]string{"convert", "toJSON", "-s=" + dir, "-e=staging", "-o=" + output})
|
||||
c.Assert(resp.Err, qt.IsNil)
|
||||
converted := readFileFrom(c, filepath.Join(output, "content", "p1.md"))
|
||||
c.Assert(converted, qt.Equals, "{\n \"title\": \"P1\",\n \"weight\": 1\n}\n\nContent\n\n", qt.Commentf(converted))
|
||||
})
|
||||
|
||||
c.Run("config, set environment", func(c *qt.C) {
|
||||
dir, clean := createSite(c)
|
||||
defer clean()
|
||||
out, err := captureStdout(func() error {
|
||||
resp := Execute([]string{"config", "-s=" + dir, "-e=staging"})
|
||||
return resp.Err
|
||||
})
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(out, qt.Contains, "params = map[myparam:paramstaging]", qt.Commentf(out))
|
||||
})
|
||||
|
||||
c.Run("deploy, environment set", func(c *qt.C) {
|
||||
dir, clean := createSite(c)
|
||||
defer clean()
|
||||
resp := Execute([]string{"deploy", "-s=" + dir, "-e=staging", "--target=mydeployment", "--dryRun"})
|
||||
c.Assert(resp.Err, qt.Not(qt.IsNil))
|
||||
c.Assert(resp.Err.Error(), qt.Contains, `no provider registered for "hugocloud"`)
|
||||
})
|
||||
|
||||
c.Run("list", func(c *qt.C) {
|
||||
dir, clean := createSite(c)
|
||||
defer clean()
|
||||
out, err := captureStdout(func() error {
|
||||
resp := Execute([]string{"list", "all", "-s=" + dir, "-e=staging"})
|
||||
return resp.Err
|
||||
})
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(out, qt.Contains, "p1.md")
|
||||
})
|
||||
|
||||
c.Run("new theme", func(c *qt.C) {
|
||||
dir, clean := createSite(c)
|
||||
defer clean()
|
||||
themesDir := filepath.Join(dir, "mythemes")
|
||||
resp := Execute([]string{"new", "theme", "mytheme", "-s=" + dir, "-e=staging", "--themesDir=" + themesDir})
|
||||
c.Assert(resp.Err, qt.IsNil)
|
||||
themeTOML := readFileFrom(c, filepath.Join(themesDir, "mytheme", "theme.toml"))
|
||||
c.Assert(themeTOML, qt.Contains, "name = \"Mytheme\"")
|
||||
})
|
||||
|
||||
c.Run("new site", func(c *qt.C) {
|
||||
dir, clean := createSite(c)
|
||||
defer clean()
|
||||
siteDir := filepath.Join(dir, "mysite")
|
||||
resp := Execute([]string{"new", "site", siteDir, "-e=staging"})
|
||||
c.Assert(resp.Err, qt.IsNil)
|
||||
config := readFileFrom(c, filepath.Join(siteDir, "config.toml"))
|
||||
c.Assert(config, qt.Contains, "baseURL = \"http://example.org/\"")
|
||||
checkNewSiteInited(c, siteDir)
|
||||
})
|
||||
|
||||
resp := Execute([]string{"-s=" + dir})
|
||||
c.Assert(resp.Err, qt.IsNil)
|
||||
result := resp.Result
|
||||
c.Assert(len(result.Sites) == 1, qt.Equals, true)
|
||||
c.Assert(len(result.Sites[0].RegularPages()) == 1, qt.Equals, true)
|
||||
}
|
||||
|
||||
func TestCommandsPersistentFlags(t *testing.T) {
|
||||
func checkNewSiteInited(c *qt.C, basepath string) {
|
||||
paths := []string{
|
||||
filepath.Join(basepath, "layouts"),
|
||||
filepath.Join(basepath, "content"),
|
||||
filepath.Join(basepath, "archetypes"),
|
||||
filepath.Join(basepath, "static"),
|
||||
filepath.Join(basepath, "data"),
|
||||
filepath.Join(basepath, "config.toml"),
|
||||
}
|
||||
|
||||
for _, path := range paths {
|
||||
_, err := os.Stat(path)
|
||||
c.Assert(err, qt.IsNil)
|
||||
}
|
||||
}
|
||||
|
||||
func readFileFrom(c *qt.C, filename string) string {
|
||||
c.Helper()
|
||||
filename = filepath.Clean(filename)
|
||||
b, err := afero.ReadFile(hugofs.Os, filename)
|
||||
c.Assert(err, qt.IsNil)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func TestFlags(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
noOpRunE := func(cmd *cobra.Command, args []string) error {
|
||||
@@ -54,90 +159,103 @@ func TestCommandsPersistentFlags(t *testing.T) {
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
check func(command []cmder)
|
||||
}{{[]string{"server",
|
||||
"--config=myconfig.toml",
|
||||
"--configDir=myconfigdir",
|
||||
"--contentDir=mycontent",
|
||||
"--disableKinds=page,home",
|
||||
"--environment=testing",
|
||||
"--configDir=myconfigdir",
|
||||
"--layoutDir=mylayouts",
|
||||
"--theme=mytheme",
|
||||
"--gc",
|
||||
"--themesDir=mythemes",
|
||||
"--cleanDestinationDir",
|
||||
"--navigateToChanged",
|
||||
"--disableLiveReload",
|
||||
"--noHTTPCache",
|
||||
"--i18n-warnings",
|
||||
"--destination=/tmp/mydestination",
|
||||
"-b=https://example.com/b/",
|
||||
"--port=1366",
|
||||
"--renderToDisk",
|
||||
"--source=mysource",
|
||||
"--path-warnings",
|
||||
}, func(commands []cmder) {
|
||||
var sc *serverCmd
|
||||
for _, command := range commands {
|
||||
if b, ok := command.(commandsBuilderGetter); ok {
|
||||
v := b.getCommandsBuilder().hugoBuilderCommon
|
||||
c.Assert(v.cfgFile, qt.Equals, "myconfig.toml")
|
||||
c.Assert(v.cfgDir, qt.Equals, "myconfigdir")
|
||||
c.Assert(v.source, qt.Equals, "mysource")
|
||||
c.Assert(v.baseURL, qt.Equals, "https://example.com/b/")
|
||||
}
|
||||
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 := viper.New()
|
||||
cmd.flagsToConfig(cfg)
|
||||
c.Assert(cfg.Get("ignoreVendor"), qt.Equals, true)
|
||||
},
|
||||
},
|
||||
{
|
||||
// https://github.com/gohugoio/hugo/issues/7642
|
||||
name: "ignoreVendorPaths",
|
||||
args: []string{"server", "--ignoreVendorPaths=github.com/**"},
|
||||
check: func(c *qt.C, cmd *serverCmd) {
|
||||
cfg := viper.New()
|
||||
cmd.flagsToConfig(cfg)
|
||||
c.Assert(cfg.Get("ignoreVendorPaths"), qt.Equals, "github.com/**")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Persistent flags",
|
||||
args: []string{"server",
|
||||
"--config=myconfig.toml",
|
||||
"--configDir=myconfigdir",
|
||||
"--contentDir=mycontent",
|
||||
"--disableKinds=page,home",
|
||||
"--environment=testing",
|
||||
"--configDir=myconfigdir",
|
||||
"--layoutDir=mylayouts",
|
||||
"--theme=mytheme",
|
||||
"--gc",
|
||||
"--themesDir=mythemes",
|
||||
"--cleanDestinationDir",
|
||||
"--navigateToChanged",
|
||||
"--disableLiveReload",
|
||||
"--noHTTPCache",
|
||||
"--i18n-warnings",
|
||||
"--destination=/tmp/mydestination",
|
||||
"-b=https://example.com/b/",
|
||||
"--port=1366",
|
||||
"--renderToDisk",
|
||||
"--source=mysource",
|
||||
"--path-warnings",
|
||||
},
|
||||
check: func(c *qt.C, sc *serverCmd) {
|
||||
c.Assert(sc, qt.Not(qt.IsNil))
|
||||
c.Assert(sc.navigateToChanged, qt.Equals, true)
|
||||
c.Assert(sc.disableLiveReload, qt.Equals, true)
|
||||
c.Assert(sc.noHTTPCache, qt.Equals, true)
|
||||
c.Assert(sc.renderToDisk, qt.Equals, true)
|
||||
c.Assert(sc.serverPort, qt.Equals, 1366)
|
||||
c.Assert(sc.environment, qt.Equals, "testing")
|
||||
|
||||
if srvCmd, ok := command.(*serverCmd); ok {
|
||||
sc = srvCmd
|
||||
}
|
||||
}
|
||||
cfg := viper.New()
|
||||
sc.flagsToConfig(cfg)
|
||||
c.Assert(cfg.GetString("publishDir"), qt.Equals, "/tmp/mydestination")
|
||||
c.Assert(cfg.GetString("contentDir"), qt.Equals, "mycontent")
|
||||
c.Assert(cfg.GetString("layoutDir"), qt.Equals, "mylayouts")
|
||||
c.Assert(cfg.GetStringSlice("theme"), qt.DeepEquals, []string{"mytheme"})
|
||||
c.Assert(cfg.GetString("themesDir"), qt.Equals, "mythemes")
|
||||
c.Assert(cfg.GetString("baseURL"), qt.Equals, "https://example.com/b/")
|
||||
|
||||
c.Assert(sc, qt.Not(qt.IsNil))
|
||||
c.Assert(sc.navigateToChanged, qt.Equals, true)
|
||||
c.Assert(sc.disableLiveReload, qt.Equals, true)
|
||||
c.Assert(sc.noHTTPCache, qt.Equals, true)
|
||||
c.Assert(sc.renderToDisk, qt.Equals, true)
|
||||
c.Assert(sc.serverPort, qt.Equals, 1366)
|
||||
c.Assert(sc.environment, qt.Equals, "testing")
|
||||
c.Assert(cfg.Get("disableKinds"), qt.DeepEquals, []string{"page", "home"})
|
||||
|
||||
cfg := viper.New()
|
||||
sc.flagsToConfig(cfg)
|
||||
c.Assert(cfg.GetString("publishDir"), qt.Equals, "/tmp/mydestination")
|
||||
c.Assert(cfg.GetString("contentDir"), qt.Equals, "mycontent")
|
||||
c.Assert(cfg.GetString("layoutDir"), qt.Equals, "mylayouts")
|
||||
c.Assert(cfg.GetStringSlice("theme"), qt.DeepEquals, []string{"mytheme"})
|
||||
c.Assert(cfg.GetString("themesDir"), qt.Equals, "mythemes")
|
||||
c.Assert(cfg.GetString("baseURL"), qt.Equals, "https://example.com/b/")
|
||||
c.Assert(cfg.GetBool("gc"), qt.Equals, true)
|
||||
|
||||
c.Assert(cfg.Get("disableKinds"), qt.DeepEquals, []string{"page", "home"})
|
||||
// The flag is named path-warnings
|
||||
c.Assert(cfg.GetBool("logPathWarnings"), qt.Equals, true)
|
||||
|
||||
c.Assert(cfg.GetBool("gc"), qt.Equals, true)
|
||||
// The flag is named i18n-warnings
|
||||
c.Assert(cfg.GetBool("logI18nWarnings"), qt.Equals, true)
|
||||
|
||||
// The flag is named path-warnings
|
||||
c.Assert(cfg.GetBool("logPathWarnings"), qt.Equals, true)
|
||||
|
||||
// The flag is named i18n-warnings
|
||||
c.Assert(cfg.GetBool("logI18nWarnings"), qt.Equals, true)
|
||||
|
||||
}}}
|
||||
}}}
|
||||
|
||||
for _, test := range tests {
|
||||
b := newCommandsBuilder()
|
||||
root := b.addAll().build()
|
||||
c.Run(test.name, func(c *qt.C) {
|
||||
|
||||
for _, c := range b.commands {
|
||||
if c.getCommand() == nil {
|
||||
continue
|
||||
b := newCommandsBuilder()
|
||||
root := b.addAll().build()
|
||||
|
||||
for _, cmd := range b.commands {
|
||||
if cmd.getCommand() == nil {
|
||||
continue
|
||||
}
|
||||
// We are only intereseted in the flag handling here.
|
||||
cmd.getCommand().RunE = noOpRunE
|
||||
}
|
||||
// We are only intereseted in the flag handling here.
|
||||
c.getCommand().RunE = noOpRunE
|
||||
}
|
||||
rootCmd := root.getCommand()
|
||||
rootCmd.SetArgs(test.args)
|
||||
c.Assert(rootCmd.Execute(), qt.IsNil)
|
||||
test.check(b.commands)
|
||||
rootCmd := root.getCommand()
|
||||
rootCmd.SetArgs(test.args)
|
||||
c.Assert(rootCmd.Execute(), qt.IsNil)
|
||||
test.check(c, b.commands[0].(*serverCmd))
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -146,16 +264,14 @@ func TestCommandsExecute(t *testing.T) {
|
||||
|
||||
c := qt.New(t)
|
||||
|
||||
dir, err := createSimpleTestSite(t, testSiteConfig{})
|
||||
dir, clean, err := createSimpleTestSite(t, testSiteConfig{})
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
dirOut, err := ioutil.TempDir("", "hugo-cli-out")
|
||||
dirOut, clean2, err := htesting.CreateTempDir(hugofs.Os, "hugo-cli-out")
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
defer func() {
|
||||
os.RemoveAll(dir)
|
||||
os.RemoveAll(dirOut)
|
||||
}()
|
||||
defer clean()
|
||||
defer clean2()
|
||||
|
||||
sourceFlag := fmt.Sprintf("-s=%s", dir)
|
||||
|
||||
@@ -222,10 +338,10 @@ type testSiteConfig struct {
|
||||
contentDir string
|
||||
}
|
||||
|
||||
func createSimpleTestSite(t *testing.T, cfg testSiteConfig) (string, error) {
|
||||
d, e := ioutil.TempDir("", "hugo-cli")
|
||||
func createSimpleTestSite(t *testing.T, cfg testSiteConfig) (string, func(), error) {
|
||||
d, clean, e := htesting.CreateTempDir(hugofs.Os, "hugo-cli")
|
||||
if e != nil {
|
||||
return "", e
|
||||
return "", nil, e
|
||||
}
|
||||
|
||||
cfgStr := `
|
||||
@@ -233,6 +349,7 @@ func createSimpleTestSite(t *testing.T, cfg testSiteConfig) (string, error) {
|
||||
baseURL = "https://example.org"
|
||||
title = "Hugo Commands"
|
||||
|
||||
|
||||
`
|
||||
|
||||
contentDir := "content"
|
||||
@@ -244,8 +361,19 @@ title = "Hugo Commands"
|
||||
contentDir = cfg.contentDir
|
||||
}
|
||||
|
||||
os.MkdirAll(filepath.Join(d, "public"), 0777)
|
||||
|
||||
// Just the basic. These are for CLI tests, not site testing.
|
||||
writeFile(t, filepath.Join(d, "config.toml"), cfgStr)
|
||||
writeFile(t, filepath.Join(d, "config", "staging", "params.toml"), `myparam="paramstaging"`)
|
||||
writeFile(t, filepath.Join(d, "config", "staging", "deployment.toml"), `
|
||||
[[targets]]
|
||||
name = "mydeployment"
|
||||
URL = "hugocloud://hugotestbucket"
|
||||
`)
|
||||
|
||||
writeFile(t, filepath.Join(d, "config", "testing", "params.toml"), `myparam="paramtesting"`)
|
||||
writeFile(t, filepath.Join(d, "config", "production", "params.toml"), `myparam="paramproduction"`)
|
||||
|
||||
writeFile(t, filepath.Join(d, contentDir, "p1.md"), `
|
||||
---
|
||||
@@ -270,7 +398,7 @@ Environment: {{ hugo.Environment }}
|
||||
|
||||
`)
|
||||
|
||||
return d, nil
|
||||
return d, clean, nil
|
||||
|
||||
}
|
||||
|
||||
|
||||
+10
-11
@@ -15,6 +15,7 @@ package commands
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
@@ -27,27 +28,23 @@ import (
|
||||
"github.com/gohugoio/hugo/modules"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
jww "github.com/spf13/jwalterweatherman"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var _ cmder = (*configCmd)(nil)
|
||||
|
||||
type configCmd struct {
|
||||
hugoBuilderCommon
|
||||
*baseCmd
|
||||
*baseBuilderCmd
|
||||
}
|
||||
|
||||
func newConfigCmd() *configCmd {
|
||||
func (b *commandsBuilder) newConfigCmd() *configCmd {
|
||||
cc := &configCmd{}
|
||||
cc.baseCmd = newBaseCmd(&cobra.Command{
|
||||
cmd := &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Print the site configuration",
|
||||
Long: `Print the site configuration, both default and custom settings.`,
|
||||
RunE: cc.printConfig,
|
||||
})
|
||||
|
||||
cc.cmd.PersistentFlags().StringVarP(&cc.source, "source", "s", "", "filesystem path to read files relative from")
|
||||
}
|
||||
|
||||
printMountsCmd := &cobra.Command{
|
||||
Use: "mounts",
|
||||
@@ -55,7 +52,9 @@ func newConfigCmd() *configCmd {
|
||||
RunE: cc.printMounts,
|
||||
}
|
||||
|
||||
cc.cmd.AddCommand(printMountsCmd)
|
||||
cmd.AddCommand(printMountsCmd)
|
||||
|
||||
cc.baseBuilderCmd = b.newBuilderBasicCmd(cmd)
|
||||
|
||||
return cc
|
||||
}
|
||||
@@ -105,9 +104,9 @@ func (c *configCmd) printConfig(cmd *cobra.Command, args []string) error {
|
||||
for _, k := range keys {
|
||||
kv := reflect.ValueOf(allSettings[k])
|
||||
if kv.Kind() == reflect.String {
|
||||
jww.FEEDBACK.Printf("%s%s\"%+v\"\n", k, separator, allSettings[k])
|
||||
fmt.Printf("%s%s\"%+v\"\n", k, separator, allSettings[k])
|
||||
} else {
|
||||
jww.FEEDBACK.Printf("%s%s%+v\n", k, separator, allSettings[k])
|
||||
fmt.Printf("%s%s%+v\n", k, separator, allSettings[k])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+17
-55
@@ -16,10 +16,11 @@ package commands
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gohugoio/hugo/parser/pageparser"
|
||||
|
||||
"github.com/gohugoio/hugo/resources/page"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
@@ -28,7 +29,6 @@ import (
|
||||
|
||||
"github.com/gohugoio/hugo/parser"
|
||||
"github.com/gohugoio/hugo/parser/metadecoders"
|
||||
"github.com/gohugoio/hugo/parser/pageparser"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
@@ -44,27 +44,25 @@ var (
|
||||
)
|
||||
|
||||
type convertCmd struct {
|
||||
hugoBuilderCommon
|
||||
|
||||
outputDir string
|
||||
unsafe bool
|
||||
|
||||
*baseCmd
|
||||
*baseBuilderCmd
|
||||
}
|
||||
|
||||
func newConvertCmd() *convertCmd {
|
||||
func (b *commandsBuilder) newConvertCmd() *convertCmd {
|
||||
cc := &convertCmd{}
|
||||
|
||||
cc.baseCmd = newBaseCmd(&cobra.Command{
|
||||
cmd := &cobra.Command{
|
||||
Use: "convert",
|
||||
Short: "Convert your content to different formats",
|
||||
Long: `Convert your content (e.g. front matter) to different formats.
|
||||
|
||||
See convert's subcommands toJSON, toTOML and toYAML for more information.`,
|
||||
RunE: nil,
|
||||
})
|
||||
}
|
||||
|
||||
cc.cmd.AddCommand(
|
||||
cmd.AddCommand(
|
||||
&cobra.Command{
|
||||
Use: "toJSON",
|
||||
Short: "Convert front matter to JSON",
|
||||
@@ -94,10 +92,10 @@ to use YAML for the front matter.`,
|
||||
},
|
||||
)
|
||||
|
||||
cc.cmd.PersistentFlags().StringVarP(&cc.outputDir, "output", "o", "", "filesystem path to write files to")
|
||||
cc.cmd.PersistentFlags().StringVarP(&cc.source, "source", "s", "", "filesystem path to read files relative from")
|
||||
cc.cmd.PersistentFlags().BoolVar(&cc.unsafe, "unsafe", false, "enable less safe operations, please backup first")
|
||||
cc.cmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{})
|
||||
cmd.PersistentFlags().StringVarP(&cc.outputDir, "output", "o", "", "filesystem path to write files to")
|
||||
cmd.PersistentFlags().BoolVar(&cc.unsafe, "unsafe", false, "enable less safe operations, please backup first")
|
||||
|
||||
cc.baseBuilderCmd = b.newBuilderBasicCmd(cmd)
|
||||
|
||||
return cc
|
||||
}
|
||||
@@ -159,7 +157,7 @@ func (cc *convertCmd) convertAndSavePage(p page.Page, site *hugolib.Site, target
|
||||
return nil
|
||||
}
|
||||
|
||||
pf, err := parseContentFile(file)
|
||||
pf, err := pageparser.ParseFrontMatterAndContent(file)
|
||||
if err != nil {
|
||||
site.Log.ERROR.Println(errMsg)
|
||||
file.Close()
|
||||
@@ -169,23 +167,23 @@ func (cc *convertCmd) convertAndSavePage(p page.Page, site *hugolib.Site, target
|
||||
file.Close()
|
||||
|
||||
// better handling of dates in formats that don't have support for them
|
||||
if pf.frontMatterFormat == metadecoders.JSON || pf.frontMatterFormat == metadecoders.YAML || pf.frontMatterFormat == metadecoders.TOML {
|
||||
for k, v := range pf.frontMatter {
|
||||
if pf.FrontMatterFormat == metadecoders.JSON || pf.FrontMatterFormat == metadecoders.YAML || pf.FrontMatterFormat == metadecoders.TOML {
|
||||
for k, v := range pf.FrontMatter {
|
||||
switch vv := v.(type) {
|
||||
case time.Time:
|
||||
pf.frontMatter[k] = vv.Format(time.RFC3339)
|
||||
pf.FrontMatter[k] = vv.Format(time.RFC3339)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var newContent bytes.Buffer
|
||||
err = parser.InterfaceToFrontMatter(pf.frontMatter, targetFormat, &newContent)
|
||||
err = parser.InterfaceToFrontMatter(pf.FrontMatter, targetFormat, &newContent)
|
||||
if err != nil {
|
||||
site.Log.ERROR.Println(errMsg)
|
||||
return err
|
||||
}
|
||||
|
||||
newContent.Write(pf.content)
|
||||
newContent.Write(pf.Content)
|
||||
|
||||
newFilename := p.File().Filename()
|
||||
|
||||
@@ -212,39 +210,3 @@ type parsedFile struct {
|
||||
// Everything after Front Matter
|
||||
content []byte
|
||||
}
|
||||
|
||||
func parseContentFile(r io.Reader) (parsedFile, error) {
|
||||
var pf parsedFile
|
||||
|
||||
psr, err := pageparser.Parse(r, pageparser.Config{})
|
||||
if err != nil {
|
||||
return pf, err
|
||||
}
|
||||
|
||||
iter := psr.Iterator()
|
||||
|
||||
walkFn := func(item pageparser.Item) bool {
|
||||
if pf.frontMatterSource != nil {
|
||||
// The rest is content.
|
||||
pf.content = psr.Input()[item.Pos:]
|
||||
// Done
|
||||
return false
|
||||
} else if item.IsFrontMatter() {
|
||||
pf.frontMatterFormat = metadecoders.FormatFromFrontMatterType(item.Type)
|
||||
pf.frontMatterSource = item.Val
|
||||
}
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
iter.PeekWalk(walkFn)
|
||||
|
||||
metadata, err := metadecoders.Default.UnmarshalToMap(pf.frontMatterSource, pf.frontMatterFormat)
|
||||
if err != nil {
|
||||
return pf, err
|
||||
}
|
||||
pf.frontMatter = metadata
|
||||
|
||||
return pf, nil
|
||||
|
||||
}
|
||||
|
||||
+17
-11
@@ -24,8 +24,10 @@ var _ cmder = (*deployCmd)(nil)
|
||||
|
||||
// deployCmd supports deploying sites to Cloud providers.
|
||||
type deployCmd struct {
|
||||
hugoBuilderCommon
|
||||
*baseCmd
|
||||
*baseBuilderCmd
|
||||
|
||||
invalidateCDN bool
|
||||
maxDeletes int
|
||||
}
|
||||
|
||||
// TODO: In addition to the "deploy" command, consider adding a "--deploy"
|
||||
@@ -38,10 +40,10 @@ type deployCmd struct {
|
||||
// run "hugo && hugo deploy" again and again and upload new stuff every time. Is
|
||||
// this intended?
|
||||
|
||||
func newDeployCmd() *deployCmd {
|
||||
func (b *commandsBuilder) newDeployCmd() *deployCmd {
|
||||
cc := &deployCmd{}
|
||||
|
||||
cc.baseCmd = newBaseCmd(&cobra.Command{
|
||||
cmd := &cobra.Command{
|
||||
Use: "deploy",
|
||||
Short: "Deploy your site to a Cloud provider.",
|
||||
Long: `Deploy your site to a Cloud provider.
|
||||
@@ -52,6 +54,8 @@ documentation.
|
||||
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cfgInit := func(c *commandeer) error {
|
||||
c.Set("invalidateCDN", cc.invalidateCDN)
|
||||
c.Set("maxDeletes", cc.maxDeletes)
|
||||
return nil
|
||||
}
|
||||
comm, err := initializeConfig(true, false, &cc.hugoBuilderCommon, cc, cfgInit)
|
||||
@@ -64,14 +68,16 @@ documentation.
|
||||
}
|
||||
return deployer.Deploy(context.Background())
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
cc.cmd.Flags().String("target", "", "target deployment from deployments section in config file; defaults to the first one")
|
||||
cc.cmd.Flags().Bool("confirm", false, "ask for confirmation before making changes to the target")
|
||||
cc.cmd.Flags().Bool("dryRun", false, "dry run")
|
||||
cc.cmd.Flags().Bool("force", false, "force upload of all files")
|
||||
cc.cmd.Flags().Bool("invalidateCDN", true, "invalidate the CDN cache listed in the deployment target")
|
||||
cc.cmd.Flags().Int("maxDeletes", 256, "maximum # of files to delete, or -1 to disable")
|
||||
cmd.Flags().String("target", "", "target deployment from deployments section in config file; defaults to the first one")
|
||||
cmd.Flags().Bool("confirm", false, "ask for confirmation before making changes to the target")
|
||||
cmd.Flags().Bool("dryRun", false, "dry run")
|
||||
cmd.Flags().Bool("force", false, "force upload of all files")
|
||||
cmd.Flags().BoolVar(&cc.invalidateCDN, "invalidateCDN", true, "invalidate the CDN cache listed in the deployment target")
|
||||
cmd.Flags().IntVar(&cc.maxDeletes, "maxDeletes", 256, "maximum # of files to delete, or -1 to disable")
|
||||
|
||||
cc.baseBuilderCmd = b.newBuilderBasicCmd(cmd)
|
||||
|
||||
return cc
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ func (g *genChromaStyles) generate() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
formatter := html.New(html.WithClasses(true))
|
||||
formatter := html.New(html.WithAllClasses(true))
|
||||
formatter.WriteCSS(os.Stdout, style)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ func (g *genDocsHelper) generate() error {
|
||||
enc := json.NewEncoder(f)
|
||||
enc.SetIndent("", " ")
|
||||
|
||||
if err := enc.Encode(docshelper.DocProviders); err != nil {
|
||||
if err := enc.Encode(docshelper.GetDocProvider()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
+69
-22
@@ -82,6 +82,7 @@ func (r Response) IsUserError() bool {
|
||||
// Execute adds all child commands to the root command HugoCmd and sets flags appropriately.
|
||||
// The args are usually filled with os.Args[1:].
|
||||
func Execute(args []string) Response {
|
||||
|
||||
hugoCmd := newCommandsBuilder().addAll().build()
|
||||
cmd := hugoCmd.getCommand()
|
||||
cmd.SetArgs(args)
|
||||
@@ -118,9 +119,9 @@ func Execute(args []string) Response {
|
||||
func initializeConfig(mustHaveConfigFile, running bool,
|
||||
h *hugoBuilderCommon,
|
||||
f flagsToConfigHandler,
|
||||
doWithCommandeer func(c *commandeer) error) (*commandeer, error) {
|
||||
cfgInit func(c *commandeer) error) (*commandeer, error) {
|
||||
|
||||
c, err := newCommandeer(mustHaveConfigFile, running, h, f, doWithCommandeer)
|
||||
c, err := newCommandeer(mustHaveConfigFile, running, h, f, cfgInit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -199,6 +200,7 @@ func initializeFlags(cmd *cobra.Command, cfg config.Provider) {
|
||||
"noTimes",
|
||||
"noChmod",
|
||||
"ignoreVendor",
|
||||
"ignoreVendorPaths",
|
||||
"templateMetrics",
|
||||
"templateMetricsHints",
|
||||
|
||||
@@ -231,11 +233,6 @@ func initializeFlags(cmd *cobra.Command, cfg config.Provider) {
|
||||
"duplicateTargetPaths",
|
||||
}
|
||||
|
||||
// Will set a value even if it is the default.
|
||||
flagKeysForced := []string{
|
||||
"minify",
|
||||
}
|
||||
|
||||
for _, key := range persFlagKeys {
|
||||
setValueFromFlag(cmd.PersistentFlags(), key, cfg, "", false)
|
||||
}
|
||||
@@ -243,9 +240,7 @@ func initializeFlags(cmd *cobra.Command, cfg config.Provider) {
|
||||
setValueFromFlag(cmd.Flags(), key, cfg, "", false)
|
||||
}
|
||||
|
||||
for _, key := range flagKeysForced {
|
||||
setValueFromFlag(cmd.Flags(), key, cfg, "", true)
|
||||
}
|
||||
setValueFromFlag(cmd.Flags(), "minify", cfg, "minifyOutput", true)
|
||||
|
||||
// Set some "config aliases"
|
||||
setValueFromFlag(cmd.Flags(), "destination", cfg, "publishDir", false)
|
||||
@@ -284,13 +279,6 @@ func setValueFromFlag(flags *flag.FlagSet, key string, cfg config.Provider, targ
|
||||
|
||||
func isTerminal() bool {
|
||||
return terminal.IsTerminal(os.Stdout)
|
||||
|
||||
}
|
||||
func ifTerminal(s string) string {
|
||||
if !isTerminal() {
|
||||
return ""
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (c *commandeer) fullBuild() error {
|
||||
@@ -301,7 +289,7 @@ func (c *commandeer) fullBuild() error {
|
||||
)
|
||||
|
||||
if !c.h.quiet {
|
||||
fmt.Print(ifTerminal(hideCursor) + "Building sites … ")
|
||||
fmt.Println("Start building sites … ")
|
||||
if isTerminal() {
|
||||
defer func() {
|
||||
fmt.Print(showCursor + clearLine)
|
||||
@@ -434,7 +422,37 @@ func (c *commandeer) initMutexProfile() (func(), error) {
|
||||
|
||||
}
|
||||
|
||||
func (c *commandeer) initMemTicker() func() {
|
||||
memticker := time.NewTicker(5 * time.Second)
|
||||
quit := make(chan struct{})
|
||||
printMem := func() {
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
fmt.Printf("\n\nAlloc = %v\nTotalAlloc = %v\nSys = %v\nNumGC = %v\n\n", formatByteCount(m.Alloc), formatByteCount(m.TotalAlloc), formatByteCount(m.Sys), m.NumGC)
|
||||
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-memticker.C:
|
||||
printMem()
|
||||
case <-quit:
|
||||
memticker.Stop()
|
||||
printMem()
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
}()
|
||||
|
||||
return func() {
|
||||
close(quit)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *commandeer) initProfiling() (func(), error) {
|
||||
|
||||
stopCPUProf, err := c.initCPUProfile()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -450,6 +468,11 @@ func (c *commandeer) initProfiling() (func(), error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var stopMemTicker func()
|
||||
if c.h.printm {
|
||||
stopMemTicker = c.initMemTicker()
|
||||
}
|
||||
|
||||
return func() {
|
||||
c.initMemProfile()
|
||||
|
||||
@@ -463,6 +486,10 @@ func (c *commandeer) initProfiling() (func(), error) {
|
||||
if stopTraceProf != nil {
|
||||
stopTraceProf()
|
||||
}
|
||||
|
||||
if stopMemTicker != nil {
|
||||
stopMemTicker()
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -722,9 +749,6 @@ func (c *commandeer) handleBuildErr(err error, msg string) {
|
||||
|
||||
func (c *commandeer) rebuildSites(events []fsnotify.Event) error {
|
||||
defer c.timeTrack(time.Now(), "Total")
|
||||
defer func() {
|
||||
c.wasError = false
|
||||
}()
|
||||
|
||||
c.buildErr = nil
|
||||
visited := c.visitedURLs.PeekAllSet()
|
||||
@@ -886,6 +910,10 @@ func (c *commandeer) handleEvents(watcher *watcher.Batcher,
|
||||
evs []fsnotify.Event,
|
||||
configSet map[string]bool) {
|
||||
|
||||
defer func() {
|
||||
c.wasError = false
|
||||
}()
|
||||
|
||||
var isHandled bool
|
||||
|
||||
for _, ev := range evs {
|
||||
@@ -1080,10 +1108,11 @@ func (c *commandeer) handleEvents(watcher *watcher.Batcher,
|
||||
// Will block forever trying to write to a channel that nobody is reading if livereload isn't initialized
|
||||
|
||||
// force refresh when more than one file
|
||||
if len(staticEvents) == 1 {
|
||||
if !c.wasError && len(staticEvents) == 1 {
|
||||
ev := staticEvents[0]
|
||||
path := c.hugo().BaseFs.SourceFilesystems.MakeStaticPathRelative(ev.Name)
|
||||
path = c.firstPathSpec().RelURL(helpers.ToSlashTrimLeading(path), false)
|
||||
|
||||
livereload.RefreshPath(path)
|
||||
} else {
|
||||
livereload.ForceRefresh()
|
||||
@@ -1107,6 +1136,10 @@ func (c *commandeer) handleEvents(watcher *watcher.Batcher,
|
||||
|
||||
if doLiveReload {
|
||||
if len(partitionedEvents.ContentEvents) == 0 && len(partitionedEvents.AssetEvents) > 0 {
|
||||
if c.wasError {
|
||||
livereload.ForceRefresh()
|
||||
return
|
||||
}
|
||||
changed := c.changeDetector.changed()
|
||||
if c.changeDetector != nil && len(changed) == 0 {
|
||||
// Nothing has changed.
|
||||
@@ -1176,3 +1209,17 @@ func pickOneWriteOrCreatePath(events []fsnotify.Event) string {
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
func formatByteCount(b uint64) string {
|
||||
const unit = 1000
|
||||
if b < unit {
|
||||
return fmt.Sprintf("%d B", b)
|
||||
}
|
||||
div, exp := int64(unit), 0
|
||||
for n := b / unit; n >= unit; n /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
return fmt.Sprintf("%.1f %cB",
|
||||
float64(b)/float64(div), "kMGTPE"[exp])
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
@@ -37,12 +36,9 @@ title = "Hugo Commands"
|
||||
contentDir = "thisdoesnotexist"
|
||||
|
||||
`
|
||||
dir, err := createSimpleTestSite(t, testSiteConfig{configTOML: cfgStr, contentDir: contentDir})
|
||||
dir, clean, err := createSimpleTestSite(t, testSiteConfig{configTOML: cfgStr, contentDir: contentDir})
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
defer func() {
|
||||
os.RemoveAll(dir)
|
||||
}()
|
||||
defer clean()
|
||||
|
||||
cmd.SetArgs([]string{"-s=" + dir, "-c=" + contentDir})
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ import (
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/gohugoio/hugo/parser/pageparser"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hugio"
|
||||
|
||||
"github.com/gohugoio/hugo/parser/metadecoders"
|
||||
@@ -397,19 +399,19 @@ func convertJekyllPost(path, relPath, targetDir string, draft bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
pf, err := parseContentFile(bytes.NewReader(contentBytes))
|
||||
pf, err := pageparser.ParseFrontMatterAndContent(bytes.NewReader(contentBytes))
|
||||
if err != nil {
|
||||
jww.ERROR.Println("Parse file error:", path)
|
||||
return err
|
||||
}
|
||||
|
||||
newmetadata, err := convertJekyllMetaData(pf.frontMatter, postName, postDate, draft)
|
||||
newmetadata, err := convertJekyllMetaData(pf.FrontMatter, postName, postDate, draft)
|
||||
if err != nil {
|
||||
jww.ERROR.Println("Convert metadata error:", path)
|
||||
return err
|
||||
}
|
||||
|
||||
content, err := convertJekyllContent(newmetadata, string(pf.content))
|
||||
content, err := convertJekyllContent(newmetadata, string(pf.Content))
|
||||
if err != nil {
|
||||
jww.ERROR.Println("Converting Jekyll error:", path)
|
||||
return err
|
||||
|
||||
+6
-8
@@ -29,8 +29,7 @@ import (
|
||||
var _ cmder = (*listCmd)(nil)
|
||||
|
||||
type listCmd struct {
|
||||
hugoBuilderCommon
|
||||
*baseCmd
|
||||
*baseBuilderCmd
|
||||
}
|
||||
|
||||
func (lc *listCmd) buildSites(config map[string]interface{}) (*hugolib.HugoSites, error) {
|
||||
@@ -59,19 +58,19 @@ func (lc *listCmd) buildSites(config map[string]interface{}) (*hugolib.HugoSites
|
||||
return sites, nil
|
||||
}
|
||||
|
||||
func newListCmd() *listCmd {
|
||||
func (b *commandsBuilder) newListCmd() *listCmd {
|
||||
cc := &listCmd{}
|
||||
|
||||
cc.baseCmd = newBaseCmd(&cobra.Command{
|
||||
cmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "Listing out various types of content",
|
||||
Long: `Listing out various types of content.
|
||||
|
||||
List requires a subcommand, e.g. ` + "`hugo list drafts`.",
|
||||
RunE: nil,
|
||||
})
|
||||
}
|
||||
|
||||
cc.cmd.AddCommand(
|
||||
cmd.AddCommand(
|
||||
&cobra.Command{
|
||||
Use: "drafts",
|
||||
Short: "List all drafts",
|
||||
@@ -202,8 +201,7 @@ List requires a subcommand, e.g. ` + "`hugo list drafts`.",
|
||||
},
|
||||
)
|
||||
|
||||
cc.cmd.PersistentFlags().StringVarP(&cc.source, "source", "s", "", "filesystem path to read files relative from")
|
||||
cc.cmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{})
|
||||
cc.baseBuilderCmd = b.newBuilderBasicCmd(cmd)
|
||||
|
||||
return cc
|
||||
}
|
||||
|
||||
+9
-10
@@ -10,31 +10,27 @@ import (
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func captureStdout(f func() (*cobra.Command, error)) (string, error) {
|
||||
func captureStdout(f func() error) (string, error) {
|
||||
old := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
_, err := f()
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err := f()
|
||||
|
||||
w.Close()
|
||||
os.Stdout = old
|
||||
|
||||
var buf bytes.Buffer
|
||||
io.Copy(&buf, r)
|
||||
return buf.String(), nil
|
||||
return buf.String(), err
|
||||
}
|
||||
|
||||
func TestListAll(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
dir, err := createSimpleTestSite(t, testSiteConfig{})
|
||||
dir, clean, err := createSimpleTestSite(t, testSiteConfig{})
|
||||
defer clean()
|
||||
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
@@ -47,7 +43,10 @@ func TestListAll(t *testing.T) {
|
||||
|
||||
cmd.SetArgs([]string{"-s=" + dir, "list", "all"})
|
||||
|
||||
out, err := captureStdout(cmd.ExecuteC)
|
||||
out, err := captureStdout(func() error {
|
||||
_, err := cmd.ExecuteC()
|
||||
return err
|
||||
})
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
r := csv.NewReader(strings.NewReader(out))
|
||||
|
||||
+133
-27
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019 The Hugo Authors. All rights reserved.
|
||||
// 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.
|
||||
@@ -14,7 +14,13 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
|
||||
"github.com/gohugoio/hugo/modules"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -26,7 +32,66 @@ type modCmd struct {
|
||||
*baseBuilderCmd
|
||||
}
|
||||
|
||||
func (c *modCmd) newVerifyCmd() *cobra.Command {
|
||||
var clean bool
|
||||
|
||||
verifyCmd := &cobra.Command{
|
||||
Use: "verify",
|
||||
Short: "Verify dependencies.",
|
||||
Long: `Verify checks that the dependencies of the current module, which are stored in a local downloaded source cache, have not been modified since being downloaded.
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return c.withModsClient(true, func(c *modules.Client) error {
|
||||
return c.Verify(clean)
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
verifyCmd.Flags().BoolVarP(&clean, "clean", "", false, "delete module cache for dependencies that fail verification")
|
||||
|
||||
return verifyCmd
|
||||
}
|
||||
|
||||
var moduleNotFoundRe = regexp.MustCompile("module.*not found")
|
||||
|
||||
func (c *modCmd) newCleanCmd() *cobra.Command {
|
||||
var pattern string
|
||||
var all bool
|
||||
cmd := &cobra.Command{
|
||||
Use: "clean",
|
||||
Short: "Delete the Hugo Module cache for the current project.",
|
||||
Long: `Delete the Hugo Module cache for the current project.
|
||||
|
||||
Note that after you run this command, all of your dependencies will be re-downloaded next time you run "hugo".
|
||||
|
||||
Also note that if you configure a positive maxAge for the "modules" file cache, it will also be cleaned as part of "hugo --gc".
|
||||
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if all {
|
||||
com, err := c.initConfig(false)
|
||||
|
||||
if err != nil && !moduleNotFoundRe.MatchString(err.Error()) {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = com.hugo().FileCaches.ModulesCache().Prune(true)
|
||||
return err
|
||||
}
|
||||
return c.withModsClient(true, func(c *modules.Client) error {
|
||||
return c.Clean(pattern)
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&pattern, "pattern", "", "", `pattern matching module paths to clean (all if not set), e.g. "**hugo*"`)
|
||||
cmd.Flags().BoolVarP(&all, "all", "", false, "clean entire module cache")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (b *commandsBuilder) newModCmd() *modCmd {
|
||||
|
||||
c := &modCmd{}
|
||||
|
||||
const commonUsage = `
|
||||
@@ -51,6 +116,8 @@ This is not needed if you only operate on modules inside /themes or if you have
|
||||
RunE: nil,
|
||||
}
|
||||
|
||||
cmd.AddCommand(newModNPMCmd(c))
|
||||
|
||||
cmd.AddCommand(
|
||||
&cobra.Command{
|
||||
Use: "get",
|
||||
@@ -72,16 +139,65 @@ Install a specific version:
|
||||
Install the latest versions of all module dependencies:
|
||||
|
||||
hugo mod get -u
|
||||
hugo mod get -u ./... (recursive)
|
||||
|
||||
Run "go help get" for more information. All flags available for "go get" is also relevant here.
|
||||
` + commonUsage,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return c.withModsClient(false, func(c *modules.Client) error {
|
||||
// We currently just pass on the flags we get to Go and
|
||||
// need to do the flag handling manually.
|
||||
if len(args) == 1 && args[0] == "-h" {
|
||||
return cmd.Help()
|
||||
// We currently just pass on the flags we get to Go and
|
||||
// need to do the flag handling manually.
|
||||
if len(args) == 1 && args[0] == "-h" {
|
||||
return cmd.Help()
|
||||
}
|
||||
|
||||
var lastArg string
|
||||
if len(args) != 0 {
|
||||
lastArg = args[len(args)-1]
|
||||
}
|
||||
|
||||
if lastArg == "./..." {
|
||||
args = args[:len(args)-1]
|
||||
// Do a recursive update.
|
||||
dirname, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Sanity check. We do recursive walking and want to avoid
|
||||
// accidents.
|
||||
if len(dirname) < 5 {
|
||||
return errors.New("must not be run from the file system root")
|
||||
}
|
||||
|
||||
filepath.Walk(dirname, func(path string, info os.FileInfo, err error) error {
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if info.Name() == "go.mod" {
|
||||
// Found a module.
|
||||
dir := filepath.Dir(path)
|
||||
fmt.Println("Update module in", dir)
|
||||
c.source = dir
|
||||
err := c.withModsClient(false, func(c *modules.Client) error {
|
||||
if len(args) == 1 && args[0] == "-h" {
|
||||
return cmd.Help()
|
||||
}
|
||||
return c.Get(args...)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.withModsClient(false, func(c *modules.Client) error {
|
||||
return c.Get(args...)
|
||||
})
|
||||
},
|
||||
@@ -132,6 +248,7 @@ If a module is vendored, that is where Hugo will look for it's dependencies.
|
||||
})
|
||||
},
|
||||
},
|
||||
c.newVerifyCmd(),
|
||||
&cobra.Command{
|
||||
Use: "tidy",
|
||||
Short: "Remove unused entries in go.mod and go.sum.",
|
||||
@@ -141,27 +258,7 @@ If a module is vendored, that is where Hugo will look for it's dependencies.
|
||||
})
|
||||
},
|
||||
},
|
||||
&cobra.Command{
|
||||
Use: "clean",
|
||||
Short: "Delete the entire Hugo Module cache.",
|
||||
Long: `Delete the entire Hugo Module cache.
|
||||
|
||||
Note that after you run this command, all of your dependencies will be re-downloaded next time you run "hugo".
|
||||
|
||||
Also note that if you configure a positive maxAge for the "modules" file cache, it will also be cleaned as part of "hugo --gc".
|
||||
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
com, err := c.initConfig(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = com.hugo().FileCaches.ModulesCache().Prune(true)
|
||||
return err
|
||||
|
||||
},
|
||||
},
|
||||
c.newCleanCmd(),
|
||||
)
|
||||
|
||||
c.baseBuilderCmd = b.newBuilderCmd(cmd)
|
||||
@@ -179,6 +276,15 @@ func (c *modCmd) withModsClient(failOnMissingConfig bool, f func(*modules.Client
|
||||
return f(com.hugo().ModulesClient)
|
||||
}
|
||||
|
||||
func (c *modCmd) withHugo(f func(*hugolib.HugoSites) error) error {
|
||||
com, err := c.initConfig(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return f(com.hugo())
|
||||
}
|
||||
|
||||
func (c *modCmd) initConfig(failOnNoConfig bool) (*commandeer, error) {
|
||||
com, err := initializeConfig(failOnNoConfig, false, &c.hugoBuilderCommon, c, nil)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright 2020 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
"github.com/gohugoio/hugo/modules/npm"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newModNPMCmd(c *modCmd) *cobra.Command {
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "npm",
|
||||
Short: "Various npm helpers.",
|
||||
Long: `Various npm (Node package manager) helpers.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return c.withHugo(func(h *hugolib.HugoSites) error {
|
||||
return nil
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
cmd.AddCommand(&cobra.Command{
|
||||
Use: "pack",
|
||||
Short: "Experimental: Prepares and writes a composite package.json file for your project.",
|
||||
Long: `Prepares and writes a composite package.json file for your project.
|
||||
|
||||
On first run it creates a "package.hugo.json" in the project root if not already there. This file will be used as a template file
|
||||
with the base dependency set.
|
||||
|
||||
This set will be merged with all "package.hugo.json" files found in the dependency tree, picking the version closest to the project.
|
||||
|
||||
This command is marked as 'Experimental'. We think it's a great idea, so it's not likely to be
|
||||
removed from Hugo, but we need to test this out in "real life" to get a feel of it,
|
||||
so this may/will change in future versions of Hugo.
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
return c.withHugo(func(h *hugolib.HugoSites) error {
|
||||
return npm.Pack(h.BaseFs.SourceFs, h.BaseFs.Assets.Dirs)
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
return cmd
|
||||
}
|
||||
+2
-2
@@ -55,8 +55,8 @@ Ensure you run this within the root directory of your site.`,
|
||||
cmd.Flags().StringVarP(&cc.contentType, "kind", "k", "", "content type to create")
|
||||
cmd.Flags().StringVar(&cc.contentEditor, "editor", "", "edit new content with this editor, if provided")
|
||||
|
||||
cmd.AddCommand(newNewSiteCmd().getCommand())
|
||||
cmd.AddCommand(newNewThemeCmd().getCommand())
|
||||
cmd.AddCommand(b.newNewSiteCmd().getCommand())
|
||||
cmd.AddCommand(b.newNewThemeCmd().getCommand())
|
||||
|
||||
cmd.RunE = cc.newContent
|
||||
|
||||
|
||||
@@ -17,9 +17,6 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
@@ -30,105 +27,3 @@ func TestNewContentPathSectionWithForwardSlashes(t *testing.T) {
|
||||
c.Assert(p, qt.Equals, filepath.FromSlash("/post/new.md"))
|
||||
c.Assert(s, qt.Equals, "post")
|
||||
}
|
||||
|
||||
func checkNewSiteInited(fs *hugofs.Fs, basepath string, t *testing.T) {
|
||||
c := qt.New(t)
|
||||
paths := []string{
|
||||
filepath.Join(basepath, "layouts"),
|
||||
filepath.Join(basepath, "content"),
|
||||
filepath.Join(basepath, "archetypes"),
|
||||
filepath.Join(basepath, "static"),
|
||||
filepath.Join(basepath, "data"),
|
||||
filepath.Join(basepath, "config.toml"),
|
||||
}
|
||||
|
||||
for _, path := range paths {
|
||||
_, err := fs.Source.Stat(path)
|
||||
c.Assert(err, qt.IsNil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoNewSite(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
n := newNewSiteCmd()
|
||||
basepath := filepath.Join("base", "blog")
|
||||
_, fs := newTestCfg()
|
||||
|
||||
c.Assert(n.doNewSite(fs, basepath, false), qt.IsNil)
|
||||
|
||||
checkNewSiteInited(fs, basepath, t)
|
||||
}
|
||||
|
||||
func TestDoNewSite_noerror_base_exists_but_empty(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
basepath := filepath.Join("base", "blog")
|
||||
_, fs := newTestCfg()
|
||||
n := newNewSiteCmd()
|
||||
|
||||
c.Assert(fs.Source.MkdirAll(basepath, 0777), qt.IsNil)
|
||||
|
||||
c.Assert(n.doNewSite(fs, basepath, false), qt.IsNil)
|
||||
}
|
||||
|
||||
func TestDoNewSite_error_base_exists(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
basepath := filepath.Join("base", "blog")
|
||||
_, fs := newTestCfg()
|
||||
n := newNewSiteCmd()
|
||||
|
||||
c.Assert(fs.Source.MkdirAll(basepath, 0777), qt.IsNil)
|
||||
_, err := fs.Source.Create(filepath.Join(basepath, "foo"))
|
||||
c.Assert(err, qt.IsNil)
|
||||
// Since the directory already exists and isn't empty, expect an error
|
||||
c.Assert(n.doNewSite(fs, basepath, false), qt.Not(qt.IsNil))
|
||||
|
||||
}
|
||||
|
||||
func TestDoNewSite_force_empty_dir(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
basepath := filepath.Join("base", "blog")
|
||||
_, fs := newTestCfg()
|
||||
n := newNewSiteCmd()
|
||||
|
||||
c.Assert(fs.Source.MkdirAll(basepath, 0777), qt.IsNil)
|
||||
c.Assert(n.doNewSite(fs, basepath, true), qt.IsNil)
|
||||
|
||||
checkNewSiteInited(fs, basepath, t)
|
||||
}
|
||||
|
||||
func TestDoNewSite_error_force_dir_inside_exists(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
basepath := filepath.Join("base", "blog")
|
||||
_, fs := newTestCfg()
|
||||
n := newNewSiteCmd()
|
||||
|
||||
contentPath := filepath.Join(basepath, "content")
|
||||
|
||||
c.Assert(fs.Source.MkdirAll(contentPath, 0777), qt.IsNil)
|
||||
c.Assert(n.doNewSite(fs, basepath, true), qt.Not(qt.IsNil))
|
||||
}
|
||||
|
||||
func TestDoNewSite_error_force_config_inside_exists(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
basepath := filepath.Join("base", "blog")
|
||||
_, fs := newTestCfg()
|
||||
n := newNewSiteCmd()
|
||||
|
||||
configPath := filepath.Join(basepath, "config.toml")
|
||||
c.Assert(fs.Source.MkdirAll(basepath, 0777), qt.IsNil)
|
||||
_, err := fs.Source.Create(configPath)
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
c.Assert(n.doNewSite(fs, basepath, true), qt.Not(qt.IsNil))
|
||||
}
|
||||
|
||||
func newTestCfg() (*viper.Viper, *hugofs.Fs) {
|
||||
|
||||
v := viper.New()
|
||||
fs := hugofs.NewMem(v)
|
||||
|
||||
v.SetFs(fs.Source)
|
||||
|
||||
return v, fs
|
||||
|
||||
}
|
||||
|
||||
@@ -37,11 +37,11 @@ var _ cmder = (*newSiteCmd)(nil)
|
||||
type newSiteCmd struct {
|
||||
configFormat string
|
||||
|
||||
*baseCmd
|
||||
*baseBuilderCmd
|
||||
}
|
||||
|
||||
func newNewSiteCmd() *newSiteCmd {
|
||||
ccmd := &newSiteCmd{}
|
||||
func (b *commandsBuilder) newNewSiteCmd() *newSiteCmd {
|
||||
cc := &newSiteCmd{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "site [path]",
|
||||
@@ -49,15 +49,15 @@ func newNewSiteCmd() *newSiteCmd {
|
||||
Long: `Create a new site in the provided directory.
|
||||
The new site will have the correct structure, but no content or theme yet.
|
||||
Use ` + "`hugo new [contentPath]`" + ` to create new content.`,
|
||||
RunE: ccmd.newSite,
|
||||
RunE: cc.newSite,
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&ccmd.configFormat, "format", "f", "toml", "config & frontmatter format")
|
||||
cmd.Flags().StringVarP(&cc.configFormat, "format", "f", "toml", "config & frontmatter format")
|
||||
cmd.Flags().Bool("force", false, "init inside non-empty directory")
|
||||
|
||||
ccmd.baseCmd = newBaseCmd(cmd)
|
||||
cc.baseBuilderCmd = b.newBuilderBasicCmd(cmd)
|
||||
|
||||
return ccmd
|
||||
return cc
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -29,12 +29,11 @@ import (
|
||||
var _ cmder = (*newThemeCmd)(nil)
|
||||
|
||||
type newThemeCmd struct {
|
||||
*baseCmd
|
||||
hugoBuilderCommon
|
||||
*baseBuilderCmd
|
||||
}
|
||||
|
||||
func newNewThemeCmd() *newThemeCmd {
|
||||
ccmd := &newThemeCmd{baseCmd: newBaseCmd(nil)}
|
||||
func (b *commandsBuilder) newNewThemeCmd() *newThemeCmd {
|
||||
cc := &newThemeCmd{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "theme [name]",
|
||||
@@ -43,12 +42,12 @@ func newNewThemeCmd() *newThemeCmd {
|
||||
New theme is a skeleton. Please add content to the touched files. Add your
|
||||
name to the copyright line in the license and adjust the theme.toml file
|
||||
as you see fit.`,
|
||||
RunE: ccmd.newTheme,
|
||||
RunE: cc.newTheme,
|
||||
}
|
||||
|
||||
ccmd.cmd = cmd
|
||||
cc.baseBuilderCmd = b.newBuilderBasicCmd(cmd)
|
||||
|
||||
return ccmd
|
||||
return cc
|
||||
}
|
||||
|
||||
// newTheme creates a new Hugo theme template
|
||||
@@ -157,7 +156,7 @@ description = ""
|
||||
homepage = "http://example.com/"
|
||||
tags = []
|
||||
features = []
|
||||
min_version = "0.41"
|
||||
min_version = "0.41.0"
|
||||
|
||||
[author]
|
||||
name = ""
|
||||
|
||||
+63
-8
@@ -292,6 +292,18 @@ type fileServer struct {
|
||||
s *serverCmd
|
||||
}
|
||||
|
||||
func (f *fileServer) rewriteRequest(r *http.Request, toPath string) *http.Request {
|
||||
r2 := new(http.Request)
|
||||
*r2 = *r
|
||||
r2.URL = new(url.URL)
|
||||
*r2.URL = *r.URL
|
||||
r2.URL.Path = toPath
|
||||
r2.Header.Set("X-Rewrite-Original-URI", r.URL.RequestURI())
|
||||
|
||||
return r2
|
||||
|
||||
}
|
||||
|
||||
func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, error) {
|
||||
baseURL := f.baseURLs[i]
|
||||
root := f.roots[i]
|
||||
@@ -340,6 +352,7 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, erro
|
||||
if err != nil {
|
||||
f.c.logger.ERROR.Println(err)
|
||||
}
|
||||
|
||||
port = 1313
|
||||
if !f.c.paused {
|
||||
port = f.c.Cfg.GetInt("liveReloadPort")
|
||||
@@ -355,31 +368,73 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, erro
|
||||
w.Header().Set("Pragma", "no-cache")
|
||||
}
|
||||
|
||||
// Ignore any query params for the operations below.
|
||||
requestURI := strings.TrimSuffix(r.RequestURI, "?"+r.URL.RawQuery)
|
||||
|
||||
for _, header := range f.c.serverConfig.MatchHeaders(requestURI) {
|
||||
w.Header().Set(header.Key, header.Value)
|
||||
}
|
||||
|
||||
if redirect := f.c.serverConfig.MatchRedirect(requestURI); !redirect.IsZero() {
|
||||
doRedirect := true
|
||||
// This matches Netlify's behaviour and is needed for SPA behaviour.
|
||||
// See https://docs.netlify.com/routing/redirects/rewrites-proxies/
|
||||
if !redirect.Force {
|
||||
path := filepath.Clean(strings.TrimPrefix(requestURI, u.Path))
|
||||
fi, err := f.c.hugo().BaseFs.PublishFs.Stat(path)
|
||||
if err == nil {
|
||||
if fi.IsDir() {
|
||||
// There will be overlapping directories, so we
|
||||
// need to check for a file.
|
||||
_, err = f.c.hugo().BaseFs.PublishFs.Stat(filepath.Join(path, "index.html"))
|
||||
doRedirect = err != nil
|
||||
} else {
|
||||
doRedirect = false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if doRedirect {
|
||||
if redirect.Status == 200 {
|
||||
if r2 := f.rewriteRequest(r, strings.TrimPrefix(redirect.To, u.Path)); r2 != nil {
|
||||
requestURI = redirect.To
|
||||
r = r2
|
||||
}
|
||||
} else {
|
||||
w.Header().Set("Content-Type", "")
|
||||
http.Redirect(w, r, redirect.To, redirect.Status)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if f.c.fastRenderMode && f.c.buildErr == nil {
|
||||
p := r.RequestURI
|
||||
if strings.HasSuffix(p, "/") || strings.HasSuffix(p, "html") || strings.HasSuffix(p, "htm") {
|
||||
if !f.c.visitedURLs.Contains(p) {
|
||||
|
||||
if strings.HasSuffix(requestURI, "/") || strings.HasSuffix(requestURI, "html") || strings.HasSuffix(requestURI, "htm") {
|
||||
if !f.c.visitedURLs.Contains(requestURI) {
|
||||
// If not already on stack, re-render that single page.
|
||||
if err := f.c.partialReRender(p); err != nil {
|
||||
f.c.handleBuildErr(err, fmt.Sprintf("Failed to render %q", p))
|
||||
if err := f.c.partialReRender(requestURI); err != nil {
|
||||
f.c.handleBuildErr(err, fmt.Sprintf("Failed to render %q", requestURI))
|
||||
if f.c.showErrorInBrowser {
|
||||
http.Redirect(w, r, p, http.StatusMovedPermanently)
|
||||
http.Redirect(w, r, requestURI, http.StatusMovedPermanently)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f.c.visitedURLs.Add(p)
|
||||
f.c.visitedURLs.Add(requestURI)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
fileserver := decorate(http.FileServer(fs))
|
||||
mu := http.NewServeMux()
|
||||
|
||||
if u.Path == "" || u.Path == "/" {
|
||||
mu.Handle("/", fileserver)
|
||||
} else {
|
||||
|
||||
@@ -30,8 +30,7 @@ var buildErrorTemplate = `<!doctype html>
|
||||
body {
|
||||
font-family: "Muli",avenir, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
font-size: 16px;
|
||||
background-color: black;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
background-color: #2f1e2e;
|
||||
}
|
||||
main {
|
||||
margin: auto;
|
||||
@@ -43,7 +42,7 @@ var buildErrorTemplate = `<!doctype html>
|
||||
padding: 1rem 0;
|
||||
}
|
||||
.stack {
|
||||
margin-top: 6rem;
|
||||
margin-top: 4rem;
|
||||
}
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
@@ -54,10 +53,7 @@ var buildErrorTemplate = `<!doctype html>
|
||||
}
|
||||
.highlight {
|
||||
overflow-x: auto;
|
||||
padding: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
background-color: #272822;
|
||||
border: 1px solid black;
|
||||
}
|
||||
a {
|
||||
color: #0594cb;
|
||||
@@ -70,14 +66,14 @@ var buildErrorTemplate = `<!doctype html>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
{{ highlight .Error "apl" "noclasses=true,style=monokai" }}
|
||||
{{ highlight .Error "apl" "linenos=false,noclasses=true,style=paraiso-dark" }}
|
||||
{{ with .File }}
|
||||
{{ $params := printf "noclasses=true,style=monokai,linenos=table,hl_lines=%d,linenostart=%d" (add .LinesPos 1) (sub .Position.LineNumber .LinesPos) }}
|
||||
{{ $params := printf "noclasses=true,style=paraiso-dark,linenos=table,hl_lines=%d,linenostart=%d" (add .LinesPos 1) (sub .Position.LineNumber .LinesPos) }}
|
||||
{{ $lexer := .ChromaLexer | default "go-html-template" }}
|
||||
{{ highlight (delimit .Lines "\n") $lexer $params }}
|
||||
{{ end }}
|
||||
{{ with .StackTrace }}
|
||||
{{ highlight . "apl" "noclasses=true,style=monokai" }}
|
||||
{{ highlight . "apl" "noclasses=true,style=paraiso-dark" }}
|
||||
{{ end }}
|
||||
<p class="version">{{ .Version }}</p>
|
||||
<a href="">Reload Page</a>
|
||||
|
||||
@@ -34,7 +34,8 @@ func TestServer(t *testing.T) {
|
||||
t.Skip("Skip server test on appveyor")
|
||||
}
|
||||
c := qt.New(t)
|
||||
dir, err := createSimpleTestSite(t, testSiteConfig{})
|
||||
dir, clean, err := createSimpleTestSite(t, testSiteConfig{})
|
||||
defer clean()
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
// Let us hope that this port is available on all systems ...
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2018 The Hugo Authors. All rights reserved.
|
||||
// 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.
|
||||
@@ -11,4 +11,10 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package publisher
|
||||
package collections
|
||||
|
||||
type Order interface {
|
||||
// Ordinal is a zero-based ordinal that represents the order of an object
|
||||
// in a collection.
|
||||
Ordinal() int
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// 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 constants
|
||||
|
||||
// Error IDs.
|
||||
// Do not change these values.
|
||||
const (
|
||||
ErrIDAmbigousDisableKindTaxonomy = "error-disable-taxonomy"
|
||||
ErrIDAmbigousOutputKindTaxonomy = "error-output-taxonomy"
|
||||
)
|
||||
@@ -11,7 +11,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package errors contains common Hugo errors and error related utilities.
|
||||
// Package herrors contains common Hugo errors and error related utilities.
|
||||
package herrors
|
||||
|
||||
import (
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package errors contains common Hugo errors and error related utilities.
|
||||
// Package herrors contains common Hugo errors and error related utilities.
|
||||
package herrors
|
||||
|
||||
import (
|
||||
|
||||
@@ -57,6 +57,11 @@ func PrintStackTrace(w io.Writer) {
|
||||
fmt.Fprintf(w, "%s", buf)
|
||||
}
|
||||
|
||||
// ErrorSender is a, typically, non-blocking error handler.
|
||||
type ErrorSender interface {
|
||||
SendError(err error)
|
||||
}
|
||||
|
||||
// Recover is a helper function that can be used to capture panics.
|
||||
// Put this at the top of a method/function that crashes in a template:
|
||||
// defer herrors.Recover()
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
// 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
|
||||
// limitatio ns under the License.
|
||||
// limitations under the License.
|
||||
|
||||
package herrors
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
// 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
|
||||
// limitatio ns under the License.
|
||||
// limitations under the License.
|
||||
|
||||
package herrors
|
||||
|
||||
|
||||
@@ -22,6 +22,42 @@ import (
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
)
|
||||
|
||||
// TODO(bep) replace the private versions in /tpl with these.
|
||||
// IsInt returns whether the given kind is a number.
|
||||
func IsNumber(kind reflect.Kind) bool {
|
||||
return IsInt(kind) || IsUint(kind) || IsFloat(kind)
|
||||
}
|
||||
|
||||
// IsInt returns whether the given kind is an int.
|
||||
func IsInt(kind reflect.Kind) bool {
|
||||
switch kind {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsUint returns whether the given kind is an uint.
|
||||
func IsUint(kind reflect.Kind) bool {
|
||||
switch kind {
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsFloat returns whether the given kind is a float.
|
||||
func IsFloat(kind reflect.Kind) bool {
|
||||
switch kind {
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsTruthful returns whether in represents a truthful value.
|
||||
// See IsTruthfulValue
|
||||
func IsTruthful(in interface{}) bool {
|
||||
|
||||
+13
-12
@@ -26,28 +26,29 @@ import (
|
||||
|
||||
// CopyFile copies a file.
|
||||
func CopyFile(fs afero.Fs, from, to string) error {
|
||||
sf, err := os.Open(from)
|
||||
sf, err := fs.Open(from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sf.Close()
|
||||
df, err := os.Create(to)
|
||||
df, err := fs.Create(to)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer df.Close()
|
||||
_, err = io.Copy(df, sf)
|
||||
if err == nil {
|
||||
si, err := os.Stat(from)
|
||||
if err != nil {
|
||||
err = os.Chmod(to, si.Mode())
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
si, err := fs.Stat(from)
|
||||
if err != nil {
|
||||
err = fs.Chmod(to, si.Mode())
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,16 @@ package hugo
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -54,6 +64,10 @@ func (i Info) Generator() template.HTML {
|
||||
return template.HTML(fmt.Sprintf(`<meta name="generator" content="Hugo %s" />`, CurrentVersion.String()))
|
||||
}
|
||||
|
||||
func (i Info) IsProduction() bool {
|
||||
return i.Environment == EnvironmentProduction
|
||||
}
|
||||
|
||||
// NewInfo creates a new Hugo Info object.
|
||||
func NewInfo(environment string) Info {
|
||||
if environment == "" {
|
||||
@@ -65,3 +79,24 @@ func NewInfo(environment string) Info {
|
||||
Environment: environment,
|
||||
}
|
||||
}
|
||||
|
||||
func GetExecEnviron(workDir string, cfg config.Provider, fs afero.Fs) []string {
|
||||
env := os.Environ()
|
||||
nodepath := filepath.Join(workDir, "node_modules")
|
||||
if np := os.Getenv("NODE_PATH"); np != "" {
|
||||
nodepath = workDir + string(os.PathListSeparator) + np
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
return env
|
||||
}
|
||||
|
||||
@@ -31,5 +31,9 @@ func TestHugoInfo(t *testing.T) {
|
||||
c.Assert(hugoInfo.BuildDate, qt.Equals, buildDate)
|
||||
c.Assert(hugoInfo.Environment, qt.Equals, "production")
|
||||
c.Assert(string(hugoInfo.Generator()), qt.Contains, fmt.Sprintf("Hugo %s", hugoInfo.Version()))
|
||||
c.Assert(hugoInfo.IsProduction(), qt.Equals, true)
|
||||
|
||||
devHugoInfo := NewInfo("development")
|
||||
c.Assert(devHugoInfo.IsProduction(), qt.Equals, false)
|
||||
|
||||
}
|
||||
|
||||
+12
-4
@@ -15,7 +15,7 @@ package hugo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"io"
|
||||
|
||||
"runtime"
|
||||
"strings"
|
||||
@@ -245,7 +245,15 @@ func goMinorVersion(version string) int {
|
||||
if strings.HasPrefix(version, "devel") {
|
||||
return 9999 // magic
|
||||
}
|
||||
i, _ := strconv.Atoi(strings.Split(version, ".")[1])
|
||||
return i
|
||||
|
||||
var major, minor int
|
||||
var trailing string
|
||||
n, err := fmt.Sscanf(version, "go%d.%d%s", &major, &minor, &trailing)
|
||||
if n == 2 && err == io.EOF {
|
||||
// Means there were no trailing characters (i.e., not an alpha/beta)
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return minor
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ package hugo
|
||||
// CurrentVersion represents the current build version.
|
||||
// This should be the only one.
|
||||
var CurrentVersion = Version{
|
||||
Number: 0.63,
|
||||
PatchLevel: 0,
|
||||
Number: 0.76,
|
||||
PatchLevel: 2,
|
||||
Suffix: "",
|
||||
}
|
||||
|
||||
@@ -84,5 +84,6 @@ func TestParseHugoVersion(t *testing.T) {
|
||||
func TestGoMinorVersion(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
c.Assert(goMinorVersion("go1.12.5"), qt.Equals, 12)
|
||||
c.Assert(goMinorVersion("go1.14rc1"), qt.Equals, 14)
|
||||
c.Assert(GoMinorVersion() >= 11, qt.Equals, true)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright 2020 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package loggers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IgnorableLogger is a logger that ignores certain log statements.
|
||||
type IgnorableLogger struct {
|
||||
logger *Logger
|
||||
statements map[string]bool
|
||||
}
|
||||
|
||||
// NewIgnorableLogger wraps the given logger and ignores the log statement IDs given.
|
||||
func NewIgnorableLogger(logger *Logger, statements ...string) IgnorableLogger {
|
||||
statementsSet := make(map[string]bool)
|
||||
for _, s := range statements {
|
||||
statementsSet[strings.ToLower(s)] = true
|
||||
|
||||
}
|
||||
return IgnorableLogger{
|
||||
logger: logger,
|
||||
statements: statementsSet,
|
||||
}
|
||||
}
|
||||
|
||||
func (l IgnorableLogger) Errorf(statementID, format string, v ...interface{}) {
|
||||
if l.statements[statementID] {
|
||||
// Ignore.
|
||||
return
|
||||
}
|
||||
ignoreMsg := fmt.Sprintf(`
|
||||
If you feel that this should not be logged as an ERROR, you can ignore it by adding this to your site config:
|
||||
ignoreErrors = [%q]`, statementID)
|
||||
|
||||
format += ignoreMsg
|
||||
|
||||
l.logger.ERROR.Printf(format, v...)
|
||||
}
|
||||
@@ -39,6 +39,23 @@ func init() {
|
||||
jww.SetLogListeners(jww.LogCounter(GlobalErrorCounter, jww.LevelError))
|
||||
}
|
||||
|
||||
func LoggerToWriterWithPrefix(logger *log.Logger, prefix string) io.Writer {
|
||||
return prefixWriter{
|
||||
logger: logger,
|
||||
prefix: prefix,
|
||||
}
|
||||
}
|
||||
|
||||
type prefixWriter struct {
|
||||
logger *log.Logger
|
||||
prefix string
|
||||
}
|
||||
|
||||
func (w prefixWriter) Write(p []byte) (n int, err error) {
|
||||
w.logger.Printf("%s: %s", w.prefix, p)
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// Logger wraps a *loggers.Logger and some other related logging state.
|
||||
type Logger struct {
|
||||
*jww.Notepad
|
||||
@@ -93,17 +110,32 @@ func NewLogger(stdoutThreshold, logThreshold jww.Threshold, outHandle, logHandle
|
||||
|
||||
// NewDebugLogger is a convenience function to create a debug logger.
|
||||
func NewDebugLogger() *Logger {
|
||||
return newBasicLogger(jww.LevelDebug)
|
||||
return NewBasicLogger(jww.LevelDebug)
|
||||
}
|
||||
|
||||
// NewWarningLogger is a convenience function to create a warning logger.
|
||||
func NewWarningLogger() *Logger {
|
||||
return newBasicLogger(jww.LevelWarn)
|
||||
return NewBasicLogger(jww.LevelWarn)
|
||||
}
|
||||
|
||||
// NewInfoLogger is a convenience function to create a info logger.
|
||||
func NewInfoLogger() *Logger {
|
||||
return NewBasicLogger(jww.LevelInfo)
|
||||
}
|
||||
|
||||
// NewErrorLogger is a convenience function to create an error logger.
|
||||
func NewErrorLogger() *Logger {
|
||||
return newBasicLogger(jww.LevelError)
|
||||
return NewBasicLogger(jww.LevelError)
|
||||
}
|
||||
|
||||
// NewBasicLogger creates a new basic logger writing to Stdout.
|
||||
func NewBasicLogger(t jww.Threshold) *Logger {
|
||||
return newLogger(t, jww.LevelError, os.Stdout, ioutil.Discard, false)
|
||||
}
|
||||
|
||||
// NewBasicLoggerForWriter creates a new basic logger writing to w.
|
||||
func NewBasicLoggerForWriter(t jww.Threshold, w io.Writer) *Logger {
|
||||
return newLogger(t, jww.LevelError, w, ioutil.Discard, false)
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -215,7 +247,3 @@ func newLogger(stdoutThreshold, logThreshold jww.Threshold, outHandle, logHandle
|
||||
errors: errorBuff,
|
||||
}
|
||||
}
|
||||
|
||||
func newBasicLogger(t jww.Threshold) *Logger {
|
||||
return newLogger(t, jww.LevelError, os.Stdout, ioutil.Discard, false)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
package loggers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
@@ -30,3 +33,17 @@ func TestLogger(t *testing.T) {
|
||||
c.Assert(l.ErrorCounter.Count(), qt.Equals, uint64(2))
|
||||
|
||||
}
|
||||
|
||||
func TestLoggerToWriterWithPrefix(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
var b bytes.Buffer
|
||||
|
||||
logger := log.New(&b, "", 0)
|
||||
|
||||
w := LoggerToWriterWithPrefix(logger, "myprefix")
|
||||
|
||||
fmt.Fprint(w, "Hello Hugo!")
|
||||
|
||||
c.Assert(b.String(), qt.Equals, "myprefix: Hello Hugo!\n")
|
||||
}
|
||||
|
||||
+10
-1
@@ -107,6 +107,15 @@ func (c *Scratch) Get(key string) interface{} {
|
||||
return val
|
||||
}
|
||||
|
||||
// Values returns the raw backing map. Note that you should just use
|
||||
// this method on the locally scoped Scratch instances you obtain via newScratch, not
|
||||
// .Page.Scratch etc., as that will lead to concurrency issues.
|
||||
func (c *Scratch) Values() map[string]interface{} {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
return c.values
|
||||
}
|
||||
|
||||
// SetInMap stores a value to a map with the given key in the Node context.
|
||||
// This map can later be retrieved with GetSortedMapValues.
|
||||
func (c *Scratch) SetInMap(key string, mapKey string, value interface{}) string {
|
||||
@@ -147,7 +156,7 @@ func (c *Scratch) GetSortedMapValues(key string) interface{} {
|
||||
return sortedArray
|
||||
}
|
||||
|
||||
// NewScratch returns a new instance Scratch.
|
||||
// NewScratch returns a new instance of Scratch.
|
||||
func NewScratch() *Scratch {
|
||||
return &Scratch{values: make(map[string]interface{})}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,9 @@ func TestScratchAdd(t *testing.T) {
|
||||
scratch.Add("scratch", scratch)
|
||||
_, err := scratch.Add("scratch", scratch)
|
||||
|
||||
m := scratch.Values()
|
||||
c.Assert(m, qt.HasLen, 5)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected error from invalid arithmetic")
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ package para
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
|
||||
"sort"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
// 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 types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
// ToStringSlicePreserveString converts v to a string slice.
|
||||
// If v is a string, it will be wrapped in a string slice.
|
||||
func ToStringSlicePreserveString(v interface{}) []string {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
if sds, ok := v.(string); ok {
|
||||
return []string{sds}
|
||||
}
|
||||
return cast.ToStringSlice(v)
|
||||
}
|
||||
|
||||
// TypeToString converts v to a string if it's a valid string type.
|
||||
// Note that this will not try to convert numeric values etc.,
|
||||
// use ToString for that.
|
||||
func TypeToString(v interface{}) (string, bool) {
|
||||
switch s := v.(type) {
|
||||
case string:
|
||||
return s, true
|
||||
case template.HTML:
|
||||
return string(s), true
|
||||
case template.CSS:
|
||||
return string(s), true
|
||||
case template.HTMLAttr:
|
||||
return string(s), true
|
||||
case template.JS:
|
||||
return string(s), true
|
||||
case template.JSStr:
|
||||
return string(s), true
|
||||
case template.URL:
|
||||
return string(s), true
|
||||
case template.Srcset:
|
||||
return string(s), true
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// ToString converts v to a string.
|
||||
func ToString(v interface{}) string {
|
||||
s, _ := ToStringE(v)
|
||||
return s
|
||||
}
|
||||
|
||||
// ToStringE converts v to a string.
|
||||
func ToStringE(v interface{}) (string, error) {
|
||||
if s, ok := TypeToString(v); ok {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
switch s := v.(type) {
|
||||
case json.RawMessage:
|
||||
return string(s), nil
|
||||
default:
|
||||
return cast.ToStringE(v)
|
||||
}
|
||||
}
|
||||
@@ -11,28 +11,28 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package asciidoc
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
|
||||
"github.com/gohugoio/hugo/markup/converter"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
func TestConvert(t *testing.T) {
|
||||
if !Supports() {
|
||||
t.Skip("asciidoc/asciidoctor not installed")
|
||||
}
|
||||
func TestToStringSlicePreserveString(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
p, err := Provider.New(converter.ProviderConfig{Logger: loggers.NewErrorLogger()})
|
||||
c.Assert(err, qt.IsNil)
|
||||
conv, err := p.New(converter.DocumentContext{})
|
||||
c.Assert(err, qt.IsNil)
|
||||
b, err := conv.Convert(converter.RenderContext{Src: []byte("testContent")})
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(string(b.Bytes()), qt.Equals, "<div class=\"paragraph\">\n<p>testContent</p>\n</div>\n")
|
||||
|
||||
c.Assert(ToStringSlicePreserveString("Hugo"), qt.DeepEquals, []string{"Hugo"})
|
||||
c.Assert(ToStringSlicePreserveString([]interface{}{"A", "B"}), qt.DeepEquals, []string{"A", "B"})
|
||||
c.Assert(ToStringSlicePreserveString(nil), qt.IsNil)
|
||||
|
||||
}
|
||||
|
||||
func TestToString(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
c.Assert(ToString([]byte("Hugo")), qt.Equals, "Hugo")
|
||||
c.Assert(ToString(json.RawMessage("Hugo")), qt.Equals, "Hugo")
|
||||
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
// 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 config
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/spf13/cast"
|
||||
jww "github.com/spf13/jwalterweatherman"
|
||||
)
|
||||
|
||||
var DefaultBuild = Build{
|
||||
UseResourceCacheWhen: "fallback",
|
||||
WriteStats: false,
|
||||
}
|
||||
|
||||
// Build holds some build related condfiguration.
|
||||
type Build struct {
|
||||
UseResourceCacheWhen string // never, fallback, always. Default is fallback
|
||||
|
||||
// When enabled, will collect and write a hugo_stats.json with some build
|
||||
// related aggregated data (e.g. CSS class names).
|
||||
WriteStats bool
|
||||
}
|
||||
|
||||
func (b Build) UseResourceCache(err error) bool {
|
||||
if b.UseResourceCacheWhen == "never" {
|
||||
return false
|
||||
}
|
||||
|
||||
if b.UseResourceCacheWhen == "fallback" {
|
||||
return err == herrors.ErrFeatureNotAvailable
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func DecodeBuild(cfg Provider) Build {
|
||||
m := cfg.GetStringMap("build")
|
||||
b := DefaultBuild
|
||||
if m == nil {
|
||||
return b
|
||||
}
|
||||
|
||||
err := mapstructure.WeakDecode(m, &b)
|
||||
if err != nil {
|
||||
return DefaultBuild
|
||||
}
|
||||
|
||||
b.UseResourceCacheWhen = strings.ToLower(b.UseResourceCacheWhen)
|
||||
when := b.UseResourceCacheWhen
|
||||
if when != "never" && when != "always" && when != "fallback" {
|
||||
b.UseResourceCacheWhen = "fallback"
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// Sitemap configures the sitemap to be generated.
|
||||
type Sitemap struct {
|
||||
ChangeFreq string
|
||||
Priority float64
|
||||
Filename string
|
||||
}
|
||||
|
||||
func DecodeSitemap(prototype Sitemap, input map[string]interface{}) Sitemap {
|
||||
|
||||
for key, value := range input {
|
||||
switch key {
|
||||
case "changefreq":
|
||||
prototype.ChangeFreq = cast.ToString(value)
|
||||
case "priority":
|
||||
prototype.Priority = cast.ToFloat64(value)
|
||||
case "filename":
|
||||
prototype.Filename = cast.ToString(value)
|
||||
default:
|
||||
jww.WARN.Printf("Unknown Sitemap field: %s\n", key)
|
||||
}
|
||||
}
|
||||
|
||||
return prototype
|
||||
}
|
||||
|
||||
// Config for the dev server.
|
||||
type Server struct {
|
||||
Headers []Headers
|
||||
Redirects []Redirect
|
||||
|
||||
compiledInit sync.Once
|
||||
compiledHeaders []glob.Glob
|
||||
compiledRedirects []glob.Glob
|
||||
}
|
||||
|
||||
func (s *Server) init() {
|
||||
|
||||
s.compiledInit.Do(func() {
|
||||
for _, h := range s.Headers {
|
||||
s.compiledHeaders = append(s.compiledHeaders, glob.MustCompile(h.For))
|
||||
}
|
||||
for _, r := range s.Redirects {
|
||||
s.compiledRedirects = append(s.compiledRedirects, glob.MustCompile(r.From))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) MatchHeaders(pattern string) []types.KeyValueStr {
|
||||
s.init()
|
||||
|
||||
if s.compiledHeaders == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var matches []types.KeyValueStr
|
||||
|
||||
for i, g := range s.compiledHeaders {
|
||||
if g.Match(pattern) {
|
||||
h := s.Headers[i]
|
||||
for k, v := range h.Values {
|
||||
matches = append(matches, types.KeyValueStr{Key: k, Value: cast.ToString(v)})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(matches, func(i, j int) bool {
|
||||
return matches[i].Key < matches[j].Key
|
||||
})
|
||||
|
||||
return matches
|
||||
|
||||
}
|
||||
|
||||
func (s *Server) MatchRedirect(pattern string) Redirect {
|
||||
s.init()
|
||||
|
||||
if s.compiledRedirects == nil {
|
||||
return Redirect{}
|
||||
}
|
||||
|
||||
pattern = strings.TrimSuffix(pattern, "index.html")
|
||||
|
||||
for i, g := range s.compiledRedirects {
|
||||
redir := s.Redirects[i]
|
||||
|
||||
// No redirect to self.
|
||||
if redir.To == pattern {
|
||||
return Redirect{}
|
||||
}
|
||||
|
||||
if g.Match(pattern) {
|
||||
return redir
|
||||
}
|
||||
}
|
||||
|
||||
return Redirect{}
|
||||
|
||||
}
|
||||
|
||||
type Headers struct {
|
||||
For string
|
||||
Values map[string]interface{}
|
||||
}
|
||||
|
||||
type Redirect struct {
|
||||
From string
|
||||
To string
|
||||
Status int
|
||||
Force bool
|
||||
}
|
||||
|
||||
func (r Redirect) IsZero() bool {
|
||||
return r.From == ""
|
||||
}
|
||||
|
||||
func DecodeServer(cfg Provider) (*Server, error) {
|
||||
m := cfg.GetStringMap("server")
|
||||
s := &Server{}
|
||||
if m == nil {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
_ = mapstructure.WeakDecode(m, s)
|
||||
|
||||
for i, redir := range s.Redirects {
|
||||
// Get it in line with the Hugo server.
|
||||
redir.To = strings.TrimSuffix(redir.To, "index.html")
|
||||
if !strings.HasPrefix(redir.To, "https") && !strings.HasSuffix(redir.To, "/") {
|
||||
// There are some tricky infinite loop situations when dealing
|
||||
// when the target does not have a trailing slash.
|
||||
// This can certainly be handled better, but not time for that now.
|
||||
return nil, errors.Errorf("unsupported redirect to value %q in server config; currently this must be either a remote destination or a local folder, e.g. \"/blog/\" or \"/blog/index.html\"", redir.To)
|
||||
}
|
||||
s.Redirects[i] = redir
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
// 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 config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func TestBuild(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
v := viper.New()
|
||||
v.Set("build", map[string]interface{}{
|
||||
"useResourceCacheWhen": "always",
|
||||
})
|
||||
|
||||
b := DecodeBuild(v)
|
||||
|
||||
c.Assert(b.UseResourceCacheWhen, qt.Equals, "always")
|
||||
|
||||
v.Set("build", map[string]interface{}{
|
||||
"useResourceCacheWhen": "foo",
|
||||
})
|
||||
|
||||
b = DecodeBuild(v)
|
||||
|
||||
c.Assert(b.UseResourceCacheWhen, qt.Equals, "fallback")
|
||||
|
||||
c.Assert(b.UseResourceCache(herrors.ErrFeatureNotAvailable), qt.Equals, true)
|
||||
c.Assert(b.UseResourceCache(errors.New("err")), qt.Equals, false)
|
||||
|
||||
b.UseResourceCacheWhen = "always"
|
||||
c.Assert(b.UseResourceCache(herrors.ErrFeatureNotAvailable), qt.Equals, true)
|
||||
c.Assert(b.UseResourceCache(errors.New("err")), qt.Equals, true)
|
||||
c.Assert(b.UseResourceCache(nil), qt.Equals, true)
|
||||
|
||||
b.UseResourceCacheWhen = "never"
|
||||
c.Assert(b.UseResourceCache(herrors.ErrFeatureNotAvailable), qt.Equals, false)
|
||||
c.Assert(b.UseResourceCache(errors.New("err")), qt.Equals, false)
|
||||
c.Assert(b.UseResourceCache(nil), qt.Equals, false)
|
||||
|
||||
}
|
||||
|
||||
func TestServer(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
cfg, err := FromConfigString(`[[server.headers]]
|
||||
for = "/*.jpg"
|
||||
|
||||
[server.headers.values]
|
||||
X-Frame-Options = "DENY"
|
||||
X-XSS-Protection = "1; mode=block"
|
||||
X-Content-Type-Options = "nosniff"
|
||||
|
||||
[[server.redirects]]
|
||||
from = "/foo/**"
|
||||
to = "/foo/index.html"
|
||||
status = 200
|
||||
|
||||
[[server.redirects]]
|
||||
from = "/google/**"
|
||||
to = "https://google.com/"
|
||||
status = 301
|
||||
|
||||
[[server.redirects]]
|
||||
from = "/**"
|
||||
to = "/default/index.html"
|
||||
status = 301
|
||||
|
||||
|
||||
|
||||
`, "toml")
|
||||
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
s, err := DecodeServer(cfg)
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
c.Assert(s.MatchHeaders("/foo.jpg"), qt.DeepEquals, []types.KeyValueStr{
|
||||
{Key: "X-Content-Type-Options", Value: "nosniff"},
|
||||
{Key: "X-Frame-Options", Value: "DENY"},
|
||||
{Key: "X-XSS-Protection", Value: "1; mode=block"}})
|
||||
|
||||
c.Assert(s.MatchRedirect("/foo/bar/baz"), qt.DeepEquals, Redirect{
|
||||
From: "/foo/**",
|
||||
To: "/foo/",
|
||||
Status: 200,
|
||||
})
|
||||
|
||||
c.Assert(s.MatchRedirect("/someother"), qt.DeepEquals, Redirect{
|
||||
From: "/**",
|
||||
To: "/default/",
|
||||
Status: 301,
|
||||
})
|
||||
|
||||
c.Assert(s.MatchRedirect("/google/foo"), qt.DeepEquals, Redirect{
|
||||
From: "/google/**",
|
||||
To: "https://google.com/",
|
||||
Status: 301,
|
||||
})
|
||||
|
||||
// No redirect loop, please.
|
||||
c.Assert(s.MatchRedirect("/default/index.html"), qt.DeepEquals, Redirect{})
|
||||
c.Assert(s.MatchRedirect("/default/"), qt.DeepEquals, Redirect{})
|
||||
|
||||
for _, errorCase := range []string{`[[server.redirects]]
|
||||
from = "/**"
|
||||
to = "/file"
|
||||
status = 301`,
|
||||
`[[server.redirects]]
|
||||
from = "/**"
|
||||
to = "/foo/file.html"
|
||||
status = 301`,
|
||||
} {
|
||||
|
||||
cfg, err := FromConfigString(errorCase, "toml")
|
||||
c.Assert(err, qt.IsNil)
|
||||
_, err = DecodeServer(cfg)
|
||||
c.Assert(err, qt.Not(qt.IsNil))
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/spf13/cast"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
)
|
||||
|
||||
// Provider provides the configuration settings for Hugo.
|
||||
@@ -35,14 +35,7 @@ type Provider interface {
|
||||
// we do not attempt to split it into fields.
|
||||
func GetStringSlicePreserveString(cfg Provider, key string) []string {
|
||||
sd := cfg.Get(key)
|
||||
return toStringSlicePreserveString(sd)
|
||||
}
|
||||
|
||||
func toStringSlicePreserveString(v interface{}) []string {
|
||||
if sds, ok := v.(string); ok {
|
||||
return []string{sds}
|
||||
}
|
||||
return cast.ToStringSlice(v)
|
||||
return types.ToStringSlicePreserveString(sd)
|
||||
}
|
||||
|
||||
// SetBaseTestDefaults provides some common config defaults used in tests.
|
||||
|
||||
@@ -80,6 +80,10 @@ type Twitter struct {
|
||||
type Vimeo struct {
|
||||
Service `mapstructure:",squash"`
|
||||
|
||||
// When set to true, the Vimeo player will be blocked from tracking any session data,
|
||||
// including all cookies and stats.
|
||||
EnableDNT bool
|
||||
|
||||
// If simple mode is enabled, only a thumbnail is fetched from i.vimeocdn.com and
|
||||
// shown with a play button overlaid. If a user clicks the button, he/she will
|
||||
// be taken to the video page on vimeo.com in a new browser tab.
|
||||
|
||||
@@ -45,6 +45,7 @@ enableDNT = true
|
||||
simple = true
|
||||
[privacy.vimeo]
|
||||
disable = true
|
||||
enableDNT = true
|
||||
simple = true
|
||||
[privacy.youtube]
|
||||
disable = true
|
||||
@@ -63,7 +64,7 @@ simple = true
|
||||
pc.GoogleAnalytics.RespectDoNotTrack, pc.GoogleAnalytics.AnonymizeIP,
|
||||
pc.GoogleAnalytics.UseSessionStorage, pc.Instagram.Disable,
|
||||
pc.Instagram.Simple, pc.Twitter.Disable, pc.Twitter.EnableDNT,
|
||||
pc.Twitter.Simple, pc.Vimeo.Disable, pc.Vimeo.Simple,
|
||||
pc.Twitter.Simple, pc.Vimeo.Disable, pc.Vimeo.EnableDNT, pc.Vimeo.Simple,
|
||||
pc.YouTube.PrivacyEnhanced, pc.YouTube.Disable,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,44 +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 config
|
||||
|
||||
import (
|
||||
"github.com/spf13/cast"
|
||||
jww "github.com/spf13/jwalterweatherman"
|
||||
)
|
||||
|
||||
// Sitemap configures the sitemap to be generated.
|
||||
type Sitemap struct {
|
||||
ChangeFreq string
|
||||
Priority float64
|
||||
Filename string
|
||||
}
|
||||
|
||||
func DecodeSitemap(prototype Sitemap, input map[string]interface{}) Sitemap {
|
||||
|
||||
for key, value := range input {
|
||||
switch key {
|
||||
case "changefreq":
|
||||
prototype.ChangeFreq = cast.ToString(value)
|
||||
case "priority":
|
||||
prototype.Priority = cast.ToFloat64(value)
|
||||
case "filename":
|
||||
prototype.Filename = cast.ToString(value)
|
||||
default:
|
||||
jww.WARN.Printf("Unknown Sitemap field: %s\n", key)
|
||||
}
|
||||
}
|
||||
|
||||
return prototype
|
||||
}
|
||||
@@ -110,7 +110,7 @@ func executeArcheTypeAsTemplate(s *hugolib.Site, name, kind, targetPath, archety
|
||||
Date: time.Now().Format(time.RFC3339),
|
||||
Name: name,
|
||||
File: f,
|
||||
Site: &s.Info,
|
||||
Site: s.Info,
|
||||
}
|
||||
|
||||
if archetypeFilename == "" {
|
||||
|
||||
+67
-16
@@ -31,7 +31,9 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/gobwas/glob"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/afero"
|
||||
jww "github.com/spf13/jwalterweatherman"
|
||||
@@ -50,6 +52,7 @@ type Deployer struct {
|
||||
|
||||
target *target // the target to deploy to
|
||||
matchers []*matcher // matchers to apply to uploaded files
|
||||
mediaTypes media.Types // Hugo's MediaType to guess ContentType
|
||||
ordering []*regexp.Regexp // orders uploads
|
||||
quiet bool // true reduces STDOUT
|
||||
confirm bool // true enables confirmation before making changes
|
||||
@@ -95,11 +98,13 @@ func New(cfg config.Provider, localFs afero.Fs) (*Deployer, error) {
|
||||
return nil, fmt.Errorf("deployment target %q not found", targetName)
|
||||
}
|
||||
}
|
||||
|
||||
return &Deployer{
|
||||
localFs: localFs,
|
||||
target: tgt,
|
||||
matchers: dcfg.Matchers,
|
||||
ordering: dcfg.ordering,
|
||||
mediaTypes: dcfg.mediaTypes,
|
||||
quiet: cfg.GetBool("quiet"),
|
||||
confirm: cfg.GetBool("confirm"),
|
||||
dryRun: cfg.GetBool("dryRun"),
|
||||
@@ -125,7 +130,11 @@ func (d *Deployer) Deploy(ctx context.Context) error {
|
||||
}
|
||||
|
||||
// Load local files from the source directory.
|
||||
local, err := walkLocal(d.localFs, d.matchers)
|
||||
var include, exclude glob.Glob
|
||||
if d.target != nil {
|
||||
include, exclude = d.target.includeGlob, d.target.excludeGlob
|
||||
}
|
||||
local, err := walkLocal(d.localFs, d.matchers, include, exclude, d.mediaTypes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -133,7 +142,7 @@ func (d *Deployer) Deploy(ctx context.Context) error {
|
||||
d.summary.NumLocal = len(local)
|
||||
|
||||
// Load remote files from the target.
|
||||
remote, err := walkRemote(ctx, bucket)
|
||||
remote, err := walkRemote(ctx, bucket, include, exclude)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -317,14 +326,15 @@ type localFile struct {
|
||||
// gzipped before upload.
|
||||
UploadSize int64
|
||||
|
||||
fs afero.Fs
|
||||
matcher *matcher
|
||||
md5 []byte // cache
|
||||
gzipped bytes.Buffer // cached of gzipped contents if gzipping
|
||||
fs afero.Fs
|
||||
matcher *matcher
|
||||
md5 []byte // cache
|
||||
gzipped bytes.Buffer // cached of gzipped contents if gzipping
|
||||
mediaTypes media.Types
|
||||
}
|
||||
|
||||
// newLocalFile initializes a *localFile.
|
||||
func newLocalFile(fs afero.Fs, nativePath, slashpath string, m *matcher) (*localFile, error) {
|
||||
func newLocalFile(fs afero.Fs, nativePath, slashpath string, m *matcher, mt media.Types) (*localFile, error) {
|
||||
f, err := fs.Open(nativePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -335,6 +345,7 @@ func newLocalFile(fs afero.Fs, nativePath, slashpath string, m *matcher) (*local
|
||||
SlashPath: slashpath,
|
||||
fs: fs,
|
||||
matcher: m,
|
||||
mediaTypes: mt,
|
||||
}
|
||||
if m != nil && m.Gzip {
|
||||
// We're going to gzip the content. Do it once now, and cache the result
|
||||
@@ -405,10 +416,13 @@ func (lf *localFile) ContentType() string {
|
||||
if lf.matcher != nil && lf.matcher.ContentType != "" {
|
||||
return lf.matcher.ContentType
|
||||
}
|
||||
// TODO: Hugo has a MediaType and a MediaTypes list and also a concept
|
||||
// of custom MIME types.
|
||||
// Use 1) The matcher 2) Hugo's MIME types 3) TypeByExtension.
|
||||
return mime.TypeByExtension(filepath.Ext(lf.NativePath))
|
||||
|
||||
ext := filepath.Ext(lf.NativePath)
|
||||
if mimeType, found := lf.mediaTypes.GetFirstBySuffix(strings.TrimPrefix(ext, ".")); found {
|
||||
return mimeType.Type()
|
||||
}
|
||||
|
||||
return mime.TypeByExtension(ext)
|
||||
}
|
||||
|
||||
// Force returns true if the file should be forced to re-upload based on the
|
||||
@@ -435,9 +449,24 @@ func (lf *localFile) MD5() []byte {
|
||||
return lf.md5
|
||||
}
|
||||
|
||||
// knownHiddenDirectory checks if the specified name is a well known
|
||||
// hidden directory.
|
||||
func knownHiddenDirectory(name string) bool {
|
||||
var knownDirectories = []string{
|
||||
".well-known",
|
||||
}
|
||||
|
||||
for _, dir := range knownDirectories {
|
||||
if name == dir {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// walkLocal walks the source directory and returns a flat list of files,
|
||||
// using localFile.SlashPath as the map keys.
|
||||
func walkLocal(fs afero.Fs, matchers []*matcher) (map[string]*localFile, error) {
|
||||
func walkLocal(fs afero.Fs, matchers []*matcher, include, exclude glob.Glob, mediaTypes media.Types) (map[string]*localFile, error) {
|
||||
retval := map[string]*localFile{}
|
||||
err := afero.Walk(fs, "", func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
@@ -446,7 +475,10 @@ func walkLocal(fs afero.Fs, matchers []*matcher) (map[string]*localFile, error)
|
||||
if info.IsDir() {
|
||||
// Skip hidden directories.
|
||||
if path != "" && strings.HasPrefix(info.Name(), ".") {
|
||||
return filepath.SkipDir
|
||||
// Except for specific hidden directories
|
||||
if !knownHiddenDirectory(info.Name()) {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -461,8 +493,18 @@ func walkLocal(fs afero.Fs, matchers []*matcher) (map[string]*localFile, error)
|
||||
path = norm.NFC.String(path)
|
||||
}
|
||||
|
||||
// Find the first matching matcher (if any).
|
||||
// Check include/exclude matchers.
|
||||
slashpath := filepath.ToSlash(path)
|
||||
if include != nil && !include.Match(slashpath) {
|
||||
jww.INFO.Printf(" dropping %q due to include\n", slashpath)
|
||||
return nil
|
||||
}
|
||||
if exclude != nil && exclude.Match(slashpath) {
|
||||
jww.INFO.Printf(" dropping %q due to exclude\n", slashpath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Find the first matching matcher (if any).
|
||||
var m *matcher
|
||||
for _, cur := range matchers {
|
||||
if cur.Matches(slashpath) {
|
||||
@@ -470,7 +512,7 @@ func walkLocal(fs afero.Fs, matchers []*matcher) (map[string]*localFile, error)
|
||||
break
|
||||
}
|
||||
}
|
||||
lf, err := newLocalFile(fs, path, slashpath, m)
|
||||
lf, err := newLocalFile(fs, path, slashpath, m, mediaTypes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -484,7 +526,7 @@ func walkLocal(fs afero.Fs, matchers []*matcher) (map[string]*localFile, error)
|
||||
}
|
||||
|
||||
// walkRemote walks the target bucket and returns a flat list.
|
||||
func walkRemote(ctx context.Context, bucket *blob.Bucket) (map[string]*blob.ListObject, error) {
|
||||
func walkRemote(ctx context.Context, bucket *blob.Bucket, include, exclude glob.Glob) (map[string]*blob.ListObject, error) {
|
||||
retval := map[string]*blob.ListObject{}
|
||||
iter := bucket.List(nil)
|
||||
for {
|
||||
@@ -495,6 +537,15 @@ func walkRemote(ctx context.Context, bucket *blob.Bucket) (map[string]*blob.List
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Check include/exclude matchers.
|
||||
if include != nil && !include.Match(obj.Key) {
|
||||
jww.INFO.Printf(" remote dropping %q due to include\n", obj.Key)
|
||||
continue
|
||||
}
|
||||
if exclude != nil && exclude.Match(obj.Key) {
|
||||
jww.INFO.Printf(" remote dropping %q due to exclude\n", obj.Key)
|
||||
continue
|
||||
}
|
||||
// If the remote didn't give us an MD5, compute one.
|
||||
// This can happen for some providers (e.g., fileblob, which uses the
|
||||
// local filesystem), but not for the most common Cloud providers
|
||||
|
||||
+51
-2
@@ -17,7 +17,10 @@ import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
hglob "github.com/gohugoio/hugo/hugofs/glob"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
@@ -29,7 +32,8 @@ type deployConfig struct {
|
||||
Matchers []*matcher
|
||||
Order []string
|
||||
|
||||
ordering []*regexp.Regexp // compiled Order
|
||||
ordering []*regexp.Regexp // compiled Order
|
||||
mediaTypes media.Types
|
||||
}
|
||||
|
||||
type target struct {
|
||||
@@ -41,6 +45,32 @@ type target struct {
|
||||
// GoogleCloudCDNOrigin specifies the Google Cloud project and CDN origin to
|
||||
// invalidate when deploying this target. It is specified as <project>/<origin>.
|
||||
GoogleCloudCDNOrigin string
|
||||
|
||||
// Optional patterns of files to include/exclude for this target.
|
||||
// Parsed using github.com/gobwas/glob.
|
||||
Include string
|
||||
Exclude string
|
||||
|
||||
// Parsed versions of Include/Exclude.
|
||||
includeGlob glob.Glob
|
||||
excludeGlob glob.Glob
|
||||
}
|
||||
|
||||
func (tgt *target) parseIncludeExclude() error {
|
||||
var err error
|
||||
if tgt.Include != "" {
|
||||
tgt.includeGlob, err = hglob.GetGlob(tgt.Include)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid deployment.target.include %q: %v", tgt.Include, err)
|
||||
}
|
||||
}
|
||||
if tgt.Exclude != "" {
|
||||
tgt.excludeGlob, err = hglob.GetGlob(tgt.Exclude)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid deployment.target.exclude %q: %v", tgt.Exclude, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// matcher represents configuration to be applied to files whose paths match
|
||||
@@ -80,13 +110,23 @@ func (m *matcher) Matches(path string) bool {
|
||||
|
||||
// decode creates a config from a given Hugo configuration.
|
||||
func decodeConfig(cfg config.Provider) (deployConfig, error) {
|
||||
var dcfg deployConfig
|
||||
|
||||
var (
|
||||
mediaTypesConfig []map[string]interface{}
|
||||
dcfg deployConfig
|
||||
)
|
||||
|
||||
if !cfg.IsSet(deploymentConfigKey) {
|
||||
return dcfg, nil
|
||||
}
|
||||
if err := mapstructure.WeakDecode(cfg.GetStringMap(deploymentConfigKey), &dcfg); err != nil {
|
||||
return dcfg, err
|
||||
}
|
||||
for _, tgt := range dcfg.Targets {
|
||||
if err := tgt.parseIncludeExclude(); err != nil {
|
||||
return dcfg, err
|
||||
}
|
||||
}
|
||||
var err error
|
||||
for _, m := range dcfg.Matchers {
|
||||
m.re, err = regexp.Compile(m.Pattern)
|
||||
@@ -101,5 +141,14 @@ func decodeConfig(cfg config.Provider) (deployConfig, error) {
|
||||
}
|
||||
dcfg.ordering = append(dcfg.ordering, re)
|
||||
}
|
||||
|
||||
if cfg.IsSet("mediaTypes") {
|
||||
mediaTypesConfig = append(mediaTypesConfig, cfg.GetStringMap("mediaTypes"))
|
||||
}
|
||||
|
||||
dcfg.mediaTypes, err = media.DecodeTypes(mediaTypesConfig...)
|
||||
if err != nil {
|
||||
return dcfg, err
|
||||
}
|
||||
return dcfg, nil
|
||||
}
|
||||
|
||||
@@ -38,18 +38,21 @@ order = ["o1", "o2"]
|
||||
name = "name0"
|
||||
url = "url0"
|
||||
cloudfrontdistributionid = "cdn0"
|
||||
include = "*.html"
|
||||
|
||||
# All uppercase.
|
||||
[[deployment.targets]]
|
||||
NAME = "name1"
|
||||
URL = "url1"
|
||||
CLOUDFRONTDISTRIBUTIONID = "cdn1"
|
||||
INCLUDE = "*.jpg"
|
||||
|
||||
# Camelcase.
|
||||
[[deployment.targets]]
|
||||
name = "name2"
|
||||
url = "url2"
|
||||
cloudFrontDistributionID = "cdn2"
|
||||
exclude = "*.png"
|
||||
|
||||
# All lowercase.
|
||||
[[deployment.matchers]]
|
||||
@@ -90,11 +93,21 @@ force = true
|
||||
|
||||
// Targets.
|
||||
c.Assert(len(dcfg.Targets), qt.Equals, 3)
|
||||
wantInclude := []string{"*.html", "*.jpg", ""}
|
||||
wantExclude := []string{"", "", "*.png"}
|
||||
for i := 0; i < 3; i++ {
|
||||
tgt := dcfg.Targets[i]
|
||||
c.Assert(tgt.Name, qt.Equals, fmt.Sprintf("name%d", i))
|
||||
c.Assert(tgt.URL, qt.Equals, fmt.Sprintf("url%d", i))
|
||||
c.Assert(tgt.CloudFrontDistributionID, qt.Equals, fmt.Sprintf("cdn%d", i))
|
||||
c.Assert(tgt.Include, qt.Equals, wantInclude[i])
|
||||
if wantInclude[i] != "" {
|
||||
c.Assert(tgt.includeGlob, qt.Not(qt.IsNil))
|
||||
}
|
||||
c.Assert(tgt.Exclude, qt.Equals, wantExclude[i])
|
||||
if wantExclude[i] != "" {
|
||||
c.Assert(tgt.excludeGlob, qt.Not(qt.IsNil))
|
||||
}
|
||||
}
|
||||
|
||||
// Matchers.
|
||||
|
||||
+257
-7
@@ -28,6 +28,7 @@ import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/gohugoio/hugo/media"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/spf13/afero"
|
||||
@@ -207,6 +208,66 @@ func TestFindDiffs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWalkLocal(t *testing.T) {
|
||||
|
||||
tests := map[string]struct {
|
||||
Given []string
|
||||
Expect []string
|
||||
}{
|
||||
"Empty": {
|
||||
Given: []string{},
|
||||
Expect: []string{},
|
||||
},
|
||||
"Normal": {
|
||||
Given: []string{"file.txt", "normal_dir/file.txt"},
|
||||
Expect: []string{"file.txt", "normal_dir/file.txt"},
|
||||
},
|
||||
"Hidden": {
|
||||
Given: []string{"file.txt", ".hidden_dir/file.txt", "normal_dir/file.txt"},
|
||||
Expect: []string{"file.txt", "normal_dir/file.txt"},
|
||||
},
|
||||
"Well Known": {
|
||||
Given: []string{"file.txt", ".hidden_dir/file.txt", ".well-known/file.txt"},
|
||||
Expect: []string{"file.txt", ".well-known/file.txt"},
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range tests {
|
||||
t.Run(desc, func(t *testing.T) {
|
||||
fs := afero.NewMemMapFs()
|
||||
for _, name := range tc.Given {
|
||||
dir, _ := path.Split(name)
|
||||
if dir != "" {
|
||||
if err := fs.MkdirAll(dir, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if fd, err := fs.Create(name); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
fd.Close()
|
||||
}
|
||||
}
|
||||
if got, err := walkLocal(fs, nil, nil, nil, media.DefaultTypes); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
expect := map[string]interface{}{}
|
||||
for _, path := range tc.Expect {
|
||||
if _, ok := got[path]; !ok {
|
||||
t.Errorf("expected %q in results, but was not found", path)
|
||||
}
|
||||
expect[path] = nil
|
||||
}
|
||||
for path := range got {
|
||||
if _, ok := expect[path]; !ok {
|
||||
t.Errorf("got %q in results unexpectedly", path)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalFile(t *testing.T) {
|
||||
const (
|
||||
content = "hello world!"
|
||||
@@ -228,6 +289,7 @@ func TestLocalFile(t *testing.T) {
|
||||
Description string
|
||||
Path string
|
||||
Matcher *matcher
|
||||
MediaTypesConfig []map[string]interface{}
|
||||
WantContent []byte
|
||||
WantSize int64
|
||||
WantMD5 []byte
|
||||
@@ -285,6 +347,18 @@ func TestLocalFile(t *testing.T) {
|
||||
WantMD5: gzMD5[:],
|
||||
WantContentEncoding: "gzip",
|
||||
},
|
||||
{
|
||||
Description: "Custom MediaType",
|
||||
Path: "foo.hugo",
|
||||
MediaTypesConfig: []map[string]interface{}{
|
||||
{
|
||||
"hugo/custom": map[string]interface{}{
|
||||
"suffixes": []string{"hugo"}}}},
|
||||
WantContent: contentBytes,
|
||||
WantSize: contentLen,
|
||||
WantMD5: contentMD5[:],
|
||||
WantContentType: "hugo/custom",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
@@ -293,7 +367,15 @@ func TestLocalFile(t *testing.T) {
|
||||
if err := afero.WriteFile(fs, tc.Path, []byte(content), os.ModePerm); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
lf, err := newLocalFile(fs, tc.Path, filepath.ToSlash(tc.Path), tc.Matcher)
|
||||
mediaTypes := media.DefaultTypes
|
||||
if len(tc.MediaTypesConfig) > 0 {
|
||||
mt, err := media.DecodeTypes(tc.MediaTypesConfig...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mediaTypes = mt
|
||||
}
|
||||
lf, err := newLocalFile(fs, tc.Path, filepath.ToSlash(tc.Path), tc.Matcher, mediaTypes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -484,6 +566,7 @@ func TestEndToEndSync(t *testing.T) {
|
||||
localFs: test.fs,
|
||||
maxDeletes: -1,
|
||||
bucket: test.bucket,
|
||||
mediaTypes: media.DefaultTypes,
|
||||
}
|
||||
|
||||
// Initial deployment should sync remote with local.
|
||||
@@ -570,6 +653,7 @@ func TestMaxDeletes(t *testing.T) {
|
||||
localFs: test.fs,
|
||||
maxDeletes: -1,
|
||||
bucket: test.bucket,
|
||||
mediaTypes: media.DefaultTypes,
|
||||
}
|
||||
|
||||
// Sync remote with local.
|
||||
@@ -640,10 +724,174 @@ func TestMaxDeletes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestIncludeExclude verifies that the include/exclude options for targets work.
|
||||
func TestIncludeExclude(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
tests := []struct {
|
||||
Include string
|
||||
Exclude string
|
||||
Want deploySummary
|
||||
}{
|
||||
{
|
||||
Want: deploySummary{NumLocal: 5, NumUploads: 5},
|
||||
},
|
||||
{
|
||||
Include: "**aaa",
|
||||
Want: deploySummary{NumLocal: 3, NumUploads: 3},
|
||||
},
|
||||
{
|
||||
Include: "**bbb",
|
||||
Want: deploySummary{NumLocal: 2, NumUploads: 2},
|
||||
},
|
||||
{
|
||||
Include: "aaa",
|
||||
Want: deploySummary{NumLocal: 1, NumUploads: 1},
|
||||
},
|
||||
{
|
||||
Exclude: "**aaa",
|
||||
Want: deploySummary{NumLocal: 2, NumUploads: 2},
|
||||
},
|
||||
{
|
||||
Exclude: "**bbb",
|
||||
Want: deploySummary{NumLocal: 3, NumUploads: 3},
|
||||
},
|
||||
{
|
||||
Exclude: "aaa",
|
||||
Want: deploySummary{NumLocal: 4, NumUploads: 4},
|
||||
},
|
||||
{
|
||||
Include: "**aaa",
|
||||
Exclude: "**nested**",
|
||||
Want: deploySummary{NumLocal: 2, NumUploads: 2},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(fmt.Sprintf("include %q exclude %q", test.Include, test.Exclude), func(t *testing.T) {
|
||||
fsTests, cleanup, err := initFsTests()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanup()
|
||||
fsTest := fsTests[1] // just do file-based test
|
||||
|
||||
_, err = initLocalFs(ctx, fsTest.fs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tgt := &target{
|
||||
Include: test.Include,
|
||||
Exclude: test.Exclude,
|
||||
}
|
||||
if err := tgt.parseIncludeExclude(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
deployer := &Deployer{
|
||||
localFs: fsTest.fs,
|
||||
maxDeletes: -1,
|
||||
bucket: fsTest.bucket,
|
||||
target: tgt,
|
||||
mediaTypes: media.DefaultTypes,
|
||||
}
|
||||
|
||||
// Sync remote with local.
|
||||
if err := deployer.Deploy(ctx); err != nil {
|
||||
t.Errorf("deploy: failed: %v", err)
|
||||
}
|
||||
if !cmp.Equal(deployer.summary, test.Want) {
|
||||
t.Errorf("deploy: got %v, want %v", deployer.summary, test.Want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestIncludeExcludeRemoteDelete verifies deleted local files that don't match include/exclude patterns
|
||||
// are not deleted on the remote.
|
||||
func TestIncludeExcludeRemoteDelete(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
tests := []struct {
|
||||
Include string
|
||||
Exclude string
|
||||
Want deploySummary
|
||||
}{
|
||||
{
|
||||
Want: deploySummary{NumLocal: 3, NumRemote: 5, NumUploads: 0, NumDeletes: 2},
|
||||
},
|
||||
{
|
||||
Include: "**aaa",
|
||||
Want: deploySummary{NumLocal: 2, NumRemote: 3, NumUploads: 0, NumDeletes: 1},
|
||||
},
|
||||
{
|
||||
Include: "subdir/**",
|
||||
Want: deploySummary{NumLocal: 1, NumRemote: 2, NumUploads: 0, NumDeletes: 1},
|
||||
},
|
||||
{
|
||||
Exclude: "**bbb",
|
||||
Want: deploySummary{NumLocal: 2, NumRemote: 3, NumUploads: 0, NumDeletes: 1},
|
||||
},
|
||||
{
|
||||
Exclude: "bbb",
|
||||
Want: deploySummary{NumLocal: 3, NumRemote: 4, NumUploads: 0, NumDeletes: 1},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(fmt.Sprintf("include %q exclude %q", test.Include, test.Exclude), func(t *testing.T) {
|
||||
fsTests, cleanup, err := initFsTests()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanup()
|
||||
fsTest := fsTests[1] // just do file-based test
|
||||
|
||||
local, err := initLocalFs(ctx, fsTest.fs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
deployer := &Deployer{
|
||||
localFs: fsTest.fs,
|
||||
maxDeletes: -1,
|
||||
bucket: fsTest.bucket,
|
||||
mediaTypes: media.DefaultTypes,
|
||||
}
|
||||
|
||||
// Initial sync to get the files on the remote
|
||||
if err := deployer.Deploy(ctx); err != nil {
|
||||
t.Errorf("deploy: failed: %v", err)
|
||||
}
|
||||
|
||||
// Delete two files, [1] and [2].
|
||||
if err := fsTest.fs.Remove(local[1].Name); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := fsTest.fs.Remove(local[2].Name); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Second sync
|
||||
tgt := &target{
|
||||
Include: test.Include,
|
||||
Exclude: test.Exclude,
|
||||
}
|
||||
if err := tgt.parseIncludeExclude(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
deployer.target = tgt
|
||||
if err := deployer.Deploy(ctx); err != nil {
|
||||
t.Errorf("deploy: failed: %v", err)
|
||||
}
|
||||
|
||||
if !cmp.Equal(deployer.summary, test.Want) {
|
||||
t.Errorf("deploy: got %v, want %v", deployer.summary, test.Want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestCompression verifies that gzip compression works correctly.
|
||||
// In particular, MD5 hashes must be of the compressed content.
|
||||
func TestCompression(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
tests, cleanup, err := initFsTests()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -656,9 +904,10 @@ func TestCompression(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
deployer := &Deployer{
|
||||
localFs: test.fs,
|
||||
bucket: test.bucket,
|
||||
matchers: []*matcher{{Pattern: ".*", Gzip: true, re: regexp.MustCompile(".*")}},
|
||||
localFs: test.fs,
|
||||
bucket: test.bucket,
|
||||
matchers: []*matcher{{Pattern: ".*", Gzip: true, re: regexp.MustCompile(".*")}},
|
||||
mediaTypes: media.DefaultTypes,
|
||||
}
|
||||
|
||||
// Initial deployment should sync remote with local.
|
||||
@@ -714,9 +963,10 @@ func TestMatching(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
deployer := &Deployer{
|
||||
localFs: test.fs,
|
||||
bucket: test.bucket,
|
||||
matchers: []*matcher{{Pattern: "^subdir/aaa$", Force: true, re: regexp.MustCompile("^subdir/aaa$")}},
|
||||
localFs: test.fs,
|
||||
bucket: test.bucket,
|
||||
matchers: []*matcher{{Pattern: "^subdir/aaa$", Force: true, re: regexp.MustCompile("^subdir/aaa$")}},
|
||||
mediaTypes: media.DefaultTypes,
|
||||
}
|
||||
|
||||
// Initial deployment to sync remote with local.
|
||||
|
||||
Vendored
+25
-15
@@ -2,10 +2,10 @@ package deps
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/atomic"
|
||||
|
||||
"github.com/gohugoio/hugo/cache/filecache"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
@@ -66,7 +66,7 @@ type Deps struct {
|
||||
FileCaches filecache.Caches
|
||||
|
||||
// The translation func to use
|
||||
Translate func(translationID string, args ...interface{}) string `json:"-"`
|
||||
Translate func(translationID string, templateData interface{}) string `json:"-"`
|
||||
|
||||
// The language in use. TODO(bep) consolidate with site
|
||||
Language *langs.Language
|
||||
@@ -93,8 +93,12 @@ type Deps struct {
|
||||
// BuildStartListeners will be notified before a build starts.
|
||||
BuildStartListeners *Listeners
|
||||
|
||||
// Atomic flags set during a build.
|
||||
BuildFlags BuildFlags
|
||||
// Atomic values set during a build.
|
||||
// This is common/global for all sites.
|
||||
BuildState *BuildState
|
||||
|
||||
// Whether we are in running (server) mode
|
||||
Running bool
|
||||
|
||||
*globalErrHandler
|
||||
}
|
||||
@@ -236,7 +240,10 @@ func New(cfg DepsCfg) (*Deps, error) {
|
||||
return nil, errors.WithMessage(err, "failed to create file caches from configuration")
|
||||
}
|
||||
|
||||
resourceSpec, err := resources.NewSpec(ps, fileCaches, logger, cfg.OutputFormats, cfg.MediaTypes)
|
||||
errorHandler := &globalErrHandler{}
|
||||
buildState := &BuildState{}
|
||||
|
||||
resourceSpec, err := resources.NewSpec(ps, fileCaches, buildState, logger, errorHandler, cfg.OutputFormats, cfg.MediaTypes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -274,8 +281,10 @@ func New(cfg DepsCfg) (*Deps, error) {
|
||||
Site: cfg.Site,
|
||||
FileCaches: fileCaches,
|
||||
BuildStartListeners: &Listeners{},
|
||||
BuildState: buildState,
|
||||
Running: cfg.Running,
|
||||
Timeout: time.Duration(timeoutms) * time.Millisecond,
|
||||
globalErrHandler: &globalErrHandler{},
|
||||
globalErrHandler: errorHandler,
|
||||
}
|
||||
|
||||
if cfg.Cfg.GetBool("templateMetrics") {
|
||||
@@ -306,7 +315,7 @@ func (d Deps) ForLanguage(cfg DepsCfg, onCreated func(d *Deps) error) (*Deps, er
|
||||
// The resource cache is global so reuse.
|
||||
// TODO(bep) clean up these inits.
|
||||
resourceCache := d.ResourceSpec.ResourceCache
|
||||
d.ResourceSpec, err = resources.NewSpec(d.PathSpec, d.ResourceSpec.FileCaches, d.Log, cfg.OutputFormats, cfg.MediaTypes)
|
||||
d.ResourceSpec, err = resources.NewSpec(d.PathSpec, d.ResourceSpec.FileCaches, d.BuildState, d.Log, d.globalErrHandler, cfg.OutputFormats, cfg.MediaTypes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -330,7 +339,6 @@ func (d Deps) ForLanguage(cfg DepsCfg, onCreated func(d *Deps) error) (*Deps, er
|
||||
}
|
||||
|
||||
d.BuildStartListeners = &Listeners{}
|
||||
d.BuildFlags = BuildFlags{}
|
||||
|
||||
return &d, nil
|
||||
|
||||
@@ -375,13 +383,15 @@ type DepsCfg struct {
|
||||
Running bool
|
||||
}
|
||||
|
||||
// BuildFlags are flags that may be turned on during a build.
|
||||
type BuildFlags struct {
|
||||
HasLateTemplate atomic.Bool
|
||||
// BuildState are flags that may be turned on during a build.
|
||||
type BuildState struct {
|
||||
counter uint64
|
||||
}
|
||||
|
||||
func NewBuildFlags() BuildFlags {
|
||||
return BuildFlags{
|
||||
//HasLateTemplate: atomic.NewBool(false),
|
||||
}
|
||||
func (b *BuildState) Incr() int {
|
||||
return int(atomic.AddUint64(&b.counter, uint64(1)))
|
||||
}
|
||||
|
||||
func NewBuildState() BuildState {
|
||||
return BuildState{}
|
||||
}
|
||||
|
||||
Vendored
+8
-4
@@ -20,9 +20,13 @@ import (
|
||||
)
|
||||
|
||||
func TestBuildFlags(t *testing.T) {
|
||||
|
||||
c := qt.New(t)
|
||||
var bf BuildFlags
|
||||
c.Assert(bf.HasLateTemplate.Load(), qt.Equals, false)
|
||||
bf.HasLateTemplate.Store(true)
|
||||
c.Assert(bf.HasLateTemplate.Load(), qt.Equals, true)
|
||||
var bf BuildState
|
||||
bf.Incr()
|
||||
bf.Incr()
|
||||
bf.Incr()
|
||||
|
||||
c.Assert(bf.Incr(), qt.Equals, 4)
|
||||
|
||||
}
|
||||
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
### Asking Support Questions
|
||||
|
||||
We have an active [discussion forum](https://discourse.gohugo.io) where users and developers can ask questions. Please don't use the GitHub issue tracker to ask questions.
|
||||
@@ -1,12 +0,0 @@
|
||||
name: Compress images
|
||||
on: pull_request
|
||||
jobs:
|
||||
build:
|
||||
name: calibreapp/image-actions
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: calibreapp/image-actions
|
||||
uses: docker://calibreapp/github-image-actions
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
|
||||
.header-link:after {
|
||||
position: relative;
|
||||
left: 0.5em;
|
||||
@@ -2,7 +2,7 @@
|
||||
@import '_tachyons';
|
||||
|
||||
/* purgecss start ignore */
|
||||
@import '_anchorforid';
|
||||
@import '_header-link';
|
||||
@import '_animation';
|
||||
@import '_documentation-styles';
|
||||
|
||||
@@ -36,4 +36,4 @@
|
||||
.mw-90 {
|
||||
max-width:90%;
|
||||
}
|
||||
/* purgecss end ignore */
|
||||
/* purgecss end ignore */
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
require("typeface-muli")
|
||||
import styles from './css/main.css';
|
||||
import './js/anchorforid.js'
|
||||
import './js/clipboardjs.js'
|
||||
import './js/codeblocks.js'
|
||||
import './js/docsearch.js'
|
||||
@@ -11,6 +10,3 @@ import './js/scrolldir.js'
|
||||
import './js/smoothscroll.js'
|
||||
import './js/tabs.js'
|
||||
import './js/nojs.js'
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
/**
|
||||
* Anchor for ID BPNY
|
||||
**/
|
||||
var anchorForId = function (id) {
|
||||
var anchor = document.createElement("a");
|
||||
anchor.className = "header-link";
|
||||
anchor.href = "#" + id;
|
||||
anchor.innerHTML = ' <svg class="fill-current o-60 hover-accent-color-light" height="22px" viewBox="0 0 24 24" width="22px" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z" fill="none"/><path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"/></svg>';
|
||||
return anchor;
|
||||
};
|
||||
|
||||
var linkifyAnchors = function (level, containingElement) {
|
||||
var headers = containingElement.getElementsByTagName("h" + level);
|
||||
for (var h = 0; h < headers.length; h++) {
|
||||
var header = headers[h];
|
||||
|
||||
if (typeof header.id !== "undefined" && header.id !== "") {
|
||||
header.appendChild(anchorForId(header.id));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
document.onreadystatechange = function () {
|
||||
if (this.readyState === "complete") {
|
||||
var contentBlock = document.getElementsByClassName("prose")[0]
|
||||
if (!contentBlock) {
|
||||
return;
|
||||
}
|
||||
for (var level = 2; level <= 4; level++) {
|
||||
linkifyAnchors(level, contentBlock);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,4 @@
|
||||
import styles from './../css/main.css';
|
||||
import './anchorforid.js'
|
||||
import './clipboardjs.js'
|
||||
import './codeblocks.js'
|
||||
import './docsearch.js'
|
||||
@@ -10,9 +9,7 @@ import './smoothscroll.js'
|
||||
import './tabs.js'
|
||||
import './nojs.js'
|
||||
|
||||
|
||||
|
||||
// TO use Jquery, just call the modules you want
|
||||
// TO use jQuery, just call the modules you want
|
||||
// var $ = require('jquery/src/core');
|
||||
// require('jquery/src/core/init');
|
||||
// require('jquery/src/manipulation');
|
||||
|
||||
@@ -5280,3 +5280,4 @@ code, .code, pre code, .highlight pre {
|
||||
max-width:90%;
|
||||
}
|
||||
/* purgecss end ignore */
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
Generated
+5
@@ -0,0 +1,5 @@
|
||||
<h{{ .Level }} id="{{ .Anchor | safeURL }}">{{ .Text | safeHTML }}
|
||||
{{- if and (ge .Level 2) (le .Level 4) }}{{" " -}}
|
||||
<a class="header-link" href="#{{ .Anchor | safeURL }}"><svg class="fill-current o-60 hover-accent-color-light" height="22px" viewBox="0 0 24 24" width="22px" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z" fill="none"/><path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"/></svg></a>
|
||||
{{- end -}}
|
||||
</h{{ .Level }}>
|
||||
+58
-56
@@ -1,70 +1,72 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="no-js" lang="{{ with $.Site.LanguageCode }}{{ . }}{{ else }}en-us{{ end }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
{{/* https://www.zachleat.com/web/preload/ */}}
|
||||
<link rel="preload" href="{{ "fonts/muli-latin-200.woff2" | absURL }}" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="{{ "fonts/muli-latin-400.woff2" | absURL }}" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="{{ "fonts/muli-latin-800.woff2" | absURL }}" as="font" type="font/woff2" crossorigin>
|
||||
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
{{/* NOTE: the Site's title, and if there is a page title, that is set too */}}
|
||||
<title>{{ block "title" . }}{{ with .Title }}{{ . }} | {{ end }}{{ .Site.Title }}{{ end }}</title>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
{{/* https://www.zachleat.com/web/preload/ */}}
|
||||
<link rel="preload" href="{{ "fonts/muli-latin-200.woff2" | absURL }}" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="{{ "fonts/muli-latin-400.woff2" | absURL }}" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="{{ "fonts/muli-latin-800.woff2" | absURL }}" as="font" type="font/woff2" crossorigin>
|
||||
|
||||
<meta name="viewport" content="width=device-width,minimum-scale=1">
|
||||
{{ hugo.Generator }}
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
{{/* NOTE: the Site's title, and if there is a page title, that is set too */}}
|
||||
<title>{{ block "title" . }}{{ with .Title }}{{ . }} | {{ end }}{{ .Site.Title }}{{ end }}</title>
|
||||
|
||||
{{ if eq (getenv "HUGO_ENV") "production" | or (eq .Site.Params.env "production") }}
|
||||
<meta name="robots" content="index, follow">
|
||||
{{ else }}
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
{{ end }}
|
||||
<meta name="viewport" content="width=device-width,minimum-scale=1">
|
||||
{{ hugo.Generator }}
|
||||
|
||||
{{ range .AlternativeOutputFormats -}}
|
||||
<link rel="{{ .Rel }}" type="{{ .MediaType.Type }}" href="{{ .Permalink | safeURL }}">
|
||||
{{ end -}}
|
||||
|
||||
{{ $isDev := eq hugo.Environment "development" }}
|
||||
{{ $stylesheet := resources.Get "output/css/app.css" }}
|
||||
{{ if not $isDev }}
|
||||
{{ $stylesheet = $stylesheet | minify | fingerprint }}
|
||||
{{ end }}
|
||||
{{ with $stylesheet }}
|
||||
{{ if $isDev }}
|
||||
<link rel="stylesheet" href="{{ .RelPermalink }}" crossorigin="anonymous">
|
||||
{{ else }}
|
||||
<link rel="stylesheet" href="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" crossorigin="anonymous">
|
||||
{{ end }}
|
||||
{{ $.Scratch.Set "stylesheet" . }}
|
||||
{{end}}
|
||||
{{ if eq (getenv "HUGO_ENV") "production" | or (eq .Site.Params.env "production") }}
|
||||
<meta name="robots" content="index, follow">
|
||||
{{ else }}
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
{{ end }}
|
||||
|
||||
{{ range .AlternativeOutputFormats -}}
|
||||
<link rel="{{ .Rel }}" type="{{ .MediaType.Type }}" href="{{ .Permalink | safeURL }}">
|
||||
{{ end -}}
|
||||
|
||||
{{ $isDev := eq hugo.Environment "development" }}
|
||||
{{ $stylesheet := resources.Get "output/css/app.css" }}
|
||||
{{ if not $isDev }}
|
||||
{{ $stylesheet = $stylesheet | minify | fingerprint }}
|
||||
{{ end }}
|
||||
{{ with $stylesheet }}
|
||||
{{ if $isDev }}
|
||||
<link rel="stylesheet" href="{{ .RelPermalink }}" crossorigin="anonymous">
|
||||
{{ else }}
|
||||
<link rel="stylesheet" href="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" crossorigin="anonymous">
|
||||
{{ end }}
|
||||
{{ $.Scratch.Set "stylesheet" . }}
|
||||
{{end}}
|
||||
|
||||
|
||||
{{ block "scripts" . }}
|
||||
{{- partial "site-scripts.html" . -}}
|
||||
{{ end }}
|
||||
{{ partial "site-manifest.html" . }}
|
||||
{{- partial "head-additions.html" . -}}
|
||||
{{- template "_internal/opengraph.html" . -}}
|
||||
{{- template "_internal/schema.html" . -}}
|
||||
{{- template "_internal/twitter_cards.html" . -}}
|
||||
{{ block "scripts" . }}
|
||||
{{- partial "site-scripts.html" . -}}
|
||||
{{ end }}
|
||||
{{ partial "site-manifest.html" . }}
|
||||
{{- partial "head-additions.html" . -}}
|
||||
{{- template "_internal/opengraph.html" . -}}
|
||||
{{- template "_internal/schema.html" . -}}
|
||||
{{- template "_internal/twitter_cards.html" . -}}
|
||||
|
||||
{{ if eq (getenv "HUGO_ENV") "production" | or (eq .Site.Params.env "production") }}
|
||||
{{ partial "gtag" . }}
|
||||
{{ end }}
|
||||
{{ if eq (getenv "HUGO_ENV") "production" | or (eq .Site.Params.env "production") }}
|
||||
{{ partial "gtag" . }}
|
||||
{{ end }}
|
||||
|
||||
|
||||
|
||||
|
||||
</head>
|
||||
<body class="ma0 sans-serif bg-primary-color-light{{ with getenv "HUGO_ENV" }} {{ . }}{{ end }}">
|
||||
{{ block "nav" . }}{{ partial "site-nav.html" . }}{{ end }}
|
||||
{{ block "header" . }}{{ end }}
|
||||
<main role="main" class="content-with-sidebar min-vh-100 pb7 pb0-ns">
|
||||
{{ block "main" . }}{{ end }}
|
||||
</main>
|
||||
|
||||
{{ block "footer" . }}{{ partialCached "site-footer.html" . }}{{ end }}
|
||||
|
||||
</head>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
<body class="ma0 sans-serif bg-primary-color-light{{ with getenv "HUGO_ENV" }} {{ . }}{{ end }}">
|
||||
{{ block "nav" . }}{{ partial "site-nav.html" . }}{{ end }}
|
||||
{{ block "header" . }}{{ end }}
|
||||
<main role="main" class="content-with-sidebar min-vh-100 pb7 pb0-ns">
|
||||
{{ block "main" . }}{{ end }}
|
||||
</main>
|
||||
|
||||
{{ block "footer" . }}{{ partialCached "site-footer.html" . }}{{ end }}
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
+22
-22
@@ -1,31 +1,31 @@
|
||||
{{ define "main" }}
|
||||
<article class="w-100 ph4 pb5 pb6-ns pt1 pt5-ns">
|
||||
<div class="flex-l">
|
||||
<article class="w-100 ph4 pb5 pb6-ns pt1 pt5-ns">
|
||||
<div class="flex-l">
|
||||
|
||||
<div class="order-2 w-100 w-20-l ph5-m ph0-l mb4 sticky">
|
||||
<div class="order-2 w-100 w-20-l ph5-m ph0-l mb4 sticky">
|
||||
{{- partial "toc.html" . -}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="order-1 w-60-l mw7 ph0 ph5-ns mid-gray nested-copy-line-height no-underline nested-links nested-img nested-copy-seperator nested-blockquote mt0-ns" style="flex-grow:1;">
|
||||
<div class="documentation-copy center measure-wide-l">
|
||||
<div id="readout" class="fixed right-0 bottom-0">
|
||||
</div>
|
||||
{{ .Render "page" }}
|
||||
{{ partial "related.html" . }}
|
||||
<div class="order-1 w-60-l mw7 ph0 ph5-ns mid-gray nested-copy-line-height no-underline nested-links nested-img nested-copy-seperator nested-blockquote mt0-ns" style="flex-grow:1;">
|
||||
<div class="documentation-copy center measure-wide-l">
|
||||
<div id="readout" class="fixed right-0 bottom-0">
|
||||
</div>
|
||||
{{ .Render "page" }}
|
||||
{{ partial "related.html" . }}
|
||||
</div>
|
||||
<div class="order-0 w-20 dn db-l">
|
||||
{{ partial "nav-links-docs.html" . }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<div class="w-100 bg-light-gray">
|
||||
<div class="mw7 pa4 center nested-lh-copy lh-copy">
|
||||
{{ partial "docs/page-meta-data.html" . }}
|
||||
{{ partial "page-edit.html" . }}
|
||||
{{ partial "tags.html" . }}
|
||||
<div class="order-0 w-20 dn db-l">
|
||||
{{ partial "nav-links-docs.html" . }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{{ end }}
|
||||
</article>
|
||||
|
||||
<div class="w-100 bg-light-gray">
|
||||
<div class="mw7 pa4 center nested-lh-copy lh-copy">
|
||||
{{ partial "docs/page-meta-data.html" . }}
|
||||
{{ partial "page-edit.html" . }}
|
||||
{{ partial "tags.html" . }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
@@ -1,4 +1,3 @@
|
||||
{{/* {{ define "header" }}{{ partial "page-header" . }}{{ end }} */}}
|
||||
{{ define "main" }}
|
||||
{{ $section_to_display := .Pages }}
|
||||
<div class="w-100 ph4 pb5 pb6-ns pt1 pt3-ns">
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
{{/* TODO: think about adding these tags back in, but they need to be limited to news
|
||||
{{ define "header" }}{{ partial "page-header" . }}{{ end }} */}}
|
||||
{{ define "main" }}
|
||||
<div class="w-100 ph4 ph5-ns pb5 pb6-ns pt1 pt3-ns ">
|
||||
|
||||
|
||||
@@ -17,10 +17,9 @@ var trackOutboundLink = function(id, url) {
|
||||
gtag('event', 'click', {
|
||||
'event_category': 'outbound',
|
||||
'event_label': id,
|
||||
'transport_type': 'beacon',
|
||||
'event_callback': function(){document.location = url;}
|
||||
'transport_type': 'beacon'
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
Generated
+1
-1
@@ -13,7 +13,7 @@
|
||||
{{ $url := printf "%s?%s" . (querify "utm_source" "homepage" "utm_medium" "banner" "utm_campaign" "hugosponsor") | safeURL }}
|
||||
{{ if eq (getenv "HUGO_ENV") "production" | or (eq $.cx.Site.Params.env "production") }}
|
||||
{{ $gtagID := printf "Sponsor %s %s" $banner.name $gtag | title }}
|
||||
<a href="{{ $url }}" onclick="trackOutboundLink({{ printf "'%s', '%s'" $gtagID $url | safeJS }}); return false;" class="grow">
|
||||
<a href="{{ $url }}" onclick="trackOutboundLink({{ printf "'%s', '%s'" $gtagID $url | safeJS }});" class="grow">
|
||||
{{ else }}
|
||||
<a href="{{ $url }}" class="grow">
|
||||
{{ end }}
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@
|
||||
<p class="dib">The Hugo Gopher is based on an original work by Renée French.</p>
|
||||
</div>
|
||||
|
||||
<img src="/images/gopher-side_color.svg" class="absolute-l bottom-0 dn db-l h4 right-0 z-999"/>
|
||||
<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">
|
||||
{{- partial "nav-mobile.html" . -}}
|
||||
|
||||
+1
-1
@@ -95,7 +95,7 @@ Showcase: {{ .Title }}
|
||||
</a>
|
||||
</li>
|
||||
{{end}}
|
||||
{{if ge $number_of_entries 20}}
|
||||
{{if gt $number_of_entries 20}}
|
||||
<li class="mt3">
|
||||
<a href="/showcase/page/2/" class="link blue">
|
||||
See More »
|
||||
|
||||
@@ -1 +1 @@
|
||||
# github.com/gohugoio/gohugoioTheme v0.0.0-20191021162625-2e7250ca437d
|
||||
# github.com/gohugoio/gohugoioTheme v0.0.0-20200711112515-b0dfe471654f
|
||||
|
||||
@@ -58,41 +58,6 @@ pattern = "2006"
|
||||
[social]
|
||||
twitter = "GoHugoIO"
|
||||
|
||||
#CUSTOM PARAMS
|
||||
[params]
|
||||
description = "The world’s fastest framework for building websites"
|
||||
## Used for views in rendered HTML (i.e., rather than using the .Hugo variable)
|
||||
release = "0.55.0-DEV"
|
||||
## Setting this to true will add a "noindex" to *EVERY* page on the site
|
||||
removefromexternalsearch = false
|
||||
## Gh repo for site footer (include trailing slash)
|
||||
ghrepo = "https://github.com/gohugoio/hugoDocs/"
|
||||
## GH Repo for filing a new issue
|
||||
github_repo = "https://github.com/gohugoio/hugo/issues/new"
|
||||
### Edit content repo (set to automatically enter "edit" mode; this is good for "improve this page" links)
|
||||
ghdocsrepo = "https://github.com/gohugoio/hugoDocs/tree/master/docs"
|
||||
## Gitter URL
|
||||
gitter = "https://gitter.im/spf13/hugo"
|
||||
## Discuss Forum URL
|
||||
forum = "https://discourse.gohugo.io/"
|
||||
## Google Tag Manager
|
||||
gtmid = ""
|
||||
|
||||
# First one is picked as the Twitter card image if not set on page.
|
||||
images = ["images/gohugoio-card.png"]
|
||||
|
||||
flex_box_interior_classes = "flex-auto w-100 w-40-l mr3 mb3 bg-white ba b--moon-gray nested-copy-line-height"
|
||||
|
||||
#sidebar_direction = "sidebar_left"
|
||||
|
||||
# MARKDOWN
|
||||
## Configuration for BlackFriday markdown parser: https://github.com/russross/blackfriday
|
||||
[blackfriday]
|
||||
plainIDAnchors = true
|
||||
# See https://github.com/gohugoio/hugo/issues/2424
|
||||
hrefTargetBlank = false
|
||||
angledQuotes = false
|
||||
latexDashes = true
|
||||
|
||||
[imaging]
|
||||
# See https://github.com/disintegration/imaging
|
||||
|
||||
@@ -14,12 +14,6 @@ pluralizeListTitles = false
|
||||
# We do redirects via Netlify's _redirects file, generated by Hugo (see "outputs" below).
|
||||
disableAliases = true
|
||||
|
||||
[markup]
|
||||
[markup.highlight]
|
||||
style = "trac"
|
||||
lineNumbersInTable = true
|
||||
noClasses = false
|
||||
|
||||
[outputs]
|
||||
home = [ "HTML", "RSS", "REDIR", "HEADERS" ]
|
||||
section = [ "HTML", "RSS"]
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
defaultMarkdownHandler = "goldmark"
|
||||
|
||||
[highlight]
|
||||
style = "trac"
|
||||
lineNumbersInTable = true
|
||||
noClasses = false
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
|
||||
description = "The world’s fastest framework for building websites"
|
||||
## Used for views in rendered HTML (i.e., rather than using the .Hugo variable)
|
||||
release = "0.52"
|
||||
## Setting this to true will add a "noindex" to *EVERY* page on the site..
|
||||
removefromexternalsearch = false
|
||||
## Gh repo for site footer (include trailing slash)
|
||||
|
||||
@@ -28,7 +28,7 @@ This has many benefits. The most noticeable is performance. HTTP servers are *ve
|
||||
## More on Static Site Generators
|
||||
|
||||
* ["An Introduction to Static Site Generators", David Walsh][]
|
||||
* ["Hugo vs. Wordpress page load speed comparison: Hugo leaves WordPress in its dust", GettingThingsTech][hugovwordpress]
|
||||
* ["Hugo vs. WordPress page load speed comparison: Hugo leaves WordPress in its dust", GettingThingsTech][hugovwordpress]
|
||||
* ["Static Site Generators", O'Reilly][]
|
||||
* [StaticGen: Top Open-Source Static Site Generators (GitHub Stars)][]
|
||||
* ["Top 10 Static Website Generators", Netlify blog][]
|
||||
|
||||
@@ -44,20 +44,18 @@ toc: true
|
||||
* Automatic and user defined [content summaries][]
|
||||
* [Shortcodes][] to enable rich content inside of Markdown
|
||||
* ["Minutes to Read"][pagevars] functionality
|
||||
* ["Wordcount"][pagevars] functionality
|
||||
* ["WordCount"][pagevars] functionality
|
||||
|
||||
## Additional Features
|
||||
|
||||
* Integrated [Disqus][] comment support
|
||||
* Integrated [Google Analytics][] support
|
||||
* Automatic [RSS][] creation
|
||||
* Support for [Go][], [Amber], and [Ace][] HTML templates
|
||||
* Support for [Go][] HTML templates
|
||||
* [Syntax highlighting][] powered by [Chroma][]
|
||||
|
||||
|
||||
[Ace]: /templates/alternatives/
|
||||
[aliases]: /content-management/urls/#aliases
|
||||
[Amber]: https://github.com/eknkc/amber
|
||||
[Chroma]: https://github.com/alecthomas/chroma
|
||||
[content summaries]: /content-management/summaries/
|
||||
[content types]: /content-management/types/
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
---
|
||||
title: Hugo and the General Data Protection Regulation (GDPR)
|
||||
linktitle: Hugo and GDPR
|
||||
@@ -53,6 +51,7 @@ enableDNT = false
|
||||
simple = false
|
||||
[privacy.vimeo]
|
||||
disable = false
|
||||
enableDNT = false
|
||||
simple = false
|
||||
[privacy.youtube]
|
||||
disable = false
|
||||
@@ -98,7 +97,7 @@ useSessionStorage
|
||||
simple
|
||||
: If simple mode is enabled, a static and no-JS version of the Instagram image card will be built. Note that this only supports image cards and the image itself will be fetched from Instagram's servers.
|
||||
|
||||
**Note:** If you use the _simple mode_ for Instagram and a site styled with Bootstrap 4, you may want to disable the inlines styles provided by Hugo:
|
||||
**Note:** If you use the _simple mode_ for Instagram and a site styled with Bootstrap 4, you may want to disable the inline styles provided by Hugo:
|
||||
|
||||
{{< code-toggle file="config">}}
|
||||
[services]
|
||||
@@ -130,6 +129,9 @@ privacyEnhanced
|
||||
|
||||
### Vimeo
|
||||
|
||||
enableDNT
|
||||
: Enabling this for the vimeo shortcode, the Vimeo player will be blocked from tracking any session data, including all cookies and stats.
|
||||
|
||||
simple
|
||||
: If simple mode is enabled, the video thumbnail is fetched from Vimeo's servers and it is overlayed with a play button. If the user clicks to play the video, it will open in a new tab directly on Vimeo's website.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Hugo's Security Model
|
||||
description: A summary of Hugo's security model.
|
||||
description: A summary of Hugo's security model.
|
||||
date: 2019-10-01
|
||||
layout: single
|
||||
keywords: ["Security", "Privacy"]
|
||||
@@ -19,14 +19,14 @@ toc: true
|
||||
|
||||
Hugo produces static output, so once built, the runtime is the browser (assuming the output is HTML) and any server (API) that you integrate with.
|
||||
|
||||
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/).
|
||||
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 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 simliar, 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.
|
||||
* 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.
|
||||
|
||||
Hugo will soon introduce a concept of _Content Source Plugins_ (AKA _Pages from Data_), but the above will still hold true.
|
||||
|
||||
|
||||
@@ -39,9 +39,7 @@ Hugo is for people who want to hand code their own website without worrying abou
|
||||
|
||||
Hugo is for people building a blog, a company site, a portfolio site, documentation, a single landing page, or a website with thousands of pages.
|
||||
|
||||
|
||||
|
||||
[@spf13]: https://twitter.com/@spf13
|
||||
[@spf13]: https://twitter.com/spf13
|
||||
[Aerobatic]: https://www.aerobatic.com/
|
||||
[Amazon S3]: https://aws.amazon.com/s3/
|
||||
[Azure]: https://docs.microsoft.com/en-us/azure/storage/blobs/storage-blob-static-website
|
||||
@@ -51,7 +49,7 @@ Hugo is for people building a blog, a company site, a portfolio site, documentat
|
||||
[GitHub Pages]: https://pages.github.com/
|
||||
[GitLab Pages]: https://about.gitlab.com/features/pages/
|
||||
[Go language]: https://golang.org/
|
||||
[GoDaddy]: https://www.godaddy.com/ "Godaddy.com Hosting"
|
||||
[GoDaddy]: https://www.godaddy.com/ "GoDaddy.com Hosting"
|
||||
[Google Cloud Storage]: https://cloud.google.com/storage/
|
||||
[Heroku]: https://www.heroku.com/
|
||||
[Jekyll]: https://jekyllrb.com/
|
||||
@@ -61,5 +59,5 @@ Hugo is for people building a blog, a company site, a portfolio site, documentat
|
||||
[Rackspace]: https://www.rackspace.com/cloud/files
|
||||
[Surge]: https://surge.sh
|
||||
[contributing to it]: https://github.com/gohugoio/hugo
|
||||
[rackspace]: https://www.rackspace.com/cloud/files
|
||||
[rackspace]: https://www.rackspace.com/openstack/public/files
|
||||
[static site generator]: /about/benefits/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
date: 2019-07-31
|
||||
date: 2020-09-13
|
||||
title: "hugo"
|
||||
slug: hugo
|
||||
url: /commands/hugo/
|
||||
@@ -15,7 +15,7 @@ hugo is the main command, used to build your Hugo site.
|
||||
Hugo is a Fast and Flexible Static Site Generator
|
||||
built with love by spf13 and friends in Go.
|
||||
|
||||
Complete documentation is available at https://gohugo.io/.
|
||||
Complete documentation is available at http://gohugo.io/.
|
||||
|
||||
```
|
||||
hugo [flags]
|
||||
@@ -24,44 +24,46 @@ hugo [flags]
|
||||
### Options
|
||||
|
||||
```
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
-D, --buildDrafts include content marked as draft
|
||||
-E, --buildExpired include expired content
|
||||
-F, --buildFuture include content with publishdate in the future
|
||||
--cacheDir string filesystem path to cache directory. Defaults: $TMPDIR/hugo_cache/
|
||||
--cleanDestinationDir remove files from destination not found in static directories
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
--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
|
||||
-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
|
||||
-l, --layoutDir string filesystem path to layout directory
|
||||
--log enable Logging
|
||||
--logFile string log File path (if set, logging enabled automatically)
|
||||
--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.
|
||||
--quiet build in quiet mode
|
||||
--renderToMemory render to memory (only useful for benchmark testing)
|
||||
-s, --source string filesystem path to read files relative from
|
||||
--templateMetrics display metrics about template executions
|
||||
--templateMetricsHints calculate some improvement hints when combined with --templateMetrics
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
--themesDir string filesystem path to themes directory
|
||||
--trace file write trace to file (not useful in general)
|
||||
-v, --verbose verbose output
|
||||
--verboseLog verbose logging
|
||||
-w, --watch watch filesystem for changes and recreate as needed
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. http://spf13.com/
|
||||
-D, --buildDrafts include content marked as draft
|
||||
-E, --buildExpired include expired content
|
||||
-F, --buildFuture include content with publishdate in the future
|
||||
--cacheDir string filesystem path to cache directory. Defaults: $TMPDIR/hugo_cache/
|
||||
--cleanDestinationDir remove files from destination not found in static directories
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
--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
|
||||
-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
|
||||
--logFile string log File path (if set, logging enabled automatically)
|
||||
--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.
|
||||
--print-mem print memory usage to screen at intervals
|
||||
--quiet build in quiet mode
|
||||
--renderToMemory render to memory (only useful for benchmark testing)
|
||||
-s, --source string filesystem path to read files relative from
|
||||
--templateMetrics display metrics about template executions
|
||||
--templateMetricsHints calculate some improvement hints when combined with --templateMetrics
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
--themesDir string filesystem path to themes directory
|
||||
--trace file write trace to file (not useful in general)
|
||||
-v, --verbose verbose output
|
||||
--verboseLog verbose logging
|
||||
-w, --watch watch filesystem for changes and recreate as needed
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
@@ -79,4 +81,4 @@ hugo [flags]
|
||||
* [hugo server](/commands/hugo_server/) - A high performance webserver
|
||||
* [hugo version](/commands/hugo_version/) - Print the version number of Hugo
|
||||
|
||||
###### Auto generated by spf13/cobra on 31-Jul-2019
|
||||
###### Auto generated by spf13/cobra on 13-Sep-2020
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
date: 2019-07-31
|
||||
date: 2020-09-13
|
||||
title: "hugo check"
|
||||
slug: hugo_check
|
||||
url: /commands/hugo_check/
|
||||
@@ -21,18 +21,19 @@ Contains some verification checks
|
||||
### 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
|
||||
--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
|
||||
--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
|
||||
@@ -40,4 +41,4 @@ Contains some verification checks
|
||||
* [hugo](/commands/hugo/) - hugo builds your site
|
||||
* [hugo check ulimit](/commands/hugo_check_ulimit/) - Check system ulimit settings
|
||||
|
||||
###### Auto generated by spf13/cobra on 31-Jul-2019
|
||||
###### Auto generated by spf13/cobra on 13-Sep-2020
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user