github.com/schumacherfm/hugo@v0.47.1/hugolib/page.go (about)

     1  // Copyright 2018 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  	"bytes"
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"reflect"
    22  	"unicode"
    23  
    24  	"github.com/gohugoio/hugo/media"
    25  
    26  	"github.com/gohugoio/hugo/common/maps"
    27  
    28  	"github.com/gohugoio/hugo/langs"
    29  
    30  	"github.com/gohugoio/hugo/related"
    31  
    32  	"github.com/bep/gitmap"
    33  
    34  	"github.com/gohugoio/hugo/helpers"
    35  	"github.com/gohugoio/hugo/hugolib/pagemeta"
    36  	"github.com/gohugoio/hugo/resource"
    37  
    38  	"github.com/gohugoio/hugo/output"
    39  	"github.com/gohugoio/hugo/parser"
    40  	"github.com/mitchellh/mapstructure"
    41  
    42  	"html/template"
    43  	"io"
    44  	"path"
    45  	"path/filepath"
    46  	"regexp"
    47  	"runtime"
    48  	"strings"
    49  	"sync"
    50  	"time"
    51  	"unicode/utf8"
    52  
    53  	bp "github.com/gohugoio/hugo/bufferpool"
    54  	"github.com/gohugoio/hugo/compare"
    55  	"github.com/gohugoio/hugo/source"
    56  	"github.com/spf13/cast"
    57  )
    58  
    59  var (
    60  	cjk = regexp.MustCompile(`\p{Han}|\p{Hangul}|\p{Hiragana}|\p{Katakana}`)
    61  
    62  	// This is all the kinds we can expect to find in .Site.Pages.
    63  	allKindsInPages = []string{KindPage, KindHome, KindSection, KindTaxonomy, KindTaxonomyTerm}
    64  
    65  	allKinds = append(allKindsInPages, []string{kindRSS, kindSitemap, kindRobotsTXT, kind404}...)
    66  
    67  	// Assert that it implements the Eqer interface.
    68  	_ compare.Eqer = (*Page)(nil)
    69  	_ compare.Eqer = (*PageOutput)(nil)
    70  
    71  	// Assert that it implements the interface needed for related searches.
    72  	_ related.Document = (*Page)(nil)
    73  )
    74  
    75  const (
    76  	KindPage = "page"
    77  
    78  	// The rest are node types; home page, sections etc.
    79  
    80  	KindHome         = "home"
    81  	KindSection      = "section"
    82  	KindTaxonomy     = "taxonomy"
    83  	KindTaxonomyTerm = "taxonomyTerm"
    84  
    85  	// Temporary state.
    86  	kindUnknown = "unknown"
    87  
    88  	// The following are (currently) temporary nodes,
    89  	// i.e. nodes we create just to render in isolation.
    90  	kindRSS       = "RSS"
    91  	kindSitemap   = "sitemap"
    92  	kindRobotsTXT = "robotsTXT"
    93  	kind404       = "404"
    94  
    95  	pageResourceType = "page"
    96  )
    97  
    98  type Page struct {
    99  	*pageInit
   100  	*pageContentInit
   101  
   102  	// Kind is the discriminator that identifies the different page types
   103  	// in the different page collections. This can, as an example, be used
   104  	// to to filter regular pages, find sections etc.
   105  	// Kind will, for the pages available to the templates, be one of:
   106  	// page, home, section, taxonomy and taxonomyTerm.
   107  	// It is of string type to make it easy to reason about in
   108  	// the templates.
   109  	Kind string
   110  
   111  	// Since Hugo 0.18 we got rid of the Node type. So now all pages are ...
   112  	// pages (regular pages, home page, sections etc.).
   113  	// Sections etc. will have child pages. These were earlier placed in .Data.Pages,
   114  	// but can now be more intuitively also be fetched directly from .Pages.
   115  	// This collection will be nil for regular pages.
   116  	Pages Pages
   117  
   118  	// Since Hugo 0.32, a Page can have resources such as images and CSS associated
   119  	// with itself. The resource will typically be placed relative to the Page,
   120  	// but templates should use the links (Permalink and RelPermalink)
   121  	// provided by the Resource object.
   122  	Resources resource.Resources
   123  
   124  	// This is the raw front matter metadata that is going to be assigned to
   125  	// the Resources above.
   126  	resourcesMetadata []map[string]interface{}
   127  
   128  	// translations will contain references to this page in other language
   129  	// if available.
   130  	translations Pages
   131  
   132  	// A key that maps to translation(s) of this page. This value is fetched
   133  	// from the page front matter.
   134  	translationKey string
   135  
   136  	// Params contains configuration defined in the params section of page frontmatter.
   137  	params map[string]interface{}
   138  
   139  	// Content sections
   140  	contentv        template.HTML
   141  	summary         template.HTML
   142  	TableOfContents template.HTML
   143  	// Passed to the shortcodes
   144  	pageWithoutContent *PageWithoutContent
   145  
   146  	Aliases []string
   147  
   148  	Images []Image
   149  	Videos []Video
   150  
   151  	truncated bool
   152  	Draft     bool
   153  	Status    string
   154  
   155  	// PageMeta contains page stats such as word count etc.
   156  	PageMeta
   157  
   158  	// Markup contains the markup type for the content.
   159  	Markup string
   160  
   161  	extension   string
   162  	contentType string
   163  	renderable  bool
   164  
   165  	Layout string
   166  
   167  	// For npn-renderable pages (see IsRenderable), the content itself
   168  	// is used as template and the template name is stored here.
   169  	selfLayout string
   170  
   171  	linkTitle string
   172  
   173  	frontmatter []byte
   174  
   175  	// rawContent is the raw content read from the content file.
   176  	rawContent []byte
   177  
   178  	// workContent is a copy of rawContent that may be mutated during site build.
   179  	workContent []byte
   180  
   181  	// whether the content is in a CJK language.
   182  	isCJKLanguage bool
   183  
   184  	shortcodeState *shortcodeHandler
   185  
   186  	// the content stripped for HTML
   187  	plain      string // TODO should be []byte
   188  	plainWords []string
   189  
   190  	// rendering configuration
   191  	renderingConfig *helpers.BlackFriday
   192  
   193  	// menus
   194  	pageMenus PageMenus
   195  
   196  	Source
   197  
   198  	Position `json:"-"`
   199  
   200  	GitInfo *gitmap.GitInfo
   201  
   202  	// This was added as part of getting the Nodes (taxonomies etc.) to work as
   203  	// Pages in Hugo 0.18.
   204  	// It is deliberately named similar to Section, but not exported (for now).
   205  	// We currently have only one level of section in Hugo, but the page can live
   206  	// any number of levels down the file path.
   207  	// To support taxonomies like /categories/hugo etc. we will need to keep track
   208  	// of that information in a general way.
   209  	// So, sections represents the path to the content, i.e. a content file or a
   210  	// virtual content file in the situations where a taxonomy or a section etc.
   211  	// isn't accomanied by one.
   212  	sections []string
   213  
   214  	// Will only be set for sections and regular pages.
   215  	parent *Page
   216  
   217  	// When we create paginator pages, we create a copy of the original,
   218  	// but keep track of it here.
   219  	origOnCopy *Page
   220  
   221  	// Will only be set for section pages and the home page.
   222  	subSections Pages
   223  
   224  	s *Site
   225  
   226  	// Pulled over from old Node. TODO(bep) reorg and group (embed)
   227  
   228  	Site *SiteInfo `json:"-"`
   229  
   230  	title       string
   231  	Description string
   232  	Keywords    []string
   233  	data        map[string]interface{}
   234  
   235  	pagemeta.PageDates
   236  
   237  	Sitemap Sitemap
   238  	pagemeta.URLPath
   239  	frontMatterURL string
   240  
   241  	permalink    string
   242  	relPermalink string
   243  
   244  	// relative target path without extension and any base path element
   245  	// from the baseURL or the language code.
   246  	// This is used to construct paths in the page resources.
   247  	relTargetPathBase string
   248  	// Is set to a forward slashed path if this is a Page resources living in a folder below its owner.
   249  	resourcePath string
   250  
   251  	// This is enabled if it is a leaf bundle (the "index.md" type) and it is marked as headless in front matter.
   252  	// Being headless means that
   253  	// 1. The page itself is not rendered to disk
   254  	// 2. It is not available in .Site.Pages etc.
   255  	// 3. But you can get it via .Site.GetPage
   256  	headless bool
   257  
   258  	layoutDescriptor output.LayoutDescriptor
   259  
   260  	scratch *maps.Scratch
   261  
   262  	// It would be tempting to use the language set on the Site, but in they way we do
   263  	// multi-site processing, these values may differ during the initial page processing.
   264  	language *langs.Language
   265  
   266  	lang string
   267  
   268  	// When in Fast Render Mode, we only render a sub set of the pages, i.e. the
   269  	// pages the user is working on. There are, however, situations where we need to
   270  	// signal other pages to be rendered.
   271  	forceRender bool
   272  
   273  	// The output formats this page will be rendered to.
   274  	outputFormats output.Formats
   275  
   276  	// This is the PageOutput that represents the first item in outputFormats.
   277  	// Use with care, as there are potential for inifinite loops.
   278  	mainPageOutput *PageOutput
   279  
   280  	targetPathDescriptorPrototype *targetPathDescriptor
   281  }
   282  
   283  func stackTrace(length int) string {
   284  	trace := make([]byte, length)
   285  	runtime.Stack(trace, true)
   286  	return string(trace)
   287  }
   288  
   289  func (p *Page) Data() interface{} {
   290  	return p.data
   291  }
   292  
   293  func (p *Page) initContent() {
   294  
   295  	p.contentInit.Do(func() {
   296  		// This careful dance is here to protect against circular loops in shortcode/content
   297  		// constructs.
   298  		// TODO(bep) context vs the remote shortcodes
   299  		ctx, cancel := context.WithTimeout(context.Background(), p.s.Timeout)
   300  		defer cancel()
   301  		c := make(chan error, 1)
   302  
   303  		go func() {
   304  			var err error
   305  			p.contentInitMu.Lock()
   306  			defer p.contentInitMu.Unlock()
   307  
   308  			err = p.prepareForRender()
   309  			if err != nil {
   310  				p.s.Log.ERROR.Printf("Failed to prepare page %q for render: %s", p.Path(), err)
   311  				return
   312  			}
   313  
   314  			if len(p.summary) == 0 {
   315  				if err = p.setAutoSummary(); err != nil {
   316  					err = fmt.Errorf("Failed to set user auto summary for page %q: %s", p.pathOrTitle(), err)
   317  				}
   318  			}
   319  			c <- err
   320  		}()
   321  
   322  		select {
   323  		case <-ctx.Done():
   324  			p.s.Log.WARN.Printf("WARNING: Timed out creating content for page %q (.Content will be empty). This is most likely a circular shortcode content loop that should be fixed. If this is just a shortcode calling a slow remote service, try to set \"timeout=20000\" (or higher, value is in milliseconds) in config.toml.\n", p.pathOrTitle())
   325  		case err := <-c:
   326  			if err != nil {
   327  				p.s.Log.ERROR.Println(err)
   328  			}
   329  		}
   330  	})
   331  
   332  }
   333  
   334  // This is sent to the shortcodes for this page. Not doing that will create an infinite regress. So,
   335  // shortcodes can access .Page.TableOfContents, but not .Page.Content etc.
   336  func (p *Page) withoutContent() *PageWithoutContent {
   337  	p.pageInit.withoutContentInit.Do(func() {
   338  		p.pageWithoutContent = &PageWithoutContent{Page: p}
   339  	})
   340  	return p.pageWithoutContent
   341  }
   342  
   343  func (p *Page) Content() (interface{}, error) {
   344  	return p.content(), nil
   345  }
   346  
   347  func (p *Page) Truncated() bool {
   348  	p.initContent()
   349  	return p.truncated
   350  }
   351  
   352  func (p *Page) content() template.HTML {
   353  	p.initContent()
   354  	return p.contentv
   355  }
   356  
   357  func (p *Page) Summary() template.HTML {
   358  	p.initContent()
   359  	return p.summary
   360  }
   361  
   362  // Sites is a convenience method to get all the Hugo sites/languages configured.
   363  func (p *Page) Sites() SiteInfos {
   364  	infos := make(SiteInfos, len(p.s.owner.Sites))
   365  	for i, site := range p.s.owner.Sites {
   366  		infos[i] = &site.Info
   367  	}
   368  
   369  	return infos
   370  }
   371  
   372  // SearchKeywords implements the related.Document interface needed for fast page searches.
   373  func (p *Page) SearchKeywords(cfg related.IndexConfig) ([]related.Keyword, error) {
   374  
   375  	v, err := p.Param(cfg.Name)
   376  	if err != nil {
   377  		return nil, err
   378  	}
   379  
   380  	return cfg.ToKeywords(v)
   381  }
   382  
   383  // PubDate is when this page was or will be published.
   384  // NOTE: This is currently used for search only and is not meant to be used
   385  // directly in templates. We need to consolidate the dates in this struct.
   386  // TODO(bep) see https://github.com/gohugoio/hugo/issues/3854
   387  func (p *Page) PubDate() time.Time {
   388  	if !p.PublishDate.IsZero() {
   389  		return p.PublishDate
   390  	}
   391  	return p.Date
   392  }
   393  
   394  func (*Page) ResourceType() string {
   395  	return pageResourceType
   396  }
   397  
   398  func (p *Page) RSSLink() template.URL {
   399  	f, found := p.outputFormats.GetByName(output.RSSFormat.Name)
   400  	if !found {
   401  		return ""
   402  	}
   403  	return template.URL(newOutputFormat(p, f).Permalink())
   404  }
   405  
   406  func (p *Page) createLayoutDescriptor() output.LayoutDescriptor {
   407  	var section string
   408  
   409  	switch p.Kind {
   410  	case KindSection:
   411  		// In Hugo 0.22 we introduce nested sections, but we still only
   412  		// use the first level to pick the correct template. This may change in
   413  		// the future.
   414  		section = p.sections[0]
   415  	case KindTaxonomy, KindTaxonomyTerm:
   416  		section = p.s.taxonomiesPluralSingular[p.sections[0]]
   417  	default:
   418  	}
   419  
   420  	return output.LayoutDescriptor{
   421  		Kind:    p.Kind,
   422  		Type:    p.Type(),
   423  		Lang:    p.Lang(),
   424  		Layout:  p.Layout,
   425  		Section: section,
   426  	}
   427  }
   428  
   429  // pageInit lazy initializes different parts of the page. It is extracted
   430  // into its own type so we can easily create a copy of a given page.
   431  type pageInit struct {
   432  	languageInit        sync.Once
   433  	pageMenusInit       sync.Once
   434  	pageMetaInit        sync.Once
   435  	renderingConfigInit sync.Once
   436  	withoutContentInit  sync.Once
   437  }
   438  
   439  type pageContentInit struct {
   440  	contentInitMu  sync.Mutex
   441  	contentInit    sync.Once
   442  	plainInit      sync.Once
   443  	plainWordsInit sync.Once
   444  }
   445  
   446  func (p *Page) resetContent() {
   447  	p.pageContentInit = &pageContentInit{}
   448  }
   449  
   450  // IsNode returns whether this is an item of one of the list types in Hugo,
   451  // i.e. not a regular content page.
   452  func (p *Page) IsNode() bool {
   453  	return p.Kind != KindPage
   454  }
   455  
   456  // IsHome returns whether this is the home page.
   457  func (p *Page) IsHome() bool {
   458  	return p.Kind == KindHome
   459  }
   460  
   461  // IsSection returns whether this is a section page.
   462  func (p *Page) IsSection() bool {
   463  	return p.Kind == KindSection
   464  }
   465  
   466  // IsPage returns whether this is a regular content page.
   467  func (p *Page) IsPage() bool {
   468  	return p.Kind == KindPage
   469  }
   470  
   471  // BundleType returns the bundle type: "leaf", "branch" or an empty string if it is none.
   472  // See https://gohugo.io/content-management/page-bundles/
   473  func (p *Page) BundleType() string {
   474  	if p.IsNode() {
   475  		return "branch"
   476  	}
   477  
   478  	var source interface{} = p.Source.File
   479  	if fi, ok := source.(*fileInfo); ok {
   480  		switch fi.bundleTp {
   481  		case bundleBranch:
   482  			return "branch"
   483  		case bundleLeaf:
   484  			return "leaf"
   485  		}
   486  	}
   487  
   488  	return ""
   489  }
   490  
   491  func (p *Page) MediaType() media.Type {
   492  	return media.OctetType
   493  }
   494  
   495  type Source struct {
   496  	Frontmatter []byte
   497  	Content     []byte
   498  	source.File
   499  }
   500  type PageMeta struct {
   501  	wordCount      int
   502  	fuzzyWordCount int
   503  	readingTime    int
   504  	Weight         int
   505  }
   506  
   507  type Position struct {
   508  	Prev          *Page
   509  	Next          *Page
   510  	PrevInSection *Page
   511  	NextInSection *Page
   512  }
   513  
   514  type Pages []*Page
   515  
   516  func (ps Pages) String() string {
   517  	return fmt.Sprintf("Pages(%d)", len(ps))
   518  }
   519  
   520  func (ps Pages) findPagePosByFilename(filename string) int {
   521  	for i, x := range ps {
   522  		if x.Source.Filename() == filename {
   523  			return i
   524  		}
   525  	}
   526  	return -1
   527  }
   528  
   529  func (ps Pages) removeFirstIfFound(p *Page) Pages {
   530  	ii := -1
   531  	for i, pp := range ps {
   532  		if pp == p {
   533  			ii = i
   534  			break
   535  		}
   536  	}
   537  
   538  	if ii != -1 {
   539  		ps = append(ps[:ii], ps[ii+1:]...)
   540  	}
   541  	return ps
   542  }
   543  
   544  func (ps Pages) findPagePosByFilnamePrefix(prefix string) int {
   545  	if prefix == "" {
   546  		return -1
   547  	}
   548  
   549  	lenDiff := -1
   550  	currPos := -1
   551  	prefixLen := len(prefix)
   552  
   553  	// Find the closest match
   554  	for i, x := range ps {
   555  		if strings.HasPrefix(x.Source.Filename(), prefix) {
   556  			diff := len(x.Source.Filename()) - prefixLen
   557  			if lenDiff == -1 || diff < lenDiff {
   558  				lenDiff = diff
   559  				currPos = i
   560  			}
   561  		}
   562  	}
   563  	return currPos
   564  }
   565  
   566  // findPagePos Given a page, it will find the position in Pages
   567  // will return -1 if not found
   568  func (ps Pages) findPagePos(page *Page) int {
   569  	for i, x := range ps {
   570  		if x.Source.Filename() == page.Source.Filename() {
   571  			return i
   572  		}
   573  	}
   574  	return -1
   575  }
   576  
   577  func (p *Page) createWorkContentCopy() {
   578  	p.workContent = make([]byte, len(p.rawContent))
   579  	copy(p.workContent, p.rawContent)
   580  }
   581  
   582  func (p *Page) Plain() string {
   583  	p.initContent()
   584  	p.initPlain(true)
   585  	return p.plain
   586  }
   587  
   588  func (p *Page) initPlain(lock bool) {
   589  	p.plainInit.Do(func() {
   590  		if lock {
   591  			p.contentInitMu.Lock()
   592  			defer p.contentInitMu.Unlock()
   593  		}
   594  		p.plain = helpers.StripHTML(string(p.contentv))
   595  	})
   596  }
   597  
   598  func (p *Page) PlainWords() []string {
   599  	p.initContent()
   600  	p.initPlainWords(true)
   601  	return p.plainWords
   602  }
   603  
   604  func (p *Page) initPlainWords(lock bool) {
   605  	p.plainWordsInit.Do(func() {
   606  		if lock {
   607  			p.contentInitMu.Lock()
   608  			defer p.contentInitMu.Unlock()
   609  		}
   610  		p.plainWords = strings.Fields(p.plain)
   611  	})
   612  }
   613  
   614  // Param is a convenience method to do lookups in Page's and Site's Params map,
   615  // in that order.
   616  //
   617  // This method is also implemented on Node and SiteInfo.
   618  func (p *Page) Param(key interface{}) (interface{}, error) {
   619  	keyStr, err := cast.ToStringE(key)
   620  	if err != nil {
   621  		return nil, err
   622  	}
   623  
   624  	keyStr = strings.ToLower(keyStr)
   625  	result, _ := p.traverseDirect(keyStr)
   626  	if result != nil {
   627  		return result, nil
   628  	}
   629  
   630  	keySegments := strings.Split(keyStr, ".")
   631  	if len(keySegments) == 1 {
   632  		return nil, nil
   633  	}
   634  
   635  	return p.traverseNested(keySegments)
   636  }
   637  
   638  func (p *Page) traverseDirect(key string) (interface{}, error) {
   639  	keyStr := strings.ToLower(key)
   640  	if val, ok := p.params[keyStr]; ok {
   641  		return val, nil
   642  	}
   643  
   644  	return p.Site.Params[keyStr], nil
   645  }
   646  
   647  func (p *Page) traverseNested(keySegments []string) (interface{}, error) {
   648  	result := traverse(keySegments, p.params)
   649  	if result != nil {
   650  		return result, nil
   651  	}
   652  
   653  	result = traverse(keySegments, p.Site.Params)
   654  	if result != nil {
   655  		return result, nil
   656  	}
   657  
   658  	// Didn't find anything, but also no problems.
   659  	return nil, nil
   660  }
   661  
   662  func traverse(keys []string, m map[string]interface{}) interface{} {
   663  	// Shift first element off.
   664  	firstKey, rest := keys[0], keys[1:]
   665  	result := m[firstKey]
   666  
   667  	// No point in continuing here.
   668  	if result == nil {
   669  		return result
   670  	}
   671  
   672  	if len(rest) == 0 {
   673  		// That was the last key.
   674  		return result
   675  	}
   676  
   677  	// That was not the last key.
   678  	return traverse(rest, cast.ToStringMap(result))
   679  }
   680  
   681  func (p *Page) Author() Author {
   682  	authors := p.Authors()
   683  
   684  	for _, author := range authors {
   685  		return author
   686  	}
   687  	return Author{}
   688  }
   689  
   690  func (p *Page) Authors() AuthorList {
   691  	authorKeys, ok := p.params["authors"]
   692  	if !ok {
   693  		return AuthorList{}
   694  	}
   695  	authors := authorKeys.([]string)
   696  	if len(authors) < 1 || len(p.Site.Authors) < 1 {
   697  		return AuthorList{}
   698  	}
   699  
   700  	al := make(AuthorList)
   701  	for _, author := range authors {
   702  		a, ok := p.Site.Authors[author]
   703  		if ok {
   704  			al[author] = a
   705  		}
   706  	}
   707  	return al
   708  }
   709  
   710  func (p *Page) UniqueID() string {
   711  	return p.Source.UniqueID()
   712  }
   713  
   714  // for logging
   715  func (p *Page) lineNumRawContentStart() int {
   716  	return bytes.Count(p.frontmatter, []byte("\n")) + 1
   717  }
   718  
   719  var (
   720  	internalSummaryDivider = []byte("HUGOMORE42")
   721  )
   722  
   723  // replaceDivider replaces the <!--more--> with an internal value and returns
   724  // whether the contentis truncated or not.
   725  // Note: The content slice will be modified if needed.
   726  func replaceDivider(content, from, to []byte) ([]byte, bool) {
   727  	dividerIdx := bytes.Index(content, from)
   728  	if dividerIdx == -1 {
   729  		return content, false
   730  	}
   731  
   732  	afterSummary := content[dividerIdx+len(from):]
   733  
   734  	// If the raw content has nothing but whitespace after the summary
   735  	// marker then the page shouldn't be marked as truncated.  This check
   736  	// is simplest against the raw content because different markup engines
   737  	// (rst and asciidoc in particular) add div and p elements after the
   738  	// summary marker.
   739  	truncated := bytes.IndexFunc(afterSummary, func(r rune) bool { return !unicode.IsSpace(r) }) != -1
   740  
   741  	content = append(content[:dividerIdx], append(to, afterSummary...)...)
   742  
   743  	return content, truncated
   744  
   745  }
   746  
   747  // We have to replace the <!--more--> with something that survives all the
   748  // rendering engines.
   749  func (p *Page) replaceDivider(content []byte) []byte {
   750  	summaryDivider := helpers.SummaryDivider
   751  	if p.Markup == "org" {
   752  		summaryDivider = []byte("# more")
   753  	}
   754  
   755  	replaced, truncated := replaceDivider(content, summaryDivider, internalSummaryDivider)
   756  
   757  	p.truncated = truncated
   758  
   759  	return replaced
   760  }
   761  
   762  // Returns the page as summary and main if a user defined split is provided.
   763  func (p *Page) setUserDefinedSummaryIfProvided(rawContentCopy []byte) (*summaryContent, error) {
   764  
   765  	sc, err := splitUserDefinedSummaryAndContent(p.Markup, rawContentCopy)
   766  
   767  	if err != nil {
   768  		return nil, err
   769  	}
   770  
   771  	if sc == nil {
   772  		// No divider found
   773  		return nil, nil
   774  	}
   775  
   776  	p.summary = helpers.BytesToHTML(sc.summary)
   777  
   778  	return sc, nil
   779  }
   780  
   781  // Make this explicit so there is no doubt about what is what.
   782  type summaryContent struct {
   783  	summary []byte
   784  	content []byte
   785  }
   786  
   787  func splitUserDefinedSummaryAndContent(markup string, c []byte) (sc *summaryContent, err error) {
   788  	defer func() {
   789  		if r := recover(); r != nil {
   790  			err = fmt.Errorf("summary split failed: %s", r)
   791  		}
   792  	}()
   793  
   794  	c = bytes.TrimSpace(c)
   795  	startDivider := bytes.Index(c, internalSummaryDivider)
   796  
   797  	if startDivider == -1 {
   798  		return
   799  	}
   800  
   801  	endDivider := startDivider + len(internalSummaryDivider)
   802  	endSummary := startDivider
   803  
   804  	var (
   805  		startMarkup []byte
   806  		endMarkup   []byte
   807  		addDiv      bool
   808  	)
   809  
   810  	switch markup {
   811  	default:
   812  		startMarkup = []byte("<p>")
   813  		endMarkup = []byte("</p>")
   814  	case "asciidoc":
   815  		startMarkup = []byte("<div class=\"paragraph\">")
   816  		endMarkup = []byte("</div>")
   817  	case "rst":
   818  		startMarkup = []byte("<p>")
   819  		endMarkup = []byte("</p>")
   820  		addDiv = true
   821  	}
   822  
   823  	// Find the closest end/start markup string to the divider
   824  	fromStart := -1
   825  	fromIdx := bytes.LastIndex(c[:startDivider], startMarkup)
   826  	if fromIdx != -1 {
   827  		fromStart = startDivider - fromIdx - len(startMarkup)
   828  	}
   829  	fromEnd := bytes.Index(c[endDivider:], endMarkup)
   830  
   831  	if fromEnd != -1 && fromEnd <= fromStart {
   832  		endSummary = startDivider + fromEnd + len(endMarkup)
   833  	} else if fromStart != -1 && fromEnd != -1 {
   834  		endSummary = startDivider - fromStart - len(startMarkup)
   835  	}
   836  
   837  	withoutDivider := bytes.TrimSpace(append(c[:startDivider], c[endDivider:]...))
   838  	var (
   839  		summary []byte
   840  	)
   841  
   842  	if len(withoutDivider) > 0 {
   843  		summary = bytes.TrimSpace(withoutDivider[:endSummary])
   844  	}
   845  
   846  	if addDiv {
   847  		// For the rst
   848  		summary = append(append([]byte(nil), summary...), []byte("</div>")...)
   849  	}
   850  
   851  	if err != nil {
   852  		return
   853  	}
   854  
   855  	sc = &summaryContent{
   856  		summary: summary,
   857  		content: withoutDivider,
   858  	}
   859  
   860  	return
   861  }
   862  
   863  func (p *Page) setAutoSummary() error {
   864  	var summary string
   865  	var truncated bool
   866  	// This careful init dance could probably be refined, but it is purely for performance
   867  	// reasons. These "plain" methods are expensive if the plain content is never actually
   868  	// used.
   869  	p.initPlain(false)
   870  	if p.isCJKLanguage {
   871  		p.initPlainWords(false)
   872  		summary, truncated = p.s.ContentSpec.TruncateWordsByRune(p.plainWords)
   873  	} else {
   874  		summary, truncated = p.s.ContentSpec.TruncateWordsToWholeSentence(p.plain)
   875  	}
   876  	p.summary = template.HTML(summary)
   877  	p.truncated = truncated
   878  
   879  	return nil
   880  
   881  }
   882  
   883  func (p *Page) renderContent(content []byte) []byte {
   884  	return p.s.ContentSpec.RenderBytes(&helpers.RenderingContext{
   885  		Content: content, RenderTOC: true, PageFmt: p.Markup,
   886  		Cfg:        p.Language(),
   887  		DocumentID: p.UniqueID(), DocumentName: p.Path(),
   888  		Config: p.getRenderingConfig()})
   889  }
   890  
   891  func (p *Page) getRenderingConfig() *helpers.BlackFriday {
   892  	p.renderingConfigInit.Do(func() {
   893  		bfParam := p.getParamToLower("blackfriday")
   894  		if bfParam == nil {
   895  			p.renderingConfig = p.s.ContentSpec.BlackFriday
   896  			return
   897  		}
   898  		// Create a copy so we can modify it.
   899  		bf := *p.s.ContentSpec.BlackFriday
   900  		p.renderingConfig = &bf
   901  
   902  		if p.Language() == nil {
   903  			panic(fmt.Sprintf("nil language for %s with source lang %s", p.BaseFileName(), p.lang))
   904  		}
   905  
   906  		pageParam := cast.ToStringMap(bfParam)
   907  		if err := mapstructure.Decode(pageParam, &p.renderingConfig); err != nil {
   908  			p.s.Log.FATAL.Printf("Failed to get rendering config for %s:\n%s", p.BaseFileName(), err.Error())
   909  		}
   910  
   911  	})
   912  
   913  	return p.renderingConfig
   914  }
   915  
   916  func (s *Site) newPage(filename string) *Page {
   917  	fi := newFileInfo(
   918  		s.SourceSpec,
   919  		s.absContentDir(),
   920  		filename,
   921  		nil,
   922  		bundleNot,
   923  	)
   924  	return s.newPageFromFile(fi)
   925  }
   926  
   927  func (s *Site) newPageFromFile(fi *fileInfo) *Page {
   928  	return &Page{
   929  		pageInit:        &pageInit{},
   930  		pageContentInit: &pageContentInit{},
   931  		Kind:            kindFromFileInfo(fi),
   932  		contentType:     "",
   933  		Source:          Source{File: fi},
   934  		Keywords:        []string{}, Sitemap: Sitemap{Priority: -1},
   935  		params:       make(map[string]interface{}),
   936  		translations: make(Pages, 0),
   937  		sections:     sectionsFromFile(fi),
   938  		Site:         &s.Info,
   939  		s:            s,
   940  	}
   941  }
   942  
   943  func (p *Page) IsRenderable() bool {
   944  	return p.renderable
   945  }
   946  
   947  func (p *Page) Type() string {
   948  	if p.contentType != "" {
   949  		return p.contentType
   950  	}
   951  
   952  	if x := p.Section(); x != "" {
   953  		return x
   954  	}
   955  
   956  	return "page"
   957  }
   958  
   959  // Section returns the first path element below the content root. Note that
   960  // since Hugo 0.22 we support nested sections, but this will always be the first
   961  // element of any nested path.
   962  func (p *Page) Section() string {
   963  	if p.Kind == KindSection || p.Kind == KindTaxonomy || p.Kind == KindTaxonomyTerm {
   964  		return p.sections[0]
   965  	}
   966  	return p.Source.Section()
   967  }
   968  
   969  func (s *Site) NewPageFrom(buf io.Reader, name string) (*Page, error) {
   970  	p, err := s.NewPage(name)
   971  	if err != nil {
   972  		return p, err
   973  	}
   974  	_, err = p.ReadFrom(buf)
   975  
   976  	return p, err
   977  }
   978  
   979  func (s *Site) NewPage(name string) (*Page, error) {
   980  	if len(name) == 0 {
   981  		return nil, errors.New("Zero length page name")
   982  	}
   983  
   984  	// Create new page
   985  	p := s.newPage(name)
   986  	p.s = s
   987  	p.Site = &s.Info
   988  
   989  	return p, nil
   990  }
   991  
   992  func (p *Page) ReadFrom(buf io.Reader) (int64, error) {
   993  	// Parse for metadata & body
   994  	if err := p.parse(buf); err != nil {
   995  		p.s.Log.ERROR.Printf("%s for %s", err, p.File.Path())
   996  		return 0, err
   997  	}
   998  
   999  	return int64(len(p.rawContent)), nil
  1000  }
  1001  
  1002  func (p *Page) WordCount() int {
  1003  	p.initContentPlainAndMeta()
  1004  	return p.wordCount
  1005  }
  1006  
  1007  func (p *Page) ReadingTime() int {
  1008  	p.initContentPlainAndMeta()
  1009  	return p.readingTime
  1010  }
  1011  
  1012  func (p *Page) FuzzyWordCount() int {
  1013  	p.initContentPlainAndMeta()
  1014  	return p.fuzzyWordCount
  1015  }
  1016  
  1017  func (p *Page) initContentPlainAndMeta() {
  1018  	p.initContent()
  1019  	p.initPlain(true)
  1020  	p.initPlainWords(true)
  1021  	p.initMeta()
  1022  }
  1023  
  1024  func (p *Page) initContentAndMeta() {
  1025  	p.initContent()
  1026  	p.initMeta()
  1027  }
  1028  
  1029  func (p *Page) initMeta() {
  1030  	p.pageMetaInit.Do(func() {
  1031  		if p.isCJKLanguage {
  1032  			p.wordCount = 0
  1033  			for _, word := range p.plainWords {
  1034  				runeCount := utf8.RuneCountInString(word)
  1035  				if len(word) == runeCount {
  1036  					p.wordCount++
  1037  				} else {
  1038  					p.wordCount += runeCount
  1039  				}
  1040  			}
  1041  		} else {
  1042  			p.wordCount = helpers.TotalWords(p.plain)
  1043  		}
  1044  
  1045  		// TODO(bep) is set in a test. Fix that.
  1046  		if p.fuzzyWordCount == 0 {
  1047  			p.fuzzyWordCount = (p.wordCount + 100) / 100 * 100
  1048  		}
  1049  
  1050  		if p.isCJKLanguage {
  1051  			p.readingTime = (p.wordCount + 500) / 501
  1052  		} else {
  1053  			p.readingTime = (p.wordCount + 212) / 213
  1054  		}
  1055  	})
  1056  }
  1057  
  1058  // HasShortcode return whether the page has a shortcode with the given name.
  1059  // This method is mainly motivated with the Hugo Docs site's need for a list
  1060  // of pages with the `todo` shortcode in it.
  1061  func (p *Page) HasShortcode(name string) bool {
  1062  	if p.shortcodeState == nil {
  1063  		return false
  1064  	}
  1065  
  1066  	return p.shortcodeState.nameSet[name]
  1067  }
  1068  
  1069  // AllTranslations returns all translations, including the current Page.
  1070  func (p *Page) AllTranslations() Pages {
  1071  	return p.translations
  1072  }
  1073  
  1074  // IsTranslated returns whether this content file is translated to
  1075  // other language(s).
  1076  func (p *Page) IsTranslated() bool {
  1077  	return len(p.translations) > 1
  1078  }
  1079  
  1080  // Translations returns the translations excluding the current Page.
  1081  func (p *Page) Translations() Pages {
  1082  	translations := make(Pages, 0)
  1083  	for _, t := range p.translations {
  1084  		if t.Lang() != p.Lang() {
  1085  			translations = append(translations, t)
  1086  		}
  1087  	}
  1088  	return translations
  1089  }
  1090  
  1091  // TranslationKey returns the key used to map language translations of this page.
  1092  // It will use the translationKey set in front matter if set, or the content path and
  1093  // filename (excluding any language code and extension), e.g. "about/index".
  1094  // The Page Kind is always prepended.
  1095  func (p *Page) TranslationKey() string {
  1096  	if p.translationKey != "" {
  1097  		return p.Kind + "/" + p.translationKey
  1098  	}
  1099  
  1100  	if p.IsNode() {
  1101  		return path.Join(p.Kind, path.Join(p.sections...), p.TranslationBaseName())
  1102  	}
  1103  
  1104  	return path.Join(p.Kind, filepath.ToSlash(p.Dir()), p.TranslationBaseName())
  1105  }
  1106  
  1107  func (p *Page) LinkTitle() string {
  1108  	if len(p.linkTitle) > 0 {
  1109  		return p.linkTitle
  1110  	}
  1111  	return p.title
  1112  }
  1113  
  1114  func (p *Page) shouldBuild() bool {
  1115  	return shouldBuild(p.s.BuildFuture, p.s.BuildExpired,
  1116  		p.s.BuildDrafts, p.Draft, p.PublishDate, p.ExpiryDate)
  1117  }
  1118  
  1119  func shouldBuild(buildFuture bool, buildExpired bool, buildDrafts bool, Draft bool,
  1120  	publishDate time.Time, expiryDate time.Time) bool {
  1121  	if !(buildDrafts || !Draft) {
  1122  		return false
  1123  	}
  1124  	if !buildFuture && !publishDate.IsZero() && publishDate.After(time.Now()) {
  1125  		return false
  1126  	}
  1127  	if !buildExpired && !expiryDate.IsZero() && expiryDate.Before(time.Now()) {
  1128  		return false
  1129  	}
  1130  	return true
  1131  }
  1132  
  1133  func (p *Page) IsDraft() bool {
  1134  	return p.Draft
  1135  }
  1136  
  1137  func (p *Page) IsFuture() bool {
  1138  	if p.PublishDate.IsZero() {
  1139  		return false
  1140  	}
  1141  	return p.PublishDate.After(time.Now())
  1142  }
  1143  
  1144  func (p *Page) IsExpired() bool {
  1145  	if p.ExpiryDate.IsZero() {
  1146  		return false
  1147  	}
  1148  	return p.ExpiryDate.Before(time.Now())
  1149  }
  1150  
  1151  func (p *Page) URL() string {
  1152  
  1153  	if p.IsPage() && p.URLPath.URL != "" {
  1154  		// This is the url set in front matter
  1155  		return p.URLPath.URL
  1156  	}
  1157  	// Fall back to the relative permalink.
  1158  	u := p.RelPermalink()
  1159  	return u
  1160  }
  1161  
  1162  // Permalink returns the absolute URL to this Page.
  1163  func (p *Page) Permalink() string {
  1164  	if p.headless {
  1165  		return ""
  1166  	}
  1167  	return p.permalink
  1168  }
  1169  
  1170  // RelPermalink gets a URL to the resource relative to the host.
  1171  func (p *Page) RelPermalink() string {
  1172  	if p.headless {
  1173  		return ""
  1174  	}
  1175  	return p.relPermalink
  1176  }
  1177  
  1178  // See resource.Resource
  1179  // This value is used, by default, in Resources.ByPrefix etc.
  1180  func (p *Page) Name() string {
  1181  	if p.resourcePath != "" {
  1182  		return p.resourcePath
  1183  	}
  1184  	return p.title
  1185  }
  1186  
  1187  func (p *Page) Title() string {
  1188  	return p.title
  1189  }
  1190  
  1191  func (p *Page) Params() map[string]interface{} {
  1192  	return p.params
  1193  }
  1194  
  1195  func (p *Page) subResourceTargetPathFactory(base string) string {
  1196  	return path.Join(p.relTargetPathBase, base)
  1197  }
  1198  
  1199  func (p *Page) initMainOutputFormat() error {
  1200  	if p.mainPageOutput != nil {
  1201  		return nil
  1202  	}
  1203  
  1204  	outFormat := p.outputFormats[0]
  1205  	pageOutput, err := newPageOutput(p, false, false, outFormat)
  1206  
  1207  	if err != nil {
  1208  		return fmt.Errorf("Failed to create output page for type %q for page %q: %s", outFormat.Name, p.pathOrTitle(), err)
  1209  	}
  1210  
  1211  	p.mainPageOutput = pageOutput
  1212  
  1213  	return nil
  1214  
  1215  }
  1216  
  1217  func (p *Page) setContentInit(start bool) error {
  1218  
  1219  	if start {
  1220  		// This is a new language.
  1221  		p.shortcodeState.clearDelta()
  1222  	}
  1223  	updated := true
  1224  	if p.shortcodeState != nil {
  1225  		updated = p.shortcodeState.updateDelta()
  1226  	}
  1227  
  1228  	if updated {
  1229  		p.resetContent()
  1230  	}
  1231  
  1232  	for _, r := range p.Resources.ByType(pageResourceType) {
  1233  		p.s.PathSpec.ProcessingStats.Incr(&p.s.PathSpec.ProcessingStats.Pages)
  1234  		bp := r.(*Page)
  1235  		if start {
  1236  			bp.shortcodeState.clearDelta()
  1237  		}
  1238  		if bp.shortcodeState != nil {
  1239  			updated = bp.shortcodeState.updateDelta()
  1240  		}
  1241  		if updated {
  1242  			bp.resetContent()
  1243  		}
  1244  	}
  1245  
  1246  	return nil
  1247  
  1248  }
  1249  
  1250  func (p *Page) prepareForRender() error {
  1251  	s := p.s
  1252  
  1253  	// If we got this far it means that this is either a new Page pointer
  1254  	// or a template or similar has changed so wee need to do a rerendering
  1255  	// of the shortcodes etc.
  1256  
  1257  	// If in watch mode or if we have multiple sites or output formats,
  1258  	// we need to keep the original so we can
  1259  	// potentially repeat this process on rebuild.
  1260  	needsACopy := s.running() || len(s.owner.Sites) > 1 || len(p.outputFormats) > 1
  1261  	var workContentCopy []byte
  1262  	if needsACopy {
  1263  		workContentCopy = make([]byte, len(p.workContent))
  1264  		copy(workContentCopy, p.workContent)
  1265  	} else {
  1266  		// Just reuse the same slice.
  1267  		workContentCopy = p.workContent
  1268  	}
  1269  
  1270  	var err error
  1271  	// Note: The shortcodes in a page cannot access the page content it lives in,
  1272  	// hence the withoutContent().
  1273  	if workContentCopy, err = handleShortcodes(p.withoutContent(), workContentCopy); err != nil {
  1274  		s.Log.ERROR.Printf("Failed to handle shortcodes for page %s: %s", p.BaseFileName(), err)
  1275  	}
  1276  
  1277  	if p.Markup != "html" {
  1278  
  1279  		// Now we know enough to create a summary of the page and count some words
  1280  		summaryContent, err := p.setUserDefinedSummaryIfProvided(workContentCopy)
  1281  
  1282  		if err != nil {
  1283  			s.Log.ERROR.Printf("Failed to set user defined summary for page %q: %s", p.Path(), err)
  1284  		} else if summaryContent != nil {
  1285  			workContentCopy = summaryContent.content
  1286  		}
  1287  
  1288  		p.contentv = helpers.BytesToHTML(workContentCopy)
  1289  
  1290  	} else {
  1291  		p.contentv = helpers.BytesToHTML(workContentCopy)
  1292  	}
  1293  
  1294  	return nil
  1295  }
  1296  
  1297  var ErrHasDraftAndPublished = errors.New("both draft and published parameters were found in page's frontmatter")
  1298  
  1299  func (p *Page) update(frontmatter map[string]interface{}) error {
  1300  	if frontmatter == nil {
  1301  		return errors.New("missing frontmatter data")
  1302  	}
  1303  	// Needed for case insensitive fetching of params values
  1304  	maps.ToLower(frontmatter)
  1305  
  1306  	var mtime time.Time
  1307  	if p.Source.FileInfo() != nil {
  1308  		mtime = p.Source.FileInfo().ModTime()
  1309  	}
  1310  
  1311  	var gitAuthorDate time.Time
  1312  	if p.GitInfo != nil {
  1313  		gitAuthorDate = p.GitInfo.AuthorDate
  1314  	}
  1315  
  1316  	descriptor := &pagemeta.FrontMatterDescriptor{
  1317  		Frontmatter:   frontmatter,
  1318  		Params:        p.params,
  1319  		Dates:         &p.PageDates,
  1320  		PageURLs:      &p.URLPath,
  1321  		BaseFilename:  p.BaseFileName(),
  1322  		ModTime:       mtime,
  1323  		GitAuthorDate: gitAuthorDate,
  1324  	}
  1325  
  1326  	// Handle the date separately
  1327  	// TODO(bep) we need to "do more" in this area so this can be split up and
  1328  	// more easily tested without the Page, but the coupling is strong.
  1329  	err := p.s.frontmatterHandler.HandleDates(descriptor)
  1330  	if err != nil {
  1331  		p.s.Log.ERROR.Printf("Failed to handle dates for page %q: %s", p.Path(), err)
  1332  	}
  1333  
  1334  	var draft, published, isCJKLanguage *bool
  1335  	for k, v := range frontmatter {
  1336  		loki := strings.ToLower(k)
  1337  
  1338  		if loki == "published" { // Intentionally undocumented
  1339  			vv, err := cast.ToBoolE(v)
  1340  			if err == nil {
  1341  				published = &vv
  1342  			}
  1343  			// published may also be a date
  1344  			continue
  1345  		}
  1346  
  1347  		if p.s.frontmatterHandler.IsDateKey(loki) {
  1348  			continue
  1349  		}
  1350  
  1351  		switch loki {
  1352  		case "title":
  1353  			p.title = cast.ToString(v)
  1354  			p.params[loki] = p.title
  1355  		case "linktitle":
  1356  			p.linkTitle = cast.ToString(v)
  1357  			p.params[loki] = p.linkTitle
  1358  		case "description":
  1359  			p.Description = cast.ToString(v)
  1360  			p.params[loki] = p.Description
  1361  		case "slug":
  1362  			p.Slug = cast.ToString(v)
  1363  			p.params[loki] = p.Slug
  1364  		case "url":
  1365  			if url := cast.ToString(v); strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") {
  1366  				return fmt.Errorf("Only relative URLs are supported, %v provided", url)
  1367  			}
  1368  			p.URLPath.URL = cast.ToString(v)
  1369  			p.frontMatterURL = p.URLPath.URL
  1370  			p.params[loki] = p.URLPath.URL
  1371  		case "type":
  1372  			p.contentType = cast.ToString(v)
  1373  			p.params[loki] = p.contentType
  1374  		case "extension", "ext":
  1375  			p.extension = cast.ToString(v)
  1376  			p.params[loki] = p.extension
  1377  		case "keywords":
  1378  			p.Keywords = cast.ToStringSlice(v)
  1379  			p.params[loki] = p.Keywords
  1380  		case "headless":
  1381  			// For now, only the leaf bundles ("index.md") can be headless (i.e. produce no output).
  1382  			// We may expand on this in the future, but that gets more complex pretty fast.
  1383  			if p.TranslationBaseName() == "index" {
  1384  				p.headless = cast.ToBool(v)
  1385  			}
  1386  			p.params[loki] = p.headless
  1387  		case "outputs":
  1388  			o := cast.ToStringSlice(v)
  1389  			if len(o) > 0 {
  1390  				// Output formats are exlicitly set in front matter, use those.
  1391  				outFormats, err := p.s.outputFormatsConfig.GetByNames(o...)
  1392  
  1393  				if err != nil {
  1394  					p.s.Log.ERROR.Printf("Failed to resolve output formats: %s", err)
  1395  				} else {
  1396  					p.outputFormats = outFormats
  1397  					p.params[loki] = outFormats
  1398  				}
  1399  
  1400  			}
  1401  		case "draft":
  1402  			draft = new(bool)
  1403  			*draft = cast.ToBool(v)
  1404  		case "layout":
  1405  			p.Layout = cast.ToString(v)
  1406  			p.params[loki] = p.Layout
  1407  		case "markup":
  1408  			p.Markup = cast.ToString(v)
  1409  			p.params[loki] = p.Markup
  1410  		case "weight":
  1411  			p.Weight = cast.ToInt(v)
  1412  			p.params[loki] = p.Weight
  1413  		case "aliases":
  1414  			p.Aliases = cast.ToStringSlice(v)
  1415  			for _, alias := range p.Aliases {
  1416  				if strings.HasPrefix(alias, "http://") || strings.HasPrefix(alias, "https://") {
  1417  					return fmt.Errorf("Only relative aliases are supported, %v provided", alias)
  1418  				}
  1419  			}
  1420  			p.params[loki] = p.Aliases
  1421  		case "status":
  1422  			p.Status = cast.ToString(v)
  1423  			p.params[loki] = p.Status
  1424  		case "sitemap":
  1425  			p.Sitemap = parseSitemap(cast.ToStringMap(v))
  1426  			p.params[loki] = p.Sitemap
  1427  		case "iscjklanguage":
  1428  			isCJKLanguage = new(bool)
  1429  			*isCJKLanguage = cast.ToBool(v)
  1430  		case "translationkey":
  1431  			p.translationKey = cast.ToString(v)
  1432  			p.params[loki] = p.translationKey
  1433  		case "resources":
  1434  			var resources []map[string]interface{}
  1435  			handled := true
  1436  
  1437  			switch vv := v.(type) {
  1438  			case []map[interface{}]interface{}:
  1439  				for _, vvv := range vv {
  1440  					resources = append(resources, cast.ToStringMap(vvv))
  1441  				}
  1442  			case []map[string]interface{}:
  1443  				for _, vvv := range vv {
  1444  					resources = append(resources, vvv)
  1445  				}
  1446  			case []interface{}:
  1447  				for _, vvv := range vv {
  1448  					switch vvvv := vvv.(type) {
  1449  					case map[interface{}]interface{}:
  1450  						resources = append(resources, cast.ToStringMap(vvvv))
  1451  					case map[string]interface{}:
  1452  						resources = append(resources, vvvv)
  1453  					}
  1454  				}
  1455  			default:
  1456  				handled = false
  1457  			}
  1458  
  1459  			if handled {
  1460  				p.params[loki] = resources
  1461  				p.resourcesMetadata = resources
  1462  				break
  1463  			}
  1464  			fallthrough
  1465  
  1466  		default:
  1467  			// If not one of the explicit values, store in Params
  1468  			switch vv := v.(type) {
  1469  			case bool:
  1470  				p.params[loki] = vv
  1471  			case string:
  1472  				p.params[loki] = vv
  1473  			case int64, int32, int16, int8, int:
  1474  				p.params[loki] = vv
  1475  			case float64, float32:
  1476  				p.params[loki] = vv
  1477  			case time.Time:
  1478  				p.params[loki] = vv
  1479  			default: // handle array of strings as well
  1480  				switch vvv := vv.(type) {
  1481  				case []interface{}:
  1482  					if len(vvv) > 0 {
  1483  						switch vvv[0].(type) {
  1484  						case map[interface{}]interface{}: // Proper parsing structured array from YAML based FrontMatter
  1485  							p.params[loki] = vvv
  1486  						case map[string]interface{}: // Proper parsing structured array from JSON based FrontMatter
  1487  							p.params[loki] = vvv
  1488  						case []interface{}:
  1489  							p.params[loki] = vvv
  1490  						default:
  1491  							a := make([]string, len(vvv))
  1492  							for i, u := range vvv {
  1493  								a[i] = cast.ToString(u)
  1494  							}
  1495  
  1496  							p.params[loki] = a
  1497  						}
  1498  					} else {
  1499  						p.params[loki] = []string{}
  1500  					}
  1501  				default:
  1502  					p.params[loki] = vv
  1503  				}
  1504  			}
  1505  		}
  1506  	}
  1507  
  1508  	// Try markup explicitly set in the frontmatter
  1509  	p.Markup = helpers.GuessType(p.Markup)
  1510  	if p.Markup == "unknown" {
  1511  		// Fall back to file extension (might also return "unknown")
  1512  		p.Markup = helpers.GuessType(p.Source.Ext())
  1513  	}
  1514  
  1515  	if draft != nil && published != nil {
  1516  		p.Draft = *draft
  1517  		p.s.Log.ERROR.Printf("page %s has both draft and published settings in its frontmatter. Using draft.", p.File.Path())
  1518  		return ErrHasDraftAndPublished
  1519  	} else if draft != nil {
  1520  		p.Draft = *draft
  1521  	} else if published != nil {
  1522  		p.Draft = !*published
  1523  	}
  1524  	p.params["draft"] = p.Draft
  1525  
  1526  	if isCJKLanguage != nil {
  1527  		p.isCJKLanguage = *isCJKLanguage
  1528  	} else if p.s.Cfg.GetBool("hasCJKLanguage") {
  1529  		if cjk.Match(p.rawContent) {
  1530  			p.isCJKLanguage = true
  1531  		} else {
  1532  			p.isCJKLanguage = false
  1533  		}
  1534  	}
  1535  	p.params["iscjklanguage"] = p.isCJKLanguage
  1536  
  1537  	return nil
  1538  }
  1539  
  1540  func (p *Page) GetParam(key string) interface{} {
  1541  	return p.getParam(key, false)
  1542  }
  1543  
  1544  func (p *Page) getParamToLower(key string) interface{} {
  1545  	return p.getParam(key, true)
  1546  }
  1547  
  1548  func (p *Page) getParam(key string, stringToLower bool) interface{} {
  1549  	v := p.params[strings.ToLower(key)]
  1550  
  1551  	if v == nil {
  1552  		return nil
  1553  	}
  1554  
  1555  	switch val := v.(type) {
  1556  	case bool:
  1557  		return val
  1558  	case string:
  1559  		if stringToLower {
  1560  			return strings.ToLower(val)
  1561  		}
  1562  		return val
  1563  	case int64, int32, int16, int8, int:
  1564  		return cast.ToInt(v)
  1565  	case float64, float32:
  1566  		return cast.ToFloat64(v)
  1567  	case time.Time:
  1568  		return val
  1569  	case []string:
  1570  		if stringToLower {
  1571  			return helpers.SliceToLower(val)
  1572  		}
  1573  		return v
  1574  	case map[string]interface{}: // JSON and TOML
  1575  		return v
  1576  	case map[interface{}]interface{}: // YAML
  1577  		return v
  1578  	}
  1579  
  1580  	p.s.Log.ERROR.Printf("GetParam(\"%s\"): Unknown type %s\n", key, reflect.TypeOf(v))
  1581  	return nil
  1582  }
  1583  
  1584  func (p *Page) HasMenuCurrent(menuID string, me *MenuEntry) bool {
  1585  
  1586  	sectionPagesMenu := p.Site.sectionPagesMenu
  1587  
  1588  	// page is labeled as "shadow-member" of the menu with the same identifier as the section
  1589  	if sectionPagesMenu != "" {
  1590  		section := p.Section()
  1591  
  1592  		if section != "" && sectionPagesMenu == menuID && section == me.Identifier {
  1593  			return true
  1594  		}
  1595  	}
  1596  
  1597  	if !me.HasChildren() {
  1598  		return false
  1599  	}
  1600  
  1601  	menus := p.Menus()
  1602  
  1603  	if m, ok := menus[menuID]; ok {
  1604  
  1605  		for _, child := range me.Children {
  1606  			if child.IsEqual(m) {
  1607  				return true
  1608  			}
  1609  			if p.HasMenuCurrent(menuID, child) {
  1610  				return true
  1611  			}
  1612  		}
  1613  
  1614  	}
  1615  
  1616  	if p.IsPage() {
  1617  		return false
  1618  	}
  1619  
  1620  	// The following logic is kept from back when Hugo had both Page and Node types.
  1621  	// TODO(bep) consolidate / clean
  1622  	nme := MenuEntry{Page: p, Name: p.title, URL: p.URL()}
  1623  
  1624  	for _, child := range me.Children {
  1625  		if nme.IsSameResource(child) {
  1626  			return true
  1627  		}
  1628  		if p.HasMenuCurrent(menuID, child) {
  1629  			return true
  1630  		}
  1631  	}
  1632  
  1633  	return false
  1634  
  1635  }
  1636  
  1637  func (p *Page) IsMenuCurrent(menuID string, inme *MenuEntry) bool {
  1638  
  1639  	menus := p.Menus()
  1640  
  1641  	if me, ok := menus[menuID]; ok {
  1642  		if me.IsEqual(inme) {
  1643  			return true
  1644  		}
  1645  	}
  1646  
  1647  	if p.IsPage() {
  1648  		return false
  1649  	}
  1650  
  1651  	// The following logic is kept from back when Hugo had both Page and Node types.
  1652  	// TODO(bep) consolidate / clean
  1653  	me := MenuEntry{Page: p, Name: p.title, URL: p.URL()}
  1654  
  1655  	if !me.IsSameResource(inme) {
  1656  		return false
  1657  	}
  1658  
  1659  	// this resource may be included in several menus
  1660  	// search for it to make sure that it is in the menu with the given menuId
  1661  	if menu, ok := (*p.Site.Menus)[menuID]; ok {
  1662  		for _, menuEntry := range *menu {
  1663  			if menuEntry.IsSameResource(inme) {
  1664  				return true
  1665  			}
  1666  
  1667  			descendantFound := p.isSameAsDescendantMenu(inme, menuEntry)
  1668  			if descendantFound {
  1669  				return descendantFound
  1670  			}
  1671  
  1672  		}
  1673  	}
  1674  
  1675  	return false
  1676  }
  1677  
  1678  func (p *Page) isSameAsDescendantMenu(inme *MenuEntry, parent *MenuEntry) bool {
  1679  	if parent.HasChildren() {
  1680  		for _, child := range parent.Children {
  1681  			if child.IsSameResource(inme) {
  1682  				return true
  1683  			}
  1684  			descendantFound := p.isSameAsDescendantMenu(inme, child)
  1685  			if descendantFound {
  1686  				return descendantFound
  1687  			}
  1688  		}
  1689  	}
  1690  	return false
  1691  }
  1692  
  1693  func (p *Page) Menus() PageMenus {
  1694  	p.pageMenusInit.Do(func() {
  1695  		p.pageMenus = PageMenus{}
  1696  
  1697  		if ms, ok := p.params["menu"]; ok {
  1698  			link := p.RelPermalink()
  1699  
  1700  			me := MenuEntry{Page: p, Name: p.LinkTitle(), Weight: p.Weight, URL: link}
  1701  
  1702  			// Could be the name of the menu to attach it to
  1703  			mname, err := cast.ToStringE(ms)
  1704  
  1705  			if err == nil {
  1706  				me.Menu = mname
  1707  				p.pageMenus[mname] = &me
  1708  				return
  1709  			}
  1710  
  1711  			// Could be a slice of strings
  1712  			mnames, err := cast.ToStringSliceE(ms)
  1713  
  1714  			if err == nil {
  1715  				for _, mname := range mnames {
  1716  					me.Menu = mname
  1717  					p.pageMenus[mname] = &me
  1718  				}
  1719  				return
  1720  			}
  1721  
  1722  			// Could be a structured menu entry
  1723  			menus, err := cast.ToStringMapE(ms)
  1724  
  1725  			if err != nil {
  1726  				p.s.Log.ERROR.Printf("unable to process menus for %q\n", p.title)
  1727  			}
  1728  
  1729  			for name, menu := range menus {
  1730  				menuEntry := MenuEntry{Page: p, Name: p.LinkTitle(), URL: link, Weight: p.Weight, Menu: name}
  1731  				if menu != nil {
  1732  					p.s.Log.DEBUG.Printf("found menu: %q, in %q\n", name, p.title)
  1733  					ime, err := cast.ToStringMapE(menu)
  1734  					if err != nil {
  1735  						p.s.Log.ERROR.Printf("unable to process menus for %q: %s", p.title, err)
  1736  					}
  1737  
  1738  					menuEntry.marshallMap(ime)
  1739  				}
  1740  				p.pageMenus[name] = &menuEntry
  1741  
  1742  			}
  1743  		}
  1744  	})
  1745  
  1746  	return p.pageMenus
  1747  }
  1748  
  1749  func (p *Page) shouldRenderTo(f output.Format) bool {
  1750  	_, found := p.outputFormats.GetByName(f.Name)
  1751  	return found
  1752  }
  1753  
  1754  func (p *Page) parse(reader io.Reader) error {
  1755  	psr, err := parser.ReadFrom(reader)
  1756  	if err != nil {
  1757  		return err
  1758  	}
  1759  
  1760  	p.renderable = psr.IsRenderable()
  1761  	p.frontmatter = psr.FrontMatter()
  1762  	p.rawContent = psr.Content()
  1763  	p.lang = p.Source.File.Lang()
  1764  
  1765  	meta, err := psr.Metadata()
  1766  	if err != nil {
  1767  		return fmt.Errorf("failed to parse page metadata for %q: %s", p.File.Path(), err)
  1768  	}
  1769  	if meta == nil {
  1770  		// missing frontmatter equivalent to empty frontmatter
  1771  		meta = map[string]interface{}{}
  1772  	}
  1773  
  1774  	if p.s != nil && p.s.owner != nil {
  1775  		gi, enabled := p.s.owner.gitInfo.forPage(p)
  1776  		if gi != nil {
  1777  			p.GitInfo = gi
  1778  		} else if enabled {
  1779  			p.s.Log.WARN.Printf("Failed to find GitInfo for page %q", p.Path())
  1780  		}
  1781  	}
  1782  
  1783  	return p.update(meta)
  1784  }
  1785  
  1786  func (p *Page) RawContent() string {
  1787  	return string(p.rawContent)
  1788  }
  1789  
  1790  func (p *Page) SetSourceContent(content []byte) {
  1791  	p.Source.Content = content
  1792  }
  1793  
  1794  func (p *Page) SetSourceMetaData(in interface{}, mark rune) (err error) {
  1795  	// See https://github.com/gohugoio/hugo/issues/2458
  1796  	defer func() {
  1797  		if r := recover(); r != nil {
  1798  			var ok bool
  1799  			err, ok = r.(error)
  1800  			if !ok {
  1801  				err = fmt.Errorf("error from marshal: %v", r)
  1802  			}
  1803  		}
  1804  	}()
  1805  
  1806  	buf := bp.GetBuffer()
  1807  	defer bp.PutBuffer(buf)
  1808  
  1809  	err = parser.InterfaceToFrontMatter(in, mark, buf)
  1810  	if err != nil {
  1811  		return
  1812  	}
  1813  
  1814  	_, err = buf.WriteRune('\n')
  1815  	if err != nil {
  1816  		return
  1817  	}
  1818  
  1819  	p.Source.Frontmatter = buf.Bytes()
  1820  
  1821  	return
  1822  }
  1823  
  1824  func (p *Page) SafeSaveSourceAs(path string) error {
  1825  	return p.saveSourceAs(path, true)
  1826  }
  1827  
  1828  func (p *Page) SaveSourceAs(path string) error {
  1829  	return p.saveSourceAs(path, false)
  1830  }
  1831  
  1832  func (p *Page) saveSourceAs(path string, safe bool) error {
  1833  	b := bp.GetBuffer()
  1834  	defer bp.PutBuffer(b)
  1835  
  1836  	b.Write(p.Source.Frontmatter)
  1837  	b.Write(p.Source.Content)
  1838  
  1839  	bc := make([]byte, b.Len(), b.Len())
  1840  	copy(bc, b.Bytes())
  1841  
  1842  	return p.saveSource(bc, path, safe)
  1843  }
  1844  
  1845  func (p *Page) saveSource(by []byte, inpath string, safe bool) (err error) {
  1846  	if !filepath.IsAbs(inpath) {
  1847  		inpath = p.s.PathSpec.AbsPathify(inpath)
  1848  	}
  1849  	p.s.Log.INFO.Println("creating", inpath)
  1850  	if safe {
  1851  		err = helpers.SafeWriteToDisk(inpath, bytes.NewReader(by), p.s.Fs.Source)
  1852  	} else {
  1853  		err = helpers.WriteToDisk(inpath, bytes.NewReader(by), p.s.Fs.Source)
  1854  	}
  1855  	if err != nil {
  1856  		return
  1857  	}
  1858  	return nil
  1859  }
  1860  
  1861  func (p *Page) SaveSource() error {
  1862  	return p.SaveSourceAs(p.FullFilePath())
  1863  }
  1864  
  1865  // TODO(bep) lazy consolidate
  1866  func (p *Page) processShortcodes() error {
  1867  	p.shortcodeState = newShortcodeHandler(p)
  1868  	tmpContent, err := p.shortcodeState.extractShortcodes(string(p.workContent), p.withoutContent())
  1869  	if err != nil {
  1870  		return err
  1871  	}
  1872  	p.workContent = []byte(tmpContent)
  1873  
  1874  	return nil
  1875  
  1876  }
  1877  
  1878  func (p *Page) FullFilePath() string {
  1879  	return filepath.Join(p.Dir(), p.LogicalName())
  1880  }
  1881  
  1882  // Returns the canonical, absolute fully-qualifed logical reference used by
  1883  // methods such as GetPage and ref/relref shortcodes to refer to
  1884  // this page. It is prefixed with a "/".
  1885  //
  1886  // For pages that have a source file, it is returns the path to this file as an
  1887  // absolute path rooted in this site's content dir.
  1888  // For pages that do not (sections witout content page etc.), it returns the
  1889  // virtual path, consistent with where you would add a source file.
  1890  func (p *Page) absoluteSourceRef() string {
  1891  	if p.Source.File != nil {
  1892  		sourcePath := p.Source.Path()
  1893  		if sourcePath != "" {
  1894  			return "/" + filepath.ToSlash(sourcePath)
  1895  		}
  1896  	}
  1897  
  1898  	if len(p.sections) > 0 {
  1899  		// no backing file, return the virtual source path
  1900  		return "/" + path.Join(p.sections...)
  1901  	}
  1902  
  1903  	return ""
  1904  }
  1905  
  1906  // Pre render prepare steps
  1907  
  1908  func (p *Page) prepareLayouts() error {
  1909  	// TODO(bep): Check the IsRenderable logic.
  1910  	if p.Kind == KindPage {
  1911  		if !p.IsRenderable() {
  1912  			self := "__" + p.UniqueID()
  1913  			err := p.s.TemplateHandler().AddLateTemplate(self, string(p.content()))
  1914  			if err != nil {
  1915  				return err
  1916  			}
  1917  			p.selfLayout = self
  1918  		}
  1919  	}
  1920  
  1921  	return nil
  1922  }
  1923  
  1924  func (p *Page) prepareData(s *Site) error {
  1925  	if p.Kind != KindSection {
  1926  		var pages Pages
  1927  		p.data = make(map[string]interface{})
  1928  
  1929  		switch p.Kind {
  1930  		case KindPage:
  1931  		case KindHome:
  1932  			pages = s.RegularPages
  1933  		case KindTaxonomy:
  1934  			plural := p.sections[0]
  1935  			term := p.sections[1]
  1936  
  1937  			if s.Info.preserveTaxonomyNames {
  1938  				if v, ok := s.taxonomiesOrigKey[fmt.Sprintf("%s-%s", plural, term)]; ok {
  1939  					term = v
  1940  				}
  1941  			}
  1942  
  1943  			singular := s.taxonomiesPluralSingular[plural]
  1944  			taxonomy := s.Taxonomies[plural].Get(term)
  1945  
  1946  			p.data[singular] = taxonomy
  1947  			p.data["Singular"] = singular
  1948  			p.data["Plural"] = plural
  1949  			p.data["Term"] = term
  1950  			pages = taxonomy.Pages()
  1951  		case KindTaxonomyTerm:
  1952  			plural := p.sections[0]
  1953  			singular := s.taxonomiesPluralSingular[plural]
  1954  
  1955  			p.data["Singular"] = singular
  1956  			p.data["Plural"] = plural
  1957  			p.data["Terms"] = s.Taxonomies[plural]
  1958  			// keep the following just for legacy reasons
  1959  			p.data["OrderedIndex"] = p.data["Terms"]
  1960  			p.data["Index"] = p.data["Terms"]
  1961  
  1962  			// A list of all KindTaxonomy pages with matching plural
  1963  			for _, p := range s.findPagesByKind(KindTaxonomy) {
  1964  				if p.sections[0] == plural {
  1965  					pages = append(pages, p)
  1966  				}
  1967  			}
  1968  		}
  1969  
  1970  		p.data["Pages"] = pages
  1971  		p.Pages = pages
  1972  	}
  1973  
  1974  	// Now we know enough to set missing dates on home page etc.
  1975  	p.updatePageDates()
  1976  
  1977  	return nil
  1978  }
  1979  
  1980  func (p *Page) updatePageDates() {
  1981  	// TODO(bep) there is a potential issue with page sorting for home pages
  1982  	// etc. without front matter dates set, but let us wrap the head around
  1983  	// that in another time.
  1984  	if !p.IsNode() {
  1985  		return
  1986  	}
  1987  
  1988  	if !p.Date.IsZero() {
  1989  		if p.Lastmod.IsZero() {
  1990  			p.Lastmod = p.Date
  1991  		}
  1992  		return
  1993  	} else if !p.Lastmod.IsZero() {
  1994  		if p.Date.IsZero() {
  1995  			p.Date = p.Lastmod
  1996  		}
  1997  		return
  1998  	}
  1999  
  2000  	// Set it to the first non Zero date in children
  2001  	var foundDate, foundLastMod bool
  2002  
  2003  	for _, child := range p.Pages {
  2004  		if !child.Date.IsZero() {
  2005  			p.Date = child.Date
  2006  			foundDate = true
  2007  		}
  2008  		if !child.Lastmod.IsZero() {
  2009  			p.Lastmod = child.Lastmod
  2010  			foundLastMod = true
  2011  		}
  2012  
  2013  		if foundDate && foundLastMod {
  2014  			break
  2015  		}
  2016  	}
  2017  }
  2018  
  2019  // copy creates a copy of this page with the lazy sync.Once vars reset
  2020  // so they will be evaluated again, for word count calculations etc.
  2021  func (p *Page) copy(initContent bool) *Page {
  2022  	p.contentInitMu.Lock()
  2023  	c := *p
  2024  	p.contentInitMu.Unlock()
  2025  	c.pageInit = &pageInit{}
  2026  	if initContent {
  2027  		if len(p.outputFormats) < 2 {
  2028  			panic(fmt.Sprintf("programming error: page %q should not need to rebuild content as it has only %d outputs", p.Path(), len(p.outputFormats)))
  2029  		}
  2030  		c.pageContentInit = &pageContentInit{}
  2031  	}
  2032  	return &c
  2033  }
  2034  
  2035  func (p *Page) Hugo() *HugoInfo {
  2036  	return hugoInfo
  2037  }
  2038  
  2039  // GetPage looks up a page for the given ref.
  2040  //    {{ with .GetPage "blog" }}{{ .Title }}{{ end }}
  2041  //
  2042  // This will return nil when no page could be found, and will return
  2043  // an error if the ref is ambiguous.
  2044  func (p *Page) GetPage(ref string) (*Page, error) {
  2045  	return p.s.getPageNew(p, ref)
  2046  }
  2047  
  2048  type refArgs struct {
  2049  	Path         string
  2050  	Lang         string
  2051  	OutputFormat string
  2052  }
  2053  
  2054  func (p *Page) decodeRefArgs(args map[string]interface{}) (refArgs, *SiteInfo, error) {
  2055  	var ra refArgs
  2056  	err := mapstructure.WeakDecode(args, &ra)
  2057  	if err != nil {
  2058  		return ra, nil, nil
  2059  	}
  2060  	s := p.Site
  2061  
  2062  	if ra.Lang != "" && ra.Lang != p.Lang() {
  2063  		// Find correct site
  2064  		found := false
  2065  		for _, ss := range p.s.owner.Sites {
  2066  			if ss.Lang() == ra.Lang {
  2067  				found = true
  2068  				s = &ss.Info
  2069  			}
  2070  		}
  2071  
  2072  		if !found {
  2073  			p.s.siteRefLinker.logNotFound(ra.Path, fmt.Sprintf("no site found with lang %q", ra.Lang), p)
  2074  			return ra, nil, nil
  2075  		}
  2076  	}
  2077  
  2078  	return ra, s, nil
  2079  }
  2080  
  2081  func (p *Page) Ref(argsm map[string]interface{}) (string, error) {
  2082  	args, s, err := p.decodeRefArgs(argsm)
  2083  	if err != nil {
  2084  		return "", fmt.Errorf("invalid arguments to Ref: %s", err)
  2085  	}
  2086  
  2087  	if s == nil {
  2088  		return p.s.siteRefLinker.notFoundURL, nil
  2089  	}
  2090  
  2091  	if args.Path == "" {
  2092  		return "", nil
  2093  	}
  2094  
  2095  	if args.OutputFormat != "" {
  2096  		return s.Ref(args.Path, p, args.OutputFormat)
  2097  	}
  2098  	return s.Ref(args.Path, p)
  2099  }
  2100  
  2101  func (p *Page) RelRef(argsm map[string]interface{}) (string, error) {
  2102  	args, s, err := p.decodeRefArgs(argsm)
  2103  	if err != nil {
  2104  		return "", fmt.Errorf("invalid arguments to Ref: %s", err)
  2105  	}
  2106  
  2107  	if s == nil {
  2108  		return p.s.siteRefLinker.notFoundURL, nil
  2109  	}
  2110  
  2111  	if args.Path == "" {
  2112  		return "", nil
  2113  	}
  2114  
  2115  	if args.OutputFormat != "" {
  2116  		return s.RelRef(args.Path, p, args.OutputFormat)
  2117  	}
  2118  	return s.RelRef(args.Path, p)
  2119  }
  2120  
  2121  func (p *Page) String() string {
  2122  	if sourceRef := p.absoluteSourceRef(); sourceRef != "" {
  2123  		return fmt.Sprintf("Page(%s)", sourceRef)
  2124  	}
  2125  	return fmt.Sprintf("Page(%q)", p.title)
  2126  }
  2127  
  2128  // Scratch returns the writable context associated with this Page.
  2129  func (p *Page) Scratch() *maps.Scratch {
  2130  	if p.scratch == nil {
  2131  		p.scratch = maps.NewScratch()
  2132  	}
  2133  	return p.scratch
  2134  }
  2135  
  2136  func (p *Page) Language() *langs.Language {
  2137  	p.initLanguage()
  2138  	return p.language
  2139  }
  2140  
  2141  func (p *Page) Lang() string {
  2142  	// When set, Language can be different from lang in the case where there is a
  2143  	// content file (doc.sv.md) with language indicator, but there is no language
  2144  	// config for that language. Then the language will fall back on the site default.
  2145  	if p.Language() != nil {
  2146  		return p.Language().Lang
  2147  	}
  2148  	return p.lang
  2149  }
  2150  
  2151  func (p *Page) isNewTranslation(candidate *Page) bool {
  2152  
  2153  	if p.Kind != candidate.Kind {
  2154  		return false
  2155  	}
  2156  
  2157  	if p.Kind == KindPage || p.Kind == kindUnknown {
  2158  		panic("Node type not currently supported for this op")
  2159  	}
  2160  
  2161  	// At this point, we know that this is a traditional Node (home page, section, taxonomy)
  2162  	// It represents the same node, but different language, if the sections is the same.
  2163  	if len(p.sections) != len(candidate.sections) {
  2164  		return false
  2165  	}
  2166  
  2167  	for i := 0; i < len(p.sections); i++ {
  2168  		if p.sections[i] != candidate.sections[i] {
  2169  			return false
  2170  		}
  2171  	}
  2172  
  2173  	// Finally check that it is not already added.
  2174  	for _, translation := range p.translations {
  2175  		if candidate == translation {
  2176  			return false
  2177  		}
  2178  	}
  2179  
  2180  	return true
  2181  
  2182  }
  2183  
  2184  func (p *Page) shouldAddLanguagePrefix() bool {
  2185  	if !p.Site.IsMultiLingual() {
  2186  		return false
  2187  	}
  2188  
  2189  	if p.s.owner.IsMultihost() {
  2190  		return true
  2191  	}
  2192  
  2193  	if p.Lang() == "" {
  2194  		return false
  2195  	}
  2196  
  2197  	if !p.Site.defaultContentLanguageInSubdir && p.Lang() == p.s.multilingual().DefaultLang.Lang {
  2198  		return false
  2199  	}
  2200  
  2201  	return true
  2202  }
  2203  
  2204  func (p *Page) initLanguage() {
  2205  	p.languageInit.Do(func() {
  2206  		if p.language != nil {
  2207  			return
  2208  		}
  2209  
  2210  		ml := p.s.multilingual()
  2211  		if ml == nil {
  2212  			panic("Multilanguage not set")
  2213  		}
  2214  		if p.lang == "" {
  2215  			p.lang = ml.DefaultLang.Lang
  2216  			p.language = ml.DefaultLang
  2217  			return
  2218  		}
  2219  
  2220  		language := ml.Language(p.lang)
  2221  
  2222  		if language == nil {
  2223  			// It can be a file named stefano.chiodino.md.
  2224  			p.s.Log.WARN.Printf("Page language (if it is that) not found in multilang setup: %s.", p.lang)
  2225  			language = ml.DefaultLang
  2226  		}
  2227  
  2228  		p.language = language
  2229  
  2230  	})
  2231  }
  2232  
  2233  func (p *Page) LanguagePrefix() string {
  2234  	return p.Site.LanguagePrefix
  2235  }
  2236  
  2237  func (p *Page) addLangPathPrefixIfFlagSet(outfile string, should bool) string {
  2238  	if helpers.IsAbsURL(outfile) {
  2239  		return outfile
  2240  	}
  2241  
  2242  	if !should {
  2243  		return outfile
  2244  	}
  2245  
  2246  	hadSlashSuffix := strings.HasSuffix(outfile, "/")
  2247  
  2248  	outfile = "/" + path.Join(p.Lang(), outfile)
  2249  	if hadSlashSuffix {
  2250  		outfile += "/"
  2251  	}
  2252  	return outfile
  2253  }
  2254  
  2255  func sectionsFromFile(fi *fileInfo) []string {
  2256  	dirname := fi.Dir()
  2257  	dirname = strings.Trim(dirname, helpers.FilePathSeparator)
  2258  	if dirname == "" {
  2259  		return nil
  2260  	}
  2261  	parts := strings.Split(dirname, helpers.FilePathSeparator)
  2262  
  2263  	if fi.bundleTp == bundleLeaf && len(parts) > 0 {
  2264  		// my-section/mybundle/index.md => my-section
  2265  		return parts[:len(parts)-1]
  2266  	}
  2267  
  2268  	return parts
  2269  }
  2270  
  2271  func kindFromFileInfo(fi *fileInfo) string {
  2272  	if fi.TranslationBaseName() == "_index" {
  2273  		if fi.Dir() == "" {
  2274  			return KindHome
  2275  		}
  2276  		// Could be index for section, taxonomy, taxonomy term
  2277  		// We don't know enough yet to determine which
  2278  		return kindUnknown
  2279  	}
  2280  	return KindPage
  2281  }
  2282  
  2283  func (p *Page) setValuesForKind(s *Site) {
  2284  	if p.Kind == kindUnknown {
  2285  		// This is either a taxonomy list, taxonomy term or a section
  2286  		nodeType := s.kindFromSections(p.sections)
  2287  
  2288  		if nodeType == kindUnknown {
  2289  			panic(fmt.Sprintf("Unable to determine page kind from %q", p.sections))
  2290  		}
  2291  
  2292  		p.Kind = nodeType
  2293  	}
  2294  
  2295  	switch p.Kind {
  2296  	case KindHome:
  2297  		p.URLPath.URL = "/"
  2298  	case KindPage:
  2299  	default:
  2300  		if p.URLPath.URL == "" {
  2301  			p.URLPath.URL = "/" + path.Join(p.sections...) + "/"
  2302  		}
  2303  	}
  2304  }
  2305  
  2306  // Used in error logs.
  2307  func (p *Page) pathOrTitle() string {
  2308  	if p.Path() != "" {
  2309  		return p.Path()
  2310  	}
  2311  	return p.title
  2312  }