depp

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

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