github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/hugolib/site.go (about)

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