diff --git a/deps/deps.go b/deps/deps.go
index ecbba2e5619..557f036f1fa 100644
--- a/deps/deps.go
+++ b/deps/deps.go
@@ -286,6 +286,10 @@ func (d Deps) ForLanguage(cfg DepsCfg, onCreated func(d *Deps) error) (*Deps, er
return nil, err
}
+ if err != nil {
+ return nil, err
+ }
+
d.Site = cfg.Site
// The resource cache is global so reuse.
diff --git a/docs/README.md b/docs/README.md
index 7ca9d734c07..a2c767b7b21 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -13,16 +13,16 @@ Note that this repository contains solely the documentation for Hugo. For contri
*Pull requests shall **only** contain changes to the actual documentation. However, changes on the code base of Hugo **and** the documentation shall be a single, atomic pull request in the [hugo](https://github.com/gohugoio/hugo) repository.*
-Spelling fixes are most welcomed, and if you want to contribute longer sections to the documentation, it would be great if you had these in mind when writing:
+Spelling fixes are most welcomed, and if you want to contribute longer sections to the documentation, it would be great if you had the following criteria in mind when writing:
* Short is good. People go to the library to read novels. If there is more than one way to _do a thing_ in Hugo, describe the current _best practice_ (avoid "… but you can also do …" and "… in older versions of Hugo you had to …".
-* For examples, try to find short snippets that teaches people about the concept. If the example is also useful as-is (copy and paste), then great, but don't list long and similar examples just so people can use them on their sites.
-* Hugo has users from all over the world, so an easy to understand and [simple English](https://simple.wikipedia.org/wiki/Basic_English) is good.
+* For example, try to find short snippets that teaches people about the concept. If the example is also useful as-is (copy and paste), then great. Don't list long and similar examples just so people can use them on their sites.
+* Hugo has users from all over the world, so easy to understand and [simple English](https://simple.wikipedia.org/wiki/Basic_English) is good.
## Branches
* The `master` branch is where the site is automatically built from, and is the place to put changes relevant to the current Hugo version.
-* The `next` branch is where we store changes that is related to the next Hugo release. This can be previewed here: https://next--gohugoio.netlify.com/
+* The `next` branch is where we store changes that are related to the next Hugo release. This can be previewed here: https://next--gohugoio.netlify.com/
## Build
diff --git a/docs/content/en/_index.md b/docs/content/en/_index.md
index bfebd8b2fc7..b4e602438c6 100644
--- a/docs/content/en/_index.md
+++ b/docs/content/en/_index.md
@@ -43,7 +43,7 @@ sections:
link: templates/
color_classes: bg-primary-color-light black
image: /images/home-page-templating-example.png
- copy: "Hugo's Go-based templating provides just the right amount of logic to build anything from the simple to complex. If you prefer Jade/Pug-like syntax, you can also use Amber, Ace, or any combination of the three."
+ copy: "Hugo's Go-based templating provides just the right amount of logic to build anything from the simple to complex."
---
Hugo is one of the most popular open-source static site generators. With its amazing speed and flexibility, Hugo makes building websites fun again.
diff --git a/docs/content/en/content-management/image-processing/index.md b/docs/content/en/content-management/image-processing/index.md
index 9ec0734cc9f..53e72bee1ee 100644
--- a/docs/content/en/content-management/image-processing/index.md
+++ b/docs/content/en/content-management/image-processing/index.md
@@ -98,11 +98,12 @@ Tags:
{{ range $k, $v := .Tags }}
TAG: {{ $k }}: {{ $v }}
{{ end }}
+{{ end }}
```
#### Exif fields
-Data
+Date
: "photo taken" date/time
Lat
diff --git a/docs/content/en/content-management/syntax-highlighting.md b/docs/content/en/content-management/syntax-highlighting.md
index a4359e05dda..1220a06c83c 100644
--- a/docs/content/en/content-management/syntax-highlighting.md
+++ b/docs/content/en/content-management/syntax-highlighting.md
@@ -74,11 +74,9 @@ func GetTitleFunc(style string) func(s string) string {
case "go":
return strings.Title
case "chicago":
- tc := transform.NewTitleConverter(transform.ChicagoStyle)
- return tc.Title
+ return transform.NewTitleConverter(transform.ChicagoStyle)
default:
- tc := transform.NewTitleConverter(transform.APStyle)
- return tc.Title
+ return transform.NewTitleConverter(transform.APStyle)
}
}
{{< / highlight >}}
@@ -93,14 +91,10 @@ See [Highlight](/functions/highlight/).
Highlighting in code fences is enabled by default.{{< new-in "0.60.0" >}}
-````
-```go-html-template{hl_lines=[3,"5-6"],linenos=true}
-```
-````
-
````
```go {linenos=table,hl_lines=[8,"15-17"],linenostart=199}
// ... code
+```
````
@@ -122,11 +116,9 @@ func GetTitleFunc(style string) func(s string) string {
case "go":
return strings.Title
case "chicago":
- tc := transform.NewTitleConverter(transform.ChicagoStyle)
- return tc.Title
+ return transform.NewTitleConverter(transform.ChicagoStyle)
default:
- tc := transform.NewTitleConverter(transform.APStyle)
- return tc.Title
+ return transform.NewTitleConverter(transform.APStyle)
}
}
```
diff --git a/docs/content/en/content-management/toc.md b/docs/content/en/content-management/toc.md
index 31326746c08..fbb2df06596 100644
--- a/docs/content/en/content-management/toc.md
+++ b/docs/content/en/content-management/toc.md
@@ -18,7 +18,11 @@ toc: true
---
{{% note "TOC Heading Levels are Fixed" %}}
-Currently, the `{{.TableOfContents}}` [page variable](/variables/page/) does not allow you to specify which heading levels you want the TOC to render. [See the related GitHub discussion (#1778)](https://github.com/gohugoio/hugo/issues/1778). As such, the resulting ` ` is going to start at `
` when pulling from `{{.Content}}`.
+
+Previously, there was no out-of-the-box way to specify which heading levels you want the TOC to render. [See the related GitHub discussion (#1778)](https://github.com/gohugoio/hugo/issues/1778). As such, the resulting ` ` was going to start at `` when pulling from `{{.Content}}`.
+
+Hugo [v0.60.0](https://github.com/gohugoio/hugo/releases/tag/v0.60.0) made a switch to [Goldmark](https://github.com/yuin/goldmark/) as the default library for Markdown which has improved and configurable implementation of TOC. Take a look at [how to configure TOC](/getting-started/configuration-markup/#table-of-contents) for Goldmark renderer.
+
{{% /note %}}
## Usage
@@ -43,7 +47,7 @@ A collection of textile samples lay spread out on the table - Samsa was a travel
Hugo will take this Markdown and create a table of contents from `## Introduction`, `## My Heading`, and `### My Subheading` and then store it in the [page variable][pagevars]`.TableOfContents`.
-The built-in `.TableOfContents` variables outputs a `` element with a child ``, whose child `` elements begin with any ``'s (i.e., `#` in markdown) inside your content.'
+The built-in `.TableOfContents` variables outputs a `` element with a child ``, whose child `` elements begin with appropriate HTML headings. See [the available settings](/getting-started/configuration-markup/#table-of-contents) to configure what heading levels you want to include in TOC.
{{% note "Table of contents not available for MMark" %}}
Hugo documents created in the [MMark](/content-management/formats/#mmark) Markdown dialect do not currently display TOCs. TOCs are, however, compatible with all other supported Markdown formats.
diff --git a/docs/content/en/getting-started/_index.md b/docs/content/en/getting-started/_index.md
index 478d1eaa689..1615bdd9156 100644
--- a/docs/content/en/getting-started/_index.md
+++ b/docs/content/en/getting-started/_index.md
@@ -17,7 +17,8 @@ aliases: [/overview/introduction/]
toc: false
---
-If this is your first time using Hugo and you've [already installed Hugo on your machine][installed], we recommend the [quick start][].
+If this is your first time using Hugo and you've [already installed Hugo on your machine][installed], we recommend the [quick start][]. You can also use [external learning resources][] to learn Hugo.
[installed]: /getting-started/installing/
[quick start]: /getting-started/quick-start/
+[external learning resources]: /getting-started/external-learning-resources/
diff --git a/docs/content/en/getting-started/configuration.md b/docs/content/en/getting-started/configuration.md
index a653c0e0b43..a53612ff464 100644
--- a/docs/content/en/getting-started/configuration.md
+++ b/docs/content/en/getting-started/configuration.md
@@ -350,9 +350,9 @@ To set config params, prefix the name with `HUGO_PARAMS_`
Test and document setting params via JSON env var.
{{< /todo >}}
-## Ignore Files When Rendering
+## Ignore Content Files When Rendering
-The following statement inside `./config.toml` will cause Hugo to ignore files ending with `.foo` and `.boo` when rendering:
+The following statement inside `./config.toml` will cause Hugo to ignore content files ending with `.foo` and `.boo` when rendering:
```
ignoreFiles = [ "\\.foo$", "\\.boo$" ]
diff --git a/docs/content/en/getting-started/external-learning-resources/hia.jpg b/docs/content/en/getting-started/external-learning-resources/hia.jpg
new file mode 100644
index 00000000000..ee45b0e9e0b
Binary files /dev/null and b/docs/content/en/getting-started/external-learning-resources/hia.jpg differ
diff --git a/docs/content/en/getting-started/external-learning-resources/index.md b/docs/content/en/getting-started/external-learning-resources/index.md
new file mode 100644
index 00000000000..4e342a6e60b
--- /dev/null
+++ b/docs/content/en/getting-started/external-learning-resources/index.md
@@ -0,0 +1,29 @@
+---
+title: External Learning Resources
+linktitle: External Learning Resources
+description: A list of tutorials and books on Hugo.
+date: 2019-10-20
+publishdate: 2019-10-20
+lastmod: 2019-10-20
+keywords: [books,tutorials,learning,usage]
+menu:
+ docs:
+ parent: "getting-started"
+ weight: 70
+weight: 70
+sections_weight: 70
+draft: false
+toc: false
+---
+
+## Books
+### Hugo In Action
+[![Hugo In Action](hia.jpg)](https://www.manning.com/books/hugo-in-action)
+
+Hugo in Action is a step-by-step guide to using Hugo to create static websites. Working with a complete example website and source code samples, you’ll learn how to build and host a low-maintenance, high-performance site that will wow your users and stay stable without relying on a third-party server.
+
+[Hugo In Action Home Page](https://www.manning.com/books/hugo-in-action)
+
+## Video tutorials
+### Video Playlist by Mike Dane
+Mike Dane expains the various features of via dedicated tutorials on [Youtube](https://www.youtube.com/watch?list=PLLAZ4kZ9dFpOnyRlyS-liKL5ReHDcj4G3&v=qtIqKaDlqXo).
diff --git a/docs/content/en/getting-started/installing.md b/docs/content/en/getting-started/installing.md
index f388188b2cb..55c876646c4 100644
--- a/docs/content/en/getting-started/installing.md
+++ b/docs/content/en/getting-started/installing.md
@@ -463,6 +463,8 @@ Hugo installed via Snap can write only inside the user’s `$HOME` directory---a
This installs the "extended" Sass/SCSS version.
+This option is not recommended because the Hugo in Linux package managers for Debian and Ubuntu is usually a few versions behind as described [here](https://github.com/gcushen/hugo-academic/issues/703)
+
### Arch Linux
You can also install Hugo from the Arch Linux [community](https://www.archlinux.org/packages/community/x86_64/hugo/) repository. Applies also to derivatives such as Manjaro.
diff --git a/docs/content/en/getting-started/quick-start.md b/docs/content/en/getting-started/quick-start.md
index 143dc0a4172..3c8eb6b8acb 100644
--- a/docs/content/en/getting-started/quick-start.md
+++ b/docs/content/en/getting-started/quick-start.md
@@ -21,7 +21,9 @@ toc: true
{{% note %}}
This quick start uses `macOS` in the examples. For instructions about how to install Hugo on other operating systems, see [install](/getting-started/installing).
-It is recommended to have [Git](https://git-scm.com/downloads) installed to run this tutorial.
+It is recommended to have [Git installed](https://git-scm.com/downloads) to run this tutorial.
+
+For other approaches learning Hugo like book or a video tutorial refer to the [external learning resources](/getting-started/external-learning-resources/) page.
{{% /note %}}
diff --git a/docs/content/en/hosting-and-deployment/hugo-deploy.md b/docs/content/en/hosting-and-deployment/hugo-deploy.md
index bce606d1f95..81436b7f36a 100644
--- a/docs/content/en/hosting-and-deployment/hugo-deploy.md
+++ b/docs/content/en/hosting-and-deployment/hugo-deploy.md
@@ -90,14 +90,14 @@ cloudFrontDistributionID =
# Samples:
[[deployment.matchers]]
-# Cache static assets for 20 years.
+# Cache static assets for 1 year.
pattern = "^.+\\.(js|css|svg|ttf)$"
-cacheControl = "max-age=630720000, no-transform, public"
+cacheControl = "max-age=31536000, no-transform, public"
gzip = true
[[deployment.matchers]]
pattern = "^.+\\.(png|jpg)$"
-cacheControl = "max-age=630720000, no-transform, public"
+cacheControl = "max-age=31536000, no-transform, public"
gzip = false
[[deployment.matchers]]
diff --git a/docs/content/en/hugo-modules/use-modules.md b/docs/content/en/hugo-modules/use-modules.md
index a0807bebc81..71430700a11 100644
--- a/docs/content/en/hugo-modules/use-modules.md
+++ b/docs/content/en/hugo-modules/use-modules.md
@@ -16,9 +16,9 @@ aliases: [/themes/usage/,/themes/installing/,/installing-and-using-themes/]
toc: true
---
-## Prerequisites
+## Prerequisite
-{{% gomodules-info %}}
+{{< gomodules-info >}}
diff --git a/docs/content/en/news/0.60.0-relnotes/index.md b/docs/content/en/news/0.60.0-relnotes/index.md
index 8a26b3de995..8cdebb35ef9 100644
--- a/docs/content/en/news/0.60.0-relnotes/index.md
+++ b/docs/content/en/news/0.60.0-relnotes/index.md
@@ -1,15 +1,24 @@
---
date: 2019-11-27
-title: "0.60.0"
-description: "0.60.0"
+title: "Now CommonMark Compliant!"
+description: "Goldmark -- CommonMark compliant, GitHub flavored, fast and flexible -- is the new default library for Markdown in Hugo."
categories: ["Releases"]
---
- [Goldmark](https://github.com/yuin/goldmark/) by [@yuin](https://github.com/yuin) is now the new default library used for Markdown in Hugo. It's CommonMark compliant and GitHub flavored, and both fast and flexible. Blackfriday, the old default, has served us well, but there have been formatting and portability issues that were hard to work around. The "CommonMark compliant" part is the main selling feature of Goldmark, but with that you also get attribute syntax on headers and code blocks (for code blocks you can turn on/off line numbers and highlight line ranges), strikethrough support and an improved and configurable implementation of `TableOfContents`. See [Markup Configuration](https://gohugo.io/getting-started/configuration-markup/) for an overview of extensions.
+[Goldmark](https://github.com/yuin/goldmark/) by [@yuin](https://github.com/yuin) is now the new default library used for Markdown in Hugo. It's CommonMark compliant and GitHub flavored, and both fast and flexible. Blackfriday, the old default, has served us well, but there have been formatting and portability issues that were hard to work around. The "CommonMark compliant" part is the main selling feature of Goldmark, but with that you also get attribute syntax on headers and code blocks (for code blocks you can turn on/off line numbers and highlight line ranges), strikethrough support and an improved and configurable implementation of `TableOfContents`. See [Markup Configuration](https://gohugo.io/getting-started/configuration-markup/) for an overview of extensions.
Please read the [Notes Section](#notes) and the updated documentation. We suggest you start with [List of content formats in Hugo](https://gohugo.io/content-management/formats/#list-of-content-formats). Goldmark is better, but the feature set is not fully comparable and it may be more stricter in some areas (there are 17 rules for how a [headline](https://spec.commonmark.org/0.29/#emphasis-and-strong-emphasis) should look like); if you have any problems you cannot work around, see [Configure Markup](https://gohugo.io/getting-started/configuration-markup/#configure-markup) for a way to change the default Markdown handler.
+Also, if you have lots of inline HTML in your Markdown files, you may have to enable the `unsafe` mode:
+
+{{< code-toggle file="config" >}}
+markup:
+ goldmark:
+ renderer:
+ unsafe: true
+{{< /code-toggle >}}
+
This release represents **62 contributions by 10 contributors** to the main Hugo code base. [@bep](https://github.com/bep) leads the Hugo development with a significant amount of contributions, but also a big shoutout to [@anthonyfok](https://github.com/anthonyfok), [@max-arnold](https://github.com/max-arnold), and [@trimbo](https://github.com/trimbo) for their ongoing contributions.
And a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) and [@onedrawingperday](https://github.com/onedrawingperday) for their relentless work on keeping the themes site in pristine condition and to [@kaushalmodi](https://github.com/kaushalmodi) and [@davidsneighbour](https://github.com/davidsneighbour) for great work on the documentation site.
diff --git a/docs/content/en/news/0.60.0-relnotes/poster-featured.png b/docs/content/en/news/0.60.0-relnotes/poster-featured.png
new file mode 100644
index 00000000000..9bd99be59a7
Binary files /dev/null and b/docs/content/en/news/0.60.0-relnotes/poster-featured.png differ
diff --git a/docs/content/en/news/0.60.1-relnotes/featured-061.png b/docs/content/en/news/0.60.1-relnotes/featured-061.png
new file mode 100644
index 00000000000..8ff4d4af9ed
Binary files /dev/null and b/docs/content/en/news/0.60.1-relnotes/featured-061.png differ
diff --git a/docs/content/en/news/0.60.1-relnotes/index.md b/docs/content/en/news/0.60.1-relnotes/index.md
index 1536c3a4cd9..2709c7b6f0b 100644
--- a/docs/content/en/news/0.60.1-relnotes/index.md
+++ b/docs/content/en/news/0.60.1-relnotes/index.md
@@ -4,9 +4,6 @@ date: 2019-11-29
title: "Hugo 0.60.1: A couple of Bug Fixes"
description: "This version fixes a couple of bugs introduced in 0.60.0."
categories: ["Releases"]
-images:
-- images/blog/hugo-bug-poster.png
-
---
diff --git a/docs/content/en/news/0.61.0-relnotes/hugo-61-featured.png b/docs/content/en/news/0.61.0-relnotes/hugo-61-featured.png
new file mode 100644
index 00000000000..8691f30e24e
Binary files /dev/null and b/docs/content/en/news/0.61.0-relnotes/hugo-61-featured.png differ
diff --git a/docs/content/en/news/0.61.0-relnotes/index.md b/docs/content/en/news/0.61.0-relnotes/index.md
index bcf40b02446..2922506dfa1 100644
--- a/docs/content/en/news/0.61.0-relnotes/index.md
+++ b/docs/content/en/news/0.61.0-relnotes/index.md
@@ -1,12 +1,12 @@
---
date: 2019-12-11
-title: "0.61.0"
-description: "0.61.0"
+title: "40K GitHub Stars Edition"
+description: "40K stars on GitHub is a good enough reason to release a new version of Hugo!"
categories: ["Releases"]
---
- This is the [40K GitHub Stars Edition](https://github.com/gohugoio/hugo/stargazers). It's mostly a bug fix release, and an important note is the deprecation of Amber and Ace as template engines. See [#6609](https://github.com/gohugoio/hugo/issues/6609) for more information.
+This is the [40K GitHub Stars Edition](https://github.com/gohugoio/hugo/stargazers). It's mostly a bug fix release, and an important note is the deprecation of Amber and Ace as template engines. See [#6609](https://github.com/gohugoio/hugo/issues/6609) for more information.
This release represents **10 contributions by 3 contributors** to the main Hugo code base.
diff --git a/docs/content/en/templates/introduction.md b/docs/content/en/templates/introduction.md
index 34a993a52b6..abbb41f4c3c 100644
--- a/docs/content/en/templates/introduction.md
+++ b/docs/content/en/templates/introduction.md
@@ -25,8 +25,6 @@ The following is only a primer on Go Templates. For an in-depth look into Go Tem
Go Templates provide an extremely simple template language that adheres to the belief that only the most basic of logic belongs in the template or view layer.
-{{< youtube gnJbPO-GFIw >}}
-
## Basic Syntax
Go Templates are HTML files with the addition of [variables][variables] and [functions][functions]. Go Template variables and functions are accessible within `{{ }}`.
diff --git a/docs/content/en/templates/output-formats.md b/docs/content/en/templates/output-formats.md
index 89accaab84f..50337a20ccf 100644
--- a/docs/content/en/templates/output-formats.md
+++ b/docs/content/en/templates/output-formats.md
@@ -4,7 +4,7 @@ linktitle: Custom Output Formats
description: Hugo can output content in multiple formats, including calendar events, e-book formats, Google AMP, and JSON search indexes, or any custom text format.
date: 2017-03-22
publishdate: 2017-03-22
-lastmod: 2017-03-22
+lastmod: 2019-12-11
categories: [templates]
keywords: ["amp","outputs","rss"]
menu:
@@ -31,7 +31,7 @@ This is the full set of built-in media types in Hugo:
**Note:**
* It is possible to add custom media types or change the defaults; e.g., if you want to change the suffix for `text/html` to `asp`.
-* The `Suffix` is the value that will be used for URLs and filenames for that media type in Hugo.
+* `Suffixes` are the values that will be used for URLs and filenames for that media type in Hugo.
* The `Type` is the identifier that must be used when defining new/custom `Output Formats` (see below).
* The full set of media types will be registered in Hugo's built-in development server to make sure they are recognized by the browser.
@@ -40,9 +40,9 @@ To add or modify a media type, define it in a `mediaTypes` section in your [site
{{< code-toggle file="config" >}}
[mediaTypes]
[mediaTypes."text/enriched"]
- suffix = "enr"
+ suffixes = ["enr"]
[mediaTypes."text/html"]
- suffix = "asp"
+ suffixes = ["asp"]
{{ code-toggle >}}
The above example adds one new media type, `text/enriched`, and changes the suffix for the built-in `text/html` media type.
@@ -52,7 +52,7 @@ The above example adds one new media type, `text/enriched`, and changes the suff
```toml
[mediaTypes]
[mediaTypes."text/html"]
-suffix = "htm"
+suffixes = ["htm"]
# Redefine HTML to update its media type.
[outputFormats]
@@ -221,7 +221,7 @@ From content files, you can use the [`ref` or `relref` shortcodes](/content-mana
A new output format needs a corresponding template in order to render anything useful.
{{% note %}}
-The key distinction for Hugo versions 0.20 and newer is that Hugo looks at an output format's `Name` and MediaType's `Suffix` when choosing the template used to render a given `Page`.
+The key distinction for Hugo versions 0.20 and newer is that Hugo looks at an output format's `Name` and MediaType's `Suffixes` when choosing the template used to render a given `Page`.
{{% /note %}}
The following table shows examples of different output formats, the suffix used, and Hugo's respective template [lookup order][]. All of the examples in the table can:
diff --git a/docs/content/en/tools/search.md b/docs/content/en/tools/search.md
index 2958ee0a0df..4c66959765f 100644
--- a/docs/content/en/tools/search.md
+++ b/docs/content/en/tools/search.md
@@ -18,7 +18,7 @@ aliases: []
toc: true
---
-A static website with a dynamic search function? Yes. As alternatives to embeddable scripts from Google or other search engines, you can provide your visitors a custom search by indexing your content files directly.
+A static website with a dynamic search function? Yes, Hugo provides an alternative to embeddable scripts from Google or other search engines for static websites. Hugo allows you to provide your visitors with a custom search function by indexing your content files directly.
* [GitHub Gist for Hugo Workflow](https://gist.github.com/sebz/efddfc8fdcb6b480f567). This gist contains a simple workflow to create a search index for your static website. It uses a simple Grunt script to index all your content files and [lunr.js](https://lunrjs.com/) to serve the search results.
* [hugo-elasticsearch](https://www.npmjs.com/package/hugo-elasticsearch). Generate [Elasticsearch](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) indexes for Hugo static sites by parsing front matter. Hugo-Elasticsearch will generate a newline delimited JSON (NDJSON) file that can be bulk uploaded into Elasticsearch using any one of the available [clients](https://www.elastic.co/guide/en/elasticsearch/client/index.html).
diff --git a/docs/data/docs.json b/docs/data/docs.json
index dea7f0bcbc1..0944ab88114 100644
--- a/docs/data/docs.json
+++ b/docs/data/docs.json
@@ -1387,8 +1387,7 @@
},
"tableOfContents": {
"startLevel": 2,
- "endLevel": 3,
- "ordered": false
+ "endLevel": 3
},
"goldmark": {
"renderer": {
diff --git a/docs/data/homepagetweets.toml b/docs/data/homepagetweets.toml
index b5f2d1b5973..f5a2b5dbe23 100644
--- a/docs/data/homepagetweets.toml
+++ b/docs/data/homepagetweets.toml
@@ -1,3 +1,10 @@
+[[tweet]]
+name = "Heinrich Hartmann"
+twitter_handle = "@heinrichhartman"
+quote = "Working with @GoHugoIO is such a joy. Having worked with #Jekyll in the past, the near instant preview is a big win! Did not expect this to make such a huge difference."
+link = "https://twitter.com/heinrichhartman/status/1199736512264462341"
+date = 2019-11-12T00:00:00Z
+
[[tweet]]
name = "Joshua Steven"
twitter_handle = "@jscarto"
diff --git a/docs/netlify.toml b/docs/netlify.toml
index 269d138aba7..f35a7c0f059 100644
--- a/docs/netlify.toml
+++ b/docs/netlify.toml
@@ -3,7 +3,7 @@ publish = "public"
command = "hugo --gc --minify"
[context.production.environment]
-HUGO_VERSION = "0.59.1"
+HUGO_VERSION = "0.61.0"
HUGO_ENV = "production"
HUGO_ENABLEGITINFO = "true"
@@ -11,20 +11,20 @@ HUGO_ENABLEGITINFO = "true"
command = "hugo --gc --minify --enableGitInfo"
[context.split1.environment]
-HUGO_VERSION = "0.59.1"
+HUGO_VERSION = "0.61.0"
HUGO_ENV = "production"
[context.deploy-preview]
command = "hugo --gc --minify --buildFuture -b $DEPLOY_PRIME_URL"
[context.deploy-preview.environment]
-HUGO_VERSION = "0.59.1"
+HUGO_VERSION = "0.61.0"
[context.branch-deploy]
command = "hugo --gc --minify -b $DEPLOY_PRIME_URL"
[context.branch-deploy.environment]
-HUGO_VERSION = "0.59.1"
+HUGO_VERSION = "0.61.0"
[context.next.environment]
HUGO_ENABLEGITINFO = "true"
diff --git a/docs/resources/_gen/images/news/0.60.0-relnotes/poster-featured_hu88aba11293facef11feec48164ba6c3f_31907_480x0_resize_catmullrom_2.png b/docs/resources/_gen/images/news/0.60.0-relnotes/poster-featured_hu88aba11293facef11feec48164ba6c3f_31907_480x0_resize_catmullrom_2.png
new file mode 100644
index 00000000000..bca6dc8a8a2
Binary files /dev/null and b/docs/resources/_gen/images/news/0.60.0-relnotes/poster-featured_hu88aba11293facef11feec48164ba6c3f_31907_480x0_resize_catmullrom_2.png differ
diff --git a/docs/resources/_gen/images/news/0.60.0-relnotes/poster-featured_hu88aba11293facef11feec48164ba6c3f_31907_640x0_resize_catmullrom_2.png b/docs/resources/_gen/images/news/0.60.0-relnotes/poster-featured_hu88aba11293facef11feec48164ba6c3f_31907_640x0_resize_catmullrom_2.png
new file mode 100644
index 00000000000..558a151c9df
Binary files /dev/null and b/docs/resources/_gen/images/news/0.60.0-relnotes/poster-featured_hu88aba11293facef11feec48164ba6c3f_31907_640x0_resize_catmullrom_2.png differ
diff --git a/docs/resources/_gen/images/news/0.60.1-relnotes/featured-061_hu55b86d71cf1e6f4fec276be0fe0d3e6e_28841_480x0_resize_catmullrom_2.png b/docs/resources/_gen/images/news/0.60.1-relnotes/featured-061_hu55b86d71cf1e6f4fec276be0fe0d3e6e_28841_480x0_resize_catmullrom_2.png
new file mode 100644
index 00000000000..61d552d8c50
Binary files /dev/null and b/docs/resources/_gen/images/news/0.60.1-relnotes/featured-061_hu55b86d71cf1e6f4fec276be0fe0d3e6e_28841_480x0_resize_catmullrom_2.png differ
diff --git a/docs/resources/_gen/images/news/0.60.1-relnotes/featured-061_hu55b86d71cf1e6f4fec276be0fe0d3e6e_28841_640x0_resize_catmullrom_2.png b/docs/resources/_gen/images/news/0.60.1-relnotes/featured-061_hu55b86d71cf1e6f4fec276be0fe0d3e6e_28841_640x0_resize_catmullrom_2.png
new file mode 100644
index 00000000000..cc3a4e4c3b1
Binary files /dev/null and b/docs/resources/_gen/images/news/0.60.1-relnotes/featured-061_hu55b86d71cf1e6f4fec276be0fe0d3e6e_28841_640x0_resize_catmullrom_2.png differ
diff --git a/docs/resources/_gen/images/news/0.61.0-relnotes/hugo-61-featured_huc7cf44fd2ae7c41ccbb87bf5c4aa169c_79929_480x0_resize_catmullrom_2.png b/docs/resources/_gen/images/news/0.61.0-relnotes/hugo-61-featured_huc7cf44fd2ae7c41ccbb87bf5c4aa169c_79929_480x0_resize_catmullrom_2.png
new file mode 100644
index 00000000000..bee94364ec0
Binary files /dev/null and b/docs/resources/_gen/images/news/0.61.0-relnotes/hugo-61-featured_huc7cf44fd2ae7c41ccbb87bf5c4aa169c_79929_480x0_resize_catmullrom_2.png differ
diff --git a/docs/resources/_gen/images/news/0.61.0-relnotes/hugo-61-featured_huc7cf44fd2ae7c41ccbb87bf5c4aa169c_79929_640x0_resize_catmullrom_2.png b/docs/resources/_gen/images/news/0.61.0-relnotes/hugo-61-featured_huc7cf44fd2ae7c41ccbb87bf5c4aa169c_79929_640x0_resize_catmullrom_2.png
new file mode 100644
index 00000000000..cc28f5b9a60
Binary files /dev/null and b/docs/resources/_gen/images/news/0.61.0-relnotes/hugo-61-featured_huc7cf44fd2ae7c41ccbb87bf5c4aa169c_79929_640x0_resize_catmullrom_2.png differ
diff --git a/helpers/content.go b/helpers/content.go
index 4dc4cd413bd..1c780fefe1b 100644
--- a/helpers/content.go
+++ b/helpers/content.go
@@ -25,13 +25,14 @@ import (
"github.com/gohugoio/hugo/common/loggers"
+ "github.com/spf13/afero"
+
"github.com/gohugoio/hugo/markup/converter"
"github.com/gohugoio/hugo/markup"
bp "github.com/gohugoio/hugo/bufferpool"
"github.com/gohugoio/hugo/config"
- "github.com/spf13/afero"
"strings"
)
@@ -78,6 +79,7 @@ func NewContentSpec(cfg config.Provider, logger *loggers.Logger, contentFs afero
ContentFs: contentFs,
Logger: logger,
})
+
if err != nil {
return nil, err
}
diff --git a/helpers/general_test.go b/helpers/general_test.go
index 104a4c35def..b45fb0e9b44 100644
--- a/helpers/general_test.go
+++ b/helpers/general_test.go
@@ -21,9 +21,8 @@ import (
"github.com/spf13/viper"
- "github.com/gohugoio/hugo/common/loggers"
-
qt "github.com/frankban/quicktest"
+ "github.com/gohugoio/hugo/common/loggers"
"github.com/spf13/afero"
)
diff --git a/hugolib/content_render_hooks_test.go b/hugolib/content_render_hooks_test.go
new file mode 100644
index 00000000000..aa697220d1b
--- /dev/null
+++ b/hugolib/content_render_hooks_test.go
@@ -0,0 +1,244 @@
+// Copyright 2019 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless requiredF by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hugolib
+
+import "testing"
+
+func TestRenderHooks(t *testing.T) {
+ config := `
+baseURL="https://example.org"
+workingDir="/mywork"
+`
+ b := newTestSitesBuilder(t).WithWorkingDir("/mywork").WithConfigFile("toml", config).Running()
+ b.WithTemplatesAdded("_default/single.html", `{{ .Content }}`)
+ b.WithTemplatesAdded("shortcodes/myshortcode1.html", `{{ partial "mypartial1" }}`)
+ b.WithTemplatesAdded("shortcodes/myshortcode2.html", `{{ partial "mypartial2" }}`)
+ b.WithTemplatesAdded("shortcodes/myshortcode3.html", `SHORT3|`)
+ b.WithTemplatesAdded("shortcodes/myshortcode4.html", `
+
+{{ .Inner | markdownify }}
+
+`)
+ b.WithTemplatesAdded("shortcodes/myshortcode5.html", `
+Inner Inline: {{ .Inner | .Page.RenderString }}
+Inner Block: {{ .Inner | .Page.RenderString (dict "display" "block" ) }}
+`)
+
+ b.WithTemplatesAdded("shortcodes/myshortcode6.html", `.Render: {{ .Page.Render "myrender" }}`)
+ b.WithTemplatesAdded("partials/mypartial1.html", `PARTIAL1`)
+ b.WithTemplatesAdded("partials/mypartial2.html", `PARTIAL2 {{ partial "mypartial3.html" }}`)
+ b.WithTemplatesAdded("partials/mypartial3.html", `PARTIAL3`)
+ b.WithTemplatesAdded("partials/mypartial4.html", `PARTIAL4`)
+ b.WithTemplatesAdded("customview/myrender.html", `myrender: {{ .Title }}|P4: {{ partial "mypartial4" }}`)
+ b.WithTemplatesAdded("_default/_markup/render-link.html", `{{ with .Page }}{{ .Title }}{{ end }}|{{ .Destination | safeURL }}|Title: {{ .Title | safeHTML }}|Text: {{ .Text | safeHTML }}|END`)
+ b.WithTemplatesAdded("docs/_markup/render-link.html", `Link docs section: {{ .Text | safeHTML }}|END`)
+ b.WithTemplatesAdded("_default/_markup/render-image.html", `IMAGE: {{ .Page.Title }}||{{ .Destination | safeURL }}|Title: {{ .Title | safeHTML }}|Text: {{ .Text | safeHTML }}|END`)
+
+ b.WithContent("customview/p1.md", `---
+title: Custom View
+---
+
+{{< myshortcode6 >}}
+
+ `, "blog/p1.md", `---
+title: Cool Page
+---
+
+[First Link](https://www.google.com "Google's Homepage")
+
+{{< myshortcode3 >}}
+
+[Second Link](https://www.google.com "Google's Homepage")
+
+Image:
+
+![Drag Racing](/images/Dragster.jpg "image title")
+
+
+`, "blog/p2.md", `---
+title: Cool Page2
+layout: mylayout
+---
+
+{{< myshortcode1 >}}
+
+[Some Text](https://www.google.com "Google's Homepage")
+
+
+
+`, "blog/p3.md", `---
+title: Cool Page3
+---
+
+{{< myshortcode2 >}}
+
+
+`, "docs/docs1.md", `---
+title: Docs 1
+---
+
+
+[Docs 1](https://www.google.com "Google's Homepage")
+
+
+`, "blog/p4.md", `---
+title: Cool Page With Image
+---
+
+Image:
+
+![Drag Racing](/images/Dragster.jpg "image title")
+
+
+`, "blog/p5.md", `---
+title: Cool Page With Markdownify
+---
+
+{{< myshortcode4 >}}
+Inner Link: [Inner Link](https://www.google.com "Google's Homepage")
+{{< /myshortcode4 >}}
+
+`, "blog/p6.md", `---
+title: With RenderString
+---
+
+{{< myshortcode5 >}}Inner Link: [Inner Link](https://www.gohugo.io "Hugo's Homepage"){{< /myshortcode5 >}}
+
+`)
+ b.Build(BuildCfg{})
+ b.AssertFileContent("public/blog/p1/index.html", `
+Cool Page|https://www.google.com|Title: Google's Homepage|Text: First Link|END
+Text: Second
+SHORT3|
+IMAGE: Cool Page||/images/Dragster.jpg|Title: image title|Text: Drag Racing|END
+`)
+
+ b.AssertFileContent("public/customview/p1/index.html", `.Render: myrender: Custom View|P4: PARTIAL4`)
+ b.AssertFileContent("public/blog/p2/index.html", `PARTIAL`)
+ b.AssertFileContent("public/blog/p3/index.html", `PARTIAL3`)
+ // We may add type template support later, keep this for then. b.AssertFileContent("public/docs/docs1/index.html", `Link docs section: Docs 1|END`)
+ b.AssertFileContent("public/blog/p4/index.html", `IMAGE: Cool Page With Image||/images/Dragster.jpg|Title: image title|Text: Drag Racing|END
`)
+ // The regular markdownify func currently gets regular links.
+ b.AssertFileContent("public/blog/p5/index.html", "Inner Link: Inner Link \n")
+
+ b.AssertFileContent("public/blog/p6/index.html",
+ "Inner Inline: Inner Link: With RenderString|https://www.gohugo.io|Title: Hugo's Homepage|Text: Inner Link|END",
+ "Inner Block: Inner Link: With RenderString|https://www.gohugo.io|Title: Hugo's Homepage|Text: Inner Link|END
",
+ )
+
+ b.EditFiles(
+ "layouts/_default/_markup/render-link.html", `EDITED: {{ .Destination | safeURL }}|`,
+ "layouts/_default/_markup/render-image.html", `IMAGE EDITED: {{ .Destination | safeURL }}|`,
+ "layouts/docs/_markup/render-link.html", `DOCS EDITED: {{ .Destination | safeURL }}|`,
+ "layouts/partials/mypartial1.html", `PARTIAL1_EDITED`,
+ "layouts/partials/mypartial3.html", `PARTIAL3_EDITED`,
+ "layouts/partials/mypartial4.html", `PARTIAL4_EDITED`,
+ "layouts/shortcodes/myshortcode3.html", `SHORT3_EDITED|`,
+ )
+
+ b.Build(BuildCfg{})
+ b.AssertFileContent("public/customview/p1/index.html", `.Render: myrender: Custom View|P4: PARTIAL4_EDITED`)
+ b.AssertFileContent("public/blog/p1/index.html", `EDITED: https://www.google.com|
`, "SHORT3_EDITED|")
+ b.AssertFileContent("public/blog/p2/index.html", `PARTIAL1_EDITED`)
+ b.AssertFileContent("public/blog/p3/index.html", `PARTIAL3_EDITED`)
+ // We may add type template support later, keep this for then. b.AssertFileContent("public/docs/docs1/index.html", `DOCS EDITED: https://www.google.com|`)
+ b.AssertFileContent("public/blog/p4/index.html", `IMAGE EDITED: /images/Dragster.jpg|`)
+ b.AssertFileContent("public/blog/p6/index.html", "Inner Link: EDITED: https://www.gohugo.io|
")
+
+}
+
+func TestRenderHooksRSS(t *testing.T) {
+
+ b := newTestSitesBuilder(t)
+
+ b.WithTemplates("index.html", `
+{{ $p := site.GetPage "p1.md" }}
+
+P1: {{ $p.Content }}
+
+ `, "index.xml", `
+
+{{ $p2 := site.GetPage "p2.md" }}
+{{ $p3 := site.GetPage "p3.md" }}
+
+P2: {{ $p2.Content }}
+P3: {{ $p3.Content }}
+
+
+ `,
+ "_default/_markup/render-link.html", `html-link: {{ .Destination | safeURL }}|`,
+ "_default/_markup/render-link.rss.xml", `xml-link: {{ .Destination | safeURL }}|`,
+ )
+
+ b.WithContent("p1.md", `---
+title: "p1"
+---
+P1. [I'm an inline-style link](https://www.gohugo.io)
+
+
+`, "p2.md", `---
+title: "p2"
+---
+P1. [I'm an inline-style link](https://www.bep.is)
+
+
+`,
+ "p3.md", `---
+title: "p2"
+outputs: ["rss"]
+---
+P3. [I'm an inline-style link](https://www.example.org)
+
+`,
+ )
+
+ b.Build(BuildCfg{})
+
+ b.AssertFileContent("public/index.html", "P1: P1. html-link: https://www.gohugo.io|
")
+ b.AssertFileContent("public/index.xml", `
+P2: P1. xml-link: https://www.bep.is|
+P3: P3. xml-link: https://www.example.org|
+`)
+
+}
+
+func TestRenderString(t *testing.T) {
+
+ b := newTestSitesBuilder(t)
+
+ b.WithTemplates("index.html", `
+{{ $p := site.GetPage "p1.md" }}
+{{ $optBlock := dict "display" "block" }}
+{{ $optOrg := dict "markup" "org" }}
+RSTART:{{ "**Bold Markdown**" | $p.RenderString }}:REND
+RSTART:{{ "**Bold Block Markdown**" | $p.RenderString $optBlock }}:REND
+RSTART:{{ "/italic org mode/" | $p.RenderString $optOrg }}:REND
+
+`)
+
+ b.WithContent("p1.md", `---
+title: "p1"
+---
+`,
+ )
+
+ b.Build(BuildCfg{})
+
+ b.AssertFileContent("public/index.html", `
+RSTART:Bold Markdown :REND
+RSTART:Bold Block Markdown
+RSTART:italic org mode :REND
+`)
+
+}
diff --git a/hugolib/filesystems/basefs.go b/hugolib/filesystems/basefs.go
index de6baa130d7..cdc39ce61cb 100644
--- a/hugolib/filesystems/basefs.go
+++ b/hugolib/filesystems/basefs.go
@@ -126,10 +126,28 @@ type SourceFilesystems struct {
StaticDirs []hugofs.FileMetaInfo
}
+// FileSystems returns the FileSystems relevant for the change detection
+// in server mode.
+// Note: This does currently not return any static fs.
+func (s *SourceFilesystems) FileSystems() []*SourceFilesystem {
+ return []*SourceFilesystem{
+ s.Content,
+ s.Data,
+ s.I18n,
+ s.Layouts,
+ s.Archetypes,
+ // TODO(bep) static
+ }
+
+}
+
// A SourceFilesystem holds the filesystem for a given source type in Hugo (data,
// i18n, layouts, static) and additional metadata to be able to use that filesystem
// in server mode.
type SourceFilesystem struct {
+ // Name matches one in files.ComponentFolders
+ Name string
+
// This is a virtual composite filesystem. It expects path relative to a context.
Fs afero.Fs
@@ -275,6 +293,19 @@ func (d *SourceFilesystem) Contains(filename string) bool {
return false
}
+// Path returns the relative path to the given filename if it is a member of
+// of the current filesystem, an empty string if not.
+func (d *SourceFilesystem) Path(filename string) string {
+ for _, dir := range d.Dirs {
+ meta := dir.Meta()
+ if strings.HasPrefix(filename, meta.Filename()) {
+ p := strings.TrimPrefix(strings.TrimPrefix(filename, meta.Filename()), filePathSeparator)
+ return p
+ }
+ }
+ return ""
+}
+
// RealDirs gets a list of absolute paths to directories starting from the given
// path.
func (d *SourceFilesystem) RealDirs(from string) []string {
@@ -349,12 +380,14 @@ func newSourceFilesystemsBuilder(p *paths.Paths, logger *loggers.Logger, b *Base
return &sourceFilesystemsBuilder{p: p, logger: logger, sourceFs: sourceFs, theBigFs: b.theBigFs, result: &SourceFilesystems{}}
}
-func (b *sourceFilesystemsBuilder) newSourceFilesystem(fs afero.Fs, dirs []hugofs.FileMetaInfo) *SourceFilesystem {
+func (b *sourceFilesystemsBuilder) newSourceFilesystem(name string, fs afero.Fs, dirs []hugofs.FileMetaInfo) *SourceFilesystem {
return &SourceFilesystem{
+ Name: name,
Fs: fs,
Dirs: dirs,
}
}
+
func (b *sourceFilesystemsBuilder) Build() (*SourceFilesystems, error) {
if b.theBigFs == nil {
@@ -369,12 +402,12 @@ func (b *sourceFilesystemsBuilder) Build() (*SourceFilesystems, error) {
createView := func(componentID string) *SourceFilesystem {
if b.theBigFs == nil || b.theBigFs.overlayMounts == nil {
- return b.newSourceFilesystem(hugofs.NoOpFs, nil)
+ return b.newSourceFilesystem(componentID, hugofs.NoOpFs, nil)
}
dirs := b.theBigFs.overlayDirs[componentID]
- return b.newSourceFilesystem(afero.NewBasePathFs(b.theBigFs.overlayMounts, componentID), dirs)
+ return b.newSourceFilesystem(componentID, afero.NewBasePathFs(b.theBigFs.overlayMounts, componentID), dirs)
}
@@ -392,14 +425,14 @@ func (b *sourceFilesystemsBuilder) Build() (*SourceFilesystems, error) {
return nil, err
}
- b.result.Data = b.newSourceFilesystem(dataFs, dataDirs)
+ b.result.Data = b.newSourceFilesystem(files.ComponentFolderData, dataFs, dataDirs)
i18nDirs := b.theBigFs.overlayDirs[files.ComponentFolderI18n]
i18nFs, err := hugofs.NewSliceFs(i18nDirs...)
if err != nil {
return nil, err
}
- b.result.I18n = b.newSourceFilesystem(i18nFs, i18nDirs)
+ b.result.I18n = b.newSourceFilesystem(files.ComponentFolderI18n, i18nFs, i18nDirs)
contentDirs := b.theBigFs.overlayDirs[files.ComponentFolderContent]
contentBfs := afero.NewBasePathFs(b.theBigFs.overlayMountsContent, files.ComponentFolderContent)
@@ -409,7 +442,7 @@ func (b *sourceFilesystemsBuilder) Build() (*SourceFilesystems, error) {
return nil, errors.Wrap(err, "create content filesystem")
}
- b.result.Content = b.newSourceFilesystem(contentFs, contentDirs)
+ b.result.Content = b.newSourceFilesystem(files.ComponentFolderContent, contentFs, contentDirs)
b.result.Work = afero.NewReadOnlyFs(b.theBigFs.overlayFull)
@@ -421,13 +454,13 @@ func (b *sourceFilesystemsBuilder) Build() (*SourceFilesystems, error) {
if b.theBigFs.staticPerLanguage != nil {
// Multihost mode
for k, v := range b.theBigFs.staticPerLanguage {
- sfs := b.newSourceFilesystem(v, b.result.StaticDirs)
+ sfs := b.newSourceFilesystem(files.ComponentFolderStatic, v, b.result.StaticDirs)
sfs.PublishFolder = k
ms[k] = sfs
}
} else {
bfs := afero.NewBasePathFs(b.theBigFs.overlayMountsStatic, files.ComponentFolderStatic)
- ms[""] = b.newSourceFilesystem(bfs, b.result.StaticDirs)
+ ms[""] = b.newSourceFilesystem(files.ComponentFolderStatic, bfs, b.result.StaticDirs)
}
return b.result, nil
diff --git a/hugolib/hugo_modules_test.go b/hugolib/hugo_modules_test.go
index 40185e051c2..90044327533 100644
--- a/hugolib/hugo_modules_test.go
+++ b/hugolib/hugo_modules_test.go
@@ -40,6 +40,9 @@ import (
// TODO(bep) this fails when testmodBuilder is also building ...
func TestHugoModules(t *testing.T) {
+ if !isCI() {
+ t.Skip("skip (relative) long running modules test when running locally")
+ }
t.Parallel()
if !isCI() || hugo.GoMinorVersion() < 12 {
diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go
index c71dcaa5940..526f39fca9a 100644
--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -20,6 +20,8 @@ import (
"strings"
"sync"
+ "github.com/gohugoio/hugo/identity"
+
radix "github.com/armon/go-radix"
"github.com/gohugoio/hugo/output"
@@ -411,7 +413,6 @@ func applyDeps(cfg deps.DepsCfg, sites ...*Site) error {
}
d.OutputFormatsConfig = s.outputFormatsConfig
}
-
}
return nil
@@ -806,12 +807,40 @@ func (h *HugoSites) findPagesByKindIn(kind string, inPages page.Pages) page.Page
return h.Sites[0].findPagesByKindIn(kind, inPages)
}
-func (h *HugoSites) findPagesByShortcode(shortcode string) page.Pages {
- var pages page.Pages
+func (h *HugoSites) resetPageStateFromEvents(idset identity.Identities) {
+
for _, s := range h.Sites {
- pages = append(pages, s.findPagesByShortcode(shortcode)...)
+ PAGES:
+ for _, p := range s.rawAllPages {
+ OUTPUTS:
+ for _, po := range p.pageOutputs {
+ if po.cp == nil {
+ continue
+ }
+ for id, _ := range idset {
+ if po.cp.dependencyTracker.Search(id) != nil {
+ po.cp.Reset()
+ p.forceRender = true
+ continue OUTPUTS
+ }
+ }
+ }
+
+ for _, s := range p.shortcodeState.shortcodes {
+ for id, _ := range idset {
+ if idm, ok := s.info.(identity.Manager); ok && idm.Search(id) != nil {
+ for _, po := range p.pageOutputs {
+ if po.cp != nil {
+ po.cp.Reset()
+ }
+ }
+ p.forceRender = true
+ continue PAGES
+ }
+ }
+ }
+ }
}
- return pages
}
// Used in partial reloading to determine if the change is in a bundle.
diff --git a/hugolib/hugo_sites_build.go b/hugolib/hugo_sites_build.go
index a70a19e7c31..d749ff581d5 100644
--- a/hugolib/hugo_sites_build.go
+++ b/hugolib/hugo_sites_build.go
@@ -71,7 +71,7 @@ func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error {
if conf.whatChanged == nil {
// Assume everything has changed
- conf.whatChanged = &whatChanged{source: true, other: true}
+ conf.whatChanged = &whatChanged{source: true}
}
var prepareErr error
diff --git a/hugolib/page.go b/hugolib/page.go
index 56202f5e0b1..fb3b597be3b 100644
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -23,6 +23,12 @@ import (
"sort"
"strings"
+ "github.com/mitchellh/mapstructure"
+
+ "github.com/gohugoio/hugo/tpl"
+
+ "github.com/gohugoio/hugo/identity"
+
"github.com/gohugoio/hugo/markup/converter"
"github.com/gohugoio/hugo/common/maps"
@@ -43,9 +49,11 @@ import (
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/source"
+ "github.com/spf13/cast"
"github.com/gohugoio/hugo/common/collections"
"github.com/gohugoio/hugo/common/text"
+ "github.com/gohugoio/hugo/markup/converter/hooks"
"github.com/gohugoio/hugo/resources"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/resources/resource"
@@ -59,7 +67,11 @@ var (
var (
pageTypesProvider = resource.NewResourceTypesProvider(media.OctetType, pageResourceType)
- nopPageOutput = &pageOutput{pagePerOutputProviders: nopPagePerOutput}
+ nopPageOutput = &pageOutput{
+ pagePerOutputProviders: nopPagePerOutput,
+ ContentProvider: page.NopPage,
+ TableOfContentsProvider: page.NopPage,
+ }
)
// pageContext provides contextual information about this page, for error
@@ -317,6 +329,54 @@ func (ps *pageState) initCommonProviders(pp pagePaths) error {
return nil
}
+func (p *pageState) createRenderHooks(f output.Format) (*hooks.Render, error) {
+
+ layoutDescriptor := p.getLayoutDescriptor()
+ layoutDescriptor.RenderingHook = true
+ layoutDescriptor.LayoutOverride = false
+ layoutDescriptor.Layout = ""
+
+ layoutDescriptor.Kind = "render-link"
+ linkLayouts, err := p.s.layoutHandler.For(layoutDescriptor, f)
+ if err != nil {
+ return nil, err
+ }
+
+ layoutDescriptor.Kind = "render-image"
+ imageLayouts, err := p.s.layoutHandler.For(layoutDescriptor, f)
+ if err != nil {
+ return nil, err
+ }
+
+ if linkLayouts == nil && imageLayouts == nil {
+ return nil, nil
+ }
+
+ var linkRenderer hooks.LinkRenderer
+ var imageRenderer hooks.LinkRenderer
+
+ if templ, found := p.s.lookupTemplate(linkLayouts...); found {
+ linkRenderer = contentLinkRenderer{
+ templateHandler: p.s.Tmpl,
+ Provider: templ.(tpl.Info),
+ templ: templ,
+ }
+ }
+
+ if templ, found := p.s.lookupTemplate(imageLayouts...); found {
+ imageRenderer = contentLinkRenderer{
+ templateHandler: p.s.Tmpl,
+ Provider: templ.(tpl.Info),
+ templ: templ,
+ }
+ }
+
+ return &hooks.Render{
+ LinkRenderer: linkRenderer,
+ ImageRenderer: imageRenderer,
+ }, nil
+}
+
func (p *pageState) getLayoutDescriptor() output.LayoutDescriptor {
p.layoutDescriptorInit.Do(func() {
var section string
@@ -464,11 +524,86 @@ func (p *pageState) AlternativeOutputFormats() page.OutputFormats {
return o
}
-func (p *pageState) Render(layout ...string) template.HTML {
+type renderStringOpts struct {
+ Display string
+ Markup string
+}
+
+var defualtRenderStringOpts = renderStringOpts{
+ Display: "inline",
+ Markup: "", // Will inherit the page's value when not set.
+}
+
+func (p *pageState) RenderString(args ...interface{}) (template.HTML, error) {
+ if len(args) < 1 || len(args) > 2 {
+ return "", errors.New("want 1 or 2 arguments")
+ }
+
+ var s string
+ opts := defualtRenderStringOpts
+ sidx := 1
+
+ if len(args) == 1 {
+ sidx = 0
+ } else {
+ m, ok := args[0].(map[string]interface{})
+ if !ok {
+ return "", errors.New("first argument must be a map")
+ }
+
+ if err := mapstructure.WeakDecode(m, &opts); err != nil {
+ return "", errors.WithMessage(err, "failed to decode options")
+ }
+ }
+
+ var err error
+ s, err = cast.ToStringE(args[sidx])
+ if err != nil {
+ return "", err
+ }
+
+ conv := p.getContentConverter()
+ if opts.Markup != "" && opts.Markup != p.m.markup {
+ var err error
+ // TODO(bep) consider cache
+ conv, err = p.m.newContentConverter(p, opts.Markup, nil)
+ if err != nil {
+ return "", p.wrapError(err)
+ }
+ }
+
+ c, err := p.pageOutput.cp.renderContentWithConverter(conv, []byte(s), false)
+ if err != nil {
+ return "", p.wrapError(err)
+ }
+
+ b := c.Bytes()
+
+ if opts.Display == "inline" {
+ // We may have to rethink this in the future when we get other
+ // renderers.
+ b = p.s.ContentSpec.TrimShortHTML(b)
+ }
+
+ return template.HTML(string(b)), nil
+}
+
+func (p *pageState) addDependency(dep identity.Provider) {
+ if !p.s.running() || p.pageOutput.cp == nil {
+ return
+ }
+ p.pageOutput.cp.dependencyTracker.Add(dep)
+}
+
+func (p *pageState) RenderWithTemplateInfo(info tpl.Info, layout ...string) (template.HTML, error) {
+ p.addDependency(info)
+ return p.Render(layout...)
+}
+
+func (p *pageState) Render(layout ...string) (template.HTML, error) {
l, err := p.getLayouts(layout...)
if err != nil {
- p.s.SendError(p.wrapError(errors.Errorf(".Render: failed to resolve layout %v", layout)))
- return ""
+ return "", p.wrapError(errors.Errorf("failed to resolve layout %v", layout))
}
for _, layout := range l {
@@ -479,17 +614,18 @@ func (p *pageState) Render(layout ...string) template.HTML {
// We default to good old HTML.
templ, _ = p.s.Tmpl.Lookup(layout + ".html")
}
+
if templ != nil {
+ p.addDependency(templ.(tpl.Info))
res, err := executeToString(p.s.Tmpl, templ, p)
if err != nil {
- p.s.SendError(p.wrapError(errors.Wrapf(err, ".Render: failed to execute template %q v", layout)))
- return ""
+ return "", p.wrapError(errors.Wrapf(err, "failed to execute template %q v", layout))
}
- return template.HTML(res)
+ return template.HTML(res), nil
}
}
- return ""
+ return "", nil
}
@@ -745,15 +881,33 @@ func (p *pageState) shiftToOutputFormat(isRenderingSite bool, idx int) error {
p.pageOutput.paginator.reset()
}
- if idx > 0 {
- // Check if we can reuse content from one of the previous formats.
- for i := idx - 1; i >= 0; i-- {
- po := p.pageOutputs[i]
- if po.cp != nil && po.cp.reuse {
- p.pageOutput.cp = po.cp
- break
+ if isRenderingSite {
+ cp := p.pageOutput.cp
+ if cp == nil {
+
+ // Look for content to reuse.
+ for i := 0; i < len(p.pageOutputs); i++ {
+ if i == idx {
+ continue
+ }
+ po := p.pageOutputs[i]
+
+ if po.cp != nil && po.cp.reuse {
+ cp = po.cp
+ break
+ }
+ }
+ }
+
+ if cp == nil {
+ var err error
+ cp, err = newPageContentOutput(p, p.pageOutput)
+ if err != nil {
+ return err
}
}
+ p.pageOutput.initContentProvider(cp)
+ p.pageOutput.cp = cp
}
for _, r := range p.Resources().ByType(pageResourceType) {
diff --git a/hugolib/page__content.go b/hugolib/page__content.go
index 1919fb17154..013ab3072b7 100644
--- a/hugolib/page__content.go
+++ b/hugolib/page__content.go
@@ -30,8 +30,7 @@ var (
type pageContent struct {
renderable bool
selfLayout string
-
- truncated bool
+ truncated bool
cmap *pageContentMap
diff --git a/hugolib/page__meta.go b/hugolib/page__meta.go
index 1fc69c21826..9f3e1687ad8 100644
--- a/hugolib/page__meta.go
+++ b/hugolib/page__meta.go
@@ -592,7 +592,7 @@ func (pm *pageMeta) setMetadata(bucket *pagesMapBucket, p *pageState, frontmatte
return nil
}
-func (p *pageMeta) applyDefaultValues() error {
+func (p *pageMeta) applyDefaultValues(ps *pageState) error {
if p.markup == "" {
if !p.File().IsZero() {
// Fall back to file extension
@@ -651,25 +651,37 @@ func (p *pageMeta) applyDefaultValues() error {
markup = "markdown"
}
- cp := p.s.ContentSpec.Converters.Get(markup)
- if cp == nil {
- return errors.Errorf("no content renderer found for markup %q", p.markup)
+ cp, err := p.newContentConverter(ps, markup, renderingConfigOverrides)
+ if err != nil {
+ return err
}
+ p.contentConverter = cp
+ }
+
+ return nil
+
+}
+
+func (p *pageMeta) newContentConverter(ps *pageState, markup string, renderingConfigOverrides map[string]interface{}) (converter.Converter, error) {
+ cp := p.s.ContentSpec.Converters.Get(markup)
+ if cp == nil {
+ return nil, errors.Errorf("no content renderer found for markup %q", p.markup)
+ }
- cpp, err := cp.New(converter.DocumentContext{
+ cpp, err := cp.New(
+ converter.DocumentContext{
+ Document: newPageForRenderHook(ps),
DocumentID: p.f.UniqueID(),
DocumentName: p.f.Path(),
ConfigOverrides: renderingConfigOverrides,
- })
+ },
+ )
- if err != nil {
- return err
- }
- p.contentConverter = cpp
+ if err != nil {
+ return nil, err
}
- return nil
-
+ return cpp, nil
}
// The output formats this page will be rendered to.
diff --git a/hugolib/page__new.go b/hugolib/page__new.go
index 99bf305aa58..d810c8df6a3 100644
--- a/hugolib/page__new.go
+++ b/hugolib/page__new.go
@@ -112,7 +112,7 @@ func newPageFromMeta(meta map[string]interface{}, metaProvider *pageMeta) (*page
}
}
- if err := metaProvider.applyDefaultValues(); err != nil {
+ if err := metaProvider.applyDefaultValues(ps); err != nil {
return err
}
@@ -134,7 +134,7 @@ func newPageFromMeta(meta map[string]interface{}, metaProvider *pageMeta) (*page
}
makeOut := func(f output.Format, render bool) *pageOutput {
- return newPageOutput(nil, ps, pp, f, render)
+ return newPageOutput(ps, pp, f, render)
}
if ps.m.standalone {
@@ -234,7 +234,7 @@ func newPageWithContent(f *fileInfo, s *Site, bundled bool, content resource.Ope
return ps.wrapError(err)
}
- if err := metaProvider.applyDefaultValues(); err != nil {
+ if err := metaProvider.applyDefaultValues(ps); err != nil {
return err
}
@@ -242,10 +242,6 @@ func newPageWithContent(f *fileInfo, s *Site, bundled bool, content resource.Ope
}
ps.init.Add(func() (interface{}, error) {
- reuseContent := ps.renderable && !ps.shortcodeState.hasShortcodes()
-
- // Creates what's needed for each output format.
- contentPerOutput := newPageContentOutput(ps)
pp, err := newPagePaths(s, ps, metaProvider)
if err != nil {
@@ -264,18 +260,18 @@ func newPageWithContent(f *fileInfo, s *Site, bundled bool, content resource.Ope
}
_, render := outputFormatsForPage.GetByName(f.Name)
- var contentProvider *pageContentOutput
- if reuseContent && i > 0 {
- contentProvider = ps.pageOutputs[0].cp
- } else {
- var err error
- contentProvider, err = contentPerOutput(f)
+ po := newPageOutput(ps, pp, f, render)
+
+ // Create a content provider for the first,
+ // we may be able to reuse it.
+ if i == 0 {
+ contentProvider, err := newPageContentOutput(ps, po)
if err != nil {
return nil, err
}
+ po.initContentProvider(contentProvider)
}
- po := newPageOutput(contentProvider, ps, pp, f, render)
ps.pageOutputs[i] = po
created[f.Name] = po
}
diff --git a/hugolib/page__output.go b/hugolib/page__output.go
index 764c46a937b..183bf010d4e 100644
--- a/hugolib/page__output.go
+++ b/hugolib/page__output.go
@@ -14,13 +14,13 @@
package hugolib
import (
+ "github.com/gohugoio/hugo/markup/converter"
"github.com/gohugoio/hugo/output"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/resources/resource"
)
func newPageOutput(
- cp *pageContentOutput, // may be nil
ps *pageState,
pp pagePaths,
f output.Format,
@@ -45,36 +45,23 @@ func newPageOutput(
paginatorProvider = pag
}
- var (
- contentProvider page.ContentProvider = page.NopPage
- tableOfContentsProvider page.TableOfContentsProvider = page.NopPage
- )
-
- if cp != nil {
- contentProvider = cp
- tableOfContentsProvider = cp
- }
-
providers := struct {
- page.ContentProvider
- page.TableOfContentsProvider
page.PaginatorProvider
resource.ResourceLinksProvider
targetPather
}{
- contentProvider,
- tableOfContentsProvider,
paginatorProvider,
linksProvider,
targetPathsProvider,
}
po := &pageOutput{
- f: f,
- cp: cp,
- pagePerOutputProviders: providers,
- render: render,
- paginator: pag,
+ f: f,
+ pagePerOutputProviders: providers,
+ ContentProvider: page.NopPage,
+ TableOfContentsProvider: page.NopPage,
+ render: render,
+ paginator: pag,
}
return po
@@ -94,16 +81,54 @@ type pageOutput struct {
// used in template(s).
paginator *pagePaginator
- // This interface provides the functionality that is specific for this
+ // These interface provides the functionality that is specific for this
// output format.
pagePerOutputProviders
+ page.ContentProvider
+ page.TableOfContentsProvider
- // This may be nil.
+ // May be nil.
cp *pageContentOutput
}
+func (o *pageOutput) initRenderHooks() error {
+ if o.cp == nil {
+ return nil
+ }
+
+ ps := o.cp.p
+
+ c := ps.getContentConverter()
+ if c == nil || !c.Supports(converter.FeatureRenderHooks) {
+ return nil
+ }
+
+ h, err := ps.createRenderHooks(o.f)
+ if err != nil {
+ return err
+ }
+ if h == nil {
+ return nil
+ }
+
+ o.cp.renderHooks = h
+
+ return nil
+
+}
+
+func (p *pageOutput) initContentProvider(cp *pageContentOutput) {
+ if cp == nil {
+ return
+ }
+ p.ContentProvider = cp
+ p.TableOfContentsProvider = cp
+ p.cp = cp
+}
+
func (p *pageOutput) enablePlaceholders() {
if p.cp != nil {
p.cp.enablePlaceholders()
}
+
}
diff --git a/hugolib/page__per_output.go b/hugolib/page__per_output.go
index d3a32e15c2a..03448ba80af 100644
--- a/hugolib/page__per_output.go
+++ b/hugolib/page__per_output.go
@@ -23,6 +23,10 @@ import (
"sync"
"unicode/utf8"
+ "github.com/gohugoio/hugo/identity"
+
+ "github.com/gohugoio/hugo/markup/converter/hooks"
+
"github.com/gohugoio/hugo/markup/converter"
"github.com/gohugoio/hugo/lazy"
@@ -58,152 +62,174 @@ var (
}
)
-func newPageContentOutput(p *pageState) func(f output.Format) (*pageContentOutput, error) {
+var pageContentOutputDependenciesID = identity.KeyValueIdentity{Key: "pageOutput", Value: "dependencies"}
+
+func newPageContentOutput(p *pageState, po *pageOutput) (*pageContentOutput, error) {
parent := p.init
- return func(f output.Format) (*pageContentOutput, error) {
- cp := &pageContentOutput{
- p: p,
- f: f,
- }
+ var dependencyTracker identity.Manager
+ if p.s.running() {
+ dependencyTracker = identity.NewManager(pageContentOutputDependenciesID)
+ }
- initContent := func() (err error) {
- if p.cmap == nil {
- // Nothing to do.
- return nil
+ cp := &pageContentOutput{
+ dependencyTracker: dependencyTracker,
+ p: p,
+ f: po.f,
+ }
+
+ initContent := func() (err error) {
+ if p.cmap == nil {
+ // Nothing to do.
+ return nil
+ }
+ defer func() {
+ // See https://github.com/gohugoio/hugo/issues/6210
+ if r := recover(); r != nil {
+ err = fmt.Errorf("%s", r)
+ p.s.Log.ERROR.Printf("[BUG] Got panic:\n%s\n%s", r, string(debug.Stack()))
}
- defer func() {
- // See https://github.com/gohugoio/hugo/issues/6210
- if r := recover(); r != nil {
- err = fmt.Errorf("%s", r)
- p.s.Log.ERROR.Printf("[BUG] Got panic:\n%s\n%s", r, string(debug.Stack()))
- }
- }()
+ }()
- var hasVariants bool
+ if err := po.initRenderHooks(); err != nil {
+ return err
+ }
- cp.contentPlaceholders, hasVariants, err = p.shortcodeState.renderShortcodesForPage(p, f)
- if err != nil {
- return err
- }
+ var hasShortcodeVariants bool
- if p.render && !hasVariants {
- // We can reuse this for the other output formats
- cp.enableReuse()
- }
+ f := po.f
+ cp.contentPlaceholders, hasShortcodeVariants, err = p.shortcodeState.renderShortcodesForPage(p, f)
+ if err != nil {
+ return err
+ }
- cp.workContent = p.contentToRender(cp.contentPlaceholders)
+ enableReuse := !(hasShortcodeVariants || cp.renderHooksHaveVariants)
- isHTML := cp.p.m.markup == "html"
+ if enableReuse {
+ // Reuse this for the other output formats.
+ // We may improve on this, but we really want to avoid re-rendering the content
+ // to all output formats.
+ // The current rule is that if you need output format-aware shortcodes or
+ // content rendering hooks, create a output format-specific template, e.g.
+ // myshortcode.amp.html.
+ cp.enableReuse()
+ }
- if p.renderable {
- if !isHTML {
- r, err := cp.renderContent(cp.workContent)
- if err != nil {
- return err
- }
- cp.convertedResult = r
- cp.workContent = r.Bytes()
+ cp.workContent = p.contentToRender(cp.contentPlaceholders)
- if _, ok := r.(converter.TableOfContentsProvider); !ok {
- tmpContent, tmpTableOfContents := helpers.ExtractTOC(cp.workContent)
- cp.tableOfContents = helpers.BytesToHTML(tmpTableOfContents)
- cp.workContent = tmpContent
- }
- }
+ isHTML := cp.p.m.markup == "html"
- if cp.placeholdersEnabled {
- // ToC was accessed via .Page.TableOfContents in the shortcode,
- // at a time when the ToC wasn't ready.
- cp.contentPlaceholders[tocShortcodePlaceholder] = string(cp.tableOfContents)
+ if p.renderable {
+ if !isHTML {
+ r, err := cp.renderContent(cp.workContent, true)
+ if err != nil {
+ return err
}
- if p.cmap.hasNonMarkdownShortcode || cp.placeholdersEnabled {
- // There are one or more replacement tokens to be replaced.
- cp.workContent, err = replaceShortcodeTokens(cp.workContent, cp.contentPlaceholders)
- if err != nil {
- return err
- }
+ cp.workContent = r.Bytes()
+
+ if tocProvider, ok := r.(converter.TableOfContentsProvider); ok {
+ cfg := p.s.ContentSpec.Converters.GetMarkupConfig()
+ cp.tableOfContents = template.HTML(
+ tocProvider.TableOfContents().ToHTML(
+ cfg.TableOfContents.StartLevel,
+ cfg.TableOfContents.EndLevel,
+ cfg.TableOfContents.Ordered,
+ ),
+ )
+ } else {
+ tmpContent, tmpTableOfContents := helpers.ExtractTOC(cp.workContent)
+ cp.tableOfContents = helpers.BytesToHTML(tmpTableOfContents)
+ cp.workContent = tmpContent
}
+ }
- if cp.p.source.hasSummaryDivider {
- if isHTML {
- src := p.source.parsed.Input()
+ if cp.placeholdersEnabled {
+ // ToC was accessed via .Page.TableOfContents in the shortcode,
+ // at a time when the ToC wasn't ready.
+ cp.contentPlaceholders[tocShortcodePlaceholder] = string(cp.tableOfContents)
+ }
- // Use the summary sections as they are provided by the user.
- if p.source.posSummaryEnd != -1 {
- cp.summary = helpers.BytesToHTML(src[p.source.posMainContent:p.source.posSummaryEnd])
- }
+ if p.cmap.hasNonMarkdownShortcode || cp.placeholdersEnabled {
+ // There are one or more replacement tokens to be replaced.
+ cp.workContent, err = replaceShortcodeTokens(cp.workContent, cp.contentPlaceholders)
+ if err != nil {
+ return err
+ }
+ }
- if cp.p.source.posBodyStart != -1 {
- cp.workContent = src[cp.p.source.posBodyStart:]
- }
+ if cp.p.source.hasSummaryDivider {
+ if isHTML {
+ src := p.source.parsed.Input()
- } else {
- summary, content, err := splitUserDefinedSummaryAndContent(cp.p.m.markup, cp.workContent)
- if err != nil {
- cp.p.s.Log.ERROR.Printf("Failed to set user defined summary for page %q: %s", cp.p.pathOrTitle(), err)
- } else {
- cp.workContent = content
- cp.summary = helpers.BytesToHTML(summary)
- }
+ // Use the summary sections as they are provided by the user.
+ if p.source.posSummaryEnd != -1 {
+ cp.summary = helpers.BytesToHTML(src[p.source.posMainContent:p.source.posSummaryEnd])
+ }
+
+ if cp.p.source.posBodyStart != -1 {
+ cp.workContent = src[cp.p.source.posBodyStart:]
}
- } else if cp.p.m.summary != "" {
- b, err := cp.p.getContentConverter().Convert(
- converter.RenderContext{
- Src: []byte(cp.p.m.summary),
- },
- )
+ } else {
+ summary, content, err := splitUserDefinedSummaryAndContent(cp.p.m.markup, cp.workContent)
if err != nil {
- return err
+ cp.p.s.Log.ERROR.Printf("Failed to set user defined summary for page %q: %s", cp.p.pathOrTitle(), err)
+ } else {
+ cp.workContent = content
+ cp.summary = helpers.BytesToHTML(summary)
}
- html := cp.p.s.ContentSpec.TrimShortHTML(b.Bytes())
- cp.summary = helpers.BytesToHTML(html)
}
+ } else if cp.p.m.summary != "" {
+ b, err := cp.renderContent([]byte(cp.p.m.summary), false)
+ if err != nil {
+ return err
+ }
+ html := cp.p.s.ContentSpec.TrimShortHTML(b.Bytes())
+ cp.summary = helpers.BytesToHTML(html)
}
+ }
- cp.content = helpers.BytesToHTML(cp.workContent)
-
- if !p.renderable {
- err := cp.addSelfTemplate()
- return err
- }
-
- return nil
+ cp.content = helpers.BytesToHTML(cp.workContent)
+ if !p.renderable {
+ err := cp.addSelfTemplate()
+ return err
}
- // Recursive loops can only happen in content files with template code (shortcodes etc.)
- // Avoid creating new goroutines if we don't have to.
- needTimeout := !p.renderable || p.shortcodeState.hasShortcodes()
-
- if needTimeout {
- cp.initMain = parent.BranchWithTimeout(p.s.siteCfg.timeout, func(ctx context.Context) (interface{}, error) {
- return nil, initContent()
- })
- } else {
- cp.initMain = parent.Branch(func() (interface{}, error) {
- return nil, initContent()
- })
- }
+ return nil
- cp.initPlain = cp.initMain.Branch(func() (interface{}, error) {
- cp.plain = helpers.StripHTML(string(cp.content))
- cp.plainWords = strings.Fields(cp.plain)
- cp.setWordCounts(p.m.isCJKLanguage)
+ }
- if err := cp.setAutoSummary(); err != nil {
- return err, nil
- }
+ // Recursive loops can only happen in content files with template code (shortcodes etc.)
+ // Avoid creating new goroutines if we don't have to.
+ needTimeout := !p.renderable || p.shortcodeState.hasShortcodes()
+ needTimeout = needTimeout || cp.renderHooks != nil
- return nil, nil
+ if needTimeout {
+ cp.initMain = parent.BranchWithTimeout(p.s.siteCfg.timeout, func(ctx context.Context) (interface{}, error) {
+ return nil, initContent()
})
+ } else {
+ cp.initMain = parent.Branch(func() (interface{}, error) {
+ return nil, initContent()
+ })
+ }
- return cp, nil
+ cp.initPlain = cp.initMain.Branch(func() (interface{}, error) {
+ cp.plain = helpers.StripHTML(string(cp.content))
+ cp.plainWords = strings.Fields(cp.plain)
+ cp.setWordCounts(p.m.isCJKLanguage)
- }
+ if err := cp.setAutoSummary(); err != nil {
+ return err, nil
+ }
+
+ return nil, nil
+ })
+
+ return cp, nil
}
@@ -211,7 +237,7 @@ func newPageContentOutput(p *pageState) func(f output.Format) (*pageContentOutpu
type pageContentOutput struct {
f output.Format
- // If we can safely reuse this for other output formats.
+ // If we can reuse this for other output formats.
reuse bool
reuseInit sync.Once
@@ -224,10 +250,15 @@ type pageContentOutput struct {
placeholdersEnabled bool
placeholdersEnabledInit sync.Once
+ // May be nil.
+ renderHooks *hooks.Render
+ // Set if there are more than one output format variant
+ renderHooksHaveVariants bool // TODO(bep) reimplement this in another way, consolidate with shortcodes
+
// Content state
- workContent []byte
- convertedResult converter.Result
+ workContent []byte
+ dependencyTracker identity.Manager // Set in server mode.
// Temporary storage of placeholders mapped to their content.
// These are shortcodes etc. Some of these will need to be replaced
@@ -248,6 +279,20 @@ type pageContentOutput struct {
readingTime int
}
+func (p *pageContentOutput) trackDependency(id identity.Provider) {
+ if p.dependencyTracker != nil {
+ p.dependencyTracker.Add(id)
+ }
+}
+
+func (p *pageContentOutput) Reset() {
+ if p.dependencyTracker != nil {
+ p.dependencyTracker.Reset()
+ }
+ p.initMain.Reset()
+ p.initPlain.Reset()
+}
+
func (p *pageContentOutput) Content() (interface{}, error) {
if p.p.s.initInit(p.initMain, p.p) {
return p.content, nil
@@ -290,10 +335,6 @@ func (p *pageContentOutput) Summary() template.HTML {
func (p *pageContentOutput) TableOfContents() template.HTML {
p.p.s.initInit(p.initMain, p.p)
- if tocProvider, ok := p.convertedResult.(converter.TableOfContentsProvider); ok {
- cfg := p.p.s.ContentSpec.Converters.GetMarkupConfig()
- return template.HTML(tocProvider.TableOfContents().ToHTML(cfg.TableOfContents.StartLevel, cfg.TableOfContents.EndLevel, cfg.TableOfContents.Ordered))
- }
return p.tableOfContents
}
@@ -331,12 +372,30 @@ func (p *pageContentOutput) setAutoSummary() error {
}
-func (cp *pageContentOutput) renderContent(content []byte) (converter.Result, error) {
- return cp.p.getContentConverter().Convert(
+func (cp *pageContentOutput) renderContent(content []byte, renderTOC bool) (converter.Result, error) {
+ c := cp.p.getContentConverter()
+ return cp.renderContentWithConverter(c, content, renderTOC)
+}
+
+func (cp *pageContentOutput) renderContentWithConverter(c converter.Converter, content []byte, renderTOC bool) (converter.Result, error) {
+
+ r, err := c.Convert(
converter.RenderContext{
- Src: content,
- RenderTOC: true,
+ Src: content,
+ RenderTOC: renderTOC,
+ RenderHooks: cp.renderHooks,
})
+
+ if err == nil {
+ if ids, ok := r.(identity.IdentitiesProvider); ok {
+ for _, v := range ids.GetIdentities() {
+ cp.trackDependency(v)
+ }
+ }
+ }
+
+ return r, err
+
}
func (p *pageContentOutput) setWordCounts(isCJKLanguage bool) {
@@ -392,9 +451,7 @@ func (p *pageContentOutput) enableReuse() {
// these will be shifted out when rendering a given output format.
type pagePerOutputProviders interface {
targetPather
- page.ContentProvider
page.PaginatorProvider
- page.TableOfContentsProvider
resource.ResourceLinksProvider
}
diff --git a/hugolib/page_test.go b/hugolib/page_test.go
index dc8bc821c15..f4bf3ac0040 100644
--- a/hugolib/page_test.go
+++ b/hugolib/page_test.go
@@ -93,12 +93,6 @@ Summary Next Line. {{}}.
More text here.
Some more text
-`
-
- simplePageWithEmbeddedScript = `---
-title: Simple
----
-
`
simplePageWithSummaryDelimiterSameLine = `---
@@ -325,6 +319,7 @@ func normalizeContent(c string) string {
}
func checkPageTOC(t *testing.T, page page.Page, toc string) {
+ t.Helper()
if page.TableOfContents() != template.HTML(toc) {
t.Fatalf("Page TableOfContents is:\n%q.\nExpected %q", page.TableOfContents(), toc)
}
diff --git a/hugolib/page_unwrap_test.go b/hugolib/page_unwrap_test.go
index 20888166ad7..bcc1b769a4f 100644
--- a/hugolib/page_unwrap_test.go
+++ b/hugolib/page_unwrap_test.go
@@ -26,6 +26,7 @@ func TestUnwrapPage(t *testing.T) {
p := &pageState{}
c.Assert(mustUnwrap(newPageForShortcode(p)), qt.Equals, p)
+ c.Assert(mustUnwrap(newPageForRenderHook(p)), qt.Equals, p)
}
func mustUnwrap(v interface{}) page.Page {
diff --git a/hugolib/pagebundler_test.go b/hugolib/pagebundler_test.go
index da7427d7e19..eeed51b91c0 100644
--- a/hugolib/pagebundler_test.go
+++ b/hugolib/pagebundler_test.go
@@ -811,6 +811,7 @@ Short Thumb Width: {{ $thumb.Width }}
writeSource(t, fs, filepath.Join(workDir, "layouts", "_default", "single.html"), singleLayout)
writeSource(t, fs, filepath.Join(workDir, "layouts", "_default", "list.html"), listLayout)
writeSource(t, fs, filepath.Join(workDir, "layouts", "shortcodes", "myShort.html"), myShort)
+ writeSource(t, fs, filepath.Join(workDir, "layouts", "shortcodes", "myShort.customo"), myShort)
writeSource(t, fs, filepath.Join(workDir, "base", "_index.md"), pageContent)
writeSource(t, fs, filepath.Join(workDir, "base", "_1.md"), pageContent)
diff --git a/hugolib/pagecollections.go b/hugolib/pagecollections.go
index 7e9682e90e1..adcbbccefef 100644
--- a/hugolib/pagecollections.go
+++ b/hugolib/pagecollections.go
@@ -358,16 +358,6 @@ func (c *PageCollections) removePage(page *pageState) {
}
}
-func (c *PageCollections) findPagesByShortcode(shortcode string) page.Pages {
- var pages page.Pages
- for _, p := range c.rawAllPages {
- if p.HasShortcode(shortcode) {
- pages = append(pages, p)
- }
- }
- return pages
-}
-
func (c *PageCollections) replacePage(page *pageState) {
// will find existing page that matches filepath and remove it
c.removePage(page)
diff --git a/hugolib/shortcode.go b/hugolib/shortcode.go
index 69bcb6d4f73..a4d635a5544 100644
--- a/hugolib/shortcode.go
+++ b/hugolib/shortcode.go
@@ -23,8 +23,6 @@ import (
"html/template"
"path"
- "github.com/gohugoio/hugo/markup/converter"
-
"github.com/gohugoio/hugo/common/herrors"
"github.com/pkg/errors"
@@ -198,7 +196,7 @@ type shortcode struct {
}
func (s shortcode) insertPlaceholder() bool {
- return !s.doMarkup || s.info.Config.Version == 1
+ return !s.doMarkup || s.info.ParseInfo().Config.Version == 1
}
func (s shortcode) innerString() string {
@@ -349,14 +347,9 @@ func renderShortcode(
// Pre Hugo 0.55 this was the behaviour even for the outer-most
// shortcode.
- if sc.doMarkup && (level > 0 || sc.info.Config.Version == 1) {
+ if sc.doMarkup && (level > 0 || sc.info.ParseInfo().Config.Version == 1) {
var err error
-
- b, err := p.getContentConverter().Convert(
- converter.RenderContext{
- Src: []byte(inner),
- },
- )
+ b, err := p.pageOutput.cp.renderContent([]byte(inner), false)
if err != nil {
return "", false, err
@@ -494,13 +487,13 @@ Loop:
case currItem.IsRightShortcodeDelim():
// we trust the template on this:
// if there's no inner, we're done
- if !sc.isInline && !sc.info.IsInner {
+ if !sc.isInline && !sc.info.ParseInfo().IsInner {
return sc, nil
}
case currItem.IsShortcodeClose():
next := pt.Peek()
- if !sc.isInline && !sc.info.IsInner {
+ if !sc.isInline && !sc.info.ParseInfo().IsInner {
if next.IsError() {
// return that error, more specific
continue
@@ -540,7 +533,7 @@ Loop:
return nil, _errors.Errorf("template for shortcode %q not found", sc.name)
}
- sc.info = tmpl.(tpl.TemplateInfoProvider).TemplateInfo()
+ sc.info = tmpl.(tpl.Info)
case currItem.IsInlineShortcodeName():
sc.name = currItem.ValStr()
sc.isInline = true
diff --git a/hugolib/shortcode_page.go b/hugolib/shortcode_page.go
index e8a3a37e19b..5a56e434f2f 100644
--- a/hugolib/shortcode_page.go
+++ b/hugolib/shortcode_page.go
@@ -54,3 +54,22 @@ func (p *pageForShortcode) TableOfContents() template.HTML {
p.p.enablePlaceholders()
return p.toc
}
+
+// This is what is sent into the content render hooks (link, image).
+type pageForRenderHooks struct {
+ page.PageWithoutContent
+ page.TableOfContentsProvider
+ page.ContentProvider
+}
+
+func newPageForRenderHook(p *pageState) page.Page {
+ return &pageForRenderHooks{
+ PageWithoutContent: p,
+ ContentProvider: page.NopPage,
+ TableOfContentsProvider: page.NopPage,
+ }
+}
+
+func (p *pageForRenderHooks) page() page.Page {
+ return p.PageWithoutContent.(page.Page)
+}
diff --git a/hugolib/shortcode_test.go b/hugolib/shortcode_test.go
index 5e71db501a1..9d948c807b7 100644
--- a/hugolib/shortcode_test.go
+++ b/hugolib/shortcode_test.go
@@ -379,8 +379,13 @@ title: "Shortcodes Galore!"
if s == nil {
return ""
}
+
+ var version int
+ if s.info != nil {
+ version = s.info.ParseInfo().Config.Version
+ }
return strReplacer.Replace(fmt.Sprintf("%s;inline:%t;closing:%t;inner:%v;params:%v;ordinal:%d;markup:%t;version:%d;pos:%d",
- s.name, s.isInline, s.isClosing, s.inner, s.params, s.ordinal, s.doMarkup, s.info.Config.Version, s.pos))
+ s.name, s.isInline, s.isClosing, s.inner, s.params, s.ordinal, s.doMarkup, version, s.pos))
}
regexpCheck := func(re string) func(c *qt.C, shortcode *shortcode, err error) {
diff --git a/hugolib/site.go b/hugolib/site.go
index 67ddff4d901..a1313d7e3b1 100644
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -28,6 +28,10 @@ import (
"strings"
"time"
+ "github.com/gohugoio/hugo/identity"
+
+ "github.com/gohugoio/hugo/markup/converter/hooks"
+
"github.com/gohugoio/hugo/resources/resource"
"github.com/gohugoio/hugo/markup/converter"
@@ -801,7 +805,6 @@ func (s *Site) multilingual() *Multilingual {
type whatChanged struct {
source bool
- other bool
files map[string]bool
}
@@ -888,10 +891,11 @@ func (s *Site) translateFileEvents(events []fsnotify.Event) []fsnotify.Event {
// It returns whetever the content source was changed.
// TODO(bep) clean up/rewrite this method.
func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) error, events []fsnotify.Event) error {
-
events = s.filterFileEvents(events)
events = s.translateFileEvents(events)
+ changeIdentities := make(identity.Identities)
+
s.Log.DEBUG.Printf("Rebuild for events %q", events)
h := s.h
@@ -902,11 +906,12 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro
sourceChanged = []fsnotify.Event{}
sourceReallyChanged = []fsnotify.Event{}
contentFilesChanged []string
- tmplChanged = []fsnotify.Event{}
- dataChanged = []fsnotify.Event{}
- i18nChanged = []fsnotify.Event{}
- shortcodesChanged = make(map[string]bool)
- sourceFilesChanged = make(map[string]bool)
+
+ tmplChanged bool
+ dataChanged bool
+ i18nChanged bool
+
+ sourceFilesChanged = make(map[string]bool)
// prevent spamming the log on changes
logger = helpers.NewDistinctFeedbackLogger()
@@ -915,37 +920,34 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro
var cachePartitions []string
for _, ev := range events {
- if assetsFilename := s.BaseFs.Assets.MakePathRelative(ev.Name); assetsFilename != "" {
- cachePartitions = append(cachePartitions, resources.ResourceKeyPartitions(assetsFilename)...)
- }
+ id, found := s.eventToIdentity(ev)
+ if found {
+ changeIdentities[id] = id
+
+ if assetsFilename := s.BaseFs.Assets.MakePathRelative(ev.Name); assetsFilename != "" {
+ cachePartitions = append(cachePartitions, resources.ResourceKeyPartitions(assetsFilename)...)
+ }
+
+ switch id.Type {
+ case files.ComponentFolderContent:
+ logger.Println("Source changed", ev)
+ sourceChanged = append(sourceChanged, ev)
+ case files.ComponentFolderLayouts:
+ logger.Println("Template changed", ev)
+ tmplChanged = true
+ case files.ComponentFolderData:
+ logger.Println("Data changed", ev)
+ dataChanged = true
+ case files.ComponentFolderI18n:
+ logger.Println("i18n changed", ev)
+ i18nChanged = true
- if s.isContentDirEvent(ev) {
- logger.Println("Source changed", ev)
- sourceChanged = append(sourceChanged, ev)
- }
- if s.isLayoutDirEvent(ev) {
- logger.Println("Template changed", ev)
- tmplChanged = append(tmplChanged, ev)
-
- if strings.Contains(ev.Name, "shortcodes") {
- shortcode := filepath.Base(ev.Name)
- shortcode = strings.TrimSuffix(shortcode, filepath.Ext(shortcode))
- shortcodesChanged[shortcode] = true
}
- }
- if s.isDataDirEvent(ev) {
- logger.Println("Data changed", ev)
- dataChanged = append(dataChanged, ev)
- }
- if s.isI18nEvent(ev) {
- logger.Println("i18n changed", ev)
- i18nChanged = append(dataChanged, ev)
}
}
changed := &whatChanged{
- source: len(sourceChanged) > 0 || len(shortcodesChanged) > 0,
- other: len(tmplChanged) > 0 || len(i18nChanged) > 0 || len(dataChanged) > 0,
+ source: len(sourceChanged) > 0,
files: sourceFilesChanged,
}
@@ -960,7 +962,7 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro
s.ResourceSpec.ResourceCache.DeletePartitions(cachePartitions...)
}
- if len(tmplChanged) > 0 || len(i18nChanged) > 0 {
+ if tmplChanged || i18nChanged {
sites := s.h.Sites
first := sites[0]
@@ -989,7 +991,7 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro
}
}
- if len(dataChanged) > 0 {
+ if dataChanged {
s.h.init.data.Reset()
}
@@ -1018,18 +1020,7 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro
sourceFilesChanged[ev.Name] = true
}
- for shortcode := range shortcodesChanged {
- // There are certain scenarios that, when a shortcode changes,
- // it isn't sufficient to just rerender the already parsed shortcode.
- // One example is if the user adds a new shortcode to the content file first,
- // and then creates the shortcode on the file system.
- // To handle these scenarios, we must do a full reprocessing of the
- // pages that keeps a reference to the changed shortcode.
- pagesWithShortcode := h.findPagesByShortcode(shortcode)
- for _, p := range pagesWithShortcode {
- contentFilesChanged = append(contentFilesChanged, p.File().Filename())
- }
- }
+ h.resetPageStateFromEvents(changeIdentities)
if len(sourceReallyChanged) > 0 || len(contentFilesChanged) > 0 {
var filenamesChanged []string
@@ -1218,20 +1209,14 @@ func (s *Site) initializeSiteInfo() error {
return nil
}
-func (s *Site) isI18nEvent(e fsnotify.Event) bool {
- return s.BaseFs.SourceFilesystems.IsI18n(e.Name)
-}
-
-func (s *Site) isDataDirEvent(e fsnotify.Event) bool {
- return s.BaseFs.SourceFilesystems.IsData(e.Name)
-}
-
-func (s *Site) isLayoutDirEvent(e fsnotify.Event) bool {
- return s.BaseFs.SourceFilesystems.IsLayout(e.Name)
-}
+func (s *Site) eventToIdentity(e fsnotify.Event) (identity.PathIdentity, bool) {
+ for _, fs := range s.BaseFs.SourceFilesystems.FileSystems() {
+ if p := fs.Path(e.Name); p != "" {
+ return identity.NewPathIdentity(fs.Name, p), true
+ }
+ }
-func (s *Site) isContentDirEvent(e fsnotify.Event) bool {
- return s.BaseFs.IsContent(e.Name)
+ return identity.PathIdentity{}, false
}
func (s *Site) readAndProcessContent(filenames ...string) error {
@@ -1562,6 +1547,26 @@ var infoOnMissingLayout = map[string]bool{
"404": true,
}
+type contentLinkRenderer struct {
+ templateHandler tpl.TemplateHandler
+ identity.Provider
+ templ tpl.Template
+}
+
+func (r contentLinkRenderer) Render(w io.Writer, ctx hooks.LinkContext) error {
+ return r.templateHandler.Execute(r.templ, w, ctx)
+}
+
+func (s *Site) lookupTemplate(layouts ...string) (tpl.Template, bool) {
+ for _, l := range layouts {
+ if templ, found := s.Tmpl.Lookup(l); found {
+ return templ, true
+ }
+ }
+
+ return nil, false
+}
+
func (s *Site) renderForLayouts(name, outputFormat string, d interface{}, w io.Writer, layouts ...string) (err error) {
templ := s.findFirstTemplate(layouts...)
if templ == nil {
diff --git a/hugolib/site_benchmark_new_test.go b/hugolib/site_benchmark_new_test.go
index 646124b09ca..13302300ee9 100644
--- a/hugolib/site_benchmark_new_test.go
+++ b/hugolib/site_benchmark_new_test.go
@@ -127,6 +127,36 @@ title = "What is Markdown"
baseURL = "https://example.com"
`)
+
+ data, err := ioutil.ReadFile(filepath.FromSlash("testdata/what-is-markdown.md"))
+ sb.Assert(err, qt.IsNil)
+ datastr := string(data)
+ getContent := func(i int) string {
+ return fmt.Sprintf(`---
+title: "Page %d"
+---
+
+`, i) + datastr
+
+ }
+ for i := 1; i <= 100; i++ {
+ sb.WithContent(fmt.Sprintf("content/page%d.md", i), getContent(i))
+ }
+
+ return sb
+ },
+ func(s *sitesBuilder) {
+ s.Assert(s.CheckExists("public/page8/index.html"), qt.Equals, true)
+ },
+ },
+ {"Markdown with custom link handler", func(b testing.TB) *sitesBuilder {
+ sb := newTestSitesBuilder(b).WithConfigFile("toml", `
+title = "What is Markdown"
+baseURL = "https://example.com"
+
+`)
+
+ sb.WithTemplatesAdded("_default/_markup/render-link.html", `CUSTOM LINK `)
data, err := ioutil.ReadFile(filepath.FromSlash("testdata/what-is-markdown.md"))
sb.Assert(err, qt.IsNil)
datastr := string(data)
diff --git a/hugolib/template_test.go b/hugolib/template_test.go
index 71b4b46c0bf..4c41894cabd 100644
--- a/hugolib/template_test.go
+++ b/hugolib/template_test.go
@@ -18,8 +18,12 @@ import (
"path/filepath"
"testing"
+ "github.com/gohugoio/hugo/identity"
+
+ qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/hugofs"
+ "github.com/gohugoio/hugo/tpl"
"github.com/spf13/viper"
)
@@ -320,6 +324,7 @@ Partial cached1: {{ partialCached "p1" "input1" $key1 }}
Partial cached2: {{ partialCached "p1" "input2" $key1 }}
Partial cached3: {{ partialCached "p1" "input3" $key2 }}
`,
+
"partials/p1.html", `partial: {{ . }}`,
)
@@ -331,3 +336,85 @@ Partial cached3: {{ partialCached "p1" "input3" $key2 }}
Partial cached3: partial: input3
`)
}
+
+func TestTemplateDependencies(t *testing.T) {
+ b := newTestSitesBuilder(t).Running()
+
+ b.WithTemplates("index.html", `
+{{ $p := site.GetPage "p1" }}
+{{ partial "p1.html" $p }}
+{{ partialCached "p2.html" "foo" }}
+{{ partials.Include "p3.html" "data" }}
+{{ partials.IncludeCached "p4.html" "foo" }}
+{{ $p := partial "p5" }}
+{{ partial "sub/p6.html" }}
+{{ partial "P7.html" }}
+{{ template "_default/foo.html" }}
+Partial nested: {{ partial "p10" }}
+
+`,
+ "partials/p1.html", `ps: {{ .Render "li" }}`,
+ "partials/p2.html", `p2`,
+ "partials/p3.html", `p3`,
+ "partials/p4.html", `p4`,
+ "partials/p5.html", `p5`,
+ "partials/sub/p6.html", `p6`,
+ "partials/P7.html", `p7`,
+ "partials/p8.html", `p8 {{ partial "p9.html" }}`,
+ "partials/p9.html", `p9`,
+ "partials/p10.html", `p10 {{ partial "p11.html" }}`,
+ "partials/p11.html", `p11`,
+ "_default/foo.html", `foo`,
+ "_default/li.html", `li {{ partial "p8.html" }}`,
+ )
+
+ b.WithContent("p1.md", `---
+title: P1
+---
+
+
+`)
+
+ b.Build(BuildCfg{})
+
+ s := b.H.Sites[0]
+
+ templ, found := s.lookupTemplate("index.html")
+ b.Assert(found, qt.Equals, true)
+
+ idset := make(map[identity.Identity]bool)
+ collectIdentities(idset, templ.(tpl.Info))
+ b.Assert(idset, qt.HasLen, 10)
+
+}
+
+func collectIdentities(set map[identity.Identity]bool, provider identity.Provider) {
+ if ids, ok := provider.(identity.IdentitiesProvider); ok {
+ for _, id := range ids.GetIdentities() {
+ collectIdentities(set, id)
+ }
+ } else {
+ set[provider.GetIdentity()] = true
+ }
+}
+
+func printRecursiveIdentities(level int, id identity.Provider) {
+ if level == 0 {
+ fmt.Println(id.GetIdentity(), "===>")
+ }
+ if ids, ok := id.(identity.IdentitiesProvider); ok {
+ level++
+ for _, id := range ids.GetIdentities() {
+ printRecursiveIdentities(level, id)
+ }
+ } else {
+ ident(level)
+ fmt.Println("ID", id)
+ }
+}
+
+func ident(n int) {
+ for i := 0; i < n; i++ {
+ fmt.Print(" ")
+ }
+}
diff --git a/hugolib/testhelpers_test.go b/hugolib/testhelpers_test.go
index ea1ee967499..80aafe052ef 100644
--- a/hugolib/testhelpers_test.go
+++ b/hugolib/testhelpers_test.go
@@ -389,8 +389,9 @@ func (s *sitesBuilder) EditFiles(filenameContent ...string) *sitesBuilder {
var changedFiles []string
for i := 0; i < len(filenameContent); i += 2 {
filename, content := filepath.FromSlash(filenameContent[i]), filenameContent[i+1]
- changedFiles = append(changedFiles, filename)
- writeSource(s.T, s.Fs, s.absFilename(filename), content)
+ absFilename := s.absFilename(filename)
+ changedFiles = append(changedFiles, absFilename)
+ writeSource(s.T, s.Fs, absFilename, content)
}
s.changedFiles = changedFiles
@@ -963,10 +964,6 @@ func isCI() bool {
return os.Getenv("CI") != ""
}
-func isGo111() bool {
- return strings.Contains(runtime.Version(), "1.11")
-}
-
// See https://github.com/golang/go/issues/19280
// Not in use.
var parallelEnabled = true
diff --git a/identity/identity.go b/identity/identity.go
new file mode 100644
index 00000000000..d06710efe91
--- /dev/null
+++ b/identity/identity.go
@@ -0,0 +1,131 @@
+package identity
+
+import (
+ "path/filepath"
+ "strings"
+ "sync"
+)
+
+// NewIdentityManager creates a new Manager starting at id.
+func NewManager(id Provider) Manager {
+ return &identityManager{
+ Provider: id,
+ ids: Identities{id.GetIdentity(): id},
+ }
+}
+
+// NewPathIdentity creates a new Identity with the two identifiers
+// type and path.
+func NewPathIdentity(typ, pat string) PathIdentity {
+ pat = strings.ToLower(strings.TrimPrefix(filepath.ToSlash(pat), "/"))
+ return PathIdentity{Type: typ, Path: pat}
+}
+
+// Identities stores identity providers.
+type Identities map[Identity]Provider
+
+func (ids Identities) search(id Identity) Provider {
+ if v, found := ids[id]; found {
+ return v
+ }
+ for _, v := range ids {
+ switch t := v.(type) {
+ case IdentitiesProvider:
+ if nested := t.GetIdentities().search(id); nested != nil {
+ return nested
+ }
+ }
+ }
+ return nil
+}
+
+// IdentitiesProvider provides all Identities.
+type IdentitiesProvider interface {
+ GetIdentities() Identities
+}
+
+// Identity represents an thing that can provide an identify. This can be
+// any Go type, but the Identity returned by GetIdentify must be hashable.
+type Identity interface {
+ Provider
+ Name() string
+}
+
+// Manager manages identities, and is itself a Provider of Identity.
+type Manager interface {
+ IdentitiesProvider
+ Provider
+ Add(ids ...Provider)
+ Search(id Identity) Provider
+ Reset()
+}
+
+// A PathIdentity is a common identity identified by a type and a path, e.g. "layouts" and "_default/single.html".
+type PathIdentity struct {
+ Type string
+ Path string
+}
+
+// GetIdentity returns itself.
+func (id PathIdentity) GetIdentity() Identity {
+ return id
+}
+
+// Name returns the Path.
+func (id PathIdentity) Name() string {
+ return id.Path
+}
+
+// A KeyValueIdentity a general purpose identity.
+type KeyValueIdentity struct {
+ Key string
+ Value string
+}
+
+// GetIdentity returns itself.
+func (id KeyValueIdentity) GetIdentity() Identity {
+ return id
+}
+
+// Name returns the Key.
+func (id KeyValueIdentity) Name() string {
+ return id.Key
+}
+
+// Provider provides the hashable Identity.
+type Provider interface {
+ GetIdentity() Identity
+}
+
+type identityManager struct {
+ sync.Mutex
+ Provider
+ ids Identities
+}
+
+func (im *identityManager) Add(ids ...Provider) {
+ im.Lock()
+ for _, id := range ids {
+ im.ids[id.GetIdentity()] = id
+ }
+ im.Unlock()
+}
+
+func (im *identityManager) Reset() {
+ im.Lock()
+ id := im.GetIdentity()
+ im.ids = Identities{id.GetIdentity(): id}
+ im.Unlock()
+}
+
+func (im *identityManager) GetIdentities() Identities {
+ im.Lock()
+ defer im.Unlock()
+ return im.ids
+}
+
+func (im *identityManager) Search(id Identity) Provider {
+ im.Lock()
+ defer im.Unlock()
+ return im.ids.search(id.GetIdentity())
+}
diff --git a/identity/identity_test.go b/identity/identity_test.go
new file mode 100644
index 00000000000..adebcad916f
--- /dev/null
+++ b/identity/identity_test.go
@@ -0,0 +1,42 @@
+// Copyright 2019 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package identity
+
+import (
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestIdentityManager(t *testing.T) {
+ c := qt.New(t)
+
+ id1 := testIdentity{name: "id1"}
+ im := NewManager(id1)
+
+ c.Assert(im.Search(id1).GetIdentity(), qt.Equals, id1)
+ c.Assert(im.Search(testIdentity{name: "notfound"}), qt.Equals, nil)
+}
+
+type testIdentity struct {
+ name string
+}
+
+func (id testIdentity) GetIdentity() Identity {
+ return id
+}
+
+func (id testIdentity) Name() string {
+ return id.name
+}
diff --git a/markup/asciidoc/convert.go b/markup/asciidoc/convert.go
index 65fdde0f564..a72aac39198 100644
--- a/markup/asciidoc/convert.go
+++ b/markup/asciidoc/convert.go
@@ -18,6 +18,7 @@ package asciidoc
import (
"os/exec"
+ "github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/markup/internal"
"github.com/gohugoio/hugo/markup/converter"
@@ -47,6 +48,10 @@ func (a *asciidocConverter) Convert(ctx converter.RenderContext) (converter.Resu
return converter.Bytes(a.getAsciidocContent(ctx.Src, a.ctx)), nil
}
+func (c *asciidocConverter) Supports(feature identity.Identity) bool {
+ return false
+}
+
// getAsciidocContent calls asciidoctor or asciidoc as an external helper
// to convert AsciiDoc content to HTML.
func (a *asciidocConverter) getAsciidocContent(src []byte, ctx converter.DocumentContext) []byte {
diff --git a/markup/blackfriday/convert.go b/markup/blackfriday/convert.go
index 350defcb63c..3df23c7ae74 100644
--- a/markup/blackfriday/convert.go
+++ b/markup/blackfriday/convert.go
@@ -15,6 +15,7 @@
package blackfriday
import (
+ "github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/markup/blackfriday/blackfriday_config"
"github.com/gohugoio/hugo/markup/converter"
"github.com/russross/blackfriday"
@@ -72,6 +73,10 @@ func (c *blackfridayConverter) Convert(ctx converter.RenderContext) (converter.R
return converter.Bytes(blackfriday.Markdown(ctx.Src, r, c.extensions)), nil
}
+func (c *blackfridayConverter) Supports(feature identity.Identity) bool {
+ return false
+}
+
func (c *blackfridayConverter) getHTMLRenderer(renderTOC bool) blackfriday.Renderer {
flags := getFlags(renderTOC, c.bf)
diff --git a/markup/converter/converter.go b/markup/converter/converter.go
index a1141f65ccc..a4585bd0380 100644
--- a/markup/converter/converter.go
+++ b/markup/converter/converter.go
@@ -16,6 +16,8 @@ package converter
import (
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/config"
+ "github.com/gohugoio/hugo/identity"
+ "github.com/gohugoio/hugo/markup/converter/hooks"
"github.com/gohugoio/hugo/markup/markup_config"
"github.com/gohugoio/hugo/markup/tableofcontents"
"github.com/spf13/afero"
@@ -67,6 +69,7 @@ func (n newConverter) Name() string {
// another format, e.g. Markdown to HTML.
type Converter interface {
Convert(ctx RenderContext) (Result, error)
+ Supports(feature identity.Identity) bool
}
// Result represents the minimum returned from Convert.
@@ -94,6 +97,7 @@ func (b Bytes) Bytes() []byte {
// DocumentContext holds contextual information about the document to convert.
type DocumentContext struct {
+ Document interface{} // May be nil. Usually a page.Page
DocumentID string
DocumentName string
ConfigOverrides map[string]interface{}
@@ -101,6 +105,11 @@ type DocumentContext struct {
// RenderContext holds contextual information about the content to render.
type RenderContext struct {
- Src []byte
- RenderTOC bool
+ Src []byte
+ RenderTOC bool
+ RenderHooks *hooks.Render
}
+
+var (
+ FeatureRenderHooks = identity.NewPathIdentity("markup", "renderingHooks")
+)
diff --git a/markup/converter/hooks/hooks.go b/markup/converter/hooks/hooks.go
new file mode 100644
index 00000000000..63beacc377e
--- /dev/null
+++ b/markup/converter/hooks/hooks.go
@@ -0,0 +1,57 @@
+// Copyright 2019 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hooks
+
+import (
+ "io"
+
+ "github.com/gohugoio/hugo/identity"
+)
+
+type LinkContext interface {
+ Page() interface{}
+ Destination() string
+ Title() string
+ Text() string
+}
+
+type Render struct {
+ LinkRenderer LinkRenderer
+ ImageRenderer LinkRenderer
+}
+
+func (r *Render) Eq(other interface{}) bool {
+ ro, ok := other.(*Render)
+ if !ok {
+ return false
+ }
+ if r == nil || ro == nil {
+ return r == nil
+ }
+
+ if r.ImageRenderer.GetIdentity() != ro.ImageRenderer.GetIdentity() {
+ return false
+ }
+
+ if r.LinkRenderer.GetIdentity() != ro.LinkRenderer.GetIdentity() {
+ return false
+ }
+
+ return true
+}
+
+type LinkRenderer interface {
+ Render(w io.Writer, ctx LinkContext) error
+ identity.Provider
+}
diff --git a/markup/goldmark/convert.go b/markup/goldmark/convert.go
index 15b0f0d77c8..130f02a2fb7 100644
--- a/markup/goldmark/convert.go
+++ b/markup/goldmark/convert.go
@@ -15,21 +15,22 @@
package goldmark
import (
+ "bufio"
"bytes"
"fmt"
"path/filepath"
"runtime/debug"
+ "github.com/gohugoio/hugo/identity"
+
"github.com/pkg/errors"
"github.com/spf13/afero"
"github.com/gohugoio/hugo/hugofs"
- "github.com/alecthomas/chroma/styles"
"github.com/gohugoio/hugo/markup/converter"
"github.com/gohugoio/hugo/markup/highlight"
- "github.com/gohugoio/hugo/markup/markup_config"
"github.com/gohugoio/hugo/markup/tableofcontents"
"github.com/yuin/goldmark"
hl "github.com/yuin/goldmark-highlighting"
@@ -48,7 +49,7 @@ type provide struct {
}
func (p provide) New(cfg converter.ProviderConfig) (converter.Provider, error) {
- md := newMarkdown(cfg.MarkupConfig)
+ md := newMarkdown(cfg)
return converter.NewProvider("goldmark", func(ctx converter.DocumentContext) (converter.Converter, error) {
return &goldmarkConverter{
ctx: ctx,
@@ -64,11 +65,13 @@ type goldmarkConverter struct {
cfg converter.ProviderConfig
}
-func newMarkdown(mcfg markup_config.Config) goldmark.Markdown {
- cfg := mcfg.Goldmark
+func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown {
+ mcfg := pcfg.MarkupConfig
+ cfg := pcfg.MarkupConfig.Goldmark
var (
extensions = []goldmark.Extender{
+ newLinks(),
newTocExtension(),
}
rendererOptions []renderer.Option
@@ -143,15 +146,53 @@ func newMarkdown(mcfg markup_config.Config) goldmark.Markdown {
}
+var _ identity.IdentitiesProvider = (*converterResult)(nil)
+
type converterResult struct {
converter.Result
toc tableofcontents.Root
+ ids identity.Identities
}
func (c converterResult) TableOfContents() tableofcontents.Root {
return c.toc
}
+func (c converterResult) GetIdentities() identity.Identities {
+ return c.ids
+}
+
+type renderContext struct {
+ util.BufWriter
+ renderContextData
+}
+
+type renderContextData interface {
+ RenderContext() converter.RenderContext
+ DocumentContext() converter.DocumentContext
+ AddIdentity(id identity.Identity)
+}
+
+type renderContextDataHolder struct {
+ rctx converter.RenderContext
+ dctx converter.DocumentContext
+ ids identity.Manager
+}
+
+func (ctx *renderContextDataHolder) RenderContext() converter.RenderContext {
+ return ctx.rctx
+}
+
+func (ctx *renderContextDataHolder) DocumentContext() converter.DocumentContext {
+ return ctx.dctx
+}
+
+func (ctx *renderContextDataHolder) AddIdentity(id identity.Identity) {
+ ctx.ids.Add(id)
+}
+
+var converterIdentity = identity.KeyValueIdentity{Key: "goldmark", Value: "converter"}
+
func (c *goldmarkConverter) Convert(ctx converter.RenderContext) (result converter.Result, err error) {
defer func() {
if r := recover(); r != nil {
@@ -166,9 +207,7 @@ func (c *goldmarkConverter) Convert(ctx converter.RenderContext) (result convert
buf := &bytes.Buffer{}
result = buf
- pctx := parser.NewContext()
- pctx.Set(tocEnableKey, ctx.RenderTOC)
-
+ pctx := newParserContext(ctx)
reader := text.NewReader(ctx.Src)
doc := c.md.Parser().Parse(
@@ -176,27 +215,58 @@ func (c *goldmarkConverter) Convert(ctx converter.RenderContext) (result convert
parser.WithContext(pctx),
)
- if err := c.md.Renderer().Render(buf, ctx.Src, doc); err != nil {
+ rcx := &renderContextDataHolder{
+ rctx: ctx,
+ dctx: c.ctx,
+ ids: identity.NewManager(converterIdentity),
+ }
+
+ w := renderContext{
+ BufWriter: bufio.NewWriter(buf),
+ renderContextData: rcx,
+ }
+
+ if err := c.md.Renderer().Render(w, ctx.Src, doc); err != nil {
return nil, err
}
- if toc, ok := pctx.Get(tocResultKey).(tableofcontents.Root); ok {
- return converterResult{
- Result: buf,
- toc: toc,
- }, nil
+ return converterResult{
+ Result: buf,
+ ids: rcx.ids.GetIdentities(),
+ toc: pctx.TableOfContents(),
+ }, nil
+
+}
+
+var featureSet = map[identity.Identity]bool{
+ converter.FeatureRenderHooks: true,
+}
+
+func (c *goldmarkConverter) Supports(feature identity.Identity) bool {
+ return featureSet[feature.GetIdentity()]
+}
+
+func newParserContext(rctx converter.RenderContext) *parserContext {
+ ctx := parser.NewContext()
+ ctx.Set(tocEnableKey, rctx.RenderTOC)
+ return &parserContext{
+ Context: ctx,
}
+}
- return buf, nil
+type parserContext struct {
+ parser.Context
}
-func newHighlighting(cfg highlight.Config) goldmark.Extender {
- style := styles.Get(cfg.Style)
- if style == nil {
- style = styles.Fallback
+func (p *parserContext) TableOfContents() tableofcontents.Root {
+ if v := p.Get(tocResultKey); v != nil {
+ return v.(tableofcontents.Root)
}
+ return tableofcontents.Root{}
+}
- e := hl.NewHighlighting(
+func newHighlighting(cfg highlight.Config) goldmark.Extender {
+ return hl.NewHighlighting(
hl.WithStyle(cfg.Style),
hl.WithGuessLanguage(cfg.GuessSyntax),
hl.WithCodeBlockOptions(highlight.GetCodeBlockOptions()),
@@ -230,6 +300,4 @@ func newHighlighting(cfg highlight.Config) goldmark.Extender {
}),
)
-
- return e
}
diff --git a/markup/goldmark/convert_test.go b/markup/goldmark/convert_test.go
index b6816d2e54a..2a97276064b 100644
--- a/markup/goldmark/convert_test.go
+++ b/markup/goldmark/convert_test.go
@@ -38,6 +38,9 @@ func TestConvert(t *testing.T) {
https://github.com/gohugoio/hugo/issues/6528
[Live Demo here!](https://docuapi.netlify.com/)
+[I'm an inline-style link with title](https://www.google.com "Google's Homepage")
+
+
## Code Fences
§§§bash
@@ -98,6 +101,7 @@ description
mconf := markup_config.Default
mconf.Highlight.NoClasses = false
+ mconf.Goldmark.Renderer.Unsafe = true
p, err := Provider.New(
converter.ProviderConfig{
@@ -106,15 +110,15 @@ description
},
)
c.Assert(err, qt.IsNil)
- conv, err := p.New(converter.DocumentContext{})
+ conv, err := p.New(converter.DocumentContext{DocumentID: "thedoc"})
c.Assert(err, qt.IsNil)
- b, err := conv.Convert(converter.RenderContext{Src: []byte(content)})
+ b, err := conv.Convert(converter.RenderContext{RenderTOC: true, Src: []byte(content)})
c.Assert(err, qt.IsNil)
got := string(b.Bytes())
// Links
- c.Assert(got, qt.Contains, `Live Demo here! `)
+ // c.Assert(got, qt.Contains, `Live Demo here! `)
// Header IDs
c.Assert(got, qt.Contains, `Custom ID `, qt.Commentf(got))
@@ -137,6 +141,11 @@ description
c.Assert(got, qt.Contains, `