From 596e0e98e4483d2a0a709412a338ceddb6538757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Sat, 6 Aug 2016 14:51:50 +0200 Subject: [PATCH] Make it possible to add a language in server mode See #2309 --- commands/benchmark.go | 6 +- commands/hugo.go | 43 +++++++++---- hugolib/hugo_sites.go | 93 +++++++++++++++++++++------- hugolib/hugo_sites_test.go | 120 +++++++++++++++++++++++++++---------- hugolib/multilingual.go | 21 +++++++ hugolib/node.go | 3 + 6 files changed, 216 insertions(+), 70 deletions(-) diff --git a/commands/benchmark.go b/commands/benchmark.go index 56a50578b..b24d06ff4 100644 --- a/commands/benchmark.go +++ b/commands/benchmark.go @@ -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) } } diff --git a/commands/hugo.go b/commands/hugo.go index eb6beebd1..723441911 100644 --- a/commands/hugo.go +++ b/commands/hugo.go @@ -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 ...") - 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...) } diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go index 93ffc53da..b41334fdc 100644 --- a/hugolib/hugo_sites.go +++ b/hugolib/hugo_sites.go @@ -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 + 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 } // 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,14 +144,20 @@ type BuildCfg struct { } // Build builds all sites. -func (h HugoSites) Build(config BuildCfg) error { - - if h.Sites == nil || len(h.Sites) == 0 { - return errors.New("No site(s) to build") - } +func (h *HugoSites) Build(config BuildCfg) error { t0 := time.Now() + if config.ResetState { + h.reset() + } + + 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. // So for now, if there are more than one site (language), @@ -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 { diff --git a/hugolib/hugo_sites_test.go b/hugolib/hugo_sites_test.go index cc8a02993..158b65124 100644 --- a/hugolib/hugo_sites_test.go +++ b/hugolib/hugo_sites_test.go @@ -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) diff --git a/hugolib/multilingual.go b/hugolib/multilingual.go index 8bc7bea33..cc9f607f2 100644 --- a/hugolib/multilingual.go +++ b/hugolib/multilingual.go @@ -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 } diff --git a/hugolib/node.go b/hugolib/node.go index 2d07e426b..db1d16631 100644 --- a/hugolib/node.go +++ b/hugolib/node.go @@ -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 }