depp

Fork https://github.com/nmeum/depp

git clone git://git.lin.moe/fork/depp.git

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