summaryrefslogtreecommitdiffstats
path: root/hugolib
diff options
context:
space:
mode:
Diffstat (limited to 'hugolib')
-rw-r--r--hugolib/content_render_hooks_test.go244
-rw-r--r--hugolib/filesystems/basefs.go49
-rw-r--r--hugolib/hugo_modules_test.go3
-rw-r--r--hugolib/hugo_sites.go39
-rw-r--r--hugolib/hugo_sites_build.go2
-rw-r--r--hugolib/hugo_sites_build_test.go16
-rw-r--r--hugolib/page.go184
-rw-r--r--hugolib/page__content.go3
-rw-r--r--hugolib/page__meta.go36
-rw-r--r--hugolib/page__new.go24
-rw-r--r--hugolib/page__output.go69
-rw-r--r--hugolib/page__per_output.go303
-rw-r--r--hugolib/page_test.go7
-rw-r--r--hugolib/page_unwrap_test.go1
-rw-r--r--hugolib/pagebundler_test.go1
-rw-r--r--hugolib/pagecollections.go10
-rw-r--r--hugolib/shortcode.go19
-rw-r--r--hugolib/shortcode_page.go19
-rw-r--r--hugolib/shortcode_test.go7
-rw-r--r--hugolib/site.go120
-rw-r--r--hugolib/site_benchmark_new_test.go30
-rw-r--r--hugolib/template_test.go87
-rw-r--r--hugolib/testhelpers_test.go9
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))
}