diff --git a/EditorConfig.sln b/EditorConfig.sln
index 7faef38..321baaa 100644
--- a/EditorConfig.sln
+++ b/EditorConfig.sln
@@ -14,12 +14,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
build\chocolatey.nuspec = build\chocolatey.nuspec
build\nuget.nuspec = build\nuget.nuspec
README.md = README.md
+ sign.snk = sign.snk
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EditorConfig.App", "src\EditorConfig.App\EditorConfig.App.csproj", "{D7CD5DEF-002E-4463-9FE8-AC6676DAA36C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EditorConfig.Tests", "src\EditorConfig.Tests\EditorConfig.Tests.csproj", "{F65280C8-E47C-48D1-B99A-33CB4C594FCB}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EditorConfig.Core.NET45", "src\EditorConfig.Core.NET45\EditorConfig.Core.NET45.csproj", "{F22050E7-E2C3-47BC-8B06-5BFE163FF045}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -38,6 +41,10 @@ Global
{F65280C8-E47C-48D1-B99A-33CB4C594FCB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F65280C8-E47C-48D1-B99A-33CB4C594FCB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F65280C8-E47C-48D1-B99A-33CB4C594FCB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F22050E7-E2C3-47BC-8B06-5BFE163FF045}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F22050E7-E2C3-47BC-8B06-5BFE163FF045}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F22050E7-E2C3-47BC-8B06-5BFE163FF045}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F22050E7-E2C3-47BC-8B06-5BFE163FF045}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/build/build.fsx b/build/build.fsx
index 46fbd75..ed7f0ac 100644
--- a/build/build.fsx
+++ b/build/build.fsx
@@ -1,5 +1,6 @@
// include Fake lib
-#r @"tools/FAKE/tools/FakeLib.dll"
+#I @"tools/FAKE/tools"
+#r "FakeLib.dll"
open Fake
open System
open SemVerHelper
diff --git a/build/nuget.nuspec b/build/nuget.nuspec
index 5c49007..932fcd1 100644
--- a/build/nuget.nuspec
+++ b/build/nuget.nuspec
@@ -13,11 +13,11 @@
https://raw.github.com/editorconfig/editorconfig-core/master/LICENSE
false
http://editorconfig.org/logo.png
-
-
-
+
+
+
diff --git a/packages/repositories.config b/packages/repositories.config
index bbcc0e0..55df448 100644
--- a/packages/repositories.config
+++ b/packages/repositories.config
@@ -1,6 +1,4 @@
-
-
\ No newline at end of file
diff --git a/src/EditorConfig.Core/sign.snk b/sign.snk
similarity index 100%
rename from src/EditorConfig.Core/sign.snk
rename to sign.snk
diff --git a/src/EditorConfig.App/EditorConfig.App.csproj b/src/EditorConfig.App/EditorConfig.App.csproj
index 0a309aa..a51b55c 100644
--- a/src/EditorConfig.App/EditorConfig.App.csproj
+++ b/src/EditorConfig.App/EditorConfig.App.csproj
@@ -33,9 +33,6 @@
4
-
- ..\..\packages\Minimatch.1.1.0.0\lib\portable-net40+sl50+win+wp80\Minimatch.dll
-
@@ -51,7 +48,6 @@
-
diff --git a/src/EditorConfig.App/packages.config b/src/EditorConfig.App/packages.config
deleted file mode 100644
index d90c6ab..0000000
--- a/src/EditorConfig.App/packages.config
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/src/EditorConfig.Core.NET45/EditorConfig.Core.NET45.csproj b/src/EditorConfig.Core.NET45/EditorConfig.Core.NET45.csproj
new file mode 100644
index 0000000..2169114
--- /dev/null
+++ b/src/EditorConfig.Core.NET45/EditorConfig.Core.NET45.csproj
@@ -0,0 +1,83 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {F22050E7-E2C3-47BC-8B06-5BFE163FF045}
+ Library
+ Properties
+ EditorConfig.Core
+ EditorConfig.Core
+ v4.5
+ 512
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+ bin\Debug\EditorConfig.Core.XML
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+ true
+
+
+ ..\..\sign.snk
+
+
+
+
+
+
+
+
+
+
+
+
+ Charset.cs
+
+
+ EditorConfigFile.cs
+
+
+ EditorConfigParser.cs
+
+
+ EndOfLine.cs
+
+
+ FileConfiguration.cs
+
+
+ IndentSize.cs
+
+
+ IndentStyle.cs
+
+
+ Minimatcher.cs
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/EditorConfig.Core.NET45/Properties/AssemblyInfo.cs b/src/EditorConfig.Core.NET45/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..ff9b497
--- /dev/null
+++ b/src/EditorConfig.Core.NET45/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("EditorConfig.Core.NET45")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("EditorConfig.Core.NET45")]
+[assembly: AssemblyCopyright("Copyright © 2015")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("d4fc2924-b345-44a1-b360-821c1478ba50")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/src/EditorConfig.Core/EditorConfig.Core.csproj b/src/EditorConfig.Core/EditorConfig.Core.csproj
index adc00bd..db642bd 100644
--- a/src/EditorConfig.Core/EditorConfig.Core.csproj
+++ b/src/EditorConfig.Core/EditorConfig.Core.csproj
@@ -36,12 +36,9 @@
true
- sign.snk
+ ..\..\sign.snk
-
- ..\..\packages\Minimatch.1.1.0.0\lib\portable-net40+sl50+win+wp80\Minimatch.dll
-
@@ -58,12 +55,9 @@
+
-
-
-
-
regexps
+ this.set = globParts.Select(g => g.Select(t => this.Parse(t, false)))
+ .Where(g => !g.Contains(null))
+ .Select(g => g.Select(t => t.Item1))
+ .ToList();
+ }
+
+ void ParseNegate()
+ {
+ var negateOffset = 0;
+
+ if (options.NoNegate) return;
+
+ for (var i = 0; i < pattern.Length && pattern[i] == '!'; i++)
+ {
+ negate = !negate;
+ negateOffset++;
+ }
+
+ if (negateOffset > 0) this.pattern = pattern.Substring(negateOffset);
+ }
+
+ static readonly Regex hasBraces = new Regex(@"\{.*\}");
+ static readonly Regex numericSet = new Regex(@"^\{(-?[0-9]+)\.\.(-?[0-9]+)\}");
+ // Brace expansion:
+ // a{b,c}d -> abd acd
+ // a{b,}c -> abc ac
+ // a{0..3}d -> a0d a1d a2d a3d
+ // a{b,c{d,e}f}g -> abg acdfg acefg
+ // a{b,c}d{e,f}g -> abdeg acdeg abdeg abdfg
+ //
+ // Invalid sets are not expanded.
+ // a{2..}b -> a{2..}b
+ // a{b}c -> a{b}c
+ ///Expands all brace ranges in a pattern, returning a sequence containing every possible combination.
+ public static IEnumerable BraceExpand(string pattern, EditorConfigMinimatcherOptions options)
+ {
+ if (options.NoBrace || !hasBraces.IsMatch(pattern))
+ {
+ // shortcut. no need to expand.
+ return new[] { pattern };
+ }
+ bool escaping = false;
+ int i;
+ // examples and comments refer to this crazy pattern:
+ // a{b,c{d,e},{f,g}h}x{y,z}
+ // expected:
+ // abxy
+ // abxz
+ // acdxy
+ // acdxz
+ // acexy
+ // acexz
+ // afhxy
+ // afhxz
+ // aghxy
+ // aghxz
+
+ // everything before the first \{ is just a prefix.
+ // So, we pluck that off, and work with the rest,
+ // and then prepend it to everything we find.
+ if (pattern[0] != '{')
+ {
+ // console.error(pattern)
+ string prefix = null;
+ for (i = 0; i < pattern.Length; i++)
+ {
+ var c = pattern[i];
+ // console.error(i, c)
+ if (c == '\\')
+ {
+ escaping = !escaping;
+ }
+ else if (c == '{' && !escaping)
+ {
+ prefix = pattern.Substring(0, i);
+ break;
+ }
+ }
+
+ // actually no sets, all { were escaped.
+ if (prefix == null)
+ {
+ // console.error("no sets")
+ return new[] { pattern };
+ }
+
+ return BraceExpand(pattern.Substring(i), options).Select(t => prefix + t);
+ }
+
+ // now we have something like:
+ // {b,c{d,e},{f,g}h}x{y,z}
+ // walk through the set, expanding each part, until
+ // the set ends. then, we'll expand the suffix.
+ // If the set only has a single member, then'll put the {} back
+
+ // first, handle numeric sets, since they're easier
+ var numset = numericSet.Match(pattern);
+ if (numset.Success)
+ {
+ // console.error("numset", numset[1], numset[2])
+ var suf = BraceExpand(pattern.Substring(numset.Length), options).ToList();
+ int start = int.Parse(numset.Groups[1].Value),
+ end = int.Parse(numset.Groups[2].Value),
+ inc = start > end ? -1 : 1;
+ var retVal = new List();
+ for (var w = start; w != (end + inc); w += inc)
+ {
+ // append all the suffixes
+ for (var ii = 0; ii < suf.Count; ii++)
+ {
+ retVal.Add(w.ToString() + suf[ii]);
+ }
+ }
+ return retVal;
+ }
+
+ // ok, walk through the set
+ // We hope, somewhat optimistically, that there
+ // will be a } at the end.
+ // If the closing brace isn't found, then the pattern is
+ // interpreted as braceExpand("\\" + pattern) so that
+ // the leading \{ will be interpreted literally.
+ i = 1; // skip the \{
+ int depth = 1;
+ var set = new List();
+ string member = "";
+
+ for (i = 1; i < pattern.Length && depth > 0; i++)
+ {
+ var c = pattern[i];
+ // console.error("", i, c)
+
+ if (escaping)
+ {
+ escaping = false;
+ member += "\\" + c;
+ }
+ else
+ {
+ switch (c)
+ {
+ case '\\':
+ escaping = true;
+ continue;
+
+ case '{':
+ depth++;
+ member += "{";
+ continue;
+
+ case '}':
+ depth--;
+ // if this closes the actual set, then we're done
+ if (depth == 0)
+ {
+ set.Add(member);
+ member = "";
+ // pluck off the close-brace
+ break;
+ }
+ else
+ {
+ member += c;
+ continue;
+ }
+
+ case ',':
+ if (depth == 1)
+ {
+ set.Add(member);
+ member = "";
+ }
+ else
+ {
+ member += c;
+ }
+ continue;
+
+ default:
+ member += c;
+ continue;
+ } // switch
+ } // else
+ } // for
+
+ // now we've either finished the set, and the suffix is
+ // pattern.substr(i), or we have *not* closed the set,
+ // and need to escape the leading brace
+ if (depth != 0)
+ {
+ // console.error("didn't close", pattern)
+ return BraceExpand("\\" + pattern, options);
+ }
+
+ // ["b", "c{d,e}","{f,g}h"] ->
+ // ["b", "cd", "ce", "fh", "gh"]
+ var addBraces = set.Count == 1;
+
+ set = set.SelectMany(p => BraceExpand(p, options)).ToList();
+
+ if (addBraces)
+ set = set.Select(s => "{" + s + "}").ToList();
+ // now attach the suffixes.
+ // x{y,z} -> ["xy", "xz"]
+ // console.error("set", set)
+ // console.error("suffix", pattern.substr(i))
+ return BraceExpand(pattern.Substring(i), options).SelectMany(s1 => set.Select(s2 => s2 + s1));
+ }
+
+ private class PatternListEntry
+ {
+ public char Type { get; set; }
+ public int Start { get; set; }
+ public int ReStart { get; set; }
+ }
+
+ abstract class ParseItem
+ {
+ public string Source { get; protected set; }
+
+ public static readonly ParseItem Empty = new LiteralItem("");
+ public static ParseItem Literal(string source) { return new LiteralItem(source); }
+ public abstract string RegexSource(EditorConfigMinimatcherOptions options);
+
+ public abstract bool Match(string input, EditorConfigMinimatcherOptions options);
+ }
+ class LiteralItem : ParseItem
+ {
+ public LiteralItem(string source) { Source = source; }
+ public override string RegexSource(EditorConfigMinimatcherOptions options) { return Regex.Escape(Source); }
+ public override bool Match(string input, EditorConfigMinimatcherOptions options)
+ {
+ return input.Equals(Source, options.NoCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
+ }
+ }
+ class MagicItem : ParseItem
+ {
+ public MagicItem(string source, EditorConfigMinimatcherOptions options)
+ {
+ Source = source;
+ regex = new Lazy(() => new Regex("^" + source + "$", options.RegexOptions));
+ }
+ readonly Lazy regex;
+
+ public override string RegexSource(EditorConfigMinimatcherOptions options) { return Source; }
+ public override bool Match(string input, EditorConfigMinimatcherOptions options)
+ {
+ return regex.Value.IsMatch(input);
+ }
+ }
+ class GlobStar : ParseItem
+ {
+ private GlobStar() { }
+ public static readonly ParseItem Instance = new GlobStar();
+
+ public override string RegexSource(EditorConfigMinimatcherOptions options)
+ {
+ return options.NoGlobStar ? star
+ : options.Dot ? twoStarDot
+ : twoStarNoDot;
+ }
+
+ public override bool Match(string input, EditorConfigMinimatcherOptions options) { throw new NotSupportedException(); }
+ }
+
+ static readonly Regex escapeCheck = new Regex(@"((?:\\{2})*)(\\?)\|");
+ // parse a component of the expanded set.
+ // At this point, no pattern may contain "/" in it
+ // so we're going to return a 2d array, where each entry is the full
+ // pattern, split on '/', and then turned into a regular expression.
+ // A regexp is made at the end which joins each array with an
+ // escaped /, and another full one which joins each regexp with |.
+ //
+ // Following the lead of Bash 4.1, note that "**" only has special meaning
+ // when it is the *only* thing in a path portion. Otherwise, any series
+ // of * is equivalent to a single *. Globstar behavior is enabled by
+ // default, and can be disabled by setting options.noglobstar.
+ private Tuple Parse(string pattern, bool isSub)
+ {
+ // shortcuts
+ if (!options.NoGlobStar && pattern == "**") return Tuple.Create(GlobStar.Instance, false);
+ if (pattern == "") return Tuple.Create(ParseItem.Empty, false);
+
+ string re = "";
+ bool hasMagic = options.NoCase, escaping = false, inClass = false;
+ // ? => one single character
+ var patternListStack = new Stack();
+ char plType;
+ char? stateChar = null;
+
+ int reClassStart = -1, classStart = -1;
+ // . and .. never match anything that doesn't start with .,
+ // even when options.dot is set.
+ string patternStart = pattern[0] == '.' ? "" // anything
+ // not (start or / followed by . or .. followed by / or end)
+ : options.Dot ? "(?!(?:^|\\/)\\.{1,2}(?:$|\\/))"
+ : "(?!\\.)";
+
+ Action clearStateChar = () =>
+ {
+ if (stateChar != null)
+ {
+ // we had some state-tracking character
+ // that wasn't consumed by this pass.
+ switch (stateChar)
+ {
+ case '*':
+ re += star;
+ hasMagic = true;
+ break;
+ case '?':
+ re += qmark;
+ hasMagic = true;
+ break;
+ default:
+ re += "\\" + stateChar;
+ break;
+ }
+ stateChar = null;
+ }
+ };
+
+ for (var i = 0; i < pattern.Length; i++)
+ {
+ var c = pattern[i];
+ //if (options.debug) {
+ // console.error("%s\t%s %s %j", pattern, i, re, c)
+ //}
+
+ // skip over any that are escaped.
+ if (escaping && reSpecials.Contains(c))
+ {
+ re += "\\" + c;
+ escaping = false;
+ continue;
+ }
+
+ switch (c)
+ {
+ case '/':
+ // completely not allowed, even escaped.
+ // Should already be path-split by now.
+ return null;
+
+ case '\\':
+ clearStateChar();
+ escaping = true;
+ continue;
+
+ // the various stateChar values
+ // for the 'extglob' stuff.
+ case '?':
+ case '*':
+ case '+':
+ case '@':
+ case '!':
+ //if (options.debug) {
+ // console.error("%s\t%s %s %j <-- stateChar", pattern, i, re, c)
+ //}
+
+ // all of those are literals inside a class, except that
+ // the glob [!a] means [^a] in regexp
+ if (inClass)
+ {
+ if (c == '!' && i == classStart + 1) c = '^';
+ re += c;
+ continue;
+ }
+
+ // if we already have a stateChar, then it means
+ // that there was something like ** or +? in there.
+ // Handle the stateChar, then proceed with this one.
+ clearStateChar();
+ stateChar = c;
+ // if extglob is disabled, then +(asdf|foo) isn't a thing.
+ // just clear the statechar *now*, rather than even diving into
+ // the patternList stuff.
+ if (options.NoExt) clearStateChar();
+ continue;
+
+ case '(':
+ if (inClass)
+ {
+ re += "(";
+ continue;
+ }
+
+ if (stateChar == null)
+ {
+ re += "\\(";
+ continue;
+ }
+
+ plType = stateChar.Value;
+ patternListStack.Push(new PatternListEntry { Type = plType, Start = i - 1, ReStart = re.Length });
+ // negation is (?:(?!js)[^/]*)
+ re += stateChar == '!' ? "(?:(?!" : "(?:";
+ stateChar = null;
+ continue;
+
+ case ')':
+ if (inClass || !patternListStack.Any())
+ {
+ re += "\\)";
+ continue;
+ }
+
+ hasMagic = true;
+ re += ')';
+ plType = patternListStack.Pop().Type;
+ // negation is (?:(?!js)[^/]*)
+ // The others are (?:)
+ switch (plType)
+ {
+ case '!':
+ re += "[^/]*?)";
+ break;
+ case '?':
+ case '+':
+ case '*': re += plType; break;
+ case '@': break; // the default anyway
+ }
+ continue;
+
+ case '|':
+ if (inClass || !patternListStack.Any() || escaping)
+ {
+ re += "\\|";
+ escaping = false;
+ continue;
+ }
+
+ re += "|";
+ continue;
+
+ // these are mostly the same in regexp and glob
+ case '[':
+ // swallow any state-tracking char before the [
+ clearStateChar();
+
+ if (inClass)
+ {
+ re += "\\" + c;
+ continue;
+ }
+
+ inClass = true;
+ classStart = i;
+ reClassStart = re.Length;
+ re += c;
+ continue;
+
+ case ']':
+ // a right bracket shall lose its special
+ // meaning and represent itself in
+ // a bracket expression if it occurs
+ // first in the list. -- POSIX.2 2.8.3.2
+ if (i == classStart + 1 || !inClass)
+ {
+ re += "\\" + c;
+ escaping = false;
+ continue;
+ }
+
+ // finish up the class.
+ hasMagic = true;
+ inClass = false;
+ re += c;
+ continue;
+
+ default:
+ // swallow any state char that wasn't consumed
+ clearStateChar();
+
+ if (escaping)
+ {
+ // no need
+ escaping = false;
+ }
+ else if (reSpecials.Contains(c) && !(c == '^' && inClass))
+ {
+ re += "\\";
+ }
+
+ re += c;
+ break;
+ } // switch
+ } // for
+
+
+ // handle the case where we left a class open.
+ // "[abc" is valid, equivalent to "\[abc"
+ if (inClass)
+ {
+ // split where the last [ was, and escape it
+ // this is a huge pita. We now have to re-walk
+ // the contents of the would-be class to re-translate
+ // any characters that were passed through as-is
+ string cs = pattern.Substring(classStart + 1);
+ var sp = this.Parse(cs, true);
+ re = re.Substring(0, reClassStart) + "\\[" + sp.Item1.Source;
+ hasMagic = hasMagic || sp.Item2;
+ }
+
+ // handle the case where we had a +( thing at the *end*
+ // of the pattern.
+ // each pattern list stack adds 3 chars, and we need to go through
+ // and escape any | chars that were passed through as-is for the regexp.
+ // Go through and escape them, taking care not to double-escape any
+ // | chars that were already escaped.
+ while (patternListStack.Any())
+ {
+ var pl = patternListStack.Pop();
+ var tail = re.Substring(pl.ReStart + 3);
+ // maybe some even number of \, then maybe 1 \, followed by a |
+ tail = escapeCheck.Replace(tail, m =>
+ {
+ string escape = m.Groups[2].Value;
+ // the | isn't already escaped, so escape it.
+ if (String.IsNullOrEmpty(escape)) escape = "\\";
+
+ // need to escape all those slashes *again*, without escaping the
+ // one that we need for escaping the | character. As it works out,
+ // escaping an even number of slashes can be done by simply repeating
+ // it exactly after itself. That's why this trick works.
+ //
+ // I am sorry that you have to see this.
+ return m.Groups[1].Value + m.Groups[1].Value + escape + "|";
+ });
+
+ // console.error("tail=%j\n %s", tail, tail)
+ var t = pl.Type == '*' ? star
+ : pl.Type == '?' ? qmark
+ : "\\" + pl.Type;
+
+ hasMagic = true;
+ re = re.Remove(pl.ReStart)
+ + t + "\\("
+ + tail;
+ }
+
+ // handle trailing things that only matter at the very end.
+ clearStateChar();
+ if (escaping)
+ {
+ // trailing \\
+ re += "\\\\";
+ }
+
+ // only need to apply the nodot start if the re starts with
+ // something that could conceivably capture a dot
+ var addPatternStart = false;
+ switch (re[0])
+ {
+ case '.':
+ case '[':
+ case '(': addPatternStart = true; break;
+ }
+
+ // if the re is not "" at this point, then we need to make sure
+ // it doesn't match against an empty path part.
+ // Otherwise a/* will match a/, which it should not.
+ if (re != "" && hasMagic) re = "(?=.)" + re;
+
+ if (addPatternStart) re = patternStart + re;
+
+ // parsing just a piece of a larger pattern.
+ if (isSub)
+ {
+ return Tuple.Create(ParseItem.Literal(re), hasMagic);
+ }
+
+ // skip the regexp for non-magical patterns
+ // unescape anything in it, though, so that it'll be
+ // an exact match against a file etc.
+ if (!hasMagic)
+ {
+ return Tuple.Create(ParseItem.Literal(GlobUnescape(pattern)), false);
+ }
+ return new Tuple(new MagicItem(re, options), false);
+ }
+
+
+ Regex MakeRegex()
+ {
+ if (this.regexp != null || isError) return this.regexp;
+
+ // at this point, this.set is a 2d array of partial
+ // pattern strings, or "**".
+ //
+ // It's better to use .match(). This function shouldn't
+ // be used, really, but it's pretty convenient sometimes,
+ // when you just want to work with a regex.
+ if (comment || empty || !set.Any())
+ {
+ this.isError = true;
+ return null;
+ }
+ var re = String.Join("|", set.Select(pattern =>
+ String.Join("\\/", pattern.Select(p => p.RegexSource(options))
+ )));
+
+ // must match entire pattern
+ // ending in a * or ** will make it less strict.
+ re = "^(?:" + re + ")$";
+
+ // can match anything, as long as it's not this.
+ if (this.negate) re = "^(?!" + re + ").*$";
+
+ try
+ {
+ return this.regexp = new Regex(re, options.RegexOptions);
+ }
+ catch
+ {
+ this.isError = true;
+ return null;
+ }
+ }
+
+
+ private bool Match(string input, bool partial)
+ {
+ // console.error("match", f, this.pattern)
+ // short-circuit in the case of busted things.
+ // comments, etc.
+ if (this.comment) return false;
+ if (this.empty) return input == "";
+
+ if (input == "/" && partial) return true;
+
+ // windows: need to use /, not \
+ // On other platforms, \ is a valid (albeit bad) filename char.
+
+ if (options.AllowWindowsPaths)
+ input = input.Replace("\\", "/");
+
+ // treat the test path as a set of pathparts.
+ var f = slashSplit.Split(input);
+ //if (options.debug) {
+ // console.error(this.pattern, "split", f)
+ //}
+
+ // just ONE of the pattern sets in this.set needs to match
+ // in order for it to be valid. If negating, then just one
+ // match means that we have failed.
+ // Either way, return on the first hit.
+
+ foreach (var pattern in set)
+ {
+ var hit = this.MatchOne(f, pattern.ToList(), partial);
+ if (hit)
+ {
+ if (options.FlipNegate) return true;
+ return !this.negate;
+ }
+
+ }
+
+ // didn't get any hits. this is success if it's a negative
+ // pattern, failure otherwise.
+ if (options.FlipNegate) return false;
+ return this.negate;
+ }
+
+ // set partial to true to test if, for example,
+ // "/a/b" matches the start of "/*/b/*/d"
+ // Partial means, if you run out of file before you run
+ // out of pattern, then that's fine, as long as all
+ // the parts match.
+ bool MatchOne(IList file, IList pattern, bool partial)
+ {
+
+ //if (options.debug) {
+ // console.error("matchOne",
+ // { "this": this
+ // , file: file
+ // , pattern: pattern })
+ //}
+
+ if (options.MatchBase && pattern.Count == 1)
+ {
+ file = new[] { file.Last(s => !String.IsNullOrEmpty(s)) };
+ }
+
+ //if (options.debug) {
+ // console.error("matchOne", file.length, pattern.length)
+ //}
+ int fi = 0, pi = 0;
+ for (; (fi < file.Count) && (pi < pattern.Count); fi++, pi++)
+ {
+
+ //if (options.debug) {
+ // console.error("matchOne loop")
+ //}
+ ParseItem p = pattern[pi];
+ string f = file[fi];
+
+ //if (options.debug) {
+ // console.error(pattern, p, f)
+ //}
+
+ // should be impossible.
+ // some invalid regexp stuff in the set.
+ if (p == null) return false;
+
+ if (p is GlobStar)
+ {
+ //if (options.debug)
+ // console.error('GLOBSTAR', [pattern, p, f])
+
+ // "**"
+ // a/**/b/**/c would match the following:
+ // a/b/x/y/z/c
+ // a/x/y/z/b/c
+ // a/b/x/b/x/c
+ // a/b/c
+ // To do this, take the rest of the pattern after
+ // the **, and see if it would match the file remainder.
+ // If so, return success.
+ // If not, the ** "swallows" a segment, and try again.
+ // This is recursively awful.
+ //
+ // a/**/b/**/c matching a/b/x/y/z/c
+ // - a matches a
+ // - doublestar
+ // - matchOne(b/x/y/z/c, b/**/c)
+ // - b matches b
+ // - doublestar
+ // - matchOne(x/y/z/c, c) -> no
+ // - matchOne(y/z/c, c) -> no
+ // - matchOne(z/c, c) -> no
+ // - matchOne(c, c) yes, hit
+ int fr = fi, pr = pi + 1;
+ if (pr == pattern.Count)
+ {
+ //if (options.debug)
+ // console.error('** at the end')
+ // a ** at the end will just swallow the rest.
+ // We have found a match.
+ // however, it will not swallow /.x, unless
+ // options.dot is set.
+ // . and .. are *never* matched by **, for explosively
+ // exponential reasons.
+ for (; fi < file.Count; fi++)
+ {
+ if (file[fi] == "." || file[fi] == ".." ||
+ (!options.Dot && !string.IsNullOrEmpty(file[fi]) && file[fi][0] == '.')) return false;
+ }
+ return true;
+ }
+
+ // ok, let's see if we can swallow whatever we can.
+ while (fr < file.Count)
+ {
+ var swallowee = file[fr];
+
+ //if (options.debug) {
+ // console.error('\nglobstar while',
+ // file, fr, pattern, pr, swallowee)
+ //}
+
+ // XXX remove this slice. Just pass the start index.
+ if (this.MatchOne(file.Skip(fr).ToList(), pattern.Skip(pr).ToList(), partial))
+ {
+ //if (options.debug)
+ // console.error('globstar found match!', fr, file.Count, swallowee)
+ // found a match.
+ return true;
+ }
+ else
+ {
+ // can't swallow "." or ".." ever.
+ // can only swallow ".foo" when explicitly asked.
+ if (swallowee == "." || swallowee == ".." ||
+ (!options.Dot && swallowee[0] == '.'))
+ {
+ //if (options.debug)
+ // console.error("dot detected!", file, fr, pattern, pr)
+ break;
+ }
+
+ // ** swallows a segment, and continue.
+ //if (options.debug)
+ // console.error('globstar swallow a segment, and continue')
+ fr++;
+ }
+ }
+ // no match was found.
+ // However, in partial mode, we can't say this is necessarily over.
+ // If there's more *pattern* left, then
+ if (partial)
+ {
+ // ran out of file
+ // console.error("\n>>> no match, partial?", file, fr, pattern, pr)
+ if (fr == file.Count) return true;
+ }
+ return false;
+ }
+
+ // something other than **
+ // non-magic patterns just have to match exactly
+ // patterns with magic have been turned into regexps.
+ if (!p.Match(f, options))
+ return false;
+ }
+
+ // Note: ending in / means that we'll get a final ""
+ // at the end of the pattern. This can only match a
+ // corresponding "" at the end of the file.
+ // If the file ends in /, then it can only match a
+ // a pattern that ends in /, unless the pattern just
+ // doesn't have any more for it. But, a/b/ should *not*
+ // match "a/b/*", even though "" matches against the
+ // [^/]*? pattern, except in partial mode, where it might
+ // simply not be reached yet.
+ // However, a/b/ should still satisfy a/*
+
+ // now either we fell off the end of the pattern, or we're done.
+ if (fi == file.Count && pi == pattern.Count)
+ {
+ // ran out of pattern and filename at the same time.
+ // an exact hit!
+ return true;
+ }
+ else if (fi == file.Count)
+ {
+ // ran out of file, but still had pattern left.
+ // this is ok if we're doing the match as part of
+ // a glob fs traversal.
+ return partial;
+ }
+ else if (pi == pattern.Count)
+ {
+ // ran out of pattern, still have file left.
+ // this is only acceptable if we're on the very last
+ // empty segment of a file with a trailing slash.
+ // a/* should match a/b/
+ var emptyFileEnd = (fi == file.Count - 1) && (file[fi] == "");
+ return emptyFileEnd;
+ }
+
+ // should be unreachable.
+ throw new InvalidOperationException("wtf?");
+ }
+
+
+ // replace stuff like \* with *
+ static readonly Regex globUnescaper = new Regex(@"\\(.)");
+ static string GlobUnescape(string s)
+ {
+ return globUnescaper.Replace(s, "$1");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/EditorConfig.Core/packages.config b/src/EditorConfig.Core/packages.config
deleted file mode 100644
index 176ee48..0000000
--- a/src/EditorConfig.Core/packages.config
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/src/EditorConfig.Tests/EditorConfig.Tests.csproj b/src/EditorConfig.Tests/EditorConfig.Tests.csproj
index cd66363..403246a 100644
--- a/src/EditorConfig.Tests/EditorConfig.Tests.csproj
+++ b/src/EditorConfig.Tests/EditorConfig.Tests.csproj
@@ -36,9 +36,6 @@
..\..\packages\FluentAssertions.3.2.1\lib\net45\FluentAssertions.Core.dll
-
- ..\..\packages\Minimatch.1.1.0.0\lib\portable-net40+sl50+win+wp80\Minimatch.dll
-
..\..\packages\NUnit.2.6.3\lib\nunit.framework.dll
diff --git a/src/EditorConfig.Tests/MiniMatcherTests.cs b/src/EditorConfig.Tests/MiniMatcherTests.cs
index 03bd99b..dddfbb1 100644
--- a/src/EditorConfig.Tests/MiniMatcherTests.cs
+++ b/src/EditorConfig.Tests/MiniMatcherTests.cs
@@ -3,8 +3,8 @@
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using EditorConfig.Core;
using FluentAssertions;
-using Minimatch;
using NUnit.Framework;
namespace EditorConfig.Tests
@@ -12,14 +12,14 @@ namespace EditorConfig.Tests
[TestFixture]
public class MiniMatcherTests
{
- private readonly Options _globOptions = new Options { MatchBase = true, Dot = true, NoExt = true };
+ private readonly EditorConfigMinimatcherOptions _globOptions = new EditorConfigMinimatcherOptions { MatchBase = true, Dot = true, NoExt = true };
[Test]
public void MatcherTest()
{
var glob = "C:/Projects/editorconfig-core-net/tests/filetree/top/of/path";
var file = "C:/Projects/editorconfig-core-net/tests/filetree/top/of/path";
- var m = new Minimatcher(glob, _globOptions);
+ var m = new EditorConfigMinimatcher(glob, _globOptions);
var match = m.IsMatch(file);
match.Should().BeTrue();
}
diff --git a/src/EditorConfig.Tests/packages.config b/src/EditorConfig.Tests/packages.config
index 52dc8d1..4f7907c 100644
--- a/src/EditorConfig.Tests/packages.config
+++ b/src/EditorConfig.Tests/packages.config
@@ -1,6 +1,5 @@
-
\ No newline at end of file