Make it possible to add a language in server mode
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Sat, 6 Aug 2016 12:51:50 +0000 (14:51 +0200)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Tue, 6 Sep 2016 15:32:17 +0000 (18:32 +0300)
See #2309

commands/benchmark.go
commands/hugo.go
hugolib/hugo_sites.go
hugolib/hugo_sites_test.go
hugolib/multilingual.go
hugolib/node.go

index 56a50578b1d34759401804bcf584006ed894c360..b24d06ff4d53f76a517389f4516bc0290ee84e9e 100644 (file)
@@ -57,8 +57,7 @@ func benchmark(cmd *cobra.Command, args []string) error {
                        return err
                }
                for i := 0; i < benchmarkTimes; i++ {
-                       _ = buildSites()
-                       Hugo.Reset()
+                       _ = resetAndbuildSites(false)
                }
                pprof.WriteHeapProfile(f)
                f.Close()
@@ -76,8 +75,7 @@ func benchmark(cmd *cobra.Command, args []string) error {
                pprof.StartCPUProfile(f)
                defer pprof.StopCPUProfile()
                for i := 0; i < benchmarkTimes; i++ {
-                       _ = buildSites()
-                       Hugo.Reset()
+                       _ = resetAndbuildSites(false)
                }
        }
 
index eb6beebd19cd5e7e8a711d0a716a98f5396f0a4e..723441911efc5d543767cf223757e0c7898afdbb 100644 (file)
@@ -419,14 +419,6 @@ func InitializeConfig(subCmdVs ...*cobra.Command) error {
                        helpers.HugoReleaseVersion(), minVersion)
        }
 
-       h, err := hugolib.NewHugoSitesFromConfiguration()
-
-       if err != nil {
-               return err
-       }
-       //TODO(bep) ml refactor ...
-       Hugo = h
-
        return nil
 
 }
@@ -444,8 +436,7 @@ func watchConfig() {
        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 @@ func getDirList() []string {
        return a
 }
 
-func buildSites(watching ...bool) (err error) {
+func reCreateAndbuildSites(watching bool) (err error) {
+       fmt.Println("Started building sites ...")
+       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 ...")
-       w := len(watching) > 0 && watching[0]
-       return Hugo.Build(hugolib.BuildCfg{Watching: w, PrintStats: true})
+       return Hugo.Build(hugolib.BuildCfg{Watching: watching, PrintStats: true})
 }
 
 func rebuildSites(events []fsnotify.Event) error {
+       initSites()
        return Hugo.Rebuild(hugolib.BuildCfg{PrintStats: true}, events...)
 }
 
index 93ffc53da4706d3cbf62c4d05eb30c3a0f5682c6..b41334fdc66ab7efb9a8e3eac02ad1070ddec00f 100644 (file)
@@ -40,24 +40,25 @@ type HugoSites struct {
 // 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
-       }
-       defaultLang := viper.GetString("DefaultContentLanguage")
-       if defaultLang == "" {
-               defaultLang = "en"
+       langConfig, err := newMultiLingualFromSites(sites...)
+
+       if err != nil {
+               return nil, err
        }
-       langConfig := &Multilingual{Languages: languages, DefaultLang: NewLanguage(defaultLang)}
 
        return &HugoSites{Multilingual: langConfig, Sites: sites}, nil
 }
 
 // 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 @@ func NewHugoSitesFromConfiguration() (*HugoSites, error) {
 
        }
 
-       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 @@ type BuildCfg struct {
        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 @@ type BuildCfg struct {
 }
 
 // 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 @@ func (h HugoSites) Build(config BuildCfg) error {
        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,9 +221,23 @@ func (h HugoSites) Build(config BuildCfg) error {
 }
 
 // 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]
 
        for _, s := range h.Sites {
index cc8a029937bcc4ab4652d4df2f36637fc6e49afe..158b6512498aef8b85d44bd153d21df8ea178429 100644 (file)
@@ -39,7 +39,7 @@ func testCommonResetState() {
 
 func TestMultiSitesBuild(t *testing.T) {
        testCommonResetState()
-       sites := createMultiTestSites(t)
+       sites := createMultiTestSites(t, multiSiteTomlConfig)
 
        err := sites.Build(BuildCfg{})
 
@@ -141,7 +141,7 @@ func TestMultiSitesBuild(t *testing.T) {
 
 func TestMultiSitesRebuild(t *testing.T) {
        testCommonResetState()
-       sites := createMultiTestSites(t)
+       sites := createMultiTestSites(t, multiSiteTomlConfig)
        cfg := BuildCfg{}
 
        err := sites.Build(cfg)
@@ -299,10 +299,95 @@ func TestMultiSitesRebuild(t *testing.T) {
 
                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(),
@@ -445,35 +530,6 @@ draft: true
 `)},
        }
 
-       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 {
                t.Fatalf("Failed to load config: %s", err)
index 8bc7bea3396b266299feb1c5dd5ed6a302f8988a..cc9f607f2a31ec0d421929560b655757f714deea 100644 (file)
@@ -6,6 +6,7 @@ import (
        "sort"
        "strings"
 
+       "errors"
        "fmt"
 
        "github.com/spf13/cast"
@@ -63,6 +64,26 @@ func (ml *Multilingual) Language(lang string) *Language {
        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 {
        return len(ml.Languages) > 1
 }
index 2d07e426b6a9e4cac92ed5e886c8a01469438b34..db1d166315959ad3fdb96330206441ea7a954d36 100644 (file)
@@ -195,6 +195,9 @@ func (n *Node) Language() *Language {
 }
 
 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
        }