cerrado @ e1664fcbc4685906d3dabc66bf947a17bce7efc0

  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, filename 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	refs := r.PathValue("refs")
 95	ref := strings.TrimSuffix(refs, ".tar.gz")
 96
 97	// TODO: remove it once we can support more than gzip
 98	if !strings.HasSuffix(refs, ".tar.gz") {
 99		ext.NotFound(w)
100		return nil
101	}
102
103	filenameWithExt := fmt.Sprintf("%s-%s.tar.gz", name, ref)
104	ext.SetFileName(w, filenameWithExt)
105	filename := fmt.Sprintf("%s-%s", name, ref)
106
107	// writing to a buffer so we can run all the process before writing error
108	var buf bytes.Buffer
109	err := g.gitService.WriteTarGZip(&buf, name, ref, filename)
110	if err != nil {
111		return err
112	}
113
114	// since that has write to w it cannot return a error.
115	_, err = io.Copy(w, &buf)
116	if err != nil {
117		slog.Error("Error copying buffer", "error", err)
118	}
119
120	return nil
121}
122
123func (g *GitHandler) Summary(w http.ResponseWriter, r *http.Request) error {
124	ext.SetHTML(w)
125	name := r.PathValue("name")
126	ref, err := g.gitService.GetHead(name)
127	if err != nil {
128		return err
129	}
130
131	tags, err := g.gitService.ListTags(name)
132	if err != nil {
133		return err
134	}
135
136	branches, err := g.gitService.ListBranches(name)
137	if err != nil {
138		return err
139	}
140
141	commits, err := g.gitService.ListCommits(name, "", 10)
142	if err != nil {
143		return err
144	}
145
146	gitList := &templates.GitItemPage{
147		Name: name,
148		Ref:  ref.Name().Short(),
149		GitItemBase: &templates.GitItemSummaryPage{
150			Tags:     tags,
151			Branches: branches,
152			Commits:  commits,
153		},
154	}
155	templates.WritePageTemplate(w, gitList)
156	return nil
157}
158
159func (g *GitHandler) About(w http.ResponseWriter, r *http.Request) error {
160	ext.SetHTML(w)
161	name := r.PathValue("name")
162	ref, err := g.gitService.GetHead(name)
163	if err != nil {
164		return err
165	}
166
167	file, err := g.gitService.GetAbout(name)
168	if err != nil {
169		return err
170	}
171
172	extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock
173	p := parser.NewWithExtensions(extensions)
174	doc := p.Parse([]byte(file))
175
176	htmlFlag := markdownhtml.CommonFlags | markdownhtml.HrefTargetBlank
177	opts := markdownhtml.RendererOptions{Flags: htmlFlag}
178	renderer := markdownhtml.NewRenderer(opts)
179
180	bs := markdown.Render(doc, renderer)
181
182	gitList := &templates.GitItemPage{
183		Name: name,
184		Ref:  ref.Name().Short(),
185		GitItemBase: &templates.GitItemAboutPage{
186			About: bs,
187		},
188	}
189	templates.WritePageTemplate(w, gitList)
190	return nil
191}
192
193func (g *GitHandler) Refs(w http.ResponseWriter, r *http.Request) error {
194	ext.SetHTML(w)
195	name := r.PathValue("name")
196
197	tags, err := g.gitService.ListTags(name)
198	if err != nil {
199		return err
200	}
201
202	branches, err := g.gitService.ListBranches(name)
203	if err != nil {
204		return err
205	}
206
207	ref, err := g.gitService.GetHead(name)
208	if err != nil {
209		return err
210	}
211
212	gitList := &templates.GitItemPage{
213		Name: name,
214		Ref:  ref.Name().Short(),
215		GitItemBase: &templates.GitItemRefsPage{
216			Tags:     tags,
217			Branches: branches,
218		},
219	}
220	templates.WritePageTemplate(w, gitList)
221	return nil
222}
223
224func (g *GitHandler) Tree(w http.ResponseWriter, r *http.Request) error {
225	ext.SetHTML(w)
226	name := r.PathValue("name")
227	ref := r.PathValue("ref")
228	rest := r.PathValue("rest")
229
230	tree, err := g.gitService.GetTree(name, ref, rest)
231	if err != nil {
232		return err
233	}
234
235	gitList := &templates.GitItemPage{
236		Name: name,
237		Ref:  ref,
238		GitItemBase: &templates.GitItemTreePage{
239			CurrentPath: rest,
240			Tree:        tree,
241		},
242	}
243	templates.WritePageTemplate(w, gitList)
244	return nil
245}
246
247func (g *GitHandler) Blob(w http.ResponseWriter, r *http.Request) error {
248	ext.SetHTML(w)
249	name := r.PathValue("name")
250	ref := r.PathValue("ref")
251	rest := r.PathValue("rest")
252
253	file, err := g.gitService.GetFileContent(name, ref, rest)
254	if err != nil {
255		return err
256	}
257
258	filename := filepath.Base(rest)
259	lexer := GetLexers(filename)
260	style := styles.Get("xcode")
261	formatter := html.New(
262		html.WithLineNumbers(true),
263	)
264	iterator, err := lexer.Tokenise(nil, file)
265	if err != nil {
266		return err
267	}
268
269	var code bytes.Buffer
270	err = formatter.Format(&code, style, iterator)
271	if err != nil {
272		return err
273	}
274
275	gitList := &templates.GitItemPage{
276		Name: name,
277		Ref:  ref,
278		GitItemBase: &templates.GitItemBlobPage{
279			File:    rest,
280			Content: code.Bytes(),
281		},
282	}
283	templates.WritePageTemplate(w, gitList)
284	return nil
285}
286
287func (g *GitHandler) Log(w http.ResponseWriter, r *http.Request) error {
288	ext.SetHTML(w)
289	name := r.PathValue("name")
290	ref := r.PathValue("ref")
291
292	commits, err := g.gitService.ListCommits(name, ref, 1000)
293	if err != nil {
294		return err
295	}
296
297	gitList := &templates.GitItemPage{
298		Name: name,
299		Ref:  ref,
300		GitItemBase: &templates.GitItemLogPage{
301			Commits: commits,
302		},
303	}
304	templates.WritePageTemplate(w, gitList)
305	return nil
306}
307
308func GetLexers(filename string) chroma.Lexer {
309	if filename == "APKBUILD" {
310		return lexers.Get("sh")
311	}
312
313	lexer := lexers.Get(filename)
314
315	if lexer == nil {
316		lexer = lexers.Get("txt")
317	}
318	return lexer
319}