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 }