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