commit a3a67163f93b4fc16c2cc0343ffb532631ec4c8b
parent 596bbea815f9215bc08806c30183631278318251
Author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Date: Wed, 14 Dec 2016 20:12:03 +0100
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
Diffstat:
3 files changed, 205 insertions(+), 18 deletions(-)
diff --git a/helpers/path.go b/helpers/path.go
@@ -157,6 +157,12 @@ func AbsPathify(inPath string) string {
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 {
@@ -172,6 +178,15 @@ func GetThemeDir() string {
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 ""
+}
+
// GetThemeStaticDirPath returns the theme's static dir path if theme is set.
// If theme is set and the static dir doesn't exist, an error is returned.
func GetThemeStaticDirPath() (string, error) {
diff --git a/hugolib/template_test.go b/hugolib/template_test.go
@@ -0,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)
+
+ }
+}
diff --git a/tpl/template.go b/tpl/template.go
@@ -447,31 +447,45 @@ func (t *GoHTMLTemplate) loadTemplates(absPath string, prefix string) {
}
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>
-
- 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),
+ // For each of the steps above, it will first look in the project, then, if theme is set,
+ // in the theme's layouts folder.
+
+ 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
+ }
}
}
}
@@ -489,6 +503,19 @@ func (t *GoHTMLTemplate) loadTemplates(absPath string, prefix string) {
}
}
+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) {
t.loadTemplates(absPath, prefix)
}