diff --git a/Mono.Addins.Setup/Mono.Addins.Setup/SetupService.cs b/Mono.Addins.Setup/Mono.Addins.Setup/SetupService.cs
index 3c81f8c3..6b0e7858 100644
--- a/Mono.Addins.Setup/Mono.Addins.Setup/SetupService.cs
+++ b/Mono.Addins.Setup/Mono.Addins.Setup/SetupService.cs
@@ -361,6 +361,8 @@ string BuildPackageInternal (IProgressStatus monitor, string targetDirectory, st
if (targetDirectory == null)
targetDirectory = basePath;
+ conf.SetBasePath (basePath);
+
// Generate the file name
string name;
diff --git a/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/FilePatternMatch.cs b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/FilePatternMatch.cs
new file mode 100644
index 00000000..595cd619
--- /dev/null
+++ b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/FilePatternMatch.cs
@@ -0,0 +1,78 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.Extensions.Internal;
+
+namespace Microsoft.Extensions.FileSystemGlobbing
+{
+ ///
+ /// Represents a file that was matched by searching using a globbing pattern
+ ///
+ struct FilePatternMatch : IEquatable
+ {
+ ///
+ /// The path to the file matched
+ ///
+ ///
+ /// If the matcher searched for "**/*.cs" using "src/Project" as the directory base and the pattern matcher found
+ /// "src/Project/Interfaces/IFile.cs", then Stem = "Interfaces/IFile.cs" and Path = "src/Project/Interfaces/IFile.cs".
+ ///
+ public string Path { get; }
+
+ ///
+ /// The subpath to the matched file under the base directory searched
+ ///
+ ///
+ /// If the matcher searched for "**/*.cs" using "src/Project" as the directory base and the pattern matcher found
+ /// "src/Project/Interfaces/IFile.cs",
+ /// then Stem = "Interfaces/IFile.cs" and Path = "src/Project/Interfaces/IFile.cs".
+ ///
+ public string Stem { get; }
+
+ ///
+ /// Initializes new instance of
+ ///
+ /// The path to the matched file
+ /// The stem
+ public FilePatternMatch(string path, string stem)
+ {
+ Path = path;
+ Stem = stem;
+ }
+
+ ///
+ /// Determines if the specified match is equivalent to the current match using a case-insensitive comparison.
+ ///
+ /// The other match to be compared
+ /// True if and are equal using case-insensitive comparison
+ public bool Equals(FilePatternMatch other)
+ {
+ return string.Equals(other.Path, Path, StringComparison.OrdinalIgnoreCase) &&
+ string.Equals(other.Stem, Stem, StringComparison.OrdinalIgnoreCase);
+ }
+
+ ///
+ /// Determines if the specified object is equivalent to the current match using a case-insensitive comparison.
+ ///
+ /// The object to be compared
+ /// True when
+ public override bool Equals(object obj)
+ {
+ return Equals((FilePatternMatch) obj);
+ }
+
+ ///
+ /// Gets a hash for the file pattern match.
+ ///
+ /// Some number
+ public override int GetHashCode()
+ {
+ var hashCodeCombiner = HashCodeCombiner.Start();
+ hashCodeCombiner.Add(Path, StringComparer.OrdinalIgnoreCase);
+ hashCodeCombiner.Add(Stem, StringComparer.OrdinalIgnoreCase);
+
+ return hashCodeCombiner;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/HashCodeCombiner.cs b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/HashCodeCombiner.cs
new file mode 100644
index 00000000..477da52f
--- /dev/null
+++ b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/HashCodeCombiner.cs
@@ -0,0 +1,84 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+
+namespace Microsoft.Extensions.Internal
+{
+ struct HashCodeCombiner
+ {
+ private long _combinedHash64;
+
+ public int CombinedHash
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get { return _combinedHash64.GetHashCode(); }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private HashCodeCombiner(long seed)
+ {
+ _combinedHash64 = seed;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Add(IEnumerable e)
+ {
+ if (e == null)
+ {
+ Add(0);
+ }
+ else
+ {
+ var count = 0;
+ foreach (object o in e)
+ {
+ Add(o);
+ count++;
+ }
+ Add(count);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator int(HashCodeCombiner self)
+ {
+ return self.CombinedHash;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Add(int i)
+ {
+ _combinedHash64 = ((_combinedHash64 << 5) + _combinedHash64) ^ i;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Add(string s)
+ {
+ var hashCode = (s != null) ? s.GetHashCode() : 0;
+ Add(hashCode);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Add(object o)
+ {
+ var hashCode = (o != null) ? o.GetHashCode() : 0;
+ Add(hashCode);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Add(TValue value, IEqualityComparer comparer)
+ {
+ var hashCode = value != null ? comparer.GetHashCode(value) : 0;
+ Add(hashCode);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static HashCodeCombiner Start()
+ {
+ return new HashCodeCombiner(0x1505L);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/ILinearPattern.cs b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/ILinearPattern.cs
new file mode 100644
index 00000000..a110b1e5
--- /dev/null
+++ b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/ILinearPattern.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+
+namespace Microsoft.Extensions.FileSystemGlobbing.Internal
+{
+ interface ILinearPattern : IPattern
+ {
+ IList Segments { get; }
+ }
+}
\ No newline at end of file
diff --git a/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/IPathSegment.cs b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/IPathSegment.cs
new file mode 100644
index 00000000..f80a5b4b
--- /dev/null
+++ b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/IPathSegment.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.Extensions.FileSystemGlobbing.Internal
+{
+ interface IPathSegment
+ {
+ bool CanProduceStem { get; }
+
+ bool Match(string value);
+ }
+}
\ No newline at end of file
diff --git a/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/IPattern.cs b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/IPattern.cs
new file mode 100644
index 00000000..fb8b2878
--- /dev/null
+++ b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/IPattern.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.Extensions.FileSystemGlobbing.Internal
+{
+ interface IPattern
+ {
+ IPatternContext CreatePatternContextForInclude();
+
+ IPatternContext CreatePatternContextForExclude();
+ }
+}
\ No newline at end of file
diff --git a/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/IPatternContext.cs b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/IPatternContext.cs
new file mode 100644
index 00000000..8268bd1c
--- /dev/null
+++ b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/IPatternContext.cs
@@ -0,0 +1,22 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using Microsoft.Extensions.FileSystemGlobbing;
+
+namespace Microsoft.Extensions.FileSystemGlobbing.Internal
+{
+ interface IPatternContext
+ {
+ void Declare(Action onDeclare);
+
+ bool Test(DirectoryInfo directory);
+
+ PatternTestResult Test(FileInfo file);
+
+ void PushDirectory(DirectoryInfo directory);
+
+ void PopDirectory();
+ }
+}
diff --git a/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/IRaggedPattern.cs b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/IRaggedPattern.cs
new file mode 100644
index 00000000..ed443727
--- /dev/null
+++ b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/IRaggedPattern.cs
@@ -0,0 +1,18 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+
+namespace Microsoft.Extensions.FileSystemGlobbing.Internal
+{
+ interface IRaggedPattern : IPattern
+ {
+ IList Segments { get; }
+
+ IList StartsWith { get; }
+
+ IList> Contains { get; }
+
+ IList EndsWith { get; }
+ }
+}
\ No newline at end of file
diff --git a/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/MatcherContext.cs b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/MatcherContext.cs
new file mode 100644
index 00000000..6b610011
--- /dev/null
+++ b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/MatcherContext.cs
@@ -0,0 +1,251 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Extensions.FileSystemGlobbing.Internal.PathSegments;
+using Microsoft.Extensions.FileSystemGlobbing.Util;
+
+namespace Microsoft.Extensions.FileSystemGlobbing.Internal
+{
+ class MatcherContext
+ {
+ private readonly DirectoryInfo _root;
+ private readonly List _includePatternContexts;
+ private readonly List _excludePatternContexts;
+ private readonly List _files;
+
+ private readonly HashSet _declaredLiteralFolderSegmentInString;
+ private readonly HashSet _declaredLiteralFolderSegments = new HashSet();
+ private readonly HashSet _declaredLiteralFileSegments = new HashSet();
+
+ private bool _declaredParentPathSegment;
+ private bool _declaredWildcardPathSegment;
+
+ private readonly StringComparison _comparisonType;
+
+ public MatcherContext(
+ IEnumerable includePatterns,
+ IEnumerable excludePatterns,
+ DirectoryInfo directoryInfo,
+ StringComparison comparison)
+ {
+ _root = directoryInfo;
+ _files = new List();
+ _comparisonType = comparison;
+
+ _includePatternContexts = includePatterns.Select(pattern => pattern.CreatePatternContextForInclude()).ToList();
+ _excludePatternContexts = excludePatterns.Select(pattern => pattern.CreatePatternContextForExclude()).ToList();
+
+ _declaredLiteralFolderSegmentInString = new HashSet(StringComparisonHelper.GetStringComparer(comparison));
+ }
+
+ public PatternMatchingResult Execute()
+ {
+ _files.Clear();
+
+ Match(_root, parentRelativePath: null);
+
+ return new PatternMatchingResult(_files, _files.Count > 0);
+ }
+
+ private void Match(DirectoryInfo directory, string parentRelativePath)
+ {
+ // Request all the including and excluding patterns to push current directory onto their status stack.
+ PushDirectory(directory);
+ Declare();
+
+ var entities = new List();
+ if (_declaredWildcardPathSegment || _declaredLiteralFileSegments.Any())
+ {
+ entities.AddRange(directory.EnumerateFileSystemInfos());
+ }
+ else
+ {
+ var candidates = directory.EnumerateFileSystemInfos().OfType();
+ foreach (var candidate in candidates)
+ {
+ if (_declaredLiteralFolderSegmentInString.Contains(candidate.Name))
+ {
+ entities.Add(candidate);
+ }
+ }
+ }
+
+ if (_declaredParentPathSegment)
+ {
+ entities.Add(directory.Parent);
+ }
+
+ // collect files and sub directories
+ var subDirectories = new List();
+ foreach (var entity in entities)
+ {
+ var fileInfo = entity as FileInfo;
+ if (fileInfo != null)
+ {
+ var result = MatchPatternContexts(fileInfo, (pattern, file) => pattern.Test(file));
+ if (result.IsSuccessful)
+ {
+ _files.Add(new FilePatternMatch(
+ path: CombinePath(parentRelativePath, fileInfo.Name),
+ stem: result.Stem));
+ }
+
+ continue;
+ }
+
+ var directoryInfo = entity as DirectoryInfo;
+ if (directoryInfo != null)
+ {
+ if (MatchPatternContexts(directoryInfo, (pattern, dir) => pattern.Test(dir)))
+ {
+ subDirectories.Add(directoryInfo);
+ }
+
+ continue;
+ }
+ }
+
+ // Matches the sub directories recursively
+ foreach (var subDir in subDirectories)
+ {
+ var relativePath = CombinePath(parentRelativePath, subDir.Name);
+
+ Match(subDir, relativePath);
+ }
+
+ // Request all the including and excluding patterns to pop their status stack.
+ PopDirectory();
+ }
+
+ private void Declare()
+ {
+ _declaredLiteralFileSegments.Clear();
+ _declaredLiteralFolderSegments.Clear();
+ _declaredParentPathSegment = false;
+ _declaredWildcardPathSegment = false;
+
+ foreach (var include in _includePatternContexts)
+ {
+ include.Declare(DeclareInclude);
+ }
+ }
+
+ private void DeclareInclude(IPathSegment patternSegment, bool isLastSegment)
+ {
+ var literalSegment = patternSegment as LiteralPathSegment;
+ if (literalSegment != null)
+ {
+ if (isLastSegment)
+ {
+ _declaredLiteralFileSegments.Add(literalSegment);
+ }
+ else
+ {
+ _declaredLiteralFolderSegments.Add(literalSegment);
+ _declaredLiteralFolderSegmentInString.Add(literalSegment.Value);
+ }
+ }
+ else if (patternSegment is ParentPathSegment)
+ {
+ _declaredParentPathSegment = true;
+ }
+ else if (patternSegment is WildcardPathSegment)
+ {
+ _declaredWildcardPathSegment = true;
+ }
+ }
+
+ internal static string CombinePath(string left, string right)
+ {
+ if (string.IsNullOrEmpty(left))
+ {
+ return right;
+ }
+ else
+ {
+ return string.Format("{0}/{1}", left, right);
+ }
+ }
+
+ // Used to adapt Test(DirectoryInfoBase) for the below overload
+ private bool MatchPatternContexts(TFileInfoBase fileinfo, Func test)
+ {
+ return MatchPatternContexts(
+ fileinfo,
+ (ctx, file) =>
+ {
+ if (test(ctx, file))
+ {
+ return PatternTestResult.Success(stem: string.Empty);
+ }
+ else
+ {
+ return PatternTestResult.Failed;
+ }
+ }).IsSuccessful;
+ }
+
+ private PatternTestResult MatchPatternContexts(TFileInfoBase fileinfo, Func test)
+ {
+ var result = PatternTestResult.Failed;
+
+ // If the given file/directory matches any including pattern, continues to next step.
+ foreach (var context in _includePatternContexts)
+ {
+ var localResult = test(context, fileinfo);
+ if (localResult.IsSuccessful)
+ {
+ result = localResult;
+ break;
+ }
+ }
+
+ // If the given file/directory doesn't match any of the including pattern, returns false.
+ if (!result.IsSuccessful)
+ {
+ return PatternTestResult.Failed;
+ }
+
+ // If the given file/directory matches any excluding pattern, returns false.
+ foreach (var context in _excludePatternContexts)
+ {
+ if (test(context, fileinfo).IsSuccessful)
+ {
+ return PatternTestResult.Failed;
+ }
+ }
+
+ return result;
+ }
+
+ private void PopDirectory()
+ {
+ foreach (var context in _excludePatternContexts)
+ {
+ context.PopDirectory();
+ }
+
+ foreach (var context in _includePatternContexts)
+ {
+ context.PopDirectory();
+ }
+ }
+
+ private void PushDirectory(DirectoryInfo directory)
+ {
+ foreach (var context in _includePatternContexts)
+ {
+ context.PushDirectory(directory);
+ }
+
+ foreach (var context in _excludePatternContexts)
+ {
+ context.PushDirectory(directory);
+ }
+ }
+ }
+}
diff --git a/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PathSegments/CurrentPathSegment.cs b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PathSegments/CurrentPathSegment.cs
new file mode 100644
index 00000000..d55537de
--- /dev/null
+++ b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PathSegments/CurrentPathSegment.cs
@@ -0,0 +1,17 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.Extensions.FileSystemGlobbing.Internal.PathSegments
+{
+ class CurrentPathSegment : IPathSegment
+ {
+ public bool CanProduceStem { get { return false; } }
+
+ public bool Match(string value)
+ {
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PathSegments/LiteralPathSegment.cs b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PathSegments/LiteralPathSegment.cs
new file mode 100644
index 00000000..644abd3c
--- /dev/null
+++ b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PathSegments/LiteralPathSegment.cs
@@ -0,0 +1,48 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.Extensions.FileSystemGlobbing.Util;
+
+namespace Microsoft.Extensions.FileSystemGlobbing.Internal.PathSegments
+{
+ class LiteralPathSegment : IPathSegment
+ {
+ private readonly StringComparison _comparisonType;
+
+ public bool CanProduceStem { get { return false; } }
+
+ public LiteralPathSegment(string value, StringComparison comparisonType)
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ Value = value;
+
+ _comparisonType = comparisonType;
+ }
+
+ public string Value { get; }
+
+ public bool Match(string value)
+ {
+ return string.Equals(Value, value, _comparisonType);
+ }
+
+ public override bool Equals(object obj)
+ {
+ var other = obj as LiteralPathSegment;
+
+ return other != null &&
+ _comparisonType == other._comparisonType &&
+ string.Equals(other.Value, Value, _comparisonType);
+ }
+
+ public override int GetHashCode()
+ {
+ return StringComparisonHelper.GetStringComparer(_comparisonType).GetHashCode(Value);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PathSegments/ParentPathSegment.cs b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PathSegments/ParentPathSegment.cs
new file mode 100644
index 00000000..4f7c85b0
--- /dev/null
+++ b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PathSegments/ParentPathSegment.cs
@@ -0,0 +1,19 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.Extensions.FileSystemGlobbing.Internal.PathSegments
+{
+ class ParentPathSegment : IPathSegment
+ {
+ private static readonly string LiteralParent = "..";
+
+ public bool CanProduceStem { get { return false; } }
+
+ public bool Match(string value)
+ {
+ return string.Equals(LiteralParent, value, StringComparison.Ordinal);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PathSegments/RecursiveWildcardSegment.cs b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PathSegments/RecursiveWildcardSegment.cs
new file mode 100644
index 00000000..a8cb411f
--- /dev/null
+++ b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PathSegments/RecursiveWildcardSegment.cs
@@ -0,0 +1,17 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.Extensions.FileSystemGlobbing.Internal.PathSegments
+{
+ class RecursiveWildcardSegment : IPathSegment
+ {
+ public bool CanProduceStem { get { return true; } }
+
+ public bool Match(string value)
+ {
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PathSegments/WildcardPathSegment.cs b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PathSegments/WildcardPathSegment.cs
new file mode 100644
index 00000000..417a8474
--- /dev/null
+++ b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PathSegments/WildcardPathSegment.cs
@@ -0,0 +1,74 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.Extensions.FileSystemGlobbing.Internal.PathSegments
+{
+ class WildcardPathSegment : IPathSegment
+ {
+ // It doesn't matter which StringComparison type is used in this MatchAll segment because
+ // all comparing are skipped since there is no content in the segment.
+ public static readonly WildcardPathSegment MatchAll = new WildcardPathSegment(
+ string.Empty, new List(), string.Empty, StringComparison.OrdinalIgnoreCase);
+
+ private readonly StringComparison _comparisonType;
+
+ public WildcardPathSegment(string beginsWith, List contains, string endsWith, StringComparison comparisonType)
+ {
+ BeginsWith = beginsWith;
+ Contains = contains;
+ EndsWith = endsWith;
+ _comparisonType = comparisonType;
+ }
+
+ public bool CanProduceStem { get { return true; } }
+
+ public string BeginsWith { get; }
+
+ public List Contains { get; }
+
+ public string EndsWith { get; }
+
+ public bool Match(string value)
+ {
+ var wildcard = this;
+
+ if (value.Length < wildcard.BeginsWith.Length + wildcard.EndsWith.Length)
+ {
+ return false;
+ }
+
+ if (!value.StartsWith(wildcard.BeginsWith, _comparisonType))
+ {
+ return false;
+ }
+
+ if (!value.EndsWith(wildcard.EndsWith, _comparisonType))
+ {
+ return false;
+ }
+
+ var beginRemaining = wildcard.BeginsWith.Length;
+ var endRemaining = value.Length - wildcard.EndsWith.Length;
+ for (var containsIndex = 0; containsIndex != wildcard.Contains.Count; ++containsIndex)
+ {
+ var containsValue = wildcard.Contains[containsIndex];
+ var indexOf = value.IndexOf(
+ value: containsValue,
+ startIndex: beginRemaining,
+ count: endRemaining - beginRemaining,
+ comparisonType: _comparisonType);
+ if (indexOf == -1)
+ {
+ return false;
+ }
+
+ beginRemaining = indexOf + containsValue.Length;
+ }
+
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PatternBuilder.cs b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PatternBuilder.cs
new file mode 100644
index 00000000..067e9400
--- /dev/null
+++ b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PatternBuilder.cs
@@ -0,0 +1,272 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.Extensions.FileSystemGlobbing.Internal.PathSegments;
+using Microsoft.Extensions.FileSystemGlobbing.Internal.PatternContexts;
+
+namespace Microsoft.Extensions.FileSystemGlobbing.Internal.Patterns
+{
+ class PatternBuilder
+ {
+ private static readonly char[] _slashes = new[] { '/', '\\' };
+ private static readonly char[] _star = new[] { '*' };
+
+ public PatternBuilder()
+ {
+ ComparisonType = StringComparison.OrdinalIgnoreCase;
+ }
+
+ public PatternBuilder(StringComparison comparisonType)
+ {
+ ComparisonType = comparisonType;
+ }
+
+ public StringComparison ComparisonType { get; }
+
+ public IPattern Build(string pattern)
+ {
+ if (pattern == null)
+ {
+ throw new ArgumentNullException("pattern");
+ }
+
+ pattern = pattern.TrimStart(_slashes);
+
+ if (pattern.TrimEnd(_slashes).Length < pattern.Length)
+ {
+ // If the pattern end with a slash, it is considered as
+ // a directory.
+ pattern = pattern.TrimEnd(_slashes) + "/**";
+ }
+
+ var allSegments = new List();
+ var isParentSegmentLegal = true;
+
+ IList segmentsPatternStartsWith = null;
+ IList> segmentsPatternContains = null;
+ IList segmentsPatternEndsWith = null;
+
+ var endPattern = pattern.Length;
+ for (int scanPattern = 0; scanPattern < endPattern;)
+ {
+ var beginSegment = scanPattern;
+ var endSegment = NextIndex(pattern, _slashes, scanPattern, endPattern);
+
+ IPathSegment segment = null;
+
+ if (segment == null && endSegment - beginSegment == 3)
+ {
+ if (pattern[beginSegment] == '*' &&
+ pattern[beginSegment + 1] == '.' &&
+ pattern[beginSegment + 2] == '*')
+ {
+ // turn *.* into *
+ beginSegment += 2;
+ }
+ }
+
+ if (segment == null && endSegment - beginSegment == 2)
+ {
+ if (pattern[beginSegment] == '*' &&
+ pattern[beginSegment + 1] == '*')
+ {
+ // recognized **
+ segment = new RecursiveWildcardSegment();
+ }
+ else if (pattern[beginSegment] == '.' &&
+ pattern[beginSegment + 1] == '.')
+ {
+ // recognized ..
+
+ if (!isParentSegmentLegal)
+ {
+ throw new ArgumentException("\"..\" can be only added at the beginning of the pattern.");
+ }
+ segment = new ParentPathSegment();
+ }
+ }
+
+ if (segment == null && endSegment - beginSegment == 1)
+ {
+ if (pattern[beginSegment] == '.')
+ {
+ // recognized .
+ segment = new CurrentPathSegment();
+ }
+ }
+
+ if (segment == null && endSegment - beginSegment > 2)
+ {
+ if (pattern[beginSegment] == '*' &&
+ pattern[beginSegment + 1] == '*' &&
+ pattern[beginSegment + 2] == '.')
+ {
+ // recognize **.
+ // swallow the first *, add the recursive path segment and
+ // the remaining part will be treat as wild card in next loop.
+ segment = new RecursiveWildcardSegment();
+ endSegment = beginSegment;
+ }
+ }
+
+ if (segment == null)
+ {
+ var beginsWith = string.Empty;
+ var contains = new List();
+ var endsWith = string.Empty;
+
+ for (int scanSegment = beginSegment; scanSegment < endSegment;)
+ {
+ var beginLiteral = scanSegment;
+ var endLiteral = NextIndex(pattern, _star, scanSegment, endSegment);
+
+ if (beginLiteral == beginSegment)
+ {
+ if (endLiteral == endSegment)
+ {
+ // and the only bit
+ segment = new LiteralPathSegment(Portion(pattern, beginLiteral, endLiteral), ComparisonType);
+ }
+ else
+ {
+ // this is the first bit
+ beginsWith = Portion(pattern, beginLiteral, endLiteral);
+ }
+ }
+ else if (endLiteral == endSegment)
+ {
+ // this is the last bit
+ endsWith = Portion(pattern, beginLiteral, endLiteral);
+ }
+ else
+ {
+ if (beginLiteral != endLiteral)
+ {
+ // this is a middle bit
+ contains.Add(Portion(pattern, beginLiteral, endLiteral));
+ }
+ else
+ {
+ // note: NOOP here, adjacent *'s are collapsed when they
+ // are mixed with literal text in a path segment
+ }
+ }
+
+ scanSegment = endLiteral + 1;
+ }
+
+ if (segment == null)
+ {
+ segment = new WildcardPathSegment(beginsWith, contains, endsWith, ComparisonType);
+ }
+ }
+
+ if (!(segment is ParentPathSegment))
+ {
+ isParentSegmentLegal = false;
+ }
+
+ if (segment is CurrentPathSegment)
+ {
+ // ignore ".\"
+ }
+ else
+ {
+ if (segment is RecursiveWildcardSegment)
+ {
+ if (segmentsPatternStartsWith == null)
+ {
+ segmentsPatternStartsWith = new List(allSegments);
+ segmentsPatternEndsWith = new List();
+ segmentsPatternContains = new List>();
+ }
+ else if (segmentsPatternEndsWith.Count != 0)
+ {
+ segmentsPatternContains.Add(segmentsPatternEndsWith);
+ segmentsPatternEndsWith = new List();
+ }
+ }
+ else if (segmentsPatternEndsWith != null)
+ {
+ segmentsPatternEndsWith.Add(segment);
+ }
+
+ allSegments.Add(segment);
+ }
+
+ scanPattern = endSegment + 1;
+ }
+
+ if (segmentsPatternStartsWith == null)
+ {
+ return new LinearPattern(allSegments);
+ }
+ else
+ {
+ return new RaggedPattern(allSegments, segmentsPatternStartsWith, segmentsPatternEndsWith, segmentsPatternContains);
+ }
+ }
+
+ private static int NextIndex(string pattern, char[] anyOf, int beginIndex, int endIndex)
+ {
+ var index = pattern.IndexOfAny(anyOf, beginIndex, endIndex - beginIndex);
+ return index == -1 ? endIndex : index;
+ }
+
+ private static string Portion(string pattern, int beginIndex, int endIndex)
+ {
+ return pattern.Substring(beginIndex, endIndex - beginIndex);
+ }
+
+ private class LinearPattern : ILinearPattern
+ {
+ public LinearPattern(List allSegments)
+ {
+ Segments = allSegments;
+ }
+
+ public IList Segments { get; }
+
+ public IPatternContext CreatePatternContextForInclude()
+ {
+ return new PatternContextLinearInclude(this);
+ }
+
+ public IPatternContext CreatePatternContextForExclude()
+ {
+ return new PatternContextLinearExclude(this);
+ }
+ }
+
+ private class RaggedPattern : IRaggedPattern
+ {
+ public RaggedPattern(List allSegments, IList segmentsPatternStartsWith, IList segmentsPatternEndsWith, IList> segmentsPatternContains)
+ {
+ Segments = allSegments;
+ StartsWith = segmentsPatternStartsWith;
+ Contains = segmentsPatternContains;
+ EndsWith = segmentsPatternEndsWith;
+ }
+
+ public IList> Contains { get; }
+
+ public IList EndsWith { get; }
+
+ public IList Segments { get; }
+
+ public IList StartsWith { get; }
+
+ public IPatternContext CreatePatternContextForInclude()
+ {
+ return new PatternContextRaggedInclude(this);
+ }
+
+ public IPatternContext CreatePatternContextForExclude()
+ {
+ return new PatternContextRaggedExclude(this);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PatternContexts/PatternContext.cs b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PatternContexts/PatternContext.cs
new file mode 100644
index 00000000..702fec30
--- /dev/null
+++ b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PatternContexts/PatternContext.cs
@@ -0,0 +1,39 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Collections.Generic;
+
+namespace Microsoft.Extensions.FileSystemGlobbing.Internal.PatternContexts
+{
+ abstract class PatternContext : IPatternContext
+ {
+ private Stack _stack = new Stack();
+ protected TFrame Frame;
+
+ public virtual void Declare(Action declare) { }
+
+ public abstract PatternTestResult Test(FileInfo file);
+
+ public abstract bool Test(DirectoryInfo directory);
+
+ public abstract void PushDirectory(DirectoryInfo directory);
+
+ public virtual void PopDirectory()
+ {
+ Frame = _stack.Pop();
+ }
+
+ protected void PushDataFrame(TFrame frame)
+ {
+ _stack.Push(Frame);
+ Frame = frame;
+ }
+
+ protected bool IsStackEmpty()
+ {
+ return _stack.Count == 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PatternContexts/PatternContextLinear.cs b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PatternContexts/PatternContextLinear.cs
new file mode 100644
index 00000000..a3c77810
--- /dev/null
+++ b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PatternContexts/PatternContextLinear.cs
@@ -0,0 +1,105 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Collections.Generic;
+
+namespace Microsoft.Extensions.FileSystemGlobbing.Internal.PatternContexts
+{
+ abstract class PatternContextLinear
+ : PatternContext
+ {
+ public PatternContextLinear(ILinearPattern pattern)
+ {
+ Pattern = pattern;
+ }
+
+ public override PatternTestResult Test(FileInfo file)
+ {
+ if (IsStackEmpty())
+ {
+ throw new InvalidOperationException("Can't test file before entering a directory.");
+ }
+
+ if(!Frame.IsNotApplicable && IsLastSegment() && TestMatchingSegment(file.Name))
+ {
+ return PatternTestResult.Success(CalculateStem(file));
+ }
+
+ return PatternTestResult.Failed;
+ }
+
+ public override void PushDirectory(DirectoryInfo directory)
+ {
+ // copy the current frame
+ var frame = Frame;
+
+ if (IsStackEmpty() || Frame.IsNotApplicable)
+ {
+ // when the stack is being initialized
+ // or no change is required.
+ }
+ else if (!TestMatchingSegment(directory.Name))
+ {
+ // nothing down this path is affected by this pattern
+ frame.IsNotApplicable = true;
+ }
+ else
+ {
+ // Determine this frame's contribution to the stem (if any)
+ var segment = Pattern.Segments[Frame.SegmentIndex];
+ if (frame.InStem || segment.CanProduceStem)
+ {
+ frame.InStem = true;
+ frame.StemItems.Add(directory.Name);
+ }
+
+ // directory matches segment, advance position in pattern
+ frame.SegmentIndex = frame.SegmentIndex + 1;
+ }
+
+ PushDataFrame(frame);
+ }
+
+ public struct FrameData
+ {
+ public bool IsNotApplicable;
+ public int SegmentIndex;
+ public bool InStem;
+ private IList _stemItems;
+
+ public IList StemItems
+ {
+ get { return _stemItems ?? (_stemItems = new List()); }
+ }
+
+ public string Stem
+ {
+ get { return _stemItems == null ? null : string.Join("/", _stemItems); }
+ }
+ }
+
+ protected ILinearPattern Pattern { get; }
+
+ protected bool IsLastSegment()
+ {
+ return Frame.SegmentIndex == Pattern.Segments.Count - 1;
+ }
+
+ protected bool TestMatchingSegment(string value)
+ {
+ if (Frame.SegmentIndex >= Pattern.Segments.Count)
+ {
+ return false;
+ }
+
+ return Pattern.Segments[Frame.SegmentIndex].Match(value);
+ }
+
+ protected string CalculateStem(FileInfo matchedFile)
+ {
+ return MatcherContext.CombinePath(Frame.Stem, matchedFile.Name);
+ }
+ }
+}
diff --git a/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PatternContexts/PatternContextLinearExclude.cs b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PatternContexts/PatternContextLinearExclude.cs
new file mode 100644
index 00000000..7c58403a
--- /dev/null
+++ b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PatternContexts/PatternContextLinearExclude.cs
@@ -0,0 +1,31 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+
+namespace Microsoft.Extensions.FileSystemGlobbing.Internal.PatternContexts
+{
+ class PatternContextLinearExclude : PatternContextLinear
+ {
+ public PatternContextLinearExclude(ILinearPattern pattern)
+ : base(pattern)
+ {
+ }
+
+ public override bool Test(DirectoryInfo directory)
+ {
+ if (IsStackEmpty())
+ {
+ throw new InvalidOperationException("Can't test directory before entering a directory.");
+ }
+
+ if (Frame.IsNotApplicable)
+ {
+ return false;
+ }
+
+ return IsLastSegment() && TestMatchingSegment(directory.Name);
+ }
+ }
+}
diff --git a/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PatternContexts/PatternContextLinearInclude.cs b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PatternContexts/PatternContextLinearInclude.cs
new file mode 100644
index 00000000..cde80265
--- /dev/null
+++ b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PatternContexts/PatternContextLinearInclude.cs
@@ -0,0 +1,49 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+
+namespace Microsoft.Extensions.FileSystemGlobbing.Internal.PatternContexts
+{
+ class PatternContextLinearInclude : PatternContextLinear
+ {
+ public PatternContextLinearInclude(ILinearPattern pattern)
+ : base(pattern)
+ {
+ }
+
+ public override void Declare(Action onDeclare)
+ {
+ if (IsStackEmpty())
+ {
+ throw new InvalidOperationException("Can't declare path segment before entering a directory.");
+ }
+
+ if (Frame.IsNotApplicable)
+ {
+ return;
+ }
+
+ if (Frame.SegmentIndex < Pattern.Segments.Count)
+ {
+ onDeclare(Pattern.Segments[Frame.SegmentIndex], IsLastSegment());
+ }
+ }
+
+ public override bool Test(DirectoryInfo directory)
+ {
+ if (IsStackEmpty())
+ {
+ throw new InvalidOperationException("Can't test directory before entering a directory.");
+ }
+
+ if (Frame.IsNotApplicable)
+ {
+ return false;
+ }
+
+ return !IsLastSegment() && TestMatchingSegment(directory.Name);
+ }
+ }
+}
diff --git a/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PatternContexts/PatternContextRagged.cs b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PatternContexts/PatternContextRagged.cs
new file mode 100644
index 00000000..d96e4b01
--- /dev/null
+++ b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PatternContexts/PatternContextRagged.cs
@@ -0,0 +1,197 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Collections.Generic;
+
+namespace Microsoft.Extensions.FileSystemGlobbing.Internal.PatternContexts
+{
+ abstract class PatternContextRagged : PatternContext
+ {
+ public PatternContextRagged(IRaggedPattern pattern)
+ {
+ Pattern = pattern;
+ }
+
+ public override PatternTestResult Test(FileInfo file)
+ {
+ if (IsStackEmpty())
+ {
+ throw new InvalidOperationException("Can't test file before entering a directory.");
+ }
+
+ if(!Frame.IsNotApplicable && IsEndingGroup() && TestMatchingGroup(file))
+ {
+ return PatternTestResult.Success(CalculateStem(file));
+ }
+ return PatternTestResult.Failed;
+ }
+
+ public sealed override void PushDirectory(DirectoryInfo directory)
+ {
+ // copy the current frame
+ var frame = Frame;
+
+ if (IsStackEmpty())
+ {
+ // initializing
+ frame.SegmentGroupIndex = -1;
+ frame.SegmentGroup = Pattern.StartsWith;
+ }
+ else if (Frame.IsNotApplicable)
+ {
+ // no change
+ }
+ else if (IsStartingGroup())
+ {
+ if (!TestMatchingSegment(directory.Name))
+ {
+ // nothing down this path is affected by this pattern
+ frame.IsNotApplicable = true;
+ }
+ else
+ {
+ // starting path incrementally satisfied
+ frame.SegmentIndex += 1;
+ }
+ }
+ else if (!IsStartingGroup() && directory.Name == "..")
+ {
+ // any parent path segment is not applicable in **
+ frame.IsNotApplicable = true;
+ }
+ else if (!IsStartingGroup() && !IsEndingGroup() && TestMatchingGroup(directory))
+ {
+ frame.SegmentIndex = Frame.SegmentGroup.Count;
+ frame.BacktrackAvailable = 0;
+ }
+ else
+ {
+ // increase directory backtrack length
+ frame.BacktrackAvailable += 1;
+ }
+
+ if (frame.InStem)
+ {
+ frame.StemItems.Add(directory.Name);
+ }
+
+ while (
+ frame.SegmentIndex == frame.SegmentGroup.Count &&
+ frame.SegmentGroupIndex != Pattern.Contains.Count)
+ {
+ frame.SegmentGroupIndex += 1;
+ frame.SegmentIndex = 0;
+ if (frame.SegmentGroupIndex < Pattern.Contains.Count)
+ {
+ frame.SegmentGroup = Pattern.Contains[frame.SegmentGroupIndex];
+ }
+ else
+ {
+ frame.SegmentGroup = Pattern.EndsWith;
+ }
+
+ // We now care about the stem
+ frame.InStem = true;
+ }
+
+ PushDataFrame(frame);
+ }
+
+ public override void PopDirectory()
+ {
+ base.PopDirectory();
+ if (Frame.StemItems.Count > 0)
+ {
+ Frame.StemItems.RemoveAt(Frame.StemItems.Count - 1);
+ }
+ }
+
+ public struct FrameData
+ {
+ public bool IsNotApplicable;
+
+ public int SegmentGroupIndex;
+
+ public IList SegmentGroup;
+
+ public int BacktrackAvailable;
+
+ public int SegmentIndex;
+
+ public bool InStem;
+
+ private IList _stemItems;
+
+ public IList StemItems
+ {
+ get { return _stemItems ?? (_stemItems = new List()); }
+ }
+
+ public string Stem
+ {
+ get { return _stemItems == null ? null : string.Join("/", _stemItems); }
+ }
+ }
+
+ protected IRaggedPattern Pattern { get; }
+
+ protected bool IsStartingGroup()
+ {
+ return Frame.SegmentGroupIndex == -1;
+ }
+
+ protected bool IsEndingGroup()
+ {
+ return Frame.SegmentGroupIndex == Pattern.Contains.Count;
+ }
+
+ protected bool TestMatchingSegment(string value)
+ {
+ if (Frame.SegmentIndex >= Frame.SegmentGroup.Count)
+ {
+ return false;
+ }
+ return Frame.SegmentGroup[Frame.SegmentIndex].Match(value);
+ }
+
+ protected bool TestMatchingGroup(FileSystemInfo value)
+ {
+ var groupLength = Frame.SegmentGroup.Count;
+ var backtrackLength = Frame.BacktrackAvailable + 1;
+ if (backtrackLength < groupLength)
+ {
+ return false;
+ }
+
+ var scan = value;
+ for (int index = 0; index != groupLength; ++index)
+ {
+ var segment = Frame.SegmentGroup[groupLength - index - 1];
+ if (!segment.Match(scan.Name))
+ {
+ return false;
+ }
+ scan = GetParent (scan);
+ }
+ return true;
+ }
+
+ FileSystemInfo GetParent (FileSystemInfo info)
+ {
+ switch (info) {
+ case FileInfo fi:
+ return fi.Directory;
+ case DirectoryInfo di:
+ return di.Parent;
+ }
+ return null;
+ }
+
+ protected string CalculateStem(FileInfo matchedFile)
+ {
+ return MatcherContext.CombinePath(Frame.Stem, matchedFile.Name);
+ }
+ }
+}
diff --git a/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PatternContexts/PatternContextRaggedExclude.cs b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PatternContexts/PatternContextRaggedExclude.cs
new file mode 100644
index 00000000..b656e68e
--- /dev/null
+++ b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PatternContexts/PatternContextRaggedExclude.cs
@@ -0,0 +1,45 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+
+namespace Microsoft.Extensions.FileSystemGlobbing.Internal.PatternContexts
+{
+ class PatternContextRaggedExclude : PatternContextRagged
+ {
+ public PatternContextRaggedExclude(IRaggedPattern pattern)
+ : base(pattern)
+ {
+ }
+
+ public override bool Test(DirectoryInfo directory)
+ {
+ if (IsStackEmpty())
+ {
+ throw new InvalidOperationException("Can't test directory before entering a directory.");
+ }
+
+ if (Frame.IsNotApplicable)
+ {
+ return false;
+ }
+
+ if (IsEndingGroup() && TestMatchingGroup(directory))
+ {
+ // directory excluded with file-like pattern
+ return true;
+ }
+
+ if (Pattern.EndsWith.Count == 0 &&
+ Frame.SegmentGroupIndex == Pattern.Contains.Count - 1 &&
+ TestMatchingGroup(directory))
+ {
+ // directory excluded by matching up to final '/**'
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PatternContexts/PatternContextRaggedInclude.cs b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PatternContexts/PatternContextRaggedInclude.cs
new file mode 100644
index 00000000..0dd0cab9
--- /dev/null
+++ b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PatternContexts/PatternContextRaggedInclude.cs
@@ -0,0 +1,60 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using Microsoft.Extensions.FileSystemGlobbing.Internal.PathSegments;
+
+namespace Microsoft.Extensions.FileSystemGlobbing.Internal.PatternContexts
+{
+ class PatternContextRaggedInclude : PatternContextRagged
+ {
+ public PatternContextRaggedInclude(IRaggedPattern pattern)
+ : base(pattern)
+ {
+ }
+
+ public override void Declare(Action onDeclare)
+ {
+ if (IsStackEmpty())
+ {
+ throw new InvalidOperationException("Can't declare path segment before entering a directory.");
+ }
+
+ if (Frame.IsNotApplicable)
+ {
+ return;
+ }
+
+ if (IsStartingGroup() && Frame.SegmentIndex < Frame.SegmentGroup.Count)
+ {
+ onDeclare(Frame.SegmentGroup[Frame.SegmentIndex], false);
+ }
+ else
+ {
+ onDeclare(WildcardPathSegment.MatchAll, false);
+ }
+ }
+
+ public override bool Test(DirectoryInfo directory)
+ {
+ if (IsStackEmpty())
+ {
+ throw new InvalidOperationException("Can't test directory before entering a directory.");
+ }
+
+ if (Frame.IsNotApplicable)
+ {
+ return false;
+ }
+
+ if (IsStartingGroup() && !TestMatchingSegment(directory.Name))
+ {
+ // deterministic not-included
+ return false;
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PatternTestResult.cs b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PatternTestResult.cs
new file mode 100644
index 00000000..301762ab
--- /dev/null
+++ b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Internal/PatternTestResult.cs
@@ -0,0 +1,24 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.Extensions.FileSystemGlobbing.Internal
+{
+ struct PatternTestResult
+ {
+ public static readonly PatternTestResult Failed = new PatternTestResult(isSuccessful: false, stem: null);
+
+ public bool IsSuccessful { get; }
+ public string Stem { get; }
+
+ private PatternTestResult(bool isSuccessful, string stem)
+ {
+ IsSuccessful = isSuccessful;
+ Stem = stem;
+ }
+
+ public static PatternTestResult Success(string stem)
+ {
+ return new PatternTestResult(isSuccessful: true, stem: stem);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Matcher.cs b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Matcher.cs
new file mode 100644
index 00000000..b1947166
--- /dev/null
+++ b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/Matcher.cs
@@ -0,0 +1,169 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Collections.Generic;
+using Microsoft.Extensions.FileSystemGlobbing.Internal;
+using Microsoft.Extensions.FileSystemGlobbing.Internal.Patterns;
+
+namespace Microsoft.Extensions.FileSystemGlobbing
+{
+ ///
+ /// Searches the file system for files with names that match specified patterns.
+ ///
+ ///
+ ///
+ /// Patterns specified in and can use
+ /// the following formats to match multiple files or directories.
+ ///
+ ///
+ /// -
+ ///
+ /// exact directory and file name
+ ///
+ ///
+ ///
+ /// -
+ /// "one.txt"
+ ///
+ /// -
+ /// "dir/two.txt"
+ ///
+ ///
+ ///
+ ///
+ /// -
+ ///
+ /// wildcards (*) in file and directory names that represent zero to many characters not including
+ /// directory separators characters
+ ///
+ ///
+ ///
+ /// -
+ /// "*.txt"all files with .txt file extension
+ ///
+ /// -
+ /// "*.*"all files with an extension
+ ///
+ /// -
+ /// "*"all files in top level directory
+ ///
+ /// -
+ /// ".*"filenames beginning with '.'
+ ///
+ /// - "*word* - all files with 'word' in the filename
+ /// -
+ /// "readme.*"
+ /// all files named 'readme' with any file extension
+ ///
+ /// -
+ /// "styles/*.css"
+ /// all files with extension '.css' in the directory 'styles/'
+ ///
+ /// -
+ /// "scripts/*/*"
+ /// all files in 'scripts/' or one level of subdirectory under 'scripts/'
+ ///
+ /// -
+ /// "images*/*"
+ /// all files in a folder with name that is or begins with 'images'
+ ///
+ ///
+ ///
+ ///
+ /// -
+ /// arbitrary directory depth ("/**/")
+ ///
+ ///
+ /// -
+ /// "**/*"all files in any subdirectory
+ ///
+ /// -
+ /// "dir/**/*"all files in any subdirectory under 'dir/'
+ ///
+ ///
+ ///
+ ///
+ /// -
+ /// relative paths
+ ///
+ /// '../shared/*' - all files in a diretory named 'shared' at the sibling level to the base directory given
+ /// to
+ ///
+ ///
+ ///
+ ///
+ class Matcher
+ {
+ private readonly IList _includePatterns = new List();
+ private readonly IList _excludePatterns = new List();
+ private readonly PatternBuilder _builder;
+ private readonly StringComparison _comparison;
+
+ ///
+ /// Initializes an instance of using case-insensitive matching
+ ///
+ public Matcher()
+ : this(StringComparison.OrdinalIgnoreCase)
+ {
+ }
+
+ ///
+ /// Initializes an instance of using the string comparsion method specified
+ ///
+ /// The to use
+ public Matcher(StringComparison comparisonType)
+ {
+ _comparison = comparisonType;
+ _builder = new PatternBuilder(comparisonType);
+ }
+
+ ///
+ ///
+ /// Add a file name pattern that the matcher should use to discover files. Patterns are relative to the root
+ /// directory given when is called.
+ ///
+ ///
+ /// Use the forward slash '/' to represent directory separator. Use '*' to represent wildcards in file and
+ /// directory names. Use '**' to represent arbitrary directory depth. Use '..' to represent a parent directory.
+ ///
+ ///
+ /// The globbing pattern
+ /// The matcher
+ public virtual Matcher AddInclude(string pattern)
+ {
+ _includePatterns.Add(_builder.Build(pattern));
+ return this;
+ }
+
+ ///
+ ///
+ /// Add a file name pattern for files the matcher should exclude from the results. Patterns are relative to the
+ /// root directory given when is called.
+ ///
+ ///
+ /// Use the forward slash '/' to represent directory separator. Use '*' to represent wildcards in file and
+ /// directory names. Use '**' to represent arbitrary directory depth. Use '..' to represent a parent directory.
+ ///
+ ///
+ /// The globbing pattern
+ /// The matcher
+ public virtual Matcher AddExclude(string pattern)
+ {
+ _excludePatterns.Add(_builder.Build(pattern));
+ return this;
+ }
+
+ ///
+ /// Searches the directory specified for all files matching patterns added to this instance of
+ ///
+ /// The root directory for the search
+ /// Always returns instance of , even if not files were matched
+ public virtual PatternMatchingResult Execute(DirectoryInfo directoryInfo)
+ {
+ var context = new MatcherContext(_includePatterns, _excludePatterns, directoryInfo, _comparison);
+ return context.Execute();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/PatternMatchingResult.cs b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/PatternMatchingResult.cs
new file mode 100644
index 00000000..5f463719
--- /dev/null
+++ b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/PatternMatchingResult.cs
@@ -0,0 +1,45 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Microsoft.Extensions.FileSystemGlobbing
+{
+ ///
+ /// Represents a collection of
+ ///
+ class PatternMatchingResult
+ {
+ ///
+ /// Initializes the result with a collection of
+ ///
+ /// A collection of
+ public PatternMatchingResult(IEnumerable files)
+ : this(files, hasMatches: files.Any())
+ {
+ Files = files;
+ }
+
+ ///
+ /// Initializes the result with a collection of
+ ///
+ /// A collection of
+ /// A value that determines if has any matches.
+ public PatternMatchingResult(IEnumerable files, bool hasMatches)
+ {
+ Files = files;
+ HasMatches = hasMatches;
+ }
+
+ ///
+ /// A collection of
+ ///
+ public IEnumerable Files { get; set; }
+
+ ///
+ /// Gets a value that determines if this instance of has any matches.
+ ///
+ public bool HasMatches { get; }
+ }
+}
\ No newline at end of file
diff --git a/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/StringComparisonHelper.cs b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/StringComparisonHelper.cs
new file mode 100644
index 00000000..d8ed3060
--- /dev/null
+++ b/Mono.Addins/Microsoft.Extensions.FileSystemGlobbing/StringComparisonHelper.cs
@@ -0,0 +1,31 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.Extensions.FileSystemGlobbing.Util
+{
+ static class StringComparisonHelper
+ {
+ public static StringComparer GetStringComparer(StringComparison comparisonType)
+ {
+ switch (comparisonType)
+ {
+ case StringComparison.CurrentCulture:
+ return StringComparer.CurrentCulture;
+ case StringComparison.CurrentCultureIgnoreCase:
+ return StringComparer.CurrentCultureIgnoreCase;
+ case StringComparison.Ordinal:
+ return StringComparer.Ordinal;
+ case StringComparison.OrdinalIgnoreCase:
+ return StringComparer.OrdinalIgnoreCase;
+ case StringComparison.InvariantCulture:
+ return StringComparer.InvariantCulture;
+ case StringComparison.InvariantCultureIgnoreCase:
+ return StringComparer.InvariantCultureIgnoreCase;
+ default:
+ throw new InvalidOperationException($"Unexpected StringComparison type: {comparisonType}");
+ }
+ }
+ }
+}
diff --git a/Mono.Addins/Mono.Addins.Description/AddinDescription.cs b/Mono.Addins/Mono.Addins.Description/AddinDescription.cs
index 158dbec5..e7c40af5 100644
--- a/Mono.Addins/Mono.Addins.Description/AddinDescription.cs
+++ b/Mono.Addins/Mono.Addins.Description/AddinDescription.cs
@@ -36,6 +36,7 @@
using Mono.Addins.Serialization;
using Mono.Addins.Database;
using System.Text;
+using Microsoft.Extensions.FileSystemGlobbing;
namespace Mono.Addins.Description
{
@@ -381,11 +382,11 @@ public StringCollection AllFiles {
get {
StringCollection col = new StringCollection ();
foreach (string s in MainModule.AllFiles)
- col.Add (s);
+ AddFileToCollection (col, s);
foreach (ModuleDescription mod in OptionalModules) {
foreach (string s in mod.AllFiles)
- col.Add (s);
+ AddFileToCollection (col, s);
}
return col;
}
@@ -922,6 +923,7 @@ public static AddinDescription Read (string configFile)
config = Read (s, Path.GetDirectoryName (configFile));
}
config.configFile = configFile;
+ config.SetBasePath (Path.GetDirectoryName (configFile));
return config;
}
@@ -951,6 +953,7 @@ public static AddinDescription Read (Stream stream, string basePath)
public static AddinDescription Read (TextReader reader, string basePath)
{
AddinDescription config = new AddinDescription ();
+ config.SetBasePath (basePath);
try {
config.configDoc = new XmlDocument ();
@@ -1072,6 +1075,21 @@ static bool GetBool (string s, bool defval)
else
return s == "true" || s == "yes";
}
+
+ void AddFileToCollection (StringCollection collection, string file)
+ {
+ bool isSimpleFile = file.IndexOf ('*') == -1;
+ if (isSimpleFile) {
+ collection.Add (file);
+ return;
+ }
+
+ var matcher = new Matcher (StringComparison.OrdinalIgnoreCase);
+ matcher.AddInclude (file);
+ var files = matcher.Execute (new DirectoryInfo (BasePath)).Files;
+ foreach (var globbedFile in files)
+ collection.Add (globbedFile.Path);
+ }
internal static AddinDescription ReadBinary (FileDatabase fdb, string configFile)
{
diff --git a/Mono.Addins/Mono.Addins.csproj b/Mono.Addins/Mono.Addins.csproj
index 88db0247..7df38590 100644
--- a/Mono.Addins/Mono.Addins.csproj
+++ b/Mono.Addins/Mono.Addins.csproj
@@ -157,6 +157,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Test/ImportGlobFileExtension/ImportGlobFileExtension.addin.xml b/Test/ImportGlobFileExtension/ImportGlobFileExtension.addin.xml
new file mode 100644
index 00000000..a7f3a982
--- /dev/null
+++ b/Test/ImportGlobFileExtension/ImportGlobFileExtension.addin.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Test/ImportGlobFileExtension/dir1/bar.bin b/Test/ImportGlobFileExtension/dir1/bar.bin
new file mode 100644
index 00000000..e69de29b
diff --git a/Test/ImportGlobFileExtension/dir1/foo.bin b/Test/ImportGlobFileExtension/dir1/foo.bin
new file mode 100644
index 00000000..e69de29b
diff --git a/Test/ImportGlobFileExtension/dir1/foo.txt b/Test/ImportGlobFileExtension/dir1/foo.txt
new file mode 100644
index 00000000..e69de29b
diff --git a/Test/ImportGlobFileExtension/dir2/subdir/subfile.txt b/Test/ImportGlobFileExtension/dir2/subdir/subfile.txt
new file mode 100644
index 00000000..e69de29b
diff --git a/Test/ImportGlobFileExtension/dir2/subdir/subsubdir/subsubfile.txt b/Test/ImportGlobFileExtension/dir2/subdir/subsubdir/subsubfile.txt
new file mode 100644
index 00000000..e69de29b
diff --git a/Test/ImportGlobFileExtension/file1.txt b/Test/ImportGlobFileExtension/file1.txt
new file mode 100644
index 00000000..e69de29b
diff --git a/Test/UnitTests/TestAddinDescription.cs b/Test/UnitTests/TestAddinDescription.cs
index 4da2fa98..eadb1f6b 100644
--- a/Test/UnitTests/TestAddinDescription.cs
+++ b/Test/UnitTests/TestAddinDescription.cs
@@ -186,6 +186,25 @@ public void WriteCorePropertiesAsProps ()
XmlDocument doc2 = desc.SaveToXml ();
Assert.AreEqual (Util.Infoset (doc1), Util.Infoset (doc2));
}
+
+ [Test]
+ public void FileGlobbingTest ()
+ {
+ string pathToTestFolder = Path.Combine ("..", "..", "..", "ImportGlobFileExtension");
+ AddinDescription desc = AddinDescription.Read (Path.Combine (pathToTestFolder, "ImportGlobFileExtension.addin.xml"));
+
+ var allFiles = desc.AllFiles;
+ Assert.IsNotNull (allFiles);
+ CollectionAssert.IsNotEmpty (allFiles);
+ Assert.AreEqual (5, allFiles.Count);
+ CollectionAssert.AreEquivalent (new string [] {
+ "file1.txt",
+ Path.Combine ("dir1", "bar.bin"),
+ Path.Combine ("dir1", "foo.bin"),
+ Path.Combine ("dir2", "subdir", "subfile.txt"),
+ Path.Combine ("dir2", "subdir", "subsubdir", "subsubfile.txt")
+ }, allFiles);
+ }
}
}