commit
message
Cleaner configuration, hosting all repos.
author
Ben Vogt <[email protected]>
date
2023-04-10 18:47:15
stats
13 file(s) changed,
133 insertions(+),
109 deletions(-)
files
README.md
commit.go
config.go
dev-config-gshr.toml
example-config-ghsr-simple.toml
example-config-go-git.toml
file.go
files.go
index.go
log.go
main.go
template.index.html
template.partials.html
1diff --git a/README.md b/README.md
2index 545e355..d3ee0ca 100644
3--- a/README.md
4+++ b/README.md
5@@ -10,7 +10,7 @@ directory for multiple repos, with...
6 * Individual commit page summarizing commit including diff.
7 * File list page for each repo for the current HEAD ref.
8 * File detail/preview page for each file in current HEAD ref.
9-* Statically clone-able git folder for each repo.
10+* Statically clone-able git dir for each repo.
11
12 ---
13
14@@ -45,27 +45,27 @@ Usage of gshr:
15
16 The toml file needs to be in the format:
17
18-* `base_url`: Base url for the site. Eg: `"/"` or `"https://mysite.com/git/"`.
19-* `site_name`: Site name displayed on the main index.html page that lists all repos.
20+* `site`: Site-level configuration.
21+ * `base_url`: Base url for the site. Eg: `"/"` or `"https://mysite.com/code/"`.
22+ * `name`: Site name displayed on the main index.html page that lists all repos.
23 * `repos` List of data for each repo.
24 * `name`: Name of repo to be used in display, paths.
25 * `description`: Description of repo used in html pages.
26 * `url`: Absolute, relative, or remote. eg: `/home/repo`, `./repo`, `git://`, `http://`.
27- * `published_git_url`: Optional Link to where the repo lives. Eg: `github.com/vogtb/gshr`.
28- * `host_git`: Bool of whether we should package the repo up into `{name}.git` and host it.
29+ * `alt_link`: Optional Link to where the repo lives. Eg: `github.com/vogtb/gshr`.
30
31-For example:
32+Example:
33
34 ```toml
35+[site]
36 base_url = "http://localhost/"
37-site_name = "public, self hosted git repositories"
38+name = "development site - should run on port 80"
39
40 [[repos]]
41 name = "gshr"
42 description = "git static host repo -- generates static html for repos"
43 url = "https://github.com/vogtb/gshr"
44-published_git_url = "https://github.com/vogtb/gshr"
45-host_git = true
46+alt_link = "https://github.com/vogtb/gshr"
47 ```
48
49 ---
50diff --git a/commit.go b/commit.go
51index 0c203a9..8251934 100644
52--- a/commit.go
53+++ b/commit.go
54@@ -13,7 +13,7 @@ import (
55 )
56
57 type CommitPage struct {
58- RepoData RepoData
59+ RepoData repoData
60 Author string
61 AuthorEmail string
62 Date string
63@@ -36,7 +36,7 @@ func (c *CommitPage) RenderPage(t *template.Template) {
64 checkErr(err)
65 }
66
67-func RenderAllCommitPages(data RepoData, r *git.Repository) {
68+func RenderAllCommitPages(data repoData, r *git.Repository) {
69 t, err := template.ParseFS(htmlTemplates, "template.commit.html", "template.partials.html")
70 checkErr(err)
71 ref, err := r.Head()
72diff --git a/config.go b/config.go
73index 096d8c5..819d20a 100644
74--- a/config.go
75+++ b/config.go
76@@ -1,34 +1,62 @@
77 package main
78
79 import (
80+ "errors"
81+ "fmt"
82 "os"
83 "path"
84+ "regexp"
85
86 "github.com/BurntSushi/toml"
87 )
88
89-type Config struct {
90- BaseURL string `toml:"base_url"`
91- SiteName string `toml:"site_name"`
92- Repos []Repo `toml:"repos"`
93+type config struct {
94+ Site siteConfig `toml:"site"`
95+ Repos []repoConfig `toml:"repos"`
96 }
97
98-type Repo struct {
99- Name string `toml:"name"`
100- Description string `toml:"description"`
101- URL string `toml:"url"`
102- HostGit bool `toml:"host_git"`
103- PublishedGitURL string `toml:"published_git_url"`
104+func (c *config) validate() {
105+ names := map[string]bool{}
106+ for _, r := range c.Repos {
107+ _, duplicate := names[r.Name]
108+ if duplicate {
109+ checkErr(errors.New(fmt.Sprintf("duplicate repo name: '%s'", r.Name)))
110+ }
111+ names[r.Name] = true
112+ }
113+}
114+
115+type repoConfig struct {
116+ Name string `toml:"name"`
117+ Description string `toml:"description"`
118+ URL string `toml:"url"`
119+ AltLink string `toml:"alt_link"`
120+}
121+
122+func (r *repoConfig) validate() {
123+ ok, err := regexp.MatchString(`[A-Za-z0-9_.-]`, r.Name)
124+ checkErr(err)
125+ if !ok {
126+ checkErr(errors.New("repo names only allow [A-Za-z0-9_.-]"))
127+ }
128+ if r.Name == "git" {
129+ checkErr(errors.New("repo in config cannot have the name 'git'"))
130+ }
131+}
132+
133+type siteConfig struct {
134+ BaseURL string `toml:"base_url"`
135+ Name string `toml:"name"`
136 }
137
138-// / CloneDir gets the directory that this repo was cloned into using the output directory
139-// / from the program arguments, and this repo's name.
140-func (r *Repo) CloneDir() string {
141+// cloneDir gets the directory that this repo was cloned into using the output directory
142+// from the program arguments, and this repo's name.
143+func (r *repoConfig) cloneDir() string {
144 return path.Join(args.OutputDir, r.Name, "git")
145 }
146
147-func (r *Repo) FindFileInRoot(oneOfThese map[string]bool) string {
148- dir, err := os.ReadDir(r.CloneDir())
149+func (r *repoConfig) findFileInRoot(oneOfThese map[string]bool) string {
150+ dir, err := os.ReadDir(r.cloneDir())
151 checkErr(err)
152 for _, e := range dir {
153 name := e.Name()
154@@ -39,16 +67,16 @@ func (r *Repo) FindFileInRoot(oneOfThese map[string]bool) string {
155 return ""
156 }
157
158-func ParseConfiguration(data string) Config {
159- conf := Config{}
160+func parseConfig(data string) config {
161+ conf := config{}
162 _, err := toml.Decode(data, &conf)
163 checkErr(err)
164 return conf
165 }
166
167-type RepoData struct {
168- Repo
169- PublishedGitURL string
170+type repoData struct {
171+ repoConfig
172+ AltLink string
173 BaseURL string
174 HeadData HeadData
175 ReadMePath string
176diff --git a/dev-config-gshr.toml b/dev-config-gshr.toml
177index f5ac421..52835e5 100644
178--- a/dev-config-gshr.toml
179+++ b/dev-config-gshr.toml
180@@ -1,9 +1,9 @@
181+[site]
182 base_url = "http://localhost/"
183-site_name = "development site - should run on port 80"
184+name = "development site - should run on port 80"
185
186 [[repos]]
187 name = "gshr"
188 description = "git static host repo -- generates static html for repos"
189 url = "https://github.com/vogtb/gshr"
190-published_git_url = "https://github.com/vogtb/gshr"
191-host_git = true
192+alt_link = "https://github.com/vogtb/gshr"
193diff --git a/example-config-ghsr-simple.toml b/example-config-ghsr-simple.toml
194index c52f5cf..41ee112 100644
195--- a/example-config-ghsr-simple.toml
196+++ b/example-config-ghsr-simple.toml
197@@ -1,9 +1,9 @@
198+[site]
199 base_url = "http://localhost/"
200-site_name = "public, self hosted git repositories"
201+name = "public, self hosted git repositories"
202
203 [[repos]]
204 name = "gshr"
205 description = "git static host repo -- generates static html for repos"
206 url = "https://github.com/vogtb/gshr"
207-published_git_url = "https://github.com/vogtb/gshr"
208-host_git = true
209+alt_link = "https://github.com/vogtb/gshr"
210diff --git a/example-config-go-git.toml b/example-config-go-git.toml
211index d626e9a..7389c68 100644
212--- a/example-config-go-git.toml
213+++ b/example-config-go-git.toml
214@@ -1,9 +1,9 @@
215+[site]
216 base_url = "http://localhost/"
217-site_name = "example of a couple go repos on github"
218+name = "example of a couple go repos on github"
219
220 [[repos]]
221 name = "go-git"
222 description = "A highly extensible Git implementation in pure Go."
223 url = "https://github.com/go-git/go-git"
224-published_git_url = "https://github.com/go-git/go-git"
225-host_git = true
226+alt_link = "https://github.com/go-git/go-git"
227diff --git a/file.go b/file.go
228index d14471c..c275856 100644
229--- a/file.go
230+++ b/file.go
231@@ -11,7 +11,7 @@ import (
232 )
233
234 type FilePage struct {
235- RepoData RepoData
236+ RepoData repoData
237 Mode string
238 Name string
239 Size string
240@@ -35,20 +35,20 @@ func (f *FilePage) RenderPage(t *template.Template) {
241 checkErr(err)
242 }
243
244-func RenderSingleFilePages(data RepoData) {
245+func RenderSingleFilePages(data repoData) {
246 t, err := template.ParseFS(htmlTemplates, "template.file.html", "template.partials.html")
247 checkErr(err)
248- err = filepath.Walk(data.CloneDir(), func(filename string, info fs.FileInfo, err error) error {
249+ err = filepath.Walk(data.cloneDir(), func(filename string, info fs.FileInfo, err error) error {
250 if info.IsDir() && info.Name() == ".git" {
251 return filepath.SkipDir
252 }
253
254 if !info.IsDir() {
255 ext := filepath.Ext(filename)
256- _, canRenderExtension := settings.TextExtensions[ext]
257- _, canRenderByFullName := settings.PlainFiles[filepath.Base(filename)]
258+ _, canRenderExtension := stt.TextExtensions[ext]
259+ _, canRenderByFullName := stt.PlainFiles[filepath.Base(filename)]
260 canRender := canRenderExtension || canRenderByFullName
261- partialPath, _ := strings.CutPrefix(filename, data.CloneDir())
262+ partialPath, _ := strings.CutPrefix(filename, data.cloneDir())
263 destDir := path.Join(args.OutputDir, data.Name, "files", partialPath)
264 outputName := path.Join(args.OutputDir, data.Name, "files", partialPath, "index.html")
265 var content template.HTML
266diff --git a/files.go b/files.go
267index d8bb711..5254499 100644
268--- a/files.go
269+++ b/files.go
270@@ -18,7 +18,7 @@ type FileOverview struct {
271 }
272
273 type FilesPage struct {
274- RepoData RepoData
275+ RepoData repoData
276 Files []FileOverview
277 }
278
279@@ -30,11 +30,11 @@ func (f *FilesPage) RenderPage(t *template.Template) {
280 checkErr(err)
281 }
282
283-func RenderAllFilesPage(data RepoData) {
284+func RenderAllFilesPage(data repoData) {
285 t, err := template.ParseFS(htmlTemplates, "template.files.html", "template.partials.html")
286 checkErr(err)
287 files := make([]FileOverview, 0)
288- err = filepath.Walk(data.CloneDir(), func(filename string, info fs.FileInfo, err error) error {
289+ err = filepath.Walk(data.cloneDir(), func(filename string, info fs.FileInfo, err error) error {
290 if info.IsDir() && info.Name() == ".git" {
291 return filepath.SkipDir
292 }
293@@ -42,7 +42,7 @@ func RenderAllFilesPage(data RepoData) {
294 if !info.IsDir() {
295 info, err := os.Stat(filename)
296 checkErr(err)
297- Name, _ := strings.CutPrefix(filename, data.CloneDir())
298+ Name, _ := strings.CutPrefix(filename, data.cloneDir())
299 Name, _ = strings.CutPrefix(Name, "/")
300 tf := FileOverview{
301 Origin: filename,
302diff --git a/index.go b/index.go
303index f1fc94a..e27b16a 100644
304--- a/index.go
305+++ b/index.go
306@@ -8,7 +8,7 @@ import (
307
308 type IndexPage struct {
309 HeadData HeadData
310- Repos []RepoData
311+ Repos []repoData
312 }
313
314 func (l *IndexPage) RenderPage(t *template.Template) {
315@@ -19,13 +19,13 @@ func (l *IndexPage) RenderPage(t *template.Template) {
316 checkErr(err)
317 }
318
319-func RenderIndexPage(repos []RepoData) {
320+func RenderIndexPage(repos []repoData) {
321 t, err := template.ParseFS(htmlTemplates, "template.index.html", "template.partials.html")
322 checkErr(err)
323 (&IndexPage{
324 HeadData: HeadData{
325- BaseURL: config.BaseURL,
326- SiteName: config.SiteName,
327+ BaseURL: conf.Site.BaseURL,
328+ SiteName: conf.Site.Name,
329 },
330 Repos: repos,
331 }).RenderPage(t)
332diff --git a/log.go b/log.go
333index 20dc28b..870c35b 100644
334--- a/log.go
335+++ b/log.go
336@@ -20,7 +20,7 @@ type LogPageCommit struct {
337 }
338
339 type LogPage struct {
340- RepoData RepoData
341+ RepoData repoData
342 HasReadMe bool
343 ReadMePath string
344 Commits []LogPageCommit
345@@ -34,7 +34,7 @@ func (l *LogPage) RenderPage(t *template.Template) {
346 checkErr(err)
347 }
348
349-func RenderLogPage(data RepoData, r *git.Repository) {
350+func RenderLogPage(data repoData, r *git.Repository) {
351 t, err := template.ParseFS(htmlTemplates, "template.log.html", "template.partials.html")
352 checkErr(err)
353 commits := make([]LogPageCommit, 0)
354diff --git a/main.go b/main.go
355index 3a333be..1c48629 100644
356--- a/main.go
357+++ b/main.go
358@@ -27,17 +27,17 @@ var css []byte
359 //go:embed favicon.ico
360 var favicon []byte
361
362-var args CmdArgs
363+var args cmArgs
364
365-var config Config
366+var conf config
367
368-var settings Settings
369+var stt settings
370
371 func main() {
372 var r *git.Repository = &git.Repository{}
373 Init()
374- allRepoData := []RepoData{}
375- for _, repo := range config.Repos {
376+ allRepoData := []repoData{}
377+ for _, repo := range conf.Repos {
378 data := CloneAndGetData(repo, r)
379 allRepoData = append(allRepoData, data)
380 RenderLogPage(data, r)
381@@ -47,7 +47,7 @@ func main() {
382 }
383 RenderIndexPage(allRepoData)
384 renderAssets()
385- for _, repo := range config.Repos {
386+ for _, repo := range conf.Repos {
387 hostRepo(repo)
388 }
389 }
390@@ -56,7 +56,7 @@ func Init() {
391 log.SetFlags(0)
392 log.SetOutput(new(logger))
393 args = DefaultCmdArgs()
394- settings = DefaultSettings()
395+ stt = DefaultSettings()
396 pwd, err := os.Getwd()
397 checkErr(err)
398 args.Wd = pwd
399@@ -81,31 +81,31 @@ func Init() {
400 configFileBytes, err := os.ReadFile(args.ConfigPath)
401 configString := string(configFileBytes)
402 checkErr(err)
403- config = ParseConfiguration(configString)
404- debug("base_url '%v'", config.BaseURL)
405- debug("site_name '%v'", config.SiteName)
406+ conf = parseConfig(configString)
407+ debug("base_url '%v'", conf.Site.BaseURL)
408+ debug("site_name '%v'", conf.Site.Name)
409 }
410
411-func CloneAndGetData(repo Repo, r *git.Repository) RepoData {
412- err := os.MkdirAll(repo.CloneDir(), 0755)
413+func CloneAndGetData(repo repoConfig, r *git.Repository) repoData {
414+ err := os.MkdirAll(repo.cloneDir(), 0755)
415 checkErr(err)
416 err = os.MkdirAll(path.Join(args.OutputDir, repo.Name), 0755)
417 checkErr(err)
418 debug("cloning '%v'", repo.Name)
419- repoRef, err := git.PlainClone(repo.CloneDir(), false, &git.CloneOptions{
420+ repoRef, err := git.PlainClone(repo.cloneDir(), false, &git.CloneOptions{
421 URL: repo.URL,
422 })
423 checkErr(err)
424- data := RepoData{
425- Repo: repo,
426- PublishedGitURL: repo.PublishedGitURL,
427- BaseURL: config.BaseURL,
428+ data := repoData{
429+ repoConfig: repo,
430+ AltLink: repo.AltLink,
431+ BaseURL: conf.Site.BaseURL,
432 HeadData: HeadData{
433- BaseURL: config.BaseURL,
434- SiteName: config.SiteName,
435+ BaseURL: conf.Site.BaseURL,
436+ SiteName: conf.Site.Name,
437 },
438- ReadMePath: repo.FindFileInRoot(settings.AllowedReadMeFiles),
439- LicenseFilePath: repo.FindFileInRoot(settings.AllowedLicenseFiles),
440+ ReadMePath: repo.findFileInRoot(stt.AllowedReadMeFiles),
441+ LicenseFilePath: repo.findFileInRoot(stt.AllowedLicenseFiles),
442 }
443 *r = *repoRef
444 return data
445@@ -118,26 +118,20 @@ func renderAssets() {
446 checkErr(os.WriteFile(path.Join(args.OutputDir, "favicon.ico"), favicon, 0666))
447 }
448
449-func hostRepo(data Repo) {
450- if data.HostGit {
451- debug("hosting of '%v' is ON", data.Name)
452- old := path.Join(data.CloneDir(), ".git")
453- renamed := path.Join(args.OutputDir, fmt.Sprintf("%v.git", data.Name))
454- repoFiles := path.Join(args.OutputDir, data.Name, "git")
455- final := path.Join(args.OutputDir, "git", data.Name)
456- debug("renaming '%v', new %v", data.Name, renamed)
457- os.MkdirAll(path.Join(args.OutputDir, "git"), 0777)
458- checkErr(os.Rename(old, renamed))
459- debug("running 'git update-server-info' in %v", renamed)
460- cmd := exec.Command("git", "update-server-info")
461- cmd.Dir = renamed
462- checkErr(cmd.Run())
463- os.RemoveAll(repoFiles)
464- checkErr(os.Rename(renamed, final))
465- debug("hosting '%v' at %v", data.Name, final)
466- } else {
467- debug("hosting of '%v' is OFF", data.Name)
468- }
469+func hostRepo(data repoConfig) {
470+ debug("hosting '%v'", data.Name)
471+ old := path.Join(data.cloneDir(), ".git")
472+ renamed := path.Join(args.OutputDir, fmt.Sprintf("%v.git", data.Name))
473+ repoFiles := path.Join(args.OutputDir, data.Name, "git")
474+ final := path.Join(args.OutputDir, fmt.Sprintf("%v.git", data.Name))
475+ debug("renaming '%v', new %v", data.Name, renamed)
476+ checkErr(os.Rename(old, renamed))
477+ debug("running 'git update-server-info' in %v", renamed)
478+ cmd := exec.Command("git", "update-server-info")
479+ cmd.Dir = renamed
480+ checkErr(cmd.Run())
481+ os.RemoveAll(repoFiles)
482+ debug("hosting '%v' at %v", data.Name, final)
483 }
484
485 type logger struct{}
486@@ -190,30 +184,30 @@ func highlight(pathOrExtension string, data *string) string {
487 return buf.String()
488 }
489
490-type CmdArgs struct {
491+type cmArgs struct {
492 Silent bool
493 Wd string
494 ConfigPath string
495 OutputDir string
496 }
497
498-func DefaultCmdArgs() CmdArgs {
499- return CmdArgs{
500+func DefaultCmdArgs() cmArgs {
501+ return cmArgs{
502 Silent: true,
503 ConfigPath: "",
504 OutputDir: "",
505 }
506 }
507
508-type Settings struct {
509+type settings struct {
510 TextExtensions map[string]bool
511 PlainFiles map[string]bool
512 AllowedLicenseFiles map[string]bool
513 AllowedReadMeFiles map[string]bool
514 }
515
516-func DefaultSettings() Settings {
517- return Settings{
518+func DefaultSettings() settings {
519+ return settings{
520 TextExtensions: map[string]bool{
521 ".c": true,
522 ".cc": true,
523diff --git a/template.index.html b/template.index.html
524index 2cc747a..d2c80d9 100644
525--- a/template.index.html
526+++ b/template.index.html
527@@ -36,17 +36,9 @@
528 </r-cell>
529 <r-cell span="7">
530 <div class="ellipsis">
531- {{ if .HostGit }}
532- <a class="mono" href="{{ .BaseURL }}git/{{ .Name }}">
533- {{ .BaseURL }}git/{{ .Name }}
534+ <a class="mono" href="{{ .BaseURL }}git/{{ .Name }}.git">
535+ {{ .BaseURL }}/{{ .Name }}.git
536 </a>
537- {{ else if not (eq .PublishedGitURL "") }}
538- <a class="mono" href="{{ .PublishedGitURL }}">
539- {{ .PublishedGitURL }}
540- </a>
541- {{ else }}
542- <em>none</em>
543- {{ end }}
544 </div>
545 </r-cell>
546 {{ end }}
547diff --git a/template.partials.html b/template.partials.html
548index 3204601..4cca5ba 100644
549--- a/template.partials.html
550+++ b/template.partials.html
551@@ -32,10 +32,10 @@
552 git clone {{ .HeadData.BaseURL }}{{ .Name }}.git
553 </div>
554 </r-cell>
555- {{ if not (eq .PublishedGitURL "") }}
556+ {{ if not (eq .AltLink "") }}
557 <r-cell span="12">
558 <div class="ellipsis">
559- alt url: <a href="{{ .PublishedGitURL }}">{{ .PublishedGitURL }}</a>
560+ alt url: <a href="{{ .AltLink }}">{{ .AltLink }}</a>
561 </div>
562 </r-cell>
563 {{ end }}