github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/tpl/tplimpl/template.go (about) 1 // Copyright 2019 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 tplimpl 15 16 import ( 17 "io" 18 "os" 19 "path/filepath" 20 "reflect" 21 "regexp" 22 "sort" 23 "strings" 24 "sync" 25 "time" 26 "unicode" 27 "unicode/utf8" 28 29 "github.com/gohugoio/hugo/common/types" 30 31 "github.com/gohugoio/hugo/helpers" 32 33 "github.com/gohugoio/hugo/output" 34 35 "github.com/gohugoio/hugo/deps" 36 "github.com/spf13/afero" 37 38 "github.com/gohugoio/hugo/common/herrors" 39 "github.com/gohugoio/hugo/hugofs" 40 "github.com/gohugoio/hugo/hugofs/files" 41 "github.com/pkg/errors" 42 43 "github.com/gohugoio/hugo/tpl/tplimpl/embedded" 44 45 htmltemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate" 46 texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate" 47 48 "github.com/gohugoio/hugo/identity" 49 "github.com/gohugoio/hugo/tpl" 50 ) 51 52 const ( 53 textTmplNamePrefix = "_text/" 54 55 shortcodesPathPrefix = "shortcodes/" 56 internalPathPrefix = "_internal/" 57 baseFileBase = "baseof" 58 ) 59 60 // The identifiers may be truncated in the log, e.g. 61 // "executing "main" at <$scaled.SRelPermalin...>: can't evaluate field SRelPermalink in type *resource.Image" 62 var identifiersRe = regexp.MustCompile(`at \<(.*?)(\.{3})?\>:`) 63 64 var embeddedTemplatesAliases = map[string][]string{ 65 "shortcodes/twitter.html": {"shortcodes/tweet.html"}, 66 } 67 68 var ( 69 _ tpl.TemplateManager = (*templateExec)(nil) 70 _ tpl.TemplateHandler = (*templateExec)(nil) 71 _ tpl.TemplateFuncGetter = (*templateExec)(nil) 72 _ tpl.TemplateFinder = (*templateExec)(nil) 73 74 _ tpl.Template = (*templateState)(nil) 75 _ tpl.Info = (*templateState)(nil) 76 ) 77 78 var baseTemplateDefineRe = regexp.MustCompile(`^{{-?\s*define`) 79 80 // needsBaseTemplate returns true if the first non-comment template block is a 81 // define block. 82 // If a base template does not exist, we will handle that when it's used. 83 func needsBaseTemplate(templ string) bool { 84 idx := -1 85 inComment := false 86 for i := 0; i < len(templ); { 87 if !inComment && strings.HasPrefix(templ[i:], "{{/*") { 88 inComment = true 89 i += 4 90 } else if inComment && strings.HasPrefix(templ[i:], "*/}}") { 91 inComment = false 92 i += 4 93 } else { 94 r, size := utf8.DecodeRuneInString(templ[i:]) 95 if !inComment { 96 if strings.HasPrefix(templ[i:], "{{") { 97 idx = i 98 break 99 } else if !unicode.IsSpace(r) { 100 break 101 } 102 } 103 i += size 104 } 105 } 106 107 if idx == -1 { 108 return false 109 } 110 111 return baseTemplateDefineRe.MatchString(templ[idx:]) 112 } 113 114 func newIdentity(name string) identity.Manager { 115 return identity.NewManager(identity.NewPathIdentity(files.ComponentFolderLayouts, name)) 116 } 117 118 func newStandaloneTextTemplate(funcs map[string]interface{}) tpl.TemplateParseFinder { 119 return &textTemplateWrapperWithLock{ 120 RWMutex: &sync.RWMutex{}, 121 Template: texttemplate.New("").Funcs(funcs), 122 } 123 } 124 125 func newTemplateExec(d *deps.Deps) (*templateExec, error) { 126 exec, funcs := newTemplateExecuter(d) 127 funcMap := make(map[string]interface{}) 128 for k, v := range funcs { 129 funcMap[k] = v.Interface() 130 } 131 132 h := &templateHandler{ 133 nameBaseTemplateName: make(map[string]string), 134 transformNotFound: make(map[string]*templateState), 135 identityNotFound: make(map[string][]identity.Manager), 136 137 shortcodes: make(map[string]*shortcodeTemplates), 138 templateInfo: make(map[string]tpl.Info), 139 baseof: make(map[string]templateInfo), 140 needsBaseof: make(map[string]templateInfo), 141 142 main: newTemplateNamespace(funcMap), 143 144 Deps: d, 145 layoutHandler: output.NewLayoutHandler(), 146 layoutsFs: d.BaseFs.Layouts.Fs, 147 layoutTemplateCache: make(map[layoutCacheKey]tpl.Template), 148 } 149 150 if err := h.loadEmbedded(); err != nil { 151 return nil, err 152 } 153 154 if err := h.loadTemplates(); err != nil { 155 return nil, err 156 } 157 158 e := &templateExec{ 159 d: d, 160 executor: exec, 161 funcs: funcs, 162 templateHandler: h, 163 } 164 165 d.SetTmpl(e) 166 d.SetTextTmpl(newStandaloneTextTemplate(funcMap)) 167 168 if d.WithTemplate != nil { 169 if err := d.WithTemplate(e); err != nil { 170 return nil, err 171 } 172 } 173 174 return e, nil 175 } 176 177 func newTemplateNamespace(funcs map[string]interface{}) *templateNamespace { 178 return &templateNamespace{ 179 prototypeHTML: htmltemplate.New("").Funcs(funcs), 180 prototypeText: texttemplate.New("").Funcs(funcs), 181 templateStateMap: &templateStateMap{ 182 templates: make(map[string]*templateState), 183 }, 184 } 185 } 186 187 func newTemplateState(templ tpl.Template, info templateInfo) *templateState { 188 return &templateState{ 189 info: info, 190 typ: info.resolveType(), 191 Template: templ, 192 Manager: newIdentity(info.name), 193 parseInfo: tpl.DefaultParseInfo, 194 } 195 } 196 197 type layoutCacheKey struct { 198 d output.LayoutDescriptor 199 f string 200 } 201 202 type templateExec struct { 203 d *deps.Deps 204 executor texttemplate.Executer 205 funcs map[string]reflect.Value 206 207 *templateHandler 208 } 209 210 func (t templateExec) Clone(d *deps.Deps) *templateExec { 211 exec, funcs := newTemplateExecuter(d) 212 t.executor = exec 213 t.funcs = funcs 214 t.d = d 215 return &t 216 } 217 218 func (t *templateExec) Execute(templ tpl.Template, wr io.Writer, data interface{}) error { 219 if rlocker, ok := templ.(types.RLocker); ok { 220 rlocker.RLock() 221 defer rlocker.RUnlock() 222 } 223 if t.Metrics != nil { 224 defer t.Metrics.MeasureSince(templ.Name(), time.Now()) 225 } 226 227 execErr := t.executor.Execute(templ, wr, data) 228 if execErr != nil { 229 execErr = t.addFileContext(templ, execErr) 230 } 231 return execErr 232 } 233 234 func (t *templateExec) GetFunc(name string) (reflect.Value, bool) { 235 v, found := t.funcs[name] 236 return v, found 237 } 238 239 func (t *templateExec) MarkReady() error { 240 var err error 241 t.readyInit.Do(func() { 242 // We only need the clones if base templates are in use. 243 if len(t.needsBaseof) > 0 { 244 err = t.main.createPrototypes() 245 } 246 }) 247 248 return err 249 } 250 251 type templateHandler struct { 252 main *templateNamespace 253 needsBaseof map[string]templateInfo 254 baseof map[string]templateInfo 255 256 readyInit sync.Once 257 258 // This is the filesystem to load the templates from. All the templates are 259 // stored in the root of this filesystem. 260 layoutsFs afero.Fs 261 262 layoutHandler *output.LayoutHandler 263 264 layoutTemplateCache map[layoutCacheKey]tpl.Template 265 layoutTemplateCacheMu sync.RWMutex 266 267 *deps.Deps 268 269 // Used to get proper filenames in errors 270 nameBaseTemplateName map[string]string 271 272 // Holds name and source of template definitions not found during the first 273 // AST transformation pass. 274 transformNotFound map[string]*templateState 275 276 // Holds identities of templates not found during first pass. 277 identityNotFound map[string][]identity.Manager 278 279 // shortcodes maps shortcode name to template variants 280 // (language, output format etc.) of that shortcode. 281 shortcodes map[string]*shortcodeTemplates 282 283 // templateInfo maps template name to some additional information about that template. 284 // Note that for shortcodes that same information is embedded in the 285 // shortcodeTemplates type. 286 templateInfo map[string]tpl.Info 287 } 288 289 // AddTemplate parses and adds a template to the collection. 290 // Templates with name prefixed with "_text" will be handled as plain 291 // text templates. 292 func (t *templateHandler) AddTemplate(name, tpl string) error { 293 templ, err := t.addTemplateTo(t.newTemplateInfo(name, tpl), t.main) 294 if err == nil { 295 t.applyTemplateTransformers(t.main, templ) 296 } 297 return err 298 } 299 300 func (t *templateHandler) Lookup(name string) (tpl.Template, bool) { 301 templ, found := t.main.Lookup(name) 302 if found { 303 return templ, true 304 } 305 306 return nil, false 307 } 308 309 func (t *templateHandler) LookupLayout(d output.LayoutDescriptor, f output.Format) (tpl.Template, bool, error) { 310 key := layoutCacheKey{d, f.Name} 311 t.layoutTemplateCacheMu.RLock() 312 if cacheVal, found := t.layoutTemplateCache[key]; found { 313 t.layoutTemplateCacheMu.RUnlock() 314 return cacheVal, true, nil 315 } 316 t.layoutTemplateCacheMu.RUnlock() 317 318 t.layoutTemplateCacheMu.Lock() 319 defer t.layoutTemplateCacheMu.Unlock() 320 321 templ, found, err := t.findLayout(d, f) 322 if err == nil && found { 323 t.layoutTemplateCache[key] = templ 324 return templ, true, nil 325 } 326 327 return nil, false, err 328 } 329 330 // This currently only applies to shortcodes and what we get here is the 331 // shortcode name. 332 func (t *templateHandler) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) { 333 name = templateBaseName(templateShortcode, name) 334 s, found := t.shortcodes[name] 335 if !found { 336 return nil, false, false 337 } 338 339 sv, found := s.fromVariants(variants) 340 if !found { 341 return nil, false, false 342 } 343 344 more := len(s.variants) > 1 345 346 return sv.ts, true, more 347 } 348 349 // LookupVariants returns all variants of name, nil if none found. 350 func (t *templateHandler) LookupVariants(name string) []tpl.Template { 351 name = templateBaseName(templateShortcode, name) 352 s, found := t.shortcodes[name] 353 if !found { 354 return nil 355 } 356 357 variants := make([]tpl.Template, len(s.variants)) 358 for i := 0; i < len(variants); i++ { 359 variants[i] = s.variants[i].ts 360 } 361 362 return variants 363 } 364 365 func (t *templateHandler) HasTemplate(name string) bool { 366 if _, found := t.baseof[name]; found { 367 return true 368 } 369 370 if _, found := t.needsBaseof[name]; found { 371 return true 372 } 373 374 _, found := t.Lookup(name) 375 return found 376 } 377 378 func (t *templateHandler) findLayout(d output.LayoutDescriptor, f output.Format) (tpl.Template, bool, error) { 379 layouts, _ := t.layoutHandler.For(d, f) 380 for _, name := range layouts { 381 templ, found := t.main.Lookup(name) 382 if found { 383 return templ, true, nil 384 } 385 386 overlay, found := t.needsBaseof[name] 387 388 if !found { 389 continue 390 } 391 392 d.Baseof = true 393 baseLayouts, _ := t.layoutHandler.For(d, f) 394 var base templateInfo 395 found = false 396 for _, l := range baseLayouts { 397 base, found = t.baseof[l] 398 if found { 399 break 400 } 401 } 402 403 templ, err := t.applyBaseTemplate(overlay, base) 404 if err != nil { 405 return nil, false, err 406 } 407 408 ts := newTemplateState(templ, overlay) 409 410 if found { 411 ts.baseInfo = base 412 413 // Add the base identity to detect changes 414 ts.Add(identity.NewPathIdentity(files.ComponentFolderLayouts, base.name)) 415 } 416 417 t.applyTemplateTransformers(t.main, ts) 418 419 if err := t.extractPartials(ts.Template); err != nil { 420 return nil, false, err 421 } 422 423 return ts, true, nil 424 425 } 426 427 return nil, false, nil 428 } 429 430 func (t *templateHandler) findTemplate(name string) *templateState { 431 if templ, found := t.Lookup(name); found { 432 return templ.(*templateState) 433 } 434 return nil 435 } 436 437 func (t *templateHandler) newTemplateInfo(name, tpl string) templateInfo { 438 var isText bool 439 name, isText = t.nameIsText(name) 440 return templateInfo{ 441 name: name, 442 isText: isText, 443 template: tpl, 444 } 445 } 446 447 func (t *templateHandler) addFileContext(templ tpl.Template, inerr error) error { 448 if strings.HasPrefix(templ.Name(), "_internal") { 449 return inerr 450 } 451 452 ts, ok := templ.(*templateState) 453 if !ok { 454 return inerr 455 } 456 457 //lint:ignore ST1008 the error is the main result 458 checkFilename := func(info templateInfo, inErr error) (error, bool) { 459 if info.filename == "" { 460 return inErr, false 461 } 462 463 lineMatcher := func(m herrors.LineMatcher) bool { 464 if m.Position.LineNumber != m.LineNumber { 465 return false 466 } 467 468 identifiers := t.extractIdentifiers(m.Error.Error()) 469 470 for _, id := range identifiers { 471 if strings.Contains(m.Line, id) { 472 return true 473 } 474 } 475 return false 476 } 477 478 f, err := t.layoutsFs.Open(info.filename) 479 if err != nil { 480 return inErr, false 481 } 482 defer f.Close() 483 484 fe, ok := herrors.WithFileContext(inErr, info.realFilename, f, lineMatcher) 485 if ok { 486 return fe, true 487 } 488 return inErr, false 489 } 490 491 inerr = errors.Wrap(inerr, "execute of template failed") 492 493 if err, ok := checkFilename(ts.info, inerr); ok { 494 return err 495 } 496 497 err, _ := checkFilename(ts.baseInfo, inerr) 498 499 return err 500 } 501 502 func (t *templateHandler) addShortcodeVariant(ts *templateState) { 503 name := ts.Name() 504 base := templateBaseName(templateShortcode, name) 505 506 shortcodename, variants := templateNameAndVariants(base) 507 508 templs, found := t.shortcodes[shortcodename] 509 if !found { 510 templs = &shortcodeTemplates{} 511 t.shortcodes[shortcodename] = templs 512 } 513 514 sv := shortcodeVariant{variants: variants, ts: ts} 515 516 i := templs.indexOf(variants) 517 518 if i != -1 { 519 // Only replace if it's an override of an internal template. 520 if !isInternal(name) { 521 templs.variants[i] = sv 522 } 523 } else { 524 templs.variants = append(templs.variants, sv) 525 } 526 } 527 528 func (t *templateHandler) addTemplateFile(name, path string) error { 529 getTemplate := func(filename string) (templateInfo, error) { 530 fs := t.Layouts.Fs 531 b, err := afero.ReadFile(fs, filename) 532 if err != nil { 533 return templateInfo{filename: filename, fs: fs}, err 534 } 535 536 s := removeLeadingBOM(string(b)) 537 538 realFilename := filename 539 if fi, err := fs.Stat(filename); err == nil { 540 if fim, ok := fi.(hugofs.FileMetaInfo); ok { 541 realFilename = fim.Meta().Filename 542 } 543 } 544 545 var isText bool 546 name, isText = t.nameIsText(name) 547 548 return templateInfo{ 549 name: name, 550 isText: isText, 551 template: s, 552 filename: filename, 553 realFilename: realFilename, 554 fs: fs, 555 }, nil 556 } 557 558 tinfo, err := getTemplate(path) 559 if err != nil { 560 return err 561 } 562 563 if isBaseTemplatePath(name) { 564 // Store it for later. 565 t.baseof[name] = tinfo 566 return nil 567 } 568 569 needsBaseof := !t.noBaseNeeded(name) && needsBaseTemplate(tinfo.template) 570 if needsBaseof { 571 t.needsBaseof[name] = tinfo 572 return nil 573 } 574 575 templ, err := t.addTemplateTo(tinfo, t.main) 576 if err != nil { 577 return tinfo.errWithFileContext("parse failed", err) 578 } 579 t.applyTemplateTransformers(t.main, templ) 580 581 return nil 582 } 583 584 func (t *templateHandler) addTemplateTo(info templateInfo, to *templateNamespace) (*templateState, error) { 585 return to.parse(info) 586 } 587 588 func (t *templateHandler) applyBaseTemplate(overlay, base templateInfo) (tpl.Template, error) { 589 if overlay.isText { 590 var ( 591 templ = t.main.prototypeTextClone.New(overlay.name) 592 err error 593 ) 594 595 if !base.IsZero() { 596 templ, err = templ.Parse(base.template) 597 if err != nil { 598 return nil, base.errWithFileContext("parse failed", err) 599 } 600 } 601 602 templ, err = texttemplate.Must(templ.Clone()).Parse(overlay.template) 603 if err != nil { 604 return nil, overlay.errWithFileContext("parse failed", err) 605 } 606 607 // The extra lookup is a workaround, see 608 // * https://github.com/golang/go/issues/16101 609 // * https://github.com/gohugoio/hugo/issues/2549 610 // templ = templ.Lookup(templ.Name()) 611 612 return templ, nil 613 } 614 615 var ( 616 templ = t.main.prototypeHTMLClone.New(overlay.name) 617 err error 618 ) 619 620 if !base.IsZero() { 621 templ, err = templ.Parse(base.template) 622 if err != nil { 623 return nil, base.errWithFileContext("parse failed", err) 624 } 625 } 626 627 templ, err = htmltemplate.Must(templ.Clone()).Parse(overlay.template) 628 if err != nil { 629 return nil, overlay.errWithFileContext("parse failed", err) 630 } 631 632 // The extra lookup is a workaround, see 633 // * https://github.com/golang/go/issues/16101 634 // * https://github.com/gohugoio/hugo/issues/2549 635 templ = templ.Lookup(templ.Name()) 636 637 return templ, err 638 } 639 640 func (t *templateHandler) applyTemplateTransformers(ns *templateNamespace, ts *templateState) (*templateContext, error) { 641 c, err := applyTemplateTransformers(ts, ns.newTemplateLookup(ts)) 642 if err != nil { 643 return nil, err 644 } 645 646 for k := range c.templateNotFound { 647 t.transformNotFound[k] = ts 648 t.identityNotFound[k] = append(t.identityNotFound[k], c.t) 649 } 650 651 for k := range c.identityNotFound { 652 t.identityNotFound[k] = append(t.identityNotFound[k], c.t) 653 } 654 655 return c, err 656 } 657 658 func (t *templateHandler) extractIdentifiers(line string) []string { 659 m := identifiersRe.FindAllStringSubmatch(line, -1) 660 identifiers := make([]string, len(m)) 661 for i := 0; i < len(m); i++ { 662 identifiers[i] = m[i][1] 663 } 664 return identifiers 665 } 666 667 func (t *templateHandler) loadEmbedded() error { 668 for _, kv := range embedded.EmbeddedTemplates { 669 name, templ := kv[0], kv[1] 670 if err := t.AddTemplate(internalPathPrefix+name, templ); err != nil { 671 return err 672 } 673 if aliases, found := embeddedTemplatesAliases[name]; found { 674 // TODO(bep) avoid reparsing these aliases 675 for _, alias := range aliases { 676 alias = internalPathPrefix + alias 677 if err := t.AddTemplate(alias, templ); err != nil { 678 return err 679 } 680 } 681 } 682 } 683 return nil 684 } 685 686 func (t *templateHandler) loadTemplates() error { 687 walker := func(path string, fi hugofs.FileMetaInfo, err error) error { 688 if err != nil || fi.IsDir() { 689 return err 690 } 691 692 if isDotFile(path) || isBackupFile(path) { 693 return nil 694 } 695 696 name := strings.TrimPrefix(filepath.ToSlash(path), "/") 697 filename := filepath.Base(path) 698 outputFormat, found := t.OutputFormatsConfig.FromFilename(filename) 699 700 if found && outputFormat.IsPlainText { 701 name = textTmplNamePrefix + name 702 } 703 704 if err := t.addTemplateFile(name, path); err != nil { 705 return err 706 } 707 708 return nil 709 } 710 711 if err := helpers.SymbolicWalk(t.Layouts.Fs, "", walker); err != nil { 712 if !os.IsNotExist(err) { 713 return err 714 } 715 return nil 716 } 717 718 return nil 719 } 720 721 func (t *templateHandler) nameIsText(name string) (string, bool) { 722 isText := strings.HasPrefix(name, textTmplNamePrefix) 723 if isText { 724 name = strings.TrimPrefix(name, textTmplNamePrefix) 725 } 726 return name, isText 727 } 728 729 func (t *templateHandler) noBaseNeeded(name string) bool { 730 if strings.HasPrefix(name, "shortcodes/") || strings.HasPrefix(name, "partials/") { 731 return true 732 } 733 return strings.Contains(name, "_markup/") 734 } 735 736 func (t *templateHandler) extractPartials(templ tpl.Template) error { 737 templs := templates(templ) 738 for _, templ := range templs { 739 if templ.Name() == "" || !strings.HasPrefix(templ.Name(), "partials/") { 740 continue 741 } 742 743 ts := newTemplateState(templ, templateInfo{name: templ.Name()}) 744 ts.typ = templatePartial 745 746 t.main.mu.RLock() 747 _, found := t.main.templates[templ.Name()] 748 t.main.mu.RUnlock() 749 750 if !found { 751 t.main.mu.Lock() 752 // This is a template defined inline. 753 _, err := applyTemplateTransformers(ts, t.main.newTemplateLookup(ts)) 754 if err != nil { 755 t.main.mu.Unlock() 756 return err 757 } 758 t.main.templates[templ.Name()] = ts 759 t.main.mu.Unlock() 760 761 } 762 } 763 764 return nil 765 } 766 767 func (t *templateHandler) postTransform() error { 768 defineCheckedHTML := false 769 defineCheckedText := false 770 771 for _, v := range t.main.templates { 772 if v.typ == templateShortcode { 773 t.addShortcodeVariant(v) 774 } 775 776 if defineCheckedHTML && defineCheckedText { 777 continue 778 } 779 780 isText := isText(v.Template) 781 if isText { 782 if defineCheckedText { 783 continue 784 } 785 defineCheckedText = true 786 } else { 787 if defineCheckedHTML { 788 continue 789 } 790 defineCheckedHTML = true 791 } 792 793 if err := t.extractPartials(v.Template); err != nil { 794 return err 795 } 796 } 797 798 for name, source := range t.transformNotFound { 799 lookup := t.main.newTemplateLookup(source) 800 templ := lookup(name) 801 if templ != nil { 802 _, err := applyTemplateTransformers(templ, lookup) 803 if err != nil { 804 return err 805 } 806 } 807 } 808 809 for k, v := range t.identityNotFound { 810 ts := t.findTemplate(k) 811 if ts != nil { 812 for _, im := range v { 813 im.Add(ts) 814 } 815 } 816 } 817 818 for _, v := range t.shortcodes { 819 sort.Slice(v.variants, func(i, j int) bool { 820 v1, v2 := v.variants[i], v.variants[j] 821 name1, name2 := v1.ts.Name(), v2.ts.Name() 822 isHTMl1, isHTML2 := strings.HasSuffix(name1, "html"), strings.HasSuffix(name2, "html") 823 824 // There will be a weighted selection later, but make 825 // sure these are sorted to get a stable selection for 826 // output formats missing specific templates. 827 // Prefer HTML. 828 if isHTMl1 || isHTML2 && !(isHTMl1 && isHTML2) { 829 return isHTMl1 830 } 831 832 return name1 < name2 833 }) 834 } 835 836 return nil 837 } 838 839 type templateNamespace struct { 840 prototypeText *texttemplate.Template 841 prototypeHTML *htmltemplate.Template 842 prototypeTextClone *texttemplate.Template 843 prototypeHTMLClone *htmltemplate.Template 844 845 *templateStateMap 846 } 847 848 func (t templateNamespace) Clone() *templateNamespace { 849 t.mu.Lock() 850 defer t.mu.Unlock() 851 852 t.templateStateMap = &templateStateMap{ 853 templates: make(map[string]*templateState), 854 } 855 856 t.prototypeText = texttemplate.Must(t.prototypeText.Clone()) 857 t.prototypeHTML = htmltemplate.Must(t.prototypeHTML.Clone()) 858 859 return &t 860 } 861 862 func (t *templateNamespace) Lookup(name string) (tpl.Template, bool) { 863 t.mu.RLock() 864 defer t.mu.RUnlock() 865 866 templ, found := t.templates[name] 867 if !found { 868 return nil, false 869 } 870 871 return templ, found 872 } 873 874 func (t *templateNamespace) createPrototypes() error { 875 t.prototypeTextClone = texttemplate.Must(t.prototypeText.Clone()) 876 t.prototypeHTMLClone = htmltemplate.Must(t.prototypeHTML.Clone()) 877 878 return nil 879 } 880 881 func (t *templateNamespace) newTemplateLookup(in *templateState) func(name string) *templateState { 882 return func(name string) *templateState { 883 if templ, found := t.templates[name]; found { 884 if templ.isText() != in.isText() { 885 return nil 886 } 887 return templ 888 } 889 if templ, found := findTemplateIn(name, in); found { 890 return newTemplateState(templ, templateInfo{name: templ.Name()}) 891 } 892 return nil 893 } 894 } 895 896 func (t *templateNamespace) parse(info templateInfo) (*templateState, error) { 897 t.mu.Lock() 898 defer t.mu.Unlock() 899 900 if info.isText { 901 prototype := t.prototypeText 902 903 templ, err := prototype.New(info.name).Parse(info.template) 904 if err != nil { 905 return nil, err 906 } 907 908 ts := newTemplateState(templ, info) 909 910 t.templates[info.name] = ts 911 912 return ts, nil 913 } 914 915 prototype := t.prototypeHTML 916 917 templ, err := prototype.New(info.name).Parse(info.template) 918 if err != nil { 919 return nil, err 920 } 921 922 ts := newTemplateState(templ, info) 923 924 t.templates[info.name] = ts 925 926 return ts, nil 927 } 928 929 type templateState struct { 930 tpl.Template 931 932 typ templateType 933 parseInfo tpl.ParseInfo 934 identity.Manager 935 936 info templateInfo 937 baseInfo templateInfo // Set when a base template is used. 938 } 939 940 func (t *templateState) ParseInfo() tpl.ParseInfo { 941 return t.parseInfo 942 } 943 944 func (t *templateState) isText() bool { 945 return isText(t.Template) 946 } 947 948 func isText(templ tpl.Template) bool { 949 _, isText := templ.(*texttemplate.Template) 950 return isText 951 } 952 953 type templateStateMap struct { 954 mu sync.RWMutex 955 templates map[string]*templateState 956 } 957 958 type templateWrapperWithLock struct { 959 *sync.RWMutex 960 tpl.Template 961 } 962 963 type textTemplateWrapperWithLock struct { 964 *sync.RWMutex 965 *texttemplate.Template 966 } 967 968 func (t *textTemplateWrapperWithLock) Lookup(name string) (tpl.Template, bool) { 969 t.RLock() 970 templ := t.Template.Lookup(name) 971 t.RUnlock() 972 if templ == nil { 973 return nil, false 974 } 975 return &textTemplateWrapperWithLock{ 976 RWMutex: t.RWMutex, 977 Template: templ, 978 }, true 979 } 980 981 func (t *textTemplateWrapperWithLock) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) { 982 panic("not supported") 983 } 984 985 func (t *textTemplateWrapperWithLock) LookupVariants(name string) []tpl.Template { 986 panic("not supported") 987 } 988 989 func (t *textTemplateWrapperWithLock) Parse(name, tpl string) (tpl.Template, error) { 990 t.Lock() 991 defer t.Unlock() 992 return t.Template.New(name).Parse(tpl) 993 } 994 995 func isBackupFile(path string) bool { 996 return path[len(path)-1] == '~' 997 } 998 999 func isBaseTemplatePath(path string) bool { 1000 return strings.Contains(filepath.Base(path), baseFileBase) 1001 } 1002 1003 func isDotFile(path string) bool { 1004 return filepath.Base(path)[0] == '.' 1005 } 1006 1007 func removeLeadingBOM(s string) string { 1008 const bom = '\ufeff' 1009 1010 for i, r := range s { 1011 if i == 0 && r != bom { 1012 return s 1013 } 1014 if i > 0 { 1015 return s[i:] 1016 } 1017 } 1018 1019 return s 1020 } 1021 1022 // resolves _internal/shortcodes/param.html => param.html etc. 1023 func templateBaseName(typ templateType, name string) string { 1024 name = strings.TrimPrefix(name, internalPathPrefix) 1025 switch typ { 1026 case templateShortcode: 1027 return strings.TrimPrefix(name, shortcodesPathPrefix) 1028 default: 1029 panic("not implemented") 1030 } 1031 } 1032 1033 func unwrap(templ tpl.Template) tpl.Template { 1034 if ts, ok := templ.(*templateState); ok { 1035 return ts.Template 1036 } 1037 return templ 1038 } 1039 1040 func templates(in tpl.Template) []tpl.Template { 1041 var templs []tpl.Template 1042 in = unwrap(in) 1043 if textt, ok := in.(*texttemplate.Template); ok { 1044 for _, t := range textt.Templates() { 1045 templs = append(templs, t) 1046 } 1047 } 1048 1049 if htmlt, ok := in.(*htmltemplate.Template); ok { 1050 for _, t := range htmlt.Templates() { 1051 templs = append(templs, t) 1052 } 1053 } 1054 1055 return templs 1056 }