shithub: hugo

Download patch

ref: 51dd462c3958f7cf032b06503f1f200a6aceebb9
parent: b6ea6d07d0b072d850fb066c78976acd6c2f5e81
author: Bjørn Erik Pedersen <[email protected]>
date: Sat Jan 13 12:21:42 EST 2018

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

--- a/hugolib/page_output.go
+++ b/hugolib/page_output.go
@@ -100,22 +100,19 @@
 		return []string{p.selfLayout}, nil
 	}
 
-	layoutOverride := ""
+	layoutDescriptor := p.layoutDescriptor
+
 	if len(layouts) > 0 {
-		layoutOverride = layouts[0]
+		layoutDescriptor.Layout = layouts[0]
+		layoutDescriptor.LayoutOverride = true
 	}
 
 	return p.s.layoutHandler.For(
-		p.layoutDescriptor,
-		layoutOverride,
+		layoutDescriptor,
 		p.outputFormat)
 }
 
 func (p *PageOutput) Render(layout ...string) template.HTML {
-	if !p.checkRender() {
-		return ""
-	}
-
 	l, err := p.layouts(layout...)
 	if err != nil {
 		helpers.DistinctErrorLog.Printf("in .Render: Failed to resolve layout %q for page %q", layout, p.pathOrTitle())
@@ -145,10 +142,6 @@
 }
 
 func (p *Page) Render(layout ...string) template.HTML {
-	if !p.checkRender() {
-		return ""
-	}
-
 	p.pageOutputInit.Do(func() {
 		if p.mainPageOutput != nil {
 			return
@@ -168,16 +161,6 @@
 	})
 
 	return p.mainPageOutput.Render(layout...)
-}
-
-// We may fix this in the future, but the layout handling in Render isn't built
-// for list pages.
-func (p *Page) checkRender() bool {
-	if p.Kind != KindPage {
-		helpers.DistinctWarnLog.Printf(".Render only available for regular pages, not for of kind %q. You probably meant .Site.RegularPages and not.Site.Pages.", p.Kind)
-		return false
-	}
-	return true
 }
 
 // OutputFormats holds a list of the relevant output formats for a given resource.
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -1541,7 +1541,7 @@
 }
 
 func (s *Site) layouts(p *PageOutput) ([]string, error) {
-	return s.layoutHandler.For(p.layoutDescriptor, "", p.outputFormat)
+	return s.layoutHandler.For(p.layoutDescriptor, p.outputFormat)
 }
 
 func (s *Site) preparePages() error {
--- a/hugolib/site_render.go
+++ b/hugolib/site_render.go
@@ -242,7 +242,6 @@
 
 	layouts, err := s.layoutHandler.For(
 		p.layoutDescriptor,
-		"",
 		p.outputFormat)
 	if err != nil {
 		return err
--- a/output/docshelper.go
+++ b/output/docshelper.go
@@ -37,27 +37,26 @@
 	)
 
 	for _, example := range []struct {
-		name           string
-		d              LayoutDescriptor
-		hasTheme       bool
-		layoutOverride string
-		f              Format
+		name     string
+		d        LayoutDescriptor
+		hasTheme bool
+		f        Format
 	}{
-		{`AMP home, with theme "demoTheme".`, LayoutDescriptor{Kind: "home"}, true, "", AMPFormat},
-		{`AMP home, French language".`, LayoutDescriptor{Kind: "home", Lang: "fr"}, false, "", AMPFormat},
-		{"RSS home, no theme.", LayoutDescriptor{Kind: "home"}, false, "", RSSFormat},
-		{"JSON home, no theme.", LayoutDescriptor{Kind: "home"}, false, "", JSONFormat},
-		{fmt.Sprintf(`CSV regular, "layout: %s" in front matter.`, demoLayout), LayoutDescriptor{Kind: "page", Layout: demoLayout}, false, "", CSVFormat},
-		{fmt.Sprintf(`JSON regular, "type: %s" in front matter.`, demoType), LayoutDescriptor{Kind: "page", Type: demoType}, false, "", JSONFormat},
-		{"HTML regular.", LayoutDescriptor{Kind: "page"}, false, "", HTMLFormat},
-		{"AMP regular.", LayoutDescriptor{Kind: "page"}, false, "", AMPFormat},
-		{"Calendar blog section.", LayoutDescriptor{Kind: "section", Section: "blog"}, false, "", CalendarFormat},
-		{"Calendar taxonomy list.", LayoutDescriptor{Kind: "taxonomy", Section: "tag"}, false, "", CalendarFormat},
-		{"Calendar taxonomy term.", LayoutDescriptor{Kind: "taxonomyTerm", Section: "tag"}, false, "", CalendarFormat},
+		{`AMP home, with theme "demoTheme".`, LayoutDescriptor{Kind: "home"}, true, AMPFormat},
+		{`AMP home, French language".`, LayoutDescriptor{Kind: "home", Lang: "fr"}, false, AMPFormat},
+		{"RSS home, no theme.", LayoutDescriptor{Kind: "home"}, false, RSSFormat},
+		{"JSON home, no theme.", LayoutDescriptor{Kind: "home"}, false, JSONFormat},
+		{fmt.Sprintf(`CSV regular, "layout: %s" in front matter.`, demoLayout), LayoutDescriptor{Kind: "page", Layout: demoLayout}, false, CSVFormat},
+		{fmt.Sprintf(`JSON regular, "type: %s" in front matter.`, demoType), LayoutDescriptor{Kind: "page", Type: demoType}, false, JSONFormat},
+		{"HTML regular.", LayoutDescriptor{Kind: "page"}, false, HTMLFormat},
+		{"AMP regular.", LayoutDescriptor{Kind: "page"}, false, AMPFormat},
+		{"Calendar blog section.", LayoutDescriptor{Kind: "section", Section: "blog"}, false, CalendarFormat},
+		{"Calendar taxonomy list.", LayoutDescriptor{Kind: "taxonomy", Section: "tag"}, false, CalendarFormat},
+		{"Calendar taxonomy term.", LayoutDescriptor{Kind: "taxonomyTerm", Section: "tag"}, false, CalendarFormat},
 	} {
 
 		l := NewLayoutHandler(example.hasTheme)
-		layouts, _ := l.For(example.d, example.layoutOverride, example.f)
+		layouts, _ := l.For(example.d, example.f)
 
 		basicExamples = append(basicExamples, Example{
 			Example:      example.name,
--- a/output/layout.go
+++ b/output/layout.go
@@ -15,11 +15,16 @@
 
 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 @@
 	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 layoutCacheKey struct {
-	d              LayoutDescriptor
-	layoutOverride string
-	f              Format
+	d LayoutDescriptor
+	f Format
 }
 
 // NewLayoutHandler creates a new LayoutHandler.
@@ -49,50 +55,12 @@
 	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 @@
 	}
 	l.mu.RUnlock()
 
-	var layouts []string
+	layouts := resolvePageTemplate(d, f)
 
-	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)
-		}
-	}
-
 	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 @@
 	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)
