github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/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  	"sort"
    28  	"strconv"
    29  	"strings"
    30  	"time"
    31  
    32  	"github.com/gohugoio/hugo/common/types"
    33  
    34  	"github.com/gohugoio/hugo/common/paths"
    35  
    36  	"github.com/gohugoio/hugo/common/constants"
    37  
    38  	"github.com/gohugoio/hugo/common/loggers"
    39  
    40  	"github.com/gohugoio/hugo/resources"
    41  
    42  	"github.com/gohugoio/hugo/identity"
    43  
    44  	"github.com/gohugoio/hugo/markup/converter/hooks"
    45  
    46  	"github.com/gohugoio/hugo/resources/resource"
    47  
    48  	"github.com/gohugoio/hugo/markup/converter"
    49  
    50  	"github.com/gohugoio/hugo/hugofs/files"
    51  
    52  	"github.com/gohugoio/hugo/common/maps"
    53  
    54  	"github.com/pkg/errors"
    55  
    56  	"github.com/gohugoio/hugo/common/text"
    57  
    58  	"github.com/gohugoio/hugo/common/hugo"
    59  	"github.com/gohugoio/hugo/publisher"
    60  	_errors "github.com/pkg/errors"
    61  
    62  	"github.com/gohugoio/hugo/langs"
    63  
    64  	"github.com/gohugoio/hugo/resources/page"
    65  
    66  	"github.com/gohugoio/hugo/config"
    67  	"github.com/gohugoio/hugo/lazy"
    68  
    69  	"github.com/gohugoio/hugo/media"
    70  
    71  	"github.com/fsnotify/fsnotify"
    72  	bp "github.com/gohugoio/hugo/bufferpool"
    73  	"github.com/gohugoio/hugo/deps"
    74  	"github.com/gohugoio/hugo/helpers"
    75  	"github.com/gohugoio/hugo/navigation"
    76  	"github.com/gohugoio/hugo/output"
    77  	"github.com/gohugoio/hugo/related"
    78  	"github.com/gohugoio/hugo/resources/page/pagemeta"
    79  	"github.com/gohugoio/hugo/source"
    80  	"github.com/gohugoio/hugo/tpl"
    81  
    82  	"github.com/spf13/afero"
    83  	"github.com/spf13/cast"
    84  )
    85  
    86  // Site contains all the information relevant for constructing a static
    87  // site.  The basic flow of information is as follows:
    88  //
    89  // 1. A list of Files is parsed and then converted into Pages.
    90  //
    91  // 2. Pages contain sections (based on the file they were generated from),
    92  //    aliases and slugs (included in a pages frontmatter) which are the
    93  //    various targets that will get generated.  There will be canonical
    94  //    listing.  The canonical path can be overruled based on a pattern.
    95  //
    96  // 3. Taxonomies are created via configuration and will present some aspect of
    97  //    the final page and typically a perm url.
    98  //
    99  // 4. All Pages are passed through a template based on their desired
   100  //    layout based on numerous different elements.
   101  //
   102  // 5. The entire collection of files is written to disk.
   103  type Site struct {
   104  
   105  	// The owning container. When multiple languages, there will be multiple
   106  	// sites .
   107  	h *HugoSites
   108  
   109  	*PageCollections
   110  
   111  	taxonomies TaxonomyList
   112  
   113  	Sections Taxonomy
   114  	Info     *SiteInfo
   115  
   116  	language   *langs.Language
   117  	siteBucket *pagesMapBucket
   118  
   119  	siteCfg siteConfigHolder
   120  
   121  	disabledKinds map[string]bool
   122  
   123  	enableInlineShortcodes 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() (interface{}, 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() (interface{}, 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() (interface{}, error) {
   315  		s.assembleMenus()
   316  		return nil, nil
   317  	})
   318  
   319  	s.init.taxonomies = init.Branch(func() (interface{}, 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  		enableInlineShortcodes: s.enableInlineShortcodes,
   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  		enableInlineShortcodes: cfg.Language.GetBool("enableInlineShortcodes"),
   568  		siteCfg:                siteConfig,
   569  
   570  		titleFunc: titleFunc,
   571  
   572  		rc: &siteRenderingContext{output.HTMLFormat},
   573  
   574  		frontmatterHandler: frontMatterHandler,
   575  		relatedDocsHandler: page.NewRelatedDocsHandler(relatedContentConfig),
   576  	}
   577  
   578  	s.prepareInits()
   579  
   580  	return s, nil
   581  }
   582  
   583  // NewSite creates a new site with the given dependency configuration.
   584  // The site will have a template system loaded and ready to use.
   585  // Note: This is mainly used in single site tests.
   586  func NewSite(cfg deps.DepsCfg) (*Site, error) {
   587  	s, err := newSite(cfg)
   588  	if err != nil {
   589  		return nil, err
   590  	}
   591  
   592  	var l configLoader
   593  	if err = l.applyDeps(cfg, s); err != nil {
   594  		return nil, err
   595  	}
   596  
   597  	return s, nil
   598  }
   599  
   600  // NewSiteDefaultLang creates a new site in the default language.
   601  // The site will have a template system loaded and ready to use.
   602  // Note: This is mainly used in single site tests.
   603  // TODO(bep) test refactor -- remove
   604  func NewSiteDefaultLang(withTemplate ...func(templ tpl.TemplateManager) error) (*Site, error) {
   605  	l := configLoader{cfg: config.New()}
   606  	if err := l.applyConfigDefaults(); err != nil {
   607  		return nil, err
   608  	}
   609  	return newSiteForLang(langs.NewDefaultLanguage(l.cfg), withTemplate...)
   610  }
   611  
   612  // NewEnglishSite creates a new site in English language.
   613  // The site will have a template system loaded and ready to use.
   614  // Note: This is mainly used in single site tests.
   615  // TODO(bep) test refactor -- remove
   616  func NewEnglishSite(withTemplate ...func(templ tpl.TemplateManager) error) (*Site, error) {
   617  	l := configLoader{cfg: config.New()}
   618  	if err := l.applyConfigDefaults(); err != nil {
   619  		return nil, err
   620  	}
   621  	return newSiteForLang(langs.NewLanguage("en", l.cfg), withTemplate...)
   622  }
   623  
   624  // newSiteForLang creates a new site in the given language.
   625  func newSiteForLang(lang *langs.Language, withTemplate ...func(templ tpl.TemplateManager) error) (*Site, error) {
   626  	withTemplates := func(templ tpl.TemplateManager) error {
   627  		for _, wt := range withTemplate {
   628  			if err := wt(templ); err != nil {
   629  				return err
   630  			}
   631  		}
   632  		return nil
   633  	}
   634  
   635  	cfg := deps.DepsCfg{WithTemplate: withTemplates, Cfg: lang}
   636  
   637  	return NewSiteForCfg(cfg)
   638  }
   639  
   640  // NewSiteForCfg creates a new site for the given configuration.
   641  // The site will have a template system loaded and ready to use.
   642  // Note: This is mainly used in single site tests.
   643  func NewSiteForCfg(cfg deps.DepsCfg) (*Site, error) {
   644  	h, err := NewHugoSites(cfg)
   645  	if err != nil {
   646  		return nil, err
   647  	}
   648  	return h.Sites[0], nil
   649  }
   650  
   651  type SiteInfo struct {
   652  	Authors page.AuthorList
   653  	Social  SiteSocial
   654  
   655  	hugoInfo     hugo.Info
   656  	title        string
   657  	RSSLink      string
   658  	Author       map[string]interface{}
   659  	LanguageCode string
   660  	Copyright    string
   661  
   662  	permalinks map[string]string
   663  
   664  	LanguagePrefix string
   665  	Languages      langs.Languages
   666  
   667  	BuildDrafts bool
   668  
   669  	canonifyURLs bool
   670  	relativeURLs bool
   671  	uglyURLs     func(p page.Page) bool
   672  
   673  	owner                          *HugoSites
   674  	s                              *Site
   675  	language                       *langs.Language
   676  	defaultContentLanguageInSubdir bool
   677  	sectionPagesMenu               string
   678  }
   679  
   680  func (s *SiteInfo) Pages() page.Pages {
   681  	return s.s.Pages()
   682  }
   683  
   684  func (s *SiteInfo) RegularPages() page.Pages {
   685  	return s.s.RegularPages()
   686  }
   687  
   688  func (s *SiteInfo) AllPages() page.Pages {
   689  	return s.s.AllPages()
   690  }
   691  
   692  func (s *SiteInfo) AllRegularPages() page.Pages {
   693  	return s.s.AllRegularPages()
   694  }
   695  
   696  func (s *SiteInfo) Permalinks() map[string]string {
   697  	// Remove in 0.61
   698  	helpers.Deprecated(".Site.Permalinks", "", true)
   699  	return s.permalinks
   700  }
   701  
   702  func (s *SiteInfo) LastChange() time.Time {
   703  	return s.s.lastmod
   704  }
   705  
   706  func (s *SiteInfo) Title() string {
   707  	return s.title
   708  }
   709  
   710  func (s *SiteInfo) Site() page.Site {
   711  	return s
   712  }
   713  
   714  func (s *SiteInfo) Menus() navigation.Menus {
   715  	return s.s.Menus()
   716  }
   717  
   718  // TODO(bep) type
   719  func (s *SiteInfo) Taxonomies() interface{} {
   720  	return s.s.Taxonomies()
   721  }
   722  
   723  func (s *SiteInfo) Params() maps.Params {
   724  	return s.s.Language().Params()
   725  }
   726  
   727  func (s *SiteInfo) Data() map[string]interface{} {
   728  	return s.s.h.Data()
   729  }
   730  
   731  func (s *SiteInfo) Language() *langs.Language {
   732  	return s.language
   733  }
   734  
   735  func (s *SiteInfo) Config() SiteConfig {
   736  	return s.s.siteConfigConfig
   737  }
   738  
   739  func (s *SiteInfo) Hugo() hugo.Info {
   740  	return s.hugoInfo
   741  }
   742  
   743  // Sites is a convenience method to get all the Hugo sites/languages configured.
   744  func (s *SiteInfo) Sites() page.Sites {
   745  	return s.s.h.siteInfos()
   746  }
   747  
   748  func (s *SiteInfo) String() string {
   749  	return fmt.Sprintf("Site(%q)", s.title)
   750  }
   751  
   752  func (s *SiteInfo) BaseURL() template.URL {
   753  	return template.URL(s.s.PathSpec.BaseURL.String())
   754  }
   755  
   756  // ServerPort returns the port part of the BaseURL, 0 if none found.
   757  func (s *SiteInfo) ServerPort() int {
   758  	ps := s.s.PathSpec.BaseURL.URL().Port()
   759  	if ps == "" {
   760  		return 0
   761  	}
   762  	p, err := strconv.Atoi(ps)
   763  	if err != nil {
   764  		return 0
   765  	}
   766  	return p
   767  }
   768  
   769  // GoogleAnalytics is kept here for historic reasons.
   770  func (s *SiteInfo) GoogleAnalytics() string {
   771  	return s.Config().Services.GoogleAnalytics.ID
   772  }
   773  
   774  // DisqusShortname is kept here for historic reasons.
   775  func (s *SiteInfo) DisqusShortname() string {
   776  	return s.Config().Services.Disqus.Shortname
   777  }
   778  
   779  // SiteSocial is a place to put social details on a site level. These are the
   780  // standard keys that themes will expect to have available, but can be
   781  // expanded to any others on a per site basis
   782  // github
   783  // facebook
   784  // facebook_admin
   785  // twitter
   786  // twitter_domain
   787  // pinterest
   788  // instagram
   789  // youtube
   790  // linkedin
   791  type SiteSocial map[string]string
   792  
   793  // Param is a convenience method to do lookups in SiteInfo's Params map.
   794  //
   795  // This method is also implemented on Page.
   796  func (s *SiteInfo) Param(key interface{}) (interface{}, error) {
   797  	return resource.Param(s, nil, key)
   798  }
   799  
   800  func (s *SiteInfo) IsMultiLingual() bool {
   801  	return len(s.Languages) > 1
   802  }
   803  
   804  func (s *SiteInfo) IsServer() bool {
   805  	return s.owner.running
   806  }
   807  
   808  type siteRefLinker struct {
   809  	s *Site
   810  
   811  	errorLogger *log.Logger
   812  	notFoundURL string
   813  }
   814  
   815  func newSiteRefLinker(cfg config.Provider, s *Site) (siteRefLinker, error) {
   816  	logger := s.Log.Error()
   817  
   818  	notFoundURL := cfg.GetString("refLinksNotFoundURL")
   819  	errLevel := cfg.GetString("refLinksErrorLevel")
   820  	if strings.EqualFold(errLevel, "warning") {
   821  		logger = s.Log.Warn()
   822  	}
   823  	return siteRefLinker{s: s, errorLogger: logger, notFoundURL: notFoundURL}, nil
   824  }
   825  
   826  func (s siteRefLinker) logNotFound(ref, what string, p page.Page, position text.Position) {
   827  	if position.IsValid() {
   828  		s.errorLogger.Printf("[%s] REF_NOT_FOUND: Ref %q: %s: %s", s.s.Lang(), ref, position.String(), what)
   829  	} else if p == nil {
   830  		s.errorLogger.Printf("[%s] REF_NOT_FOUND: Ref %q: %s", s.s.Lang(), ref, what)
   831  	} else {
   832  		s.errorLogger.Printf("[%s] REF_NOT_FOUND: Ref %q from page %q: %s", s.s.Lang(), ref, p.Path(), what)
   833  	}
   834  }
   835  
   836  func (s *siteRefLinker) refLink(ref string, source interface{}, relative bool, outputFormat string) (string, error) {
   837  	p, err := unwrapPage(source)
   838  	if err != nil {
   839  		return "", err
   840  	}
   841  
   842  	var refURL *url.URL
   843  
   844  	ref = filepath.ToSlash(ref)
   845  
   846  	refURL, err = url.Parse(ref)
   847  
   848  	if err != nil {
   849  		return s.notFoundURL, err
   850  	}
   851  
   852  	var target page.Page
   853  	var link string
   854  
   855  	if refURL.Path != "" {
   856  		var err error
   857  		target, err = s.s.getPageRef(p, refURL.Path)
   858  		var pos text.Position
   859  		if err != nil || target == nil {
   860  			if p, ok := source.(text.Positioner); ok {
   861  				pos = p.Position()
   862  			}
   863  		}
   864  
   865  		if err != nil {
   866  			s.logNotFound(refURL.Path, err.Error(), p, pos)
   867  			return s.notFoundURL, nil
   868  		}
   869  
   870  		if target == nil {
   871  			s.logNotFound(refURL.Path, "page not found", p, pos)
   872  			return s.notFoundURL, nil
   873  		}
   874  
   875  		var permalinker Permalinker = target
   876  
   877  		if outputFormat != "" {
   878  			o := target.OutputFormats().Get(outputFormat)
   879  
   880  			if o == nil {
   881  				s.logNotFound(refURL.Path, fmt.Sprintf("output format %q", outputFormat), p, pos)
   882  				return s.notFoundURL, nil
   883  			}
   884  			permalinker = o
   885  		}
   886  
   887  		if relative {
   888  			link = permalinker.RelPermalink()
   889  		} else {
   890  			link = permalinker.Permalink()
   891  		}
   892  	}
   893  
   894  	if refURL.Fragment != "" {
   895  		_ = target
   896  		link = link + "#" + refURL.Fragment
   897  
   898  		if pctx, ok := target.(pageContext); ok {
   899  			if refURL.Path != "" {
   900  				if di, ok := pctx.getContentConverter().(converter.DocumentInfo); ok {
   901  					link = link + di.AnchorSuffix()
   902  				}
   903  			}
   904  		} else if pctx, ok := p.(pageContext); ok {
   905  			if di, ok := pctx.getContentConverter().(converter.DocumentInfo); ok {
   906  				link = link + di.AnchorSuffix()
   907  			}
   908  		}
   909  
   910  	}
   911  
   912  	return link, nil
   913  }
   914  
   915  func (s *Site) running() bool {
   916  	return s.h != nil && s.h.running
   917  }
   918  
   919  func (s *Site) multilingual() *Multilingual {
   920  	return s.h.multilingual
   921  }
   922  
   923  type whatChanged struct {
   924  	source bool
   925  	files  map[string]bool
   926  }
   927  
   928  // RegisterMediaTypes will register the Site's media types in the mime
   929  // package, so it will behave correctly with Hugo's built-in server.
   930  func (s *Site) RegisterMediaTypes() {
   931  	for _, mt := range s.mediaTypesConfig {
   932  		for _, suffix := range mt.Suffixes() {
   933  			_ = mime.AddExtensionType(mt.Delimiter+suffix, mt.Type()+"; charset=utf-8")
   934  		}
   935  	}
   936  }
   937  
   938  func (s *Site) filterFileEvents(events []fsnotify.Event) []fsnotify.Event {
   939  	var filtered []fsnotify.Event
   940  	seen := make(map[fsnotify.Event]bool)
   941  
   942  	for _, ev := range events {
   943  		// Avoid processing the same event twice.
   944  		if seen[ev] {
   945  			continue
   946  		}
   947  		seen[ev] = true
   948  
   949  		if s.SourceSpec.IgnoreFile(ev.Name) {
   950  			continue
   951  		}
   952  
   953  		// Throw away any directories
   954  		isRegular, err := s.SourceSpec.IsRegularSourceFile(ev.Name)
   955  		if err != nil && os.IsNotExist(err) && (ev.Op&fsnotify.Remove == fsnotify.Remove || ev.Op&fsnotify.Rename == fsnotify.Rename) {
   956  			// Force keep of event
   957  			isRegular = true
   958  		}
   959  		if !isRegular {
   960  			continue
   961  		}
   962  
   963  		filtered = append(filtered, ev)
   964  	}
   965  
   966  	return filtered
   967  }
   968  
   969  func (s *Site) translateFileEvents(events []fsnotify.Event) []fsnotify.Event {
   970  	var filtered []fsnotify.Event
   971  
   972  	eventMap := make(map[string][]fsnotify.Event)
   973  
   974  	// We often get a Remove etc. followed by a Create, a Create followed by a Write.
   975  	// Remove the superfluous events to mage the update logic simpler.
   976  	for _, ev := range events {
   977  		eventMap[ev.Name] = append(eventMap[ev.Name], ev)
   978  	}
   979  
   980  	for _, ev := range events {
   981  		mapped := eventMap[ev.Name]
   982  
   983  		// Keep one
   984  		found := false
   985  		var kept fsnotify.Event
   986  		for i, ev2 := range mapped {
   987  			if i == 0 {
   988  				kept = ev2
   989  			}
   990  
   991  			if ev2.Op&fsnotify.Write == fsnotify.Write {
   992  				kept = ev2
   993  				found = true
   994  			}
   995  
   996  			if !found && ev2.Op&fsnotify.Create == fsnotify.Create {
   997  				kept = ev2
   998  			}
   999  		}
  1000  
  1001  		filtered = append(filtered, kept)
  1002  	}
  1003  
  1004  	return filtered
  1005  }
  1006  
  1007  var (
  1008  	// These are only used for cache busting, so false positives are fine.
  1009  	// We also deliberately do not match for file suffixes to also catch
  1010  	// directory names.
  1011  	// TODO(bep) consider this when completing the relevant PR rewrite on this.
  1012  	cssFileRe   = regexp.MustCompile("(css|sass|scss)")
  1013  	cssConfigRe = regexp.MustCompile(`(postcss|tailwind)\.config\.js`)
  1014  	jsFileRe    = regexp.MustCompile("(js|ts|jsx|tsx)")
  1015  )
  1016  
  1017  // reBuild partially rebuilds a site given the filesystem events.
  1018  // It returns whatever the content source was changed.
  1019  // TODO(bep) clean up/rewrite this method.
  1020  func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) error, events []fsnotify.Event) error {
  1021  	events = s.filterFileEvents(events)
  1022  	events = s.translateFileEvents(events)
  1023  
  1024  	changeIdentities := make(identity.Identities)
  1025  
  1026  	s.Log.Debugf("Rebuild for events %q", events)
  1027  
  1028  	h := s.h
  1029  
  1030  	// First we need to determine what changed
  1031  
  1032  	var (
  1033  		sourceChanged       = []fsnotify.Event{}
  1034  		sourceReallyChanged = []fsnotify.Event{}
  1035  		contentFilesChanged []string
  1036  
  1037  		tmplChanged bool
  1038  		tmplAdded   bool
  1039  		dataChanged bool
  1040  		i18nChanged bool
  1041  
  1042  		sourceFilesChanged = make(map[string]bool)
  1043  
  1044  		// prevent spamming the log on changes
  1045  		logger = helpers.NewDistinctErrorLogger()
  1046  	)
  1047  
  1048  	var cachePartitions []string
  1049  	// Special case
  1050  	// TODO(bep) I have a ongoing branch where I have redone the cache. Consider this there.
  1051  	var (
  1052  		evictCSSRe *regexp.Regexp
  1053  		evictJSRe  *regexp.Regexp
  1054  	)
  1055  
  1056  	for _, ev := range events {
  1057  		if assetsFilename, _ := s.BaseFs.Assets.MakePathRelative(ev.Name); assetsFilename != "" {
  1058  			cachePartitions = append(cachePartitions, resources.ResourceKeyPartitions(assetsFilename)...)
  1059  			if evictCSSRe == nil {
  1060  				if cssFileRe.MatchString(assetsFilename) || cssConfigRe.MatchString(assetsFilename) {
  1061  					evictCSSRe = cssFileRe
  1062  				}
  1063  			}
  1064  			if evictJSRe == nil && jsFileRe.MatchString(assetsFilename) {
  1065  				evictJSRe = jsFileRe
  1066  			}
  1067  		}
  1068  
  1069  		id, found := s.eventToIdentity(ev)
  1070  		if found {
  1071  			changeIdentities[id] = id
  1072  
  1073  			switch id.Type {
  1074  			case files.ComponentFolderContent:
  1075  				logger.Println("Source changed", ev)
  1076  				sourceChanged = append(sourceChanged, ev)
  1077  			case files.ComponentFolderLayouts:
  1078  				tmplChanged = true
  1079  				if !s.Tmpl().HasTemplate(id.Path) {
  1080  					tmplAdded = true
  1081  				}
  1082  				if tmplAdded {
  1083  					logger.Println("Template added", ev)
  1084  				} else {
  1085  					logger.Println("Template changed", ev)
  1086  				}
  1087  
  1088  			case files.ComponentFolderData:
  1089  				logger.Println("Data changed", ev)
  1090  				dataChanged = true
  1091  			case files.ComponentFolderI18n:
  1092  				logger.Println("i18n changed", ev)
  1093  				i18nChanged = true
  1094  
  1095  			}
  1096  		}
  1097  	}
  1098  
  1099  	changed := &whatChanged{
  1100  		source: len(sourceChanged) > 0,
  1101  		files:  sourceFilesChanged,
  1102  	}
  1103  
  1104  	config.whatChanged = changed
  1105  
  1106  	if err := init(config); err != nil {
  1107  		return err
  1108  	}
  1109  
  1110  	// These in memory resource caches will be rebuilt on demand.
  1111  	for _, s := range s.h.Sites {
  1112  		s.ResourceSpec.ResourceCache.DeletePartitions(cachePartitions...)
  1113  		if evictCSSRe != nil {
  1114  			s.ResourceSpec.ResourceCache.DeleteMatches(evictCSSRe)
  1115  		}
  1116  		if evictJSRe != nil {
  1117  			s.ResourceSpec.ResourceCache.DeleteMatches(evictJSRe)
  1118  		}
  1119  	}
  1120  
  1121  	if tmplChanged || i18nChanged {
  1122  		sites := s.h.Sites
  1123  		first := sites[0]
  1124  
  1125  		s.h.init.Reset()
  1126  
  1127  		// TOD(bep) globals clean
  1128  		if err := first.Deps.LoadResources(); err != nil {
  1129  			return err
  1130  		}
  1131  
  1132  		for i := 1; i < len(sites); i++ {
  1133  			site := sites[i]
  1134  			var err error
  1135  			depsCfg := deps.DepsCfg{
  1136  				Language:      site.language,
  1137  				MediaTypes:    site.mediaTypesConfig,
  1138  				OutputFormats: site.outputFormatsConfig,
  1139  			}
  1140  			site.Deps, err = first.Deps.ForLanguage(depsCfg, func(d *deps.Deps) error {
  1141  				d.Site = site.Info
  1142  				return nil
  1143  			})
  1144  			if err != nil {
  1145  				return err
  1146  			}
  1147  		}
  1148  	}
  1149  
  1150  	if dataChanged {
  1151  		s.h.init.data.Reset()
  1152  	}
  1153  
  1154  	for _, ev := range sourceChanged {
  1155  		removed := false
  1156  
  1157  		if ev.Op&fsnotify.Remove == fsnotify.Remove {
  1158  			removed = true
  1159  		}
  1160  
  1161  		// Some editors (Vim) sometimes issue only a Rename operation when writing an existing file
  1162  		// Sometimes a rename operation means that file has been renamed other times it means
  1163  		// it's been updated
  1164  		if ev.Op&fsnotify.Rename == fsnotify.Rename {
  1165  			// If the file is still on disk, it's only been updated, if it's not, it's been moved
  1166  			if ex, err := afero.Exists(s.Fs.Source, ev.Name); !ex || err != nil {
  1167  				removed = true
  1168  			}
  1169  		}
  1170  
  1171  		if removed && files.IsContentFile(ev.Name) {
  1172  			h.removePageByFilename(ev.Name)
  1173  		}
  1174  
  1175  		sourceReallyChanged = append(sourceReallyChanged, ev)
  1176  		sourceFilesChanged[ev.Name] = true
  1177  	}
  1178  
  1179  	if config.ErrRecovery || tmplAdded || dataChanged {
  1180  		h.resetPageState()
  1181  	} else {
  1182  		h.resetPageStateFromEvents(changeIdentities)
  1183  	}
  1184  
  1185  	if len(sourceReallyChanged) > 0 || len(contentFilesChanged) > 0 {
  1186  		var filenamesChanged []string
  1187  		for _, e := range sourceReallyChanged {
  1188  			filenamesChanged = append(filenamesChanged, e.Name)
  1189  		}
  1190  		if len(contentFilesChanged) > 0 {
  1191  			filenamesChanged = append(filenamesChanged, contentFilesChanged...)
  1192  		}
  1193  
  1194  		filenamesChanged = helpers.UniqueStringsReuse(filenamesChanged)
  1195  
  1196  		if err := s.readAndProcessContent(*config, filenamesChanged...); err != nil {
  1197  			return err
  1198  		}
  1199  
  1200  	}
  1201  
  1202  	return nil
  1203  }
  1204  
  1205  func (s *Site) process(config BuildCfg) (err error) {
  1206  	if err = s.initialize(); err != nil {
  1207  		err = errors.Wrap(err, "initialize")
  1208  		return
  1209  	}
  1210  	if err = s.readAndProcessContent(config); err != nil {
  1211  		err = errors.Wrap(err, "readAndProcessContent")
  1212  		return
  1213  	}
  1214  	return err
  1215  }
  1216  
  1217  func (s *Site) render(ctx *siteRenderContext) (err error) {
  1218  	if err := page.Clear(); err != nil {
  1219  		return err
  1220  	}
  1221  
  1222  	if ctx.outIdx == 0 {
  1223  		// Note that even if disableAliases is set, the aliases themselves are
  1224  		// preserved on page. The motivation with this is to be able to generate
  1225  		// 301 redirects in a .htacess file and similar using a custom output format.
  1226  		if !s.Cfg.GetBool("disableAliases") {
  1227  			// Aliases must be rendered before pages.
  1228  			// Some sites, Hugo docs included, have faulty alias definitions that point
  1229  			// to itself or another real page. These will be overwritten in the next
  1230  			// step.
  1231  			if err = s.renderAliases(); err != nil {
  1232  				return
  1233  			}
  1234  		}
  1235  	}
  1236  
  1237  	if err = s.renderPages(ctx); err != nil {
  1238  		return
  1239  	}
  1240  
  1241  	if ctx.outIdx == 0 {
  1242  		if err = s.renderSitemap(); err != nil {
  1243  			return
  1244  		}
  1245  
  1246  		if ctx.multihost {
  1247  			if err = s.renderRobotsTXT(); err != nil {
  1248  				return
  1249  			}
  1250  		}
  1251  
  1252  		if err = s.render404(); err != nil {
  1253  			return
  1254  		}
  1255  	}
  1256  
  1257  	if !ctx.renderSingletonPages() {
  1258  		return
  1259  	}
  1260  
  1261  	if err = s.renderMainLanguageRedirect(); err != nil {
  1262  		return
  1263  	}
  1264  
  1265  	return
  1266  }
  1267  
  1268  func (s *Site) Initialise() (err error) {
  1269  	return s.initialize()
  1270  }
  1271  
  1272  func (s *Site) initialize() (err error) {
  1273  	return s.initializeSiteInfo()
  1274  }
  1275  
  1276  // HomeAbsURL is a convenience method giving the absolute URL to the home page.
  1277  func (s *SiteInfo) HomeAbsURL() string {
  1278  	base := ""
  1279  	if s.IsMultiLingual() {
  1280  		base = s.Language().Lang
  1281  	}
  1282  	return s.owner.AbsURL(base, false)
  1283  }
  1284  
  1285  // SitemapAbsURL is a convenience method giving the absolute URL to the sitemap.
  1286  func (s *SiteInfo) SitemapAbsURL() string {
  1287  	p := s.HomeAbsURL()
  1288  	if !strings.HasSuffix(p, "/") {
  1289  		p += "/"
  1290  	}
  1291  	p += s.s.siteCfg.sitemap.Filename
  1292  	return p
  1293  }
  1294  
  1295  func (s *Site) initializeSiteInfo() error {
  1296  	var (
  1297  		lang      = s.language
  1298  		languages langs.Languages
  1299  	)
  1300  
  1301  	if s.h != nil && s.h.multilingual != nil {
  1302  		languages = s.h.multilingual.Languages
  1303  	}
  1304  
  1305  	permalinks := s.Cfg.GetStringMapString("permalinks")
  1306  
  1307  	defaultContentInSubDir := s.Cfg.GetBool("defaultContentLanguageInSubdir")
  1308  	defaultContentLanguage := s.Cfg.GetString("defaultContentLanguage")
  1309  
  1310  	languagePrefix := ""
  1311  	if s.multilingualEnabled() && (defaultContentInSubDir || lang.Lang != defaultContentLanguage) {
  1312  		languagePrefix = "/" + lang.Lang
  1313  	}
  1314  
  1315  	uglyURLs := func(p page.Page) bool {
  1316  		return false
  1317  	}
  1318  
  1319  	v := s.Cfg.Get("uglyURLs")
  1320  	if v != nil {
  1321  		switch vv := v.(type) {
  1322  		case bool:
  1323  			uglyURLs = func(p page.Page) bool {
  1324  				return vv
  1325  			}
  1326  		case string:
  1327  			// Is what be get from CLI (--uglyURLs)
  1328  			vvv := cast.ToBool(vv)
  1329  			uglyURLs = func(p page.Page) bool {
  1330  				return vvv
  1331  			}
  1332  		default:
  1333  			m := maps.ToStringMapBool(v)
  1334  			uglyURLs = func(p page.Page) bool {
  1335  				return m[p.Section()]
  1336  			}
  1337  		}
  1338  	}
  1339  
  1340  	s.Info = &SiteInfo{
  1341  		title:                          lang.GetString("title"),
  1342  		Author:                         lang.GetStringMap("author"),
  1343  		Social:                         lang.GetStringMapString("social"),
  1344  		LanguageCode:                   lang.GetString("languageCode"),
  1345  		Copyright:                      lang.GetString("copyright"),
  1346  		language:                       lang,
  1347  		LanguagePrefix:                 languagePrefix,
  1348  		Languages:                      languages,
  1349  		defaultContentLanguageInSubdir: defaultContentInSubDir,
  1350  		sectionPagesMenu:               lang.GetString("sectionPagesMenu"),
  1351  		BuildDrafts:                    s.Cfg.GetBool("buildDrafts"),
  1352  		canonifyURLs:                   s.Cfg.GetBool("canonifyURLs"),
  1353  		relativeURLs:                   s.Cfg.GetBool("relativeURLs"),
  1354  		uglyURLs:                       uglyURLs,
  1355  		permalinks:                     permalinks,
  1356  		owner:                          s.h,
  1357  		s:                              s,
  1358  		hugoInfo:                       hugo.NewInfo(s.Cfg.GetString("environment")),
  1359  	}
  1360  
  1361  	rssOutputFormat, found := s.outputFormats[page.KindHome].GetByName(output.RSSFormat.Name)
  1362  
  1363  	if found {
  1364  		s.Info.RSSLink = s.permalink(rssOutputFormat.BaseFilename())
  1365  	}
  1366  
  1367  	return nil
  1368  }
  1369  
  1370  func (s *Site) eventToIdentity(e fsnotify.Event) (identity.PathIdentity, bool) {
  1371  	for _, fs := range s.BaseFs.SourceFilesystems.FileSystems() {
  1372  		if p := fs.Path(e.Name); p != "" {
  1373  			return identity.NewPathIdentity(fs.Name, filepath.ToSlash(p)), true
  1374  		}
  1375  	}
  1376  	return identity.PathIdentity{}, false
  1377  }
  1378  
  1379  func (s *Site) readAndProcessContent(buildConfig BuildCfg, filenames ...string) error {
  1380  	sourceSpec := source.NewSourceSpec(s.PathSpec, buildConfig.ContentInclusionFilter, s.BaseFs.Content.Fs)
  1381  
  1382  	proc := newPagesProcessor(s.h, sourceSpec)
  1383  
  1384  	c := newPagesCollector(sourceSpec, s.h.getContentMaps(), s.Log, s.h.ContentChanges, proc, filenames...)
  1385  
  1386  	if err := c.Collect(); err != nil {
  1387  		return err
  1388  	}
  1389  
  1390  	return nil
  1391  }
  1392  
  1393  func (s *Site) getMenusFromConfig() navigation.Menus {
  1394  	ret := navigation.Menus{}
  1395  
  1396  	if menus := s.language.GetStringMap("menus"); menus != nil {
  1397  		for name, menu := range menus {
  1398  			m, err := cast.ToSliceE(menu)
  1399  			if err != nil {
  1400  				s.Log.Errorf("unable to process menus in site config\n")
  1401  				s.Log.Errorln(err)
  1402  			} else {
  1403  				handleErr := func(err error) {
  1404  					if err == nil {
  1405  						return
  1406  					}
  1407  					s.Log.Errorf("unable to process menus in site config\n")
  1408  					s.Log.Errorln(err)
  1409  
  1410  				}
  1411  
  1412  				for _, entry := range m {
  1413  					s.Log.Debugf("found menu: %q, in site config\n", name)
  1414  
  1415  					menuEntry := navigation.MenuEntry{Menu: name}
  1416  					ime, err := maps.ToStringMapE(entry)
  1417  					handleErr(err)
  1418  
  1419  					err = menuEntry.MarshallMap(ime)
  1420  					handleErr(err)
  1421  
  1422  					// TODO(bep) clean up all of this
  1423  					menuEntry.ConfiguredURL = s.Info.createNodeMenuEntryURL(menuEntry.ConfiguredURL)
  1424  
  1425  					if ret[name] == nil {
  1426  						ret[name] = navigation.Menu{}
  1427  					}
  1428  					ret[name] = ret[name].Add(&menuEntry)
  1429  				}
  1430  			}
  1431  		}
  1432  		return ret
  1433  	}
  1434  	return ret
  1435  }
  1436  
  1437  func (s *SiteInfo) createNodeMenuEntryURL(in string) string {
  1438  	if !strings.HasPrefix(in, "/") {
  1439  		return in
  1440  	}
  1441  	// make it match the nodes
  1442  	menuEntryURL := in
  1443  	menuEntryURL = helpers.SanitizeURLKeepTrailingSlash(s.s.PathSpec.URLize(menuEntryURL))
  1444  	if !s.canonifyURLs {
  1445  		menuEntryURL = paths.AddContextRoot(s.s.PathSpec.BaseURL.String(), menuEntryURL)
  1446  	}
  1447  	return menuEntryURL
  1448  }
  1449  
  1450  func (s *Site) assembleMenus() {
  1451  	s.menus = make(navigation.Menus)
  1452  
  1453  	type twoD struct {
  1454  		MenuName, EntryName string
  1455  	}
  1456  	flat := map[twoD]*navigation.MenuEntry{}
  1457  	children := map[twoD]navigation.Menu{}
  1458  
  1459  	// add menu entries from config to flat hash
  1460  	menuConfig := s.getMenusFromConfig()
  1461  	for name, menu := range menuConfig {
  1462  		for _, me := range menu {
  1463  			if types.IsNil(me.Page) && me.PageRef != "" {
  1464  				// Try to resolve the page.
  1465  				me.Page, _ = s.getPageNew(nil, me.PageRef)
  1466  			}
  1467  			flat[twoD{name, me.KeyName()}] = me
  1468  		}
  1469  	}
  1470  
  1471  	sectionPagesMenu := s.Info.sectionPagesMenu
  1472  
  1473  	if sectionPagesMenu != "" {
  1474  		s.pageMap.sections.Walk(func(s string, v interface{}) bool {
  1475  			p := v.(*contentNode).p
  1476  			if p.IsHome() {
  1477  				return false
  1478  			}
  1479  			// From Hugo 0.22 we have nested sections, but until we get a
  1480  			// feel of how that would work in this setting, let us keep
  1481  			// this menu for the top level only.
  1482  			id := p.Section()
  1483  			if _, ok := flat[twoD{sectionPagesMenu, id}]; ok {
  1484  				return false
  1485  			}
  1486  
  1487  			me := navigation.MenuEntry{
  1488  				Identifier: id,
  1489  				Name:       p.LinkTitle(),
  1490  				Weight:     p.Weight(),
  1491  				Page:       p,
  1492  			}
  1493  			flat[twoD{sectionPagesMenu, me.KeyName()}] = &me
  1494  
  1495  			return false
  1496  		})
  1497  	}
  1498  
  1499  	// Add menu entries provided by pages
  1500  	s.pageMap.pageTrees.WalkRenderable(func(ss string, n *contentNode) bool {
  1501  		p := n.p
  1502  
  1503  		for name, me := range p.pageMenus.menus() {
  1504  			if _, ok := flat[twoD{name, me.KeyName()}]; ok {
  1505  				err := p.wrapError(errors.Errorf("duplicate menu entry with identifier %q in menu %q", me.KeyName(), name))
  1506  				s.Log.Warnln(err)
  1507  				continue
  1508  			}
  1509  			flat[twoD{name, me.KeyName()}] = me
  1510  		}
  1511  
  1512  		return false
  1513  	})
  1514  
  1515  	// Create Children Menus First
  1516  	for _, e := range flat {
  1517  		if e.Parent != "" {
  1518  			children[twoD{e.Menu, e.Parent}] = children[twoD{e.Menu, e.Parent}].Add(e)
  1519  		}
  1520  	}
  1521  
  1522  	// Placing Children in Parents (in flat)
  1523  	for p, childmenu := range children {
  1524  		_, ok := flat[twoD{p.MenuName, p.EntryName}]
  1525  		if !ok {
  1526  			// if parent does not exist, create one without a URL
  1527  			flat[twoD{p.MenuName, p.EntryName}] = &navigation.MenuEntry{Name: p.EntryName}
  1528  		}
  1529  		flat[twoD{p.MenuName, p.EntryName}].Children = childmenu
  1530  	}
  1531  
  1532  	// Assembling Top Level of Tree
  1533  	for menu, e := range flat {
  1534  		if e.Parent == "" {
  1535  			_, ok := s.menus[menu.MenuName]
  1536  			if !ok {
  1537  				s.menus[menu.MenuName] = navigation.Menu{}
  1538  			}
  1539  			s.menus[menu.MenuName] = s.menus[menu.MenuName].Add(e)
  1540  		}
  1541  	}
  1542  }
  1543  
  1544  // get any language code to prefix the target file path with.
  1545  func (s *Site) getLanguageTargetPathLang(alwaysInSubDir bool) string {
  1546  	if s.h.IsMultihost() {
  1547  		return s.Language().Lang
  1548  	}
  1549  
  1550  	return s.getLanguagePermalinkLang(alwaysInSubDir)
  1551  }
  1552  
  1553  // get any lanaguagecode to prefix the relative permalink with.
  1554  func (s *Site) getLanguagePermalinkLang(alwaysInSubDir bool) string {
  1555  	if !s.Info.IsMultiLingual() || s.h.IsMultihost() {
  1556  		return ""
  1557  	}
  1558  
  1559  	if alwaysInSubDir {
  1560  		return s.Language().Lang
  1561  	}
  1562  
  1563  	isDefault := s.Language().Lang == s.multilingual().DefaultLang.Lang
  1564  
  1565  	if !isDefault || s.Info.defaultContentLanguageInSubdir {
  1566  		return s.Language().Lang
  1567  	}
  1568  
  1569  	return ""
  1570  }
  1571  
  1572  func (s *Site) getTaxonomyKey(key string) string {
  1573  	if s.PathSpec.DisablePathToLower {
  1574  		return s.PathSpec.MakePath(key)
  1575  	}
  1576  	return strings.ToLower(s.PathSpec.MakePath(key))
  1577  }
  1578  
  1579  // Prepare site for a new full build.
  1580  func (s *Site) resetBuildState(sourceChanged bool) {
  1581  	s.relatedDocsHandler = s.relatedDocsHandler.Clone()
  1582  	s.init.Reset()
  1583  
  1584  	if sourceChanged {
  1585  		s.pageMap.contentMap.pageReverseIndex.Reset()
  1586  		s.PageCollections = newPageCollections(s.pageMap)
  1587  		s.pageMap.withEveryBundlePage(func(p *pageState) bool {
  1588  			p.pagePages = &pagePages{}
  1589  			if p.bucket != nil {
  1590  				p.bucket.pagesMapBucketPages = &pagesMapBucketPages{}
  1591  			}
  1592  			p.parent = nil
  1593  			p.Scratcher = maps.NewScratcher()
  1594  			return false
  1595  		})
  1596  	} else {
  1597  		s.pageMap.withEveryBundlePage(func(p *pageState) bool {
  1598  			p.Scratcher = maps.NewScratcher()
  1599  			return false
  1600  		})
  1601  	}
  1602  }
  1603  
  1604  func (s *Site) errorCollator(results <-chan error, errs chan<- error) {
  1605  	var errors []error
  1606  	for e := range results {
  1607  		errors = append(errors, e)
  1608  	}
  1609  
  1610  	errs <- s.h.pickOneAndLogTheRest(errors)
  1611  
  1612  	close(errs)
  1613  }
  1614  
  1615  // GetPage looks up a page of a given type for the given ref.
  1616  // In Hugo <= 0.44 you had to add Page Kind (section, home) etc. as the first
  1617  // argument and then either a unix styled path (with or without a leading slash))
  1618  // or path elements separated.
  1619  // When we now remove the Kind from this API, we need to make the transition as painless
  1620  // as possible for existing sites. Most sites will use {{ .Site.GetPage "section" "my/section" }},
  1621  // i.e. 2 arguments, so we test for that.
  1622  func (s *SiteInfo) GetPage(ref ...string) (page.Page, error) {
  1623  	p, err := s.s.getPageOldVersion(ref...)
  1624  
  1625  	if p == nil {
  1626  		// The nil struct has meaning in some situations, mostly to avoid breaking
  1627  		// existing sites doing $nilpage.IsDescendant($p), which will always return
  1628  		// false.
  1629  		p = page.NilPage
  1630  	}
  1631  
  1632  	return p, err
  1633  }
  1634  
  1635  func (s *SiteInfo) GetPageWithTemplateInfo(info tpl.Info, ref ...string) (page.Page, error) {
  1636  	p, err := s.GetPage(ref...)
  1637  	if p != nil {
  1638  		// Track pages referenced by templates/shortcodes
  1639  		// when in server mode.
  1640  		if im, ok := info.(identity.Manager); ok {
  1641  			im.Add(p)
  1642  		}
  1643  	}
  1644  	return p, err
  1645  }
  1646  
  1647  func (s *Site) permalink(link string) string {
  1648  	return s.PathSpec.PermalinkForBaseURL(link, s.PathSpec.BaseURL.String())
  1649  }
  1650  
  1651  func (s *Site) absURLPath(targetPath string) string {
  1652  	var path string
  1653  	if s.Info.relativeURLs {
  1654  		path = helpers.GetDottedRelativePath(targetPath)
  1655  	} else {
  1656  		url := s.PathSpec.BaseURL.String()
  1657  		if !strings.HasSuffix(url, "/") {
  1658  			url += "/"
  1659  		}
  1660  		path = url
  1661  	}
  1662  
  1663  	return path
  1664  }
  1665  
  1666  func (s *Site) lookupLayouts(layouts ...string) tpl.Template {
  1667  	for _, l := range layouts {
  1668  		if templ, found := s.Tmpl().Lookup(l); found {
  1669  			return templ
  1670  		}
  1671  	}
  1672  
  1673  	return nil
  1674  }
  1675  
  1676  func (s *Site) renderAndWriteXML(statCounter *uint64, name string, targetPath string, d interface{}, templ tpl.Template) error {
  1677  	s.Log.Debugf("Render XML for %q to %q", name, targetPath)
  1678  	renderBuffer := bp.GetBuffer()
  1679  	defer bp.PutBuffer(renderBuffer)
  1680  
  1681  	if err := s.renderForTemplate(name, "", d, renderBuffer, templ); err != nil {
  1682  		return err
  1683  	}
  1684  
  1685  	pd := publisher.Descriptor{
  1686  		Src:         renderBuffer,
  1687  		TargetPath:  targetPath,
  1688  		StatCounter: statCounter,
  1689  		// For the minification part of XML,
  1690  		// we currently only use the MIME type.
  1691  		OutputFormat: output.RSSFormat,
  1692  		AbsURLPath:   s.absURLPath(targetPath),
  1693  	}
  1694  
  1695  	return s.publisher.Publish(pd)
  1696  }
  1697  
  1698  func (s *Site) renderAndWritePage(statCounter *uint64, name string, targetPath string, p *pageState, templ tpl.Template) error {
  1699  	s.Log.Debugf("Render %s to %q", name, targetPath)
  1700  	renderBuffer := bp.GetBuffer()
  1701  	defer bp.PutBuffer(renderBuffer)
  1702  
  1703  	of := p.outputFormat()
  1704  
  1705  	if err := s.renderForTemplate(p.Kind(), of.Name, p, renderBuffer, templ); err != nil {
  1706  		return err
  1707  	}
  1708  
  1709  	if renderBuffer.Len() == 0 {
  1710  		return nil
  1711  	}
  1712  
  1713  	isHTML := of.IsHTML
  1714  	isRSS := of.Name == "RSS"
  1715  
  1716  	pd := publisher.Descriptor{
  1717  		Src:          renderBuffer,
  1718  		TargetPath:   targetPath,
  1719  		StatCounter:  statCounter,
  1720  		OutputFormat: p.outputFormat(),
  1721  	}
  1722  
  1723  	if isRSS {
  1724  		// Always canonify URLs in RSS
  1725  		pd.AbsURLPath = s.absURLPath(targetPath)
  1726  	} else if isHTML {
  1727  		if s.Info.relativeURLs || s.Info.canonifyURLs {
  1728  			pd.AbsURLPath = s.absURLPath(targetPath)
  1729  		}
  1730  
  1731  		if s.running() && s.Cfg.GetBool("watch") && !s.Cfg.GetBool("disableLiveReload") {
  1732  			pd.LiveReloadBaseURL = s.PathSpec.BaseURL.URL()
  1733  			if s.Cfg.GetInt("liveReloadPort") != -1 {
  1734  				pd.LiveReloadBaseURL.Host = fmt.Sprintf("%s:%d", pd.LiveReloadBaseURL.Hostname(), s.Cfg.GetInt("liveReloadPort"))
  1735  			}
  1736  		}
  1737  
  1738  		// For performance reasons we only inject the Hugo generator tag on the home page.
  1739  		if p.IsHome() {
  1740  			pd.AddHugoGeneratorTag = !s.Cfg.GetBool("disableHugoGeneratorInject")
  1741  		}
  1742  
  1743  	}
  1744  
  1745  	return s.publisher.Publish(pd)
  1746  }
  1747  
  1748  var infoOnMissingLayout = map[string]bool{
  1749  	// The 404 layout is very much optional in Hugo, but we do look for it.
  1750  	"404": true,
  1751  }
  1752  
  1753  // hookRenderer is the canonical implementation of all hooks.ITEMRenderer,
  1754  // where ITEM is the thing being hooked.
  1755  type hookRenderer struct {
  1756  	templateHandler tpl.TemplateHandler
  1757  	identity.SearchProvider
  1758  	templ tpl.Template
  1759  }
  1760  
  1761  func (hr hookRenderer) RenderLink(w io.Writer, ctx hooks.LinkContext) error {
  1762  	return hr.templateHandler.Execute(hr.templ, w, ctx)
  1763  }
  1764  
  1765  func (hr hookRenderer) RenderHeading(w io.Writer, ctx hooks.HeadingContext) error {
  1766  	return hr.templateHandler.Execute(hr.templ, w, ctx)
  1767  }
  1768  
  1769  func (s *Site) renderForTemplate(name, outputFormat string, d interface{}, w io.Writer, templ tpl.Template) (err error) {
  1770  	if templ == nil {
  1771  		s.logMissingLayout(name, "", "", outputFormat)
  1772  		return nil
  1773  	}
  1774  
  1775  	if err = s.Tmpl().Execute(templ, w, d); err != nil {
  1776  		return _errors.Wrapf(err, "render of %q failed", name)
  1777  	}
  1778  	return
  1779  }
  1780  
  1781  func (s *Site) lookupTemplate(layouts ...string) (tpl.Template, bool) {
  1782  	for _, l := range layouts {
  1783  		if templ, found := s.Tmpl().Lookup(l); found {
  1784  			return templ, true
  1785  		}
  1786  	}
  1787  
  1788  	return nil, false
  1789  }
  1790  
  1791  func (s *Site) publish(statCounter *uint64, path string, r io.Reader) (err error) {
  1792  	s.PathSpec.ProcessingStats.Incr(statCounter)
  1793  
  1794  	return helpers.WriteToDisk(filepath.Clean(path), r, s.BaseFs.PublishFs)
  1795  }
  1796  
  1797  func (s *Site) kindFromFileInfoOrSections(fi *fileInfo, sections []string) string {
  1798  	if fi.TranslationBaseName() == "_index" {
  1799  		if fi.Dir() == "" {
  1800  			return page.KindHome
  1801  		}
  1802  
  1803  		return s.kindFromSections(sections)
  1804  
  1805  	}
  1806  
  1807  	return page.KindPage
  1808  }
  1809  
  1810  func (s *Site) kindFromSections(sections []string) string {
  1811  	if len(sections) == 0 {
  1812  		return page.KindHome
  1813  	}
  1814  
  1815  	return s.kindFromSectionPath(path.Join(sections...))
  1816  }
  1817  
  1818  func (s *Site) kindFromSectionPath(sectionPath string) string {
  1819  	for _, plural := range s.siteCfg.taxonomiesConfig {
  1820  		if plural == sectionPath {
  1821  			return page.KindTaxonomy
  1822  		}
  1823  
  1824  		if strings.HasPrefix(sectionPath, plural) {
  1825  			return page.KindTerm
  1826  		}
  1827  
  1828  	}
  1829  
  1830  	return page.KindSection
  1831  }
  1832  
  1833  func (s *Site) newPage(
  1834  	n *contentNode,
  1835  	parentbBucket *pagesMapBucket,
  1836  	kind, title string,
  1837  	sections ...string) *pageState {
  1838  	m := map[string]interface{}{}
  1839  	if title != "" {
  1840  		m["title"] = title
  1841  	}
  1842  
  1843  	p, err := newPageFromMeta(
  1844  		n,
  1845  		parentbBucket,
  1846  		m,
  1847  		&pageMeta{
  1848  			s:        s,
  1849  			kind:     kind,
  1850  			sections: sections,
  1851  		})
  1852  	if err != nil {
  1853  		panic(err)
  1854  	}
  1855  
  1856  	return p
  1857  }
  1858  
  1859  func (s *Site) shouldBuild(p page.Page) bool {
  1860  	return shouldBuild(s.BuildFuture, s.BuildExpired,
  1861  		s.BuildDrafts, p.Draft(), p.PublishDate(), p.ExpiryDate())
  1862  }
  1863  
  1864  func shouldBuild(buildFuture bool, buildExpired bool, buildDrafts bool, Draft bool,
  1865  	publishDate time.Time, expiryDate time.Time) bool {
  1866  	if !(buildDrafts || !Draft) {
  1867  		return false
  1868  	}
  1869  	if !buildFuture && !publishDate.IsZero() && publishDate.After(time.Now()) {
  1870  		return false
  1871  	}
  1872  	if !buildExpired && !expiryDate.IsZero() && expiryDate.Before(time.Now()) {
  1873  		return false
  1874  	}
  1875  	return true
  1876  }