Skip to content

Commit f25d8a9

Browse files
committed
Fix sub-folder baseURL handling for Page resources
I.e. images etc. Fixes #4228
1 parent 54a89cd commit f25d8a9

File tree

9 files changed

+136
-52
lines changed

9 files changed

+136
-52
lines changed

‎helpers/pathspec.go‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ import (
2626
type PathSpec struct {
2727
BaseURL
2828

29+
// If the baseURL contains a base path, e.g. https://example.com/docs, then "/docs" will be the BasePath.
30+
// This will not be set if canonifyURLs is enabled.
31+
BasePath string
32+
2933
disablePathToLower bool
3034
removePathAccents bool
3135
uglyURLs bool
@@ -124,6 +128,13 @@ func NewPathSpec(fs *hugofs.Fs, cfg config.Provider) (*PathSpec, error) {
124128
ProcessingStats: NewProcessingStats(lang),
125129
}
126130

131+
if !ps.canonifyURLs {
132+
basePath := ps.BaseURL.url.Path
133+
if basePath != "" && basePath != "/" {
134+
ps.BasePath = basePath
135+
}
136+
}
137+
127138
publishDir := ps.AbsPathify(cfg.GetString("publishDir")) + FilePathSeparator
128139
// If root, remove the second '/'
129140
if publishDir == "//" {

‎helpers/url.go‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -319,12 +319,11 @@ func AddContextRoot(baseURL, relativePath string) string {
319319
// If canonifyURLs is set, we will globally prepend the absURL with any sub-folder,
320320
// so avoid doing anything here to avoid getting double paths.
321321
func (p *PathSpec) PrependBasePath(rel string) string {
322-
basePath := p.BaseURL.url.Path
323-
if !p.canonifyURLs && basePath != "" && basePath != "/" {
322+
if p.BasePath != "" {
324323
rel = filepath.ToSlash(rel)
325324
// Need to prepend any path from the baseURL
326325
hadSlash := strings.HasSuffix(rel, "/")
327-
rel = path.Join(basePath, rel)
326+
rel = path.Join(p.BasePath, rel)
328327
if hadSlash {
329328
rel += "/"
330329
}

‎hugolib/hugo_sites_build_test.go‎

Lines changed: 63 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -235,10 +235,11 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) {
235235

236236
require.Equal(t, "en", enSite.Language.Lang)
237237

238-
if len(enSite.RegularPages) != 4 {
239-
t.Fatal("Expected 4 english pages")
238+
if len(enSite.RegularPages) != 5 {
239+
t.Fatal("Expected 5 english pages")
240240
}
241-
require.Len(t, enSite.AllPages, 28, "should have 28 total pages (including translations and index types)")
241+
242+
require.Len(t, enSite.AllPages, 32, "should have 32 total pages (including translations and index types)")
242243

243244
doc1en := enSite.RegularPages[0]
244245
permalink := doc1en.Permalink()
@@ -291,8 +292,8 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) {
291292
frSite := sites.Sites[1]
292293

293294
require.Equal(t, "fr", frSite.Language.Lang)
294-
require.Len(t, frSite.RegularPages, 3, "should have 3 pages")
295-
require.Len(t, frSite.AllPages, 28, "should have 28 total pages (including translations and nodes)")
295+
require.Len(t, frSite.RegularPages, 4, "should have 3 pages")
296+
require.Len(t, frSite.AllPages, 32, "should have 32 total pages (including translations and nodes)")
296297

297298
for _, frenchPage := range frSite.RegularPages {
298299
require.Equal(t, "fr", frenchPage.Lang())
@@ -392,6 +393,25 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) {
392393
next = next.Next
393394
}
394395

396+
// Check bundles
397+
bundleFr := enSite.getPage(KindPage, "bundles/b1/index.md")
398+
require.NotNil(t, bundleFr)
399+
require.Equal(t, "/blog/fr/bundles/b1/", bundleFr.RelPermalink())
400+
require.Equal(t, 1, len(bundleFr.Resources))
401+
logoFr := bundleFr.Resources.GetByPrefix("logo")
402+
require.NotNil(t, logoFr)
403+
require.Equal(t, "/blog/fr/bundles/b1/logo.png", logoFr.RelPermalink())
404+
require.Contains(t, readFileFromFs(t, fs.Destination, filepath.FromSlash("public/fr/bundles/b1/logo.png")), "PNG Data")
405+
406+
bundleEn := enSite.getPage(KindPage, "bundles/b1/index.en.md")
407+
require.NotNil(t, bundleEn)
408+
require.Equal(t, "/blog/en/bundles/b1/", bundleEn.RelPermalink())
409+
require.Equal(t, 1, len(bundleEn.Resources))
410+
logoEn := bundleEn.Resources.GetByPrefix("logo")
411+
require.NotNil(t, logoEn)
412+
require.Equal(t, "/blog/en/bundles/b1/logo.png", logoEn.RelPermalink())
413+
require.Contains(t, readFileFromFs(t, fs.Destination, filepath.FromSlash("public/en/bundles/b1/logo.png")), "PNG Data")
414+
395415
}
396416

397417
func TestMultiSitesRebuild(t *testing.T) {
@@ -420,8 +440,8 @@ func TestMultiSitesRebuild(t *testing.T) {
420440
enSite := sites.Sites[0]
421441
frSite := sites.Sites[1]
422442

423-
require.Len(t, enSite.RegularPages, 4)
424-
require.Len(t, frSite.RegularPages, 3)
443+
require.Len(t, enSite.RegularPages, 5)
444+
require.Len(t, frSite.RegularPages, 4)
425445

426446
// Verify translations
427447
th.assertFileContent("public/en/sect/doc1-slug/index.html", "Hello")
@@ -449,7 +469,7 @@ func TestMultiSitesRebuild(t *testing.T) {
449469
},
450470
[]fsnotify.Event{{Name: filepath.FromSlash("content/sect/doc2.en.md"), Op: fsnotify.Remove}},
451471
func(t *testing.T) {
452-
require.Len(t, enSite.RegularPages, 3, "1 en removed")
472+
require.Len(t, enSite.RegularPages, 4, "1 en removed")
453473

454474
// Check build stats
455475
require.Equal(t, 1, enSite.draftCount, "Draft")
@@ -472,9 +492,9 @@ func TestMultiSitesRebuild(t *testing.T) {
472492
{Name: filepath.FromSlash("content/new1.fr.md"), Op: fsnotify.Create},
473493
},
474494
func(t *testing.T) {
475-
require.Len(t, enSite.RegularPages, 5)
476-
require.Len(t, enSite.AllPages, 30)
477-
require.Len(t, frSite.RegularPages, 4)
495+
require.Len(t, enSite.RegularPages, 6)
496+
require.Len(t, enSite.AllPages, 34)
497+
require.Len(t, frSite.RegularPages, 5)
478498
require.Equal(t, "new_fr_1", frSite.RegularPages[3].Title)
479499
require.Equal(t, "new_en_2", enSite.RegularPages[0].Title)
480500
require.Equal(t, "new_en_1", enSite.RegularPages[1].Title)
@@ -492,7 +512,7 @@ func TestMultiSitesRebuild(t *testing.T) {
492512
},
493513
[]fsnotify.Event{{Name: filepath.FromSlash("content/sect/doc1.en.md"), Op: fsnotify.Write}},
494514
func(t *testing.T) {
495-
require.Len(t, enSite.RegularPages, 5)
515+
require.Len(t, enSite.RegularPages, 6)
496516
doc1 := readDestination(t, fs, "public/en/sect/doc1-slug/index.html")
497517
require.True(t, strings.Contains(doc1, "CHANGED"), doc1)
498518

@@ -510,7 +530,7 @@ func TestMultiSitesRebuild(t *testing.T) {
510530
{Name: filepath.FromSlash("content/new1.en.md"), Op: fsnotify.Rename},
511531
},
512532
func(t *testing.T) {
513-
require.Len(t, enSite.RegularPages, 5, "Rename")
533+
require.Len(t, enSite.RegularPages, 6, "Rename")
514534
require.Equal(t, "new_en_1", enSite.RegularPages[1].Title)
515535
rendered := readDestination(t, fs, "public/en/new1renamed/index.html")
516536
require.True(t, strings.Contains(rendered, "new_en_1"), rendered)
@@ -525,9 +545,9 @@ func TestMultiSitesRebuild(t *testing.T) {
525545
},
526546
[]fsnotify.Event{{Name: filepath.FromSlash("layouts/_default/single.html"), Op: fsnotify.Write}},
527547
func(t *testing.T) {
528-
require.Len(t, enSite.RegularPages, 5)
529-
require.Len(t, enSite.AllPages, 30)
530-
require.Len(t, frSite.RegularPages, 4)
548+
require.Len(t, enSite.RegularPages, 6)
549+
require.Len(t, enSite.AllPages, 34)
550+
require.Len(t, frSite.RegularPages, 5)
531551
doc1 := readDestination(t, fs, "public/en/sect/doc1-slug/index.html")
532552
require.True(t, strings.Contains(doc1, "Template Changed"), doc1)
533553
},
@@ -542,9 +562,9 @@ func TestMultiSitesRebuild(t *testing.T) {
542562
},
543563
[]fsnotify.Event{{Name: filepath.FromSlash("i18n/fr.yaml"), Op: fsnotify.Write}},
544564
func(t *testing.T) {
545-
require.Len(t, enSite.RegularPages, 5)
546-
require.Len(t, enSite.AllPages, 30)
547-
require.Len(t, frSite.RegularPages, 4)
565+
require.Len(t, enSite.RegularPages, 6)
566+
require.Len(t, enSite.AllPages, 34)
567+
require.Len(t, frSite.RegularPages, 5)
548568
docEn := readDestination(t, fs, "public/en/sect/doc1-slug/index.html")
549569
require.True(t, strings.Contains(docEn, "Hello"), "No Hello")
550570
docFr := readDestination(t, fs, "public/fr/sect/doc1/index.html")
@@ -566,9 +586,9 @@ func TestMultiSitesRebuild(t *testing.T) {
566586
{Name: filepath.FromSlash("layouts/shortcodes/shortcode.html"), Op: fsnotify.Write},
567587
},
568588
func(t *testing.T) {
569-
require.Len(t, enSite.RegularPages, 5)
570-
require.Len(t, enSite.AllPages, 30)
571-
require.Len(t, frSite.RegularPages, 4)
589+
require.Len(t, enSite.RegularPages, 6)
590+
require.Len(t, enSite.AllPages, 34)
591+
require.Len(t, frSite.RegularPages, 5)
572592
th.assertFileContent("public/fr/sect/doc1/index.html", "Single", "Modified Shortcode: Salut")
573593
th.assertFileContent("public/en/sect/doc1-slug/index.html", "Single", "Modified Shortcode: Hello")
574594
},
@@ -657,8 +677,8 @@ title = "Svenska"
657677
require.Len(t, homeEn.Translations(), 4)
658678
require.Equal(t, "sv", homeEn.Translations()[0].Lang())
659679

660-
require.Len(t, enSite.RegularPages, 4)
661-
require.Len(t, frSite.RegularPages, 3)
680+
require.Len(t, enSite.RegularPages, 5)
681+
require.Len(t, frSite.RegularPages, 4)
662682

663683
// Veriy Swedish site
664684
require.Len(t, svSite.RegularPages, 1)
@@ -1241,6 +1261,24 @@ lag:
12411261
- Sogndal
12421262
---
12431263
# Tax NB
1264+
`},
1265+
// Bundle
1266+
{filepath.FromSlash("bundles/b1/index.en.md"), `---
1267+
title: Bundle EN
1268+
publishdate: "2000-01-06"
1269+
weight: 2001
1270+
---
1271+
# Bundle Content EN
1272+
`},
1273+
{filepath.FromSlash("bundles/b1/index.md"), `---
1274+
title: Bundle Default
1275+
publishdate: "2000-01-06"
1276+
weight: 2002
1277+
---
1278+
# Bundle Content Default
1279+
`},
1280+
{filepath.FromSlash("bundles/b1/logo.png"), `
1281+
PNG Data
12441282
`},
12451283
}
12461284

@@ -1309,7 +1347,7 @@ func readFileFromFs(t testing.TB, fs afero.Fs, filename string) string {
13091347
b, err := afero.ReadFile(fs, filename)
13101348
if err != nil {
13111349
// Print some debug info
1312-
root := "/" //strings.Split(filename, helpers.FilePathSeparator)[0]
1350+
root := "" //strings.Split(filename, helpers.FilePathSeparator)[0]
13131351
afero.Walk(fs, root, func(path string, info os.FileInfo, err error) error {
13141352
if info != nil && !info.IsDir() {
13151353
fmt.Println(" ", path)

‎hugolib/page.go‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,9 +225,12 @@ type Page struct {
225225
Sitemap Sitemap
226226

227227
URLPath
228-
permalink string
229-
relPermalink string
230-
relPermalinkBase string // relPermalink without extension
228+
permalink string
229+
relPermalink string
230+
231+
// relPermalink without extension and any base path element from the baseURL.
232+
// This is used to construct paths in the page resources.
233+
relPermalinkBase string
231234

232235
layoutDescriptor output.LayoutDescriptor
233236

‎hugolib/page_paths.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,9 @@ func (p *Page) initURLs() error {
137137
if err != nil {
138138
return err
139139
}
140-
rel = p.s.PathSpec.PrependBasePath(rel)
141-
p.relPermalink = rel
140+
142141
p.relPermalinkBase = strings.TrimSuffix(rel, f.MediaType.FullSuffix())
142+
p.relPermalink = p.s.PathSpec.PrependBasePath(rel)
143143
p.layoutDescriptor = p.createLayoutDescriptor()
144144
return nil
145145
}

‎resource/image.go‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ type Image struct {
108108
configInit sync.Once
109109
configLoaded bool
110110

111-
copiedToDestinationInit sync.Once
111+
copyToDestinationInit sync.Once
112112

113113
imaging *Imaging
114114

@@ -206,7 +206,7 @@ func (i *Image) doWithImageConfig(action, spec string, f func(src image.Image, c
206206
conf.Filter = imageFilters[conf.FilterStr]
207207
}
208208

209-
key := i.relPermalinkForRel(i.filenameFromConfig(conf))
209+
key := i.relPermalinkForRel(i.filenameFromConfig(conf), false)
210210

211211
return i.spec.imageCache.getOrCreate(i.spec, key, func(resourceCacheFilename string) (*Image, error) {
212212
ci := i.clone()
@@ -232,7 +232,7 @@ func (i *Image) doWithImageConfig(action, spec string, f func(src image.Image, c
232232
ci.config = image.Config{Width: b.Max.X, Height: b.Max.Y}
233233
ci.configLoaded = true
234234

235-
return ci, i.encodeToDestinations(converted, conf, resourceCacheFilename, ci.RelPermalink())
235+
return ci, i.encodeToDestinations(converted, conf, resourceCacheFilename, ci.target())
236236
})
237237

238238
}
@@ -392,8 +392,8 @@ func (i *Image) decodeSource() (image.Image, error) {
392392
func (i *Image) copyToDestination(src string) error {
393393
var res error
394394

395-
i.copiedToDestinationInit.Do(func() {
396-
target := filepath.Join(i.absPublishDir, i.RelPermalink())
395+
i.copyToDestinationInit.Do(func() {
396+
target := filepath.Join(i.absPublishDir, i.target())
397397

398398
// Fast path:
399399
// This is a processed version of the original.

‎resource/resource.go‎

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -219,11 +219,11 @@ type genericResource struct {
219219
}
220220

221221
func (l *genericResource) Permalink() string {
222-
return l.spec.PermalinkForBaseURL(l.RelPermalink(), l.spec.BaseURL.String())
222+
return l.spec.PermalinkForBaseURL(l.relPermalinkForRel(l.rel, false), l.spec.BaseURL.String())
223223
}
224224

225225
func (l *genericResource) RelPermalink() string {
226-
return l.relPermalinkForRel(l.rel)
226+
return l.relPermalinkForRel(l.rel, true)
227227
}
228228

229229
// Implement the Cloner interface.
@@ -232,16 +232,21 @@ func (l genericResource) WithNewBase(base string) Resource {
232232
return &l
233233
}
234234

235-
func (l *genericResource) relPermalinkForRel(rel string) string {
235+
func (l *genericResource) relPermalinkForRel(rel string, addBasePath bool) string {
236236
if l.link != nil {
237237
rel = l.link(rel)
238238
}
239239

240240
if l.base != "" {
241241
rel = path.Join(l.base, rel)
242-
if rel[0] != '/' {
243-
rel = "/" + rel
244-
}
242+
}
243+
244+
if addBasePath && l.spec.PathSpec.BasePath != "" {
245+
rel = path.Join(l.spec.PathSpec.BasePath, rel)
246+
}
247+
248+
if rel[0] != '/' {
249+
rel = "/" + rel
245250
}
246251

247252
return l.spec.PathSpec.URLizeFilename(rel)
@@ -262,11 +267,15 @@ func (l *genericResource) Publish() error {
262267
}
263268
defer f.Close()
264269

265-
target := filepath.Join(l.absPublishDir, l.RelPermalink())
270+
target := filepath.Join(l.absPublishDir, l.target())
266271

267272
return helpers.WriteToDisk(target, f, l.spec.Fs.Destination)
268273
}
269274

275+
func (l *genericResource) target() string {
276+
return l.relPermalinkForRel(l.rel, false)
277+
}
278+
270279
func (r *Spec) newGenericResource(
271280
linker func(base string) string,
272281
osFileInfo os.FileInfo,

0 commit comments

Comments
 (0)