From f25d8a9e17fb65fa41dafdcbf0358853d68eaf45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Fri, 5 Jan 2018 11:07:50 +0100 Subject: [PATCH] Fix sub-folder baseURL handling for Page resources I.e. images etc. Fixes #4228 --- helpers/pathspec.go | 11 ++++ helpers/url.go | 5 +- hugolib/hugo_sites_build_test.go | 88 +++++++++++++++++++++++--------- hugolib/page.go | 9 ++-- hugolib/page_paths.go | 4 +- resource/image.go | 10 ++-- resource/resource.go | 23 ++++++--- resource/resource_test.go | 31 ++++++++--- resource/testhelpers_test.go | 7 ++- 9 files changed, 136 insertions(+), 52 deletions(-) diff --git a/helpers/pathspec.go b/helpers/pathspec.go index 281203712..450391f16 100644 --- a/helpers/pathspec.go +++ b/helpers/pathspec.go @@ -26,6 +26,10 @@ import ( type PathSpec struct { BaseURL + // If the baseURL contains a base path, e.g. https://example.com/docs, then "/docs" will be the BasePath. + // This will not be set if canonifyURLs is enabled. + BasePath string + disablePathToLower bool removePathAccents bool uglyURLs bool @@ -124,6 +128,13 @@ func NewPathSpec(fs *hugofs.Fs, cfg config.Provider) (*PathSpec, error) { ProcessingStats: NewProcessingStats(lang), } + if !ps.canonifyURLs { + basePath := ps.BaseURL.url.Path + if basePath != "" && basePath != "/" { + ps.BasePath = basePath + } + } + publishDir := ps.AbsPathify(cfg.GetString("publishDir")) + FilePathSeparator // If root, remove the second '/' if publishDir == "//" { diff --git a/helpers/url.go b/helpers/url.go index 8faefeefa..e2501cd1a 100644 --- a/helpers/url.go +++ b/helpers/url.go @@ -319,12 +319,11 @@ func AddContextRoot(baseURL, relativePath string) string { // If canonifyURLs is set, we will globally prepend the absURL with any sub-folder, // so avoid doing anything here to avoid getting double paths. func (p *PathSpec) PrependBasePath(rel string) string { - basePath := p.BaseURL.url.Path - if !p.canonifyURLs && basePath != "" && basePath != "/" { + if p.BasePath != "" { rel = filepath.ToSlash(rel) // Need to prepend any path from the baseURL hadSlash := strings.HasSuffix(rel, "/") - rel = path.Join(basePath, rel) + rel = path.Join(p.BasePath, rel) if hadSlash { rel += "/" } diff --git a/hugolib/hugo_sites_build_test.go b/hugolib/hugo_sites_build_test.go index 3d66f7fe6..09cd7aff3 100644 --- a/hugolib/hugo_sites_build_test.go +++ b/hugolib/hugo_sites_build_test.go @@ -235,10 +235,11 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) { require.Equal(t, "en", enSite.Language.Lang) - if len(enSite.RegularPages) != 4 { - t.Fatal("Expected 4 english pages") + if len(enSite.RegularPages) != 5 { + t.Fatal("Expected 5 english pages") } - require.Len(t, enSite.AllPages, 28, "should have 28 total pages (including translations and index types)") + + require.Len(t, enSite.AllPages, 32, "should have 32 total pages (including translations and index types)") doc1en := enSite.RegularPages[0] permalink := doc1en.Permalink() @@ -291,8 +292,8 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) { frSite := sites.Sites[1] require.Equal(t, "fr", frSite.Language.Lang) - require.Len(t, frSite.RegularPages, 3, "should have 3 pages") - require.Len(t, frSite.AllPages, 28, "should have 28 total pages (including translations and nodes)") + require.Len(t, frSite.RegularPages, 4, "should have 3 pages") + require.Len(t, frSite.AllPages, 32, "should have 32 total pages (including translations and nodes)") for _, frenchPage := range frSite.RegularPages { require.Equal(t, "fr", frenchPage.Lang()) @@ -392,6 +393,25 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) { next = next.Next } + // Check bundles + bundleFr := enSite.getPage(KindPage, "bundles/b1/index.md") + require.NotNil(t, bundleFr) + require.Equal(t, "/blog/fr/bundles/b1/", bundleFr.RelPermalink()) + require.Equal(t, 1, len(bundleFr.Resources)) + logoFr := bundleFr.Resources.GetByPrefix("logo") + require.NotNil(t, logoFr) + require.Equal(t, "/blog/fr/bundles/b1/logo.png", logoFr.RelPermalink()) + require.Contains(t, readFileFromFs(t, fs.Destination, filepath.FromSlash("public/fr/bundles/b1/logo.png")), "PNG Data") + + bundleEn := enSite.getPage(KindPage, "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)) + logoEn := bundleEn.Resources.GetByPrefix("logo") + require.NotNil(t, logoEn) + require.Equal(t, "/blog/en/bundles/b1/logo.png", logoEn.RelPermalink()) + require.Contains(t, readFileFromFs(t, fs.Destination, filepath.FromSlash("public/en/bundles/b1/logo.png")), "PNG Data") + } func TestMultiSitesRebuild(t *testing.T) { @@ -420,8 +440,8 @@ func TestMultiSitesRebuild(t *testing.T) { enSite := sites.Sites[0] frSite := sites.Sites[1] - require.Len(t, enSite.RegularPages, 4) - require.Len(t, frSite.RegularPages, 3) + require.Len(t, enSite.RegularPages, 5) + require.Len(t, frSite.RegularPages, 4) // Verify translations th.assertFileContent("public/en/sect/doc1-slug/index.html", "Hello") @@ -449,7 +469,7 @@ func TestMultiSitesRebuild(t *testing.T) { }, []fsnotify.Event{{Name: filepath.FromSlash("content/sect/doc2.en.md"), Op: fsnotify.Remove}}, func(t *testing.T) { - require.Len(t, enSite.RegularPages, 3, "1 en removed") + require.Len(t, enSite.RegularPages, 4, "1 en removed") // Check build stats require.Equal(t, 1, enSite.draftCount, "Draft") @@ -472,9 +492,9 @@ func TestMultiSitesRebuild(t *testing.T) { {Name: filepath.FromSlash("content/new1.fr.md"), Op: fsnotify.Create}, }, func(t *testing.T) { - require.Len(t, enSite.RegularPages, 5) - require.Len(t, enSite.AllPages, 30) - require.Len(t, frSite.RegularPages, 4) + require.Len(t, enSite.RegularPages, 6) + require.Len(t, enSite.AllPages, 34) + require.Len(t, frSite.RegularPages, 5) require.Equal(t, "new_fr_1", frSite.RegularPages[3].Title) require.Equal(t, "new_en_2", enSite.RegularPages[0].Title) require.Equal(t, "new_en_1", enSite.RegularPages[1].Title) @@ -492,7 +512,7 @@ func TestMultiSitesRebuild(t *testing.T) { }, []fsnotify.Event{{Name: filepath.FromSlash("content/sect/doc1.en.md"), Op: fsnotify.Write}}, func(t *testing.T) { - require.Len(t, enSite.RegularPages, 5) + require.Len(t, enSite.RegularPages, 6) doc1 := readDestination(t, fs, "public/en/sect/doc1-slug/index.html") require.True(t, strings.Contains(doc1, "CHANGED"), doc1) @@ -510,7 +530,7 @@ func TestMultiSitesRebuild(t *testing.T) { {Name: filepath.FromSlash("content/new1.en.md"), Op: fsnotify.Rename}, }, func(t *testing.T) { - require.Len(t, enSite.RegularPages, 5, "Rename") + require.Len(t, enSite.RegularPages, 6, "Rename") require.Equal(t, "new_en_1", enSite.RegularPages[1].Title) rendered := readDestination(t, fs, "public/en/new1renamed/index.html") require.True(t, strings.Contains(rendered, "new_en_1"), rendered) @@ -525,9 +545,9 @@ func TestMultiSitesRebuild(t *testing.T) { }, []fsnotify.Event{{Name: filepath.FromSlash("layouts/_default/single.html"), Op: fsnotify.Write}}, func(t *testing.T) { - require.Len(t, enSite.RegularPages, 5) - require.Len(t, enSite.AllPages, 30) - require.Len(t, frSite.RegularPages, 4) + require.Len(t, enSite.RegularPages, 6) + require.Len(t, enSite.AllPages, 34) + require.Len(t, frSite.RegularPages, 5) doc1 := readDestination(t, fs, "public/en/sect/doc1-slug/index.html") require.True(t, strings.Contains(doc1, "Template Changed"), doc1) }, @@ -542,9 +562,9 @@ func TestMultiSitesRebuild(t *testing.T) { }, []fsnotify.Event{{Name: filepath.FromSlash("i18n/fr.yaml"), Op: fsnotify.Write}}, func(t *testing.T) { - require.Len(t, enSite.RegularPages, 5) - require.Len(t, enSite.AllPages, 30) - require.Len(t, frSite.RegularPages, 4) + require.Len(t, enSite.RegularPages, 6) + require.Len(t, enSite.AllPages, 34) + require.Len(t, frSite.RegularPages, 5) docEn := readDestination(t, fs, "public/en/sect/doc1-slug/index.html") require.True(t, strings.Contains(docEn, "Hello"), "No Hello") docFr := readDestination(t, fs, "public/fr/sect/doc1/index.html") @@ -566,9 +586,9 @@ func TestMultiSitesRebuild(t *testing.T) { {Name: filepath.FromSlash("layouts/shortcodes/shortcode.html"), Op: fsnotify.Write}, }, func(t *testing.T) { - require.Len(t, enSite.RegularPages, 5) - require.Len(t, enSite.AllPages, 30) - require.Len(t, frSite.RegularPages, 4) + require.Len(t, enSite.RegularPages, 6) + require.Len(t, enSite.AllPages, 34) + require.Len(t, frSite.RegularPages, 5) th.assertFileContent("public/fr/sect/doc1/index.html", "Single", "Modified Shortcode: Salut") th.assertFileContent("public/en/sect/doc1-slug/index.html", "Single", "Modified Shortcode: Hello") }, @@ -657,8 +677,8 @@ title = "Svenska" require.Len(t, homeEn.Translations(), 4) require.Equal(t, "sv", homeEn.Translations()[0].Lang()) - require.Len(t, enSite.RegularPages, 4) - require.Len(t, frSite.RegularPages, 3) + require.Len(t, enSite.RegularPages, 5) + require.Len(t, frSite.RegularPages, 4) // Veriy Swedish site require.Len(t, svSite.RegularPages, 1) @@ -1241,6 +1261,24 @@ lag: - Sogndal --- # Tax NB +`}, + // Bundle + {filepath.FromSlash("bundles/b1/index.en.md"), `--- +title: Bundle EN +publishdate: "2000-01-06" +weight: 2001 +--- +# Bundle Content EN +`}, + {filepath.FromSlash("bundles/b1/index.md"), `--- +title: Bundle Default +publishdate: "2000-01-06" +weight: 2002 +--- +# Bundle Content Default +`}, + {filepath.FromSlash("bundles/b1/logo.png"), ` +PNG Data `}, } @@ -1309,7 +1347,7 @@ func readFileFromFs(t testing.TB, fs afero.Fs, filename string) string { b, err := afero.ReadFile(fs, filename) if err != nil { // Print some debug info - root := "/" //strings.Split(filename, helpers.FilePathSeparator)[0] + root := "" //strings.Split(filename, helpers.FilePathSeparator)[0] afero.Walk(fs, root, func(path string, info os.FileInfo, err error) error { if info != nil && !info.IsDir() { fmt.Println(" ", path) diff --git a/hugolib/page.go b/hugolib/page.go index a10887ad4..437170f42 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -225,9 +225,12 @@ type Page struct { Sitemap Sitemap URLPath - permalink string - relPermalink string - relPermalinkBase string // relPermalink without extension + permalink string + relPermalink string + + // relPermalink without extension and any base path element from the baseURL. + // This is used to construct paths in the page resources. + relPermalinkBase string layoutDescriptor output.LayoutDescriptor diff --git a/hugolib/page_paths.go b/hugolib/page_paths.go index 3eb1add80..287678c61 100644 --- a/hugolib/page_paths.go +++ b/hugolib/page_paths.go @@ -137,9 +137,9 @@ func (p *Page) initURLs() error { if err != nil { return err } - rel = p.s.PathSpec.PrependBasePath(rel) - p.relPermalink = rel + p.relPermalinkBase = strings.TrimSuffix(rel, f.MediaType.FullSuffix()) + p.relPermalink = p.s.PathSpec.PrependBasePath(rel) p.layoutDescriptor = p.createLayoutDescriptor() return nil } diff --git a/resource/image.go b/resource/image.go index 159161de2..8a65be1bf 100644 --- a/resource/image.go +++ b/resource/image.go @@ -108,7 +108,7 @@ type Image struct { configInit sync.Once configLoaded bool - copiedToDestinationInit sync.Once + copyToDestinationInit sync.Once imaging *Imaging @@ -206,7 +206,7 @@ func (i *Image) doWithImageConfig(action, spec string, f func(src image.Image, c conf.Filter = imageFilters[conf.FilterStr] } - key := i.relPermalinkForRel(i.filenameFromConfig(conf)) + key := i.relPermalinkForRel(i.filenameFromConfig(conf), false) return i.spec.imageCache.getOrCreate(i.spec, key, func(resourceCacheFilename string) (*Image, error) { ci := i.clone() @@ -232,7 +232,7 @@ func (i *Image) doWithImageConfig(action, spec string, f func(src image.Image, c ci.config = image.Config{Width: b.Max.X, Height: b.Max.Y} ci.configLoaded = true - return ci, i.encodeToDestinations(converted, conf, resourceCacheFilename, ci.RelPermalink()) + return ci, i.encodeToDestinations(converted, conf, resourceCacheFilename, ci.target()) }) } @@ -392,8 +392,8 @@ func (i *Image) decodeSource() (image.Image, error) { func (i *Image) copyToDestination(src string) error { var res error - i.copiedToDestinationInit.Do(func() { - target := filepath.Join(i.absPublishDir, i.RelPermalink()) + i.copyToDestinationInit.Do(func() { + target := filepath.Join(i.absPublishDir, i.target()) // Fast path: // This is a processed version of the original. diff --git a/resource/resource.go b/resource/resource.go index 19392f3d3..a9fa3df16 100644 --- a/resource/resource.go +++ b/resource/resource.go @@ -219,11 +219,11 @@ type genericResource struct { } func (l *genericResource) Permalink() string { - return l.spec.PermalinkForBaseURL(l.RelPermalink(), l.spec.BaseURL.String()) + return l.spec.PermalinkForBaseURL(l.relPermalinkForRel(l.rel, false), l.spec.BaseURL.String()) } func (l *genericResource) RelPermalink() string { - return l.relPermalinkForRel(l.rel) + return l.relPermalinkForRel(l.rel, true) } // Implement the Cloner interface. @@ -232,16 +232,21 @@ func (l genericResource) WithNewBase(base string) Resource { return &l } -func (l *genericResource) relPermalinkForRel(rel string) string { +func (l *genericResource) relPermalinkForRel(rel string, addBasePath bool) string { if l.link != nil { rel = l.link(rel) } if l.base != "" { rel = path.Join(l.base, rel) - if rel[0] != '/' { - rel = "/" + rel - } + } + + if addBasePath && l.spec.PathSpec.BasePath != "" { + rel = path.Join(l.spec.PathSpec.BasePath, rel) + } + + if rel[0] != '/' { + rel = "/" + rel } return l.spec.PathSpec.URLizeFilename(rel) @@ -262,11 +267,15 @@ func (l *genericResource) Publish() error { } defer f.Close() - target := filepath.Join(l.absPublishDir, l.RelPermalink()) + target := filepath.Join(l.absPublishDir, l.target()) return helpers.WriteToDisk(target, f, l.spec.Fs.Destination) } +func (l *genericResource) target() string { + return l.relPermalinkForRel(l.rel, false) +} + func (r *Spec) newGenericResource( linker func(base string) string, osFileInfo os.FileInfo, diff --git a/resource/resource_test.go b/resource/resource_test.go index 34d63cd60..847c26843 100644 --- a/resource/resource_test.go +++ b/resource/resource_test.go @@ -28,7 +28,7 @@ func TestGenericResource(t *testing.T) { r := spec.newGenericResource(nil, nil, "/public", "/a/foo.css", "foo.css", "css") assert.Equal("https://example.com/foo.css", r.Permalink()) - assert.Equal("foo.css", r.RelPermalink()) + assert.Equal("/foo.css", r.RelPermalink()) assert.Equal("css", r.ResourceType()) } @@ -60,7 +60,7 @@ func TestNewResourceFromFilename(t *testing.T) { assert.NoError(err) assert.NotNil(r) assert.Equal("image", r.ResourceType()) - assert.Equal("a/b/logo.png", r.RelPermalink()) + assert.Equal("/a/b/logo.png", r.RelPermalink()) assert.Equal("https://example.com/a/b/logo.png", r.Permalink()) r, err = spec.NewResourceFromFilename(nil, "/public", "/root/a/b/data.json", "a/b/data.json") @@ -74,6 +74,25 @@ func TestNewResourceFromFilename(t *testing.T) { assert.Equal("/aceof/a/b/data.json", cloned.RelPermalink()) } +func TestNewResourceFromFilenameSubPathInBaseURL(t *testing.T) { + assert := require.New(t) + spec := newTestResourceSpecForBaseURL(assert, "https://example.com/docs") + + writeSource(t, spec.Fs, "/project/a/b/logo.png", "image") + + r, err := spec.NewResourceFromFilename(nil, "/public", + filepath.FromSlash("/project/a/b/logo.png"), filepath.FromSlash("a/b/logo.png")) + + assert.NoError(err) + assert.NotNil(r) + assert.Equal("image", r.ResourceType()) + assert.Equal("/docs/a/b/logo.png", r.RelPermalink()) + assert.Equal("https://example.com/docs/a/b/logo.png", r.Permalink()) + img := r.(*Image) + assert.Equal("/a/b/logo.png", img.target()) + +} + func TestResourcesByType(t *testing.T) { assert := require.New(t) spec := newTestResourceSpec(assert) @@ -99,10 +118,10 @@ func TestResourcesGetByPrefix(t *testing.T) { spec.newGenericResource(nil, nil, "/public", "/b/foo3.css", "foo3.css", "css")} assert.Nil(resources.GetByPrefix("asdf")) - assert.Equal("logo1.png", resources.GetByPrefix("logo").RelPermalink()) - assert.Equal("foo2.css", resources.GetByPrefix("foo2").RelPermalink()) - assert.Equal("foo1.css", resources.GetByPrefix("foo1").RelPermalink()) - assert.Equal("foo1.css", resources.GetByPrefix("foo1").RelPermalink()) + assert.Equal("/logo1.png", resources.GetByPrefix("logo").RelPermalink()) + assert.Equal("/foo2.css", resources.GetByPrefix("foo2").RelPermalink()) + assert.Equal("/foo1.css", resources.GetByPrefix("foo1").RelPermalink()) + assert.Equal("/foo1.css", resources.GetByPrefix("foo1").RelPermalink()) assert.Nil(resources.GetByPrefix("asdfasdf")) } diff --git a/resource/testhelpers_test.go b/resource/testhelpers_test.go index 7ab210688..668885682 100644 --- a/resource/testhelpers_test.go +++ b/resource/testhelpers_test.go @@ -18,12 +18,17 @@ import ( ) func newTestResourceSpec(assert *require.Assertions) *Spec { + return newTestResourceSpecForBaseURL(assert, "https://example.com/") +} + +func newTestResourceSpecForBaseURL(assert *require.Assertions, baseURL string) *Spec { cfg := viper.New() - cfg.Set("baseURL", "https://example.com/") + cfg.Set("baseURL", baseURL) cfg.Set("resourceDir", "/res") fs := hugofs.NewMem(cfg) s, err := helpers.NewPathSpec(fs, cfg) + assert.NoError(err) spec, err := NewSpec(s, media.DefaultTypes)