Compare commits

...

77 Commits

Author SHA1 Message Date
hugoreleaser 9f9695cf7b releaser: Add release notes to /docs for release of 0.44
[ci skip]
2018-07-13 06:03:09 +00:00
hugoreleaser 0831d8ccaf releaser: Bump versions for release of 0.44
[ci skip]
2018-07-13 06:03:09 +00:00
Bjørn Erik Pedersen 1d4ff024ca Finish release notes 0.44 2018-07-13 07:53:33 +02:00
hugoreleaser 521e63ac7c releaser: Add release notes draft for 0.44
Rename to *-ready.md to continue. [ci skip]
2018-07-13 05:27:58 +00:00
Bjørn Erik Pedersen 47d38628ec resource: Clean up the in-memory Resource reader usage
Turns out `strings.Reader` implements both `io.Reader` and `io.Seeker`, so we don't need anything special.

Updates #4936
2018-07-12 21:32:52 +02:00
Bjørn Erik Pedersen 0024dcfe3e resource: Move opening of the transformed resources after cache check
The old version should be functionally the same, but opening up these readers (potentially files) every time is wasteful if we don't read from them.
2018-07-12 19:36:12 +02:00
Bjørn Erik Pedersen beec1fc98e resource: Fix resources.Concat for transformed resources
Fixes #4936
2018-07-12 19:36:12 +02:00
Bjørn Erik Pedersen 306573def0 Improve type support in resources.Concat
This allows the result of `.Resources.Match` and similar to be concatenated.

Fixes #4934
2018-07-12 13:43:27 +02:00
Bjørn Erik Pedersen 80c8f3b81a hugolib: Fix static filesystem for themed multihost sites
Multihost is where each language has its own `baseURL`. In this configuration, static files from the theme was not picked up.

This was a regression in Hugo `0.42`. This commit also adds proper tests for this, so that does not happen again.

Fixes #4929
2018-07-12 12:18:56 +02:00
Bjørn Erik Pedersen 6b6dcb44a0 Flush partialCached cache on rebuilds
Fixes #4931
2018-07-11 20:40:04 +02:00
Bjørn Erik Pedersen d96f2a460f resource: Include the transformation step in the error message
Fixes #4924
2018-07-11 11:03:15 +02:00
Bjørn Erik Pedersen ac53035030 media: Only show deprecation warning when needed
Fixes #4927
2018-07-10 23:21:48 +02:00
Bjørn Erik Pedersen b874a1ba7a media: Allow multiple file suffixes per media type
Before this commit, `Suffix` on `MediaType` was used both to set a custom file suffix and as a way to augment the mediatype definition (what you see after the "+", e.g. "image/svg+xml").

This had its limitations. For one, it was only possible with one file extension per MIME type.

Now you can specify multiple file suffixes using "suffixes", but you need to specify the full MIME type
identifier:

[mediaTypes]
[mediaTypes."image/svg+xml"]
suffixes = ["svg", "abc ]

In most cases, it will be enough to just change:

[mediaTypes]
[mediaTypes."my/custom-mediatype"]
suffix = "txt"

To:

[mediaTypes]
[mediaTypes."my/custom-mediatype"]
suffixes = ["txt"]

Hugo will still respect values set in "suffix" if no value for "suffixes" is provided, but this will be removed in a future release.

Note that you can still get the Media Type's suffix from a template: {{ $mediaType.Suffix }}. But this will now map to the MIME type filename.

Fixes #4920
2018-07-10 22:13:52 +02:00
Anthony Fok 4108705934 hugolib: Remove empty resources/ dir after TestNewSiteDefaultLang 2018-07-10 09:01:54 +02:00
Anthony Fok 9c1e82085e .gitattributes: Exclude *.svg from CRLF/LF conversion
I ran into trouble after importing the Hugo v0.43 release tarball
into the Debian packaging git repository...
2018-07-09 21:06:15 -06:00
Anthony Fok 2b73e89d6d tpl: Set permission of embedded templates to 0644 2018-07-09 12:57:08 -06:00
Bjørn Erik Pedersen febf0aec8b Build a fully static Windows extended binary 2018-07-09 20:42:08 +02:00
Bjørn Erik Pedersen a4fad5be6b Adjust release notes
[ci skip]
2018-07-09 12:11:57 +02:00
hugoreleaser 6084f0433c releaser: Prepare repository for 0.44-DEV
[ci skip]
2018-07-09 10:03:30 +00:00
hugoreleaser cbb7214b6c releaser: Add release notes to /docs for release of 0.43
[ci skip]
2018-07-09 10:00:04 +00:00
hugoreleaser e6136b36f3 releaser: Bump versions for release of 0.43
[ci skip]
2018-07-09 10:00:04 +00:00
Bjørn Erik Pedersen 659917a002 releaser: Revert to the old versions of the release pipeline 2018-07-09 11:54:10 +02:00
hugoreleaser 9d194ab904 releaser: Add release for 0.43 2018-07-09 10:52:02 +02:00
Anthony Fok a305609e18 snap: Migrate from deprecated keywords to "override-build"
Also add verbosity and echo messages to aid debugging.

See #4908
2018-07-09 02:41:10 -06:00
Bjørn Erik Pedersen 179de5f5bc Revert "Consider root and current section's content type if set in front matter"
This reverts commit c790029e1d.
2018-07-09 10:29:18 +02:00
Bjørn Erik Pedersen 5dc1f95b63 Revert "Restrict the new type layout resolution to page and section kinds"
This reverts commit 78e8a744b3.
2018-07-09 10:29:16 +02:00
Bjørn Erik Pedersen 6bc892fc24 Revert "output: gofmt -l -w output/layout.go"
This reverts commit f465571b33.
2018-07-09 10:29:13 +02:00
Bjørn Erik Pedersen 5f06dbf779 Revert "snap: Build both hugo and hugo.extended for 0.43"
This reverts commit e1027c5846.
2018-07-09 09:45:18 +02:00
Bjørn Erik Pedersen 0850e97984 Revert "Revert "Add Goreleaser extended config""
I reverted the wrong commit.

This reverts commit a3b4b10f65.
2018-07-09 09:44:51 +02:00
Bjørn Erik Pedersen a3b4b10f65 Revert "Add Goreleaser extended config"
@anthonyfok we need to discuss how we want the Snap build to look before we implement it. I don't think this is how.

People who want SCSS builds can download from the release page for this release.

This reverts commit 626afc9825.
2018-07-09 09:30:10 +02:00
Bjørn Erik Pedersen e3df6478f0 Bump CircleCI image 2018-07-09 09:24:34 +02:00
Bjørn Erik Pedersen 626afc9825 Add Goreleaser extended config
See #4908
2018-07-09 09:24:34 +02:00
Anthony Fok e1027c5846 snap: Build both hugo and hugo.extended for 0.43
Due to snap's design, the name "hugo_extended" needs to be created
via an automatic alias request, see
https://forum.snapcraft.io/t/hugo-auto-alias-request-for-hugo-extended-hugo-extended/6297

Also migrate from deprecated "prepare", "build" and "install" keywords
to "override-build".

See #4908
2018-07-09 01:15:21 -06:00
Carlos Alexandro Becker e1a052ecb8 fix: nfpm replacements
refs https://github.com/goreleaser/goreleaser/issues/715
2018-07-09 08:06:13 +02:00
Bjørn Erik Pedersen bfc3488b8e Add temporary build script 2018-07-08 16:08:22 +02:00
Anthony Fok ce84b524f4 Add "extended" to "hugo version"
Fixes #4913
2018-07-08 11:08:54 +02:00
Yang Li 3cea2932e1 Fix typos 2018-07-07 12:29:56 +02:00
Bjørn Erik Pedersen 0efd374805 Merge commit '98293eaa1570b5aff4452021c8b6d6c8560b3f06' 2018-07-06 17:53:17 +02:00
Bjørn Erik Pedersen 98293eaa15 Squashed 'docs/' changes from 501c6e233..f59b3ab06
f59b3ab06 Fix typo in template lookup order
1e5536d6c Mutlilingual: Document "content directory per language" system (#509)
849a86048 Update index.md
0c24d229b Polish Hugo Next
a4c9b0ee2 Polish
bbec2c76e Some more in birthday post
fc9681e21 More on contributors
09fe3ea31 Some more on the birthday post
8da357240 Content and images for the 5th birthday blog post
fb45bb8dc Add draft for anniversary blog post
4666d0a18 Release 0.42.2
9b74d286a Merge branch 'temp422'
354e7b66b releaser: Add release notes to /docs for release of 0.42.2
57a617f34 releaser: Bump versions for release of 0.42.2
ccc3ac1b8 Update errorf.md
35706c21a Update errorf.md
1c0f35fd1 Update errorf.md
b6170774b Add syntax highlighting gallery links for Chroma
f91d9da47 Update usage.md
c9a8f0190 Improve theme components documentation
3c4e39ddd Release 0.42.1
b45eb453f Merge branch 'temp421'
c74682a10 releaser: Prepare repository for 0.43-DEV
321e07fa5 releaser: Add release notes to /docs for release of 0.42.1
7154271e0 releaser: Bump versions for release of 0.42.1
360d8244f Add link to Privacy Config
1f2454247 Fix typo
a8f5f994e Fix typo
d9f3f078c Update simple variants documentation (#500)
f5cfd44e0 Release 0.42
fe604b321 releaser: Prepare repository for 0.43-DEV
c3e5b3ca0 releaser: Add release notes to /docs for release of 0.42
3174d1b37 releaser: Bump versions for release of 0.42
48cc2d51f docs: Update theme documentation
1922fb1a6 docs: Remove some files now moved
d7e4c453a Merge commit 'b239595af5a9fc1fc9a1ccc666c3ab06ccc32f04'
c40964c1b tplimpl: Remove speakerdeck shortcode
081f8a0f9 tpl/strings: strings.RuneCount
828ea5f15 tpl: Add strings.Repeat
a6b9f654a Add a BlackFriday option for rel="noreferrer" on external links
edb786516 Add a BlackFriday option for rel="nofollow" on external links
e4374971f releaser: Prepare repository for 0.42-DEV

git-subtree-dir: docs
git-subtree-split: f59b3ab06f
2018-07-06 17:52:13 +02:00
Bjørn Erik Pedersen 2b8d907ab7 Add a newScratch template func
Fixes #4685
2018-07-06 17:51:38 +02:00
Bjørn Erik Pedersen 43338c3a99 hugolib: Do not create paginator pages for the other output formats
This is a recent regression in Hugo, where we have started to produce `/page/30/index.json` when the main output format (usually `HTML`) is set up with pagination.

For JSON this is potentially lot of superflous work and hurts performance.

This commit reinstates the earlier behaviour: We only create paginators if in use in the main output format.

And add a test for it to prevent this from happening again.

Fixes #4890
2018-07-06 14:01:27 +02:00
Bjørn Erik Pedersen dea71670c0 Add Hugo Piper with SCSS support and much more
Before this commit, you would have to use page bundles to do image processing etc. in Hugo.

This commit adds

* A new `/assets` top-level project or theme dir (configurable via `assetDir`)
* A new template func, `resources.Get` which can be used to "get a resource" that can be further processed.

This means that you can now do this in your templates (or shortcodes):

```bash
{{ $sunset := (resources.Get "images/sunset.jpg").Fill "300x200" }}
```

This also adds a new `extended` build tag that enables powerful SCSS/SASS support with source maps. To compile this from source, you will also need a C compiler installed:

```
HUGO_BUILD_TAGS=extended mage install
```

Note that you can use output of the SCSS processing later in a non-SCSSS-enabled Hugo.

The `SCSS` processor is a _Resource transformation step_ and it can be chained with the many others in a pipeline:

```bash
{{ $css := resources.Get "styles.scss" | resources.ToCSS | resources.PostCSS | resources.Minify | resources.Fingerprint }}
<link rel="stylesheet" href="{{ $styles.RelPermalink }}" integrity="{{ $styles.Data.Digest }}" media="screen">
```

The transformation funcs above have aliases, so it can be shortened to:

```bash
{{ $css := resources.Get "styles.scss" | toCSS | postCSS | minify | fingerprint }}
<link rel="stylesheet" href="{{ $styles.RelPermalink }}" integrity="{{ $styles.Data.Digest }}" media="screen">
```

A quick tip would be to avoid the fingerprinting part, and possibly also the not-superfast `postCSS` when you're doing development, as it allows Hugo to be smarter about the rebuilding.

Documentation will follow, but have a look at the demo repo in https://github.com/bep/hugo-sass-test

New functions to create `Resource` objects:

* `resources.Get` (see above)
* `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.

Fixes #4381
Fixes #4903
Fixes #4858
2018-07-06 11:46:12 +02:00
Bjørn Erik Pedersen a5d0a57e6b output: Fix the shortcodes/partials vs base template detection
Fixes #4897
2018-07-02 10:34:38 +02:00
Anthony Fok f465571b33 output: gofmt -l -w output/layout.go 2018-07-02 00:08:43 -06:00
Bjørn Erik Pedersen f8212d2000 tpl/collections: Return en empty slice in after instead of error
When the given index is out of bounds. So it can safely be used with `with` etc. without extra length checking.

Fixes #4894
2018-07-01 20:34:02 +02:00
Bjørn Erik Pedersen 78e8a744b3 Restrict the new type layout resolution to page and section kinds
Will have to test and evaluate this a little.

See  #4891
2018-06-30 16:50:02 +02:00
Bjørn Erik Pedersen c790029e1d Consider root and current section's content type if set in front matter
This should allow for less duplication of templates. Before this commit it was possible to override the content page of a given page/section, but only one page at a time.

Full "template sets" can now be inherited by setting `type: blog` etc. in the section content page's front matter, and that type will be considered when looking for layouts for all pages in that section.

For nested sections, it will use consider both `type` set in the current section first, then `type` set in the first section below home, e.g. `/docs`.

This commit also adds a new Page method: `FirstSection`. This navigates up to the first section below home (e.g. `/docs`). For the home page it will return itself.

Fixes #4891
2018-06-30 13:10:04 +02:00
Bjørn Erik Pedersen 554553c09c circleci: Update docker image
With Go 10.3 and newest Goreleaser and Mage.
2018-06-29 10:46:21 +02:00
Bjørn Erik Pedersen de37455ec7 hugolib: Allow forward slash in shortcode names
Fixes #4886
2018-06-28 16:06:16 +02:00
Bjørn Erik Pedersen 282f6035e7 Merge branch 'release-0.42.2' 2018-06-28 14:40:23 +02:00
hugoreleaser 360fa12213 releaser: Prepare repository for 0.43-DEV
[ci skip]
2018-06-28 12:38:48 +00:00
hugoreleaser 02aa320030 releaser: Add release notes to /docs for release of 0.42.2
[ci skip]
2018-06-28 12:36:15 +00:00
hugoreleaser d2640fbc19 releaser: Bump versions for release of 0.42.2
[ci skip]
2018-06-28 12:36:15 +00:00
hugoreleaser 1637d12e37 Release 0.42.2 2018-06-28 14:32:27 +02:00
Bjørn Erik Pedersen 3a7706b069 commands: Fix broken server-reload on config changes
This was accidently broken in Hugo 0.42.

Fixes #4878
2018-06-28 14:08:52 +02:00
Bjørn Erik Pedersen 2955f93fc6 commands: Fix broken server-reload on config changes
This was accidently broken in Hugo 0.42.

Fixes #4878
2018-06-28 12:22:00 +02:00
Carlos Alexandro Becker 1f0c4e1fb3 Update GoReleaser config 2018-06-28 07:54:18 +02:00
Bjørn Erik Pedersen 91ab455d84 tpl: Remove some "debug info"
See #4881
2018-06-26 11:33:12 +02:00
Bjørn Erik Pedersen ca1e46efb9 tpl: Update internal pagination template to support Bootstrap 4
Fixes #4881
2018-06-26 11:31:46 +02:00
openscript fd71fa89bd Create missing head.html partial on new theme generation 2018-06-25 12:06:45 -06:00
openscript b5a3aa7082 Add html doctype to baseof.html template for new themes 2018-06-25 12:06:45 -06:00
Anthony Fok 3d5928889a Revert "tpl: Support text/template/parse API change in go1.11"
Go developers have undone the breaking API changes
in the following commit:

commit bedfa4e1c37bd08063865da628f242d27ca06ec4
Author: Daniel Theophanes <kardianos@gmail.com>
Date:   Thu Jun 21 10:41:26 2018 -0700

    text/template/parse: undo breaking API changes

    golang.org/cl/84480 altered the API for the parse package for
    clarity and consistency. However, the changes also broke the
    API for consumers of the package. This CL reverts the API
    to the previous spelling, adding only a single new exported
    symbol.

    Fixes #25968

    Change-Id: Ieb81054b61eeac7df3bc3864ef446df43c26b80f
    Reviewed-on: https://go-review.googlesource.com/120355
    Reviewed-by: Daniel Martí <mvdan@mvdan.cc>
    Reviewed-by: Rob Pike <r@golang.org>
    Run-TryBot: Daniel Martí <mvdan@mvdan.cc>
    TryBot-Result: Gobot Gobot <gobot@golang.org>

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

This reverts commit 9f27091e10.

Closes #4784
Fixes #4873
2018-06-23 15:07:52 -06:00
Bjørn Erik Pedersen dc7bc7b4d2 appveyor: Disable asciidoctor for now
It fails on installation.
2018-06-23 21:42:21 +02:00
Anthony Fok 42ed602580 hugolib: Replace deprecated {Get,}ByPrefix with {Get,}Match 2018-06-15 09:03:56 +02:00
Brian Fitzgerald 6a2968fd5c Adds .gitattributes to force Go files to LF
Even on Windows machines, we want *.go files to have LF line
endings (go fmt insists). This leaves other files as auto, but
makes *.go files always be LF on Windows machines. With this
change, running go fmt will be a pleasant operation, not a
painful one.

Note that changing .gitattributes only affects files at initial
checkout. To update an entire working directory after making a
change like this, do this (but only after committing all work in
progress, this will destroy uncommitted changes):

$ git rm --cached -r .
$ git reset --hard

Again - do not do this to a working directory with uncommitted
work in it.
2018-06-14 16:08:34 -06:00
Anthony Fok 23d5fc82ee Update to Go 1.9.7 and Go 1.10.3
for Travis CI, Docker and Snap
2018-06-14 15:50:25 -06:00
Ryan Skoblenick 8531ec7ca3 Update Dockerfile to a multi-stage build
- Hugo container is based on SCRATCH to further reduce the footprint
  and the vulnerability surface
- Update Alpine image to 3.7 in the build container
- Update Go Lang to 1.10 in the build container
- Add .dockerignore file per the Docker best practices

Closes #4154, #4155, #4157
2018-06-14 15:47:04 -06:00
Anthony Fok 9f27091e10 tpl: Support text/template/parse API change in go1.11
Thanks to @rjoleary for the investigation.

Fixes #4784
2018-06-14 08:58:42 +02:00
hugoreleaser 187621ae24 releaser: Prepare repository for 0.43-DEV
[ci skip]
2018-06-13 10:19:00 +00:00
hugoreleaser 4172a835e5 releaser: Add release notes to /docs for release of 0.42.1
[ci skip]
2018-06-13 10:16:33 +00:00
hugoreleaser fc97388968 releaser: Bump versions for release of 0.42.1
[ci skip]
2018-06-13 10:16:33 +00:00
Bjørn Erik Pedersen d67e843c12 Release 0.42.1 2018-06-13 12:11:30 +02:00
hugoreleaser 6e33c557b0 releaser: Add release notes draft for 0.42.1
Rename to *-ready.md to continue. [ci skip]
2018-06-13 10:09:45 +00:00
Bjørn Erik Pedersen 128f14efad hugolib: Reset the global pages cache on server rebuilds
In Hugo 0.42, this could lead to errors of type `runtime error: invalid memory address or nil pointer dereference` in some rare situations.

Note that this was also an issue before 0.42, but the symptom was then potentially stale list content on rebuilds on content changes.

This commit also improves the above error message.

Fixes #4845
2018-06-13 12:00:30 +02:00
Bjørn Erik Pedersen 34ee27a78b commands: Do not fail server build when /static is missing
This was a un-intended change in Hugo 0.42. Most sites will have a static directory so this should not be a big issue, but this commit will revert back to old behaviour.

Fixes #4846
2018-06-13 08:48:20 +02:00
hugoreleaser 0f1fc01ef2 releaser: Prepare repository for 0.43-DEV
[ci skip]
2018-06-12 08:45:06 +02:00
Bjørn Erik Pedersen f32ccd018f Merge tag 'v0.42'
releaser: 0.42 [ci skip]
2018-06-12 08:42:25 +02:00
188 changed files with 6524 additions and 1722 deletions
+8
View File
@@ -0,0 +1,8 @@
*.md
*.log
*.txt
.git
.github
.circleci
docs
examples
+8
View File
@@ -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
+2
View File
@@ -15,5 +15,7 @@ vendor/*/
*.debug
coverage*.out
dock.sh
GoBuilds
dist
+6 -3
View File
@@ -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:
+6
View File
@@ -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
Regular → Executable
+19 -15
View File
@@ -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" ]
Generated
+295 -17
View File
@@ -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
+20
View File
@@ -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"
+8 -2
View File
@@ -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
Executable
+2
View File
@@ -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
+107 -14
View File
@@ -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
-5
View File
@@ -235,11 +235,6 @@ Single: {{ .Title }}
List: {{ .Title }}
`)
writeFile(t, filepath.Join(d, "static", "my.txt"), `
MyMy
`)
return d, nil
+75 -33
View File
@@ -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 := ""
+3 -1
View File
@@ -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")
+1 -1
View File
@@ -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")
+20 -8
View File
@@ -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)
}
+23
View File
@@ -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++ {
+135
View File
@@ -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")
}
}
+109
View File
@@ -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)
}
}
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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 {
+3
View File
@@ -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)
+56 -5
View File
@@ -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
+1 -1
View File
@@ -70,7 +70,7 @@ twitter = "GoHugoIO"
[params]
description = "The worlds 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)
+19
View File
@@ -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 wont 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).
+7 -4
View File
@@ -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.
+3
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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:
Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

