shithub: hugo

Download patch

ref: f25d8a9e17fb65fa41dafdcbf0358853d68eaf45
parent: 54a89cde69c1fcda7afe0ba0f4e0de86f5f69aa5
author: Bjørn Erik Pedersen <[email protected]>
date: Fri Jan 5 06:07:50 EST 2018

Fix sub-folder baseURL handling for Page resources

I.e. images etc.

Fixes #4228

--- a/helpers/pathspec.go
+++ b/helpers/pathspec.go
@@ -26,6 +26,10 @@
 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
@@ -122,6 +126,13 @@
 		staticDirs:                     staticDirs,
 		theme:                          cfg.GetString("theme"),
 		ProcessingStats:                NewProcessingStats(lang),
+	}
+
+	if !ps.canonifyURLs {
+		basePath := ps.BaseURL.url.Path
+		if basePath != "" && basePath != "/" {
+			ps.BasePath = basePath
+		}
 	}
 
 	publishDir := ps.AbsPathify(cfg.GetString("publishDir")) + FilePathSeparator
--- a/helpers/url.go
+++ b/helpers/url.go
@@ -319,12 +319,11 @@
 // 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 += "/"
 		}
--- a/hugolib/hugo_sites_build_test.go
+++ b/hugolib/hugo_sites_build_test.go
@@ -235,11 +235,12 @@
 
 	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()
 	require.NoError(t, err, "permalink call failed")
@@ -291,8 +292,8 @@
 	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 @@
 		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 @@
 	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 @@
 			},
 			[]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 @@
 				{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 @@
 			},
 			[]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 @@
 				{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 @@
 			},
 			[]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 @@
 			},
 			[]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 @@
 				{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 @@
 	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)
@@ -1242,6 +1262,24 @@
 ---
 # 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
+`},
 	}
 
 	configFile := "multilangconfig." + configSuffix
@@ -1309,7 +1347,7 @@
 	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)
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -225,9 +225,12 @@
 	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
 
--- a/hugolib/page_paths.go
+++ b/hugolib/page_paths.go
@@ -137,9 +137,9 @@
 	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
 }
--- a/resource/image.go
+++ b/resource/image.go
@@ -108,7 +108,7 @@
 	configInit   sync.Once
 	configLoaded bool
 
-	copiedToDestinationInit sync.Once
+	copyToDestinationInit sync.Once
 
 	imaging *Imaging
 
@@ -206,7 +206,7 @@
 		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 @@
 		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) 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.
--- a/resource/resource.go
+++ b/resource/resource.go
@@ -219,11 +219,11 @@
 }
 
 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,7 +232,7 @@
 	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)
 	}
@@ -239,11 +239,16 @@
 
 	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,9 +267,13 @@
 	}
 	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(
--- a/resource/resource_test.go
+++ b/resource/resource_test.go
@@ -28,7 +28,7 @@
 	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 @@
 	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 @@
 	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 @@
 		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"))
 
 }
--- a/resource/testhelpers_test.go
+++ b/resource/testhelpers_test.go
@@ -18,12 +18,17 @@
 )
 
 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)