shithub: hugo

Download patch

ref: ebcc1e6699dc60adf9b4b0b4edd80eeb185355f1
parent: 664fd991356861893632627c25e9827103a0e6c3
author: bep <[email protected]>
date: Wed Feb 11 15:24:56 EST 2015

Add data files support in themes

If duplicate keys, the main data dir wins.

Fixes #892

--- a/commands/hugo.go
+++ b/commands/hugo.go
@@ -252,13 +252,13 @@
 	syncer.SrcFs = hugofs.SourceFs
 	syncer.DestFs = hugofs.DestinationFS
 
-	if themeSet() {
-		themeDir := helpers.AbsPathify("themes/"+viper.GetString("theme")) + "/static/"
-		if _, err := os.Stat(themeDir); os.IsNotExist(err) {
-			jww.ERROR.Println("Unable to find static directory for theme:", viper.GetString("theme"), "in", themeDir)
-			return nil
-		}
+	themeDir, err := helpers.GetThemeStaticDirPath()
+	if err != nil {
+		jww.ERROR.Println(err)
+		return nil
+	}
 
+	if themeDir != "" {
 		// Copy Static to Destination
 		jww.INFO.Println("syncing from", themeDir, "to", publishDir)
 		utils.CheckErr(syncer.Sync(publishDir, themeDir), fmt.Sprintf("Error copying static files of theme to %s", publishDir))
@@ -292,15 +292,11 @@
 	filepath.Walk(helpers.AbsPathify(viper.GetString("ContentDir")), walker)
 	filepath.Walk(helpers.AbsPathify(viper.GetString("LayoutDir")), walker)
 	filepath.Walk(helpers.AbsPathify(viper.GetString("StaticDir")), walker)
-	if themeSet() {
+	if helpers.ThemeSet() {
 		filepath.Walk(helpers.AbsPathify("themes/"+viper.GetString("theme")), walker)
 	}
 
 	return a
-}
-
-func themeSet() bool {
-	return viper.GetString("theme") != ""
 }
 
 func buildSite(watching ...bool) (err error) {
--- a/helpers/general.go
+++ b/helpers/general.go
@@ -19,13 +19,13 @@
 	"encoding/hex"
 	"errors"
 	"fmt"
+	bp "github.com/spf13/hugo/bufferpool"
+	"github.com/spf13/viper"
 	"io"
 	"net"
 	"path/filepath"
 	"reflect"
 	"strings"
-
-	bp "github.com/spf13/hugo/bufferpool"
 )
 
 // Filepath separator defined by os.Separator.
@@ -98,6 +98,10 @@
 // BytesToReader does the opposite of ReaderToBytes.
 func BytesToReader(in []byte) io.Reader {
 	return bytes.NewReader(in)
+}
+
+func ThemeSet() bool {
+	return viper.GetString("theme") != ""
 }
 
 // SliceToLower goes through the source slice and lowers all values.
--- a/helpers/path.go
+++ b/helpers/path.go
@@ -178,6 +178,29 @@
 	return AbsPathify(viper.GetString("StaticDir"))
 }
 
+// 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) {
+	return getThemeDirPath("static")
+}
+
+// GetThemeStaticDirPath returns the theme's data dir path if theme is set.
+// If theme is set and the data dir doesn't exist, an error is returned.
+func GetThemeDataDirPath() (string, error) {
+	return getThemeDirPath("data")
+}
+
+func getThemeDirPath(path string) (string, error) {
+	var themeDir string
+	if ThemeSet() {
+		themeDir = AbsPathify("themes/"+viper.GetString("theme")) + FilePathSeparator + path
+		if _, err := os.Stat(themeDir); os.IsNotExist(err) {
+			return "", fmt.Errorf("Unable to find %s directory for theme %s in %s", path, viper.GetString("theme"), themeDir)
+		}
+	}
+	return themeDir, nil
+}
+
 func GetThemesDirPath() string {
 	return AbsPathify(filepath.Join("themes", viper.GetString("theme"), "static"))
 }
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -267,42 +267,46 @@
 	return s.Tmpl.AddTemplate(name, data)
 }
 
