shithub: hugo

Download patch

ref: 077005e514b1ed50d84ceb90c7c72f184cb04521
parent: efc0e05c4ef974414b010b3fba5e40dab0d43876
author: Bjørn Erik Pedersen <[email protected]>
date: Wed Apr 12 16:40:36 EDT 2017

output: Fix base theme vs project base template logic

Fixes #3323

--- a/output/layout_base.go
+++ b/output/layout_base.go
@@ -38,7 +38,11 @@
 }
 
 type TemplateLookupDescriptor struct {
-	// The full path to the site or theme root.
+	// TemplateDir is the project or theme root of the current template.
+	// This will be the same as WorkingDir for non-theme templates.
+	TemplateDir string
+
+	// The full path to the site root.
 	WorkingDir string
 
 	// Main project layout dir, defaults to "layouts"
@@ -51,8 +55,8 @@
 	// The template name prefix to look for, i.e. "theme".
 	Prefix string
 
-	// The theme name if active.
-	Theme string
+	// The theme dir if theme active.
+	ThemeDir string
 
 	// All the output formats in play. This is used to decide if text/template or
 	// html/template.
@@ -64,8 +68,6 @@
 
 func CreateTemplateNames(d TemplateLookupDescriptor) (TemplateNames, error) {
 
-	var id TemplateNames
-
 	name := filepath.ToSlash(d.RelPath)
 
 	if d.Prefix != "" {
@@ -72,9 +74,24 @@
 		name = strings.Trim(d.Prefix, "/") + "/" + name
 	}
 
-	baseLayoutDir := filepath.Join(d.WorkingDir, d.LayoutDir)
-	fullPath := filepath.Join(baseLayoutDir, d.RelPath)
+	var (
+		id TemplateNames
 
+		// This is the path to the actual template in process. This may
+		// be in the theme's or the project's /layouts.
+		baseLayoutDir = filepath.Join(d.TemplateDir, d.LayoutDir)
+		fullPath      = filepath.Join(baseLayoutDir, d.RelPath)
+
+		// This is always the project's layout dir.
+		baseWorkLayoutDir = filepath.Join(d.WorkingDir, d.LayoutDir)
+
+		baseThemeLayoutDir string
+	)
+
+	if d.ThemeDir != "" {
+		baseThemeLayoutDir = filepath.Join(d.ThemeDir, "layouts")
+	}
+
 	// The filename will have a suffix with an optional type indicator.
 	// Examples:
 	// index.html
@@ -140,8 +157,8 @@
 		currBaseFilename := fmt.Sprintf("%s-%s", filenameNoSuffix, baseFilename)
 
 		templateDir := filepath.Dir(fullPath)
-		themeDir := filepath.Join(d.WorkingDir, d.Theme)
 
+		// Find the base, e.g. "_default".
 		baseTemplatedDir := strings.TrimPrefix(templateDir, baseLayoutDir)
 		baseTemplatedDir = strings.TrimPrefix(baseTemplatedDir, helpers.FilePathSeparator)
 
@@ -162,7 +179,7 @@
 
 	Loop:
 		for _, pair := range pairsToCheck {
-			pathsToCheck := basePathsToCheck(pair, baseLayoutDir, themeDir)
+			pathsToCheck := basePathsToCheck(pair, baseLayoutDir, baseWorkLayoutDir, baseThemeLayoutDir)
 
 			for _, pathToCheck := range pathsToCheck {
 				if ok, err := d.FileExists(pathToCheck); err == nil && ok {
@@ -177,13 +194,18 @@
 
 }
 
-func basePathsToCheck(path []string, layoutDir, themeDir string) []string {
-	// Always look in the project.
-	pathsToCheck := []string{filepath.Join((append([]string{layoutDir}, path...))...)}
+func basePathsToCheck(path []string, layoutDir, workLayoutDir, themeLayoutDir string) []string {
+	// workLayoutDir will always be the most specific, so start there.
+	pathsToCheck := []string{filepath.Join((append([]string{workLayoutDir}, path...))...)}
 
+	if layoutDir != "" && layoutDir != workLayoutDir {
+		pathsToCheck = append(pathsToCheck, filepath.Join((append([]string{layoutDir}, path...))...))
+	}
+
 	// May have a theme
-	if themeDir != "" {
-		pathsToCheck = append(pathsToCheck, filepath.Join((append([]string{themeDir, "layouts"}, path...))...))
+	if themeLayoutDir != "" && themeLayoutDir != layoutDir {
+		pathsToCheck = append(pathsToCheck, filepath.Join((append([]string{themeLayoutDir}, path...))...))
+
 	}
 
 	return pathsToCheck
--- a/output/layout_base_test.go
+++ b/output/layout_base_test.go
@@ -25,6 +25,7 @@
 
 	var (
 		workingDir     = "/sites/mysite/"
+		themeDir       = "/themes/mytheme/"
 		layoutBase1    = "layouts"
 		layoutPath1    = "_default/single.html"
 		layoutPathAmp  = "_default/single.amp.html"
@@ -38,76 +39,76 @@
 		basePathMatchStrings string
 		expect               TemplateNames
 	}{
-		{"No base", TemplateLookupDescriptor{WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPath1}, false, "",
+		{"No base", TemplateLookupDescriptor{TemplateDir: workingDir, WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPath1}, false, "",
 			TemplateNames{
 				Name:            "_default/single.html",
 				OverlayFilename: "/sites/mysite/layouts/_default/single.html",
 			}},
-		{"Base", TemplateLookupDescriptor{WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPath1}, true, "",
+		{"Base", TemplateLookupDescriptor{TemplateDir: workingDir, WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPath1}, true, "",
 			TemplateNames{
 				Name:            "_default/single.html",
 				OverlayFilename: "/sites/mysite/layouts/_default/single.html",
 				MasterFilename:  "/sites/mysite/layouts/_default/single-baseof.html",
 			}},
-		{"Base in theme", TemplateLookupDescriptor{WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPath1, Theme: "mytheme"}, true,
+		{"Base in theme", TemplateLookupDescriptor{TemplateDir: workingDir, WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPath1, ThemeDir: themeDir}, true,
 			"mytheme/layouts/_default/baseof.html",
 			TemplateNames{
 				Name:            "_default/single.html",
 				OverlayFilename: "/sites/mysite/layouts/_default/single.html",
-				MasterFilename:  "/sites/mysite/mytheme/layouts/_default/baseof.html",
+				MasterFilename:  "/themes/mytheme/layouts/_default/baseof.html",
 			}},
-		{"Template in theme, base in theme", TemplateLookupDescriptor{WorkingDir: filepath.Join(workingDir, "mytheme"), LayoutDir: layoutBase1, RelPath: layoutPath1, Theme: "mytheme"}, true,
+		{"Template in theme, base in theme", TemplateLookupDescriptor{TemplateDir: themeDir, WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPath1, ThemeDir: themeDir}, true,
 			"mytheme/layouts/_default/baseof.html",
 			TemplateNames{
 				Name:            "_default/single.html",
-				OverlayFilename: "/sites/mysite/mytheme/layouts/_default/single.html",
-				MasterFilename:  "/sites/mysite/mytheme/layouts/_default/baseof.html",
+				OverlayFilename: "/themes/mytheme/layouts/_default/single.html",
+				MasterFilename:  "/themes/mytheme/layouts/_default/baseof.html",
 			}},
-		{"Template in theme, base in site", TemplateLookupDescriptor{WorkingDir: filepath.Join(workingDir, "mytheme"), LayoutDir: layoutBase1, RelPath: layoutPath1, Theme: "mytheme"}, true,
-			"mytheme/layouts/_default/baseof.html",
+		{"Template in theme, base in site", TemplateLookupDescriptor{TemplateDir: themeDir, WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPath1, ThemeDir: themeDir}, true,
+			"/sites/mysite/layouts/_default/baseof.html",
 			TemplateNames{
 				Name:            "_default/single.html",
-				OverlayFilename: "/sites/mysite/mytheme/layouts/_default/single.html",
-				MasterFilename:  "/sites/mysite/mytheme/layouts/_default/baseof.html",
+				OverlayFilename: "/themes/mytheme/layouts/_default/single.html",
+				MasterFilename:  "/sites/mysite/layouts/_default/baseof.html",
 			}},
-		{"Template in site, base in theme", TemplateLookupDescriptor{WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPath1, Theme: "mytheme"}, true,
-			"/sites/mysite/mytheme/layouts/_default/baseof.html",
+		{"Template in site, base in theme", TemplateLookupDescriptor{TemplateDir: workingDir, WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPath1, ThemeDir: themeDir}, true,
+			"/themes/mytheme",
 			TemplateNames{
 				Name:            "_default/single.html",
 				OverlayFilename: "/sites/mysite/layouts/_default/single.html",
-				MasterFilename:  "/sites/mysite/mytheme/layouts/_default/baseof.html",
+				MasterFilename:  "/themes/mytheme/layouts/_default/single-baseof.html",
 			}},
-		{"With prefix, base in theme", TemplateLookupDescriptor{WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPath1,
-			Theme: "mytheme", Prefix: "someprefix"}, true,
+		{"With prefix, base in theme", TemplateLookupDescriptor{TemplateDir: workingDir, WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPath1,
+			ThemeDir: themeDir, Prefix: "someprefix"}, true,
 			"mytheme/layouts/_default/baseof.html",
 			TemplateNames{
 				Name:            "someprefix/_default/single.html",
 				OverlayFilename: "/sites/mysite/layouts/_default/single.html",
-				MasterFilename:  "/sites/mysite/mytheme/layouts/_default/baseof.html",
+				MasterFilename:  "/themes/mytheme/layouts/_default/baseof.html",
 			}},
-		{"Partial", TemplateLookupDescriptor{WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: "partials/menu.html"}, true,
+		{"Partial", TemplateLookupDescriptor{TemplateDir: workingDir, WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: "partials/menu.html"}, true,
 			"mytheme/layouts/_default/baseof.html",
 			TemplateNames{
 				Name:            "partials/menu.html",
 				OverlayFilename: "/sites/mysite/layouts/partials/menu.html",
 			}},
-		{"AMP, no base", TemplateLookupDescriptor{WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPathAmp}, false, "",
+		{"AMP, no base", TemplateLookupDescriptor{TemplateDir: workingDir, WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPathAmp}, false, "",
 			TemplateNames{
 				Name:            "_default/single.amp.html",
 				OverlayFilename: "/sites/mysite/layouts/_default/single.amp.html",
 			}},
-		{"JSON, no base", TemplateLookupDescriptor{WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPathJSON}, false, "",
+		{"JSON, no base", TemplateLookupDescriptor{TemplateDir: workingDir, WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPathJSON}, false, "",
 			TemplateNames{
 				Name:            "_default/single.json",
 				OverlayFilename: "/sites/mysite/layouts/_default/single.json",
 			}},
-		{"AMP with base", TemplateLookupDescriptor{WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPathAmp}, true, "single-baseof.html|single-baseof.amp.html",
+		{"AMP with base", TemplateLookupDescriptor{TemplateDir: workingDir, WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPathAmp}, true, "single-baseof.html|single-baseof.amp.html",
 			TemplateNames{
 				Name:            "_default/single.amp.html",
 				OverlayFilename: "/sites/mysite/layouts/_default/single.amp.html",
 				MasterFilename:  "/sites/mysite/layouts/_default/single-baseof.amp.html",
 			}},
-		{"AMP with no match in base", TemplateLookupDescriptor{WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPathAmp}, true, "single-baseof.html",
+		{"AMP with no match in base", TemplateLookupDescriptor{TemplateDir: workingDir, WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPathAmp}, true, "single-baseof.html",
 			TemplateNames{
 				Name:            "_default/single.amp.html",
 				OverlayFilename: "/sites/mysite/layouts/_default/single.amp.html",
@@ -115,7 +116,7 @@
 				MasterFilename: "",
 			}},
 
-		{"JSON with base", TemplateLookupDescriptor{WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPathJSON}, true, "single-baseof.json",
+		{"JSON with base", TemplateLookupDescriptor{TemplateDir: workingDir, WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPathJSON}, true, "single-baseof.json",
 			TemplateNames{
 				Name:            "_default/single.json",
 				OverlayFilename: "/sites/mysite/layouts/_default/single.json",
--- a/tpl/tplimpl/template.go
+++ b/tpl/tplimpl/template.go
@@ -420,13 +420,15 @@
 
 			li := strings.LastIndex(path, layoutDir) + len(layoutDir) + 1
 			relPath := path[li:]
+			templateDir := path[:li-len(layoutDir)-1]
 
 			descriptor := output.TemplateLookupDescriptor{
+				TemplateDir:   templateDir,
 				WorkingDir:    workingDir,
 				LayoutDir:     layoutDir,
 				RelPath:       relPath,
 				Prefix:        prefix,
-				Theme:         t.PathSpec.Theme(),
+				ThemeDir:      themeDir,
 				OutputFormats: t.OutputFormatsConfig,
 				FileExists: func(filename string) (bool, error) {
 					return helpers.Exists(filename, t.Fs.Source)