diff --git a/main.go b/main.go index 692ae42..1d46793 100644 --- a/main.go +++ b/main.go @@ -1,17 +1,14 @@ package main import ( - "bytes" _ "embed" "encoding/json" "flag" "fmt" - "html/template" "io" "log" "net/url" "os" - "os/exec" "path/filepath" "reflect" "strings" @@ -171,328 +168,20 @@ func main() { log.Printf("user cache set: %s", tmp) - pro := &project{ - base: dir, - Name: opt.Name, - repo: tmp, - } + pro := NewProject(dir, opt.Name, tmp, opt) // Create base directories. - if err := pro.init(opt.Force); err != nil { + if err := pro.init(); err != nil { log.Fatalf("unable to initialize output directory: %v", err) } // Clone target repo. - if err := pro.save(opt.Source); err != nil { + if err := pro.save(); err != nil { log.Fatalf("unable to set up repo: %v", err) } - t := template.Must(template.New("page").Funcs(template.FuncMap{ - "diffstatbodyparser": diffstatbodyparser, - "diffbodyparser": diffbodyparser, - }).Parse(tpl)) - - branches, err := pro.branchfilter(opt.Branches) - if err != nil { - log.Fatalf("unable to filter branches: %v", err) - } - - updateBranches(branches, pro) - writePages(branches, pro, t) - writeMainIndex(branches, pro, t, opt) -} - -func updateBranches(branches []branch, pro *project) { - for _, b := range branches { - // NOTE: Is this needed still if the repo is downloaded each time the script is run? - ref := fmt.Sprintf("refs/heads/%s:refs/origin/%s", b, b) - - cmd := exec.Command("git", "fetch", "--force", "origin", ref) - cmd.Dir = pro.repo - - log.Printf("updating branch: %s", b) - - if _, err := cmd.Output(); err != nil { - log.Printf("unable to fetch branch: %v", err) - continue - } - } -} - -func writePages(branches []branch, pro *project, t *template.Template) { - for _, b := range branches { - log.Printf("processing branch: %s", b) - - go writeBranchPage(pro, b, t) - - for i, c := range b.Commits { - log.Printf("processing commit: %s: %d/%d", c.Abbr, i+1, len(b.Commits)) - - base := filepath.Join(pro.base, "commit", c.Hash) - - if err := os.MkdirAll(base, 0755); err != nil { - if err != nil { - log.Printf("unable to create commit directory: %v", err) - } - - continue - } - - for _, par := range c.Parents { - writeCommitDiff(par, c, pro, base, b, t) - } - - for _, obj := range c.Tree { - dst := filepath.Join(pro.base, "object", obj.Dir()) - - if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil { - if err != nil { - log.Printf("unable to create object directory: %v", err) - } - continue - } - - writeObjectBlob(obj, pro, dst) - writeNom(fmt.Sprintf("%s.html", dst), obj, pro, b, c, t, base) - } - - writeCommitPage(base, pro, c, b, t) - } - } -} - -func writeMainIndex(branches []branch, pro *project, t *template.Template, opt *options) { - // This is the main index or project home. - f, err := os.Create(filepath.Join(pro.base, "index.html")) - - defer f.Close() - - if err != nil { - log.Fatalf("unable to create home page: %v", err) - } - - p := page{ - Data: Data{ - "Branches": branches, - "Link": opt.URL, - "Project": pro.Name, - }, - Base: "./", - Title: pro.Name, - } - - if err := t.Execute(f, p); err != nil { - log.Fatalf("unable to apply template: %v", err) - } -} - -func writeCommitDiff(par string, c commit, pro *project, base string, b branch, t *template.Template) { - cmd := exec.Command("git", "diff", "-p", fmt.Sprintf("%s..%s", par, c.Hash)) - cmd.Dir = pro.repo - - out, err := cmd.Output() - - if err != nil { - log.Printf("unable to diff against parent: %v", err) - - return - } - - dst := filepath.Join(base, fmt.Sprintf("diff-%s.html", par)) - f, err := os.Create(dst) - - defer f.Close() - - if err != nil { - log.Printf("unable to create commit diff to parent: %v", err) - - return - } - - p := page{ - Data: Data{ - "Diff": diff{ - Body: fmt.Sprintf("%s", out), - Commit: c, - Parent: par, - }, - "Project": pro.Name, - }, - Base: "../../", - Title: strings.Join([]string{pro.Name, b.Name, c.Abbr}, ": "), - } - - if err := t.Execute(f, p); err != nil { - log.Printf("unable to apply template: %v", err) - - return - } -} - -func writeBranchPage(pro *project, b branch, t *template.Template) { - dst := filepath.Join(pro.base, "branch", b.Name, "index.html") - - if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil { - if err != nil { - log.Fatalf("unable to create branch directory: %v", err) - } - return - } - - f, err := os.Create(dst) + pro.updateBranches() - defer f.Close() - - if err != nil { - // TODO: Remove from branches slice? - log.Printf("unable to create branch page: %v", err) - - return - } - - p := page{ - Data: Data{ - "Commits": b.Commits, - "Branch": b, - "Project": pro.Name, - }, - Base: "../../", - Title: strings.Join([]string{pro.Name, b.Name}, ": "), - } - - if err := t.Execute(f, p); err != nil { - log.Printf("unable to apply template: %v", err) - return - } -} - -func writeObjectBlob(obj object, pro *project, dst string) { - cmd := exec.Command("git", "cat-file", "blob", obj.Hash) - cmd.Dir = pro.repo - - out, err := cmd.Output() - - if err != nil { - log.Printf("unable to save object: %v", err) - return - } - - f, err := os.Create(dst) - - defer f.Close() - - if err != nil { - log.Printf("unable to create object: %v", err) - return - } - - if _, err := f.Write(out); err != nil { - log.Printf("unable to write object blob: %v", err) - return - } -} - -func writeNom(nom string, obj object, pro *project, b branch, c commit, t *template.Template, base string) { - f, err := os.Create(nom) - defer f.Close() - - if err != nil { - log.Printf("unable to create object: %v", err) - return - } - - o := &show{ - object: object{ - Hash: obj.Hash, - Path: obj.Path, - }, - Bin: types[filepath.Ext(obj.Path)], - } - - if o.Bin { - // TODO. - } else { - cmd := exec.Command("git", "show", "--no-notes", obj.Hash) - cmd.Dir = pro.repo - - out, err := cmd.Output() - - if err != nil { - log.Printf("unable to show object: %v", err) - - return - } - - sep := []byte("\n") - var lines = make([]int, bytes.Count(out, sep)) - - for i := range lines { - lines[i] = i + 1 - } - - if bytes.LastIndex(out, sep) != len(out)-1 { - lines = append(lines, len(lines)) - } - - o.Lines = lines - o.Body = fmt.Sprintf("%s", out) - } - - p := page{ - Data: Data{ - "Object": *o, - "Project": pro.Name, - }, - Base: "../../", - Title: strings.Join([]string{pro.Name, b.Name, c.Abbr, obj.Path}, ": "), - } - - if err := t.Execute(f, p); err != nil { - log.Printf("unable to apply template: %v", err) - return - } - - lnk := filepath.Join(base, fmt.Sprintf("%s.html", obj.Path)) - - if err := os.MkdirAll(filepath.Dir(lnk), 0755); err != nil { - if err != nil { - log.Printf("unable to create hard link path: %v", err) - } - return - } - - if err := os.Link(nom, lnk); err != nil { - if os.IsExist(err) { - return - } - - log.Printf("unable to hard link object into commit folder: %v", err) - } -} - -func writeCommitPage(base string, pro *project, c commit, b branch, t *template.Template) { - dst := filepath.Join(base, "index.html") - f, err := os.Create(dst) - - defer f.Close() - - if err != nil { - log.Printf("unable to create commit page: %v", err) - // TODO(spike): handle error? - return - } - - p := page{ - Data: Data{ - "Commit": c, - "Project": pro.Name, - }, - Base: "../../", - Title: strings.Join([]string{pro.Name, b.Name, c.Abbr}, ": "), - } - - if err := t.Execute(f, p); err != nil { - log.Printf("unable to apply template: %v", err) - return - } + pro.writePages() + pro.writeMainIndex() } diff --git a/parsers.go b/parsers.go deleted file mode 100644 index eac48ab..0000000 --- a/parsers.go +++ /dev/null @@ -1,87 +0,0 @@ -package main - -import ( - "fmt" - "html/template" - "regexp" - "strings" -) - -// Match diff body @@ del, ins line numbers. -var aline = regexp.MustCompile(`\-(.*?),`) -var bline = regexp.MustCompile(`\+(.*?),`) - -// Match diff body keywords. -var xline = regexp.MustCompile(`^(deleted|index|new|rename|similarity)`) - -// Helps target file specific diff blocks. -var diffanchor = regexp.MustCompile(`b\/(.*?)$`) - -func diffbodyparser(d diff) template.HTML { - var results []string - feed := strings.Split(strings.TrimSuffix(template.HTMLEscapeString(d.Body), "\n"), "\n") - - var a, b string - - for _, line := range feed { - if strings.HasPrefix(line, "diff") { - line = diffanchor.ReplaceAllString(line, `b/<a id="$1">$1</a>`) - line = fmt.Sprintf("<strong>%s</strong>", line) - } - - line = xline.ReplaceAllString(line, "<em>$1</em>") - - if strings.HasPrefix(line, "@@") { - if a != "" && !strings.HasPrefix(a, "---") { - repl := fmt.Sprintf(`<a href="commit/%s/%s.html#L$1">-$1</a>,`, d.Parent, a) - line = aline.ReplaceAllString(line, repl) - } - - if b != "" && !strings.HasPrefix(b, "+++") { - repl := fmt.Sprintf(`<a href="commit/%s/%s.html#L$1">+$1</a>,`, d.Commit.Hash, b) - line = bline.ReplaceAllString(line, repl) - } - } - - if strings.HasPrefix(line, "---") { - a = strings.TrimPrefix(line, "--- a/") - line = fmt.Sprintf("<mark>%s</mark>", line) - } else if strings.HasPrefix(line, "-") { - line = fmt.Sprintf("<del>%s</del>", line) - } - - if strings.HasPrefix(line, "+++") { - b = strings.TrimPrefix(line, "+++ b/") - line = fmt.Sprintf("<mark>%s</mark>", line) - } else if strings.HasPrefix(line, "+") { - line = fmt.Sprintf("<ins>%s</ins>", line) - } - - results = append(results, line) - } - - return template.HTML(strings.Join(results, "\n")) -} - -func diffstatbodyparser(o overview) template.HTML { - var results []string - feed := strings.Split(strings.TrimSuffix(o.Body, "\n"), "\n") - - for i, line := range feed { - if i < len(feed)-1 { - // Link files to corresponding diff. - columns := strings.Split(line, "|") - files := strings.Split(columns[0], "=>") - - a := strings.TrimSpace(files[len(files)-1]) - b := fmt.Sprintf(`<a href="commit/%s/diff-%s.html#%s">%s</a>`, o.Hash, o.Parent, a, a) - l := strings.LastIndex(line, a) - - line = line[:l] + strings.Replace(line[l:], a, b, 1) - } - - results = append(results, line) - } - - return template.HTML(strings.Join(results, "\n")) -} diff --git a/project.go b/project.go index 9bad5f0..048fe58 100644 --- a/project.go +++ b/project.go @@ -1,15 +1,14 @@ package main import ( - "bufio" "bytes" "fmt" + "html/template" "log" "os" "os/exec" "path/filepath" "strings" - "time" ) // SEP is a browser generated UUID v4 used to separate out commit line items. @@ -20,20 +19,44 @@ var types = make(map[string]bool) type project struct { base string - Branches []branch Name string repo string + options *options + template *template.Template + branches []branch +} + +func NewProject(base string, name string, repo string, options *options) *project { + funcMap := template.FuncMap{ + "diffstatbodyparser": diffstatbodyparser, + "diffbodyparser": diffbodyparser, + } + template := template.Must(template.New("page").Funcs(funcMap).Parse(tpl)) + + branches, err := branchFilter(repo, name, options) + if err != nil { + log.Fatalf("unable to filter branches: %v", err) + } + + return &project{ + base: base, + Name: name, + repo: repo, + options: options, + template: template, + branches: branches, + } } // Creates base directories for holding objects, branches, and commits. -func (pro *project) init(f bool) error { +func (p *project) init() error { dirs := []string{"branch", "commit", "object"} for _, dir := range dirs { - d := filepath.Join(pro.base, dir) + d := filepath.Join(p.base, dir) // Clear existing dirs when -f true. - if f && dir != "branch" { + if p.options.Force && dir != "branch" { if err := os.RemoveAll(d); err != nil { return fmt.Errorf("unable to remove directory: %v", err) } @@ -48,230 +71,305 @@ func (pro *project) init(f bool) error { } // Saves a local clone of `target` repo. -func (pro *project) save(target string) error { - if _, err := os.Stat(pro.repo); err != nil { +func (p *project) save() error { + if _, err := os.Stat(p.repo); err != nil { return err } - return exec.Command("git", "clone", target, pro.repo).Run() + return exec.Command("git", "clone", p.options.Source, p.repo).Run() } -// Goes through list of branches and returns those that match whitelist. -func (pro *project) branchfilter(whitelist manyflag) ([]branch, error) { - cmd := exec.Command("git", "branch", "-a") - cmd.Dir = pro.repo - - out, err := cmd.Output() +func (p *project) updateBranches() { + for _, b := range p.branches { + // NOTE: Is this needed still if the repo is downloaded each time the script is run? + ref := fmt.Sprintf("refs/heads/%s:refs/origin/%s", b, b) - if err != nil { - return nil, err - } + cmd := exec.Command("git", "fetch", "--force", "origin", ref) + cmd.Dir = p.repo - var b = make(map[string]branch) - var m = make(map[string]bool) + log.Printf("updating branch: %s", b) - scanner := bufio.NewScanner(bytes.NewReader(out)) + if _, err := cmd.Output(); err != nil { + log.Printf("unable to fetch branch: %v", err) + continue + } + } +} - for scanner.Scan() { - t := strings.TrimSpace(strings.TrimPrefix(scanner.Text(), "*")) - _, f := filepath.Split(t) +func (p *project) writePages() { + for _, b := range p.branches { + log.Printf("processing branch: %s", b) - m[f] = true - } + go p.writeBranchPage(b) - if err := scanner.Err(); err != nil { - return nil, err - } + for i, c := range b.Commits { + log.Printf("processing commit: %s: %d/%d", c.Abbr, i+1, len(b.Commits)) - // Filter to match options, but return all if no branch flags given. - if len(whitelist) > 0 { - for k := range m { - m[k] = contains(whitelist, k) - } - } else { - // In git given order at this point. - for k := range m { - whitelist = append(whitelist, k) - } - } + base := filepath.Join(p.base, "commit", c.Hash) - for k, v := range m { - if v { - // TODO: Try a goroutine? - commits, err := pro.commitparser(k) + if err := os.MkdirAll(base, 0755); err != nil { + if err != nil { + log.Printf("unable to create commit directory: %v", err) + } - if err != nil { continue } - b[k] = branch{commits, k, pro.Name} - } - } + for _, par := range c.Parents { + p.writeCommitDiff(par, c, base, b) + } - // Fill in resulting slice with desired branches in order. - var results []branch + for _, obj := range c.Tree { + dst := filepath.Join(p.base, "object", obj.Dir()) - for _, v := range whitelist { - results = append(results, b[v]) - } + if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil { + if err != nil { + log.Printf("unable to create object directory: %v", err) + } + continue + } - return results, nil -} + p.writeObjectBlob(obj, dst) + p.writeNom(fmt.Sprintf("%s.html", dst), obj, b, c, base) + } -func (pro *project) commitparser(b string) ([]commit, error) { - fst := strings.Join([]string{"%H", "%P", "%s", "%aN", "%aE", "%aD", "%h"}, SEP) - ref := fmt.Sprintf("origin/%s", b) + p.writeCommitPage(base, c, b) + } + } +} - cmd := exec.Command("git", "log", fmt.Sprintf("--format=%s", fst), ref) - cmd.Dir = pro.repo +func (p *project) writeMainIndex() { + // This is the main index or project home. + f, err := os.Create(filepath.Join(p.base, "index.html")) - out, err := cmd.Output() + defer f.Close() if err != nil { - return nil, err + log.Fatalf("unable to create home page: %v", err) } - results := []commit{} - scanner := bufio.NewScanner(bytes.NewReader(out)) + page := page{ + Data: Data{ + "Branches": p.branches, + "Link": p.options.URL, + "Project": p.Name, + }, + Base: "./", + Title: p.Name, + } - for scanner.Scan() { - text := strings.TrimSpace(scanner.Text()) - data := strings.Split(text, SEP) + if err := p.template.Execute(f, page); err != nil { + log.Fatalf("unable to apply template: %v", err) + } +} - h := data[0] +func (p *project) writeCommitDiff(par string, c commit, base string, b branch) { + cmd := exec.Command("git", "diff", "-p", fmt.Sprintf("%s..%s", par, c.Hash)) + cmd.Dir = p.repo - var history []overview - var parents []string + out, err := cmd.Output() - if data[1] != "" { - parents = strings.Split(data[1], " ") - } + if err != nil { + log.Printf("unable to diff against parent: %v", err) - for _, p := range parents { - diffstat, err := pro.diffstatparser(h, p) + return + } - if err != nil { - log.Printf("unable to diffstat against parent: %s", err) + dst := filepath.Join(base, fmt.Sprintf("diff-%s.html", par)) + f, err := os.Create(dst) - continue - } + defer f.Close() - history = append(history, overview{diffstat, h, p}) - } + if err != nil { + log.Printf("unable to create commit diff to parent: %v", err) - a := author{data[4], data[3]} + return + } - date, err := time.Parse("Mon, 2 Jan 2006 15:04:05 -0700", data[5]) + page := page{ + Data: Data{ + "Diff": diff{ + Body: fmt.Sprintf("%s", out), + Commit: c, + Parent: par, + }, + "Project": p.Name, + }, + Base: "../../", + Title: strings.Join([]string{p.Name, b.Name, c.Abbr}, ": "), + } - if err != nil { - log.Printf("unable to parse commit date: %s", err) + if err := p.template.Execute(f, page); err != nil { + log.Printf("unable to apply template: %v", err) - continue - } + return + } +} - body, err := pro.bodyparser(h) +func (p *project) writeBranchPage(b branch) { + dst := filepath.Join(p.base, "branch", b.Name, "index.html") + if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil { if err != nil { - log.Printf("unable to parse commit body: %s", err) - - continue + log.Fatalf("unable to create branch directory: %v", err) } + return + } - tree, err := pro.treeparser(h) - - if err != nil { - log.Printf("unable to parse commit tree: %s", err) + f, err := os.Create(dst) - continue - } + defer f.Close() - c := commit{ - Abbr: data[6], - Author: a, - Body: body, - Branch: b, - Date: date, - Hash: h, - History: history, - Parents: parents, - Project: pro.Name, - Subject: data[2], - Tree: tree, - } + if err != nil { + // TODO: Remove from branches slice? + log.Printf("unable to create branch page: %v", err) - results = append(results, c) + return } - if err := scanner.Err(); err != nil { - return nil, err + page := page{ + Data: Data{ + "Commits": b.Commits, + "Branch": b, + "Project": p.Name, + }, + Base: "../../", + Title: strings.Join([]string{p.Name, b.Name}, ": "), } - return results, nil + if err := p.template.Execute(f, page); err != nil { + log.Printf("unable to apply template: %v", err) + return + } } -func (pro *project) treeparser(h string) ([]object, error) { - cmd := exec.Command("git", "ls-tree", "-r", "--format=%(objectname) %(path)", h) - cmd.Dir = pro.repo +func (p *project) writeObjectBlob(obj object, dst string) { + cmd := exec.Command("git", "cat-file", "blob", obj.Hash) + cmd.Dir = p.repo out, err := cmd.Output() if err != nil { - return nil, err + log.Printf("unable to save object: %v", err) + return } - var results []object - feed := strings.Split(strings.TrimSuffix(fmt.Sprintf("%s", out), "\n"), "\n") + f, err := os.Create(dst) - for _, line := range feed { - w := strings.Split(line, " ") + defer f.Close() - results = append(results, object{ - Hash: w[0], - Path: w[1], - }) + if err != nil { + log.Printf("unable to create object: %v", err) + return } - return results, nil + if _, err := f.Write(out); err != nil { + log.Printf("unable to write object blob: %v", err) + return + } } -func (pro *project) diffstatparser(h, p string) (string, error) { - cmd := exec.Command("git", "diff", "--stat", fmt.Sprintf("%s..%s", p, h)) - cmd.Dir = pro.repo - - out, err := cmd.Output() +func (p *project) writeNom(nom string, obj object, b branch, c commit, base string) { + f, err := os.Create(nom) + defer f.Close() if err != nil { - return "", err + log.Printf("unable to create object: %v", err) + return } - var results []string - feed := strings.Split(strings.TrimSuffix(fmt.Sprintf("%s", out), "\n"), "\n") + o := &show{ + object: object{ + Hash: obj.Hash, + Path: obj.Path, + }, + Bin: types[filepath.Ext(obj.Path)], + } - for _, line := range feed { - // NOTE: This is hackish I know, attach to project? - i := strings.Index(line, "|") + if o.Bin { + // TODO. + } else { + cmd := exec.Command("git", "show", "--no-notes", obj.Hash) + cmd.Dir = p.repo - if i != -1 { - ext := filepath.Ext(strings.TrimSpace(line[:i])) - types[ext] = strings.Contains(line, "Bin") + out, err := cmd.Output() + + if err != nil { + log.Printf("unable to show object: %v", err) + + return + } + + sep := []byte("\n") + var lines = make([]int, bytes.Count(out, sep)) + + for i := range lines { + lines[i] = i + 1 + } + + if bytes.LastIndex(out, sep) != len(out)-1 { + lines = append(lines, len(lines)) } - results = append(results, strings.TrimSpace(line)) + o.Lines = lines + o.Body = fmt.Sprintf("%s", out) + } + + page := page{ + Data: Data{ + "Object": *o, + "Project": p.Name, + }, + Base: "../../", + Title: strings.Join([]string{p.Name, b.Name, c.Abbr, obj.Path}, ": "), + } + + if err := p.template.Execute(f, page); err != nil { + log.Printf("unable to apply template: %v", err) + return + } + + lnk := filepath.Join(base, fmt.Sprintf("%s.html", obj.Path)) + + if err := os.MkdirAll(filepath.Dir(lnk), 0755); err != nil { + if err != nil { + log.Printf("unable to create hard link path: %v", err) + } + return } - return strings.Join(results, "\n"), nil + if err := os.Link(nom, lnk); err != nil { + if os.IsExist(err) { + return + } + + log.Printf("unable to hard link object into commit folder: %v", err) + } } -func (pro *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 = pro.repo +func (p *project) writeCommitPage(base string, c commit, b branch) { + dst := filepath.Join(base, "index.html") + f, err := os.Create(dst) - out, err := cmd.Output() + defer f.Close() if err != nil { - return "", err + log.Printf("unable to create commit page: %v", err) + // TODO(spike): handle error? + return } - return strings.TrimSuffix(fmt.Sprintf("%s", out), "\n"), nil + page := page{ + Data: Data{ + "Commit": c, + "Project": p.Name, + }, + Base: "../../", + Title: strings.Join([]string{p.Name, b.Name, c.Abbr}, ": "), + } + + if err := p.template.Execute(f, page); err != nil { + log.Printf("unable to apply template: %v", err) + return + } }
home › develop › 16415ed › 4126508