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