gitea/vendor/github.com/editorconfig/editorconfig-core-go/v2/fnmatch.go

178 lines
3.8 KiB
Go

package editorconfig
import (
"fmt"
"regexp"
"strconv"
"strings"
)
var (
// findLeftBrackets matches the opening left bracket {
findLeftBrackets = regexp.MustCompile(`(^|[^\\])\{`)
// findLeftBrackets matches the closing right bracket {
findRightBrackets = regexp.MustCompile(`(^|[^\\])\}`)
// findNumericRange matches a range of number, e.g. -2..5
findNumericRange = regexp.MustCompile(`^([+-]?\d+)\.\.([+-]?\d+)$`)
)
// FnmatchCase tests whether the name matches the given pattern case included.
func FnmatchCase(pattern, name string) (bool, error) {
p, err := translate(pattern)
if err != nil {
return false, err
}
r, err := regexp.Compile(fmt.Sprintf("^%s$", p))
if err != nil {
return false, err
}
return r.MatchString(name), nil
}
func translate(pattern string) (string, error) {
index := 0
pat := []rune(pattern)
length := len(pat)
result := strings.Builder{}
braceLevel := 0
isEscaped := false
inBrackets := false
matchesBraces := len(findLeftBrackets.FindAllString(pattern, -1)) == len(findRightBrackets.FindAllString(pattern, -1))
for index < length {
r := pat[index]
index++
if r == '*' {
p := index
if p < length && pat[p] == '*' {
result.WriteString(".*")
index++
} else {
result.WriteString("[^/]*")
}
} else if r == '/' {
p := index
if p+2 < length && pat[p] == '*' && pat[p+1] == '*' && pat[p+2] == '/' {
result.WriteString("(?:/|/.*/)")
index += 3
} else {
result.WriteRune(r)
}
} else if r == '?' {
result.WriteString("[^/]")
} else if r == '[' {
if inBrackets {
result.WriteString("\\[")
} else {
hasSlash := false
res := strings.Builder{}
p := index
for p < length {
if pat[p] == ']' && pat[p-1] != '\\' {
break
}
res.WriteRune(pat[p])
if pat[p] == '/' && pat[p-1] != '\\' {
hasSlash = true
break
}
p++
}
if hasSlash {
result.WriteString("\\[" + res.String())
index = p + 1
} else {
inBrackets = true
if index < length && pat[index] == '!' || pat[index] == '^' {
index++
result.WriteString("[^")
} else {
result.WriteRune('[')
}
}
}
} else if r == ']' {
if inBrackets && pat[index-2] == '\\' {
result.WriteString("\\]")
} else {
result.WriteRune(r)
inBrackets = false
}
} else if r == '{' {
hasComma := false
p := index
res := strings.Builder{}
for p < length {
if pat[p] == '}' && pat[p-1] != '\\' {
break
}
res.WriteRune(pat[p])
if pat[p] == ',' && pat[p-1] != '\\' {
hasComma = true
break
}
p++
}
if !hasComma && p < length {
inner := res.String()
sub := findNumericRange.FindStringSubmatch(inner)
if len(sub) == 3 {
from, _ := strconv.Atoi(sub[1])
to, _ := strconv.Atoi(sub[2])
result.WriteString("(?:")
// XXX does not scale well
for i := from; i < to; i++ {
result.WriteString(strconv.Itoa(i))
result.WriteRune('|')
}
result.WriteString(strconv.Itoa(to))
result.WriteRune(')')
} else {
r, _ := translate(inner)
result.WriteString(fmt.Sprintf("\\{%s\\}", r))
}
index = p + 1
} else if matchesBraces {
result.WriteString("(?:")
braceLevel++
} else {
result.WriteString("\\{")
}
} else if r == '}' {
if braceLevel > 0 {
if isEscaped {
result.WriteRune('}')
isEscaped = false
} else {
result.WriteRune(')')
braceLevel--
}
} else {
result.WriteString("\\}")
}
} else if r == ',' {
if braceLevel == 0 || isEscaped {
result.WriteRune(r)
} else {
result.WriteRune('|')
}
} else if r != '\\' || isEscaped {
result.WriteString(regexp.QuoteMeta(string(r)))
isEscaped = false
} else {
isEscaped = true
}
}
return result.String(), nil
}