///<summary>Creates a new GlobMatcher instance, parsing the pattern into a regex.</summary> public static GlobMatcher Create(string pattern, GlobMatcherOptions options = null) { if (pattern == null) { throw new ArgumentNullException(nameof(pattern)); } options = options ?? new GlobMatcherOptions(); pattern = pattern.Trim(); if (options.AllowWindowsPathsInPatterns) { pattern = pattern.Replace('\\', '/'); } // empty patterns and comments match nothing. if (!options.NoComment && !string.IsNullOrEmpty(pattern) && pattern[0] == '#') { return(new GlobMatcher(options, comment: true)); } if (String.IsNullOrEmpty(pattern)) { return(new GlobMatcher(options, empty: true)); } // step 1: figure out negation, etc. bool negate = ParseNegate(options, ref pattern); // step 2: expand braces var globSet = BraceExpand(pattern, options); // step 3: now we have a set, so turn each one into a series of path-portion // matching patterns. /*var list = new List<string>(globSet.Count); * for (var index = 0; index < globSet.Count; index++) * { * var s = globSet[index]; * list.Add(ourSlashSplit.Split(s)); * }*/ // glob --> regexps var list1 = new List <PatternCase>(globSet.Count); foreach (var g in globSet) { var parsedSet = Parse(options, g); if (parsedSet == null) { goto nextG; } list1.Add(parsedSet); nextG :; } return(new GlobMatcher(options, list1, negate)); }
private GlobMatcher(GlobMatcherOptions options, List <PatternCase>?parsedPatternSet = null, bool negate = false, bool comment = false, bool empty = false) { _myOptions = options; _mySet = parsedPatternSet; _myNegate = negate; _myComment = comment; _myEmpty = empty; }
public MatchContext(GlobMatcherOptions options, string str, PatternCase patternCase) { _myOptions = options; _myStr = str; _myPatternCase = patternCase; _myStartOffset = 0; _myEndOffset = _myStr.Length; _myStartItem = 0; _myEndItem = _myPatternCase.Count - 1; _myLastAsteriskItem = -1; _myNextPositionForAsterisk = -1; }
public bool CheckChar(GlobMatcherOptions options, char c, StringComparison comparison) { if (IsPathSeparator(options, c)) { return(false); } if (PossibleChars != null) { return((PossibleChars.IndexOf(c.ToString(CultureInfo.InvariantCulture), comparison) != -1) != Negate); } return(true); }
private static bool ParseNegate(GlobMatcherOptions options, ref string pattern) { var negateOffset = 0; if (options.NoNegate) { return(false); } bool negate = false; for (var i = 0; i < pattern.Length && pattern[i] == '!'; i++) { negate = !negate; negateOffset++; } if (negateOffset > 0) { pattern = pattern.Substring(negateOffset); } return(negate); }
// parse a component of the expanded set. // ReSharper disable once FunctionComplexityOverflow private static PatternCase Parse(GlobMatcherOptions options, string?pattern) { if (pattern?.Length == 0) { return(new PatternCase()); } var result = new PatternCase(); var sb = new StringBuilder(); bool escaping = false, inClass = false, negate = false, range = false; int classStart = -1; void FinishLiteral() { Debug.Assert(!escaping && !inClass, "!escaping && !inClass"); if (sb.Length <= 0) { return; } result.Add(new Literal(sb.ToString())); sb.Clear(); } void AppendChar(char c1) { if (inClass && range) { char firstChar = sb[sb.Length - 1]; firstChar++; for (char c2 = firstChar; c2 <= c1; c2++) { sb.Append(c2); } range = false; } else { sb.Append(c1); } } if (pattern == null) { throw new InvalidOperationException(); } for (var i = 0; i < pattern.Length; i++) { var c = pattern[i]; // skip over any that are escaped. if (escaping && c != '/') { AppendChar(c); escaping = false; } else { switch (c) { case '/': if (inClass) { // Class is left open HandleOpenClass(); continue; } else { if (escaping) { sb.Append('\\'); escaping = false; } FinishLiteral(); if (!(result.LastOrDefault() is PathSeparator)) { result.Add(PathSeparator.Instance); } } break; case '\\': escaping = true; break; case '!': case '^': if (inClass && i == classStart + 1) { // the glob [!a] means negation negate = true; } else { AppendChar(c); } break; case '?': if (inClass) { AppendChar(c); } else { FinishLiteral(); result.Add(OneChar.EmptyInstance); } break; case '*': if (inClass) { AppendChar(c); } else { FinishLiteral(); if (result.LastOrDefault() is Asterisk && !options.NoGlobStar) { result.RemoveAt(result.Count - 1); result.Add(new DoubleAsterisk()); } else if (!(result.LastOrDefault() is SimpleAsterisk)) { result.Add(new SimpleAsterisk()); } } break; // these are mostly the same in regexp and glob case '[': if (inClass) { AppendChar(c); } else { FinishLiteral(); inClass = true; negate = false; range = false; classStart = i; } break; 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 || (negate && i == classStart + 2) || !inClass) { AppendChar(c); } else { if (range) { sb.Append('-'); } // finish up the class. inClass = false; result.Add(new OneChar(sb.ToString(), negate)); sb.Clear(); } break; case '-': if (i == classStart + 1 || (negate && i == classStart + 2) || !inClass || range) { AppendChar(c); } else { range = true; } break; default: AppendChar(c); break; } // switch } // if if (i == pattern.Length - 1) { if (inClass) { HandleOpenClass(); // Do not continue, because next check could be relevant } } if (i == pattern.Length - 1) { if (escaping) { sb.Append('\\'); escaping = false; FinishLiteral(); } else { FinishLiteral(); } } void HandleOpenClass() { // handle the case where we left a class open. // "[abc" is valid, equivalent to "\[abc" // 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 sb.Clear(); if (result.LastOrDefault() is Literal literal) { sb.Append(literal.Source); result.RemoveAt(result.Count - 1); } sb.Append('['); escaping = false; i = classStart; inClass = false; } // Handle open class } // for result.Build(); return(result); }
private static bool IsPathSeparator(GlobMatcherOptions options, char c) { // windows: need to use /, not \ // On other platforms, \ is a valid (albeit bad) filename char. return(c == '/' || (options.AllowWindowsPaths && c == '\\')); }
// 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 ///<summary>Expands all brace ranges in a pattern, returning a sequence containing every possible combination.</summary> private static IList <string> BraceExpand(string pattern, GlobMatcherOptions options) { if (options.NoBrace || !OurHasBraces.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 }); } var braceExpand = BraceExpand(pattern.Substring(i), options); for (var index = 0; index < braceExpand.Count; index++) { braceExpand[index] = prefix + braceExpand[index]; } return(braceExpand); } // 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 put the {} back // first, handle numeric sets, since they're easier var numset = OurNumericSet.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, CultureInfo.InvariantCulture), end = int.Parse(numset.Groups[2].Value, CultureInfo.InvariantCulture), inc = start > end ? -1 : 1; var retVal = new List <string>(Math.Abs(end + inc - start) * suf.Count); for (var w = start; w != (end + inc); w += inc) { // append all the suffixes foreach (var t in suf) { retVal.Add(w.ToString(CultureInfo.InvariantCulture) + t); } } 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. int depth = 1; var set = new List <string>(); 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; var set1 = new List <string>(set.Count); foreach (string p in set) { set1.AddRange(BraceExpand(p, options)); } set = set1; if (addBraces) { for (var index = 0; index < set.Count; index++) { set[index] = "{" + set[index] + "}"; } } // now attach the suffixes. // x{y,z} -> ["xy", "xz"] // console.error("set", set) // console.error("suffix", pattern.substr(i)) var s2 = BraceExpand(pattern.Substring(i), options); var list1 = new List <string>(s2.Count * set.Count); list1.AddRange( from s1 in s2 from s in set select s + s1); return(list1); }