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