summaryrefslogtreecommitdiffstats
path: root/hugolib/page__new.go
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2019-01-02 12:33:26 +0100
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2019-03-23 18:51:22 +0100
commit597e418cb02883418f2cebb41400e8e61413f651 (patch)
tree177ad9c540b2583b6dab138c9f0490d28989c7f7 /hugolib/page__new.go
parent44f5c1c14cb1f42cc5f01739c289e9cfc83602af (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/page__new.go')
-rw-r--r--hugolib/page__new.go291
1 files changed, 291 insertions, 0 deletions
diff --git a/hugolib/page__new.go b/hugolib/page__new.go
new file mode 100644
index 000000000..0f419b5da
--- /dev/null
+++ b/hugolib/page__new.go
@@ -0,0 +1,291 @@
+// 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.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hugolib
+
+import (
+ "html/template"
+ "strings"
+
+ "github.com/gohugoio/hugo/common/hugo"
+
+ "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/source"
+
+ "github.com/gohugoio/hugo/parser/pageparser"
+ "github.com/pkg/errors"
+
+ "github.com/gohugoio/hugo/output"
+
+ "github.com/gohugoio/hugo/lazy"
+
+ "github.com/gohugoio/hugo/resources/page"
+ "github.com/gohugoio/hugo/resources/resource"
+)
+
+func newPageBase(metaProvider *pageMeta) (*pageState, error) {
+ if metaProvider.s == nil {
+ panic("must provide a Site")
+ }
+
+ s := metaProvider.s
+
+ ps := &pageState{
+ pageOutput: nopPageOutput,
+ pageCommon: &pageCommon{
+ FileProvider: metaProvider,
+ AuthorProvider: metaProvider,
+ Scratcher: maps.NewScratcher(),
+ Positioner: page.NopPage,
+ InSectionPositioner: page.NopPage,
+ ResourceMetaProvider: metaProvider,
+ ResourceParamsProvider: metaProvider,
+ PageMetaProvider: metaProvider,
+ RelatedKeywordsProvider: metaProvider,
+ OutputFormatsProvider: page.NopPage,
+ ResourceTypesProvider: pageTypesProvider,
+ RefProvider: page.NopPage,
+ ShortcodeInfoProvider: page.NopPage,
+ LanguageProvider: s,
+
+ InternalDependencies: s,
+ init: lazy.New(),
+ m: metaProvider,
+ s: s},
+ }
+
+ siteAdapter := pageSiteAdapter{s: s, p: ps}
+
+ deprecatedWarningPage := struct {
+ source.FileWithoutOverlap
+ page.DeprecatedWarningPageMethods1
+ }{
+ FileWithoutOverlap: metaProvider.File(),
+ DeprecatedWarningPageMethods1: &pageDeprecatedWarning{p: ps},
+ }
+
+ ps.DeprecatedWarningPageMethods = page.NewDeprecatedWarningPage(deprecatedWarningPage)
+ ps.pageMenus = &pageMenus{p: ps}
+ ps.PageMenusProvider = ps.pageMenus
+ ps.GetPageProvider = siteAdapter
+ ps.GitInfoProvider = ps
+ ps.TranslationsProvider = ps
+ ps.ResourceDataProvider = &pageData{pageState: ps}
+ ps.RawContentProvider = ps
+ ps.ChildCareProvider = ps
+ ps.TreeProvider = pageTree{p: ps}
+ ps.Eqer = ps
+ ps.TranslationKeyProvider = ps
+ ps.ShortcodeInfoProvider = ps
+ ps.PageRenderProvider = ps
+ ps.AlternativeOutputFormatsProvider = ps
+
+ return ps, nil
+
+}
+
+func newPageFromMeta(metaProvider *pageMeta) (*pageState, error) {
+ ps, err := newPageBase(metaProvider)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := metaProvider.applyDefaultValues(); err != nil {
+ return nil, err
+ }
+
+ ps.init.Add(func() (interface{}, error) {
+ pp, err := newPagePaths(metaProvider.s, ps, metaProvider)
+ if err != nil {
+ return nil, err
+ }
+
+ makeOut := func(f output.Format, render bool) *pageOutput {
+ return newPageOutput(nil, ps, pp, f, render)
+ }
+
+ if ps.m.standalone {
+ ps.pageOutput = makeOut(ps.m.outputFormats()[0], true)
+ } else {
+ ps.pageOutputs = make([]*pageOutput, len(ps.s.h.renderFormats))
+ created := make(map[string]*pageOutput)
+ outputFormatsForPage := ps.m.outputFormats()
+ for i, f := range ps.s.h.renderFormats {
+ po, found := created[f.Name]
+ if !found {
+ _, shouldRender := outputFormatsForPage.GetByName(f.Name)
+ po = makeOut(f, shouldRender)
+ created[f.Name] = po
+ }
+ ps.pageOutputs[i] = po
+ }
+ }
+
+ if err := ps.initCommonProviders(pp); err != nil {
+ return nil, err
+ }
+
+ return nil, nil
+
+ })
+
+ return ps, err
+
+}
+
+// Used by the legacy 404, sitemap and robots.txt rendering
+func newPageStandalone(m *pageMeta, f output.Format) (*pageState, error) {
+ m.configuredOutputFormats = output.Formats{f}
+ m.standalone = true
+ p, err := newPageFromMeta(m)
+
+ if err != nil {
+ return nil, err
+ }
+
+ if err := p.initPage(); err != nil {
+ return nil, err
+ }
+
+ return p, nil
+
+}
+
+func newPageWithContent(f *fileInfo, s *Site, content resource.OpenReadSeekCloser) (*pageState, error) {
+ sections := s.sectionsFromFile(f)
+ kind := s.kindFromFileInfoOrSections(f, sections)
+ if kind == page.KindTaxonomy {
+ s.PathSpec.MakePathsSanitized(sections)
+ }
+
+ metaProvider := &pageMeta{kind: kind, sections: sections, s: s, f: f}
+
+ ps, err := newPageBase(metaProvider)
+ if err != nil {
+ return nil, err
+ }
+
+ gi, err := s.h.gitInfoForPage(ps)
+ if err != nil {
+ return nil, errors.Wrap(err, "failed to load Git data")
+ }
+ ps.gitInfo = gi
+
+ r, err := content()
+ if err != nil {
+ return nil, err
+ }
+ defer r.Close()
+
+ parseResult, err := pageparser.Parse(
+ r,
+ pageparser.Config{EnableEmoji: s.siteCfg.enableEmoji},
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ ps.pageContent = pageContent{
+ source: rawPageContent{
+ parsed: parseResult,
+ posMainContent: -1,
+ posSummaryEnd: -1,
+ posBodyStart: -1,
+ },
+ }
+
+ ps.shortcodeState = newShortcodeHandler(ps, ps.s, nil)
+
+ if err := ps.mapContent(metaProvider); err != nil {
+ return nil, ps.wrapError(err)
+ }
+
+ if err := metaProvider.applyDefaultValues(); err != nil {
+ return nil, err
+ }
+
+ ps.init.Add(func() (interface{}, error) {
+ reuseContent := ps.renderable && !ps.shortcodeState.hasShortcodes()
+
+ // Creates what's needed for each output format.
+ contentPerOutput := newPageContentOutput(ps)
+
+ pp, err := newPagePaths(s, ps, metaProvider)
+ if err != nil {
+ return nil, err
+ }
+
+ // Prepare output formats for all sites.
+ ps.pageOutputs = make([]*pageOutput, len(ps.s.h.renderFormats))
+ created := make(map[string]*pageOutput)
+ outputFormatsForPage := ps.m.outputFormats()
+
+ for i, f := range ps.s.h.renderFormats {
+ if po, found := created[f.Name]; found {
+ ps.pageOutputs[i] = po
+ continue
+ }
+
+ _, render := outputFormatsForPage.GetByName(f.Name)
+ var contentProvider *pageContentOutput
+ if reuseContent && i > 0 {
+ contentProvider = ps.pageOutputs[0].cp
+ } else {
+ var err error
+ contentProvider, err = contentPerOutput(f)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ po := newPageOutput(contentProvider, ps, pp, f, render)
+ ps.pageOutputs[i] = po
+ created[f.Name] = po
+ }
+
+ if err := ps.initCommonProviders(pp); err != nil {
+ return nil, err
+ }
+
+ return nil, nil
+ })
+
+ return ps, nil
+}
+
+type pageDeprecatedWarning struct {
+ p *pageState
+}
+
+func (p *pageDeprecatedWarning) IsDraft() bool { return p.p.m.draft }
+func (p *pageDeprecatedWarning) Hugo() hugo.Info { return p.p.s.Info.Hugo() }
+func (p *pageDeprecatedWarning) LanguagePrefix() string { return p.p.s.Info.LanguagePrefix }
+func (p *pageDeprecatedWarning) GetParam(key string) interface{} {
+ return p.p.m.params[strings.ToLower(key)]
+}
+func (p *pageDeprecatedWarning) RSSLink() template.URL {
+ f := p.p.OutputFormats().Get("RSS")
+ if f == nil {
+ return ""
+ }
+ return template.URL(f.Permalink())
+}
+func (p *pageDeprecatedWarning) URL() string {
+ if p.p.IsPage() && p.p.m.urlPaths.URL != "" {
+ // This is the url set in front matter
+ return p.p.m.urlPaths.URL
+ }
+ // Fall back to the relative permalink.
+ return p.p.RelPermalink()
+
+}