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