コード例 #1
0
        ///<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));
        }
コード例 #2
0
 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;
 }
コード例 #3
0
 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;
 }
コード例 #4
0
            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);
            }
コード例 #5
0
        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);
        }
コード例 #6
0
        // 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);
        }
コード例 #7
0
 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 == '\\'));
 }
コード例 #8
0
        // 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);
        }