package modules import ( "fmt" "sort" "time" "gsg/internal/core" ) type RepoSummary struct { core.RepoInfo LastCommit time.Time } func init() { core.Register("repos", reposMod) } // repos: every visible repository, newest activity first. // Site-scoped -- does not touch c.Repo, so it is safe on the site Ctx. func reposMod(c *core.Ctx, args ...string) (any, error) { out := make([]RepoSummary, 0, len(c.Site.Repos)) for _, h := range c.Site.Repos { if h.Info.Hidden { continue } s := RepoSummary{RepoInfo: h.Info} if log, err := h.Repo.Log(h.Repo.HeadRef(), 1); err == nil && len(log) > 0 { s.LastCommit = log[0].When } out = append(out, s) } sort.Slice(out, func(i, j int) bool { return out[i].LastCommit.After(out[j].LastCommit) }) return out, nil } // Section is a named group of repositories, from `gsg.section` in each // repo's config (or `gitweb.category`). type Section struct { Name string // "" means the repo set has no sections at all Repos []RepoSummary } func init() { core.Register("sections", sectionsMod) } // sections: repos grouped by gsg.section, sections alphabetical, unsectioned // last. Built on top of `repos` rather than re-walking the repo set, so the // index page pays for the log lookups exactly once. // // Returns a single unnamed Section when nothing sets gsg.section, which lets // the template render a flat list without branching on the config. func sectionsMod(c *core.Ctx, args ...string) (any, error) { v, err := c.Mod("repos") if err != nil { return nil, err } list, ok := v.([]RepoSummary) if !ok { return nil, fmt.Errorf("sections: repos returned %T", v) } any := false groups := map[string][]RepoSummary{} for _, r := range list { if r.Section != "" { any = true } groups[r.Section] = append(groups[r.Section], r) } if !any { return []Section{{Name: "", Repos: list}}, nil } names := make([]string, 0, len(groups)) for n := range groups { if n != "" { names = append(names, n) } } sort.Strings(names) out := make([]Section, 0, len(groups)) for _, n := range names { out = append(out, Section{Name: n, Repos: groups[n]}) } if rest, ok := groups[""]; ok { out = append(out, Section{Name: unsectioned, Repos: rest}) } return out, nil } // unsectioned labels the leftover group. Named rather than inlined so it is // obvious this string is user-facing. const unsectioned = "Other"