summaryrefslogtreecommitdiff
path: root/coverage/codeunit.py
diff options
context:
space:
mode:
Diffstat (limited to 'coverage/codeunit.py')
-rw-r--r--coverage/codeunit.py175
1 files changed, 165 insertions, 10 deletions
diff --git a/coverage/codeunit.py b/coverage/codeunit.py
index c58e237b..d9cd5e44 100644
--- a/coverage/codeunit.py
+++ b/coverage/codeunit.py
@@ -1,9 +1,11 @@
"""Code unit (module) handling for Coverage."""
-import glob, os
+import glob, os, re
from coverage.backward import open_source, string_class, StringIO
-from coverage.misc import CoverageException
+from coverage.misc import CoverageException, NoSource
+from coverage.parser import CodeParser, PythonParser
+from coverage.phystokens import source_token_lines, source_encoding
def code_unit_factory(morfs, file_locator):
@@ -29,7 +31,19 @@ def code_unit_factory(morfs, file_locator):
globbed.append(morf)
morfs = globbed
- code_units = [CodeUnit(morf, file_locator) for morf in morfs]
+ code_units = []
+ for morf in morfs:
+ # Hacked-in Mako support. Disabled for going onto trunk.
+ if 0 and isinstance(morf, string_class) and "/mako/" in morf:
+ # Super hack! Do mako both ways!
+ if 0:
+ cu = PythonCodeUnit(morf, file_locator)
+ cu.name += '_fako'
+ code_units.append(cu)
+ klass = MakoCodeUnit
+ else:
+ klass = PythonCodeUnit
+ code_units.append(klass(morf, file_locator))
return code_units
@@ -44,6 +58,7 @@ class CodeUnit(object):
`relative` is a boolean.
"""
+
def __init__(self, morf, file_locator):
self.file_locator = file_locator
@@ -51,11 +66,7 @@ class CodeUnit(object):
f = morf.__file__
else:
f = morf
- # .pyc files should always refer to a .py instead.
- if f.endswith(('.pyc', '.pyo')):
- f = f[:-1]
- elif f.endswith('$py.class'): # Jython
- f = f[:-9] + ".py"
+ f = self._adjust_filename(f)
self.filename = self.file_locator.canonical_filename(f)
if hasattr(morf, '__name__'):
@@ -99,7 +110,7 @@ class CodeUnit(object):
the same directory, but need to differentiate same-named files from
different directories.
- For example, the file a/b/c.py might return 'a_b_c'
+ For example, the file a/b/c.py will return 'a_b_c'
"""
if self.modname:
@@ -127,7 +138,68 @@ class CodeUnit(object):
def should_be_python(self):
"""Does it seem like this file should contain Python?
- This is used to decide if a file reported as part of the exection of
+ This is used to decide if a file reported as part of the execution of
+ a program was really likely to have contained Python in the first
+ place.
+ """
+ return False
+
+
+class PythonCodeUnit(CodeUnit):
+ """Represents a Python file."""
+
+ parser_class = PythonParser
+
+ def _adjust_filename(self, fname):
+ # .pyc files should always refer to a .py instead.
+ if fname.endswith(('.pyc', '.pyo')):
+ fname = fname[:-1]
+ elif fname.endswith('$py.class'): # Jython
+ fname = fname[:-9] + ".py"
+ return fname
+
+ def find_source(self, filename):
+ """Find the source for `filename`.
+
+ Returns two values: the actual filename, and the source.
+
+ The source returned depends on which of these cases holds:
+
+ * The filename seems to be a non-source file: returns None
+
+ * The filename is a source file, and actually exists: returns None.
+
+ * The filename is a source file, and is in a zip file or egg:
+ returns the source.
+
+ * The filename is a source file, but couldn't be found: raises
+ `NoSource`.
+
+ """
+ source = None
+
+ base, ext = os.path.splitext(filename)
+ TRY_EXTS = {
+ '.py': ['.py', '.pyw'],
+ '.pyw': ['.pyw'],
+ }
+ try_exts = TRY_EXTS.get(ext)
+ if not try_exts:
+ return filename, None
+
+ for try_ext in try_exts:
+ try_filename = base + try_ext
+ if os.path.exists(try_filename):
+ return try_filename, None
+ source = self.file_locator.get_zip_data(try_filename)
+ if source:
+ return try_filename, source
+ raise NoSource("No source for code: '%s'" % filename)
+
+ def should_be_python(self):
+ """Does it seem like this file should contain Python?
+
+ This is used to decide if a file reported as part of the execution of
a program was really likely to have contained Python in the first
place.
@@ -143,3 +215,86 @@ class CodeUnit(object):
return True
# Everything else is probably not Python.
return False
+
+ def source_token_lines(self, source):
+ return source_token_lines(source)
+
+ def source_encoding(self, source):
+ return source_encoding(source)
+
+
+def mako_template_name(py_filename):
+ with open(py_filename) as f:
+ py_source = f.read()
+
+ # Find the template filename. TODO: string escapes in the string.
+ m = re.search(r"^_template_filename = u?'([^']+)'", py_source, flags=re.MULTILINE)
+ if not m:
+ raise Exception("Couldn't find template filename in Mako file %r" % py_filename)
+ template_filename = m.group(1)
+ return template_filename
+
+
+class MakoParser(CodeParser):
+ def __init__(self, cu, text, filename, exclude):
+ self.cu = cu
+ self.text = text
+ self.filename = filename
+ self.exclude = exclude
+
+ def parse_source(self):
+ """Returns executable_line_numbers, excluded_line_numbers"""
+ with open(self.cu.filename) as f:
+ py_source = f.read()
+
+ # Get the line numbers.
+ self.py_to_html = {}
+ html_linenum = None
+ for linenum, line in enumerate(py_source.splitlines(), start=1):
+ m_source_line = re.search(r"^\s*# SOURCE LINE (\d+)$", line)
+ if m_source_line:
+ html_linenum = int(m_source_line.group(1))
+ else:
+ m_boilerplate_line = re.search(r"^\s*# BOILERPLATE", line)
+ if m_boilerplate_line:
+ html_linenum = None
+ elif html_linenum:
+ self.py_to_html[linenum] = html_linenum
+
+ return set(self.py_to_html.values()), set()
+
+ def translate_lines(self, lines):
+ tlines = set(self.py_to_html.get(l, -1) for l in lines)
+ tlines.remove(-1)
+ return tlines
+
+
+class MakoCodeUnit(CodeUnit):
+ parser_class = MakoParser
+
+ def __init__(self, *args, **kwargs):
+ super(MakoCodeUnit, self).__init__(*args, **kwargs)
+ self.mako_filename = mako_template_name(self.filename)
+
+ def source_file(self):
+ return open(self.mako_filename)
+
+ def find_source(self, filename):
+ """Find the source for `filename`.
+
+ Returns two values: the actual filename, and the source.
+
+ """
+ mako_filename = mako_template_name(filename)
+ with open(mako_filename) as f:
+ source = f.read()
+
+ return mako_filename, source
+
+ def source_token_lines(self, source):
+ """Return the 'tokenized' text for the code."""
+ for line in source.splitlines():
+ yield [('txt', line)]
+
+ def source_encoding(self, source):
+ return "utf-8"