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

(.*)

\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) +// 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 @@ 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)) } - 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 { 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|`, "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 ``, + "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 }