diff options
author | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2019-01-02 12:33:26 +0100 |
---|---|---|
committer | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2019-03-23 18:51:22 +0100 |
commit | 597e418cb02883418f2cebb41400e8e61413f651 (patch) | |
tree | 177ad9c540b2583b6dab138c9f0490d28989c7f7 /hugolib/site.go | |
parent | 44f5c1c14cb1f42cc5f01739c289e9cfc83602af (diff) |
Make Page an interface
The main motivation of this commit is to add a `page.Page` interface to replace the very file-oriented `hugolib.Page` struct.
This is all a preparation step for issue #5074, "pages from other data sources".
But this also fixes a set of annoying limitations, especially related to custom output formats, and shortcodes.
Most notable changes:
* The inner content of shortcodes using the `{{%` as the outer-most delimiter will now be sent to the content renderer, e.g. Blackfriday.
This means that any markdown will partake in the global ToC and footnote context etc.
* The Custom Output formats are now "fully virtualized". This removes many of the current limitations.
* The taxonomy list type now has a reference to the `Page` object.
This improves the taxonomy template `.Title` situation and make common template constructs much simpler.
See #5074
Fixes #5763
Fixes #5758
Fixes #5090
Fixes #5204
Fixes #4695
Fixes #5607
Fixes #5707
Fixes #5719
Fixes #3113
Fixes #5706
Fixes #5767
Fixes #5723
Fixes #5769
Fixes #5770
Fixes #5771
Fixes #5759
Fixes #5776
Fixes #5777
Fixes #5778
Diffstat (limited to 'hugolib/site.go')
-rw-r--r-- | hugolib/site.go | 1132 |
1 files changed, 591 insertions, 541 deletions
diff --git a/hugolib/site.go b/hugolib/site.go index 43b398b70..be70db5ee 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -1,4 +1,4 @@ -// Copyright 2017 The Hugo Authors. All rights reserved. +// Copyright 2019 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,59 +22,54 @@ import ( "mime" "net/url" "os" + "path" "path/filepath" "sort" "strconv" "strings" "time" + "github.com/gohugoio/hugo/common/maps" + "github.com/pkg/errors" "github.com/gohugoio/hugo/common/text" - "github.com/gohugoio/hugo/hugofs" - - "github.com/gohugoio/hugo/common/herrors" - "github.com/gohugoio/hugo/common/hugo" - "github.com/gohugoio/hugo/common/maps" "github.com/gohugoio/hugo/publisher" _errors "github.com/pkg/errors" "github.com/gohugoio/hugo/langs" - src "github.com/gohugoio/hugo/source" - - "golang.org/x/sync/errgroup" + "github.com/gohugoio/hugo/resources/page" "github.com/gohugoio/hugo/config" + "github.com/gohugoio/hugo/lazy" + "golang.org/x/sync/errgroup" "github.com/gohugoio/hugo/media" - "github.com/gohugoio/hugo/parser/metadecoders" - - "github.com/markbates/inflect" "github.com/fsnotify/fsnotify" bp "github.com/gohugoio/hugo/bufferpool" "github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/helpers" - "github.com/gohugoio/hugo/hugolib/pagemeta" + "github.com/gohugoio/hugo/navigation" "github.com/gohugoio/hugo/output" "github.com/gohugoio/hugo/related" "github.com/gohugoio/hugo/resources" + "github.com/gohugoio/hugo/resources/page/pagemeta" + "github.com/gohugoio/hugo/resources/resource" "github.com/gohugoio/hugo/source" "github.com/gohugoio/hugo/tpl" + "github.com/spf13/afero" "github.com/spf13/cast" - "github.com/spf13/nitro" "github.com/spf13/viper" ) // used to indicate if run as a test. var testMode bool -var defaultTimer *nitro.B - // Site contains all the information relevant for constructing a static // site. The basic flow of information is as follows: // @@ -93,34 +88,27 @@ var defaultTimer *nitro.B // // 5. The entire collection of files is written to disk. type Site struct { - owner *HugoSites + + // The owning container. When multiple languages, there will be multiple + // sites. + h *HugoSites *PageCollections Taxonomies TaxonomyList - // Plural is what we get in the folder, so keep track of this mapping - // to get the singular form from that value. - taxonomiesPluralSingular map[string]string - - // This is temporary, see https://github.com/gohugoio/hugo/issues/2835 - // Maps "actors-gerard-depardieu" to "Gérard Depardieu" when preserveTaxonomyNames - // is set. - taxonomiesOrigKey map[string]string + taxonomyNodes taxonomyNodeInfos Sections Taxonomy Info SiteInfo - Menus Menus - timer *nitro.B layoutHandler *output.LayoutHandler - draftCount int - futureCount int - expiredCount int + buildStats *buildStats - Data map[string]interface{} - Language *langs.Language + language *langs.Language + + siteCfg siteConfigHolder disabledKinds map[string]bool @@ -137,7 +125,7 @@ type Site struct { outputFormatsConfig output.Formats mediaTypesConfig media.Types - siteConfig SiteConfig + siteConfigConfig SiteConfig // How to handle page front matter. frontmatterHandler pagemeta.FrontMatterHandler @@ -158,23 +146,162 @@ type Site struct { // The func used to title case titles. titleFunc func(s string) string - relatedDocsHandler *relatedDocsHandler + relatedDocsHandler *page.RelatedDocsHandler siteRefLinker - // Set in some tests - shortcodePlaceholderFunc func() string publisher publisher.Publisher + + menus navigation.Menus + + // Shortcut to the home page. Note that this may be nil if + // home page, for some odd reason, is disabled. + home *pageState + + // The last modification date of this site. + lastmod time.Time + + // Lazily loaded site dependencies + init *siteInit +} + +type siteConfigHolder struct { + sitemap config.Sitemap + taxonomiesConfig map[string]string + timeout time.Duration + hasCJKLanguage bool + enableEmoji bool +} + +// Lazily loaded site dependencies. +type siteInit struct { + prevNext *lazy.Init + prevNextInSection *lazy.Init + menus *lazy.Init +} + +func (init *siteInit) Reset() { + init.prevNext.Reset() + init.prevNextInSection.Reset() + init.menus.Reset() +} + +func (s *Site) initInit(init *lazy.Init, pctx pageContext) { + _, err := init.Do() + if err != nil { + s.h.FatalError(pctx.wrapError(err)) + } +} + +func (s *Site) prepareInits() { + s.init = &siteInit{} + + var init lazy.Init + + s.init.prevNext = init.Branch(func() (interface{}, error) { + regularPages := s.findWorkPagesByKind(page.KindPage) + for i, p := range regularPages { + if p.posNextPrev == nil { + continue + } + p.posNextPrev.nextPage = nil + p.posNextPrev.prevPage = nil + + if i > 0 { + p.posNextPrev.nextPage = regularPages[i-1] + } + + if i < len(regularPages)-1 { + p.posNextPrev.prevPage = regularPages[i+1] + } + } + return nil, nil + }) + + s.init.prevNextInSection = init.Branch(func() (interface{}, error) { + var rootSection []int + for i, p1 := range s.workAllPages { + if p1.IsPage() && p1.Section() == "" { + rootSection = append(rootSection, i) + } + if p1.IsSection() && len(p1.SectionsEntries()) <= 1 { + sectionPages := p1.Pages() + for i, p2 := range sectionPages { + p2s := p2.(*pageState) + if p2s.posNextPrevSection == nil { + continue + } + + p2s.posNextPrevSection.nextPage = nil + p2s.posNextPrevSection.prevPage = nil + + if i > 0 { + p2s.posNextPrevSection.nextPage = sectionPages[i-1] + } + + if i < len(sectionPages)-1 { + p2s.posNextPrevSection.prevPage = sectionPages[i+1] + } + } + } + } + + for i, j := range rootSection { + p := s.workAllPages[j] + if i > 0 { + p.posNextPrevSection.nextPage = s.workAllPages[rootSection[i-1]] + } + + if i < len(rootSection)-1 { + p.posNextPrevSection.prevPage = s.workAllPages[rootSection[i+1]] + } + } + + return nil, nil + }) + + s.init.menus = init.Branch(func() (interface{}, error) { + s.assembleMenus() + return nil, nil + }) + +} + +// Build stats for a given site. +type buildStats struct { + draftCount int + futureCount int + expiredCount int +} + +// TODO(bep) consolidate all site stats into this +func (b *buildStats) update(p page.Page) { + if p.Draft() { + b.draftCount++ + } + + if resource.IsFuture(p) { + b.futureCount++ + } + + if resource.IsExpired(p) { + b.expiredCount++ + } } type siteRenderingContext struct { output.Format } +func (s *Site) Menus() navigation.Menus { + s.init.menus.Do() + return s.menus +} + func (s *Site) initRenderFormats() { formatSet := make(map[string]bool) formats := output.Formats{} - for _, p := range s.Pages { - for _, f := range p.outputFormats { + for _, p := range s.workAllPages { + for _, f := range p.m.configuredOutputFormats { if !formatSet[f.Name] { formats = append(formats, f) formatSet[f.Name] = true @@ -182,10 +309,30 @@ func (s *Site) initRenderFormats() { } } + // Add the per kind configured output formats + for _, kind := range allKindsInPages { + if siteFormats, found := s.outputFormats[kind]; found { + for _, f := range siteFormats { + if !formatSet[f.Name] { + formats = append(formats, f) + formatSet[f.Name] = true + } + } + } + } + sort.Sort(formats) s.renderFormats = formats } +func (s *Site) GetRelatedDocsHandler() *page.RelatedDocsHandler { + return s.relatedDocsHandler +} + +func (s *Site) Language() *langs.Language { + return s.language +} + func (s *Site) isEnabled(kind string) bool { if kind == kindUnknown { panic("Unknown kind") @@ -199,19 +346,23 @@ func (s *Site) reset() *Site { layoutHandler: output.NewLayoutHandler(), disabledKinds: s.disabledKinds, titleFunc: s.titleFunc, - relatedDocsHandler: newSearchIndexHandler(s.relatedDocsHandler.cfg), + relatedDocsHandler: s.relatedDocsHandler.Clone(), siteRefLinker: s.siteRefLinker, outputFormats: s.outputFormats, rc: s.rc, outputFormatsConfig: s.outputFormatsConfig, frontmatterHandler: s.frontmatterHandler, mediaTypesConfig: s.mediaTypesConfig, - Language: s.Language, - owner: s.owner, + language: s.language, + h: s.h, publisher: s.publisher, - siteConfig: s.siteConfig, + siteConfigConfig: s.siteConfigConfig, enableInlineShortcodes: s.enableInlineShortcodes, - PageCollections: newPageCollections()} + buildStats: &buildStats{}, + init: s.init, + PageCollections: newPageCollections(), + siteCfg: s.siteCfg, + } } @@ -262,6 +413,8 @@ func newSite(cfg deps.DepsCfg) (*Site, error) { return nil, err } + taxonomies := cfg.Language.GetStringMapString("taxonomies") + var relatedContentConfig related.Config if cfg.Language.IsSet("related") { @@ -271,7 +424,6 @@ func newSite(cfg deps.DepsCfg) (*Site, error) { } } else { relatedContentConfig = related.DefaultConfig - taxonomies := cfg.Language.GetStringMapString("taxonomies") if _, found := taxonomies["tag"]; found { relatedContentConfig.Add(related.IndexConfig{Name: "tags", Weight: 80}) } @@ -284,21 +436,33 @@ func newSite(cfg deps.DepsCfg) (*Site, error) { return nil, err } + siteConfig := siteConfigHolder{ + sitemap: config.DecodeSitemap(config.Sitemap{Priority: -1, Filename: "sitemap.xml"}, cfg.Language.GetStringMap("sitemap")), + taxonomiesConfig: taxonomies, + timeout: time.Duration(cfg.Language.GetInt("timeout")) * time.Millisecond, + hasCJKLanguage: cfg.Language.GetBool("hasCJKLanguage"), + enableEmoji: cfg.Language.Cfg.GetBool("enableEmoji"), + } + s := &Site{ PageCollections: c, layoutHandler: output.NewLayoutHandler(), - Language: cfg.Language, + language: cfg.Language, disabledKinds: disabledKinds, titleFunc: titleFunc, - relatedDocsHandler: newSearchIndexHandler(relatedContentConfig), + relatedDocsHandler: page.NewRelatedDocsHandler(relatedContentConfig), outputFormats: outputFormats, rc: &siteRenderingContext{output.HTMLFormat}, outputFormatsConfig: siteOutputFormatsConfig, mediaTypesConfig: siteMediaTypesConfig, frontmatterHandler: frontMatterHandler, + buildStats: &buildStats{}, enableInlineShortcodes: cfg.Language.GetBool("enableInlineShortcodes"), + siteCfg: siteConfig, } + s.prepareInits() + return s, nil } @@ -372,52 +536,94 @@ func NewSiteForCfg(cfg deps.DepsCfg) (*Site, error) { } -type SiteInfos []*SiteInfo +type SiteInfo struct { + Authors page.AuthorList + Social SiteSocial -// First is a convenience method to get the first Site, i.e. the main language. -func (s SiteInfos) First() *SiteInfo { - if len(s) == 0 { - return nil - } - return s[0] -} + hugoInfo hugo.Info + title string + RSSLink string + Author map[string]interface{} + LanguageCode string + Copyright string + + permalinks map[string]string + + LanguagePrefix string + Languages langs.Languages + + BuildDrafts bool + + canonifyURLs bool + relativeURLs bool + uglyURLs func(p page.Page) bool -type SiteInfo struct { - Taxonomies TaxonomyList - Authors AuthorList - Social SiteSocial - *PageCollections - Menus *Menus - hugoInfo hugo.Info - Title string - RSSLink string - Author map[string]interface{} - LanguageCode string - Copyright string - LastChange time.Time - Permalinks PermalinkOverrides - Params map[string]interface{} - BuildDrafts bool - canonifyURLs bool - relativeURLs bool - uglyURLs func(p *Page) bool - preserveTaxonomyNames bool - Data *map[string]interface{} owner *HugoSites s *Site language *langs.Language - LanguagePrefix string - Languages langs.Languages defaultContentLanguageInSubdir bool sectionPagesMenu string } +func (s *SiteInfo) Pages() page.Pages { + return s.s.Pages() + +} + +func (s *SiteInfo) RegularPages() page.Pages { + return s.s.RegularPages() + +} + +func (s *SiteInfo) AllPages() page.Pages { + return s.s.AllPages() +} + +func (s *SiteInfo) AllRegularPages() page.Pages { + return s.s.AllRegularPages() +} + +func (s *SiteInfo) Permalinks() map[string]string { + // Remove in 0.57 + helpers.Deprecated("Site", ".Permalinks", "", false) + return s.permalinks +} + +func (s *SiteInfo) LastChange() time.Time { + return s.s.lastmod +} + +func (s *SiteInfo) Title() string { + return s.title +} + +func (s *SiteInfo) Site() page.Site { + return s +} + +func (s *SiteInfo) Menus() navigation.Menus { + return s.s.Menus() +} + +// TODO(bep) type +func (s *SiteInfo) Taxonomies() interface{} { + return s.s.Taxonomies +} + +func (s *SiteInfo) Params() map[string]interface{} { + return s.s.Language().Params() +} + +func (s *SiteInfo) Data() map[string]interface{} { + return s.s.h.Data() +} + func (s *SiteInfo) Language() *langs.Language { return s.language } func (s *SiteInfo) Config() SiteConfig { - return s.s.siteConfig + return s.s.siteConfigConfig } func (s *SiteInfo) Hugo() hugo.Info { @@ -425,11 +631,12 @@ func (s *SiteInfo) Hugo() hugo.Info { } // Sites is a convenience method to get all the Hugo sites/languages configured. -func (s *SiteInfo) Sites() SiteInfos { - return s.s.owner.siteInfos() +func (s *SiteInfo) Sites() page.Sites { + return s.s.h.siteInfos() } + func (s *SiteInfo) String() string { - return fmt.Sprintf("Site(%q)", s.Title) + return fmt.Sprintf("Site(%q)", s.title) } func (s *SiteInfo) BaseURL() template.URL { @@ -484,7 +691,7 @@ func (s *SiteInfo) Param(key interface{}) (interface{}, error) { return nil, err } keyStr = strings.ToLower(keyStr) - return s.Params[keyStr], nil + return s.Params()[keyStr], nil } func (s *SiteInfo) IsMultiLingual() bool { @@ -513,28 +720,24 @@ func newSiteRefLinker(cfg config.Provider, s *Site) (siteRefLinker, error) { return siteRefLinker{s: s, errorLogger: logger, notFoundURL: notFoundURL}, nil } -func (s siteRefLinker) logNotFound(ref, what string, p *Page, position text.Position) { +func (s siteRefLinker) logNotFound(ref, what string, p page.Page, position text.Position) { if position.IsValid() { s.errorLogger.Printf("[%s] REF_NOT_FOUND: Ref %q: %s: %s", s.s.Lang(), ref, position.String(), what) } else if p == nil { s.errorLogger.Printf("[%s] REF_NOT_FOUND: Ref %q: %s", s.s.Lang(), ref, what) } else { - s.errorLogger.Printf("[%s] REF_NOT_FOUND: Ref %q from page %q: %s", s.s.Lang(), ref, p.pathOrTitle(), what) + s.errorLogger.Printf("[%s] REF_NOT_FOUND: Ref %q from page %q: %s", s.s.Lang(), ref, p.Path(), what) } } func (s *siteRefLinker) refLink(ref string, source interface{}, relative bool, outputFormat string) (string, error) { - var page *Page - switch v := source.(type) { - case *Page: - page = v - case pageContainer: - page = v.page() + p, err := unwrapPage(source) + if err != nil { + return "", err } var refURL *url.URL - var err error ref = filepath.ToSlash(ref) @@ -544,11 +747,11 @@ func (s *siteRefLinker) refLink(ref string, source interface{}, relative bool, o return s.notFoundURL, err } - var target *Page + var target page.Page var link string if refURL.Path != "" { - target, err := s.s.getPageNew(page, refURL.Path) + target, err := s.s.getPageNew(p, refURL.Path) var pos text.Position if err != nil || target == nil { if p, ok := source.(text.Positioner); ok { @@ -558,12 +761,12 @@ func (s *siteRefLinker) refLink(ref string, source interface{}, relative bool, o } if err != nil { - s.logNotFound(refURL.Path, err.Error(), page, pos) + s.logNotFound(refURL.Path, err.Error(), p, pos) return s.notFoundURL, nil } if target == nil { - s.logNotFound(refURL.Path, "page not found", page, pos) + s.logNotFound(refURL.Path, "page not found", p, pos) return s.notFoundURL, nil } @@ -573,7 +776,7 @@ func (s *siteRefLinker) refLink(ref string, source interface{}, relative bool, o o := target.OutputFormats().Get(outputFormat) if o == nil { - s.logNotFound(refURL.Path, fmt.Sprintf("output format %q", outputFormat), page, pos) + s.logNotFound(refURL.Path, fmt.Sprintf("output format %q", outputFormat), p, pos) return s.notFoundURL, nil } permalinker = o @@ -587,22 +790,24 @@ func (s *siteRefLinker) refLink(ref string, source interface{}, relative bool, o } if refURL.Fragment != "" { + _ = target link = link + "#" + refURL.Fragment - - if refURL.Path != "" && target != nil && !target.getRenderingConfig().PlainIDAnchors { - link = link + ":" + target.UniqueID() - } else if page != nil && !page.getRenderingConfig().PlainIDAnchors { - link = link + ":" + page.UniqueID() + if pctx, ok := target.(pageContext); ok && target.File() != nil && !pctx.getRenderingConfig().PlainIDAnchors { + if refURL.Path != "" { + link = link + ":" + target.File().UniqueID() + } + } else if pctx, ok := p.(pageContext); ok && p.File() != nil && !pctx.getRenderingConfig().PlainIDAnchors { + link = link + ":" + p.File().UniqueID() } - } + } return link, nil } // Ref will give an absolute URL to ref in the given Page. -func (s *SiteInfo) Ref(ref string, page *Page, options ...string) (string, error) { - // Remove in Hugo 0.53 - helpers.Deprecated("Site", ".Ref", "Use .Site.GetPage", false) +func (s *SiteInfo) Ref(ref string, page page.Page, options ...string) (string, error) { + // Remove in Hugo 0.54 + helpers.Deprecated("Site", ".Ref", "Use .Site.GetPage", true) outputFormat := "" if len(options) > 0 { outputFormat = options[0] @@ -612,9 +817,9 @@ func (s *SiteInfo) Ref(ref string, page *Page, options ...string) (string, error } // RelRef will give an relative URL to ref in the given Page. -func (s *SiteInfo) RelRef(ref string, page *Page, options ...string) (string, error) { - // Remove in Hugo 0.53 - helpers.Deprecated("Site", ".RelRef", "Use .Site.GetPage", false) +func (s *SiteInfo) RelRef(ref string, page page.Page, options ...string) (string, error) { + // Remove in Hugo 0.54 + helpers.Deprecated("Site", ".RelRef", "Use .Site.GetPage", true) outputFormat := "" if len(options) > 0 { outputFormat = options[0] @@ -624,22 +829,11 @@ func (s *SiteInfo) RelRef(ref string, page *Page, options ...string) (string, er } func (s *Site) running() bool { - return s.owner != nil && s.owner.running + return s.h != nil && s.h.running } func (s *Site) multilingual() *Multilingual { - return s.owner.multilingual -} - -func init() { - defaultTimer = nitro.Initalize() -} - -func (s *Site) timerStep(step string) { - if s.timer == nil { - s.timer = defaultTimer - } - s.timer.Step(step) + return s.h.multilingual } type whatChanged struct { @@ -737,9 +931,7 @@ func (s *Site) processPartial(events []fsnotify.Event) (whatChanged, error) { s.Log.DEBUG.Printf("Rebuild for events %q", events) - h := s.owner - - s.timerStep("initialize rebuild") + h := s.h // First we need to determine what changed @@ -771,7 +963,6 @@ func (s *Site) processPartial(events []fsnotify.Event) (whatChanged, error) { tmplChanged = append(tmplChanged, ev) if strings.Contains(ev.Name, "shortcodes") { - clearIsInnerShortcodeCache() shortcode := filepath.Base(ev.Name) shortcode = strings.TrimSuffix(shortcode, filepath.Ext(shortcode)) shortcodesChanged[shortcode] = true @@ -788,14 +979,16 @@ func (s *Site) processPartial(events []fsnotify.Event) (whatChanged, error) { } // These in memory resource caches will be rebuilt on demand. - for _, s := range s.owner.Sites { + for _, s := range s.h.Sites { s.ResourceSpec.ResourceCache.DeletePartitions(cachePartitions...) } if len(tmplChanged) > 0 || len(i18nChanged) > 0 { - sites := s.owner.Sites + sites := s.h.Sites first := sites[0] + s.h.init.Reset() + // TOD(bep) globals clean if err := first.Deps.LoadResources(); err != nil { return whatChanged{}, err @@ -805,7 +998,7 @@ func (s *Site) processPartial(events []fsnotify.Event) (whatChanged, error) { site := sites[i] var err error depsCfg := deps.DepsCfg{ - Language: site.Language, + Language: site.language, MediaTypes: site.mediaTypesConfig, OutputFormats: site.outputFormatsConfig, } @@ -817,14 +1010,10 @@ func (s *Site) processPartial(events []fsnotify.Event) (whatChanged, error) { return whatChanged{}, err } } - - s.timerStep("template prep") } if len(dataChanged) > 0 { - if err := s.readDataFromSourceFS(); err != nil { - return whatChanged{}, err - } + s.h.init.data.Reset() } for _, ev := range sourceChanged { @@ -860,7 +1049,7 @@ func (s *Site) processPartial(events []fsnotify.Event) (whatChanged, error) { // pages that keeps a reference to the changed shortcode. pagesWithShortcode := h.findPagesByShortcode(shortcode) for _, p := range pagesWithShortcode { - contentFilesChanged = append(contentFilesChanged, p.File.Filename()) + contentFilesChanged = append(contentFilesChanged, p.File().Filename()) } } @@ -891,193 +1080,72 @@ func (s *Site) processPartial(events []fsnotify.Event) (whatChanged, error) { } -func (s *Site) loadData(fs afero.Fs) (err error) { - spec := src.NewSourceSpec(s.PathSpec, fs) - fileSystem := spec.NewFilesystem("") - s.Data = make(map[string]interface{}) - for _, r := range fileSystem.Files() { - if err := s.handleDataFile(r); err != nil { - return err - } +func (s *Site) process(config BuildCfg) (err error) { + if err = s.initialize(); err != nil { + return } - - return -} - -func (s *Site) errWithFileContext(err error, f source.File) error { - rfi, ok := f.FileInfo().(hugofs.RealFilenameInfo) - if !ok { + if err := s.readAndProcessContent(); err != nil { return err } - - realFilename := rfi.RealFilename() - - err, _ = herrors.WithFileContextForFile( - err, - realFilename, - realFilename, - s.SourceSpec.Fs.Source, - herrors.SimpleLineMatcher) - return err -} -func (s *Site) handleDataFile(r source.ReadableFile) error { - var current map[string]interface{} - - f, err := r.Open() - if err != nil { - return _errors.Wrapf(err, "Failed to open data file %q:", r.LogicalName()) - } - defer f.Close() - - // Crawl in data tree to insert data - current = s.Data - keyParts := strings.Split(r.Dir(), helpers.FilePathSeparator) - // The first path element is the virtual folder (typically theme name), which is - // not part of the key. - if len(keyParts) > 1 { - for _, key := range keyParts[1:] { - if key != "" { - if _, ok := current[key]; !ok { - current[key] = make(map[string]interface{}) - } - current = current[key].(map[string]interface{}) - } - } - } - - data, err := s.readData(r) - if err != nil { - return s.errWithFileContext(err, r) - } - - if data == nil { - return nil - } - - // filepath.Walk walks the files in lexical order, '/' comes before '.' - // this warning could happen if - // 1. A theme uses the same key; the main data folder wins - // 2. A sub folder uses the same key: the sub folder wins - higherPrecedentData := current[r.BaseFileName()] - - switch data.(type) { - case nil: - // hear the crickets? - - case map[string]interface{}: - - switch higherPrecedentData.(type) { - case nil: - current[r.BaseFileName()] = data - case map[string]interface{}: - // merge maps: insert entries from data for keys that - // don't already exist in higherPrecedentData - higherPrecedentMap := higherPrecedentData.(map[string]interface{}) - for key, value := range data.(map[string]interface{}) { - if _, exists := higherPrecedentMap[key]; exists { - s.Log.WARN.Printf("Data for key '%s' in path '%s' is overridden by higher precedence data already in the data tree", key, r.Path()) - } else { - higherPrecedentMap[key] = value - } - } - default: - // can't merge: higherPrecedentData is not a map - s.Log.WARN.Printf("The %T data from '%s' overridden by "+ - "higher precedence %T data already in the data tree", data, r.Path(), higherPrecedentData) - } - - case []interface{}: - if higherPrecedentData == nil { - current[r.BaseFileName()] = data - } else { - // we don't merge array data - s.Log.WARN.Printf("The %T data from '%s' overridden by "+ - "higher precedence %T data already in the data tree", data, r.Path(), higherPrecedentData) - } - - default: - s.Log.ERROR.Printf("unexpected data type %T in file %s", data, r.LogicalName()) - } - - return nil } -func (s *Site) readData(f source.ReadableFile) (interface{}, error) { - file, err := f.Open() - if err != nil { - return nil, _errors.Wrap(err, "readData: failed to open data file") +func (s *Site) setupSitePages() { + var homeDates *resource.Dates + if s.home != nil { + // If the home page has no dates set, we fall back to the site dates. + homeDates = &s.home.m.Dates } - defer file.Close() - content := helpers.ReaderToBytes(file) - - format := metadecoders.FormatFromString(f.Extension()) - return metadecoders.Default.Unmarshal(content, format) -} -func (s *Site) readDataFromSourceFS() error { - err := s.loadData(s.PathSpec.BaseFs.Data.Fs) - s.timerStep("load data") - return err -} - -func (s *Site) process(config BuildCfg) (err error) { - if err = s.initialize(); err != nil { + if !s.lastmod.IsZero() && (homeDates == nil || !resource.IsZeroDates(homeDates)) { return } - s.timerStep("initialize") - if err = s.readDataFromSourceFS(); err != nil { + if homeDates != nil && !s.lastmod.IsZero() { + homeDates.FDate = s.lastmod + homeDates.FLastmod = s.lastmod return - } - - s.timerStep("load i18n") - if err := s.readAndProcessContent(); err != nil { - return err } - s.timerStep("read and convert pages from source") - return err + var siteLastmod time.Time + var siteLastDate time.Time -} - -func (s *Site) setupSitePages() { - var siteLastChange time.Time - - for i, page := range s.RegularPages { - if i > 0 { - page.NextPag |