summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2017-05-06 20:15:28 +0200
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2017-05-13 22:44:15 +0300
commitaf72db806f2c1c0bf1dfe5832275c41eeba89906 (patch)
treea1bc9c7d09836073a811a1b395b7756727fdb210
parente951d65771ca299aa899e91bfe00411a5ada8f19 (diff)
hugolib: Handle shortcode per output format
This commit allows shortcode per output format, a typical use case would be the special AMP media tags. Note that this will only re-render the "overridden" shortcodes and only in pages where these are used, so performance in the normal case should not suffer. Closes #3220
-rw-r--r--hugolib/handler_page.go4
-rw-r--r--hugolib/hugo_sites.go33
-rw-r--r--hugolib/hugo_sites_build.go2
-rw-r--r--hugolib/page.go9
-rw-r--r--hugolib/shortcode.go211
-rw-r--r--hugolib/shortcode_test.go164
-rw-r--r--hugolib/site_output_test.go9
-rw-r--r--hugolib/site_render.go6
-rw-r--r--tpl/template.go5
-rw-r--r--tpl/tplimpl/template.go22
10 files changed, 399 insertions, 66 deletions
diff --git a/hugolib/handler_page.go b/hugolib/handler_page.go
index a7dbff2e6..70266fb21 100644
--- a/hugolib/handler_page.go
+++ b/hugolib/handler_page.go
@@ -80,7 +80,7 @@ func (h htmlHandler) PageConvert(p *Page) HandledResult {
p.createWorkContentCopy()
if err := p.processShortcodes(); err != nil {
- return HandledResult{err: err}
+ p.s.Log.ERROR.Println(err)
}
return HandledResult{err: nil}
@@ -131,7 +131,7 @@ func commonConvert(p *Page) HandledResult {
p.createWorkContentCopy()
if err := p.processShortcodes(); err != nil {
- return HandledResult{err: err}
+ p.s.Log.ERROR.Println(err)
}
// TODO(bep) these page handlers need to be re-evaluated, as it is hard to
diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go
index 8a8fc2223..6e7034bd2 100644
--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -492,12 +492,7 @@ func (h *HugoSites) setupTranslations() {
}
}
-func (s *Site) preparePagesForRender(outFormatIdx int, cfg *BuildCfg) {
-
- if outFormatIdx > 0 {
- // TODO(bep) for now
- return
- }
+func (s *Site) preparePagesForRender(cfg *BuildCfg) {
pageChan := make(chan *Page)
wg := &sync.WaitGroup{}
@@ -508,8 +503,16 @@ func (s *Site) preparePagesForRender(outFormatIdx int, cfg *BuildCfg) {
go func(pages <-chan *Page, wg *sync.WaitGroup) {
defer wg.Done()
for p := range pages {
+ if !p.shouldRenderTo(s.rc.Format) {
+ // No need to prepare
+ continue
+ }
+ var shortcodeUpdate bool
+ if p.shortcodeState != nil {
+ shortcodeUpdate = p.shortcodeState.updateDelta()
+ }
- if !cfg.whatChanged.other && p.rendered {
+ if !shortcodeUpdate && !cfg.whatChanged.other && p.rendered {
// No need to process it again.
continue
}
@@ -521,10 +524,12 @@ func (s *Site) preparePagesForRender(outFormatIdx int, cfg *BuildCfg) {
// Mark it as rendered
p.rendered = true
- // If in watch mode, we need to keep the original so we can
- // repeat this process on rebuild.
+ // If in watch mode or if we have multiple output formats,
+ // we need to keep the original so we can
+ // potentially repeat this process on rebuild.
+ needsACopy := cfg.Watching || len(p.outputFormats) > 1
var workContentCopy []byte
- if cfg.Watching {
+ if needsACopy {
workContentCopy = make([]byte, len(p.workContent))
copy(workContentCopy, p.workContent)
} else {
@@ -589,15 +594,15 @@ func (h *HugoSites) Pages() Pages {
}
func handleShortcodes(p *Page, rawContentCopy []byte) ([]byte, error) {
- if p.shortcodeState != nil && len(p.shortcodeState.contentShortCodes) > 0 {
- p.s.Log.DEBUG.Printf("Replace %d shortcodes in %q", len(p.shortcodeState.contentShortCodes), p.BaseFileName())
- shortcodes, err := executeShortcodeFuncMap(p.shortcodeState.contentShortCodes)
+ if p.shortcodeState != nil && len(p.shortcodeState.contentShortcodes) > 0 {
+ p.s.Log.DEBUG.Printf("Replace %d shortcodes in %q", len(p.shortcodeState.contentShortcodes), p.BaseFileName())
+ err := p.shortcodeState.executeShortcodesForDelta(p)
if err != nil {
return rawContentCopy, err
}
- rawContentCopy, err = replaceShortcodeTokens(rawContentCopy, shortcodePlaceholderPrefix, shortcodes)
+ rawContentCopy, err = replaceShortcodeTokens(rawContentCopy, shortcodePlaceholderPrefix, p.shortcodeState.renderedShortcodes)
if err != nil {
p.s.Log.FATAL.Printf("Failed to replace shortcode tokens in %s:\n%s", p.BaseFileName(), err.Error())
diff --git a/hugolib/hugo_sites_build.go b/hugolib/hugo_sites_build.go
index 12689f6de..61e2ac337 100644
--- a/hugolib/hugo_sites_build.go
+++ b/hugolib/hugo_sites_build.go
@@ -213,7 +213,7 @@ func (h *HugoSites) render(config *BuildCfg) error {
s.initRenderFormats()
for i, rf := range s.renderFormats {
s.rc = &siteRenderingContext{Format: rf}
- s.preparePagesForRender(i, config)
+ s.preparePagesForRender(config)
if !config.SkipRender {
if err := s.render(i); err != nil {
diff --git a/hugolib/page.go b/hugolib/page.go
index 21603c3dc..8de4ad924 100644
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -1257,6 +1257,11 @@ func (p *Page) Menus() PageMenus {
return p.pageMenus
}
+func (p *Page) shouldRenderTo(f output.Format) bool {
+ _, found := p.outputFormats.GetByName(f.Name)
+ return found
+}
+
func (p *Page) determineMarkupType() string {
// Try markup explicitly set in the frontmatter
p.Markup = helpers.GuessType(p.Markup)
@@ -1372,8 +1377,8 @@ func (p *Page) SaveSource() error {
}
func (p *Page) processShortcodes() error {
- p.shortcodeState = newShortcodeHandler()
- tmpContent, err := p.shortcodeState.extractAndRenderShortcodes(string(p.workContent), p)
+ p.shortcodeState = newShortcodeHandler(p)
+ tmpContent, err := p.shortcodeState.extractShortcodes(string(p.workContent), p)
if err != nil {
return err
}
diff --git a/hugolib/shortcode.go b/hugolib/shortcode.go
index abe445d71..963ebd0b5 100644
--- a/hugolib/shortcode.go
+++ b/hugolib/shortcode.go
@@ -1,4 +1,4 @@
-// Copyright 2015 The Hugo Authors. All rights reserved.
+// Copyright 2017 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.
@@ -24,6 +24,10 @@ import (
"strings"
"sync"
+ "github.com/spf13/hugo/output"
+
+ "github.com/spf13/hugo/media"
+
bp "github.com/spf13/hugo/bufferpool"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/tpl"
@@ -149,9 +153,43 @@ func (sc shortcode) String() string {
return fmt.Sprintf("%s(%q, %t){%s}", sc.name, params, sc.doMarkup, sc.inner)
}
+// We may have special shortcode templates for AMP etc.
+// Note that in the below, OutputFormat may be empty.
+// We will try to look for the most specific shortcode template available.
+type scKey struct {
+ OutputFormat string
+ Suffix string
+ ShortcodePlaceholder string
+}
+
+func newScKey(m media.Type, shortcodeplaceholder string) scKey {
+ return scKey{Suffix: m.Suffix, ShortcodePlaceholder: shortcodeplaceholder}
+}
+
+func newScKeyFromOutputFormat(o output.Format, shortcodeplaceholder string) scKey {
+ return scKey{Suffix: o.MediaType.Suffix, OutputFormat: o.Name, ShortcodePlaceholder: shortcodeplaceholder}
+}
+
+func newDefaultScKey(shortcodeplaceholder string) scKey {
+ return newScKey(media.HTMLType, shortcodeplaceholder)
+}
+
type shortcodeHandler struct {
- // Maps the shortcodeplaceholder with the shortcode rendering func.
- contentShortCodes map[string]func() (string, error)
+ init sync.Once
+
+ p *Page
+
+ // This is all shortcode rendering funcs for all potential output formats.
+ contentShortcodes map[scKey]func() (string, error)
+
+ // This map contains the new or changed set of shortcodes that need
+ // to be rendered for the current output format.
+ contentShortcodesDelta map[scKey]func() (string, error)
+
+ // This maps the shorcode placeholders with the rendered content.
+ // We will do (potential) partial re-rendering per output format,
+ // so keep this for the unchanged.
+ renderedShortcodes map[string]string
// Maps the shortcodeplaceholder with the actual shortcode.
shortcodes map[string]shortcode
@@ -160,11 +198,13 @@ type shortcodeHandler struct {
nameSet map[string]bool
}
-func newShortcodeHandler() *shortcodeHandler {
+func newShortcodeHandler(p *Page) *shortcodeHandler {
return &shortcodeHandler{
- contentShortCodes: make(map[string]func() (string, error)),
- shortcodes: make(map[string]shortcode),
- nameSet: make(map[string]bool),
+ p: p,
+ contentShortcodes: make(map[scKey]func() (string, error)),
+ shortcodes: make(map[string]shortcode),
+ nameSet: make(map[string]bool),
+ renderedShortcodes: make(map[string]string),
}
}
@@ -208,11 +248,30 @@ const innerNewlineRegexp = "\n"
const innerCleanupRegexp = `\A<p>(.*)</p>\n\z`
const innerCleanupExpand = "$1"
-func renderShortcode(sc shortcode, parent *ShortcodeWithPage, p *Page) string {
- tmpl := getShortcodeTemplate(sc.name, p.s.Tmpl)
+func prepareShortcodeForPage(placeholder string, sc shortcode, parent *ShortcodeWithPage, p *Page) map[scKey]func() (string, error) {
+
+ m := make(map[scKey]func() (string, error))
+
+ for _, f := range p.outputFormats {
+ // The most specific template will win.
+ key := newScKeyFromOutputFormat(f, placeholder)
+ m[key] = func() (string, error) {
+ return renderShortcode(key, sc, nil, p), nil
+ }
+ }
+
+ return m
+}
+func renderShortcode(
+ tmplKey scKey,
+ sc shortcode,
+ parent *ShortcodeWithPage,
+ p *Page) string {
+
+ tmpl := getShortcodeTemplateForTemplateKey(tmplKey, sc.name, p.s.Tmpl)
if tmpl == nil {
- p.s.Log.ERROR.Printf("Unable to locate template for shortcode '%s' in page %q", sc.name, p.Path())
+ p.s.Log.ERROR.Printf("Unable to locate template for shortcode %q in page %q", sc.name, p.Path())
return ""
}
@@ -228,7 +287,7 @@ func renderShortcode(sc shortcode, parent *ShortcodeWithPage, p *Page) string {
case string:
inner += innerData.(string)
case shortcode:
- inner += renderShortcode(innerData.(shortcode), data, p)
+ inner += renderShortcode(tmplKey, innerData.(shortcode), data, p)
default:
p.s.Log.ERROR.Printf("Illegal state on shortcode rendering of %q in page %q. Illegal type in inner data: %s ",
sc.name, p.Path(), reflect.TypeOf(innerData))
@@ -268,6 +327,7 @@ func renderShortcode(sc shortcode, parent *ShortcodeWithPage, p *Page) string {
}
}
+ // TODO(bep) we may have plain text inner templates.
data.Inner = template.HTML(newInner)
} else {
data.Inner = template.HTML(inner)
@@ -278,51 +338,91 @@ func renderShortcode(sc shortcode, parent *ShortcodeWithPage, p *Page) string {
return renderShortcodeWithPage(tmpl, data)
}
-func (s *shortcodeHandler) extractAndRenderShortcodes(stringToParse string, p *Page) (string, error) {
- content, err := s.extractShortcodes(stringToParse, p)
-
- if err != nil {
- // try to render what we have whilst logging the error
- p.s.Log.ERROR.Println(err.Error())
+// The delta represents new output format-versions of the shortcodes,
+// which, combined with the ones that do not have alternative representations,
+// builds a complete set ready for a full rebuild of the Page content.
+// This method returns false if there are no new shortcode variants in the
+// current rendering context's output format. This mean we can safely reuse
+// the content from the previous output format, if any.
+func (s *shortcodeHandler) updateDelta() bool {
+ s.init.Do(func() {
+ s.contentShortcodes = createShortcodeRenderers(s.shortcodes, s.p)
+ })
+
+ contentShortcodes := s.contentShortcodesForOutputFormat(s.p.s.rc.Format)
+
+ if s.contentShortcodesDelta == nil || len(s.contentShortcodesDelta) == 0 {
+ s.contentShortcodesDelta = contentShortcodes
+ return true
}
- s.contentShortCodes = renderShortcodes(s.shortcodes, p)
+ delta := make(map[scKey]func() (string, error))
+
+ for k, v := range contentShortcodes {
+ if _, found := s.contentShortcodesDelta[k]; !found {
+ delta[k] = v
+ }
+ }
- return content, err
+ s.contentShortcodesDelta = delta
+ return len(delta) > 0
}
-var emptyShortcodeFn = func() (string, error) { return "", nil }
+func (s *shortcodeHandler) contentShortcodesForOutputFormat(f output.Format) map[scKey]func() (string, error) {
+ contentShortcodesForOuputFormat := make(map[scKey]func() (string, error))
+ for shortcodePlaceholder := range s.shortcodes {
-func executeShortcodeFuncMap(funcs map[string]func() (string, error)) (map[string]string, error) {
- result := make(map[string]string)
+ key := newScKeyFromOutputFormat(f, shortcodePlaceholder)
+ renderFn, found := s.contentShortcodes[key]
- for k, v := range funcs {
- s, err := v()
+ if !found {
+ key.OutputFormat = ""
+ renderFn, found = s.contentShortcodes[key]
+ }
+
+ // Fall back to HTML
+ if !found && key.Suffix != "html" {
+ key.Suffix = "html"
+ renderFn, found = s.contentShortcodes[key]
+ }
+
+ if !found {
+ panic(fmt.Sprintf("Shortcode %q could not be found", shortcodePlaceholder))
+ }
+ contentShortcodesForOuputFormat[newScKeyFromOutputFormat(f, shortcodePlaceholder)] = renderFn
+ }
+
+ return contentShortcodesForOuputFormat
+}
+
+func (s *shortcodeHandler) executeShortcodesForDelta(p *Page) error {
+
+ for k, render := range s.contentShortcodesDelta {
+ renderedShortcode, err := render()
if err != nil {
- return nil, fmt.Errorf("Failed to execute shortcode with key %s: %s", k, err)
+ return fmt.Errorf("Failed to execute shortcode in page %q: %s", p.Path(), err)
}
- result[k] = s
+
+ s.renderedShortcodes[k.ShortcodePlaceholder] = renderedShortcode
}
- return result, nil
+ return nil
+
}
-func renderShortcodes(shortcodes map[string]shortcode, p *Page) map[string]func() (string, error) {
+func createShortcodeRenderers(shortcodes map[string]shortcode, p *Page) map[scKey]func() (string, error) {
- renderedShortcodes := make(map[string]func() (string, error))
+ shortcodeRenderers := make(map[scKey]func() (string, error))
- for key, sc := range shortcodes {
- if sc.err != nil {
- // need to have something to replace with
- renderedShortcodes[key] = emptyShortcodeFn
- } else {
- shortcode := sc
- renderedShortcodes[key] = func() (string, error) { return renderShortcode(shortcode, nil, p), nil }
+ for k, v := range shortcodes {
+ prepared := prepareShortcodeForPage(k, v, nil, p)
+ for kk, vv := range prepared {
+ shortcodeRenderers[kk] = vv
}
}
- return renderedShortcodes
+ return shortcodeRenderers
}
var errShortCodeIllegalState = errors.New("Illegal shortcode state")
@@ -395,7 +495,9 @@ Loop:
sc.inner = append(sc.inner, currItem.val)
case tScName:
sc.name = currItem.val
- tmpl := getShortcodeTemplate(sc.name, p.s.Tmpl)
+ // We pick the first template for an arbitrary output format
+ // if more than one. It is "all inner or no inner".
+ tmpl := getShortcodeTemplateForTemplateKey(scKey{}, sc.name, p.s.Tmpl)
if tmpl == nil {
return sc, fmt.Errorf("Unable to locate template for shortcode %q in page %q", sc.name, p.Path())
}
@@ -566,17 +668,38 @@ func replaceShortcodeTokens(source []byte, prefix string, replacements map[strin
return source, nil
}
-func getShortcodeTemplate(name string, t tpl.TemplateFinder) *tpl.TemplateAdapter {
+func getShortcodeTemplateForTemplateKey(key scKey, shortcodeName string, t tpl.TemplateFinder) *tpl.TemplateAdapter {
isInnerShortcodeCache.RLock()
defer isInnerShortcodeCache.RUnlock()
- if x := t.Lookup("shortcodes/" + name + ".html"); x != nil {
- return x
+ var names []string
+
+ suffix := strings.ToLower(key.Suffix)
+ outFormat := strings.ToLower(key.OutputFormat)
+
+ if outFormat != "" && suffix != "" {
+ names = append(names, fmt.Sprintf("%s.%s.%s", shortcodeName, outFormat, suffix))
}
- if x := t.Lookup("theme/shortcodes/" + name + ".html"); x != nil {
- return x
+
+ if suffix != "" {
+ names = append(names, fmt.Sprintf("%s.%s", shortcodeName, suffix))
+ }
+
+ names = append(names, shortcodeName)
+
+ for _, name := range names {
+
+ if x := t.Lookup("shortcodes/" + name); x != nil {
+ return x
+ }
+ if x := t.Lookup("theme/shortcodes/" + name); x != nil {
+ return x
+ }
+ if x := t.Lookup("_internal/shortcodes/" + name); x != nil {
+ return x
+ }
}
- return t.Lookup("_internal/shortcodes/" + name + ".html")
+ return nil
}
func renderShortcodeWithPage(tmpl tpl.Template, data *ShortcodeWithPage) string {
diff --git a/hugolib/shortcode_test.go b/hugolib/shortcode_test.go
index 3d1922462..2c1e887c9 100644
--- a/hugolib/shortcode_test.go
+++ b/hugolib/shortcode_test.go
@@ -22,6 +22,14 @@ import (
"strings"
"testing"
+ jww "github.com/spf13/jwalterweatherman"
+
+ "github.com/spf13/afero"
+
+ "github.com/spf13/hugo/output"
+
+ "github.com/spf13/hugo/media"
+
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/source"
@@ -353,7 +361,7 @@ func TestExtractShortcodes(t *testing.T) {
return nil
})
- s := newShortcodeHandler()
+ s := newShortcodeHandler(p)
content, err := s.extractShortcodes(this.input, p)
if b, ok := this.expect.(bool); ok && !b {
@@ -563,6 +571,150 @@ tags:
}
+func TestShortcodeMultipleOutputFormats(t *testing.T) {
+ t.Parallel()
+
+ siteConfig := `
+baseURL = "http://example.com/blog"
+
+paginate = 1
+
+disableKinds = ["section", "taxonomy", "taxonomyTerm", "RSS", "sitemap", "robotsTXT", "404"]
+
+[outputs]
+home = [ "HTML", "AMP", "Calendar" ]
+page = [ "HTML", "AMP", "JSON" ]
+
+`
+
+ pageTemplate := `---
+title: "%s"
+---
+# Doc
+
+{{< myShort >}}
+{{< noExt >}}
+{{%% onlyHTML %%}}
+
+{{< myInner >}}{{< myShort >}}{{< /myInner >}}
+
+`
+
+ pageTemplateCSVOnly := `---
+title: "%s"
+outputs: ["CSV"]
+---
+# Doc
+
+CSV: {{< myShort >}}
+`
+
+ pageTemplateShortcodeNotFound := `---
+title: "%s"
+outputs: ["CSV"]
+---
+# Doc
+
+NotFound: {{< thisDoesNotExist >}}
+`
+
+ mf := afero.NewMemMapFs()
+
+ th, h := newTestSitesFromConfig(t, mf, siteConfig,
+ "layouts/_default/single.html", `Single HTML: {{ .Title }}|{{ .Content }}`,
+ "layouts/_default/single.json", `Single JSON: {{ .Title }}|{{ .Content }}`,
+ "layouts/_default/single.csv", `Single CSV: {{ .Title }}|{{ .Content }}`,
+ "layouts/index.html", `Home HTML: {{ .Title }}|{{ .Content }}`,
+ "layouts/index.amp.html", `Home AMP: {{ .Title }}|{{ .Content }}`,
+ "layouts/index.ics", `Home Calendar: {{ .Title }}|{{ .Content }}`,
+ "layouts/shortcodes/myShort.html", `ShortHTML`,
+ "layouts/shortcodes/myShort.amp.html", `ShortAMP`,
+ "layouts/shortcodes/myShort.csv", `ShortCSV`,
+ "layouts/shortcodes/myShort.ics", `ShortCalendar`,
+ "layouts/shortcodes/myShort.json", `ShortJSON`,
+ "layouts/shortcodes/noExt", `ShortNoExt`,
+ "layouts/shortcodes/onlyHTML.html", `ShortOnlyHTML`,
+ "layouts/shortcodes/myInner.html", `myInner:--{{- .Inner -}}--`,
+ )
+
+ fs := th.Fs
+
+ writeSource(t, fs, "content/_index.md", fmt.Sprintf(pageTemplate, "Home"))
+ writeSource(t, fs, "content/sect/mypage.md", fmt.Sprintf(pageTemplate, "Single"))
+ writeSource(t, fs, "content/sect/mycsvpage.md", fmt.Sprintf(pageTemplateCSVOnly, "Single CSV"))
+ writeSource(t, fs, "content/sect/notfound.md", fmt.Sprintf(pageTemplateShortcodeNotFound, "Single CSV"))
+
+ require.NoError(t, h.Build(BuildCfg{}))
+ require.Len(t, h.Sites, 1)
+
+ s := h.Sites[0]
+ home := s.getPage(KindHome)
+ require.NotNil(t, home)
+ require.Len(t, home.outputFormats, 3)
+
+ th.assertFileContent("public/index.html",
+ "Home HTML",
+ "ShortHTML",
+ "ShortNoExt",
+ "ShortOnlyHTML",
+ "myInner:--ShortHTML--",
+ )
+
+ th.assertFileContent("public/amp/index.html",
+ "Home AMP",
+ "ShortAMP",
+ "ShortNoExt",
+ "ShortOnlyHTML",
+ "myInner:--ShortAMP--",
+ )
+
+ th.assertFileContent("public/index.ics",
+ "Home Calendar",
+ "ShortCalendar",
+ "ShortNoExt",
+ "ShortOnlyHTML",
+ "myInner:--ShortCalendar--",
+ )
+
+ th.assertFileContent("public/sect/mypage/index.html",
+ "Single HTML",
+ "ShortHTML",
+ "ShortNoExt",
+ "ShortOnlyHTML",
+ "myInner:--ShortHTML--",
+ )
+
+ th.assertFileContent("public/sect/mypage/index.json",
+ "Single JSON",
+ "ShortJSON",
+ "ShortNoExt",
+ "ShortOnlyHTML",
+ "myInner:--ShortJSON--",
+ )
+
+ th.assertFileContent("public/amp/sect/mypage/index.html",
+ // No special AMP template
+ "Single HTML",
+ "ShortAMP",
+ "ShortNoExt",
+ "ShortOnlyHTML",
+ "myInner:--ShortAMP--",
+ )
+
+ th.assertFileContent("public/sect/mycsvpage/index.csv",
+ "Single CSV",
+ "ShortCSV",
+ )
+
+ th.assertFileContent("public/sect/notfound/index.csv",
+ "NotFound:",
+ "thisDoesNotExist",
+ )
+
+ require.Equal(t, uint64(1), s.Log.LogCountForLevel(jww.LevelError))
+
+}
+
func collectAndSortShortcodes(shortcodes map[string]shortcode) []string {
var asArray []string
@@ -681,3 +833,13 @@ func TestReplaceShortcodeTokens(t *testing.T) {
}
}
+
+func TestScKey(t *testing.T) {
+ require.Equal(t, scKey{Suffix: "xml", ShortcodePlaceholder: "ABCD"},
+ newScKey(media.XMLType, "ABCD"))
+ require.Equal(t, scKey{Suffix: "html", OutputFormat: "AMP", ShortcodePlaceholder: "EFGH"},
+ newScKeyFromOutputFormat(output.AMPFormat, "EFGH"))
+ require.Equal(t, scKey{Suffix: "html", ShortcodePlaceholder: "IJKL"},
+ newDefaultScKey("IJKL"))
+
+}
diff --git a/hugolib/site_output_test.go b/hugolib/site_output_test.go
index ef2cdf9ad..2935f3257 100644
--- a/hugolib/site_output_test.go
+++ b/hugolib/site_output_test.go
@@ -99,6 +99,8 @@ title: "%s"
outputs: %s
---
# Doc
+
+{{< myShort >}}
`
mf := afero.NewMemMapFs()
@@ -118,6 +120,8 @@ other = "Olboge"
"layouts/partials/GoHugo.html", `Go Hugo Partial`,
"layouts/_default/baseof.json", `START JSON:{{block "main" .}}default content{{ end }}:END JSON`,
"layouts/_default/baseof.html", `START HTML:{{block "main" .}}default content{{ end }}:END HTML`,
+ "layouts/shortcodes/myShort.html", `ShortHTML`,
+ "layouts/shortcodes/myShort.json", `ShortJSON`,
"layouts/_default/list.json", `{{ define "main" }}
List JSON|{{ .Title }}|{{ .Content }}|Alt formats: {{ len .AlternativeOutputFormats -}}|
@@ -141,6 +145,7 @@ List HTML|{{.Title }}|
{{ .Site.Language.Lang }}: {{ T "elbow" -}}
Partial Hugo 1: {{ partial "GoHugo.html" . }}
Partial Hugo 2: {{ partial "GoHugo" . -}}
+Content: {{ .Content }}
{{ end }}
`,
)
@@ -180,6 +185,7 @@ Partial Hugo 2: {{ partial "GoHugo" . -}}
"Output/Rel: JSON/alternate|",
"Output/Rel: HTML/canonical|",
"en: Elbow",
+ "ShortJSON",
)
th.assertFileContent("public/index.html",
@@ -187,6 +193,7 @@ Partial Hugo 2: {{ partial "GoHugo" . -}}
// parsed with html/template.
`List HTML|JSON Home|<atom:link href=http://example.com/blog/ rel="self" type="text/html&#43;html" />`,
"en: Elbow",
+ "ShortHTML",
)
th.assertFileContent("public/nn/index.html",
"List HTML|JSON Nynorsk Heim|",
@@ -196,10 +203,12 @@ Partial Hugo 2: {{ partial "GoHugo" . -}}
"Output/Rel: JSON/canonical|",
// JSON is plain text, so no need to safeHTML this and that
`<atom:link href=http://example.com/blog/index.json rel="self" type="application/json+json" />`,
+ "ShortJSON",
)
th.assertFileContent("public/nn/index.json",
"List JSON|JSON Nynorsk Heim|",
"nn: Olboge",
+ "ShortJSON",
)
}
diff --git a/hugolib/site_render.go b/hugolib/site_render.go
index ac7b25da7..5bb240161 100644
--- a/hugolib/site_render.go
+++ b/hugolib/site_render.go
@@ -77,8 +77,6 @@ func pageRenderer(s *Site, pages <-chan *Page, results chan<- error, wg *sync.Wa
if i == 0 {
pageOutput, err = newPageOutput(page, false, outFormat)
page.mainPageOutput = pageOutput
- } else {
- pageOutput, err = page.mainPageOutput.copyWithFormat(outFormat)
}
if outFormat != page.s.rc.Format {
@@ -86,6 +84,10 @@ func pageRenderer(s *Site, pages <-chan *Page, results chan<- error, wg *sync.Wa
continue
}
+ if pageOutput == nil {
+ pageOutput, err = page.mainPageOutput.copyWithFormat(outFormat)
+ }
+
if err != nil {
s.Log.ERROR.Printf("Failed to create output page for type %q for page %q: %s", outFormat.Name, page, err)
continue
diff --git a/tpl/template.go b/tpl/template.go
index 9fbf6b7b8..aa46a8ac2 100644
--- a/tpl/template.go
+++ b/tpl/template.go
@@ -58,6 +58,11 @@ type TemplateExecutor interface {
Tree() string
}
+// TemplateDebugger prints some debug info to stdoud.
+type TemplateDebugger interface {
+ Debug()
+}
+
// TemplateAdapter implements the TemplateExecutor interface.
type TemplateAdapter struct {
Template
diff --git a/tpl/tplimpl/template.go b/tpl/tplimpl/template.go
index 77826e0b0..ae1ae6820 100644
--- a/tpl/tplimpl/template.go
+++ b/tpl/tplimpl/template.go
@@ -14,7 +14,9 @@
package tplimpl
import (
+ "fmt"
"html/template"
+ "path"
"strings"
texttemplate "text/template"
@@ -39,6 +41,7 @@ const (
var (
_ tpl.TemplateHandler = (*templateHandler)(nil)
+ _ tpl.TemplateDebugger = (*templateHandler)(nil)
_ tpl.TemplateFuncsGetter = (*templateHandler)(nil)
_ tpl.TemplateTestMocker = (*templateHandler)(nil)
_ tpl.TemplateFinder = (*htmlTemplates)(nil)
@@ -88,6 +91,11 @@ func (t *templateHandler) addError(name string, err error) {
t.errors = append(t.errors, &templateErr{name, err})
}
+func (t *templateHandler) Debug() {
+ fmt.Println("HTML templates:\n", t.html.t.DefinedTemplates())
+ fmt.Println("\n\nText templates:\n", t.text.t.DefinedTemplates())
+}
+
// PrintErrors prints the accumulated errors as ERROR to the log.
func (t *templateHandler) PrintErrors() {
for _, e := range t.errors {
@@ -293,6 +301,13 @@ func (t *htmlTemplates) addTemplateIn(tt *template.Template, name, tpl string) e
return err
}
+ if strings.Contains(name, "shortcodes") {
+ // We need to keep track of one ot the output format's shortcode template
+ // without knowing the rendering context.
+ withoutExt := strings.TrimSuffix(name, path.Ext(name))
+ tt.AddParseTree(withoutExt, templ.Tree)
+ }
+
return nil
}
@@ -315,6 +330,13 @@ func (t *textTemplates) addTemplateIn(tt *texttemplate.Template, name, tpl strin
return err
}
+ if strings.Contains(name, "shortcodes") {
+ // We need to keep track of one ot the output format's shortcode template
+ // without knowing the rendering context.
+ withoutExt := strings.TrimSuffix(name, path.Ext(name))
+ tt.AddParseTree(withoutExt, templ.Tree)
+ }
+
return nil
}