github.com/olliephillips/hugo@v0.42.2/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  }