mirror of
				https://github.com/gohugoio/hugo.git
				synced 2024-05-11 05:54:58 +00:00 
			
		
		
		
	The main item in this commit is showing of errors with a file context when running `hugo server`. This can be turned off: `hugo server --disableBrowserError` (can also be set in `config.toml`). But to get there, the error handling in Hugo needed a revision. There are some items left TODO for commits soon to follow, most notable errors in content and config files. Fixes #5284 Fixes #5290 See #5325 See #5324
		
			
				
	
	
		
			450 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			450 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2016 The Hugo Authors. All rights reserved.
 | |
| //
 | |
| // Licensed under the Apache License, Version 2.0 (the "License");
 | |
| // you may not use this file except in compliance with the License.
 | |
| // You may obtain a copy of the License at
 | |
| // http://www.apache.org/licenses/LICENSE-2.0
 | |
| //
 | |
| // Unless required by applicable law or agreed to in writing, software
 | |
| // distributed under the License is distributed on an "AS IS" BASIS,
 | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| // See the License for the specific language governing permissions and
 | |
| // limitations under the License.
 | |
| 
 | |
| package hugolib
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"path"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 
 | |
| 	"github.com/pkg/errors"
 | |
| 
 | |
| 	"github.com/gohugoio/hugo/output"
 | |
| )
 | |
| 
 | |
| // renderPages renders pages each corresponding to a markdown file.
 | |
| // TODO(bep np doc
 | |
