summaryrefslogtreecommitdiffhomepage
path: root/hugolib/hugo_sites.go
diff options
context:
space:
mode:
Diffstat (limited to 'hugolib/hugo_sites.go')
-rw-r--r--hugolib/hugo_sites.go633
1 files changed, 633 insertions, 0 deletions
diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go
new file mode 100644
index 000000000..4fe3f8771
--- /dev/null
+++ b/hugolib/hugo_sites.go
@@ -0,0 +1,633 @@
+// Copyright 2016-present The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hugolib
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+ "sync"
+
+ "github.com/gohugoio/hugo/deps"
+ "github.com/gohugoio/hugo/helpers"
+
+ "github.com/gohugoio/hugo/i18n"
+ "github.com/gohugoio/hugo/tpl"
+ "github.com/gohugoio/hugo/tpl/tplimpl"
+)
+
+// HugoSites represents the sites to build. Each site represents a language.
+type HugoSites struct {
+ Sites []*Site
+
+ runMode runmode
+
+ multilingual *Multilingual
+
+ *deps.Deps
+}
+
+// NewHugoSites creates a new collection of sites given the input sites, building
+// a language configuration based on those.
+func newHugoSites(cfg deps.DepsCfg, sites ...*Site) (*HugoSites, error) {
+
+ if cfg.Language != nil {
+ return nil, errors.New("Cannot provide Language in Cfg when sites are provided")
+ }
+
+ langConfig, err := newMultiLingualFromSites(cfg.Cfg, sites...)
+
+ if err != nil {
+ return nil, err
+ }
+
+ h := &HugoSites{
+ multilingual: langConfig,
+ Sites: sites}
+
+ for _, s := range sites {
+ s.owner = h
+ }
+
+ // TODO(bep)
+ cfg.Cfg.Set("multilingual", sites[0].multilingualEnabled())
+
+ if err := applyDepsIfNeeded(cfg, sites...); err != nil {
+ return nil, err
+ }
+
+ h.Deps = sites[0].Deps
+
+ return h, nil
+}
+
+func applyDepsIfNeeded(cfg deps.DepsCfg, sites ...*Site) error {
+ if cfg.TemplateProvider == nil {
+ cfg.TemplateProvider = tplimpl.DefaultTemplateProvider
+ }
+
+ if cfg.TranslationProvider == nil {
+ cfg.TranslationProvider = i18n.NewTranslationProvider()
+ }
+
+ var (
+ d *deps.Deps
+ err error
+ )
+
+ for _, s := range sites {
+ if s.Deps != nil {
+ continue
+ }
+
+ if d == nil {
+ cfg.Language = s.Language
+ cfg.WithTemplate = s.withSiteTemplates(cfg.WithTemplate)
+
+ var err error
+ d, err = deps.New(cfg)
+ if err != nil {
+ return err
+ }
+
+ d.OutputFormatsConfig = s.outputFormatsConfig
+ s.Deps = d
+
+ if err = d.LoadResources(); err != nil {
+ return err
+ }
+
+ } else {
+ d, err = d.ForLanguage(s.Language)
+ if err != nil {
+ return err
+ }
+ d.OutputFormatsConfig = s.outputFormatsConfig
+ s.Deps = d
+ }
+
+ }
+
+ return nil
+}
+
+// NewHugoSites creates HugoSites from the given config.
+func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
+ sites, err := createSitesFromConfig(cfg)
+ if err != nil {
+ return nil, err
+ }
+ return newHugoSites(cfg, sites...)
+}
+
+func (s *Site) withSiteTemplates(withTemplates ...func(templ tpl.TemplateHandler) error) func(templ tpl.TemplateHandler) error {
+ return func(templ tpl.TemplateHandler) error {
+ templ.LoadTemplates(s.PathSpec.GetLayoutDirPath(), "")
+ if s.PathSpec.ThemeSet() {
+ templ.LoadTemplates(s.PathSpec.GetThemeDir()+"/layouts", "theme")
+ }
+
+ for _, wt := range withTemplates {
+ if wt == nil {
+ continue
+ }
+ if err := wt(templ); err != nil {
+ return err
+ }
+ }
+
+ return nil
+ }
+}
+
+func createSitesFromConfig(cfg deps.DepsCfg) ([]*Site, error) {
+
+ var (
+ sites []*Site
+ )
+
+ multilingual := cfg.Cfg.GetStringMap("languages")
+
+ if len(multilingual) == 0 {
+ l := helpers.NewDefaultLanguage(cfg.Cfg)
+ cfg.Language = l
+ s, err := newSite(cfg)
+ if err != nil {
+ return nil, err
+ }
+ sites = append(sites, s)
+ }
+
+ if len(multilingual) > 0 {
+ var err error
+
+ languages, err := toSortedLanguages(cfg.Cfg, multilingual)
+
+ if err != nil {
+ return nil, fmt.Errorf("Failed to parse multilingual config: %s", err)
+ }
+
+ for _, lang := range languages {
+ var s *Site
+ var err error
+ cfg.Language = lang
+ s, err = newSite(cfg)
+
+ if err != nil {
+ return nil, err
+ }
+
+ sites = append(sites, s)
+ }
+ }
+
+ return sites, nil
+}
+
+// Reset resets the sites and template caches, making it ready for a full rebuild.
+func (h *HugoSites) reset() {
+ for i, s := range h.Sites {
+ h.Sites[i] = s.reset()
+ }
+}
+
+func (h *HugoSites) createSitesFromConfig() error {
+
+ depsCfg := deps.DepsCfg{Fs: h.Fs, Cfg: h.Cfg}
+ sites, err := createSitesFromConfig(depsCfg)
+
+ if err != nil {
+ return err
+ }
+
+ langConfig, err := newMultiLingualFromSites(depsCfg.Cfg, sites...)
+
+ if err != nil {
+ return err
+ }
+
+ h.Sites = sites
+
+ for _, s := range sites {
+ s.owner = h
+ }
+
+ if err := applyDepsIfNeeded(depsCfg, sites...); err != nil {
+ return err
+ }
+
+ h.Deps = sites[0].Deps
+
+ h.multilingual = langConfig
+
+ return nil
+}
+
+func (h *HugoSites) toSiteInfos() []*SiteInfo {
+ infos := make([]*SiteInfo, len(h.Sites))
+ for i, s := range h.Sites {
+ infos[i] = &s.Info
+ }
+ return infos
+}
+
+// BuildCfg holds build options used to, as an example, skip the render step.
+type BuildCfg struct {
+ // Whether we are in watch (server) mode
+ 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 indicate what changed (for rebuilds).
+ whatChanged *whatChanged
+}
+
+func (h *HugoSites) renderCrossSitesArtifacts() error {
+
+ if !h.multilingual.enabled() {
+ return nil
+ }
+
+ if h.Cfg.GetBool("disableSitemap") {
+ return nil
+ }
+
+ sitemapEnabled := false
+ for _, s := range h.Sites {
+ if s.isEnabled(kindSitemap) {
+ sitemapEnabled = true
+ break
+ }
+ }
+
+ if !sitemapEnabled {
+ return nil
+ }
+
+ // TODO(bep) DRY
+ sitemapDefault := parseSitemap(h.Cfg.GetStringMap("sitemap"))
+
+ s := h.Sites[0]
+
+ smLayouts := []string{"sitemapindex.xml", "_default/sitemapindex.xml", "_internal/_default/sitemapindex.xml"}
+
+ return s.renderAndWriteXML("sitemapindex",
+ sitemapDefault.Filename, h.toSiteInfos(), s.appendThemeTemplates(smLayouts)...)
+}
+
+func (h *HugoSites) assignMissingTranslations() error {
+ // This looks heavy, but it should be a small number of nodes by now.
+ allPages := h.findAllPagesByKindNotIn(KindPage)
+ for _, nodeType := range []string{KindHome, KindSection, KindTaxonomy, KindTaxonomyTerm} {
+ nodes := h.findPagesByKindIn(nodeType, allPages)
+
+ // Assign translations
+ for _, t1 := range nodes {
+ for _, t2 := range nodes {
+ if t1.isNewTranslation(t2) {
+ t1.translations = append(t1.translations, t2)
+ }
+ }
+ }
+ }
+
+ // Now we can sort the translations.
+ for _, p := range allPages {
+ if len(p.translations) > 0 {
+ pageBy(languagePageSort).Sort(p.translations)
+ }
+ }
+ return nil
+
+}
+
+// createMissingPages creates home page, taxonomies etc. that isnt't created as an
+// effect of having a content file.
+func (h *HugoSites) createMissingPages() error {
+ var newPages Pages
+
+ for _, s := range h.Sites {
+ if s.isEnabled(KindHome) {
+ // home pages
+ home := s.findPagesByKind(KindHome)
+ if len(home) > 1 {
+ panic("Too many homes")
+ }
+ if len(home) == 0 {
+ n := s.newHomePage()
+ s.Pages = append(s.Pages, n)
+ newPages = append(newPages, n)
+ }
+ }
+
+ // Will create content-less root sections.
+ newSections := s.assembleSections()
+ s.Pages = append(s.Pages, newSections...)
+ newPages = append(newPages, newSections...)
+
+ // taxonomy list and terms pages
+ taxonomies := s.Language.GetStringMapString("taxonomies")
+ if len(taxonomies) > 0 {
+ taxonomyPages := s.findPagesByKind(KindTaxonomy)
+ taxonomyTermsPages := s.findPagesByKind(KindTaxonomyTerm)
+ for _, plural := range taxonomies {
+ if s.isEnabled(KindTaxonomyTerm) {
+ foundTaxonomyTermsPage := false
+ for _, p := range taxonomyTermsPages {
+ if p.sections[0] == plural {
+ foundTaxonomyTermsPage = true
+ break
+ }
+ }
+
+ if !foundTaxonomyTermsPage {
+ foundTaxonomyTermsPage = true
+ n := s.newTaxonomyTermsPage(plural)
+ s.Pages = append(s.Pages, n)
+ newPages = append(newPages, n)
+ }
+ }
+
+ if s.isEnabled(KindTaxonomy) {
+ for key := range s.Taxonomies[plural] {
+ foundTaxonomyPage := false
+ origKey := key
+
+ if s.Info.preserveTaxonomyNames {
+ key = s.PathSpec.MakePathSanitized(key)
+ }
+ for _, p := range taxonomyPages {
+ if p.sections[0] == plural && p.sections[1] == key {
+ foundTaxonomyPage = true
+ break
+ }
+ }
+
+ if !foundTaxonomyPage {
+ n := s.newTaxonomyPage(plural, origKey)
+ s.Pages = append(s.Pages, n)
+ newPages = append(newPages, n)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if len(newPages) > 0 {
+ // This resorting is unfortunate, but it also needs to be sorted
+ // when sections are created.
+ first := h.Sites[0]
+
+ first.AllPages = append(first.AllPages, newPages...)
+
+ first.AllPages.Sort()
+
+ for _, s := range h.Sites {
+ s.Pages.Sort()
+ }
+
+ for i := 1; i < len(h.Sites); i++ {
+ h.Sites[i].AllPages = first.AllPages
+ }
+ }
+
+ return nil
+}
+
+func (s *Site) assignSiteByLanguage(p *Page) {
+
+ pageLang := p.Lang()
+
+ if pageLang == "" {
+ panic("Page language missing: " + p.Title)
+ }
+
+ for _, site := range s.owner.Sites {
+ if strings.HasPrefix(site.Language.Lang, pageLang) {
+ p.s = site
+ p.Site = &site.Info
+ return
+ }
+ }
+
+}
+
+func (h *HugoSites) setupTranslations() {
+
+ master := h.Sites[0]
+
+ for _, p := range master.rawAllPages {
+ if p.Lang() == "" {
+ panic("Page language missing: " + p.Title)
+ }
+
+ if p.Kind == kindUnknown {
+ p.Kind = p.s.kindFromSections(p.sections)
+ }
+
+ if !p.s.isEnabled(p.Kind) {
+ continue
+ }
+
+ shouldBuild := p.shouldBuild()
+
+ for i, site := range h.Sites {
+ // The site is assigned by language when read.
+ if site == p.s {
+ site.updateBuildStats(p)
+ if shouldBuild {
+ site.Pages = append(site.Pages, p)
+ }
+ }
+
+ if !shouldBuild {
+ continue
+ }
+
+ if i == 0 {
+ site.AllPages = append(site.AllPages, p)
+ }
+ }
+
+ }
+
+ // Pull over the collections from the master site
+ for i := 1; i < len(h.Sites); i++ {
+ h.Sites[i].AllPages = h.Sites[0].AllPages
+ h.Sites[i].Data = h.Sites[0].Data
+ }
+
+ if len(h.Sites) > 1 {
+ pages := h.Sites[0].AllPages
+ allTranslations := pagesToTranslationsMap(pages)
+ assignTranslationsToPages(allTranslations, pages)
+ }
+}
+
+func (s *Site) preparePagesForRender(cfg *BuildCfg) {
+
+ pageChan := make(chan *Page)
+ wg := &sync.WaitGroup{}
+ numWorkers := getGoMaxProcs() * 4
+
+ for i := 0; i < numWorkers; i++ {
+ wg.Add(1)
+ go func(pages <-chan *Page, wg *sync.WaitGroup) {
+ defer wg.Done()
+ for p := range pages {
+ if !p.shouldRenderTo(s.rc.Format) {
+ // No need to prepare
+ continue
+ }
+ var shortcodeUpdate bool
+ if p.shortcodeState != nil {
+ shortcodeUpdate = p.shortcodeState.updateDelta()
+ }
+
+ if !shortcodeUpdate && !cfg.whatChanged.other && p.rendered {
+ // No need to process it again.
+ continue
+ }
+
+ // If we got this far it means that this is either a new Page pointer
+ // or a template or similar has changed so wee need to do a rerendering
+ // of the shortcodes etc.
+
+ // Mark it as rendered
+ p.rendered = true
+
+ // If in watch mode or if we have multiple output formats,
+ // we need to keep the original so we can
+ // potentially repeat this process on rebuild.
+ needsACopy := cfg.Watching || len(p.outputFormats) > 1
+ var workContentCopy []byte
+ if needsACopy {
+ workContentCopy = make([]byte, len(p.workContent))
+ copy(workContentCopy, p.workContent)
+ } else {
+ // Just reuse the same slice.
+ workContentCopy = p.workContent
+ }
+
+ if p.Markup == "markdown" {
+ tmpContent, tmpTableOfContents := helpers.ExtractTOC(workContentCopy)
+ p.TableOfContents = helpers.BytesToHTML(tmpTableOfContents)
+ workContentCopy = tmpContent
+ }
+
+ var err error
+ if workContentCopy, err = handleShortcodes(p, workContentCopy); err != nil {
+ s.Log.ERROR.Printf("Failed to handle shortcodes for page %s: %s", p.BaseFileName(), err)
+ }
+
+ if p.Markup != "html" {
+
+ // Now we know enough to create a summary of the page and count some words
+ summaryContent, err := p.setUserDefinedSummaryIfProvided(workContentCopy)
+
+ if err != nil {
+ s.Log.ERROR.Printf("Failed to set user defined summary for page %q: %s", p.Path(), err)
+ } else if summaryContent != nil {
+ workContentCopy = summaryContent.content
+ }
+
+ p.Content = helpers.BytesToHTML(workContentCopy)
+
+ if summaryContent == nil {
+ if err := p.setAutoSummary(); err != nil {
+ s.Log.ERROR.Printf("Failed to set user auto summary for page %q: %s", p.pathOrTitle(), err)
+ }
+ }
+
+ } else {
+ p.Content = helpers.BytesToHTML(workContentCopy)
+ }
+
+ //analyze for raw stats
+ p.analyzePage()
+
+ }
+ }(pageChan, wg)
+ }
+
+ for _, p := range s.Pages {
+ pageChan <- p
+ }
+
+ close(pageChan)
+
+ wg.Wait()
+
+}
+
+// Pages returns all pages for all sites.
+func (h *HugoSites) Pages() Pages {
+ return h.Sites[0].AllPages
+}
+
+func handleShortcodes(p *Page, rawContentCopy []byte) ([]byte, error) {
+ if p.shortcodeState != nil && len(p.shortcodeState.contentShortcodes) > 0 {
+ p.s.Log.DEBUG.Printf("Replace %d shortcodes in %q", len(p.shortcodeState.contentShortcodes), p.BaseFileName())
+ err := p.shortcodeState.executeShortcodesForDelta(p)
+
+ if err != nil {
+ return rawContentCopy, err
+ }
+
+ rawContentCopy, err = replaceShortcodeTokens(rawContentCopy, shortcodePlaceholderPrefix, p.shortcodeState.renderedShortcodes)
+
+ if err != nil {
+ p.s.Log.FATAL.Printf("Failed to replace shortcode tokens in %s:\n%s", p.BaseFileName(), err.Error())
+ }
+ }
+
+ return rawContentCopy, nil
+}
+
+func (s *Site) updateBuildStats(page *Page) {
+ if page.IsDraft() {
+ s.draftCount++
+ }
+
+ if page.IsFuture() {
+ s.futureCount++
+ }
+
+ if page.IsExpired() {
+ s.expiredCount++
+ }
+}
+
+func (h *HugoSites) findPagesByKindNotIn(kind string, inPages Pages) Pages {
+ return h.Sites[0].findPagesByKindNotIn(kind, inPages)
+}
+
+func (h *HugoSites) findPagesByKindIn(kind string, inPages Pages) Pages {
+ return h.Sites[0].findPagesByKindIn(kind, inPages)
+}
+
+func (h *HugoSites) findAllPagesByKind(kind string) Pages {
+ return h.findPagesByKindIn(kind, h.Sites[0].AllPages)
+}
+
+func (h *HugoSites) findAllPagesByKindNotIn(kind string) Pages {
+ return h.findPagesByKindNotIn(kind, h.Sites[0].AllPages)
+}