github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/hugolib/site_render.go (about)

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