// 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 Newtonsoft.Json.Linq; namespace Microsoft.DotNet.ProjectModel.Files { public class IncludeContext { private static readonly char[] PatternSeparator = new[] { ';' }; public IncludeContext( string sourceBasePath, string option, JObject rawObject, string[] defaultBuiltInInclude, string[] defaultBuiltInExclude) { if (sourceBasePath == null) { throw new ArgumentNullException(nameof(sourceBasePath)); } if (option == null) { throw new ArgumentNullException(nameof(option)); } if (rawObject == null) { throw new ArgumentNullException(nameof(rawObject)); } SourceBasePath = sourceBasePath; Option = option; var token = rawObject.Value(option); if (token.Type != JTokenType.Object) { IncludePatterns = CreateCollection( sourceBasePath, option, ExtractValues(token), literalPath: false); } else { IncludePatterns = CreateCollection( sourceBasePath, "include", ExtractValues(token.Value("include")), literalPath: false); ExcludePatterns = CreateCollection( sourceBasePath, "exclude", ExtractValues(token.Value("exclude")), literalPath: false); IncludeFiles = CreateCollection( sourceBasePath, "includeFiles", ExtractValues(token.Value("includeFiles")), literalPath: true); ExcludeFiles = CreateCollection( sourceBasePath, "excludeFiles", ExtractValues(token.Value("excludeFiles")), literalPath: true); var builtIns = token.Value("builtIns") as JObject; if (builtIns != null) { BuiltInsInclude = CreateCollection( sourceBasePath, "include", ExtractValues(builtIns.Value("include")), literalPath: false); BuiltInsExclude = CreateCollection( sourceBasePath, "exclude", ExtractValues(builtIns.Value("exclude")), literalPath: false); } var mappings = token.Value("mappings") as JObject; if (mappings != null) { Mappings = new Dictionary(); foreach (var map in mappings) { Mappings.Add( map.Key, new IncludeContext( sourceBasePath, map.Key, mappings, defaultBuiltInInclude: null, defaultBuiltInExclude: null)); } } } if (defaultBuiltInInclude != null && (BuiltInsInclude == null || !BuiltInsInclude.Any())) { BuiltInsInclude = defaultBuiltInInclude.ToList(); } if (defaultBuiltInExclude != null && (BuiltInsExclude == null || !BuiltInsExclude.Any())) { BuiltInsExclude = defaultBuiltInExclude.ToList(); } } public string SourceBasePath { get; } public string Option { get; } public List IncludePatterns { get; } public List ExcludePatterns { get; } public List IncludeFiles { get; } public List ExcludeFiles { get; } public List BuiltInsInclude { get; } public List BuiltInsExclude { get; } public IDictionary Mappings { get; } public override bool Equals(object obj) { var other = obj as IncludeContext; return other != null && SourceBasePath == other.SourceBasePath && Option == other.Option && EnumerableEquals(IncludePatterns, other.IncludePatterns) && EnumerableEquals(ExcludePatterns, other.ExcludePatterns) && EnumerableEquals(IncludeFiles, other.IncludeFiles) && EnumerableEquals(ExcludeFiles, other.ExcludeFiles) && EnumerableEquals(BuiltInsInclude, other.BuiltInsInclude) && EnumerableEquals(BuiltInsExclude, other.BuiltInsExclude) && EnumerableEquals(Mappings, other.Mappings); } public override int GetHashCode() { return base.GetHashCode(); } private static bool EnumerableEquals(IEnumerable left, IEnumerable right) => Enumerable.SequenceEqual(left ?? EmptyArray.Value, right ?? EmptyArray.Value); private static string[] ExtractValues(JToken token) { if (token != null) { if (token.Type == JTokenType.String) { return new string[] { token.Value() }; } else if (token.Type == JTokenType.Array) { return token.Values().ToArray(); } } return new string[0]; } internal static List CreateCollection( string projectDirectory, string propertyName, IEnumerable patternsStrings, bool literalPath) { var patterns = patternsStrings .SelectMany(patternsString => GetSourcesSplit(patternsString)) .Select(patternString => patternString.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar)); foreach (var pattern in patterns) { if (Path.IsPathRooted(pattern)) { throw new InvalidOperationException($"The '{propertyName}' property cannot be a rooted path."); } if (literalPath && pattern.Contains('*')) { throw new InvalidOperationException($"The '{propertyName}' property cannot contain wildcard characters."); } } return new List(patterns.Select(pattern => FolderToPattern(pattern, projectDirectory))); } private static IEnumerable GetSourcesSplit(string sourceDescription) { if (string.IsNullOrEmpty(sourceDescription)) { return Enumerable.Empty(); } return sourceDescription.Split(PatternSeparator, StringSplitOptions.RemoveEmptyEntries); } private static string FolderToPattern(string candidate, string projectDir) { // If it's already a pattern, no change is needed if (candidate.Contains('*')) { return candidate; } // If the given string ends with a path separator, or it is an existing directory // we convert this folder name to a pattern matching all files in the folder if (candidate.EndsWith(@"\") || candidate.EndsWith("/") || Directory.Exists(Path.Combine(projectDir, candidate))) { return Path.Combine(candidate, "**", "*"); } // Otherwise, it represents a single file return candidate; } } }