github.com/olliephillips/hugo@v0.42.2/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 }