mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-07 20:30:36 -05:00
ba1d0051b4
So we can use it and output.Format as map key etc. This commit also fixes the media.Type implementation so it does not need to mutate itself to handle different suffixes for the same MIME type, e.g. jpg vs. jpeg. This means that there are no Suffix or FullSuffix on media.Type anymore. Fixes #8317 Fixes #8324
290 lines
6.8 KiB
Go
290 lines
6.8 KiB
Go
// Copyright 2017-present 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 output
|
|
|
|
import (
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/gohugoio/hugo/helpers"
|
|
)
|
|
|
|
// These may be used as content sections with potential conflicts. Avoid that.
|
|
var reservedSections = map[string]bool{
|
|
"shortcodes": true,
|
|
"partials": true,
|
|
}
|
|
|
|
// LayoutDescriptor describes how a layout should be chosen. This is
|
|
// typically built from a Page.
|
|
type LayoutDescriptor struct {
|
|
Type string
|
|
Section string
|
|
Kind string
|
|
Lang string
|
|
Layout string
|
|
// LayoutOverride indicates what we should only look for the above layout.
|
|
LayoutOverride bool
|
|
|
|
RenderingHook bool
|
|
Baseof bool
|
|
}
|
|
|
|
func (d LayoutDescriptor) isList() bool {
|
|
return !d.RenderingHook && d.Kind != "page" && d.Kind != "404"
|
|
}
|
|
|
|
// LayoutHandler calculates the layout template to use to render a given output type.
|
|
type LayoutHandler struct {
|
|
mu sync.RWMutex
|
|
cache map[layoutCacheKey][]string
|
|
}
|
|
|
|
type layoutCacheKey struct {
|
|
d LayoutDescriptor
|
|
f string
|
|
}
|
|
|
|
// NewLayoutHandler creates a new LayoutHandler.
|
|
func NewLayoutHandler() *LayoutHandler {
|
|
return &LayoutHandler{cache: make(map[layoutCacheKey][]string)}
|
|
}
|
|
|
|
// For returns a layout for the given LayoutDescriptor and options.
|
|
// Layouts are rendered and cached internally.
|
|
func (l *LayoutHandler) For(d LayoutDescriptor, f Format) ([]string, error) {
|
|
// We will get lots of requests for the same layouts, so avoid recalculations.
|
|
key := layoutCacheKey{d, f.Name}
|
|
l.mu.RLock()
|
|
if cacheVal, found := l.cache[key]; found {
|
|
l.mu.RUnlock()
|
|
return cacheVal, nil
|
|
}
|
|
l.mu.RUnlock()
|
|
|
|
layouts := resolvePageTemplate(d, f)
|
|
|
|
layouts = helpers.UniqueStringsReuse(layouts)
|
|
|
|
l.mu.Lock()
|
|
l.cache[key] = layouts
|
|
l.mu.Unlock()
|
|
|
|
return layouts, nil
|
|
}
|
|
|
|
type layoutBuilder struct {
|
|
layoutVariations []string
|
|
typeVariations []string
|
|
d LayoutDescriptor
|
|
f Format
|
|
}
|
|
|
|
func (l *layoutBuilder) addLayoutVariations(vars ...string) {
|
|
for _, layoutVar := range vars {
|
|
if l.d.Baseof && layoutVar != "baseof" {
|
|
l.layoutVariations = append(l.layoutVariations, layoutVar+"-baseof")
|
|
continue
|
|
}
|
|
if !l.d.RenderingHook && !l.d.Baseof && l.d.LayoutOverride && layoutVar != l.d.Layout {
|
|
continue
|
|
}
|
|
l.layoutVariations = append(l.layoutVariations, layoutVar)
|
|
}
|
|
}
|
|
|
|
func (l *layoutBuilder) addTypeVariations(vars ...string) {
|
|
for _, typeVar := range vars {
|
|
if !reservedSections[typeVar] {
|
|
if l.d.RenderingHook {
|
|
typeVar = typeVar + renderingHookRoot
|
|
}
|
|
l.typeVariations = append(l.typeVariations, typeVar)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (l *layoutBuilder) addSectionType() {
|
|
if l.d.Section != "" {
|
|
l.addTypeVariations(l.d.Section)
|
|
}
|
|
}
|
|
|
|
func (l *layoutBuilder) addKind() {
|
|
l.addLayoutVariations(l.d.Kind)
|
|
l.addTypeVariations(l.d.Kind)
|
|
}
|
|
|
|
const renderingHookRoot = "/_markup"
|
|
|
|
func resolvePageTemplate(d LayoutDescriptor, f Format) []string {
|
|
b := &layoutBuilder{d: d, f: f}
|
|
|
|
if !d.RenderingHook && d.Layout != "" {
|
|
b.addLayoutVariations(d.Layout)
|
|
}
|
|
if d.Type != "" {
|
|
b.addTypeVariations(d.Type)
|
|
}
|
|
|
|
if d.RenderingHook {
|
|
b.addLayoutVariations(d.Kind)
|
|
b.addSectionType()
|
|
}
|
|
|
|
switch d.Kind {
|
|
case "page":
|
|
b.addLayoutVariations("single")
|
|
b.addSectionType()
|
|
case "home":
|
|
b.addLayoutVariations("index", "home")
|
|
// Also look in the root
|
|
b.addTypeVariations("")
|
|
case "section":
|
|
if d.Section != "" {
|
|
b.addLayoutVariations(d.Section)
|
|
}
|
|
b.addSectionType()
|
|
b.addKind()
|
|
case "term":
|
|
b.addKind()
|
|
if d.Section != "" {
|
|
b.addLayoutVariations(d.Section)
|
|
}
|
|
b.addLayoutVariations("taxonomy")
|
|
b.addTypeVariations("taxonomy")
|
|
b.addSectionType()
|
|
case "taxonomy":
|
|
if d.Section != "" {
|
|
b.addLayoutVariations(d.Section + ".terms")
|
|
}
|
|
b.addSectionType()
|
|
b.addLayoutVariations("terms")
|
|
// For legacy reasons this is deliberately put last.
|
|
b.addKind()
|
|
case "404":
|
|
b.addLayoutVariations("404")
|
|
b.addTypeVariations("")
|
|
}
|
|
|
|
isRSS := f.Name == RSSFormat.Name
|
|
if !d.RenderingHook && !d.Baseof && isRSS {
|
|
// The historic and common rss.xml case
|
|
b.addLayoutVariations("")
|
|
}
|
|
|
|
if d.Baseof || d.Kind != "404" {
|
|
// Most have _default in their lookup path
|
|
b.addTypeVariations("_default")
|
|
}
|
|
|
|
if d.isList() {
|
|
// Add the common list type
|
|
b.addLayoutVariations("list")
|
|
}
|
|
|
|
if d.Baseof {
|
|
b.addLayoutVariations("baseof")
|
|
}
|
|
|
|
layouts := b.resolveVariations()
|
|
|
|
if !d.RenderingHook && !d.Baseof && isRSS {
|
|
layouts = append(layouts, "_internal/_default/rss.xml")
|
|
}
|
|
|
|
return layouts
|
|
}
|
|
|
|
func (l *layoutBuilder) resolveVariations() []string {
|
|
var layouts []string
|
|
|
|
var variations []string
|
|
name := strings.ToLower(l.f.Name)
|
|
|
|
if l.d.Lang != "" {
|
|
// We prefer the most specific type before language.
|
|
variations = append(variations, []string{l.d.Lang + "." + name, name, l.d.Lang}...)
|
|
} else {
|
|
variations = append(variations, name)
|
|
}
|
|
|
|
variations = append(variations, "")
|
|
|
|
for _, typeVar := range l.typeVariations {
|
|
for _, variation := range variations {
|
|
for _, layoutVar := range l.layoutVariations {
|
|
if variation == "" && layoutVar == "" {
|
|
continue
|
|
}
|
|
|
|
s := constructLayoutPath(typeVar, layoutVar, variation, l.f.MediaType.FirstSuffix.Suffix)
|
|
if s != "" {
|
|
layouts = append(layouts, s)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return layouts
|
|
}
|
|
|
|
// constructLayoutPath constructs a layout path given a type, layout,
|
|
// variations, and extension. The path constructed follows the pattern of
|
|
// type/layout.variations.extension. If any value is empty, it will be left out
|
|
// of the path construction.
|
|
//
|
|
// Path construction requires at least 2 of 3 out of layout, variations, and extension.
|
|
// If more than one of those is empty, an empty string is returned.
|
|
func constructLayoutPath(typ, layout, variations, extension string) string {
|
|
// we already know that layout and variations are not both empty because of
|
|
// checks in resolveVariants().
|
|
if extension == "" && (layout == "" || variations == "") {
|
|
return ""
|
|
}
|
|
|
|
// Commence valid path construction...
|
|
|
|
var (
|
|
p strings.Builder
|
|
needDot bool
|
|
)
|
|
|
|
if typ != "" {
|
|
p.WriteString(typ)
|
|
p.WriteString("/")
|
|
}
|
|
|
|
if layout != "" {
|
|
p.WriteString(layout)
|
|
needDot = true
|
|
}
|
|
|
|
if variations != "" {
|
|
if needDot {
|
|
p.WriteString(".")
|
|
}
|
|
p.WriteString(variations)
|
|
needDot = true
|
|
}
|
|
|
|
if extension != "" {
|
|
if needDot {
|
|
p.WriteString(".")
|
|
}
|
|
p.WriteString(extension)
|
|
}
|
|
|
|
return p.String()
|
|
}
|