ang=pt-BR">Português do Brasil Polski български Italiano Suomalainen Website Go1.6 quit - git.papill0n.org

No Description

quit.go 18KB

    package main import ( "bytes" "fmt" "html/template" "io" "log" "math" "net/http" "os" "path" "path/filepath" "sort" "strings" "sync" "time" "github.com/libgit2/git2go" "github.com/russross/blackfriday" "github.com/heyLu/quit/perf" ) var performance = perf.NewPerf() func main() { repoPath := "." if len(os.Args) > 1 { repoPath = os.Args[1] } var err error repoPath, err = filepath.Abs(repoPath) if err != nil { log.Fatal("getting absolute path: ", err) } repo, err := git.OpenRepository(repoPath) if err != nil { log.Fatal("opening repository: ", err) } http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { performance.MeasureRequest(req) handleError := func(w http.ResponseWriter, err error) { log.Println("git: ", err) http.Error(w, "internal server error", http.StatusInternalServerError) } fancyRepo := NewFancyRepo(repo) commit, err := fancyRepo.Commit() if err != nil { handleError(w, err) return } files, err := commit.Files(false) if err != nil { handleError(w, err) return } var wg sync.WaitGroup wg.Add(len(files)) for _, f := range files { go func(f *FancyFile) { defer wg.Done() _, err := f.Commit() if err != nil { handleError(w, err) } }(f) } wg.Add(2) go func() { defer wg.Done() _, err := fancyRepo.CommitCount() if err != nil { handleError(w, err) } }() go func() { defer wg.Done() _, err := fancyRepo.Contributors() if err != nil { handleError(w, err) } }() wg.Wait() buf := new(bytes.Buffer) start := time.Now() err = repoTmpl.Execute(buf, map[string]interface{}{ "RepoPath": path.Base(repoPath), "Repo": fancyRepo, "Style": template.CSS(repoStyle), "Performance": performance.Render(req), }) performance.Measure(req, "template", time.Since(start).String()) if err != nil { log.Println("rendering repo: ", err) http.Error(w, "internal server error", http.StatusInternalServerError) return } io.Copy(w, buf) }) http.Handle("/__performance__/", performance) log.Fatal(http.ListenAndServe("localhost:12345", nil)) } type FancyRepo struct { repo *git.Repository commit *FancyCommit commitCount int branches []*git.Branch contributors []*git.Signature languageStats *LanguageStats } func NewFancyRepo(repo *git.Repository) *FancyRepo { return &FancyRepo{repo: repo, commitCount: -1} } func (fr *FancyRepo) Commit() (*FancyCommit, error) { if fr.commit == nil { head, err := fr.repo.Head() if err != nil { return nil, err } commit, err := fr.repo.LookupCommit(head.Target()) if err != nil { return nil, err } fr.commit = NewFancyCommit(commit) } return fr.commit, nil } func (fr *FancyRepo) CommitCount() (int, error) { if fr.commitCount < 0 { walk, err := fr.repo.Walk() if err != nil { return -1, err } head, err := fr.repo.Head() if err != nil { return -1, err } err = walk.Push(head.Target()) if err != nil { return -1, err } c := 0 err = walk.Iterate(func(commit *git.Commit) bool { c += 1 return true }) if err != nil { return -1, err } fr.commitCount = c } return fr.commitCount, nil } func (fr *FancyRepo) Contributors() ([]*git.Signature, error) { if fr.contributors == nil { walk, err := fr.repo.Walk() if err != nil { return nil, err } head, err := fr.repo.Head() if err != nil { return nil, err } err = walk.Push(head.Target()) if err != nil { return nil, err } authors := make(map[string]*git.Signature) err = walk.Iterate(func(commit *git.Commit) bool { authors[commit.Author().Name] = commit.Author() return true }) if err != nil { return nil, err } for _, author := range authors { fr.contributors = append(fr.contributors, author) } } return fr.contributors, nil } func (fr *FancyRepo) Branches() ([]*git.Branch, error) { if fr.branches == nil { it, err := fr.repo.NewBranchIterator(git.BranchRemote) if err != nil { return nil, err } err = it.ForEach(func(b *git.Branch, bt git.BranchType) error { fr.branches = append(fr.branches, b) return nil }) if err != nil { return nil, err } } return fr.branches, nil } func (fr *FancyRepo) Tags() ([]string, error) { return fr.repo.Tags.List() } var languageSuffixes = map[string]string{ ".c": "C", ".h": "C", ".cpp": "C++", "CMakeLists.txt": "CMake", ".cmake": "CMake", ".css": "CSS", ".go": "Go", ".html": "HTML", ".js": "JavaScript", ".java": "Java", "Makefile": "Makefile", ".py": "Python", ".rb": "Ruby", ".rs": "Rust", ".sh": "Shell", ".bash": "Shell", } var languageColors = map[string]string{ "C": "#555555", "C++": "#f34b7d", //"CMake": "", "CSS": "#563d7c", "Go": "#375eab", "HTML": "#e34c26", "JavaScript": "#f1e05a", "Java": "#b07219", "Makefile": "#427819", "Python": "#3572A5", "Ruby": "#701516", "Rust": "#dea584", "Shell": "#89e051", } func fileToLanguage(filename string) string { filename = strings.ToLower(filename) for suffix, lang := range languageSuffixes { if strings.HasSuffix(filename, suffix) { return lang } } return "" } type LanguageStats struct { percentages map[string]float64 Languages []*LanguageInfo } type LanguageInfo struct { Language string Percentage float64 } func (li *LanguageInfo) String() string { return fmt.Sprintf("%s (%.2f%%)", li.Language, li.Percentage) } type languageByPercent []*LanguageInfo func (l languageByPercent) Len() int { return len(l) } func (l languageByPercent) Less(i, j int) bool { return l[i].Percentage < l[j].Percentage } func (l languageByPercent) Swap(i, j int) { l[i], l[j] = l[j], l[i] } func NewLanguageStats(percentages map[string]float64) *LanguageStats { for lang, percentage := range percentages { if percentage < 0.001 { delete(percentages, lang) continue } percentage = math.Trunc(percentage*10000) / 100 percentages[lang] = percentage } languages := make([]*LanguageInfo, 0, len(percentages)) for lang, percentage := range percentages { languages = append(languages, &LanguageInfo{Language: lang, Percentage: percentage}) } sort.Sort(sort.Reverse(languageByPercent(languages))) return &LanguageStats{ percentages: percentages, Languages: languages, } } func (li *LanguageStats) String() string { return fmt.Sprintf("%v", li.percentages) } func (fr *FancyRepo) LanguageStats() (*LanguageStats, error) { if fr.languageStats == nil { commit, err := fr.Commit() if err != nil { return nil, err } files, err := commit.Files(true) if err != nil { return nil, err } total := 0 languages := make(map[string]int) for _, f := range files { if f.entry.Type != git.ObjectBlob { continue } lang := fileToLanguage(f.Name()) if lang == "" { continue } total += 1 languages[lang] += 1 } percentages := make(map[string]float64) for lang, count := range languages { percentages[lang] = float64(count) / float64(total) } fr.languageStats = NewLanguageStats(percentages) } return fr.languageStats, nil } type FancyCommit struct { commit *git.Commit files []*FancyFile filesCache map[string]*FancyFile } func NewFancyCommit(commit *git.Commit) *FancyCommit { return &FancyCommit{ commit: commit, filesCache: make(map[string]*FancyFile), } } func (fc *FancyCommit) Author() string { return fc.commit.Author().Name } func (fc *FancyCommit) Date() *FancyTime { return &FancyTime{fc.commit.Author().When} } type FancyTime struct { time.Time } func (fc FancyTime) PrettyString() string { makeForm := func(c int, singular, plural string) string { form := plural if c == 1 { form = singular } return fmt.Sprintf("%d %s ago", c, form) } now := time.Now() diff := time.Since(fc.Time) switch { case diff < time.Minute: return makeForm(int(diff/time.Second), "second", "seconds") case diff < time.Hour: return makeForm(int(diff/time.Minute), "minute", "minutes") case now.AddDate(0, 0, -1).Before(fc.Time): return makeForm(int(diff/time.Hour), "hour", "hours") case now.AddDate(0, 0, -7).Before(fc.Time): d := now.Day() - fc.Time.Day() if d <= 0 { d += 30 } return makeForm(d, "day", "days") case now.AddDate(0, -1, 0).Before(fc.Time): d := now.Day() - fc.Time.Day() if d <= 0 { d += 30 } return makeForm(d/7, "week", "weeks") case now.AddDate(-1, 0, 0).Before(fc.Time): m := now.Month() - fc.Time.Month() if m <= 0 { m += 12 } return makeForm(int(m), "month", "months") default: return makeForm(now.Year()-fc.Time.Year(), "year", "years") } } func (fc *FancyCommit) Id(n int) string { if n == 0 { return fc.commit.TreeId().String() } return fc.commit.TreeId().String()[:n] } func (fc *FancyCommit) Summary() string { return fc.commit.Summary() } func (fc *FancyCommit) Description() string { return fc.commit.Message() } type byTypeAndName []*FancyFile func (b byTypeAndName) Len() int { return len(b) } func (b byTypeAndName) Less(i, j int) bool { return b[i].Less(b[j]) } func (b byTypeAndName) Swap(i, j int) { b[i], b[j] = b[j], b[i] } func (fc *FancyCommit) Files(recursive bool) ([]*FancyFile, error) { if recursive || fc.files == nil { tree, err := fc.commit.Tree() if err != nil { return nil, err } recurse := 1 if recursive { recurse = 0 } files := make([]*FancyFile, 0) err = tree.Walk(func(s string, entry *git.TreeEntry) int { files = append(files, NewFancyFile(fc.commit.Owner(), entry)) return recurse }) if err != nil { return nil, err } sort.Sort(byTypeAndName(files)) if recursive { return files, nil } fc.files = files } return fc.files, nil } var readmeNames = []string{"README.md", "readme.md", "README.rst", "readme.rst", "README.txt", "readme.txt", "README"} func (fc *FancyCommit) Readme() (*FancyFile, error) { readme := fc.filesCache["|||README|||"] if readme == nil { tree, err := fc.commit.Tree() if err != nil { return nil, err } var entry *git.TreeEntry for _, n := range readmeNames { entry, err = tree.EntryByPath(n) if err == nil { break } } if entry == nil { return nil, nil } readme = NewFancyFile(fc.commit.Owner(), entry) fc.filesCache["|||README|||"] = readme } return readme, nil } type FancyFile struct { repo *git.Repository entry *git.TreeEntry commit *FancyCommit } func NewFancyFile(repo *git.Repository, entry *git.TreeEntry) *FancyFile { return &FancyFile{repo: repo, entry: entry} } func (fc *FancyFile) Name() string { return fc.entry.Name } func (fc *FancyFile) Commit() (*FancyCommit, error) { if fc.commit == nil { walk, err := fc.repo.Walk() if err != nil { return nil, err } err = walk.PushHead() if err != nil { return nil, err } opts, err := git.DefaultDiffOptions() if err != nil { return nil, err } opts.Pathspec = []string{fc.entry.Name} var lastCommit *git.Commit var findErr error c := 0 err = walk.Iterate(func(commit *git.Commit) bool { c += 1 if commit.ParentCount() <= 0 { lastCommit = commit return false } tree, err := commit.Tree() if err != nil { findErr = err return false } matchesAll := true for i := uint(0); i < commit.ParentCount(); i++ { parentCommit := commit.Parent(i) parentTree, err := parentCommit.Tree() if err != nil { findErr = err return false } diff, err := fc.repo.DiffTreeToTree(parentTree, tree, &opts) if err != nil { findErr = err return false } n, err := diff.NumDeltas() if err != nil { findErr = err return false } matchesAll = matchesAll && n > 0 } if !matchesAll { return true } lastCommit = commit return false }) if err != nil { return nil, err } if findErr != nil { return nil, err } if lastCommit == nil { return nil, fmt.Errorf("did not find commit for %q", fc.entry.Name) } fc.commit = NewFancyCommit(lastCommit) } return fc.commit, nil } func (fc *FancyFile) Type() string { return strings.ToLower(fc.entry.Type.String()) } func (fc *FancyFile) RawContents() (string, error) { blob, err := fc.repo.LookupBlob(fc.entry.Id) if err != nil { return "", err } return string(blob.Contents()), nil } func (fc *FancyFile) Contents() (template.HTML, error) { blob, err := fc.repo.LookupBlob(fc.entry.Id) if err != nil { return "", err } buf := new(bytes.Buffer) contents := blob.Contents() if strings.HasSuffix(fc.entry.Name, ".md") { buf.Write(blackfriday.MarkdownCommon(contents)) } else { buf.WriteString("<code><pre>") template.HTMLEscape(buf, contents) buf.WriteString("</pre></code>") } return template.HTML(buf.String()), nil } func (fc *FancyFile) Less(file *FancyFile) bool { if fc.entry.Type == file.entry.Type { return fc.entry.Name < file.entry.Name } return fc.entry.Type < file.entry.Type } var repoFuncs = template.FuncMap{ "count": func(count int, singular, plural string) template.HTML { form := plural if count == 1 { form = singular } return template.HTML(fmt.Sprintf("<span class=\"count\">%d</span> %s", count, form)) }, "languageToColor": func(l string) string { c, ok := languageColors[l] if !ok { return "black" } return c }, } var repoTmpl = template.Must(template.New("").Funcs(repoFuncs).Parse(`<!doctype html> <html> <head> <meta charset="utf-8" /> <title>{{ .RepoPath }}</title> <style>{{ .Style }}</style> </head> <body> <section id="performance-bar"></section> <div id="container"> <section id="repo" class="repo-info"> <a id="commits" class="repo-stat" href="#"><span class="icon">⏲</span>{{ count .Repo.CommitCount "commit" "commits" }}</a> <a id="branches" class="repo-stat" href="#"><span class="icon">⛗</span>{{ count (len .Repo.Branches) "branch" "branches" }}</a> <a id="tags" class="repo-stat" href="#"><span class="icon">🏷&#xFE0E;</span>{{ count (len .Repo.Tags) "tag" "tags" }}</a> <a id="contributors" class="repo-stat" href="#"><span class="icon">👥</span>{{ count (len .Repo.Contributors) "contributor" "contributors" }}</a> </section> <div id="languages"> <ol class="language-list"> {{ range $language := .Repo.LanguageStats.Languages }} <li> <span class="language">{{ $language.Language }}</span> <span class="percentage">{{ $language.Percentage }}%</span> </li> {{ end }} </ol> <div id="language-bar"> {{ range $language := .Repo.LanguageStats.Languages }} <span class="language" title="{{ $language }}" style="width: {{ $language.Percentage }}%; background-color: {{ languageToColor $language.Language }}"><!--{{ $language.Language }}--></span> {{ end }} </div> </div> <section id="commit" class="commit-info"> <div class="commit-author">{{ .Repo.Commit.Author }}</div> <div class="commit-summary" title="{{ .Repo.Commit.Description }}">{{ .Repo.Commit.Summary }}</div> <div class="commit-id" data-commit="{{ .Repo.Commit.Id 0 }}"><a href="#">{{ .Repo.Commit.Id 10 }}</a></div> <time class="commit-date" title="{{ .Repo.Commit.Date }}">{{ .Repo.Commit.Date.PrettyString }}</time> </section> <table id="files"> {{ range $file := .Repo.Commit.Files false }} <tr class="file"> <td class="file-name file-type-{{ $file.Type }}">{{ $file.Name }}</td> <td class="file-commit" title="{{ $file.Commit.Description }}">{{ $file.Commit.Summary }}</td> <td class="file-date" title="{{ $file.Commit.Date }}">{{ $file.Commit.Date.PrettyString }}</td> </tr> {{ end }} </table> {{ if .Repo.Commit.Readme }} <section id="file" class="readme"> <h1 class="file-name">{{ .Repo.Commit.Readme.Name }}</h1> <div class="contents"> {{ .Repo.Commit.Readme.Contents }} </div> </section> {{ end }} </div> {{ .Performance }} </body> </html> `)) var repoStyle = ` * { box-sizing: border-box; } body { font-family: Arial, sans-serif; } #container { width: 70vw; margin: 0 auto; } #repo { display: flex; justify-content: center; border: 1px solid #ddd; border-radius: 3px; padding: 0.75em; margin-bottom: 1em } #repo .repo-stat { margin-right: 1em; color: black; text-decoration: none; } #repo .repo-stat:hover { color: #ad56a0; } #repo .repo-stat:hover .icon { color: black; } #repo .repo-stat .count { font-weight: 600; } #repo .icon { padding-right: 0.2em; } .language-list { display: none; } #language-bar { display: flex; height: 0.5em; margin: -1em 0 1em; } #commit { display: flex; justify-content: space-between; align-items: baseline; padding: 0.6em 0.5em; background-color: hsla(300, 70%, 80%, 0.2); border: 1px solid rgba(200, 0, 255, 0.15); border-radius: 3px; border-bottom-left-radius: 0; border-bottom-right-radius: 0; } #commit .commit-author { font-weight: 600; } #commit .commit-id { font-family: monospace; } #files td { border: 1px solid rgba(200, 0, 200, 0.15); border-radius: 3px; border-bottom-left-radius: 0; border-bottom-right-radius: 0; padding: 0.2em 0.5em; font-size: 95%; } #files tr:hover { background-color: #efefef; } #files { width: 70vw; border-collapse: collapse; margin-top: -1px; } #files .file-name { color: #ad56a0; } .file-type-blob:before { content:"🖹"; padding-right: 0.5em; } .file-type-tree:before { content: "🗀"; padding-right: 0.2em; } .file-type-tree:after { content: "/"; } #file .file-name { font-size: 90%; padding: 0.5em; margin-bottom: -1px; background-color: hsla(300, 70%, 80%, 0.2); border: 1px solid #fbd3f5; border-radius: 3px; border-bottom-left-radius: 0; border-bottom-right-radius: 0; } #file .contents { border: 1px solid #fbd3f5; padding: 0.5em 3em; } #file .contents > h1 { border-bottom: 1px solid #ddd; padding-bottom: 0.5em; } `
Releases - git.papill0n.org

Releases

lu/lp - git.papill0n.org

6 次代码提交 (b7deef8a6c3bdf85e3fa4edaf8d018f8efebf9d5)

作者 SHA1 备注 提交日期
  Lucas Stadler 1edaade071 fix fetching dependencies for pixl 11 年之前
  Lucas Stadler 3133c0b5f5 use controls from three.js. 13 年之前
  Lucas Stadler 2349149fbd make running the server a bit more convenient. 13 年之前
  Lucas Stadler 2a39244e89 chunking demo using three.js. 13 年之前
  Lucas Stadler 251e2f7f87 use glmatrix library for multiplication. 13 年之前
  Lucas Stadler 3258b71ce3 make fetching the live world data convenient. 13 年之前