cerrado @ 8f9853c8e26ffbad74e6414cec31104281a3860b

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