github.com/neohugo/neohugo@v0.123.8/hugolib/site_new.go (about)

     1  // Copyright 2024 The Hugo Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package hugolib
    15  
    16  import (
    17  	"context"
    18  	"errors"
    19  	"fmt"
    20  	"html/template"
    21  	"os"
    22  	"sort"
    23  	"time"
    24  
    25  	"github.com/bep/logg"
    26  	"github.com/neohugo/neohugo/cache/dynacache"
    27  	"github.com/neohugo/neohugo/common/loggers"
    28  	"github.com/neohugo/neohugo/common/maps"
    29  	"github.com/neohugo/neohugo/common/neohugo"
    30  	"github.com/neohugo/neohugo/common/para"
    31  	"github.com/neohugo/neohugo/config"
    32  	"github.com/neohugo/neohugo/config/allconfig"
    33  	"github.com/neohugo/neohugo/deps"
    34  	"github.com/neohugo/neohugo/hugolib/doctree"
    35  	"github.com/neohugo/neohugo/identity"
    36  	"github.com/neohugo/neohugo/langs"
    37  	"github.com/neohugo/neohugo/langs/i18n"
    38  	"github.com/neohugo/neohugo/lazy"
    39  	"github.com/neohugo/neohugo/modules"
    40  	"github.com/neohugo/neohugo/navigation"
    41  	"github.com/neohugo/neohugo/output"
    42  	"github.com/neohugo/neohugo/publisher"
    43  	"github.com/neohugo/neohugo/resources"
    44  	"github.com/neohugo/neohugo/resources/page"
    45  	"github.com/neohugo/neohugo/resources/page/pagemeta"
    46  	"github.com/neohugo/neohugo/resources/page/siteidentities"
    47  	"github.com/neohugo/neohugo/resources/resource"
    48  	"github.com/neohugo/neohugo/tpl"
    49  	"github.com/neohugo/neohugo/tpl/tplimpl"
    50  )
    51  
    52  var _ page.Site = (*Site)(nil)
    53  
    54  type Site struct {
    55  	conf      *allconfig.Config
    56  	language  *langs.Language
    57  	languagei int
    58  	pageMap   *pageMap
    59  
    60  	// The owning container.
    61  	h *HugoSites
    62  
    63  	*deps.Deps
    64  
    65  	// Page navigation.
    66  	*pageFinder
    67  	taxonomies page.TaxonomyList
    68  	menus      navigation.Menus
    69  
    70  	// Shortcut to the home page. Note that this may be nil if
    71  	// home page, for some odd reason, is disabled.
    72  	home *pageState
    73  
    74  	// The last modification date of this site.
    75  	lastmod time.Time
    76  
    77  	relatedDocsHandler *page.RelatedDocsHandler
    78  	siteRefLinker
    79  	publisher          publisher.Publisher
    80  	frontmatterHandler pagemeta.FrontMatterHandler
    81  
    82  	// We render each site for all the relevant output formats in serial with
    83  	// this rendering context pointing to the current one.
    84  	rc *siteRenderingContext
    85  
    86  	// The output formats that we need to render this site in. This slice
    87  	// will be fixed once set.
    88  	// This will be the union of Site.Pages' outputFormats.
    89  	// This slice will be sorted.
    90  	renderFormats output.Formats
    91  
    92  	// Lazily loaded site dependencies
    93  	init *siteInit
    94  }
    95  
    96  func (s *Site) Debug() {
    97  	fmt.Println("Debugging site", s.Lang(), "=>")
    98  	// fmt.Println(s.pageMap.testDump())
    99  }
   100  
   101  // NewHugoSites creates HugoSites from the given config.
   102  func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
   103  	conf := cfg.Configs.GetFirstLanguageConfig()
   104  
   105  	var logger loggers.Logger
   106  	if cfg.TestLogger != nil {
   107  		logger = cfg.TestLogger
   108  	} else {
   109  		var logHookLast func(e *logg.Entry) error
   110  		if cfg.Configs.Base.PanicOnWarning {
   111  			logHookLast = loggers.PanicOnWarningHook
   112  		}
   113  		if cfg.LogOut == nil {
   114  			cfg.LogOut = os.Stdout
   115  		}
   116  		if cfg.LogLevel == 0 {
   117  			cfg.LogLevel = logg.LevelWarn
   118  		}
   119  
   120  		logOpts := loggers.Options{
   121  			Level:              cfg.LogLevel,
   122  			DistinctLevel:      logg.LevelWarn, // This will drop duplicate log warning and errors.
   123  			HandlerPost:        logHookLast,
   124  			Stdout:             cfg.LogOut,
   125  			Stderr:             cfg.LogOut,
   126  			StoreErrors:        conf.Running(),
   127  			SuppressStatements: conf.IgnoredLogs(),
   128  		}
   129  		logger = loggers.New(logOpts)
   130  
   131  	}
   132  
   133  	memCache := dynacache.New(dynacache.Options{Running: conf.Running(), Log: logger})
   134  
   135  	firstSiteDeps := &deps.Deps{
   136  		Fs:                  cfg.Fs,
   137  		Log:                 logger,
   138  		Conf:                conf,
   139  		MemCache:            memCache,
   140  		TemplateProvider:    tplimpl.DefaultTemplateProvider,
   141  		TranslationProvider: i18n.NewTranslationProvider(),
   142  	}
   143  
   144  	if err := firstSiteDeps.Init(); err != nil {
   145  		return nil, err
   146  	}
   147  
   148  	confm := cfg.Configs
   149  	if err := confm.Validate(logger); err != nil {
   150  		return nil, err
   151  	}
   152  	var sites []*Site
   153  
   154  	ns := &contentNodeShifter{
   155  		numLanguages: len(confm.Languages),
   156  	}
   157  
   158  	treeConfig := doctree.Config[contentNodeI]{
   159  		Shifter: ns,
   160  	}
   161  
   162  	pageTrees := &pageTrees{
   163  		treePages: doctree.New(
   164  			treeConfig,
   165  		),
   166  		treeResources: doctree.New(
   167  			treeConfig,
   168  		),
   169  		treeTaxonomyEntries: doctree.NewTreeShiftTree[*weightedContentNode](doctree.DimensionLanguage.Index(), len(confm.Languages)),
   170  	}
   171  
   172  	pageTrees.createMutableTrees()
   173  
   174  	for i, confp := range confm.ConfigLangs() {
   175  		language := confp.Language()
   176  		if language.Disabled {
   177  			continue
   178  		}
   179  		k := language.Lang
   180  		conf := confm.LanguageConfigMap[k]
   181  		frontmatterHandler, err := pagemeta.NewFrontmatterHandler(firstSiteDeps.Log, conf.Frontmatter)
   182  		if err != nil {
   183  			return nil, err
   184  		}
   185  
   186  		langs.SetParams(language, conf.Params)
   187  
   188  		s := &Site{
   189  			conf:               conf,
   190  			language:           language,
   191  			languagei:          i,
   192  			frontmatterHandler: frontmatterHandler,
   193  		}
   194  
   195  		if i == 0 {
   196  			firstSiteDeps.Site = s
   197  			s.Deps = firstSiteDeps
   198  		} else {
   199  			d, err := firstSiteDeps.Clone(s, confp)
   200  			if err != nil {
   201  				return nil, err
   202  			}
   203  			s.Deps = d
   204  		}
   205  
   206  		s.pageMap = newPageMap(i, s, memCache, pageTrees)
   207  
   208  		s.pageFinder = newPageFinder(s.pageMap)
   209  		s.siteRefLinker, err = newSiteRefLinker(s)
   210  		if err != nil {
   211  			return nil, err
   212  		}
   213  		// Set up the main publishing chain.
   214  		pub, err := publisher.NewDestinationPublisher(
   215  			firstSiteDeps.ResourceSpec,
   216  			s.conf.OutputFormats.Config,
   217  			s.conf.MediaTypes.Config,
   218  		)
   219  		if err != nil {
   220  			return nil, err
   221  		}
   222  
   223  		s.publisher = pub
   224  		s.relatedDocsHandler = page.NewRelatedDocsHandler(s.conf.Related)
   225  		// Site deps end.
   226  
   227  		s.prepareInits()
   228  		sites = append(sites, s)
   229  	}
   230  
   231  	if len(sites) == 0 {
   232  		return nil, errors.New("no sites to build")
   233  	}
   234  
   235  	// Pull the default content language to the top, then sort the sites by language weight (if set) or lang.
   236  	defaultContentLanguage := confm.Base.DefaultContentLanguage
   237  	sort.Slice(sites, func(i, j int) bool {
   238  		li := sites[i].language
   239  		lj := sites[j].language
   240  		if li.Lang == defaultContentLanguage {
   241  			return true
   242  		}
   243  
   244  		if lj.Lang == defaultContentLanguage {
   245  			return false
   246  		}
   247  
   248  		if li.Weight != lj.Weight {
   249  			return li.Weight < lj.Weight
   250  		}
   251  		return li.Lang < lj.Lang
   252  	})
   253  
   254  	h, err := newHugoSites(cfg, firstSiteDeps, pageTrees, sites)
   255  	if err == nil && h == nil {
   256  		panic("hugo: newHugoSitesNew returned nil error and nil HugoSites")
   257  	}
   258  
   259  	return h, err
   260  }
   261  
   262  func newHugoSites(cfg deps.DepsCfg, d *deps.Deps, pageTrees *pageTrees, sites []*Site) (*HugoSites, error) {
   263  	numWorkers := config.GetNumWorkerMultiplier()
   264  	numWorkersSite := numWorkers
   265  	if numWorkersSite > len(sites) {
   266  		numWorkersSite = len(sites)
   267  	}
   268  	workersSite := para.New(numWorkersSite)
   269  
   270  	h := &HugoSites{
   271  		Sites:           sites,
   272  		Deps:            sites[0].Deps,
   273  		Configs:         cfg.Configs,
   274  		workersSite:     workersSite,
   275  		numWorkersSites: numWorkers,
   276  		numWorkers:      numWorkers,
   277  		pageTrees:       pageTrees,
   278  		cachePages: dynacache.GetOrCreatePartition[string,
   279  			page.Pages](d.MemCache, "/pags/all",
   280  			dynacache.OptionsPartition{Weight: 10, ClearWhen: dynacache.ClearOnRebuild},
   281  		),
   282  		cacheContentSource:      dynacache.GetOrCreatePartition[string, *resources.StaleValue[[]byte]](d.MemCache, "/cont/src", dynacache.OptionsPartition{Weight: 70, ClearWhen: dynacache.ClearOnChange}),
   283  		translationKeyPages:     maps.NewSliceCache[page.Page](),
   284  		currentSite:             sites[0],
   285  		skipRebuildForFilenames: make(map[string]bool),
   286  		init: &hugoSitesInit{
   287  			data:    lazy.New(),
   288  			layouts: lazy.New(),
   289  			gitInfo: lazy.New(),
   290  		},
   291  	}
   292  
   293  	// Assemble dependencies to be used in hugo.Deps.
   294  	var dependencies []*neohugo.Dependency
   295  	var depFromMod func(m modules.Module) *neohugo.Dependency
   296  	depFromMod = func(m modules.Module) *neohugo.Dependency {
   297  		dep := &neohugo.Dependency{
   298  			Path:    m.Path(),
   299  			Version: m.Version(),
   300  			Time:    m.Time(),
   301  			Vendor:  m.Vendor(),
   302  		}
   303  
   304  		// These are pointers, but this all came from JSON so there's no recursive navigation,
   305  		// so just create new values.
   306  		if m.Replace() != nil {
   307  			dep.Replace = depFromMod(m.Replace())
   308  		}
   309  		if m.Owner() != nil {
   310  			dep.Owner = depFromMod(m.Owner())
   311  		}
   312  		return dep
   313  	}
   314  	for _, m := range d.Paths.AllModules() {
   315  		dependencies = append(dependencies, depFromMod(m))
   316  	}
   317  
   318  	h.hugoInfo = neohugo.NewInfo(h.Configs.GetFirstLanguageConfig(), dependencies)
   319  
   320  	var prototype *deps.Deps
   321  	for i, s := range sites {
   322  		s.h = h
   323  		if err := s.Deps.Compile(prototype); err != nil {
   324  			return nil, err
   325  		}
   326  		if i == 0 {
   327  			prototype = s.Deps
   328  		}
   329  	}
   330  
   331  	h.fatalErrorHandler = &fatalErrorHandler{
   332  		h:     h,
   333  		donec: make(chan bool),
   334  	}
   335  
   336  	h.init.data.Add(func(context.Context) (any, error) {
   337  		err := h.loadData()
   338  		if err != nil {
   339  			return nil, fmt.Errorf("failed to load data: %w", err)
   340  		}
   341  		return nil, nil
   342  	})
   343  
   344  	h.init.layouts.Add(func(context.Context) (any, error) {
   345  		for _, s := range h.Sites {
   346  			if err := s.Tmpl().(tpl.TemplateManager).MarkReady(); err != nil {
   347  				return nil, err
   348  			}
   349  		}
   350  		return nil, nil
   351  	})
   352  
   353  	h.init.gitInfo.Add(func(context.Context) (any, error) {
   354  		err := h.loadGitInfo()
   355  		if err != nil {
   356  			return nil, fmt.Errorf("failed to load Git info: %w", err)
   357  		}
   358  		return nil, nil
   359  	})
   360  
   361  	return h, nil
   362  }
   363  
   364  // Returns true if we're running in a server.
   365  // Deprecated: use hugo.IsServer instead
   366  func (s *Site) IsServer() bool {
   367  	neohugo.Deprecate(".Site.IsServer", "Use hugo.IsServer instead.", "v0.120.0")
   368  	return s.conf.Internal.Running
   369  }
   370  
   371  // Returns the server port.
   372  func (s *Site) ServerPort() int {
   373  	return s.conf.C.BaseURL.Port()
   374  }
   375  
   376  // Returns the configured title for this Site.
   377  func (s *Site) Title() string {
   378  	return s.conf.Title
   379  }
   380  
   381  func (s *Site) Copyright() string {
   382  	return s.conf.Copyright
   383  }
   384  
   385  func (s *Site) RSSLink() template.URL {
   386  	neohugo.Deprecate("Site.RSSLink", "Use the Output Format's Permalink method instead, e.g. .OutputFormats.Get \"RSS\".Permalink", "v0.114.0")
   387  	rssOutputFormat := s.home.OutputFormats().Get("rss")
   388  	return template.URL(rssOutputFormat.Permalink())
   389  }
   390  
   391  func (s *Site) Config() page.SiteConfig {
   392  	return page.SiteConfig{
   393  		Privacy:  s.conf.Privacy,
   394  		Services: s.conf.Services,
   395  	}
   396  }
   397  
   398  func (s *Site) LanguageCode() string {
   399  	return s.Language().LanguageCode()
   400  }
   401  
   402  // Returns all Sites for all languages.
   403  func (s *Site) Sites() page.Sites {
   404  	sites := make(page.Sites, len(s.h.Sites))
   405  	for i, s := range s.h.Sites {
   406  		sites[i] = s.Site()
   407  	}
   408  	return sites
   409  }
   410  
   411  // Returns Site currently rendering.
   412  func (s *Site) Current() page.Site {
   413  	return s.h.currentSite
   414  }
   415  
   416  // MainSections returns the list of main sections.
   417  func (s *Site) MainSections() []string {
   418  	return s.conf.C.MainSections
   419  }
   420  
   421  // Returns a struct with some information about the build.
   422  func (s *Site) Hugo() neohugo.HugoInfo {
   423  	if s.h == nil || s.h.hugoInfo.Environment == "" {
   424  		panic("site: hugo: hugoInfo not initialized")
   425  	}
   426  	return s.h.hugoInfo
   427  }
   428  
   429  // Returns the BaseURL for this Site.
   430  func (s *Site) BaseURL() string {
   431  	return s.conf.C.BaseURL.WithPath
   432  }
   433  
   434  // Returns the last modification date of the content.
   435  // Deprecated: Use .Lastmod instead.
   436  func (s *Site) LastChange() time.Time {
   437  	return s.lastmod
   438  }
   439  
   440  // Returns the last modification date of the content.
   441  func (s *Site) Lastmod() time.Time {
   442  	return s.lastmod
   443  }
   444  
   445  // Returns the Params configured for this site.
   446  func (s *Site) Params() maps.Params {
   447  	return s.conf.Params
   448  }
   449  
   450  func (s *Site) Author() map[string]any {
   451  	return s.conf.Author
   452  }
   453  
   454  func (s *Site) Authors() page.AuthorList {
   455  	return page.AuthorList{}
   456  }
   457  
   458  func (s *Site) Social() map[string]string {
   459  	return s.conf.Social
   460  }
   461  
   462  // Deprecated: Use .Site.Config.Services.Disqus.Shortname instead
   463  func (s *Site) DisqusShortname() string {
   464  	neohugo.Deprecate(".Site.DisqusShortname", "Use .Site.Config.Services.Disqus.Shortname instead.", "v0.120.0")
   465  	return s.Config().Services.Disqus.Shortname
   466  }
   467  
   468  // Deprecated: Use .Site.Config.Services.GoogleAnalytics.ID instead
   469  func (s *Site) GoogleAnalytics() string {
   470  	neohugo.Deprecate(".Site.GoogleAnalytics", "Use .Site.Config.Services.GoogleAnalytics.ID instead.", "v0.120.0")
   471  	return s.Config().Services.GoogleAnalytics.ID
   472  }
   473  
   474  func (s *Site) Param(key any) (any, error) {
   475  	return resource.Param(s, nil, key)
   476  }
   477  
   478  // Returns a map of all the data inside /data.
   479  func (s *Site) Data() map[string]any {
   480  	return s.s.h.Data()
   481  }
   482  
   483  func (s *Site) BuildDrafts() bool {
   484  	return s.conf.BuildDrafts
   485  }
   486  
   487  func (s *Site) IsMultiLingual() bool {
   488  	return s.h.isMultiLingual()
   489  }
   490  
   491  func (s *Site) LanguagePrefix() string {
   492  	prefix := s.GetLanguagePrefix()
   493  	if prefix == "" {
   494  		return ""
   495  	}
   496  	return "/" + prefix
   497  }
   498  
   499  func (s *Site) Site() page.Site {
   500  	return page.WrapSite(s)
   501  }
   502  
   503  func (s *Site) ForEeachIdentityByName(name string, f func(identity.Identity) bool) {
   504  	if id, found := siteidentities.FromString(name); found {
   505  		if f(id) {
   506  			return
   507  		}
   508  	}
   509  }
   510  
   511  // Pages returns all pages.
   512  // This is for the current language only.
   513  func (s *Site) Pages() page.Pages {
   514  	return s.pageMap.getPagesInSection(
   515  		pageMapQueryPagesInSection{
   516  			pageMapQueryPagesBelowPath: pageMapQueryPagesBelowPath{
   517  				Path:    "",
   518  				KeyPart: "global",
   519  				Include: pagePredicates.ShouldListGlobal,
   520  			},
   521  			Recursive:   true,
   522  			IncludeSelf: true,
   523  		},
   524  	)
   525  }
   526  
   527  // RegularPages returns all the regular pages.
   528  // This is for the current language only.
   529  func (s *Site) RegularPages() page.Pages {
   530  	return s.pageMap.getPagesInSection(
   531  		pageMapQueryPagesInSection{
   532  			pageMapQueryPagesBelowPath: pageMapQueryPagesBelowPath{
   533  				Path:    "",
   534  				KeyPart: "global",
   535  				Include: pagePredicates.ShouldListGlobal.And(pagePredicates.KindPage),
   536  			},
   537  			Recursive: true,
   538  		},
   539  	)
   540  }
   541  
   542  // AllPages returns all pages for all sites.
   543  func (s *Site) AllPages() page.Pages {
   544  	return s.h.Pages()
   545  }
   546  
   547  // AllRegularPages returns all regular pages for all sites.
   548  func (s *Site) AllRegularPages() page.Pages {
   549  	return s.h.RegularPages()
   550  }