ref: a3a67163f93b4fc16c2cc0343ffb532631ec4c8b
parent: 596bbea815f9215bc08806c30183631278318251
author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
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) {--
⑨