Compare commits

..

237 Commits

Author SHA1 Message Date
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 6f069e549b Merge commit '5a83bf314f4c0ce1d61341e0a1df21c9998e8154' 2018-12-21 09:45:41 +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
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
542 changed files with 25833 additions and 29422 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
defaults: &defaults
docker:
- image: bepsays/ci-goreleaser:1.11-2
- image: bepsays/ci-goreleaser:1.11-3
environment:
CGO_ENABLED: "0"
+4 -1
View File
@@ -1,4 +1,4 @@
hugo
/hugo
docs/public*
/.idea
hugo.exe
@@ -19,3 +19,6 @@ dock.sh
GoBuilds
dist
vendor
+19 -8
View File
@@ -2,19 +2,25 @@ language: go
sudo: false
dist: trusty
env:
HUGO_BUILD_TAGS="extended"
global:
- HUGO_BUILD_TAGS="extended"
git:
depth: false
go:
- 1.11
- "1.11.4"
- 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
@@ -22,12 +28,17 @@ install:
- 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
- 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
```
+76 -9
View File
@@ -4,16 +4,15 @@ A Fast and Flexible Static Site Generator built with love by [bep](https://githu
[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/) |
[Developer Chat (no support)](https://gitter.im/spf13/hugo) |
[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)
[![Dev chat at https://gitter.im/spf13/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)
[![Go Report Card](https://goreportcard.com/badge/github.com/gohugoio/hugo)](https://goreportcard.com/report/github.com/gohugoio/hugo)
## Overview
@@ -36,7 +35,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 +49,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 +60,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 +119,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/chaseadamsio/goorgeous](https://github.com/chaseadamsio/goorgeous) | 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/
+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
+353
View File
@@ -0,0 +1,353 @@
// 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
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) *Cache {
return &Cache{
Fs: fs,
nlocker: &lockTracker{Locker: locker.NewLocker(), seen: make(map[string]struct{})},
maxAge: maxAge,
}
}
// 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.Now().Sub(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) {
dcfg, err := decodeConfig(p)
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.Resources.Fs
} else {
cfs = fs
}
var baseDir string
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.
baseDir = filepath.Join(v.Dir, filecacheRootDirname, k)
} else {
baseDir = filepath.Join(v.Dir, k)
}
if err = cfs.MkdirAll(baseDir, 0777); err != nil && !os.IsExist(err) {
return nil, err
}
bfs := afero.NewBasePathFs(cfs, baseDir)
m[k] = NewCache(bfs, v.MaxAge)
}
return m, nil
}
func cleanID(name string) string {
return strings.TrimPrefix(filepath.Clean(name), helpers.FilePathSeparator)
}
+202
View File
@@ -0,0 +1,202 @@
// 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/helpers"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
"github.com/spf13/afero"
)
const (
cachesConfigKey = "caches"
resourcesGenDir = ":resourceDir/_gen"
)
var defaultCacheConfig = cacheConfig{
MaxAge: -1, // Never expire
Dir: ":cacheDir/:project",
}
const (
cacheKeyGetJSON = "getjson"
cacheKeyGetCSV = "getcsv"
cacheKeyImages = "images"
cacheKeyAssets = "assets"
)
var defaultCacheConfigs = map[string]cacheConfig{
cacheKeyGetJSON: defaultCacheConfig,
cacheKeyGetCSV: defaultCacheConfig,
cacheKeyImages: cacheConfig{
MaxAge: -1,
Dir: resourcesGenDir,
},
cacheKeyAssets: cacheConfig{
MaxAge: -1,
Dir: resourcesGenDir,
},
}
type cachesConfig map[string]cacheConfig
type cacheConfig 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]
}
// AssetsCache gets the file cache for assets (processed resources, SCSS etc.).
func (f Caches) AssetsCache() *Cache {
return f[cacheKeyAssets]
}
func decodeConfig(p *helpers.PathSpec) (cachesConfig, error) {
c := make(cachesConfig)
valid := make(map[string]bool)
// Add defaults
for k, v := range defaultCacheConfigs {
c[k] = v
valid[k] = true
}
cfg := p.Cfg
m := cfg.GetStringMap(cachesConfigKey)
_, isOsFs := p.Fs.Source.(*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(p, 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 disabled {
v.MaxAge = 0
}
c[k] = v
}
return c, nil
}
// Resolves :resourceDir => /myproject/resources etc., :cacheDir => ...
func resolveDirPlaceholder(p *helpers.PathSpec, placeholder string) (cacheDir string, isResource bool, err error) {
switch strings.ToLower(placeholder) {
case ":resourcedir":
return "", true, nil
case ":cachedir":
d, err := helpers.GetCacheDir(p.Fs.Source, p.Cfg)
return d, false, err
case ":project":
return filepath.Base(p.WorkingDir), false, nil
}
return "", false, errors.Errorf("%q is not a valid placeholder (valid values are :cacheDir or :resourceDir)", placeholder)
}
+207
View File
@@ -0,0 +1,207 @@
// 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/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/hugofs"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
)
func TestDecodeConfig(t *testing.T) {
t.Parallel()
assert := require.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")
assert.NoError(err)
fs := hugofs.NewMem(cfg)
p, err := helpers.NewPathSpec(fs, cfg)
assert.NoError(err)
decoded, err := decodeConfig(p)
assert.NoError(err)
assert.Equal(4, len(decoded))
c2 := decoded["getcsv"]
assert.Equal("11h0m0s", c2.MaxAge.String())
assert.Equal(filepath.FromSlash("/path/to/c2"), c2.Dir)
c3 := decoded["images"]
assert.Equal(time.Duration(-1), c3.MaxAge)
assert.Equal(filepath.FromSlash("/path/to/c3"), c3.Dir)
}
func TestDecodeConfigIgnoreCache(t *testing.T) {
t.Parallel()
assert := require.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")
assert.NoError(err)
fs := hugofs.NewMem(cfg)
p, err := helpers.NewPathSpec(fs, cfg)
assert.NoError(err)
decoded, err := decodeConfig(p)
assert.NoError(err)
assert.Equal(4, len(decoded))
for _, v := range decoded {
assert.Equal(time.Duration(0), v.MaxAge)
}
}
func TestDecodeConfigDefault(t *testing.T) {
assert := require.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 := hugofs.NewMem(cfg)
p, err := helpers.NewPathSpec(fs, cfg)
assert.NoError(err)
decoded, err := decodeConfig(p)
assert.NoError(err)
assert.Equal(4, len(decoded))
imgConfig := decoded[cacheKeyImages]
jsonConfig := decoded[cacheKeyGetJSON]
if runtime.GOOS == "windows" {
assert.Equal("_gen", imgConfig.Dir)
} else {
assert.Equal("_gen", imgConfig.Dir)
assert.Equal("/cache/thecache/hugoproject", jsonConfig.Dir)
}
assert.True(imgConfig.isResourceDir)
assert.False(jsonConfig.isResourceDir)
}
func TestDecodeConfigInvalidDir(t *testing.T) {
t.Parallel()
assert := require.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")
assert.NoError(err)
fs := hugofs.NewMem(cfg)
p, err := helpers.NewPathSpec(fs, cfg)
assert.NoError(err)
_, err = decodeConfig(p)
assert.Error(err)
}
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
}
+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 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 {
err := afero.Walk(cache.Fs, "", func(name string, info os.FileInfo, err error) error {
if info == nil {
return nil
}
name = cleanID(name)
if info.IsDir() {
f, err := cache.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 cache.Fs.Remove(name)
}
return nil
}
shouldRemove := cache.isExpired(info.ModTime())
if !shouldRemove && len(cache.nlocker.seen) > 0 {
// Remove it if it's not been touched/used in the last build.
_, seen := cache.nlocker.seen[name]
shouldRemove = !seen
}
if shouldRemove {
err := cache.Fs.Remove(name)
if err == nil {
counter++
}
return err
}
return nil
})
if err != nil {
return counter, errors.Wrapf(err, "failed to prune cache %q", k)
}
}
return counter, nil
}
+118
View File
@@ -0,0 +1,118 @@
// 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/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
"github.com/stretchr/testify/require"
)
func TestPrune(t *testing.T) {
t.Parallel()
assert := require.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"
`
cfg, err := config.FromConfigString(configStr, "toml")
assert.NoError(err)
for _, name := range []string{cacheKeyGetCSV, cacheKeyGetJSON, cacheKeyAssets, cacheKeyImages} {
msg := fmt.Sprintf("cache: %s", name)
fs := hugofs.NewMem(cfg)
p, err := helpers.NewPathSpec(fs, cfg)
assert.NoError(err)
caches, err := NewCaches(p)
assert.NoError(err)
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()
assert.NoError(err)
assert.Equal(5, count, msg)
for i := 0; i < 10; i++ {
id := fmt.Sprintf("i%d", i)
v := cache.getString(id)
if i < 5 {
assert.Equal("", v, id)
} else {
assert.Equal("abc", v, id)
}
}
caches, err = NewCaches(p)
assert.NoError(err)
cache = caches[name]
// Touch one and then prune.
cache.GetOrCreateBytes("i5", func() ([]byte, error) {
return []byte("abc"), nil
})
count, err = caches.Prune()
assert.NoError(err)
assert.Equal(4, count)
// 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 {
assert.Equal("", v, id)
} else {
assert.Equal("abc", v, id)
}
}
}
}
+257
View File
@@ -0,0 +1,257 @@
// 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"
"regexp"
"strings"
"sync"
"testing"
"time"
"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"
"github.com/stretchr/testify/require"
)
func TestFileCache(t *testing.T) {
t.Parallel()
assert := require.New(t)
tempWorkingDir, err := ioutil.TempDir("", "hugo_filecache_test_work")
assert.NoError(err)
defer os.Remove(tempWorkingDir)
tempCacheDir, err := ioutil.TempDir("", "hugo_filecache_test_cache")
assert.NoError(err)
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)
cfg, err := config.FromConfigString(configStr, "toml")
assert.NoError(err)
fs := hugofs.NewFrom(osfs, cfg)
p, err := helpers.NewPathSpec(fs, cfg)
assert.NoError(err)
caches, err := NewCaches(p)
assert.NoError(err)
cache := caches.Get("GetJSON")
assert.NotNil(cache)
assert.Equal("10h0m0s", cache.maxAge.String())
bfs, ok := cache.Fs.(*afero.BasePathFs)
assert.True(ok)
filename, err := bfs.RealPath("key")
assert.NoError(err)
if test.cacheDir != "" {
assert.Equal(filepath.Join(test.cacheDir, "c/"+filecacheRootDirname+"/getjson/key"), filename)
} else {
// Temp dir.
assert.Regexp(regexp.MustCompile(".*hugo_cache.*"+filecacheRootDirname+".*key"), filename)
}
cache = caches.Get("Images")
assert.NotNil(cache)
assert.Equal(time.Duration(-1), cache.maxAge)
bfs, ok = cache.Fs.(*afero.BasePathFs)
assert.True(ok)
filename, _ = bfs.RealPath("key")
assert.Equal(filepath.FromSlash("_gen/images/key"), filename)
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 _, c := range []*Cache{caches.ImageCache(), caches.AssetsCache(), caches.GetJSONCache(), caches.GetCSVCache()} {
for i := 0; i < 2; i++ {
info, r, err := c.GetOrCreate("a", rf("abc"))
assert.NoError(err)
assert.NotNil(r)
assert.Equal("a", info.Name)
b, _ := ioutil.ReadAll(r)
r.Close()
assert.Equal("abc", string(b))
info, b, err = c.GetOrCreateBytes("b", bf)
assert.NoError(err)
assert.NotNil(r)
assert.Equal("b", info.Name)
assert.Equal("bcd", string(b))
_, b, err = c.GetOrCreateBytes("a", bf)
assert.NoError(err)
assert.Equal("abc", string(b))
_, r, err = c.GetOrCreate("a", rf("bcd"))
assert.NoError(err)
b, _ = ioutil.ReadAll(r)
r.Close()
assert.Equal("abc", string(b))
}
}
assert.NotNil(caches.Get("getJSON"))
info, w, err := caches.ImageCache().WriteCloser("mykey")
assert.NoError(err)
assert.Equal("mykey", info.Name)
io.WriteString(w, "Hugo is great!")
w.Close()
assert.Equal("Hugo is great!", caches.ImageCache().getString("mykey"))
info, r, err := caches.ImageCache().Get("mykey")
assert.NoError(err)
assert.NotNil(r)
assert.Equal("mykey", info.Name)
b, _ := ioutil.ReadAll(r)
r.Close()
assert.Equal("Hugo is great!", string(b))
info, b, err = caches.ImageCache().GetBytes("mykey")
assert.NoError(err)
assert.Equal("mykey", info.Name)
assert.Equal("Hugo is great!", string(b))
}
}
func TestFileCacheConcurrent(t *testing.T) {
t.Parallel()
assert := require.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"
`
cfg, err := config.FromConfigString(configStr, "toml")
assert.NoError(err)
fs := hugofs.NewMem(cfg)
p, err := helpers.NewPathSpec(fs, cfg)
assert.NoError(err)
caches, err := NewCaches(p)
assert.NoError(err)
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++ {
c := caches.Get(cacheName)
assert.NotNil(c)
filename, data := filenameData(i)
_, r, err := c.GetOrCreate(filename, func() (io.ReadCloser, error) {
return hugio.ToReadCloser(strings.NewReader(data)), nil
})
assert.NoError(err)
b, _ := ioutil.ReadAll(r)
r.Close()
assert.Equal(data, string(b))
// Trigger some expiration.
time.Sleep(50 * time.Millisecond)
}
}(i)
}
wg.Wait()
}
func TestCleanID(t *testing.T) {
assert := require.New(t)
assert.Equal(filepath.FromSlash("a/b/c.txt"), cleanID(filepath.FromSlash("/a/b//c.txt")))
assert.Equal(filepath.FromSlash("a/b/c.txt"), cleanID(filepath.FromSlash("a/b//c.txt")))
}
+84
View File
@@ -0,0 +1,84 @@
// Copyright 2018 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package 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)
// Double check
if entry, found := c.cache[key]; found {
return entry.value, entry.err
}
// 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"
"github.com/stretchr/testify/require"
)
func TestNamedCache(t *testing.T) {
t.Parallel()
assert := require.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)
assert.NoError(err)
assert.Equal(1, v1)
v2, err := cache.GetOrCreate("a2", create)
assert.NoError(err)
assert.Equal(2, v2)
}
cache.Clear()
v3, err := cache.GetOrCreate("a2", create)
assert.NoError(err)
assert.Equal(3, v3)
}
func TestNamedCacheConcurrent(t *testing.T) {
t.Parallel()
assert := require.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))
assert.NoError(err)
assert.Equal(j, v)
}
}()
}
wg.Wait()
}
-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
}
+84 -23
View File
@@ -14,6 +14,16 @@
package commands
import (
"bytes"
"errors"
"io/ioutil"
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/common/hugo"
jww "github.com/spf13/jwalterweatherman"
"os"
"path/filepath"
"regexp"
@@ -21,13 +31,13 @@ import (
"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"
@@ -46,6 +56,8 @@ type commandeerHugoState 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 +81,45 @@ type commandeer struct {
serverPorts []int
languagesConfigured bool
languages langs.Languages
doLiveReload bool
fastRenderMode bool
showErrorInBrowser bool
configured bool
paused bool
// Any error from the last build.
buildErr error
}
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{}) {
@@ -105,6 +153,8 @@ func newCommandeer(mustHaveConfigFile, running bool, h *hugoBuilderCommon, f fla
doWithCommandeer: doWithCommandeer,
visitedURLs: types.NewEvictingStringQueue(10),
debounce: rebuildDebouncer,
// 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 +228,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 +249,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 +258,7 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
}
cfg.Set("workingDir", dir)
cfg.Set("environment", environment)
return nil
}
@@ -214,8 +271,18 @@ 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),
Environment: environment},
doWithCommandeer,
doWithConfig)
@@ -236,6 +303,11 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
c.languages = l
}
// Set some commonly used flags
c.doLiveReload = !c.h.buildWatch && !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 +316,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 +342,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{
@@ -302,20 +372,11 @@ 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())
@@ -331,7 +392,7 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
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)
strings.ToUpper(name), hugo.CurrentVersion.ReleaseVersion(), minVersion)
}
return nil
+44 -32
View File
@@ -16,11 +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"
)
@@ -46,9 +48,8 @@ func (b *commandsBuilder) addAll() *commandsBuilder {
newEnvCmd(),
newConfigCmd(),
newCheckCmd(),
b.newBenchmarkCmd(),
newConvertCmd(),
newNewCmd(),
b.newNewCmd(),
newListCmd(),
newImportCmd(),
newGenCmd(),
@@ -82,14 +83,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,6 +163,7 @@ 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
@@ -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,8 +190,9 @@ Complete documentation is available at http://gohugo.io/.`,
}
type hugoBuilderCommon struct {
source string
baseURL string
source string
baseURL string
environment string
buildWatch bool
@@ -204,15 +206,45 @@ 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) handleFlags(cmd *cobra.Command) {
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().StringVarP(&cc.environment, "environment", "e", "", "build environment")
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/")
@@ -234,6 +266,8 @@ func (cc *hugoBuilderCommon) handleFlags(cmd *cobra.Command) {
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 +276,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 +289,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)
}
}
}
+7 -2
View File
@@ -56,8 +56,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",
@@ -76,8 +79,9 @@ func TestCommandsPersistentFlags(t *testing.T) {
var sc *serverCmd
for _, command := range commands {
if b, ok := command.(commandsBuilderGetter); ok {
v := b.getCmmandsBuilder().hugoBuilderCommon
v := b.getCommandsBuilder().hugoBuilderCommon
assert.Equal("myconfig.toml", v.cfgFile)
assert.Equal("myconfigdir", v.cfgDir)
assert.Equal("mysource", v.source)
assert.Equal("https://example.com/b/", v.baseURL)
}
@@ -93,6 +97,7 @@ func TestCommandsPersistentFlags(t *testing.T) {
assert.True(sc.noHTTPCache)
assert.True(sc.renderToDisk)
assert.Equal(1366, sc.serverPort)
assert.Equal("testing", sc.environment)
cfg := viper.New()
sc.flagsToConfig(cfg)
@@ -160,7 +165,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")}, ""},
@@ -233,6 +237,7 @@ Single: {{ .Title }}
writeFile(t, filepath.Join(d, "layouts", "_default", "list.html"), `
List: {{ .Title }}
Environment: {{ hugo.Environment }}
`)
+93 -38
View File
@@ -14,17 +14,27 @@
package commands
import (
"bytes"
"fmt"
"io"
"strings"
"time"
"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"
src "github.com/gohugoio/hugo/source"
"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 +70,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 +79,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 +88,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 +101,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 +111,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
@@ -114,17 +126,17 @@ func (cc *convertCmd) convertContents(mark rune) error {
site.Log.FEEDBACK.Println("processing", len(site.AllPages), "content files")
for _, p := range site.AllPages {
if err := cc.convertAndSavePage(p, site, mark); err != nil {
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 *hugolib.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 {
if err := cc.convertAndSavePage(r.(*hugolib.Page), site, targetFormat); err != nil {
return err
}
}
@@ -134,61 +146,104 @@ func (cc *convertCmd) convertAndSavePage(p *hugolib.Page, site *hugolib.Site, ma
return nil
}
errMsg := fmt.Errorf("Error processing file %q", p.Path())
site.Log.INFO.Println("Attempting to convert", p.LogicalName())
newPage, err := site.NewPage(p.LogicalName())
if err != nil {
return err
}
f, _ := p.File.(src.ReadableFile)
file, err := f.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
}
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
}
newContent.Write(pf.content)
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
}
+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)
}
+328 -263
View File
@@ -18,16 +18,24 @@ package commands
import (
"fmt"
"io/ioutil"
"os/signal"
"sort"
"sync/atomic"
"github.com/gohugoio/hugo/common/hugo"
"github.com/pkg/errors"
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/terminal"
"syscall"
"github.com/gohugoio/hugo/hugolib/filesystems"
"golang.org/x/sync/errgroup"
"log"
"os"
"path/filepath"
"runtime"
@@ -36,7 +44,7 @@ import (
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/parser"
"github.com/gohugoio/hugo/parser/metadecoders"
flag "github.com/spf13/pflag"
"github.com/fsnotify/fsnotify"
@@ -85,7 +93,7 @@ func Execute(args []string) Response {
}
if err == nil {
errCount := int(jww.LogCountForLevelsGreaterThanorEqualTo(jww.LevelError))
errCount := int(loggers.GlobalErrorCounter.Count())
if errCount > 0 {
err = fmt.Errorf("logged %d errors", errCount)
} else if resp.Result != nil {
@@ -118,13 +126,13 @@ func initializeConfig(mustHaveConfigFile, running bool,
}
func (c *commandeer) createLogger(cfg config.Provider) (*jww.Notepad, error) {
func (c *commandeer) createLogger(cfg config.Provider, running bool) (*loggers.Logger, error) {
var (
logHandle = ioutil.Discard
logThreshold = jww.LevelWarn
logFile = cfg.GetString("logFile")
outHandle = os.Stdout
stdoutThreshold = jww.LevelError
stdoutThreshold = jww.LevelWarn
)
if c.h.verboseLog || c.h.logging || (c.h.logFile != "") {
@@ -155,13 +163,10 @@ func (c *commandeer) createLogger(cfg config.Provider) (*jww.Notepad, error) {
}
}
// The global logger is used in some few cases.
jww.SetLogOutput(logHandle)
jww.SetLogThreshold(logThreshold)
jww.SetStdoutThreshold(stdoutThreshold)
loggers.InitGlobalLogger(stdoutThreshold, logThreshold, outHandle, logHandle)
helpers.InitLoggers()
return jww.NewNotepad(stdoutThreshold, logThreshold, outHandle, logHandle, "", log.Ldate|log.Ltime), nil
return loggers.NewLogger(stdoutThreshold, logThreshold, outHandle, logHandle, running), nil
}
func initializeFlags(cmd *cobra.Command, cfg config.Provider) {
@@ -258,6 +263,17 @@ func setValueFromFlag(flags *flag.FlagSet, key string, cfg config.Provider, targ
}
}
func isTerminal() bool {
return terminal.IsTerminal(os.Stdout)
}
func ifTerminal(s string) string {
if !isTerminal() {
return ""
}
return s
}
func (c *commandeer) fullBuild() error {
var (
g errgroup.Group
@@ -265,19 +281,21 @@ func (c *commandeer) fullBuild() error {
)
if !c.h.quiet {
fmt.Print(hideCursor + "Building sites … ")
defer func() {
fmt.Print(showCursor + clearLine)
}()
fmt.Print(ifTerminal(hideCursor) + "Building sites … ")
if isTerminal() {
defer func() {
fmt.Print(showCursor + clearLine)
}()
}
}
copyStaticFunc := func() error {
cnt, err := c.copyStatic()
if err != nil {
if !os.IsNotExist(err) {
return fmt.Errorf("Error copying static files: %s", err)
return errors.Wrap(err, "Error copying static files")
}
c.Logger.WARN.Println("No Static directory found")
c.logger.INFO.Println("No Static directory found")
}
langCount = cnt
langCount = cnt
@@ -285,7 +303,7 @@ func (c *commandeer) fullBuild() error {
}
buildSitesFunc := func() error {
if err := c.buildSites(); err != nil {
return fmt.Errorf("Error building site: %s", err)
return errors.Wrap(err, "Error building site")
}
return nil
}
@@ -345,8 +363,8 @@ func (c *commandeer) build() error {
if err != nil {
return err
}
c.Logger.FEEDBACK.Println("Watching for changes in", c.hugo.PathSpec.AbsPathify(c.Cfg.GetString("contentDir")))
c.Logger.FEEDBACK.Println("Press Ctrl+C to stop")
c.logger.FEEDBACK.Println("Watching for changes in", c.hugo.PathSpec.AbsPathify(c.Cfg.GetString("contentDir")))
c.logger.FEEDBACK.Println("Press Ctrl+C to stop")
watcher, err := c.newWatcher(watchDirs...)
checkErr(c.Logger, err)
defer watcher.Close()
@@ -388,7 +406,7 @@ func (c *commandeer) doWithPublishDirs(f func(sourceFs *filesystems.SourceFilesy
staticFilesystems := c.hugo.BaseFs.SourceFilesystems.Static
if len(staticFilesystems) == 0 {
c.Logger.WARN.Println("No static directories found to sync")
c.logger.INFO.Println("No static directories found to sync")
return langCount, nil
}
@@ -448,13 +466,13 @@ func (c *commandeer) copyStaticTo(sourceFs *filesystems.SourceFilesystem) (uint6
syncer.Delete = c.Cfg.GetBool("cleanDestinationDir")
if syncer.Delete {
c.Logger.INFO.Println("removing all files from destination that don't exist in static dirs")
c.logger.INFO.Println("removing all files from destination that don't exist in static dirs")
syncer.DeleteFilter = func(f os.FileInfo) bool {
return f.IsDir() && strings.HasPrefix(f.Name(), ".")
}
}
c.Logger.INFO.Println("syncing static files to", publishDir)
c.logger.INFO.Println("syncing static files to", publishDir)
var err error
@@ -480,7 +498,7 @@ func (c *commandeer) timeTrack(start time.Time, name string) {
return
}
elapsed := time.Since(start)
c.Logger.FEEDBACK.Printf("%s in %v ms", name, int(1000*elapsed.Seconds()))
c.logger.FEEDBACK.Printf("%s in %v ms", name, int(1000*elapsed.Seconds()))
}
// getDirList provides NewWatcher() with a list of directories to watch for changes.
@@ -498,7 +516,7 @@ func (c *commandeer) getDirList() ([]string, error) {
return nil
}
c.Logger.ERROR.Println("Walker: ", err)
c.logger.ERROR.Println("Walker: ", err)
return nil
}
@@ -511,16 +529,16 @@ func (c *commandeer) getDirList() ([]string, error) {
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
link, err := filepath.EvalSymlinks(path)
if err != nil {
c.Logger.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", path, err)
c.logger.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", path, err)
return nil
}
linkfi, err := helpers.LstatIfPossible(c.Fs.Source, link)
if err != nil {
c.Logger.ERROR.Printf("Cannot stat %q: %s", link, err)
c.logger.ERROR.Printf("Cannot stat %q: %s", link, err)
return nil
}
if !allowSymbolicDirs && !linkfi.Mode().IsRegular() {
c.Logger.ERROR.Printf("Symbolic links for directories not supported, skipping %q", path)
c.logger.ERROR.Printf("Symbolic links for directories not supported, skipping %q", path)
return nil
}
@@ -603,7 +621,7 @@ func (c *commandeer) getDirList() ([]string, error) {
func (c *commandeer) resetAndBuildSites() (err error) {
if !c.h.quiet {
c.Logger.FEEDBACK.Println("Started building sites ...")
c.logger.FEEDBACK.Println("Started building sites ...")
}
return c.hugo.Build(hugolib.BuildCfg{ResetState: true})
}
@@ -612,12 +630,22 @@ func (c *commandeer) buildSites() (err error) {
return c.hugo.Build(hugolib.BuildCfg{})
}
func (c *commandeer) handleBuildErr(err error, msg string) {
c.buildErr = err
c.logger.ERROR.Print(msg + ":\n\n")
c.logger.ERROR.Println(helpers.FirstUpper(err.Error()))
if !c.h.quiet && c.h.verbose {
herrors.PrintStackTrace(err)
}
}
func (c *commandeer) rebuildSites(events []fsnotify.Event) error {
defer c.timeTrack(time.Now(), "Total")
c.buildErr = nil
visited := c.visitedURLs.PeekAllSet()
doLiveReload := !c.h.buildWatch && !c.Cfg.GetBool("disableLiveReload")
if doLiveReload && !c.Cfg.GetBool("disableFastRender") {
if c.fastRenderMode {
// Make sure we always render the home pages
for _, l := range c.languages {
@@ -625,7 +653,7 @@ func (c *commandeer) rebuildSites(events []fsnotify.Event) error {
if langPath != "" {
langPath = langPath + "/"
}
home := c.hugo.PathSpec.PrependBasePath("/" + langPath)
home := c.hugo.PathSpec.PrependBasePath("/"+langPath, false)
visited[home] = true
}
@@ -633,20 +661,31 @@ func (c *commandeer) rebuildSites(events []fsnotify.Event) error {
return c.hugo.Build(hugolib.BuildCfg{RecentlyVisited: visited}, events...)
}
func (c *commandeer) partialReRender(urls ...string) error {
c.buildErr = nil
visited := make(map[string]bool)
for _, url := range urls {
visited[url] = true
}
return c.hugo.Build(hugolib.BuildCfg{RecentlyVisited: visited, PartialReRender: true})
}
func (c *commandeer) fullRebuild() {
c.commandeerHugoState = &commandeerHugoState{}
err := c.loadConfig(true, true)
if err != nil {
jww.ERROR.Println("Failed to reload config:", err)
// Set the processing on pause until the state is recovered.
c.paused = true
c.handleBuildErr(err, "Failed to reload config")
} else {
c.paused = false
}
if !c.paused {
if err := c.buildSites(); err != nil {
jww.ERROR.Println(err)
err := c.buildSites()
if err != nil {
c.logger.ERROR.Println(err)
} else if !c.h.buildWatch && !c.Cfg.GetBool("disableLiveReload") {
livereload.ForceRefresh()
}
@@ -679,8 +718,8 @@ func (c *commandeer) newWatcher(dirList ...string) (*watcher.Batcher, error) {
// Identifies changes to config (config.toml) files.
configSet := make(map[string]bool)
c.logger.FEEDBACK.Println("Watching for config changes in", strings.Join(c.configFiles, ", "))
for _, configFile := range c.configFiles {
c.Logger.FEEDBACK.Println("Watching for config changes in", configFile)
watcher.Add(configFile)
configSet[configFile] = true
}
@@ -689,235 +728,14 @@ func (c *commandeer) newWatcher(dirList ...string) (*watcher.Batcher, error) {
for {
select {
case evs := <-watcher.Events:
for _, ev := range evs {
if configSet[ev.Name] {
if ev.Op&fsnotify.Chmod == fsnotify.Chmod {
continue
}
if ev.Op&fsnotify.Remove == fsnotify.Remove {
for _, configFile := range c.configFiles {
counter := 0
for watcher.Add(configFile) != nil {
counter++
if counter >= 100 {
break
}
time.Sleep(100 * time.Millisecond)
}
}
}
// Config file changed. Need full rebuild.
c.fullRebuild()
break
}
}
if c.paused {
// Wait for the server to get into a consistent state before
// we continue with processing.
continue
}
if len(evs) > 50 {
// This is probably a mass edit of the content dir.
// Schedule a full rebuild for when it slows down.
c.debounce(c.fullRebuild)
continue
}
c.Logger.INFO.Println("Received System Events:", evs)
staticEvents := []fsnotify.Event{}
dynamicEvents := []fsnotify.Event{}
// Special handling for symbolic links inside /content.
filtered := []fsnotify.Event{}
for _, ev := range evs {
// Check the most specific first, i.e. files.
contentMapped := c.hugo.ContentChanges.GetSymbolicLinkMappings(ev.Name)
if len(contentMapped) > 0 {
for _, mapped := range contentMapped {
filtered = append(filtered, fsnotify.Event{Name: mapped, Op: ev.Op})
}
continue
}
// Check for any symbolic directory mapping.
dir, name := filepath.Split(ev.Name)
contentMapped = c.hugo.ContentChanges.GetSymbolicLinkMappings(dir)
if len(contentMapped) == 0 {
filtered = append(filtered, ev)
continue
}
for _, mapped := range contentMapped {
mappedFilename := filepath.Join(mapped, name)
filtered = append(filtered, fsnotify.Event{Name: mappedFilename, Op: ev.Op})
}
}
evs = filtered
for _, ev := range evs {
ext := filepath.Ext(ev.Name)
baseName := filepath.Base(ev.Name)
istemp := strings.HasSuffix(ext, "~") ||
(ext == ".swp") || // vim
(ext == ".swx") || // vim
(ext == ".tmp") || // generic temp file
(ext == ".DS_Store") || // OSX Thumbnail
baseName == "4913" || // vim
strings.HasPrefix(ext, ".goutputstream") || // gnome
strings.HasSuffix(ext, "jb_old___") || // intelliJ
strings.HasSuffix(ext, "jb_tmp___") || // intelliJ
strings.HasSuffix(ext, "jb_bak___") || // intelliJ
strings.HasPrefix(ext, ".sb-") || // byword
strings.HasPrefix(baseName, ".#") || // emacs
strings.HasPrefix(baseName, "#") // emacs
if istemp {
continue
}
// Sometimes during rm -rf operations a '"": REMOVE' is triggered. Just ignore these
if ev.Name == "" {
continue
}
// Write and rename operations are often followed by CHMOD.
// There may be valid use cases for rebuilding the site on CHMOD,
// but that will require more complex logic than this simple conditional.
// On OS X this seems to be related to Spotlight, see:
// https://github.com/go-fsnotify/fsnotify/issues/15
// A workaround is to put your site(s) on the Spotlight exception list,
// but that may be a little mysterious for most end users.
// So, for now, we skip reload on CHMOD.
// We do have to check for WRITE though. On slower laptops a Chmod
// could be aggregated with other important events, and we still want
// to rebuild on those
if ev.Op&(fsnotify.Chmod|fsnotify.Write|fsnotify.Create) == fsnotify.Chmod {
continue
}
walkAdder := func(path string, f os.FileInfo, err error) error {
if f.IsDir() {
c.Logger.FEEDBACK.Println("adding created directory to watchlist", path)
if err := watcher.Add(path); err != nil {
return err
}
} else if !staticSyncer.isStatic(path) {
// Hugo's rebuilding logic is entirely file based. When you drop a new folder into
// /content on OSX, the above logic will handle future watching of those files,
// but the initial CREATE is lost.
dynamicEvents = append(dynamicEvents, fsnotify.Event{Name: path, Op: fsnotify.Create})
}
return nil
}
// recursively add new directories to watch list
// When mkdir -p is used, only the top directory triggers an event (at least on OSX)
if ev.Op&fsnotify.Create == fsnotify.Create {
if s, err := c.Fs.Source.Stat(ev.Name); err == nil && s.Mode().IsDir() {
_ = helpers.SymbolicWalk(c.Fs.Source, ev.Name, walkAdder)
}
}
if staticSyncer.isStatic(ev.Name) {
staticEvents = append(staticEvents, ev)
} else {
dynamicEvents = append(dynamicEvents, ev)
}
}
if len(staticEvents) > 0 {
c.Logger.FEEDBACK.Println("\nStatic file changes detected")
const layout = "2006-01-02 15:04:05.000 -0700"
c.Logger.FEEDBACK.Println(time.Now().Format(layout))
if c.Cfg.GetBool("forceSyncStatic") {
c.Logger.FEEDBACK.Printf("Syncing all static files\n")
_, err := c.copyStatic()
if err != nil {
stopOnErr(c.Logger, err, "Error copying static files to publish dir")
}
} else {
if err := staticSyncer.syncsStaticEvents(staticEvents); err != nil {
c.Logger.ERROR.Println(err)
continue
}
}
if !c.h.buildWatch && !c.Cfg.GetBool("disableLiveReload") {
// Will block forever trying to write to a channel that nobody is reading if livereload isn't initialized
// force refresh when more than one file
if len(staticEvents) == 1 {
ev := staticEvents[0]
path := c.hugo.BaseFs.SourceFilesystems.MakeStaticPathRelative(ev.Name)
path = c.firstPathSpec().RelURL(helpers.ToSlashTrimLeading(path), false)
livereload.RefreshPath(path)
} else {
livereload.ForceRefresh()
}
}
}
if len(dynamicEvents) > 0 {
partitionedEvents := partitionDynamicEvents(
c.firstPathSpec().BaseFs.SourceFilesystems,
dynamicEvents)
doLiveReload := !c.h.buildWatch && !c.Cfg.GetBool("disableLiveReload")
onePageName := pickOneWriteOrCreatePath(partitionedEvents.ContentEvents)
c.Logger.FEEDBACK.Println("\nChange detected, rebuilding site")
const layout = "2006-01-02 15:04:05.000 -0700"
c.Logger.FEEDBACK.Println(time.Now().Format(layout))
c.changeDetector.PrepareNew()
if err := c.rebuildSites(dynamicEvents); err != nil {
c.Logger.ERROR.Println("Failed to rebuild site:", err)
}
if doLiveReload {
if len(partitionedEvents.ContentEvents) == 0 && len(partitionedEvents.AssetEvents) > 0 {
changed := c.changeDetector.changed()
if c.changeDetector != nil && len(changed) == 0 {
// Nothing has changed.
continue
} else if len(changed) == 1 {
pathToRefresh := c.firstPathSpec().RelURL(helpers.ToSlashTrimLeading(changed[0]), false)
livereload.RefreshPath(pathToRefresh)
} else {
livereload.ForceRefresh()
}
}
if len(partitionedEvents.ContentEvents) > 0 {
navigate := c.Cfg.GetBool("navigateToChanged")
// We have fetched the same page above, but it may have
// changed.
var p *hugolib.Page
if navigate {
if onePageName != "" {
p = c.hugo.GetContentPage(onePageName)
}
}
if p != nil {
livereload.NavigateToPathForPort(p.RelPermalink(), p.Site.ServerPort())
} else {
livereload.ForceRefresh()
}
}
}
c.handleEvents(watcher, staticSyncer, evs, configSet)
if c.showErrorInBrowser && c.errCount() > 0 {
// Need to reload browser to show the error
livereload.ForceRefresh()
}
case err := <-watcher.Errors:
if err != nil {
c.Logger.ERROR.Println(err)
c.logger.ERROR.Println("Error while watching:", err)
}
}
}
@@ -926,6 +744,253 @@ func (c *commandeer) newWatcher(dirList ...string) (*watcher.Batcher, error) {
return watcher, nil
}
func (c *commandeer) handleEvents(watcher *watcher.Batcher,
staticSyncer *staticSyncer,
evs []fsnotify.Event,
configSet map[string]bool) {
for _, ev := range evs {
isConfig := configSet[ev.Name]
if !isConfig {
// It may be one of the /config folders
dirname := filepath.Dir(ev.Name)
if dirname != "." && configSet[dirname] {
isConfig = true
}
}
if isConfig {
if ev.Op&fsnotify.Chmod == fsnotify.Chmod {
continue
}
if ev.Op&fsnotify.Remove == fsnotify.Remove {
for _, configFile := range c.configFiles {
counter := 0
for watcher.Add(configFile) != nil {
counter++
if counter >= 100 {
break
}
time.Sleep(100 * time.Millisecond)
}
}
}
// Config file(s) changed. Need full rebuild.
c.fullRebuild()
break
}
}
if c.paused {
// Wait for the server to get into a consistent state before
// we continue with processing.
return
}
if len(evs) > 50 {
// This is probably a mass edit of the content dir.
// Schedule a full rebuild for when it slows down.
c.debounce(c.fullRebuild)
return
}
c.logger.INFO.Println("Received System Events:", evs)
staticEvents := []fsnotify.Event{}
dynamicEvents := []fsnotify.Event{}
// Special handling for symbolic links inside /content.
filtered := []fsnotify.Event{}
for _, ev := range evs {
// Check the most specific first, i.e. files.
contentMapped := c.hugo.ContentChanges.GetSymbolicLinkMappings(ev.Name)
if len(contentMapped) > 0 {
for _, mapped := range contentMapped {
filtered = append(filtered, fsnotify.Event{Name: mapped, Op: ev.Op})
}
continue
}
// Check for any symbolic directory mapping.
dir, name := filepath.Split(ev.Name)
contentMapped = c.hugo.ContentChanges.GetSymbolicLinkMappings(dir)
if len(contentMapped) == 0 {
filtered = append(filtered, ev)
continue
}
for _, mapped := range contentMapped {
mappedFilename := filepath.Join(mapped, name)
filtered = append(filtered, fsnotify.Event{Name: mappedFilename, Op: ev.Op})
}
}
evs = filtered
for _, ev := range evs {
ext := filepath.Ext(ev.Name)
baseName := filepath.Base(ev.Name)
istemp := strings.HasSuffix(ext, "~") ||
(ext == ".swp") || // vim
(ext == ".swx") || // vim
(ext == ".tmp") || // generic temp file
(ext == ".DS_Store") || // OSX Thumbnail
baseName == "4913" || // vim
strings.HasPrefix(ext, ".goutputstream") || // gnome
strings.HasSuffix(ext, "jb_old___") || // intelliJ
strings.HasSuffix(ext, "jb_tmp___") || // intelliJ
strings.HasSuffix(ext, "jb_bak___") || // intelliJ
strings.HasPrefix(ext, ".sb-") || // byword
strings.HasPrefix(baseName, ".#") || // emacs
strings.HasPrefix(baseName, "#") // emacs
if istemp {
continue
}
if c.hugo.Deps.SourceSpec.IgnoreFile(ev.Name) {
continue
}
// Sometimes during rm -rf operations a '"": REMOVE' is triggered. Just ignore these
if ev.Name == "" {
continue
}
// Write and rename operations are often followed by CHMOD.
// There may be valid use cases for rebuilding the site on CHMOD,
// but that will require more complex logic than this simple conditional.
// On OS X this seems to be related to Spotlight, see:
// https://github.com/go-fsnotify/fsnotify/issues/15
// A workaround is to put your site(s) on the Spotlight exception list,
// but that may be a little mysterious for most end users.
// So, for now, we skip reload on CHMOD.
// We do have to check for WRITE though. On slower laptops a Chmod
// could be aggregated with other important events, and we still want
// to rebuild on those
if ev.Op&(fsnotify.Chmod|fsnotify.Write|fsnotify.Create) == fsnotify.Chmod {
continue
}
walkAdder := func(path string, f os.FileInfo, err error) error {
if f.IsDir() {
c.logger.FEEDBACK.Println("adding created directory to watchlist", path)
if err := watcher.Add(path); err != nil {
return err
}
} else if !staticSyncer.isStatic(path) {
// Hugo's rebuilding logic is entirely file based. When you drop a new folder into
// /content on OSX, the above logic will handle future watching of those files,
// but the initial CREATE is lost.
dynamicEvents = append(dynamicEvents, fsnotify.Event{Name: path, Op: fsnotify.Create})
}
return nil
}
// recursively add new directories to watch list
// When mkdir -p is used, only the top directory triggers an event (at least on OSX)
if ev.Op&fsnotify.Create == fsnotify.Create {
if s, err := c.Fs.Source.Stat(ev.Name); err == nil && s.Mode().IsDir() {
_ = helpers.SymbolicWalk(c.Fs.Source, ev.Name, walkAdder)
}
}
if staticSyncer.isStatic(ev.Name) {
staticEvents = append(staticEvents, ev)
} else {
dynamicEvents = append(dynamicEvents, ev)
}
}
if len(staticEvents) > 0 {
c.logger.FEEDBACK.Println("\nStatic file changes detected")
const layout = "2006-01-02 15:04:05.000 -0700"
c.logger.FEEDBACK.Println(time.Now().Format(layout))
if c.Cfg.GetBool("forceSyncStatic") {
c.logger.FEEDBACK.Printf("Syncing all static files\n")
_, err := c.copyStatic()
if err != nil {
c.logger.ERROR.Println("Error copying static files to publish dir:", err)
return
}
} else {
if err := staticSyncer.syncsStaticEvents(staticEvents); err != nil {
c.logger.ERROR.Println("Error syncing static files to publish dir:", err)
return
}
}
if !c.h.buildWatch && !c.Cfg.GetBool("disableLiveReload") {
// Will block forever trying to write to a channel that nobody is reading if livereload isn't initialized
// force refresh when more than one file
if len(staticEvents) == 1 {
ev := staticEvents[0]
path := c.hugo.BaseFs.SourceFilesystems.MakeStaticPathRelative(ev.Name)
path = c.firstPathSpec().RelURL(helpers.ToSlashTrimLeading(path), false)
livereload.RefreshPath(path)
} else {
livereload.ForceRefresh()
}
}
}
if len(dynamicEvents) > 0 {
partitionedEvents := partitionDynamicEvents(
c.firstPathSpec().BaseFs.SourceFilesystems,
dynamicEvents)
doLiveReload := !c.h.buildWatch && !c.Cfg.GetBool("disableLiveReload")
onePageName := pickOneWriteOrCreatePath(partitionedEvents.ContentEvents)
c.logger.FEEDBACK.Println("\nChange detected, rebuilding site")
const layout = "2006-01-02 15:04:05.000 -0700"
c.logger.FEEDBACK.Println(time.Now().Format(layout))
c.changeDetector.PrepareNew()
if err := c.rebuildSites(dynamicEvents); err != nil {
c.handleBuildErr(err, "Rebuild failed")
}
if doLiveReload {
if len(partitionedEvents.ContentEvents) == 0 && len(partitionedEvents.AssetEvents) > 0 {
changed := c.changeDetector.changed()
if c.changeDetector != nil && len(changed) == 0 {
// Nothing has changed.
return
} else if len(changed) == 1 {
pathToRefresh := c.firstPathSpec().RelURL(helpers.ToSlashTrimLeading(changed[0]), false)
livereload.RefreshPath(pathToRefresh)
} else {
livereload.ForceRefresh()
}
}
if len(partitionedEvents.ContentEvents) > 0 {
navigate := c.Cfg.GetBool("navigateToChanged")
// We have fetched the same page above, but it may have
// changed.
var p *hugolib.Page
if navigate {
if onePageName != "" {
p = c.hugo.GetContentPage(onePageName)
}
}
if p != nil {
livereload.NavigateToPathForPort(p.RelPermalink(), p.Site.ServerPort())
} else {
livereload.ForceRefresh()
}
}
}
}
}
// dynamicEvents contains events that is considered dynamic, as in "not static".
// Both of these categories will trigger a new build, but the asset events
// does not fit into the "navigate to changed" logic.
@@ -980,14 +1045,14 @@ func (c *commandeer) isThemeVsHugoVersionMismatch(fs afero.Fs) (dir string, mism
b, err := afero.ReadFile(fs, path)
tomlMeta, err := parser.HandleTOMLMetaData(b)
tomlMeta, err := metadecoders.Default.UnmarshalToMap(b, metadecoders.TOML)
if err != nil {
continue
}
if minVersion, ok := tomlMeta["min_version"]; ok {
if helpers.CompareVersion(minVersion) > 0 {
if hugo.CompareVersion(minVersion) > 0 {
return absThemeDir, true, fmt.Sprint(minVersion)
}
}
+59 -26
View File
@@ -16,6 +16,7 @@ package commands
import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
@@ -24,6 +25,9 @@ import (
"strconv"
"strings"
"time"
"unicode"
"github.com/gohugoio/hugo/parser/metadecoders"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
@@ -253,7 +257,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 +266,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,15 +292,14 @@ 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)
return helpers.WriteToDisk(filepath.Join(inpath, "config."+string(kind)), &buf, fs)
}
func copyFile(source string, dest string) error {
@@ -445,39 +448,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:", filename)
}
page.SetSourceContent([]byte(content))
page.SetSourceMetaData(newmetadata, parser.FormatToLeadRune("yaml"))
page.SaveSourceAs(targetFile)
jww.TRACE.Println("Target file:", targetFile)
return nil
}
@@ -557,7 +546,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 +559,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 +569,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 ")
+3
View File
@@ -97,6 +97,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{}{},
+16 -14
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,22 @@ 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.PersistentFlags().StringVarP(&cc.source, "source", "s", "", "filesystem path to read files relative from")
cmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{})
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
}
+6 -4
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
}
+72 -20
View File
@@ -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)
@@ -271,10 +280,11 @@ func (sc *serverCmd) server(cmd *cobra.Command, args []string) error {
}
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) {
@@ -290,6 +300,8 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, erro
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 {
jww.FEEDBACK.Println("Serving pages from " + absPublishDir)
@@ -301,30 +313,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, 301)
return
}
}
}
f.c.visitedURLs.Add(p)
}
}
h.ServeHTTP(w, r)
@@ -345,6 +386,11 @@ 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()
@@ -365,11 +411,17 @@ func (c *commandeer) serve(s *serverCmd) error {
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")
@@ -392,7 +444,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 +505,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()
}
+14
View File
@@ -18,6 +18,7 @@ import (
"net/http"
"os"
"runtime"
"strings"
"testing"
"time"
@@ -67,6 +68,7 @@ func TestServer(t *testing.T) {
homeContent := helpers.ReaderToString(resp.Body)
assert.Contains(homeContent, "List: Hugo Commands")
assert.Contains(homeContent, "Environment: development")
// Stop the server.
stop <- true
@@ -113,6 +115,18 @@ func TestFixURL(t *testing.T) {
}
}
func TestRemoveErrorPrefixFromLog(t *testing.T) {
assert := require.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)
assert.False(strings.Contains(withoutError, "ERROR"), withoutError)
}
func isWindowsCI() bool {
return runtime.GOOS == "windows" && os.Getenv("CI") != ""
}
+3 -3
View File
@@ -105,10 +105,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 +117,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())
}
+114
View File
@@ -0,0 +1,114 @@
// 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 (
"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())
}
for _, v := range from {
tos = append(tos, v)
}
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
}
+78
View File
@@ -0,0 +1,78 @@
// 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 (
"fmt"
"reflect"
"testing"
"github.com/stretchr/testify/require"
)
func TestAppend(t *testing.T) {
t.Parallel()
for i, 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},
} {
errMsg := fmt.Sprintf("[%d]", i)
result, err := Append(test.start, test.addend...)
if b, ok := test.expected.(bool); ok && !b {
require.Error(t, err, errMsg)
continue
}
require.NoError(t, err, errMsg)
if !reflect.DeepEqual(test.expected, result) {
t.Fatalf("%s got\n%T: %v\nexpected\n%T: %v", errMsg, result, result, test.expected, 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()
}
+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 collections
import (
"errors"
"fmt"
"testing"
"github.com/alecthomas/assert"
)
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 {
name string
}
type tstSlicerIn2 struct {
name string
}
type tstSlicer struct {
name 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.Name()
}
func (p *tstSlicerIn2) Name() string {
return p.Name()
}
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()
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 := fmt.Sprintf("[%d] %v", i, test.args)
result := Slice(test.args...)
assert.Equal(t, test.expected, result, errMsg)
}
assert.Len(t, Slice(), 0)
}
+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 le != nil && 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"
"github.com/stretchr/testify/require"
)
func TestErrorLocator(t *testing.T) {
assert := require.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)
assert.Equal([]string{"LINE 3", "LINE 4", "This is THEONE", "LINE 6", "LINE 7"}, location.Lines)
pos := location.Position()
assert.Equal(5, pos.LineNumber)
assert.Equal(2, location.LinesPos)
assert.Equal([]string{"This is THEONE"}, locateErrorInString(`This is THEONE`, lineMatcher).Lines)
location = locateErrorInString(`L1
This is THEONE
L2
`, lineMatcher)
assert.Equal(2, location.Position().LineNumber)
assert.Equal(1, location.LinesPos)
assert.Equal([]string{"L1", "This is THEONE", "L2", ""}, location.Lines)
location = locateErrorInString(`This is THEONE
L2
`, lineMatcher)
assert.Equal(0, location.LinesPos)
assert.Equal([]string{"This is THEONE", "L2", ""}, location.Lines)
location = locateErrorInString(`L1
This THEONE
`, lineMatcher)
assert.Equal([]string{"L1", "This THEONE", ""}, location.Lines)
assert.Equal(1, location.LinesPos)
location = locateErrorInString(`L1
L2
This THEONE
`, lineMatcher)
assert.Equal([]string{"L1", "L2", "This THEONE", ""}, location.Lines)
assert.Equal(2, location.LinesPos)
location = locateErrorInString("NO MATCH", lineMatcher)
assert.Equal(-1, location.Position().LineNumber)
assert.Equal(-1, location.LinesPos)
assert.Equal(0, len(location.Lines))
lineMatcher = func(m LineMatcher) bool {
return m.LineNumber == 6
}
location = locateErrorInString(`A
B
C
D
E
F
G
H
I
J`, lineMatcher)
assert.Equal([]string{"D", "E", "F", "G", "H"}, location.Lines)
assert.Equal(6, location.Position().LineNumber)
assert.Equal(2, location.LinesPos)
// Test match EOF
lineMatcher = func(m LineMatcher) bool {
return m.LineNumber == 4
}
location = locateErrorInString(`A
B
C
`, lineMatcher)
assert.Equal([]string{"B", "C", ""}, location.Lines)
assert.Equal(4, location.Position().LineNumber)
assert.Equal(2, location.LinesPos)
offsetMatcher := func(m LineMatcher) bool {
return m.Offset == 1
}
location = locateErrorInString(`A
B
C
D
E`, offsetMatcher)
assert.Equal([]string{"A", "B", "C", "D"}, location.Lines)
assert.Equal(2, location.Position().LineNumber)
assert.Equal(1, location.LinesPos)
}
+53
View File
@@ -0,0 +1,53 @@
// Copyright 2018 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package herrors contains common Hugo errors and error related utilities.
package herrors
import (
"errors"
"fmt"
"io"
"os"
_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)
}
}
}
// 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")
+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, ""
}
}
+57
View File
@@ -0,0 +1,57 @@
// 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 (
"fmt"
"testing"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
)
func TestToLineNumberError(t *testing.T) {
t.Parallel()
assert := require.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 := fmt.Sprintf("[%d][%T]", i, got)
le, ok := got.(FileError)
assert.True(ok)
assert.True(ok, errMsg)
pos := le.Position()
assert.Equal(test.lineNumber, pos.LineNumber, errMsg)
assert.Equal(test.columnNumber, pos.ColumnNumber, errMsg)
assert.Error(errors.Cause(got))
}
}
+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
}
}
+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"
)
func TestHugoInfo(t *testing.T) {
assert := require.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("")
assert.Equal(CurrentVersion.Version(), hugoInfo.Version())
assert.IsType(VersionString(""), hugoInfo.Version())
assert.Equal(commitHash, hugoInfo.CommitHash)
assert.Equal(buildDate, hugoInfo.BuildDate)
assert.Equal("production", hugoInfo.Environment)
assert.Contains(hugoInfo.Generator(), fmt.Sprintf("Hugo %s", hugoInfo.Version()))
}
+24
View File
@@ -0,0 +1,24 @@
// 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 "github.com/gohugoio/hugo/langs"
// Site represents a site in the build. This is currently a very narrow interface,
// but the actual implementation will be richer, see hugolib.SiteInfo.
type Site interface {
Language() *langs.Language
IsServer() bool
Hugo() Info
}
@@ -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
+56 -38
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,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package helpers
package hugo
import (
"fmt"
"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 +37,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 +74,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 +92,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,36 +103,52 @@ 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 {
func version(version float32, patchVersion int, suffix string) string {
if patchVersion > 0 {
return fmt.Sprintf("%.2f.%d%s", version, patchVersion, suffix)
}
@@ -142,7 +160,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 +186,7 @@ func compareVersionsWithSuffix(inVersion float32, inPatchVersion int, suffix str
return -1
}
v, err := ParseHugoVersion(s)
v, err := ParseVersion(s)
if err != nil {
return -1
}
+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.53,
PatchLevel: 0,
Suffix: "",
}
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package helpers
package hugo
import (
"testing"
@@ -21,10 +21,10 @@ import (
)
func TestHugoVersion(t *testing.T) {
assert.Equal(t, "0.15-DEV", hugoVersion(0.15, 0, "-DEV"))
assert.Equal(t, "0.15.2-DEV", hugoVersion(0.15, 2, "-DEV"))
assert.Equal(t, "0.15-DEV", version(0.15, 0, "-DEV"))
assert.Equal(t, "0.15.2-DEV", version(0.15, 2, "-DEV"))
v := HugoVersion{Number: 0.21, PatchLevel: 0, Suffix: "-DEV"}
v := Version{Number: 0.21, PatchLevel: 0, Suffix: "-DEV"}
require.Equal(t, v.ReleaseVersion().String(), "0.21")
require.Equal(t, "0.21-DEV", v.String())
@@ -62,9 +62,9 @@ func TestCompareVersions(t *testing.T) {
}
func TestParseHugoVersion(t *testing.T) {
require.Equal(t, "0.25", MustParseHugoVersion("0.25").String())
require.Equal(t, "0.25.2", MustParseHugoVersion("0.25.2").String())
require.Equal(t, "0.25-test", MustParseHugoVersion("0.25-test").String())
require.Equal(t, "0.25-DEV", MustParseHugoVersion("0.25-DEV").String())
require.Equal(t, "0.25", MustParseVersion("0.25").String())
require.Equal(t, "0.25.2", MustParseVersion("0.25.2").String())
require.Equal(t, "0.25-test", MustParseVersion("0.25-test").String())
require.Equal(t, "0.25-DEV", MustParseVersion("0.25-DEV").String())
}
+142 -6
View File
@@ -14,24 +14,160 @@
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
// 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{}
outHandle, logHandle = getLogWriters(outHandle, logHandle)
listeners := []jww.LogListener{jww.LogCounter(errorCounter, jww.LevelError)}
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,
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"
"github.com/stretchr/testify/require"
)
// 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) {
assert := require.New(t)
l := NewWarningLogger()
l.ERROR.Println("One error")
l.ERROR.Println("Two error")
l.WARN.Println("A warning")
assert.Equal(uint64(2), l.ErrorCounter.Count())
}
+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
}
}
}
+51
View File
@@ -16,6 +16,8 @@ package maps
import (
"reflect"
"testing"
"github.com/stretchr/testify/require"
)
func TestToLower(t *testing.T) {
@@ -70,3 +72,52 @@ func TestToLower(t *testing.T) {
}
}
}
func TestRenameKeys(t *testing.T) {
assert := require.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",
)
assert.NoError(err)
renamer.Rename(m)
if !reflect.DeepEqual(expected, m) {
t.Errorf("Expected\n%#v, got\n%#v\n", expected, m)
}
}
+5 -6
View File
@@ -18,6 +18,7 @@ import (
"sort"
"sync"
"github.com/gohugoio/hugo/common/collections"
"github.com/gohugoio/hugo/common/math"
)
@@ -40,14 +41,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"
"github.com/stretchr/testify/require"
)
func TestScratchAdd(t *testing.T) {
t.Parallel()
assert := require.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"))
assert.Equal(int64(30), scratch.Get("int1"))
assert.Equal(20, scratch.Get("int2"))
scratch.Add("float1", float64(10.5))
scratch.Add("float1", float64(20.1))
assert.Equal(t, float64(30.6), scratch.Get("float1"))
assert.Equal(float64(30.6), scratch.Get("float1"))
scratch.Add("string1", "Hello ")
scratch.Add("string1", "big ")
scratch.Add("string1", "World!")
assert.Equal(t, "Hello big World!", scratch.Get("string1"))
assert.Equal("Hello big World!", scratch.Get("string1"))
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()
assert := require.New(t)
scratch := NewScratch()
_, err := scratch.Add("intSlice", []int{1, 2})
assert.Nil(t, err)
assert.NoError(err)
_, err = scratch.Add("intSlice", 3)
assert.Nil(t, err)
assert.NoError(err)
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)
assert.NoError(err)
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()
assert := require.New(t)
scratch := NewScratch()
scratch.Set("slice", []interface{}{})
_, err := scratch.Add("slice", []int{1, 2})
assert.NoError(err)
assert.Equal([]int{1, 2}, scratch.Get("slice"))
}
// https://github.com/gohugoio/hugo/issues/5361
func TestScratchAddDifferentTypedSliceToInterfaceSlice(t *testing.T) {
t.Parallel()
assert := require.New(t)
scratch := NewScratch()
scratch.Set("slice", []string{"foo"})
_, err := scratch.Add("slice", []int{1, 2})
assert.NoError(err)
assert.Equal([]interface{}{"foo", 1, 2}, scratch.Get("slice"))
}
func TestScratchSet(t *testing.T) {
t.Parallel()
assert := require.New(t)
scratch := NewScratch()
scratch.Set("key", "val")
assert.Equal(t, "val", scratch.Get("key"))
assert.Equal("val", scratch.Get("key"))
}
func TestScratchDelete(t *testing.T) {
t.Parallel()
assert := require.New(t)
scratch := NewScratch()
scratch.Set("key", "val")
scratch.Delete("key")
scratch.Add("key", "Lucy Parsons")
assert.Equal(t, "Lucy Parsons", scratch.Get("key"))
assert.Equal("Lucy Parsons", scratch.Get("key"))
}
// 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()
assert := require.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"))
assert.Equal([]interface{}{0: "Abc (updated)", 1: "Def", 2: "Lux", 3: "Zyx"}, scratch.GetSortedMapValues("key"))
}
func TestScratchGetSortedMapValues(t *testing.T) {
+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"
"github.com/stretchr/testify/require"
)
func TestPositionStringFormatter(t *testing.T) {
assert := require.New(t)
pos := Position{Filename: "/my/file.txt", LineNumber: 12, ColumnNumber: 13, Offset: 14}
assert.Equal("/my/file.txt|13|12", createPositionStringFormatter(":file|:col|:line")(pos))
assert.Equal("13|/my/file.txt|12", createPositionStringFormatter(":col|:file|:line")(pos))
assert.Equal("好:13", createPositionStringFormatter("好::col")(pos))
assert.Equal("\"/my/file.txt:12:13\"", createPositionStringFormatter("")(pos))
assert.Equal("\"/my/file.txt:12:13\"", pos.String())
}
+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()
+3
View File
@@ -36,6 +36,9 @@ func TestEvictingStringQueue(t *testing.T) {
queue.Add("a")
queue.Add("b")
assert.True(queue.Contains("a"))
assert.False(queue.Contains("foo"))
assert.Equal([]string{"b", "a"}, queue.PeekAll())
assert.Equal("b", queue.Peek())
queue.Add("c")
+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)
}
+106
View File
@@ -0,0 +1,106 @@
// 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 (
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/parser/metadecoders"
"github.com/spf13/afero"
"github.com/spf13/viper"
)
// 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()
v.AutomaticEnv()
v.SetEnvPrefix("hugo")
return v
}
-14
View File
@@ -14,11 +14,7 @@
package config
import (
"strings"
"github.com/spf13/cast"
"github.com/spf13/viper"
)
// Provider provides the configuration settings for Hugo.
@@ -34,16 +30,6 @@ 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.
+11 -7
View File
@@ -16,7 +16,9 @@ package create
import (
"bytes"
"fmt"
"github.com/pkg/errors"
"io"
"os"
"os/exec"
@@ -69,6 +71,7 @@ func NewContent(
siteUsed := false
if archetypeFilename != "" {
var err error
siteUsed, err = usesSiteVar(archetypeFs, archetypeFilename)
if err != nil {
@@ -128,14 +131,14 @@ func newContentFromDir(
// Just copy the file to destination.
in, err := sourceFs.Open(filename)
if err != nil {
return err
return errors.Wrap(err, "failed to open non-content file")
}
targetFilename := filepath.Join(targetPath, strings.TrimPrefix(filename, archetypeDir))
targetDir := filepath.Dir(targetFilename)
if err := targetFs.MkdirAll(targetDir, 0777); err != nil && !os.IsExist(err) {
return fmt.Errorf("failed to create target directory for %s: %s", targetDir, err)
return errors.Wrapf(err, "failed to create target directory for %s:", targetDir)
}
out, err := targetFs.Create(targetFilename)
@@ -156,11 +159,11 @@ func newContentFromDir(
content, err := executeArcheTypeAsTemplate(s, name, archetypeDir, targetFilename, filename)
if err != nil {
return err
return errors.Wrap(err, "failed to execute archetype template")
}
if err := helpers.SafeWriteToDisk(targetFilename, bytes.NewReader(content), targetFs); err != nil {
return err
return errors.Wrap(err, "failed to save results")
}
}
@@ -187,6 +190,7 @@ func mapArcheTypeDir(
var m archetypeMap
walkFn := func(filename string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
@@ -214,7 +218,7 @@ func mapArcheTypeDir(
}
if err := helpers.SymbolicWalk(fs, archetypeDir, walkFn); err != nil {
return m, err
return m, errors.Wrapf(err, "failed to walk archetype dir %q", archetypeDir)
}
return m, nil
@@ -223,7 +227,7 @@ func mapArcheTypeDir(
func usesSiteVar(fs afero.Fs, filename string) (bool, error) {
f, err := fs.Open(filename)
if err != nil {
return false, fmt.Errorf("failed to open archetype file: %s", err)
return false, errors.Wrap(err, "failed to open archetype file")
}
defer f.Close()
return helpers.ReaderContains(f, []byte(".Site")), nil
+4 -2
View File
@@ -20,6 +20,8 @@ import (
"strings"
"time"
"github.com/pkg/errors"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/source"
@@ -127,14 +129,14 @@ func executeArcheTypeAsTemplate(s *hugolib.Site, name, kind, targetPath, archety
templateHandler := s.Deps.Tmpl.(tpl.TemplateHandler)
templateName := "_text/" + helpers.Filename(archetypeFilename)
if err := templateHandler.AddTemplate(templateName, string(archetypeTemplate)); err != nil {
return nil, fmt.Errorf("Failed to parse archetype file %q: %s", archetypeFilename, err)
return nil, errors.Wrapf(err, "Failed to parse archetype file %q:", archetypeFilename)
}
templ, _ := templateHandler.Lookup(templateName)
var buff bytes.Buffer
if err := templ.Execute(&buff, data); err != nil {
return nil, fmt.Errorf("Failed to process archetype file %q: %s", archetypeFilename, err)
return nil, errors.Wrapf(err, "Failed to process archetype file %q:", archetypeFilename)
}
archetypeContent = []byte(archetypeShortcodeReplacementsPost.Replace(buff.String()))
+12
View File
@@ -93,6 +93,9 @@ func TestNewContentFromDir(t *testing.T) {
archetypeDir := filepath.Join("archetypes", "my-bundle")
assert.NoError(fs.Source.Mkdir(archetypeDir, 0755))
archetypeThemeDir := filepath.Join("themes", "mytheme", "archetypes", "my-theme-bundle")
assert.NoError(fs.Source.Mkdir(archetypeThemeDir, 0755))
contentFile := `
File: %s
Site Lang: {{ .Site.Language.Lang }}
@@ -107,6 +110,9 @@ i18n: {{ T "hugo" }}
assert.NoError(afero.WriteFile(fs.Source, filepath.Join(archetypeDir, "resources", "hugo1.json"), []byte(`hugo1: {{ printf "no template handling in here" }}`), 0755))
assert.NoError(afero.WriteFile(fs.Source, filepath.Join(archetypeDir, "resources", "hugo2.xml"), []byte(`hugo2: {{ printf "no template handling in here" }}`), 0755))
assert.NoError(afero.WriteFile(fs.Source, filepath.Join(archetypeThemeDir, "index.md"), []byte(fmt.Sprintf(contentFile, "index.md")), 0755))
assert.NoError(afero.WriteFile(fs.Source, filepath.Join(archetypeThemeDir, "resources", "hugo1.json"), []byte(`hugo1: {{ printf "no template handling in here" }}`), 0755))
h, err := hugolib.NewHugoSites(deps.DepsCfg{Cfg: cfg, Fs: fs})
assert.NoError(err)
assert.Equal(2, len(h.Sites))
@@ -123,6 +129,10 @@ i18n: {{ T "hugo" }}
assertContains(assert, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-post/pages/bio.md")), `File: bio.md`, `Site Lang: en`, `Name: My Post`)
assert.NoError(create.NewContent(h, "my-theme-bundle", "post/my-theme-post"))
assertContains(assert, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-theme-post/index.md")), `File: index.md`, `Site Lang: en`, `Name: My Theme Post`, `i18n: Hugo Rocks!`)
assertContains(assert, readFileFromFs(t, fs.Source, filepath.Join("content", "post/my-theme-post/resources/hugo1.json")), `hugo1: {{ printf "no template handling in here" }}`)
}
func initFs(fs *hugofs.Fs) error {
@@ -231,6 +241,8 @@ func readFileFromFs(t *testing.T, fs afero.Fs, filename string) string {
func newTestCfg(assert *require.Assertions) (*viper.Viper, *hugofs.Fs) {
cfg := `
theme = "mytheme"
[languages]
[languages.en]
+73 -10
View File
@@ -4,8 +4,11 @@ import (
"sync"
"time"
"github.com/gohugoio/hugo/common/loggers"
"github.com/pkg/errors"
"github.com/gohugoio/hugo/cache/filecache"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
@@ -25,7 +28,7 @@ import (
type Deps struct {
// The logger to use.
Log *jww.Notepad `json:"-"`
Log *loggers.Logger `json:"-"`
// Used to log errors that may repeat itself many times.
DistinctErrorLog *helpers.DistinctLogger
@@ -54,11 +57,18 @@ type Deps struct {
// The configuration to use
Cfg config.Provider `json:"-"`
// The file cache to use.
FileCaches filecache.Caches
// The translation func to use
Translate func(translationID string, args ...interface{}) string `json:"-"`
// The language in use. TODO(bep) consolidate with site
Language *langs.Language
// The site building.
Site hugo.Site
// All the output formats available for the current site.
OutputFormatsConfig output.Formats
@@ -74,6 +84,33 @@ type Deps struct {
// BuildStartListeners will be notified before a build starts.
BuildStartListeners *Listeners
*globalErrHandler
}
type globalErrHandler struct {
// Channel for some "hard to get to" build errors
buildErrors chan error
}
// SendErr sends the error on a channel to be handled later.
// This can be used in situations where returning and aborting the current
// operation isn't practical.
func (e *globalErrHandler) SendError(err error) {
if e.buildErrors != nil {
select {
case e.buildErrors <- err:
default:
}
return
}
jww.ERROR.Println(err)
}
func (e *globalErrHandler) StartErrorCollector() chan error {
e.buildErrors = make(chan error, 10)
return e.buildErrors
}
// Listeners represents an event listener.
@@ -86,6 +123,9 @@ type Listeners struct {
// Add adds a function to a Listeners instance.
func (b *Listeners) Add(f func()) {
if b == nil {
return
}
b.Lock()
defer b.Unlock()
b.listeners = append(b.listeners, f)
@@ -122,10 +162,6 @@ func (d *Deps) LoadResources() error {
return err
}
if th, ok := d.Tmpl.(tpl.TemplateHandler); ok {
th.PrintErrors()
}
return nil
}
@@ -159,13 +195,26 @@ func New(cfg DepsCfg) (*Deps, error) {
fs = hugofs.NewDefault(cfg.Language)
}
if cfg.MediaTypes == nil {
cfg.MediaTypes = media.DefaultTypes
}
if cfg.OutputFormats == nil {
cfg.OutputFormats = output.DefaultFormats
}
ps, err := helpers.NewPathSpec(fs, cfg.Language)
if err != nil {
return nil, err
}
resourceSpec, err := resource.NewSpec(ps, logger, cfg.OutputFormats, cfg.MediaTypes)
fileCaches, err := filecache.NewCaches(ps)
if err != nil {
return nil, errors.WithMessage(err, "failed to create file caches from configuration")
}
resourceSpec, err := resource.NewSpec(ps, fileCaches, logger, cfg.OutputFormats, cfg.MediaTypes)
if err != nil {
return nil, err
}
@@ -197,8 +246,11 @@ func New(cfg DepsCfg) (*Deps, error) {
ResourceSpec: resourceSpec,
Cfg: cfg.Language,
Language: cfg.Language,
Site: cfg.Site,
FileCaches: fileCaches,
BuildStartListeners: &Listeners{},
Timeout: time.Duration(timeoutms) * time.Millisecond,
globalErrHandler: &globalErrHandler{},
}
if cfg.Cfg.GetBool("templateMetrics") {
@@ -210,7 +262,7 @@ func New(cfg DepsCfg) (*Deps, error) {
// ForLanguage creates a copy of the Deps with the language dependent
// parts switched out.
func (d Deps) ForLanguage(cfg DepsCfg) (*Deps, error) {
func (d Deps) ForLanguage(cfg DepsCfg, onCreated func(d *Deps) error) (*Deps, error) {
l := cfg.Language
var err error
@@ -224,10 +276,12 @@ func (d Deps) ForLanguage(cfg DepsCfg) (*Deps, error) {
return nil, err
}
d.Site = cfg.Site
// The resource cache is global so reuse.
// TODO(bep) clean up these inits.
resourceCache := d.ResourceSpec.ResourceCache
d.ResourceSpec, err = resource.NewSpec(d.PathSpec, d.Log, cfg.OutputFormats, cfg.MediaTypes)
d.ResourceSpec, err = resource.NewSpec(d.PathSpec, d.ResourceSpec.FileCaches, d.Log, cfg.OutputFormats, cfg.MediaTypes)
if err != nil {
return nil, err
}
@@ -236,6 +290,12 @@ func (d Deps) ForLanguage(cfg DepsCfg) (*Deps, error) {
d.Cfg = l
d.Language = l
if onCreated != nil {
if err = onCreated(&d); err != nil {
return nil, err
}
}
if err := d.translationProvider.Clone(&d); err != nil {
return nil, err
}
@@ -256,7 +316,7 @@ func (d Deps) ForLanguage(cfg DepsCfg) (*Deps, error) {
type DepsCfg struct {
// The Logger to use.
Logger *jww.Notepad
Logger *loggers.Logger
// The file systems to use
Fs *hugofs.Fs
@@ -264,6 +324,9 @@ type DepsCfg struct {
// The language to use.
Language *langs.Language
// The Site in use
Site hugo.Site
// The configuration to use.
Cfg config.Provider
+21
View File
@@ -0,0 +1,21 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 120
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 30
# Issues with these labels will never be considered stale
exemptLabels:
- Keep
- Security
# Label to use when marking an issue as stale
staleLabel: Stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. The resources of the Hugo team are limited, and so we are asking for your help.
If you still think this is important, please tell us why.
This issue will automatically be closed in the near future if no further activity occurs. Thank you for all your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false
+8
View File
@@ -0,0 +1,8 @@
Add some **general info** about {{ replace .Name "-" " " | title }} here.
The site is built by:
* [Person 1](https://example.com)
* [Person 1](https://example.com)
Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

+37
View File
@@ -0,0 +1,37 @@
---
title: {{ replace .Name "-" " " | title }}
date: {{ now.Format "2006-01-02" }}
description: "A short description of this page."
# The URL to the site on the internet.
siteURL: https://gohugo.io/
# Link to the site's Hugo source code if public and you can/want to share.
# Remove or leave blank if not needed/wanted.
siteSource: https://github.com/gohugoio/hugoDocs
# Add credit to the article author. Leave blank or remove if not needed/wanted.
byline: "[bep](https://github.com/bep), Hugo Lead"
---
To complete this showcase:
1. Write the story about your site in this file.
2. Add a summary to the `bio.md` file in this folder.
3. Replace the `featured-template.png` with a screenshot of your site. You can rename it, but it must contain the word `featured`.
4. Create a new pull request in https://github.com/gohugoio/hugoDocs/pulls
The content of this bundle explained:
index.md
: The main content file. Fill in required front matter metadata and write your story. I does not have to be a novel. It can even be self-promotional, but it should include Hugo in some form.
bio.md
: A short summary of the website. Site credits (who built it) fits nicely here.
featured.png
: A reasonably sized screenshot of your website. It can be named anything, but the name must start with "featured". The sample image is `1500x750` (2:1 aspect ratio).
+2 -2
View File
@@ -69,7 +69,7 @@ twitter = "GoHugoIO"
[params]
description = "The worlds fastest framework for building websites"
## Used for views in rendered HTML (i.e., rather than using the .Hugo variable)
release = "0.49"
release = "0.53"
## Setting this to true will add a "noindex" to *EVERY* page on the site
removefromexternalsearch = false
## Gh repo for site footer (include trailing slash)
@@ -107,7 +107,7 @@ twitter = "GoHugoIO"
# Note that you can also set this per image processing.
resampleFilter = "CatmullRom"
# Defatult JPEG quality setting. Default is 75.
# Default JPEG quality setting. Default is 75.
quality = 75
anchor = "smart"
+1 -1
View File
@@ -199,7 +199,7 @@ You can configure an `imaging` section in `config.toml` with default image proce
# See https://github.com/disintegration/imaging
resampleFilter = "box"
# Defatult JPEG quality setting. Default is 75.
# Default JPEG quality setting. Default is 75.
quality = 68
```
+4 -3
View File
@@ -1,5 +1,5 @@
---
date: 2018-09-24
date: 2018-12-23
title: "hugo"
slug: hugo
url: /commands/hugo/
@@ -31,11 +31,13 @@ hugo [flags]
--cacheDir string filesystem path to cache directory. Defaults: $TMPDIR/hugo_cache/
--cleanDestinationDir remove files from destination not found in static directories
--config string config file (default is path/config.yaml|json|toml)
--configDir string config dir (default "config")
-c, --contentDir string filesystem path to content directory
--debug debug output
-d, --destination string filesystem path to write files to
--disableKinds strings disable different kind of pages (home, RSS etc.)
--enableGitInfo add Git revision, date and author info to the pages
-e, --environment string build environment
--forceSyncStatic copy all files when static is changed.
--gc enable to run some cleanup tasks (remove unused cache files) after the build
-h, --help help for hugo
@@ -62,7 +64,6 @@ hugo [flags]
### SEE ALSO
* [hugo benchmark](/commands/hugo_benchmark/) - Benchmark Hugo by building a site a number of times.
* [hugo check](/commands/hugo_check/) - Contains some verification checks
* [hugo config](/commands/hugo_config/) - Print the site configuration
* [hugo convert](/commands/hugo_convert/) - Convert your content to different formats
@@ -74,4 +75,4 @@ hugo [flags]
* [hugo server](/commands/hugo_server/) - A high performance webserver
* [hugo version](/commands/hugo_version/) - Print the version number of Hugo
###### Auto generated by spf13/cobra on 24-Sep-2018
###### Auto generated by spf13/cobra on 23-Dec-2018
+12 -9
View File
@@ -1,5 +1,5 @@
---
date: 2018-09-24
date: 2018-12-12
title: "hugo benchmark"
slug: hugo_benchmark
url: /commands/hugo_benchmark/
@@ -32,6 +32,7 @@ hugo benchmark [flags]
-d, --destination string filesystem path to write files to
--disableKinds strings disable different kind of pages (home, RSS etc.)
--enableGitInfo add Git revision, date and author info to the pages
-e, --environment string build environment
--forceSyncStatic copy all files when static is changed.
--gc enable to run some cleanup tasks (remove unused cache files) after the build
-h, --help help for benchmark
@@ -39,6 +40,7 @@ hugo benchmark [flags]
--ignoreCache ignores the cache directory
-l, --layoutDir string filesystem path to layout directory
--memprofile string path/filename for the memory profile file
--minify minify any supported output format (HTML, XML etc.)
--noChmod don't sync permission mode of files
--noTimes don't sync modification time of files
--renderToMemory render to memory (only useful for benchmark testing)
@@ -53,17 +55,18 @@ hugo benchmark [flags]
### Options inherited from parent commands
```
--config string config file (default is path/config.yaml|json|toml)
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-v, --verbose verbose output
--verboseLog verbose logging
--config string config file (default is path/config.yaml|json|toml)
--configDir string config dir (default "config")
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-v, --verbose verbose output
--verboseLog verbose logging
```
### SEE ALSO
* [hugo](/commands/hugo/) - hugo builds your site
###### Auto generated by spf13/cobra on 24-Sep-2018
###### Auto generated by spf13/cobra on 12-Dec-2018
+10 -9
View File
@@ -1,5 +1,5 @@
---
date: 2018-09-24
date: 2018-12-23
title: "hugo check"
slug: hugo_check
url: /commands/hugo_check/
@@ -21,13 +21,14 @@ Contains some verification checks
### Options inherited from parent commands
```
--config string config file (default is path/config.yaml|json|toml)
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-v, --verbose verbose output
--verboseLog verbose logging
--config string config file (default is path/config.yaml|json|toml)
--configDir string config dir (default "config")
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-v, --verbose verbose output
--verboseLog verbose logging
```
### SEE ALSO
@@ -35,4 +36,4 @@ Contains some verification checks
* [hugo](/commands/hugo/) - hugo builds your site
* [hugo check ulimit](/commands/hugo_check_ulimit/) - Check system ulimit settings
###### Auto generated by spf13/cobra on 24-Sep-2018
###### Auto generated by spf13/cobra on 23-Dec-2018
+10 -9
View File
@@ -1,5 +1,5 @@
---
date: 2018-09-24
date: 2018-12-23
title: "hugo check ulimit"
slug: hugo_check_ulimit
url: /commands/hugo_check_ulimit/
@@ -26,17 +26,18 @@ hugo check ulimit [flags]
### Options inherited from parent commands
```
--config string config file (default is path/config.yaml|json|toml)
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-v, --verbose verbose output
--verboseLog verbose logging
--config string config file (default is path/config.yaml|json|toml)
--configDir string config dir (default "config")
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-v, --verbose verbose output
--verboseLog verbose logging
```
### SEE ALSO
* [hugo check](/commands/hugo_check/) - Contains some verification checks
###### Auto generated by spf13/cobra on 24-Sep-2018
###### Auto generated by spf13/cobra on 23-Dec-2018
+10 -9
View File
@@ -1,5 +1,5 @@
---
date: 2018-09-24
date: 2018-12-23
title: "hugo config"
slug: hugo_config
url: /commands/hugo_config/
@@ -26,17 +26,18 @@ hugo config [flags]
### Options inherited from parent commands
```
--config string config file (default is path/config.yaml|json|toml)
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-v, --verbose verbose output
--verboseLog verbose logging
--config string config file (default is path/config.yaml|json|toml)
--configDir string config dir (default "config")
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-v, --verbose verbose output
--verboseLog verbose logging
```
### SEE ALSO
* [hugo](/commands/hugo/) - hugo builds your site
###### Auto generated by spf13/cobra on 24-Sep-2018
###### Auto generated by spf13/cobra on 23-Dec-2018
+10 -9
View File
@@ -1,5 +1,5 @@
---
date: 2018-09-24
date: 2018-12-23
title: "hugo convert"
slug: hugo_convert
url: /commands/hugo_convert/
@@ -26,13 +26,14 @@ See convert's subcommands toJSON, toTOML and toYAML for more information.
### Options inherited from parent commands
```
--config string config file (default is path/config.yaml|json|toml)
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-v, --verbose verbose output
--verboseLog verbose logging
--config string config file (default is path/config.yaml|json|toml)
--configDir string config dir (default "config")
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-v, --verbose verbose output
--verboseLog verbose logging
```
### SEE ALSO
@@ -42,4 +43,4 @@ See convert's subcommands toJSON, toTOML and toYAML for more information.
* [hugo convert toTOML](/commands/hugo_convert_totoml/) - Convert front matter to TOML
* [hugo convert toYAML](/commands/hugo_convert_toyaml/) - Convert front matter to YAML
###### Auto generated by spf13/cobra on 24-Sep-2018
###### Auto generated by spf13/cobra on 23-Dec-2018
+13 -12
View File
@@ -1,5 +1,5 @@
---
date: 2018-09-24
date: 2018-12-23
title: "hugo convert toJSON"
slug: hugo_convert_toJSON
url: /commands/hugo_convert_tojson/
@@ -26,20 +26,21 @@ hugo convert toJSON [flags]
### Options inherited from parent commands
```
--config string config file (default is path/config.yaml|json|toml)
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
-o, --output string filesystem path to write files to
--quiet build in quiet mode
-s, --source string filesystem path to read files relative from
--unsafe enable less safe operations, please backup first
-v, --verbose verbose output
--verboseLog verbose logging
--config string config file (default is path/config.yaml|json|toml)
--configDir string config dir (default "config")
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
-o, --output string filesystem path to write files to
--quiet build in quiet mode
-s, --source string filesystem path to read files relative from
--unsafe enable less safe operations, please backup first
-v, --verbose verbose output
--verboseLog verbose logging
```
### SEE ALSO
* [hugo convert](/commands/hugo_convert/) - Convert your content to different formats
###### Auto generated by spf13/cobra on 24-Sep-2018
###### Auto generated by spf13/cobra on 23-Dec-2018
+13 -12
View File
@@ -1,5 +1,5 @@
---
date: 2018-09-24
date: 2018-12-23
title: "hugo convert toTOML"
slug: hugo_convert_toTOML
url: /commands/hugo_convert_totoml/
@@ -26,20 +26,21 @@ hugo convert toTOML [flags]
### Options inherited from parent commands
```
--config string config file (default is path/config.yaml|json|toml)
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
-o, --output string filesystem path to write files to
--quiet build in quiet mode
-s, --source string filesystem path to read files relative from
--unsafe enable less safe operations, please backup first
-v, --verbose verbose output
--verboseLog verbose logging
--config string config file (default is path/config.yaml|json|toml)
--configDir string config dir (default "config")
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
-o, --output string filesystem path to write files to
--quiet build in quiet mode
-s, --source string filesystem path to read files relative from
--unsafe enable less safe operations, please backup first
-v, --verbose verbose output
--verboseLog verbose logging
```
### SEE ALSO
* [hugo convert](/commands/hugo_convert/) - Convert your content to different formats
###### Auto generated by spf13/cobra on 24-Sep-2018
###### Auto generated by spf13/cobra on 23-Dec-2018
+13 -12
View File
@@ -1,5 +1,5 @@
---
date: 2018-09-24
date: 2018-12-23
title: "hugo convert toYAML"
slug: hugo_convert_toYAML
url: /commands/hugo_convert_toyaml/
@@ -26,20 +26,21 @@ hugo convert toYAML [flags]
### Options inherited from parent commands
```
--config string config file (default is path/config.yaml|json|toml)
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
-o, --output string filesystem path to write files to
--quiet build in quiet mode
-s, --source string filesystem path to read files relative from
--unsafe enable less safe operations, please backup first
-v, --verbose verbose output
--verboseLog verbose logging
--config string config file (default is path/config.yaml|json|toml)
--configDir string config dir (default "config")
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
-o, --output string filesystem path to write files to
--quiet build in quiet mode
-s, --source string filesystem path to read files relative from
--unsafe enable less safe operations, please backup first
-v, --verbose verbose output
--verboseLog verbose logging
```
### SEE ALSO
* [hugo convert](/commands/hugo_convert/) - Convert your content to different formats
###### Auto generated by spf13/cobra on 24-Sep-2018
###### Auto generated by spf13/cobra on 23-Dec-2018
+10 -9
View File
@@ -1,5 +1,5 @@
---
date: 2018-09-24
date: 2018-12-23
title: "hugo env"
slug: hugo_env
url: /commands/hugo_env/
@@ -25,17 +25,18 @@ hugo env [flags]
### Options inherited from parent commands
```
--config string config file (default is path/config.yaml|json|toml)
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-v, --verbose verbose output
--verboseLog verbose logging
--config string config file (default is path/config.yaml|json|toml)
--configDir string config dir (default "config")
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-v, --verbose verbose output
--verboseLog verbose logging
```
### SEE ALSO
* [hugo](/commands/hugo/) - hugo builds your site
###### Auto generated by spf13/cobra on 24-Sep-2018
###### Auto generated by spf13/cobra on 23-Dec-2018
+10 -9
View File
@@ -1,5 +1,5 @@
---
date: 2018-09-24
date: 2018-12-23
title: "hugo gen"
slug: hugo_gen
url: /commands/hugo_gen/
@@ -21,13 +21,14 @@ A collection of several useful generators.
### Options inherited from parent commands
```
--config string config file (default is path/config.yaml|json|toml)
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-v, --verbose verbose output
--verboseLog verbose logging
--config string config file (default is path/config.yaml|json|toml)
--configDir string config dir (default "config")
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-v, --verbose verbose output
--verboseLog verbose logging
```
### SEE ALSO
@@ -38,4 +39,4 @@ A collection of several useful generators.
* [hugo gen doc](/commands/hugo_gen_doc/) - Generate Markdown documentation for the Hugo CLI.
* [hugo gen man](/commands/hugo_gen_man/) - Generate man pages for the Hugo CLI
###### Auto generated by spf13/cobra on 24-Sep-2018
###### Auto generated by spf13/cobra on 23-Dec-2018
@@ -1,5 +1,5 @@
---
date: 2018-09-24
date: 2018-12-23
title: "hugo gen autocomplete"
slug: hugo_gen_autocomplete
url: /commands/hugo_gen_autocomplete/
@@ -43,17 +43,18 @@ hugo gen autocomplete [flags]
### Options inherited from parent commands
```
--config string config file (default is path/config.yaml|json|toml)
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-v, --verbose verbose output
--verboseLog verbose logging
--config string config file (default is path/config.yaml|json|toml)
--configDir string config dir (default "config")
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-v, --verbose verbose output
--verboseLog verbose logging
```
### SEE ALSO
* [hugo gen](/commands/hugo_gen/) - A collection of several useful generators.
###### Auto generated by spf13/cobra on 24-Sep-2018
###### Auto generated by spf13/cobra on 23-Dec-2018
@@ -1,5 +1,5 @@
---
date: 2018-09-24
date: 2018-12-23
title: "hugo gen chromastyles"
slug: hugo_gen_chromastyles
url: /commands/hugo_gen_chromastyles/
@@ -30,17 +30,18 @@ hugo gen chromastyles [flags]
### Options inherited from parent commands
```
--config string config file (default is path/config.yaml|json|toml)
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-v, --verbose verbose output
--verboseLog verbose logging
--config string config file (default is path/config.yaml|json|toml)
--configDir string config dir (default "config")
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-v, --verbose verbose output
--verboseLog verbose logging
```
### SEE ALSO
* [hugo gen](/commands/hugo_gen/) - A collection of several useful generators.
###### Auto generated by spf13/cobra on 24-Sep-2018
###### Auto generated by spf13/cobra on 23-Dec-2018
+10 -9
View File
@@ -1,5 +1,5 @@
---
date: 2018-09-24
date: 2018-12-23
title: "hugo gen doc"
slug: hugo_gen_doc
url: /commands/hugo_gen_doc/
@@ -32,17 +32,18 @@ hugo gen doc [flags]
### Options inherited from parent commands
```
--config string config file (default is path/config.yaml|json|toml)
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-v, --verbose verbose output
--verboseLog verbose logging
--config string config file (default is path/config.yaml|json|toml)
--configDir string config dir (default "config")
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-v, --verbose verbose output
--verboseLog verbose logging
```
### SEE ALSO
* [hugo gen](/commands/hugo_gen/) - A collection of several useful generators.
###### Auto generated by spf13/cobra on 24-Sep-2018
###### Auto generated by spf13/cobra on 23-Dec-2018
+10 -9
View File
@@ -1,5 +1,5 @@
---
date: 2018-09-24
date: 2018-12-23
title: "hugo gen man"
slug: hugo_gen_man
url: /commands/hugo_gen_man/
@@ -28,17 +28,18 @@ hugo gen man [flags]
### Options inherited from parent commands
```
--config string config file (default is path/config.yaml|json|toml)
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-v, --verbose verbose output
--verboseLog verbose logging
--config string config file (default is path/config.yaml|json|toml)
--configDir string config dir (default "config")
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-v, --verbose verbose output
--verboseLog verbose logging
```
### SEE ALSO
* [hugo gen](/commands/hugo_gen/) - A collection of several useful generators.
###### Auto generated by spf13/cobra on 24-Sep-2018
###### Auto generated by spf13/cobra on 23-Dec-2018
+10 -9
View File
@@ -1,5 +1,5 @@
---
date: 2018-09-24
date: 2018-12-23
title: "hugo import"
slug: hugo_import
url: /commands/hugo_import/
@@ -23,13 +23,14 @@ Import requires a subcommand, e.g. `hugo import jekyll jekyll_root_path target_p
### Options inherited from parent commands
```
--config string config file (default is path/config.yaml|json|toml)
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-v, --verbose verbose output
--verboseLog verbose logging
--config string config file (default is path/config.yaml|json|toml)
--configDir string config dir (default "config")
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-v, --verbose verbose output
--verboseLog verbose logging
```
### SEE ALSO
@@ -37,4 +38,4 @@ Import requires a subcommand, e.g. `hugo import jekyll jekyll_root_path target_p
* [hugo](/commands/hugo/) - hugo builds your site
* [hugo import jekyll](/commands/hugo_import_jekyll/) - hugo import from Jekyll
###### Auto generated by spf13/cobra on 24-Sep-2018
###### Auto generated by spf13/cobra on 23-Dec-2018
+10 -9
View File
@@ -1,5 +1,5 @@
---
date: 2018-09-24
date: 2018-12-23
title: "hugo import jekyll"
slug: hugo_import_jekyll
url: /commands/hugo_import_jekyll/
@@ -28,17 +28,18 @@ hugo import jekyll [flags]
### Options inherited from parent commands
```
--config string config file (default is path/config.yaml|json|toml)
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-v, --verbose verbose output
--verboseLog verbose logging
--config string config file (default is path/config.yaml|json|toml)
--configDir string config dir (default "config")
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-v, --verbose verbose output
--verboseLog verbose logging
```
### SEE ALSO
* [hugo import](/commands/hugo_import/) - Import your site from others.
###### Auto generated by spf13/cobra on 24-Sep-2018
###### Auto generated by spf13/cobra on 23-Dec-2018
+10 -9
View File
@@ -1,5 +1,5 @@
---
date: 2018-09-24
date: 2018-12-23
title: "hugo list"
slug: hugo_list
url: /commands/hugo_list/
@@ -24,13 +24,14 @@ List requires a subcommand, e.g. `hugo list drafts`.
### Options inherited from parent commands
```
--config string config file (default is path/config.yaml|json|toml)
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-v, --verbose verbose output
--verboseLog verbose logging
--config string config file (default is path/config.yaml|json|toml)
--configDir string config dir (default "config")
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-v, --verbose verbose output
--verboseLog verbose logging
```
### SEE ALSO
@@ -40,4 +41,4 @@ List requires a subcommand, e.g. `hugo list drafts`.
* [hugo list expired](/commands/hugo_list_expired/) - List all posts already expired
* [hugo list future](/commands/hugo_list_future/) - List all posts dated in the future
###### Auto generated by spf13/cobra on 24-Sep-2018
###### Auto generated by spf13/cobra on 23-Dec-2018
+11 -10
View File
@@ -1,5 +1,5 @@
---
date: 2018-09-24
date: 2018-12-23
title: "hugo list drafts"
slug: hugo_list_drafts
url: /commands/hugo_list_drafts/
@@ -25,18 +25,19 @@ hugo list drafts [flags]
### Options inherited from parent commands
```
--config string config file (default is path/config.yaml|json|toml)
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-s, --source string filesystem path to read files relative from
-v, --verbose verbose output
--verboseLog verbose logging
--config string config file (default is path/config.yaml|json|toml)
--configDir string config dir (default "config")
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-s, --source string filesystem path to read files relative from
-v, --verbose verbose output
--verboseLog verbose logging
```
### SEE ALSO
* [hugo list](/commands/hugo_list/) - Listing out various types of content
###### Auto generated by spf13/cobra on 24-Sep-2018
###### Auto generated by spf13/cobra on 23-Dec-2018
+11 -10
View File
@@ -1,5 +1,5 @@
---
date: 2018-09-24
date: 2018-12-23
title: "hugo list expired"
slug: hugo_list_expired
url: /commands/hugo_list_expired/
@@ -26,18 +26,19 @@ hugo list expired [flags]
### Options inherited from parent commands
```
--config string config file (default is path/config.yaml|json|toml)
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-s, --source string filesystem path to read files relative from
-v, --verbose verbose output
--verboseLog verbose logging
--config string config file (default is path/config.yaml|json|toml)
--configDir string config dir (default "config")
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-s, --source string filesystem path to read files relative from
-v, --verbose verbose output
--verboseLog verbose logging
```
### SEE ALSO
* [hugo list](/commands/hugo_list/) - Listing out various types of content
###### Auto generated by spf13/cobra on 24-Sep-2018
###### Auto generated by spf13/cobra on 23-Dec-2018
+11 -10
View File
@@ -1,5 +1,5 @@
---
date: 2018-09-24
date: 2018-12-23
title: "hugo list future"
slug: hugo_list_future
url: /commands/hugo_list_future/
@@ -26,18 +26,19 @@ hugo list future [flags]
### Options inherited from parent commands
```
--config string config file (default is path/config.yaml|json|toml)
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-s, --source string filesystem path to read files relative from
-v, --verbose verbose output
--verboseLog verbose logging
--config string config file (default is path/config.yaml|json|toml)
--configDir string config dir (default "config")
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-s, --source string filesystem path to read files relative from
-v, --verbose verbose output
--verboseLog verbose logging
```
### SEE ALSO
* [hugo list](/commands/hugo_list/) - Listing out various types of content
###### Auto generated by spf13/cobra on 24-Sep-2018
###### Auto generated by spf13/cobra on 23-Dec-2018
+40 -13
View File
@@ -1,5 +1,5 @@
---
date: 2018-09-24
date: 2018-12-23
title: "hugo new"
slug: hugo_new
url: /commands/hugo_new/
@@ -17,6 +17,8 @@ You can also specify the kind with `-k KIND`.
If archetypes are provided in your theme or site, they will be used.
Ensure you run this within the root directory of your site.
```
hugo new [path] [flags]
```
@@ -24,22 +26,47 @@ hugo new [path] [flags]
### Options
```
--editor string edit new content with this editor, if provided
-h, --help help for new
-k, --kind string content type to create
-s, --source string filesystem path to read files relative from
-b, --baseURL string hostname (and path) to the root, e.g. http://spf13.com/
-D, --buildDrafts include content marked as draft
-E, --buildExpired include expired content
-F, --buildFuture include content with publishdate in the future
--cacheDir string filesystem path to cache directory. Defaults: $TMPDIR/hugo_cache/
--cleanDestinationDir remove files from destination not found in static directories
-c, --contentDir string filesystem path to content directory
-d, --destination string filesystem path to write files to
--disableKinds strings disable different kind of pages (home, RSS etc.)
--editor string edit new content with this editor, if provided
--enableGitInfo add Git revision, date and author info to the pages
-e, --environment string build environment
--forceSyncStatic copy all files when static is changed.
--gc enable to run some cleanup tasks (remove unused cache files) after the build
-h, --help help for new
--i18n-warnings print missing translations
--ignoreCache ignores the cache directory
-k, --kind string content type to create
-l, --layoutDir string filesystem path to layout directory
--minify minify any supported output format (HTML, XML etc.)
--noChmod don't sync permission mode of files
--noTimes don't sync modification time of files
-s, --source string filesystem path to read files relative from
--stepAnalysis display memory and timing of different steps of the program
--templateMetrics display metrics about template executions
--templateMetricsHints calculate some improvement hints when combined with --templateMetrics
-t, --theme string theme to use (located in /themes/THEMENAME/)
--themesDir string filesystem path to themes directory
```
### Options inherited from parent commands
```
--config string config file (default is path/config.yaml|json|toml)
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-v, --verbose verbose output
--verboseLog verbose logging
--config string config file (default is path/config.yaml|json|toml)
--configDir string config dir (default "config")
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-v, --verbose verbose output
--verboseLog verbose logging
```
### SEE ALSO
@@ -48,4 +75,4 @@ hugo new [path] [flags]
* [hugo new site](/commands/hugo_new_site/) - Create a new site (skeleton)
* [hugo new theme](/commands/hugo_new_theme/) - Create a new theme
###### Auto generated by spf13/cobra on 24-Sep-2018
###### Auto generated by spf13/cobra on 23-Dec-2018
+11 -10
View File
@@ -1,5 +1,5 @@
---
date: 2018-09-24
date: 2018-12-23
title: "hugo new site"
slug: hugo_new_site
url: /commands/hugo_new_site/
@@ -29,18 +29,19 @@ hugo new site [path] [flags]
### Options inherited from parent commands
```
--config string config file (default is path/config.yaml|json|toml)
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-s, --source string filesystem path to read files relative from
-v, --verbose verbose output
--verboseLog verbose logging
--config string config file (default is path/config.yaml|json|toml)
--configDir string config dir (default "config")
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-s, --source string filesystem path to read files relative from
-v, --verbose verbose output
--verboseLog verbose logging
```
### SEE ALSO
* [hugo new](/commands/hugo_new/) - Create new content for your site
###### Auto generated by spf13/cobra on 24-Sep-2018
###### Auto generated by spf13/cobra on 23-Dec-2018
+11 -10
View File
@@ -1,5 +1,5 @@
---
date: 2018-09-24
date: 2018-12-23
title: "hugo new theme"
slug: hugo_new_theme
url: /commands/hugo_new_theme/
@@ -28,18 +28,19 @@ hugo new theme [name] [flags]
### Options inherited from parent commands
```
--config string config file (default is path/config.yaml|json|toml)
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-s, --source string filesystem path to read files relative from
-v, --verbose verbose output
--verboseLog verbose logging
--config string config file (default is path/config.yaml|json|toml)
--configDir string config dir (default "config")
--debug debug output
--log enable Logging
--logFile string log File path (if set, logging enabled automatically)
--quiet build in quiet mode
-s, --source string filesystem path to read files relative from
-v, --verbose verbose output
--verboseLog verbose logging
```
### SEE ALSO
* [hugo new](/commands/hugo_new/) - Create new content for your site
###### Auto generated by spf13/cobra on 24-Sep-2018
###### Auto generated by spf13/cobra on 23-Dec-2018

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