diff options
author | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2019-11-27 13:42:36 +0100 |
---|---|---|
committer | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2019-12-18 11:44:40 +0100 |
commit | e625088ef5a970388ad50e464e87db56b358dac4 (patch) | |
tree | f7b26dec1f3695411558d05ca7d0995817a42250 /hugolib | |
parent | 67f3aa72cf9aaf3d6e447fa6bc12de704d46adf7 (diff) |
Add render template hooks for links and images
This commit also
* revises the change detection for templates used by content files in server mode.
* Adds a Page.RenderString method
Fixes #6545
Fixes #4663
Closes #6043
Diffstat (limited to 'hugolib')
-rw-r--r-- | hugolib/content_render_hooks_test.go | 244 | ||||
-rw-r--r-- | hugolib/filesystems/basefs.go | 49 | ||||
-rw-r--r-- | hugolib/hugo_modules_test.go | 3 | ||||
-rw-r--r-- | hugolib/hugo_sites.go | 39 | ||||
-rw-r--r-- | hugolib/hugo_sites_build.go | 2 | ||||
-rw-r--r-- | hugolib/hugo_sites_build_test.go | 16 | ||||
-rw-r--r-- | hugolib/page.go | 184 | ||||
-rw-r--r-- | hugolib/page__content.go | 3 | ||||
-rw-r--r-- | hugolib/page__meta.go | 36 | ||||
-rw-r--r-- | hugolib/page__new.go | 24 | ||||
-rw-r--r-- | hugolib/page__output.go | 69 | ||||
-rw-r--r-- | hugolib/page__per_output.go | 303 | ||||
-rw-r--r-- | hugolib/page_test.go | 7 | ||||
-rw-r--r-- | hugolib/page_unwrap_test.go | 1 | ||||
-rw-r--r-- | hugolib/pagebundler_test.go | 1 | ||||
-rw-r--r-- | hugolib/pagecollections.go | 10 | ||||
-rw-r--r-- | hugolib/shortcode.go | 19 | ||||
-rw-r--r-- | hugolib/shortcode_page.go | 19 | ||||
-rw-r--r-- | hugolib/shortcode_test.go | 7 | ||||
-rw-r--r-- | hugolib/site.go | 120 | ||||
-rw-r--r-- | hugolib/site_benchmark_new_test.go | 30 | ||||
-rw-r--r-- | hugolib/template_test.go | 87 | ||||
-rw-r--r-- | hugolib/testhelpers_test.go | 9 |
23 files changed, 987 insertions, 295 deletions
diff --git a/hugolib/content_render_hooks_test.go b/hugolib/content_render_hooks_test.go new file mode 100644 index 000000000..aa697220d --- /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", ` +<div class="foo"> +{{ .Inner | markdownify }} +</div> +`) + 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", ` +<p>Cool Page|https://www.google.com|Title: Google's Homepage|Text: First Link|END</p> +Text: Second +SHORT3| +<p>IMAGE: Cool Page||/images/Dragster.jpg|Title: image title|Text: Drag Racing|END</p> +`) + + 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", `<p>IMAGE: Cool Page With Image||/images/Dragster.jpg|Title: image title|Text: Drag Racing|END</p>`) + // The regular markdownify func currently gets regular links. + b.AssertFileContent("public/blog/p5/index.html", "Inner Link: <a href=\"https://www.google.com\" title=\"Google's Homepage\">Inner Link</a>\n</div>") + + 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: <p>Inner Link: With RenderString|https://www.gohugo.io|Title: Hugo's Homepage|Text: Inner Link|END</p>", + ) + + 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", `<p>EDITED: https://www.google.com|</p>`, "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|</p>`) + b.AssertFileContent("public/blog/p4/index.html", `IMAGE EDITED: /images/Dragster.jpg|`) + b.AssertFileContent("public/blog/p6/index.html", "<p>Inner Link: EDITED: https://www.gohugo.io|</p>") + +} + +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: <p>P1. html-link: https://www.gohugo.io|</p>") + b.AssertFileContent("public/index.xml", ` +P2: <p>P1. xml-link: https://www.bep.is|</p> +P3: <p>P3. xml-link: https://www.example.org|</p> +`) + +} + +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:<strong>Bold Markdown</strong>:REND +RSTART:<p><strong>Bold Block Markdown</strong></p> +RSTART:<em>italic org mode</em>:REND +`) + +} diff --git a/hugolib/filesystems/basefs.go b/hugolib/filesystems/basefs.go index de6baa130..cdc39ce61 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 40185e051..900443275 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 c71dcaa59..526f39fca 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 a70a19e7c..d749ff581 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/hugo_sites_build_test.go b/hugolib/hugo_sites_build_test.go index feee85910..d62d6d519 100644 --- a/hugolib/hugo_sites_build_test.go +++ b/hugolib/hugo_sites_build_test.go @@ -1459,3 +1459,19 @@ other = %q return &multiSiteTestBuilder{sitesBuilder: b, configFormat: configFormat, config: config, configData: configData} } + +func TestRebuildOnAssetChange(t *testing.T) { + b := newTestSitesBuilder(t).Running() + b.WithTemplatesAdded("index.html", ` +{{ (resources.Get "data.json").Content }} +`) + b.WithSourceFile("assets/data.json", "orig data") + + b.Build(BuildCfg{}) + b.AssertFileContent("public/index.html", `orig data`) + + b.EditFiles("assets/data.json", "changed data") + + b.Build(BuildCfg{}) + b.AssertFileContent("public/index.html", `changed data`) +} diff --git a/hugolib/page.go b/hugolib/page.go index 56202f5e0..fb3b597be 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 { |