diff --git a/hugolib/site.go b/hugolib/site.go index d0546e9107f..d0a3bd37091 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -15,7 +15,9 @@ package hugolib import ( "context" + "errors" "fmt" + "html/template" "io" "mime" "net/url" @@ -28,10 +30,24 @@ import ( "time" "github.com/bep/logg" + "github.com/gohugoio/hugo/cache/dynacache" "github.com/gohugoio/hugo/common/htime" "github.com/gohugoio/hugo/common/hugio" + "github.com/gohugoio/hugo/common/hugo" + "github.com/gohugoio/hugo/common/loggers" + "github.com/gohugoio/hugo/common/maps" + "github.com/gohugoio/hugo/common/para" "github.com/gohugoio/hugo/common/types" + "github.com/gohugoio/hugo/config" + "github.com/gohugoio/hugo/config/allconfig" + "github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/hugolib/doctree" + "github.com/gohugoio/hugo/hugolib/pagesfromdata" + "github.com/gohugoio/hugo/internal/warpc" + "github.com/gohugoio/hugo/langs/i18n" + "github.com/gohugoio/hugo/modules" + "github.com/gohugoio/hugo/resources" + "github.com/gohugoio/hugo/tpl/tplimpl" "golang.org/x/text/unicode/norm" "github.com/gohugoio/hugo/common/paths" @@ -50,6 +66,9 @@ import ( "github.com/gohugoio/hugo/resources/kinds" "github.com/gohugoio/hugo/resources/page" + "github.com/gohugoio/hugo/resources/page/pagemeta" + "github.com/gohugoio/hugo/resources/page/siteidentities" + "github.com/gohugoio/hugo/resources/resource" "github.com/gohugoio/hugo/lazy" @@ -61,6 +80,556 @@ import ( "github.com/gohugoio/hugo/tpl" ) +var _ page.Site = (*Site)(nil) + +type siteState int + +const ( + siteStateInit siteState = iota + siteStateReady +) + +type Site struct { + state siteState + conf *allconfig.Config + language *langs.Language + languagei int + pageMap *pageMap + + // The owning container. + h *HugoSites + + *deps.Deps + + // Page navigation. + *pageFinder + taxonomies page.TaxonomyList + menus navigation.Menus + + // Shortcut to the home page. Note that this may be nil if + // home page, for some odd reason, is disabled. + home *pageState + + // The last modification date of this site. + lastmod time.Time + + relatedDocsHandler *page.RelatedDocsHandler + siteRefLinker + publisher publisher.Publisher + frontmatterHandler pagemeta.FrontMatterHandler + + // The output formats that we need to render this site in. This slice + // will be fixed once set. + // This will be the union of Site.Pages' outputFormats. + // This slice will be sorted. + renderFormats output.Formats + + // Lazily loaded site dependencies + init *siteInit +} + +func (s *Site) Debug() { + fmt.Println("Debugging site", s.Lang(), "=>") + // fmt.Println(s.pageMap.testDump()) +} + +// NewHugoSites creates HugoSites from the given config. +func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) { + conf := cfg.Configs.GetFirstLanguageConfig() + + var logger loggers.Logger + if cfg.TestLogger != nil { + logger = cfg.TestLogger + } else { + var logHookLast func(e *logg.Entry) error + if cfg.Configs.Base.PanicOnWarning { + logHookLast = loggers.PanicOnWarningHook + } + if cfg.LogOut == nil { + cfg.LogOut = os.Stdout + } + if cfg.LogLevel == 0 { + cfg.LogLevel = logg.LevelWarn + } + + logOpts := loggers.Options{ + Level: cfg.LogLevel, + DistinctLevel: logg.LevelWarn, // This will drop duplicate log warning and errors. + HandlerPost: logHookLast, + Stdout: cfg.LogOut, + Stderr: cfg.LogOut, + StoreErrors: conf.Watching(), + SuppressStatements: conf.IgnoredLogs(), + } + logger = loggers.New(logOpts) + + } + + memCache := dynacache.New(dynacache.Options{Watching: conf.Watching(), Log: logger}) + + var h *HugoSites + onSignalRebuild := func(ids ...identity.Identity) { + // This channel is buffered, but make sure we do this in a non-blocking way. + if cfg.ChangesFromBuild != nil { + go func() { + cfg.ChangesFromBuild <- ids + }() + } + } + + firstSiteDeps := &deps.Deps{ + Fs: cfg.Fs, + Log: logger, + Conf: conf, + BuildState: &deps.BuildState{ + OnSignalRebuild: onSignalRebuild, + }, + MemCache: memCache, + TemplateProvider: tplimpl.DefaultTemplateProvider, + TranslationProvider: i18n.NewTranslationProvider(), + WasmDispatchers: warpc.AllDispatchers( + warpc.Options{ + CompilationCacheDir: filepath.Join(conf.Dirs().CacheDir, "_warpc"), + + // Katex is relatively slow. + PoolSize: 8, + Infof: logger.InfoCommand("wasm").Logf, + }, + ), + } + + if err := firstSiteDeps.Init(); err != nil { + return nil, err + } + + confm := cfg.Configs + if err := confm.Validate(logger); err != nil { + return nil, err + } + var sites []*Site + + ns := &contentNodeShifter{ + numLanguages: len(confm.Languages), + } + + treeConfig := doctree.Config[contentNodeI]{ + Shifter: ns, + } + + pageTrees := &pageTrees{ + treePages: doctree.New( + treeConfig, + ), + treeResources: doctree.New( + treeConfig, + ), + treeTaxonomyEntries: doctree.NewTreeShiftTree[*weightedContentNode](doctree.DimensionLanguage.Index(), len(confm.Languages)), + treePagesFromTemplateAdapters: doctree.NewTreeShiftTree[*pagesfromdata.PagesFromTemplate](doctree.DimensionLanguage.Index(), len(confm.Languages)), + } + + pageTrees.createMutableTrees() + + for i, confp := range confm.ConfigLangs() { + language := confp.Language() + if language.Disabled { + continue + } + k := language.Lang + conf := confm.LanguageConfigMap[k] + frontmatterHandler, err := pagemeta.NewFrontmatterHandler(firstSiteDeps.Log, conf.Frontmatter) + if err != nil { + return nil, err + } + + langs.SetParams(language, conf.Params) + + s := &Site{ + conf: conf, + language: language, + languagei: i, + frontmatterHandler: frontmatterHandler, + } + + if i == 0 { + firstSiteDeps.Site = s + s.Deps = firstSiteDeps + } else { + d, err := firstSiteDeps.Clone(s, confp) + if err != nil { + return nil, err + } + s.Deps = d + } + + s.pageMap = newPageMap(i, s, memCache, pageTrees) + + s.pageFinder = newPageFinder(s.pageMap) + s.siteRefLinker, err = newSiteRefLinker(s) + if err != nil { + return nil, err + } + // Set up the main publishing chain. + pub, err := publisher.NewDestinationPublisher( + firstSiteDeps.ResourceSpec, + s.conf.OutputFormats.Config, + s.conf.MediaTypes.Config, + ) + if err != nil { + return nil, err + } + + s.publisher = pub + s.relatedDocsHandler = page.NewRelatedDocsHandler(s.conf.Related) + // Site deps end. + + s.prepareInits() + sites = append(sites, s) + } + + if len(sites) == 0 { + return nil, errors.New("no sites to build") + } + + // Pull the default content language to the top, then sort the sites by language weight (if set) or lang. + defaultContentLanguage := confm.Base.DefaultContentLanguage + sort.Slice(sites, func(i, j int) bool { + li := sites[i].language + lj := sites[j].language + if li.Lang == defaultContentLanguage { + return true + } + + if lj.Lang == defaultContentLanguage { + return false + } + + if li.Weight != lj.Weight { + return li.Weight < lj.Weight + } + return li.Lang < lj.Lang + }) + + var err error + h, err = newHugoSites(cfg, firstSiteDeps, pageTrees, sites) + if err == nil && h == nil { + panic("hugo: newHugoSitesNew returned nil error and nil HugoSites") + } + + return h, err +} + +func newHugoSites(cfg deps.DepsCfg, d *deps.Deps, pageTrees *pageTrees, sites []*Site) (*HugoSites, error) { + numWorkers := config.GetNumWorkerMultiplier() + numWorkersSite := numWorkers + if numWorkersSite > len(sites) { + numWorkersSite = len(sites) + } + workersSite := para.New(numWorkersSite) + + h := &HugoSites{ + Sites: sites, + Deps: sites[0].Deps, + Configs: cfg.Configs, + workersSite: workersSite, + numWorkersSites: numWorkers, + numWorkers: numWorkers, + pageTrees: pageTrees, + cachePages: dynacache.GetOrCreatePartition[string, + page.Pages](d.MemCache, "/pags/all", + dynacache.OptionsPartition{Weight: 10, ClearWhen: dynacache.ClearOnRebuild}, + ), + cacheContentSource: dynacache.GetOrCreatePartition[string, *resources.StaleValue[[]byte]](d.MemCache, "/cont/src", dynacache.OptionsPartition{Weight: 70, ClearWhen: dynacache.ClearOnChange}), + translationKeyPages: maps.NewSliceCache[page.Page](), + currentSite: sites[0], + skipRebuildForFilenames: make(map[string]bool), + init: &hugoSitesInit{ + data: lazy.New(), + layouts: lazy.New(), + gitInfo: lazy.New(), + }, + } + + // Assemble dependencies to be used in hugo.Deps. + var dependencies []*hugo.Dependency + var depFromMod func(m modules.Module) *hugo.Dependency + depFromMod = func(m modules.Module) *hugo.Dependency { + dep := &hugo.Dependency{ + Path: m.Path(), + Version: m.Version(), + Time: m.Time(), + Vendor: m.Vendor(), + } + + // These are pointers, but this all came from JSON so there's no recursive navigation, + // so just create new values. + if m.Replace() != nil { + dep.Replace = depFromMod(m.Replace()) + } + if m.Owner() != nil { + dep.Owner = depFromMod(m.Owner()) + } + return dep + } + for _, m := range d.Paths.AllModules() { + dependencies = append(dependencies, depFromMod(m)) + } + + h.hugoInfo = hugo.NewInfo(h.Configs.GetFirstLanguageConfig(), dependencies) + + var prototype *deps.Deps + for i, s := range sites { + s.h = h + if err := s.Deps.Compile(prototype); err != nil { + return nil, err + } + if i == 0 { + prototype = s.Deps + } + } + + h.fatalErrorHandler = &fatalErrorHandler{ + h: h, + donec: make(chan bool), + } + + h.init.data.Add(func(context.Context) (any, error) { + err := h.loadData() + if err != nil { + return nil, fmt.Errorf("failed to load data: %w", err) + } + return nil, nil + }) + + h.init.layouts.Add(func(context.Context) (any, error) { + for _, s := range h.Sites { + if err := s.Tmpl().(tpl.TemplateManager).MarkReady(); err != nil { + return nil, err + } + } + return nil, nil + }) + + h.init.gitInfo.Add(func(context.Context) (any, error) { + err := h.loadGitInfo() + if err != nil { + return nil, fmt.Errorf("failed to load Git info: %w", err) + } + return nil, nil + }) + + return h, nil +} + +// Deprecated: Use hugo.IsServer instead. +func (s *Site) IsServer() bool { + hugo.Deprecate(".Site.IsServer", "Use hugo.IsServer instead.", "v0.120.0") + return s.conf.Internal.Running +} + +// Returns the server port. +func (s *Site) ServerPort() int { + return s.conf.C.BaseURL.Port() +} + +// Returns the configured title for this Site. +func (s *Site) Title() string { + return s.conf.Title +} + +func (s *Site) Copyright() string { + return s.conf.Copyright +} + +// Deprecated: Use .Site.Home.OutputFormats.Get "rss" instead. +func (s *Site) RSSLink() template.URL { + hugo.Deprecate(".Site.RSSLink", "Use the Output Format's Permalink method instead, e.g. .OutputFormats.Get \"RSS\".Permalink", "v0.114.0") + rssOutputFormat := s.home.OutputFormats().Get("rss") + return template.URL(rssOutputFormat.Permalink()) +} + +func (s *Site) Config() page.SiteConfig { + return page.SiteConfig{ + Privacy: s.conf.Privacy, + Services: s.conf.Services, + } +} + +func (s *Site) LanguageCode() string { + return s.Language().LanguageCode() +} + +// Returns all Sites for all languages. +func (s *Site) Sites() page.Sites { + sites := make(page.Sites, len(s.h.Sites)) + for i, s := range s.h.Sites { + sites[i] = s.Site() + } + return sites +} + +// Returns Site currently rendering. +func (s *Site) Current() page.Site { + return s.h.currentSite +} + +// MainSections returns the list of main sections. +func (s *Site) MainSections() []string { + s.CheckReady() + return s.conf.C.MainSections +} + +// Returns a struct with some information about the build. +func (s *Site) Hugo() hugo.HugoInfo { + if s.h == nil || s.h.hugoInfo.Environment == "" { + panic("site: hugo: hugoInfo not initialized") + } + return s.h.hugoInfo +} + +// Returns the BaseURL for this Site. +func (s *Site) BaseURL() string { + return s.conf.C.BaseURL.WithPath +} + +// Deprecated: Use .Site.Lastmod instead. +func (s *Site) LastChange() time.Time { + s.CheckReady() + hugo.Deprecate(".Site.LastChange", "Use .Site.Lastmod instead.", "v0.123.0") + return s.lastmod +} + +// Returns the last modification date of the content. +func (s *Site) Lastmod() time.Time { + return s.lastmod +} + +// Returns the Params configured for this site. +func (s *Site) Params() maps.Params { + return s.conf.Params +} + +// Deprecated: Use taxonomies instead. +func (s *Site) Author() map[string]any { + if len(s.conf.Author) != 0 { + hugo.Deprecate(".Site.Author", "Use taxonomies instead.", "v0.124.0") + } + return s.conf.Author +} + +// Deprecated: Use taxonomies instead. +func (s *Site) Authors() page.AuthorList { + hugo.Deprecate(".Site.Authors", "Use taxonomies instead.", "v0.124.0") + return page.AuthorList{} +} + +// Deprecated: Use .Site.Params instead. +func (s *Site) Social() map[string]string { + hugo.Deprecate(".Site.Social", "Use .Site.Params instead.", "v0.124.0") + return s.conf.Social +} + +// Deprecated: Use .Site.Config.Services.Disqus.Shortname instead. +func (s *Site) DisqusShortname() string { + hugo.Deprecate(".Site.DisqusShortname", "Use .Site.Config.Services.Disqus.Shortname instead.", "v0.120.0") + return s.Config().Services.Disqus.Shortname +} + +// Deprecated: Use .Site.Config.Services.GoogleAnalytics.ID instead. +func (s *Site) GoogleAnalytics() string { + hugo.Deprecate(".Site.GoogleAnalytics", "Use .Site.Config.Services.GoogleAnalytics.ID instead.", "v0.120.0") + return s.Config().Services.GoogleAnalytics.ID +} + +func (s *Site) Param(key any) (any, error) { + return resource.Param(s, nil, key) +} + +// Returns a map of all the data inside /data. +func (s *Site) Data() map[string]any { + return s.s.h.Data() +} + +func (s *Site) BuildDrafts() bool { + return s.conf.BuildDrafts +} + +// Deprecated: Use hugo.IsMultilingual instead. +func (s *Site) IsMultiLingual() bool { + hugo.Deprecate(".Site.IsMultiLingual", "Use hugo.IsMultilingual instead.", "v0.124.0") + return s.h.isMultilingual() +} + +func (s *Site) LanguagePrefix() string { + prefix := s.GetLanguagePrefix() + if prefix == "" { + return "" + } + return "/" + prefix +} + +func (s *Site) Site() page.Site { + return page.WrapSite(s) +} + +func (s *Site) ForEeachIdentityByName(name string, f func(identity.Identity) bool) { + if id, found := siteidentities.FromString(name); found { + if f(id) { + return + } + } +} + +// Pages returns all pages. +// This is for the current language only. +func (s *Site) Pages() page.Pages { + s.CheckReady() + return s.pageMap.getPagesInSection( + pageMapQueryPagesInSection{ + pageMapQueryPagesBelowPath: pageMapQueryPagesBelowPath{ + Path: "", + KeyPart: "global", + Include: pagePredicates.ShouldListGlobal, + }, + Recursive: true, + IncludeSelf: true, + }, + ) +} + +// RegularPages returns all the regular pages. +// This is for the current language only. +func (s *Site) RegularPages() page.Pages { + s.CheckReady() + return s.pageMap.getPagesInSection( + pageMapQueryPagesInSection{ + pageMapQueryPagesBelowPath: pageMapQueryPagesBelowPath{ + Path: "", + KeyPart: "global", + Include: pagePredicates.ShouldListGlobal.And(pagePredicates.KindPage), + }, + Recursive: true, + }, + ) +} + +// AllPages returns all pages for all sites. +func (s *Site) AllPages() page.Pages { + s.CheckReady() + return s.h.Pages() +} + +// AllRegularPages returns all regular pages for all sites. +func (s *Site) AllRegularPages() page.Pages { + s.CheckReady() + return s.h.RegularPages() +} + +func (s *Site) CheckReady() { + if s.state != siteStateReady { + panic("this method cannot be called before the site is fully initialized") + } +} + func (s *Site) Taxonomies() page.TaxonomyList { s.CheckReady() s.init.taxonomies.Do(context.Background()) diff --git a/hugolib/site_new.go b/hugolib/site_new.go deleted file mode 100644 index 19a7e42d755..00000000000 --- a/hugolib/site_new.go +++ /dev/null @@ -1,603 +0,0 @@ -// Copyright 2024 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "context" - "errors" - "fmt" - "html/template" - "os" - "path/filepath" - "sort" - "time" - - "github.com/bep/logg" - "github.com/gohugoio/hugo/cache/dynacache" - "github.com/gohugoio/hugo/common/hugo" - "github.com/gohugoio/hugo/common/loggers" - "github.com/gohugoio/hugo/common/maps" - "github.com/gohugoio/hugo/common/para" - "github.com/gohugoio/hugo/config" - "github.com/gohugoio/hugo/config/allconfig" - "github.com/gohugoio/hugo/deps" - "github.com/gohugoio/hugo/hugolib/doctree" - "github.com/gohugoio/hugo/hugolib/pagesfromdata" - "github.com/gohugoio/hugo/identity" - "github.com/gohugoio/hugo/internal/warpc" - "github.com/gohugoio/hugo/langs" - "github.com/gohugoio/hugo/langs/i18n" - "github.com/gohugoio/hugo/lazy" - "github.com/gohugoio/hugo/modules" - "github.com/gohugoio/hugo/navigation" - "github.com/gohugoio/hugo/output" - "github.com/gohugoio/hugo/publisher" - "github.com/gohugoio/hugo/resources" - "github.com/gohugoio/hugo/resources/page" - "github.com/gohugoio/hugo/resources/page/pagemeta" - "github.com/gohugoio/hugo/resources/page/siteidentities" - "github.com/gohugoio/hugo/resources/resource" - "github.com/gohugoio/hugo/tpl" - "github.com/gohugoio/hugo/tpl/tplimpl" -) - -var _ page.Site = (*Site)(nil) - -type siteState int - -const ( - siteStateInit siteState = iota - siteStateReady -) - -type Site struct { - state siteState - conf *allconfig.Config - language *langs.Language - languagei int - pageMap *pageMap - - // The owning container. - h *HugoSites - - *deps.Deps - - // Page navigation. - *pageFinder - taxonomies page.TaxonomyList - menus navigation.Menus - - // Shortcut to the home page. Note that this may be nil if - // home page, for some odd reason, is disabled. - home *pageState - - // The last modification date of this site. - lastmod time.Time - - relatedDocsHandler *page.RelatedDocsHandler - siteRefLinker - publisher publisher.Publisher - frontmatterHandler pagemeta.FrontMatterHandler - - // The output formats that we need to render this site in. This slice - // will be fixed once set. - // This will be the union of Site.Pages' outputFormats. - // This slice will be sorted. - renderFormats output.Formats - - // Lazily loaded site dependencies - init *siteInit -} - -func (s *Site) Debug() { - fmt.Println("Debugging site", s.Lang(), "=>") - // fmt.Println(s.pageMap.testDump()) -} - -// NewHugoSites creates HugoSites from the given config. -func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) { - conf := cfg.Configs.GetFirstLanguageConfig() - - var logger loggers.Logger - if cfg.TestLogger != nil { - logger = cfg.TestLogger - } else { - var logHookLast func(e *logg.Entry) error - if cfg.Configs.Base.PanicOnWarning { - logHookLast = loggers.PanicOnWarningHook - } - if cfg.LogOut == nil { - cfg.LogOut = os.Stdout - } - if cfg.LogLevel == 0 { - cfg.LogLevel = logg.LevelWarn - } - - logOpts := loggers.Options{ - Level: cfg.LogLevel, - DistinctLevel: logg.LevelWarn, // This will drop duplicate log warning and errors. - HandlerPost: logHookLast, - Stdout: cfg.LogOut, - Stderr: cfg.LogOut, - StoreErrors: conf.Watching(), - SuppressStatements: conf.IgnoredLogs(), - } - logger = loggers.New(logOpts) - - } - - memCache := dynacache.New(dynacache.Options{Watching: conf.Watching(), Log: logger}) - - var h *HugoSites - onSignalRebuild := func(ids ...identity.Identity) { - // This channel is buffered, but make sure we do this in a non-blocking way. - if cfg.ChangesFromBuild != nil { - go func() { - cfg.ChangesFromBuild <- ids - }() - } - } - - firstSiteDeps := &deps.Deps{ - Fs: cfg.Fs, - Log: logger, - Conf: conf, - BuildState: &deps.BuildState{ - OnSignalRebuild: onSignalRebuild, - }, - MemCache: memCache, - TemplateProvider: tplimpl.DefaultTemplateProvider, - TranslationProvider: i18n.NewTranslationProvider(), - WasmDispatchers: warpc.AllDispatchers( - warpc.Options{ - CompilationCacheDir: filepath.Join(conf.Dirs().CacheDir, "_warpc"), - - // Katex is relatively slow. - PoolSize: 8, - Infof: logger.InfoCommand("wasm").Logf, - }, - ), - } - - if err := firstSiteDeps.Init(); err != nil { - return nil, err - } - - confm := cfg.Configs - if err := confm.Validate(logger); err != nil { - return nil, err - } - var sites []*Site - - ns := &contentNodeShifter{ - numLanguages: len(confm.Languages), - } - - treeConfig := doctree.Config[contentNodeI]{ - Shifter: ns, - } - - pageTrees := &pageTrees{ - treePages: doctree.New( - treeConfig, - ), - treeResources: doctree.New( - treeConfig, - ), - treeTaxonomyEntries: doctree.NewTreeShiftTree[*weightedContentNode](doctree.DimensionLanguage.Index(), len(confm.Languages)), - treePagesFromTemplateAdapters: doctree.NewTreeShiftTree[*pagesfromdata.PagesFromTemplate](doctree.DimensionLanguage.Index(), len(confm.Languages)), - } - - pageTrees.createMutableTrees() - - for i, confp := range confm.ConfigLangs() { - language := confp.Language() - if language.Disabled { - continue - } - k := language.Lang - conf := confm.LanguageConfigMap[k] - frontmatterHandler, err := pagemeta.NewFrontmatterHandler(firstSiteDeps.Log, conf.Frontmatter) - if err != nil { - return nil, err - } - - langs.SetParams(language, conf.Params) - - s := &Site{ - conf: conf, - language: language, - languagei: i, - frontmatterHandler: frontmatterHandler, - } - - if i == 0 { - firstSiteDeps.Site = s - s.Deps = firstSiteDeps - } else { - d, err := firstSiteDeps.Clone(s, confp) - if err != nil { - return nil, err - } - s.Deps = d - } - - s.pageMap = newPageMap(i, s, memCache, pageTrees) - - s.pageFinder = newPageFinder(s.pageMap) - s.siteRefLinker, err = newSiteRefLinker(s) - if err != nil { - return nil, err - } - // Set up the main publishing chain. - pub, err := publisher.NewDestinationPublisher( - firstSiteDeps.ResourceSpec, - s.conf.OutputFormats.Config, - s.conf.MediaTypes.Config, - ) - if err != nil { - return nil, err - } - - s.publisher = pub - s.relatedDocsHandler = page.NewRelatedDocsHandler(s.conf.Related) - // Site deps end. - - s.prepareInits() - sites = append(sites, s) - } - - if len(sites) == 0 { - return nil, errors.New("no sites to build") - } - - // Pull the default content language to the top, then sort the sites by language weight (if set) or lang. - defaultContentLanguage := confm.Base.DefaultContentLanguage - sort.Slice(sites, func(i, j int) bool { - li := sites[i].language - lj := sites[j].language - if li.Lang == defaultContentLanguage { - return true - } - - if lj.Lang == defaultContentLanguage { - return false - } - - if li.Weight != lj.Weight { - return li.Weight < lj.Weight - } - return li.Lang < lj.Lang - }) - - var err error - h, err = newHugoSites(cfg, firstSiteDeps, pageTrees, sites) - if err == nil && h == nil { - panic("hugo: newHugoSitesNew returned nil error and nil HugoSites") - } - - return h, err -} - -func newHugoSites(cfg deps.DepsCfg, d *deps.Deps, pageTrees *pageTrees, sites []*Site) (*HugoSites, error) { - numWorkers := config.GetNumWorkerMultiplier() - numWorkersSite := numWorkers - if numWorkersSite > len(sites) { - numWorkersSite = len(sites) - } - workersSite := para.New(numWorkersSite) - - h := &HugoSites{ - Sites: sites, - Deps: sites[0].Deps, - Configs: cfg.Configs, - workersSite: workersSite, - numWorkersSites: numWorkers, - numWorkers: numWorkers, - pageTrees: pageTrees, - cachePages: dynacache.GetOrCreatePartition[string, - page.Pages](d.MemCache, "/pags/all", - dynacache.OptionsPartition{Weight: 10, ClearWhen: dynacache.ClearOnRebuild}, - ), - cacheContentSource: dynacache.GetOrCreatePartition[string, *resources.StaleValue[[]byte]](d.MemCache, "/cont/src", dynacache.OptionsPartition{Weight: 70, ClearWhen: dynacache.ClearOnChange}), - translationKeyPages: maps.NewSliceCache[page.Page](), - currentSite: sites[0], - skipRebuildForFilenames: make(map[string]bool), - init: &hugoSitesInit{ - data: lazy.New(), - layouts: lazy.New(), - gitInfo: lazy.New(), - }, - } - - // Assemble dependencies to be used in hugo.Deps. - var dependencies []*hugo.Dependency - var depFromMod func(m modules.Module) *hugo.Dependency - depFromMod = func(m modules.Module) *hugo.Dependency { - dep := &hugo.Dependency{ - Path: m.Path(), - Version: m.Version(), - Time: m.Time(), - Vendor: m.Vendor(), - } - - // These are pointers, but this all came from JSON so there's no recursive navigation, - // so just create new values. - if m.Replace() != nil { - dep.Replace = depFromMod(m.Replace()) - } - if m.Owner() != nil { - dep.Owner = depFromMod(m.Owner()) - } - return dep - } - for _, m := range d.Paths.AllModules() { - dependencies = append(dependencies, depFromMod(m)) - } - - h.hugoInfo = hugo.NewInfo(h.Configs.GetFirstLanguageConfig(), dependencies) - - var prototype *deps.Deps - for i, s := range sites { - s.h = h - if err := s.Deps.Compile(prototype); err != nil { - return nil, err - } - if i == 0 { - prototype = s.Deps - } - } - - h.fatalErrorHandler = &fatalErrorHandler{ - h: h, - donec: make(chan bool), - } - - h.init.data.Add(func(context.Context) (any, error) { - err := h.loadData() - if err != nil { - return nil, fmt.Errorf("failed to load data: %w", err) - } - return nil, nil - }) - - h.init.layouts.Add(func(context.Context) (any, error) { - for _, s := range h.Sites { - if err := s.Tmpl().(tpl.TemplateManager).MarkReady(); err != nil { - return nil, err - } - } - return nil, nil - }) - - h.init.gitInfo.Add(func(context.Context) (any, error) { - err := h.loadGitInfo() - if err != nil { - return nil, fmt.Errorf("failed to load Git info: %w", err) - } - return nil, nil - }) - - return h, nil -} - -// Deprecated: Use hugo.IsServer instead. -func (s *Site) IsServer() bool { - hugo.Deprecate(".Site.IsServer", "Use hugo.IsServer instead.", "v0.120.0") - return s.conf.Internal.Running -} - -// Returns the server port. -func (s *Site) ServerPort() int { - return s.conf.C.BaseURL.Port() -} - -// Returns the configured title for this Site. -func (s *Site) Title() string { - return s.conf.Title -} - -func (s *Site) Copyright() string { - return s.conf.Copyright -} - -// Deprecated: Use .Site.Home.OutputFormats.Get "rss" instead. -func (s *Site) RSSLink() template.URL { - hugo.Deprecate(".Site.RSSLink", "Use the Output Format's Permalink method instead, e.g. .OutputFormats.Get \"RSS\".Permalink", "v0.114.0") - rssOutputFormat := s.home.OutputFormats().Get("rss") - return template.URL(rssOutputFormat.Permalink()) -} - -func (s *Site) Config() page.SiteConfig { - return page.SiteConfig{ - Privacy: s.conf.Privacy, - Services: s.conf.Services, - } -} - -func (s *Site) LanguageCode() string { - return s.Language().LanguageCode() -} - -// Returns all Sites for all languages. -func (s *Site) Sites() page.Sites { - sites := make(page.Sites, len(s.h.Sites)) - for i, s := range s.h.Sites { - sites[i] = s.Site() - } - return sites -} - -// Returns Site currently rendering. -func (s *Site) Current() page.Site { - return s.h.currentSite -} - -// MainSections returns the list of main sections. -func (s *Site) MainSections() []string { - s.CheckReady() - return s.conf.C.MainSections -} - -// Returns a struct with some information about the build. -func (s *Site) Hugo() hugo.HugoInfo { - if s.h == nil || s.h.hugoInfo.Environment == "" { - panic("site: hugo: hugoInfo not initialized") - } - return s.h.hugoInfo -} - -// Returns the BaseURL for this Site. -func (s *Site) BaseURL() string { - return s.conf.C.BaseURL.WithPath -} - -// Deprecated: Use .Site.Lastmod instead. -func (s *Site) LastChange() time.Time { - s.CheckReady() - hugo.Deprecate(".Site.LastChange", "Use .Site.Lastmod instead.", "v0.123.0") - return s.lastmod -} - -// Returns the last modification date of the content. -func (s *Site) Lastmod() time.Time { - return s.lastmod -} - -// Returns the Params configured for this site. -func (s *Site) Params() maps.Params { - return s.conf.Params -} - -// Deprecated: Use taxonomies instead. -func (s *Site) Author() map[string]any { - if len(s.conf.Author) != 0 { - hugo.Deprecate(".Site.Author", "Use taxonomies instead.", "v0.124.0") - } - return s.conf.Author -} - -// Deprecated: Use taxonomies instead. -func (s *Site) Authors() page.AuthorList { - hugo.Deprecate(".Site.Authors", "Use taxonomies instead.", "v0.124.0") - return page.AuthorList{} -} - -// Deprecated: Use .Site.Params instead. -func (s *Site) Social() map[string]string { - hugo.Deprecate(".Site.Social", "Use .Site.Params instead.", "v0.124.0") - return s.conf.Social -} - -// Deprecated: Use .Site.Config.Services.Disqus.Shortname instead. -func (s *Site) DisqusShortname() string { - hugo.Deprecate(".Site.DisqusShortname", "Use .Site.Config.Services.Disqus.Shortname instead.", "v0.120.0") - return s.Config().Services.Disqus.Shortname -} - -// Deprecated: Use .Site.Config.Services.GoogleAnalytics.ID instead. -func (s *Site) GoogleAnalytics() string { - hugo.Deprecate(".Site.GoogleAnalytics", "Use .Site.Config.Services.GoogleAnalytics.ID instead.", "v0.120.0") - return s.Config().Services.GoogleAnalytics.ID -} - -func (s *Site) Param(key any) (any, error) { - return resource.Param(s, nil, key) -} - -// Returns a map of all the data inside /data. -func (s *Site) Data() map[string]any { - return s.s.h.Data() -} - -func (s *Site) BuildDrafts() bool { - return s.conf.BuildDrafts -} - -// Deprecated: Use hugo.IsMultilingual instead. -func (s *Site) IsMultiLingual() bool { - hugo.Deprecate(".Site.IsMultiLingual", "Use hugo.IsMultilingual instead.", "v0.124.0") - return s.h.isMultilingual() -} - -func (s *Site) LanguagePrefix() string { - prefix := s.GetLanguagePrefix() - if prefix == "" { - return "" - } - return "/" + prefix -} - -func (s *Site) Site() page.Site { - return page.WrapSite(s) -} - -func (s *Site) ForEeachIdentityByName(name string, f func(identity.Identity) bool) { - if id, found := siteidentities.FromString(name); found { - if f(id) { - return - } - } -} - -// Pages returns all pages. -// This is for the current language only. -func (s *Site) Pages() page.Pages { - s.CheckReady() - return s.pageMap.getPagesInSection( - pageMapQueryPagesInSection{ - pageMapQueryPagesBelowPath: pageMapQueryPagesBelowPath{ - Path: "", - KeyPart: "global", - Include: pagePredicates.ShouldListGlobal, - }, - Recursive: true, - IncludeSelf: true, - }, - ) -} - -// RegularPages returns all the regular pages. -// This is for the current language only. -func (s *Site) RegularPages() page.Pages { - s.CheckReady() - return s.pageMap.getPagesInSection( - pageMapQueryPagesInSection{ - pageMapQueryPagesBelowPath: pageMapQueryPagesBelowPath{ - Path: "", - KeyPart: "global", - Include: pagePredicates.ShouldListGlobal.And(pagePredicates.KindPage), - }, - Recursive: true, - }, - ) -} - -// AllPages returns all pages for all sites. -func (s *Site) AllPages() page.Pages { - s.CheckReady() - return s.h.Pages() -} - -// AllRegularPages returns all regular pages for all sites. -func (s *Site) AllRegularPages() page.Pages { - s.CheckReady() - return s.h.RegularPages() -} - -func (s *Site) CheckReady() { - if s.state != siteStateReady { - panic("this method cannot be called before the site is fully initialized") - } -}