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) + assert.NotNil(page2, errorMsg) + assert.Equal(test.kind, page2.Kind, errorMsg) + assert.Equal(test.expectedTitle, page2.title) + } + // vas(todo) add ambiguity detection tests + } diff --git a/hugolib/pages_language_merge_test.go b/hugolib/pages_language_merge_test.go index b7c78836c..0ff3bd544 100644 --- a/hugolib/pages_language_merge_test.go +++ b/hugolib/pages_language_merge_test.go @@ -68,8 +68,8 @@ func TestMergeLanguages(t *testing.T) { assert.Equal(4, len(firstNN.Sites())) assert.Equal("en", firstNN.Sites().First().Language.Lang) - nnBundle := nnSite.getPage("page", "bundle") - enBundle := enSite.getPage("page", "bundle") + nnBundle, _ := nnSite.getPage(nil, "bundle") + enBundle, _ := enSite.getPage(nil, "bundle") assert.Equal(6, len(enBundle.Resources)) assert.Equal(2, len(nnBundle.Resources)) diff --git a/hugolib/shortcode_test.go b/hugolib/shortcode_test.go index 1437ae0cf..55bd58d01 100644 --- a/hugolib/shortcode_test.go +++ b/hugolib/shortcode_test.go @@ -687,7 +687,7 @@ NotFound: {{< thisDoesNotExist >}} require.Len(t, h.Sites, 1) s := h.Sites[0] - home := s.getPage(KindHome) + home, _ := s.getPage(nil, "/") require.NotNil(t, home) require.Len(t, home.outputFormats, 3) diff --git a/hugolib/site.go b/hugolib/site.go index 5e300393b..e04983695 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -21,6 +21,7 @@ import ( "mime" "net/url" "os" + "path" "path/filepath" "sort" "strconv" @@ -492,7 +493,6 @@ func (s *SiteInfo) refLink(ref string, page *Page, relative bool, outputFormat s var err error ref = filepath.ToSlash(ref) - ref = strings.TrimPrefix(ref, "/") refURL, err = url.Parse(ref) @@ -504,7 +504,11 @@ func (s *SiteInfo) refLink(ref string, page *Page, relative bool, outputFormat s var link string if refURL.Path != "" { - target := s.getPage(KindPage, refURL.Path) + target, err := s.getPage(page, refURL.Path) + + if err != nil { + return "", err + } if target == nil { return "", fmt.Errorf("No page found with path or logical name \"%s\".\n", refURL.Path) @@ -1598,13 +1602,20 @@ func (s *Site) appendThemeTemplates(in []string) []string { } -// GetPage looks up a page of a given type in the path given. +// GetPage looks up a page of a given type for the given ref. // {{ with .Site.GetPage "section" "blog" }}{{ .Title }}{{ end }} // -// This will return nil when no page could be found, and will return the -// first page found if the key is ambigous. -func (s *SiteInfo) GetPage(typ string, path ...string) (*Page, error) { - return s.getPage(typ, path...), nil +// This will return nil when no page could be found, and will return an +// error if the key is ambiguous. +func (s *SiteInfo) GetPage(typ string, ref ...string) (*Page, error) { + var key string + if len(ref) == 1 { + key = filepath.ToSlash(ref[0]) + } else { + key = path.Join(ref...) + } + + return s.getPage(nil, key) } func (s *Site) permalinkForOutputFormat(link string, f output.Format) (string, error) { diff --git a/hugolib/site_output_test.go b/hugolib/site_output_test.go index 0677dfbfb..8c9aa113a 100644 --- a/hugolib/site_output_test.go +++ b/hugolib/site_output_test.go @@ -150,7 +150,7 @@ Len Pages: {{ .Kind }} {{ len .Site.RegularPages }} Page Number: {{ .Paginator.P s := h.Sites[0] require.Equal(t, "en", s.Language.Lang) - home := s.getPage(KindHome) + home, _ := s.getPage(nil, "/") require.NotNil(t, home) @@ -325,7 +325,7 @@ baseName = "customdelimbase" th.assertFileContent("public/customdelimbase_del", "custom delim") s := h.Sites[0] - home := s.getPage(KindHome) + home, _ := s.getPage(nil, "/") require.NotNil(t, home) outputs := home.OutputFormats() diff --git a/hugolib/site_sections_test.go b/hugolib/site_sections_test.go index 772d23e23..d8579887a 100644 --- a/hugolib/site_sections_test.go +++ b/hugolib/site_sections_test.go @@ -135,19 +135,19 @@ PAG|{{ .Title }}|{{ $sect.InSection . }} }}, {"empty1", func(p *Page) { // > b,c - assert.NotNil(p.s.getPage(KindSection, "empty1", "b")) - assert.NotNil(p.s.getPage(KindSection, "empty1", "b", "c")) + assert.NotNil(p.s.getPage(nil, "empty1/b")) + assert.NotNil(p.s.getPage(nil, "empty1/b/c")) }}, {"empty2", func(p *Page) { // > b,c,d where b and d have content files. - b := p.s.getPage(KindSection, "empty2", "b") + b, _ := p.s.getPage(nil, "empty2/b") assert.NotNil(b) assert.Equal("T40_-1", b.title) - c := p.s.getPage(KindSection, "empty2", "b", "c") + c, _ := p.s.getPage(nil, "empty2/b/c") assert.NotNil(c) assert.Equal("Cs", c.title) - d := p.s.getPage(KindSection, "empty2", "b", "c", "d") + d, _ := p.s.getPage(nil, "empty2/b/c/d") assert.NotNil(d) assert.Equal("T41_-1", d.title) @@ -156,9 +156,9 @@ PAG|{{ .Title }}|{{ $sect.InSection . }} assert.False(c.Eq("asdf")) }}, - {"empty3", func(p *Page) { + {"/empty3", func(p *Page) { // b,c,d with regular page in b - b := p.s.getPage(KindSection, "empty3", "b") + b, _ := p.s.getPage(nil, "/empty3/b") assert.NotNil(b) assert.Len(b.Pages, 1) assert.Equal("empty3.md", b.Pages[0].File.LogicalName()) @@ -200,7 +200,8 @@ PAG|{{ .Title }}|{{ $sect.InSection . }} active, err = p.InSection(child) assert.NoError(err) assert.True(active) - active, err = p.InSection(p.s.getPage(KindHome)) + homePage, _ := p.s.getPage(nil, "/") + active, err = p.InSection(homePage) assert.NoError(err) assert.False(active) @@ -235,7 +236,7 @@ PAG|{{ .Title }}|{{ $sect.InSection . }} assert.Equal("T2_-1", p.Parent().title) assert.Len(p.Sections(), 0) - l1 := p.s.getPage(KindSection, "l1") + l1, _ := p.s.getPage(nil, "l1") isDescendant, err := l1.IsDescendant(p) assert.NoError(err) assert.False(isDescendant) @@ -265,16 +266,18 @@ PAG|{{ .Title }}|{{ $sect.InSection . }} }}, } - home := s.getPage(KindHome) + home, _ := s.getPage(nil, "/") for _, test := range tests { sections := strings.Split(test.sections, ",") - p := s.getPage(KindSection, sections...) + p, _ := s.Info.GetPage(KindSection, sections...) assert.NotNil(p, fmt.Sprint(sections)) if p.Pages != nil { assert.Equal(p.Pages, p.data["Pages"]) } + + fmt.Println(p, test.sections) assert.NotNil(p.Parent(), fmt.Sprintf("Parent nil: %q", test.sections)) test.verify(p) } @@ -284,7 +287,7 @@ PAG|{{ .Title }}|{{ $sect.InSection . }} assert.Len(home.Sections(), 9) assert.Equal(home.Sections(), s.Info.Sections()) - rootPage := s.getPage(KindPage, "mypage.md") + rootPage, _ := s.getPage(nil, "mypage.md") assert.NotNil(rootPage) assert.True(rootPage.Parent().IsHome()) @@ -294,7 +297,7 @@ PAG|{{ .Title }}|{{ $sect.InSection . }} // If we later decide to do something about this, we will have to do some normalization in // getPage. // TODO(bep) - sectionWithSpace := s.getPage(KindSection, "Spaces in Section") + sectionWithSpace, _ := s.getPage(nil, "Spaces in Section") require.NotNil(t, sectionWithSpace) require.Equal(t, "/spaces-in-section/", sectionWithSpace.RelPermalink()) diff --git a/hugolib/site_test.go b/hugolib/site_test.go index 202c01986..d9460e91a 100644 --- a/hugolib/site_test.go +++ b/hugolib/site_test.go @@ -628,7 +628,8 @@ func TestOrderedPages(t *testing.T) { s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) - if s.getPage(KindSection, "sect").Pages[1].title != "Three" || s.getPage(KindSection, "sect").Pages[2].title != "Four" { + sect, _ := s.getPage(nil, "sect") + if sect.Pages[1].title != "Three" || sect.Pages[2].title != "Four" { t.Error("Pages in unexpected order.") } @@ -874,8 +875,10 @@ func TestWeightedTaxonomies(t *testing.T) { func setupLinkingMockSite(t *testing.T) *Site { sources := [][2]string{ {filepath.FromSlash("level2/unique.md"), ""}, + {filepath.FromSlash("_index.md"), ""}, {filepath.FromSlash("rootfile.md"), ""}, {filepath.FromSlash("root-image.png"), ""}, + {filepath.FromSlash("common.md"), ""}, {filepath.FromSlash("level2/2-root.md"), ""}, {filepath.FromSlash("level2/common.md"), ""}, @@ -883,7 +886,7 @@ func setupLinkingMockSite(t *testing.T) *Site { {filepath.FromSlash("level2/2-image.png"), ""}, {filepath.FromSlash("level2/common.png"), ""}, - {filepath.FromSlash("level2/level3/start.md"), ""}, + {filepath.FromSlash("level2/level3/current.md"), ""}, {filepath.FromSlash("level2/level3/3-root.md"), ""}, {filepath.FromSlash("level2/level3/common.md"), ""}, {filepath.FromSlash("level2/level3/3-image.png"), ""}, @@ -910,7 +913,7 @@ func TestRefLinking(t *testing.T) { t.Parallel() site := setupLinkingMockSite(t) - currentPage := site.getPage(KindPage, "level2/level3/start.md") + currentPage, _ := site.getPage(nil, "level2/level3/current.md") if currentPage == nil { t.Fatalf("failed to find current page in site") } @@ -921,12 +924,16 @@ func TestRefLinking(t *testing.T) { relative bool expected string }{ - {"unique.md", "", true, "/level2/unique/"}, - {"level2/common.md", "", true, "/level2/common/"}, + {"/level2/unique.md", "", true, "/level2/unique/"}, + {"../unique.md", "", true, "/level2/unique/"}, + {"/level2/common.md", "", true, "/level2/common/"}, + {"../common.md", "", true, "/level2/common/"}, + {"common.md", "", true, "/level2/level3/common/"}, + {"/common.md", "", true, "/common/"}, {"3-root.md", "", true, "/level2/level3/3-root/"}, } { if out, err := site.Info.refLink(test.link, currentPage, test.relative, test.outputFormat); err != nil || out != test.expected { - t.Errorf("[%d] Expected %s to resolve to (%s), got (%s) - error: %s", i, test.link, test.expected, out, err) + t.Errorf("[%d] Expected %q from %q to resolve to %q, got %q - error: %s", i, test.link, currentPage.absoluteSourceRef(), test.expected, out, err) } } diff --git a/hugolib/site_url_test.go b/hugolib/site_url_test.go index 5b9d19e0d..fff75bda5 100644 --- a/hugolib/site_url_test.go +++ b/hugolib/site_url_test.go @@ -117,12 +117,12 @@ Do not go gentle into that good night. assert.Len(s.RegularPages, 2) - notUgly := s.getPage(KindPage, "sect1/p1.md") + notUgly, _ := s.getPage(nil, "sect1/p1.md") assert.NotNil(notUgly) assert.Equal("sect1", notUgly.Section()) assert.Equal("/sect1/p1/", notUgly.RelPermalink()) - ugly := s.getPage(KindPage, "sect2/p2.md") + ugly, _ := s.getPage(nil, "sect2/p2.md") assert.NotNil(ugly) assert.Equal("sect2", ugly.Section()) assert.Equal("/sect2/p2.html", ugly.RelPermalink()) @@ -175,7 +175,7 @@ Do not go gentle into that good night. assert.Len(s.RegularPages, 10) - sect1 := s.getPage(KindSection, "sect1") + sect1, _ := s.getPage(nil, "sect1") assert.NotNil(sect1) assert.Equal("/ss1/", sect1.RelPermalink()) th.assertFileContent(filepath.Join("public", "ss1", "index.html"), "P1|URL: /ss1/|Next: /ss1/page/2/") diff --git a/hugolib/taxonomy_test.go b/hugolib/taxonomy_test.go index 0445de58f..828f0c410 100644 --- a/hugolib/taxonomy_test.go +++ b/hugolib/taxonomy_test.go @@ -167,7 +167,7 @@ permalinkeds: } for taxonomy, count := range taxonomyTermPageCounts { - term := s.getPage(KindTaxonomyTerm, taxonomy) + term, _ := s.getPage(nil, taxonomy) require.NotNil(t, term) require.Len(t, term.Pages, count) @@ -176,7 +176,7 @@ permalinkeds: } } - cat1 := s.getPage(KindTaxonomy, "categories", "cat1") + cat1, _ := s.getPage(nil, "categories/cat1") require.NotNil(t, cat1) if uglyURLs { require.Equal(t, "/blog/categories/cat1.html", cat1.RelPermalink()) @@ -184,8 +184,8 @@ permalinkeds: require.Equal(t, "/blog/categories/cat1/", cat1.RelPermalink()) } - pl1 := s.getPage(KindTaxonomy, "permalinkeds", "pl1") - permalinkeds := s.getPage(KindTaxonomyTerm, "permalinkeds") + pl1, _ := s.getPage(nil, "permalinkeds/pl1") + permalinkeds, _ := s.getPage(nil, "permalinkeds") require.NotNil(t, pl1) require.NotNil(t, permalinkeds) if uglyURLs { @@ -198,11 +198,11 @@ permalinkeds: // Issue #3070 preserveTaxonomyNames if preserveTaxonomyNames { - helloWorld := s.getPage(KindTaxonomy, "others", "Hello Hugo world") + helloWorld, _ := s.getPage(nil, "others/Hello Hugo world") require.NotNil(t, helloWorld) require.Equal(t, "Hello Hugo world", helloWorld.title) } else { - helloWorld := s.getPage(KindTaxonomy, "others", "hello-hugo-world") + helloWorld, _ := s.getPage(nil, "others/hello-hugo-world") require.NotNil(t, helloWorld) require.Equal(t, "Hello Hugo World", helloWorld.title) } |