github.com/kristoff-it/hugo@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  }