Add support for NuSpecs
This brings over the NuGet.Core code that existed for loading NuSpecs. I also added the File list to the manifest to support builing a package from a nuspec, and the ability to save a nuspec.
This commit is contained in:
parent
592fef980c
commit
6e1c39d764
8 changed files with 656 additions and 5 deletions
|
@ -0,0 +1,26 @@
|
|||
// 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.IO;
|
||||
|
||||
namespace NuGet
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an empty framework folder in NuGet 2.0+ packages.
|
||||
/// An empty framework folder is represented by a file named "_._".
|
||||
/// </summary>
|
||||
internal sealed class EmptyFrameworkFolderFile : PhysicalPackageFile
|
||||
{
|
||||
public EmptyFrameworkFolderFile(string directoryPathInPackage) :
|
||||
base(() => Stream.Null)
|
||||
{
|
||||
if (directoryPathInPackage == null)
|
||||
{
|
||||
throw new ArgumentNullException("directoryPathInPackage");
|
||||
}
|
||||
|
||||
TargetPath = System.IO.Path.Combine(directoryPathInPackage, Constants.PackageEmptyFileName);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,8 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
@ -24,6 +26,8 @@ namespace NuGet
|
|||
|
||||
public ManifestMetadata Metadata { get; }
|
||||
|
||||
public ICollection<ManifestFile> Files { get; } = new List<ManifestFile>();
|
||||
|
||||
/// <summary>
|
||||
/// Saves the current manifest to the specified stream.
|
||||
/// </summary>
|
||||
|
@ -53,9 +57,37 @@ namespace NuGet
|
|||
int version = Math.Max(minimumManifestVersion, ManifestVersionUtility.GetManifestVersion(Metadata));
|
||||
var schemaNamespace = (XNamespace)ManifestSchemaUtility.GetSchemaNamespace(version);
|
||||
|
||||
new XDocument(
|
||||
var document = new XDocument(
|
||||
new XElement(schemaNamespace + "package",
|
||||
Metadata.ToXElement(schemaNamespace))).Save(stream);
|
||||
Metadata.ToXElement(schemaNamespace)));
|
||||
|
||||
var fileElement = Files.ToXElement(schemaNamespace);
|
||||
|
||||
if (fileElement != null)
|
||||
{
|
||||
document.Root.Add(fileElement);
|
||||
}
|
||||
|
||||
document.Save(stream);
|
||||
}
|
||||
|
||||
public static Manifest ReadFrom(Stream stream)
|
||||
{
|
||||
XDocument document = XDocument.Load(stream);
|
||||
var schemaNamespace = GetSchemaNamespace(document);
|
||||
|
||||
return document.Root.ReadManifest(schemaNamespace);
|
||||
}
|
||||
|
||||
private static string GetSchemaNamespace(XDocument document)
|
||||
{
|
||||
string schemaNamespace = ManifestSchemaUtility.SchemaVersionV1;
|
||||
var rootNameSpace = document.Root.Name.Namespace;
|
||||
if (rootNameSpace != null && !String.IsNullOrEmpty(rootNameSpace.NamespaceName))
|
||||
{
|
||||
schemaNamespace = rootNameSpace.NamespaceName;
|
||||
}
|
||||
return schemaNamespace;
|
||||
}
|
||||
|
||||
public static Manifest Create(PackageBuilder copy)
|
||||
|
|
21
src/Microsoft.DotNet.Tools.Pack/NuGet/ManifestFile.cs
Normal file
21
src/Microsoft.DotNet.Tools.Pack/NuGet/ManifestFile.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
// 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.
|
||||
|
||||
namespace NuGet
|
||||
{
|
||||
public class ManifestFile
|
||||
{
|
||||
public ManifestFile(string source, string target, string exclude)
|
||||
{
|
||||
Source = source;
|
||||
Target = target;
|
||||
Exclude = exclude;
|
||||
}
|
||||
|
||||
public string Source { get; }
|
||||
|
||||
public string Target { get; }
|
||||
|
||||
public string Exclude { get; }
|
||||
}
|
||||
}
|
|
@ -61,6 +61,8 @@ namespace NuGet
|
|||
|
||||
public bool RequireLicenseAcceptance { get; set; }
|
||||
|
||||
public bool DevelopmentDependency { get; set; }
|
||||
|
||||
public string Description { get; set; }
|
||||
|
||||
public string Summary { get; set; }
|
||||
|
|
|
@ -19,6 +19,7 @@ namespace NuGet
|
|||
{
|
||||
private const string DefaultContentType = "application/octet";
|
||||
internal const string ManifestRelationType = "manifest";
|
||||
private bool _includeEmptyDirectories = false;
|
||||
|
||||
public PackageBuilder()
|
||||
{
|
||||
|
@ -264,6 +265,43 @@ namespace NuGet
|
|||
}
|
||||
}
|
||||
|
||||
public void Populate(ManifestMetadata manifestMetadata)
|
||||
{
|
||||
Id = manifestMetadata.Id;
|
||||
Version = manifestMetadata.Version;
|
||||
Title = manifestMetadata.Title;
|
||||
AppendIfNotNull(Authors, manifestMetadata.Authors);
|
||||
AppendIfNotNull(Owners, manifestMetadata.Owners);
|
||||
IconUrl = manifestMetadata.IconUrl;
|
||||
LicenseUrl = manifestMetadata.LicenseUrl;
|
||||
ProjectUrl = manifestMetadata.ProjectUrl;
|
||||
RequireLicenseAcceptance = manifestMetadata.RequireLicenseAcceptance;
|
||||
DevelopmentDependency = manifestMetadata.DevelopmentDependency;
|
||||
Description = manifestMetadata.Description;
|
||||
Summary = manifestMetadata.Summary;
|
||||
ReleaseNotes = manifestMetadata.ReleaseNotes;
|
||||
Language = manifestMetadata.Language;
|
||||
Copyright = manifestMetadata.Copyright;
|
||||
MinClientVersion = manifestMetadata.MinClientVersion;
|
||||
|
||||
if (manifestMetadata.Tags != null)
|
||||
{
|
||||
Tags.AddRange(ParseTags(manifestMetadata.Tags));
|
||||
}
|
||||
|
||||
AppendIfNotNull(DependencySets, manifestMetadata.DependencySets);
|
||||
AppendIfNotNull(FrameworkAssemblies, manifestMetadata.FrameworkAssemblies);
|
||||
AppendIfNotNull(PackageAssemblyReferences, manifestMetadata.PackageAssemblyReferences);
|
||||
}
|
||||
|
||||
public void PopulateFiles(string basePath, IEnumerable<ManifestFile> files)
|
||||
{
|
||||
foreach (var file in files)
|
||||
{
|
||||
AddFiles(basePath, file.Source, file.Target, file.Exclude);
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteManifest(ZipArchive package, int minimumManifestVersion)
|
||||
{
|
||||
string path = Id + Constants.ManifestExtension;
|
||||
|
@ -310,6 +348,39 @@ namespace NuGet
|
|||
return extensions;
|
||||
}
|
||||
|
||||
private void AddFiles(string basePath, string source, string destination, string exclude = null)
|
||||
{
|
||||
List<PhysicalPackageFile> searchFiles = PathResolver.ResolveSearchPattern(basePath, source, destination, _includeEmptyDirectories).ToList();
|
||||
|
||||
ExcludeFiles(searchFiles, basePath, exclude);
|
||||
|
||||
if (!PathResolver.IsWildcardSearch(source) && !PathResolver.IsDirectoryPath(source) && !searchFiles.Any())
|
||||
{
|
||||
// TODO: Resources
|
||||
throw new FileNotFoundException(
|
||||
String.Format(CultureInfo.CurrentCulture, "NuGetResources.PackageAuthoring_FileNotFound {0}", source));
|
||||
}
|
||||
|
||||
|
||||
Files.AddRange(searchFiles);
|
||||
}
|
||||
|
||||
private static void ExcludeFiles(List<PhysicalPackageFile> searchFiles, string basePath, string exclude)
|
||||
{
|
||||
if (String.IsNullOrEmpty(exclude))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// One or more exclusions may be specified in the file. Split it and prepend the base path to the wildcard provided.
|
||||
var exclusions = exclude.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var item in exclusions)
|
||||
{
|
||||
string wildCard = PathResolver.NormalizeWildcardForExcludedFiles(basePath, item);
|
||||
PathResolver.FilterPackageFiles(searchFiles, p => p.SourcePath, new[] { wildCard });
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreatePart(ZipArchive package, string path, Stream sourceStream)
|
||||
{
|
||||
if (PackageHelper.IsManifest(path))
|
||||
|
@ -324,6 +395,15 @@ namespace NuGet
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tags come in this format. tag1 tag2 tag3 etc..
|
||||
/// </summary>
|
||||
private static IEnumerable<string> ParseTags(string tags)
|
||||
{
|
||||
return from tag in tags.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
select tag.Trim();
|
||||
}
|
||||
|
||||
private static bool IsPrereleaseDependency(PackageDependency dependency)
|
||||
{
|
||||
return dependency.VersionRange.MinVersion?.IsPrerelease == true ||
|
||||
|
@ -340,7 +420,7 @@ namespace NuGet
|
|||
return version == null || version.Release.Length <= 20;
|
||||
}
|
||||
|
||||
private void WriteOpcManifestRelationship(ZipArchive package, string path)
|
||||
private static void WriteOpcManifestRelationship(ZipArchive package, string path)
|
||||
{
|
||||
ZipArchiveEntry relsEntry = package.CreateEntry("_rels/.rels", CompressionLevel.Optimal);
|
||||
|
||||
|
@ -374,9 +454,17 @@ namespace NuGet
|
|||
}
|
||||
|
||||
// Generate a relationship id for compatibility
|
||||
private string GenerateRelationshipId()
|
||||
private static string GenerateRelationshipId()
|
||||
{
|
||||
return "R" + Guid.NewGuid().ToString("N").Substring(0, 16);
|
||||
}
|
||||
|
||||
private static void AppendIfNotNull<T>(List<T> collection, IEnumerable<T> toAdd)
|
||||
{
|
||||
if (toAdd != null)
|
||||
{
|
||||
collection.AddRange(toAdd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ using System.Linq;
|
|||
using System.Xml.Linq;
|
||||
using NuGet.Frameworks;
|
||||
using NuGet.Packaging.Core;
|
||||
using NuGet.Versioning;
|
||||
|
||||
namespace NuGet
|
||||
{
|
||||
|
@ -21,6 +22,7 @@ namespace NuGet
|
|||
private const string FrameworkAssembly = "frameworkAssembly";
|
||||
private const string AssemblyName = "assemblyName";
|
||||
private const string Dependencies = "dependencies";
|
||||
private const string Files = "files";
|
||||
|
||||
public static XElement ToXElement(this ManifestMetadata metadata, XNamespace ns)
|
||||
{
|
||||
|
@ -34,6 +36,7 @@ namespace NuGet
|
|||
elem.Add(new XElement(ns + "version", metadata.Version.ToString()));
|
||||
AddElementIfNotNull(elem, ns, "title", metadata.Title);
|
||||
elem.Add(new XElement(ns + "requireLicenseAcceptance", metadata.RequireLicenseAcceptance));
|
||||
elem.Add(new XElement(ns + "developmentDependency", metadata.DevelopmentDependency));
|
||||
AddElementIfNotNull(elem, ns, "authors", metadata.Authors, authors => string.Join(",", authors));
|
||||
AddElementIfNotNull(elem, ns, "owners", metadata.Owners, owners => string.Join(",", owners));
|
||||
AddElementIfNotNull(elem, ns, "licenseUrl", metadata.LicenseUrl);
|
||||
|
@ -71,6 +74,78 @@ namespace NuGet
|
|||
return elem;
|
||||
}
|
||||
|
||||
public static Manifest ReadManifest(this XElement element, XNamespace ns)
|
||||
{
|
||||
if (element.Name != ns + "package")
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var metadataElement = element.Element(ns + "metadata");
|
||||
if (metadataElement == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
ManifestMetadata metadata = new ManifestMetadata();
|
||||
|
||||
metadata.MinClientVersionString = metadataElement.Attribute("minClientVersion")?.Value;
|
||||
metadata.Id = metadataElement.Element(ns + "id")?.Value;
|
||||
metadata.Version = ConvertIfNotNull(metadataElement.Element(ns + "version")?.Value, s => new NuGetVersion(s));
|
||||
metadata.Title = metadataElement.Element(ns + "title")?.Value;
|
||||
metadata.RequireLicenseAcceptance = ConvertIfNotNull(metadataElement.Element(ns + "requireLicenseAcceptance")?.Value, s => bool.Parse(s));
|
||||
metadata.DevelopmentDependency = ConvertIfNotNull(metadataElement.Element(ns + "developmentDependency")?.Value, s => bool.Parse(s));
|
||||
metadata.Authors = ConvertIfNotNull(metadataElement.Element(ns + "authors")?.Value, s => s.Split(','));
|
||||
metadata.Owners = ConvertIfNotNull(metadataElement.Element(ns + "owners")?.Value, s => s.Split(','));
|
||||
metadata.LicenseUrl = ConvertIfNotNull(metadataElement.Element(ns + "licenseUrl")?.Value, s => new Uri(s));
|
||||
metadata.ProjectUrl = ConvertIfNotNull(metadataElement.Element(ns + "projectUrl")?.Value, s => new Uri(s));
|
||||
metadata.IconUrl = ConvertIfNotNull(metadataElement.Element(ns + "iconUrl")?.Value, s => new Uri(s));
|
||||
metadata.Description = metadataElement.Element(ns + "description")?.Value;
|
||||
metadata.Summary = metadataElement.Element(ns + "summary")?.Value;
|
||||
metadata.ReleaseNotes = metadataElement.Element(ns + "releaseNotes")?.Value;
|
||||
metadata.Copyright = metadataElement.Element(ns + "copyright")?.Value;
|
||||
metadata.Language = metadataElement.Element(ns + "language")?.Value;
|
||||
metadata.Tags = metadataElement.Element(ns + "tags")?.Value;
|
||||
|
||||
metadata.DependencySets = GetItemSetsFromGroupableXElements(
|
||||
ns,
|
||||
metadataElement,
|
||||
Dependencies,
|
||||
"dependency",
|
||||
TargetFramework,
|
||||
GetPackageDependencyFromXElement,
|
||||
(tfm, deps) => new PackageDependencySet(tfm, deps));
|
||||
|
||||
metadata.PackageAssemblyReferences = GetItemSetsFromGroupableXElements(
|
||||
ns,
|
||||
metadataElement,
|
||||
References,
|
||||
Reference,
|
||||
TargetFramework,
|
||||
GetPackageReferenceFromXElement,
|
||||
(tfm, refs) => new PackageReferenceSet(tfm, refs)).ToArray();
|
||||
|
||||
metadata.FrameworkAssemblies = GetFrameworkAssembliesFromXElement(ns, metadataElement);
|
||||
|
||||
Manifest manifest = new Manifest(metadata);
|
||||
|
||||
var files = GetManifestFilesFromXElement(ns, element);
|
||||
if (files != null)
|
||||
{
|
||||
foreach(var file in files)
|
||||
{
|
||||
manifest.Files.Add(file);
|
||||
}
|
||||
}
|
||||
|
||||
return manifest;
|
||||
}
|
||||
|
||||
public static XElement ToXElement(this IEnumerable<ManifestFile> fileList, XNamespace ns)
|
||||
{
|
||||
return GetXElementFromManifestFiles(ns, fileList);
|
||||
}
|
||||
|
||||
private static XElement GetXElementFromGroupableItemSets<TSet, TItem>(
|
||||
XNamespace ns,
|
||||
IEnumerable<TSet> objectSets,
|
||||
|
@ -128,11 +203,45 @@ namespace NuGet
|
|||
return new XElement(ns + parentName, childElements.ToArray());
|
||||
}
|
||||
|
||||
private static IEnumerable<TSet> GetItemSetsFromGroupableXElements<TSet, TItem>(
|
||||
XNamespace ns,
|
||||
XElement parent,
|
||||
string rootName,
|
||||
string elementName,
|
||||
string identifierAttributeName,
|
||||
Func<XElement, TItem> getItemFromXElement,
|
||||
Func<string, IEnumerable<TItem>, TSet> getItemSet)
|
||||
{
|
||||
XElement rootElement = parent.Element(ns + rootName);
|
||||
|
||||
if (rootElement == null)
|
||||
{
|
||||
return Enumerable.Empty<TSet>();
|
||||
}
|
||||
|
||||
var groups = rootElement.Elements(ns + Group);
|
||||
|
||||
if (groups == null || !groups.Any())
|
||||
{
|
||||
// no groupable sets, all are ungroupable
|
||||
return new[] { getItemSet(null, rootElement.Elements(ns + elementName).Select(e => getItemFromXElement(e))) };
|
||||
}
|
||||
|
||||
return groups.Select(g =>
|
||||
getItemSet(g.Attribute(identifierAttributeName)?.Value,
|
||||
g.Elements(ns + elementName).Select(e => getItemFromXElement(e))));
|
||||
}
|
||||
|
||||
private static XElement GetXElementFromPackageReference(XNamespace ns, string reference)
|
||||
{
|
||||
return new XElement(ns + Reference, new XAttribute(File, reference));
|
||||
}
|
||||
|
||||
private static string GetPackageReferenceFromXElement(XElement element)
|
||||
{
|
||||
return element.Attribute(File)?.Value;
|
||||
}
|
||||
|
||||
private static XElement GetXElementFromPackageDependency(XNamespace ns, PackageDependency dependency)
|
||||
{
|
||||
return new XElement(ns + "dependency",
|
||||
|
@ -140,6 +249,12 @@ namespace NuGet
|
|||
dependency.VersionRange != null ? new XAttribute("version", dependency.VersionRange.ToString()) : null);
|
||||
}
|
||||
|
||||
private static PackageDependency GetPackageDependencyFromXElement(XElement element)
|
||||
{
|
||||
return new PackageDependency(element.Attribute("id").Value,
|
||||
ConvertIfNotNull(element.Attribute("version")?.Value, s => VersionRange.Parse(s)));
|
||||
}
|
||||
|
||||
private static XElement GetXElementFromFrameworkAssemblies(XNamespace ns, IEnumerable<FrameworkAssemblyReference> references)
|
||||
{
|
||||
if (references == null || !references.Any())
|
||||
|
@ -157,6 +272,53 @@ namespace NuGet
|
|||
null)));
|
||||
}
|
||||
|
||||
private static IEnumerable<FrameworkAssemblyReference> GetFrameworkAssembliesFromXElement(XNamespace ns, XElement parent)
|
||||
{
|
||||
var frameworkAssembliesElement = parent.Element(ns + FrameworkAssemblies);
|
||||
|
||||
if (frameworkAssembliesElement == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return frameworkAssembliesElement.Elements(ns + FrameworkAssembly).Select(e =>
|
||||
new FrameworkAssemblyReference(e.Attribute(AssemblyName).Value,
|
||||
e.Attribute("targetFramework")?.Value?.Split(',')?
|
||||
.Select(tf => NuGetFramework.Parse(tf)) ?? Enumerable.Empty<NuGetFramework>()));
|
||||
|
||||
}
|
||||
|
||||
private static XElement GetXElementFromManifestFiles(XNamespace ns, IEnumerable<ManifestFile> files)
|
||||
{
|
||||
if (files == null || !files.Any())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new XElement(ns + Files,
|
||||
files.Select(file =>
|
||||
new XElement(ns + File,
|
||||
new XAttribute("src", file.Source),
|
||||
new XAttribute("target", file.Source),
|
||||
new XAttribute("exclude", file.Exclude)
|
||||
)));
|
||||
}
|
||||
|
||||
private static IEnumerable<ManifestFile> GetManifestFilesFromXElement(XNamespace ns, XElement parent)
|
||||
{
|
||||
var filesElement = parent.Element(ns + Files);
|
||||
|
||||
if (filesElement == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return filesElement.Elements(ns + File).Select(f =>
|
||||
new ManifestFile(f.Attribute("src").Value,
|
||||
f.Attribute("target").Value,
|
||||
f.Attribute("exclude").Value));
|
||||
}
|
||||
|
||||
private static void AddElementIfNotNull<T>(XElement parent, XNamespace ns, string name, T value)
|
||||
where T : class
|
||||
{
|
||||
|
@ -178,5 +340,16 @@ namespace NuGet
|
|||
}
|
||||
}
|
||||
}
|
||||
private static TDest ConvertIfNotNull<TDest, TSource>(TSource value, Func<TSource, TDest> convert)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var converted = convert(value);
|
||||
|
||||
return converted;
|
||||
}
|
||||
|
||||
return default(TDest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,12 @@ namespace NuGet
|
|||
public class PackageReferenceSet
|
||||
{
|
||||
public PackageReferenceSet(IEnumerable<string> references)
|
||||
: this(null, references)
|
||||
: this((NuGetFramework)null, references)
|
||||
{
|
||||
}
|
||||
|
||||
public PackageReferenceSet(string targetFramework, IEnumerable<string> references)
|
||||
: this(targetFramework != null ? NuGetFramework.Parse(targetFramework) : null, references)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
304
src/Microsoft.DotNet.Tools.Pack/NuGet/PathResolver.cs
Normal file
304
src/Microsoft.DotNet.Tools.Pack/NuGet/PathResolver.cs
Normal file
|
@ -0,0 +1,304 @@
|
|||
// 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 System.Text.RegularExpressions;
|
||||
|
||||
namespace NuGet
|
||||
{
|
||||
public static class PathResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a collection of files from the source that matches the wildcard.
|
||||
/// </summary>
|
||||
/// <param name="source">The collection of files to match.</param>
|
||||
/// <param name="getPath">Function that returns the path to filter a package file </param>
|
||||
/// <param name="wildcards">The wildcards to apply to match the path with.</param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<T> GetMatches<T>(IEnumerable<T> source, Func<T, string> getPath, IEnumerable<string> wildcards)
|
||||
{
|
||||
var filters = wildcards.Select(WildcardToRegex);
|
||||
return source.Where(item =>
|
||||
{
|
||||
string path = getPath(item);
|
||||
return filters.Any(f => f.IsMatch(path));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes files from the source that match any wildcard.
|
||||
/// </summary>
|
||||
public static void FilterPackageFiles<T>(ICollection<T> source, Func<T, string> getPath, IEnumerable<string> wildcards)
|
||||
{
|
||||
var matchedFiles = new HashSet<T>(GetMatches(source, getPath, wildcards));
|
||||
var itemsToRemove = source.Where(matchedFiles.Contains).ToArray();
|
||||
foreach (var item in itemsToRemove)
|
||||
{
|
||||
source.Remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
public static string NormalizeWildcardForExcludedFiles(string basePath, string wildcard)
|
||||
{
|
||||
if (wildcard.StartsWith("**", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Allow any path to match the first '**' segment, see issue 2891 for more details.
|
||||
return wildcard;
|
||||
}
|
||||
basePath = NormalizeBasePath(basePath, ref wildcard);
|
||||
return Path.Combine(basePath, wildcard);
|
||||
}
|
||||
|
||||
private static Regex WildcardToRegex(string wildcard)
|
||||
{
|
||||
var pattern = Regex.Escape(wildcard);
|
||||
if (Path.DirectorySeparatorChar == '/')
|
||||
{
|
||||
// regex wildcard adjustments for *nix-style file systems
|
||||
pattern = pattern
|
||||
.Replace(@"\*\*/", ".*") //For recursive wildcards /**/, include the current directory.
|
||||
.Replace(@"\*\*", ".*") // For recursive wildcards that don't end in a slash e.g. **.txt would be treated as a .txt file at any depth
|
||||
.Replace(@"\*", @"[^/]*(/)?") // For non recursive searches, limit it any character that is not a directory separator
|
||||
.Replace(@"\?", "."); // ? translates to a single any character
|
||||
}
|
||||
else
|
||||
{
|
||||
// regex wildcard adjustments for Windows-style file systems
|
||||
pattern = pattern
|
||||
.Replace("/", @"\\") // On Windows, / is treated the same as \.
|
||||
.Replace(@"\*\*\\", ".*") //For recursive wildcards \**\, include the current directory.
|
||||
.Replace(@"\*\*", ".*") // For recursive wildcards that don't end in a slash e.g. **.txt would be treated as a .txt file at any depth
|
||||
.Replace(@"\*", @"[^\\]*(\\)?") // For non recursive searches, limit it any character that is not a directory separator
|
||||
.Replace(@"\?", "."); // ? translates to a single any character
|
||||
}
|
||||
|
||||
return new Regex('^' + pattern + '$', RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture);
|
||||
}
|
||||
|
||||
internal static IEnumerable<PhysicalPackageFile> ResolveSearchPattern(string basePath, string searchPath, string targetPath, bool includeEmptyDirectories)
|
||||
{
|
||||
string normalizedBasePath;
|
||||
IEnumerable<SearchPathResult> searchResults = PerformWildcardSearchInternal(basePath, searchPath, includeEmptyDirectories, out normalizedBasePath);
|
||||
|
||||
return searchResults.Select(result =>
|
||||
result.IsFile
|
||||
? new PhysicalPackageFile
|
||||
{
|
||||
SourcePath = result.Path,
|
||||
TargetPath = ResolvePackagePath(normalizedBasePath, searchPath, result.Path, targetPath)
|
||||
}
|
||||
: new EmptyFrameworkFolderFile(ResolvePackagePath(normalizedBasePath, searchPath, result.Path, targetPath))
|
||||
{
|
||||
SourcePath = result.Path
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static IEnumerable<string> PerformWildcardSearch(string basePath, string searchPath)
|
||||
{
|
||||
string normalizedBasePath;
|
||||
var searchResults = PerformWildcardSearchInternal(basePath, searchPath, includeEmptyDirectories: false, normalizedBasePath: out normalizedBasePath);
|
||||
return searchResults.Select(s => s.Path);
|
||||
}
|
||||
|
||||
private static IEnumerable<SearchPathResult> PerformWildcardSearchInternal(string basePath, string searchPath, bool includeEmptyDirectories, out string normalizedBasePath)
|
||||
{
|
||||
if (!searchPath.StartsWith(@"\\", StringComparison.OrdinalIgnoreCase)
|
||||
&& Path.DirectorySeparatorChar != '/')
|
||||
{
|
||||
//If the system's DirectorySeparatorChar is '/' we're probably dealing with Mac or *nix
|
||||
// In any case, if '/' is the separator, we don't want to trim off the first char ever
|
||||
// since it would completely change the meaning of the path
|
||||
// eg: /var/somedir/ is not at all the same as var/somedir (relative vs absolute)
|
||||
|
||||
// If we aren't dealing with network paths, trim the leading slash.
|
||||
searchPath = searchPath.TrimStart(Path.DirectorySeparatorChar);
|
||||
}
|
||||
|
||||
bool searchDirectory = false;
|
||||
|
||||
// If the searchPath ends with \ or /, we treat searchPath as a directory,
|
||||
// and will include everything under it, recursively
|
||||
if (IsDirectoryPath(searchPath))
|
||||
{
|
||||
searchPath = searchPath + "**" + Path.DirectorySeparatorChar + "*";
|
||||
searchDirectory = true;
|
||||
}
|
||||
|
||||
basePath = NormalizeBasePath(basePath, ref searchPath);
|
||||
normalizedBasePath = GetPathToEnumerateFrom(basePath, searchPath);
|
||||
|
||||
// Append the basePath to searchPattern and get the search regex. We need to do this because the search regex is matched from line start.
|
||||
Regex searchRegex = WildcardToRegex(Path.Combine(basePath, searchPath));
|
||||
|
||||
// This is a hack to prevent enumerating over the entire directory tree if the only wildcard characters are the ones in the file name.
|
||||
// If the path portion of the search path does not contain any wildcard characters only iterate over the TopDirectory.
|
||||
SearchOption searchOption = SearchOption.AllDirectories;
|
||||
// (a) Path is not recursive search
|
||||
bool isRecursiveSearch = searchPath.IndexOf("**", StringComparison.OrdinalIgnoreCase) != -1;
|
||||
// (b) Path does not have any wildcards.
|
||||
bool isWildcardPath = Path.GetDirectoryName(searchPath).Contains('*');
|
||||
if (!isRecursiveSearch && !isWildcardPath)
|
||||
{
|
||||
searchOption = SearchOption.TopDirectoryOnly;
|
||||
}
|
||||
|
||||
// Starting from the base path, enumerate over all files and match it using the wildcard expression provided by the user.
|
||||
// Note: We use Directory.GetFiles() instead of Directory.EnumerateFiles() here to support Mono
|
||||
var matchedFiles = from file in Directory.GetFiles(normalizedBasePath, "*.*", searchOption)
|
||||
where searchRegex.IsMatch(file)
|
||||
select new SearchPathResult(file, isFile: true);
|
||||
|
||||
if (!includeEmptyDirectories)
|
||||
{
|
||||
return matchedFiles;
|
||||
}
|
||||
|
||||
// retrieve empty directories
|
||||
// Note: We use Directory.GetDirectories() instead of Directory.EnumerateDirectories() here to support Mono
|
||||
var matchedDirectories = from directory in Directory.GetDirectories(normalizedBasePath, "*.*", searchOption)
|
||||
where searchRegex.IsMatch(directory) && IsEmptyDirectory(directory)
|
||||
select new SearchPathResult(directory, isFile: false);
|
||||
|
||||
if (searchDirectory && IsEmptyDirectory(normalizedBasePath))
|
||||
{
|
||||
matchedDirectories = matchedDirectories.Concat(new [] { new SearchPathResult(normalizedBasePath, isFile: false) });
|
||||
}
|
||||
|
||||
return matchedFiles.Concat(matchedDirectories);
|
||||
}
|
||||
|
||||
internal static string GetPathToEnumerateFrom(string basePath, string searchPath)
|
||||
{
|
||||
string basePathToEnumerate;
|
||||
int wildcardIndex = searchPath.IndexOf('*');
|
||||
if (wildcardIndex == -1)
|
||||
{
|
||||
// For paths without wildcard, we could either have base relative paths (such as lib\foo.dll) or paths outside the base path
|
||||
// (such as basePath: C:\packages and searchPath: D:\packages\foo.dll)
|
||||
// In this case, Path.Combine would pick up the right root to enumerate from.
|
||||
var searchRoot = Path.GetDirectoryName(searchPath);
|
||||
basePathToEnumerate = Path.Combine(basePath, searchRoot);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If not, find the first directory separator and use the path to the left of it as the base path to enumerate from.
|
||||
int directorySeparatoryIndex = searchPath.LastIndexOf(Path.DirectorySeparatorChar, wildcardIndex);
|
||||
if (directorySeparatoryIndex == -1)
|
||||
{
|
||||
// We're looking at a path like "NuGet*.dll", NuGet*\bin\release\*.dll
|
||||
// In this case, the basePath would continue to be the path to begin enumeration from.
|
||||
basePathToEnumerate = basePath;
|
||||
}
|
||||
else
|
||||
{
|
||||
string nonWildcardPortion = searchPath.Substring(0, directorySeparatoryIndex);
|
||||
basePathToEnumerate = Path.Combine(basePath, nonWildcardPortion);
|
||||
}
|
||||
}
|
||||
return basePathToEnumerate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determins the path of the file inside a package.
|
||||
/// For recursive wildcard paths, we preserve the path portion beginning with the wildcard.
|
||||
/// For non-recursive wildcard paths, we use the file name from the actual file path on disk.
|
||||
/// </summary>
|
||||
internal static string ResolvePackagePath(string searchDirectory, string searchPattern, string fullPath, string targetPath)
|
||||
{
|
||||
string packagePath;
|
||||
bool isDirectorySearch = IsDirectoryPath(searchPattern);
|
||||
bool isWildcardSearch = IsWildcardSearch(searchPattern);
|
||||
bool isRecursiveWildcardSearch = isWildcardSearch && searchPattern.IndexOf("**", StringComparison.OrdinalIgnoreCase) != -1;
|
||||
|
||||
if ((isRecursiveWildcardSearch || isDirectorySearch) && fullPath.StartsWith(searchDirectory, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// The search pattern is recursive. Preserve the non-wildcard portion of the path.
|
||||
// e.g. Search: X:\foo\**\*.cs results in SearchDirectory: X:\foo and a file path of X:\foo\bar\biz\boz.cs
|
||||
// Truncating X:\foo\ would result in the package path.
|
||||
packagePath = fullPath.Substring(searchDirectory.Length).TrimStart(Path.DirectorySeparatorChar);
|
||||
}
|
||||
else if (!isWildcardSearch && Path.GetExtension(searchPattern).Equals(Path.GetExtension(targetPath), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// If the search does not contain wild cards, and the target path shares the same extension, copy it
|
||||
// e.g. <file src="ie\css\style.css" target="Content\css\ie.css" /> --> Content\css\ie.css
|
||||
return targetPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
packagePath = Path.GetFileName(fullPath);
|
||||
}
|
||||
return Path.Combine(targetPath ?? String.Empty, packagePath);
|
||||
}
|
||||
|
||||
private static readonly string OneDotSlash = "." + Path.DirectorySeparatorChar;
|
||||
private static readonly string TwoDotSlash = ".." + Path.DirectorySeparatorChar;
|
||||
|
||||
internal static string NormalizeBasePath(string basePath, ref string searchPath)
|
||||
{
|
||||
// If no base path is provided, use the current directory.
|
||||
basePath = String.IsNullOrEmpty(basePath) ? OneDotSlash : basePath;
|
||||
|
||||
// If the search path is relative, transfer the ..\ portion to the base path.
|
||||
// This needs to be done because the base path determines the root for our enumeration.
|
||||
while (searchPath.StartsWith(TwoDotSlash, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
basePath = Path.Combine(basePath, TwoDotSlash);
|
||||
searchPath = searchPath.Substring(TwoDotSlash.Length);
|
||||
}
|
||||
|
||||
return Path.GetFullPath(basePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the path contains any wildcard characters.
|
||||
/// </summary>
|
||||
internal static bool IsWildcardSearch(string filter)
|
||||
{
|
||||
return filter.IndexOf('*') != -1;
|
||||
}
|
||||
|
||||
internal static bool IsDirectoryPath(string path)
|
||||
{
|
||||
return path != null && path.Length > 1 &&
|
||||
(path[path.Length - 1] == Path.DirectorySeparatorChar ||
|
||||
path[path.Length - 1] == Path.AltDirectorySeparatorChar);
|
||||
}
|
||||
|
||||
private static bool IsEmptyDirectory(string directory)
|
||||
{
|
||||
return !Directory.EnumerateFileSystemEntries(directory).Any();
|
||||
}
|
||||
|
||||
private struct SearchPathResult
|
||||
{
|
||||
private readonly string _path;
|
||||
private readonly bool _isFile;
|
||||
|
||||
public string Path
|
||||
{
|
||||
get
|
||||
{
|
||||
return _path;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsFile
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isFile;
|
||||
}
|
||||
}
|
||||
|
||||
public SearchPathResult(string path, bool isFile)
|
||||
{
|
||||
_path = path;
|
||||
_isFile = isFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue