Download
89 lines 2.5 KiBGoModified 2026-10-27 raw
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
package render

import (
	"encoding/xml"
	"maps"
	"os"
	"slices"
	"time"
)

// ---- sitemaps ----

// The site's sitemap is an index of one sitemap per repository listed on
// the index page. A repository's sitemap names every page of its default
// branch; other branches ask not to be indexed, so they are left out.

const sitemapNS = "http://www.sitemaps.org/schemas/sitemap/0.9"

type sitemapIndex struct {
	XMLName  xml.Name     `xml:"sitemapindex"`
	NS       string       `xml:"xmlns,attr"`
	Sitemaps []sitemapLoc `xml:"sitemap"`
}

type urlSet struct {
	XMLName xml.Name     `xml:"urlset"`
	NS      string       `xml:"xmlns,attr"`
	URLs    []sitemapLoc `xml:"url"`
}

type sitemapLoc struct {
	Loc     string `xml:"loc"`
	LastMod string `xml:"lastmod,omitempty"`
}

// sitemapMax is the most URLs one sitemap may hold.
const sitemapMax = 50000

func lastMod(t time.Time) string { return t.UTC().Format(time.RFC3339) }

func writeXML(path string, v any) error {
	b, err := xml.MarshalIndent(v, "", "  ")
	if err != nil {
		return err
	}
	return writePair(path, append([]byte(xml.Header), append(b, '\n')...), false)
}

// writeSitemap lists every page of the view, each dated by the last
// commit that changed what it shows. Only the default branch has one.
func (c *repoCtx) writeSitemap() error {
	var urls []sitemapLoc
	add := func(p string) {
		u := sitemapLoc{Loc: c.s.absURL(c.s.CodeURL(c.key, p))}
		if t, ok := c.mtime[p]; ok {
			u.LastMod = lastMod(time.Unix(t, 0))
		}
		urls = append(urls, u)
	}
	for _, d := range slices.Sorted(maps.Keys(c.tree.dirs)) {
		add(d)
	}
	for _, p := range c.tree.sortedPaths() {
		if e := c.tree.byPath[p]; !e.IsSymlink() && !e.IsSubmodule() {
			add(p)
		}
	}
	if len(urls) > sitemapMax {
		c.logf("sitemap: %d pages; listing the first %d", len(urls), sitemapMax)
		urls = urls[:sitemapMax]
	}
	return writeXML(c.s.Lay.Sitemap(c.key), urlSet{NS: sitemapNS, URLs: urls})
}

// writeSiteSitemap indexes the sitemaps of the repositories on the index
// page, dated by each one's latest commit. A repository rendered before
// sitemaps existed has none until its next render, and is left out.
func (s *Site) writeSiteSitemap(view indexView) error {
	idx := sitemapIndex{NS: sitemapNS}
	for _, sec := range view.Sections {
		for _, r := range sec.Repos {
			if _, err := os.Stat(s.Lay.Sitemap(r.Name)); err != nil {
				continue
			}
			idx.Sitemaps = append(idx.Sitemaps, sitemapLoc{Loc: s.absURL(SitemapURL(r.Name)), LastMod: r.Updated})
		}
	}
	return writeXML(s.Lay.SiteSitemap(), idx)
}