shithub: hugo

Download patch

ref: 596e0e98e4483d2a0a709412a338ceddb6538757
parent: 7cac19b1e3d2631395b88998b523a5a6d84b9e29
author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
date: Sat Aug 6 10:51:50 EDT 2016

Make it possible to add a language in server mode

See #2309

--- a/commands/benchmark.go
+++ b/commands/benchmark.go
@@ -57,8 +57,7 @@
 			return err
 		}
 		for i := 0; i < benchmarkTimes; i++ {
-			_ = buildSites()
-			Hugo.Reset()
+			_ = resetAndbuildSites(false)
 		}
 		pprof.WriteHeapProfile(f)
 		f.Close()
@@ -76,8 +75,7 @@
 		pprof.StartCPUProfile(f)
 		defer pprof.StopCPUProfile()
 		for i := 0; i < benchmarkTimes; i++ {
-			_ = buildSites()
-			Hugo.Reset()
+			_ = resetAndbuildSites(false)
 		}
 	}
 
--- a/commands/hugo.go
+++ b/commands/hugo.go
@@ -419,14 +419,6 @@
 			helpers.HugoReleaseVersion(), minVersion)
 	}
 
-	h, err := hugolib.NewHugoSitesFromConfiguration()
-
-	if err != nil {
-		return err
-	}
-	//TODO(bep) ml refactor ...
-	Hugo = h
-
 	return nil
 
 }
