depp

No frills static page generator for Git repositories

git clone https://git.8pit.net/depp.git

  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}