gshr
git static host repo -- generates static html for repos
git clone https://git.vogt.world/gshr.git
Log | Files | README.md | LICENSE
← Commit log
commit
message
Rough multi-repo using toml config
author
Ben Vogt <[email protected]>
date
2023-04-04 23:17:46
stats
12 file(s) changed, 291 insertions(+), 158 deletions(-)
files
Makefile
commit.go
config.go
file.go
files.go
go.mod
go.sum
gshr.toml
index.go
log.go
main.go
templates/index.html
  1diff --git a/Makefile b/Makefile
  2index 030420c..87a4dec 100644
  3--- a/Makefile
  4+++ b/Makefile
  5@@ -24,9 +24,7 @@ target/gshr-${OS}-${ARCH}-${ENVIRONMENT}.bin: Makefile target $(wildcard *.go)
  6 
  7 dev: Makefile target target/output target/cloning target/gshr-${OS}-${ARCH}-${ENVIRONMENT}.bin
  8 	./target/gshr-${OS}-${ARCH}-${ENVIRONMENT}.bin \
  9-    --desc="git static host repo -- generates static html for repos" \
 10-    --repo=/Users/bvogt/dev/src/ben/gshr \
 11-    --git-url="[email protected]:vogtb/gshr.git" \
 12+    --config=$(PWD)/gshr.toml \
 13     --output=$(PWD)/target/output \
 14     --clone=$(PWD)/target/cloning \
 15 		&& \
 16diff --git a/commit.go b/commit.go
 17index da1354e..365d6e3 100644
 18--- a/commit.go
 19+++ b/commit.go
 20@@ -28,15 +28,15 @@ type CommitPage struct {
 21 }
 22 
 23 func (c *CommitPage) Render(t *template.Template) {
 24-	err := os.MkdirAll(path.Join(config.OutputDir, "commit", c.Hash), 0755)
 25+	err := os.MkdirAll(path.Join(args.OutputDir, c.RepoData.Name, "commit", c.Hash), 0755)
 26 	checkErr(err)
 27-	output, err := os.Create(path.Join(config.OutputDir, "commit", c.Hash, "index.html"))
 28+	output, err := os.Create(path.Join(args.OutputDir, c.RepoData.Name, "commit", c.Hash, "index.html"))
 29 	checkErr(err)
 30 	err = t.Execute(output, c)
 31 	checkErr(err)
 32 }
 33 
 34-func RenderAllCommitPages(r *git.Repository) {
 35+func RenderAllCommitPages(data RepoData, r *git.Repository) {
 36 	t, err := template.ParseFS(htmlTemplates, "templates/commit.html", "templates/partials.html")
 37 	checkErr(err)
 38 	ref, err := r.Head()
 39@@ -89,7 +89,7 @@ func RenderAllCommitPages(r *git.Repository) {
 40 		}
 41 		checkErr(err)
 42 		(&CommitPage{
 43-			RepoData:        config.RepoData,
 44+			RepoData:        data,
 45 			Author:          c.Author.Name,
 46 			AuthorEmail:     c.Author.Email,
 47 			Message:         c.Message,
 48diff --git a/config.go b/config.go
 49index fb73fb4..5f76096 100644
 50--- a/config.go
 51+++ b/config.go
 52@@ -1,104 +1,41 @@
 53 package main
 54 
 55+import (
 56+	"github.com/BurntSushi/toml"
 57+)
 58+
 59 type Config struct {
 60-	DebugOn             bool
 61-	Repo                string
 62-	OutputDir           string
 63-	CloneDir            string
 64-	RepoData            RepoData
 65-	AllowedLicenseFiles map[string]bool
 66-	AllowedReadMeFiles  map[string]bool
 67-	TextExtensions      map[string]bool
 68-	PlainFiles          map[string]bool
 69+	Repos   []Repo
 70+	BaseURL string
 71+}
 72+
 73+type Repo struct {
 74+	Name        string
 75+	Description string
 76+	Path        string
 77+	GitURL      string
 78+}
 79+
 80+func ParseConfiguration(data string) Config {
 81+	conf := Config{}
 82+	_, err := toml.Decode(data, &conf)
 83+	checkErr(err)
 84+	return conf
 85+}
 86+
 87+type CmdArgs struct {
 88+	DebugOn    bool
 89+	ConfigFile string
 90+	OutputDir  string
 91+	CloneDir   string
 92 }
 93 
 94-func DefaultConfig() Config {
 95-	return Config{
 96-		DebugOn:   true,
 97-		Repo:      "",
 98-		OutputDir: "",
 99-		CloneDir:  "",
100-		RepoData: RepoData{
101-			Name:            "",
102-			GitURL:          "",
103-			Description:     "",
104-			BaseURL:         "/",
105-			ReadMePath:      "",
106-			LicenseFilePath: "",
107-		},
108-		TextExtensions: map[string]bool{
109-			".c":          true,
110-			".cc":         true,
111-			".conf":       true,
112-			".config":     true,
113-			".cpp":        true,
114-			".cs":         true,
115-			".css":        true,
116-			".csv":        true,
117-			".Dockerfile": true,
118-			".gitignore":  true,
119-			".gitmodules": true,
120-			".go":         true,
121-			".h":          true,
122-			".htm":        true,
123-			".html":       true,
124-			".iml":        true,
125-			".js":         true,
126-			".json":       true,
127-			".jsx":        true,
128-			".less":       true,
129-			".lock":       true,
130-			".log":        true,
131-			".Makefile":   true,
132-			".md":         true,
133-			".mod":        true,
134-			".php":        true,
135-			".py":         true,
136-			".rb":         true,
137-			".rs":         true,
138-			".scss":       true,
139-			".sql":        true,
140-			".sum":        true,
141-			".svg":        true,
142-			".toml":       true,
143-			".ts":         true,
144-			".tsv":        true,
145-			".tsx":        true,
146-			".txt":        true,
147-			".xml":        true,
148-			".yaml":       true,
149-			".yml":        true,
150-		},
151-		PlainFiles: map[string]bool{
152-			"Dockerfile":  true,
153-			"license-mit": true,
154-			"LICENSE-MIT": true,
155-			"license":     true,
156-			"LICENSE":     true,
157-			"Makefile":    true,
158-			"readme":      true,
159-			"Readme":      true,
160-			"ReadMe":      true,
161-			"README":      true,
162-		},
163-		AllowedLicenseFiles: map[string]bool{
164-			"license-mit": true,
165-			"LICENSE-MIT": true,
166-			"license.md":  true,
167-			"LICENSE.md":  true,
168-			"license.txt": true,
169-			"LICENSE.txt": true,
170-			"LICENSE":     true,
171-		},
172-		AllowedReadMeFiles: map[string]bool{
173-			"readme.md":  true,
174-			"Readme.md":  true,
175-			"ReadMe.md":  true,
176-			"README.md":  true,
177-			"readme.txt": true,
178-			"README.txt": true,
179-			"README":     true,
180-		},
181+func DefaultCmdArgs() CmdArgs {
182+	return CmdArgs{
183+		DebugOn:    true,
184+		ConfigFile: "",
185+		OutputDir:  "",
186+		CloneDir:   "",
187 	}
188 }
189 
190diff --git a/file.go b/file.go
191index a31b6e8..0cf7e18 100644
192--- a/file.go
193+++ b/file.go
194@@ -41,28 +41,28 @@ func (f *FilePage) Render(t *template.Template) {
195 	checkErr(err)
196 }
197 
198-func RenderSingleFilePages() {
199+func RenderSingleFilePages(data RepoData) {
200 	t, err := template.ParseFS(htmlTemplates, "templates/file.html", "templates/partials.html")
201 	checkErr(err)
202-	err = filepath.Walk(config.CloneDir, func(filename string, info fs.FileInfo, err error) error {
203+	err = filepath.Walk(path.Join(args.CloneDir, data.Name), func(filename string, info fs.FileInfo, err error) error {
204 		if info.IsDir() && info.Name() == ".git" {
205 			return filepath.SkipDir
206 		}
207 
208 		if !info.IsDir() {
209 			ext := filepath.Ext(filename)
210-			_, canRenderExtension := config.TextExtensions[ext]
211-			_, canRenderByFullName := config.PlainFiles[filepath.Base(filename)]
212-			partialPath, _ := strings.CutPrefix(filename, config.CloneDir)
213-			outputName := path.Join(config.OutputDir, "files", partialPath, "index.html")
214-			debug("reading = %v", partialPath)
215+			_, canRenderExtension := settings.TextExtensions[ext]
216+			_, canRenderByFullName := settings.PlainFiles[filepath.Base(filename)]
217+			partialPath, _ := strings.CutPrefix(filename, path.Join(args.CloneDir, data.Name))
218+			outputName := path.Join(args.OutputDir, "files", partialPath, "index.html")
219+			debug("reading %v %v", data.Name, partialPath)
220 			(&FilePage{
221-				RepoData:       config.RepoData,
222+				RepoData:       data,
223 				Extension:      ext,
224 				CanRender:      canRenderExtension || canRenderByFullName,
225 				Origin:         filename,
226 				Destination:    outputName,
227-				DestinationDir: path.Join(config.OutputDir, "files", partialPath),
228+				DestinationDir: path.Join(args.OutputDir, data.Name, "files", partialPath),
229 			}).Render(t)
230 		}
231 		return nil
232diff --git a/files.go b/files.go
233index 57ee8dc..a5373dc 100644
234--- a/files.go
235+++ b/files.go
236@@ -22,18 +22,18 @@ type FilesPage struct {
237 	Files    []FileOverview
238 }
239 
240-func (fi *FilesPage) Render(t *template.Template) {
241-	output, err := os.Create(path.Join(config.OutputDir, "files.html"))
242+func (f *FilesPage) Render(t *template.Template) {
243+	output, err := os.Create(path.Join(args.OutputDir, f.RepoData.Name, "files.html"))
244 	checkErr(err)
245-	err = t.Execute(output, fi)
246+	err = t.Execute(output, f)
247 	checkErr(err)
248 }
249 
250-func RenderAllFilesPage() {
251+func RenderAllFilesPage(data RepoData) {
252 	t, err := template.ParseFS(htmlTemplates, "templates/files.html", "templates/partials.html")
253 	checkErr(err)
254 	files := make([]FileOverview, 0)
255-	err = filepath.Walk(config.CloneDir, func(filename string, info fs.FileInfo, err error) error {
256+	err = filepath.Walk(path.Join(args.CloneDir, data.Name), func(filename string, info fs.FileInfo, err error) error {
257 		if info.IsDir() && info.Name() == ".git" {
258 			return filepath.SkipDir
259 		}
260@@ -41,7 +41,7 @@ func RenderAllFilesPage() {
261 		if !info.IsDir() {
262 			info, err := os.Stat(filename)
263 			checkErr(err)
264-			Name, _ := strings.CutPrefix(filename, config.CloneDir)
265+			Name, _ := strings.CutPrefix(filename, path.Join(args.CloneDir, data.Name))
266 			Name, _ = strings.CutPrefix(Name, "/")
267 			tf := FileOverview{
268 				Origin: filename,
269@@ -55,7 +55,7 @@ func RenderAllFilesPage() {
270 	})
271 	checkErr(err)
272 	index := FilesPage{
273-		RepoData: config.RepoData,
274+		RepoData: data,
275 		Files:    files,
276 	}
277 	index.Render(t)
278diff --git a/go.mod b/go.mod
279index 8f34af0..716b88c 100644
280--- a/go.mod
281+++ b/go.mod
282@@ -7,6 +7,8 @@ require (
283 	github.com/go-git/go-git/v5 v5.6.1
284 )
285 
286+require github.com/BurntSushi/toml v1.2.1 // indirect
287+
288 require (
289 	github.com/Microsoft/go-winio v0.5.2 // indirect
290 	github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect
291@@ -19,6 +21,7 @@ require (
292 	github.com/imdario/mergo v0.3.13 // indirect
293 	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
294 	github.com/kevinburke/ssh_config v1.2.0 // indirect
295+	github.com/pelletier/go-toml/v2 v2.0.7
296 	github.com/pjbgf/sha1cd v0.3.0 // indirect
297 	github.com/sergi/go-diff v1.3.1 // indirect
298 	github.com/skeema/knownhosts v1.1.0 // indirect
299diff --git a/go.sum b/go.sum
300index a3a1f90..f62a225 100644
301--- a/go.sum
302+++ b/go.sum
303@@ -1,3 +1,5 @@
304+github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
305+github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
306 github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
307 github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
308 github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA=
309@@ -52,6 +54,8 @@ github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
310 github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
311 github.com/mmcloughlin/avo v0.5.0/go.mod h1:ChHFdoV7ql95Wi7vuq2YT1bwCJqiWdZrQ1im3VujLYM=
312 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
313+github.com/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us=
314+github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
315 github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
316 github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
317 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
318@@ -65,10 +69,15 @@ github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic
319 github.com/skeema/knownhosts v1.1.0 h1:Wvr9V0MxhjRbl3f9nMnKnFfiWTJmtECJ9Njkea3ysW0=
320 github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXijpFO6roag=
321 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
322+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
323+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
324 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
325 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
326-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
327 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
328+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
329+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
330+github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
331+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
332 github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
333 github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
334 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
335@@ -144,6 +153,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
336 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
337 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
338 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
339-gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
340 gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
341+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
342+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
343 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
344diff --git a/gshr.toml b/gshr.toml
345new file mode 100644
346index 0000000..748275a
347--- /dev/null
348+++ b/gshr.toml
349@@ -0,0 +1,7 @@
350+base_url = "/"
351+
352+[[repos]]
353+name = "gshr"
354+description = "git static host repo -- generates static html for repos"
355+path = "/Users/bvogt/dev/src/ben/gshr"
356+git_url = "[email protected]:vogtb/gshr.git"
357diff --git a/index.go b/index.go
358new file mode 100644
359index 0000000..a696b52
360--- /dev/null
361+++ b/index.go
362@@ -0,0 +1,28 @@
363+package main
364+
365+import (
366+	"html/template"
367+	"os"
368+	"path"
369+)
370+
371+type IndexPage struct {
372+	BaseURL string
373+	Repos   []RepoData
374+}
375+
376+func (l *IndexPage) Render(t *template.Template) {
377+	output, err := os.Create(path.Join(args.OutputDir, "index.html"))
378+	checkErr(err)
379+	err = t.Execute(output, l)
380+	checkErr(err)
381+}
382+
383+func RenderIndexPage(repos []RepoData) {
384+	t, err := template.ParseFS(htmlTemplates, "templates/index.html", "templates/partials.html")
385+	checkErr(err)
386+	(&IndexPage{
387+		BaseURL: config.BaseURL,
388+		Repos:   repos,
389+	}).Render(t)
390+}
391diff --git a/log.go b/log.go
392index 79675c8..4c7f849 100644
393--- a/log.go
394+++ b/log.go
395@@ -26,14 +26,14 @@ type LogPage struct {
396 	Commits    []LogPageCommit
397 }
398 
399-func (mi *LogPage) Render(t *template.Template) {
400-	output, err := os.Create(path.Join(config.OutputDir, "log.html"))
401+func (l *LogPage) Render(t *template.Template) {
402+	output, err := os.Create(path.Join(args.OutputDir, l.RepoData.Name, "log.html"))
403 	checkErr(err)
404-	err = t.Execute(output, mi)
405+	err = t.Execute(output, l)
406 	checkErr(err)
407 }
408 
409-func RenderLogPage(r *git.Repository) {
410+func RenderLogPage(data RepoData, r *git.Repository) {
411 	t, err := template.ParseFS(htmlTemplates, "templates/log.html", "templates/partials.html")
412 	checkErr(err)
413 	commits := make([]LogPageCommit, 0)
414@@ -64,7 +64,7 @@ func RenderLogPage(r *git.Repository) {
415 	})
416 	checkErr(err)
417 	(&LogPage{
418-		RepoData: config.RepoData,
419+		RepoData: data,
420 		Commits:  commits,
421 	}).Render(t)
422 }
423diff --git a/main.go b/main.go
424index ac1bef3..ea0e1ec 100644
425--- a/main.go
426+++ b/main.go
427@@ -4,7 +4,6 @@ import (
428 	"bytes"
429 	"embed"
430 	_ "embed"
431-	"errors"
432 	"flag"
433 	"fmt"
434 	"math/rand"
435@@ -20,54 +19,67 @@ import (
436 //go:embed templates/*
437 var htmlTemplates embed.FS
438 
439+var args CmdArgs
440+
441 var config Config
442 
443+var settings Settings
444+
445 func main() {
446 	var r *git.Repository = &git.Repository{}
447 	Init()
448-	CloneAndInfo(r)
449-	RenderLogPage(r)
450-	RenderAllCommitPages(r)
451-	RenderAllFilesPage()
452-	RenderSingleFilePages()
453+	allRepoData := []RepoData{}
454+	for _, repo := range config.Repos {
455+		data := CloneAndGetData(repo, r)
456+		allRepoData = append(allRepoData, data)
457+		RenderLogPage(data, r)
458+		RenderAllCommitPages(data, r)
459+		RenderAllFilesPage(data)
460+		RenderSingleFilePages(data)
461+	}
462+	RenderIndexPage(allRepoData)
463 }
464 
465 func Init() {
466-	config = DefaultConfig()
467-	flag.StringVar(&config.Repo, "repo", "", "Repo to use.")
468-	flag.BoolVar(&config.DebugOn, "debug", true, "Run in debug mode.")
469-	flag.StringVar(&config.OutputDir, "output", "", "Dir of output.")
470-	flag.StringVar(&config.CloneDir, "clone", "", "Dir to clone into. Default is /tmp/${rand}")
471-	flag.StringVar(&config.RepoData.BaseURL, "base-url", "/", "Base URL for serving.")
472-	flag.StringVar(&config.RepoData.GitURL, "git-url", "", "Show where repo is hosted.")
473-	flag.StringVar(&config.RepoData.Description, "desc", "<no description>", "Description to show.")
474+	args = DefaultCmdArgs()
475+	settings = DefaultSettings()
476+	flag.StringVar(&args.ConfigFile, "config", "", "Config file.")
477+	flag.BoolVar(&args.DebugOn, "debug", true, "Run in debug mode.")
478+	flag.StringVar(&args.OutputDir, "output", "", "Dir of output.")
479+	flag.StringVar(&args.CloneDir, "clone", "", "Dir to clone into. Default is /tmp/${rand}")
480 	flag.Parse()
481 
482-	if config.Repo == "" {
483-		checkErr(errors.New("--repo flag is required"))
484-	}
485-
486-	if config.CloneDir == "" {
487-		config.CloneDir = fmt.Sprintf("/tmp/gshr-temp-clone-%v", rand.Uint32())
488+	if args.CloneDir == "" {
489+		args.CloneDir = fmt.Sprintf("/tmp/gshr-temp-clone-%v", rand.Uint32())
490 	}
491 
492-	config.RepoData.BaseURL = path.Join(config.RepoData.BaseURL, "/")
493-	config.RepoData.Name = path.Clean(path.Base(config.Repo))
494-
495-	debug("repo = %v", config.Repo)
496-	debug("output = %v", config.OutputDir)
497-	debug("clone = %v", config.CloneDir)
498-	debug("base-url = %v", config.RepoData.BaseURL)
499+	debug("config = %v", args.ConfigFile)
500+	debug("output = %v", args.OutputDir)
501+	debug("clone = %v", args.CloneDir)
502+	configFileByes, err := os.ReadFile(args.ConfigFile)
503+	checkErr(err)
504+	config = ParseConfiguration(string(configFileByes))
505 }
506 
507-func CloneAndInfo(r *git.Repository) {
508-	repo, err := git.PlainClone(config.CloneDir, false, &git.CloneOptions{
509-		URL: config.Repo,
510+func CloneAndGetData(repo Repo, r *git.Repository) RepoData {
511+	err := os.MkdirAll(path.Join(args.CloneDir, repo.Name), 0755)
512+	checkErr(err)
513+	err = os.MkdirAll(path.Join(args.OutputDir, repo.Name), 0755)
514+	checkErr(err)
515+	repoRef, err := git.PlainClone(path.Join(args.CloneDir, repo.Name), false, &git.CloneOptions{
516+		URL: repo.Path,
517 	})
518 	checkErr(err)
519-	config.RepoData.ReadMePath = findFileInRoot(config.AllowedReadMeFiles)
520-	config.RepoData.LicenseFilePath = findFileInRoot(config.AllowedLicenseFiles)
521-	*r = *repo
522+	data := RepoData{
523+		Name:            repo.Name,
524+		GitURL:          repo.GitURL,
525+		Description:     repo.Description,
526+		BaseURL:         config.BaseURL,
527+		ReadMePath:      findFileInRoot(repo.Name, settings.AllowedReadMeFiles),
528+		LicenseFilePath: findFileInRoot(repo.Name, settings.AllowedLicenseFiles),
529+	}
530+	*r = *repoRef
531+	return data
532 }
533 
534 func checkErr(err error) {
535@@ -78,7 +90,7 @@ func checkErr(err error) {
536 }
537 
538 func debug(format string, a ...any) {
539-	if config.DebugOn {
540+	if args.DebugOn {
541 		fmt.Printf(format, a...)
542 		fmt.Print("\n")
543 	}
544@@ -105,8 +117,8 @@ func highlight(pathOrExtension string, data *string) string {
545 	return buf.String()
546 }
547 
548-func findFileInRoot(oneOfThese map[string]bool) string {
549-	dir, err := os.ReadDir(config.CloneDir)
550+func findFileInRoot(name string, oneOfThese map[string]bool) string {
551+	dir, err := os.ReadDir(path.Join(args.CloneDir, name))
552 	checkErr(err)
553 	for _, e := range dir {
554 		name := e.Name()
555@@ -116,3 +128,88 @@ func findFileInRoot(oneOfThese map[string]bool) string {
556 	}
557 	return ""
558 }
559+
560+type Settings struct {
561+	TextExtensions      map[string]bool
562+	PlainFiles          map[string]bool
563+	AllowedLicenseFiles map[string]bool
564+	AllowedReadMeFiles  map[string]bool
565+}
566+
567+func DefaultSettings() Settings {
568+	return Settings{
569+		TextExtensions: map[string]bool{
570+			".c":          true,
571+			".cc":         true,
572+			".conf":       true,
573+			".config":     true,
574+			".cpp":        true,
575+			".cs":         true,
576+			".css":        true,
577+			".csv":        true,
578+			".Dockerfile": true,
579+			".gitignore":  true,
580+			".gitmodules": true,
581+			".go":         true,
582+			".h":          true,
583+			".htm":        true,
584+			".html":       true,
585+			".iml":        true,
586+			".js":         true,
587+			".json":       true,
588+			".jsx":        true,
589+			".less":       true,
590+			".lock":       true,
591+			".log":        true,
592+			".Makefile":   true,
593+			".md":         true,
594+			".mod":        true,
595+			".php":        true,
596+			".py":         true,
597+			".rb":         true,
598+			".rs":         true,
599+			".scss":       true,
600+			".sql":        true,
601+			".sum":        true,
602+			".svg":        true,
603+			".toml":       true,
604+			".ts":         true,
605+			".tsv":        true,
606+			".tsx":        true,
607+			".txt":        true,
608+			".xml":        true,
609+			".yaml":       true,
610+			".yml":        true,
611+		},
612+		PlainFiles: map[string]bool{
613+			"Dockerfile":  true,
614+			"license-mit": true,
615+			"LICENSE-MIT": true,
616+			"license":     true,
617+			"LICENSE":     true,
618+			"Makefile":    true,
619+			"readme":      true,
620+			"Readme":      true,
621+			"ReadMe":      true,
622+			"README":      true,
623+		},
624+		AllowedLicenseFiles: map[string]bool{
625+			"license-mit": true,
626+			"LICENSE-MIT": true,
627+			"license.md":  true,
628+			"LICENSE.md":  true,
629+			"license.txt": true,
630+			"LICENSE.txt": true,
631+			"LICENSE":     true,
632+		},
633+		AllowedReadMeFiles: map[string]bool{
634+			"readme.md":  true,
635+			"Readme.md":  true,
636+			"ReadMe.md":  true,
637+			"README.md":  true,
638+			"readme.txt": true,
639+			"README.txt": true,
640+			"README":     true,
641+		},
642+	}
643+}
644diff --git a/templates/index.html b/templates/index.html
645new file mode 100644
646index 0000000..71bb554
647--- /dev/null
648+++ b/templates/index.html
649@@ -0,0 +1,39 @@
650+<!DOCTYPE html>
651+<html lang="en">
652+
653+<head>
654+  {{ template "head" .RepoData }}
655+</head>
656+
657+<body>
658+  <div class="content">
659+    <div class="index">
660+      <div class="metadata">
661+        <table>
662+          <tbody>
663+            <tr>
664+              <td>
665+                <h1>whatever</h1>
666+                <span class="desc">
667+                  ???
668+                </span>
669+              </td>
670+            </tr>
671+            <tr>
672+              <td>
673+                <a href="/whatever">link?</a>
674+              </td>
675+            </tr>
676+            <tr>
677+              <td>
678+                etc.....
679+              </td>
680+            </tr>
681+          </tbody>
682+        </table>
683+      </div>
684+    </div>
685+  </div>
686+</body>
687+
688+</html>
689\ No newline at end of file