1
0
Fork 0
hugo/tpl/tplimpl/templatedescriptor.go

232 lines
7.0 KiB
Go

// Copyright 2025 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tplimpl
import (
"github.com/gohugoio/hugo/resources/kinds"
)
const baseNameBaseof = "baseof"
// This is used both as a key and in lookups.
type TemplateDescriptor struct {
// Group 1.
Kind string // page, home, section, taxonomy, term (and only those)
LayoutFromTemplate string // list, single, all,mycustomlayout
LayoutFromUser string // custom layout set in front matter, e.g. list, single, all, mycustomlayout
// Group 2.
OutputFormat string // rss, csv ...
MediaType string // text/html, text/plain, ...
Lang string // en, nn, fr, ...
Variant1 string // contextual variant, e.g. "link" in render hooks."
Variant2 string // contextual variant, e.g. "id" in render.
// Misc.
LayoutFromUserMustMatch bool // If set, we only look for the exact layout.
IsPlainText bool // Whether this is a plain text template.
}
func (d *TemplateDescriptor) normalizeFromFile() {
if d.LayoutFromTemplate == d.OutputFormat {
d.LayoutFromTemplate = ""
}
if d.Kind == kinds.KindTemporary {
d.Kind = ""
}
if d.LayoutFromTemplate == d.Kind {
d.LayoutFromTemplate = ""
}
}
type descriptorHandler struct {
opts StoreOptions
}
// Note that this in this setup is usually a descriptor constructed from a page,
// so we want to find the best match for that page.
func (s descriptorHandler) compareDescriptors(category Category, isEmbedded bool, this, other TemplateDescriptor) weight {
if this.LayoutFromUserMustMatch && this.LayoutFromUser != other.LayoutFromTemplate {
return weightNoMatch
}
w := this.doCompare(category, isEmbedded, other)
if w.w1 <= 0 {
if category == CategoryMarkup && (this.Variant1 == other.Variant1) && (this.Variant2 == other.Variant2 || this.Variant2 != "" && other.Variant2 == "") {
// See issue 13242.
if this.OutputFormat != other.OutputFormat && this.OutputFormat == s.opts.DefaultOutputFormat {
return w
}
w.w1 = 1
return w
}
}
return w
}
//lint:ignore ST1006 this vs other makes it easier to reason about.
func (this TemplateDescriptor) doCompare(category Category, isEmbedded bool, other TemplateDescriptor) weight {
w := weightNoMatch
// HTML in plain text is OK, but not the other way around.
if other.IsPlainText && !this.IsPlainText {
return w
}
if other.Kind != "" && other.Kind != this.Kind {
return w
}
if other.LayoutFromTemplate != "" && other.LayoutFromTemplate != layoutAll {
if this.LayoutFromUser == "" {
if other.LayoutFromTemplate != this.LayoutFromTemplate {
return w
}
} else if isLayoutStandard(this.LayoutFromUser) {
if other.LayoutFromTemplate != this.LayoutFromUser {
return w
}
}
}
if other.Lang != "" && other.Lang != this.Lang {
return w
}
if other.OutputFormat != "" && other.OutputFormat != this.OutputFormat {
if this.MediaType != other.MediaType {
return w
}
// We want e.g. home page in amp output format (media type text/html) to
// find a template even if one isn't specified for that output format,
// when one exist for the html output format (same media type).
skip := category != CategoryBaseof && (this.Kind == "" || (this.Kind != other.Kind && (this.LayoutFromTemplate != other.LayoutFromTemplate && other.LayoutFromTemplate != layoutAll)))
if this.LayoutFromUser != "" {
skip = skip && (this.LayoutFromUser != other.LayoutFromTemplate)
}
if skip {
return w
}
// Continue.
}
// One example of variant1 and 2 is for render codeblocks:
// variant1=codeblock, variant2=go (language).
if other.Variant1 != "" && other.Variant1 != this.Variant1 {
return w
}
if isEmbedded {
if other.Variant2 != "" && other.Variant2 != this.Variant2 {
return w
}
} else {
// If both are set and different, no match.
if other.Variant2 != "" && this.Variant2 != "" && other.Variant2 != this.Variant2 {
return w
}
}
const (
weightKind = 3 // page, home, section, taxonomy, term (and only those)
weightcustomLayout = 4 // custom layout (mylayout, set in e.g. front matter)
weightLayoutStandard = 2 // standard layouts (single,list,all)
weightOutputFormat = 2 // a configured output format (e.g. rss, html, json)
weightMediaType = 1 // a configured media type (e.g. text/html, text/plain)
weightLang = 1 // a configured language (e.g. en, nn, fr, ...)
weightVariant1 = 4 // currently used for render hooks, e.g. "link", "image"
weightVariant2 = 2 // currently used for render hooks, e.g. the language "go" in code blocks.
// We will use the values for group 2 and 3
// if the distance up to the template is shorter than
// the one we're comparing with.
// E.g for a page in /posts/mypage.md with the
// two templates /layouts/posts/single.html and /layouts/page.html,
// the first one is the best match even if the second one
// has a higher w1 value.
weight2Group1 = 1 // kind, standardl layout (single,list,all)
weight2Group2 = 2 // custom layout (mylayout)
weight3 = 1 // for media type, lang, output format.
)
// Now we now know that the other descriptor is a subset of this.
// Now calculate the weights.
w.w1++
if other.Kind != "" && other.Kind == this.Kind {
w.w1 += weightKind
w.w2 = weight2Group1
}
if this.LayoutFromUser == "" && other.LayoutFromTemplate != "" && (other.LayoutFromTemplate == this.LayoutFromTemplate || other.LayoutFromTemplate == layoutAll) {
w.w1 += weightLayoutStandard
w.w2 = weight2Group1
}
// LayoutCustom is only set in this (usually from Page.Layout).
if this.LayoutFromUser != "" && this.LayoutFromUser == other.LayoutFromTemplate {
w.w1 += weightcustomLayout
w.w2 = weight2Group2
}
if other.Lang != "" && other.Lang == this.Lang {
w.w1 += weightLang
w.w3 += weight3
}
if other.OutputFormat != "" && other.OutputFormat == this.OutputFormat {
w.w1 += weightOutputFormat
w.w3 += weight3
}
if other.MediaType != "" && other.MediaType == this.MediaType {
w.w1 += weightMediaType
w.w3 += weight3
}
if other.Variant1 != "" && other.Variant1 == this.Variant1 {
w.w1 += weightVariant1
}
if other.Variant1 != "" && other.Variant2 == this.Variant2 {
w.w1 += weightVariant2
}
return w
}
func (d TemplateDescriptor) IsZero() bool {
return d == TemplateDescriptor{}
}
//lint:ignore ST1006 this vs other makes it easier to reason about.
func (this TemplateDescriptor) isKindInLayout(layout string) bool {
if this.Kind == "" {
return true
}
if this.Kind != kinds.KindPage {
return layout != layoutSingle
}
return layout != layoutList
}