/
hound.go
104 lines (87 loc) · 2.83 KB
/
hound.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
package main
import (
"errors"
"fmt"
"github.com/ezekg/git-hound/Godeps/_workspace/src/github.com/fatih/color"
"github.com/ezekg/git-hound/Godeps/_workspace/src/gopkg.in/yaml.v2"
"github.com/ezekg/git-hound/Godeps/_workspace/src/sourcegraph.com/sourcegraph/go-diff/diff"
"io/ioutil"
"path/filepath"
"regexp"
)
// A Hound contains the local configuration filename and all regexp patterns
// used for sniffing git-diffs.
type Hound struct {
Config string
Fails []string `yaml:"fail"`
Warns []string `yaml:"warn"`
Skips []string `yaml:"skip"`
}
// New initializes a new Hound instance by parsing regexp patterns from a
// local configuration file and returns a status bool.
func (h *Hound) New() bool {
config, err := h.LoadConfig()
if err != nil {
return false
}
err = h.Parse(config)
if err != nil {
return false
}
return true
}
// LoadConfig reads a local configuration file of regexp patterns and returns
// the contents of the file.
func (h *Hound) LoadConfig() ([]byte, error) {
filename, _ := filepath.Abs(h.Config)
return ioutil.ReadFile(filename)
}
// Parse parses a configuration byte array and returns an error.
func (h *Hound) Parse(data []byte) error {
return yaml.Unmarshal(data, h)
}
// Sniff matches the passed git-diff hunk against all regexp patterns that
// were parsed from the local configuration.
func (h *Hound) Sniff(fileName string, hunk *diff.Hunk, warnc chan string, failc chan error, donec chan bool) {
defer func() { donec <- true }()
r1, _ := regexp.Compile(`^\w+\/`)
fileName = r1.ReplaceAllString(fileName, "")
if _, ok := h.MatchPatterns(h.Skips, []byte(fileName)); ok {
return
}
r2, _ := regexp.Compile(`(?m)^\+\s*(.+)$`)
matches := r2.FindAllSubmatch(hunk.Body, -1)
for _, match := range matches {
line := match[1]
if pattern, warned := h.MatchPatterns(h.Warns, line); warned {
msg := color.YellowString(fmt.Sprintf(
"warning: pattern `%s` match found for `%s` starting at line %d in %s\n",
pattern, line, hunk.NewStartLine, fileName))
warnc <- msg
}
if pattern, failed := h.MatchPatterns(h.Fails, line); failed {
msg := color.RedString(fmt.Sprintf(
"failure: pattern `%s` match found for `%s` starting at line %d in %s\n",
pattern, line, hunk.NewStartLine, fileName))
failc <- errors.New(msg)
}
}
}
// Match matches a byte array against a regexp pattern and returns a bool.
func (h *Hound) Match(pattern string, subject []byte) bool {
r, err := regexp.Compile(pattern)
if err != nil {
return false
}
return r.Match(subject)
}
// MatchPatterns matches a byte array against an array of regexp patterns and
// returns the matched pattern and a bool.
func (h *Hound) MatchPatterns(patterns []string, subject []byte) (string, bool) {
for _, pattern := range patterns {
if match := h.Match(pattern, subject); match {
return pattern, true
}
}
return "", false
}