Compare commits
77 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9f9695cf7b | |||
| 0831d8ccaf | |||
| 1d4ff024ca | |||
| 521e63ac7c | |||
| 47d38628ec | |||
| 0024dcfe3e | |||
| beec1fc98e | |||
| 306573def0 | |||
| 80c8f3b81a | |||
| 6b6dcb44a0 | |||
| d96f2a460f | |||
| ac53035030 | |||
| b874a1ba7a | |||
| 4108705934 | |||
| 9c1e82085e | |||
| 2b73e89d6d | |||
| febf0aec8b | |||
| a4fad5be6b | |||
| 6084f0433c | |||
| cbb7214b6c | |||
| e6136b36f3 | |||
| 659917a002 | |||
| 9d194ab904 | |||
| a305609e18 | |||
| 179de5f5bc | |||
| 5dc1f95b63 | |||
| 6bc892fc24 | |||
| 5f06dbf779 | |||
| 0850e97984 | |||
| a3b4b10f65 | |||
| e3df6478f0 | |||
| 626afc9825 | |||
| e1027c5846 | |||
| e1a052ecb8 | |||
| bfc3488b8e | |||
| ce84b524f4 | |||
| 3cea2932e1 | |||
| 0efd374805 | |||
| 98293eaa15 | |||
| 2b8d907ab7 | |||
| 43338c3a99 | |||
| dea71670c0 | |||
| a5d0a57e6b | |||
| f465571b33 | |||
| f8212d2000 | |||
| 78e8a744b3 | |||
| c790029e1d | |||
| 554553c09c | |||
| de37455ec7 | |||
| 282f6035e7 | |||
| 360fa12213 | |||
| 02aa320030 | |||
| d2640fbc19 | |||
| 1637d12e37 | |||
| 3a7706b069 | |||
| 2955f93fc6 | |||
| 1f0c4e1fb3 | |||
| 91ab455d84 | |||
| ca1e46efb9 | |||
| fd71fa89bd | |||
| b5a3aa7082 | |||
| 3d5928889a | |||
| dc7bc7b4d2 | |||
| 42ed602580 | |||
| 6a2968fd5c | |||
| 23d5fc82ee | |||
| 8531ec7ca3 | |||
| 9f27091e10 | |||
| 187621ae24 | |||
| 4172a835e5 | |||
| fc97388968 | |||
| d67e843c12 | |||
| 6e33c557b0 | |||
| 128f14efad | |||
| 34ee27a78b | |||
| 0f1fc01ef2 | |||
| f32ccd018f |
@@ -0,0 +1,8 @@
|
||||
*.md
|
||||
*.log
|
||||
*.txt
|
||||
.git
|
||||
.github
|
||||
.circleci
|
||||
docs
|
||||
examples
|
||||
@@ -0,0 +1,8 @@
|
||||
# Text files have auto line endings
|
||||
* text=auto
|
||||
|
||||
# Go source files always have LF line endings
|
||||
*.go text eol=lf
|
||||
|
||||
# SVG files should not be modified
|
||||
*.svg -text
|
||||
@@ -15,5 +15,7 @@ vendor/*/
|
||||
*.debug
|
||||
coverage*.out
|
||||
|
||||
dock.sh
|
||||
|
||||
GoBuilds
|
||||
dist
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
language: go
|
||||
sudo: false
|
||||
dist: trusty
|
||||
env:
|
||||
HUGO_BUILD_TAGS="extended"
|
||||
git:
|
||||
depth: false
|
||||
go:
|
||||
- 1.9.5
|
||||
- "1.10.1"
|
||||
- 1.9.7
|
||||
- "1.10.3"
|
||||
- tip
|
||||
os:
|
||||
- linux
|
||||
@@ -18,8 +20,9 @@ install:
|
||||
- go get github.com/magefile/mage
|
||||
- mage -v vendor
|
||||
script:
|
||||
- mage -v hugoRace
|
||||
- mage -v test
|
||||
- mage -v check
|
||||
- mage -v hugo
|
||||
- ./hugo -s docs/
|
||||
- ./hugo --renderToMemory -s docs/
|
||||
before_install:
|
||||
|
||||
@@ -192,6 +192,12 @@ To list all available commands along with descriptions:
|
||||
mage -l
|
||||
```
|
||||
|
||||
**Note:** From Hugo 0.43 we have added a build tag, `extended` that adds **SCSS support**. This needs a C compiler installed to build. You can enable this when building by:
|
||||
|
||||
```bash
|
||||
HUGO_BUILD_TAGS=extended mage install
|
||||
````
|
||||
|
||||
### Updating the Hugo Sources
|
||||
|
||||
If you want to stay in sync with the Hugo repository, you can easily pull down
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
FROM golang:1.9.0-alpine3.6 AS build
|
||||
# GitHub: https://github.com/gohugoio
|
||||
# Twitter: https://twitter.com/gohugoio
|
||||
# Website: https://gohugo.io/
|
||||
|
||||
RUN apk add --no-cache --virtual git musl-dev
|
||||
RUN go get github.com/golang/dep/cmd/dep
|
||||
FROM golang:1.10.3-alpine3.7 AS build
|
||||
|
||||
ENV CGO_ENABLED=0
|
||||
ENV GOOS=linux
|
||||
|
||||
WORKDIR /go/src/github.com/gohugoio/hugo
|
||||
ADD . /go/src/github.com/gohugoio/hugo/
|
||||
RUN dep ensure
|
||||
RUN go install -ldflags '-s -w'
|
||||
RUN apk add --no-cache \
|
||||
git \
|
||||
musl-dev && \
|
||||
go get github.com/golang/dep/cmd/dep
|
||||
COPY . /go/src/github.com/gohugoio/hugo/
|
||||
RUN dep ensure -vendor-only && \
|
||||
go install -ldflags '-s -w'
|
||||
|
||||
FROM alpine:3.6
|
||||
RUN \
|
||||
adduser -h /site -s /sbin/nologin -u 1000 -D hugo && \
|
||||
apk add --no-cache \
|
||||
dumb-init
|
||||
COPY --from=build /go/bin/hugo /bin/hugo
|
||||
USER hugo
|
||||
# ---
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /go/bin/hugo /hugo
|
||||
WORKDIR /site
|
||||
VOLUME /site
|
||||
EXPOSE 1313
|
||||
|
||||
ENTRYPOINT ["/usr/bin/dumb-init", "--", "hugo"]
|
||||
ENTRYPOINT [ "/hugo" ]
|
||||
CMD [ "--help" ]
|
||||
|
||||
@@ -3,23 +3,46 @@
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:f6a10073544cc0bd1eb9fd9f8d9bf4644971910a0f8393d51b5b4d286e554849"
|
||||
name = "github.com/BurntSushi/locker"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "a6e239ea1c69bff1cfdb20c4b73dadf52f784b6a"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:af3600b068c8dd0e004122f154de7ece06b0b13376f79e976701aab4834c860a"
|
||||
name = "github.com/BurntSushi/toml"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "a368813c5e648fee92e5f6c30e3944ff9d5e8895"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:1080c443bebc98b1c25665b46b321dc02f8a4d384fe544379dcba1e651f59321"
|
||||
name = "github.com/PuerkitoBio/purell"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "0bcb03f4b4d0a9428594752bd2a3b9aa0a9d4bd4"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:331a419049c2be691e5ba1d24342fc77c7e767a80c666a18fd8a9f7b82419c1c"
|
||||
name = "github.com/PuerkitoBio/urlesc"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "de5bf2ad457846296e2031421a34e2568e304e35"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:60d5ffa86fb76749180841154b3714cde4771723b7e52329fb97ec916a6a68a6"
|
||||
name = "github.com/alecthomas/assert"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "405dbfeb8e38effee6e723317226e93fff912d06"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:2d78d7ffff1988e613e4946d6a6914190db0b38d533e392208797d2f557c92b6"
|
||||
name = "github.com/alecthomas/chroma"
|
||||
packages = [
|
||||
".",
|
||||
@@ -52,83 +75,135 @@
|
||||
"lexers/w",
|
||||
"lexers/x",
|
||||
"lexers/y",
|
||||
"styles"
|
||||
"styles",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "1b755a90bd109f170385cb3964f0abdfd3451145"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:ba0a389824c3c3d378f5f334370524b7d04b5a5ccedce7dd2da05ae8d3868609"
|
||||
name = "github.com/alecthomas/colour"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "60882d9e27213e8552dcff6328914fe4c2b44bc9"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:5723237c9114bb92b4571532d9580dd0ed885ec3eb371d58085c074895c04a51"
|
||||
name = "github.com/alecthomas/repr"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "f49988b46e025398b9f834f7c726afe001ec481f"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:05d0e4ae6b8d0273647964fe26762e3345d5d196aaeb858838e75336703451ba"
|
||||
name = "github.com/bep/debounce"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "844797fa1dd9ba969d71b62797ff19d1e49d4eac"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:88b2a2ccfbcf8aa697ec140e196c524f40bbdc8e6f6d03ff79354bc694cb677d"
|
||||
name = "github.com/bep/gitmap"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "012701e8669671499fc43e9792335a1dcbfe2afb"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:7337271448975bc9cf3deae77bb2d7f7fc937c60c59b606ffcd25bfc754dd2ec"
|
||||
name = "github.com/bep/go-tocss"
|
||||
packages = [
|
||||
"scss",
|
||||
"scss/libsass",
|
||||
"tocss",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "2abb118dc8688b6c7df44e12f4152c2bded9b19c"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e5ad472b763adca2591568384b8f07974d2da4a98b55ffb360e2cecc2a216bae"
|
||||
name = "github.com/chaseadamsio/goorgeous"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "dcf1ef873b8987bf12596fe6951c48347986eb2f"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:f30036d9c6eb2b9262edac69b5f6b59e25b25310ffee3035ab0e7c4b722a8552"
|
||||
name = "github.com/cpuguy83/go-md2man"
|
||||
packages = ["md2man"]
|
||||
pruneopts = ""
|
||||
revision = "a65d4d2de4d5f7c74868dfa9b202a3c8be315aaa"
|
||||
version = "v1.0.6"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:8f6c36ee73ded871996ada894e20dfecde56557d08b4d1849d5489076679971f"
|
||||
name = "github.com/danwakefield/fnmatch"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "cbb64ac3d964b81592e64f957ad53df015803288"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0a39ec8bf5629610a4bc7873a92039ee509246da3cef1a0ea60f1ed7e5f9cea5"
|
||||
name = "github.com/davecgh/go-spew"
|
||||
packages = ["spew"]
|
||||
pruneopts = ""
|
||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8dc164acd41f84c9f704362d061f90cf45014c3b7eb3fead126efb7d152e2efc"
|
||||
name = "github.com/disintegration/imaging"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "dd50a3ee9985ccd313a2f03c398fcaedc96dc707"
|
||||
version = "v1.2.4"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:536d2bdf1cdef7ec49e1cb4a8a083ae639159f0fd11a5829fe4d35ecb8cc3a1a"
|
||||
name = "github.com/dlclark/regexp2"
|
||||
packages = [
|
||||
".",
|
||||
"syntax"
|
||||
"syntax",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "487489b64fb796de2e55f4e8a4ad1e145f80e957"
|
||||
version = "v1.1.6"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:fbbae68d476d61afb78178dd4c5532dbfff154e2fea72d1312138bf35c613ec1"
|
||||
name = "github.com/eknkc/amber"
|
||||
packages = [
|
||||
".",
|
||||
"parser"
|
||||
"parser",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "cdade1c073850f4ffc70a829e31235ea6892853b"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e5c807ac3b60699ccec9263f6eec756251b17e78582eb149f90ac345d9e58327"
|
||||
name = "github.com/fortytw2/leaktest"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "a5ef70473c97b71626b9abeda80ee92ba2a7de9e"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:b2106f1668ea5efc1ecc480f7e922a093adb9563fd9ce58585292871f0d0f229"
|
||||
name = "github.com/fsnotify/fsnotify"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9"
|
||||
version = "v1.4.7"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:b7a7e17513aeee6492d93015c7bf29c86a0c1c91210ea56b21e36c1a40958cba"
|
||||
name = "github.com/gobwas/glob"
|
||||
packages = [
|
||||
".",
|
||||
@@ -138,31 +213,39 @@
|
||||
"syntax/ast",
|
||||
"syntax/lexer",
|
||||
"util/runes",
|
||||
"util/strings"
|
||||
"util/strings",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "5ccd90ef52e1e632236f7326478d4faa74f99438"
|
||||
version = "v0.2.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:fe1b4d4cbe48c0d55507c55f8663aa4185576cc58fa0c8be03bb8f19dfe17a9c"
|
||||
name = "github.com/gorilla/websocket"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:4423ee95d6ee30bb22f680445c58889bb5b91e1b955405bf34374a053784a8a2"
|
||||
name = "github.com/hashicorp/go-immutable-radix"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "7f3cd4390caab3250a57f30efdb2a65dd7649ecf"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:9c776d7d9c54b7ed89f119e449983c3f24c0023e75001d6092442412ebca6b94"
|
||||
name = "github.com/hashicorp/golang-lru"
|
||||
packages = ["simplelru"]
|
||||
pruneopts = ""
|
||||
revision = "0fb14efe8c47ae851c0034ed7a448854d3d34cf3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:ccfc44438ba7fc24effffbe9c7b75bbbb51e7a275e361e62a4dbffbc9e8e43d9"
|
||||
name = "github.com/hashicorp/hcl"
|
||||
packages = [
|
||||
".",
|
||||
@@ -174,195 +257,317 @@
|
||||
"hcl/token",
|
||||
"json/parser",
|
||||
"json/scanner",
|
||||
"json/token"
|
||||
"json/token",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "ef8a98b0bbce4a65b5aa4c368430a80ddc533168"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
|
||||
name = "github.com/inconshreveable/mousetrap"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
|
||||
version = "v1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3b82a4308adc7319d0c7efbad106e966cbbf70e47f16fb5f7aca131b01f6f9b1"
|
||||
name = "github.com/jdkato/prose"
|
||||
packages = [
|
||||
"internal/util",
|
||||
"transform"
|
||||
"transform",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "20d3663d4bc9dd10d75abcde9d92e04b4861c674"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:f72872791669f287262777a6a635c7aaebbdd9919cffacea802f8f9c1dc2e6f6"
|
||||
name = "github.com/kyokomi/emoji"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "7e06b236c489543f53868841f188a294e3383eab"
|
||||
version = "v1.5"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3ee69edc976a8e66debd179073163cbbef426faa7c0ef2d8ff8e3be159699d2e"
|
||||
name = "github.com/magefile/mage"
|
||||
packages = [
|
||||
"mg",
|
||||
"sh"
|
||||
"sh",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "2f974307b636f59c13b88704cf350a4772fef271"
|
||||
version = "v1.0.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:093cd7ebfdecf5692c66f0b0f7a1877d796c89d9940f5769439c53dc65264036"
|
||||
name = "github.com/magiconair/properties"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "c3beff4c2358b44d0493c7dda585e7db7ff28ae6"
|
||||
version = "v1.7.6"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:fd46423a2fe75d52968baf37471b99e6f05cea3ac29c756128ead26af34e1c35"
|
||||
name = "github.com/markbates/inflect"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "a12c3aec81a6a938bf584a4bac567afed9256586"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:78229b46ddb7434f881390029bd1af7661294af31f6802e0e1bedaad4ab0af3c"
|
||||
name = "github.com/mattn/go-isatty"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
|
||||
version = "v0.0.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:81e673df85e765593a863f67cba4544cf40e8919590f04d67664940786c2b61a"
|
||||
name = "github.com/mattn/go-runewidth"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "9e777a8366cce605130a531d2cd6363d07ad7317"
|
||||
version = "v0.0.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:6066f4c384723dd468c96adc13bfe4d977f182c2305e1a82c94bb50e1507d528"
|
||||
name = "github.com/miekg/mmark"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "fd2f6c1403b37925bd7fe13af05853b8ae58ee5f"
|
||||
version = "v1.3.6"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:0de0f377aeccd41384e883c59c6f184c9db01c96db33a2724a1eaadd60f92629"
|
||||
name = "github.com/mitchellh/hashstructure"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "2bca23e0e452137f789efbc8610126fd8b94f73b"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:59fa50d593e5673a0dfffa1852b66fd700c05b35e368680b4b89a68fdb2c1379"
|
||||
name = "github.com/mitchellh/mapstructure"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "00c29f56e2386353d58c599509e8dc3801b0d716"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:08b8db6381176e5472815f67f29b2b8b21d5bf3adaba4de3e5416faaf26f3689"
|
||||
name = "github.com/muesli/smartcrop"
|
||||
packages = [
|
||||
".",
|
||||
"options"
|
||||
"options",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "f6ebaa786a12a0fdb2d7c6dee72808e68c296464"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:34afc91b88b56c71639e275ff3ea8179a8f094ddc86df96e26074000cade42fe"
|
||||
name = "github.com/nicksnyder/go-i18n"
|
||||
packages = [
|
||||
"i18n/bundle",
|
||||
"i18n/language",
|
||||
"i18n/translation"
|
||||
"i18n/translation",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "0dc1626d56435e9d605a29875701721c54bc9bbd"
|
||||
version = "v1.10.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:2656200f82783893859c77903afc794d97c1f5054e2b57f7021e33a6047e7b1e"
|
||||
name = "github.com/olekukonko/tablewriter"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "b8a9be070da40449e501c3c4730a889e42d87a9e"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:fef37519c971ab3c464318a0c9da6a7fb9c21439aa587e61bf10af651e6119b9"
|
||||
name = "github.com/pelletier/go-toml"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "acdc4509485b587f5e675510c4f2c63e90ff68a8"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:256484dbbcd271f9ecebc6795b2df8cad4c458dd0f5fd82a8c2fa0c29f233411"
|
||||
name = "github.com/pmezard/go-difflib"
|
||||
packages = ["difflib"]
|
||||
pruneopts = ""
|
||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:10bc894f31df5ca3366a50122ee2ade6b56f3f5531c523a8a551748a0663aa1b"
|
||||
name = "github.com/russross/blackfriday"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "11635eb403ff09dbc3a6b5a007ab5ab09151c229"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:afa9b7bb3d9e633ef32e46ab4b6a87b5376271e3d86b09a379029bbb10e26fa0"
|
||||
name = "github.com/sanity-io/litter"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "ae543b7ba8fd6af63e4976198f146e1348ae53c1"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:1ebe873a8dc99e3316c30fea2e211038c4d1fcb605eee17e00a5e91b8817925e"
|
||||
name = "github.com/sergi/go-diff"
|
||||
packages = ["diffmatchpatch"]
|
||||
pruneopts = ""
|
||||
revision = "1744e2970ca51c86172c8190fadad617561ed6e7"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:606fa779c7a80ab6a9ec5836cd12759f86cd6e1d82d5d60cf7ec5133e78c6cf8"
|
||||
name = "github.com/shurcooL/sanitized_anchor_name"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "86672fcb3f950f35f2e675df2240550f2a50762f"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3d6c88f060fdaac3291f3d18f58eb542fe3a2b330609b269962d26d0c99104be"
|
||||
name = "github.com/spf13/afero"
|
||||
packages = [
|
||||
".",
|
||||
"mem"
|
||||
"mem",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "787d034dfe70e44075ccc060d346146ef53270ad"
|
||||
version = "v1.1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d0b38ba6da419a6d4380700218eeec8623841d44a856bb57369c172fbf692ab4"
|
||||
name = "github.com/spf13/cast"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "8965335b8c7107321228e3e3702cab9832751bac"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:9e14345bfb9aa30952b41ffceaa2acd559933cfe3d17bd87b7f6e54b6c618492"
|
||||
name = "github.com/spf13/cobra"
|
||||
packages = [
|
||||
".",
|
||||
"doc"
|
||||
"doc",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "a1f051bc3eba734da4772d60e2d677f47cf93ef4"
|
||||
version = "v0.0.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:7c00f445c89f468abd5ab86546ba1fa11bac66e63325ade8ca05aa7220791fc7"
|
||||
name = "github.com/spf13/fsync"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "12a01e648f05a938100a26858d2d59a120307a18"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:104517520aab91164020ab6524a5d6b7cafc641b2e42ac6236f6ac1deac4f66a"
|
||||
name = "github.com/spf13/jwalterweatherman"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "7c0cea34c8ece3fbeb2b27ab9b59511d360fb394"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:5082c0d57a199dabc0868010784039249af0932fad2990b51ad9404c9a6b6e5d"
|
||||
name = "github.com/spf13/nitro"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "24d7ef30a12da0bdc5e2eb370a79c659ddccf0e8"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:261bc565833ef4f02121450d74eb88d5ae4bd74bfe5d0e862cddb8550ec35000"
|
||||
name = "github.com/spf13/pflag"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:79af66c513727a0ed32fb4137ad7d0613b01e8e2f5ce9479724e26e2c9068011"
|
||||
name = "github.com/spf13/viper"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "b5e8006cbee93ec955a89ab31e0e3ce3204f3736"
|
||||
version = "v1.0.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a70d585d45f695f2e8e6782569bdf181419667a35e6035ceb086706b495aa21a"
|
||||
name = "github.com/stretchr/testify"
|
||||
packages = [
|
||||
"assert",
|
||||
"require"
|
||||
"require",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71"
|
||||
version = "v1.2.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:54b1c3f97e0d74b571055cc17af4a978b6160e6ccfda73608aba048e404319c1"
|
||||
name = "github.com/tdewolff/minify"
|
||||
packages = [
|
||||
".",
|
||||
"css",
|
||||
"html",
|
||||
"js",
|
||||
"json",
|
||||
"svg",
|
||||
"xml",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "8d72a4127ae33b755e95bffede9b92e396267ce2"
|
||||
version = "v2.3.5"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:33d327ae34260328b00ed15f89961446d805797a96de8643a983020825bb51ae"
|
||||
name = "github.com/tdewolff/parse"
|
||||
packages = [
|
||||
".",
|
||||
"buffer",
|
||||
"css",
|
||||
"html",
|
||||
"js",
|
||||
"json",
|
||||
"strconv",
|
||||
"svg",
|
||||
"xml",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "d739d6fccb0971177e06352fea02d3552625efb1"
|
||||
version = "v2.3.3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:12e69a2969bdcdf2928607e51966a868482c2a450f1c97c5dde004257c8369ff"
|
||||
name = "github.com/wellington/go-libsass"
|
||||
packages = ["libs"]
|
||||
pruneopts = ""
|
||||
revision = "615eaa47ef794d037c1906a0eb7bf85375a5decf"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:b1e5dc66cd8ad9712eb8fd4dfaecac6ae45cbcde94476c5b64d905c5d789eef7"
|
||||
name = "github.com/yosssi/ace"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "ea038f4770b6746c3f8f84f14fa60d9fe1205b56"
|
||||
version = "v0.0.5"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:4525c51a65e4974007814da8e2621343840d769d80b2b2ab79e6958342726587"
|
||||
name = "golang.org/x/image"
|
||||
packages = [
|
||||
"bmp",
|
||||
@@ -373,33 +578,41 @@
|
||||
"tiff/lzw",
|
||||
"vp8",
|
||||
"vp8l",
|
||||
"webp"
|
||||
"webp",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "f315e440302883054d0c2bd85486878cb4f8572c"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:8bb19e8d6e1321626d3f58ffee75699e86940c79d959b30fcf632883d7c4d303"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"context",
|
||||
"idna"
|
||||
"idna",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "61147c48b25b599e5b561d2e9c4f3e1ef489ca41"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:d84d0f563cc649de4c9a8272a0395f75b11952202d18d4d927e933cc91493062"
|
||||
name = "golang.org/x/sync"
|
||||
packages = ["errgroup"]
|
||||
pruneopts = ""
|
||||
revision = "1d60e4601c6fd243af51cc01ddf169918a5407ca"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:137a530894d869c2fb09e135e6bb7a5d701347ba273471eaa2483053f8fbeb35"
|
||||
name = "golang.org/x/sys"
|
||||
packages = ["unix"]
|
||||
pruneopts = ""
|
||||
revision = "3b87a42e500a6dc65dae1a55d0b641295971163e"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:4cf01dae652ef4428cf9d354bdec79467e436ecfa92d943b1ada8184e1a938b6"
|
||||
name = "golang.org/x/text"
|
||||
packages = [
|
||||
"collate",
|
||||
@@ -418,19 +631,84 @@
|
||||
"unicode/cldr",
|
||||
"unicode/norm",
|
||||
"unicode/rangetable",
|
||||
"width"
|
||||
"width",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "2cb43934f0eece38629746959acc633cba083fe4"
|
||||
|
||||
[[projects]]
|
||||
branch = "v2"
|
||||
digest = "1:f0620375dd1f6251d9973b5f2596228cc8042e887cd7f827e4220bc1ce8c30e2"
|
||||
name = "gopkg.in/yaml.v2"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
|
||||
version = "v2.2.1"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "78b19539f7321429f217fc482de9e7cb4e2edd9b054ba8ec36b1e62bc4281b4f"
|
||||
input-imports = [
|
||||
"github.com/BurntSushi/locker",
|
||||
"github.com/BurntSushi/toml",
|
||||
"github.com/PuerkitoBio/purell",
|
||||
"github.com/alecthomas/assert",
|
||||
"github.com/alecthomas/chroma",
|
||||
"github.com/alecthomas/chroma/formatters",
|
||||
"github.com/alecthomas/chroma/formatters/html",
|
||||
"github.com/alecthomas/chroma/lexers",
|
||||
"github.com/alecthomas/chroma/styles",
|
||||
"github.com/bep/debounce",
|
||||
"github.com/bep/gitmap",
|
||||
"github.com/bep/go-tocss/scss",
|
||||
"github.com/bep/go-tocss/scss/libsass",
|
||||
"github.com/bep/go-tocss/tocss",
|
||||
"github.com/chaseadamsio/goorgeous",
|
||||
"github.com/disintegration/imaging",
|
||||
"github.com/eknkc/amber",
|
||||
"github.com/fortytw2/leaktest",
|
||||
"github.com/fsnotify/fsnotify",
|
||||
"github.com/gobwas/glob",
|
||||
"github.com/gorilla/websocket",
|
||||
"github.com/hashicorp/go-immutable-radix",
|
||||
"github.com/jdkato/prose/transform",
|
||||
"github.com/kyokomi/emoji",
|
||||
"github.com/magefile/mage/mg",
|
||||
"github.com/magefile/mage/sh",
|
||||
"github.com/markbates/inflect",
|
||||
"github.com/miekg/mmark",
|
||||
"github.com/mitchellh/hashstructure",
|
||||
"github.com/mitchellh/mapstructure",
|
||||
"github.com/muesli/smartcrop",
|
||||
"github.com/nicksnyder/go-i18n/i18n/bundle",
|
||||
"github.com/nicksnyder/go-i18n/i18n/language",
|
||||
"github.com/olekukonko/tablewriter",
|
||||
"github.com/russross/blackfriday",
|
||||
"github.com/sanity-io/litter",
|
||||
"github.com/spf13/afero",
|
||||
"github.com/spf13/cast",
|
||||
"github.com/spf13/cobra",
|
||||
"github.com/spf13/cobra/doc",
|
||||
"github.com/spf13/fsync",
|
||||
"github.com/spf13/jwalterweatherman",
|
||||
"github.com/spf13/nitro",
|
||||
"github.com/spf13/pflag",
|
||||
"github.com/spf13/viper",
|
||||
"github.com/stretchr/testify/assert",
|
||||
"github.com/stretchr/testify/require",
|
||||
"github.com/tdewolff/minify",
|
||||
"github.com/tdewolff/minify/css",
|
||||
"github.com/tdewolff/minify/html",
|
||||
"github.com/tdewolff/minify/js",
|
||||
"github.com/tdewolff/minify/json",
|
||||
"github.com/tdewolff/minify/svg",
|
||||
"github.com/tdewolff/minify/xml",
|
||||
"github.com/yosssi/ace",
|
||||
"golang.org/x/image/webp",
|
||||
"golang.org/x/net/context",
|
||||
"golang.org/x/sync/errgroup",
|
||||
"golang.org/x/text/transform",
|
||||
"golang.org/x/text/unicode/norm",
|
||||
"gopkg.in/yaml.v2",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
||||
@@ -16,6 +16,14 @@
|
||||
branch = "master"
|
||||
name = "github.com/bep/gitmap"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/bep/go-tocss"
|
||||
|
||||
[[override]]
|
||||
branch = "master"
|
||||
name = "github.com/wellington/go-libsass"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/chaseadamsio/goorgeous"
|
||||
version = "^1.1.0"
|
||||
@@ -149,3 +157,15 @@
|
||||
[[constraint]]
|
||||
name = "github.com/bep/debounce"
|
||||
version = "^1.1.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/tdewolff/minify"
|
||||
version = "^2.3.5"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/BurntSushi/locker"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/mitchellh/hashstructure"
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
image: Visual Studio 2015
|
||||
|
||||
init:
|
||||
- set PATH=%PATH%;C:\MinGW\bin;%GOPATH%\bin
|
||||
- set PATH=%PATH%;C:\mingw-w64\x86_64-7.3.0-posix-seh-rt_v5-rev0\mingw64\bin;%GOPATH%\bin
|
||||
- go version
|
||||
- go env
|
||||
|
||||
environment:
|
||||
GOPATH: C:\GOPATH\
|
||||
HUGO_BUILD_TAGS: extended
|
||||
|
||||
# clones and cd's to path
|
||||
clone_folder: C:\GOPATH\src\github.com\gohugoio\hugo
|
||||
|
||||
install:
|
||||
- gem install asciidoctor
|
||||
# - gem install asciidoctor
|
||||
- pip install docutils
|
||||
- go get github.com/magefile/mage
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# Temp script used to test new builds.
|
||||
docker run --rm --mount type=bind,source="$(pwd)",target=/go/src/github.com/gohugoio/hugo -w /go/src/github.com/gohugoio/hugo -i -t bepsays/ci-goreleaser:latest /bin/bash
|
||||
@@ -16,6 +16,7 @@ package commands
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -37,23 +38,31 @@ import (
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
)
|
||||
|
||||
type commandeer struct {
|
||||
type commandeerHugoState struct {
|
||||
*deps.DepsCfg
|
||||
hugo *hugolib.HugoSites
|
||||
fsCreate sync.Once
|
||||
}
|
||||
|
||||
hugo *hugolib.HugoSites
|
||||
type commandeer struct {
|
||||
*commandeerHugoState
|
||||
|
||||
// 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.
|
||||
changeDetector *fileChangeDetector
|
||||
|
||||
// We need to reuse this on server rebuilds.
|
||||
destinationFs afero.Fs
|
||||
|
||||
h *hugoBuilderCommon
|
||||
ftch flagsToConfigHandler
|
||||
|
||||
visitedURLs *types.EvictingStringQueue
|
||||
|
||||
// We watch these for changes.
|
||||
configFiles []string
|
||||
|
||||
doWithCommandeer func(c *commandeer) error
|
||||
|
||||
// We can do this only once.
|
||||
fsCreate sync.Once
|
||||
// We watch these for changes.
|
||||
configFiles []string
|
||||
|
||||
// Used in cases where we get flooded with events in server mode.
|
||||
debounce func(f func())
|
||||
@@ -73,6 +82,7 @@ func (c *commandeer) Set(key string, value interface{}) {
|
||||
}
|
||||
|
||||
func (c *commandeer) initFs(fs *hugofs.Fs) error {
|
||||
c.destinationFs = fs.Destination
|
||||
c.DepsCfg.Fs = fs
|
||||
|
||||
return nil
|
||||
@@ -89,16 +99,79 @@ func newCommandeer(mustHaveConfigFile, running bool, h *hugoBuilderCommon, f fla
|
||||
}
|
||||
|
||||
c := &commandeer{
|
||||
h: h,
|
||||
ftch: f,
|
||||
doWithCommandeer: doWithCommandeer,
|
||||
visitedURLs: types.NewEvictingStringQueue(10),
|
||||
debounce: rebuildDebouncer,
|
||||
h: h,
|
||||
ftch: f,
|
||||
commandeerHugoState: &commandeerHugoState{},
|
||||
doWithCommandeer: doWithCommandeer,
|
||||
visitedURLs: types.NewEvictingStringQueue(10),
|
||||
debounce: rebuildDebouncer,
|
||||
}
|
||||
|
||||
return c, c.loadConfig(mustHaveConfigFile, running)
|
||||
}
|
||||
|
||||
type fileChangeDetector struct {
|
||||
sync.Mutex
|
||||
current map[string]string
|
||||
prev map[string]string
|
||||
|
||||
irrelevantRe *regexp.Regexp
|
||||
}
|
||||
|
||||
func (f *fileChangeDetector) OnFileClose(name, md5sum string) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.current[name] = md5sum
|
||||
}
|
||||
|
||||
func (f *fileChangeDetector) changed() []string {
|
||||
if f == nil {
|
||||
return nil
|
||||
}
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
var c []string
|
||||
for k, v := range f.current {
|
||||
vv, found := f.prev[k]
|
||||
if !found || v != vv {
|
||||
c = append(c, k)
|
||||
}
|
||||
}
|
||||
|
||||
return f.filterIrrelevant(c)
|
||||
}
|
||||
|
||||
func (f *fileChangeDetector) filterIrrelevant(in []string) []string {
|
||||
var filtered []string
|
||||
for _, v := range in {
|
||||
if !f.irrelevantRe.MatchString(v) {
|
||||
filtered = append(filtered, v)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func (f *fileChangeDetector) PrepareNew() {
|
||||
if f == nil {
|
||||
return
|
||||
}
|
||||
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
if f.current == nil {
|
||||
f.current = make(map[string]string)
|
||||
f.prev = make(map[string]string)
|
||||
return
|
||||
}
|
||||
|
||||
f.prev = make(map[string]string)
|
||||
for k, v := range f.current {
|
||||
f.prev[k] = v
|
||||
}
|
||||
f.current = make(map[string]string)
|
||||
}
|
||||
|
||||
func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
|
||||
|
||||
if c.DepsCfg == nil {
|
||||
@@ -188,11 +261,31 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
|
||||
c.fsCreate.Do(func() {
|
||||
fs := hugofs.NewFrom(sourceFs, config)
|
||||
|
||||
// Hugo writes the output to memory instead of the disk.
|
||||
if createMemFs {
|
||||
if c.destinationFs != nil {
|
||||
// Need to reuse the destination on server rebuilds.
|
||||
fs.Destination = c.destinationFs
|
||||
} else if createMemFs {
|
||||
// Hugo writes the output to memory instead of the disk.
|
||||
fs.Destination = new(afero.MemMapFs)
|
||||
}
|
||||
|
||||
doLiveReload := !c.h.buildWatch && !config.GetBool("disableLiveReload")
|
||||
fastRenderMode := doLiveReload && !config.GetBool("disableFastRender")
|
||||
|
||||
if fastRenderMode {
|
||||
// For now, fast render mode only. It should, however, be fast enough
|
||||
// for the full variant, too.
|
||||
changeDetector := &fileChangeDetector{
|
||||
// We use this detector to decide to do a Hot reload of a single path or not.
|
||||
// We need to filter out source maps and possibly some other to be able
|
||||
// to make that decision.
|
||||
irrelevantRe: regexp.MustCompile(`\.map$`),
|
||||
}
|
||||
changeDetector.PrepareNew()
|
||||
fs.Destination = hugofs.NewHashingFs(fs.Destination, changeDetector)
|
||||
c.changeDetector = changeDetector
|
||||
}
|
||||
|
||||
err = c.initFs(fs)
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
@@ -235,11 +235,6 @@ Single: {{ .Title }}
|
||||
|
||||
List: {{ .Title }}
|
||||
|
||||
`)
|
||||
|
||||
writeFile(t, filepath.Join(d, "static", "my.txt"), `
|
||||
MyMy
|
||||
|
||||
`)
|
||||
|
||||
return d, nil
|
||||
|
||||
@@ -277,7 +277,10 @@ func (c *commandeer) fullBuild() error {
|
||||
copyStaticFunc := func() error {
|
||||
cnt, err := c.copyStatic()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error copying static files: %s", err)
|
||||
if !os.IsNotExist(err) {
|
||||
return fmt.Errorf("Error copying static files: %s", err)
|
||||
}
|
||||
c.Logger.WARN.Println("No Static directory found")
|
||||
}
|
||||
langCount = cnt
|
||||
langCount = cnt
|
||||
@@ -471,6 +474,10 @@ func (c *commandeer) copyStaticTo(sourceFs *filesystems.SourceFilesystem) (uint6
|
||||
return numFiles, err
|
||||
}
|
||||
|
||||
func (c *commandeer) firstPathSpec() *helpers.PathSpec {
|
||||
return c.hugo.Sites[0].PathSpec
|
||||
}
|
||||
|
||||
func (c *commandeer) timeTrack(start time.Time, name string) {
|
||||
if c.h.quiet {
|
||||
return
|
||||
@@ -549,8 +556,8 @@ func (c *commandeer) getDirList() ([]string, error) {
|
||||
// SymbolicWalk will log anny ERRORs
|
||||
// Also note that the Dirnames fetched below will contain any relevant theme
|
||||
// directories.
|
||||
for _, contentDir := range c.hugo.PathSpec.BaseFs.AbsContentDirs {
|
||||
_ = helpers.SymbolicWalk(c.Fs.Source, contentDir.Value, symLinkWalker)
|
||||
for _, contentDir := range c.hugo.PathSpec.BaseFs.Content.Dirnames {
|
||||
_ = helpers.SymbolicWalk(c.Fs.Source, contentDir, symLinkWalker)
|
||||
}
|
||||
|
||||
for _, staticDir := range c.hugo.PathSpec.BaseFs.Data.Dirnames {
|
||||
@@ -571,6 +578,10 @@ func (c *commandeer) getDirList() ([]string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
for _, assetDir := range c.hugo.PathSpec.BaseFs.Assets.Dirnames {
|
||||
_ = helpers.SymbolicWalk(c.Fs.Source, assetDir, regularWalker)
|
||||
}
|
||||
|
||||
if len(nested) > 0 {
|
||||
for {
|
||||
|
||||
@@ -593,14 +604,6 @@ func (c *commandeer) getDirList() ([]string, error) {
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (c *commandeer) recreateAndBuildSites(watching bool) (err error) {
|
||||
defer c.timeTrack(time.Now(), "Total")
|
||||
if !c.h.quiet {
|
||||
c.Logger.FEEDBACK.Println("Started building sites ...")
|
||||
}
|
||||
return c.hugo.Build(hugolib.BuildCfg{CreateSitesFromConfig: true})
|
||||
}
|
||||
|
||||
func (c *commandeer) resetAndBuildSites() (err error) {
|
||||
if !c.h.quiet {
|
||||
c.Logger.FEEDBACK.Println("Started building sites ...")
|
||||
@@ -634,9 +637,10 @@ func (c *commandeer) rebuildSites(events []fsnotify.Event) error {
|
||||
}
|
||||
|
||||
func (c *commandeer) fullRebuild() {
|
||||
c.commandeerHugoState = &commandeerHugoState{}
|
||||
if err := c.loadConfig(true, true); err != nil {
|
||||
jww.ERROR.Println("Failed to reload config:", err)
|
||||
} else if err := c.recreateAndBuildSites(true); err != nil {
|
||||
} else if err := c.buildSites(); err != nil {
|
||||
jww.ERROR.Println(err)
|
||||
} else if !c.h.buildWatch && !c.Cfg.GetBool("disableLiveReload") {
|
||||
livereload.ForceRefresh()
|
||||
@@ -822,13 +826,11 @@ func (c *commandeer) newWatcher(dirList ...string) (*watcher.Batcher, error) {
|
||||
// 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) > 0 {
|
||||
for _, ev := range staticEvents {
|
||||
|
||||
path := c.hugo.BaseFs.SourceFilesystems.MakeStaticPathRelative(ev.Name)
|
||||
livereload.RefreshPath(path)
|
||||
}
|
||||
|
||||
if 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()
|
||||
}
|
||||
@@ -836,34 +838,54 @@ func (c *commandeer) newWatcher(dirList ...string) (*watcher.Batcher, error) {
|
||||
}
|
||||
|
||||
if len(dynamicEvents) > 0 {
|
||||
partitionedEvents := partitionDynamicEvents(
|
||||
c.firstPathSpec().BaseFs.SourceFilesystems,
|
||||
dynamicEvents)
|
||||
|
||||
doLiveReload := !c.h.buildWatch && !c.Cfg.GetBool("disableLiveReload")
|
||||
onePageName := pickOneWriteOrCreatePath(dynamicEvents)
|
||||
onePageName := pickOneWriteOrCreatePath(partitionedEvents.ContentEvents)
|
||||
|
||||
c.Logger.FEEDBACK.Println("\nChange detected, rebuilding site")
|
||||
const layout = "2006-01-02 15:04:05.000 -0700"
|
||||
c.Logger.FEEDBACK.Println(time.Now().Format(layout))
|
||||
|
||||
c.changeDetector.PrepareNew()
|
||||
if err := c.rebuildSites(dynamicEvents); err != nil {
|
||||
c.Logger.ERROR.Println("Failed to rebuild site:", err)
|
||||
}
|
||||
|
||||
if doLiveReload {
|
||||
navigate := c.Cfg.GetBool("navigateToChanged")
|
||||
// We have fetched the same page above, but it may have
|
||||
// changed.
|
||||
var p *hugolib.Page
|
||||
|
||||
if navigate {
|
||||
if onePageName != "" {
|
||||
p = c.hugo.GetContentPage(onePageName)
|
||||
if len(partitionedEvents.ContentEvents) == 0 && len(partitionedEvents.AssetEvents) > 0 {
|
||||
changed := c.changeDetector.changed()
|
||||
if c.changeDetector != nil && len(changed) == 0 {
|
||||
// Nothing has changed.
|
||||
continue
|
||||
} else if len(changed) == 1 {
|
||||
pathToRefresh := c.firstPathSpec().RelURL(helpers.ToSlashTrimLeading(changed[0]), false)
|
||||
livereload.RefreshPath(pathToRefresh)
|
||||
} else {
|
||||
livereload.ForceRefresh()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if p != nil {
|
||||
livereload.NavigateToPathForPort(p.RelPermalink(), p.Site.ServerPort())
|
||||
} else {
|
||||
livereload.ForceRefresh()
|
||||
if len(partitionedEvents.ContentEvents) > 0 {
|
||||
|
||||
navigate := c.Cfg.GetBool("navigateToChanged")
|
||||
// We have fetched the same page above, but it may have
|
||||
// changed.
|
||||
var p *hugolib.Page
|
||||
|
||||
if navigate {
|
||||
if onePageName != "" {
|
||||
p = c.hugo.GetContentPage(onePageName)
|
||||
}
|
||||
}
|
||||
|
||||
if p != nil {
|
||||
livereload.NavigateToPathForPort(p.RelPermalink(), p.Site.ServerPort())
|
||||
} else {
|
||||
livereload.ForceRefresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -878,6 +900,26 @@ func (c *commandeer) newWatcher(dirList ...string) (*watcher.Batcher, error) {
|
||||
return watcher, nil
|
||||
}
|
||||
|
||||
// dynamicEvents contains events that is considered dynamic, as in "not static".
|
||||
// Both of these categories will trigger a new build, but the asset events
|
||||
// does not fit into the "navigate to changed" logic.
|
||||
type dynamicEvents struct {
|
||||
ContentEvents []fsnotify.Event
|
||||
AssetEvents []fsnotify.Event
|
||||
}
|
||||
|
||||
func partitionDynamicEvents(sourceFs *filesystems.SourceFilesystems, events []fsnotify.Event) (de dynamicEvents) {
|
||||
for _, e := range events {
|
||||
if sourceFs.IsAsset(e.Name) {
|
||||
de.AssetEvents = append(de.AssetEvents, e)
|
||||
} else {
|
||||
de.ContentEvents = append(de.ContentEvents, e)
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func pickOneWriteOrCreatePath(events []fsnotify.Event) string {
|
||||
name := ""
|
||||
|
||||
|
||||
@@ -80,7 +80,8 @@ func (n *newThemeCmd) newTheme(cmd *cobra.Command, args []string) error {
|
||||
touchFile(cfg.Fs.Source, createpath, "layouts", "_default", "list.html")
|
||||
touchFile(cfg.Fs.Source, createpath, "layouts", "_default", "single.html")
|
||||
|
||||
baseofDefault := []byte(`<html>
|
||||
baseofDefault := []byte(`<!DOCTYPE html>
|
||||
<html>
|
||||
{{- partial "head.html" . -}}
|
||||
<body>
|
||||
{{- partial "header.html" . -}}
|
||||
@@ -96,6 +97,7 @@ func (n *newThemeCmd) newTheme(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
touchFile(cfg.Fs.Source, createpath, "layouts", "partials", "head.html")
|
||||
touchFile(cfg.Fs.Source, createpath, "layouts", "partials", "header.html")
|
||||
touchFile(cfg.Fs.Source, createpath, "layouts", "partials", "footer.html")
|
||||
|
||||
|
||||
@@ -298,7 +298,7 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, erro
|
||||
}
|
||||
}
|
||||
|
||||
httpFs := afero.NewHttpFs(f.c.Fs.Destination)
|
||||
httpFs := afero.NewHttpFs(f.c.destinationFs)
|
||||
fs := filesOnlyFs{httpFs.Dir(absPublishDir)}
|
||||
|
||||
doLiveReload := !f.s.buildWatch && !f.c.Cfg.GetBool("disableLiveReload")
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
"github.com/gohugoio/hugo/resource/tocss/scss"
|
||||
"github.com/spf13/cobra"
|
||||
jww "github.com/spf13/jwalterweatherman"
|
||||
)
|
||||
@@ -44,13 +45,24 @@ func newVersionCmd() *versionCmd {
|
||||
}
|
||||
|
||||
func printHugoVersion() {
|
||||
if hugolib.CommitHash == "" {
|
||||
if hugolib.BuildDate == "" {
|
||||
jww.FEEDBACK.Printf("Hugo Static Site Generator v%s %s/%s\n", helpers.CurrentHugoVersion, runtime.GOOS, runtime.GOARCH)
|
||||
} else {
|
||||
jww.FEEDBACK.Printf("Hugo Static Site Generator v%s %s/%s BuildDate: %s\n", helpers.CurrentHugoVersion, runtime.GOOS, runtime.GOARCH, hugolib.BuildDate)
|
||||
}
|
||||
} else {
|
||||
jww.FEEDBACK.Printf("Hugo Static Site Generator v%s-%s %s/%s BuildDate: %s\n", helpers.CurrentHugoVersion, strings.ToUpper(hugolib.CommitHash), runtime.GOOS, runtime.GOARCH, hugolib.BuildDate)
|
||||
program := "Hugo Static Site Generator"
|
||||
|
||||
version := "v" + helpers.CurrentHugoVersion.String()
|
||||
if hugolib.CommitHash != "" {
|
||||
version += "-" + strings.ToUpper(hugolib.CommitHash)
|
||||
}
|
||||
if scss.Supports() {
|
||||
version += "/extended"
|
||||
}
|
||||
|
||||
osArch := runtime.GOOS + "/" + runtime.GOARCH
|
||||
|
||||
var buildDate string
|
||||
if hugolib.BuildDate != "" {
|
||||
buildDate = hugolib.BuildDate
|
||||
} else {
|
||||
buildDate = "unknown"
|
||||
}
|
||||
|
||||
jww.FEEDBACK.Println(program, version, osArch, "BuildDate:", buildDate)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2018 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package errors contains common Hugo errors and error related utilities.
|
||||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// We will, at least to begin with, make some Hugo features (SCSS with libsass) optional,
|
||||
// and this error is used to signal those situations.
|
||||
var FeatureNotAvailableErr = errors.New("this feature is not available in your current Hugo version")
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2018 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -11,14 +11,14 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package hugolib
|
||||
package maps
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/gohugoio/hugo/tpl/math"
|
||||
"github.com/gohugoio/hugo/common/math"
|
||||
)
|
||||
|
||||
// Scratch is a writable context used for stateful operations in Page/Node rendering.
|
||||
@@ -130,6 +130,6 @@ func (c *Scratch) GetSortedMapValues(key string) interface{} {
|
||||
return sortedArray
|
||||
}
|
||||
|
||||
func newScratch() *Scratch {
|
||||
func NewScratch() *Scratch {
|
||||
return &Scratch{values: make(map[string]interface{})}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2018 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -11,7 +11,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package hugolib
|
||||
package maps
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
|
||||
func TestScratchAdd(t *testing.T) {
|
||||
t.Parallel()
|
||||
scratch := newScratch()
|
||||
scratch := NewScratch()
|
||||
scratch.Add("int1", 10)
|
||||
scratch.Add("int1", 20)
|
||||
scratch.Add("int2", 20)
|
||||
@@ -53,7 +53,7 @@ func TestScratchAdd(t *testing.T) {
|
||||
|
||||
func TestScratchAddSlice(t *testing.T) {
|
||||
t.Parallel()
|
||||
scratch := newScratch()
|
||||
scratch := NewScratch()
|
||||
|
||||
_, err := scratch.Add("intSlice", []int{1, 2})
|
||||
assert.Nil(t, err)
|
||||
@@ -82,14 +82,14 @@ func TestScratchAddSlice(t *testing.T) {
|
||||
|
||||
func TestScratchSet(t *testing.T) {
|
||||
t.Parallel()
|
||||
scratch := newScratch()
|
||||
scratch := NewScratch()
|
||||
scratch.Set("key", "val")
|
||||
assert.Equal(t, "val", scratch.Get("key"))
|
||||
}
|
||||
|
||||
func TestScratchDelete(t *testing.T) {
|
||||
t.Parallel()
|
||||
scratch := newScratch()
|
||||
scratch := NewScratch()
|
||||
scratch.Set("key", "val")
|
||||
scratch.Delete("key")
|
||||
scratch.Add("key", "Lucy Parsons")
|
||||
@@ -99,7 +99,7 @@ func TestScratchDelete(t *testing.T) {
|
||||
// Issue #2005
|
||||
func TestScratchInParallel(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
scratch := newScratch()
|
||||
scratch := NewScratch()
|
||||
key := "counter"
|
||||
scratch.Set(key, int64(1))
|
||||
for i := 1; i <= 10; i++ {
|
||||
@@ -133,7 +133,7 @@ func TestScratchInParallel(t *testing.T) {
|
||||
|
||||
func TestScratchGet(t *testing.T) {
|
||||
t.Parallel()
|
||||
scratch := newScratch()
|
||||
scratch := NewScratch()
|
||||
nothing := scratch.Get("nothing")
|
||||
if nothing != nil {
|
||||
t.Errorf("Should not return anything, but got %v", nothing)
|
||||
@@ -142,7 +142,7 @@ func TestScratchGet(t *testing.T) {
|
||||
|
||||
func TestScratchSetInMap(t *testing.T) {
|
||||
t.Parallel()
|
||||
scratch := newScratch()
|
||||
scratch := NewScratch()
|
||||
scratch.SetInMap("key", "lux", "Lux")
|
||||
scratch.SetInMap("key", "abc", "Abc")
|
||||
scratch.SetInMap("key", "zyx", "Zyx")
|
||||
@@ -153,7 +153,7 @@ func TestScratchSetInMap(t *testing.T) {
|
||||
|
||||
func TestScratchGetSortedMapValues(t *testing.T) {
|
||||
t.Parallel()
|
||||
scratch := newScratch()
|
||||
scratch := NewScratch()
|
||||
nothing := scratch.GetSortedMapValues("nothing")
|
||||
if nothing != nil {
|
||||
t.Errorf("Should not return anything, but got %v", nothing)
|
||||
@@ -161,7 +161,7 @@ func TestScratchGetSortedMapValues(t *testing.T) {
|
||||
}
|
||||
|
||||
func BenchmarkScratchGet(b *testing.B) {
|
||||
scratch := newScratch()
|
||||
scratch := NewScratch()
|
||||
scratch.Add("A", 1)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
@@ -0,0 +1,135 @@
|
||||
// Copyright 2018 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package math
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// DoArithmetic performs arithmetic operations (+,-,*,/) using reflection to
|
||||
// determine the type of the two terms.
|
||||
func DoArithmetic(a, b interface{}, op rune) (interface{}, error) {
|
||||
av := reflect.ValueOf(a)
|
||||
bv := reflect.ValueOf(b)
|
||||
var ai, bi int64
|
||||
var af, bf float64
|
||||
var au, bu uint64
|
||||
switch av.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
ai = av.Int()
|
||||
switch bv.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
bi = bv.Int()
|
||||
case reflect.Float32, reflect.Float64:
|
||||
af = float64(ai) // may overflow
|
||||
ai = 0
|
||||
bf = bv.Float()
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
bu = bv.Uint()
|
||||
if ai >= 0 {
|
||||
au = uint64(ai)
|
||||
ai = 0
|
||||
} else {
|
||||
bi = int64(bu) // may overflow
|
||||
bu = 0
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("Can't apply the operator to the values")
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
af = av.Float()
|
||||
switch bv.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
bf = float64(bv.Int()) // may overflow
|
||||
case reflect.Float32, reflect.Float64:
|
||||
bf = bv.Float()
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
bf = float64(bv.Uint()) // may overflow
|
||||
default:
|
||||
return nil, errors.New("Can't apply the operator to the values")
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
au = av.Uint()
|
||||
switch bv.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
bi = bv.Int()
|
||||
if bi >= 0 {
|
||||
bu = uint64(bi)
|
||||
bi = 0
|
||||
} else {
|
||||
ai = int64(au) // may overflow
|
||||
au = 0
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
af = float64(au) // may overflow
|
||||
au = 0
|
||||
bf = bv.Float()
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
bu = bv.Uint()
|
||||
default:
|
||||
return nil, errors.New("Can't apply the operator to the values")
|
||||
}
|
||||
case reflect.String:
|
||||
as := av.String()
|
||||
if bv.Kind() == reflect.String && op == '+' {
|
||||
bs := bv.String()
|
||||
return as + bs, nil
|
||||
}
|
||||
return nil, errors.New("Can't apply the operator to the values")
|
||||
default:
|
||||
return nil, errors.New("Can't apply the operator to the values")
|
||||
}
|
||||
|
||||
switch op {
|
||||
case '+':
|
||||
if ai != 0 || bi != 0 {
|
||||
return ai + bi, nil
|
||||
} else if af != 0 || bf != 0 {
|
||||
return af + bf, nil
|
||||
} else if au != 0 || bu != 0 {
|
||||
return au + bu, nil
|
||||
}
|
||||
return 0, nil
|
||||
case '-':
|
||||
if ai != 0 || bi != 0 {
|
||||
return ai - bi, nil
|
||||
} else if af != 0 || bf != 0 {
|
||||
return af - bf, nil
|
||||
} else if au != 0 || bu != 0 {
|
||||
return au - bu, nil
|
||||
}
|
||||
return 0, nil
|
||||
case '*':
|
||||
if ai != 0 || bi != 0 {
|
||||
return ai * bi, nil
|
||||
} else if af != 0 || bf != 0 {
|
||||
return af * bf, nil
|
||||
} else if au != 0 || bu != 0 {
|
||||
return au * bu, nil
|
||||
}
|
||||
return 0, nil
|
||||
case '/':
|
||||
if bi != 0 {
|
||||
return ai / bi, nil
|
||||
} else if bf != 0 {
|
||||
return af / bf, nil
|
||||
} else if bu != 0 {
|
||||
return au / bu, nil
|
||||
}
|
||||
return nil, errors.New("Can't divide the value by 0")
|
||||
default:
|
||||
return nil, errors.New("There is no such an operation")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright 2018 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package math
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/alecthomas/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDoArithmetic(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for i, test := range []struct {
|
||||
a interface{}
|
||||
b interface{}
|
||||
op rune
|
||||
expect interface{}
|
||||
}{
|
||||
{3, 2, '+', int64(5)},
|
||||
{3, 2, '-', int64(1)},
|
||||
{3, 2, '*', int64(6)},
|
||||
{3, 2, '/', int64(1)},
|
||||
{3.0, 2, '+', float64(5)},
|
||||
{3.0, 2, '-', float64(1)},
|
||||
{3.0, 2, '*', float64(6)},
|
||||
{3.0, 2, '/', float64(1.5)},
|
||||
{3, 2.0, '+', float64(5)},
|
||||
{3, 2.0, '-', float64(1)},
|
||||
{3, 2.0, '*', float64(6)},
|
||||
{3, 2.0, '/', float64(1.5)},
|
||||
{3.0, 2.0, '+', float64(5)},
|
||||
{3.0, 2.0, '-', float64(1)},
|
||||
{3.0, 2.0, '*', float64(6)},
|
||||
{3.0, 2.0, '/', float64(1.5)},
|
||||
{uint(3), uint(2), '+', uint64(5)},
|
||||
{uint(3), uint(2), '-', uint64(1)},
|
||||
{uint(3), uint(2), '*', uint64(6)},
|
||||
{uint(3), uint(2), '/', uint64(1)},
|
||||
{uint(3), 2, '+', uint64(5)},
|
||||
{uint(3), 2, '-', uint64(1)},
|
||||
{uint(3), 2, '*', uint64(6)},
|
||||
{uint(3), 2, '/', uint64(1)},
|
||||
{3, uint(2), '+', uint64(5)},
|
||||
{3, uint(2), '-', uint64(1)},
|
||||
{3, uint(2), '*', uint64(6)},
|
||||
{3, uint(2), '/', uint64(1)},
|
||||
{uint(3), -2, '+', int64(1)},
|
||||
{uint(3), -2, '-', int64(5)},
|
||||
{uint(3), -2, '*', int64(-6)},
|
||||
{uint(3), -2, '/', int64(-1)},
|
||||
{-3, uint(2), '+', int64(-1)},
|
||||
{-3, uint(2), '-', int64(-5)},
|
||||
{-3, uint(2), '*', int64(-6)},
|
||||
{-3, uint(2), '/', int64(-1)},
|
||||
{uint(3), 2.0, '+', float64(5)},
|
||||
{uint(3), 2.0, '-', float64(1)},
|
||||
{uint(3), 2.0, '*', float64(6)},
|
||||
{uint(3), 2.0, '/', float64(1.5)},
|
||||
{3.0, uint(2), '+', float64(5)},
|
||||
{3.0, uint(2), '-', float64(1)},
|
||||
{3.0, uint(2), '*', float64(6)},
|
||||
{3.0, uint(2), '/', float64(1.5)},
|
||||
{0, 0, '+', 0},
|
||||
{0, 0, '-', 0},
|
||||
{0, 0, '*', 0},
|
||||
{"foo", "bar", '+', "foobar"},
|
||||
{3, 0, '/', false},
|
||||
{3.0, 0, '/', false},
|
||||
{3, 0.0, '/', false},
|
||||
{uint(3), uint(0), '/', false},
|
||||
{3, uint(0), '/', false},
|
||||
{-3, uint(0), '/', false},
|
||||
{uint(3), 0, '/', false},
|
||||
{3.0, uint(0), '/', false},
|
||||
{uint(3), 0.0, '/', false},
|
||||
{3, "foo", '+', false},
|
||||
{3.0, "foo", '+', false},
|
||||
{uint(3), "foo", '+', false},
|
||||
{"foo", 3, '+', false},
|
||||
{"foo", "bar", '-', false},
|
||||
{3, 2, '%', false},
|
||||
} {
|
||||
errMsg := fmt.Sprintf("[%d] %v", i, test)
|
||||
|
||||
result, err := DoArithmetic(test.a, test.b, test.op)
|
||||
|
||||
if b, ok := test.expect.(bool); ok && !b {
|
||||
require.Error(t, err, errMsg)
|
||||
continue
|
||||
}
|
||||
|
||||
require.NoError(t, err, errMsg)
|
||||
assert.Equal(t, test.expect, result, errMsg)
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,7 @@ func DecodeConfig(cfg config.Provider) (c Config, err error) {
|
||||
|
||||
err = mapstructure.WeakDecode(m, &c)
|
||||
|
||||
// Keep backwards compability.
|
||||
// Keep backwards compatibility.
|
||||
if c.GoogleAnalytics.ID == "" {
|
||||
// Try the global config
|
||||
c.GoogleAnalytics.ID = cfg.GetString(googleAnalyticsKey)
|
||||
|
||||
@@ -134,7 +134,7 @@ func executeArcheTypeAsTemplate(s *hugolib.Site, kind, targetPath, archetypeFile
|
||||
return nil, fmt.Errorf("Failed to parse archetype file %q: %s", archetypeFilename, err)
|
||||
}
|
||||
|
||||
templ := templateHandler.Lookup(templateName)
|
||||
templ, _ := templateHandler.Lookup(templateName)
|
||||
|
||||
var buff bytes.Buffer
|
||||
if err := templ.Execute(&buff, data); err != nil {
|
||||
|
||||
@@ -88,6 +88,8 @@ func initViper(v *viper.Viper) {
|
||||
v.Set("i18nDir", "i18n")
|
||||
v.Set("theme", "sample")
|
||||
v.Set("archetypeDir", "archetypes")
|
||||
v.Set("resourceDir", "resources")
|
||||
v.Set("publishDir", "public")
|
||||
}
|
||||
|
||||
func initFs(fs *hugofs.Fs) error {
|
||||
@@ -191,6 +193,7 @@ func newTestCfg() (*viper.Viper, *hugofs.Fs) {
|
||||
v.Set("i18nDir", "i18n")
|
||||
v.Set("layoutDir", "layouts")
|
||||
v.Set("archetypeDir", "archetypes")
|
||||
v.Set("assetDir", "assets")
|
||||
|
||||
fs := hugofs.NewMem(v)
|
||||
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
package deps
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
"github.com/gohugoio/hugo/metrics"
|
||||
"github.com/gohugoio/hugo/output"
|
||||
"github.com/gohugoio/hugo/resource"
|
||||
"github.com/gohugoio/hugo/source"
|
||||
"github.com/gohugoio/hugo/tpl"
|
||||
jww "github.com/spf13/jwalterweatherman"
|
||||
@@ -21,6 +23,7 @@ import (
|
||||
// There will be normally only one instance of deps in play
|
||||
// at a given time, i.e. one per Site built.
|
||||
type Deps struct {
|
||||
|
||||
// The logger to use.
|
||||
Log *jww.Notepad `json:"-"`
|
||||
|
||||
@@ -30,6 +33,9 @@ type Deps struct {
|
||||
// The templates to use. This will usually implement the full tpl.TemplateHandler.
|
||||
Tmpl tpl.TemplateFinder `json:"-"`
|
||||
|
||||
// We use this to parse and execute ad-hoc text templates.
|
||||
TextTmpl tpl.TemplateParseFinder `json:"-"`
|
||||
|
||||
// The file systems to use.
|
||||
Fs *hugofs.Fs `json:"-"`
|
||||
|
||||
@@ -42,6 +48,9 @@ type Deps struct {
|
||||
// The SourceSpec to use
|
||||
SourceSpec *source.SourceSpec `json:"-"`
|
||||
|
||||
// The Resource Spec to use
|
||||
ResourceSpec *resource.Spec
|
||||
|
||||
// The configuration to use
|
||||
Cfg config.Provider `json:"-"`
|
||||
|
||||
@@ -62,6 +71,30 @@ type Deps struct {
|
||||
|
||||
// Timeout is configurable in site config.
|
||||
Timeout time.Duration
|
||||
|
||||
// BuildStartListeners will be notified before a build starts.
|
||||
BuildStartListeners *Listeners
|
||||
}
|
||||
|
||||
type Listeners struct {
|
||||
sync.Mutex
|
||||
|
||||
// A list of funcs to be notified about an event.
|
||||
listeners []func()
|
||||
}
|
||||
|
||||
func (b *Listeners) Add(f func()) {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
b.listeners = append(b.listeners, f)
|
||||
}
|
||||
|
||||
func (b *Listeners) Notify() {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
for _, notify := range b.listeners {
|
||||
notify()
|
||||
}
|
||||
}
|
||||
|
||||
// ResourceProvider is used to create and refresh, and clone resources needed.
|
||||
@@ -115,7 +148,7 @@ func New(cfg DepsCfg) (*Deps, error) {
|
||||
}
|
||||
|
||||
if logger == nil {
|
||||
logger = jww.NewNotepad(jww.LevelError, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
|
||||
logger = loggers.NewErrorLogger()
|
||||
}
|
||||
|
||||
if fs == nil {
|
||||
@@ -129,6 +162,11 @@ func New(cfg DepsCfg) (*Deps, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resourceSpec, err := resource.NewSpec(ps, logger, cfg.MediaTypes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
contentSpec, err := helpers.NewContentSpec(cfg.Language)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -153,8 +191,10 @@ func New(cfg DepsCfg) (*Deps, error) {
|
||||
PathSpec: ps,
|
||||
ContentSpec: contentSpec,
|
||||
SourceSpec: sp,
|
||||
ResourceSpec: resourceSpec,
|
||||
Cfg: cfg.Language,
|
||||
Language: cfg.Language,
|
||||
BuildStartListeners: &Listeners{},
|
||||
Timeout: time.Duration(timeoutms) * time.Millisecond,
|
||||
}
|
||||
|
||||
@@ -167,7 +207,8 @@ func New(cfg DepsCfg) (*Deps, error) {
|
||||
|
||||
// ForLanguage creates a copy of the Deps with the language dependent
|
||||
// parts switched out.
|
||||
func (d Deps) ForLanguage(l *langs.Language) (*Deps, error) {
|
||||
func (d Deps) ForLanguage(cfg DepsCfg) (*Deps, error) {
|
||||
l := cfg.Language
|
||||
var err error
|
||||
|
||||
d.PathSpec, err = helpers.NewPathSpecWithBaseBaseFsProvided(d.Fs, l, d.BaseFs)
|
||||
@@ -180,6 +221,11 @@ func (d Deps) ForLanguage(l *langs.Language) (*Deps, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d.ResourceSpec, err = resource.NewSpec(d.PathSpec, d.Log, cfg.MediaTypes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d.Cfg = l
|
||||
d.Language = l
|
||||
|
||||
@@ -191,6 +237,8 @@ func (d Deps) ForLanguage(l *langs.Language) (*Deps, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d.BuildStartListeners = &Listeners{}
|
||||
|
||||
return &d, nil
|
||||
|
||||
}
|
||||
@@ -212,6 +260,9 @@ type DepsCfg struct {
|
||||
// The configuration to use.
|
||||
Cfg config.Provider
|
||||
|
||||
// The media types configured.
|
||||
MediaTypes media.Types
|
||||
|
||||
// Template handling.
|
||||
TemplateProvider ResourceProvider
|
||||
WithTemplate func(templ tpl.TemplateHandler) error
|
||||
|
||||
@@ -70,7 +70,7 @@ twitter = "GoHugoIO"
|
||||
[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.42"
|
||||
release = "0.44"
|
||||
## Setting this to true will add a "noindex" to *EVERY* page on the site
|
||||
removefromexternalsearch = false
|
||||
## Gh repo for site footer (include trailing slash)
|
||||
|
||||
@@ -49,8 +49,10 @@ simple = false
|
||||
[privacy.twitter]
|
||||
disable = false
|
||||
enableDNT = false
|
||||
simple = false
|
||||
[privacy.vimeo]
|
||||
disable = false
|
||||
simple = false
|
||||
[privacy.youtube]
|
||||
disable = false
|
||||
privacyEnhanced = false
|
||||
@@ -108,8 +110,25 @@ disableInlineCSS = true
|
||||
enableDNT
|
||||
: Enabling this for the twitter/tweet shortcode, the tweet and its embedded page on your site are not used for purposes that include personalized suggestions and personalized ads.
|
||||
|
||||
simple
|
||||
: If simple mode is enabled, a static and no-JS version of a tweet will be built.
|
||||
|
||||
|
||||
**Note:** If you use the _simple mode_ for Twitter, you may want to disable the inlines styles provided by Hugo:
|
||||
|
||||
{{< code-toggle file="config">}}
|
||||
[services]
|
||||
[services.twitter]
|
||||
disableInlineCSS = true
|
||||
{{< /code-toggle >}}
|
||||
|
||||
### YouTube
|
||||
|
||||
privacyEnhanced
|
||||
: When you turn on privacy-enhanced mode, YouTube won’t store information about visitors on your website unless the user plays the embedded video.
|
||||
|
||||
### Vimeo
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -35,14 +35,13 @@ help = "Help"
|
||||
title = "My blog"
|
||||
weight = 1
|
||||
[languages.en.params]
|
||||
linkedin = "english-link"
|
||||
linkedin = "https://linkedin.com/whoever"
|
||||
|
||||
[languages.fr]
|
||||
copyright = "Tout est à moi"
|
||||
title = "Mon blog"
|
||||
title = "Mon blogue"
|
||||
weight = 2
|
||||
[languages.fr.params]
|
||||
linkedin = "lien-francais"
|
||||
linkedin = "https://linkedin.com/fr/whoever"
|
||||
[languages.fr.params.navigation]
|
||||
help = "Aide"
|
||||
{{< /code-toggle >}}
|
||||
@@ -55,11 +54,13 @@ and taxonomy pages will be rendered below `/` in English (your default content l
|
||||
|
||||
When working with front matter `Params` in [single page templates][singles], omit the `params` in the key for the translation.
|
||||
|
||||
If you want all of the languages to be put below their respective language code, enable `defaultContentLanguageInSubdir: true`.
|
||||
`defaultContentLanguage` sets the project's default language. If not set, the default language will be `en`.
|
||||
|
||||
If the default language needs to be rendererd below its own language code (`/en`) like the others, set `defaultContentLanguageInSubdir: true`.
|
||||
|
||||
Only the obvious non-global options can be overridden per language. Examples of global options are `baseURL`, `buildDrafts`, etc.
|
||||
|
||||
## Disable a Language
|
||||
### Disable a Language
|
||||
|
||||
You can disable one or more languages. This can be useful when working on a new translation.
|
||||
|
||||
@@ -81,7 +82,7 @@ HUGO_DISABLELANGUAGES=" " hugo server
|
||||
```
|
||||
|
||||
|
||||
## Configure Multilingual Multihost
|
||||
### Configure Multilingual Multihost
|
||||
|
||||
From **Hugo 0.31** we support multiple languages in a multihost configuration. See [this issue](https://github.com/gohugoio/hugo/issues/4027) for details.
|
||||
|
||||
@@ -94,11 +95,11 @@ Example:
|
||||
|
||||
{{< code-toggle file="config" >}}
|
||||
[languages]
|
||||
[languages.no]
|
||||
baseURL = "https://example.no"
|
||||
languageName = "Norsk"
|
||||
[languages.fr]
|
||||
baseURL = "https://example.fr"
|
||||
languageName = "Français"
|
||||
weight = 1
|
||||
title = "På norsk"
|
||||
title = "En Français"
|
||||
|
||||
[languages.en]
|
||||
baseURL = "https://example.com"
|
||||
@@ -127,7 +128,7 @@ Press Ctrl+C to stop
|
||||
|
||||
Live reload and `--navigateToChanged` between the servers work as expected.
|
||||
|
||||
## Taxonomies and Blackfriday
|
||||
### Taxonomies and Blackfriday
|
||||
|
||||
Taxonomies and [Blackfriday configuration][config] can also be set per language:
|
||||
|
||||
@@ -156,40 +157,113 @@ plaque = "plaques"
|
||||
|
||||
## Translate Your Content
|
||||
|
||||
Translated articles are identified by the name of the content file.
|
||||
There are two ways to manage your content translation, both ensures each page is assigned a language and linked to its translations.
|
||||
|
||||
### Examples of Translated Articles
|
||||
### Translation by filename
|
||||
|
||||
Considering the following example:
|
||||
|
||||
1. `/content/about.en.md`
|
||||
2. `/content/about.fr.md`
|
||||
|
||||
In this example, the `about.md` will be assigned the configured `defaultContentLanguage`.
|
||||
The first file is assigned the english language and linked to the second.
|
||||
The second file is assigned the french language and linked to the first.
|
||||
|
||||
1. `/content/about.md`
|
||||
2. `/content/about.fr.md`
|
||||
Their language is __assigned__ according to the language code added as __suffix to the filename__.
|
||||
|
||||
This way, you can slowly start to translate your current content without having to rename everything. If left unspecified, the default value for `defaultContentLanguage` is `en`.
|
||||
By having the same **path and base filename**, the content pieces are __linked__ together as translated pages.
|
||||
{{< note >}}
|
||||
|
||||
By having the same **directory and base filename**, the content pieces are linked together as translated pieces.
|
||||
If a file is missing any language code, it will be assigned the default language.
|
||||
|
||||
You can also set the key used to link the translations explicitly in front matter:
|
||||
{{</ note >}}
|
||||
### Translation by content directory
|
||||
|
||||
This system uses different content directories for each of the languages. Each language's content directory is set using the `contentDir` param.
|
||||
|
||||
{{< code-toggle file="config" >}}
|
||||
|
||||
languages:
|
||||
en:
|
||||
weight: 10
|
||||
languageName: "English"
|
||||
contentDir: "content/english"
|
||||
nn:
|
||||
weight: 20
|
||||
languageName: "Français"
|
||||
contentDir: "content/french"
|
||||
|
||||
{{< /code-toggle >}}
|
||||
|
||||
The value of `contentDir` can be any valid path, even absolute path references. The only restriction is that the content directories cannot overlap.
|
||||
|
||||
Considering the following example in conjunction with the configuration above:
|
||||
|
||||
1. `/content/english/about.md`
|
||||
2. `/content/french/about.md`
|
||||
|
||||
The first file is assigned the english language and is linked to the second.
|
||||
<br>The second file is assigned the french language and is linked to the first.
|
||||
|
||||
Their language is __assigned__ according to the content directory they are __placed__ in.
|
||||
|
||||
By having the same **path and basename** (relative to their language content directory), the content pieces are __linked__ together as translated pages.
|
||||
|
||||
### Bypassing default linking.
|
||||
|
||||
Any pages sharing the same `translationKey` set in front matter will be linked as translated pages regardless of basename or location.
|
||||
|
||||
Considering the following example:
|
||||
|
||||
1. `/content/about-us.en.md`
|
||||
2. `/content/om.nn.md`
|
||||
3. `/content/presentation/a-propos.fr.md`
|
||||
|
||||
```yaml
|
||||
translationKey: "my-story"
|
||||
# set in all three pages
|
||||
translationKey: "about"
|
||||
```
|
||||
|
||||
If you need distinct URLs per language, you can set the slug in the non-default language file. For example, you can define a custom slug for a French translation in the front matter of `content/about.fr.md` as follows:
|
||||
By setting the `translationKey` front matter param to `about` in all three pages, they will be __linked__ as translated pages.
|
||||
|
||||
```yaml
|
||||
|
||||
### Localizing permalinks
|
||||
|
||||
Because paths and filenames are used to handle linking, all translated pages, except for the language part, will be sharing the same url.
|
||||
|
||||
To localize the URLs, the [`slug`]({{< ref "content-management/organization/index.md#slug" >}}) or [`url`]({{< ref "content-management/organization/index.md#url" >}}) front matter param can be set in any of the non-default language file.
|
||||
|
||||
For example, a french translation (`content/about.fr.md`) can have its own localized slug.
|
||||
|
||||
{{< code-toggle >}}
|
||||
Title: A Propos
|
||||
slug: "a-propos"
|
||||
{{< /code-toggle >}}
|
||||
|
||||
```
|
||||
|
||||
At render, Hugo will build both `/about/` and `/a-propos/` as properly linked translated pages.
|
||||
At render, Hugo will build both `/about/` and `fr/a-propos/` while maintaning their translation linking.
|
||||
{{% note %}}
|
||||
If using `url`, remember to include the language part as well: `fr/compagnie/a-propos/`.
|
||||
{{%/ note %}}
|
||||
|
||||
For merging of content from other languages (i.e. missing content translations), see [lang.Merge](/functions/lang.merge/).
|
||||
### Page Bundles
|
||||
|
||||
## Link to Translated Content
|
||||
To avoid the burden of having to duplicate files, each Page Bundle inherits the resources of its linked translated pages' bundles except for the content files (markdown files, html files etc...).
|
||||
|
||||
Therefore, from within a template, the page will have access to the files from all linked pages' bundles.
|
||||
|
||||
If, across the linked bundles, two or more files share the same basenname, only one will be included and chosen as follows:
|
||||
|
||||
* File from current language Bundle, if present.
|
||||
* First file found across bundles by order of language `Weight`.
|
||||
|
||||
{{% note %}}
|
||||
|
||||
Page Bundle's resources follow the same language assignement logic as content files, be it by filename (`image.jpg`, `image.fr.jpg`) or by directory (`english/about/header.jpg`, `french/about/header.jpg`).
|
||||
|
||||
{{%/ note %}}
|
||||
|
||||
## Reference the Translated Content
|
||||
|
||||
To create a list of links to translated content, use a template similar to the following:
|
||||
|
||||
@@ -210,7 +284,7 @@ The above can be put in a `partial` (i.e., inside `layouts/partials/`) and inclu
|
||||
|
||||
The above also uses the [`i18n` function][i18func] described in the next section.
|
||||
|
||||
## List All Available Languages
|
||||
### List All Available Languages
|
||||
|
||||
`.AllTranslations` on a `Page` can be used to list all translations, including itself. Called on the home page it can be used to build a language navigator:
|
||||
|
||||
|
||||
@@ -374,6 +374,10 @@ Using the preceding `youtube` example (without `autoplay="true"`), the following
|
||||
|
||||
{{< youtube w7Ft2ymGmfc >}}
|
||||
|
||||
## Privacy Config
|
||||
|
||||
To learn how to configure your Hugo site to meet the new EU privacy regulation, see [Hugo and the GDPR][].
|
||||
|
||||
## Create Custom Shortcodes
|
||||
|
||||
To learn more about creating custom shortcodes, see the [shortcode template documentation][].
|
||||
@@ -382,6 +386,7 @@ To learn more about creating custom shortcodes, see the [shortcode template docu
|
||||
[contentmanagementsection]: /content-management/formats/
|
||||
[examplegist]: https://gist.github.com/spf13/7896402
|
||||
[figureelement]: http://html5doctor.com/the-figure-figcaption-elements/ "An article from HTML5 doctor discussing the fig and figcaption elements."
|
||||
[Hugo and the GDPR]: /about/hugo-and-gdpr/
|
||||
[Instagram]: https://www.instagram.com/
|
||||
[pagevariables]: /variables/page/
|
||||
[partials]: /templates/partials/
|
||||
|
||||
@@ -72,7 +72,13 @@ pygmentsCodefences
|
||||
: Set to true to enable syntax highlighting in code fences with a language tag in markdown (see below for an example).
|
||||
|
||||
pygmentsStyle
|
||||
: The style of code highlighting. See https://help.farbox.com/pygments.html for a gallery. Note that this option is not relevant when `pygmentsUseClasses` is set.
|
||||
: The style of code highlighting. Note that this option is not
|
||||
relevant when `pygmentsUseClasses` is set.
|
||||
|
||||
Syntax highlighting galleries:
|
||||
**Chroma** ([short snippets](https://xyproto.github.io/splash/docs/all.html),
|
||||
[long snippets](https://xyproto.github.io/splash/docs/longer/all.html)),
|
||||
[Pygments](https://help.farbox.com/pygments.html)
|
||||
|
||||
pygmentsUseClasses
|
||||
: Set to `true` to use CSS classes to format your highlighted code. See [Generate Syntax Highlighter CSS](#generate-syntax-highlighter-css).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: errorf
|
||||
linktitle: errorf
|
||||
description: Evaluates a format string and logs it to ERROR.
|
||||
description: Log ERROR and fail the build from the templates.
|
||||
date: 2017-09-30
|
||||
publishdate: 2017-09-30
|
||||
lastmod: 2017-09-30
|
||||
@@ -18,9 +18,12 @@ deprecated: false
|
||||
aliases: []
|
||||
---
|
||||
|
||||
`errorf` will evaluate a format string, then output the result to the ERROR log.
|
||||
This will also cause the build to fail.
|
||||
`errorf` will evaluate a format string, then output the result to the ERROR log (and only once per error message to avoid flooding the log).
|
||||
|
||||
This will also cause the build to fail (the `hugo` command will `exit -1`).
|
||||
|
||||
```
|
||||
{{ errorf "Something went horribly wrong! %s" err }}
|
||||
{{ errorf "Failed to handle page %q" .Path }}
|
||||
```
|
||||
|
||||
Note that `errorf` supports all the formatting verbs of the [fmt](https://golang.org/pkg/fmt/) package.
|
||||
|
||||
@@ -22,6 +22,9 @@ aliases: [/extras/scratch/,/doc/scratch/]
|
||||
|
||||
In most cases you can do okay without `Scratch`, but due to scoping issues, there are many use cases that aren't solvable in Go Templates without `Scratch`'s help.
|
||||
|
||||
`.Scratch` is available as methods on `Page` and `Shortcode`. Since Hugo 0.43 you can also create a locally scoped `Scratch` using the template func `newScratch`.
|
||||
|
||||
|
||||
{{% note %}}
|
||||
See [this Go issue](https://github.com/golang/go/issues/10608) for the main motivation behind Scratch.
|
||||
{{% /note %}}
|
||||
|
||||
@@ -12,7 +12,7 @@ toc: true
|
||||
|
||||
## The Config Toggler!
|
||||
|
||||
This is an exemple for the Config Toggle shortcode.
|
||||
This is an example for the Config Toggle shortcode.
|
||||
Its purpose is to let users choose a Config language by clicking on its corresponding tab. Upon doing so, every Code toggler on the page will be switched to the target language. Also, target language will be saved in user's `localStorage` so when they go to a different pages, Code Toggler display their last "toggled" config language.
|
||||
|
||||
## That Config Toggler
|
||||
|
||||
@@ -104,7 +104,7 @@ The most common usage is probably to run `hugo` with your current directory bein
|
||||
|
||||
This generates your website to the `public/` directory by default, although you can customize the output directory in your [site configuration][config] by changing the `publishDir` field.
|
||||
|
||||
The site Hugo renders into `public/` is ready to be deployed to your web server:
|
||||
The command `hugo` renders your site into `public/` dir and is ready to be deployed to your web server:
|
||||
|
||||
```
|
||||
hugo
|
||||
|
||||
@@ -82,7 +82,7 @@ Hugo now has:
|
||||
* Add a `GetPage` to the site benchmarks [a1956391](https://github.com/gohugoio/hugo/commit/a19563910eec5fed08f3b02563b9a7b38026183d) [@bep](https://github.com/bep)
|
||||
* Add headless bundle support [0432c64d](https://github.com/gohugoio/hugo/commit/0432c64dd22e4610302162678bb93661ba68d758) [@bep](https://github.com/bep) [#4311](https://github.com/gohugoio/hugo/issues/4311)
|
||||
* Merge matching resources params maps [5a0819b9](https://github.com/gohugoio/hugo/commit/5a0819b9b5eb9e79826cfa0a65f235d9821b1ac4) [@bep](https://github.com/bep) [#4315](https://github.com/gohugoio/hugo/issues/4315)
|
||||
* Add some general code contribution criterias [78c86330](https://github.com/gohugoio/hugo/commit/78c863305f337ed4faf3cf0a23675f28b0ae5641) [@bep](https://github.com/bep)
|
||||
* Add some general code contribution criteria [78c86330](https://github.com/gohugoio/hugo/commit/78c863305f337ed4faf3cf0a23675f28b0ae5641) [@bep](https://github.com/bep)
|
||||
* Tighten page kind logic, introduce tests [8125b4b0](https://github.com/gohugoio/hugo/commit/8125b4b03d10eb73f8aea3f9ea41172aba8df082) [@vassudanagunta](https://github.com/vassudanagunta)
|
||||
|
||||
## Fixes
|
||||
|
||||
@@ -25,7 +25,7 @@ You would experience errors of type:
|
||||
png: invalid format: not enough pixel data
|
||||
```
|
||||
|
||||
This commit fixes that by adding a mutex per image. This should also improve the performance, sligthly, as it avoids duplicate work.
|
||||
This commit fixes that by adding a mutex per image. This should also improve the performance, slightly, as it avoids duplicate work.
|
||||
|
||||
The current workaround before this fix is to always operate on the original:
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 73 KiB |
@@ -1,8 +1,8 @@
|
||||
|
||||
---
|
||||
date: 2018-06-12
|
||||
title: "0.42"
|
||||
description: "0.42"
|
||||
title: "Hugo 0.42: Theme Composition and Inheritance!"
|
||||
description: "Hugo 0.42 adds Theme Components support, a new and powerful way of composing your Hugo sites."
|
||||
categories: ["Releases"]
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
---
|
||||
date: 2018-06-13
|
||||
title: "Hugo 0.42.1: Two Bug Fixes"
|
||||
description: "Hugo 0.42.1 fixes two issues."
|
||||
categories: ["Releases"]
|
||||
images:
|
||||
- images/blog/hugo-bug-poster.png
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
This is a bug-fix release with two fixes:
|
||||
|
||||
* Reset the global pages cache on server rebuilds [128f14ef](https://github.com/gohugoio/hugo/commit/128f14efad90886ffef37c01ac1e20436a732f97) [@bep](https://github.com/bep) [#4845](https://github.com/gohugoio/hugo/issues/4845)
|
||||
* Do not fail server build when /static is missing [34ee27a7](https://github.com/gohugoio/hugo/commit/34ee27a78b9e2b5f475d44253ae234067b76cc6e) [@bep](https://github.com/bep) [#4846](https://github.com/gohugoio/hugo/issues/4846)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
---
|
||||
date: 2018-06-28
|
||||
title: "Hugo 0.42.2: One Bug Fix"
|
||||
description: "Hugo 0.42.2 fixes server reload on config changes."
|
||||
categories: ["Releases"]
|
||||
images:
|
||||
- images/blog/hugo-bug-poster.png
|
||||
|
||||
---
|
||||
|
||||
|
||||
This release fixes broken server-reload on config changes. This is a regression from Hugo `0.42`. [3a7706b0](https://github.com/gohugoio/hugo/commit/3a7706b069107e5fa6112b3f7ce006f16867cb38) [@bep](https://github.com/bep) [#4878](https://github.com/gohugoio/hugo/issues/4878)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
|
||||
---
|
||||
date: 2018-07-09
|
||||
title: "0.43"
|
||||
description: "0.43"
|
||||
categories: ["Releases"]
|
||||
---
|
||||
|
||||
Hugo `0.43` adds a powerful and very simple to use **Assets Pipeline** with **SASS and SCSS** with source map support, **PostCSS** and **minification** and **fingerprinting** and **Subresource Integrity** and ... much more. Oh, did we mention that you can now do **ad-hoc image processing** and execute text resources as Go templates?
|
||||
|
||||
An example pipeline:
|
||||
|
||||
```go-html-template
|
||||
{{ $styles := resources.Get "scss/main.scss" | toCSS | postCSS | minify | fingerprint }}
|
||||
<link rel="stylesheet" href="{{ $styles.Permalink }}" integrity="{{ $styles.Data.Integrity }}" media="screen">
|
||||
```
|
||||
|
||||
To me, the above is beautiful in its speed and simplicity. It could be printed on a t-shirt. I wrote in the [Hugo Birthday Post](https://gohugo.io/news/lets-celebrate-hugos-5th-birthday/) some days ago about the value of a single binary with native and fast implementations. I should have added _simplicity_ as a keyword. There seem to be a misconception that all of this needs to be hard and painful.
|
||||
|
||||
New functions to create `Resource` objects:
|
||||
|
||||
* `resources.Get`
|
||||
* `resources.FromString`: Create a Resource from a string.
|
||||
|
||||
New `Resource` transformation funcs:
|
||||
|
||||
* `resources.ToCSS`: Compile `SCSS` or `SASS` into `CSS`.
|
||||
* `resources.PostCSS`: Process your CSS with PostCSS. Config file support (project or theme or passed as an option).
|
||||
* `resources.Minify`: Currently supports `css`, `js`, `json`, `html`, `svg`, `xml`.
|
||||
* `resources.Fingerprint`: Creates a fingerprinted version of the given Resource with Subresource Integrity.
|
||||
* `resources.Concat`: Concatenates a list of Resource objects. Think of this as a poor man's bundler.
|
||||
* `resources.ExecuteAsTemplate`: Parses and executes the given Resource and data context (e.g. .Site) as a Go template.
|
||||
|
||||
|
||||
I, [@bep](https://github.com/bep), implemented this in [dea71670](https://github.com/gohugoio/hugo/commit/dea71670c059ab4d5a42bd22503f18c087dd22d4). We will work hard to get the documentation up to date, but follow the links above for details, and also see this [demo project](https://github.com/bep/hugo-sass-test).
|
||||
|
||||
|
||||
This release represents **35 contributions by 7 contributors** to the main Hugo code base.
|
||||
[@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@anthonyfok](https://github.com/anthonyfok), [@openscript](https://github.com/openscript), and [@caarlos0](https://github.com/caarlos0) for their ongoing contributions.
|
||||
And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the themes site in pristine condition and to [@kaushalmodi](https://github.com/kaushalmodi) for his great work on the documentation site.
|
||||
|
||||
Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs),
|
||||
which has received **11 contributions by 5 contributors**. A special thanks to [@bep](https://github.com/bep), [@danrl](https://github.com/danrl), [@regisphilibert](https://github.com/regisphilibert), and [@digitalcraftsman](https://github.com/digitalcraftsman) for their work on the documentation site.
|
||||
|
||||
Hugo now has:
|
||||
|
||||
* 26968+ [stars](https://github.com/gohugoio/hugo/stargazers)
|
||||
* 443+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
|
||||
* 238+ [themes](http://themes.gohugo.io/)
|
||||
|
||||
## Notes
|
||||
|
||||
* Replace deprecated {Get,}ByPrefix with {Get,}Match [42ed6025](https://github.com/gohugoio/hugo/commit/42ed602580a672e420e1d860384e812f4871ff67) [@anthonyfok](https://github.com/anthonyfok)
|
||||
* Hugo is now released with two binary version: One with and one without SCSS/SASS support. At the time of writing, this is only available in the binaries on the GitHub release page. Brew, Snap builds etc. will come. But note that you **only need the extended version if you want to edit SCSS**. For your CI server, or if you don't use SCSS, you will most likely want the non-extended version.
|
||||
|
||||
## Enhancements
|
||||
|
||||
### Templates
|
||||
|
||||
* Return en empty slice in `after` instead of error [f8212d20](https://github.com/gohugoio/hugo/commit/f8212d20009c4b5cc6e1ec733d09531eb6525d9f) [@bep](https://github.com/bep) [#4894](https://github.com/gohugoio/hugo/issues/4894)
|
||||
* Update internal pagination template to support Bootstrap 4 [ca1e46ef](https://github.com/gohugoio/hugo/commit/ca1e46efb94e3f3d2c8482cb9434d2f38ffd2683) [@bep](https://github.com/bep) [#4881](https://github.com/gohugoio/hugo/issues/4881)
|
||||
* Support text/template/parse API change in go1.11 [9f27091e](https://github.com/gohugoio/hugo/commit/9f27091e1067875e2577c331acc60adaef5bb234) [@anthonyfok](https://github.com/anthonyfok) [#4784](https://github.com/gohugoio/hugo/issues/4784)
|
||||
|
||||
### Core
|
||||
|
||||
* Allow forward slash in shortcode names [de37455e](https://github.com/gohugoio/hugo/commit/de37455ec73cffd039b44e8f6c62d2884b1d6bbd) [@bep](https://github.com/bep) [#4886](https://github.com/gohugoio/hugo/issues/4886)
|
||||
* Reset the global pages cache on server rebuilds [128f14ef](https://github.com/gohugoio/hugo/commit/128f14efad90886ffef37c01ac1e20436a732f97) [@bep](https://github.com/bep) [#4845](https://github.com/gohugoio/hugo/issues/4845)
|
||||
|
||||
### Other
|
||||
|
||||
* Bump CircleCI image [e3df6478](https://github.com/gohugoio/hugo/commit/e3df6478f09a7a5fed96aced791fa94fd2c35d1a) [@bep](https://github.com/bep)
|
||||
* Add Goreleaser extended config [626afc98](https://github.com/gohugoio/hugo/commit/626afc98254421f5a5edc97c541b10bd81d5bbbb) [@bep](https://github.com/bep) [#4908](https://github.com/gohugoio/hugo/issues/4908)
|
||||
* Build both hugo and hugo.extended for 0.43 [e1027c58](https://github.com/gohugoio/hugo/commit/e1027c5846b48c4ad450f6cc27e2654c9e0dae39) [@anthonyfok](https://github.com/anthonyfok) [#4908](https://github.com/gohugoio/hugo/issues/4908)
|
||||
* Add temporary build script [bfc3488b](https://github.com/gohugoio/hugo/commit/bfc3488b8e8b3dc1ffc6a339ee2dac8dcbdb55a9) [@bep](https://github.com/bep)
|
||||
* Add "extended" to "hugo version" [ce84b524](https://github.com/gohugoio/hugo/commit/ce84b524f4e94299b5b66afe7ce1a9bd4a9959fc) [@anthonyfok](https://github.com/anthonyfok) [#4913](https://github.com/gohugoio/hugo/issues/4913)
|
||||
* Add a newScratch template func [2b8d907a](https://github.com/gohugoio/hugo/commit/2b8d907ab731627f4e2a30442cd729064516c8bb) [@bep](https://github.com/bep) [#4685](https://github.com/gohugoio/hugo/issues/4685)
|
||||
* Add Hugo Piper with SCSS support and much more [dea71670](https://github.com/gohugoio/hugo/commit/dea71670c059ab4d5a42bd22503f18c087dd22d4) [@bep](https://github.com/bep) [#4381](https://github.com/gohugoio/hugo/issues/4381)[#4903](https://github.com/gohugoio/hugo/issues/4903)[#4858](https://github.com/gohugoio/hugo/issues/4858)
|
||||
* Consider root and current section's content type if set in front matter [c790029e](https://github.com/gohugoio/hugo/commit/c790029e1dbb0b66af18d05764bd6045deb2e180) [@bep](https://github.com/bep) [#4891](https://github.com/gohugoio/hugo/issues/4891)
|
||||
* Update docker image [554553c0](https://github.com/gohugoio/hugo/commit/554553c09c7657d28681e1fa0638806a452737a0) [@bep](https://github.com/bep)
|
||||
* Merge branch 'release-0.42.2' [282f6035](https://github.com/gohugoio/hugo/commit/282f6035e7c36f8550d91033e3a66718468c6c8b) [@bep](https://github.com/bep)
|
||||
* Release 0.42.2 [1637d12e](https://github.com/gohugoio/hugo/commit/1637d12e3762fc1ebab4cd675f75afaf25f59cdb) [@bep](https://github.com/bep)
|
||||
* Update GoReleaser config [1f0c4e1f](https://github.com/gohugoio/hugo/commit/1f0c4e1fb347bb233f3312c424fbf5a013c03604) [@caarlos0](https://github.com/caarlos0)
|
||||
* Create missing head.html partial on new theme generation [fd71fa89](https://github.com/gohugoio/hugo/commit/fd71fa89bd6c197402582c87b2b76d4b96d562bf) [@openscript](https://github.com/openscript)
|
||||
* Add html doctype to baseof.html template for new themes [b5a3aa70](https://github.com/gohugoio/hugo/commit/b5a3aa7082135d0a573f4fbb00f798e26b67b902) [@openscript](https://github.com/openscript)
|
||||
* Adds .gitattributes to force Go files to LF [6a2968fd](https://github.com/gohugoio/hugo/commit/6a2968fd5c0116d93de0f379ac615e9076821899) [@neurocline](https://github.com/neurocline)
|
||||
* Update to Go 1.9.7 and Go 1.10.3 [23d5fc82](https://github.com/gohugoio/hugo/commit/23d5fc82ee01d56440d0991c899acd31e9b63e27) [@anthonyfok](https://github.com/anthonyfok)
|
||||
* Update Dockerfile to a multi-stage build [8531ec7c](https://github.com/gohugoio/hugo/commit/8531ec7ca36fd35a57fba06bbb06a65c94dfd3ed) [@skoblenick](https://github.com/skoblenick) [#4154](https://github.com/gohugoio/hugo/issues/4154)
|
||||
* Release 0.42.1 [d67e843c](https://github.com/gohugoio/hugo/commit/d67e843c1212e1f53933556b5f946c8541188d9a) [@bep](https://github.com/bep)
|
||||
* Do not fail server build when /static is missing [34ee27a7](https://github.com/gohugoio/hugo/commit/34ee27a78b9e2b5f475d44253ae234067b76cc6e) [@bep](https://github.com/bep) [#4846](https://github.com/gohugoio/hugo/issues/4846)
|
||||
|
||||
## Fixes
|
||||
|
||||
* Do not create paginator pages for the other output formats [43338c3a](https://github.com/gohugoio/hugo/commit/43338c3a99769eb7d0df0c12559b8b3d42b67dba) [@bep](https://github.com/bep) [#4890](https://github.com/gohugoio/hugo/issues/4890)
|
||||
* Fix the shortcodes/partials vs base template detection [a5d0a57e](https://github.com/gohugoio/hugo/commit/a5d0a57e6bdab583134a68c035aac9b3007f006a) [@bep](https://github.com/bep) [#4897](https://github.com/gohugoio/hugo/issues/4897)
|
||||
* nfpm replacements [e1a052ec](https://github.com/gohugoio/hugo/commit/e1a052ecb823c688406a8af97dfaaf52a75231da) [@caarlos0](https://github.com/caarlos0)
|
||||
* Fix typos [3cea2932](https://github.com/gohugoio/hugo/commit/3cea2932e17a08ebc19cd05f3079d9379bc8fba5) [@idealhack](https://github.com/idealhack)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
|
||||
---
|
||||
date: 2018-07-13
|
||||
title: "0.44"
|
||||
description: "0.44"
|
||||
categories: ["Releases"]
|
||||
---
|
||||
|
||||
|
||||
Hugo `0.44` is the follow-up release, or **The Sequel**, of the very well received `0.43` only days ago. That release added **Hugo Pipes**, with SCSS/SASS support, assets bundling and minification, ad-hoc image processing and much more.
|
||||
|
||||
This is mostly a bug-fix release, but it also includes several important improvements.
|
||||
|
||||
Many complained that their SVG images vanished when browsed from the `hugo server`. With **Hugo Pipes** MIME types suddenly got really important, but Hugo's use of `Suffix` was ambiguous. This became visible when we redefined the `image/svg+xml` to work with **Hugo Pipes**. We have now added a `Suffixes` field on the MIME type definition in Hugo, which is a list of one or more filename suffixes the MIME type is identified with. If you need to add a custom MIME type definition, this means that you also need to specify the full MIME type as the key, e.g. `image/svg+xml`.
|
||||
|
||||
Hugo now has:
|
||||
|
||||
* 27120+ [stars](https://github.com/gohugoio/hugo/stargazers)
|
||||
* 443+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
|
||||
* 239+ [themes](http://themes.gohugo.io/)
|
||||
|
||||
## Notes
|
||||
* `MediaType.Suffix` is deprecated and replaced with a plural version, `MediaType.Suffixes`, with a more specific definition. You will get a detailed WARNING in the console if you need to do anything.
|
||||
|
||||
## Enhancements
|
||||
* Allow multiple file suffixes per media type [b874a1ba](https://github.com/gohugoio/hugo/commit/b874a1ba7ab8394dc741c8c70303a30a35b63e43) [@bep](https://github.com/bep) [#4920](https://github.com/gohugoio/hugo/issues/4920)
|
||||
* Clean up the in-memory Resource reader usage [47d38628](https://github.com/gohugoio/hugo/commit/47d38628ec0f4e72ff17661f13456b2a1511fe13) [@bep](https://github.com/bep) [#4936](https://github.com/gohugoio/hugo/issues/4936)
|
||||
* Move opening of the transformed resources after cache check [0024dcfe](https://github.com/gohugoio/hugo/commit/0024dcfe3e016c67046de06d1dac5e7f5235f9e1) [@bep](https://github.com/bep)
|
||||
* Improve type support in `resources.Concat` [306573de](https://github.com/gohugoio/hugo/commit/306573def0e20ec16ee5c447981cc09ed8bb7ec7) [@bep](https://github.com/bep) [#4934](https://github.com/gohugoio/hugo/issues/4934)
|
||||
* Flush `partialCached` cache on rebuilds [6b6dcb44](https://github.com/gohugoio/hugo/commit/6b6dcb44a014699c289bf32fe57d4c4216777be0) [@bep](https://github.com/bep) [#4931](https://github.com/gohugoio/hugo/issues/4931)
|
||||
* Include the transformation step in the error message [d96f2a46](https://github.com/gohugoio/hugo/commit/d96f2a460f58e91d8f6253a489d4879acfec6916) [@bep](https://github.com/bep) [#4924](https://github.com/gohugoio/hugo/issues/4924)
|
||||
* Exclude *.svg from CRLF/LF conversion [9c1e8208](https://github.com/gohugoio/hugo/commit/9c1e82085eb07d5b4dcdacbe82d5bafd26e08631) [@anthonyfok](https://github.com/anthonyfok)
|
||||
|
||||
## Fixes
|
||||
|
||||
* Fix `resources.Concat` for transformed resources [beec1fc9](https://github.com/gohugoio/hugo/commit/beec1fc98e5d37bba742d6bc2a0ff7c344b469f8) [@bep](https://github.com/bep) [#4936](https://github.com/gohugoio/hugo/issues/4936)
|
||||
* Fix static filesystem for themed multihost sites [80c8f3b8](https://github.com/gohugoio/hugo/commit/80c8f3b81a9849080e64bf877288ede28d960d3f) [@bep](https://github.com/bep) [#4929](https://github.com/gohugoio/hugo/issues/4929)
|
||||
* Set permission of embedded templates to 0644 [2b73e89d](https://github.com/gohugoio/hugo/commit/2b73e89d6d2822e86360a6c92c87f539677c119b) [@anthonyfok](https://github.com/anthonyfok)
|
||||
|
||||
|
After Width: | Height: | Size: 175 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,108 @@
|
||||
---
|
||||
date: 2018-07-04
|
||||
title: "Let’s celebrate Hugo’s 5th birthday"
|
||||
description: "How a side project became one of the most popular frameworks for building websites."
|
||||
categories: [blog]
|
||||
author: bep
|
||||
---
|
||||
|
||||
_By Bjørn Erik Pedersen ([@bepsays](https://twitter.com/bepsays) / [@bep](https://github.com/bep)), Hugo Lead_
|
||||
|
||||
**Five years ago today, [Steve Francia](https://github.com/spf13/) made his [first commit](https://github.com/gohugoio/hugo/commit/50a1d6f3f155ab837310e00ffb309a9199773c73
|
||||
) on the Hugo project: "Hugo: A Fast and Flexible Static Site Generator built with love by spf13 in GoLang".**
|
||||
|
||||
Steve was writing that on a train commute to New York. I'm writing this article running Hugo `v0.43-DEV`, the preview version of the next Hugo release. The release is scheduled for Monday and adds a powerful [assets pipeline](https://github.com/gohugoio/hugo/issues/4854#issue-333062459), with SCSS/SASS support, assets minification, fingerprinting/subresource integrity, ad-hoc image processing and much more.
|
||||
|
||||
**I cannot remember the last time I was this excited about a Hugo release. "Game changer" may be too strong, but it makes for a really nice integrated website design-workflow that, with Hugo's build speed, is hard to beat.**
|
||||
|
||||
{{< imgproc sunset Fill "600x300" >}}
|
||||
Fetch and scale an image in the upcoming Hugo 0.43.
|
||||
{{< /imgproc >}}
|
||||
|
||||
But that is a release for Monday. Now is a time to look at the current status of Hugo after the first five years.
|
||||
|
||||
## Hugo in Numbers
|
||||
|
||||
According to [BuiltWith](https://trends.builtwith.com/cms/Hugo), more than 29 000 live websites are built with Hugo. Of those, 390 are in the top 1 million. Wappalyzer [reports](https://www.wappalyzer.com/categories/static-site-generator) that Hugo serves almost 50% of the static sites.
|
||||
|
||||
Hugo is big in the [public sector](https://discourse.gohugo.io/t/hugo-in-public-administration/8792), with the US Government as a prominent user. Some examples are [vote.gov](https://vote.gov/) and [digital.gov](https://digital.gov/).
|
||||
|
||||
[Smashing Magazine](https://www.smashingmagazine.com/) is a big and very popular Hugo site. It is [reported](https://discourse.gohugo.io/t/smashing-magazine-s-redesign-powered-by-hugo-jamstack/5826/7) that they build their complex site with 7500 content pages in 13 seconds.
|
||||
|
||||
Some other example sites are [kubernetes.io](https://kubernetes.io/), [letsencrypt.org](https://gohugo.io/showcase/letsencrypt/), [support.1password.com](http://gohugo.io/showcase/1password-support/), [netlify.com](https://www.netlify.com), [litecoin.org](https://litecoin.org/), and [forestry.io](https://forestry.io/).
|
||||
|
||||
|
||||
{{< imgproc graph-stars Fit "600x400" >}}
|
||||
Number of GitHub stars in relation to the Hugo release dates.
|
||||
{{< /imgproc >}}
|
||||
|
||||
More numbers:
|
||||
|
||||
* 26800+ [stars](https://github.com/gohugoio/hugo/stargazers) on GitHub.
|
||||
* 444+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors) to the Hugo source repository, 654+ to [Hugo Docs](https://github.com/gohugoio/hugoDocs/graphs/contributors). [@bep](https://github.com/bep) is the most active with around 70% of the current code base (line count).
|
||||
* 235+ [themes](https://themes.gohugo.io/)
|
||||
* 50% increase in the number of user sessions on the [gohugo.io](https://gohugo.io/) web sites the last 12 months.[^2]
|
||||
* Hugo build release binaries for [a myriad](https://github.com/gohugoio/hugo/releases/tag/v0.42.2) of platforms. And since it can also be installed from Chocolatey on Windows, Brew on MacOs, Snap on Linux and `apt-get` on Debian/Ubuntu, it is impossible to give accurate download numbers. But the number is not small.
|
||||
|
||||
## Hugo Next
|
||||
|
||||
We're not finished with Hugo, but Hugo `0.43` very nicely wraps up the first five years. It started out as a small and fast static site generator. It is now [even faster](https://forestry.io/blog/hugo-vs-jekyll-benchmark/), and now so loaded with features that it has grown out of being just a "static site generator". It is a _framework for building websites_.
|
||||
|
||||
My interest in Hugo started on the Sunday when I moved my blog, [bepsays.com](https://bepsays.com/en/), twice. The second static generator choice of that day, Hugo, was a good match. I remember Steve being very enthusiastic about getting patches with fixes and enhancements, and I was eventually taken over by the simplicity and power of Go, the programming language, and started to implement lots of new features.
|
||||
|
||||
My goal with all of this, if there is one, is to get a single binary with native and really fast implementations of the complete stack I need for web development and content editing. The single binary takes most of the pain out of installation and upgrades (if you stick with the same binary, it will continue to just work for decades).
|
||||
|
||||
**With 0.43, we are almost there.** With that release, it should be possible to set up a Hugo-only project without any additional tools (Gulp, WebPack) for all aspects of website building. There will still be situations where those tools will still be needed, of course, but we will continue to fill the gaps in the feature set.
|
||||
|
||||
Hugo has stuck with the sub-zero versions to signal active development, with a new main release every 5-6 weeks. But we take stability very seriously (breaking things add lots of support work, we don't like that) and most site upgrades are [smooth](https://twitter.com/tmmx/status/1006288444459503616). But we are closing in on the first major stable version.
|
||||
|
||||
|
||||
### The Road to 1.0
|
||||
|
||||
We have some more technical tasks that needs to be done (there is ongoing work to get the page quries into a more consistent state, also a simpler `.GetPage` method), but also some cool new functionality. The following roadmap is taken from memory, and may not be complete, but should be a good indication of what's ahead.
|
||||
|
||||
Pages from "other data sources"
|
||||
: Currently, in addition to Hugo's list pages, every URL must be backed by a content file (Markdown, HTML etc.). This covers most use cases, but we need a flexible way to generate pages from other data sources. Think product catalogues and similar.
|
||||
|
||||
Upgrade Blackfriday to v2
|
||||
: [Blackfriday](https://github.com/russross/blackfriday) is the main content renderer in Hugo. It has been rewritten to a more flexible architecture, which should allow us to fix some of the current shortcomings.
|
||||
|
||||
We should be able to create a better and easier-to-use data structure from the rendered content: Summary, the content after the summary, being able to range over the footnotes and the ToC. Having ToC as a proper data structure also open up a few other potential uses; using it as an index in [Related Content](https://gohugo.io/content-management/related/) would be one example.
|
||||
|
||||
This should also enable us to _do more_ with [Custom Output Formats](/templates/output-formats). It is already very powerful. GettyPubs are using it in [Quire](https://github.com/gettypubs/quire) to build [beautiful multi-platform publications](http://www.getty.edu/publications/digital/digitalpubs.html). But it can be improved. For rendering of content files, you are currently restricted to HTML. It would be great if we could configure alternative renderers per output format, such as LaTeX and EPUB.
|
||||
|
||||
Related to this is also to add a configurable "Markdown URL rewriter", which should make more portable URLs in Markdown, e.g. image links that work both when viewed on GitHub and your published site.
|
||||
|
||||
### The Road to the Future
|
||||
|
||||
These are the items that first comes to mind if you ask me to think even further ahead:
|
||||
|
||||
Dependency manager for Theme Components
|
||||
: In Hugo `0.42` we added [Theme Components](/themes/theme-components/) and Theme Inheritance. With SCSS support in Hugo `0.43`, which also follows the same project/themes precedence order (add `_variables.scss` to your project, configure SASS colour variables in `config.toml`), we have a solid foundation for creating easy to use and extensible themes. But we are missing some infrastructure around this. We have a site with 235+ [themes](https://themes.gohugo.io/)[^themes] listed, but you currently need to do some added work to get the theme up and running for your site. In the Go world, we don't have NPM to use, which is a curse and a blessing, but I have some ideas about building a simple dependency manager into Hugo, modelled after how Go is doing it (`hugo install`). You should be able to configure what theme and theme components you want to use, and Hugo should handle the installation of the correct versions. This should make it easier for the user, but it would also enable community driven and even commercial "theme stores".
|
||||
|
||||
|
||||
{{< imgproc graph-themes Fit "600x400" >}}
|
||||
Number of Hugo themes on themes.gohugo.io in relation to the Hugo release dates.
|
||||
{{< /imgproc >}}
|
||||
|
||||
|
||||
The "New York Times on Hugo" Use Case
|
||||
: There are recurring questions on the support forum from [really big sites](https://discourse.gohugo.io/t/transition-2m-posts-from-wordpress-to-hugo/12704) that want to move to Hugo. There are many [good reasons](https://www.netlify.com/blog/2016/05/18/9-reasons-your-site-should-be-static/) why they want this (security, cost-saving, EU regulations etc.). And while there have been reports about companies building 600 000 pages with Hugo on very powerful hardware, we will have to rethink the build model to make this usable. Keywords are: streaming builds, segmented builds, partial rebuilds. There are other site generators also talking about this. It should be possible, and my instinct tells me that it should be easier to do when your starting point is "really fast". But this is not a small weekend project for me, and I have already talked to several companies about sponsoring this.
|
||||
|
||||
Plugins
|
||||
: A Theme Component could also be called a plugin. But there are several potential plugin hooks into Hugo's build pipeline: Resource transformations, content rendering etc. We will eventually get there, but we should do it without giving up too much of the Hugo speed and simplicity.
|
||||
|
||||
|
||||
## Thanks
|
||||
|
||||
So, thanks to everyone who have contributed to getting Hugo where it is today. It is hard to single out individuals, but a big shout-out to all the Hugo experts and moderators helping out making the [discourse.gohugo.io](https://discourse.gohugo.io/) a very active and possibly one of the best support forums out there.
|
||||
|
||||
And the last shout-out goes to two maintainers who have been there more or less from the start. [@digitalcraftsman](https://github.com/digitalcraftsman/) has been doing a fantastic job keeping the fast growing theme site and [repository](https://github.com/gohugoio/hugoThemes) in pristine condition. I have it on my watch list, but that is just out of curiosity. There are lots of activity, but it runs as clock work. [Anthony Fok](https://github.com/anthonyfok) has contributed with a variety of things but is most notable as the Linux expert on the team. He manages the Debian build and is the one to thank for up-to-date binaries on Debian and Ubuntu.
|
||||
|
||||
One final note: If you have not done so already, please visit [github.com/gohugoio/hugo](https://github.com/gohugoio/hugo) and push the "star button".
|
||||
|
||||
Gopher artwork by [Ashley McNamara](https://github.com/ashleymcnamara/gophers/) (licensed under [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/)). Inspired by [Renee French](https://reneefrench.blogspot.com/).
|
||||
|
||||
[^2]: Numbers from Google Analytics. The Hugo websites are https://discourse.gohugo.io/, https://gohugo.io/ and https://themes.gohugo.io/. It is rumoured that when [Matt Biilman](https://twitter.com/biilmann?lang=en), CEO and Co-founder of Netlify, opened the first power bill after sponsoring Hugo's hosting, said: "Du må lave fis med mig, those Hugo sites have lots of web traffic!"
|
||||
[^sgen]: That was at the time of writing this article. _Next_, a React based static site generator, has momentum and is closing in on Hugo's 2nd place.
|
||||
[^themes]: We pull all the themes from GitHub and build the theme site and 235 demo sites on Netlify in 4 minutes. And that is impressing.
|
||||
|
After Width: | Height: | Size: 378 KiB |
@@ -5,7 +5,7 @@ description: Hugo searches for the layout to use for a given page in a well defi
|
||||
godocref:
|
||||
date: 2017-02-01
|
||||
publishdate: 2017-02-01
|
||||
lastmod: 2017-05-25
|
||||
lastmod: 2017-07-05
|
||||
categories: [templates,fundamentals]
|
||||
keywords: [templates]
|
||||
menu:
|
||||
@@ -32,7 +32,7 @@ Output Format
|
||||
: See [Custom Output Formats](/templates/output-formats). An output format has both a `name` (e.g. `rss`, `amp`, `html`) and a `suffix` (e.g. `xml`, `html`). We prefer matches with both (e.g. `index.amp.html`, but look for less specific templates.
|
||||
|
||||
Language
|
||||
: We will consider a language code in the template name. If the site language is `fr`, `index.fr.amp.html` will win over `index.amp.html`, but we will `index.amp.html` will be chosen before `index.fr.html`.
|
||||
: We will consider a language code in the template name. If the site language is `fr`, `index.fr.amp.html` will win over `index.amp.html`, but `index.amp.html` will be chosen before `index.fr.html`.
|
||||
|
||||
Layout
|
||||
: Can be set in page front matter.
|
||||
|
||||
@@ -25,16 +25,16 @@ theme = ["my-shortcodes", "base-theme", "hyde"]
|
||||
|
||||
You can even nest this, and have the theme component itself include theme components in its own `config.toml` (theme inheritance).[^1]
|
||||
|
||||
The theme definition example above in `config.toml` creates a theme with 3 theme components with presedence from left to right.
|
||||
The theme definition example above in `config.toml` creates a theme with 3 theme components with precedence from left to right.
|
||||
|
||||
So, Hugo will, for any given file, data entry etc., look first in the project, and then in `my-shortcode`, `base-theme` and lastly `hyde`.
|
||||
For any given file, data entry, etc., Hugo will look first in the project and then in `my-shortcode`, `base-theme`, and lastly `hyde`.
|
||||
|
||||
Hugo uses two different algorithms to merge the filesystems, depending on the file type:
|
||||
|
||||
* For `i18n` and `data` files, Hugo merges deeply using the translation id and data key inside the files.
|
||||
* For `static`, `layouts` (templates) and `archetypes` files, these are merged on file level. So the left-most file will be chosen.
|
||||
* For `static`, `layouts` (templates), and `archetypes` files, these are merged on file level. So the left-most file will be chosen.
|
||||
|
||||
The name used in the `theme` definition above must match a folder in `/your-site/themes`, e.g. `/your-site/themes/my-shortcodes`. There are plans to improve on this and get a URL scheme so this can be resolved automatically.
|
||||
The name used in the `theme` definition above must match a folder in `/your-site/themes`, e.g. `/your-site/themes/my-shortcodes`. There are plans to improve on this and get a URL scheme so this can be resolved automatically.
|
||||
|
||||
Also note that a component that is part of a theme can have its own configuration file, e.g. `config.toml`. There are currently some restrictions to what a theme component can configure:
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
date = "2016-10-22"
|
||||
|
||||
[[article]]
|
||||
title = "通过 Gitlab-cl 将 Hugo blog 自动部署至 GitHub <small>(Chinese, Continious integration)</small>"
|
||||
title = "通过 Gitlab-cl 将 Hugo blog 自动部署至 GitHub <small>(Chinese, Continuous integration)</small>"
|
||||
url = "https://zetaoyang.github.io/post/2016/10/17/gitlab-cl.html"
|
||||
author = "Zetao Yang"
|
||||
date = "2016-10-17"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{{ $original := .Page.Resources.GetMatch (printf "%s*" (.Get 0)) }}
|
||||
{{ $original := .Page.Resources.GetMatch (printf "*%s*" (.Get 0)) }}
|
||||
{{ $command := .Get 1 }}
|
||||
{{ $options := .Get 2 }}
|
||||
{{ if eq $command "Fit"}}
|
||||
@@ -11,8 +11,8 @@
|
||||
{{ errorf "Invalid image processing command: Must be one of Fit, Fill or Resize."}}
|
||||
{{ end }}
|
||||
{{ $image := .Scratch.Get "image" }}
|
||||
<figure style="width: {{ add $image.Width 3 }}px; padding: 3px; background-color: #cccc">
|
||||
<img src="{{ $image.RelPermalink }}" width="{{ $image.Width }}" height="{{ $image.Height }}">
|
||||
<figure style="padding: 0.25rem; margin: 2rem 0; background-color: #cccc">
|
||||
<img style="max-width: 100%; height: auto;" src="{{ $image.RelPermalink }}" width="{{ $image.Width }}" height="{{ $image.Height }}">
|
||||
<figcaption>
|
||||
<small>
|
||||
{{ with .Inner }}
|
||||
|
||||
@@ -3,7 +3,7 @@ publish = "public"
|
||||
command = "hugo"
|
||||
|
||||
[context.production.environment]
|
||||
HUGO_VERSION = "0.41"
|
||||
HUGO_VERSION = "0.42.2"
|
||||
HUGO_ENV = "production"
|
||||
HUGO_ENABLEGITINFO = "true"
|
||||
|
||||
@@ -11,20 +11,20 @@ HUGO_ENABLEGITINFO = "true"
|
||||
command = "hugo --enableGitInfo"
|
||||
|
||||
[context.split1.environment]
|
||||
HUGO_VERSION = "0.41"
|
||||
HUGO_VERSION = "0.42.2"
|
||||
HUGO_ENV = "production"
|
||||
|
||||
[context.deploy-preview]
|
||||
command = "hugo -b $DEPLOY_PRIME_URL"
|
||||
command = "hugo --buildFuture -b $DEPLOY_PRIME_URL"
|
||||
|
||||
[context.deploy-preview.environment]
|
||||
HUGO_VERSION = "0.41"
|
||||
HUGO_VERSION = "0.42.2"
|
||||
|
||||
[context.branch-deploy]
|
||||
command = "hugo -b $DEPLOY_PRIME_URL"
|
||||
|
||||
[context.branch-deploy.environment]
|
||||
HUGO_VERSION = "0.41"
|
||||
HUGO_VERSION = "0.42.2"
|
||||
|
||||
[context.next.environment]
|
||||
HUGO_ENABLEGITINFO = "true"
|
||||
|
||||
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 102 KiB |
@@ -37,7 +37,7 @@ function toggleTabs(event) {
|
||||
for (i = 0; i < allTabs.length; i++) {
|
||||
allTabs[i].addEventListener("click", toggleTabs)
|
||||
}
|
||||
// Upon page load, if user has a prefered language in its localStorage, tabs are set to it.
|
||||
// Upon page load, if user has a preferred language in its localStorage, tabs are set to it.
|
||||
if(window.localStorage.getItem('configLangPref')) {
|
||||
toggleTabs(window.localStorage.getItem('configLangPref'))
|
||||
}
|
||||
|
||||
@@ -399,7 +399,7 @@ function toggleTabs(event) {
|
||||
for (i = 0; i < allTabs.length; i++) {
|
||||
allTabs[i].addEventListener("click", toggleTabs);
|
||||
}
|
||||
// Upon page load, if user has a prefered language in its localStorage, tabs are set to it.
|
||||
// Upon page load, if user has a preferred language in its localStorage, tabs are set to it.
|
||||
if (window.localStorage.getItem('configLangPref')) {
|
||||
toggleTabs(window.localStorage.getItem('configLangPref'));
|
||||
}
|
||||
@@ -2083,7 +2083,7 @@ function defaultClearTimeout () {
|
||||
} ())
|
||||
function runTimeout(fun) {
|
||||
if (cachedSetTimeout === setTimeout) {
|
||||
//normal enviroments in sane situations
|
||||
//normal environments in sane situations
|
||||
return setTimeout(fun, 0);
|
||||
}
|
||||
// if setTimeout wasn't available but was latter defined
|
||||
@@ -2108,7 +2108,7 @@ function runTimeout(fun) {
|
||||
}
|
||||
function runClearTimeout(marker) {
|
||||
if (cachedClearTimeout === clearTimeout) {
|
||||
//normal enviroments in sane situations
|
||||
//normal environments in sane situations
|
||||
return clearTimeout(marker);
|
||||
}
|
||||
// if clearTimeout wasn't available but was latter defined
|
||||
@@ -2190,7 +2190,7 @@ process.nextTick = function (fun) {
|
||||
}
|
||||
};
|
||||
|
||||
// v8 likes predictible objects
|
||||
// v8 likes predictable objects
|
||||
function Item(fun, array) {
|
||||
this.fun = fun;
|
||||
this.array = array;
|
||||
@@ -4181,7 +4181,7 @@ AlgoliaSearchCore.prototype._jsonRequest = function(initialOpts) {
|
||||
return client._promise.reject(err);
|
||||
}
|
||||
|
||||
// When a timeout occured, retry by raising timeout
|
||||
// When a timeout occurred, retry by raising timeout
|
||||
if (err instanceof errors.RequestTimeout) {
|
||||
return retryRequestWithHigherTimeout();
|
||||
}
|
||||
@@ -4892,7 +4892,7 @@ IndexCore.prototype._search = function(params, url, callback, additionalUA) {
|
||||
* @param attrs (optional) if set, contains the array of attribute names to retrieve
|
||||
* @param callback (optional) the result callback called with two arguments
|
||||
* error: null or Error('message')
|
||||
* content: the object to retrieve or the error message if a failure occured
|
||||
* content: the object to retrieve or the error message if a failure occurred
|
||||
*/
|
||||
IndexCore.prototype.getObject = function(objectID, attrs, callback) {
|
||||
var indexObj = this;
|
||||
@@ -6701,7 +6701,7 @@ function Typeahead(o) {
|
||||
if (_.isMsie() && ($menu[0] === active || $menu[0].contains(active))) {
|
||||
$e.preventDefault();
|
||||
// stop immediate in order to prevent Input#_onBlur from
|
||||
// getting exectued
|
||||
// getting executed
|
||||
$e.stopImmediatePropagation();
|
||||
_.defer(function() { $input.focus(); });
|
||||
}
|
||||
@@ -10431,7 +10431,7 @@ while (++i < len) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// v8 likes predictible objects
|
||||
// v8 likes predictable objects
|
||||
function Item(fun, array) {
|
||||
this.fun = fun;
|
||||
this.array = array;
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
project_name: hugo_extended
|
||||
builds:
|
||||
- binary: hugo
|
||||
ldflags:
|
||||
- -s -w -X github.com/gohugoio/hugo/hugolib.BuildDate={{.Date}}
|
||||
- "-extldflags '-static'"
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- CC=x86_64-w64-mingw32-gcc
|
||||
- CXX=x86_64-w64-mingw32-g++
|
||||
- CGO_LDFLAGS="-static"
|
||||
flags:
|
||||
- -tags
|
||||
- extended
|
||||
goos:
|
||||
- windows
|
||||
goarch:
|
||||
- amd64
|
||||
- binary: hugo
|
||||
ldflags: -s -w -X github.com/gohugoio/hugo/hugolib.BuildDate={{.Date}}
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- CC=o64-clang
|
||||
- CXX=o64-clang++
|
||||
flags:
|
||||
- -tags
|
||||
- extended
|
||||
goos:
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
- binary: hugo
|
||||
ldflags: -s -w -X github.com/gohugoio/hugo/hugolib.BuildDate={{.Date}}
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
flags:
|
||||
- -tags
|
||||
- extended
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
nfpm:
|
||||
formats:
|
||||
- deb
|
||||
vendor: "gohugo.io"
|
||||
homepage: "https://gohugo.io/"
|
||||
maintainer: "Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>"
|
||||
description: "A Fast and Flexible Static Site Generator built with love in GoLang."
|
||||
license: "Apache 2.0"
|
||||
replacements:
|
||||
amd64: 64bit
|
||||
386: 32bit
|
||||
arm: ARM
|
||||
arm64: ARM64
|
||||
darwin: macOS
|
||||
linux: Linux
|
||||
windows: Windows
|
||||
openbsd: OpenBSD
|
||||
netbsd: NetBSD
|
||||
freebsd: FreeBSD
|
||||
dragonfly: DragonFlyBSD
|
||||
archive:
|
||||
format: tar.gz
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
name_template: "{{.ProjectName}}_{{.Version}}_{{.Os}}-{{.Arch}}"
|
||||
replacements:
|
||||
amd64: 64bit
|
||||
386: 32bit
|
||||
arm: ARM
|
||||
arm64: ARM64
|
||||
darwin: macOS
|
||||
linux: Linux
|
||||
windows: Windows
|
||||
openbsd: OpenBSD
|
||||
netbsd: NetBSD
|
||||
freebsd: FreeBSD
|
||||
dragonfly: DragonFlyBSD
|
||||
files:
|
||||
- README.md
|
||||
- LICENSE
|
||||
release:
|
||||
draft: true
|
||||
@@ -356,7 +356,7 @@ func MD5String(f string) string {
|
||||
// MD5FromFileFast creates a MD5 hash from the given file. It only reads parts of
|
||||
// the file for speed, so don't use it if the files are very subtly different.
|
||||
// It will not close the file.
|
||||
func MD5FromFileFast(f afero.File) (string, error) {
|
||||
func MD5FromFileFast(r io.ReadSeeker) (string, error) {
|
||||
const (
|
||||
// Do not change once set in stone!
|
||||
maxChunks = 8
|
||||
@@ -369,7 +369,7 @@ func MD5FromFileFast(f afero.File) (string, error) {
|
||||
|
||||
for i := 0; i < maxChunks; i++ {
|
||||
if i > 0 {
|
||||
_, err := f.Seek(seek, 0)
|
||||
_, err := r.Seek(seek, 0)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
@@ -378,7 +378,7 @@ func MD5FromFileFast(f afero.File) (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
_, err := io.ReadAtLeast(f, buff, peekSize)
|
||||
_, err := io.ReadAtLeast(r, buff, peekSize)
|
||||
if err != nil {
|
||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||
h.Write(buff)
|
||||
|
||||
@@ -123,7 +123,7 @@ func (v HugoVersion) NextPatchLevel(level int) HugoVersion {
|
||||
// CurrentHugoVersion represents the current build version.
|
||||
// This should be the only one.
|
||||
var CurrentHugoVersion = HugoVersion{
|
||||
Number: 0.42,
|
||||
Number: 0.44,
|
||||
PatchLevel: 0,
|
||||
Suffix: "",
|
||||
}
|
||||
|
||||
@@ -90,6 +90,11 @@ func (p *PathSpec) MakePathSanitized(s string) string {
|
||||
return strings.ToLower(p.MakePath(s))
|
||||
}
|
||||
|
||||
// ToSlashTrimLeading is just a filepath.ToSlaas with an added / prefix trimmer.
|
||||
func ToSlashTrimLeading(s string) string {
|
||||
return strings.TrimPrefix(filepath.ToSlash(s), "/")
|
||||
}
|
||||
|
||||
// MakeTitle converts the path given to a suitable title, trimming whitespace
|
||||
// and replacing hyphens with whitespace.
|
||||
func MakeTitle(inpath string) string {
|
||||
@@ -222,12 +227,22 @@ func GetDottedRelativePath(inPath string) string {
|
||||
return dottedPath
|
||||
}
|
||||
|
||||
// ExtNoDelimiter takes a path and returns the extension, excluding the delmiter, i.e. "md".
|
||||
func ExtNoDelimiter(in string) string {
|
||||
return strings.TrimPrefix(Ext(in), ".")
|
||||
}
|
||||
|
||||
// Ext takes a path and returns the extension, including the delmiter, i.e. ".md".
|
||||
func Ext(in string) string {
|
||||
_, ext := fileAndExt(in, fpb)
|
||||
return ext
|
||||
}
|
||||
|
||||
// PathAndExt is the same as FileAndExt, but it uses the path package.
|
||||
func PathAndExt(in string) (string, string) {
|
||||
return fileAndExt(in, pb)
|
||||
}
|
||||
|
||||
// FileAndExt takes a path and returns the file and extension separated,
|
||||
// the extension including the delmiter, i.e. ".md".
|
||||
func FileAndExt(in string) (string, string) {
|
||||
|
||||
@@ -78,6 +78,9 @@ func TestMakePathSanitized(t *testing.T) {
|
||||
v.Set("dataDir", "data")
|
||||
v.Set("i18nDir", "i18n")
|
||||
v.Set("layoutDir", "layouts")
|
||||
v.Set("assetDir", "assets")
|
||||
v.Set("resourceDir", "resources")
|
||||
v.Set("publishDir", "public")
|
||||
v.Set("archetypeDir", "archetypes")
|
||||
|
||||
l := langs.NewDefaultLanguage(v)
|
||||
@@ -475,6 +478,7 @@ func createTempDirWithNonZeroLengthFiles() (string, error) {
|
||||
return "", fileErr
|
||||
}
|
||||
byteString := []byte("byteString")
|
||||
|
||||
fileErr = ioutil.WriteFile(f.Name(), byteString, 0644)
|
||||
if fileErr != nil {
|
||||
// delete the file
|
||||
@@ -585,6 +589,11 @@ func TestAbsPathify(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestExtNoDelimiter(t *testing.T) {
|
||||
assert := require.New(t)
|
||||
assert.Equal("json", ExtNoDelimiter(filepath.FromSlash("/my/data.json")))
|
||||
}
|
||||
|
||||
func TestFilename(t *testing.T) {
|
||||
type test struct {
|
||||
input, expected string
|
||||
|
||||
@@ -38,6 +38,9 @@ func newTestCfg() *viper.Viper {
|
||||
v.Set("dataDir", "data")
|
||||
v.Set("i18nDir", "i18n")
|
||||
v.Set("layoutDir", "layouts")
|
||||
v.Set("assetDir", "assets")
|
||||
v.Set("resourceDir", "resources")
|
||||
v.Set("publishDir", "public")
|
||||
v.Set("archetypeDir", "archetypes")
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright 2018 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package hugofs
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
// RealFilenameInfo is a thin wrapper around os.FileInfo adding the real filename.
|
||||
type RealFilenameInfo interface {
|
||||
os.FileInfo
|
||||
|
||||
// This is the real filename to the file in the underlying filesystem.
|
||||
RealFilename() string
|
||||
}
|
||||
|
||||
type realFilenameInfo struct {
|
||||
os.FileInfo
|
||||
realFilename string
|
||||
}
|
||||
|
||||
func (f *realFilenameInfo) RealFilename() string {
|
||||
return f.realFilename
|
||||
}
|
||||
|
||||
func NewBasePathRealFilenameFs(base *afero.BasePathFs) *BasePathRealFilenameFs {
|
||||
return &BasePathRealFilenameFs{BasePathFs: base}
|
||||
}
|
||||
|
||||
// This is a thin wrapper around afero.BasePathFs that provides the real filename
|
||||
// in Stat and LstatIfPossible.
|
||||
type BasePathRealFilenameFs struct {
|
||||
*afero.BasePathFs
|
||||
}
|
||||
|
||||
func (b *BasePathRealFilenameFs) Stat(name string) (os.FileInfo, error) {
|
||||
fi, err := b.BasePathFs.Stat(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, ok := fi.(RealFilenameInfo); ok {
|
||||
return fi, nil
|
||||
}
|
||||
|
||||
filename, err := b.RealPath(name)
|
||||
if err != nil {
|
||||
return nil, &os.PathError{Op: "stat", Path: name, Err: err}
|
||||
}
|
||||
|
||||
return &realFilenameInfo{FileInfo: fi, realFilename: filename}, nil
|
||||
}
|
||||
|
||||
func (b *BasePathRealFilenameFs) LstatIfPossible(name string) (os.FileInfo, bool, error) {
|
||||
|
||||
fi, ok, err := b.BasePathFs.LstatIfPossible(name)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if _, ok := fi.(RealFilenameInfo); ok {
|
||||
return fi, ok, nil
|
||||
}
|
||||
|
||||
filename, err := b.RealPath(name)
|
||||
if err != nil {
|
||||
return nil, false, &os.PathError{Op: "lstat", Path: name, Err: err}
|
||||
}
|
||||
|
||||
return &realFilenameInfo{FileInfo: fi, realFilename: filename}, ok, nil
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// Copyright 2018 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package hugofs
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"hash"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
var (
|
||||
_ afero.Fs = (*md5HashingFs)(nil)
|
||||
)
|
||||
|
||||
// FileHashReceiver will receive the filename an the content's MD5 sum on file close.
|
||||
type FileHashReceiver interface {
|
||||
OnFileClose(name, md5sum string)
|
||||
}
|
||||
|
||||
type md5HashingFs struct {
|
||||
afero.Fs
|
||||
hashReceiver FileHashReceiver
|
||||
}
|
||||
|
||||
// NewHashingFs creates a new filesystem that will receive MD5 checksums of
|
||||
// any written file content on Close. Note that this is probably not a good
|
||||
// idea for "full build" situations, but when doing fast render mode, the amount
|
||||
// of files published is low, and it would be really nice to know exactly which
|
||||
// of these files where actually changed.
|
||||
// Note that this will only work for file operations that use the io.Writer
|
||||
// to write content to file, but that is fine for the "publish content" use case.
|
||||
func NewHashingFs(delegate afero.Fs, hashReceiver FileHashReceiver) afero.Fs {
|
||||
return &md5HashingFs{Fs: delegate, hashReceiver: hashReceiver}
|
||||
}
|
||||
|
||||
func (fs *md5HashingFs) Create(name string) (afero.File, error) {
|
||||
f, err := fs.Fs.Create(name)
|
||||
if err == nil {
|
||||
f = fs.wrapFile(f)
|
||||
}
|
||||
return f, err
|
||||
}
|
||||
|
||||
func (fs *md5HashingFs) OpenFile(name string, flag int, perm os.FileMode) (afero.File, error) {
|
||||
f, err := fs.Fs.OpenFile(name, flag, perm)
|
||||
if err == nil && isWrite(flag) {
|
||||
f = fs.wrapFile(f)
|
||||
}
|
||||
return f, err
|
||||
}
|
||||
|
||||
func (fs *md5HashingFs) wrapFile(f afero.File) afero.File {
|
||||
return &hashingFile{File: f, h: md5.New(), hashReceiver: fs.hashReceiver}
|
||||
}
|
||||
|
||||
func isWrite(flag int) bool {
|
||||
return flag&os.O_RDWR != 0 || flag&os.O_WRONLY != 0
|
||||
}
|
||||
|
||||
func (fs *md5HashingFs) Name() string {
|
||||
return "md5HashingFs"
|
||||
}
|
||||
|
||||
type hashingFile struct {
|
||||
hashReceiver FileHashReceiver
|
||||
h hash.Hash
|
||||
afero.File
|
||||
}
|
||||
|
||||
func (h *hashingFile) Write(p []byte) (n int, err error) {
|
||||
n, err = h.File.Write(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return h.h.Write(p)
|
||||
}
|
||||
|
||||
func (h *hashingFile) Close() error {
|
||||
sum := hex.EncodeToString(h.h.Sum(nil))
|
||||
h.hashReceiver.OnFileClose(h.Name(), sum)
|
||||
return h.File.Close()
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2018 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package hugofs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type testHashReceiver struct {
|
||||
sum string
|
||||
name string
|
||||
}
|
||||
|
||||
func (t *testHashReceiver) OnFileClose(name, md5hash string) {
|
||||
t.name = name
|
||||
t.sum = md5hash
|
||||
}
|
||||
|
||||
func TestHashingFs(t *testing.T) {
|
||||
assert := require.New(t)
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
observer := &testHashReceiver{}
|
||||
ofs := NewHashingFs(fs, observer)
|
||||
|
||||
f, err := ofs.Create("hashme")
|
||||
assert.NoError(err)
|
||||
_, err = f.Write([]byte("content"))
|
||||
assert.NoError(err)
|
||||
assert.NoError(f.Close())
|
||||
assert.Equal("9a0364b9e99bb480dd25e1f0284c8555", observer.sum)
|
||||
assert.Equal("hashme", observer.name)
|
||||
|
||||
f, err = ofs.Create("nowrites")
|
||||
assert.NoError(err)
|
||||
assert.NoError(f.Close())
|
||||
assert.Equal("d41d8cd98f00b204e9800998ecf8427e", observer.sum)
|
||||
|
||||
}
|
||||
@@ -59,13 +59,14 @@ func (a aliasHandler) renderAlias(isXHTML bool, permalink string, page *Page) (i
|
||||
t = "alias-xhtml"
|
||||
}
|
||||
|
||||
var templ *tpl.TemplateAdapter
|
||||
var templ tpl.Template
|
||||
var found bool
|
||||
|
||||
if a.t != nil {
|
||||
templ = a.t.Lookup("alias.html")
|
||||
templ, found = a.t.Lookup("alias.html")
|
||||
}
|
||||
|
||||
if templ == nil {
|
||||
if !found {
|
||||
def := defaultAliasTemplates.Lookup(t)
|
||||
if def != nil {
|
||||
templ = &tpl.TemplateAdapter{Template: def}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2018 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -411,6 +411,7 @@ func loadDefaultSettingsFor(v *viper.Viper) error {
|
||||
v.SetDefault("metaDataFormat", "toml")
|
||||
v.SetDefault("contentDir", "content")
|
||||
v.SetDefault("layoutDir", "layouts")
|
||||
v.SetDefault("assetDir", "assets")
|
||||
v.SetDefault("staticDir", "static")
|
||||
v.SetDefault("resourceDir", "resources")
|
||||
v.SetDefault("archetypeDir", "archetypes")
|
||||
|
||||
@@ -219,8 +219,11 @@ map[string]interface {}{
|
||||
"mediatype": Type{
|
||||
MainType: "text",
|
||||
SubType: "m1",
|
||||
Suffix: "m1main",
|
||||
OldSuffix: "m1main",
|
||||
Delimiter: ".",
|
||||
Suffixes: []string{
|
||||
"m1main",
|
||||
},
|
||||
},
|
||||
},
|
||||
"o2": map[string]interface {}{
|
||||
@@ -228,8 +231,11 @@ map[string]interface {}{
|
||||
"mediatype": Type{
|
||||
MainType: "text",
|
||||
SubType: "m2",
|
||||
Suffix: "m2theme",
|
||||
OldSuffix: "m2theme",
|
||||
Delimiter: ".",
|
||||
Suffixes: []string{
|
||||
"m2theme",
|
||||
},
|
||||
},
|
||||
},
|
||||
}`, got["outputformats"])
|
||||
|
||||
@@ -28,7 +28,6 @@ import (
|
||||
|
||||
"fmt"
|
||||
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/hugolib/paths"
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
"github.com/spf13/afero"
|
||||
@@ -45,20 +44,10 @@ var filePathSeparator = string(filepath.Separator)
|
||||
// to underline that even if they can be composites, they all have a base path set to a specific
|
||||
// resource folder, e.g "/my-project/content". So, no absolute filenames needed.
|
||||
type BaseFs struct {
|
||||
// TODO(bep) make this go away
|
||||
AbsContentDirs []types.KeyValueStr
|
||||
|
||||
// The filesystem used to capture content. This can be a composite and
|
||||
// language aware file system.
|
||||
ContentFs afero.Fs
|
||||
|
||||
// SourceFilesystems contains the different source file systems.
|
||||
*SourceFilesystems
|
||||
|
||||
// The filesystem used to store resources (processed images etc.).
|
||||
// This usually maps to /my-project/resources.
|
||||
ResourcesFs afero.Fs
|
||||
|
||||
// The filesystem used to publish the rendered site.
|
||||
// This usually maps to /my-project/public.
|
||||
PublishFs afero.Fs
|
||||
@@ -71,35 +60,31 @@ type BaseFs struct {
|
||||
|
||||
// RelContentDir tries to create a path relative to the content root from
|
||||
// the given filename. The return value is the path and language code.
|
||||
func (b *BaseFs) RelContentDir(filename string) (string, string) {
|
||||
for _, dir := range b.AbsContentDirs {
|
||||
if strings.HasPrefix(filename, dir.Value) {
|
||||
rel := strings.TrimPrefix(filename, dir.Value)
|
||||
return strings.TrimPrefix(rel, filePathSeparator), dir.Key
|
||||
func (b *BaseFs) RelContentDir(filename string) string {
|
||||
for _, dirname := range b.SourceFilesystems.Content.Dirnames {
|
||||
if strings.HasPrefix(filename, dirname) {
|
||||
rel := strings.TrimPrefix(filename, dirname)
|
||||
return strings.TrimPrefix(rel, filePathSeparator)
|
||||
}
|
||||
}
|
||||
// Either not a content dir or already relative.
|
||||
return filename, ""
|
||||
}
|
||||
|
||||
// IsContent returns whether the given filename is in the content filesystem.
|
||||
func (b *BaseFs) IsContent(filename string) bool {
|
||||
for _, dir := range b.AbsContentDirs {
|
||||
if strings.HasPrefix(filename, dir.Value) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return filename
|
||||
}
|
||||
|
||||
// SourceFilesystems contains the different source file systems. These can be
|
||||
// composite file systems (theme and project etc.), and they have all root
|
||||
// set to the source type the provides: data, i18n, static, layouts.
|
||||
type SourceFilesystems struct {
|
||||
Content *SourceFilesystem
|
||||
Data *SourceFilesystem
|
||||
I18n *SourceFilesystem
|
||||
Layouts *SourceFilesystem
|
||||
Archetypes *SourceFilesystem
|
||||
Assets *SourceFilesystem
|
||||
Resources *SourceFilesystem
|
||||
|
||||
// This is a unified read-only view of the project's and themes' workdir.
|
||||
Work *SourceFilesystem
|
||||
|
||||
// When in multihost we have one static filesystem per language. The sync
|
||||
// static files is currently done outside of the Hugo build (where there is
|
||||
@@ -112,8 +97,14 @@ type SourceFilesystems struct {
|
||||
// i18n, layouts, static) and additional metadata to be able to use that filesystem
|
||||
// in server mode.
|
||||
type SourceFilesystem struct {
|
||||
// This is a virtual composite filesystem. It expects path relative to a context.
|
||||
Fs afero.Fs
|
||||
|
||||
// This is the base source filesystem. In real Hugo, this will be the OS filesystem.
|
||||
// Use this if you need to resolve items in Dirnames below.
|
||||
SourceFs afero.Fs
|
||||
|
||||
// Dirnames is absolute filenames to the directories in this filesystem.
|
||||
Dirnames []string
|
||||
|
||||
// When syncing a source folder to the target (e.g. /public), this may
|
||||
@@ -122,6 +113,50 @@ type SourceFilesystem struct {
|
||||
PublishFolder string
|
||||
}
|
||||
|
||||
// ContentStaticAssetFs will create a new composite filesystem from the content,
|
||||
// static, and asset filesystems. The site language is needed to pick the correct static filesystem.
|
||||
// The order is content, static and then assets.
|
||||
// TODO(bep) check usage
|
||||
func (s SourceFilesystems) ContentStaticAssetFs(lang string) afero.Fs {
|
||||
staticFs := s.StaticFs(lang)
|
||||
|
||||
base := afero.NewCopyOnWriteFs(s.Assets.Fs, staticFs)
|
||||
return afero.NewCopyOnWriteFs(base, s.Content.Fs)
|
||||
|
||||
}
|
||||
|
||||
// StaticFs returns the static filesystem for the given language.
|
||||
// This can be a composite filesystem.
|
||||
func (s SourceFilesystems) StaticFs(lang string) afero.Fs {
|
||||
var staticFs afero.Fs = hugofs.NoOpFs
|
||||
|
||||
if fs, ok := s.Static[lang]; ok {
|
||||
staticFs = fs.Fs
|
||||
} else if fs, ok := s.Static[""]; ok {
|
||||
staticFs = fs.Fs
|
||||
}
|
||||
|
||||
return staticFs
|
||||
}
|
||||
|
||||
// StatResource looks for a resource in these filesystems in order: static, assets and finally content.
|
||||
// If found in any of them, it returns FileInfo and the relevant filesystem.
|
||||
// Any non os.IsNotExist error will be returned.
|
||||
// An os.IsNotExist error wil be returned only if all filesystems return such an error.
|
||||
// Note that if we only wanted to find the file, we could create a composite Afero fs,
|
||||
// but we also need to know which filesystem root it lives in.
|
||||
func (s SourceFilesystems) StatResource(lang, filename string) (fi os.FileInfo, fs afero.Fs, err error) {
|
||||
for _, fsToCheck := range []afero.Fs{s.StaticFs(lang), s.Assets.Fs, s.Content.Fs} {
|
||||
fs = fsToCheck
|
||||
fi, err = fs.Stat(filename)
|
||||
if err == nil || !os.IsNotExist(err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
// Not found.
|
||||
return
|
||||
}
|
||||
|
||||
// IsStatic returns true if the given filename is a member of one of the static
|
||||
// filesystems.
|
||||
func (s SourceFilesystems) IsStatic(filename string) bool {
|
||||
@@ -133,6 +168,11 @@ func (s SourceFilesystems) IsStatic(filename string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsContent returns true if the given filename is a member of the content filesystem.
|
||||
func (s SourceFilesystems) IsContent(filename string) bool {
|
||||
return s.Content.Contains(filename)
|
||||
}
|
||||
|
||||
// IsLayout returns true if the given filename is a member of the layouts filesystem.
|
||||
func (s SourceFilesystems) IsLayout(filename string) bool {
|
||||
return s.Layouts.Contains(filename)
|
||||
@@ -143,6 +183,11 @@ func (s SourceFilesystems) IsData(filename string) bool {
|
||||
return s.Data.Contains(filename)
|
||||
}
|
||||
|
||||
// IsAsset returns true if the given filename is a member of the data filesystem.
|
||||
func (s SourceFilesystems) IsAsset(filename string) bool {
|
||||
return s.Assets.Contains(filename)
|
||||
}
|
||||
|
||||
// IsI18n returns true if the given filename is a member of the i18n filesystem.
|
||||
func (s SourceFilesystems) IsI18n(filename string) bool {
|
||||
return s.I18n.Contains(filename)
|
||||
@@ -171,6 +216,18 @@ func (d *SourceFilesystem) MakePathRelative(filename string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (d *SourceFilesystem) RealFilename(rel string) string {
|
||||
fi, err := d.Fs.Stat(rel)
|
||||
if err != nil {
|
||||
return rel
|
||||
}
|
||||
if realfi, ok := fi.(hugofs.RealFilenameInfo); ok {
|
||||
return realfi.RealFilename()
|
||||
}
|
||||
|
||||
return rel
|
||||
}
|
||||
|
||||
// Contains returns whether the given filename is a member of the current filesystem.
|
||||
func (d *SourceFilesystem) Contains(filename string) bool {
|
||||
for _, dir := range d.Dirnames {
|
||||
@@ -181,6 +238,20 @@ func (d *SourceFilesystem) Contains(filename string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// RealDirs gets a list of absolute paths to directories starting from the given
|
||||
// path.
|
||||
func (d *SourceFilesystem) RealDirs(from string) []string {
|
||||
var dirnames []string
|
||||
for _, dir := range d.Dirnames {
|
||||
dirname := filepath.Join(dir, from)
|
||||
|
||||
if _, err := hugofs.Os.Stat(dirname); err == nil {
|
||||
dirnames = append(dirnames, dirname)
|
||||
}
|
||||
}
|
||||
return dirnames
|
||||
}
|
||||
|
||||
// WithBaseFs allows reuse of some potentially expensive to create parts that remain
|
||||
// the same across sites/languages.
|
||||
func WithBaseFs(b *BaseFs) func(*BaseFs) error {
|
||||
@@ -191,11 +262,15 @@ func WithBaseFs(b *BaseFs) func(*BaseFs) error {
|
||||
}
|
||||
}
|
||||
|
||||
func newRealBase(base afero.Fs) afero.Fs {
|
||||
return hugofs.NewBasePathRealFilenameFs(base.(*afero.BasePathFs))
|
||||
|
||||
}
|
||||
|
||||
// NewBase builds the filesystems used by Hugo given the paths and options provided.NewBase
|
||||
func NewBase(p *paths.Paths, options ...func(*BaseFs) error) (*BaseFs, error) {
|
||||
fs := p.Fs
|
||||
|
||||
resourcesFs := afero.NewBasePathFs(fs.Source, p.AbsResourcesDir)
|
||||
publishFs := afero.NewBasePathFs(fs.Destination, p.AbsPublishDir)
|
||||
|
||||
contentFs, absContentDirs, err := createContentFs(fs.Source, p.WorkingDir, p.DefaultContentLanguage, p.Languages)
|
||||
@@ -209,17 +284,14 @@ func NewBase(p *paths.Paths, options ...func(*BaseFs) error) (*BaseFs, error) {
|
||||
if i == j {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(d1.Value, d2.Value) || strings.HasPrefix(d2.Value, d1.Value) {
|
||||
if strings.HasPrefix(d1, d2) || strings.HasPrefix(d2, d1) {
|
||||
return nil, fmt.Errorf("found overlapping content dirs (%q and %q)", d1, d2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b := &BaseFs{
|
||||
AbsContentDirs: absContentDirs,
|
||||
ContentFs: contentFs,
|
||||
ResourcesFs: resourcesFs,
|
||||
PublishFs: publishFs,
|
||||
PublishFs: publishFs,
|
||||
}
|
||||
|
||||
for _, opt := range options {
|
||||
@@ -234,6 +306,12 @@ func NewBase(p *paths.Paths, options ...func(*BaseFs) error) (*BaseFs, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sourceFilesystems.Content = &SourceFilesystem{
|
||||
SourceFs: fs.Source,
|
||||
Fs: contentFs,
|
||||
Dirnames: absContentDirs,
|
||||
}
|
||||
|
||||
b.SourceFilesystems = sourceFilesystems
|
||||
b.themeFs = builder.themeFs
|
||||
b.AbsThemeDirs = builder.absThemeDirs
|
||||
@@ -281,18 +359,39 @@ func (b *sourceFilesystemsBuilder) Build() (*SourceFilesystems, error) {
|
||||
}
|
||||
b.result.I18n = sfs
|
||||
|
||||
sfs, err = b.createFs("layoutDir", "layouts")
|
||||
sfs, err = b.createFs(false, true, "layoutDir", "layouts")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.result.Layouts = sfs
|
||||
|
||||
sfs, err = b.createFs("archetypeDir", "archetypes")
|
||||
sfs, err = b.createFs(false, true, "archetypeDir", "archetypes")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.result.Archetypes = sfs
|
||||
|
||||
sfs, err = b.createFs(false, true, "assetDir", "assets")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.result.Assets = sfs
|
||||
|
||||
sfs, err = b.createFs(true, false, "resourceDir", "resources")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b.result.Resources = sfs
|
||||
|
||||
err = b.createStaticFs()
|
||||
|
||||
sfs, err = b.createFs(false, true, "", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.result.Work = sfs
|
||||
|
||||
err = b.createStaticFs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -301,23 +400,38 @@ func (b *sourceFilesystemsBuilder) Build() (*SourceFilesystems, error) {
|
||||
return b.result, nil
|
||||
}
|
||||
|
||||
func (b *sourceFilesystemsBuilder) createFs(dirKey, themeFolder string) (*SourceFilesystem, error) {
|
||||
s := &SourceFilesystem{}
|
||||
dir := b.p.Cfg.GetString(dirKey)
|
||||
if dir == "" {
|
||||
return s, fmt.Errorf("config %q not set", dirKey)
|
||||
func (b *sourceFilesystemsBuilder) createFs(
|
||||
mkdir bool,
|
||||
readOnly bool,
|
||||
dirKey, themeFolder string) (*SourceFilesystem, error) {
|
||||
s := &SourceFilesystem{
|
||||
SourceFs: b.p.Fs.Source,
|
||||
}
|
||||
var dir string
|
||||
if dirKey != "" {
|
||||
dir = b.p.Cfg.GetString(dirKey)
|
||||
if dir == "" {
|
||||
return s, fmt.Errorf("config %q not set", dirKey)
|
||||
}
|
||||
}
|
||||
|
||||
var fs afero.Fs
|
||||
|
||||
absDir := b.p.AbsPathify(dir)
|
||||
if b.existsInSource(absDir) {
|
||||
fs = afero.NewBasePathFs(b.p.Fs.Source, absDir)
|
||||
existsInSource := b.existsInSource(absDir)
|
||||
if !existsInSource && mkdir {
|
||||
// We really need this directory. Make it.
|
||||
if err := b.p.Fs.Source.MkdirAll(absDir, 0777); err == nil {
|
||||
existsInSource = true
|
||||
}
|
||||
}
|
||||
if existsInSource {
|
||||
fs = newRealBase(afero.NewBasePathFs(b.p.Fs.Source, absDir))
|
||||
s.Dirnames = []string{absDir}
|
||||
}
|
||||
|
||||
if b.hasTheme {
|
||||
themeFolderFs := afero.NewBasePathFs(b.themeFs, themeFolder)
|
||||
themeFolderFs := newRealBase(afero.NewBasePathFs(b.themeFs, themeFolder))
|
||||
if fs == nil {
|
||||
fs = themeFolderFs
|
||||
} else {
|
||||
@@ -334,8 +448,10 @@ func (b *sourceFilesystemsBuilder) createFs(dirKey, themeFolder string) (*Source
|
||||
|
||||
if fs == nil {
|
||||
s.Fs = hugofs.NoOpFs
|
||||
} else {
|
||||
} else if readOnly {
|
||||
s.Fs = afero.NewReadOnlyFs(fs)
|
||||
} else {
|
||||
s.Fs = fs
|
||||
}
|
||||
|
||||
return s, nil
|
||||
@@ -344,7 +460,9 @@ func (b *sourceFilesystemsBuilder) createFs(dirKey, themeFolder string) (*Source
|
||||
// Used for data, i18n -- we cannot use overlay filsesystems for those, but we need
|
||||
// to keep a strict order.
|
||||
func (b *sourceFilesystemsBuilder) createRootMappingFs(dirKey, themeFolder string) (*SourceFilesystem, error) {
|
||||
s := &SourceFilesystem{}
|
||||
s := &SourceFilesystem{
|
||||
SourceFs: b.p.Fs.Source,
|
||||
}
|
||||
|
||||
projectDir := b.p.Cfg.GetString(dirKey)
|
||||
if projectDir == "" {
|
||||
@@ -381,7 +499,6 @@ func (b *sourceFilesystemsBuilder) createRootMappingFs(dirKey, themeFolder strin
|
||||
s.Fs = afero.NewReadOnlyFs(fs)
|
||||
|
||||
return s, nil
|
||||
|
||||
}
|
||||
|
||||
func (b *sourceFilesystemsBuilder) existsInSource(abspath string) bool {
|
||||
@@ -396,7 +513,9 @@ func (b *sourceFilesystemsBuilder) createStaticFs() error {
|
||||
|
||||
if isMultihost {
|
||||
for _, l := range b.p.Languages {
|
||||
s := &SourceFilesystem{PublishFolder: l.Lang}
|
||||
s := &SourceFilesystem{
|
||||
SourceFs: b.p.Fs.Source,
|
||||
PublishFolder: l.Lang}
|
||||
staticDirs := removeDuplicatesKeepRight(getStaticDirs(l))
|
||||
if len(staticDirs) == 0 {
|
||||
continue
|
||||
@@ -416,6 +535,14 @@ func (b *sourceFilesystemsBuilder) createStaticFs() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if b.hasTheme {
|
||||
themeFolder := "static"
|
||||
fs = afero.NewCopyOnWriteFs(newRealBase(afero.NewBasePathFs(b.themeFs, themeFolder)), fs)
|
||||
for _, absThemeDir := range b.absThemeDirs {
|
||||
s.Dirnames = append(s.Dirnames, filepath.Join(absThemeDir, themeFolder))
|
||||
}
|
||||
}
|
||||
|
||||
s.Fs = fs
|
||||
ms[l.Lang] = s
|
||||
|
||||
@@ -424,7 +551,10 @@ func (b *sourceFilesystemsBuilder) createStaticFs() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
s := &SourceFilesystem{}
|
||||
s := &SourceFilesystem{
|
||||
SourceFs: b.p.Fs.Source,
|
||||
}
|
||||
|
||||
var staticDirs []string
|
||||
|
||||
for _, l := range b.p.Languages {
|
||||
@@ -451,7 +581,7 @@ func (b *sourceFilesystemsBuilder) createStaticFs() error {
|
||||
|
||||
if b.hasTheme {
|
||||
themeFolder := "static"
|
||||
fs = afero.NewCopyOnWriteFs(afero.NewBasePathFs(b.themeFs, themeFolder), fs)
|
||||
fs = afero.NewCopyOnWriteFs(newRealBase(afero.NewBasePathFs(b.themeFs, themeFolder)), fs)
|
||||
for _, absThemeDir := range b.absThemeDirs {
|
||||
s.Dirnames = append(s.Dirnames, filepath.Join(absThemeDir, themeFolder))
|
||||
}
|
||||
@@ -484,7 +614,7 @@ func getStringOrStringSlice(cfg config.Provider, key string, id int) []string {
|
||||
func createContentFs(fs afero.Fs,
|
||||
workingDir,
|
||||
defaultContentLanguage string,
|
||||
languages langs.Languages) (afero.Fs, []types.KeyValueStr, error) {
|
||||
languages langs.Languages) (afero.Fs, []string, error) {
|
||||
|
||||
var contentLanguages langs.Languages
|
||||
var contentDirSeen = make(map[string]bool)
|
||||
@@ -511,7 +641,7 @@ func createContentFs(fs afero.Fs,
|
||||
|
||||
}
|
||||
|
||||
var absContentDirs []types.KeyValueStr
|
||||
var absContentDirs []string
|
||||
|
||||
fs, err := createContentOverlayFs(fs, workingDir, contentLanguages, languageSet, &absContentDirs)
|
||||
return fs, absContentDirs, err
|
||||
@@ -522,7 +652,7 @@ func createContentOverlayFs(source afero.Fs,
|
||||
workingDir string,
|
||||
languages langs.Languages,
|
||||
languageSet map[string]bool,
|
||||
absContentDirs *[]types.KeyValueStr) (afero.Fs, error) {
|
||||
absContentDirs *[]string) (afero.Fs, error) {
|
||||
if len(languages) == 0 {
|
||||
return source, nil
|
||||
}
|
||||
@@ -548,7 +678,7 @@ func createContentOverlayFs(source afero.Fs,
|
||||
return nil, fmt.Errorf("invalid content dir %q: Path is too short", absContentDir)
|
||||
}
|
||||
|
||||
*absContentDirs = append(*absContentDirs, types.KeyValueStr{Key: language.Lang, Value: absContentDir})
|
||||
*absContentDirs = append(*absContentDirs, absContentDir)
|
||||
|
||||
overlay := hugofs.NewLanguageFs(language.Lang, languageSet, afero.NewBasePathFs(source, absContentDir))
|
||||
if len(languages) == 1 {
|
||||
@@ -597,10 +727,10 @@ func createOverlayFs(source afero.Fs, absPaths []string) (afero.Fs, error) {
|
||||
}
|
||||
|
||||
if len(absPaths) == 1 {
|
||||
return afero.NewReadOnlyFs(afero.NewBasePathFs(source, absPaths[0])), nil
|
||||
return afero.NewReadOnlyFs(newRealBase(afero.NewBasePathFs(source, absPaths[0]))), nil
|
||||
}
|
||||
|
||||
base := afero.NewReadOnlyFs(afero.NewBasePathFs(source, absPaths[0]))
|
||||
base := afero.NewReadOnlyFs(newRealBase(afero.NewBasePathFs(source, absPaths[0])))
|
||||
overlay, err := createOverlayFs(source, absPaths[1:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -20,6 +20,8 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
@@ -60,6 +62,10 @@ theme = ["atheme"]
|
||||
setConfigAndWriteSomeFilesTo(fs.Source, v, "staticDir", "mystatic", 6)
|
||||
setConfigAndWriteSomeFilesTo(fs.Source, v, "dataDir", "mydata", 7)
|
||||
setConfigAndWriteSomeFilesTo(fs.Source, v, "archetypeDir", "myarchetypes", 8)
|
||||
setConfigAndWriteSomeFilesTo(fs.Source, v, "assetDir", "myassets", 9)
|
||||
setConfigAndWriteSomeFilesTo(fs.Source, v, "resourceDir", "myrsesource", 10)
|
||||
|
||||
v.Set("publishDir", "public")
|
||||
|
||||
p, err := paths.New(fs, v)
|
||||
assert.NoError(err)
|
||||
@@ -88,12 +94,15 @@ theme = ["atheme"]
|
||||
_, err = ff.Readdirnames(-1)
|
||||
assert.NoError(err)
|
||||
|
||||
checkFileCount(bfs.ContentFs, "", assert, 3)
|
||||
checkFileCount(bfs.Content.Fs, "", assert, 3)
|
||||
checkFileCount(bfs.I18n.Fs, "", assert, 6) // 4 + 2 themes
|
||||
checkFileCount(bfs.Layouts.Fs, "", assert, 5)
|
||||
checkFileCount(bfs.Static[""].Fs, "", assert, 6)
|
||||
checkFileCount(bfs.Data.Fs, "", assert, 9) // 7 + 2 themes
|
||||
checkFileCount(bfs.Archetypes.Fs, "", assert, 8)
|
||||
checkFileCount(bfs.Assets.Fs, "", assert, 9)
|
||||
checkFileCount(bfs.Resources.Fs, "", assert, 10)
|
||||
checkFileCount(bfs.Work.Fs, "", assert, 57)
|
||||
|
||||
assert.Equal([]string{filepath.FromSlash("/my/work/mydata"), filepath.FromSlash("/my/work/themes/btheme/data"), filepath.FromSlash("/my/work/themes/atheme/data")}, bfs.Data.Dirnames)
|
||||
|
||||
@@ -101,15 +110,16 @@ theme = ["atheme"]
|
||||
assert.True(bfs.IsI18n(filepath.Join(workingDir, "myi18n", "file1.txt")))
|
||||
assert.True(bfs.IsLayout(filepath.Join(workingDir, "mylayouts", "file1.txt")))
|
||||
assert.True(bfs.IsStatic(filepath.Join(workingDir, "mystatic", "file1.txt")))
|
||||
assert.True(bfs.IsAsset(filepath.Join(workingDir, "myassets", "file1.txt")))
|
||||
|
||||
contentFilename := filepath.Join(workingDir, "mycontent", "file1.txt")
|
||||
assert.True(bfs.IsContent(contentFilename))
|
||||
rel, _ := bfs.RelContentDir(contentFilename)
|
||||
rel := bfs.RelContentDir(contentFilename)
|
||||
assert.Equal("file1.txt", rel)
|
||||
|
||||
}
|
||||
|
||||
func TestNewBaseFsEmpty(t *testing.T) {
|
||||
assert := require.New(t)
|
||||
func createConfig() *viper.Viper {
|
||||
v := viper.New()
|
||||
v.Set("contentDir", "mycontent")
|
||||
v.Set("i18nDir", "myi18n")
|
||||
@@ -117,18 +127,157 @@ func TestNewBaseFsEmpty(t *testing.T) {
|
||||
v.Set("dataDir", "mydata")
|
||||
v.Set("layoutDir", "mylayouts")
|
||||
v.Set("archetypeDir", "myarchetypes")
|
||||
v.Set("assetDir", "myassets")
|
||||
v.Set("resourceDir", "resources")
|
||||
v.Set("publishDir", "public")
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func TestNewBaseFsEmpty(t *testing.T) {
|
||||
assert := require.New(t)
|
||||
v := createConfig()
|
||||
fs := hugofs.NewMem(v)
|
||||
p, err := paths.New(fs, v)
|
||||
assert.NoError(err)
|
||||
bfs, err := NewBase(p)
|
||||
assert.NoError(err)
|
||||
assert.NotNil(bfs)
|
||||
assert.Equal(hugofs.NoOpFs, bfs.Archetypes.Fs)
|
||||
assert.Equal(hugofs.NoOpFs, bfs.Layouts.Fs)
|
||||
assert.Equal(hugofs.NoOpFs, bfs.Data.Fs)
|
||||
assert.Equal(hugofs.NoOpFs, bfs.Assets.Fs)
|
||||
assert.Equal(hugofs.NoOpFs, bfs.I18n.Fs)
|
||||
assert.NotNil(hugofs.NoOpFs, bfs.ContentFs)
|
||||
assert.NotNil(hugofs.NoOpFs, bfs.Static)
|
||||
assert.NotNil(bfs.Work.Fs)
|
||||
assert.NotNil(bfs.Content.Fs)
|
||||
assert.NotNil(bfs.Static)
|
||||
}
|
||||
|
||||
func TestRealDirs(t *testing.T) {
|
||||
assert := require.New(t)
|
||||
v := createConfig()
|
||||
fs := hugofs.NewDefault(v)
|
||||
sfs := fs.Source
|
||||
|
||||
root, err := afero.TempDir(sfs, "", "realdir")
|
||||
assert.NoError(err)
|
||||
themesDir, err := afero.TempDir(sfs, "", "themesDir")
|
||||
assert.NoError(err)
|
||||
defer func() {
|
||||
os.RemoveAll(root)
|
||||
os.RemoveAll(themesDir)
|
||||
}()
|
||||
|
||||
v.Set("workingDir", root)
|
||||
v.Set("themesDir", themesDir)
|
||||
v.Set("theme", "mytheme")
|
||||
|
||||
assert.NoError(sfs.MkdirAll(filepath.Join(root, "myassets", "scss", "sf1"), 0755))
|
||||
assert.NoError(sfs.MkdirAll(filepath.Join(root, "myassets", "scss", "sf2"), 0755))
|
||||
assert.NoError(sfs.MkdirAll(filepath.Join(themesDir, "mytheme", "assets", "scss", "sf2"), 0755))
|
||||
assert.NoError(sfs.MkdirAll(filepath.Join(themesDir, "mytheme", "assets", "scss", "sf3"), 0755))
|
||||
assert.NoError(sfs.MkdirAll(filepath.Join(root, "resources"), 0755))
|
||||
assert.NoError(sfs.MkdirAll(filepath.Join(themesDir, "mytheme", "resources"), 0755))
|
||||
|
||||
assert.NoError(sfs.MkdirAll(filepath.Join(root, "myassets", "js", "f2"), 0755))
|
||||
|
||||
afero.WriteFile(sfs, filepath.Join(filepath.Join(root, "myassets", "scss", "sf1", "a1.scss")), []byte("content"), 0755)
|
||||
afero.WriteFile(sfs, filepath.Join(filepath.Join(root, "myassets", "scss", "sf2", "a3.scss")), []byte("content"), 0755)
|
||||
afero.WriteFile(sfs, filepath.Join(filepath.Join(root, "myassets", "scss", "a2.scss")), []byte("content"), 0755)
|
||||
afero.WriteFile(sfs, filepath.Join(filepath.Join(themesDir, "mytheme", "assets", "scss", "sf2", "a3.scss")), []byte("content"), 0755)
|
||||
afero.WriteFile(sfs, filepath.Join(filepath.Join(themesDir, "mytheme", "assets", "scss", "sf3", "a4.scss")), []byte("content"), 0755)
|
||||
|
||||
afero.WriteFile(sfs, filepath.Join(filepath.Join(themesDir, "mytheme", "resources", "t1.txt")), []byte("content"), 0755)
|
||||
afero.WriteFile(sfs, filepath.Join(filepath.Join(root, "resources", "p1.txt")), []byte("content"), 0755)
|
||||
afero.WriteFile(sfs, filepath.Join(filepath.Join(root, "resources", "p2.txt")), []byte("content"), 0755)
|
||||
|
||||
afero.WriteFile(sfs, filepath.Join(filepath.Join(root, "myassets", "js", "f2", "a1.js")), []byte("content"), 0755)
|
||||
afero.WriteFile(sfs, filepath.Join(filepath.Join(root, "myassets", "js", "a2.js")), []byte("content"), 0755)
|
||||
|
||||
p, err := paths.New(fs, v)
|
||||
assert.NoError(err)
|
||||
bfs, err := NewBase(p)
|
||||
assert.NoError(err)
|
||||
assert.NotNil(bfs)
|
||||
checkFileCount(bfs.Assets.Fs, "", assert, 6)
|
||||
|
||||
realDirs := bfs.Assets.RealDirs("scss")
|
||||
assert.Equal(2, len(realDirs))
|
||||
assert.Equal(filepath.Join(root, "myassets/scss"), realDirs[0])
|
||||
assert.Equal(filepath.Join(themesDir, "mytheme/assets/scss"), realDirs[len(realDirs)-1])
|
||||
|
||||
checkFileCount(bfs.Resources.Fs, "", assert, 3)
|
||||
|
||||
}
|
||||
|
||||
func TestStaticFs(t *testing.T) {
|
||||
assert := require.New(t)
|
||||
v := createConfig()
|
||||
workDir := "mywork"
|
||||
v.Set("workingDir", workDir)
|
||||
v.Set("themesDir", "themes")
|
||||
v.Set("theme", "t1")
|
||||
|
||||
fs := hugofs.NewMem(v)
|
||||
|
||||
themeStaticDir := filepath.Join(workDir, "themes", "t1", "static")
|
||||
|
||||
afero.WriteFile(fs.Source, filepath.Join(workDir, "mystatic", "f1.txt"), []byte("Hugo Rocks!"), 0755)
|
||||
afero.WriteFile(fs.Source, filepath.Join(themeStaticDir, "f1.txt"), []byte("Hugo Themes Rocks!"), 0755)
|
||||
afero.WriteFile(fs.Source, filepath.Join(themeStaticDir, "f2.txt"), []byte("Hugo Themes Still Rocks!"), 0755)
|
||||
|
||||
p, err := paths.New(fs, v)
|
||||
assert.NoError(err)
|
||||
bfs, err := NewBase(p)
|
||||
sfs := bfs.StaticFs("en")
|
||||
checkFileContent(sfs, "f1.txt", assert, "Hugo Rocks!")
|
||||
checkFileContent(sfs, "f2.txt", assert, "Hugo Themes Still Rocks!")
|
||||
|
||||
}
|
||||
|
||||
func TestStaticFsMultiHost(t *testing.T) {
|
||||
assert := require.New(t)
|
||||
v := createConfig()
|
||||
workDir := "mywork"
|
||||
v.Set("workingDir", workDir)
|
||||
v.Set("themesDir", "themes")
|
||||
v.Set("theme", "t1")
|
||||
v.Set("multihost", true)
|
||||
|
||||
vn := viper.New()
|
||||
vn.Set("staticDir", "nn_static")
|
||||
|
||||
en := langs.NewLanguage("en", v)
|
||||
no := langs.NewLanguage("no", v)
|
||||
no.Set("staticDir", "static_no")
|
||||
|
||||
languages := langs.Languages{
|
||||
en,
|
||||
no,
|
||||
}
|
||||
|
||||
v.Set("languagesSorted", languages)
|
||||
|
||||
fs := hugofs.NewMem(v)
|
||||
|
||||
themeStaticDir := filepath.Join(workDir, "themes", "t1", "static")
|
||||
|
||||
afero.WriteFile(fs.Source, filepath.Join(workDir, "mystatic", "f1.txt"), []byte("Hugo Rocks!"), 0755)
|
||||
afero.WriteFile(fs.Source, filepath.Join(workDir, "static_no", "f1.txt"), []byte("Hugo Rocks in Norway!"), 0755)
|
||||
|
||||
afero.WriteFile(fs.Source, filepath.Join(themeStaticDir, "f1.txt"), []byte("Hugo Themes Rocks!"), 0755)
|
||||
afero.WriteFile(fs.Source, filepath.Join(themeStaticDir, "f2.txt"), []byte("Hugo Themes Still Rocks!"), 0755)
|
||||
|
||||
p, err := paths.New(fs, v)
|
||||
assert.NoError(err)
|
||||
bfs, err := NewBase(p)
|
||||
enFs := bfs.StaticFs("en")
|
||||
checkFileContent(enFs, "f1.txt", assert, "Hugo Rocks!")
|
||||
checkFileContent(enFs, "f2.txt", assert, "Hugo Themes Still Rocks!")
|
||||
|
||||
noFs := bfs.StaticFs("no")
|
||||
checkFileContent(noFs, "f1.txt", assert, "Hugo Rocks in Norway!")
|
||||
checkFileContent(noFs, "f2.txt", assert, "Hugo Themes Still Rocks!")
|
||||
}
|
||||
|
||||
func checkFileCount(fs afero.Fs, dirname string, assert *require.Assertions, expected int) {
|
||||
@@ -137,6 +286,18 @@ func checkFileCount(fs afero.Fs, dirname string, assert *require.Assertions, exp
|
||||
assert.Equal(expected, count)
|
||||
}
|
||||
|
||||
func checkFileContent(fs afero.Fs, filename string, assert *require.Assertions, expected ...string) {
|
||||
|
||||
b, err := afero.ReadFile(fs, filename)
|
||||
assert.NoError(err)
|
||||
|
||||
content := string(b)
|
||||
|
||||
for _, e := range expected {
|
||||
assert.Contains(content, e)
|
||||
}
|
||||
}
|
||||
|
||||
func countFileaAndGetDirs(fs afero.Fs, dirname string) (int, []string, error) {
|
||||
if fs == nil {
|
||||
return 0, nil, errors.New("no fs")
|
||||
|
||||
@@ -21,8 +21,6 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gohugoio/hugo/resource"
|
||||
|
||||
"github.com/gohugoio/hugo/deps"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
@@ -182,8 +180,10 @@ func applyDepsIfNeeded(cfg deps.DepsCfg, sites ...*Site) error {
|
||||
continue
|
||||
}
|
||||
|
||||
cfg.Language = s.Language
|
||||
cfg.MediaTypes = s.mediaTypesConfig
|
||||
|
||||
if d == nil {
|
||||
cfg.Language = s.Language
|
||||
cfg.WithTemplate = s.withSiteTemplates(cfg.WithTemplate)
|
||||
|
||||
var err error
|
||||
@@ -200,7 +200,7 @@ func applyDepsIfNeeded(cfg deps.DepsCfg, sites ...*Site) error {
|
||||
}
|
||||
|
||||
} else {
|
||||
d, err = d.ForLanguage(s.Language)
|
||||
d, err = d.ForLanguage(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -208,11 +208,6 @@ func applyDepsIfNeeded(cfg deps.DepsCfg, sites ...*Site) error {
|
||||
s.Deps = d
|
||||
}
|
||||
|
||||
s.resourceSpec, err = resource.NewSpec(s.Deps.PathSpec, s.mediaTypesConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -701,7 +696,7 @@ func (m *contentChangeMap) resolveAndRemove(filename string) (string, string, bu
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
// Bundles share resources, so we need to start from the virtual root.
|
||||
relPath, _ := m.pathSpec.RelContentDir(filename)
|
||||
relPath := m.pathSpec.RelContentDir(filename)
|
||||
dir, name := filepath.Split(relPath)
|
||||
if !strings.HasSuffix(dir, helpers.FilePathSeparator) {
|
||||
dir += helpers.FilePathSeparator
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
// Build builds all sites. If filesystem events are provided,
|
||||
// this is considered to be a potential partial rebuild.
|
||||
func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error {
|
||||
|
||||
if h.Metrics != nil {
|
||||
h.Metrics.Reset()
|
||||
}
|
||||
@@ -42,6 +43,10 @@ func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error {
|
||||
conf.whatChanged = &whatChanged{source: true, other: true}
|
||||
}
|
||||
|
||||
for _, s := range h.Sites {
|
||||
s.Deps.BuildStartListeners.Notify()
|
||||
}
|
||||
|
||||
if len(events) > 0 {
|
||||
// Rebuild
|
||||
if err := h.initRebuild(conf); err != nil {
|
||||
|
||||
@@ -415,7 +415,7 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) {
|
||||
require.NotNil(t, bundleFr)
|
||||
require.Equal(t, "/blog/fr/bundles/b1/", bundleFr.RelPermalink())
|
||||
require.Equal(t, 1, len(bundleFr.Resources))
|
||||
logoFr := bundleFr.Resources.GetByPrefix("logo")
|
||||
logoFr := bundleFr.Resources.GetMatch("logo*")
|
||||
require.NotNil(t, logoFr)
|
||||
require.Equal(t, "/blog/fr/bundles/b1/logo.png", logoFr.RelPermalink())
|
||||
b.AssertFileContent("public/fr/bundles/b1/logo.png", "PNG Data")
|
||||
@@ -424,7 +424,7 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) {
|
||||
require.NotNil(t, bundleEn)
|
||||
require.Equal(t, "/blog/en/bundles/b1/", bundleEn.RelPermalink())
|
||||
require.Equal(t, 1, len(bundleEn.Resources))
|
||||
logoEn := bundleEn.Resources.GetByPrefix("logo")
|
||||
logoEn := bundleEn.Resources.GetMatch("logo*")
|
||||
require.NotNil(t, logoEn)
|
||||
require.Equal(t, "/blog/en/bundles/b1/logo.png", logoEn.RelPermalink())
|
||||
b.AssertFileContent("public/en/bundles/b1/logo.png", "PNG Data")
|
||||
@@ -461,7 +461,7 @@ func TestMultiSitesRebuild(t *testing.T) {
|
||||
b.AssertFileContent("public/fr/sect/doc1/index.html", "Single", "Shortcode: Bonjour")
|
||||
b.AssertFileContent("public/en/sect/doc1-slug/index.html", "Single", "Shortcode: Hello")
|
||||
|
||||
contentFs := b.H.BaseFs.ContentFs
|
||||
contentFs := b.H.BaseFs.Content.Fs
|
||||
|
||||
for i, this := range []struct {
|
||||
preFunc func(t *testing.T)
|
||||
@@ -698,7 +698,7 @@ title = "Svenska"
|
||||
|
||||
// Regular pages have no children
|
||||
require.Len(t, svPage.Pages, 0)
|
||||
require.Len(t, svPage.Data["Pages"], 0)
|
||||
require.Len(t, svPage.data["Pages"], 0)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ languageName = "Nynorsk"
|
||||
require.NotNil(t, bundleEn)
|
||||
require.Equal(t, "/docs/bundles/b1/", bundleEn.RelPermalink())
|
||||
require.Equal(t, 1, len(bundleEn.Resources))
|
||||
logoEn := bundleEn.Resources.GetByPrefix("logo")
|
||||
logoEn := bundleEn.Resources.GetMatch("logo*")
|
||||
require.NotNil(t, logoEn)
|
||||
require.Equal(t, "/docs/bundles/b1/logo.png", logoEn.RelPermalink())
|
||||
b.AssertFileContent("public/en/bundles/b1/logo.png", "PNG Data")
|
||||
@@ -105,7 +105,7 @@ languageName = "Nynorsk"
|
||||
require.NotNil(t, bundleFr)
|
||||
require.Equal(t, "/bundles/b1/", bundleFr.RelPermalink())
|
||||
require.Equal(t, 1, len(bundleFr.Resources))
|
||||
logoFr := bundleFr.Resources.GetByPrefix("logo")
|
||||
logoFr := bundleFr.Resources.GetMatch("logo*")
|
||||
require.NotNil(t, logoFr)
|
||||
require.Equal(t, "/bundles/b1/logo.png", logoFr.RelPermalink())
|
||||
b.AssertFileContent("public/fr/bundles/b1/logo.png", "PNG Data")
|
||||
|
||||
@@ -21,6 +21,8 @@ import (
|
||||
"reflect"
|
||||
"unicode"
|
||||
|
||||
"github.com/gohugoio/hugo/media"
|
||||
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
@@ -228,7 +230,7 @@ type Page struct {
|
||||
title string
|
||||
Description string
|
||||
Keywords []string
|
||||
Data map[string]interface{}
|
||||
data map[string]interface{}
|
||||
|
||||
pagemeta.PageDates
|
||||
|
||||
@@ -239,7 +241,8 @@ type Page struct {
|
||||
permalink string
|
||||
relPermalink string
|
||||
|
||||
// relative target path without extension and any base path element from the baseURL.
|
||||
// relative target path without extension and any base path element
|
||||
// from the baseURL or the language code.
|
||||
// This is used to construct paths in the page resources.
|
||||
relTargetPathBase string
|
||||
// Is set to a forward slashed path if this is a Page resources living in a folder below its owner.
|
||||
@@ -254,7 +257,7 @@ type Page struct {
|
||||
|
||||
layoutDescriptor output.LayoutDescriptor
|
||||
|
||||
scratch *Scratch
|
||||
scratch *maps.Scratch
|
||||
|
||||
// It would be tempting to use the language set on the Site, but in they way we do
|
||||
// multi-site processing, these values may differ during the initial page processing.
|
||||
@@ -272,12 +275,16 @@ type Page struct {
|
||||
targetPathDescriptorPrototype *targetPathDescriptor
|
||||
}
|
||||
|
||||
func stackTrace() string {
|
||||
trace := make([]byte, 2000)
|
||||
func stackTrace(length int) string {
|
||||
trace := make([]byte, length)
|
||||
runtime.Stack(trace, true)
|
||||
return string(trace)
|
||||
}
|
||||
|
||||
func (p *Page) Data() interface{} {
|
||||
return p.data
|
||||
}
|
||||
|
||||
func (p *Page) initContent() {
|
||||
|
||||
p.contentInit.Do(func() {
|
||||
@@ -476,6 +483,10 @@ func (p *Page) BundleType() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *Page) MediaType() media.Type {
|
||||
return media.OctetType
|
||||
}
|
||||
|
||||
type Source struct {
|
||||
Frontmatter []byte
|
||||
Content []byte
|
||||
@@ -1884,7 +1895,7 @@ func (p *Page) prepareLayouts() error {
|
||||
func (p *Page) prepareData(s *Site) error {
|
||||
if p.Kind != KindSection {
|
||||
var pages Pages
|
||||
p.Data = make(map[string]interface{})
|
||||
p.data = make(map[string]interface{})
|
||||
|
||||
switch p.Kind {
|
||||
case KindPage:
|
||||
@@ -1903,21 +1914,21 @@ func (p *Page) prepareData(s *Site) error {
|
||||
singular := s.taxonomiesPluralSingular[plural]
|
||||
taxonomy := s.Taxonomies[plural].Get(term)
|
||||
|
||||
p.Data[singular] = taxonomy
|
||||
p.Data["Singular"] = singular
|
||||
p.Data["Plural"] = plural
|
||||
p.Data["Term"] = term
|
||||
p.data[singular] = taxonomy
|
||||
p.data["Singular"] = singular
|
||||
p.data["Plural"] = plural
|
||||
p.data["Term"] = term
|
||||
pages = taxonomy.Pages()
|
||||
case KindTaxonomyTerm:
|
||||
plural := p.sections[0]
|
||||
singular := s.taxonomiesPluralSingular[plural]
|
||||
|
||||
p.Data["Singular"] = singular
|
||||
p.Data["Plural"] = plural
|
||||
p.Data["Terms"] = s.Taxonomies[plural]
|
||||
p.data["Singular"] = singular
|
||||
p.data["Plural"] = plural
|
||||
p.data["Terms"] = s.Taxonomies[plural]
|
||||
// keep the following just for legacy reasons
|
||||
p.Data["OrderedIndex"] = p.Data["Terms"]
|
||||
p.Data["Index"] = p.Data["Terms"]
|
||||
p.data["OrderedIndex"] = p.data["Terms"]
|
||||
p.data["Index"] = p.data["Terms"]
|
||||
|
||||
// A list of all KindTaxonomy pages with matching plural
|
||||
for _, p := range s.findPagesByKind(KindTaxonomy) {
|
||||
@@ -1927,7 +1938,7 @@ func (p *Page) prepareData(s *Site) error {
|
||||
}
|
||||
}
|
||||
|
||||
p.Data["Pages"] = pages
|
||||
p.data["Pages"] = pages
|
||||
p.Pages = pages
|
||||
}
|
||||
|
||||
@@ -2025,9 +2036,9 @@ func (p *Page) String() string {
|
||||
}
|
||||
|
||||
// Scratch returns the writable context associated with this Page.
|
||||
func (p *Page) Scratch() *Scratch {
|
||||
func (p *Page) Scratch() *maps.Scratch {
|
||||
if p.scratch == nil {
|
||||
p.scratch = newScratch()
|
||||
p.scratch = maps.NewScratch()
|
||||
}
|
||||
return p.scratch
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ func (s *siteContentProcessor) process(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
for _, file := range files {
|
||||
f, err := s.site.BaseFs.ContentFs.Open(file.Filename())
|
||||
f, err := s.site.BaseFs.Content.Fs.Open(file.Filename())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open assets file: %s", err)
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ func TestPageBundlerCaptureSymlinks(t *testing.T) {
|
||||
|
||||
assert := require.New(t)
|
||||
ps, workDir := newTestBundleSymbolicSources(t)
|
||||
sourceSpec := source.NewSourceSpec(ps, ps.BaseFs.ContentFs)
|
||||
sourceSpec := source.NewSourceSpec(ps, ps.BaseFs.Content.Fs)
|
||||
|
||||
fileStore := &storeFilenames{}
|
||||
logger := loggers.NewErrorLogger()
|
||||
@@ -137,7 +137,7 @@ func TestPageBundlerCaptureBasic(t *testing.T) {
|
||||
ps, err := helpers.NewPathSpec(fs, cfg)
|
||||
assert.NoError(err)
|
||||
|
||||
sourceSpec := source.NewSourceSpec(ps, ps.BaseFs.ContentFs)
|
||||
sourceSpec := source.NewSourceSpec(ps, ps.BaseFs.Content.Fs)
|
||||
|
||||
fileStore := &storeFilenames{}
|
||||
|
||||
@@ -183,7 +183,7 @@ func TestPageBundlerCaptureMultilingual(t *testing.T) {
|
||||
ps, err := helpers.NewPathSpec(fs, cfg)
|
||||
assert.NoError(err)
|
||||
|
||||
sourceSpec := source.NewSourceSpec(ps, ps.BaseFs.ContentFs)
|
||||
sourceSpec := source.NewSourceSpec(ps, ps.BaseFs.Content.Fs)
|
||||
fileStore := &storeFilenames{}
|
||||
c := newCapturer(loggers.NewErrorLogger(), sourceSpec, fileStore, nil)
|
||||
|
||||
|
||||
@@ -326,9 +326,14 @@ func (c *contentHandlers) createResource() contentHandler {
|
||||
return notHandled
|
||||
}
|
||||
|
||||
resource, err := c.s.resourceSpec.NewResourceFromFilename(
|
||||
ctx.parentPage.subResourceTargetPathFactory,
|
||||
ctx.source.Filename(), ctx.target)
|
||||
resource, err := c.s.ResourceSpec.New(
|
||||
resource.ResourceSourceDescriptor{
|
||||
TargetPathBuilder: ctx.parentPage.subResourceTargetPathFactory,
|
||||
SourceFile: ctx.source,
|
||||
RelTargetFilename: ctx.target,
|
||||
URLBase: c.s.GetURLLanguageBasePath(),
|
||||
TargetPathBase: c.s.GetTargetLanguageBasePath(),
|
||||
})
|
||||
|
||||
return handlerResult{err: err, handled: true, resource: resource}
|
||||
}
|
||||
@@ -336,7 +341,7 @@ func (c *contentHandlers) createResource() contentHandler {
|
||||
|
||||
func (c *contentHandlers) copyFile() contentHandler {
|
||||
return func(ctx *handlerContext) handlerResult {
|
||||
f, err := c.s.BaseFs.ContentFs.Open(ctx.source.Filename())
|
||||
f, err := c.s.BaseFs.Content.Fs.Open(ctx.source.Filename())
|
||||
if err != nil {
|
||||
err := fmt.Errorf("failed to open file in copyFile: %s", err)
|
||||
return handlerResult{err: err}
|
||||
|
||||
@@ -37,7 +37,6 @@ import (
|
||||
|
||||
"github.com/gohugoio/hugo/deps"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/resource"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -147,9 +146,9 @@ func TestPageBundlerSiteRegular(t *testing.T) {
|
||||
assert.Equal(leafBundle1, firstPage.Parent())
|
||||
assert.Equal(leafBundle1, secondPage.Parent())
|
||||
|
||||
assert.Equal(firstPage, pageResources.GetByPrefix("1"))
|
||||
assert.Equal(secondPage, pageResources.GetByPrefix("2"))
|
||||
assert.Nil(pageResources.GetByPrefix("doesnotexist"))
|
||||
assert.Equal(firstPage, pageResources.GetMatch("1*"))
|
||||
assert.Equal(secondPage, pageResources.GetMatch("2*"))
|
||||
assert.Nil(pageResources.GetMatch("doesnotexist*"))
|
||||
|
||||
imageResources := leafBundle1.Resources.ByType("image")
|
||||
assert.Equal(3, len(imageResources))
|
||||
@@ -158,7 +157,6 @@ func TestPageBundlerSiteRegular(t *testing.T) {
|
||||
altFormat := leafBundle1.OutputFormats().Get("CUSTOMO")
|
||||
assert.NotNil(altFormat)
|
||||
|
||||
assert.Equal(filepath.FromSlash("/work/base/b/my-bundle/c/logo.png"), image.(resource.Source).AbsSourceFilename())
|
||||
assert.Equal("https://example.com/2017/pageslug/c/logo.png", image.Permalink())
|
||||
|
||||
th.assertFileContent(filepath.FromSlash("/work/public/2017/pageslug/c/logo.png"), "content")
|
||||
@@ -265,9 +263,9 @@ func TestPageBundlerSiteMultilingual(t *testing.T) {
|
||||
|
||||
// See https://github.com/gohugoio/hugo/issues/4295
|
||||
// Every resource should have its Name prefixed with its base folder.
|
||||
cBundleResources := bundleWithSubPath.Resources.ByPrefix("c/")
|
||||
cBundleResources := bundleWithSubPath.Resources.Match("c/**")
|
||||
assert.Equal(4, len(cBundleResources))
|
||||
bundlePage := bundleWithSubPath.Resources.GetByPrefix("c/page")
|
||||
bundlePage := bundleWithSubPath.Resources.GetMatch("c/page*")
|
||||
assert.NotNil(bundlePage)
|
||||
assert.IsType(&Page{}, bundlePage)
|
||||
|
||||
@@ -490,7 +488,7 @@ TheContent.
|
||||
singleLayout := `
|
||||
Single Title: {{ .Title }}
|
||||
Content: {{ .Content }}
|
||||
{{ $sunset := .Resources.GetByPrefix "my-sunset-1" }}
|
||||
{{ $sunset := .Resources.GetMatch "my-sunset-1*" }}
|
||||
{{ with $sunset }}
|
||||
Sunset RelPermalink: {{ .RelPermalink }}
|
||||
{{ $thumb := .Fill "123x123" }}
|
||||
@@ -509,7 +507,7 @@ Thumb RelPermalink: {{ $thumb.RelPermalink }}
|
||||
|
||||
myShort := `
|
||||
MyShort in {{ .Page.Path }}:
|
||||
{{ $sunset := .Page.Resources.GetByPrefix "my-sunset-2" }}
|
||||
{{ $sunset := .Page.Resources.GetMatch "my-sunset-2*" }}
|
||||
{{ with $sunset }}
|
||||
Short Sunset RelPermalink: {{ .RelPermalink }}
|
||||
{{ $thumb := .Fill "56x56" }}
|
||||
|
||||
@@ -220,6 +220,6 @@ func (c *PageCollections) clearResourceCacheForPage(page *Page) {
|
||||
dir := path.Dir(first.RelPermalink())
|
||||
dir = strings.TrimPrefix(dir, page.LanguagePrefix())
|
||||
// This is done to keep the memory usage in check when doing live reloads.
|
||||
page.s.resourceSpec.DeleteCacheByPrefix(dir)
|
||||
page.s.ResourceSpec.DeleteCacheByPrefix(dir)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,10 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
bp "github.com/gohugoio/hugo/bufferpool"
|
||||
|
||||
"github.com/gohugoio/hugo/tpl"
|
||||
|
||||
"github.com/gohugoio/hugo/resource"
|
||||
|
||||
"github.com/gohugoio/hugo/media"
|
||||
@@ -119,15 +123,15 @@ func (p *PageOutput) Render(layout ...string) template.HTML {
|
||||
}
|
||||
|
||||
for _, layout := range l {
|
||||
templ := p.s.Tmpl.Lookup(layout)
|
||||
if templ == nil {
|
||||
templ, found := p.s.Tmpl.Lookup(layout)
|
||||
if !found {
|
||||
// This is legacy from when we had only one output format and
|
||||
// HTML templates only. Some have references to layouts without suffix.
|
||||
// We default to good old HTML.
|
||||
templ = p.s.Tmpl.Lookup(layout + ".html")
|
||||
templ, found = p.s.Tmpl.Lookup(layout + ".html")
|
||||
}
|
||||
if templ != nil {
|
||||
res, err := templ.ExecuteToString(p)
|
||||
res, err := executeToString(templ, p)
|
||||
if err != nil {
|
||||
p.s.DistinctErrorLog.Printf("in .Render: Failed to execute template %q: %s", layout, err)
|
||||
return template.HTML("")
|
||||
@@ -140,7 +144,20 @@ func (p *PageOutput) Render(layout ...string) template.HTML {
|
||||
|
||||
}
|
||||
|
||||
func executeToString(templ tpl.Template, data interface{}) (string, error) {
|
||||
b := bp.GetBuffer()
|
||||
defer bp.PutBuffer(b)
|
||||
if err := templ.Execute(b, data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return b.String(), nil
|
||||
|
||||
}
|
||||
|
||||
func (p *Page) Render(layout ...string) template.HTML {
|
||||
if p.mainPageOutput == nil {
|
||||
panic(fmt.Sprintf("programming error: no mainPageOutput for %q", p.Path()))
|
||||
}
|
||||
return p.mainPageOutput.Render(layout...)
|
||||
}
|
||||
|
||||
@@ -201,7 +218,7 @@ func newOutputFormat(p *Page, f output.Format) *OutputFormat {
|
||||
func (p *PageOutput) AlternativeOutputFormats() (OutputFormats, error) {
|
||||
var o OutputFormats
|
||||
for _, of := range p.OutputFormats() {
|
||||
if of.f.NotAlternative || of.f == p.outputFormat {
|
||||
if of.f.NotAlternative || of.f.Name == p.outputFormat.Name {
|
||||
continue
|
||||
}
|
||||
o = append(o, of)
|
||||
@@ -262,7 +279,7 @@ func (p *PageOutput) renderResources() error {
|
||||
// mode when the same resource is member of different page bundles.
|
||||
p.deleteResource(i)
|
||||
} else {
|
||||
p.s.Log.ERROR.Printf("Failed to publish %q for page %q: %s", src.AbsSourceFilename(), p.pathOrTitle(), err)
|
||||
p.s.Log.ERROR.Printf("Failed to publish Resource for page %q: %s", p.pathOrTitle(), err)
|
||||
}
|
||||
} else {
|
||||
p.s.PathSpec.ProcessingStats.Incr(&p.s.PathSpec.ProcessingStats.Files)
|
||||
|
||||
@@ -139,7 +139,11 @@ func (p *Page) initURLs() error {
|
||||
return err
|
||||
}
|
||||
|
||||
p.relTargetPathBase = strings.TrimSuffix(target, f.MediaType.FullSuffix())
|
||||
p.relTargetPathBase = strings.TrimPrefix(strings.TrimSuffix(target, f.MediaType.FullSuffix()), "/")
|
||||
if prefix := p.s.GetLanguagePrefix(); prefix != "" {
|
||||
// Any language code in the path will be added later.
|
||||
p.relTargetPathBase = strings.TrimPrefix(p.relTargetPathBase, prefix+"/")
|
||||
}
|
||||
p.relPermalink = p.s.PathSpec.PrependBasePath(rel)
|
||||
p.layoutDescriptor = p.createLayoutDescriptor()
|
||||
return nil
|
||||
@@ -235,7 +239,7 @@ func createTargetPath(d targetPathDescriptor) string {
|
||||
}
|
||||
|
||||
if isUgly {
|
||||
pagePath += d.Type.MediaType.Delimiter + d.Type.MediaType.Suffix
|
||||
pagePath += d.Type.MediaType.FullSuffix()
|
||||
} else {
|
||||
pagePath = filepath.Join(pagePath, d.Type.BaseName+d.Type.MediaType.FullSuffix())
|
||||
}
|
||||
|
||||
@@ -27,10 +27,10 @@ import (
|
||||
|
||||
func TestPageTargetPath(t *testing.T) {
|
||||
|
||||
pathSpec := newTestDefaultPathSpec()
|
||||
pathSpec := newTestDefaultPathSpec(t)
|
||||
|
||||
noExtNoDelimMediaType := media.TextType
|
||||
noExtNoDelimMediaType.Suffix = ""
|
||||
noExtNoDelimMediaType.Suffixes = []string{}
|
||||
noExtNoDelimMediaType.Delimiter = ""
|
||||
|
||||
// Netlify style _redirects
|
||||
@@ -169,8 +169,8 @@ func TestPageTargetPath(t *testing.T) {
|
||||
} else if test.d.Kind == KindHome && test.d.Type.Path != "" {
|
||||
} else if (!strings.HasPrefix(expected, "/index") || test.d.Addends != "") && test.d.URL == "" && isUgly {
|
||||
expected = strings.Replace(expected,
|
||||
"/"+test.d.Type.BaseName+"."+test.d.Type.MediaType.Suffix,
|
||||
"."+test.d.Type.MediaType.Suffix, -1)
|
||||
"/"+test.d.Type.BaseName+"."+test.d.Type.MediaType.Suffix(),
|
||||
"."+test.d.Type.MediaType.Suffix(), -1)
|
||||
}
|
||||
|
||||
if test.d.LangPrefix != "" && !(test.d.Kind == KindPage && test.d.URL != "") {
|
||||
|
||||
@@ -1830,6 +1830,33 @@ Summary: In Chinese, 好 means good.
|
||||
|
||||
}
|
||||
|
||||
func TestScratchSite(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
b := newTestSitesBuilder(t)
|
||||
b.WithSimpleConfigFile().WithTemplatesAdded("index.html", `
|
||||
{{ .Scratch.Set "b" "bv" }}
|
||||
B: {{ .Scratch.Get "b" }}
|
||||
`,
|
||||
"shortcodes/scratch.html", `
|
||||
{{ .Scratch.Set "c" "cv" }}
|
||||
C: {{ .Scratch.Get "c" }}
|
||||
`,
|
||||
)
|
||||
|
||||
b.WithContentAdded("scratchme.md", `
|
||||
---
|
||||
title: Scratch Me!
|
||||
---
|
||||
|
||||
{{< scratch >}}
|
||||
`)
|
||||
b.Build(BuildCfg{})
|
||||
|
||||
b.AssertFileContent("public/index.html", "B: bv")
|
||||
b.AssertFileContent("public/scratchme/index.html", "C: cv")
|
||||
}
|
||||
|
||||
func BenchmarkParsePage(b *testing.B) {
|
||||
s := newTestSite(b)
|
||||
f, _ := os.Open("testdata/redis.cn.md")
|
||||
|
||||
@@ -289,7 +289,7 @@ func (p *PageOutput) Paginator(options ...interface{}) (*Pager, error) {
|
||||
if p.s.owner.IsMultihost() {
|
||||
pathDescriptor.LangPrefix = ""
|
||||
}
|
||||
pagers, err := paginatePages(pathDescriptor, p.Data["Pages"], pagerSize)
|
||||
pagers, err := paginatePages(pathDescriptor, p.data["Pages"], pagerSize)
|
||||
|
||||
if err != nil {
|
||||
initError = err
|
||||
|
||||
@@ -239,7 +239,7 @@ func TestPaginationURLFactory(t *testing.T) {
|
||||
}
|
||||
|
||||
if uglyURLs {
|
||||
expected = expected[:len(expected)-1] + "." + test.d.Type.MediaType.Suffix
|
||||
expected = expected[:len(expected)-1] + "." + test.d.Type.MediaType.Suffix()
|
||||
}
|
||||
|
||||
pathSpec := newTestPathSpec(fs, cfg)
|
||||
@@ -281,7 +281,7 @@ func doTestPaginator(t *testing.T, useViper bool) {
|
||||
pages := createTestPages(s, 12)
|
||||
n1, _ := newPageOutput(s.newHomePage(), false, false, output.HTMLFormat)
|
||||
n2, _ := newPageOutput(s.newHomePage(), false, false, output.HTMLFormat)
|
||||
n1.Data["Pages"] = pages
|
||||
n1.data["Pages"] = pages
|
||||
|
||||
var paginator1 *Pager
|
||||
|
||||
@@ -301,7 +301,7 @@ func doTestPaginator(t *testing.T, useViper bool) {
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, paginator2, paginator1.Next())
|
||||
|
||||
n1.Data["Pages"] = createTestPages(s, 1)
|
||||
n1.data["Pages"] = createTestPages(s, 1)
|
||||
samePaginator, _ := n1.Paginator()
|
||||
require.Equal(t, paginator1, samePaginator)
|
||||
|
||||
|
||||
@@ -27,13 +27,21 @@ type BaseURL struct {
|
||||
}
|
||||
|
||||
func (b BaseURL) String() string {
|
||||
return b.urlStr
|
||||
if b.urlStr != "" {
|
||||
return b.urlStr
|
||||
}
|
||||
return b.url.String()
|
||||
}
|
||||
|
||||
func (b BaseURL) Path() string {
|
||||
return b.url.Path
|
||||
}
|
||||
|
||||
// HostURL returns the URL to the host root without any path elements.
|
||||
func (b BaseURL) HostURL() string {
|
||||
return strings.TrimSuffix(b.String(), b.Path())
|
||||
}
|
||||
|
||||
// WithProtocol returns the BaseURL prefixed with the given protocol.
|
||||
// The Protocol is normally of the form "scheme://", i.e. "webcal://".
|
||||
func (b BaseURL) WithProtocol(protocol string) (string, error) {
|
||||
|
||||
@@ -58,4 +58,9 @@ func TestBaseURL(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "", b.String())
|
||||
|
||||
// BaseURL with sub path
|
||||
b, err = newBaseURLFromString("http://example.com/sub")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "http://example.com/sub", b.String())
|
||||
require.Equal(t, "http://example.com", b.HostURL())
|
||||
}
|
||||
|
||||
@@ -39,11 +39,14 @@ type Paths struct {
|
||||
// Directories
|
||||
// TODO(bep) when we have trimmed down mos of the dirs usage outside of this package, make
|
||||
// these into an interface.
|
||||
ContentDir string
|
||||
ThemesDir string
|
||||
WorkingDir string
|
||||
ContentDir string
|
||||
ThemesDir string
|
||||
WorkingDir string
|
||||
|
||||
// Directories to store Resource related artifacts.
|
||||
AbsResourcesDir string
|
||||
AbsPublishDir string
|
||||
|
||||
AbsPublishDir string
|
||||
|
||||
// pagination path handling
|
||||
PaginatePath string
|
||||
@@ -79,12 +82,21 @@ func New(fs *hugofs.Fs, cfg config.Provider) (*Paths, error) {
|
||||
return nil, fmt.Errorf("Failed to create baseURL from %q: %s", baseURLstr, err)
|
||||
}
|
||||
|
||||
// TODO(bep)
|
||||
contentDir := cfg.GetString("contentDir")
|
||||
workingDir := cfg.GetString("workingDir")
|
||||
resourceDir := cfg.GetString("resourceDir")
|
||||
publishDir := cfg.GetString("publishDir")
|
||||
|
||||
if contentDir == "" {
|
||||
return nil, fmt.Errorf("contentDir not set")
|
||||
}
|
||||
if resourceDir == "" {
|
||||
return nil, fmt.Errorf("resourceDir not set")
|
||||
}
|
||||
if publishDir == "" {
|
||||
return nil, fmt.Errorf("publishDir not set")
|
||||
}
|
||||
|
||||
defaultContentLanguage := cfg.GetString("defaultContentLanguage")
|
||||
|
||||
var (
|
||||
@@ -183,6 +195,21 @@ func (p *Paths) Themes() []string {
|
||||
return p.themes
|
||||
}
|
||||
|
||||
func (p *Paths) GetTargetLanguageBasePath() string {
|
||||
if p.Languages.IsMultihost() {
|
||||
// In a multihost configuration all assets will be published below the language code.
|
||||
return p.Lang()
|
||||
}
|
||||
return p.GetLanguagePrefix()
|
||||
}
|
||||
|
||||
func (p *Paths) GetURLLanguageBasePath() string {
|
||||
if p.Languages.IsMultihost() {
|
||||
return ""
|
||||
}
|
||||
return p.GetLanguagePrefix()
|
||||
}
|
||||
|
||||
func (p *Paths) GetLanguagePrefix() string {
|
||||
if !p.multilingual {
|
||||
return ""
|
||||
|
||||
@@ -30,6 +30,10 @@ func TestNewPaths(t *testing.T) {
|
||||
v.Set("defaultContentLanguageInSubdir", true)
|
||||
v.Set("defaultContentLanguage", "no")
|
||||
v.Set("multilingual", true)
|
||||
v.Set("contentDir", "content")
|
||||
v.Set("workingDir", "work")
|
||||
v.Set("resourceDir", "resources")
|
||||
v.Set("publishDir", "public")
|
||||
|
||||
p, err := New(fs, v)
|
||||
assert.NoError(err)
|
||||
|
||||
@@ -19,23 +19,29 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
// GC requires a build first.
|
||||
func (h *HugoSites) GC() (int, error) {
|
||||
s := h.Sites[0]
|
||||
fs := h.PathSpec.BaseFs.ResourcesFs
|
||||
fs := h.PathSpec.BaseFs.Resources.Fs
|
||||
|
||||
imageCacheDir := s.resourceSpec.GenImagePath
|
||||
imageCacheDir := s.ResourceSpec.GenImagePath
|
||||
if len(imageCacheDir) < 10 {
|
||||
panic("invalid image cache")
|
||||
}
|
||||
assetsCacheDir := s.ResourceSpec.GenAssetsPath
|
||||
if len(assetsCacheDir) < 10 {
|
||||
panic("invalid assets cache")
|
||||
}
|
||||
|
||||
isInUse := func(filename string) bool {
|
||||
isImageInUse := func(filename string) bool {
|
||||
key := strings.TrimPrefix(filename, imageCacheDir)
|
||||
for _, site := range h.Sites {
|
||||
if site.resourceSpec.IsInCache(key) {
|
||||
if site.ResourceSpec.IsInImageCache(key) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -43,44 +49,68 @@ func (h *HugoSites) GC() (int, error) {
|
||||
return false
|
||||
}
|
||||
|
||||
counter := 0
|
||||
|
||||
err := afero.Walk(fs, imageCacheDir, func(path string, info os.FileInfo, err error) error {
|
||||
if info == nil {
|
||||
return nil
|
||||
isAssetInUse := func(filename string) bool {
|
||||
key := strings.TrimPrefix(filename, assetsCacheDir)
|
||||
// These assets are stored in tuplets with an added extension to the key.
|
||||
key = strings.TrimSuffix(key, helpers.Ext(key))
|
||||
for _, site := range h.Sites {
|
||||
if site.ResourceSpec.ResourceCache.Contains(key) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(path, imageCacheDir) {
|
||||
return fmt.Errorf("Invalid state, walk outside of resource dir: %q", path)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
f, err := fs.Open(path)
|
||||
if err != nil {
|
||||
walker := func(dirname string, inUse func(filename string) bool) (int, error) {
|
||||
counter := 0
|
||||
err := afero.Walk(fs, dirname, func(path string, info os.FileInfo, err error) error {
|
||||
if info == nil {
|
||||
return nil
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = f.Readdirnames(1)
|
||||
if err == io.EOF {
|
||||
// Empty dir.
|
||||
s.Fs.Source.Remove(path)
|
||||
|
||||
if !strings.HasPrefix(path, dirname) {
|
||||
return fmt.Errorf("Invalid state, walk outside of resource dir: %q", path)
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
f, err := fs.Open(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = f.Readdirnames(1)
|
||||
if err == io.EOF {
|
||||
// Empty dir.
|
||||
s.Fs.Source.Remove(path)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
inUse := inUse(path)
|
||||
if !inUse {
|
||||
err := fs.Remove(path)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
s.Log.ERROR.Printf("Failed to remove %q: %s", path, err)
|
||||
} else {
|
||||
counter++
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
})
|
||||
|
||||
inUse := isInUse(path)
|
||||
if !inUse {
|
||||
err := fs.Remove(path)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
s.Log.ERROR.Printf("Failed to remove %q: %s", path, err)
|
||||
} else {
|
||||
counter++
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return counter, err
|
||||
}
|
||||
|
||||
return counter, err
|
||||
imageCounter, err1 := walker(imageCacheDir, isImageInUse)
|
||||
assetsCounter, err2 := walker(assetsCacheDir, isAssetInUse)
|
||||
totalCount := imageCounter + assetsCounter
|
||||
|
||||
if err1 != nil {
|
||||
return totalCount, err1
|
||||
}
|
||||
|
||||
return totalCount, err2
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
// Copyright 2018 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package hugolib
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/resource/tocss/scss"
|
||||
)
|
||||
|
||||
func TestResourceChain(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
shouldRun func() bool
|
||||
prepare func(b *sitesBuilder)
|
||||
verify func(b *sitesBuilder)
|
||||
}{
|
||||
{"tocss", func() bool { return scss.Supports() }, func(b *sitesBuilder) {
|
||||
b.WithTemplates("home.html", `
|
||||
{{ $scss := resources.Get "scss/styles2.scss" | toCSS }}
|
||||
{{ $sass := resources.Get "sass/styles3.sass" | toCSS }}
|
||||
{{ $scssCustomTarget := resources.Get "scss/styles2.scss" | toCSS (dict "targetPath" "styles/main.css") }}
|
||||
{{ $scssCustomTargetString := resources.Get "scss/styles2.scss" | toCSS "styles/main.css" }}
|
||||
{{ $scssMin := resources.Get "scss/styles2.scss" | toCSS | minify }}
|
||||
{{ $scssFromTempl := ".{{ .Kind }} { color: blue; }" | resources.FromString "kindofblue.templ" | resources.ExecuteAsTemplate "kindofblue.scss" . | toCSS (dict "targetPath" "styles/templ.css") | minify }}
|
||||
{{ $bundle1 := slice $scssFromTempl $scssMin | resources.Concat "styles/bundle1.css" }}
|
||||
T1: Len Content: {{ len $scss.Content }}|RelPermalink: {{ $scss.RelPermalink }}|Permalink: {{ $scss.Permalink }}|MediaType: {{ $scss.MediaType.Type }}
|
||||
T2: Content: {{ $scssMin.Content }}|RelPermalink: {{ $scssMin.RelPermalink }}
|
||||
T3: Content: {{ len $scssCustomTarget.Content }}|RelPermalink: {{ $scssCustomTarget.RelPermalink }}|MediaType: {{ $scssCustomTarget.MediaType.Type }}
|
||||
T4: Content: {{ len $scssCustomTargetString.Content }}|RelPermalink: {{ $scssCustomTargetString.RelPermalink }}|MediaType: {{ $scssCustomTargetString.MediaType.Type }}
|
||||
T5: Content: {{ $sass.Content }}|T5 RelPermalink: {{ $sass.RelPermalink }}|
|
||||
T6: {{ $bundle1.Permalink }}
|
||||
`)
|
||||
}, func(b *sitesBuilder) {
|
||||
b.AssertFileContent("public/index.html", `T1: Len Content: 24|RelPermalink: /scss/styles2.css|Permalink: http://example.com/scss/styles2.css|MediaType: text/css`)
|
||||
b.AssertFileContent("public/index.html", `T2: Content: body{color:#333}|RelPermalink: /scss/styles2.min.css`)
|
||||
b.AssertFileContent("public/index.html", `T3: Content: 24|RelPermalink: /styles/main.css|MediaType: text/css`)
|
||||
b.AssertFileContent("public/index.html", `T4: Content: 24|RelPermalink: /styles/main.css|MediaType: text/css`)
|
||||
b.AssertFileContent("public/index.html", `T5: Content: .content-navigation {`)
|
||||
b.AssertFileContent("public/index.html", `T5 RelPermalink: /sass/styles3.css|`)
|
||||
b.AssertFileContent("public/index.html", `T6: http://example.com/styles/bundle1.css`)
|
||||
|
||||
b.AssertFileContent("public/styles/templ.min.css", `.home{color:blue}`)
|
||||
b.AssertFileContent("public/styles/bundle1.css", `.home{color:blue}body{color:#333}`)
|
||||
|
||||
}},
|
||||
|
||||
{"minify", func() bool { return true }, func(b *sitesBuilder) {
|
||||
b.WithTemplates("home.html", `
|
||||
Min CSS: {{ ( resources.Get "css/styles1.css" | minify ).Content }}
|
||||
Min JS: {{ ( resources.Get "js/script1.js" | resources.Minify ).Content | safeJS }}
|
||||
Min JSON: {{ ( resources.Get "mydata/json1.json" | resources.Minify ).Content | safeHTML }}
|
||||
Min XML: {{ ( resources.Get "mydata/xml1.xml" | resources.Minify ).Content | safeHTML }}
|
||||
Min SVG: {{ ( resources.Get "mydata/svg1.svg" | resources.Minify ).Content | safeHTML }}
|
||||
Min SVG again: {{ ( resources.Get "mydata/svg1.svg" | resources.Minify ).Content | safeHTML }}
|
||||
Min HTML: {{ ( resources.Get "mydata/html1.html" | resources.Minify ).Content | safeHTML }}
|
||||
|
||||
|
||||
`)
|
||||
}, func(b *sitesBuilder) {
|
||||
b.AssertFileContent("public/index.html", `Min CSS: h1{font-style:bold}`)
|
||||
b.AssertFileContent("public/index.html", `Min JS: var x;x=5;document.getElementById("demo").innerHTML=x*10;`)
|
||||
b.AssertFileContent("public/index.html", `Min JSON: {"employees":[{"firstName":"John","lastName":"Doe"},{"firstName":"Anna","lastName":"Smith"},{"firstName":"Peter","lastName":"Jones"}]}`)
|
||||
b.AssertFileContent("public/index.html", `Min XML: <hello><world>Hugo Rocks!</<world></hello>`)
|
||||
b.AssertFileContent("public/index.html", `Min SVG: <svg height="100" width="100"><path d="M5 10 20 40z"/></svg>`)
|
||||
b.AssertFileContent("public/index.html", `Min SVG again: <svg height="100" width="100"><path d="M5 10 20 40z"/></svg>`)
|
||||
b.AssertFileContent("public/index.html", `Min HTML: <a href=#>Cool</a>`)
|
||||
}},
|
||||
|
||||
{"concat", func() bool { return true }, func(b *sitesBuilder) {
|
||||
b.WithTemplates("home.html", `
|
||||
{{ $a := "A" | resources.FromString "a.txt"}}
|
||||
{{ $b := "B" | resources.FromString "b.txt"}}
|
||||
{{ $c := "C" | resources.FromString "c.txt"}}
|
||||
{{ $textResources := .Resources.Match "*.txt" }}
|
||||
{{ $combined := slice $a $b $c | resources.Concat "bundle/concat.txt" }}
|
||||
T1: Content: {{ $combined.Content }}|RelPermalink: {{ $combined.RelPermalink }}|Permalink: {{ $combined.Permalink }}|MediaType: {{ $combined.MediaType.Type }}
|
||||
{{ with $textResources }}
|
||||
{{ $combinedText := . | resources.Concat "bundle/concattxt.txt" }}
|
||||
T2: Content: {{ $combinedText.Content }}|{{ $combinedText.RelPermalink }}
|
||||
{{ end }}
|
||||
`)
|
||||
}, func(b *sitesBuilder) {
|
||||
b.AssertFileContent("public/index.html", `T1: Content: ABC|RelPermalink: /bundle/concat.txt|Permalink: http://example.com/bundle/concat.txt|MediaType: text/plain`)
|
||||
b.AssertFileContent("public/bundle/concat.txt", "ABC")
|
||||
|
||||
b.AssertFileContent("public/index.html", `T2: Content: t1t|t2t|`)
|
||||
b.AssertFileContent("public/bundle/concattxt.txt", "t1t|t2t|")
|
||||
}},
|
||||
{"fromstring", func() bool { return true }, func(b *sitesBuilder) {
|
||||
b.WithTemplates("home.html", `
|
||||
{{ $r := "Hugo Rocks!" | resources.FromString "rocks/hugo.txt" }}
|
||||
{{ $r.Content }}|{{ $r.RelPermalink }}|{{ $r.Permalink }}|{{ $r.MediaType.Type }}
|
||||
`)
|
||||
|
||||
}, func(b *sitesBuilder) {
|
||||
b.AssertFileContent("public/index.html", `Hugo Rocks!|/rocks/hugo.txt|http://example.com/rocks/hugo.txt|text/plain`)
|
||||
b.AssertFileContent("public/rocks/hugo.txt", "Hugo Rocks!")
|
||||
|
||||
}},
|
||||
{"execute-as-template", func() bool { return true }, func(b *sitesBuilder) {
|
||||
b.WithTemplates("home.html", `
|
||||
|
||||
{{ $result := "{{ .Kind | upper }}" | resources.FromString "mytpl.txt" | resources.ExecuteAsTemplate "result.txt" . }}
|
||||
T1: {{ $result.Content }}|{{ $result.RelPermalink}}|{{$result.MediaType.Type }}
|
||||
`)
|
||||
|
||||
}, func(b *sitesBuilder) {
|
||||
b.AssertFileContent("public/index.html", `T1: HOME|/result.txt|text/plain`)
|
||||
|
||||
}},
|
||||
{"fingerprint", func() bool { return true }, func(b *sitesBuilder) {
|
||||
b.WithTemplates("home.html", `
|
||||
{{ $r := "ab" | resources.FromString "rocks/hugo.txt" }}
|
||||
{{ $result := $r | fingerprint }}
|
||||
{{ $result512 := $r | fingerprint "sha512" }}
|
||||
{{ $resultMD5 := $r | fingerprint "md5" }}
|
||||
T1: {{ $result.Content }}|{{ $result.RelPermalink}}|{{$result.MediaType.Type }}|{{ $result.Data.Integrity }}|
|
||||
T2: {{ $result512.Content }}|{{ $result512.RelPermalink}}|{{$result512.MediaType.Type }}|{{ $result512.Data.Integrity }}|
|
||||
T3: {{ $resultMD5.Content }}|{{ $resultMD5.RelPermalink}}|{{$resultMD5.MediaType.Type }}|{{ $resultMD5.Data.Integrity }}|
|
||||
`)
|
||||
}, func(b *sitesBuilder) {
|
||||
b.AssertFileContent("public/index.html", `T1: ab|/rocks/hugo.fb8e20fc2e4c3f248c60c39bd652f3c1347298bb977b8b4d5903b85055620603.txt|text/plain|sha256-+44g/C5MPySMYMOb1lLzwTRymLuXe4tNWQO4UFViBgM=|`)
|
||||
b.AssertFileContent("public/index.html", `T2: ab|/rocks/hugo.2d408a0717ec188158278a796c689044361dc6fdde28d6f04973b80896e1823975cdbf12eb63f9e0591328ee235d80e9b5bf1aa6a44f4617ff3caf6400eb172d.txt|text/plain|sha512-LUCKBxfsGIFYJ4p5bGiQRDYdxv3eKNbwSXO4CJbhgjl1zb8S62P54FkTKO4jXYDptb8apqRPRhf/PK9kAOsXLQ==|`)
|
||||
b.AssertFileContent("public/index.html", `T3: ab|/rocks/hugo.187ef4436122d1cc2f40dc2b92f0eba0.txt|text/plain|md5-GH70Q2Ei0cwvQNwrkvDroA==|`)
|
||||
}},
|
||||
{"template", func() bool { return true }, func(b *sitesBuilder) {}, func(b *sitesBuilder) {
|
||||
}},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
if !test.shouldRun() {
|
||||
t.Log("Skip", test.name)
|
||||
continue
|
||||
}
|
||||
|
||||
b := newTestSitesBuilder(t).WithLogger(loggers.NewWarningLogger())
|
||||
b.WithSimpleConfigFile()
|
||||
b.WithContent("_index.md", `
|
||||
---
|
||||
title: Home
|
||||
---
|
||||
|
||||
Home.
|
||||
|
||||
`,
|
||||
"page1.md", `
|
||||
---
|
||||
title: Hello1
|
||||
---
|
||||
|
||||
Hello1
|
||||
`,
|
||||
"page2.md", `
|
||||
---
|
||||
title: Hello2
|
||||
---
|
||||
|
||||
Hello2
|
||||
`,
|
||||
"t1.txt", "t1t|",
|
||||
"t2.txt", "t2t|",
|
||||
)
|
||||
|
||||
b.WithSourceFile(filepath.Join("assets", "css", "styles1.css"), `
|
||||
h1 {
|
||||
font-style: bold;
|
||||
}
|
||||
`)
|
||||
|
||||
b.WithSourceFile(filepath.Join("assets", "js", "script1.js"), `
|
||||
var x;
|
||||
x = 5;
|
||||
document.getElementById("demo").innerHTML = x * 10;
|
||||
`)
|
||||
|
||||
b.WithSourceFile(filepath.Join("assets", "mydata", "json1.json"), `
|
||||
{
|
||||
"employees":[
|
||||
{"firstName":"John", "lastName":"Doe"},
|
||||
{"firstName":"Anna", "lastName":"Smith"},
|
||||
{"firstName":"Peter", "lastName":"Jones"}
|
||||
]
|
||||
}
|
||||
`)
|
||||
|
||||
b.WithSourceFile(filepath.Join("assets", "mydata", "svg1.svg"), `
|
||||
<svg height="100" width="100">
|
||||
<line x1="5" y1="10" x2="20" y2="40"/>
|
||||
</svg>
|
||||
`)
|
||||
|
||||
b.WithSourceFile(filepath.Join("assets", "mydata", "xml1.xml"), `
|
||||
<hello>
|
||||
<world>Hugo Rocks!</<world>
|
||||
</hello>
|
||||
`)
|
||||
|
||||
b.WithSourceFile(filepath.Join("assets", "mydata", "html1.html"), `
|
||||
<html>
|
||||
<a href="#">
|
||||
Cool
|
||||
</a >
|
||||
</html>
|
||||
`)
|
||||
|
||||
b.WithSourceFile(filepath.Join("assets", "scss", "styles2.scss"), `
|
||||
$color: #333;
|
||||
|
||||
body {
|
||||
color: $color;
|
||||
}
|
||||
`)
|
||||
|
||||
b.WithSourceFile(filepath.Join("assets", "sass", "styles3.sass"), `
|
||||
$color: #333;
|
||||
|
||||
.content-navigation
|
||||
border-color: $color
|
||||
|
||||
`)
|
||||
|
||||
t.Log("Test", test.name)
|
||||
test.prepare(b)
|
||||
b.Build(BuildCfg{})
|
||||
test.verify(b)
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/output"
|
||||
|
||||
"github.com/gohugoio/hugo/media"
|
||||
@@ -45,7 +46,7 @@ type ShortcodeWithPage struct {
|
||||
// this ordinal will represent the position of this shortcode in the page content.
|
||||
Ordinal int
|
||||
|
||||
scratch *Scratch
|
||||
scratch *maps.Scratch
|
||||
}
|
||||
|
||||
// Site returns information about the current site.
|
||||
@@ -65,9 +66,9 @@ func (scp *ShortcodeWithPage) RelRef(ref string) (string, error) {
|
||||
|
||||
// Scratch returns a scratch-pad scoped for this shortcode. This can be used
|
||||
// as a temporary storage for variables, counters etc.
|
||||
func (scp *ShortcodeWithPage) Scratch() *Scratch {
|
||||
func (scp *ShortcodeWithPage) Scratch() *maps.Scratch {
|
||||
if scp.scratch == nil {
|
||||
scp.scratch = newScratch()
|
||||
scp.scratch = maps.NewScratch()
|
||||
}
|
||||
return scp.scratch
|
||||
}
|
||||
@@ -172,11 +173,11 @@ type scKey struct {
|
||||
}
|
||||
|
||||
func newScKey(m media.Type, shortcodeplaceholder string) scKey {
|
||||
return scKey{Suffix: m.Suffix, ShortcodePlaceholder: shortcodeplaceholder}
|
||||
return scKey{Suffix: m.Suffix(), ShortcodePlaceholder: shortcodeplaceholder}
|
||||
}
|
||||
|
||||
func newScKeyFromLangAndOutputFormat(lang string, o output.Format, shortcodeplaceholder string) scKey {
|
||||
return scKey{Lang: lang, Suffix: o.MediaType.Suffix, OutputFormat: o.Name, ShortcodePlaceholder: shortcodeplaceholder}
|
||||
return scKey{Lang: lang, Suffix: o.MediaType.Suffix(), OutputFormat: o.Name, ShortcodePlaceholder: shortcodeplaceholder}
|
||||
}
|
||||
|
||||
func newDefaultScKey(shortcodeplaceholder string) scKey {
|
||||
@@ -545,7 +546,7 @@ Loop:
|
||||
}
|
||||
|
||||
var err error
|
||||
isInner, err = isInnerShortcode(tmpl)
|
||||
isInner, err = isInnerShortcode(tmpl.(tpl.TemplateExecutor))
|
||||
if err != nil {
|
||||
return sc, fmt.Errorf("Failed to handle template for shortcode %q for page %q: %s", sc.name, p.Path(), err)
|
||||
}
|
||||
@@ -709,7 +710,7 @@ func replaceShortcodeTokens(source []byte, prefix string, replacements map[strin
|
||||
return source, nil
|
||||
}
|
||||
|
||||
func getShortcodeTemplateForTemplateKey(key scKey, shortcodeName string, t tpl.TemplateFinder) *tpl.TemplateAdapter {
|
||||
func getShortcodeTemplateForTemplateKey(key scKey, shortcodeName string, t tpl.TemplateFinder) tpl.Template {
|
||||
isInnerShortcodeCache.RLock()
|
||||
defer isInnerShortcodeCache.RUnlock()
|
||||
|
||||
@@ -737,13 +738,13 @@ func getShortcodeTemplateForTemplateKey(key scKey, shortcodeName string, t tpl.T
|
||||
|
||||
for _, name := range names {
|
||||
|
||||
if x := t.Lookup("shortcodes/" + name); x != nil {
|
||||
if x, found := t.Lookup("shortcodes/" + name); found {
|
||||
return x
|
||||
}
|
||||
if x := t.Lookup("theme/shortcodes/" + name); x != nil {
|
||||
if x, found := t.Lookup("theme/shortcodes/" + name); found {
|
||||
return x
|
||||
}
|
||||
if x := t.Lookup("_internal/shortcodes/" + name); x != nil {
|
||||
if x, found := t.Lookup("_internal/shortcodes/" + name); found {
|
||||
return x
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,6 +464,8 @@ Loop:
|
||||
for {
|
||||
switch r := l.next(); {
|
||||
case isAlphaNumericOrHyphen(r):
|
||||
// Allow forward slash inside names to make it possible to create namespaces.
|
||||
case r == '/':
|
||||
default:
|
||||
l.backup()
|
||||
word := l.input[l.start:l.pos]
|
||||
|
||||