hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

site.go (49214B)

    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 hugolib
   15 
   16 import (
   17 	"fmt"
   18 	"html/template"
   19 	"io"
   20 	"log"
   21 	"mime"
   22 	"net/url"
   23 	"os"
   24 	"path"
   25 	"path/filepath"
   26 	"regexp"
   27 	"runtime"
   28 	"sort"
   29 	"strconv"
   30 	"strings"
   31 	"time"
   32 
   33 	"github.com/gohugoio/hugo/common/htime"
   34 	"github.com/gohugoio/hugo/common/hugio"
   35 	"github.com/gohugoio/hugo/common/types"
   36 	"github.com/gohugoio/hugo/modules"
   37 	"golang.org/x/text/unicode/norm"
   38 
   39 	"github.com/gohugoio/hugo/common/paths"
   40 
   41 	"github.com/gohugoio/hugo/common/constants"
   42 
   43 	"github.com/gohugoio/hugo/common/loggers"
   44 
   45 	"github.com/gohugoio/hugo/resources"
   46 
   47 	"github.com/gohugoio/hugo/identity"
   48 
   49 	"github.com/gohugoio/hugo/markup/converter/hooks"
   50 
   51 	"github.com/gohugoio/hugo/resources/resource"
   52 
   53 	"github.com/gohugoio/hugo/markup/converter"
   54 
   55 	"github.com/gohugoio/hugo/hugofs/files"
   56 
   57 	"github.com/gohugoio/hugo/common/maps"
   58 
   59 	"github.com/gohugoio/hugo/common/text"
   60 
   61 	"github.com/gohugoio/hugo/common/hugo"
   62 	"github.com/gohugoio/hugo/publisher"
   63 
   64 	"github.com/gohugoio/hugo/langs"
   65 
   66 	"github.com/gohugoio/hugo/resources/page"
   67 
   68 	"github.com/gohugoio/hugo/config"
   69 	"github.com/gohugoio/hugo/lazy"
   70 
   71 	"github.com/gohugoio/hugo/media"
   72 
   73 	"github.com/fsnotify/fsnotify"
   74 	bp "github.com/gohugoio/hugo/bufferpool"
   75 	"github.com/gohugoio/hugo/deps"
   76 	"github.com/gohugoio/hugo/helpers"
   77 	"github.com/gohugoio/hugo/navigation"
   78 	"github.com/gohugoio/hugo/output"
   79 	"github.com/gohugoio/hugo/related"
   80 	"github.com/gohugoio/hugo/resources/page/pagemeta"
   81 	"github.com/gohugoio/hugo/source"
   82 	"github.com/gohugoio/hugo/tpl"
   83 
   84 	"github.com/spf13/afero"
   85 	"github.com/spf13/cast"
   86 )
   87 
   88 // Site contains all the information relevant for constructing a static
   89 // site.  The basic flow of information is as follows:
   90 //
   91 // 1. A list of Files is parsed and then converted into Pages.
   92 //
   93 // 2. Pages contain sections (based on the file they were generated from),
   94 //    aliases and slugs (included in a pages frontmatter) which are the
   95 //    various targets that will get generated.  There will be canonical
   96 //    listing.  The canonical path can be overruled based on a pattern.
   97 //
   98 // 3. Taxonomies are created via configuration and will present some aspect of
   99 //    the final page and typically a perm url.
  100 //
  101 // 4. All Pages are passed through a template based on their desired
  102 //    layout based on numerous different elements.
  103 //
  104 // 5. The entire collection of files is written to disk.
  105 type Site struct {
  106 
  107 	// The owning container. When multiple languages, there will be multiple
  108 	// sites .
  109 	h *HugoSites
  110 
  111 	*PageCollections
  112 
  113 	taxonomies TaxonomyList
  114 
  115 	Sections Taxonomy
  116 	Info     *SiteInfo
  117 
  118 	language   *langs.Language
  119 	siteBucket *pagesMapBucket
  120 
  121 	siteCfg siteConfigHolder
  122 
  123 	disabledKinds map[string]bool
  124 
  125 	// Output formats defined in site config per Page Kind, or some defaults
  126 	// if not set.
  127 	// Output formats defined in Page front matter will override these.
  128 	outputFormats map[string]output.Formats
  129 
  130 	// All the output formats and media types available for this site.
  131 	// These values will be merged from the Hugo defaults, the site config and,
  132 	// finally, the language settings.
  133 	outputFormatsConfig output.Formats
  134 	mediaTypesConfig    media.Types
  135 
  136 	siteConfigConfig SiteConfig
  137 
  138 	// How to handle page front matter.
  139 	frontmatterHandler pagemeta.FrontMatterHandler
  140 
  141 	// We render each site for all the relevant output formats in serial with
  142 	// this rendering context pointing to the current one.
  143 	rc *siteRenderingContext
  144 
  145 	// The output formats that we need to render this site in. This slice
  146 	// will be fixed once set.
  147 	// This will be the union of Site.Pages' outputFormats.
  148 	// This slice will be sorted.
  149 	renderFormats output.Formats
  150 
  151 	// Logger etc.
  152 	*deps.Deps `json:"-"`
  153 
  154 	// The func used to title case titles.
  155 	titleFunc func(s string) string
  156 
  157 	relatedDocsHandler *page.RelatedDocsHandler
  158 	siteRefLinker
  159 
  160 	publisher publisher.Publisher
  161 
  162 	menus navigation.Menus
  163 
  164 	// Shortcut to the home page. Note that this may be nil if
  165 	// home page, for some odd reason, is disabled.
  166 	home *pageState
  167 
  168 	// The last modification date of this site.
  169 	lastmod time.Time
  170 
  171 	// Lazily loaded site dependencies
  172 	init *siteInit
  173 }
  174 
  175 func (s *Site) Taxonomies() TaxonomyList {
  176 	s.init.taxonomies.Do()
  177 	return s.taxonomies
  178 }
  179 
  180 type taxonomiesConfig map[string]string
  181 
  182 func (t taxonomiesConfig) Values() []viewName {
  183 	var vals []viewName
  184 	for k, v := range t {
  185 		vals = append(vals, viewName{singular: k, plural: v})
  186 	}
  187 	sort.Slice(vals, func(i, j int) bool {
  188 		return vals[i].plural < vals[j].plural
  189 	})
  190 
  191 	return vals
  192 }
  193 
  194 type siteConfigHolder struct {
  195 	sitemap          config.Sitemap
  196 	taxonomiesConfig taxonomiesConfig
  197 	timeout          time.Duration
  198 	hasCJKLanguage   bool
  199 	enableEmoji      bool
  200 }
  201 
  202 // Lazily loaded site dependencies.
  203 type siteInit struct {
  204 	prevNext          *lazy.Init
  205 	prevNextInSection *lazy.Init
  206 	menus             *lazy.Init
  207 	taxonomies        *lazy.Init
  208 }
  209 
  210 func (init *siteInit) Reset() {
  211 	init.prevNext.Reset()
  212 	init.prevNextInSection.Reset()
  213 	init.menus.Reset()
  214 	init.taxonomies.Reset()
  215 }
  216 
  217 func (s *Site) initInit(init *lazy.Init, pctx pageContext) bool {
  218 	_, err := init.Do()
  219 	if err != nil {
  220 		s.h.FatalError(pctx.wrapError(err))
  221 	}
  222 	return err == nil
  223 }
  224 
  225 func (s *Site) prepareInits() {
  226 	s.init = &siteInit{}
  227 
  228 	var init lazy.Init
  229 
  230 	s.init.prevNext = init.Branch(func() (any, error) {
  231 		regularPages := s.RegularPages()
  232 		for i, p := range regularPages {
  233 			np, ok := p.(nextPrevProvider)
  234 			if !ok {
  235 				continue
  236 			}
  237 
  238 			pos := np.getNextPrev()
  239 			if pos == nil {
  240 				continue
  241 			}
  242 
  243 			pos.nextPage = nil
  244 			pos.prevPage = nil
  245 
  246 			if i > 0 {
  247 				pos.nextPage = regularPages[i-1]
  248 			}
  249 
  250 			if i < len(regularPages)-1 {
  251 				pos.prevPage = regularPages[i+1]
  252 			}
  253 		}
  254 		return nil, nil
  255 	})
  256 
  257 	s.init.prevNextInSection = init.Branch(func() (any, error) {
  258 		var sections page.Pages
  259 		s.home.treeRef.m.collectSectionsRecursiveIncludingSelf(pageMapQuery{Prefix: s.home.treeRef.key}, func(n *contentNode) {
  260 			sections = append(sections, n.p)
  261 		})
  262 
  263 		setNextPrev := func(pas page.Pages) {
  264 			for i, p := range pas {
  265 				np, ok := p.(nextPrevInSectionProvider)
  266 				if !ok {
  267 					continue
  268 				}
  269 
  270 				pos := np.getNextPrevInSection()
  271 				if pos == nil {
  272 					continue
  273 				}
  274 
  275 				pos.nextPage = nil
  276 				pos.prevPage = nil
  277 
  278 				if i > 0 {
  279 					pos.nextPage = pas[i-1]
  280 				}
  281 
  282 				if i < len(pas)-1 {
  283 					pos.prevPage = pas[i+1]
  284 				}
  285 			}
  286 		}
  287 
  288 		for _, sect := range sections {
  289 			treeRef := sect.(treeRefProvider).getTreeRef()
  290 
  291 			var pas page.Pages
  292 			treeRef.m.collectPages(pageMapQuery{Prefix: treeRef.key + cmBranchSeparator}, func(c *contentNode) {
  293 				pas = append(pas, c.p)
  294 			})
  295 			page.SortByDefault(pas)
  296 
  297 			setNextPrev(pas)
  298 		}
  299 
  300 		// The root section only goes one level down.
  301 		treeRef := s.home.getTreeRef()
  302 
  303 		var pas page.Pages
  304 		treeRef.m.collectPages(pageMapQuery{Prefix: treeRef.key + cmBranchSeparator}, func(c *contentNode) {
  305 			pas = append(pas, c.p)
  306 		})
  307 		page.SortByDefault(pas)
  308 
  309 		setNextPrev(pas)
  310 
  311 		return nil, nil
  312 	})
  313 
  314 	s.init.menus = init.Branch(func() (any, error) {
  315 		s.assembleMenus()
  316 		return nil, nil
  317 	})
  318 
  319 	s.init.taxonomies = init.Branch(func() (any, error) {
  320 		err := s.pageMap.assembleTaxonomies()
  321 		return nil, err
  322 	})
  323 }
  324 
  325 type siteRenderingContext struct {
  326 	output.Format
  327 }
  328 
  329 func (s *Site) Menus() navigation.Menus {
  330 	s.init.menus.Do()
  331 	return s.menus
  332 }
  333 
  334 func (s *Site) initRenderFormats() {
  335 	formatSet := make(map[string]bool)
  336 	formats := output.Formats{}
  337 	s.pageMap.pageTrees.WalkRenderable(func(s string, n *contentNode) bool {
  338 		for _, f := range n.p.m.configuredOutputFormats {
  339 			if !formatSet[f.Name] {
  340 				formats = append(formats, f)
  341 				formatSet[f.Name] = true
  342 			}
  343 		}
  344 		return false
  345 	})
  346 
  347 	// Add the per kind configured output formats
  348 	for _, kind := range allKindsInPages {
  349 		if siteFormats, found := s.outputFormats[kind]; found {
  350 			for _, f := range siteFormats {
  351 				if !formatSet[f.Name] {
  352 					formats = append(formats, f)
  353 					formatSet[f.Name] = true
  354 				}
  355 			}
  356 		}
  357 	}
  358 
  359 	sort.Sort(formats)
  360 	s.renderFormats = formats
  361 }
  362 
  363 func (s *Site) GetRelatedDocsHandler() *page.RelatedDocsHandler {
  364 	return s.relatedDocsHandler
  365 }
  366 
  367 func (s *Site) Language() *langs.Language {
  368 	return s.language
  369 }
  370 
  371 func (s *Site) isEnabled(kind string) bool {
  372 	if kind == kindUnknown {
  373 		panic("Unknown kind")
  374 	}
  375 	return !s.disabledKinds[kind]
  376 }
  377 
  378 // reset returns a new Site prepared for rebuild.
  379 func (s *Site) reset() *Site {
  380 	return &Site{
  381 		Deps:                s.Deps,
  382 		disabledKinds:       s.disabledKinds,
  383 		titleFunc:           s.titleFunc,
  384 		relatedDocsHandler:  s.relatedDocsHandler.Clone(),
  385 		siteRefLinker:       s.siteRefLinker,
  386 		outputFormats:       s.outputFormats,
  387 		rc:                  s.rc,
  388 		outputFormatsConfig: s.outputFormatsConfig,
  389 		frontmatterHandler:  s.frontmatterHandler,
  390 		mediaTypesConfig:    s.mediaTypesConfig,
  391 		language:            s.language,
  392 		siteBucket:          s.siteBucket,
  393 		h:                   s.h,
  394 		publisher:           s.publisher,
  395 		siteConfigConfig:    s.siteConfigConfig,
  396 		init:                s.init,
  397 		PageCollections:     s.PageCollections,
  398 		siteCfg:             s.siteCfg,
  399 	}
  400 }
  401 
  402 // newSite creates a new site with the given configuration.
  403 func newSite(cfg deps.DepsCfg) (*Site, error) {
  404 	if cfg.Language == nil {
  405 		cfg.Language = langs.NewDefaultLanguage(cfg.Cfg)
  406 	}
  407 	if cfg.Logger == nil {
  408 		panic("logger must be set")
  409 	}
  410 
  411 	ignoreErrors := cast.ToStringSlice(cfg.Language.Get("ignoreErrors"))
  412 	ignorableLogger := loggers.NewIgnorableLogger(cfg.Logger, ignoreErrors...)
  413 
  414 	disabledKinds := make(map[string]bool)
  415 	for _, disabled := range cast.ToStringSlice(cfg.Language.Get("disableKinds")) {
  416 		disabledKinds[disabled] = true
  417 	}
  418 
  419 	if disabledKinds["taxonomyTerm"] {
  420 		// Correct from the value it had before Hugo 0.73.0.
  421 		if disabledKinds[page.KindTaxonomy] {
  422 			disabledKinds[page.KindTerm] = true
  423 		} else {
  424 			disabledKinds[page.KindTaxonomy] = true
  425 		}
  426 
  427 		delete(disabledKinds, "taxonomyTerm")
  428 	} else if disabledKinds[page.KindTaxonomy] && !disabledKinds[page.KindTerm] {
  429 		// This is a potentially ambigous situation. It may be correct.
  430 		ignorableLogger.Errorsf(constants.ErrIDAmbigousDisableKindTaxonomy, `You have the value 'taxonomy' in the disabledKinds list. In Hugo 0.73.0 we fixed these to be what most people expect (taxonomy and term).
  431 But this also means that your site configuration may not do what you expect. If it is correct, you can suppress this message by following the instructions below.`)
  432 	}
  433 
  434 	var (
  435 		mediaTypesConfig    []map[string]any
  436 		outputFormatsConfig []map[string]any
  437 
  438 		siteOutputFormatsConfig output.Formats
  439 		siteMediaTypesConfig    media.Types
  440 		err                     error
  441 	)
  442 
  443 	// Add language last, if set, so it gets precedence.
  444 	for _, cfg := range []config.Provider{cfg.Cfg, cfg.Language} {
  445 		if cfg.IsSet("mediaTypes") {
  446 			mediaTypesConfig = append(mediaTypesConfig, cfg.GetStringMap("mediaTypes"))
  447 		}
  448 		if cfg.IsSet("outputFormats") {
  449 			outputFormatsConfig = append(outputFormatsConfig, cfg.GetStringMap("outputFormats"))
  450 		}
  451 	}
  452 
  453 	siteMediaTypesConfig, err = media.DecodeTypes(mediaTypesConfig...)
  454 	if err != nil {
  455 		return nil, err
  456 	}
  457 
  458 	siteOutputFormatsConfig, err = output.DecodeFormats(siteMediaTypesConfig, outputFormatsConfig...)
  459 	if err != nil {
  460 		return nil, err
  461 	}
  462 
  463 	rssDisabled := disabledKinds[kindRSS]
  464 	if rssDisabled {
  465 		// Legacy
  466 		tmp := siteOutputFormatsConfig[:0]
  467 		for _, x := range siteOutputFormatsConfig {
  468 			if !strings.EqualFold(x.Name, "rss") {
  469 				tmp = append(tmp, x)
  470 			}
  471 		}
  472 		siteOutputFormatsConfig = tmp
  473 	}
  474 
  475 	var siteOutputs map[string]any
  476 	if cfg.Language.IsSet("outputs") {
  477 		siteOutputs = cfg.Language.GetStringMap("outputs")
  478 
  479 		// Check and correct taxonomy kinds vs pre Hugo 0.73.0.
  480 		v1, hasTaxonomyTerm := siteOutputs["taxonomyterm"]
  481 		v2, hasTaxonomy := siteOutputs[page.KindTaxonomy]
  482 		_, hasTerm := siteOutputs[page.KindTerm]
  483 		if hasTaxonomy && hasTaxonomyTerm {
  484 			siteOutputs[page.KindTaxonomy] = v1
  485 			siteOutputs[page.KindTerm] = v2
  486 			delete(siteOutputs, "taxonomyTerm")
  487 		} else if hasTaxonomy && !hasTerm {
  488 			// This is a potentially ambigous situation. It may be correct.
  489 			ignorableLogger.Errorsf(constants.ErrIDAmbigousOutputKindTaxonomy, `You have configured output formats for 'taxonomy' in your site configuration. In Hugo 0.73.0 we fixed these to be what most people expect (taxonomy and term).
  490 But this also means that your site configuration may not do what you expect. If it is correct, you can suppress this message by following the instructions below.`)
  491 		}
  492 		if !hasTaxonomy && hasTaxonomyTerm {
  493 			siteOutputs[page.KindTaxonomy] = v1
  494 			delete(siteOutputs, "taxonomyterm")
  495 		}
  496 	}
  497 
  498 	outputFormats, err := createSiteOutputFormats(siteOutputFormatsConfig, siteOutputs, rssDisabled)
  499 	if err != nil {
  500 		return nil, err
  501 	}
  502 
  503 	taxonomies := cfg.Language.GetStringMapString("taxonomies")
  504 
  505 	var relatedContentConfig related.Config
  506 
  507 	if cfg.Language.IsSet("related") {
  508 		relatedContentConfig, err = related.DecodeConfig(cfg.Language.GetParams("related"))
  509 		if err != nil {
  510 			return nil, fmt.Errorf("failed to decode related config: %w", err)
  511 		}
  512 	} else {
  513 		relatedContentConfig = related.DefaultConfig
  514 		if _, found := taxonomies["tag"]; found {
  515 			relatedContentConfig.Add(related.IndexConfig{Name: "tags", Weight: 80})
  516 		}
  517 	}
  518 
  519 	titleFunc := helpers.GetTitleFunc(cfg.Language.GetString("titleCaseStyle"))
  520 
  521 	frontMatterHandler, err := pagemeta.NewFrontmatterHandler(cfg.Logger, cfg.Cfg)
  522 	if err != nil {
  523 		return nil, err
  524 	}
  525 
  526 	timeout := 30 * time.Second
  527 	if cfg.Language.IsSet("timeout") {
  528 		v := cfg.Language.Get("timeout")
  529 		d, err := types.ToDurationE(v)
  530 		if err == nil {
  531 			timeout = d
  532 		}
  533 	}
  534 
  535 	siteConfig := siteConfigHolder{
  536 		sitemap:          config.DecodeSitemap(config.Sitemap{Priority: -1, Filename: "sitemap.xml"}, cfg.Language.GetStringMap("sitemap")),
  537 		taxonomiesConfig: taxonomies,
  538 		timeout:          timeout,
  539 		hasCJKLanguage:   cfg.Language.GetBool("hasCJKLanguage"),
  540 		enableEmoji:      cfg.Language.Cfg.GetBool("enableEmoji"),
  541 	}
  542 
  543 	var siteBucket *pagesMapBucket
  544 	if cfg.Language.IsSet("cascade") {
  545 		var err error
  546 		cascade, err := page.DecodeCascade(cfg.Language.Get("cascade"))
  547 		if err != nil {
  548 			return nil, fmt.Errorf("failed to decode cascade config: %s", err)
  549 		}
  550 
  551 		siteBucket = &pagesMapBucket{
  552 			cascade: cascade,
  553 		}
  554 
  555 	}
  556 
  557 	s := &Site{
  558 		language:      cfg.Language,
  559 		siteBucket:    siteBucket,
  560 		disabledKinds: disabledKinds,
  561 
  562 		outputFormats:       outputFormats,
  563 		outputFormatsConfig: siteOutputFormatsConfig,
  564 		mediaTypesConfig:    siteMediaTypesConfig,
  565 
  566 		siteCfg: siteConfig,
  567 
  568 		titleFunc: titleFunc,
  569 
  570 		rc: &siteRenderingContext{output.HTMLFormat},
  571 
  572 		frontmatterHandler: frontMatterHandler,
  573 		relatedDocsHandler: page.NewRelatedDocsHandler(relatedContentConfig),
  574 	}
  575 
  576 	s.prepareInits()
  577 
  578 	return s, nil
  579 }
  580 
  581 // NewSite creates a new site with the given dependency configuration.
  582 // The site will have a template system loaded and ready to use.
  583 // Note: This is mainly used in single site tests.
  584 func NewSite(cfg deps.DepsCfg) (*Site, error) {
  585 	s, err := newSite(cfg)
  586 	if err != nil {
  587 		return nil, err
  588 	}
  589 
  590 	var l configLoader
  591 	if err = l.applyDeps(cfg, s); err != nil {
  592 		return nil, err
  593 	}
  594 
  595 	return s, nil
  596 }
  597 
  598 // NewSiteDefaultLang creates a new site in the default language.
  599 // The site will have a template system loaded and ready to use.
  600 // Note: This is mainly used in single site tests.
  601 // TODO(bep) test refactor -- remove
  602 func NewSiteDefaultLang(withTemplate ...func(templ tpl.TemplateManager) error) (*Site, error) {
  603 	l := configLoader{cfg: config.New()}
  604 	if err := l.applyConfigDefaults(); err != nil {
  605 		return nil, err
  606 	}
  607 	return newSiteForLang(langs.NewDefaultLanguage(l.cfg), withTemplate...)
  608 }
  609 
  610 // NewEnglishSite creates a new site in English language.
  611 // The site will have a template system loaded and ready to use.
  612 // Note: This is mainly used in single site tests.
  613 // TODO(bep) test refactor -- remove
  614 func NewEnglishSite(withTemplate ...func(templ tpl.TemplateManager) error) (*Site, error) {
  615 	l := configLoader{cfg: config.New()}
  616 	if err := l.applyConfigDefaults(); err != nil {
  617 		return nil, err
  618 	}
  619 	return newSiteForLang(langs.NewLanguage("en", l.cfg), withTemplate...)
  620 }
  621 
  622 // newSiteForLang creates a new site in the given language.
  623 func newSiteForLang(lang *langs.Language, withTemplate ...func(templ tpl.TemplateManager) error) (*Site, error) {
  624 	withTemplates := func(templ tpl.TemplateManager) error {
  625 		for _, wt := range withTemplate {
  626 			if err := wt(templ); err != nil {
  627 				return err
  628 			}
  629 		}
  630 		return nil
  631 	}
  632 
  633 	cfg := deps.DepsCfg{WithTemplate: withTemplates, Cfg: lang}
  634 
  635 	return NewSiteForCfg(cfg)
  636 }
  637 
  638 // NewSiteForCfg creates a new site for the given configuration.
  639 // The site will have a template system loaded and ready to use.
  640 // Note: This is mainly used in single site tests.
  641 func NewSiteForCfg(cfg deps.DepsCfg) (*Site, error) {
  642 	h, err := NewHugoSites(cfg)
  643 	if err != nil {
  644 		return nil, err
  645 	}
  646 	return h.Sites[0], nil
  647 }
  648 
  649 type SiteInfo struct {
  650 	Authors page.AuthorList
  651 	Social  SiteSocial
  652 
  653 	hugoInfo     hugo.Info
  654 	title        string
  655 	RSSLink      string
  656 	Author       map[string]any
  657 	LanguageCode string
  658 	Copyright    string
  659 
  660 	permalinks map[string]string
  661 
  662 	LanguagePrefix string
  663 	Languages      langs.Languages
  664 
  665 	BuildDrafts bool
  666 
  667 	canonifyURLs bool
  668 	relativeURLs bool
  669 	uglyURLs     func(p page.Page) bool
  670 
  671 	owner                          *HugoSites
  672 	s                              *Site
  673 	language                       *langs.Language
  674 	defaultContentLanguageInSubdir bool
  675 	sectionPagesMenu               string
  676 }
  677 
  678 func (s *SiteInfo) Pages() page.Pages {
  679 	return s.s.Pages()
  680 }
  681 
  682 func (s *SiteInfo) RegularPages() page.Pages {
  683 	return s.s.RegularPages()
  684 }
  685 
  686 func (s *SiteInfo) AllPages() page.Pages {
  687 	return s.s.AllPages()
  688 }
  689 
  690 func (s *SiteInfo) AllRegularPages() page.Pages {
  691 	return s.s.AllRegularPages()
  692 }
  693 
  694 func (s *SiteInfo) LastChange() time.Time {
  695 	return s.s.lastmod
  696 }
  697 
  698 func (s *SiteInfo) Title() string {
  699 	return s.title
  700 }
  701 
  702 func (s *SiteInfo) Site() page.Site {
  703 	return s
  704 }
  705 
  706 func (s *SiteInfo) Menus() navigation.Menus {
  707 	return s.s.Menus()
  708 }
  709 
  710 // TODO(bep) type
  711 func (s *SiteInfo) Taxonomies() any {
  712 	return s.s.Taxonomies()
  713 }
  714 
  715 func (s *SiteInfo) Params() maps.Params {
  716 	return s.s.Language().Params()
  717 }
  718 
  719 func (s *SiteInfo) Data() map[string]any {
  720 	return s.s.h.Data()
  721 }
  722 
  723 func (s *SiteInfo) Language() *langs.Language {
  724 	return s.language
  725 }
  726 
  727 func (s *SiteInfo) Config() SiteConfig {
  728 	return s.s.siteConfigConfig
  729 }
  730 
  731 func (s *SiteInfo) Hugo() hugo.Info {
  732 	return s.hugoInfo
  733 }
  734 
  735 // Sites is a convenience method to get all the Hugo sites/languages configured.
  736 func (s *SiteInfo) Sites() page.Sites {
  737 	return s.s.h.siteInfos()
  738 }
  739 
  740 // Current returns the currently rendered Site.
  741 // If that isn't set yet, which is the situation before we start rendering,
  742 // if will return the Site itself.
  743 func (s *SiteInfo) Current() page.Site {
  744 	if s.s.h.currentSite == nil {
  745 		return s
  746 	}
  747 	return s.s.h.currentSite.Info
  748 }
  749 
  750 func (s *SiteInfo) String() string {
  751 	return fmt.Sprintf("Site(%q)", s.title)
  752 }
  753 
  754 func (s *SiteInfo) BaseURL() template.URL {
  755 	return template.URL(s.s.PathSpec.BaseURL.String())
  756 }
  757 
  758 // ServerPort returns the port part of the BaseURL, 0 if none found.
  759 func (s *SiteInfo) ServerPort() int {
  760 	ps := s.s.PathSpec.BaseURL.URL().Port()
  761 	if ps == "" {
  762 		return 0
  763 	}
  764 	p, err := strconv.Atoi(ps)
  765 	if err != nil {
  766 		return 0
  767 	}
  768 	return p
  769 }
  770 
  771 // GoogleAnalytics is kept here for historic reasons.
  772 func (s *SiteInfo) GoogleAnalytics() string {
  773 	return s.Config().Services.GoogleAnalytics.ID
  774 }
  775 
  776 // DisqusShortname is kept here for historic reasons.
  777 func (s *SiteInfo) DisqusShortname() string {
  778 	return s.Config().Services.Disqus.Shortname
  779 }
  780 
  781 // SiteSocial is a place to put social details on a site level. These are the
  782 // standard keys that themes will expect to have available, but can be
  783 // expanded to any others on a per site basis
  784 // github
  785 // facebook
  786 // facebook_admin
  787 // twitter
  788 // twitter_domain
  789 // pinterest
  790 // instagram
  791 // youtube
  792 // linkedin
  793 type SiteSocial map[string]string
  794 
  795 // Param is a convenience method to do lookups in SiteInfo's Params map.
  796 //
  797 // This method is also implemented on Page.
  798 func (s *SiteInfo) Param(key any) (any, error) {
  799 	return resource.Param(s, nil, key)
  800 }
  801 
  802 func (s *SiteInfo) IsMultiLingual() bool {
  803 	return len(s.Languages) > 1
  804 }
  805 
  806 func (s *SiteInfo) IsServer() bool {
  807 	return s.owner.running
  808 }
  809 
  810 type siteRefLinker struct {
  811 	s *Site
  812 
  813 	errorLogger *log.Logger
  814 	notFoundURL string
  815 }
  816 
  817 func newSiteRefLinker(cfg config.Provider, s *Site) (siteRefLinker, error) {
  818 	logger := s.Log.Error()
  819 
  820 	notFoundURL := cfg.GetString("refLinksNotFoundURL")
  821 	errLevel := cfg.GetString("refLinksErrorLevel")
  822 	if strings.EqualFold(errLevel, "warning") {
  823 		logger = s.Log.Warn()
  824 	}
  825 	return siteRefLinker{s: s, errorLogger: logger, notFoundURL: notFoundURL}, nil
  826 }
  827 
  828 func (s siteRefLinker) logNotFound(ref, what string, p page.Page, position text.Position) {
  829 	if position.IsValid() {
  830 		s.errorLogger.Printf("[%s] REF_NOT_FOUND: Ref %q: %s: %s", s.s.Lang(), ref, position.String(), what)
  831 	} else if p == nil {
  832 		s.errorLogger.Printf("[%s] REF_NOT_FOUND: Ref %q: %s", s.s.Lang(), ref, what)
  833 	} else {
  834 		s.errorLogger.Printf("[%s] REF_NOT_FOUND: Ref %q from page %q: %s", s.s.Lang(), ref, p.Pathc(), what)
  835 	}
  836 }
  837 
  838 func (s *siteRefLinker) refLink(ref string, source any, relative bool, outputFormat string) (string, error) {
  839 	p, err := unwrapPage(source)
  840 	if err != nil {
  841 		return "", err
  842 	}
  843 
  844 	var refURL *url.URL
  845 
  846 	ref = filepath.ToSlash(ref)
  847 
  848 	refURL, err = url.Parse(ref)
  849 
  850 	if err != nil {
  851 		return s.notFoundURL, err
  852 	}
  853 
  854 	var target page.Page
  855 	var link string
  856 
  857 	if refURL.Path != "" {
  858 		var err error
  859 		target, err = s.s.getPageRef(p, refURL.Path)
  860 		var pos text.Position
  861 		if err != nil || target == nil {
  862 			if p, ok := source.(text.Positioner); ok {
  863 				pos = p.Position()
  864 			}
  865 		}
  866 
  867 		if err != nil {
  868 			s.logNotFound(refURL.Path, err.Error(), p, pos)
  869 			return s.notFoundURL, nil
  870 		}
  871 
  872 		if target == nil {
  873 			s.logNotFound(refURL.Path, "page not found", p, pos)
  874 			return s.notFoundURL, nil
  875 		}
  876 
  877 		var permalinker Permalinker = target
  878 
  879 		if outputFormat != "" {
  880 			o := target.OutputFormats().Get(outputFormat)
  881 
  882 			if o == nil {
  883 				s.logNotFound(refURL.Path, fmt.Sprintf("output format %q", outputFormat), p, pos)
  884 				return s.notFoundURL, nil
  885 			}
  886 			permalinker = o
  887 		}
  888 
  889 		if relative {
  890 			link = permalinker.RelPermalink()
  891 		} else {
  892 			link = permalinker.Permalink()
  893 		}
  894 	}
  895 
  896 	if refURL.Fragment != "" {
  897 		_ = target
  898 		link = link + "#" + refURL.Fragment
  899 
  900 		if pctx, ok := target.(pageContext); ok {
  901 			if refURL.Path != "" {
  902 				if di, ok := pctx.getContentConverter().(converter.DocumentInfo); ok {
  903 					link = link + di.AnchorSuffix()
  904 				}
  905 			}
  906 		} else if pctx, ok := p.(pageContext); ok {
  907 			if di, ok := pctx.getContentConverter().(converter.DocumentInfo); ok {
  908 				link = link + di.AnchorSuffix()
  909 			}
  910 		}
  911 
  912 	}
  913 
  914 	return link, nil
  915 }
  916 
  917 func (s *Site) running() bool {
  918 	return s.h != nil && s.h.running
  919 }
  920 
  921 func (s *Site) multilingual() *Multilingual {
  922 	return s.h.multilingual
  923 }
  924 
  925 type whatChanged struct {
  926 	source bool
  927 	files  map[string]bool
  928 }
  929 
  930 // RegisterMediaTypes will register the Site's media types in the mime
  931 // package, so it will behave correctly with Hugo's built-in server.
  932 func (s *Site) RegisterMediaTypes() {
  933 	for _, mt := range s.mediaTypesConfig {
  934 		for _, suffix := range mt.Suffixes() {
  935 			_ = mime.AddExtensionType(mt.Delimiter+suffix, mt.Type()+"; charset=utf-8")
  936 		}
  937 	}
  938 }
  939 
  940 func (s *Site) filterFileEvents(events []fsnotify.Event) []fsnotify.Event {
  941 	var filtered []fsnotify.Event
  942 	seen := make(map[fsnotify.Event]bool)
  943 
  944 	for _, ev := range events {
  945 		// Avoid processing the same event twice.
  946 		if seen[ev] {
  947 			continue
  948 		}
  949 		seen[ev] = true
  950 
  951 		if s.SourceSpec.IgnoreFile(ev.Name) {
  952 			continue
  953 		}
  954 
  955 		// Throw away any directories
  956 		isRegular, err := s.SourceSpec.IsRegularSourceFile(ev.Name)
  957 		if err != nil && os.IsNotExist(err) && (ev.Op&fsnotify.Remove == fsnotify.Remove || ev.Op&fsnotify.Rename == fsnotify.Rename) {
  958 			// Force keep of event
  959 			isRegular = true
  960 		}
  961 		if !isRegular {
  962 			continue
  963 		}
  964 
  965 		if runtime.GOOS == "darwin" { // When a file system is HFS+, its filepath is in NFD form.
  966 			ev.Name = norm.NFC.String(ev.Name)
  967 		}
  968 
  969 		filtered = append(filtered, ev)
  970 	}
  971 
  972 	return filtered
  973 }
  974 
  975 func (s *Site) translateFileEvents(events []fsnotify.Event) []fsnotify.Event {
  976 	var filtered []fsnotify.Event
  977 
  978 	eventMap := make(map[string][]fsnotify.Event)
  979 
  980 	// We often get a Remove etc. followed by a Create, a Create followed by a Write.
  981 	// Remove the superfluous events to mage the update logic simpler.
  982 	for _, ev := range events {
  983 		eventMap[ev.Name] = append(eventMap[ev.Name], ev)
  984 	}
  985 
  986 	for _, ev := range events {
  987 		mapped := eventMap[ev.Name]
  988 
  989 		// Keep one
  990 		found := false
  991 		var kept fsnotify.Event
  992 		for i, ev2 := range mapped {
  993 			if i == 0 {
  994 				kept = ev2
  995 			}
  996 
  997 			if ev2.Op&fsnotify.Write == fsnotify.Write {
  998 				kept = ev2
  999 				found = true
 1000 			}
 1001 
 1002 			if !found && ev2.Op&fsnotify.Create == fsnotify.Create {
 1003 				kept = ev2
 1004 			}
 1005 		}
 1006 
 1007 		filtered = append(filtered, kept)
 1008 	}
 1009 
 1010 	return filtered
 1011 }
 1012 
 1013 var (
 1014 	// These are only used for cache busting, so false positives are fine.
 1015 	// We also deliberately do not match for file suffixes to also catch
 1016 	// directory names.
 1017 	// TODO(bep) consider this when completing the relevant PR rewrite on this.
 1018 	cssFileRe   = regexp.MustCompile("(css|sass|scss)")
 1019 	cssConfigRe = regexp.MustCompile(`(postcss|tailwind)\.config\.js`)
 1020 	jsFileRe    = regexp.MustCompile("(js|ts|jsx|tsx)")
 1021 )
 1022 
 1023 // reBuild partially rebuilds a site given the filesystem events.
 1024 // It returns whatever the content source was changed.
 1025 // TODO(bep) clean up/rewrite this method.
 1026 func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) error, events []fsnotify.Event) error {
 1027 	events = s.filterFileEvents(events)
 1028 	events = s.translateFileEvents(events)
 1029 
 1030 	changeIdentities := make(identity.Identities)
 1031 
 1032 	s.Log.Debugf("Rebuild for events %q", events)
 1033 
 1034 	h := s.h
 1035 
 1036 	// First we need to determine what changed
 1037 
 1038 	var (
 1039 		sourceChanged       = []fsnotify.Event{}
 1040 		sourceReallyChanged = []fsnotify.Event{}
 1041 		contentFilesChanged []string
 1042 
 1043 		tmplChanged bool
 1044 		tmplAdded   bool
 1045 		dataChanged bool
 1046 		i18nChanged bool
 1047 
 1048 		sourceFilesChanged = make(map[string]bool)
 1049 
 1050 		// prevent spamming the log on changes
 1051 		logger = helpers.NewDistinctErrorLogger()
 1052 	)
 1053 
 1054 	var cachePartitions []string
 1055 	// Special case
 1056 	// TODO(bep) I have a ongoing branch where I have redone the cache. Consider this there.
 1057 	var (
 1058 		evictCSSRe *regexp.Regexp
 1059 		evictJSRe  *regexp.Regexp
 1060 	)
 1061 
 1062 	for _, ev := range events {
 1063 		if assetsFilename, _ := s.BaseFs.Assets.MakePathRelative(ev.Name); assetsFilename != "" {
 1064 			cachePartitions = append(cachePartitions, resources.ResourceKeyPartitions(assetsFilename)...)
 1065 			if evictCSSRe == nil {
 1066 				if cssFileRe.MatchString(assetsFilename) || cssConfigRe.MatchString(assetsFilename) {
 1067 					evictCSSRe = cssFileRe
 1068 				}
 1069 			}
 1070 			if evictJSRe == nil && jsFileRe.MatchString(assetsFilename) {
 1071 				evictJSRe = jsFileRe
 1072 			}
 1073 		}
 1074 
 1075 		id, found := s.eventToIdentity(ev)
 1076 		if found {
 1077 			changeIdentities[id] = id
 1078 
 1079 			switch id.Type {
 1080 			case files.ComponentFolderContent:
 1081 				logger.Println("Source changed", ev)
 1082 				sourceChanged = append(sourceChanged, ev)
 1083 			case files.ComponentFolderLayouts:
 1084 				tmplChanged = true
 1085 				if !s.Tmpl().HasTemplate(id.Path) {
 1086 					tmplAdded = true
 1087 				}
 1088 				if tmplAdded {
 1089 					logger.Println("Template added", ev)
 1090 				} else {
 1091 					logger.Println("Template changed", ev)
 1092 				}
 1093 
 1094 			case files.ComponentFolderData:
 1095 				logger.Println("Data changed", ev)
 1096 				dataChanged = true
 1097 			case files.ComponentFolderI18n:
 1098 				logger.Println("i18n changed", ev)
 1099 				i18nChanged = true
 1100 
 1101 			}
 1102 		}
 1103 	}
 1104 
 1105 	changed := &whatChanged{
 1106 		source: len(sourceChanged) > 0,
 1107 		files:  sourceFilesChanged,
 1108 	}
 1109 
 1110 	config.whatChanged = changed
 1111 
 1112 	if err := init(config); err != nil {
 1113 		return err
 1114 	}
 1115 
 1116 	// These in memory resource caches will be rebuilt on demand.
 1117 	for _, s := range s.h.Sites {
 1118 		s.ResourceSpec.ResourceCache.DeletePartitions(cachePartitions...)
 1119 		if evictCSSRe != nil {
 1120 			s.ResourceSpec.ResourceCache.DeleteMatches(evictCSSRe)
 1121 		}
 1122 		if evictJSRe != nil {
 1123 			s.ResourceSpec.ResourceCache.DeleteMatches(evictJSRe)
 1124 		}
 1125 	}
 1126 
 1127 	if tmplChanged || i18nChanged {
 1128 		sites := s.h.Sites
 1129 		first := sites[0]
 1130 
 1131 		s.h.init.Reset()
 1132 
 1133 		// TOD(bep) globals clean
 1134 		if err := first.Deps.LoadResources(); err != nil {
 1135 			return err
 1136 		}
 1137 
 1138 		for i := 1; i < len(sites); i++ {
 1139 			site := sites[i]
 1140 			var err error
 1141 			depsCfg := deps.DepsCfg{
 1142 				Language:      site.language,
 1143 				MediaTypes:    site.mediaTypesConfig,
 1144 				OutputFormats: site.outputFormatsConfig,
 1145 			}
 1146 			site.Deps, err = first.Deps.ForLanguage(depsCfg, func(d *deps.Deps) error {
 1147 				d.Site = site.Info
 1148 				return nil
 1149 			})
 1150 			if err != nil {
 1151 				return err
 1152 			}
 1153 		}
 1154 	}
 1155 
 1156 	if dataChanged {
 1157 		s.h.init.data.Reset()
 1158 	}
 1159 
 1160 	for _, ev := range sourceChanged {
 1161 		removed := false
 1162 
 1163 		if ev.Op&fsnotify.Remove == fsnotify.Remove {
 1164 			removed = true
 1165 		}
 1166 
 1167 		// Some editors (Vim) sometimes issue only a Rename operation when writing an existing file
 1168 		// Sometimes a rename operation means that file has been renamed other times it means
 1169 		// it's been updated
 1170 		if ev.Op&fsnotify.Rename == fsnotify.Rename {
 1171 			// If the file is still on disk, it's only been updated, if it's not, it's been moved
 1172 			if ex, err := afero.Exists(s.Fs.Source, ev.Name); !ex || err != nil {
 1173 				removed = true
 1174 			}
 1175 		}
 1176 
 1177 		if removed && files.IsContentFile(ev.Name) {
 1178 			h.removePageByFilename(ev.Name)
 1179 		}
 1180 
 1181 		sourceReallyChanged = append(sourceReallyChanged, ev)
 1182 		sourceFilesChanged[ev.Name] = true
 1183 	}
 1184 
 1185 	if config.ErrRecovery || tmplAdded || dataChanged {
 1186 		h.resetPageState()
 1187 	} else {
 1188 		h.resetPageStateFromEvents(changeIdentities)
 1189 	}
 1190 
 1191 	if len(sourceReallyChanged) > 0 || len(contentFilesChanged) > 0 {
 1192 		var filenamesChanged []string
 1193 		for _, e := range sourceReallyChanged {
 1194 			filenamesChanged = append(filenamesChanged, e.Name)
 1195 		}
 1196 		if len(contentFilesChanged) > 0 {
 1197 			filenamesChanged = append(filenamesChanged, contentFilesChanged...)
 1198 		}
 1199 
 1200 		filenamesChanged = helpers.UniqueStringsReuse(filenamesChanged)
 1201 
 1202 		if err := s.readAndProcessContent(*config, filenamesChanged...); err != nil {
 1203 			return err
 1204 		}
 1205 
 1206 	}
 1207 
 1208 	return nil
 1209 }
 1210 
 1211 func (s *Site) process(config BuildCfg) (err error) {
 1212 	if err = s.initialize(); err != nil {
 1213 		err = fmt.Errorf("initialize: %w", err)
 1214 		return
 1215 	}
 1216 	if err = s.readAndProcessContent(config); err != nil {
 1217 		err = fmt.Errorf("readAndProcessContent: %w", err)
 1218 		return
 1219 	}
 1220 	return err
 1221 }
 1222 
 1223 func (s *Site) render(ctx *siteRenderContext) (err error) {
 1224 	if err := page.Clear(); err != nil {
 1225 		return err
 1226 	}
 1227 
 1228 	if ctx.outIdx == 0 {
 1229 		// Note that even if disableAliases is set, the aliases themselves are
 1230 		// preserved on page. The motivation with this is to be able to generate
 1231 		// 301 redirects in a .htacess file and similar using a custom output format.
 1232 		if !s.Cfg.GetBool("disableAliases") {
 1233 			// Aliases must be rendered before pages.
 1234 			// Some sites, Hugo docs included, have faulty alias definitions that point
 1235 			// to itself or another real page. These will be overwritten in the next
 1236 			// step.
 1237 			if err = s.renderAliases(); err != nil {
 1238 				return
 1239 			}
 1240 		}
 1241 	}
 1242 
 1243 	if err = s.renderPages(ctx); err != nil {
 1244 		return
 1245 	}
 1246 
 1247 	if ctx.outIdx == 0 {
 1248 		if err = s.renderSitemap(); err != nil {
 1249 			return
 1250 		}
 1251 
 1252 		if ctx.multihost {
 1253 			if err = s.renderRobotsTXT(); err != nil {
 1254 				return
 1255 			}
 1256 		}
 1257 
 1258 		if err = s.render404(); err != nil {
 1259 			return
 1260 		}
 1261 	}
 1262 
 1263 	if !ctx.renderSingletonPages() {
 1264 		return
 1265 	}
 1266 
 1267 	if err = s.renderMainLanguageRedirect(); err != nil {
 1268 		return
 1269 	}
 1270 
 1271 	return
 1272 }
 1273 
 1274 func (s *Site) Initialise() (err error) {
 1275 	return s.initialize()
 1276 }
 1277 
 1278 func (s *Site) initialize() (err error) {
 1279 	return s.initializeSiteInfo()
 1280 }
 1281 
 1282 // HomeAbsURL is a convenience method giving the absolute URL to the home page.
 1283 func (s *SiteInfo) HomeAbsURL() string {
 1284 	base := ""
 1285 	if s.IsMultiLingual() {
 1286 		base = s.Language().Lang
 1287 	}
 1288 	return s.owner.AbsURL(base, false)
 1289 }
 1290 
 1291 // SitemapAbsURL is a convenience method giving the absolute URL to the sitemap.
 1292 func (s *SiteInfo) SitemapAbsURL() string {
 1293 	p := s.HomeAbsURL()
 1294 	if !strings.HasSuffix(p, "/") {
 1295 		p += "/"
 1296 	}
 1297 	p += s.s.siteCfg.sitemap.Filename
 1298 	return p
 1299 }
 1300 
 1301 func (s *Site) initializeSiteInfo() error {
 1302 	var (
 1303 		lang      = s.language
 1304 		languages langs.Languages
 1305 	)
 1306 
 1307 	if s.h != nil && s.h.multilingual != nil {
 1308 		languages = s.h.multilingual.Languages
 1309 	}
 1310 
 1311 	permalinks := s.Cfg.GetStringMapString("permalinks")
 1312 
 1313 	defaultContentInSubDir := s.Cfg.GetBool("defaultContentLanguageInSubdir")
 1314 	defaultContentLanguage := s.Cfg.GetString("defaultContentLanguage")
 1315 
 1316 	languagePrefix := ""
 1317 	if s.multilingualEnabled() && (defaultContentInSubDir || lang.Lang != defaultContentLanguage) {
 1318 		languagePrefix = "/" + lang.Lang
 1319 	}
 1320 
 1321 	uglyURLs := func(p page.Page) bool {
 1322 		return false
 1323 	}
 1324 
 1325 	v := s.Cfg.Get("uglyURLs")
 1326 	if v != nil {
 1327 		switch vv := v.(type) {
 1328 		case bool:
 1329 			uglyURLs = func(p page.Page) bool {
 1330 				return vv
 1331 			}
 1332 		case string:
 1333 			// Is what be get from CLI (--uglyURLs)
 1334 			vvv := cast.ToBool(vv)
 1335 			uglyURLs = func(p page.Page) bool {
 1336 				return vvv
 1337 			}
 1338 		default:
 1339 			m := maps.ToStringMapBool(v)
 1340 			uglyURLs = func(p page.Page) bool {
 1341 				return m[p.Section()]
 1342 			}
 1343 		}
 1344 	}
 1345 
 1346 	// Assemble dependencies to be used in hugo.Deps.
 1347 	// TODO(bep) another reminder: We need to clean up this Site vs HugoSites construct.
 1348 	var deps []*hugo.Dependency
 1349 	var depFromMod func(m modules.Module) *hugo.Dependency
 1350 	depFromMod = func(m modules.Module) *hugo.Dependency {
 1351 		dep := &hugo.Dependency{
 1352 			Path:    m.Path(),
 1353 			Version: m.Version(),
 1354 			Time:    m.Time(),
 1355 			Vendor:  m.Vendor(),
 1356 		}
 1357 
 1358 		// These are pointers, but this all came from JSON so there's no recursive navigation,
 1359 		// so just create new values.
 1360 		if m.Replace() != nil {
 1361 			dep.Replace = depFromMod(m.Replace())
 1362 		}
 1363 		if m.Owner() != nil {
 1364 			dep.Owner = depFromMod(m.Owner())
 1365 		}
 1366 		return dep
 1367 	}
 1368 	for _, m := range s.Paths.AllModules {
 1369 		deps = append(deps, depFromMod(m))
 1370 	}
 1371 
 1372 	s.Info = &SiteInfo{
 1373 		title:                          lang.GetString("title"),
 1374 		Author:                         lang.GetStringMap("author"),
 1375 		Social:                         lang.GetStringMapString("social"),
 1376 		LanguageCode:                   lang.GetString("languageCode"),
 1377 		Copyright:                      lang.GetString("copyright"),
 1378 		language:                       lang,
 1379 		LanguagePrefix:                 languagePrefix,
 1380 		Languages:                      languages,
 1381 		defaultContentLanguageInSubdir: defaultContentInSubDir,
 1382 		sectionPagesMenu:               lang.GetString("sectionPagesMenu"),
 1383 		BuildDrafts:                    s.Cfg.GetBool("buildDrafts"),
 1384 		canonifyURLs:                   s.Cfg.GetBool("canonifyURLs"),
 1385 		relativeURLs:                   s.Cfg.GetBool("relativeURLs"),
 1386 		uglyURLs:                       uglyURLs,
 1387 		permalinks:                     permalinks,
 1388 		owner:                          s.h,
 1389 		s:                              s,
 1390 		hugoInfo:                       hugo.NewInfo(s.Cfg.GetString("environment"), deps),
 1391 	}
 1392 
 1393 	rssOutputFormat, found := s.outputFormats[page.KindHome].GetByName(output.RSSFormat.Name)
 1394 
 1395 	if found {
 1396 		s.Info.RSSLink = s.permalink(rssOutputFormat.BaseFilename())
 1397 	}
 1398 
 1399 	return nil
 1400 }
 1401 
 1402 func (s *Site) eventToIdentity(e fsnotify.Event) (identity.PathIdentity, bool) {
 1403 	for _, fs := range s.BaseFs.SourceFilesystems.FileSystems() {
 1404 		if p := fs.Path(e.Name); p != "" {
 1405 			return identity.NewPathIdentity(fs.Name, filepath.ToSlash(p)), true
 1406 		}
 1407 	}
 1408 	return identity.PathIdentity{}, false
 1409 }
 1410 
 1411 func (s *Site) readAndProcessContent(buildConfig BuildCfg, filenames ...string) error {
 1412 	sourceSpec := source.NewSourceSpec(s.PathSpec, buildConfig.ContentInclusionFilter, s.BaseFs.Content.Fs)
 1413 
 1414 	proc := newPagesProcessor(s.h, sourceSpec)
 1415 
 1416 	c := newPagesCollector(sourceSpec, s.h.getContentMaps(), s.Log, s.h.ContentChanges, proc, filenames...)
 1417 
 1418 	if err := c.Collect(); err != nil {
 1419 		return err
 1420 	}
 1421 
 1422 	return nil
 1423 }
 1424 
 1425 func (s *Site) getMenusFromConfig() navigation.Menus {
 1426 	ret := navigation.Menus{}
 1427 
 1428 	if menus := s.language.GetStringMap("menus"); menus != nil {
 1429 		for name, menu := range menus {
 1430 			m, err := cast.ToSliceE(menu)
 1431 			if err != nil {
 1432 				s.Log.Errorf("menus in site config contain errors\n")
 1433 				s.Log.Errorln(err)
 1434 			} else {
 1435 				handleErr := func(err error) {
 1436 					if err == nil {
 1437 						return
 1438 					}
 1439 					s.Log.Errorf("menus in site config contain errors\n")
 1440 					s.Log.Errorln(err)
 1441 				}
 1442 
 1443 				for _, entry := range m {
 1444 					s.Log.Debugf("found menu: %q, in site config\n", name)
 1445 
 1446 					menuEntry := navigation.MenuEntry{Menu: name}
 1447 					ime, err := maps.ToStringMapE(entry)
 1448 					handleErr(err)
 1449 
 1450 					err = menuEntry.MarshallMap(ime)
 1451 					handleErr(err)
 1452 
 1453 					// TODO(bep) clean up all of this
 1454 					menuEntry.ConfiguredURL = s.Info.createNodeMenuEntryURL(menuEntry.ConfiguredURL)
 1455 
 1456 					if ret[name] == nil {
 1457 						ret[name] = navigation.Menu{}
 1458 					}
 1459 					ret[name] = ret[name].Add(&menuEntry)
 1460 				}
 1461 			}
 1462 		}
 1463 		return ret
 1464 	}
 1465 	return ret
 1466 }
 1467 
 1468 func (s *SiteInfo) createNodeMenuEntryURL(in string) string {
 1469 	if !strings.HasPrefix(in, "/") {
 1470 		return in
 1471 	}
 1472 	// make it match the nodes
 1473 	menuEntryURL := in
 1474 	menuEntryURL = helpers.SanitizeURLKeepTrailingSlash(s.s.PathSpec.URLize(menuEntryURL))
 1475 	if !s.canonifyURLs {
 1476 		menuEntryURL = paths.AddContextRoot(s.s.PathSpec.BaseURL.String(), menuEntryURL)
 1477 	}
 1478 	return menuEntryURL
 1479 }
 1480 
 1481 func (s *Site) assembleMenus() {
 1482 	s.menus = make(navigation.Menus)
 1483 
 1484 	type twoD struct {
 1485 		MenuName, EntryName string
 1486 	}
 1487 	flat := map[twoD]*navigation.MenuEntry{}
 1488 	children := map[twoD]navigation.Menu{}
 1489 
 1490 	// add menu entries from config to flat hash
 1491 	menuConfig := s.getMenusFromConfig()
 1492 	for name, menu := range menuConfig {
 1493 		for _, me := range menu {
 1494 			if types.IsNil(me.Page) && me.PageRef != "" {
 1495 				// Try to resolve the page.
 1496 				me.Page, _ = s.getPageNew(nil, me.PageRef)
 1497 			}
 1498 			flat[twoD{name, me.KeyName()}] = me
 1499 		}
 1500 	}
 1501 
 1502 	sectionPagesMenu := s.Info.sectionPagesMenu
 1503 
 1504 	if sectionPagesMenu != "" {
 1505 		s.pageMap.sections.Walk(func(s string, v any) bool {
 1506 			p := v.(*contentNode).p
 1507 			if p.IsHome() {
 1508 				return false
 1509 			}
 1510 			// From Hugo 0.22 we have nested sections, but until we get a
 1511 			// feel of how that would work in this setting, let us keep
 1512 			// this menu for the top level only.
 1513 			id := p.Section()
 1514 			if _, ok := flat[twoD{sectionPagesMenu, id}]; ok {
 1515 				return false
 1516 			}
 1517 
 1518 			me := navigation.MenuEntry{
 1519 				Identifier: id,
 1520 				Name:       p.LinkTitle(),
 1521 				Weight:     p.Weight(),
 1522 				Page:       p,
 1523 			}
 1524 			flat[twoD{sectionPagesMenu, me.KeyName()}] = &me
 1525 
 1526 			return false
 1527 		})
 1528 	}
 1529 
 1530 	// Add menu entries provided by pages
 1531 	s.pageMap.pageTrees.WalkRenderable(func(ss string, n *contentNode) bool {
 1532 		p := n.p
 1533 
 1534 		for name, me := range p.pageMenus.menus() {
 1535 			if _, ok := flat[twoD{name, me.KeyName()}]; ok {
 1536 				err := p.wrapError(fmt.Errorf("duplicate menu entry with identifier %q in menu %q", me.KeyName(), name))
 1537 				s.Log.Warnln(err)
 1538 				continue
 1539 			}
 1540 			flat[twoD{name, me.KeyName()}] = me
 1541 		}
 1542 
 1543 		return false
 1544 	})
 1545 
 1546 	// Create Children Menus First
 1547 	for _, e := range flat {
 1548 		if e.Parent != "" {
 1549 			children[twoD{e.Menu, e.Parent}] = children[twoD{e.Menu, e.Parent}].Add(e)
 1550 		}
 1551 	}
 1552 
 1553 	// Placing Children in Parents (in flat)
 1554 	for p, childmenu := range children {
 1555 		_, ok := flat[twoD{p.MenuName, p.EntryName}]
 1556 		if !ok {
 1557 			// if parent does not exist, create one without a URL
 1558 			flat[twoD{p.MenuName, p.EntryName}] = &navigation.MenuEntry{Name: p.EntryName}
 1559 		}
 1560 		flat[twoD{p.MenuName, p.EntryName}].Children = childmenu
 1561 	}
 1562 
 1563 	// Assembling Top Level of Tree
 1564 	for menu, e := range flat {
 1565 		if e.Parent == "" {
 1566 			_, ok := s.menus[menu.MenuName]
 1567 			if !ok {
 1568 				s.menus[menu.MenuName] = navigation.Menu{}
 1569 			}
 1570 			s.menus[menu.MenuName] = s.menus[menu.MenuName].Add(e)
 1571 		}
 1572 	}
 1573 }
 1574 
 1575 // get any language code to prefix the target file path with.
 1576 func (s *Site) getLanguageTargetPathLang(alwaysInSubDir bool) string {
 1577 	if s.h.IsMultihost() {
 1578 		return s.Language().Lang
 1579 	}
 1580 
 1581 	return s.getLanguagePermalinkLang(alwaysInSubDir)
 1582 }
 1583 
 1584 // get any lanaguagecode to prefix the relative permalink with.
 1585 func (s *Site) getLanguagePermalinkLang(alwaysInSubDir bool) string {
 1586 	if !s.Info.IsMultiLingual() || s.h.IsMultihost() {
 1587 		return ""
 1588 	}
 1589 
 1590 	if alwaysInSubDir {
 1591 		return s.Language().Lang
 1592 	}
 1593 
 1594 	isDefault := s.Language().Lang == s.multilingual().DefaultLang.Lang
 1595 
 1596 	if !isDefault || s.Info.defaultContentLanguageInSubdir {
 1597 		return s.Language().Lang
 1598 	}
 1599 
 1600 	return ""
 1601 }
 1602 
 1603 func (s *Site) getTaxonomyKey(key string) string {
 1604 	if s.PathSpec.DisablePathToLower {
 1605 		return s.PathSpec.MakePath(key)
 1606 	}
 1607 	return strings.ToLower(s.PathSpec.MakePath(key))
 1608 }
 1609 
 1610 // Prepare site for a new full build.
 1611 func (s *Site) resetBuildState(sourceChanged bool) {
 1612 	s.relatedDocsHandler = s.relatedDocsHandler.Clone()
 1613 	s.init.Reset()
 1614 
 1615 	if sourceChanged {
 1616 		s.pageMap.contentMap.pageReverseIndex.Reset()
 1617 		s.PageCollections = newPageCollections(s.pageMap)
 1618 		s.pageMap.withEveryBundlePage(func(p *pageState) bool {
 1619 			p.pagePages = &pagePages{}
 1620 			if p.bucket != nil {
 1621 				p.bucket.pagesMapBucketPages = &pagesMapBucketPages{}
 1622 			}
 1623 			p.parent = nil
 1624 			p.Scratcher = maps.NewScratcher()
 1625 			return false
 1626 		})
 1627 	} else {
 1628 		s.pageMap.withEveryBundlePage(func(p *pageState) bool {
 1629 			p.Scratcher = maps.NewScratcher()
 1630 			return false
 1631 		})
 1632 	}
 1633 }
 1634 
 1635 func (s *Site) errorCollator(results <-chan error, errs chan<- error) {
 1636 	var errors []error
 1637 	for e := range results {
 1638 		errors = append(errors, e)
 1639 	}
 1640 
 1641 	errs <- s.h.pickOneAndLogTheRest(errors)
 1642 
 1643 	close(errs)
 1644 }
 1645 
 1646 // GetPage looks up a page of a given type for the given ref.
 1647 // In Hugo <= 0.44 you had to add Page Kind (section, home) etc. as the first
 1648 // argument and then either a unix styled path (with or without a leading slash))
 1649 // or path elements separated.
 1650 // When we now remove the Kind from this API, we need to make the transition as painless
 1651 // as possible for existing sites. Most sites will use {{ .Site.GetPage "section" "my/section" }},
 1652 // i.e. 2 arguments, so we test for that.
 1653 func (s *SiteInfo) GetPage(ref ...string) (page.Page, error) {
 1654 	p, err := s.s.getPageOldVersion(ref...)
 1655 
 1656 	if p == nil {
 1657 		// The nil struct has meaning in some situations, mostly to avoid breaking
 1658 		// existing sites doing $nilpage.IsDescendant($p), which will always return
 1659 		// false.
 1660 		p = page.NilPage
 1661 	}
 1662 
 1663 	return p, err
 1664 }
 1665 
 1666 func (s *SiteInfo) GetPageWithTemplateInfo(info tpl.Info, ref ...string) (page.Page, error) {
 1667 	p, err := s.GetPage(ref...)
 1668 	if p != nil {
 1669 		// Track pages referenced by templates/shortcodes
 1670 		// when in server mode.
 1671 		if im, ok := info.(identity.Manager); ok {
 1672 			im.Add(p)
 1673 		}
 1674 	}
 1675 	return p, err
 1676 }
 1677 
 1678 func (s *Site) permalink(link string) string {
 1679 	return s.PathSpec.PermalinkForBaseURL(link, s.PathSpec.BaseURL.String())
 1680 }
 1681 
 1682 func (s *Site) absURLPath(targetPath string) string {
 1683 	var path string
 1684 	if s.Info.relativeURLs {
 1685 		path = helpers.GetDottedRelativePath(targetPath)
 1686 	} else {
 1687 		url := s.PathSpec.BaseURL.String()
 1688 		if !strings.HasSuffix(url, "/") {
 1689 			url += "/"
 1690 		}
 1691 		path = url
 1692 	}
 1693 
 1694 	return path
 1695 }
 1696 
 1697 func (s *Site) lookupLayouts(layouts ...string) tpl.Template {
 1698 	for _, l := range layouts {
 1699 		if templ, found := s.Tmpl().Lookup(l); found {
 1700 			return templ
 1701 		}
 1702 	}
 1703 
 1704 	return nil
 1705 }
 1706 
 1707 func (s *Site) renderAndWriteXML(statCounter *uint64, name string, targetPath string, d any, templ tpl.Template) error {
 1708 	s.Log.Debugf("Render XML for %q to %q", name, targetPath)
 1709 	renderBuffer := bp.GetBuffer()
 1710 	defer bp.PutBuffer(renderBuffer)
 1711 
 1712 	if err := s.renderForTemplate(name, "", d, renderBuffer, templ); err != nil {
 1713 		return err
 1714 	}
 1715 
 1716 	pd := publisher.Descriptor{
 1717 		Src:         renderBuffer,
 1718 		TargetPath:  targetPath,
 1719 		StatCounter: statCounter,
 1720 		// For the minification part of XML,
 1721 		// we currently only use the MIME type.
 1722 		OutputFormat: output.RSSFormat,
 1723 		AbsURLPath:   s.absURLPath(targetPath),
 1724 	}
 1725 
 1726 	return s.publisher.Publish(pd)
 1727 }
 1728 
 1729 func (s *Site) renderAndWritePage(statCounter *uint64, name string, targetPath string, p *pageState, templ tpl.Template) error {
 1730 	s.Log.Debugf("Render %s to %q", name, targetPath)
 1731 	s.h.IncrPageRender()
 1732 	renderBuffer := bp.GetBuffer()
 1733 	defer bp.PutBuffer(renderBuffer)
 1734 
 1735 	of := p.outputFormat()
 1736 
 1737 	if err := s.renderForTemplate(p.Kind(), of.Name, p, renderBuffer, templ); err != nil {
 1738 		return err
 1739 	}
 1740 
 1741 	if renderBuffer.Len() == 0 {
 1742 		return nil
 1743 	}
 1744 
 1745 	isHTML := of.IsHTML
 1746 	isRSS := of.Name == "RSS"
 1747 
 1748 	pd := publisher.Descriptor{
 1749 		Src:          renderBuffer,
 1750 		TargetPath:   targetPath,
 1751 		StatCounter:  statCounter,
 1752 		OutputFormat: p.outputFormat(),
 1753 	}
 1754 
 1755 	if isRSS {
 1756 		// Always canonify URLs in RSS
 1757 		pd.AbsURLPath = s.absURLPath(targetPath)
 1758 	} else if isHTML {
 1759 		if s.Info.relativeURLs || s.Info.canonifyURLs {
 1760 			pd.AbsURLPath = s.absURLPath(targetPath)
 1761 		}
 1762 
 1763 		if s.running() && s.Cfg.GetBool("watch") && !s.Cfg.GetBool("disableLiveReload") {
 1764 			pd.LiveReloadBaseURL = s.PathSpec.BaseURL.URL()
 1765 			if s.Cfg.GetInt("liveReloadPort") != -1 {
 1766 				pd.LiveReloadBaseURL.Host = fmt.Sprintf("%s:%d", pd.LiveReloadBaseURL.Hostname(), s.Cfg.GetInt("liveReloadPort"))
 1767 			}
 1768 		}
 1769 
 1770 		// For performance reasons we only inject the Hugo generator tag on the home page.
 1771 		if p.IsHome() {
 1772 			pd.AddHugoGeneratorTag = !s.Cfg.GetBool("disableHugoGeneratorInject")
 1773 		}
 1774 
 1775 	}
 1776 
 1777 	return s.publisher.Publish(pd)
 1778 }
 1779 
 1780 var infoOnMissingLayout = map[string]bool{
 1781 	// The 404 layout is very much optional in Hugo, but we do look for it.
 1782 	"404": true,
 1783 }
 1784 
 1785 // hookRendererTemplate is the canonical implementation of all hooks.ITEMRenderer,
 1786 // where ITEM is the thing being hooked.
 1787 type hookRendererTemplate struct {
 1788 	templateHandler tpl.TemplateHandler
 1789 	identity.SearchProvider
 1790 	templ           tpl.Template
 1791 	resolvePosition func(ctx any) text.Position
 1792 }
 1793 
 1794 func (hr hookRendererTemplate) RenderLink(w io.Writer, ctx hooks.LinkContext) error {
 1795 	return hr.templateHandler.Execute(hr.templ, w, ctx)
 1796 }
 1797 
 1798 func (hr hookRendererTemplate) RenderHeading(w io.Writer, ctx hooks.HeadingContext) error {
 1799 	return hr.templateHandler.Execute(hr.templ, w, ctx)
 1800 }
 1801 
 1802 func (hr hookRendererTemplate) RenderCodeblock(w hugio.FlexiWriter, ctx hooks.CodeblockContext) error {
 1803 	return hr.templateHandler.Execute(hr.templ, w, ctx)
 1804 }
 1805 
 1806 func (hr hookRendererTemplate) ResolvePosition(ctx any) text.Position {
 1807 	return hr.resolvePosition(ctx)
 1808 }
 1809 
 1810 func (hr hookRendererTemplate) IsDefaultCodeBlockRenderer() bool {
 1811 	return false
 1812 }
 1813 
 1814 func (s *Site) renderForTemplate(name, outputFormat string, d any, w io.Writer, templ tpl.Template) (err error) {
 1815 	if templ == nil {
 1816 		s.logMissingLayout(name, "", "", outputFormat)
 1817 		return nil
 1818 	}
 1819 
 1820 	if err = s.Tmpl().Execute(templ, w, d); err != nil {
 1821 		return fmt.Errorf("render of %q failed: %w", name, err)
 1822 	}
 1823 	return
 1824 }
 1825 
 1826 func (s *Site) lookupTemplate(layouts ...string) (tpl.Template, bool) {
 1827 	for _, l := range layouts {
 1828 		if templ, found := s.Tmpl().Lookup(l); found {
 1829 			return templ, true
 1830 		}
 1831 	}
 1832 
 1833 	return nil, false
 1834 }
 1835 
 1836 func (s *Site) publish(statCounter *uint64, path string, r io.Reader, fs afero.Fs) (err error) {
 1837 	s.PathSpec.ProcessingStats.Incr(statCounter)
 1838 
 1839 	return helpers.WriteToDisk(filepath.Clean(path), r, fs)
 1840 }
 1841 
 1842 func (s *Site) kindFromFileInfoOrSections(fi *fileInfo, sections []string) string {
 1843 	if fi.TranslationBaseName() == "_index" {
 1844 		if fi.Dir() == "" {
 1845 			return page.KindHome
 1846 		}
 1847 
 1848 		return s.kindFromSections(sections)
 1849 
 1850 	}
 1851 
 1852 	return page.KindPage
 1853 }
 1854 
 1855 func (s *Site) kindFromSections(sections []string) string {
 1856 	if len(sections) == 0 {
 1857 		return page.KindHome
 1858 	}
 1859 
 1860 	return s.kindFromSectionPath(path.Join(sections...))
 1861 }
 1862 
 1863 func (s *Site) kindFromSectionPath(sectionPath string) string {
 1864 	for _, plural := range s.siteCfg.taxonomiesConfig {
 1865 		if plural == sectionPath {
 1866 			return page.KindTaxonomy
 1867 		}
 1868 
 1869 		if strings.HasPrefix(sectionPath, plural) {
 1870 			return page.KindTerm
 1871 		}
 1872 
 1873 	}
 1874 
 1875 	return page.KindSection
 1876 }
 1877 
 1878 func (s *Site) newPage(
 1879 	n *contentNode,
 1880 	parentbBucket *pagesMapBucket,
 1881 	kind, title string,
 1882 	sections ...string) *pageState {
 1883 	m := map[string]any{}
 1884 	if title != "" {
 1885 		m["title"] = title
 1886 	}
 1887 
 1888 	p, err := newPageFromMeta(
 1889 		n,
 1890 		parentbBucket,
 1891 		m,
 1892 		&pageMeta{
 1893 			s:        s,
 1894 			kind:     kind,
 1895 			sections: sections,
 1896 		})
 1897 	if err != nil {
 1898 		panic(err)
 1899 	}
 1900 
 1901 	return p
 1902 }
 1903 
 1904 func (s *Site) shouldBuild(p page.Page) bool {
 1905 	return shouldBuild(s.BuildFuture, s.BuildExpired,
 1906 		s.BuildDrafts, p.Draft(), p.PublishDate(), p.ExpiryDate())
 1907 }
 1908 
 1909 func shouldBuild(buildFuture bool, buildExpired bool, buildDrafts bool, Draft bool,
 1910 	publishDate time.Time, expiryDate time.Time) bool {
 1911 	if !(buildDrafts || !Draft) {
 1912 		return false
 1913 	}
 1914 	hnow := htime.Now()
 1915 	if !buildFuture && !publishDate.IsZero() && publishDate.After(hnow) {
 1916 		return false
 1917 	}
 1918 	if !buildExpired && !expiryDate.IsZero() && expiryDate.Before(hnow) {
 1919 		return false
 1920 	}
 1921 	return true
 1922 }