@@ -3,12 +3,23 @@ package codeowners
3
3
import (
4
4
"bufio"
5
5
"bytes"
6
+ "errors"
6
7
"fmt"
7
8
"io"
8
9
"regexp"
9
10
"strings"
10
11
)
11
12
13
+ type ErrInvalidOwnerFormat struct {
14
+ Owner string
15
+ }
16
+
17
+ func (err ErrInvalidOwnerFormat ) Error () string {
18
+ return fmt .Sprintf ("invalid owner format '%s'" , err .Owner )
19
+ }
20
+
21
+ var ErrNoMatch = errors .New ("no match" )
22
+
12
23
var (
13
24
emailRegexp = regexp .MustCompile (`\A[A-Z0-9a-z\._%\+\-]+@[A-Za-z0-9\.\-]+\.[A-Za-z]{2,6}\z` )
14
25
teamRegexp = regexp .MustCompile (`\A@([a-zA-Z0-9\-]+\/[a-zA-Z0-9_\-]+)\z` )
@@ -20,8 +31,52 @@ const (
20
31
stateOwners
21
32
)
22
33
23
- // ParseFile parses a CODEOWNERS file, returning a set of rules.
24
- func ParseFile (f io.Reader ) (Ruleset , error ) {
34
+ var DefaultMatchers = []Matcher {
35
+ MatcherFunc (EmailMatcher ),
36
+ MatcherFunc (TeamMatcher ),
37
+ MatcherFunc (UsernameMatcher ),
38
+ }
39
+
40
+ type Matcher interface {
41
+ Match (s string ) (Owner , error )
42
+ }
43
+
44
+ type MatcherFunc func (s string ) (Owner , error )
45
+
46
+ func (f MatcherFunc ) Match (s string ) (Owner , error ) {
47
+ return f (s )
48
+ }
49
+
50
+ func EmailMatcher (s string ) (Owner , error ) {
51
+ match := emailRegexp .FindStringSubmatch (s )
52
+ if match == nil {
53
+ return Owner {}, ErrNoMatch
54
+ }
55
+
56
+ return Owner {Value : match [0 ], Type : EmailOwner }, nil
57
+ }
58
+
59
+ func TeamMatcher (s string ) (Owner , error ) {
60
+ match := teamRegexp .FindStringSubmatch (s )
61
+ if match == nil {
62
+ return Owner {}, ErrNoMatch
63
+ }
64
+
65
+ return Owner {Value : match [1 ], Type : TeamOwner }, nil
66
+ }
67
+
68
+ func UsernameMatcher (s string ) (Owner , error ) {
69
+ match := usernameRegexp .FindStringSubmatch (s )
70
+ if match == nil {
71
+ return Owner {}, ErrNoMatch
72
+ }
73
+
74
+ return Owner {Value : match [1 ], Type : UsernameOwner }, nil
75
+ }
76
+
77
+ // ParseFile parses a CODEOWNERS file and Matcher, returning a set of rules.
78
+ // If no Matchers are passed explicitly the DefaultMatchers are used.
79
+ func ParseFile (f io.Reader , mm ... Matcher ) (Ruleset , error ) {
25
80
rules := Ruleset {}
26
81
scanner := bufio .NewScanner (f )
27
82
lineNo := 0
@@ -34,7 +89,7 @@ func ParseFile(f io.Reader) (Ruleset, error) {
34
89
continue
35
90
}
36
91
37
- rule , err := parseRule (line )
92
+ rule , err := parseRule (line , mm )
38
93
if err != nil {
39
94
return nil , fmt .Errorf ("line %d: %w" , lineNo , err )
40
95
}
@@ -45,7 +100,7 @@ func ParseFile(f io.Reader) (Ruleset, error) {
45
100
}
46
101
47
102
// parseRule parses a single line of a CODEOWNERS file, returning a Rule struct
48
- func parseRule (ruleStr string ) (Rule , error ) {
103
+ func parseRule (ruleStr string , mm [] Matcher ) (Rule , error ) {
49
104
r := Rule {}
50
105
51
106
state := statePattern
@@ -95,9 +150,9 @@ func parseRule(ruleStr string) (Rule, error) {
95
150
// through whitespace before or after owner declarations
96
151
if buf .Len () > 0 {
97
152
ownerStr := buf .String ()
98
- owner , err := newOwner (ownerStr )
153
+ owner , err := newOwner (ownerStr , mm )
99
154
if err != nil {
100
- return r , fmt .Errorf ("%s at position %d" , err . Error () , i + 1 - len (ownerStr ))
155
+ return r , fmt .Errorf ("%w at position %d" , err , i + 1 - len (ownerStr ))
101
156
}
102
157
r .Owners = append (r .Owners , owner )
103
158
buf .Reset ()
@@ -131,7 +186,7 @@ func parseRule(ruleStr string) (Rule, error) {
131
186
// If there's an owner left in the buffer, don't leave it behind
132
187
if buf .Len () > 0 {
133
188
ownerStr := buf .String ()
134
- owner , err := newOwner (ownerStr )
189
+ owner , err := newOwner (ownerStr , mm )
135
190
if err != nil {
136
191
return r , fmt .Errorf ("%s at position %d" , err .Error (), len (ruleStr )+ 1 - len (ownerStr ))
137
192
}
@@ -143,23 +198,25 @@ func parseRule(ruleStr string) (Rule, error) {
143
198
}
144
199
145
200
// newOwner figures out which kind of owner this is and returns an Owner struct
146
- func newOwner (s string ) (Owner , error ) {
147
- match := emailRegexp .FindStringSubmatch (s )
148
- if match != nil {
149
- return Owner {Value : match [0 ], Type : EmailOwner }, nil
201
+ func newOwner (s string , mm []Matcher ) (Owner , error ) {
202
+ if len (mm ) == 0 {
203
+ mm = DefaultMatchers
150
204
}
151
205
152
- match = teamRegexp .FindStringSubmatch (s )
153
- if match != nil {
154
- return Owner {Value : match [1 ], Type : TeamOwner }, nil
155
- }
206
+ for _ , m := range mm {
207
+ o , err := m .Match (s )
208
+ if errors .Is (err , ErrNoMatch ) {
209
+ continue
210
+ } else if err != nil {
211
+ return Owner {}, err
212
+ }
156
213
157
- match = usernameRegexp .FindStringSubmatch (s )
158
- if match != nil {
159
- return Owner {Value : match [1 ], Type : UsernameOwner }, nil
214
+ return o , nil
160
215
}
161
216
162
- return Owner {}, fmt .Errorf ("invalid owner format '%s'" , s )
217
+ return Owner {}, ErrInvalidOwnerFormat {
218
+ Owner : s ,
219
+ }
163
220
}
164
221
165
222
func isWhitespace (ch rune ) bool {
0 commit comments