gitee.com/mirrors/Hugo-Go@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 }