github.com/shohhei1126/hugo@v0.42.2-0.20180623210752-3d5928889ad7/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 != s.rc.Format { 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 != page.s.rc.Format { 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 if pageOutput.IsNode() { 172 if err := s.renderPaginator(pageOutput); err != nil { 173 results <- err 174 } 175 } 176 } 177 178 } 179 } 180 } 181 182 // renderPaginator must be run after the owning Page has been rendered. 183 func (s *Site) renderPaginator(p *PageOutput) error { 184 if p.paginator != nil { 185 s.Log.DEBUG.Printf("Render paginator for page %q", p.Path()) 186 paginatePath := s.Cfg.GetString("paginatePath") 187 188 // write alias for page 1 189 addend := fmt.Sprintf("/%s/%d", paginatePath, 1) 190 target, err := p.createTargetPath(p.outputFormat, false, addend) 191 if err != nil { 192 return err 193 } 194 195 // TODO(bep) do better 196 link := newOutputFormat(p.Page, p.outputFormat).Permalink() 197 if err := s.writeDestAlias(target, link, nil); err != nil { 198 return err 199 } 200 201 pagers := p.paginator.Pagers() 202 203 for i, pager := range pagers { 204 if i == 0 { 205 // already created 206 continue 207 } 208 209 pagerNode, err := p.copy() 210 if err != nil { 211 return err 212 } 213 214 pagerNode.origOnCopy = p.Page 215 216 pagerNode.paginator = pager 217 if pager.TotalPages() > 0 { 218 first, _ := pager.page(0) 219 pagerNode.Date = first.Date 220 pagerNode.Lastmod = first.Lastmod 221 } 222 223 pageNumber := i + 1 224 addend := fmt.Sprintf("/%s/%d", paginatePath, pageNumber) 225 targetPath, _ := p.targetPath(addend) 226 layouts, err := p.layouts() 227 228 if err != nil { 229 return err 230 } 231 232 if err := s.renderAndWritePage( 233 &s.PathSpec.ProcessingStats.PaginatorPages, 234 pagerNode.title, 235 targetPath, pagerNode, layouts...); err != nil { 236 return err 237 } 238 239 } 240 } 241 return nil 242 } 243 244 func (s *Site) renderRSS(p *PageOutput) error { 245 246 if !s.isEnabled(kindRSS) { 247 return nil 248 } 249 250 p.Kind = kindRSS 251 252 limit := s.Cfg.GetInt("rssLimit") 253 if limit >= 0 && len(p.Pages) > limit { 254 p.Pages = p.Pages[:limit] 255 p.Data["Pages"] = p.Pages 256 } 257 258 layouts, err := s.layoutHandler.For( 259 p.layoutDescriptor, 260 p.outputFormat) 261 if err != nil { 262 return err 263 } 264 265 targetPath, err := p.targetPath() 266 if err != nil { 267 return err 268 } 269 270 return s.renderAndWriteXML(&s.PathSpec.ProcessingStats.Pages, p.title, 271 targetPath, p, layouts...) 272 } 273 274 func (s *Site) render404() error { 275 if !s.isEnabled(kind404) { 276 return nil 277 } 278 279 p := s.newNodePage(kind404) 280 281 p.title = "404 Page not found" 282 p.Data["Pages"] = s.Pages 283 p.Pages = s.Pages 284 p.URLPath.URL = "404.html" 285 286 if err := p.initTargetPathDescriptor(); err != nil { 287 return err 288 } 289 290 nfLayouts := []string{"404.html"} 291 292 htmlOut := output.HTMLFormat 293 htmlOut.BaseName = "404" 294 295 pageOutput, err := newPageOutput(p, false, false, htmlOut) 296 if err != nil { 297 return err 298 } 299 300 targetPath, err := pageOutput.targetPath() 301 if err != nil { 302 s.Log.ERROR.Printf("Failed to create target path for page %q: %s", p, err) 303 } 304 305 return s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "404 page", targetPath, pageOutput, s.appendThemeTemplates(nfLayouts)...) 306 } 307 308 func (s *Site) renderSitemap() error { 309 if !s.isEnabled(kindSitemap) { 310 return nil 311 } 312 313 sitemapDefault := parseSitemap(s.Cfg.GetStringMap("sitemap")) 314 315 n := s.newNodePage(kindSitemap) 316 317 // Include all pages (regular, home page, taxonomies etc.) 318 pages := s.Pages 319 320 page := s.newNodePage(kindSitemap) 321 page.URLPath.URL = "" 322 if err := page.initTargetPathDescriptor(); err != nil { 323 return err 324 } 325 page.Sitemap.ChangeFreq = sitemapDefault.ChangeFreq 326 page.Sitemap.Priority = sitemapDefault.Priority 327 page.Sitemap.Filename = sitemapDefault.Filename 328 329 n.Data["Pages"] = pages 330 n.Pages = pages 331 332 // TODO(bep) we have several of these 333 if err := page.initTargetPathDescriptor(); err != nil { 334 return err 335 } 336 337 // TODO(bep) this should be done somewhere else 338 for _, page := range pages { 339 if page.Sitemap.ChangeFreq == "" { 340 page.Sitemap.ChangeFreq = sitemapDefault.ChangeFreq 341 } 342 343 if page.Sitemap.Priority == -1 { 344 page.Sitemap.Priority = sitemapDefault.Priority 345 } 346 347 if page.Sitemap.Filename == "" { 348 page.Sitemap.Filename = sitemapDefault.Filename 349 } 350 } 351 352 smLayouts := []string{"sitemap.xml", "_default/sitemap.xml", "_internal/_default/sitemap.xml"} 353 addLanguagePrefix := n.Site.IsMultiLingual() 354 355 return s.renderAndWriteXML(&s.PathSpec.ProcessingStats.Sitemaps, "sitemap", 356 n.addLangPathPrefixIfFlagSet(page.Sitemap.Filename, addLanguagePrefix), n, s.appendThemeTemplates(smLayouts)...) 357 } 358 359 func (s *Site) renderRobotsTXT() error { 360 if !s.isEnabled(kindRobotsTXT) { 361 return nil 362 } 363 364 if !s.Cfg.GetBool("enableRobotsTXT") { 365 return nil 366 } 367 368 p := s.newNodePage(kindRobotsTXT) 369 if err := p.initTargetPathDescriptor(); err != nil { 370 return err 371 } 372 p.Data["Pages"] = s.Pages 373 p.Pages = s.Pages 374 375 rLayouts := []string{"robots.txt", "_default/robots.txt", "_internal/_default/robots.txt"} 376 377 pageOutput, err := newPageOutput(p, false, false, output.RobotsTxtFormat) 378 if err != nil { 379 return err 380 } 381 382 targetPath, err := pageOutput.targetPath() 383 if err != nil { 384 s.Log.ERROR.Printf("Failed to create target path for page %q: %s", p, err) 385 } 386 387 return s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "Robots Txt", targetPath, pageOutput, s.appendThemeTemplates(rLayouts)...) 388 389 } 390 391 // renderAliases renders shell pages that simply have a redirect in the header. 392 func (s *Site) renderAliases() error { 393 for _, p := range s.Pages { 394 if len(p.Aliases) == 0 { 395 continue 396 } 397 398 for _, f := range p.outputFormats { 399 if !f.IsHTML { 400 continue 401 } 402 403 o := newOutputFormat(p, f) 404 plink := o.Permalink() 405 406 for _, a := range p.Aliases { 407 if f.Path != "" { 408 // Make sure AMP and similar doesn't clash with regular aliases. 409 a = path.Join(a, f.Path) 410 } 411 412 lang := p.Lang() 413 414 if s.owner.multihost && !strings.HasPrefix(a, "/"+lang) { 415 // These need to be in its language root. 416 a = path.Join(lang, a) 417 } 418 419 if err := s.writeDestAlias(a, plink, p); err != nil { 420 return err 421 } 422 } 423 } 424 } 425 426 if s.owner.multilingual.enabled() && !s.owner.IsMultihost() { 427 mainLang := s.owner.multilingual.DefaultLang 428 if s.Info.defaultContentLanguageInSubdir { 429 mainLangURL := s.PathSpec.AbsURL(mainLang.Lang, false) 430 s.Log.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL) 431 if err := s.publishDestAlias(true, "/", mainLangURL, nil); err != nil { 432 return err 433 } 434 } else { 435 mainLangURL := s.PathSpec.AbsURL("", false) 436 s.Log.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL) 437 if err := s.publishDestAlias(true, mainLang.Lang, mainLangURL, nil); err != nil { 438 return err 439 } 440 } 441 } 442 443 return nil 444 }