Migration This is my new public git source viewer. Source coming soon. Please contact with concerns/bugs. Contact me

internal/modules/repos.go

3a50036d96eb14462e96598fd169d1c79264ac4b · 2.4 KB · 93 lines raw

1 package modules
2
3 import (
4 "fmt"
5 "sort"
6 "time"
7
8 "gsg/internal/core"
9 )
10
11 type RepoSummary struct {
12 core.RepoInfo
13 LastCommit time.Time
14 }
15
16 func init() { core.Register("repos", reposMod) }
17
18 // repos: every visible repository, newest activity first.
19 // Site-scoped -- does not touch c.Repo, so it is safe on the site Ctx.
20 func reposMod(c *core.Ctx, args ...string) (any, error) {
21 out := make([]RepoSummary, 0, len(c.Site.Repos))
22 for _, h := range c.Site.Repos {
23 if h.Info.Hidden {
24 continue
25 }
26 s := RepoSummary{RepoInfo: h.Info}
27 if log, err := h.Repo.Log(h.Repo.HeadRef(), 1); err == nil && len(log) > 0 {
28 s.LastCommit = log[0].When
29 }
30 out = append(out, s)
31 }
32 sort.Slice(out, func(i, j int) bool { return out[i].LastCommit.After(out[j].LastCommit) })
33 return out, nil
34 }
35
36 // Section is a named group of repositories, from `gsg.section` in each
37 // repo's config (or `gitweb.category`).
38 type Section struct {
39 Name string // "" means the repo set has no sections at all
40 Repos []RepoSummary
41 }
42
43 func init() { core.Register("sections", sectionsMod) }
44
45 // sections: repos grouped by gsg.section, sections alphabetical, unsectioned
46 // last. Built on top of `repos` rather than re-walking the repo set, so the
47 // index page pays for the log lookups exactly once.
48 //
49 // Returns a single unnamed Section when nothing sets gsg.section, which lets
50 // the template render a flat list without branching on the config.
51 func sectionsMod(c *core.Ctx, args ...string) (any, error) {
52 v, err := c.Mod("repos")
53 if err != nil {
54 return nil, err
55 }
56 list, ok := v.([]RepoSummary)
57 if !ok {
58 return nil, fmt.Errorf("sections: repos returned %T", v)
59 }
60
61 any := false
62 groups := map[string][]RepoSummary{}
63 for _, r := range list {
64 if r.Section != "" {
65 any = true
66 }
67 groups[r.Section] = append(groups[r.Section], r)
68 }
69 if !any {
70 return []Section{{Name: "", Repos: list}}, nil
71 }
72
73 names := make([]string, 0, len(groups))
74 for n := range groups {
75 if n != "" {
76 names = append(names, n)
77 }
78 }
79 sort.Strings(names)
80
81 out := make([]Section, 0, len(groups))
82 for _, n := range names {
83 out = append(out, Section{Name: n, Repos: groups[n]})
84 }
85 if rest, ok := groups[""]; ok {
86 out = append(out, Section{Name: unsectioned, Repos: rest})
87 }
88 return out, nil
89 }
90
91 // unsectioned labels the leftover group. Named rather than inlined so it is
92 // obvious this string is user-facing.
93 const unsectioned = "Other"