// Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.Extensions.FileSystemGlobbing; using Microsoft.Extensions.JsonParser.Sources; namespace Microsoft.DotNet.ProjectModel.Files { public class PatternGroup { private readonly List _excludeGroups = new List(); private readonly Matcher _matcher = new Matcher(); internal PatternGroup(IEnumerable includePatterns) { IncludeLiterals = Enumerable.Empty(); IncludePatterns = includePatterns; ExcludePatterns = Enumerable.Empty(); _matcher.AddIncludePatterns(IncludePatterns); } internal PatternGroup(IEnumerable includePatterns, IEnumerable excludePatterns, IEnumerable includeLiterals) { IncludeLiterals = includeLiterals; IncludePatterns = includePatterns; ExcludePatterns = excludePatterns; _matcher.AddIncludePatterns(IncludePatterns); _matcher.AddExcludePatterns(ExcludePatterns); } internal static PatternGroup Build(JsonObject rawProject, string projectDirectory, string projectFilePath, string name, IEnumerable fallbackIncluding = null, IEnumerable additionalIncluding = null, IEnumerable additionalExcluding = null, bool includePatternsOnly = false, ICollection warnings = null) { string includePropertyName = name; additionalIncluding = additionalIncluding ?? Enumerable.Empty(); var includePatterns = PatternsCollectionHelper.GetPatternsCollection(rawProject, projectDirectory, projectFilePath, includePropertyName, defaultPatterns: fallbackIncluding) .Concat(additionalIncluding) .Distinct(); if (includePatternsOnly) { return new PatternGroup(includePatterns); } additionalExcluding = additionalExcluding ?? Enumerable.Empty(); var excludePatterns = PatternsCollectionHelper.GetPatternsCollection(rawProject, projectDirectory, projectFilePath, propertyName: name + "Exclude") .Concat(additionalExcluding) .Distinct(); var includeLiterals = PatternsCollectionHelper.GetPatternsCollection(rawProject, projectDirectory, projectFilePath, propertyName: name + "Files", literalPath: true) .Distinct(); return new PatternGroup(includePatterns, excludePatterns, includeLiterals); } public IEnumerable IncludeLiterals { get; } public IEnumerable IncludePatterns { get; } public IEnumerable ExcludePatterns { get; } public IEnumerable ExcludePatternsGroup { get { return _excludeGroups; } } public PatternGroup ExcludeGroup(PatternGroup group) { _excludeGroups.Add(group); return this; } public IEnumerable SearchFiles(string rootPath) { // literal included files are added at the last, but the search happens early // so as to make the process fail early in case there is missing file. fail early // helps to avoid unnecessary globing for performance optimization var literalIncludedFiles = new List(); foreach (var literalRelativePath in IncludeLiterals) { var fullPath = Path.GetFullPath(Path.Combine(rootPath, literalRelativePath)); if (!File.Exists(fullPath)) { throw new InvalidOperationException(string.Format("Can't find file {0}", literalRelativePath)); } // TODO: extract utility like NuGet.PathUtility.GetPathWithForwardSlashes() literalIncludedFiles.Add(fullPath.Replace('\\', '/')); } // globing files var globbingResults = _matcher.GetResultsInFullPath(rootPath); // if there is no results generated in globing, skip excluding other groups // for performance optimization. if (globbingResults.Any()) { foreach (var group in _excludeGroups) { globbingResults = globbingResults.Except(group.SearchFiles(rootPath)); } } return globbingResults.Concat(literalIncludedFiles).Distinct(); } public override string ToString() { return string.Format("Pattern group: Literals [{0}] Includes [{1}] Excludes [{2}]", string.Join(", ", IncludeLiterals), string.Join(", ", IncludePatterns), string.Join(", ", ExcludePatterns)); } } }