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