@@ -444,8 +436,7 @@
 	viper.OnConfigChange(func(e fsnotify.Event) {
 		fmt.Println("Config file changed:", e.Name)
 		// Force a full rebuild
-		Hugo.Reset()
-		utils.CheckErr(buildSites(true))
+		utils.CheckErr(reCreateAndbuildSites(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()
@@ -638,13 +629,39 @@
 	return a
 }
 
-func buildSites(watching ...bool) (err error) {
+func reCreateAndbuildSites(watching bool) (err error) {
 	fmt.Println("Started building sites ...")
-	w := len(watching) > 0 && watching[0]
-	return Hugo.Build(hugolib.BuildCfg{Watching: w, PrintStats: true})
+	return Hugo.Build(hugolib.BuildCfg{CreateSitesFromConfig: true, Watching: watching, PrintStats: true})
 }
 
+func resetAndbuildSites(watching bool) (err error) {
+	fmt.Println("Started building sites ...")
+	return Hugo.Build(hugolib.BuildCfg{ResetState: true, Watching: watching, PrintStats: true})
+}
+
+func initSites() error {
+	if Hugo != nil {
+		return nil
+	}
+
+	h, err := hugolib.NewHugoSitesFromConfiguration()
+
+	if err != nil {
+		return err
+	}
+	Hugo = h
+
+	return nil
+}
+
+func buildSites(watching bool) (err error) {
+	initSites()
+	fmt.Println("Started building sites ...")
+	return Hugo.Build(hugolib.BuildCfg{Watching: watching, PrintStats: true})
+}
+
 func rebuildSites(events []fsnotify.Event) error {
+	initSites()
 	return Hugo.Rebuild(hugolib.BuildCfg{PrintStats: true}, events...)
 }
 
--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -40,18 +40,11 @@
 // NewHugoSites creates a new collection of sites given the input sites, building
 // a language configuration based on those.
 func NewHugoSites(sites ...*Site) (*HugoSites, error) {
-	languages := make(Languages, len(sites))
-	for i, s := range sites {
-		if s.Language == nil {
-			return nil, errors.New("Missing language for site")
-		}
-		languages[i] = s.Language
+	langConfig, err := newMultiLingualFromSites(sites...)
+
+	if err != nil {
+		return nil, err
 	}
-	defaultLang := viper.GetString("DefaultContentLanguage")
-	if defaultLang == "" {
-		defaultLang = "en"
-	}
-	langConfig := &Multilingual{Languages: languages, DefaultLang: NewLanguage(defaultLang)}
 
 	return &HugoSites{Multilingual: langConfig, Sites: sites}, nil
 }
@@ -58,6 +51,14 @@
 
 // NewHugoSitesFromConfiguration creates HugoSites from the global Viper config.
 func NewHugoSitesFromConfiguration() (*HugoSites, error) {
+	sites, err := createSitesFromConfig()
+	if err != nil {
+		return nil, err
+	}
+	return NewHugoSites(sites...)
+}
+
+func createSitesFromConfig() ([]*Site, error) {
 	var sites []*Site
 	multilingual := viper.GetStringMap("Languages")
 	if len(multilingual) == 0 {
@@ -80,19 +81,43 @@
 
 	}
 
-	return NewHugoSites(sites...)
-
+	return sites, nil
 }
 
 // Reset resets the sites, making it ready for a full rebuild.
 // TODO(bep) multilingo
-func (h HugoSites) Reset() {
+func (h *HugoSites) reset() {
 	for i, s := range h.Sites {
 		h.Sites[i] = s.Reset()
 	}
 }
 
-func (h HugoSites) toSiteInfos() []*SiteInfo {
+func (h *HugoSites) reCreateFromConfig() error {
+	oldSite := h.Sites[0]
+	sites, err := createSitesFromConfig()
+
+	if err != nil {
+		return err
+	}
+
+	langConfig, err := newMultiLingualFromSites(sites...)
+
+	if err != nil {
+		return err
+	}
+
+	h.Sites = sites
+	h.Multilingual = langConfig
+
+	for _, s := range h.Sites {
+		// TODO(bep) ml Tmpl
+		s.Tmpl = oldSite.Tmpl
+	}
+
+	return nil
+}
+
+func (h *HugoSites) toSiteInfos() []*SiteInfo {
 	infos := make([]*SiteInfo, len(h.Sites))
 	for i, s := range h.Sites {
 		infos[i] = &s.Info
@@ -106,6 +131,11 @@
 	Watching bool
 	// Print build stats at the end of a build
 	PrintStats bool
+	// Reset site state before build. Use to force full rebuilds.
+	ResetState bool
+	// Re-creates the sites from configuration before a build.
+	// This is needed if new languages are added.
+	CreateSitesFromConfig bool
 	// Skip rendering. Useful for testing.
 	SkipRender bool
 	// Use this to add templates to use for rendering.
@@ -114,13 +144,19 @@
 }
 
 // Build builds all sites.
-func (h HugoSites) Build(config BuildCfg) error {
+func (h *HugoSites) Build(config BuildCfg) error {
 
-	if h.Sites == nil || len(h.Sites) == 0 {
-		return errors.New("No site(s) to build")
+	t0 := time.Now()
+
+	if config.ResetState {
+		h.reset()
 	}
 
-	t0 := time.Now()
+	if config.CreateSitesFromConfig {
+		if err := h.reCreateFromConfig(); err != nil {
+			return err
+		}
+	}
 
 	// We should probably refactor the Site and pull up most of the logic from there to here,
 	// but that seems like a daunting task.
@@ -143,6 +179,7 @@
 	if len(h.Sites) > 1 {
 		// Initialize the rest
 		for _, site := range h.Sites[1:] {
+			// TODO(bep) ml Tmpl
 			site.Tmpl = firstSite.Tmpl
 			site.initializeSiteInfo()
 		}
@@ -184,8 +221,22 @@
 }
 
 // Rebuild rebuilds all sites.
-func (h HugoSites) Rebuild(config BuildCfg, events ...fsnotify.Event) error {
+func (h *HugoSites) Rebuild(config BuildCfg, events ...fsnotify.Event) error {
 	t0 := time.Now()
+
+	if config.CreateSitesFromConfig {
+		return errors.New("Rebuild does not support 'CreateSitesFromConfig'. Use Build.")
+	}
+
+	if config.ResetState {
+		return errors.New("Rebuild does not support 'ResetState'. Use Build.")
+	}
+
+	for _, s := range h.Sites {
+		// TODO(bep) ml
+		s.Multilingual = h.Multilingual
+		s.RunMode.Watching = config.Watching
+	}
 
 	firstSite := h.Sites[0]
 
--- a/hugolib/hugo_sites_test.go
+++ b/hugolib/hugo_sites_test.go
@@ -39,7 +39,7 @@
 
 func TestMultiSitesBuild(t *testing.T) {
 	testCommonResetState()
-	sites := createMultiTestSites(t)
+	sites := createMultiTestSites(t, multiSiteTomlConfig)
 
 	err := sites.Build(BuildCfg{})
 
@@ -141,7 +141,7 @@
 
 func TestMultiSitesRebuild(t *testing.T) {
 	testCommonResetState()
-	sites := createMultiTestSites(t)
+	sites := createMultiTestSites(t, multiSiteTomlConfig)
 	cfg := BuildCfg{}
 
 	err := sites.Build(cfg)
@@ -299,11 +299,96 @@
 
 		this.assertFunc(t)
 	}
+}
 
+func TestAddNewLanguage(t *testing.T) {
+	testCommonResetState()
+
+	sites := createMultiTestSites(t, multiSiteTomlConfig)
+	cfg := BuildCfg{}
+
+	err := sites.Build(cfg)
+
+	if err != nil {
+		t.Fatalf("Failed to build sites: %s", err)
+	}
+
+	newConfig := multiSiteTomlConfig + `
+
+[Languages.no]
+weight = 15
+title = "Norsk"
+`
+
+	writeNewContentFile(t, "Norwegian Contentfile", "2016-01-01", "content/sect/doc1.no.md", 10)
+	// replace the config
+	writeSource(t, "multilangconfig.toml", newConfig)
+
+	// Watching does not work with in-memory fs, so we trigger a reload manually
+	require.NoError(t, viper.ReadInConfig())
+
+	err = sites.Build(BuildCfg{CreateSitesFromConfig: true})
+
+	if err != nil {
+		t.Fatalf("Failed to rebuild sites: %s", err)
+	}
+
+	require.Len(t, sites.Sites, 3, fmt.Sprintf("Len %d", len(sites.Sites)))
+
+	// The Norwegian site should be put in the middle (language weight=15)
+	enSite := sites.Sites[0]
+	noSite := sites.Sites[1]
+	frSite := sites.Sites[2]
+	require.True(t, enSite.Language.Lang == "en", enSite.Language.Lang)
+	require.True(t, noSite.Language.Lang == "no", noSite.Language.Lang)
+	require.True(t, frSite.Language.Lang == "fr", frSite.Language.Lang)
+
+	require.Len(t, enSite.Pages, 3)
+	require.Len(t, frSite.Pages, 3)
+
+	// Veriy Norwegian site
+	require.Len(t, noSite.Pages, 1)
+	noPage := noSite.Pages[0]
+	require.Equal(t, "Norwegian Contentfile", noPage.Title)
+	require.Equal(t, "no", noPage.Lang())
+	require.Len(t, noPage.Translations(), 2)
+	require.Len(t, noPage.AllTranslations(), 3)
+	require.Equal(t, "en", noPage.Translations()[0].Lang())
+	//noFile := readDestination(t, "/public/no/doc1/index.html")
+	//require.True(t, strings.Contains("foo", noFile), noFile)
+
 }
 
-func createMultiTestSites(t *testing.T) *HugoSites {
+var multiSiteTomlConfig = `
+DefaultExtension = "html"
+baseurl = "http://example.com/blog"
+DisableSitemap = false
+DisableRSS = false
+RSSUri = "index.xml"
 
+paginate = 2
+DefaultContentLanguage = "fr"
+
+[permalinks]
+  other = "/somewhere/else/:filename"
+
+[Taxonomies]
+tag = "tags"
+
+[Languages]
+[Languages.en]
+weight = 10
+title = "English"
+
+[Languages.fr]
+weight = 20
+title = "Français"
+[Languages.fr.Taxonomies]
+plaque = "plaques"
+`
+
+func createMultiTestSites(t *testing.T, tomlConfig string) *HugoSites {
+
 	// Add some layouts
 	if err := afero.WriteFile(hugofs.Source(),
 		filepath.Join("layouts", "_default/single.html"),
@@ -444,35 +529,6 @@
 # Draft
 `)},
 	}
-
-	tomlConfig := `
-DefaultExtension = "html"
-baseurl = "http://example.com/blog"
-DisableSitemap = false
-DisableRSS = false
-RSSUri = "index.xml"
-
-paginate = 2
-DefaultContentLanguage = "fr"
-
-
-[permalinks]
-  other = "/somewhere/else/:filename"
-
-[Taxonomies]
-tag = "tags"
-
-[Languages]
-[Languages.en]
-weight = 1
-title = "English"
-
-[Languages.fr]
-weight = 2
-title = "Français"
-[Languages.fr.Taxonomies]
-plaque = "plaques"
-`
 
 	writeSource(t, "multilangconfig.toml", tomlConfig)
 	if err := LoadGlobalConfig("", "multilangconfig.toml"); err != nil {
--- a/hugolib/multilingual.go
+++ b/hugolib/multilingual.go
@@ -6,6 +6,7 @@
 	"sort"
 	"strings"
 
+	"errors"
 	"fmt"
 
 	"github.com/spf13/cast"
@@ -61,6 +62,26 @@
 		}
 	})
 	return ml.langMap[lang]
+}
+
+func newMultiLingualFromSites(sites ...*Site) (*Multilingual, error) {
+	languages := make(Languages, len(sites))
+
+	for i, s := range sites {
+		if s.Language == nil {
+			return nil, errors.New("Missing language for site")
+		}
+		languages[i] = s.Language
+	}
+
+	defaultLang := viper.GetString("DefaultContentLanguage")
+
+	if defaultLang == "" {
+		defaultLang = "en"
+	}
+
+	return &Multilingual{Languages: languages, DefaultLang: NewLanguage(defaultLang)}, nil
+
 }
 
 func (ml *Multilingual) enabled() bool {
--- a/hugolib/node.go
+++ b/hugolib/node.go
@@ -195,6 +195,9 @@
 }
 
 func (n *Node) Lang() string {
+	// When set, Language can be different from lang in the case where there is a
+	// content file (doc.sv.md) with language indicator, but there is no language
+	// config for that language. Then the language will fall back on the site default.
 	if n.Language() != nil {
 		return n.Language().Lang
 	}
--