// 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 <string> file, IList <ParseItem> 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. // TODO:: BERND FIXED THE CASE WHERE IT IS EMPTY ON START if (swallowee == "." || swallowee == ".." || (!options.Dot && swallowee.Length > 0 && 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?"); }