diff options
Diffstat (limited to 'hugolib/site_new.go')
-rw-r--r-- | hugolib/site_new.go | 458 |
1 files changed, 458 insertions, 0 deletions
diff --git a/hugolib/site_new.go b/hugolib/site_new.go new file mode 100644 index 000000000..f449b857a --- /dev/null +++ b/hugolib/site_new.go @@ -0,0 +1,458 @@ +// Copyright 2023 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" + "sort" + "time" + + radix "github.com/armon/go-radix" + "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/identity" + "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/page" + "github.com/gohugoio/hugo/resources/page/pagemeta" + "github.com/gohugoio/hugo/resources/resource" + "github.com/gohugoio/hugo/tpl" + "github.com/gohugoio/hugo/tpl/tplimpl" +) + +var ( + _ page.Site = (*Site)(nil) +) + +type Site struct { + conf *allconfig.Config + language *langs.Language + + // The owning container. + h *HugoSites + + *deps.Deps + + // Page navigation. + *PageCollections + taxonomies page.TaxonomyList + menus navigation.Menus + + siteBucket *pagesMapBucket + + // 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 + + // We render each site for all the relevant output formats in serial with + // this rendering context pointing to the current one. + rc *siteRenderingContext + + // 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() + + logger := cfg.Logger + if logger == nil { + logger = loggers.NewErrorLogger() + } + ignorableLogger := loggers.NewIgnorableLogger(logger, conf.IgnoredErrors()) + + firstSiteDeps := &deps.Deps{ + Fs: cfg.Fs, + Log: ignorableLogger, + Conf: conf, + TemplateProvider: tplimpl.DefaultTemplateProvider, + TranslationProvider: i18n.NewTranslationProvider(), + } + if err := firstSiteDeps.Init(); err != nil { + return nil, err + } + + confm := cfg.Configs + var sites []*Site + + for i, confp := range confm.ConfigLangs() { + language := confp.Language() + if confp.IsLangDisabled(language.Lang) { + continue + } + k := language.Lang + conf := confm.LanguageConfigMap[k] + + frontmatterHandler, err := pagemeta.NewFrontmatterHandler(cfg.Logger, conf.Frontmatter) + if err != nil { + return nil, err + } + + s := &Site{ + conf: conf, + language: language, + siteBucket: &pagesMapBucket{ + cascade: conf.Cascade.Config, + }, + 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 + } + + // Site deps start. + var taxonomiesConfig taxonomiesConfig = conf.Taxonomies + pm := &pageMap{ + contentMap: newContentMap(contentMapConfig{ + lang: k, + taxonomyConfig: taxonomiesConfig.Values(), + taxonomyDisabled: !conf.IsKindEnabled(page.KindTerm), + taxonomyTermDisabled: !conf.IsKindEnabled(page.KindTaxonomy), + pageDisabled: !conf.IsKindEnabled(page.KindPage), + }), + s: s, + } + + s.PageCollections = newPageCollections(pm) + s.siteRefLinker, err = newSiteRefLinker(s) + + // 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") + } + + // Sort the sites by language weight (if set) or lang. + sort.Slice(sites, func(i, j int) bool { + li := sites[i].language + lj := sites[j].language + if li.Weight != lj.Weight { + return li.Weight < lj.Weight + } + return li.Lang < lj.Lang + }) + + h, err := newHugoSitesNew(cfg, firstSiteDeps, sites) + if err == nil && h == nil { + panic("hugo: newHugoSitesNew returned nil error and nil HugoSites") + } + + return h, err +} + +func newHugoSitesNew(cfg deps.DepsCfg, d *deps.Deps, sites []*Site) (*HugoSites, error) { + numWorkers := config.GetNumWorkerMultiplier() + if numWorkers > len(sites) { + numWorkers = len(sites) + } + var workers *para.Workers + if numWorkers > 1 { + workers = para.New(numWorkers) + } + + h := &HugoSites{ + Sites: sites, + Deps: sites[0].Deps, + Configs: cfg.Configs, + workers: workers, + numWorkers: numWorkers, + currentSite: sites[0], + skipRebuildForFilenames: make(map[string]bool), + init: &hugoSitesInit{ + data: lazy.New(), + layouts: lazy.New(), + gitInfo: lazy.New(), + translations: 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.Base.Environment, 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), + } + + // Only needed in server mode. + if cfg.Configs.Base.Internal.Running { + h.ContentChanges = &contentChangeMap{ + pathSpec: h.PathSpec, + symContent: make(map[string]map[string]bool), + leafBundles: radix.New(), + branchBundles: make(map[string]bool), + } + } + + h.init.data.Add(func(context.Context) (any, error) { + err := h.loadData(h.PathSpec.BaseFs.Data.Dirs) + 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.translations.Add(func(context.Context) (any, error) { + if len(h.Sites) > 1 { + allTranslations := pagesToTranslationsMap(h.Sites) + assignTranslationsToPages(allTranslations, h.Sites) + } + + 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 +} + +// Returns true if we're running in a server. +func (s *Site) IsServer() bool { + 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 +} + +func (s *Site) RSSLink() string { + rssOutputFormat, found := s.conf.C.KindOutputFormats[page.KindHome].GetByName("rss") + if !found { + return "" + } + return s.permalink(rssOutputFormat.BaseFilename()) +} + +func (s *Site) Config() page.SiteConfig { + return page.SiteConfig{ + Privacy: s.conf.Privacy, + Services: s.conf.Services, + } +} + +func (s *Site) LanguageCode() string { + if s.conf.LanguageCode != "" { + return s.conf.LanguageCode + } + return s.language.Lang +} + +// 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 { + 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() template.URL { + return template.URL(s.conf.C.BaseURL.WithPath) +} + +// Returns the last modification date of the content. +func (s *Site) LastChange() time.Time { + return s.lastmod +} + +// Returns the Params configured for this site. +func (s *Site) Params() maps.Params { + return s.conf.Params +} + +func (s *Site) Author() map[string]any { + return s.conf.Author +} + +func (s *Site) Social() map[string]string { + return s.conf.Social +} + +// TODO(bep): deprecate. +func (s *Site) DisqusShortname() string { + return s.Config().Services.Disqus.Shortname +} + +// TODO(bep): deprecate. +func (s *Site) GoogleAnalytics() string { + return s.Config().Services.GoogleAnalytics.ID +} + +func (s *Site) Param(key string) (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) LanguagePrefix() string { + conf := s.s.Conf + if !conf.IsMultiLingual() { + return "" + } + + if !conf.DefaultContentLanguageInSubdir() && s.language.Lang == conf.DefaultContentLanguage() { + return "" + } + + return "/" + s.language.Lang +} + +// Returns the identity of this site. +// This is for internal use only. +func (s *Site) GetIdentity() identity.Identity { + return identity.KeyValueIdentity{Key: "site", Value: s.Lang()} +} + +func (s *Site) Site() page.Site { + return page.WrapSite(s) +} |