Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(307)

Side by Side Diff: third_party/coverage-3.6/coverage/files.py

Issue 14988009: First cut of testing infrastructure for recipes. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: nitfixen Created 7 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 """File wrangling."""
2
3 from coverage.backward import to_string
4 from coverage.misc import CoverageException
5 import fnmatch, os, os.path, re, sys
6
7 class FileLocator(object):
8 """Understand how filenames work."""
9
10 def __init__(self):
11 # The absolute path to our current directory.
12 self.relative_dir = os.path.normcase(abs_file(os.curdir) + os.sep)
13
14 # Cache of results of calling the canonical_filename() method, to
15 # avoid duplicating work.
16 self.canonical_filename_cache = {}
17
18 def relative_filename(self, filename):
19 """Return the relative form of `filename`.
20
21 The filename will be relative to the current directory when the
22 `FileLocator` was constructed.
23
24 """
25 fnorm = os.path.normcase(filename)
26 if fnorm.startswith(self.relative_dir):
27 filename = filename[len(self.relative_dir):]
28 return filename
29
30 def canonical_filename(self, filename):
31 """Return a canonical filename for `filename`.
32
33 An absolute path with no redundant components and normalized case.
34
35 """
36 if filename not in self.canonical_filename_cache:
37 if not os.path.isabs(filename):
38 for path in [os.curdir] + sys.path:
39 if path is None:
40 continue
41 f = os.path.join(path, filename)
42 if os.path.exists(f):
43 filename = f
44 break
45 cf = abs_file(filename)
46 self.canonical_filename_cache[filename] = cf
47 return self.canonical_filename_cache[filename]
48
49 def get_zip_data(self, filename):
50 """Get data from `filename` if it is a zip file path.
51
52 Returns the string data read from the zip file, or None if no zip file
53 could be found or `filename` isn't in it. The data returned will be
54 an empty string if the file is empty.
55
56 """
57 import zipimport
58 markers = ['.zip'+os.sep, '.egg'+os.sep]
59 for marker in markers:
60 if marker in filename:
61 parts = filename.split(marker)
62 try:
63 zi = zipimport.zipimporter(parts[0]+marker[:-1])
64 except zipimport.ZipImportError:
65 continue
66 try:
67 data = zi.get_data(parts[1])
68 except IOError:
69 continue
70 return to_string(data)
71 return None
72
73
74 if sys.platform == 'win32':
75
76 def actual_path(path):
77 """Get the actual path of `path`, including the correct case."""
78 if path in actual_path.cache:
79 return actual_path.cache[path]
80
81 head, tail = os.path.split(path)
82 if not tail:
83 actpath = head
84 elif not head:
85 actpath = tail
86 else:
87 head = actual_path(head)
88 if head in actual_path.list_cache:
89 files = actual_path.list_cache[head]
90 else:
91 try:
92 files = os.listdir(head)
93 except OSError:
94 files = []
95 actual_path.list_cache[head] = files
96 normtail = os.path.normcase(tail)
97 for f in files:
98 if os.path.normcase(f) == normtail:
99 tail = f
100 break
101 actpath = os.path.join(head, tail)
102 actual_path.cache[path] = actpath
103 return actpath
104
105 actual_path.cache = {}
106 actual_path.list_cache = {}
107
108 else:
109 def actual_path(filename):
110 """The actual path for non-Windows platforms."""
111 return filename
112
113 def abs_file(filename):
114 """Return the absolute normalized form of `filename`."""
115 path = os.path.abspath(os.path.realpath(filename))
116 path = actual_path(path)
117 return path
118
119
120 def prep_patterns(patterns):
121 """Prepare the file patterns for use in a `FnmatchMatcher`.
122
123 If a pattern starts with a wildcard, it is used as a pattern
124 as-is. If it does not start with a wildcard, then it is made
125 absolute with the current directory.
126
127 If `patterns` is None, an empty list is returned.
128
129 """
130 patterns = patterns or []
131 prepped = []
132 for p in patterns or []:
133 if p.startswith("*") or p.startswith("?"):
134 prepped.append(p)
135 else:
136 prepped.append(abs_file(p))
137 return prepped
138
139
140 class TreeMatcher(object):
141 """A matcher for files in a tree."""
142 def __init__(self, directories):
143 self.dirs = directories[:]
144
145 def __repr__(self):
146 return "<TreeMatcher %r>" % self.dirs
147
148 def add(self, directory):
149 """Add another directory to the list we match for."""
150 self.dirs.append(directory)
151
152 def match(self, fpath):
153 """Does `fpath` indicate a file in one of our trees?"""
154 for d in self.dirs:
155 if fpath.startswith(d):
156 if fpath == d:
157 # This is the same file!
158 return True
159 if fpath[len(d)] == os.sep:
160 # This is a file in the directory
161 return True
162 return False
163
164
165 class FnmatchMatcher(object):
166 """A matcher for files by filename pattern."""
167 def __init__(self, pats):
168 self.pats = pats[:]
169
170 def __repr__(self):
171 return "<FnmatchMatcher %r>" % self.pats
172
173 def match(self, fpath):
174 """Does `fpath` match one of our filename patterns?"""
175 for pat in self.pats:
176 if fnmatch.fnmatch(fpath, pat):
177 return True
178 return False
179
180
181 def sep(s):
182 """Find the path separator used in this string, or os.sep if none."""
183 sep_match = re.search(r"[\\/]", s)
184 if sep_match:
185 the_sep = sep_match.group(0)
186 else:
187 the_sep = os.sep
188 return the_sep
189
190
191 class PathAliases(object):
192 """A collection of aliases for paths.
193
194 When combining data files from remote machines, often the paths to source
195 code are different, for example, due to OS differences, or because of
196 serialized checkouts on continuous integration machines.
197
198 A `PathAliases` object tracks a list of pattern/result pairs, and can
199 map a path through those aliases to produce a unified path.
200
201 `locator` is a FileLocator that is used to canonicalize the results.
202
203 """
204 def __init__(self, locator=None):
205 self.aliases = []
206 self.locator = locator
207
208 def add(self, pattern, result):
209 """Add the `pattern`/`result` pair to the list of aliases.
210
211 `pattern` is an `fnmatch`-style pattern. `result` is a simple
212 string. When mapping paths, if a path starts with a match against
213 `pattern`, then that match is replaced with `result`. This models
214 isomorphic source trees being rooted at different places on two
215 different machines.
216
217 `pattern` can't end with a wildcard component, since that would
218 match an entire tree, and not just its root.
219
220 """
221 # The pattern can't end with a wildcard component.
222 pattern = pattern.rstrip(r"\/")
223 if pattern.endswith("*"):
224 raise CoverageException("Pattern must not end with wildcards.")
225 pattern_sep = sep(pattern)
226 pattern += pattern_sep
227
228 # Make a regex from the pattern. fnmatch always adds a \Z or $ to
229 # match the whole string, which we don't want.
230 regex_pat = fnmatch.translate(pattern).replace(r'\Z(', '(')
231 if regex_pat.endswith("$"):
232 regex_pat = regex_pat[:-1]
233 # We want */a/b.py to match on Windows to, so change slash to match
234 # either separator.
235 regex_pat = regex_pat.replace(r"\/", r"[\\/]")
236 # We want case-insensitive matching, so add that flag.
237 regex = re.compile(r"(?i)" + regex_pat)
238
239 # Normalize the result: it must end with a path separator.
240 result_sep = sep(result)
241 result = result.rstrip(r"\/") + result_sep
242 self.aliases.append((regex, result, pattern_sep, result_sep))
243
244 def map(self, path):
245 """Map `path` through the aliases.
246
247 `path` is checked against all of the patterns. The first pattern to
248 match is used to replace the root of the path with the result root.
249 Only one pattern is ever used. If no patterns match, `path` is
250 returned unchanged.
251
252 The separator style in the result is made to match that of the result
253 in the alias.
254
255 """
256 for regex, result, pattern_sep, result_sep in self.aliases:
257 m = regex.match(path)
258 if m:
259 new = path.replace(m.group(0), result)
260 if pattern_sep != result_sep:
261 new = new.replace(pattern_sep, result_sep)
262 if self.locator:
263 new = self.locator.canonical_filename(new)
264 return new
265 return path
266
267
268 def find_python_files(dirname):
269 """Yield all of the importable Python files in `dirname`, recursively.
270
271 To be importable, the files have to be in a directory with a __init__.py,
272 except for `dirname` itself, which isn't required to have one. The
273 assumption is that `dirname` was specified directly, so the user knows
274 best, but subdirectories are checked for a __init__.py to be sure we only
275 find the importable files.
276
277 """
278 for i, (dirpath, dirnames, filenames) in enumerate(os.walk(dirname)):
279 if i > 0 and '__init__.py' not in filenames:
280 # If a directory doesn't have __init__.py, then it isn't
281 # importable and neither are its files
282 del dirnames[:]
283 continue
284 for filename in filenames:
285 # We're only interested in files that look like reasonable Python
286 # files: Must end with .py, and must not have certain funny
287 # characters that probably mean they are editor junk.
288 if re.match(r"^[^.#~!$@%^&*()+=,]+\.py$", filename):
289 yield os.path.join(dirpath, filename)
OLDNEW
« no previous file with comments | « third_party/coverage-3.6/coverage/execfile.py ('k') | third_party/coverage-3.6/coverage/fullcoverage/encodings.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698