+		if d.Section != "" {
+			b.addLayoutVariations(d.Section + ".terms")
+		}
+		b.addTypeVariations("taxonomy")
+		b.addSectionType()
+		b.addLayoutVariations("terms")
+
 	}
-	return layouts
-}
 
-func resolveTemplate(templ string, d LayoutDescriptor, f Format) []string {
+	isRSS := f.Name == RSSFormat.Name
+	if isRSS {
+		// The historic and common rss.xml case
+		b.addLayoutVariations("")
+	}
 
-	// VARIATIONS will be replaced with
-	// .lang.name.suffix
-	// .name.suffix
-	// .lang.suffix
-	// .suffix
-	var replacementValues []string
+	// All have _default in their lookup path
+	b.addTypeVariations("_default")
 
-	name := strings.ToLower(f.Name)
-
-	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)
 
-	for _, field := range templFields {
-		for _, replacements := range replacementValues {
-			layouts = append(layouts, replaceKeyValues(field, "VARIATIONS", replacements, "SECTION", d.Section))
+	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.
@@ -262,38 +292,4 @@
 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)
 }
--- a/output/layout_test.go
+++ b/output/layout_test.go
@@ -14,6 +14,9 @@
 package output
 
 import (
+	"fmt"
+	"reflect"
+	"strings"
 	"testing"
 
 	"github.com/gohugoio/hugo/media"
@@ -37,6 +40,8 @@
 			BaseName:  "index",
 		}
 
