Files
goreportcard/check/utils.go
2015-01-30 00:35:43 +09:00

193 lines
4.4 KiB
Go

package check
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"reflect"
"strconv"
"strings"
"syscall"
)
// GoFiles returns a slice of Go filenames
// in a given directory.
func GoFiles(dir string) ([]string, error) {
var filenames []string
visit := func(fp string, fi os.FileInfo, err error) error {
if strings.Contains(fp, "Godeps") {
return nil
}
if err != nil {
fmt.Println(err) // can't walk here,
return nil // but continue walking elsewhere
}
if fi.IsDir() {
return nil // not a file. ignore.
}
ext := filepath.Ext(fi.Name())
if ext == ".go" {
filenames = append(filenames, fp)
}
return nil
}
err := filepath.Walk(dir, visit)
return filenames, err
}
// lineCount returns the number of lines in a given file
func lineCount(filepath string) (int, error) {
out, err := exec.Command("wc", "-l", filepath).Output()
if err != nil {
return 0, err
}
// wc output is like: 999 filename.go
count, err := strconv.Atoi(strings.Split(strings.TrimSpace(string(out)), " ")[0])
if err != nil {
return 0, err
}
return count, nil
}
type Error struct {
LineNumber int `json:"line_number"`
ErrorString string `json:"error_string"`
}
type FileSummary struct {
Filename string `json:"filename"`
FileURL string `json:"file_url"`
Errors []Error `json:"errors"`
}
// ByFilename implements sort.Interface for []Person based on
// the Age field.
type ByFilename []FileSummary
func (a ByFilename) Len() int { return len(a) }
func (a ByFilename) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByFilename) Less(i, j int) bool { return a[i].Filename < a[j].Filename }
func getFileSummary(filename, dir, cmd, out string) (FileSummary, error) {
filename = strings.TrimPrefix(filename, "repos/src")
githubLink := strings.TrimPrefix(dir, "repos/src")
fileURL := "https://" + strings.TrimPrefix(dir, "repos/src/") + "/blob/master" + strings.TrimPrefix(filename, githubLink)
fs := FileSummary{
Filename: filename,
FileURL: fileURL,
}
split := strings.Split(string(out), "\n")
for _, sp := range split[0 : len(split)-1] {
parts := strings.Split(sp, ":")
msg := sp
if cmd != "gocyclo" {
msg = parts[len(parts)-1]
}
e := Error{ErrorString: msg}
switch cmd {
case "golint", "gocyclo", "vet":
ln, err := strconv.Atoi(strings.Split(sp, ":")[1])
if err != nil {
return fs, err
}
e.LineNumber = ln
}
fs.Errors = append(fs.Errors, e)
}
return fs, nil
}
// GoTool runs a given go command (for example gofmt, go tool vet)
// on a directory
func GoTool(dir string, filenames, command []string) (float64, []FileSummary, error) {
var failed = []FileSummary{}
for _, fi := range filenames {
params := command[1:]
params = append(params, fi)
cmd := exec.Command(command[0], params...)
stdout, err := cmd.StdoutPipe()
if err != nil {
return 0, []FileSummary{}, nil
}
stderr, err := cmd.StderrPipe()
if err != nil {
return 0, []FileSummary{}, nil
}
err = cmd.Start()
if err != nil {
return 0, []FileSummary{}, nil
}
out, err := ioutil.ReadAll(stdout)
if err != nil {
return 0, []FileSummary{}, nil
}
errout, err := ioutil.ReadAll(stderr)
if err != nil {
return 0, []FileSummary{}, nil
}
if string(out) != "" {
fs, err := getFileSummary(fi, dir, command[0], string(out))
if err != nil {
return 0, []FileSummary{}, nil
}
failed = append(failed, fs)
}
// go vet logs to stderr
if string(errout) != "" {
cmd := command[0]
if reflect.DeepEqual(command, []string{"go", "tool", "vet"}) {
cmd = "vet"
}
fs, err := getFileSummary(fi, dir, cmd, string(errout))
if err != nil {
return 0, []FileSummary{}, nil
}
failed = append(failed, fs)
}
err = cmd.Wait()
if exitErr, ok := err.(*exec.ExitError); ok {
// The program has exited with an exit code != 0
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
// some commands exit 1 when files fail to pass (for example go vet)
if status.ExitStatus() != 1 {
return 0, failed, err
// return 0, Error{}, err
}
}
}
}
if len(filenames) == 1 {
lc, err := lineCount(filenames[0])
if err != nil {
return 0, failed, err
}
var errors int
if len(failed) != 0 {
errors = len(failed[0].Errors)
}
return float64(lc-errors) / float64(lc), failed, nil
}
return float64(len(filenames)-len(failed)) / float64(len(filenames)), failed, nil
}