cerrado @ 981e192d2ed0cc3772f8ba8107a4cfc8dc33d214

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