github.com/neohugo/neohugo@v0.123.8/hugolib/site.go (about) 1 // Copyright 2024 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 hugolib 15 16 import ( 17 "context" 18 "fmt" 19 "io" 20 "mime" 21 "net/url" 22 "path/filepath" 23 "runtime" 24 "sort" 25 "strings" 26 "sync" 27 "time" 28 29 "github.com/bep/logg" 30 "github.com/neohugo/neohugo/common/htime" 31 "github.com/neohugo/neohugo/common/hugio" 32 "github.com/neohugo/neohugo/common/types" 33 "github.com/neohugo/neohugo/hugolib/doctree" 34 "golang.org/x/text/unicode/norm" 35 36 "github.com/neohugo/neohugo/common/paths" 37 38 "github.com/neohugo/neohugo/identity" 39 40 "github.com/neohugo/neohugo/markup/converter/hooks" 41 42 "github.com/neohugo/neohugo/markup/converter" 43 44 "github.com/neohugo/neohugo/common/text" 45 46 "github.com/neohugo/neohugo/publisher" 47 48 "github.com/neohugo/neohugo/langs" 49 50 "github.com/neohugo/neohugo/resources/kinds" 51 "github.com/neohugo/neohugo/resources/page" 52 53 "github.com/neohugo/neohugo/lazy" 54 55 "github.com/fsnotify/fsnotify" 56 bp "github.com/neohugo/neohugo/bufferpool" 57 "github.com/neohugo/neohugo/helpers" 58 "github.com/neohugo/neohugo/navigation" 59 "github.com/neohugo/neohugo/output" 60 "github.com/neohugo/neohugo/tpl" 61 ) 62 63 func (s *Site) Taxonomies() page.TaxonomyList { 64 s.init.taxonomies.Do(context.Background()) // nolint 65 return s.taxonomies 66 } 67 68 type ( 69 taxonomiesConfig map[string]string 70 taxonomiesConfigValues struct { 71 views []viewName 72 viewsByTreeKey map[string]viewName 73 } 74 ) 75 76 func (t taxonomiesConfig) Values() taxonomiesConfigValues { 77 var views []viewName 78 for k, v := range t { 79 views = append(views, viewName{singular: k, plural: v, pluralTreeKey: cleanTreeKey(v)}) 80 } 81 sort.Slice(views, func(i, j int) bool { 82 return views[i].plural < views[j].plural 83 }) 84 85 viewsByTreeKey := make(map[string]viewName) 86 for _, v := range views { 87 viewsByTreeKey[v.pluralTreeKey] = v 88 } 89 90 return taxonomiesConfigValues{ 91 views: views, 92 viewsByTreeKey: viewsByTreeKey, 93 } 94 } 95 96 // Lazily loaded site dependencies. 97 type siteInit struct { 98 prevNext *lazy.Init 99 prevNextInSection *lazy.Init 100 menus *lazy.Init 101 taxonomies *lazy.Init 102 } 103 104 func (init *siteInit) Reset() { 105 init.prevNext.Reset() 106 init.prevNextInSection.Reset() 107 init.menus.Reset() 108 init.taxonomies.Reset() 109 } 110 111 func (s *Site) prepareInits() { 112 s.init = &siteInit{} 113 114 var init lazy.Init 115 116 s.init.prevNext = init.Branch(func(context.Context) (any, error) { 117 regularPages := s.RegularPages() 118 for i, p := range regularPages { 119 np, ok := p.(nextPrevProvider) 120 if !ok { 121 continue 122 } 123 124 pos := np.getNextPrev() 125 if pos == nil { 126 continue 127 } 128 129 pos.nextPage = nil 130 pos.prevPage = nil 131 132 if i > 0 { 133 pos.nextPage = regularPages[i-1] 134 } 135 136 if i < len(regularPages)-1 { 137 pos.prevPage = regularPages[i+1] 138 } 139 } 140 return nil, nil 141 }) 142 143 s.init.prevNextInSection = init.Branch(func(context.Context) (any, error) { 144 setNextPrev := func(pas page.Pages) { 145 for i, p := range pas { 146 np, ok := p.(nextPrevInSectionProvider) 147 if !ok { 148 continue 149 } 150 151 pos := np.getNextPrevInSection() 152 if pos == nil { 153 continue 154 } 155 156 pos.nextPage = nil 157 pos.prevPage = nil 158 159 if i > 0 { 160 pos.nextPage = pas[i-1] 161 } 162 163 if i < len(pas)-1 { 164 pos.prevPage = pas[i+1] 165 } 166 } 167 } 168 169 sections := s.pageMap.getPagesInSection( 170 pageMapQueryPagesInSection{ 171 pageMapQueryPagesBelowPath: pageMapQueryPagesBelowPath{ 172 Path: "", 173 KeyPart: "sectionorhome", 174 Include: pagePredicates.KindSection.Or(pagePredicates.KindHome), 175 }, 176 IncludeSelf: true, 177 Recursive: true, 178 }, 179 ) 180 181 for _, section := range sections { 182 setNextPrev(section.RegularPages()) 183 } 184 185 return nil, nil 186 }) 187 188 s.init.menus = init.Branch(func(context.Context) (any, error) { 189 err := s.assembleMenus() 190 return nil, err 191 }) 192 193 s.init.taxonomies = init.Branch(func(ctx context.Context) (any, error) { 194 if err := s.pageMap.CreateSiteTaxonomies(ctx); err != nil { 195 return nil, err 196 } 197 return s.taxonomies, nil 198 }) 199 } 200 201 type siteRenderingContext struct { 202 output.Format 203 } 204 205 func (s *Site) Menus() navigation.Menus { 206 //nolint 207 s.init.menus.Do(context.Background()) 208 return s.menus 209 } 210 211 func (s *Site) initRenderFormats() { 212 formatSet := make(map[string]bool) 213 formats := output.Formats{} 214 215 w := &doctree.NodeShiftTreeWalker[contentNodeI]{ 216 Tree: s.pageMap.treePages, 217 Handle: func(key string, n contentNodeI, match doctree.DimensionFlag) (bool, error) { 218 if p, ok := n.(*pageState); ok { 219 for _, f := range p.m.configuredOutputFormats { 220 if !formatSet[f.Name] { 221 formats = append(formats, f) 222 formatSet[f.Name] = true 223 } 224 } 225 } 226 return false, nil 227 }, 228 } 229 230 if err := w.Walk(context.TODO()); err != nil { 231 panic(err) 232 } 233 234 // Add the per kind configured output formats 235 for _, kind := range kinds.AllKindsInPages { 236 if siteFormats, found := s.conf.C.KindOutputFormats[kind]; found { 237 for _, f := range siteFormats { 238 if !formatSet[f.Name] { 239 formats = append(formats, f) 240 formatSet[f.Name] = true 241 } 242 } 243 } 244 } 245 246 sort.Sort(formats) 247 s.renderFormats = formats 248 } 249 250 func (s *Site) GetRelatedDocsHandler() *page.RelatedDocsHandler { 251 return s.relatedDocsHandler 252 } 253 254 func (s *Site) Language() *langs.Language { 255 return s.language 256 } 257 258 func (s *Site) Languages() langs.Languages { 259 return s.h.Configs.Languages 260 } 261 262 type siteRefLinker struct { 263 s *Site 264 265 errorLogger logg.LevelLogger 266 notFoundURL string 267 } 268 269 func newSiteRefLinker(s *Site) (siteRefLinker, error) { 270 logger := s.Log.Error() 271 272 notFoundURL := s.conf.RefLinksNotFoundURL 273 errLevel := s.conf.RefLinksErrorLevel 274 if strings.EqualFold(errLevel, "warning") { 275 logger = s.Log.Warn() 276 } 277 return siteRefLinker{s: s, errorLogger: logger, notFoundURL: notFoundURL}, nil 278 } 279 280 func (s siteRefLinker) logNotFound(ref, what string, p page.Page, position text.Position) { 281 if position.IsValid() { 282 s.errorLogger.Logf("[%s] REF_NOT_FOUND: Ref %q: %s: %s", s.s.Lang(), ref, position.String(), what) 283 } else if p == nil { 284 s.errorLogger.Logf("[%s] REF_NOT_FOUND: Ref %q: %s", s.s.Lang(), ref, what) 285 } else { 286 s.errorLogger.Logf("[%s] REF_NOT_FOUND: Ref %q from page %q: %s", s.s.Lang(), ref, p.Path(), what) 287 } 288 } 289 290 func (s *siteRefLinker) refLink(ref string, source any, relative bool, outputFormat string) (string, error) { 291 p, err := unwrapPage(source) 292 if err != nil { 293 return "", err 294 } 295 296 var refURL *url.URL 297 298 ref = filepath.ToSlash(ref) 299 300 refURL, err = url.Parse(ref) 301 if err != nil { 302 return s.notFoundURL, err 303 } 304 305 var target page.Page 306 var link string 307 308 if refURL.Path != "" { 309 var err error 310 target, err = s.s.getPageRef(p, refURL.Path) 311 var pos text.Position 312 if err != nil || target == nil { 313 if p, ok := source.(text.Positioner); ok { 314 pos = p.Position() 315 } 316 } 317 318 if err != nil { 319 s.logNotFound(refURL.Path, err.Error(), p, pos) 320 return s.notFoundURL, nil 321 } 322 323 if target == nil { 324 s.logNotFound(refURL.Path, "page not found", p, pos) 325 return s.notFoundURL, nil 326 } 327 328 var permalinker Permalinker = target 329 330 if outputFormat != "" { 331 o := target.OutputFormats().Get(outputFormat) 332 333 if o == nil { 334 s.logNotFound(refURL.Path, fmt.Sprintf("output format %q", outputFormat), p, pos) 335 return s.notFoundURL, nil 336 } 337 permalinker = o 338 } 339 340 if relative { 341 link = permalinker.RelPermalink() 342 } else { 343 link = permalinker.Permalink() 344 } 345 } 346 347 if refURL.Fragment != "" { 348 _ = target 349 link = link + "#" + refURL.Fragment 350 351 if pctx, ok := target.(pageContext); ok { 352 if refURL.Path != "" { 353 if di, ok := pctx.getContentConverter().(converter.DocumentInfo); ok { 354 link = link + di.AnchorSuffix() 355 } 356 } 357 } else if pctx, ok := p.(pageContext); ok { 358 if di, ok := pctx.getContentConverter().(converter.DocumentInfo); ok { 359 link = link + di.AnchorSuffix() 360 } 361 } 362 363 } 364 365 return link, nil 366 } 367 368 func (s *Site) watching() bool { 369 return s.h != nil && s.h.Configs.Base.Internal.Watch 370 } 371 372 type whatChanged struct { 373 mu sync.Mutex 374 375 contentChanged bool 376 identitySet identity.Identities 377 } 378 379 func (w *whatChanged) Add(ids ...identity.Identity) { 380 w.mu.Lock() 381 defer w.mu.Unlock() 382 383 for _, id := range ids { 384 w.identitySet[id] = true 385 } 386 } 387 388 func (w *whatChanged) Changes() []identity.Identity { 389 if w == nil || w.identitySet == nil { 390 return nil 391 } 392 return w.identitySet.AsSlice() 393 } 394 395 // RegisterMediaTypes will register the Site's media types in the mime 396 // package, so it will behave correctly with Hugo's built-in server. 397 func (s *Site) RegisterMediaTypes() { 398 for _, mt := range s.conf.MediaTypes.Config { 399 for _, suffix := range mt.Suffixes() { 400 _ = mime.AddExtensionType(mt.Delimiter+suffix, mt.Type) 401 } 402 } 403 } 404 405 func (h *HugoSites) fileEventsFilter(events []fsnotify.Event) []fsnotify.Event { 406 seen := make(map[fsnotify.Event]bool) 407 408 n := 0 409 for _, ev := range events { 410 // Avoid processing the same event twice. 411 if seen[ev] { 412 continue 413 } 414 seen[ev] = true 415 416 if h.SourceSpec.IgnoreFile(ev.Name) { 417 continue 418 } 419 420 if runtime.GOOS == "darwin" { // When a file system is HFS+, its filepath is in NFD form. 421 ev.Name = norm.NFC.String(ev.Name) 422 } 423 424 events[n] = ev 425 n++ 426 } 427 return events[:n] 428 } 429 430 func (h *HugoSites) fileEventsTranslate(events []fsnotify.Event) []fsnotify.Event { 431 eventMap := make(map[string][]fsnotify.Event) 432 433 // We often get a Remove etc. followed by a Create, a Create followed by a Write. 434 // Remove the superfluous events to make the update logic simpler. 435 for _, ev := range events { 436 eventMap[ev.Name] = append(eventMap[ev.Name], ev) 437 } 438 439 n := 0 440 for _, ev := range events { 441 mapped := eventMap[ev.Name] 442 443 // Keep one 444 found := false 445 var kept fsnotify.Event 446 for i, ev2 := range mapped { 447 if i == 0 { 448 kept = ev2 449 } 450 451 if ev2.Op&fsnotify.Write == fsnotify.Write { 452 kept = ev2 453 found = true 454 } 455 456 if !found && ev2.Op&fsnotify.Create == fsnotify.Create { 457 kept = ev2 458 } 459 } 460 461 events[n] = kept 462 n++ 463 } 464 465 return events 466 } 467 468 func (h *HugoSites) fileEventsContentPaths(p []pathChange) []pathChange { 469 var bundles []pathChange 470 var dirs []pathChange 471 var regular []pathChange 472 473 var others []pathChange 474 for _, p := range p { 475 if p.isDir { 476 dirs = append(dirs, p) 477 } else { 478 others = append(others, p) 479 } 480 } 481 482 // Remove all files below dir. 483 if len(dirs) > 0 { 484 n := 0 485 for _, d := range dirs { 486 dir := d.p.Path() + "/" 487 for _, o := range others { 488 if !strings.HasPrefix(o.p.Path(), dir) { 489 others[n] = o 490 n++ 491 } 492 } 493 494 } 495 others = others[:n] 496 } 497 498 for _, p := range others { 499 if p.p.IsBundle() { 500 bundles = append(bundles, p) 501 } else { 502 regular = append(regular, p) 503 } 504 } 505 506 // Remove any files below leaf bundles. 507 // Remove any files in the same folder as branch bundles. 508 var keepers []pathChange 509 510 for _, o := range regular { 511 keep := true 512 for _, b := range bundles { 513 prefix := b.p.Base() + "/" 514 if b.p.IsLeafBundle() && strings.HasPrefix(o.p.Path(), prefix) { 515 keep = false 516 break 517 } else if b.p.IsBranchBundle() && o.p.Dir() == b.p.Dir() { 518 keep = false 519 break 520 } 521 } 522 523 if keep { 524 keepers = append(keepers, o) 525 } 526 } 527 528 keepers = append(dirs, keepers...) 529 keepers = append(bundles, keepers...) 530 531 return keepers 532 } 533 534 // HomeAbsURL is a convenience method giving the absolute URL to the home page. 535 func (s *Site) HomeAbsURL() string { 536 base := "" 537 if len(s.conf.Languages) > 1 { 538 base = s.Language().Lang 539 } 540 return s.AbsURL(base, false) 541 } 542 543 // SitemapAbsURL is a convenience method giving the absolute URL to the sitemap. 544 func (s *Site) SitemapAbsURL() string { 545 p := s.HomeAbsURL() 546 if !strings.HasSuffix(p, "/") { 547 p += "/" 548 } 549 p += s.conf.Sitemap.Filename 550 return p 551 } 552 553 func (s *Site) createNodeMenuEntryURL(in string) string { 554 if !strings.HasPrefix(in, "/") { 555 return in 556 } 557 // make it match the nodes 558 menuEntryURL := in 559 menuEntryURL = s.s.PathSpec.URLize(menuEntryURL) 560 if !s.conf.CanonifyURLs { 561 menuEntryURL = paths.AddContextRoot(s.s.PathSpec.Cfg.BaseURL().String(), menuEntryURL) 562 } 563 return menuEntryURL 564 } 565 566 func (s *Site) assembleMenus() error { 567 s.menus = make(navigation.Menus) 568 569 type twoD struct { 570 MenuName, EntryName string 571 } 572 flat := map[twoD]*navigation.MenuEntry{} 573 children := map[twoD]navigation.Menu{} 574 575 // add menu entries from config to flat hash 576 for name, menu := range s.conf.Menus.Config { 577 for _, me := range menu { 578 if types.IsNil(me.Page) && me.PageRef != "" { 579 // Try to resolve the page. 580 me.Page, _ = s.getPage(nil, me.PageRef) 581 } 582 583 // If page is still nill, we must make sure that we have a URL that considers baseURL etc. 584 if types.IsNil(me.Page) { 585 me.ConfiguredURL = s.createNodeMenuEntryURL(me.MenuConfig.URL) 586 } 587 588 flat[twoD{name, me.KeyName()}] = me 589 } 590 } 591 592 sectionPagesMenu := s.conf.SectionPagesMenu 593 594 if sectionPagesMenu != "" { 595 if err := s.pageMap.forEachPage(pagePredicates.ShouldListGlobal, func(p *pageState) (bool, error) { 596 if p.IsHome() || !p.m.shouldBeCheckedForMenuDefinitions() { 597 return false, nil 598 } 599 // The section pages menus are attached to the top level section. 600 id := p.Section() 601 if _, ok := flat[twoD{sectionPagesMenu, id}]; ok { 602 return false, nil 603 } 604 me := navigation.MenuEntry{ 605 MenuConfig: navigation.MenuConfig{ 606 Identifier: id, 607 Name: p.LinkTitle(), 608 Weight: p.Weight(), 609 }, 610 Page: p, 611 } 612 navigation.SetPageValues(&me, p) 613 flat[twoD{sectionPagesMenu, me.KeyName()}] = &me 614 return false, nil 615 }); err != nil { 616 return err 617 } 618 } 619 // Add menu entries provided by pages 620 if err := s.pageMap.forEachPage(pagePredicates.ShouldListGlobal, func(p *pageState) (bool, error) { 621 for name, me := range p.pageMenus.menus() { 622 if _, ok := flat[twoD{name, me.KeyName()}]; ok { 623 err := p.wrapError(fmt.Errorf("duplicate menu entry with identifier %q in menu %q", me.KeyName(), name)) 624 s.Log.Warnln(err) 625 continue 626 } 627 flat[twoD{name, me.KeyName()}] = me 628 } 629 return false, nil 630 }); err != nil { 631 return err 632 } 633 634 // Create Children Menus First 635 for _, e := range flat { 636 if e.Parent != "" { 637 children[twoD{e.Menu, e.Parent}] = children[twoD{e.Menu, e.Parent}].Add(e) 638 } 639 } 640 641 // Placing Children in Parents (in flat) 642 for p, childmenu := range children { 643 _, ok := flat[twoD{p.MenuName, p.EntryName}] 644 if !ok { 645 // if parent does not exist, create one without a URL 646 flat[twoD{p.MenuName, p.EntryName}] = &navigation.MenuEntry{ 647 MenuConfig: navigation.MenuConfig{ 648 Name: p.EntryName, 649 }, 650 } 651 } 652 flat[twoD{p.MenuName, p.EntryName}].Children = childmenu 653 } 654 655 // Assembling Top Level of Tree 656 for menu, e := range flat { 657 if e.Parent == "" { 658 _, ok := s.menus[menu.MenuName] 659 if !ok { 660 s.menus[menu.MenuName] = navigation.Menu{} 661 } 662 s.menus[menu.MenuName] = s.menus[menu.MenuName].Add(e) 663 } 664 } 665 666 return nil 667 } 668 669 // get any language code to prefix the target file path with. 670 func (s *Site) getLanguageTargetPathLang(alwaysInSubDir bool) string { 671 if s.h.Conf.IsMultihost() { 672 return s.Language().Lang 673 } 674 675 return s.getLanguagePermalinkLang(alwaysInSubDir) 676 } 677 678 // get any language code to prefix the relative permalink with. 679 func (s *Site) getLanguagePermalinkLang(alwaysInSubDir bool) string { 680 if s.h.Conf.IsMultihost() { 681 return "" 682 } 683 684 if s.h.Conf.IsMultiLingual() && alwaysInSubDir { 685 return s.Language().Lang 686 } 687 688 return s.GetLanguagePrefix() 689 } 690 691 // Prepare site for a new full build. 692 func (s *Site) resetBuildState(sourceChanged bool) { 693 s.relatedDocsHandler = s.relatedDocsHandler.Clone() 694 s.init.Reset() 695 } 696 697 func (s *Site) errorCollator(results <-chan error, errs chan<- error) { 698 var errors []error 699 for e := range results { 700 errors = append(errors, e) 701 } 702 703 errs <- s.h.pickOneAndLogTheRest(errors) 704 705 close(errs) 706 } 707 708 // GetPage looks up a page of a given type for the given ref. 709 // In Hugo <= 0.44 you had to add Page Kind (section, home) etc. as the first 710 // argument and then either a unix styled path (with or without a leading slash)) 711 // or path elements separated. 712 // When we now remove the Kind from this API, we need to make the transition as painless 713 // as possible for existing sites. Most sites will use {{ .Site.GetPage "section" "my/section" }}, 714 // i.e. 2 arguments, so we test for that. 715 func (s *Site) GetPage(ref ...string) (page.Page, error) { 716 p, err := s.s.getPageForRefs(ref...) 717 718 if p == nil { 719 // The nil struct has meaning in some situations, mostly to avoid breaking 720 // existing sites doing $nilpage.IsDescendant($p), which will always return 721 // false. 722 p = page.NilPage 723 } 724 725 return p, err 726 } 727 728 func (s *Site) absURLPath(targetPath string) string { 729 var path string 730 if s.conf.RelativeURLs { 731 path = helpers.GetDottedRelativePath(targetPath) 732 } else { 733 url := s.PathSpec.Cfg.BaseURL().String() 734 if !strings.HasSuffix(url, "/") { 735 url += "/" 736 } 737 path = url 738 } 739 740 return path 741 } 742 743 const ( 744 pageDependencyScopeDefault int = iota 745 pageDependencyScopeGlobal 746 ) 747 748 func (s *Site) renderAndWritePage(statCounter *uint64, name string, targetPath string, p *pageState, d any, templ tpl.Template) error { 749 s.h.buildCounters.pageRenderCounter.Add(1) 750 renderBuffer := bp.GetBuffer() 751 defer bp.PutBuffer(renderBuffer) 752 753 of := p.outputFormat() 754 p.incrRenderState() 755 756 ctx := tpl.Context.Page.Set(context.Background(), p) 757 ctx = tpl.Context.DependencyManagerScopedProvider.Set(ctx, p) 758 759 if err := s.renderForTemplate(ctx, p.Kind(), of.Name, d, renderBuffer, templ); err != nil { 760 return err 761 } 762 763 if renderBuffer.Len() == 0 { 764 return nil 765 } 766 767 isHTML := of.IsHTML 768 isRSS := of.Name == "rss" 769 770 pd := publisher.Descriptor{ 771 Src: renderBuffer, 772 TargetPath: targetPath, 773 StatCounter: statCounter, 774 OutputFormat: p.outputFormat(), 775 } 776 777 if isRSS { 778 // Always canonify URLs in RSS 779 pd.AbsURLPath = s.absURLPath(targetPath) 780 } else if isHTML { 781 if s.conf.RelativeURLs || s.conf.CanonifyURLs { 782 pd.AbsURLPath = s.absURLPath(targetPath) 783 } 784 785 if s.watching() && s.conf.Internal.Running && !s.conf.DisableLiveReload { 786 pd.LiveReloadBaseURL = s.Conf.BaseURLLiveReload().URL() 787 } 788 789 // For performance reasons we only inject the Hugo generator tag on the home page. 790 if p.IsHome() { 791 pd.AddHugoGeneratorTag = s.conf.DisableHugoGeneratorInject 792 } 793 794 } 795 796 return s.publisher.Publish(pd) 797 } 798 799 var infoOnMissingLayout = map[string]bool{ 800 // The 404 layout is very much optional in Hugo, but we do look for it. 801 "404": true, 802 } 803 804 // hookRendererTemplate is the canonical implementation of all hooks.ITEMRenderer, 805 // where ITEM is the thing being hooked. 806 type hookRendererTemplate struct { 807 templateHandler tpl.TemplateHandler 808 templ tpl.Template 809 resolvePosition func(ctx any) text.Position 810 } 811 812 func (hr hookRendererTemplate) RenderLink(cctx context.Context, w io.Writer, ctx hooks.LinkContext) error { 813 return hr.templateHandler.ExecuteWithContext(cctx, hr.templ, w, ctx) 814 } 815 816 func (hr hookRendererTemplate) RenderHeading(cctx context.Context, w io.Writer, ctx hooks.HeadingContext) error { 817 return hr.templateHandler.ExecuteWithContext(cctx, hr.templ, w, ctx) 818 } 819 820 func (hr hookRendererTemplate) RenderCodeblock(cctx context.Context, w hugio.FlexiWriter, ctx hooks.CodeblockContext) error { 821 return hr.templateHandler.ExecuteWithContext(cctx, hr.templ, w, ctx) 822 } 823 824 func (hr hookRendererTemplate) ResolvePosition(ctx any) text.Position { 825 return hr.resolvePosition(ctx) 826 } 827 828 func (hr hookRendererTemplate) IsDefaultCodeBlockRenderer() bool { 829 return false 830 } 831 832 func (s *Site) renderForTemplate(ctx context.Context, name, outputFormat string, d any, w io.Writer, templ tpl.Template) (err error) { 833 if templ == nil { 834 s.logMissingLayout(name, "", "", outputFormat) 835 return nil 836 } 837 838 if ctx == nil { 839 panic("nil context") 840 } 841 842 if err = s.Tmpl().ExecuteWithContext(ctx, templ, w, d); err != nil { 843 return fmt.Errorf("render of %q failed: %w", name, err) 844 } 845 return 846 } 847 848 func (s *Site) shouldBuild(p page.Page) bool { 849 if !s.conf.IsKindEnabled(p.Kind()) { 850 return false 851 } 852 return shouldBuild(s.Conf.BuildFuture(), s.Conf.BuildExpired(), 853 s.Conf.BuildDrafts(), p.Draft(), p.PublishDate(), p.ExpiryDate()) 854 } 855 856 func shouldBuild(buildFuture bool, buildExpired bool, buildDrafts bool, Draft bool, 857 publishDate time.Time, expiryDate time.Time, 858 ) bool { 859 if !(buildDrafts || !Draft) { 860 return false 861 } 862 hnow := htime.Now() 863 if !buildFuture && !publishDate.IsZero() && publishDate.After(hnow) { 864 return false 865 } 866 if !buildExpired && !expiryDate.IsZero() && expiryDate.Before(hnow) { 867 return false 868 } 869 return true 870 } 871 872 func (s *Site) render(ctx *siteRenderContext) (err error) { 873 if err := page.Clear(); err != nil { 874 return err 875 } 876 877 if ctx.outIdx == 0 { 878 // Note that even if disableAliases is set, the aliases themselves are 879 // preserved on page. The motivation with this is to be able to generate 880 // 301 redirects in a .htaccess file and similar using a custom output format. 881 if !s.conf.DisableAliases { 882 // Aliases must be rendered before pages. 883 // Some sites, Hugo docs included, have faulty alias definitions that point 884 // to itself or another real page. These will be overwritten in the next 885 // step. 886 if err = s.renderAliases(); err != nil { 887 return 888 } 889 } 890 } 891 892 if err = s.renderPages(ctx); err != nil { 893 return 894 } 895 896 if !ctx.shouldRenderStandalonePage("") { 897 return 898 } 899 900 if err = s.renderMainLanguageRedirect(); err != nil { 901 return 902 } 903 904 return 905 }