refactor to prepare for CLI

This commit is contained in:
Jeremy Rans
2018-12-14 23:35:02 -06:00
parent 62259ac9c6
commit c958ecb465
4 changed files with 159 additions and 131 deletions

View File

@@ -1,5 +1,11 @@
package check
import (
"fmt"
"log"
"sort"
)
// Check describes what methods various checks (gofmt, go lint, etc.)
// should implement
type Check interface {
@@ -10,3 +16,99 @@ type Check interface {
// as well as a map of filename to output
Percentage() (float64, []FileSummary, error)
}
type Score struct {
Name string `json:"name"`
Description string `json:"description"`
FileSummaries []FileSummary `json:"file_summaries"`
Weight float64 `json:"weight"`
Percentage float64 `json:"percentage"`
Error string `json:"error"`
}
type ChecksResult struct {
Checks []Score `json:"checks"`
Average float64 `json:"average"`
Grade Grade `json:"GradeFromPercentage"`
Files int `json:"files"`
Issues int `json:"issues"`
}
func CheckDir(dir string) (ChecksResult, error) {
filenames, skipped, err := GoFiles(dir)
if err != nil {
return ChecksResult{}, fmt.Errorf("could not get filenames: %v", err)
}
if len(filenames) == 0 {
return ChecksResult{}, fmt.Errorf("no .go files found")
}
err = RenameFiles(skipped)
if err != nil {
log.Println("Could not remove files:", err)
}
defer RevertFiles(skipped)
checks := []Check{
GoFmt{Dir: dir, Filenames: filenames},
GoVet{Dir: dir, Filenames: filenames},
GoLint{Dir: dir, Filenames: filenames},
GoCyclo{Dir: dir, Filenames: filenames},
License{Dir: dir, Filenames: []string{}},
Misspell{Dir: dir, Filenames: filenames},
IneffAssign{Dir: dir, Filenames: filenames},
// ErrCheck{Dir: dir, Filenames: filenames}, // disable errcheck for now, too slow and not finalized
}
ch := make(chan Score)
for _, c := range checks {
go func(c Check) {
p, summaries, err := c.Percentage()
errMsg := ""
if err != nil {
log.Printf("ERROR: (%s) %v", c.Name(), err)
errMsg = err.Error()
}
s := Score{
Name: c.Name(),
Description: c.Description(),
FileSummaries: summaries,
Weight: c.Weight(),
Percentage: p,
Error: errMsg,
}
ch <- s
}(c)
}
resp := ChecksResult{
Files: len(filenames),
}
var total, totalWeight float64
var issues = make(map[string]bool)
for i := 0; i < len(checks); i++ {
s := <-ch
resp.Checks = append(resp.Checks, s)
total += s.Percentage * s.Weight
totalWeight += s.Weight
for _, fs := range s.FileSummaries {
issues[fs.Filename] = true
}
}
total /= totalWeight
sort.Sort(ByWeight(resp.Checks))
resp.Average = total
resp.Issues = len(issues)
resp.Grade = GradeFromPercentage(total * 100)
return resp, nil
}
// ByWeight implements sorting for checks by weight descending
type ByWeight []Score
func (a ByWeight) Len() int { return len(a) }
func (a ByWeight) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByWeight) Less(i, j int) bool { return a[i].Weight > a[j].Weight }

37
check/grade.go Normal file
View File

@@ -0,0 +1,37 @@
package check
// 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"
)
// GradeFromPercentage is a helper for getting the GradeFromPercentage for a percentage
func GradeFromPercentage(percentage float64) Grade {
switch {
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
}
}

View File

