mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-29 10:42:05 -05:00
b874a1ba7a
Before this commit, `Suffix` on `MediaType` was used both to set a custom file suffix and as a way to augment the mediatype definition (what you see after the "+", e.g. "image/svg+xml"). This had its limitations. For one, it was only possible with one file extension per MIME type. Now you can specify multiple file suffixes using "suffixes", but you need to specify the full MIME type identifier: [mediaTypes] [mediaTypes."image/svg+xml"] suffixes = ["svg", "abc ] In most cases, it will be enough to just change: [mediaTypes] [mediaTypes."my/custom-mediatype"] suffix = "txt" To: [mediaTypes] [mediaTypes."my/custom-mediatype"] suffixes = ["txt"] Hugo will still respect values set in "suffix" if no value for "suffixes" is provided, but this will be removed in a future release. Note that you can still get the Media Type's suffix from a template: {{ $mediaType.Suffix }}. But this will now map to the MIME type filename. Fixes #4920
272 lines
6 KiB
Go
272 lines
6 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 (
|
|
"fmt"
|
|
"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
|
|
}
|
|
|
|
// 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 = prependTextPrefixIfNeeded(f, layouts...)
|
|
layouts = helpers.UniqueStrings(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.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] {
|
|
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)
|
|
}
|
|
|
|
func resolvePageTemplate(d LayoutDescriptor, f Format) []string {
|
|
|
|
b := &layoutBuilder{d: d, f: f}
|
|
|
|
if d.Layout != "" {
|
|
b.addLayoutVariations(d.Layout)
|
|
}
|
|
|
|
if d.Type != "" {
|
|
b.addTypeVariations(d.Type)
|
|
}
|
|
|
|
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 "taxonomy":
|
|
if d.Section != "" {
|
|
b.addLayoutVariations(d.Section)
|
|
}
|
|
b.addKind()
|
|
b.addSectionType()
|
|
|
|
case "taxonomyTerm":
|
|
if d.Section != "" {
|
|
b.addLayoutVariations(d.Section + ".terms")
|
|
}
|
|
b.addTypeVariations("taxonomy")
|
|
b.addSectionType()
|
|
b.addLayoutVariations("terms")
|
|
|
|
}
|
|
|
|
isRSS := f.Name == RSSFormat.Name
|
|
if isRSS {
|
|
// The historic and common rss.xml case
|
|
b.addLayoutVariations("")
|
|
}
|
|
|
|
// All have _default in their lookup path
|
|
b.addTypeVariations("_default")
|
|
|
|
if d.Kind != "page" {
|
|
// Add the common list type
|
|
b.addLayoutVariations("list")
|
|
}
|
|
|
|
layouts := b.resolveVariations()
|
|
|
|
if 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{fmt.Sprintf("%s.%s", 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
|
|
}
|
|
template := layoutTemplate(typeVar, layoutVar)
|
|
layouts = append(layouts, replaceKeyValues(template,
|
|
"TYPE", typeVar,
|
|
"LAYOUT", layoutVar,
|
|
"VARIATIONS", variation,
|
|
"EXTENSION", l.f.MediaType.Suffix(),
|
|
))
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return filterDotLess(layouts)
|
|
}
|
|
|
|
func layoutTemplate(typeVar, layoutVar string) string {
|
|
|
|
var l string
|
|
|
|
if typeVar != "" {
|
|
l = "TYPE/"
|
|
}
|
|
|
|
if layoutVar != "" {
|
|
l += "LAYOUT.VARIATIONS.EXTENSION"
|
|
} else {
|
|
l += "VARIATIONS.EXTENSION"
|
|
}
|
|
|
|
return l
|
|
}
|
|
|
|
func filterDotLess(layouts []string) []string {
|
|
var filteredLayouts []string
|
|
|
|
for _, l := range layouts {
|
|
l = strings.Replace(l, "..", ".", -1)
|
|
l = strings.Trim(l, ".")
|
|
// If media type has no suffix, we have "index" type of layouts in this list, which
|
|
// doesn't make much sense.
|
|
if strings.Contains(l, ".") {
|
|
filteredLayouts = append(filteredLayouts, l)
|
|
}
|
|
}
|
|
|
|
return filteredLayouts
|
|
}
|
|
|
|
func prependTextPrefixIfNeeded(f Format, layouts ...string) []string {
|
|
if !f.IsPlainText {
|
|
return layouts
|
|
}
|
|
|
|
newLayouts := make([]string, len(layouts))
|
|
|
|
for i, l := range layouts {
|
|
newLayouts[i] = "_text/" + l
|
|
}
|
|
|
|
return newLayouts
|
|
}
|
|
|
|
func replaceKeyValues(s string, oldNew ...string) string {
|
|
replacer := strings.NewReplacer(oldNew...)
|
|
return replacer.Replace(s)
|
|
}
|