shithub: hugo

Download patch

ref: a3a67163f93b4fc16c2cc0343ffb532631ec4c8b
parent: 596bbea815f9215bc08806c30183631278318251
author: Bjørn Erik Pedersen <[email protected]>
date: Wed Dec 14 15:12:03 EST 2016

hugolib: Enable override of theme base template only

This commit fixes the base template lookup order to match the behaviour of regular templates.

```
1. <current-path>/<template-name>-baseof.<suffix>, e.g. list-baseof.<suffix>.
2. <current-path>/baseof.<suffix>
3. _default/<template-name>-baseof.<suffix>, e.g. list-baseof.<suffix>.
4. _default/baseof.<suffix>

For each of the steps above, it will first look in the project, then, if theme is set,
in the theme's layouts folder.
```

Fixes #2783

--- a/helpers/path.go
+++ b/helpers/path.go
@@ -157,6 +157,12 @@
 	return filepath.Clean(filepath.Join(viper.GetString("workingDir"), inPath))
 }
 
+// GetLayoutDirPath returns the absolute path to the layout file dir
+// for the current Hugo project.
+func GetLayoutDirPath() string {
+	return AbsPathify(viper.GetString("layoutDir"))
+}
+
 // GetStaticDirPath returns the absolute path to the static file dir
 // for the current Hugo project.
 func GetStaticDirPath() string {
@@ -168,6 +174,15 @@
 func GetThemeDir() string {
 	if ThemeSet() {
 		return AbsPathify(filepath.Join(viper.GetString("themesDir"), viper.GetString("theme")))
+	}
+	return ""
+}
+
+// GetRelativeThemeDir gets the relative root directory of the current theme, if there is one.
+// If there is no theme, returns the empty string.
+func GetRelativeThemeDir() string {
+	if ThemeSet() {
+		return strings.TrimPrefix(filepath.Join(viper.GetString("themesDir"), viper.GetString("theme")), FilePathSeparator)
 	}
 	return ""
 }
--- /dev/null
+++ b/hugolib/template_test.go
@@ -1,0 +1,145 @@
+// Copyright 2016 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 hugolib
+
+import (
+	"path/filepath"
+	"testing"
+
+	"github.com/spf13/viper"
+)
+
+func TestBaseGoTemplate(t *testing.T) {
+	// Variants:
+	//   1. <current-path>/<template-name>-baseof.<suffix>, e.g. list-baseof.<suffix>.
+	//   2. <current-path>/baseof.<suffix>
+	//   3. _default/<template-name>-baseof.<suffix>, e.g. list-baseof.<suffix>.
+	//   4. _default/baseof.<suffix>
+	for i, this := range []struct {
+		setup  func(t *testing.T)
+		assert func(t *testing.T)
+	}{
+		{
+			// Variant 1
+			func(t *testing.T) {
+				writeSource(t, filepath.Join("layouts", "section", "sect-baseof.html"), `Base: {{block "main" .}}block{{end}}`)
+				writeSource(t, filepath.Join("layouts", "section", "sect.html"), `{{define "main"}}sect{{ end }}`)
+
+			},
+			func(t *testing.T) {
+				assertFileContent(t, filepath.Join("public", "sect", "index.html"), false, "Base: sect")
+			},
+		},
+		{
+			// Variant 2
+			func(t *testing.T) {
+				writeSource(t, filepath.Join("layouts", "baseof.html"), `Base: {{block "main" .}}block{{end}}`)
+				writeSource(t, filepath.Join("layouts", "index.html"), `{{define "main"}}index{{ end }}`)
+
+			},
+			func(t *testing.T) {
+				assertFileContent(t, filepath.Join("public", "index.html"), false, "Base: index")
+			},
+		},
+		{
+			// Variant 3
+			func(t *testing.T) {
+				writeSource(t, filepath.Join("layouts", "_default", "list-baseof.html"), `Base: {{block "main" .}}block{{end}}`)
+				writeSource(t, filepath.Join("layouts", "_default", "list.html"), `{{define "main"}}list{{ end }}`)
+
+			},
+			func(t *testing.T) {
+				assertFileContent(t, filepath.Join("public", "sect", "index.html"), false, "Base: list")
+			},
+		},
+		{
+			// Variant 4
+			func(t *testing.T) {
+				writeSource(t, filepath.Join("layouts", "_default", "baseof.html"), `Base: {{block "main" .}}block{{end}}`)
+				writeSource(t, filepath.Join("layouts", "_default", "list.html"), `{{define "main"}}list{{ end }}`)
+
+			},
+			func(t *testing.T) {
+				assertFileContent(t, filepath.Join("public", "sect", "index.html"), false, "Base: list")
+			},
+		},
+		{
+			// Variant 1, theme,  use project's base
+			func(t *testing.T) {
+				viper.Set("theme", "mytheme")
+				writeSource(t, filepath.Join("layouts", "section", "sect-baseof.html"), `Base: {{block "main" .}}block{{end}}`)
+				writeSource(t, filepath.Join("themes", "mytheme", "layouts", "section", "sect-baseof.html"), `Base Theme: {{block "main" .}}block{{end}}`)
+				writeSource(t, filepath.Join("layouts", "section", "sect.html"), `{{define "main"}}sect{{ end }}`)
+
+			},
+			func(t *testing.T) {
+				assertFileContent(t, filepath.Join("public", "sect", "index.html"), false, "Base: sect")
+			},
+		},
+		{
+			// Variant 1, theme,  use theme's base
+			func(t *testing.T) {
+				viper.Set("theme", "mytheme")
+				writeSource(t, filepath.Join("themes", "mytheme", "layouts", "section", "sect-baseof.html"), `Base Theme: {{block "main" .}}block{{end}}`)
+				writeSource(t, filepath.Join("layouts", "section", "sect.html"), `{{define "main"}}sect{{ end }}`)
+
+			},
+			func(t *testing.T) {
+				assertFileContent(t, filepath.Join("public", "sect", "index.html"), false, "Base Theme: sect")
+			},
+		},
+		{
+			// Variant 4, theme, use project's base
+			func(t *testing.T) {
+				viper.Set("theme", "mytheme")
+				writeSource(t, filepath.Join("layouts", "_default", "baseof.html"), `Base: {{block "main" .}}block{{end}}`)
+				writeSource(t, filepath.Join("themes", "mytheme", "layouts", "_default", "baseof.html"), `Base Theme: {{block "main" .}}block{{end}}`)
+				writeSource(t, filepath.Join("themes", "mytheme", "layouts", "_default", "list.html"), `{{define "main"}}list{{ end }}`)
+
+			},
+			func(t *testing.T) {
+				assertFileContent(t, filepath.Join("public", "sect", "index.html"), false, "Base: list")
+			},
+		},
+		{
+			// Variant 4, theme, use themes's base
+			func(t *testing.T) {
+				viper.Set("theme", "mytheme")
+				writeSource(t, filepath.Join("themes", "mytheme", "layouts", "_default", "baseof.html"), `Base Theme: {{block "main" .}}block{{end}}`)
+				writeSource(t, filepath.Join("themes", "mytheme", "layouts", "_default", "list.html"), `{{define "main"}}list{{ end }}`)
+
+			},
+			func(t *testing.T) {
+				assertFileContent(t, filepath.Join("public", "sect", "index.html"), false, "Base Theme: list")
+			},
+		},
+	} {
+
+		testCommonResetState()
+
+		writeSource(t, filepath.Join("content", "sect", "page.md"), `---
+title: Template test
+---
+Some content
+`)
+		this.setup(t)
+
+		if err := buildAndRenderSite(newSiteDefaultLang()); err != nil {
+			t.Fatalf("[%d] Failed to build site: %s", i, err)
+		}
+
+		this.assert(t)
+
+	}
+}
--- a/tpl/template.go
+++ b/tpl/template.go
@@ -447,31 +447,45 @@
 				}
 				if needsBase {
 
+					layoutDir := helpers.GetLayoutDirPath()
+					currBaseFilename := fmt.Sprintf("%s-%s", helpers.Filename(path), baseFileName)
+					templateDir := filepath.Dir(path)
+					themeDir := filepath.Join(helpers.GetThemeDir())
+					relativeThemeLayoutsDir := filepath.Join(helpers.GetRelativeThemeDir(), "layouts")
+
+					var baseTemplatedDir string
+
+					if strings.HasPrefix(templateDir, relativeThemeLayoutsDir) {
+						baseTemplatedDir = strings.TrimPrefix(templateDir, relativeThemeLayoutsDir)
+					} else {
+						baseTemplatedDir = strings.TrimPrefix(templateDir, layoutDir)
+					}
+
+					baseTemplatedDir = strings.TrimPrefix(baseTemplatedDir, helpers.FilePathSeparator)
+
 					// Look for base template in the follwing order:
 					//   1. <current-path>/<template-name>-baseof.<suffix>, e.g. list-baseof.<suffix>.
 					//   2. <current-path>/baseof.<suffix>
 					//   3. _default/<template-name>-baseof.<suffix>, e.g. list-baseof.<suffix>.
 					//   4. _default/baseof.<suffix>
-					//   5. <themedir>/layouts/_default/<template-name>-baseof.<suffix>
-					//   6. <themedir>/layouts/_default/baseof.<suffix>
+					// For each of the steps above, it will first look in the project, then, if theme is set,
+					// in the theme's layouts folder.
 
-					currBaseFilename := fmt.Sprintf("%s-%s", helpers.Filename(path), baseFileName)
-					templateDir := filepath.Dir(path)
-					themeDir := helpers.GetThemeDir()
-
-					pathsToCheck := []string{
-						filepath.Join(templateDir, currBaseFilename),
-						filepath.Join(templateDir, baseFileName),
-						filepath.Join(absPath, "_default", currBaseFilename),
-						filepath.Join(absPath, "_default", baseFileName),
-						filepath.Join(themeDir, "layouts", "_default", currBaseFilename),
-						filepath.Join(themeDir, "layouts", "_default", baseFileName),
+					pairsToCheck := [][]string{
+						[]string{baseTemplatedDir, currBaseFilename},
+						[]string{baseTemplatedDir, baseFileName},
+						[]string{"_default", currBaseFilename},
+						[]string{"_default", baseFileName},
 					}
 
-					for _, pathToCheck := range pathsToCheck {
-						if ok, err := helpers.Exists(pathToCheck, hugofs.Source()); err == nil && ok {
-							baseTemplatePath = pathToCheck
-							break
+				Loop:
+					for _, pair := range pairsToCheck {
+						pathsToCheck := basePathsToCheck(pair, layoutDir, themeDir)
+						for _, pathToCheck := range pathsToCheck {
+							if ok, err := helpers.Exists(pathToCheck, hugofs.Source()); err == nil && ok {
+								baseTemplatePath = pathToCheck
+								break Loop
+							}
 						}
 					}
 				}
@@ -487,6 +501,19 @@
 	if err := helpers.SymbolicWalk(hugofs.Source(), absPath, walker); err != nil {
 		jww.ERROR.Printf("Failed to load templates: %s", err)
 	}
+}
+
+func basePathsToCheck(path []string, layoutDir, themeDir string) []string {
+	// Always look in the project.
+	pathsToCheck := []string{filepath.Join((append([]string{layoutDir}, path...))...)}
+
+	// May have a theme
+	if themeDir != "" {
+		pathsToCheck = append(pathsToCheck, filepath.Join((append([]string{themeDir, "layouts"}, path...))...))
+	}
+
+	return pathsToCheck
+
 }
 
 func (t *GoHTMLTemplate) LoadTemplatesWithPrefix(absPath string, prefix string) {