| func (s *Site) renderPages(cfg *BuildCfg) error {
 | |
| 
 | |
| 	results := make(chan error)
 | |
| 	pages := make(chan *Page)
 | |
| 	errs := make(chan error)
 | |
| 
 | |
| 	go s.errorCollator(results, errs)
 | |
| 
 | |
| 	numWorkers := getGoMaxProcs() * 4
 | |
| 
 | |
| 	wg := &sync.WaitGroup{}
 | |
| 
 | |
| 	for i := 0; i < numWorkers; i++ {
 | |
| 		wg.Add(1)
 | |
| 		go pageRenderer(s, pages, results, wg)
 | |
| 	}
 | |
| 
 | |
| 	if len(s.headlessPages) > 0 {
 | |
| 		wg.Add(1)
 | |
| 		go headlessPagesPublisher(s, wg)
 | |
| 	}
 | |
| 
 | |
| 	for _, page := range s.Pages {
 | |
| 		if cfg.shouldRender(page) {
 | |
| 			pages <- page
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	close(pages)
 | |
| 
 | |
| 	wg.Wait()
 | |
| 
 | |
| 	close(results)
 | |
| 
 | |
| 	err := <-errs
 | |
| 	if err != nil {
 | |
| 		return errors.Wrap(err, "failed to render pages")
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func headlessPagesPublisher(s *Site, wg *sync.WaitGroup) {
 | |
| 	defer wg.Done()
 | |
| 	for _, page := range s.headlessPages {
 | |
| 		outFormat := page.outputFormats[0] // There is only one
 | |
| 		if outFormat.Name != s.rc.Format.Name {
 | |
| 			// Avoid double work.
 | |
| 			continue
 | |
| 		}
 | |
| 		pageOutput, err := newPageOutput(page, false, false, outFormat)
 | |
| 		if err == nil {
 | |
| 			page.mainPageOutput = pageOutput
 | |
| 			err = pageOutput.renderResources()
 | |
| 		}
 | |
| 
 | |
| 		if err != nil {
 | |
| 			s.Log.ERROR.Printf("Failed to render resources for headless page %q: %s", page, err)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func pageRenderer(s *Site, pages <-chan *Page, results chan<- error, wg *sync.WaitGroup) {
 | |
| 	defer wg.Done()
 | |
| 
 | |
| 	for page := range pages {
 | |
| 
 | |
| 		for i, outFormat := range page.outputFormats {
 | |
| 
 | |
| 			if outFormat.Name != page.s.rc.Format.Name {
 | |
| 				// Will be rendered  ... later.
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			var (
 | |
| 				pageOutput *PageOutput
 | |
| 				err        error
 | |
| 			)
 | |
| 
 | |
| 			if i == 0 {
 | |
| 				pageOutput = page.mainPageOutput
 | |
| 			} else {
 | |
| 				pageOutput, err = page.mainPageOutput.copyWithFormat(outFormat, true)
 | |
| 			}
 | |
| 
 | |
| 			if err != nil {
 | |
| 				s.Log.ERROR.Printf("Failed to create output page for type %q for page %q: %s", outFormat.Name, page, err)
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			if pageOutput == nil {
 | |
| 				panic("no pageOutput")
 | |
| 			}
 | |
| 
 | |
| 			// We only need to re-publish the resources if the output format is different
 | |
| 			// from all of the previous (e.g. the "amp" use case).
 | |
| 			shouldRender := i == 0
 | |
| 			if i > 0 {
 | |
| 				for j := i; j >= 0; j-- {
 | |
| 					if outFormat.Path != page.outputFormats[j].Path {
 | |
| 						shouldRender = true
 | |
| 					} else {
 | |
| 						shouldRender = false
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			if shouldRender {
 | |
| 				if err := pageOutput.renderResources(); err != nil {
 | |
| 					// TODO(bep) 2errors
 | |
| 					s.Log.ERROR.Printf("Failed to render resources for page %q: %s", page, err)
 | |
| 					continue
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			var layouts []string
 | |
| 
 | |
| 			if page.selfLayout != "" {
 | |
| 				layouts = []string{page.selfLayout}
 | |
| 			} else {
 | |
| 				layouts, err = s.layouts(pageOutput)
 | |
| 				if err != nil {
 | |
| 					s.Log.ERROR.Printf("Failed to resolve layout output %q for page %q: %s", outFormat.Name, page, err)
 | |
| 					continue
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			switch pageOutput.outputFormat.Name {
 | |
| 
 | |
| 			case "RSS":
 | |
| 				if err := s.renderRSS(pageOutput); err != nil {
 | |
| 					results <- err
 | |
| 				}
 | |
| 			default:
 | |
| 				targetPath, err := pageOutput.targetPath()
 | |
| 				if err != nil {
 | |
| 					s.Log.ERROR.Printf("Failed to create target path for output %q for page %q: %s", outFormat.Name, page, err)
 | |
| 					continue
 | |
| 				}
 | |
| 
 | |
| 				s.Log.DEBUG.Printf("Render %s to %q with layouts %q", pageOutput.Kind, targetPath, layouts)
 | |
| 
 | |
| 				if err := s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "page "+pageOutput.FullFilePath(), targetPath, pageOutput, layouts...); err != nil {
 | |
| 					results <- err
 | |
| 				}
 | |
| 
 | |
| 				// Only render paginators for the main output format
 | |
| 				if i == 0 && pageOutput.IsNode() {
 | |
| 					if err := s.renderPaginator(pageOutput); err != nil {
 | |
| 						results <- err
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // renderPaginator must be run after the owning Page has been rendered.
 | |
| func (s *Site) renderPaginator(p *PageOutput) error {
 | |
| 	if p.paginator != nil {
 | |
| 		s.Log.DEBUG.Printf("Render paginator for page %q", p.Path())
 | |
| 		paginatePath := s.Cfg.GetString("paginatePath")
 | |
| 
 | |
| 		// write alias for page 1
 | |
| 		addend := fmt.Sprintf("/%s/%d", paginatePath, 1)
 | |
| 		target, err := p.createTargetPath(p.outputFormat, false, addend)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		// TODO(bep) do better
 | |
| 		link := newOutputFormat(p.Page, p.outputFormat).Permalink()
 | |
| 		if err := s.writeDestAlias(target, link, p.outputFormat, nil); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		pagers := p.paginator.Pagers()
 | |
| 
 | |
| 		for i, pager := range pagers {
 | |
| 			if i == 0 {
 | |
| 				// already created
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			pagerNode, err := p.copy()
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 
 | |
| 			pagerNode.origOnCopy = p.Page
 | |
| 
 | |
| 			pagerNode.paginator = pager
 | |
| 			if pager.TotalPages() > 0 {
 | |
| 				first, _ := pager.page(0)
 | |
| 				pagerNode.Date = first.Date
 | |
| 				pagerNode.Lastmod = first.Lastmod
 | |
| 			}
 | |
| 
 | |
| 			pageNumber := i + 1
 | |
| 			addend := fmt.Sprintf("/%s/%d", paginatePath, pageNumber)
 | |
| 			targetPath, _ := p.targetPath(addend)
 | |
| 			layouts, err := p.layouts()
 | |
| 
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 
 | |
| 			if err := s.renderAndWritePage(
 | |
| 				&s.PathSpec.ProcessingStats.PaginatorPages,
 | |
| 				pagerNode.title,
 | |
| 				targetPath, pagerNode, layouts...); err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (s *Site) renderRSS(p *PageOutput) error {
 | |
| 
 | |
| 	if !s.isEnabled(kindRSS) {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	limit := s.Cfg.GetInt("rssLimit")
 | |
| 	if limit >= 0 && len(p.Pages) > limit {
 | |
| 		p.Pages = p.Pages[:limit]
 | |
| 		p.data["Pages"] = p.Pages
 | |
| 	}
 | |
| 
 | |
| 	layouts, err := s.layoutHandler.For(
 | |
| 		p.layoutDescriptor,
 | |
| 		p.outputFormat)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	targetPath, err := p.targetPath()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	return s.renderAndWriteXML(&s.PathSpec.ProcessingStats.Pages, p.title,
 | |
| 		targetPath, p, layouts...)
 | |
| }
 | |
| 
 | |
| func (s *Site) render404() error {
 | |
| 	if !s.isEnabled(kind404) {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	p := s.newNodePage(kind404)
 | |
| 
 | |
| 	p.title = "404 Page not found"
 | |
| 	p.data["Pages"] = s.Pages
 | |
| 	p.Pages = s.Pages
 | |
| 	p.URLPath.URL = "404.html"
 | |
| 
 | |
| 	if err := p.initTargetPathDescriptor(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	nfLayouts := []string{"404.html"}
 | |
| 
 | |
| 	htmlOut := output.HTMLFormat
 | |
| 	htmlOut.BaseName = "404"
 | |
| 
 | |
| 	pageOutput, err := newPageOutput(p, false, false, htmlOut)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	targetPath, err := pageOutput.targetPath()
 | |
| 	if err != nil {
 | |
| 		s.Log.ERROR.Printf("Failed to create target path for page %q: %s", p, err)
 | |
| 	}
 | |
| 
 | |
| 	return s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "404 page", targetPath, pageOutput, s.appendThemeTemplates(nfLayouts)...)
 | |
| }
 | |
| 
 | |
| func (s *Site) renderSitemap() error {
 | |
| 	if !s.isEnabled(kindSitemap) {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	sitemapDefault := parseSitemap(s.Cfg.GetStringMap("sitemap"))
 | |
| 
 | |
| 	n := s.newNodePage(kindSitemap)
 | |
| 
 | |
| 	// Include all pages (regular, home page, taxonomies etc.)
 | |
| 	pages := s.Pages
 | |
| 
 | |
| 	page := s.newNodePage(kindSitemap)
 | |
| 	page.URLPath.URL = ""
 | |
| 	if err := page.initTargetPathDescriptor(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	page.Sitemap.ChangeFreq = sitemapDefault.ChangeFreq
 | |
| 	page.Sitemap.Priority = sitemapDefault.Priority
 | |
| 	page.Sitemap.Filename = sitemapDefault.Filename
 | |
| 
 | |
| 	n.data["Pages"] = pages
 | |
| 	n.Pages = pages
 | |
| 
 | |
| 	// TODO(bep) we have several of these
 | |
| 	if err := page.initTargetPathDescriptor(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	// TODO(bep) this should be done somewhere else
 | |
| 	for _, page := range pages {
 | |
| 		if page.Sitemap.ChangeFreq == "" {
 | |
| 			page.Sitemap.ChangeFreq = sitemapDefault.ChangeFreq
 | |
| 		}
 | |
| 
 | |
| 		if page.Sitemap.Priority == -1 {
 | |
| 			page.Sitemap.Priority = sitemapDefault.Priority
 | |
| 		}
 | |
| 
 | |
| 		if page.Sitemap.Filename == "" {
 | |
| 			page.Sitemap.Filename = sitemapDefault.Filename
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	smLayouts := []string{"sitemap.xml", "_default/sitemap.xml", "_internal/_default/sitemap.xml"}
 | |
| 	addLanguagePrefix := n.Site.IsMultiLingual()
 | |
| 
 | |
| 	return s.renderAndWriteXML(&s.PathSpec.ProcessingStats.Sitemaps, "sitemap",
 | |
| 		n.addLangPathPrefixIfFlagSet(page.Sitemap.Filename, addLanguagePrefix), n, s.appendThemeTemplates(smLayouts)...)
 | |
| }
 | |
| 
 | |
| func (s *Site) renderRobotsTXT() error {
 | |
| 	if !s.isEnabled(kindRobotsTXT) {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	if !s.Cfg.GetBool("enableRobotsTXT") {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	p := s.newNodePage(kindRobotsTXT)
 | |
| 	if err := p.initTargetPathDescriptor(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	p.data["Pages"] = s.Pages
 | |
| 	p.Pages = s.Pages
 | |
| 
 | |
| 	rLayouts := []string{"robots.txt", "_default/robots.txt", "_internal/_default/robots.txt"}
 | |
| 
 | |
| 	pageOutput, err := newPageOutput(p, false, false, output.RobotsTxtFormat)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	targetPath, err := pageOutput.targetPath()
 | |
| 	if err != nil {
 | |
| 		s.Log.ERROR.Printf("Failed to create target path for page %q: %s", p, err)
 | |
| 	}
 | |
| 
 | |
| 	return s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "Robots Txt", targetPath, pageOutput, s.appendThemeTemplates(rLayouts)...)
 | |
| 
 | |
| }
 | |
| 
 | |
| // renderAliases renders shell pages that simply have a redirect in the header.
 | |
| func (s *Site) renderAliases() error {
 | |
| 	for _, p := range s.Pages {
 | |
| 		if len(p.Aliases) == 0 {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		for _, f := range p.outputFormats {
 | |
| 			if !f.IsHTML {
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			o := newOutputFormat(p, f)
 | |
| 			plink := o.Permalink()
 | |
| 
 | |
| 			for _, a := range p.Aliases {
 | |
| 				if f.Path != "" {
 | |
| 					// Make sure AMP and similar doesn't clash with regular aliases.
 | |
| 					a = path.Join(a, f.Path)
 | |
| 				}
 | |
| 
 | |
| 				lang := p.Lang()
 | |
| 
 | |
| 				if s.owner.multihost && !strings.HasPrefix(a, "/"+lang) {
 | |
| 					// These need to be in its language root.
 | |
| 					a = path.Join(lang, a)
 | |
| 				}
 | |
| 
 | |
| 				if err := s.writeDestAlias(a, plink, f, p); err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if s.owner.multilingual.enabled() && !s.owner.IsMultihost() {
 | |
| 		html, found := s.outputFormatsConfig.GetByName("HTML")
 | |
| 		if found {
 | |
| 			mainLang := s.owner.multilingual.DefaultLang
 | |
| 			if s.Info.defaultContentLanguageInSubdir {
 | |
| 				mainLangURL := s.PathSpec.AbsURL(mainLang.Lang, false)
 | |
| 				s.Log.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL)
 | |
| 				if err := s.publishDestAlias(true, "/", mainLangURL, html, nil); err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 			} else {
 | |
| 				mainLangURL := s.PathSpec.AbsURL("", false)
 | |
| 				s.Log.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL)
 | |
| 				if err := s.publishDestAlias(true, mainLang.Lang, mainLangURL, html, nil); err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 |