templatedescriptor.go - hugo - [fork] hugo port for 9front
(HTM) git clone https://git.drkhsh.at/hugo.git
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) Submodules
(DIR) README
(DIR) LICENSE
---
templatedescriptor.go (7437B)
---
1 // Copyright 2025 The Hugo Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 package tplimpl
15
16 import (
17 "github.com/gohugoio/hugo/resources/kinds"
18 )
19
20 const baseNameBaseof = "baseof"
21
22 // This is used both as a key and in lookups.
23 type TemplateDescriptor struct {
24 // Group 1.
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
28
29 // Group 2.
30 OutputFormat string // rss, csv ...
31 MediaType string // text/html, text/plain, ...
32 Lang string // en, nn, fr, ...
33
34 Variant1 string // contextual variant, e.g. "link" in render hooks."
35 Variant2 string // contextual variant, e.g. "id" in render.
36
37 // Misc.
38 LayoutFromUserMustMatch bool // If set, we only look for the exact layout.
39 IsPlainText bool // Whether this is a plain text template.
40 AlwaysAllowPlainText bool // Whether to e.g. allow plain text templates to be rendered in HTML.
41 }
42
43 func (d *TemplateDescriptor) normalizeFromFile() {
44 if d.LayoutFromTemplate == d.OutputFormat {
45 d.LayoutFromTemplate = ""
46 }
47
48 if d.Kind == kinds.KindTemporary {
49 d.Kind = ""
50 }
51
52 if d.LayoutFromTemplate == d.Kind {
53 d.LayoutFromTemplate = ""
54 }
55 }
56
57 type descriptorHandler struct {
58 opts StoreOptions
59 }
60
61 // Note that this in this setup is usually a descriptor constructed from a page,
62 // so we want to find the best match for that page.
63 func (s descriptorHandler) compareDescriptors(category Category, isEmbedded bool, this, other TemplateDescriptor) weight {
64 if this.LayoutFromUserMustMatch && this.LayoutFromUser != other.LayoutFromTemplate {
65 return weightNoMatch
66 }
67
68 w := this.doCompare(category, s.opts.DefaultContentLanguage, other)
69
70 if w.w1 <= 0 {
71 if category == CategoryMarkup && (this.Variant1 == other.Variant1) && (this.Variant2 == other.Variant2 || this.Variant2 != "" && other.Variant2 == "") {
72 // See issue 13242.
73 if this.OutputFormat != other.OutputFormat && this.OutputFormat == s.opts.DefaultOutputFormat {
74 return w
75 }
76
77 w.w1 = 1
78 }
79
80 if category == CategoryShortcode {
81 if (this.IsPlainText == other.IsPlainText || !other.IsPlainText) || this.AlwaysAllowPlainText {
82 w.w1 = 1
83 }
84 }
85 }
86
87 return w
88 }
89
90 //lint:ignore ST1006 this vs other makes it easier to reason about.
91 func (this TemplateDescriptor) doCompare(category Category, defaultContentLanguage string, other TemplateDescriptor) weight {
92 w := weightNoMatch
93
94 if !this.AlwaysAllowPlainText {
95 // HTML in plain text is OK, but not the other way around.
96 if other.IsPlainText && !this.IsPlainText {
97 return w
98 }
99 }
100
101 if other.Kind != "" && other.Kind != this.Kind {
102 return w
103 }
104
105 if other.LayoutFromTemplate != "" && other.LayoutFromTemplate != layoutAll {
106 if this.LayoutFromUser == "" || this.LayoutFromUser != other.LayoutFromTemplate {
107 if other.LayoutFromTemplate != this.LayoutFromTemplate {
108 return w
109 }
110 }
111 }
112
113 if other.Lang != "" && other.Lang != this.Lang {
114 return w
115 }
116
117 if other.OutputFormat != "" && other.OutputFormat != this.OutputFormat {
118 if this.MediaType != other.MediaType {
119 return w
120 }
121
122 // We want e.g. home page in amp output format (media type text/html) to
123 // find a template even if one isn't specified for that output format,
124 // when one exist for the html output format (same media type).
125 skip := category != CategoryBaseof && (this.Kind == "" || (this.Kind != other.Kind && (this.LayoutFromTemplate != other.LayoutFromTemplate && other.LayoutFromTemplate != layoutAll)))
126 if this.LayoutFromUser != "" {
127 skip = skip && (this.LayoutFromUser != other.LayoutFromTemplate)
128 }
129 if skip {
130 return w
131 }
132
133 // Continue.
134 }
135
136 if other.MediaType != this.MediaType {
137 return w
138 }
139
140 // One example of variant1 and 2 is for render codeblocks:
141 // variant1=codeblock, variant2=go (language).
142 if other.Variant1 != "" {
143 if other.Variant1 != this.Variant1 {
144 return w
145 }
146
147 if other.Variant2 != "" && other.Variant2 != this.Variant2 {
148 return w
149 }
150 }
151
152 const (
153 weightKind = 5 // page, home, section, taxonomy, term (and only those)
154 weightcustomLayout = 6 // custom layout (mylayout, set in e.g. front matter)
155 weightLayoutStandard = 4 // standard layouts (single,list)
156 weightLayoutAll = 2 // the "all" layout
157 weightOutputFormat = 4 // a configured output format (e.g. rss, html, json)
158 weightMediaType = 1 // a configured media type (e.g. text/html, text/plain)
159 weightLang = 1 // a configured language (e.g. en, nn, fr, ...)
160 weightVariant1 = 6 // currently used for render hooks, e.g. "link", "image"
161 weightVariant2 = 4 // currently used for render hooks, e.g. the language "go" in code blocks.
162
163 // We will use the values for group 2 and 3
164 // if the distance up to the template is shorter than
165 // the one we're comparing with.
166 // E.g for a page in /posts/mypage.md with the
167 // two templates /layouts/posts/single.html and /layouts/page.html,
168 // the first one is the best match even if the second one
169 // has a higher w1 value.
170 weight2Group1 = 1 // kind, standardl layout (single,list,all)
171 weight2Group2 = 2 // custom layout (mylayout)
172
173 weight3 = 1 // for media type, lang, output format.
174 )
175
176 // Now we now know that the other descriptor is a subset of this.
177 // Now calculate the weights.
178 w.w1++
179
180 if other.Kind != "" && other.Kind == this.Kind {
181 w.w1 += weightKind
182 w.w2 = weight2Group1
183 }
184
185 if other.LayoutFromTemplate != "" && (other.LayoutFromTemplate == this.LayoutFromTemplate) {
186 w.w1 += weightLayoutStandard
187 w.w2 = weight2Group1
188 } else if other.LayoutFromTemplate == layoutAll {
189 w.w1 += weightLayoutAll
190 w.w2 = weight2Group1
191 }
192
193 // LayoutCustom is only set in this (usually from Page.Layout).
194 if this.LayoutFromUser != "" && this.LayoutFromUser == other.LayoutFromTemplate {
195 w.w1 += weightcustomLayout
196 w.w2 = weight2Group2
197 }
198
199 if (other.Lang != "" && other.Lang == this.Lang) || (other.Lang == "" && this.Lang == defaultContentLanguage) {
200 w.w1 += weightLang
201 w.w3 += weight3
202 }
203
204 if other.OutputFormat != "" && other.OutputFormat == this.OutputFormat {
205 w.w1 += weightOutputFormat
206 w.w3 += weight3
207 }
208
209 if other.MediaType != "" && other.MediaType == this.MediaType {
210 w.w1 += weightMediaType
211 w.w3 += weight3
212 }
213
214 if other.Variant1 != "" && other.Variant1 == this.Variant1 {
215 w.w1 += weightVariant1
216 }
217
218 if other.Variant1 != "" && other.Variant2 == this.Variant2 {
219 w.w1 += weightVariant2
220 }
221
222 return w
223 }
224
225 func (d TemplateDescriptor) IsZero() bool {
226 return d == TemplateDescriptor{}
227 }
228
229 //lint:ignore ST1006 this vs other makes it easier to reason about.
230 func (this TemplateDescriptor) isKindInLayout(layout string) bool {
231 if this.Kind == "" {
232 return true
233 }
234 if this.Kind != kinds.KindPage {
235 return layout != layoutSingle
236 }
237 return layout != layoutList
238 }