hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

site_render.go (9082B)

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