Add indicator for last matched result

This information is needed as well if we want to match
in a way that the last match has the highest priority.

We now introduce a new method `MatchesResult` to not break the previous
API.

Signed-off-by: Sascha Grunert <sgrunert@suse.com>
This commit is contained in:
Sascha Grunert 2019-10-18 09:30:47 +02:00
parent 01ea27db51
commit 5e07044cf0
No known key found for this signature in database
GPG Key ID: 8CE029DD1A866E52
2 changed files with 87 additions and 26 deletions

View File

@ -57,11 +57,11 @@ func NewPatternMatcher(patterns []string) (*PatternMatcher, error) {
return pm, nil
}
// Matches verifies the provided filepath against all patterns.
// It returns the amount of `matches` and `excludes` for the patterns on
// success, otherwise an error.
// It is not safe to be called concurrently.
func (pm *PatternMatcher) Matches(file string) (matches, excludes uint, err error) {
// Deprecated: Please use the `MatchesResult` method instead.
// Matches matches path against all the patterns. Matches is not safe to be
// called concurrently
func (pm *PatternMatcher) Matches(file string) (bool, error) {
matched := false
file = filepath.FromSlash(file)
parentPath := filepath.Dir(file)
parentPathDirs := strings.Split(parentPath, string(os.PathSeparator))
@ -75,7 +75,7 @@ func (pm *PatternMatcher) Matches(file string) (matches, excludes uint, err erro
match, err := pattern.match(file)
if err != nil {
return 0, 0, err
return false, err
}
if !match && parentPath != "." {
@ -86,35 +86,94 @@ func (pm *PatternMatcher) Matches(file string) (matches, excludes uint, err erro
}
if match {
matched = !negative
}
}
if matched {
logrus.Debugf("Skipping excluded path: %s", file)
}
return matched, nil
}
type MatchResult struct {
isMatched bool
matches, excludes uint
}
// Excludes returns true if the overall result is matched
func (m *MatchResult) IsMatched() bool {
return m.isMatched
}
// Excludes returns the amount of matches of an MatchResult
func (m *MatchResult) Matches() uint {
return m.matches
}
// Excludes returns the amount of excludes of an MatchResult
func (m *MatchResult) Excludes() uint {
return m.excludes
}
// MatchesResult verifies the provided filepath against all patterns.
// It returns the `*MatchResult` result for the patterns on success, otherwise
// an error. This method is not safe to be called concurrently.
func (pm *PatternMatcher) MatchesResult(file string) (res *MatchResult, err error) {
file = filepath.FromSlash(file)
parentPath := filepath.Dir(file)
parentPathDirs := strings.Split(parentPath, string(os.PathSeparator))
res = &MatchResult{false, 0, 0}
for _, pattern := range pm.patterns {
negative := false
if pattern.exclusion {
negative = true
}
match, err := pattern.match(file)
if err != nil {
return nil, err
}
if !match && parentPath != "." {
// Check to see if the pattern matches one of our parent dirs.
if len(pattern.dirs) <= len(parentPathDirs) {
match, _ = pattern.match(strings.Join(
parentPathDirs[:len(pattern.dirs)],
string(os.PathSeparator)),
)
}
}
if match {
res.isMatched = !negative
if negative {
excludes++
res.excludes++
} else {
matches++
res.matches++
}
}
}
if matches > 0 {
if res.matches > 0 {
logrus.Debugf("Skipping excluded path: %s", file)
}
return matches, excludes, nil
return res, nil
}
// IsMatch verifies the provided filepath against all patterns and returns true
// if it matches. A match is valid if the amount of positive matches is larger
// than the negative (excludes) ones.
// if it matches. A match is valid if the last match is a positive one.
// It returns an error on failure and is not safe to be called concurrently.
func (pm *PatternMatcher) IsMatch(file string) (matched bool, err error) {
matches, excludes, err := pm.Matches(file)
res, err := pm.MatchesResult(file)
if err != nil {
return false, err
}
if matches > excludes && matches-excludes > 0 {
matched = true
}
return matched, nil
return res.isMatched, nil
}
// Exclusions returns true if any of the patterns define exclusions

View File

@ -225,11 +225,11 @@ func TestPatternMatches(t *testing.T) {
}
}
// An exclusion followed by an inclusion should return true.
// An exclusion followed by an inclusion should return false.
func TestExclusionPatternMatchesPatternBefore(t *testing.T) {
match, _ := Matches("fileutils.go", []string{"!fileutils.go", "*.go"})
if match {
t.Errorf("failed to get true match on exclusion pattern, got %v", match)
if !match {
t.Errorf("failed to get false match on exclusion pattern, got %v", match)
}
}
@ -385,8 +385,9 @@ func TestMatches(t *testing.T) {
desc := fmt.Sprintf("pattern=%q text=%q", test.pattern, test.text)
pm, err := NewPatternMatcher([]string{test.pattern})
require.NoError(t, err, desc)
res, _, _ := pm.Matches(test.text)
assert.Equal(t, test.pass, res > 0, desc)
res, err := pm.MatchesResult(test.text)
assert.Nil(t, err)
assert.Equal(t, test.pass, res.isMatched, desc)
}
}
@ -611,11 +612,12 @@ func TestMatchesAmount(t *testing.T) {
for _, testCase := range testData {
pm, err := NewPatternMatcher(testCase.patterns)
require.NoError(t, err)
matches, excludes, err := pm.Matches(testCase.input)
res, err := pm.MatchesResult(testCase.input)
require.NoError(t, err)
desc := fmt.Sprintf("pattern=%q input=%q", testCase.patterns, testCase.input)
assert.Equal(t, testCase.excludes, excludes, desc)
assert.Equal(t, testCase.matches, matches, desc)
assert.Equal(t, testCase.excludes, res.Excludes(), desc)
assert.Equal(t, testCase.matches, res.Matches(), desc)
assert.Equal(t, testCase.isMatch, res.IsMatched(), desc)
isMatch, err := pm.IsMatch(testCase.input)
assert.Equal(t, testCase.isMatch, isMatch, desc)