summaryrefslogtreecommitdiffstats
path: root/hugolib/site.go
diff options
context:
space:
mode:
Diffstat (limited to 'hugolib/site.go')
-rw-r--r--hugolib/site.go257
1 files changed, 196 insertions, 61 deletions
diff --git a/hugolib/site.go b/hugolib/site.go
index edcccfe28..8403a6af0 100644
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -58,6 +58,15 @@ var (
distinctFeedbackLogger = helpers.NewDistinctFeedbackLogger()
)
+type nodeCache struct {
+ m map[string]*Node
+ sync.RWMutex
+}
+
+func (c *nodeCache) reset() {
+ c.m = make(map[string]*Node)
+}
+
// Site contains all the information relevant for constructing a static
// site. The basic flow of information is as follows:
//
@@ -76,7 +85,12 @@ var (
//
// 5. The entire collection of files is written to disk.
type Site struct {
- owner *HugoSites
+ owner *HugoSites
+
+ // Used internally to discover duplicates.
+ nodeCache *nodeCache
+ nodeCacheInit sync.Once
+
Pages Pages
AllPages Pages
rawAllPages Pages
@@ -167,6 +181,7 @@ type SiteInfo struct {
paginationPageCount uint64
Data *map[string]interface{}
+ owner *HugoSites
multilingual *Multilingual
Language *helpers.Language
LanguagePrefix string
@@ -773,13 +788,15 @@ func (s *Site) render() (err error) {
return
}
s.timerStep("render and write aliases")
- if err = s.renderTaxonomiesLists(); err != nil {
+ if err = s.renderTaxonomiesLists(false); err != nil {
return
}
s.timerStep("render and write taxonomies")
- s.renderListsOfTaxonomyTerms()
+ if err = s.renderListsOfTaxonomyTerms(false); err != nil {
+ return
+ }
s.timerStep("render & write taxonomy lists")
- if err = s.renderSectionLists(); err != nil {
+ if err = s.renderSectionLists(false); err != nil {
return
}
s.timerStep("render and write lists")
@@ -787,7 +804,7 @@ func (s *Site) render() (err error) {
return
}
s.timerStep("render and write pages")
- if err = s.renderHomePage(); err != nil {
+ if err = s.renderHomePage(false); err != nil {
return
}
s.timerStep("render and write homepage")
@@ -904,6 +921,7 @@ func (s *Site) initializeSiteInfo() {
Params: params,
Permalinks: permalinks,
Data: &s.Data,
+ owner: s.owner,
}
}
@@ -1372,6 +1390,8 @@ func (s *Site) assembleTaxonomies() {
// Prepare site for a new full build.
func (s *Site) resetBuildState() {
+ s.nodeCache.reset()
+
s.Pages = make(Pages, 0)
s.AllPages = make(Pages, 0)
@@ -1557,7 +1577,7 @@ type taxRenderInfo struct {
// renderTaxonomiesLists renders the listing pages based on the meta data
// each unique term within a taxonomy will have a page created
-func (s *Site) renderTaxonomiesLists() error {
+func (s *Site) renderTaxonomiesLists(prepare bool) error {
wg := &sync.WaitGroup{}
taxes := make(chan taxRenderInfo)
@@ -1567,7 +1587,7 @@ func (s *Site) renderTaxonomiesLists() error {
for i := 0; i < procs*4; i++ {
wg.Add(1)
- go taxonomyRenderer(s, taxes, results, wg)
+ go taxonomyRenderer(prepare, s, taxes, results, wg)
}
errs := make(chan error)
@@ -1593,9 +1613,19 @@ func (s *Site) renderTaxonomiesLists() error {
return nil
}
-func (s *Site) newTaxonomyNode(t taxRenderInfo) (*Node, string) {
+func (s *Site) newTaxonomyNode(prepare bool, t taxRenderInfo, counter int) (*Node, string) {
key := t.key
- n := s.newNode()
+ n := s.nodeLookup(fmt.Sprintf("tax-%s-%s", t.plural, key), counter, prepare)
+
+ if s.Info.preserveTaxonomyNames {
+ key = helpers.MakePathSanitized(key)
+ }
+ base := t.plural + "/" + key
+
+ if !prepare {
+ return n, base
+ }
+
if s.Info.preserveTaxonomyNames {
key = helpers.MakePathSanitized(key)
// keep as is in the title
@@ -1603,7 +1633,6 @@ func (s *Site) newTaxonomyNode(t taxRenderInfo) (*Node, string) {
} else {
n.Title = strings.Replace(strings.Title(t.key), "-", " ", -1)
}
- base := t.plural + "/" + key
s.setURLs(n, base)
if len(t.pages) > 0 {
n.Date = t.pages[0].Page.Date
@@ -1616,19 +1645,30 @@ func (s *Site) newTaxonomyNode(t taxRenderInfo) (*Node, string) {
return n, base
}
-func taxonomyRenderer(s *Site, taxes <-chan taxRenderInfo, results chan<- error, wg *sync.WaitGroup) {
+func taxonomyRenderer(prepare bool, s *Site, taxes <-chan taxRenderInfo, results chan<- error, wg *sync.WaitGroup) {
defer wg.Done()
var n *Node
for t := range taxes {
- var base string
- layouts := s.appendThemeTemplates(
- []string{"taxonomy/" + t.singular + ".html", "indexes/" + t.singular + ".html", "_default/taxonomy.html", "_default/list.html"})
+ var (
+ base string
+ baseWithLanguagePrefix string
+ paginatePath string
+ layouts []string
+ )
- n, base = s.newTaxonomyNode(t)
- baseWithLanguagePrefix := n.addLangPathPrefix(base)
+ n, base = s.newTaxonomyNode(prepare, t, 0)
+
+ if prepare {
+ continue
+ }
+
+ baseWithLanguagePrefix = n.addLangPathPrefix(base)
+
+ layouts = s.appendThemeTemplates(
+ []string{"taxonomy/" + t.singular + ".html", "indexes/" + t.singular + ".html", "_default/taxonomy.html", "_default/list.html"})
dest := base
if viper.GetBool("UglyURLs") {
@@ -1644,7 +1684,7 @@ func taxonomyRenderer(s *Site, taxes <-chan taxRenderInfo, results chan<- error,
if n.paginator != nil {
- paginatePath := viper.GetString("paginatePath")
+ paginatePath = viper.GetString("paginatePath")
// write alias for page 1
s.writeDestAlias(helpers.PaginateAliasPath(baseWithLanguagePrefix, 1), n.Permalink())
@@ -1657,13 +1697,15 @@ func taxonomyRenderer(s *Site, taxes <-chan taxRenderInfo, results chan<- error,
continue
}
- taxonomyPagerNode, _ := s.newTaxonomyNode(t)
+ taxonomyPagerNode, _ := s.newTaxonomyNode(true, t, i)
+
taxonomyPagerNode.paginator = pager
if pager.TotalPages() > 0 {
first, _ := pager.page(0)
taxonomyPagerNode.Date = first.Date
taxonomyPagerNode.Lastmod = first.Lastmod
}
+
pageNumber := i + 1
htmlBase := fmt.Sprintf("/%s/%s/%d", baseWithLanguagePrefix, paginatePath, pageNumber)
if err := s.renderAndWritePage(fmt.Sprintf("taxonomy %s", t.singular), htmlBase, taxonomyPagerNode, layouts...); err != nil {
@@ -1673,13 +1715,21 @@ func taxonomyRenderer(s *Site, taxes <-chan taxRenderInfo, results chan<- error,
}
}
+ if prepare {
+ continue
+ }
+
if !viper.GetBool("DisableRSS") {
// XML Feed
+ c := *n
+ rssNode := &c
+ rssNode.nodeID = ""
rssuri := viper.GetString("RSSUri")
- s.setURLs(n, base+"/"+rssuri)
+ s.setURLs(rssNode, base+"/"+rssuri)
+
rssLayouts := []string{"taxonomy/" + t.singular + ".rss.xml", "_default/rss.xml", "rss.xml", "_internal/_default/rss.xml"}
- if err := s.renderAndWriteXML("taxonomy "+t.singular+" rss", baseWithLanguagePrefix+"/"+rssuri, n, s.appendThemeTemplates(rssLayouts)...); err != nil {
+ if err := s.renderAndWriteXML("taxonomy "+t.singular+" rss", baseWithLanguagePrefix+"/"+rssuri, rssNode, s.appendThemeTemplates(rssLayouts)...); err != nil {
results <- err
continue
}
@@ -1688,18 +1738,24 @@ func taxonomyRenderer(s *Site, taxes <-chan taxRenderInfo, results chan<- error,
}
// renderListsOfTaxonomyTerms renders a page per taxonomy that lists the terms for that taxonomy
-func (s *Site) renderListsOfTaxonomyTerms() (err error) {
+func (s *Site) renderListsOfTaxonomyTerms(prepare bool) (err error) {
taxonomies := s.Language.GetStringMapString("Taxonomies")
for singular, plural := range taxonomies {
- n := s.newNode()
- n.Title = strings.Title(plural)
- s.setURLs(n, plural)
- n.Data["Singular"] = singular
- n.Data["Plural"] = plural
- n.Data["Terms"] = s.Taxonomies[plural]
- // keep the following just for legacy reasons
- n.Data["OrderedIndex"] = n.Data["Terms"]
- n.Data["Index"] = n.Data["Terms"]
+ n := s.nodeLookup(fmt.Sprintf("taxlist-%s", plural), 0, prepare)
+
+ if prepare {
+ n.Title = strings.Title(plural)
+ s.setURLs(n, plural)
+ n.Data["Singular"] = singular
+ n.Data["Plural"] = plural
+ n.Data["Terms"] = s.Taxonomies[plural]
+ // keep the following just for legacy reasons
+ n.Data["OrderedIndex"] = n.Data["Terms"]
+ n.Data["Index"] = n.Data["Terms"]
+
+ continue
+ }
+
layouts := []string{"taxonomy/" + singular + ".terms.html", "_default/terms.html", "indexes/indexes.html"}
layouts = s.appendThemeTemplates(layouts)
if s.layoutExists(layouts...) {
@@ -1712,8 +1768,13 @@ func (s *Site) renderListsOfTaxonomyTerms() (err error) {
return
}
-func (s *Site) newSectionListNode(sectionName, section string, data WeightedPages) *Node {
- n := s.newNode()
+func (s *Site) newSectionListNode(prepare bool, sectionName, section string, data WeightedPages, counter int) *Node {
+ n := s.nodeLookup(fmt.Sprintf("sect-%s", sectionName), counter, prepare)
+
+ if !prepare {
+ return n
+ }
+
sectionName = helpers.FirstUpper(sectionName)
if viper.GetBool("PluralizeListTitles") {
n.Title = inflect.Pluralize(sectionName)
@@ -1729,7 +1790,7 @@ func (s *Site) newSectionListNode(sectionName, section string, data WeightedPage
}
// renderSectionLists renders a page for each section
-func (s *Site) renderSectionLists() error {
+func (s *Site) renderSectionLists(prepare bool) error {
for section, data := range s.Sections {
// section keys can be lower case (depending on site.pathifyTaxonomyKeys)
// extract the original casing from the first page to get sensible titles.
@@ -1737,6 +1798,13 @@ func (s *Site) renderSectionLists() error {
if !s.Info.preserveTaxonomyNames && len(data) > 0 {
sectionName = data[0].Page.Section()
}
+
+ n := s.newSectionListNode(prepare, sectionName, section, data, 0)
+
+ if prepare {
+ continue
+ }
+
layouts := s.appendThemeTemplates(
[]string{"section/" + section + ".html", "_default/section.html", "_default/list.html", "indexes/" + section + ".html", "_default/indexes.html"})
@@ -1744,7 +1812,6 @@ func (s *Site) renderSectionLists() error {
section = helpers.MakePathSanitized(section)
}
- n := s.newSectionListNode(sectionName, section, data)
base := n.addLangPathPrefix(section)
if err := s.renderAndWritePage(fmt.Sprintf("section %s", section), base, n, s.appendThemeTemplates(layouts)...); err != nil {
@@ -1766,7 +1833,7 @@ func (s *Site) renderSectionLists() error {
continue
}
- sectionPagerNode := s.newSectionListNode(sectionName, section, data)
+ sectionPagerNode := s.newSectionListNode(true, sectionName, section, data, i)
sectionPagerNode.paginator = pager
if pager.TotalPages() > 0 {
first, _ := pager.page(0)
@@ -1781,6 +1848,10 @@ func (s *Site) renderSectionLists() error {
}
}
+ if prepare {
+ return nil
+ }
+
if !viper.GetBool("DisableRSS") && section != "" {
// XML Feed
rssuri := viper.GetString("RSSUri")
@@ -1794,9 +1865,12 @@ func (s *Site) renderSectionLists() error {
return nil
}
-func (s *Site) renderHomePage() error {
+func (s *Site) renderHomePage(prepare bool) error {
- n := s.newHomeNode()
+ n := s.newHomeNode(prepare, 0)
+ if prepare {
+ return nil
+ }
layouts := s.appendThemeTemplates([]string{"index.html", "_default/list.html"})
base := n.addLangFilepathPrefix("")
@@ -1807,9 +1881,11 @@ func (s *Site) renderHomePage() error {
if n.paginator != nil {
paginatePath := viper.GetString("paginatePath")
- // write alias for page 1
- // TODO(bep) ml all of these n.addLang ... fix.
- s.writeDestAlias(n.addLangPathPrefix(helpers.PaginateAliasPath("", 1)), n.Permalink())
+ {
+ // write alias for page 1
+ // TODO(bep) ml all of these n.addLang ... fix.
+ s.writeDestAlias(n.addLangPathPrefix(helpers.PaginateAliasPath("", 1)), n.Permalink())
+ }
pagers := n.paginator.Pagers()
@@ -1819,39 +1895,44 @@ func (s *Site) renderHomePage() error {
continue
}
- homePagerNode := s.newHomeNode()
+ homePagerNode := s.newHomeNode(true, i)
+
homePagerNode.paginator = pager
if pager.TotalPages() > 0 {
first, _ := pager.page(0)
homePagerNode.Date = first.Date
homePagerNode.Lastmod = first.Lastmod
}
+
pageNumber := i + 1
htmlBase := fmt.Sprintf("/%s/%d", paginatePath, pageNumber)
htmlBase = n.addLangPathPrefix(htmlBase)
- if err := s.renderAndWritePage(fmt.Sprintf("homepage"), filepath.FromSlash(htmlBase), homePagerNode, layouts...); err != nil {
+ if err := s.renderAndWritePage(fmt.Sprintf("homepage"),
+ filepath.FromSlash(htmlBase), homePagerNode, layouts...); err != nil {
return err
}
+
}
}
if !viper.GetBool("DisableRSS") {
// XML Feed
- s.setURLs(n, viper.GetString("RSSUri"))
- n.Title = ""
+ rssNode := s.newNode("rss-home")
+ s.setURLs(rssNode, viper.GetString("RSSUri"))
+ rssNode.Title = ""
high := 50
if len(s.Pages) < high {
high = len(s.Pages)
}
- n.Data["Pages"] = s.Pages[:high]
+ rssNode.Data["Pages"] = s.Pages[:high]
if len(s.Pages) > 0 {
- n.Date = s.Pages[0].Date
- n.Lastmod = s.Pages[0].Lastmod
+ rssNode.Date = s.Pages[0].Date
+ rssNode.Lastmod = s.Pages[0].Lastmod
}
rssLayouts := []string{"rss.xml", "_default/rss.xml", "_internal/_default/rss.xml"}
- if err := s.renderAndWriteXML("homepage rss", n.addLangPathPrefix(viper.GetString("RSSUri")), n, s.appendThemeTemplates(rssLayouts)...); err != nil {
+ if err := s.renderAndWriteXML("homepage rss", rssNode.addLangPathPrefix(viper.GetString("RSSUri")), rssNode, s.appendThemeTemplates(rssLayouts)...); err != nil {
return err
}
}
@@ -1860,23 +1941,20 @@ func (s *Site) renderHomePage() error {
return nil
}
- // TODO(bep) reusing the Home Node smells trouble
- n.URLPath.URL = helpers.URLize("404.html")
- n.IsHome = false
- n.Title = "404 Page not found"
- n.URLPath.Permalink = permalink("404.html")
- n.scratch = newScratch()
+ node404 := s.newNode("404")
+ node404.Title = "404 Page not found"
+ s.setURLs(node404, "404.html")
nfLayouts := []string{"404.html"}
- if nfErr := s.renderAndWritePage("404 page", "404.html", n, s.appendThemeTemplates(nfLayouts)...); nfErr != nil {
+ if nfErr := s.renderAndWritePage("404 page", "404.html", node404, s.appendThemeTemplates(nfLayouts)...); nfErr != nil {
return nfErr
}
return nil
}
-func (s *Site) newHomeNode() *Node {
- n := s.newNode()
+func (s *Site) newHomeNode(prepare bool, counter int) *Node {
+ n := s.nodeLookup("home", counter, prepare)
n.Title = n.Site.Title
n.IsHome = true
s.setURLs(n, "/")
@@ -1895,7 +1973,7 @@ func (s *Site) renderSitemap() error {
sitemapDefault := parseSitemap(viper.GetStringMap("Sitemap"))
- n := s.newNode()
+ n := s.newNode("sitemap")
// Prepend homepage to the list of pages
pages := make(Pages, 0)
@@ -1942,7 +2020,7 @@ func (s *Site) renderRobotsTXT() error {
return nil
}
- n := s.newNode()
+ n := s.newNode("robots")
n.Data["Pages"] = s.Pages
rLayouts := []string{"robots.txt", "_default/robots.txt", "_internal/_default/robots.txt"}
@@ -1988,13 +2066,70 @@ func permalink(plink string) string {
func permalinkStr(plink string) string {
return helpers.MakePermalink(viper.GetString("BaseURL"), helpers.URLizeAndPrep(plink)).String()
}
+func (s *Site) newNode(nodeID string) *Node {
+ return s.nodeLookup(nodeID, 0, true)
+}
-func (s *Site) newNode() *Node {
- return &Node{
+func (s *Site) getNode(nodeID string) *Node {
+ return s.getOrAddNode(nodeID, false)
+}
+
+func (s *Site) getOrAddNode(nodeID string, add bool) *Node {
+ s.nodeCacheInit.Do(func() {
+ s.nodeCache = &nodeCache{m: make(map[string]*Node)}
+ })
+
+ s.nodeCache.RLock()
+ if n, ok := s.nodeCache.m[nodeID]; ok {
+ s.nodeCache.RUnlock()
+ if !add {
+ return n
+ }
+ panic(fmt.Sprintf("Node with ID %q in use", nodeID))
+ }
+
+ s.nodeCache.RUnlock()
+ s.nodeCache.Lock()
+
+ if !add {
+ // this is a test type error, print the keys
+ for k, _ := range s.nodeCache.m {
+ fmt.Println("Node:", k)
+ }
+ return nil
+ }
+
+ // Double check
+ if _, ok := s.nodeCache.m[nodeID]; ok {
+ s.nodeCache.Unlock()
+ panic(fmt.Sprintf("Node with ID %q in use", nodeID))
+ }
+
+ n := &Node{
+ nodeID: nodeID,
Data: make(map[string]interface{}),
Site: &s.Info,
language: s.Language,
}
+
+ s.nodeCache.m[nodeID] = n
+ s.nodeCache.Unlock()
+ return n
+}
+
+func (s *Site) nodeLookup(nodeIDPrefix string, counter int, add bool) *Node {
+
+ nodeID := fmt.Sprintf("%s-%d", nodeIDPrefix, counter)
+
+ n := s.getOrAddNode(nodeID, add)
+
+ // Paginator nodes (counter > 0) gets created during rendering and cannot take part in any
+ // global translations mapping
+ if add && s.owner != nil && counter == 0 {
+ s.owner.addNode(nodeID, n)
+ }
+
+ return n
}
func (s *Site) layoutExists(layouts ...string) bool {