/// <summary> /// Parse the given <paramref name="fileSpec" /> into a <see cref="MSBuildGlob" /> using a given /// <paramref name="globRoot" />. /// </summary> /// <param name="globRoot"> /// The root of the glob. /// The fixed directory part of the glob and the match arguments (<see cref="IsMatch" /> and <see cref="MatchInfo" />) /// will get normalized against this root. /// If empty, the current working directory is used. /// Cannot be null, and cannot contain invalid path arguments. /// </param> /// <param name="fileSpec">The string to parse</param> /// <returns></returns> public static MSBuildGlob Parse(string globRoot, string fileSpec) { ErrorUtilities.VerifyThrowArgumentNull(globRoot, nameof(globRoot)); ErrorUtilities.VerifyThrowArgumentNull(fileSpec, nameof(fileSpec)); ErrorUtilities.VerifyThrowArgumentInvalidPath(globRoot, nameof(globRoot)); if (globRoot == string.Empty) { globRoot = Directory.GetCurrentDirectory(); } globRoot = FileUtilities.NormalizePath(globRoot).WithTrailingSlash(); var lazyState = new Lazy <GlobState>(() => { string fixedDirectoryPart = null; string wildcardDirectoryPart = null; string filenamePart = null; string matchFileExpression; bool needsRecursion; bool isLegalFileSpec; FileMatcher.GetFileSpecInfo( fileSpec, out fixedDirectoryPart, out wildcardDirectoryPart, out filenamePart, out matchFileExpression, out needsRecursion, out isLegalFileSpec, FileMatcher.s_defaultGetFileSystemEntries, (fixedDirPart, wildcardDirPart, filePart) => { var normalizedFixedPart = NormalizeTheFixedDirectoryPartAgainstTheGlobRoot(fixedDirPart, globRoot); return(Tuple.Create(normalizedFixedPart, wildcardDirPart, filePart)); }); // compile the regex since it's expected to be used multiple times var regex = isLegalFileSpec ? new Regex(matchFileExpression, FileMatcher.DefaultRegexOptions | RegexOptions.Compiled) : null; return(new GlobState(globRoot, fileSpec, isLegalFileSpec, fixedDirectoryPart, wildcardDirectoryPart, filenamePart, matchFileExpression, needsRecursion, regex)); }, true); return(new MSBuildGlob(lazyState)); }
/// <summary> /// Parse the given <paramref name="fileSpec" /> into a <see cref="MSBuildGlob" /> using a given /// <paramref name="globRoot" />. /// </summary> /// <param name="globRoot"> /// The root of the glob. /// The fixed directory part of the glob and the match arguments (<see cref="IsMatch" /> and <see cref="MatchInfo" />) /// will get normalized against this root. /// If empty, the current working directory is used. /// Cannot be null, and cannot contain invalid path arguments. /// </param> /// <param name="fileSpec">The string to parse</param> /// <returns></returns> public static MSBuildGlob Parse(string globRoot, string fileSpec) { ErrorUtilities.VerifyThrowArgumentNull(globRoot, nameof(globRoot)); ErrorUtilities.VerifyThrowArgumentNull(fileSpec, nameof(fileSpec)); ErrorUtilities.VerifyThrowArgumentInvalidPath(globRoot, nameof(globRoot)); if (globRoot == string.Empty) { globRoot = Directory.GetCurrentDirectory(); } globRoot = FileUtilities.NormalizePath(globRoot).WithTrailingSlash(); string fixedDirectoryPart = null; string wildcardDirectoryPart = null; string filenamePart = null; string matchFileExpression; bool needsRecursion; bool isLegalFileSpec; FileMatcher.GetFileSpecInfo( fileSpec, out fixedDirectoryPart, out wildcardDirectoryPart, out filenamePart, out matchFileExpression, out needsRecursion, out isLegalFileSpec, FileMatcher.s_defaultGetFileSystemEntries, (fixedDirPart, wildcardDirPart, filePart) => { var normalizedFixedPart = NormalizeTheFixedDirectoryPartAgainstTheGlobRoot(fixedDirPart, globRoot); return(Tuple.Create(normalizedFixedPart, wildcardDirPart, filePart)); }); return(new MSBuildGlob( globRoot, fileSpec, fixedDirectoryPart, wildcardDirectoryPart, filenamePart, matchFileExpression, needsRecursion, isLegalFileSpec)); }
// this method parses the glob and extracts the fixed directory part in order to normalize it and make it absolute // without this normalization step, strings pointing outside the globbing cone would still match when they shouldn't // for example, we dont want "**/*.cs" to match "../Shared/Foo.cs" private static Regex CreateRegex(string unescapedFileSpec, string currentDirectory) { Regex regex = null; string fixedDirPart = null; string wildcardDirectoryPart = null; string filenamePart = null; FileMatcher.SplitFileSpec( unescapedFileSpec, out fixedDirPart, out wildcardDirectoryPart, out filenamePart, FileMatcher.s_defaultGetFileSystemEntries); if (FileUtilities.PathIsInvalid(fixedDirPart)) { return(null); } var absoluteFixedDirPart = Path.Combine(currentDirectory, fixedDirPart); var normalizedFixedDirPart = string.IsNullOrEmpty(absoluteFixedDirPart) // currentDirectory is empty for some in-memory projects ? Directory.GetCurrentDirectory() : FileUtilities.GetFullPathNoThrow(absoluteFixedDirPart); normalizedFixedDirPart = FileUtilities.EnsureTrailingSlash(normalizedFixedDirPart); var recombinedFileSpec = string.Join("", normalizedFixedDirPart, wildcardDirectoryPart, filenamePart); bool isRecursive; bool isLegal; FileMatcher.GetFileSpecInfo( recombinedFileSpec, out regex, out isRecursive, out isLegal, FileMatcher.s_defaultGetFileSystemEntries); return(isLegal ? regex : null); }
internal static Func <string, bool> GetMatchTester(string filespec, string currentDirectory) { var unescapedSpec = EscapingUtilities.UnescapeAll(filespec); Regex regex = null; // TODO: assumption on file system case sensitivity: https://github.com/Microsoft/msbuild/issues/781 if (FilespecHasWildcards(filespec)) { Regex regexFileMatch; bool isRecursive; bool isLegal; // TODO: If creating Regex's here ends up being expensive perf-wise, consider how to avoid it in common cases FileMatcher.GetFileSpecInfo( unescapedSpec, out regexFileMatch, out isRecursive, out isLegal, FileMatcher.s_defaultGetFileSystemEntries); // If the spec is not legal, it doesn't match anything regex = isLegal ? regexFileMatch : null; } return(file => { var unescapedFile = EscapingUtilities.UnescapeAll(file); // check if there is a regex matching the file if (regex != null) { return regex.IsMatch(unescapedFile); } return FileUtilities.ComparePathsNoThrow(unescapedSpec, unescapedFile, currentDirectory); }); }
// Returns a Func that will return true IFF its argument matches any of the specified filespecs internal static Func <string, bool> GetMatchTester(IList <string> filespecs) { List <Regex> regexes = null; HashSet <string> exactmatches = null; foreach (var spec in filespecs) { if (FilespecHasWildcards(spec)) { Regex regexFileMatch; bool isRecursive; bool isLegal; // TODO: If creating Regex's here ends up being expensive perf-wise, consider how to avoid it in common cases FileMatcher.GetFileSpecInfo ( spec, out regexFileMatch, out isRecursive, out isLegal, FileMatcher.s_defaultGetFileSystemEntries ); if (isLegal) { if (regexes == null) { regexes = new List <Regex>(); } regexes.Add(regexFileMatch); } else { // If the spec is not legal, it doesn't match anything } } else { if (exactmatches == null) { // TODO: How to handle case sensitivity here? Existing behavior is to be case-insensitive, // which works for Windows but probably isn't the right thing on other OS's exactmatches = new HashSet <string>(StringComparer.OrdinalIgnoreCase); } exactmatches.Add(spec); } } return(file => { if (exactmatches != null) { if (exactmatches.Contains(file)) { return true; } } if (regexes != null) { foreach (Regex regex in regexes) { if (regex.IsMatch(file)) { return true; } } } return false; }); }