diff options
-rw-r--r-- | hugolib/disableKinds_test.go | 12 | ||||
-rw-r--r-- | hugolib/hugo_sites_build_test.go | 30 | ||||
-rw-r--r-- | hugolib/hugo_sites_multihost_test.go | 10 | ||||
-rw-r--r-- | hugolib/language_content_dir_test.go | 2 | ||||
-rw-r--r-- | hugolib/page.go | 44 | ||||
-rw-r--r-- | hugolib/page_bundler_test.go | 50 | ||||
-rw-r--r-- | hugolib/page_collections.go | 177 | ||||
-rw-r--r-- | hugolib/page_collections_test.go | 25 | ||||
-rw-r--r-- | hugolib/pages_language_merge_test.go | 4 | ||||
-rw-r--r-- | hugolib/shortcode_test.go | 2 | ||||
-rw-r--r-- | hugolib/site.go | 25 | ||||
-rw-r--r-- | hugolib/site_output_test.go | 4 | ||||
-rw-r--r-- | hugolib/site_sections_test.go | 29 | ||||
-rw-r--r-- | hugolib/site_test.go | 19 | ||||
-rw-r--r-- | hugolib/site_url_test.go | 6 | ||||
-rw-r--r-- | hugolib/taxonomy_test.go | 12 |
16 files changed, 296 insertions, 155 deletions
diff --git a/hugolib/disableKinds_test.go b/hugolib/disableKinds_test.go index edada1419..d94307e46 100644 --- a/hugolib/disableKinds_test.go +++ b/hugolib/disableKinds_test.go @@ -130,7 +130,7 @@ func assertDisabledKinds(th testHelper, s *Site, disabled ...string) { }, disabled, KindPage, "public/sect/p1/index.html", "Single|P1") assertDisabledKind(th, func(isDisabled bool) bool { - p := s.getPage(KindHome) + p, _ := s.getPage(nil, "/") if isDisabled { return p == nil } @@ -138,7 +138,7 @@ func assertDisabledKinds(th testHelper, s *Site, disabled ...string) { }, disabled, KindHome, "public/index.html", "Home") assertDisabledKind(th, func(isDisabled bool) bool { - p := s.getPage(KindSection, "sect") + p, _ := s.getPage(nil, "sect") if isDisabled { return p == nil } @@ -146,7 +146,7 @@ func assertDisabledKinds(th testHelper, s *Site, disabled ...string) { }, disabled, KindSection, "public/sect/index.html", "Sects") assertDisabledKind(th, func(isDisabled bool) bool { - p := s.getPage(KindTaxonomy, "tags", "tag1") + p, _ := s.getPage(nil, "tags/tag1") if isDisabled { return p == nil @@ -156,7 +156,7 @@ func assertDisabledKinds(th testHelper, s *Site, disabled ...string) { }, disabled, KindTaxonomy, "public/tags/tag1/index.html", "Tag1") assertDisabledKind(th, func(isDisabled bool) bool { - p := s.getPage(KindTaxonomyTerm, "tags") + p, _ := s.getPage(nil, "tags") if isDisabled { return p == nil } @@ -165,7 +165,7 @@ func assertDisabledKinds(th testHelper, s *Site, disabled ...string) { }, disabled, KindTaxonomyTerm, "public/tags/index.html", "Tags") assertDisabledKind(th, func(isDisabled bool) bool { - p := s.getPage(KindTaxonomyTerm, "categories") + p, _ := s.getPage(nil, "categories") if isDisabled { return p == nil @@ -175,7 +175,7 @@ func assertDisabledKinds(th testHelper, s *Site, disabled ...string) { }, disabled, KindTaxonomyTerm, "public/categories/index.html", "Category Terms") assertDisabledKind(th, func(isDisabled bool) bool { - p := s.getPage(KindTaxonomy, "categories", "hugo") + p, _ := s.getPage(nil, "categories/hugo") if isDisabled { return p == nil } diff --git a/hugolib/hugo_sites_build_test.go b/hugolib/hugo_sites_build_test.go index 4c32fa2f6..34b67be5e 100644 --- a/hugolib/hugo_sites_build_test.go +++ b/hugolib/hugo_sites_build_test.go @@ -186,12 +186,12 @@ p1 = "p1en" assert.Len(sites, 2) nnSite := sites[0] - nnHome := nnSite.getPage(KindHome) + nnHome, _ := nnSite.getPage(nil, "/") assert.Len(nnHome.AllTranslations(), 2) assert.Len(nnHome.Translations(), 1) assert.True(nnHome.IsTranslated()) - enHome := sites[1].getPage(KindHome) + enHome, _ := sites[1].getPage(nil, "/") p1, err := enHome.Param("p1") assert.NoError(err) @@ -242,7 +242,7 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) { require.Nil(t, gp2) enSite := sites[0] - enSiteHome := enSite.getPage(KindHome) + enSiteHome, _ := enSite.getPage(nil, "/") require.True(t, enSiteHome.IsTranslated()) require.Equal(t, "en", enSite.Language.Lang) @@ -310,10 +310,10 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) { // isn't ideal in a multilingual setup. You want a way to get the current language version if available. // Now you can do lookups with translation base name to get that behaviour. // Let us test all the regular page variants: - getPageDoc1En := enSite.getPage(KindPage, filepath.ToSlash(doc1en.Path())) - getPageDoc1EnBase := enSite.getPage(KindPage, "sect/doc1") - getPageDoc1Fr := frSite.getPage(KindPage, filepath.ToSlash(doc1fr.Path())) - getPageDoc1FrBase := frSite.getPage(KindPage, "sect/doc1") + getPageDoc1En, _ := enSite.getPage(nil, filepath.ToSlash(doc1en.Path())) + getPageDoc1EnBase, _ := enSite.getPage(nil, "sect/doc1") + getPageDoc1Fr, _ := frSite.getPage(nil, filepath.ToSlash(doc1fr.Path())) + getPageDoc1FrBase, _ := frSite.getPage(nil, "sect/doc1") require.Equal(t, doc1en, getPageDoc1En) require.Equal(t, doc1fr, getPageDoc1Fr) require.Equal(t, doc1en, getPageDoc1EnBase) @@ -331,7 +331,7 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) { b.AssertFileContent("public/en/sect/doc1-slug/index.html", "Single", "Shortcode: Hello", "LingoDefault") // Check node translations - homeEn := enSite.getPage(KindHome) + homeEn, _ := enSite.getPage(nil, "/") require.NotNil(t, homeEn) require.Len(t, homeEn.Translations(), 3) require.Equal(t, "fr", homeEn.Translations()[0].Lang()) @@ -341,7 +341,7 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) { require.Equal(t, "På bokmål", homeEn.Translations()[2].title, configSuffix) require.Equal(t, "Bokmål", homeEn.Translations()[2].Language().LanguageName, configSuffix) - sectFr := frSite.getPage(KindSection, "sect") + sectFr, _ := frSite.getPage(nil, "sect") require.NotNil(t, sectFr) require.Equal(t, "fr", sectFr.Lang()) @@ -351,12 +351,12 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) { nnSite := sites[2] require.Equal(t, "nn", nnSite.Language.Lang) - taxNn := nnSite.getPage(KindTaxonomyTerm, "lag") + taxNn, _ := nnSite.getPage(nil, "lag") require.NotNil(t, taxNn) require.Len(t, taxNn.Translations(), 1) require.Equal(t, "nb", taxNn.Translations()[0].Lang()) - taxTermNn := nnSite.getPage(KindTaxonomy, "lag", "sogndal") + taxTermNn, _ := nnSite.getPage(nil, "lag/sogndal") require.NotNil(t, taxTermNn) require.Len(t, taxTermNn.Translations(), 1) require.Equal(t, "nb", taxTermNn.Translations()[0].Lang()) @@ -411,7 +411,7 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) { } // Check bundles - bundleFr := frSite.getPage(KindPage, "bundles/b1/index.md") + bundleFr, _ := frSite.getPage(nil, "bundles/b1/index.md") require.NotNil(t, bundleFr) require.Equal(t, "/blog/fr/bundles/b1/", bundleFr.RelPermalink()) require.Equal(t, 1, len(bundleFr.Resources)) @@ -420,7 +420,7 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) { require.Equal(t, "/blog/fr/bundles/b1/logo.png", logoFr.RelPermalink()) b.AssertFileContent("public/fr/bundles/b1/logo.png", "PNG Data") - bundleEn := enSite.getPage(KindPage, "bundles/b1/index.en.md") + bundleEn, _ := enSite.getPage(nil, "bundles/b1/index.en.md") require.NotNil(t, bundleEn) require.Equal(t, "/blog/en/bundles/b1/", bundleEn.RelPermalink()) require.Equal(t, 1, len(bundleEn.Resources)) @@ -582,7 +582,7 @@ func TestMultiSitesRebuild(t *testing.T) { docFr := readDestination(t, fs, "public/fr/sect/doc1/index.html") require.True(t, strings.Contains(docFr, "Salut"), "No Salut") - homeEn := enSite.getPage(KindHome) + homeEn, _ := enSite.getPage(nil, "/") require.NotNil(t, homeEn) assert.Len(homeEn.Translations(), 3) require.Equal(t, "fr", homeEn.Translations()[0].Lang()) @@ -678,7 +678,7 @@ title = "Svenska" require.True(t, svSite.Language.Lang == "sv", svSite.Language.Lang) require.True(t, frSite.Language.Lang == "fr", frSite.Language.Lang) - homeEn := enSite.getPage(KindHome) + homeEn, _ := enSite.getPage(nil, "/") require.NotNil(t, homeEn) require.Len(t, homeEn.Translations(), 4) require.Equal(t, "sv", homeEn.Translations()[0].Lang()) diff --git a/hugolib/hugo_sites_multihost_test.go b/hugolib/hugo_sites_multihost_test.go index 4062868e6..736c6d4f0 100644 --- a/hugolib/hugo_sites_multihost_test.go +++ b/hugolib/hugo_sites_multihost_test.go @@ -55,7 +55,7 @@ languageName = "Nynorsk" s1 := b.H.Sites[0] - s1h := s1.getPage(KindHome) + s1h, _ := s1.getPage(nil, "/") assert.True(s1h.IsTranslated()) assert.Len(s1h.Translations(), 2) assert.Equal("https://example.com/docs/", s1h.Permalink()) @@ -66,7 +66,7 @@ languageName = "Nynorsk" // For multihost, we never want any content in the root. // // check url in front matter: - pageWithURLInFrontMatter := s1.getPage(KindPage, "sect/doc3.en.md") + pageWithURLInFrontMatter, _ := s1.getPage(nil, "sect/doc3.en.md") assert.NotNil(pageWithURLInFrontMatter) assert.Equal("/superbob", pageWithURLInFrontMatter.URL()) assert.Equal("/docs/superbob/", pageWithURLInFrontMatter.RelPermalink()) @@ -78,7 +78,7 @@ languageName = "Nynorsk" s2 := b.H.Sites[1] - s2h := s2.getPage(KindHome) + s2h, _ := s2.getPage(nil, "/") assert.Equal("https://example.fr/", s2h.Permalink()) b.AssertFileContent("public/fr/index.html", "French Home Page") @@ -92,7 +92,7 @@ languageName = "Nynorsk" // Check bundles - bundleEn := s1.getPage(KindPage, "bundles/b1/index.en.md") + bundleEn, _ := s1.getPage(nil, "bundles/b1/index.en.md") require.NotNil(t, bundleEn) require.Equal(t, "/docs/bundles/b1/", bundleEn.RelPermalink()) require.Equal(t, 1, len(bundleEn.Resources)) @@ -101,7 +101,7 @@ languageName = "Nynorsk" require.Equal(t, "/docs/bundles/b1/logo.png", logoEn.RelPermalink()) b.AssertFileContent("public/en/bundles/b1/logo.png", "PNG Data") - bundleFr := s2.getPage(KindPage, "bundles/b1/index.md") + bundleFr, _ := s2.getPage(nil, "bundles/b1/index.md") require.NotNil(t, bundleFr) require.Equal(t, "/bundles/b1/", bundleFr.RelPermalink()) require.Equal(t, 1, len(bundleFr.Resources)) diff --git a/hugolib/language_content_dir_test.go b/hugolib/language_content_dir_test.go index 7195e8e7b..73119d3db 100644 --- a/hugolib/language_content_dir_test.go +++ b/hugolib/language_content_dir_test.go @@ -244,7 +244,7 @@ Content. b.AssertFileContent("/my/project/public/sv/sect/mybundle/logo.png", "PNG Data") b.AssertFileContent("/my/project/public/nn/sect/mybundle/logo.png", "PNG Data") - nnSect := nnSite.getPage(KindSection, "sect") + nnSect, _ := nnSite.getPage(nil, "sect") assert.NotNil(nnSect) assert.Equal(12, len(nnSect.Pages)) nnHome, _ := nnSite.Info.Home() diff --git a/hugolib/page.go b/hugolib/page.go index 564524825..710bca3f1 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -1874,6 +1874,28 @@ func (p *Page) FullFilePath() string { return filepath.Join(p.Dir(), p.LogicalName()) } +// Returns the canonical, absolute fully-qualifed logical reference used by +// methods such as GetPage and ref/relref shortcodes to unambiguously refer to +// this page. As an absolute path, it is prefixed with a "/". +// +// For pages that have a backing file in the content directory, it is returns +// the path to this file as an absolute path rooted in the content dir. For +// pages or nodes that do not, it returns the virtual path, consistent with +// where you would add a backing content file. +// +// The "/" prefix and support for pages without backing files should be the +// only difference with FullFilePath() +func (p *Page) absoluteSourceRef() string { + sourcePath := p.Source.Path() + if sourcePath != "" { + return "/" + filepath.ToSlash(sourcePath) + } else if len(p.sections) > 0 { + // no backing file, return the virtual source path + return "/" + path.Join(p.sections...) + } + return "" +} + // Pre render prepare steps func (p *Page) prepareLayouts() error { @@ -2007,14 +2029,23 @@ func (p *Page) Hugo() *HugoInfo { return hugoInfo } +// GetPage looks up a page for the given ref. +// {{ with .GetPage "blog" }}{{ .Title }}{{ end }} +// +// This will return nil when no page could be found, and will return +// an error if the ref is ambiguous. +func (p *Page) GetPage(ref string) (*Page, error) { + return p.s.getPage(p, ref) +} + func (p *Page) Ref(refs ...string) (string, error) { if len(refs) == 0 { return "", nil } if len(refs) > 1 { - return p.Site.Ref(refs[0], nil, refs[1]) + return p.Site.Ref(refs[0], p, refs[1]) } - return p.Site.Ref(refs[0], nil) + return p.Site.Ref(refs[0], p) } func (p *Page) RelRef(refs ...string) (string, error) { @@ -2022,17 +2053,16 @@ func (p *Page) RelRef(refs ...string) (string, error) { return "", nil } if len(refs) > 1 { - return p.Site.RelRef(refs[0], nil, refs[1]) + return p.Site.RelRef(refs[0], p, refs[1]) } - return p.Site.RelRef(refs[0], nil) + return p.Site.RelRef(refs[0], p) } func (p *Page) String() string { - if p.Path() != "" { - return fmt.Sprintf("Page(%s)", p.Path()) + if p.absoluteSourceRef() != "" { + return fmt.Sprintf("Page(%s)", p.absoluteSourceRef()) } return fmt.Sprintf("Page(%q)", p.title) - } // Scratch returns the writable context associated with this Page. diff --git a/hugolib/page_bundler_test.go b/hugolib/page_bundler_test.go index 811dbf56f..f12b7d24f 100644 --- a/hugolib/page_bundler_test.go +++ b/hugolib/page_bundler_test.go @@ -48,6 +48,7 @@ func TestPageBundlerSiteRegular(t *testing.T) { for _, ugly := range []bool{false, true} { t.Run(fmt.Sprintf("ugly=%t", ugly), func(t *testing.T) { + var samePage *Page assert := require.New(t) fs, cfg := newTestBundleSources(t) @@ -83,12 +84,14 @@ func TestPageBundlerSiteRegular(t *testing.T) { assert.Len(s.RegularPages, 8) - singlePage := s.getPage(KindPage, "a/1.md") + singlePage, _ := s.getPage(nil, "a/1.md") assert.Equal("", singlePage.BundleType()) assert.NotNil(singlePage) - assert.Equal(singlePage, s.getPage("page", "a/1")) - assert.Equal(singlePage, s.getPage("page", "1")) + samePage, _ = s.getPage(nil, "a/1") + assert.Equal(singlePage, samePage) + samePage, _ = s.getPage(nil, "1") + assert.Equal(singlePage, samePage) assert.Contains(singlePage.content(), "TheContent") @@ -106,18 +109,18 @@ func TestPageBundlerSiteRegular(t *testing.T) { // This should be just copied to destination. th.assertFileContent(filepath.FromSlash("/work/public/assets/pic1.png"), "content") - leafBundle1 := s.getPage(KindPage, "b/my-bundle/index.md") + leafBundle1, _ := s.getPage(nil, "b/my-bundle/index.md") assert.NotNil(leafBundle1) assert.Equal("leaf", leafBundle1.BundleType()) assert.Equal("b", leafBundle1.Section()) - sectionB := s.getPage(KindSection, "b") + sectionB, _ := s.getPage(nil, "/b") assert.NotNil(sectionB) home, _ := s.Info.Home() assert.Equal("branch", home.BundleType()) // This is a root bundle and should live in the "home section" // See https://github.com/gohugoio/hugo/issues/4332 - rootBundle := s.getPage(KindPage, "root") + rootBundle, _ := s.getPage(nil, "root") assert.NotNil(rootBundle) assert.True(rootBundle.Parent().IsHome()) if ugly { @@ -126,9 +129,9 @@ func TestPageBundlerSiteRegular(t *testing.T) { assert.Equal("/root/", rootBundle.RelPermalink()) } - leafBundle2 := s.getPage(KindPage, "a/b/index.md") + leafBundle2, _ := s.getPage(nil, "a/b/index.md") assert.NotNil(leafBundle2) - unicodeBundle := s.getPage(KindPage, "c/bundle/index.md") + unicodeBundle, _ := s.getPage(nil, "c/bundle/index.md") assert.NotNil(unicodeBundle) pageResources := leafBundle1.Resources.ByType(pageResourceType) @@ -211,6 +214,7 @@ func TestPageBundlerSiteMultilingual(t *testing.T) { for _, ugly := range []bool{false, true} { t.Run(fmt.Sprintf("ugly=%t", ugly), func(t *testing.T) { + var samePage *Page assert := require.New(t) fs, cfg := newTestBundleSourcesMultilingual(t) @@ -230,7 +234,7 @@ func TestPageBundlerSiteMultilingual(t *testing.T) { assert.Equal(16, len(s.Pages)) assert.Equal(31, len(s.AllPages)) - bundleWithSubPath := s.getPage(KindPage, "lb/index") + bundleWithSubPath, _ := s.getPage(nil, "lb/index") assert.NotNil(bundleWithSubPath) // See https://github.com/gohugoio/hugo/issues/4312 @@ -244,22 +248,28 @@ func TestPageBundlerSiteMultilingual(t *testing.T) { // and probably also just b (aka "my-bundle") // These may also be translated, so we also need to test that. // "bf", "my-bf-bundle", "index.md + nn - bfBundle := s.getPage(KindPage, "bf/my-bf-bundle/index") + bfBundle, _ := s.getPage(nil, "bf/my-bf-bundle/index") assert.NotNil(bfBundle) assert.Equal("en", bfBundle.Lang()) - assert.Equal(bfBundle, s.getPage(KindPage, "bf/my-bf-bundle/index.md")) - assert.Equal(bfBundle, s.getPage(KindPage, "bf/my-bf-bundle")) - assert.Equal(bfBundle, s.getPage(KindPage, "my-bf-bundle")) + samePage, _ = s.getPage(nil, "bf/my-bf-bundle/index.md") + assert.Equal(bfBundle, samePage) + samePage, _ = s.getPage(nil, "bf/my-bf-bundle") + assert.Equal(bfBundle, samePage) + samePage, _ = s.getPage(nil, "my-bf-bundle") + assert.Equal(bfBundle, samePage) nnSite := sites.Sites[1] assert.Equal(7, len(nnSite.RegularPages)) - bfBundleNN := nnSite.getPage(KindPage, "bf/my-bf-bundle/index") + bfBundleNN, _ := nnSite.getPage(nil, "bf/my-bf-bundle/index") assert.NotNil(bfBundleNN) assert.Equal("nn", bfBundleNN.Lang()) - assert.Equal(bfBundleNN, nnSite.getPage(KindPage, "bf/my-bf-bundle/index.nn.md")) - assert.Equal(bfBundleNN, nnSite.getPage(KindPage, "bf/my-bf-bundle")) - assert.Equal(bfBundleNN, nnSite.getPage(KindPage, "my-bf-bundle")) + samePage, _ = nnSite.getPage(nil, "bf/my-bf-bundle/index.nn.md") + assert.Equal(bfBundleNN, samePage) + samePage, _ = nnSite.getPage(nil, "bf/my-bf-bundle") + assert.Equal(bfBundleNN, samePage) + samePage, _ = nnSite.getPage(nil, "my-bf-bundle") + assert.Equal(bfBundleNN, samePage) // See https://github.com/gohugoio/hugo/issues/4295 // Every resource should have its Name prefixed with its base folder. @@ -334,7 +344,7 @@ func TestPageBundlerSiteWitSymbolicLinksInContent(t *testing.T) { th := testHelper{s.Cfg, s.Fs, t} assert.Equal(7, len(s.RegularPages)) - a1Bundle := s.getPage(KindPage, "symbolic2/a1/index.md") + a1Bundle, _ := s.getPage(nil, "symbolic2/a1/index.md") assert.NotNil(a1Bundle) assert.Equal(2, len(a1Bundle.Resources)) assert.Equal(1, len(a1Bundle.Resources.ByType(pageResourceType))) @@ -394,10 +404,10 @@ HEADLESS {{< myShort >}} assert.Equal(1, len(s.RegularPages)) assert.Equal(1, len(s.headlessPages)) - regular := s.getPage(KindPage, "a/index") + regular, _ := s.getPage(nil, "a/index") assert.Equal("/a/s1/", regular.RelPermalink()) - headless := s.getPage(KindPage, "b/index") + headless, _ := s.getPage(nil, "b/index") assert.NotNil(headless) assert.True(headless.headless) assert.Equal("Headless Bundle in Topless Bar", headless.Title()) diff --git a/hugolib/page_collections.go b/hugolib/page_collections.go index 8395502f5..df93b6f9f 100644 --- a/hugolib/page_collections.go +++ b/hugolib/page_collections.go @@ -14,11 +14,12 @@ package hugolib import ( + "fmt" "path" "path/filepath" "strings" + "sync" - "github.com/gohugoio/hugo/cache" "github.com/gohugoio/hugo/helpers" ) @@ -48,9 +49,30 @@ type PageCollections struct { // Includes headless bundles, i.e. bundles that produce no output for its content page. headlessPages Pages - pageCache *cache.PartitionedLazyCache + pageIndex } +type pageIndex struct { + initSync sync.Once + index map[string]*Page + load func() map[string]*Page +} + +func (pi *pageIndex) init() { + pi.initSync.Do(func() { + pi.index = pi.load() + }) +} + +// Get initializes the index if not already done so, then +// looks up the given page ref, returns nil if no value found. +func (pi *pageIndex) Get(ref string) *Page { + pi.init() + return pi.index[ref] +} + +var ambiguityFlag = &Page{Kind: "dummy", title: "ambiguity flag"} + func (c *PageCollections) refreshPageCaches() { c.indexPages = c.findPagesByKindNotIn(KindPage, c.Pages) c.RegularPages = c.findPagesByKindIn(KindPage, c.Pages) @@ -62,60 +84,82 @@ func (c *PageCollections) refreshPageCaches() { s = c.Pages[0].s } - cacheLoader := func(kind string) func() (map[string]interface{}, error) { - return func() (map[string]interface{}, error) { - cache := make(map[string]interface{}) - switch kind { - case KindPage: - // Note that we deliberately use the pages from all sites - // in this cache, as we intend to use this in the ref and relref - // shortcodes. If the user says "sect/doc1.en.md", he/she knows - // what he/she is looking for. - for _, pageCollection := range []Pages{c.AllRegularPages, c.headlessPages} { - for _, p := range pageCollection { - cache[filepath.ToSlash(p.Source.Path())] = p - - if s != nil && p.s == s { - // Ref/Relref supports this potentially ambiguous lookup. - cache[p.Source.LogicalName()] = p - - translasionBaseName := p.Source.TranslationBaseName() - dir := filepath.ToSlash(strings.TrimSuffix(p.Dir(), helpers.FilePathSeparator)) - - if translasionBaseName == "index" { - _, name := path.Split(dir) - cache[name] = p - cache[dir] = p - } else { - // Again, ambigous - cache[translasionBaseName] = p - } - - // We need a way to get to the current language version. - pathWithNoExtensions := path.Join(dir, translasionBaseName) - cache[pathWithNoExtensions] = p - } + indexLoader := func() map[string]*Page { + index := make(map[string]*Page) + + // Note that we deliberately use the pages from all sites + // in this index, as we intend to use this in the ref and relref + // shortcodes. If the user says "sect/doc1.en.md", he/she knows + // what he/she is looking for. + for _, pageCollection := range []Pages{c.AllRegularPages, c.headlessPages} { + for _, p := range pageCollection { + + sourceRef := p.absoluteSourceRef() + if sourceRef != "" { + // index the canonical, unambiguous ref + // e.g. /section/article.md + indexPage(index, sourceRef, p) + + // also index the legacy canonical lookup (not guaranteed to be unambiguous) + // e.g. section/article.md + indexPage(index, sourceRef[1:], p) + } + + if s != nil && p.s == s { + // Ref/Relref supports this potentially ambiguous lookup. + indexPage(index, p.Source.LogicalName(), p) + + translationBaseName := p.Source.TranslationBaseName() + dir := filepath.ToSlash(strings.TrimSuffix(p.Dir(), helpers.FilePathSeparator)) + + if translationBaseName == "index" { + _, name := path.Split(dir) + indexPage(index, name, p) + indexPage(index, dir, p) + } else { + // Again, ambiguous + indexPage(index, translationBaseName, p) } + // We need a way to get to the current language version. + pathWithNoExtensions := path.Join(dir, translationBaseName) + indexPage(index, pathWithNoExtensions, p) } - default: - for _, p := range c.indexPages { - key := path.Join(p.sections...) - cache[key] = p - } + } + } + + for _, p := range c.indexPages { + // index the canonical, unambiguous ref for any backing file + // e.g. /section/_index.md + sourceRef := p.absoluteSourceRef() + if sourceRef != "" { + indexPage(index, sourceRef, p) } - return cache, nil + ref := path.Join(p.sections...) + + // index the canonical, unambiguous virtual ref + // e.g. /section + // (this may already have been indexed above) + indexPage(index, "/"+ref, p) + + // index the legacy canonical ref (not guaranteed to be unambiguous) + // e.g. section + indexPage(index, ref, p) } + return index } - partitions := make([]cache.Partition, len(allKindsInPages)) + c.pageIndex = pageIndex{load: indexLoader} +} - for i, kind := range allKindsInPages { - partitions[i] = cache.Partition{Key: kind, Load: cacheLoader(kind)} +func indexPage(index map[string]*Page, ref string, p *Page) { + existing := index[ref] + if existing == nil { + index[ref] = p + } else if existing != ambiguityFlag && existing != p { + index[ref] = ambiguityFlag } - - c.pageCache = cache.NewPartitionedLazyCache(partitions...) } func newPageCollections() *PageCollections { @@ -126,20 +170,39 @@ func newPageCollectionsFromPages(pages Pages) *PageCollections { return &PageCollections{rawAllPages: pages} } -func (c *PageCollections) getPage(typ string, sections ...string) *Page { - var key string - if len(sections) == 1 { - key = filepath.ToSlash(sections[0]) - } else { - key = path.Join(sections...) - } +// context: page used to resolve relative paths +// ref: either unix-style paths (i.e. callers responsible for +// calling filepath.ToSlash as necessary) or shorthand refs. +func (c *PageCollections) getPage(context *Page, ref string) (*Page, error) { + + var result *Page + + if len(ref) > 0 && ref[0:1] == "/" { - p, _ := c.pageCache.Get(typ, key) - if p == nil { - return nil + // it's an absolute path + result = c.pageIndex.Get(ref) + + } else { // either relative path or other supported ref + + // If there's a page context. relative ref interpretation takes precedence. + if context != nil { + // For relative refs `filepath.Join` will properly resolve ".." (parent dir) + // and other elements in the path + apath := path.Join("/", strings.Join(context.sections, "/"), ref) + result = c.pageIndex.Get(apath) + } + + // finally, let's try it as-is for a match against all the alternate refs indexed for each page + if result == nil { + result = c.pageIndex.Get(ref) + + if result == ambiguityFlag { + return nil, fmt.Errorf("The reference \"%s\" in %q resolves to more than one page. Use either an absolute path (begins with \"/\") or relative path to the content directory target.", ref, context.absoluteSourceRef()) + } + } } - return p.(*Page) + return result, nil } func (*PageCollections) findPagesByKindIn(kind string, inPages Pages) Pages { diff --git a/hugolib/page_collections_test.go b/hugolib/page_collections_test.go index c6f4a4a26..bee0cf0d3 100644 --- a/hugolib/page_collections_test.go +++ b/hugolib/page_collections_test.go @@ -55,12 +55,12 @@ func BenchmarkGetPage(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - home := s.getPage(KindHome) + home, _ := s.getPage(nil, "/") if home == nil { b.Fatal("Home is nil") } - p := s.getPage(KindSection, pagePaths[i]) + p, _ := s.getPage(nil, pagePaths[i]) if p == nil { b.Fatal("Section is nil") } @@ -91,7 +91,7 @@ func BenchmarkGetPageRegular(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - page := s.getPage(KindPage, pagePaths[i]) + page, _ := s.getPage(nil, pagePaths[i]) require.NotNil(b, page) } } @@ -131,10 +131,27 @@ func TestGetPage(t *testing.T) { for i, test := range tests { errorMsg := fmt.Sprintf("Test %d", i) - page := s.getPage(test.kind, test.path...) + + // test legacy public Site.GetPage + page, _ := s.Info.GetPage(test.kind, test.path...) assert.NotNil(page, errorMsg) assert.Equal(test.kind, page.Kind, errorMsg) assert.Equal(test.expectedTitle, page.title) + + // test new internal Site.getPage + var ref string + if len(test.path) == 1 { + ref = filepath.ToSlash(test.path[0]) + } else { + ref = path.Join(test.path...) + } + page2, _ := s.getPage(nil, ref) |