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 }