summaryrefslogtreecommitdiffhomepage
path: root/hugolib/site_render.go
diff options
context:
space:
mode:
Diffstat (limited to 'hugolib/site_render.go')
-rw-r--r--hugolib/site_render.go400
1 files changed, 400 insertions, 0 deletions
diff --git a/hugolib/site_render.go b/hugolib/site_render.go
new file mode 100644
index 000000000..a24946cf3
--- /dev/null
+++ b/hugolib/site_render.go
@@ -0,0 +1,400 @@
+// 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"
+ "sync"
+
+ "github.com/gohugoio/hugo/helpers"
+
+ "github.com/gohugoio/hugo/output"
+
+ bp "github.com/gohugoio/hugo/bufferpool"
+)
+
+// renderPages renders pages each corresponding to a markdown file.
+// TODO(bep np doc
+func (s *Site) renderPages() error {
+
+ results := make(chan error)
+ pages := make(chan *Page)
+ errs := make(chan error)
+
+ go errorCollator(results, errs)
+
+ numWorkers := getGoMaxProcs() * 4
+
+ wg := &sync.WaitGroup{}
+
+ for i := 0; i < numWorkers; i++ {
+ wg.Add(1)
+ go pageRenderer(s, pages, results, wg)
+ }
+
+ for _, page := range s.Pages {
+ pages <- page
+ }
+
+ close(pages)
+
+ wg.Wait()
+
+ close(results)
+
+ err := <-errs
+ if err != nil {
+ return fmt.Errorf("Error(s) rendering pages: %s", err)
+ }
+ return nil
+}
+
+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 {
+
+ var (
+ pageOutput *PageOutput
+ err error
+ )
+
+ if i == 0 {
+ pageOutput, err = newPageOutput(page, false, outFormat)
+ page.mainPageOutput = pageOutput
+ }
+
+ if outFormat != page.s.rc.Format {
+ // Will be rendered ... later.
+ continue
+ }
+
+ if pageOutput == nil {
+ pageOutput, err = page.mainPageOutput.copyWithFormat(outFormat)
+ }
+
+ if err != nil {
+ s.Log.ERROR.Printf("Failed to create output page for type %q for page %q: %s", outFormat.Name, 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("page "+pageOutput.FullFilePath(), targetPath, pageOutput, layouts...); err != nil {
+ results <- err
+ }
+
+ if 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, addend)
+ if err != nil {
+ return err
+ }
+
+ // TODO(bep) do better
+ link := newOutputFormat(p.Page, p.outputFormat).Permalink()
+ if err := s.writeDestAlias(target, link, 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(
+ pagerNode.Title,
+ targetPath, pagerNode, layouts...); err != nil {
+ return err
+ }
+
+ }
+ }
+ return nil
+}
+
+func (s *Site) renderRSS(p *PageOutput) error {
+
+ if !s.isEnabled(kindRSS) {
+ return nil
+ }
+
+ if s.Cfg.GetBool("disableRSS") {
+ return nil
+ }
+
+ p.Kind = kindRSS
+
+ 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(p.Title,
+ targetPath, p, layouts...)
+}
+
+func (s *Site) render404() error {
+ if !s.isEnabled(kind404) {
+ return nil
+ }
+
+ if s.Cfg.GetBool("disable404") {
+ return nil
+ }
+
+ if s.owner.multilingual.enabled() && (s.Language.Lang != s.owner.multilingual.DefaultLang.Lang) {
+ 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"}
+
+ pageOutput, err := newPageOutput(p, false, output.HTMLFormat)
+ if err != nil {
+ return err
+ }
+
+ return s.renderAndWritePage("404 page", "404.html", pageOutput, s.appendThemeTemplates(nfLayouts)...)
+
+}
+
+func (s *Site) renderSitemap() error {
+ if !s.isEnabled(kindSitemap) {
+ return nil
+ }
+
+ if s.Cfg.GetBool("disableSitemap") {
+ 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("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
+ }
+
+ n := s.newNodePage(kindRobotsTXT)
+ if err := n.initTargetPathDescriptor(); err != nil {
+ return err
+ }
+ n.Data["Pages"] = s.Pages
+ n.Pages = s.Pages
+
+ rLayouts := []string{"robots.txt", "_default/robots.txt", "_internal/_default/robots.txt"}
+ outBuffer := bp.GetBuffer()
+ defer bp.PutBuffer(outBuffer)
+ if err := s.renderForLayouts("robots", n, outBuffer, s.appendThemeTemplates(rLayouts)...); err != nil {
+ helpers.DistinctWarnLog.Println(err)
+ return nil
+ }
+
+ if outBuffer.Len() == 0 {
+ return nil
+ }
+
+ return s.publish("robots.txt", outBuffer)
+}
+
+// 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)
+ }
+
+ if err := s.writeDestAlias(a, plink, p); err != nil {
+ return err
+ }
+ }
+ }
+ }
+
+ if s.owner.multilingual.enabled() {
+ 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, 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, nil); err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
+}