all repos — elgit @ 74eea2ebbccc8458b5b62002d281b2b49f5c30c4

fork of legit: web frontend for git, written in go

switch to gomponents

Alan Pearce
commit

74eea2ebbccc8458b5b62002d281b2b49f5c30c4

parent

776a7b0dafa651c5ba71381608e9726d9756c3b1

M config.yamlconfig.yaml
@@ -9,7 +9,6 @@ mainBranch:
- master - main dirs: - templates: ./templates static: ./static meta: title: icy does git
M config/config.goconfig/config.go
@@ -8,28 +8,35 @@
"gopkg.in/yaml.v3" ) +type Repo struct { + ScanPath string `yaml:"scanPath"` + Readme []string `yaml:"readme"` + MainBranch []string `yaml:"mainBranch"` + Ignore []string `yaml:"ignore,omitempty"` + Unlisted []string `yaml:"unlisted,omitempty"` +} + +type Dirs struct { + Static string `yaml:"static"` +} + +type Meta struct { + Title string `yaml:"title"` + Description string `yaml:"description"` + SyntaxHighlight string `yaml:"syntaxHighlight"` +} + +type Server struct { + Name string `yaml:"name,omitempty"` + Host string `yaml:"host"` + Port int `yaml:"port"` +} + type Config struct { - Repo struct { - ScanPath string `yaml:"scanPath"` - Readme []string `yaml:"readme"` - MainBranch []string `yaml:"mainBranch"` - Ignore []string `yaml:"ignore,omitempty"` - Unlisted []string `yaml:"unlisted,omitempty"` - } `yaml:"repo"` - Dirs struct { - Templates string `yaml:"templates"` - Static string `yaml:"static"` - } `yaml:"dirs"` - Meta struct { - Title string `yaml:"title"` - Description string `yaml:"description"` - SyntaxHighlight string `yaml:"syntaxHighlight"` - } `yaml:"meta"` - Server struct { - Name string `yaml:"name,omitempty"` - Host string `yaml:"host"` - Port int `yaml:"port"` - } `yaml:"server"` + Repo Repo `yaml:"repo"` + Dirs Dirs `yaml:"dirs"` + Meta Meta `yaml:"meta"` + Server Server `yaml:"server"` } func Read(f string) (*Config, error) {
@@ -44,9 +51,6 @@ return nil, fmt.Errorf("parsing config: %w", err)
} if c.Repo.ScanPath, err = filepath.Abs(c.Repo.ScanPath); err != nil { - return nil, err - } - if c.Dirs.Templates, err = filepath.Abs(c.Dirs.Templates); err != nil { return nil, err } if c.Dirs.Static, err = filepath.Abs(c.Dirs.Static); err != nil {
M git/diff.gogit/diff.go
@@ -25,20 +25,24 @@ IsNew bool
IsDelete bool } +type Commit struct { + Message string + Author object.Signature + This string + Parent string +} + +type Stat struct { + FilesChanged int + Insertions int + Deletions int +} + // A nicer git diff representation. type NiceDiff struct { - Commit struct { - Message string - Author object.Signature - This string - Parent string - } - Stat struct { - FilesChanged int - Insertions int - Deletions int - } - Diff []Diff + Commit Commit + Stat Stat + Diff []Diff } func (g *GitRepo) Diff() (*NiceDiff, error) {
M go.modgo.mod
@@ -32,6 +32,7 @@ github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/sergi/go-diff v1.3.1 // indirect github.com/skeema/knownhosts v1.3.1 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect + go.alanpearce.eu/gomponents v1.4.0 // indirect golang.org/x/crypto v0.33.0 // indirect golang.org/x/net v0.35.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
M go.sumgo.sum
@@ -106,6 +106,8 @@ github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.alanpearce.eu/gomponents v1.4.0 h1:Ibvoce+U0rnPKlDOE+wXDbNniNQL8mYO667+qS5J1Go= +go.alanpearce.eu/gomponents v1.4.0/go.mod h1:WxD+6FRSvwThQOzV0r6zPAA9CRb41lutUZMSC7r6BRc= golang.org/x/arch v0.1.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
M main.gomain.go
@@ -23,7 +23,6 @@
if err := UnveilPaths([]string{ c.Dirs.Static, c.Repo.ScanPath, - c.Dirs.Templates, }, "r"); err != nil { log.Fatalf("unveil: %s", err)
M readmereadme
@@ -4,6 +4,9 @@
A git web frontend written in Go, forked from elgit after the author moved on. +FORK CHANGES +• Uses gomponents instead of html/template. + FEATURES • Cloning over http(s).
@@ -37,7 +40,6 @@ ignore:
- foo - bar dirs: - templates: ./templates static: ./static meta: title: git good
@@ -52,7 +54,7 @@ These options are fairly self-explanatory, but of note are:
• repo.scanPath: where all your git repos live (or die). elgit doesn't traverse subdirs yet. -• dirs: use this to override the default templates and static assets. +• dirs: use this to override the static assets. • repo.readme: readme files to look for. • repo.mainBranch: main branch names to look for. • repo.ignore: repos to ignore, relative to scanPath.
M routes/routes.goroutes/routes.go
@@ -3,7 +3,6 @@
import ( "compress/gzip" "fmt" - "html/template" "log" "net/http" "os"
@@ -19,6 +18,7 @@ "github.com/microcosm-cc/bluemonday"
"github.com/russross/blackfriday/v2" "go.alanpearce.eu/elgit/config" "go.alanpearce.eu/elgit/git" + "go.alanpearce.eu/elgit/templates" ) type deps struct {
@@ -79,14 +79,23 @@ sort.Slice(infos, func(i, j int) bool {
return infos[j].d.Before(infos[i].d) }) - tpath := filepath.Join(d.c.Dirs.Templates, "*") - t := template.Must(template.ParseGlob(tpath)) + // Convert to the format expected by the templates package + repoInfos := make([]templates.RepoInfo, len(infos)) + for i, info := range infos { + repoInfos[i] = templates.RepoInfo{ + DisplayName: info.DisplayName, + Name: info.Name, + Desc: info.Desc, + Idle: info.Idle, + LastCommit: info.d, + } + } - data := make(map[string]interface{}) - data["meta"] = d.c.Meta - data["info"] = infos + pageData := templates.PageData{ + Meta: d.c.Meta, + } - if err := t.ExecuteTemplate(w, "index", data); err != nil { + if err := templates.Index(pageData, repoInfos).Render(w); err != nil { log.Println(err) return }
@@ -119,7 +128,7 @@ log.Println(err)
return } - var readmeContent template.HTML + var readmeContent string for _, readme := range d.c.Repo.Readme { ext := filepath.Ext(readme) content, _ := gr.FileContent(readme)
@@ -131,12 +140,10 @@ []byte(content),
blackfriday.WithExtensions(blackfriday.CommonExtensions), ) html := bluemonday.UGCPolicy().SanitizeBytes(unsafe) - readmeContent = template.HTML(html) + readmeContent = string(html) default: safe := bluemonday.UGCPolicy().SanitizeBytes([]byte(content)) - readmeContent = template.HTML( - fmt.Sprintf(`<pre>%s</pre>`, safe), - ) + readmeContent = fmt.Sprintf(`<pre>%s</pre>`, safe) } break }
@@ -153,25 +160,21 @@ log.Println(err)
return } - tpath := filepath.Join(d.c.Dirs.Templates, "*") - t := template.Must(template.ParseGlob(tpath)) - if len(commits) >= 3 { commits = commits[:3] } - data := make(map[string]any) - data["name"] = name - data["displayname"] = getDisplayName(name) - data["ref"] = mainBranch - data["readme"] = readmeContent - data["commits"] = commits - data["desc"] = getDescription(path) - data["servername"] = d.c.Server.Name - data["meta"] = d.c.Meta - data["gomod"] = isGoModule(gr) + pageData := templates.PageData{ + Meta: d.c.Meta, + Name: name, + DisplayName: getDisplayName(name), + Ref: mainBranch, + Description: getDescription(path), + Servername: d.c.Server.Name, + Gomod: isGoModule(gr), + } - if err := t.ExecuteTemplate(w, "repo", data); err != nil { + if err := templates.Repo(pageData, commits, readmeContent).Render(w); err != nil { log.Println(err) } }
@@ -352,19 +355,16 @@ log.Println(err)
return } - tpath := filepath.Join(d.c.Dirs.Templates, "*") - t := template.Must(template.ParseGlob(tpath)) - - data := make(map[string]interface{}) - data["commits"] = commits - data["meta"] = d.c.Meta - data["name"] = name - data["displayname"] = getDisplayName(name) - data["ref"] = ref - data["desc"] = getDescription(path) - data["log"] = true + pageData := templates.PageData{ + Meta: d.c.Meta, + Name: name, + DisplayName: getDisplayName(name), + Ref: ref, + Description: getDescription(path), + Log: true, + } - if err := t.ExecuteTemplate(w, "log", data); err != nil { + if err := templates.Log(pageData, commits).Render(w); err != nil { log.Println(err) return }
@@ -397,21 +397,17 @@ log.Println(err)
return } - tpath := filepath.Join(d.c.Dirs.Templates, "*") - t := template.Must(template.ParseGlob(tpath)) - - data := make(map[string]interface{}) + pageData := templates.PageData{ + Meta: d.c.Meta, + Name: name, + Stat: diff.Stat, + Diff: diff.Diff, + DisplayName: getDisplayName(name), + Ref: ref, + Description: getDescription(path), + } - data["commit"] = diff.Commit - data["stat"] = diff.Stat - data["diff"] = diff.Diff - data["meta"] = d.c.Meta - data["name"] = name - data["displayname"] = getDisplayName(name) - data["ref"] = ref - data["desc"] = getDescription(path) - - if err := t.ExecuteTemplate(w, "commit", data); err != nil { + if err := templates.Commit(pageData, diff).Render(w); err != nil { log.Println(err) return }
@@ -450,19 +446,14 @@ d.Write500(w)
return } - tpath := filepath.Join(d.c.Dirs.Templates, "*") - t := template.Must(template.ParseGlob(tpath)) + pageData := templates.PageData{ + Meta: d.c.Meta, + Name: name, + DisplayName: getDisplayName(name), + Description: getDescription(path), + } - data := make(map[string]interface{}) - - data["meta"] = d.c.Meta - data["name"] = name - data["displayname"] = getDisplayName(name) - data["branches"] = branches - data["tags"] = tags - data["desc"] = getDescription(path) - - if err := t.ExecuteTemplate(w, "refs", data); err != nil { + if err := templates.Refs(pageData, branches, tags).Render(w); err != nil { log.Println(err) return }
M routes/template.goroutes/template.go
@@ -2,45 +2,63 @@ package routes
import ( "bytes" - "html/template" "io" "log" "net/http" - "path/filepath" "strings" "github.com/alecthomas/chroma/v2/formatters/html" "github.com/alecthomas/chroma/v2/lexers" "github.com/alecthomas/chroma/v2/styles" "go.alanpearce.eu/elgit/git" + "go.alanpearce.eu/elgit/templates" ) func (d *deps) Write404(w http.ResponseWriter) { - tpath := filepath.Join(d.c.Dirs.Templates, "*") - t := template.Must(template.ParseGlob(tpath)) - w.WriteHeader(404) - if err := t.ExecuteTemplate(w, "404", nil); err != nil { + w.WriteHeader(http.StatusNotFound) + data := templates.PageData{ + Meta: d.c.Meta, + } + if err := templates.NotFound(data).Render(w); err != nil { log.Printf("404 template: %s", err) } } func (d *deps) Write500(w http.ResponseWriter) { - tpath := filepath.Join(d.c.Dirs.Templates, "*") - t := template.Must(template.ParseGlob(tpath)) - w.WriteHeader(500) - if err := t.ExecuteTemplate(w, "500", nil); err != nil { + w.WriteHeader(http.StatusInternalServerError) + data := templates.PageData{ + Meta: d.c.Meta, + } + if err := templates.ServerError(data).Render(w); err != nil { log.Printf("500 template: %s", err) } } func (d *deps) listFiles(files []git.NiceTree, data map[string]any, w http.ResponseWriter) { - tpath := filepath.Join(d.c.Dirs.Templates, "*") - t := template.Must(template.ParseGlob(tpath)) + pageData := templates.PageData{ + Meta: d.c.Meta, + DisplayName: getDisplayName(data["name"].(string)), + Name: data["name"].(string), + Ref: data["ref"].(string), + Description: data["desc"].(string), + Parent: "", + } + + if parent, ok := data["parent"]; ok && parent != nil { + pageData.Parent = parent.(string) + } - data["files"] = files - data["meta"] = d.c.Meta + readme := "" + if readmeContent, ok := data["readme"]; ok && readmeContent != nil { + readme = readmeContent.(string) + } - if err := t.ExecuteTemplate(w, "tree", data); err != nil { + dotdot := "" + if dotdotPath, ok := data["dotdot"]; ok && dotdotPath != nil { + dotdot = dotdotPath.(string) + } + + if err := templates.Tree(pageData, files, readme, dotdot).Render(w); err != nil { log.Println(err) return }
@@ -77,9 +95,6 @@ name, content string,
data map[string]any, w http.ResponseWriter, ) { - tpath := filepath.Join(d.c.Dirs.Templates, "*") - t := template.Must(template.ParseGlob(tpath)) - lexer := lexers.Get(name) if lexer == nil { lexer = lexers.Get(".txt")
@@ -108,20 +123,23 @@ d.Write500(w)
return } - data["content"] = template.HTML(code.String()) - data["meta"] = d.c.Meta - data["chroma"] = true + pageData := templates.PageData{ + Meta: d.c.Meta, + DisplayName: getDisplayName(data["name"].(string)), + Content: code.String(), + Name: data["name"].(string), + Ref: data["ref"].(string), + Description: data["desc"].(string), + Path: data["path"].(string), + } - if err := t.ExecuteTemplate(w, "file", data); err != nil { + if err := templates.File(pageData, true).Render(w); err != nil { log.Println(err) return } } func (d *deps) showFile(content string, data map[string]any, w http.ResponseWriter) { - tpath := filepath.Join(d.c.Dirs.Templates, "*") - t := template.Must(template.ParseGlob(tpath)) - lc, err := countLines(strings.NewReader(content)) if err != nil { // Non-fatal, we'll just skip showing line numbers in the template.
@@ -135,12 +153,18 @@ lines[i] = i + 1
} } - data["linecount"] = lines - data["content"] = content - data["meta"] = d.c.Meta - data["chroma"] = false + pageData := templates.PageData{ + Meta: d.c.Meta, + DisplayName: getDisplayName(data["name"].(string)), + LineCount: lines, + Content: content, + Name: data["name"].(string), + Ref: data["ref"].(string), + Description: data["desc"].(string), + Path: data["path"].(string), + } - if err := t.ExecuteTemplate(w, "file", data); err != nil { + if err := templates.File(pageData, false).Render(w); err != nil { log.Println(err) return }
D templates/404.html
@@ -1,14 +0,0 @@
-{{ define "404" }} -<!DOCTYPE html> -<html> - <title>404</title> -{{ template "head" . }} - <body> - {{ template "nav" . }} - <main> - <h3>404 &mdash; nothing like that here.</h3> - </main> - </body> - -</html> -{{ end }}
D templates/500.html
@@ -1,14 +0,0 @@
-{{ define "500" }} -<!DOCTYPE html> -<html> - <title>500</title> -{{ template "head" . }} - <body> - {{ template "nav" . }} - <main> - <h3>500 &mdash; something broke!</h3> - </main> - </body> - -</html> -{{ end }}
A templates/commit.go
@@ -0,0 +1,111 @@
+package templates + +import ( + "fmt" + + "github.com/bluekeyes/go-gitdiff/gitdiff" + "go.alanpearce.eu/elgit/git" + g "go.alanpearce.eu/gomponents" + . "go.alanpearce.eu/gomponents/html" +) + +// Commit renders a commit diff page +func Commit(data PageData, diff *git.NiceDiff) g.Node { + return Page(data, []g.Node{ + RepoHeader(data), + RenderNav(data), + Main( + Section(Class("commit"), + Pre(g.Text(diff.Commit.Message)), + Div(Class("commit-info"), + g.Text(diff.Commit.Author.Name+" "), + A(Href(fmt.Sprintf("mailto:%s", diff.Commit.Author.Email)), + Class("commit-email"), + g.Text(diff.Commit.Author.Email)), + Div(g.Text(diff.Commit.Author.When.Format("Mon, 02 Jan 2006 15:04:05 -0700"))), + ), + Div( + Strong(g.Text("commit")), + P(A(Href(fmt.Sprintf("/%s/commit/%s", data.Name, diff.Commit.This)), + Class("commit-hash"), + g.Text(diff.Commit.This))), + ), + g.If(diff.Commit.Parent != "", + Div( + Strong(g.Text("parent")), + P(A(Href(fmt.Sprintf("/%s/commit/%s", data.Name, diff.Commit.Parent)), + Class("commit-hash"), + g.Text(diff.Commit.Parent))), + ), + ), + Div(Class("diff-stat"), + Div(g.Textf("%d files changed, %d insertions(+), %d deletions(-)", + diff.Stat.FilesChanged, diff.Stat.Insertions, diff.Stat.Deletions)), + Div( + Br(), + Strong(g.Text("jump to")), + Ul(g.Map(diff.Diff, func(d git.Diff) g.Node { + return Li(A(Href("#"+d.Name.New), g.Text(d.Name.New))) + })), + ), + ), + ), + Section( + g.Map(diff.Diff, func(d git.Diff) g.Node { + return Div(ID(d.Name.New), + Div(Class("diff"), + g.If(d.IsNew, + Span(Class("diff-type"), g.Text("A")), + g.If(d.IsDelete, + Span(Class("diff-type"), g.Text("D")), + Span(Class("diff-type"), g.Text("M")), + ), + ), + g.Text(" "), + g.If(d.Name.Old != "", + g.Group{ + A(Href(fmt.Sprintf("/%s/blob/%s/%s", data.Name, diff.Commit.Parent, d.Name.Old)), + g.Text(d.Name.Old)), + g.If(d.Name.New != "", + g.Group{ + g.Text(" → "), + A( + Href( + fmt.Sprintf("/%s/blob/%s/%s", data.Name, diff.Commit.This, d.Name.New), + ), + g.Text(d.Name.New), + ), + }, + ), + }, + A(Href(fmt.Sprintf("/%s/blob/%s/%s", data.Name, diff.Commit.This, d.Name.New)), + g.Text(d.Name.New)), + ), + g.If(d.IsBinary, + P(g.Text("Not showing binary file.")), + Pre( + g.Group(g.Map(d.TextFragments, func(tf git.TextFragment) g.Node { + return g.Group( + []g.Node{ + Span(g.Attr("style", "display: block"), g.Text(tf.Header)), + g.Map(tf.Lines, func(line gitdiff.Line) g.Node { + switch line.Op.String() { + case "+": + return Span(Class("diff-add"), g.Text(line.String())) + case "-": + return Span(Class("diff-del"), g.Text(line.String())) + default: + return Span(Class("diff-noop"), g.Text(line.String())) + } + }), + }) + })), + ), + ), + ), + ) + })..., + ), + ), + }) +}
D templates/commit.html
@@ -1,104 +0,0 @@
-{{ define "commit" }} -<!DOCTYPE html> -<html> -{{ template "head" . }} - - <body> - {{ template "repoheader" . }} - {{ template "nav" . }} - <main> - <section class="commit"> - <pre> - {{- .commit.Message -}} - </pre> - <div class="commit-info"> - {{ .commit.Author.Name }} <a href="mailto:{{ .commit.Author.Email }}" class="commit-email">{{ .commit.Author.Email}}</a> - <div>{{ .commit.Author.When.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}</div> - </div> - - <div> - <strong>commit</strong> - <p><a href="/{{ .name }}/commit/{{ .commit.This }}" class="commit-hash"> - {{ .commit.This }} - </a> - </p> - </div> - - {{ if .commit.Parent }} - <div> - <strong>parent</strong> - <p><a href="/{{ .name }}/commit/{{ .commit.Parent }}" class="commit-hash"> - {{ .commit.Parent }} - </a></p> - </div> - - {{ end }} - <div class="diff-stat"> - <div> - {{ .stat.FilesChanged }} files changed, - {{ .stat.Insertions }} insertions(+), - {{ .stat.Deletions }} deletions(-) - </div> - <div> - <br> - <strong>jump to</strong> - <ul> - {{ range .diff }} - <li><a href="#{{ .Name.New }}">{{ .Name.New }}</a></li> - {{ end }} - </ul> - </div> - </div> - </section> - <section> - {{ $repo := .name }} - {{ $this := .commit.This }} - {{ $parent := .commit.Parent }} - {{ range .diff }} - <div id="{{ .Name.New }}"> - <div class="diff"> - {{ if .IsNew }} - <span class="diff-type">A</span> - {{ end }} - {{ if .IsDelete }} - <span class="diff-type">D</span> - {{ end }} - {{ if not (or .IsNew .IsDelete) }} - <span class="diff-type">M</span> - {{ end }} - {{ if .Name.Old }} - <a href="/{{ $repo }}/blob/{{ $parent }}/{{ .Name.Old }}">{{ .Name.Old }}</a> - {{ if .Name.New }} - &#8594; - <a href="/{{ $repo }}/blob/{{ $this }}/{{ .Name.New }}">{{ .Name.New }}</a> - {{ end }} - {{ else }} - <a href="/{{ $repo }}/blob/{{ $this }}/{{ .Name.New }}">{{ .Name.New }}</a> - {{- end -}} - {{ if .IsBinary }} - <p>Not showing binary file.</p> - {{ else }} - <pre><span style="display: block">{{- .Header -}}</span> - {{- range .TextFragments -}} - {{- range .Lines -}} - {{- if eq .Op.String "+" -}} - <span class="diff-add">{{ .String }}</span> - {{- end -}} - {{- if eq .Op.String "-" -}} - <span class="diff-del">{{ .String }}</span> - {{- end -}} - {{- if eq .Op.String " " -}} - <span class="diff-noop">{{ .String }}</span> - {{- end -}} - {{- end -}} - {{- end -}} - {{- end -}} - </pre> - </div> - </div> - {{ end }} - </section> - </main> - </body> -</html> -{{ end }}
A templates/errors.go
@@ -0,0 +1,22 @@
+package templates + +import ( + g "go.alanpearce.eu/gomponents" + . "go.alanpearce.eu/gomponents/html" +) + +func NotFound(data PageData) g.Node { + return Page(data, []g.Node{ + Main( + H3(g.Text("404 — nothing like that here.")), + ), + }) +} + +func ServerError(data PageData) g.Node { + return Page(data, []g.Node{ + Main( + H3(g.Text("500 — something broke!")), + ), + }) +}
A templates/file.go
@@ -0,0 +1,44 @@
+package templates + +import ( + "fmt" + + g "go.alanpearce.eu/gomponents" + . "go.alanpearce.eu/gomponents/html" +) + +// File renders a file view page +func File(data PageData, chroma bool) g.Node { + return Page(data, []g.Node{ + RepoHeader(data), + RenderNav(data), + Main( + P(g.Text(data.Path), g.Text(" ("), A(Href("?raw=true"), g.Text("view raw")), g.Text(")")), + g.If(chroma, + Div(Class("chroma-file-wrapper"), g.Raw(data.Content)), + Div(Class("file-wrapper"), + Table( + TBody( + Tr( + Td(Class("line-numbers"), + Pre(g.Map(data.LineCount, func(line int) g.Node { + return g.Group([]g.Node{ + g.Text("\n"), + A( + ID(fmt.Sprintf("L%d", line)), + Href(fmt.Sprintf("#L%d", line)), + g.Text(fmt.Sprintf("%d", line)), + )}) + })), + ), + Td(Class("file-content"), + Pre(g.Text(data.Content)), + ), + ), + ), + ), + ), + ), + ), + }) +}
D templates/file.html
@@ -1,37 +0,0 @@
-{{ define "file" }} -<!DOCTYPE html> -<html> - {{ template "head" . }} - <body> - {{ template "repoheader" . }} - {{ template "nav" . }} - <main> - <p>{{ .path }} (<a style="color: gray" href="?raw=true">view raw</a>)</p> - {{if .chroma }} - <div class="chroma-file-wrapper"> - {{ .content }} - </div> - {{else}} - <div class="file-wrapper"> - <table> - <tbody><tr> - <td class="line-numbers"> - <pre> - {{- range .linecount }} - <a id="L{{ . }}" href="#L{{ . }}">{{ . }}</a> - {{- end -}} - </pre> - </td> - <td class="file-content"> - <pre> - {{- .content -}} - </pre> - </td> - </tr></tbody> - </table> - </div> - {{end}} - </main> - </body> -</html> -{{ end }}
D templates/head.html
@@ -1,32 +0,0 @@
-{{ define "head" }} - <head> - <meta charset="utf-8"> - <meta name="viewport" content="width=device-width, initial-scale=1"> - <link rel="stylesheet" href="/static/style.css" type="text/css"> - <link rel="icon" type="image/png" sizes="32x32" href="/static/elgit.png"> - {{ if .parent }} - <title>{{ .meta.Title }} &mdash; {{ .name }} ({{ .ref }}): {{ .parent }}/</title> - - {{ else if .path }} - <title>{{ .meta.Title }} &mdash; {{ .name }} ({{ .ref }}): {{ .path }}</title> - {{ else if .files }} - <title>{{ .meta.Title }} &mdash; {{ .name }} ({{ .ref }})</title> - {{ else if .commit }} - <title>{{ .meta.Title }} &mdash; {{ .name }}: {{ .commit.This }}</title> - {{ else if .branches }} - <title>{{ .meta.Title }} &mdash; {{ .name }}: refs</title> - {{ else if .commits }} - {{ if .log }} - <title>{{ .meta.Title }} &mdash; {{ .name }}: log</title> - {{ else }} - <title>{{ .meta.Title }} &mdash; {{ .name }}</title> - {{ end }} - {{ else }} - <title>{{ .meta.Title }}</title> - {{ end }} - {{ if and .servername .gomod }} - <meta name="go-import" content="{{ .servername}}/{{ .name }} git https://{{ .servername }}/{{ .name }}"> - {{ end }} - <!-- other meta tags here --> - </head> -{{ end }}
A templates/index.go
@@ -0,0 +1,36 @@
+package templates + +import ( + "time" + + g "go.alanpearce.eu/gomponents" + . "go.alanpearce.eu/gomponents/html" +) + +type RepoInfo struct { + DisplayName string + Name string + Desc string + Idle string + LastCommit time.Time +} + +func Index(data PageData, repos []RepoInfo) g.Node { + return Page(data, g.Group{ + Header( + H1(g.Text(data.Meta.Title)), + H2(g.Text(data.Meta.Description)), + ), + Main( + Div(Class("index"), + g.Map(repos, func(repo RepoInfo) g.Node { + return g.Group{ + Div(Class("index-name"), A(Href("/"+repo.Name), g.Text(repo.DisplayName))), + Div(Class("desc"), g.Text(repo.Desc)), + Div(g.Text(repo.Idle)), + } + }), + ), + ), + }) +}
D templates/index.html
@@ -1,22 +0,0 @@
-{{ define "index" }} -<!DOCTYPE html> -<html> -{{ template "head" . }} - - <body> - <header> - <h1>{{ .meta.Title }}</h1> - <h2>{{ .meta.Description }}</h2> - </header> - <main> - <div class="index"> - {{ range .info }} - <div class="index-name"><a href="/{{ .Name }}">{{ .DisplayName }}</a></div> - <div class="desc">{{ .Desc }}</div> - <div>{{ .Idle }}</div> - {{ end }} - </div> - </main> - </body> -</html> -{{ end }}
A templates/log.go
@@ -0,0 +1,44 @@
+package templates + +import ( + "fmt" + + "github.com/go-git/go-git/v5/plumbing/object" + g "go.alanpearce.eu/gomponents" + . "go.alanpearce.eu/gomponents/html" +) + +// Log renders the commit log page +func Log(data PageData, commits []*object.Commit) g.Node { + return Page(data, []g.Node{ + RepoHeader(data), + RenderNav(data), + Main( + Div(Class("log"), + g.Map(commits, func(commit *object.Commit) g.Node { + return g.Group{ + Div( + Div( + A( + Href(fmt.Sprintf("/%s/commit/%s", data.Name, commit.Hash.String())), + Class("commit-hash"), + g.Text(commit.Hash.String()[:8]), + ), + ), + Pre(g.Text(commit.Message)), + ), + Div(Class("commit-info"), + g.Text(commit.Author.Name+" "), + A( + Href(fmt.Sprintf("mailto:%s", commit.Author.Email)), + Class("commit-email"), + g.Text(commit.Author.Email), + ), + Div(g.Text(commit.Author.When.Format("Mon, 02 Jan 2006 15:04:05 -0700"))), + ), + } + }), + ), + ), + }) +}
D templates/log.html
@@ -1,26 +0,0 @@
-{{ define "log" }} -<!DOCTYPE html> -<html> -{{ template "head" . }} - - <body> - {{ template "repoheader" . }} - {{ template "nav" . }} - <main> - {{ $repo := .name }} - <div class="log"> - {{ range .commits }} - <div> - <div><a href="/{{ $repo }}/commit/{{ .Hash.String }}" class="commit-hash">{{ slice .Hash.String 0 8 }}</a></div> - <pre>{{ .Message }}</pre> - </div> - <div class="commit-info"> - {{ .Author.Name }} <a href="mailto:{{ .Author.Email }}" class="commit-email">{{ .Author.Email }}</a> - <div>{{ .Author.When.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}</div> - </div> - {{ end }} - </div> - </main> - </body> -</html> -{{ end }}
D templates/nav.html
@@ -1,14 +0,0 @@
-{{ define "nav" }} - <nav> - <ul> - {{ if .name }} - <li><a href="/{{ .name }}">summary</a></li> - <li><a href="/{{ .name }}/refs">refs</a></li> - {{ if .ref }} - <li><a href="/{{ .name }}/tree/{{ .ref }}/">tree</a></li> - <li><a href="/{{ .name }}/log/{{ .ref }}">log</a></li> - {{ end }} - {{ end }} - </ul> - </nav> -{{ end }}
A templates/page.go
@@ -0,0 +1,107 @@
+package templates + +import ( + "go.alanpearce.eu/elgit/config" + "go.alanpearce.eu/elgit/git" + g "go.alanpearce.eu/gomponents" + c "go.alanpearce.eu/gomponents/components" + . "go.alanpearce.eu/gomponents/html" +) + +type PageData struct { + Meta config.Meta + DisplayName string + Name string + Ref string + Readme string + Description string + Path string + Parent string + Files any + Diff []git.Diff + Stat git.Stat + Commit *git.Commit + Commits []*git.Commit + LineCount []int + Content string + Branches any + Log bool + Servername string + Gomod bool +} + +func RenderHead(data PageData) []g.Node { + var title string + metaTitle := data.Meta.Title + + if data.Parent != "" { + title = metaTitle + " — " + data.Name + " (" + data.Ref + "): " + data.Parent + "/" + } else if data.Path != "" { + title = metaTitle + " — " + data.Name + " (" + data.Ref + "): " + data.Path + } else if data.Files != nil { + title = metaTitle + " — " + data.Name + " (" + data.Ref + ")" + } else if data.Commit != nil { + title = metaTitle + " — " + data.Name + ": " + data.Commit.This + } else if data.Branches != nil { + title = metaTitle + " — " + data.Name + ": refs" + } else if data.Commits != nil { + if data.Log { + title = metaTitle + " — " + data.Name + ": log" + } else { + title = metaTitle + " — " + data.Name + } + } else { + title = metaTitle + } + + return []g.Node{ + Link(Rel("stylesheet"), Href("/static/style.css"), Type("text/css")), + Link(Rel("icon"), Type("image/png"), g.Attr("sizes", "32x32"), Href("/static/elgit.png")), + TitleEl(g.Text(title)), + g.If(data.Servername != "" && data.Gomod, + Meta( + Name("go-import"), + Content(data.Servername+"/"+data.Name+" git https://"+data.Servername+"/"+data.Name), + ), + ), + } +} + +func RenderNav(data PageData) g.Node { + var items []g.Node + + if data.Name != "" { + items = append(items, Li(A(Href("/"+data.Name), g.Text("summary")))) + items = append(items, Li(A(Href("/"+data.Name+"/refs"), g.Text("refs")))) + + if data.Ref != "" { + items = append(items, Li(A(Href("/"+data.Name+"/tree/"+data.Ref+"/"), g.Text("tree")))) + items = append(items, Li(A(Href("/"+data.Name+"/log/"+data.Ref), g.Text("log")))) + } + } + + return Nav(Ul(items...)) +} + +func RepoHeader(data PageData) g.Node { + return Header( + H2( + A( + Href("/"), + g.Text("all repos"), + ), + g.Textf(" — %s", data.DisplayName), + g.Text(" "), + g.If(data.Ref != "", Span(Class("ref"), g.Textf("@ %s", data.Ref))), + ), + H3(Class("desc"), g.Text(data.Description)), + ) +} + +func Page(data PageData, body []g.Node) g.Node { + return c.HTML5(c.HTML5Props{ + Language: "en", + Head: RenderHead(data), + Body: body, + }) +}
A templates/refs.go
@@ -0,0 +1,61 @@
+package templates + +import ( + "fmt" + + "github.com/go-git/go-git/v5/plumbing" + "go.alanpearce.eu/elgit/git" + g "go.alanpearce.eu/gomponents" + . "go.alanpearce.eu/gomponents/html" +) + +// Refs renders the repository references page +func Refs(data PageData, branches []*plumbing.Reference, tags []*git.TagReference) g.Node { + return Page(data, []g.Node{ + RepoHeader(data), + RenderNav(data), + Main( + H3(g.Text("branches")), + Div(Class("refs"), + g.Map(branches, func(branch *plumbing.Reference) g.Node { + return Div( + Strong(g.Text(branch.Name().Short())), + A( + Href(fmt.Sprintf("/%s/tree/%s/", data.Name, branch.Name().Short())), + g.Text("browse"), + ), + g.Text(" "), + A(Href(fmt.Sprintf("/%s/log/%s", data.Name, branch.Name().Short())), g.Text("log")), + g.Text(" "), + A( + Href(fmt.Sprintf("/%s/archive/%s.tar.gz", data.Name, branch.Name().Short())), + g.Text("tar.gz"), + ), + ) + }), + ), + g.If(len(tags) > 0, + g.Group([]g.Node{ + H3(g.Text("tags")), + Div(Class("refs"), + g.Map(tags, func(tag *git.TagReference) g.Node { + return Div( + Strong(g.Text(tag.Name())), + A(Href(fmt.Sprintf("/%s/tree/%s/", data.Name, tag.Name())), g.Text("browse")), + g.Text(" "), + A(Href(fmt.Sprintf("/%s/log/%s", data.Name, tag.Name())), g.Text("log")), + g.Text(" "), + A( + Href(fmt.Sprintf("/%s/archive/%s.tar.gz", data.Name, tag.Name())), + g.Text("tar.gz"), + ), + g.Text(" "), + g.If(tag.Message() != "", Pre(g.Text(tag.Message()))), + ) + }), + ), + }), + ), + ), + }) +}
D templates/refs.html
@@ -1,41 +0,0 @@
-{{ define "refs" }} -<!DOCTYPE html> -<html> -{{ template "head" . }} - - <body> - {{ template "repoheader" . }} - {{ template "nav" . }} - <main> - {{ $name := .name }} - <h3>branches</h3> - <div class="refs"> - {{ range .branches }} - <div> - <strong>{{ .Name.Short }}</strong> - <a href="/{{ $name }}/tree/{{ .Name.Short }}/">browse</a> - <a href="/{{ $name }}/log/{{ .Name.Short }}">log</a> - <a href="/{{ $name }}/archive/{{ .Name.Short }}.tar.gz">tar.gz</a> - </div> - {{ end }} - </div> - {{ if .tags }} - <h3>tags</h3> - <div class="refs"> - {{ range .tags }} - <div> - <strong>{{ .Name }}</strong> - <a href="/{{ $name }}/tree/{{ .Name }}/">browse</a> - <a href="/{{ $name }}/log/{{ .Name }}">log</a> - <a href="/{{ $name }}/archive/{{ .Name }}.tar.gz">tar.gz</a> - {{ if .Message }} - <pre>{{ .Message }}</pre> - </div> - {{ end }} - {{ end }} - </div> - {{ end }} - </main> - </body> -</html> -{{ end }}
D templates/repo-header.html
@@ -1,12 +0,0 @@
-{{ define "repoheader" }} -<header> - <h2> - <a href="/">all repos</a> - &mdash; {{ .displayname }} - {{ if .ref }} - <span class="ref">@ {{ .ref }}</span> - {{ end }} - </h2> - <h3 class="desc">{{ .desc }}</h3> -</header> -{{ end }}
A templates/repo.go
@@ -0,0 +1,52 @@
+package templates + +import ( + "fmt" + + "github.com/go-git/go-git/v5/plumbing/object" + g "go.alanpearce.eu/gomponents" + . "go.alanpearce.eu/gomponents/html" +) + +// Repo renders the repository summary page +func Repo(data PageData, commits []*object.Commit, readme string) g.Node { + return Page(data, []g.Node{ + RepoHeader(data), + RenderNav(data), + Main( + Div(Class("log"), + g.Map(commits, func(commit *object.Commit) g.Node { + return g.Group{ + Div( + Div( + A( + Href(fmt.Sprintf("/%s/commit/%s", data.Name, commit.Hash.String())), + Class("commit-hash"), + g.Text(commit.Hash.String()[:8]), + ), + ), + Pre(g.Text(commit.Message)), + ), + Div(Class("commit-info"), + g.Text(commit.Author.Name), + g.Text(" "), + A( + Href(fmt.Sprintf("mailto:%s", commit.Author.Email)), + Class("commit-email"), + g.Text(commit.Author.Email), + ), + Div(g.Text(commit.Author.When.Format("Mon, 02 Jan 2006 15:04:05 -0700"))), + ), + } + }), + ), + g.If(readme != "", + Article(Class("readme"), g.Raw(readme)), + ), + Div(Class("clone-url"), + Strong(g.Text("clone")), + Pre(g.Text(fmt.Sprintf("git clone https://%s/%s", data.Servername, data.Name))), + ), + ), + }) +}
D templates/repo.html
@@ -1,39 +0,0 @@
-{{ define "repo" }} -<!DOCTYPE html> -<html> -{{ template "head" . }} - - <body> - {{ template "repoheader" . }} - {{ template "nav" . }} - - <main> - {{ $repo := .name }} - <div class="log"> - {{ range .commits }} - <div> - <div><a href="/{{ $repo }}/commit/{{ .Hash.String }}" class="commit-hash">{{ slice .Hash.String 0 8 }}</a></div> - <pre>{{ .Message }}</pre> - </div> - <div class="commit-info"> - {{ .Author.Name }} <a href="mailto:{{ .Author.Email }}" class="commit-email">{{ .Author.Email }}</a> - <div>{{ .Author.When.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}</div> - </div> - {{ end }} - </div> -{{- if .readme }} - <article class="readme"> - {{- .readme -}} - </article> -{{- end -}} - - <div class="clone-url"> - <strong>clone</strong> - <pre> -git clone https://{{ .servername }}/{{ .name }} - </pre> - </div> - </main> - </body> -</html> -{{ end }}
A templates/tree.go
@@ -0,0 +1,76 @@
+package templates + +import ( + "fmt" + + "go.alanpearce.eu/elgit/git" + g "go.alanpearce.eu/gomponents" + . "go.alanpearce.eu/gomponents/html" +) + +// Tree renders a repository file tree view +func Tree(data PageData, files []git.NiceTree, readme string, dotdot string) g.Node { + return Page(data, []g.Node{ + RepoHeader(data), + RenderNav(data), + Main( + Div(Class("tree"), + g.If(data.Parent != "", + g.Group{ + Div(), + Div(), + Div(A(Href(fmt.Sprintf("/%s/tree/%s/%s", data.Name, data.Ref, dotdot)), g.Text(".."))), + }, + ), + g.Map(files, func(file git.NiceTree) g.Node { + if !file.IsFile { + return g.Group{ + Div(Class("mode"), g.Text(file.Mode)), + Div(Class("size"), g.Text(fmt.Sprintf("%d", file.Size))), + Div( + g.If(data.Parent != "", + A( + Href( + fmt.Sprintf("/%s/tree/%s/%s/%s", data.Name, data.Ref, data.Parent, file.Name), + ), + g.Text(file.Name+"/"), + ), + A( + Href(fmt.Sprintf("/%s/tree/%s/%s", data.Name, data.Ref, file.Name)), + g.Text(file.Name+"/"), + ), + ), + ), + } + } + return nil + }), + g.Map(files, func(file git.NiceTree) g.Node { + if file.IsFile { + return g.Group{ + Div(Class("mode"), g.Text(file.Mode)), + Div(Class("size"), g.Text(fmt.Sprintf("%d", file.Size))), + Div( + g.If(data.Parent != "", + A( + Href( + fmt.Sprintf("/%s/blob/%s/%s/%s", data.Name, data.Ref, data.Parent, file.Name), + ), + g.Text(file.Name), + ), + A( + Href(fmt.Sprintf("/%s/blob/%s/%s", data.Name, data.Ref, file.Name)), + g.Text(file.Name), + ), + ), + ), + } + } + return nil + }), + ), + Article( + g.If(readme != "", Pre(g.Raw(readme))), + ), + )}) +}
D templates/tree.html
@@ -1,56 +0,0 @@
-{{ define "tree" }} -<!DOCTYPE html> -<html> - -{{ template "head" . }} - - <body> - {{ template "repoheader" . }} - {{ template "nav" . }} - <main> - {{ $repo := .name }} - {{ $ref := .ref }} - {{ $parent := .parent }} - - <div class="tree"> - {{ if $parent }} - <div></div> - <div></div> - <div><a href="/{{ $repo }}/tree/{{ $ref }}/{{ .dotdot }}">..</a></div> - {{ end }} - {{ range .files }} - {{ if not .IsFile }} - <div class="mode">{{ .Mode }}</div> - <div class="size">{{ .Size }}</div> - <div> - {{ if $parent }} - <a href="/{{ $repo }}/tree/{{ $ref }}/{{ $parent }}/{{ .Name }}">{{ .Name }}/</a> - {{ else }} - <a href="/{{ $repo }}/tree/{{ $ref }}/{{ .Name }}">{{ .Name }}/</a> - {{ end }} - </div> - {{ end }} - {{ end }} - {{ range .files }} - {{ if .IsFile }} - <div class="mode">{{ .Mode }}</div> - <div class="size">{{ .Size }}</div> - <div> - {{ if $parent }} - <a href="/{{ $repo }}/blob/{{ $ref }}/{{ $parent }}/{{ .Name }}">{{ .Name }}</a> - {{ else }} - <a href="/{{ $repo }}/blob/{{ $ref }}/{{ .Name }}">{{ .Name }}</a> - {{ end }} - </div> - {{ end }} - {{ end }} - </div> - <article> - <pre> - {{- if .readme }}{{ .readme }}{{- end -}} - </pre> - </article> - </main> - </body> -</html> -{{ end }}