github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/hugolib/site_new.go (about)

     1  // Copyright 2023 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  	radix "github.com/armon/go-radix"
    26  	"github.com/bep/logg"
    27  	"github.com/gohugoio/hugo/common/hugo"
    28  	"github.com/gohugoio/hugo/common/loggers"
    29  	"github.com/gohugoio/hugo/common/maps"
    30  	"github.com/gohugoio/hugo/common/para"
    31  	"github.com/gohugoio/hugo/config"
    32  	"github.com/gohugoio/hugo/config/allconfig"
    33  	"github.com/gohugoio/hugo/deps"
    34  	"github.com/gohugoio/hugo/helpers"
    35  	"github.com/gohugoio/hugo/identity"
    36  	"github.com/gohugoio/hugo/langs"
    37  	"github.com/gohugoio/hugo/langs/i18n"
    38  	"github.com/gohugoio/hugo/lazy"
    39  	"github.com/gohugoio/hugo/modules"
    40  	"github.com/gohugoio/hugo/navigation"
    41  	"github.com/gohugoio/hugo/output"
    42  	"github.com/gohugoio/hugo/publisher"
    43  	"github.com/gohugoio/hugo/resources/kinds"
    44  	"github.com/gohugoio/hugo/resources/page"
    45  	"github.com/gohugoio/hugo/resources/page/pagemeta"
    46  	"github.com/gohugoio/hugo/resources/resource"
    47  	"github.com/gohugoio/hugo/tpl"
    48  	"github.com/gohugoio/hugo/tpl/tplimpl"
    49  )
    50  
    51  var (
    52  	_ page.Site = (*Site)(nil)
    53  )
    54  
    55  type Site struct {
    56  	conf     *allconfig.Config
    57  	language *langs.Language
    58  
    59  	// The owning container.
    60  	h *HugoSites
    61  
    62  	*deps.Deps
    63  
    64  	// Page navigation.
    65  	*PageCollections
    66  	taxonomies page.TaxonomyList
    67  	menus      navigation.Menus
    68  
    69  	siteBucket *pagesMapBucket
    70  
    71  	// Shortcut to the home page. Note that this may be nil if
    72  	// home page, for some odd reason, is disabled.
    73  	home *pageState
    74  
    75  	// The last modification date of this site.
    76  	lastmod time.Time
    77  
    78  	relatedDocsHandler *page.RelatedDocsHandler
    79  	siteRefLinker
    80  	publisher          publisher.Publisher
    81  	frontmatterHandler pagemeta.FrontMatterHandler
    82  
    83  	// We render each site for all the relevant output formats in serial with
    84  	// this rendering context pointing to the current one.
    85  	rc *siteRenderingContext
    86  
    87  	// The output formats that we need to render this site in. This slice
    88  	// will be fixed once set.
    89  	// This will be the union of Site.Pages' outputFormats.
    90  	// This slice will be sorted.
    91  	renderFormats output.Formats
    92  
    93  	// Lazily loaded site dependencies
    94  	init *siteInit
    95  }
    96  
    97  func (s *Site) Debug() {
    98  	fmt.Println("Debugging site", s.Lang(), "=>")
    99  	fmt.Println(s.pageMap.testDump())
   100  }
   101  
   102  // NewHugoSites creates HugoSites from the given config.
   103  func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
   104  	conf := cfg.Configs.GetFirstLanguageConfig()
   105  
   106  	var logger loggers.Logger
   107  	if cfg.TestLogger != nil {
   108  		logger = cfg.TestLogger
   109  	} else {
   110  		var logHookLast func(e *logg.Entry) error
   111  		if cfg.Configs.Base.PanicOnWarning {
   112  			logHookLast = loggers.PanicOnWarningHook
   113  		}
   114  		if cfg.LogOut == nil {
   115  			cfg.LogOut = os.Stdout
   116  		}
   117  		if cfg.LogLevel == 0 {
   118  			cfg.LogLevel = logg.LevelWarn
   119  		}
   120  
   121  		logOpts := loggers.Options{
   122  			Level:              cfg.LogLevel,
   123  			Distinct:           true, // This will drop duplicate log warning and errors.
   124  			HandlerPost:        logHookLast,
   125  			Stdout:             cfg.LogOut,
   126  			Stderr:             cfg.LogOut,
   127  			StoreErrors:        conf.Running(),
   128  			SuppressStatements: conf.IgnoredErrors(),
   129  		}
   130  		logger = loggers.New(logOpts)
   131  	}
   132  
   133  	firstSiteDeps := &deps.Deps{
   134  		Fs:                  cfg.Fs,
   135  		Log:                 logger,
   136  		Conf:                conf,
   137  		TemplateProvider:    tplimpl.DefaultTemplateProvider,
   138  		TranslationProvider: i18n.NewTranslationProvider(),
   139  	}
   140  
   141  	if err := firstSiteDeps.Init(); err != nil {
   142  		return nil, err
   143  	}
   144  
   145  	confm := cfg.Configs
   146  	var sites []*Site
   147  
   148  	for i, confp := range confm.ConfigLangs() {
   149  		language := confp.Language()
   150  		if confp.IsLangDisabled(language.Lang) {
   151  			continue
   152  		}
   153  		k := language.Lang
   154  		conf := confm.LanguageConfigMap[k]
   155  
   156  		frontmatterHandler, err := pagemeta.NewFrontmatterHandler(firstSiteDeps.Log, conf.Frontmatter)
   157  		if err != nil {
   158  			return nil, err
   159  		}
   160  
   161  		langs.SetParams(language, conf.Params)
   162  
   163  		s := &Site{
   164  			conf:     conf,
   165  			language: language,
   166  			siteBucket: &pagesMapBucket{
   167  				cascade: conf.Cascade.Config,
   168  			},
   169  			frontmatterHandler: frontmatterHandler,
   170  		}
   171  
   172  		if i == 0 {
   173  			firstSiteDeps.Site = s
   174  			s.Deps = firstSiteDeps
   175  		} else {
   176  			d, err := firstSiteDeps.Clone(s, confp)
   177  			if err != nil {
   178  				return nil, err
   179  			}
   180  			s.Deps = d
   181  		}
   182  
   183  		// Site deps start.
   184  		var taxonomiesConfig taxonomiesConfig = conf.Taxonomies
   185  		pm := &pageMap{
   186  			contentMap: newContentMap(contentMapConfig{
   187  				lang:                 k,
   188  				taxonomyConfig:       taxonomiesConfig.Values(),
   189  				taxonomyDisabled:     !conf.IsKindEnabled(kinds.KindTerm),
   190  				taxonomyTermDisabled: !conf.IsKindEnabled(kinds.KindTaxonomy),
   191  				pageDisabled:         !conf.IsKindEnabled(kinds.KindPage),
   192  			}),
   193  			s: s,
   194  		}
   195  
   196  		s.PageCollections = newPageCollections(pm)
   197  		s.siteRefLinker, err = newSiteRefLinker(s)
   198  		if err != nil {
   199  			return nil, err
   200  		}
   201  		// Set up the main publishing chain.
   202  		pub, err := publisher.NewDestinationPublisher(
   203  			firstSiteDeps.ResourceSpec,
   204  			s.conf.OutputFormats.Config,
   205  			s.conf.MediaTypes.Config,
   206  		)
   207  		if err != nil {
   208  			return nil, err
   209  		}
   210  
   211  		s.publisher = pub
   212  		s.relatedDocsHandler = page.NewRelatedDocsHandler(s.conf.Related)
   213  		// Site deps end.
   214  
   215  		s.prepareInits()
   216  		sites = append(sites, s)
   217  	}
   218  
   219  	if len(sites) == 0 {
   220  		return nil, errors.New("no sites to build")
   221  	}
   222  
   223  	// Sort the sites by language weight (if set) or lang.
   224  	sort.Slice(sites, func(i, j int) bool {
   225  		li := sites[i].language
   226  		lj := sites[j].language
   227  		if li.Weight != lj.Weight {
   228  			return li.Weight < lj.Weight
   229  		}
   230  		return li.Lang < lj.Lang
   231  	})
   232  
   233  	h, err := newHugoSitesNew(cfg, firstSiteDeps, sites)
   234  	if err == nil && h == nil {
   235  		panic("hugo: newHugoSitesNew returned nil error and nil HugoSites")
   236  	}
   237  
   238  	return h, err
   239  
   240  }
   241  
   242  func newHugoSitesNew(cfg deps.DepsCfg, d *deps.Deps, sites []*Site) (*HugoSites, error) {
   243  	numWorkers := config.GetNumWorkerMultiplier()
   244  	if numWorkers > len(sites) {
   245  		numWorkers = len(sites)
   246  	}
   247  	var workers *para.Workers
   248  	if numWorkers > 1 {
   249  		workers = para.New(numWorkers)
   250  	}
   251  
   252  	h := &HugoSites{
   253  		Sites:                   sites,
   254  		Deps:                    sites[0].Deps,
   255  		Configs:                 cfg.Configs,
   256  		workers:                 workers,
   257  		numWorkers:              numWorkers,
   258  		currentSite:             sites[0],
   259  		skipRebuildForFilenames: make(map[string]bool),
   260  		init: &hugoSitesInit{
   261  			data:         lazy.New(),
   262  			layouts:      lazy.New(),
   263  			gitInfo:      lazy.New(),
   264  			translations: lazy.New(),
   265  		},
   266  	}
   267  
   268  	// Assemble dependencies to be used in hugo.Deps.
   269  	var dependencies []*hugo.Dependency
   270  	var depFromMod func(m modules.Module) *hugo.Dependency
   271  	depFromMod = func(m modules.Module) *hugo.Dependency {
   272  		dep := &hugo.Dependency{
   273  			Path:    m.Path(),
   274  			Version: m.Version(),
   275  			Time:    m.Time(),
   276  			Vendor:  m.Vendor(),
   277  		}
   278  
   279  		// These are pointers, but this all came from JSON so there's no recursive navigation,
   280  		// so just create new values.
   281  		if m.Replace() != nil {
   282  			dep.Replace = depFromMod(m.Replace())
   283  		}
   284  		if m.Owner() != nil {
   285  			dep.Owner = depFromMod(m.Owner())
   286  		}
   287  		return dep
   288  	}
   289  	for _, m := range d.Paths.AllModules() {
   290  		dependencies = append(dependencies, depFromMod(m))
   291  	}
   292  
   293  	h.hugoInfo = hugo.NewInfo(h.Configs.GetFirstLanguageConfig(), dependencies)
   294  
   295  	var prototype *deps.Deps
   296  	for i, s := range sites {
   297  		s.h = h
   298  		if err := s.Deps.Compile(prototype); err != nil {
   299  			return nil, err
   300  		}
   301  		if i == 0 {
   302  			prototype = s.Deps
   303  		}
   304  	}
   305  
   306  	h.fatalErrorHandler = &fatalErrorHandler{
   307  		h:     h,
   308  		donec: make(chan bool),
   309  	}
   310  
   311  	// Only needed in server mode.
   312  	if cfg.Configs.Base.Internal.Watch {
   313  		h.ContentChanges = &contentChangeMap{
   314  			pathSpec:      h.PathSpec,
   315  			symContent:    make(map[string]map[string]bool),
   316  			leafBundles:   radix.New(),
   317  			branchBundles: make(map[string]bool),
   318  		}
   319  	}
   320  
   321  	h.init.data.Add(func(context.Context) (any, error) {
   322  		err := h.loadData(h.PathSpec.BaseFs.Data.Dirs)
   323  		if err != nil {
   324  			return nil, fmt.Errorf("failed to load data: %w", err)
   325  		}
   326  		return nil, nil
   327  	})
   328  
   329  	h.init.layouts.Add(func(context.Context) (any, error) {
   330  		for _, s := range h.Sites {
   331  			if err := s.Tmpl().(tpl.TemplateManager).MarkReady(); err != nil {
   332  				return nil, err
   333  			}
   334  		}
   335  		return nil, nil
   336  	})
   337  
   338  	h.init.translations.Add(func(context.Context) (any, error) {
   339  		if len(h.Sites) > 1 {
   340  			allTranslations := pagesToTranslationsMap(h.Sites)
   341  			assignTranslationsToPages(allTranslations, h.Sites)
   342  		}
   343  
   344  		return nil, nil
   345  	})
   346  
   347  	h.init.gitInfo.Add(func(context.Context) (any, error) {
   348  		err := h.loadGitInfo()
   349  		if err != nil {
   350  			return nil, fmt.Errorf("failed to load Git info: %w", err)
   351  		}
   352  		return nil, nil
   353  	})
   354  
   355  	return h, nil
   356  }
   357  
   358  // Returns true if we're running in a server.
   359  // Deprecated: use hugo.IsServer instead
   360  func (s *Site) IsServer() bool {
   361  	helpers.Deprecated(".Site.IsServer", "Use hugo.IsServer instead.", false)
   362  	return s.conf.Internal.Running
   363  }
   364  
   365  // Returns the server port.
   366  func (s *Site) ServerPort() int {
   367  	return s.conf.C.BaseURL.Port()
   368  }
   369  
   370  // Returns the configured title for this Site.
   371  func (s *Site) Title() string {
   372  	return s.conf.Title
   373  }
   374  
   375  func (s *Site) Copyright() string {
   376  	return s.conf.Copyright
   377  }
   378  
   379  func (s *Site) RSSLink() template.URL {
   380  	helpers.Deprecated("Site.RSSLink", "Use the Output Format's Permalink method instead, e.g. .OutputFormats.Get \"RSS\".Permalink", false)
   381  	rssOutputFormat := s.home.OutputFormats().Get("rss")
   382  	return template.URL(rssOutputFormat.Permalink())
   383  }
   384  
   385  func (s *Site) Config() page.SiteConfig {
   386  	return page.SiteConfig{
   387  		Privacy:  s.conf.Privacy,
   388  		Services: s.conf.Services,
   389  	}
   390  }
   391  
   392  func (s *Site) LanguageCode() string {
   393  	return s.Language().LanguageCode()
   394  }
   395  
   396  // Returns all Sites for all languages.
   397  func (s *Site) Sites() page.Sites {
   398  	sites := make(page.Sites, len(s.h.Sites))
   399  	for i, s := range s.h.Sites {
   400  		sites[i] = s.Site()
   401  	}
   402  	return sites
   403  }
   404  
   405  // Returns Site currently rendering.
   406  func (s *Site) Current() page.Site {
   407  	return s.h.currentSite
   408  }
   409  
   410  // MainSections returns the list of main sections.
   411  func (s *Site) MainSections() []string {
   412  	return s.conf.C.MainSections
   413  }
   414  
   415  // Returns a struct with some information about the build.
   416  func (s *Site) Hugo() hugo.HugoInfo {
   417  	if s.h == nil || s.h.hugoInfo.Environment == "" {
   418  		panic("site: hugo: hugoInfo not initialized")
   419  	}
   420  	return s.h.hugoInfo
   421  }
   422  
   423  // Returns the BaseURL for this Site.
   424  func (s *Site) BaseURL() template.URL {
   425  	return template.URL(s.conf.C.BaseURL.WithPath)
   426  }
   427  
   428  // Returns the last modification date of the content.
   429  func (s *Site) LastChange() time.Time {
   430  	return s.lastmod
   431  }
   432  
   433  // Returns the Params configured for this site.
   434  func (s *Site) Params() maps.Params {
   435  	return s.conf.Params
   436  }
   437  
   438  func (s *Site) Author() map[string]any {
   439  	return s.conf.Author
   440  }
   441  
   442  func (s *Site) Authors() page.AuthorList {
   443  	return page.AuthorList{}
   444  }
   445  
   446  func (s *Site) Social() map[string]string {
   447  	return s.conf.Social
   448  }
   449  
   450  // Deprecated: Use .Site.Config.Services.Disqus.Shortname instead
   451  func (s *Site) DisqusShortname() string {
   452  	helpers.Deprecated(".Site.DisqusShortname", "Use .Site.Config.Services.Disqus.Shortname instead.", false)
   453  	return s.Config().Services.Disqus.Shortname
   454  }
   455  
   456  // Deprecated: Use .Site.Config.Services.GoogleAnalytics.ID instead
   457  func (s *Site) GoogleAnalytics() string {
   458  	helpers.Deprecated(".Site.GoogleAnalytics", "Use .Site.Config.Services.GoogleAnalytics.ID instead.", false)
   459  	return s.Config().Services.GoogleAnalytics.ID
   460  }
   461  
   462  func (s *Site) Param(key any) (any, error) {
   463  	return resource.Param(s, nil, key)
   464  }
   465  
   466  // Returns a map of all the data inside /data.
   467  func (s *Site) Data() map[string]any {
   468  	return s.s.h.Data()
   469  }
   470  
   471  func (s *Site) BuildDrafts() bool {
   472  	return s.conf.BuildDrafts
   473  }
   474  
   475  func (s *Site) IsMultiLingual() bool {
   476  	return s.h.isMultiLingual()
   477  }
   478  
   479  func (s *Site) LanguagePrefix() string {
   480  	prefix := s.GetLanguagePrefix()
   481  	if prefix == "" {
   482  		return ""
   483  	}
   484  	return "/" + prefix
   485  }
   486  
   487  // Returns the identity of this site.
   488  // This is for internal use only.
   489  func (s *Site) GetIdentity() identity.Identity {
   490  	return identity.KeyValueIdentity{Key: "site", Value: s.Lang()}
   491  }
   492  
   493  func (s *Site) Site() page.Site {
   494  	return page.WrapSite(s)
   495  }