Skip to content

Commit ab17310

Browse files
committed
tpl: Make any layout set in front matter higher priority
Fixes gohugoio#13541
1 parent c871062 commit ab17310

File tree

7 files changed

+124
-102
lines changed

7 files changed

+124
-102
lines changed

hugolib/alias.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func (a aliasHandler) renderAlias(permalink string, p page.Page) (io.Reader, err
5353
if ps, ok := p.(*pageState); ok {
5454
base, templateDesc = ps.GetInternalTemplateBasePathAndDescriptor()
5555
}
56-
templateDesc.Layout = ""
56+
templateDesc.LayoutFromUser = ""
5757
templateDesc.Kind = ""
5858
templateDesc.OutputFormat = output.AliasHTMLFormat.Name
5959

hugolib/content_map_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,7 @@ title: p1
538538
-- content/p1/c.html --
539539
<p>c</p>
540540
-- layouts/_default/single.html --
541+
Path: {{ .Path }}|{{.Kind }}
541542
|{{ (.Resources.Get "a.html").RelPermalink -}}
542543
|{{ (.Resources.Get "b.html").RelPermalink -}}
543544
|{{ (.Resources.Get "c.html").Publish }}

hugolib/page.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -482,21 +482,21 @@ func (po *pageOutput) GetInternalTemplateBasePathAndDescriptor() (string, tplimp
482482
f := po.f
483483
base := p.PathInfo().BaseReTyped(p.m.pageConfig.Type)
484484
return base, tplimpl.TemplateDescriptor{
485-
Kind: p.Kind(),
486-
Lang: p.Language().Lang,
487-
Layout: p.Layout(),
488-
OutputFormat: f.Name,
489-
MediaType: f.MediaType.Type,
490-
IsPlainText: f.IsPlainText,
485+
Kind: p.Kind(),
486+
Lang: p.Language().Lang,
487+
LayoutFromUser: p.Layout(),
488+
OutputFormat: f.Name,
489+
MediaType: f.MediaType.Type,
490+
IsPlainText: f.IsPlainText,
491491
}
492492
}
493493

494494
func (p *pageState) resolveTemplate(layouts ...string) (*tplimpl.TemplInfo, bool, error) {
495495
dir, d := p.GetInternalTemplateBasePathAndDescriptor()
496496

497497
if len(layouts) > 0 {
498-
d.Layout = layouts[0]
499-
d.LayoutMustMatch = true
498+
d.LayoutFromUser = layouts[0]
499+
d.LayoutFromUserMustMatch = true
500500
}
501501

502502
q := tplimpl.TemplateQuery{

tpl/tplimpl/templatedescriptor.go

Lines changed: 37 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ const baseNameBaseof = "baseof"
2222
// This is used both as a key and in lookups.
2323
type TemplateDescriptor struct {
2424
// Group 1.
25-
Kind string // page, home, section, taxonomy, term (and only those)
26-
Layout string // list, single, baseof, mycustomlayout.
25+
Kind string // page, home, section, taxonomy, term (and only those)
26+
LayoutFromTemplate string // list, single, all,mycustomlayout
27+
LayoutFromUser string // custom layout set in front matter, e.g. list, single, all, mycustomlayout
2728

2829
// Group 2.
2930
OutputFormat string // rss, csv ...
@@ -34,23 +35,21 @@ type TemplateDescriptor struct {
3435
Variant2 string // contextual variant, e.g. "id" in render.
3536

3637
// Misc.
37-
LayoutMustMatch bool // If set, we only look for the exact layout.
38-
IsPlainText bool // Whether this is a plain text template.
38+
LayoutFromUserMustMatch bool // If set, we only look for the exact layout.
39+
IsPlainText bool // Whether this is a plain text template.
3940
}
4041

4142
func (d *TemplateDescriptor) normalizeFromFile() {
42-
// fmt.Println("normalizeFromFile", "kind:", d.Kind, "layout:", d.Layout, "of:", d.OutputFormat)
43-
44-
if d.Layout == d.OutputFormat {
45-
d.Layout = ""
43+
if d.LayoutFromTemplate == d.OutputFormat {
44+
d.LayoutFromTemplate = ""
4645
}
4746

4847
if d.Kind == kinds.KindTemporary {
4948
d.Kind = ""
5049
}
5150

52-
if d.Layout == d.Kind {
53-
d.Layout = ""
51+
if d.LayoutFromTemplate == d.Kind {
52+
d.LayoutFromTemplate = ""
5453
}
5554
}
5655

@@ -61,7 +60,7 @@ type descriptorHandler struct {
6160
// Note that this in this setup is usually a descriptor constructed from a page,
6261
// so we want to find the best match for that page.
6362
func (s descriptorHandler) compareDescriptors(category Category, isEmbedded bool, this, other TemplateDescriptor) weight {
64-
if this.LayoutMustMatch && this.Layout != other.Layout {
63+
if this.LayoutFromUserMustMatch && this.LayoutFromUser != other.LayoutFromTemplate {
6564
return weightNoMatch
6665
}
6766

@@ -94,21 +93,12 @@ func (this TemplateDescriptor) doCompare(category Category, isEmbedded bool, oth
9493
return w
9594
}
9695

97-
if other.Layout != "" && other.Layout != layoutAll && other.Layout != this.Layout {
98-
if isLayoutCustom(this.Layout) {
99-
if this.Kind == "" {
100-
this.Layout = ""
101-
} else if this.Kind == kinds.KindPage {
102-
this.Layout = layoutSingle
103-
} else {
104-
this.Layout = layoutList
96+
if other.LayoutFromTemplate != "" && other.LayoutFromTemplate != layoutAll {
97+
if this.LayoutFromUser == "" {
98+
if other.LayoutFromTemplate != this.LayoutFromTemplate {
99+
return w
105100
}
106101
}
107-
108-
// Test again.
109-
if other.Layout != this.Layout {
110-
return w
111-
}
112102
}
113103

114104
if other.Lang != "" && other.Lang != this.Lang {
@@ -123,7 +113,11 @@ func (this TemplateDescriptor) doCompare(category Category, isEmbedded bool, oth
123113
// We want e.g. home page in amp output format (media type text/html) to
124114
// find a template even if one isn't specified for that output format,
125115
// when one exist for the html output format (same media type).
126-
if category != CategoryBaseof && (this.Kind == "" || (this.Kind != other.Kind && (this.Layout != other.Layout && other.Layout != layoutAll))) {
116+
skip := category != CategoryBaseof && (this.Kind == "" || (this.Kind != other.Kind && (this.LayoutFromTemplate != other.LayoutFromTemplate && other.LayoutFromTemplate != layoutAll)))
117+
if this.LayoutFromUser != "" {
118+
skip = skip && (this.LayoutFromUser != other.LayoutFromTemplate)
119+
}
120+
if skip {
127121
return w
128122
}
129123

@@ -148,14 +142,14 @@ func (this TemplateDescriptor) doCompare(category Category, isEmbedded bool, oth
148142
}
149143

150144
const (
151-
weightKind = 3 // page, home, section, taxonomy, term (and only those)
152-
weightcustomLayout = 4 // custom layout (mylayout, set in e.g. front matter)
153-
weightLayout = 2 // standard layouts (single,list,all)
154-
weightOutputFormat = 2 // a configured output format (e.g. rss, html, json)
155-
weightMediaType = 1 // a configured media type (e.g. text/html, text/plain)
156-
weightLang = 1 // a configured language (e.g. en, nn, fr, ...)
157-
weightVariant1 = 4 // currently used for render hooks, e.g. "link", "image"
158-
weightVariant2 = 2 // currently used for render hooks, e.g. the language "go" in code blocks.
145+
weightKind = 3 // page, home, section, taxonomy, term (and only those)
146+
weightcustomLayout = 4 // custom layout (mylayout, set in e.g. front matter)
147+
weightLayoutStandard = 2 // standard layouts (single,list,all)
148+
weightOutputFormat = 2 // a configured output format (e.g. rss, html, json)
149+
weightMediaType = 1 // a configured media type (e.g. text/html, text/plain)
150+
weightLang = 1 // a configured language (e.g. en, nn, fr, ...)
151+
weightVariant1 = 4 // currently used for render hooks, e.g. "link", "image"
152+
weightVariant2 = 2 // currently used for render hooks, e.g. the language "go" in code blocks.
159153

160154
// We will use the values for group 2 and 3
161155
// if the distance up to the template is shorter than
@@ -179,14 +173,16 @@ func (this TemplateDescriptor) doCompare(category Category, isEmbedded bool, oth
179173
w.w2 = weight2Group1
180174
}
181175

182-
if other.Layout != "" && other.Layout == this.Layout || other.Layout == layoutAll {
183-
if isLayoutCustom(this.Layout) {
184-
w.w1 += weightcustomLayout
185-
w.w2 = weight2Group2
186-
} else {
187-
w.w1 += weightLayout
188-
w.w2 = weight2Group1
189-
}
176+
if this.LayoutFromUser == "" && other.LayoutFromTemplate != "" && (other.LayoutFromTemplate == this.LayoutFromTemplate || other.LayoutFromTemplate == layoutAll) {
177+
w.w1 += weightLayoutStandard
178+
w.w2 = weight2Group1
179+
180+
}
181+
182+
// LayoutCustom is only set in this (usually from Page.Layout).
183+
if this.LayoutFromUser != "" && this.LayoutFromUser == other.LayoutFromTemplate {
184+
w.w1 += weightcustomLayout
185+
w.w2 = weight2Group2
190186
}
191187

192188
if other.Lang != "" && other.Lang == this.Lang {

tpl/tplimpl/templatedescriptor_test.go

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -38,29 +38,29 @@ func TestTemplateDescriptorCompare(t *testing.T) {
3838
check(
3939

4040
CategoryBaseof,
41-
TemplateDescriptor{Kind: "", Layout: "", Lang: "", OutputFormat: "404", MediaType: "text/html"},
42-
TemplateDescriptor{Kind: "", Layout: "", Lang: "", OutputFormat: "html", MediaType: "text/html"},
41+
TemplateDescriptor{Kind: "", LayoutFromTemplate: "", Lang: "", OutputFormat: "404", MediaType: "text/html"},
42+
TemplateDescriptor{Kind: "", LayoutFromTemplate: "", Lang: "", OutputFormat: "html", MediaType: "text/html"},
4343
false,
4444
)
4545

4646
check(
4747
CategoryLayout,
4848
TemplateDescriptor{Kind: "", Lang: "en", OutputFormat: "404", MediaType: "text/html"},
49-
TemplateDescriptor{Kind: "", Layout: "", Lang: "", OutputFormat: "alias", MediaType: "text/html"},
49+
TemplateDescriptor{Kind: "", LayoutFromTemplate: "", Lang: "", OutputFormat: "alias", MediaType: "text/html"},
5050
true,
5151
)
5252

5353
less(
5454
CategoryLayout,
55-
TemplateDescriptor{Kind: kinds.KindHome, Layout: "list", OutputFormat: "html"},
56-
TemplateDescriptor{Layout: "list", OutputFormat: "html"},
55+
TemplateDescriptor{Kind: kinds.KindHome, LayoutFromTemplate: "list", OutputFormat: "html"},
56+
TemplateDescriptor{LayoutFromTemplate: "list", OutputFormat: "html"},
5757
TemplateDescriptor{Kind: kinds.KindHome, OutputFormat: "html"},
5858
)
5959

6060
check(
6161
CategoryLayout,
62-
TemplateDescriptor{Kind: kinds.KindHome, Layout: "list", OutputFormat: "html", MediaType: "text/html"},
63-
TemplateDescriptor{Kind: kinds.KindHome, Layout: "list", OutputFormat: "myformat", MediaType: "text/html"},
62+
TemplateDescriptor{Kind: kinds.KindHome, LayoutFromTemplate: "list", OutputFormat: "html", MediaType: "text/html"},
63+
TemplateDescriptor{Kind: kinds.KindHome, LayoutFromTemplate: "list", OutputFormat: "myformat", MediaType: "text/html"},
6464
false,
6565
)
6666
}
@@ -78,20 +78,20 @@ func BenchmarkCompareDescriptors(b *testing.B) {
7878
d1, d2 TemplateDescriptor
7979
}{
8080
{
81-
TemplateDescriptor{Kind: "", Layout: "", OutputFormat: "404", MediaType: "text/html", Lang: "en", Variant1: "", Variant2: "", LayoutMustMatch: false, IsPlainText: false},
82-
TemplateDescriptor{Kind: "", Layout: "", OutputFormat: "rss", MediaType: "application/rss+xml", Lang: "", Variant1: "", Variant2: "", LayoutMustMatch: false, IsPlainText: false},
81+
TemplateDescriptor{Kind: "", LayoutFromTemplate: "", OutputFormat: "404", MediaType: "text/html", Lang: "en", Variant1: "", Variant2: "", LayoutFromUserMustMatch: false, IsPlainText: false},
82+
TemplateDescriptor{Kind: "", LayoutFromTemplate: "", OutputFormat: "rss", MediaType: "application/rss+xml", Lang: "", Variant1: "", Variant2: "", LayoutFromUserMustMatch: false, IsPlainText: false},
8383
},
8484
{
85-
TemplateDescriptor{Kind: "page", Layout: "single", OutputFormat: "html", MediaType: "text/html", Lang: "en", Variant1: "", Variant2: "", LayoutMustMatch: false, IsPlainText: false},
86-
TemplateDescriptor{Kind: "", Layout: "list", OutputFormat: "", MediaType: "application/rss+xml", Lang: "", Variant1: "", Variant2: "", LayoutMustMatch: false, IsPlainText: false},
85+
TemplateDescriptor{Kind: "page", LayoutFromTemplate: "single", OutputFormat: "html", MediaType: "text/html", Lang: "en", Variant1: "", Variant2: "", LayoutFromUserMustMatch: false, IsPlainText: false},
86+
TemplateDescriptor{Kind: "", LayoutFromTemplate: "list", OutputFormat: "", MediaType: "application/rss+xml", Lang: "", Variant1: "", Variant2: "", LayoutFromUserMustMatch: false, IsPlainText: false},
8787
},
8888
{
89-
TemplateDescriptor{Kind: "page", Layout: "single", OutputFormat: "html", MediaType: "text/html", Lang: "en", Variant1: "", Variant2: "", LayoutMustMatch: false, IsPlainText: false},
90-
TemplateDescriptor{Kind: "", Layout: "", OutputFormat: "alias", MediaType: "text/html", Lang: "", Variant1: "", Variant2: "", LayoutMustMatch: false, IsPlainText: false},
89+
TemplateDescriptor{Kind: "page", LayoutFromTemplate: "single", OutputFormat: "html", MediaType: "text/html", Lang: "en", Variant1: "", Variant2: "", LayoutFromUserMustMatch: false, IsPlainText: false},
90+
TemplateDescriptor{Kind: "", LayoutFromTemplate: "", OutputFormat: "alias", MediaType: "text/html", Lang: "", Variant1: "", Variant2: "", LayoutFromUserMustMatch: false, IsPlainText: false},
9191
},
9292
{
93-
TemplateDescriptor{Kind: "page", Layout: "single", OutputFormat: "rss", MediaType: "application/rss+xml", Lang: "en", Variant1: "", Variant2: "", LayoutMustMatch: false, IsPlainText: false},
94-
TemplateDescriptor{Kind: "", Layout: "single", OutputFormat: "rss", MediaType: "application/rss+xml", Lang: "nn", Variant1: "", Variant2: "", LayoutMustMatch: false, IsPlainText: false},
93+
TemplateDescriptor{Kind: "page", LayoutFromTemplate: "single", OutputFormat: "rss", MediaType: "application/rss+xml", Lang: "en", Variant1: "", Variant2: "", LayoutFromUserMustMatch: false, IsPlainText: false},
94+
TemplateDescriptor{Kind: "", LayoutFromTemplate: "single", OutputFormat: "rss", MediaType: "application/rss+xml", Lang: "nn", Variant1: "", Variant2: "", LayoutFromUserMustMatch: false, IsPlainText: false},
9595
},
9696
}
9797

tpl/tplimpl/templatestore.go

Lines changed: 25 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -378,11 +378,11 @@ func (q *TemplateQuery) init() {
378378
} else if kinds.GetKindMain(q.Desc.Kind) == "" {
379379
q.Desc.Kind = ""
380380
}
381-
if q.Desc.Layout == "" && q.Desc.Kind != "" {
381+
if q.Desc.LayoutFromTemplate == "" && q.Desc.Kind != "" {
382382
if q.Desc.Kind == kinds.KindPage {
383-
q.Desc.Layout = layoutSingle
383+
q.Desc.LayoutFromTemplate = layoutSingle
384384
} else {
385-
q.Desc.Layout = layoutList
385+
q.Desc.LayoutFromTemplate = layoutList
386386
}
387387
}
388388

@@ -447,7 +447,7 @@ func (s *TemplateStore) FindAllBaseTemplateCandidates(overlayKey string, desc Te
447447
continue
448448
}
449449

450-
if vv.D.isKindInLayout(desc.Layout) && s.dh.compareDescriptors(CategoryBaseof, false, descBaseof, vv.D).w1 > 0 {
450+
if vv.D.isKindInLayout(desc.LayoutFromTemplate) && s.dh.compareDescriptors(CategoryBaseof, false, descBaseof, vv.D).w1 > 0 {
451451
result = append(result, keyTemplateInfo{Key: k, Info: vv})
452452
}
453453
}
@@ -549,7 +549,7 @@ func (s *TemplateStore) LookupPartial(pth string) *TemplInfo {
549549
ti, _ := s.cacheLookupPartials.GetOrCreate(pth, func() (*TemplInfo, error) {
550550
d := s.templateDescriptorFromPath(pth)
551551
desc := d.Desc
552-
if desc.Layout != "" {
552+
if desc.LayoutFromTemplate != "" {
553553
panic("shortcode template descriptor must not have a layout")
554554
}
555555
best := s.getBest()
@@ -610,7 +610,7 @@ func (s *TemplateStore) PrintDebug(prefix string, category Category, w io.Writer
610610
return
611611
}
612612
s := strings.ReplaceAll(strings.TrimSpace(vv.content), "\n", " ")
613-
ts := fmt.Sprintf("kind: %q layout: %q content: %.30s", vv.D.Kind, vv.D.Layout, s)
613+
ts := fmt.Sprintf("kind: %q layout: %q content: %.30s", vv.D.Kind, vv.D.LayoutFromTemplate, s)
614614
fmt.Fprintf(w, "%s%s %s\n", strings.Repeat(" ", level), key, ts)
615615
}
616616
s.treeMain.WalkPrefix(prefix, func(key string, v map[nodeKey]*TemplInfo) (bool, error) {
@@ -1573,12 +1573,12 @@ func (s *TemplateStore) toKeyCategoryAndDescriptor(p *paths.Path) (string, strin
15731573
}
15741574

15751575
d := TemplateDescriptor{
1576-
Lang: p.Lang(),
1577-
OutputFormat: p.OutputFormat(),
1578-
MediaType: mediaType.Type,
1579-
Kind: p.Kind(),
1580-
Layout: layout,
1581-
IsPlainText: outputFormat.IsPlainText,
1576+
Lang: p.Lang(),
1577+
OutputFormat: p.OutputFormat(),
1578+
MediaType: mediaType.Type,
1579+
Kind: p.Kind(),
1580+
LayoutFromTemplate: layout,
1581+
IsPlainText: outputFormat.IsPlainText,
15821582
}
15831583

15841584
d.normalizeFromFile()
@@ -1611,7 +1611,7 @@ func (s *TemplateStore) toKeyCategoryAndDescriptor(p *paths.Path) (string, strin
16111611
}
16121612

16131613
if category == CategoryPartial {
1614-
d.Layout = ""
1614+
d.LayoutFromTemplate = ""
16151615
k1 = p.PathNoIdentifier()
16161616
}
16171617

@@ -1626,15 +1626,15 @@ func (s *TemplateStore) toKeyCategoryAndDescriptor(p *paths.Path) (string, strin
16261626
}
16271627

16281628
// Legacy layout for home page.
1629-
if d.Layout == "index" {
1629+
if d.LayoutFromTemplate == "index" {
16301630
if d.Kind == "" {
16311631
d.Kind = kinds.KindHome
16321632
}
1633-
d.Layout = ""
1633+
d.LayoutFromTemplate = ""
16341634
}
16351635

1636-
if d.Layout == d.Kind {
1637-
d.Layout = ""
1636+
if d.LayoutFromTemplate == d.Kind {
1637+
d.LayoutFromTemplate = ""
16381638
}
16391639

16401640
k1 = strings.TrimPrefix(k1, "/_default")
@@ -1645,7 +1645,7 @@ func (s *TemplateStore) toKeyCategoryAndDescriptor(p *paths.Path) (string, strin
16451645
if category == CategoryMarkup {
16461646
// We store all template nodes for a given directory on the same level.
16471647
k1 = strings.TrimSuffix(k1, "/_markup")
1648-
parts := strings.Split(d.Layout, "-")
1648+
parts := strings.Split(d.LayoutFromTemplate, "-")
16491649
if len(parts) < 2 {
16501650
return "", "", 0, TemplateDescriptor{}, fmt.Errorf("unrecognized render hook template")
16511651
}
@@ -1654,7 +1654,7 @@ func (s *TemplateStore) toKeyCategoryAndDescriptor(p *paths.Path) (string, strin
16541654
if len(parts) > 2 {
16551655
d.Variant2 = parts[2]
16561656
}
1657-
d.Layout = "" // This allows using page layout as part of the key for lookups.
1657+
d.LayoutFromTemplate = "" // This allows using page layout as part of the key for lookups.
16581658
}
16591659

16601660
return k1, k2, category, d, nil
@@ -1868,8 +1868,8 @@ func (best *bestMatch) isBetter(w weight, ti *TemplInfo) bool {
18681868
return true
18691869
}
18701870

1871-
if ti.D.Layout != "" && best.desc.Layout != "" {
1872-
return ti.D.Layout != layoutAll
1871+
if ti.D.LayoutFromTemplate != "" && best.desc.LayoutFromTemplate != "" {
1872+
return ti.D.LayoutFromTemplate != layoutAll
18731873
}
18741874

18751875
return w.distance < best.w.distance || ti.PathInfo.Path() < best.templ.PathInfo.Path()
@@ -1920,17 +1920,6 @@ type weight struct {
19201920
distance int
19211921
}
19221922

1923-
func (w weight) isEqualWeights(other weight) bool {
1924-
return w.w1 == other.w1 && w.w2 == other.w2 && w.w3 == other.w3
1925-
}
1926-
1927-
func isLayoutCustom(s string) bool {
1928-
if s == "" || isLayoutStandard(s) {
1929-
return false
1930-
}
1931-
return true
1932-
}
1933-
19341923
func isLayoutStandard(s string) bool {
19351924
switch s {
19361925
case layoutAll, layoutList, layoutSingle:
@@ -1940,6 +1929,10 @@ func isLayoutStandard(s string) bool {
19401929
}
19411930
}
19421931

1932+
func (w weight) isEqualWeights(other weight) bool {
1933+
return w.w1 == other.w1 && w.w2 == other.w2 && w.w3 == other.w3
1934+
}
1935+
19431936
func configureSiteStorage(opts SiteOptions, watching bool) *storeSite {
19441937
funcsv := make(map[string]reflect.Value)
19451938

0 commit comments

Comments
 (0)