1package main
2
3import (
4 "embed"
5 "flag"
6 "fmt"
7 "html/template"
8 "log"
9 "os"
10 "path/filepath"
11 "sort"
12 "time"
13
14 "github.com/nmeum/depp/css"
15 "github.com/nmeum/depp/gitweb"
16)
17
18type Repo struct {
19 Name string
20 Title string
21 Desc string
22 Modified time.Time
23}
24
25type Page struct {
26 CurPage int
27 NumPages int
28
29 Title string
30 Desc string
31 Repos []Repo
32}
33
34//go:embed tmpl
35var templates embed.FS
36
37var (
38 desc = flag.String("s", "", "short description of git host")
39 title = flag.String("t", "depp-index", "page title")
40 dest = flag.String("d", "./www", "output directory for HTML files")
41 strip = flag.Bool("x", false, "strip .git extension from repository name in link")
42 items = flag.Int("p", 20, "amount of repos per HTML page, a zero value disables pagination")
43)
44
45func usage() {
46 fmt.Fprintf(flag.CommandLine.Output(),
47 "USAGE: %s [FLAGS] REPOSITORY...\n\n"+
48 "The following flags are supported:\n\n", os.Args[0])
49
50 flag.PrintDefaults()
51 os.Exit(2)
52}
53
54func repoLink(repo *Repo) string {
55 if *strip {
56 // Return a post-processed repository name without .git
57 return repo.Title
58 } else {
59 // Return the raw file name, potentially including .git
60 return repo.Name
61 }
62}
63
64func pageName(page int) string {
65 if page == 0 {
66 return "index.html"
67 } else {
68 return fmt.Sprintf("page-%d.html", page)
69 }
70}
71
72func pageRefs(page Page) []int {
73 pages := make([]int, page.NumPages)
74 for i := 0; i < page.NumPages; i++ {
75 pages[i] = i
76 }
77 return pages
78}
79
80func createHTML(page Page, path string) error {
81 const name = "base.tmpl"
82
83 html := template.New(name)
84 html.Funcs(template.FuncMap{
85 "repoLink": repoLink,
86 "pageName": pageName,
87 "pageRefs": pageRefs,
88 })
89
90 tmpl, err := html.ParseFS(templates, "tmpl/*.tmpl")
91 if err != nil {
92 return err
93 }
94
95 file, err := os.Create(path)
96 if err != nil {
97 return err
98 }
99 defer file.Close()
100
101 err = tmpl.Execute(file, page)
102 if err != nil {
103 return err
104 }
105
106 return nil
107}
108
109func getRepos(fps []string) ([]Repo, error) {
110 repos := make([]Repo, len(fps))
111 for i, fp := range fps {
112 r, err := gitweb.NewRepo(fp, nil, 0)
113 if err != nil {
114 return []Repo{}, err
115 }
116
117 commit, err := r.Tip()
118 if err != nil {
119 return []Repo{}, err
120 }
121 desc, err := r.Description()
122 if err != nil {
123 return []Repo{}, err
124 }
125
126 sig := commit.Committer
127 repos[i] = Repo{
128 Name: filepath.Base(fp),
129 Title: r.Title,
130 Desc: desc,
131 Modified: sig.When,
132 }
133 }
134
135 sort.Sort(byModified(repos))
136 return repos, nil
137}
138
139func getPages(repos []Repo) []Page {
140 var numPages int
141 if *items == 0 {
142 numPages = 1
143 } else {
144 // division is not truncated towards zero
145 numPages = 1 + (len(repos)-1) / *items
146 }
147
148 pages := make([]Page, numPages)
149 for i := 0; i < numPages; i++ {
150 var maxrepos int
151 if *items == 0 {
152 maxrepos = len(repos)
153 } else {
154 maxrepos = min(*items, len(repos))
155 }
156
157 pages[i] = Page{
158 CurPage: i,
159 NumPages: numPages,
160 Title: *title,
161 Desc: *desc,
162 Repos: repos[0:maxrepos],
163 }
164
165 repos = repos[maxrepos:]
166 }
167
168 return pages
169}
170
171func main() {
172 flag.Usage = usage
173 flag.Parse()
174
175 log.SetFlags(log.Lshortfile)
176 if flag.NArg() == 0 {
177 usage()
178 }
179
180 repos, err := getRepos(flag.Args())
181 if err != nil {
182 log.Fatal(err)
183 }
184 pages := getPages(repos)
185
186 err = os.MkdirAll(*dest, 0755)
187 if err != nil {
188 log.Fatal(err)
189 }
190
191 err = css.Create(filepath.Join(*dest, "style.css"))
192 if err != nil {
193 log.Fatal(err)
194 }
195
196 for _, page := range pages {
197 fp := filepath.Join(*dest, pageName(page.CurPage))
198 err = createHTML(page, fp)
199 if err != nil {
200 log.Fatal(err)
201 }
202 }
203}