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) {