1package main23import (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"1516 "github.com/nmeum/depp/css"17 "github.com/nmeum/depp/gitweb"18)1920type Repo struct {21 Name string22 Title string23 Desc string24 Modified time.Time25}2627type Page struct {28 CurPage int29 NumPages int3031 Title string32 Desc string33 Repos []Repo34}3536//go:embed tmpl37var templates embed.FS3839var (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)4748func 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])5253 flag.PrintDefaults()54 os.Exit(2)55}5657func repoLink(repo *Repo) string {58 if *strip {59 // Return a post-processed repository name without .git60 ext := strings.LastIndex(repo.Name, ".git")61 if ext > 0 {62 return repo.Name[0:ext]63 }64 }65 return repo.Name66}6768func pageName(page int) string {69 if page == 0 {70 return "index.html"71 } else {72 return fmt.Sprintf("page-%d.html", page)73 }74}7576func pageRefs(page Page) []int {77 pages := make([]int, page.NumPages)78 for i := 0; i < page.NumPages; i++ {79 pages[i] = i80 }81 return pages82}8384func createHTML(page Page, path string) error {85 const name = "base.tmpl"8687 html := template.New(name)88 html.Funcs(template.FuncMap{89 "repoLink": repoLink,90 "pageName": pageName,91 "pageRefs": pageRefs,92 })9394 tmpl, err := html.ParseFS(templates, "tmpl/*.tmpl")95 if err != nil {96 return err97 }9899 file, err := os.Create(path)100 if err != nil {101 return err102 }103 defer file.Close()104105 err = tmpl.Execute(file, page)106 if err != nil {107 return err108 }109110 return nil111}112113func 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 nil120 }121122 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 err126 }127 commit, err := r.Tip()128 if err != nil {129 return err130 }131 desc, err := r.Description()132 if err != nil {133 return err134 }135136 sig := commit.Committer137138 repos = append(repos, Repo{139 Name: reponame,140 Title: r.Title,141 Desc: desc,142 Modified: sig.When,143 })144145 }146 return filepath.SkipDir147 }); err != nil {148 return []Repo{}, err149 }150 }151152 sort.Sort(byModified(repos))153 return repos, nil154}155156func 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{}, err162 }163164 commit, err := r.Tip()165 if err != nil {166 return []Repo{}, err167 }168 desc, err := r.Description()169 if err != nil {170 return []Repo{}, err171 }172173 sig := commit.Committer174 repos[i] = Repo{175 Name: filepath.Base(fp),176 Title: r.Title,177 Desc: desc,178 Modified: sig.When,179 }180 }181182 sort.Sort(byModified(repos))183 return repos, nil184}185186func getPages(repos []Repo) []Page {187 var numPages int188 if *items == 0 {189 numPages = 1190 } else {191 // division is not truncated towards zero192 numPages = 1 + (len(repos)-1) / *items193 }194195 pages := make([]Page, numPages)196 for i := 0; i < numPages; i++ {197 var maxrepos int198 if *items == 0 {199 maxrepos = len(repos)200 } else {201 maxrepos = min(*items, len(repos))202 }203204 pages[i] = Page{205 CurPage: i,206 NumPages: numPages,207 Title: *title,208 Desc: *desc,209 Repos: repos[0:maxrepos],210 }211212 repos = repos[maxrepos:]213 }214215 return pages216}217218func main() {219 flag.Usage = usage220 flag.Parse()221222 log.SetFlags(log.Lshortfile)223 if flag.NArg() == 0 {224 usage()225 }226227 var (228 repos []Repo229 err error230 )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 }239240 pages := getPages(repos)241242 err = os.MkdirAll(*dest, 0755)243 if err != nil {244 log.Fatal(err)245 }246247 err = css.Create(filepath.Join(*dest, "style.css"))248 if err != nil {249 log.Fatal(err)250 }251252 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}