diff --git a/helpers/path.go b/helpers/path.go index 6a35a55ca..7dea0b0dd 100644 --- 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 new file mode 100644 index 000000000..d09ea7a72 --- /dev/null +++ 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. /-baseof., e.g. list-baseof.. + // 2. /baseof. + // 3. _default/-baseof., e.g. list-baseof.. + // 4. _default/baseof. + 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 index 6c5bea9df..0872372b5 100644 --- 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. /-baseof., e.g. list-baseof.. // 2. /baseof. // 3. _default/-baseof., e.g. list-baseof.. // 4. _default/baseof. - // 5. /layouts/_default/-baseof. - // 6. /layouts/_default/baseof. + // 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 + } } } } @@ -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) }