summaryrefslogtreecommitdiffstats
path: root/output/layout.go
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2018-01-13 17:21:42 +0100
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2018-01-14 19:59:51 +0100
commit51dd462c3958f7cf032b06503f1f200a6aceebb9 (patch)
tree6e153b1daa623729a7a469b996b15cf38ac5cf8e /output/layout.go
parentb6ea6d07d0b072d850fb066c78976acd6c2f5e81 (diff)
layout: Respect Type and Layout for list template selection
This commit also has some other nice side-effects: * The layout logic is unified for all page types, which should make it less surprising * Page.Render now supports all types * The legacy "indexes" type is removed from the template lookup order. This is an undocumented type from early Hugo days. This means that having a template in, say, `/layouts/indexes/list.html` will no longer work. * The theme override logic is improved. As an example, an `index.html` in theme will now wn over a `_default/list.html` in the project, which most will expect. Fixes #3005 Fixes #3245
Diffstat (limited to 'output/layout.go')
-rw-r--r--output/layout.go300
1 files changed, 148 insertions, 152 deletions
diff --git a/output/layout.go b/output/layout.go
index ee3305758..5ffc19130 100644
--- a/output/layout.go
+++ b/output/layout.go
@@ -15,11 +15,16 @@ package output
import (
"fmt"
- "path"
"strings"
"sync"
)
+// 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 {
@@ -28,6 +33,8 @@ type LayoutDescriptor struct {
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.
@@ -39,9 +46,8 @@ type LayoutHandler struct {
}
type layoutCacheKey struct {
- d LayoutDescriptor
- layoutOverride string
- f Format
+ d LayoutDescriptor
+ f Format
}
// NewLayoutHandler creates a new LayoutHandler.
@@ -49,50 +55,12 @@ func NewLayoutHandler(hasTheme bool) *LayoutHandler {
return &LayoutHandler{hasTheme: hasTheme, cache: make(map[layoutCacheKey][]string)}
}
-// RSS:
-// Home:"rss.xml", "_default/rss.xml", "_internal/_default/rss.xml"
-// Section: "section/" + section + ".rss.xml", "_default/rss.xml", "rss.xml", "_internal/_default/rss.xml"
-// Taxonomy "taxonomy/" + singular + ".rss.xml", "_default/rss.xml", "rss.xml", "_internal/_default/rss.xml"
-// Tax term: taxonomy/" + singular + ".terms.rss.xml", "_default/rss.xml", "rss.xml", "_internal/_default/rss.xml"
-
-const (
-
- // TODO(bep) variations reduce to 1 "."
-
- // The RSS templates doesn't map easily into the regular pages.
- layoutsRSSHome = `VARIATIONS _default/VARIATIONS _internal/_default/rss.xml`
- layoutsRSSSection = `section/SECTION.VARIATIONS _default/VARIATIONS VARIATIONS _internal/_default/rss.xml`
- layoutsRSSTaxonomy = `taxonomy/SECTION.VARIATIONS _default/VARIATIONS VARIATIONS _internal/_default/rss.xml`
- layoutsRSSTaxonomyTerm = `taxonomy/SECTION.terms.VARIATIONS _default/VARIATIONS VARIATIONS _internal/_default/rss.xml`
-
- layoutsHome = "index.VARIATIONS _default/list.VARIATIONS"
- layoutsSection = `
-section/SECTION.VARIATIONS
-SECTION/list.VARIATIONS
-_default/section.VARIATIONS
-_default/list.VARIATIONS
-indexes/SECTION.VARIATIONS
-_default/indexes.VARIATIONS
-`
- layoutsTaxonomy = `
-taxonomy/SECTION.VARIATIONS
-indexes/SECTION.VARIATIONS
-_default/taxonomy.VARIATIONS
-_default/list.VARIATIONS
-`
- layoutsTaxonomyTerm = `
-taxonomy/SECTION.terms.VARIATIONS
-_default/terms.VARIATIONS
-indexes/indexes.VARIATIONS
-`
-)
-
// For returns a layout for the given LayoutDescriptor and options.
// Layouts are rendered and cached internally.
-func (l *LayoutHandler) For(d LayoutDescriptor, layoutOverride string, f Format) ([]string, error) {
+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, layoutOverride, f}
+ key := layoutCacheKey{d, f}
l.mu.RLock()
if cacheVal, found := l.cache[key]; found {
l.mu.RUnlock()
@@ -100,53 +68,18 @@ func (l *LayoutHandler) For(d LayoutDescriptor, layoutOverride string, f Format)
}
l.mu.RUnlock()
- var layouts []string
-
- if layoutOverride != "" && d.Kind != "page" {
- return layouts, fmt.Errorf("Custom layout (%q) only supported for regular pages, not kind %q", layoutOverride, d.Kind)
- }
-
- layout := d.Layout
-
- if layoutOverride != "" {
- layout = layoutOverride
- }
-
- isRSS := f.Name == RSSFormat.Name
-
- if d.Kind == "page" {
- if isRSS {
- return []string{}, nil
- }
- layouts = regularPageLayouts(d.Type, layout, f)
- } else {
- if isRSS {
- layouts = resolveListTemplate(d, f,
- layoutsRSSHome,
- layoutsRSSSection,
- layoutsRSSTaxonomy,
- layoutsRSSTaxonomyTerm)
- } else {
- layouts = resolveListTemplate(d, f,
- layoutsHome,
- layoutsSection,
- layoutsTaxonomy,
- layoutsTaxonomyTerm)
- }
- }
+ layouts := resolvePageTemplate(d, f)
if l.hasTheme {
+ // From Hugo 0.33 we interleave the project/theme templates. This was kind of a fundamental change, but the
+ // previous behaviour was surprising.
+ // As an example, an `index.html` in theme for the home page will now win over a `_default/list.html` in the project.
layoutsWithThemeLayouts := []string{}
+
// First place all non internal templates
for _, t := range layouts {
if !strings.HasPrefix(t, "_internal/") {
layoutsWithThemeLayouts = append(layoutsWithThemeLayouts, t)
- }
- }
-
- // Then place theme templates with the same names
- for _, t := range layouts {
- if !strings.HasPrefix(t, "_internal/") {
layoutsWithThemeLayouts = append(layoutsWithThemeLayouts, "theme/"+t)
}
}
@@ -170,70 +103,167 @@ func (l *LayoutHandler) For(d LayoutDescriptor, layoutOverride string, f Format)
return layouts, nil
}
-func resolveListTemplate(d LayoutDescriptor, f Format,
- homeLayouts,
- sectionLayouts,
- taxonomyLayouts,
- taxonomyTermLayouts string) []string {
- var layouts []string
+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":
- layouts = resolveTemplate(homeLayouts, d, f)
+ b.addLayoutVariations("index", "home")
+ // Also look in the root
+ b.addTypeVariations("")
case "section":
- layouts = resolveTemplate(sectionLayouts, d, f)
+ if d.Section != "" {
+ b.addLayoutVariations(d.Section)
+ }
+ b.addSectionType()
+ b.addKind()
case "taxonomy":
- layouts = resolveTemplate(taxonomyLayouts, d, f)
+ if d.Section != "" {
+ b.addLayoutVariations(d.Section)
+ }
+ b.addKind()
+ b.addSectionType()
+
case "taxonomyTerm":
- layouts = resolveTemplate(taxonomyTermLayouts, d, f)
- }
- return layouts
-}
+ if d.Section != "" {
+ b.addLayoutVariations(d.Section + ".terms")
+ }
+ b.addTypeVariations("taxonomy")
+ b.addSectionType()
+ b.addLayoutVariations("terms")
-func resolveTemplate(templ string, d LayoutDescriptor, f Format) []string {
+ }
- // VARIATIONS will be replaced with
- // .lang.name.suffix
- // .name.suffix
- // .lang.suffix
- // .suffix
- var replacementValues []string
+ isRSS := f.Name == RSSFormat.Name
+ if isRSS {
+ // The historic and common rss.xml case
+ b.addLayoutVariations("")
+ }
- name := strings.ToLower(f.Name)
+ // All have _default in their lookup path
+ b.addTypeVariations("_default")
- if d.Lang != "" {
- replacementValues = append(replacementValues, fmt.Sprintf("%s.%s.%s", d.Lang, name, f.MediaType.Suffix))
+ if d.Kind != "page" {
+ // Add the common list type
+ b.addLayoutVariations("list")
}
- replacementValues = append(replacementValues, fmt.Sprintf("%s.%s", name, f.MediaType.Suffix))
+ layouts := b.resolveVariations()
- if d.Lang != "" {
- replacementValues = append(replacementValues, fmt.Sprintf("%s.%s", d.Lang, f.MediaType.Suffix))
+ if isRSS {
+ layouts = append(layouts, "_internal/_default/rss.xml")
}
- isRSS := f.Name == RSSFormat.Name
+ return layouts
- if !isRSS {
- replacementValues = append(replacementValues, f.MediaType.Suffix)
- }
+}
+
+func (l *layoutBuilder) resolveVariations() []string {
var layouts []string
- templFields := strings.Fields(templ)
+ 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)
+ }
- for _, field := range templFields {
- for _, replacements := range replacementValues {
- layouts = append(layouts, replaceKeyValues(field, "VARIATIONS", replacements, "SECTION", d.Section))
+ 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.
@@ -263,37 +293,3 @@ func replaceKeyValues(s string, oldNew ...string) string {
replacer := strings.NewReplacer(oldNew...)
return replacer.Replace(s)
}
-
-func regularPageLayouts(types string, layout string, f Format) []string {
- var layouts []string
-
- if layout == "" {
- layout = "single"
- }
-
- delimiter := "."
- if f.MediaType.Delimiter == "" {
- delimiter = ""
- }
-
- suffix := delimiter + f.MediaType.Suffix
- name := strings.ToLower(f.Name)
-
- if types != "" {
- t := strings.Split(types, "/")
-
- // Add type/layout.html
- for i := range t {
- search := t[:len(t)-i]
- layouts = append(layouts, fmt.Sprintf("%s/%s.%s%s", strings.ToLower(path.Join(search...)), layout, name, suffix))
- layouts = append(layouts, fmt.Sprintf("%s/%s%s", strings.ToLower(path.Join(search...)), layout, suffix))
-
- }
- }
-
- // Add _default/layout.html
- layouts = append(layouts, fmt.Sprintf("_default/%s.%s%s", layout, name, suffix))
- layouts = append(layouts, fmt.Sprintf("_default/%s%s", layout, suffix))
-
- return filterDotLess(layouts)
-}