@@ -3,12 +3,23 @@ package codeowners
33import (
44 "bufio"
55 "bytes"
6+ "errors"
67 "fmt"
78 "io"
89 "regexp"
910 "strings"
1011)
1112
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+
1223var (
1324 emailRegexp = regexp .MustCompile (`\A[A-Z0-9a-z\._%\+\-]+@[A-Za-z0-9\.\-]+\.[A-Za-z]{2,6}\z` )
1425 teamRegexp = regexp .MustCompile (`\A@([a-zA-Z0-9\-]+\/[a-zA-Z0-9_\-]+)\z` )
@@ -20,8 +31,52 @@ const (
2031 stateOwners
2132)
2233
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 ) {
2580 rules := Ruleset {}
2681 scanner := bufio .NewScanner (f )
2782 lineNo := 0
@@ -34,7 +89,7 @@ func ParseFile(f io.Reader) (Ruleset, error) {
3489 continue
3590 }
3691
37- rule , err := parseRule (line )
92+ rule , err := parseRule (line , mm )
3893 if err != nil {
3994 return nil , fmt .Errorf ("line %d: %w" , lineNo , err )
4095 }
@@ -45,7 +100,7 @@ func ParseFile(f io.Reader) (Ruleset, error) {
45100}
46101
47102// 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 ) {
49104 r := Rule {}
50105
51106 state := statePattern
@@ -95,9 +150,9 @@ func parseRule(ruleStr string) (Rule, error) {
95150 // through whitespace before or after owner declarations
96151 if buf .Len () > 0 {
97152 ownerStr := buf .String ()
98- owner , err := newOwner (ownerStr )
153+ owner , err := newOwner (ownerStr , mm )
99154 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 ))
101156 }
102157 r .Owners = append (r .Owners , owner )
103158 buf .Reset ()
@@ -131,7 +186,7 @@ func parseRule(ruleStr string) (Rule, error) {
131186 // If there's an owner left in the buffer, don't leave it behind
132187 if buf .Len () > 0 {
133188 ownerStr := buf .String ()
134- owner , err := newOwner (ownerStr )
189+ owner , err := newOwner (ownerStr , mm )
135190 if err != nil {
136191 return r , fmt .Errorf ("%s at position %d" , err .Error (), len (ruleStr )+ 1 - len (ownerStr ))
137192 }
@@ -143,23 +198,25 @@ func parseRule(ruleStr string) (Rule, error) {
143198}
144199
145200// 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
150204 }
151205
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+ }
156213
157- match = usernameRegexp .FindStringSubmatch (s )
158- if match != nil {
159- return Owner {Value : match [1 ], Type : UsernameOwner }, nil
214+ return o , nil
160215 }
161216
162- return Owner {}, fmt .Errorf ("invalid owner format '%s'" , s )
217+ return Owner {}, ErrInvalidOwnerFormat {
218+ Owner : s ,
219+ }
163220}
164221
165222func isWhitespace (ch rune ) bool {
0 commit comments