cerrado @ 5ce7d18d415c6911740ba15e3b4e6a1b94562019

  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/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 *service.GitService
 30		config     configurationRepository
 31	}
 32
 33	configurationRepository interface {
 34		GetRootReadme() string
 35		GetSyntaxHighlight() string
 36	}
 37)
 38
 39func NewGitHandler(gitService *service.GitService, confRepo configurationRepository) *GitHandler {
 40	return &GitHandler{
 41		gitService: gitService,
 42		config:     confRepo,
 43	}
 44}
 45
 46func (g *GitHandler) List(w http.ResponseWriter, _ *http.Request) error {
 47	repos, err := g.gitService.ListRepositories()
 48	if err != nil {
 49		return err
 50	}
 51
 52	f, err := os.Open(g.config.GetRootReadme())
 53	if err != nil {
 54		return err
 55	}
 56
 57	bs, err := io.ReadAll(f)
 58	if err != nil {
 59		return err
 60	}
 61
 62	extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock
 63	p := parser.NewWithExtensions(extensions)
 64	doc := p.Parse(bs)
 65
 66	htmlFlag := markdownhtml.CommonFlags | markdownhtml.HrefTargetBlank
 67	opts := markdownhtml.RendererOptions{Flags: htmlFlag}
 68	renderer := markdownhtml.NewRenderer(opts)
 69
 70	bs = markdown.Render(doc, renderer)
 71
 72	gitList := &templates.GitListPage{
 73		Respositories: repos,
 74		About:         bs,
 75	}
 76	templates.WritePageTemplate(w, gitList)
 77	return nil
 78}
 79
 80func (g *GitHandler) Archive(w http.ResponseWriter, r *http.Request) error {
 81	ext.SetGZip(w)
 82	name := r.PathValue("name")
 83	file := r.PathValue("file")
 84	ref := strings.TrimSuffix(file, ".tar.gz")
 85
 86	// TODO: remove it once we can support more than gzip
 87	if !strings.HasSuffix(file, ".tar.gz") {
 88		ext.NotFound(w)
 89		return nil
 90	}
 91
 92	filename := fmt.Sprintf("%s-%s.tar.gz", name, ref)
 93	ext.SetFileName(w, filename)
 94
 95	prefix := fmt.Sprintf("%s-%s", name, ref)
 96	err := g.gitService.WriteTarGZip(w, name, ref, prefix)
 97	if err != nil {
 98		// once we start writing to the body we can't report error anymore
 99		// so we are only left with printing the error.
100		slog.Error("Error generating tar gzip file", "error", err)
101	}
102
103	return nil
104}
105
106func (g *GitHandler) Summary(w http.ResponseWriter, r *http.Request) error {
107	ext.SetHTML(w)
108	name := r.PathValue("name")
109	ref, err := g.gitService.GetHead(name)
110	if err != nil {
111		return err
112	}
113
114	tags, err := g.gitService.ListTags(name)
115	if err != nil {
116		return err
117	}
118
119	branches, err := g.gitService.ListBranches(name)
120	if err != nil {
121		return err
122	}
123
124	commits, err := g.gitService.ListCommits(name, "", 10)
125	if err != nil {
126		return err
127	}
128
129	gitList := &templates.GitItemPage{
130		Name: name,
131		Ref:  ref.Name().Short(),
132		GitItemBase: &templates.GitItemSummaryPage{
133			Tags:     tags,
134			Branches: branches,
135			Commits:  commits,
136		},
137	}
138	templates.WritePageTemplate(w, gitList)
139	return nil
140}
141
142func (g *GitHandler) About(w http.ResponseWriter, r *http.Request) error {
143	ext.SetHTML(w)
144	name := r.PathValue("name")
145	ref, err := g.gitService.GetHead(name)
146	if err != nil {
147		return err
148	}
149
150	file, err := g.gitService.GetAbout(name)
151	if errors.Is(err, object.ErrFileNotFound) {
152		templates.WritePageTemplate(w, &templates.GitItemPage{
153			Name: name,
154			Ref:  ref.Name().Short(),
155			GitItemBase: &templates.GitItemAboutPage{
156				About: []byte("About file not configured properly"),
157			},
158		})
159		return nil
160	}
161	if err != nil {
162		return err
163	}
164
165	extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock
166	p := parser.NewWithExtensions(extensions)
167	doc := p.Parse(file)
168
169	htmlFlag := markdownhtml.CommonFlags | markdownhtml.HrefTargetBlank
170	opts := markdownhtml.RendererOptions{Flags: htmlFlag}
171	renderer := markdownhtml.NewRenderer(opts)
172
173	bs := markdown.Render(doc, renderer)
174
175	gitList := &templates.GitItemPage{
176		Name: name,
177		Ref:  ref.Name().Short(),
178		GitItemBase: &templates.GitItemAboutPage{
179			About: bs,
180		},
181	}
182	templates.WritePageTemplate(w, gitList)
183	return nil
184}
185
186func (g *GitHandler) Refs(w http.ResponseWriter, r *http.Request) error {
187	ext.SetHTML(w)
188	name := r.PathValue("name")
189
190	tags, err := g.gitService.ListTags(name)
191	if err != nil {
192		return err
193	}
194
195	branches, err := g.gitService.ListBranches(name)
196	if err != nil {
197		return err
198	}
199
200	ref, err := g.gitService.GetHead(name)
201	if err != nil {
202		return err
203	}
204
205	gitList := &templates.GitItemPage{
206		Name: name,
207		Ref:  ref.Name().Short(),
208		GitItemBase: &templates.GitItemRefsPage{
209			Tags:     tags,
210			Branches: branches,
211		},
212	}
213	templates.WritePageTemplate(w, gitList)
214	return nil
215}
216
217func (g *GitHandler) Tree(w http.ResponseWriter, r *http.Request) error {
218	ext.SetHTML(w)
219	name := r.PathValue("name")
220	ref := r.PathValue("ref")
221	rest := r.PathValue("rest")
222	paths := []string{}
223
224	// this is avoid Split from generating a len 1 array with empty string
225	if rest != "" {
226		paths = strings.Split(rest, "/")
227	}
228
229	tree, err := g.gitService.GetTree(name, ref, rest)
230	if err != nil {
231		return err
232	}
233
234	gitList := &templates.GitItemPage{
235		Name: name,
236		Ref:  ref,
237		GitItemBase: &templates.GitItemTreePage{
238			Path: paths,
239			Tree: tree,
240		},
241	}
242	templates.WritePageTemplate(w, gitList)
243	return nil
244}
245
246func (g *GitHandler) Blob(w http.ResponseWriter, r *http.Request) error {
247	ext.SetHTML(w)
248	name := r.PathValue("name")
249	ref := r.PathValue("ref")
250	rest := r.PathValue("rest")
251	paths := []string{}
252
253	// this is avoid Split from generating a len 1 array with empty string
254	if rest != "" {
255		paths = strings.Split(rest, "/")
256	}
257
258	isBin, err := g.gitService.IsBinary(name, ref, rest)
259	if err != nil {
260		return err
261	}
262
263	// if it is binary no need to over all the chroma process
264	if isBin {
265		gitList := &templates.GitItemPage{
266			Name: name,
267			Ref:  ref,
268			GitItemBase: &templates.GitItemBlobPage{
269				Path:    paths,
270				Content: []byte("Binary file"),
271			},
272		}
273		templates.WritePageTemplate(w, gitList)
274		return nil
275	}
276
277	file, err := g.gitService.GetFileContent(name, ref, rest)
278	if err != nil {
279		return err
280	}
281
282	filename := filepath.Base(rest)
283	lexer := GetLexers(filename)
284	style := styles.Get(g.config.GetSyntaxHighlight())
285
286	formatter := html.New(
287		html.WithLineNumbers(true),
288		html.WithLinkableLineNumbers(true, "L"),
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			Path:    paths,
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	diff, err := g.gitService.Diff(name, ref)
346	if err != nil {
347		return err
348	}
349
350	gitList := &templates.GitItemPage{
351		Name: name,
352		Ref:  ref,
353		GitItemBase: &templates.GitItemCommitPage{
354			Commit: commit,
355			Diff:   diff,
356		},
357	}
358	templates.WritePageTemplate(w, gitList)
359	return nil
360}
361
362func GetLexers(filename string) chroma.Lexer {
363	if filename == "APKBUILD" {
364		return lexers.Get("sh")
365	}
366
367	if strings.HasSuffix(filename, ".qtpl") {
368		return lexers.Get("html")
369	}
370
371	lexer := lexers.Get(filename)
372
373	if lexer == nil {
374		lexer = lexers.Get("txt")
375	}
376	return lexer
377}