github.com/fighterlyt/hugo@v0.47.1/tpl/tplimpl/template.go (about) 1 // Copyright 2017-present 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 "fmt" 18 "html/template" 19 "path" 20 "strings" 21 texttemplate "text/template" 22 23 "github.com/gohugoio/hugo/tpl/tplimpl/embedded" 24 25 "github.com/eknkc/amber" 26 27 "os" 28 29 "github.com/gohugoio/hugo/output" 30 31 "path/filepath" 32 "sync" 33 34 "github.com/gohugoio/hugo/deps" 35 "github.com/gohugoio/hugo/helpers" 36 "github.com/gohugoio/hugo/tpl" 37 "github.com/spf13/afero" 38 ) 39 40 const ( 41 textTmplNamePrefix = "_text/" 42 ) 43 44 var ( 45 _ tpl.TemplateHandler = (*templateHandler)(nil) 46 _ tpl.TemplateDebugger = (*templateHandler)(nil) 47 _ tpl.TemplateFuncsGetter = (*templateHandler)(nil) 48 _ tpl.TemplateTestMocker = (*templateHandler)(nil) 49 _ tpl.TemplateFinder = (*htmlTemplates)(nil) 50 _ tpl.TemplateFinder = (*textTemplates)(nil) 51 _ templateLoader = (*htmlTemplates)(nil) 52 _ templateLoader = (*textTemplates)(nil) 53 _ templateLoader = (*templateHandler)(nil) 54 _ templateFuncsterTemplater = (*htmlTemplates)(nil) 55 _ templateFuncsterTemplater = (*textTemplates)(nil) 56 ) 57 58 // Protecting global map access (Amber) 59 var amberMu sync.Mutex 60 61 type templateErr struct { 62 name string 63 err error 64 } 65 66 type templateLoader interface { 67 handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (string, error)) error 68 addTemplate(name, tpl string) error 69 addLateTemplate(name, tpl string) error 70 } 71 72 type templateFuncsterTemplater interface { 73 templateFuncsterSetter 74 tpl.TemplateFinder 75 setFuncs(funcMap map[string]interface{}) 76 } 77 78 type templateFuncsterSetter interface { 79 setTemplateFuncster(f *templateFuncster) 80 } 81 82 // templateHandler holds the templates in play. 83 // It implements the templateLoader and tpl.TemplateHandler interfaces. 84 type templateHandler struct { 85 mu sync.Mutex 86 87 // text holds all the pure text templates. 88 text *textTemplates 89 html *htmlTemplates 90 91 extTextTemplates []*textTemplate 92 93 amberFuncMap template.FuncMap 94 95 errors []*templateErr 96 97 // This is the filesystem to load the templates from. All the templates are 98 // stored in the root of this filesystem. 99 layoutsFs afero.Fs 100 101 *deps.Deps 102 } 103 104 // NewTextTemplate provides a text template parser that has all the Hugo 105 // template funcs etc. built-in. 106 func (t *templateHandler) NewTextTemplate() tpl.TemplateParseFinder { 107 t.mu.Lock() 108 t.mu.Unlock() 109 110 tt := &textTemplate{t: texttemplate.New("")} 111 t.extTextTemplates = append(t.extTextTemplates, tt) 112 113 return tt 114 115 } 116 117 func (t *templateHandler) addError(name string, err error) { 118 t.errors = append(t.errors, &templateErr{name, err}) 119 } 120 121 func (t *templateHandler) Debug() { 122 fmt.Println("HTML templates:\n", t.html.t.DefinedTemplates()) 123 fmt.Println("\n\nText templates:\n", t.text.t.DefinedTemplates()) 124 } 125 126 // PrintErrors prints the accumulated errors as ERROR to the log. 127 func (t *templateHandler) PrintErrors() { 128 for _, e := range t.errors { 129 t.Log.ERROR.Println(e.name, ":", e.err) 130 } 131 } 132 133 // Lookup tries to find a template with the given name in both template 134 // collections: First HTML, then the plain text template collection. 135 func (t *templateHandler) Lookup(name string) (tpl.Template, bool) { 136 137 if strings.HasPrefix(name, textTmplNamePrefix) { 138 // The caller has explicitly asked for a text template, so only look 139 // in the text template collection. 140 // The templates are stored without the prefix identificator. 141 name = strings.TrimPrefix(name, textTmplNamePrefix) 142 143 return t.text.Lookup(name) 144 } 145 146 // Look in both 147 if te, found := t.html.Lookup(name); found { 148 return te, true 149 } 150 151 return t.text.Lookup(name) 152 153 } 154 155 func (t *templateHandler) clone(d *deps.Deps) *templateHandler { 156 c := &templateHandler{ 157 Deps: d, 158 layoutsFs: d.BaseFs.Layouts.Fs, 159 html: &htmlTemplates{t: template.Must(t.html.t.Clone()), overlays: make(map[string]*template.Template)}, 160 text: &textTemplates{textTemplate: &textTemplate{t: texttemplate.Must(t.text.t.Clone())}, overlays: make(map[string]*texttemplate.Template)}, 161 errors: make([]*templateErr, 0), 162 } 163 164 d.Tmpl = c 165 166 c.initFuncs() 167 168 for k, v := range t.html.overlays { 169 vc := template.Must(v.Clone()) 170 // The extra lookup is a workaround, see 171 // * https://github.com/golang/go/issues/16101 172 // * https://github.com/gohugoio/hugo/issues/2549 173 vc = vc.Lookup(vc.Name()) 174 vc.Funcs(c.html.funcster.funcMap) 175 c.html.overlays[k] = vc 176 } 177 178 for k, v := range t.text.overlays { 179 vc := texttemplate.Must(v.Clone()) 180 vc = vc.Lookup(vc.Name()) 181 vc.Funcs(texttemplate.FuncMap(c.text.funcster.funcMap)) 182 c.text.overlays[k] = vc 183 } 184 185 return c 186 187 } 188 189 func newTemplateAdapter(deps *deps.Deps) *templateHandler { 190 htmlT := &htmlTemplates{ 191 t: template.New(""), 192 overlays: make(map[string]*template.Template), 193 } 194 textT := &textTemplates{ 195 textTemplate: &textTemplate{t: texttemplate.New("")}, 196 overlays: make(map[string]*texttemplate.Template), 197 } 198 return &templateHandler{ 199 Deps: deps, 200 layoutsFs: deps.BaseFs.Layouts.Fs, 201 html: htmlT, 202 text: textT, 203 errors: make([]*templateErr, 0), 204 } 205 206 } 207 208 type htmlTemplates struct { 209 funcster *templateFuncster 210 211 t *template.Template 212 213 // This looks, and is, strange. 214 // The clone is used by non-renderable content pages, and these need to be 215 // re-parsed on content change, and to avoid the 216 // "cannot Parse after Execute" error, we need to re-clone it from the original clone. 217 clone *template.Template 218 cloneClone *template.Template 219 220 // a separate storage for the overlays created from cloned master templates. 221 // note: No mutex protection, so we add these in one Go routine, then just read. 222 overlays map[string]*template.Template 223 } 224 225 func (t *htmlTemplates) setTemplateFuncster(f *templateFuncster) { 226 t.funcster = f 227 } 228 229 func (t *htmlTemplates) Lookup(name string) (tpl.Template, bool) { 230 templ := t.lookup(name) 231 if templ == nil { 232 return nil, false 233 } 234 return &tpl.TemplateAdapter{Template: templ, Metrics: t.funcster.Deps.Metrics}, true 235 } 236 237 func (t *htmlTemplates) lookup(name string) *template.Template { 238 239 // Need to check in the overlay registry first as it will also be found below. 240 if t.overlays != nil { 241 if templ, ok := t.overlays[name]; ok { 242 return templ 243 } 244 } 245 246 if templ := t.t.Lookup(name); templ != nil { 247 return templ 248 } 249 250 if t.clone != nil { 251 return t.clone.Lookup(name) 252 } 253 254 return nil 255 } 256 257 func (t *textTemplates) setTemplateFuncster(f *templateFuncster) { 258 t.funcster = f 259 } 260 261 type textTemplates struct { 262 *textTemplate 263 funcster *templateFuncster 264 clone *texttemplate.Template 265 cloneClone *texttemplate.Template 266 267 overlays map[string]*texttemplate.Template 268 } 269 270 func (t *textTemplates) Lookup(name string) (tpl.Template, bool) { 271 templ := t.lookup(name) 272 if templ == nil { 273 return nil, false 274 } 275 return &tpl.TemplateAdapter{Template: templ, Metrics: t.funcster.Deps.Metrics}, true 276 } 277 278 func (t *textTemplates) lookup(name string) *texttemplate.Template { 279 280 // Need to check in the overlay registry first as it will also be found below. 281 if t.overlays != nil { 282 if templ, ok := t.overlays[name]; ok { 283 return templ 284 } 285 } 286 287 if templ := t.t.Lookup(name); templ != nil { 288 return templ 289 } 290 291 if t.clone != nil { 292 return t.clone.Lookup(name) 293 } 294 295 return nil 296 } 297 298 func (t *templateHandler) setFuncs(funcMap map[string]interface{}) { 299 t.html.setFuncs(funcMap) 300 t.text.setFuncs(funcMap) 301 } 302 303 // SetFuncs replaces the funcs in the func maps with new definitions. 304 // This is only used in tests. 305 func (t *templateHandler) SetFuncs(funcMap map[string]interface{}) { 306 t.setFuncs(funcMap) 307 } 308 309 func (t *templateHandler) GetFuncs() map[string]interface{} { 310 return t.html.funcster.funcMap 311 } 312 313 func (t *htmlTemplates) setFuncs(funcMap map[string]interface{}) { 314 t.t.Funcs(funcMap) 315 } 316 317 func (t *textTemplates) setFuncs(funcMap map[string]interface{}) { 318 t.t.Funcs(funcMap) 319 } 320 321 // LoadTemplates loads the templates from the layouts filesystem. 322 // A prefix can be given to indicate a template namespace to load the templates 323 // into, i.e. "_internal" etc. 324 func (t *templateHandler) LoadTemplates(prefix string) { 325 t.loadTemplates(prefix) 326 327 } 328 329 func (t *htmlTemplates) addTemplateIn(tt *template.Template, name, tpl string) error { 330 templ, err := tt.New(name).Parse(tpl) 331 if err != nil { 332 return err 333 } 334 335 if err := applyTemplateTransformersToHMLTTemplate(templ); err != nil { 336 return err 337 } 338 339 if strings.Contains(name, "shortcodes") { 340 // We need to keep track of one ot the output format's shortcode template 341 // without knowing the rendering context. 342 withoutExt := strings.TrimSuffix(name, path.Ext(name)) 343 clone := template.Must(templ.Clone()) 344 tt.AddParseTree(withoutExt, clone.Tree) 345 } 346 347 return nil 348 } 349 350 func (t *htmlTemplates) addTemplate(name, tpl string) error { 351 return t.addTemplateIn(t.t, name, tpl) 352 } 353 354 func (t *htmlTemplates) addLateTemplate(name, tpl string) error { 355 return t.addTemplateIn(t.clone, name, tpl) 356 } 357 358 type textTemplate struct { 359 t *texttemplate.Template 360 } 361 362 func (t *textTemplate) Parse(name, tpl string) (tpl.Template, error) { 363 return t.parSeIn(t.t, name, tpl) 364 } 365 366 func (t *textTemplate) Lookup(name string) (tpl.Template, bool) { 367 tpl := t.t.Lookup(name) 368 return tpl, tpl != nil 369 } 370 371 func (t *textTemplate) parSeIn(tt *texttemplate.Template, name, tpl string) (*texttemplate.Template, error) { 372 templ, err := tt.New(name).Parse(tpl) 373 if err != nil { 374 return nil, err 375 } 376 377 if err := applyTemplateTransformersToTextTemplate(templ); err != nil { 378 return nil, err 379 } 380 return templ, nil 381 } 382 383 func (t *textTemplates) addTemplateIn(tt *texttemplate.Template, name, tpl string) error { 384 name = strings.TrimPrefix(name, textTmplNamePrefix) 385 templ, err := t.parSeIn(tt, name, tpl) 386 if err != nil { 387 return err 388 } 389 390 if err := applyTemplateTransformersToTextTemplate(templ); err != nil { 391 return err 392 } 393 394 if strings.Contains(name, "shortcodes") { 395 // We need to keep track of one ot the output format's shortcode template 396 // without knowing the rendering context. 397 withoutExt := strings.TrimSuffix(name, path.Ext(name)) 398 clone := texttemplate.Must(templ.Clone()) 399 tt.AddParseTree(withoutExt, clone.Tree) 400 } 401 402 return nil 403 } 404 405 func (t *textTemplates) addTemplate(name, tpl string) error { 406 return t.addTemplateIn(t.t, name, tpl) 407 } 408 409 func (t *textTemplates) addLateTemplate(name, tpl string) error { 410 return t.addTemplateIn(t.clone, name, tpl) 411 } 412 413 func (t *templateHandler) addTemplate(name, tpl string) error { 414 return t.AddTemplate(name, tpl) 415 } 416 417 func (t *templateHandler) addLateTemplate(name, tpl string) error { 418 return t.AddLateTemplate(name, tpl) 419 } 420 421 // AddLateTemplate is used to add a template late, i.e. after the 422 // regular templates have started its execution. 423 func (t *templateHandler) AddLateTemplate(name, tpl string) error { 424 h := t.getTemplateHandler(name) 425 if err := h.addLateTemplate(name, tpl); err != nil { 426 t.addError(name, err) 427 return err 428 } 429 return nil 430 } 431 432 // AddTemplate parses and adds a template to the collection. 433 // Templates with name prefixed with "_text" will be handled as plain 434 // text templates. 435 func (t *templateHandler) AddTemplate(name, tpl string) error { 436 h := t.getTemplateHandler(name) 437 if err := h.addTemplate(name, tpl); err != nil { 438 t.addError(name, err) 439 return err 440 } 441 return nil 442 } 443 444 // MarkReady marks the templates as "ready for execution". No changes allowed 445 // after this is set. 446 // TODO(bep) if this proves to be resource heavy, we could detect 447 // earlier if we really need this, or make it lazy. 448 func (t *templateHandler) MarkReady() { 449 if t.html.clone == nil { 450 t.html.clone = template.Must(t.html.t.Clone()) 451 t.html.cloneClone = template.Must(t.html.clone.Clone()) 452 } 453 if t.text.clone == nil { 454 t.text.clone = texttemplate.Must(t.text.t.Clone()) 455 t.text.cloneClone = texttemplate.Must(t.text.clone.Clone()) 456 } 457 } 458 459 // RebuildClone rebuilds the cloned templates. Used for live-reloads. 460 func (t *templateHandler) RebuildClone() { 461 t.html.clone = template.Must(t.html.cloneClone.Clone()) 462 t.text.clone = texttemplate.Must(t.text.cloneClone.Clone()) 463 } 464 465 func (t *templateHandler) loadTemplates(prefix string) { 466 walker := func(path string, fi os.FileInfo, err error) error { 467 if err != nil || fi.IsDir() { 468 return nil 469 } 470 471 if isDotFile(path) || isBackupFile(path) || isBaseTemplate(path) { 472 return nil 473 } 474 475 workingDir := t.PathSpec.WorkingDir 476 477 descriptor := output.TemplateLookupDescriptor{ 478 WorkingDir: workingDir, 479 RelPath: path, 480 Prefix: prefix, 481 OutputFormats: t.OutputFormatsConfig, 482 FileExists: func(filename string) (bool, error) { 483 return helpers.Exists(filename, t.Layouts.Fs) 484 }, 485 ContainsAny: func(filename string, subslices [][]byte) (bool, error) { 486 return helpers.FileContainsAny(filename, subslices, t.Layouts.Fs) 487 }, 488 } 489 490 tplID, err := output.CreateTemplateNames(descriptor) 491 if err != nil { 492 t.Log.ERROR.Printf("Failed to resolve template in path %q: %s", path, err) 493 494 return nil 495 } 496 497 if err := t.addTemplateFile(tplID.Name, tplID.MasterFilename, tplID.OverlayFilename); err != nil { 498 t.Log.ERROR.Printf("Failed to add template %q in path %q: %s", tplID.Name, path, err) 499 } 500 501 return nil 502 } 503 504 if err := helpers.SymbolicWalk(t.Layouts.Fs, "", walker); err != nil { 505 t.Log.ERROR.Printf("Failed to load templates: %s", err) 506 } 507 508 } 509 510 func (t *templateHandler) initFuncs() { 511 512 // Both template types will get their own funcster instance, which 513 // in the current case contains the same set of funcs. 514 funcMap := createFuncMap(t.Deps) 515 for _, funcsterHolder := range []templateFuncsterSetter{t.html, t.text} { 516 funcster := newTemplateFuncster(t.Deps) 517 518 // The URL funcs in the funcMap is somewhat language dependent, 519 // so we need to wait until the language and site config is loaded. 520 funcster.initFuncMap(funcMap) 521 522 funcsterHolder.setTemplateFuncster(funcster) 523 524 } 525 526 for _, extText := range t.extTextTemplates { 527 extText.t.Funcs(funcMap) 528 } 529 530 // Amber is HTML only. 531 t.amberFuncMap = template.FuncMap{} 532 533 amberMu.Lock() 534 for k, v := range amber.FuncMap { 535 t.amberFuncMap[k] = v 536 } 537 538 for k, v := range t.html.funcster.funcMap { 539 t.amberFuncMap[k] = v 540 // Hacky, but we need to make sure that the func names are in the global map. 541 amber.FuncMap[k] = func() string { 542 panic("should never be invoked") 543 } 544 } 545 amberMu.Unlock() 546 547 } 548 549 func (t *templateHandler) getTemplateHandler(name string) templateLoader { 550 if strings.HasPrefix(name, textTmplNamePrefix) { 551 return t.text 552 } 553 return t.html 554 } 555 556 func (t *templateHandler) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (string, error)) error { 557 h := t.getTemplateHandler(name) 558 return h.handleMaster(name, overlayFilename, masterFilename, onMissing) 559 } 560 561 func (t *htmlTemplates) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (string, error)) error { 562 563 masterTpl := t.lookup(masterFilename) 564 565 if masterTpl == nil { 566 templ, err := onMissing(masterFilename) 567 if err != nil { 568 return err 569 } 570 571 masterTpl, err = t.t.New(overlayFilename).Parse(templ) 572 if err != nil { 573 return err 574 } 575 } 576 577 templ, err := onMissing(overlayFilename) 578 if err != nil { 579 return err 580 } 581 582 overlayTpl, err := template.Must(masterTpl.Clone()).Parse(templ) 583 if err != nil { 584 return err 585 } 586 587 // The extra lookup is a workaround, see 588 // * https://github.com/golang/go/issues/16101 589 // * https://github.com/gohugoio/hugo/issues/2549 590 overlayTpl = overlayTpl.Lookup(overlayTpl.Name()) 591 if err := applyTemplateTransformersToHMLTTemplate(overlayTpl); err != nil { 592 return err 593 } 594 595 t.overlays[name] = overlayTpl 596 597 return err 598 599 } 600 601 func (t *textTemplates) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (string, error)) error { 602 603 name = strings.TrimPrefix(name, textTmplNamePrefix) 604 masterTpl := t.lookup(masterFilename) 605 606 if masterTpl == nil { 607 templ, err := onMissing(masterFilename) 608 if err != nil { 609 return err 610 } 611 612 masterTpl, err = t.t.New(overlayFilename).Parse(templ) 613 if err != nil { 614 return err 615 } 616 } 617 618 templ, err := onMissing(overlayFilename) 619 if err != nil { 620 return err 621 } 622 623 overlayTpl, err := texttemplate.Must(masterTpl.Clone()).Parse(templ) 624 if err != nil { 625 return err 626 } 627 628 overlayTpl = overlayTpl.Lookup(overlayTpl.Name()) 629 if err := applyTemplateTransformersToTextTemplate(overlayTpl); err != nil { 630 return err 631 } 632 t.overlays[name] = overlayTpl 633 634 return err 635 636 } 637 638 func (t *templateHandler) addTemplateFile(name, baseTemplatePath, path string) error { 639 t.checkState() 640 641 t.Log.DEBUG.Printf("Add template file: name %q, baseTemplatePath %q, path %q", name, baseTemplatePath, path) 642 643 getTemplate := func(filename string) (string, error) { 644 b, err := afero.ReadFile(t.Layouts.Fs, filename) 645 if err != nil { 646 return "", err 647 } 648 s := string(b) 649 650 return s, nil 651 } 652 653 // get the suffix and switch on that 654 ext := filepath.Ext(path) 655 switch ext { 656 case ".amber": 657 // Only HTML support for Amber 658 withoutExt := strings.TrimSuffix(name, filepath.Ext(name)) 659 templateName := withoutExt + ".html" 660 b, err := afero.ReadFile(t.Layouts.Fs, path) 661 662 if err != nil { 663 return err 664 } 665 666 amberMu.Lock() 667 templ, err := t.compileAmberWithTemplate(b, path, t.html.t.New(templateName)) 668 amberMu.Unlock() 669 if err != nil { 670 return err 671 } 672 673 if err := applyTemplateTransformersToHMLTTemplate(templ); err != nil { 674 return err 675 } 676 677 if strings.Contains(templateName, "shortcodes") { 678 // We need to keep track of one ot the output format's shortcode template 679 // without knowing the rendering context. 680 clone := template.Must(templ.Clone()) 681 t.html.t.AddParseTree(withoutExt, clone.Tree) 682 } 683 684 return nil 685 686 case ".ace": 687 // Only HTML support for Ace 688 var innerContent, baseContent []byte 689 innerContent, err := afero.ReadFile(t.Layouts.Fs, path) 690 691 if err != nil { 692 return err 693 } 694 695 if baseTemplatePath != "" { 696 baseContent, err = afero.ReadFile(t.Layouts.Fs, baseTemplatePath) 697 if err != nil { 698 return err 699 } 700 } 701 702 return t.addAceTemplate(name, baseTemplatePath, path, baseContent, innerContent) 703 default: 704 705 if baseTemplatePath != "" { 706 return t.handleMaster(name, path, baseTemplatePath, getTemplate) 707 } 708 709 templ, err := getTemplate(path) 710 711 if err != nil { 712 return err 713 } 714 715 return t.AddTemplate(name, templ) 716 } 717 } 718 719 var embeddedTemplatesAliases = map[string][]string{ 720 "shortcodes/twitter.html": []string{"shortcodes/tweet.html"}, 721 } 722 723 func (t *templateHandler) loadEmbedded() { 724 for _, kv := range embedded.EmbeddedTemplates { 725 // TODO(bep) error handling 726 name, templ := kv[0], kv[1] 727 t.addInternalTemplate(name, templ) 728 if aliases, found := embeddedTemplatesAliases[name]; found { 729 for _, alias := range aliases { 730 t.addInternalTemplate(alias, templ) 731 } 732 733 } 734 } 735 736 } 737 738 func (t *templateHandler) addInternalTemplate(name, tpl string) error { 739 return t.AddTemplate("_internal/"+name, tpl) 740 } 741 742 func (t *templateHandler) checkState() { 743 if t.html.clone != nil || t.text.clone != nil { 744 panic("template is cloned and cannot be modfified") 745 } 746 } 747 748 func isDotFile(path string) bool { 749 return filepath.Base(path)[0] == '.' 750 } 751 752 func isBackupFile(path string) bool { 753 return path[len(path)-1] == '~' 754 } 755 756 const baseFileBase = "baseof" 757 758 func isBaseTemplate(path string) bool { 759 return strings.Contains(filepath.Base(path), baseFileBase) 760 }