Goit

Simple and lightweight Git web server
git clone http://git.omkov.net/Goit
Log | Tree | Refs | README | Download

AuthorJakob Wakeling <[email protected]>
Date2023-07-19 10:07:36
Commit17c34f47738b5ccbed6a6f6906e167c673e962b4
Parent1828e5fb2957c068734703ded8c0b6144b9bba3d

Make use of nested Go HTML templates

Diffstat

M main.go | 14 +++++++++++++-
A res/base/head.html.tmpl | 7 +++++++
A res/base/repo_header.html.tmpl | 29 +++++++++++++++++++++++++++++
R res/error.html -> res/error.html.tmpl | 0
M res/repo_index.html | 54 +++++++++++++++++++++++++-----------------------------
M res/repo_log.html | 38 ++------------------------------------
M res/repo_refs.html | 29 ++++-------------------------
M res/repo_tree.html | 61 ++++++++++++++++++++-----------------------------------------
M res/res.go | 12 +++++++++---
M src/goit.go | 13 +++++++++++--
M src/http.go | 18 ++++++++++++++++--
M src/repo.go | 53 +++++++++++++++++++++++++----------------------------

12 files changed, 161 insertions, 167 deletions

diff --git a/main.go b/main.go
index 63f947d..0f97b7b 100644
--- a/main.go
+++ b/main.go
@@ -45,6 +45,7 @@ func main() {
 	h.Path("/{repo}/git-receive-pack").Methods(http.MethodPost).HandlerFunc(goit.HandleReceivePack)
 
 	h.Path("/static/style.css").Methods(http.MethodGet).HandlerFunc(handleStyle)
+	h.Path("/static/favicon.png").Methods(http.MethodGet).HandlerFunc(handleFavicon)
 
 	h.PathPrefix("/").HandlerFunc(goit.HttpNotFound)
 
@@ -70,12 +71,23 @@ func logHttp(handler http.Handler) http.Handler {
 }
 
 func handleStyle(w http.ResponseWriter, r *http.Request) {
-	w.Header().Add("Content-Type", "text/css")
+	w.Header().Set("Content-Type", "text/css")
 	if _, err := w.Write([]byte(res.Style)); err != nil {
 		log.Println("[Style]", err.Error())
 	}
 }
 
+func handleFavicon(w http.ResponseWriter, r *http.Request) {
+	if goit.Favicon == nil {
+		goit.HttpError(w, http.StatusNotFound)
+	} else {
+		w.Header().Set("Content-Type", "image/png")
+		if _, err := w.Write(goit.Favicon); err != nil {
+			log.Println("[Favicon]", err.Error())
+		}
+	}
+}
+
 func redirectDotGit(w http.ResponseWriter, r *http.Request) {
 	http.Redirect(w, r, strings.TrimSuffix(r.URL.Path, ".git"), http.StatusMovedPermanently)
 }
diff --git a/res/base/head.html.tmpl b/res/base/head.html.tmpl
new file mode 100644
index 0000000..5f5135a
--- /dev/null
+++ b/res/base/head.html.tmpl
@@ -0,0 +1,7 @@
+{{define "base/head"}}
+<meta charset="UTF-8">
+<title>{{.Title}}</title>
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<link rel="stylesheet" type="text/css" href="/static/style.css">
+<link rel="icon" type="image/png" href="/static/favicon.png">
+{{end}}
diff --git a/res/base/repo_header.html.tmpl b/res/base/repo_header.html.tmpl
new file mode 100644
index 0000000..b25b18e
--- /dev/null
+++ b/res/base/repo_header.html.tmpl
@@ -0,0 +1,29 @@
+<table>
+	<tr>
+		<td rowspan="2"><a href="/">
+			<img style="max-height: 24px;" src="/static/favicon.png">
+		</a></td>
+		<td><h1>{{.Name}}</h1></td>
+	</tr>
+	<tr>
+		<td>{{.Description}}</td>
+	</tr>
+	<tr>
+		<td></td>
+		<td>git clone <a href="{{.Url}}">{{.Url}}</a></td>
+	</tr>
+	<tr>
+		<td></td>
+		<td>
+			<a href="/{{.Name}}/log">Log</a>
+			| <a href="/{{.Name}}/tree">Tree</a>
+			| <a href="/{{.Name}}/refs">Refs</a>
+			{{if .HasReadme}}
+				| <a href="">README</a>
+			{{end}}
+			{{if .HasLicence}}
+				| <a href="">LICENCE</a>
+			{{end}}
+		</td>
+	</tr>
+</table>
diff --git a/res/error.html b/res/error.html.tmpl
similarity index 100%
rename from res/error.html
rename to res/error.html.tmpl
diff --git a/res/repo_index.html b/res/repo_index.html
index 2662bd3..49e67a2 100644
--- a/res/repo_index.html
+++ b/res/repo_index.html
@@ -1,32 +1,28 @@
 <!DOCTYPE html>
-<head>
-	<meta charset="UTF-8">
-	<title>Repositories</title>
-	<meta name="viewport" content="width=device-width, initial-scale=1.0">
-	<link rel="stylesheet" type="text/css" href="/static/style.css">
-	<link rel="icon" type="image/png" href="/static/favicon.png">
-</head>
+<head>{{template "base/head" .}}</head>
 <body>
-	<table>
-		<thead>
-			<tr>
-				<td><b>Name</b></td>
-				<td><b>Description</b></td>
-				<td><b>Owner</b></td>
-				<td><b>Visibility</b></td>
-				<td><b>Last Commit</b></td>
-			</tr>
-		</thead>
-		<tbody>
-		{{range .Repos}}
-			<tr>
-				<td><a href="/{{.Name}}/">{{.Name}}</a></td>
-				<td>{{.Description}}</td>
-				<td>{{.Owner}}</td>
-				<td>{{.Visibility}}</td>
-				<td>{{.LastCommit}}</td>
-			</tr>
-		{{end}}
-		</tbody>
-	</table>
+	<main>
+		<table>
+			<thead>
+				<tr>
+					<td><b>Name</b></td>
+					<td><b>Description</b></td>
+					<td><b>Owner</b></td>
+					<td><b>Visibility</b></td>
+					<td><b>Last Commit</b></td>
+				</tr>
+			</thead>
+			<tbody>
+			{{range .Repos}}
+				<tr>
+					<td><a href="/{{.Name}}/">{{.Name}}</a></td>
+					<td>{{.Description}}</td>
+					<td>{{.Owner}}</td>
+					<td>{{.Visibility}}</td>
+					<td>{{.LastCommit}}</td>
+				</tr>
+			{{end}}
+			</tbody>
+		</table>
+	</main>
 </body>
diff --git a/res/repo_log.html b/res/repo_log.html
index 999aa88..4c1aeac 100644
--- a/res/repo_log.html
+++ b/res/repo_log.html
@@ -1,41 +1,7 @@
 <!DOCTYPE html>
-<head>
-	<meta charset="UTF-8">
-	<title>Log</title>
-	<meta name="viewport" content="width=device-width, initial-scale=1.0">
-	<link rel="stylesheet" type="text/css" href="/static/style.css">
-	<link rel="icon" type="image/png" href="/static/favicon.png">
-</head>
+<head>{{template "base/head" .}}</head>
 <body>
-	<header>
-		<table>
-			<tr>
-				<td rowspan="2"><a href="/"><img style="max-height: 22px;" src=""></a></td>
-				<td><h1>{{.Name}}</h1></td>
-			</tr>
-			<tr>
-				<td>{{.Description}}</td>
-			</tr>
-			<tr>
-				<td></td>
-				<td>git clone <a href="{{.Url}}">{{.Url}}</a></td>
-			</tr>
-			<tr>
-				<td></td>
-				<td>
-					<a href="/{{.Name}}/log">Log</a>
-					| <a href="/{{.Name}}/tree">Tree</a>
-					| <a href="/{{.Name}}/refs">Refs</a>
-					{{if .HasReadme}}
-						| <a href="">README</a>
-					{{end}}
-					{{if .HasLicence}}
-						| <a href="">LICENCE</a>
-					{{end}}
-				</td>
-			</tr>
-		</table>
-	</header><hr>
+	<header>{{template "base/repo_header" .}}</header><hr>
 	<main>
 		{{if .Commits}}
 			<table>
diff --git a/res/repo_refs.html b/res/repo_refs.html
index 9fc5f00..b250660 100644
--- a/res/repo_refs.html
+++ b/res/repo_refs.html
@@ -1,30 +1,8 @@
 <!DOCTYPE html>
-<head>
-	<meta charset="UTF-8">
-	<title>Refs</title>
-	<meta name="viewport" content="width=device-width, initial-scale=1.0">
-	<link rel="stylesheet" type="text/css" href="/static/style.css">
-	<link rel="icon" type="image/png" href="/static/favicon.png">
-</head>
+<head>{{template "base/head" .}}</head>
 <body>
-	<table>
-		<tr><td><h1>{{.Name}}</h1></td></tr>
-		<tr><td><span>{{.Description}}</span></td></tr>
-		<tr><td>git clone <a href="{{.Url}}">{{.Url}}</a></td></tr>
-		<tr>
-			<td>
-				<a href="/{{.Name}}/log">Log</a>
-				| <a href="/{{.Name}}/tree">Tree</a>
-				| <a href="/{{.Name}}/refs">Refs</a>
-				{{if .HasReadme}}
-					| <a href="">README</a>
-				{{end}}
-				{{if .HasLicence}}
-					| <a href="">LICENCE</a>
-				{{end}}
-			</td>
-		</tr>
-	</table><hr>
+	<header>{{template "base/repo_header" .}}</header><hr>
+	<main>
 	{{if .Branches}}
 		<h2>Branches</h2>
 		<table>
@@ -63,4 +41,5 @@
 			</tbody>
 		</table>
 	{{end}}
+	</main>
 </body>
diff --git a/res/repo_tree.html b/res/repo_tree.html
index ca65c95..4ca17bc 100644
--- a/res/repo_tree.html
+++ b/res/repo_tree.html
@@ -1,44 +1,23 @@
 <!DOCTYPE html>
-<head>
-	<meta charset="UTF-8">
-	<title>Tree</title>
-	<meta name="viewport" content="width=device-width, initial-scale=1.0">
-	<link rel="stylesheet" type="text/css" href="/static/style.css">
-	<link rel="icon" type="image/png" href="/static/favicon.png">
-</head>
+<head>{{template "base/head" .}}</head>
 <body>
-	<table>
-		<tr><td><h1>{{.Name}}</h1></td></tr>
-		<tr><td><span>{{.Description}}</span></td></tr>
-		<tr><td>git clone <a href="{{.Url}}">{{.Url}}</a></td></tr>
-		<tr>
-			<td>
-				<a href="/{{.Name}}/log">Log</a>
-				| <a href="/{{.Name}}/tree">Tree</a>
-				| <a href="/{{.Name}}/refs">Refs</a>
-				{{if .HasReadme}}
-					| <a href="">README</a>
-				{{end}}
-				{{if .HasLicence}}
-					| <a href="">LICENCE</a>
-				{{end}}
-			</td>
-		</tr>
-	</table><hr>
-	<table>
-		<thead>
-			<tr>
-				<td><b>Name</b></td>
-				<td><b>Size</b></td>
-			</tr>
-		</thead>
-		<tbody>
-		{{range .Files}}
-			<tr>
-				<td>{{.Name}}</a></td>
-				<td>{{.Size}}</td>
-			</tr>
-		{{end}}
-		</tbody>
-	</table>
+	<header>{{template "base/repo_header" .}}</header><hr>
+	<main>
+		<table>
+			<thead>
+				<tr>
+					<td><b>Name</b></td>
+					<td><b>Size</b></td>
+				</tr>
+			</thead>
+			<tbody>
+			{{range .Files}}
+				<tr>
+					<td>{{.Name}}</a></td>
+					<td>{{.Size}}</td>
+				</tr>
+			{{end}}
+			</tbody>
+		</table>
+	</main>
 </body>
diff --git a/res/res.go b/res/res.go
index cb3647b..0f1cd3a 100644
--- a/res/res.go
+++ b/res/res.go
@@ -2,6 +2,15 @@ package res
 
 import _ "embed"
 
+//go:embed error.html.tmpl
+var Error string
+
+//go:embed base/head.html.tmpl
+var BaseHead string
+
+//go:embed base/repo_header.html.tmpl
+var RepoHeader string
+
 //go:embed repo_index.html
 var RepoIndex string
 
@@ -26,8 +35,5 @@ var UserCreate string
 //go:embed admin_user_index.html
 var AdminUserIndex string
 
-//go:embed error.html
-var Error string
-
 //go:embed style.css
 var Style string
diff --git a/src/goit.go b/src/goit.go
index 7553fc4..22634d0 100644
--- a/src/goit.go
+++ b/src/goit.go
@@ -31,6 +31,7 @@ var config = Config{
 }
 
 var db *sql.DB
+var Favicon []byte
 
 /* Initialise Goit. */
 func InitGoit(conf string) (err error) {
@@ -44,6 +45,14 @@ func InitGoit(conf string) (err error) {
 		}
 	}
 
+	if dat, err := os.ReadFile(path.Join(config.DataPath, "favicon.png")); err != nil {
+		if !errors.Is(err, os.ErrNotExist) {
+			return fmt.Errorf("[Config] %w", err)
+		}
+	} else {
+		Favicon = dat
+	}
+
 	if db, err = sql.Open("sqlite3", path.Join(config.DataPath, "goit.db")); err != nil {
 		return fmt.Errorf("[Database] %w", err)
 	}
@@ -96,6 +105,6 @@ func InitGoit(conf string) (err error) {
 	return nil
 }
 
-func GetRepoPath(username, reponame string) string {
-	return path.Join(config.DataPath, "repos", username, reponame+".git")
+func GetRepoPath(name string) string {
+	return path.Join(config.DataPath, "repos", name+".git")
 }
diff --git a/src/http.go b/src/http.go
index d2e2162..37cb9df 100644
--- a/src/http.go
+++ b/src/http.go
@@ -8,12 +8,26 @@ import (
 	"github.com/Jamozed/Goit/res"
 )
 
-var htmlError *template.Template = template.Must(template.New("error").Parse(res.Error))
+var tmpl = template.Must(template.New("error").Parse(res.Error))
+
+func init() {
+	tmpl.Option("missingkey=zero")
+
+	template.Must(tmpl.New("base/head").Parse(res.BaseHead))
+	template.Must(tmpl.New("base/repo_header").Parse(res.RepoHeader))
+
+	template.Must(tmpl.New("repo_index").Parse(res.RepoIndex))
+	template.Must(tmpl.New("repo_create").Parse(res.RepoCreate))
+
+	template.Must(tmpl.New("repo_log").Parse(res.RepoLog))
+	template.Must(tmpl.New("repo_tree").Parse(res.RepoTree))
+	template.Must(tmpl.New("repo_refs").Parse(res.RepoRefs))
+}
 
 func HttpError(w http.ResponseWriter, code int) {
 	w.WriteHeader(code)
 	s := fmt.Sprint(code) + " " + http.StatusText(code)
-	htmlError.Execute(w, struct{ Status string }{s})
+	tmpl.ExecuteTemplate(w, "error", struct{ Status string }{s})
 }
 
 func HttpNotFound(w http.ResponseWriter, r *http.Request) {
diff --git a/src/repo.go b/src/repo.go
index 400e9d2..4220c8b 100644
--- a/src/repo.go
+++ b/src/repo.go
@@ -7,13 +7,11 @@ package goit
 import (
 	"database/sql"
 	"errors"
-	"html/template"
 	"log"
 	"net/http"
 	"strings"
 	"time"
 
-	"github.com/Jamozed/Goit/res"
 	"github.com/go-git/go-git/v5"
 	"github.com/go-git/go-git/v5/plumbing"
 	"github.com/go-git/go-git/v5/plumbing/object"
@@ -30,14 +28,6 @@ type Repo struct {
 	IsPrivate     bool
 }
 
-var (
-	repoIndex  *template.Template = template.Must(template.New("repo_index").Parse(res.RepoIndex))
-	repoCreate *template.Template = template.Must(template.New("repo_create").Parse(res.RepoCreate))
-	repoLog    *template.Template = template.Must(template.New("repo_log").Parse(res.RepoLog))
-	repoTree   *template.Template = template.Must(template.New("repo_tree").Parse(res.RepoTree))
-	repoRefs   *template.Template = template.Must(template.New("repo_refs").Parse(res.RepoRefs))
-)
-
 func HandleIndex(w http.ResponseWriter, r *http.Request) {
 	authOk, uid := AuthHttp(r)
 
@@ -68,8 +58,11 @@ func HandleIndex(w http.ResponseWriter, r *http.Request) {
 		if err := rows.Err(); err != nil {
 			log.Println("[Index:SELECT:Err]", err.Error())
 			HttpError(w, http.StatusInternalServerError)
-		} else {
-			repoIndex.Execute(w, struct{ Repos []row }{repos})
+		} else if err := tmpl.ExecuteTemplate(w, "repo_index", struct {
+			Title string
+			Repos []row
+		}{"Repositories", repos}); err != nil {
+			log.Println("[Repo:Index]", err.Error())
 		}
 	}
 }
@@ -85,9 +78,9 @@ func HandleRepoCreate(w http.ResponseWriter, r *http.Request) {
 			log.Println("[RepoCreate:RepoExists]", err.Error())
 			HttpError(w, http.StatusInternalServerError)
 		} else if taken {
-			repoCreate.Execute(w, struct{ Msg string }{"Reponame is taken"})
+			tmpl.ExecuteTemplate(w, "repo_create", struct{ Msg string }{"Reponame is taken"})
 		} else if SliceContains[string](reserved, name) {
-			repoCreate.Execute(w, struct{ Msg string }{"Reponame is reserved"})
+			tmpl.ExecuteTemplate(w, "repo_create", struct{ Msg string }{"Reponame is reserved"})
 		} else {
 			if _, err := db.Exec(
 				`INSERT INTO repos (
@@ -102,7 +95,7 @@ func HandleRepoCreate(w http.ResponseWriter, r *http.Request) {
 			}
 		}
 	} else /* GET */ {
-		repoCreate.Execute(w, nil)
+		tmpl.ExecuteTemplate(w, "repo_create", nil)
 	}
 }
 
@@ -121,7 +114,7 @@ func HandleRepoLog(w http.ResponseWriter, r *http.Request) {
 	type row struct{ Date, Message, Author string }
 	commits := []row{}
 
-	if gr, err := git.PlainOpen("./" + reponame + ".git"); err != nil {
+	if gr, err := git.PlainOpen(GetRepoPath(reponame)); err != nil {
 		log.Println("[Repo:Log]", err.Error())
 		HttpError(w, http.StatusInternalServerError)
 		return
@@ -144,11 +137,13 @@ func HandleRepoLog(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	if err := repoLog.Execute(w, struct {
-		Name, Description, Url string
-		HasReadme, HasLicence  bool
-		Commits                []row
-	}{reponame, repo.Description, r.URL.Host + "/" + repo.Name + ".git", false, false, commits}); err != nil {
+	if err := tmpl.ExecuteTemplate(w, "repo_log", struct {
+		Title, Name, Description, Url string
+		HasReadme, HasLicence         bool
+		Commits                       []row
+	}{
+		"Log", reponame, repo.Description, r.URL.Host + "/" + repo.Name + ".git", false, false, commits,
+	}); err != nil {
 		log.Println("[Repo:Log]", err.Error())
 	}
 }
@@ -174,7 +169,7 @@ func HandleRepoRefs(w http.ResponseWriter, r *http.Request) {
 	bras := []bra{}
 	tags := []tag{}
 
-	if gr, err := git.PlainOpen("./" + reponame + ".git"); err != nil {
+	if gr, err := git.PlainOpen(GetRepoPath(reponame)); err != nil {
 		log.Println("[Repo:Refs]", err.Error())
 		HttpError(w, http.StatusInternalServerError)
 		return
@@ -202,12 +197,14 @@ func HandleRepoRefs(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	if err := repoRefs.Execute(w, struct {
-		Name, Description, Url string
-		HasReadme, HasLicence  bool
-		Branches               []bra
-		Tags                   []tag
-	}{reponame, repo.Description, r.URL.Host + "/" + repo.Name + ".git", false, false, bras, tags}); err != nil {
+	if err := tmpl.ExecuteTemplate(w, "repo_refs", struct {
+		Title, Name, Description, Url string
+		HasReadme, HasLicence         bool
+		Branches                      []bra
+		Tags                          []tag
+	}{
+		"Refs", reponame, repo.Description, r.URL.Host + "/" + repo.Name + ".git", false, false, bras, tags,
+	}); err != nil {
 		log.Println("[Repo:Refs]", err.Error())
 	}
 }