diff --git a/main.go b/main.go index 0279152..e52e99b 100644 --- a/main.go +++ b/main.go @@ -94,6 +94,7 @@ type score struct { type checksResp struct { Checks []score `json:"checks"` Average float64 `json:"average"` + Grade Grade `json:"grade"` Files int `json:"files"` Issues int `json:"issues"` Repo string `json:"repo"` @@ -117,47 +118,69 @@ func getFromCache(repo string) (checksResp, error) { return resp, nil } -func checkHandler(w http.ResponseWriter, r *http.Request) { - repo := r.FormValue("repo") +// Grade represents a grade returned by the server, which is normally +// somewhere between A+ (highest) and F (lowest). +type Grade string + +// The Grade constants below indicate the current available +// grades. +const ( + GradeAPlus Grade = "A+" + GradeA = "A" + GradeB = "B" + GradeC = "C" + GradeD = "D" + GradeE = "E" + GradeF = "F" +) + +// getGrade is a helper for getting the grade for a percentage +func getGrade(percentage float64) Grade { + switch true { + case percentage > 90: + return GradeAPlus + case percentage > 80: + return GradeA + case percentage > 70: + return GradeB + case percentage > 60: + return GradeC + case percentage > 50: + return GradeD + case percentage > 40: + return GradeE + default: + return GradeF + } +} + +func newChecksResp(repo string, forceRefresh bool) (checksResp, error) { url := repo if !strings.HasPrefix(url, "https://github.com/") { url = "https://github.com/" + url } - w.Header().Set("Content-Type", "application/json") - - // if this is a GET request, fetch from cached version in mongo - if r.Method == "GET" { + if !forceRefresh { resp, err := getFromCache(repo) if err != nil { // just log the error and continue log.Println(err) } else { - b, err := json.Marshal(resp) - if err != nil { - log.Println("ERROR: could not marshal json:", err) - http.Error(w, err.Error(), 500) - return - } - w.Write(b) - log.Println("Loaded from cache!", repo) - return + resp.Grade = getGrade(resp.Average * 100) // grade is not stored for some repos, yet + return resp, nil } } + // fetch the repo and grade it err := clone(url) if err != nil { - log.Println("ERROR: could not clone repo: ", err) - http.Error(w, fmt.Sprintf("Could not clone repo: %v", err), 500) - return + return checksResp{}, fmt.Errorf("Could not clone repo: %v", err) } dir := dirName(url) filenames, err := check.GoFiles(dir) if err != nil { - log.Println("ERROR: could not get filenames: ", err) - http.Error(w, fmt.Sprintf("Could not get filenames: %v", err), 500) - return + return checksResp{}, fmt.Errorf("Could not get filenames: %v", err) } checks := []check.Check{check.GoFmt{Dir: dir, Filenames: filenames}, check.GoVet{Dir: dir, Filenames: filenames}, @@ -198,6 +221,22 @@ func checkHandler(w http.ResponseWriter, r *http.Request) { resp.Average = avg / float64(len(checks)) resp.Issues = len(issues) + resp.Grade = getGrade(resp.Average * 100) + + return resp, nil +} + +func checkHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + repo := r.FormValue("repo") + forceRefresh := r.Method != "GET" // if this is a GET request, try fetch from cached version in mongo first + resp, err := newChecksResp(repo, forceRefresh) + if err != nil { + b, _ := json.Marshal(err) + w.WriteHeader(http.StatusInternalServerError) + w.Write(b) + } b, err := json.Marshal(resp) if err != nil { @@ -224,9 +263,35 @@ func reportHandler(w http.ResponseWriter, r *http.Request, org, repo string) { http.ServeFile(w, r, "templates/home.html") } -func makeReportHandler(fn func(http.ResponseWriter, *http.Request, string, string)) http.HandlerFunc { +func badgeURL(grade Grade) string { + colorMap := map[Grade]string{ + GradeAPlus: "brightgreen", + GradeA: "brightgreen", + GradeB: "yellowgreen", + GradeC: "yellow", + GradeD: "orange", + GradeE: "red", + GradeF: "red", + } + url := fmt.Sprintf("https://img.shields.io/badge/go_report-%s-%s.svg", grade, colorMap[grade]) + return url +} + +func badgeHandler(w http.ResponseWriter, r *http.Request, org, repo string) { + name := fmt.Sprintf("%s/%s", org, repo) + resp, err := newChecksResp(name, false) + if err != nil { + log.Printf("ERROR: fetching badge for %s: %v", name, err) + http.Redirect(w, r, "https://img.shields.io/badge/go%20report-error-lightgrey.svg", http.StatusTemporaryRedirect) + return + } + + http.Redirect(w, r, badgeURL(resp.Grade), http.StatusTemporaryRedirect) +} + +func makeHandler(name string, fn func(http.ResponseWriter, *http.Request, string, string)) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - validPath := regexp.MustCompile(`^/report/([a-zA-Z0-9\-_]+)/([a-zA-Z0-9\-_]+)$`) + validPath := regexp.MustCompile(fmt.Sprintf(`^/%s/([a-zA-Z0-9\-_]+)/([a-zA-Z0-9\-_]+)$`, name)) m := validPath.FindStringSubmatch(r.URL.Path) if m == nil { @@ -244,7 +309,8 @@ func main() { http.HandleFunc("/assets/", assetsHandler) http.HandleFunc("/checks", checkHandler) - http.HandleFunc("/report/", makeReportHandler(reportHandler)) + http.HandleFunc("/report/", makeHandler("report", reportHandler)) + http.HandleFunc("/badge/", makeHandler("badge", badgeHandler)) http.HandleFunc("/", homeHandler) fmt.Println("Running on 127.0.01:8080...") diff --git a/templates/home.html b/templates/home.html index b2374a6..2b6d33b 100644 --- a/templates/home.html +++ b/templates/home.html @@ -258,31 +258,16 @@