Introduce HugoSites type

And a Hugo global variable which contains the site under build.

This is really needed to get some level of control of the "multiple languages" in play.

There are still work related to this scattered around, but that will come.

With this commit, the multilingual feature is starting to work.
This commit is contained in:
Bjørn Erik Pedersen 2016-07-26 10:24:27 +02:00
parent 618948e4a8
commit 75dd596e6c
16 changed files with 117 additions and 81 deletions

View file

@ -57,8 +57,8 @@ func benchmark(cmd *cobra.Command, args []string) error {
return err
}
for i := 0; i < benchmarkTimes; i++ {
Sites = nil
_ = buildSite()
_ = buildSites()
Hugo.Reset()
}
pprof.WriteHeapProfile(f)
f.Close()
@ -76,8 +76,8 @@ func benchmark(cmd *cobra.Command, args []string) error {
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
for i := 0; i < benchmarkTimes; i++ {
Sites = nil
_ = buildSite()
_ = buildSites()
Hugo.Reset()
}
}

View file

@ -46,10 +46,20 @@ import (
"github.com/spf13/viper"
)
// Sites represents the Hugo sites to build. This variable is exported as it
type HugoSites []*hugolib.Site
// Reset resets the sites, making it ready for a full rebuild.
// TODO(bep) multilingo
func (h HugoSites) Reset() {
for i, s := range h {
h[i] = s.Reset()
}
}
// Hugo represents the Hugo sites to build. This variable is exported as it
// is used by at least one external library (the Hugo caddy plugin). We should
// provide a cleaner external API, but until then, this is it.
var Sites map[string]*hugolib.Site
var Hugo HugoSites
// Reset resets Hugo ready for a new full build. This is mainly only useful
// for benchmark testing etc. via the CLI commands.
@ -493,7 +503,15 @@ func InitializeConfig(subCmdVs ...*cobra.Command) error {
helpers.HugoReleaseVersion(), minVersion)
}
return readMultilingualConfiguration()
h, err := readMultilingualConfiguration()
if err != nil {
return err
}
//TODO(bep) refactor ...
Hugo = h
return nil
}
@ -510,8 +528,8 @@ func watchConfig() {
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("Config file changed:", e.Name)
// Force a full rebuild
Sites = nil
utils.CheckErr(buildSite(true))
Hugo.Reset()
utils.CheckErr(buildSites(true))
if !viper.GetBool("DisableLiveReload") {
// Will block forever trying to write to a channel that nobody is reading if livereload isn't initialized
livereload.ForceRefresh()
@ -537,7 +555,7 @@ func build(watches ...bool) error {
if len(watches) > 0 && watches[0] {
watch = true
}
if err := buildSite(buildWatch || watch); err != nil {
if err := buildSites(buildWatch || watch); err != nil {
return fmt.Errorf("Error building site: %s", err)
}
@ -704,32 +722,21 @@ func getDirList() []string {
return a
}
func buildSite(watching ...bool) (err error) {
func buildSites(watching ...bool) (err error) {
fmt.Println("Started building site")
t0 := time.Now()
if Sites == nil {
Sites = make(map[string]*hugolib.Site)
}
for _, lang := range langConfigsList {
for _, site := range Hugo {
t1 := time.Now()
mainSite, present := Sites[lang.Lang]
if !present {
mainSite = new(hugolib.Site)
Sites[lang.Lang] = mainSite
mainSite.SetMultilingualConfig(lang, langConfigsList)
}
if len(watching) > 0 && watching[0] {
mainSite.RunMode.Watching = true
site.RunMode.Watching = true
}
if err := mainSite.Build(); err != nil {
if err := site.Build(); err != nil {
return err
}
mainSite.Stats(lang.Lang, t1)
site.Stats(t1)
}
jww.FEEDBACK.Printf("total in %v ms\n", int(1000*time.Since(t0).Seconds()))
@ -737,18 +744,17 @@ func buildSite(watching ...bool) (err error) {
return nil
}
func rebuildSite(events []fsnotify.Event) error {
func rebuildSites(events []fsnotify.Event) error {
t0 := time.Now()
for _, lang := range langConfigsList {
for _, site := range Hugo {
t1 := time.Now()
site := Sites[lang.Lang]
if err := site.ReBuild(events); err != nil {
return err
}
site.Stats(lang.Lang, t1)
site.Stats(t1)
}
jww.FEEDBACK.Printf("total in %v ms\n", int(1000*time.Since(t0).Seconds()))
@ -969,7 +975,7 @@ func NewWatcher(port int) error {
const layout = "2006-01-02 15:04 -0700"
fmt.Println(time.Now().Format(layout))
rebuildSite(dynamicEvents)
rebuildSites(dynamicEvents)
if !buildWatch && !viper.GetBool("DisableLiveReload") {
// Will block forever trying to write to a channel that nobody is reading if livereload isn't initialized

View file

@ -11,24 +11,30 @@ import (
"github.com/spf13/viper"
)
var langConfigsList hugolib.Languages
func readMultilingualConfiguration() error {
func readMultilingualConfiguration() (HugoSites, error) {
h := make(HugoSites, 0)
multilingual := viper.GetStringMap("Multilingual")
if len(multilingual) == 0 {
// TODO(bep) multilingo langConfigsList = append(langConfigsList, hugolib.NewLanguage("en"))
return nil
h = append(h, hugolib.NewSite(hugolib.NewLanguage("en")))
return h, nil
}
var err error
langConfigsList, err = toSortedLanguages(multilingual)
langConfigsList, err := toSortedLanguages(multilingual)
if err != nil {
return fmt.Errorf("Failed to parse multilingual config: %s", err)
return nil, fmt.Errorf("Failed to parse multilingual config: %s", err)
}
return nil
for _, lang := range langConfigsList {
s := hugolib.NewSite(lang)
s.SetMultilingualConfig(lang, langConfigsList)
h = append(h, s)
}
return h, nil
}
func toSortedLanguages(l map[string]interface{}) (hugolib.Languages, error) {

View file

@ -87,7 +87,7 @@ func TestDataDirUnknownFormat(t *testing.T) {
sources := []source.ByteSource{
{Name: filepath.FromSlash("test.roml"), Content: []byte("boo")},
}
s := &Site{}
s := newSiteDefaultLang()
err := s.loadData([]source.Input{&source.InMemorySource{ByteSource: sources}})
if err != nil {
t.Fatalf("Should not return an error")
@ -95,7 +95,7 @@ func TestDataDirUnknownFormat(t *testing.T) {
}
func doTestDataDir(t *testing.T, expected interface{}, sources []source.Input) {
s := &Site{}
s := newSiteDefaultLang()
err := s.loadData(sources)
if err != nil {
t.Fatalf("Error loading data: %s", err)

View file

@ -47,6 +47,7 @@ func TestDefaultHandler(t *testing.T) {
s := &Site{
Source: &source.InMemorySource{ByteSource: sources},
targets: targetList{page: &target.PagePub{UglyURLs: true}},
Lang: NewLanguage("en"),
}
s.initializeSiteInfo()

View file

@ -683,6 +683,7 @@ func createTestSite(pageSources []source.ByteSource) *Site {
s := &Site{
Source: &source.InMemorySource{ByteSource: pageSources},
Lang: newDefaultLanguage(),
}
return s
}

View file

@ -22,6 +22,11 @@ func NewLanguage(lang string) *Language {
return &Language{Lang: lang, params: make(map[string]interface{})}
}
// TODO(bep) multilingo
func newDefaultLanguage() *Language {
return NewLanguage("en")
}
type Languages []*Language
func NewLanguages(l ...*Language) Languages {
@ -93,10 +98,9 @@ func (l *Language) Get(key string) interface{} {
return viper.Get(key)
}
// TODO(bep) multilingo move this to a constructor.
func (s *Site) SetMultilingualConfig(currentLang *Language, languages Languages) {
// TODO(bep) multilingo evaluate
viper.Set("CurrentLanguage", currentLang)
ml := &Multilingual{
Languages: languages,
}
@ -108,14 +112,11 @@ func (s *Site) multilingualEnabled() bool {
return s.Multilingual != nil && s.Multilingual.enabled()
}
func currentLanguageString() string {
return currentLanguage().Lang
// TODO(bep) multilingo remove these
func (s *Site) currentLanguageString() string {
return s.currentLanguage().Lang
}
func currentLanguage() *Language {
l := viper.Get("CurrentLanguage")
if l == nil {
panic("CurrentLanguage not set")
}
return l.(*Language)
func (s *Site) currentLanguage() *Language {
return s.Lang
}

View file

@ -226,7 +226,7 @@ func doTestPaginator(t *testing.T, useViper bool) {
viper.Set("paginate", -1)
}
pages := createTestPages(12)
s := &Site{}
s := newSiteDefaultLang()
n1 := s.newHomeNode()
n2 := s.newHomeNode()
n1.Data["Pages"] = pages
@ -264,7 +264,7 @@ func TestPaginatorWithNegativePaginate(t *testing.T) {
defer viper.Reset()
viper.Set("paginate", -1)
s := &Site{}
s := newSiteDefaultLang()
_, err := s.newHomeNode().Paginator()
assert.NotNil(t, err)
}
@ -287,7 +287,7 @@ func doTestPaginate(t *testing.T, useViper bool) {
}
pages := createTestPages(6)
s := &Site{}
s := newSiteDefaultLang()
n1 := s.newHomeNode()
n2 := s.newHomeNode()
@ -320,7 +320,7 @@ func doTestPaginate(t *testing.T, useViper bool) {
}
func TestInvalidOptions(t *testing.T) {
s := &Site{}
s := newSiteDefaultLang()
n1 := s.newHomeNode()
_, err := n1.Paginate(createTestPages(1), 1, 2)
assert.NotNil(t, err)
@ -335,7 +335,7 @@ func TestPaginateWithNegativePaginate(t *testing.T) {
defer viper.Reset()
viper.Set("paginate", -1)
s := &Site{}
s := newSiteDefaultLang()
_, err := s.newHomeNode().Paginate(createTestPages(2))
assert.NotNil(t, err)
}
@ -358,7 +358,7 @@ func TestPaginatorFollowedByPaginateShouldFail(t *testing.T) {
defer viper.Reset()
viper.Set("paginate", 10)
s := &Site{}
s := newSiteDefaultLang()
n1 := s.newHomeNode()
n2 := s.newHomeNode()
@ -377,7 +377,7 @@ func TestPaginateFollowedByDifferentPaginateShouldFail(t *testing.T) {
defer viper.Reset()
viper.Set("paginate", 10)
s := &Site{}
s := newSiteDefaultLang()
n1 := s.newHomeNode()
n2 := s.newHomeNode()

View file

@ -40,6 +40,7 @@ func TestRobotsTXTOutput(t *testing.T) {
s := &Site{
Source: &source.InMemorySource{ByteSource: weightedSources},
Lang: newDefaultLanguage(),
}
s.initializeSiteInfo()

View file

@ -55,6 +55,7 @@ func TestRSSOutput(t *testing.T) {
hugofs.InitMemFs()
s := &Site{
Source: &source.InMemorySource{ByteSource: weightedSources},
Lang: newDefaultLanguage(),
}
s.initializeSiteInfo()
s.prepTemplates("rss.xml", rssTemplate)

View file

@ -499,6 +499,7 @@ e`,
s := &Site{
Source: &source.InMemorySource{ByteSource: sources},
targets: targetList{page: &target.PagePub{UglyURLs: false}},
Lang: newDefaultLanguage(),
}
s.initializeSiteInfo()

View file

@ -92,6 +92,21 @@ type Site struct {
futureCount int
expiredCount int
Data map[string]interface{}
Lang *Language
}
// TODO(bep) multilingo
// Reset returns a new Site prepared for rebuild.
func (s *Site) Reset() *Site {
return &Site{Lang: s.Lang, Multilingual: s.Multilingual}
}
func NewSite(lang *Language) *Site {
return &Site{Lang: lang}
}
func newSiteDefaultLang() *Site {
return NewSite(newDefaultLanguage())
}
type targetList struct {
@ -705,7 +720,7 @@ func (s *Site) Process() (err error) {
i18nSources = []source.Input{&source.Filesystem{Base: themeI18nDir}, i18nSources[0]}
}
if err = loadI18n(i18nSources, currentLanguageString()); err != nil {
if err = loadI18n(i18nSources, s.currentLanguageString()); err != nil {
return
}
s.timerStep("load i18n")
@ -742,7 +757,7 @@ func (s *Site) setupTranslations() {
return
}
currentLang := currentLanguageString()
currentLang := s.currentLanguageString()
allTranslations := pagesToTranslationsMap(s.Multilingual, s.AllPages)
assignTranslationsToPages(allTranslations, s.AllPages)
@ -819,20 +834,10 @@ func (s *Site) initialize() (err error) {
func (s *Site) initializeSiteInfo() {
var (
lang *Language
lang *Language = s.Lang
languages Languages
)
cl := viper.Get("CurrentLanguage")
if cl == nil {
// Set default to english
// TODO(bep) multilingo this looks clumsy
lang = NewLanguage("en")
viper.Set("CurrentLanguage", lang)
} else {
lang = cl.(*Language)
}
if s.Multilingual != nil {
languages = s.Multilingual.Languages
}
@ -1610,7 +1615,7 @@ func (s *Site) newTaxonomyNode(t taxRenderInfo) (*Node, string) {
func (s *Site) addMultilingualPrefix(basePath string) string {
hadPrefix := strings.HasPrefix(basePath, "/")
if s.multilingualEnabled() {
basePath = path.Join(currentLanguageString(), basePath)
basePath = path.Join(s.currentLanguageString(), basePath)
if hadPrefix {
basePath = "/" + basePath
}
@ -1961,7 +1966,7 @@ func (s *Site) renderRobotsTXT() error {
// Stats prints Hugo builds stats to the console.
// This is what you see after a successful hugo build.
func (s *Site) Stats(lang string, t0 time.Time) {
func (s *Site) Stats(t0 time.Time) {
jww.FEEDBACK.Println(s.draftStats())
jww.FEEDBACK.Println(s.futureStats())
jww.FEEDBACK.Println(s.expiredStats())
@ -1974,9 +1979,9 @@ func (s *Site) Stats(lang string, t0 time.Time) {
jww.FEEDBACK.Printf("%d %s created\n", len(s.Taxonomies[pl]), pl)
}
if lang != "" {
jww.FEEDBACK.Printf("rendered lang %q in %v ms\n", lang, int(1000*time.Since(t0).Seconds()))
}
// TODO(bep) will always have lang. Not sure this should always be printed.
jww.FEEDBACK.Printf("rendered lang %q in %v ms\n", s.Lang.Lang, int(1000*time.Since(t0).Seconds()))
}
func (s *Site) setURLs(n *Node, in string) {

View file

@ -215,7 +215,7 @@ func TestRenderThingOrDefault(t *testing.T) {
for i, test := range tests {
s := &Site{}
s := newSiteDefaultLang()
p, err := NewPageFrom(strings.NewReader(pageSimpleTitle), "content/a/file.md")
if err != nil {
@ -262,6 +262,7 @@ func TestDraftAndFutureRender(t *testing.T) {
siteSetup := func() *Site {
s := &Site{
Source: &source.InMemorySource{ByteSource: sources},
Lang: newDefaultLanguage(),
}
s.initializeSiteInfo()
@ -320,6 +321,7 @@ func TestFutureExpirationRender(t *testing.T) {
siteSetup := func() *Site {
s := &Site{
Source: &source.InMemorySource{ByteSource: sources},
Lang: newDefaultLanguage(),
}
s.initializeSiteInfo()
@ -413,6 +415,7 @@ THE END.`, refShortcode)),
s := &Site{
Source: &source.InMemorySource{ByteSource: sources},
targets: targetList{page: &target.PagePub{UglyURLs: uglyURLs}},
Lang: newDefaultLanguage(),
}
s.initializeSiteInfo()
@ -479,6 +482,7 @@ func doTestShouldAlwaysHaveUglyURLs(t *testing.T, uglyURLs bool) {
s := &Site{
Source: &source.InMemorySource{ByteSource: sources},
targets: targetList{page: &target.PagePub{UglyURLs: uglyURLs}},
Lang: newDefaultLanguage(),
}
s.initializeSiteInfo()
@ -572,6 +576,7 @@ func doTestSectionNaming(t *testing.T, canonify, uglify, pluralize bool) {
s := &Site{
Source: &source.InMemorySource{ByteSource: sources},
targets: targetList{page: &target.PagePub{UglyURLs: uglify}},
Lang: newDefaultLanguage(),
}
s.initializeSiteInfo()
@ -636,6 +641,7 @@ func TestSkipRender(t *testing.T) {
s := &Site{
Source: &source.InMemorySource{ByteSource: sources},
targets: targetList{page: &target.PagePub{UglyURLs: true}},
Lang: newDefaultLanguage(),
}
s.initializeSiteInfo()
@ -693,6 +699,7 @@ func TestAbsURLify(t *testing.T) {
s := &Site{
Source: &source.InMemorySource{ByteSource: sources},
targets: targetList{page: &target.PagePub{UglyURLs: true}},
Lang: newDefaultLanguage(),
}
t.Logf("Rendering with BaseURL %q and CanonifyURLs set %v", viper.GetString("baseURL"), canonify)
s.initializeSiteInfo()
@ -788,6 +795,7 @@ func TestOrderedPages(t *testing.T) {
viper.Set("baseurl", "http://auth/bub")
s := &Site{
Source: &source.InMemorySource{ByteSource: weightedSources},
Lang: newDefaultLanguage(),
}
s.initializeSiteInfo()
@ -1040,6 +1048,7 @@ func TestWeightedTaxonomies(t *testing.T) {
viper.Set("taxonomies", taxonomies)
s := &Site{
Source: &source.InMemorySource{ByteSource: sources},
Lang: newDefaultLanguage(),
}
s.initializeSiteInfo()
@ -1107,6 +1116,7 @@ func setupLinkingMockSite(t *testing.T) *Site {
site := &Site{
Source: &source.InMemorySource{ByteSource: sources},
Lang: newDefaultLanguage(),
}
site.initializeSiteInfo()
@ -1402,13 +1412,13 @@ NOTE: should use the "permalinks" configuration with :filename
// Multilingual settings
viper.Set("Multilingual", true)
en := NewLanguage("en")
viper.Set("CurrentLanguage", en)
viper.Set("DefaultContentLanguage", "fr")
viper.Set("paginate", "2")
languages := NewLanguages(en, NewLanguage("fr"))
s := &Site{
Source: &source.InMemorySource{ByteSource: sources},
Lang: en,
Multilingual: &Multilingual{
Languages: languages,
},

View file

@ -73,7 +73,7 @@ func TestShouldNotAddTrailingSlashToBaseURL(t *testing.T) {
{"http://base.com", "http://base.com"}} {
viper.Set("BaseURL", this.in)
s := &Site{}
s := newSiteDefaultLang()
s.initializeSiteInfo()
if s.Info.BaseURL != template.URL(this.expected) {
@ -93,6 +93,7 @@ func TestPageCount(t *testing.T) {
viper.Set("paginate", 10)
s := &Site{
Source: &source.InMemorySource{ByteSource: urlFakeSource},
Lang: newDefaultLanguage(),
}
s.initializeSiteInfo()
s.prepTemplates("indexes/blue.html", indexTemplate)

View file

@ -27,7 +27,7 @@ func TestSiteInfoParams(t *testing.T) {
defer viper.Reset()
viper.Set("Params", map[string]interface{}{"MyGlobalParam": "FOOBAR_PARAM"})
s := &Site{}
s := newSiteDefaultLang()
s.initialize()
if s.Info.Params["MyGlobalParam"] != "FOOBAR_PARAM" {
@ -53,7 +53,7 @@ func TestSiteInfoPermalinks(t *testing.T) {
defer viper.Reset()
viper.Set("Permalinks", map[string]interface{}{"section": "/:title"})
s := &Site{}
s := newSiteDefaultLang()
s.initialize()
permalink := s.Info.Permalinks["section"]

View file

@ -17,11 +17,12 @@ import (
"bytes"
"testing"
"reflect"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/hugo/source"
"github.com/spf13/viper"
"reflect"
)
const SITEMAP_TEMPLATE = `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
@ -45,6 +46,7 @@ func TestSitemapOutput(t *testing.T) {
s := &Site{
Source: &source.InMemorySource{ByteSource: weightedSources},
Lang: newDefaultLanguage(),
}
s.initializeSiteInfo()