github.com/pietrocarrara/hugo@v0.47.1/hugolib/shortcode.go (about)

     1  // Copyright 2017 The Hugo Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package hugolib
    15  
    16  import (
    17  	"bytes"
    18  	"errors"
    19  	"fmt"
    20  	"html/template"
    21  	"reflect"
    22  	"regexp"
    23  	"sort"
    24  	"strings"
    25  	"sync"
    26  
    27  	"github.com/gohugoio/hugo/common/maps"
    28  	"github.com/gohugoio/hugo/output"
    29  
    30  	"github.com/gohugoio/hugo/media"
    31  
    32  	bp "github.com/gohugoio/hugo/bufferpool"
    33  	"github.com/gohugoio/hugo/helpers"
    34  	"github.com/gohugoio/hugo/tpl"
    35  )
    36  
    37  // ShortcodeWithPage is the "." context in a shortcode template.
    38  type ShortcodeWithPage struct {
    39  	Params        interface{}
    40  	Inner         template.HTML
    41  	Page          *PageWithoutContent
    42  	Parent        *ShortcodeWithPage
    43  	IsNamedParams bool
    44  
    45  	// Zero-based ordinal in relation to its parent. If the parent is the page itself,
    46  	// this ordinal will represent the position of this shortcode in the page content.
    47  	Ordinal int
    48  
    49  	scratch *maps.Scratch
    50  }
    51  
    52  // Site returns information about the current site.
    53  func (scp *ShortcodeWithPage) Site() *SiteInfo {
    54  	return scp.Page.Site
    55  }
    56  
    57  // Ref is a shortcut to the Ref method on Page.
    58  func (scp *ShortcodeWithPage) Ref(args map[string]interface{}) (string, error) {
    59  	return scp.Page.Ref(args)
    60  }
    61  
    62  // RelRef is a shortcut to the RelRef method on Page.
    63  func (scp *ShortcodeWithPage) RelRef(args map[string]interface{}) (string, error) {
    64  	return scp.Page.RelRef(args)
    65  }
    66  
    67  // Scratch returns a scratch-pad scoped for this shortcode. This can be used
    68  // as a temporary storage for variables, counters etc.
    69  func (scp *ShortcodeWithPage) Scratch() *maps.Scratch {
    70  	if scp.scratch == nil {
    71  		scp.scratch = maps.NewScratch()
    72  	}
    73  	return scp.scratch
    74  }
    75  
    76  // Get is a convenience method to look up shortcode parameters by its key.
    77  func (scp *ShortcodeWithPage) Get(key interface{}) interface{} {
    78  	if scp.Params == nil {
    79  		return nil
    80  	}
    81  	if reflect.ValueOf(scp.Params).Len() == 0 {
    82  		return nil
    83  	}
    84  
    85  	var x reflect.Value
    86  
    87  	switch key.(type) {
    88  	case int64, int32, int16, int8, int:
    89  		if reflect.TypeOf(scp.Params).Kind() == reflect.Map {
    90  			// We treat this as a non error, so people can do similar to
    91  			// {{ $myParam := .Get "myParam" | default .Get 0 }}
    92  			// Without having to do additional checks.
    93  			return nil
    94  		} else if reflect.TypeOf(scp.Params).Kind() == reflect.Slice {
    95  			idx := int(reflect.ValueOf(key).Int())
    96  			ln := reflect.ValueOf(scp.Params).Len()
    97  			if idx > ln-1 {
    98  				return ""
    99  			}
   100  			x = reflect.ValueOf(scp.Params).Index(idx)
   101  		}
   102  	case string:
   103  		if reflect.TypeOf(scp.Params).Kind() == reflect.Map {
   104  			x = reflect.ValueOf(scp.Params).MapIndex(reflect.ValueOf(key))
   105  			if !x.IsValid() {
   106  				return ""
   107  			}
   108  		} else if reflect.TypeOf(scp.Params).Kind() == reflect.Slice {
   109  			// We treat this as a non error, so people can do similar to
   110  			// {{ $myParam := .Get "myParam" | default .Get 0 }}
   111  			// Without having to do additional checks.
   112  			return nil
   113  		}
   114  	}
   115  
   116  	switch x.Kind() {
   117  	case reflect.String:
   118  		return x.String()
   119  	case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int:
   120  		return x.Int()
   121  	default:
   122  		return x
   123  	}
   124  
   125  }
   126  
   127  // Note - this value must not contain any markup syntax
   128  const shortcodePlaceholderPrefix = "HUGOSHORTCODE"
   129  
   130  type shortcode struct {
   131  	name     string
   132  	inner    []interface{} // string or nested shortcode
   133  	params   interface{}   // map or array
   134  	ordinal  int
   135  	err      error
   136  	doMarkup bool
   137  }
   138  
   139  func (sc shortcode) String() string {
   140  	// for testing (mostly), so any change here will break tests!
   141  	var params interface{}
   142  	switch v := sc.params.(type) {
   143  	case map[string]string:
   144  		// sort the keys so test assertions won't fail
   145  		var keys []string
   146  		for k := range v {
   147  			keys = append(keys, k)
   148  		}
   149  		sort.Strings(keys)
   150  		var tmp = make([]string, len(keys))
   151  
   152  		for i, k := range keys {
   153  			tmp[i] = k + ":" + v[k]
   154  		}
   155  		params = tmp
   156  
   157  	default:
   158  		// use it as is
   159  		params = sc.params
   160  	}
   161  
   162  	return fmt.Sprintf("%s(%q, %t){%s}", sc.name, params, sc.doMarkup, sc.inner)
   163  }
   164  
   165  // We may have special shortcode templates for AMP etc.
   166  // Note that in the below, OutputFormat may be empty.
   167  // We will try to look for the most specific shortcode template available.
   168  type scKey struct {
   169  	Lang                 string
   170  	OutputFormat         string
   171  	Suffix               string
   172  	ShortcodePlaceholder string
   173  }
   174  
   175  func newScKey(m media.Type, shortcodeplaceholder string) scKey {
   176  	return scKey{Suffix: m.Suffix(), ShortcodePlaceholder: shortcodeplaceholder}
   177  }
   178  
   179  func newScKeyFromLangAndOutputFormat(lang string, o output.Format, shortcodeplaceholder string) scKey {
   180  	return scKey{Lang: lang, Suffix: o.MediaType.Suffix(), OutputFormat: o.Name, ShortcodePlaceholder: shortcodeplaceholder}
   181  }
   182  
   183  func newDefaultScKey(shortcodeplaceholder string) scKey {
   184  	return newScKey(media.HTMLType, shortcodeplaceholder)
   185  }
   186  
   187  type shortcodeHandler struct {
   188  	init sync.Once
   189  
   190  	p *PageWithoutContent
   191  
   192  	// This is all shortcode rendering funcs for all potential output formats.
   193  	contentShortcodes *orderedMap
   194  
   195  	// This map contains the new or changed set of shortcodes that need
   196  	// to be rendered for the current output format.
   197  	contentShortcodesDelta *orderedMap
   198  
   199  	// This maps the shorcode placeholders with the rendered content.
   200  	// We will do (potential) partial re-rendering per output format,
   201  	// so keep this for the unchanged.
   202  	renderedShortcodes map[string]string
   203  
   204  	// Maps the shortcodeplaceholder with the actual shortcode.
   205  	shortcodes *orderedMap
   206  
   207  	// All the shortcode names in this set.
   208  	nameSet map[string]bool
   209  
   210  	placeholderID   int
   211  	placeholderFunc func() string
   212  }
   213  
   214  func (s *shortcodeHandler) nextPlaceholderID() int {
   215  	s.placeholderID++
   216  	return s.placeholderID
   217  }
   218  
   219  func (s *shortcodeHandler) createShortcodePlaceholder() string {
   220  	if s.placeholderFunc != nil {
   221  		return s.placeholderFunc()
   222  	}
   223  	return fmt.Sprintf("HAHA%s-%p-%d-HBHB", shortcodePlaceholderPrefix, s.p.Page, s.nextPlaceholderID())
   224  }
   225  
   226  func newShortcodeHandler(p *Page) *shortcodeHandler {
   227  	return &shortcodeHandler{
   228  		p:                  p.withoutContent(),
   229  		contentShortcodes:  newOrderedMap(),
   230  		shortcodes:         newOrderedMap(),
   231  		nameSet:            make(map[string]bool),
   232  		renderedShortcodes: make(map[string]string),
   233  	}
   234  }
   235  
   236  // TODO(bep) make it non-global
   237  var isInnerShortcodeCache = struct {
   238  	sync.RWMutex
   239  	m map[string]bool
   240  }{m: make(map[string]bool)}
   241  
   242  // to avoid potential costly look-aheads for closing tags we look inside the template itself
   243  // we could change the syntax to self-closing tags, but that would make users cry
   244  // the value found is cached
   245  func isInnerShortcode(t tpl.TemplateExecutor) (bool, error) {
   246  	isInnerShortcodeCache.RLock()
   247  	m, ok := isInnerShortcodeCache.m[t.Name()]
   248  	isInnerShortcodeCache.RUnlock()
   249  
   250  	if ok {
   251  		return m, nil
   252  	}
   253  
   254  	isInnerShortcodeCache.Lock()
   255  	defer isInnerShortcodeCache.Unlock()
   256  	match, _ := regexp.MatchString("{{.*?\\.Inner.*?}}", t.Tree())
   257  	isInnerShortcodeCache.m[t.Name()] = match
   258  
   259  	return match, nil
   260  }
   261  
   262  func clearIsInnerShortcodeCache() {
   263  	isInnerShortcodeCache.Lock()
   264  	defer isInnerShortcodeCache.Unlock()
   265  	isInnerShortcodeCache.m = make(map[string]bool)
   266  }
   267  
   268  const innerNewlineRegexp = "\n"
   269  const innerCleanupRegexp = `\A<p>(.*)</p>\n\z`
   270  const innerCleanupExpand = "$1"
   271  
   272  func prepareShortcodeForPage(placeholder string, sc *shortcode, parent *ShortcodeWithPage, p *PageWithoutContent) map[scKey]func() (string, error) {
   273  
   274  	m := make(map[scKey]func() (string, error))
   275  	lang := p.Lang()
   276  
   277  	for _, f := range p.outputFormats {
   278  		// The most specific template will win.
   279  		key := newScKeyFromLangAndOutputFormat(lang, f, placeholder)
   280  		m[key] = func() (string, error) {
   281  			return renderShortcode(key, sc, nil, p), nil
   282  		}
   283  	}
   284  
   285  	return m
   286  }
   287  
   288  func renderShortcode(
   289  	tmplKey scKey,
   290  	sc *shortcode,
   291  	parent *ShortcodeWithPage,
   292  	p *PageWithoutContent) string {
   293  
   294  	tmpl := getShortcodeTemplateForTemplateKey(tmplKey, sc.name, p.s.Tmpl)
   295  	if tmpl == nil {
   296  		p.s.Log.ERROR.Printf("Unable to locate template for shortcode %q in page %q", sc.name, p.Path())
   297  		return ""
   298  	}
   299  
   300  	data := &ShortcodeWithPage{Ordinal: sc.ordinal, Params: sc.params, Page: p, Parent: parent}
   301  	if sc.params != nil {
   302  		data.IsNamedParams = reflect.TypeOf(sc.params).Kind() == reflect.Map
   303  	}
   304  
   305  	if len(sc.inner) > 0 {
   306  		var inner string
   307  		for _, innerData := range sc.inner {
   308  			switch innerData.(type) {
   309  			case string:
   310  				inner += innerData.(string)
   311  			case *shortcode:
   312  				inner += renderShortcode(tmplKey, innerData.(*shortcode), data, p)
   313  			default:
   314  				p.s.Log.ERROR.Printf("Illegal state on shortcode rendering of %q in page %q. Illegal type in inner data: %s ",
   315  					sc.name, p.Path(), reflect.TypeOf(innerData))
   316  				return ""
   317  			}
   318  		}
   319  
   320  		if sc.doMarkup {
   321  			newInner := p.s.ContentSpec.RenderBytes(&helpers.RenderingContext{
   322  				Content:      []byte(inner),
   323  				PageFmt:      p.Markup,
   324  				Cfg:          p.Language(),
   325  				DocumentID:   p.UniqueID(),
   326  				DocumentName: p.Path(),
   327  				Config:       p.getRenderingConfig()})
   328  
   329  			// If the type is “unknown” or “markdown”, we assume the markdown
   330  			// generation has been performed. Given the input: `a line`, markdown
   331  			// specifies the HTML `<p>a line</p>\n`. When dealing with documents as a
   332  			// whole, this is OK. When dealing with an `{{ .Inner }}` block in Hugo,
   333  			// this is not so good. This code does two things:
   334  			//
   335  			// 1.  Check to see if inner has a newline in it. If so, the Inner data is
   336  			//     unchanged.
   337  			// 2   If inner does not have a newline, strip the wrapping <p> block and
   338  			//     the newline. This was previously tricked out by wrapping shortcode
   339  			//     substitutions in <div>HUGOSHORTCODE-1</div> which prevents the
   340  			//     generation, but means that you can’t use shortcodes inside of
   341  			//     markdown structures itself (e.g., `[foo]({{% ref foo.md %}})`).
   342  			switch p.Markup {
   343  			case "unknown", "markdown":
   344  				if match, _ := regexp.MatchString(innerNewlineRegexp, inner); !match {
   345  					cleaner, err := regexp.Compile(innerCleanupRegexp)
   346  
   347  					if err == nil {
   348  						newInner = cleaner.ReplaceAll(newInner, []byte(innerCleanupExpand))
   349  					}
   350  				}
   351  			}
   352  
   353  			// TODO(bep) we may have plain text inner templates.
   354  			data.Inner = template.HTML(newInner)
   355  		} else {
   356  			data.Inner = template.HTML(inner)
   357  		}
   358  
   359  	}
   360  
   361  	return renderShortcodeWithPage(tmpl, data)
   362  }
   363  
   364  // The delta represents new output format-versions of the shortcodes,
   365  // which, combined with the ones that do not have alternative representations,
   366  // builds a complete set ready for a full rebuild of the Page content.
   367  // This method returns false if there are no new shortcode variants in the
   368  // current rendering context's output format. This mean we can safely reuse
   369  // the content from the previous output format, if any.
   370  func (s *shortcodeHandler) updateDelta() bool {
   371  	s.init.Do(func() {
   372  		s.contentShortcodes = createShortcodeRenderers(s.shortcodes, s.p.withoutContent())
   373  	})
   374  
   375  	if !s.p.shouldRenderTo(s.p.s.rc.Format) {
   376  		// TODO(bep) add test for this re translations
   377  		return false
   378  	}
   379  	of := s.p.s.rc.Format
   380  	contentShortcodes := s.contentShortcodesForOutputFormat(of)
   381  
   382  	if s.contentShortcodesDelta == nil || s.contentShortcodesDelta.Len() == 0 {
   383  		s.contentShortcodesDelta = contentShortcodes
   384  		return true
   385  	}
   386  
   387  	delta := newOrderedMap()
   388  
   389  	for _, k := range contentShortcodes.Keys() {
   390  		if !s.contentShortcodesDelta.Contains(k) {
   391  			v, _ := contentShortcodes.Get(k)
   392  			delta.Add(k, v)
   393  		}
   394  	}
   395  
   396  	s.contentShortcodesDelta = delta
   397  
   398  	return delta.Len() > 0
   399  }
   400  
   401  func (s *shortcodeHandler) clearDelta() {
   402  	if s == nil {
   403  		return
   404  	}
   405  	s.contentShortcodesDelta = newOrderedMap()
   406  }
   407  
   408  func (s *shortcodeHandler) contentShortcodesForOutputFormat(f output.Format) *orderedMap {
   409  	contentShortcodesForOuputFormat := newOrderedMap()
   410  	lang := s.p.Lang()
   411  
   412  	for _, key := range s.shortcodes.Keys() {
   413  		shortcodePlaceholder := key.(string)
   414  
   415  		key := newScKeyFromLangAndOutputFormat(lang, f, shortcodePlaceholder)
   416  		renderFn, found := s.contentShortcodes.Get(key)
   417  
   418  		if !found {
   419  			key.OutputFormat = ""
   420  			renderFn, found = s.contentShortcodes.Get(key)
   421  		}
   422  
   423  		// Fall back to HTML
   424  		if !found && key.Suffix != "html" {
   425  			key.Suffix = "html"
   426  			renderFn, found = s.contentShortcodes.Get(key)
   427  		}
   428  
   429  		if !found {
   430  			panic(fmt.Sprintf("Shortcode %q could not be found", shortcodePlaceholder))
   431  		}
   432  		contentShortcodesForOuputFormat.Add(newScKeyFromLangAndOutputFormat(lang, f, shortcodePlaceholder), renderFn)
   433  	}
   434  
   435  	return contentShortcodesForOuputFormat
   436  }
   437  
   438  func (s *shortcodeHandler) executeShortcodesForDelta(p *PageWithoutContent) error {
   439  
   440  	for _, k := range s.contentShortcodesDelta.Keys() {
   441  		render := s.contentShortcodesDelta.getShortcodeRenderer(k)
   442  		renderedShortcode, err := render()
   443  		if err != nil {
   444  			return fmt.Errorf("Failed to execute shortcode in page %q: %s", p.Path(), err)
   445  		}
   446  
   447  		s.renderedShortcodes[k.(scKey).ShortcodePlaceholder] = renderedShortcode
   448  	}
   449  
   450  	return nil
   451  
   452  }
   453  
   454  func createShortcodeRenderers(shortcodes *orderedMap, p *PageWithoutContent) *orderedMap {
   455  
   456  	shortcodeRenderers := newOrderedMap()
   457  
   458  	for _, k := range shortcodes.Keys() {
   459  		v := shortcodes.getShortcode(k)
   460  		prepared := prepareShortcodeForPage(k.(string), v, nil, p)
   461  		for kk, vv := range prepared {
   462  			shortcodeRenderers.Add(kk, vv)
   463  		}
   464  	}
   465  
   466  	return shortcodeRenderers
   467  }
   468  
   469  var errShortCodeIllegalState = errors.New("Illegal shortcode state")
   470  
   471  // pageTokens state:
   472  // - before: positioned just before the shortcode start
   473  // - after: shortcode(s) consumed (plural when they are nested)
   474  func (s *shortcodeHandler) extractShortcode(ordinal int, pt *pageTokens, p *PageWithoutContent) (*shortcode, error) {
   475  	sc := &shortcode{ordinal: ordinal}
   476  	var isInner = false
   477  
   478  	var currItem item
   479  	var cnt = 0
   480  	var nestedOrdinal = 0
   481  
   482  Loop:
   483  	for {
   484  		currItem = pt.next()
   485  
   486  		switch currItem.typ {
   487  		case tLeftDelimScWithMarkup, tLeftDelimScNoMarkup:
   488  			next := pt.peek()
   489  			if next.typ == tScClose {
   490  				continue
   491  			}
   492  
   493  			if cnt > 0 {
   494  				// nested shortcode; append it to inner content
   495  				pt.backup3(currItem, next)
   496  				nested, err := s.extractShortcode(nestedOrdinal, pt, p)
   497  				nestedOrdinal++
   498  				if nested.name != "" {
   499  					s.nameSet[nested.name] = true
   500  				}
   501  				if err == nil {
   502  					sc.inner = append(sc.inner, nested)
   503  				} else {
   504  					return sc, err
   505  				}
   506  
   507  			} else {
   508  				sc.doMarkup = currItem.typ == tLeftDelimScWithMarkup
   509  			}
   510  
   511  			cnt++
   512  
   513  		case tRightDelimScWithMarkup, tRightDelimScNoMarkup:
   514  			// we trust the template on this:
   515  			// if there's no inner, we're done
   516  			if !isInner {
   517  				return sc, nil
   518  			}
   519  
   520  		case tScClose:
   521  			next := pt.peek()
   522  			if !isInner {
   523  				if next.typ == tError {
   524  					// return that error, more specific
   525  					continue
   526  				}
   527  				return sc, fmt.Errorf("Shortcode '%s' in page '%s' has no .Inner, yet a closing tag was provided", next.val, p.FullFilePath())
   528  			}
   529  			if next.typ == tRightDelimScWithMarkup || next.typ == tRightDelimScNoMarkup {
   530  				// self-closing
   531  				pt.consume(1)
   532  			} else {
   533  				pt.consume(2)
   534  			}
   535  
   536  			return sc, nil
   537  		case tText:
   538  			sc.inner = append(sc.inner, currItem.val)
   539  		case tScName:
   540  			sc.name = currItem.val
   541  			// We pick the first template for an arbitrary output format
   542  			// if more than one. It is "all inner or no inner".
   543  			tmpl := getShortcodeTemplateForTemplateKey(scKey{}, sc.name, p.s.Tmpl)
   544  			if tmpl == nil {
   545  				return sc, fmt.Errorf("Unable to locate template for shortcode %q in page %q", sc.name, p.Path())
   546  			}
   547  
   548  			var err error
   549  			isInner, err = isInnerShortcode(tmpl.(tpl.TemplateExecutor))
   550  			if err != nil {
   551  				return sc, fmt.Errorf("Failed to handle template for shortcode %q for page %q: %s", sc.name, p.Path(), err)
   552  			}
   553  
   554  		case tScParam:
   555  			if !pt.isValueNext() {
   556  				continue
   557  			} else if pt.peek().typ == tScParamVal {
   558  				// named params
   559  				if sc.params == nil {
   560  					params := make(map[string]string)
   561  					params[currItem.val] = pt.next().val
   562  					sc.params = params
   563  				} else {
   564  					if params, ok := sc.params.(map[string]string); ok {
   565  						params[currItem.val] = pt.next().val
   566  					} else {
   567  						return sc, errShortCodeIllegalState
   568  					}
   569  
   570  				}
   571  			} else {
   572  				// positional params
   573  				if sc.params == nil {
   574  					var params []string
   575  					params = append(params, currItem.val)
   576  					sc.params = params
   577  				} else {
   578  					if params, ok := sc.params.([]string); ok {
   579  						params = append(params, currItem.val)
   580  						sc.params = params
   581  					} else {
   582  						return sc, errShortCodeIllegalState
   583  					}
   584  
   585  				}
   586  			}
   587  
   588  		case tError, tEOF:
   589  			// handled by caller
   590  			pt.backup()
   591  			break Loop
   592  
   593  		}
   594  	}
   595  	return sc, nil
   596  }
   597  
   598  func (s *shortcodeHandler) extractShortcodes(stringToParse string, p *PageWithoutContent) (string, error) {
   599  
   600  	startIdx := strings.Index(stringToParse, "{{")
   601  
   602  	// short cut for docs with no shortcodes
   603  	if startIdx < 0 {
   604  		return stringToParse, nil
   605  	}
   606  
   607  	// the parser takes a string;
   608  	// since this is an internal API, it could make sense to use the mutable []byte all the way, but
   609  	// it seems that the time isn't really spent in the byte copy operations, and the impl. gets a lot cleaner
   610  	pt := &pageTokens{lexer: newShortcodeLexer("parse-page", stringToParse, pos(startIdx))}
   611  
   612  	result := bp.GetBuffer()
   613  	defer bp.PutBuffer(result)
   614  	//var result bytes.Buffer
   615  
   616  	// the parser is guaranteed to return items in proper order or fail, so …
   617  	// … it's safe to keep some "global" state
   618  	var currItem item
   619  	var currShortcode shortcode
   620  	var ordinal int
   621  
   622  Loop:
   623  	for {
   624  		currItem = pt.next()
   625  
   626  		switch currItem.typ {
   627  		case tText:
   628  			result.WriteString(currItem.val)
   629  		case tLeftDelimScWithMarkup, tLeftDelimScNoMarkup:
   630  			// let extractShortcode handle left delim (will do so recursively)
   631  			pt.backup()
   632  
   633  			currShortcode, err := s.extractShortcode(ordinal, pt, p)
   634  
   635  			if currShortcode.name != "" {
   636  				s.nameSet[currShortcode.name] = true
   637  			}
   638  
   639  			if err != nil {
   640  				return result.String(), err
   641  			}
   642  
   643  			if currShortcode.params == nil {
   644  				currShortcode.params = make([]string, 0)
   645  			}
   646  
   647  			placeHolder := s.createShortcodePlaceholder()
   648  			result.WriteString(placeHolder)
   649  			ordinal++
   650  			s.shortcodes.Add(placeHolder, currShortcode)
   651  		case tEOF:
   652  			break Loop
   653  		case tError:
   654  			err := fmt.Errorf("%s:%d: %s",
   655  				p.FullFilePath(), (p.lineNumRawContentStart() + pt.lexer.lineNum() - 1), currItem)
   656  			currShortcode.err = err
   657  			return result.String(), err
   658  		}
   659  	}
   660  
   661  	return result.String(), nil
   662  
   663  }
   664  
   665  // Replace prefixed shortcode tokens (HUGOSHORTCODE-1, HUGOSHORTCODE-2) with the real content.
   666  // Note: This function will rewrite the input slice.
   667  func replaceShortcodeTokens(source []byte, prefix string, replacements map[string]string) ([]byte, error) {
   668  
   669  	if len(replacements) == 0 {
   670  		return source, nil
   671  	}
   672  
   673  	start := 0
   674  
   675  	pre := []byte("HAHA" + prefix)
   676  	post := []byte("HBHB")
   677  	pStart := []byte("<p>")
   678  	pEnd := []byte("</p>")
   679  
   680  	k := bytes.Index(source[start:], pre)
   681  
   682  	for k != -1 {
   683  		j := start + k
   684  		postIdx := bytes.Index(source[j:], post)
   685  		if postIdx < 0 {
   686  			// this should never happen, but let the caller decide to panic or not
   687  			return nil, errors.New("illegal state in content; shortcode token missing end delim")
   688  		}
   689  
   690  		end := j + postIdx + 4
   691  
   692  		newVal := []byte(replacements[string(source[j:end])])
   693  
   694  		// Issue #1148: Check for wrapping p-tags <p>
   695  		if j >= 3 && bytes.Equal(source[j-3:j], pStart) {
   696  			if (k+4) < len(source) && bytes.Equal(source[end:end+4], pEnd) {
   697  				j -= 3
   698  				end += 4
   699  			}
   700  		}
   701  
   702  		// This and other cool slice tricks: https://github.com/golang/go/wiki/SliceTricks
   703  		source = append(source[:j], append(newVal, source[end:]...)...)
   704  		start = j
   705  		k = bytes.Index(source[start:], pre)
   706  
   707  	}
   708  
   709  	return source, nil
   710  }
   711  
   712  func getShortcodeTemplateForTemplateKey(key scKey, shortcodeName string, t tpl.TemplateFinder) tpl.Template {
   713  	isInnerShortcodeCache.RLock()
   714  	defer isInnerShortcodeCache.RUnlock()
   715  
   716  	var names []string
   717  
   718  	suffix := strings.ToLower(key.Suffix)
   719  	outFormat := strings.ToLower(key.OutputFormat)
   720  	lang := strings.ToLower(key.Lang)
   721  
   722  	if outFormat != "" && suffix != "" {
   723  		if lang != "" {
   724  			names = append(names, fmt.Sprintf("%s.%s.%s.%s", shortcodeName, lang, outFormat, suffix))
   725  		}
   726  		names = append(names, fmt.Sprintf("%s.%s.%s", shortcodeName, outFormat, suffix))
   727  	}
   728  
   729  	if suffix != "" {
   730  		if lang != "" {
   731  			names = append(names, fmt.Sprintf("%s.%s.%s", shortcodeName, lang, suffix))
   732  		}
   733  		names = append(names, fmt.Sprintf("%s.%s", shortcodeName, suffix))
   734  	}
   735  
   736  	names = append(names, shortcodeName)
   737  
   738  	for _, name := range names {
   739  
   740  		if x, found := t.Lookup("shortcodes/" + name); found {
   741  			return x
   742  		}
   743  		if x, found := t.Lookup("theme/shortcodes/" + name); found {
   744  			return x
   745  		}
   746  		if x, found := t.Lookup("_internal/shortcodes/" + name); found {
   747  			return x
   748  		}
   749  	}
   750  	return nil
   751  }
   752  
   753  func renderShortcodeWithPage(tmpl tpl.Template, data *ShortcodeWithPage) string {
   754  	buffer := bp.GetBuffer()
   755  	defer bp.PutBuffer(buffer)
   756  
   757  	isInnerShortcodeCache.RLock()
   758  	err := tmpl.Execute(buffer, data)
   759  	isInnerShortcodeCache.RUnlock()
   760  	if err != nil {
   761  		data.Page.s.Log.ERROR.Printf("error processing shortcode %q for page %q: %s", tmpl.Name(), data.Page.Path(), err)
   762  	}
   763  	return buffer.String()
   764  }