@@ -2,48 +2,13 @@ package handlers
import (
"fmt"
"github.com/gojp/goreportcard/check"
"log"
"net/http"
"strings"
)
// 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"
)
// grade is a helper for getting the grade for a percentage
func grade(percentage float64) Grade {
switch {
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 badgePath(grade Grade, style string, dev bool) string {
func badgePath(grade check.Grade, style string, dev bool) string {
return fmt.Sprintf("assets/badges/%s_%s.svg", strings.ToLower(string(grade)), strings.ToLower(style))
}

View File

@@ -5,11 +5,10 @@ import (
"errors"
"fmt"
"log"
"sort"
"time"
"github.com/boltdb/bolt"
humanize "github.com/dustin/go-humanize"
"github.com/dustin/go-humanize"
"github.com/gojp/goreportcard/check"
"github.com/gojp/goreportcard/download"
)
@@ -63,26 +62,16 @@ func getFromCache(repo string) (checksResp, error) {
return resp, nil
}
type score struct {
Name string `json:"name"`
Description string `json:"description"`
FileSummaries []check.FileSummary `json:"file_summaries"`
Weight float64 `json:"weight"`
Percentage float64 `json:"percentage"`
Error string `json:"error"`
}
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"`
ResolvedRepo string `json:"resolvedRepo"`
LastRefresh time.Time `json:"last_refresh"`
LastRefreshFormatted string `json:"formatted_last_refresh"`
LastRefreshHumanized string `json:"humanized_last_refresh"`
Checks []check.Score `json:"checks"`
Average float64 `json:"average"`
Grade check.Grade `json:"grade"`
Files int `json:"files"`
Issues int `json:"issues"`
Repo string `json:"repo"`
LastRefresh time.Time `json:"last_refresh"`
LastRefreshFormatted string `json:"formatted_last_refresh"`
LastRefreshHumanized string `json:"humanized_last_refresh"`
}
func newChecksResp(repo string, forceRefresh bool) (checksResp, error) {
@@ -92,7 +81,7 @@ func newChecksResp(repo string, forceRefresh bool) (checksResp, error) {
// just log the error and continue
log.Println(err)
} else {
resp.Grade = grade(resp.Average * 100) // grade is not stored for some repos, yet
resp.Grade = check.GradeFromPercentage(resp.Average * 100) // grade is not stored for some repos, yet
return resp, nil
}
}
@@ -104,82 +93,24 @@ func newChecksResp(repo string, forceRefresh bool) (checksResp, error) {
}
repo = repoRoot.Root
dir := dirName(repo)
filenames, skipped, err := check.GoFiles(dir)
checkResult, err := check.CheckDir(dirName(repo))
if err != nil {
return checksResp{}, fmt.Errorf("could not get filenames: %v", err)
}
if len(filenames) == 0 {
return checksResp{}, fmt.Errorf("no .go files found")
}
err = check.RenameFiles(skipped)
if err != nil {
log.Println("Could not remove files:", err)
}
defer check.RevertFiles(skipped)
checks := []check.Check{
check.GoFmt{Dir: dir, Filenames: filenames},
check.GoVet{Dir: dir, Filenames: filenames},
check.GoLint{Dir: dir, Filenames: filenames},
check.GoCyclo{Dir: dir, Filenames: filenames},
check.License{Dir: dir, Filenames: []string{}},
check.Misspell{Dir: dir, Filenames: filenames},
check.IneffAssign{Dir: dir, Filenames: filenames},
// check.ErrCheck{Dir: dir, Filenames: filenames}, // disable errcheck for now, too slow and not finalized
}
ch := make(chan score)
for _, c := range checks {
go func(c check.Check) {
p, summaries, err := c.Percentage()
errMsg := ""
if err != nil {
log.Printf("ERROR: (%s) %v", c.Name(), err)
errMsg = err.Error()
}
s := score{
Name: c.Name(),
Description: c.Description(),
FileSummaries: summaries,
Weight: c.Weight(),
Percentage: p,
Error: errMsg,
}
ch <- s
}(c)
return checksResp{}, err
}
t := time.Now().UTC()
resp := checksResp{
Checks: checkResult.Checks,
Average: checkResult.Average,
Grade: checkResult.Grade,
Files: checkResult.Files,
Issues: checkResult.Issues,
Repo: repo,
ResolvedRepo: repoRoot.Repo,
Files: len(filenames),
LastRefresh: t,
LastRefreshFormatted: t.Format(time.UnixDate),
LastRefreshHumanized: humanize.Time(t),
}
var total, totalWeight float64
var issues = make(map[string]bool)
for i := 0; i < len(checks); i++ {
s := <-ch
resp.Checks = append(resp.Checks, s)
total += s.Percentage * s.Weight
totalWeight += s.Weight
for _, fs := range s.FileSummaries {
issues[fs.Filename] = true
}
}
total /= totalWeight
sort.Sort(ByWeight(resp.Checks))
resp.Average = total
resp.Issues = len(issues)
resp.Grade = grade(total * 100)
respBytes, err := json.Marshal(resp)
if err != nil {
return checksResp{}, fmt.Errorf("could not marshal json: %v", err)
@@ -242,10 +173,3 @@ func newChecksResp(repo string, forceRefresh bool) (checksResp, error) {
return resp, nil
}
// ByWeight implements sorting for checks by weight descending
type ByWeight []score
func (a ByWeight) Len() int { return len(a) }
func (a ByWeight) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByWeight) Less(i, j int) bool { return a[i].Weight > a[j].Weight }