Compare commits

..

574 Commits

Author SHA1 Message Date
hugoreleaser 58c56e9ded releaser: Add release notes to /docs for release of 0.57.1
[ci skip]
2019-08-15 18:52:49 +00:00
hugoreleaser 484106cfa2 releaser: Bump versions for release of 0.57.1
[ci skip]
2019-08-15 18:52:49 +00:00
hugoreleaser 4d60a920fa releaser: Add release notes for 0.57.1
[ci skip]
2019-08-15 18:52:49 +00:00
Bjørn Erik Pedersen 6ccf50ea7b hugolib: Fix draft etc. handling of _index.md pages
We will need to revisit this with a proper spec, but this commit makes sure that draft/expiryDate etc. set in front matter on _index.md content files that should disable the page will:

* Not crash
* Make the rendered page not render any `.Content`.

Fixes #6222
Fixes #6210
2019-08-15 20:41:48 +02:00
Bjørn Erik Pedersen 67524c9936 Fix mainSections logic
Fixes #6217
2019-08-15 20:21:57 +02:00
Bjørn Erik Pedersen 952a319496 Fix live reload mount logic with sub paths
Fixes #6209
2019-08-15 19:14:22 +02:00
Bjørn Erik Pedersen 321418f22a modules: Disable "auto tidy" for now
See #6115
2019-08-15 16:50:02 +02:00
Bjørn Erik Pedersen 7f3aab5ac2 hugolib: Recover and log panics in content init
See #6210
2019-08-15 10:56:49 +02:00
Bjørn Erik Pedersen 028b992611 hugolib: Add some outputs tests
See #6210
2019-08-15 10:30:37 +02:00
Bjørn Erik Pedersen 9475f61a37 hugolib: Fix taxonomies vs expired
In Hugo 0.57 we needed to delay the page metadata initialization until we had built the page graph.

This introduced a regression in that we now created taxonomy entries for expired pages.

This fixes that by moving the "should not build" filter before we assemble the taxonomies.

Fixes #6213
2019-08-15 09:52:08 +02:00
HyeonGyu Lee ea9261e856 commands: Make sure the hugo field is always initialized before it's used
Wrap the field to make it accessible after initialization.

Fixes #6193
2019-08-15 09:33:47 +02:00
hugoreleaser 37f5929803 releaser: Prepare repository for 0.58.0-DEV
[ci skip]
2019-08-14 08:13:53 +00:00
hugoreleaser 9b00e64765 releaser: Add release notes to /docs for release of 0.57.0
[ci skip]
2019-08-14 08:05:33 +00:00
hugoreleaser ab849fcc98 releaser: Bump versions for release of 0.57.0
[ci skip]
2019-08-14 08:05:33 +00:00
hugoreleaser 8a4c080cc5 Add release notes for 0.57.0 2019-08-14 09:56:35 +02:00
Bjørn Erik Pedersen 4644b95bd5 hugolib: Remove temporary warning 2019-08-14 08:11:39 +02:00
Bjørn Erik Pedersen 1089cfe4e1 Add FileInfo to resources created with resources.Match etc.
Without it, image resize fails.

See #6190
2019-08-13 18:45:08 +02:00
Bjørn Erik Pedersen cd575023af Improve the server assets cache invalidation logic
Fixes #6199
2019-08-13 18:09:46 +02:00
Bjørn Erik Pedersen 6315098104 modules: Do not fail build on errors in theme.toml
Fixes #6162
2019-08-13 18:09:11 +02:00
Bjørn Erik Pedersen b64617fe4f Add resources.Match and resources.GetMatch
Fix #6190
2019-08-13 11:44:20 +02:00
Bjørn Erik Pedersen 17ca8f0c4c commands: Fix faulty -h logic in hugo mod get
Fixes #6197
2019-08-13 11:17:19 +02:00
Bjørn Erik Pedersen 9e57182705 tests: Convert from testify to quicktest 2019-08-12 13:26:32 +02:00
Christian Muehlhaeuser 6027ee1108 Avoid unnecessary conversions
No need to convert these types.
2019-08-10 20:12:38 +02:00
Christian Muehlhaeuser c577a9ed23 Fixed ineffectual assignments
Dropped/fixed ineffectual assignments after static code analysis.
2019-08-10 20:11:51 +02:00
Christian Muehlhaeuser a93cbb0d6c Simplify code
- Use bytes.Equal instead of bytes.Compare
- Omit range's value where it's unused
2019-08-10 20:10:32 +02:00
Bjørn Erik Pedersen bd98182dbd Implement cascading front matter
Fixes #6041
2019-08-10 20:07:42 +02:00
Christian Muehlhaeuser e88d798990 Fixed tautological error conditions
Drop error & nil checks where the value can not have changed.
2019-08-10 01:04:43 +02:00
XhmikosR c0eef3b401 Use the SVG logo in README.md 2019-08-09 16:58:41 +02:00
Bjørn Erik Pedersen 166a394a2f Fix static sync issue with virtual mounts
Fixes #6165
2019-08-09 10:18:28 +02:00
Bjørn Erik Pedersen 8243952046 Add a branch bundle test case
See #6173
2019-08-09 09:44:41 +02:00
Bjørn Erik Pedersen 7ff0a8ee9f Simplify page tree logic
This is preparation for #6041.

For historic reasons, the code for bulding the section tree and the taxonomies were very much separate.

This works, but makes it hard to extend, maintain, and possibly not so fast as it could be.

This simplification also introduces 3 slightly breaking changes, which I suspect most people will be pleased about. See referenced issues:

This commit also switches the radix tree dependency to a mutable implementation: github.com/armon/go-radix.

Fixes #6154
Fixes #6153
Fixes #6152
2019-08-08 20:13:39 +02:00
Bjørn Erik Pedersen df374851a0 hugolib: Add some more site benchmarks 2019-08-08 11:04:47 +02:00
HyeonGyu Lee a843ca53b5 transform/urlreplacers: Cache the next position of urlreplacer.prefix
Improved performance due to `bytes.Index` repeated calls

Fixes #5942
2019-08-06 17:46:20 +02:00
Bjørn Erik Pedersen 02397e76ce postcss: Fix no-map vs noMap discrepancy
Fixes #6166
2019-08-05 13:48:58 +02:00
Bjørn Erik Pedersen de87624241 hugolib: Fix output format handling of mix cased page kinds
Fixes #4528
2019-08-05 11:22:03 +02:00
Bjørn Erik Pedersen 9ef4dca361 hugolib: Fix broken test 2019-08-01 22:09:58 +02:00
Bjørn Erik Pedersen 2d1d33673d tpl: Regenerate templates 2019-08-01 14:05:20 +02:00
Christian Oliff be0d4efc3d tpl: Always load GitHub Gists over HTTPS 2019-08-01 13:57:41 +02:00
XhmikosR f7f549e3a7 Fix assorted typos 2019-08-01 13:55:48 +02:00
Shawn Smith 6512d128c6 common/collections: Fix typo 2019-08-01 13:52:05 +02:00
Robin Andersson b8758de19e examples: Fix multilingual example compatibility with latest version 2019-08-01 13:49:07 +02:00
Bjørn Erik Pedersen 53077b0da5 Merge pull request #6149 from bep/sort-caseinsensitive
Implement lexicographically string sorting
2019-08-01 10:19:19 +02:00
hugoreleaser a4f96a9d8c releaser: Prepare repository for 0.57.0-DEV
[ci skip]
2019-07-31 12:56:14 +00:00
hugoreleaser f637a1eace releaser: Add release notes to /docs for release of 0.56.3
[ci skip]
2019-07-31 12:48:33 +00:00
hugoreleaser 9433c3c706 releaser: Bump versions for release of 0.56.3
[ci skip]
2019-07-31 12:48:33 +00:00
hugoreleaser 7b448340c0 releaser: Add release notes for 0.56.3
[ci skip]
2019-07-31 12:48:33 +00:00
Bjørn Erik Pedersen 0e086785fa hugolib: Fix bundle header clone logic
Fixes #6136
2019-07-31 14:20:24 +02:00
Bjørn Erik Pedersen 02b947eaa3 docs: Regenerate CLI docs 2019-07-31 12:11:22 +02:00
Bjørn Erik Pedersen d7c233afee commands: Add "hugo config mounts" command
This prints the effective file mounts in a project.

Fixes #6144
2019-07-31 12:10:05 +02:00
Bjørn Erik Pedersen 45ee8a7a52 commands: Cleanup the hugo config command
Most importantly filter out some keys not relevant for the end user.

See #6144
2019-07-31 12:10:05 +02:00
Bjørn Erik Pedersen 4b6c5eba30 Move the mount duplicate filter to the modules package
Also simplify the mount validation logic. There are plenty of ways a user can create mount configs that behaves oddly.
2019-07-31 12:10:05 +02:00
Bjørn Erik Pedersen edf9f0a354 Allow overlap in module mounts
Fixes #6146
2019-07-31 12:10:05 +02:00
Bjørn Erik Pedersen 36220851e4 Fix self-mounts on the main project
Fixes #6143
2019-07-31 12:10:05 +02:00
hugoreleaser 53ade40ba4 releaser: Prepare repository for 0.57.0-DEV
[ci skip]
2019-07-30 09:12:29 +00:00
hugoreleaser 5f033a7fcc releaser: Add release notes to /docs for release of 0.56.2
[ci skip]
2019-07-30 09:05:09 +00:00
hugoreleaser 2e6da00679 releaser: Bump versions for release of 0.56.2
[ci skip]
2019-07-30 09:05:09 +00:00
hugoreleaser 06f29068f8 releaser: Add release notes for 0.56.2
[ci skip]
2019-07-30 09:05:09 +00:00
Bjørn Erik Pedersen 84bc8d84e4 Add some more content language test assertions
See #6136
2019-07-30 09:58:58 +02:00
Bjørn Erik Pedersen 6eca0a3dee commands: Fix config reloading in Vim and similar
The config path was simplified in Hugo 0.56.0 to support more config dirs/files (go.mod etc.),
and the new code path assumed that every file change would trigger a `Write` event.

This is not true for Vim etc. which triggers a `Chmod` and then a `Rename`.

Lesson learned: Be really careful changing Os/editor specific code without proper tests.

Fixes #6139
2019-07-30 09:35:01 +02:00
Bjørn Erik Pedersen e28bd4c0f8 Fix Jekyll import
Fixes #6131
2019-07-30 09:14:14 +02:00
Bjørn Erik Pedersen 2c7c361091 releaser: Create a proper automated title for patch releases
Fixes #6126
2019-07-30 09:02:03 +02:00
Bjørn Erik Pedersen c62bbf7b11 Fix image format detection for upper case extensions, e.g. JPG
This regression was introduced in 0.56.0.

Fixes #6137
2019-07-30 08:53:50 +02:00
Bjørn Erik Pedersen 00a238e32c Fix i18n project vs theme order
Fixes #6134
2019-07-29 19:10:15 +02:00
Bjørn Erik Pedersen e5f9602459 Add proper error message when receiving nil in Resource transformation
Closes #6128
2019-07-29 09:59:39 +02:00
Bjørn Erik Pedersen 9f497e7b5f Merge branch 'release-0.56.1' 2019-07-28 17:06:46 +02:00
hugoreleaser 3ad16af6f8 releaser: Prepare repository for 0.57.0-DEV
[ci skip]
2019-07-28 15:05:28 +00:00
Niklas Fasching 56908509eb deps: Update go-org to v0.1.2
fixes superfluous whitespace around code blocks due to misuse of pre tag
2019-07-28 16:52:27 +02:00
hugoreleaser 0ad218afc5 releaser: Add release notes to /docs for release of 0.56.1
[ci skip]
2019-07-28 14:51:43 +00:00
hugoreleaser b5fda703f9 releaser: Bump versions for release of 0.56.1
[ci skip]
2019-07-28 14:51:43 +00:00
hugoreleaser 447700228c releaser: Add release notes for 0.56.1
[ci skip]
2019-07-28 14:51:43 +00:00
Bjørn Erik Pedersen 544f826dd6 releaser: Make it a one click release for patch releases 2019-07-28 15:05:15 +02:00
Bjørn Erik Pedersen e393c6290e common/maps: Do not return error on params dot access on incompatible types
This error was introduced in 0.56 and has shown some site breakage in the wild.

Fixes #6121
2019-07-28 13:03:12 +02:00
Bjørn Erik Pedersen 93d02aabe6 resources: Fix image Width/Height regression
Fixes #6120
2019-07-28 12:34:18 +02:00
Bjørn Erik Pedersen 508db1906b Revert "cache/filecache: Skip resource cache init if the fs is missing"
I need to revisit this.

This reverts commit da4c4a7789.
2019-07-26 16:29:33 +02:00
Bjørn Erik Pedersen e5fe378925 modules: Set GO111MODULE=on
Fixes #6114
2019-07-26 10:55:50 +02:00
Bjørn Erik Pedersen da4c4a7789 cache/filecache: Skip resource cache init if the fs is missing
Fixes #6113
2019-07-26 10:45:07 +02:00
hugoreleaser b4827fbb14 releaser: Prepare repository for 0.57.0-DEV
[ci skip]
2019-07-25 16:45:08 +00:00
hugoreleaser 45f4eb9846 releaser: Add release notes to /docs for release of 0.56.0
[ci skip]
2019-07-25 16:37:58 +00:00
hugoreleaser 263f86d456 releaser: Bump versions for release of 0.56.0
[ci skip]
2019-07-25 16:37:58 +00:00
Bjørn Erik Pedersen bed75108e4 Update release notes 2019-07-25 18:29:55 +02:00
hugoreleaser 8d7006d853 releaser: Add release notes draft for 0.56.0
Rename to *-ready.md to continue. [ci skip]
2019-07-25 15:58:15 +00:00
Bjørn Erik Pedersen e5b6e2085a resources: Fix test on Windows 2019-07-25 16:54:05 +02:00
Bjørn Erik Pedersen 77bf2991b1 docs: Add Hugo Modules docs 2019-07-25 13:21:30 +02:00
Bjørn Erik Pedersen e5f2299741 Block symlink dir traversal for /static
This is in line with how it behaved before, but it was lifted a little for the project mount for Hugo Modules,
but that could create hard-to-detect loops.
2019-07-25 11:27:25 +02:00
Bjørn Erik Pedersen 87a07282a2 modules: Gofmt 2019-07-24 23:31:30 +02:00
Bjørn Erik Pedersen 882d678bbf modules: Rename disabled => disable in config 2019-07-24 21:46:24 +02:00
Bjørn Erik Pedersen fa28df1058 hugolib: Fix broken test 2019-07-24 19:50:34 +02:00
Bjørn Erik Pedersen 072aa7f110 hugolib: Add a symdiff test 2019-07-24 10:05:09 +02:00
Bjørn Erik Pedersen 215d2ed883 docs: Regenerate CLI docs 2019-07-24 09:40:41 +02:00
Bjørn Erik Pedersen 23adc0c2d9 docs: Regenerate data helpers 2019-07-24 09:39:12 +02:00
Bjørn Erik Pedersen 461b0e066f Merge commit '5cb4e46cfa65bddae8ac5344540e3dd1e4c266b2' 2019-07-24 09:36:50 +02:00
Bjørn Erik Pedersen 5cb4e46cfa Squashed 'docs/' changes from 540aeddc7..41aae7abb
41aae7abb Better deploy script example

git-subtree-dir: docs
git-subtree-split: 41aae7abb3
2019-07-24 09:36:50 +02:00
Bjørn Erik Pedersen 9f5a92078a Add Hugo Modules
This commit implements Hugo Modules.

This is a broad subject, but some keywords include:

* A new `module` configuration section where you can import almost anything. You can configure both your own file mounts nd the file mounts of the modules you import. This is the new recommended way of configuring what you earlier put in `configDir`, `staticDir` etc. And it also allows you to mount folders in non-Hugo-projects, e.g. the `SCSS` folder in the Bootstrap GitHub project.
* A module consists of a set of mounts to the standard 7 component types in Hugo: `static`, `content`, `layouts`, `data`, `assets`, `i18n`, and `archetypes`. Yes, Theme Components can now include content, which should be very useful, especially in bigger multilingual projects.
* Modules not in your local file cache will be downloaded automatically and even "hot replaced" while the server is running.
* Hugo Modules supports and encourages semver versioned modules, and uses the minimal version selection algorithm to resolve versions.
* A new set of CLI commands are provided to manage all of this: `hugo mod init`,  `hugo mod get`,  `hugo mod graph`,  `hugo mod tidy`, and  `hugo mod vendor`.

All of the above is backed by Go Modules.

Fixes #5973
Fixes #5996
Fixes #6010
Fixes #5911
Fixes #5940
Fixes #6074
Fixes #6082
Fixes #6092
2019-07-24 09:35:53 +02:00
Bjørn Erik Pedersen 47953148b6 deps: Tidy 2019-07-23 00:13:46 +02:00
Bjørn Erik Pedersen 2fc0abd22a Fix livereload for @import case
Fixes #6106
2019-07-22 13:54:24 +02:00
peaceiris 53da3881de releaser: Remove unused function
gitLog function is not used
2019-07-20 12:33:30 +02:00
Bjørn Erik Pedersen ac101aba4f deps: Update xerrors
To get it to build on Go 1.13.
2019-07-19 09:09:04 +02:00
Bjørn Erik Pedersen 58a47ccde0 travis: Ignore errors in go mod download
It sometimes fails with 502 errors, but the dependency download later when tests are run.
2019-07-17 15:35:31 +02:00
Bjørn Erik Pedersen 95b1d3013b deps: Update Chroma
Fixes #6088
2019-07-16 21:08:18 +02:00
Eric Selin a5604e18b0 resource/postcss: Change postcss to check for local installation under node_modules/.bin
Fixes #5091
2019-07-16 10:02:20 +02:00
Bjørn Erik Pedersen 05d0eddd2b Merge commit '35febb2e2a3780c3338a2665fddea7dda28a17f4' 2019-07-15 23:50:56 +02:00
Bjørn Erik Pedersen 35febb2e2a Squashed 'docs/' changes from 0c3c04fd6..540aeddc7
540aeddc7 Small fixes for Open Graph internal template
47bbb3fd7 Bump lodash.template from 4.4.0 to 4.5.0 in /themes/gohugoioTheme
64c1cc3db Update title.md
36c27be37 Update configuration.md
4627cfe9a added commento.io to the list
341d9d945 Fix link to font assets
4a6876dec Small typo fix in configuration.md
736d6ddd7 Add bypasscensorship.org showcase.
de7ab636b Use actual width in image processing examples
6c88ab2da Update directory-structure.md
88f559e81 Fix spelling mistakes
7bd427141 Rephrase four points from Hosting on Github docs
bce1b1832 Fix typos for absLangURL and relLangURL functions
abab57335 Update directory-structure.md
a6716f2ed Fix metadata list
5ef61d912 Fix typo
d9e467b8b Actuall show how to generate pages in quickstart
0aadc92e7 Update link to issue theme submission
b1902faaa Remove incorrect note about Hugo snap --classic use.
d451e7362 Update documentation on page variables
ab2233c39 Update publish_to_ghpages.sh
7c120346b Fix typo in syntax highlighting page.
f01dc555d Correct some typos
be825ade9 Videos plural produces og:video
bc962fbdf Updated installing.md file to include linuxbrew installation
08c9f8fc8 Mention 1 MB file size limit in readFile doc
7145e29e2 Fixes bad URL in menus.md for .hasChildren * part of issue #814
0edbd59c4 add warning about .Hugo variable being deprecated
77f74ced5 fix: typo in output-formats.md
f71b67f34 Correct typo "RFC339" to "RFC1123Z" (#837)
424c0207a baseurl -> baseURL
f4e24a03e Render launched; no invite code needed
37e1101d3 Add Render hosting guide
01d0a6481 Bump js-yaml from 3.12.0 to 3.13.1 in /themes/gohugoioTheme
edb7b36b0 Link site hierarchy schema to larger version
af72acda0 Fixes typo in fingerprint.md
0a738467b Adding required XML tag to sample templates
52211f656 Remove note that XML declaration is not needed
a39b9ac87 Adding required XML tag to sample templates
1a44d8a50 Add configuration information for Azure
5b7c6b5a1 remove google plus
5a64301ba HTTPS link to yaml.org/spec/

git-subtree-dir: docs
git-subtree-split: 540aeddc79
2019-07-15 23:49:41 +02:00
Bjørn Erik Pedersen c624a77992 tpl/collections: Add Merge function
Merges two maps recursively and returns a new one. Merge is case-insensitive.

Fixes #5992
2019-07-15 20:47:35 +02:00
Niklas Fasching 020086cb2b Add org to front matter formats
actually it's 5, there's also csv. Afaict it's not meant to be used in that way
though so let's just add org.
2019-07-10 11:14:37 +02:00
Niklas Fasching 8524baee16 deps: Update go-org
- added support for anonymous footnotes
- changed source block html output to more closely resemble emacs org-export
  output
2019-07-10 11:14:37 +02:00
Kaushal Modi 90b0127f63 Fix typo s/Meny/Menu/ 2019-07-09 17:28:53 +02:00
Bjørn Erik Pedersen 7611078dae hugolib: Add testfile to .gitignore 2019-07-07 13:01:30 +02:00
Bjørn Erik Pedersen b2a3d4644b tpl/tplimpl: Regenerate templates 2019-06-26 22:39:32 +02:00
Mark Mandel 88c8a15be1 Pagination - do not render href if no next item
When using a html link checker with Hugo, this template consistently causes errors, as it renders `href=""` attributes when next/previous is disabled.

This change makes it so that the `href` attribute is not rendered at all if `HasNext` is false - which is better semantically, and makes link checking far easier.
2019-06-26 22:38:30 +02:00
Justin Van Patten 59c4bc52ed Include path to source page in non-relative ref/relref warning
We occasionally see warnings when building our site:

```
WARN 2019/06/25 23:07:08 make non-relative ref/relref page reference(s) in page %q absolute, e.g. {{< ref "/blog/my-post.md" >}}
```

But the `%q` value is missing, making it difficult to track down the source of the warning.

This change addresses that, by including the source path in the warning:

```
WARN 2019/06/25 23:07:31 make non-relative ref/relref page reference(s) in page "blog/my-post/index.md" absolute, e.g. {{< ref "/blog/my-post.md" >}}
```
2019-06-26 09:25:12 +02:00
Bjørn Erik Pedersen dc1d4a9200 hugolib: Add another site benchmark
Also rework test test setup so the benchmarks can also be run as tests, which
is useful when creating new benchmarks an make sure they ... work.
2019-06-26 09:09:40 +02:00
Joshua Arnott de7b9475c0 commands: Remove comma after URL in new site output
Some terminals include the comma in the URL when hyperlinking, resulting in a 404
2019-06-19 23:03:30 +02:00
Bjørn Erik Pedersen d6ca728ff3 deps: Revert to an older version of Blackfriday
See #6040
2019-06-16 22:13:23 +02:00
Christian Oliff ff10aa522d examples: Update link to prevent redirect 2019-06-12 13:56:05 +02:00
Christian Oliff 9f258d2b8f examples: Update URLs 2019-06-12 12:29:19 +02:00
Bjørn Erik Pedersen 31c9c690fd releaser: Update Go Releaser and consolidate the 2 configs
Fixes #5949
2019-06-11 00:02:06 +02:00
Bjørn Erik Pedersen e8a716b23a tpl/collections: Fix slice type handling in sort
The `sort` template func was producing a `[]page.Page` which did not work in `.Paginate`.

Fixes #6023
2019-06-10 08:32:44 +02:00
Bjørn Erik Pedersen 3e6cb2cb77 hugolib: Fix bundle path when slug is set
Fixes #4870
2019-06-09 16:55:08 +02:00
Bjørn Erik Pedersen 8d898ad667 tpl/collections: Unwrap any interface value in sort and where
Hugo `0.55.0` introduced some new interface types for `Page` etc.

This worked great in general, but there were cases where this would fail in `where` and `sort`.

One such example would be sorting by `MenuItem.Page.Date` where `Page` on `MenuItem` was a small subset of the bigger `page.Page` interface.

This commit fixes that by unwrapping such interface values.

Fixes #5989
2019-06-09 16:54:36 +02:00
Niklas Fasching fad183c4ae Refactor Org mode front matter: Introduce '#+KEY[]:' array notation
Hugo requires some front matter values to be arrays (e.g. for taxonomies).
Org mode front matter syntax (`#+KEY: VALUE`) does however not support anything
but string values normally - which is why goorgeous hardcoded the values for
the keys tags, categories & aliases to be parsed as string arrays. This causes
problems with custom taxonomies.

A simple thing we can do instead is make keywords ending in '[]' be parsed as
string arrays.
2019-06-08 10:13:00 +02:00
Niklas Fasching b6867bf806 Improve Org mode support: Replace goorgeous with go-org
Sadly, goorgeous has not been updated in over a year and still has a lot of
open issues (e.g. no support for nested lists).

go-org fixes most of those issues and supports a larger subset of Org mode
syntax.
2019-06-08 10:13:00 +02:00
Robert van Gent 9df57154ee deploy: Default --target to the first deployment target 2019-06-08 00:41:59 +02:00
Vincent Fiduccia 35abce27ca Add safety barrier between concatenated javascript resources 2019-06-02 10:23:48 +02:00
Robert van Gent 8914fe7ed7 Update CLI doc for "long" form 2019-05-31 22:08:12 +02:00
Robert van Gent b0f536fb27 Drop dashes in http header matcher attributes; other changes from code review 2019-05-31 22:08:12 +02:00
Robert van Gent 1384d77a04 docs: Add documentation for "hugo deploy" 2019-05-31 22:08:12 +02:00
Robert van Gent 5e83f42562 deploy: remove TODO comment about subfolders; handled by GoCDK blob URLs 2019-05-30 22:42:44 +02:00
Robert van Gent b376b2685a deps: Update gocloud.dev to v0.15.0. 2019-05-30 22:07:04 +02:00
Anton Harniakou fb007e9ae5 tpl/collections: Convert numeric values to float64 and compare them
Fixes #5685
2019-05-30 11:32:58 +02:00
Bjørn Erik Pedersen 4c560020bc resources: Return nil when not found in resources.Get
Fixes #5999
2019-05-29 22:33:03 +02:00
Bjørn Erik Pedersen d44d3ea898 deps: Update Viper
This will hopefully improve the dependency/install problems we have seen lately.

See #5954
2019-05-28 16:11:09 +02:00
Bruno Amaral d1cf53f5f4 Remove references to Google+
According to google, this is no longer supported and structured data should be used instead: https://support.google.com/webmasters/answer/6083347?hl=en
2019-05-26 19:24:45 +02:00
Bjørn Erik Pedersen 811ee996a6 Update gitmap to get CommitDate field 2019-05-25 10:43:09 +02:00
Bjørn Erik Pedersen 4f61a926f6 Merge commit 'be04ece8590f775a52ea167fbe4555753e8c5211' 2019-05-25 10:41:51 +02:00
Bjørn Erik Pedersen be04ece859 Squashed 'docs/' changes from 50c02516c..0c3c04fd6
0c3c04fd6 Merge pull request #827 from BCNelson/range-patch
b14dd3505 Updated link to Go Docs to more helpful page
c3d83149a Boolean Values in the Where Function
56d4cea3c hugo-extended no longer the default
e9b15c698 Update index.md
11de98e0d Release 0.55.6
d2def17f4 Merge branch 'release-0.55.6'
115457b70 releaser: Add release notes to /docs for release of 0.55.6
25855690d Fix misspelling
fc5e43db2 Update 404.md with Netlify Instructions
9f684318f Removed mentioning of removed --stepAnalysis command line option
86ba9071c Related md5 and aliases for sha1/sha256
15f48966a Added related sha function into the See Also
5e08b6930 Fix date description
908e87ab2 Revert docs/config.toml changes
c925646cf Add a "deploy" command
e765084dd Update sectionvars.md
e08be4d81 Update sectionvars.md
7c9653cbe Hugo 0.55.5
1035ff028 releaser: Add release notes to /docs for release of 0.55.5
cda21f9d4 docs: Regenerate docs helper

git-subtree-dir: docs
git-subtree-split: 0c3c04fd63
2019-05-25 10:41:23 +02:00
Ruslan Nasonov 5b4b8bb3c1 commands: Create new 'hugo list all' command
New:
- command `hugo list all`, return all posts meta in csv format

Refactoring:
- move common parts in commands/list.go to function `buildSites`
- change way to detect path to content

See #5904
2019-05-25 00:09:51 +02:00
Brice BERDAH 2278b0eb02 Medium -> Hugo exporting tool
Suggesting a simple yet powerful tool for people looking to export their Medium blog to Hugo
2019-05-24 15:35:41 +02:00
Brian 'Redbeard' Harrington 41974303f3 dockerfile: Switch base image for final build
Switching the base image for the final build to alpine as it still
provides a minimal interface, but has a mechanism for easily including
relevant CA certificates.  This is currently pinned to a tagged version,
though since none of the underlying mechanisms are used this should
balance both remaining stable, supported for a period of time, and
providing usable functionality.

Resolves #5970
Affects #5056
2019-05-23 12:49:13 -06:00
Bjørn Erik Pedersen 6b3f1a1002 Merge branch 'release-0.55.6' 2019-05-18 10:18:23 +02:00
hugoreleaser fa8e334dae releaser: Prepare repository for 0.56.0-DEV
[ci skip]
2019-05-18 07:57:50 +00:00
hugoreleaser a5d4c82d2f releaser: Add release notes to /docs for release of 0.55.6
[ci skip]
2019-05-18 07:55:28 +00:00
hugoreleaser 32f4b38338 releaser: Bump versions for release of 0.55.6
[ci skip]
2019-05-18 07:55:28 +00:00
Bjørn Erik Pedersen 9b48c5d6bd Release 0.55.6 2019-05-18 09:50:50 +02:00
hugoreleaser 8a1873a2d5 releaser: Add release notes draft for 0.55.6
Rename to *-ready.md to continue. [ci skip]
2019-05-18 07:39:23 +00:00
Bjørn Erik Pedersen 95ce2a40e7 hugolib: Prevent parallel server rebuilds
There have been reports about infrequent paginator crashes when running the Hugo server since 0.55.0.

The reason have been narrowed down to that of parallel rebuilds.

This isn't a new thing, but the changes in 0.55.0 made it extra important to serialize the page initialization.

This commit fixes that by protecting the `Build` method with a lock when running in server mode.

Fixes #5885
Fixes #5968
2019-05-18 09:01:40 +02:00
Rodolfo Carvalho e22b3f54c3 tpl: Fix internal templates usage of safeHTMLAttr
The `safeHTMLAttr` function operates on a full attribute definition, not
just within the attribute value.

Docs: https://gohugo.io/functions/safehtmlattr/

For `opengraph.html`, run the whole `content` HTML attribute through
`safeHTMLAttr`. That will preserve `+` signs in formatted dates.

For `vimeo_simple.html`, `safeHTMLAttr` was in the context of an
attribute value, thus having no effect. In this case we could replace it
with `safeURL`, but since the code is coming from an API it is safer to
just let Go's template engine sanitize the value as it already does with
`provider_url`.

Fixes #5236 (no need to change Go upstream)
Related to #5246
2019-05-17 16:00:54 +02:00
Bjørn Erik Pedersen 71b8d8b6a4 Update to Go 1.12.5 and Go 1.11.10
Fixes #5944
2019-05-15 07:49:04 +02:00
Robert van Gent 5dc6d0df94 deploy: Add tests; fix Windows 2019-05-09 22:11:23 +02:00
Robert van Gent 527cf1ab03 deploy: Support configuration of upload order 2019-05-06 22:59:07 +02:00
Robert van Gent f4956d9aae deploy: Support invalidating a CloudFront CDN cache 2019-05-06 21:09:33 +02:00
Bjørn Erik Pedersen 2838d58b1d i18n: Move the package below /langs
To get fewer top level packages.
2019-05-04 18:25:56 +02:00
Robert van Gent f330e869e4 deploy: compute MD5 by reading if List didn't provide one 2019-05-04 09:31:34 +02:00
Robert van Gent f2b5d4de34 Revert docs/config.toml changes 2019-05-03 17:58:40 +02:00
Bjørn Erik Pedersen 0091b1f893 travis: Use proxy.golang.org 2019-05-03 17:58:40 +02:00
Robert van Gent c7165589b3 Add a "deploy" command 2019-05-03 17:58:40 +02:00
Bjørn Erik Pedersen ad5703a917 hugolib: Disable racy test
See #5926
2019-05-03 09:20:39 +02:00
hugoreleaser f94a388ad3 releaser: Prepare repository for 0.56.0-DEV
[ci skip]
2019-05-02 13:06:04 +00:00
hugoreleaser a83256b9cd releaser: Add release notes to /docs for release of 0.55.5
[ci skip]
2019-05-02 13:03:34 +00:00
hugoreleaser 77ad1336d2 releaser: Bump versions for release of 0.55.5
[ci skip]
2019-05-02 13:03:34 +00:00
Bjørn Erik Pedersen e33ed29b75 Release 0.55.5 2019-05-02 14:50:04 +02:00
hugoreleaser 2fb708d3d0 releaser: Add release notes draft for 0.55.5
Rename to *-ready.md to continue. [ci skip]
2019-05-02 12:36:11 +00:00
Bjørn Erik Pedersen cee181c3a6 docs: Regenerate docs helper 2019-05-02 14:26:02 +02:00
Bjørn Erik Pedersen b1d438ea28 Merge commit 'f147b5c53eda15982e0fe762847532eeddb7485f' 2019-05-02 14:24:13 +02:00
Bjørn Erik Pedersen f147b5c53e Squashed 'docs/' changes from 13097daf2..50c02516c
50c02516c resources: Clean
380554caf Added Pharmaseal case study
bd462671d Fix typo
0522a2595 no -> fr
f2c4671db Fix broken Netlify setup
056da25f2 Release 0.55.4
2ddca746e Merge branch 'temp554'
3d6cf9967 releaser: Add release notes to /docs for release of 0.55.4
9dc637cab Minor link fix
95186103c Release 0.55.3
b2449cd25 releaser: Add release notes to /docs for release of 0.55.3
55934c3a2 docs: Regenerate docs helper

git-subtree-dir: docs
git-subtree-split: 50c02516cb
2019-05-02 14:23:40 +02:00
Bjørn Erik Pedersen 6b76841b05 output: Fix permalink in sitemap etc. when multiple permalinkable output formats
In Hugo 0.55.0 we made AMP `permalinkable`. We also render the output formats in their natural sort order, meaning `AMP` will be rendered before `HTML`. References in the sitemap would then point to the AMP version, and this is normally not what you'd want.

This commit fixes that by making `HTML` by default sort before the others.

If this is not you want, you can set `weight` on the output format configuration.

Fixes #5910
2019-05-02 14:23:16 +02:00
Bjørn Erik Pedersen bcbed4ebda hugolib: Fix PrevInSection/NextInSection for nested sections
This was broken in Hugo 0.55.0.

Fixes #5883
2019-05-01 19:34:28 +02:00
Bjørn Erik Pedersen 009076e5ee lazy: Fix concurrent initialization order
Fixes #5901
2019-05-01 16:00:31 +02:00
David Birks 1cbb501be8 Update blackfriday to v1.5.2 2019-05-01 11:56:53 +02:00
Bjørn Erik Pedersen 66b143a01d tpl/compare: Fix nil compare in eq/ne for interface values
Fixes #5905
2019-04-29 18:43:44 +02:00
Cameron Moore 4f93f8c670 tpl: Fix hugo package name and add godocs 2019-04-27 22:44:57 +02:00
Cameron Moore f76e50118b tpl: Provide more detailed errors in Where 2019-04-27 09:54:22 +02:00
hugoreleaser f84df16fea releaser: Prepare repository for 0.56.0-DEV
[ci skip]
2019-04-25 07:41:16 +00:00
hugoreleaser 579004178b releaser: Add release notes to /docs for release of 0.55.4
[ci skip]
2019-04-25 07:38:48 +00:00
hugoreleaser f2fbca23aa releaser: Bump versions for release of 0.55.4
[ci skip]
2019-04-25 07:38:48 +00:00
Bjørn Erik Pedersen 1707f1a5f5 Release 0.55.4 2019-04-25 09:28:17 +02:00
hugoreleaser 211d667979 releaser: Add release notes draft for 0.55.4
Rename to *-ready.md to continue. [ci skip]
2019-04-25 07:26:35 +00:00
Bjørn Erik Pedersen 33c738116c hugolib: Fix shortcode version=1 logic
Fixes #5831
2019-04-24 15:35:04 +02:00
Bjørn Erik Pedersen 69a56420ae hugolib: Avoid recloning of shortcode templates
```bash
benchmark                                    old ns/op     new ns/op     delta
BenchmarkSiteNew/Bundle_with_image-4         14572242      14382188      -1.30%
BenchmarkSiteNew/Bundle_with_JSON_file-4     13683922      13738196      +0.40%
BenchmarkSiteNew/Multiple_languages-4        41912231      25192494      -39.89%

benchmark                                    old allocs     new allocs     delta
BenchmarkSiteNew/Bundle_with_image-4         57496          57493          -0.01%
BenchmarkSiteNew/Bundle_with_JSON_file-4     57492          57501          +0.02%
BenchmarkSiteNew/Multiple_languages-4        242422         118809         -50.99%

benchmark                                    old bytes     new bytes     delta
BenchmarkSiteNew/Bundle_with_image-4         3845077       3844065       -0.03%
BenchmarkSiteNew/Bundle_with_JSON_file-4     3627442       3627798       +0.01%
BenchmarkSiteNew/Multiple_languages-4        13963502      7543885       -45.97%
```

Fixes #5890
2019-04-24 12:37:57 +02:00
Bjørn Erik Pedersen 4756ec3cd8 i18n: Avoid rebuilding the Translations map for every lookup
```bash
benchmark                                                    old ns/op     new ns/op     delta
BenchmarkI18nTranslate/all-present-4                         764           757           -0.92%
BenchmarkI18nTranslate/present-in-default-4                  2578          1457          -43.48%
BenchmarkI18nTranslate/present-in-current-4                  764           766           +0.26%
BenchmarkI18nTranslate/missing-4                             3362          1103          -67.19%
BenchmarkI18nTranslate/file-missing-4                        4646          3611          -22.28%
BenchmarkI18nTranslate/context-provided-4                    2013          2014          +0.05%
BenchmarkI18nTranslate/same-id-and-translation-4             1961          784           -60.02%
BenchmarkI18nTranslate/same-id-and-translation-default-4     3717          1405          -62.20%
BenchmarkI18nTranslate/unknown-language-code-4               1775          1787          +0.68%

benchmark                                                    old allocs     new allocs     delta
BenchmarkI18nTranslate/all-present-4                         6              6              +0.00%
BenchmarkI18nTranslate/present-in-default-4                  16             10             -37.50%
BenchmarkI18nTranslate/present-in-current-4                  6              6              +0.00%
BenchmarkI18nTranslate/missing-4                             20             8              -60.00%
BenchmarkI18nTranslate/file-missing-4                        27             21             -22.22%
BenchmarkI18nTranslate/context-provided-4                    15             15             +0.00%
BenchmarkI18nTranslate/same-id-and-translation-4             12             6              -50.00%
BenchmarkI18nTranslate/same-id-and-translation-default-4     22             10             -54.55%
BenchmarkI18nTranslate/unknown-language-code-4               13             13             +0.00%

benchmark                                                    old bytes     new bytes     delta
BenchmarkI18nTranslate/all-present-4                         152           152           +0.00%
BenchmarkI18nTranslate/present-in-default-4                  1144          216           -81.12%
BenchmarkI18nTranslate/present-in-current-4                  152           152           +0.00%
BenchmarkI18nTranslate/missing-4                             2008          152           -92.43%
BenchmarkI18nTranslate/file-missing-4                        1208          600           -50.33%
BenchmarkI18nTranslate/context-provided-4                    704           704           +0.00%
BenchmarkI18nTranslate/same-id-and-translation-4             1080          152           -85.93%
BenchmarkI18nTranslate/same-id-and-translation-default-4     2073          216           -89.58%
BenchmarkI18nTranslate/unknown-language-code-4               696           696           +0.00%
```

Fixes #5892
2019-04-23 21:40:50 +02:00
Bjørn Erik Pedersen 4c3c512038 commands: Init mem profile at the end
Much more useful ...
2019-04-23 17:14:02 +02:00
Bjørn Erik Pedersen 0775c98e6c hugolib: No links for bundled pages
This fixes a bug introduced in Hugo 0.55.

Fixes #5882
2019-04-22 13:22:01 +02:00
hugoreleaser 1477fb33c9 releaser: Prepare repository for 0.56.0-DEV
[ci skip]
2019-04-20 14:25:57 +00:00
hugoreleaser 993b84333c releaser: Add release notes to /docs for release of 0.55.3
[ci skip]
2019-04-20 14:23:12 +00:00
hugoreleaser a9ee2ab58c releaser: Bump versions for release of 0.55.3
[ci skip]
2019-04-20 14:23:12 +00:00
Bjørn Erik Pedersen c85b726f8a Release 0.55.3 2019-04-20 16:12:42 +02:00
hugoreleaser 89cf7a3f4b releaser: Add release notes draft for 0.55.3
Rename to *-ready.md to continue. [ci skip]
2019-04-20 14:08:54 +00:00
Bjørn Erik Pedersen 75b16e30ec docs: Regenerate docs helper 2019-04-20 15:20:08 +02:00
Bjørn Erik Pedersen 0508ca1856 Merge commit 'a0c28c943c2f4714fa340b22a583b96f5013090b' 2019-04-20 15:19:38 +02:00
Bjørn Erik Pedersen a0c28c943c Squashed 'docs/' changes from 0f2bf195e..13097daf2
13097daf2 Update shortcode docs vs 0.55
9a7e19231 Update URL docs for relative URLs etc
bd58dd877 Adjust Output Formats docs
f3e9fbc00 Document output format `permalinkable`
3778ce689 Update partials.md
7b207a404 Partial: Update variable name in example
7d7b1f03c Document partials returning a value.
7ae93b7ca Merge branch 'temp552'
81910d266 releaser: Add release notes to /docs for release of 0.55.2
c166cef47 Fix typo
23adeb672 Point mmark links to its new repository
369a87339 Merge branch 'temp551'
eb6097e9f Bump to 0.55.1
766ee61a8 releaser: Add release notes to /docs for release of 0.55.1
a5a5f5d75 Start of taxonomy docs update for Hugo 0.55
fb5f75e9a Remove the space in `. RelPermalink`

git-subtree-dir: docs
git-subtree-split: 13097daf2e
2019-04-20 15:18:59 +02:00
Bjørn Erik Pedersen c7dd66bfe2 Fix links for non-HTML output formats
They were not correct for regular pages.

Fixes #5877
2019-04-20 15:01:20 +02:00
Bjørn Erik Pedersen 7aeeb60d7e hugolib: Add some OutputFormats.Get tests
See #5877
2019-04-19 10:52:31 +02:00
Bjørn Erik Pedersen ea529c847e Fix menu URL when multiple permalinkable output formats
In Hugo `0.55` we introduced the `permalinkable` config attribute on Output Format, default enabled for `AMP` and `HTML`.

This meant that a Page could have different `RelPermalink` and `Permalink` depending on the rendering format.

The menu `URL` did not reflect that fact.

Fixes #5849
2019-04-19 10:29:52 +02:00
Bjørn Erik Pedersen 6c80acbd5e hugolib: Add some integration tests for in/uniq using Pages
See #5875
See #5852
2019-04-19 09:07:21 +02:00
Bjørn Erik Pedersen 7fbfedf013 tpl/collections: Return error on invalid input in in
See #5875
2019-04-19 08:58:12 +02:00
Bjørn Erik Pedersen 06f56fc983 tpl/collections: Make Pages etc. work with the in func
Fixes #5875
2019-04-18 23:42:01 +02:00
Bjørn Erik Pedersen d7a67dcb51 tpl/collections: Make Pages etc. work in uniq
Fixes #5852
2019-04-18 16:50:13 +02:00
Bjørn Erik Pedersen 3e421bd47c commands: Replace IsDraft with Draft in list command
Fixes #5873
2019-04-18 09:25:40 +02:00
hugoreleaser 1028760331 releaser: Prepare repository for 0.56.0-DEV
[ci skip]
2019-04-17 12:22:55 +00:00
hugoreleaser 9d02034881 releaser: Add release notes to /docs for release of 0.55.2
[ci skip]
2019-04-17 12:20:37 +00:00
hugoreleaser ccfbb63d17 releaser: Bump versions for release of 0.55.2
[ci skip]
2019-04-17 12:20:37 +00:00
Bjørn Erik Pedersen fcd63a865e Release 0.55.2 2019-04-17 14:13:46 +02:00
hugoreleaser 7ace59d5bf releaser: Add release notes draft for 0.55.2
Rename to *-ready.md to continue. [ci skip]
2019-04-17 12:10:37 +00:00
Bjørn Erik Pedersen 35f41834ea hugolib: Add more tests for Permalinkable
See #5849
2019-04-17 13:26:27 +02:00
Bjørn Erik Pedersen 9b17cbb62a hugolib: Fix Pages reinitialization on rebuilds
Which had some unpredictable behaviour when using `.Pages` on home page etc. that had a content page.

Fixes #5833
2019-04-17 12:56:46 +02:00
Bjørn Erik Pedersen 2957795f52 tpl/tplimpl: Handle late transformation of templates
Fixes #5865
2019-04-16 21:24:09 +02:00
Bjørn Erik Pedersen 56550d1e44 hugolib: Fix shortcode namespace issue
Fixes #5863
2019-04-15 18:31:56 +02:00
Bjørn Erik Pedersen 7881b0965f hugolib: Fix false WARNINGs in lang prefix check
Add a slash to the check to make it less likely to match a valid value.

Fixes #5860
2019-04-15 17:12:29 +02:00
Bjørn Erik Pedersen 49d0a82641 hugolib: Fix bundle resource publishing when multiple output formats
The faulty logic published the bundled resources for the "first output" format.

This worked most of the time, but since the output formats list is sorted,
any output format only used for some of the pages (e.g. CSS) would not work properly.

Fixes #5858
2019-04-15 17:01:39 +02:00
Bjørn Erik Pedersen b799b12f4a hugolib: Fix panic for unused taxonomy content files
In Hugo 0.55 we connected the taxonomy nodes with their owning Page.

This failed if you had, say, a content file for a author that did not author anything in the site:

```
content/authors/silent-persin/_index.md
```

Fixes #5847
2019-04-15 13:36:05 +02:00
Bjørn Erik Pedersen 701486728e hugolib: Fix dates for sections with dates in front matter
Fixes #5854
2019-04-13 20:01:43 +02:00
Bjørn Erik Pedersen f2795d4d2c Fix WeightedPages in union etc.
We introduced a callback func() to get the owner Page in 0.55.0.

Sadly, funcs is  not comparable type in Go.

This commit replaces the func with a struct pointer that wraps the Page.

Fixes #5850
2019-04-13 18:52:38 +02:00
hugoreleaser e85c057f99 releaser: Prepare repository for 0.56.0-DEV
[ci skip]
2019-04-12 09:58:35 +00:00
hugoreleaser 223b3c2eec releaser: Add release notes to /docs for release of 0.55.1
[ci skip]
2019-04-12 09:56:13 +00:00
hugoreleaser 7ca15c7ddb releaser: Bump versions for release of 0.55.1
[ci skip]
2019-04-12 09:56:13 +00:00
Bjørn Erik Pedersen adb776b22b Release 0.55.1 2019-04-12 11:30:04 +02:00
hugoreleaser 5efdd14ebd releaser: Add release notes draft for 0.55.1
Rename to *-ready.md to continue. [ci skip]
2019-04-12 09:29:27 +00:00
Bjørn Erik Pedersen 8d7607aed1 hugolib: Add a test for parent's resources in shortcode
See #5833
2019-04-12 10:44:21 +02:00
Bjørn Erik Pedersen 4d425a86f5 Fix 2019-04-12 10:26:10 +02:00
Bjørn Erik Pedersen 9e9a1f92ba hugolib: Fix simple menu config
This stopped working in Hugo 0.55:

```bash
---
menu: "main"
---
```

This was also the case for using a slice of menu entries.

This still worked:

---
menu:
  main:
    weight: 30
---
2019-04-12 10:04:17 +02:00
Bjørn Erik Pedersen f7375c4972 Fix paginator refresh on server change
Fixes #5838
2019-04-12 09:18:59 +02:00
Yihui Xie 7966c0b5b7 Remove the space in . RelPermalink
The same fix as 3b86b4a9f5, applied to doc.
2019-04-10 22:45:20 +02:00
Bjørn Erik Pedersen cc98fc8ad2 Merge commit '8b73644d81a81e82851b7f7c90aa8ab5524fbe93' 2019-04-09 23:59:59 +02:00
Bjørn Erik Pedersen 8b73644d81 Squashed 'docs/' changes from acf70cdd9..0f2bf195e
0f2bf195e Adjust shortcodes for 0.55
48c577121 Fix deprecation warnings
ed95a341a Update index.md
e4bc8981f Release 0.55.0
25acacb91 Merge branch 'temp55'
b240e321a releaser: Add release notes to /docs for release of 0.55.0
86a190c92 docs: Add information about summary front matter variable
9d94f4340 integrity: Add support for sha384
dd3fe9f92 Add HUGO_NUMWORKERMULTIPLIER
968372d86 Add trace.out to .gitignore
660772713 Add the Fireship images to cache
41a3e19cb hugolib: Allow page-relative aliases
4497e4b12 Update faq.md
b0ee3dccc output: Add missing JSON tag
173dac0b7 docs: Regenerate docshelper data
e30473062 docs: Regenerate CLI docs
8cb775be2 Merge commit 'c2037f0c9a3a35b9db9c404f6b5f94ec8b690a53'
5bc64bf44 Make Page an interface
371ff6c12 Add skipHTML option to blackfriday config

git-subtree-dir: docs
git-subtree-split: 0f2bf195ef
2019-04-09 23:59:13 +02:00
Bjørn Erik Pedersen 3b86b4a9f5 resources/page: Fix .RSSLinke deprecation message
Closes #4427
2019-04-09 16:47:29 +02:00
Bjørn Erik Pedersen 27a8049da7 tpl/tplimpl: Replace deprecated .GetParam usage
Fixes #5834
2019-04-09 16:39:12 +02:00
hugoreleaser ee4274244b releaser: Prepare repository for 0.56.0-DEV
[ci skip]
2019-04-08 16:42:12 +00:00
hugoreleaser 4333cc77fe releaser: Add release notes to /docs for release of 0.55.0
[ci skip]
2019-04-08 16:39:45 +00:00
hugoreleaser 452cdcd7c8 releaser: Bump versions for release of 0.55.0
[ci skip]
2019-04-08 16:39:45 +00:00
Bjørn Erik Pedersen 05d614e13b Release 0.55.0 2019-04-08 18:34:48 +02:00
hugoreleaser 018cd0c9da releaser: Add release notes draft for 0.55.0
Rename to *-ready.md to continue. [ci skip]
2019-04-08 18:33:35 +02:00
Bjørn Erik Pedersen 612a06f067 Misc paginator adjustments
* Rewind paginator for server mode
* Add some more related tests.
* Replace the clumsy scratch constructs in internal paginator template with variables

See #5825
2019-04-08 11:02:12 +02:00
Bjørn Erik Pedersen 3db4a1cf7a Update to Go 1.12.2 and Go 1.11.7
Fixes #5819
2019-04-07 18:54:53 +02:00
Bjørn Erik Pedersen f34e6172cf hugolib: Log warning on relative front matter url with lang
Will do this for one version only, as there may be situations where this is the correct thing.

Also add some more related test cases.

Fixes #5818
2019-04-07 18:54:18 +02:00
Bjørn Erik Pedersen 708d4ceebd Adjust rlimit logic
Closes #5821
2019-04-07 00:53:55 +02:00
Jim McDonald ed65bda3b4 docs: Add information about summary front matter variable 2019-04-06 00:17:54 +02:00
Jim McDonald 3a62d54745 hugolib: Consider summary in front matter for .Summary
Add the ability to have a `summary` page variable that overrides
the auto-generated summary.  Logic for obtaining summary becomes:

  * if summary divider is present in content, use the text above it
  * if summary variables is present in page metadata, use that
  * auto-generate summary from first _x_ words of the content

Fixes #5800
2019-04-05 19:11:04 +02:00
Bjørn Erik Pedersen ebab291c0e resources/page: Regenerate JSON wrapper 2019-04-05 16:58:52 +02:00
Bjørn Erik Pedersen 75467cd785 resources/page: Add missing GitInfo to Page 2019-04-05 16:52:37 +02:00
Bjørn Erik Pedersen d1553b4b0f integrity: Add support for sha384
Fixes #5815
2019-04-05 16:35:07 +02:00
Bjørn Erik Pedersen 1d9dde82a0 hugolib: Fix default date assignment for sections
See #5784
2019-04-05 11:20:09 +02:00
Bjørn Erik Pedersen 87b16abd93 Add HUGO_NUMWORKERMULTIPLIER
And use that to calculate number of workers, if set, else fall back to number of logical CPUs.

Also tweak the relevant related settings to match the new setup.

Also remove the setting of `runtime.GOMAXPROCS` as this has been the default behaviour since Go 1.5.

Fixes #5814
2019-04-05 10:21:25 +02:00
Bjørn Erik Pedersen 415ca9673d hugolib: Fix the GOMAXPROCS env get
Fixes #5813
2019-04-05 09:57:43 +02:00
Bjørn Erik Pedersen 8559f5c29f Use YAML for the benchmark compare 2019-04-05 08:55:39 +02:00
Bjørn Erik Pedersen e2dc432fe2 hugolib: Fix benchmark for YAML front matter 2019-04-05 08:52:58 +02:00
Bjørn Erik Pedersen 950295516d hugolib: Buffer the render pages chan 2019-04-04 16:50:10 +02:00
Bjørn Erik Pedersen 032e6802d1 deps: Update to imaging v1.6.0
Closes #5812
2019-04-04 14:39:39 +02:00
Bjørn Erik Pedersen d0d661dffd hugolib: Re-work "fast render" logic in the new flow
Note that this fixes some "live reload" issues recently introduced in non-released code.

Closes #5811
See #5784
2019-04-04 13:51:22 +02:00
Bjørn Erik Pedersen 4494a01b79 metrics: Adjust the howSimilar logic vs strings
Also add a test.
2019-04-02 21:00:42 +02:00
Bjørn Erik Pedersen e91e222cd2 resources/page: Implement compare.ProbablyEqer for the core slices
Fixes #5808
2019-04-02 16:10:13 +02:00
Bjørn Erik Pedersen 5185fb065b hugolib: Allow relative URLs in front matter
Before this commit you would have to do this in multilingual setups:

```
---
title: "Custom!"
url: "/jp/custom/foo"
---
```

This commit allows for relative URLs, e.g:

```
---
title: "Custom!"
url: "custom/foo"
---
```

Which is obviously easier and more portable.

The meaning of relative may change to include more in the future (e.g. role based access).

Fixes #5704
2019-04-02 10:33:15 +02:00
Bjørn Erik Pedersen 92baa14fd3 hugolib: Allow page-relative aliases
Fixes #5757
2019-04-02 10:32:47 +02:00
Bjørn Erik Pedersen a55640de8e tpl: Allow the partial template func to return any type
This commit adds support for return values in partials.

This means that you can now do this and similar:

    {{ $v := add . 42 }}
    {{ return $v }}

Partials without a `return` statement will be rendered as before.

This works for both `partial` and `partialCached`.

Fixes #5783
2019-04-02 10:30:24 +02:00
Bjørn Erik Pedersen 9225db636e releaser: Remove version replacement in docs config
Fixes #5665
2019-04-01 09:11:36 +02:00
Bjørn Erik Pedersen f7ef7d3761 releaser: Adjust patch vs images logic
Fixes #5664
2019-04-01 09:07:19 +02:00
Bjørn Erik Pedersen f9d6feca08 hugolib: Fix alias path for AMP and similar
Fixes #5760
2019-03-30 16:46:51 +01:00
Bjørn Erik Pedersen 439ab0339d resources: Fix image publish ordering issue
Fixes #5730
2019-03-30 10:17:25 +01:00
Bjørn Erik Pedersen b6a60f718e output: Add missing JSON tag 2019-03-28 20:59:09 +01:00
Bjørn Erik Pedersen bfdc44964a docs: Regenerate docshelper data
Closes #5799
2019-03-28 09:49:54 +01:00
Bjørn Erik Pedersen cc8515f187 deps: Update Chroma
Closes #5780
2019-03-28 09:46:55 +01:00
Bjørn Erik Pedersen 8bfd3a54a4 hugolib: Add a simple test for jsonify of Site
Closes #5780
2019-03-28 09:45:25 +01:00
Bjørn Erik Pedersen bb533ca5e1 docs: Regenerate CLI docs
Fixes #5779
2019-03-26 18:46:01 +01:00
Bjørn Erik Pedersen 876e5f67b1 Merge commit 'c2037f0c9a3a35b9db9c404f6b5f94ec8b690a53' 2019-03-26 18:41:36 +01:00
Bjørn Erik Pedersen c2037f0c9a Squashed 'docs/' changes from 4f4d1f48c..acf70cdd9
acf70cdd9 Update CSS after removing Purge
74c0bb7f2 Remove purge from Webpack, temporarily
397b7284f Merge commit 'ccb1e38f3822e9b5e4fb89a296324daf3400f23b'
ccb1e38f3 Squashed 'themes/gohugoioTheme/' changes from 042112e2d..b7853ae70
bca84e914 Fix search box text being invisible in Safari
3f21d0957 FAQ: "this feature is not available in your current Hugo version"
d8b318f90 Remove Gulp from Victor Hugo description
09d9b4c24 Update contribute/development.md
4d0f6ba47 Theme Components Support on the Hugo Themes Site (#769)
f2ec3f2a6 Fix broken link 404
af82c56c5 config: Remove superflous param
8dc14f563 Update theme total on homepage (#765)
7838fd7e2 Update description of Atlas to reflect new changes
6abab23c6 update: total of hugo themes
1dba24fde Clarify: summaryLength uses words not characters
8c54f45d6 Add missing punctuation
d17000481 Remove Hugo Frontend caddy-plugin
a7094ae62 remove unneeded meta tags
f8b0f31c6 Fix typo in cross references
61781bab9 Fix: use gh shortcode correctly
357bfca43 Update files.md
d1d2406cc Updated Hugo convert to clarify for and not ..
86850f3c3 Fix typos in multilingual.md
b68d0d892 Fix typo
8cba70e28 Document disableAliases
00e440260 Remove extra whitespace
212d63e4c Update with latest Theme Submission Guidelines (#744)
c24ea638f Update frontends.md
942bc1972 Add one more video
2e1e1091d Update the rest of the videos
a7dc70f2b Add new installation video
1a78f8487 Hosting on AWS amplify
112420c46 Fix typo
f723425e4 Add fireship.io bio to showcase
574f43503 Fixed typo
88e0e0cb7 Release 0.54.0
303edf19e Merge branch 'temp54'
c38debebd releaser: Prepare repository for 0.55.0-DEV
5c6c56fa1 releaser: Add release notes to /docs for release of 0.54.0
f86e05950 releaser: Bump versions for release of 0.54.0
6a2684fe4 Add the default cache configuration
f75c0125a Merge commit 'f27faf9afd0a8db768a21954b8755f1bf1a14f1b'
e440dc6a4 Merge commit '5e078383a787e8b5ec3ba73f05ea4130840afbe2'
981bf53f8 Update _index.md
b54aea066 tpl: Fix reflect
41f8af5a6 Merge commit '978856e2ad12d2bcaf37bb9e31f806b30a4c42f4'

git-subtree-dir: docs
git-subtree-split: acf70cdd9f
2019-03-26 18:41:09 +01:00
Bjørn Erik Pedersen 9bc6187b83 hugolib: Do not fall back to site title if not set in content file
See #5784
2019-03-26 15:49:32 +01:00
Bjørn Erik Pedersen 10bb614a70 deps: Update Afero
Closes #5673
2019-03-26 13:12:58 +01:00
Bjørn Erik Pedersen bceda1b288 hugolib: Add a test for home page with no title
See #5784
2019-03-26 10:28:02 +01:00
Bjørn Erik Pedersen 4dae52af68 Avoid nilpointer on no File on Page
Fixes #5781
2019-03-26 10:20:40 +01:00
Bjørn Erik Pedersen 794d4052b8 herrors/errors: Improve the "feature not available" error 2019-03-26 10:15:57 +01:00
Bjørn Erik Pedersen a7ee9b0bb9 hugolib: Add String() to fileInfo
See #5784
2019-03-25 08:29:29 +01:00
Bjørn Erik Pedersen 91ef9655aa resources/page: Re-introduce .Page.Page
It was removed in 597e418cb0 -- it's not documented, but it's used in too many real sites on the web.

See #5784
2019-03-25 07:55:46 +01:00
Bjørn Erik Pedersen b5f39d23b8 all: Apply staticcheck recommendations 2019-03-24 16:14:51 +01:00
Bjørn Erik Pedersen 3011f36c27 hugolib: Remove unused slice 2019-03-24 16:14:51 +01:00
Bjørn Erik Pedersen d30e845485 Run gofmt -s 2019-03-23 20:15:06 +01:00
Bjørn Erik Pedersen 597e418cb0 Make Page an interface
The main motivation of this commit is to add a `page.Page` interface to replace the very file-oriented `hugolib.Page` struct.
This is all a preparation step for issue  #5074, "pages from other data sources".

But this also fixes a set of annoying limitations, especially related to custom output formats, and shortcodes.

Most notable changes:

* The inner content of shortcodes using the `{{%` as the outer-most delimiter will now be sent to the content renderer, e.g. Blackfriday.
  This means that any markdown will partake in the global ToC and footnote context etc.
* The Custom Output formats are now "fully virtualized". This removes many of the current limitations.
* The taxonomy list type now has a reference to the `Page` object.
  This improves the taxonomy template `.Title` situation and make common template constructs much simpler.

See #5074
Fixes #5763
Fixes #5758
Fixes #5090
Fixes #5204
Fixes #4695
Fixes #5607
Fixes #5707
Fixes #5719
Fixes #3113
Fixes #5706
Fixes #5767
Fixes #5723
Fixes #5769
Fixes #5770
Fixes #5771
Fixes #5759
Fixes #5776
Fixes #5777
Fixes #5778
2019-03-23 18:51:22 +01:00
Daniel Compton 44f5c1c14c List future and expired dates in CSV format
It is useful to see the date that a post will be published, or the date
that it has expired, to build tooling around it. This commit writes
posts and their publish/expired date as CSV.

Fixes #5610
2019-03-19 00:33:02 +01:00
Bjørn Erik Pedersen 984a73af9e Update to Go 1.12.1 and Go 1.11.6
Fixes #5755
2019-03-17 08:37:42 +01:00
Bjørn Erik Pedersen e54213f525 tpl/tplimpl: Fix mutex unlock 2019-03-16 09:40:27 +01:00
Bjørn Erik Pedersen 4a2a8afff2 commands: Fix doLiveReload logic
Fixes #5754
2019-03-14 21:39:00 +01:00
Bjørn Erik Pedersen 79d517d86c deps: Update Viper 2019-03-13 10:42:43 +01:00
Bjørn Erik Pedersen 34c49d788c hugolib: Adjust site benchmark
Having all the pages with shortcodes isn't realistic.
2019-03-12 20:27:50 +01:00
Bjørn Erik Pedersen b9e75afd6c Update to Go 1.12
Fixes #5716
2019-03-07 10:26:26 +01:00
Bjørn Erik Pedersen dfc72d61a5 Remove Gitter dev chat link
Almost every question we get there belong on https://discourse.gohugo.io/
2019-03-07 00:37:50 +01:00
Bjørn Erik Pedersen 02eaddc2fb tpl/tplimpl: Fix template truth logic
Before this commit, due to a bug in Go's `text/template` package, this would print different output for typed nil interface values:

```
{{ if .AuthenticatedUser }}User is authenticated!{{ else }}{{ end }}
{{ if not .AuthenticatedUser }}{{ else }}}User is authenticated!{{ end }}
```

This commit works around this by wrapping every `if` and `with` with a custom `getif` template func with truth logic that matches `not`, `and` and `or`.

Those 3 template funcs from Go's stdlib are now pulled into Hugo's source tree and adjusted to support custom zero values, e.g. types that implement `IsZero`.

This means that you can now do:

```
{{ with .Date }}{{ . }}{{ end }}
```

And it would work as expected.

Fixes #5738
2019-03-06 22:52:38 +01:00
grahamjamesaddis bdf47e8da8 Update Travis config to work for forked builds 2019-03-05 14:26:49 +01:00
arrtchiu 75904332f3 Add skipHTML option to blackfriday config 2019-03-04 11:27:18 +01:00
Elliott Sales de Andrade 60c0eb4e89 Update stretchr/testify to 1.3.0. 2019-03-02 10:24:21 +01:00
Berin Larson c154c2f7b2 Rewrite relative action URLS
Fixes #5701
2019-02-26 11:31:34 +01:00
Anthony Metzidis 075b17ee1d Support Docker args TAGS, WORKDIR, CGO; speed up repetitive builds 2019-02-22 11:53:17 -08:00
Bjørn Erik Pedersen b4148cd1d9 hugolib: Adjust test for Go 1.12 2019-02-19 21:47:38 +01:00
Anton Harniakou 908692fae5 Support nested keys/fields with missing values with the where function
Before this commit `where` would produce an error and bail building the
site. Now, `where` simply skips an element of a collection and does not
add it to the final result.

Closes #5637
Closes #5416
2019-02-06 19:53:34 +01:00
Bjørn Erik Pedersen 7e4b18c5ae commands: Update debouncer version 2019-02-02 14:05:28 +01:00
Iskander (Alex) Sharipov 483cf19d5d common/herrors: Fix args order in strings.TrimPrefix
Old code always returned "." or "" (if filepath.Ext(filename) returned ".").
Now it properly trims the prefix.

Signed-off-by: Iskander Sharipov <quasilyte@gmail.com>
2019-02-02 09:55:31 +01:00
Iskander (Alex) Sharipov 7201042946 tpl: Fix strings.HasPrefix args order 2019-02-02 01:26:05 +01:00
hugoreleaser 5383fe458c releaser: Prepare repository for 0.55.0-DEV
[ci skip]
2019-02-01 09:42:55 +00:00
hugoreleaser b1a82c61ab releaser: Add release notes to /docs for release of 0.54.0
[ci skip]
2019-02-01 09:40:32 +00:00
hugoreleaser 6fe6b9c17f releaser: Bump versions for release of 0.54.0
[ci skip]
2019-02-01 09:40:32 +00:00
Bjørn Erik Pedersen 52cec0fb2d Release 0.54.0 2019-02-01 10:34:37 +01:00
hugoreleaser 23e52ea4c8 releaser: Add release notes draft for 0.54.0
Rename to *-ready.md to continue. [ci skip]
2019-02-01 09:31:43 +00:00
Bjørn Erik Pedersen 9b619dc023 releaser: Adjust patch logic
See #5639
2019-02-01 10:09:14 +01:00
Bjørn Erik Pedersen fab41f42d3 common/hugo: Use official semver even for main releases
Closes #5639
2019-02-01 09:44:12 +01:00
Bjørn Erik Pedersen f27faf9afd Squashed 'docs/' changes from 49809a038..4f4d1f48c
4f4d1f48c Merge commit 'ccb3ee5c57a00d5d32c53c882cf9893181a6ec10'
ccb3ee5c5 Squashed 'themes/gohugoioTheme/' changes from dafc91ff1..042112e2d

git-subtree-dir: docs
git-subtree-split: 4f4d1f48cd
2019-02-01 09:08:24 +01:00
Bjørn Erik Pedersen c7c66e664a Merge commit 'f27faf9afd0a8db768a21954b8755f1bf1a14f1b' 2019-02-01 09:08:24 +01:00
Bjørn Erik Pedersen 5e078383a7 Squashed 'docs/' changes from 785e375f..49809a03
49809a03 Merge commit '20a631b4964fc0ab9137cce1e41774cbc17de044'
20a631b4 Squashed 'themes/gohugoioTheme/' changes from b8202f539..dafc91ff1
8b58f565 Re-generate CLI docs
4653a724 Add Netlify deployment badge
2d6246bc Remove some deprecated site variables
e6777153 Improve Algolia Search Display Styling
1570999f Add missing "." in front of gitlab-ci.yaml example
b922ae7d This adds documentation to the new configDir/Environment logic from .53 (#729)
7cff379f Correctly escape multi-word taxonomy terms in example
2dfeeda4 fix typo by removing stray paren
0870bd9a Fix typo in `paginate` description
91e8be85 Fixes https://github.com/gohugoio/hugo/issues/5609
c1db65ec Make the dummy URL more obvious
b4589ff0 Fix a link
b73dcb9a Consistently use "posts" as section name in examples
7a56abbc Format definitions
a9c6fd9b Minor clarification over the last commit
5c86bdc8 Add alternative instructions for Quick Start for non-git users
dafe7ee9 Add Visual Studio Code plug-ins
110ed19e Update HUGO_VERSION
2abd031a Update page.md
b332f7b9 Update page.md
f5a8c9d4 Update static-files.md
6d0c155c Add note about relative protocol URLs
a13751ac Theme Warning: Remove note about unquoted URLs
4c8f7d68 Incorporate feedback
6f2b9cf0 Update Creating Themes Warning
40d88d98 Fix ToC example to use binary true/false
4a11f3f1 Fix typo
2dbfc0a4 Fix a typo in taxonomies
d63790ef Do not mark UndocumentedFeature issues as stale
d7aff095 Regenerate docs.json
71c0826f Update transform.Unmarshal.md

git-subtree-dir: docs
git-subtree-split: 49809a038b
2019-02-01 09:01:04 +01:00
Bjørn Erik Pedersen ddc15ed41b Merge commit '5e078383a787e8b5ec3ba73f05ea4130840afbe2' 2019-02-01 09:01:04 +01:00
Bjørn Erik Pedersen ddc6d4e30f tpl/data: Adjust tests
See #5643
2019-02-01 08:54:30 +01:00
Anthony Fok 6a2bfcbec8 tpl/data: Prevent getJSON and getCSV fetch failure from aborting build
Fixes #5643
2019-02-01 08:52:29 +01:00
Bjørn Erik Pedersen 59d87044a4 commands: Add test for --configDir
See #5662
2019-02-01 08:40:53 +01:00
Bjørn Erik Pedersen 3244cb3b31 Ignore unknown config files in config dir
Swap files etc.

Fixes #5646
2019-02-01 07:47:40 +01:00
Anton Harniakou d9282cf98a Store supported config formats in a variable 2019-02-01 07:17:37 +01:00
Bjørn Erik Pedersen c52045bbb3 Fix some inline shortcode issues
Fixes #5645
Fixes #5653
2019-01-31 19:08:19 +01:00
Bjørn Erik Pedersen 8ed2a1caa9 Bump to Go 1.11.5
Fixes #5654
2019-01-31 13:23:55 +01:00
Bjørn Erik Pedersen e85961390a Update Afero
Fixes #5650
2019-01-30 09:35:55 +01:00
Anthony Fok 2a9060a85c hugolib: Expand TestPageWithEmoji to cover '+', '-' and '_' too
See #5635 and commit 3038464e
2019-01-29 13:03:42 -07:00
Anthony Fok 3038464ea6 Accept hyphen and plus sign in emoji detection
Fixes #5635
2019-01-29 05:38:36 -07:00
Elliot Murphy 526b5b1c49 Fix OpenGraph image fallback to site params
Signed-off-by: Elliot Murphy <statik@users.noreply.github.com>
2019-01-25 18:14:10 +01:00
Anton Harniakou 26f75edb7a Support numeric sort in ByParam
With this commit ByParam takes into account a type of a value under a
key. If both values are numeric then they're coerced into float64 and
then get compared.
If any value isn't numeric, for example it's nil or string, then both
values coerced into string and get compared as strings
(lexicographicaly)

Nil values are always sent to the end.

Numeric values confirm to any type listed below:
uint8, uint16, uint32, uint64, int, int8, int16, int32, int64, float32, float64

Closes #5305
2019-01-20 13:05:20 +00:00
Bjørn Erik Pedersen e1a66c7343 Fix Params case handling in the new site global
Fixes #5615
2019-01-20 12:42:24 +00:00
Anton Harniakou db3c49d049 Make hugo server -t work again
This commit solves an issue where hugo would ignore the cli -t flag
and only use a theme defined in config.toml.

Also allow -t flag to accept a string slice.

Closes #5569
Closes #5061
Related #4868
2019-01-13 20:13:59 +00:00
Anthony Fok e3cb8e6c78 Add configFile(s) back to the watch list after RENAME event too
Alleviates #5205
2019-01-10 15:57:30 -07:00
mywaiting 55251aa890 Remove historical rssURI config 2019-01-10 18:24:38 +01:00
Anton Harniakou 843fcd19d4 Use subtests with server_test.go
Use Golang's subtests to provide a convenient way
to run specific tests.
Example:
go test -run=TestFixURL/Basic_production
2019-01-08 14:54:11 +01:00
Bjørn Erik Pedersen ce8a09a4c0 resources: Move resource interfaces into its own package 2019-01-02 14:25:37 +01:00
Bjørn Erik Pedersen 669ada4367 resource: Move resource processors into sub-packages 2019-01-02 14:25:37 +01:00
Marc Robinson 507451229c Update _index.md
Corrects misspelling of sync (synch)
2019-01-02 09:58:39 +01:00
Bjørn Erik Pedersen 40ffb0484b hugolib: Restore 0.48 slash handling in taxonomies
Fixes #5571
2018-12-30 18:50:09 +01:00
Bjørn Erik Pedersen 4bae8b04aa Revert " Fix handling of taxonomy terms containing slashes"
See #4090
See #5571

This reverts commit fff132537b.
2018-12-30 18:50:09 +01:00
Bjørn Erik Pedersen 0584432b07 Update go.sum 2018-12-30 17:07:31 +01:00
Bjørn Erik Pedersen 65fa069271 Revert "hugolib: Restore taxonomy term path separation"
See #5571

This reverts commit 9ce0a1fb70.
2018-12-29 10:00:17 +01:00
Cameron Moore 9e4f9e0bb6 tpl: Fix reflect
Fixes #5564
2018-12-28 10:41:46 +01:00
Bjørn Erik Pedersen 3f3187de0f cache/namedmemcache: Fix data race 2018-12-27 12:05:42 +01:00
Bjørn Erik Pedersen cc351958e1 Update Chroma
Fixes #4993
2018-12-27 10:58:34 +01:00
Bjørn Erik Pedersen c24f3ae22b Make docshelper run again
Fixes #5568
2018-12-27 10:08:44 +01:00
Bjørn Erik Pedersen 214e9745b6 releaser: Use Git short commit in release binaries
Fixes #5562
2018-12-26 11:31:58 +01:00
Bjørn Erik Pedersen ecc8922752 releaser: Bump Goreleaser version 2018-12-26 11:30:05 +01:00
Bjørn Erik Pedersen 22afe28047 Merge commit '978856e2ad12d2bcaf37bb9e31f806b30a4c42f4' 2018-12-26 10:27:54 +01:00
Bjørn Erik Pedersen 978856e2ad Squashed 'docs/' changes from bd91d1cfd..785e375f5
785e375f5 Change 'my-another-post' to 'my-other-post'
ba9a69d25 Update reflect.IsSlice.md
806344258 Update reflect.IsMap.md
757c0dafa Update index.md
d3f20a2d5 Update index.md
9952e72a0 Formatting
70458ccf2 Formatting
7e0dd3969 Merge branch 'config-dir'
06a5c11fc Release 0.53
01c00229f releaser: Prepare repository for 0.54-DEV
29b223f78 releaser: Add release notes to /docs for release of 0.53
4f61d1194 releaser: Bump versions for release of 0.53
320a0ac68 Refactor config below /config
ec16b611e docs: Adjust CSV example
cecf97dfc Rename CSV option from comma to delimiter
18d06df58 docs: Document transform.Unmarshal
fdace78b5 docs: Regenerate CLI docs
b8f84ac59 hugolib: Add .Name as a shortcode variable
6612dd1b8 Merge commit '5a83bf314f4c0ce1d61341e0a1df21c9998e8154'
9b211089f docs: Regenerate CLI docs
9af46af3c Merge commit 'eb16165694f868d73e57b6aed5c26ba5e98229de'
12f46f356 tpl: Add reflect namespace

git-subtree-dir: docs
git-subtree-split: 785e375f57
2018-12-26 10:27:12 +01:00
hugoreleaser 8431871201 releaser: Prepare repository for 0.54-DEV
[ci skip]
2018-12-24 08:26:54 +00:00
hugoreleaser 8fc339dc25 releaser: Add release notes to /docs for release of 0.53
[ci skip]
2018-12-24 08:24:42 +00:00
hugoreleaser 5236cb8e9a releaser: Bump versions for release of 0.53
[ci skip]
2018-12-24 08:24:42 +00:00
Bjørn Erik Pedersen a28865cfc3 release: Fix goreleaser config 2018-12-24 09:20:09 +01:00
hugoreleaser cb6c77b813 Release 0.53 2018-12-24 09:05:26 +01:00
Bjørn Erik Pedersen be58c7b9c8 tpl/transform: Include options in cache key
See #5555
2018-12-23 21:34:17 +01:00
Bjørn Erik Pedersen 62d031aedf docs: Adjust CSV example
See #5555
2018-12-23 21:12:41 +01:00
Bjørn Erik Pedersen ce06bdb16a Rename CSV option from comma to delimiter
See #5555
2018-12-23 21:09:09 +01:00
Bjørn Erik Pedersen 2efc1a64c3 docs: Document transform.Unmarshal
Fixes #5556
2018-12-23 19:43:17 +01:00
Bjørn Erik Pedersen 25ddbb09fe hugolib: Adjust test
See #5544
2018-12-23 19:42:51 +01:00
Bjørn Erik Pedersen e691c48a5a docs: Regenerate CLI docs
Fixes #5544
2018-12-23 19:05:40 +01:00
Bjørn Erik Pedersen 094709e105 tpl/transform: Simplify transform.Unmarshal func
See #5428
2018-12-23 16:43:04 +01:00
Bjørn Erik Pedersen a574469797 Add CSV support to transform.Unmarshal
Fixes #5555
2018-12-23 16:33:21 +01:00
Bjørn Erik Pedersen 822dc627a1 tpl/transform: Add transform.Unmarshal func
Fixes #5428
2018-12-23 10:02:42 +01:00
Bjørn Erik Pedersen 43f9df0194 Prevent resource publishing for transformed inline resources
That is, if only `.Content` is accessed.

This means that, for a transformed resource to be published to `/public`, you need to access either `.RelPermalink` or `Permalink`.

Fixes #4944
2018-12-21 13:41:46 +01:00
Bjørn Erik Pedersen 1021714449 hugolib: Add .Name as a shortcode variable
Fixes #5546
2018-12-21 09:51:15 +01:00
Bjørn Erik Pedersen 5a83bf314f Squashed 'docs/' changes from d1cf9adc4..bd91d1cfd
bd91d1cfd Fix typo
0d45ea9cf Fix order of arguments in another strings.TrimLeft example
0982f65c1 Fix the order of arguments to strings.TrimRight
f1f9b7cd6 Arguments in example
5d9c7327f Fix shortcode example escape chars

git-subtree-dir: docs
git-subtree-split: bd91d1cfdd
2018-12-21 09:45:41 +01:00
Bjørn Erik Pedersen 6f069e549b Merge commit '5a83bf314f4c0ce1d61341e0a1df21c9998e8154' 2018-12-21 09:45:41 +01:00
Cameron Moore 0483299bc0 hugolib: Improve logic of output path trimming
Fixes #4666
2018-12-21 09:42:37 +01:00
Anthony Fok 27b62a546c snap: Revert build-snap to 1.11/stable channel for go
This is to fix the following snap build error:

    Failed to install or refresh a snap: 'go' does not exist
    or is not available on the desired channel '1.11.4/stable'.
    Use `snap info go` to get a list of channels the snap is available on.

`snap info go` reveals that the '1.11/stable' channel automatically
follows the latest minor point release as it becomes available.
2018-12-20 21:46:47 -07:00
Bjørn Erik Pedersen 35bfca3b14 commands: Remove the benchmark command
It's not particulary useful, and when we start to get bug reports about it, it is easier to remove it.

Closes #5543
2018-12-20 20:55:26 +01:00
Bjørn Erik Pedersen 9cd54cab20 Move the emoji parsing to pageparser
This avoids double parsing the page content when `enableEmoji=true`.

This commit also adds some general improvements to the parser, making it in general much faster:

```bash
benchmark                     old ns/op     new ns/op     delta
BenchmarkShortcodeLexer-4     90258         101730        +12.71%
BenchmarkParse-4              148940        15037         -89.90%

benchmark                     old allocs     new allocs     delta
BenchmarkShortcodeLexer-4     456            700            +53.51%
BenchmarkParse-4              28             33             +17.86%

benchmark                     old bytes     new bytes     delta
BenchmarkShortcodeLexer-4     69875         81014         +15.94%
BenchmarkParse-4              8128          8304          +2.17%
```

Running some site benchmarks with Emoji support turned on:

```bash
benchmark                                                                                     old ns/op     new ns/op     delta
BenchmarkSiteBuilding/TOML,num_langs=3,num_pages=5000,tags_per_page=5,shortcodes,render-4     924556797     818115620     -11.51%

benchmark                                                                                     old allocs     new allocs     delta
BenchmarkSiteBuilding/TOML,num_langs=3,num_pages=5000,tags_per_page=5,shortcodes,render-4     4112613        4133787        +0.51%

benchmark                                                                                     old bytes     new bytes     delta
BenchmarkSiteBuilding/TOML,num_langs=3,num_pages=5000,tags_per_page=5,shortcodes,render-4     426982864     424363832     -0.61%
```

Fixes #5534
2018-12-20 20:08:01 +01:00
Bjørn Erik Pedersen a8853f1c5a parser/pageparser: Split the page lexer into some more files
See #5534
2018-12-20 20:08:01 +01:00
Bjørn Erik Pedersen 4d93aca27d hugolib: Enable Emoji in site benchmark 2018-12-20 17:24:51 +01:00
Bjørn Erik Pedersen f2167de834 parser/pageparser: Add a benchmark 2018-12-19 20:08:22 +01:00
Bjørn Erik Pedersen 64b6b29075 tpl: Fix case handling in cast params
Fixes #5538
2018-12-19 13:19:22 +01:00
Bjørn Erik Pedersen bb9c2988f8 Update to Go 1.11.4
Fixes #5524
2018-12-19 11:48:17 +01:00
Bjørn Erik Pedersen c04030b14c releaser: Get ldflags for build date etc. in line for all builds
Closes #5539
Closes ##5388
2018-12-19 10:01:26 +01:00
Bjørn Erik Pedersen f7691fe965 transform/urlreplacers: Simplify implementation 2018-12-17 19:17:56 +01:00
Bjørn Erik Pedersen efe0b4e5c0 transform/urlreplacers: Support unquoted URLs in canonifyURLs replacer
Fixes #5529
2018-12-17 19:17:56 +01:00
Bjørn Erik Pedersen d5a0b6bbbc hugolib: Remove "double layout" lookup
Fixes #5390
2018-12-17 11:34:32 +01:00
Bjørn Erik Pedersen 5178cd13a7 Fix "failed to create file caches from configuration: file exists" on Windows
Fixes #5497
2018-12-17 10:36:52 +01:00
Bjørn Erik Pedersen 9ce0a1fb70 hugolib: Restore taxonomy term path separation
Fixes #5513
2018-12-14 10:29:32 +01:00
Jean-François YUEN ab9214768d importer: fix jekyll import highlight options 2018-12-12 19:12:12 +01:00
Bjørn Erik Pedersen 5068681707 docs: Regenerate CLI docs
Fixes #5507
2018-12-12 09:09:45 +01:00
Bjørn Erik Pedersen b17a61a605 Merge commit 'eb16165694f868d73e57b6aed5c26ba5e98229de' 2018-12-12 09:06:42 +01:00
Bjørn Erik Pedersen eb16165694 Squashed 'docs/' changes from 37095d540..d1cf9adc4
d1cf9adc4 Fix typo
26e10a690 Fix the name and arg mismatch between partial defn and call
2db0e53cd Merge commit '9c36cff15224f6cbd19058ad61311229b7a23c83'
9c36cff15 Squashed 'themes/gohugoioTheme/' changes from 68ddff44..b8202f53
4b021eff8 Update lang.Merge.md
b37af2916 Add title to yaml
a9a281233 Fixed incorrect usage of the code-toggle shortcode
4560a0169 Update Warning for Theme Links (#676)
0305e3c6b Document .File.ContentBaseName
6d30c5aa1 Update configuration.md
158df174a Document .Sites and .Sites.First
0c0f583b8 Add stale config
e2531afd8 Document path template functions
4dd779057 Clarify that partialCached is per site/language
19e5bbe0c Update index.md
44b000857 Add missing dot
a41300cf9 Release 0.52
2d1d92b88 Merge branch 'temp52'
c5925250d releaser: Prepare repository for 0.53-DEV
d000b04a2 releaser: Add release notes to /docs for release of 0.52
4bb983a0a releaser: Bump versions for release of 0.52
36736ca28 tpl: Add "param" shortcode
378677aa6 Add Elasticsearch/bonsai.io to services doc.
4c3fd4fa4 docs: Document inline shortcodes
6c64c374c Whitelist CSS modules from purge
817a872b9 Improve search icon position
cf86ff1c7 Add minification and resource cache clear to build command
fd77e8df3 Update asset dependencies and adopt Hugo Pipes
cdbe97e8c Update render.md
b0e279220 git command to update submodule to latest
a1cb98c12 cache/filecache: Add a :project placeholder
07c1b2b46 cache/filecache: Use time.Duration for maxAge
ffa9b165e Add AND as a title
6e7733b40 Add OR as a sub title to make it easier to find in search
72b6791a1 docs: Document the new file cache
714d3ca91 Fix minification issues
cd1e961da Revert "Add Elasticsearch/bonsai.io to services doc."
15a0cda6e Add Elasticsearch/bonsai.io to services doc.
f931d86de Release 0.51
e2ffe867a Merge branch 'temp51'
423e7f5c8 releaser: Prepare repository for 0.52-DEV
c6f2d6ae1 releaser: Add release notes to /docs for release of 0.51
5bbb556dc releaser: Bump versions for release of 0.51
3b2b172b9 docs: Document shortcode error handling
b8672f3d4 docs: Document symdiff
4bc6071e6 docs: Document complement
d1baab752 docs: Re-generate CLI docs
9ea667e24 Revert "tpl: Update Jsonify to return pretty-print output"
ce5a1403d docs: Regenerate the docs helper
99a1f4a94 Fix note for reserved partial name(starting with -> including).
eba3cbc42 fix accidentally modification on paragraph.
3eebd98c3 Add note for reserved partial name.
40b881cc2 Document templates.Exists
b5c3bcd3b Update multilingual.md
61c59c67e Fix misspelling (#648)
f21d8c4a4 Correct minor typo (#5372)
e967001b9 Release 0.50
685fd6b08 releaser: Prepare repository for 0.51-DEV
f245a9faa releaser: Add release notes to /docs for release of 0.50
4354da30d releaser: Bump versions for release of 0.50
feaa05469 docs: Regenerate CLI docs
5c724200c Merge commit 'd6a4af7018e8618944a6471ceeb7aae1d4df6afa'
2ddab36c2 Merge commit '74309fe5699a595080fdb3a14711e0869babce99'
8cf296a7c docs: Regenerate CLI docs
9097683dd tpl: Update Jsonify to return pretty-print output

git-subtree-dir: docs
git-subtree-split: d1cf9adc41
2018-12-12 09:05:31 +01:00
Bjørn Erik Pedersen 7829474088 Add /config dir support
This commit adds support for a configuration directory (default `config`). The different pieces in this puzzle are:

* A new `--environment` (or `-e`) flag. This can also be set with the `HUGO_ENVIRONMENT` OS environment variable. The value for `environment` defaults to `production` when running `hugo` and `development` when running `hugo server`. You can set it to any value you want (e.g. `hugo server -e "Sensible Environment"`), but as it is used to load configuration from the file system, the letter case may be important. You can get this value in your templates with `{{ hugo.Environment }}`.
* A new `--configDir` flag (defaults to `config` below your project). This can also be set with `HUGO_CONFIGDIR` OS environment variable.

If the `configDir` exists, the configuration files will be read and merged on top of each other from left to right; the right-most value will win on duplicates.

Given the example tree below:

If `environment` is `production`, the left-most `config.toml` would be the one directly below the project (this can now be omitted if you want), and then `_default/config.toml` and finally `production/config.toml`. And since these will be merged, you can just provide the environment specific configuration setting in you production config, e.g. `enableGitInfo = true`. The order within the directories will be lexical (`config.toml` and then `params.toml`).

```bash
config
├── _default
│   ├── config.toml
│   ├── languages.toml
│   ├── menus
│   │   ├── menus.en.toml
│   │   └── menus.zh.toml
│   └── params.toml
├── development
│   └── params.toml
└── production
    ├── config.toml
    └── params.toml
```

Some configuration maps support the language code in the filename (e.g. `menus.en.toml`): `menus` (`menu` also works) and `params`.

Also note that the only folders with "a meaning" in the above listing is the top level directories below `config`. The `menus` sub folder is just added for better organization.

We use `TOML` in the example above, but Hugo also supports `JSON` and `YAML` as configuration formats. These can be mixed.

Fixes #5422
2018-12-11 13:08:36 +01:00
Iskander (Alex) Sharipov 256418917c resource/image: Fix "always false" condition
Found using gocritic linter.
2018-12-10 15:31:44 +01:00
Cameron Moore c84f506f8e tpl: Add reflect namespace
Add a reflect namespace that offers a two boolean functions for
testing if a value is a map or slice.

Fixes #4081
2018-12-07 23:29:37 +01:00
Bjørn Erik Pedersen 4b5f743959 minifiers: Fixx CSS2 color code handling
Fixes #5506
2018-12-07 07:49:26 +01:00
Bjørn Erik Pedersen 931a132450 tpl/hugo: Use the correct Hugo var
See #5467
2018-12-06 14:57:56 +01:00
Bjørn Erik Pedersen 8378358857 hugolib: Add .Site.Sites
Fixes #5504
2018-12-06 14:37:25 +01:00
Bjørn Erik Pedersen 831d23cb4d Add tpl/site and tpl/hugo
This means that the current `.Site` and ´.Hugo` is available as a globals, so you can do `site.IsServer`, `hugo.Version` etc.

Fixes #5470
Fixes #5467
Fixes #5503
2018-12-06 14:37:25 +01:00
Bjørn Erik Pedersen 514e18dc27 cache/filecache: Simplify test
See #5497
2018-12-04 19:19:20 +01:00
Bjørn Erik Pedersen b804a70881 cache/filecache: Use OS fs for test
See #5497
2018-12-04 18:03:49 +01:00
Cameron Moore 30a7c9ea37 tpl: Add godoc packages comments
Also fix package name in tpl/templates.
2018-11-30 08:56:30 +01:00
Christian Oliff 8346848109 common/collections: Fix defines typo 2018-11-29 09:57:51 +01:00
hugoreleaser 54ecade02f releaser: Prepare repository for 0.53-DEV
[ci skip]
2018-11-28 14:08:36 +00:00
hugoreleaser 9433cf5a92 releaser: Add release notes to /docs for release of 0.52
[ci skip]
2018-11-28 14:06:31 +00:00
hugoreleaser 3c909eb3f3 releaser: Bump versions for release of 0.52
[ci skip]
2018-11-28 14:06:31 +00:00
Bjørn Erik Pedersen 724b3e673b Release 0.52 2018-11-28 14:59:48 +01:00
hugoreleaser 056c754771 releaser: Add release notes draft for 0.52
Rename to *-ready.md to continue. [ci skip]
2018-11-28 13:14:49 +00:00
Bjørn Erik Pedersen a9a93d082d hugolib: Fall back to title in ByLinkTitle sort
Fixes #4953
2018-11-28 13:52:26 +01:00
Bjørn Erik Pedersen b09a40333f hugolib: Improve nil handling in IsDescendant and IsAncestor
Fixes #5461
2018-11-28 12:36:59 +01:00
Bjørn Erik Pedersen 7540a62834 parser/pageparser: Fix handling of commented out front matter
When the page parser was rewritten in 0.51, this was interpreted literally, but commented out front matter is used in the wild to "hide it from GitHub", e.g:

```
<!--
+++
title = "hello"
+++
-->
```

Fixes #5478
2018-11-28 10:28:50 +01:00
Bjørn Erik Pedersen 7e75aeca80 Remove duplicate mapstructure depdendency 2018-11-28 00:17:43 +01:00
Bjørn Erik Pedersen e14e0b192f Add dependency list to README 2018-11-28 00:08:27 +01:00
Cameron Moore ed698e94c1 tpl/path: Add tests 2018-11-27 19:17:35 +01:00
Bjørn Erik Pedersen 89e2716d29 tpl: Regenerate templates 2018-11-27 18:15:55 +01:00
Max Arnold aba2647c15 tpl: Fix whitespace issue
Introduced  in #5276.
2018-11-27 18:15:10 +01:00
Bjørn Erik Pedersen f37c5a2567 tpl: Add "param" shortcode
Fixes #4010
2018-11-27 17:34:03 +01:00
Bjørn Erik Pedersen aded0f25fd docs: Document inline shortcodes
See #4011
2018-11-27 16:14:09 +01:00
Bjørn Erik Pedersen bc337e6ab5 Add inline shortcode support
An inline shortcode's name must end with `.inline`, all lowercase.

E.g.:

```bash
{{< time.inline >}}{{ now }}{{< /time.inline >}}
```

The above will print the current date and time.

Note that an inline shortcode's inner content is parsed and executed as a Go text template with the same context as a regular shortcode template.

This means that the current page can be accessed via `.Page.Title` etc. This also means that there are no concept of "nested inline shortcodes".

The same inline shortcode can be reused later in the same content file, with different params if needed, using the self-closing syntax:

```
{{< time.inline />}}
```

Fixes #4011
2018-11-27 16:14:09 +01:00
Cameron Moore 112461fded tpl/collections: Add float64 support to where
Fixes #5466
2018-11-27 09:04:02 +01:00
Bjørn Erik Pedersen 94ab125b27 parser/pageparser: Fix when only shortcode and then summary
Fixes #5464
2018-11-24 18:23:23 +01:00
Bjørn Erik Pedersen dcfeed35c6 commands: Include drafts in convert command
Fixes #5457
2018-11-24 13:11:14 +01:00
Bjørn Erik Pedersen f9b4eb4f39 Handle themes in the new file cache (for images, assets)
In the newly consolidated file cache implementation, we forgot that we also look in the theme(s) for assets (SCSS transformations etc.), which is not good for Netlify and the demo sites.

Fixes #5460
2018-11-23 21:09:00 +01:00
Kris Budhram e82b2dc8c1 Fix ignored --config flag with 'new' command 2018-11-18 19:09:28 +01:00
Bjørn Erik Pedersen 5df2b79dd2 Fix Permalink for resource, baseURL with path and canonifyURLs set
Fixes #5226
2018-11-15 16:37:11 +01:00
Bjørn Erik Pedersen 12742bac71 Add tests for permalink on Resource with baseURL with path
See #5226
2018-11-15 08:45:15 +01:00
Bjørn Erik Pedersen fabf026f49 helpers: Add a comment about file mode for new files
See #5434
2018-11-14 23:14:51 +01:00
Bjørn Erik Pedersen 94f0f7e597 cache/filecache: Add a :project placeholder
This allows for "cache per Hugo project", making `hugo --gc` work as expected, even if you have several Hugo projects running on the same PC.

See #5439
2018-11-14 23:14:51 +01:00
Bjørn Erik Pedersen 3c29c5af8e cache/filecache: Add a cache prune func
Fixes #5439
2018-11-14 23:14:51 +01:00
Bjørn Erik Pedersen 33502667fb cache/filecache: Add a filecache root dir
This is just a safe guard to make sure we don't evict/remove files that do not belong to the cache.
2018-11-14 23:14:51 +01:00
Bjørn Erik Pedersen d3489eba5d cache/filecache: Use time.Duration for maxAge
Fixes #5438
2018-11-14 23:14:50 +01:00
Bjørn Erik Pedersen 17d7ecde2b cache/filecache: Split implementation and config into separate files 2018-11-14 23:14:50 +01:00
Bjørn Erik Pedersen e4b2572880 Update to LibSASS 3.5.5
Fixes #5432
See #5435
2018-11-14 17:47:08 +01:00
Bjørn Erik Pedersen 782dd15858 More spelling corrections 2018-11-13 18:28:40 +01:00
Bjørn Erik Pedersen aff9c09166 cache/filecache: Spelling corrections 2018-11-13 18:16:53 +01:00
Bjørn Erik Pedersen fdd4a768f0 Remove appveyor
We now build on Travis for all platforms.
2018-11-13 14:36:17 +01:00
Bjørn Erik Pedersen abeeff1325 docs: Document the new file cache
See #5404
2018-11-13 14:29:45 +01:00
Bjørn Erik Pedersen f7aeaa6129 Add a consolidated file cache
This commits reworks how file caching is performed in Hugo. Now there is only one way, and it can be configured.

This is the default configuration:

```toml
[caches]
[caches.getjson]
dir = ":cacheDir"
maxAge = -1
[caches.getcsv]
dir = ":cacheDir"
maxAge = -1
[caches.images]
dir = ":resourceDir/_gen"
maxAge = -1
[caches.assets]
dir = ":resourceDir/_gen"
maxAge = -1
```

You can override any of these cache setting in your own `config.toml`.

The placeholders explained:

`:cacheDir`: This is the value of the `cacheDir` config option if set (can also be set via OS env variable `HUGO_CACHEDIR`). It will fall back to `/opt/build/cache/hugo_cache/` on Netlify, or a `hugo_cache` directory below the OS temp dir for the others.
`:resourceDir`: This is the value of the `resourceDir` config option.

`maxAge` is the time in seconds before a cache entry will be evicted, -1 means forever and 0 effectively turns that particular cache off.

This means that if you run your builds on Netlify, all caches configured with `:cacheDir` will be saved and restored on the next build. For other CI vendors, please read their documentation. For an CircleCI example, see https://github.com/bep/hugo-sass-test/blob/6c3960a8f4b90e8938228688bc49bdcdd6b2d99e/.circleci/config.yml

Fixes #5404
2018-11-13 14:19:42 +01:00
Emir Beganović 7d78a2afd3 Add Windows build config to Travis 2018-11-13 11:39:54 +01:00
Bjørn Erik Pedersen 5570a6e478 Revert "Add Elasticsearch/bonsai.io to services doc."
This reverts commit c0b3a1af03.
2018-11-09 23:12:27 +01:00
Allison Zadrozny c0b3a1af03 Add Elasticsearch/bonsai.io to services doc.
Hip hip for OSS! 🙌
2018-11-09 22:56:38 +01:00
Ian Lance Taylor a8cb1b07b4 tpl: Fix test to pass with gccgo
When run under gccgo, the test looks for the name that gccgo gives to
a thunk method.  This name is not normally visible, but can be seen
when using reflect.FuncForPC as this code does.  That name changed in
https://golang.org/cl/89555.  Change the test to work with both the
old name "$thunk0" and the new name "thunk0".

Fixes golang/go#28669
2018-11-09 08:54:08 +01:00
hugoreleaser 2900801acf releaser: Prepare repository for 0.52-DEV
[ci skip]
2018-11-07 10:11:48 +00:00
hugoreleaser f3d5190793 releaser: Add release notes to /docs for release of 0.51
[ci skip]
2018-11-07 10:09:43 +00:00
hugoreleaser 1ccc0b79a2 releaser: Bump versions for release of 0.51
[ci skip]
2018-11-07 10:09:43 +00:00
Bjørn Erik Pedersen cbed50e22b Release 0.51 2018-11-07 11:00:25 +01:00
hugoreleaser 05ecd8111c releaser: Add release notes draft for 0.51
Rename to *-ready.md to continue. [ci skip]
2018-11-07 09:48:47 +00:00
Bjørn Erik Pedersen e456e34bdb docs: Document shortcode error handling
Closes https://github.com/gohugoio/hugoDocs/issues/660
2018-11-07 10:38:58 +01:00
Bjørn Erik Pedersen 5d14d04ac6 docs: Document symdiff
Closes https://github.com/gohugoio/hugoDocs/issues/659
2018-11-07 10:38:58 +01:00
Bjørn Erik Pedersen ddcb402859 docs: Document complement
Closes https://github.com/gohugoio/hugoDocs/issues/658
2018-11-07 10:38:58 +01:00
Bjørn Erik Pedersen 3a44920e79 hugolib: Improve error message on duplicate menu items 2018-11-07 10:38:58 +01:00
Bjørn Erik Pedersen 79a06aa4b6 tpl/collections: Properly handle pointer types in complement/symdiff
We cannot compare them by values, because that gets `hash of unhashable type` for the prime use case.
2018-11-07 09:29:13 +01:00
Bjørn Erik Pedersen d212f60949 deps: Update minify
No functional changes, just support for Go Modules.
2018-11-07 00:23:08 +01:00
Bjørn Erik Pedersen 488776b649 tpl/collections: Add collections.SymDiff
Fixes #5410
2018-11-06 16:51:49 +01:00
Bjørn Erik Pedersen b8b8436fcc hugolib: Fix changing paginators in lazy render
Fixes #5406
2018-11-06 11:04:10 +01:00
Bjørn Erik Pedersen 42d8dfc8c8 tpl/collections: Add collections.Complement
Fixes #5400
2018-11-06 08:48:58 +01:00
秦世成 47506d1644 commands: Fix spelling 2018-11-06 08:45:19 +01:00
Bjørn Erik Pedersen 2998fa0cd5 docs: Re-generate CLI docs 2018-11-05 14:18:45 +01:00
Bjørn Erik Pedersen d16a7a33ff Fix shortcode directly following a shortcode delimiter
Fixes #5402
2018-11-05 14:14:28 +01:00
Bjørn Erik Pedersen 5b1edd281a commands: Add --minify to hugo server 2018-11-04 18:39:14 +01:00
Bjørn Erik Pedersen 2bd9d9099d commands: Fix recently broken error template
We need a test for this ...
2018-11-03 16:55:45 +01:00
Bjørn Erik Pedersen 4b7d3e57a4 Make WARN the new default log log level
This commit also pulls down the log level for a set of WARN statements to INFO. There should be no ERRORs or WARNINGs in a regular Hugo build. That is the story about the Boy Who Cried Wolf.

Since the WARN log is now more visible, this commit also improves on some of them, most notable the "layout not found", which now would look something like this:

```bash
WARN 2018/11/02 09:02:18 Found no layout for "home", language "en", output format "CSS": create a template below /layouts with one of these filenames: index.en.css.css, home.en.css.css, list.en.css.css, index.css.css, home.css.css, list.css.css, index.en.css, home.en.css, list.en.css, index.css, home.css, list.css, _default/index.en.css.css, _default/home.en.css.css, _default/list.en.css.css, _default/index.css.css, _default/home.css.css, _default/list.css.css, _default/index.en.css, _default/home.en.css, _default/list.en.css, _default/index.css, _default/home.css, _default/list.css
```

Fixes #5203
2018-11-03 12:06:23 +01:00
Bjørn Erik Pedersen 4f3c095963 Revert "tpl: Update Jsonify to return pretty-print output"
This reverts commit 5a52cd5f92.

Closes #5394
2018-11-02 19:03:48 +01:00
Bjørn Erik Pedersen 486bc46a52 docs: Regenerate the docs helper 2018-11-02 13:36:09 +01:00
Bjørn Erik Pedersen 61f210dd7a tpl: Fix the docshelper 2018-11-02 13:36:09 +01:00
Kris Budhram f8446188db Skip watcher event files if matched in ignoreFiles 2018-11-02 09:50:31 +01:00
Bjørn Erik Pedersen d523aa4bb0 deps: Update Chroma
Fixes #5392
2018-11-02 09:31:51 +01:00
Lorenz Cuno Klopfenstein b8725f5181 Fix ANSI character output regression on Windows
Fixes #5377
2018-11-02 09:09:02 +01:00
Bjørn Erik Pedersen 0bc4b0246d hugolib: Remove deprecated useModTimeAsFallback 2018-11-01 21:14:30 +01:00
Bjørn Erik Pedersen faeb55c1d8 hugolib: Bump to ERROR for the deprecated Pages.Sort 2018-11-01 21:11:45 +01:00
Bjørn Erik Pedersen 6c6a6c87ec hugolib: Deprecate .Site.Ref and .Site.RelRef
Fixes #5386
2018-11-01 21:08:12 +01:00
Bjørn Erik Pedersen 1d18eb0574 Add file (line/col) info to ref/relref errors
See #5371
2018-11-01 21:06:35 +01:00
Bjørn Erik Pedersen 33a7b36fd4 hugolib: Add .Position to shortcode
To allow for better error logging in shortcodes. Note that this may be expensive to calculate, so this is primarily for error situations.

See #5371
2018-11-01 21:06:35 +01:00
Bjørn Erik Pedersen 6180c85fb8 hugolib: Fix REF_NOT_FOUND logging to include page path
Fixes #5371
2018-11-01 21:06:35 +01:00
Bjørn Erik Pedersen d3a98325c3 common/loggers: Improve log color regexp
To avoid coloring WARNING inside log errors.
2018-11-01 09:33:32 +01:00
Bjørn Erik Pedersen b2a676f5f0 hugolib: Fix broken manual summary handling
Fixes #5381
2018-10-31 23:14:37 +01:00
Bjørn Erik Pedersen 3a786a248d tpl: Fix BOM issue in templates
Fixes #4895
2018-10-30 18:12:14 +01:00
Bjørn Erik Pedersen 729593c842 hugolib: Fix deadlock when content building times out
Fixes #5375
2018-10-30 12:18:29 +01:00
Christian Oliff e65268f2c2 Correct minor typo (#5372)
> Errors will no show up

should be:

> Errors will now show up
2018-10-29 12:09:19 +01:00
hugoreleaser f863b6a07e releaser: Prepare repository for 0.51-DEV
[ci skip]
2018-10-29 09:54:42 +00:00
hugoreleaser f5be599204 releaser: Add release notes to /docs for release of 0.50
[ci skip]
2018-10-29 09:51:50 +00:00
hugoreleaser b14c11b0e1 releaser: Bump versions for release of 0.50
[ci skip]
2018-10-29 09:51:50 +00:00
Bjørn Erik Pedersen 0addb2add6 Release 0.50 2018-10-29 10:39:51 +01:00
hugoreleaser 8bba85601f releaser: Add release notes draft for 0.50
Rename to *-ready.md to continue. [ci skip]
2018-10-29 10:39:51 +01:00
Bjørn Erik Pedersen 7082a5d143 Update go.sum 2018-10-29 09:42:53 +01:00
Bjørn Erik Pedersen aa281b5135 mod: Update minify
Closes #5261
2018-10-29 09:35:14 +01:00
Bjørn Erik Pedersen 3250198724 docs: Regenerate CLI docs 2018-10-29 09:29:27 +01:00
Bjørn Erik Pedersen d6a4af7018 Squashed 'docs/' changes from 084804447..37095d540
37095d540 Fix shortcode example

git-subtree-dir: docs
git-subtree-split: 37095d5403
2018-10-29 09:28:01 +01:00
Bjørn Erik Pedersen 27569275b1 Merge commit 'd6a4af7018e8618944a6471ceeb7aae1d4df6afa' 2018-10-29 09:28:01 +01:00
Bjørn Erik Pedersen 4b2738d871 Merge commit '74309fe5699a595080fdb3a14711e0869babce99' 2018-10-29 09:23:25 +01:00
Bjørn Erik Pedersen 74309fe569 Squashed 'docs/' changes from e5e98b950..084804447
084804447 Update shortcode-templates.md
c01b02434 Correct misspelling of 'default'
52a831cca Added missing parenthesis
59e8e660a Fix spelling typos
29ad53c9c Yes, HTML is a valid content format
c6b193c6f Update shortcode-templates.md
1f2846e6d Fix typo in output format README
5882f7a4c Fix typo
a90a00bb0 Update multilingual.md
62bf0f184 Documentation for Open Graph & Twitter Cards
f4d624da3 Document "images", "videos", etc. in front-matter
6a85b5df1 Document anchorize and Resources.Content
04c8a5b0e Fix minor typo in 0.49.2 release note
dbe77e948 Release 0.49.2
ea6c9658e Merge branch 'temp492'
85c45b725 Merge branch 'release-0.49.2'
7ad1fba29 releaser: Prepare repository for 0.50-DEV
b25bcc3f2 releaser: Add release notes to /docs for release of 0.49.2
78b751b91 releaser: Bump versions for release of 0.49.2
e3f09762c Release 0.49.1
bd5b94558 Merge branch 'temp491'
0007e0661 Merge branch 'release-0.49.1'
74d2f3a6f releaser: Prepare repository for 0.50-DEV
bbee7e9d3 releaser: Add release notes to /docs for release of 0.49.1
ae40c89c7 releaser: Bump versions for release of 0.49.1
11079fb48 Add draft statement to FAQ
069b9472f Addin Hokus CMS to frontends list.
6e8850670 Add MediaType docs
f3ca6209a Add `languageName` to configuration
fd1cde5ea tpl: Add a delimiter parameter to lang.NumFmt
c620ff78a Update doc to use proper variable
7317c339a add tools->hugo-elasticsearch description to docs
d758ef94a hugolib: Introduce Page.NextPage and Page.PrevPage
9c93ac031 Update installing.md
7c0b5b7f5 Use ISO 639-1 code for examples
9a9e40ba8 Fix spelling
9a6216c18 Hugo 0.49
55aa91185 Merge branch 'temp49'
e0a36421e releaser: Prepare repository for 0.50-DEV
c07b3b385 releaser: Add release notes to /docs for release of 0.49
c1175a12a releaser: Bump versions for release of 0.49
2966f6254 docs: Document directory based archetypes
73dcd02ed Add showcase archetype folder
0a55ad11b docs: Regenerate CLI docs
e09866c2d docs: Document group
ef986358a Merge commit '807c551922707fc5ae0eb26e8f01638c0c63fdb3'
681f14fc9 tpl/collections: Allow first function to return an empty slice
f6dcc93bc docs: Add docs for append
aae528ca3 Merge commit '13e64d72763bf8d6d92d4cdfc15ed45ee9debfab'
02b62294c tpl/strings: Add strings.FirstUpper
bf3e61ba3 hugolib: Do not FirstUpper taxonomy titles

git-subtree-dir: docs
git-subtree-split: 0848044474
2018-10-29 09:19:20 +01:00
Bjørn Erik Pedersen 9c88a8a55a common/loggers: Make sure the global logger also gets colored labels
See #4414
2018-10-28 16:06:50 +01:00
Bjørn Erik Pedersen 95e72f5e8e i18n: Avoid using the global logger
See #4414
2018-10-28 14:42:41 +01:00
Bjørn Erik Pedersen 1c7b7b4ef2 common/loggers: Add color to ERROR and WARN
Fixes #4414
2018-10-28 14:16:23 +01:00
Bjørn Erik Pedersen 1ad117cbe2 common/herrors: Make the file error log format configurable
Will check for an OS environment variable named `HUGO_FILE_LOG_FORMAT`.

The tokens available are `:file` (absolute filename), `:line` (line number) and `:col` (column number).

The default format is: \":file::line::col\"`

How to set OS environment variables is OS specific, but an example on Unix setting an alternative format when running Hugo:

```bash
env HUGO_FILE_LOG_FORMAT="\":file::line\"" hugo
```

The above will include filename and line number quoted.

Fixes #5352
2018-10-27 19:14:16 +02:00
Bjørn Erik Pedersen dac7092a9c common/collections: Allow a mix of slice types in append/Scratch.Add
The type handling in these was improved in Hugo 0.49, but this also meant that it was no longer possible to start out with a string slice and later append `Page` etc. to it.

This commit makes sure that the old behaviour is now possible again by falling back to a `[]interface{}` as a last resort.

Fixes #5361
2018-10-27 15:05:40 +02:00
Bjørn Erik Pedersen b27ccf34bf resource: Allow .Data.Integrity to be accessed on its own
Fixes #5296
2018-10-27 11:34:15 +02:00
Bjørn Erik Pedersen 286d0b7dc3 Revert "mod: Update minify"
This reverts commit 83c873ff37.
2018-10-26 19:30:01 +02:00
Bjørn Erik Pedersen 83c873ff37 mod: Update minify
Closes #5261
2018-10-26 17:16:01 +02:00
Bjørn Erik Pedersen a2440dc0e2 mod: Update cast
Closes #5340
2018-10-26 17:13:22 +02:00
Bjørn Erik Pedersen 1e9ac3dcc2 commands: Truncate the error log on repeated config errors 2018-10-26 17:02:53 +02:00
Bjørn Erik Pedersen 40e99672b6 docs: Regenerate CLI docs
Closes #5354
2018-10-26 15:10:43 +02:00
Bjørn Erik Pedersen 3a3badfd1d resource: Serialize image processing
Fixes #5220
2018-10-26 14:40:05 +02:00
Bjørn Erik Pedersen df021317a9 commands: Only show Ansi escape codes if in a terminal 2018-10-26 14:33:44 +02:00
Bjørn Erik Pedersen 78578632f5 Fix archetype handling of directories in theme
Fixes #5318
2018-10-26 12:16:28 +02:00
Bjørn Erik Pedersen 6b78b3810a Revert "commands: Read disableFastRender from flag even if it's not changed"
On second thought, removing this isn't worth it.

This reverts commit 78a4c2e32e.
2018-10-24 22:15:57 +02:00
Bjørn Erik Pedersen 78a4c2e32e commands: Read disableFastRender from flag even if it's not changed
Fixes #5353
2018-10-24 20:33:07 +02:00
Bjørn Erik Pedersen d4ebfea1ff comamnds: Use overflow-x: auto; for browser errors 2018-10-24 19:20:37 +02:00
Bjørn Erik Pedersen 93aa6261b4 common/loggers: Remove the ANSI color for the browser error version 2018-10-24 17:22:07 +02:00
Bjørn Erik Pedersen acc14b4646 hugolib: Adjust error test to make it pass on Go tip 2018-10-24 15:19:49 +02:00
Bjørn Erik Pedersen 2d7709d155 tpl: Handle truncated identifiers in Go template errors
Long identifiers will give errors on the format:

```bash
 _default/single.html:5:14: executing "main" at <.ThisIsAVeryLongTitl...>: can't evaluate field ThisIsAVeryLongTitle
```

Hugo use this value to match the "base template or not", so we need to strip the "...".

Fixes #5346
2018-10-24 13:54:04 +02:00
Bjørn Erik Pedersen deff9e154b Add some color to the relevant filenames in terminal log
Fixes #5344
2018-10-24 13:02:18 +02:00
Bjørn Erik Pedersen 889aca054a Run gofmt -s 2018-10-24 00:12:32 +02:00
Bjørn Erik Pedersen e3ed4a83b8 hugolib: Rename some page_* files
To make it easier to see/work with the source files that is about the `Page` struct.
2018-10-23 22:21:21 +02:00
Bjørn Erik Pedersen 6636cf1bea Resolve error handling/parser related TODOs
See #5324
2018-10-23 19:41:22 +02:00
Bjørn Erik Pedersen f669ef6bec herrors: Improve handling of JSON errors
`*json.UnmarshalTypeError` and `*json.SyntaxError` has a byte `Offset`, so use that.

This commit also reworks/simplifies the errror line matching logic. This also makes the file reading unbuffered, but that should be fine in this error case.

See #5324
2018-10-23 14:35:43 +02:00
Bjørn Erik Pedersen ed7b3e2619 commands, hugolib: Get file context in "config parse failed" errors
Fixes #5325
2018-10-23 08:09:41 +02:00
Bjørn Erik Pedersen 2bf686ee21 hugolib: Improve errors in /i18n handlling
See #5324
2018-10-22 20:46:14 +02:00
Bjørn Erik Pedersen 9f74dc2a52 hugolib: Improve errors in /data handlling
See #5324
2018-10-22 20:46:14 +02:00
Bjørn Erik Pedersen d1661b823a hugolib: Continue the file context/line number errors work
See #5324
2018-10-22 20:46:14 +02:00
Bjørn Erik Pedersen 7930d2132a hugolib: Remove the now superflous Source struct
See #5324
2018-10-22 20:46:14 +02:00
Bjørn Erik Pedersen eb038cfa0a Convert the rest to new page parser code paths
And remove some now unused code.

See #5324
2018-10-22 20:46:14 +02:00
Bjørn Erik Pedersen 129c27ee6e parser/metadecoders: Consolidate the metadata decoders
See #5324
2018-10-22 20:46:13 +02:00
Bjørn Erik Pedersen 44da60d869 hugolib: Redo the summary delimiter logic
Now that we have a proper page parse tree, this can be greatly simplified.

See #5324
2018-10-22 20:46:13 +02:00
Bjørn Erik Pedersen 1e3e34002d hugolib: Integrate new page parser
See #5324
2018-10-22 20:46:13 +02:00
Bjørn Erik Pedersen 1b7ecfc2e1 hugolib: Use []byte in shortcode parsing
See #5324
2018-10-22 19:57:44 +02:00
Bjørn Erik Pedersen 27f5a906a2 parser/pageparser: Use []byte in page lexer
See #5324
2018-10-22 19:57:44 +02:00
Bjørn Erik Pedersen 2fdc4a24d5 parser/pageparser: Add front matter etc. support
See #5324
2018-10-22 19:57:43 +02:00
Bjørn Erik Pedersen f6863e1ef7 parser/pageparser: File renames and splitting
See #5324
2018-10-22 19:57:43 +02:00
Bjørn Erik Pedersen d6c16afde0 Move the shortcode parser to the new pageparser package
See #5324
2018-10-22 19:57:43 +02:00
Bjørn Erik Pedersen 6f3716dc22 commands: Avoid panic in error handler on config errors 2018-10-22 19:51:59 +02:00
Sean Prashad 5a52cd5f92 tpl: Update Jsonify to return pretty-print output
Fixes #5040
2018-10-21 23:36:35 +02:00
Bjørn Erik Pedersen 4a366fcfee Prevent stale content in Fast Render Mode
We do that by re-render visited pages that is not already in the stack. This may potentially do some double work, but that small penalty should be well worth it.

Fixes #5281
2018-10-17 10:15:22 +02:00
Bjørn Erik Pedersen 083311d033 hugolib: Fix test on Windows 2018-10-17 08:37:02 +02:00
Bjørn Erik Pedersen 0fe4ff1875 tpl: Improve the Execute panic error message
See #5327
2018-10-17 08:25:57 +02:00
Janus 1f42e47e47 Allow date and slug from filename for leaf bundles
Fixes #4558
2018-10-16 23:51:48 +02:00
Bjørn Erik Pedersen 35fbfb19a1 commands: Show server error info in browser
The main item in this commit is showing of errors with a file context when running `hugo server`.

This can be turned off: `hugo server --disableBrowserError` (can also be set in `config.toml`).

But to get there, the error handling in Hugo needed a revision. There are some items left TODO for commits soon to follow, most notable errors in content and config files.

Fixes #5284
Fixes #5290
See #5325
See #5324
2018-10-16 22:10:56 +02:00
Kris Budhram 3a3089121b Simple doc fix in CONTRIBUTING.md 2018-10-16 13:30:00 +02:00
Akshay Raj Gollahalli c21e5179ce tpl: Use .Lastmod in embedded schema template
Fixes #5320
2018-10-16 11:52:32 +02:00
Bjørn Erik Pedersen a205f24ba5 Revert "commands: Add .gitignore "hugo new site""
This fails on Windows, so it needs to be reconsidered.

This reverts commit 92979d9288.
2018-10-16 08:43:38 +02:00
Nathaniel Peiffer 92979d9288 commands: Add .gitignore "hugo new site" 2018-10-16 08:36:27 +02:00
Cameron Moore 0a3340e952 resource: Optimize integrity string generation
Remove use of fmt.Sprintf for simple string concatenation.  A simple
change for a small perf boost.

```
name         old time/op    new time/op    delta
Integrity-4     525ns ± 2%     268ns ± 2%  -48.92%  (p=0.000 n=10+10)

name         old alloc/op   new alloc/op   delta
Integrity-4      144B ± 0%      112B ± 0%  -22.22%  (p=0.000 n=10+10)

name         old allocs/op  new allocs/op  delta
Integrity-4      5.00 ± 0%      3.00 ± 0%  -40.00%  (p=0.000 n=10+10)
```
2018-10-16 08:33:38 +02:00
Elliot Forbes 6b21ac3e67 commands: Add help text to "hugo new" 2018-10-15 17:48:19 +02:00
Anthony Fok d14420539a snap: Set "extended" tag based on build_url on Launchpad
Read build_url from $SNAPCRAFT_IMAGE_INFO to determine whether
to add the "extended" build tag or not.  This allows us to do away
with the extended-snap-master and extended-snap-stable branches.
2018-10-12 11:10:38 +08:00
Shreyansh Khajanchi 3d4a9882bf helpers: Call rst2html directly on *nix
Initially, rst2html was called via the python interpreter which would
fail if the script was wrapped in a launcher as on NixOS.
Ideally, on *nix, binaries should be invoked directly to ensure that
shebangs work properly as is being done now.
Handle the case of windows as it doesn't do shebangs.
2018-10-11 22:46:10 +02:00
Ben Abbott bdca972794 Update URLs to stop 301 redirects 2018-10-11 22:44:06 +02:00
Bjørn Erik Pedersen 604ddb90c5 Merge branch 'release-0.49.2' 2018-10-11 12:01:51 +02:00
hugoreleaser b09e495a71 releaser: Prepare repository for 0.50-DEV
[ci skip]
2018-10-11 09:49:59 +00:00
hugoreleaser 42cde66663 releaser: Add release notes to /docs for release of 0.49.2
[ci skip]
2018-10-11 09:47:57 +00:00
hugoreleaser ad60948c46 releaser: Bump versions for release of 0.49.2
[ci skip]
2018-10-11 09:47:57 +00:00
Bjørn Erik Pedersen c397f2c080 Release 0.49.2 2018-10-11 11:41:46 +02:00
hugoreleaser b9e18e20c8 releaser: Add release notes draft for 0.49.2
Rename to *-ready.md to continue. [ci skip]
2018-10-11 09:39:29 +00:00
Bjørn Erik Pedersen 2159d77f36 common/collections: Fix type checking in Append
The fix introduced in Hugo `0.49.1` had an unintended side-effect in the `Append` func used in both `append` and `.Scratch.Add`.

This commit fixes that by loosen/fixing the type checking so concrete types can be appended to interface slices.

Fixes #5303
2018-10-11 11:30:17 +02:00
Bjørn Erik Pedersen 535755e4f8 common/collections: Fix type checking in Append
The fix introduced in Hugo `0.49.1` had an unintended side-effect in the `Append` func used in both `append` and `.Scratch.Add`.

This commit fixes that by loosen/fixing the type checking so concrete types can be appended to interface slices.

Fixes #5303
2018-10-11 11:24:10 +02:00
Bjørn Erik Pedersen 3583dd6d71 Merge branch 'release-0.49.1' 2018-10-10 13:00:01 +02:00
hugoreleaser e5b4cb2419 releaser: Prepare repository for 0.50-DEV
[ci skip]
2018-10-10 10:38:45 +00:00
hugoreleaser 821adf3ae8 releaser: Add release notes to /docs for release of 0.49.1
[ci skip]
2018-10-10 10:36:37 +00:00
hugoreleaser 8ba9a27366 releaser: Bump versions for release of 0.49.1
[ci skip]
2018-10-10 10:36:37 +00:00
Bjørn Erik Pedersen 235acf2232 Relase 0.49.1 2018-10-10 12:31:46 +02:00
hugoreleaser ac0fac9347 releaser: Add release notes draft for 0.49.1
Rename to *-ready.md to continue. [ci skip]
2018-10-10 10:28:25 +00:00
Bjørn Erik Pedersen 23f48c300c common/maps: Improve append in Scratch
This commit consolidates the reflective collections handling in `.Scratch` vs the `tpl` package so they use the same code paths.

This commit also adds support for a corner case where a typed slice is appended to a nil or empty `[]interface{}`.

Fixes #5275
2018-10-10 12:21:51 +02:00
Bjørn Erik Pedersen e2201ef15f tpl/collections: Fix handling of different interface types in Slice
In Hugo `0.49` we improved type support in `slice`. This has an unfortunate side effect in that `resources.Concat` now expects something that can resolve to `resource.Resources`.

This worked for most situations, but when you try to `slice` different `Resource` objects, you would be getting `[]interface {}` and not `resource.Resources`. And `concat` would fail:

```bash
error calling Concat: slice []interface {} not supported in concat.
```

This commit fixes that by simplifying the type checking logic in `Slice`:

* If the first item implements the `Slicer` interface, we try that
* If the above fails or the first item does not implement `Slicer`, we just return the `[]interface {}`

Fixes #5269
2018-10-10 12:21:42 +02:00
Bjørn Erik Pedersen b5e17f7c83 Add GOPATH Hugo building tip 2018-10-09 11:09:00 +02:00
Bjørn Erik Pedersen 31a8bb8c07 common/maps: Improve append in Scratch
This commit consolidates the reflective collections handling in `.Scratch` vs the `tpl` package so they use the same code paths.

This commit also adds support for a corner case where a typed slice is appended to a nil or empty `[]interface{}`.

Fixes #5275
2018-10-08 12:30:50 +02:00
Bjørn Erik Pedersen 8e825ddf5b Revert "tpl: Fix baseof.html in error message"
I need to rethink this.

This reverts commit 646a52a5c5.
2018-10-07 21:08:41 +02:00
Bjørn Erik Pedersen 646a52a5c5 tpl: Fix baseof.html in error message
This fix should also make the template loadin slightly faster, as we avoid to reparse the baseof.html files more than one time.

Fixes #5288
2018-10-06 12:04:29 +02:00
Bjørn Erik Pedersen e421696d02 helpers: Consolidate MakeSegment vs MakePathSanitized
In short:

* Avoid double tolower in MakeSegment
* Use MakePathSanitized for taxonomies in pageToPermalinkTitle; this matches what MakeSegment does.
* Move the "double hyphen and space" logic into UnicodeSanitize

The last bullet may be slightly breaking for some that now does not get the "--" in some URLs, but we need to reduce the amount of URL logic.

See #4926
2018-10-03 15:55:25 +02:00
Gregorio Martinez 4b4af2c52e hugolib: Use stdlib context package 2018-10-03 14:59:35 +02:00
Cameron Moore fae48d7457 hugolib: Normalize permalink path segments
When constructing permalinks, ensure that most inputs used as path
segments are normalized with PathSpec.MakeSegment instead of
PathSpec.URLize.

The primary exception to that rule is with taxonomy titles in
pageToPermalinkTitle(). The approach taken here is to use URLize for
taxonomy pages. Everything else will use MakeSegment. The reason for
this exception is that people use taxonomies such as "s1/p1" to generate
URLs precisely they way they wish (see #5223). Tests have been added to
check for this case.

Fixes #4926
2018-10-03 11:02:15 +03:00
Gregorio Martinez 06d28a464d hugolib: Fix FuzzyWordCount test error message 2018-10-03 10:01:30 +03:00
Kaushal Modi 6818170308 Render Markdown in figure shortcode "caption" and "attr" params
Fixes https://github.com/gohugoio/hugo/issues/4406.
2018-10-03 09:55:53 +03:00
Kaushal Modi c5279064df Re-organize the figure shortcode for better readability 2018-10-03 09:55:53 +03:00
Gregorio Martinez 152cffb13a Update README & CONTRIBUTING
Fix broken links for Dev Chat that point to broken gitter.im room.
Remove lines about `mage vendor` target that is no longer available.
Remove broken table of contents links for headings in CONTRIBUTING.md
Fix typo
2018-10-03 09:50:02 +03:00
Cameron Moore 0d5110d033 tpl: Cast IsSet key to int for indexed types
Don't assume that the user sends an int as the key when checking against
indexed types.

Fixes #3681
2018-10-03 09:36:27 +03:00
Cameron Moore d3b81ee58e hugolib: Improve error message for bad taxonomy weights
Error is now "Unable to convert taxonomy weight []string{"40"} to int
for variables/files.md"
2018-10-03 09:34:45 +03:00
Cameron Moore 1fd30d462e hugolib: Cast taxonomy weight parameters to int
Fixes #4628
2018-10-03 09:34:45 +03:00
Cameron Moore 498d629958 hugolib: Allow nil to be unwrapped as *Page
Previously, calls to *Page.Eq(nil) would always return false because the
unwrapPage func didn't support the nil case.  Add support for unwrapping
nil to a *Page.

Fixes #5043
2018-10-03 09:33:42 +03:00
Bjørn Erik Pedersen 10ac2ec446 tpl/collections: Fix handling of different interface types in Slice
In Hugo `0.49` we improved type support in `slice`. This has an unfortunate side effect in that `resources.Concat` now expects something that can resolve to `resource.Resources`.

This worked for most situations, but when you try to `slice` different `Resource` objects, you would be getting `[]interface {}` and not `resource.Resources`. And `concat` would fail:

```bash
error calling Concat: slice []interface {} not supported in concat.
```

This commit fixes that by simplifying the type checking logic in `Slice`:

* If the first item implements the `Slicer` interface, we try that
* If the above fails or the first item does not implement `Slicer`, we just return the `[]interface {}`

Fixes #5269
2018-10-02 23:54:16 +03:00
Cameron Moore ce264b936c tpl: Add a delimiter parameter to lang.NumFmt
The original implementation of NumFmt did not take into account that the
options delimiter (a space) could be a valid option.  Adding a delim
parameter seems like the simplest, safest, and most flexible way to
solve this oversight in a backwards-compatible way.

Fixes #5260
2018-10-02 17:41:48 +03:00
Anthony Fok 91f49c0700 snap: Add custom x-nodejs plugin to support ppc64el and s390x 2018-09-30 23:03:05 -06:00
Anthony Fok a475bf125c snap: Fetch mage with GO111MODULE=off 2018-09-30 23:03:01 -06:00
Anthony Fok f1dede3708 snap: Revert 'Skip "mage -v test" due to build failure on Launchpad'
This reverts commit 52ac85fbc4.
2018-09-30 23:02:58 -06:00
Anthony Fok fa873a6cb3 snap: Use build-snaps instead of building go from source
See https://forum.snapcraft.io/t/go-part-fails-with-go1-11-if-go-mod-exists-in-the-top-level-directory/7546/11
2018-09-30 23:02:34 -06:00
Bjørn Erik Pedersen fb732d5322 hugolib: Be a litle more specific in NextPage TODO 2018-09-26 09:26:53 +02:00
Ricardo N Feliciano ad705aac06 hugolib: Introduce Page.NextPage and Page.PrevPage
Introduce new page position variables in order to fix the ordering issue
of `.Next` and `.Prev` while also allowing an upgrade path via
deprecation.

`.NextInSection` becomes `.NextPageInSection`.
`.PrevInSection` becomes `.PrevPageInSection`.

`.Next` becomes a function returning `.PrevPage`.
`.Prev` becomes a function returning `.NextPage`.

Fixes #1061
2018-09-26 09:22:43 +02:00
Anthony Fok 52ac85fbc4 snap: Skip "mage -v test" due to build failure on Launchpad
The run "go test ./... -tags none" fails with multiple errors of

    use of internal package github.com/gohugoio/hugo/tpl/internal not allowed

See https://launchpadlibrarian.net/390252700/buildlog_snap_ubuntu_xenial_amd64_hugo-dev_BUILDING.txt.gz

I was unable to reproduce it with snapcraft in LXD
on my local Debian laptop, so this error is still a mystery to me.
2018-09-25 16:08:22 -06:00
Anthony Fok 048a64be1c releaser: Update path to snap/snapcraft.yaml 2018-09-25 07:21:09 -06:00
Anthony Fok 3033a9a37e snap: Fix go plugin build failure by renaming go.mod
as a temporary workaround.

See https://forum.snapcraft.io/t/go-plugin-go1-11-fails-if-go-mod-exists-in-the-top-level-directory/7546
and https://discourse.gohugo.io/t/hugo-0-48-released/13908/12
2018-09-25 07:05:55 -06:00
Anthony Fok 27d4211187 snap: Move snapcraft.yaml to snap/snapcraft.yaml 2018-09-25 07:03:51 -06:00
Anthony Fok 4730a78a76 snapcraft: Try installing postcss again
Revert "snap: Skip installing postcss due to failure on build server"

This reverts commit 66f688f712.
2018-09-24 22:20:45 -06:00
Anthony Fok b7706d6aec snapcraft: Set GO111MODULE=on in override-build script
Apparently, snapcraft's yet-to-be-documented environment declaration
feature is for run-time only.

See https://discourse.gohugo.io/t/hugo-0-48-released/13908/9
and https://forum.snapcraft.io/t/declaratively-defining-environment-variables/175/29
2018-09-24 22:15:42 -06:00
Bjørn Erik Pedersen 48413d76f4 build: Update the temp docker script 2018-09-24 12:41:12 +02:00
hugoreleaser 66b9e2bff2 releaser: Prepare repository for 0.50-DEV
[ci skip]
2018-09-24 10:04:24 +00:00
1010 changed files with 69936 additions and 44785 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
defaults: &defaults
docker:
- image: bepsays/ci-goreleaser:1.11-2
- image: bepsays/ci-goreleaser:1.12-5
environment:
CGO_ENABLED: "0"
+1
View File
@@ -6,3 +6,4 @@
.circleci
docs
examples
Dockerfile
+5 -1
View File
@@ -1,4 +1,4 @@
hugo
/hugo
docs/public*
/.idea
hugo.exe
@@ -19,3 +19,7 @@ dock.sh
GoBuilds
dist
resources/sunset.jpg
vendor
+23 -10
View File
@@ -1,33 +1,46 @@
language: go
sudo: false
dist: trusty
dist: xenial
env:
HUGO_BUILD_TAGS="extended"
global:
- GOPROXY="https://proxy.golang.org"
- HUGO_BUILD_TAGS="extended"
git:
depth: false
go:
- 1.11
- "1.11.10"
- "1.12.5"
- tip
os:
- linux
- osx
- windows
matrix:
allow_failures:
- go: tip
fast_finish: true
exclude:
- os: windows
go: tip
install:
- mkdir -p $HOME/src
- mv $HOME/gopath/src/github.com/gohugoio/hugo $HOME/src
- mv $TRAVIS_BUILD_DIR $HOME/src
- export TRAVIS_BUILD_DIR=$HOME/src/hugo
- cd $HOME/src/hugo
- go get github.com/magefile/mage
script:
- go mod download
- mage -v test
- mage -v check
- mage -v hugo
- ./hugo -s docs/
- ./hugo --renderToMemory -s docs/
- go mod download || true
- mage -v test
- mage -v check
- mage -v hugo
- ./hugo -s docs/
- ./hugo --renderToMemory -s docs/
- df -h
before_install:
- df -h
# https://travis-ci.community/t/go-cant-find-gcc-with-go1-11-1-on-windows/293/5
- if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then choco install mingw -y; export PATH=/c/tools/mingw64/bin:"$PATH"; fi
- gem install asciidoctor
- type asciidoctor
+7 -20
View File
@@ -18,11 +18,8 @@ The Hugo community and maintainers are [very active](https://github.com/gohugoio
* [Submitting Patches](#submitting-patches)
* [Code Contribution Guidelines](#code-contribution-guidelines)
* [Git Commit Message Guidelines](#git-commit-message-guidelines)
* [Vendored Dependencies](#vendored-dependencies)
* [Fetching the Sources From GitHub](#fetching-the-sources-from-github)
* [Using Git Remotes](#using-git-remotes)
* [Build Hugo with Your Changes](#build-hugo-with-your-changes)
* [Updating the Hugo Sources](#updating-the-hugo-sources)
* [Building Hugo with Your Changes](#building-hugo-with-your-changes)
## Asking Support Questions
@@ -73,7 +70,7 @@ To make the contribution process as seamless as possible, we ask for the followi
* Run `go fmt`.
* Add documentation if you are adding new features or changing functionality. The docs site lives in `/docs`.
* Squash your commits into a single commit. `git rebase -i`. Its okay to force update your pull request with `git push -f`.
* Ensure that `mage check` succeeds. [Travis CI](https://travis-ci.org/gohugoio/hugo) (Linux and macOS) and [AppVeyor](https://ci.appveyor.com/project/gohugoio/hugo/branch/master) (Windows) will fail the build if `mage check` fails.
* Ensure that `mage check` succeeds. [Travis CI](https://travis-ci.org/gohugoio/hugo) (Windows, Linux and macOS) will fail the build if `mage check` fails.
* Follow the **Git Commit Message Guidelines** below.
### Git Commit Message Guidelines
@@ -106,7 +103,7 @@ Fixes #1949
### Fetching the Sources From GitHub
Since Hugo 0.48, Hugo uses the Go Modules support built into Go 1.11 to build. The easiest is is to clone Hugo in a directory outside of `GOPATH`, as in the following example:
Since Hugo 0.48, Hugo uses the Go Modules support built into Go 1.11 to build. The easiest is to clone Hugo in a directory outside of `GOPATH`, as in the following example:
```bash
mkdir $HOME/src
@@ -116,13 +113,15 @@ cd hugo
go install
```
>Note: Some Go tools may not be fully updated to support Go Modules yet. One example would be LiteIDE. Follow [this workaround](https://github.com/visualfc/liteide/issues/986#issuecomment-428117702) for how to continue to work with Hugo below `GOPATH`.
For some convenient build and test targets, you also will want to install Mage:
```bash
go get github.com/magefile/mage
```
Now, to make a change to Hugos's source:
Now, to make a change to Hugo's source:
1. Create a new branch for your changes (the branch name is arbitrary):
@@ -160,7 +159,7 @@ Hugo uses [mage](https://github.com/magefile/mage) to sync vendor dependencies,
cd $HOME/go/src/github.com/gohugoio/hugo
```
To build Hugo:
To build Hugo:
```bash
mage hugo
@@ -190,15 +189,3 @@ mage -l
```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
the source changes, but you'll need to keep the vendored packages up-to-date as
well.
```bash
git pull
mage vendor
```
+18 -11
View File
@@ -2,25 +2,32 @@
# Twitter: https://twitter.com/gohugoio
# Website: https://gohugo.io/
FROM golang:1.11-alpine3.7 AS build
FROM golang:1.11-stretch AS build
ENV CGO_ENABLED=0
ENV GOOS=linux
ENV GO111MODULE=on
WORKDIR /go/src/github.com/gohugoio/hugo
RUN apk add --no-cache \
git \
musl-dev
RUN apt-get install \
git gcc g++ binutils
COPY . /go/src/github.com/gohugoio/hugo/
RUN go install -ldflags '-s -w'
ENV GO111MODULE=on
RUN go get -d .
ARG CGO=0
ENV CGO_ENABLED=${CGO}
ENV GOOS=linux
# default non-existent build tag so -tags always has an arg
ARG BUILD_TAGS="99notag"
RUN go install -ldflags '-w -extldflags "-static"' -tags ${BUILD_TAGS}
# ---
FROM scratch
FROM alpine:3.9
RUN apk add --no-cache ca-certificates
COPY --from=build /go/bin/hugo /hugo
WORKDIR /site
VOLUME /site
ARG WORKDIR="/site"
WORKDIR ${WORKDIR}
VOLUME ${WORKDIR}
EXPOSE 1313
ENTRYPOINT [ "/hugo" ]
CMD [ "--help" ]
+75 -10
View File
@@ -1,19 +1,16 @@
![Hugo](https://raw.githubusercontent.com/gohugoio/hugoDocs/master/static/img/hugo-logo.png)
<img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/static/images/hugo-logo-wide.svg?sanitize=true" alt="Hugo" width="565">
A Fast and Flexible Static Site Generator built with love by [bep](https://github.com/bep), [spf13](http://spf13.com/) and [friends](https://github.com/gohugoio/hugo/graphs/contributors) in [Go][].
[Website](https://gohugo.io) |
[Forum](https://discourse.gohugo.io) |
[Developer Chat (no support)](https://gitter.im/gohugoio/hugo) |
[Documentation](https://gohugo.io/overview/introduction/) |
[Installation Guide](https://gohugo.io/overview/installing/) |
[Documentation](https://gohugo.io/getting-started/) |
[Installation Guide](https://gohugo.io/getting-started/installing/) |
[Contribution Guide](CONTRIBUTING.md) |
[Twitter](https://twitter.com/gohugoio)
[![GoDoc](https://godoc.org/github.com/gohugoio/hugo?status.svg)](https://godoc.org/github.com/gohugoio/hugo)
[![Linux and macOS Build Status](https://api.travis-ci.org/gohugoio/hugo.svg?branch=master&label=Linux+and+macOS+build "Linux and macOS Build Status")](https://travis-ci.org/gohugoio/hugo)
[![Windows Build Status](https://ci.appveyor.com/api/projects/status/a5mr220vsd091kua?svg=true&label=Windows+build "Windows Build Status")](https://ci.appveyor.com/project/bep/hugo/branch/master)
[![Dev chat at https://gitter.im/gohugoio/hugo](https://img.shields.io/badge/gitter-developer_chat-46bc99.svg)](https://gitter.im/spf13/hugo?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Linux and macOS Build Status](https://api.travis-ci.org/gohugoio/hugo.svg?branch=master&label=Windows+and+Linux+and+macOS+build "Windows, Linux and macOS Build Status")](https://travis-ci.org/gohugoio/hugo)
[![Go Report Card](https://goreportcard.com/badge/github.com/gohugoio/hugo)](https://goreportcard.com/report/github.com/gohugoio/hugo)
## Overview
@@ -36,7 +33,7 @@ Currently, we provide pre-built Hugo binaries for Windows, Linux, FreeBSD, NetBS
Hugo may also be compiled from source wherever the Go compiler tool chain can run, e.g. for other operating systems including DragonFly BSD, OpenBSD, Plan 9, and Solaris.
**Complete documentation is available at [Hugo Documentation][].**
**Complete documentation is available at [Hugo Documentation](https://gohugo.io/getting-started/).**
## Choose How to Install
@@ -50,7 +47,7 @@ Building the binaries is an easy task for an experienced `go` getter.
### Install Hugo as Your Site Generator (Binary Install)
Use the [installation instructions in the Hugo documentation](https://gohugo.io/overview/installing/).
Use the [installation instructions in the Hugo documentation](https://gohugo.io/getting-started/installing/).
### Build and Install the Binaries from Source (Advanced Install)
@@ -61,7 +58,7 @@ Use the [installation instructions in the Hugo documentation](https://gohugo.io/
#### Fetch from GitHub
Since Hugo 0.48, Hugo uses the Go Modules support built into Go 1.11 to build. The easiest is is to clone Hugo in a directory outside of `GOPATH`, as in the following example:
Since Hugo 0.48, Hugo uses the Go Modules support built into Go 1.11 to build. The easiest is to clone Hugo in a directory outside of `GOPATH`, as in the following example:
```bash
mkdir $HOME/src
@@ -120,3 +117,71 @@ For a complete guide to contributing code to Hugo, see the [Contribution Guide](
[Go]: https://golang.org/
[Hugo Documentation]: https://gohugo.io/overview/introduction/
## Dependencies
Hugo stands on the shoulder of many great open source libraries, in lexical order:
| Dependency | License |
| :------------- | :------------- |
| [github.com/BurntSushi/locker](https://github.com/BurntSushi/locker) | The Unlicense |
| [github.com/BurntSushi/toml](https://github.com/BurntSushi/toml) | MIT License |
| [github.com/PuerkitoBio/purell](https://github.com/PuerkitoBio/purell) | BSD 3-Clause "New" or "Revised" License |
| [github.com/PuerkitoBio/urlesc](https://github.com/PuerkitoBio/urlesc) | BSD 3-Clause "New" or "Revised" License |
| [github.com/alecthomas/chroma](https://github.com/alecthomas/chroma) | MIT License |
| [github.com/bep/debounce](https://github.com/bep/debounce) | MIT License |
| [github.com/bep/gitmap](https://github.com/bep/gitmap) | MIT License |
| [github.com/bep/go-tocss](https://github.com/bep/go-tocss) | MIT License |
| [github.com/niklasfasching/go-org](https://github.com/niklasfasching/go-org) | MIT License |
| [github.com/cpuguy83/go-md2man](https://github.com/cpuguy83/go-md2man) | MIT License |
| [github.com/danwakefield/fnmatch](https://github.com/danwakefield/fnmatch) | BSD 2-Clause "Simplified" License |
| [github.com/disintegration/imaging](https://github.com/disintegration/imaging) | MIT License |
| [github.com/dlclark/regexp2](https://github.com/dlclark/regexp2) | MIT License |
| [github.com/eknkc/amber](https://github.com/eknkc/amber) | MIT License |
| [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify) | BSD 3-Clause "New" or "Revised" License |
| [github.com/gobwas/glob](https://github.com/gobwas/glob) | MIT License |
| [github.com/gorilla/websocket](https://github.com/gorilla/websocket) | BSD 2-Clause "Simplified" License |
| [github.com/hashicorp/go-immutable-radix](https://github.com/hashicorp/go-immutable-radix) | Mozilla Public License 2.0 |
| [github.com/hashicorp/golang-lru](https://github.com/hashicorp/golang-lru) | Mozilla Public License 2.0 |
| [github.com/hashicorp/hcl](https://github.com/hashicorp/hcl) | Mozilla Public License 2.0 |
| [github.com/jdkato/prose](https://github.com/jdkato/prose) | MIT License |
| [github.com/kyokomi/emoji](https://github.com/kyokomi/emoji) | MIT License |
| [github.com/magiconair/properties](https://github.com/magiconair/properties) | BSD 2-Clause "Simplified" License |
| [github.com/markbates/inflect](https://github.com/markbates/inflect) | MIT License |
| [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) | MIT License |
| [github.com/mattn/go-runewidth](https://github.com/mattn/go-runewidth) | MIT License |
| [github.com/miekg/mmark](https://github.com/miekg/mmark) | Simplified BSD License |
| [github.com/mitchellh/hashstructure](https://github.com/mitchellh/hashstructure) | MIT License |
| [github.com/mitchellh/mapstructure](https://github.com/mitchellh/mapstructure) | MIT License |
| [github.com/muesli/smartcrop](https://github.com/muesli/smartcrop) | MIT License |
| [github.com/nicksnyder/go-i18n](https://github.com/nicksnyder/go-i18n) | MIT License |
| [github.com/olekukonko/tablewriter](https://github.com/olekukonko/tablewriter) | MIT License |
| [github.com/pelletier/go-toml](https://github.com/pelletier/go-toml) | MIT License |
| [github.com/pkg/errors](https://github.com/pkg/errors) | BSD 2-Clause "Simplified" License |
| [github.com/russross/blackfriday](https://github.com/russross/blackfriday) | Simplified BSD License |
| [github.com/shurcooL/sanitized_anchor_name](https://github.com/shurcooL/sanitized_anchor_name) | MIT License |
| [github.com/spf13/afero](https://github.com/spf13/afero) | Apache License 2.0 |
| [github.com/spf13/cast](https://github.com/spf13/cast) | MIT License |
| [github.com/spf13/cobra](https://github.com/spf13/cobra) | Apache License 2.0 |
| [github.com/spf13/fsync](https://github.com/spf13/fsync) | MIT License |
| [github.com/spf13/jwalterweatherman](https://github.com/spf13/jwalterweatherman) | MIT License |
| [github.com/spf13/nitro](https://github.com/spf13/nitro) | Apache License 2.0 |
| [github.com/spf13/pflag](https://github.com/spf13/pflag) | BSD 3-Clause "New" or "Revised" License |
| [github.com/spf13/viper](https://github.com/spf13/viper) | MIT License |
| [github.com/tdewolff/minify](https://github.com/tdewolff/minify) | MIT License |
| [github.com/tdewolff/parse](https://github.com/tdewolff/parse) | MIT License |
| [github.com/wellington/go-libsass](https://github.com/wellington/go-libsass) | Apache License 2.0 |
| [github.com/yosssi/ace](https://github.com/yosssi/ace) | MIT License |
| [golang.org/x/image](https://golang.org/x/image) | BSD 3-Clause "New" or "Revised" License |
| [golang.org/x/net](https://golang.org/x/net) | BSD 3-Clause "New" or "Revised" License |
| [golang.org/x/sync](https://golang.org/x/sync) | BSD 3-Clause "New" or "Revised" License |
| [golang.org/x/sys](https://golang.org/x/sys) | BSD 3-Clause "New" or "Revised" License |
| [golang.org/x/text](https://golang.org/x/text) | BSD 3-Clause "New" or "Revised" License
| [gopkg.in/yaml.v2](https://gopkg.in/yaml.v2) | Apache License 2.0 |
-24
View File
@@ -1,24 +0,0 @@
image: Visual Studio 2015
init:
- 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:
HUGO_BUILD_TAGS: extended
# clones and cd's to path
clone_folder: C:\hugo
install:
# - gem install asciidoctor
- pip install docutils
- go get github.com/magefile/mage
build_script:
- go mod download
- mage hugoRace
- mage -v check
- hugo -s docs/
- hugo --renderToMemory -s docs/
Executable
+1
View File
@@ -0,0 +1 @@
gobench -package=./hugolib -bench="BenchmarkSiteNew/Deep_content_tree"
+1 -2
View File
@@ -1,2 +1 @@
# 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
docker run --rm --mount type=bind,source="$(pwd)",target=/hugo -w /hugo -i -t bepsays/ci-goreleaser:1.11-2 /bin/bash
+7 -3
View File
@@ -14,14 +14,18 @@
package bufferpool
import (
"github.com/stretchr/testify/assert"
"testing"
qt "github.com/frankban/quicktest"
)
func TestBufferPool(t *testing.T) {
c := qt.New(t)
buff := GetBuffer()
buff.WriteString("do be do be do")
assert.Equal(t, "do be do be do", buff.String())
c.Assert(buff.String(), qt.Equals, "do be do be do")
PutBuffer(buff)
assert.Equal(t, 0, buff.Len())
c.Assert(buff.Len(), qt.Equals, 0)
}
+366
View File
@@ -0,0 +1,366 @@
// 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 filecache
import (
"bytes"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
"time"
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/helpers"
"github.com/BurntSushi/locker"
"github.com/spf13/afero"
)
const (
filecacheRootDirname = "filecache"
)
// Cache caches a set of files in a directory. This is usually a file on
// disk, but since this is backed by an Afero file system, it can be anything.
type Cache struct {
Fs afero.Fs
// Max age for items in this cache. Negative duration means forever,
// 0 is effectively turning this cache off.
maxAge time.Duration
// When set, we just remove this entire root directory on expiration.
pruneAllRootDir string
nlocker *lockTracker
}
type lockTracker struct {
seenMu sync.RWMutex
seen map[string]struct{}
*locker.Locker
}
// Lock tracks the ids in use. We use this information to do garbage collection
// after a Hugo build.
func (l *lockTracker) Lock(id string) {
l.seenMu.RLock()
if _, seen := l.seen[id]; !seen {
l.seenMu.RUnlock()
l.seenMu.Lock()
l.seen[id] = struct{}{}
l.seenMu.Unlock()
} else {
l.seenMu.RUnlock()
}
l.Locker.Lock(id)
}
// ItemInfo contains info about a cached file.
type ItemInfo struct {
// This is the file's name relative to the cache's filesystem.
Name string
}
// NewCache creates a new file cache with the given filesystem and max age.
func NewCache(fs afero.Fs, maxAge time.Duration, pruneAllRootDir string) *Cache {
return &Cache{
Fs: fs,
nlocker: &lockTracker{Locker: locker.NewLocker(), seen: make(map[string]struct{})},
maxAge: maxAge,
pruneAllRootDir: pruneAllRootDir,
}
}
// lockedFile is a file with a lock that is released on Close.
type lockedFile struct {
afero.File
unlock func()
}
func (l *lockedFile) Close() error {
defer l.unlock()
return l.File.Close()
}
// WriteCloser returns a transactional writer into the cache.
// It's important that it's closed when done.
func (c *Cache) WriteCloser(id string) (ItemInfo, io.WriteCloser, error) {
id = cleanID(id)
c.nlocker.Lock(id)
info := ItemInfo{Name: id}
f, err := helpers.OpenFileForWriting(c.Fs, id)
if err != nil {
c.nlocker.Unlock(id)
return info, nil, err
}
return info, &lockedFile{
File: f,
unlock: func() { c.nlocker.Unlock(id) },
}, nil
}
// ReadOrCreate tries to lookup the file in cache.
// If found, it is passed to read and then closed.
// If not found a new file is created and passed to create, which should close
// it when done.
func (c *Cache) ReadOrCreate(id string,
read func(info ItemInfo, r io.Reader) error,
create func(info ItemInfo, w io.WriteCloser) error) (info ItemInfo, err error) {
id = cleanID(id)
c.nlocker.Lock(id)
defer c.nlocker.Unlock(id)
info = ItemInfo{Name: id}
if r := c.getOrRemove(id); r != nil {
err = read(info, r)
defer r.Close()
return
}
f, err := helpers.OpenFileForWriting(c.Fs, id)
if err != nil {
return
}
err = create(info, f)
return
}
// GetOrCreate tries to get the file with the given id from cache. If not found or expired, create will
// be invoked and the result cached.
// This method is protected by a named lock using the given id as identifier.
func (c *Cache) GetOrCreate(id string, create func() (io.ReadCloser, error)) (ItemInfo, io.ReadCloser, error) {
id = cleanID(id)
c.nlocker.Lock(id)
defer c.nlocker.Unlock(id)
info := ItemInfo{Name: id}
if r := c.getOrRemove(id); r != nil {
return info, r, nil
}
r, err := create()
if err != nil {
return info, nil, err
}
if c.maxAge == 0 {
// No caching.
return info, hugio.ToReadCloser(r), nil
}
var buff bytes.Buffer
return info,
hugio.ToReadCloser(&buff),
afero.WriteReader(c.Fs, id, io.TeeReader(r, &buff))
}
// GetOrCreateBytes is the same as GetOrCreate, but produces a byte slice.
func (c *Cache) GetOrCreateBytes(id string, create func() ([]byte, error)) (ItemInfo, []byte, error) {
id = cleanID(id)
c.nlocker.Lock(id)
defer c.nlocker.Unlock(id)
info := ItemInfo{Name: id}
if r := c.getOrRemove(id); r != nil {
defer r.Close()
b, err := ioutil.ReadAll(r)
return info, b, err
}
b, err := create()
if err != nil {
return info, nil, err
}
if c.maxAge == 0 {
return info, b, nil
}
if err := afero.WriteReader(c.Fs, id, bytes.NewReader(b)); err != nil {
return info, nil, err
}
return info, b, nil
}
// GetBytes gets the file content with the given id from the cahce, nil if none found.
func (c *Cache) GetBytes(id string) (ItemInfo, []byte, error) {
id = cleanID(id)
c.nlocker.Lock(id)
defer c.nlocker.Unlock(id)
info := ItemInfo{Name: id}
if r := c.getOrRemove(id); r != nil {
defer r.Close()
b, err := ioutil.ReadAll(r)
return info, b, err
}
return info, nil, nil
}
// Get gets the file with the given id from the cahce, nil if none found.
func (c *Cache) Get(id string) (ItemInfo, io.ReadCloser, error) {
id = cleanID(id)
c.nlocker.Lock(id)
defer c.nlocker.Unlock(id)
info := ItemInfo{Name: id}
r := c.getOrRemove(id)
return info, r, nil
}
// getOrRemove gets the file with the given id. If it's expired, it will
// be removed.
func (c *Cache) getOrRemove(id string) hugio.ReadSeekCloser {
if c.maxAge == 0 {
// No caching.
return nil
}
if c.maxAge > 0 {
fi, err := c.Fs.Stat(id)
if err != nil {
return nil
}
if c.isExpired(fi.ModTime()) {
c.Fs.Remove(id)
return nil
}
}
f, err := c.Fs.Open(id)
if err != nil {
return nil
}
return f
}
func (c *Cache) isExpired(modTime time.Time) bool {
if c.maxAge < 0 {
return false
}
return c.maxAge == 0 || time.Since(modTime) > c.maxAge
}
// For testing
func (c *Cache) getString(id string) string {
id = cleanID(id)
c.nlocker.Lock(id)
defer c.nlocker.Unlock(id)
f, err := c.Fs.Open(id)
if err != nil {
return ""
}
defer f.Close()
b, _ := ioutil.ReadAll(f)
return string(b)
}
// Caches is a named set of caches.
type Caches map[string]*Cache
// Get gets a named cache, nil if none found.
func (f Caches) Get(name string) *Cache {
return f[strings.ToLower(name)]
}
// NewCaches creates a new set of file caches from the given
// configuration.
func NewCaches(p *helpers.PathSpec) (Caches, error) {
var dcfg Configs
if c, ok := p.Cfg.Get("filecacheConfigs").(Configs); ok {
dcfg = c
} else {
var err error
dcfg, err = DecodeConfig(p.Fs.Source, p.Cfg)
if err != nil {
return nil, err
}
}
fs := p.Fs.Source
m := make(Caches)
for k, v := range dcfg {
var cfs afero.Fs
if v.isResourceDir {
cfs = p.BaseFs.ResourcesCache
} else {
cfs = fs
}
if cfs == nil {
// TODO(bep) we still have some places that do not initialize the
// full dependencies of a site, e.g. the import Jekyll command.
// That command does not need these caches, so let us just continue
// for now.
continue
}
baseDir := v.Dir
if err := cfs.MkdirAll(baseDir, 0777); err != nil && !os.IsExist(err) {
return nil, err
}
bfs := afero.NewBasePathFs(cfs, baseDir)
var pruneAllRootDir string
if k == cacheKeyModules {
pruneAllRootDir = "pkg"
}
m[k] = NewCache(bfs, v.MaxAge, pruneAllRootDir)
}
return m, nil
}
func cleanID(name string) string {
return strings.TrimPrefix(filepath.Clean(name), helpers.FilePathSeparator)
}
+230
View File
@@ -0,0 +1,230 @@
// 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 filecache
import (
"path"
"path/filepath"
"strings"
"time"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
"github.com/spf13/afero"
)
const (
cachesConfigKey = "caches"
resourcesGenDir = ":resourceDir/_gen"
)
var defaultCacheConfig = Config{
MaxAge: -1, // Never expire
Dir: ":cacheDir/:project",
}
const (
cacheKeyGetJSON = "getjson"
cacheKeyGetCSV = "getcsv"
cacheKeyImages = "images"
cacheKeyAssets = "assets"
cacheKeyModules = "modules"
)
type Configs map[string]Config
func (c Configs) CacheDirModules() string {
return c[cacheKeyModules].Dir
}
var defaultCacheConfigs = Configs{
cacheKeyModules: {
MaxAge: -1,
Dir: ":cacheDir/modules",
},
cacheKeyGetJSON: defaultCacheConfig,
cacheKeyGetCSV: defaultCacheConfig,
cacheKeyImages: {
MaxAge: -1,
Dir: resourcesGenDir,
},
cacheKeyAssets: {
MaxAge: -1,
Dir: resourcesGenDir,
},
}
type Config struct {
// Max age of cache entries in this cache. Any items older than this will
// be removed and not returned from the cache.
// a negative value means forever, 0 means cache is disabled.
MaxAge time.Duration
// The directory where files are stored.
Dir string
// Will resources/_gen will get its own composite filesystem that
// also checks any theme.
isResourceDir bool
}
// GetJSONCache gets the file cache for getJSON.
func (f Caches) GetJSONCache() *Cache {
return f[cacheKeyGetJSON]
}
// GetCSVCache gets the file cache for getCSV.
func (f Caches) GetCSVCache() *Cache {
return f[cacheKeyGetCSV]
}
// ImageCache gets the file cache for processed images.
func (f Caches) ImageCache() *Cache {
return f[cacheKeyImages]
}
// ModulesCache gets the file cache for Hugo Modules.
func (f Caches) ModulesCache() *Cache {
return f[cacheKeyModules]
}
// AssetsCache gets the file cache for assets (processed resources, SCSS etc.).
func (f Caches) AssetsCache() *Cache {
return f[cacheKeyAssets]
}
func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) {
c := make(Configs)
valid := make(map[string]bool)
// Add defaults
for k, v := range defaultCacheConfigs {
c[k] = v
valid[k] = true
}
m := cfg.GetStringMap(cachesConfigKey)
_, isOsFs := fs.(*afero.OsFs)
for k, v := range m {
cc := defaultCacheConfig
dc := &mapstructure.DecoderConfig{
Result: &cc,
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
WeaklyTypedInput: true,
}
decoder, err := mapstructure.NewDecoder(dc)
if err != nil {
return c, err
}
if err := decoder.Decode(v); err != nil {
return nil, err
}
if cc.Dir == "" {
return c, errors.New("must provide cache Dir")
}
name := strings.ToLower(k)
if !valid[name] {
return nil, errors.Errorf("%q is not a valid cache name", name)
}
c[name] = cc
}
// This is a very old flag in Hugo, but we need to respect it.
disabled := cfg.GetBool("ignoreCache")
for k, v := range c {
dir := filepath.ToSlash(filepath.Clean(v.Dir))
hadSlash := strings.HasPrefix(dir, "/")
parts := strings.Split(dir, "/")
for i, part := range parts {
if strings.HasPrefix(part, ":") {
resolved, isResource, err := resolveDirPlaceholder(fs, cfg, part)
if err != nil {
return c, err
}
if isResource {
v.isResourceDir = true
}
parts[i] = resolved
}
}
dir = path.Join(parts...)
if hadSlash {
dir = "/" + dir
}
v.Dir = filepath.Clean(filepath.FromSlash(dir))
if !v.isResourceDir {
if isOsFs && !filepath.IsAbs(v.Dir) {
return c, errors.Errorf("%q must resolve to an absolute directory", v.Dir)
}
// Avoid cache in root, e.g. / (Unix) or c:\ (Windows)
if len(strings.TrimPrefix(v.Dir, filepath.VolumeName(v.Dir))) == 1 {
return c, errors.Errorf("%q is a root folder and not allowed as cache dir", v.Dir)
}
}
if !strings.HasPrefix(v.Dir, "_gen") {
// We do cache eviction (file removes) and since the user can set
// his/hers own cache directory, we really want to make sure
// we do not delete any files that do not belong to this cache.
// We do add the cache name as the root, but this is an extra safe
// guard. We skip the files inside /resources/_gen/ because
// that would be breaking.
v.Dir = filepath.Join(v.Dir, filecacheRootDirname, k)
} else {
v.Dir = filepath.Join(v.Dir, k)
}
if disabled {
v.MaxAge = 0
}
c[k] = v
}
return c, nil
}
// Resolves :resourceDir => /myproject/resources etc., :cacheDir => ...
func resolveDirPlaceholder(fs afero.Fs, cfg config.Provider, placeholder string) (cacheDir string, isResource bool, err error) {
workingDir := cfg.GetString("workingDir")
switch strings.ToLower(placeholder) {
case ":resourcedir":
return "", true, nil
case ":cachedir":
d, err := helpers.GetCacheDir(fs, cfg)
return d, false, err
case ":project":
return filepath.Base(workingDir), false, nil
}
return "", false, errors.Errorf("%q is not a valid placeholder (valid values are :cacheDir or :resourceDir)", placeholder)
}
+196
View File
@@ -0,0 +1,196 @@
// 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 filecache
import (
"path/filepath"
"runtime"
"strings"
"testing"
"time"
"github.com/spf13/afero"
"github.com/gohugoio/hugo/config"
qt "github.com/frankban/quicktest"
"github.com/spf13/viper"
)
func TestDecodeConfig(t *testing.T) {
t.Parallel()
c := qt.New(t)
configStr := `
resourceDir = "myresources"
contentDir = "content"
dataDir = "data"
i18nDir = "i18n"
layoutDir = "layouts"
assetDir = "assets"
archetypeDir = "archetypes"
[caches]
[caches.getJSON]
maxAge = "10m"
dir = "/path/to/c1"
[caches.getCSV]
maxAge = "11h"
dir = "/path/to/c2"
[caches.images]
dir = "/path/to/c3"
`
cfg, err := config.FromConfigString(configStr, "toml")
c.Assert(err, qt.IsNil)
fs := afero.NewMemMapFs()
decoded, err := DecodeConfig(fs, cfg)
c.Assert(err, qt.IsNil)
c.Assert(len(decoded), qt.Equals, 5)
c2 := decoded["getcsv"]
c.Assert(c2.MaxAge.String(), qt.Equals, "11h0m0s")
c.Assert(c2.Dir, qt.Equals, filepath.FromSlash("/path/to/c2/filecache/getcsv"))
c3 := decoded["images"]
c.Assert(c3.MaxAge, qt.Equals, time.Duration(-1))
c.Assert(c3.Dir, qt.Equals, filepath.FromSlash("/path/to/c3/filecache/images"))
}
func TestDecodeConfigIgnoreCache(t *testing.T) {
t.Parallel()
c := qt.New(t)
configStr := `
resourceDir = "myresources"
contentDir = "content"
dataDir = "data"
i18nDir = "i18n"
layoutDir = "layouts"
assetDir = "assets"
archeTypedir = "archetypes"
ignoreCache = true
[caches]
[caches.getJSON]
maxAge = 1234
dir = "/path/to/c1"
[caches.getCSV]
maxAge = 3456
dir = "/path/to/c2"
[caches.images]
dir = "/path/to/c3"
`
cfg, err := config.FromConfigString(configStr, "toml")
c.Assert(err, qt.IsNil)
fs := afero.NewMemMapFs()
decoded, err := DecodeConfig(fs, cfg)
c.Assert(err, qt.IsNil)
c.Assert(len(decoded), qt.Equals, 5)
for _, v := range decoded {
c.Assert(v.MaxAge, qt.Equals, time.Duration(0))
}
}
func TestDecodeConfigDefault(t *testing.T) {
c := qt.New(t)
cfg := newTestConfig()
if runtime.GOOS == "windows" {
cfg.Set("resourceDir", "c:\\cache\\resources")
cfg.Set("cacheDir", "c:\\cache\\thecache")
} else {
cfg.Set("resourceDir", "/cache/resources")
cfg.Set("cacheDir", "/cache/thecache")
}
fs := afero.NewMemMapFs()
decoded, err := DecodeConfig(fs, cfg)
c.Assert(err, qt.IsNil)
c.Assert(len(decoded), qt.Equals, 5)
imgConfig := decoded[cacheKeyImages]
jsonConfig := decoded[cacheKeyGetJSON]
if runtime.GOOS == "windows" {
c.Assert(imgConfig.Dir, qt.Equals, filepath.FromSlash("_gen/images"))
} else {
c.Assert(imgConfig.Dir, qt.Equals, "_gen/images")
c.Assert(jsonConfig.Dir, qt.Equals, "/cache/thecache/hugoproject/filecache/getjson")
}
c.Assert(imgConfig.isResourceDir, qt.Equals, true)
c.Assert(jsonConfig.isResourceDir, qt.Equals, false)
}
func TestDecodeConfigInvalidDir(t *testing.T) {
t.Parallel()
c := qt.New(t)
configStr := `
resourceDir = "myresources"
contentDir = "content"
dataDir = "data"
i18nDir = "i18n"
layoutDir = "layouts"
assetDir = "assets"
archeTypedir = "archetypes"
[caches]
[caches.getJSON]
maxAge = "10m"
dir = "/"
`
if runtime.GOOS == "windows" {
configStr = strings.Replace(configStr, "/", "c:\\\\", 1)
}
cfg, err := config.FromConfigString(configStr, "toml")
c.Assert(err, qt.IsNil)
fs := afero.NewMemMapFs()
_, err = DecodeConfig(fs, cfg)
c.Assert(err, qt.Not(qt.IsNil))
}
func newTestConfig() *viper.Viper {
cfg := viper.New()
cfg.Set("workingDir", filepath.FromSlash("/my/cool/hugoproject"))
cfg.Set("contentDir", "content")
cfg.Set("dataDir", "data")
cfg.Set("resourceDir", "resources")
cfg.Set("i18nDir", "i18n")
cfg.Set("layoutDir", "layouts")
cfg.Set("archetypeDir", "archetypes")
cfg.Set("assetDir", "assets")
return cfg
}
+127
View File
@@ -0,0 +1,127 @@
// 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 filecache
import (
"io"
"os"
"github.com/pkg/errors"
"github.com/spf13/afero"
)
// Prune removes expired and unused items from this cache.
// The last one requires a full build so the cache usage can be tracked.
// Note that we operate directly on the filesystem here, so this is not
// thread safe.
func (c Caches) Prune() (int, error) {
counter := 0
for k, cache := range c {
count, err := cache.Prune(false)
if err != nil {
return counter, errors.Wrapf(err, "failed to prune cache %q", k)
}
counter += count
}
return counter, nil
}
// Prune removes expired and unused items from this cache.
// If force is set, everything will be removed not considering expiry time.
func (c *Cache) Prune(force bool) (int, error) {
if c.pruneAllRootDir != "" {
return c.pruneRootDir(force)
}
counter := 0
err := afero.Walk(c.Fs, "", func(name string, info os.FileInfo, err error) error {
if info == nil {
return nil
}
name = cleanID(name)
if info.IsDir() {
f, err := c.Fs.Open(name)
if err != nil {
// This cache dir may not exist.
return nil
}
defer f.Close()
_, err = f.Readdirnames(1)
if err == io.EOF {
// Empty dir.
return c.Fs.Remove(name)
}
return nil
}
shouldRemove := force || c.isExpired(info.ModTime())
if !shouldRemove && len(c.nlocker.seen) > 0 {
// Remove it if it's not been touched/used in the last build.
_, seen := c.nlocker.seen[name]
shouldRemove = !seen
}
if shouldRemove {
err := c.Fs.Remove(name)
if err == nil {
counter++
}
return err
}
return nil
})
return counter, err
}
func (c *Cache) pruneRootDir(force bool) (int, error) {
info, err := c.Fs.Stat(c.pruneAllRootDir)
if err != nil {
if os.IsNotExist(err) {
return 0, nil
}
return 0, err
}
if !force && !c.isExpired(info.ModTime()) {
return 0, nil
}
counter := 0
// Module cache has 0555 directories; make them writable in order to remove content.
afero.Walk(c.Fs, c.pruneAllRootDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
if info.IsDir() {
counter++
c.Fs.Chmod(path, 0777)
}
return nil
})
return 1, c.Fs.RemoveAll(c.pruneAllRootDir)
}
+111
View File
@@ -0,0 +1,111 @@
// 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 filecache
import (
"fmt"
"testing"
"time"
"github.com/spf13/afero"
qt "github.com/frankban/quicktest"
)
func TestPrune(t *testing.T) {
t.Parallel()
c := qt.New(t)
configStr := `
resourceDir = "myresources"
contentDir = "content"
dataDir = "data"
i18nDir = "i18n"
layoutDir = "layouts"
assetDir = "assets"
archeTypedir = "archetypes"
[caches]
[caches.getjson]
maxAge = "200ms"
dir = "/cache/c"
[caches.getcsv]
maxAge = "200ms"
dir = "/cache/d"
[caches.assets]
maxAge = "200ms"
dir = ":resourceDir/_gen"
[caches.images]
maxAge = "200ms"
dir = ":resourceDir/_gen"
`
for _, name := range []string{cacheKeyGetCSV, cacheKeyGetJSON, cacheKeyAssets, cacheKeyImages} {
msg := qt.Commentf("cache: %s", name)
p := newPathsSpec(t, afero.NewMemMapFs(), configStr)
caches, err := NewCaches(p)
c.Assert(err, qt.IsNil)
cache := caches[name]
for i := 0; i < 10; i++ {
id := fmt.Sprintf("i%d", i)
cache.GetOrCreateBytes(id, func() ([]byte, error) {
return []byte("abc"), nil
})
if i == 4 {
// This will expire the first 5
time.Sleep(201 * time.Millisecond)
}
}
count, err := caches.Prune()
c.Assert(err, qt.IsNil)
c.Assert(count, qt.Equals, 5, msg)
for i := 0; i < 10; i++ {
id := fmt.Sprintf("i%d", i)
v := cache.getString(id)
if i < 5 {
c.Assert(v, qt.Equals, "")
} else {
c.Assert(v, qt.Equals, "abc")
}
}
caches, err = NewCaches(p)
c.Assert(err, qt.IsNil)
cache = caches[name]
// Touch one and then prune.
cache.GetOrCreateBytes("i5", func() ([]byte, error) {
return []byte("abc"), nil
})
count, err = caches.Prune()
c.Assert(err, qt.IsNil)
c.Assert(count, qt.Equals, 4)
// Now only the i5 should be left.
for i := 0; i < 10; i++ {
id := fmt.Sprintf("i%d", i)
v := cache.getString(id)
if i != 5 {
c.Assert(v, qt.Equals, "")
} else {
c.Assert(v, qt.Equals, "abc")
}
}
}
}
+298
View File
@@ -0,0 +1,298 @@
// 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 filecache
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
"testing"
"time"
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/modules"
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
"github.com/spf13/afero"
qt "github.com/frankban/quicktest"
)
func TestFileCache(t *testing.T) {
t.Parallel()
c := qt.New(t)
tempWorkingDir, err := ioutil.TempDir("", "hugo_filecache_test_work")
c.Assert(err, qt.IsNil)
defer os.Remove(tempWorkingDir)
tempCacheDir, err := ioutil.TempDir("", "hugo_filecache_test_cache")
c.Assert(err, qt.IsNil)
defer os.Remove(tempCacheDir)
osfs := afero.NewOsFs()
for _, test := range []struct {
cacheDir string
workingDir string
}{
// Run with same dirs twice to make sure that works.
{tempCacheDir, tempWorkingDir},
{tempCacheDir, tempWorkingDir},
} {
configStr := `
workingDir = "WORKING_DIR"
resourceDir = "resources"
cacheDir = "CACHEDIR"
contentDir = "content"
dataDir = "data"
i18nDir = "i18n"
layoutDir = "layouts"
assetDir = "assets"
archeTypedir = "archetypes"
[caches]
[caches.getJSON]
maxAge = "10h"
dir = ":cacheDir/c"
`
winPathSep := "\\\\"
replacer := strings.NewReplacer("CACHEDIR", test.cacheDir, "WORKING_DIR", test.workingDir)
configStr = replacer.Replace(configStr)
configStr = strings.Replace(configStr, "\\", winPathSep, -1)
p := newPathsSpec(t, osfs, configStr)
caches, err := NewCaches(p)
c.Assert(err, qt.IsNil)
cache := caches.Get("GetJSON")
c.Assert(cache, qt.Not(qt.IsNil))
c.Assert(cache.maxAge.String(), qt.Equals, "10h0m0s")
bfs, ok := cache.Fs.(*afero.BasePathFs)
c.Assert(ok, qt.Equals, true)
filename, err := bfs.RealPath("key")
c.Assert(err, qt.IsNil)
if test.cacheDir != "" {
c.Assert(filename, qt.Equals, filepath.Join(test.cacheDir, "c/"+filecacheRootDirname+"/getjson/key"))
} else {
// Temp dir.
c.Assert(filename, qt.Matches, ".*hugo_cache.*"+filecacheRootDirname+".*key")
}
cache = caches.Get("Images")
c.Assert(cache, qt.Not(qt.IsNil))
c.Assert(cache.maxAge, qt.Equals, time.Duration(-1))
bfs, ok = cache.Fs.(*afero.BasePathFs)
c.Assert(ok, qt.Equals, true)
filename, _ = bfs.RealPath("key")
c.Assert(filename, qt.Equals, filepath.FromSlash("_gen/images/key"))
rf := func(s string) func() (io.ReadCloser, error) {
return func() (io.ReadCloser, error) {
return struct {
io.ReadSeeker
io.Closer
}{
strings.NewReader(s),
ioutil.NopCloser(nil),
}, nil
}
}
bf := func() ([]byte, error) {
return []byte("bcd"), nil
}
for _, ca := range []*Cache{caches.ImageCache(), caches.AssetsCache(), caches.GetJSONCache(), caches.GetCSVCache()} {
for i := 0; i < 2; i++ {
info, r, err := ca.GetOrCreate("a", rf("abc"))
c.Assert(err, qt.IsNil)
c.Assert(r, qt.Not(qt.IsNil))
c.Assert(info.Name, qt.Equals, "a")
b, _ := ioutil.ReadAll(r)
r.Close()
c.Assert(string(b), qt.Equals, "abc")
info, b, err = ca.GetOrCreateBytes("b", bf)
c.Assert(err, qt.IsNil)
c.Assert(r, qt.Not(qt.IsNil))
c.Assert(info.Name, qt.Equals, "b")
c.Assert(string(b), qt.Equals, "bcd")
_, b, err = ca.GetOrCreateBytes("a", bf)
c.Assert(err, qt.IsNil)
c.Assert(string(b), qt.Equals, "abc")
_, r, err = ca.GetOrCreate("a", rf("bcd"))
c.Assert(err, qt.IsNil)
b, _ = ioutil.ReadAll(r)
r.Close()
c.Assert(string(b), qt.Equals, "abc")
}
}
c.Assert(caches.Get("getJSON"), qt.Not(qt.IsNil))
info, w, err := caches.ImageCache().WriteCloser("mykey")
c.Assert(err, qt.IsNil)
c.Assert(info.Name, qt.Equals, "mykey")
io.WriteString(w, "Hugo is great!")
w.Close()
c.Assert(caches.ImageCache().getString("mykey"), qt.Equals, "Hugo is great!")
info, r, err := caches.ImageCache().Get("mykey")
c.Assert(err, qt.IsNil)
c.Assert(r, qt.Not(qt.IsNil))
c.Assert(info.Name, qt.Equals, "mykey")
b, _ := ioutil.ReadAll(r)
r.Close()
c.Assert(string(b), qt.Equals, "Hugo is great!")
info, b, err = caches.ImageCache().GetBytes("mykey")
c.Assert(err, qt.IsNil)
c.Assert(info.Name, qt.Equals, "mykey")
c.Assert(string(b), qt.Equals, "Hugo is great!")
}
}
func TestFileCacheConcurrent(t *testing.T) {
t.Parallel()
c := qt.New(t)
configStr := `
resourceDir = "myresources"
contentDir = "content"
dataDir = "data"
i18nDir = "i18n"
layoutDir = "layouts"
assetDir = "assets"
archeTypedir = "archetypes"
[caches]
[caches.getjson]
maxAge = "1s"
dir = "/cache/c"
`
p := newPathsSpec(t, afero.NewMemMapFs(), configStr)
caches, err := NewCaches(p)
c.Assert(err, qt.IsNil)
const cacheName = "getjson"
filenameData := func(i int) (string, string) {
data := fmt.Sprintf("data: %d", i)
filename := fmt.Sprintf("file%d", i)
return filename, data
}
var wg sync.WaitGroup
for i := 0; i < 50; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
for j := 0; j < 20; j++ {
ca := caches.Get(cacheName)
c.Assert(ca, qt.Not(qt.IsNil))
filename, data := filenameData(i)
_, r, err := ca.GetOrCreate(filename, func() (io.ReadCloser, error) {
return hugio.ToReadCloser(strings.NewReader(data)), nil
})
c.Assert(err, qt.IsNil)
b, _ := ioutil.ReadAll(r)
r.Close()
c.Assert(string(b), qt.Equals, data)
// Trigger some expiration.
time.Sleep(50 * time.Millisecond)
}
}(i)
}
wg.Wait()
}
func TestCleanID(t *testing.T) {
c := qt.New(t)
c.Assert(cleanID(filepath.FromSlash("/a/b//c.txt")), qt.Equals, filepath.FromSlash("a/b/c.txt"))
c.Assert(cleanID(filepath.FromSlash("a/b//c.txt")), qt.Equals, filepath.FromSlash("a/b/c.txt"))
}
func initConfig(fs afero.Fs, cfg config.Provider) error {
if _, err := langs.LoadLanguageSettings(cfg, nil); err != nil {
return err
}
modConfig, err := modules.DecodeConfig(cfg)
if err != nil {
return err
}
workingDir := cfg.GetString("workingDir")
themesDir := cfg.GetString("themesDir")
if !filepath.IsAbs(themesDir) {
themesDir = filepath.Join(workingDir, themesDir)
}
modulesClient := modules.NewClient(modules.ClientConfig{
Fs: fs,
WorkingDir: workingDir,
ThemesDir: themesDir,
ModuleConfig: modConfig,
IgnoreVendor: true,
})
moduleConfig, err := modulesClient.Collect()
if err != nil {
return err
}
if err := modules.ApplyProjectConfigDefaults(cfg, moduleConfig.ActiveModules[len(moduleConfig.ActiveModules)-1]); err != nil {
return err
}
cfg.Set("allModules", moduleConfig.ActiveModules)
return nil
}
func newPathsSpec(t *testing.T, fs afero.Fs, configStr string) *helpers.PathSpec {
c := qt.New(t)
cfg, err := config.FromConfigString(configStr, "toml")
c.Assert(err, qt.IsNil)
initConfig(fs, cfg)
p, err := helpers.NewPathSpec(hugofs.NewFrom(fs, cfg), cfg, nil)
c.Assert(err, qt.IsNil)
return p
}
+79
View File
@@ -0,0 +1,79 @@
// 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 namedmemcache provides a memory cache with a named lock. This is suitable
// for situations where creating the cached resource can be time consuming or otherwise
// resource hungry, or in situations where a "once only per key" is a requirement.
package namedmemcache
import (
"sync"
"github.com/BurntSushi/locker"
)
// Cache holds the cached values.
type Cache struct {
nlocker *locker.Locker
cache map[string]cacheEntry
mu sync.RWMutex
}
type cacheEntry struct {
value interface{}
err error
}
// New creates a new cache.
func New() *Cache {
return &Cache{
nlocker: locker.NewLocker(),
cache: make(map[string]cacheEntry),
}
}
// Clear clears the cache state.
func (c *Cache) Clear() {
c.mu.Lock()
defer c.mu.Unlock()
c.cache = make(map[string]cacheEntry)
c.nlocker = locker.NewLocker()
}
// GetOrCreate tries to get the value with the given cache key, if not found
// create will be called and cached.
// This method is thread safe. It also guarantees that the create func for a given
// key is invoced only once for this cache.
func (c *Cache) GetOrCreate(key string, create func() (interface{}, error)) (interface{}, error) {
c.mu.RLock()
entry, found := c.cache[key]
c.mu.RUnlock()
if found {
return entry.value, entry.err
}
c.nlocker.Lock(key)
defer c.nlocker.Unlock(key)
// Create it.
value, err := create()
c.mu.Lock()
c.cache[key] = cacheEntry{value: value, err: err}
c.mu.Unlock()
return value, err
}
+80
View File
@@ -0,0 +1,80 @@
// 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 namedmemcache
import (
"fmt"
"sync"
"testing"
qt "github.com/frankban/quicktest"
)
func TestNamedCache(t *testing.T) {
t.Parallel()
c := qt.New(t)
cache := New()
counter := 0
create := func() (interface{}, error) {
counter++
return counter, nil
}
for i := 0; i < 5; i++ {
v1, err := cache.GetOrCreate("a1", create)
c.Assert(err, qt.IsNil)
c.Assert(v1, qt.Equals, 1)
v2, err := cache.GetOrCreate("a2", create)
c.Assert(err, qt.IsNil)
c.Assert(v2, qt.Equals, 2)
}
cache.Clear()
v3, err := cache.GetOrCreate("a2", create)
c.Assert(err, qt.IsNil)
c.Assert(v3, qt.Equals, 3)
}
func TestNamedCacheConcurrent(t *testing.T) {
t.Parallel()
c := qt.New(t)
var wg sync.WaitGroup
cache := New()
create := func(i int) func() (interface{}, error) {
return func() (interface{}, error) {
return i, nil
}
}
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 100; j++ {
id := fmt.Sprintf("id%d", j)
v, err := cache.GetOrCreate(id, create(j))
c.Assert(err, qt.IsNil)
c.Assert(v, qt.Equals, j)
}
}()
}
wg.Wait()
}
+20 -20
View File
@@ -18,13 +18,13 @@ import (
"sync"
"testing"
"github.com/stretchr/testify/require"
qt "github.com/frankban/quicktest"
)
func TestNewPartitionedLazyCache(t *testing.T) {
t.Parallel()
assert := require.New(t)
c := qt.New(t)
p1 := Partition{
Key: "p1",
@@ -51,28 +51,28 @@ func TestNewPartitionedLazyCache(t *testing.T) {
cache := NewPartitionedLazyCache(p1, p2)
v, err := cache.Get("p1", "p1_1")
assert.NoError(err)
assert.Equal("p1v1", v)
c.Assert(err, qt.IsNil)
c.Assert(v, qt.Equals, "p1v1")
v, err = cache.Get("p1", "p2_1")
assert.NoError(err)
assert.Nil(v)
c.Assert(err, qt.IsNil)
c.Assert(v, qt.IsNil)
v, err = cache.Get("p1", "p1_nil")
assert.NoError(err)
assert.Nil(v)
c.Assert(err, qt.IsNil)
c.Assert(v, qt.IsNil)
v, err = cache.Get("p2", "p2_3")
assert.NoError(err)
assert.Equal("p2v3", v)
c.Assert(err, qt.IsNil)
c.Assert(v, qt.Equals, "p2v3")
v, err = cache.Get("doesnotexist", "p1_1")
assert.NoError(err)
assert.Nil(v)
c.Assert(err, qt.IsNil)
c.Assert(v, qt.IsNil)
v, err = cache.Get("p1", "doesnotexist")
assert.NoError(err)
assert.Nil(v)
c.Assert(err, qt.IsNil)
c.Assert(v, qt.IsNil)
errorP := Partition{
Key: "p3",
@@ -84,18 +84,18 @@ func TestNewPartitionedLazyCache(t *testing.T) {
cache = NewPartitionedLazyCache(errorP)
v, err = cache.Get("p1", "doesnotexist")
assert.NoError(err)
assert.Nil(v)
c.Assert(err, qt.IsNil)
c.Assert(v, qt.IsNil)
_, err = cache.Get("p3", "doesnotexist")
assert.Error(err)
c.Assert(err, qt.Not(qt.IsNil))
}
func TestConcurrentPartitionedLazyCache(t *testing.T) {
t.Parallel()
assert := require.New(t)
c := qt.New(t)
var wg sync.WaitGroup
@@ -129,8 +129,8 @@ func TestConcurrentPartitionedLazyCache(t *testing.T) {
defer wg.Done()
for j := 0; j < 10; j++ {
v, err := cache.Get("p1", "p1_1")
assert.NoError(err)
assert.Equal("p1v1", v)
c.Assert(err, qt.IsNil)
c.Assert(v, qt.Equals, "p1v1")
}
}()
}
+548
View File
@@ -0,0 +1,548 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
// Some functions in this file (see comments) is based on the Go source code,
// copyright The Go Authors and governed by a BSD-style license.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package codegen contains helpers for code generation.
package codegen
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"path"
"path/filepath"
"reflect"
"regexp"
"sort"
"strings"
"sync"
)
// Make room for insertions
const weightWidth = 1000
// NewInspector creates a new Inspector given a source root.
func NewInspector(root string) *Inspector {
return &Inspector{ProjectRootDir: root}
}
// Inspector provides methods to help code generation. It uses a combination
// of reflection and source code AST to do the heavy lifting.
type Inspector struct {
ProjectRootDir string
init sync.Once
// Determines method order. Go's reflect sorts lexicographically, so
// we must parse the source to preserve this order.
methodWeight map[string]map[string]int
}
// MethodsFromTypes create a method set from the include slice, excluding any
// method in exclude.
func (c *Inspector) MethodsFromTypes(include []reflect.Type, exclude []reflect.Type) Methods {
c.parseSource()
var methods Methods
var excludes = make(map[string]bool)
if len(exclude) > 0 {
for _, m := range c.MethodsFromTypes(exclude, nil) {
excludes[m.Name] = true
}
}
// There may be overlapping interfaces in types. Do a simple check for now.
seen := make(map[string]bool)
nameAndPackage := func(t reflect.Type) (string, string) {
var name, pkg string
isPointer := t.Kind() == reflect.Ptr
if isPointer {
t = t.Elem()
}
pkgPrefix := ""
if pkgPath := t.PkgPath(); pkgPath != "" {
pkgPath = strings.TrimSuffix(pkgPath, "/")
_, shortPath := path.Split(pkgPath)
pkgPrefix = shortPath + "."
pkg = pkgPath
}
name = t.Name()
if name == "" {
// interface{}
name = t.String()
}
if isPointer {
pkgPrefix = "*" + pkgPrefix
}
name = pkgPrefix + name
return name, pkg
}
for _, t := range include {
for i := 0; i < t.NumMethod(); i++ {
m := t.Method(i)
if excludes[m.Name] || seen[m.Name] {
continue
}
seen[m.Name] = true
if m.PkgPath != "" {
// Not exported
continue
}
numIn := m.Type.NumIn()
ownerName, _ := nameAndPackage(t)
method := Method{Owner: t, OwnerName: ownerName, Name: m.Name}
for i := 0; i < numIn; i++ {
in := m.Type.In(i)
name, pkg := nameAndPackage(in)
if pkg != "" {
method.Imports = append(method.Imports, pkg)
}
method.In = append(method.In, name)
}
numOut := m.Type.NumOut()
if numOut > 0 {
for i := 0; i < numOut; i++ {
out := m.Type.Out(i)
name, pkg := nameAndPackage(out)
if pkg != "" {
method.Imports = append(method.Imports, pkg)
}
method.Out = append(method.Out, name)
}
}
methods = append(methods, method)
}
}
sort.SliceStable(methods, func(i, j int) bool {
mi, mj := methods[i], methods[j]
wi := c.methodWeight[mi.OwnerName][mi.Name]
wj := c.methodWeight[mj.OwnerName][mj.Name]
if wi == wj {
return mi.Name < mj.Name
}
return wi < wj
})
return methods
}
func (c *Inspector) parseSource() {
c.init.Do(func() {
if !strings.Contains(c.ProjectRootDir, "hugo") {
panic("dir must be set to the Hugo root")
}
c.methodWeight = make(map[string]map[string]int)
dirExcludes := regexp.MustCompile("docs|examples")
fileExcludes := regexp.MustCompile("autogen")
var filenames []string
filepath.Walk(c.ProjectRootDir, func(path string, info os.FileInfo, err error) error {
if info.IsDir() {
if dirExcludes.MatchString(info.Name()) {
return filepath.SkipDir
}
}
if !strings.HasSuffix(path, ".go") || fileExcludes.MatchString(path) {
return nil
}
filenames = append(filenames, path)
return nil
})
for _, filename := range filenames {
pkg := c.packageFromPath(filename)
fset := token.NewFileSet()
node, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)
if err != nil {
panic(err)
}
ast.Inspect(node, func(n ast.Node) bool {
switch t := n.(type) {
case *ast.TypeSpec:
if t.Name.IsExported() {
switch it := t.Type.(type) {
case *ast.InterfaceType:
iface := pkg + "." + t.Name.Name
methodNames := collectMethodsRecursive(pkg, it.Methods.List)
weights := make(map[string]int)
weight := weightWidth
for _, name := range methodNames {
weights[name] = weight
weight += weightWidth
}
c.methodWeight[iface] = weights
}
}
}
return true
})
}
// Complement
for _, v1 := range c.methodWeight {
for k2, w := range v1 {
if v, found := c.methodWeight[k2]; found {
for k3, v3 := range v {
v1[k3] = (v3 / weightWidth) + w
}
}
}
}
})
}
func (c *Inspector) packageFromPath(p string) string {
p = filepath.ToSlash(p)
base := path.Base(p)
if !strings.Contains(base, ".") {
return base
}
return path.Base(strings.TrimSuffix(p, base))
}
// Method holds enough information about it to recreate it.
type Method struct {
// The interface we extracted this method from.
Owner reflect.Type
// String version of the above, on the form PACKAGE.NAME, e.g.
// page.Page
OwnerName string
// Method name.
Name string
// Imports needed to satisfy the method signature.
Imports []string
// Argument types, including any package prefix, e.g. string, int, interface{},
// net.Url
In []string
// Return types.
Out []string
}
// Declaration creates a method declaration (without any body) for the given receiver.
func (m Method) Declaration(receiver string) string {
return fmt.Sprintf("func (%s %s) %s%s %s", receiverShort(receiver), receiver, m.Name, m.inStr(), m.outStr())
}
// DeclarationNamed creates a method declaration (without any body) for the given receiver
// with named return values.
func (m Method) DeclarationNamed(receiver string) string {
return fmt.Sprintf("func (%s %s) %s%s %s", receiverShort(receiver), receiver, m.Name, m.inStr(), m.outStrNamed())
}
// Delegate creates a delegate call string.
func (m Method) Delegate(receiver, delegate string) string {
ret := ""
if len(m.Out) > 0 {
ret = "return "
}
return fmt.Sprintf("%s%s.%s.%s%s", ret, receiverShort(receiver), delegate, m.Name, m.inOutStr())
}
func (m Method) String() string {
return m.Name + m.inStr() + " " + m.outStr() + "\n"
}
func (m Method) inOutStr() string {
if len(m.In) == 0 {
return "()"
}
args := make([]string, len(m.In))
for i := 0; i < len(args); i++ {
args[i] = fmt.Sprintf("arg%d", i)
}
return "(" + strings.Join(args, ", ") + ")"
}
func (m Method) inStr() string {
if len(m.In) == 0 {
return "()"
}
args := make([]string, len(m.In))
for i := 0; i < len(args); i++ {
args[i] = fmt.Sprintf("arg%d %s", i, m.In[i])
}
return "(" + strings.Join(args, ", ") + ")"
}
func (m Method) outStr() string {
if len(m.Out) == 0 {
return ""
}
if len(m.Out) == 1 {
return m.Out[0]
}
return "(" + strings.Join(m.Out, ", ") + ")"
}
func (m Method) outStrNamed() string {
if len(m.Out) == 0 {
return ""
}
outs := make([]string, len(m.Out))
for i := 0; i < len(outs); i++ {
outs[i] = fmt.Sprintf("o%d %s", i, m.Out[i])
}
return "(" + strings.Join(outs, ", ") + ")"
}
// Methods represents a list of methods for one or more interfaces.
// The order matches the defined order in their source file(s).
type Methods []Method
// Imports returns a sorted list of package imports needed to satisfy the
// signatures of all methods.
func (m Methods) Imports() []string {
var pkgImports []string
for _, method := range m {
pkgImports = append(pkgImports, method.Imports...)
}
if len(pkgImports) > 0 {
pkgImports = uniqueNonEmptyStrings(pkgImports)
sort.Strings(pkgImports)
}
return pkgImports
}
// ToMarshalJSON creates a MarshalJSON method for these methods. Any method name
// matchin any of the regexps in excludes will be ignored.
func (m Methods) ToMarshalJSON(receiver, pkgPath string, excludes ...string) (string, []string) {
var sb strings.Builder
r := receiverShort(receiver)
what := firstToUpper(trimAsterisk(receiver))
pgkName := path.Base(pkgPath)
fmt.Fprintf(&sb, "func Marshal%sToJSON(%s %s) ([]byte, error) {\n", what, r, receiver)
var methods Methods
var excludeRes = make([]*regexp.Regexp, len(excludes))
for i, exclude := range excludes {
excludeRes[i] = regexp.MustCompile(exclude)
}
for _, method := range m {
// Exclude methods with arguments and incompatible return values
if len(method.In) > 0 || len(method.Out) == 0 || len(method.Out) > 2 {
continue
}
if len(method.Out) == 2 {
if method.Out[1] != "error" {
continue
}
}
for _, re := range excludeRes {
if re.MatchString(method.Name) {
continue
}
}
methods = append(methods, method)
}
for _, method := range methods {
varn := varName(method.Name)
if len(method.Out) == 1 {
fmt.Fprintf(&sb, "\t%s := %s.%s()\n", varn, r, method.Name)
} else {
fmt.Fprintf(&sb, "\t%s, err := %s.%s()\n", varn, r, method.Name)
fmt.Fprint(&sb, "\tif err != nil {\n\t\treturn nil, err\n\t}\n")
}
}
fmt.Fprint(&sb, "\n\ts := struct {\n")
for _, method := range methods {
fmt.Fprintf(&sb, "\t\t%s %s\n", method.Name, typeName(method.Out[0], pgkName))
}
fmt.Fprint(&sb, "\n\t}{\n")
for _, method := range methods {
varn := varName(method.Name)
fmt.Fprintf(&sb, "\t\t%s: %s,\n", method.Name, varn)
}
fmt.Fprint(&sb, "\n\t}\n\n")
fmt.Fprint(&sb, "\treturn json.Marshal(&s)\n}")
pkgImports := append(methods.Imports(), "encoding/json")
if pkgPath != "" {
// Exclude self
for i, pkgImp := range pkgImports {
if pkgImp == pkgPath {
pkgImports = append(pkgImports[:i], pkgImports[i+1:]...)
}
}
}
return sb.String(), pkgImports
}
func collectMethodsRecursive(pkg string, f []*ast.Field) []string {
var methodNames []string
for _, m := range f {
if m.Names != nil {
methodNames = append(methodNames, m.Names[0].Name)
continue
}
if ident, ok := m.Type.(*ast.Ident); ok && ident.Obj != nil {
// Embedded interface
methodNames = append(
methodNames,
collectMethodsRecursive(
pkg,
ident.Obj.Decl.(*ast.TypeSpec).Type.(*ast.InterfaceType).Methods.List)...)
} else {
// Embedded, but in a different file/package. Return the
// package.Name and deal with that later.
name := packageName(m.Type)
if !strings.Contains(name, ".") {
// Assume current package
name = pkg + "." + name
}
methodNames = append(methodNames, name)
}
}
return methodNames
}
func firstToLower(name string) string {
return strings.ToLower(name[:1]) + name[1:]
}
func firstToUpper(name string) string {
return strings.ToUpper(name[:1]) + name[1:]
}
func packageName(e ast.Expr) string {
switch tp := e.(type) {
case *ast.Ident:
return tp.Name
case *ast.SelectorExpr:
return fmt.Sprintf("%s.%s", packageName(tp.X), packageName(tp.Sel))
}
return ""
}
func receiverShort(receiver string) string {
return strings.ToLower(trimAsterisk(receiver))[:1]
}
func trimAsterisk(name string) string {
return strings.TrimPrefix(name, "*")
}
func typeName(name, pkg string) string {
return strings.TrimPrefix(name, pkg+".")
}
func uniqueNonEmptyStrings(s []string) []string {
var unique []string
set := map[string]interface{}{}
for _, val := range s {
if val == "" {
continue
}
if _, ok := set[val]; !ok {
unique = append(unique, val)
set[val] = val
}
}
return unique
}
func varName(name string) string {
name = firstToLower(name)
// Adjust some reserved keywords, see https://golang.org/ref/spec#Keywords
switch name {
case "type":
name = "typ"
case "package":
name = "pkg"
// Not reserved, but syntax highlighters has it as a keyword.
case "len":
name = "length"
}
return name
}
@@ -1,4 +1,4 @@
// Copyright 2017-present The Hugo Authors. All rights reserved.
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -11,13 +11,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package hugolib
package codegen
import (
"github.com/gohugoio/hugo/resource"
)
var (
_ resource.Resource = (*Page)(nil)
_ resource.Resource = (*PageOutput)(nil)
)
type IEmbed interface {
MethodEmbed3(s string) string
MethodEmbed1() string
MethodEmbed2()
}
+100
View File
@@ -0,0 +1,100 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package codegen
import (
"fmt"
"net"
"os"
"reflect"
"testing"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/common/herrors"
)
func TestMethods(t *testing.T) {
var (
zeroIE = reflect.TypeOf((*IEmbed)(nil)).Elem()
zeroIEOnly = reflect.TypeOf((*IEOnly)(nil)).Elem()
zeroI = reflect.TypeOf((*I)(nil)).Elem()
)
dir, _ := os.Getwd()
insp := NewInspector(dir)
t.Run("MethodsFromTypes", func(t *testing.T) {
c := qt.New(t)
methods := insp.MethodsFromTypes([]reflect.Type{zeroI}, nil)
methodsStr := fmt.Sprint(methods)
c.Assert(methodsStr, qt.Contains, "Method1(arg0 herrors.ErrorContext)")
c.Assert(methodsStr, qt.Contains, "Method7() interface {}")
c.Assert(methodsStr, qt.Contains, "Method0() string\n Method4() string")
c.Assert(methodsStr, qt.Contains, "MethodEmbed3(arg0 string) string\n MethodEmbed1() string")
c.Assert(methods.Imports(), qt.Contains, "github.com/gohugoio/hugo/common/herrors")
})
t.Run("EmbedOnly", func(t *testing.T) {
c := qt.New(t)
methods := insp.MethodsFromTypes([]reflect.Type{zeroIEOnly}, nil)
methodsStr := fmt.Sprint(methods)
c.Assert(methodsStr, qt.Contains, "MethodEmbed3(arg0 string) string")
})
t.Run("ToMarshalJSON", func(t *testing.T) {
c := qt.New(t)
m, pkg := insp.MethodsFromTypes(
[]reflect.Type{zeroI},
[]reflect.Type{zeroIE}).ToMarshalJSON("*page", "page")
c.Assert(m, qt.Contains, "method6 := p.Method6()")
c.Assert(m, qt.Contains, "Method0: method0,")
c.Assert(m, qt.Contains, "return json.Marshal(&s)")
c.Assert(pkg, qt.Contains, "github.com/gohugoio/hugo/common/herrors")
c.Assert(pkg, qt.Contains, "encoding/json")
fmt.Println(pkg)
})
}
type I interface {
IEmbed
Method0() string
Method4() string
Method1(myerr herrors.ErrorContext)
Method3(myint int, mystring string)
Method5() (string, error)
Method6() *net.IP
Method7() interface{}
Method8() herrors.ErrorContext
method2()
method9() os.FileInfo
}
type IEOnly interface {
IEmbed
}
-115
View File
@@ -1,115 +0,0 @@
// Copyright 2015 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
import (
"os"
"runtime"
"runtime/pprof"
"time"
"github.com/spf13/cobra"
jww "github.com/spf13/jwalterweatherman"
)
type benchmarkCmd struct {
benchmarkTimes int
cpuProfileFile string
memProfileFile string
*baseBuilderCmd
}
func (b *commandsBuilder) newBenchmarkCmd() *benchmarkCmd {
cmd := &cobra.Command{
Use: "benchmark",
Short: "Benchmark Hugo by building a site a number of times.",
Long: `Hugo can build a site many times over and analyze the running process
creating a benchmark.`,
}
c := &benchmarkCmd{baseBuilderCmd: b.newBuilderCmd(cmd)}
cmd.Flags().StringVar(&c.cpuProfileFile, "cpuprofile", "", "path/filename for the CPU profile file")
cmd.Flags().StringVar(&c.memProfileFile, "memprofile", "", "path/filename for the memory profile file")
cmd.Flags().IntVarP(&c.benchmarkTimes, "count", "n", 13, "number of times to build the site")
cmd.Flags().Bool("renderToMemory", false, "render to memory (only useful for benchmark testing)")
cmd.RunE = c.benchmark
return c
}
func (c *benchmarkCmd) benchmark(cmd *cobra.Command, args []string) error {
cfgInit := func(c *commandeer) error {
return nil
}
comm, err := initializeConfig(true, false, &c.hugoBuilderCommon, c, cfgInit)
if err != nil {
return err
}
var memProf *os.File
if c.memProfileFile != "" {
memProf, err = os.Create(c.memProfileFile)
if err != nil {
return err
}
}
var cpuProf *os.File
if c.cpuProfileFile != "" {
cpuProf, err = os.Create(c.cpuProfileFile)
if err != nil {
return err
}
}
var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)
memAllocated := memStats.TotalAlloc
mallocs := memStats.Mallocs
if cpuProf != nil {
pprof.StartCPUProfile(cpuProf)
}
t := time.Now()
for i := 0; i < c.benchmarkTimes; i++ {
if err = comm.resetAndBuildSites(); err != nil {
return err
}
}
totalTime := time.Since(t)
if memProf != nil {
pprof.WriteHeapProfile(memProf)
memProf.Close()
}
if cpuProf != nil {
pprof.StopCPUProfile()
cpuProf.Close()
}
runtime.ReadMemStats(&memStats)
totalMemAllocated := memStats.TotalAlloc - memAllocated
totalMallocs := memStats.Mallocs - mallocs
jww.FEEDBACK.Println()
jww.FEEDBACK.Printf("Average time per operation: %vms\n", int(1000*totalTime.Seconds()/float64(c.benchmarkTimes)))
jww.FEEDBACK.Printf("Average memory allocated per operation: %vkB\n", totalMemAllocated/uint64(c.benchmarkTimes)/1024)
jww.FEEDBACK.Printf("Average allocations per operation: %v\n", totalMallocs/uint64(c.benchmarkTimes))
return nil
}
+121 -46
View File
@@ -1,4 +1,4 @@
// Copyright 2018 The Hugo Authors. All rights reserved.
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,20 +14,33 @@
package commands
import (
"bytes"
"errors"
"sync"
"golang.org/x/sync/semaphore"
"github.com/gohugoio/hugo/modules"
"io/ioutil"
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/common/hugo"
jww "github.com/spf13/jwalterweatherman"
"os"
"path/filepath"
"regexp"
"strings"
"sync"
"time"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/config"
"github.com/spf13/cobra"
"github.com/spf13/afero"
"github.com/gohugoio/hugo/hugolib"
"github.com/spf13/afero"
"github.com/bep/debounce"
"github.com/gohugoio/hugo/common/types"
@@ -39,13 +52,16 @@ import (
type commandeerHugoState struct {
*deps.DepsCfg
hugo *hugolib.HugoSites
fsCreate sync.Once
hugoSites *hugolib.HugoSites
fsCreate sync.Once
created chan struct{}
}
type commandeer struct {
*commandeerHugoState
logger *loggers.Logger
// 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
@@ -69,9 +85,58 @@ type commandeer struct {
serverPorts []int
languagesConfigured bool
languages langs.Languages
doLiveReload bool
fastRenderMode bool
showErrorInBrowser bool
configured bool
paused bool
fullRebuildSem *semaphore.Weighted
// Any error from the last build.
buildErr error
}
func newCommandeerHugoState() *commandeerHugoState {
return &commandeerHugoState{
created: make(chan struct{}),
}
}
func (c *commandeerHugoState) hugo() *hugolib.HugoSites {
<-c.created
return c.hugoSites
}
func (c *commandeer) errCount() int {
return int(c.logger.ErrorCounter.Count())
}
func (c *commandeer) getErrorWithContext() interface{} {
errCount := c.errCount()
if errCount == 0 {
return nil
}
m := make(map[string]interface{})
m["Error"] = errors.New(removeErrorPrefixFromLog(c.logger.Errors()))
m["Version"] = hugo.BuildVersionString()
fe := herrors.UnwrapErrorWithFileContext(c.buildErr)
if fe != nil {
m["File"] = fe
}
if c.h.verbose {
var b bytes.Buffer
herrors.FprintStackTrace(&b, c.buildErr)
m["StackTrace"] = b.String()
}
return m
}
func (c *commandeer) Set(key string, value interface{}) {
@@ -95,16 +160,19 @@ func newCommandeer(mustHaveConfigFile, running bool, h *hugoBuilderCommon, f fla
// The time value used is tested with mass content replacements in a fairly big Hugo site.
// It is better to wait for some seconds in those cases rather than get flooded
// with rebuilds.
rebuildDebouncer, _, _ = debounce.New(4 * time.Second)
rebuildDebouncer = debounce.New(4 * time.Second)
}
c := &commandeer{
h: h,
ftch: f,
commandeerHugoState: &commandeerHugoState{},
commandeerHugoState: newCommandeerHugoState(),
doWithCommandeer: doWithCommandeer,
visitedURLs: types.NewEvictingStringQueue(10),
debounce: rebuildDebouncer,
fullRebuildSem: semaphore.NewWeighted(1),
// This will be replaced later, but we need something to log to before the configuration is read.
logger: loggers.NewLogger(jww.LevelError, jww.LevelError, os.Stdout, ioutil.Discard, running),
}
return c, c.loadConfig(mustHaveConfigFile, running)
@@ -178,6 +246,11 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
c.DepsCfg = &deps.DepsCfg{}
}
if c.logger != nil {
// Truncate the error log if this is a reload.
c.logger.Reset()
}
cfg := c.DepsCfg
c.configured = false
cfg.Running = running
@@ -194,6 +267,8 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
sourceFs = c.DepsCfg.Fs.Source
}
environment := c.h.getEnvironment(running)
doWithConfig := func(cfg config.Provider) error {
if c.ftch != nil {
@@ -201,7 +276,7 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
}
cfg.Set("workingDir", dir)
cfg.Set("environment", environment)
return nil
}
@@ -214,8 +289,19 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
return err
}
configPath := c.h.source
if configPath == "" {
configPath = dir
}
config, configFiles, err := hugolib.LoadConfig(
hugolib.ConfigSourceDescriptor{Fs: sourceFs, Path: c.h.source, WorkingDir: dir, Filename: c.h.cfgFile},
hugolib.ConfigSourceDescriptor{
Fs: sourceFs,
Path: configPath,
WorkingDir: dir,
Filename: c.h.cfgFile,
AbsConfigDir: c.h.getConfigDir(dir),
Environ: os.Environ(),
Environment: environment},
doWithCommandeer,
doWithConfig)
@@ -223,7 +309,7 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
if mustHaveConfigFile {
return err
}
if err != hugolib.ErrNoConfigFile {
if err != hugolib.ErrNoConfigFile && !modules.IsNotExist(err) {
return err
}
@@ -236,6 +322,11 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
c.languages = l
}
// Set some commonly used flags
c.doLiveReload = running && !c.Cfg.GetBool("disableLiveReload")
c.fastRenderMode = c.doLiveReload && !c.Cfg.GetBool("disableFastRender")
c.showErrorInBrowser = c.doLiveReload && !c.Cfg.GetBool("disableBrowserError")
// This is potentially double work, but we need to do this one more time now
// that all the languages have been configured.
if c.doWithCommandeer != nil {
@@ -244,12 +335,13 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
}
}
logger, err := c.createLogger(config)
logger, err := c.createLogger(config, running)
if err != nil {
return err
}
cfg.Logger = logger
c.logger = logger
createMemFs := config.GetBool("renderToMemory")
@@ -269,10 +361,7 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
fs.Destination = new(afero.MemMapFs)
}
doLiveReload := !c.h.buildWatch && !config.GetBool("disableLiveReload")
fastRenderMode := doLiveReload && !config.GetBool("disableFastRender")
if fastRenderMode {
if c.fastRenderMode {
// For now, fast render mode only. It should, however, be fast enough
// for the full variant, too.
changeDetector := &fileChangeDetector{
@@ -281,20 +370,30 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
// to make that decision.
irrelevantRe: regexp.MustCompile(`\.map$`),
}
changeDetector.PrepareNew()
fs.Destination = hugofs.NewHashingFs(fs.Destination, changeDetector)
c.changeDetector = changeDetector
}
if c.Cfg.GetBool("logPathWarnings") {
fs.Destination = hugofs.NewCreateCountingFs(fs.Destination)
}
// To debug hard-to-find path issues.
//fs.Destination = hugofs.NewStacktracerFs(fs.Destination, `fr/fr`)
err = c.initFs(fs)
if err != nil {
close(c.created)
return
}
var h *hugolib.HugoSites
h, err = hugolib.NewHugoSites(*c.DepsCfg)
c.hugo = h
c.hugoSites = h
close(c.created)
})
@@ -302,38 +401,14 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
return err
}
cacheDir := config.GetString("cacheDir")
if cacheDir != "" {
if helpers.FilePathSeparator != cacheDir[len(cacheDir)-1:] {
cacheDir = cacheDir + helpers.FilePathSeparator
}
isDir, err := helpers.DirExists(cacheDir, sourceFs)
checkErr(cfg.Logger, err)
if !isDir {
mkdir(cacheDir)
}
config.Set("cacheDir", cacheDir)
} else {
config.Set("cacheDir", helpers.GetTempDir("hugo_cache", sourceFs))
cacheDir, err := helpers.GetCacheDir(sourceFs, config)
if err != nil {
return err
}
config.Set("cacheDir", cacheDir)
cfg.Logger.INFO.Println("Using config file:", config.ConfigFileUsed())
themeDir := c.hugo.PathSpec.GetFirstThemeDir()
if themeDir != "" {
if _, err := sourceFs.Stat(themeDir); os.IsNotExist(err) {
return newSystemError("Unable to find theme Directory:", themeDir)
}
}
dir, themeVersionMismatch, minVersion := c.isThemeVsHugoVersionMismatch(sourceFs)
if themeVersionMismatch {
name := filepath.Base(dir)
cfg.Logger.ERROR.Printf("%s theme does not support Hugo version %s. Minimum version required is %s\n",
strings.ToUpper(name), helpers.CurrentHugoVersion.ReleaseVersion(), minVersion)
}
return nil
}
+73 -40
View File
@@ -1,4 +1,4 @@
// Copyright 2017 The Hugo Authors. All rights reserved.
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -16,12 +16,13 @@ package commands
import (
"os"
"github.com/gohugoio/hugo/hugolib/paths"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
"github.com/spf13/cobra"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/nitro"
)
type commandsBuilder struct {
@@ -46,13 +47,14 @@ func (b *commandsBuilder) addAll() *commandsBuilder {
newEnvCmd(),
newConfigCmd(),
newCheckCmd(),
b.newBenchmarkCmd(),
newDeployCmd(),
newConvertCmd(),
newNewCmd(),
b.newNewCmd(),
newListCmd(),
newImportCmd(),
newGenCmd(),
createReleaser(),
b.newModCmd(),
)
return b
@@ -82,14 +84,14 @@ var _ commandsBuilderGetter = (*baseBuilderCmd)(nil)
// Used in tests.
type commandsBuilderGetter interface {
getCmmandsBuilder() *commandsBuilder
getCommandsBuilder() *commandsBuilder
}
type baseBuilderCmd struct {
*baseCmd
*commandsBuilder
}
func (b *baseBuilderCmd) getCmmandsBuilder() *commandsBuilder {
func (b *baseBuilderCmd) getCommandsBuilder() *commandsBuilder {
return b.commandsBuilder
}
@@ -162,11 +164,11 @@ Complete documentation is available at http://gohugo.io/.`,
})
cc.cmd.PersistentFlags().StringVar(&cc.cfgFile, "config", "", "config file (default is path/config.yaml|json|toml)")
cc.cmd.PersistentFlags().StringVar(&cc.cfgDir, "configDir", "config", "config dir")
cc.cmd.PersistentFlags().BoolVar(&cc.quiet, "quiet", false, "build in quiet mode")
// Set bash-completion
validConfigFilenames := []string{"json", "js", "yaml", "yml", "toml", "tml"}
_ = cc.cmd.PersistentFlags().SetAnnotation("config", cobra.BashCompFilenameExt, validConfigFilenames)
_ = cc.cmd.PersistentFlags().SetAnnotation("config", cobra.BashCompFilenameExt, config.ValidConfigFileExtensions)
cc.cmd.PersistentFlags().BoolVarP(&cc.verbose, "verbose", "v", false, "verbose output")
cc.cmd.PersistentFlags().BoolVarP(&cc.debug, "debug", "", false, "debug output")
@@ -177,7 +179,6 @@ Complete documentation is available at http://gohugo.io/.`,
cc.cmd.Flags().BoolVarP(&cc.buildWatch, "watch", "w", false, "watch filesystem for changes and recreate as needed")
cc.cmd.Flags().Bool("renderToMemory", false, "render to memory (only useful for benchmark testing)")
cc.cmd.Flags().Bool("minify", false, "minify any supported output format (HTML, XML etc.)")
// Set bash-completion
_ = cc.cmd.PersistentFlags().SetAnnotation("logFile", cobra.BashCompFilenameExt, []string{})
@@ -189,13 +190,20 @@ Complete documentation is available at http://gohugo.io/.`,
}
type hugoBuilderCommon struct {
source string
baseURL string
source string
baseURL string
environment string
buildWatch bool
gc bool
// Profile flags (for debugging of performance problems)
cpuprofile string
memprofile string
mutexprofile string
traceprofile string
// TODO(bep) var vs string
logging bool
verbose bool
@@ -204,36 +212,83 @@ type hugoBuilderCommon struct {
quiet bool
cfgFile string
cfgDir string
logFile string
}
func (cc *hugoBuilderCommon) getConfigDir(baseDir string) string {
if cc.cfgDir != "" {
return paths.AbsPathify(baseDir, cc.cfgDir)
}
if v, found := os.LookupEnv("HUGO_CONFIGDIR"); found {
return paths.AbsPathify(baseDir, v)
}
return paths.AbsPathify(baseDir, "config")
}
func (cc *hugoBuilderCommon) getEnvironment(isServer bool) string {
if cc.environment != "" {
return cc.environment
}
if v, found := os.LookupEnv("HUGO_ENVIRONMENT"); found {
return v
}
if isServer {
return hugo.EnvironmentDevelopment
}
return hugo.EnvironmentProduction
}
func (cc *hugoBuilderCommon) handleCommonBuilderFlags(cmd *cobra.Command) {
cmd.PersistentFlags().StringVarP(&cc.source, "source", "s", "", "filesystem path to read files relative from")
cmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{})
cmd.PersistentFlags().StringVarP(&cc.environment, "environment", "e", "", "build environment")
cmd.PersistentFlags().StringP("themesDir", "", "", "filesystem path to themes directory")
cmd.PersistentFlags().BoolP("ignoreVendor", "", false, "ignores any _vendor directory")
}
func (cc *hugoBuilderCommon) handleFlags(cmd *cobra.Command) {
cc.handleCommonBuilderFlags(cmd)
cmd.Flags().Bool("cleanDestinationDir", false, "remove files from destination not found in static directories")
cmd.Flags().BoolP("buildDrafts", "D", false, "include content marked as draft")
cmd.Flags().BoolP("buildFuture", "F", false, "include content with publishdate in the future")
cmd.Flags().BoolP("buildExpired", "E", false, "include expired content")
cmd.Flags().StringVarP(&cc.source, "source", "s", "", "filesystem path to read files relative from")
cmd.Flags().StringP("contentDir", "c", "", "filesystem path to content directory")
cmd.Flags().StringP("layoutDir", "l", "", "filesystem path to layout directory")
cmd.Flags().StringP("cacheDir", "", "", "filesystem path to cache directory. Defaults: $TMPDIR/hugo_cache/")
cmd.Flags().BoolP("ignoreCache", "", false, "ignores the cache directory")
cmd.Flags().StringP("destination", "d", "", "filesystem path to write files to")
cmd.Flags().StringP("theme", "t", "", "theme to use (located in /themes/THEMENAME/)")
cmd.Flags().StringP("themesDir", "", "", "filesystem path to themes directory")
cmd.Flags().StringSliceP("theme", "t", []string{}, "themes to use (located in /themes/THEMENAME/)")
cmd.Flags().StringVarP(&cc.baseURL, "baseURL", "b", "", "hostname (and path) to the root, e.g. http://spf13.com/")
cmd.Flags().Bool("enableGitInfo", false, "add Git revision, date and author info to the pages")
cmd.Flags().BoolVar(&cc.gc, "gc", false, "enable to run some cleanup tasks (remove unused cache files) after the build")
cmd.Flags().BoolVar(&nitro.AnalysisOn, "stepAnalysis", false, "display memory and timing of different steps of the program")
cmd.Flags().Bool("templateMetrics", false, "display metrics about template executions")
cmd.Flags().Bool("templateMetricsHints", false, "calculate some improvement hints when combined with --templateMetrics")
cmd.Flags().BoolP("forceSyncStatic", "", false, "copy all files when static is changed.")
cmd.Flags().BoolP("noTimes", "", false, "don't sync modification time of files")
cmd.Flags().BoolP("noChmod", "", false, "don't sync permission mode of files")
cmd.Flags().BoolP("i18n-warnings", "", false, "print missing translations")
cmd.Flags().BoolP("path-warnings", "", false, "print warnings on duplicate target paths etc.")
cmd.Flags().StringVarP(&cc.cpuprofile, "profile-cpu", "", "", "write cpu profile to `file`")
cmd.Flags().StringVarP(&cc.memprofile, "profile-mem", "", "", "write memory profile to `file`")
cmd.Flags().StringVarP(&cc.mutexprofile, "profile-mutex", "", "", "write Mutex profile to `file`")
cmd.Flags().StringVarP(&cc.traceprofile, "trace", "", "", "write trace to `file` (not useful in general)")
// Hide these for now.
cmd.Flags().MarkHidden("profile-cpu")
cmd.Flags().MarkHidden("profile-mem")
cmd.Flags().MarkHidden("profile-mutex")
cmd.Flags().StringSlice("disableKinds", []string{}, "disable different kind of pages (home, RSS etc.)")
cmd.Flags().Bool("minify", false, "minify any supported output format (HTML, XML etc.)")
// Set bash-completion.
// Each flag must first be defined before using the SetAnnotation() call.
_ = cmd.Flags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{})
@@ -242,7 +297,7 @@ func (cc *hugoBuilderCommon) handleFlags(cmd *cobra.Command) {
_ = cmd.Flags().SetAnnotation("theme", cobra.BashCompSubdirsInDir, []string{"themes"})
}
func checkErr(logger *jww.Notepad, err error, s ...string) {
func checkErr(logger *loggers.Logger, err error, s ...string) {
if err == nil {
return
}
@@ -255,25 +310,3 @@ func checkErr(logger *jww.Notepad, err error, s ...string) {
}
logger.ERROR.Println(err)
}
func stopOnErr(logger *jww.Notepad, err error, s ...string) {
if err == nil {
return
}
defer os.Exit(-1)
if len(s) == 0 {
newMessage := err.Error()
// Printing an empty string results in a error with
// no message, no bueno.
if newMessage != "" {
logger.CRITICAL.Println(newMessage)
}
}
for _, message := range s {
if message != "" {
logger.CRITICAL.Println(message)
}
}
}
+78 -44
View File
@@ -1,4 +1,4 @@
// Copyright 2018 The Hugo Authors. All rights reserved.
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -20,32 +20,34 @@ import (
"path/filepath"
"testing"
"github.com/gohugoio/hugo/common/types"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
qt "github.com/frankban/quicktest"
)
func TestExecute(t *testing.T) {
assert := require.New(t)
c := qt.New(t)
dir, err := createSimpleTestSite(t)
assert.NoError(err)
dir, err := createSimpleTestSite(t, testSiteConfig{})
c.Assert(err, qt.IsNil)
defer func() {
os.RemoveAll(dir)
}()
resp := Execute([]string{"-s=" + dir})
assert.NoError(resp.Err)
c.Assert(resp.Err, qt.IsNil)
result := resp.Result
assert.True(len(result.Sites) == 1)
assert.True(len(result.Sites[0].RegularPages) == 1)
c.Assert(len(result.Sites) == 1, qt.Equals, true)
c.Assert(len(result.Sites[0].RegularPages()) == 1, qt.Equals, true)
}
func TestCommandsPersistentFlags(t *testing.T) {
assert := require.New(t)
c := qt.New(t)
noOpRunE := func(cmd *cobra.Command, args []string) error {
return nil
@@ -56,8 +58,11 @@ func TestCommandsPersistentFlags(t *testing.T) {
check func(command []cmder)
}{{[]string{"server",
"--config=myconfig.toml",
"--configDir=myconfigdir",
"--contentDir=mycontent",
"--disableKinds=page,home",
"--environment=testing",
"--configDir=myconfigdir",
"--layoutDir=mylayouts",
"--theme=mytheme",
"--gc",
@@ -72,14 +77,16 @@ func TestCommandsPersistentFlags(t *testing.T) {
"--port=1366",
"--renderToDisk",
"--source=mysource",
"--path-warnings",
}, func(commands []cmder) {
var sc *serverCmd
for _, command := range commands {
if b, ok := command.(commandsBuilderGetter); ok {
v := b.getCmmandsBuilder().hugoBuilderCommon
assert.Equal("myconfig.toml", v.cfgFile)
assert.Equal("mysource", v.source)
assert.Equal("https://example.com/b/", v.baseURL)
v := b.getCommandsBuilder().hugoBuilderCommon
c.Assert(v.cfgFile, qt.Equals, "myconfig.toml")
c.Assert(v.cfgDir, qt.Equals, "myconfigdir")
c.Assert(v.source, qt.Equals, "mysource")
c.Assert(v.baseURL, qt.Equals, "https://example.com/b/")
}
if srvCmd, ok := command.(*serverCmd); ok {
@@ -87,28 +94,32 @@ func TestCommandsPersistentFlags(t *testing.T) {
}
}
assert.NotNil(sc)
assert.True(sc.navigateToChanged)
assert.True(sc.disableLiveReload)
assert.True(sc.noHTTPCache)
assert.True(sc.renderToDisk)
assert.Equal(1366, sc.serverPort)
c.Assert(sc, qt.Not(qt.IsNil))
c.Assert(sc.navigateToChanged, qt.Equals, true)
c.Assert(sc.disableLiveReload, qt.Equals, true)
c.Assert(sc.noHTTPCache, qt.Equals, true)
c.Assert(sc.renderToDisk, qt.Equals, true)
c.Assert(sc.serverPort, qt.Equals, 1366)
c.Assert(sc.environment, qt.Equals, "testing")
cfg := viper.New()
sc.flagsToConfig(cfg)
assert.Equal("/tmp/mydestination", cfg.GetString("publishDir"))
assert.Equal("mycontent", cfg.GetString("contentDir"))
assert.Equal("mylayouts", cfg.GetString("layoutDir"))
assert.Equal("mytheme", cfg.GetString("theme"))
assert.Equal("mythemes", cfg.GetString("themesDir"))
assert.Equal("https://example.com/b/", cfg.GetString("baseURL"))
c.Assert(cfg.GetString("publishDir"), qt.Equals, "/tmp/mydestination")
c.Assert(cfg.GetString("contentDir"), qt.Equals, "mycontent")
c.Assert(cfg.GetString("layoutDir"), qt.Equals, "mylayouts")
c.Assert(cfg.GetStringSlice("theme"), qt.DeepEquals, []string{"mytheme"})
c.Assert(cfg.GetString("themesDir"), qt.Equals, "mythemes")
c.Assert(cfg.GetString("baseURL"), qt.Equals, "https://example.com/b/")
assert.Equal([]string{"page", "home"}, cfg.Get("disableKinds"))
c.Assert(cfg.Get("disableKinds"), qt.DeepEquals, []string{"page", "home"})
assert.True(cfg.GetBool("gc"))
c.Assert(cfg.GetBool("gc"), qt.Equals, true)
// The flag is named path-warnings
c.Assert(cfg.GetBool("logPathWarnings"), qt.Equals, true)
// The flag is named i18n-warnings
assert.True(cfg.GetBool("logI18nWarnings"))
c.Assert(cfg.GetBool("logI18nWarnings"), qt.Equals, true)
}}}
@@ -125,7 +136,7 @@ func TestCommandsPersistentFlags(t *testing.T) {
}
rootCmd := root.getCommand()
rootCmd.SetArgs(test.args)
assert.NoError(rootCmd.Execute())
c.Assert(rootCmd.Execute(), qt.IsNil)
test.check(b.commands)
}
@@ -133,13 +144,13 @@ func TestCommandsPersistentFlags(t *testing.T) {
func TestCommandsExecute(t *testing.T) {
assert := require.New(t)
c := qt.New(t)
dir, err := createSimpleTestSite(t)
assert.NoError(err)
dir, err := createSimpleTestSite(t, testSiteConfig{})
c.Assert(err, qt.IsNil)
dirOut, err := ioutil.TempDir("", "hugo-cli-out")
assert.NoError(err)
c.Assert(err, qt.IsNil)
defer func() {
os.RemoveAll(dir)
@@ -160,7 +171,6 @@ func TestCommandsExecute(t *testing.T) {
{nil, []string{sourceFlag}, ""},
{nil, []string{sourceFlag, "--renderToMemory"}, ""},
{[]string{"config"}, []string{sourceFlag}, ""},
{[]string{"benchmark"}, []string{sourceFlag, "-n=1"}, ""},
{[]string{"convert", "toTOML"}, []string{sourceFlag, "-o=" + filepath.Join(dirOut, "toml")}, ""},
{[]string{"convert", "toYAML"}, []string{sourceFlag, "-o=" + filepath.Join(dirOut, "yaml")}, ""},
{[]string{"convert", "toJSON"}, []string{sourceFlag, "-o=" + filepath.Join(dirOut, "json")}, ""},
@@ -179,8 +189,8 @@ func TestCommandsExecute(t *testing.T) {
}
for _, test := range tests {
hugoCmd := newCommandsBuilder().addAll().build().getCommand()
b := newCommandsBuilder().addAll().build()
hugoCmd := b.getCommand()
test.flags = append(test.flags, "--quiet")
hugoCmd.SetArgs(append(test.commands, test.flags...))
@@ -190,31 +200,54 @@ func TestCommandsExecute(t *testing.T) {
_, err := hugoCmd.ExecuteC()
if test.expectErrToContain != "" {
assert.Error(err, fmt.Sprintf("%v", test.commands))
assert.Contains(err.Error(), test.expectErrToContain)
c.Assert(err, qt.Not(qt.IsNil))
c.Assert(err.Error(), qt.Contains, test.expectErrToContain)
} else {
assert.NoError(err, fmt.Sprintf("%v", test.commands))
c.Assert(err, qt.IsNil)
}
// Assert that we have not left any development debug artifacts in
// the code.
if b.c != nil {
_, ok := b.c.destinationFs.(types.DevMarker)
c.Assert(ok, qt.Equals, false)
}
}
}
func createSimpleTestSite(t *testing.T) (string, error) {
type testSiteConfig struct {
configTOML string
contentDir string
}
func createSimpleTestSite(t *testing.T, cfg testSiteConfig) (string, error) {
d, e := ioutil.TempDir("", "hugo-cli")
if e != nil {
return "", e
}
// Just the basic. These are for CLI tests, not site testing.
writeFile(t, filepath.Join(d, "config.toml"), `
cfgStr := `
baseURL = "https://example.org"
title = "Hugo Commands"
`)
`
writeFile(t, filepath.Join(d, "content", "p1.md"), `
contentDir := "content"
if cfg.configTOML != "" {
cfgStr = cfg.configTOML
}
if cfg.contentDir != "" {
contentDir = cfg.contentDir
}
// Just the basic. These are for CLI tests, not site testing.
writeFile(t, filepath.Join(d, "config.toml"), cfgStr)
writeFile(t, filepath.Join(d, contentDir, "p1.md"), `
---
title: "P1"
weight: 1
@@ -233,6 +266,7 @@ Single: {{ .Title }}
writeFile(t, filepath.Join(d, "layouts", "_default", "list.html"), `
List: {{ .Title }}
Environment: {{ hugo.Environment }}
`)
+76 -6
View File
@@ -14,8 +14,17 @@
package commands
import (
"encoding/json"
"os"
"reflect"
"regexp"
"sort"
"strings"
"github.com/gohugoio/hugo/parser"
"github.com/gohugoio/hugo/parser/metadecoders"
"github.com/gohugoio/hugo/modules"
"github.com/spf13/cobra"
jww "github.com/spf13/jwalterweatherman"
@@ -38,29 +47,58 @@ func newConfigCmd() *configCmd {
RunE: cc.printConfig,
})
cc.cmd.Flags().StringVarP(&cc.source, "source", "s", "", "filesystem path to read files relative from")
cc.cmd.PersistentFlags().StringVarP(&cc.source, "source", "s", "", "filesystem path to read files relative from")
printMountsCmd := &cobra.Command{
Use: "mounts",
Short: "Print the configured file mounts",
RunE: cc.printMounts,
}
cc.cmd.AddCommand(printMountsCmd)
return cc
}
func (c *configCmd) printMounts(cmd *cobra.Command, args []string) error {
cfg, err := initializeConfig(true, false, &c.hugoBuilderCommon, c, nil)
if err != nil {
return err
}
allModules := cfg.Cfg.Get("allmodules").(modules.Modules)
for _, m := range allModules {
if err := parser.InterfaceToConfig(&modMounts{m: m}, metadecoders.JSON, os.Stdout); err != nil {
return err
}
}
return nil
}
func (c *configCmd) printConfig(cmd *cobra.Command, args []string) error {
cfg, err := initializeConfig(true, false, &c.hugoBuilderCommon, c, nil)
if err != nil {
return err
}
allSettings := cfg.Cfg.(*viper.Viper).AllSettings()
var separator string
if allSettings["metadataformat"] == "toml" {
// We need to clean up this, but we store objects in the config that
// isn't really interesting to the end user, so filter these.
ignoreKeysRe := regexp.MustCompile("client|sorted|filecacheconfigs|allmodules|multilingual")
separator := ": "
if len(cfg.configFiles) > 0 && strings.HasSuffix(cfg.configFiles[0], ".toml") {
separator = " = "
} else {
separator = ": "
}
var keys []string
for k := range allSettings {
if ignoreKeysRe.MatchString(k) {
continue
}
keys = append(keys, k)
}
sort.Strings(keys)
@@ -75,3 +113,35 @@ func (c *configCmd) printConfig(cmd *cobra.Command, args []string) error {
return nil
}
type modMounts struct {
m modules.Module
}
type modMount struct {
Source string `json:"source"`
Target string `json:"target"`
Lang string `json:"lang,omitempty"`
}
func (m *modMounts) MarshalJSON() ([]byte, error) {
var mounts []modMount
for _, mount := range m.m.Mounts() {
mounts = append(mounts, modMount{
Source: mount.Source,
Target: mount.Target,
Lang: mount.Lang,
})
}
return json.Marshal(&struct {
Path string `json:"path"`
Dir string `json:"dir"`
Mounts []modMount `json:"mounts"`
}{
Path: m.m.Path(),
Dir: m.m.Dir(),
Mounts: mounts,
})
}
+103 -47
View File
@@ -1,4 +1,4 @@
// Copyright 2015 The Hugo Authors. All rights reserved.
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,17 +14,28 @@
package commands
import (
"bytes"
"fmt"
"io"
"strings"
"time"
src "github.com/gohugoio/hugo/source"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/parser"
"github.com/gohugoio/hugo/parser/metadecoders"
"github.com/gohugoio/hugo/parser/pageparser"
"github.com/pkg/errors"
"github.com/gohugoio/hugo/hugolib"
"path/filepath"
"github.com/gohugoio/hugo/parser"
"github.com/spf13/cast"
"github.com/spf13/cobra"
)
@@ -60,7 +71,7 @@ See convert's subcommands toJSON, toTOML and toYAML for more information.`,
Long: `toJSON converts all front matter in the content directory
to use JSON for the front matter.`,
RunE: func(cmd *cobra.Command, args []string) error {
return cc.convertContents(rune([]byte(parser.JSONLead)[0]))
return cc.convertContents(metadecoders.JSON)
},
},
&cobra.Command{
@@ -69,7 +80,7 @@ to use JSON for the front matter.`,
Long: `toTOML converts all front matter in the content directory
to use TOML for the front matter.`,
RunE: func(cmd *cobra.Command, args []string) error {
return cc.convertContents(rune([]byte(parser.TOMLLead)[0]))
return cc.convertContents(metadecoders.TOML)
},
},
&cobra.Command{
@@ -78,7 +89,7 @@ to use TOML for the front matter.`,
Long: `toYAML converts all front matter in the content directory
to use YAML for the front matter.`,
RunE: func(cmd *cobra.Command, args []string) error {
return cc.convertContents(rune([]byte(parser.YAMLLead)[0]))
return cc.convertContents(metadecoders.YAML)
},
},
)
@@ -91,7 +102,7 @@ to use YAML for the front matter.`,
return cc
}
func (cc *convertCmd) convertContents(mark rune) error {
func (cc *convertCmd) convertContents(format metadecoders.Format) error {
if cc.outputDir == "" && !cc.unsafe {
return newUserError("Unsafe operation not allowed, use --unsafe or set a different output path")
}
@@ -101,6 +112,8 @@ func (cc *convertCmd) convertContents(mark rune) error {
return err
}
c.Cfg.Set("buildDrafts", true)
h, err := hugolib.NewHugoSites(*c.DepsCfg)
if err != nil {
return err
@@ -112,83 +125,126 @@ func (cc *convertCmd) convertContents(mark rune) error {
site := h.Sites[0]
site.Log.FEEDBACK.Println("processing", len(site.AllPages), "content files")
for _, p := range site.AllPages {
if err := cc.convertAndSavePage(p, site, mark); err != nil {
site.Log.FEEDBACK.Println("processing", len(site.AllPages()), "content files")
for _, p := range site.AllPages() {
if err := cc.convertAndSavePage(p, site, format); err != nil {
return err
}
}
return nil
}
func (cc *convertCmd) convertAndSavePage(p *hugolib.Page, site *hugolib.Site, mark rune) error {
func (cc *convertCmd) convertAndSavePage(p page.Page, site *hugolib.Site, targetFormat metadecoders.Format) error {
// The resources are not in .Site.AllPages.
for _, r := range p.Resources.ByType("page") {
if err := cc.convertAndSavePage(r.(*hugolib.Page), site, mark); err != nil {
for _, r := range p.Resources().ByType("page") {
if err := cc.convertAndSavePage(r.(page.Page), site, targetFormat); err != nil {
return err
}
}
if p.Filename() == "" {
if p.File().IsZero() {
// No content file.
return nil
}
site.Log.INFO.Println("Attempting to convert", p.LogicalName())
newPage, err := site.NewPage(p.LogicalName())
if err != nil {
return err
}
errMsg := fmt.Errorf("Error processing file %q", p.Path())
f, _ := p.File.(src.ReadableFile)
file, err := f.Open()
site.Log.INFO.Println("Attempting to convert", p.File().Filename())
f := p.File()
file, err := f.FileInfo().Meta().Open()
if err != nil {
site.Log.ERROR.Println("Error reading file:", p.Path())
site.Log.ERROR.Println(errMsg)
file.Close()
return nil
}
psr, err := parser.ReadFrom(file)
pf, err := parseContentFile(file)
if err != nil {
site.Log.ERROR.Println("Error processing file:", p.Path())
site.Log.ERROR.Println(errMsg)
file.Close()
return err
}
file.Close()
metadata, err := psr.Metadata()
// better handling of dates in formats that don't have support for them
if pf.frontMatterFormat == metadecoders.JSON || pf.frontMatterFormat == metadecoders.YAML || pf.frontMatterFormat == metadecoders.TOML {
for k, v := range pf.frontMatter {
switch vv := v.(type) {
case time.Time:
pf.frontMatter[k] = vv.Format(time.RFC3339)
}
}
}
var newContent bytes.Buffer
err = parser.InterfaceToFrontMatter(pf.frontMatter, targetFormat, &newContent)
if err != nil {
site.Log.ERROR.Println("Error processing file:", p.Path())
site.Log.ERROR.Println(errMsg)
return err
}
// better handling of dates in formats that don't have support for them
if mark == parser.FormatToLeadRune("json") || mark == parser.FormatToLeadRune("yaml") || mark == parser.FormatToLeadRune("toml") {
newMetadata := cast.ToStringMap(metadata)
for k, v := range newMetadata {
switch vv := v.(type) {
case time.Time:
newMetadata[k] = vv.Format(time.RFC3339)
}
}
metadata = newMetadata
}
newContent.Write(pf.content)
newPage.SetSourceContent(psr.Content())
if err = newPage.SetSourceMetaData(metadata, mark); err != nil {
site.Log.ERROR.Printf("Failed to set source metadata for file %q: %s. For more info see For more info see https://github.com/gohugoio/hugo/issues/2458", newPage.FullFilePath(), err)
return nil
}
newFilename := p.File().Filename()
newFilename := p.Filename()
if cc.outputDir != "" {
newFilename = filepath.Join(cc.outputDir, p.Dir(), newPage.LogicalName())
contentDir := strings.TrimSuffix(newFilename, p.Path())
contentDir = filepath.Base(contentDir)
newFilename = filepath.Join(cc.outputDir, contentDir, p.Path())
}
if err = newPage.SaveSourceAs(newFilename); err != nil {
return fmt.Errorf("Failed to save file %q: %s", newFilename, err)
fs := hugofs.Os
if err := helpers.WriteToDisk(newFilename, &newContent, fs); err != nil {
return errors.Wrapf(err, "Failed to save file %q:", newFilename)
}
return nil
}
type parsedFile struct {
frontMatterFormat metadecoders.Format
frontMatterSource []byte
frontMatter map[string]interface{}
// Everything after Front Matter
content []byte
}
func parseContentFile(r io.Reader) (parsedFile, error) {
var pf parsedFile
psr, err := pageparser.Parse(r, pageparser.Config{})
if err != nil {
return pf, err
}
iter := psr.Iterator()
walkFn := func(item pageparser.Item) bool {
if pf.frontMatterSource != nil {
// The rest is content.
pf.content = psr.Input()[item.Pos:]
// Done
return false
} else if item.IsFrontMatter() {
pf.frontMatterFormat = metadecoders.FormatFromFrontMatterType(item.Type)
pf.frontMatterSource = item.Val
}
return true
}
iter.PeekWalk(walkFn)
metadata, err := metadecoders.Default.UnmarshalToMap(pf.frontMatterSource, pf.frontMatterFormat)
if err != nil {
return pf, err
}
pf.frontMatter = metadata
return pf, nil
}
+77
View File
@@ -0,0 +1,77 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
import (
"context"
"github.com/gohugoio/hugo/deploy"
"github.com/spf13/cobra"
)
var _ cmder = (*deployCmd)(nil)
// deployCmd supports deploying sites to Cloud providers.
type deployCmd struct {
hugoBuilderCommon
*baseCmd
}
// TODO: In addition to the "deploy" command, consider adding a "--deploy"
// flag for the default command; this would build the site and then deploy it.
// It's not obvious how to do this; would all of the deploy-specific flags
// have to exist at the top level as well?
// TODO: The output files change every time "hugo" is executed, it looks
// like because of map order randomization. This means that you can
// run "hugo && hugo deploy" again and again and upload new stuff every time. Is
// this intended?
func newDeployCmd() *deployCmd {
cc := &deployCmd{}
cc.baseCmd = newBaseCmd(&cobra.Command{
Use: "deploy",
Short: "Deploy your site to a Cloud provider.",
Long: `Deploy your site to a Cloud provider.
See https://gohugo.io/hosting-and-deployment/hugo-deploy/ for detailed
documentation.
`,
RunE: func(cmd *cobra.Command, args []string) error {
cfgInit := func(c *commandeer) error {
return nil
}
comm, err := initializeConfig(true, false, &cc.hugoBuilderCommon, cc, cfgInit)
if err != nil {
return err
}
deployer, err := deploy.New(comm.Cfg, comm.hugo().PathSpec.PublishFs)
if err != nil {
return err
}
return deployer.Deploy(context.Background())
},
})
cc.cmd.Flags().String("target", "", "target deployment from deployments section in config file; defaults to the first one")
cc.cmd.Flags().Bool("confirm", false, "ask for confirmation before making changes to the target")
cc.cmd.Flags().Bool("dryRun", false, "dry run")
cc.cmd.Flags().Bool("force", false, "force upload of all files")
cc.cmd.Flags().Bool("invalidateCDN", true, "invalidate the CDN cache via the cloudFrontDistributionID listed in the deployment target")
cc.cmd.Flags().Int("maxDeletes", 256, "maximum # of files to delete, or -1 to disable")
return cc
}
+2 -1
View File
@@ -17,6 +17,7 @@ import (
"fmt"
"strings"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
"github.com/spf13/cobra"
@@ -45,7 +46,7 @@ in the "man" directory under the current directory.`,
header := &doc.GenManHeader{
Section: "1",
Manual: "Hugo Manual",
Source: fmt.Sprintf("Hugo %s", helpers.CurrentHugoVersion),
Source: fmt.Sprintf("Hugo %s", hugo.CurrentVersion),
}
if !strings.HasSuffix(cc.genmandir, helpers.FilePathSeparator) {
cc.genmandir += helpers.FilePathSeparator
+7
View File
@@ -23,6 +23,13 @@ import (
"github.com/spf13/cobra"
)
const (
ansiEsc = "\u001B"
clearLine = "\r\033[K"
hideCursor = ansiEsc + "[?25l"
showCursor = ansiEsc + "[?25h"
)
type flagsToConfigHandler interface {
flagsToConfig(cfg config.Provider)
}
+610 -439
View File
File diff suppressed because it is too large Load Diff
+52
View File
@@ -0,0 +1,52 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
import (
"os"
"testing"
qt "github.com/frankban/quicktest"
)
// Issue #5662
func TestHugoWithContentDirOverride(t *testing.T) {
c := qt.New(t)
hugoCmd := newCommandsBuilder().addAll().build()
cmd := hugoCmd.getCommand()
contentDir := "contentOverride"
cfgStr := `
baseURL = "https://example.org"
title = "Hugo Commands"
contentDir = "thisdoesnotexist"
`
dir, err := createSimpleTestSite(t, testSiteConfig{configTOML: cfgStr, contentDir: contentDir})
c.Assert(err, qt.IsNil)
defer func() {
os.RemoveAll(dir)
}()
cmd.SetArgs([]string{"-s=" + dir, "-c=" + contentDir})
_, err = cmd.ExecuteC()
c.Assert(err, qt.IsNil)
}
+72 -90
View File
@@ -1,4 +1,4 @@
// Copyright 2016 The Hugo Authors. All rights reserved.
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@ package commands
import (
"bytes"
"errors"
"io"
"fmt"
"io/ioutil"
"os"
"path/filepath"
@@ -24,6 +24,11 @@ import (
"strconv"
"strings"
"time"
"unicode"
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/parser/metadecoders"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
@@ -109,7 +114,7 @@ func (i *importCmd) importFromJekyll(cmd *cobra.Command, args []string) error {
jww.FEEDBACK.Println("Importing...")
fileCount := 0
callback := func(path string, fi os.FileInfo, err error) error {
callback := func(path string, fi hugofs.FileMetaInfo, err error) error {
if err != nil {
return err
}
@@ -253,7 +258,7 @@ func (i *importCmd) loadJekyllConfig(fs afero.Fs, jekyllRoot string) map[string]
return nil
}
c, err := parser.HandleYAMLMetaData(b)
c, err := metadecoders.Default.UnmarshalToMap(b, metadecoders.YAML)
if err != nil {
return nil
@@ -262,7 +267,7 @@ func (i *importCmd) loadJekyllConfig(fs afero.Fs, jekyllRoot string) map[string]
return c
}
func (i *importCmd) createConfigFromJekyll(fs afero.Fs, inpath string, kind string, jekyllConfig map[string]interface{}) (err error) {
func (i *importCmd) createConfigFromJekyll(fs afero.Fs, inpath string, kind metadecoders.Format, jekyllConfig map[string]interface{}) (err error) {
title := "My New Hugo Site"
baseURL := "http://example.org/"
@@ -288,77 +293,20 @@ func (i *importCmd) createConfigFromJekyll(fs afero.Fs, inpath string, kind stri
"languageCode": "en-us",
"disablePathToLower": true,
}
kind = parser.FormatSanitize(kind)
var buf bytes.Buffer
err = parser.InterfaceToConfig(in, parser.FormatToLeadRune(kind), &buf)
err = parser.InterfaceToConfig(in, kind, &buf)
if err != nil {
return err
}
return helpers.WriteToDisk(filepath.Join(inpath, "config."+kind), &buf, fs)
}
func copyFile(source string, dest string) error {
sf, err := os.Open(source)
if err != nil {
return err
}
defer sf.Close()
df, err := os.Create(dest)
if err != nil {
return err
}
defer df.Close()
_, err = io.Copy(df, sf)
if err == nil {
si, err := os.Stat(source)
if err != nil {
err = os.Chmod(dest, si.Mode())
if err != nil {
return err
}
}
}
return nil
}
func copyDir(source string, dest string) error {
fi, err := os.Stat(source)
if err != nil {
return err
}
if !fi.IsDir() {
return errors.New(source + " is not a directory")
}
err = os.MkdirAll(dest, fi.Mode())
if err != nil {
return err
}
entries, err := ioutil.ReadDir(source)
for _, entry := range entries {
sfp := filepath.Join(source, entry.Name())
dfp := filepath.Join(dest, entry.Name())
if entry.IsDir() {
err = copyDir(sfp, dfp)
if err != nil {
jww.ERROR.Println(err)
}
} else {
err = copyFile(sfp, dfp)
if err != nil {
jww.ERROR.Println(err)
}
}
}
return nil
return helpers.WriteToDisk(filepath.Join(inpath, "config."+string(kind)), &buf, fs)
}
func (i *importCmd) copyJekyllFilesAndFolders(jekyllRoot, dest string, jekyllPostDirs map[string]bool) (err error) {
fi, err := os.Stat(jekyllRoot)
fs := hugofs.Os
fi, err := fs.Stat(jekyllRoot)
if err != nil {
return err
}
@@ -370,13 +318,17 @@ func (i *importCmd) copyJekyllFilesAndFolders(jekyllRoot, dest string, jekyllPos
return err
}
entries, err := ioutil.ReadDir(jekyllRoot)
if err != nil {
return err
}
for _, entry := range entries {
sfp := filepath.Join(jekyllRoot, entry.Name())
dfp := filepath.Join(dest, entry.Name())
if entry.IsDir() {
if entry.Name()[0] != '_' && entry.Name()[0] != '.' {
if _, ok := jekyllPostDirs[entry.Name()]; !ok {
err = copyDir(sfp, dfp)
err = hugio.CopyDir(fs, sfp, dfp, nil)
if err != nil {
jww.ERROR.Println(err)
}
@@ -395,7 +347,7 @@ func (i *importCmd) copyJekyllFilesAndFolders(jekyllRoot, dest string, jekyllPos
}
if !isExcept && entry.Name()[0] != '.' && entry.Name()[0] != '_' {
err = copyFile(sfp, dfp)
err = hugio.CopyFile(fs, sfp, dfp)
if err != nil {
jww.ERROR.Println(err)
}
@@ -445,39 +397,25 @@ func convertJekyllPost(s *hugolib.Site, path, relPath, targetDir string, draft b
return err
}
psr, err := parser.ReadFrom(bytes.NewReader(contentBytes))
pf, err := parseContentFile(bytes.NewReader(contentBytes))
if err != nil {
jww.ERROR.Println("Parse file error:", path)
return err
}
metadata, err := psr.Metadata()
if err != nil {
jww.ERROR.Println("Processing file error:", path)
return err
}
newmetadata, err := convertJekyllMetaData(metadata, postName, postDate, draft)
newmetadata, err := convertJekyllMetaData(pf.frontMatter, postName, postDate, draft)
if err != nil {
jww.ERROR.Println("Convert metadata error:", path)
return err
}
jww.TRACE.Println(newmetadata)
content := convertJekyllContent(newmetadata, string(psr.Content()))
content := convertJekyllContent(newmetadata, string(pf.content))
page, err := s.NewPage(filename)
if err != nil {
jww.ERROR.Println("New page error", filename)
return err
fs := hugofs.Os
if err := helpers.WriteToDisk(targetFile, strings.NewReader(content), fs); err != nil {
return fmt.Errorf("failed to save file %q: %s", filename, err)
}
page.SetSourceContent([]byte(content))
page.SetSourceMetaData(newmetadata, parser.FormatToLeadRune("yaml"))
page.SaveSourceAs(targetFile)
jww.TRACE.Println("Target file:", targetFile)
return nil
}
@@ -557,7 +495,6 @@ func convertJekyllContent(m interface{}, content string) string {
}{
{regexp.MustCompile("(?i)<!-- more -->"), "<!--more-->"},
{regexp.MustCompile(`\{%\s*raw\s*%\}\s*(.*?)\s*\{%\s*endraw\s*%\}`), "$1"},
{regexp.MustCompile(`{%\s*highlight\s*(.*?)\s*%}`), "{{< highlight $1 >}}"},
{regexp.MustCompile(`{%\s*endhighlight\s*%}`), "{{< / highlight >}}"},
}
@@ -571,6 +508,7 @@ func convertJekyllContent(m interface{}, content string) string {
}{
// Octopress image tag: http://octopress.org/docs/plugins/image-tag/
{regexp.MustCompile(`{%\s+img\s*(.*?)\s*%}`), replaceImageTag},
{regexp.MustCompile(`{%\s*highlight\s*(.*?)\s*%}`), replaceHighlightTag},
}
for _, replace := range replaceListFunc {
@@ -580,6 +518,50 @@ func convertJekyllContent(m interface{}, content string) string {
return content
}
func replaceHighlightTag(match string) string {
r := regexp.MustCompile(`{%\s*highlight\s*(.*?)\s*%}`)
parts := r.FindStringSubmatch(match)
lastQuote := rune(0)
f := func(c rune) bool {
switch {
case c == lastQuote:
lastQuote = rune(0)
return false
case lastQuote != rune(0):
return false
case unicode.In(c, unicode.Quotation_Mark):
lastQuote = c
return false
default:
return unicode.IsSpace(c)
}
}
// splitting string by space but considering quoted section
items := strings.FieldsFunc(parts[1], f)
result := bytes.NewBufferString("{{< highlight ")
result.WriteString(items[0]) // language
options := items[1:]
for i, opt := range options {
opt = strings.Replace(opt, "\"", "", -1)
if opt == "linenos" {
opt = "linenos=table"
}
if i == 0 {
opt = " \"" + opt
}
if i < len(options)-1 {
opt += ","
} else if i == len(options)-1 {
opt += "\""
}
result.WriteString(opt)
}
result.WriteString(" >}}")
return result.String()
}
func replaceImageTag(match string) string {
r := regexp.MustCompile(`{%\s+img\s*(\p{L}*)\s+([\S]*/[\S]+)\s+(\d*)\s*(\d*)\s*(.*?)\s*%}`)
result := bytes.NewBufferString("{{< figure ")
+15 -8
View File
@@ -15,12 +15,14 @@ package commands
import (
"encoding/json"
"github.com/stretchr/testify/assert"
"testing"
"time"
qt "github.com/frankban/quicktest"
)
func TestParseJekyllFilename(t *testing.T) {
c := qt.New(t)
filenameArray := []string{
"2015-01-02-test.md",
"2012-03-15-中文.markup",
@@ -36,13 +38,14 @@ func TestParseJekyllFilename(t *testing.T) {
for i, filename := range filenameArray {
postDate, postName, err := parseJekyllFilename(filename)
assert.Equal(t, err, nil)
assert.Equal(t, expectResult[i].postDate.Format("2006-01-02"), postDate.Format("2006-01-02"))
assert.Equal(t, expectResult[i].postName, postName)
c.Assert(err, qt.IsNil)
c.Assert(expectResult[i].postDate.Format("2006-01-02"), qt.Equals, postDate.Format("2006-01-02"))
c.Assert(expectResult[i].postName, qt.Equals, postName)
}
}
func TestConvertJekyllMetadata(t *testing.T) {
c := qt.New(t)
testDataList := []struct {
metadata interface{}
postName string
@@ -73,14 +76,15 @@ func TestConvertJekyllMetadata(t *testing.T) {
for _, data := range testDataList {
result, err := convertJekyllMetaData(data.metadata, data.postName, data.postDate, data.draft)
assert.Equal(t, nil, err)
c.Assert(err, qt.IsNil)
jsonResult, err := json.Marshal(result)
assert.Equal(t, nil, err)
assert.Equal(t, data.expect, string(jsonResult))
c.Assert(err, qt.IsNil)
c.Assert(string(jsonResult), qt.Equals, data.expect)
}
}
func TestConvertJekyllContent(t *testing.T) {
c := qt.New(t)
testDataList := []struct {
metadata interface{}
content string
@@ -97,6 +101,9 @@ func TestConvertJekyllContent(t *testing.T) {
{map[interface{}]interface{}{},
"{% highlight go %}\nvar s int\n{% endhighlight %}",
"{{< highlight go >}}\nvar s int\n{{< / highlight >}}"},
{map[interface{}]interface{}{},
"{% highlight go linenos hl_lines=\"1 2\" %}\nvar s string\nvar i int\n{% endhighlight %}",
"{{< highlight go \"linenos=table,hl_lines=1 2\" >}}\nvar s string\nvar i int\n{{< / highlight >}}"},
// Octopress image tag
{map[interface{}]interface{}{},
@@ -121,6 +128,6 @@ func TestConvertJekyllContent(t *testing.T) {
for _, data := range testDataList {
result := convertJekyllContent(data.metadata, data.content)
assert.Equal(t, data.expect, result)
c.Assert(data.expect, qt.Equals, result)
}
}
+14 -8
View File
@@ -36,14 +36,17 @@ This is primarily to ensure that Hugo can watch enough files on some OSs`,
var rLimit syscall.Rlimit
err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
if err != nil {
return newSystemError("Error Getting Rlimit ", err)
return newSystemError("Error Getting rlimit ", err)
}
jww.FEEDBACK.Println("Current rLimit:", rLimit)
if rLimit.Cur >= newRlimit {
return nil
}
jww.FEEDBACK.Println("Attempting to increase limit")
rLimit.Max = 999999
rLimit.Cur = 999999
rLimit.Cur = newRlimit
err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
if err != nil {
return newSystemError("Error Setting rLimit ", err)
@@ -61,18 +64,21 @@ This is primarily to ensure that Hugo can watch enough files on some OSs`,
return &limitCmd{baseCmd: newBaseCmd(ccmd)}
}
const newRlimit = 10240
func tweakLimit() {
var rLimit syscall.Rlimit
err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
if err != nil {
jww.ERROR.Println("Unable to obtain rLimit", err)
jww.WARN.Println("Unable to get rlimit:", err)
return
}
if rLimit.Cur < rLimit.Max {
rLimit.Max = 64000
rLimit.Cur = 64000
if rLimit.Cur < newRlimit {
rLimit.Cur = newRlimit
err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
if err != nil {
jww.WARN.Println("Unable to increase number of open files limit", err)
// This may not succeed, see https://github.com/golang/go/issues/30401
jww.INFO.Println("Unable to increase number of open files limit:", err)
}
}
}
+112 -60
View File
@@ -1,4 +1,4 @@
// Copyright 2015 The Hugo Authors. All rights reserved.
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,9 +14,14 @@
package commands
import (
"path/filepath"
"encoding/csv"
"os"
"strconv"
"strings"
"time"
"github.com/gohugoio/hugo/hugolib"
"github.com/gohugoio/hugo/resources/resource"
"github.com/spf13/cobra"
jww "github.com/spf13/jwalterweatherman"
)
@@ -28,6 +33,32 @@ type listCmd struct {
*baseCmd
}
func (lc *listCmd) buildSites(config map[string]interface{}) (*hugolib.HugoSites, error) {
cfgInit := func(c *commandeer) error {
for key, value := range config {
c.Set(key, value)
}
return nil
}
c, err := initializeConfig(true, false, &lc.hugoBuilderCommon, lc, cfgInit)
if err != nil {
return nil, err
}
sites, err := hugolib.NewHugoSites(*c.DepsCfg)
if err != nil {
return nil, newSystemError("Error creating sites", err)
}
if err := sites.Build(hugolib.BuildCfg{SkipRender: true}); err != nil {
return nil, newSystemError("Error Processing Source Content", err)
}
return sites, nil
}
func newListCmd() *listCmd {
cc := &listCmd{}
@@ -46,106 +77,127 @@ List requires a subcommand, e.g. ` + "`hugo list drafts`.",
Short: "List all drafts",
Long: `List all of the drafts in your content directory.`,
RunE: func(cmd *cobra.Command, args []string) error {
cfgInit := func(c *commandeer) error {
c.Set("buildDrafts", true)
return nil
}
c, err := initializeConfig(true, false, &cc.hugoBuilderCommon, cc, cfgInit)
if err != nil {
return err
}
sites, err := hugolib.NewHugoSites(*c.DepsCfg)
sites, err := cc.buildSites(map[string]interface{}{"buildDrafts": true})
if err != nil {
return newSystemError("Error creating sites", err)
}
if err := sites.Build(hugolib.BuildCfg{SkipRender: true}); err != nil {
return newSystemError("Error Processing Source Content", err)
return newSystemError("Error building sites", err)
}
for _, p := range sites.Pages() {
if p.IsDraft() {
jww.FEEDBACK.Println(filepath.Join(p.File.Dir(), p.File.LogicalName()))
if p.Draft() {
jww.FEEDBACK.Println(strings.TrimPrefix(p.File().Filename(), sites.WorkingDir+string(os.PathSeparator)))
}
}
return nil
},
},
&cobra.Command{
Use: "future",
Short: "List all posts dated in the future",
Long: `List all of the posts in your content directory which will be
posted in the future.`,
Long: `List all of the posts in your content directory which will be posted in the future.`,
RunE: func(cmd *cobra.Command, args []string) error {
cfgInit := func(c *commandeer) error {
c.Set("buildFuture", true)
return nil
}
c, err := initializeConfig(true, false, &cc.hugoBuilderCommon, cc, cfgInit)
if err != nil {
return err
}
sites, err := hugolib.NewHugoSites(*c.DepsCfg)
sites, err := cc.buildSites(map[string]interface{}{"buildFuture": true})
if err != nil {
return newSystemError("Error creating sites", err)
return newSystemError("Error building sites", err)
}
if err := sites.Build(hugolib.BuildCfg{SkipRender: true}); err != nil {
return newSystemError("Error Processing Source Content", err)
}
writer := csv.NewWriter(os.Stdout)
defer writer.Flush()
for _, p := range sites.Pages() {
if p.IsFuture() {
jww.FEEDBACK.Println(filepath.Join(p.File.Dir(), p.File.LogicalName()))
if resource.IsFuture(p) {
err := writer.Write([]string{
strings.TrimPrefix(p.File().Filename(), sites.WorkingDir+string(os.PathSeparator)),
p.PublishDate().Format(time.RFC3339),
})
if err != nil {
return newSystemError("Error writing future posts to stdout", err)
}
}
}
return nil
},
},
&cobra.Command{
Use: "expired",
Short: "List all posts already expired",
Long: `List all of the posts in your content directory which has already
expired.`,
Long: `List all of the posts in your content directory which has already expired.`,
RunE: func(cmd *cobra.Command, args []string) error {
cfgInit := func(c *commandeer) error {
c.Set("buildExpired", true)
return nil
}
c, err := initializeConfig(true, false, &cc.hugoBuilderCommon, cc, cfgInit)
if err != nil {
return err
}
sites, err := hugolib.NewHugoSites(*c.DepsCfg)
sites, err := cc.buildSites(map[string]interface{}{"buildExpired": true})
if err != nil {
return newSystemError("Error creating sites", err)
return newSystemError("Error building sites", err)
}
if err := sites.Build(hugolib.BuildCfg{SkipRender: true}); err != nil {
return newSystemError("Error Processing Source Content", err)
}
writer := csv.NewWriter(os.Stdout)
defer writer.Flush()
for _, p := range sites.Pages() {
if p.IsExpired() {
jww.FEEDBACK.Println(filepath.Join(p.File.Dir(), p.File.LogicalName()))
if resource.IsExpired(p) {
err := writer.Write([]string{
strings.TrimPrefix(p.File().Filename(), sites.WorkingDir+string(os.PathSeparator)),
p.ExpiryDate().Format(time.RFC3339),
})
if err != nil {
return newSystemError("Error writing expired posts to stdout", err)
}
}
}
return nil
},
},
&cobra.Command{
Use: "all",
Short: "List all posts",
Long: `List all of the posts in your content directory, include drafts, future and expired pages.`,
RunE: func(cmd *cobra.Command, args []string) error {
sites, err := cc.buildSites(map[string]interface{}{
"buildExpired": true,
"buildDrafts": true,
"buildFuture": true,
})
if err != nil {
return newSystemError("Error building sites", err)
}
writer := csv.NewWriter(os.Stdout)
defer writer.Flush()
writer.Write([]string{
"path",
"slug",
"title",
"date",
"expiryDate",
"publishDate",
"draft",
"permalink",
})
for _, p := range sites.Pages() {
if !p.IsPage() {
continue
}
err := writer.Write([]string{
strings.TrimPrefix(p.File().Filename(), sites.WorkingDir+string(os.PathSeparator)),
p.Slug(),
p.Title(),
p.Date().Format(time.RFC3339),
p.ExpiryDate().Format(time.RFC3339),
p.PublishDate().Format(time.RFC3339),
strconv.FormatBool(p.Draft()),
p.Permalink(),
})
if err != nil {
return newSystemError("Error writing posts to stdout", err)
}
}
return nil
},
},
)
+72
View File
@@ -0,0 +1,72 @@
package commands
import (
"bytes"
"encoding/csv"
"io"
"os"
"path/filepath"
"strings"
"testing"
qt "github.com/frankban/quicktest"
"github.com/spf13/cobra"
)
func captureStdout(f func() (*cobra.Command, error)) (string, error) {
old := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
_, err := f()
if err != nil {
return "", err
}
w.Close()
os.Stdout = old
var buf bytes.Buffer
io.Copy(&buf, r)
return buf.String(), nil
}
func TestListAll(t *testing.T) {
c := qt.New(t)
dir, err := createSimpleTestSite(t, testSiteConfig{})
c.Assert(err, qt.IsNil)
hugoCmd := newCommandsBuilder().addAll().build()
cmd := hugoCmd.getCommand()
defer func() {
os.RemoveAll(dir)
}()
cmd.SetArgs([]string{"-s=" + dir, "list", "all"})
out, err := captureStdout(cmd.ExecuteC)
c.Assert(err, qt.IsNil)
r := csv.NewReader(strings.NewReader(out))
header, err := r.Read()
c.Assert(err, qt.IsNil)
c.Assert(header, qt.DeepEquals, []string{
"path", "slug", "title",
"date", "expiryDate", "publishDate",
"draft", "permalink",
})
record, err := r.Read()
c.Assert(err, qt.IsNil)
c.Assert(record, qt.DeepEquals, []string{
filepath.Join("content", "p1.md"), "", "P1",
"0001-01-01T00:00:00Z", "0001-01-01T00:00:00Z", "0001-01-01T00:00:00Z",
"false", "https://example.org/p1/",
})
}
+188
View File
@@ -0,0 +1,188 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
import (
"os"
"github.com/gohugoio/hugo/modules"
"github.com/spf13/cobra"
)
var _ cmder = (*modCmd)(nil)
type modCmd struct {
*baseBuilderCmd
}
func (b *commandsBuilder) newModCmd() *modCmd {
c := &modCmd{}
const commonUsage = `
Note that Hugo will always start out by resolving the components defined in the site
configuration, provided by a _vendor directory (if no --ignoreVendor flag provided),
Go Modules, or a folder inside the themes directory, in that order.
See https://gohugo.io/hugo-modules/ for more information.
`
cmd := &cobra.Command{
Use: "mod",
Short: "Various Hugo Modules helpers.",
Long: `Various helpers to help manage the modules in your project's dependency graph.
Most operations here requires a Go version installed on your system (>= Go 1.12) and the relevant VCS client (typically Git).
This is not needed if you only operate on modules inside /themes or if you have vendored them via "hugo mod vendor".
` + commonUsage,
RunE: nil,
}
cmd.AddCommand(
&cobra.Command{
Use: "get",
DisableFlagParsing: true,
Short: "Resolves dependencies in your current Hugo Project.",
Long: `
Resolves dependencies in your current Hugo Project.
Some examples:
Install the latest version possible for a given module:
hugo mod get github.com/gohugoio/testshortcodes
Install a specific version:
hugo mod get github.com/gohugoio/testshortcodes@v0.3.0
Install the latest versions of all module dependencies:
hugo mod get -u
Run "go help get" for more information. All flags available for "go get" is also relevant here.
` + commonUsage,
RunE: func(cmd *cobra.Command, args []string) error {
return c.withModsClient(false, func(c *modules.Client) error {
// We currently just pass on the flags we get to Go and
// need to do the flag handling manually.
if len(args) == 1 && args[0] == "-h" {
return cmd.Help()
}
return c.Get(args...)
})
},
},
&cobra.Command{
Use: "graph",
Short: "Print a module dependency graph.",
Long: `Print a module dependency graph with information about module status (disabled, vendored).
Note that for vendored modules, that is the version listed and not the one from go.mod.
`,
RunE: func(cmd *cobra.Command, args []string) error {
return c.withModsClient(true, func(c *modules.Client) error {
return c.Graph(os.Stdout)
})
},
},
&cobra.Command{
Use: "init",
Short: "Initialize this project as a Hugo Module.",
Long: `Initialize this project as a Hugo Module.
It will try to guess the module path, but you may help by passing it as an argument, e.g:
hugo mod init github.com/gohugoio/testshortcodes
Note that Hugo Modules supports multi-module projects, so you can initialize a Hugo Module
inside a subfolder on GitHub, as one example.
`,
RunE: func(cmd *cobra.Command, args []string) error {
var path string
if len(args) >= 1 {
path = args[0]
}
return c.withModsClient(false, func(c *modules.Client) error {
return c.Init(path)
})
},
},
&cobra.Command{
Use: "vendor",
Short: "Vendor all module dependencies into the _vendor directory.",
Long: `Vendor all module dependencies into the _vendor directory.
If a module is vendored, that is where Hugo will look for it's dependencies.
`,
RunE: func(cmd *cobra.Command, args []string) error {
return c.withModsClient(true, func(c *modules.Client) error {
return c.Vendor()
})
},
},
&cobra.Command{
Use: "tidy",
Short: "Remove unused entries in go.mod and go.sum.",
RunE: func(cmd *cobra.Command, args []string) error {
return c.withModsClient(true, func(c *modules.Client) error {
return c.Tidy()
})
},
},
&cobra.Command{
Use: "clean",
Short: "Delete the entire Hugo Module cache.",
Long: `Delete the entire Hugo Module cache.
Note that after you run this command, all of your dependencies will be re-downloaded next time you run "hugo".
Also note that if you configure a positive maxAge for the "modules" file cache, it will also be cleaned as part of "hugo --gc".
`,
RunE: func(cmd *cobra.Command, args []string) error {
com, err := c.initConfig(true)
if err != nil {
return err
}
_, err = com.hugo().FileCaches.ModulesCache().Prune(true)
return err
},
},
)
c.baseBuilderCmd = b.newBuilderCmd(cmd)
return c
}
func (c *modCmd) withModsClient(failOnMissingConfig bool, f func(*modules.Client) error) error {
com, err := c.initConfig(failOnMissingConfig)
if err != nil {
return err
}
return f(com.hugo().ModulesClient)
}
func (c *modCmd) initConfig(failOnNoConfig bool) (*commandeer, error) {
com, err := initializeConfig(failOnNoConfig, false, &c.hugoBuilderCommon, c, nil)
if err != nil {
return nil, err
}
return com, nil
}
+18 -18
View File
@@ -30,16 +30,14 @@ import (
var _ cmder = (*newCmd)(nil)
type newCmd struct {
hugoBuilderCommon
contentEditor string
contentType string
*baseCmd
*baseBuilderCmd
}
func newNewCmd() *newCmd {
cc := &newCmd{}
cc.baseCmd = newBaseCmd(&cobra.Command{
func (b *commandsBuilder) newNewCmd() *newCmd {
cmd := &cobra.Command{
Use: "new [path]",
Short: "Create new content for your site",
Long: `Create a new content file and automatically set the date and title.
@@ -47,18 +45,20 @@ It will guess which kind of file to create based on the path provided.
You can also specify the kind with ` + "`-k KIND`" + `.
If archetypes are provided in your theme or site, they will be used.`,
If archetypes are provided in your theme or site, they will be used.
RunE: cc.newContent,
})
Ensure you run this within the root directory of your site.`,
}
cc.cmd.Flags().StringVarP(&cc.contentType, "kind", "k", "", "content type to create")
cc.cmd.PersistentFlags().StringVarP(&cc.source, "source", "s", "", "filesystem path to read files relative from")
cc.cmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{})
cc.cmd.Flags().StringVar(&cc.contentEditor, "editor", "", "edit new content with this editor, if provided")
cc := &newCmd{baseBuilderCmd: b.newBuilderCmd(cmd)}
cc.cmd.AddCommand(newNewSiteCmd().getCommand())
cc.cmd.AddCommand(newNewThemeCmd().getCommand())
cmd.Flags().StringVarP(&cc.contentType, "kind", "k", "", "content type to create")
cmd.Flags().StringVar(&cc.contentEditor, "editor", "", "edit new content with this editor, if provided")
cmd.AddCommand(newNewSiteCmd().getCommand())
cmd.AddCommand(newNewThemeCmd().getCommand())
cmd.RunE = cc.newContent
return cc
}
@@ -85,13 +85,13 @@ func (n *newCmd) newContent(cmd *cobra.Command, args []string) error {
var kind string
createPath, kind = newContentPathSection(c.hugo, createPath)
createPath, kind = newContentPathSection(c.hugo(), createPath)
if n.contentType != "" {
kind = n.contentType
}
return create.NewContent(c.hugo, kind, createPath)
return create.NewContent(c.hugo(), kind, createPath)
}
func mkdir(x ...string) {
@@ -118,8 +118,8 @@ func newContentPathSection(h *hugolib.HugoSites, path string) (string, string) {
createpath := filepath.FromSlash(path)
if h != nil {
for _, s := range h.Sites {
createpath = strings.TrimPrefix(createpath, s.PathSpec.ContentDir)
for _, dir := range h.BaseFs.Content.Dirs {
createpath = strings.TrimPrefix(createpath, dir.Meta().Filename())
}
}
+27 -21
View File
@@ -1,4 +1,4 @@
// Copyright 2018 The Hugo Authors. All rights reserved.
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -19,19 +19,20 @@ import (
"github.com/gohugoio/hugo/hugofs"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
qt "github.com/frankban/quicktest"
)
// Issue #1133
func TestNewContentPathSectionWithForwardSlashes(t *testing.T) {
c := qt.New(t)
p, s := newContentPathSection(nil, "/post/new.md")
assert.Equal(t, filepath.FromSlash("/post/new.md"), p)
assert.Equal(t, "post", s)
c.Assert(p, qt.Equals, filepath.FromSlash("/post/new.md"))
c.Assert(s, qt.Equals, "post")
}
func checkNewSiteInited(fs *hugofs.Fs, basepath string, t *testing.T) {
c := qt.New(t)
paths := []string{
filepath.Join(basepath, "layouts"),
filepath.Join(basepath, "content"),
@@ -43,77 +44,82 @@ func checkNewSiteInited(fs *hugofs.Fs, basepath string, t *testing.T) {
for _, path := range paths {
_, err := fs.Source.Stat(path)
require.NoError(t, err)
c.Assert(err, qt.IsNil)
}
}
func TestDoNewSite(t *testing.T) {
c := qt.New(t)
n := newNewSiteCmd()
basepath := filepath.Join("base", "blog")
_, fs := newTestCfg()
require.NoError(t, n.doNewSite(fs, basepath, false))
c.Assert(n.doNewSite(fs, basepath, false), qt.IsNil)
checkNewSiteInited(fs, basepath, t)
}
func TestDoNewSite_noerror_base_exists_but_empty(t *testing.T) {
c := qt.New(t)
basepath := filepath.Join("base", "blog")
_, fs := newTestCfg()
n := newNewSiteCmd()
require.NoError(t, fs.Source.MkdirAll(basepath, 777))
c.Assert(fs.Source.MkdirAll(basepath, 0777), qt.IsNil)
require.NoError(t, n.doNewSite(fs, basepath, false))
c.Assert(n.doNewSite(fs, basepath, false), qt.IsNil)
}
func TestDoNewSite_error_base_exists(t *testing.T) {
c := qt.New(t)
basepath := filepath.Join("base", "blog")
_, fs := newTestCfg()
n := newNewSiteCmd()
require.NoError(t, fs.Source.MkdirAll(basepath, 777))
c.Assert(fs.Source.MkdirAll(basepath, 0777), qt.IsNil)
_, err := fs.Source.Create(filepath.Join(basepath, "foo"))
require.NoError(t, err)
c.Assert(err, qt.IsNil)
// Since the directory already exists and isn't empty, expect an error
require.Error(t, n.doNewSite(fs, basepath, false))
c.Assert(n.doNewSite(fs, basepath, false), qt.Not(qt.IsNil))
}
func TestDoNewSite_force_empty_dir(t *testing.T) {
c := qt.New(t)
basepath := filepath.Join("base", "blog")
_, fs := newTestCfg()
n := newNewSiteCmd()
require.NoError(t, fs.Source.MkdirAll(basepath, 777))
require.NoError(t, n.doNewSite(fs, basepath, true))
c.Assert(fs.Source.MkdirAll(basepath, 0777), qt.IsNil)
c.Assert(n.doNewSite(fs, basepath, true), qt.IsNil)
checkNewSiteInited(fs, basepath, t)
}
func TestDoNewSite_error_force_dir_inside_exists(t *testing.T) {
c := qt.New(t)
basepath := filepath.Join("base", "blog")
_, fs := newTestCfg()
n := newNewSiteCmd()
contentPath := filepath.Join(basepath, "content")
require.NoError(t, fs.Source.MkdirAll(contentPath, 777))
require.Error(t, n.doNewSite(fs, basepath, true))
c.Assert(fs.Source.MkdirAll(contentPath, 0777), qt.IsNil)
c.Assert(n.doNewSite(fs, basepath, true), qt.Not(qt.IsNil))
}
func TestDoNewSite_error_force_config_inside_exists(t *testing.T) {
c := qt.New(t)
basepath := filepath.Join("base", "blog")
_, fs := newTestCfg()
n := newNewSiteCmd()
configPath := filepath.Join(basepath, "config.toml")
require.NoError(t, fs.Source.MkdirAll(basepath, 777))
c.Assert(fs.Source.MkdirAll(basepath, 0777), qt.IsNil)
_, err := fs.Source.Create(configPath)
require.NoError(t, err)
c.Assert(err, qt.IsNil)
require.Error(t, n.doNewSite(fs, basepath, true))
c.Assert(n.doNewSite(fs, basepath, true), qt.Not(qt.IsNil))
}
func newTestCfg() (*viper.Viper, *hugofs.Fs) {
+7 -5
View File
@@ -16,10 +16,13 @@ package commands
import (
"bytes"
"errors"
"fmt"
"path/filepath"
"strings"
"github.com/gohugoio/hugo/parser/metadecoders"
_errors "github.com/pkg/errors"
"github.com/gohugoio/hugo/create"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
@@ -92,7 +95,7 @@ func (n *newSiteCmd) doNewSite(fs *hugofs.Fs, basepath string, force bool) error
for _, dir := range dirs {
if err := fs.Source.MkdirAll(dir, 0777); err != nil {
return fmt.Errorf("Failed to create dir: %s", err)
return _errors.Wrap(err, "Failed to create dir")
}
}
@@ -130,10 +133,9 @@ func createConfig(fs *hugofs.Fs, inpath string, kind string) (err error) {
"title": "My New Hugo Site",
"languageCode": "en-us",
}
kind = parser.FormatSanitize(kind)
var buf bytes.Buffer
err = parser.InterfaceToConfig(in, parser.FormatToLeadRune(kind), &buf)
err = parser.InterfaceToConfig(in, metadecoders.FormatFromString(kind), &buf)
if err != nil {
return err
}
@@ -147,7 +149,7 @@ func nextStepsText() string {
nextStepsText.WriteString(`Just a few more steps and you're ready to go:
1. Download a theme into the same-named folder.
Choose a theme from https://themes.gohugo.io/, or
Choose a theme from https://themes.gohugo.io/ or
create your own with the "hugo new theme <THEMENAME>" command.
2. Perhaps you want to add some content. You can add single files
with "hugo new `)
+1 -1
View File
@@ -63,7 +63,7 @@ func (n *newThemeCmd) newTheme(cmd *cobra.Command, args []string) error {
return newUserError("theme name needs to be provided")
}
createpath := c.hugo.PathSpec.AbsPathify(filepath.Join(c.Cfg.GetString("themesDir"), args[0]))
createpath := c.hugo().PathSpec.AbsPathify(filepath.Join(c.Cfg.GetString("themesDir"), args[0]))
jww.FEEDBACK.Println("Creating theme at", createpath)
cfg := c.DepsCfg
+93 -36
View File
@@ -1,4 +1,4 @@
// Copyright 2018 The Hugo Authors. All rights reserved.
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,6 +14,7 @@
package commands
import (
"bytes"
"fmt"
"net"
"net/http"
@@ -21,6 +22,7 @@ import (
"os"
"os/signal"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
@@ -28,10 +30,12 @@ import (
"syscall"
"time"
"github.com/pkg/errors"
"github.com/gohugoio/hugo/livereload"
"github.com/gohugoio/hugo/tpl"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
"github.com/spf13/afero"
"github.com/spf13/cobra"
@@ -52,7 +56,8 @@ type serverCmd struct {
serverWatch bool
noHTTPCache bool
disableFastRender bool
disableFastRender bool
disableBrowserError bool
*baseBuilderCmd
}
@@ -93,6 +98,7 @@ of a second, you will be able to save and see your changes nearly instantly.`,
cc.cmd.Flags().BoolVar(&cc.navigateToChanged, "navigateToChanged", false, "navigate to changed content file on live browser reload")
cc.cmd.Flags().BoolVar(&cc.renderToDisk, "renderToDisk", false, "render to Destination path (default is render to memory & serve from there)")
cc.cmd.Flags().BoolVar(&cc.disableFastRender, "disableFastRender", false, "enables full re-renders on changes")
cc.cmd.Flags().BoolVar(&cc.disableBrowserError, "disableBrowserError", false, "do not show build errors in the browser")
cc.cmd.Flags().String("memstats", "", "log memory usage to this file")
cc.cmd.Flags().String("meminterval", "100ms", "interval to poll memory usage (requires --memstats), valid time units are \"ns\", \"us\" (or \"µs\"), \"ms\", \"s\", \"m\", \"h\".")
@@ -142,6 +148,9 @@ func (sc *serverCmd) server(cmd *cobra.Command, args []string) error {
if cmd.Flags().Changed("disableFastRender") {
c.Set("disableFastRender", sc.disableFastRender)
}
if cmd.Flags().Changed("disableBrowserError") {
c.Set("disableBrowserError", sc.disableBrowserError)
}
if sc.serverWatch {
c.Set("watch", true)
}
@@ -176,7 +185,7 @@ func (sc *serverCmd) server(cmd *cobra.Command, args []string) error {
// port set explicitly by user -- he/she probably meant it!
err = newSystemErrorF("Server startup failed: %s", err)
}
jww.ERROR.Println("port", sc.serverPort, "already in use, attempting to use an available port")
c.logger.FEEDBACK.Println("port", sc.serverPort, "already in use, attempting to use an available port")
sp, err := helpers.FindAvailablePort()
if err != nil {
err = newSystemError("Unable to find alternative port to use:", err)
@@ -223,7 +232,7 @@ func (sc *serverCmd) server(cmd *cobra.Command, args []string) error {
}
if err := memStats(); err != nil {
jww.ERROR.Println("memstats error:", err)
jww.WARN.Println("memstats error:", err)
}
c, err := initializeConfig(true, true, &sc.hugoBuilderCommon, sc, cfgInit)
@@ -235,7 +244,7 @@ func (sc *serverCmd) server(cmd *cobra.Command, args []string) error {
return err
}
for _, s := range c.hugo.Sites {
for _, s := range c.hugo().Sites {
s.RegisterMediaTypes()
}
@@ -247,15 +256,11 @@ func (sc *serverCmd) server(cmd *cobra.Command, args []string) error {
return err
}
baseWatchDir := c.Cfg.GetString("workingDir")
relWatchDirs := make([]string, len(watchDirs))
for i, dir := range watchDirs {
relWatchDirs[i], _ = helpers.GetRelativePath(dir, baseWatchDir)
watchGroups := helpers.ExtractAndGroupRootPaths(watchDirs)
for _, group := range watchGroups {
jww.FEEDBACK.Printf("Watching for changes in %s\n", group)
}
rootWatchDirs := strings.Join(helpers.UniqueStrings(helpers.ExtractRootPaths(relWatchDirs)), ",")
jww.FEEDBACK.Printf("Watching for changes in %s%s{%s}\n", baseWatchDir, helpers.FilePathSeparator, rootWatchDirs)
watcher, err := c.newWatcher(watchDirs...)
if err != nil {
@@ -270,11 +275,21 @@ func (sc *serverCmd) server(cmd *cobra.Command, args []string) error {
}
func getRootWatchDirsStr(baseDir string, watchDirs []string) string {
relWatchDirs := make([]string, len(watchDirs))
for i, dir := range watchDirs {
relWatchDirs[i], _ = helpers.GetRelativePath(dir, baseDir)
}
return strings.Join(helpers.UniqueStringsSorted(helpers.ExtractRootPaths(relWatchDirs)), ",")
}
type fileServer struct {
baseURLs []string
roots []string
c *commandeer
s *serverCmd
baseURLs []string
roots []string
errorTemplate tpl.Template
c *commandeer
s *serverCmd
}
func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, error) {
@@ -288,7 +303,9 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, erro
publishDir = filepath.Join(publishDir, root)
}
absPublishDir := f.c.hugo.PathSpec.AbsPathify(publishDir)
absPublishDir := f.c.hugo().PathSpec.AbsPathify(publishDir)
jww.FEEDBACK.Printf("Environment: %q", f.c.hugo().Deps.Site.Hugo().Environment)
if i == 0 {
if f.s.renderToDisk {
@@ -301,30 +318,59 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, erro
httpFs := afero.NewHttpFs(f.c.destinationFs)
fs := filesOnlyFs{httpFs.Dir(absPublishDir)}
doLiveReload := !f.s.buildWatch && !f.c.Cfg.GetBool("disableLiveReload")
fastRenderMode := doLiveReload && !f.c.Cfg.GetBool("disableFastRender")
if i == 0 && fastRenderMode {
if i == 0 && f.c.fastRenderMode {
jww.FEEDBACK.Println("Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender")
}
// We're only interested in the path
u, err := url.Parse(baseURL)
if err != nil {
return nil, "", "", fmt.Errorf("Invalid baseURL: %s", err)
return nil, "", "", errors.Wrap(err, "Invalid baseURL")
}
decorate := func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if f.c.showErrorInBrowser {
// First check the error state
err := f.c.getErrorWithContext()
if err != nil {
w.WriteHeader(500)
var b bytes.Buffer
err := f.errorTemplate.Execute(&b, err)
if err != nil {
f.c.logger.ERROR.Println(err)
}
port = 1313
if !f.c.paused {
port = f.c.Cfg.GetInt("liveReloadPort")
}
fmt.Fprint(w, injectLiveReloadScript(&b, port))
return
}
}
if f.s.noHTTPCache {
w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0")
w.Header().Set("Pragma", "no-cache")
}
if fastRenderMode {
if f.c.fastRenderMode && f.c.buildErr == nil {
p := r.RequestURI
if strings.HasSuffix(p, "/") || strings.HasSuffix(p, "html") || strings.HasSuffix(p, "htm") {
if !f.c.visitedURLs.Contains(p) {
// If not already on stack, re-render that single page.
if err := f.c.partialReRender(p); err != nil {
f.c.handleBuildErr(err, fmt.Sprintf("Failed to render %q", p))
if f.c.showErrorInBrowser {
http.Redirect(w, r, p, http.StatusMovedPermanently)
return
}
}
}
f.c.visitedURLs.Add(p)
}
}
h.ServeHTTP(w, r)
@@ -345,9 +391,14 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, erro
return mu, u.String(), endpoint, nil
}
var logErrorRe = regexp.MustCompile(`(?s)ERROR \d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} `)
func removeErrorPrefixFromLog(content string) string {
return logErrorRe.ReplaceAllLiteralString(content, "")
}
func (c *commandeer) serve(s *serverCmd) error {
isMultiHost := c.hugo.IsMultihost()
isMultiHost := c.hugo().IsMultihost()
var (
baseURLs []string
@@ -355,21 +406,27 @@ func (c *commandeer) serve(s *serverCmd) error {
)
if isMultiHost {
for _, s := range c.hugo.Sites {
for _, s := range c.hugo().Sites {
baseURLs = append(baseURLs, s.BaseURL.String())
roots = append(roots, s.Language.Lang)
roots = append(roots, s.Language().Lang)
}
} else {
s := c.hugo.Sites[0]
s := c.hugo().Sites[0]
baseURLs = []string{s.BaseURL.String()}
roots = []string{""}
}
templ, err := c.hugo().TextTmpl.Parse("__default_server_error", buildErrorTemplate)
if err != nil {
return err
}
srv := &fileServer{
baseURLs: baseURLs,
roots: roots,
c: c,
s: s,
baseURLs: baseURLs,
roots: roots,
c: c,
s: s,
errorTemplate: templ,
}
doLiveReload := !c.Cfg.GetBool("disableLiveReload")
@@ -378,7 +435,7 @@ func (c *commandeer) serve(s *serverCmd) error {
livereload.Initialize()
}
var sigs = make(chan os.Signal)
var sigs = make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
for i := range baseURLs {
@@ -392,7 +449,7 @@ func (c *commandeer) serve(s *serverCmd) error {
go func() {
err = http.ListenAndServe(endpoint, mu)
if err != nil {
jww.ERROR.Printf("Error: %s\n", err.Error())
c.logger.ERROR.Printf("Error: %s\n", err.Error())
os.Exit(1)
}
}()
@@ -453,7 +510,7 @@ func (sc *serverCmd) fixURL(cfg config.Provider, s string, port int) (string, er
if strings.Contains(u.Host, ":") {
u.Host, _, err = net.SplitHostPort(u.Host)
if err != nil {
return "", fmt.Errorf("Failed to split baseURL hostpost: %s", err)
return "", errors.Wrap(err, "Failed to split baseURL hostpost")
}
}
u.Host += fmt.Sprintf(":%d", port)
+95
View File
@@ -0,0 +1,95 @@
// Copyright 2018 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
import (
"bytes"
"io"
"github.com/gohugoio/hugo/transform"
"github.com/gohugoio/hugo/transform/livereloadinject"
)
var buildErrorTemplate = `<!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8">
<title>Hugo Server: Error</title>
<style type="text/css">
body {
font-family: "Muli",avenir, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-size: 16px;
background-color: black;
color: rgba(255, 255, 255, 0.9);
}
main {
margin: auto;
width: 95%;
padding: 1rem;
}
.version {
color: #ccc;
padding: 1rem 0;
}
.stack {
margin-top: 6rem;
}
pre {
white-space: pre-wrap;
white-space: -moz-pre-wrap;
white-space: -pre-wrap;
white-space: -o-pre-wrap;
word-wrap: break-word;
}
.highlight {
overflow-x: auto;
padding: 0.75rem;
margin-bottom: 1rem;
background-color: #272822;
border: 1px solid black;
}
a {
color: #0594cb;
text-decoration: none;
}
a:hover {
color: #ccc;
}
</style>
</head>
<body>
<main>
{{ highlight .Error "apl" "noclasses=true,style=monokai" }}
{{ with .File }}
{{ $params := printf "noclasses=true,style=monokai,linenos=table,hl_lines=%d,linenostart=%d" (add .LinesPos 1) (sub .Position.LineNumber .LinesPos) }}
{{ $lexer := .ChromaLexer | default "go-html-template" }}
{{ highlight (delimit .Lines "\n") $lexer $params }}
{{ end }}
{{ with .StackTrace }}
{{ highlight . "apl" "noclasses=true,style=monokai" }}
{{ end }}
<p class="version">{{ .Version }}</p>
<a href="">Reload Page</a>
</main>
</body>
</html>
`
func injectLiveReloadScript(src io.Reader, port int) string {
var b bytes.Buffer
chain := transform.Chain{livereloadinject.New(port)}
chain.Apply(&b, src)
return b.String()
}
+38 -22
View File
@@ -18,13 +18,14 @@ import (
"net/http"
"os"
"runtime"
"strings"
"testing"
"time"
"github.com/gohugoio/hugo/helpers"
qt "github.com/frankban/quicktest"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
)
func TestServer(t *testing.T) {
@@ -32,9 +33,9 @@ func TestServer(t *testing.T) {
// TODO(bep) not sure why server tests have started to fail on the Windows CI server.
t.Skip("Skip server test on appveyor")
}
assert := require.New(t)
dir, err := createSimpleTestSite(t)
assert.NoError(err)
c := qt.New(t)
dir, err := createSimpleTestSite(t, testSiteConfig{})
c.Assert(err, qt.IsNil)
// Let us hope that this port is available on all systems ...
port := 1331
@@ -53,7 +54,7 @@ func TestServer(t *testing.T) {
go func() {
_, err = cmd.ExecuteC()
assert.NoError(err)
c.Assert(err, qt.IsNil)
}()
// There is no way to know exactly when the server is ready for connections.
@@ -62,11 +63,12 @@ func TestServer(t *testing.T) {
time.Sleep(2 * time.Second)
resp, err := http.Get("http://localhost:1331/")
assert.NoError(err)
c.Assert(err, qt.IsNil)
defer resp.Body.Close()
homeContent := helpers.ReaderToString(resp.Body)
assert.Contains(homeContent, "List: Hugo Commands")
c.Assert(homeContent, qt.Contains, "List: Hugo Commands")
c.Assert(homeContent, qt.Contains, "Environment: development")
// Stop the server.
stop <- true
@@ -95,24 +97,38 @@ func TestFixURL(t *testing.T) {
{"No config", "", "", true, 1313, "//localhost:1313/"},
}
for i, test := range tests {
b := newCommandsBuilder()
s := b.newServerCmd()
v := viper.New()
baseURL := test.CLIBaseURL
v.Set("baseURL", test.CfgBaseURL)
s.serverAppend = test.AppendPort
s.serverPort = test.Port
result, err := s.fixURL(v, baseURL, s.serverPort)
if err != nil {
t.Errorf("Test #%d %s: unexpected error %s", i, test.TestName, err)
}
if result != test.Result {
t.Errorf("Test #%d %s: expected %q, got %q", i, test.TestName, test.Result, result)
}
for _, test := range tests {
t.Run(test.TestName, func(t *testing.T) {
b := newCommandsBuilder()
s := b.newServerCmd()
v := viper.New()
baseURL := test.CLIBaseURL
v.Set("baseURL", test.CfgBaseURL)
s.serverAppend = test.AppendPort
s.serverPort = test.Port
result, err := s.fixURL(v, baseURL, s.serverPort)
if err != nil {
t.Errorf("Unexpected error %s", err)
}
if result != test.Result {
t.Errorf("Expected %q, got %q", test.Result, result)
}
})
}
}
func TestRemoveErrorPrefixFromLog(t *testing.T) {
c := qt.New(t)
content := `ERROR 2018/10/07 13:11:12 Error while rendering "home": template: _default/baseof.html:4:3: executing "main" at <partial "logo" .>: error calling partial: template: partials/logo.html:5:84: executing "partials/logo.html" at <$resized.AHeight>: can't evaluate field AHeight in type *resource.Image
ERROR 2018/10/07 13:11:12 Rebuild failed: logged 1 error(s)
`
withoutError := removeErrorPrefixFromLog(content)
c.Assert(strings.Contains(withoutError, "ERROR"), qt.Equals, false)
}
func isWindowsCI() bool {
return runtime.GOOS == "windows" && os.Getenv("CI") != ""
}
+7 -5
View File
@@ -33,14 +33,14 @@ func newStaticSyncer(c *commandeer) (*staticSyncer, error) {
}
func (s *staticSyncer) isStatic(filename string) bool {
return s.c.hugo.BaseFs.SourceFilesystems.IsStatic(filename)
return s.c.hugo().BaseFs.SourceFilesystems.IsStatic(filename)
}
func (s *staticSyncer) syncsStaticEvents(staticEvents []fsnotify.Event) error {
c := s.c
syncFn := func(sourceFs *filesystems.SourceFilesystem) (uint64, error) {
publishDir := c.hugo.PathSpec.PublishDir
publishDir := c.hugo().PathSpec.PublishDir
// If root, remove the second '/'
if publishDir == "//" {
publishDir = helpers.FilePathSeparator
@@ -53,6 +53,7 @@ func (s *staticSyncer) syncsStaticEvents(staticEvents []fsnotify.Event) error {
syncer := fsync.NewSyncer()
syncer.NoTimes = c.Cfg.GetBool("noTimes")
syncer.NoChmod = c.Cfg.GetBool("noChmod")
syncer.ChmodFilter = chmodFilter
syncer.SrcFs = sourceFs.Fs
syncer.DestFs = c.Fs.Destination
@@ -79,6 +80,7 @@ func (s *staticSyncer) syncsStaticEvents(staticEvents []fsnotify.Event) error {
fromPath := ev.Name
relPath := sourceFs.MakePathRelative(fromPath)
if relPath == "" {
// Not member of this virtual host.
continue
@@ -105,10 +107,10 @@ func (s *staticSyncer) syncsStaticEvents(staticEvents []fsnotify.Event) error {
logger.Println("Syncing", relPath, "to", publishDir)
if err := syncer.Sync(filepath.Join(publishDir, relPath), relPath); err != nil {
c.Logger.ERROR.Println(err)
c.logger.ERROR.Println(err)
}
} else {
c.Logger.ERROR.Println(err)
c.logger.ERROR.Println(err)
}
continue
@@ -117,7 +119,7 @@ func (s *staticSyncer) syncsStaticEvents(staticEvents []fsnotify.Event) error {
// For all other event operations Hugo will sync static.
logger.Println("Syncing", relPath, "to", publishDir)
if err := syncer.Sync(filepath.Join(publishDir, relPath), relPath); err != nil {
c.Logger.ERROR.Println(err)
c.logger.ERROR.Println(err)
}
}
+2 -26
View File
@@ -14,12 +14,7 @@
package commands
import (
"runtime"
"strings"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugolib"
"github.com/gohugoio/hugo/resource/tocss/scss"
"github.com/gohugoio/hugo/common/hugo"
"github.com/spf13/cobra"
jww "github.com/spf13/jwalterweatherman"
)
@@ -45,24 +40,5 @@ func newVersionCmd() *versionCmd {
}
func printHugoVersion() {
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)
jww.FEEDBACK.Println(hugo.BuildVersionString())
}
+112
View File
@@ -0,0 +1,112 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package collections
import (
"fmt"
"reflect"
)
// Append appends from to a slice to and returns the resulting slice.
// If length of from is one and the only element is a slice of same type as to,
// it will be appended.
func Append(to interface{}, from ...interface{}) (interface{}, error) {
tov, toIsNil := indirect(reflect.ValueOf(to))
toIsNil = toIsNil || to == nil
var tot reflect.Type
if !toIsNil {
if tov.Kind() != reflect.Slice {
return nil, fmt.Errorf("expected a slice, got %T", to)
}
tot = tov.Type().Elem()
toIsNil = tov.Len() == 0
if len(from) == 1 {
fromv := reflect.ValueOf(from[0])
if fromv.Kind() == reflect.Slice {
if toIsNil {
// If we get nil []string, we just return the []string
return from[0], nil
}
fromt := reflect.TypeOf(from[0]).Elem()
// If we get []string []string, we append the from slice to to
if tot == fromt {
return reflect.AppendSlice(tov, fromv).Interface(), nil
} else if !fromt.AssignableTo(tot) {
// Fall back to a []interface{} slice.
return appendToInterfaceSliceFromValues(tov, fromv)
}
}
}
}
if toIsNil {
return Slice(from...), nil
}
for _, f := range from {
fv := reflect.ValueOf(f)
if !fv.Type().AssignableTo(tot) {
// Fall back to a []interface{} slice.
return appendToInterfaceSlice(tov, from...)
}
tov = reflect.Append(tov, fv)
}
return tov.Interface(), nil
}
func appendToInterfaceSliceFromValues(slice1, slice2 reflect.Value) ([]interface{}, error) {
var tos []interface{}
for _, slice := range []reflect.Value{slice1, slice2} {
for i := 0; i < slice.Len(); i++ {
tos = append(tos, slice.Index(i).Interface())
}
}
return tos, nil
}
func appendToInterfaceSlice(tov reflect.Value, from ...interface{}) ([]interface{}, error) {
var tos []interface{}
for i := 0; i < tov.Len(); i++ {
tos = append(tos, tov.Index(i).Interface())
}
tos = append(tos, from...)
return tos, nil
}
// indirect is borrowed from the Go stdlib: 'text/template/exec.go'
// TODO(bep) consolidate
func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() {
if v.IsNil() {
return v, true
}
if v.Kind() == reflect.Interface && v.NumMethod() > 0 {
break
}
}
return v, false
}
+73
View File
@@ -0,0 +1,73 @@
// 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 collections
import (
"testing"
qt "github.com/frankban/quicktest"
)
func TestAppend(t *testing.T) {
t.Parallel()
c := qt.New(t)
for _, test := range []struct {
start interface{}
addend []interface{}
expected interface{}
}{
{[]string{"a", "b"}, []interface{}{"c"}, []string{"a", "b", "c"}},
{[]string{"a", "b"}, []interface{}{"c", "d", "e"}, []string{"a", "b", "c", "d", "e"}},
{[]string{"a", "b"}, []interface{}{[]string{"c", "d", "e"}}, []string{"a", "b", "c", "d", "e"}},
{nil, []interface{}{"a", "b"}, []string{"a", "b"}},
{nil, []interface{}{nil}, []interface{}{nil}},
{[]interface{}{}, []interface{}{[]string{"c", "d", "e"}}, []string{"c", "d", "e"}},
{tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}},
[]interface{}{&tstSlicer{"c"}},
tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}, &tstSlicer{"c"}}},
{&tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}},
[]interface{}{&tstSlicer{"c"}},
tstSlicers{&tstSlicer{"a"},
&tstSlicer{"b"},
&tstSlicer{"c"}}},
{testSlicerInterfaces{&tstSlicerIn1{"a"}, &tstSlicerIn1{"b"}},
[]interface{}{&tstSlicerIn1{"c"}},
testSlicerInterfaces{&tstSlicerIn1{"a"}, &tstSlicerIn1{"b"}, &tstSlicerIn1{"c"}}},
//https://github.com/gohugoio/hugo/issues/5361
{[]string{"a", "b"}, []interface{}{tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}}},
[]interface{}{"a", "b", &tstSlicer{"a"}, &tstSlicer{"b"}}},
{[]string{"a", "b"}, []interface{}{&tstSlicer{"a"}},
[]interface{}{"a", "b", &tstSlicer{"a"}}},
// Errors
{"", []interface{}{[]string{"a", "b"}}, false},
// No string concatenation.
{"ab",
[]interface{}{"c"},
false},
} {
result, err := Append(test.start, test.addend...)
if b, ok := test.expected.(bool); ok && !b {
c.Assert(err, qt.Not(qt.IsNil))
continue
}
c.Assert(err, qt.IsNil)
c.Assert(result, qt.DeepEquals, test.expected)
}
}
-7
View File
@@ -19,10 +19,3 @@ package collections
type Grouper interface {
Group(key interface{}, items interface{}) (interface{}, error)
}
// Slicer definse a very generic way to create a typed slice. This is used
// in collections.Slice template func to get types such as Pages, PageGroups etc.
// instead of the less useful []interface{}.
type Slicer interface {
Slice(items interface{}) (interface{}, error)
}
+66
View File
@@ -0,0 +1,66 @@
// 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 collections
import (
"reflect"
)
// Slicer defines a very generic way to create a typed slice. This is used
// in collections.Slice template func to get types such as Pages, PageGroups etc.
// instead of the less useful []interface{}.
type Slicer interface {
Slice(items interface{}) (interface{}, error)
}
// Slice returns a slice of all passed arguments.
func Slice(args ...interface{}) interface{} {
if len(args) == 0 {
return args
}
first := args[0]
firstType := reflect.TypeOf(first)
if firstType == nil {
return args
}
if g, ok := first.(Slicer); ok {
v, err := g.Slice(args)
if err == nil {
return v
}
// If Slice fails, the items are not of the same type and
// []interface{} is the best we can do.
return args
}
if len(args) > 1 {
// This can be a mix of types.
for i := 1; i < len(args); i++ {
if firstType != reflect.TypeOf(args[i]) {
// []interface{} is the best we can do
return args
}
}
}
slice := reflect.MakeSlice(reflect.SliceOf(firstType), len(args), len(args))
for i, arg := range args {
slice.Index(i).Set(reflect.ValueOf(arg))
}
return slice.Interface()
}
+124
View File
@@ -0,0 +1,124 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package collections
import (
"errors"
"testing"
qt "github.com/frankban/quicktest"
)
var _ Slicer = (*tstSlicer)(nil)
var _ Slicer = (*tstSlicerIn1)(nil)
var _ Slicer = (*tstSlicerIn2)(nil)
var _ testSlicerInterface = (*tstSlicerIn1)(nil)
var _ testSlicerInterface = (*tstSlicerIn1)(nil)
type testSlicerInterface interface {
Name() string
}
type testSlicerInterfaces []testSlicerInterface
type tstSlicerIn1 struct {
TheName string
}
type tstSlicerIn2 struct {
TheName string
}
type tstSlicer struct {
TheName string
}
func (p *tstSlicerIn1) Slice(in interface{}) (interface{}, error) {
items := in.([]interface{})
result := make(testSlicerInterfaces, len(items))
for i, v := range items {
switch vv := v.(type) {
case testSlicerInterface:
result[i] = vv
default:
return nil, errors.New("invalid type")
}
}
return result, nil
}
func (p *tstSlicerIn2) Slice(in interface{}) (interface{}, error) {
items := in.([]interface{})
result := make(testSlicerInterfaces, len(items))
for i, v := range items {
switch vv := v.(type) {
case testSlicerInterface:
result[i] = vv
default:
return nil, errors.New("invalid type")
}
}
return result, nil
}
func (p *tstSlicerIn1) Name() string {
return p.TheName
}
func (p *tstSlicerIn2) Name() string {
return p.TheName
}
func (p *tstSlicer) Slice(in interface{}) (interface{}, error) {
items := in.([]interface{})
result := make(tstSlicers, len(items))
for i, v := range items {
switch vv := v.(type) {
case *tstSlicer:
result[i] = vv
default:
return nil, errors.New("invalid type")
}
}
return result, nil
}
type tstSlicers []*tstSlicer
func TestSlice(t *testing.T) {
t.Parallel()
c := qt.New(t)
for i, test := range []struct {
args []interface{}
expected interface{}
}{
{[]interface{}{"a", "b"}, []string{"a", "b"}},
{[]interface{}{&tstSlicer{"a"}, &tstSlicer{"b"}}, tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}}},
{[]interface{}{&tstSlicer{"a"}, "b"}, []interface{}{&tstSlicer{"a"}, "b"}},
{[]interface{}{}, []interface{}{}},
{[]interface{}{nil}, []interface{}{nil}},
{[]interface{}{5, "b"}, []interface{}{5, "b"}},
{[]interface{}{&tstSlicerIn1{"a"}, &tstSlicerIn2{"b"}}, testSlicerInterfaces{&tstSlicerIn1{"a"}, &tstSlicerIn2{"b"}}},
{[]interface{}{&tstSlicerIn1{"a"}, &tstSlicer{"b"}}, []interface{}{&tstSlicerIn1{"a"}, &tstSlicer{"b"}}},
} {
errMsg := qt.Commentf("[%d] %v", i, test.args)
result := Slice(test.args...)
c.Assert(test.expected, qt.DeepEquals, result, errMsg)
}
}
+255
View File
@@ -0,0 +1,255 @@
// 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 herrors
import (
"io"
"io/ioutil"
"path/filepath"
"strings"
"github.com/gohugoio/hugo/common/text"
"github.com/spf13/afero"
)
// LineMatcher contains the elements used to match an error to a line
type LineMatcher struct {
Position text.Position
Error error
LineNumber int
Offset int
Line string
}
// LineMatcherFn is used to match a line with an error.
type LineMatcherFn func(m LineMatcher) bool
// SimpleLineMatcher simply matches by line number.
var SimpleLineMatcher = func(m LineMatcher) bool {
return m.Position.LineNumber == m.LineNumber
}
var _ text.Positioner = ErrorContext{}
// ErrorContext contains contextual information about an error. This will
// typically be the lines surrounding some problem in a file.
type ErrorContext struct {
// If a match will contain the matched line and up to 2 lines before and after.
// Will be empty if no match.
Lines []string
// The position of the error in the Lines above. 0 based.
LinesPos int
position text.Position
// The lexer to use for syntax highlighting.
// https://gohugo.io/content-management/syntax-highlighting/#list-of-chroma-highlighting-languages
ChromaLexer string
}
// Position returns the text position of this error.
func (e ErrorContext) Position() text.Position {
return e.position
}
var _ causer = (*ErrorWithFileContext)(nil)
// ErrorWithFileContext is an error with some additional file context related
// to that error.
type ErrorWithFileContext struct {
cause error
ErrorContext
}
func (e *ErrorWithFileContext) Error() string {
pos := e.Position()
if pos.IsValid() {
return pos.String() + ": " + e.cause.Error()
}
return e.cause.Error()
}
func (e *ErrorWithFileContext) Cause() error {
return e.cause
}
// WithFileContextForFile will try to add a file context with lines matching the given matcher.
// If no match could be found, the original error is returned with false as the second return value.
func WithFileContextForFile(e error, realFilename, filename string, fs afero.Fs, matcher LineMatcherFn) (error, bool) {
f, err := fs.Open(filename)
if err != nil {
return e, false
}
defer f.Close()
return WithFileContext(e, realFilename, f, matcher)
}
// WithFileContextForFile will try to add a file context with lines matching the given matcher.
// If no match could be found, the original error is returned with false as the second return value.
func WithFileContext(e error, realFilename string, r io.Reader, matcher LineMatcherFn) (error, bool) {
if e == nil {
panic("error missing")
}
le := UnwrapFileError(e)
if le == nil {
var ok bool
if le, ok = ToFileError("", e).(FileError); !ok {
return e, false
}
}
var errCtx ErrorContext
posle := le.Position()
if posle.Offset != -1 {
errCtx = locateError(r, le, func(m LineMatcher) bool {
if posle.Offset >= m.Offset && posle.Offset < m.Offset+len(m.Line) {
lno := posle.LineNumber - m.Position.LineNumber + m.LineNumber
m.Position = text.Position{LineNumber: lno}
}
return matcher(m)
})
} else {
errCtx = locateError(r, le, matcher)
}
pos := &errCtx.position
if pos.LineNumber == -1 {
return e, false
}
pos.Filename = realFilename
if le.Type() != "" {
errCtx.ChromaLexer = chromaLexerFromType(le.Type())
} else {
errCtx.ChromaLexer = chromaLexerFromFilename(realFilename)
}
return &ErrorWithFileContext{cause: e, ErrorContext: errCtx}, true
}
// UnwrapErrorWithFileContext tries to unwrap an ErrorWithFileContext from err.
// It returns nil if this is not possible.
func UnwrapErrorWithFileContext(err error) *ErrorWithFileContext {
for err != nil {
switch v := err.(type) {
case *ErrorWithFileContext:
return v
case causer:
err = v.Cause()
default:
return nil
}
}
return nil
}
func chromaLexerFromType(fileType string) string {
switch fileType {
case "html", "htm":
return "go-html-template"
}
return fileType
}
func extNoDelimiter(filename string) string {
return strings.TrimPrefix(filepath.Ext(filename), ".")
}
func chromaLexerFromFilename(filename string) string {
if strings.Contains(filename, "layouts") {
return "go-html-template"
}
ext := extNoDelimiter(filename)
return chromaLexerFromType(ext)
}
func locateErrorInString(src string, matcher LineMatcherFn) ErrorContext {
return locateError(strings.NewReader(src), &fileError{}, matcher)
}
func locateError(r io.Reader, le FileError, matches LineMatcherFn) ErrorContext {
if le == nil {
panic("must provide an error")
}
errCtx := ErrorContext{position: text.Position{LineNumber: -1, ColumnNumber: 1, Offset: -1}, LinesPos: -1}
b, err := ioutil.ReadAll(r)
if err != nil {
return errCtx
}
pos := &errCtx.position
lepos := le.Position()
lines := strings.Split(string(b), "\n")
if lepos.ColumnNumber >= 0 {
pos.ColumnNumber = lepos.ColumnNumber
}
lineNo := 0
posBytes := 0
for li, line := range lines {
lineNo = li + 1
m := LineMatcher{
Position: le.Position(),
Error: le,
LineNumber: lineNo,
Offset: posBytes,
Line: line,
}
if errCtx.LinesPos == -1 && matches(m) {
pos.LineNumber = lineNo
break
}
posBytes += len(line)
}
if pos.LineNumber != -1 {
low := pos.LineNumber - 3
if low < 0 {
low = 0
}
if pos.LineNumber > 2 {
errCtx.LinesPos = 2
} else {
errCtx.LinesPos = pos.LineNumber - 1
}
high := pos.LineNumber + 2
if high > len(lines) {
high = len(lines)
}
errCtx.Lines = lines[low:high]
}
return errCtx
}
+129
View File
@@ -0,0 +1,129 @@
// 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 herrors
import (
"strings"
"testing"
qt "github.com/frankban/quicktest"
)
func TestErrorLocator(t *testing.T) {
c := qt.New(t)
lineMatcher := func(m LineMatcher) bool {
return strings.Contains(m.Line, "THEONE")
}
lines := `LINE 1
LINE 2
LINE 3
LINE 4
This is THEONE
LINE 6
LINE 7
LINE 8
`
location := locateErrorInString(lines, lineMatcher)
c.Assert(location.Lines, qt.DeepEquals, []string{"LINE 3", "LINE 4", "This is THEONE", "LINE 6", "LINE 7"})
pos := location.Position()
c.Assert(pos.LineNumber, qt.Equals, 5)
c.Assert(location.LinesPos, qt.Equals, 2)
c.Assert(locateErrorInString(`This is THEONE`, lineMatcher).Lines, qt.DeepEquals, []string{"This is THEONE"})
location = locateErrorInString(`L1
This is THEONE
L2
`, lineMatcher)
c.Assert(location.Position().LineNumber, qt.Equals, 2)
c.Assert(location.LinesPos, qt.Equals, 1)
c.Assert(location.Lines, qt.DeepEquals, []string{"L1", "This is THEONE", "L2", ""})
location = locateErrorInString(`This is THEONE
L2
`, lineMatcher)
c.Assert(location.LinesPos, qt.Equals, 0)
c.Assert(location.Lines, qt.DeepEquals, []string{"This is THEONE", "L2", ""})
location = locateErrorInString(`L1
This THEONE
`, lineMatcher)
c.Assert(location.Lines, qt.DeepEquals, []string{"L1", "This THEONE", ""})
c.Assert(location.LinesPos, qt.Equals, 1)
location = locateErrorInString(`L1
L2
This THEONE
`, lineMatcher)
c.Assert(location.Lines, qt.DeepEquals, []string{"L1", "L2", "This THEONE", ""})
c.Assert(location.LinesPos, qt.Equals, 2)
location = locateErrorInString("NO MATCH", lineMatcher)
c.Assert(location.Position().LineNumber, qt.Equals, -1)
c.Assert(location.LinesPos, qt.Equals, -1)
c.Assert(len(location.Lines), qt.Equals, 0)
lineMatcher = func(m LineMatcher) bool {
return m.LineNumber == 6
}
location = locateErrorInString(`A
B
C
D
E
F
G
H
I
J`, lineMatcher)
c.Assert(location.Lines, qt.DeepEquals, []string{"D", "E", "F", "G", "H"})
c.Assert(location.Position().LineNumber, qt.Equals, 6)
c.Assert(location.LinesPos, qt.Equals, 2)
// Test match EOF
lineMatcher = func(m LineMatcher) bool {
return m.LineNumber == 4
}
location = locateErrorInString(`A
B
C
`, lineMatcher)
c.Assert(location.Lines, qt.DeepEquals, []string{"B", "C", ""})
c.Assert(location.Position().LineNumber, qt.Equals, 4)
c.Assert(location.LinesPos, qt.Equals, 2)
offsetMatcher := func(m LineMatcher) bool {
return m.Offset == 1
}
location = locateErrorInString(`A
B
C
D
E`, offsetMatcher)
c.Assert(location.Lines, qt.DeepEquals, []string{"A", "B", "C", "D"})
c.Assert(location.Position().LineNumber, qt.Equals, 2)
c.Assert(location.LinesPos, qt.Equals, 1)
}
+65
View File
@@ -0,0 +1,65 @@
// 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 herrors contains common Hugo errors and error related utilities.
package herrors
import (
"errors"
"fmt"
"io"
"os"
"runtime/debug"
_errors "github.com/pkg/errors"
)
// As defined in https://godoc.org/github.com/pkg/errors
type causer interface {
Cause() error
}
type stackTracer interface {
StackTrace() _errors.StackTrace
}
// PrintStackTrace prints the error's stack trace to stdoud.
func PrintStackTrace(err error) {
FprintStackTrace(os.Stdout, err)
}
// FprintStackTrace prints the error's stack trace to w.
func FprintStackTrace(w io.Writer, err error) {
if err, ok := err.(stackTracer); ok {
for _, f := range err.StackTrace() {
fmt.Fprintf(w, "%+s:%d\n", f, f)
}
}
}
// Recover is a helper function that can be used to capture panics.
// Put this at the top of a method/function that crashes in a template:
// defer herrors.Recover()
func Recover(args ...interface{}) {
if r := recover(); r != nil {
args = append(args, "stacktrace from panic: \n"+string(debug.Stack()), "\n")
fmt.Println(args...)
}
}
// ErrFeatureNotAvailable denotes that a feature is unavailable.
//
// We will, at least to begin with, make some Hugo features (SCSS with libsass) optional,
// and this error is used to signal those situations.
var ErrFeatureNotAvailable = errors.New("this feature is not available in your current Hugo version, see https://goo.gl/YMrWcn for more information")
+133
View File
@@ -0,0 +1,133 @@
// 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
// limitatio ns under the License.
package herrors
import (
"encoding/json"
"github.com/gohugoio/hugo/common/text"
"github.com/pkg/errors"
)
var (
_ causer = (*fileError)(nil)
)
// FileError represents an error when handling a file: Parsing a config file,
// execute a template etc.
type FileError interface {
error
text.Positioner
// A string identifying the type of file, e.g. JSON, TOML, markdown etc.
Type() string
}
var _ FileError = (*fileError)(nil)
type fileError struct {
position text.Position
fileType string
cause error
}
// Position returns the text position of this error.
func (e fileError) Position() text.Position {
return e.position
}
func (e *fileError) Type() string {
return e.fileType
}
func (e *fileError) Error() string {
if e.cause == nil {
return ""
}
return e.cause.Error()
}
func (f *fileError) Cause() error {
return f.cause
}
// NewFileError creates a new FileError.
func NewFileError(fileType string, offset, lineNumber, columnNumber int, err error) FileError {
pos := text.Position{Offset: offset, LineNumber: lineNumber, ColumnNumber: columnNumber}
return &fileError{cause: err, fileType: fileType, position: pos}
}
// UnwrapFileError tries to unwrap a FileError from err.
// It returns nil if this is not possible.
func UnwrapFileError(err error) FileError {
for err != nil {
switch v := err.(type) {
case FileError:
return v
case causer:
err = v.Cause()
default:
return nil
}
}
return nil
}
// ToFileErrorWithOffset will return a new FileError with a line number
// with the given offset from the original.
func ToFileErrorWithOffset(fe FileError, offset int) FileError {
pos := fe.Position()
return ToFileErrorWithLineNumber(fe, pos.LineNumber+offset)
}
// ToFileErrorWithOffset will return a new FileError with the given line number.
func ToFileErrorWithLineNumber(fe FileError, lineNumber int) FileError {
pos := fe.Position()
pos.LineNumber = lineNumber
return &fileError{cause: fe, fileType: fe.Type(), position: pos}
}
// ToFileError will convert the given error to an error supporting
// the FileError interface.
func ToFileError(fileType string, err error) FileError {
for _, handle := range lineNumberExtractors {
lno, col := handle(err)
offset, typ := extractOffsetAndType(err)
if fileType == "" {
fileType = typ
}
if lno > 0 || offset != -1 {
return NewFileError(fileType, offset, lno, col, err)
}
}
// Fall back to the pointing to line number 1.
return NewFileError(fileType, -1, 1, 1, err)
}
func extractOffsetAndType(e error) (int, string) {
e = errors.Cause(e)
switch v := e.(type) {
case *json.UnmarshalTypeError:
return int(v.Offset), "json"
case *json.SyntaxError:
return int(v.Offset), "json"
default:
return -1, ""
}
}
+56
View File
@@ -0,0 +1,56 @@
// 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 herrors
import (
"testing"
"github.com/pkg/errors"
qt "github.com/frankban/quicktest"
)
func TestToLineNumberError(t *testing.T) {
t.Parallel()
c := qt.New(t)
for i, test := range []struct {
in error
offset int
lineNumber int
columnNumber int
}{
{errors.New("no line number for you"), 0, 1, 1},
{errors.New(`template: _default/single.html:4:15: executing "_default/single.html" at <.Titles>: can't evaluate field Titles in type *hugolib.PageOutput`), 0, 4, 15},
{errors.New("parse failed: template: _default/bundle-resource-meta.html:11: unexpected in operand"), 0, 11, 1},
{errors.New(`failed:: template: _default/bundle-resource-meta.html:2:7: executing "main" at <.Titles>`), 0, 2, 7},
{errors.New("error in front matter: Near line 32 (last key parsed 'title')"), 0, 32, 1},
{errors.New(`failed to load translations: (6, 7): was expecting token =, but got "g" instead`), 0, 6, 7},
} {
got := ToFileError("template", test.in)
errMsg := qt.Commentf("[%d][%T]", i, got)
le, ok := got.(FileError)
c.Assert(ok, qt.Equals, true)
c.Assert(ok, qt.Equals, true, errMsg)
pos := le.Position()
c.Assert(pos.LineNumber, qt.Equals, test.lineNumber, errMsg)
c.Assert(pos.ColumnNumber, qt.Equals, test.columnNumber, errMsg)
c.Assert(errors.Cause(got), qt.Not(qt.IsNil))
}
}
+66
View File
@@ -0,0 +1,66 @@
// 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
// limitatio ns under the License.
package herrors
import (
"regexp"
"strconv"
)
var lineNumberExtractors = []lineNumberExtractor{
// Template/shortcode parse errors
newLineNumberErrHandlerFromRegexp(".*:(\\d+):(\\d*):"),
newLineNumberErrHandlerFromRegexp(".*:(\\d+):"),
// TOML parse errors
newLineNumberErrHandlerFromRegexp(".*Near line (\\d+)(\\s.*)"),
// YAML parse errors
newLineNumberErrHandlerFromRegexp("line (\\d+):"),
// i18n bundle errors
newLineNumberErrHandlerFromRegexp("\\((\\d+),\\s(\\d*)"),
}
type lineNumberExtractor func(e error) (int, int)
func newLineNumberErrHandlerFromRegexp(expression string) lineNumberExtractor {
re := regexp.MustCompile(expression)
return extractLineNo(re)
}
func extractLineNo(re *regexp.Regexp) lineNumberExtractor {
return func(e error) (int, int) {
if e == nil {
panic("no error")
}
col := 1
s := e.Error()
m := re.FindStringSubmatch(s)
if len(m) >= 2 {
lno, _ := strconv.Atoi(m[1])
if len(m) > 2 {
col, _ = strconv.Atoi(m[2])
}
if col <= 0 {
col = 1
}
return lno, col
}
return -1, col
}
}
+91
View File
@@ -0,0 +1,91 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
// Some functions in this file (see comments) is based on the Go source code,
// copyright The Go Authors and governed by a BSD-style license.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package hreflect contains reflect helpers.
package hreflect
import (
"reflect"
"github.com/gohugoio/hugo/common/types"
)
// IsTruthful returns whether in represents a truthful value.
// See IsTruthfulValue
func IsTruthful(in interface{}) bool {
switch v := in.(type) {
case reflect.Value:
return IsTruthfulValue(v)
default:
return IsTruthfulValue(reflect.ValueOf(in))
}
}
var zeroType = reflect.TypeOf((*types.Zeroer)(nil)).Elem()
// IsTruthfulValue returns whether the given value has a meaningful truth value.
// This is based on template.IsTrue in Go's stdlib, but also considers
// IsZero and any interface value will be unwrapped before it's considered
// for truthfulness.
//
// Based on:
// https://github.com/golang/go/blob/178a2c42254166cffed1b25fb1d3c7a5727cada6/src/text/template/exec.go#L306
func IsTruthfulValue(val reflect.Value) (truth bool) {
val = indirectInterface(val)
if !val.IsValid() {
// Something like var x interface{}, never set. It's a form of nil.
return
}
if val.Type().Implements(zeroType) {
return !val.Interface().(types.Zeroer).IsZero()
}
switch val.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
truth = val.Len() > 0
case reflect.Bool:
truth = val.Bool()
case reflect.Complex64, reflect.Complex128:
truth = val.Complex() != 0
case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Interface:
truth = !val.IsNil()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
truth = val.Int() != 0
case reflect.Float32, reflect.Float64:
truth = val.Float() != 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
truth = val.Uint() != 0
case reflect.Struct:
truth = true // Struct values are always true.
default:
return
}
return
}
// Based on: https://github.com/golang/go/blob/178a2c42254166cffed1b25fb1d3c7a5727cada6/src/text/template/exec.go#L931
func indirectInterface(v reflect.Value) reflect.Value {
if v.Kind() != reflect.Interface {
return v
}
if v.IsNil() {
return reflect.Value{}
}
return v.Elem()
}
+42
View File
@@ -0,0 +1,42 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hreflect
import (
"reflect"
"testing"
"time"
qt "github.com/frankban/quicktest"
)
func TestIsTruthful(t *testing.T) {
c := qt.New(t)
c.Assert(IsTruthful(true), qt.Equals, true)
c.Assert(IsTruthful(false), qt.Equals, false)
c.Assert(IsTruthful(time.Now()), qt.Equals, true)
c.Assert(IsTruthful(time.Time{}), qt.Equals, false)
}
func BenchmarkIsTruthFul(b *testing.B) {
v := reflect.ValueOf("Hugo")
b.ResetTimer()
for i := 0; i < b.N; i++ {
if !IsTruthfulValue(v) {
b.Fatal("not truthful")
}
}
}
+90
View File
@@ -0,0 +1,90 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hugio
import (
"io"
"io/ioutil"
"os"
"path/filepath"
"github.com/pkg/errors"
"github.com/spf13/afero"
)
// CopyFile copies a file.
func CopyFile(fs afero.Fs, from, to string) error {
sf, err := os.Open(from)
if err != nil {
return err
}
defer sf.Close()
df, err := os.Create(to)
if err != nil {
return err
}
defer df.Close()
_, err = io.Copy(df, sf)
if err == nil {
si, err := os.Stat(from)
if err != nil {
err = os.Chmod(to, si.Mode())
if err != nil {
return err
}
}
}
return nil
}
// CopyDir copies a directory.
func CopyDir(fs afero.Fs, from, to string, shouldCopy func(filename string) bool) error {
fi, err := os.Stat(from)
if err != nil {
return err
}
if !fi.IsDir() {
return errors.Errorf("%q is not a directory", from)
}
err = fs.MkdirAll(to, 0777) // before umask
if err != nil {
return err
}
entries, _ := ioutil.ReadDir(from)
for _, entry := range entries {
fromFilename := filepath.Join(from, entry.Name())
toFilename := filepath.Join(to, entry.Name())
if entry.IsDir() {
if shouldCopy != nil && !shouldCopy(fromFilename) {
continue
}
if err := CopyDir(fs, fromFilename, toFilename, shouldCopy); err != nil {
return err
}
} else {
if err := CopyFile(fs, fromFilename, toFilename); err != nil {
return err
}
}
}
return nil
}
+2 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2018 The Hugo Authors. All rights reserved.
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -32,6 +32,7 @@ type ReadSeekCloser interface {
}
// ReadSeekerNoOpCloser implements ReadSeekCloser by doing nothing in Close.
// TODO(bep) rename this and simila to ReadSeekerNopCloser, naming used in stdlib, which kind of makes sense.
type ReadSeekerNoOpCloser struct {
ReadSeeker
}
+33
View File
@@ -15,6 +15,7 @@ package hugio
import (
"io"
"io/ioutil"
)
type multiWriteCloser struct {
@@ -41,3 +42,35 @@ func NewMultiWriteCloser(writeClosers ...io.WriteCloser) io.WriteCloser {
}
return multiWriteCloser{Writer: io.MultiWriter(writers...), closers: writeClosers}
}
// ToWriteCloser creates an io.WriteCloser from the given io.Writer.
// If it's not already, one will be created with a Close method that does nothing.
func ToWriteCloser(w io.Writer) io.WriteCloser {
if rw, ok := w.(io.WriteCloser); ok {
return rw
}
return struct {
io.Writer
io.Closer
}{
w,
ioutil.NopCloser(nil),
}
}
// ToReadCloser creates an io.ReadCloser from the given io.Reader.
// If it's not already, one will be created with a Close method that does nothing.
func ToReadCloser(r io.Reader) io.ReadCloser {
if rc, ok := r.(io.ReadCloser); ok {
return rc
}
return struct {
io.Reader
io.Closer
}{
r,
ioutil.NopCloser(nil),
}
}
+67
View File
@@ -0,0 +1,67 @@
// 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 hugo
import (
"fmt"
"html/template"
)
const (
EnvironmentDevelopment = "development"
EnvironmentProduction = "production"
)
var (
// commitHash contains the current Git revision. Use make to build to make
// sure this gets set.
commitHash string
// buildDate contains the date of the current build.
buildDate string
)
// Info contains information about the current Hugo environment
type Info struct {
CommitHash string
BuildDate string
// The build environment.
// Defaults are "production" (hugo) and "development" (hugo server).
// This can also be set by the user.
// It can be any string, but it will be all lower case.
Environment string
}
// Version returns the current version as a comparable version string.
func (i Info) Version() VersionString {
return CurrentVersion.Version()
}
// Generator a Hugo meta generator HTML tag.
func (i Info) Generator() template.HTML {
return template.HTML(fmt.Sprintf(`<meta name="generator" content="Hugo %s" />`, CurrentVersion.String()))
}
// NewInfo creates a new Hugo Info object.
func NewInfo(environment string) Info {
if environment == "" {
environment = EnvironmentProduction
}
return Info{
CommitHash: commitHash,
BuildDate: buildDate,
Environment: environment,
}
}
@@ -11,23 +11,25 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package hugolib
package hugo
import (
"fmt"
"testing"
"github.com/gohugoio/hugo/helpers"
"github.com/stretchr/testify/require"
qt "github.com/frankban/quicktest"
)
func TestHugoInfo(t *testing.T) {
assert := require.New(t)
c := qt.New(t)
assert.Equal(helpers.CurrentHugoVersion.Version(), hugoInfo.Version)
assert.IsType(helpers.HugoVersionString(""), hugoInfo.Version)
assert.Equal(CommitHash, hugoInfo.CommitHash)
assert.Equal(BuildDate, hugoInfo.BuildDate)
assert.Contains(hugoInfo.Generator, fmt.Sprintf("Hugo %s", hugoInfo.Version))
hugoInfo := NewInfo("")
c.Assert(hugoInfo.Version(), qt.Equals, CurrentVersion.Version())
c.Assert(fmt.Sprintf("%T", VersionString("")), qt.Equals, fmt.Sprintf("%T", hugoInfo.Version()))
c.Assert(hugoInfo.CommitHash, qt.Equals, commitHash)
c.Assert(hugoInfo.BuildDate, qt.Equals, buildDate)
c.Assert(hugoInfo.Environment, qt.Equals, "production")
c.Assert(string(hugoInfo.Generator()), qt.Contains, fmt.Sprintf("Hugo %s", hugoInfo.Version()))
}
@@ -11,13 +11,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// +build windows
// +build extended
package commands
package hugo
const (
ansiEsc = ""
clearLine = ""
hideCursor = ""
showCursor = ""
)
var IsExtended = true
@@ -11,13 +11,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !windows
// +build !extended
package commands
package hugo
const (
ansiEsc = "\u001B"
clearLine = "\r\033[K"
hideCursor = ansiEsc + "[?25l"
showCursor = ansiEsc + "[?25h"
)
var IsExtended = false
+71 -39
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.
@@ -11,18 +11,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package helpers
package hugo
import (
"fmt"
"strconv"
"runtime"
"strings"
"github.com/gohugoio/hugo/compare"
"github.com/spf13/cast"
)
// HugoVersion represents the Hugo build version.
type HugoVersion struct {
// Version represents the Hugo build version.
type Version struct {
// Major and minor version.
Number float32
@@ -35,34 +38,34 @@ type HugoVersion struct {
}
var (
_ compare.Eqer = (*HugoVersionString)(nil)
_ compare.Comparer = (*HugoVersionString)(nil)
_ compare.Eqer = (*VersionString)(nil)
_ compare.Comparer = (*VersionString)(nil)
)
func (v HugoVersion) String() string {
return hugoVersion(v.Number, v.PatchLevel, v.Suffix)
func (v Version) String() string {
return version(v.Number, v.PatchLevel, v.Suffix)
}
// Version returns the Hugo version.
func (v HugoVersion) Version() HugoVersionString {
return HugoVersionString(v.String())
func (v Version) Version() VersionString {
return VersionString(v.String())
}
// HugoVersionString represents a Hugo version string.
type HugoVersionString string
// VersionString represents a Hugo version string.
type VersionString string
func (h HugoVersionString) String() string {
func (h VersionString) String() string {
return string(h)
}
// Compare implements the compare.Comparer interface.
func (h HugoVersionString) Compare(other interface{}) int {
v := MustParseHugoVersion(h.String())
func (h VersionString) Compare(other interface{}) int {
v := MustParseVersion(h.String())
return compareVersionsWithSuffix(v.Number, v.PatchLevel, v.Suffix, other)
}
// Eq implements the compare.Eqer interface.
func (h HugoVersionString) Eq(other interface{}) bool {
func (h VersionString) Eq(other interface{}) bool {
s, err := cast.ToStringE(other)
if err != nil {
return false
@@ -72,9 +75,9 @@ func (h HugoVersionString) Eq(other interface{}) bool {
var versionSuffixes = []string{"-test", "-DEV"}
// ParseHugoVersion parses a version string.
func ParseHugoVersion(s string) (HugoVersion, error) {
var vv HugoVersion
// ParseVersion parses a version string.
func ParseVersion(s string) (Version, error) {
var vv Version
for _, suffix := range versionSuffixes {
if strings.HasSuffix(s, suffix) {
vv.Suffix = suffix
@@ -90,10 +93,10 @@ func ParseHugoVersion(s string) (HugoVersion, error) {
return vv, nil
}
// MustParseHugoVersion parses a version string
// MustParseVersion parses a version string
// and panics if any error occurs.
func MustParseHugoVersion(s string) HugoVersion {
vv, err := ParseHugoVersion(s)
func MustParseVersion(s string) Version {
vv, err := ParseVersion(s)
if err != nil {
panic(err)
}
@@ -101,37 +104,53 @@ func MustParseHugoVersion(s string) HugoVersion {
}
// ReleaseVersion represents the release version.
func (v HugoVersion) ReleaseVersion() HugoVersion {
func (v Version) ReleaseVersion() Version {
v.Suffix = ""
return v
}
// Next returns the next Hugo release version.
func (v HugoVersion) Next() HugoVersion {
return HugoVersion{Number: v.Number + 0.01}
func (v Version) Next() Version {
return Version{Number: v.Number + 0.01}
}
// Prev returns the previous Hugo release version.
func (v HugoVersion) Prev() HugoVersion {
return HugoVersion{Number: v.Number - 0.01}
func (v Version) Prev() Version {
return Version{Number: v.Number - 0.01}
}
// NextPatchLevel returns the next patch/bugfix Hugo version.
// This will be a patch increment on the previous Hugo version.
func (v HugoVersion) NextPatchLevel(level int) HugoVersion {
return HugoVersion{Number: v.Number - 0.01, PatchLevel: level}
func (v Version) NextPatchLevel(level int) Version {
return Version{Number: v.Number - 0.01, PatchLevel: level}
}
// CurrentHugoVersion represents the current build version.
// This should be the only one.
var CurrentHugoVersion = HugoVersion{
Number: 0.49,
PatchLevel: 0,
Suffix: "",
// BuildVersionString creates a version string. This is what you see when
// running "hugo version".
func BuildVersionString() string {
program := "Hugo Static Site Generator"
version := "v" + CurrentVersion.String()
if commitHash != "" {
version += "-" + strings.ToUpper(commitHash)
}
if IsExtended {
version += "/extended"
}
osArch := runtime.GOOS + "/" + runtime.GOARCH
date := buildDate
if date == "" {
date = "unknown"
}
return fmt.Sprintf("%s %s %s BuildDate: %s", program, version, osArch, date)
}
func hugoVersion(version float32, patchVersion int, suffix string) string {
if patchVersion > 0 {
func version(version float32, patchVersion int, suffix string) string {
if patchVersion > 0 || version > 0.53 {
return fmt.Sprintf("%.2f.%d%s", version, patchVersion, suffix)
}
return fmt.Sprintf("%.2f%s", version, suffix)
@@ -142,7 +161,7 @@ func hugoVersion(version float32, patchVersion int, suffix string) string {
// It returns -1 if the given version is less than, 0 if equal and 1 if greater than
// the running version.
func CompareVersion(version interface{}) int {
return compareVersionsWithSuffix(CurrentHugoVersion.Number, CurrentHugoVersion.PatchLevel, CurrentHugoVersion.Suffix, version)
return compareVersionsWithSuffix(CurrentVersion.Number, CurrentVersion.PatchLevel, CurrentVersion.Suffix, version)
}
func compareVersions(inVersion float32, inPatchVersion int, in interface{}) int {
@@ -168,7 +187,7 @@ func compareVersionsWithSuffix(inVersion float32, inPatchVersion int, suffix str
return -1
}
v, err := ParseHugoVersion(s)
v, err := ParseVersion(s)
if err != nil {
return -1
}
@@ -217,3 +236,16 @@ func compareFloatVersions(version float32, v float32) int {
}
return 1
}
func GoMinorVersion() int {
return goMinorVersion(runtime.Version())
}
func goMinorVersion(version string) int {
if strings.HasPrefix(version, "devel") {
return 9999 // magic
}
i, _ := strconv.Atoi(strings.Split(version, ".")[1])
return i
}
+22
View File
@@ -0,0 +1,22 @@
// 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 hugo
// CurrentVersion represents the current build version.
// This should be the only one.
var CurrentVersion = Version{
Number: 0.57,
PatchLevel: 1,
Suffix: "",
}
+88
View File
@@ -0,0 +1,88 @@
// Copyright 2015 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hugo
import (
"testing"
qt "github.com/frankban/quicktest"
)
func TestHugoVersion(t *testing.T) {
c := qt.New(t)
c.Assert(version(0.15, 0, "-DEV"), qt.Equals, "0.15-DEV")
c.Assert(version(0.15, 2, "-DEV"), qt.Equals, "0.15.2-DEV")
v := Version{Number: 0.21, PatchLevel: 0, Suffix: "-DEV"}
c.Assert(v.ReleaseVersion().String(), qt.Equals, "0.21")
c.Assert(v.String(), qt.Equals, "0.21-DEV")
c.Assert(v.Next().String(), qt.Equals, "0.22")
nextVersionString := v.Next().Version()
c.Assert(nextVersionString.String(), qt.Equals, "0.22")
c.Assert(nextVersionString.Eq("0.22"), qt.Equals, true)
c.Assert(nextVersionString.Eq("0.21"), qt.Equals, false)
c.Assert(nextVersionString.Eq(nextVersionString), qt.Equals, true)
c.Assert(v.NextPatchLevel(3).String(), qt.Equals, "0.20.3")
// We started to use full semver versions even for main
// releases in v0.54.0
v = Version{Number: 0.53, PatchLevel: 0}
c.Assert(v.String(), qt.Equals, "0.53")
c.Assert(v.Next().String(), qt.Equals, "0.54.0")
c.Assert(v.Next().Next().String(), qt.Equals, "0.55.0")
v = Version{Number: 0.54, PatchLevel: 0, Suffix: "-DEV"}
c.Assert(v.String(), qt.Equals, "0.54.0-DEV")
}
func TestCompareVersions(t *testing.T) {
c := qt.New(t)
c.Assert(compareVersions(0.20, 0, 0.20), qt.Equals, 0)
c.Assert(compareVersions(0.20, 0, float32(0.20)), qt.Equals, 0)
c.Assert(compareVersions(0.20, 0, float64(0.20)), qt.Equals, 0)
c.Assert(compareVersions(0.19, 1, 0.20), qt.Equals, 1)
c.Assert(compareVersions(0.19, 3, "0.20.2"), qt.Equals, 1)
c.Assert(compareVersions(0.19, 1, 0.01), qt.Equals, -1)
c.Assert(compareVersions(0, 1, 3), qt.Equals, 1)
c.Assert(compareVersions(0, 1, int32(3)), qt.Equals, 1)
c.Assert(compareVersions(0, 1, int64(3)), qt.Equals, 1)
c.Assert(compareVersions(0.20, 0, "0.20"), qt.Equals, 0)
c.Assert(compareVersions(0.20, 1, "0.20.1"), qt.Equals, 0)
c.Assert(compareVersions(0.20, 1, "0.20"), qt.Equals, -1)
c.Assert(compareVersions(0.20, 0, "0.20.1"), qt.Equals, 1)
c.Assert(compareVersions(0.20, 1, "0.20.2"), qt.Equals, 1)
c.Assert(compareVersions(0.21, 1, "0.22.1"), qt.Equals, 1)
c.Assert(compareVersions(0.22, 0, "0.22-DEV"), qt.Equals, -1)
c.Assert(compareVersions(0.22, 0, "0.22.1-DEV"), qt.Equals, 1)
c.Assert(compareVersionsWithSuffix(0.22, 0, "-DEV", "0.22"), qt.Equals, 1)
c.Assert(compareVersionsWithSuffix(0.22, 1, "-DEV", "0.22"), qt.Equals, -1)
c.Assert(compareVersionsWithSuffix(0.22, 1, "-DEV", "0.22.1-DEV"), qt.Equals, 0)
}
func TestParseHugoVersion(t *testing.T) {
c := qt.New(t)
c.Assert(MustParseVersion("0.25").String(), qt.Equals, "0.25")
c.Assert(MustParseVersion("0.25.2").String(), qt.Equals, "0.25.2")
c.Assert(MustParseVersion("0.25-test").String(), qt.Equals, "0.25-test")
c.Assert(MustParseVersion("0.25-DEV").String(), qt.Equals, "0.25-DEV")
}
func TestGoMinorVersion(t *testing.T) {
c := qt.New(t)
c.Assert(goMinorVersion("go1.12.5"), qt.Equals, 12)
c.Assert(GoMinorVersion() >= 11, qt.Equals, true)
}
+145 -6
View File
@@ -14,24 +14,163 @@
package loggers
import (
"bytes"
"io"
"io/ioutil"
"log"
"os"
"regexp"
"github.com/gohugoio/hugo/common/terminal"
jww "github.com/spf13/jwalterweatherman"
)
var (
// Counts ERROR logs to the global jww logger.
GlobalErrorCounter *jww.Counter
)
func init() {
GlobalErrorCounter = &jww.Counter{}
jww.SetLogListeners(jww.LogCounter(GlobalErrorCounter, jww.LevelError))
}
// Logger wraps a *loggers.Logger and some other related logging state.
type Logger struct {
*jww.Notepad
ErrorCounter *jww.Counter
WarnCounter *jww.Counter
// This is only set in server mode.
errors *bytes.Buffer
}
func (l *Logger) Errors() string {
if l.errors == nil {
return ""
}
return ansiColorRe.ReplaceAllString(l.errors.String(), "")
}
// Reset resets the logger's internal state.
func (l *Logger) Reset() {
l.ErrorCounter.Reset()
if l.errors != nil {
l.errors.Reset()
}
}
// NewLogger creates a new Logger for the given thresholds
func NewLogger(stdoutThreshold, logThreshold jww.Threshold, outHandle, logHandle io.Writer, saveErrors bool) *Logger {
return newLogger(stdoutThreshold, logThreshold, outHandle, logHandle, saveErrors)
}
// NewDebugLogger is a convenience function to create a debug logger.
func NewDebugLogger() *jww.Notepad {
return jww.NewNotepad(jww.LevelDebug, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
func NewDebugLogger() *Logger {
return newBasicLogger(jww.LevelDebug)
}
// NewWarningLogger is a convenience function to create a warning logger.
func NewWarningLogger() *jww.Notepad {
return jww.NewNotepad(jww.LevelWarn, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
func NewWarningLogger() *Logger {
return newBasicLogger(jww.LevelWarn)
}
// NewErrorLogger is a convenience function to create an error logger.
func NewErrorLogger() *jww.Notepad {
return jww.NewNotepad(jww.LevelError, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
func NewErrorLogger() *Logger {
return newBasicLogger(jww.LevelError)
}
var (
ansiColorRe = regexp.MustCompile("(?s)\\033\\[\\d*(;\\d*)*m")
errorRe = regexp.MustCompile("^(ERROR|FATAL|WARN)")
)
type ansiCleaner struct {
w io.Writer
}
func (a ansiCleaner) Write(p []byte) (n int, err error) {
return a.w.Write(ansiColorRe.ReplaceAll(p, []byte("")))
}
type labelColorizer struct {
w io.Writer
}
func (a labelColorizer) Write(p []byte) (n int, err error) {
replaced := errorRe.ReplaceAllStringFunc(string(p), func(m string) string {
switch m {
case "ERROR", "FATAL":
return terminal.Error(m)
case "WARN":
return terminal.Warning(m)
default:
return m
}
})
// io.MultiWriter will abort if we return a bigger write count than input
// bytes, so we lie a little.
_, err = a.w.Write([]byte(replaced))
return len(p), err
}
// InitGlobalLogger initializes the global logger, used in some rare cases.
func InitGlobalLogger(stdoutThreshold, logThreshold jww.Threshold, outHandle, logHandle io.Writer) {
outHandle, logHandle = getLogWriters(outHandle, logHandle)
jww.SetStdoutOutput(outHandle)
jww.SetLogOutput(logHandle)
jww.SetLogThreshold(logThreshold)
jww.SetStdoutThreshold(stdoutThreshold)
}
func getLogWriters(outHandle, logHandle io.Writer) (io.Writer, io.Writer) {
isTerm := terminal.IsTerminal(os.Stdout)
if logHandle != ioutil.Discard && isTerm {
// Remove any Ansi coloring from log output
logHandle = ansiCleaner{w: logHandle}
}
if isTerm {
outHandle = labelColorizer{w: outHandle}
}
return outHandle, logHandle
}
func newLogger(stdoutThreshold, logThreshold jww.Threshold, outHandle, logHandle io.Writer, saveErrors bool) *Logger {
errorCounter := &jww.Counter{}
warnCounter := &jww.Counter{}
outHandle, logHandle = getLogWriters(outHandle, logHandle)
listeners := []jww.LogListener{jww.LogCounter(errorCounter, jww.LevelError), jww.LogCounter(warnCounter, jww.LevelWarn)}
var errorBuff *bytes.Buffer
if saveErrors {
errorBuff = new(bytes.Buffer)
errorCapture := func(t jww.Threshold) io.Writer {
if t != jww.LevelError {
// Only interested in ERROR
return nil
}
return errorBuff
}
listeners = append(listeners, errorCapture)
}
return &Logger{
Notepad: jww.NewNotepad(stdoutThreshold, logThreshold, outHandle, logHandle, "", log.Ldate|log.Ltime, listeners...),
ErrorCounter: errorCounter,
WarnCounter: warnCounter,
errors: errorBuff,
}
}
func newBasicLogger(t jww.Threshold) *Logger {
return newLogger(t, jww.LevelError, os.Stdout, ioutil.Discard, false)
}
@@ -11,15 +11,22 @@
// 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
package loggers
import (
"errors"
"testing"
qt "github.com/frankban/quicktest"
)
// ErrFeatureNotAvailable denotes that a feature is unavailable.
//
// We will, at least to begin with, make some Hugo features (SCSS with libsass) optional,
// and this error is used to signal those situations.
var ErrFeatureNotAvailable = errors.New("this feature is not available in your current Hugo version")
func TestLogger(t *testing.T) {
c := qt.New(t)
l := NewWarningLogger()
l.ERROR.Println("One error")
l.ERROR.Println("Two error")
l.WARN.Println("A warning")
c.Assert(l.ErrorCounter.Count(), qt.Equals, uint64(2))
}
+72
View File
@@ -16,6 +16,8 @@ package maps
import (
"strings"
"github.com/gobwas/glob"
"github.com/spf13/cast"
)
@@ -42,3 +44,73 @@ func ToLower(m map[string]interface{}) {
}
}
type keyRename struct {
pattern glob.Glob
newKey string
}
// KeyRenamer supports renaming of keys in a map.
type KeyRenamer struct {
renames []keyRename
}
// NewKeyRenamer creates a new KeyRenamer given a list of pattern and new key
// value pairs.
func NewKeyRenamer(patternKeys ...string) (KeyRenamer, error) {
var renames []keyRename
for i := 0; i < len(patternKeys); i += 2 {
g, err := glob.Compile(strings.ToLower(patternKeys[i]), '/')
if err != nil {
return KeyRenamer{}, err
}
renames = append(renames, keyRename{pattern: g, newKey: patternKeys[i+1]})
}
return KeyRenamer{renames: renames}, nil
}
func (r KeyRenamer) getNewKey(keyPath string) string {
for _, matcher := range r.renames {
if matcher.pattern.Match(keyPath) {
return matcher.newKey
}
}
return ""
}
// Rename renames the keys in the given map according
// to the patterns in the current KeyRenamer.
func (r KeyRenamer) Rename(m map[string]interface{}) {
r.renamePath("", m)
}
func (KeyRenamer) keyPath(k1, k2 string) string {
k1, k2 = strings.ToLower(k1), strings.ToLower(k2)
if k1 == "" {
return k2
} else {
return k1 + "/" + k2
}
}
func (r KeyRenamer) renamePath(parentKeyPath string, m map[string]interface{}) {
for key, val := range m {
keyPath := r.keyPath(parentKeyPath, key)
switch val.(type) {
case map[interface{}]interface{}:
val = cast.ToStringMap(val)
r.renamePath(keyPath, val.(map[string]interface{}))
case map[string]interface{}:
r.renamePath(keyPath, val.(map[string]interface{}))
}
newKey := r.getNewKey(keyPath)
if newKey != "" {
delete(m, key)
m[newKey] = val
}
}
}
+31
View File
@@ -0,0 +1,31 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package maps
import (
"github.com/spf13/cast"
)
// GetString tries to get a value with key from map m and convert it to a string.
// It will return an empty string if not found or if it cannot be convertd to a string.
func GetString(m map[string]interface{}, key string) string {
if m == nil {
return ""
}
v, found := m[key]
if !found {
return ""
}
return cast.ToString(v)
}
+51
View File
@@ -16,6 +16,8 @@ package maps
import (
"reflect"
"testing"
qt "github.com/frankban/quicktest"
)
func TestToLower(t *testing.T) {
@@ -70,3 +72,52 @@ func TestToLower(t *testing.T) {
}
}
}
func TestRenameKeys(t *testing.T) {
c := qt.New(t)
m := map[string]interface{}{
"a": 32,
"ren1": "m1",
"ren2": "m1_2",
"sub": map[string]interface{}{
"subsub": map[string]interface{}{
"REN1": "m2",
"ren2": "m2_2",
},
},
"no": map[string]interface{}{
"ren1": "m2",
"ren2": "m2_2",
},
}
expected := map[string]interface{}{
"a": 32,
"new1": "m1",
"new2": "m1_2",
"sub": map[string]interface{}{
"subsub": map[string]interface{}{
"new1": "m2",
"ren2": "m2_2",
},
},
"no": map[string]interface{}{
"ren1": "m2",
"ren2": "m2_2",
},
}
renamer, err := NewKeyRenamer(
"{ren1,sub/*/ren1}", "new1",
"{Ren2,sub/ren2}", "new2",
)
c.Assert(err, qt.IsNil)
renamer.Rename(m)
if !reflect.DeepEqual(expected, m) {
t.Errorf("Expected\n%#v, got\n%#v\n", expected, m)
}
}
+94
View File
@@ -0,0 +1,94 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package maps
import (
"strings"
"github.com/spf13/cast"
)
// GetNestedParam gets the first match of the keyStr in the candidates given.
// It will first try the exact match and then try to find it as a nested map value,
// using the given separator, e.g. "mymap.name".
// It assumes that all the maps given have lower cased keys.
func GetNestedParam(keyStr, separator string, candidates ...map[string]interface{}) (interface{}, error) {
keyStr = strings.ToLower(keyStr)
lookupFn := func(key string) interface{} {
for _, m := range candidates {
if v, ok := m[key]; ok {
return v
}
}
return nil
}
v, _, _, err := GetNestedParamFn(keyStr, separator, lookupFn)
return v, err
}
func GetNestedParamFn(keyStr, separator string, lookupFn func(key string) interface{}) (interface{}, string, map[string]interface{}, error) {
result, _ := traverseDirectParams(keyStr, lookupFn)
if result != nil {
return result, keyStr, nil, nil
}
keySegments := strings.Split(keyStr, separator)
if len(keySegments) == 1 {
return nil, keyStr, nil, nil
}
return traverseNestedParams(keySegments, lookupFn)
}
func traverseDirectParams(keyStr string, lookupFn func(key string) interface{}) (interface{}, error) {
return lookupFn(keyStr), nil
}
func traverseNestedParams(keySegments []string, lookupFn func(key string) interface{}) (interface{}, string, map[string]interface{}, error) {
firstKey, rest := keySegments[0], keySegments[1:]
result := lookupFn(firstKey)
if result == nil || len(rest) == 0 {
return result, firstKey, nil, nil
}
switch m := result.(type) {
case map[string]interface{}:
v, key, owner := traverseParams(rest, m)
return v, key, owner, nil
default:
return nil, "", nil, nil
}
}
func traverseParams(keys []string, m map[string]interface{}) (interface{}, string, map[string]interface{}) {
// Shift first element off.
firstKey, rest := keys[0], keys[1:]
result := m[firstKey]
// No point in continuing here.
if result == nil {
return result, "", nil
}
if len(rest) == 0 {
// That was the last key.
return result, firstKey, m
}
// That was not the last key.
return traverseParams(rest, cast.ToStringMap(result))
}
+51
View File
@@ -0,0 +1,51 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package maps
import (
"testing"
qt "github.com/frankban/quicktest"
)
func TestGetNestedParam(t *testing.T) {
m := map[string]interface{}{
"string": "value",
"first": 1,
"with_underscore": 2,
"nested": map[string]interface{}{
"color": "blue",
"nestednested": map[string]interface{}{
"color": "green",
},
},
}
c := qt.New(t)
must := func(keyStr, separator string, candidates ...map[string]interface{}) interface{} {
v, err := GetNestedParam(keyStr, separator, candidates...)
c.Assert(err, qt.IsNil)
return v
}
c.Assert(must("first", "_", m), qt.Equals, 1)
c.Assert(must("First", "_", m), qt.Equals, 1)
c.Assert(must("with_underscore", "_", m), qt.Equals, 2)
c.Assert(must("nested_color", "_", m), qt.Equals, "blue")
c.Assert(must("nested.nestednested.color", ".", m), qt.Equals, "green")
c.Assert(must("string.name", ".", m), qt.IsNil)
}
+24 -7
View File
@@ -1,4 +1,4 @@
// Copyright 2018 The Hugo Authors. All rights reserved.
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ import (
"sort"
"sync"
"github.com/gohugoio/hugo/common/collections"
"github.com/gohugoio/hugo/common/math"
)
@@ -27,6 +28,24 @@ type Scratch struct {
mu sync.RWMutex
}
// Scratcher provides a scratching service.
type Scratcher interface {
Scratch() *Scratch
}
type scratcher struct {
s *Scratch
}
func (s scratcher) Scratch() *Scratch {
return s.s
}
// NewScratcher creates a new Scratcher.
func NewScratcher() Scratcher {
return scratcher{s: NewScratch()}
}
// Add will, for single values, add (using the + operator) the addend to the existing addend (if found).
// Supports numeric values and strings.
//
@@ -40,14 +59,12 @@ func (c *Scratch) Add(key string, newAddend interface{}) (string, error) {
if found {
var err error
addendV := reflect.ValueOf(existingAddend)
addendV := reflect.TypeOf(existingAddend)
if addendV.Kind() == reflect.Slice || addendV.Kind() == reflect.Array {
nav := reflect.ValueOf(newAddend)
if nav.Kind() == reflect.Slice || nav.Kind() == reflect.Array {
newVal = reflect.AppendSlice(addendV, nav).Interface()
} else {
newVal = reflect.Append(addendV, nav).Interface()
newVal, err = collections.Append(existingAddend, newAddend)
if err != nil {
return "", err
}
} else {
newVal, err = math.DoArithmetic(existingAddend, newAddend, '+')
+49 -12
View File
@@ -18,29 +18,31 @@ import (
"sync"
"testing"
"github.com/stretchr/testify/assert"
qt "github.com/frankban/quicktest"
)
func TestScratchAdd(t *testing.T) {
t.Parallel()
c := qt.New(t)
scratch := NewScratch()
scratch.Add("int1", 10)
scratch.Add("int1", 20)
scratch.Add("int2", 20)
assert.Equal(t, int64(30), scratch.Get("int1"))
assert.Equal(t, 20, scratch.Get("int2"))
c.Assert(scratch.Get("int1"), qt.Equals, int64(30))
c.Assert(scratch.Get("int2"), qt.Equals, 20)
scratch.Add("float1", float64(10.5))
scratch.Add("float1", float64(20.1))
assert.Equal(t, float64(30.6), scratch.Get("float1"))
c.Assert(scratch.Get("float1"), qt.Equals, float64(30.6))
scratch.Add("string1", "Hello ")
scratch.Add("string1", "big ")
scratch.Add("string1", "World!")
assert.Equal(t, "Hello big World!", scratch.Get("string1"))
c.Assert(scratch.Get("string1"), qt.Equals, "Hello big World!")
scratch.Add("scratch", scratch)
_, err := scratch.Add("scratch", scratch)
@@ -53,12 +55,14 @@ func TestScratchAdd(t *testing.T) {
func TestScratchAddSlice(t *testing.T) {
t.Parallel()
c := qt.New(t)
scratch := NewScratch()
_, err := scratch.Add("intSlice", []int{1, 2})
assert.Nil(t, err)
c.Assert(err, qt.IsNil)
_, err = scratch.Add("intSlice", 3)
assert.Nil(t, err)
c.Assert(err, qt.IsNil)
sl := scratch.Get("intSlice")
expected := []int{1, 2, 3}
@@ -66,10 +70,9 @@ func TestScratchAddSlice(t *testing.T) {
if !reflect.DeepEqual(expected, sl) {
t.Errorf("Slice difference, go %q expected %q", sl, expected)
}
_, err = scratch.Add("intSlice", []int{4, 5})
assert.Nil(t, err)
c.Assert(err, qt.IsNil)
sl = scratch.Get("intSlice")
expected = []int{1, 2, 3, 4, 5}
@@ -77,29 +80,61 @@ func TestScratchAddSlice(t *testing.T) {
if !reflect.DeepEqual(expected, sl) {
t.Errorf("Slice difference, go %q expected %q", sl, expected)
}
}
// https://github.com/gohugoio/hugo/issues/5275
func TestScratchAddTypedSliceToInterfaceSlice(t *testing.T) {
t.Parallel()
c := qt.New(t)
scratch := NewScratch()
scratch.Set("slice", []interface{}{})
_, err := scratch.Add("slice", []int{1, 2})
c.Assert(err, qt.IsNil)
c.Assert(scratch.Get("slice"), qt.DeepEquals, []int{1, 2})
}
// https://github.com/gohugoio/hugo/issues/5361
func TestScratchAddDifferentTypedSliceToInterfaceSlice(t *testing.T) {
t.Parallel()
c := qt.New(t)
scratch := NewScratch()
scratch.Set("slice", []string{"foo"})
_, err := scratch.Add("slice", []int{1, 2})
c.Assert(err, qt.IsNil)
c.Assert(scratch.Get("slice"), qt.DeepEquals, []interface{}{"foo", 1, 2})
}
func TestScratchSet(t *testing.T) {
t.Parallel()
c := qt.New(t)
scratch := NewScratch()
scratch.Set("key", "val")
assert.Equal(t, "val", scratch.Get("key"))
c.Assert(scratch.Get("key"), qt.Equals, "val")
}
func TestScratchDelete(t *testing.T) {
t.Parallel()
c := qt.New(t)
scratch := NewScratch()
scratch.Set("key", "val")
scratch.Delete("key")
scratch.Add("key", "Lucy Parsons")
assert.Equal(t, "Lucy Parsons", scratch.Get("key"))
c.Assert(scratch.Get("key"), qt.Equals, "Lucy Parsons")
}
// Issue #2005
func TestScratchInParallel(t *testing.T) {
var wg sync.WaitGroup
scratch := NewScratch()
key := "counter"
scratch.Set(key, int64(1))
for i := 1; i <= 10; i++ {
@@ -142,13 +177,15 @@ func TestScratchGet(t *testing.T) {
func TestScratchSetInMap(t *testing.T) {
t.Parallel()
c := qt.New(t)
scratch := NewScratch()
scratch.SetInMap("key", "lux", "Lux")
scratch.SetInMap("key", "abc", "Abc")
scratch.SetInMap("key", "zyx", "Zyx")
scratch.SetInMap("key", "abc", "Abc (updated)")
scratch.SetInMap("key", "def", "Def")
assert.Equal(t, []interface{}{0: "Abc (updated)", 1: "Def", 2: "Lux", 3: "Zyx"}, scratch.GetSortedMapValues("key"))
c.Assert(scratch.GetSortedMapValues("key"), qt.DeepEquals, []interface{}{0: "Abc (updated)", 1: "Def", 2: "Lux", 3: "Zyx"})
}
func TestScratchGetSortedMapValues(t *testing.T) {
+7 -7
View File
@@ -46,7 +46,7 @@ func DoArithmetic(a, b interface{}, op rune) (interface{}, error) {
bu = 0
}
default:
return nil, errors.New("Can't apply the operator to the values")
return nil, errors.New("can't apply the operator to the values")
}
case reflect.Float32, reflect.Float64:
af = av.Float()
@@ -58,7 +58,7 @@ func DoArithmetic(a, b interface{}, op rune) (interface{}, error) {
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")
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()
@@ -79,7 +79,7 @@ func DoArithmetic(a, b interface{}, op rune) (interface{}, error) {
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")
return nil, errors.New("can't apply the operator to the values")
}
case reflect.String:
as := av.String()
@@ -87,9 +87,9 @@ func DoArithmetic(a, b interface{}, op rune) (interface{}, error) {
bs := bv.String()
return as + bs, nil
}
return nil, errors.New("Can't apply the operator to the values")
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")
return nil, errors.New("can't apply the operator to the values")
}
switch op {
@@ -128,8 +128,8 @@ func DoArithmetic(a, b interface{}, op rune) (interface{}, error) {
} else if bu != 0 {
return au / bu, nil
}
return nil, errors.New("Can't divide the value by 0")
return nil, errors.New("can't divide the value by 0")
default:
return nil, errors.New("There is no such an operation")
return nil, errors.New("there is no such an operation")
}
}
+6 -9
View File
@@ -14,17 +14,16 @@
package math
import (
"fmt"
"testing"
"github.com/alecthomas/assert"
"github.com/stretchr/testify/require"
qt "github.com/frankban/quicktest"
)
func TestDoArithmetic(t *testing.T) {
t.Parallel()
c := qt.New(t)
for i, test := range []struct {
for _, test := range []struct {
a interface{}
b interface{}
op rune
@@ -94,16 +93,14 @@ func TestDoArithmetic(t *testing.T) {
{"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)
c.Assert(err, qt.Not(qt.IsNil))
continue
}
require.NoError(t, err, errMsg)
assert.Equal(t, test.expect, result, errMsg)
c.Assert(err, qt.IsNil)
c.Assert(test.expect, qt.Equals, result)
}
}
+70
View File
@@ -0,0 +1,70 @@
// 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 terminal contains helper for the terminal, such as coloring output.
package terminal
import (
"fmt"
"os"
"runtime"
"strings"
isatty "github.com/mattn/go-isatty"
)
const (
errorColor = "\033[1;31m%s\033[0m"
warningColor = "\033[0;33m%s\033[0m"
noticeColor = "\033[1;36m%s\033[0m"
)
// IsTerminal return true if the file descriptor is terminal and the TERM
// environment variable isn't a dumb one.
func IsTerminal(f *os.File) bool {
if runtime.GOOS == "windows" {
return false
}
fd := f.Fd()
return os.Getenv("TERM") != "dumb" && (isatty.IsTerminal(fd) || isatty.IsCygwinTerminal(fd))
}
// Notice colorizes the string in a noticeable color.
func Notice(s string) string {
return colorize(s, noticeColor)
}
// Error colorizes the string in a colour that grabs attention.
func Error(s string) string {
return colorize(s, errorColor)
}
// Warning colorizes the string in a colour that warns.
func Warning(s string) string {
return colorize(s, warningColor)
}
// colorize s in color.
func colorize(s, color string) string {
s = fmt.Sprintf(color, doublePercent(s))
return singlePercent(s)
}
func doublePercent(str string) string {
return strings.Replace(str, "%", "%%", -1)
}
func singlePercent(str string) string {
return strings.Replace(str, "%%", "%", -1)
}
+99
View File
@@ -0,0 +1,99 @@
// 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 text
import (
"fmt"
"os"
"strings"
"github.com/gohugoio/hugo/common/terminal"
)
// Positioner represents a thing that knows its position in a text file or stream,
// typically an error.
type Positioner interface {
Position() Position
}
// Position holds a source position in a text file or stream.
type Position struct {
Filename string // filename, if any
Offset int // byte offset, starting at 0. It's set to -1 if not provided.
LineNumber int // line number, starting at 1
ColumnNumber int // column number, starting at 1 (character count per line)
}
func (pos Position) String() string {
if pos.Filename == "" {
pos.Filename = "<stream>"
}
return positionStringFormatfunc(pos)
}
// IsValid returns true if line number is > 0.
func (pos Position) IsValid() bool {
return pos.LineNumber > 0
}
var positionStringFormatfunc func(p Position) string
func createPositionStringFormatter(formatStr string) func(p Position) string {
if formatStr == "" {
formatStr = "\":file::line::col\""
}
var identifiers = []string{":file", ":line", ":col"}
var identifiersFound []string
for i := range formatStr {
for _, id := range identifiers {
if strings.HasPrefix(formatStr[i:], id) {
identifiersFound = append(identifiersFound, id)
}
}
}
replacer := strings.NewReplacer(":file", "%s", ":line", "%d", ":col", "%d")
format := replacer.Replace(formatStr)
f := func(pos Position) string {
args := make([]interface{}, len(identifiersFound))
for i, id := range identifiersFound {
switch id {
case ":file":
args[i] = pos.Filename
case ":line":
args[i] = pos.LineNumber
case ":col":
args[i] = pos.ColumnNumber
}
}
msg := fmt.Sprintf(format, args...)
if terminal.IsTerminal(os.Stdout) {
return terminal.Notice(msg)
}
return msg
}
return f
}
func init() {
positionStringFormatfunc = createPositionStringFormatter(os.Getenv("HUGO_FILE_LOG_FORMAT"))
}
+33
View File
@@ -0,0 +1,33 @@
// 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 text
import (
"testing"
qt "github.com/frankban/quicktest"
)
func TestPositionStringFormatter(t *testing.T) {
c := qt.New(t)
pos := Position{Filename: "/my/file.txt", LineNumber: 12, ColumnNumber: 13, Offset: 14}
c.Assert(createPositionStringFormatter(":file|:col|:line")(pos), qt.Equals, "/my/file.txt|13|12")
c.Assert(createPositionStringFormatter(":col|:file|:line")(pos), qt.Equals, "13|/my/file.txt|12")
c.Assert(createPositionStringFormatter("好::col")(pos), qt.Equals, "好:13")
c.Assert(createPositionStringFormatter("")(pos), qt.Equals, "\"/my/file.txt:12:13\"")
c.Assert(pos.String(), qt.Equals, "\"/my/file.txt:12:13\"")
}
+7
View File
@@ -52,6 +52,13 @@ func (q *EvictingStringQueue) Add(v string) {
q.mu.Unlock()
}
// Contains returns whether the queue contains v.
func (q *EvictingStringQueue) Contains(v string) bool {
q.mu.Lock()
defer q.mu.Unlock()
return q.set[v]
}
// Peek looks at the last element added to the queue.
func (q *EvictingStringQueue) Peek() string {
q.mu.Lock()
+13 -10
View File
@@ -17,33 +17,36 @@ import (
"sync"
"testing"
"github.com/stretchr/testify/require"
qt "github.com/frankban/quicktest"
)
func TestEvictingStringQueue(t *testing.T) {
assert := require.New(t)
c := qt.New(t)
queue := NewEvictingStringQueue(3)
assert.Equal("", queue.Peek())
c.Assert(queue.Peek(), qt.Equals, "")
queue.Add("a")
queue.Add("b")
queue.Add("a")
assert.Equal("b", queue.Peek())
c.Assert(queue.Peek(), qt.Equals, "b")
queue.Add("b")
assert.Equal("b", queue.Peek())
c.Assert(queue.Peek(), qt.Equals, "b")
queue.Add("a")
queue.Add("b")
assert.Equal([]string{"b", "a"}, queue.PeekAll())
assert.Equal("b", queue.Peek())
c.Assert(queue.Contains("a"), qt.Equals, true)
c.Assert(queue.Contains("foo"), qt.Equals, false)
c.Assert(queue.PeekAll(), qt.DeepEquals, []string{"b", "a"})
c.Assert(queue.Peek(), qt.Equals, "b")
queue.Add("c")
queue.Add("d")
// Overflowed, a should now be removed.
assert.Equal([]string{"d", "c", "b"}, queue.PeekAll())
assert.Len(queue.PeekAllSet(), 3)
assert.True(queue.PeekAllSet()["c"])
c.Assert(queue.PeekAll(), qt.DeepEquals, []string{"d", "c", "b"})
c.Assert(len(queue.PeekAllSet()), qt.Equals, 3)
c.Assert(queue.PeekAllSet()["c"], qt.Equals, true)
}
func TestEvictingStringQueueConcurrent(t *testing.T) {
+29 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2018 The Hugo Authors. All rights reserved.
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@ package types
import (
"fmt"
"reflect"
"github.com/spf13/cast"
)
@@ -50,3 +51,30 @@ func NewKeyValuesStrings(key string, values ...string) KeyValues {
}
return KeyValues{Key: key, Values: iv}
}
// Zeroer, as implemented by time.Time, will be used by the truth template
// funcs in Hugo (if, with, not, and, or).
type Zeroer interface {
IsZero() bool
}
// IsNil reports whether v is nil.
func IsNil(v interface{}) bool {
if v == nil {
return true
}
value := reflect.ValueOf(v)
switch value.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
return value.IsNil()
}
return false
}
// DevMarker is a marker interface for types that should only be used during
// development.
type DevMarker interface {
DevOnly()
}
+4 -4
View File
@@ -16,14 +16,14 @@ package types
import (
"testing"
"github.com/stretchr/testify/require"
qt "github.com/frankban/quicktest"
)
func TestKeyValues(t *testing.T) {
assert := require.New(t)
c := qt.New(t)
kv := NewKeyValuesStrings("key", "a1", "a2")
assert.Equal("key", kv.KeyString())
assert.Equal([]interface{}{"a1", "a2"}, kv.Values)
c.Assert(kv.KeyString(), qt.Equals, "key")
c.Assert(kv.Values, qt.DeepEquals, []interface{}{"a1", "a2"})
}
+22
View File
@@ -0,0 +1,22 @@
// 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 urls
// RefLinker is implemented by those who support reference linking.
// args must contain a path, but can also point to the target
// language or output format.
type RefLinker interface {
Ref(args map[string]interface{}) (string, error)
RelRef(args map[string]interface{}) (string, error)
}
+7 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2017-present The Hugo Authors. All rights reserved.
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -20,6 +20,12 @@ type Eqer interface {
Eq(other interface{}) bool
}
// ProbablyEqer is an equal check that may return false positives, but never
// a false negative.
type ProbablyEqer interface {
ProbablyEq(other interface{}) bool
}
// Comparer can be used to compare two values.
// This will be used when using the le, ge etc. operators in the templates.
// Compare returns -1 if the given version is less than, 0 if equal and 1 if greater than
+113
View File
@@ -0,0 +1,113 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package compare
import (
"strings"
"unicode"
"unicode/utf8"
)
// Strings returns an integer comparing two strings lexicographically.
func Strings(s, t string) int {
c := compareFold(s, t)
if c == 0 {
// "B" and "b" would be the same so we need a tiebreaker.
return strings.Compare(s, t)
}
return c
}
// This function is derived from strings.EqualFold in Go's stdlib.
// https://github.com/golang/go/blob/ad4a58e31501bce5de2aad90a620eaecdc1eecb8/src/strings/strings.go#L893
func compareFold(s, t string) int {
for s != "" && t != "" {
var sr, tr rune
if s[0] < utf8.RuneSelf {
sr, s = rune(s[0]), s[1:]
} else {
r, size := utf8.DecodeRuneInString(s)
sr, s = r, s[size:]
}
if t[0] < utf8.RuneSelf {
tr, t = rune(t[0]), t[1:]
} else {
r, size := utf8.DecodeRuneInString(t)
tr, t = r, t[size:]
}
if tr == sr {
continue
}
c := 1
if tr < sr {
tr, sr = sr, tr
c = -c
}
// ASCII only.
if tr < utf8.RuneSelf {
if sr >= 'A' && sr <= 'Z' {
if tr <= 'Z' {
// Same case.
return -c
}
diff := tr - (sr + 'a' - 'A')
if diff == 0 {
continue
}
if diff < 0 {
return c
}
if diff > 0 {
return -c
}
}
}
// Unicode.
r := unicode.SimpleFold(sr)
for r != sr && r < tr {
r = unicode.SimpleFold(r)
}
if r == tr {
continue
}
return -c
}
if s == "" && t == "" {
return 0
}
if s == "" {
return -1
}
return 1
}
// LessStrings returns whether s is less than t lexicographically.
func LessStrings(s, t string) bool {
return Strings(s, t) < 0
}
+65
View File
@@ -0,0 +1,65 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package compare
import (
"sort"
"strings"
"testing"
qt "github.com/frankban/quicktest"
)
func TestCompare(t *testing.T) {
c := qt.New(t)
for _, test := range []struct {
a string
b string
}{
{"a", "a"},
{"A", "a"},
{"Ab", "Ac"},
{"az", "Za"},
{"C", "D"},
{"B", "a"},
{"C", ""},
{"", ""},
{"αβδC", "ΑΒΔD"},
{"αβδC", "ΑΒΔ"},
{"αβδ", "ΑΒΔD"},
{"αβδ", "ΑΒΔ"},
{"β", "δ"},
{"好", strings.ToLower("好")},
} {
expect := strings.Compare(strings.ToLower(test.a), strings.ToLower(test.b))
got := compareFold(test.a, test.b)
c.Assert(got, qt.Equals, expect)
}
}
func TestLexicographicSort(t *testing.T) {
c := qt.New(t)
s := []string{"b", "Bz", "ba", "A", "Ba", "ba"}
sort.Slice(s, func(i, j int) bool {
return LessStrings(s[i], s[j])
})
c.Assert(s, qt.DeepEquals, []string{"A", "b", "Ba", "ba", "ba", "Bz"})
}
+125
View File
@@ -0,0 +1,125 @@
// 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 config
import (
"path/filepath"
"strings"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/parser/metadecoders"
"github.com/spf13/afero"
"github.com/spf13/viper"
)
var (
ValidConfigFileExtensions = []string{"toml", "yaml", "yml", "json"}
validConfigFileExtensionsMap map[string]bool = make(map[string]bool)
)
func init() {
for _, ext := range ValidConfigFileExtensions {
validConfigFileExtensionsMap[ext] = true
}
}
// IsValidConfigFilename returns whether filename is one of the supported
// config formats in Hugo.
func IsValidConfigFilename(filename string) bool {
ext := strings.ToLower(strings.TrimPrefix(filepath.Ext(filename), "."))
return validConfigFileExtensionsMap[ext]
}
// FromConfigString creates a config from the given YAML, JSON or TOML config. This is useful in tests.
func FromConfigString(config, configType string) (Provider, error) {
v := newViper()
m, err := readConfig(metadecoders.FormatFromString(configType), []byte(config))
if err != nil {
return nil, err
}
v.MergeConfigMap(m)
return v, nil
}
// FromFile loads the configuration from the given filename.
func FromFile(fs afero.Fs, filename string) (Provider, error) {
m, err := loadConfigFromFile(fs, filename)
if err != nil {
return nil, err
}
v := newViper()
err = v.MergeConfigMap(m)
if err != nil {
return nil, err
}
return v, nil
}
// FromFileToMap is the same as FromFile, but it returns the config values
// as a simple map.
func FromFileToMap(fs afero.Fs, filename string) (map[string]interface{}, error) {
return loadConfigFromFile(fs, filename)
}
func readConfig(format metadecoders.Format, data []byte) (map[string]interface{}, error) {
m, err := metadecoders.Default.UnmarshalToMap(data, format)
if err != nil {
return nil, err
}
RenameKeys(m)
return m, nil
}
func loadConfigFromFile(fs afero.Fs, filename string) (map[string]interface{}, error) {
m, err := metadecoders.Default.UnmarshalFileToMap(fs, filename)
if err != nil {
return nil, err
}
RenameKeys(m)
return m, nil
}
var keyAliases maps.KeyRenamer
func init() {
var err error
keyAliases, err = maps.NewKeyRenamer(
// Before 0.53 we used singular for "menu".
"{menu,languages/*/menu}", "menus",
)
if err != nil {
panic(err)
}
}
// RenameKeys renames config keys in m recursively according to a global Hugo
// alias definition.
func RenameKeys(m map[string]interface{}) {
keyAliases.Rename(m)
}
func newViper() *viper.Viper {
v := viper.New()
return v
}
+34
View File
@@ -0,0 +1,34 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"strings"
"testing"
qt "github.com/frankban/quicktest"
)
func TestIsValidConfigFileName(t *testing.T) {
c := qt.New(t)
for _, ext := range ValidConfigFileExtensions {
filename := "config." + ext
c.Assert(IsValidConfigFilename(filename), qt.Equals, true)
c.Assert(IsValidConfigFilename(strings.ToUpper(filename)), qt.Equals, true)
}
c.Assert(IsValidConfigFilename(""), qt.Equals, false)
c.Assert(IsValidConfigFilename("config.toml.swp"), qt.Equals, false)
}
+19 -17
View File
@@ -1,4 +1,4 @@
// Copyright 2017-present The Hugo Authors. All rights reserved.
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,11 +14,7 @@
package config
import (
"strings"
"github.com/spf13/cast"
"github.com/spf13/viper"
)
// Provider provides the configuration settings for Hugo.
@@ -34,23 +30,29 @@ type Provider interface {
IsSet(key string) bool
}
// FromConfigString creates a config from the given YAML, JSON or TOML config. This is useful in tests.
func FromConfigString(config, configType string) (Provider, error) {
v := viper.New()
v.SetConfigType(configType)
if err := v.ReadConfig(strings.NewReader(config)); err != nil {
return nil, err
}
return v, nil
}
// GetStringSlicePreserveString returns a string slice from the given config and key.
// It differs from the GetStringSlice method in that if the config value is a string,
// we do not attempt to split it into fields.
func GetStringSlicePreserveString(cfg Provider, key string) []string {
sd := cfg.Get(key)
if sds, ok := sd.(string); ok {
return toStringSlicePreserveString(sd)
}
func toStringSlicePreserveString(v interface{}) []string {
if sds, ok := v.(string); ok {
return []string{sds}
}
return cast.ToStringSlice(sd)
return cast.ToStringSlice(v)
}
// SetBaseTestDefaults provides some common config defaults used in tests.
func SetBaseTestDefaults(cfg Provider) {
cfg.Set("resourceDir", "resources")
cfg.Set("contentDir", "content")
cfg.Set("dataDir", "data")
cfg.Set("i18nDir", "i18n")
cfg.Set("layoutDir", "layouts")
cfg.Set("assetDir", "assets")
cfg.Set("archetypeDir", "archetypes")
cfg.Set("publishDir", "public")
}
+5 -5
View File
@@ -16,12 +16,12 @@ package config
import (
"testing"
qt "github.com/frankban/quicktest"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
)
func TestGetStringSlicePreserveString(t *testing.T) {
assert := require.New(t)
c := qt.New(t)
cfg := viper.New()
s := "This is a string"
@@ -30,7 +30,7 @@ func TestGetStringSlicePreserveString(t *testing.T) {
cfg.Set("s1", s)
cfg.Set("s2", sSlice)
assert.Equal([]string{s}, GetStringSlicePreserveString(cfg, "s1"))
assert.Equal(sSlice, GetStringSlicePreserveString(cfg, "s2"))
assert.Nil(GetStringSlicePreserveString(cfg, "s3"))
c.Assert(GetStringSlicePreserveString(cfg, "s1"), qt.DeepEquals, []string{s})
c.Assert(GetStringSlicePreserveString(cfg, "s2"), qt.DeepEquals, sSlice)
c.Assert(GetStringSlicePreserveString(cfg, "s3"), qt.IsNil)
}
+57
View File
@@ -0,0 +1,57 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"os"
"runtime"
"strconv"
"strings"
)
// GetNumWorkerMultiplier returns the base value used to calculate the number
// of workers to use for Hugo's parallel execution.
// It returns the value in HUGO_NUMWORKERMULTIPLIER OS env variable if set to a
// positive integer, else the number of logical CPUs.
func GetNumWorkerMultiplier() int {
if gmp := os.Getenv("HUGO_NUMWORKERMULTIPLIER"); gmp != "" {
if p, err := strconv.Atoi(gmp); err == nil && p > 0 {
return p
}
}
return runtime.NumCPU()
}
// SetEnvVars sets vars on the form key=value in the oldVars slice.
func SetEnvVars(oldVars *[]string, keyValues ...string) {
for i := 0; i < len(keyValues); i += 2 {
setEnvVar(oldVars, keyValues[i], keyValues[i+1])
}
}
func SplitEnvVar(v string) (string, string) {
parts := strings.Split(v, "=")
return parts[0], parts[1]
}
func setEnvVar(vars *[]string, key, value string) {
for i := range *vars {
if strings.HasPrefix((*vars)[i], key+"=") {
(*vars)[i] = key + "=" + value
return
}
}
// New var.
*vars = append(*vars, key+"="+value)
}
+32
View File
@@ -0,0 +1,32 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"testing"
qt "github.com/frankban/quicktest"
)
func TestSetEnvVars(t *testing.T) {
t.Parallel()
c := qt.New(t)
vars := []string{"FOO=bar", "HUGO=cool", "BAR=foo"}
SetEnvVars(&vars, "HUGO", "rocking!", "NEW", "bar")
c.Assert(vars, qt.DeepEquals, []string{"FOO=bar", "HUGO=rocking!", "BAR=foo", "NEW=bar"})
key, val := SplitEnvVar("HUGO=rocks")
c.Assert(key, qt.Equals, "HUGO")
c.Assert(val, qt.Equals, "rocks")
}

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