2005-06-05 Peter Bartok <pbartok@novell.com>
[mono.git] / mcs / nant / src / DirectoryScanner.cs
1 // NAnt - A .NET build tool\r
2 // Copyright (C) 2001 Gerry Shaw\r
3 //\r
4 // This program is free software; you can redistribute it and/or modify\r
5 // it under the terms of the GNU General Public License as published by\r
6 // the Free Software Foundation; either version 2 of the License, or\r
7 // (at your option) any later version.\r
8 //\r
9 // This program is distributed in the hope that it will be useful,\r
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of\r
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
12 // GNU General Public License for more details.\r
13 //\r
14 // You should have received a copy of the GNU General Public License\r
15 // along with this program; if not, write to the Free Software\r
16 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\r
17 //\r
18 // Gerry Shaw (gerry_shaw@yahoo.com)\r
19 \r
20 /*\r
21 Examples:\r
22 "**\*.class" matches all .class files/dirs in a directory tree.\r
23 \r
24 "test\a??.java" matches all files/dirs which start with an 'a', then two\r
25 more characters and then ".java", in a directory called test.\r
26 \r
27 "**" matches everything in a directory tree.\r
28 \r
29 "**\test\**\XYZ*" matches all files/dirs that start with "XYZ" and where\r
30 there is a parent directory called test (e.g. "abc\test\def\ghi\XYZ123").\r
31 \r
32 Example of usage:\r
33 \r
34 DirectoryScanner scanner = DirectoryScanner();\r
35 scanner.Includes.Add("**\\*.class");\r
36 scanner.Exlucdes.Add("modules\\*\\**");\r
37 scanner.BaseDirectory = "test";\r
38 scanner.Scan();\r
39 foreach (string filename in GetIncludedFiles()) {\r
40     Console.WriteLine(filename);\r
41 }\r
42 */\r
43 \r
44 namespace SourceForge.NAnt {\r
45 \r
46     using System;\r
47     using System.Collections.Specialized;\r
48     using System.IO;\r
49     using System.Text;\r
50     using System.Text.RegularExpressions;\r
51 \r
52     public class DirectoryScanner {\r
53 \r
54         string _baseDirectory = Environment.CurrentDirectory;\r
55 \r
56         // holds the nant patterns\r
57         StringCollection _includes = new StringCollection();\r
58         StringCollection _excludes = new StringCollection();\r
59 \r
60         // holds the nant patterns converted to regular expression patterns\r
61         StringCollection _includePatterns = null;\r
62         StringCollection _excludePatterns = null;\r
63 \r
64         // holds the result from a scan\r
65         StringCollection _fileNames = null;\r
66         StringCollection _directoryNames = null;\r
67 \r
68         public StringCollection Includes {\r
69             get { return _includes; }\r
70         }\r
71 \r
72         public StringCollection Excludes {\r
73             get { return _excludes; }\r
74         }\r
75 \r
76         public string BaseDirectory {\r
77             get { return _baseDirectory; }\r
78             set { _baseDirectory = value; }\r
79         }\r
80 \r
81         public StringCollection FileNames {\r
82             get {\r
83                 if (_fileNames == null) {\r
84                     Scan();\r
85                 }\r
86                 return _fileNames;\r
87             }\r
88         }\r
89 \r
90         public StringCollection DirectoryNames {\r
91             get {\r
92                 if (_directoryNames == null) {\r
93                     Scan();\r
94                 }\r
95                 return _directoryNames;\r
96             }\r
97         }\r
98 \r
99         public void Scan() {\r
100             _includePatterns = new StringCollection();\r
101             foreach (string pattern in Includes) {\r
102                 _includePatterns.Add(ToRegexPattern(pattern));\r
103             }\r
104 \r
105             _excludePatterns = new StringCollection();\r
106             foreach (string pattern in Excludes) {\r
107                 _excludePatterns.Add(ToRegexPattern(pattern));\r
108             }\r
109 \r
110             _fileNames      = new StringCollection();\r
111             _directoryNames = new StringCollection();\r
112 \r
113             ScanDirectory(Path.GetFullPath(BaseDirectory));\r
114         }\r
115 \r
116         void ScanDirectory(string path) {\r
117             // get info for the current directory\r
118             DirectoryInfo currentDirectoryInfo = new DirectoryInfo(path);\r
119 \r
120             // scan subfolders\r
121             foreach (DirectoryInfo directoryInfo in currentDirectoryInfo.GetDirectories()) {\r
122                 ScanDirectory(directoryInfo.FullName);\r
123             }\r
124 \r
125             // scan files\r
126             foreach (FileInfo fileInfo in currentDirectoryInfo.GetFiles()) {\r
127                 string filename = Path.Combine(path, fileInfo.Name);\r
128                 if (IsPathIncluded(filename)) {\r
129                     _fileNames.Add(filename);\r
130                 }\r
131             }\r
132 \r
133             // Check current path last so that delete task will correctly\r
134             // delete empty directories.  This may *seem* like a special case\r
135             // but it is more like formalizing something in a way that makes\r
136             // writing the delete task easier :)\r
137             if (IsPathIncluded(path)) {\r
138                 _directoryNames.Add(path);\r
139             }\r
140         }\r
141 \r
142         bool IsPathIncluded(string path) {\r
143             bool included = false;\r
144 \r
145             // check path against includes\r
146             foreach (string pattern in _includePatterns) {\r
147                 Match m = Regex.Match(path, pattern);\r
148                 if (m.Success) {\r
149                     included = true;\r
150                     break;\r
151                 }\r
152             }\r
153 \r
154             // check path against excludes\r
155             if (included) {\r
156                 foreach (string pattern in _excludePatterns) {\r
157                     Match m = Regex.Match(path, pattern);\r
158                     if (m.Success) {\r
159                         included = false;\r
160                         break;\r
161                     }\r
162                 }\r
163             }\r
164 \r
165             return included;\r
166         }\r
167 \r
168         string ToRegexPattern(string nantPattern) {\r
169 \r
170             StringBuilder pattern = new StringBuilder(nantPattern);\r
171 \r
172             // NAnt patterns can use either / \ as a directory seperator.\r
173             // We must replace both of these characters with Path.DirectorySeperatorChar\r
174             pattern.Replace('/',  Path.DirectorySeparatorChar);\r
175             pattern.Replace('\\', Path.DirectorySeparatorChar);\r
176 \r
177             // Patterns MUST be full paths.\r
178             if (!Path.IsPathRooted(pattern.ToString())) {\r
179                 pattern = new StringBuilder(Path.Combine(BaseDirectory, pattern.ToString()));\r
180             }\r
181 \r
182             // The '\' character is a special character in regular expressions\r
183             // and must be escaped before doing anything else.\r
184             pattern.Replace(@"\", @"\\");\r
185 \r
186             // Escape the rest of the regular expression special characters.\r
187             // NOTE: Characters other than . $ ^ { [ ( | ) * + ? \ match themselves.\r
188             // TODO: Decide if ] and } are missing from this list, the above\r
189             // list of characters was taking from the .NET SDK docs.\r
190             pattern.Replace(".", @"\.");\r
191             pattern.Replace("$", @"\$");\r
192             pattern.Replace("^", @"\^");\r
193             pattern.Replace("{", @"\{");\r
194             pattern.Replace("[", @"\[");\r
195             pattern.Replace("(", @"\(");\r
196             pattern.Replace(")", @"\)");\r
197             pattern.Replace("+", @"\+");\r
198 \r
199             // Special case directory seperator string under Windows.\r
200             string seperator = Path.DirectorySeparatorChar.ToString();\r
201             if (seperator == @"\") {\r
202                 seperator = @"\\";\r
203             }\r
204 \r
205             // Convert NAnt pattern characters to regular expression patterns.\r
206 \r
207             // SPECIAL CASE: to match subdirectory OR current directory.  If\r
208             // we don't do this then we can write something like 'src/**/*.cs'\r
209             // to match all the files ending in .cs in the src directory OR\r
210             // subdirectories of src.\r
211             pattern.Replace(seperator + "**", "(" + seperator + ".|)|");\r
212 \r
213             // | is a place holder for * to prevent it from being replaced in next line\r
214             pattern.Replace("**", ".|");\r
215             pattern.Replace("*", "[^" + seperator + "]*");\r
216             pattern.Replace("?", "[^" + seperator + "]?");\r
217             pattern.Replace('|', '*'); // replace place holder string\r
218 \r
219             // Help speed up the search\r
220             pattern.Insert(0, '^'); // start of line\r
221             pattern.Append('$'); // end of line\r
222 \r
223             return pattern.ToString();\r
224         }\r
225     }\r
226 }\r