1package main
2
3import (
4 "embed"
5 "errors"
6 "flag"
7 "fmt"
8 "html/template"
9 "io/fs"
10 "log"
11 "net/url"
12 "os"
13 "path/filepath"
14
15 "github.com/nmeum/depp/css"
16 "github.com/nmeum/depp/gitweb"
17)
18
19//go:embed tmpl
20var templates embed.FS
21
22var (
23 commits = flag.Uint("c", 5, "amount of recent commits to include")
24 force = flag.Bool("f", false, "force rebuilding of all HTML files")
25 gitURL = flag.String("u", "", "clone URL for the Git repository")
26 destination = flag.String("d", "./www", "output directory for HTML files")
27 verbose = flag.Bool("v", false, "print the name of each changed file")
28)
29
30var tmpl *template.Template
31
32const (
33 // Name of file used to record the hash of the generated tree object.
34 stateFile = ".tree"
35)
36
37func usage() {
38 fmt.Fprintf(flag.CommandLine.Output(),
39 "USAGE: %s [FLAGS] REPOSITORY\n\n"+
40 "The following flags are supported:\n\n", os.Args[0])
41
42 flag.PrintDefaults()
43 os.Exit(2)
44}
45
46func walkPages(name string, page *gitweb.RepoPage) error {
47 if *verbose {
48 fmt.Println(name)
49 }
50
51 dest := filepath.Join(*destination, name+".html")
52 if page == nil { // file was removed
53 err := os.Remove(dest)
54 if err != nil && !errors.Is(err, fs.ErrNotExist) {
55 return err
56 }
57
58 // In case name refers to a (now empty) directory:
59 os.Remove(filepath.Join(*destination, name))
60
61 return nil
62 } else if isIndexPage(page) {
63 dest = filepath.Join(*destination, "index.html")
64 }
65
66 err := os.MkdirAll(filepath.Dir(dest), 0755)
67 if err != nil {
68 return err
69 }
70 file, err := os.Create(dest)
71 if err != nil {
72 return err
73 }
74 defer file.Close()
75
76 err = tmpl.Execute(file, page)
77 if err != nil {
78 return err
79 }
80
81 return nil
82}
83
84func buildHTML() (*template.Template, error) {
85 var err error
86
87 const name = "base.tmpl"
88 tmpl := template.New(name)
89
90 funcMap := template.FuncMap{
91 "summarize": summarize,
92 "getRelPath": getRelPath,
93 "increment": increment,
94 "decrement": decrement,
95 "getLines": getLines,
96 "padNumber": padNumber,
97 "relIndex": relIndex,
98 "isIndexPage": isIndexPage,
99 "renderReadme": renderReadme,
100 }
101 tmpl = tmpl.Funcs(funcMap)
102
103 tmpl, err = tmpl.ParseFS(templates, "tmpl/*.tmpl")
104 if err != nil {
105 return nil, err
106 }
107
108 return tmpl, nil
109}
110
111func generate(repo *gitweb.Repo) error {
112 var err error
113 tmpl, err = buildHTML()
114 if err != nil {
115 return err
116 }
117 err = repo.Walk(walkPages)
118 if err != nil {
119 return err
120 }
121
122 cssPath := filepath.Join(*destination, "style.css")
123 _, err = os.Stat(cssPath)
124 if *force || errors.Is(err, os.ErrNotExist) {
125 err = css.Create(cssPath)
126 if err != nil {
127 return err
128 }
129 }
130
131 return nil
132}
133
134func main() {
135 flag.Usage = usage
136 flag.Parse()
137
138 log.SetFlags(log.Lshortfile)
139 if flag.NArg() != 1 {
140 usage()
141 }
142
143 gitURL, err := url.Parse(*gitURL)
144 if err != nil {
145 log.Fatal(err)
146 }
147
148 path := flag.Arg(0)
149 statePath := filepath.Join(*destination, stateFile)
150
151 repo, err := gitweb.NewRepo(path, gitURL, *commits)
152 if err != nil {
153 log.Fatal(err)
154 }
155 if !*force {
156 err = repo.ReadState(statePath)
157 if err != nil && !errors.Is(err, os.ErrNotExist) {
158 log.Fatal(err)
159 }
160 }
161
162 err = generate(repo)
163 if err != nil {
164 log.Fatal(err)
165 }
166 err = repo.WriteState(statePath)
167 if err != nil {
168 log.Fatal(err)
169 }
170}