-func (s *Site) loadData(fs source.Input) (err error) {
+func (s *Site) loadData(sources []source.Input) (err error) {
 	s.Data = make(map[string]interface{})
 	var current map[string]interface{}
-
-	for _, r := range fs.Files() {
-		// Crawl in data tree to insert data
-		current = s.Data
-		for _, key := range strings.Split(r.Dir(), helpers.FilePathSeparator) {
-			if key != "" {
-				if _, ok := current[key]; !ok {
-					current[key] = make(map[string]interface{})
+	for _, currentSource := range sources {
+		for _, r := range currentSource.Files() {
+			// Crawl in data tree to insert data
+			current = s.Data
+			for _, key := range strings.Split(r.Dir(), helpers.FilePathSeparator) {
+				if key != "" {
+					if _, ok := current[key]; !ok {
+						current[key] = make(map[string]interface{})
+					}
+					current = current[key].(map[string]interface{})
 				}
-				current = current[key].(map[string]interface{})
 			}
-		}
 
-		data, err := readData(r)
-		if err != nil {
-			return fmt.Errorf("Failed to read data from %s: %s", filepath.Join(r.Path(), r.LogicalName()), err)
-		}
+			data, err := readData(r)
+			if err != nil {
+				return fmt.Errorf("Failed to read data from %s: %s", filepath.Join(r.Path(), r.LogicalName()), err)
+			}
 
-		// Copy content from current to data when needed
-		if _, ok := current[r.BaseFileName()]; ok {
-			data := data.(map[string]interface{})
+			// Copy content from current to data when needed
+			if _, ok := current[r.BaseFileName()]; ok {
+				data := data.(map[string]interface{})
 
-			for key, value := range current[r.BaseFileName()].(map[string]interface{}) {
-				if _, override := data[key]; override {
-					// filepath.Walk walks the files in lexical order, '/' comes before '.'
-					jww.ERROR.Printf("Data for key '%s' in path '%s' is overridden in subfolder", key, r.Path())
+				for key, value := range current[r.BaseFileName()].(map[string]interface{}) {
+					if _, override := data[key]; override {
+						// filepath.Walk walks the files in lexical order, '/' comes before '.'
+						// this warning could happen if
+						// 1. A theme uses the same key; the main data folder wins
+						// 2. A sub folder uses the same key: the sub folder wins
+						jww.WARN.Printf("Data for key '%s' in path '%s' is overridden in subfolder", key, r.Path())
+					}
+					data[key] = value
 				}
-				data[key] = value
 			}
-		}
 
-		// Insert data
-		current[r.BaseFileName()] = data
+			// Insert data
+			current[r.BaseFileName()] = data
+		}
 	}
 
 	return
@@ -329,7 +333,17 @@
 	s.Tmpl.PrintErrors()
 	s.timerStep("initialize & template prep")
 
-	if err = s.loadData(&source.Filesystem{Base: s.absDataDir()}); err != nil {
+	dataSources := make([]source.Input, 0, 2)
+
+	dataSources = append(dataSources, &source.Filesystem{Base: s.absDataDir()})
+
+	// have to be last - duplicate keys in earlier entries will win
+	themeStaticDir, err := helpers.GetThemeDataDirPath()
+	if err == nil {
+		dataSources = append(dataSources, &source.Filesystem{Base: themeStaticDir})
+	}
+
+	if err = s.loadData(dataSources); err != nil {
 		return
 	}
 	s.timerStep("load data")
--- a/hugolib/site_test.go
+++ b/hugolib/site_test.go
@@ -760,7 +760,7 @@
 		t.Fatalf("Error %s", err)
 	}
 
-	doTestDataDir(t, expected, sources)
+	doTestDataDir(t, expected, []source.Input{&source.InMemorySource{ByteSource: sources}})
 }
 
 func TestDataDirToml(t *testing.T) {
@@ -774,7 +774,7 @@
 		t.Fatalf("Error %s", err)
 	}
 
-	doTestDataDir(t, expected, sources)
+	doTestDataDir(t, expected, []source.Input{&source.InMemorySource{ByteSource: sources}})
 }
 
 func TestDataDirYamlWithOverridenValue(t *testing.T) {
@@ -789,23 +789,40 @@
 	expected := map[string]interface{}{"a": map[string]interface{}{"a": 1},
 		"test": map[string]interface{}{"v1": map[string]interface{}{"v1-2": 2}, "v2": map[string]interface{}{"v2": []interface{}{2, 3}}}}
 
-	doTestDataDir(t, expected, sources)
+	doTestDataDir(t, expected, []source.Input{&source.InMemorySource{ByteSource: sources}})
 }
 
+// issue 892
+func TestDataDirMultipleSources(t *testing.T) {
+	s1 := []source.ByteSource{
+		{filepath.FromSlash("test/first.toml"), []byte("[foo]\nbar = 1")},
+	}
+
+	s2 := []source.ByteSource{
+		{filepath.FromSlash("test/first.toml"), []byte("[foo]\nbar = 2")},
+		{filepath.FromSlash("test/second.toml"), []byte("[foo]\ntender = 2")},
+	}
+
+	expected := map[string]interface{}{"a": map[string]interface{}{"a": 1}}
+
+	doTestDataDir(t, expected, []source.Input{&source.InMemorySource{ByteSource: s1}, &source.InMemorySource{ByteSource: s2}})
+
+}
+
 func TestDataDirUnknownFormat(t *testing.T) {
 	sources := []source.ByteSource{
 		{filepath.FromSlash("test.roml"), []byte("boo")},
 	}
 	s := &Site{}
-	err := s.loadData(&source.InMemorySource{ByteSource: sources})
+	err := s.loadData([]source.Input{&source.InMemorySource{ByteSource: sources}})
 	if err == nil {
 		t.Fatalf("Should return an error")
 	}
 }
 
-func doTestDataDir(t *testing.T, expected interface{}, sources []source.ByteSource) {
+func doTestDataDir(t *testing.T, expected interface{}, sources []source.Input) {
 	s := &Site{}
-	err := s.loadData(&source.InMemorySource{ByteSource: sources})
+	err := s.loadData(sources)
 	if err != nil {
 		t.Fatalf("Error loading data: %s", err)
 	}