github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/hugolib/site_render.go (about) 1 // Copyright 2019 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/tpl" 23 24 "github.com/gohugoio/hugo/config" 25 26 "github.com/gohugoio/hugo/output" 27 "github.com/pkg/errors" 28 29 "github.com/gohugoio/hugo/resources/page" 30 "github.com/gohugoio/hugo/resources/page/pagemeta" 31 ) 32 33 type siteRenderContext struct { 34 cfg *BuildCfg 35 36 // Zero based index for all output formats combined. 37 sitesOutIdx int 38 39 // Zero based index of the output formats configured within a Site. 40 // Note that these outputs are sorted. 41 outIdx int 42 43 multihost bool 44 } 45 46 // Whether to render 404.html, robotsTXT.txt which usually is rendered 47 // once only in the site root. 48 func (s siteRenderContext) renderSingletonPages() bool { 49 if s.multihost { 50 // 1 per site 51 return s.outIdx == 0 52 } 53 54 // 1 for all sites 55 return s.sitesOutIdx == 0 56 } 57 58 // renderPages renders pages each corresponding to a markdown file. 59 // TODO(bep np doc 60 func (s *Site) renderPages(ctx *siteRenderContext) error { 61 numWorkers := config.GetNumWorkerMultiplier() 62 63 results := make(chan error) 64 pages := make(chan *pageState, numWorkers) // buffered for performance 65 errs := make(chan error) 66 67 go s.errorCollator(results, errs) 68 69 wg := &sync.WaitGroup{} 70 71 for i := 0; i < numWorkers; i++ { 72 wg.Add(1) 73 go pageRenderer(ctx, s, pages, results, wg) 74 } 75 76 cfg := ctx.cfg 77 78 s.pageMap.pageTrees.Walk(func(ss string, n *contentNode) bool { 79 if cfg.shouldRender(n.p) { 80 select { 81 case <-s.h.Done(): 82 return true 83 default: 84 pages <- n.p 85 } 86 } 87 return false 88 }) 89 90 close(pages) 91 92 wg.Wait() 93 94 close(results) 95 96 err := <-errs 97 if err != nil { 98 return errors.Wrap(err, "failed to render pages") 99 } 100 return nil 101 } 102 103 func pageRenderer( 104 ctx *siteRenderContext, 105 s *Site, 106 pages <-chan *pageState, 107 results chan<- error, 108 wg *sync.WaitGroup) { 109 defer wg.Done() 110 111 for p := range pages { 112 if p.m.buildConfig.PublishResources { 113 if err := p.renderResources(); err != nil { 114 s.SendError(p.errorf(err, "failed to render page resources")) 115 continue 116 } 117 } 118 119 if !p.render { 120 // Nothing more to do for this page. 121 continue 122 } 123 124 templ, found, err := p.resolveTemplate() 125 if err != nil { 126 s.SendError(p.errorf(err, "failed to resolve template")) 127 continue 128 } 129 130 if !found { 131 s.logMissingLayout("", p.Layout(), p.Kind(), p.f.Name) 132 continue 133 } 134 135 targetPath := p.targetPaths().TargetFilename 136 137 if err := s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "page "+p.Title(), targetPath, p, templ); err != nil { 138 results <- err 139 } 140 141 if p.paginator != nil && p.paginator.current != nil { 142 if err := s.renderPaginator(p, templ); err != nil { 143 results <- err 144 } 145 } 146 } 147 } 148 149 func (s *Site) logMissingLayout(name, layout, kind, outputFormat string) { 150 log := s.Log.Warn() 151 if name != "" && infoOnMissingLayout[name] { 152 log = s.Log.Info() 153 } 154 155 errMsg := "You should create a template file which matches Hugo Layouts Lookup Rules for this combination." 156 var args []interface{} 157 msg := "found no layout file for" 158 if outputFormat != "" { 159 msg += " %q" 160 args = append(args, outputFormat) 161 } 162 163 if layout != "" { 164 msg += " for layout %q" 165 args = append(args, layout) 166 } 167 168 if kind != "" { 169 msg += " for kind %q" 170 args = append(args, kind) 171 } 172 173 if name != "" { 174 msg += " for %q" 175 args = append(args, name) 176 } 177 178 msg += ": " + errMsg 179 180 log.Printf(msg, args...) 181 } 182 183 // renderPaginator must be run after the owning Page has been rendered. 184 func (s *Site) renderPaginator(p *pageState, templ tpl.Template) error { 185 paginatePath := s.Cfg.GetString("paginatePath") 186 187 d := p.targetPathDescriptor 188 f := p.s.rc.Format 189 d.Type = f 190 191 if p.paginator.current == nil || p.paginator.current != p.paginator.current.First() { 192 panic(fmt.Sprintf("invalid paginator state for %q", p.pathOrTitle())) 193 } 194 195 if f.IsHTML { 196 // Write alias for page 1 197 d.Addends = fmt.Sprintf("/%s/%d", paginatePath, 1) 198 targetPaths := page.CreateTargetPaths(d) 199 200 if err := s.writeDestAlias(targetPaths.TargetFilename, p.Permalink(), f, nil); err != nil { 201 return err 202 } 203 } 204 205 // Render pages for the rest 206 for current := p.paginator.current.Next(); current != nil; current = current.Next() { 207 208 p.paginator.current = current 209 d.Addends = fmt.Sprintf("/%s/%d", paginatePath, current.PageNumber()) 210 targetPaths := page.CreateTargetPaths(d) 211 212 if err := s.renderAndWritePage( 213 &s.PathSpec.ProcessingStats.PaginatorPages, 214 p.Title(), 215 targetPaths.TargetFilename, p, templ); err != nil { 216 return err 217 } 218 219 } 220 221 return nil 222 } 223 224 func (s *Site) render404() error { 225 p, err := newPageStandalone(&pageMeta{ 226 s: s, 227 kind: kind404, 228 urlPaths: pagemeta.URLPath{ 229 URL: "404.html", 230 }, 231 }, 232 output.HTMLFormat, 233 ) 234 if err != nil { 235 return err 236 } 237 238 if !p.render { 239 return nil 240 } 241 242 var d output.LayoutDescriptor 243 d.Kind = kind404 244 245 templ, found, err := s.Tmpl().LookupLayout(d, output.HTMLFormat) 246 if err != nil { 247 return err 248 } 249 if !found { 250 return nil 251 } 252 253 targetPath := p.targetPaths().TargetFilename 254 255 if targetPath == "" { 256 return errors.New("failed to create targetPath for 404 page") 257 } 258 259 return s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "404 page", targetPath, p, templ) 260 } 261 262 func (s *Site) renderSitemap() error { 263 p, err := newPageStandalone(&pageMeta{ 264 s: s, 265 kind: kindSitemap, 266 urlPaths: pagemeta.URLPath{ 267 URL: s.siteCfg.sitemap.Filename, 268 }, 269 }, 270 output.HTMLFormat, 271 ) 272 if err != nil { 273 return err 274 } 275 276 if !p.render { 277 return nil 278 } 279 280 targetPath := p.targetPaths().TargetFilename 281 282 if targetPath == "" { 283 return errors.New("failed to create targetPath for sitemap") 284 } 285 286 templ := s.lookupLayouts("sitemap.xml", "_default/sitemap.xml", "_internal/_default/sitemap.xml") 287 288 return s.renderAndWriteXML(&s.PathSpec.ProcessingStats.Sitemaps, "sitemap", targetPath, p, templ) 289 } 290 291 func (s *Site) renderRobotsTXT() error { 292 if !s.Cfg.GetBool("enableRobotsTXT") { 293 return nil 294 } 295 296 p, err := newPageStandalone(&pageMeta{ 297 s: s, 298 kind: kindRobotsTXT, 299 urlPaths: pagemeta.URLPath{ 300 URL: "robots.txt", 301 }, 302 }, 303 output.RobotsTxtFormat) 304 if err != nil { 305 return err 306 } 307 308 if !p.render { 309 return nil 310 } 311 312 templ := s.lookupLayouts("robots.txt", "_default/robots.txt", "_internal/_default/robots.txt") 313 314 return s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "Robots Txt", p.targetPaths().TargetFilename, p, templ) 315 } 316 317 // renderAliases renders shell pages that simply have a redirect in the header. 318 func (s *Site) renderAliases() error { 319 var err error 320 s.pageMap.pageTrees.WalkLinkable(func(ss string, n *contentNode) bool { 321 p := n.p 322 if len(p.Aliases()) == 0 { 323 return false 324 } 325 326 pathSeen := make(map[string]bool) 327 328 for _, of := range p.OutputFormats() { 329 if !of.Format.IsHTML { 330 continue 331 } 332 333 f := of.Format 334 335 if pathSeen[f.Path] { 336 continue 337 } 338 pathSeen[f.Path] = true 339 340 plink := of.Permalink() 341 342 for _, a := range p.Aliases() { 343 isRelative := !strings.HasPrefix(a, "/") 344 345 if isRelative { 346 // Make alias relative, where "." will be on the 347 // same directory level as the current page. 348 basePath := path.Join(p.targetPaths().SubResourceBaseLink, "..") 349 a = path.Join(basePath, a) 350 351 } else { 352 // Make sure AMP and similar doesn't clash with regular aliases. 353 a = path.Join(f.Path, a) 354 } 355 356 if s.UglyURLs && !strings.HasSuffix(a, ".html") { 357 a += ".html" 358 } 359 360 lang := p.Language().Lang 361 362 if s.h.multihost && !strings.HasPrefix(a, "/"+lang) { 363 // These need to be in its language root. 364 a = path.Join(lang, a) 365 } 366 367 err = s.writeDestAlias(a, plink, f, p) 368 if err != nil { 369 return true 370 } 371 } 372 } 373 return false 374 }) 375 376 return err 377 } 378 379 // renderMainLanguageRedirect creates a redirect to the main language home, 380 // depending on if it lives in sub folder (e.g. /en) or not. 381 func (s *Site) renderMainLanguageRedirect() error { 382 if !s.h.multilingual.enabled() || s.h.IsMultihost() { 383 // No need for a redirect 384 return nil 385 } 386 387 html, found := s.outputFormatsConfig.GetByName("HTML") 388 if found { 389 mainLang := s.h.multilingual.DefaultLang 390 if s.Info.defaultContentLanguageInSubdir { 391 mainLangURL := s.PathSpec.AbsURL(mainLang.Lang+"/", false) 392 s.Log.Debugf("Write redirect to main language %s: %s", mainLang, mainLangURL) 393 if err := s.publishDestAlias(true, "/", mainLangURL, html, nil); err != nil { 394 return err 395 } 396 } else { 397 mainLangURL := s.PathSpec.AbsURL("", false) 398 s.Log.Debugf("Write redirect to main language %s: %s", mainLang, mainLangURL) 399 if err := s.publishDestAlias(true, mainLang.Lang, mainLangURL, html, nil); err != nil { 400 return err 401 } 402 } 403 } 404 405 return nil 406 }