github.com/rabbouni145/gg@v0.47.1/hugolib/site_render.go (about) 1 // Copyright 2016 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 "fmt" 18 "path" 19 "strings" 20 "sync" 21 22 "github.com/gohugoio/hugo/output" 23 ) 24 25 // renderPages renders pages each corresponding to a markdown file. 26 // TODO(bep np doc 27 func (s *Site) renderPages(cfg *BuildCfg) error { 28 29 results := make(chan error) 30 pages := make(chan *Page) 31 errs := make(chan error) 32 33 go errorCollator(results, errs) 34 35 numWorkers := getGoMaxProcs() * 4 36 37 wg := &sync.WaitGroup{} 38 39 for i := 0; i < numWorkers; i++ { 40 wg.Add(1) 41 go pageRenderer(s, pages, results, wg) 42 } 43 44 if len(s.headlessPages) > 0 { 45 wg.Add(1) 46 go headlessPagesPublisher(s, wg) 47 } 48 49 for _, page := range s.Pages { 50 if cfg.shouldRender(page) { 51 pages <- page 52 } 53 } 54 55 close(pages) 56 57 wg.Wait() 58 59 close(results) 60 61 err := <-errs 62 if err != nil { 63 return fmt.Errorf("Error(s) rendering pages: %s", err) 64 } 65 return nil 66 } 67 68 func headlessPagesPublisher(s *Site, wg *sync.WaitGroup) { 69 defer wg.Done() 70 for _, page := range s.headlessPages { 71 outFormat := page.outputFormats[0] // There is only one 72 if outFormat.Name != s.rc.Format.Name { 73 // Avoid double work. 74 continue 75 } 76 pageOutput, err := newPageOutput(page, false, false, outFormat) 77 if err == nil { 78 page.mainPageOutput = pageOutput 79 err = pageOutput.renderResources() 80 } 81 82 if err != nil { 83 s.Log.ERROR.Printf("Failed to render resources for headless page %q: %s", page, err) 84 } 85 } 86 } 87 88 func pageRenderer(s *Site, pages <-chan *Page, results chan<- error, wg *sync.WaitGroup) { 89 defer wg.Done() 90 91 for page := range pages { 92 93 for i, outFormat := range page.outputFormats { 94 95 if outFormat.Name != page.s.rc.Format.Name { 96 // Will be rendered ... later. 97 continue 98 } 99 100 var ( 101 pageOutput *PageOutput 102 err error 103 ) 104 105 if i == 0 { 106 pageOutput = page.mainPageOutput 107 } else { 108 pageOutput, err = page.mainPageOutput.copyWithFormat(outFormat, true) 109 } 110 111 if err != nil { 112 s.Log.ERROR.Printf("Failed to create output page for type %q for page %q: %s", outFormat.Name, page, err) 113 continue 114 } 115 116 if pageOutput == nil { 117 panic("no pageOutput") 118 } 119 120 // We only need to re-publish the resources if the output format is different 121 // from all of the previous (e.g. the "amp" use case). 122 shouldRender := i == 0 123 if i > 0 { 124 for j := i; j >= 0; j-- { 125 if outFormat.Path != page.outputFormats[j].Path { 126 shouldRender = true 127 } else { 128 shouldRender = false 129 } 130 } 131 } 132 133 if shouldRender { 134 if err := pageOutput.renderResources(); err != nil { 135 s.Log.ERROR.Printf("Failed to render resources for page %q: %s", page, err) 136 continue 137 } 138 } 139 140 var layouts []string 141 142 if page.selfLayout != "" { 143 layouts = []string{page.selfLayout} 144 } else { 145 layouts, err = s.layouts(pageOutput) 146 if err != nil { 147 s.Log.ERROR.Printf("Failed to resolve layout output %q for page %q: %s", outFormat.Name, page, err) 148 continue 149 } 150 } 151 152 switch pageOutput.outputFormat.Name { 153 154 case "RSS": 155 if err := s.renderRSS(pageOutput); err != nil { 156 results <- err 157 } 158 default: 159 targetPath, err := pageOutput.targetPath() 160 if err != nil { 161 s.Log.ERROR.Printf("Failed to create target path for output %q for page %q: %s", outFormat.Name, page, err) 162 continue 163 } 164 165 s.Log.DEBUG.Printf("Render %s to %q with layouts %q", pageOutput.Kind, targetPath, layouts) 166 167 if err := s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "page "+pageOutput.FullFilePath(), targetPath, pageOutput, layouts...); err != nil { 168 results <- err 169 } 170 171 // Only render paginators for the main output format 172 if i == 0 && pageOutput.IsNode() { 173 if err := s.renderPaginator(pageOutput); err != nil { 174 results <- err 175 } 176 } 177 } 178 179 } 180 } 181 } 182 183 // renderPaginator must be run after the owning Page has been rendered. 184 func (s *Site) renderPaginator(p *PageOutput) error { 185 if p.paginator != nil { 186 s.Log.DEBUG.Printf("Render paginator for page %q", p.Path()) 187 paginatePath := s.Cfg.GetString("paginatePath") 188 189 // write alias for page 1 190 addend := fmt.Sprintf("/%s/%d", paginatePath, 1) 191 target, err := p.createTargetPath(p.outputFormat, false, addend) 192 if err != nil { 193 return err 194 } 195 196 // TODO(bep) do better 197 link := newOutputFormat(p.Page, p.outputFormat).Permalink() 198 if err := s.writeDestAlias(target, link, p.outputFormat, nil); err != nil { 199 return err 200 } 201 202 pagers := p.paginator.Pagers() 203 204 for i, pager := range pagers { 205 if i == 0 { 206 // already created 207 continue 208 } 209 210 pagerNode, err := p.copy() 211 if err != nil { 212 return err 213 } 214 215 pagerNode.origOnCopy = p.Page 216 217 pagerNode.paginator = pager 218 if pager.TotalPages() > 0 { 219 first, _ := pager.page(0) 220 pagerNode.Date = first.Date 221 pagerNode.Lastmod = first.Lastmod 222 } 223 224 pageNumber := i + 1 225 addend := fmt.Sprintf("/%s/%d", paginatePath, pageNumber) 226 targetPath, _ := p.targetPath(addend) 227 layouts, err := p.layouts() 228 229 if err != nil { 230 return err 231 } 232 233 if err := s.renderAndWritePage( 234 &s.PathSpec.ProcessingStats.PaginatorPages, 235 pagerNode.title, 236 targetPath, pagerNode, layouts...); err != nil { 237 return err 238 } 239 240 } 241 } 242 return nil 243 } 244 245 func (s *Site) renderRSS(p *PageOutput) error { 246 247 if !s.isEnabled(kindRSS) { 248 return nil 249 } 250 251 p.Kind = kindRSS 252 253 limit := s.Cfg.GetInt("rssLimit") 254 if limit >= 0 && len(p.Pages) > limit { 255 p.Pages = p.Pages[:limit] 256 p.data["Pages"] = p.Pages 257 } 258 259 layouts, err := s.layoutHandler.For( 260 p.layoutDescriptor, 261 p.outputFormat) 262 if err != nil { 263 return err 264 } 265 266 targetPath, err := p.targetPath() 267 if err != nil { 268 return err 269 } 270 271 return s.renderAndWriteXML(&s.PathSpec.ProcessingStats.Pages, p.title, 272 targetPath, p, layouts...) 273 } 274 275 func (s *Site) render404() error { 276 if !s.isEnabled(kind404) { 277 return nil 278 } 279 280 p := s.newNodePage(kind404) 281 282 p.title = "404 Page not found" 283 p.data["Pages"] = s.Pages 284 p.Pages = s.Pages 285 p.URLPath.URL = "404.html" 286 287 if err := p.initTargetPathDescriptor(); err != nil { 288 return err 289 } 290 291 nfLayouts := []string{"404.html"} 292 293 htmlOut := output.HTMLFormat 294 htmlOut.BaseName = "404" 295 296 pageOutput, err := newPageOutput(p, false, false, htmlOut) 297 if err != nil { 298 return err 299 } 300 301 targetPath, err := pageOutput.targetPath() 302 if err != nil { 303 s.Log.ERROR.Printf("Failed to create target path for page %q: %s", p, err) 304 } 305 306 return s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "404 page", targetPath, pageOutput, s.appendThemeTemplates(nfLayouts)...) 307 } 308 309 func (s *Site) renderSitemap() error { 310 if !s.isEnabled(kindSitemap) { 311 return nil 312 } 313 314 sitemapDefault := parseSitemap(s.Cfg.GetStringMap("sitemap")) 315 316 n := s.newNodePage(kindSitemap) 317 318 // Include all pages (regular, home page, taxonomies etc.) 319 pages := s.Pages 320 321 page := s.newNodePage(kindSitemap) 322 page.URLPath.URL = "" 323 if err := page.initTargetPathDescriptor(); err != nil { 324 return err 325 } 326 page.Sitemap.ChangeFreq = sitemapDefault.ChangeFreq 327 page.Sitemap.Priority = sitemapDefault.Priority 328 page.Sitemap.Filename = sitemapDefault.Filename 329 330 n.data["Pages"] = pages 331 n.Pages = pages 332 333 // TODO(bep) we have several of these 334 if err := page.initTargetPathDescriptor(); err != nil { 335 return err 336 } 337 338 // TODO(bep) this should be done somewhere else 339 for _, page := range pages { 340 if page.Sitemap.ChangeFreq == "" { 341 page.Sitemap.ChangeFreq = sitemapDefault.ChangeFreq 342 } 343 344 if page.Sitemap.Priority == -1 { 345 page.Sitemap.Priority = sitemapDefault.Priority 346 } 347 348 if page.Sitemap.Filename == "" { 349 page.Sitemap.Filename = sitemapDefault.Filename 350 } 351 } 352 353 smLayouts := []string{"sitemap.xml", "_default/sitemap.xml", "_internal/_default/sitemap.xml"} 354 addLanguagePrefix := n.Site.IsMultiLingual() 355 356 return s.renderAndWriteXML(&s.PathSpec.ProcessingStats.Sitemaps, "sitemap", 357 n.addLangPathPrefixIfFlagSet(page.Sitemap.Filename, addLanguagePrefix), n, s.appendThemeTemplates(smLayouts)...) 358 } 359 360 func (s *Site) renderRobotsTXT() error { 361 if !s.isEnabled(kindRobotsTXT) { 362 return nil 363 } 364 365 if !s.Cfg.GetBool("enableRobotsTXT") { 366 return nil 367 } 368 369 p := s.newNodePage(kindRobotsTXT) 370 if err := p.initTargetPathDescriptor(); err != nil { 371 return err 372 } 373 p.data["Pages"] = s.Pages 374 p.Pages = s.Pages 375 376 rLayouts := []string{"robots.txt", "_default/robots.txt", "_internal/_default/robots.txt"} 377 378 pageOutput, err := newPageOutput(p, false, false, output.RobotsTxtFormat) 379 if err != nil { 380 return err 381 } 382 383 targetPath, err := pageOutput.targetPath() 384 if err != nil { 385 s.Log.ERROR.Printf("Failed to create target path for page %q: %s", p, err) 386 } 387 388 return s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "Robots Txt", targetPath, pageOutput, s.appendThemeTemplates(rLayouts)...) 389 390 } 391 392 // renderAliases renders shell pages that simply have a redirect in the header. 393 func (s *Site) renderAliases() error { 394 for _, p := range s.Pages { 395 if len(p.Aliases) == 0 { 396 continue 397 } 398 399 for _, f := range p.outputFormats { 400 if !f.IsHTML { 401 continue 402 } 403 404 o := newOutputFormat(p, f) 405 plink := o.Permalink() 406 407 for _, a := range p.Aliases { 408 if f.Path != "" { 409 // Make sure AMP and similar doesn't clash with regular aliases. 410 a = path.Join(a, f.Path) 411 } 412 413 lang := p.Lang() 414 415 if s.owner.multihost && !strings.HasPrefix(a, "/"+lang) { 416 // These need to be in its language root. 417 a = path.Join(lang, a) 418 } 419 420 if err := s.writeDestAlias(a, plink, f, p); err != nil { 421 return err 422 } 423 } 424 } 425 } 426 427 if s.owner.multilingual.enabled() && !s.owner.IsMultihost() { 428 html, found := s.outputFormatsConfig.GetByName("HTML") 429 if found { 430 mainLang := s.owner.multilingual.DefaultLang 431 if s.Info.defaultContentLanguageInSubdir { 432 mainLangURL := s.PathSpec.AbsURL(mainLang.Lang, false) 433 s.Log.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL) 434 if err := s.publishDestAlias(true, "/", mainLangURL, html, nil); err != nil { 435 return err 436 } 437 } else { 438 mainLangURL := s.PathSpec.AbsURL("", false) 439 s.Log.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL) 440 if err := s.publishDestAlias(true, mainLang.Lang, mainLangURL, html, nil); err != nil { 441 return err 442 } 443 } 444 } 445 } 446 447 return nil 448 }