cerrado @ 18aa098f50e2a2c7db01dd4d04dde460fd40f5d5

feat: Add blob endpoint
   1diff --git a/pkg/git/git.go b/pkg/git/git.go
   2index ce72465a84f2b8f0c65411b176b7a075ca031609..6a7b91fcba4060e8c2462a6ff03cf9fd309218d4 100644
   3--- a/pkg/git/git.go
   4+++ b/pkg/git/git.go
   5@@ -13,7 +13,8 @@
   6 var ()
   7 
   8 var (
   9-	MissingRefErr = errors.New("Reference not found")
  10+	MissingRefErr  = errors.New("Reference not found")
  11+	TreeForFileErr = errors.New("Trying to get tree of a file")
  12 )
  13 
  14 type (
  15@@ -145,9 +146,73 @@
  16 	return branches, nil
  17 }
  18 
  19+func (g *GitRepository) Tree(path string) (*object.Tree, error) {
  20+	err := g.validateRef()
  21+	if err != nil {
  22+		return nil, err
  23+	}
  24+
  25+	c, err := g.repository.CommitObject(g.ref)
  26+	if err != nil {
  27+		return nil, err
  28+	}
  29+
  30+	tree, err := c.Tree()
  31+	if err != nil {
  32+		return nil, err
  33+	}
  34+
  35+	if path == "" {
  36+		return tree, nil
  37+	} else {
  38+		o, err := tree.FindEntry(path)
  39+		if err != nil {
  40+			return nil, err
  41+		}
  42+
  43+		if !o.Mode.IsFile() {
  44+			subtree, err := tree.Tree(path)
  45+			if err != nil {
  46+				return nil, err
  47+			}
  48+			return subtree, nil
  49+		} else {
  50+			return nil, TreeForFileErr
  51+		}
  52+	}
  53+}
  54+
  55 func (g *GitRepository) validateRef() error {
  56 	if !g.setRef {
  57 		return g.SetRef("")
  58 	}
  59 	return nil
  60 }
  61+
  62+func (g *GitRepository) FileContent(path string) (string, error) {
  63+	c, err := g.repository.CommitObject(g.ref)
  64+	if err != nil {
  65+		return "", err
  66+	}
  67+
  68+	tree, err := c.Tree()
  69+	if err != nil {
  70+		return "", err
  71+	}
  72+
  73+	file, err := tree.File(path)
  74+	if err != nil {
  75+		return "", err
  76+	}
  77+
  78+	isbin, err := file.IsBinary()
  79+	if err != nil {
  80+		return "", err
  81+	}
  82+
  83+	if !isbin {
  84+		return file.Contents()
  85+	} else {
  86+		return "Binary file", nil
  87+	}
  88+}
  89diff --git a/pkg/handler/git/handler.go b/pkg/handler/git/handler.go
  90index e2f40422ed212156620190800a9ec6569bc4c11f..f3e74c747e433b33f1b68d13473ef5ab507b5b95 100644
  91--- a/pkg/handler/git/handler.go
  92+++ b/pkg/handler/git/handler.go
  93@@ -1,11 +1,17 @@
  94 package git
  95 
  96 import (
  97+	"bytes"
  98 	"log/slog"
  99 	"net/http"
 100+	"path/filepath"
 101 
 102 	"git.gabrielgio.me/cerrado/pkg/service"
 103 	"git.gabrielgio.me/cerrado/templates"
 104+	"github.com/alecthomas/chroma/v2"
 105+	"github.com/alecthomas/chroma/v2/formatters/html"
 106+	"github.com/alecthomas/chroma/v2/lexers"
 107+	"github.com/alecthomas/chroma/v2/styles"
 108 	"github.com/go-git/go-git/v5/plumbing"
 109 	"github.com/go-git/go-git/v5/plumbing/object"
 110 )
 111@@ -19,6 +25,8 @@ 	gitService interface {
 112 		ListRepositories() ([]*service.Repository, error)
 113 		ListCommits(name string, ref string) ([]*object.Commit, error)
 114 		GetHead(name string) (*plumbing.Reference, error)
 115+		GetTree(name, ref, path string) (*object.Tree, error)
 116+		GetFileContent(name, ref, path string) (string, error)
 117 		ListTags(name string) ([]*object.Tag, error)
 118 		ListBranches(name string) ([]*plumbing.Reference, error)
 119 	}
 120@@ -107,10 +115,64 @@
 121 func (g *GitHandler) Tree(w http.ResponseWriter, r *http.Request) {
 122 	name := r.PathValue("name")
 123 	ref := r.PathValue("ref")
 124+	rest := r.PathValue("rest")
 125+
 126+	tree, err := g.gitService.GetTree(name, ref, rest)
 127+	if err != nil {
 128+		slog.Error("Error loading tree", "error", err)
 129+		return
 130+	}
 131+
 132 	gitList := &templates.GitItemPage{
 133-		Name:        name,
 134-		Ref:         ref,
 135-		GitItemBase: &templates.GitItemTreePage{},
 136+		Name: name,
 137+		Ref:  ref,
 138+		GitItemBase: &templates.GitItemTreePage{
 139+			CurrentPath: rest,
 140+			Tree:        tree,
 141+			Ref:         ref,
 142+			Name:        name,
 143+		},
 144+	}
 145+	templates.WritePageTemplate(w, gitList)
 146+}
 147+
 148+func (g *GitHandler) Blob(w http.ResponseWriter, r *http.Request) {
 149+	name := r.PathValue("name")
 150+	ref := r.PathValue("ref")
 151+	rest := r.PathValue("rest")
 152+
 153+	file, err := g.gitService.GetFileContent(name, ref, rest)
 154+	if err != nil {
 155+		slog.Error("Error loading blob", "error", err)
 156+		return
 157+	}
 158+
 159+	filename := filepath.Base(rest)
 160+	lexer := GetLexers(filename)
 161+	style := styles.Get("xcode")
 162+	formatter := html.New(
 163+		html.WithLineNumbers(true),
 164+	)
 165+	iterator, err := lexer.Tokenise(nil, file)
 166+	if err != nil {
 167+		slog.Error("Error tokenise", "error", err)
 168+		return
 169+	}
 170+
 171+	var code bytes.Buffer
 172+	err = formatter.Format(&code, style, iterator)
 173+	if err != nil {
 174+		slog.Error("Error format", "error", err)
 175+		return
 176+	}
 177+
 178+	gitList := &templates.GitItemPage{
 179+		Name: name,
 180+		Ref:  ref,
 181+		GitItemBase: &templates.GitItemBlobPage{
 182+			File:    rest,
 183+			Content: code.Bytes(),
 184+		},
 185 	}
 186 	templates.WritePageTemplate(w, gitList)
 187 }
 188@@ -134,3 +196,16 @@ 		},
 189 	}
 190 	templates.WritePageTemplate(w, gitList)
 191 }
 192+
 193+func GetLexers(filename string) chroma.Lexer {
 194+	if filename == "APKBUILD" {
 195+		return lexers.Get("sh")
 196+	}
 197+
 198+	lexer := lexers.Get(filename)
 199+
 200+	if lexer == nil {
 201+		lexer = lexers.Get("txt")
 202+	}
 203+	return lexer
 204+}
 205diff --git a/pkg/handler/router.go b/pkg/handler/router.go
 206index bdf883ed8a8af090669e285504118bda8d86450a..ed782f75d525c637e539a4e6c07795cc0d8ac8d2 100644
 207--- a/pkg/handler/router.go
 208+++ b/pkg/handler/router.go
 209@@ -35,7 +35,8 @@ 	mux.HandleFunc("/static/{file}", staticHandler)
 210 	mux.HandleFunc("/{name}/about/{$}", gitHandler.About)
 211 	mux.HandleFunc("/{name}", gitHandler.Summary)
 212 	mux.HandleFunc("/{name}/refs/{$}", gitHandler.Refs)
 213-	mux.HandleFunc("/{name}/tree/{ref}", gitHandler.Tree)
 214+	mux.HandleFunc("/{name}/tree/{ref}/{rest...}", gitHandler.Tree)
 215+	mux.HandleFunc("/{name}/blob/{ref}/{rest...}", gitHandler.Blob)
 216 	mux.HandleFunc("/{name}/log/{ref}", gitHandler.Log)
 217 	mux.HandleFunc("/config", configHander)
 218 	mux.HandleFunc("/about", aboutHandler.About)
 219diff --git a/pkg/service/git.go b/pkg/service/git.go
 220index 9bf11f4e79cb77ae692820236f59db9bc9ea0453..f8867850cd8a76933da262156fdbac70134a27ff 100644
 221--- a/pkg/service/git.go
 222+++ b/pkg/service/git.go
 223@@ -89,6 +89,38 @@ 	}
 224 	return repo.Commits()
 225 }
 226 
 227+func (g *GitService) GetTree(name, ref, path string) (*object.Tree, error) {
 228+	// TODO: handle nil
 229+	r := g.configRepo.GetByName(name)
 230+
 231+	repo, err := git.OpenRepository(r.Path)
 232+	if err != nil {
 233+		return nil, err
 234+	}
 235+	err = repo.SetRef(ref)
 236+	if err != nil {
 237+		return nil, err
 238+	}
 239+
 240+	return repo.Tree(path)
 241+}
 242+
 243+func (g *GitService) GetFileContent(name, ref, path string) (string, error) {
 244+	// TODO: handle nil
 245+	r := g.configRepo.GetByName(name)
 246+
 247+	repo, err := git.OpenRepository(r.Path)
 248+	if err != nil {
 249+		return "", err
 250+	}
 251+	err = repo.SetRef(ref)
 252+	if err != nil {
 253+		return "", err
 254+	}
 255+
 256+	return repo.FileContent(path)
 257+}
 258+
 259 func (g *GitService) ListTags(name string) ([]*object.Tag, error) {
 260 	// TODO: handle nil
 261 	r := g.configRepo.GetByName(name)
 262diff --git a/templates/base.qtpl b/templates/base.qtpl
 263index 16b878014575bb4d5d6dc275b17774b1d63079c3..180b1ab428bc964511451c36569b198be2f5d3ee 100644
 264--- a/templates/base.qtpl
 265+++ b/templates/base.qtpl
 266@@ -30,6 +30,11 @@         return t.Format("2006-01-02")
 267     }
 268 %}
 269 
 270+{% code func Ignore[T any](v T, _ error) T {
 271+        return v
 272+    }
 273+%}
 274+
 275 Page prints a page implementing Page interface.
 276 {% func PageTemplate(p Page) %}
 277 <html lang="en">
 278diff --git a/templates/base.qtpl.go b/templates/base.qtpl.go
 279index 6d4d0a082e97a6f3bba19dc6a343ba313eabcc21..c5570c8ee4119c1861d86e15aec1f7e24a4a35a9 100644
 280--- a/templates/base.qtpl.go
 281+++ b/templates/base.qtpl.go
 282@@ -71,174 +71,179 @@ func TimeFormat(t time.Time) string {
 283 	return t.Format("2006-01-02")
 284 }
 285 
 286+//line base.qtpl:33
 287+func Ignore[T any](v T, _ error) T {
 288+	return v
 289+}
 290+
 291 // Page prints a page implementing Page interface.
 292 
 293-//line base.qtpl:34
 294+//line base.qtpl:39
 295 func StreamPageTemplate(qw422016 *qt422016.Writer, p Page) {
 296-//line base.qtpl:34
 297+//line base.qtpl:39
 298 	qw422016.N().S(`
 299 <html lang="en">
 300     <head>
 301         <meta charset="utf-8">
 302         <link rel="icon" href="data:,">
 303         <title>cerrado | `)
 304-//line base.qtpl:39
 305+//line base.qtpl:44
 306 	p.StreamTitle(qw422016)
 307-//line base.qtpl:39
 308+//line base.qtpl:44
 309 	qw422016.N().S(`</title> 
 310         <link rel="stylesheet" href="/static/main`)
 311-//line base.qtpl:40
 312+//line base.qtpl:45
 313 	qw422016.E().S(Slug)
 314-//line base.qtpl:40
 315+//line base.qtpl:45
 316 	qw422016.N().S(`.css">
 317         <meta name="viewport" content="width=device-width, initial-scale=1" />
 318     </head>
 319     <body>
 320         `)
 321-//line base.qtpl:44
 322+//line base.qtpl:49
 323 	p.StreamNavbar(qw422016)
 324-//line base.qtpl:44
 325+//line base.qtpl:49
 326 	qw422016.N().S(`
 327         <div class="container">
 328             `)
 329-//line base.qtpl:46
 330+//line base.qtpl:51
 331 	p.StreamContent(qw422016)
 332-//line base.qtpl:46
 333+//line base.qtpl:51
 334 	qw422016.N().S(`
 335         </div>
 336     </body>
 337     `)
 338-//line base.qtpl:49
 339+//line base.qtpl:54
 340 	p.StreamScript(qw422016)
 341-//line base.qtpl:49
 342+//line base.qtpl:54
 343 	qw422016.N().S(`
 344 </html>
 345 `)
 346-//line base.qtpl:51
 347+//line base.qtpl:56
 348 }
 349 
 350-//line base.qtpl:51
 351+//line base.qtpl:56
 352 func WritePageTemplate(qq422016 qtio422016.Writer, p Page) {
 353-//line base.qtpl:51
 354+//line base.qtpl:56
 355 	qw422016 := qt422016.AcquireWriter(qq422016)
 356-//line base.qtpl:51
 357+//line base.qtpl:56
 358 	StreamPageTemplate(qw422016, p)
 359-//line base.qtpl:51
 360+//line base.qtpl:56
 361 	qt422016.ReleaseWriter(qw422016)
 362-//line base.qtpl:51
 363+//line base.qtpl:56
 364 }
 365 
 366-//line base.qtpl:51
 367+//line base.qtpl:56
 368 func PageTemplate(p Page) string {
 369-//line base.qtpl:51
 370+//line base.qtpl:56
 371 	qb422016 := qt422016.AcquireByteBuffer()
 372-//line base.qtpl:51
 373+//line base.qtpl:56
 374 	WritePageTemplate(qb422016, p)
 375-//line base.qtpl:51
 376+//line base.qtpl:56
 377 	qs422016 := string(qb422016.B)
 378-//line base.qtpl:51
 379+//line base.qtpl:56
 380 	qt422016.ReleaseByteBuffer(qb422016)
 381-//line base.qtpl:51
 382+//line base.qtpl:56
 383 	return qs422016
 384-//line base.qtpl:51
 385+//line base.qtpl:56
 386 }
 387 
 388-//line base.qtpl:53
 389+//line base.qtpl:58
 390 type BasePage struct{}
 391 
 392-//line base.qtpl:54
 393+//line base.qtpl:59
 394 func (p *BasePage) StreamTitle(qw422016 *qt422016.Writer) {
 395-//line base.qtpl:54
 396+//line base.qtpl:59
 397 	qw422016.N().S(`Empty`)
 398-//line base.qtpl:54
 399+//line base.qtpl:59
 400 }
 401 
 402-//line base.qtpl:54
 403+//line base.qtpl:59
 404 func (p *BasePage) WriteTitle(qq422016 qtio422016.Writer) {
 405-//line base.qtpl:54
 406+//line base.qtpl:59
 407 	qw422016 := qt422016.AcquireWriter(qq422016)
 408-//line base.qtpl:54
 409+//line base.qtpl:59
 410 	p.StreamTitle(qw422016)
 411-//line base.qtpl:54
 412+//line base.qtpl:59
 413 	qt422016.ReleaseWriter(qw422016)
 414-//line base.qtpl:54
 415+//line base.qtpl:59
 416 }
 417 
 418-//line base.qtpl:54
 419+//line base.qtpl:59
 420 func (p *BasePage) Title() string {
 421-//line base.qtpl:54
 422+//line base.qtpl:59
 423 	qb422016 := qt422016.AcquireByteBuffer()
 424-//line base.qtpl:54
 425+//line base.qtpl:59
 426 	p.WriteTitle(qb422016)
 427-//line base.qtpl:54
 428+//line base.qtpl:59
 429 	qs422016 := string(qb422016.B)
 430-//line base.qtpl:54
 431+//line base.qtpl:59
 432 	qt422016.ReleaseByteBuffer(qb422016)
 433-//line base.qtpl:54
 434+//line base.qtpl:59
 435 	return qs422016
 436-//line base.qtpl:54
 437+//line base.qtpl:59
 438 }
 439 
 440-//line base.qtpl:55
 441+//line base.qtpl:60
 442 func (p *BasePage) StreamBody(qw422016 *qt422016.Writer) {
 443-//line base.qtpl:55
 444+//line base.qtpl:60
 445 	qw422016.N().S(`HelloWorld`)
 446-//line base.qtpl:55
 447+//line base.qtpl:60
 448 }
 449 
 450-//line base.qtpl:55
 451+//line base.qtpl:60
 452 func (p *BasePage) WriteBody(qq422016 qtio422016.Writer) {
 453-//line base.qtpl:55
 454+//line base.qtpl:60
 455 	qw422016 := qt422016.AcquireWriter(qq422016)
 456-//line base.qtpl:55
 457+//line base.qtpl:60
 458 	p.StreamBody(qw422016)
 459-//line base.qtpl:55
 460+//line base.qtpl:60
 461 	qt422016.ReleaseWriter(qw422016)
 462-//line base.qtpl:55
 463+//line base.qtpl:60
 464 }
 465 
 466-//line base.qtpl:55
 467+//line base.qtpl:60
 468 func (p *BasePage) Body() string {
 469-//line base.qtpl:55
 470+//line base.qtpl:60
 471 	qb422016 := qt422016.AcquireByteBuffer()
 472-//line base.qtpl:55
 473+//line base.qtpl:60
 474 	p.WriteBody(qb422016)
 475-//line base.qtpl:55
 476+//line base.qtpl:60
 477 	qs422016 := string(qb422016.B)
 478-//line base.qtpl:55
 479+//line base.qtpl:60
 480 	qt422016.ReleaseByteBuffer(qb422016)
 481-//line base.qtpl:55
 482+//line base.qtpl:60
 483 	return qs422016
 484-//line base.qtpl:55
 485+//line base.qtpl:60
 486 }
 487 
 488-//line base.qtpl:56
 489+//line base.qtpl:61
 490 func (p *BasePage) StreamScript(qw422016 *qt422016.Writer) {
 491-//line base.qtpl:56
 492+//line base.qtpl:61
 493 }
 494 
 495-//line base.qtpl:56
 496+//line base.qtpl:61
 497 func (p *BasePage) WriteScript(qq422016 qtio422016.Writer) {
 498-//line base.qtpl:56
 499+//line base.qtpl:61
 500 	qw422016 := qt422016.AcquireWriter(qq422016)
 501-//line base.qtpl:56
 502+//line base.qtpl:61
 503 	p.StreamScript(qw422016)
 504-//line base.qtpl:56
 505+//line base.qtpl:61
 506 	qt422016.ReleaseWriter(qw422016)
 507-//line base.qtpl:56
 508+//line base.qtpl:61
 509 }
 510 
 511-//line base.qtpl:56
 512+//line base.qtpl:61
 513 func (p *BasePage) Script() string {
 514-//line base.qtpl:56
 515+//line base.qtpl:61
 516 	qb422016 := qt422016.AcquireByteBuffer()
 517-//line base.qtpl:56
 518+//line base.qtpl:61
 519 	p.WriteScript(qb422016)
 520-//line base.qtpl:56
 521+//line base.qtpl:61
 522 	qs422016 := string(qb422016.B)
 523-//line base.qtpl:56
 524+//line base.qtpl:61
 525 	qt422016.ReleaseByteBuffer(qb422016)
 526-//line base.qtpl:56
 527+//line base.qtpl:61
 528 	return qs422016
 529-//line base.qtpl:56
 530+//line base.qtpl:61
 531 }
 532diff --git a/templates/gititemblob.qtpl b/templates/gititemblob.qtpl
 533new file mode 100644
 534index 0000000000000000000000000000000000000000..89d002d2521e4b56c31db6811d137186f830614e
 535--- /dev/null
 536+++ b/templates/gititemblob.qtpl
 537@@ -0,0 +1,14 @@
 538+{% code
 539+type GitItemBlobPage struct {
 540+    File string
 541+    Content []byte
 542+}
 543+%}
 544+
 545+{% func (g *GitItemBlobPage) Nav(name, ref string) %}{%= GitItemNav(name, ref, Tree) %}{% endfunc %}
 546+
 547+{% func (g *GitItemBlobPage) GitContent() %}
 548+<div class="code-view">
 549+{%z= g.Content %}
 550+</div>
 551+{% endfunc %}
 552diff --git a/templates/gititemblob.qtpl.go b/templates/gititemblob.qtpl.go
 553new file mode 100644
 554index 0000000000000000000000000000000000000000..6b4e1878145e79855875577f6edc748cd6b5ea1c
 555--- /dev/null
 556+++ b/templates/gititemblob.qtpl.go
 557@@ -0,0 +1,98 @@
 558+// Code generated by qtc from "gititemblob.qtpl". DO NOT EDIT.
 559+// See https://github.com/valyala/quicktemplate for details.
 560+
 561+//line gititemblob.qtpl:1
 562+package templates
 563+
 564+//line gititemblob.qtpl:1
 565+import (
 566+	qtio422016 "io"
 567+
 568+	qt422016 "github.com/valyala/quicktemplate"
 569+)
 570+
 571+//line gititemblob.qtpl:1
 572+var (
 573+	_ = qtio422016.Copy
 574+	_ = qt422016.AcquireByteBuffer
 575+)
 576+
 577+//line gititemblob.qtpl:2
 578+type GitItemBlobPage struct {
 579+	File    string
 580+	Content []byte
 581+}
 582+
 583+//line gititemblob.qtpl:8
 584+func (g *GitItemBlobPage) StreamNav(qw422016 *qt422016.Writer, name, ref string) {
 585+//line gititemblob.qtpl:8
 586+	StreamGitItemNav(qw422016, name, ref, Tree)
 587+//line gititemblob.qtpl:8
 588+}
 589+
 590+//line gititemblob.qtpl:8
 591+func (g *GitItemBlobPage) WriteNav(qq422016 qtio422016.Writer, name, ref string) {
 592+//line gititemblob.qtpl:8
 593+	qw422016 := qt422016.AcquireWriter(qq422016)
 594+//line gititemblob.qtpl:8
 595+	g.StreamNav(qw422016, name, ref)
 596+//line gititemblob.qtpl:8
 597+	qt422016.ReleaseWriter(qw422016)
 598+//line gititemblob.qtpl:8
 599+}
 600+
 601+//line gititemblob.qtpl:8
 602+func (g *GitItemBlobPage) Nav(name, ref string) string {
 603+//line gititemblob.qtpl:8
 604+	qb422016 := qt422016.AcquireByteBuffer()
 605+//line gititemblob.qtpl:8
 606+	g.WriteNav(qb422016, name, ref)
 607+//line gititemblob.qtpl:8
 608+	qs422016 := string(qb422016.B)
 609+//line gititemblob.qtpl:8
 610+	qt422016.ReleaseByteBuffer(qb422016)
 611+//line gititemblob.qtpl:8
 612+	return qs422016
 613+//line gititemblob.qtpl:8
 614+}
 615+
 616+//line gititemblob.qtpl:10
 617+func (g *GitItemBlobPage) StreamGitContent(qw422016 *qt422016.Writer) {
 618+//line gititemblob.qtpl:10
 619+	qw422016.N().S(`
 620+<div class="code-view">
 621+`)
 622+//line gititemblob.qtpl:12
 623+	qw422016.N().Z(g.Content)
 624+//line gititemblob.qtpl:12
 625+	qw422016.N().S(`
 626+</div>
 627+`)
 628+//line gititemblob.qtpl:14
 629+}
 630+
 631+//line gititemblob.qtpl:14
 632+func (g *GitItemBlobPage) WriteGitContent(qq422016 qtio422016.Writer) {
 633+//line gititemblob.qtpl:14
 634+	qw422016 := qt422016.AcquireWriter(qq422016)
 635+//line gititemblob.qtpl:14
 636+	g.StreamGitContent(qw422016)
 637+//line gititemblob.qtpl:14
 638+	qt422016.ReleaseWriter(qw422016)
 639+//line gititemblob.qtpl:14
 640+}
 641+
 642+//line gititemblob.qtpl:14
 643+func (g *GitItemBlobPage) GitContent() string {
 644+//line gititemblob.qtpl:14
 645+	qb422016 := qt422016.AcquireByteBuffer()
 646+//line gititemblob.qtpl:14
 647+	g.WriteGitContent(qb422016)
 648+//line gititemblob.qtpl:14
 649+	qs422016 := string(qb422016.B)
 650+//line gititemblob.qtpl:14
 651+	qt422016.ReleaseByteBuffer(qb422016)
 652+//line gititemblob.qtpl:14
 653+	return qs422016
 654+//line gititemblob.qtpl:14
 655+}
 656diff --git a/templates/gititemtree.qtpl b/templates/gititemtree.qtpl
 657index 5ace7b0ea7262684dcbb60ef67ce6f4249ff15e3..be7a27de050be204b7d90bac4fcffe073c44a921 100644
 658--- a/templates/gititemtree.qtpl
 659+++ b/templates/gititemtree.qtpl
 660@@ -1,10 +1,28 @@
 661+{% import "github.com/go-git/go-git/v5/plumbing/object" %}
 662+
 663 {% code
 664 type GitItemTreePage struct {
 665+    CurrentPath string
 666+    Tree *object.Tree
 667+
 668+    // TODO: remove this since it can be passed by GitCommit
 669+    Ref string
 670+    Name string
 671 }
 672 %}
 673 
 674 {% func (g *GitItemTreePage) Nav(name, ref string) %}{%= GitItemNav(name, ref, Tree) %}{% endfunc %}
 675 
 676 {% func (g *GitItemTreePage) GitContent() %}
 677-<h4>Tree</h4>
 678+{% for _, e := range g.Tree.Entries %}
 679+<div class="row">
 680+  <div class="col">{%s Ignore(e.Mode.ToOSFileMode()).String() %}</div>
 681+  {% if e.Mode.IsFile() %}
 682+  <div class="col-md"><a href="/{%s g.Name %}/blob/{%s g.Ref%}/{%s g.CurrentPath %}/{%s e.Name %}">{%s e.Name %}</a></div>
 683+  {% else %}
 684+  <div class="col-md"><a href="./{%s g.CurrentPath %}/{%s e.Name %}">{%s e.Name %}</a></div>
 685+  {% endif %}
 686+  <div class="col-md">{%dl Ignore(g.Tree.Size(e.Name))%} KiB</div>
 687+</div>
 688+{% endfor %}
 689 {% endfunc %}
 690diff --git a/templates/gititemtree.qtpl.go b/templates/gititemtree.qtpl.go
 691index d8beb0e09acb90fabbe5c2bb75de765952ca11a3..cdc374fef67dd51aedbd48aad507ea3ff515e48f 100644
 692--- a/templates/gititemtree.qtpl.go
 693+++ b/templates/gititemtree.qtpl.go
 694@@ -5,86 +5,165 @@ //line gititemtree.qtpl:1
 695 package templates
 696 
 697 //line gititemtree.qtpl:1
 698+import "github.com/go-git/go-git/v5/plumbing/object"
 699+
 700+//line gititemtree.qtpl:3
 701 import (
 702 	qtio422016 "io"
 703 
 704 	qt422016 "github.com/valyala/quicktemplate"
 705 )
 706 
 707-//line gititemtree.qtpl:1
 708+//line gititemtree.qtpl:3
 709 var (
 710 	_ = qtio422016.Copy
 711 	_ = qt422016.AcquireByteBuffer
 712 )
 713 
 714-//line gititemtree.qtpl:2
 715+//line gititemtree.qtpl:4
 716 type GitItemTreePage struct {
 717+	CurrentPath string
 718+	Tree        *object.Tree
 719+
 720+	// TODO: remove this since it can be passed by GitCommit
 721+	Ref  string
 722+	Name string
 723 }
 724 
 725-//line gititemtree.qtpl:6
 726+//line gititemtree.qtpl:14
 727 func (g *GitItemTreePage) StreamNav(qw422016 *qt422016.Writer, name, ref string) {
 728-//line gititemtree.qtpl:6
 729+//line gititemtree.qtpl:14
 730 	StreamGitItemNav(qw422016, name, ref, Tree)
 731-//line gititemtree.qtpl:6
 732+//line gititemtree.qtpl:14
 733 }
 734 
 735-//line gititemtree.qtpl:6
 736+//line gititemtree.qtpl:14
 737 func (g *GitItemTreePage) WriteNav(qq422016 qtio422016.Writer, name, ref string) {
 738-//line gititemtree.qtpl:6
 739+//line gititemtree.qtpl:14
 740 	qw422016 := qt422016.AcquireWriter(qq422016)
 741-//line gititemtree.qtpl:6
 742+//line gititemtree.qtpl:14
 743 	g.StreamNav(qw422016, name, ref)
 744-//line gititemtree.qtpl:6
 745+//line gititemtree.qtpl:14
 746 	qt422016.ReleaseWriter(qw422016)
 747-//line gititemtree.qtpl:6
 748+//line gititemtree.qtpl:14
 749 }
 750 
 751-//line gititemtree.qtpl:6
 752+//line gititemtree.qtpl:14
 753 func (g *GitItemTreePage) Nav(name, ref string) string {
 754-//line gititemtree.qtpl:6
 755+//line gititemtree.qtpl:14
 756 	qb422016 := qt422016.AcquireByteBuffer()
 757-//line gititemtree.qtpl:6
 758+//line gititemtree.qtpl:14
 759 	g.WriteNav(qb422016, name, ref)
 760-//line gititemtree.qtpl:6
 761+//line gititemtree.qtpl:14
 762 	qs422016 := string(qb422016.B)
 763-//line gititemtree.qtpl:6
 764+//line gititemtree.qtpl:14
 765 	qt422016.ReleaseByteBuffer(qb422016)
 766-//line gititemtree.qtpl:6
 767+//line gititemtree.qtpl:14
 768 	return qs422016
 769-//line gititemtree.qtpl:6
 770+//line gititemtree.qtpl:14
 771 }
 772 
 773-//line gititemtree.qtpl:8
 774+//line gititemtree.qtpl:16
 775 func (g *GitItemTreePage) StreamGitContent(qw422016 *qt422016.Writer) {
 776-//line gititemtree.qtpl:8
 777+//line gititemtree.qtpl:16
 778+	qw422016.N().S(`
 779+`)
 780+//line gititemtree.qtpl:17
 781+	for _, e := range g.Tree.Entries {
 782+//line gititemtree.qtpl:17
 783+		qw422016.N().S(`
 784+<div class="row">
 785+  <div class="col">`)
 786+//line gititemtree.qtpl:19
 787+		qw422016.E().S(Ignore(e.Mode.ToOSFileMode()).String())
 788+//line gititemtree.qtpl:19
 789+		qw422016.N().S(`</div>
 790+  `)
 791+//line gititemtree.qtpl:20
 792+		if e.Mode.IsFile() {
 793+//line gititemtree.qtpl:20
 794+			qw422016.N().S(`
 795+  <div class="col-md"><a href="/`)
 796+//line gititemtree.qtpl:21
 797+			qw422016.E().S(g.Name)
 798+//line gititemtree.qtpl:21
 799+			qw422016.N().S(`/blob/`)
 800+//line gititemtree.qtpl:21
 801+			qw422016.E().S(g.Ref)
 802+//line gititemtree.qtpl:21
 803+			qw422016.N().S(`/`)
 804+//line gititemtree.qtpl:21
 805+			qw422016.E().S(g.CurrentPath)
 806+//line gititemtree.qtpl:21
 807+			qw422016.N().S(`/`)
 808+//line gititemtree.qtpl:21
 809+			qw422016.E().S(e.Name)
 810+//line gititemtree.qtpl:21
 811+			qw422016.N().S(`">`)
 812+//line gititemtree.qtpl:21
 813+			qw422016.E().S(e.Name)
 814+//line gititemtree.qtpl:21
 815+			qw422016.N().S(`</a></div>
 816+  `)
 817+//line gititemtree.qtpl:22
 818+		} else {
 819+//line gititemtree.qtpl:22
 820+			qw422016.N().S(`
 821+  <div class="col-md"><a href="./`)
 822+//line gititemtree.qtpl:23
 823+			qw422016.E().S(g.CurrentPath)
 824+//line gititemtree.qtpl:23
 825+			qw422016.N().S(`/`)
 826+//line gititemtree.qtpl:23
 827+			qw422016.E().S(e.Name)
 828+//line gititemtree.qtpl:23
 829+			qw422016.N().S(`">`)
 830+//line gititemtree.qtpl:23
 831+			qw422016.E().S(e.Name)
 832+//line gititemtree.qtpl:23
 833+			qw422016.N().S(`</a></div>
 834+  `)
 835+//line gititemtree.qtpl:24
 836+		}
 837+//line gititemtree.qtpl:24
 838+		qw422016.N().S(`
 839+  <div class="col-md">`)
 840+//line gititemtree.qtpl:25
 841+		qw422016.N().DL(Ignore(g.Tree.Size(e.Name)))
 842+//line gititemtree.qtpl:25
 843+		qw422016.N().S(` KiB</div>
 844+</div>
 845+`)
 846+//line gititemtree.qtpl:27
 847+	}
 848+//line gititemtree.qtpl:27
 849 	qw422016.N().S(`
 850-<h4>Tree</h4>
 851 `)
 852-//line gititemtree.qtpl:10
 853+//line gititemtree.qtpl:28
 854 }
 855 
 856-//line gititemtree.qtpl:10
 857+//line gititemtree.qtpl:28
 858 func (g *GitItemTreePage) WriteGitContent(qq422016 qtio422016.Writer) {
 859-//line gititemtree.qtpl:10
 860+//line gititemtree.qtpl:28
 861 	qw422016 := qt422016.AcquireWriter(qq422016)
 862-//line gititemtree.qtpl:10
 863+//line gititemtree.qtpl:28
 864 	g.StreamGitContent(qw422016)
 865-//line gititemtree.qtpl:10
 866+//line gititemtree.qtpl:28
 867 	qt422016.ReleaseWriter(qw422016)
 868-//line gititemtree.qtpl:10
 869+//line gititemtree.qtpl:28
 870 }
 871 
 872-//line gititemtree.qtpl:10
 873+//line gititemtree.qtpl:28
 874 func (g *GitItemTreePage) GitContent() string {
 875-//line gititemtree.qtpl:10
 876+//line gititemtree.qtpl:28
 877 	qb422016 := qt422016.AcquireByteBuffer()
 878-//line gititemtree.qtpl:10
 879+//line gititemtree.qtpl:28
 880 	g.WriteGitContent(qb422016)
 881-//line gititemtree.qtpl:10
 882+//line gititemtree.qtpl:28
 883 	qs422016 := string(qb422016.B)
 884-//line gititemtree.qtpl:10
 885+//line gititemtree.qtpl:28
 886 	qt422016.ReleaseByteBuffer(qb422016)
 887-//line gititemtree.qtpl:10
 888+//line gititemtree.qtpl:28
 889 	return qs422016
 890-//line gititemtree.qtpl:10
 891+//line gititemtree.qtpl:28
 892 }
 893diff --git a/templates/navbar.qtpl b/templates/navbar.qtpl
 894index 775f496a4b52da8eef28656dedee80fe9995871d..9681fa4a5fd873e89621c1cdad5151f49661716b 100644
 895--- a/templates/navbar.qtpl
 896+++ b/templates/navbar.qtpl
 897@@ -25,7 +25,10 @@ {% func Navbar (s Selection) %}
 898         <nav class="container navbar navbar-expand">
 899           <div class="navbar-nav">
 900             <a class="nav-link{%= insertIfEqual(s, Git) %}" href="/">git</a>
 901+{% comment %}
 902+Add this back once needed
 903             <a class="nav-link{%= insertIfEqual(s, List) %}" href="/list">list</a>
 904+{% endcomment %}
 905             <a class="nav-link{%= insertIfEqual(s, About) %}" href="/about">about</a>
 906             <a class="nav-link{%= insertIfEqual(s, Config) %}" href="/config">config</a>
 907           </div>
 908diff --git a/templates/navbar.qtpl.go b/templates/navbar.qtpl.go
 909index cddc6a6de17621590eb849a2ef2a27b84c661715..a2989db1381d0e7ff34c0499825bc27a2b303271 100644
 910--- a/templates/navbar.qtpl.go
 911+++ b/templates/navbar.qtpl.go
 912@@ -86,167 +86,165 @@ //line navbar.qtpl:27
 913 	streaminsertIfEqual(qw422016, s, Git)
 914 //line navbar.qtpl:27
 915 	qw422016.N().S(`" href="/">git</a>
 916+`)
 917+//line navbar.qtpl:31
 918+	qw422016.N().S(`
 919             <a class="nav-link`)
 920-//line navbar.qtpl:28
 921-	streaminsertIfEqual(qw422016, s, List)
 922-//line navbar.qtpl:28
 923-	qw422016.N().S(`" href="/list">list</a>
 924-            <a class="nav-link`)
 925-//line navbar.qtpl:29
 926+//line navbar.qtpl:32
 927 	streaminsertIfEqual(qw422016, s, About)
 928-//line navbar.qtpl:29
 929+//line navbar.qtpl:32
 930 	qw422016.N().S(`" href="/about">about</a>
 931             <a class="nav-link`)
 932-//line navbar.qtpl:30
 933+//line navbar.qtpl:33
 934 	streaminsertIfEqual(qw422016, s, Config)
 935-//line navbar.qtpl:30
 936+//line navbar.qtpl:33
 937 	qw422016.N().S(`" href="/config">config</a>
 938           </div>
 939         </nav>
 940 `)
 941-//line navbar.qtpl:33
 942+//line navbar.qtpl:36
 943 }
 944 
 945-//line navbar.qtpl:33
 946+//line navbar.qtpl:36
 947 func WriteNavbar(qq422016 qtio422016.Writer, s Selection) {
 948-//line navbar.qtpl:33
 949+//line navbar.qtpl:36
 950 	qw422016 := qt422016.AcquireWriter(qq422016)
 951-//line navbar.qtpl:33
 952+//line navbar.qtpl:36
 953 	StreamNavbar(qw422016, s)
 954-//line navbar.qtpl:33
 955+//line navbar.qtpl:36
 956 	qt422016.ReleaseWriter(qw422016)
 957-//line navbar.qtpl:33
 958+//line navbar.qtpl:36
 959 }
 960 
 961-//line navbar.qtpl:33
 962+//line navbar.qtpl:36
 963 func Navbar(s Selection) string {
 964-//line navbar.qtpl:33
 965+//line navbar.qtpl:36
 966 	qb422016 := qt422016.AcquireByteBuffer()
 967-//line navbar.qtpl:33
 968+//line navbar.qtpl:36
 969 	WriteNavbar(qb422016, s)
 970-//line navbar.qtpl:33
 971+//line navbar.qtpl:36
 972 	qs422016 := string(qb422016.B)
 973-//line navbar.qtpl:33
 974+//line navbar.qtpl:36
 975 	qt422016.ReleaseByteBuffer(qb422016)
 976-//line navbar.qtpl:33
 977+//line navbar.qtpl:36
 978 	return qs422016
 979-//line navbar.qtpl:33
 980+//line navbar.qtpl:36
 981 }
 982 
 983-//line navbar.qtpl:35
 984+//line navbar.qtpl:38
 985 func StreamGitItemNav(qw422016 *qt422016.Writer, name, ref string, s GitSelection) {
 986-//line navbar.qtpl:35
 987+//line navbar.qtpl:38
 988 	qw422016.N().S(`
 989 <div class="row">
 990     <h3>`)
 991-//line navbar.qtpl:37
 992+//line navbar.qtpl:40
 993 	qw422016.E().S(name)
 994-//line navbar.qtpl:37
 995+//line navbar.qtpl:40
 996 	qw422016.N().S(` `)
 997-//line navbar.qtpl:37
 998+//line navbar.qtpl:40
 999 	if ref != "" && (s == Log || s == Tree) {
1000-//line navbar.qtpl:37
1001+//line navbar.qtpl:40
1002 		qw422016.N().S(`@ `)
1003-//line navbar.qtpl:37
1004+//line navbar.qtpl:40
1005 		qw422016.E().S(ref)
1006-//line navbar.qtpl:37
1007+//line navbar.qtpl:40
1008 	}
1009-//line navbar.qtpl:37
1010+//line navbar.qtpl:40
1011 	qw422016.N().S(`</h3>
1012 </div>
1013 <div class="row">
1014   <ul class="nav">
1015     <li class="nav-item">
1016       <a class="nav-link`)
1017-//line navbar.qtpl:42
1018-	streaminsertIfEqual(qw422016, s, Readme)
1019-//line navbar.qtpl:42
1020-	qw422016.N().S(`" aria-current="page" href="/`)
1021-//line navbar.qtpl:42
1022-	qw422016.E().S(name)
1023-//line navbar.qtpl:42
1024-	qw422016.N().S(`/about">about</a>
1025-    </li>
1026-    <li class="nav-item">
1027-      <a class="nav-link`)
1028 //line navbar.qtpl:45
1029-	streaminsertIfEqual(qw422016, s, Log)
1030+	streaminsertIfEqual(qw422016, s, Readme)
1031 //line navbar.qtpl:45
1032 	qw422016.N().S(`" aria-current="page" href="/`)
1033 //line navbar.qtpl:45
1034 	qw422016.E().S(name)
1035 //line navbar.qtpl:45
1036-	qw422016.N().S(`/log/`)
1037-//line navbar.qtpl:45
1038-	qw422016.E().S(ref)
1039-//line navbar.qtpl:45
1040-	qw422016.N().S(`">log</a>
1041+	qw422016.N().S(`/about">about</a>
1042     </li>
1043     <li class="nav-item">
1044       <a class="nav-link`)
1045 //line navbar.qtpl:48
1046-	streaminsertIfEqual(qw422016, s, Summary)
1047+	streaminsertIfEqual(qw422016, s, Log)
1048 //line navbar.qtpl:48
1049 	qw422016.N().S(`" aria-current="page" href="/`)
1050 //line navbar.qtpl:48
1051 	qw422016.E().S(name)
1052 //line navbar.qtpl:48
1053-	qw422016.N().S(`">summary</a>
1054+	qw422016.N().S(`/log/`)
1055+//line navbar.qtpl:48
1056+	qw422016.E().S(ref)
1057+//line navbar.qtpl:48
1058+	qw422016.N().S(`">log</a>
1059     </li>
1060     <li class="nav-item">
1061       <a class="nav-link`)
1062 //line navbar.qtpl:51
1063-	streaminsertIfEqual(qw422016, s, Refs)
1064+	streaminsertIfEqual(qw422016, s, Summary)
1065 //line navbar.qtpl:51
1066 	qw422016.N().S(`" aria-current="page" href="/`)
1067 //line navbar.qtpl:51
1068 	qw422016.E().S(name)
1069 //line navbar.qtpl:51
1070-	qw422016.N().S(`/refs">refs</a>
1071+	qw422016.N().S(`">summary</a>
1072     </li>
1073     <li class="nav-item">
1074       <a class="nav-link`)
1075 //line navbar.qtpl:54
1076-	streaminsertIfEqual(qw422016, s, Tree)
1077+	streaminsertIfEqual(qw422016, s, Refs)
1078 //line navbar.qtpl:54
1079 	qw422016.N().S(`" aria-current="page" href="/`)
1080 //line navbar.qtpl:54
1081 	qw422016.E().S(name)
1082 //line navbar.qtpl:54
1083+	qw422016.N().S(`/refs">refs</a>
1084+    </li>
1085+    <li class="nav-item">
1086+      <a class="nav-link`)
1087+//line navbar.qtpl:57
1088+	streaminsertIfEqual(qw422016, s, Tree)
1089+//line navbar.qtpl:57
1090+	qw422016.N().S(`" aria-current="page" href="/`)
1091+//line navbar.qtpl:57
1092+	qw422016.E().S(name)
1093+//line navbar.qtpl:57
1094 	qw422016.N().S(`/tree/`)
1095-//line navbar.qtpl:54
1096+//line navbar.qtpl:57
1097 	qw422016.E().S(ref)
1098-//line navbar.qtpl:54
1099+//line navbar.qtpl:57
1100 	qw422016.N().S(`">tree</a>
1101     </li>
1102   </ul>
1103 </div>
1104 `)
1105-//line navbar.qtpl:58
1106+//line navbar.qtpl:61
1107 }
1108 
1109-//line navbar.qtpl:58
1110+//line navbar.qtpl:61
1111 func WriteGitItemNav(qq422016 qtio422016.Writer, name, ref string, s GitSelection) {
1112-//line navbar.qtpl:58
1113+//line navbar.qtpl:61
1114 	qw422016 := qt422016.AcquireWriter(qq422016)
1115-//line navbar.qtpl:58
1116+//line navbar.qtpl:61
1117 	StreamGitItemNav(qw422016, name, ref, s)
1118-//line navbar.qtpl:58
1119+//line navbar.qtpl:61
1120 	qt422016.ReleaseWriter(qw422016)
1121-//line navbar.qtpl:58
1122+//line navbar.qtpl:61
1123 }
1124 
1125-//line navbar.qtpl:58
1126+//line navbar.qtpl:61
1127 func GitItemNav(name, ref string, s GitSelection) string {
1128-//line navbar.qtpl:58
1129+//line navbar.qtpl:61
1130 	qb422016 := qt422016.AcquireByteBuffer()
1131-//line navbar.qtpl:58
1132+//line navbar.qtpl:61
1133 	WriteGitItemNav(qb422016, name, ref, s)
1134-//line navbar.qtpl:58
1135+//line navbar.qtpl:61
1136 	qs422016 := string(qb422016.B)
1137-//line navbar.qtpl:58
1138+//line navbar.qtpl:61
1139 	qt422016.ReleaseByteBuffer(qb422016)
1140-//line navbar.qtpl:58
1141+//line navbar.qtpl:61
1142 	return qs422016
1143-//line navbar.qtpl:58
1144+//line navbar.qtpl:61
1145 }