ref: af72db806f2c1c0bf1dfe5832275c41eeba89906
parent: e951d65771ca299aa899e91bfe00411a5ada8f19
author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
date: Sat May 6 16:15:28 EDT 2017
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
--- a/hugolib/handler_page.go
+++ b/hugolib/handler_page.go
@@ -80,7 +80,7 @@
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 @@
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
--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -492,13 +492,8 @@
}
}
-func (s *Site) preparePagesForRender(outFormatIdx int, cfg *BuildCfg) {+func (s *Site) preparePagesForRender(cfg *BuildCfg) {- if outFormatIdx > 0 {- // TODO(bep) for now
- return
- }
-
pageChan := make(chan *Page)
wg := &sync.WaitGroup{}numWorkers := getGoMaxProcs() * 4
@@ -508,8 +503,16 @@
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 @@
// 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 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())--- a/hugolib/hugo_sites_build.go
+++ b/hugolib/hugo_sites_build.go
@@ -213,7 +213,7 @@
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 {--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -1257,6 +1257,11 @@
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) 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
}
--- 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 @@
"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,10 +153,44 @@
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 @@
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 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 @@
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 @@
}
}
+ // TODO(bep) we may have plain text inner templates.
data.Inner = template.HTML(newInner)
} else {data.Inner = template.HTML(inner)
@@ -278,51 +338,91 @@
return renderShortcodeWithPage(tmpl, data)
}
-func (s *shortcodeHandler) extractAndRenderShortcodes(stringToParse string, p *Page) (string, error) {- content, err := s.extractShortcodes(stringToParse, p)
+// 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)
+ })
- if err != nil {- // try to render what we have whilst logging the error
- p.s.Log.ERROR.Println(err.Error())
+ 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))
- return content, err
+ for k, v := range contentShortcodes {+ if _, found := s.contentShortcodesDelta[k]; !found {+ delta[k] = v
+ }
+ }
+ 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 @@
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 @@
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))}
- return t.Lookup("_internal/shortcodes/" + name + ".html")+
+ 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 nil
}
func renderShortcodeWithPage(tmpl tpl.Template, data *ShortcodeWithPage) string {--- a/hugolib/shortcode_test.go
+++ b/hugolib/shortcode_test.go
@@ -22,6 +22,14 @@
"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 @@
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 @@
}
+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
@@ -679,5 +831,15 @@
}
}
+
+}
+
+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"))}
--- a/hugolib/site_output_test.go
+++ b/hugolib/site_output_test.go
@@ -99,6 +99,8 @@
outputs: %s
---
# Doc
+
+{{< myShort >}}`
mf := afero.NewMemMapFs()
@@ -118,6 +120,8 @@
"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 @@
{{ .Site.Language.Lang }}: {{ T "elbow" -}} Partial Hugo 1: {{ partial "GoHugo.html" . }} Partial Hugo 2: {{ partial "GoHugo" . -}}+Content: {{ .Content }} {{ end }}`,
)
@@ -180,6 +185,7 @@
"Output/Rel: JSON/alternate|",
"Output/Rel: HTML/canonical|",
"en: Elbow",
+ "ShortJSON",
)
th.assertFileContent("public/index.html",@@ -187,6 +193,7 @@
// parsed with html/template.
`List HTML|JSON Home|<atom:link href=http://example.com/blog/ rel="self" type="text/html+html" />`,
"en: Elbow",
+ "ShortHTML",
)
th.assertFileContent("public/nn/index.html","List HTML|JSON Nynorsk Heim|",
@@ -196,10 +203,12 @@
"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",
)
}
--- a/hugolib/site_render.go
+++ b/hugolib/site_render.go
@@ -77,13 +77,15 @@
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 {// Will be rendered ... later.
continue
+ }
+
+ if pageOutput == nil {+ pageOutput, err = page.mainPageOutput.copyWithFormat(outFormat)
}
if err != nil {--- a/tpl/template.go
+++ b/tpl/template.go
@@ -58,6 +58,11 @@
Tree() string
}
+// TemplateDebugger prints some debug info to stdoud.
+type TemplateDebugger interface {+ Debug()
+}
+
// TemplateAdapter implements the TemplateExecutor interface.
type TemplateAdapter struct {Template
--- 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 @@
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 @@
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 @@
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
}
@@ -313,6 +328,13 @@
if err := applyTemplateTransformersToTextTemplate(templ); err != nil {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
--
⑨