예제 #1
0
        public static HashSet <string> GetFiles(
            ResultCache cache,
            IEnumerable <string> searchablePaths,
            IEnumerable <IPattern> includePaths,
            IEnumerable <IPattern> excludePaths,
            string basePath = null)
        {
            searchablePaths = searchablePaths as string[] ?? searchablePaths.ToArray();
            includePaths    = includePaths as IPattern[] ?? includePaths.ToArray();

            if (excludePaths != null)
            {
                excludePaths = excludePaths as IPattern[] ?? excludePaths.ToArray();
            }
            else
            {
                excludePaths = new List <IPattern>();
            }

            basePath = basePath ?? "./";
            bool   returnAbsolutePaths = Path.IsPathRooted(basePath);
            string absBasePath;

            if (basePath == "./" || basePath == ".")
            {
                absBasePath = new DirectoryInfo(Directory.GetCurrentDirectory()).FullName;
            }
            else
            {
                absBasePath = new DirectoryInfo(basePath).FullName;
            }

            (int hash, bool hasResults, HashSet <string> files)cacheRequest =
                cache?.GetResultsOrReserve(
                    searchablePaths,
                    includePaths,
                    excludePaths,
                    basePath)
                ?? (0, false, null);

            if (cacheRequest.hasResults)
            {
                Log.Debug("Getting files for query hash {{{0}}} from cached results for base path '{1}':",
                          cacheRequest.hash, basePath);

                Log.IndentedCollection(cacheRequest.files, Log.Debug);
                return(cacheRequest.files);
            }

            Log.Debug("Getting files for query hash {{{0}}} using base path '{1}' and provided include/exclude paths:",
                      cacheRequest.hash, basePath);

            using (new CompositeDisposable(
                       new Log.ScopedIndent(),
                       new Log.ScopedTimer(Log.Level.Debug, "GetFiles(...)", $"{{{cacheRequest.hash,-11}}} {basePath}")))
            {
                Log.Debug("search paths:");
                Log.IndentedCollection(searchablePaths, Log.Debug);

                HashSet <string>       includeFiles;
                HashSet <GLOB>         includeGlobs;
                HashSet <RegexPattern> includeRegexes;
                HashSet <string>       excludeFiles;
                HashSet <GLOB>         excludeGlobs;
                HashSet <RegexPattern> excludeRegexes;

                (includeFiles, includeGlobs, includeRegexes) = ProcessFileValues(includePaths);
                (excludeFiles, excludeGlobs, excludeRegexes) = ProcessFileValues(excludePaths);
                var searchPathMatches = new Dictionary <string, List <PatternMatch> >();

                foreach (string currentSearchPath in searchablePaths)
                {
                    var currentMatches = new List <PatternMatch>();

                    var dir = new DirectoryInfo(new DirectoryInfo(
                                                    currentSearchPath == "./" || currentSearchPath == "."
                            ? Directory.GetCurrentDirectory()
                            : currentSearchPath)
                                                .FullName);

                    bool makeCandidatesAbsolute = Path.IsPathRooted(currentSearchPath);

                    if (!dir.Exists)
                    {
                        Log.Debug("Skipping searchable directory because it does not exist: {0}'", currentSearchPath);
                        continue;
                    }

                    string[] candidates = dir.GetFilesSafeRecursive("*")
                                          .Select(f => GetPath(makeCandidatesAbsolute, f.FullName, absBasePath))
                                          .Reverse()
                                          .ToArray();

                    DirectoryInfo[] allDirs = dir.GetDirectoriesSafeRecursive("*")
                                              .Reverse()
                                              .Concat(new[] { dir })
                                              .ToArray();

                    #region includes
                    if (includeGlobs != null && includeGlobs.Count > 0)
                    {
                        foreach (GLOB glob in includeGlobs)
                        {
                            IEnumerable <string> matchesForGlob = candidates.Where(f => glob.IsMatch(f));

                            currentMatches.AddRange(matchesForGlob.Select(
                                                        f => new PatternMatch(f, currentSearchPath, "glob \"" + glob.Pattern + "\"")));
                        }
                    }

                    if (includeRegexes != null && includeRegexes.Count > 0)
                    {
                        IEnumerable <PatternMatch> matchesForRegex = includeRegexes.SelectMany(
                            r => r.FilterMatches(candidates).Select(
                                f => new PatternMatch(f,
                                                      currentSearchPath,
                                                      r.ToString())));

                        currentMatches.AddRange(matchesForRegex);
                    }

                    var literalPatternMatches = new Dictionary <string, PatternMatch[]>();
                    if (includeFiles != null && includeFiles.Count > 0)
                    {
                        foreach (string filePattern in includeFiles.ToArray())
                        {
                            string[] matchesForFile =
                                (from dirInfo in allDirs
                                 select Path.Combine(dirInfo.FullName, filePattern)
                                 into includeFilePath
                                 where File.Exists(includeFilePath)
                                 select GetPath(makeCandidatesAbsolute, includeFilePath, absBasePath))
                                .ToArray();

                            PatternMatch[] patternMatchesForFile = matchesForFile.Select(
                                f => new PatternMatch(f, currentSearchPath, filePattern))
                                                                   .ToArray();

                            if (patternMatchesForFile.Length > 0)
                            {
                                literalPatternMatches[filePattern] = patternMatchesForFile;
                                currentMatches.AddRange(patternMatchesForFile);
                            }
                        }
                    }
                    #endregion

                    #region excludes
                    IEnumerable <PatternMatch> tempMatches = currentMatches;
                    if (excludeGlobs != null && excludeGlobs.Count > 0)
                    {
                        var excludeGlob = new CompositeGlob(excludeGlobs, null);
                        tempMatches = tempMatches.Where(m => !excludeGlob.IsMatch(m.File))
                                      .ToList(); // Helps with debugging
                    }

                    if (excludeRegexes != null && excludeRegexes.Count > 0)
                    {
                        tempMatches = tempMatches.Where(m => excludeRegexes.All(r => !r.Regex.IsMatch(m.File)))
                                      .ToList(); // Helps with debugging
                    }

                    if (excludeFiles != null && excludeFiles.Count > 0)
                    {
                        tempMatches = tempMatches.Where(m => excludeFiles.All(x => x != Path.GetFileName(m.File)))
                                      .ToList(); // Helps with debugging
                    }
                    #endregion

                    #region literal validation
                    currentMatches = tempMatches.ToList();
                    foreach (KeyValuePair <string, PatternMatch[]> kvp in literalPatternMatches)
                    {
                        PatternMatch[] patternMatchesForFile = kvp.Value;
                        if (patternMatchesForFile.Length > 1 &&
                            currentMatches.Intersect(patternMatchesForFile).Count() > 1)
                        {
                            Log.Warn("The literal pattern '{0}' matched more than one file.",
                                     kvp.Key);
                            Log.Warn("Only the first match '{0}' will be returned. See all matches below.",
                                     patternMatchesForFile.First().File);

                            int count = 0;
                            Log.IndentedCollection(patternMatchesForFile,
                                                   p => $"{p.File} (from {p.SearchPath}) {(count++ == 0 ? "*" : "")}",
                                                   Log.Warn);

                            currentMatches = currentMatches.Except(patternMatchesForFile.Skip(1)).ToList();
                        }
                    }

                    searchPathMatches[currentSearchPath] = currentMatches;
                    #endregion
                }

                #region validation
                IEnumerable <PatternMatch> allMatches   = searchPathMatches.Values.SelectMany(v => v);
                IEnumerable <PatternMatch> finalMatches = ValidateMatches(allMatches, includePaths);
                #endregion

                #region results
                HashSet <string> allMatchedFiles =
                    finalMatches.Select(m => GetPath(returnAbsolutePaths, m.File, absBasePath)).ToHashSet();

                Log.Debug("include globs:");
                Log.IndentedCollection(includeGlobs, s => s.Pattern, Log.Debug);
                Log.Debug("include regexes:");
                Log.IndentedCollection(includeRegexes, r => r.Value, Log.Debug);
                Log.Debug("include literals:");
                Log.IndentedCollection(includeFiles, Log.Debug);
                Log.Debug("exclude globs:");
                Log.IndentedCollection(excludeGlobs, s => s.Pattern, Log.Debug);
                Log.Debug("exclude regexes:");
                Log.IndentedCollection(excludeRegexes, r => r.Value, Log.Debug);
                Log.Debug("exclude literals:");
                Log.IndentedCollection(excludeFiles, Log.Debug);
                Log.Debug("matched files:");
                Log.IndentedCollection(allMatchedFiles, Log.Debug);

                cache?.CacheResults(allMatchedFiles, cacheRequest.hash);
                return(allMatchedFiles);

                #endregion
            }
        }