+		htmlFormat = HTMLFormat
+
 		noExtDelimFormat = Format{
 			Name:      "NEM",
 			MediaType: noExtNoDelimMediaType,
@@ -56,58 +61,75 @@
 		layoutOverride string
 		tp             Format
 		expect         []string
+		expectCount    int
 	}{
 		{"Home", LayoutDescriptor{Kind: "home"}, true, "", ampType,
-			[]string{"index.amp.html", "index.html", "_default/list.amp.html", "_default/list.html", "theme/index.amp.html", "theme/index.html"}},
+			[]string{"index.amp.html", "theme/index.amp.html", "home.amp.html", "theme/home.amp.html", "list.amp.html", "theme/list.amp.html", "index.html", "theme/index.html", "home.html", "theme/home.html", "list.html", "theme/list.html", "_default/index.amp.html"}, 24},
+		{"Home, HTML", LayoutDescriptor{Kind: "home"}, true, "", htmlFormat,
+			// We will eventually get to index.html. This looks stuttery, but makes the lookup logic easy to understand.
+			[]string{"index.html.html", "theme/index.html.html", "home.html.html"}, 24},
 		{"Home, french language", LayoutDescriptor{Kind: "home", Lang: "fr"}, true, "", ampType,
-			[]string{"index.fr.amp.html", "index.amp.html", "index.fr.html", "index.html", "_default/list.fr.amp.html", "_default/list.amp.html", "_default/list.fr.html", "_default/list.html", "theme/index.fr.amp.html", "theme/index.amp.html", "theme/index.fr.html"}},
+			[]string{"index.fr.amp.html", "theme/index.fr.amp.html"},
+			48},
 		{"Home, no ext or delim", LayoutDescriptor{Kind: "home"}, true, "", noExtDelimFormat,
-			[]string{"index.nem", "_default/list.nem"}},
+			[]string{"index.nem", "theme/index.nem", "home.nem", "theme/home.nem", "list.nem"}, 12},
 		{"Home, no ext", LayoutDescriptor{Kind: "home"}, true, "", noExt,
-			[]string{"index.nex", "_default/list.nex"}},
+			[]string{"index.nex", "theme/index.nex", "home.nex", "theme/home.nex", "list.nex"}, 12},
 		{"Page, no ext or delim", LayoutDescriptor{Kind: "page"}, true, "", noExtDelimFormat,
-			[]string{"_default/single.nem", "theme/_default/single.nem"}},
+			[]string{"_default/single.nem", "theme/_default/single.nem"}, 2},
 		{"Section", LayoutDescriptor{Kind: "section", Section: "sect1"}, false, "", ampType,
-			[]string{"section/sect1.amp.html", "section/sect1.html"}},
+			[]string{"sect1/sect1.amp.html", "sect1/section.amp.html", "sect1/list.amp.html", "sect1/sect1.html", "sect1/section.html", "sect1/list.html", "section/sect1.amp.html", "section/section.amp.html"}, 18},
+		{"Section with layout", LayoutDescriptor{Kind: "section", Section: "sect1", Layout: "mylayout"}, false, "", ampType,
+			[]string{"sect1/mylayout.amp.html", "sect1/sect1.amp.html", "sect1/section.amp.html", "sect1/list.amp.html", "sect1/mylayout.html", "sect1/sect1.html"}, 24},
 		{"Taxonomy", LayoutDescriptor{Kind: "taxonomy", Section: "tag"}, false, "", ampType,
-			[]string{"taxonomy/tag.amp.html", "taxonomy/tag.html"}},
+			[]string{"taxonomy/tag.amp.html", "taxonomy/taxonomy.amp.html", "taxonomy/list.amp.html", "taxonomy/tag.html", "taxonomy/taxonomy.html"}, 18},
 		{"Taxonomy term", LayoutDescriptor{Kind: "taxonomyTerm", Section: "categories"}, false, "", ampType,
-			[]string{"taxonomy/categories.terms.amp.html", "taxonomy/categories.terms.html", "_default/terms.amp.html"}},
+			[]string{"taxonomy/categories.terms.amp.html", "taxonomy/terms.amp.html", "taxonomy/list.amp.html", "taxonomy/categories.terms.html", "taxonomy/terms.html"}, 18},
 		{"Page", LayoutDescriptor{Kind: "page"}, true, "", ampType,
-			[]string{"_default/single.amp.html", "_default/single.html", "theme/_default/single.amp.html"}},
+			[]string{"_default/single.amp.html", "theme/_default/single.amp.html", "_default/single.html", "theme/_default/single.html"}, 4},
 		{"Page with layout", LayoutDescriptor{Kind: "page", Layout: "mylayout"}, false, "", ampType,
-			[]string{"_default/mylayout.amp.html", "_default/mylayout.html"}},
+			[]string{"_default/mylayout.amp.html", "_default/single.amp.html", "_default/mylayout.html", "_default/single.html"}, 4},
 		{"Page with layout and type", LayoutDescriptor{Kind: "page", Layout: "mylayout", Type: "myttype"}, false, "", ampType,
-			[]string{"myttype/mylayout.amp.html", "myttype/mylayout.html", "_default/mylayout.amp.html"}},
+			[]string{"myttype/mylayout.amp.html", "myttype/single.amp.html", "myttype/mylayout.html"}, 8},
 		{"Page with layout and type with subtype", LayoutDescriptor{Kind: "page", Layout: "mylayout", Type: "myttype/mysubtype"}, false, "", ampType,
-			[]string{"myttype/mysubtype/mylayout.amp.html", "myttype/mysubtype/mylayout.html", "myttype/mylayout.amp.html"}},
-		{"Page with overridden layout", LayoutDescriptor{Kind: "page", Layout: "mylayout", Type: "myttype"}, false, "myotherlayout", ampType,
-			[]string{"myttype/myotherlayout.amp.html", "myttype/myotherlayout.html"}},
+			[]string{"myttype/mysubtype/mylayout.amp.html", "myttype/mysubtype/single.amp.html", "myttype/mysubtype/mylayout.html"}, 8},
 		// RSS
 		{"RSS Home with theme", LayoutDescriptor{Kind: "home"}, true, "", RSSFormat,
-			[]string{"rss.xml", "_default/rss.xml", "theme/rss.xml", "theme/_default/rss.xml", "_internal/_default/rss.xml"}},
+			[]string{"index.rss.xml", "theme/index.rss.xml", "home.rss.xml", "theme/home.rss.xml", "rss.xml"}, 29},
 		{"RSS Section", LayoutDescriptor{Kind: "section", Section: "sect1"}, false, "", RSSFormat,
-			[]string{"section/sect1.rss.xml", "_default/rss.xml", "rss.xml", "_internal/_default/rss.xml"}},
+			[]string{"sect1/sect1.rss.xml", "sect1/section.rss.xml", "sect1/rss.xml", "sect1/list.rss.xml", "sect1/sect1.xml", "sect1/section.xml"}, 22},
 		{"RSS Taxonomy", LayoutDescriptor{Kind: "taxonomy", Section: "tag"}, false, "", RSSFormat,
-			[]string{"taxonomy/tag.rss.xml", "_default/rss.xml", "rss.xml", "_internal/_default/rss.xml"}},
+			[]string{"taxonomy/tag.rss.xml", "taxonomy/taxonomy.rss.xml", "taxonomy/rss.xml", "taxonomy/list.rss.xml", "taxonomy/tag.xml", "taxonomy/taxonomy.xml"}, 22},
 		{"RSS Taxonomy term", LayoutDescriptor{Kind: "taxonomyTerm", Section: "tag"}, false, "", RSSFormat,
-			[]string{"taxonomy/tag.terms.rss.xml", "_default/rss.xml", "rss.xml", "_internal/_default/rss.xml"}},
+			[]string{"taxonomy/tag.terms.rss.xml", "taxonomy/terms.rss.xml", "taxonomy/rss.xml", "taxonomy/list.rss.xml", "taxonomy/tag.terms.xml"}, 22},
 		{"Home plain text", LayoutDescriptor{Kind: "home"}, true, "", JSONFormat,
-			[]string{"_text/index.json.json", "_text/index.json", "_text/_default/list.json.json", "_text/_default/list.json", "_text/theme/index.json.json", "_text/theme/index.json"}},
+			[]string{"_text/index.json.json", "_text/theme/index.json.json", "_text/home.json.json", "_text/theme/home.json.json"}, 24},
 		{"Page plain text", LayoutDescriptor{Kind: "page"}, true, "", JSONFormat,
-			[]string{"_text/_default/single.json.json", "_text/_default/single.json", "_text/theme/_default/single.json.json"}},
+			[]string{"_text/_default/single.json.json", "_text/theme/_default/single.json.json", "_text/_default/single.json", "_text/theme/_default/single.json"}, 4},
+		{"Reserved section, shortcodes", LayoutDescriptor{Kind: "section", Section: "shortcodes", Type: "shortcodes"}, true, "", ampType,
+			[]string{"section/shortcodes.amp.html", "theme/section/shortcodes.amp.html"}, 24},
+		{"Reserved section, partials", LayoutDescriptor{Kind: "section", Section: "partials", Type: "partials"}, true, "", ampType,
+			[]string{"section/partials.amp.html", "theme/section/partials.amp.html"}, 24},
 	} {
 		t.Run(this.name, func(t *testing.T) {
 			l := NewLayoutHandler(this.hasTheme)
 
-			layouts, err := l.For(this.d, this.layoutOverride, this.tp)
+			layouts, err := l.For(this.d, this.tp)
 
 			require.NoError(t, err)
 			require.NotNil(t, layouts)
-			require.True(t, len(layouts) >= len(this.expect))
+			require.True(t, len(layouts) >= len(this.expect), fmt.Sprint(layouts))
 			// Not checking the complete list for now ...
-			require.Equal(t, this.expect, layouts[:len(this.expect)])
+			got := layouts[:len(this.expect)]
+			if len(layouts) != this.expectCount || !reflect.DeepEqual(got, this.expect) {
+				formatted := strings.Replace(fmt.Sprintf("%v", layouts), "[", "\"", 1)
+				formatted = strings.Replace(formatted, "]", "\"", 1)
+				formatted = strings.Replace(formatted, " ", "\", \"", -1)
 
+				t.Fatalf("Got %d/%d:\n%v\nExpected:\n%v\nAll:\n%v\nFormatted:\n%s", len(layouts), this.expectCount, got, this.expect, layouts, formatted)
+
+			}
+
 			if !this.hasTheme {
 				for _, layout := range layouts {
 					require.NotContains(t, layout, "theme")
@@ -116,10 +138,6 @@
 		})
 	}
 
-	l := NewLayoutHandler(false)
-	_, err := l.For(LayoutDescriptor{Kind: "taxonomyTerm", Section: "tag"}, "override", RSSFormat)
-	require.Error(t, err)
-
 }
 
 func BenchmarkLayout(b *testing.B) {
@@ -127,7 +145,7 @@
 	l := NewLayoutHandler(false)
 
 	for i := 0; i < b.N; i++ {
-		layouts, err := l.For(descriptor, "", HTMLFormat)
+		layouts, err := l.For(descriptor, HTMLFormat)
 		require.NoError(b, err)
 		require.NotEmpty(b, layouts)
 	}