cerrado @ ba84c0d82066739adbca468846a2688e02432b6f

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