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