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