cerrado @ 6b96b76d66a929a2b428505809fda23a19005c63

  1package git
  2
  3import (
  4	"bytes"
  5	"fmt"
  6	"io"
  7	"log/slog"
  8	"net/http"
  9	"os"
 10	"path/filepath"
 11	"strings"
 12
 13	"git.gabrielgio.me/cerrado/pkg/ext"
 14	"git.gabrielgio.me/cerrado/pkg/service"
 15	"git.gabrielgio.me/cerrado/templates"
 16	"github.com/alecthomas/chroma/v2"
 17	"github.com/alecthomas/chroma/v2/formatters/html"
 18	"github.com/alecthomas/chroma/v2/lexers"
 19	"github.com/alecthomas/chroma/v2/styles"
 20	"github.com/go-git/go-git/v5/plumbing"
 21	"github.com/go-git/go-git/v5/plumbing/object"
 22	"github.com/gomarkdown/markdown"
 23	markdownhtml "github.com/gomarkdown/markdown/html"
 24	"github.com/gomarkdown/markdown/parser"
 25)
 26
 27type (
 28	GitHandler struct {
 29		gitService gitService
 30		readmePath string
 31	}
 32
 33	gitService interface {
 34		ListRepositories() ([]*service.Repository, error)
 35		ListCommits(name string, ref string, count int) ([]*object.Commit, error)
 36		GetHead(name string) (*plumbing.Reference, error)
 37		GetTree(name, ref, path string) (*object.Tree, error)
 38		GetFileContent(name, ref, path string) (string, error)
 39		GetAbout(name string) (string, error)
 40		ListTags(name string) ([]*plumbing.Reference, error)
 41		ListBranches(name string) ([]*plumbing.Reference, error)
 42		WriteTarGZip(w io.Writer, name, ref, prefix string) error
 43	}
 44
 45	configurationRepository interface {
 46		GetRootReadme() string
 47	}
 48)
 49
 50func NewGitHandler(gitService gitService, confRepo configurationRepository) *GitHandler {
 51	return &GitHandler{
 52		gitService: gitService,
 53		readmePath: confRepo.GetRootReadme(),
 54	}
 55}
 56
 57func (g *GitHandler) List(w http.ResponseWriter, _ *http.Request) error {
 58	repos, err := g.gitService.ListRepositories()
 59	if err != nil {
 60		return err
 61	}
 62
 63	f, err := os.Open(g.readmePath)
 64	if err != nil {
 65		return err
 66	}
 67
 68	bs, err := io.ReadAll(f)
 69	if err != nil {
 70		return err
 71	}
 72
 73	extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock
 74	p := parser.NewWithExtensions(extensions)
 75	doc := p.Parse(bs)
 76
 77	htmlFlag := markdownhtml.CommonFlags | markdownhtml.HrefTargetBlank
 78	opts := markdownhtml.RendererOptions{Flags: htmlFlag}
 79	renderer := markdownhtml.NewRenderer(opts)
 80
 81	bs = markdown.Render(doc, renderer)
 82
 83	gitList := &templates.GitListPage{
 84		Respositories: repos,
 85		About:         bs,
 86	}
 87	templates.WritePageTemplate(w, gitList)
 88	return nil
 89}
 90
 91func (g *GitHandler) Archive(w http.ResponseWriter, r *http.Request) error {
 92	ext.SetGZip(w)
 93	name := r.PathValue("name")
 94	file := r.PathValue("file")
 95	ref := strings.TrimSuffix(file, ".tar.gz")
 96
 97	// TODO: remove it once we can support more than gzip
 98	if !strings.HasSuffix(file, ".tar.gz") {
 99		ext.NotFound(w)
100		return nil
101	}
102
103	filename := fmt.Sprintf("%s-%s.tar.gz", name, ref)
104	ext.SetFileName(w, filename)
105
106	prefix := fmt.Sprintf("%s-%s", name, ref)
107	err := g.gitService.WriteTarGZip(w, name, ref, prefix)
108	if err != nil {
109		// once we start writing to the body we can't report error anymore
110		// so we are only left with printing the error.
111		slog.Error("Error generating tar gzip file", "error", err)
112	}
113
114	return nil
115}
116
117func (g *GitHandler) Summary(w http.ResponseWriter, r *http.Request) error {
118	ext.SetHTML(w)
119	name := r.PathValue("name")
120	ref, err := g.gitService.GetHead(name)
121	if err != nil {
122		return err
123	}
124
125	tags, err := g.gitService.ListTags(name)
126	if err != nil {
127		return err
128	}
129
130	branches, err := g.gitService.ListBranches(name)
131	if err != nil {
132		return err
133	}
134
135	commits, err := g.gitService.ListCommits(name, "", 10)
136	if err != nil {
137		return err
138	}
139
140	gitList := &templates.GitItemPage{
141		Name: name,
142		Ref:  ref.Name().Short(),
143		GitItemBase: &templates.GitItemSummaryPage{
144			Tags:     tags,
145			Branches: branches,
146			Commits:  commits,
147		},
148	}
149	templates.WritePageTemplate(w, gitList)
150	return nil
151}
152
153func (g *GitHandler) About(w http.ResponseWriter, r *http.Request) error {
154	ext.SetHTML(w)
155	name := r.PathValue("name")
156	ref, err := g.gitService.GetHead(name)
157	if err != nil {
158		return err
159	}
160
161	file, err := g.gitService.GetAbout(name)
162	if err != nil {
163		return err
164	}
165
166	extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock
167	p := parser.NewWithExtensions(extensions)
168	doc := p.Parse([]byte(file))
169
170	htmlFlag := markdownhtml.CommonFlags | markdownhtml.HrefTargetBlank
171	opts := markdownhtml.RendererOptions{Flags: htmlFlag}
172	renderer := markdownhtml.NewRenderer(opts)
173
174	bs := markdown.Render(doc, renderer)
175
176	gitList := &templates.GitItemPage{
177		Name: name,
178		Ref:  ref.Name().Short(),
179		GitItemBase: &templates.GitItemAboutPage{
180			About: bs,
181		},
182	}
183	templates.WritePageTemplate(w, gitList)
184	return nil
185}
186
187func (g *GitHandler) Refs(w http.ResponseWriter, r *http.Request) error {
188	ext.SetHTML(w)
189	name := r.PathValue("name")
190
191	tags, err := g.gitService.ListTags(name)
192	if err != nil {
193		return err
194	}
195
196	branches, err := g.gitService.ListBranches(name)
197	if err != nil {
198		return err
199	}
200
201	ref, err := g.gitService.GetHead(name)
202	if err != nil {
203		return err
204	}
205
206	gitList := &templates.GitItemPage{
207		Name: name,
208		Ref:  ref.Name().Short(),
209		GitItemBase: &templates.GitItemRefsPage{
210			Tags:     tags,
211			Branches: branches,
212		},
213	}
214	templates.WritePageTemplate(w, gitList)
215	return nil
216}
217
218func (g *GitHandler) Tree(w http.ResponseWriter, r *http.Request) error {
219	ext.SetHTML(w)
220	name := r.PathValue("name")
221	ref := r.PathValue("ref")
222	rest := r.PathValue("rest")
223
224	tree, err := g.gitService.GetTree(name, ref, rest)
225	if err != nil {
226		return err
227	}
228
229	gitList := &templates.GitItemPage{
230		Name: name,
231		Ref:  ref,
232		GitItemBase: &templates.GitItemTreePage{
233			CurrentPath: rest,
234			Tree:        tree,
235		},
236	}
237	templates.WritePageTemplate(w, gitList)
238	return nil
239}
240
241func (g *GitHandler) Blob(w http.ResponseWriter, r *http.Request) error {
242	ext.SetHTML(w)
243	name := r.PathValue("name")
244	ref := r.PathValue("ref")
245	rest := r.PathValue("rest")
246
247	file, err := g.gitService.GetFileContent(name, ref, rest)
248	if err != nil {
249		return err
250	}
251
252	filename := filepath.Base(rest)
253	lexer := GetLexers(filename)
254	style := styles.Get("xcode")
255	formatter := html.New(
256		html.WithLineNumbers(true),
257	)
258	iterator, err := lexer.Tokenise(nil, file)
259	if err != nil {
260		return err
261	}
262
263	var code bytes.Buffer
264	err = formatter.Format(&code, style, iterator)
265	if err != nil {
266		return err
267	}
268
269	gitList := &templates.GitItemPage{
270		Name: name,
271		Ref:  ref,
272		GitItemBase: &templates.GitItemBlobPage{
273			File:    rest,
274			Content: code.Bytes(),
275		},
276	}
277	templates.WritePageTemplate(w, gitList)
278	return nil
279}
280
281func (g *GitHandler) Log(w http.ResponseWriter, r *http.Request) error {
282	ext.SetHTML(w)
283	name := r.PathValue("name")
284	ref := r.PathValue("ref")
285
286	commits, err := g.gitService.ListCommits(name, ref, 1000)
287	if err != nil {
288		return err
289	}
290
291	gitList := &templates.GitItemPage{
292		Name: name,
293		Ref:  ref,
294		GitItemBase: &templates.GitItemLogPage{
295			Commits: commits,
296		},
297	}
298	templates.WritePageTemplate(w, gitList)
299	return nil
300}
301
302func GetLexers(filename string) chroma.Lexer {
303	if filename == "APKBUILD" {
304		return lexers.Get("sh")
305	}
306
307	lexer := lexers.Get(filename)
308
309	if lexer == nil {
310		lexer = lexers.Get("txt")
311	}
312	return lexer
313}