summaryrefslogtreecommitdiffstats
path: root/hugolib
diff options
context:
space:
mode:
Diffstat (limited to 'hugolib')
-rw-r--r--hugolib/config.go2
-rw-r--r--hugolib/shortcode.go116
-rw-r--r--hugolib/shortcode_test.go50
-rw-r--r--hugolib/site.go56
4 files changed, 172 insertions, 52 deletions
diff --git a/hugolib/config.go b/hugolib/config.go
index 388069047..77ebb42ae 100644
--- a/hugolib/config.go
+++ b/hugolib/config.go
@@ -482,6 +482,6 @@ func loadDefaultSettingsFor(v *viper.Viper) error {
v.SetDefault("debug", false)
v.SetDefault("disableFastRender", false)
v.SetDefault("timeout", 10000) // 10 seconds
-
+ v.SetDefault("enableInlineShortcodes", false)
return nil
}
diff --git a/hugolib/shortcode.go b/hugolib/shortcode.go
index 1860a5e90..8be312f83 100644
--- a/hugolib/shortcode.go
+++ b/hugolib/shortcode.go
@@ -18,6 +18,9 @@ import (
"errors"
"fmt"
"html/template"
+ "path"
+
+ "github.com/gohugoio/hugo/common/herrors"
"reflect"
@@ -163,13 +166,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 {
@@ -245,6 +250,8 @@ type shortcodeHandler struct {
placeholderID int
placeholderFunc func() string
+
+ enableInlineShortcodes bool
}
func (s *shortcodeHandler) nextPlaceholderID() int {
@@ -259,11 +266,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
@@ -313,11 +321,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)
@@ -335,7 +358,34 @@ 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 {
+ fe := herrors.ToFileError("html", err)
+ l1, l2 := p.posFromPage(sc.pos).LineNumber, fe.Position().LineNumber
+ fe = herrors.ToFileErrorWithLineNumber(fe, l1+l2-1)
+ return "", p.errWithFileContext(fe)
+ }
+
+ } 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
@@ -406,7 +456,16 @@ func renderShortcode(
}
- return renderShortcodeWithPage(tmpl, data)
+ s, err := renderShortcodeWithPage(tmpl, data)
+
+ if err != nil && sc.isInline {
+ fe := herrors.ToFileError("html", err)
+ l1, l2 := p.posFromPage(sc.pos).LineNumber, fe.Position().LineNumber
+ fe = herrors.ToFileErrorWithLineNumber(fe, l1+l2-1)
+ return "", fe
+ }
+
+ return s, err
}
// The delta represents new output format-versions of the shortcodes,
@@ -417,7 +476,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) {
@@ -505,13 +564,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)
}
@@ -541,7 +600,9 @@ Loop:
currItem := pt.Next()
switch {
case currItem.IsLeftShortcodeDelim():
- sc.pos = currItem.Pos
+ if sc.pos == 0 {
+ sc.pos = currItem.Pos
+ }
next := pt.Peek()
if next.IsShortcodeClose() {
continue
@@ -570,13 +631,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
@@ -588,6 +649,7 @@ Loop:
// self-closing
pt.Consume(1)
} else {
+ sc.isClosing = true
pt.Consume(2)
}
@@ -609,6 +671,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
@@ -751,7 +817,7 @@ func renderShortcodeWithPage(tmpl tpl.Template, data *ShortcodeWithPage) (string
err := tmpl.Execute(buffer, data)
isInnerShortcodeCache.RUnlock()
if err != nil {
- return "", data.Page.errorf(err, "failed to process shortcode")
+ return "", _errors.Wrap(err, "failed to process shortcode")
}
return buffer.String(), nil
}
diff --git a/hugolib/shortcode_test.go b/hugolib/shortcode_test.go
index 30fdbead3..3a1656e26 100644
--- a/hugolib/shortcode_test.go
+++ b/hugolib/shortcode_test.go
@@ -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",
+ )
+ }
+
+ })
+
+ }
+}
diff --git a/hugolib/site.go b/hugolib/site.go
index fb32853e3..25eb34f05 100644
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -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.
@@ -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()}
}
@@ -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