Skip to content

Commit

Permalink
Add inline shortcode support
Browse files Browse the repository at this point in the history
  • Loading branch information
bep committed Nov 27, 2018
1 parent 94ab125 commit aa8014b
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 49 deletions.
94 changes: 72 additions & 22 deletions hugolib/shortcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"errors"
"fmt"
"html/template"
"path"

"reflect"

Expand Down Expand Up @@ -163,13 +164,15 @@ func (scp *ShortcodeWithPage) page() *Page {
const shortcodePlaceholderPrefix = "HUGOSHORTCODE"

type shortcode struct {
name string
inner []interface{} // string or nested shortcode
params interface{} // map or array
ordinal int
err error
doMarkup bool
pos int // the position in bytes in the source file
name string
isInline bool // inline shortcode. Any inner will be a Go template.
isClosing bool // whether a closing tag was provided
inner []interface{} // string or nested shortcode
params interface{} // map or array
ordinal int
err error
doMarkup bool
pos int // the position in bytes in the source file
}

func (sc shortcode) String() string {
Expand Down Expand Up @@ -245,6 +248,8 @@ type shortcodeHandler struct {

placeholderID int
placeholderFunc func() string

enableInlineShortcodes bool
}

func (s *shortcodeHandler) nextPlaceholderID() int {
Expand All @@ -259,11 +264,12 @@ func (s *shortcodeHandler) createShortcodePlaceholder() string {
func newShortcodeHandler(p *Page) *shortcodeHandler {

s := &shortcodeHandler{
p: p.withoutContent(),
contentShortcodes: newOrderedMap(),
shortcodes: newOrderedMap(),
nameSet: make(map[string]bool),
renderedShortcodes: make(map[string]string),
p: p.withoutContent(),
enableInlineShortcodes: p.s.enableInlineShortcodes,
contentShortcodes: newOrderedMap(),
shortcodes: newOrderedMap(),
nameSet: make(map[string]bool),
renderedShortcodes: make(map[string]string),
}

placeholderFunc := p.s.shortcodePlaceholderFunc
Expand Down Expand Up @@ -313,11 +319,26 @@ const innerNewlineRegexp = "\n"
const innerCleanupRegexp = `\A<p>(.*)</p>\n\z`
const innerCleanupExpand = "$1"

func prepareShortcodeForPage(placeholder string, sc *shortcode, parent *ShortcodeWithPage, p *PageWithoutContent) map[scKey]func() (string, error) {

func (s *shortcodeHandler) prepareShortcodeForPage(placeholder string, sc *shortcode, parent *ShortcodeWithPage, p *PageWithoutContent) map[scKey]func() (string, error) {
m := make(map[scKey]func() (string, error))
lang := p.Lang()

if sc.isInline {
key := newScKeyFromLangAndOutputFormat(lang, p.outputFormats[0], placeholder)
if !s.enableInlineShortcodes {
m[key] = func() (string, error) {
return "", nil
}
} else {
m[key] = func() (string, error) {
return renderShortcode(key, sc, nil, p)
}
}

return m

}

for _, f := range p.outputFormats {
// The most specific template will win.
key := newScKeyFromLangAndOutputFormat(lang, f, placeholder)
Expand All @@ -335,7 +356,31 @@ func renderShortcode(
parent *ShortcodeWithPage,
p *PageWithoutContent) (string, error) {

tmpl := getShortcodeTemplateForTemplateKey(tmplKey, sc.name, p.s.Tmpl)
var tmpl tpl.Template

if sc.isInline {
templName := path.Join("_inline_shortcode", p.Path(), sc.name)
if sc.isClosing {
templStr := sc.inner[0].(string)

var err error
tmpl, err = p.s.TextTmpl.Parse(templName, templStr)
if err != nil {
return "", err
}

} else {
// Re-use of shortcode defined earlier in the same page.
var found bool
tmpl, found = p.s.TextTmpl.Lookup(templName)
if !found {
return "", _errors.Errorf("no earlier definition of shortcode %q found", sc.name)
}
}
} else {
tmpl = getShortcodeTemplateForTemplateKey(tmplKey, sc.name, p.s.Tmpl)
}

if tmpl == nil {
p.s.Log.ERROR.Printf("Unable to locate template for shortcode %q in page %q", sc.name, p.Path())
return "", nil
Expand Down Expand Up @@ -417,7 +462,7 @@ func renderShortcode(
// 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.withoutContent())
s.contentShortcodes = s.createShortcodeRenderers(s.p.withoutContent())
})

if !s.p.shouldRenderTo(s.p.s.rc.Format) {
Expand Down Expand Up @@ -505,13 +550,13 @@ func (s *shortcodeHandler) executeShortcodesForDelta(p *PageWithoutContent) erro

}

func createShortcodeRenderers(shortcodes *orderedMap, p *PageWithoutContent) *orderedMap {
func (s *shortcodeHandler) createShortcodeRenderers(p *PageWithoutContent) *orderedMap {

shortcodeRenderers := newOrderedMap()

for _, k := range shortcodes.Keys() {
v := shortcodes.getShortcode(k)
prepared := prepareShortcodeForPage(k.(string), v, nil, p)
for _, k := range s.shortcodes.Keys() {
v := s.shortcodes.getShortcode(k)
prepared := s.prepareShortcodeForPage(k.(string), v, nil, p)
for kk, vv := range prepared {
shortcodeRenderers.Add(kk, vv)
}
Expand Down Expand Up @@ -570,13 +615,13 @@ Loop:
case currItem.IsRightShortcodeDelim():
// we trust the template on this:
// if there's no inner, we're done
if !isInner {
if !sc.isInline && !isInner {
return sc, nil
}

case currItem.IsShortcodeClose():
next := pt.Peek()
if !isInner {
if !sc.isInline && !isInner {
if next.IsError() {
// return that error, more specific
continue
Expand All @@ -588,6 +633,7 @@ Loop:
// self-closing
pt.Consume(1)
} else {
sc.isClosing = true
pt.Consume(2)
}

Expand All @@ -609,6 +655,10 @@ Loop:
return sc, fail(_errors.Wrapf(err, "failed to handle template for shortcode %q", sc.name), currItem)
}

case currItem.IsInlineShortcodeName():
sc.name = currItem.ValStr()
sc.isInline = true

case currItem.IsShortcodeParam():
if !pt.IsValueNext() {
continue
Expand Down
50 changes: 50 additions & 0 deletions hugolib/shortcode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1062,3 +1062,53 @@ String: {{ . | safeHTML }}
)

}

func TestInlineShortcodes(t *testing.T) {
for _, enableInlineShortcodes := range []bool{true, false} {
t.Run(fmt.Sprintf("enableInlineShortcodes=%t", enableInlineShortcodes),
func(t *testing.T) {
conf := fmt.Sprintf(`
baseURL = "https://example.com"
enableInlineShortcodes = %t
`, enableInlineShortcodes)

b := newTestSitesBuilder(t)
b.WithConfigFile("toml", conf)
b.WithContent("page-md-shortcode.md", `---
title: "Hugo"
---
FIRST:{{< myshort.inline "first" >}}
Page: {{ .Page.Title }}
Seq: {{ seq 3 }}
Param: {{ .Get 0 }}
{{< /myshort.inline >}}:END:
SECOND:{{< myshort.inline "second" />}}:END
`)

b.WithTemplatesAdded("layouts/_default/single.html", `
CONTENT:{{ .Content }}
`)

b.CreateSites().Build(BuildCfg{})

if enableInlineShortcodes {
b.AssertFileContent("public/page-md-shortcode/index.html",
"Page: Hugo",
"Seq: [1 2 3]",
"Param: first",
"Param: second",
)
} else {
b.AssertFileContent("public/page-md-shortcode/index.html",
"FIRST::END",
"SECOND::END",
)
}

})

}
}
56 changes: 30 additions & 26 deletions hugolib/site.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ type Site struct {

disabledKinds map[string]bool

enableInlineShortcodes bool

// Output formats defined in site config per Page Kind, or some defaults
// if not set.
// Output formats defined in Page front matter will override these.
Expand Down Expand Up @@ -194,21 +196,22 @@ func (s *Site) isEnabled(kind string) bool {
// reset returns a new Site prepared for rebuild.
func (s *Site) reset() *Site {
return &Site{Deps: s.Deps,
layoutHandler: output.NewLayoutHandler(),
disabledKinds: s.disabledKinds,
titleFunc: s.titleFunc,
relatedDocsHandler: newSearchIndexHandler(s.relatedDocsHandler.cfg),
siteRefLinker: s.siteRefLinker,
outputFormats: s.outputFormats,
rc: s.rc,
outputFormatsConfig: s.outputFormatsConfig,
frontmatterHandler: s.frontmatterHandler,
mediaTypesConfig: s.mediaTypesConfig,
Language: s.Language,
owner: s.owner,
publisher: s.publisher,
siteConfig: s.siteConfig,
PageCollections: newPageCollections()}
layoutHandler: output.NewLayoutHandler(),
disabledKinds: s.disabledKinds,
titleFunc: s.titleFunc,
relatedDocsHandler: newSearchIndexHandler(s.relatedDocsHandler.cfg),
siteRefLinker: s.siteRefLinker,
outputFormats: s.outputFormats,
rc: s.rc,
outputFormatsConfig: s.outputFormatsConfig,
frontmatterHandler: s.frontmatterHandler,
mediaTypesConfig: s.mediaTypesConfig,
Language: s.Language,
owner: s.owner,
publisher: s.publisher,
siteConfig: s.siteConfig,
enableInlineShortcodes: s.enableInlineShortcodes,
PageCollections: newPageCollections()}

}

Expand Down Expand Up @@ -282,17 +285,18 @@ func newSite(cfg deps.DepsCfg) (*Site, error) {
}

s := &Site{
PageCollections: c,
layoutHandler: output.NewLayoutHandler(),
Language: cfg.Language,
disabledKinds: disabledKinds,
titleFunc: titleFunc,
relatedDocsHandler: newSearchIndexHandler(relatedContentConfig),
outputFormats: outputFormats,
rc: &siteRenderingContext{output.HTMLFormat},
outputFormatsConfig: siteOutputFormatsConfig,
mediaTypesConfig: siteMediaTypesConfig,
frontmatterHandler: frontMatterHandler,
PageCollections: c,
layoutHandler: output.NewLayoutHandler(),
Language: cfg.Language,
disabledKinds: disabledKinds,
titleFunc: titleFunc,
relatedDocsHandler: newSearchIndexHandler(relatedContentConfig),
outputFormats: outputFormats,
rc: &siteRenderingContext{output.HTMLFormat},
outputFormatsConfig: siteOutputFormatsConfig,
mediaTypesConfig: siteMediaTypesConfig,
frontmatterHandler: frontMatterHandler,
enableInlineShortcodes: cfg.Language.GetBool("enableInlineShortcodes"),
}

return s, nil
Expand Down
5 changes: 5 additions & 0 deletions parser/pageparser/item.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ func (i Item) IsShortcodeName() bool {
return i.Type == tScName
}

func (i Item) IsInlineShortcodeName() bool {
return i.Type == tScNameInline
}

func (i Item) IsLeftShortcodeDelim() bool {
return i.Type == tLeftDelimScWithMarkup || i.Type == tLeftDelimScNoMarkup
}
Expand Down Expand Up @@ -119,6 +123,7 @@ const (
tRightDelimScWithMarkup
tScClose
tScName
tScNameInline
tScParam
tScParamVal

Expand Down
Loading

0 comments on commit aa8014b

Please sign in to comment.