Changes
diff --git a/main.go b/main.go
index 2da686d..720f8c8 100644
--- a/main.go
+++ b/main.go
@@ -21,29 +21,23 @@ import (
"time"
)
-// For separating out commit line items.
+// SEP is a UUID v4 used to separate out commit line items.
const SEP = "6f6c1745-e902-474a-9e99-08d0084fb011"
-//go:embed branch.html.tmpl
-var bTmpl string
+//go:embed page.html.tmpl
+var tpl string
-//go:embed home.html.tmpl
-var rTmpl string
-
-//go:embed commit.html.tmpl
-var cTmpl string
-
-//go:embed diff.html.tmpl
-var dTmpl string
-
-//go:embed object.html.tmpl
-var oTmpl string
-
-type repository struct {
+type project struct {
base string
Branches []branch
Name string
- temp string
+ repo string
+}
+
+type page struct {
+ Breadcrumbs []string
+ Data map[string]interface{}
+ Title string
}
type branch struct {
@@ -110,9 +104,10 @@ type options struct {
Branches manyflag `json:"branches"`
config string
Force bool `json:"force"`
- Project string `json:"project"`
+ Name string `json:"name"`
Quiet bool `json:"quiet"`
- Repo string `json:"repo"`
+ Source string `json:"source"`
+ Template string `json:"template"`
URL string `json:"url"`
}
@@ -149,10 +144,12 @@ func main() {
config: ".jimmy.json",
}
- flag.StringVar(&opt.Project, "p", "Jimbo", "Project title")
- flag.StringVar(&opt.Repo, "r", "", "Target repo")
+ // NOTE: Flags need match each option key's first letter.
+ flag.StringVar(&opt.Name, "n", "Jimbo", "Project title")
+ flag.StringVar(&opt.Source, "s", "", "Source repository")
flag.Var(&opt.Branches, "b", "Target branches")
- flag.StringVar(&opt.URL, "u", "https://host.net/project.git", "Repo public URL")
+ flag.StringVar(&opt.Template, "t", "", "Page template")
+ flag.StringVar(&opt.URL, "u", "https://host.net/project.git", "Source URL")
flag.BoolVar(&opt.Quiet, "q", false, "Be quiet")
flag.BoolVar(&opt.Force, "f", false, "Force rebuild")
flag.Parse()
@@ -161,6 +158,16 @@ func main() {
log.SetOutput(io.Discard)
}
+ if opt.Template != "" {
+ bs, err := os.ReadFile(opt.Template)
+
+ if err != nil {
+ log.Printf("unable to read template: %v", err)
+ } else {
+ tpl = string(bs)
+ }
+ }
+
cwd, err := os.Getwd()
if err != nil {
@@ -168,11 +175,11 @@ func main() {
}
// Defaults to the current working directory if no argument present.
- outpath := flag.Arg(0)
+ dir := flag.Arg(0)
- // Make sure `outpath` is an absolute path.
- if ok := filepath.IsAbs(outpath); !ok {
- outpath = filepath.Join(cwd, outpath)
+ // Make sure `dir` is an absolute path.
+ if ok := filepath.IsAbs(dir); !ok {
+ dir = filepath.Join(cwd, dir)
}
// Create a separate options instance for reading config file values into.
@@ -182,14 +189,14 @@ func main() {
store.Branches = append(store.Branches, opt.Branches...)
// Attempt to read saved settings.
- bs, err := os.ReadFile(filepath.Join(outpath, opt.config))
+ cnf, err := os.ReadFile(filepath.Join(dir, opt.config))
if err != nil {
log.Printf("unable to read config file: %v", err)
}
// If a config file exists and an option has not been set, override default to match.
- if err := json.Unmarshal(bs, &store); err != nil {
+ if err := json.Unmarshal(cnf, &store); err != nil {
log.Printf("unable to parse config file: %v", err)
}
@@ -201,6 +208,8 @@ func main() {
flagset[f.Name] = true
})
+ log.Print(flagset)
+
ref := reflect.ValueOf(store)
tab := tabwriter.NewWriter(log.Writer(), 0, 0, 0, '.', 0)
@@ -228,27 +237,27 @@ func main() {
tab.Flush()
// The repo flag is required at this point.
- if ok := filepath.IsAbs(opt.Repo); ok {
+ if ok := filepath.IsAbs(opt.Source); ok {
// Option considered repo-like if it contains a hidden `.git` dir.
- if _, err := os.Stat(filepath.Join(opt.Repo, ".git")); os.IsNotExist(err) {
+ if _, err := os.Stat(filepath.Join(opt.Source, ".git")); os.IsNotExist(err) {
flag.Usage()
os.Exit(1)
}
} else {
// Allow for URL-looking non-local repos.
- if _, err := url.ParseRequestURI(opt.Repo); err != nil {
+ if _, err := url.ParseRequestURI(opt.Source); err != nil {
flag.Usage()
os.Exit(1)
}
}
- // Make sure `outpath` exists.
- if err := os.MkdirAll(outpath, 0750); err != nil {
+ // Make sure `dir` exists.
+ if err := os.MkdirAll(dir, 0750); err != nil {
log.Fatalf("unable to create output directory: %v", err)
}
// Save current settings for future use.
- if err := opt.save(outpath); err != nil {
+ if err := opt.save(dir); err != nil {
log.Fatalf("unable to save options: %v", err)
}
@@ -258,31 +267,31 @@ func main() {
log.Fatalf("unable to locate user cache folder: %s", err)
}
- p, err := os.MkdirTemp(ucd, "gtx-*")
+ tmp, err := os.MkdirTemp(ucd, "gtx-*")
if err != nil {
log.Fatalf("unable to locate temporary host dir: %s", err)
}
- log.Printf("user cache set: %s", p)
+ log.Printf("user cache set: %s", tmp)
- repo := &repository{
- base: outpath,
- Name: opt.Project,
- temp: p,
+ prj := &project{
+ base: dir,
+ Name: opt.Name,
+ repo: tmp,
}
// Create base directories.
- if err := repo.init(opt.Force); err != nil {
+ if err := prj.init(opt.Force); err != nil {
log.Fatalf("unable to initialize output directory: %v", err)
}
// Clone target repo.
- if err := repo.save(opt.Repo); err != nil {
+ if err := prj.save(opt.Source); err != nil {
log.Fatalf("unable to set up repo: %v", err)
}
- branches, err := repo.branchfilter(opt.Branches)
+ branches, err := prj.branchfilter(opt.Branches)
if err != nil {
log.Fatalf("unable to filter branches: %v", err)
@@ -296,7 +305,7 @@ func main() {
ref := fmt.Sprintf("refs/heads/%s:refs/origin/%s", b, b)
cmd := exec.Command("git", "fetch", "--force", "origin", ref)
- cmd.Dir = repo.temp
+ cmd.Dir = prj.repo
if _, err := cmd.Output(); err != nil {
log.Printf("unable to fetch branch: %v", err)
@@ -310,7 +319,7 @@ func main() {
go func() {
defer wg.Done()
- dst := filepath.Join(outpath, "branch", b.Name, "index.html")
+ dst := filepath.Join(prj.base, "branch", b.Name, "index.html")
if err := os.MkdirAll(filepath.Dir(dst), 0750); err != nil {
if err != nil {
@@ -331,9 +340,16 @@ func main() {
return
}
- t := template.Must(template.New("branch").Parse(bTmpl))
+ t := template.Must(template.New("branch").Parse(tpl))
+ p := page{
+ Data: map[string]interface{}{
+ "Commits": b.Commits,
+ "Project": prj.Name,
+ },
+ Title: strings.Join([]string{prj.Name, b.Name}, ": "),
+ }
- if err := t.Execute(f, b); err != nil {
+ if err := t.Execute(f, p); err != nil {
log.Printf("unable to apply template: %v", err)
return
@@ -345,7 +361,7 @@ func main() {
for i, c := range b.Commits {
log.Printf("processing commit: %s: %d/%d", c.Abbr, i+1, len(b.Commits))
- base := filepath.Join(outpath, "commit", c.Hash)
+ base := filepath.Join(prj.base, "commit", c.Hash)
if err := os.MkdirAll(base, 0750); err != nil {
if err != nil {
@@ -355,50 +371,53 @@ func main() {
continue
}
- for _, parent := range c.Parents {
+ for _, psh := range c.Parents {
wg.Add(1)
go func() {
defer wg.Done()
- dst := filepath.Join(base, fmt.Sprintf("diff-to-%s.html", parent))
- f, err := os.Create(dst)
+ // NOTE: Use <em>, <ins>, and <del> instead of blue, green, red <font> elements
+ cmd := exec.Command("git", "diff", "-p", fmt.Sprintf("%s..%s", psh, c.Hash))
+ cmd.Dir = prj.repo
- defer f.Close()
+ out, err := cmd.Output()
if err != nil {
- log.Printf("unable to create commit diff to parent page: %v", err)
+ log.Printf("unable to diff against parent: %v", err)
return
}
- t := template.Must(template.New("diff").Parse(dTmpl))
-
- // NOTE: Use <em>, <ins>, and <del> instead of blue, green, red <font> elements
- cmd := exec.Command("git", "diff", "-p", fmt.Sprintf("%s..%s", parent, c.Hash))
- cmd.Dir = repo.temp
+ dst := filepath.Join(base, fmt.Sprintf("diff-to-%s.html", psh))
+ f, err := os.Create(dst)
- out, err := cmd.Output()
+ defer f.Close()
if err != nil {
- log.Printf("unable to diff against parent: %v", err)
+ log.Printf("unable to create commit diff to parent page: %v", err)
return
}
- body := fmt.Sprintf("%s", out)
- data := struct {
- Body string
- Commit commit
- // TODO: Make this a hash type?
- Parent string
- }{
- Body: body,
- Commit: c,
- Parent: parent,
+ t := template.Must(template.New("diff").Parse(tpl))
+ p := page{
+ Data: map[string]interface{}{
+ "Diff": struct {
+ Body string
+ Commit commit
+ Parent string
+ }{
+ Body: fmt.Sprintf("%s", out),
+ Commit: c,
+ Parent: psh,
+ },
+ "Project": prj.Name,
+ },
+ Title: strings.Join([]string{prj.Name, b.Name, c.Abbr}, ": "),
}
- if err := t.Execute(f, data); err != nil {
+ if err := t.Execute(f, p); err != nil {
log.Printf("unable to apply template: %v", err)
return
@@ -406,8 +425,8 @@ func main() {
}()
}
- for _, object := range c.Tree {
- dst := filepath.Join(outpath, "object", object.Hash[0:2], object.Hash)
+ for _, obj := range c.Tree {
+ dst := filepath.Join(prj.base, "object", obj.Hash[0:2], obj.Hash)
if err := os.MkdirAll(filepath.Dir(dst), 0750); err != nil {
if err != nil {
@@ -417,13 +436,9 @@ func main() {
continue
}
- wg.Add(1)
-
- go func(name string) {
- defer wg.Done()
-
- cmd := exec.Command("git", "show", "--no-notes", object.Hash)
- cmd.Dir = repo.temp
+ func(name string) {
+ cmd := exec.Command("git", "show", "--no-notes", obj.Hash)
+ cmd.Dir = prj.repo
out, err := cmd.Output()
@@ -443,28 +458,38 @@ func main() {
return
}
- body := fmt.Sprintf("%s", out)
- data := struct {
- Body string
- Hash string
- Project string
- }{
- Body: body,
- Hash: object.Hash,
- Project: opt.Project,
+ var lines = make([]int, bytes.Count(out, []byte("\n")))
+
+ for i := range lines {
+ lines[i] = i + 1
}
- t := template.Must(template.New("object").Parse(oTmpl))
+ t := template.Must(template.New("object").Parse(tpl))
+ p := page{
+ Data: map[string]interface{}{
+ "Object": struct {
+ Body string
+ Hash string
+ Lines []int
+ }{
+ Body: fmt.Sprintf("%s", out),
+ Hash: obj.Hash,
+ Lines: lines,
+ },
+ "Project": prj.Name,
+ },
+ Title: strings.Join([]string{prj.Name, b.Name, c.Abbr, obj.Path}, ": "),
+ }
- if err := t.Execute(f, data); err != nil {
+ if err := t.Execute(f, p); err != nil {
log.Printf("unable to apply template: %v", err)
return
}
- link := filepath.Join(base, fmt.Sprintf("%s.html", object.Path))
+ lnk := filepath.Join(base, fmt.Sprintf("%s.html", obj.Path))
- if err := os.MkdirAll(filepath.Dir(link), 0750); err != nil {
+ if err := os.MkdirAll(filepath.Dir(lnk), 0750); err != nil {
if err != nil {
log.Printf("unable to create hard link path: %v", err)
}
@@ -472,7 +497,7 @@ func main() {
return
}
- if err := os.Link(name, link); err != nil {
+ if err := os.Link(name, lnk); err != nil {
if os.IsExist(err) {
return
}
@@ -482,8 +507,8 @@ func main() {
}(fmt.Sprintf("%s.html", dst))
func(name string) {
- cmd := exec.Command("git", "cat-file", "blob", object.Hash)
- cmd.Dir = repo.temp
+ cmd := exec.Command("git", "cat-file", "blob", obj.Hash)
+ cmd.Dir = prj.repo
out, err := cmd.Output()
@@ -527,9 +552,16 @@ func main() {
return
}
- t := template.Must(template.New("commit").Parse(cTmpl))
+ t := template.Must(template.New("commit").Parse(tpl))
+ p := page{
+ Data: map[string]interface{}{
+ "Commit": c,
+ "Project": prj.Name,
+ },
+ Title: strings.Join([]string{prj.Name, b.Name, c.Abbr}, ": "),
+ }
- if err := t.Execute(f, c); err != nil {
+ if err := t.Execute(f, p); err != nil {
log.Printf("unable to apply template: %v", err)
return
@@ -544,7 +576,7 @@ func main() {
defer wg.Done()
// This is the main index or project home.
- f, err := os.Create(filepath.Join(outpath, "index.html"))
+ f, err := os.Create(filepath.Join(prj.base, "index.html"))
defer f.Close()
@@ -552,18 +584,17 @@ func main() {
log.Fatalf("unable to create home page: %v", err)
}
- t := template.Must(template.New("home").Parse(rTmpl))
- data := struct {
- Branches []branch
- Link string
- Project string
- }{
- Branches: branches,
- Link: opt.URL,
- Project: opt.Project,
+ t := template.Must(template.New("home").Parse(tpl))
+ p := page{
+ Data: map[string]interface{}{
+ "Branches": branches,
+ "Link": opt.URL,
+ "Project": prj.Name,
+ },
+ Title: prj.Name,
}
- if err := t.Execute(f, data); err != nil {
+ if err := t.Execute(f, p); err != nil {
log.Fatalf("unable to apply template: %v", err)
}
}()
@@ -572,11 +603,11 @@ func main() {
}
// Creates base directories for holding objects, branches, and commits.
-func (r *repository) init(f bool) error {
+func (prj *project) init(f bool) error {
dirs := []string{"branch", "commit", "object"}
for _, dir := range dirs {
- d := filepath.Join(r.base, dir)
+ d := filepath.Join(prj.base, dir)
// Clear existing dirs when -f true.
if f && dir != "branch" {
@@ -594,18 +625,18 @@ func (r *repository) init(f bool) error {
}
// Saves a local clone of `target` repo.
-func (r *repository) save(target string) error {
- if _, err := os.Stat(r.temp); err != nil {
+func (prj *project) save(target string) error {
+ if _, err := os.Stat(prj.repo); err != nil {
return err
}
- return exec.Command("git", "clone", target, r.temp).Run()
+ return exec.Command("git", "clone", target, prj.repo).Run()
}
// Goes through list of branches and returns those that match whitelist.
-func (r *repository) branchfilter(whitelist manyflag) ([]branch, error) {
+func (prj *project) branchfilter(whitelist manyflag) ([]branch, error) {
cmd := exec.Command("git", "branch", "-a")
- cmd.Dir = r.temp
+ cmd.Dir = prj.repo
out, err := cmd.Output()
@@ -640,25 +671,25 @@ func (r *repository) branchfilter(whitelist manyflag) ([]branch, error) {
for k, v := range m {
if v {
- commits, err := r.commitparser(k)
+ commits, err := prj.commitparser(k)
if err != nil {
continue
}
- results = append(results, branch{commits, k, r.Name})
+ results = append(results, branch{commits, k, prj.Name})
}
}
return results, nil
}
-func (r *repository) commitparser(b string) ([]commit, error) {
+func (prj *project) commitparser(b string) ([]commit, error) {
fst := strings.Join([]string{"%H", "%P", "%s", "%aN", "%aE", "%aD", "%h"}, SEP)
ref := fmt.Sprintf("origin/%s", b)
cmd := exec.Command("git", "log", "--graph", fmt.Sprintf("--format=%s", fst), ref)
- cmd.Dir = r.temp
+ cmd.Dir = prj.repo
out, err := cmd.Output()
@@ -684,7 +715,7 @@ func (r *repository) commitparser(b string) ([]commit, error) {
continue
}
- body, err := r.bodyparser(h)
+ body, err := prj.bodyparser(h)
if err != nil {
log.Printf("unable to parse commit body: %s", err)
@@ -692,7 +723,7 @@ func (r *repository) commitparser(b string) ([]commit, error) {
continue
}
- tree, err := r.treeparser(h)
+ tree, err := prj.treeparser(h)
if err != nil {
log.Printf("unable to parse commit tree: %s", err)
@@ -708,7 +739,7 @@ func (r *repository) commitparser(b string) ([]commit, error) {
}
for _, p := range parents {
- diffstat, err := r.diffparser(h, p)
+ diffstat, err := prj.diffparser(h, p)
if err != nil {
log.Printf("unable to diff stat against parent: %s", err)
@@ -730,7 +761,7 @@ func (r *repository) commitparser(b string) ([]commit, error) {
Tree: tree,
Graph: g,
Parents: parents,
- Project: r.Name,
+ Project: prj.Name,
Subject: data[2],
}
@@ -744,10 +775,10 @@ func (r *repository) commitparser(b string) ([]commit, error) {
return results, nil
}
-func (r *repository) treeparser(h string) ([]object, error) {
+func (prj *project) treeparser(h string) ([]object, error) {
// git ls-tree --format='%(objectname) %(path)' <tree-ish>
cmd := exec.Command("git", "ls-tree", "-r", "--format=%(objectname) %(path)", h)
- cmd.Dir = r.temp
+ cmd.Dir = prj.repo
out, err := cmd.Output()
@@ -770,10 +801,10 @@ func (r *repository) treeparser(h string) ([]object, error) {
return results, nil
}
-func (r *repository) diffparser(h, p string) (string, error) {
+func (prj *project) diffparser(h, p string) (string, error) {
// histo, file, changes, sum
cmd := exec.Command("git", "diff", "--stat", fmt.Sprintf("%s..%s", p, h))
- cmd.Dir = r.temp
+ cmd.Dir = prj.repo
out, err := cmd.Output()
@@ -797,10 +828,10 @@ func (r *repository) diffparser(h, p string) (string, error) {
return strings.Join(results, "\n"), nil
}
-func (r *repository) bodyparser(h string) (string, error) {
+func (prj *project) bodyparser(h string) (string, error) {
// Because the commit message body is multiline and is tripping the scanner.
cmd := exec.Command("git", "show", "--no-patch", "--format=%B", h)
- cmd.Dir = r.temp
+ cmd.Dir = prj.repo
out, err := cmd.Output()