2 // System.IO.SearchPattern2.cs: Filename glob support.
5 // Dan Lewis (dihlewis@yahoo.co.uk)
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 // Copied from corlib/System.IO/SearchPatter.cs
36 // FIXME: there's a complication with this algorithm under windows.
37 // the pattern '*.*' matches all files (i think . matches the extension),
38 // whereas under UNIX it should only match files containing the '.' character.
40 class SearchPattern2 {
41 public SearchPattern2 (string pattern) : this (pattern, false) { }
43 public SearchPattern2 (string pattern, bool ignore)
46 this.pattern = pattern;
50 // OSX has a case-insensitive yet case-aware filesystem
51 // so we need a overload in here for the Kqueue watcher
52 public bool IsMatch (string text, bool ignorecase)
55 bool match = String.Compare (pattern, text, ignorecase) == 0;
60 // This is a special case for FSW. It needs to match e.g. subdir/file.txt
61 // when the pattern is "file.txt"
62 var fileName = Path.GetFileName (text);
65 return (String.Compare (pattern, fileName, ignorecase) == 0);
68 return Match (ops, fileName, 0);
71 public bool IsMatch (string text)
73 return IsMatch (text, ignore);
76 public bool HasWildcard {
77 get { return hasWildcard; }
81 Op ops; // the compiled pattern
82 bool ignore; // ignore case
86 private void Compile (string pattern)
88 if (pattern == null || pattern.IndexOfAny (InvalidChars) >= 0)
89 throw new ArgumentException ("Invalid search pattern: '" + pattern + "'");
91 if (pattern == "*") { // common case
92 ops = new Op (OpCode.True);
101 while (ptr < pattern.Length) {
104 switch (pattern [ptr]) {
106 op = new Op (OpCode.AnyChar);
112 op = new Op (OpCode.AnyString);
118 op = new Op (OpCode.ExactString);
119 int end = pattern.IndexOfAny (WildcardChars, ptr);
121 end = pattern.Length;
123 op.Argument = pattern.Substring (ptr, end - ptr);
125 op.Argument = op.Argument.ToLower ();
140 ops = new Op (OpCode.End);
142 last_op.Next = new Op (OpCode.End);
145 private bool Match (Op op, string text, int ptr)
153 if (ptr == text.Length)
158 case OpCode.ExactString:
159 int length = op.Argument.Length;
160 if (ptr + length > text.Length)
163 string str = text.Substring (ptr, length);
165 str = str.ToLower ();
167 if (str != op.Argument)
174 if (++ ptr > text.Length)
178 case OpCode.AnyString:
179 while (ptr <= text.Length) {
180 if (Match (op.Next, text, ptr))
197 internal static readonly char [] WildcardChars = { '*', '?' };
198 internal static readonly char [] InvalidChars = { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };
201 public Op (OpCode code)
204 this.Argument = null;
209 public string Argument;
213 private enum OpCode {
214 ExactString, // literal
217 End, // end of pattern
218 True // always succeeds