mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-19 21:21:39 +02:00
Make Page an interface
The main motivation of this commit is to add a `page.Page` interface to replace the very file-oriented `hugolib.Page` struct. This is all a preparation step for issue #5074, "pages from other data sources". But this also fixes a set of annoying limitations, especially related to custom output formats, and shortcodes. Most notable changes: * The inner content of shortcodes using the `{{%` as the outer-most delimiter will now be sent to the content renderer, e.g. Blackfriday. This means that any markdown will partake in the global ToC and footnote context etc. * The Custom Output formats are now "fully virtualized". This removes many of the current limitations. * The taxonomy list type now has a reference to the `Page` object. This improves the taxonomy template `.Title` situation and make common template constructs much simpler. See #5074 Fixes #5763 Fixes #5758 Fixes #5090 Fixes #5204 Fixes #4695 Fixes #5607 Fixes #5707 Fixes #5719 Fixes #3113 Fixes #5706 Fixes #5767 Fixes #5723 Fixes #5769 Fixes #5770 Fixes #5771 Fixes #5759 Fixes #5776 Fixes #5777 Fixes #5778
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// Copyright 2017 The Hugo Authors. All rights reserved.
|
||||
// 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.
|
||||
@@ -15,12 +15,14 @@ package hugolib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"html/template"
|
||||
"path"
|
||||
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"reflect"
|
||||
|
||||
@@ -28,6 +30,7 @@ import (
|
||||
"sort"
|
||||
|
||||
"github.com/gohugoio/hugo/parser/pageparser"
|
||||
"github.com/gohugoio/hugo/resources/page"
|
||||
|
||||
_errors "github.com/pkg/errors"
|
||||
|
||||
@@ -39,8 +42,6 @@ import (
|
||||
"github.com/gohugoio/hugo/common/urls"
|
||||
"github.com/gohugoio/hugo/output"
|
||||
|
||||
"github.com/gohugoio/hugo/media"
|
||||
|
||||
bp "github.com/gohugoio/hugo/bufferpool"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/tpl"
|
||||
@@ -48,7 +49,7 @@ import (
|
||||
|
||||
var (
|
||||
_ urls.RefLinker = (*ShortcodeWithPage)(nil)
|
||||
_ pageContainer = (*ShortcodeWithPage)(nil)
|
||||
_ pageWrapper = (*ShortcodeWithPage)(nil)
|
||||
_ text.Positioner = (*ShortcodeWithPage)(nil)
|
||||
)
|
||||
|
||||
@@ -56,7 +57,7 @@ var (
|
||||
type ShortcodeWithPage struct {
|
||||
Params interface{}
|
||||
Inner template.HTML
|
||||
Page *PageWithoutContent
|
||||
Page page.Page
|
||||
Parent *ShortcodeWithPage
|
||||
Name string
|
||||
IsNamedParams bool
|
||||
@@ -77,26 +78,28 @@ type ShortcodeWithPage struct {
|
||||
// may be expensive to calculate, so only use this in error situations.
|
||||
func (scp *ShortcodeWithPage) Position() text.Position {
|
||||
scp.posInit.Do(func() {
|
||||
scp.pos = scp.Page.posFromPage(scp.posOffset)
|
||||
if p, ok := mustUnwrapPage(scp.Page).(pageContext); ok {
|
||||
scp.pos = p.posOffset(scp.posOffset)
|
||||
}
|
||||
})
|
||||
return scp.pos
|
||||
}
|
||||
|
||||
// Site returns information about the current site.
|
||||
func (scp *ShortcodeWithPage) Site() *SiteInfo {
|
||||
return scp.Page.Site
|
||||
func (scp *ShortcodeWithPage) Site() page.Site {
|
||||
return scp.Page.Site()
|
||||
}
|
||||
|
||||
// Ref is a shortcut to the Ref method on Page. It passes itself as a context
|
||||
// to get better error messages.
|
||||
func (scp *ShortcodeWithPage) Ref(args map[string]interface{}) (string, error) {
|
||||
return scp.Page.ref(args, scp)
|
||||
return scp.Page.RefFrom(args, scp)
|
||||
}
|
||||
|
||||
// RelRef is a shortcut to the RelRef method on Page. It passes itself as a context
|
||||
// to get better error messages.
|
||||
func (scp *ShortcodeWithPage) RelRef(args map[string]interface{}) (string, error) {
|
||||
return scp.Page.relRef(args, scp)
|
||||
return scp.Page.RelRefFrom(args, scp)
|
||||
}
|
||||
|
||||
// Scratch returns a scratch-pad scoped for this shortcode. This can be used
|
||||
@@ -159,12 +162,16 @@ func (scp *ShortcodeWithPage) Get(key interface{}) interface{} {
|
||||
|
||||
}
|
||||
|
||||
func (scp *ShortcodeWithPage) page() *Page {
|
||||
return scp.Page.Page
|
||||
func (scp *ShortcodeWithPage) page() page.Page {
|
||||
return scp.Page
|
||||
}
|
||||
|
||||
// Note - this value must not contain any markup syntax
|
||||
const shortcodePlaceholderPrefix = "HUGOSHORTCODE"
|
||||
const shortcodePlaceholderPrefix = "HAHAHUGOSHORTCODE"
|
||||
|
||||
func createShortcodePlaceholder(id string, ordinal int) string {
|
||||
return shortcodePlaceholderPrefix + "-" + id + strconv.Itoa(ordinal) + "-HBHB"
|
||||
}
|
||||
|
||||
type shortcode struct {
|
||||
name string
|
||||
@@ -174,8 +181,24 @@ type shortcode struct {
|
||||
params interface{} // map or array
|
||||
ordinal int
|
||||
err error
|
||||
doMarkup bool
|
||||
pos int // the position in bytes in the source file
|
||||
|
||||
info tpl.Info
|
||||
|
||||
// If set, the rendered shortcode is sent as part of the surrounding content
|
||||
// to Blackfriday and similar.
|
||||
// Before Hug0 0.55 we didn't send any shortcode output to the markup
|
||||
// renderer, and this flag told Hugo to process the {{ .Inner }} content
|
||||
// separately.
|
||||
// The old behaviour can be had by starting your shortcode template with:
|
||||
// {{ $_hugo_config := `{ "version": 1 }`}}
|
||||
doMarkup bool
|
||||
|
||||
// the placeholder in the source when passed to Blackfriday etc.
|
||||
// This also identifies the rendered shortcode.
|
||||
placeholder string
|
||||
|
||||
pos int // the position in bytes in the source file
|
||||
length int // the length in bytes in the source file
|
||||
}
|
||||
|
||||
func (s shortcode) innerString() string {
|
||||
@@ -214,193 +237,92 @@ func (sc shortcode) String() string {
|
||||
return fmt.Sprintf("%s(%q, %t){%s}", sc.name, params, sc.doMarkup, sc.inner)
|
||||
}
|
||||
|
||||
// We may have special shortcode templates for AMP etc.
|
||||
// Note that in the below, OutputFormat may be empty.
|
||||
// We will try to look for the most specific shortcode template available.
|
||||
type scKey struct {
|
||||
Lang string
|
||||
OutputFormat string
|
||||
Suffix string
|
||||
ShortcodePlaceholder string
|
||||
}
|
||||
|
||||
func newScKey(m media.Type, shortcodeplaceholder string) scKey {
|
||||
return scKey{Suffix: m.Suffix(), ShortcodePlaceholder: shortcodeplaceholder}
|
||||
}
|
||||
|
||||
func newScKeyFromLangAndOutputFormat(lang string, o output.Format, shortcodeplaceholder string) scKey {
|
||||
return scKey{Lang: lang, Suffix: o.MediaType.Suffix(), OutputFormat: o.Name, ShortcodePlaceholder: shortcodeplaceholder}
|
||||
}
|
||||
|
||||
func newDefaultScKey(shortcodeplaceholder string) scKey {
|
||||
return newScKey(media.HTMLType, shortcodeplaceholder)
|
||||
}
|
||||
|
||||
type shortcodeHandler struct {
|
||||
init sync.Once
|
||||
p *pageState
|
||||
|
||||
p *PageWithoutContent
|
||||
s *Site
|
||||
|
||||
// This is all shortcode rendering funcs for all potential output formats.
|
||||
contentShortcodes *orderedMap
|
||||
|
||||
// This map contains the new or changed set of shortcodes that need
|
||||
// to be rendered for the current output format.
|
||||
contentShortcodesDelta *orderedMap
|
||||
|
||||
// This maps the shorcode placeholders with the rendered content.
|
||||
// We will do (potential) partial re-rendering per output format,
|
||||
// so keep this for the unchanged.
|
||||
renderedShortcodes map[string]string
|
||||
|
||||
// Maps the shortcodeplaceholder with the actual shortcode.
|
||||
shortcodes *orderedMap
|
||||
// Ordered list of shortcodes for a page.
|
||||
shortcodes []*shortcode
|
||||
|
||||
// All the shortcode names in this set.
|
||||
nameSet map[string]bool
|
||||
|
||||
placeholderID int
|
||||
placeholderFunc func() string
|
||||
|
||||
// Configuration
|
||||
enableInlineShortcodes bool
|
||||
}
|
||||
|
||||
func (s *shortcodeHandler) nextPlaceholderID() int {
|
||||
s.placeholderID++
|
||||
return s.placeholderID
|
||||
}
|
||||
func newShortcodeHandler(p *pageState, s *Site, placeholderFunc func() string) *shortcodeHandler {
|
||||
|
||||
func (s *shortcodeHandler) createShortcodePlaceholder() string {
|
||||
return s.placeholderFunc()
|
||||
}
|
||||
|
||||
func newShortcodeHandler(p *Page) *shortcodeHandler {
|
||||
|
||||
s := &shortcodeHandler{
|
||||
p: p.withoutContent(),
|
||||
enableInlineShortcodes: p.s.enableInlineShortcodes,
|
||||
contentShortcodes: newOrderedMap(),
|
||||
shortcodes: newOrderedMap(),
|
||||
sh := &shortcodeHandler{
|
||||
p: p,
|
||||
s: s,
|
||||
enableInlineShortcodes: s.enableInlineShortcodes,
|
||||
shortcodes: make([]*shortcode, 0, 4),
|
||||
nameSet: make(map[string]bool),
|
||||
renderedShortcodes: make(map[string]string),
|
||||
}
|
||||
|
||||
placeholderFunc := p.s.shortcodePlaceholderFunc
|
||||
if placeholderFunc == nil {
|
||||
placeholderFunc = func() string {
|
||||
return fmt.Sprintf("HAHA%s-%p-%d-HBHB", shortcodePlaceholderPrefix, p, s.nextPlaceholderID())
|
||||
}
|
||||
|
||||
}
|
||||
s.placeholderFunc = placeholderFunc
|
||||
return s
|
||||
return sh
|
||||
}
|
||||
|
||||
// TODO(bep) make it non-global
|
||||
var isInnerShortcodeCache = struct {
|
||||
sync.RWMutex
|
||||
m map[string]bool
|
||||
}{m: make(map[string]bool)}
|
||||
|
||||
// to avoid potential costly look-aheads for closing tags we look inside the template itself
|
||||
// we could change the syntax to self-closing tags, but that would make users cry
|
||||
// the value found is cached
|
||||
func isInnerShortcode(t tpl.TemplateExecutor) (bool, error) {
|
||||
isInnerShortcodeCache.RLock()
|
||||
m, ok := isInnerShortcodeCache.m[t.Name()]
|
||||
isInnerShortcodeCache.RUnlock()
|
||||
|
||||
if ok {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
isInnerShortcodeCache.Lock()
|
||||
defer isInnerShortcodeCache.Unlock()
|
||||
match, _ := regexp.MatchString("{{.*?\\.Inner.*?}}", t.Tree())
|
||||
isInnerShortcodeCache.m[t.Name()] = match
|
||||
|
||||
return match, nil
|
||||
}
|
||||
|
||||
func clearIsInnerShortcodeCache() {
|
||||
isInnerShortcodeCache.Lock()
|
||||
defer isInnerShortcodeCache.Unlock()
|
||||
isInnerShortcodeCache.m = make(map[string]bool)
|
||||
}
|
||||
|
||||
const innerNewlineRegexp = "\n"
|
||||
const innerCleanupRegexp = `\A<p>(.*)</p>\n\z`
|
||||
const innerCleanupExpand = "$1"
|
||||
|
||||
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)
|
||||
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)
|
||||
m[key] = func() (string, error) {
|
||||
return renderShortcode(key, sc, nil, p)
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
const (
|
||||
innerNewlineRegexp = "\n"
|
||||
innerCleanupRegexp = `\A<p>(.*)</p>\n\z`
|
||||
innerCleanupExpand = "$1"
|
||||
)
|
||||
|
||||
func renderShortcode(
|
||||
tmplKey scKey,
|
||||
level int,
|
||||
s *Site,
|
||||
tplVariants tpl.TemplateVariants,
|
||||
sc *shortcode,
|
||||
parent *ShortcodeWithPage,
|
||||
p *PageWithoutContent) (string, error) {
|
||||
p *pageState) (string, bool, error) {
|
||||
|
||||
var tmpl tpl.Template
|
||||
|
||||
// Tracks whether this shortcode or any of its children has template variations
|
||||
// in other languages or output formats. We are currently only interested in
|
||||
// the output formats, so we may get some false positives -- we
|
||||
// should improve on that.
|
||||
var hasVariants bool
|
||||
|
||||
if sc.isInline {
|
||||
if !p.s.enableInlineShortcodes {
|
||||
return "", nil
|
||||
return "", false, nil
|
||||
}
|
||||
templName := path.Join("_inline_shortcode", p.Path(), sc.name)
|
||||
templName := path.Join("_inline_shortcode", p.File().Path(), sc.name)
|
||||
if sc.isClosing {
|
||||
templStr := sc.innerString()
|
||||
|
||||
var err error
|
||||
tmpl, err = p.s.TextTmpl.Parse(templName, templStr)
|
||||
tmpl, err = s.TextTmpl.Parse(templName, templStr)
|
||||
if err != nil {
|
||||
fe := herrors.ToFileError("html", err)
|
||||
l1, l2 := p.posFromPage(sc.pos).LineNumber, fe.Position().LineNumber
|
||||
l1, l2 := p.posOffset(sc.pos).LineNumber, fe.Position().LineNumber
|
||||
fe = herrors.ToFileErrorWithLineNumber(fe, l1+l2-1)
|
||||
return "", p.errWithFileContext(fe)
|
||||
return "", false, p.wrapError(fe)
|
||||
}
|
||||
|
||||
} else {
|
||||
// Re-use of shortcode defined earlier in the same page.
|
||||
var found bool
|
||||
tmpl, found = p.s.TextTmpl.Lookup(templName)
|
||||
tmpl, found = s.TextTmpl.Lookup(templName)
|
||||
if !found {
|
||||
return "", _errors.Errorf("no earlier definition of shortcode %q found", sc.name)
|
||||
return "", false, _errors.Errorf("no earlier definition of shortcode %q found", sc.name)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tmpl = getShortcodeTemplateForTemplateKey(tmplKey, sc.name, p.s.Tmpl)
|
||||
var found, more bool
|
||||
tmpl, found, more = s.Tmpl.LookupVariant(sc.name, tplVariants)
|
||||
if !found {
|
||||
s.Log.ERROR.Printf("Unable to locate template for shortcode %q in page %q", sc.name, p.File().Path())
|
||||
return "", false, nil
|
||||
}
|
||||
hasVariants = hasVariants || more
|
||||
}
|
||||
|
||||
if tmpl == nil {
|
||||
p.s.Log.ERROR.Printf("Unable to locate template for shortcode %q in page %q", sc.name, p.Path())
|
||||
return "", nil
|
||||
}
|
||||
|
||||
data := &ShortcodeWithPage{Ordinal: sc.ordinal, posOffset: sc.pos, Params: sc.params, Page: p, Parent: parent, Name: sc.name}
|
||||
data := &ShortcodeWithPage{Ordinal: sc.ordinal, posOffset: sc.pos, Params: sc.params, Page: newPageForShortcode(p), Parent: parent, Name: sc.name}
|
||||
if sc.params != nil {
|
||||
data.IsNamedParams = reflect.TypeOf(sc.params).Kind() == reflect.Map
|
||||
}
|
||||
@@ -408,32 +330,35 @@ func renderShortcode(
|
||||
if len(sc.inner) > 0 {
|
||||
var inner string
|
||||
for _, innerData := range sc.inner {
|
||||
switch innerData.(type) {
|
||||
switch innerData := innerData.(type) {
|
||||
case string:
|
||||
inner += innerData.(string)
|
||||
inner += innerData
|
||||
case *shortcode:
|
||||
s, err := renderShortcode(tmplKey, innerData.(*shortcode), data, p)
|
||||
s, more, err := renderShortcode(level+1, s, tplVariants, innerData, data, p)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", false, err
|
||||
}
|
||||
hasVariants = hasVariants || more
|
||||
inner += s
|
||||
default:
|
||||
p.s.Log.ERROR.Printf("Illegal state on shortcode rendering of %q in page %q. Illegal type in inner data: %s ",
|
||||
sc.name, p.Path(), reflect.TypeOf(innerData))
|
||||
return "", nil
|
||||
s.Log.ERROR.Printf("Illegal state on shortcode rendering of %q in page %q. Illegal type in inner data: %s ",
|
||||
sc.name, p.File().Path(), reflect.TypeOf(innerData))
|
||||
return "", false, nil
|
||||
}
|
||||
}
|
||||
|
||||
if sc.doMarkup {
|
||||
newInner := p.s.ContentSpec.RenderBytes(&helpers.RenderingContext{
|
||||
// Pre Hugo 0.55 this was the behaviour even for the outer-most
|
||||
// shortcode.
|
||||
if sc.doMarkup && (level > 0 || sc.info.Config.Version == 1) {
|
||||
newInner := s.ContentSpec.RenderBytes(&helpers.RenderingContext{
|
||||
Content: []byte(inner),
|
||||
PageFmt: p.Markup,
|
||||
PageFmt: p.m.markup,
|
||||
Cfg: p.Language(),
|
||||
DocumentID: p.UniqueID(),
|
||||
DocumentName: p.Path(),
|
||||
DocumentID: p.File().UniqueID(),
|
||||
DocumentName: p.File().Path(),
|
||||
Config: p.getRenderingConfig()})
|
||||
|
||||
// If the type is “unknown” or “markdown”, we assume the markdown
|
||||
// If the type is “” (unknown) or “markdown”, we assume the markdown
|
||||
// generation has been performed. Given the input: `a line`, markdown
|
||||
// specifies the HTML `<p>a line</p>\n`. When dealing with documents as a
|
||||
// whole, this is OK. When dealing with an `{{ .Inner }}` block in Hugo,
|
||||
@@ -442,12 +367,9 @@ func renderShortcode(
|
||||
// 1. Check to see if inner has a newline in it. If so, the Inner data is
|
||||
// unchanged.
|
||||
// 2 If inner does not have a newline, strip the wrapping <p> block and
|
||||
// the newline. This was previously tricked out by wrapping shortcode
|
||||
// substitutions in <div>HUGOSHORTCODE-1</div> which prevents the
|
||||
// generation, but means that you can’t use shortcodes inside of
|
||||
// markdown structures itself (e.g., `[foo]({{% ref foo.md %}})`).
|
||||
switch p.Markup {
|
||||
case "unknown", "markdown":
|
||||
// the newline.
|
||||
switch p.m.markup {
|
||||
case "", "markdown":
|
||||
if match, _ := regexp.MatchString(innerNewlineRegexp, inner); !match {
|
||||
cleaner, err := regexp.Compile(innerCleanupRegexp)
|
||||
|
||||
@@ -465,147 +387,71 @@ func renderShortcode(
|
||||
|
||||
}
|
||||
|
||||
s, err := renderShortcodeWithPage(tmpl, data)
|
||||
result, 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 "", false, fe
|
||||
}
|
||||
|
||||
return s, err
|
||||
return result, hasVariants, err
|
||||
}
|
||||
|
||||
// The delta represents new output format-versions of the shortcodes,
|
||||
// which, combined with the ones that do not have alternative representations,
|
||||
// builds a complete set ready for a full rebuild of the Page content.
|
||||
// This method returns false if there are no new shortcode variants in the
|
||||
// current rendering context's output format. This mean we can safely reuse
|
||||
// the content from the previous output format, if any.
|
||||
func (s *shortcodeHandler) updateDelta() bool {
|
||||
s.init.Do(func() {
|
||||
s.contentShortcodes = s.createShortcodeRenderers(s.p.withoutContent())
|
||||
})
|
||||
|
||||
if !s.p.shouldRenderTo(s.p.s.rc.Format) {
|
||||
// TODO(bep) add test for this re translations
|
||||
return false
|
||||
}
|
||||
of := s.p.s.rc.Format
|
||||
contentShortcodes := s.contentShortcodesForOutputFormat(of)
|
||||
|
||||
if s.contentShortcodesDelta == nil || s.contentShortcodesDelta.Len() == 0 {
|
||||
s.contentShortcodesDelta = contentShortcodes
|
||||
return true
|
||||
}
|
||||
|
||||
delta := newOrderedMap()
|
||||
|
||||
for _, k := range contentShortcodes.Keys() {
|
||||
if !s.contentShortcodesDelta.Contains(k) {
|
||||
v, _ := contentShortcodes.Get(k)
|
||||
delta.Add(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
s.contentShortcodesDelta = delta
|
||||
|
||||
return delta.Len() > 0
|
||||
func (s *shortcodeHandler) hasShortcodes() bool {
|
||||
return len(s.shortcodes) > 0
|
||||
}
|
||||
|
||||
func (s *shortcodeHandler) clearDelta() {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
s.contentShortcodesDelta = newOrderedMap()
|
||||
}
|
||||
func (s *shortcodeHandler) renderShortcodesForPage(p *pageState, f output.Format) (map[string]string, bool, error) {
|
||||
|
||||
func (s *shortcodeHandler) contentShortcodesForOutputFormat(f output.Format) *orderedMap {
|
||||
contentShortcodesForOuputFormat := newOrderedMap()
|
||||
lang := s.p.Lang()
|
||||
rendered := make(map[string]string)
|
||||
|
||||
for _, key := range s.shortcodes.Keys() {
|
||||
shortcodePlaceholder := key.(string)
|
||||
|
||||
key := newScKeyFromLangAndOutputFormat(lang, f, shortcodePlaceholder)
|
||||
renderFn, found := s.contentShortcodes.Get(key)
|
||||
|
||||
if !found {
|
||||
key.OutputFormat = ""
|
||||
renderFn, found = s.contentShortcodes.Get(key)
|
||||
}
|
||||
|
||||
// Fall back to HTML
|
||||
if !found && key.Suffix != "html" {
|
||||
key.Suffix = "html"
|
||||
renderFn, found = s.contentShortcodes.Get(key)
|
||||
if !found {
|
||||
key.OutputFormat = "HTML"
|
||||
renderFn, found = s.contentShortcodes.Get(key)
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
panic(fmt.Sprintf("Shortcode %q could not be found", shortcodePlaceholder))
|
||||
}
|
||||
contentShortcodesForOuputFormat.Add(newScKeyFromLangAndOutputFormat(lang, f, shortcodePlaceholder), renderFn)
|
||||
tplVariants := tpl.TemplateVariants{
|
||||
Language: p.Language().Lang,
|
||||
OutputFormat: f,
|
||||
}
|
||||
|
||||
return contentShortcodesForOuputFormat
|
||||
}
|
||||
var hasVariants bool
|
||||
|
||||
func (s *shortcodeHandler) executeShortcodesForDelta(p *PageWithoutContent) error {
|
||||
|
||||
for _, k := range s.contentShortcodesDelta.Keys() {
|
||||
render := s.contentShortcodesDelta.getShortcodeRenderer(k)
|
||||
renderedShortcode, err := render()
|
||||
for _, v := range s.shortcodes {
|
||||
s, more, err := renderShortcode(0, s.s, tplVariants, v, nil, p)
|
||||
if err != nil {
|
||||
sc := s.shortcodes.getShortcode(k.(scKey).ShortcodePlaceholder)
|
||||
if sc != nil {
|
||||
err = p.errWithFileContext(p.parseError(_errors.Wrapf(err, "failed to render shortcode %q", sc.name), p.source.parsed.Input(), sc.pos))
|
||||
}
|
||||
|
||||
p.s.SendError(err)
|
||||
continue
|
||||
err = p.parseError(_errors.Wrapf(err, "failed to render shortcode %q", v.name), p.source.parsed.Input(), v.pos)
|
||||
return nil, false, err
|
||||
}
|
||||
hasVariants = hasVariants || more
|
||||
rendered[v.placeholder] = s
|
||||
|
||||
s.renderedShortcodes[k.(scKey).ShortcodePlaceholder] = renderedShortcode
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (s *shortcodeHandler) createShortcodeRenderers(p *PageWithoutContent) *orderedMap {
|
||||
|
||||
shortcodeRenderers := newOrderedMap()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
return shortcodeRenderers
|
||||
return rendered, hasVariants, nil
|
||||
}
|
||||
|
||||
var errShortCodeIllegalState = errors.New("Illegal shortcode state")
|
||||
|
||||
func (s *shortcodeHandler) parseError(err error, input []byte, pos int) error {
|
||||
if s.p != nil {
|
||||
return s.p.parseError(err, input, pos)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// pageTokens state:
|
||||
// - before: positioned just before the shortcode start
|
||||
// - after: shortcode(s) consumed (plural when they are nested)
|
||||
func (s *shortcodeHandler) extractShortcode(ordinal int, pt *pageparser.Iterator, p *Page) (*shortcode, error) {
|
||||
func (s *shortcodeHandler) extractShortcode(ordinal, level int, pt *pageparser.Iterator) (*shortcode, error) {
|
||||
if s == nil {
|
||||
panic("handler nil")
|
||||
}
|
||||
sc := &shortcode{ordinal: ordinal}
|
||||
var isInner = false
|
||||
|
||||
var cnt = 0
|
||||
var nestedOrdinal = 0
|
||||
var nextLevel = level + 1
|
||||
|
||||
fail := func(err error, i pageparser.Item) error {
|
||||
return p.parseError(err, pt.Input(), i.Pos)
|
||||
return s.parseError(err, pt.Input(), i.Pos)
|
||||
}
|
||||
|
||||
Loop:
|
||||
@@ -613,9 +459,6 @@ Loop:
|
||||
currItem := pt.Next()
|
||||
switch {
|
||||
case currItem.IsLeftShortcodeDelim():
|
||||
if sc.pos == 0 {
|
||||
sc.pos = currItem.Pos
|
||||
}
|
||||
next := pt.Peek()
|
||||
if next.IsShortcodeClose() {
|
||||
continue
|
||||
@@ -624,7 +467,7 @@ Loop:
|
||||
if cnt > 0 {
|
||||
// nested shortcode; append it to inner content
|
||||
pt.Backup()
|
||||
nested, err := s.extractShortcode(nestedOrdinal, pt, p)
|
||||
nested, err := s.extractShortcode(nestedOrdinal, nextLevel, pt)
|
||||
nestedOrdinal++
|
||||
if nested.name != "" {
|
||||
s.nameSet[nested.name] = true
|
||||
@@ -644,13 +487,13 @@ Loop:
|
||||
case currItem.IsRightShortcodeDelim():
|
||||
// we trust the template on this:
|
||||
// if there's no inner, we're done
|
||||
if !sc.isInline && !isInner {
|
||||
if !sc.isInline && !sc.info.IsInner {
|
||||
return sc, nil
|
||||
}
|
||||
|
||||
case currItem.IsShortcodeClose():
|
||||
next := pt.Peek()
|
||||
if !sc.isInline && !isInner {
|
||||
if !sc.isInline && !sc.info.IsInner {
|
||||
if next.IsError() {
|
||||
// return that error, more specific
|
||||
continue
|
||||
@@ -670,24 +513,21 @@ Loop:
|
||||
case currItem.IsText():
|
||||
sc.inner = append(sc.inner, currItem.ValStr())
|
||||
case currItem.IsShortcodeName():
|
||||
|
||||
sc.name = currItem.ValStr()
|
||||
|
||||
// Check if the template expects inner content.
|
||||
// We pick the first template for an arbitrary output format
|
||||
// if more than one. It is "all inner or no inner".
|
||||
tmpl := getShortcodeTemplateForTemplateKey(scKey{}, sc.name, p.s.Tmpl)
|
||||
if tmpl == nil {
|
||||
return sc, fail(_errors.Errorf("template for shortcode %q not found", sc.name), currItem)
|
||||
}
|
||||
|
||||
var err error
|
||||
isInner, err = isInnerShortcode(tmpl.(tpl.TemplateExecutor))
|
||||
if err != nil {
|
||||
return sc, fail(_errors.Wrapf(err, "failed to handle template for shortcode %q", sc.name), currItem)
|
||||
tmpl, found, _ := s.s.Tmpl.LookupVariant(sc.name, tpl.TemplateVariants{})
|
||||
if !found {
|
||||
return nil, _errors.Errorf("template for shortcode %q not found", sc.name)
|
||||
}
|
||||
|
||||
sc.info = tmpl.(tpl.TemplateInfoProvider).TemplateInfo()
|
||||
case currItem.IsInlineShortcodeName():
|
||||
sc.name = currItem.ValStr()
|
||||
sc.isInline = true
|
||||
|
||||
case currItem.IsShortcodeParam():
|
||||
if !pt.IsValueNext() {
|
||||
continue
|
||||
@@ -721,7 +561,6 @@ Loop:
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
case currItem.IsDone():
|
||||
// handled by caller
|
||||
pt.Backup()
|
||||
@@ -732,11 +571,9 @@ Loop:
|
||||
return sc, nil
|
||||
}
|
||||
|
||||
var shortCodeStart = []byte("{{")
|
||||
|
||||
// Replace prefixed shortcode tokens (HUGOSHORTCODE-1, HUGOSHORTCODE-2) with the real content.
|
||||
// Replace prefixed shortcode tokens with the real content.
|
||||
// Note: This function will rewrite the input slice.
|
||||
func replaceShortcodeTokens(source []byte, prefix string, replacements map[string]string) ([]byte, error) {
|
||||
func replaceShortcodeTokens(source []byte, replacements map[string]string) ([]byte, error) {
|
||||
|
||||
if len(replacements) == 0 {
|
||||
return source, nil
|
||||
@@ -744,7 +581,7 @@ func replaceShortcodeTokens(source []byte, prefix string, replacements map[strin
|
||||
|
||||
start := 0
|
||||
|
||||
pre := []byte("HAHA" + prefix)
|
||||
pre := []byte(shortcodePlaceholderPrefix)
|
||||
post := []byte("HBHB")
|
||||
pStart := []byte("<p>")
|
||||
pEnd := []byte("</p>")
|
||||
@@ -781,54 +618,11 @@ func replaceShortcodeTokens(source []byte, prefix string, replacements map[strin
|
||||
return source, nil
|
||||
}
|
||||
|
||||
func getShortcodeTemplateForTemplateKey(key scKey, shortcodeName string, t tpl.TemplateFinder) tpl.Template {
|
||||
isInnerShortcodeCache.RLock()
|
||||
defer isInnerShortcodeCache.RUnlock()
|
||||
|
||||
var names []string
|
||||
|
||||
suffix := strings.ToLower(key.Suffix)
|
||||
outFormat := strings.ToLower(key.OutputFormat)
|
||||
lang := strings.ToLower(key.Lang)
|
||||
|
||||
if outFormat != "" && suffix != "" {
|
||||
if lang != "" {
|
||||
names = append(names, fmt.Sprintf("%s.%s.%s.%s", shortcodeName, lang, outFormat, suffix))
|
||||
}
|
||||
names = append(names, fmt.Sprintf("%s.%s.%s", shortcodeName, outFormat, suffix))
|
||||
}
|
||||
|
||||
if suffix != "" {
|
||||
if lang != "" {
|
||||
names = append(names, fmt.Sprintf("%s.%s.%s", shortcodeName, lang, suffix))
|
||||
}
|
||||
names = append(names, fmt.Sprintf("%s.%s", shortcodeName, suffix))
|
||||
}
|
||||
|
||||
names = append(names, shortcodeName)
|
||||
|
||||
for _, name := range names {
|
||||
|
||||
if x, found := t.Lookup("shortcodes/" + name); found {
|
||||
return x
|
||||
}
|
||||
if x, found := t.Lookup("theme/shortcodes/" + name); found {
|
||||
return x
|
||||
}
|
||||
if x, found := t.Lookup("_internal/shortcodes/" + name); found {
|
||||
return x
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func renderShortcodeWithPage(tmpl tpl.Template, data *ShortcodeWithPage) (string, error) {
|
||||
buffer := bp.GetBuffer()
|
||||
defer bp.PutBuffer(buffer)
|
||||
|
||||
isInnerShortcodeCache.RLock()
|
||||
err := tmpl.Execute(buffer, data)
|
||||
isInnerShortcodeCache.RUnlock()
|
||||
if err != nil {
|
||||
return "", _errors.Wrap(err, "failed to process shortcode")
|
||||
}
|
||||
|
Reference in New Issue
Block a user