Reads data files inside data/ and makes data available in .Site.Data

Fixes #476.

Conflicts:
	hugolib/site.go
This commit is contained in:
Erlend Klakegg Bergheim 2015-01-20 23:08:01 +01:00 committed by bep
parent 4c7e119ca1
commit 773812de6f
4 changed files with 86 additions and 0 deletions

View file

@ -119,6 +119,7 @@ func InitializeConfig() {
viper.SetDefault("StaticDir", "static") viper.SetDefault("StaticDir", "static")
viper.SetDefault("ArchetypeDir", "archetypes") viper.SetDefault("ArchetypeDir", "archetypes")
viper.SetDefault("PublishDir", "public") viper.SetDefault("PublishDir", "public")
viper.SetDefault("DataDir", "data")
viper.SetDefault("DefaultLayout", "post") viper.SetDefault("DefaultLayout", "post")
viper.SetDefault("BuildDrafts", false) viper.SetDefault("BuildDrafts", false)
viper.SetDefault("BuildFuture", false) viper.SetDefault("BuildFuture", false)
@ -287,6 +288,7 @@ func getDirList() []string {
return nil return nil
} }
filepath.Walk(helpers.AbsPathify(viper.GetString("DataDir")), walker)
filepath.Walk(helpers.AbsPathify(viper.GetString("ContentDir")), walker) filepath.Walk(helpers.AbsPathify(viper.GetString("ContentDir")), walker)
filepath.Walk(helpers.AbsPathify(viper.GetString("LayoutDir")), walker) filepath.Walk(helpers.AbsPathify(viper.GetString("LayoutDir")), walker)
filepath.Walk(helpers.AbsPathify(viper.GetString("StaticDir")), walker) filepath.Walk(helpers.AbsPathify(viper.GetString("StaticDir")), walker)

View file

@ -130,6 +130,7 @@ func NewSite(cmd *cobra.Command, args []string) {
mkdir(createpath, "content") mkdir(createpath, "content")
mkdir(createpath, "archetypes") mkdir(createpath, "archetypes")
mkdir(createpath, "static") mkdir(createpath, "static")
mkdir(createpath, "data")
createConfig(createpath, configFormat) createConfig(createpath, configFormat)
} }

View file

@ -34,6 +34,7 @@ import (
bp "github.com/spf13/hugo/bufferpool" bp "github.com/spf13/hugo/bufferpool"
"github.com/spf13/hugo/helpers" "github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/hugofs" "github.com/spf13/hugo/hugofs"
"github.com/spf13/hugo/parser"
"github.com/spf13/hugo/source" "github.com/spf13/hugo/source"
"github.com/spf13/hugo/target" "github.com/spf13/hugo/target"
"github.com/spf13/hugo/tpl" "github.com/spf13/hugo/tpl"
@ -81,6 +82,7 @@ type Site struct {
params map[string]interface{} params map[string]interface{}
draftCount int draftCount int
futureCount int futureCount int
Data map[string]interface{}
} }
type targetList struct { type targetList struct {
@ -112,6 +114,7 @@ type SiteInfo struct {
BuildDrafts bool BuildDrafts bool
canonifyUrls bool canonifyUrls bool
paginationPageCount uint64 paginationPageCount uint64
Data *map[string]interface{}
} }
// SiteSocial is a place to put social details on a site level. These are the // SiteSocial is a place to put social details on a site level. These are the
@ -264,12 +267,71 @@ func (s *Site) addTemplate(name, data string) error {
return s.Tmpl.AddTemplate(name, data) return s.Tmpl.AddTemplate(name, data)
} }
func (s *Site) loadData(fs source.Input) (err error) {
s.Data = make(map[string]interface{})
for _, r := range fs.Files() {
// Crawl in data tree to insert data
var current map[string]interface{}
current = s.Data
for _, key := range strings.Split(r.Dir(), string(os.PathSeparator)) {
if key != "" {
if _, ok := current[key]; !ok {
current[key] = make(map[string]interface{})
}
current = current[key].(map[string]interface{})
}
}
// Read data file
data, err := readFile(r)
if err != nil {
return err
}
// 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 {
return errors.New("Data in " + r.Path() + " is overrided in subfolder.")
} else {
data[key] = value
}
}
}
// Insert data
current[r.BaseFileName()] = data
}
return
}
func readFile(f *source.File) (interface{}, error) {
switch f.Extension() {
case "yaml", "yml":
return parser.HandleYamlMetaData(f.Bytes())
case "json":
return parser.HandleJsonMetaData(f.Bytes())
case "toml":
return parser.HandleTomlMetaData(f.Bytes())
default:
return nil, errors.New("Not supported for data: " + f.Extension())
}
}
func (s *Site) Process() (err error) { func (s *Site) Process() (err error) {
if err = s.initialize(); err != nil { if err = s.initialize(); err != nil {
return return
} }
s.prepTemplates() s.prepTemplates()
s.Tmpl.PrintErrors() s.Tmpl.PrintErrors()
if err = s.loadData(&source.Filesystem{Base: s.absDataDir()}); err != nil {
return
}
s.timerStep("load data")
s.timerStep("initialize & template prep") s.timerStep("initialize & template prep")
if err = s.CreatePages(); err != nil { if err = s.CreatePages(); err != nil {
return return
@ -379,6 +441,7 @@ func (s *Site) initializeSiteInfo() {
Menus: &s.Menus, Menus: &s.Menus,
Params: params, Params: params,
Permalinks: permalinks, Permalinks: permalinks,
Data: &s.Data,
} }
} }
@ -386,6 +449,10 @@ func (s *Site) hasTheme() bool {
return viper.GetString("theme") != "" return viper.GetString("theme") != ""
} }
func (s *Site) absDataDir() string {
return helpers.AbsPathify(viper.GetString("DataDir"))
}
func (s *Site) absThemeDir() string { func (s *Site) absThemeDir() string {
return helpers.AbsPathify("themes/" + viper.GetString("theme")) return helpers.AbsPathify("themes/" + viper.GetString("theme"))
} }

View file

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"html/template" "html/template"
"io" "io"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
"testing" "testing"
@ -745,3 +746,18 @@ func TestWeightedTaxonomies(t *testing.T) {
t.Errorf("Pages in unexpected order, 'bza' expected first, got '%v'", s.Taxonomies["categories"]["e"][0].Page.Title) t.Errorf("Pages in unexpected order, 'bza' expected first, got '%v'", s.Taxonomies["categories"]["e"][0].Page.Title)
} }
} }
func TestDataDir(t *testing.T) {
sources := []source.ByteSource{
{filepath.FromSlash("test" + string(os.PathSeparator) + "foo.yaml"), []byte("bar: foofoo")},
{filepath.FromSlash("test.yaml"), []byte("hello:\n- world: foo")},
}
s := &Site{}
s.loadData(&source.InMemorySource{ByteSource: sources})
expected := "map[test:map[hello:[map[world:foo]] foo:map[bar:foofoo]]]"
if fmt.Sprint(s.Data) != expected {
t.Errorf("Expected structure '%s', got '%s'", expected, s.Data)
}
}