145 lines
4.5 KiB
Go
145 lines
4.5 KiB
Go
package tsoptions
|
|
|
|
import (
|
|
"regexp"
|
|
"strings"
|
|
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/vfs"
|
|
"github.com/dlclark/regexp2"
|
|
)
|
|
|
|
func getWildcardDirectories(include []string, exclude []string, comparePathsOptions tspath.ComparePathsOptions) map[string]bool {
|
|
// We watch a directory recursively if it contains a wildcard anywhere in a directory segment
|
|
// of the pattern:
|
|
//
|
|
// /a/b/**/d - Watch /a/b recursively to catch changes to any d in any subfolder recursively
|
|
// /a/b/*/d - Watch /a/b recursively to catch any d in any immediate subfolder, even if a new subfolder is added
|
|
// /a/b - Watch /a/b recursively to catch changes to anything in any recursive subfoler
|
|
//
|
|
// We watch a directory without recursion if it contains a wildcard in the file segment of
|
|
// the pattern:
|
|
//
|
|
// /a/b/* - Watch /a/b directly to catch any new file
|
|
// /a/b/a?z - Watch /a/b directly to catch any new file matching a?z
|
|
|
|
if len(include) == 0 {
|
|
return nil
|
|
}
|
|
|
|
rawExcludeRegex := vfs.GetRegularExpressionForWildcard(exclude, comparePathsOptions.CurrentDirectory, "exclude")
|
|
var excludeRegex *regexp.Regexp
|
|
if rawExcludeRegex != "" {
|
|
options := ""
|
|
if !comparePathsOptions.UseCaseSensitiveFileNames {
|
|
options = "(?i)"
|
|
}
|
|
excludeRegex = regexp.MustCompile(options + rawExcludeRegex)
|
|
}
|
|
|
|
wildcardDirectories := make(map[string]bool)
|
|
wildCardKeyToPath := make(map[string]string)
|
|
|
|
var recursiveKeys []string
|
|
|
|
for _, file := range include {
|
|
spec := tspath.NormalizeSlashes(tspath.CombinePaths(comparePathsOptions.CurrentDirectory, file))
|
|
if excludeRegex != nil && excludeRegex.MatchString(spec) {
|
|
continue
|
|
}
|
|
|
|
match := getWildcardDirectoryFromSpec(spec, comparePathsOptions.UseCaseSensitiveFileNames)
|
|
if match != nil {
|
|
key := match.Key
|
|
path := match.Path
|
|
recursive := match.Recursive
|
|
|
|
existingPath, existsPath := wildCardKeyToPath[key]
|
|
var existingRecursive bool
|
|
|
|
if existsPath {
|
|
existingRecursive = wildcardDirectories[existingPath]
|
|
}
|
|
|
|
if !existsPath || (!existingRecursive && recursive) {
|
|
pathToUse := path
|
|
if existsPath {
|
|
pathToUse = existingPath
|
|
}
|
|
wildcardDirectories[pathToUse] = recursive
|
|
|
|
if !existsPath {
|
|
wildCardKeyToPath[key] = path
|
|
}
|
|
|
|
if recursive {
|
|
recursiveKeys = append(recursiveKeys, key)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove any subpaths under an existing recursively watched directory
|
|
for path := range wildcardDirectories {
|
|
for _, recursiveKey := range recursiveKeys {
|
|
key := toCanonicalKey(path, comparePathsOptions.UseCaseSensitiveFileNames)
|
|
if key != recursiveKey && tspath.ContainsPath(recursiveKey, key, comparePathsOptions) {
|
|
delete(wildcardDirectories, path)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return wildcardDirectories
|
|
}
|
|
|
|
func toCanonicalKey(path string, useCaseSensitiveFileNames bool) string {
|
|
if useCaseSensitiveFileNames {
|
|
return path
|
|
}
|
|
return strings.ToLower(path)
|
|
}
|
|
|
|
// wildcardDirectoryPattern matches paths with wildcard characters
|
|
var wildcardDirectoryPattern = regexp2.MustCompile(`^[^*?]*(?=\/[^/]*[*?])`, 0)
|
|
|
|
// wildcardDirectoryMatch represents the result of a wildcard directory match
|
|
type wildcardDirectoryMatch struct {
|
|
Key string
|
|
Path string
|
|
Recursive bool
|
|
}
|
|
|
|
func getWildcardDirectoryFromSpec(spec string, useCaseSensitiveFileNames bool) *wildcardDirectoryMatch {
|
|
match, _ := wildcardDirectoryPattern.FindStringMatch(spec)
|
|
if match != nil {
|
|
// We check this with a few `Index` calls because it's more efficient than complex regex
|
|
questionWildcardIndex := strings.Index(spec, "?")
|
|
starWildcardIndex := strings.Index(spec, "*")
|
|
lastDirectorySeparatorIndex := strings.LastIndexByte(spec, tspath.DirectorySeparator)
|
|
|
|
// Determine if this should be watched recursively
|
|
recursive := (questionWildcardIndex != -1 && questionWildcardIndex < lastDirectorySeparatorIndex) ||
|
|
(starWildcardIndex != -1 && starWildcardIndex < lastDirectorySeparatorIndex)
|
|
|
|
return &wildcardDirectoryMatch{
|
|
Key: toCanonicalKey(match.String(), useCaseSensitiveFileNames),
|
|
Path: match.String(),
|
|
Recursive: recursive,
|
|
}
|
|
}
|
|
|
|
if lastSepIndex := strings.LastIndexByte(spec, tspath.DirectorySeparator); lastSepIndex != -1 {
|
|
lastSegment := spec[lastSepIndex+1:]
|
|
if vfs.IsImplicitGlob(lastSegment) {
|
|
path := tspath.RemoveTrailingDirectorySeparator(spec)
|
|
return &wildcardDirectoryMatch{
|
|
Key: toCanonicalKey(path, useCaseSensitiveFileNames),
|
|
Path: path,
|
|
Recursive: true,
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|