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