gitshow

Log | Files | Refs

commit 010cff4221ed23f5f54d3aa8c52dba9b15378e33
parent 7a4c864ad9ebef818f57a75b5606ec70d3c5e28b
Author: Thomas Vigouroux <thomas.vigouroux@univ-grenoble-alpes.fr>
Date:   Thu, 11 Apr 2024 17:10:58 +0200

refactor(template): use template fragment instead of being dumb

This is inspired by https://htmx.org/essays/template-fragments/

Diffstat:
Mfile.go | 191++++++++++++++++++++++++++++++++++++++-----------------------------------------
Mgitshow.go | 2+-
Mlog.go | 2+-
Mrepo.go | 2+-
Mtemplates/commit.html | 8+++-----
Mtemplates/file.html | 10++++------
Mtemplates/log.html | 8+++-----
Mtemplates/repo.html | 8+++-----
Mtemplates/tree.html | 12+++++-------
Mtree.go | 176+++++++++++++++++++++++++++++++++++--------------------------------------------
10 files changed, 191 insertions(+), 228 deletions(-)

diff --git a/file.go b/file.go @@ -1,115 +1,110 @@ package main import ( - // "github.com/gorilla/mux" + "github.com/gorilla/mux" // "fmt" - "github.com/libgit2/git2go/v31" "html/template" - // "log" + "github.com/go-git/go-git/v5/plumbing" + "log" "net/http" - // "path/filepath" - // "strings" + "path/filepath" + "strings" // Chroma - // chromahtml "github.com/alecthomas/chroma/v2/formatters/html" - // "github.com/alecthomas/chroma/v2/lexers" - // "github.com/alecthomas/chroma/v2/styles" + chromahtml "github.com/alecthomas/chroma/v2/formatters/html" + "github.com/alecthomas/chroma/v2/lexers" + "github.com/alecthomas/chroma/v2/styles" ) type FilePageData struct { Name string Content template.HTML - Ref *git.Oid + Ref string } func getFile(w http.ResponseWriter, r *http.Request) { - // logger := log.Default() - // vars := mux.Vars(r) - // reponame := vars["repository"] - // - // repo, err := checkRepo(reponame) - // if err != nil { - // w.WriteHeader(http.StatusNotFound) - // return - // } - // - // ref, err := repo.RevparseSingle(vars["ref"]) - // if err != nil { - // w.WriteHeader(http.StatusNotFound) - // return - // } - // - // commit, err := ref.AsCommit() - // if err != nil { - // w.WriteHeader(http.StatusNotFound) - // return - // } - // - // tree, err := commit.Tree() - // if err != nil { - // logger.Fatalf("Could not get tree") - // } - // - // // Get the path of the entry - // path := r.RequestURI[len("/repos/") + len(reponame) + len("/file/") + len(vars["ref"]) + 1:len(r.RequestURI)] - // logger.Printf("Trying to get file: %s (%s)", path, r.RequestURI) - // entry, err := tree.EntryByPath(path) - // if err != nil || entry.Type != git.ObjectBlob { - // w.WriteHeader(http.StatusNotFound) - // return - // } - // - // blob, err := repo.LookupBlob(entry.Id) - // if err != nil { - // logger.Fatalf("Could not find file") - // } - // - // lexer := lexers.Match(path) - // if lexer == nil { - // lexer = lexers.Fallback - // } - // - // if err != nil { - // logger.Fatal(err) - // } - // - // var formatter = chromahtml.New( - // chromahtml.WithLineNumbers(true), - // chromahtml.WithClasses(true), - // chromahtml.WithLinkableLineNumbers(true, ""), - // ) - // - // if formatter == nil { - // logger.Fatal("Could not get html formatter") - // } - // - // iterator, err := lexer.Tokenise(nil, fmt.Sprintf("%s", blob.Contents())) - // if err != nil { - // w.WriteHeader(http.StatusInternalServerError) - // return - // } - // - // sbuilder := new(strings.Builder) - // formatter.Format(sbuilder, styles.Fallback, iterator) - // - // w.WriteHeader(http.StatusOK) - // - // tmpl := template.Must(template.ParseFiles( - // filepath.Join("templates", "file.html"), - // filepath.Join("templates", "wrap.html"), - // )) - // - // _, ishtmx := r.Header["Hx-Request"] - // - // data := FilePageData { - // Name: reponame, - // Content: template.HTML(sbuilder.String()), - // Ref: ref.Id(), - // } - // - // if ishtmx { - // tmpl.ExecuteTemplate(w, "file_content", data) - // } else { - // tmpl.ExecuteTemplate(w, "file_full", data) - // } + logger := log.Default() + vars := mux.Vars(r) + reponame := vars["repository"] + + repo, err := checkRepo(reponame) + if err != nil { + w.WriteHeader(http.StatusNotFound) + return + } + + hash, err := repo.ResolveRevision(plumbing.Revision(vars["ref"])) + if err != nil { + w.WriteHeader(http.StatusNotFound) + return + } + + commit, err := repo.CommitObject(*hash) + if err != nil { + w.WriteHeader(http.StatusNotFound) + return + } + + // Get the path of the entry + path := r.RequestURI[len("/repos/") + len(reponame) + len("/file/") + len(vars["ref"]) + 1:len(r.RequestURI)] + logger.Printf("Trying to get file: %s (%s)", path, r.RequestURI) + entry, err := commit.File(path) + if err != nil { + w.WriteHeader(http.StatusNotFound) + return + } + + content, err := entry.Contents() + if err != nil { + logger.Fatal(err) + } + + lexer := lexers.Match(path) + if lexer == nil { + lexer = lexers.Fallback + } + + if err != nil { + logger.Fatal(err) + } + + var formatter = chromahtml.New( + chromahtml.WithLineNumbers(true), + chromahtml.WithClasses(true), + chromahtml.WithLinkableLineNumbers(true, ""), + ) + + if formatter == nil { + logger.Fatal("Could not get html formatter") + } + + iterator, err := lexer.Tokenise(nil, content) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + sbuilder := new(strings.Builder) + formatter.Format(sbuilder, styles.Fallback, iterator) + + w.WriteHeader(http.StatusOK) + + tmpl := template.Must(template.ParseFiles( + filepath.Join("templates", "file.html"), + filepath.Join("templates", "wrap.html"), + )) + + _, ishtmx := r.Header["Hx-Request"] + + data := FilePageData { + Name: reponame, + Content: template.HTML(sbuilder.String()), + Ref: commit.Hash.String(), + } + + if ishtmx { + tmpl.ExecuteTemplate(w, "file_content", data) + } else { + tmpl.Execute(w, data) + } } diff --git a/gitshow.go b/gitshow.go @@ -35,7 +35,7 @@ var reporouter *mux.Router // Got from mux repository func loggingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Println(r.Method, r.RequestURI) + log.Println(r.Method, r.RequestURI, r.Header["Hx-Request"]) next.ServeHTTP(w, r) }) } diff --git a/log.go b/log.go @@ -69,6 +69,6 @@ func getLog(w http.ResponseWriter, r *http.Request) { if ishtmx { tmpl.ExecuteTemplate(w, "log_content", data) } else { - tmpl.ExecuteTemplate(w, "log_full", data) + tmpl.Execute(w, data) } } diff --git a/repo.go b/repo.go @@ -78,6 +78,6 @@ func getRepo(w http.ResponseWriter, r *http.Request) { if ishtmx { tmpl.ExecuteTemplate(w, "repo_content", data) } else { - tmpl.ExecuteTemplate(w, "repo_full", data) + tmpl.Execute(w, data) } } diff --git a/templates/commit.html b/templates/commit.html @@ -1,4 +1,6 @@ -{{define "file_content"}} +{{template "header" .}} + +{{block "file_content" .}} <div id="tab-list" role="tablist" hx-push-url="true"> <button hx-get="/repos/{{.Name}}">about</button> <button hx-get="/repos/{{.Name}}/log">log</button> @@ -7,8 +9,4 @@ {{.Content}} {{end}} -{{define "file_full"}} -{{template "header" .}} -{{template "file_content" .}} {{template "footer" .}} -{{end}} diff --git a/templates/file.html b/templates/file.html @@ -1,14 +1,12 @@ -{{define "file_content"}} +{{template "header" .}} + +{{block "file_content" .}} <div id="tab-list" role="tablist" hx-push-url="true"> <button hx-get="/repos/{{.Name}}">about</button> <button hx-get="/repos/{{.Name}}/log">log</button> - <button hx-get="/repos/{{.Name}}/tree/{{.Ref.String}}">tree</button> + <button hx-get="/repos/{{.Name}}/tree/{{.Ref}}/">tree</button> </div> {{.Content}} {{end}} -{{define "file_full"}} -{{template "header" .}} -{{template "file_content" .}} {{template "footer" .}} -{{end}} diff --git a/templates/log.html b/templates/log.html @@ -1,4 +1,6 @@ -{{define "log_content"}} +{{template "header" .}} + +{{block "log_content" .}} <div id="tab-list" role="tablist" hx-push-url="true"> <button hx-get="/repos/{{.Name}}">about</button> <button hx-get="/repos/{{.Name}}/log" class="selected">log</button> @@ -17,8 +19,4 @@ </table> {{end}} -{{define "log_full"}} -{{template "header" .}} -{{template "log_content" .}} {{template "footer" .}} -{{end}} diff --git a/templates/repo.html b/templates/repo.html @@ -1,4 +1,6 @@ -{{define "repo_content"}} +{{template "header" .}} + +{{block "repo_content" .}} <div id="tab-list" role="tablist" hx-push-url="true"> <button hx-get="/repos/{{.Name}}" class="selected">about</button> <button hx-get="/repos/{{.Name}}/log">log</button> @@ -9,8 +11,4 @@ </pre> {{end}} -{{define "repo_full"}} -{{template "header" .}} -{{template "repo_content" .}} {{template "footer" .}} -{{end}} diff --git a/templates/tree.html b/templates/tree.html @@ -1,11 +1,13 @@ -{{define "tree_content"}} +{{template "header" .}} + +{{block "tree_content" .}} <div id="tab-list" role="tablist" hx-push-url="true"> <button hx-get="/repos/{{.Name}}">about</button> <button hx-get="/repos/{{.Name}}/log">log</button> - <button hx-get="/repos/{{.Name}}/tree/{{.Ref.String}}" class="selected">tree</button> + <button hx-get="/repos/{{.Name}}/tree/{{.Ref}}/" class="selected">tree</button> </div> {{$repoName:=.Name}} -{{$branchName:=.Ref.String}} +{{$branchName:=.Ref}} <table> {{range .Entries}} <tr> @@ -16,8 +18,4 @@ </table> {{end}} -{{define "tree_full"}} -{{template "header" .}} -{{template "tree_content" .}} {{template "footer" .}} -{{end}} diff --git a/tree.go b/tree.go @@ -1,115 +1,93 @@ package main import ( - // "github.com/gorilla/mux" + "github.com/gorilla/mux" // "fmt" - "github.com/libgit2/git2go/v31" - // "html/template" - // "log" + // "github.com/libgit2/git2go/v31" + "html/template" + "log" + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/go-git/go-git/v5/plumbing" "net/http" - // "path/filepath" + "path/filepath" ) type TreePageData struct { Name string - Entries []*git.TreeEntry - Ref *git.Oid + Ref string + Entries []object.TreeEntry } func getTree(w http.ResponseWriter, r *http.Request) { - // logger := log.Default() - // vars := mux.Vars(r) - // reponame := vars["repository"] - // - // repo, err := checkRepo(reponame) - // if err != nil { - // w.WriteHeader(http.StatusNotFound) - // return - // } - // - // var commit *git.Commit - // refname, present := vars["ref"] - // - // if !present { - // head, err := repo.Head() - // if err != nil { - // // TODO: handle no head - // w.WriteHeader(http.StatusInternalServerError) - // return - // } - // - // branch := head.Branch() - // // Now get the corresponding commit - // ref, err := branch.Resolve() - // if err != nil { - // logger.Fatalf("Malformed repository") - // } - // - // commit, err = repo.LookupCommit(ref.Target()) - // if err != nil { - // logger.Fatalf("Could not get commit") - // } - // } else { - // obj, err := repo.RevparseSingle(refname) - // if err != nil { - // w.WriteHeader(http.StatusNotFound) - // return - // } - // commit, err = obj.AsCommit() - // if err != nil { - // w.WriteHeader(http.StatusNotFound) - // return - // } - // } - // logger.Printf("Called %s: ref to %s", refname, commit.Id().String()) - // - // tree, err := commit.Tree() - // if err != nil { - // logger.Fatalf("Could not get tree") - // } - // - // url, err := reporouter.Get("tree").URL("repository", reponame, "ref", commit.Id().String()) + logger := log.Default() + vars := mux.Vars(r) + reponame := vars["repository"] + + repo, err := checkRepo(reponame) + if err != nil { + w.WriteHeader(http.StatusNotFound) + return + } + + var revstr string + revstr, present := vars["ref"] + + if !present { + head, err := repo.Head() + if err != nil { + // TODO: handle no head + w.WriteHeader(http.StatusInternalServerError) + return + } + revstr = head.Name().String() + } + + logger.Printf("In repo %s, search for %s", reponame, revstr) + + hash, err := repo.ResolveRevision(plumbing.Revision(revstr)) + if err != nil { + w.WriteHeader(http.StatusNotFound) + return + } + + commit, err := repo.CommitObject(*hash) + if err != nil { + logger.Fatal(err) + } + logger.Printf("Called %s: ref to %s", revstr, commit.Hash.String()) + + tree, err := commit.Tree() + if err != nil { + logger.Fatalf("Could not get tree") + } + + // url, err := reporouter.Get("tree").URL("repository", reponame, "ref", commit.Hash.String()) // if err != nil { // logger.Fatalf("Could not generate URL") // } - // - // url.Host = r.URL.Host - // url.Scheme = r.URL.Scheme - // - // w.Header().Add("HX-Push-Url", url.String()) - // w.WriteHeader(http.StatusOK) - // - // tmpl := template.Must(template.New("tree_root").Funcs(template.FuncMap{ - // "isFile": func(obj *git.TreeEntry) bool { - // blob, err := repo.LookupBlob(obj.Id) - // if err != nil { - // return false - // } - // - // - // return obj.Type == git.ObjectBlob && !blob.IsBinary() - // }, - // }).ParseFiles( - // filepath.Join("templates", "tree.html"), - // filepath.Join("templates", "wrap.html"), - // )) - // - // _, ishtmx := r.Header["Hx-Request"] - // - // var entries []*git.TreeEntry = make([]*git.TreeEntry, 0) - // for i := uint64(0); i < tree.EntryCount(); i++ { - // entries = append(entries, tree.EntryByIndex(i)) - // } - // - // data := TreePageData{ - // Name: reponame, - // Entries: entries, - // Ref: commit.Id(), - // } - // - // if ishtmx { - // tmpl.ExecuteTemplate(w, "tree_content", data) - // } else { - // tmpl.ExecuteTemplate(w, "tree_full", data) - // } + + w.WriteHeader(http.StatusOK) + + tmpl := template.Must(template.New("tree_root").Funcs(template.FuncMap{ + "isFile": func(obj *object.TreeEntry) bool { + return obj.Mode.IsFile() + }, + }).ParseFiles( + filepath.Join("templates", "tree.html"), + filepath.Join("templates", "wrap.html"), + )) + + _, ishtmx := r.Header["Hx-Request"] + + data := TreePageData{ + Name: reponame, + Entries: tree.Entries, + Ref: commit.Hash.String(), + } + + if ishtmx { + tmpl.ExecuteTemplate(w, "tree_content", data) + } else { + tmpl.Execute(w, data) + } }