Skip to content

Commit

Permalink
Make .RenderString render shortcodes
Browse files Browse the repository at this point in the history
Fixes #6703
  • Loading branch information
bep committed May 30, 2022
1 parent d2cfaed commit 9e904d7
Show file tree
Hide file tree
Showing 10 changed files with 315 additions and 111 deletions.
5 changes: 3 additions & 2 deletions common/text/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,12 @@ func Puts(s string) string {

// VisitLinesAfter calls the given function for each line, including newlines, in the given string.
func VisitLinesAfter(s string, fn func(line string)) {
high := strings.Index(s, "\n")
high := strings.IndexRune(s, '\n')
for high != -1 {
fn(s[:high+1])
s = s[high+1:]
high = strings.Index(s, "\n")

high = strings.IndexRune(s, '\n')
}

if s != "" {
Expand Down
15 changes: 15 additions & 0 deletions common/text/transform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,18 @@ line 3`
c.Assert(collected, qt.DeepEquals, []string{"line 1\n", "line 2\n", "\n", "line 3"})

}

func BenchmarkVisitLinesAfter(b *testing.B) {
const lines = `line 1
line 2
line 3`

for i := 0; i < b.N; i++ {
VisitLinesAfter(lines, func(s string) {

})

}

}
2 changes: 1 addition & 1 deletion hugolib/content_map_page.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ func (m *pageMap) newPageFromContentNode(n *contentNode, parentBucket *pagesMapB
},
}

ps.shortcodeState = newShortcodeHandler(ps, ps.s, nil)
ps.shortcodeState = newShortcodeHandler(ps, ps.s)

if err := ps.mapContent(parentBucket, metaProvider); err != nil {
return nil, ps.wrapError(err)
Expand Down
70 changes: 0 additions & 70 deletions hugolib/content_render_hooks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
"testing"

qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/common/loggers"
)

func TestRenderHookEditNestedPartial(t *testing.T) {
Expand Down Expand Up @@ -428,72 +427,3 @@ Image:
<p>html-image: image.jpg|Text: Hello<br> Goodbye|Plain: Hello GoodbyeEND</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
RSTART:{{ "## Header2" | $p.RenderString }}:REND
`, "_default/_markup/render-heading.html", "Hook Heading: {{ .Level }}")

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
RSTART:Hook Heading: 2:REND
`)
}

// https://github.com/gohugoio/hugo/issues/6882
func TestRenderStringOnListPage(t *testing.T) {
renderStringTempl := `
{{ .RenderString "**Hello**" }}
`
b := newTestSitesBuilder(t)
b.WithContent("mysection/p1.md", `FOO`)
b.WithTemplates(
"index.html", renderStringTempl,
"_default/list.html", renderStringTempl,
"_default/single.html", renderStringTempl,
)

b.Build(BuildCfg{})

for _, filename := range []string{
"index.html",
"mysection/index.html",
"categories/index.html",
"tags/index.html",
"mysection/p1/index.html",
} {
b.AssertFileContent("public/"+filename, `<strong>Hello</strong>`)
}
}

// Issue 9433
func TestRenderStringOnPageNotBackedByAFile(t *testing.T) {
t.Parallel()
logger := loggers.NewWarningLogger()
b := newTestSitesBuilder(t).WithLogger(logger).WithConfigFile("toml", `
disableKinds = ["page", "section", "taxonomy", "term"]
`)
b.WithTemplates("index.html", `{{ .RenderString "**Hello**" }}`).WithContent("p1.md", "")
b.BuildE(BuildCfg{})
b.Assert(int(logger.LogCounters().WarnCounter.Count()), qt.Equals, 0)
}
43 changes: 30 additions & 13 deletions hugolib/page.go
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ func (p *pageState) HasShortcode(name string) bool {
return false
}

return p.shortcodeState.nameSet[name]
return p.shortcodeState.hasName(name)
}

func (p *pageState) Site() page.Site {
Expand Down Expand Up @@ -610,13 +610,30 @@ func (p *pageState) getContentConverter() converter.Converter {
}

func (p *pageState) mapContent(bucket *pagesMapBucket, meta *pageMeta) error {
s := p.shortcodeState

rn := &pageContentMap{
p.cmap = &pageContentMap{
items: make([]any, 0, 20),
}

iter := p.source.parsed.Iterator()
return p.mapContentForResult(
p.source.parsed,
p.shortcodeState,
p.cmap,
meta.markup,
func(m map[string]interface{}) error {
return meta.setMetadata(bucket, p, m)
},
)
}

func (p *pageState) mapContentForResult(
result pageparser.Result,
s *shortcodeHandler,
rn *pageContentMap,
markup string,
withFrontMatter func(map[string]any) error,
) error {

iter := result.Iterator()

fail := func(err error, i pageparser.Item) error {
if fe, ok := err.(herrors.FileError); ok {
Expand Down Expand Up @@ -660,8 +677,10 @@ Loop:
}
}

if err := meta.setMetadata(bucket, p, m); err != nil {
return err
if withFrontMatter != nil {
if err := withFrontMatter(m); err != nil {
return err
}
}

frontMatterSet = true
Expand Down Expand Up @@ -697,7 +716,7 @@ Loop:
p.source.posBodyStart = posBody
p.source.hasSummaryDivider = true

if meta.markup != "html" {
if markup != "html" {
// The content will be rendered by Goldmark or similar,
// and we need to track the summary.
rn.AddReplacement(internalSummaryDividerPre, it)
Expand All @@ -720,7 +739,7 @@ Loop:
}

if currShortcode.name != "" {
s.nameSet[currShortcode.name] = true
s.addName(currShortcode.name)
}

if currShortcode.params == nil {
Expand Down Expand Up @@ -752,16 +771,14 @@ Loop:
}
}

if !frontMatterSet {
if !frontMatterSet && withFrontMatter != nil {
// Page content without front matter. Assign default front matter from
// cascades etc.
if err := meta.setMetadata(bucket, p, nil); err != nil {
if err := withFrontMatter(nil); err != nil {
return err
}
}

p.cmap = rn

return nil
}

Expand Down
6 changes: 3 additions & 3 deletions hugolib/page__content.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ type pageContent struct {
}

// returns the content to be processed by Goldmark or similar.
func (p pageContent) contentToRender(renderedShortcodes map[string]string) []byte {
source := p.source.parsed.Input()
func (p pageContent) contentToRender(parsed pageparser.Result, pm *pageContentMap, renderedShortcodes map[string]string) []byte {
source := parsed.Input()

c := make([]byte, 0, len(source)+(len(source)/10))

for _, it := range p.cmap.items {
for _, it := range pm.items {
switch v := it.(type) {
case pageparser.Item:
c = append(c, source[v.Pos:v.Pos+len(v.Val)]...)
Expand Down
86 changes: 74 additions & 12 deletions hugolib/page__per_output.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ import (

"errors"

"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/common/text"
"github.com/gohugoio/hugo/common/types/hstring"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/parser/pageparser"
"github.com/mitchellh/mapstructure"
"github.com/spf13/cast"

Expand Down Expand Up @@ -117,7 +119,7 @@ func newPageContentOutput(p *pageState, po *pageOutput) (*pageContentOutput, err
p.pageOutputTemplateVariationsState.Store(2)
}

cp.workContent = p.contentToRender(cp.contentPlaceholders)
cp.workContent = p.contentToRender(p.source.parsed, p.cmap, cp.contentPlaceholders)

isHTML := cp.p.m.markup == "html"

Expand Down Expand Up @@ -332,11 +334,12 @@ func (p *pageContentOutput) WordCount() int {
}

func (p *pageContentOutput) RenderString(args ...any) (template.HTML, error) {
defer herrors.Recover()
if len(args) < 1 || len(args) > 2 {
return "", errors.New("want 1 or 2 arguments")
}

var s string
var contentToRender string
opts := defaultRenderStringOpts
sidx := 1

Expand All @@ -353,16 +356,16 @@ func (p *pageContentOutput) RenderString(args ...any) (template.HTML, error) {
}
}

contentToRender := args[sidx]
contentToRenderv := args[sidx]

if _, ok := contentToRender.(hstring.RenderedString); ok {
if _, ok := contentToRenderv.(hstring.RenderedString); ok {
// This content is already rendered, this is potentially
// a infinite recursion.
return "", errors.New("text is already rendered, repeating it may cause infinite recursion")
}

var err error
s, err = cast.ToStringE(contentToRender)
contentToRender, err = cast.ToStringE(contentToRenderv)
if err != nil {
return "", err
}
Expand All @@ -381,20 +384,79 @@ func (p *pageContentOutput) RenderString(args ...any) (template.HTML, error) {
}
}

c, err := p.renderContentWithConverter(conv, []byte(s), false)
if err != nil {
return "", p.p.wrapError(err)
}
var rendered []byte

if strings.Contains(contentToRender, "{{") {
// Probably a shortcode.
parsed, err := pageparser.ParseMain(strings.NewReader(contentToRender), pageparser.Config{})
if err != nil {
return "", err
}
pm := &pageContentMap{
items: make([]any, 0, 20),
}
s := newShortcodeHandler(p.p, p.p.s)

if err := p.p.mapContentForResult(
parsed,
s,
pm,
opts.Markup,
nil,
); err != nil {
return "", err
}

placeholders, hasShortcodeVariants, err := s.renderShortcodesForPage(p.p, p.f)
if err != nil {
return "", err
}

if hasShortcodeVariants {
p.p.pageOutputTemplateVariationsState.Store(2)
}

b, err := p.renderContentWithConverter(conv, p.p.contentToRender(parsed, pm, placeholders), false)
if err != nil {
return "", p.p.wrapError(err)
}
rendered = b.Bytes()

b := c.Bytes()
if p.placeholdersEnabled {
// ToC was accessed via .Page.TableOfContents in the shortcode,
// at a time when the ToC wasn't ready.
if _, err := p.p.Content(); err != nil {
return "", err
}
placeholders[tocShortcodePlaceholder] = string(p.tableOfContents)
}

if pm.hasNonMarkdownShortcode || p.placeholdersEnabled {
rendered, err = replaceShortcodeTokens(rendered, placeholders)
if err != nil {
return "", err
}
}

// We need a consolidated view in $page.HasShortcode
p.p.shortcodeState.transferNames(s)

} else {
c, err := p.renderContentWithConverter(conv, []byte(contentToRender), false)
if err != nil {
return "", p.p.wrapError(err)
}

rendered = c.Bytes()
}

if opts.Display == "inline" {
// We may have to rethink this in the future when we get other
// renderers.
b = p.p.s.ContentSpec.TrimShortHTML(b)
rendered = p.p.s.ContentSpec.TrimShortHTML(rendered)
}

return template.HTML(string(b)), nil
return template.HTML(string(rendered)), nil
}

func (p *pageContentOutput) RenderWithTemplateInfo(info tpl.Info, layout ...string) (template.HTML, error) {
Expand Down
Loading

0 comments on commit 9e904d7

Please sign in to comment.