+2 -2
View File
@@ -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)
+100
View File
@@ -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)
Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

@@ -0,0 +1,108 @@
---
date: 2018-07-04
title: "Lets celebrate Hugos 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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

+2 -2
View File
@@ -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.
+4 -4
View File
@@ -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:
+1 -1
View File
@@ -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"
+3 -3
View File
@@ -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 }}
+5 -5
View File
@@ -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"
+1 -1
View File
@@ -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'))
}
+8 -8
View File
@@ -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;
+85
View File
@@ -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
+3 -3
View File
@@ -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)
+1 -1
View File
@@ -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: "",
}
+15
View File
@@ -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) {
+9
View File
@@ -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
+3
View File
@@ -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
}
+84
View File
@@ -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
}
+96
View File
@@ -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()
}
+53
View File
@@ -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)
}
+4 -3
View File
@@ -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 -1
View File
@@ -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.
+1
View File
@@ -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")
+8 -2
View File
@@ -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"])
+185 -55
View File
@@ -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
+167 -6
View File
@@ -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")
+5 -10
View File
@@ -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
+5
View File
@@ -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 {
+4 -4
View File
@@ -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)
}
+2 -2
View File
@@ -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")
+29 -18
View File
@@ -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
}
+1 -1
View File
@@ -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)
}
+3 -3
View File
@@ -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)
+9 -4
View File
@@ -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}
+7 -9
View File
@@ -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" }}
+1 -1
View File
@@ -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)
}
}
+23 -6
View File
@@ -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)
+6 -2
View File
@@ -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())
}
+4 -4
View File
@@ -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 != "") {
+27
View File
@@ -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")
+1 -1
View File
@@ -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
+3 -3
View File
@@ -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)
+9 -1
View File
@@ -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) {
+5
View File
@@ -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())
}
+32 -5
View File
@@ -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 ""
+4
View File
@@ -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)
+63 -33
View File
@@ -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
}
+243
View File
@@ -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(&#34;demo&#34;).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-&#43;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)
}
}
+11 -10
View File
@@ -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
}
}
+2
View File
@@ -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]

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