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/page__new.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/page__new.go')
-rw-r--r-- | hugolib/page__new.go | 291 |
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() + +} |